Skip to content

Commit b3dfc24

Browse files
ccoenenatbaumerMaureenHelm
authored andcommitted
arch: arm: Add support for multiple zero-latency irq priorities
Add the ability to have multiple irq priority levels which are not masked by irq_lock() by adding CONFIG_ZERO_LATENCY_LEVELS. If CONFIG_ZERO_LATENCY_LEVELS is set to a value > 1 then multiple zero latency irqs are reserved by the kernel (and not only one). The priority of the zero-latency interrupt can be configured by IRQ_CONNECT. To be backwards compatible the prio argument in IRQ_CONNECT is still ignored and the target prio set to zero if CONFIG_ZERO_LATENCY_LEVELS is 1 (default). Implements #45276 Signed-off-by: Christoph Coenen <[email protected]>
1 parent 993cad1 commit b3dfc24

File tree

8 files changed

+265
-7
lines changed

8 files changed

+265
-7
lines changed

arch/arm/core/aarch32/cortex_m/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,15 @@ config ZERO_LATENCY_IRQS
302302
higher priority than the rest of the kernel they cannot use any
303303
kernel functionality.
304304

305+
config ZERO_LATENCY_LEVELS
306+
int "Number of interrupt priority levels reserved for zero latency"
307+
depends on ZERO_LATENCY_IRQS
308+
range 1 255
309+
help
310+
The amount of interrupt priority levels reserved for zero latency
311+
interrupts. Increase this value to reserve more than one priority
312+
level for zero latency interrupts.
313+
305314
config DYNAMIC_DIRECT_INTERRUPTS
306315
bool "Support for dynamic direct interrupts"
307316
depends on DYNAMIC_INTERRUPTS

arch/arm/core/aarch32/irq_manage.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ void z_arm_irq_priority_set(unsigned int irq, unsigned int prio, uint32_t flags)
7474
* via flags
7575
*/
7676
if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS) && (flags & IRQ_ZERO_LATENCY)) {
77-
prio = _EXC_ZERO_LATENCY_IRQS_PRIO;
77+
if (ZERO_LATENCY_LEVELS == 1) {
78+
prio = _EXC_ZERO_LATENCY_IRQS_PRIO;
79+
} else {
80+
/* Use caller supplied prio level as-is */
81+
}
7882
} else {
7983
prio += _IRQ_PRIO_OFFSET;
8084
}

include/zephyr/arch/arm/aarch32/exc.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949

5050
#define _EXC_FAULT_PRIO 0
5151
#define _EXC_ZERO_LATENCY_IRQS_PRIO 0
52-
#define _EXC_SVC_PRIO COND_CODE_1(CONFIG_ZERO_LATENCY_IRQS, (1), (0))
52+
#define _EXC_SVC_PRIO COND_CODE_1(CONFIG_ZERO_LATENCY_IRQS, \
53+
(CONFIG_ZERO_LATENCY_LEVELS), (0))
5354
#define _IRQ_PRIO_OFFSET (_EXCEPTION_RESERVED_PRIO + _EXC_SVC_PRIO)
5455
#define IRQ_PRIO_LOWEST (BIT(NUM_IRQ_PRIO_BITS) - (_IRQ_PRIO_OFFSET) - 1)
5556

include/zephyr/arch/arm/aarch32/irq.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,27 @@ extern void z_arm_interrupt_init(void);
8686

8787
/* Flags for use with IRQ_CONNECT() */
8888
/**
89-
* Set this interrupt up as a zero-latency IRQ. It has a fixed hardware
90-
* priority level (discarding what was supplied in the interrupt's priority
91-
* argument), and will run even if irq_lock() is active. Be careful!
89+
* Set this interrupt up as a zero-latency IRQ. If CONFIG_ZERO_LATENCY_LEVELS
90+
* is 1 it has a fixed hardware priority level (discarding what was supplied
91+
* in the interrupt's priority argument). If CONFIG_ZERO_LATENCY_LEVELS is
92+
* greater 1 it has the priority level assigned by the argument.
93+
* The interrupt wil run even if irq_lock() is active. Be careful!
9294
*/
9395
#define IRQ_ZERO_LATENCY BIT(0)
9496

