The far machinery (ELPM reads, RAMPZ page commands, wire-word math) costs ~46 B over the m328P's 504, and the tsb-calibrated C++-to-asm gap says no implementation of this feature set reaches 512 on this chip — a boundary its hardware does not have anyway: the 1284P's smallest boot sector is 1 KiB. The slot therefore becomes per-geometry (512 B, or 1 KiB past 64 KiB), which the host derives from the word-addressing flag; slot arithmetic unifies (the index is the wire high byte with its low bit dropped in either unit), the update preflight demands a two-slot boot section in the chip's own terms, and pbapp's hand-back jumps to the real slot base. libavr's far primitives split their RAMPZ/Z asm operands (a page never crosses 64 KiB, so callers keep a byte and a 16-bit cursor — the 32-bit address folds away; flash_load_far's byte form becomes the out-RAMPZ+elpm pair avr-libc's pgm_read_byte_far rebuilds per call), and the host splits reads at 64 KiB boundaries. All ten chips pass the full suite — the 1284P at 558 B including protocol, relocation, and the power-fail self-update — with pureboot byte-identical across generated and reflect modes everywhere, and the original three chips' images unchanged to the byte (488/502/504). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
72 lines
2.1 KiB
C++
72 lines
2.1 KiB
C++
// 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 mega 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 tinies idle: reset reaches their loader through
|
|
// the patched vector, so the application owes it nothing.
|
|
#include <libavr/libavr.hpp>
|
|
|
|
using namespace avr::literals;
|
|
|
|
namespace {
|
|
|
|
consteval avr::hertz_t clock()
|
|
{
|
|
if (avr::hw::db.name == "ATtiny13A")
|
|
return 9.6_MHz;
|
|
if (avr::hw::db.name == "ATtiny85")
|
|
return 8_MHz;
|
|
return 16_MHz;
|
|
}
|
|
|
|
using dev = avr::device<{.clock = clock()}>;
|
|
|
|
template <avr::hertz_t C,
|
|
bool Hardware = avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART")>
|
|
struct link {
|
|
using tx_t = avr::uart::usart0<C, {.baud = 115200_Bd, .max_baud_error = 2.5_pct}>;
|
|
static void tx(char c)
|
|
{
|
|
tx_t::write(static_cast<std::uint8_t>(c));
|
|
}
|
|
[[noreturn]] static void idle()
|
|
{
|
|
// 'L' hands back to the loader at the top slot — 512 bytes, or the
|
|
// 1 KiB the >64 KiB chips use.
|
|
constexpr std::uint32_t slot = avr::hw::db.mem.flash_size > 65536 ? 1024 : 512;
|
|
for (;;)
|
|
if (tx_t::read_blocking() == 'L')
|
|
reinterpret_cast<void (*)()>(static_cast<std::uint16_t>((avr::hw::db.mem.flash_size - slot) / 2))();
|
|
}
|
|
};
|
|
|
|
template <avr::hertz_t C>
|
|
struct link<C, false> {
|
|
using tx_t = avr::uart::software_tx<C, avr::pb1, 57600_Bd>;
|
|
static void tx(char c)
|
|
{
|
|
tx_t::write(static_cast<std::uint8_t>(c));
|
|
}
|
|
[[noreturn]] static void idle()
|
|
{
|
|
while (true) {
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
avr::init<typename link<dev::clock>::tx_t>();
|
|
link<dev::clock>::tx('A');
|
|
link<dev::clock>::tx('P');
|
|
link<dev::clock>::tx('P');
|
|
link<dev::clock>::idle();
|
|
}
|