// simavr "device" for the TSB bootloader: load the boot-linked ELF into the // ATmega328P boot section, enter it (BOOTRST is not modelled, so we set PC to // the boot base, exactly as simavr's own board_simduino does), and expose // UART0 as a pty. A host client (Python pyserial, or the real tsbloader) then // speaks the TSB protocol over that pty and actually flashes the device. // // SPM genuinely writes avr->flash on the mega cores, so on exit (or SIGTERM) // we dump the flash image to a file for a ground-truth cross-check against // what the client read back through the bootloader. #include #include #include #include #include #include #include "avr_uart.h" #include "sim_avr.h" #include "sim_elf.h" #include "uart_pty.h" static avr_t *avr; static uart_pty_t uart_pty; static const char *dump_path; static void finish(int sig) { (void)sig; if (dump_path) { FILE *f = fopen(dump_path, "wb"); if (f) { fwrite(avr->flash, 1, avr->flashend + 1, f); fclose(f); } } uart_pty_stop(&uart_pty); _exit(0); } int main(int argc, char *argv[]) { if (argc < 3) { fprintf(stderr, "usage: %s [flash_dump.bin]\n", argv[0]); return 2; } uint32_t boot_base = (uint32_t)strtoul(argv[2], NULL, 0); dump_path = argc >= 4 ? argv[3] : NULL; avr = avr_make_mcu_by_name("atmega328p"); if (!avr) { fprintf(stderr, "device: no ATmega328P core\n"); return 1; } avr_init(avr); avr->frequency = 16000000; // Real flash powers up erased (0xff); the app region must look erased // before the bootloader programs it. memset(avr->flash, 0xff, avr->flashend + 1); // simavr's ELF loader flattens the flash base to 0 (it expects an app at // 0x0), but it hands back the boot code in fw.flash; place it at the boot // section base ourselves and enter there (BOOTRST is not modelled). 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 + boot_base, fw.flash, fw.flashsize); avr->pc = boot_base; avr->codeend = avr->flashend; // Optional: seed the config page (one page below the boot section) with a // hex byte string, so the password gate and emergency erase can be tested. // Layout: [appjump lo][appjump hi][timeout][password...][0xff]. const char *cfg = getenv("TSB_CONFIG"); if (cfg) { uint32_t app_end = boot_base - 128; // config page sits directly below the boot code for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) { char b[3] = {cfg[i], cfg[i + 1], 0}; avr->flash[app_end + i / 2] = (uint8_t)strtoul(b, NULL, 16); } } // POLL_SLEEP makes simavr usleep(1) on every status-register read while the // UART is idle — a host-CPU-saving hack that models no hardware and paces a // tight-polling loader (one that releases TX between bytes, as one-wire does) // in real time, distorting protocol timing. Clear it so the loader runs at // true cycle speed. uint32_t uflags = 0; avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS('0'), &uflags); uflags &= ~AVR_UART_FLAG_POLL_SLEEP; avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS('0'), &uflags); uart_pty_init(avr, &uart_pty); uart_pty_connect(&uart_pty, '0'); printf("TSB_PTY %s\n", uart_pty.pty.slavename); fflush(stdout); signal(SIGTERM, finish); signal(SIGINT, finish); for (;;) { int state = avr_run(avr); if (state == cpu_Done || state == cpu_Crashed) break; } finish(0); return 0; }