Skip to content

Commit 66f384f

Browse files
authored
Merge pull request #181 from f9micro/optimize-interrupt
Add BASEPRI zero-latency ISR support
2 parents 6ec8361 + 61e5798 commit 66f384f

File tree

10 files changed

+546
-36
lines changed

10 files changed

+546
-36
lines changed

include/platform/irq-latency.h

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/* Copyright (c) 2026 The F9 Microkernel Project. All rights reserved.
2+
* Use of this source code is governed by a BSD-style license that can be
3+
* found in the LICENSE file.
4+
*/
5+
6+
#ifndef PLATFORM_IRQ_LATENCY_H_
7+
#define PLATFORM_IRQ_LATENCY_H_
8+
9+
#include <stdint.h>
10+
11+
/* DWT (Data Watchpoint and Trace) registers for cycle counting */
12+
#define DWT_CTRL ((volatile uint32_t *) 0xE0001000)
13+
#define DWT_CYCCNT ((volatile uint32_t *) 0xE0001004)
14+
#define DWT_CTRL_CYCCNTENA (1 << 0)
15+
16+
#define DEMCR ((volatile uint32_t *) 0xE000EDFC)
17+
#define DEMCR_TRCENA (1 << 24)
18+
19+
/**
20+
* @file irq_latency.h
21+
* @brief Interrupt latency measurement and profiling infrastructure
22+
*
23+
* Provides cycle-accurate latency tracking for zero-latency ISRs and
24+
* standard IRQs. Enables validation of BASEPRI-based zero-latency
25+
* interrupt performance (<10 cycle target).
26+
*
27+
* Usage:
28+
* 1. Call latency_sample_start() at ISR entry
29+
* 2. Call latency_sample_end(priority, irq_num) at ISR exit
30+
* 3. View statistics via KDB 'L' command
31+
*/
32+
33+
/**
34+
* Latency statistics per interrupt priority level.
35+
*/
36+
typedef struct {
37+
uint32_t count; /* Number of samples */
38+
uint32_t min; /* Minimum latency (cycles) */
39+
uint32_t max; /* Maximum latency (cycles) */
40+
uint32_t sum; /* Sum for average calculation */
41+
uint32_t avg; /* Average latency (cycles) */
42+
} latency_stats_t;
43+
44+
/**
45+
* Get current cycle count from DWT_CYCCNT.
46+
* Returns 0 if DWT is not enabled.
47+
*/
48+
static inline uint32_t get_cycle_count(void)
49+
{
50+
return *DWT_CYCCNT;
51+
}
52+
53+
/**
54+
* Enable DWT cycle counter for latency measurements.
55+
* Called during system initialization.
56+
*/
57+
void latency_init(void);
58+
59+
/**
60+
* Record latency sample for an interrupt.
61+
*
62+
* @param priority Interrupt priority (0x0-0xF)
63+
* @param irq_num IRQ number (-15 to 239)
64+
* @param cycles Measured latency in cycles
65+
*/
66+
void latency_record(uint8_t priority, int16_t irq_num, uint32_t cycles);
67+
68+
/**
69+
* Get latency statistics for a priority level.
70+
*
71+
* @param priority Interrupt priority (0x0-0xF)
72+
* @return Pointer to statistics structure
73+
*/
74+
const latency_stats_t *latency_get_stats(uint8_t priority);
75+
76+
/**
77+
* Get a best-effort atomic snapshot of latency statistics.
78+
*
79+
* Uses relaxed atomics only; intended for diagnostic reads outside ISR
80+
* context. Returns 1 on success, 0 on invalid input.
81+
*/
82+
int latency_get_stats_snapshot(uint8_t priority, latency_stats_t *out);
83+
84+
/**
85+
* Reset all latency statistics.
86+
*/
87+
void latency_reset(void);
88+
89+
/**
90+
* Get interrupt number from IPSR.
91+
* Returns 0 for thread mode, 1-15 for exceptions, 16+ for IRQs.
92+
*/
93+
static inline uint32_t get_irq_number(void)
94+
{
95+
uint32_t ipsr;
96+
__asm__ __volatile__("mrs %0, ipsr" : "=r"(ipsr));
97+
return ipsr & 0x1FF;
98+
}
99+
100+
/**
101+
* Latency measurement helper - call at ISR entry.
102+
* Returns timestamp for latency_sample_end().
103+
*/
104+
static inline uint32_t latency_sample_start(void)
105+
{
106+
return get_cycle_count();
107+
}
108+
109+
/**
110+
* Latency measurement helper - call at ISR exit.
111+
*
112+
* @param start_cycles Timestamp from latency_sample_start()
113+
* @param priority Interrupt priority level
114+
* @param irq_num IRQ number from IPSR
115+
*/
116+
static inline void latency_sample_end(uint32_t start_cycles,
117+
uint8_t priority,
118+
int16_t irq_num)
119+
{
120+
uint32_t end_cycles = get_cycle_count();
121+
uint32_t elapsed = end_cycles - start_cycles;
122+
latency_record(priority, irq_num, elapsed);
123+
}
124+
125+
#endif /* PLATFORM_IRQ_LATENCY_H_ */

