|
| 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