// Test-fixture application for the pureboot protocol tests: prints "APP" on // the chip's serial link (the same link the loader uses) — the proof that // the loader's hand-over, and on the tinies the host's reset-vector // surgery, actually launched it. Linked normally (crt, vectors at 0); on // the tinies its reset vector is the rjmp the host re-homes. // // On the hardware-USART link it then listens, and an 'L' makes it jump into // the resident loader — the application-owned loader entry a // BOOTRST-unprogrammed mega relies on (reset always boots the application // there), exercised by the self-update tests. The software link idles: // reset reaches those loaders through the patched vector (or the runner // models BOOTRST), so the application owes them nothing. // // PUREBOOT_HANDOVER drops the listening and jumps straight in, leaving the // USART enabled behind it — the hand-over state a loader bit-banging on that // USART's own pins has to survive. // // The fixture speaks the deployment its loader was built for: the same // PUREBOOT_* defines configure it, and without them it assumes the stock // deployment (the crystal/RC clock table below, the chip's natural link). #include using namespace avr::literals; namespace { consteval avr::hertz_t clock() { #if defined(PUREBOOT_CLOCK_HZ) return avr::hertz_t{PUREBOOT_CLOCK_HZ}; #else auto name = std::string_view{avr::hw::db.name}; if (name.starts_with("ATtiny13")) return 9.6_MHz; if (name.starts_with("ATtiny")) return 8_MHz; return 16_MHz; #endif } #if !defined(PUREBOOT_TX) #define PUREBOOT_TX pb1 #endif #if !defined(PUREBOOT_USART) #define PUREBOOT_USART 0 #endif consteval bool use_hardware() { #if defined(PUREBOOT_SOFT_SERIAL) return false; #else return avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART"); #endif } using dev = avr::device<{.clock = clock()}>; template struct link { #if defined(PUREBOOT_BAUD) static constexpr avr::baud_t baud{PUREBOOT_BAUD}; #else static constexpr avr::baud_t baud{115200}; #endif using tx_t = avr::uart::usart<'0' + PUREBOOT_USART, C, {.baud = baud, .max_baud_error = 2.5_pct}>; static void tx(char c) { tx_t::write(static_cast(c)); } // The loader sits in the top slot — 512 bytes on every chip. The jump // takes a word address, which is what makes the >64 KiB chips' entry // reachable through a 16-bit pointer at all. static void enter_loader() { constexpr std::uint32_t slot = 512; reinterpret_cast(static_cast((avr::hw::db.mem.flash_size - slot) / 2))(); } [[noreturn]] static void idle() { #if defined(PUREBOOT_HANDOVER) // Hand back at once, with this USART still enabled — the state that // leaves a bit-banged loader on its pins mute unless the loader // releases it. Unconditional because there is no command wire to // wait on: that loader's link is the pins, not this peripheral. enter_loader(); __builtin_unreachable(); #else for (;;) { auto command = tx_t::read_blocking(); if (command == 'L') enter_loader(); // 'D' leaves every word of the SPM page buffer dirty, so that a // following 'L' enters the loader with the buffer it never clears. if (command == 'D') { for (std::uint16_t at = 0; at < avr::spm::page_bytes; at += 2) avr::spm::fill(at, 0xdead); tx('D'); } } #endif } }; template struct link { #if defined(PUREBOOT_BAUD) static constexpr avr::baud_t baud{PUREBOOT_BAUD}; #else static constexpr avr::baud_t baud{57600}; #endif using tx_t = avr::uart::software_tx; static void tx(char c) { tx_t::write(static_cast(c)); } [[noreturn]] static void idle() { while (true) { } } }; } // namespace int main() { avr::init::tx_t>(); #if !defined(PUREBOOT_HANDOVER) link::tx('A'); link::tx('P'); link::tx('P'); #endif // The hand-over fixture stays silent: nothing is listening on the USART it // brings up — the loader it hands to speaks those pins directly — so its // banner would be a write into a peer that does not exist. link::idle(); }