Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
"rmii_config.h": "c",
"conn.h": "c",
"ssi.h": "c",
"flash.h": "c"
"flash.h": "c",
"printf.h": "c",
"stdlib.h": "c",
"pico.h": "c",
"platform.h": "c",
"error.h": "c"
},
"cortex-debug.variableUseNaturalFormat": false,
"cortex-debug.registerUseNaturalFormat": false
Expand Down
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ add_executable(pico-debug
gdb.c gdb.h
breakpoint.c breakpoint.h

prof.c

cmdline.c cmdline.h
utils.c utils.h

Expand Down Expand Up @@ -102,8 +104,11 @@ target_compile_definitions(pico-debug PRIVATE
CMD_CDC=2
CMD_TCP=3335

PROF_CDC=0
PROF_TCP=0

# Debug on CDC2
# DEBUG_CDC=2
DEBUG_CDC=2
DEBUG_BUF=1
)

Expand Down
55 changes: 55 additions & 0 deletions PROFILING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Profiling RP2040 Code

This branch of `pico_debug` has a profiler interface instead of gdb on CDC port 0.
(In the future it should be possible for the two to coexist, but that's not done yet.)
Wifi support is forcibly disabled as well.

This will only work with code compiled with `-fno-omit-frame-pointer`, because
interpreting the ARM unwind tables is too expensive at runtime (and plus not implemented
because it's super annoying). I may experiment with that in the future -- the unwind tables can be downloaded to the debug probe and evaluated over SWD -- but this is good enough for most
use cases, even though it does make the `r7` register unavailable and thus does have a performance impact.

The front end is based on [`samply`](https://github.com/mstange/samply). Check out the `pico`
branch and build (`cargo build`).

## Setup

Build this code and flash it to a Pico that's connected to your target in a "Picoprobe" setup.
If you're targeting another rp2040-based device, you may need to modify some hardcoded values
in `prof.c` (memory and flash regions). This will be runtime configurable in the future.

Install `samply` (`pico` branch).

You'll need your code compiled with `-fno-omit-frame-pointer` (`add_compile_options(-fno-omit-frame-pointer)`), and then you'll need to have the `program.elf` binary available. You should also download the [bootloader binary for your chip revision](https://github.com/raspberrypi/pico-bootrom/releases) for more complete symbols.

## Running

Connect the picoprobe to your computer via USB. Connecting to CDC port 3 with a terminal emulator
should give you a `pico_debug>` prompt (you don't need to do anything here, but a good verification
that things are working).

Run:
```
samply --pico /dev/tty.usbXXXX1 --pico-bootloader b2.elf --pico-reset --rate 50 program.elf
```

the target will reset and sampling will start. Omit `--pico-reset` to profile an already-running device. Press `^C` when done to open the profiler front-end. (Note: `program.elf` should already be flashed onto the target pico. This program does not download the program, but it will do that in the future.)

`--rate` takes an argument in Hz (not ms). Note that the default for `samply` is 1ms, which is too fast for this. (It will work, but your code won't run anywhere near full speed!)

## Internals

This uses the SWD protocol (the bulk of the code in this repo comes from https://github.com/essele/pico_debug -- a very fast rp2040 debug interface) to halt each core at a given sampling interval, and then read `pc`, `lr`, `fp`, and walk the frame pointer hierarchy back.

In my measurements, it takes around 500µs to halt, sample (with shallow frame depth), and resume a core. It could be down to 300µs, but there's some weirdness with SWD I need to investigate where the cores aren't actually getting unhalted until I read their status. This isn't amazing, but is completely workable for a 20ms or so sampling interval.

The protocol itself is simple. When a connection is made to CDC port 0, there are a few commands that can be sent:

* `r` -- reset both cores
* `0xa0` `0xvvvv` `0xff` -- start sampling
* `v` is the sampling interval, in ms (little endian)
* `f` is flags: `1 << 0` sample core 0 only instead of both cores
* `0xa1` -- stop sampling

When sampling is running, a stream of data will be received with the samples. See `prof.c` for the (simple) format.

15 changes: 12 additions & 3 deletions adi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1259,11 +1259,20 @@ int check_cores() {
}




int dp_init() {
int dp_force_init() {
CHECK_OK(dp_initialise());
CHECK_OK(core_select(0));
return SWD_OK;
}

int dp_init() {
static bool initialized = false;
if (!initialized) {
int rv = dp_force_init();
if (rv == SWD_OK) {
initialized = true;
}
return rv;
}
return SWD_OK;
}
16 changes: 15 additions & 1 deletion id_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
#define USBD_CDC_2_EP_OUT (0x04)
#define USBD_CDC_2_EP_IN (0x86)

#define USBD_CDC_3_EP_CMD (0x87)
#define USBD_CDC_3_EP_OUT (0x08)
#define USBD_CDC_3_EP_IN (0x89)

#define USBD_CDC_CMD_MAX_SIZE (8)
#define USBD_CDC_IN_OUT_MAX_SIZE (64)

Expand All @@ -48,6 +52,7 @@
#define USBD_STR_CDC0 (0x04)
#define USBD_STR_CDC1 (0x05)
#define USBD_STR_CDC2 (0x06)
#define USBD_STR_CDC3 (0x07)

//
// Officially, according to the USB specs, we shoul duse TUSB_CLASS_MISC, SUBCLASS_COMMON, and PROTOCOL_IAD
Expand Down Expand Up @@ -85,6 +90,8 @@ enum
ITF_NUM_CDC_1_DATA,
ITF_NUM_CDC_2,
ITF_NUM_CDC_2_DATA,
ITF_NUM_CDC_3,
ITF_NUM_CDC_3_DATA,
ITF_NUM_TOTAL
};

Expand All @@ -104,6 +111,9 @@ uint8_t const usbd_desc_cfg[] =

TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_2, USBD_STR_CDC2, USBD_CDC_2_EP_CMD,
USBD_CDC_CMD_MAX_SIZE, USBD_CDC_2_EP_OUT, USBD_CDC_2_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE),

TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_3, USBD_STR_CDC3, USBD_CDC_3_EP_CMD,
USBD_CDC_CMD_MAX_SIZE, USBD_CDC_3_EP_OUT, USBD_CDC_3_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE),
};


Expand Down Expand Up @@ -159,7 +169,7 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid
break;

case USBD_STR_SERIAL:
len = string_to_descriptor("11223344", desc_str);
len = string_to_descriptor("pdebug", desc_str);
break;

case USBD_STR_CDC0:
Expand All @@ -174,6 +184,10 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, __unused uint16_t langid
len = string_to_descriptor("debug-debug", desc_str);
break;

case USBD_STR_CDC3:
len = string_to_descriptor("debug-prof", desc_str);
break;

default:
return NULL;
}
Expand Down
13 changes: 7 additions & 6 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void main_poll() {
debug_poll();
}

void prof_init();

int main() {
// Take us to 150Mhz (for future rmii support)
Expand All @@ -70,15 +71,15 @@ int main() {
dbg_uart_init();

// Create the GDB server task..
gdb_init();
//gdb_init();

prof_init();

// See if we have enough information to start the wifi...
// This is horrible here, needs to be a separate func/file with wifi/net related stuff.
if (*cf->main->wifi.ssid && *cf->main->wifi.creds) {
cyw43_arch_wifi_connect_async(cf->main->wifi.ssid, cf->main->wifi.creds, CYW43_AUTH_WPA2_AES_PSK);
}


//if (*cf->main->wifi.ssid && *cf->main->wifi.creds) {
// cyw43_arch_wifi_connect_async(cf->main->wifi.ssid, cf->main->wifi.creds, CYW43_AUTH_WPA2_AES_PSK);
//}

// And start the scheduler...
leos_init(main_poll);
Expand Down
2 changes: 2 additions & 0 deletions modules/lerp_io/include/lerp/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ struct io *io_init(int cdc_port, int tcp_port, int buf_size);

void io_poll();
int io_get_byte(struct io *io);
int io_get_byte_nb(struct io *io);
int io_put_byte(struct io *io, uint8_t ch);
int io_put_bytes(struct io *io, uint8_t *data, int len);
int io_put_hexbyte(struct io *io, uint8_t b);
int io_printf(struct io *io, char *format, ...);
int io_aprintf(struct io *io, char *format, va_list args);
Expand Down
23 changes: 23 additions & 0 deletions modules/lerp_io/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,14 @@ int io_is_connected(struct io *io) {
return io->usb_is_connected || (io->pcb != NULL);
}

int io_get_byte_nb(struct io *io) {
if (circ_is_empty(io->input)) {
return 0;
}

return circ_get_byte(io->input);
}

int io_get_byte(struct io *io) {
int reason;

Expand Down Expand Up @@ -369,6 +377,19 @@ int io_put_byte(struct io *io, uint8_t ch) {
return 0;
}

int io_put_bytes(struct io *io, uint8_t *data, int len) {
int reason;

if (circ_is_full(io->output)) {
io->waiting_on_output = current_task();
reason = task_block();
if (reason < 0) return reason;
}
//debug_putch(ch);
circ_add_bytes(io->output, data, len);
return 0;
}

int io_read_flush(struct io *io) {
if (io->usb_is_connected) tud_cdc_n_read_flush(io->cdc_port);
circ_clean(io->input);
Expand Down Expand Up @@ -489,6 +510,8 @@ struct io *io_init(int cdc_port, int tcp_port, int buf_size) {
struct io *io = malloc(sizeof(struct io));
if (!io) return NULL;

tcp_port = 0;

io->cdc_port = cdc_port;
io->tcp_port = tcp_port;
io->pcb = NULL;
Expand Down
Loading