Keep native USB responsive during ESP32 emergency radio sleep

This commit is contained in:
mikecarper committed 2026-10-28 03:05:05 -07:00
1 parent 3385f2acaa
commit 5cbff19463
2 files changed
+41 -4

No files matched your search

+15 -4
View File
@@ -1413,12 +1413,15 @@ void ESP32Board::powerOff() {
#if MESH_ENABLE_EMERGENCY_POWER
bool ESP32Board::emergencySleepMilliseconds(uint32_t millisecs) {
// The caller owns radio suspension and reversible wireless shutdown. Never
// force a live WiFi/Bluetooth stack through explicit light sleep.
if (millisecs == 0 || isRadioTestActive() || xPortInIsrContext()
|| xTaskGetSchedulerState() != taskSCHEDULER_RUNNING
|| !mesh::esp32::manualLightSleepReady()) return false;
|| xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) return false;
// Native USB loses its connection in light sleep, even without an open CDC
// reader. Retain the ordinary physical-host/loss-grace policy here too.
// RF remains suspended, but an attached host gets frequent command service.
const bool usb_held = isUsbSleepHeld();
if (!usb_held && !mesh::esp32::manualLightSleepReady()) return false;
if (millisecs > 10000UL) millisecs = 10000UL;
if (usb_held && millisecs > 100UL) millisecs = 100UL;
const esp_err_t watchdog_status = esp_task_wdt_status(nullptr);
const bool watched = watchdog_status == ESP_OK;
if (!watched && watchdog_status != ESP_ERR_NOT_FOUND
@@ -1427,7 +1430,15 @@ bool ESP32Board::emergencySleepMilliseconds(uint32_t millisecs) {
if (watched && millisecs > uint32_t(CONFIG_ESP_TASK_WDT_TIMEOUT_S) * 500UL)
millisecs = uint32_t(CONFIG_ESP_TASK_WDT_TIMEOUT_S) * 500UL;
#endif
const TickType_t usb_ticks = usb_held ? pdMS_TO_TICKS(millisecs) : 0;
if (usb_held && usb_ticks == 0) return false;
if (watched && esp_task_wdt_reset() != ESP_OK) return false;
if (usb_held) {
// This also remains safe during asynchronous wireless teardown. Do not
// arm or clear wake sources while yielding with the USB peripheral live.
vTaskDelay(usb_ticks);
return !watched || esp_task_wdt_reset() == ESP_OK;
}
if (esp_sleep_enable_timer_wakeup(uint64_t(millisecs) * 1000ULL) != ESP_OK)
return false;
// Add no radio/GPIO wake. Existing button sources remain untouched. The
+26
View File
@@ -22,6 +22,7 @@ HARNESS = r'''
#include <vector>
static std::vector<std::string> events;
static bool radio_test = false, interrupt_context = false, wireless_ready = true;
static bool usb_held = false;
static unsigned primask = 0, basepri = 0, faultmask = 0;
static unsigned __get_IPSR() { return interrupt_context; }
static unsigned __get_PRIMASK() { return primask; }
@@ -102,6 +103,7 @@ struct NRF52Board : Base {
};
struct ESP32Board : Base {
bool isRadioTestActive() const { return radio_test; }
bool isUsbSleepHeld() const { return usb_held; }
@ESP_SUPPORT@
bool emergencySleepMilliseconds(uint32_t millisecs) override;
};
@@ -109,6 +111,7 @@ struct ESP32Board : Base {
@ESP_SLEEP@
static void reset() {
events.clear(); radio_test = interrupt_context = false; wireless_ready = true;
usb_held = false;
primask = basepri = faultmask = 0; nrf_nvic_state.__cr_flag = 0;
scheduler = taskSCHEDULER_RUNNING;
tick_ms = 1; delayed_ticks = nrf_feeds = 0;
@@ -165,6 +168,29 @@ int main() {
reset(); wireless_ready = false;
assert(!esp.emergencySleepMilliseconds(100)); assert(events.empty());
reset(); usb_held = true; wireless_ready = false; timer_us = 555;
assert(esp.emergencySleepMilliseconds(UINT32_MAX));
assert(delayed_ticks == 100 && events == std::vector<std::string>{"delay"});
assert(!timer_calls && !sleep_calls && !clear_calls && timer_us == 555);
assert(other_wake_sources == 0x71); // USB, RF and existing wake ownership are untouched.
usb_held = false; events.clear();
assert(!esp.emergencySleepMilliseconds(100) && events.empty()); // Teardown is still live.
wireless_ready = true;
assert(esp.emergencySleepMilliseconds(100)); // Recheck the host on every call; detach permits light sleep.
assert(last_armed_us == 100000 && sleep_calls == 1 && !timer_us);
reset(); usb_held = true; task_watchdog_status = ESP_OK;
assert(esp.emergencySleepMilliseconds(250));
assert(delayed_ticks == 100 && task_watchdog_feeds == 2);
assert((events == std::vector<std::string>{"task_feed", "delay", "task_feed"}));
assert(!timer_calls && !sleep_calls && !clear_calls);
reset(); usb_held = true; tick_ms = 1000;
assert(!esp.emergencySleepMilliseconds(100) && events.empty());
for (unsigned failure : {1U, 2U}) {
reset(); usb_held = true; task_watchdog_status = ESP_OK; fail_watchdog_feed = failure;
assert(!esp.emergencySleepMilliseconds(100));
assert(delayed_ticks == (failure == 1 ? 0U : 100U));
assert(!timer_calls && !sleep_calls && !clear_calls);
}
reset(); task_watchdog_status = 99;
assert(!esp.emergencySleepMilliseconds(100)); assert(events.empty());
reset(); assert(esp.emergencySleepMilliseconds(UINT32_MAX));