/** library for Serial Wire Debug (SWD) communication * @file * @author King Kévin * @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 // standard integer types #include // boolean type #include // NULL definition /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // interrupt handler #include // real-time control clock library #include // general purpose input output library #include // 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 */ static 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) { if (swclk_pin > (1 << 15) || swdio_pin > (1 << 15) || __builtin_popcount(swclk_pin) != 1 || __builtin_popcount(swdio_pin) != 1) { // check if pin exists and is unique 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+1, 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_transaction(~0ULL, 50+1, true); // 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 ""; } // index is zero based id--; // check band and name if (bank >= LENGTH(swd_jep106) || 0 == swd_jep106[bank][id]) { return ""; } return swd_jep106[bank][id]; } static const struct swd_partno_s { uint16_t designer; uint8_t partno; 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 = 0xBA, .name = "CM3DAP", // used in Cortex-M3 and Cortex-M4 }, { .designer = 0x23B, // ARM .partno = 0xBB, .name = "CM0DAP", // used in Cortex-M0 }, { .designer = 0x23B, // ARM .partno = 0xBC, .name = "CM0PDAP", // used in Cortex-M0+ }, }; const char* swd_dpidr_partno(uint16_t designer, uint8_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) { break; } } if (i < LENGTH(swd_partno)) { return swd_partno[i].name; } else { return ""; } } /** interrupt service routine called for timer * * this is just acting as a shift register * the host will write data on the falling edge, and read data just before the rising edge (we could also do it on the rising edge) * 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 } }