Unify RAK repeater storage firmware and bootloader handoff

This commit is contained in:
mikecarper committed 2026-09-25 10:35:04 -07:00
1 parent 7a8bddda53
commit fca10471c3
27 files changed
+723 -29

No files matched your search

+6 -1
View File
@@ -149,7 +149,12 @@ void ManagementReporter::snapshot() {
}
#elif defined(NRF52_PLATFORM)
const auto bl = ota::ota_bootloader_app_caps();
#if defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE)
#if defined(OTA_RAK_AUTO_STORE)
const auto* active_context = ota::ota_context_if_active();
const uint16_t codecs = bl.codec_mask &
(!active_context ? 4u : active_context->fetch_store.usesExternal() ? 5u :
active_context->fetch_store.usesInternal() ? 4u : 0u);
#elif defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE)
const uint16_t codecs = bl.codec_mask & 5u; // full + in-place
#else
const uint16_t codecs = bl.codec_mask & 4u; // internal app path accepts deltas only
+15 -1
View File
@@ -76,7 +76,21 @@ void formatStorageLayout(MainBoard& board, char* reply, size_t reply_size) {
(unsigned long)internal_fs_start,
(unsigned long)(internal_fs_size / 1024UL));
#if defined(ENABLE_OTA) && defined(OTA_QSPI_STORE)
#if defined(ENABLE_OTA) && defined(OTA_RAK_AUTO_STORE)
(void)board;
mesh::ota::OtaStoreAdaptiveNrf52& store = mesh::ota::ota_ctx().fetch_store;
if (store.usesExternal()) {
const uint32_t capacity = store.qspiCapacity();
appendStorageLayout(reply, reply_size,
"; ota=%s qspi=%luK id=%06lX",
store.selectionReason(), (unsigned long)(capacity / 1024UL),
(unsigned long)store.jedec_id());
} else {
appendStorageLayout(reply, reply_size, "; ota=%s internal=%luK",
store.selectionReason(),
(unsigned long)(store.capacity() / 1024UL));
}
#elif defined(ENABLE_OTA) && defined(OTA_QSPI_STORE)
(void)board;
mesh::ota::OtaStoreQspiNrf52& store = mesh::ota::ota_ctx().fetch_store;
const uint32_t capacity = store.capacity();
+38 -3
View File
@@ -465,6 +465,9 @@ bool ota_apply_detools_mota(const uint8_t*, uint32_t, const SignerAllowlist&, Ap
// not be published until the command reply has drained and reset is imminent.
static OtaStoreFlashNrf52* g_nrf52_apply_store = nullptr;
#endif
#if defined(OTA_QSPI_STORE)
static uint8_t g_nrf52_qspi_handoff = GPREGRET2_OTA_STAGE_QSPI;
#endif
static void ota_nrf52_set_reset_handoff(uint8_t request, uint8_t source) {
uint8_t sd_en = 0;
@@ -494,10 +497,26 @@ static bool ota_nrf52_clear_reset_reasons() {
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)
#if defined(OTA_RAK_AUTO_STORE)
if (g_nrf52_apply_store) {
if (g_nrf52_apply_store->is_hybrid()) {
if (!ota_nrf52_clear_reset_reasons() ||
!g_nrf52_apply_store->publish_hybrid_handoff()) {
ota_nrf52_set_reset_handoff(0u, 0xBDu);
NVIC_SystemReset();
return;
}
stage_handoff = GPREGRET2_OTA_STAGE_HYBRID;
} else {
stage_handoff = mota_nrf52_flash_stage_handoff(ota_nrf52_effective_stage_ceiling());
}
} else {
stage_handoff = g_nrf52_qspi_handoff;
}
#elif defined(OTA_SD_STORE)
stage_handoff = GPREGRET2_OTA_STAGE_SD;
#elif defined(OTA_QSPI_STORE)
stage_handoff = GPREGRET2_OTA_STAGE_QSPI;
stage_handoff = g_nrf52_qspi_handoff;
#elif defined(OTA_FLASH_STORE)
if (g_nrf52_apply_store && g_nrf52_apply_store->is_hybrid()) {
// The bootloader admits retained SRAM only after a clean software-reset
@@ -1171,7 +1190,23 @@ bool ota_prepare_bootloader_update_nrf52(OtaStoreSdNrf52& store,
#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", false, st, msg);
#if defined(OTA_RAK_AUTO_STORE)
g_nrf52_apply_store = nullptr;
#endif
g_nrf52_qspi_handoff = GPREGRET2_OTA_STAGE_QSPI;
if (!ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_QSPI, "QSPI", false, st, msg)) {
return false;
}
#if defined(OTA_RAK_AUTO_STORE) && !defined(RAK_3401)
if (store.jedec_id() == 0xC84015UL) {
OtaBootloaderIdentity identity;
if (ota_installed_bootloader_identity(identity) && identity.crc_ok &&
strcmp(identity.device_name, "4631_AUTO_DFU") == 0) {
g_nrf52_qspi_handoff = GPREGRET2_OTA_STAGE_RAK15001;
}
}
#endif
return true;
}
#if defined(OTA_QSPI_BOOTLOADER_UPDATE)
+2 -1
View File
@@ -26,7 +26,8 @@
#if !defined(NRF52_PLATFORM) || !defined(OTA_FLASH_STORE)
#error "OTA_INTERNAL_BOOTLOADER_UPDATE requires nRF52 internal-flash staging"
#endif
#if defined(OTA_QSPI_STORE) || defined(OTA_SD_STORE) || defined(QSPIFLASH)
#if (defined(OTA_QSPI_STORE) && !defined(OTA_RAK_AUTO_STORE)) || \
defined(OTA_SD_STORE) || defined(QSPIFLASH)
#error "internal bootloader staging cannot share an external OTA/filesystem store"
#endif
#endif
+58 -6
View File
@@ -181,6 +181,9 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
"OTA: status | stats | ls | get <id> flash [rescue] | install | rescue install <hash16> | "
"cancel | announce | self | folder | config | key");
#endif
#elif defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
strcpy(reply,
"OTA: status | stats | ls | get <id> flash | install | bootloader | cancel | announce | self | storage | folder | config | key");
#elif defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
#if defined(OTA_QSPI_BOOTLOADER_UPDATE)
strcpy(reply,
@@ -244,7 +247,11 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
// nRF52 applies via the bootloader - show (cached) whether it can, so `ota get`/`install` won't surprise.
// blrc = the bootloader's last apply code (diagnostic; 0xB8=success, see ota_delta.c).
const OtaBlCaps& bl = c.bootloaderAppCaps();
#if defined(OTA_QSPI_STORE)
#if defined(OTA_RAK_AUTO_STORE)
const char* bl_state = !bl.present ? "NONE" :
c.fetch_store.usesExternal() ? "QSPI" :
c.fetch_store.usesInternal() ? "internal" : "storage-ERR";
#elif defined(OTA_QSPI_STORE)
const char* bl_state = !bl.present ? "NONE" :
(bl.storage_flags & OTA_BL_STORAGE_QSPI) ? "QSPI" : "NO-QSPI";
#elif defined(OTA_SD_STORE)
@@ -348,7 +355,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
#endif
#endif
#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) && !defined(OTA_SD_STORE) && \
!defined(OTA_QSPI_STORE)
(!defined(OTA_QSPI_STORE) || defined(OTA_RAK_AUTO_STORE))
SelfFwInfo list_self;
bool list_has_endf = ota_self_firmware(list_self) && list_self.valid;
#endif
@@ -407,6 +414,11 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
&& h->codec < 16 && (list_bl.codec_mask & (1u << h->codec));
#if defined(OTA_SD_STORE)
installable = installable && (list_bl.storage_flags & OTA_BL_STORAGE_SD);
#elif defined(OTA_RAK_AUTO_STORE)
if (c.fetch_store.usesExternal())
installable = installable && (list_bl.storage_flags & OTA_BL_STORAGE_QSPI);
if (h->codec == CODEC_FULL)
installable = installable && c.fetch_store.usesExternal();
#elif defined(OTA_QSPI_STORE)
installable = installable && (list_bl.storage_flags & OTA_BL_STORAGE_QSPI);
#endif
@@ -584,6 +596,15 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
strcpy(reply, "ERR bootloader cannot apply an update staged on SD; update it over USB first");
return true;
}
#elif defined(OTA_RAK_AUTO_STORE)
if (c.fetch_store.usesExternal() && !(bl.storage_flags & OTA_BL_STORAGE_QSPI)) {
strcpy(reply, "ERR bootloader cannot apply from detected QSPI storage");
return true;
}
if (!c.fetch_store.usesExternal() && !c.fetch_store.usesInternal()) {
snprintf(reply, 160, "ERR OTA storage unsafe: %s", c.fetch_store.selectionReason());
return true;
}
#elif defined(OTA_QSPI_STORE)
if (!(bl.storage_flags & OTA_BL_STORAGE_QSPI)) {
strcpy(reply, "ERR bootloader cannot apply an update staged on QSPI; update it over USB first");
@@ -592,7 +613,7 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
#endif
#endif
#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE) && !defined(OTA_SD_STORE) && \
!defined(OTA_QSPI_STORE)
(!defined(OTA_QSPI_STORE) || defined(OTA_RAK_AUTO_STORE))
SelfFwInfo self;
bool has_endf = ota_self_firmware(self) && self.valid;
#if defined(OTA_INTERNAL_BOOTLOADER_UPDATE)
@@ -667,7 +688,9 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
if (was_folder && c.folder_dest) c.folder_dest->clear();
c.fetch_to_folder = false;
c.manager.set_fetch_store(&c.fetch_store); // revert to the default flash store (a folder pull switched it)
#if defined(NRF52_PLATFORM) && !defined(OTA_SD_STORE) && !defined(OTA_QSPI_STORE)
#if defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
c.manager.set_accept_full(c.fetch_store.usesExternal());
#elif defined(NRF52_PLATFORM) && !defined(OTA_SD_STORE) && !defined(OTA_QSPI_STORE)
c.manager.set_accept_full(false);
#endif
const bool discarded = !was_folder && !was_sd_archive &&
@@ -710,7 +733,21 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
// ---- raw-QSPI staging diagnostics (read-only probe; preserves a latched fetch failure) ----
} else if (is_cmd(a, "qspi|storage", &rest)) {
#if defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
#if defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
if (c.fetch_store.usesExternal()) {
const uint32_t qspi_capacity = c.fetch_store.qspiCapacity();
snprintf(reply, 160, "QSPI %s jedec=%06lX size=%luK sr1=%02X stage=%s%s%s",
c.fetch_store.selectionReason(), (unsigned long)c.fetch_store.jedec_id(),
(unsigned long)(qspi_capacity / 1024), c.fetch_store.status1(),
c.fetch_store.last_stage(), c.fetch_store.last_error()[0] ? " error=" : "",
c.fetch_store.last_error());
} else if (c.fetch_store.usesInternal()) {
snprintf(reply, 160, "OTA storage: %s; internal capacity=%luK",
c.fetch_store.selectionReason(), (unsigned long)(c.fetch_store.capacity() / 1024));
} else {
snprintf(reply, 160, "OTA storage unsafe: %s", c.fetch_store.selectionReason());
}
#elif defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
// This probe only reads JEDEC/SR1. capacity() deliberately preserves a
// latched fetch failure so asking for diagnostics cannot erase its cause.
uint32_t qspi_capacity = c.fetch_store.capacity();
@@ -735,7 +772,22 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
#if defined(NRF52_PLATFORM)
// nRF52 applies via the bootloader, so surface whether THIS device's bootloader can install this store.
const OtaBlCaps& bl = c.bootloaderAppCaps(); // cached (flash scanned once)
#if defined(OTA_QSPI_STORE)
#if defined(OTA_RAK_AUTO_STORE)
if (c.fetch_store.usesExternal()) {
const uint32_t qspi_capacity = c.fetch_store.qspiCapacity();
snprintf(reply + n, 160 - n, " | QSPI store:%s%uK | bootloader: %s",
qspi_capacity ? "" : "ERR ",
(unsigned)(qspi_capacity / 1024),
(bl.present && (bl.storage_flags & OTA_BL_STORAGE_QSPI)) ? "QSPI apply OK" : "NO QSPI apply");
} else if (c.fetch_store.usesInternal()) {
snprintf(reply + n, 160 - n, " | internal store:%luK | bootloader: %s",
(unsigned long)(c.fetch_store.capacity() / 1024),
bl.present ? "delta apply OK" : "NO delta apply");
} else {
snprintf(reply + n, 160 - n, " | storage unsafe: %s",
c.fetch_store.selectionReason());
}
#elif defined(OTA_QSPI_STORE)
uint32_t qspi_capacity = c.fetch_store.capacity();
n += snprintf(reply + n, 160 - n, " | QSPI store:%s%uK",
qspi_capacity ? "" : "ERR ", (unsigned)(qspi_capacity / 1024));
+32 -2
View File
@@ -26,7 +26,9 @@
#define OTA_DYNAMIC_CONTEXT 0
#endif
#if defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
#if defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
#include "OtaStoreAdaptiveNrf52.h"
#elif defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
#include "OtaStoreQspiNrf52.h"
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
#include "OtaStoreSdNrf52.h"
@@ -107,6 +109,8 @@ struct OtaContext {
// store object for OtaManager, while folder captures replace it with the
// host-backed FolderMotaStore for the duration of the pull.
OtaStoreRam<1> fetch_store;
#elif defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
OtaStoreAdaptiveNrf52 fetch_store;
#elif defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
OtaStoreQspiNrf52 fetch_store; // persistent raw QSPI staging, full + in-place delta
#elif defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
@@ -305,7 +309,15 @@ struct OtaContext {
}
}
bool ok;
#if defined(NRF52_PLATFORM) && (defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE))
#if defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
ok = fetch_store.usesExternal()
? ota_apply_mota_nrf52(fetch_store.externalStore(), allow, apply_st, msg)
: fetch_store.usesInternal()
? ota_apply_mota_nrf52(fetch_store.internalStore(), allow, apply_st, msg)
: false;
if (!fetch_store.usesExternal() && !fetch_store.usesInternal())
strncpy(msg, fetch_store.selectionReason(), 96);
#elif 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) && defined(OTA_FLASH_STORE)
if (rescue_base_hash) {
@@ -346,9 +358,19 @@ struct OtaContext {
msg[95] = 0; return false;
}
const OtaBootloaderIdentity& installed = bootloaderIdentity();
#if defined(OTA_RAK_AUTO_STORE)
if (!fetch_store.usesInternal()) {
strncpy(msg, "bootloader LoRa update needs the internal OTAFIX profile", 96);
msg[95] = 0; return false;
}
bool ok = ota_prepare_bootloader_update_nrf52(
fetch_store.internalStore(), allow, installed, manager.fetchManifestId(), operator_mid,
operator_hash8, apply_st, msg);
#else
bool ok = ota_prepare_bootloader_update_nrf52(
fetch_store, allow, installed, manager.fetchManifestId(), operator_mid,
operator_hash8, apply_st, msg);
#endif
if (ok) { bootloader_apply_pending = true; apply_pending = true; }
return ok;
#else
@@ -664,6 +686,9 @@ struct OtaContext {
manager.set_accept_full(true);
manager.set_autofetch(OtaManager::AUTOFETCH_OFF);
autoinstall = AUTOINSTALL_OFF;
#elif defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
manager.set_accept_full(fetch_store.usesExternal());
manager.set_apply_codec(CODEC_DETOOLS_INPLACE);
#elif defined(NRF52_PLATFORM) && (defined(OTA_SD_STORE) || defined(OTA_QSPI_STORE))
manager.set_accept_full(true);
manager.set_apply_codec(CODEC_DETOOLS_INPLACE);
@@ -678,7 +703,12 @@ struct OtaContext {
#if defined(NRF52_PLATFORM) && \
(defined(OTA_QSPI_BOOTLOADER_UPDATE) || defined(OTA_INTERNAL_BOOTLOADER_UPDATE) || \
defined(OTA_SD_BOOTLOADER_UPDATE))
#if defined(OTA_RAK_AUTO_STORE)
manager.set_accept_bootloader(fetch_store.usesInternal() &&
ota_bootloader_self_update_caps_valid(ota_bootloader_update_caps()));
#else
manager.set_accept_bootloader(true);
#endif
#else
manager.set_accept_bootloader(false);
#endif
+1
View File
@@ -55,6 +55,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_RAK15001 = 0x52u;
static const uint8_t GPREGRET2_OTA_STAGE_SD = 0x53u;
static const uint8_t GPREGRET2_OTA_STAGE_HYBRID = 0xA6u;
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
#include <string.h>
namespace mesh {
namespace ota {
enum class RakStorageChoice : uint8_t { Internal, Qspi, Unsafe };
// detected: 0 = absent, 1 = RAK15001 C, 2 = W25Q16, 3 = both.
// A QSPI bootloader can only apply from the chip and wiring it was built for.
// Never stage into a different NOR just because it answered a JEDEC query.
inline RakStorageChoice rak_storage_choice(uint8_t detected,
bool qspi_bootloader,
bool identity_valid,
const char* device_name,
bool rak3401) {
if (detected > 3u || detected == 3u) return RakStorageChoice::Unsafe;
const char* auto_name = rak3401 ? "3401_AUTO_DFU" : "4631_AUTO_DFU";
const bool merged_bootloader = identity_valid && device_name &&
strcmp(device_name, auto_name) == 0;
if (detected == 0u) return !qspi_bootloader || merged_bootloader
? RakStorageChoice::Internal : RakStorageChoice::Unsafe;
if (!qspi_bootloader) return RakStorageChoice::Internal;
if (!identity_valid || !device_name) return RakStorageChoice::Unsafe;
if (merged_bootloader) {
return rak3401 && detected == 1u ? RakStorageChoice::Unsafe
: RakStorageChoice::Qspi;
}
const char* expected = rak3401
? (detected == 2u ? "3401_W25Q16_DFU" : "")
: (detected == 1u ? "4631_15001C_DFU" : "4631_W25Q16_DFU");
return expected[0] && strcmp(device_name, expected) == 0
? RakStorageChoice::Qspi : RakStorageChoice::Unsafe;
}
} // namespace ota
} // namespace mesh
+148
View File
@@ -0,0 +1,148 @@
#pragma once
#if defined(NRF52_PLATFORM) && defined(OTA_RAK_AUTO_STORE)
#include "OtaBlInfo.h"
#include "OtaApply.h"
#include "OtaBootloaderUpdate.h"
#include "OtaStoreFlashNrf52.h"
#include "OtaStoreQspiNrf52.h"
#include "OtaRakStoragePolicy.h"
#include <new>
namespace mesh {
namespace ota {
// One RAK application image supports the board's internal OTA store and its
// explicitly matched external NOR. Selection is fixed for the lifetime of an
// app boot; no staged container is ever moved between backends.
class OtaStoreAdaptiveNrf52 : public OtaStore {
enum Mode : uint8_t { UNSELECTED, INTERNAL_MODE, QSPI_MODE, UNSAFE };
mutable Mode _mode = UNSELECTED;
mutable const char* _reason = "not probed";
// Only one backend is usable for a given board/bootloader pairing. Sharing
// their two flash-page buffers saves 8 KiB of scarce nRF52840 runtime RAM.
union StoreStorage {
OtaStoreFlashNrf52 internal;
OtaStoreQspiNrf52 external;
StoreStorage() {}
~StoreStorage() {}
};
mutable StoreStorage _storage;
void activateInternal() const {
new (&_storage.internal) OtaStoreFlashNrf52();
_mode = INTERNAL_MODE;
}
void activateExternal() const {
new (&_storage.external) OtaStoreQspiNrf52();
_mode = QSPI_MODE;
}
void select() const {
if (_mode != UNSELECTED) return;
const uint8_t detected = OtaStoreQspiNrf52::autoDetect();
const OtaBlCaps caps = ota_bootloader_app_caps();
if (detected == 3u) {
_mode = UNSAFE;
_reason = "both external NOR types detected";
return;
}
const bool qspi_bootloader = caps.present &&
(caps.storage_flags & OTA_BL_STORAGE_QSPI) != 0;
OtaBootloaderIdentity identity;
const bool identity_valid = qspi_bootloader &&
ota_installed_bootloader_identity(identity) && identity.crc_ok;
#if defined(RAK_3401)
const bool rak3401 = true;
#else
const bool rak3401 = false;
#endif
const RakStorageChoice choice = rak_storage_choice(
detected, qspi_bootloader, identity_valid,
identity_valid ? identity.device_name : nullptr, rak3401);
if (choice == RakStorageChoice::Internal) {
activateInternal();
_reason = detected == 0u ? "no external NOR" :
"external NOR fitted; internal bootloader";
return;
}
if (choice == RakStorageChoice::Qspi) {
activateExternal();
_reason = detected == 1u ? "RAK15001 C" : "W25Q16";
return;
}
_mode = UNSAFE;
if (detected == 0u) {
_reason = "QSPI bootloader but no matched external NOR";
} else if (!identity_valid) {
_reason = "QSPI bootloader identity unavailable";
} else {
_reason = "external NOR and OTAFIX bootloader do not match";
}
}
OtaStore* active() {
select();
return _mode == QSPI_MODE ? static_cast<OtaStore*>(&_storage.external) :
_mode == INTERNAL_MODE ? static_cast<OtaStore*>(&_storage.internal) : nullptr;
}
const OtaStore* active() const {
select();
return _mode == QSPI_MODE ? static_cast<const OtaStore*>(&_storage.external) :
_mode == INTERNAL_MODE ? static_cast<const OtaStore*>(&_storage.internal) : nullptr;
}
public:
~OtaStoreAdaptiveNrf52() override {
if (_mode == QSPI_MODE) _storage.external.~OtaStoreQspiNrf52();
else if (_mode == INTERNAL_MODE) _storage.internal.~OtaStoreFlashNrf52();
}
bool usesExternal() const { select(); return _mode == QSPI_MODE; }
bool usesInternal() const { select(); return _mode == INTERNAL_MODE; }
const char* selectionReason() const { select(); return _reason; }
OtaStoreFlashNrf52& internalStore() { select(); return _storage.internal; }
OtaStoreQspiNrf52& externalStore() { select(); return _storage.external; }
uint32_t qspiCapacity() const { return usesExternal() ? _storage.external.capacity() : 0u; }
uint32_t jedec_id() const { return usesExternal() ? _storage.external.jedec_id() : 0u; }
uint8_t status1() const { return usesExternal() ? _storage.external.status1() : 0xFFu; }
const char* last_stage() const { return usesExternal() ? _storage.external.last_stage() : "inactive"; }
const char* last_error() const {
return usesExternal() ? _storage.external.last_error() : selectionReason();
}
bool begin(uint32_t n) override { OtaStore* s = active(); return s && s->begin(n); }
bool write(uint32_t p, const uint8_t* d, uint32_t n) override {
OtaStore* s = active(); return s && s->write(p, d, n);
}
bool read(uint32_t p, uint8_t* d, uint32_t n) const override {
const OtaStore* s = active(); return s && s->read(p, d, n);
}
uint32_t capacity() const override {
const OtaStore* s = active(); return s ? s->capacity() : 0u;
}
uint32_t staged_size() const override {
const OtaStore* s = active(); return s ? s->staged_size() : 0u;
}
void clear() override { OtaStore* s = active(); if (s) s->clear(); }
bool discard() override { OtaStore* s = active(); return s && s->discard(); }
bool set_meta_size(uint32_t n) override {
OtaStore* s = active(); return s && s->set_meta_size(n);
}
bool finalize() override { OtaStore* s = active(); return s && s->finalize(); }
void checkpoint() override { OtaStore* s = active(); if (s) s->checkpoint(); }
bool reopen() override { OtaStore* s = active(); return s && s->reopen(); }
bool reopenFor(const uint8_t* mid, uint32_t target) override {
OtaStore* s = active(); return s && s->reopenFor(mid, target);
}
bool plan_layout(bool full, uint32_t image, uint32_t payload_off,
uint32_t payload_size, bool bootloader) override {
OtaStore* s = active();
return s && s->plan_layout(full, image, payload_off, payload_size, bootloader);
}
};
} // namespace ota
} // namespace mesh
#endif
+100
View File
@@ -19,6 +19,12 @@ namespace ota {
namespace {
#if defined(OTA_RAK_AUTO_STORE)
static uint8_t auto_cs_pin = NRF_QSPI_PIN_NOT_CONNECTED;
static uint32_t auto_jedec_id = 0;
static uint8_t auto_detection = 0xFF;
#endif
#if defined(OTA_QSPI_SCK_PHYSICAL_PIN) && defined(OTA_QSPI_SCK_ARDUINO_PIN)
#error "QSPI SCK must use either a physical or Arduino pin override"
#elif !defined(OTA_QSPI_SCK_PHYSICAL_PIN) && !defined(OTA_QSPI_SCK_ARDUINO_PIN)
@@ -100,11 +106,15 @@ static uint8_t qspi_sck_pin() {
}
static uint8_t qspi_cs_pin() {
#if defined(OTA_RAK_AUTO_STORE)
return auto_cs_pin;
#else
#ifdef OTA_QSPI_CS_PHYSICAL_PIN
return OTA_QSPI_CS_PHYSICAL_PIN;
#else
return arduino_to_physical(OTA_QSPI_CS_ARDUINO_PIN);
#endif
#endif
}
static uint8_t qspi_io0_pin() {
@@ -243,8 +253,90 @@ static void wake_qspi_flash_before_activate(const nrf_qspi_pins_t& pins) {
delayMicroseconds(MOTA_QSPI_DPD_WAKE_GUARD_US);
}
#if defined(OTA_RAK_AUTO_STORE)
static uint8_t gpio_spi_byte(const nrf_qspi_pins_t& pins, uint8_t out) {
uint8_t in = 0;
for (uint8_t bit = 0; bit < 8u; ++bit) {
nrf_gpio_pin_write(pins.io0_pin, (out & 0x80u) != 0);
out <<= 1;
delayMicroseconds(1);
nrf_gpio_pin_set(pins.sck_pin);
delayMicroseconds(1);
in = (uint8_t)((in << 1) | nrf_gpio_pin_read(pins.io1_pin));
nrf_gpio_pin_clear(pins.sck_pin);
delayMicroseconds(1);
}
return in;
}
static uint32_t probe_nor(uint8_t cs) {
const nrf_qspi_pins_t pins = {
qspi_sck_pin(), cs, qspi_io0_pin(), qspi_io1_pin(),
qspi_io2_pin(), qspi_io3_pin()
};
// A previous reset may have left the NOR in deep power-down. This GPIO
// wake precedes the nRF QSPI ACTIVATE for the same reason as ensureFlash().
wake_qspi_flash_before_activate(pins);
nrf_gpio_pin_clear(cs);
delayMicroseconds(1);
(void)gpio_spi_byte(pins, 0x9Fu);
const uint32_t id = ((uint32_t)gpio_spi_byte(pins, 0xFFu) << 16) |
((uint32_t)gpio_spi_byte(pins, 0xFFu) << 8) |
gpio_spi_byte(pins, 0xFFu);
nrf_gpio_pin_set(cs);
delayMicroseconds(1);
if (id == 0xC84015UL || id == 0xEF4015UL) {
// Detection is read-only. Return a recognized NOR to low-power standby;
// ensureFlash() issues its own 0xAB before taking over with QSPI.
nrf_gpio_pin_clear(cs);
delayMicroseconds(1);
(void)gpio_spi_byte(pins, 0xB9u);
nrf_gpio_pin_set(cs);
delayMicroseconds(MOTA_QSPI_DPD_ENTRY_GUARD_US);
}
return id;
}
#endif
} // namespace
#if defined(OTA_RAK_AUTO_STORE)
uint8_t OtaStoreQspiNrf52::autoDetect() {
if (auto_detection != 0xFFu) return auto_detection;
const uint8_t w25_cs = arduino_to_physical(31);
nrf_gpio_pin_set(w25_cs);
nrf_gpio_cfg_output(w25_cs);
#if defined(RAK_3401)
// RAK13302 shares clock/data with the flash. Its NSS must stay high while
// the GPIO probe owns those signals, then SPI1 is restored for the radio.
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
SPI1.end();
const uint32_t w25_id = probe_nor(w25_cs);
SPI1.begin();
auto_detection = w25_id == 0xEF4015UL ? 2u : 0u;
#else
const uint8_t rak_cs = arduino_to_physical(26);
nrf_gpio_pin_set(rak_cs);
nrf_gpio_cfg_output(rak_cs);
const uint32_t rak_id = probe_nor(rak_cs);
const uint32_t w25_id = probe_nor(w25_cs);
const bool rak = rak_id == 0xC84015UL;
const bool w25 = w25_id == 0xEF4015UL;
auto_detection = rak && w25 ? 3u : rak ? 1u : w25 ? 2u : 0u;
#endif
if (auto_detection == 1u) {
auto_cs_pin = arduino_to_physical(26);
auto_jedec_id = 0xC84015UL;
} else if (auto_detection == 2u) {
auto_cs_pin = w25_cs;
auto_jedec_id = 0xEF4015UL;
}
return auto_detection;
}
#endif
OtaStoreQspiNrf52::OtaStoreQspiNrf52() {
resetSession();
}
@@ -442,6 +534,14 @@ bool OtaStoreQspiNrf52::ensureFlash() {
fail("QSPI JEDEC ID does not match target");
return false;
}
#endif
#if defined(OTA_RAK_AUTO_STORE)
if (auto_detection == 0xFFu || auto_detection == 0u ||
auto_detection == 3u || _jedec_id != auto_jedec_id) {
releaseFlash();
fail("QSPI JEDEC ID does not match detected RAK storage");
return false;
}
#endif
_flash_size = 1UL << jedec[2];
if (_flash_size > MAX_FLASH) {
+6
View File
@@ -166,6 +166,12 @@ public:
OtaStoreQspiNrf52();
~OtaStoreQspiNrf52() override;
#if defined(OTA_RAK_AUTO_STORE)
// Probe exact supported NOR parts before activating the nRF QSPI block.
// 0 = absent/unsupported, 1 = RAK15001 C, 2 = W25Q16, 3 = ambiguous.
static uint8_t autoDetect();
#endif
bool begin(uint32_t total_size) override;
bool write(uint32_t offset, const uint8_t *data, uint32_t len) override;
bool read(uint32_t offset, uint8_t *buf, uint32_t len) const override;
+23 -1
View File
@@ -2,7 +2,7 @@
#include <stdint.h>
// AUTO-GENERATED by tools/mota/gen_targets.py - do not edit by hand.
// 612 OTA-capable build targets. Maps target_id (= sha2-256:4 of the target name, LE uint32)
// 634 OTA-capable build targets. Maps target_id (= sha2-256:4 of the target name, LE uint32)
// to the human-readable env name, so a node/tool can name a target seen over the air WITHOUT
// transmitting the string in the .mota / LoRa protocol. Regenerate when the OTA env set changes.
// Size-constrained receivers can set OTA_TARGET_NAME_TABLE=0. They still match targets by ID;
@@ -47,6 +47,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x875f3744, "Generic_ESPNOW_repeatr" },
{ 0xdaf349be, "Generic_ESPNOW_room_svr" },
{ 0x93b93199, "Generic_ESPNOW_terminal_chat" },
{ 0x710abb59, "GEPRC_Linkflow_900_repeater" },
{ 0x5293da13, "Heltec_ct62_companion_radio_ble" },
{ 0xc6dd7723, "Heltec_ct62_companion_radio_ble_ps" },
{ 0xf2eec98c, "Heltec_ct62_companion_radio_usb" },
@@ -101,6 +102,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x1b4399de, "Heltec_t114_repeater_bridge_rs232" },
{ 0xf7e47568, "Heltec_t114_repeater_lora_ota_no_external_sensors" },
{ 0xb1153422, "Heltec_t114_room_server" },
{ 0x388f2804, "Heltec_t114_sensor" },
{ 0x075ca3f9, "Heltec_t114_without_display_companion_radio_ble" },
{ 0x2f9f8e7d, "Heltec_t114_without_display_companion_radio_usb" },
{ 0xc9dc5d8c, "Heltec_t114_without_display_repeater" },
@@ -189,6 +191,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0xe2a1907b, "heltec_v4_expansionkit_tft_companion_radio_ble_ps" },
{ 0xbb0ca8db, "heltec_v4_expansionkit_tft_companion_radio_full_femon" },
{ 0xf75feb3e, "heltec_v4_kiss_modem" },
{ 0xb0a1e71f, "heltec_v4_partition_expander" },
{ 0xbc9cdc70, "heltec_v4_r8_companion_radio_ble" },
{ 0x19e31fc6, "heltec_v4_r8_companion_radio_ble_ps" },
{ 0xa01b330e, "heltec_v4_r8_companion_radio_full" },
@@ -216,6 +219,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x0dacb683, "heltec_v4_r8_tft_terminal_chat" },
{ 0xe792a051, "heltec_v4_repeater" },
{ 0x2d5ea842, "heltec_v4_repeater_bridge_espnow" },
{ 0x30c59b1d, "heltec_v4_repeater_legacy_partition_test" },
{ 0xab74db9e, "heltec_v4_repeater_observer_mqtt" },
{ 0xeed78ce4, "heltec_v4_room_server" },
{ 0x7778b7e1, "heltec_v4_room_server_observer_mqtt" },
@@ -395,6 +399,15 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x2d589527, "meshnology_w12_terminal_chat" },
{ 0x77d0810c, "Minewsemi_me25ls01_repeater" },
{ 0x11b131e7, "Minewsemi_me25ls01_repeater_lora_ota_no_external_sensors" },
{ 0x54850763, "MKE_s3_companion_radio_ble" },
{ 0x0fa61805, "MKE_s3_companion_radio_usb" },
{ 0x8039574f, "MKE_s3_companion_radio_wifi" },
{ 0xa65555d6, "MKE_s3_repeater" },
{ 0x22b1804c, "MKE_s3_repeater_bridge_espnow" },
{ 0xa9f631b1, "MKE_s3_repeater_bridge_rs232" },
{ 0x49d11eca, "MKE_s3_room_server" },
{ 0xd41f0638, "MKE_s3_sensor" },
{ 0xe3105eb8, "MKE_s3_terminal_chat" },
{ 0xf8259880, "Nano_G2_Ultra_repeater" },
{ 0x063f1f00, "nibble_screen_connect_companion_radio_ble_" },
{ 0x70d71f92, "nibble_screen_connect_companion_radio_full_" },
@@ -445,6 +458,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x6925aaf0, "RAK_3112_terminal_chat" },
{ 0x2fa509c1, "RAK_3401_repeater_lora_ota_no_external_sensors" },
{ 0xa52ba3c8, "RAK_3401_repeater_rak13302_w25q16_lora_ota" },
{ 0xaf0b81dd, "RAK_3401_repeater_unified_lora_ota" },
{ 0x9f4d58ac, "RAK_4631_companion_radio_ble" },
{ 0x7a55dec3, "RAK_4631_companion_radio_ethernet" },
{ 0x5e69738a, "RAK_4631_companion_radio_full" },
@@ -454,6 +468,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x41e33a97, "RAK_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors" },
{ 0x29a0da19, "RAK_4631_repeater_lora_ota_no_external_sensors" },
{ 0x29bc38dc, "RAK_4631_repeater_rak15001_slot_c_lora_ota" },
{ 0x05f5ffae, "RAK_4631_repeater_unified_lora_ota" },
{ 0xc62da371, "RAK_4631_repeater_w25q16_lora_ota" },
{ 0xf1d3c5a8, "RAK_4631_terminal_chat" },
{ 0xc6d55752, "RAK_WisMesh_Tag_companion_radio_ble" },
@@ -490,6 +505,10 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x2a2f303b, "Station_G3_ESP32_companion_radio_wifi" },
{ 0x0ba4453d, "Station_G3_ESP32_kiss_modem" },
{ 0xf8d1958f, "Station_G3_ESP32_logging_repeater" },
{ 0x75e85141, "Station_G3_ESP32_r2_repeater" },
{ 0xd62ed92e, "Station_G3_ESP32_r2_repeater_observer_mqtt" },
{ 0x7abaf0d5, "Station_G3_ESP32_r2_room_server" },
{ 0x87edc08b, "Station_G3_ESP32_r2_room_server_observer_mqtt" },
{ 0x3c49caf8, "Station_G3_ESP32_repeater" },
{ 0x58689ef9, "Station_G3_ESP32_repeater_observer_mqtt" },
{ 0x91878dd8, "Station_G3_ESP32_room_server" },
@@ -500,6 +519,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x000fc519, "t1000e_repeater" },
{ 0x7b071fa0, "t1000e_repeater_lora_ota_no_external_sensors" },
{ 0x3e55e34d, "t1000e_room_server" },
{ 0x54606a4f, "t1000e_sensor" },
{ 0x16482630, "T_Beam_S3_Supreme_SX1262_companion_radio_ble" },
{ 0xce0f07ad, "T_Beam_S3_Supreme_SX1262_companion_radio_ble_ps" },
{ 0x01c43d49, "T_Beam_S3_Supreme_SX1262_companion_radio_wifi" },
@@ -607,6 +627,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0xe90a6e74, "Xiao_nrf52_companion_radio_usb" },
{ 0x833e5cdc, "Xiao_nrf52_kiss_modem" },
{ 0x93145fbb, "Xiao_nrf52_repeater" },
{ 0x8d3dd6d5, "Xiao_nrf52_repeater_bridge_rs232" },
{ 0x33a154ff, "Xiao_nrf52_room_server" },
{ 0x6658b418, "Xiao_S3_companion_radio_ble" },
{ 0x0cfb9c00, "Xiao_S3_companion_radio_ble_ps" },
@@ -623,6 +644,7 @@ inline const char* ota_target_env_name(uint32_t target_id) {
{ 0x396fecc7, "Xiao_S3_WIO_companion_radio_usb" },
{ 0xf94e3407, "Xiao_S3_WIO_companion_radio_wifi" },
{ 0xe1930103, "Xiao_S3_WIO_kiss_modem" },
{ 0xa4740508, "Xiao_S3_WIO_partition_expander" },
{ 0x9f19bcd1, "Xiao_S3_WIO_repeater" },
{ 0xfcebff61, "Xiao_S3_WIO_repeater_bridge_espnow" },
{ 0xac5c1405, "Xiao_S3_WIO_repeater_observer_mqtt" },