// simavr "device" for the TSB bootloader: load the boot-linked ELF into the // ATmega328P boot section, enter it (BOOTRST is not modelled, so PC is set 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. #include #include #include #include #include #include #include #include // The parts headers (uart_pty.h) carry no C++ linkage guards of their own, // unlike simavr's core headers - the block covers both harmlessly. extern "C" { #include "avr_uart.h" #include "sim_avr.h" #include "sim_elf.h" #include "uart_pty.h" } namespace { // What the loaders are built for: tsb/*.cpp's device clock and the chip's SPM // page, which is also the config page sitting directly below the boot section. constexpr std::uint32_t clock_hz = 16'000'000; constexpr std::uint32_t page_bytes = 128; avr_t *avr; uart_pty_t uart_pty; [[noreturn]] void finish(int) { uart_pty_stop(&uart_pty); _exit(0); } } // namespace int main(int argc, char *argv[]) { if (argc != 3) { std::println(stderr, "usage: {} ", argv[0]); return 2; } const auto boot_base = static_cast(std::strtoul(argv[2], nullptr, 0)); avr = avr_make_mcu_by_name("atmega328p"); if (!avr) { std::println(stderr, "device: no ATmega328P core"); return 1; } avr_init(avr); avr->frequency = clock_hz; // Real flash powers up erased (0xff); the app region must look erased // before the bootloader programs it. std::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, so it is placed at the // boot section base by hand. elf_firmware_t fw{}; if (elf_read_firmware(argv[1], &fw) != 0) { std::println(stderr, "device: cannot read {}", argv[1]); return 1; } // An image that runs past flash end cannot execute on hardware, and a // naive copy of it would smash the heap beyond avr->flash - after which // the simulation misbehaves in ways that point everywhere but here. // Refuse it loudly instead. if (boot_base + fw.flashsize > avr->flashend + 1) { std::println(stderr, "device: {} B at {:#x} runs past flash end {:#x} - image does not fit its slot", fw.flashsize, boot_base, avr->flashend); return 1; } std::memcpy(avr->flash + boot_base, fw.flash, fw.flashsize); avr->pc = boot_base; avr->codeend = avr->flashend; // Optional: seed the config page with a hex byte string, so the password // gate and emergency erase can be tested. // Layout: [appjump lo][appjump hi][timeout][password...][0xff]. if (const char *cfg = std::getenv("TSB_CONFIG")) { const auto app_end = boot_base - page_bytes; for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) { const std::array pair{cfg[i], cfg[i + 1], '\0'}; avr->flash[app_end + i / 2] = static_cast(std::strtoul(pair.data(), nullptr, 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. std::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'); std::println("TSB_PTY {}", uart_pty.pty.slavename); std::fflush(stdout); std::signal(SIGTERM, finish); std::signal(SIGINT, finish); for (;;) { const auto state = avr_run(avr); if (state == cpu_Done || state == cpu_Crashed) { break; } } finish(0); }