From 57d94cf631ed67ba7c245310e6cb4db069c4e76f Mon Sep 17 00:00:00 2001 From: BlackMark Date: Sun, 19 Jul 2026 13:33:53 +0200 Subject: [PATCH] tsb: refactor the pure tier onto libavr sugar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The showcase tier now leans on the helpers it fed back instead of reaching under them: the info block is an avr::flash_table (no raw [[gnu::progmem]]), a page is filled with spm::fill(addr, span) (no hand-packed lo|hi<<8 loop), and the WDT-reset bail reads field<"MCUSR","WDRF">::test() (no read() & {}(1).value). Zero-overhead throughout: .text stays 740 B, byte-identical across generated and reflect modes, protocol test green. The info block streams through the existing address-based send_flash rather than a range-for over the flash_table — the range-for is a distinct loop that cannot share the loader's one flash streamer, so it would add 14 B for no functional gain. Co-Authored-By: Claude Opus 4.8 --- tsb/tsb_pure.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tsb/tsb_pure.cpp b/tsb/tsb_pure.cpp index c93b8fe..bc8004d 100644 --- a/tsb/tsb_pure.cpp +++ b/tsb/tsb_pure.cpp @@ -4,9 +4,9 @@ // reimplementing the TinySafeBoot wire protocol (native-UART fixed-baud // lineage) on libavr. This variant is written for clarity: well-factored // functions, no compiler-specific size hacks, no inline assembly. The only -// attributes are the ones the task inherently needs — [[gnu::progmem]] for the -// flash-resident info block and the naked reset entry that stands in for the -// absent C runtime. +// attribute is the one the task inherently needs — the naked reset entry that +// stands in for the absent C runtime; the flash-resident info block is a libavr +// flash_table. // // The structure follows the hand-written reference: one SRAM page buffer that // every page transfer shares, separate flash/EEPROM leaf routines (so nothing @@ -48,10 +48,11 @@ constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1; // Firmware version stamp: YY*512 + MM*32 + DD, the encoding the host decodes. constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19; -// The 16-byte device-info block the host reads on activation. Flash-resident so -// it needs no .data (there is no crt to copy it). +// The 16-byte device-info block the host reads on activation. A flash_table +// keeps it in progmem with no .data image (there is no crt to copy one) and +// reads it back through LPM. // clang-format off -[[gnu::progmem]] constexpr std::uint8_t info[16] = { +inline constexpr std::array info_data = { 'T', 'S', 'B', build_date & 0xFF, build_date >> 8, 0xF3, // status byte (native-UART fixed-baud lineage) @@ -62,6 +63,7 @@ constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19; 0xAA, 0xAA, // ATmega processor-type marker (bytes 14 == 15) }; // clang-format on +using info = avr::flash_table; // One page staged in SRAM. Scratch that is always filled before it is read, so // it lives in .noinit — no startup clear (there is no crt to run one) and no @@ -116,8 +118,7 @@ bool request_confirm() // high, as the SPM word buffer wants). void write_flash_page(std::uint16_t addr) { - for (std::uint16_t i = 0; i < page; i += 2) - spm::fill(addr + i, static_cast(buffer[i] | (buffer[i + 1] << 8))); + spm::fill(addr, std::span{buffer, page}); spm::write_page(addr); spm::wait(); } @@ -200,7 +201,7 @@ void write_config() { // A bootloader may be entered by a watchdog reset; the reference loader // hands straight back to the application in that case rather than run. - if (avr::hw::reg<"MCUSR">::read() & avr::hw::field<"MCUSR", "WDRF">{}(1).value) + if (avr::hw::field<"MCUSR", "WDRF">::test()) appjump(); avr::init(); @@ -225,7 +226,7 @@ void write_config() for (;;) { } - send_flash(reinterpret_cast(&info[0]), sizeof(info)); + send_flash(reinterpret_cast(info::storage.data()), info::size()); for (;;) { tx(confirm); // Mainloop ready