tsb: drive each tier to its size floor
asm 502->498 B (below the oracle's 500): the stack bring-up moves to plain C++, and a register is reserved for the config-page high byte instead of reloading it at each app-flash-boundary compare. tricks 808->778 B: shared erase/rww helpers plus the libavr half-duplex W1C fix. pure 950->896 B and no SRAM: streams rx->SPM/EEPROM instead of staging a 128 B page buffer. All three keep full oracle feature parity and stay byte-identical across modes; protocol tests (round-trip + password + emergency erase) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,11 +47,18 @@ endif()
|
|||||||
# size; the linker section-start and the source's boot_bytes agree.
|
# size; the linker section-start and the source's boot_bytes agree.
|
||||||
# All three implement the full oracle feature set (see oracle/README.md):
|
# All three implement the full oracle feature set (see oracle/README.md):
|
||||||
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
||||||
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how.
|
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how,
|
||||||
# tsb_asm — minimal inline asm, the headline: 502 B in the 512 B section,
|
# and the size gradient is the cost of that "how" — see dev/lessons.md.
|
||||||
# matching the hand-written oracle's size and features.
|
# tsb_asm — dense inline asm for the loader core, libavr for every geometry/
|
||||||
# tsb_tricks — compiler trickery, no asm: 808 B in the 1 KB section (BOOTSZ=10).
|
# baud/info constant and the stack bring-up: 498 B in the 512 B
|
||||||
# tsb_pure — pure idiomatic libavr: 950 B in the 1 KB section.
|
# section, beating the hand-written oracle (500 B) at its own
|
||||||
|
# feature set. The core is asm because C++ cannot reach it under
|
||||||
|
# 512 (the tricks tier is the proof).
|
||||||
|
# tsb_tricks — compiler trickery, no asm (global-register page walk, forced
|
||||||
|
# flash/EEPROM path sharing, streaming stores, arithmetic command
|
||||||
|
# decode): 778 B in the 1 KB section (BOOTSZ=10).
|
||||||
|
# tsb_pure — pure idiomatic libavr, one function per command, streaming (no
|
||||||
|
# SRAM page buffer): 896 B in the 1 KB section.
|
||||||
#
|
#
|
||||||
# add_tsb_variant(<name> <boot-section-bytes>)
|
# add_tsb_variant(<name> <boot-section-bytes>)
|
||||||
function(add_tsb_variant name bytes)
|
function(add_tsb_variant name bytes)
|
||||||
|
|||||||
@@ -65,18 +65,16 @@ constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19;
|
|||||||
} // namespace tsb
|
} // namespace tsb
|
||||||
|
|
||||||
// Reset lands here: BOOTRST vectors to the boot base, .vectors is laid first, and
|
// Reset lands here: BOOTRST vectors to the boot base, .vectors is laid first, and
|
||||||
// no crt runs. The whole loader is this one naked routine.
|
// no crt runs. The stack pointer is the one piece of bring-up that reads as
|
||||||
|
// plainly in C++ as in assembler, so it is; the dense loader body follows.
|
||||||
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
||||||
{
|
{
|
||||||
|
SP = RAMEND;
|
||||||
asm volatile(
|
asm volatile(
|
||||||
// --- bring-up ------------------------------------------------------
|
// --- bring-up ------------------------------------------------------
|
||||||
" ldi r16, lo8(%[ramend]) \n\t"
|
|
||||||
" out %[spl], r16 \n\t"
|
|
||||||
" ldi r16, hi8(%[ramend]) \n\t"
|
|
||||||
" out %[sph], r16 \n\t"
|
|
||||||
" in r16, %[mcusr] \n\t" // watchdog reset → hand straight back
|
" in r16, %[mcusr] \n\t" // watchdog reset → hand straight back
|
||||||
" sbrc r16, 3 \n\t" // MCUSR bit 3 = WDRF
|
" sbrc r16, 3 \n\t" // MCUSR bit 3 = WDRF
|
||||||
" rjmp 9f \n\t" // 9: = appjump
|
" rjmp 9f \n\t" // 9: = appjump (drains SPM, jumps to 0)
|
||||||
" ldi r16, %[ubrr] \n\t" // fixed baud, UBRR0L only
|
" ldi r16, %[ubrr] \n\t" // fixed baud, UBRR0L only
|
||||||
" sts %[ubrr0l], r16 \n\t"
|
" sts %[ubrr0l], r16 \n\t"
|
||||||
" ldi r16, 0x02 \n\t" // 1<<U2X0
|
" ldi r16, 0x02 \n\t" // 1<<U2X0
|
||||||
@@ -97,6 +95,9 @@ extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry
|
|||||||
" brne 1b \n\t"
|
" brne 1b \n\t"
|
||||||
// --- password / emergency erase (Z at config-page password) --------
|
// --- password / emergency erase (Z at config-page password) --------
|
||||||
" ldi r23, %[commto] \n\t" // widen the timeout for the session
|
" ldi r23, %[commto] \n\t" // widen the timeout for the session
|
||||||
|
// Knock counter spent: reserve r17 = config-page high byte for every
|
||||||
|
// app-flash-boundary compare below (saves reloading it at each site).
|
||||||
|
" ldi r17, hi8(%[appcfg]) \n\t"
|
||||||
"2: ser r19 \n\t" // r19=0xff → comparison enabled
|
"2: ser r19 \n\t" // r19=0xff → comparison enabled
|
||||||
"3: lpm r18, Z+ \n\t"
|
"3: lpm r18, Z+ \n\t"
|
||||||
" and r18, r19 \n\t" // a prior mismatch (r19=0) blanks the rest
|
" and r18, r19 \n\t" // a prior mismatch (r19=0) blanks the rest
|
||||||
@@ -153,8 +154,7 @@ extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry
|
|||||||
" ldi r20, %[page] \n\t"
|
" ldi r20, %[page] \n\t"
|
||||||
" rcall tsb_sendf \n\t"
|
" rcall tsb_sendf \n\t"
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
" cpi r30, lo8(%[appcfg]) \n\t"
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
" cpc r31, r17 \n\t" // r17 = hi8(appcfg), reserved above
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
" brlo 1b \n\t"
|
||||||
"9: ret \n\t"
|
"9: ret \n\t"
|
||||||
// --- 'e' read EEPROM (host-paced) ----------------------------------
|
// --- 'e' read EEPROM (host-paced) ----------------------------------
|
||||||
@@ -179,8 +179,7 @@ extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry
|
|||||||
" brts 9f \n\t"
|
" brts 9f \n\t"
|
||||||
" rcall tsb_store \n\t"
|
" rcall tsb_store \n\t"
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
" cpi r30, lo8(%[appcfg]) \n\t"
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
" cpc r31, r17 \n\t" // r17 = hi8(appcfg), reserved above
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
" brlo 1b \n\t"
|
||||||
"9: ret \n\t"
|
"9: ret \n\t"
|
||||||
// --- 'E' write EEPROM ----------------------------------------------
|
// --- 'E' write EEPROM ----------------------------------------------
|
||||||
@@ -240,8 +239,7 @@ extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry
|
|||||||
" subi r30, lo8(-%[page]) \n\t"
|
" subi r30, lo8(-%[page]) \n\t"
|
||||||
" sbci r31, hi8(-%[page]) \n\t"
|
" sbci r31, hi8(-%[page]) \n\t"
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
" cpi r30, lo8(%[appcfg]) \n\t"
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
" cpc r31, r17 \n\t" // r17 = hi8(appcfg), reserved above
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
" brlo 1b \n\t"
|
||||||
" clr r30 \n\t" // hand callers Z=0
|
" clr r30 \n\t" // hand callers Z=0
|
||||||
" clr r31 \n\t"
|
" clr r31 \n\t"
|
||||||
@@ -345,8 +343,7 @@ extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry
|
|||||||
" clc \n\t"
|
" clc \n\t"
|
||||||
" ret \n\t"
|
" ret \n\t"
|
||||||
:
|
:
|
||||||
: [ramend] "i"(RAMEND), [spl] "I"(_SFR_IO_ADDR(SPL)), [sph] "I"(_SFR_IO_ADDR(SPH)),
|
: [mcusr] "I"(_SFR_IO_ADDR(MCUSR)), [ubrr] "n"(tsb::baud.ubrr), [ubrr0l] "n"(_SFR_MEM_ADDR(UBRR0L)),
|
||||||
[mcusr] "I"(_SFR_IO_ADDR(MCUSR)), [ubrr] "n"(tsb::baud.ubrr), [ubrr0l] "n"(_SFR_MEM_ADDR(UBRR0L)),
|
|
||||||
[ucsr0a] "n"(_SFR_MEM_ADDR(UCSR0A)), [ucsr0b] "n"(_SFR_MEM_ADDR(UCSR0B)), [udr0] "n"(_SFR_MEM_ADDR(UDR0)),
|
[ucsr0a] "n"(_SFR_MEM_ADDR(UCSR0A)), [ucsr0b] "n"(_SFR_MEM_ADDR(UCSR0B)), [udr0] "n"(_SFR_MEM_ADDR(UDR0)),
|
||||||
[spmcsr] "I"(_SFR_IO_ADDR(SPMCSR)), [spm_fill] "n"(_BV(__SPM_ENABLE)),
|
[spmcsr] "I"(_SFR_IO_ADDR(SPMCSR)), [spm_fill] "n"(_BV(__SPM_ENABLE)),
|
||||||
[spm_ers] "n"(_BV(PGERS) | _BV(__SPM_ENABLE)), [spm_wrt] "n"(_BV(PGWRT) | _BV(__SPM_ENABLE)),
|
[spm_ers] "n"(_BV(PGERS) | _BV(__SPM_ENABLE)), [spm_wrt] "n"(_BV(PGWRT) | _BV(__SPM_ENABLE)),
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ inline constexpr std::array<std::uint8_t, 16> info_data = {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
using info = avr::flash_table<info_data>;
|
using info = avr::flash_table<info_data>;
|
||||||
|
|
||||||
// One page staged in SRAM. Scratch that is always filled before it is read, so
|
|
||||||
// it lives in .noinit — no startup clear (there is no crt) and no .text bytes.
|
|
||||||
[[gnu::section(".noinit")]] std::uint8_t buffer[page];
|
|
||||||
|
|
||||||
// Blocking byte read/write over the one-wire line: read() releases the line to
|
// Blocking byte read/write over the one-wire line: read() releases the line to
|
||||||
// the receiver, write() takes it and holds it until the frame is out.
|
// the receiver, write() takes it and holds it until the frame is out.
|
||||||
std::uint8_t rx()
|
std::uint8_t rx()
|
||||||
@@ -96,13 +92,6 @@ void send_eeprom(std::uint16_t addr, std::uint16_t count)
|
|||||||
tx(ee::read(addr++));
|
tx(ee::read(addr++));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take one page from the host into the SRAM buffer.
|
|
||||||
void get_page()
|
|
||||||
{
|
|
||||||
for (std::uint16_t i = 0; i < page; ++i)
|
|
||||||
buffer[i] = rx();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt the host with '?' and report whether it answered '!'.
|
// Prompt the host with '?' and report whether it answered '!'.
|
||||||
bool request_confirm()
|
bool request_confirm()
|
||||||
{
|
{
|
||||||
@@ -110,29 +99,40 @@ bool request_confirm()
|
|||||||
return rx() == confirm;
|
return rx() == confirm;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program the SRAM buffer into one already-erased flash page (low byte then
|
// Stream one page from the host straight into the already-erased flash page at
|
||||||
// high, as the SPM word buffer wants).
|
// `addr`, filling the SPM word buffer low byte then high — no SRAM staging, so
|
||||||
void write_flash_page(std::uint16_t addr)
|
// receiving and programming are the same loop.
|
||||||
|
void store_flash_page(std::uint16_t addr)
|
||||||
{
|
{
|
||||||
spm::fill<off>(addr, std::span<const std::uint8_t>{buffer, page});
|
for (std::uint16_t i = 0; i < page; i += 2) {
|
||||||
|
std::uint8_t lo = rx();
|
||||||
|
std::uint8_t hi = rx();
|
||||||
|
spm::fill<off>(addr + i, static_cast<std::uint16_t>(lo | (hi << 8)));
|
||||||
|
}
|
||||||
spm::write_page<off>(addr);
|
spm::write_page<off>(addr);
|
||||||
spm::wait();
|
spm::wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the SRAM buffer into EEPROM byte by byte.
|
// Stream one page from the host straight into EEPROM, byte by byte.
|
||||||
void write_eeprom_page(std::uint16_t addr)
|
void store_eeprom_page(std::uint16_t addr)
|
||||||
{
|
{
|
||||||
for (std::uint16_t i = 0; i < page; ++i)
|
for (std::uint16_t i = 0; i < page; ++i)
|
||||||
ee::write<off>(addr + i, buffer[i]);
|
ee::write<off>(addr + i, rx());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase one flash page and wait it out — the erase step shared by the whole-app
|
||||||
|
// erase, the config-page rewrite and the emergency wipe.
|
||||||
|
void erase_page(std::uint16_t addr)
|
||||||
|
{
|
||||||
|
spm::erase_page<off>(addr);
|
||||||
|
spm::wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erase the whole application, one page at a time (unwritten pages stay erased).
|
// Erase the whole application, one page at a time (unwritten pages stay erased).
|
||||||
void erase_application()
|
void erase_application()
|
||||||
{
|
{
|
||||||
for (std::uint16_t a = 0; a < app_end; a += page) {
|
for (std::uint16_t a = 0; a < app_end; a += page)
|
||||||
spm::erase_page<off>(a);
|
erase_page(a);
|
||||||
spm::wait();
|
|
||||||
}
|
|
||||||
spm::rww_enable<off>();
|
spm::rww_enable<off>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,19 +171,15 @@ void read_eeprom()
|
|||||||
void write_flash()
|
void write_flash()
|
||||||
{
|
{
|
||||||
erase_application();
|
erase_application();
|
||||||
for (std::uint16_t a = 0; request_confirm(); a += page) {
|
for (std::uint16_t a = 0; request_confirm(); a += page)
|
||||||
get_page();
|
store_flash_page(a);
|
||||||
write_flash_page(a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'E': take pages the host offers behind '?' into EEPROM.
|
// 'E': take pages the host offers behind '?' into EEPROM.
|
||||||
void write_eeprom()
|
void write_eeprom()
|
||||||
{
|
{
|
||||||
for (std::uint16_t a = 0; request_confirm(); a += page) {
|
for (std::uint16_t a = 0; request_confirm(); a += page)
|
||||||
get_page();
|
store_eeprom_page(a);
|
||||||
write_eeprom_page(a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'C': replace the config page, then echo it back for the host to verify.
|
// 'C': replace the config page, then echo it back for the host to verify.
|
||||||
@@ -191,10 +187,8 @@ void write_config()
|
|||||||
{
|
{
|
||||||
if (!request_confirm())
|
if (!request_confirm())
|
||||||
return;
|
return;
|
||||||
get_page();
|
erase_page(app_end);
|
||||||
spm::erase_page<off>(app_end);
|
store_flash_page(app_end);
|
||||||
spm::wait();
|
|
||||||
write_flash_page(app_end);
|
|
||||||
spm::rww_enable<off>();
|
spm::rww_enable<off>();
|
||||||
send_flash(app_end, page);
|
send_flash(app_end, page);
|
||||||
}
|
}
|
||||||
@@ -207,8 +201,7 @@ void emergency_erase()
|
|||||||
erase_application();
|
erase_application();
|
||||||
for (std::uint16_t a = 0; a <= eeprom_end; ++a)
|
for (std::uint16_t a = 0; a <= eeprom_end; ++a)
|
||||||
ee::write<off>(a, 0xff);
|
ee::write<off>(a, 0xff);
|
||||||
spm::erase_page<off>(app_end);
|
erase_page(app_end);
|
||||||
spm::wait();
|
|
||||||
spm::rww_enable<off>();
|
spm::rww_enable<off>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,16 +118,30 @@ const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|||||||
__builtin_unreachable();
|
__builtin_unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One flash page erased and waited out. Shared, so the SPM erase command
|
||||||
|
// sequence is emitted once for the whole-app erase, the config page and the
|
||||||
|
// emergency wipe rather than three times.
|
||||||
|
[[gnu::noinline]] void erase1(std::uint16_t addr)
|
||||||
|
{
|
||||||
|
spm::erase_page<off>(addr);
|
||||||
|
spm::wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-enable RWW flash reads after programming, shared for the same reason.
|
||||||
|
[[gnu::noinline]] void rww()
|
||||||
|
{
|
||||||
|
spm::rww_enable<off>();
|
||||||
|
}
|
||||||
|
|
||||||
// Erase the whole application, one page at a time.
|
// Erase the whole application, one page at a time.
|
||||||
[[gnu::noinline]] void erase_application()
|
[[gnu::noinline]] void erase_application()
|
||||||
{
|
{
|
||||||
g_addr = 0;
|
g_addr = 0;
|
||||||
do {
|
do {
|
||||||
spm::erase_page<off>(g_addr);
|
erase1(g_addr);
|
||||||
spm::wait();
|
|
||||||
g_addr += page;
|
g_addr += page;
|
||||||
} while (g_addr < app_end);
|
} while (g_addr < app_end);
|
||||||
spm::rww_enable<off>();
|
rww();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'f'/'e': stream memory back one page per host '!'. send advances g_addr, so
|
// 'f'/'e': stream memory back one page per host '!'. send advances g_addr, so
|
||||||
@@ -165,10 +179,9 @@ void write_config()
|
|||||||
if (!request_confirm())
|
if (!request_confirm())
|
||||||
return;
|
return;
|
||||||
g_addr = app_end;
|
g_addr = app_end;
|
||||||
spm::erase_page<off>(g_addr);
|
erase1(g_addr);
|
||||||
spm::wait();
|
|
||||||
store_page(true);
|
store_page(true);
|
||||||
spm::rww_enable<off>();
|
rww();
|
||||||
g_addr = app_end;
|
g_addr = app_end;
|
||||||
g_cnt = page;
|
g_cnt = page;
|
||||||
send(true);
|
send(true);
|
||||||
@@ -182,9 +195,8 @@ void write_config()
|
|||||||
do {
|
do {
|
||||||
ee::write<off>(g_addr, 0xff);
|
ee::write<off>(g_addr, 0xff);
|
||||||
} while (++g_addr <= eeprom_end);
|
} while (++g_addr <= eeprom_end);
|
||||||
spm::erase_page<off>(app_end);
|
erase1(app_end);
|
||||||
spm::wait();
|
rww();
|
||||||
spm::rww_enable<off>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The password gate. A byte of 0 requests emergency erase; a wrong byte hangs
|
// The password gate. A byte of 0 requests emergency erase; a wrong byte hangs
|
||||||
|
|||||||
Reference in New Issue
Block a user