1 /*
2  * Copyright (c) 2017 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/ztest.h>
9 #include <zephyr/irq.h>
10 #include <zephyr/tc_util.h>
11 #include <zephyr/sw_isr_table.h>
12 #include <zephyr/interrupt_util.h>
13 #include <zephyr/sys/barrier.h>
14 
15 extern uint32_t _irq_vector_table[];
16 
17 #if defined(ARCH_IRQ_DIRECT_CONNECT) && defined(CONFIG_GEN_IRQ_VECTOR_TABLE)
18 #define HAS_DIRECT_IRQS
19 #endif
20 
21 #if defined(CONFIG_RISCV)
22 #if defined(CONFIG_NRFX_CLIC)
23 #define ISR1_OFFSET	15
24 #define ISR3_OFFSET	16
25 #define ISR5_OFFSET	17
26 #define TRIG_CHECK_SIZE	18
27 #elif defined(CONFIG_RISCV_HAS_CLIC)
28 #define ISR1_OFFSET	3
29 #define ISR3_OFFSET	17
30 #define ISR5_OFFSET	18
31 #define TRIG_CHECK_SIZE	19
32 #else
33 
34 /* RISC-V has very few IRQ lines which can be triggered from software */
35 #define ISR3_OFFSET	1
36 
37 /* Since we have so few lines we have to share the same line for two different
38  * tests
39  */
40 #ifdef HAS_DIRECT_IRQS
41 #define ISR1_OFFSET	5
42 #else
43 #define ISR5_OFFSET	5
44 #endif
45 #define TRIG_CHECK_SIZE	6
46 #endif
47 
48 #define IRQ_LINE(offset)        offset
49 #if defined(CONFIG_RISCV_RESERVED_IRQ_ISR_TABLES_OFFSET)
50 #define TABLE_INDEX(offset)     offset + CONFIG_RISCV_RESERVED_IRQ_ISR_TABLES_OFFSET
51 #else
52 #define TABLE_INDEX(offset)     offset
53 #endif
54 
55 #else
56 #define ISR1_OFFSET	0
57 #define ISR2_OFFSET	1
58 #define ISR3_OFFSET	2
59 #define ISR4_OFFSET	3
60 #define ISR5_OFFSET	4
61 #define ISR6_OFFSET	5
62 
63 #if defined(CONFIG_SOC_ARC_EMSDP)
64 /* ARC EMSDP' console will use irq 108 / irq 107, will conflict
65  * with isr used here, so add a workaround
66  */
67 #define TEST_NUM_IRQS	105
68 #elif defined(CONFIG_SOC_NRF5340_CPUAPP) || defined(CONFIG_SOC_NRF9160)
69 /* In nRF9160 and application core in nRF5340, not all interrupts with highest
70  * numbers are implemented. Thus, limit the number of interrupts reported to
71  * the test, so that it does not try to use some unavailable ones.
72  */
73 #define TEST_NUM_IRQS	33
74 #elif defined(CONFIG_SOC_STM32G071XX)
75 /* In STM32G071XX limit the number of interrupts reported to
76  * the test, so that it does not try to use some of the IRQs
77  * at the end of the vector table that are already used by
78  * the board.
79  */
80 #define TEST_NUM_IRQS	26
81 #elif defined(CONFIG_SOC_SERIES_NPCX7) || defined(CONFIG_SOC_SERIES_NPCX9)
82 /* Both NPCX7 and NPCX9 series use the IRQs at the end of the vector table, for
83  * example, the IRQ 60 and 61 used for Multi-Input Wake-Up Unit (MIWU) devices
84  * by default, and conflicts with ISR used for testing. Move IRQs for this
85  * test suite to solve the issue.
86  */
87 #define TEST_NUM_IRQS	44
88 #elif defined(CONFIG_SOC_LPC55S16)
89 /* IRQ 57 is reserved in the NXP LPC55S16 SoC. Thus, limit the number
90  * of interrupts reported to the test, so that it does not try to use
91  * it.
92  */
93 #define TEST_NUM_IRQS	57
94 #else
95 #define TEST_NUM_IRQS	CONFIG_NUM_IRQS
96 #endif
97 
98 #define TEST_IRQ_TABLE_SIZE	(IRQ_TABLE_SIZE - \
99 				 (CONFIG_NUM_IRQS - TEST_NUM_IRQS))
100 #define IRQ_LINE(offset)	(TEST_NUM_IRQS - ((offset) + 1))
101 #define TABLE_INDEX(offset)	(TEST_IRQ_TABLE_SIZE - ((offset) + 1))
102 #define TRIG_CHECK_SIZE		6
103 #endif
104 
105 #define ISR3_ARG	0xb01dface
106 #define ISR4_ARG	0xca55e77e
107 #define ISR5_ARG	0xf0ccac1a
108 #define ISR6_ARG	0xba5eba11
109 
110 #if defined(CONFIG_RISCV_HAS_CLIC)
111 #define IRQ_FLAGS 1 /* rising edge */
112 #else
113 #define IRQ_FLAGS 0
114 #endif
115 
116 static volatile int trigger_check[TRIG_CHECK_SIZE];
117 
118 #ifdef HAS_DIRECT_IRQS
119 #ifdef ISR1_OFFSET
ISR_DIRECT_DECLARE(isr1)120 ISR_DIRECT_DECLARE(isr1)
121 {
122 	printk("isr1 ran\n");
123 	trigger_check[ISR1_OFFSET]++;
124 	return 0;
125 }
126 #endif
127 
128 #ifdef ISR2_OFFSET
ISR_DIRECT_DECLARE(isr2)129 ISR_DIRECT_DECLARE(isr2)
130 {
131 	printk("isr2 ran\n");
132 	trigger_check[ISR2_OFFSET]++;
133 	return 1;
134 }
135 #endif
136 #endif
137 
138 #ifdef ISR3_OFFSET
isr3(const void * param)139 void isr3(const void *param)
140 {
141 	printk("%s ran with parameter %p\n", __func__, param);
142 	trigger_check[ISR3_OFFSET]++;
143 }
144 #endif
145 
146 #ifdef ISR4_OFFSET
isr4(const void * param)147 void isr4(const void *param)
148 {
149 	printk("%s ran with parameter %p\n", __func__, param);
150 	trigger_check[ISR4_OFFSET]++;
151 }
152 #endif
153 
154 #ifdef ISR5_OFFSET
isr5(const void * param)155 void isr5(const void *param)
156 {
157 	printk("%s ran with parameter %p\n", __func__, param);
158 	trigger_check[ISR5_OFFSET]++;
159 }
160 #endif
161 
162 #ifdef ISR6_OFFSET
isr6(const void * param)163 void isr6(const void *param)
164 {
165 	printk("%s ran with parameter %p\n", __func__, param);
166 	trigger_check[ISR6_OFFSET]++;
167 }
168 #endif
169 
170 #ifndef CONFIG_CPU_CORTEX_M
171 /* Need to turn optimization off. Otherwise compiler may generate incorrect
172  * code, not knowing that trigger_irq() affects the value of trigger_check,
173  * even if declared volatile.
174  *
175  * A memory barrier does not help, we need an 'instruction barrier' but GCC
176  * doesn't support this; we need to tell the compiler not to reorder memory
177  * accesses to trigger_check around calls to trigger_irq.
178  */
179 __no_optimization
180 #endif
test_irq(int offset)181 int test_irq(int offset)
182 {
183 #ifndef NO_TRIGGER_FROM_SW
184 	TC_PRINT("triggering irq %d\n", IRQ_LINE(offset));
185 	trigger_irq(IRQ_LINE(offset));
186 #ifdef CONFIG_CPU_CORTEX_M
187 	barrier_dsync_fence_full();
188 	barrier_isync_fence_full();
189 #endif
190 	if (trigger_check[offset] != 1) {
191 		TC_PRINT("interrupt %d didn't run once, ran %d times\n",
192 			 IRQ_LINE(offset),
193 			 trigger_check[offset]);
194 		return -1;
195 	}
196 #else
197 	/* This arch doesn't support triggering interrupts from software */
198 	ARG_UNUSED(offset);
199 #endif
200 	return 0;
201 }
202 
203 
204 
205 #ifdef HAS_DIRECT_IRQS
check_vector(void * isr,int offset)206 static int check_vector(void *isr, int offset)
207 {
208 /*
209  * The problem with an IRQ table where the entries are jump opcodes is that it
210  * the destination address is encoded in the opcode and strictly depending on
211  * the address of the instruction itself (and very much architecture
212  * dependent). For the sake of simplicity just skip the checks.
213  */
214 #ifndef CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE
215 	TC_PRINT("Checking _irq_vector_table entry %d for irq %d\n",
216 		 TABLE_INDEX(offset), IRQ_LINE(offset));
217 
218 	if (_irq_vector_table[TABLE_INDEX(offset)] != (uint32_t)isr) {
219 		TC_PRINT("bad entry %d in vector table\n", TABLE_INDEX(offset));
220 		return -1;
221 	}
222 #endif /* !CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE */
223 
224 	if (test_irq(offset)) {
225 		return -1;
226 	}
227 
228 	return 0;
229 }
230 #endif
231 
232 #ifdef CONFIG_GEN_SW_ISR_TABLE
check_sw_isr(void * isr,uintptr_t arg,int offset)233 static int check_sw_isr(void *isr, uintptr_t arg, int offset)
234 {
235 	struct _isr_table_entry *e = &_sw_isr_table[TABLE_INDEX(offset)];
236 
237 	TC_PRINT("Checking _sw_isr_table entry %d for irq %d\n",
238 		 TABLE_INDEX(offset), IRQ_LINE(offset));
239 
240 	if (e->arg != (void *)arg) {
241 		TC_PRINT("bad argument in SW isr table\n");
242 		TC_PRINT("expected %p got %p\n", (void *)arg, e->arg);
243 		return -1;
244 	}
245 	if (e->isr != isr) {
246 		TC_PRINT("Bad ISR in SW isr table\n");
247 		TC_PRINT("expected %p got %p\n", (void *)isr, e->isr);
248 		return -1;
249 	}
250 #if defined(CONFIG_GEN_IRQ_VECTOR_TABLE) && !defined(CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE)
251 	void *v = (void *)_irq_vector_table[TABLE_INDEX(offset)];
252 	if (v != _isr_wrapper) {
253 		TC_PRINT("Vector does not point to _isr_wrapper\n");
254 		TC_PRINT("expected %p got %p\n", _isr_wrapper, v);
255 		return -1;
256 	}
257 #endif /* CONFIG_GEN_IRQ_VECTOR_TABLE && !CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE */
258 
259 	if (test_irq(offset)) {
260 		return -1;
261 	}
262 	return 0;
263 }
264 #endif
265 
266 /**
267  * @ingroup kernel_interrupt_tests
268  * @brief test to validate direct interrupt
269  *
270  * @details initialize two direct interrupt handler using IRQ_DIRECT_CONNECT
271  * api at build time. For ‘direct’ interrupts, address of handler function will
272  * be placed in the irq vector table. And each entry contains the pointer to
273  * isr and the corresponding parameters.
274  *
275  * At the end according to architecture, we manually trigger the interrupt.
276  * And all irq handler should get called.
277  *
278  * @see IRQ_DIRECT_CONNECT(), irq_enable()
279  *
280  */
ZTEST(gen_isr_table,test_build_time_direct_interrupt)281 ZTEST(gen_isr_table, test_build_time_direct_interrupt)
282 {
283 #ifndef HAS_DIRECT_IRQS
284 	ztest_test_skip();
285 #else
286 
287 #ifdef ISR1_OFFSET
288 	IRQ_DIRECT_CONNECT(IRQ_LINE(ISR1_OFFSET), 0, isr1, IRQ_FLAGS);
289 	irq_enable(IRQ_LINE(ISR1_OFFSET));
290 	TC_PRINT("isr1 isr=%p irq=%d\n", isr1, IRQ_LINE(ISR1_OFFSET));
291 	zassert_ok(check_vector(isr1, ISR1_OFFSET),
292 			"check direct interrpt isr1 failed");
293 #endif
294 
295 #ifdef ISR2_OFFSET
296 	IRQ_DIRECT_CONNECT(IRQ_LINE(ISR2_OFFSET), 0, isr2, IRQ_FLAGS);
297 	irq_enable(IRQ_LINE(ISR2_OFFSET));
298 	TC_PRINT("isr2 isr=%p irq=%d\n", isr2, IRQ_LINE(ISR2_OFFSET));
299 
300 
301 	zassert_ok(check_vector(isr2, ISR2_OFFSET),
302 			"check direct interrpt isr2 failed");
303 #endif
304 #endif
305 }
306 
307 /**
308  * @ingroup kernel_interrupt_tests
309  * @brief test to validate gen_isr_table and interrupt
310  *
311  * @details initialize two normal interrupt handler using IRQ_CONNECT api at
312  * build time. For 'regular' interrupts, the address of the common software isr
313  * table is placed in the irq vector table, and software ISR table is an array
314  * of struct _isr_table_entry. And each entry contains the pointer to isr and
315  * the corresponding parameters.
316  *
317  * At the end according to architecture, we manually trigger the interrupt.
318  * And all irq handler should get called.
319  *
320  * @see IRQ_CONNECT(), irq_enable()
321  *
322  */
ZTEST(gen_isr_table,test_build_time_interrupt)323 ZTEST(gen_isr_table, test_build_time_interrupt)
324 {
325 #ifndef CONFIG_GEN_SW_ISR_TABLE
326 	ztest_test_skip();
327 #else
328 	TC_PRINT("_sw_isr_table at location %p\n", _sw_isr_table);
329 
330 #ifdef ISR3_OFFSET
331 	IRQ_CONNECT(IRQ_LINE(ISR3_OFFSET), 1, isr3, ISR3_ARG, IRQ_FLAGS);
332 	irq_enable(IRQ_LINE(ISR3_OFFSET));
333 	TC_PRINT("isr3 isr=%p irq=%d param=%p\n", isr3, IRQ_LINE(ISR3_OFFSET),
334 		 (void *)ISR3_ARG);
335 
336 	zassert_ok(check_sw_isr(isr3, ISR3_ARG, ISR3_OFFSET),
337 			"check interrupt isr3 failed");
338 #endif
339 
340 #ifdef ISR4_OFFSET
341 	IRQ_CONNECT(IRQ_LINE(ISR4_OFFSET), 1, isr4, ISR4_ARG, IRQ_FLAGS);
342 	irq_enable(IRQ_LINE(ISR4_OFFSET));
343 	TC_PRINT("isr4 isr=%p irq=%d param=%p\n", isr4, IRQ_LINE(ISR4_OFFSET),
344 		 (void *)ISR4_ARG);
345 
346 	zassert_ok(check_sw_isr(isr4, ISR4_ARG, ISR4_OFFSET),
347 			"check interrupt isr4 failed");
348 #endif
349 #endif
350 }
351 
352 /**
353  * @ingroup kernel_interrupt_tests
354  * @brief test to validate gen_isr_table and dynamic interrupt
355  *
356  * @details initialize two dynamic interrupt handler using irq_connect_dynamic
357  * api at run time. For dynamic interrupts, the address of the common software
358  * isr table is also placed in the irq vector table. Software ISR table is an
359  * array of struct _isr_table_entry. And each entry contains the pointer to isr
360  * and the corresponding parameters.
361  *
362  * At the end according to architecture, we manually trigger the interrupt.
363  * And all irq handler should get called.
364  *
365  * @see irq_connect_dynamic(), irq_enable()
366  *
367  */
ZTEST(gen_isr_table,test_run_time_interrupt)368 ZTEST(gen_isr_table, test_run_time_interrupt)
369 {
370 
371 #ifndef CONFIG_GEN_SW_ISR_TABLE
372 	ztest_test_skip();
373 #else
374 
375 #ifdef ISR5_OFFSET
376 	irq_connect_dynamic(IRQ_LINE(ISR5_OFFSET), 1, isr5,
377 			    (const void *)ISR5_ARG, IRQ_FLAGS);
378 	irq_enable(IRQ_LINE(ISR5_OFFSET));
379 	TC_PRINT("isr5 isr=%p irq=%d param=%p\n", isr5, IRQ_LINE(ISR5_OFFSET),
380 		 (void *)ISR5_ARG);
381 	zassert_ok(check_sw_isr(isr5, ISR5_ARG, ISR5_OFFSET),
382 			"test dynamic interrupt isr5 failed");
383 #endif
384 
385 #ifdef ISR6_OFFSET
386 	irq_connect_dynamic(IRQ_LINE(ISR6_OFFSET), 1, isr6,
387 			    (const void *)ISR6_ARG, IRQ_FLAGS);
388 	irq_enable(IRQ_LINE(ISR6_OFFSET));
389 	TC_PRINT("isr6 isr=%p irq=%d param=%p\n", isr6, IRQ_LINE(ISR6_OFFSET),
390 		 (void *)ISR6_ARG);
391 
392 	zassert_ok(check_sw_isr(isr6, ISR6_ARG, ISR6_OFFSET),
393 			"check dynamic interrupt isr6 failed");
394 #endif
395 #endif
396 }
397 
gen_isr_table_setup(void)398 static void *gen_isr_table_setup(void)
399 {
400 	TC_PRINT("IRQ configuration (total lines %d):\n", CONFIG_NUM_IRQS);
401 
402 #pragma GCC diagnostic push
403 #pragma GCC diagnostic ignored "-Wunused-label"
404 
405 	return NULL;
406 }
407 
408 ZTEST_SUITE(gen_isr_table, NULL, gen_isr_table_setup, NULL, NULL, NULL);
409