- Run out of SRAM, it goes quite a lot faster - Cache the DAP clock frequency - Read RDATA and write WDATA all in one go, and merge the turnaround into the ACK phase. Reduces the number of missed PIO SM cycles during an SWD transfer Also fix a compiler warning and demote the loglevel of some debug messages. Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
311 lines
9.5 KiB
C
311 lines
9.5 KiB
C
/*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include <pico/stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <hardware/clocks.h>
|
|
#include <hardware/gpio.h>
|
|
|
|
#include "led.h"
|
|
#include "picoprobe_config.h"
|
|
#include "probe.pio.h"
|
|
#include "tusb.h"
|
|
|
|
#define DIV_ROUND_UP(m, n) (((m) + (n) - 1) / (n))
|
|
|
|
// Only want to set / clear one gpio per event so go up in powers of 2
|
|
enum _dbg_pins {
|
|
DBG_PIN_WRITE = 1,
|
|
DBG_PIN_WRITE_WAIT = 2,
|
|
DBG_PIN_READ = 4,
|
|
DBG_PIN_PKT = 8,
|
|
};
|
|
|
|
CU_REGISTER_DEBUG_PINS(probe_timing)
|
|
|
|
// Uncomment to enable debug
|
|
//CU_SELECT_DEBUG_PINS(probe_timing)
|
|
|
|
#define PROBE_BUF_SIZE 8192
|
|
struct _probe {
|
|
// Total length
|
|
uint tx_len;
|
|
// Data back to host
|
|
uint8_t tx_buf[PROBE_BUF_SIZE];
|
|
|
|
// CMD / Data RX'd from
|
|
uint rx_len;
|
|
uint8_t rx_buf[PROBE_BUF_SIZE];
|
|
|
|
// PIO offset
|
|
uint offset;
|
|
|
|
uint cached_freq_khz;
|
|
};
|
|
|
|
static struct _probe probe;
|
|
|
|
enum PROBE_CMDS {
|
|
PROBE_INVALID = 0, // Invalid command
|
|
PROBE_WRITE_BITS = 1, // Host wants us to write bits
|
|
PROBE_READ_BITS = 2, // Host wants us to read bits
|
|
PROBE_SET_FREQ = 3, // Set TCK
|
|
PROBE_RESET = 4, // Reset all state
|
|
PROBE_TARGET_RESET = 5, // Reset target
|
|
};
|
|
|
|
struct __attribute__((__packed__)) probe_cmd_hdr {
|
|
uint8_t id;
|
|
uint8_t cmd;
|
|
uint32_t bits;
|
|
};
|
|
|
|
struct __attribute__((__packed__)) probe_pkt_hdr {
|
|
uint32_t total_packet_length;
|
|
};
|
|
|
|
void probe_set_swclk_freq(uint freq_khz) {
|
|
if (freq_khz != probe.cached_freq_khz) {
|
|
uint clk_sys_freq_khz = clock_get_hz(clk_sys) / 1000;
|
|
picoprobe_info("Set swclk freq %dKHz sysclk %dkHz\n", freq_khz, clk_sys_freq_khz);
|
|
// Worked out with saleae
|
|
uint32_t divider = clk_sys_freq_khz / freq_khz / 2;
|
|
pio_sm_set_clkdiv_int_frac(pio0, PROBE_SM, divider, 0);
|
|
probe.cached_freq_khz = freq_khz;
|
|
}
|
|
}
|
|
|
|
void probe_assert_reset(bool state)
|
|
{
|
|
/* Change the direction to out to drive pin to 0 or to in to emulate open drain */
|
|
gpio_set_dir(PROBE_PIN_RESET, state);
|
|
}
|
|
|
|
void probe_write_bits(uint bit_count, uint32_t data_byte) {
|
|
DEBUG_PINS_SET(probe_timing, DBG_PIN_WRITE);
|
|
pio_sm_put_blocking(pio0, PROBE_SM, bit_count - 1);
|
|
pio_sm_put_blocking(pio0, PROBE_SM, data_byte);
|
|
DEBUG_PINS_SET(probe_timing, DBG_PIN_WRITE_WAIT);
|
|
picoprobe_dump("Write %d bits 0x%x\n", bit_count, data_byte);
|
|
// Wait for pio to push garbage to rx fifo so we know it has finished sending
|
|
pio_sm_get_blocking(pio0, PROBE_SM);
|
|
DEBUG_PINS_CLR(probe_timing, DBG_PIN_WRITE_WAIT);
|
|
DEBUG_PINS_CLR(probe_timing, DBG_PIN_WRITE);
|
|
}
|
|
|
|
uint32_t probe_read_bits(uint bit_count) {
|
|
DEBUG_PINS_SET(probe_timing, DBG_PIN_READ);
|
|
pio_sm_put_blocking(pio0, PROBE_SM, bit_count - 1);
|
|
uint32_t data = pio_sm_get_blocking(pio0, PROBE_SM);
|
|
uint32_t data_shifted = data;
|
|
if (bit_count < 32) {
|
|
data_shifted = data >> (32 - bit_count);
|
|
}
|
|
|
|
picoprobe_dump("Read %d bits 0x%x (shifted 0x%x)\n", bit_count, data, data_shifted);
|
|
DEBUG_PINS_CLR(probe_timing, DBG_PIN_READ);
|
|
return data_shifted;
|
|
}
|
|
|
|
void probe_read_mode(void) {
|
|
pio_sm_exec(pio0, PROBE_SM, pio_encode_jmp(probe.offset + probe_offset_in_posedge));
|
|
while(pio0->dbg_padoe & (1 << PROBE_PIN_SWDIO));
|
|
}
|
|
|
|
void probe_write_mode(void) {
|
|
pio_sm_exec(pio0, PROBE_SM, pio_encode_jmp(probe.offset + probe_offset_out_negedge));
|
|
while(!(pio0->dbg_padoe & (1 << PROBE_PIN_SWDIO)));
|
|
}
|
|
|
|
void probe_init() {
|
|
// Funcsel pins
|
|
pio_gpio_init(pio0, PROBE_PIN_SWCLK);
|
|
pio_gpio_init(pio0, PROBE_PIN_SWDIO);
|
|
// Make sure SWDIO has a pullup on it. Idle state is high
|
|
gpio_pull_up(PROBE_PIN_SWDIO);
|
|
|
|
// Target reset pin: pull up, input to emulate open drain pin
|
|
gpio_pull_up(PROBE_PIN_RESET);
|
|
// gpio_init will leave the pin cleared and set as input
|
|
gpio_init(PROBE_PIN_RESET);
|
|
|
|
uint offset = pio_add_program(pio0, &probe_program);
|
|
probe.offset = offset;
|
|
|
|
pio_sm_config sm_config = probe_program_get_default_config(offset);
|
|
|
|
// Set SWCLK as a sideset pin
|
|
sm_config_set_sideset_pins(&sm_config, PROBE_PIN_SWCLK);
|
|
|
|
// Set SWDIO offset
|
|
sm_config_set_out_pins(&sm_config, PROBE_PIN_SWDIO, 1);
|
|
sm_config_set_set_pins(&sm_config, PROBE_PIN_SWDIO, 1);
|
|
sm_config_set_in_pins(&sm_config, PROBE_PIN_SWDIO);
|
|
|
|
// Set SWD and SWDIO pins as output to start. This will be set in the sm
|
|
pio_sm_set_consecutive_pindirs(pio0, PROBE_SM, PROBE_PIN_OFFSET, 2, true);
|
|
|
|
// shift output right, autopull off, autopull threshold
|
|
sm_config_set_out_shift(&sm_config, true, false, 0);
|
|
// shift input right as swd data is lsb first, autopush off
|
|
sm_config_set_in_shift(&sm_config, true, false, 0);
|
|
|
|
// Init SM with config
|
|
pio_sm_init(pio0, PROBE_SM, offset, &sm_config);
|
|
|
|
// Set up divisor
|
|
probe_set_swclk_freq(1000);
|
|
|
|
// Enable SM
|
|
pio_sm_set_enabled(pio0, PROBE_SM, 1);
|
|
|
|
// Jump to write program
|
|
probe_write_mode();
|
|
}
|
|
|
|
void probe_deinit(void)
|
|
{
|
|
probe_read_mode();
|
|
pio_sm_set_enabled(pio0, PROBE_SM, 0);
|
|
pio_remove_program(pio0, &probe_program, probe.offset);
|
|
}
|
|
|
|
void probe_handle_read(uint total_bits) {
|
|
picoprobe_debug("Read %d bits\n", total_bits);
|
|
probe_read_mode();
|
|
|
|
uint chunk;
|
|
uint bits = total_bits;
|
|
while (bits > 0) {
|
|
if (bits > 8) {
|
|
chunk = 8;
|
|
} else {
|
|
chunk = bits;
|
|
}
|
|
probe.tx_buf[probe.tx_len] = (uint8_t)probe_read_bits(chunk);
|
|
probe.tx_len++;
|
|
// Decrement remaining bits
|
|
bits -= chunk;
|
|
}
|
|
}
|
|
|
|
void probe_handle_write(uint8_t *data, uint total_bits) {
|
|
picoprobe_debug("Write %d bits\n", total_bits);
|
|
|
|
led_signal_activity(total_bits);
|
|
|
|
probe_write_mode();
|
|
|
|
uint chunk;
|
|
uint bits = total_bits;
|
|
while (bits > 0) {
|
|
if (bits > 8) {
|
|
chunk = 8;
|
|
} else {
|
|
chunk = bits;
|
|
}
|
|
|
|
probe_write_bits(chunk, (uint32_t)*data++);
|
|
bits -= chunk;
|
|
}
|
|
}
|
|
|
|
void probe_prepare_read_header(struct probe_cmd_hdr *hdr) {
|
|
// We have a read so need to prefix the data with the cmd header
|
|
if (probe.tx_len == 0) {
|
|
// Reserve some space for probe_pkt_hdr
|
|
probe.tx_len += sizeof(struct probe_pkt_hdr);
|
|
}
|
|
|
|
memcpy((void*)&probe.tx_buf[probe.tx_len], hdr, sizeof(struct probe_cmd_hdr));
|
|
probe.tx_len += sizeof(struct probe_cmd_hdr);
|
|
}
|
|
|
|
void probe_handle_pkt(void) {
|
|
uint8_t *pkt = &probe.rx_buf[0] + sizeof(struct probe_pkt_hdr);
|
|
uint remaining = probe.rx_len - sizeof(struct probe_pkt_hdr);
|
|
|
|
DEBUG_PINS_SET(probe_timing, DBG_PIN_PKT);
|
|
|
|
picoprobe_debug("Processing packet of length %d\n", probe.rx_len);
|
|
|
|
probe.tx_len = 0;
|
|
while (remaining) {
|
|
struct probe_cmd_hdr *hdr = (struct probe_cmd_hdr*)pkt;
|
|
uint data_bytes = DIV_ROUND_UP(hdr->bits, 8);
|
|
pkt += sizeof(struct probe_cmd_hdr);
|
|
remaining -= sizeof(struct probe_cmd_hdr);
|
|
|
|
if (hdr->cmd == PROBE_WRITE_BITS) {
|
|
uint8_t *data = pkt;
|
|
probe_handle_write(data, hdr->bits);
|
|
pkt += data_bytes;
|
|
remaining -= data_bytes;
|
|
} else if (hdr->cmd == PROBE_READ_BITS) {
|
|
probe_prepare_read_header(hdr);
|
|
probe_handle_read(hdr->bits);
|
|
} else if (hdr->cmd == PROBE_SET_FREQ) {
|
|
probe_set_swclk_freq(hdr->bits);
|
|
} else if (hdr->cmd == PROBE_RESET) {
|
|
// TODO: Is there anything to do after a reset?
|
|
// tx len and rx len should already be 0
|
|
;
|
|
} else if (hdr->cmd == PROBE_TARGET_RESET) {
|
|
probe_assert_reset(hdr->bits);
|
|
}
|
|
}
|
|
probe.rx_len = 0;
|
|
|
|
if (probe.tx_len) {
|
|
// Fill in total packet length before sending
|
|
struct probe_pkt_hdr *tx_hdr = (struct probe_pkt_hdr*)&probe.tx_buf[0];
|
|
tx_hdr->total_packet_length = probe.tx_len;
|
|
tud_vendor_write(&probe.tx_buf[0], probe.tx_len);
|
|
picoprobe_debug("Picoprobe wrote %d response bytes\n", probe.tx_len);
|
|
}
|
|
probe.tx_len = 0;
|
|
|
|
DEBUG_PINS_CLR(probe_timing, DBG_PIN_PKT);
|
|
}
|
|
|
|
// USB bits
|
|
void probe_task(void) {
|
|
if ( tud_vendor_available() ) {
|
|
uint count = tud_vendor_read(&probe.rx_buf[probe.rx_len], 64);
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
probe.rx_len += count;
|
|
}
|
|
|
|
if (probe.rx_len >= sizeof(struct probe_pkt_hdr)) {
|
|
struct probe_pkt_hdr *pkt_hdr = (struct probe_pkt_hdr*)&probe.rx_buf[0];
|
|
if (pkt_hdr->total_packet_length == probe.rx_len) {
|
|
probe_handle_pkt();
|
|
}
|
|
}
|
|
} |