Skip to content

Commit fc33a71

Browse files
committed
adapt #10899 to main
1 parent e512a25 commit fc33a71

3 files changed

Lines changed: 141 additions & 7 deletions

File tree

ports/espressif/common-hal/alarm/SleepMemory.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,44 @@
1616
// Data storage for singleton instance of SleepMemory.
1717
// Might be RTC_SLOW_MEM or RTC_FAST_MEM, depending on setting of CONFIG_ESP32S2_RTCDATA_IN_FAST_MEM.
1818
#if defined(CONFIG_SOC_RTC_FAST_MEM_SUPPORTED) || defined(CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED)
19-
static RTC_DATA_ATTR uint8_t _sleep_mem[SLEEP_MEMORY_LENGTH];
19+
static RTC_NOINIT_ATTR uint8_t _sleep_mem[SLEEP_MEMORY_LENGTH];
20+
21+
void alarm_sleep_memory_reset(void) {
22+
// With RTC_NOINIT_ATTR, the bootloader does not initialize sleep memory.
23+
// Preserve contents on resets where the RTC domain stays powered (software
24+
// reset, watchdog, panic, deep sleep wake). Clear on everything else —
25+
// after power-on, SRAM contents are undefined; after brown-out, the RTC
26+
// domain was reset by hardware (System Reset scope per TRM Figure 6.1-1).
27+
esp_reset_reason_t reason = esp_reset_reason();
28+
switch (reason) {
29+
case ESP_RST_SW: // microcontroller.reset() / esp_restart()
30+
case ESP_RST_DEEPSLEEP: // deep sleep wake
31+
case ESP_RST_PANIC: // unhandled exception
32+
case ESP_RST_INT_WDT: // interrupt watchdog
33+
case ESP_RST_TASK_WDT: // task watchdog
34+
case ESP_RST_WDT: // other watchdog
35+
// RTC domain was not reset — sleep memory is intact.
36+
break;
37+
default:
38+
// Power-on, brown-out, unknown, or any other reason where
39+
// RTC SRAM contents may be undefined. Clear to zero.
40+
memset(_sleep_mem, 0, sizeof(_sleep_mem));
41+
break;
42+
}
43+
}
44+
2045
#else
46+
2147
// Chips without RTC memory can't persist SleepMemory across deep sleep.
2248
static uint8_t _sleep_mem[SLEEP_MEMORY_LENGTH];
23-
#endif
2449

2550
void alarm_sleep_memory_reset(void) {
26-
// ESP-IDF build system takes care of doing esp_sleep_pd_config() or the equivalent with
27-
// the correct settings, depending on which RTC mem we are using.
28-
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/sleep_modes.html#power-down-of-rtc-peripherals-and-memories
51+
// Do nothing for chips without RTC memory.
2952
}
3053

54+
#endif
55+
56+
3157
uint32_t common_hal_alarm_sleep_memory_get_length(alarm_sleep_memory_obj_t *self) {
3258
return sizeof(_sleep_mem);
3359
}

shared-bindings/alarm/SleepMemory.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@
1212
#include "shared-bindings/alarm/SleepMemory.h"
1313