9597
#ifdef CONFIG_CPU_CORTEX_M
98+
99+
#if defined(CONFIG_ZERO_LATENCY_LEVELS)
100+
#define ZERO_LATENCY_LEVELS CONFIG_ZERO_LATENCY_LEVELS
101+
#else
102+
#define ZERO_LATENCY_LEVELS 1
103+
#endif
104+
96105
#define _CHECK_PRIO(priority_p, flags_p) \
97-
BUILD_ASSERT((flags_p & IRQ_ZERO_LATENCY) || \
98-
priority_p <= IRQ_PRIO_LOWEST, \
106+
BUILD_ASSERT(((flags_p & IRQ_ZERO_LATENCY) && \
107+
((ZERO_LATENCY_LEVELS == 1) || \
108+
(priority_p < ZERO_LATENCY_LEVELS))) || \
109+
(priority_p <= IRQ_PRIO_LOWEST), \
99110
"Invalid interrupt priority. Values must not exceed IRQ_PRIO_LOWEST");
100111
#else
101112
#define _CHECK_PRIO(priority_p, flags_p)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(arm_irq_zero_latency_levels)
7+
8+
target_sources(app PRIVATE
9+
src/main.c
10+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_DYNAMIC_INTERRUPTS=y
3+
CONFIG_DYNAMIC_DIRECT_INTERRUPTS=y
4+
CONFIG_ZERO_LATENCY_IRQS=y
5+
CONFIG_ZERO_LATENCY_LEVELS=2
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright (c) 2022 Baumer (www.baumer.com)
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <ztest.h>
8+
#include <arch/cpu.h>
9+
#include <arch/arm/aarch32/cortex_m/cmsis.h>
10+
11+
12+
#define EXECUTION_TRACE_LENGTH 6
13+
14+
#define IRQ_A_PRIO 1 /* lower priority */
15+
#define IRQ_B_PRIO 0 /* higher priority */
16+
17+
18+
#define CHECK_STEP(pos, val) zassert_equal( \
19+
execution_trace[pos], \
20+
val, \
21+
"Expected %s for step %d but got %s", \
22+
execution_step_str(val), \
23+
pos, \
24+
execution_step_str(execution_trace[pos]))
25+
26+
27+
enum execution_step {
28+
STEP_MAIN_BEGIN,
29+
STEP_MAIN_END,
30+
STEP_ISR_A_BEGIN,
31+
STEP_ISR_A_END,
32+
STEP_ISR_B_BEGIN,
33+
STEP_ISR_B_END,
34+
};
35+
36+
static volatile enum execution_step execution_trace[EXECUTION_TRACE_LENGTH];
37+
static volatile int execution_trace_pos;
38+
39+
static int irq_a;
40+
static int irq_b;
41+
42+
static const char *execution_step_str(enum execution_step s)
43+
{
44+
const char *res = "invalid";
45+
46+
switch (s) {
47+
case STEP_MAIN_BEGIN:
48+
res = "STEP_MAIN_BEGIN";
49+
break;
50+
case STEP_MAIN_END:
51+
res = "STEP_MAIN_END";
52+
break;
53+
case STEP_ISR_A_BEGIN:
54+
res = "STEP_ISR_A_BEGIN";
55+
break;
56+
case STEP_ISR_A_END:
57+
res = "STEP_ISR_A_END";
58+
break;
59+
case STEP_ISR_B_BEGIN:
60+
res = "STEP_ISR_B_BEGIN";
61+
break;
62+
case STEP_ISR_B_END:
63+
res = "STEP_ISR_B_END";
64+
break;
65+
default:
66+
break;
67+
}
68+
return res;
69+
}
70+
71+
72+
static void execution_trace_add(enum execution_step s)
73+
{
74+
__ASSERT(execution_trace_pos < EXECUTION_TRACE_LENGTH,
75+
"Execution trace overflow");
76+
execution_trace[execution_trace_pos] = s;
77+
execution_trace_pos++;
78+
}
79+
80+
81+
82+
void isr_a_handler(const void *args)
83+
{
84+
ARG_UNUSED(args);
85+
execution_trace_add(STEP_ISR_A_BEGIN);
86+
87+
/* Set higher prior irq b pending */
88+
NVIC_SetPendingIRQ(irq_b);
89+
__DSB();
90+
__ISB();
91+
92+
execution_trace_add(STEP_ISR_A_END);
93+
}
94+
95+
96+
void isr_b_handler(const void *args)
97+
{
98+
ARG_UNUSED(args);
99+
execution_trace_add(STEP_ISR_B_BEGIN);
100+
execution_trace_add(STEP_ISR_B_END);
101+
}
102+
103+
104+
static int find_unused_irq(int start)
105+
{
106+
int i;
107+
108+
for (i = start - 1; i >= 0; i--) {
109+
if (NVIC_GetEnableIRQ(i) == 0) {
110+
/*
111+
* Interrupts configured statically with IRQ_CONNECT(.)
112+
* are automatically enabled. NVIC_GetEnableIRQ()
113+
* returning false, here, implies that the IRQ line is
114+
* either not implemented or it is not enabled, thus,
115+
* currently not in use by Zephyr.
116+
*/
117+
118+
/* Set the NVIC line to pending. */
119+
NVIC_SetPendingIRQ(i);
120+
121+
if (NVIC_GetPendingIRQ(i)) {
122+
/*
123+
* If the NVIC line is pending, it is
124+
* guaranteed that it is implemented; clear the
125+
* line.
126+
*/
127+
NVIC_ClearPendingIRQ(i);
128+
129+
if (!NVIC_GetPendingIRQ(i)) {
130+
/*
131+
* If the NVIC line can be successfully
132+
* un-pended, it is guaranteed that it
133+
* can be used for software interrupt
134+
* triggering. Return the NVIC line
135+
* number.
136+
*/
137+
break;
138+
}
139+
}
140+
}
141+
}
142+
143+
zassert_true(i >= 0,
144+
"No available IRQ line to configure as zero-latency\n");
145+
146+
TC_PRINT("Available IRQ line: %u\n", i);
147+
return i;
148+
}
149+
150+
151+
void test_arm_zero_latency_levels(void)
152+
{
153+
/*
154+
* Confirm that a zero-latency interrupt with lower priority will be
155+
* interrupted by a zero-latency interrupt with higher priority.
156+
*/
157+
158+
if (!IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) {
159+
TC_PRINT("Skipped (Cortex-M Mainline only)\n");
160+
return;
161+
}
162+
163+
/* Determine two NVIC IRQ lines that are not currently in use. */
164+
irq_a = find_unused_irq(CONFIG_NUM_IRQS);
165+
irq_b = find_unused_irq(irq_a);
166+
167+
/* Configure IRQ A as zero-latency interrupt with prio 1 */
168+
arch_irq_connect_dynamic(irq_a, IRQ_A_PRIO, isr_a_handler,
169+
NULL, IRQ_ZERO_LATENCY);
170+
NVIC_ClearPendingIRQ(irq_a);
171+
NVIC_EnableIRQ(irq_a);
172+
173+
/* Configure irq_b as zero-latency interrupt with prio 0 */
174+
arch_irq_connect_dynamic(irq_b, IRQ_B_PRIO, isr_b_handler,
175+
NULL, IRQ_ZERO_LATENCY);
176+
NVIC_ClearPendingIRQ(irq_b);
177+
NVIC_EnableIRQ(irq_b);
178+
179+
/* Lock interrupts */
180+
int key = irq_lock();
181+
182+
execution_trace_add(STEP_MAIN_BEGIN);
183+
184+
/* Trigger irq_a */
185+
NVIC_SetPendingIRQ(irq_a);
186+
__DSB();
187+
__ISB();
188+
189+
execution_trace_add(STEP_MAIN_END);
190+
191+
/* Confirm that irq_a interrupted main and irq_b interrupted irq_a */
192+
CHECK_STEP(0, STEP_MAIN_BEGIN);
193+
CHECK_STEP(1, STEP_ISR_A_BEGIN);
194+
CHECK_STEP(2, STEP_ISR_B_BEGIN);
195+
CHECK_STEP(3, STEP_ISR_B_END);
196+
CHECK_STEP(4, STEP_ISR_A_END);
197+
CHECK_STEP(5, STEP_MAIN_END);
198+
199+
/* Unlock interrupts */
200+
irq_unlock(key);
201+
}
202+
203+
204+
void test_main(void)
205+
{
206+
ztest_test_suite(arm_irq_zero_latency_levels,
207+
ztest_unit_test(test_arm_zero_latency_levels));
208+
ztest_run_test_suite(arm_irq_zero_latency_levels);
209+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
common:
2+
filter: (CONFIG_ARMV6_M_ARMV8_M_BASELINE or CONFIG_ARMV7_M_ARMV8_M_MAINLINE) and not CONFIG_SOC_FAMILY_NRF
3+
tags: arm interrupt
4+
arch_allow: arm
5+
tests:
6+
arch.arm.irq_zero_latency_levels:
7+
filter: not CONFIG_TRUSTED_EXECUTION_NONSECURE
8+
arch.arm.irq_zero_latency_levels.secure_fw:
9+
filter: CONFIG_TRUSTED_EXECUTION_SECURE

0 commit comments

Comments
 (0)