Files
bootloader/test/device.cpp
T
BlackMarkandClaude Opus 5.5 bc671618a7 test: the simavr device stops dumping a flash image nobody reads
Every protocol run wrote the simulated flash to /tmp/tsb_dump.bin on exit -
outside the repository, from four tests that run in parallel into one file - for
a cross-check no test made. The dump goes, and the device's clock and page size
are named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:41:55 +02:00

120 lines
3.9 KiB
C++

// 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 <array>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <print>
#include <unistd.h>
// 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: {} <tsb.elf> <boot_base_hex>", argv[0]);
return 2;
}
const auto boot_base = static_cast<std::uint32_t>(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::uint8_t>(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);
}