Add MeshTower SD bootloader updates over LoRa
This commit is contained in:
1 parent
9e4d246ac6
commit
3d78d9c846
26 files changed
+763
-114
No files matched your search
+107
-38
@@ -485,6 +485,8 @@ void ota_reboot_to_bootloader_update() {
|
||||
uint8_t source = GPREGRET2_OTA_STAGE_QSPI;
|
||||
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
|
||||
source = GPREGRET2_OTA_STAGE_EXPANDED;
|
||||
#elif defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
source = GPREGRET2_OTA_STAGE_SD;
|
||||
#endif
|
||||
uint8_t sd_en = 0;
|
||||
sd_softdevice_is_enabled(&sd_en);
|
||||
@@ -749,22 +751,10 @@ static bool ota_apply_mota_nrf52_external(Store& store, const SignerAllowlist& a
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OTA_SD_STORE)
|
||||
bool ota_apply_mota_nrf52(OtaStoreSdNrf52& store, const SignerAllowlist& allow,
|
||||
ApplyState& st, char* msg) {
|
||||
return ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_SD, "SD", st, msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OTA_QSPI_STORE)
|
||||
bool ota_apply_mota_nrf52(OtaStoreQspiNrf52& store, const SignerAllowlist& allow,
|
||||
ApplyState& st, char* msg) {
|
||||
return ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_QSPI, "QSPI", st, msg);
|
||||
}
|
||||
|
||||
#if defined(OTA_QSPI_BOOTLOADER_UPDATE)
|
||||
static bool qspi_bootloader_crc_ok(OtaStoreQspiNrf52& store, uint32_t payload_off,
|
||||
const OtaBootloaderIdentity& identity) {
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE) || defined(OTA_QSPI_BOOTLOADER_UPDATE)
|
||||
template <typename Store>
|
||||
static bool external_bootloader_crc_ok(Store& store, uint32_t payload_off,
|
||||
const OtaBootloaderIdentity& identity) {
|
||||
uint8_t buf[512];
|
||||
uint32_t crc = UINT32_MAX;
|
||||
for (uint32_t off = 0; off < OTA_BOOT_IMAGE_SIZE; off += sizeof(buf)) {
|
||||
@@ -781,10 +771,10 @@ static bool qspi_bootloader_crc_ok(OtaStoreQspiNrf52& store, uint32_t payload_of
|
||||
return ~crc == identity.crc32;
|
||||
}
|
||||
|
||||
static bool qspi_bootloader_image_metadata(OtaStoreQspiNrf52& store, uint32_t payload_off,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
OtaBootloaderIdentity& candidate,
|
||||
OtaBootloaderCapsMarker& caps) {
|
||||
template <typename Store>
|
||||
static bool external_bootloader_image_metadata(Store& store, uint32_t payload_off,
|
||||
OtaBootloaderIdentity& candidate,
|
||||
OtaBootloaderCapsMarker& caps) {
|
||||
// Keep enough overlap to catch a 44-byte manifest or 16-byte capability marker split across a
|
||||
// window. Every aligned manifest candidate is considered; a magic string in a literal pool is not
|
||||
// a terminal failure.
|
||||
@@ -804,7 +794,8 @@ static bool qspi_bootloader_image_metadata(OtaStoreQspiNrf52& store, uint32_t pa
|
||||
OtaBootloaderCapsMarker parsed;
|
||||
if (ota_bootloader_caps_marker_parse(buf + local, parsed) &&
|
||||
parsed.apply_abi >= MOTA_BOOT_FORMAT_VER &&
|
||||
(parsed.codec_mask & (1u << CODEC_FULL)) != 0 &&
|
||||
(parsed.codec_mask & OTA_BL_REQUIRED_APP_CODEC_MASK) ==
|
||||
OTA_BL_REQUIRED_APP_CODEC_MASK &&
|
||||
(parsed.storage_flags & OTA_BL_STORAGE_BOOT_UPDATE) != 0) {
|
||||
if (++valid_caps != 1u) return false;
|
||||
if (parsed.storage_flags == ota_bootloader_update_storage_flags()) caps = parsed;
|
||||
@@ -812,10 +803,8 @@ static bool qspi_bootloader_image_metadata(OtaStoreQspiNrf52& store, uint32_t pa
|
||||
}
|
||||
if ((absolute & 3u) != 0 || local + OTA_BOOT_MANIFEST_SIZE > len) continue;
|
||||
OtaBootloaderIdentity parsed;
|
||||
if (!ota_bootloader_manifest_parse(buf + local, absolute, parsed) ||
|
||||
parsed.board_id != installed.board_id ||
|
||||
memcmp(parsed.device_name, installed.device_name, OTA_BOOT_DEVICE_NAME_SIZE) != 0) continue;
|
||||
if (qspi_bootloader_crc_ok(store, payload_off, parsed)) {
|
||||
if (!ota_bootloader_manifest_parse(buf + local, absolute, parsed)) continue;
|
||||
if (external_bootloader_crc_ok(store, payload_off, parsed)) {
|
||||
parsed.crc_ok = true;
|
||||
if (!ota_bootloader_identity_add_unique(parsed, candidate, valid_identities)) return false;
|
||||
}
|
||||
@@ -825,13 +814,30 @@ static bool qspi_bootloader_image_metadata(OtaStoreQspiNrf52& store, uint32_t pa
|
||||
candidate.present && candidate.crc_ok && caps.present;
|
||||
}
|
||||
|
||||
bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
const SignerAllowlist& allow,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
const uint8_t actual_mid[4],
|
||||
const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8],
|
||||
ApplyState& st, char* msg) {
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
static bool external_bootloader_approve(OtaStoreSdNrf52& store,
|
||||
const uint8_t image_hash[32]) {
|
||||
return store.approve_for_bootloader(image_hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OTA_QSPI_BOOTLOADER_UPDATE)
|
||||
static bool external_bootloader_approve(OtaStoreQspiNrf52& store,
|
||||
const uint8_t image_hash[32]) {
|
||||
(void)image_hash;
|
||||
return store.approve_for_bootloader();
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename Store>
|
||||
static bool ota_prepare_bootloader_update_external(Store& store,
|
||||
const SignerAllowlist& allow,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
const uint8_t actual_mid[4],
|
||||
const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8],
|
||||
const char* storage_name,
|
||||
ApplyState& st, char* msg) {
|
||||
static const size_t CAP = 96;
|
||||
st = ApplyState();
|
||||
const uint32_t total = store.staged_size();
|
||||
@@ -839,12 +845,23 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
if (total < 8u + MOTA_MFL + 5u || !store.read(0, hdr, sizeof(hdr)) ||
|
||||
memcmp(hdr, MOTA_MAGIC, sizeof(MOTA_MAGIC)) != 0 || rd_u32le(hdr + 4) != total ||
|
||||
!store.read(8, manifest, sizeof(manifest))) {
|
||||
strcpy(msg, "QSPI bootloader container parse failed"); return false;
|
||||
snprintf(msg, CAP, "%s bootloader container parse failed", storage_name); return false;
|
||||
}
|
||||
MotaManifest m;
|
||||
if (!mota_parse_manifest(manifest, sizeof(manifest), m)) {
|
||||
strcpy(msg, "not a valid v3 bootloader package"); return false;
|
||||
}
|
||||
// Authenticate this exact local manifest copy before any removable-media
|
||||
// reread. The streaming verifier below requires byte identity with it, and
|
||||
// the SD approval token copies its signed image_hash.
|
||||
mesh::Identity authenticated_signer(m.signer_pubkey);
|
||||
if (!authenticated_signer.verify(
|
||||
m.signature, m.manifest_start, (int)m.signed_len)) {
|
||||
strcpy(msg, "bootloader package signature invalid"); return false;
|
||||
}
|
||||
if (!allow.contains(m.signer_pubkey)) {
|
||||
strcpy(msg, "bootloader signer is not in the trusted allowlist"); return false;
|
||||
}
|
||||
switch (ota_bootloader_confirmation_gate(m, installed, actual_mid, operator_mid,
|
||||
operator_hash8)) {
|
||||
case OTA_BOOT_CONFIRM_OK: break;
|
||||
@@ -859,7 +876,8 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
|
||||
const OtaBlCaps current_caps = ota_bootloader_caps();
|
||||
if (!ota_bootloader_self_update_caps_valid(current_caps)) {
|
||||
strcpy(msg, "installed bootloader cannot safely self-update from QSPI"); return false;
|
||||
snprintf(msg, CAP, "installed bootloader cannot safely self-update from %s", storage_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t payload_off64 = 8u + MOTA_MFL + (uint64_t)m.block_count * 4u;
|
||||
@@ -868,7 +886,8 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
}
|
||||
const uint32_t payload_off = (uint32_t)payload_off64;
|
||||
|
||||
VerifyResult vr = ota_verify(static_cast<const OtaStore&>(store), allow);
|
||||
VerifyResult vr = ota_verify(
|
||||
static_cast<const OtaStore&>(store), allow, manifest);
|
||||
st.manifest_ok = vr.parsed;
|
||||
st.sig_ok = vr.sig_ok;
|
||||
st.trusted = vr.trusted;
|
||||
@@ -890,18 +909,68 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
}
|
||||
OtaBootloaderIdentity candidate;
|
||||
OtaBootloaderCapsMarker candidate_caps;
|
||||
if (!qspi_bootloader_image_metadata(store, payload_off, installed, candidate, candidate_caps) ||
|
||||
if (!external_bootloader_image_metadata(store, payload_off, candidate, candidate_caps) ||
|
||||
!ota_bootloader_identity_matches(installed, candidate)) {
|
||||
strcpy(msg, "candidate bootloader identity/capability/CRC mismatch"); return false;
|
||||
}
|
||||
st.slot_ok = true;
|
||||
if (!store.approve_for_bootloader()) {
|
||||
snprintf(msg, CAP, "QSPI bootloader handoff failed: %s", store.last_error()); return false;
|
||||
if (!external_bootloader_approve(store, m.image_hash)) {
|
||||
snprintf(msg, CAP, "%s bootloader handoff failed: %s", storage_name, store.last_error());
|
||||
return false;
|
||||
}
|
||||
strcpy(msg, "trusted bootloader verified and armed; rebooting after this reply");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(OTA_SD_STORE)
|
||||
bool ota_apply_mota_nrf52(OtaStoreSdNrf52& store, const SignerAllowlist& allow,
|
||||
ApplyState& st, char* msg) {
|
||||
return ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_SD, "SD", st, msg);
|
||||
}
|
||||
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
bool ota_prepare_bootloader_update_nrf52(OtaStoreSdNrf52& store,
|
||||
const SignerAllowlist& allow,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
const uint8_t actual_mid[4],
|
||||
const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8],
|
||||
ApplyState& st, char* msg) {
|
||||
SelfFwInfo fi;
|
||||
if (!ota_self_firmware(fi) ||
|
||||
!ota_bootloader_scratch_headroom_valid(
|
||||
fi.valid, mota_nrf52_app_base(), fi.image_len, OTA_BOOT_SCRATCH_START) ||
|
||||
!ota_bootloader_live_bank_preserves_scratch(
|
||||
mota_nrf52_app_base(), fi.image_len, OTA_BOOT_SCRATCH_START)) {
|
||||
st = ApplyState();
|
||||
strcpy(msg, "running firmware/settings do not preserve E0000 scratch");
|
||||
return false;
|
||||
}
|
||||
return ota_prepare_bootloader_update_external(
|
||||
store, allow, installed, actual_mid, operator_mid, operator_hash8, "SD", st, msg);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(OTA_QSPI_STORE)
|
||||
bool ota_apply_mota_nrf52(OtaStoreQspiNrf52& store, const SignerAllowlist& allow,
|
||||
ApplyState& st, char* msg) {
|
||||
return ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_QSPI, "QSPI", st, msg);
|
||||
}
|
||||
|
||||
#if defined(OTA_QSPI_BOOTLOADER_UPDATE)
|
||||
bool ota_prepare_bootloader_update_nrf52(OtaStoreQspiNrf52& store,
|
||||
const SignerAllowlist& allow,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
const uint8_t actual_mid[4],
|
||||
const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8],
|
||||
ApplyState& st, char* msg) {
|
||||
return ota_prepare_bootloader_update_external(
|
||||
store, allow, installed, actual_mid, operator_mid, operator_hash8, "QSPI", st, msg);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE) && defined(OTA_FLASH_STORE)
|
||||
|
||||
@@ -86,6 +86,15 @@ bool ota_rescue_mota_nrf52(const uint8_t* buf, uint32_t len,
|
||||
class OtaStoreSdNrf52;
|
||||
bool ota_apply_mota_nrf52(OtaStoreSdNrf52& store,
|
||||
const SignerAllowlist& allow, ApplyState& st, char* msg);
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
bool ota_prepare_bootloader_update_nrf52(OtaStoreSdNrf52& store,
|
||||
const SignerAllowlist& allow,
|
||||
const OtaBootloaderIdentity& installed,
|
||||
const uint8_t actual_mid[4],
|
||||
const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8],
|
||||
ApplyState& st, char* msg);
|
||||
#endif
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
|
||||
class OtaStoreQspiNrf52;
|
||||
|
||||
@@ -34,16 +34,29 @@ static const uint8_t OTA_BL_STORAGE_STAGE_CEILING = 0x02;
|
||||
static const uint8_t OTA_BL_STORAGE_QSPI = 0x04;
|
||||
static const uint8_t OTA_BL_STORAGE_BOOT_UPDATE = 0x08;
|
||||
static const uint8_t OTA_BL_STORAGE_KNOWN = 0x0F;
|
||||
static const uint8_t OTA_BL_PROFILE_SD_BOOT_UPDATE =
|
||||
OTA_BL_STORAGE_SD | OTA_BL_STORAGE_BOOT_UPDATE;
|
||||
static const uint8_t OTA_BL_PROFILE_INTERNAL_BOOT_UPDATE =
|
||||
OTA_BL_STORAGE_STAGE_CEILING | OTA_BL_STORAGE_BOOT_UPDATE;
|
||||
static const uint8_t OTA_BL_PROFILE_QSPI_BOOT_UPDATE =
|
||||
OTA_BL_STORAGE_STAGE_CEILING | OTA_BL_STORAGE_QSPI |
|
||||
OTA_BL_STORAGE_BOOT_UPDATE;
|
||||
// A successor must retain both application update paths used by qualified
|
||||
// external stores: bit 0 CODEC_FULL and bit 2 CODEC_DETOOLS_INPLACE.
|
||||
static const uint16_t OTA_BL_REQUIRED_APP_CODEC_MASK = 0x0005u;
|
||||
|
||||
inline uint8_t ota_bootloader_update_storage_flags() {
|
||||
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
|
||||
// No external-storage bit means the ordinary internal flash store. The
|
||||
// same store holds either an app delta or a boot package; BOOT_UPDATE marks
|
||||
// only the privileged package capability, not a second storage backend.
|
||||
return OTA_BL_STORAGE_STAGE_CEILING | OTA_BL_STORAGE_BOOT_UPDATE;
|
||||
return OTA_BL_PROFILE_INTERNAL_BOOT_UPDATE;
|
||||
#elif defined(OTA_QSPI_BOOTLOADER_UPDATE)
|
||||
return OTA_BL_STORAGE_STAGE_CEILING | OTA_BL_STORAGE_QSPI |
|
||||
OTA_BL_STORAGE_BOOT_UPDATE;
|
||||
return OTA_BL_PROFILE_QSPI_BOOT_UPDATE;
|
||||
#elif defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
// The complete container remains in the contiguous SD staging file. No
|
||||
// internal stage-ceiling capability is implied by this external store.
|
||||
return OTA_BL_PROFILE_SD_BOOT_UPDATE;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
@@ -52,7 +65,9 @@ inline uint8_t ota_bootloader_update_storage_flags() {
|
||||
inline bool ota_bootloader_self_update_caps_valid(const OtaBlCaps& c) {
|
||||
const uint8_t required = ota_bootloader_update_storage_flags();
|
||||
return required != 0 && c.present && c.apply_abi >= 3u &&
|
||||
(c.codec_mask & 1u) != 0 && c.storage_flags == required;
|
||||
(c.codec_mask & OTA_BL_REQUIRED_APP_CODEC_MASK) ==
|
||||
OTA_BL_REQUIRED_APP_CODEC_MASK &&
|
||||
c.storage_flags == required;
|
||||
}
|
||||
|
||||
// Prefer a continuity-capable marker over a numerically newer legacy-looking candidate. Bootloader
|
||||
@@ -88,7 +103,8 @@ inline OtaBlCaps ota_bl_caps_scan_aligned(const uint8_t* bytes, size_t len,
|
||||
if (abi == 0 || abi == 0xFFFFu || codecs == 0 || (storage & ~OTA_BL_STORAGE_KNOWN) != 0 ||
|
||||
p[13] != 0 || p[14] != 0 || p[15] != 0) continue;
|
||||
const bool exact_profile = exact_storage_flags == 0 || storage == exact_storage_flags;
|
||||
if (require_boot_update && exact_profile && abi >= 3u && (codecs & 1u) != 0 &&
|
||||
if (require_boot_update && exact_profile && abi >= 3u &&
|
||||
(codecs & OTA_BL_REQUIRED_APP_CODEC_MASK) == OTA_BL_REQUIRED_APP_CODEC_MASK &&
|
||||
(storage & OTA_BL_STORAGE_BOOT_UPDATE) != 0) {
|
||||
// A self-update build must have one unambiguous privileged marker. Do
|
||||
// not silently choose between two otherwise valid structures (including
|
||||
@@ -118,7 +134,8 @@ inline OtaBlCaps ota_bootloader_caps() {
|
||||
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_bl_caps_scan_aligned(lo, (size_t)(hi - lo),
|
||||
#if defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
|
||||
#if defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
true, ota_bootloader_update_storage_flags()
|
||||
#else
|
||||
false
|
||||
|
||||
@@ -31,6 +31,18 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
#if !defined(NRF52_PLATFORM) || !defined(OTA_SD_STORE)
|
||||
#error "OTA_SD_BOOTLOADER_UPDATE requires nRF52 SD staging"
|
||||
#endif
|
||||
#if !defined(HELTEC_TOWER_V2_SDCARD)
|
||||
#error "OTA_SD_BOOTLOADER_UPDATE is restricted to the qualified MeshTower V2 SD target"
|
||||
#endif
|
||||
#if defined(OTA_FLASH_STORE) || defined(OTA_QSPI_STORE) || defined(QSPIFLASH)
|
||||
#error "SD bootloader staging cannot share another OTA/filesystem store"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
namespace ota {
|
||||
|
||||
@@ -45,9 +57,65 @@ static const uint32_t OTA_BOOT_IMAGE_START = 0x000F4000UL;
|
||||
static const uint32_t OTA_BOOT_IMAGE_SIZE = 0x0000A000UL; // F4000..FE000, padded to exactly 40 KiB
|
||||
static const uint32_t OTA_BOOT_SCRATCH_START = 0x000E0000UL;
|
||||
static const uint32_t OTA_BOOT_SCRATCH_END = 0x000EA000UL;
|
||||
static const uint32_t OTA_NRF52840_BOOT_SETTINGS_ADDRESS = 0x000FF000UL;
|
||||
static const uint16_t OTA_BOOT_BANK_VALID_APP = 0x0001u;
|
||||
static const uint16_t OTA_BOOT_BANK_ERASED_FALLBACK = 0xFFFFu;
|
||||
static const uint32_t OTA_NRF52840_RAM_START = 0x20000000UL;
|
||||
static const uint32_t OTA_NRF52840_RAM_END = 0x20040000UL;
|
||||
|
||||
inline bool ota_bootloader_image_geometry_valid(uint32_t image_size,
|
||||
uint32_t payload_size) {
|
||||
return image_size == OTA_BOOT_IMAGE_SIZE && payload_size == OTA_BOOT_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// External SD keeps the normal ED000 application linker, but OTAFIX needs the
|
||||
// XIAO-sized E0000..EA000 internal scratch range temporarily while replacing
|
||||
// itself. A hash-valid live EndF must prove the complete running image ends
|
||||
// before that scratch range; a linker maximum alone is not sufficient.
|
||||
inline bool ota_bootloader_scratch_headroom_valid(bool self_valid,
|
||||
uint32_t app_base,
|
||||
uint32_t image_len,
|
||||
uint32_t scratch_start) {
|
||||
return self_valid && app_base < scratch_start && image_len != 0 &&
|
||||
image_len <= scratch_start - app_base;
|
||||
}
|
||||
|
||||
// The bootloader may validate BANK_VALID_APP over bank_0_size on every reset.
|
||||
// Erasing the SD authorization-token page must not change bytes covered by a
|
||||
// nonzero stored CRC. A CRC-bound bank must cover the complete hash-valid live
|
||||
// image but stop by scratch_start; a smaller recorded size would disagree with
|
||||
// OTAFIX even if it did not overlap scratch. Debugger/UF2 installs can have
|
||||
// erased settings, and a valid-app record with CRC zero explicitly disables
|
||||
// that CRC check.
|
||||
inline bool ota_bootloader_scratch_bank_geometry_valid(uint16_t bank_0,
|
||||
uint16_t bank_0_crc,
|
||||
uint32_t bank_0_size,
|
||||
uint32_t live_image_size,
|
||||
uint32_t safe_span) {
|
||||
if (bank_0 == OTA_BOOT_BANK_ERASED_FALLBACK) return true;
|
||||
if (bank_0 != OTA_BOOT_BANK_VALID_APP) return false;
|
||||
if (bank_0_crc == 0u) return true;
|
||||
return live_image_size != 0u && bank_0_size >= live_image_size &&
|
||||
bank_0_size <= safe_span;
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
inline bool ota_bootloader_live_bank_preserves_scratch(uint32_t app_base,
|
||||
uint32_t live_image_size,
|
||||
uint32_t scratch_start) {
|
||||
if (app_base >= scratch_start) return false;
|
||||
const volatile uint8_t* raw =
|
||||
(const volatile uint8_t*)(uintptr_t)OTA_NRF52840_BOOT_SETTINGS_ADDRESS;
|
||||
const uint16_t bank_0 = (uint16_t)(raw[0] | ((uint16_t)raw[1] << 8));
|
||||
const uint16_t bank_0_crc = (uint16_t)(raw[2] | ((uint16_t)raw[3] << 8));
|
||||
const uint32_t bank_0_size = (uint32_t)raw[8] | ((uint32_t)raw[9] << 8) |
|
||||
((uint32_t)raw[10] << 16) | ((uint32_t)raw[11] << 24);
|
||||
return ota_bootloader_scratch_bank_geometry_valid(
|
||||
bank_0, bank_0_crc, bank_0_size, live_image_size,
|
||||
scratch_start - app_base);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only the two official XIAO nRF52840 OTAFIX identities are eligible. Runtime matching against the
|
||||
// installed, CRC-valid manifest distinguishes the base and Sense modules even for carrier-board aliases.
|
||||
static const uint32_t OTA_XIAO_BOARD_ID_BASE = 0x28860044UL;
|
||||
@@ -284,7 +352,8 @@ inline bool ota_bootloader_caps_from_image(const uint8_t* image, size_t image_si
|
||||
OtaBootloaderCapsMarker parsed;
|
||||
if (!ota_bootloader_caps_marker_parse(image + off, parsed) ||
|
||||
parsed.apply_abi < MOTA_BOOT_FORMAT_VER ||
|
||||
(parsed.codec_mask & (1u << CODEC_FULL)) == 0 ||
|
||||
(parsed.codec_mask & OTA_BL_REQUIRED_APP_CODEC_MASK) !=
|
||||
OTA_BL_REQUIRED_APP_CODEC_MASK ||
|
||||
(parsed.storage_flags & OTA_BL_STORAGE_BOOT_UPDATE) == 0) continue;
|
||||
if (++valid_count != 1u) return false; // privileged marker identity must be unambiguous
|
||||
if (parsed.storage_flags != exact_storage_flags) continue;
|
||||
|
||||
@@ -189,6 +189,16 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
"OTA: status | stats | ls | get <id> flash | install | cancel | announce | self | qspi | "
|
||||
"folder | config | key");
|
||||
#endif
|
||||
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
strcpy(reply,
|
||||
"OTA: status | stats | ls | get <id> flash | install | bootloader | cancel | announce | self | "
|
||||
"folder | cache | config | key");
|
||||
#else
|
||||
strcpy(reply,
|
||||
"OTA: status | stats | ls | get <id> flash | install | cancel | announce | self | folder | "
|
||||
"cache | config | key");
|
||||
#endif
|
||||
#else
|
||||
snprintf(reply, 160,
|
||||
"OTA: status | stats | ls | get <id> flash | install | cancel | announce | self | folder | "
|
||||
@@ -328,6 +338,16 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
!defined(OTA_QSPI_STORE)
|
||||
SelfFwInfo list_self;
|
||||
bool list_has_endf = ota_self_firmware(list_self) && list_self.valid;
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
SelfFwInfo list_sd_self;
|
||||
const bool list_sd_headroom = ota_self_firmware(list_sd_self) &&
|
||||
ota_bootloader_scratch_headroom_valid(
|
||||
list_sd_self.valid, mota_nrf52_app_base(), list_sd_self.image_len,
|
||||
OTA_BOOT_SCRATCH_START) &&
|
||||
ota_bootloader_live_bank_preserves_scratch(
|
||||
mota_nrf52_app_base(), list_sd_self.image_len,
|
||||
OTA_BOOT_SCRATCH_START);
|
||||
#endif
|
||||
uint32_t now = millis(); int shown = 0;
|
||||
uint16_t first = (uint16_t)(page - 1) * PAGE_SIZE;
|
||||
@@ -355,12 +375,16 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
if (boot_package) {
|
||||
bool installable = false;
|
||||
#if defined(NRF52_PLATFORM) && \
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE))
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE))
|
||||
const OtaBootloaderIdentity& bid = c.bootloaderIdentity();
|
||||
installable = h->flags == (MFLAG_FULL | MFLAG_SIGNED | MFLAG_BOOTLOADER) &&
|
||||
h->codec == CODEC_FULL && bid.present && bid.crc_ok &&
|
||||
h->target_id == ota_bootloader_target_id(bid) &&
|
||||
ota_bootloader_self_update_caps_valid(list_bl);
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
installable = installable && list_sd_headroom;
|
||||
#endif
|
||||
#endif
|
||||
fit = installable ? "yours" : "bootloader unsupported";
|
||||
} else if (myt && h->target_id == myt) {
|
||||
@@ -489,7 +513,8 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
#else
|
||||
if (selboot) {
|
||||
#if defined(NRF52_PLATFORM) && \
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE))
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE))
|
||||
const OtaBootloaderIdentity& bid = c.bootloaderIdentity();
|
||||
const OtaBlCaps& bl = c.bootloaderCaps();
|
||||
if (selflags != (MFLAG_FULL | MFLAG_SIGNED | MFLAG_BOOTLOADER) ||
|
||||
@@ -505,6 +530,19 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
strcpy(reply, "ERR installed bootloader lacks safe LoRa bootloader-update support");
|
||||
return true;
|
||||
}
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
SelfFwInfo sd_self;
|
||||
if (!ota_self_firmware(sd_self) ||
|
||||
!ota_bootloader_scratch_headroom_valid(
|
||||
sd_self.valid, mota_nrf52_app_base(), sd_self.image_len,
|
||||
OTA_BOOT_SCRATCH_START) ||
|
||||
!ota_bootloader_live_bank_preserves_scratch(
|
||||
mota_nrf52_app_base(), sd_self.image_len,
|
||||
OTA_BOOT_SCRATCH_START)) {
|
||||
strcpy(reply, "ERR running firmware/settings do not preserve E0000 scratch; use local DFU/SWD");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
strcpy(reply, "ERR this build cannot install bootloader packages; use folder capture or USB DFU");
|
||||
return true;
|
||||
@@ -716,7 +754,8 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
|
||||
} else if (is_cmd(a, "bootloader|blupdate", &rest)) {
|
||||
#if defined(NRF52_PLATFORM) && !defined(OTA_SEEDER_ONLY) && \
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE))
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE))
|
||||
const OtaBootloaderIdentity& bid = c.bootloaderIdentity();
|
||||
const OtaBlCaps& bl = c.bootloaderCaps();
|
||||
if (*rest == 0 || strcmp(rest, "status") == 0) {
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
// Per-device OTA singleton shared by the CLI (OtaCli) and the mesh adapter (the example's MyMesh).
|
||||
// Holds the session engine, a persistent staging store (fetch), a RAM serve buffer, and the signer
|
||||
// allowlist. nRF52 uses one of three stores selected by the exact target: internal flash for in-place
|
||||
// deltas, raw microSD, or dedicated raw QSPI for full images and in-place deltas. Flash-backed stores
|
||||
// deltas, raw microSD, or dedicated raw QSPI for full images and in-place deltas. One exact microSD
|
||||
// target also accepts explicitly confirmed signed bootloader packages. Flash-backed stores
|
||||
// coalesce writes at their erase-page boundary and checkpoint payload before leaf metadata, so an
|
||||
// interrupted fetch can be verified and resumed without trusting stale progress markers.
|
||||
|
||||
@@ -238,7 +239,8 @@ struct OtaContext {
|
||||
bool apply_fetched_bootloader(const uint8_t operator_mid[4],
|
||||
const uint8_t operator_hash8[8], char* msg) {
|
||||
#if defined(NRF52_PLATFORM) && !defined(OTA_SEEDER_ONLY) && \
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE))
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE))
|
||||
if (fetch_to_folder) {
|
||||
strncpy(msg, "refused: bootloader package is not in local install storage", 96);
|
||||
msg[95] = 0; return false;
|
||||
@@ -518,7 +520,8 @@ struct OtaContext {
|
||||
manager.set_apply_codec2(CODEC_DETOOLS_INPLACE); // also accepted -> a single in-place .mota fits both
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) && \
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE))
|
||||
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
|
||||
defined(OTA_SD_BOOTLOADER_UPDATE))
|
||||
manager.set_accept_bootloader(true);
|
||||
#else
|
||||
manager.set_accept_bootloader(false);
|
||||
|
||||
@@ -52,6 +52,7 @@ static const uint8_t GPREGRET_OTA_BOOTLOADER_UPDATE = 0x6Bu;
|
||||
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;
|
||||
|
||||
inline uint8_t mota_nrf52_flash_stage_handoff(uint32_t effective_stage_ceiling) {
|
||||
if (effective_stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Internal authorization token for a bootloader package staged on removable SD.
|
||||
// Keep byte-identical with OTAFIX src/ota_sd_boot_token.h.
|
||||
#ifndef OTA_SD_BOOT_TOKEN_H_
|
||||
#define OTA_SD_BOOT_TOKEN_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MOTA_SD_BOOT_TOKEN_VERSION 1u
|
||||
#define MOTA_SD_BOOT_TOKEN_LEN 64u
|
||||
#define MOTA_SD_BOOT_TOKEN_IMAGE_HASH_OFFSET 24u
|
||||
#define MOTA_SD_BOOT_TOKEN_CRC_OFFSET 56u
|
||||
|
||||
static const uint8_t MOTA_SD_BOOT_TOKEN_MAGIC[8] = {
|
||||
'M', 'O', 'T', 'A', 'S', 'D', 'B', 'L'
|
||||
};
|
||||
|
||||
static inline uint32_t mota_sd_boot_token_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);
|
||||
}
|
||||
|
||||
static inline void mota_sd_boot_token_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);
|
||||
}
|
||||
|
||||
static inline uint32_t mota_sd_boot_token_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 < 8; bit++) {
|
||||
crc = (crc >> 1) ^ (0xEDB88320u & (uint32_t)-(int32_t)(crc & 1u));
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
// image_hash is copied from the exact signed manifest buffer authenticated by
|
||||
// the application. It binds the only bootloader bytes that the MBR can install.
|
||||
static inline void mota_sd_boot_token_encode(uint8_t out[MOTA_SD_BOOT_TOKEN_LEN],
|
||||
uint32_t total,
|
||||
const uint8_t image_hash[32]) {
|
||||
memset(out, 0, MOTA_SD_BOOT_TOKEN_LEN);
|
||||
memcpy(out, MOTA_SD_BOOT_TOKEN_MAGIC, sizeof(MOTA_SD_BOOT_TOKEN_MAGIC));
|
||||
mota_sd_boot_token_wr32(out + 8u, MOTA_SD_BOOT_TOKEN_VERSION);
|
||||
mota_sd_boot_token_wr32(out + 12u, MOTA_SD_BOOT_TOKEN_LEN);
|
||||
mota_sd_boot_token_wr32(out + 16u, total);
|
||||
mota_sd_boot_token_wr32(out + 20u, ~total);
|
||||
memcpy(out + MOTA_SD_BOOT_TOKEN_IMAGE_HASH_OFFSET, image_hash, 32u);
|
||||
const uint32_t crc = mota_sd_boot_token_crc32(out, MOTA_SD_BOOT_TOKEN_CRC_OFFSET);
|
||||
mota_sd_boot_token_wr32(out + MOTA_SD_BOOT_TOKEN_CRC_OFFSET, crc);
|
||||
mota_sd_boot_token_wr32(out + MOTA_SD_BOOT_TOKEN_CRC_OFFSET + 4u, ~crc);
|
||||
}
|
||||
|
||||
static inline int mota_sd_boot_token_valid(const uint8_t token[MOTA_SD_BOOT_TOKEN_LEN],
|
||||
uint32_t total,
|
||||
const uint8_t image_hash[32]) {
|
||||
const uint32_t crc = mota_sd_boot_token_rd32(token + MOTA_SD_BOOT_TOKEN_CRC_OFFSET);
|
||||
return memcmp(token, MOTA_SD_BOOT_TOKEN_MAGIC, sizeof(MOTA_SD_BOOT_TOKEN_MAGIC)) == 0 &&
|
||||
mota_sd_boot_token_rd32(token + 8u) == MOTA_SD_BOOT_TOKEN_VERSION &&
|
||||
mota_sd_boot_token_rd32(token + 12u) == MOTA_SD_BOOT_TOKEN_LEN &&
|
||||
mota_sd_boot_token_rd32(token + 16u) == total &&
|
||||
mota_sd_boot_token_rd32(token + 20u) == ~total &&
|
||||
memcmp(token + MOTA_SD_BOOT_TOKEN_IMAGE_HASH_OFFSET, image_hash, 32u) == 0 &&
|
||||
mota_sd_boot_token_crc32(token, MOTA_SD_BOOT_TOKEN_CRC_OFFSET) == crc &&
|
||||
mota_sd_boot_token_rd32(token + MOTA_SD_BOOT_TOKEN_CRC_OFFSET + 4u) == ~crc;
|
||||
}
|
||||
|
||||
#endif // OTA_SD_BOOT_TOKEN_H_
|
||||
@@ -9,9 +9,13 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "MeshCore.h"
|
||||
#include "OtaBootloaderUpdate.h"
|
||||
#include "OtaByteIO.h"
|
||||
#include "OtaFlashLayout_nrf52.h"
|
||||
#include "OtaSdBootToken.h"
|
||||
#include "OtaSdHandoff.h"
|
||||
#include "OtaSelf.h"
|
||||
#include "flash/flash_nrf5x.h"
|
||||
|
||||
#ifndef OTA_SD_CS_PIN
|
||||
#define OTA_SD_CS_PIN PIN_SPI1_NSS
|
||||
@@ -44,6 +48,7 @@ void OtaStoreSdNrf52::resetStoreState() {
|
||||
_allocated_sectors = 0;
|
||||
_partition_start = 0;
|
||||
_partition_end = 0;
|
||||
_planned_bootloader = false;
|
||||
if (_file && *_file) _file->close();
|
||||
if (_sd) _sd->end();
|
||||
_mounted = false;
|
||||
@@ -370,7 +375,19 @@ bool OtaStoreSdNrf52::locate_file() {
|
||||
bool OtaStoreSdNrf52::plan_layout(bool, uint32_t image_size,
|
||||
uint32_t, uint32_t payload_size,
|
||||
bool is_bootloader) {
|
||||
if (is_bootloader) return false;
|
||||
_planned_bootloader = false;
|
||||
if (is_bootloader) {
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
if (ota_bootloader_image_geometry_valid(image_size, payload_size)) {
|
||||
_planned_bootloader = true;
|
||||
return true;
|
||||
}
|
||||
fail("bootloader package geometry mismatch");
|
||||
#else
|
||||
fail("bootloader update is not enabled for this SD target");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
if (image_size == 0 || payload_size == 0 || app_base >= MOTA_NRF52_APP_END ||
|
||||
image_size > MOTA_NRF52_APP_END - app_base) {
|
||||
@@ -464,6 +481,7 @@ void OtaStoreSdNrf52::checkpoint() {
|
||||
|
||||
bool OtaStoreSdNrf52::reopen() {
|
||||
_total = 0;
|
||||
_planned_bootloader = false;
|
||||
if (!mount()) return false;
|
||||
if (*_file) _file->close();
|
||||
if (!_file->open(PATH, O_RDWR)) return false;
|
||||
@@ -480,6 +498,32 @@ bool OtaStoreSdNrf52::reopen() {
|
||||
return false;
|
||||
}
|
||||
_total = total;
|
||||
uint8_t manifest[MOTA_MFL];
|
||||
MotaManifest parsed;
|
||||
if (total < 8u + sizeof(manifest) + 5u || !_file->seekSet(8u) ||
|
||||
_file->read(manifest, sizeof(manifest)) != (int)sizeof(manifest) ||
|
||||
!mota_parse_manifest(manifest, sizeof(manifest), parsed)) {
|
||||
_total = 0;
|
||||
_file->close();
|
||||
return false;
|
||||
}
|
||||
if (parsed.is_bootloader()) {
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
const uint64_t payload_off = 8u + sizeof(manifest) +
|
||||
(uint64_t)parsed.block_count * 4u;
|
||||
if (!ota_bootloader_image_geometry_valid(parsed.image_size, parsed.payload_size) ||
|
||||
payload_off + parsed.payload_size + 5u != total) {
|
||||
_total = 0;
|
||||
_file->close();
|
||||
return false;
|
||||
}
|
||||
_planned_bootloader = true;
|
||||
#else
|
||||
_total = 0;
|
||||
_file->close();
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
if (!locate_file()) {
|
||||
_total = 0;
|
||||
_file->close();
|
||||
@@ -490,6 +534,7 @@ bool OtaStoreSdNrf52::reopen() {
|
||||
|
||||
void OtaStoreSdNrf52::clear() {
|
||||
_total = 0;
|
||||
_planned_bootloader = false;
|
||||
_first_sector = 0;
|
||||
_allocated_sectors = 0;
|
||||
if (!mount()) return;
|
||||
@@ -498,14 +543,54 @@ void OtaStoreSdNrf52::clear() {
|
||||
_sd->remove(PATH);
|
||||
}
|
||||
|
||||
bool OtaStoreSdNrf52::approve_for_bootloader() {
|
||||
bool OtaStoreSdNrf52::approve_for_bootloader(
|
||||
const uint8_t expected_boot_image_hash[32]) {
|
||||
if (!_total || !finalize()) return false;
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
if (_planned_bootloader) {
|
||||
SelfFwInfo fi;
|
||||
if (!expected_boot_image_hash ||
|
||||
_total != MOTA_NRF52_BOOT_CONTAINER_SIZE ||
|
||||
!ota_self_firmware(fi) ||
|
||||
!ota_bootloader_scratch_headroom_valid(
|
||||
fi.valid, mota_nrf52_app_base(), fi.image_len,
|
||||
OTA_BOOT_SCRATCH_START) ||
|
||||
!ota_bootloader_live_bank_preserves_scratch(
|
||||
mota_nrf52_app_base(), fi.image_len, OTA_BOOT_SCRATCH_START)) {
|
||||
fail("running firmware/settings do not preserve boot scratch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!write(8 + MOTA_OFF_APPROVAL, APPROVAL_YES, sizeof(APPROVAL_YES)) ||
|
||||
!_file->sync() || !_sd->card()->syncDevice()) {
|
||||
fail("SD approval write failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(OTA_SD_BOOTLOADER_UPDATE)
|
||||
if (_planned_bootloader) {
|
||||
uint8_t token[MOTA_SD_BOOT_TOKEN_LEN];
|
||||
mota_sd_boot_token_encode(token, _total, expected_boot_image_hash);
|
||||
|
||||
// This page is outside the hash-valid live app and below InternalFS. The
|
||||
// token is temporary: OTAFIX validates it against the current SD bytes,
|
||||
// then consumes the page as the first bootloader scratch page.
|
||||
flash_nrf5x_flush();
|
||||
if (!flash_nrf5x_erase(OTA_BOOT_SCRATCH_START) ||
|
||||
flash_nrf5x_write(OTA_BOOT_SCRATCH_START, token, sizeof(token)) < 0) {
|
||||
fail("SD bootloader token flash write failed");
|
||||
return false;
|
||||
}
|
||||
flash_nrf5x_flush();
|
||||
if (memcmp((const void*)(uintptr_t)OTA_BOOT_SCRATCH_START,
|
||||
token, sizeof(token)) != 0) {
|
||||
fail("SD bootloader token flash verify failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t sector[MOTA_SD_SECTOR_SIZE];
|
||||
if (!_sd->card()->readSector(MOTA_SD_HANDOFF_SECTOR, sector)) {
|
||||
fail("SD handoff sector read failed");
|
||||
|
||||
@@ -18,6 +18,8 @@ class OtaCacheSdNrf52;
|
||||
// Persistent nRF52840 OTA store backed by the MeshTower V2 microSD socket.
|
||||
// The .mota remains a normal file, but is preallocated contiguously so the
|
||||
// bootloader can read it by raw sectors without embedding a FAT implementation.
|
||||
// The exact self-update build uses this same one-file store for either an
|
||||
// application package or an explicitly confirmed signed bootloader package.
|
||||
class OtaStoreSdNrf52 : public OtaStore {
|
||||
public:
|
||||
OtaStoreSdNrf52();
|
||||
@@ -36,9 +38,14 @@ public:
|
||||
uint32_t payload_off, uint32_t payload_size,
|
||||
bool is_bootloader) override;
|
||||
|
||||
// Called only after the app has verified payload, base, signature and trust.
|
||||
// Writes APRV into the file, then publishes the raw-sector handoff record.
|
||||
bool approve_for_bootloader();
|
||||
// Called only after the app has verified package kind, payload, applicable
|
||||
// base/identity, signature, trust, and storage-specific safety geometry.
|
||||
// For a bootloader package it writes APRV, binds the exact authenticated
|
||||
// signed image_hash to an internal-flash token, and only then publishes the
|
||||
// raw-sector handoff. Application packages retain the ordinary APRV+handoff
|
||||
// path and omit expected_boot_image_hash.
|
||||
bool approve_for_bootloader(
|
||||
const uint8_t expected_boot_image_hash[32] = nullptr);
|
||||
bool formatCard(MainBoard& board);
|
||||
bool eraseCard(MainBoard& board);
|
||||
bool getSpace(MainBoard& board, uint64_t& used_bytes, uint64_t& free_bytes);
|
||||
@@ -60,6 +67,7 @@ private:
|
||||
SdFs* _sd = nullptr;
|
||||
mutable FsFile* _file = nullptr;
|
||||
bool _mounted = false;
|
||||
bool _planned_bootloader = false;
|
||||
uint32_t _total = 0;
|
||||
uint32_t _first_sector = 0;
|
||||
uint32_t _allocated_sectors = 0;
|
||||
|
||||
@@ -28,7 +28,9 @@ VerifyResult ota_verify(const uint8_t* buf, uint32_t len, const SignerAllowlist&
|
||||
return r;
|
||||
}
|
||||
|
||||
VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow) {
|
||||
static VerifyResult ota_verify_store(const OtaStore& store,
|
||||
const SignerAllowlist& allow,
|
||||
const uint8_t* expected_manifest) {
|
||||
VerifyResult r;
|
||||
const uint32_t total = store.staged_size();
|
||||
uint8_t hdr[8], manifest[MOTA_MFL], trailer[5];
|
||||
@@ -36,6 +38,8 @@ VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow) {
|
||||
!store.read(0, hdr, sizeof(hdr)) ||
|
||||
memcmp(hdr, MOTA_MAGIC, 4) != 0 || rd_u32le(hdr + 4) != total ||
|
||||
!store.read(8, manifest, sizeof(manifest)) ||
|
||||
(expected_manifest &&
|
||||
memcmp(manifest, expected_manifest, sizeof(manifest)) != 0) ||
|
||||
!store.read(total - 5, trailer, sizeof(trailer)) ||
|
||||
memcmp(trailer, MOTA_TRAILER, sizeof(trailer)) != 0) return r;
|
||||
|
||||
@@ -99,5 +103,15 @@ VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow) {
|
||||
return r;
|
||||
}
|
||||
|
||||
VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow) {
|
||||
return ota_verify_store(store, allow, nullptr);
|
||||
}
|
||||
|
||||
VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow,
|
||||
const uint8_t expected_manifest[MOTA_MFL]) {
|
||||
if (!expected_manifest) return VerifyResult();
|
||||
return ota_verify_store(store, allow, expected_manifest);
|
||||
}
|
||||
|
||||
} // namespace ota
|
||||
} // namespace mesh
|
||||
@@ -31,5 +31,11 @@ VerifyResult ota_verify(const uint8_t* buf, uint32_t len, const SignerAllowlist&
|
||||
// contiguous memory view (notably the MeshTower V2 SD-backed nRF52 store).
|
||||
VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow);
|
||||
|
||||
// As above, but the store's manifest must be byte-identical to an already
|
||||
// authenticated local copy. This binds removable-media payload verification
|
||||
// to the exact signed anchors later written into an internal approval token.
|
||||
VerifyResult ota_verify(const OtaStore& store, const SignerAllowlist& allow,
|
||||
const uint8_t expected_manifest[MOTA_MFL]);
|
||||
|
||||
} // namespace ota
|
||||
} // namespace mesh
|
||||
Reference in new issue
Block a user