Add memory sample feature (#541)

* Add memory sampling feature.

Currently only gets 10 samples per second, but the overall scaffolding
looks like it works.

Change-Id: I25a2bbcba322f2101c3de598c225f83c902680fa

* Basic memory sample speed-ups.

977 samples/second.

Change-Id: I6ea874f25051aca1cbe3aa2918567a4ee316c4be

* Add base64 dumping of sample buffer.

We can't just dump raw data, because the API we use to get data to the
"user" uses NULL-terminated strings.

Change-Id: I3f33faaa485a74735c13cdaad685e336c1e2095f

* WIP on optimizing PC sampling.

1k samples per second on my laptop, which is roughly double what it was.

Change-Id: I6a77df8aa53118e44928f96d22210df84be45eda

* WIP

Change-Id: I4300692355cb0cf997ec59ab5ca71543b295abb0

* Use small batch to sample memory.

5k samples/second. No error checking.

Change-Id: I8a7f08e49cb153699021e27f8006beb0e6db70ee

* Collect memory samples near continuously.

Rewrite OpenOCD's core loop to get rid of the fixed 100ms delay.
Now collecting 15k samples/second.

Change-Id: Iba5e73e96e8d226a0b5777ecac19453c152dc634

* Fix build.

Change-Id: If2fe7a0c77e0d6545c93fa0d4a013c50a9b9d896

* Fix the mess I left after resolving conflicts.

Change-Id: I96abd47a7834bf8f5e005ba63020f0a0cc429548

* Support 64-bit address in memory sampling.

* Support sampling 64-bit values.

* Better error reporting. WIP on 64-bit support.

* Speed up single 32-bit memory sample.

21k samples/second.

* WIP on review feedback.

Change-Id: I00e453fd685d173b0206d925090beb06c1f057ca

* Make memory sample buffers/config per-target.

Change-Id: I5c2f5997795c7a434e71b36ca4c712623daf993c

* Document, and add bucket clear option.

Change-Id: I922b883adfa787fb4f5a894db872d04fda126cbd
Signed-off-by: Tim Newsome <tim@sifive.com>

* Fix whitespace.

Change-Id: Iabfeb0068d7138d9b252ac127d1b1f949cf19632
Signed-off-by: Tim Newsome <tim@sifive.com>

* Document sample buffer full behavior.

Change-Id: Ib3c30d34b1f9f30cf403afda8fdccb850bc8b4df
Signed-off-by: Tim Newsome <tim@sifive.com>

* Actually clear the sample buffer in dump_sample_buf.

Change-Id: Ifda22643f1e58f69a6382abc90474659d7330ac5
Signed-off-by: Tim Newsome <tim@sifive.com>

* Use compatible string formatting.

Change-Id: Ia5e5333e036c1dbe457bc977fcee41983b9a9b77
Signed-off-by: Tim Newsome <tim@sifive.com>
This commit is contained in:
Tim Newsome 2020-10-07 14:31:36 -07:00 committed by GitHub
parent e2cfd4e063
commit 6c1bd05088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 713 additions and 84 deletions

View File

@ -9641,6 +9641,13 @@ OpenOCD exposes each hart as a separate core.
@subsection RISC-V Debug Configuration Commands
@deffn Command {riscv dump_sample_buf} [base64]
Dump and clear the contents of the sample buffer. Which samples are collected
is configured with @code{riscv memory_sample}. If the optional base64
argument is passed, the raw buffer is dumped in base64 format, so that
external tools can gather the data efficiently.
@end deffn
@deffn Command {riscv expose_csrs} n[-m|=name] [...]
Configure which CSRs to expose in addition to the standard ones. The CSRs to expose
can be specified as individual register numbers or register ranges (inclusive). For the
@ -9662,6 +9669,16 @@ For individually listed registers, a human-readable name can be optionally provi
This command must be executed before `init`.
@end deffn
@deffn Command {riscv memory_sample} bucket address|clear [size=4]
Configure OpenOCD to frequently read size bytes at the given addresses.
Execute the command with no arguments to see the current configuration. Use
clear to stop using a given bucket.
OpenOCD will allocate a 1MB sample buffer, and when it fills up no more
samples will be collected until it is emptied with @code{riscv
dump_sample_buf}.
@end deffn
@deffn Command {riscv repeat_read} count address [size=4]
Quickly read count words of the given size from address. This can be useful
to read out a buffer that's memory-mapped to be accessed through a single

View File

@ -30,7 +30,9 @@ noinst_LTLIBRARIES += %D%/libhelper.la
%D%/system.h \
%D%/jep106.h \
%D%/jep106.inc \
%D%/jim-nvp.h
%D%/jim-nvp.h \
%D%/base64.c \
%D%/base64.h
if IOUTIL
%C%_libhelper_la_SOURCES += %D%/ioutil.c

157
src/helper/base64.c Normal file
View File

@ -0,0 +1,157 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "base64.h"
static const unsigned char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* base64_encode - Base64 encode
* @src: Data to be encoded
* @len: Length of the data to be encoded
* @out_len: Pointer to output length variable, or %NULL if not used
* Returns: Allocated buffer of out_len bytes of encoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer. Returned buffer is
* nul terminated to make it easier to use as a C string. The nul terminator is
* not included in out_len.
*/
unsigned char *base64_encode(const unsigned char *src, size_t len,
size_t *out_len)
{
unsigned char *out, *pos;
const unsigned char *end, *in;
size_t olen;
int line_len;
olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
olen += olen / 72; /* line feeds */
olen++; /* nul termination */
if (olen < len)
return NULL; /* integer overflow */
out = malloc(olen);
if (out == NULL)
return NULL;
end = src + len;
in = src;
pos = out;
line_len = 0;
while (end - in >= 3) {
*pos++ = base64_table[in[0] >> 2];
*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64_table[in[2] & 0x3f];
in += 3;
line_len += 4;
if (line_len >= 72) {
*pos++ = '\n';
line_len = 0;
}
}
if (end - in) {
*pos++ = base64_table[in[0] >> 2];
if (end - in == 1) {
*pos++ = base64_table[(in[0] & 0x03) << 4];
*pos++ = '=';
} else {
*pos++ = base64_table[((in[0] & 0x03) << 4) |
(in[1] >> 4)];
*pos++ = base64_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
line_len += 4;
}
if (line_len)
*pos++ = '\n';
*pos = '\0';
if (out_len)
*out_len = pos - out;
return out;
}
/**
* base64_decode - Base64 decode
* @src: Data to be decoded
* @len: Length of the data to be decoded
* @out_len: Pointer to output length variable
* Returns: Allocated buffer of out_len bytes of decoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer.
*/
unsigned char *base64_decode(const unsigned char *src, size_t len,
size_t *out_len)
{
unsigned char dtable[256], *out, *pos, block[4], tmp;
size_t i, count, olen;
int pad = 0;
memset(dtable, 0x80, 256);
for (i = 0; i < sizeof(base64_table) - 1; i++)
dtable[base64_table[i]] = (unsigned char) i;
dtable['='] = 0;
count = 0;
for (i = 0; i < len; i++) {
if (dtable[src[i]] != 0x80)
count++;
}
if (count == 0 || count % 4)
return NULL;
olen = count / 4 * 3;
pos = out = malloc(olen);
if (out == NULL)
return NULL;
count = 0;
for (i = 0; i < len; i++) {
tmp = dtable[src[i]];
if (tmp == 0x80)
continue;
if (src[i] == '=')
pad++;
block[count] = tmp;
count++;
if (count == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
count = 0;
if (pad) {
if (pad == 1)
pos--;
else if (pad == 2)
pos -= 2;
else {
/* Invalid padding */
free(out);
return NULL;
}
break;
}
}
}
*out_len = pos - out;
return out;
}

17
src/helper/base64.h Normal file
View File

@ -0,0 +1,17 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#ifndef BASE64_H
#define BASE64_H
unsigned char *base64_encode(const unsigned char *src, size_t len,
size_t *out_len);
unsigned char *base64_decode(const unsigned char *src, size_t len,
size_t *out_len);
#endif /* BASE64_H */

View File

@ -195,7 +195,7 @@ struct command_context *current_command_context(Jim_Interp *interp)
static int script_command_run(Jim_Interp *interp,
int argc, Jim_Obj * const *argv, struct command *c)
{
target_call_timer_callbacks_now();
target_call_timer_callbacks_now(NULL);
LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
unsigned nwords;
@ -1201,7 +1201,7 @@ COMMAND_HANDLER(handle_sleep_command)
if (!busy) {
int64_t then = timeval_ms();
while (timeval_ms() - then < (int64_t)duration) {
target_call_timer_callbacks_now();
target_call_timer_callbacks_now(NULL);
usleep(1000);
}
} else

View File

@ -2634,13 +2634,13 @@ static int gdb_query_packet(struct connection *connection,
/* We want to print all debug output to GDB connection */
log_add_callback(gdb_log_callback, connection);
target_call_timer_callbacks_now();
target_call_timer_callbacks_now(NULL);
/* some commands need to know the GDB connection, make note of current
* GDB connection. */
current_gdb_connection = gdb_connection;
command_run_line(cmd_ctx, cmd);
current_gdb_connection = NULL;
target_call_timer_callbacks_now();
target_call_timer_callbacks_now(NULL);
log_remove_callback(gdb_log_callback, connection);
free(cmd);
}

View File

@ -33,6 +33,7 @@
#include "openocd.h"
#include "tcl_server.h"
#include "telnet_server.h"
#include "time_support.h"
#include <signal.h>
@ -441,6 +442,8 @@ int server_loop(struct command_context *command_context)
/* used in accept() */
int retval;
int64_t next_event = timeval_ms() + polling_period;
#ifndef _WIN32
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
LOG_ERROR("couldn't set SIGPIPE to SIG_IGN");
@ -482,7 +485,12 @@ int server_loop(struct command_context *command_context)
retval = socket_select(fd_max + 1, &read_fds, NULL, NULL, &tv);
} else {
/* Every 100ms, can be changed with "poll_period" command */
tv.tv_usec = polling_period * 1000;
int timeout_ms = next_event - timeval_ms();
if (timeout_ms < 0)
timeout_ms = 0;
else if (timeout_ms > polling_period)
timeout_ms = polling_period;
tv.tv_usec = timeout_ms * 1000;
/* Only while we're sleeping we'll let others run */
openocd_sleep_prelude();
kept_alive();
@ -515,7 +523,7 @@ int server_loop(struct command_context *command_context)
if (retval == 0) {
/* We only execute these callbacks when there was nothing to do or we timed
*out */
target_call_timer_callbacks();
target_call_timer_callbacks(&next_event);
process_jim_events(command_context);
FD_ZERO(&read_fds); /* eCos leaves read_fds unchanged in this case! */

View File

@ -2023,6 +2023,220 @@ static int riscv013_set_register_buf(struct target *target,
return result;
}
static uint32_t sb_sbaccess(unsigned size_bytes)
{
switch (size_bytes) {
case 1:
return set_field(0, DM_SBCS_SBACCESS, 0);
case 2:
return set_field(0, DM_SBCS_SBACCESS, 1);
case 4:
return set_field(0, DM_SBCS_SBACCESS, 2);
case 8:
return set_field(0, DM_SBCS_SBACCESS, 3);
case 16:
return set_field(0, DM_SBCS_SBACCESS, 4);
}
assert(0);
return 0; /* Make mingw happy. */
}
static int sb_write_address(struct target *target, target_addr_t address,
bool ensure_success)
{
RISCV013_INFO(info);
unsigned sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE);
/* There currently is no support for >64-bit addresses in OpenOCD. */
if (sbasize > 96)
dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS3, 0, false, false);
if (sbasize > 64)
dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS2, 0, false, false);
if (sbasize > 32)
dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS1, address >> 32, false, false);
return dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS0, address,
false, ensure_success);
}
static int batch_run(const struct target *target, struct riscv_batch *batch)
{
RISCV013_INFO(info);
RISCV_INFO(r);
if (r->reset_delays_wait >= 0) {
r->reset_delays_wait -= batch->used_scans;
if (r->reset_delays_wait <= 0) {
batch->idle_count = 0;
info->dmi_busy_delay = 0;
info->ac_busy_delay = 0;
}
}
return riscv_batch_run(batch);
}
static int sba_supports_access(struct target *target, unsigned size_bytes)
{
RISCV013_INFO(info);
switch (size_bytes) {
case 1:
return get_field(info->sbcs, DM_SBCS_SBACCESS8);
case 2:
return get_field(info->sbcs, DM_SBCS_SBACCESS16);
case 4:
return get_field(info->sbcs, DM_SBCS_SBACCESS32);
case 8:
return get_field(info->sbcs, DM_SBCS_SBACCESS64);
case 16:
return get_field(info->sbcs, DM_SBCS_SBACCESS128);
default:
return 0;
}
}
static int sample_memory_bus_v1(struct target *target,
riscv_sample_buf_t *buf,
const riscv_sample_config_t *config,
int64_t until_ms)
{
RISCV013_INFO(info);
unsigned sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE);
if (sbasize > 64) {
LOG_ERROR("Memory sampling is only implemented for sbasize <= 64.");
return ERROR_NOT_IMPLEMENTED;
}
if (get_field(info->sbcs, DM_SBCS_SBVERSION) != 1) {
LOG_ERROR("Memory sampling is only implemented for SBA version 1.");
return ERROR_NOT_IMPLEMENTED;
}
uint32_t sbcs = 0;
uint32_t sbcs_valid = false;
uint32_t sbaddress0 = 0;
bool sbaddress0_valid = false;
uint32_t sbaddress1 = 0;
bool sbaddress1_valid = false;
/* How often to read each value in a batch. */
const unsigned repeat = 5;
unsigned enabled_count = 0;
for (unsigned i = 0; i < DIM(config->bucket); i++) {
if (config->bucket[i].enabled)
enabled_count++;
}
while (timeval_ms() < until_ms) {
/*
* batch_run() adds to the batch, so we can't simply reuse the same
* batch over and over. So we create a new one every time through the
* loop.
*/
struct riscv_batch *batch = riscv_batch_alloc(
target, 1 + enabled_count * 5 * repeat,
info->dmi_busy_delay + info->bus_master_read_delay);
unsigned result_bytes = 0;
for (unsigned n = 0; n < repeat; n++) {
for (unsigned i = 0; i < DIM(config->bucket); i++) {
if (config->bucket[i].enabled) {
if (!sba_supports_access(target, config->bucket[i].size_bytes)) {
LOG_ERROR("Hardware does not support SBA access for %d-byte memory sampling.",
config->bucket[i].size_bytes);
return ERROR_NOT_IMPLEMENTED;
}
uint32_t sbcs_write = DM_SBCS_SBREADONADDR;
if (enabled_count == 1)
sbcs_write |= DM_SBCS_SBREADONDATA;
sbcs_write |= sb_sbaccess(config->bucket[i].size_bytes);
if (!sbcs_valid || sbcs_write != sbcs) {
riscv_batch_add_dmi_write(batch, DM_SBCS, sbcs_write);
sbcs = sbcs_write;
sbcs_valid = true;
}
if (sbasize > 32 &&
(!sbaddress1_valid ||
sbaddress1 != config->bucket[i].address >> 32)) {
sbaddress1 = config->bucket[i].address >> 32;
riscv_batch_add_dmi_write(batch, DM_SBADDRESS1, sbaddress1);
sbaddress1_valid = true;
}
if (!sbaddress0_valid ||
sbaddress0 != (config->bucket[i].address & 0xffffffff)) {
sbaddress0 = config->bucket[i].address;
riscv_batch_add_dmi_write(batch, DM_SBADDRESS0, sbaddress0);
sbaddress0_valid = true;
}
if (config->bucket[i].size_bytes > 4)
riscv_batch_add_dmi_read(batch, DM_SBDATA1);
riscv_batch_add_dmi_read(batch, DM_SBDATA0);
result_bytes += 1 + config->bucket[i].size_bytes;
}
}
}
if (buf->used + result_bytes >= buf->size) {
riscv_batch_free(batch);
break;
}
size_t sbcs_key = riscv_batch_add_dmi_read(batch, DM_SBCS);
int result = batch_run(target, batch);
if (result != ERROR_OK)
return result;
uint32_t sbcs_read = riscv_batch_get_dmi_read_data(batch, sbcs_key);
if (get_field(sbcs_read, DM_SBCS_SBBUSYERROR)) {
/* Discard this batch (too much hassle to try to recover partial
* data) and try again with a larger delay. */
info->bus_master_read_delay += info->bus_master_read_delay / 10 + 1;
dmi_write(target, DM_SBCS, DM_SBCS_SBBUSYERROR | DM_SBCS_SBERROR);
riscv_batch_free(batch);
continue;
}
if (get_field(sbcs_read, DM_SBCS_SBERROR)) {
/* The memory we're sampling was unreadable, somehow. Give up. */
dmi_write(target, DM_SBCS, DM_SBCS_SBBUSYERROR | DM_SBCS_SBERROR);
riscv_batch_free(batch);
return ERROR_FAIL;
}
unsigned read = 0;
for (unsigned n = 0; n < repeat; n++) {
for (unsigned i = 0; i < DIM(config->bucket); i++) {
if (config->bucket[i].enabled) {
assert(i < RISCV_SAMPLE_BUF_TIMESTAMP);
uint64_t value = 0;
if (config->bucket[i].size_bytes > 4)
value = ((uint64_t) riscv_batch_get_dmi_read_data(batch, read++)) << 32;
value |= riscv_batch_get_dmi_read_data(batch, read++);
buf->buf[buf->used] = i;
buf_set_u64(buf->buf + buf->used + 1, 0, config->bucket[i].size_bytes * 8, value);
buf->used += 1 + config->bucket[i].size_bytes;
}
}
}
riscv_batch_free(batch);
}
return ERROR_OK;
}
static int sample_memory(struct target *target,
riscv_sample_buf_t *buf,
riscv_sample_config_t *config,
int64_t until_ms)
{
if (!config->enabled)
return ERROR_OK;
return sample_memory_bus_v1(target, buf, config, until_ms);
}
static int init_target(struct command_context *cmd_ctx,
struct target *target)
{
@ -2062,6 +2276,7 @@ static int init_target(struct command_context *cmd_ctx,
generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
if (!generic_info->version_specific)
return ERROR_FAIL;
generic_info->sample_memory = sample_memory;
riscv013_info_t *info = get_info(target);
info->progbufsize = -1;
@ -2299,24 +2514,6 @@ static int read_memory_bus_word(struct target *target, target_addr_t address,
return ERROR_OK;
}
static uint32_t sb_sbaccess(unsigned size_bytes)
{
switch (size_bytes) {
case 1:
return set_field(0, DM_SBCS_SBACCESS, 0);
case 2:
return set_field(0, DM_SBCS_SBACCESS, 1);
case 4:
return set_field(0, DM_SBCS_SBACCESS, 2);
case 8:
return set_field(0, DM_SBCS_SBACCESS, 3);
case 16:
return set_field(0, DM_SBCS_SBACCESS, 4);
}
assert(0);
return 0; /* Make mingw happy. */
}
static target_addr_t sb_read_address(struct target *target)
{
RISCV013_INFO(info);
@ -2333,20 +2530,6 @@ static target_addr_t sb_read_address(struct target *target)
return address;
}
static int sb_write_address(struct target *target, target_addr_t address)
{
RISCV013_INFO(info);
unsigned sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE);
/* There currently is no support for >64-bit addresses in OpenOCD. */
if (sbasize > 96)
dmi_write(target, DM_SBADDRESS3, 0);
if (sbasize > 64)
dmi_write(target, DM_SBADDRESS2, 0);
if (sbasize > 32)
dmi_write(target, DM_SBADDRESS1, address >> 32);
return dmi_write(target, DM_SBADDRESS0, address);
}
static int read_sbcs_nonbusy(struct target *target, uint32_t *sbcs)
{
time_t start = time(NULL);
@ -2474,6 +2657,10 @@ static int read_memory_bus_v0(struct target *target, target_addr_t address,
}
}
uint32_t sbcs;
if (dmi_read(target, &sbcs, DM_SBCS) != ERROR_OK)
return ERROR_FAIL;
return ERROR_OK;
}
@ -2503,7 +2690,7 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address,
return ERROR_FAIL;
/* This address write will trigger the first read. */
if (sb_write_address(target, next_address) != ERROR_OK)
if (sb_write_address(target, next_address, true) != ERROR_OK)
return ERROR_FAIL;
if (info->bus_master_read_delay) {
@ -2613,21 +2800,6 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address,
return ERROR_OK;
}
static int batch_run(const struct target *target, struct riscv_batch *batch)
{
RISCV013_INFO(info);
RISCV_INFO(r);
if (r->reset_delays_wait >= 0) {
r->reset_delays_wait -= batch->used_scans;
if (r->reset_delays_wait <= 0) {
batch->idle_count = 0;
info->dmi_busy_delay = 0;
info->ac_busy_delay = 0;
}
}
return riscv_batch_run(batch);
}
static void log_mem_access_result(struct target *target, bool success, int method, bool read)
{
RISCV_INFO(r);
@ -2708,11 +2880,7 @@ static bool mem_should_skip_sysbus(struct target *target, target_addr_t address,
assert(skip_reason);
RISCV013_INFO(info);
if ((!get_field(info->sbcs, DM_SBCS_SBACCESS8) || size != 1) &&
(!get_field(info->sbcs, DM_SBCS_SBACCESS16) || size != 2) &&
(!get_field(info->sbcs, DM_SBCS_SBACCESS32) || size != 4) &&
(!get_field(info->sbcs, DM_SBCS_SBACCESS64) || size != 8) &&
(!get_field(info->sbcs, DM_SBCS_SBACCESS128) || size != 16)) {
if (!sba_supports_access(target, size)) {
LOG_DEBUG("Skipping mem %s via system bus - unsupported size.",
read ? "read" : "write");
*skip_reason = "skipped (unsupported size)";
@ -3475,7 +3643,7 @@ static int write_memory_bus_v1(struct target *target, target_addr_t address,
int result;
sb_write_address(target, next_address);
sb_write_address(target, next_address, true);
while (next_address < end_address) {
LOG_DEBUG("transferring burst starting at address 0x%" TARGET_PRIxADDR,
next_address);
@ -3848,7 +4016,7 @@ struct target_type riscv013_target = {
.write_memory = write_memory,
.arch_state = arch_state,
.arch_state = arch_state
};
/*** 0.13-specific implementations of various RISC-V helper functions. ***/

View File

@ -15,6 +15,7 @@
#include "jtag/jtag.h"
#include "target/register.h"
#include "target/breakpoints.h"
#include "helper/base64.h"
#include "helper/time_support.h"
#include "riscv.h"
#include "gdb_regs.h"
@ -255,6 +256,21 @@ virt2phys_info_t sv48 = {
.pa_ppn_mask = {0x1ff, 0x1ff, 0x1ff, 0x1ffff},
};
void riscv_sample_buf_maybe_add_timestamp(struct target *target)
{
RISCV_INFO(r);
uint32_t now = timeval_ms() & 0xffffffff;
if (r->sample_buf.used + 5 < r->sample_buf.size &&
(r->sample_buf.used == 0 || r->sample_buf.last_timestamp != now)) {
r->sample_buf.buf[r->sample_buf.used++] = RISCV_SAMPLE_BUF_TIMESTAMP;
r->sample_buf.buf[r->sample_buf.used++] = now & 0xff;
r->sample_buf.buf[r->sample_buf.used++] = (now >> 8) & 0xff;
r->sample_buf.buf[r->sample_buf.used++] = (now >> 16) & 0xff;
r->sample_buf.buf[r->sample_buf.used++] = (now >> 24) & 0xff;
r->sample_buf.last_timestamp = now;
}
}
static int riscv_resume_go_all_harts(struct target *target);
void select_dmi_via_bscan(struct target *target)
@ -2126,11 +2142,58 @@ int set_debug_reason(struct target *target, enum riscv_halt_reason halt_reason)
return ERROR_OK;
}
int sample_memory(struct target *target)
{
RISCV_INFO(r);
if (!r->sample_buf.buf || !r->sample_config.enabled)
return ERROR_OK;
LOG_DEBUG("buf used/size: %d/%d", r->sample_buf.used, r->sample_buf.size);
uint64_t start = timeval_ms();
riscv_sample_buf_maybe_add_timestamp(target);
int result = ERROR_OK;
if (r->sample_memory) {
result = r->sample_memory(target, &r->sample_buf, &r->sample_config,
start + TARGET_DEFAULT_POLLING_INTERVAL);
if (result != ERROR_NOT_IMPLEMENTED)
goto exit;
}
/* Default slow path. */
while (timeval_ms() - start < TARGET_DEFAULT_POLLING_INTERVAL) {
for (unsigned i = 0; i < DIM(r->sample_config.bucket); i++) {
if (r->sample_config.bucket[i].enabled &&
r->sample_buf.used + 1 + r->sample_config.bucket[i].size_bytes < r->sample_buf.size) {
assert(i < RISCV_SAMPLE_BUF_TIMESTAMP);
r->sample_buf.buf[r->sample_buf.used] = i;
result = riscv_read_phys_memory(
target, r->sample_config.bucket[i].address,
r->sample_config.bucket[i].size_bytes, 1,
r->sample_buf.buf + r->sample_buf.used + 1);
if (result == ERROR_OK)
r->sample_buf.used += 1 + r->sample_config.bucket[i].size_bytes;
else
goto exit;
}
}
}
exit:
if (result != ERROR_OK) {
LOG_INFO("Turning off memory sampling because it failed.");
r->sample_config.enabled = false;
}
return result;
}
/*** OpenOCD Interface ***/
int riscv_openocd_poll(struct target *target)
{
LOG_DEBUG("polling all harts");
int halted_hart = -1;
if (riscv_rtos_enabled(target)) {
/* Check every hart for an event. */
for (int i = 0; i < riscv_count_harts(target); ++i) {
@ -2171,14 +2234,12 @@ int riscv_openocd_poll(struct target *target)
} else if (target->smp) {
unsigned halts_discovered = 0;
unsigned total_targets = 0;
bool newly_halted[RISCV_MAX_HARTS] = {0};
unsigned should_remain_halted = 0;
unsigned should_resume = 0;
unsigned i = 0;
for (struct target_list *list = target->head; list != NULL;
list = list->next, i++) {
total_targets++;
struct target *t = list->target;
riscv_info_t *r = riscv_info(t);
assert(i < DIM(newly_halted));
@ -2238,13 +2299,27 @@ int riscv_openocd_poll(struct target *target)
LOG_DEBUG("resume all");
riscv_resume(target, true, 0, 0, 0, false);
}
/* Sample memory if any target is running. */
for (struct target_list *list = target->head; list != NULL;
list = list->next, i++) {
struct target *t = list->target;
if (t->state == TARGET_RUNNING) {
sample_memory(target);
break;
}
}
return ERROR_OK;
} else {
enum riscv_poll_hart out = riscv_poll_hart(target,
riscv_current_hartid(target));
if (out == RPH_NO_CHANGE || out == RPH_DISCOVERED_RUNNING)
if (out == RPH_NO_CHANGE || out == RPH_DISCOVERED_RUNNING) {
if (target->state == TARGET_RUNNING)
sample_memory(target);
return ERROR_OK;
}
else if (out == RPH_ERROR)
return ERROR_FAIL;
@ -2918,7 +2993,159 @@ COMMAND_HANDLER(handle_repeat_read)
return result;
}
COMMAND_HANDLER(handle_memory_sample_command)
{
struct target *target = get_current_target(CMD_CTX);
RISCV_INFO(r);
if (CMD_ARGC == 0) {
command_print(CMD, "Memory sample configuration for %s:", target_name(target));
for (unsigned i = 0; i < DIM(r->sample_config.bucket); i++) {
if (r->sample_config.bucket[i].enabled) {
command_print(CMD, "bucket %d; address=0x%" TARGET_PRIxADDR "; size=%d", i,
r->sample_config.bucket[i].address,
r->sample_config.bucket[i].size_bytes);
} else {
command_print(CMD, "bucket %d; disabled", i);
}
}
return ERROR_OK;
}
if (CMD_ARGC < 2) {
LOG_ERROR("Command requires at least bucket and address arguments.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
if (riscv_rtos_enabled(target)) {
LOG_ERROR("Memory sampling is not supported with `-rtos riscv`.");
return ERROR_FAIL;
}
uint32_t bucket;
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], bucket);
if (bucket > DIM(r->sample_config.bucket)) {
LOG_ERROR("Max bucket number is %d.", (unsigned) DIM(r->sample_config.bucket));
return ERROR_COMMAND_ARGUMENT_INVALID;
}
if (!strcmp(CMD_ARGV[1], "clear")) {
r->sample_config.bucket[bucket].enabled = false;
} else {
COMMAND_PARSE_ADDRESS(CMD_ARGV[1], r->sample_config.bucket[bucket].address);
if (CMD_ARGC > 2) {
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], r->sample_config.bucket[bucket].size_bytes);
if (r->sample_config.bucket[bucket].size_bytes != 4 &&
r->sample_config.bucket[bucket].size_bytes != 8) {
LOG_ERROR("Only 4-byte and 8-byte sizes are supported.");
return ERROR_COMMAND_ARGUMENT_INVALID;
}
} else {
r->sample_config.bucket[bucket].size_bytes = 4;
}
r->sample_config.bucket[bucket].enabled = true;
}
if (!r->sample_buf.buf) {
r->sample_buf.size = 1024 * 1024;
r->sample_buf.buf = malloc(r->sample_buf.size);
}
/* Clear the buffer when the configuration is changed. */
r->sample_buf.used = 0;
r->sample_config.enabled = true;
return ERROR_OK;
}
COMMAND_HANDLER(handle_dump_sample_buf_command)
{
struct target *target = get_current_target(CMD_CTX);
RISCV_INFO(r);
if (CMD_ARGC > 1) {
LOG_ERROR("Command takes at most 1 arguments.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
bool base64 = false;
if (CMD_ARGC > 0) {
if (!strcmp(CMD_ARGV[0], "base64")) {
base64 = true;
} else {
LOG_ERROR("Unknown argument: %s", CMD_ARGV[0]);
return ERROR_COMMAND_SYNTAX_ERROR;
}
}
int result = ERROR_OK;
if (base64) {
unsigned char *encoded = base64_encode(r->sample_buf.buf,
r->sample_buf.used, NULL);
if (!encoded) {
LOG_ERROR("Failed base64 encode!");
result = ERROR_FAIL;
goto error;
}
command_print(CMD, "%s", encoded);
free(encoded);
} else {
unsigned i = 0;
while (i < r->sample_buf.used) {
uint8_t command = r->sample_buf.buf[i++];
if (command == RISCV_SAMPLE_BUF_TIMESTAMP) {
uint32_t timestamp = buf_get_u32(r->sample_buf.buf + i, 0, 32);
i += 4;
command_print(CMD, "timestamp: %u", timestamp);
} else if (command < DIM(r->sample_config.bucket)) {
command_print_sameline(CMD, "0x%" TARGET_PRIxADDR ": ",
r->sample_config.bucket[command].address);
if (r->sample_config.bucket[command].size_bytes == 4) {
uint32_t value = buf_get_u32(r->sample_buf.buf + i, 0, 32);
i += 4;
command_print(CMD, "0x%08" PRIx32, value);
} else if (r->sample_config.bucket[command].size_bytes == 8) {
uint64_t value = buf_get_u64(r->sample_buf.buf + i, 0, 64);
i += 8;
command_print(CMD, "0x%016" PRIx64, value);
} else {
LOG_ERROR("Found invalid size in bucket %d: %d", command,
r->sample_config.bucket[command].size_bytes);
result = ERROR_FAIL;
goto error;
}
} else {
LOG_ERROR("Found invalid command byte in sample buf: 0x%2x at offset 0x%x",
command, i - 1);
result = ERROR_FAIL;
goto error;
}
}
}
error:
/* Clear the sample buffer even when there was an error. */
r->sample_buf.used = 0;
return result;
}
static const struct command_registration riscv_exec_command_handlers[] = {
{
.name = "dump_sample_buf",
.handler = handle_dump_sample_buf_command,
.mode = COMMAND_ANY,
.usage = "riscv dump_sample_buf [base64]",
.help = "Print the contents of the sample buffer, and clear the buffer."
},
{
.name = "memory_sample",
.handler = handle_memory_sample_command,
.mode = COMMAND_ANY,
.usage = "riscv memory_sample bucket address|clear [size=4]",
.help = "Causes OpenOCD to frequently read size bytes at the given address."
},
{
.name = "repeat_read",
.handler = handle_repeat_read,

View File

@ -60,6 +60,23 @@ typedef struct {
unsigned custom_number;
} riscv_reg_info_t;
#define RISCV_SAMPLE_BUF_TIMESTAMP 0x80
typedef struct {
uint8_t *buf;
unsigned used;
unsigned size;
uint32_t last_timestamp;
} riscv_sample_buf_t;
typedef struct {
bool enabled;
struct {
bool enabled;
target_addr_t address;
uint32_t size_bytes;
} bucket[16];
} riscv_sample_config_t;
typedef struct {
struct list_head list;
uint16_t low, high;
@ -169,6 +186,11 @@ typedef struct {
int (*test_compliance)(struct target *target);
int (*sample_memory)(struct target *target,
riscv_sample_buf_t *buf,
riscv_sample_config_t *config,
int64_t until_ms);
int (*read_memory)(struct target *target, target_addr_t address,
uint32_t size, uint32_t count, uint8_t *buffer, uint32_t increment);
@ -211,6 +233,9 @@ typedef struct {
* Custom registers are for non-standard extensions and use abstract register numbers
* from range 0xc000 ... 0xffff. */
struct list_head expose_custom;
riscv_sample_config_t sample_config;
riscv_sample_buf_t sample_buf;
} riscv_info_t;
typedef struct {

View File

@ -158,7 +158,7 @@ static struct target_event_callback *target_event_callbacks;
static struct target_timer_callback *target_timer_callbacks;
LIST_HEAD(target_reset_callback_list);
LIST_HEAD(target_trace_callback_list);
static const int polling_interval = 100;
static const int polling_interval = TARGET_DEFAULT_POLLING_INTERVAL;
static const Jim_Nvp nvp_assert[] = {
{ .name = "assert", NVP_ASSERT },
@ -674,7 +674,7 @@ static int target_process_reset(struct command_invocation *cmd, enum target_rese
}
/* We want any events to be processed before the prompt */
retval = target_call_timer_callbacks_now();
retval = target_call_timer_callbacks_now(NULL);
for (target = all_targets; target; target = target->next) {
target->type->check_reset(target);
@ -1534,8 +1534,7 @@ int target_register_timer_callback(int (*callback)(void *priv),
(*callbacks_p)->time_ms = time_ms;
(*callbacks_p)->removed = false;
gettimeofday(&(*callbacks_p)->when, NULL);
timeval_add_time(&(*callbacks_p)->when, 0, time_ms * 1000);
(*callbacks_p)->when = timeval_ms() + time_ms;
(*callbacks_p)->priv = priv;
(*callbacks_p)->next = NULL;
@ -1669,15 +1668,14 @@ int target_call_trace_callbacks(struct target *target, size_t len, uint8_t *data
}
static int target_timer_callback_periodic_restart(
struct target_timer_callback *cb, struct timeval *now)
struct target_timer_callback *cb, int64_t *now)
{
cb->when = *now;
timeval_add_time(&cb->when, 0, cb->time_ms * 1000L);
cb->when = *now + cb->time_ms;
return ERROR_OK;
}
static int target_call_timer_callback(struct target_timer_callback *cb,
struct timeval *now)
int64_t *now)
{
cb->callback(cb->priv);
@ -1687,7 +1685,7 @@ static int target_call_timer_callback(struct target_timer_callback *cb,
return target_unregister_timer_callback(cb->callback, cb->priv);
}
static int target_call_timer_callbacks_check_time(int checktime)
static int target_call_timer_callbacks_check_time(int64_t *next_event, int checktime)
{
static bool callback_processing;
@ -1699,8 +1697,9 @@ static int target_call_timer_callbacks_check_time(int checktime)
keep_alive();
struct timeval now;
gettimeofday(&now, NULL);
int64_t now = timeval_ms();
if (next_event)
*next_event = now + 1000;
/* Store an address of the place containing a pointer to the
* next item; initially, that's a standalone "root of the
@ -1716,11 +1715,14 @@ static int target_call_timer_callbacks_check_time(int checktime)
bool call_it = (*callback)->callback &&
((!checktime && (*callback)->type == TARGET_TIMER_TYPE_PERIODIC) ||
timeval_compare(&now, &(*callback)->when) >= 0);
now >= (*callback)->when);
if (call_it)
target_call_timer_callback(*callback, &now);
if (next_event && (*callback)->when < *next_event)
*next_event = (*callback)->when;
callback = &(*callback)->next;
}
@ -1728,15 +1730,19 @@ static int target_call_timer_callbacks_check_time(int checktime)
return ERROR_OK;
}
int target_call_timer_callbacks(void)
/**
* This function updates *next_event with the time (according to timeval_ms())
* that it would next need to be called in order to run all callbacks on time.
*/
int target_call_timer_callbacks(int64_t *next_event)
{
return target_call_timer_callbacks_check_time(1);
return target_call_timer_callbacks_check_time(next_event, 1);
}
/* invoke periodic callbacks immediately */
int target_call_timer_callbacks_now(void)
int target_call_timer_callbacks_now(int64_t *next_event)
{
return target_call_timer_callbacks_check_time(0);
return target_call_timer_callbacks_check_time(next_event, 0);
}
/* Prints the working area layout for debug purposes */

View File

@ -329,7 +329,7 @@ struct target_timer_callback {
unsigned int time_ms;
enum target_timer_type type;
bool removed;
struct timeval when;
int64_t when; /* output of timeval_ms() */
void *priv;
struct target_timer_callback *next;
};
@ -397,12 +397,12 @@ int target_call_trace_callbacks(struct target *target, size_t len, uint8_t *data
int target_register_timer_callback(int (*callback)(void *priv),
unsigned int time_ms, enum target_timer_type type, void *priv);
int target_unregister_timer_callback(int (*callback)(void *priv), void *priv);
int target_call_timer_callbacks(void);
int target_call_timer_callbacks(int64_t *next_event);
/**
* Invoke this to ensure that e.g. polling timer callbacks happen before
* a synchronous command completes.
*/
int target_call_timer_callbacks_now(void);
int target_call_timer_callbacks_now(int64_t *next_event);
struct target *get_target_by_num(int num);
struct target *get_current_target(struct command_context *cmd_ctx);
@ -771,4 +771,6 @@ void target_handle_md_output(struct command_invocation *cmd,
extern bool get_target_reset_nag(void);
#define TARGET_DEFAULT_POLLING_INTERVAL 100
#endif /* OPENOCD_TARGET_TARGET_H */