Compare commits

..

19 Commits

Author SHA1 Message Date
Jonathan Bell
0b6f8db41a cdc_uart: support databits, stopbits and parity setup 2023-09-18 16:17:29 +01:00
Jonathan Bell
e51647492e cdc_uart: cater for Windows driver deficiencies
It's possible for the Windows CDC-ACM driver to ignore the IN endpoint
for long periods of time - multiple frames - if the host application
doesn't consume uart RX data. Boost buffer sizes to compensate.

Also prevent usb_thread from potentially being idle for a tick when
there's work to do.
2023-09-05 10:59:50 +01:00
Jonathan Bell
d13775ee72 Merge branch 'fix_debug' into 'master'
Fix ARM CMSIS-DAP issues

See merge request projectmu/picoprobe!1
2023-08-25 10:13:04 +01:00
Sunaabh Trivedi
58fa7a14cd Added a custom USB endpoint handler to process commands on a packet basis, as opposed to the byte FIFO employed previously. This allows multiple commmands to be framed correctly, so they can be processed sequentially without losing packets.
Suspend DAP thread until the end of the USB callback. This prevents the need for continous polling by DAP thread.
2023-08-24 17:15:47 +01:00
Jonathan Bell
1586ef0caa picoprobe version 1.0.3 2023-08-15 11:48:50 +01:00
Jonathan Bell
0761424821 probe: hook up reset functionality to DAP commands 2023-08-15 11:48:50 +01:00
Jonathan Bell
d47b3082f8 Fix debug prints
- The reset pin must move otherwise uart0 tx is squashed
- Don't preempt printf, it doesn't like it
- Set up the UART by default
2023-08-15 11:48:50 +01:00
P33M
6473166494 usb_descriptors: disable remote wake (#91)
see https://github.com/raspberrypi/picoprobe/issues/71
2023-08-09 13:13:36 +01:00
Luke Wren
3a1887ff06 Merge pull request #90 from raspberrypi/lurch-patch-1
Small README tweaks
2023-06-22 16:17:39 +01:00
Andrew Scheller
13b420d34c Small README tweaks 2023-06-22 15:56:45 +01:00
Roger Wolff
eb494103d4 buildsystem improvements to make it easier to build for debugprobe. (#87)
Merge documentation pull request from @rewolff
2023-06-22 13:50:25 +01:00
P33M
7de418cce3 Merge pull request #89 from raspberrypi/fix-deinit-without-init
Don't call probe_read_mode() in deinit() without matching prior init(), fixes #88
2023-06-18 11:00:24 +01:00
Luke Wren
0746b5a844 Don't call probe_read_mode() in deinit() without matching prior init(), fixes #88 2023-06-17 19:59:52 +01:00
P33M
d04ff3b472 Merge pull request #83 from raspberrypi/pio-program-improvements
PIO program improvements
2023-05-30 10:22:02 +01:00
Luke Wren
57f5569894 Update probe_oen.pio to use the same logic as the new probe.pio.
Fix a couple of compilation issues in the helpers for probe_oen.pio.
2023-05-09 13:36:10 +01:00
Luke Wren
ae5bdb082d Reduce SWCLK frequency from SM/2 to SM/4, to reduce dead cycles.
Also fix divider becoming 0 when extremely high SWCLK frequencies
are requested (this would have been safe but you would get an
extremely slow SWCLK).
2023-05-09 11:29:53 +01:00
Luke Wren
491b96c1d4 Update PIO program to pass read/write commands through FIFO
alongside bit counts. Also, don't return RX data on write commands.
These two changes allow the probe code to return early after
pushing write commands into the FIFO, which improves throughput.
2023-05-09 11:15:12 +01:00
Jonathan Bell
46eb924601 board configs: differentiate the USB product strings 2023-05-02 10:09:01 +01:00
Jonathan Bell
cdc33333c9 picoprobe_config: default should be the Pico, not Debug Probe 2023-05-02 10:00:44 +01:00
18 changed files with 561 additions and 137 deletions

View File

@@ -17,6 +17,7 @@ add_executable(picoprobe
src/cdc_uart.c
src/get_serial.c
src/sw_dp_pio.c
src/tusb_edpt_handler.c
)
target_sources(picoprobe PRIVATE
@@ -44,6 +45,17 @@ target_compile_definitions (picoprobe PRIVATE
PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1
)
option (DEBUGPROBE "compile for the debugprobe" OFF)
if (DEBUGPROBE)
target_compile_definitions (picoprobe PRIVATE
DEBUGPROBE=1
)
set_target_properties(picoprobe PROPERTIES
OUTPUT_NAME "debugprobe"
)
endif ()
target_link_libraries(picoprobe PRIVATE
pico_multicore
pico_stdlib

View File

@@ -4,6 +4,36 @@ Picoprobe allows a Pico / RP2040 to be used as USB -> SWD and UART bridge. This
# Documentation
Picoprobe documentation can be found in the [Pico Getting Started Guide](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf). See "Appendix A: Using Picoprobe".
# Hacking
For the purpose of making changes or studying of the code, you may want to compile the code yourself.
To compile this project firstly initialize and update the submodules:
```
git submodule update --init
```
then create and switch to the build directory:
```
mkdir build
cd build
```
then run cmake and build the code:
```
cmake ..
make
```
Done! You should now have a `picoprobe.uf2` that you can upload to your Pico in the normal way.
If you want to create the version that runs on the Raspberry Pi Debug Probe, then you need to invoke `cmake` in the sequence above with the `DEBUGPROBE=ON` option:
```
cmake -DDEBUGPROBE=ON ..
```
This will build with the configuration for the Debug Probe and call the output program `debugprobe.uf2`, as opposed to `picoprobe.uf2` for the vanilla version.
Note that if you first ran through the whole sequence to compile for the Pico, then you don't need to start back at the top. You can just go back to the `cmake` step and start from there.
# TODO
- TinyUSB's vendor interface is FIFO-based and not packet-based. Using raw tx/rx callbacks is preferable as this stops DAP command batches from being concatenated, which confused openOCD.
- Instead of polling, move the DAP thread to an asynchronously started/stopped one-shot operation to reduce CPU wakeups

View File

@@ -460,7 +460,11 @@ __STATIC_FORCEINLINE void PIN_nTRST_OUT (uint32_t bit) {
\return Current status of the nRESET DAP hardware I/O pin.
*/
__STATIC_FORCEINLINE uint32_t PIN_nRESET_IN (void) {
#ifdef PROBE_PIN_RESET
return probe_reset_level();
#else
return (0U);
#endif
}
/** nRESET I/O pin: Set Output.
@@ -469,7 +473,11 @@ __STATIC_FORCEINLINE uint32_t PIN_nRESET_IN (void) {
- 1: release device hardware reset.
*/
__STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) {
;
#ifdef PROBE_PIN_RESET
probe_assert_reset(!!bit);
#else
(void) bit;
#endif
}
///@}

View File

@@ -50,4 +50,6 @@
#define PICOPROBE_UART_RX_LED 7
#define PICOPROBE_UART_TX_LED 8
#endif
#define PROBE_PRODUCT_STRING "Debug Probe (CMSIS-DAP)"
#endif

View File

@@ -38,7 +38,7 @@
/* Include CDC interface to bridge to target UART. Omit if not used. */
#define PROBE_CDC_UART
/* Target reset GPIO (active-low). Omit if not used.*/
#define PROBE_PIN_RESET 0
#define PROBE_PIN_RESET 1
#define PROBE_SM 0
#define PROBE_PIN_OFFSET 12
@@ -68,9 +68,6 @@
#define PICOPROBE_UART_RX 5
#define PICOPROBE_UART_INTERFACE uart1
#define PICOPROBE_UART_BAUDRATE 115200
/* Flow control - some or all of these can be omitted if not used */
#define PICOPROBE_UART_RTS 9
#define PICOPROBE_UART_DTR 10
#endif
/* LED config - some or all of these can be omitted if not used */
@@ -80,4 +77,6 @@
#define PICOPROBE_UART_RX_LED 7
#define PICOPROBE_UART_TX_LED 8
#define PROBE_PRODUCT_STRING "Example Debug Probe"
#endif

View File

@@ -35,7 +35,9 @@
#define PROBE_PIN_SWCLK (PROBE_PIN_OFFSET + 0) // 2
#define PROBE_PIN_SWDIO (PROBE_PIN_OFFSET + 1) // 3
// Target reset config
#define PROBE_PIN_RESET 0
#if false
#define PROBE_PIN_RESET 1
#endif
// UART config
#define PICOPROBE_UART_TX 4
@@ -45,4 +47,6 @@
#define PICOPROBE_USB_CONNECTED_LED 25
#define PROBE_PRODUCT_STRING "Picoprobe (CMSIS-DAP)"
#endif

View File

@@ -34,8 +34,9 @@
TaskHandle_t uart_taskhandle;
TickType_t last_wake, interval = 100;
static uint8_t tx_buf[CFG_TUD_CDC_TX_BUFSIZE];
static uint8_t rx_buf[CFG_TUD_CDC_RX_BUFSIZE];
/* Max 1 FIFO worth of data */
static uint8_t tx_buf[32];
static uint8_t rx_buf[32];
// Actually s^-1 so 25ms
#define DEBOUNCE_MS 40
static uint debounce_ticks = 5;
@@ -54,22 +55,12 @@ void cdc_uart_init(void) {
gpio_set_pulls(PICOPROBE_UART_TX, 1, 0);
gpio_set_pulls(PICOPROBE_UART_RX, 1, 0);
uart_init(PICOPROBE_UART_INTERFACE, PICOPROBE_UART_BAUDRATE);
#ifdef PICOPROBE_UART_RTS
gpio_init(PICOPROBE_UART_RTS);
gpio_set_dir(PICOPROBE_UART_RTS, GPIO_OUT);
gpio_put(PICOPROBE_UART_RTS, 1);
#endif
#ifdef PICOPROBE_UART_DTR
gpio_init(PICOPROBE_UART_DTR);
gpio_set_dir(PICOPROBE_UART_DTR, GPIO_OUT);
gpio_put(PICOPROBE_UART_DTR, 1);
#endif
}
void cdc_task(void)
{
static int was_connected = 0;
static uint cdc_tx_oe = 0;
uint rx_len = 0;
// Consume uart fifo regardless even if not connected
@@ -88,6 +79,9 @@ void cdc_task(void)
rx_led_debounce = debounce_ticks;
#endif
written = MIN(tud_cdc_write_available(), rx_len);
if (rx_len > written)
cdc_tx_oe++;
if (written > 0) {
tud_cdc_write(rx_buf, written);
tud_cdc_write_flush();
@@ -124,6 +118,7 @@ void cdc_task(void)
} else if (was_connected) {
tud_cdc_write_clear();
was_connected = 0;
cdc_tx_oe = 0;
}
}
@@ -142,6 +137,8 @@ void cdc_thread(void *ptr)
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
{
uart_parity_t parity;
uint data_bits, stop_bits;
/* Set the tick thread interval to the amount of time it takes to
* fill up half a FIFO. Millis is too coarse for integer divide.
*/
@@ -150,24 +147,62 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
vTaskSuspend(uart_taskhandle);
interval = MAX(1, micros / ((1000 * 1000) / configTICK_RATE_HZ));
debounce_ticks = MAX(1, configTICK_RATE_HZ / (interval * DEBOUNCE_MS));
picoprobe_info("New baud rate %d micros %d interval %u\n",
picoprobe_info("New baud rate %ld micros %ld interval %lu\n",
line_coding->bit_rate, micros, interval);
uart_deinit(PICOPROBE_UART_INTERFACE);
tud_cdc_write_clear();
tud_cdc_read_flush();
uart_init(PICOPROBE_UART_INTERFACE, line_coding->bit_rate);
switch (line_coding->parity) {
case CDC_LINE_CODING_PARITY_ODD:
parity = UART_PARITY_ODD;
break;
case CDC_LINE_CODING_PARITY_EVEN:
parity = UART_PARITY_EVEN;
break;
default:
picoprobe_info("invalid parity setting %u\n", line_coding->parity);
/* fallthrough */
case CDC_LINE_CODING_PARITY_NONE:
parity = UART_PARITY_NONE;
break;
}
switch (line_coding->data_bits) {
case 5:
case 6:
case 7:
case 8:
data_bits = line_coding->data_bits;
break;
default:
picoprobe_info("invalid data bits setting: %u\n", line_coding->data_bits);
data_bits = 8;
break;
}
/* The PL011 only supports 1 or 2 stop bits. 1.5 stop bits is translated to 2,
* which is safer than the alternative. */
switch (line_coding->stop_bits) {
case CDC_LINE_CONDING_STOP_BITS_1_5:
case CDC_LINE_CONDING_STOP_BITS_2:
stop_bits = 2;
break;
default:
picoprobe_info("invalid stop bits setting: %u\n", line_coding->stop_bits);
/* fallthrough */
case CDC_LINE_CONDING_STOP_BITS_1:
stop_bits = 1;
break;
}
uart_set_format(PICOPROBE_UART_INTERFACE, data_bits, stop_bits, parity);
vTaskResume(uart_taskhandle);
}
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
#ifdef PICOPROBE_UART_RTS
gpio_put(PICOPROBE_UART_RTS, !rts);
#endif
#ifdef PICOPROBE_UART_DTR
gpio_put(PICOPROBE_UART_DTR, !dtr);
#endif
/* CDC drivers use linestate as a bodge to activate/deactivate the interface.
* Resume our UART polling on activate, stop on deactivate */
if (!dtr && !rts) {

View File

@@ -39,6 +39,7 @@
#include "cdc_uart.h"
#include "get_serial.h"
#include "led.h"
#include "tusb_edpt_handler.h"
#include "DAP.h"
// UART0 for Picoprobe debug
@@ -53,10 +54,12 @@ static uint8_t RxDataBuffer[CFG_TUD_HID_EP_BUFSIZE];
#define TUD_TASK_PRIO (tskIDLE_PRIORITY + 2)
#define DAP_TASK_PRIO (tskIDLE_PRIORITY + 1)
static TaskHandle_t dap_taskhandle, tud_taskhandle;
TaskHandle_t dap_taskhandle, tud_taskhandle;
void usb_thread(void *ptr)
{
TickType_t wake;
wake = xTaskGetTickCount();
do {
tud_task();
#ifdef PICOPROBE_USB_CONNECTED_LED
@@ -65,8 +68,9 @@ void usb_thread(void *ptr)
else
gpio_put(PICOPROBE_USB_CONNECTED_LED, 0);
#endif
// Trivial delay to save power
vTaskDelay(1);
// Go to sleep for up to a tick if nothing to do
if (!tud_task_event_ready())
xTaskDelayUntil(&wake, 1);
} while (1);
}
@@ -75,22 +79,6 @@ void usb_thread(void *ptr)
#define tud_vendor_flush(x) ((void)0)
#endif
void dap_thread(void *ptr)
{
uint32_t resp_len;
do {
if (tud_vendor_available()) {
tud_vendor_read(RxDataBuffer, sizeof(RxDataBuffer));
resp_len = DAP_ProcessCommand(RxDataBuffer, TxDataBuffer);
tud_vendor_write(TxDataBuffer, resp_len);
tud_vendor_flush();
} else {
// Trivial delay to save power
vTaskDelay(1);
}
} while (1);
}
int main(void) {
board_init();
@@ -99,6 +87,7 @@ int main(void) {
tusb_init();
DAP_Setup();
stdio_uart_init();
led_init();

View File

@@ -26,29 +26,50 @@
#ifndef PICOPROBE_H_
#define PICOPROBE_H_
#include "FreeRTOS.h"
#include "task.h"
#if false
#define picoprobe_info(format,args...) printf(format, ## args)
#define picoprobe_info(format,args...) \
do { \
vTaskSuspendAll(); \
printf(format, ## args); \
xTaskResumeAll(); \
} while (0)
#else
#define picoprobe_info(format,...) ((void)0)
#endif
#if false
#define picoprobe_debug(format,args...) printf(format, ## args)
#define picoprobe_debug(format,args...) \
do { \
vTaskSuspendAll(); \
printf(format, ## args); \
xTaskResumeAll(); \
} while (0)
#else
#define picoprobe_debug(format,...) ((void)0)
#endif
#if false
#define picoprobe_dump(format,args...) printf(format, ## args)
#define picoprobe_dump(format,args...)\
do { \
vTaskSuspendAll(); \
printf(format, ## args); \
xTaskResumeAll(); \
} while (0)
#else
#define picoprobe_dump(format,...) ((void)0)
#endif
// TODO tie this up with PICO_BOARD defines in the main SDK
//#include "board_pico_config.h"
#ifndef DEBUGPROBE
#include "board_pico_config.h"
#else
#include "board_debugprobe_config.h"
#endif
//#include "board_example_config.h"

View File

@@ -62,8 +62,9 @@ static struct _probe probe;
void probe_set_swclk_freq(uint 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;
uint32_t divider = clk_sys_freq_khz / freq_khz / 4;
if (divider == 0)
divider = 1;
pio_sm_set_clkdiv_int_frac(pio0, PROBE_SM, divider, 0);
}
@@ -75,21 +76,48 @@ void probe_assert_reset(bool state)
#endif
}
int probe_reset_level(void)
{
#if defined(PROBE_PIN_RESET)
return gpio_get(PROBE_PIN_RESET);
#else
return 0;
#endif
}
typedef enum probe_pio_command {
CMD_WRITE = 0,
CMD_SKIP,
CMD_TURNAROUND,
CMD_READ
} probe_pio_command_t;
static inline uint32_t fmt_probe_command(uint bit_count, bool out_en, probe_pio_command_t cmd) {
uint cmd_addr =
cmd == CMD_WRITE ? probe.offset + probe_offset_write_cmd :
cmd == CMD_SKIP ? probe.offset + probe_offset_get_next_cmd :
cmd == CMD_TURNAROUND ? probe.offset + probe_offset_turnaround_cmd :
probe.offset + probe_offset_read_cmd;
return ((bit_count - 1) & 0xff) | ((uint)out_en << 8) | (cmd_addr << 9);
}
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, fmt_probe_command(bit_count, true, CMD_WRITE));
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);
// Return immediately so we can cue up the next command whilst this one runs
DEBUG_PINS_CLR(probe_timing, DBG_PIN_WRITE);
}
void probe_hiz_clocks(uint bit_count) {
pio_sm_put_blocking(pio0, PROBE_SM, fmt_probe_command(bit_count, false, CMD_TURNAROUND));
pio_sm_put_blocking(pio0, PROBE_SM, 0);
}
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);
pio_sm_put_blocking(pio0, PROBE_SM, fmt_probe_command(bit_count, false, CMD_READ));
uint32_t data = pio_sm_get_blocking(pio0, PROBE_SM);
uint32_t data_shifted = data;
if (bit_count < 32) {
@@ -101,14 +129,20 @@ uint32_t probe_read_bits(uint bit_count) {
return data_shifted;
}
static void probe_wait_idle() {
pio0->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + PROBE_SM);
while (!(pio0->fdebug & (1u << (PIO_FDEBUG_TXSTALL_LSB + PROBE_SM))))
;
}
void probe_read_mode(void) {
pio_sm_exec(pio0, PROBE_SM, pio_encode_jmp(probe.offset + probe_offset_in_posedge));
while(pio_sm_get_pc(pio0, PROBE_SM) != probe.offset + probe_offset_in_idle);
pio_sm_put_blocking(pio0, PROBE_SM, fmt_probe_command(0, false, CMD_SKIP));
probe_wait_idle();
}
void probe_write_mode(void) {
pio_sm_exec(pio0, PROBE_SM, pio_encode_jmp(probe.offset + probe_offset_out_negedge));
while(pio_sm_get_pc(pio0, PROBE_SM) != probe.offset + probe_offset_out_idle);
pio_sm_put_blocking(pio0, PROBE_SM, fmt_probe_command(0, true, CMD_SKIP));
probe_wait_idle();
}
void probe_init() {
@@ -123,19 +157,17 @@ void probe_init() {
// Set up divisor
probe_set_swclk_freq(1000);
// Enable SM
// Jump SM to command dispatch routine, and enable it
pio_sm_exec(pio0, PROBE_SM, offset + probe_offset_get_next_cmd);
pio_sm_set_enabled(pio0, PROBE_SM, 1);
probe.initted = 1;
}
// Jump to write program
probe_write_mode();
}
void probe_deinit(void)
{
probe_read_mode();
if (probe.initted) {
probe_read_mode();
pio_sm_set_enabled(pio0, PROBE_SM, 0);
pio_remove_program(pio0, &probe_program, probe.offset);
probe.initted = 0;

View File

@@ -35,13 +35,18 @@
#endif
void probe_set_swclk_freq(uint freq_khz);
// Bit counts in the range 1..256
void probe_write_bits(uint bit_count, uint32_t data_byte);
uint32_t probe_read_bits(uint bit_count);
void probe_hiz_clocks(uint bit_count);
void probe_read_mode(void);
void probe_write_mode(void);
void probe_init(void);
void probe_deinit(void);
void probe_assert_reset(bool state);
int probe_reset_level(void);
#endif

View File

@@ -1,7 +1,7 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
* Copyright (c) 2021-2023 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
@@ -23,32 +23,50 @@
*
*/
// Every TX FIFO entry is either a command, or up to 32 bits of data.
// Command format:
//
// | 13:9 | 8 | 7:0 |
// | Cmd | Dir | Count |
//
// Count is the number of bits to be transferred by this command, minus 1.
// Dir is the output enable for the SWDIO pin.
// Cmd is the address of the write_cmd, read_cmd or get_next_cmd label.
//
// write_cmd expects a FIFO data entry, but read_cmd does not.
//
// read_cmd pushes data to the FIFO, but write_cmd does not. (The lack of RX
// garbage on writes allows the interface code to return early after pushing a
// write command, as there is no need in general to poll for a command's
// completion as long as all commands are executed in order.)
//
// The SWCLK period is 4 PIO SM execution cycles.
.program probe
.side_set 1 opt
public out_negedge:
set pindirs, 1 side 0x0 ; Init OE clock 0
public out_idle:
pull ; Pull number of bits to shift -1 from tx fifo and put into output shift register
mov x, osr ; mov bits to shift -1 from output shift register into x
pull ; Pull data to shift out
out_negedge_bitloop:
out pins, 1 side 0x0 ; clock data out on falling edge
jmp x-- out_negedge_bitloop side 0x1 ; data is present for posedge
set pins, 1 side 0x0 ; Drive data high (idle bus state)
push ; Push to rx fifo just so processor knows when done
jmp out_negedge ; Wait for next transaction
public write_cmd:
public turnaround_cmd: ; Alias of write, used for probe_oen.pio
pull
write_bitloop:
out pins, 1 [1] side 0x0 ; Data is output by host on negedge
jmp x-- write_bitloop [1] side 0x1 ; ...and captured by target on posedge
; Fall through to next command
.wrap_target
public get_next_cmd:
pull side 0x0 ; SWCLK is initially low
out x, 8 ; Get bit count
out pindirs, 1 ; Set SWDIO direction
out pc, 5 ; Go to command routine
read_bitloop:
nop ; Additional delay on taken loop branch
public read_cmd:
in pins, 1 [1] side 0x1 ; Data is captured by host on posedge
jmp x-- read_bitloop side 0x0
push
.wrap ; Wrap to next command
public in_posedge:
set pindirs, 0 side 0x0 ; INIT IE clock 0
public in_idle:
pull ; Pull number of bits to shift -1 from tx fifo and put into output shift register
mov x, osr ; mov bits to shift -1 from output shift register into x into x
in_posedge_bitloop:
in pins, 1 side 0x1 ; Generate posedge and read data
jmp x-- in_posedge_bitloop side 0x0 ;
push ; Push to rx fifo when done
jmp in_posedge ; Jump back to start
; Implement probe_gpio_init() and probe_sm_init() methods here - set pins, offsets, sidesets etc
% c-sdk {

View File

@@ -1,39 +1,48 @@
; Output-enable active-low variant of the SWD probe
; This program is very similar to the one in probe.pio. The only difference is
; that here write_cmd and turnaround_cmd are split into two separate routines,
; whose difference is OEn being high/low.
; SWDIO_OEn is pin 0, SWCLK pin 1, SWDIO (out) pin 2, SWDI (in) pin 3.
; Pin 0 and 1 are sideset pins
.program probe
.side_set 2 opt
; SWDIO_OEN is pin 0, SWCLK pin 1, SWDIO (out) pin 2, SWDI (in) pin 3.
; Pin 0 and 1 are sideset pins
public out_negedge:
set pindirs, 0x1 side 0x0 ; OE_N 0, data high, clock 0
public out_idle:
pull ; pull nbits - 1
mov x, osr
pull ; pull data
public out_negedge_bitloop:
out pins, 1 side 0x0
jmp x-- out_negedge_bitloop side 0x2 ; OE_N 0, clock high
set pins, 1 side 0x0 ; drive data high (idle bus state)
push ; Push to rx fifo just so processor knows when done
jmp out_negedge ; Wait for next transaction
public turnaround_cmd:
pull
turnaround_bitloop:
nop [1] side 0x1
jmp x-- turnaround_bitloop [1] side 0x3
jmp get_next_cmd
public in_posedge:
set pindirs, 0x0 side 0x1 ; OE_N 1, data high, clock 0
public in_idle:
pull
mov x, osr
in_posedge_bitloop:
in pins, 1 side 0x1 ; OE_N 1, clock 0
jmp x-- in_posedge_bitloop side 0x3 ; OE_N 1, clock 1
push
jmp in_posedge
public write_cmd:
pull
write_bitloop:
out pins, 1 [1] side 0x0 ; Data is output by host on negedge
jmp x-- write_bitloop [1] side 0x2 ; ...and captured by target on posedge
; Fall through to next command
.wrap_target
public get_next_cmd:
pull side 0x1 ; SWCLK initially low, OEn disabled
out x, 8 ; Get bit count
out pindirs, 1 ; Set SWDIO direction
out pc, 5 ; Go to command routine
read_bitloop:
nop ; Additional delay on taken loop branch
public read_cmd:
in pins, 1 [1] side 0x3 ; Data is captured by host on posedge
jmp x-- read_bitloop side 0x1
push
.wrap ; Wrap to next command
; Implement probe_gpio_init() and probe_sm_init() methods here - set pins, offsets, sidesets etc
% c-sdk {
void probe_gpio_init()
static inline void probe_gpio_init()
{
#if defined(PROBE_PIN_RESET)
// Target reset pin: pull up, input to emulate open drain pin
@@ -51,10 +60,10 @@ void probe_gpio_init()
gpio_pull_up(PROBE_PIN_SWDIOEN);
}
void probe_sm_init(pio_sm_config* sm_config) {
static inline void probe_sm_init(pio_sm_config* sm_config) {
// Set SWDIOEN and SWCLK as sideset pins
sm_config_set_sideset_pins(&sm_config, PROBE_PIN_SWDIOEN);
sm_config_set_sideset_pins(sm_config, PROBE_PIN_SWDIOEN);
// Set SWDIO offset
sm_config_set_out_pins(sm_config, PROBE_PIN_SWDIO, 1);
@@ -70,4 +79,4 @@ void probe_sm_init(pio_sm_config* sm_config) {
sm_config_set_in_shift(sm_config, true, false, 0);
}
%}
%}

View File

@@ -133,8 +133,6 @@ uint8_t SWD_Transfer (uint32_t request, uint32_t *data) {
probe_write_bits(8, prq);
/* Turnaround (ignore read bits) */
probe_read_mode();
ack = probe_read_bits(DAP_Data.swd_conf.turnaround + 3);
ack >>= DAP_Data.swd_conf.turnaround;
@@ -154,12 +152,10 @@ uint8_t SWD_Transfer (uint32_t request, uint32_t *data) {
picoprobe_debug("Read %02x ack %02x 0x%08x parity %01x\n",
prq, ack, val, bit);
/* Turnaround for line idle */
probe_read_bits(DAP_Data.swd_conf.turnaround);
probe_write_mode();
probe_hiz_clocks(DAP_Data.swd_conf.turnaround);
} else {
/* Turnaround for write */
probe_read_bits(DAP_Data.swd_conf.turnaround);
probe_write_mode();
probe_hiz_clocks(DAP_Data.swd_conf.turnaround);
/* Write WDATA[0:31] */
val = *data;
@@ -178,9 +174,9 @@ uint8_t SWD_Transfer (uint32_t request, uint32_t *data) {
/* Idle cycles - drive 0 for N clocks */
if (DAP_Data.transfer.idle_cycles) {
for (n = DAP_Data.transfer.idle_cycles; n; ) {
if (n > 32) {
probe_write_bits(32, 0);
n -= 32;
if (n > 256) {
probe_write_bits(256, 0);
n -= 256;
} else {
probe_write_bits(n, 0);
n -= n;
@@ -195,8 +191,7 @@ uint8_t SWD_Transfer (uint32_t request, uint32_t *data) {
/* Dummy Read RDATA[0:31] + Parity */
probe_read_bits(33);
}
probe_read_bits(DAP_Data.swd_conf.turnaround);
probe_write_mode();
probe_hiz_clocks(DAP_Data.swd_conf.turnaround);
if (DAP_Data.swd_conf.data_phase && ((request & DAP_TRANSFER_RnW) == 0U)) {
/* Dummy Write WDATA[0:31] + Parity */
probe_write_bits(32, 0);
@@ -209,7 +204,6 @@ uint8_t SWD_Transfer (uint32_t request, uint32_t *data) {
n = DAP_Data.swd_conf.turnaround + 32U + 1U;
/* Back off data phase */
probe_read_bits(n);
probe_write_mode();
return ((uint8_t)ack);
}

View File

@@ -68,8 +68,15 @@
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 1
#define CFG_TUD_CDC_RX_BUFSIZE 64
#define CFG_TUD_CDC_TX_BUFSIZE 64
/*
* TX bufsize (actually UART RX) is oversized because the Windows CDC-ACM
* driver submits a grand total of _one_ URB at any one time.
* This means the application must consume the data before the next IN token
* is issued. At high datarates this leads to huge variation in instantaneous
* throughput on USB, so a large runway is needed.
*/
#define CFG_TUD_CDC_RX_BUFSIZE 128
#define CFG_TUD_CDC_TX_BUFSIZE 4096
#define CFG_TUD_VENDOR_RX_BUFSIZE 8192
#define CFG_TUD_VENDOR_TX_BUFSIZE 8192

218
src/tusb_edpt_handler.c Normal file
View File

@@ -0,0 +1,218 @@
/**
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "tusb_edpt_handler.h"
#include "DAP.h"
static uint8_t itf_num;
static uint8_t _rhport;
volatile uint32_t _resp_len;
uint8_t _out_ep_addr;
uint8_t _in_ep_addr;
buffer_t USBRequestBuffer;
buffer_t USBResponseBuffer;
void dap_edpt_init(void) {
}
void dap_edpt_reset(uint8_t __unused rhport)
{
itf_num = 0;
}
uint16_t dap_edpt_open(uint8_t __unused rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
{
TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass &&
PICOPROBE_INTERFACE_SUBCLASS == itf_desc->bInterfaceSubClass &&
PICOPROBE_INTERFACE_PROTOCOL == itf_desc->bInterfaceProtocol, 0);
// Initialise circular buffer indices
USBResponseBuffer.packet_wr_idx = 0;
USBResponseBuffer.packet_rd_idx = 0;
USBRequestBuffer.packet_wr_idx = 0;
USBRequestBuffer.packet_rd_idx = 0;
// Initialse full/empty flags
USBResponseBuffer.wasFull = false;
USBResponseBuffer.wasEmpty = true;
USBRequestBuffer.wasFull = false;
USBRequestBuffer.wasEmpty = true;
uint16_t const drv_len = sizeof(tusb_desc_interface_t) + (itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t));
TU_VERIFY(max_len >= drv_len, 0);
itf_num = itf_desc->bInterfaceNumber;
// Initialising the OUT endpoint
tusb_desc_endpoint_t *edpt_desc = (tusb_desc_endpoint_t *) (itf_desc + 1);
uint8_t ep_addr = edpt_desc->bEndpointAddress;
_out_ep_addr = ep_addr;
// The OUT endpoint requires a call to usbd_edpt_xfer to initialise the endpoint, giving tinyUSB a buffer to consume when a transfer occurs at the endpoint
usbd_edpt_open(rhport, edpt_desc);
usbd_edpt_xfer(rhport, ep_addr, &(USBRequestBuffer.data[USBRequestBuffer.packet_wr_idx][0]), DAP_PACKET_SIZE);
// Initiliasing the IN endpoint
edpt_desc++;
ep_addr = edpt_desc->bEndpointAddress;
_in_ep_addr = ep_addr;
// The IN endpoint doesn't need a transfer to initialise it, as this will be done by the main loop of dap_thread
usbd_edpt_open(rhport, edpt_desc);
return drv_len;
}
bool dap_edpt_control_xfer_cb(uint8_t __unused rhport, uint8_t stage, tusb_control_request_t const *request)
{
return false;
}
// Manage USBResponseBuffer (request) write and USBRequestBuffer (response) read indices
bool dap_edpt_xfer_cb(uint8_t __unused rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
const uint8_t ep_dir = tu_edpt_dir(ep_addr);
if(ep_dir == TUSB_DIR_IN)
{
if(xferred_bytes >= 0u && xferred_bytes <= DAP_PACKET_SIZE)
{
USBResponseBuffer.packet_rd_idx = (USBResponseBuffer.packet_rd_idx + 1) % DAP_PACKET_COUNT;
// This checks that the buffer was not empty in DAP thread, which means the next buffer was not queued up for the in endpoint callback
// So, queue up the buffer at the new read index, since we expect read to catch up to write at this point.
// It is possible for the read index to be multiple spaces behind the write index (if the USB callbacks are lagging behind dap thread),
// so we account for this by only setting wasEmpty to true if the next callback will empty the buffer
if(!USBResponseBuffer.wasEmpty)
{
usbd_edpt_xfer(rhport, ep_addr, &(USBResponseBuffer.data[USBResponseBuffer.packet_rd_idx][0]), (uint16_t) _resp_len);
USBResponseBuffer.wasEmpty = ((USBResponseBuffer.packet_rd_idx + 1) % DAP_PACKET_COUNT == USBResponseBuffer.packet_wr_idx);
}
// Wake up DAP thread after processing the callback
vTaskResume(dap_taskhandle);
return true;
}
return false;
} else if(ep_dir == TUSB_DIR_OUT) {
if(xferred_bytes >= 0u && xferred_bytes <= DAP_PACKET_SIZE)
{
// Only queue the next buffer in the out callback if the buffer is not full
// If full, we set the wasFull flag, which will be checked by dap thread
if(!buffer_full(&USBRequestBuffer))
{
USBRequestBuffer.packet_wr_idx = (USBRequestBuffer.packet_wr_idx + 1) % DAP_PACKET_COUNT;
usbd_edpt_xfer(rhport, ep_addr, &(USBRequestBuffer.data[USBRequestBuffer.packet_wr_idx][0]), DAP_PACKET_SIZE);
USBRequestBuffer.wasFull = false;
}
else {
USBRequestBuffer.wasFull = true;
}
// Wake up DAP thread after processing the callback
vTaskResume(dap_taskhandle);
return true;
}
return false;
}
else return false;
}
void dap_thread(void *ptr)
{
uint8_t DAPRequestBuffer[DAP_PACKET_SIZE];
uint8_t DAPResponseBuffer[DAP_PACKET_SIZE];
do
{
while(USBRequestBuffer.packet_rd_idx != USBRequestBuffer.packet_wr_idx)
{
// Read a single packet from the USB buffer into the DAP Request buffer
memcpy(DAPRequestBuffer, &(USBRequestBuffer.data[USBRequestBuffer.packet_rd_idx]), DAP_PACKET_SIZE);
USBRequestBuffer.packet_rd_idx = (USBRequestBuffer.packet_rd_idx + 1) % DAP_PACKET_COUNT;
// If the buffer was full in the out callback, we need to queue up another buffer for the endpoint to consume, now that we know there is space in the buffer.
if(USBRequestBuffer.wasFull)
{
vTaskSuspendAll(); // Suspend the scheduler to safely update the write index
USBRequestBuffer.packet_wr_idx = (USBRequestBuffer.packet_wr_idx + 1) % DAP_PACKET_COUNT;
usbd_edpt_xfer(_rhport, _out_ep_addr, &(USBRequestBuffer.data[USBRequestBuffer.packet_wr_idx][0]), DAP_PACKET_SIZE);
USBRequestBuffer.wasFull = false;
xTaskResumeAll();
}
_resp_len = DAP_ProcessCommand(DAPRequestBuffer, DAPResponseBuffer);
// Suspend the scheduler to avoid stale values/race conditions between threads
vTaskSuspendAll();
if(buffer_empty(&USBResponseBuffer))
{
memcpy(&(USBResponseBuffer.data[USBResponseBuffer.packet_wr_idx]), DAPResponseBuffer, (uint16_t) _resp_len);
USBResponseBuffer.packet_wr_idx = (USBResponseBuffer.packet_wr_idx + 1) % DAP_PACKET_COUNT;
usbd_edpt_xfer(_rhport, _in_ep_addr, &(USBResponseBuffer.data[USBResponseBuffer.packet_rd_idx][0]), (uint16_t) _resp_len);
} else {
memcpy(&(USBResponseBuffer.data[USBResponseBuffer.packet_wr_idx]), DAPResponseBuffer, (uint16_t) _resp_len);
USBResponseBuffer.packet_wr_idx = (USBResponseBuffer.packet_wr_idx + 1) % DAP_PACKET_COUNT;
// The In callback needs to check this flag to know when to queue up the next buffer.
USBResponseBuffer.wasEmpty = false;
}
xTaskResumeAll();
}
// Suspend DAP thread until it is awoken by a USB thread callback
vTaskSuspend(dap_taskhandle);
} while (1);
}
usbd_class_driver_t const _dap_edpt_driver =
{
.init = dap_edpt_init,
.reset = dap_edpt_reset,
.open = dap_edpt_open,
.control_xfer_cb = dap_edpt_control_xfer_cb,
.xfer_cb = dap_edpt_xfer_cb,
.sof = NULL,
#if CFG_TUSB_DEBUG >= 2
.name = "PICOPROBE ENDPOINT"
#endif
};
// Add the custom driver to the tinyUSB stack
usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count)
{
*driver_count = 1;
return &_dap_edpt_driver;
}
bool buffer_full(buffer_t *buffer)
{
return ((buffer->packet_wr_idx + 1) % DAP_PACKET_COUNT == buffer->packet_rd_idx);
}
bool buffer_empty(buffer_t *buffer)
{
return (buffer->packet_wr_idx == buffer->packet_rd_idx);
}

41
src/tusb_edpt_handler.h Normal file
View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef TUSB_EDPT_HANDLER_H
#define TUSB_EDPT_HANDLER_H
#include "tusb.h"
#include "device/usbd_pvt.h"
#include "DAP_config.h"
#define PICOPROBE_INTERFACE_SUBCLASS 0x00
#define PICOPROBE_INTERFACE_PROTOCOL 0x00
typedef struct {
uint8_t data[DAP_PACKET_COUNT][DAP_PACKET_SIZE];
volatile uint32_t packet_wr_idx;
volatile uint32_t packet_rd_idx;
volatile bool wasEmpty;
volatile bool wasFull;
} buffer_t;
extern TaskHandle_t dap_taskhandle, tud_taskhandle;
/* Main DAP loop */
void dap_thread(void *ptr);
/* Endpoint Handling */
void picoprobe_edpt_init(void);
uint16_t picoprobe_edpt_open(uint8_t __unused rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len);
bool picoprobe_edpt_control_xfer_cb(uint8_t __unused rhport, uint8_t stage, tusb_control_request_t const *request);
bool picoprobe_edpt_xfer_cb(uint8_t __unused rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
/* Helper Functions */
bool buffer_full(buffer_t *buffer);
bool buffer_empty(buffer_t *buffer);
#endif

View File

@@ -48,7 +48,7 @@ tusb_desc_device_t const desc_device =
.idVendor = 0x2E8A, // Pi
.idProduct = 0x000c, // CMSIS-DAP Debug Probe
.bcdDevice = 0x0101, // Version 01.01
.bcdDevice = 0x0103, // Version 01.03
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
@@ -99,7 +99,7 @@ uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf)
uint8_t const desc_configuration[] =
{
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 100),
// Interface 0
#if (PICOPROBE_DEBUG_PROTOCOL == PROTO_DAP_V1)
// HID (named interface)
@@ -133,7 +133,7 @@ char const* string_desc_arr [] =
{
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Raspberry Pi", // 1: Manufacturer
"Debug Probe (CMSIS-DAP)", // 2: Product
PROBE_PRODUCT_STRING, // 2: Product
usb_serial, // 3: Serial, uses flash unique ID
"CMSIS-DAP v1 Interface", // 4: Interface descriptor for HID transport
"CMSIS-DAP v2 Interface", // 5: Interface descriptor for Bulk transport
@@ -242,4 +242,4 @@ TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size");
uint8_t const * tud_descriptor_bos_cb(void)
{
return desc_bos;
}
}