This commit is contained in:
Koen De Vleeschauwer 2021-11-28 14:13:24 +01:00 committed by Rachel Mant
parent 738ac96e57
commit 0a0f5a9dd7
13 changed files with 1198 additions and 1 deletions

271
UsingRTT.md Normal file
View File

@ -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)

View File

@ -1,6 +1,7 @@
# Black Magic Probe
# there are two connections, one for GDB and one for UART debugging
# copy this to /etc/udev/rules.d/99-blackmagic.rules
# and run /usr/sbin/udevadm control --reload-rules
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb"
SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg"
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6017", MODE="0666"

View File

@ -95,6 +95,15 @@ VPATH += platforms/common
CFLAGS += -Iplatforms/common
endif
ifeq ($(ENABLE_RTT), 1)
CFLAGS += -DENABLE_RTT
SRC += rtt.c rtt_if.c
endif
ifdef RTT_IDENT
CFLAGS += -DRTT_IDENT=$(RTT_IDENT)
endif
OBJ = $(patsubst %.S,%.o,$(patsubst %.c,%.o,$(SRC)))
$(TARGET): include/version.h $(OBJ)

View File

@ -34,6 +34,10 @@
#include "version.h"
#include "serialno.h"
#ifdef ENABLE_RTT
#include "rtt.h"
#endif
#ifdef PLATFORM_HAS_TRACESWO
# include "traceswo.h"
#endif
@ -56,6 +60,9 @@ static bool cmd_target_power(target *t, int argc, const char **argv);
static bool cmd_traceswo(target *t, int argc, const char **argv);
#endif
static bool cmd_heapinfo(target *t, int argc, const char **argv);
#ifdef ENABLE_RTT
static bool cmd_rtt(target *t, int argc, const char **argv);
#endif
#if defined(PLATFORM_HAS_DEBUG) && (PC_HOSTED == 0)
static bool cmd_debug_bmp(target *t, int argc, const char **argv);
#endif
@ -74,6 +81,9 @@ const struct command_s cmd_list[] = {
#ifdef PLATFORM_HAS_POWER_SWITCH
{"tpwr", (cmd_handler)cmd_target_power, "Supplies power to the target: (enable|disable)"},
#endif
#ifdef ENABLE_RTT
{"rtt", (cmd_handler)cmd_rtt, "enable|disable|status|channel 0..15|ident (str)|cblock|poll maxms minms maxerr" },
#endif
#ifdef PLATFORM_HAS_TRACESWO
#if defined TRACESWO_PROTOCOL && TRACESWO_PROTOCOL == 2
{"traceswo", (cmd_handler)cmd_traceswo, "Start trace capture, NRZ mode: (baudrate) (decode channel ...)" },
@ -410,6 +420,93 @@ static bool cmd_target_power(target *t, int argc, const char **argv)
}
#endif
#ifdef ENABLE_RTT
const char* onoroffstr[2] = {"off", "on"};
static const char* onoroff(bool bval) {
return bval ? onoroffstr[1] : onoroffstr[0];
}
static bool cmd_rtt(target *t, int argc, const char **argv)
{
(void)t;
if ((argc == 1) || ((argc == 2) && !strncmp(argv[1], "enabled", strlen(argv[1])))) {
rtt_enabled = true;
rtt_found = false;
}
else if ((argc == 2) && !strncmp(argv[1], "disabled", strlen(argv[1]))) {
rtt_enabled = false;
rtt_found = false;
}
else if ((argc == 2) && !strncmp(argv[1], "status", strlen(argv[1]))) {
gdb_outf("rtt: %s found: %s ident: ",
onoroff(rtt_enabled), rtt_found ? "yes" : "no");
if (rtt_ident[0] == '\0')
gdb_out("off");
else
gdb_outf("\"%s\"", rtt_ident);
gdb_outf(" halt: %s", onoroff(target_no_background_memory_access(t)));
gdb_out(" channels: ");
if (rtt_auto_channel) gdb_out("auto ");
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++)
if (rtt_channel[i].is_enabled) gdb_outf("%d ", i);
gdb_outf("\nmax poll ms: %u min poll ms: %u max errs: %u\n",
rtt_max_poll_ms, rtt_min_poll_ms, rtt_max_poll_errs);
}
else if ((argc >= 2) && !strncmp(argv[1], "channel", strlen(argv[1]))) {
/* mon rtt channel switches to auto rtt channel selection
mon rtt channel number... selects channels given */
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++)
rtt_channel[i].is_enabled = false;
if (argc == 2) {
rtt_auto_channel = true;
} else {
rtt_auto_channel = false;
for (int i = 2; i < argc; i++) {
int chan = atoi(argv[i]);
if ((chan >= 0) && (chan < MAX_RTT_CHAN))
rtt_channel[chan].is_enabled = true;
}
}
}
else if ((argc == 2) && !strncmp(argv[1], "ident", strlen(argv[1]))) {
rtt_ident[0] = '\0';
}
else if ((argc == 2) && !strncmp(argv[1], "poll", strlen(argv[1])))
gdb_outf("%u %u %u\n", rtt_max_poll_ms, rtt_min_poll_ms, rtt_max_poll_errs);
else if ((argc == 2) && !strncmp(argv[1], "cblock", strlen(argv[1]))) {
gdb_outf("cbaddr: 0x%x\n", rtt_cbaddr);
gdb_out("ch ena cfg i/o buf@ size head@ tail@ flg\n");
for (uint32_t i = 0; i < MAX_RTT_CHAN; i++) {
gdb_outf("%2d %c %c %s 0x%08x %5d 0x%08x 0x%08x %d\n",
i, rtt_channel[i].is_enabled ? 'y' : 'n', rtt_channel[i].is_configured ? 'y' : 'n',
rtt_channel[i].is_output ? "out" : "in ", rtt_channel[i].buf_addr, rtt_channel[i].buf_size,
rtt_channel[i].head_addr, rtt_channel[i].tail_addr, rtt_channel[i].flag);
}
}
else if ((argc == 3) && !strncmp(argv[1], "ident", strlen(argv[1]))) {
strncpy(rtt_ident, argv[2], sizeof(rtt_ident));
rtt_ident[sizeof(rtt_ident)-1] = '\0';
for (uint32_t i = 0; i < sizeof(rtt_ident); i++)
if (rtt_ident[i] == '_') rtt_ident[i] = ' ';
}
else if ((argc == 5) && !strncmp(argv[1], "poll", strlen(argv[1]))) {
/* set polling params */
int32_t new_max_poll_ms = atoi(argv[2]);
int32_t new_min_poll_ms = atoi(argv[3]);
int32_t new_max_poll_errs = atoi(argv[4]);
if ((new_max_poll_ms >= 0) && (new_min_poll_ms >= 0) && (new_max_poll_errs >= 0)
&& (new_max_poll_ms >= new_min_poll_ms)) {
rtt_max_poll_ms = new_max_poll_ms;
rtt_min_poll_ms = new_min_poll_ms;
rtt_max_poll_errs = new_max_poll_errs;
}
else gdb_out("how?\n");
}
else gdb_out("what?\n");
return true;
}
#endif
#ifdef PLATFORM_HAS_TRACESWO
static bool cmd_traceswo(target *t, int argc, const char **argv)
{

View File

@ -35,6 +35,9 @@
#include "command.h"
#include "crc32.h"
#include "morse.h"
#ifdef ENABLE_RTT
#include "rtt.h"
#endif
enum gdb_signal {
GDB_SIGINT = 2,
@ -193,6 +196,9 @@ int gdb_main_loop(struct target_controller *tc, bool in_syscall)
if((c == '\x03') || (c == '\x04')) {
target_halt_request(cur_target);
}
#ifdef ENABLE_RTT
if (rtt_enabled) poll_rtt(cur_target);
#endif
}
SET_RUN_STATE(0);
@ -518,6 +524,10 @@ handle_v_packet(char *packet, int plen)
}
break;
}
#ifdef ENABLE_RTT
/* force searching rtt control block */
rtt_found = false;
#endif
/* Run target program. For us (embedded) this means reset. */
if (cur_target) {
target_set_cmdline(cur_target, cmdline);

34
src/include/rtt.h Normal file
View File

@ -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

36
src/include/rtt_if.h Normal file
View File

@ -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

View File

@ -30,6 +30,10 @@
#include "gdb_if.h"
#include <signal.h>
#ifdef ENABLE_RTT
#include "rtt_if.h"
#endif
#include "bmp_remote.h"
#include "bmp_hosted.h"
#include "stlinkv2.h"
@ -58,6 +62,9 @@ static void exit_function(void)
default:
break;
}
#ifdef ENABLE_RTT
rtt_if_exit();
#endif
fflush(stdout);
}
@ -110,6 +117,9 @@ void platform_init(int argc, char **argv)
exit(cl_execute(&cl_opts));
else {
gdb_if_init();
#ifdef ENABLE_RTT
rtt_if_init();
#endif
return;
}
}

