Fix nRF52 idle sleep event and CryptoCell handling
This commit is contained in:
1 parent
756d887bad
commit
629557fcfe
22 files changed
+1767
-20
No files matched your search
@@ -4,6 +4,7 @@
|
||||
#include "NRF52VoltagePolicy.h"
|
||||
#include "Nrf52BootloaderVersion.h"
|
||||
#include "nrf52/SoftDeviceState.h"
|
||||
#include "nrf52/SleepCycleState.h"
|
||||
#include "nrf52/ShutdownLeds.h"
|
||||
#include <target.h>
|
||||
#include "UserGpioPinPolicy.h"
|
||||
@@ -13,6 +14,7 @@
|
||||
#include "ble_gap.h"
|
||||
#include "ble_hci.h"
|
||||
#include <nrf.h>
|
||||
#include <nrf_nvic.h>
|
||||
#include <nrf_soc.h>
|
||||
#ifdef USE_TINYUSB
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
@@ -464,19 +466,88 @@ void NRF52BoardDCDC::begin() {
|
||||
}
|
||||
|
||||
bool NRF52Board::isExternalPowered() {
|
||||
// Check if SoftDevice is enabled before using its API
|
||||
mesh_nrf52::SleepCycleSnapshot cycle;
|
||||
if (mesh_nrf52::sleepCycleState().snapshot(cycle))
|
||||
return (cycle.usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
|
||||
|
||||
// Setup and callers outside a prepared loop retain the SDK access policy.
|
||||
// Never reuse a cached value after a SoftDevice transition invalidated it.
|
||||
uint8_t sd_enabled = 0;
|
||||
mesh_nrf52::softdeviceIsEnabled(sd_enabled);
|
||||
if (mesh_nrf52::softdeviceIsEnabled(sd_enabled) != NRF_SUCCESS) return false;
|
||||
|
||||
if (sd_enabled) {
|
||||
uint32_t usb_status;
|
||||
sd_power_usbregstatus_get(&usb_status);
|
||||
volatile uint32_t status = 0;
|
||||
const uint32_t error = sd_power_usbregstatus_get((uint32_t*)&status);
|
||||
__asm volatile ("" ::: "memory");
|
||||
if (error != NRF_SUCCESS) return false;
|
||||
const uint32_t usb_status = status;
|
||||
return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
|
||||
} else {
|
||||
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NRF52Board::prepareSleepCycle() {
|
||||
auto& state = mesh_nrf52::sleepCycleState();
|
||||
const uint32_t token = state.begin();
|
||||
const auto application_context = []() {
|
||||
return __get_IPSR() == 0 && __get_PRIMASK() == 0
|
||||
&& __get_BASEPRI() == 0 && __get_FAULTMASK() == 0
|
||||
// SDK critical regions can mask application IRQs in the NVIC even
|
||||
// while the CPU mask registers are clear. This public field is
|
||||
// volatile: read it afresh before queries and after the forced yield.
|
||||
&& nrf_nvic_state.__cr_flag == 0
|
||||
&& xTaskGetSchedulerState() == taskSCHEDULER_RUNNING;
|
||||
};
|
||||
if (!application_context()) {
|
||||
state.publish(token, false, 0, NRF_ERROR_INVALID_STATE);
|
||||
return;
|
||||
}
|
||||
#ifdef NRF_CRYPTOCELL
|
||||
// The pinned Adafruit CC310 library polls (no-interrupts archive), but can
|
||||
// leave its disabled NVIC IRQ pending. The SDK treats that as application
|
||||
// work forever. Match the core's waitForEvent cleanup only at loop entry;
|
||||
// never clear an enabled IRQ belonging to an interrupt-driven owner.
|
||||
if (NVIC_GetEnableIRQ(CRYPTOCELL_IRQn) == 0)
|
||||
NVIC_ClearPendingIRQ(CRYPTOCELL_IRQn);
|
||||
#endif
|
||||
uint8_t sd_enabled = 0;
|
||||
uint32_t error = mesh_nrf52::softdeviceIsEnabled(sd_enabled);
|
||||
uint32_t usb_status = 0;
|
||||
if (error == NRF_SUCCESS) {
|
||||
if (sd_enabled) {
|
||||
volatile uint32_t status = 0;
|
||||
error = sd_power_usbregstatus_get((uint32_t*)&status);
|
||||
__asm volatile ("" ::: "memory");
|
||||
usb_status = status;
|
||||
} else {
|
||||
usb_status = NRF_POWER->USBREGSTATUS;
|
||||
}
|
||||
}
|
||||
if (error == NRF_SUCCESS && sd_enabled && state.current(token)) {
|
||||
// Raw SEV/WFE does not acknowledge the SDK's separate application-IRQ
|
||||
// bookkeeping. First force a real application PendSV while the loop task
|
||||
// remains ready (so the RTOS idle task cannot consume that acknowledgement).
|
||||
// The public SDK contract then makes this one early wait non-blocking.
|
||||
// The pinned S140 6.1.1/7.3.0 forwarding/wait code confirms its private IRQ
|
||||
// sequence cannot wrap back to the last acknowledged value. Do not access
|
||||
// those SDK-private counters or rely on SEV alone to release an SDK wait.
|
||||
taskYIELD();
|
||||
if (!application_context() || (SCB->ICSR & SCB_ICSR_PENDSVSET_Msk)) {
|
||||
error = NRF_ERROR_INVALID_STATE;
|
||||
} else if (state.current(token)) {
|
||||
error = sd_app_evt_wait();
|
||||
}
|
||||
}
|
||||
state.publish(token, sd_enabled != 0, usb_status, error);
|
||||
|
||||
// FreeRTOS yields and the state-query SVC can both leave a stale event.
|
||||
// Clear it ONLY at loop entry, before servicing/checking application work.
|
||||
// A radio interrupt after this point must remain visible to the late wait.
|
||||
__SEV();
|
||||
__WFE();
|
||||
}
|
||||
|
||||
bool NRF52Board::isUsbDataConnected() {
|
||||
#if defined(USE_TINYUSB)
|
||||
#if defined(CFG_TUD_CDC) && CFG_TUD_CDC
|
||||
@@ -498,12 +569,25 @@ bool NRF52Board::isUsbHostConnected() {
|
||||
}
|
||||
|
||||
void NRF52Board::sleep(uint32_t secs) {
|
||||
(void)secs;
|
||||
const bool diagnostics = mesh::powerdiag::enabled();
|
||||
auto& state = mesh_nrf52::sleepCycleState();
|
||||
mesh_nrf52::SleepCycleSnapshot cycle;
|
||||
const bool prepared = state.take(cycle);
|
||||
if (isRadioTestActive()) {
|
||||
if (diagnostics) mesh::powerdiag::boardVeto(mesh::powerdiag::Reason::Board, millis(), 2);
|
||||
delay(1);
|
||||
return;
|
||||
}
|
||||
if (!prepared || !state.current(cycle.generation)) {
|
||||
if (diagnostics) {
|
||||
const uint32_t error = state.lastError();
|
||||
if (error != NRF_SUCCESS) mesh::powerdiag::boardError(static_cast<int32_t>(error), millis());
|
||||
else mesh::powerdiag::boardVeto(mesh::powerdiag::Reason::Board, millis(), 3);
|
||||
}
|
||||
delay(1);
|
||||
return;
|
||||
}
|
||||
// Clear FPU interrupt flags to avoid insomnia
|
||||
// see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
|
||||
#if (__FPU_USED == 1)
|
||||
@@ -514,10 +598,7 @@ void NRF52Board::sleep(uint32_t secs) {
|
||||
|
||||
// On nRF52, we use event-driven sleep instead of timed sleep
|
||||
// The 'secs' parameter is ignored - we wake on any interrupt
|
||||
uint8_t sd_enabled = 0;
|
||||
mesh_nrf52::softdeviceIsEnabled(sd_enabled);
|
||||
|
||||
if (sd_enabled) {
|
||||
if (cycle.softdevice_enabled) {
|
||||
// A single call is required here. If an interrupt arrived since the last
|
||||
// wait, SoftDevice returns immediately so the main loop can service the
|
||||
// flag or BLE queue before sleeping again.
|
||||
@@ -708,6 +789,9 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[], bool force_ap) {
|
||||
Bluefruit.configPrphConn(BLE_GATT_ATT_MTU_DEFAULT,
|
||||
BLE_GAP_EVENT_LENGTH_MIN, 2, 1);
|
||||
|
||||
// Even a failed begin can leave SoftDevice enabled partway through SDK
|
||||
// startup. Require a fresh authoritative query before the next sleep.
|
||||
mesh_nrf52::sleepCycleState().invalidate();
|
||||
if (!Bluefruit.begin(1, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ public:
|
||||
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
|
||||
virtual bool startOTAUpdate(const char *id, char reply[], bool force_ap = false) override;
|
||||
virtual bool stopOTAUpdate(char reply[]) override;
|
||||
// Must precede all work servicing/checks in each application loop. The
|
||||
// authoritative SDK queries and stale-event clear are never done at sleep.
|
||||
void prepareSleepCycle();
|
||||
virtual void sleep(uint32_t secs) override;
|
||||
bool isUserGpioAvailable(uint8_t pin) const override;
|
||||
bool isExternalPowered() override;
|
||||
|
||||
@@ -736,7 +736,27 @@ static size_t writeTinyUsbCdcOnce(void* context, const uint8_t* data,
|
||||
return tud_cdc_n_write(instance, data, attempt);
|
||||
}
|
||||
|
||||
static SingleAttemptNonBlockingStream nonblocking_primary_usb_companion_port(
|
||||
#if defined(ENABLE_USB_INTERFACE) || defined(USE_TINYUSB)
|
||||
// Adafruit's empty CDC available() calls yield(), which sets the Cortex-M
|
||||
// event register through FreeRTOS. Polling an idle console must not recreate
|
||||
// that scheduler event after the loop has prepared its sleep decision.
|
||||
// Only CDC0 is the primary protocol/console; CDC1 keeps its logging owner.
|
||||
class Nrf52PrimaryUsbStream : public SingleAttemptNonBlockingStream {
|
||||
public:
|
||||
using SingleAttemptNonBlockingStream::SingleAttemptNonBlockingStream;
|
||||
|
||||
int available() override {
|
||||
if (!canAccessPrimaryUsbSession(nullptr)) return 0;
|
||||
const int count = static_cast<int>(tud_cdc_n_available(0));
|
||||
// A USB owner callback may have closed the epoch during the FIFO query.
|
||||
return canAccessPrimaryUsbSession(nullptr) ? count : 0;
|
||||
}
|
||||
};
|
||||
#else
|
||||
using Nrf52PrimaryUsbStream = SingleAttemptNonBlockingStream;
|
||||
#endif
|
||||
|
||||
static Nrf52PrimaryUsbStream nonblocking_primary_usb_companion_port(
|
||||
Serial, writeTinyUsbCdcOnce, reinterpret_cast<void*>(uintptr_t{0})
|
||||
#if defined(ENABLE_USB_INTERFACE) || defined(USE_TINYUSB)
|
||||
, canAccessPrimaryUsbSession
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../BluetoothMac.h"
|
||||
#include "../CompanionFrameQueue.h"
|
||||
#include "SoftDeviceState.h"
|
||||
#include "SleepCycleState.h"
|
||||
#include "BleDebugTrace.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -712,6 +713,9 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
|
||||
// task observation incorrectly reports that the BLE/SOC workers never
|
||||
// started. Bluefruit.begin() is the reliable startup result available to
|
||||
// this application.
|
||||
// A partial/failed begin must not leave the current loop's cached
|
||||
// SoftDevice state eligible for bare-metal sleep.
|
||||
mesh_nrf52::sleepCycleState().invalidate();
|
||||
if (!Bluefruit.begin()) {
|
||||
uint8_t softdevice_enabled = 0;
|
||||
const uint32_t softdevice_status =
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh_nrf52 {
|
||||
|
||||
struct SleepCycleSnapshot {
|
||||
uint32_t generation = 0;
|
||||
uint32_t usb_status = 0;
|
||||
bool softdevice_enabled = false;
|
||||
};
|
||||
|
||||
// The application task publishes once at loop entry, before checking work.
|
||||
// A SoftDevice startup attempt can invalidate from another task without
|
||||
// racing the publication. No SDK state is inferred from NVIC/vector state.
|
||||
class SleepCycleState {
|
||||
std::atomic<uint32_t> generation_{0};
|
||||
std::atomic<uint32_t> prepared_generation_{0};
|
||||
std::atomic<uint32_t> usb_status_{0};
|
||||
std::atomic<uint32_t> error_{0};
|
||||
std::atomic<bool> softdevice_enabled_{false};
|
||||
std::atomic<bool> prepared_{false};
|
||||
|
||||
public:
|
||||
uint32_t begin() {
|
||||
const uint32_t token = generation_.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
prepared_.store(false, std::memory_order_release);
|
||||
return token;
|
||||
}
|
||||
|
||||
bool publish(uint32_t token, bool softdevice_enabled, uint32_t usb_status,
|
||||
uint32_t error = 0) {
|
||||
if (!current(token)) return false;
|
||||
usb_status_.store(usb_status, std::memory_order_relaxed);
|
||||
softdevice_enabled_.store(softdevice_enabled, std::memory_order_relaxed);
|
||||
error_.store(error, std::memory_order_relaxed);
|
||||
prepared_generation_.store(token, std::memory_order_relaxed);
|
||||
prepared_.store(error == 0, std::memory_order_release);
|
||||
return current(token);
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
generation_.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool current(uint32_t token) const {
|
||||
return generation_.load(std::memory_order_acquire) == token;
|
||||
}
|
||||
|
||||
bool snapshot(SleepCycleSnapshot& out) const {
|
||||
const uint32_t token = generation_.load(std::memory_order_acquire);
|
||||
if (!prepared_.load(std::memory_order_acquire)) return false;
|
||||
SleepCycleSnapshot candidate;
|
||||
candidate.generation = prepared_generation_.load(std::memory_order_relaxed);
|
||||
candidate.usb_status = usb_status_.load(std::memory_order_relaxed);
|
||||
candidate.softdevice_enabled = softdevice_enabled_.load(std::memory_order_relaxed);
|
||||
if (candidate.generation != token || !current(token)
|
||||
|| !prepared_.load(std::memory_order_acquire)) return false;
|
||||
out = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool take(SleepCycleSnapshot& out) {
|
||||
if (!snapshot(out)) return false;
|
||||
prepared_.store(false, std::memory_order_release);
|
||||
return current(out.generation);
|
||||
}
|
||||
|
||||
uint32_t lastError() const {
|
||||
const uint32_t token = generation_.load(std::memory_order_acquire);
|
||||
const uint32_t prepared_token = prepared_generation_.load(std::memory_order_relaxed);
|
||||
const uint32_t error = error_.load(std::memory_order_relaxed);
|
||||
return prepared_token == token && current(token) ? error : 0;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SleepCycleState) <= 24, "Sleep-cycle cache exceeded its RAM budget");
|
||||
|
||||
inline SleepCycleState& sleepCycleState() {
|
||||
static SleepCycleState value;
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace mesh_nrf52
|
||||
Reference in new issue
Block a user