1 /*
2  * Copyright (c) 2022 Baumer (www.baumer.com)
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/ztest.h>
8 #include <zephyr/arch/cpu.h>
9 #include <cmsis_core.h>
10 #include <zephyr/sys/barrier.h>
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 #define CHECK_STEP(pos, val)                                                                       \
18 	zassert_equal(execution_trace[pos], val, "Expected %s for step %d but got %s",             \
19 		      execution_step_str(val), pos, execution_step_str(execution_trace[pos]))
20 
21 enum execution_step {
22 	STEP_MAIN_BEGIN,
23 	STEP_MAIN_END,
24 	STEP_ISR_A_BEGIN,
25 	STEP_ISR_A_END,
26 	STEP_ISR_B_BEGIN,
27 	STEP_ISR_B_END,
28 };
29 
30 static volatile enum execution_step execution_trace[EXECUTION_TRACE_LENGTH];
31 static volatile int execution_trace_pos;
32 
33 static int irq_a;
34 static int irq_b;
35 
execution_step_str(enum execution_step s)36 static const char *execution_step_str(enum execution_step s)
37 {
38 	const char *res = "invalid";
39 
40 	switch (s) {
41 	case STEP_MAIN_BEGIN:
42 		res = "STEP_MAIN_BEGIN";
43 		break;
44 	case STEP_MAIN_END:
45 		res = "STEP_MAIN_END";
46 		break;
47 	case STEP_ISR_A_BEGIN:
48 		res = "STEP_ISR_A_BEGIN";
49 		break;
50 	case STEP_ISR_A_END:
51 		res = "STEP_ISR_A_END";
52 		break;
53 	case STEP_ISR_B_BEGIN:
54 		res = "STEP_ISR_B_BEGIN";
55 		break;
56 	case STEP_ISR_B_END:
57 		res = "STEP_ISR_B_END";
58 		break;
59 	default:
60 		break;
61 	}
62 	return res;
63 }
64 
execution_trace_add(enum execution_step s)65 static void execution_trace_add(enum execution_step s)
66 {
67 	__ASSERT(execution_trace_pos < EXECUTION_TRACE_LENGTH, "Execution trace overflow");
68 	execution_trace[execution_trace_pos] = s;
69 	execution_trace_pos++;
70 }
71 
isr_a_handler(const void * args)72 void isr_a_handler(const void *args)
73 {
74 	ARG_UNUSED(args);
75 	execution_trace_add(STEP_ISR_A_BEGIN);
76 
77 	/* Set higher prior irq b pending */
78 	NVIC_SetPendingIRQ(irq_b);
79 	barrier_dsync_fence_full();
80 	barrier_isync_fence_full();
81 
82 	execution_trace_add(STEP_ISR_A_END);
83 }
84 
isr_b_handler(const void * args)85 void isr_b_handler(const void *args)
86 {
87 	ARG_UNUSED(args);
88 	execution_trace_add(STEP_ISR_B_BEGIN);
89 	execution_trace_add(STEP_ISR_B_END);
90 }
91 
find_unused_irq(int start)92 static int find_unused_irq(int start)
93 {
94 	int i;
95 
96 	for (i = start - 1; i >= 0; i--) {
97 		if (NVIC_GetEnableIRQ(i) == 0) {
98 			/*
99 			 * Interrupts configured statically with IRQ_CONNECT(.)
100 			 * are automatically enabled. NVIC_GetEnableIRQ()
101 			 * returning false, here, implies that the IRQ line is
102 			 * either not implemented or it is not enabled, thus,
103 			 * currently not in use by Zephyr.
104 			 */
105 
106 			/* Set the NVIC line to pending. */
107 			NVIC_SetPendingIRQ(i);
108 
109 			if (NVIC_GetPendingIRQ(i)) {
110 				/*
111 				 * If the NVIC line is pending, it is
112 				 * guaranteed that it is implemented; clear the
113 				 * line.
114 				 */
115 				NVIC_ClearPendingIRQ(i);
116 
117 				if (!NVIC_GetPendingIRQ(i)) {
118 					/*
119 					 * If the NVIC line can be successfully
120 					 * un-pended, it is guaranteed that it
121 					 * can be used for software interrupt
122 					 * triggering. Return the NVIC line
123 					 * number.
124 					 */
125 					break;
126 				}
127 			}
128 		}
129 	}
130 
131 	zassert_true(i >= 0, "No available IRQ line to configure as zero-latency\n");
132 
133 	TC_PRINT("Available IRQ line: %u\n", i);
134 	return i;
135 }
136 
ZTEST(arm_irq_zero_latency_levels,test_arm_zero_latency_levels)137 ZTEST(arm_irq_zero_latency_levels, test_arm_zero_latency_levels)
138 {
139 	/*
140 	 * Confirm that a zero-latency interrupt with lower priority will be
141 	 * interrupted by a zero-latency interrupt with higher priority.
142 	 */
143 
144 	if (!IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) {
145 		TC_PRINT("Skipped (Cortex-M Mainline only)\n");
146 		return;
147 	}
148 
149 	/* Determine two NVIC IRQ lines that are not currently in use. */
150 	irq_a = find_unused_irq(CONFIG_NUM_IRQS);
151 	irq_b = find_unused_irq(irq_a);
152 
153 	/* Configure IRQ A as zero-latency interrupt with prio 1 */
154 	arch_irq_connect_dynamic(irq_a, IRQ_A_PRIO, isr_a_handler, NULL, IRQ_ZERO_LATENCY);
155 	NVIC_ClearPendingIRQ(irq_a);
156 	NVIC_EnableIRQ(irq_a);
157 
158 	/* Configure irq_b as zero-latency interrupt with prio 0 */
159 	arch_irq_connect_dynamic(irq_b, IRQ_B_PRIO, isr_b_handler, NULL, IRQ_ZERO_LATENCY);
160 	NVIC_ClearPendingIRQ(irq_b);
161 	NVIC_EnableIRQ(irq_b);
162 
163 	/* Lock interrupts */
164 	unsigned int key = irq_lock();
165 
166 	execution_trace_add(STEP_MAIN_BEGIN);
167 
168 	/* Trigger irq_a */
169 	NVIC_SetPendingIRQ(irq_a);
170 	barrier_dsync_fence_full();
171 	barrier_isync_fence_full();
172 
173 	execution_trace_add(STEP_MAIN_END);
174 
175 	/* Confirm that irq_a interrupted main and irq_b interrupted irq_a */
176 	CHECK_STEP(0, STEP_MAIN_BEGIN);
177 	CHECK_STEP(1, STEP_ISR_A_BEGIN);
178 	CHECK_STEP(2, STEP_ISR_B_BEGIN);
179 	CHECK_STEP(3, STEP_ISR_B_END);
180 	CHECK_STEP(4, STEP_ISR_A_END);
181 	CHECK_STEP(5, STEP_MAIN_END);
182 
183 	/* Unlock interrupts */
184 	irq_unlock(key);
185 }
186 
187 ZTEST_SUITE(arm_irq_zero_latency_levels, NULL, NULL, NULL, NULL, NULL);
188