script: add flash_detect and flash_read commands

Read-only SPI flash commands over the BSCAN proxy (via a small adapter
to spi_flash's xfer callback). flash_detect identifies the chip from
its JEDEC ID; flash_read hex-dumps a region.

Validated on the KCU105: flash_detect -> Micron MT25QU256, flash_read
shows the stored bitstream (sync word 0xAA995566 at 0x50). Erase/program
commands deferred to Phase 4.
This commit is contained in:
2026-05-24 00:13:29 +02:00
parent c4afe877ce
commit 350918dbe8

View File

@@ -39,6 +39,7 @@
#include "os_interface/os_interface.h" #include "os_interface/os_interface.h"
#include "fpga/fpga.h" #include "fpga/fpga.h"
#include "bscan_spi/bscan_spi.h" #include "bscan_spi/bscan_spi.h"
#include "spi_flash/spi_flash.h"
#include "env.h" #include "env.h"
@@ -2997,6 +2998,140 @@ static int cmd_bscan_jedec(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR; return JTAG_CORE_NO_ERROR;
} }
/* Adapt the BSCAN proxy to the spi_flash transport callback. */
struct flash_proxy_ctx {
jtag_core *jc;
const fpga_target *t;
};
static int flash_proxy_xfer(void *c, const uint8_t *tx, size_t txlen,
uint8_t *rx, size_t rxlen)
{
struct flash_proxy_ctx *p = (struct flash_proxy_ctx *)c;
return bscan_spi_xfer(p->jc, p->t, tx, txlen, rx, rxlen);
}
/* Look up the FPGA target for a chain device and wire a spi_flash over
* the proxy. Returns 0 and fills *sf/*pc, or prints an error and returns
* a JTAG_CORE_* code. */
static int flash_setup(script_ctx *ctx, int device,
spi_flash *sf, struct flash_proxy_ctx *pc)
{
jtag_core *jc = (jtag_core *)ctx->app_ctx;
unsigned long idcode;
const fpga_target *t;
if (jtagcore_get_number_of_devices(jc) <= 0) {
ctx->script_printf(ctx, MSG_ERROR, "No device on the chain. Run jtag_autoinit first.\n");
return JTAG_CORE_NOT_FOUND;
}
idcode = jtagcore_get_dev_id(jc, device);
t = fpga_lookup_by_idcode(idcode);
if (!t) {
ctx->script_printf(ctx, MSG_ERROR, "Device %d IDCODE 0x%.8lX not in FPGA registry\n", device, idcode);
return JTAG_CORE_NOT_FOUND;
}
pc->jc = jc;
pc->t = t;
memset(sf, 0, sizeof(*sf));
sf->xfer = flash_proxy_xfer;
sf->ctx = pc;
return JTAG_CORE_NO_ERROR;
}
const char *cmd_flash_detect_help[] = {
"<device>",
"Identify the SPI flash behind the loaded BSCAN proxy: read its JEDEC",
"ID and match the built-in chip database. Requires a proxy loaded.",
""};
static int cmd_flash_detect(script_ctx *ctx, char *line)
{
char dev_txt[DEFAULT_BUFLEN];
struct flash_proxy_ctx pc;
spi_flash sf;
int device, r;
if (get_param(ctx, line, 1, dev_txt) < 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: flash_detect <device>\n");
return JTAG_CORE_BAD_PARAMETER;
}
device = (int)strtoul(dev_txt, NULL, 0);
if ((r = flash_setup(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
r = spi_flash_detect(&sf);
if (r < 0) {
ctx->script_printf(ctx, MSG_ERROR, "spi_flash_detect failed (proxy not loaded?)\n");
return JTAG_CORE_IO_ERROR;
}
ctx->script_printf(ctx, MSG_INFO_0, "JEDEC ID: %.2X %.2X %.2X\n",
sf.jedec[0], sf.jedec[1], sf.jedec[2]);
ctx->last_data_value = (sf.jedec[0] << 16) | (sf.jedec[1] << 8) | sf.jedec[2];
if (!sf.chip) {
ctx->script_printf(ctx, MSG_WARNING, "Flash not in database — add it to spi_flash.c\n");
return JTAG_CORE_NO_ERROR;
}
ctx->script_printf(ctx, MSG_INFO_0,
"Flash: %s - %u MB, page %u B, sector %u B, %d-byte address\n",
sf.chip->name, sf.chip->size_bytes / (1024u * 1024u),
sf.chip->page_size, sf.chip->sector_size, sf.chip->addr_bytes);
return JTAG_CORE_NO_ERROR;
}
const char *cmd_flash_read_help[] = {
"<device> <addr> <len>",
"Read <len> bytes from the SPI flash at <addr> (hex) and hex-dump them.",
"Read-only. Requires a proxy loaded and a flash recognised by flash_detect.",
""};
static int cmd_flash_read(script_ctx *ctx, char *line)
{
char dev_txt[DEFAULT_BUFLEN], addr_txt[DEFAULT_BUFLEN], len_txt[DEFAULT_BUFLEN];
struct flash_proxy_ctx pc;
spi_flash sf;
uint8_t *buf;
uint32_t addr;
size_t len, i;
int device, r;
if (get_param(ctx, line, 1, dev_txt) < 0 || get_param(ctx, line, 2, addr_txt) < 0 ||
get_param(ctx, line, 3, len_txt) < 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: flash_read <device> <addr> <len>\n");
return JTAG_CORE_BAD_PARAMETER;
}
device = (int)strtoul(dev_txt, NULL, 0);
addr = (uint32_t)strtoul(addr_txt, NULL, 0);
len = (size_t)strtoul(len_txt, NULL, 0);
if (len == 0) return JTAG_CORE_NO_ERROR;
if (len > 65536) {
ctx->script_printf(ctx, MSG_ERROR, "flash_read: len capped at 65536 for an interactive dump\n");
return JTAG_CORE_BAD_PARAMETER;
}
if ((r = flash_setup(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
if (spi_flash_detect(&sf) < 0 || !sf.chip) {
ctx->script_printf(ctx, MSG_ERROR, "Flash not detected/known — run flash_detect first.\n");
return JTAG_CORE_NOT_FOUND;
}
buf = malloc(len);
if (!buf) return JTAG_CORE_BAD_PARAMETER;
if (spi_flash_read(&sf, addr, buf, len) < 0) {
free(buf);
ctx->script_printf(ctx, MSG_ERROR, "spi_flash_read failed\n");
return JTAG_CORE_IO_ERROR;
}
for (i = 0; i < len; i += 16) {
char linebuf[16 * 3 + 16];
size_t j, pos = 0;
pos += (size_t)snprintf(linebuf + pos, sizeof(linebuf) - pos, "%.8X: ", (unsigned)(addr + i));
for (j = 0; j < 16 && (i + j) < len; j++) {
pos += (size_t)snprintf(linebuf + pos, sizeof(linebuf) - pos, "%.2X ", buf[i + j]);
}
ctx->script_printf(ctx, MSG_INFO_0, "%s\n", linebuf);
}
free(buf);
return JTAG_CORE_NO_ERROR;
}
cmd_list script_commands_list[] = cmd_list script_commands_list[] =
{ {
{"print", cmd_print, cmd_print_help}, {"print", cmd_print, cmd_print_help},
@@ -3045,6 +3180,8 @@ cmd_list script_commands_list[] =
{"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help}, {"bscan_shift_dr", cmd_bscan_shift_dr, cmd_bscan_shift_dr_help},
{"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help}, {"bscan_load_bitstream", cmd_bscan_load_bitstream, cmd_bscan_load_bitstream_help},
{"bscan_jedec", cmd_bscan_jedec, cmd_bscan_jedec_help}, {"bscan_jedec", cmd_bscan_jedec, cmd_bscan_jedec_help},
{"flash_detect", cmd_flash_detect, cmd_flash_detect_help},
{"flash_read", cmd_flash_read, cmd_flash_read_help},
{0, 0}}; {0, 0}};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////