The native-UART fixed-baud TinySafeBoot protocol, ported onto libavr as a
crt-free boot-section loader, in three variants that trade clarity for size:
tsb_pure 740 B idiomatic C++: SRAM page buffer, separate flash/EEPROM
leaves, shared framing; the polled `unused` guard posture.
tsb_tricks 658 B unified runtime-flag paths (noinline/noclone), call-saved
global-register page walk — attributes only, no asm.
tsb_asm 508 B streaming store + hand-rolled UART/SPM/EEPROM/erase loops;
fits the 512 B boot section (BOOTSZ=11). Trims the optional
password gate and WDT-reset bail — unreachable in C++ with
both (hand-asm is ~15 % denser). Tiers 1-2 keep them and
live in the 1 KB section they fit.
All three are .text byte-identical across libavr's generated and reflect modes.
The CMake build strips the leaked -O3 (a Release build is silently -O3, not the
-Os this loader is measured against) and gates each variant's size against its
section. A simavr harness (test/device.c + test/tsbtest.py) drives the real wire
protocol over a pty and flashes the device; the size and protocol tests run in
ctest. Verified byte-for-byte against the reference tsbloader_adv (C#/mono):
activate, read info, flash write + verify.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
2.4 KiB
C
87 lines
2.4 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 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 <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.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 <tsb.elf> <boot_base_hex> [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;
|
|
|
|
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;
|
|
}
|