include/platform/irq.h

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,34 @@
1515

1616
void irq_init(void);
1717

18+
/*
19+
* Interrupt Priority Levels (ARM Cortex-M 4-bit priorities)
20+
*/
21+
#define IRQ_PRIO_ZERO_LATENCY_MAX 0x2 /* Highest priority, never masked */
22+
#define IRQ_PRIO_SYSTICK 0x3 /* System timer */
23+
#define IRQ_PRIO_KERNEL_MASK 0x40 /* BASEPRI mask (0x4 << 4) */
24+
#define IRQ_PRIO_USER_DEFAULT 0x8 /* Default user IRQ priority */
25+
#define IRQ_PRIO_LOWEST 0xF /* SVCall, PendSV */
26+
27+
/*
28+
* System state tracking for ISR context.
29+
* 0 = Thread mode (PSP), 1+ = Handler mode (MSP, tracks nesting depth).
30+
*/
31+
extern volatile uint32_t irq_system_state;
32+
33+
/*
34+
* Fast ISR context check using hardware IPSR register.
35+
* Returns: true if currently in exception handler, false if in thread mode.
36+
* Zero overhead: Single MRS instruction, no memory access or race conditions.
37+
*/
38+
static inline bool in_isr_context(void)
39+
{
40+
return IPSR() != 0;
41+
}
42+
43+
/*
44+
* PRIMASK-based critical sections (blocks ALL interrupts).
45+
*/
1846
static inline void irq_disable(void)
1947
{
2048
__asm__ __volatile__("cpsid i" ::: "memory");
@@ -45,6 +73,53 @@ static inline void irq_restore_flags(uint32_t flags)
4573
__asm__ __volatile__("msr primask, %0" ::"r"(flags) : "memory");
4674
}
4775

