pureboot: every libavr chip — 37 loaders, the m48 class, the 644 geometry
The chip table becomes family blocks covering all 37 targets. The m48s are a new deployment class: no boot section, so the tiny profile spoken over the hardware USART — host-patched reset vector, trampoline hand-over, a 510-byte budget (474 B built), no fuse preflight — while their RWWSRE store stays the buffer discard (Atmel-8271 §26.2); the device keys the patch flag and the CPU-halt waits on the curated boot-section capability and the discard on the RWWSRE bit itself. The 644s' 64 KiB is exactly the 16-bit byte space: plain LPM, byte wire addresses, 498 B in a 512-byte slot — and their 1 KiB minimum boot section holds the resident and staging slots together, so self-update needs no fuse step (the update test's slot pick now keys word-flash on base >= 64 KiB; base + slot merely touching the boundary stays byte-addressed). The 1284 joins the 1284P's word-addressed 1 KiB slot at 558 B. BOOT_FUSE gains every boot-sectioned family's ladder and fuse byte; the planner exercises them all. The sim scaffolding keys patch-vector-ness instead of the atmega name prefix, the fixture app picks its clock by family (the tiny25/45/13 builds surfaced the 16 MHz fallthrough as garbled banners), and the runner's wrapped flash ioctl performs the m48 discard simavr's no-RWW cores turn into a stray buffer fill. Sizes across the fleet: 466-504 B megas, 474 B m48s, 498 B 644s, 488-502 B tinies, 558 B 1284s — every chip passing size/pi/planner/protocol/reloc/update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,9 +16,10 @@
|
||||
// resident — how pureboot updates itself, host-driven, with no other
|
||||
// firmware involved.
|
||||
//
|
||||
// Entry: reset lands in avr::startup::entry below (BOOTRST on the mega; the
|
||||
// patched reset vector — or erased flash walking up into the loader — on the
|
||||
// tinies). A watchdog reset hands straight to the application. Otherwise the
|
||||
// Entry: reset lands in avr::startup::entry below (BOOTRST on the
|
||||
// boot-sectioned megas; the patched reset vector — or erased flash walking
|
||||
// up into the loader — on the tinies and the boot-section-less m48s). A
|
||||
// watchdog reset hands straight to the application. Otherwise the
|
||||
// host has one activation window per awaited knock byte ("pb"); an idle line
|
||||
// boots the application. A session then stays in the command loop until 'J'
|
||||
// jumps away or the chip resets.
|
||||
@@ -44,9 +45,10 @@ constexpr std::uint8_t ack = '+';
|
||||
// from code.
|
||||
consteval avr::hertz_t clock()
|
||||
{
|
||||
if (avr::hw::db.name == "ATtiny13A")
|
||||
auto name = std::string_view{avr::hw::db.name};
|
||||
if (name.starts_with("ATtiny13"))
|
||||
return 9.6_MHz;
|
||||
if (avr::hw::db.name == "ATtiny85")
|
||||
if (name.starts_with("ATtiny"))
|
||||
return 8_MHz;
|
||||
return 16_MHz;
|
||||
}
|
||||
@@ -62,21 +64,19 @@ consteval std::int16_t wdrf_field()
|
||||
|
||||
// Geometry: the resident loader owns the top slot of flash — 512 bytes,
|
||||
// except on the >64 KiB chips whose own smallest boot sector is 1 KiB (the
|
||||
// 1284P): there the slot is 1 KiB, matching the hardware boundary the
|
||||
// 1284s): there the slot is 1 KiB, matching the hardware boundary the
|
||||
// 512-byte figure comes from everywhere else. The word below the slot is
|
||||
// the trampoline (the application's relocated reset vector) on chips
|
||||
// without a hardware boot section. The RWWSRE bit marks a separate boot
|
||||
// section — on classic AVR the two capabilities coincide (the m8/m32 packs
|
||||
// spell its register SPMCR).
|
||||
// without a hardware boot section — the tinies and the m48s, whose SPM
|
||||
// runs from anywhere (Atmel-8271 §26). A boot section also means the CPU
|
||||
// runs on while the RWW section programs; everywhere else it halts through
|
||||
// the operation. The m48s still carry RWWSRE as their temporary-buffer
|
||||
// discard (§26.2), so the discard picks by that bit, not by the section.
|
||||
constexpr std::uint16_t slot_bytes = spm::flash_bytes > 65536 ? 1024 : 512;
|
||||
constexpr std::uint32_t base = spm::flash_bytes - slot_bytes;
|
||||
constexpr std::uint16_t page = spm::page_bytes;
|
||||
constexpr bool boot_section = [] {
|
||||
for (auto reg : {"SPMCSR", "SPMCR"})
|
||||
if (avr::hw::db.field_index(reg, "RWWSRE") >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}();
|
||||
constexpr bool boot_section = avr::hw::curated::has_boot_section();
|
||||
constexpr bool rww_discard = spm::detail::has_rww();
|
||||
|
||||
// Past 64 KiB a byte address no longer fits the wire's 16 bits, so on the
|
||||
// large chips every flash address on the wire — and all slot arithmetic —
|
||||
@@ -85,7 +85,8 @@ constexpr bool boot_section = [] {
|
||||
// 2 x 256 words), so the slot index is the high byte with its low bit
|
||||
// dropped everywhere.
|
||||
constexpr bool word_flash = spm::flash_bytes > 65536;
|
||||
constexpr std::uint16_t wire_base = word_flash ? static_cast<std::uint16_t>(base / 2) : static_cast<std::uint16_t>(base);
|
||||
constexpr std::uint16_t wire_base =
|
||||
word_flash ? static_cast<std::uint16_t>(base / 2) : static_cast<std::uint16_t>(base);
|
||||
constexpr std::uint16_t wire_page_mask = word_flash ? (page / 2 - 1) : (page - 1);
|
||||
|
||||
// The activation window, in seconds, is a compile-time constant (the build
|
||||
@@ -332,9 +333,10 @@ void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
||||
{
|
||||
// A buffer word cannot be loaded twice without an erase (§26.2.1), so a
|
||||
// refused page's drained data must not linger for the next write:
|
||||
// discard the buffer up front — CTPB on the tinies; on the mega writing
|
||||
// RWWSRE aborts a pending load (§26.2.2).
|
||||
if constexpr (boot_section)
|
||||
// discard the buffer up front — CTPB on the tinies; on the megas
|
||||
// writing RWWSRE aborts a pending load (§26.2.2 — on the m48s that
|
||||
// flush is the bit's whole documented job).
|
||||
if constexpr (rww_discard)
|
||||
spm::rww_enable<off>();
|
||||
else
|
||||
spm::clear_buffer<off>();
|
||||
@@ -376,8 +378,9 @@ void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
||||
page_high = static_cast<std::uint8_t>(address >> 8) & 0xfe;
|
||||
}
|
||||
if (page_high != slot_high) {
|
||||
// The tinies halt the CPU through the erase and the write, so only
|
||||
// the megas — running on while their RWW section programs — wait.
|
||||
// The tinies and the m48s halt the CPU through the erase and the
|
||||
// write, so only the boot-sectioned megas — running on while their
|
||||
// RWW section programs — wait.
|
||||
spm::erase_page<off>(address);
|
||||
if constexpr (boot_section)
|
||||
spm::wait();
|
||||
@@ -416,8 +419,8 @@ void send_fuses()
|
||||
// program_flash refuses this one slot and the info block is addressed
|
||||
// from it, so both follow wherever the code was flashed.
|
||||
const std::uint16_t ra_words = reinterpret_cast<std::uint16_t>(__builtin_return_address(0));
|
||||
const std::uint8_t slot_high = word_flash ? static_cast<std::uint8_t>(ra_words >> 8) & 0xfe
|
||||
: static_cast<std::uint8_t>((ra_words >> 8) << 1);
|
||||
const std::uint8_t slot_high =
|
||||
word_flash ? static_cast<std::uint8_t>(ra_words >> 8) & 0xfe : static_cast<std::uint8_t>((ra_words >> 8) << 1);
|
||||
|
||||
// The knock: 'p' then 'b', each under a fresh window; any other byte is
|
||||
// line noise and waits again. Falling out of a window runs the app.
|
||||
|
||||
Reference in New Issue
Block a user