mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-27 00:39:56 +00:00
Replay initial nRF52 USB READY after application handoff
This commit is contained in:
@@ -24,6 +24,10 @@ framework implementation stops the build for review. The backport:
|
||||
- rechecks attachment while waiting, including completion by another callback;
|
||||
- bounds both waits with one 100,000-poll budget, independent of OS ticks;
|
||||
- ignores stale READY on a disabled peripheral and exits if it is removed.
|
||||
- replays an already asserted regulator READY state when the application USB
|
||||
task starts with VBUS present, as the bootloader does. The framework's
|
||||
original startup replayed DETECTED only, so a missing later power edge could
|
||||
leave the new application's USB transport unattached after a UF2 handoff.
|
||||
|
||||
The poll budget is an iteration limit, not a promised elapsed-time timeout.
|
||||
If the clock/peripheral never becomes ready, the caller returns instead of
|
||||
@@ -36,6 +40,7 @@ patched handler from the pinned-framework fixture, model W1C event semantics,
|
||||
and cover duplicate/nested callbacks, delayed/missing clocks and READY, removal,
|
||||
retry, and detached USB. Restoring the inherited READY prefix must reproduce
|
||||
the infinite wait under a subprocess deadline. Additional tests check SDK
|
||||
isolation, idempotence, fail-closed patching, and all nRF52 environment hooks.
|
||||
isolation, idempotence, fail-closed patching, the startup DETECTED/READY order,
|
||||
and all nRF52 environment hooks.
|
||||
The harness is shared with OTAFIX's TinyUSB fork at
|
||||
`test/otafix/nrf5x_power_test.py`; keep the two copies in sync when extending it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Compile a build-local fix for the framework's inherited nRF52 USB READY hang.
|
||||
"""Compile build-local fixes for the framework's nRF52 USB power startup.
|
||||
|
||||
Never modify PlatformIO's shared framework package. Fail closed if a framework
|
||||
update changes the code this narrow backport expects. The same handler policy
|
||||
@@ -60,6 +60,22 @@ ATTACH = """ // Enable pull up
|
||||
|
||||
case USB_EVT_REMOVED:"""
|
||||
|
||||
OLD_PORT_INIT = """ if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) {
|
||||
tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED);
|
||||
}
|
||||
}
|
||||
"""
|
||||
PORT_INIT = """ if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) {
|
||||
tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED);
|
||||
}
|
||||
// On an application handoff, VBUS and the regulator may already be ready.
|
||||
// The power driver only reports future edges, so replay both initial states.
|
||||
if (usb_reg & POWER_USBREGSTATUS_OUTPUTRDY_Msk) {
|
||||
tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_READY);
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def patched_source(source):
|
||||
source = source.replace("\r\n", "\n")
|
||||
@@ -73,10 +89,24 @@ def patched_source(source):
|
||||
return source.replace(OLD_READY, READY).replace(OLD_ATTACH, ATTACH)
|
||||
|
||||
|
||||
def patched_port_source(source):
|
||||
source = source.replace("\r\n", "\n")
|
||||
if PORT_INIT in source and OLD_PORT_INIT not in source:
|
||||
return source
|
||||
if source.count(OLD_PORT_INIT) != 1:
|
||||
raise RuntimeError(
|
||||
"nRF52 USB power fix: unrecognized USB startup; review the "
|
||||
"framework update before building (shared SDK was not modified)"
|
||||
)
|
||||
return source.replace(OLD_PORT_INIT, PORT_INIT)
|
||||
|
||||
|
||||
def replace_driver(build_env, node):
|
||||
# SCons passes a not-yet-created VariantDir node, not the SDK source path.
|
||||
source = Path(node.srcnode().get_abspath())
|
||||
patched = patched_source(source.read_text(encoding="utf-8"))
|
||||
patcher = (patched_port_source if source.name == "Adafruit_TinyUSB_nrf.cpp"
|
||||
else patched_source)
|
||||
patched = patcher(source.read_text(encoding="utf-8"))
|
||||
destination = Path(build_env.subst("$BUILD_DIR")) / "patched-nrf52-usb" / source.name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not destination.exists() or destination.read_text(encoding="utf-8") != patched:
|
||||
@@ -90,6 +120,10 @@ def install(build_env):
|
||||
replace_driver,
|
||||
"*Adafruit_TinyUSB_Arduino*src*portable*nordic*nrf5x*dcd_nrf5x.c",
|
||||
)
|
||||
build_env.AddBuildMiddleware(
|
||||
replace_driver,
|
||||
"*Adafruit_TinyUSB_Arduino*src*arduino*ports*nrf*Adafruit_TinyUSB_nrf.cpp",
|
||||
)
|
||||
|
||||
|
||||
if "Import" in globals():
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import configparser
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
@@ -15,6 +17,32 @@ SPEC = importlib.util.spec_from_file_location("usb_fix", ROOT / "scripts/nrf52_u
|
||||
FIX = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(FIX)
|
||||
ORIGINAL = (ROOT / "test/fixtures/nrf52_usb_power_original.c").read_text()
|
||||
PORT_SOURCE = """
|
||||
typedef struct { unsigned USBREGSTATUS; } PowerRegisters;
|
||||
static PowerRegisters registers;
|
||||
#define NRF_POWER (®isters)
|
||||
#define POWER_USBREGSTATUS_VBUSDETECT_Msk 1u
|
||||
#define POWER_USBREGSTATUS_OUTPUTRDY_Msk 2u
|
||||
enum { NRFX_POWER_USB_EVT_DETECTED = 0, NRFX_POWER_USB_EVT_READY = 2 };
|
||||
static unsigned events[2], event_count;
|
||||
static void tusb_hal_nrf_power_event(unsigned event) { events[event_count++] = event; }
|
||||
static void usb_hardware_init(void) {
|
||||
unsigned usb_reg = NRF_POWER->USBREGSTATUS;
|
||||
if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) {
|
||||
tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
for (unsigned status = 0; status < 4; ++status) {
|
||||
registers.USBREGSTATUS = status;
|
||||
event_count = 0;
|
||||
usb_hardware_init();
|
||||
if (event_count != ((status & 1u) != 0u) + ((status & 2u) != 0u)) return 1;
|
||||
if (status & 1u && events[0] != NRFX_POWER_USB_EVT_DETECTED) return 2;
|
||||
if (status & 2u && events[event_count - 1] != NRFX_POWER_USB_EVT_READY) return 3;
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class UsbPowerTests(unittest.TestCase):
|
||||
@@ -26,6 +54,26 @@ class UsbPowerTests(unittest.TestCase):
|
||||
self.assertEqual(fixed, FIX.patched_source(ORIGINAL.replace("\n", "\r\n")))
|
||||
self.assertEqual(fixed, FIX.patched_source(fixed))
|
||||
|
||||
def test_initial_ready_is_replayed_after_detected(self):
|
||||
compiler = shutil.which("c++")
|
||||
if compiler is None:
|
||||
self.skipTest("C++ compiler unavailable")
|
||||
patched = FIX.patched_port_source(PORT_SOURCE)
|
||||
self.assertEqual(patched, FIX.patched_port_source(patched))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
source = Path(directory) / "usb_port.cpp"
|
||||
program = Path(directory) / "usb_port"
|
||||
source.write_text(patched)
|
||||
subprocess.run([compiler, "-std=c++11", "-o", str(program), str(source)],
|
||||
check=True, capture_output=True)
|
||||
subprocess.run([str(program)], check=True, capture_output=True)
|
||||
|
||||
def test_unrecognized_usb_port_fails_closed(self):
|
||||
for source in ("", PORT_SOURCE.replace("USBREGSTATUS", "STATUS"),
|
||||
PORT_SOURCE + PORT_SOURCE):
|
||||
with self.subTest(source=source[:40]), self.assertRaises(RuntimeError):
|
||||
FIX.patched_port_source(source)
|
||||
|
||||
def test_unrecognized_or_partly_fixed_driver_fails_closed(self):
|
||||
for source in ("", ORIGINAL.replace("hfclk_running()", "clock_running()"),
|
||||
ORIGINAL + ORIGINAL, ORIGINAL.replace(FIX.OLD_READY, FIX.READY)):
|
||||
|
||||
Reference in New Issue
Block a user