pureboot: harden the sim device runner
Cancel the GPIO bridge's cycle timers with the state they drive: avr_reset drops the TX latch, whose falling edge starts a spurious decode before bridge_reset runs, and the stale sampler then interleaves with the loader's first real answer through the shared shift state — the first post-reset replies came back corrupted and the knock retries burned the activation window into the application. Wrap the mega's registered flash ioctl to re-dispatch page erases with Z masked to the page boundary: simavr's PGERS handler erases spm_pagesize bytes from Z & ~1 (its PGWRT path masks correctly), wiping the neighbouring page when Z sits past the page start, which hardware permits (§26.8.1). Model the write-once temporary buffer in the tiny NVM module — silicon refuses a second load per word until the buffer clears, and a last-write- wins model masks real firmware bugs. Optional arguments select the reset vector (the mega's fuse profiles) and a raw flash image to resume from (power-fail tests re-enter a dumped state). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,43 @@ static const char *dump_path;
|
||||
static uint32_t reset_pc;
|
||||
static volatile sig_atomic_t reset_requested;
|
||||
|
||||
// simavr 1.6's avr_flash PGERS handler erases spm_pagesize bytes starting at
|
||||
// Z & ~1 instead of the page containing Z (its PGWRT path masks correctly) —
|
||||
// hardware ignores the in-page bits (§26.8.1), so an erase issued with Z
|
||||
// anywhere inside the page wipes half the neighbouring page in simulation
|
||||
// only. Wrap the mega's registered flash ioctl and re-dispatch page erases
|
||||
// with Z forced to the page boundary; everything else passes through.
|
||||
static avr_flash_t *mega_flash;
|
||||
static int (*mega_flash_ioctl)(avr_io_t *io, uint32_t ctl, void *param);
|
||||
|
||||
static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
||||
{
|
||||
if (ctl == AVR_IOCTL_FLASH_SPM && avr_regbit_get(io->avr, mega_flash->pgers)) {
|
||||
uint16_t z = (uint16_t)(io->avr->data[30] | (io->avr->data[31] << 8));
|
||||
uint16_t masked = (uint16_t)(z & ~(mega_flash->spm_pagesize - 1));
|
||||
io->avr->data[30] = (uint8_t)masked;
|
||||
io->avr->data[31] = (uint8_t)(masked >> 8);
|
||||
int result = mega_flash_ioctl(io, ctl, param);
|
||||
io->avr->data[30] = (uint8_t)z;
|
||||
io->avr->data[31] = (uint8_t)(z >> 8);
|
||||
return result;
|
||||
}
|
||||
return mega_flash_ioctl(io, ctl, param);
|
||||
}
|
||||
|
||||
static void fix_mega_flash_erase(void)
|
||||
{
|
||||
for (avr_io_t *io = avr->io_port; io; io = io->next) {
|
||||
if (io->kind && strcmp(io->kind, "flash") == 0) {
|
||||
mega_flash = (avr_flash_t *)io;
|
||||
mega_flash_ioctl = io->ioctl;
|
||||
io->ioctl = fixed_flash_ioctl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "device: no flash module to fix — SPM page erases may misalign\n");
|
||||
}
|
||||
|
||||
static void request_reset(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
@@ -54,6 +91,7 @@ static void request_reset(int sig)
|
||||
typedef struct {
|
||||
avr_io_t io;
|
||||
uint8_t buffer[128];
|
||||
uint8_t used[128]; // a buffer word loads once until erased — like silicon
|
||||
unsigned page;
|
||||
} tiny_nvm_t;
|
||||
|
||||
@@ -71,16 +109,21 @@ static int nvm_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
||||
uint32_t page_base = (uint32_t)(z & ~(n->page - 1)) % (mcu->flashend + 1);
|
||||
if (command == 0x01) { // SPMEN alone: buffer fill from r1:r0
|
||||
unsigned offset = z & (n->page - 1) & ~1u;
|
||||
if (!n->used[offset]) { // first write wins until the buffer clears
|
||||
n->buffer[offset] = mcu->data[0];
|
||||
n->buffer[offset + 1] = mcu->data[1];
|
||||
n->used[offset] = 1;
|
||||
}
|
||||
} else if (command == 0x03) { // PGERS
|
||||
memset(mcu->flash + page_base, 0xff, n->page);
|
||||
} else if (command == 0x05) { // PGWRT: programming only clears bits
|
||||
for (unsigned i = 0; i < n->page; i++)
|
||||
mcu->flash[page_base + i] &= n->buffer[i];
|
||||
memset(n->buffer, 0xff, n->page);
|
||||
memset(n->used, 0, n->page);
|
||||
} else if (command == 0x11) { // CTPB
|
||||
memset(n->buffer, 0xff, n->page);
|
||||
memset(n->used, 0, n->page);
|
||||
}
|
||||
mcu->data[0x57] &= (uint8_t)~0x1f; // the operation completes instantly
|
||||
return 0;
|
||||
@@ -162,8 +205,14 @@ static void rx_start_next(void)
|
||||
// A reset abandons whatever the bridge was mid-transfer: bytes still queued
|
||||
// for a chip that no longer has the context to receive them meaningfully,
|
||||
// and a decode in progress on a TX line the reset may have already changed.
|
||||
// The pending cycle timers must go with the state: avr_reset drops the TX
|
||||
// output latch, whose falling edge starts a spurious decode before this
|
||||
// runs, and a stale tx_sample would then interleave with the loader's first
|
||||
// real answer through the shared shift state, corrupting it.
|
||||
static void bridge_reset(void)
|
||||
{
|
||||
avr_cycle_timer_cancel(avr, tx_sample, NULL);
|
||||
avr_cycle_timer_cancel(avr, rx_step, NULL);
|
||||
rx_head = rx_tail = 0;
|
||||
rx_active = 0;
|
||||
tx_active = 0;
|
||||
@@ -215,8 +264,14 @@ static void finish(int sig)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 8) {
|
||||
fprintf(stderr, "usage: %s <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>\n", argv[0]);
|
||||
if (argc < 8 || argc > 10) {
|
||||
fprintf(stderr,
|
||||
"usage: %s <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
||||
" [reset_hex] [resume_flash]\n"
|
||||
" reset_hex: reset vector (default: base on the mega, 0 on the tinies)\n"
|
||||
" resume_flash: raw full-flash image loaded instead of the ELF — a prior\n"
|
||||
" run's dump, for power-fail resume tests\n",
|
||||
argv[0]);
|
||||
return 2;
|
||||
}
|
||||
const char *mcu_name = argv[2];
|
||||
@@ -235,16 +290,27 @@ int main(int argc, char *argv[])
|
||||
avr->frequency = (uint32_t)strtoul(argv[3], NULL, 0);
|
||||
memset(avr->flash, 0xff, avr->flashend + 1); // real flash powers up erased
|
||||
|
||||
if (argc > 9) {
|
||||
// Resume: the full flash image of an interrupted prior run.
|
||||
FILE *f = fopen(argv[9], "rb");
|
||||
if (!f || fread(avr->flash, 1, avr->flashend + 1, f) == 0) {
|
||||
fprintf(stderr, "device: cannot read %s\n", argv[9]);
|
||||
return 1;
|
||||
}
|
||||
fclose(f);
|
||||
} else {
|
||||
elf_firmware_t fw = {0};
|
||||
if (elf_read_firmware(argv[1], &fw) != 0) {
|
||||
fprintf(stderr, "device: cannot read %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
memcpy(avr->flash + base, fw.flash, fw.flashsize);
|
||||
// The mega enters the loader in hardware (BOOTRST, not modeled); the
|
||||
// tinies reset to word 0 like silicon — erased flash walks up into the
|
||||
// loader, and after the host's surgery the patched vector routes there.
|
||||
reset_pc = use_uart_pty ? base : 0;
|
||||
}
|
||||
// The mega enters the loader in hardware (BOOTRST, not modeled — the
|
||||
// argument picks the modeled fuse's target); the tinies reset to word 0
|
||||
// like silicon — erased flash walks up into the loader, and after the
|
||||
// host's surgery the patched vector routes there.
|
||||
reset_pc = argc > 8 ? (uint32_t)strtoul(argv[8], NULL, 0) : (use_uart_pty ? base : 0);
|
||||
avr->pc = reset_pc;
|
||||
avr->codeend = avr->flashend;
|
||||
|
||||
@@ -258,6 +324,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
if (use_uart_pty) {
|
||||
fix_mega_flash_erase();
|
||||
// POLL_SLEEP paces an idle-polling loader in host real time (a
|
||||
// no-hardware CPU-saving hack); clear it so cycles run free.
|
||||
uint32_t flags = 0;
|
||||
|
||||
Reference in New Issue
Block a user