1414
//| class SleepMemory:
15-
//| """Store raw bytes in RAM that persists during deep sleep.
15+
//| """Store raw bytes in RAM that persists across deep sleep and software resets.
1616
//| The class acts as a ``bytearray``.
17-
//| If power is lost, the memory contents are lost.
17+
//| Contents are preserved across ``microcontroller.reset()``, watchdog resets,
18+
//| and deep sleep wake. Contents are lost when power is removed and restored,
19+
//| or on brown-out reset.
20+
//|
21+
//| .. note::
22+
//| Programs that call ``microcontroller.reset()`` should wait at least
23+
//| one second after boot before resetting, otherwise CircuitPython's
24+
//| double-reset safe mode detector may activate. See
25+
//| ``supervisor/shared/safe_mode.c``.
1826
//|
1927
//| Note that this class can't be imported and used directly. The sole
2028
//| instance of :class:`SleepMemory` is available at
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Reproducer for https://github.com/adafruit/circuitpython/issues/10896
2+
3+
Copy to code.py. Sequences through test steps using sleep_memory to
4+
track state.
5+
6+
The test scoreboard is printed to the magtag e-ink display after every
7+
run so the final display shows cumulative results.
8+
"""
9+
10+
import alarm
11+
import binascii
12+
import microcontroller
13+
import struct
14+
import supervisor
15+
import time
16+
17+
# Safe mode avoidance
18+
# On boot CircuitPython writes a SAFE_MODE_USER guard to an RTC register,
19+
# If a second boot occurs before 1000ms elapses then we enter safe mode.
20+
# So, wait for 1000+1ms before calling microcontroller.reset().
21+
_SAFE_MODE_WINDOW_MS = 1000
22+
23+
24+
def _wait_for_safe_mode_window():
25+
elapsed_ms = time.monotonic() * 1000
26+
remaining = _SAFE_MODE_WINDOW_MS + 1 - elapsed_ms
27+
if remaining > 0:
28+
time.sleep(remaining / 1000)
29+
30+
31+
# Test result enum
32+
_UNTESTED = 0
33+
_PASS = 1
34+
_FAIL = 2
35+
_LABEL = {_UNTESTED: "-", _PASS: "PASS", _FAIL: "FAIL"}
36+
37+
# CRC32-protected state in sleep_memory
38+
# [magic:2][step:1][r_reset:1][r_reload:1][pad:1][crc32:4]
39+
_MAGIC = 0xBE01
40+
_FMT = "<HBBBx"
41+
_DATA_SZ = struct.calcsize(_FMT) # 6
42+
_TOTAL = _DATA_SZ + 4 # 10
43+
44+
45+
def _write(step, r_reset=_UNTESTED, r_reload=_UNTESTED):
46+
data = struct.pack(_FMT, _MAGIC, step, r_reset, r_reload)
47+
crc = struct.pack("<I", binascii.crc32(data) & 0xFFFFFFFF)
48+
alarm.sleep_memory[0:_TOTAL] = data + crc
49+
50+
51+
def _read():
52+
raw = bytes(alarm.sleep_memory[0:_TOTAL])
53+
data = raw[:_DATA_SZ]
54+
stored_crc = struct.unpack_from("<I", raw, _DATA_SZ)[0]
55+
if (binascii.crc32(data) & 0xFFFFFFFF) != stored_crc:
56+
return (0, _UNTESTED, _UNTESTED, False)
57+
magic, step, r_reset, r_reload = struct.unpack(_FMT, data)
58+
if magic != _MAGIC:
59+
return (0, _UNTESTED, _UNTESTED, False)
60+
return (step, r_reset, r_reload, True)
61+
62+
63+
def _scoreboard(r_reset, r_reload):
64+
print(f" reset: {_LABEL[r_reset]}")
65+
print(f" reload: {_LABEL[r_reload]}")
66+
67+
68+
# Main
69+
reason = microcontroller.cpu.reset_reason
70+
step, r_reset, r_reload, valid = _read()
71+
72+
if reason == microcontroller.ResetReason.POWER_ON:
73+
_scoreboard(_UNTESTED, _UNTESTED)
74+
print("writing marker, resetting...")
75+
_write(0)
76+
_wait_for_safe_mode_window()
77+
microcontroller.reset()
78+
79+
elif valid and step == 0:
80+
r_reset = _PASS
81+
_scoreboard(r_reset, _UNTESTED)
82+
print("testing supervisor.reload()...")
83+
_write(1, r_reset)
84+
_wait_for_safe_mode_window()
85+
supervisor.reload()
86+
87+
elif not valid and step == 0:
88+
r_reset = _FAIL
89+
_scoreboard(r_reset, _UNTESTED)
90+
print("Install patched FW, power cycle")
91+
92+
elif valid and step == 1:
93+
r_reload = _PASS
94+
_scoreboard(r_reset, r_reload)
95+
_write(2, r_reset, r_reload)
96+
97+
else:
98+
_scoreboard(r_reset, r_reload)
99+
print(f"unexpected: step={step} valid={valid}")
100+
print("Power cycle to restart tests")

0 commit comments

Comments
 (0)