457 lines
13 KiB
C
457 lines
13 KiB
C
/*
|
|
* 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) {
|
|
const uint32_t ram_start = r->start;
|
|
const 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%" PRIx32 "\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%" PRIx32 "\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;
|
|
else 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)
|
|
continue;
|
|
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;
|
|
}
|