Koen De Vleeschauwer
3 years ago
committed by
Rachel Mant
13 changed files with 1198 additions and 1 deletions
@ -0,0 +1,271 @@ |
|||
# Using RTT |
|||
|
|||
When debugging arm processors, there are three ways for the target to print debug messages on the host: Semihosting, Serial Wire Output SWO, and Real-Time Transfer RTT. |
|||
|
|||
[Black Magic Probe](https://github.com/blacksphere/blackmagic) (BMP) is an open source debugger probe that already implements Semihosting and Single Wire Output. This patch adds Real-Time Transfer RTT output to usb serial port. |
|||
|
|||
- RTT is implemented, not as a user program, but as a serial port device. To read RTT output, use a terminal emulator and connect to the serial port. |
|||
|
|||
- A novel way to detect RTT automatically, fast and convenient. |
|||
|
|||
## Use |
|||
This example uses linux as operating system. For Windows and MacOS see the *Operating Systems* section. |
|||
|
|||
In one window open a terminal emulator (minicom, putty) and connect to the usb uart: |
|||
``` |
|||
$ minicom -c on -D /dev/ttyBmpTarg |
|||
``` |
|||
|
|||
In another window open a debugger: |
|||
``` |
|||
$ gdb |
|||
(gdb) target extended-remote /dev/ttyBmpGdb |
|||
(gdb) monitor swdp_scan |
|||
(gdb) attach 1 |
|||
(gdb) monitor rtt |
|||
(gdb) run |
|||
^C |
|||
(gdb) monitor rtt status |
|||
rtt: on found: yes ident: off halt: off channels: auto 0 1 3 |
|||
max poll ms: 256 min poll ms: 8 max errs: 10 |
|||
``` |
|||
|
|||
The terminal emulator displays RTT output from the target, |
|||
and characters typed in the terminal emulator are sent via RTT to the target. |
|||
|
|||
|
|||
## gdb commands |
|||
|
|||
The following new gdb commands are available: |
|||
|
|||
- ``monitor rtt`` |
|||
|
|||
switch rtt on |
|||
|
|||
- ``monitor rtt enable`` |
|||
|
|||
switch rtt on |
|||
|
|||
- ``monitor rtt disable`` |
|||
|
|||
switch rtt off |
|||
|
|||
- ``monitor rtt poll `` max_poll_ms min_poll_ms max_errs |
|||
|
|||
sets maximum time between polls, minimum time between polls, and the maximum number of errors before RTT disconnects from the target. Times in milliseconds. It is best if max_poll_ms/min_poll_ms is a power of two. As an example, if you wish to check for RTT output between once per second to eight times per second: ``monitor rtt poll 1000 125 10``. |
|||
|
|||
- ``monitor rtt status`` |
|||
|
|||
show status. |
|||
|
|||
rtt|found|state |
|||
---|---|--- |
|||
rtt: off|found: no|rtt inactive |
|||
rtt: on|found: no|searching for rtt control block |
|||
rtt: on|found: yes|rtt active |
|||
rtt: off|found: yes|corrupt rtt control block, or target memory access error |
|||
|
|||
A status of `rtt: on found: no` indicates bmp is still searching for the rtt control block in target ram, but has not found anything yet. A status of `rtt: on found: yes` indicates the control block has been found and rtt is active. |
|||
|
|||
- ``monitor rtt channel`` |
|||
|
|||
enables the first two output channels, and the first input channel. (default) |
|||
|
|||
- ``monitor rtt channel number...`` |
|||
|
|||
enables the given RTT channel numbers. Channels are numbers from 0 to 15, inclusive. Eg. ``monitor rtt channel 0 1 4`` to enable channels 0, 1, and 4. |
|||
|
|||
- ``monitor rtt ident string`` |
|||
|
|||
sets RTT ident to *string*. If *string* contains a space, replace the space with an underscore _. Setting ident string is optional, RTT works fine without. |
|||
|
|||
- ``monitor rtt ident`` |
|||
|
|||
clears ident string. (default) |
|||
|
|||
- ``monitor rtt cblock`` |
|||
|
|||
shows rtt control block data, and which channels are enabled. This is an example control block: |
|||
|
|||
``` |
|||
(gdb) mon rtt cb |
|||
cbaddr: 0x200000a0 |
|||
ch ena cfg i/o buf@ size head@ tail@ flg |
|||
0 y y out 0x20000148 1024 0x200000c4 0x200000c8 2 |
|||
1 y n out 0x00000000 0 0x200000dc 0x200000e0 0 |
|||
2 n n out 0x00000000 0 0x200000f4 0x200000f8 0 |
|||
3 y y in 0x20000548 16 0x2000010c 0x20000110 0 |
|||
4 n n in 0x00000000 0 0x20000124 0x20000128 0 |
|||
5 n n in 0x00000000 0 0x2000013c 0x20000140 0 |
|||
6 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
7 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
8 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
9 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
10 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
11 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
12 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
13 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
14 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
15 n n in 0x00000000 0 0x00000000 0x00000000 0 |
|||
``` |
|||
|
|||
Channels are listed, one channel per line. The columns are: channel, enabled, configured, input/output, buffer address, buffer size, address of head pointer, address of tail pointer, flag. Each channel is a circular buffer with head and tail pointer. |
|||
|
|||
Note the columns `ena` for enabled, `cfg` for configured. |
|||
|
|||
Configured channels have a non-zero buffer address and non-zero size. Configured channels are marked yes `y` in the column `cfg` . What channels are configured depends upon target software. |
|||
|
|||
Channels the user wants to see are marked yes `y` in the column enabled `ena`. The user can change which channels are shown with the `monitor rtt channel` command. |
|||
|
|||
Output channels are displayed, and Input channels receive keyboard input, if they are marked yes in both *enabled* and *configured*. |
|||
|
|||
The control block is cached for speed. In an interrupted program, `monitor rtt` will force a reload of the control block when the program continues. |
|||
|
|||
## Identifier string |
|||
It is possible to set an RTT identifier string. |
|||
As an example, if the RTT identifier is "IDENT STR": |
|||
``` |
|||
$ gdb |
|||
(gdb) target extended-remote /dev/ttyBmpGdb |
|||
(gdb) monitor swdp_scan |
|||
(gdb) attach 1 |
|||
(gdb) monitor rtt ident IDENT_STR |
|||
(gdb) monitor rtt |
|||
(gdb) run |
|||
^C |
|||
(gdb) monitor rtt status |
|||
rtt: on found: yes ident: "IDENT STR" halt: off channels: auto 0 1 3 |
|||
max poll ms: 256 min poll ms: 8 max errs: 10 |
|||
``` |
|||
Note replacing space with underscore _ in *monitor rtt ident*. |
|||
|
|||
Setting an identifier string is optional. RTT gives the same output at the same speed, with or without specifying identifier string. |
|||
|
|||
## Operating systems |
|||
|
|||
[Configuration](https://github.com/blacksphere/blackmagic/wiki/Getting-Started) instructions for windows, linux and macos. |
|||
|
|||
### Windows |
|||
|
|||
After configuration, Black Magic Probe shows up in Windows as two _USB Serial (CDC)_ ports. |
|||
|
|||
Connect arm-none-eabi-gdb, the gnu debugger for arm processors, to the lower numbered of the two COM ports. Connect an ansi terminal emulator to the higher numbered of the two COM ports. |
|||
|
|||
Sample gdb session: |
|||
``` |
|||
(gdb) target extended-remote COM3 |
|||
(gdb) monitor swdp_scan |
|||
(gdb) attach 1 |
|||
(gdb) monitor rtt |
|||
(gdb) run |
|||
``` |
|||
|
|||
For COM port COM10 and higher, add the prefix `\\.\`, e.g. |
|||
``` |
|||
target extended-remote \\.\COM10 |
|||
``` |
|||
|
|||
Target RTT output will appear in the terminal, and what you type in the terminal will be sent to the RTT input of the target. |
|||
|
|||
### linux |
|||
On linux, install [udev rules](https://github.com/blacksphere/blackmagic/blob/master/driver/99-blackmagic.rules). Disconnect and re-connect the BMP. Check the device shows up in /dev/ : |
|||
``` |
|||
$ ls -l /dev/ttyBmp* |
|||
lrwxrwxrwx 1 root root 7 Dec 13 07:29 /dev/ttyBmpGdb -> ttyACM0 |
|||
lrwxrwxrwx 1 root root 7 Dec 13 07:29 /dev/ttyBmpTarg -> ttyACM2 |
|||
``` |
|||
Connect terminal emulator to /dev/ttyBmpTarg and gdb to /dev/ttyBmpGdb . |
|||
|
|||
In one window: |
|||
``` |
|||
minicom -c on -D /dev/ttyBmpTarg |
|||
``` |
|||
In another window : |
|||
``` |
|||
gdb |
|||
(gdb) target extended-remote /dev/ttyBmpGdb |
|||
(gdb) monitor swdp_scan |
|||
(gdb) attach 1 |
|||
(gdb) monitor rtt |
|||
(gdb) run |
|||
``` |
|||
|
|||
### MacOS |
|||
|
|||
On MacOS the tty devices have different names than on linux. On connecting blackmagic to the computer 4 devices are created, 2 'tty' and 2 'cu' devices. Gdb connects to the first cu device (e.g.: `target extended-remote /dev/cu.usbmodemDDCEC9EC1`), while RTT is connected to the second tty device (`minicom -c on -D /dev/tty.usbmodemDDCEC9EC3`). In full: |
|||
|
|||
In one Terminal window, connect a terminal emulator to /dev/tty.usbmodemDDCEC9EC3 : |
|||
|
|||
``` |
|||
minicom -c on -D /dev/tty.usbmodemDDCEC9EC3 |
|||
``` |
|||
In another Terminal window, connect gdb to /dev/cu.usbmodemDDCEC9EC1 : |
|||
``` |
|||
gdb |
|||
(gdb) target extended-remote /dev/cu.usbmodemDDCEC9EC1 |
|||
(gdb) monitor swdp_scan |
|||
(gdb) attach 1 |
|||
(gdb) monitor rtt |
|||
(gdb) run |
|||
``` |
|||
RTT input/output is in the window running _minicom_. |
|||
|
|||
## Notes |
|||
|
|||
- Design goal was smallest, simplest implementation that has good practical use. |
|||
|
|||
- RTT code size is 3.5 kbyte - the whole debugger 110 kbyte. |
|||
|
|||
- Because RTT is implemented as a serial port device, there is no need to write and maintain software for different host operating systems. A serial port works everywhere - linux, windows and mac. You can even use an Android mobile phone as RTT terminal. |
|||
|
|||
- Because polling occurs between debugger probe and target, the load on the host is small. There is no constant usb traffic, there are no real-time requirements on the host. |
|||
|
|||
- RTT polling frequency is adaptive and goes up and down with RTT activity. Use *monitor rtt poll* to balance response speed and target load for your use. |
|||
|
|||
- Detects RTT automatically, very convenient. |
|||
|
|||
- When using RTT as a terminal, sending data from host to target, you may need to change local echo, carriage return and/or line feed settings in your terminal emulator. |
|||
|
|||
- Architectures such as risc-v may not allow the debugger access to target memory while the target is running. As a workaround, on these architectures RTT briefly halts the target during polling. If the target is halted during polling, `monitor rtt status` shows `halt: on`. |
|||
|
|||
- Measured RTT speed. |
|||
|
|||
| debugger | char/s | |
|||
| ------------------------- | ------ | |
|||
| bmp stm32f723 stlinkv3 | 49811 | |
|||
| bmp stm32f411 black pill | 50073 | |
|||
| bmp stm32f103 blue pill | 50142 | |
|||
|
|||
This is the speed at which characters can be sent from target to debugger probe, in reasonable circumstances. Test target is an stm32f103 blue pill running an [Arduino sketch](https://github.com/koendv/Arduino-RTTStream/blob/main/examples/SpeedTest/SpeedTest.ino). Default *monitor rtt poll* settings on debugger. Default RTT buffer size in target and debugger. Overhead for printf() calls included. |
|||
|
|||
## Compiling firmware |
|||
To compile with RTT support, add *ENABLE_RTT=1*. |
|||
|
|||
Eg. for STM32F103 blue pill: |
|||
``` |
|||
make clean |
|||
make PROBE_HOST=stlink ENABLE_RTT=1 |
|||
``` |
|||
or for the STM32F411 *[Black Pill](https://www.aliexpress.com/item/1005001456186625.html)*: |
|||
``` |
|||
make clean |
|||
make PROBE_HOST=f4discovery BLACKPILL=1 ENABLE_RTT=1 |
|||
``` |
|||
Setting an ident string is optional. But if you wish, you can set the default RTT ident at compile time. |
|||
For STM32F103 *Blue Pill*: |
|||
``` |
|||
make clean |
|||
make PROBE_HOST=stlink ENABLE_RTT=1 "RTT_IDENT=IDENT\ STR" |
|||
``` |
|||
or for STM32F411 *Black Pill*: |
|||
``` |
|||
make clean |
|||
make PROBE_HOST=f4discovery BLACKPILL=1 ENABLE_RTT=1 "RTT_IDENT=IDENT\ STR" |
|||
``` |
|||
Note the backslash \\ before the space. |
|||
|
|||
## Links |
|||
- [OpenOCD](https://openocd.org/doc/html/General-Commands.html#Real-Time-Transfer-_0028RTT_0029) |
|||
- [probe-rs](https://probe.rs/) and [rtt-target](https://github.com/mvirkkunen/rtt-target) for the _rust_ programming language. |
|||
- [RTT Stream](https://github.com/koendv/Arduino-RTTStream) for Arduino on arm processors |
|||
- [\[WIP\] RTT support - PR from katyo](https://github.com/blacksphere/blackmagic/pull/833) |
@ -0,0 +1,34 @@ |
|||
#ifndef RTT_H |
|||
#define RTT_H |
|||
#include <target.h> |
|||
|
|||
#define MAX_RTT_CHAN 16 |
|||
|
|||
extern char rtt_ident[16]; // string
|
|||
extern bool rtt_enabled; // rtt on/off
|
|||
extern bool rtt_found; // control block found
|
|||
extern uint32_t rtt_cbaddr; // control block address
|
|||
extern uint32_t rtt_min_poll_ms; // min time between polls (ms)
|
|||
extern uint32_t rtt_max_poll_ms; // max time between polls (ms)
|
|||
extern uint32_t rtt_max_poll_errs; // max number of errors before disconnect
|
|||
extern bool rtt_auto_channel; // manual or auto channel selection
|
|||
extern bool rtt_flag_skip; // skip if host-to-target fifo full
|
|||
extern bool rtt_flag_block; // block if host-to-target fifo full
|
|||
|
|||
struct rtt_channel_struct { |
|||
bool is_enabled; // does user want to see this channel?
|
|||
bool is_configured; // is channel configured in control block?
|
|||
bool is_output; |
|||
uint32_t buf_addr; |
|||
uint32_t buf_size; |
|||
uint32_t head_addr; |
|||
uint32_t tail_addr; |
|||
uint32_t flag; |
|||
}; |
|||
|
|||
extern struct rtt_channel_struct rtt_channel[MAX_RTT_CHAN]; |
|||
|
|||
// true if target memory access does not work when target running
|
|||
extern bool target_no_background_memory_access(target *cur_target); |
|||
extern void poll_rtt(target *cur_target); |
|||
#endif |
@ -0,0 +1,36 @@ |
|||
#ifndef RTT_IF_H |
|||
#define RTT_IF_H |
|||
/* rtt i/o to terminal */ |
|||
|
|||
/* default buffer sizes, 8 bytes added to up buffer for alignment and padding */ |
|||
/* override RTT_UP_BUF_SIZE and RTT_DOWN_BUF_SIZE in platform.h if needed */ |
|||
|
|||
#if !defined(RTT_UP_BUF_SIZE) || !defined(RTT_DOWN_BUF_SIZE) |
|||
#if (PC_HOSTED == 1) |
|||
#define RTT_UP_BUF_SIZE (4096 + 8) |
|||
#define RTT_DOWN_BUF_SIZE (512) |
|||
#elif defined(STM32F7) |
|||
#define RTT_UP_BUF_SIZE (4096 + 8) |
|||
#define RTT_DOWN_BUF_SIZE (2048) |
|||
#elif defined(STM32F4) |
|||
#define RTT_UP_BUF_SIZE (2048 + 8) |
|||
#define RTT_DOWN_BUF_SIZE (256) |
|||
#else /* stm32f103 */ |
|||
#define RTT_UP_BUF_SIZE (1024 + 8) |
|||
#define RTT_DOWN_BUF_SIZE (256) |
|||
#endif |
|||
#endif |
|||
|
|||
/* hosted initialisation */ |
|||
extern int rtt_if_init(void); |
|||
/* hosted teardown */ |
|||
extern int rtt_if_exit(void); |
|||
|
|||
/* target to host: write len bytes from the buffer starting at buf. return number bytes written */ |
|||
extern uint32_t rtt_write(const char *buf, uint32_t len); |
|||
/* host to target: read one character, non-blocking. return character, -1 if no character */ |
|||
extern int32_t rtt_getchar(); |
|||
/* host to target: true if no characters available for reading */ |
|||
extern bool rtt_nodata(); |
|||
|
|||
#endif |
@ -0,0 +1,127 @@ |
|||
/*
|
|||
* This file is part of the Black Magic Debug project. |
|||
* |
|||
* MIT License |
|||
* |
|||
* Copyright (c) 2021 Koen De Vleeschauwer |
|||
* |
|||
* 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 <general.h> |
|||
#include <unistd.h> |
|||
#include <fcntl.h> |
|||
#include <rtt_if.h> |
|||
|
|||
/* maybe rewrite this as tcp server */ |
|||
|
|||
#ifndef WIN32 |
|||
#include <termios.h> |
|||
|
|||
/* linux */ |
|||
static struct termios saved_ttystate; |
|||
static bool tty_saved = false; |
|||
|
|||
/* set up and tear down */ |
|||
|
|||
int rtt_if_init() |
|||
{ |
|||
struct termios ttystate; |
|||
tcgetattr(STDIN_FILENO, &saved_ttystate); |
|||
tty_saved = true; |
|||
tcgetattr(STDIN_FILENO, &ttystate); |
|||
ttystate.c_lflag &= ~ICANON; |
|||
ttystate.c_lflag &= ~ECHO; |
|||
ttystate.c_cc[VMIN] = 1; |
|||
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate); |
|||
int flags = fcntl(0, F_GETFL, 0); |
|||
fcntl(0, F_SETFL, flags | O_NONBLOCK); |
|||
return 0; |
|||
} |
|||
|
|||
int rtt_if_exit() |
|||
{ |
|||
if (tty_saved) |
|||
tcsetattr(STDIN_FILENO, TCSANOW, &saved_ttystate); |
|||
return 0; |
|||
} |
|||
|
|||
/* write buffer to terminal */ |
|||
|
|||
uint32_t rtt_write(const char *buf, uint32_t len) |
|||
{ |
|||
write(1, buf, len); |
|||
return len; |
|||
} |
|||
|
|||
/* read character from terminal */ |
|||
|
|||
int32_t rtt_getchar() |
|||
{ |
|||
char ch; |
|||
int len; |
|||
len = read(0, &ch, 1); |
|||
if (len == 1) return ch; |
|||
return -1; |
|||
} |
|||
|
|||
/* true if no characters available */ |
|||
|
|||
bool rtt_nodata() |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#else |
|||
|
|||
/* windows, output only */ |
|||
|
|||
int rtt_if_init() |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int rtt_if_exit() |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
/* write buffer to terminal */ |
|||
|
|||
uint32_t rtt_write(const char *buf, uint32_t len) |
|||
{ |
|||
write(1, buf, len); |
|||
return len; |
|||
} |
|||
|
|||
/* read character from terminal */ |
|||
|
|||
int32_t rtt_getchar() |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
/* true if no characters available */ |
|||
|
|||
bool rtt_nodata() |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
#endif |
@ -0,0 +1,136 @@ |
|||
/*
|
|||
* This file is part of the Black Magic Debug project. |
|||
* |
|||
* MIT License |
|||
* |
|||
* Copyright (c) 2021 Koen De Vleeschauwer |
|||
* |
|||
* 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 "general.h" |
|||
#include "platform.h" |
|||
#include <assert.h> |
|||
#include "cdcacm.h" |
|||
#include "rtt.h" |
|||
#include "rtt_if.h" |
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* rtt terminal i/o |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
/* usb uart receive buffer */ |
|||
static char recv_buf[RTT_DOWN_BUF_SIZE]; |
|||
static uint32_t recv_head = 0; |
|||
static uint32_t recv_tail = 0; |
|||
|
|||
/* data from host to target: number of free bytes in usb receive buffer */ |
|||
inline static uint32_t recv_bytes_free() |
|||
{ |
|||
uint32_t bytes_free; |
|||
if (recv_tail <= recv_head) bytes_free = sizeof(recv_buf) - recv_head + recv_tail - 1; |
|||
else bytes_free = recv_tail - recv_head - 1; |
|||
return bytes_free; |
|||
} |
|||
|
|||
/* data from host to target: true if not enough free buffer space and we need to close flow control */ |
|||
inline static bool recv_set_nak() |
|||
{ |
|||
assert(sizeof(recv_buf) > 2 * CDCACM_PACKET_SIZE); |
|||
bool nak = recv_bytes_free() < 2 * CDCACM_PACKET_SIZE; |
|||
return nak; |
|||
} |
|||
|
|||
/* usbuart_usb_out_cb is called when usb uart has received new data for target.
|
|||
this routine has to be fast */ |
|||
|
|||
void usbuart_usb_out_cb(usbd_device *dev, uint8_t ep) |
|||
{ |
|||
(void)dev; |
|||
(void)ep; |
|||
char usb_buf[CDCACM_PACKET_SIZE]; |
|||
|
|||
/* close flow control while processing packet */ |
|||
usbd_ep_nak_set(usbdev, CDCACM_UART_ENDPOINT, 1); |
|||
|
|||
const uint16_t len = usbd_ep_read_packet(usbdev, CDCACM_UART_ENDPOINT, usb_buf, CDCACM_PACKET_SIZE); |
|||
|
|||
/* skip flag: drop packet if not enough free buffer space */ |
|||
if (rtt_flag_skip && (len > recv_bytes_free())) { |
|||
usbd_ep_nak_set(usbdev, CDCACM_UART_ENDPOINT, 0); |
|||
return; |
|||
} |
|||
|
|||
/* copy data to recv_buf */ |
|||
for (int i = 0; i < len; i++) { |
|||
uint32_t next_recv_head = (recv_head + 1) % sizeof(recv_buf); |
|||
if (next_recv_head == recv_tail) |
|||
break; /* overflow */ |
|||
recv_buf[recv_head] = usb_buf[i]; |
|||
recv_head = next_recv_head; |
|||
} |
|||
|
|||
/* block flag: flow control closed if not enough free buffer space */ |
|||
if (!(rtt_flag_block && recv_set_nak())) |
|||
usbd_ep_nak_set(usbdev, CDCACM_UART_ENDPOINT, 0); |
|||
|
|||
return; |
|||
} |
|||
|
|||
/* rtt host to target: read one character */ |
|||
int32_t rtt_getchar() |
|||
{ |
|||
int retval; |
|||
|
|||
if (recv_head == recv_tail) |
|||
return -1; |
|||
retval = recv_buf[recv_tail]; |
|||
recv_tail = (recv_tail + 1) % sizeof(recv_buf); |
|||
|
|||
/* open flow control if enough free buffer space */ |
|||
if (!recv_set_nak()) |
|||
usbd_ep_nak_set(usbdev, CDCACM_UART_ENDPOINT, 0); |
|||
|
|||
return retval; |
|||
} |
|||
|
|||
/* rtt host to target: true if no characters available for reading */ |
|||
bool rtt_nodata() |
|||
{ |
|||
return recv_head == recv_tail; |
|||
} |
|||
|
|||
/* rtt target to host: write string */ |
|||
uint32_t rtt_write(const char *buf, uint32_t len) |
|||
{ |
|||
if ((len != 0) && usbdev && cdcacm_get_config() && cdcacm_get_dtr()) { |
|||
for (uint32_t p = 0; p < len; p += CDCACM_PACKET_SIZE) { |
|||
uint32_t plen = MIN(CDCACM_PACKET_SIZE, len - p); |
|||
while(usbd_ep_write_packet(usbdev, CDCACM_UART_ENDPOINT, buf + p, plen) <= 0); |
|||
} |
|||
/* flush 64-byte packet on full-speed */ |
|||
if ((CDCACM_PACKET_SIZE == 64) && ((len % CDCACM_PACKET_SIZE) == 0)) |
|||
while(usbd_ep_write_packet(usbdev, CDCACM_UART_ENDPOINT, NULL, 0) <= 0); |
|||
} |
|||
return len; |
|||
} |
|||
// not truncated
|
@ -0,0 +1,463 @@ |
|||
|
|||
/*
|
|||
* This file is part of the Black Magic Debug project. |
|||
* |
|||
* MIT License |
|||
* |
|||
* Copyright (c) 2021 Koen De Vleeschauwer |
|||
* |
|||
* 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 "general.h" |
|||
#include "platform.h" |
|||
#include "gdb_packet.h" |
|||
#include "target.h" |
|||
#include "target/target_internal.h" |
|||
#include "rtt.h" |
|||
#include "rtt_if.h" |
|||
|
|||
bool rtt_enabled = false; |
|||
bool rtt_found = false; |
|||
static bool rtt_halt = false; // true if rtt needs to halt target to access memory
|
|||
uint32_t rtt_cbaddr = 0; |
|||
bool rtt_auto_channel = true; |
|||
struct rtt_channel_struct rtt_channel[MAX_RTT_CHAN]; |
|||
|
|||
uint32_t rtt_min_poll_ms = 8; /* 8 ms */ |
|||
uint32_t rtt_max_poll_ms = 256; /* 0.256 s */ |
|||
uint32_t rtt_max_poll_errs = 10; |
|||
static uint32_t poll_ms; |
|||
static uint32_t poll_errs; |
|||
static uint32_t last_poll_ms; |
|||
/* flags for data from host to target */ |
|||
bool rtt_flag_skip = false; |
|||
bool rtt_flag_block = false; |
|||
|
|||
typedef enum rtt_retval { |
|||
RTT_OK, |
|||
RTT_IDLE, |
|||
RTT_ERR |
|||
} rtt_retval; |
|||
|
|||
#ifdef RTT_IDENT |
|||
#define Q(x) #x |
|||
#define QUOTE(x) Q(x) |
|||
char rtt_ident[16] = QUOTE(RTT_IDENT); |
|||
#else |
|||
char rtt_ident[16] = {0}; |
|||
#endif |
|||
|
|||
/* usb uart transmit buffer */ |
|||
static char xmit_buf[RTT_UP_BUF_SIZE]; |
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* rtt control block |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
uint32_t fastsrch(target *cur_target) |
|||
{ |
|||
const uint32_t m = 16; |
|||
const uint64_t q = 0x797a9691; /* prime */ |
|||
const uint64_t rm = 0x73b07d01; |
|||
const uint64_t p = 0x444110cd; |
|||
const uint32_t stride = 128; |
|||
uint64_t t = 0; |
|||
uint8_t srch_buf[m+stride]; |
|||
|
|||
for (struct target_ram *r = cur_target->ram; r; r = r->next) { |
|||
uint32_t ram_start = r->start; |
|||
uint32_t ram_end = r->start + r->length; |
|||
|
|||
t = 0; |
|||
memset(srch_buf, 0, sizeof(srch_buf)); |
|||
|
|||
for (uint32_t addr = ram_start; addr < ram_end; addr += stride) { |
|||
uint32_t buf_siz = MIN(stride, ram_end - addr); |
|||
memcpy(srch_buf, srch_buf + stride, m); |
|||
if (target_mem_read(cur_target, srch_buf + m, addr, buf_siz)) { |
|||
gdb_outf("rtt: read fail at 0x%x\r\n", addr); |
|||
return 0; |
|||
} |
|||
for (uint32_t i = 0; i < buf_siz; i++) { |
|||
t = (t + q - rm * srch_buf[i] % q) % q; |
|||
t = ((t << 8) + srch_buf[i + m]) % q; |
|||
if (p == t) { |
|||
uint32_t offset = i - m + 1; |
|||
return addr + offset; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
/* no match */ |
|||
return 0; |
|||
} |
|||
|
|||
uint32_t memsrch(target *cur_target) |
|||
{ |
|||
char *srch_str = rtt_ident; |
|||
uint32_t srch_str_len = strlen(srch_str); |
|||
uint8_t srch_buf[128]; |
|||
|
|||
if ((srch_str_len == 0) || (srch_str_len > sizeof(srch_buf) / 2)) |
|||
return 0; |
|||
|
|||
if (rtt_cbaddr && !target_mem_read(cur_target, srch_buf, rtt_cbaddr, srch_str_len) |
|||
&& (strncmp((const char *)(srch_buf), srch_str, srch_str_len) == 0)) { |
|||
/* still at same place */ |
|||
return rtt_cbaddr; |
|||
} |
|||
|
|||
for (struct target_ram *r = cur_target->ram; r; r = r->next) { |
|||
uint32_t ram_end = r->start + r->length; |
|||
for (uint32_t addr = r->start; addr < ram_end; addr += sizeof(srch_buf) - srch_str_len - 1) { |
|||
uint32_t buf_siz = MIN(ram_end - addr, sizeof(srch_buf)); |
|||
if (target_mem_read(cur_target, srch_buf, addr, buf_siz)) { |
|||
gdb_outf("rtt: read fail at 0x%x\r\n", addr); |
|||
continue; |
|||
} |
|||
for (uint32_t offset = 0; offset + srch_str_len + 1 < buf_siz; offset++) { |
|||
if (strncmp((const char *)(srch_buf + offset), srch_str, srch_str_len) == 0) { |
|||
uint32_t cb_addr = addr + offset; |
|||
return cb_addr; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static void find_rtt(target *cur_target) |
|||
{ |
|||
rtt_found = false; |
|||
poll_ms = rtt_max_poll_ms; |
|||
poll_errs = 0; |
|||
last_poll_ms = 0; |
|||
|
|||
if (!cur_target || !rtt_enabled) |
|||
return; |
|||
|
|||
if (rtt_ident[0] == 0) rtt_cbaddr = fastsrch(cur_target); |
|||
else rtt_cbaddr = memsrch(cur_target); |
|||
DEBUG_INFO("rtt: match at 0x%" PRIx32 "\r\n", rtt_cbaddr); |
|||
|
|||
if (rtt_cbaddr) { |
|||
uint32_t num_buf[2]; |
|||
int32_t num_up_buf; |
|||
int32_t num_down_buf; |
|||
if (target_mem_read(cur_target, num_buf, rtt_cbaddr + 16, sizeof(num_buf))) |
|||
return; |
|||
num_up_buf = num_buf[0]; |
|||
num_down_buf = num_buf[1]; |
|||
|
|||
if ((num_up_buf > 255) || (num_down_buf > 255)) { |
|||
gdb_out("rtt: bad cblock\r\n"); |
|||
rtt_enabled = false; |
|||
return; |
|||
} else if ((num_up_buf == 0) && (num_down_buf == 0)) |
|||
gdb_out("rtt: empty cblock\r\n"); |
|||
|
|||
for (int32_t i = 0; i < MAX_RTT_CHAN; i++) { |
|||
uint32_t buf_desc[6]; |
|||
|
|||
rtt_channel[i].is_configured = false; |
|||
rtt_channel[i].is_output = false; |
|||
rtt_channel[i].buf_addr = 0; |
|||
rtt_channel[i].buf_size = 0; |
|||
rtt_channel[i].head_addr = 0; |
|||
rtt_channel[i].tail_addr = 0; |
|||
rtt_channel[i].flag = 0; |
|||
|
|||
if (i >= num_up_buf + num_down_buf) continue; |
|||
if (target_mem_read(cur_target, buf_desc, rtt_cbaddr + 24 + i * 24, sizeof(buf_desc))) |
|||
return; |
|||
rtt_channel[i].is_output = i < num_up_buf; |
|||
rtt_channel[i].buf_addr = buf_desc[1]; |
|||
rtt_channel[i].buf_size = buf_desc[2]; |
|||
rtt_channel[i].head_addr = rtt_cbaddr + 24 + i * 24 + 12; |
|||
rtt_channel[i].tail_addr = rtt_cbaddr + 24 + i * 24 + 16; |
|||
rtt_channel[i].flag = buf_desc[5]; |
|||
rtt_channel[i].is_configured = (rtt_channel[i].buf_addr != 0) && (rtt_channel[i].buf_size != 0); |
|||
} |
|||
|
|||
/* auto channel: enable output channels 0 and 1 and first input channel */ |
|||
if (rtt_auto_channel) { |
|||
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++) |
|||
rtt_channel[i].is_enabled = false; |
|||
rtt_channel[0].is_enabled = num_up_buf > 0; |
|||
rtt_channel[1].is_enabled = num_up_buf > 1; |
|||
if ((num_up_buf < MAX_RTT_CHAN) && (num_down_buf > 0)) |
|||
rtt_channel[num_up_buf].is_enabled = true; |
|||
} |
|||
|
|||
/* get flags for data from host to target */ |
|||
rtt_flag_skip = false; |
|||
rtt_flag_block = false; |
|||
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++) |
|||
if (rtt_channel[i].is_enabled && rtt_channel[i].is_configured && !rtt_channel[i].is_output) { |
|||
rtt_flag_skip = rtt_channel[i].flag == 0; |
|||
rtt_flag_block = rtt_channel[i].flag == 2; |
|||
break; |
|||
} |
|||
|
|||
rtt_found = true; |
|||
DEBUG_INFO("rtt found\n"); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* rtt from host to target |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
/* poll if host has new data for target */ |
|||
static rtt_retval read_rtt(target *cur_target, uint32_t i) |
|||
{ |
|||
uint32_t head_tail[2]; |
|||
uint32_t buf_head; |
|||
uint32_t buf_tail; |
|||
uint32_t next_head; |
|||
int ch; |
|||
|
|||
/* copy data from recv_buf to target rtt 'down' buffer */ |
|||
if (rtt_nodata()) |
|||
return RTT_IDLE; |
|||
|
|||
if ((cur_target == NULL) || rtt_channel[i].is_output || (rtt_channel[i].buf_addr == 0) || (rtt_channel[i].buf_size == 0)) |
|||
return RTT_IDLE; |
|||
|
|||
/* read down buffer head and tail from target */ |
|||
if (target_mem_read(cur_target, head_tail, rtt_channel[i].head_addr, sizeof(head_tail))) { |
|||
return RTT_ERR; |
|||
} |
|||
|
|||
buf_head = head_tail[0]; |
|||
buf_tail = head_tail[1]; |
|||
|
|||
if ((buf_head >= rtt_channel[i].buf_size) || (buf_tail >= rtt_channel[i].buf_size)) { |
|||
return RTT_ERR; |
|||
} |
|||
|
|||
/* write recv_buf to target rtt 'down' buf */ |
|||
while (((next_head = ((buf_head + 1) % rtt_channel[i].buf_size)) != buf_tail) && ((ch = rtt_getchar()) != -1)) { |
|||
if (target_mem_write(cur_target, rtt_channel[i].buf_addr + buf_head, &ch, 1)) { |
|||
return RTT_ERR; |
|||
} |
|||
|
|||
/* advance pointers */ |
|||
buf_head = next_head; |
|||
} |
|||
|
|||
/* update head of target 'down' buffer */ |
|||
if (target_mem_write(cur_target, rtt_channel[i].head_addr, &buf_head, sizeof(buf_head))) { |
|||
return RTT_ERR; |
|||
} |
|||
|
|||
return RTT_OK; |
|||
} |
|||
|
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* rtt from target to host |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
/* target_mem_read, word aligned for speed.
|
|||
note: dest has to be len + 8 bytes, to allow for alignment and padding. |
|||
*/ |
|||
int target_aligned_mem_read(target *t, void *dest, target_addr src, size_t len) |
|||
{ |
|||
uint32_t src0 = src; |
|||
uint32_t len0 = len; |
|||
uint32_t offset = src & 0x3; |
|||
src0 -= offset; |
|||
len0 += offset; |
|||
if ((len0 & 0x3) != 0) len0 = (len0 + 4) & ~0x3; |
|||
|
|||
if ((src0 == src) && (len0 == len)) |
|||
return target_mem_read(t, dest, src, len); |
|||
else { |
|||
uint32_t retval = target_mem_read(t, dest, src0, len0); |
|||
memmove(dest, dest + offset, len); |
|||
return retval; |
|||
} |
|||
} |
|||
|
|||
/* poll if target has new data for host */ |
|||
static rtt_retval print_rtt(target *cur_target, uint32_t i) |
|||
{ |
|||
uint32_t head; |
|||
uint32_t tail; |
|||
|
|||
if (!cur_target || !rtt_channel[i].is_output || (rtt_channel[i].buf_addr == 0) || (rtt_channel[i].head_addr == 0)) |
|||
return RTT_IDLE; |
|||
|
|||
uint32_t head_tail[2]; |
|||
if (target_mem_read(cur_target, head_tail, rtt_channel[i].head_addr, sizeof(head_tail))) { |
|||
return RTT_ERR; |
|||
} |
|||
head = head_tail[0]; |
|||
tail = head_tail[1]; |
|||
|
|||
if ((head >= rtt_channel[i].buf_size) || (tail >= rtt_channel[i].buf_size)) { |
|||
return RTT_ERR; |
|||
} |
|||
|
|||
if (head == tail) |
|||
return RTT_IDLE; |
|||
|
|||
uint32_t bytes_free = sizeof(xmit_buf) - 8; /* need 8 bytes for alignment and padding */ |
|||
uint32_t bytes_read = 0; |
|||
|
|||
if (tail > head) { |
|||
uint32_t len = rtt_channel[i].buf_size - tail; |
|||
if (len > bytes_free) |
|||
len = bytes_free; |
|||
if (target_aligned_mem_read(cur_target, xmit_buf + bytes_read, rtt_channel[i].buf_addr + tail, len)) |
|||
return RTT_ERR; |
|||
bytes_free -= len; |
|||
bytes_read += len; |
|||
tail = (tail + len) % rtt_channel[i].buf_size; |
|||
} |
|||
|
|||
if ((head > tail) && (bytes_free > 0)) { |
|||
uint32_t len = head - tail; |
|||
if (len > bytes_free) |
|||
len = bytes_free; |
|||
if (target_aligned_mem_read(cur_target, xmit_buf + bytes_read, rtt_channel[i].buf_addr + tail, len)) |
|||
return RTT_ERR; |
|||
bytes_read += len; |
|||
tail = (tail + len) % rtt_channel[i].buf_size; |
|||
} |
|||
|
|||
/* update tail on target */ |
|||
if (target_mem_write(cur_target, rtt_channel[i].tail_addr, &tail, sizeof(tail))) |
|||
return RTT_ERR; |
|||
|
|||
/* write buffer to usb */ |
|||
rtt_write(xmit_buf, bytes_read); |
|||
|
|||
return RTT_OK; |
|||
} |
|||
|
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* target background memory access |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
/* target_no_background_memory_access() is true if the target needs to be halted during jtag memory access
|
|||
target_no_background_memory_access() is false if the target allows jtag memory access while running */ |
|||
|
|||
bool target_no_background_memory_access(target *cur_target) |
|||
{ |
|||
/* if error message is 'rtt: read fail at' add target to expression below.
|
|||
As a first approximation, assume all arm processors allow memory access while running, and no riscv does. */ |
|||
bool riscv_core = cur_target && target_core_name(cur_target) && strstr(target_core_name(cur_target), "RVDBG"); |
|||
return riscv_core; |
|||
} |
|||
|
|||
/*********************************************************************
|
|||
* |
|||
* rtt top level |
|||
* |
|||
********************************************************************** |
|||
*/ |
|||
|
|||
void poll_rtt(target *cur_target) |
|||
{ |
|||
/* rtt off */ |
|||
if (!cur_target || !rtt_enabled) { |
|||
return; |
|||
} |
|||
/* target present and rtt enabled */ |
|||
uint32_t now = platform_time_ms(); |
|||
bool rtt_err = false; |
|||
bool rtt_busy = false; |
|||
|
|||
if ((last_poll_ms + poll_ms <= now) || (now < last_poll_ms)) { |
|||
target_addr watch; |
|||
enum target_halt_reason reason; |
|||
bool resume_target = false; |
|||
if (!rtt_found) { |
|||
/* check if target needs to be halted during memory access */ |
|||
rtt_halt = target_no_background_memory_access(cur_target); |
|||
} |
|||
if (rtt_halt && (target_halt_poll(cur_target, &watch) == TARGET_HALT_RUNNING)) { |
|||
/* briefly halt target during target memory access */ |
|||
target_halt_request(cur_target); |
|||
while((reason = target_halt_poll(cur_target, &watch)) == TARGET_HALT_RUNNING); |
|||
resume_target = reason == TARGET_HALT_REQUEST; |
|||
} |
|||
if (!rtt_found) { |
|||
/* find rtt control block in target memory */ |
|||
find_rtt(cur_target); |
|||
} |
|||
/* do rtt i/o if control block found */ |
|||
if (rtt_found) { |
|||
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++) { |
|||
rtt_retval v; |
|||
if (rtt_channel[i].is_enabled && rtt_channel[i].is_configured) { |
|||
if (rtt_channel[i].is_output) |
|||
v = print_rtt(cur_target, i); |
|||
else |
|||
v = read_rtt(cur_target, i); |
|||
if (v == RTT_OK) rtt_busy = true; |
|||
else if (v == RTT_ERR) rtt_err = true; |
|||
} |
|||
} |
|||
} |
|||
/* continue target if halted */ |
|||
if (resume_target) { |
|||
target_halt_resume(cur_target, false); |
|||
} |
|||
|
|||
/* update last poll time */ |
|||
last_poll_ms = now; |
|||
|
|||
/* rtt polling frequency goes up and down with rtt activity */ |
|||
if (rtt_busy && !rtt_err) |
|||
poll_ms /= 2; |
|||
else poll_ms *= 2; |
|||
if (poll_ms > rtt_max_poll_ms) poll_ms = rtt_max_poll_ms; |
|||
else if (poll_ms < rtt_min_poll_ms) poll_ms = rtt_min_poll_ms; |
|||
|
|||
if (rtt_err) { |
|||
gdb_out("rtt: err\r\n"); |
|||
poll_errs++; |
|||
if ((rtt_max_poll_errs != 0) && (poll_errs > rtt_max_poll_errs)) { |
|||
gdb_out("\r\nrtt lost\r\n"); |
|||
rtt_enabled = false; |
|||
} |
|||
} |
|||
} |
|||
return; |
|||
} |
|||
|
|||
// not truncated
|
Loading…
Reference in new issue