stm32f1/lib/swd.c

411 lines
14 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 */
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 "<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;
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 "<unknown>";
}
}
/** 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
}
}