Observe shared TWI0 IRQ in nRF radio timing captures

This commit is contained in:
mikecarper committed 2026-10-23 04:08:05 -07:00
1 parent 1b97c089a7
commit aa480ae79e
5 files changed
+23 -12

No files matched your search

+2 -1
View File
@@ -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'
+2 -1
View File
@@ -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<uint32_t> 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);
+3 -2
View File
@@ -15,7 +15,8 @@
#include <stdint.h>
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
+3 -3
View File
@@ -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 <cassert>\n#include <cstdint>\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<void(*)()>(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)
+13 -5
View File
@@ -27,7 +27,7 @@ FIXTURE=r'''
#include <helpers/radiolib/ProfileSwitchTiming.h>
@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);