diff --git a/scripts/nrf52_hil_irq_observer.py b/scripts/nrf52_hil_irq_observer.py index d20a4a1d1..110595bbd 100644 --- a/scripts/nrf52_hil_irq_observer.py +++ b/scripts/nrf52_hil_irq_observer.py @@ -2,7 +2,7 @@ The framework's same-object weak vector aliases and its LTO-visible scheduler call bypass GNU --wrap. Only for MESH_HIL_IRQ_TIMING=1, name the wrappers at -those seven call sites explicitly. Keep each real handler, SVC/PendSV vector, +those eight call sites explicitly. Keep each real handler, SVC/PendSV vector, and the naked port's register/exception-return sequence unchanged. The linker wrap options still resolve the observer's __real_* calls to the real handlers. No installed framework source is modified. @@ -15,6 +15,7 @@ from pathlib import Path IRQ_HANDLERS = ( "POWER_CLOCK_IRQHandler", "UARTE0_UART0_IRQHandler", "GPIOTE_IRQHandler", "RTC1_IRQHandler", "USBD_IRQHandler", "UARTE1_IRQHandler", + "SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler", ) WRAPPED_SYMBOLS = IRQ_HANDLERS + ("vTaskSwitchContext",) PORT_CALL = ' " bl vTaskSwitchContext \\n"\n' diff --git a/src/helpers/radiolib/NrfRadioIrqObserver.cpp b/src/helpers/radiolib/NrfRadioIrqObserver.cpp index 895638302..9b3b982b9 100644 --- a/src/helpers/radiolib/NrfRadioIrqObserver.cpp +++ b/src/helpers/radiolib/NrfRadioIrqObserver.cpp @@ -6,7 +6,7 @@ extern "C" { volatile Counter mesh_hil_irq_counters[SourceCount] = {}; volatile uint32_t mesh_hil_irq_selected[TaskKindCount + 2] = {}; volatile Capture mesh_hil_radio_irqs = { - 0x4d434952, 1, sizeof(Capture), 0, SourceCount, 0, 0, 0, 0, 0, {}}; + 0x4d434952, 2, sizeof(Capture), 0, SourceCount, 0, 0, 0, 0, 0, {}}; } std::atomic revision{0}; Work work; @@ -29,6 +29,7 @@ MESH_OBSERVE_IRQ(UARTE0_UART0_IRQHandler, Uart0) MESH_OBSERVE_IRQ(UARTE1_IRQHandler, Uart1) MESH_OBSERVE_IRQ(POWER_CLOCK_IRQHandler, PowerClock) MESH_OBSERVE_IRQ(USBD_IRQHandler, Usb) +MESH_OBSERVE_IRQ(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler, Twim0Spim0) #undef MESH_OBSERVE_IRQ extern "C" void __real_vTaskSwitchContext(void); diff --git a/src/helpers/radiolib/NrfRadioIrqObserver.h b/src/helpers/radiolib/NrfRadioIrqObserver.h index 5369366a8..ef9f380c2 100644 --- a/src/helpers/radiolib/NrfRadioIrqObserver.h +++ b/src/helpers/radiolib/NrfRadioIrqObserver.h @@ -15,7 +15,8 @@ #include namespace mesh { namespace irqobserver { -enum Source : uint32_t { Rtc1, Gpiote, Uart0, Uart1, PowerClock, Usb, Scheduler, SourceCount }; +// Append sources to retain the original per-source indices in captures. +enum Source : uint32_t { Rtc1, Gpiote, Uart0, Uart1, PowerClock, Usb, Scheduler, Twim0Spim0, SourceCount }; enum TaskKind : uint32_t { LoopTask, TimerTask, UsbTask, OtherTask, TaskKindCount }; struct Counter { uint32_t sequence, calls, cycles; }; struct Sample { @@ -186,7 +187,7 @@ inline void finish(uint32_t direction_index, uint32_t result, bool worst) { barrier(); ++capture.sequence; } -static_assert(sizeof(Capture) == 392 && sizeof(Counter) == 12, +static_assert(sizeof(Capture) == 424 && sizeof(Counter) == 12 && SourceCount == 8, "Unexpected IRQ observer ABI"); } } // namespace mesh::irqobserver #endif diff --git a/test/test_nrf52_hil_irq_binding.py b/test/test_nrf52_hil_irq_binding.py index 31b0aba4b..532cebf61 100644 --- a/test/test_nrf52_hil_irq_binding.py +++ b/test/test_nrf52_hil_irq_binding.py @@ -103,7 +103,7 @@ class Nrf52HilIrqBindingTest(unittest.TestCase): self.assertNotIn("__file__", scope) self.assertEqual(len(env.middleware), expected) - def test_startup_changes_only_six_vectors_and_preserves_all_real_aliases(self): + def test_startup_changes_only_selected_vectors_and_preserves_all_real_aliases(self): patched = PATCH.patched_startup(STARTUP.replace("\n", "\r\n")) self.assertEqual(PATCH.patched_startup(patched), patched) expected = STARTUP @@ -185,13 +185,13 @@ class Nrf52HilIrqBindingTest(unittest.TestCase): source = '#include \n#include \n' source += 'extern "C" const uint32_t test_vectors[];\n' source += 'extern "C" void SVC_Handler() {}\nextern "C" void PendSV_Handler() {}\n' - source += "static unsigned real_calls[6], wrapped_calls[6];\n" + source += f"static unsigned real_calls[{len(PATCH.IRQ_HANDLERS)}], wrapped_calls[{len(PATCH.IRQ_HANDLERS)}];\n" for index, symbol in enumerate(PATCH.IRQ_HANDLERS): source += f'extern "C" void {symbol}() {{ ++real_calls[{index}]; }}\n' source += f'extern "C" void __real_{symbol}();\n' source += (f'extern "C" void __wrap_{symbol}() {{ ++wrapped_calls[{index}]; ' f'__real_{symbol}(); }}\n') - source += "int main() { for (unsigned i = 0; i < 6; ++i) { " + source += f"int main() {{ for (unsigned i = 0; i < {len(PATCH.IRQ_HANDLERS)}; ++i) {{ " source += "reinterpret_cast(uintptr_t(test_vectors[i+2]))(); " source += "assert(real_calls[i] == 1); assert(wrapped_calls[i] == EXPECT_WRAPPED); } }\n" (directory / "handlers.cpp").write_text(source) diff --git a/test/test_nrf_radio_irq_observer.py b/test/test_nrf_radio_irq_observer.py index 9ce3cd5ec..15a91acc3 100644 --- a/test/test_nrf_radio_irq_observer.py +++ b/test/test_nrf_radio_irq_observer.py @@ -27,7 +27,7 @@ FIXTURE=r''' #include @TIMING_GLOBALS@ DwtMock dwt; -unsigned real_calls[7]{},durations[7]={640,128,192,256,320,384,448}; +unsigned real_calls[8]{},durations[8]={640,128,192,256,320,384,448,512}; bool nested=false; const char* selected_task_name="loop"; const char* pcTaskGetName(void* task) { assert(task==nullptr);return selected_task_name; } @@ -38,6 +38,7 @@ extern "C" void __wrap_UARTE1_IRQHandler(); extern "C" void __wrap_POWER_CLOCK_IRQHandler(); extern "C" void __wrap_USBD_IRQHandler(); extern "C" void __wrap_vTaskSwitchContext(); +extern "C" void __wrap_SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(); extern "C" void __real_RTC1_IRQHandler() { ++real_calls[0];dwt.CYCCNT+=durations[0]; if(nested) __wrap_GPIOTE_IRQHandler(); @@ -49,15 +50,19 @@ REAL_HANDLER(UARTE1_IRQHandler,3) REAL_HANDLER(POWER_CLOCK_IRQHandler,4) REAL_HANDLER(USBD_IRQHandler,5) REAL_HANDLER(vTaskSwitchContext,6) +REAL_HANDLER(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler,7) int main() { namespace obs=mesh::irqobserver; namespace timing=mesh::profiletiming; auto& capture=obs::mesh_hil_radio_irqs; - assert(capture.magic==0x4d434952 && capture.version==1 && capture.size_bytes==392 && capture.source_count==7); + assert(capture.magic==0x4d434952 && capture.version==2 && capture.size_bytes==424 && capture.source_count==8); + static_assert(obs::Scheduler==6 && obs::Twim0Spim0==7,"Original indices must remain stable"); + static_assert(sizeof(obs::mesh_hil_irq_counters)==96,"IRQ counter bank size changed"); // Actual wrappers forward every source exactly once, with no active hop. __wrap_UARTE0_UART0_IRQHandler();__wrap_UARTE1_IRQHandler(); __wrap_POWER_CLOCK_IRQHandler();__wrap_USBD_IRQHandler();__wrap_vTaskSwitchContext(); - for(unsigned i=2;i<7;++i) assert(real_calls[i]==1); + __wrap_SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(); + for(unsigned i=2;i<8;++i) assert(real_calls[i]==1); // Nested source time is explicitly inclusive in its parent; deltas remain // per source and are attached to the same worst successful physical hop. dwt.CYCCNT=100;durations[0]=640;durations[1]=128;nested=true; @@ -94,9 +99,12 @@ int main() { obs::mesh_hil_irq_counters[obs::Uart1].cycles=UINT32_MAX-10; dwt.CYCCNT=UINT32_MAX-31;durations[3]=64; timing::begin(1,0,timing::readClock());__wrap_UARTE1_IRQHandler(); + __wrap_SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(); timing::finish(timing::Success,timing::readEndpointClock()); - assert(capture.direction[1].last_ticks==64 && capture.direction[1].last.calls[obs::Uart1]==1); + assert(capture.direction[1].last_ticks==576 && capture.direction[1].last.calls[obs::Uart1]==1); assert(capture.direction[1].last.cycles[obs::Uart1]==64); + assert(capture.direction[1].last.calls[obs::Twim0Spim0]==1 && capture.direction[1].last.cycles[obs::Twim0Spim0]==512); + assert(capture.direction[1].worst.calls[obs::Twim0Spim0]==1 && capture.direction[1].worst.cycles[obs::Twim0Spim0]==512); // A permanently odd writer sequence cannot hang the application or publish // stale counter data as valid attribution. Timing itself remains available. obs::mesh_hil_irq_counters[obs::Gpiote].sequence=1; @@ -106,7 +114,7 @@ int main() { dwt.CYCCNT+=128; timing::finish(timing::Success,timing::readEndpointClock()); assert(capture.snapshot_failures==failures+1 && !capture.direction[1].last_valid); - for(unsigned i=0;i<7;++i) { + for(unsigned i=0;i<8;++i) { assert(capture.direction[1].last.calls[i]==0 && capture.direction[1].last.cycles[i]==0); } assert(capture.sequence%2==0 && capture.snapshots==10 && capture.retries>=8);