// The TinySafeBoot native-UART fixed-baud protocol, as every tier speaks it: // the link, the handshake and command bytes, the boot geometry and the // device-info block the host reads on activation. #pragma once #include namespace tsb { using namespace avr::literals; namespace spm = avr::spm; namespace ee = avr::eeprom; constexpr auto clock = 16_MHz; constexpr auto baud_rate = 115200_Bd; // Fixed 8N1; the library solves UBRR + U2X from clock and baud. constexpr auto baud = avr::uart::solve_baud(clock, baud_rate, 8, avr::uart::parity::none); // One bit time on the wire: the turn-around a shared-line peer needs to stop // driving before this one starts. constexpr auto guard_cycles = static_cast(clock.hz / baud.actual); // The loader is purely polled - it never enables interrupts - so every SPM and // EEPROM lock folds to nothing under this posture. constexpr auto off = avr::irq::guard_policy::unused; // Strict request/response: every SPM operation is waited out before the next // byte moves, so no flash operation is ever in flight at an EEPROM access - // the write procedure's step 2 has nothing to guard, the omission the // datasheet grants (DS40002061B section 8.6.3). constexpr auto no_spm = ee::spm_interlock::omitted; constexpr std::uint8_t confirm = '!'; constexpr std::uint8_t request = '?'; constexpr std::uint8_t knock = '@'; constexpr std::uint8_t activation_knocks = 3; namespace command { constexpr std::uint8_t read_flash = 'f'; constexpr std::uint8_t write_flash = 'F'; constexpr std::uint8_t read_eeprom = 'e'; constexpr std::uint8_t write_eeprom = 'E'; constexpr std::uint8_t read_config = 'c'; constexpr std::uint8_t write_config = 'C'; } // namespace command // The lockout-proof floor for the receive window: the oracle's F_CPU/1MHz. constexpr auto act_min = static_cast(clock.hz / 1'000'000); // Post-activation window: the host gets seconds, not milliseconds, mid-session. constexpr std::uint8_t comm_window = 200; constexpr std::uint16_t page = spm::page_bytes; constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1; // The config page (TSB's LASTPAGE), one page below the boot section: the // application's jump vector, then these. consteval std::uint16_t config_page(std::uint16_t boot_bytes) { return spm::flash_bytes - boot_bytes - page; } constexpr std::uint8_t timeout_at = 2; constexpr std::uint8_t password_at = 3; // The firmware version stamp, in the encoding the host decodes. consteval std::uint16_t date_stamp(std::uint16_t year, std::uint16_t month, std::uint16_t day) { return year * 512 + month * 32 + day; } // BUILDSTATE F3: the native-UART fixed-baud lineage. constexpr std::uint8_t build_state = 0xF3; constexpr std::uint8_t atmega_marker = 0xAA; consteval auto info_block(std::uint16_t build_date, std::uint16_t app_end) { const auto low = [](std::uint16_t word) { return static_cast(word); }; const auto high = [](std::uint16_t word) { return static_cast(word >> 8); }; const std::uint16_t app_end_words = app_end / 2; return std::to_array({ 'T', 'S', 'B', low(build_date), high(build_date), build_state, avr::hw::db.signature[0], avr::hw::db.signature[1], avr::hw::db.signature[2], page / 2, low(app_end_words), high(app_end_words), low(eeprom_end), high(eeprom_end), atmega_marker, atmega_marker, }); } inline const std::uint8_t *flash_ptr(std::uint16_t addr) { return reinterpret_cast(addr); } } // namespace tsb