76+
/*
77+
* BASEPRI-based critical sections (blocks interrupts >= priority level).
78+
* Zero-latency ISRs at priority 0x0-0x2 can preempt kernel critical sections.
79+
*/
80+
static inline void irq_disable_below(uint8_t priority)
81+
{
82+
uint32_t basepri = (priority << 4) & 0xFF;
83+
__asm__ __volatile__("msr basepri, %0" ::"r"(basepri) : "memory");
84+
}
85+
86+
static inline void irq_enable_all(void)
87+
{
88+
__asm__ __volatile__("msr basepri, %0" ::"r"(0) : "memory");
89+
}
90+
91+
static inline uint32_t irq_save_basepri(uint8_t priority)
92+
{
93+
uint32_t prev_basepri;
94+
uint32_t new_basepri = (priority << 4) & 0xFF;
95+
__asm__ __volatile__(
96+
"mrs %0, basepri\n\t"
97+
"msr basepri, %1"
98+
: "=r"(prev_basepri)
99+
: "r"(new_basepri)
100+
: "memory");
101+
return prev_basepri;
102+
}
103+
104+
static inline void irq_restore_basepri(uint32_t basepri)
105+
{
106+
__asm__ __volatile__("msr basepri, %0" ::"r"(basepri) : "memory");
107+
}
108+
109+
/*
110+
* Kernel critical section (masks interrupts >= 0x4, allows 0x0-0x3).
111+
* Use this as the default for scheduler, IPC, and memory operations.
112+
*/
113+
static inline uint32_t irq_kernel_critical_enter(void)
114+
{
115+
return irq_save_basepri(IRQ_PRIO_KERNEL_MASK >> 4);
116+
}
117+
118+
static inline void irq_kernel_critical_exit(uint32_t basepri)
119+
{
120+
irq_restore_basepri(basepri);
121+
}
122+
48123
static inline void irq_svc(void)
49124
{
50125
__asm__ __volatile__("svc #0");
@@ -242,7 +317,6 @@ extern volatile uint32_t __irq_saved_regs[8];
242317
request_schedule(); \
243318
irq_return(); \
244319
}
245-
246320
extern volatile tcb_t *current;
247321

248322
#endif /* PLATFORM_IRQ_H_ */

kernel/build.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ kernel-y = \
2121
interrupt.o
2222

2323
KDB-$(CONFIG_KDB) = \
24-
kdb.o
24+
kdb.o \
25+
kdb-latency.o
2526

2627
KPROBES-$(CONFIG_KPROBES) = \
2728
kprobes.o

