469 lines
16 KiB
C
469 lines
16 KiB
C
/** library for Serial Wire Debug (SWD) communication
|
|
* @file
|
|
* @author King Kévin <kingkevin@cuvoodoo.info>
|
|
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
|
* @date 2018-2021
|
|
* @note peripherals used: timer @ref swd_timer, GPIO @ref swd_gpio
|
|
* @implements "ARM Debug Interface Architecture Specification ADIv6.0" (ARM IHI 0074A)
|
|
* @note this library implements DP architecture version 3 (DPv3), but only DPv1 feature could be tested.
|
|
* @implements The physical layer (electrical characteristic and timing) is described in "ARM DSTREAM System and Interface Design Reference Guide" (ARM DUI0499K)
|
|
*/
|
|
|
|
/* standard libraries */
|
|
#include <stdint.h> // standard integer types
|
|
#include <stdbool.h> // boolean type
|
|
#include <stddef.h> // NULL definition
|
|
|
|
/* STM32 (including CM3) libraries */
|
|
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
|
#include <libopencm3/cm3/nvic.h> // interrupt handler
|
|
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
|
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
|
#include <libopencm3/stm32/timer.h> // timer library
|
|
|
|
/* own libraries */
|
|
#include "global.h" // helper macros
|
|
#include "swd.h" // own definitions
|
|
|
|
/** @defgroup swd_gpio GPIO used for SWDIO and SWCLK
|
|
* @{
|
|
*/
|
|
#define SWD_SWCLK_PIN PB10 /**< default GPIO pin for clock signal */
|
|
#define SWD_SWDIO_PIN PB2 /**< default GPIO pin for data input/output signal */
|
|
static uint32_t swd_swclk_rcc = GPIO_RCC(SWD_SWCLK_PIN);
|
|
static uint32_t swd_swclk_port = GPIO_PORT(SWD_SWCLK_PIN);
|
|
static uint32_t swd_swclk_pin = GPIO_PIN(SWD_SWCLK_PIN);
|
|
static uint32_t swd_swdio_rcc = GPIO_RCC(SWD_SWDIO_PIN);
|
|
static uint32_t swd_swdio_port = GPIO_PORT(SWD_SWDIO_PIN);
|
|
static uint32_t swd_swdio_pin = GPIO_PIN(SWD_SWDIO_PIN);
|
|
/** @} */
|
|
|
|
/** @defgroup swd_timer timer used to generate a periodic clock
|
|
* @note the clock does not actually need to be periodic (it makes it just more readable)
|
|
* @{
|
|
*/
|
|
#define SWD_TIMER 2 /**< timer ID */
|
|
/** @} */
|
|
|
|
static volatile uint64_t swd_bits_out = 0; /** a buffer for the data bits to output/write (LSb first) */
|
|
static volatile uint64_t swd_bits_in = 0; /** a buffer for the data bits to input/read (LSB first) */
|
|
static volatile uint8_t swd_bits_count = 0; /** number of bits to read/write */
|
|
static volatile uint8_t swd_bits_i = 0; /** transaction bit index */
|
|
|
|
void swd_setup(uint32_t freq)
|
|
{
|
|
// setup GPIO
|
|
swd_set_pins(swd_swclk_port, swd_swclk_pin, swd_swdio_port, swd_swdio_pin);
|
|
|
|
// setup timer to generate periodic clock
|
|
rcc_periph_clock_enable(RCC_TIM(SWD_TIMER)); // enable clock for timer peripheral
|
|
rcc_periph_reset_pulse(RST_TIM(SWD_TIMER)); // reset timer state
|
|
timer_disable_counter(TIM(SWD_TIMER)); // disable timer to configure it
|
|
timer_set_mode(TIM(SWD_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
|
|
timer_set_prescaler(TIM(SWD_TIMER), 1 - 1); // don't use prescaler, allowing period down to 72 MHz / 2^16 = 1099 Hz
|
|
uint32_t period = rcc_ahb_frequency / freq / 2; // get period based on frequency (/2 because on cycle has 2 edges)
|
|
if (period > 0xffff) { // maximum frequency reached
|
|
period = 0xffff;
|
|
} else if (period > 0) { // not minimum frequency reached
|
|
period -= 1; // correct timer overflow offset
|
|
}
|
|
timer_set_period(TIM(SWD_TIMER), period); // set period the generate clock frequency (x2 for the two edges)
|
|
timer_clear_flag(TIM(SWD_TIMER), TIM_SR_UIF); // clear update (overflow) flag
|
|
timer_update_on_overflow(TIM(SWD_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
|
|
timer_enable_irq(TIM(SWD_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow to generate the clock signal
|
|
nvic_enable_irq(NVIC_TIM_IRQ(SWD_TIMER)); // catch interrupt in service routine
|
|
|
|
// reset variables
|
|
swd_bits_count = 0;
|
|
}
|
|
|
|
/** release used pins */
|
|
void swd_release_pins(void)
|
|
{
|
|
// release GPIO
|
|
gpio_mode_setup(swd_swclk_port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, swd_swclk_pin); // put clock signal pin back to input floating
|
|
gpio_mode_setup(swd_swdio_port, GPIO_MODE_INPUT, GPIO_PUPD_NONE, swd_swdio_pin); // put data signal pin back to input floating
|
|
}
|
|
|
|
void swd_release(void)
|
|
{
|
|
// release timer
|
|
timer_disable_counter(TIM(SWD_TIMER)); // disable timer
|
|
rcc_periph_reset_pulse(RST_TIM(SWD_TIMER)); // reset timer state
|
|
rcc_periph_clock_disable(RCC_TIM(SWD_TIMER)); // disable clock for timer peripheral
|
|
|
|
swd_release_pins(); // release GPIO
|
|
}
|
|
|
|
bool swd_set_pins(uint32_t swclk_port, uint32_t swclk_pin, uint32_t swdio_port, uint32_t swdio_pin)
|
|
{
|
|
// check if pin exists and is unique
|
|
if (__builtin_popcount(swclk_pin) != 1 || __builtin_popcount(swdio_pin) != 1) {
|
|
return false;
|
|
}
|
|
// check if pin really exists
|
|
if (swclk_pin > (1 << 15) || swdio_pin > (1 << 15)) {
|
|
return false;
|
|
}
|
|
// ensure pin is different
|
|
if (swclk_port == swdio_port && swclk_pin == swdio_pin) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t swclk_rcc = 0;
|
|
switch (swclk_port) {
|
|
case GPIOA:
|
|
swclk_rcc = RCC_GPIOA;
|
|
break;
|
|
case GPIOB:
|
|
swclk_rcc = RCC_GPIOB;
|
|
break;
|
|
case GPIOC:
|
|
swclk_rcc = RCC_GPIOC;
|
|
break;
|
|
case GPIOD:
|
|
swclk_rcc = RCC_GPIOD;
|
|
break;
|
|
case GPIOE:
|
|
swclk_rcc = RCC_GPIOE;
|
|
break;
|
|
case GPIOF:
|
|
swclk_rcc = RCC_GPIOF;
|
|
break;
|
|
case GPIOG:
|
|
swclk_rcc = RCC_GPIOG;
|
|
break;
|
|
default: // unknown port
|
|
return false;
|
|
}
|
|
uint32_t swdio_rcc = 0;
|
|
switch (swdio_port) {
|
|
case GPIOA:
|
|
swdio_rcc = RCC_GPIOA;
|
|
break;
|
|
case GPIOB:
|
|
swdio_rcc = RCC_GPIOB;
|
|
break;
|
|
case GPIOC:
|
|
swdio_rcc = RCC_GPIOC;
|
|
break;
|
|
case GPIOD:
|
|
swdio_rcc = RCC_GPIOD;
|
|
break;
|
|
case GPIOE:
|
|
swdio_rcc = RCC_GPIOE;
|
|
break;
|
|
case GPIOF:
|
|
swdio_rcc = RCC_GPIOF;
|
|
break;
|
|
case GPIOG:
|
|
swdio_rcc = RCC_GPIOG;
|
|
break;
|
|
default: // unknown port
|
|
return false;
|
|
}
|
|
|
|
swd_release_pins(); // release already set pins (not a problem even if not already used
|
|
|
|
// remember pins
|
|
swd_swclk_rcc = swclk_rcc;
|
|
swd_swclk_port = swclk_port;
|
|
swd_swclk_pin = swclk_pin;
|
|
swd_swdio_rcc = swdio_rcc;
|
|
swd_swdio_port = swdio_port;
|
|
swd_swdio_pin = swdio_pin;
|
|
|
|
// setup GPIO for clock and data signals
|
|
rcc_periph_clock_enable(swd_swclk_rcc); // enable clock for GPIO peripheral for clock signal
|
|
gpio_set(swd_swclk_port, swd_swclk_pin); // inactive clock is high
|
|
gpio_mode_setup(swd_swclk_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swclk_pin); // the host controls the clock
|
|
gpio_set_output_options(swd_swclk_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, swd_swclk_pin); // set SWCLK pin output as push-pull
|
|
rcc_periph_clock_enable(swd_swdio_rcc); // enable clock for GPIO peripheral for data signal
|
|
gpio_set(swd_swdio_port, swd_swdio_pin); // inactive data is high (resetting the target when clock is active)
|
|
gpio_mode_setup(swd_swdio_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swdio_pin); // the data signal is half duplex, with the host controlling who is driving the data signal when
|
|
gpio_set_output_options(swd_swdio_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, swd_swdio_pin); // set SWDIO pin output as push-pull
|
|
|
|
return true;
|
|
}
|
|
|
|
/** perform SWD transaction (one sequence of bits read or write)
|
|
* @param[in] output bits to write (LSb first)
|
|
* @param[in] bit_count number of bits to read/write
|
|
* @param[in] write true for write transaction, false for read transaction
|
|
* @return the bits read
|
|
*/
|
|
uint64_t swd_transaction(uint64_t output, uint8_t bit_count, bool write)
|
|
{
|
|
// sanity check
|
|
if (0 == bit_count) {
|
|
return ~0ULL;
|
|
}
|
|
// initialize read/write
|
|
if (write) {
|
|
gpio_mode_setup(swd_swdio_port, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, swd_swdio_pin); // we drive the data signal to output data
|
|
swd_bits_out = output; // set data to output
|
|
} else {
|
|
gpio_mode_setup(swd_swdio_port, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, swd_swdio_pin); // the target will drive the data signal, we just pull it up
|
|
swd_bits_out = ~0ULL; // set the value so we pull up
|
|
}
|
|
swd_bits_in = 0; // reset input buffer
|
|
swd_bits_count = bit_count; // set the number of bits to read/write
|
|
swd_bits_i = 0; // reset read index
|
|
// start transaction
|
|
timer_enable_counter(TIM(SWD_TIMER)); // start timer
|
|
while (swd_bits_i < swd_bits_count) { // wait until all bits are transmitted
|
|
__WFI(); // go to sleep
|
|
}
|
|
return swd_bits_out;
|
|
}
|
|
|
|
void swd_line_reset(void)
|
|
{
|
|
swd_transaction(~0ULL, 50 + 2, true); // sent high for at least 50 cycle to issue line reset and put target in reset state
|
|
}
|
|
|
|
void swd_jtag_to_swd(void)
|
|
{
|
|
swd_transaction(0xE79E, 16, true); // the sequence is a constant magic value
|
|
}
|
|
|
|
void swd_swd_to_jtag(void)
|
|
{
|
|
swd_transaction(0xE73C, 16, true); // the sequence is a constant magic value
|
|
}
|
|
|
|
void swd_idle_cycles(uint8_t nb)
|
|
{
|
|
swd_transaction(0, nb, true); // idle has data signal low
|
|
}
|
|
|
|
void swd_packet_request(bool apndp, uint8_t a, bool rnw)
|
|
{
|
|
uint8_t request = (1 << 0) | (0 << 6) | (1 << 7); // start, stop, and park bits are set
|
|
if (apndp) {
|
|
request |= (1 << 1); // set APnDP bit
|
|
}
|
|
if (rnw) {
|
|
request |= (1 << 2); // set RnW bit
|
|
}
|
|
request |= ((a & 0xc) << 1); // set A[3:2]
|
|
static const bool parity_lut[] = {false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false}; // true if the number of 1's is a 4-bit value is odd, false else
|
|
if (parity_lut[(request >> 1) & 0xf]) {
|
|
request |= (1 << 5); // set even parity bit
|
|
}
|
|
swd_transaction(request, 8, true); // write packet request
|
|
}
|
|
|
|
void swd_turnaround(uint8_t nb)
|
|
{
|
|
swd_transaction(~0ULL, nb, false); // switch to input to read and pull up
|
|
}
|
|
|
|
enum swd_ack_e swd_acknowledge_response(void)
|
|
{
|
|
return swd_transaction(~0ULL, 3, false) & 0x7; // read 3 bits
|
|
}
|
|
|
|
void swd_write(uint32_t wdata)
|
|
{
|
|
swd_transaction(wdata | ((0 == __builtin_parity(wdata)) ? (0ULL << 32) : (1ULL << 32)), 33, true); // set parity and send data
|
|
}
|
|
|
|
bool swd_read(uint32_t* rdata)
|
|
{
|
|
uint64_t data = swd_transaction(~0UL, 33, false); // read 33 bits
|
|
*rdata = data; // return the 32 bits of read data
|
|
return ((0==__builtin_parity(*rdata)) ? (0ULL << 32) : (1ULL << 32)) == (data & (1ULL << 32)); // check parity
|
|
}
|
|
|
|
void swd_jtag_to_ds(void)
|
|
{
|
|
swd_transaction(~0ULL, 5, true); // place JTAG TAP in TLR state
|
|
swd_transaction(0x33BBBBBA, 21, true); // send JTAG-to-DS select sequence
|
|
}
|
|
|
|
void swd_swd_to_ds(void)
|
|
{
|
|
swd_line_reset(); // place SWD TAP is reset state
|
|
swd_transaction(0xE3BC, 16, true); // send SWD-to-DS select sequence
|
|
}
|
|
|
|
void swd_selection_alert(enum swd_activation_code_e activation_code)
|
|
{
|
|
swd_transaction(~0ULL, 5, true); // ensure selection alert is detected
|
|
swd_transaction(0x6209F392, 32, true); // send selection alert sequence (part 1)
|
|
swd_transaction(0x86852D95, 32, true); // send selection alert sequence (part 2)
|
|
swd_transaction(0xE3DDAFE9, 32, true); // send selection alert sequence (part 3)
|
|
swd_transaction(0x19BC0EA2, 32, true); // send selection alert sequence (part 4)
|
|
swd_transaction(0, 4, true); // send 4 low cycles
|
|
// send activation code, if known
|
|
switch (activation_code) {
|
|
case SWD_ACTIVATION_CODE_JTAG:
|
|
swd_transaction(0, 24, true);
|
|
break;
|
|
case SWD_ACTIVATION_CODE_SWDP:
|
|
swd_transaction(0x58, 16, true);
|
|
break;
|
|
case SWD_ACTIVATION_CODE_JTAGDP:
|
|
swd_transaction(0x50, 16, true);
|
|
break;
|
|
default:
|
|
break; // unknown, but let the user issue it using swd_transaction
|
|
}
|
|
}
|
|
|
|
/** name of the manufacturer corresponding to its bank and id
|
|
* @implements JEDEC JEP106
|
|
* @note auto-generated and provided by the OpenOCD project ( https://sourceforge.net/p/openocd/code/ci/master/tree/src/helper/jep106.inc?format=raw )
|
|
*/
|
|
static const char * const swd_jep106[][126] = {
|
|
#include "jep106.inc"
|
|
};
|
|
|
|
const char *swd_jep106_manufacturer(uint8_t bank, uint8_t id)
|
|
{
|
|
// check id
|
|
if (id < 1 || id > 126) {
|
|
return "<invalid>";
|
|
}
|
|
|
|
// index is zero based
|
|
id--;
|
|
|
|
// check band and name
|
|
if (bank >= LENGTH(swd_jep106) || 0 == swd_jep106[bank][id]) {
|
|
return "<unknown>";
|
|
}
|
|
|
|
return swd_jep106[bank][id];
|
|
}
|
|
|
|
static const struct swd_partno_s {
|
|
uint16_t designer;
|
|
uint16_t partno;
|
|
uint16_t partno_mask;
|
|
const char* name;
|
|
} swd_partno[] = {
|
|
// based on https://developer.arm.com/docs/103489943/latest/what-is-the-id-code-of-a-cortex-m0-dap-or-cortex-m0-dap
|
|
{
|
|
.designer = 0x23B, // ARM
|
|
.partno = 0xBA00,
|
|
.partno_mask = 0xFF00,
|
|
.name = "CM3DAP", // used in Cortex-M3 and Cortex-M4
|
|
},
|
|
{
|
|
.designer = 0x23B, // ARM
|
|
.partno = 0xBB00,
|
|
.partno_mask = 0xFF00,
|
|
.name = "CM0DAP", // used in Cortex-M0
|
|
},
|
|
{
|
|
.designer = 0x23B, // ARM
|
|
.partno = 0xBC00,
|
|
.partno_mask = 0xFF00,
|
|
.name = "CM0PDAP", // used in Cortex-M0+
|
|
},
|
|
// based on RM0008 Reference manual, STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6412,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F10xxx low-density",
|
|
},
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6410,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F10xxx medium-density",
|
|
},
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6414,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F10xxx high-density",
|
|
},
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6430,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F10xxx XL-density",
|
|
},
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6418,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F10xxx connectivity line",
|
|
},
|
|
// based on RM0368 Reference manual, STM32F401xB/C and STM32F401xD/E advanced Arm®-based 32-bit MCUs
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6423,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F401xB/C",
|
|
},
|
|
{
|
|
.designer = 0x020, // ST
|
|
.partno = 0x6433,
|
|
.partno_mask = 0xFFFF,
|
|
.name = "STM32F401xD/E",
|
|
},
|
|
};
|
|
|
|
const char* swd_dpidr_partno(uint16_t designer, uint16_t partno)
|
|
{
|
|
uint32_t i = 0; // swd_partno index
|
|
// find matching part number
|
|
for (i = 0; i < LENGTH(swd_partno); i++) {
|
|
if (designer == swd_partno[i].designer && ((partno & swd_partno[i].partno_mask) == swd_partno[i].partno)) {
|
|
break;
|
|
}
|
|
}
|
|
if (i < LENGTH(swd_partno)) {
|
|
return swd_partno[i].name;
|
|
} else {
|
|
return "<unknown>";
|
|
}
|
|
}
|
|
|
|
/** interrupt service routine called for timer
|
|
*
|
|
* this is just acting as a shift register
|
|
* the host will write data on the falling edge (-5 ns < Tos < 5 ns), and read data just before the rising edge (Tis > 4 ns)
|
|
* the target will read and write data signal on clock rising edge
|
|
* this phase shift is not very clear in the standard, but explains the line turn-round cycle when switching between writing and reading.
|
|
*
|
|
* only this ISR should change the data and clock signal output
|
|
* the timer stops on rising edge when the last bit is sent
|
|
* @implements ARM DUI0499K 2.1.2 Serial Wire Debug
|
|
*/
|
|
void TIM_ISR(SWD_TIMER)(void)
|
|
{
|
|
static bool edge_falling = true;
|
|
if (timer_get_flag(TIM(SWD_TIMER), TIM_SR_UIF)) { // overflow update event happened
|
|
timer_clear_flag(TIM(SWD_TIMER), TIM_SR_UIF); // clear flag
|
|
if (swd_bits_i >= swd_bits_count) { // end of activity reached, and no data has been made available in time
|
|
timer_disable_counter(TIM(SWD_TIMER)); // disable timer
|
|
return; // nothing to do
|
|
}
|
|
if (edge_falling) { // falling edge: we output data
|
|
if (swd_bits_out & (1ULL << swd_bits_i)) { // output data, LSb first
|
|
gpio_set(swd_swdio_port, swd_swdio_pin); // output high
|
|
} else {
|
|
gpio_clear(swd_swdio_port, swd_swdio_pin); // output low
|
|
}
|
|
gpio_clear(swd_swclk_port, swd_swclk_pin); // output falling clock edge
|
|
} else { // rising edge: read data
|
|
if (gpio_get(swd_swdio_port, swd_swdio_pin)) { // read data
|
|
swd_bits_out |= (1ULL << swd_bits_i); // set bit
|
|
} else {
|
|
swd_bits_out &= ~(1ULL << swd_bits_i); // clear bit
|
|
}
|
|
swd_bits_i++;
|
|
gpio_set(swd_swclk_port, swd_swclk_pin); // output rising clock edge
|
|
}
|
|
edge_falling = !edge_falling; // remember opposite upcoming edge
|
|
} else { // no other interrupt should occur
|
|
while (true); // unhandled exception: wait for the watchdog to bite
|
|
}
|
|
}
|