View File

@ -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

View File

@ -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

View File

@ -259,6 +259,7 @@ static void usbuart_change_dma_tx_buf(void)
buf_tx_act_idx ^= 1;
}
#ifndef ENABLE_RTT
void usbuart_usb_out_cb(usbd_device *dev, uint8_t ep)
{
(void)ep;
@ -301,6 +302,7 @@ void usbuart_usb_out_cb(usbd_device *dev, uint8_t ep)
if (TX_BUF_SIZE - buf_tx_act_sz >= CDCACM_PACKET_SIZE)
usbd_ep_nak_set(dev, CDCACM_UART_ENDPOINT, 0);
}
#endif
#ifdef USBUART_DEBUG
int usbuart_debug_write(const char *buf, size_t len)

View File

@ -99,6 +99,7 @@ void usbuart_set_line_coding(struct usb_cdc_line_coding *coding)
}
}
#ifndef ENABLE_RTT
void usbuart_usb_out_cb(usbd_device *dev, uint8_t ep)
{
(void)ep;
@ -110,7 +111,7 @@ void usbuart_usb_out_cb(usbd_device *dev, uint8_t ep)
for(int i = 0; i < len; i++)
uart_send_blocking(USBUART, buf[i]);
}
#endif
void usbuart_usb_in_cb(usbd_device *dev, uint8_t ep)
{

463
src/rtt.c Normal file
View File

@ -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