kernel/ipc.c

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,19 @@ static void do_ipc(tcb_t *from, tcb_t *to)
214214
* CONSTRAINT: Callback MUST NOT destroy its own TCB.
215215
*/
216216
if (to->ipc_notify && to->notify_pending && to->notify_depth < 3) {
217-
uint32_t irq_flags;
217+
uint32_t basepri;
218218
uint8_t generation_before;
219219
notify_handler_t callback;
220220

221221
/* Atomically increment depth and capture generation.
222-
* IRQ masking prevents race with nested interrupt-driven IPC.
222+
* BASEPRI masking prevents race with nested interrupt-driven IPC.
223+
* Zero-latency ISRs (0x0-0x2) can still preempt during this operation.
223224
*/
224-
irq_flags = irq_save_flags();
225+
basepri = irq_kernel_critical_enter();
225226
to->notify_depth++;
226227
generation_before = to->notify_generation;
227228
callback = to->ipc_notify;
228-
irq_restore_flags(irq_flags);
229+
irq_kernel_critical_exit(basepri);
229230

230231
/* Recursion protection: prevent unbounded callback nesting.
231232
* Max depth 3 allows: serial → network → timer notification chains.
@@ -245,11 +246,27 @@ static void do_ipc(tcb_t *from, tcb_t *to)
245246
/* Atomically decrement depth only if TCB still valid.
246247
* Generation counter detects TCB destruction during callback.
247248
* If TCB was destroyed, skip depth decrement (would be use-after-free).
249+
*
250+
* SAFETY: We must verify 'to' is still a valid TCB before accessing it.
251+
* Search thread_map to confirm the pointer hasn't been freed and
252+
* reused.
248253
*/
249-
irq_flags = irq_save_flags();
250-
if (to->notify_generation == generation_before)
254+
basepri = irq_kernel_critical_enter();
255+
256+
/* Verify TCB is still valid by checking thread_map */
257+
int tcb_valid = 0;
258+
for (int i = 1; i < thread_count; ++i) {
259+
if (thread_map[i] == to) {
260+
tcb_valid = 1;
261+
break;
262+
}
263+
}
264+
265+
/* Only decrement if TCB is valid AND generation hasn't changed */
266+
if (tcb_valid && to->notify_generation == generation_before)
251267
to->notify_depth--;
252-
irq_restore_flags(irq_flags);
268+
269+
irq_kernel_critical_exit(basepri);
253270

254271
/* Check for preemption after notification.
255272
* Callback may have made higher-priority threads runnable.

kernel/kdb-latency.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* Copyright (c) 2026 The F9 Microkernel Project. All rights reserved.
2+
* Use of this source code is governed by a BSD-style license that can be
3+
* found in the LICENSE file.
4+
*/
5+
6+
#include <debug.h>
7+
#include <platform/irq-latency.h>
8+
9+
/**
10+
* KDB command: Display interrupt latency statistics.
11+
*
12+
* Shows min/avg/max latency for each priority level, highlighting
13+
* zero-latency ISRs (0x0-0x2) and standard user IRQs.
14+
*/
15+
void kdb_show_latency(void)
16+
{
17+
int i;
18+
latency_stats_t stats;
19+
int has_data = 0;
20+
21+
dbg_printf(DL_KDB, "\n=== Interrupt Latency Statistics ===\n");
22+
dbg_printf(DL_KDB, "Prio Type Count Min Avg Max\n");
23+
dbg_printf(DL_KDB, "---- ---------------- ------ ----- ----- -----\n");
24+
25+
for (i = 0; i < 16; i++) {
26+
if (!latency_get_stats_snapshot(i, &stats))
27+
continue;
28+
29+
if (stats.count == 0)
30+
continue;
31+
32+
has_data = 1;
33+
34+
const char *type;
35+
if (i <= 0x2)
36+
type = "Zero-latency ISR";
37+
else if (i == 0x3)
38+
type = "SysTick";
39+
else if (i <= 0xE)
40+
type = "User IRQ";
41+
else
42+
type = "SVCall/PendSV";
43+
44+
stats.avg = stats.count > 0 ? (stats.sum / stats.count) : 0;
45+
dbg_printf(DL_KDB, "0x%X %-16s %6u %5u %5u %5u\n", i, type,
46+
stats.count, stats.min, stats.avg, stats.max);
47+
}
48+
49+
if (!has_data) {
50+
dbg_printf(DL_KDB, "(No latency samples recorded yet)\n");
51+
}
52+
53+
dbg_printf(DL_KDB, "\nNotes:\n");
54+
dbg_printf(DL_KDB, " - Zero-latency ISRs (0x0-0x2) target <10 cycles\n");
55+
dbg_printf(DL_KDB, " - User IRQs (0x4-0xE) masked during kernel ops\n");
56+
dbg_printf(DL_KDB, " - Use 'r' to reset statistics\n");
57+
dbg_printf(DL_KDB, "\n");
58+
}
59+
60+
/**
61+
* KDB command: Reset latency statistics.
62+
*/
63+
void kdb_reset_latency(void)
64+
{
65+
latency_reset();
66+
dbg_printf(DL_KDB, "Latency statistics reset.\n");
67+
}

kernel/kdb.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ extern void kdb_dump_as(void);
3030
extern void kdb_show_sampling(void);
3131
extern void kdb_show_tickless_verify(void);
3232
extern void kdb_dump_notifications(void);
33+
extern void kdb_show_latency(void);
34+
extern void kdb_reset_latency(void);
3335

3436
struct kdb_t kdb_functions[] = {
3537
{.option = 'K',
@@ -84,6 +86,14 @@ struct kdb_t kdb_functions[] = {
8486
.menuentry = "show tickless scheduling stat",
8587
.function = kdb_show_tickless_verify},
8688
#endif
89+
{.option = 'L',
90+
.name = "LATENCY",
91+
.menuentry = "show interrupt latency",
92+
.function = kdb_show_latency},
93+
{.option = 'r',
94+
.name = "RESET LATENCY",
95+
.menuentry = "reset latency statistics",
96+
.function = kdb_reset_latency},
8797
/* Insert KDB functions here */
8898
};
8999

0 commit comments

Comments
 (0)