tsb: one spelling of the clock and the baud, and the asm's bits by name
The tricks and asm tiers spelled 16_MHz three times each and now name it once; the policy tier's lean bring-up solves the baud from the link config the serial unit is declared with instead of restating 115200 8N1. The asm tier's immediates - RXEN0, RXC0's bit, SPMEN, PGWRT|SPMEN and the three UART addresses - are operands read from the chip database rather than numbers with comments beside them, so avr/io.h is left for SPMCSR's I/O address alone; pure and tricks drop an include nothing used. Headers that described the code as it was are corrected (pure's "only attribute", the policy tier's history), the rule references leave the comments, and locals are const auto. All four images are byte-identical to before, in both modes for tsb_asm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
20b1485b6f
commit
aafaf25a47
4 files changed
+62
-66
No files matched your search
+24
-20
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
#include <libavr/libavr.hpp>
|
||||||
|
|
||||||
#include <avr/io.h> // SP / RAMEND for the crt-free boot entry, SFR addresses for the asm routines
|
#include <avr/io.h> // SPMCSR's I/O address for the page-store routine
|
||||||
|
|
||||||
using namespace avr::literals;
|
using namespace avr::literals;
|
||||||
namespace spm = avr::spm;
|
namespace spm = avr::spm;
|
||||||
@@ -56,6 +56,8 @@ constexpr std::uint8_t confirm = '!';
|
|||||||
constexpr std::uint8_t request = '?';
|
constexpr std::uint8_t request = '?';
|
||||||
constexpr std::uint8_t knock = '@';
|
constexpr std::uint8_t knock = '@';
|
||||||
|
|
||||||
|
constexpr auto clock = 16_MHz;
|
||||||
|
|
||||||
// Boot geometry for the 512 B boot section (BOOTSZ=11); the page size and the
|
// Boot geometry for the 512 B boot section (BOOTSZ=11); the page size and the
|
||||||
// flash/EEPROM extents are the chip database's to know. app_end is the config
|
// flash/EEPROM extents are the chip database's to know. app_end is the config
|
||||||
// page (TSB's LASTPAGE), one page below the boot section.
|
// page (TSB's LASTPAGE), one page below the boot section.
|
||||||
@@ -65,20 +67,20 @@ constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
|||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
||||||
|
|
||||||
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
||||||
// it follows the clock rather than restating it (rule 41).
|
// it follows the clock rather than restating it.
|
||||||
constexpr auto act_min = static_cast<std::uint8_t>((16_MHz).hz / 1'000'000);
|
constexpr auto act_min = static_cast<std::uint8_t>(clock.hz / 1'000'000);
|
||||||
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
||||||
constexpr std::uint8_t comm_window = 200;
|
constexpr std::uint8_t comm_window = 200;
|
||||||
|
|
||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
||||||
|
|
||||||
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
||||||
constexpr auto baud = avr::uart::solve_baud(16_MHz, 115200_Bd, 8, avr::uart::parity::none);
|
constexpr auto baud = avr::uart::solve_baud(clock, 115200_Bd, 8, avr::uart::parity::none);
|
||||||
|
|
||||||
// One bit time on the wire: the turn-around a shared-line peer needs to stop
|
// One bit time on the wire: the turn-around a shared-line peer needs to stop
|
||||||
// driving before this one starts. Derived from the solved rate, so it follows
|
// driving before this one starts. Derived from the solved rate, so it follows
|
||||||
// the link rather than a count measured against one.
|
// the link rather than a count measured against one.
|
||||||
constexpr auto guard_cycles = static_cast<std::uint32_t>((16_MHz).hz / baud.actual);
|
constexpr auto guard_cycles = static_cast<std::uint32_t>(clock.hz / baud.actual);
|
||||||
|
|
||||||
// The 16-byte device-info block, streamed out on activation.
|
// The 16-byte device-info block, streamed out on activation.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -113,7 +115,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
std::uint8_t byte;
|
std::uint8_t byte;
|
||||||
asm volatile(" tst %[dir] \n\t" // already receiving? keep the line released
|
asm volatile(" tst %[dir] \n\t" // already receiving? keep the line released
|
||||||
" brne 1f \n\t"
|
" brne 1f \n\t"
|
||||||
" ldi %[b], 0x10 \n\t" // RXEN0 alone: release the line and listen
|
" ldi %[b], %[rxen] \n\t" // the receiver alone: release the line and listen
|
||||||
" sts %[ucsr0b], %[b] \n\t"
|
" sts %[ucsr0b], %[b] \n\t"
|
||||||
" ser %[b] \n\t"
|
" ser %[b] \n\t"
|
||||||
" mov %[dir], %[b] \n\t"
|
" mov %[dir], %[b] \n\t"
|
||||||
@@ -122,7 +124,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
" clr r26 \n\t"
|
" clr r26 \n\t"
|
||||||
"2: ser %[b] \n\t"
|
"2: ser %[b] \n\t"
|
||||||
"3: lds %[b], %[ucsr0a] \n\t"
|
"3: lds %[b], %[ucsr0a] \n\t"
|
||||||
" sbrc %[b], 7 \n\t" // RXC0
|
" sbrc %[b], %[rxc] \n\t"
|
||||||
" rjmp 4f \n\t"
|
" rjmp 4f \n\t"
|
||||||
" dec %[b] \n\t"
|
" dec %[b] \n\t"
|
||||||
" brne 3b \n\t"
|
" brne 3b \n\t"
|
||||||
@@ -133,8 +135,9 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
"4: lds %[b], %[udr0] \n\t"
|
"4: lds %[b], %[udr0] \n\t"
|
||||||
"5: \n\t"
|
"5: \n\t"
|
||||||
: [b] "=&d"(byte), [dir] "+r"(g_receiving)
|
: [b] "=&d"(byte), [dir] "+r"(g_receiving)
|
||||||
: [to] "r"(g_window), [actmin] "M"(act_min), [ucsr0a] "n"(_SFR_MEM_ADDR(UCSR0A)),
|
: [to] "r"(g_window), [actmin] "M"(act_min), [rxen] "M"(hw::ucsr0b::rxen0(1).value),
|
||||||
[ucsr0b] "n"(_SFR_MEM_ADDR(UCSR0B)), [udr0] "n"(_SFR_MEM_ADDR(UDR0))
|
[rxc] "I"(std::countr_zero(hw::ucsr0a::rxc0.mask)), [ucsr0a] "n"(hw::ucsr0a::addr),
|
||||||
|
[ucsr0b] "n"(hw::ucsr0b::addr), [udr0] "n"(hw::udr0::addr)
|
||||||
: "r26", "r27", "cc");
|
: "r26", "r27", "cc");
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
@@ -169,7 +172,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
// global-register rule, see tsb_tricks.cpp).
|
// global-register rule, see tsb_tricks.cpp).
|
||||||
[[gnu::noinline, gnu::noclone]] std::uint8_t sflash()
|
[[gnu::noinline, gnu::noclone]] std::uint8_t sflash()
|
||||||
{
|
{
|
||||||
std::uint8_t byte = avr::flash_load(flash_ptr(g_addr));
|
const auto byte = avr::flash_load(flash_ptr(g_addr));
|
||||||
++g_addr;
|
++g_addr;
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
@@ -177,7 +180,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
// One EEPROM byte <- [g_addr++].
|
// One EEPROM byte <- [g_addr++].
|
||||||
[[gnu::noinline, gnu::noclone]] std::uint8_t eerd()
|
[[gnu::noinline, gnu::noclone]] std::uint8_t eerd()
|
||||||
{
|
{
|
||||||
std::uint8_t byte = ee::read<no_spm>(g_addr);
|
const auto byte = ee::read<no_spm>(g_addr);
|
||||||
++g_addr;
|
++g_addr;
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
@@ -213,7 +216,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
tsb_app();
|
tsb_app();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step g_addr one page down and erase that page (the decrement lives here -
|
// Step g_addr one page down and erase that page (the decrement lives here -
|
||||||
// the global-register rule).
|
// the global-register rule).
|
||||||
[[gnu::noinline, gnu::noclone]] void erase_below()
|
[[gnu::noinline, gnu::noclone]] void erase_below()
|
||||||
{
|
{
|
||||||
@@ -233,7 +236,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stream one host page into the erased flash page at g_addr (asm 2 of 2): the
|
// Stream one host page into the erased flash page at g_addr (asm 2 of 2): the
|
||||||
// word pair stages in r0:r1 straight from rx (whose register set is known -
|
// word pair stages in r0:r1 straight from rx (whose register set is known -
|
||||||
// the cross-call liveness C++ cannot express), Z walks the page and PGWRT
|
// the cross-call liveness C++ cannot express), Z walks the page and PGWRT
|
||||||
// programs it. g_addr is left at the next page base.
|
// programs it. g_addr is left at the next page base.
|
||||||
[[gnu::noinline, gnu::noclone]] void store_flash()
|
[[gnu::noinline, gnu::noclone]] void store_flash()
|
||||||
@@ -243,8 +246,8 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
"1: rcall %x[rx] \n\t"
|
"1: rcall %x[rx] \n\t"
|
||||||
" mov r0, r24 \n\t" // word low byte
|
" mov r0, r24 \n\t" // word low byte
|
||||||
" rcall %x[rx] \n\t"
|
" rcall %x[rx] \n\t"
|
||||||
" mov r1, r24 \n\t" // word high byte
|
" mov r1, r24 \n\t" // word high byte
|
||||||
" ldi r24, 0x01 \n\t" // SPMEN: buffer the word at Z
|
" ldi r24, %[fill] \n\t" // buffer the word at Z
|
||||||
" out %[spmcsr], r24 \n\t"
|
" out %[spmcsr], r24 \n\t"
|
||||||
" spm \n\t"
|
" spm \n\t"
|
||||||
" clr r1 \n\t"
|
" clr r1 \n\t"
|
||||||
@@ -254,12 +257,13 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
" movw %[base], r30 \n\t" // g_addr = the next page base
|
" movw %[base], r30 \n\t" // g_addr = the next page base
|
||||||
" subi r30, %[pagelo] \n\t" // Z back to this page's base
|
" subi r30, %[pagelo] \n\t" // Z back to this page's base
|
||||||
" sbci r31, %[pagehi] \n\t"
|
" sbci r31, %[pagehi] \n\t"
|
||||||
" ldi r24, 0x05 \n\t" // PGWRT | SPMEN: program the page
|
" ldi r24, %[write] \n\t" // program the page
|
||||||
" out %[spmcsr], r24 \n\t"
|
" out %[spmcsr], r24 \n\t"
|
||||||
" spm \n\t"
|
" spm \n\t"
|
||||||
: [base] "+r"(g_addr)
|
: [base] "+r"(g_addr)
|
||||||
: [rx] "i"(&rx), [spmcsr] "I"(_SFR_IO_ADDR(SPMCSR)), [words] "M"(page / 2), [pagelo] "M"(page & 0xff),
|
: [rx] "i"(&rx), [spmcsr] "I"(_SFR_IO_ADDR(SPMCSR)), [fill] "M"(hw::spmcsr::spmen(1).value),
|
||||||
[pagehi] "M"(page >> 8)
|
[write] "M"(hw::spmcsr::pgwrt(1).value | hw::spmcsr::spmen(1).value), [words] "M"(page / 2),
|
||||||
|
[pagelo] "M"(page & 0xff), [pagehi] "M"(page >> 8)
|
||||||
: "r0", "r1", "r20", "r24", "r26", "r27", "r30", "r31", "cc", "memory");
|
: "r0", "r1", "r20", "r24", "r26", "r27", "r30", "r31", "cc", "memory");
|
||||||
settle();
|
settle();
|
||||||
}
|
}
|
||||||
@@ -285,7 +289,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
g_receiving = 0;
|
g_receiving = 0;
|
||||||
|
|
||||||
// Activation: 3x'@', each inside the config page's timeout window (rx
|
// Activation: 3x'@', each inside the config page's timeout window (rx
|
||||||
// floors it so a corrupt page cannot lock the loader out); anything else -
|
// floors it so a corrupt page cannot lock the loader out); anything else -
|
||||||
// including silence - hands over.
|
// including silence - hands over.
|
||||||
g_window = avr::flash_load(flash_ptr(app_end + 2));
|
g_window = avr::flash_load(flash_ptr(app_end + 2));
|
||||||
for (std::uint8_t k = 3; k; --k) {
|
for (std::uint8_t k = 3; k; --k) {
|
||||||
@@ -311,7 +315,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
sendf();
|
sendf();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint8_t got = rx();
|
const auto got = rx();
|
||||||
if (got == 0) {
|
if (got == 0) {
|
||||||
if (mask == 0) {
|
if (mask == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+21
-26
@@ -2,23 +2,17 @@
|
|||||||
//
|
//
|
||||||
// The full TinySafeBoot feature set - watchdog bail, one-wire half-duplex,
|
// The full TinySafeBoot feature set - watchdog bail, one-wire half-duplex,
|
||||||
// config-page activation timeout, password gate, emergency erase, and
|
// config-page activation timeout, password gate, emergency erase, and
|
||||||
// config/flash/EEPROM read-write - under philosophy #5 exactly as pureboot
|
// config/flash/EEPROM read-write - with no assembly and no register variables:
|
||||||
// obeys it: no assembly, no register variables; code, attributes, and flags
|
// code, attributes and flags only. It applies pureboot's size techniques - the
|
||||||
// only. Every lesson pureboot's development produced is applied - the
|
|
||||||
// library's half-duplex serial and startup entry, lean bring-up from reset
|
// library's half-duplex serial and startup entry, lean bring-up from reset
|
||||||
// state, one merged send loop over both memories, oracle-shaped loop bounds,
|
// state, one merged send loop over both memories, oracle-shaped loop bounds,
|
||||||
// locals threaded through noinline primitives, pureboot's codegen flags -
|
// locals threaded through noinline primitives, pureboot's codegen flags - and
|
||||||
// and the result sits below the idiomatic tier and above the 512 B boot
|
// lands between the idiomatic tier and the 512 B section the tricks/asm tiers
|
||||||
// section the tricks/asm tiers reach with the banned mechanisms
|
// reach with the banned mechanisms (oracle/README.md holds all four). The gap
|
||||||
// (oracle/README.md holds all four). This tier exists to keep that gap an
|
// to 512 is the rent of policy-clean C++: helpers that hold a cursor across
|
||||||
// artifact
|
// rx()/tx() pay push/pop and argument threading where a global-register
|
||||||
// rather than a claim: the gap to 512 is the rent of policy-clean C++ -
|
// protocol pays nothing, and both control-flow merges tried (a parametrized
|
||||||
// helpers that hold a cursor across rx()/tx() pay push/pop and argument
|
// paged session, a merged store loop) measured larger than the split cases.
|
||||||
// threading where a global-register protocol pays nothing, and both
|
|
||||||
// control-flow merges tried (a parametrized paged session, a merged store
|
|
||||||
// loop) measured larger than the split cases they replaced. TSB's wire fixes
|
|
||||||
// the per-command loop shapes on the device, so pureboot 5's one-transfer-
|
|
||||||
// loop collapse has no purchase here.
|
|
||||||
//
|
//
|
||||||
// The wire protocol is strict request/response, which is what makes the
|
// The wire protocol is strict request/response, which is what makes the
|
||||||
// shared line safe: the device drives it only between a received command and
|
// shared line safe: the device drives it only between a received command and
|
||||||
@@ -36,11 +30,12 @@ using dev = avr::device<{.clock = 16_MHz}>;
|
|||||||
// 115200 at 16 MHz lands +2.1 % off, past the receiver-tolerance table the
|
// 115200 at 16 MHz lands +2.1 % off, past the receiver-tolerance table the
|
||||||
// solver holds rates to - the oracle's own deployment has run there for a
|
// solver holds rates to - the oracle's own deployment has run there for a
|
||||||
// decade, so the override states that it is meant.
|
// decade, so the override states that it is meant.
|
||||||
using serial_t = dev::uart0<{
|
constexpr avr::uart::config link{
|
||||||
.baud = 115200_Bd,
|
.baud = 115200_Bd,
|
||||||
.allow_baud_error = true,
|
.allow_baud_error = true,
|
||||||
.half_duplex = true,
|
.half_duplex = true,
|
||||||
}>;
|
};
|
||||||
|
using serial_t = dev::uart0<link>;
|
||||||
inline constexpr serial_t serial{};
|
inline constexpr serial_t serial{};
|
||||||
|
|
||||||
namespace tsb {
|
namespace tsb {
|
||||||
@@ -70,7 +65,7 @@ constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
|||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
||||||
|
|
||||||
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
||||||
// it follows the clock rather than restating it (rule 41).
|
// it follows the clock rather than restating it.
|
||||||
constexpr auto act_min = static_cast<std::uint8_t>(dev::clock.hz / 1'000'000);
|
constexpr auto act_min = static_cast<std::uint8_t>(dev::clock.hz / 1'000'000);
|
||||||
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
||||||
constexpr std::uint8_t comm_window = 200;
|
constexpr std::uint8_t comm_window = 200;
|
||||||
@@ -166,8 +161,8 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
{
|
{
|
||||||
const auto open = spm::page::begin<spm::from::boot_section, off>(at);
|
const auto open = spm::page::begin<spm::from::boot_section, off>(at);
|
||||||
do {
|
do {
|
||||||
std::uint8_t low = rx();
|
const auto low = rx();
|
||||||
std::uint8_t high = rx();
|
const auto high = rx();
|
||||||
spm::fill<off>(open, at, std::bit_cast<std::uint16_t>(std::array{low, high}));
|
spm::fill<off>(open, at, std::bit_cast<std::uint16_t>(std::array{low, high}));
|
||||||
at += 2;
|
at += 2;
|
||||||
} while (static_cast<std::uint8_t>(at) & (page - 1));
|
} while (static_cast<std::uint8_t>(at) & (page - 1));
|
||||||
@@ -214,18 +209,18 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lean bring-up from reset state: UCSR0C already reads 8N1, UBRR0H reads
|
// Lean bring-up from reset state: UCSR0C already reads 8N1, UBRR0H reads
|
||||||
// 0, and the half-duplex write()/read() raise TXEN0/RXEN0 on first use -
|
// 0, and the half-duplex write()/read() raise TXEN0/RXEN0 on first use -
|
||||||
// only the divisor low byte and U2X0 need a store. The solver still does
|
// only the divisor low byte and U2X0 need a store. The solver still does
|
||||||
// the datasheet work; the asserts pin the reset-state assumptions.
|
// the datasheet work; the asserts pin the reset-state assumptions.
|
||||||
{
|
{
|
||||||
constexpr auto sol = avr::uart::solve_baud(dev::clock, 115200_Bd, 8, avr::uart::parity::none);
|
constexpr auto sol = avr::uart::solve_baud(dev::clock, link.baud, link.data_bits, link.par);
|
||||||
static_assert(sol.u2x && sol.ubrr < 256, "lean bring-up writes UBRR0L only, with U2X0");
|
static_assert(sol.u2x && sol.ubrr < 256, "lean bring-up writes UBRR0L only, with U2X0");
|
||||||
avr::hw::ubrr0::write(static_cast<std::uint8_t>(sol.ubrr));
|
avr::hw::ubrr0::write(static_cast<std::uint8_t>(sol.ubrr));
|
||||||
avr::hw::ucsr0a::write(avr::hw::ucsr0a::u2x0(1));
|
avr::hw::ucsr0a::write(avr::hw::ucsr0a::u2x0(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activation: 3x'@', each inside the config page's timeout window
|
// Activation: 3x'@', each inside the config page's timeout window
|
||||||
// (floored so a corrupt page cannot lock the loader out); anything else -
|
// (floored so a corrupt page cannot lock the loader out); anything else -
|
||||||
// including silence - hands over.
|
// including silence - hands over.
|
||||||
window = avr::flash_load(flash_ptr(app_end + 2)) | act_min;
|
window = avr::flash_load(flash_ptr(app_end + 2)) | act_min;
|
||||||
for (std::uint8_t k = 3; k; --k) {
|
for (std::uint8_t k = 3; k; --k) {
|
||||||
@@ -243,13 +238,13 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
std::uint16_t at = app_end + 3;
|
std::uint16_t at = app_end + 3;
|
||||||
std::uint8_t mask = 0xff;
|
std::uint8_t mask = 0xff;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
std::uint8_t expected = avr::flash_load(flash_ptr(at)) & mask;
|
const std::uint8_t expected = avr::flash_load(flash_ptr(at)) & mask;
|
||||||
++at;
|
++at;
|
||||||
if (expected == 0xff) {
|
if (expected == 0xff) {
|
||||||
send_block(false, reinterpret_cast<std::uint16_t>(info.data()), info.size());
|
send_block(false, reinterpret_cast<std::uint16_t>(info.data()), info.size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint8_t got = rx();
|
const auto got = rx();
|
||||||
if (got == 0) {
|
if (got == 0) {
|
||||||
if (mask == 0) {
|
if (mask == 0) {
|
||||||
continue;
|
continue;
|
||||||
@@ -271,7 +266,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
tx(confirm); // Mainloop ready
|
tx(confirm); // Mainloop ready
|
||||||
const std::uint8_t command = rx();
|
const auto command = rx();
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'f': // read application flash, one page per host '!'
|
case 'f': // read application flash, one page per host '!'
|
||||||
for (std::uint16_t a = 0; a < app_end; a += page) {
|
for (std::uint16_t a = 0; a < app_end; a += page) {
|
||||||
|
|||||||
+7
-10
@@ -4,16 +4,13 @@
|
|||||||
// TinySafeBoot native-UART fixed-baud protocol on libavr with the full feature
|
// TinySafeBoot native-UART fixed-baud protocol on libavr with the full feature
|
||||||
// set of the hand-written oracle: a watchdog-reset bail, one-wire half-duplex,
|
// set of the hand-written oracle: a watchdog-reset bail, one-wire half-duplex,
|
||||||
// a config-page activation timeout, the password gate, emergency erase, and
|
// a config-page activation timeout, the password gate, emergency erase, and
|
||||||
// config/flash/EEPROM read-write. This variant is written for clarity -
|
// config/flash/EEPROM read-write. This variant is written for clarity -
|
||||||
// well-factored functions, no compiler-specific size hacks, no inline assembly.
|
// well-factored functions, no compiler-specific size hacks, no inline assembly.
|
||||||
// The one-wire wiring, the flash-resident info block and every SPM/EEPROM lock
|
// The one-wire wiring, the flash-resident info block, every SPM/EEPROM lock and
|
||||||
// are libavr's to handle; the only attribute is the naked reset entry that
|
// the crt-free reset entry are libavr's to handle.
|
||||||
// stands in for the absent C runtime.
|
|
||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
#include <libavr/libavr.hpp>
|
||||||
|
|
||||||
#include <avr/io.h> // SP / RAMEND for the crt-free boot entry
|
|
||||||
|
|
||||||
using namespace avr::literals;
|
using namespace avr::literals;
|
||||||
namespace spm = avr::spm;
|
namespace spm = avr::spm;
|
||||||
namespace ee = avr::eeprom;
|
namespace ee = avr::eeprom;
|
||||||
@@ -144,8 +141,8 @@ void store_flash_page(std::uint16_t addr)
|
|||||||
{
|
{
|
||||||
const auto open = spm::page::begin<spm::from::boot_section, off>(addr);
|
const auto open = spm::page::begin<spm::from::boot_section, off>(addr);
|
||||||
for (std::uint16_t i = 0; i < page; i += 2) {
|
for (std::uint16_t i = 0; i < page; i += 2) {
|
||||||
std::uint8_t lo = rx();
|
const auto lo = rx();
|
||||||
std::uint8_t hi = rx();
|
const auto hi = rx();
|
||||||
spm::fill<off>(open, addr + i, static_cast<std::uint16_t>(lo | (hi << 8)));
|
spm::fill<off>(open, addr + i, static_cast<std::uint16_t>(lo | (hi << 8)));
|
||||||
}
|
}
|
||||||
spm::write_page<spm::from::boot_section, off>(addr); // blocking: waits the write out
|
spm::write_page<spm::from::boot_section, off>(addr); // blocking: waits the write out
|
||||||
@@ -265,11 +262,11 @@ enum class gate : std::uint8_t { pass, emergency };
|
|||||||
gate password_gate()
|
gate password_gate()
|
||||||
{
|
{
|
||||||
for (const std::uint8_t *pw = flash_ptr(app_end + 3);; ++pw) {
|
for (const std::uint8_t *pw = flash_ptr(app_end + 3);; ++pw) {
|
||||||
std::uint8_t expected = avr::flash_load(pw);
|
const auto expected = avr::flash_load(pw);
|
||||||
if (expected == 0xff) {
|
if (expected == 0xff) {
|
||||||
return gate::pass;
|
return gate::pass;
|
||||||
}
|
}
|
||||||
std::uint8_t got = rx();
|
const auto got = rx();
|
||||||
if (got == 0) {
|
if (got == 0) {
|
||||||
return gate::emergency;
|
return gate::emergency;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -31,8 +31,6 @@
|
|||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
#include <libavr/libavr.hpp>
|
||||||
|
|
||||||
#include <avr/io.h> // SP / RAMEND for the crt-free boot entry
|
|
||||||
|
|
||||||
using namespace avr::literals;
|
using namespace avr::literals;
|
||||||
namespace spm = avr::spm;
|
namespace spm = avr::spm;
|
||||||
namespace ee = avr::eeprom;
|
namespace ee = avr::eeprom;
|
||||||
@@ -55,6 +53,8 @@ constexpr std::uint8_t confirm = '!';
|
|||||||
constexpr std::uint8_t request = '?';
|
constexpr std::uint8_t request = '?';
|
||||||
constexpr std::uint8_t knock = '@';
|
constexpr std::uint8_t knock = '@';
|
||||||
|
|
||||||
|
constexpr auto clock = 16_MHz;
|
||||||
|
|
||||||
// Boot geometry for the 1 KB boot section (BOOTSZ=10); the page size and the
|
// Boot geometry for the 1 KB boot section (BOOTSZ=10); the page size and the
|
||||||
// flash/EEPROM extents are the chip database's to know. app_end is the config
|
// flash/EEPROM extents are the chip database's to know. app_end is the config
|
||||||
// page (TSB's LASTPAGE), one page below the boot section.
|
// page (TSB's LASTPAGE), one page below the boot section.
|
||||||
@@ -64,20 +64,20 @@ constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
|||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
||||||
|
|
||||||
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
// Lockout-proof floor for the activation window: the oracle's F_CPU/1MHz, so
|
||||||
// it follows the clock rather than restating it (rule 41).
|
// it follows the clock rather than restating it.
|
||||||
constexpr auto act_min = static_cast<std::uint8_t>((16_MHz).hz / 1'000'000);
|
constexpr auto act_min = static_cast<std::uint8_t>(clock.hz / 1'000'000);
|
||||||
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
// Post-activation window: the host gets seconds, not milliseconds, mid-session.
|
||||||
constexpr std::uint8_t comm_window = 200;
|
constexpr std::uint8_t comm_window = 200;
|
||||||
|
|
||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
||||||
|
|
||||||
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
||||||
constexpr auto baud = avr::uart::solve_baud(16_MHz, 115200_Bd, 8, avr::uart::parity::none);
|
constexpr auto baud = avr::uart::solve_baud(clock, 115200_Bd, 8, avr::uart::parity::none);
|
||||||
|
|
||||||
// One bit time on the wire: the turn-around a shared-line peer needs to stop
|
// One bit time on the wire: the turn-around a shared-line peer needs to stop
|
||||||
// driving before this one starts. Derived from the solved rate, so it follows
|
// driving before this one starts. Derived from the solved rate, so it follows
|
||||||
// the link rather than a count measured against one.
|
// the link rather than a count measured against one.
|
||||||
constexpr auto guard_cycles = static_cast<std::uint32_t>((16_MHz).hz / baud.actual);
|
constexpr auto guard_cycles = static_cast<std::uint32_t>(clock.hz / baud.actual);
|
||||||
|
|
||||||
// The 16-byte device-info block, streamed out on activation.
|
// The 16-byte device-info block, streamed out on activation.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -160,7 +160,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
// One flash byte <- [g_addr++] (the advance right before ret - see header).
|
// One flash byte <- [g_addr++] (the advance right before ret - see header).
|
||||||
[[gnu::noinline, gnu::noclone]] std::uint8_t sflash()
|
[[gnu::noinline, gnu::noclone]] std::uint8_t sflash()
|
||||||
{
|
{
|
||||||
std::uint8_t byte = avr::flash_load(flash_ptr(g_addr));
|
const auto byte = avr::flash_load(flash_ptr(g_addr));
|
||||||
++g_addr;
|
++g_addr;
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
// One EEPROM byte <- [g_addr++].
|
// One EEPROM byte <- [g_addr++].
|
||||||
[[gnu::noinline, gnu::noclone]] std::uint8_t eerd()
|
[[gnu::noinline, gnu::noclone]] std::uint8_t eerd()
|
||||||
{
|
{
|
||||||
std::uint8_t byte = ee::read<no_spm>(g_addr);
|
const auto byte = ee::read<no_spm>(g_addr);
|
||||||
++g_addr;
|
++g_addr;
|
||||||
return byte;
|
return byte;
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
g_receiving = 0;
|
g_receiving = 0;
|
||||||
|
|
||||||
// Activation: 3x'@', each inside the config page's timeout window (rx
|
// Activation: 3x'@', each inside the config page's timeout window (rx
|
||||||
// floors it so a corrupt page cannot lock the loader out); anything else -
|
// floors it so a corrupt page cannot lock the loader out); anything else -
|
||||||
// including silence - hands over.
|
// including silence - hands over.
|
||||||
g_window = avr::flash_load(flash_ptr(app_end + 2));
|
g_window = avr::flash_load(flash_ptr(app_end + 2));
|
||||||
for (std::uint8_t k = 3; k; --k) {
|
for (std::uint8_t k = 3; k; --k) {
|
||||||
@@ -289,7 +289,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
sendf();
|
sendf();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint8_t got = rx();
|
const auto got = rx();
|
||||||
if (got == 0) {
|
if (got == 0) {
|
||||||
if (mask == 0) {
|
if (mask == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in new issue
Block a user