This commit is contained in:
mikecarper
2026-09-05 19:15:57 +00:00
parent c5c9e61c7b
commit 63574eb686
3 changed files with 50 additions and 28 deletions
+16 -10
View File
@@ -1650,9 +1650,9 @@ provisioned locally before either application or bootloader OTA.</p>
</tr>
<tr>
<td>Qualified internal-flash target</td>
<td style="text-align: right;">normal <code>0xED000</code> limit</td>
<td style="text-align: right;">normal <code>0xED000</code> flash limit; top 64 KiB SRAM reserved for application-delta staging</td>
<td style="text-align: right;">shared internal slot, exact start <code>0xE2000</code></td>
<td style="text-align: right;">the same eleven-page slot; no second reservation</td>
<td style="text-align: right;">the same eleven-page slot; no second flash reservation</td>
<td>source <code>0xED</code>, flags <code>0x0A</code></td>
</tr>
<tr>
@@ -1698,10 +1698,13 @@ write a raw-sector compatibility record because a blank sector is not proof of
ownership. Upgrade preview.12 through USB/BLE DFU or SWD. Both fmt2 application
and fmt3 bootloader OTA then require the installed BLM2 metadata to match the
live SoftDevice/application layout; neither MeshCore nor OTAFIX uses sector 1.</p>
<p>The internal path does not change the application linker or permanently set
aside separate app-OTA, boot-package, and scratch regions. The ordinary
bottom-aligned internal store holds one container at a time: either an
application delta or a bootloader package.</p>
<p>The internal path does not change the application's <code>0xED000</code> flash limit or
permanently set aside separate app-OTA, boot-package, and flash-scratch
regions. Qualified applications do use a dedicated linker that reserves the
top 64 KiB of SRAM for hybrid application-delta staging and a retained
authorization record. That SRAM is never part of a bootloader-update package.
The ordinary bottom-aligned internal flash store holds one container prefix at
a time: either an application delta or the complete bootloader package.</p>
<p>The exact bootloader container is 41,330 bytes: 365 bytes of signed mOTA
metadata, a 40 KiB payload, and the five-byte trailer. Below the normal
<code>0xED000</code> store ceiling it bottom-aligns at <code>0xE2000</code>. Admission requires a
@@ -1711,10 +1714,13 @@ erasing and compacts the payload forward in place to the page-aligned raw range
<code>0xE2000..0xEC000</code>; it verifies every page and the whole image before asking the
MBR to copy that image over <code>0xF4000..0xFE000</code>.</p>
<p>An ordinary application delta can be smaller or larger than this eleven-page
shape. It bottom-aligns dynamically below <code>0xED000</code> and may begin below
<code>0xE2000</code>; its detools workspace must stop at its actual container start, while
the reconstructed application must stop below <code>0xED000</code>. The two package kinds
are mutually exclusive because they use the same store.</p>
shape. On the qualified profile, a delta larger than one page uses the minimum
page-aligned flash prefix ending at <code>0xED000</code> and keeps up to 64 KiB of its
logical tail in reset-retained SRAM; its detools workspace must stop at the
actual flash-prefix start. A one-page-or-smaller delta stays wholly in flash.
In either case the reconstructed application must stop below <code>0xED000</code>. The
two package kinds are mutually exclusive because they use the same flash
store, and a bootloader package never uses the volatile SRAM suffix.</p>
<p>For internal-self-update builds, an absent or corrupt live <code>EndF</code> disables
<strong>all</strong> internal staging before the first erase. The older 608 KiB rescue
estimate is unsafe when a normally linked application may extend to
+33 -17
View File
@@ -2629,7 +2629,9 @@ fills its own <code>leaves[i]</code> as each verified block lands.</p>
(<code>!= FF FF FF FF</code>). Because <code>leaves[]</code> live in the staged flash region, availability <strong>survives reboot</strong>.</p>
<p><strong>Commit order per block (crash-safe):</strong> (1) verify proof, (2) write block payload to its offset, (3) write
<code>leaves[i]</code> <strong>last</strong>. A power loss before step 3 leaves the slot erased -&gt; the block is simply re-fetched
(idempotent). On boot a node rebuilds an in-RAM present-bitmap by scanning <code>leaves[]</code>.</p>
(idempotent). On boot a node rebuilds an in-RAM present-bitmap by scanning <code>leaves[]</code> for a persistent,
reopenable store. A hybrid nRF52 transfer is the deliberate exception: its SRAM-backed payload suffix is
volatile, so the application refuses to adopt that staged header instead of rebuilding partial progress.</p>
<p><strong>Resume (<code>OtaManager::resumeStaged</code> + <code>OtaStore::checkpoint</code>/<code>reopen</code>):</strong> an interrupted fetch resumes from
the staged container after a reboot - re-parse the stored manifest, recompute geometry, count present
blocks, continue fetching the holes (or jump straight to COMPLETE). The checkpoint cadence (persist progress
@@ -2640,11 +2642,14 @@ floor requires a strictly newer manifest. An explicit MID pull may deliberately
package and keeps target <code>0</code> as a MID-only wildcard. Stores keep <code>leaves[]</code> in RAM until flush and never
auto-GC, preserving resumable progress. The debug/operator equivalent is <code>ota dev resume &lt;MID8&gt;</code>; after a
reboot the MID is mandatory, while a no-argument form may only reuse a still-active session MID. It never
uses the <code>nullptr</code> automatic-adoption path, so a malformed MID or no active MID fails closed.</p>
uses the <code>nullptr</code> automatic-adoption path, so a malformed MID or no active MID fails closed.
Hybrid nRF52 staging cannot enter this resume path after an application restart, even if its flash prefix
still contains metadata; the complete logical container must be fetched again.</p>
<p><strong>Flash-store note (RX-safe writes):</strong> a flash page-erase halts the CPU (~85 ms on nRF52) and starves LoRa
RX, so the flash stores (<code>OtaStoreFlashNrf52</code>/<code>OtaStoreFlashEsp32</code>) <strong>coalesce writes to the erase unit</strong>
(4 KB page / sector) and commit each once off the per-packet path - RAM stays O(one page), not O(image). A
small delta that fits page 0 does zero flash I/O until COMPLETE.</p>
(4 KB page / sector) and commit each once off the per-packet path. Ordinary stores keep RAM at O(one page),
not O(image); a qualified hybrid nRF52 profile additionally reserves one fixed 64 KiB staging arena. A small
delta that fits page 0 does zero flash I/O until COMPLETE.</p>
<hr />
<h2 id="8-lora-ota-protocol">8. LoRa OTA protocol</h2>
<p>Carried in MeshCore packets with <strong><code>PAYLOAD_TYPE_OTA = 0x0C</code></strong>. Every OTA packet payload is:</p>
@@ -3199,16 +3204,26 @@ retains its <code>.part</code> file; it does not erase the unrelated manual-inst
<li><strong>nRF52 internal staging ceiling:</strong> an internal-store application derives the ceiling from facts available
in every build, not a board-name list. A companion that actually links the internal ExtraFS datastore stays
below <code>0xD4000</code>; a default linker region or a role that does not mount ExtraFS can reclaim the unused
100 KiB through <code>0xED000</code>. An internal bootloader-self-update target keeps that normal linker and ceiling;
it does not reserve a second boot-package or scratch region. The application uses a larger-than-legacy
window only when the
installed bootloader advertises the GPREGRET2 ceiling-handoff capability. The bootloader treats every
unknown/legacy handoff value as
<code>0xD4000</code>, and accepts a container only at the bottom-aligned position for the selected ceiling.</li>
100 KiB through <code>0xED000</code>. A qualified internal bootloader-self-update target keeps that normal flash
ceiling and does not reserve a second boot-package or flash-scratch region, but it uses a dedicated
application linker that reserves the top 64 KiB of SRAM (<code>0x20030000..0x20040000</code>) plus a 72-byte
retained authorization record after the existing persistent clock bytes. Application deltas larger than
one flash page use a deterministic page-aligned flash prefix ending at <code>0xED000</code> and place up to 64 KiB
of the logical container tail in that SRAM. Packages of one page or less, and bootloader-update packages,
remain wholly in flash. Hybrid staging is enabled only when the installed bootloader contains exactly one
valid <code>MOTARAMA</code> capability marker; otherwise a larger hybrid-profile fetch fails before erase. Immediately
before a software reset the application publishes a valid-last <code>MOTAHYB1</code> record binding the split geometry
and normalized container hash. The bootloader consumes it once and rejects power-loss, stale-reset, corrupt,
or mismatched-RAM cases before its first application write. A hybrid transfer cannot resume after an
application restart because its suffix is deliberately volatile. The bootloader treats every unknown/legacy
GPREGRET2 handoff value as <code>0xD4000</code>, and accepts a flash-only container only at the bottom-aligned position
for the selected ceiling.</li>
<li><strong>nRF52 dynamic apply window:</strong> the post-build hook records the resolved app base, linked app end,
internal-ExtraFS/SD/QSPI storage flags, and desired staging ceiling immediately before <code>EndF</code>. <code>motatool</code> reads that authenticated
firmware record and chooses <code>memory_size</code> from the actual patch size and bottom-aligned stage address;
firmware without the record retains the conservative <code>0x98000</code> default. Before writing <code>APRV</code>, an
internal-ExtraFS/SD/QSPI/hybrid-RAM storage flags, and desired staging ceiling immediately before <code>EndF</code>.
<code>motatool</code> reads that authenticated firmware record and chooses <code>memory_size</code> from the actual patch size
and bottom-aligned stage address. For a hybrid base it charges only the deterministic flash prefix against
that workspace; the retained-RAM suffix is still part of the same hashed logical container.
Firmware without the record retains the conservative <code>0x98000</code> default. Before writing <code>APRV</code>, an
internal-store app validates the staged-address bound; an external SD/QSPI app validates the full detools
geometry against the application workspace. The bootloader independently parses and validates the same
geometry before its first application write. Expanded auto-sized packages require a bootloader
@@ -3280,10 +3295,11 @@ retains its <code>.part</code> file; it does not erase the unrelated manual-inst
It holds either an ordinary app delta or the exact 41,330-byte v3 container, never both. The boot package
bottom-aligns at <code>0xE2000</code>; a hash-valid live <code>EndF</code> must prove the complete running image ends at or below
that address before the first erase. OTAFIX reads each source window before erasing and compacts the
payload forward in the same eleven pages to raw <code>0xE2000..0xEC000</code>; no separate scratch bank or special
application linker exists. GPREGRET <code>0x6B</code> plus GPREGRET2 <code>0xED</code> selects boot update, while ordinary app
apply uses GPREGRET <code>0x6A</code> plus the same storage source. Exact installed/candidate capability flags are
<code>0x0A</code> (<code>STAGE_CEILING|BOOT_UPDATE</code>). Ordinary deltas remain dynamically sized, may start below
payload forward in the same eleven pages to raw <code>0xE2000..0xEC000</code>; no separate flash scratch bank exists.
The qualified application linker reserves a 64 KiB SRAM arena only for hybrid application deltas; the
bootloader package remains wholly in flash. GPREGRET <code>0x6B</code> plus GPREGRET2 <code>0xED</code> selects boot update,
while ordinary app apply uses GPREGRET <code>0x6A</code> plus the same storage source. Exact installed/candidate
capability flags are <code>0x0A</code> (<code>STAGE_CEILING|BOOT_UPDATE</code>). Ordinary deltas remain dynamically sized, may start below
<code>0xE2000</code>, and reconstruct only below the normal <code>0xED000</code> app ceiling. The same signature, explicit
confirmation, exact identity, vector, CRC, and single-marker rules as the XIAO path apply. Bootloader FULL
admission is isolated from ordinary application FULL policy, and privileged partials are never resumed
File diff suppressed because one or more lines are too long