script: add flash_erase/write/verify, flash_read to file (Phase 4)

Completes the flash command set over the BSCAN proxy:
 - flash_erase <dev> <addr> <len>  : erase covering sectors (destructive)
 - flash_write <dev> <addr> <file> : program from a file (no auto-erase)
 - flash_verify <dev> <addr> <file>: compare flash against a file
 - flash_read gains an optional [file] arg for a raw binary dump (backup)

Validated on the KCU105 with a save -> erase -> write-random -> verify
-> erase -> restore round-trip (region left untouched). Apparent write
throughput ~100 KB/s once the proxy is loaded.
This commit is contained in:
2026-05-24 00:43:06 +02:00
parent 6330326513
commit d6f843e081

View File

@@ -3080,40 +3080,70 @@ static int cmd_flash_detect(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR;
}
/* Set up the proxy transport and detect the flash. Returns NO_ERROR with
* sf->chip set, or an error code (with a message already printed). */
static int flash_setup_detected(script_ctx *ctx, int device,
spi_flash *sf, struct flash_proxy_ctx *pc)
{
int r = flash_setup(ctx, device, sf, pc);
if (r != 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;
}
return JTAG_CORE_NO_ERROR;
}
/* Read a whole file into a malloc'd buffer. Caller frees. */
static uint8_t *read_file(const char *path, size_t *out_len)
{
FILE *f = fopen(path, "rb");
long sz;
uint8_t *b;
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
sz = ftell(f);
rewind(f);
if (sz <= 0) { fclose(f); return NULL; }
b = malloc((size_t)sz);
if (!b) { fclose(f); return NULL; }
if (fread(b, 1, (size_t)sz, f) != (size_t)sz) { free(b); fclose(f); return NULL; }
fclose(f);
*out_len = (size_t)sz;
return b;
}
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.",
"<device> <addr> <len> [file]",
"Read <len> bytes from the SPI flash at <addr> (hex). With [file], dump",
"the raw bytes to it (e.g. to back up before erasing); otherwise hex-dump.",
"Read-only. Requires a proxy loaded and a flash known to 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];
char dev_txt[DEFAULT_BUFLEN], addr_txt[DEFAULT_BUFLEN], len_txt[DEFAULT_BUFLEN], file[DEFAULT_BUFLEN];
struct flash_proxy_ctx pc;
spi_flash sf;
uint8_t *buf;
uint32_t addr;
size_t len, i;
int device, r;
int device, r, to_file;
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");
ctx->script_printf(ctx, MSG_ERROR, "Usage: flash_read <device> <addr> <len> [file]\n");
return JTAG_CORE_BAD_PARAMETER;
}
to_file = (get_param(ctx, line, 4, file) > 0);
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");
if (!to_file && len > 65536) {
ctx->script_printf(ctx, MSG_ERROR, "flash_read: len capped at 65536 for a hex dump (use a file for more)\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;
}
if ((r = flash_setup_detected(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
buf = malloc(len);
if (!buf) return JTAG_CORE_BAD_PARAMETER;
@@ -3123,6 +3153,20 @@ static int cmd_flash_read(script_ctx *ctx, char *line)
return JTAG_CORE_IO_ERROR;
}
if (to_file) {
FILE *f = fopen(file, "wb");
size_t wr = 0;
if (f) { wr = fwrite(buf, 1, len, f); fclose(f); }
free(buf);
if (!f || wr != len) {
ctx->script_printf(ctx, MSG_ERROR, "Can't write %s\n", file);
return JTAG_CORE_IO_ERROR;
}
ctx->script_printf(ctx, MSG_INFO_0, "Read %lu bytes from 0x%.8X to %s\n",
(unsigned long)len, (unsigned)addr, file);
return JTAG_CORE_NO_ERROR;
}
for (i = 0; i < len; i += 16) {
char linebuf[16 * 3 + 16];
size_t j, pos = 0;
@@ -3136,6 +3180,130 @@ static int cmd_flash_read(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR;
}
const char *cmd_flash_erase_help[] = {
"<device> <addr> <len>",
"Erase the flash sectors covering [addr, addr+len) (hex). DESTRUCTIVE.",
"Requires a proxy loaded and a flash known to flash_detect.",
""};
static int cmd_flash_erase(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;
uint32_t addr, end, a, ss;
size_t len;
int device, r, nsec = 0;
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_erase <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 ((r = flash_setup_detected(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
ss = sf.chip->sector_size;
end = addr + (uint32_t)len; /* exclusive */
for (a = addr - (addr % ss); a < end; a += ss) {
if (spi_flash_erase_sector(&sf, a) < 0) {
ctx->script_printf(ctx, MSG_ERROR, "Erase failed at 0x%.8X\n", a);
return JTAG_CORE_IO_ERROR;
}
nsec++;
}
ctx->script_printf(ctx, MSG_INFO_0, "Erased %d sector(s) of %u B from 0x%.8X\n",
nsec, ss, addr - (addr % ss));
return JTAG_CORE_NO_ERROR;
}
const char *cmd_flash_write_help[] = {
"<device> <addr> <file>",
"Program the flash at <addr> (hex) with the contents of <file>. DESTRUCTIVE.",
"Does NOT erase first - run flash_erase over the range beforehand.",
""};
static int cmd_flash_write(script_ctx *ctx, char *line)
{
char dev_txt[DEFAULT_BUFLEN], addr_txt[DEFAULT_BUFLEN], file[DEFAULT_BUFLEN];
struct flash_proxy_ctx pc;
spi_flash sf;
uint8_t *data;
uint32_t addr;
size_t len = 0;
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, file) < 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: flash_write <device> <addr> <file>\n");
return JTAG_CORE_BAD_PARAMETER;
}
device = (int)strtoul(dev_txt, NULL, 0);
addr = (uint32_t)strtoul(addr_txt, NULL, 0);
if ((r = flash_setup_detected(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
data = read_file(file, &len);
if (!data) {
ctx->script_printf(ctx, MSG_ERROR, "Can't read %s\n", file);
return JTAG_CORE_IO_ERROR;
}
if (spi_flash_program(&sf, addr, data, len) < 0) {
free(data);
ctx->script_printf(ctx, MSG_ERROR, "spi_flash_program failed\n");
return JTAG_CORE_IO_ERROR;
}
free(data);
ctx->script_printf(ctx, MSG_INFO_0, "Wrote %lu bytes to 0x%.8X\n",
(unsigned long)len, (unsigned)addr);
return JTAG_CORE_NO_ERROR;
}
const char *cmd_flash_verify_help[] = {
"<device> <addr> <file>",
"Compare the flash content at <addr> (hex) against <file>.",
""};
static int cmd_flash_verify(script_ctx *ctx, char *line)
{
char dev_txt[DEFAULT_BUFLEN], addr_txt[DEFAULT_BUFLEN], file[DEFAULT_BUFLEN];
struct flash_proxy_ctx pc;
spi_flash sf;
uint8_t *data;
uint32_t addr, mismatch = 0;
size_t len = 0;
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, file) < 0) {
ctx->script_printf(ctx, MSG_ERROR, "Usage: flash_verify <device> <addr> <file>\n");
return JTAG_CORE_BAD_PARAMETER;
}
device = (int)strtoul(dev_txt, NULL, 0);
addr = (uint32_t)strtoul(addr_txt, NULL, 0);
if ((r = flash_setup_detected(ctx, device, &sf, &pc)) != JTAG_CORE_NO_ERROR) return r;
data = read_file(file, &len);
if (!data) {
ctx->script_printf(ctx, MSG_ERROR, "Can't read %s\n", file);
return JTAG_CORE_IO_ERROR;
}
r = spi_flash_verify(&sf, addr, data, len, &mismatch);
free(data);
if (r < 0) {
ctx->script_printf(ctx, MSG_ERROR, "spi_flash_verify failed\n");
return JTAG_CORE_IO_ERROR;
}
ctx->last_data_value = r; /* 0 = match, 1 = mismatch */
if (r == 1) {
ctx->script_printf(ctx, MSG_WARNING,
"Verify MISMATCH at offset 0x%X (flash 0x%.8X)\n",
(unsigned)mismatch, (unsigned)(addr + mismatch));
return JTAG_CORE_NO_ERROR;
}
ctx->script_printf(ctx, MSG_INFO_0, "Verify OK (%lu bytes)\n", (unsigned long)len);
return JTAG_CORE_NO_ERROR;
}
cmd_list script_commands_list[] =
{
{"print", cmd_print, cmd_print_help},
@@ -3186,6 +3354,9 @@ cmd_list script_commands_list[] =
{"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},
{"flash_erase", cmd_flash_erase, cmd_flash_erase_help},
{"flash_write", cmd_flash_write, cmd_flash_write_help},
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
{0, 0}};
///////////////////////////////////////////////////////////////////////////////