Add retained-RAM staging for nRF52 mOTA

This commit is contained in:
mikecarper committed 2026-09-05 12:08:21 -07:00
1 parent 23bf83277c
commit 49dcba5c6e
25 files changed
+2071 -83

No files matched your search

+210 -11
View File
@@ -458,6 +458,39 @@ bool ota_apply_verify_slot(ApplyState&) { return false; }
bool ota_apply_arm() { return false; }
bool ota_apply_detools_mota(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { st = ApplyState(); strcpy(msg, "use ota_apply_mota_nrf52"); return false; }
#if defined(OTA_FLASH_STORE)
// Points at OtaContext's long-lived member only after the application package
// has passed every gate and APRV was committed. The retained descriptor must
// not be published until the command reply has drained and reset is imminent.
static OtaStoreFlashNrf52* g_nrf52_apply_store = nullptr;
#endif
static void ota_nrf52_set_reset_handoff(uint8_t request, uint8_t source) {
uint8_t sd_en = 0;
sd_softdevice_is_enabled(&sd_en);
if (sd_en) { // POWER is SD-restricted while the SoftDevice runs
sd_power_gpregret_clr(1, 0xFFFFFFFF);
sd_power_gpregret_set(1, source);
sd_power_gpregret_clr(0, 0xFFFFFFFF);
if (request) sd_power_gpregret_set(0, request);
} else {
NRF_POWER->GPREGRET2 = source;
NRF_POWER->GPREGRET = request;
}
}
static bool ota_nrf52_clear_reset_reasons() {
uint8_t sd_en = 0;
if (sd_softdevice_is_enabled(&sd_en) != NRF_SUCCESS) return false;
if (sd_en) {
return sd_power_reset_reason_clr(0xFFFFFFFFu) == NRF_SUCCESS;
}
NRF_POWER->RESETREAS = NRF_POWER->RESETREAS;
__DMB();
__DSB();
return NRF_POWER->RESETREAS == 0u;
}
void ota_reboot_to_apply() { // public: set the apply magic + reset (does not return)
uint8_t stage_handoff = GPREGRET2_OTA_STAGE_LEGACY;
#if defined(OTA_SD_STORE)
@@ -465,19 +498,24 @@ void ota_reboot_to_apply() { // public: set the apply magic +
#elif defined(OTA_QSPI_STORE)
stage_handoff = GPREGRET2_OTA_STAGE_QSPI;
#elif defined(OTA_FLASH_STORE)
stage_handoff = mota_nrf52_flash_stage_handoff(ota_nrf52_effective_stage_ceiling());
#endif
uint8_t sd_en = 0;
sd_softdevice_is_enabled(&sd_en);
if (sd_en) { // POWER is SD-restricted while the SoftDevice runs
sd_power_gpregret_clr(1, 0xFFFFFFFF);
sd_power_gpregret_set(1, stage_handoff);
sd_power_gpregret_clr(0, 0xFFFFFFFF);
sd_power_gpregret_set(0, GPREGRET_OTA_APPLY);
if (g_nrf52_apply_store && g_nrf52_apply_store->is_hybrid()) {
// The bootloader admits retained SRAM only after a clean software-reset
// handoff. Clear every prior reset reason immediately before publishing
// the valid-last descriptor, then make A6/6A the only apply trigger.
if (!ota_nrf52_clear_reset_reasons() ||
!g_nrf52_apply_store->publish_hybrid_handoff()) {
// Never fall back to scanning the flash prefix as a legacy container:
// it intentionally advertises a total larger than its flash span.
ota_nrf52_set_reset_handoff(0u, 0xBDu);
NVIC_SystemReset();
return;
}
stage_handoff = GPREGRET2_OTA_STAGE_HYBRID;
} else {
NRF_POWER->GPREGRET2 = stage_handoff;
NRF_POWER->GPREGRET = GPREGRET_OTA_APPLY;
stage_handoff = mota_nrf52_flash_stage_handoff(ota_nrf52_effective_stage_ceiling());
}
#endif
ota_nrf52_set_reset_handoff(GPREGRET_OTA_APPLY, stage_handoff);
NVIC_SystemReset(); // does not return
}
@@ -514,6 +552,9 @@ static bool ota_apply_mota_nrf52_impl(const uint8_t* buf, uint32_t len,
const uint8_t* rescue_base_hash,
uint32_t local_target_id,
ApplyState& st, char* msg) {
#if defined(OTA_FLASH_STORE)
g_nrf52_apply_store = nullptr;
#endif
st = ApplyState();
MotaManifest m;
if (!mota_parse(buf, len, m)) { strcpy(msg, "parse failed"); return false; }
@@ -638,6 +679,164 @@ bool ota_rescue_mota_nrf52(const uint8_t* buf, uint32_t len, const SignerAllowli
return ota_apply_mota_nrf52_impl(buf, len, allow, operator_base_hash, local_target_id, st, msg);
}
#if defined(OTA_FLASH_STORE)
bool ota_apply_mota_nrf52(OtaStoreFlashNrf52& store,
const SignerAllowlist& allow,
ApplyState& st, char* msg) {
static const size_t CAP = 96;
g_nrf52_apply_store = nullptr;
st = ApplyState();
uint8_t header[8];
uint8_t manifest[MOTA_MFL];
const uint32_t total = store.staged_size();
if (total < MOTA_NRF52_CONTAINER_MIN_SIZE ||
!store.read(0, header, sizeof(header)) ||
memcmp(header, MOTA_MAGIC, sizeof(MOTA_MAGIC)) != 0 ||
rd_u32le(header + 4u) != total ||
!store.read(8u, manifest, sizeof(manifest))) {
strcpy(msg, "internal container parse failed");
return false;
}
MotaManifest m;
if (!mota_parse_manifest(manifest, sizeof(manifest), m)) {
strcpy(msg, "internal manifest parse failed");
return false;
}
if (m.is_bootloader()) {
strcpy(msg, "use explicit ota bootloader install");
return false;
}
if (m.is_full() || m.codec_id != CODEC_DETOOLS_INPLACE) {
strcpy(msg, "not an in-place delta");
return false;
}
const uint64_t payload_off64 =
8u + MOTA_MFL + (uint64_t)m.block_count * 4u;
if (payload_off64 > UINT32_MAX ||
payload_off64 + m.payload_size + 5u != total) {
strcpy(msg, "internal container layout mismatch");
return false;
}
const uint32_t payload_off = (uint32_t)payload_off64;
const uint32_t app_base = mota_nrf52_app_base();
const uint32_t app_ceiling = mota_nrf52_application_ceiling();
if (!mota_nrf52_target_image_fits(app_base, m.image_size,
app_ceiling)) {
strcpy(msg, "target image exceeds linked application region");
return false;
}
st.image_size = m.image_size;
memcpy(st.image_hash, m.image_hash, sizeof(st.image_hash));
st.manifest_ok = true;
const OtaBlCaps bl = ota_bootloader_app_caps();
if (!bl.present) {
strcpy(msg, "bootloader has no OTA-apply support; update it first");
return false;
}
if (bl.apply_abi < m.format_ver ||
!(bl.codec_mask & (1u << m.codec_id))) {
snprintf(msg, CAP,
"bootloader cannot apply update (abi=%u codecs=0x%x; need %u/%u)",
bl.apply_abi, bl.codec_mask, m.format_ver, m.codec_id);
return false;
}
if (store.is_hybrid() &&
!ota_bootloader_supports_hybrid(ota_bootloader_ram_caps())) {
strcpy(msg, "bootloader lacks hybrid RAM staging support");
return false;
}
// Verify through the logical OtaStore. A hybrid container is deliberately
// non-contiguous; data() is never consulted by this path. The resulting SHA
// is normalized across APRV bytes and is the handoff-record hash contract.
VerifyResult vr = ota_verify(
static_cast<const OtaStore&>(store), allow, manifest);
st.sig_ok = vr.sig_ok;
st.trusted = vr.trusted;
if (!vr.root_ok || !vr.payload_ok || !vr.image_ok ||
!vr.container_hash_ok) {
strcpy(msg, "payload hash mismatch (incomplete or corrupt .mota)");
return false;
}
SelfFwInfo fi;
if (!ota_self_firmware(fi) || !fi.valid) {
strcpy(msg, "cannot read running firmware (no EndF)");
return false;
}
if (!m.base_hash || memcmp(m.base_hash, fi.body_hash, 8u) != 0) {
strcpy(msg, "not built for the running firmware (base mismatch)");
return false;
}
st.slot_ok = true;
if (vr.is_signed) {
if (!vr.sig_ok) {
strcpy(msg, "bad signature");
return false;
}
if (!vr.trusted) {
strcpy(msg, "untrusted signer (pubkey not in allowlist)");
return false;
}
}
uint8_t patch_header[32];
const uint32_t patch_header_len =
m.payload_size < sizeof(patch_header)
? m.payload_size : (uint32_t)sizeof(patch_header);
InplacePatchDims d;
if (!store.read(payload_off, patch_header, patch_header_len) ||
!parse_inplace_patch_dims(patch_header, patch_header_len, d)) {
strcpy(msg, "bad in-place patch header");
return false;
}
if (d.memory == 0u || d.segment != MOTA_NRF52_FLASH_PAGE ||
d.shift > d.memory || d.shift % d.segment != 0u ||
d.from > d.memory - d.shift || d.from != fi.image_len ||
d.to > d.memory || d.to != m.image_size ||
!mota_nrf52_internal_patch_workspace_valid(
d.memory, app_base, store.write_start(), m.image_size,
app_ceiling)) {
strcpy(msg, "invalid in-place patch geometry");
return false;
}
if (store.is_hybrid()) {
uint32_t planned_start = 0, planned_flash = 0, planned_ram = 0;
if (fi.image_len > UINT32_MAX - app_base ||
!mota_nrf52_hybrid_stage_plan(
total, app_base, app_base + fi.image_len,
MOTA_NRF52_STAGE_CEILING_EXPANDED,
planned_start, planned_flash, planned_ram) ||
planned_start != store.write_start() ||
planned_flash != store.flash_len() ||
planned_ram != store.ram_len()) {
strcpy(msg, "hybrid staging geometry mismatch");
return false;
}
} else if (store.ram_len() != 0u) {
strcpy(msg, "internal staging geometry mismatch");
return false;
}
if (!store.approve_for_application(vr.container_hash)) {
strcpy(msg, "internal approval/handoff preparation failed");
return false;
}
g_nrf52_apply_store = &store;
snprintf(msg, CAP,
"verified%s%s; rebooting into bootloader after this reply",
vr.is_signed ? " (signer trusted)" : " (unsigned)",
store.is_hybrid() ? " in flash+RAM" : "");
return true;
}
#endif
#if defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE)
static const size_t NRF52_APPLY_MSG_CAP = 96;
+8 -2
View File
@@ -80,6 +80,13 @@ bool ota_apply_detools_mota(const uint8_t* buf, uint32_t len,
// Returns true (msg = "verified...") when approved, false (msg = the first failing gate) otherwise.
bool ota_apply_mota_nrf52(const uint8_t* buf, uint32_t len,
const SignerAllowlist& allow, ApplyState& st, char* msg);
#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE)
class OtaStoreFlashNrf52;
// Streaming internal-store path. Required for the hybrid source because its
// flash prefix and retained-SRAM suffix have no single contiguous address.
bool ota_apply_mota_nrf52(OtaStoreFlashNrf52& store,
const SignerAllowlist& allow, ApplyState& st, char* msg);
#endif
// Recovery for failed app-side EndF validation. This is available only through the explicit
// `ota rescue install <base_hash16>` CLI command and never through normal or automatic installation.
bool ota_rescue_mota_nrf52(const uint8_t* buf, uint32_t len,
@@ -115,7 +122,6 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
#endif
#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) && \
defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
class OtaStoreFlashNrf52;
bool ota_prepare_bootloader_update_nrf52(OtaStoreFlashNrf52& store,
const SignerAllowlist& allow,
const OtaBootloaderIdentity& installed,
@@ -137,7 +143,7 @@ bool ota_installed_bootloader_identity(OtaBootloaderIdentity& out);
inline uint8_t ota_nrf52_boot_result_or_zero(uint8_t value) {
return ((value >= 0x90u && value <= 0x9Fu) ||
// B0..BC are the ordinary apply progress/failure/success results;
// SD-backed OTAFIX additionally uses BD for an authorization failure.
// Retained-source OTAFIX paths use BD for authorization/handoff failure.
(value >= 0xB0u && value <= 0xBDu) ||
(value >= 0xC0u && value <= 0xCFu)) ? value : 0u;
}
+61
View File
@@ -15,6 +15,7 @@
#if defined(NRF52_PLATFORM)
#include "OtaFlashLayout_nrf52.h"
#endif
#include "OtaHybridHandoff.h"
namespace mesh {
namespace ota {
@@ -29,6 +30,56 @@ struct OtaBlCaps {
uint8_t storage_flags = 0; // OTA_BL_STORAGE_* capability bits
};
// Independent marker for the reset-retained hybrid source. Keep this separate
// from MOTABLDR: bootloader self-update policy relies on the exact deployed
// storage_flags byte, and older applications must continue to parse it.
struct OtaRamCaps {
bool present = false;
uint16_t abi = 0;
uint16_t record_len = 0;
uint32_t arena_size = 0;
};
inline bool ota_ram_caps_marker_parse(const uint8_t raw[16],
OtaRamCaps& out) {
out = OtaRamCaps();
if (!raw || memcmp(raw, MOTA_RAM_CAP_MAGIC,
sizeof(MOTA_RAM_CAP_MAGIC)) != 0) {
return false;
}
const uint16_t abi = mota_hybrid_rd16(raw + 8u);
const uint16_t record_len = mota_hybrid_rd16(raw + 10u);
const uint32_t arena_size = mota_hybrid_rd32(raw + 12u);
if (abi != MOTA_RAM_CAP_ABI || record_len != MOTA_HYBRID_AUTH_LEN ||
arena_size != MOTA_NRF52_HYBRID_RAM_SIZE) {
return false;
}
out.present = true;
out.abi = abi;
out.record_len = record_len;
out.arena_size = arena_size;
return true;
}
inline OtaRamCaps ota_ram_caps_scan_aligned(const uint8_t* bytes, size_t len) {
OtaRamCaps result;
if (!bytes) return result;
uint8_t matches = 0;
for (size_t off = 0; off + 16u <= len; off += 4u) {
OtaRamCaps candidate;
if (!ota_ram_caps_marker_parse(bytes + off, candidate)) continue;
if (++matches != 1u) return OtaRamCaps();
result = candidate;
}
return matches == 1u ? result : OtaRamCaps();
}
inline bool ota_bootloader_supports_hybrid(const OtaRamCaps& caps) {
return caps.present && caps.abi == MOTA_RAM_CAP_ABI &&
caps.record_len == MOTA_HYBRID_AUTH_LEN &&
caps.arena_size == MOTA_NRF52_HYBRID_RAM_SIZE;
}
static const uint8_t OTA_BL_STORAGE_SD = 0x01;
static const uint8_t OTA_BL_STORAGE_STAGE_CEILING = 0x02;
static const uint8_t OTA_BL_STORAGE_QSPI = 0x04;
@@ -184,6 +235,16 @@ inline OtaBlCaps ota_bootloader_app_caps() {
#endif
}
inline OtaRamCaps ota_bootloader_ram_caps() {
#if defined(NRF52_PLATFORM) && defined(OTA_HYBRID_RAM_STORE)
const uint8_t* lo = (const uint8_t*)(uintptr_t)MOTA_NRF52_BL_START;
const uint8_t* hi = (const uint8_t*)(uintptr_t)MOTA_NRF52_BL_END;
return ota_ram_caps_scan_aligned(lo, (size_t)(hi - lo));
#else
return OtaRamCaps();
#endif
}
// Scan the same region using the stricter, fail-closed rules required before
// replacing the bootloader itself. Legacy bootloaders remain valid for normal
// application deltas, but can never enter this privileged path.
+8 -2
View File
@@ -1127,7 +1127,8 @@ static bool handle_dev(const char* d, char* reply, OtaContext& c) {
strcpy(reply, "ERR completed fetch is in the host folder, not local verification storage");
return true;
}
#if defined(NRF52_PLATFORM) && (defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE))
#if defined(NRF52_PLATFORM) && \
(defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE) || defined(OTA_FLASH_STORE))
if (c.manager.fetchState() == OtaManager::COMPLETE) {
VerifyResult r = ota_verify(static_cast<const OtaStore&>(c.fetch_store), c.allow);
sprintf(reply, "verify parsed=%d root=%d payload=%d img=%d signed=%d sig=%d trust=%d | ok=%d auto=%d",
@@ -1137,7 +1138,8 @@ static bool handle_dev(const char* d, char* reply, OtaContext& c) {
}
#endif
const uint8_t* buf; uint32_t len;
#if defined(NRF52_PLATFORM) && (defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE))
#if defined(NRF52_PLATFORM) && \
(defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE) || defined(OTA_FLASH_STORE))
buf = c.serve_buf; len = c.serve_buf ? c.serve_expected : 0;
#else
if (c.manager.fetchState() == OtaManager::COMPLETE) { buf = c.fetch_store.data(); len = c.fetch_store.staged_size(); }
@@ -1184,6 +1186,10 @@ static bool handle_dev(const char* d, char* reply, OtaContext& c) {
}
} else if (strncmp(d, "clear", 5) == 0) {
if (c.apply_pending) {
strcpy(reply, "ERR update is armed; reboot is pending");
return true;
}
const bool was_folder = c.fetch_to_folder;
bool was_sd_archive = false;
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
+12 -4
View File
@@ -258,13 +258,21 @@ struct OtaContext {
bool ok;
#if defined(NRF52_PLATFORM) && (defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE))
ok = ota_apply_mota_nrf52(fetch_store, allow, apply_st, msg);
#elif defined(NRF52_PLATFORM)
#elif defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE)
if (rescue_base_hash) {
ok = ota_rescue_mota_nrf52(fetch_store.data(), fetch_store.staged_size(), allow,
rescue_base_hash, manager.target(), apply_st, msg);
if (fetch_store.is_hybrid()) {
strncpy(msg, "rescue cannot use volatile hybrid staging", 96);
msg[95] = 0;
ok = false;
} else {
ok = ota_rescue_mota_nrf52(fetch_store.data(), fetch_store.staged_size(), allow,
rescue_base_hash, manager.target(), apply_st, msg);
}
} else {
ok = ota_apply_mota_nrf52(fetch_store.data(), fetch_store.staged_size(), allow, apply_st, msg);
ok = ota_apply_mota_nrf52(fetch_store, allow, apply_st, msg);
}
#elif defined(NRF52_PLATFORM)
ok = ota_apply_mota_nrf52(fetch_store.data(), fetch_store.staged_size(), allow, apply_st, msg);
#elif defined(ESP32_PLATFORM) && defined(OTA_FLASH_STORE)
ok = ota_apply_detools_mota(fetch_store, allow, apply_st, msg);
#else
+67 -2
View File
@@ -15,7 +15,9 @@ namespace ota {
static const uint32_t MOTA_NRF52_APP_BASE_S140_V6 = 0x00026000u;
static const uint32_t MOTA_NRF52_APP_BASE_S140_V7 = 0x00027000u;
#if defined(NRF52_PLATFORM)
#if defined(MOTA_NRF52_TEST_APP_BASE)
inline uint32_t mota_nrf52_app_base() { return MOTA_NRF52_TEST_APP_BASE; }
#elif defined(NRF52_PLATFORM)
extern "C" uint32_t __flash_arduino_start[]; // nrf52_common.ld: ORIGIN(FLASH)
extern "C" uint32_t __flash_arduino_end[]; // nrf52_common.ld: ORIGIN(FLASH) + LENGTH(FLASH)
extern "C" const uint8_t g_meshcore_internal_extrafs __attribute__((weak));
@@ -54,6 +56,13 @@ static const uint8_t GPREGRET2_OTA_STAGE_LEGACY = 0xD4u;
static const uint8_t GPREGRET2_OTA_STAGE_EXPANDED = 0xEDu;
static const uint8_t GPREGRET2_OTA_STAGE_QSPI = 0x51u;
static const uint8_t GPREGRET2_OTA_STAGE_SD = 0x53u;
static const uint8_t GPREGRET2_OTA_STAGE_HYBRID = 0xA6u;
// Fixed SRAM reserved by the dedicated internal-only mOTA linker. A hybrid
// container is one logical stream: a page-aligned flash prefix followed by a
// reset-retained suffix at this address.
static const uint32_t MOTA_NRF52_HYBRID_RAM_START = 0x20030000u;
static const uint32_t MOTA_NRF52_HYBRID_RAM_SIZE = 0x00010000u;
inline uint8_t mota_nrf52_flash_stage_handoff(uint32_t effective_stage_ceiling) {
if (effective_stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED)
@@ -123,7 +132,9 @@ inline uint32_t mota_nrf52_stage_ceiling_for_layout(uint32_t linked_app_end,
}
inline uint32_t mota_nrf52_layout_stage_ceiling() {
#if defined(NRF52_PLATFORM)
#if defined(MOTA_NRF52_TEST_LAYOUT_STAGE_CEILING)
return MOTA_NRF52_TEST_LAYOUT_STAGE_CEILING;
#elif defined(NRF52_PLATFORM)
// DataStore.cpp defines this symbol only for a companion that really constructs internal ExtraFS.
// An undefined weak symbol has address zero, so other roles do not inherit nrf52_base's broad EXTRAFS
// feature define as a false-positive storage reservation.
@@ -273,6 +284,51 @@ inline bool mota_nrf52_stage_plan(uint32_t total_size, uint32_t app_base, uint32
mota_nrf52_layout_stage_ceiling(), out_start);
}
// Plan the exact split used by a hybrid-capable profile for every application
// delta larger than one page. Reserve as little flash as possible (but always
// one complete page for the header/manifest/approval), leaving the largest
// possible detools workspace below it. The remainder is the reset-retained
// SRAM suffix.
inline bool mota_nrf52_hybrid_stage_plan(uint32_t total_size,
uint32_t app_base,
uint32_t app_end,
uint32_t stage_ceiling,
uint32_t& out_start,
uint32_t& out_flash_len,
uint32_t& out_ram_len) {
if (stage_ceiling != MOTA_NRF52_STAGE_CEILING_EXPANDED ||
(app_base != MOTA_NRF52_APP_BASE_S140_V6 &&
app_base != MOTA_NRF52_APP_BASE_S140_V7) ||
!mota_nrf52_layout_valid(app_base, stage_ceiling) ||
app_end < app_base || app_end > stage_ceiling ||
total_size < MOTA_NRF52_CONTAINER_MIN_SIZE) {
return false;
}
uint32_t flash_needed = total_size > MOTA_NRF52_HYBRID_RAM_SIZE
? total_size - MOTA_NRF52_HYBRID_RAM_SIZE : 0u;
if (flash_needed < MOTA_NRF52_FLASH_PAGE)
flash_needed = MOTA_NRF52_FLASH_PAGE;
if (flash_needed > UINT32_MAX - (MOTA_NRF52_FLASH_PAGE - 1u))
return false;
const uint32_t flash_len =
(flash_needed + MOTA_NRF52_FLASH_PAGE - 1u) &
~(MOTA_NRF52_FLASH_PAGE - 1u);
if (flash_len >= total_size || flash_len > stage_ceiling - app_end)
return false;
const uint32_t start = stage_ceiling - flash_len;
const uint32_t ram_len = total_size - flash_len;
if (start < app_end || ram_len == 0u ||
ram_len > MOTA_NRF52_HYBRID_RAM_SIZE)
return false;
out_start = start;
out_flash_len = flash_len;
out_ram_len = ram_len;
return true;
}
// Durable internal-store cancellation has to work even when the live
// OtaStore object is empty (for example, autofetch is off after a reboot). It
// also has to invalidate every older bottom-aligned header: invalidating only
@@ -294,6 +350,15 @@ inline bool mota_nrf52_staged_header_matches_layout(
return true;
}
uint32_t hybrid_flash_len = 0, hybrid_ram_len = 0;
if (effective_stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED &&
mota_nrf52_hybrid_stage_plan(
total_size, app_base, app_end, effective_stage_ceiling,
planned, hybrid_flash_len, hybrid_ram_len) &&
planned == address) {
return true;
}
// A device that moved from the legacy D4000 ceiling to the expanded ED000
// ceiling can still contain an older, otherwise reopenable legacy header.
// The expanded layout has no filesystem in that range, so it is safe to
+149
View File
@@ -0,0 +1,149 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "OtaFlashLayout_nrf52.h"
// Reset-retained authorization for the nRF52840 hybrid internal-flash/SRAM
// staging source. The application publishes this record only after streaming
// verification and approval. OTAFIX copies and consumes it before trusting any
// retained bytes. A power loss destroys the SRAM suffix and therefore makes
// the hash check fail closed before the first application write.
namespace mesh {
namespace ota {
static const uintptr_t MOTA_HYBRID_AUTH_ADDR = 0x20006008u;
static const uint16_t MOTA_HYBRID_AUTH_VERSION = 1u;
static const uint16_t MOTA_HYBRID_AUTH_LEN = 72u;
static const uint8_t MOTA_HYBRID_AUTH_PURPOSE_APP = 1u;
static const uint8_t MOTA_HYBRID_AUTH_FORMAT_APP = 2u;
static const uint8_t MOTA_HYBRID_AUTH_MAGIC[8] = {
'M', 'O', 'T', 'A', 'H', 'Y', 'B', '1'};
static const uint8_t MOTA_RAM_CAP_MAGIC[8] = {
'M', 'O', 'T', 'A', 'R', 'A', 'M', 'A'};
static const uint16_t MOTA_RAM_CAP_ABI = 1u;
static const uint16_t MOTA_RAM_CAP_RECORD_LEN = MOTA_HYBRID_AUTH_LEN;
static_assert(MOTA_HYBRID_AUTH_ADDR + MOTA_HYBRID_AUTH_LEN == 0x20006050u,
"hybrid auth must occupy the dedicated retained-RAM tail");
static_assert(MOTA_NRF52_HYBRID_RAM_START + MOTA_NRF52_HYBRID_RAM_SIZE ==
0x20040000u,
"hybrid arena must occupy the top 64 KiB of nRF52840 SRAM");
inline uint16_t mota_hybrid_rd16(const uint8_t* p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
inline uint32_t mota_hybrid_rd32(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
inline void mota_hybrid_wr16(uint8_t* p, uint16_t value) {
p[0] = (uint8_t)value;
p[1] = (uint8_t)(value >> 8);
}
inline void mota_hybrid_wr32(uint8_t* p, uint32_t value) {
p[0] = (uint8_t)value;
p[1] = (uint8_t)(value >> 8);
p[2] = (uint8_t)(value >> 16);
p[3] = (uint8_t)(value >> 24);
}
inline uint32_t mota_hybrid_crc32(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFFu;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t bit = 0; bit < 8u; ++bit)
crc = (crc >> 1) ^
(0xEDB88320u & (uint32_t)-(int32_t)(crc & 1u));
}
return ~crc;
}
inline bool mota_hybrid_auth_geometry_valid(uint32_t total,
uint32_t flash_start,
uint32_t flash_len,
uint32_t ram_len) {
if (total < MOTA_NRF52_CONTAINER_MIN_SIZE ||
flash_len < MOTA_NRF52_FLASH_PAGE ||
(flash_start & (MOTA_NRF52_FLASH_PAGE - 1u)) != 0u ||
(flash_len & (MOTA_NRF52_FLASH_PAGE - 1u)) != 0u ||
flash_start > UINT32_MAX - flash_len ||
(uint64_t)flash_len + ram_len != total || ram_len == 0u ||
ram_len > MOTA_NRF52_HYBRID_RAM_SIZE) {
return false;
}
uint32_t flash_needed = total > MOTA_NRF52_HYBRID_RAM_SIZE
? total - MOTA_NRF52_HYBRID_RAM_SIZE : 0u;
if (flash_needed < MOTA_NRF52_FLASH_PAGE)
flash_needed = MOTA_NRF52_FLASH_PAGE;
if (flash_needed > UINT32_MAX - (MOTA_NRF52_FLASH_PAGE - 1u))
return false;
const uint32_t expected_flash_len =
(flash_needed + MOTA_NRF52_FLASH_PAGE - 1u) &
~(MOTA_NRF52_FLASH_PAGE - 1u);
if (flash_len != expected_flash_len || flash_len >= total)
return false;
// Hybrid staging is qualified only for dedicated no-ExtraFS profiles. It
// must never reinterpret a legacy D4000 handoff as permission to use SRAM.
return flash_start + flash_len ==
MOTA_NRF52_STAGE_CEILING_EXPANDED;
}
inline bool mota_hybrid_auth_encode(
uint8_t out[MOTA_HYBRID_AUTH_LEN], uint32_t total,
uint32_t flash_start, uint32_t flash_len, uint32_t ram_len,
const uint8_t normalized_container_sha256[32]) {
if (!out || !normalized_container_sha256 ||
!mota_hybrid_auth_geometry_valid(
total, flash_start, flash_len, ram_len)) {
return false;
}
memset(out, 0, MOTA_HYBRID_AUTH_LEN);
memcpy(out, MOTA_HYBRID_AUTH_MAGIC, sizeof(MOTA_HYBRID_AUTH_MAGIC));
mota_hybrid_wr16(out + 8u, MOTA_HYBRID_AUTH_VERSION);
mota_hybrid_wr16(out + 10u, MOTA_HYBRID_AUTH_LEN);
out[12] = MOTA_HYBRID_AUTH_PURPOSE_APP;
out[13] = MOTA_HYBRID_AUTH_FORMAT_APP;
mota_hybrid_wr32(out + 16u, total);
mota_hybrid_wr32(out + 20u, flash_start);
mota_hybrid_wr32(out + 24u, flash_len);
mota_hybrid_wr32(out + 28u, ram_len);
memcpy(out + 32u, normalized_container_sha256, 32u);
const uint32_t crc = mota_hybrid_crc32(out, 64u);
mota_hybrid_wr32(out + 64u, crc);
mota_hybrid_wr32(out + 68u, ~crc);
return true;
}
inline bool mota_hybrid_auth_valid(
const uint8_t record[MOTA_HYBRID_AUTH_LEN]) {
if (!record ||
memcmp(record, MOTA_HYBRID_AUTH_MAGIC,
sizeof(MOTA_HYBRID_AUTH_MAGIC)) != 0 ||
mota_hybrid_rd16(record + 8u) != MOTA_HYBRID_AUTH_VERSION ||
mota_hybrid_rd16(record + 10u) != MOTA_HYBRID_AUTH_LEN ||
record[12] != MOTA_HYBRID_AUTH_PURPOSE_APP ||
record[13] != MOTA_HYBRID_AUTH_FORMAT_APP ||
mota_hybrid_rd16(record + 14u) != 0u) {
return false;
}
const uint32_t crc = mota_hybrid_rd32(record + 64u);
return mota_hybrid_auth_geometry_valid(
mota_hybrid_rd32(record + 16u),
mota_hybrid_rd32(record + 20u),
mota_hybrid_rd32(record + 24u),
mota_hybrid_rd32(record + 28u)) &&
mota_hybrid_crc32(record, 64u) == crc &&
mota_hybrid_rd32(record + 68u) == ~crc;
}
} // namespace ota
} // namespace mesh
+256 -17
View File
@@ -7,22 +7,79 @@
#include "MotaContainer.h"
#include "OtaDebug.h"
#include "OtaByteIO.h" // align_down / rd_u32le (flash-page geometry + header read)
#include "OtaHybridHandoff.h"
#include <string.h>
#include "nrf.h"
#include "flash/flash_nrf5x.h" // Adafruit core internal-flash driver (SoftDevice-safe; LittleFS path)
namespace mesh {
namespace ota {
namespace {
static void clear_hybrid_auth_record() {
#if defined(OTA_HYBRID_RAM_STORE)
volatile uint32_t* const retained =
reinterpret_cast<volatile uint32_t*>(MOTA_HYBRID_AUTH_ADDR);
for (uint32_t i = 0; i < MOTA_HYBRID_AUTH_LEN / 4u; ++i)
retained[i] = 0u;
__DMB();
__DSB();
#endif
}
static bool publish_hybrid_auth_record(
const uint8_t record[MOTA_HYBRID_AUTH_LEN]) {
#if defined(OTA_HYBRID_RAM_STORE)
if (!mota_hybrid_auth_valid(record)) return false;
volatile uint32_t* const retained =
reinterpret_cast<volatile uint32_t*>(MOTA_HYBRID_AUTH_ADDR);
// Invalidate first, publish the body, then make the first magic word live.
// Any reset during this sequence leaves a record OTAFIX rejects.
retained[0] = 0u;
__DMB();
for (uint32_t i = 1u; i < MOTA_HYBRID_AUTH_LEN / 4u; ++i)
retained[i] = mota_hybrid_rd32(record + i * 4u);
__DMB();
retained[0] = mota_hybrid_rd32(record);
__DMB();
__DSB();
for (uint32_t i = 0; i < MOTA_HYBRID_AUTH_LEN / 4u; ++i)
if (retained[i] != mota_hybrid_rd32(record + i * 4u)) return false;
return true;
#else
(void)record;
return false;
#endif
}
} // namespace
OtaStoreFlashNrf52::OtaStoreFlashNrf52() {
// A descriptor is valid for one immediate software-reset handoff only.
// OTAFIX consumes it before an application starts; clear any stale bytes if
// a reset reached this constructor without entering the hybrid path.
clear_hybrid_auth_record();
}
void OtaStoreFlashNrf52::reset_session() {
_write_start = 0;
_stage_ceiling = MOTA_NRF52_STAGE_CEILING_LEGACY;
_flash_len = 0;
_ram_len = 0;
_total = 0;
_hybrid = false;
_pay_idx = 0;
_flushed = false;
_io_ok = true;
_planned_bootloader = false;
_planned_hybrid = false;
_planned_total = 0;
_planned_start = 0;
_planned_flash_len = 0;
_planned_ram_len = 0;
memset(_hybrid_handoff, 0, sizeof(_hybrid_handoff));
_hybrid_handoff_ready = false;
}
// A valid EndF gives the exact live-image extent. Legacy app-only builds keep
@@ -46,11 +103,54 @@ bool OtaStoreFlashNrf52::plan_layout(bool is_full, uint32_t image_size,
uint32_t payload_off, uint32_t payload_size,
bool is_bootloader) {
_planned_bootloader = false;
_planned_hybrid = false;
_planned_total = 0;
_planned_start = 0;
if (!is_bootloader) return true;
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
_planned_flash_len = 0;
_planned_ram_len = 0;
const uint64_t total64 = (uint64_t)payload_off + payload_size + 5u;
if (total64 > UINT32_MAX) return false;
if (!is_bootloader) {
#if defined(OTA_HYBRID_RAM_STORE)
const uint32_t total = (uint32_t)total64;
const uint32_t app_base = mota_nrf52_app_base();
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
uint32_t app_end = 0;
uint32_t start = 0;
if (!protected_app_end(app_base, stage_ceiling, app_end)) return false;
uint32_t flash_len = 0, ram_len = 0;
const OtaRamCaps ram_caps = ota_bootloader_ram_caps();
// The layout record tells the offline packager to size detools' workspace
// against this exact split. Use it deterministically for every application
// delta larger than the mandatory flash page; choosing a smaller pure-
// flash start merely because the bytes fit could invalidate that geometry.
if (!is_full && ota_bootloader_supports_hybrid(ram_caps) &&
stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED &&
mota_nrf52_hybrid_stage_plan(
total, app_base, app_end, stage_ceiling,
start, flash_len, ram_len)) {
_planned_hybrid = true;
_planned_total = total;
_planned_start = start;
_planned_flash_len = flash_len;
_planned_ram_len = ram_len;
return true;
}
// A container no larger than one page cannot have a non-empty SRAM suffix
// under the frozen split formula. Preserve the established flash path for
// that edge case; all larger hybrid-profile application packages fail
// closed if the installed bootloader lacks MOTARAMA.
if (total <= MOTA_NRF52_FLASH_PAGE)
return mota_nrf52_stage_plan(
total, app_base, app_end, stage_ceiling, start);
return false;
#else
(void)is_full;
return true;
#endif
}
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
if (!is_full || image_size != 40u * 1024u || payload_size != 40u * 1024u ||
payload_off != 365u || total64 != MOTA_NRF52_BOOT_CONTAINER_SIZE ||
!ota_bootloader_self_update_caps_valid(ota_bootloader_update_caps()))
@@ -118,6 +218,7 @@ bool OtaStoreFlashNrf52::invalidate_staged_header(
}
bool OtaStoreFlashNrf52::discard() {
clear_hybrid_auth_record();
const uint32_t app_base = mota_nrf52_app_base();
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
uint32_t app_end = 0;
@@ -139,7 +240,8 @@ bool OtaStoreFlashNrf52::discard() {
}
bool OtaStoreFlashNrf52::flush_page(uint32_t page_idx, const uint8_t* buf) {
if (!_io_ok || _write_start > _stage_ceiling - PG ||
if (!_io_ok || _flash_len < PG || page_idx >= _flash_len / PG ||
_write_start > _stage_ceiling - PG ||
page_idx > (_stage_ceiling - PG - _write_start) / PG) {
_io_ok = false;
return false;
@@ -156,8 +258,18 @@ uint32_t OtaStoreFlashNrf52::capacity() const {
const uint32_t app_base = mota_nrf52_app_base();
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
uint32_t app_end;
return protected_app_end(app_base, stage_ceiling, app_end)
? mota_nrf52_stage_capacity(app_base, app_end, stage_ceiling) : 0;
if (!protected_app_end(app_base, stage_ceiling, app_end)) return 0;
const uint32_t flash_capacity =
mota_nrf52_stage_capacity(app_base, app_end, stage_ceiling);
#if defined(OTA_HYBRID_RAM_STORE)
const OtaRamCaps ram_caps = ota_bootloader_ram_caps();
if (stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED &&
flash_capacity >= PG && ota_bootloader_supports_hybrid(ram_caps) &&
flash_capacity <= UINT32_MAX - MOTA_NRF52_HYBRID_RAM_SIZE) {
return flash_capacity + MOTA_NRF52_HYBRID_RAM_SIZE;
}
#endif
return flash_capacity;
}
bool OtaStoreFlashNrf52::flush_pay() {
@@ -168,14 +280,20 @@ uint32_t OtaStoreFlashNrf52::run(uint32_t pos, uint32_t remain) const {
uint32_t trailer = _total - 5; // container is always >= 13 bytes (begin checks)
if (pos >= trailer) return remain; // tail: caller bounds remain to <= 5 already
uint32_t end = pos + remain;
uint32_t page_end = (pos / PG + 1) * PG;
if (end > page_end) end = page_end; // a run stays within one flash page,
if (!_hybrid || pos < _flash_len) {
uint32_t page_end = (pos / PG + 1) * PG;
if (end > page_end) end = page_end; // a run stays within one flash page,
if (_hybrid && end > _flash_len) end = _flash_len; // and one backing region,
}
if (end > trailer) end = trailer; // and never crosses into the trailer tail
return end - pos;
}
const uint8_t* OtaStoreFlashNrf52::read_slot(uint32_t pos) const {
if (pos >= _total - 5) return _trailer + (pos - (_total - 5)); // trailer tail (RAM until finalize)
if (_hybrid && pos >= _flash_len)
return (const uint8_t*)(uintptr_t)(MOTA_NRF52_HYBRID_RAM_START +
pos - _flash_len);
uint32_t page = pos / PG;
if (page == 0) return _meta_page + pos; // pinned page 0 (incl. leaves)
if (page == _pay_idx) return _pay_page + (pos - page * PG); // current sliding payload page
@@ -184,6 +302,9 @@ const uint8_t* OtaStoreFlashNrf52::read_slot(uint32_t pos) const {
uint8_t* OtaStoreFlashNrf52::write_slot(uint32_t pos) {
if (pos >= _total - 5) return _trailer + (pos - (_total - 5));
if (_hybrid && pos >= _flash_len)
return (uint8_t*)(uintptr_t)(MOTA_NRF52_HYBRID_RAM_START +
pos - _flash_len);
uint32_t page = pos / PG;
if (page == 0) return _meta_page + pos;
if (page > _pay_idx) {
@@ -196,8 +317,12 @@ uint8_t* OtaStoreFlashNrf52::write_slot(uint32_t pos) {
bool OtaStoreFlashNrf52::begin(uint32_t total_size) {
const bool planned_bootloader = _planned_bootloader;
const bool planned_hybrid = _planned_hybrid;
const uint32_t planned_total = _planned_total;
const uint32_t planned_start = _planned_start;
const uint32_t planned_flash_len = _planned_flash_len;
const uint32_t planned_ram_len = _planned_ram_len;
clear_hybrid_auth_record();
reset_session();
// never collide with the running application image (its extent comes from its EndF trailer)
@@ -224,23 +349,57 @@ bool OtaStoreFlashNrf52::begin(uint32_t total_size) {
// Bottom-align below the selected ceiling and reject unless it sits above the running image. The
// approval path later verifies the patch's detools workspace ends at/below this exact start.
uint32_t start;
if (!mota_nrf52_stage_plan(total_size, app_base, app_end, stage_ceiling, start)) return false;
if (planned_bootloader && start != planned_start) return false;
uint32_t start = 0;
uint32_t flash_len = 0;
uint32_t ram_len = 0;
if (planned_hybrid) {
#if defined(OTA_HYBRID_RAM_STORE)
if (planned_bootloader || total_size != planned_total ||
stage_ceiling != MOTA_NRF52_STAGE_CEILING_EXPANDED ||
!ota_bootloader_supports_hybrid(ota_bootloader_ram_caps()) ||
!mota_nrf52_hybrid_stage_plan(
total_size, app_base, app_end, stage_ceiling,
start, flash_len, ram_len) ||
start != planned_start || flash_len != planned_flash_len ||
ram_len != planned_ram_len) {
return false;
}
#else
return false;
#endif
} else {
if (!mota_nrf52_stage_plan(
total_size, app_base, app_end, stage_ceiling, start)) {
return false;
}
if (planned_bootloader && start != planned_start) return false;
flash_len = stage_ceiling - start;
}
_write_start = start;
_stage_ceiling = stage_ceiling;
_flash_len = flash_len;
_ram_len = ram_len;
_total = total_size;
_hybrid = planned_hybrid;
memset(_meta_page, 0xFF, PG); // assemble page 0 in RAM; 0xFF = erased sentinel (unfilled leaf slots)
memset(_trailer, 0xFF, sizeof(_trailer));
#if defined(OTA_HYBRID_RAM_STORE)
if (_hybrid)
memset((void*)(uintptr_t)MOTA_NRF52_HYBRID_RAM_START, 0xFF, _ram_len);
#endif
_pay_idx = 0;
_flushed = false;
_io_ok = true;
_planned_bootloader = planned_bootloader;
_planned_hybrid = planned_hybrid;
_planned_total = planned_total;
_planned_start = planned_start;
OTA_DBG("OTA flash: begin total=%u start=%08x app_end=%08x ceiling=%08x\n",
(unsigned)total_size, (unsigned)start, (unsigned)app_end, (unsigned)stage_ceiling);
_planned_flash_len = planned_flash_len;
_planned_ram_len = planned_ram_len;
OTA_DBG("OTA flash: begin total=%u start=%08x flash=%u ram=%u app_end=%08x ceiling=%08x\n",
(unsigned)total_size, (unsigned)start, (unsigned)flash_len,
(unsigned)ram_len, (unsigned)app_end, (unsigned)stage_ceiling);
return true; // no pre-erase: each page is erased by its own (single) flush
}
@@ -282,17 +441,90 @@ bool OtaStoreFlashNrf52::finalize() {
OTA_DBG("OTA flash: finalize total=%u\n", (unsigned)_total);
if (!flush_pay()) return false; // the last (highest) payload page, if one is open
if (!flush_page(0, _meta_page)) return false; // header + manifest + leaves + first payload bytes
uint32_t trailer_addr = _write_start + _total - 5;
if (flash_nrf5x_write(trailer_addr, _trailer, 5) < 0) { _io_ok = false; return false; }
flash_nrf5x_flush();
if (!flash_matches(trailer_addr, _trailer, 5)) { _io_ok = false; return false; }
const uint32_t trailer_off = _total - sizeof(_trailer);
uint32_t flash_bytes = sizeof(_trailer);
if (_hybrid) {
flash_bytes = trailer_off < _flash_len ? _flash_len - trailer_off : 0u;
if (flash_bytes > sizeof(_trailer)) flash_bytes = sizeof(_trailer);
}
if (flash_bytes != 0u) {
const uint32_t trailer_addr = _write_start + trailer_off;
if (flash_nrf5x_write(trailer_addr, _trailer, flash_bytes) < 0) {
_io_ok = false;
return false;
}
flash_nrf5x_flush();
if (!flash_matches(trailer_addr, _trailer, flash_bytes)) {
_io_ok = false;
return false;
}
}
if (_hybrid && flash_bytes < sizeof(_trailer)) {
const uint32_t logical = trailer_off + flash_bytes;
uint8_t* const dst = (uint8_t*)(uintptr_t)(
MOTA_NRF52_HYBRID_RAM_START + logical - _flash_len);
memcpy(dst, _trailer + flash_bytes, sizeof(_trailer) - flash_bytes);
__DMB();
__DSB();
if (memcmp(dst, _trailer + flash_bytes,
sizeof(_trailer) - flash_bytes) != 0) {
_io_ok = false;
return false;
}
}
_flushed = true;
return true;
}
bool OtaStoreFlashNrf52::approve_for_application(
const uint8_t normalized_container_hash[32]) {
clear_hybrid_auth_record();
_hybrid_handoff_ready = false;
if (!finalize() || _total < 8u + MOTA_MFL + 5u) return false;
const uint32_t offset = 8u + MOTA_OFF_APPROVAL;
if (offset + sizeof(APPROVAL_YES) > _flash_len) return false;
const uint32_t address = _write_start + offset;
if (flash_nrf5x_write(address, APPROVAL_YES, sizeof(APPROVAL_YES)) < 0) {
_io_ok = false;
return false;
}
flash_nrf5x_flush();
if (!flash_matches(address, APPROVAL_YES, sizeof(APPROVAL_YES))) {
_io_ok = false;
return false;
}
memcpy(_meta_page + offset, APPROVAL_YES, sizeof(APPROVAL_YES));
if (!_hybrid) return true;
#if defined(OTA_HYBRID_RAM_STORE)
if (!normalized_container_hash ||
!mota_hybrid_auth_encode(
_hybrid_handoff, _total, _write_start, _flash_len, _ram_len,
normalized_container_hash)) {
_io_ok = false;
return false;
}
_hybrid_handoff_ready = true;
return true;
#else
return false;
#endif
}
bool OtaStoreFlashNrf52::publish_hybrid_handoff() {
#if defined(OTA_HYBRID_RAM_STORE)
if (!_hybrid || !_hybrid_handoff_ready || !_flushed || !_io_ok ||
!mota_hybrid_auth_valid(_hybrid_handoff)) {
return false;
}
return publish_hybrid_auth_record(_hybrid_handoff);
#else
return false;
#endif
}
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
bool OtaStoreFlashNrf52::approve_for_bootloader() {
if (!finalize() || _total < 8u + MOTA_MFL + 5u) return false;
if (_hybrid || !finalize() || _total < 8u + MOTA_MFL + 5u) return false;
const uint32_t offset = 8u + MOTA_OFF_APPROVAL;
if (offset + sizeof(APPROVAL_YES) > _total) return false;
const uint32_t address = _write_start + offset;
@@ -326,6 +558,10 @@ void OtaStoreFlashNrf52::checkpoint() {
// the first match (highest address = most recent for the common single-container case). The manager then
// parses the loaded manifest and validates geometry/root, so a stale leftover is rejected there.
bool OtaStoreFlashNrf52::reopen() {
// A retained-SRAM suffix cannot survive a power cycle or app restart as a
// resumable store. Reset all in-memory split state and adopt only a complete
// flash span whose advertised total fits before the selected ceiling.
reset_session();
const uint32_t app_base = mota_nrf52_app_base();
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
uint32_t app_end;
@@ -358,7 +594,10 @@ bool OtaStoreFlashNrf52::reopen() {
}
_write_start = start;
_stage_ceiling = stage_ceiling;
_flash_len = stage_ceiling - start;
_ram_len = 0;
_total = total;
_hybrid = false;
memcpy(_meta_page, p, PG); // load page 0 (header+manifest+leaves) into RAM to continue
memcpy(_trailer, p + (total - 5), 5); // recover the trailer tail (flushed at last finalize, if any)
_pay_idx = 0;
+25 -1
View File
@@ -4,6 +4,7 @@
#include "OtaStore.h"
#include "OtaFlashLayout_nrf52.h"
#include "OtaHybridHandoff.h"
// Persistent flash-backed OtaStore for nRF52840. Stages the received `.mota` below the selected
// filesystem-safe ceiling, bottom-aligned so the bootloader can find it. Internal ExtraFS roles stop
@@ -37,12 +38,18 @@ class OtaStoreFlashNrf52 : public OtaStore {
uint32_t _write_start = 0; // flash address of container offset 0 (page-aligned)
uint32_t _stage_ceiling = MOTA_NRF52_STAGE_CEILING_LEGACY;
uint32_t _flash_len = 0; // logical prefix backed by internal flash
uint32_t _ram_len = 0; // logical suffix backed by the fixed SRAM arena
uint32_t _total = 0; // staged container size (0 = none)
bool _hybrid = false; // flash prefix + reset-retained SRAM suffix
bool _flushed = false; // finalize() committed everything to flash
bool _io_ok = true; // cleared on a failed/out-of-bounds flash write or readback mismatch
bool _planned_bootloader = false; // manifest-kind decision made before begin()/any erase
bool _planned_hybrid = false;
uint32_t _planned_total = 0;
uint32_t _planned_start = 0;
uint32_t _planned_flash_len = 0;
uint32_t _planned_ram_len = 0;
uint8_t _meta_page[PG]; // pinned flash page 0 (header + manifest + leaves + 1st payload)
uint8_t _pay_page[PG]; // sliding buffer for one payload page (index _pay_idx)
@@ -63,6 +70,7 @@ class OtaStoreFlashNrf52 : public OtaStore {
static bool invalidate_staged_header(void* context, uint32_t address);
public:
OtaStoreFlashNrf52();
bool plan_layout(bool is_full, uint32_t image_size, uint32_t payload_off,
uint32_t payload_size, bool is_bootloader) override;
bool begin(uint32_t total_size) override;
@@ -77,6 +85,12 @@ public:
void checkpoint() override; // persist page 0 (leaves) + the open payload page so a reboot can resume
bool reopen() override; // re-attach to a container already staged in flash (scan for it)
// Mark a verified application package approved. For a hybrid package this
// also prepares (but does not yet publish) the reset-retained authorization;
// publication happens immediately before the software-reset handoff.
bool approve_for_application(const uint8_t normalized_container_hash[32]);
bool publish_hybrid_handoff();
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
// Clear the manifest approval word only after the privileged signed-image gates pass. OTAFIX scans
// this page-aligned shared container after the ED handoff and repeats the structural, identity,
@@ -88,8 +102,18 @@ public:
// Contiguous view (flash is memory-mapped). VALID ONLY AFTER finalize() - before that, page 0 and the
// tail are still in RAM. OtaManager/OtaCli/verify use this only once the transfer is COMPLETE.
const uint8_t* data() const { return (_flushed && _io_ok) ? (const uint8_t*)(uintptr_t)_write_start : nullptr; }
const uint8_t* data() const {
return (_flushed && _io_ok && !_hybrid)
? (const uint8_t*)(uintptr_t)_write_start : nullptr;
}
uint32_t write_start() const { return _write_start; }
uint32_t flash_len() const { return _flash_len; }
uint32_t ram_len() const { return _ram_len; }
bool is_hybrid() const { return _hybrid; }
private:
uint8_t _hybrid_handoff[MOTA_HYBRID_AUTH_LEN] = {0};
bool _hybrid_handoff_ready = false;
};
} // namespace ota