pureboot 7: the same features in fewer words on every chip
Four cuts, none touching what the loader can do. The entry stub stops re-doing the reset logic's own SP write where the datasheet guarantees RAMEND (stack::hardware — the classic megas keep theirs). The autobaud unit moves into GPIOR2:GPIOR1 wherever the chip has the pair: one-word accesses, no RAM object, and the host's measured-clock peek follows it by version and geometry. 'J' rides the unified decode, carrying a selector it ignores so its address is the same two reads as every other command — the tool sends the bare form to older residents. run_app stops insisting on a body of its own. The fleet lands at 358–410 B stock and 438–474 B autobaud; the tightest image in the space — the 1284s' autobaud on a USART's own pins with the OSCCAL trim — drops from 510 to 484 of its 512. Every chip's suite is green on the wire that changed, and the README's table is machine-checked against the built images. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,7 +75,7 @@ static_assert(PUREBOOT_OSCCAL >= 0 && PUREBOOT_OSCCAL <= 0xff, "PUREBOOT_OSCCAL
|
||||
|
||||
// The loader's one identity number. The protocol carries none of its own —
|
||||
// a version implies it, and the host tool holds that map (README.md).
|
||||
constexpr std::uint8_t version = 6;
|
||||
constexpr std::uint8_t version = 7;
|
||||
|
||||
// The image's identity stamp, for the host tool rather than for the wire: an
|
||||
// update image is a bare 512-byte slot, and without this nothing in it says
|
||||
@@ -241,7 +241,10 @@ struct software_link {
|
||||
// every rate. Activation differs in kind from the other two — there is no
|
||||
// clock to time a window against — so this backend brings its own, below.
|
||||
struct autobaud_link {
|
||||
using uart = avr::uart::software_autobaud<avr::PUREBOOT_RX, avr::PUREBOOT_TX>;
|
||||
// The unit in GPIOR2:GPIOR1 where the chip has them: the loader owns the
|
||||
// whole chip while it runs, and the pair is four instructions cheaper per
|
||||
// session than a RAM word.
|
||||
using uart = avr::uart::software_autobaud<avr::PUREBOOT_RX, avr::PUREBOOT_TX, avr::uart::unit_home::gpior>;
|
||||
|
||||
static void init()
|
||||
{
|
||||
@@ -288,7 +291,7 @@ extern "C" [[noreturn]] void pureboot_app();
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
[[gnu::noinline, noreturn]] void run_app()
|
||||
[[noreturn]] void run_app()
|
||||
{
|
||||
jump(pureboot_app);
|
||||
}
|
||||
@@ -488,12 +491,6 @@ void fill_page(std::uint8_t bank, std::uint16_t at)
|
||||
tx_ack();
|
||||
const std::uint8_t command = link::rx();
|
||||
switch (command) {
|
||||
case 'J': { // jump to a wire word address: hand-over and staging transfer
|
||||
auto target = reinterpret_cast<void (*)()>(rx16());
|
||||
tx_ack();
|
||||
link::drain();
|
||||
jump(target);
|
||||
}
|
||||
case 'b': // identity: the version, then the three signature bytes
|
||||
// Straight out of the stamp, so the wire and the image can never
|
||||
// disagree about what this loader is. The indices are constant and
|
||||
@@ -502,18 +499,26 @@ void fill_page(std::uint8_t bank, std::uint16_t at)
|
||||
for (std::uint8_t at = stamp_identity; at != sizeof identity_stamp; ++at)
|
||||
link::tx(identity_stamp[at]);
|
||||
break;
|
||||
case 'J': // jump: sel8 (reserved), addr16 as a wire word address
|
||||
case 'W': // fill one flash page buffer: sel8, addr16, then page bytes
|
||||
case 'G': // read: sel8, addr16, n8 (0 = 256)
|
||||
case 'g': { // write: sel8, addr16, n8, then n bytes, each acked
|
||||
// One decode, one cursor and one loop for every space and both
|
||||
// directions: a command per memory would carry a copy of all three
|
||||
// each. 'W' joins the same decode rather than keeping an address
|
||||
// form of its own, so flash addressing is uniform across every
|
||||
// command that names it.
|
||||
// One decode, one cursor and one loop for every space, both
|
||||
// directions and the jump: a command per memory would carry a copy
|
||||
// of all three each. 'J' — the hand-over and staging transfer —
|
||||
// carries a selector it ignores so its address rides the same two
|
||||
// reads as everything else; 'W' joins the same decode rather than
|
||||
// keeping an address form of its own, so flash addressing is
|
||||
// uniform across every command that names it.
|
||||
const std::uint8_t selector = link::rx();
|
||||
const std::uint8_t space = space_of(selector);
|
||||
const std::uint8_t bank = bank_of(selector);
|
||||
std::uint16_t at = rx16();
|
||||
if (command == 'J') {
|
||||
tx_ack();
|
||||
link::drain();
|
||||
jump(reinterpret_cast<void (*)()>(at));
|
||||
}
|
||||
if (command == 'W') {
|
||||
fill_page(bank, at);
|
||||
break;
|
||||
@@ -540,4 +545,7 @@ void fill_page(std::uint8_t bank, std::uint16_t at)
|
||||
} // namespace
|
||||
} // namespace pureboot
|
||||
|
||||
template struct avr::startup::entry<pureboot::run>;
|
||||
// stack::hardware: activation is reset-only, so the reset logic's own
|
||||
// SP = RAMEND stands wherever the datasheet guarantees it (the classic
|
||||
// megas still get the write); a 'J' entry runs on the caller's live stack.
|
||||
template struct avr::startup::entry<pureboot::run, avr::startup::stack::hardware>;
|
||||
|
||||
Reference in New Issue
Block a user