Fix QSPI wake race during OTA staging

This commit is contained in:
mikecarper
2026-08-20 18:12:09 -07:00
parent 509f831442
commit eec34b93e5
3 changed files with 32 additions and 2 deletions
+5 -2
View File
@@ -266,7 +266,7 @@ bool OtaStoreQspiNrf52::ensureFlash() {
return false;
}
_qspi_awake = true;
delayMicroseconds(50);
delayMicroseconds(MOTA_QSPI_DPD_WAKE_GUARD_US);
alignas(4) uint8_t jedec[4] = { 0, 0, 0, 0 };
if (!customInstruction(0x9F, NRF_QSPI_CINSTR_LEN_4B, jedec) || jedec[0] == 0 || jedec[0] == 0xFF ||
jedec[2] < 20 || jedec[2] > 24) {
@@ -306,7 +306,10 @@ void OtaStoreQspiNrf52::releaseFlash() {
// releasing the nRF QSPI peripheral instead of leaving both active for
// the rest of the boot. ensureFlash() issues 0xAB on the next operation.
(void)customInstruction(0xB9, NRF_QSPI_CINSTR_LEN_1B);
delayMicroseconds(5);
// Do not deactivate or let a following ensureFlash() assert CS until the
// flash has both entered and remained in DPD for its required interval.
// plan_layout() is intentionally followed immediately by begin().
delayMicroseconds(MOTA_QSPI_DPD_ENTRY_GUARD_US);
}
// Match nrfx_qspi_uninit(): DEACTIVATE does not require a READY wait before
// disabling the peripheral. Trigger it after every successful ENABLE, even
+17
View File
@@ -1,5 +1,7 @@
#pragma once
#include <stdint.h>
#if defined(OTA_QSPI_STORE) && defined(QSPIFLASH)
#error "OTA_QSPI_STORE raw staging cannot share a QSPI chip with QSPIFLASH"
#endif
@@ -16,6 +18,21 @@
#error "RAK3401's RAK13302 radio already owns the WisBlock SPI bus/chip-select"
#endif
namespace mesh {
namespace ota {
// A status/capacity probe can be followed immediately by begin(). Supported
// NOR parts need time both to enter deep power-down after B9 and to remain
// there before CS may be asserted again. MX25R1635F needs 10 us + 30 us;
// retain margin for scheduling granularity and other matched flashes.
static const uint32_t MOTA_QSPI_DPD_ENTRY_GUARD_US = 50u;
// Release-from-deep-power-down latency is as high as 45 us on supported NOR.
static const uint32_t MOTA_QSPI_DPD_WAKE_GUARD_US = 50u;
} // namespace ota
} // namespace mesh
#if defined(NRF52_PLATFORM) && defined(OTA_QSPI_STORE)
#include "OtaStore.h"
+10
View File
@@ -2,6 +2,7 @@
#include <cstring>
#include "helpers/ota/OtaFlashLayout_nrf52.h"
#include "helpers/ota/OtaStoreQspiNrf52.h"
#include "helpers/ota/OtaSdHandoff.h"
using namespace mesh::ota;
@@ -25,6 +26,15 @@ static constexpr uint32_t CAP_V7 = LEGACY - APP_END_V7;
static constexpr uint32_t CAP_V6_EXPANDED = EXPANDED - APP_END_V6;
static constexpr uint32_t CAP_V7_EXPANDED = EXPANDED - APP_END_V7;
TEST(OtaQspiTiming, PreservesDeepPowerDownEntryAndWakeGuards) {
// MX25R1635F requires 10 us to enter DPD plus 30 us before another command;
// its release latency can reach 45 us. These constants are consumed by the
// real HAL path, so a future power-saving edit cannot restore the live
// plan_layout()->begin() race without failing the native suite.
EXPECT_GE(MOTA_QSPI_DPD_ENTRY_GUARD_US, 50u);
EXPECT_GE(MOTA_QSPI_DPD_WAKE_GUARD_US, 45u);
}
TEST(OtaFlashPlan, SelectsCeilingFromLinkedLayoutAndStorage) {
// Actual internal secondary storage is authoritative regardless of linker selection.
EXPECT_EQ(mota_nrf52_stage_ceiling_for_layout(EXPANDED, true), LEGACY);