Linux Audio

Check our new training course

Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
/*
 * Copyright (c) 2017 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
#include <zephyr/irq.h>
#include <zephyr/tc_util.h>
#include <zephyr/sw_isr_table.h>
#include <zephyr/interrupt_util.h>
#include <zephyr/sys/barrier.h>

extern uint32_t _irq_vector_table[];

#if defined(ARCH_IRQ_DIRECT_CONNECT) && defined(CONFIG_GEN_IRQ_VECTOR_TABLE)
#define HAS_DIRECT_IRQS
#endif

#if defined(CONFIG_RISCV)
/* RISC-V has very few IRQ lines which can be triggered from software */
#define ISR3_OFFSET	1

/* Since we have so few lines we have to share the same line for two different
 * tests
 */
#ifdef HAS_DIRECT_IRQS
#define ISR1_OFFSET	5
#else
#define ISR5_OFFSET	5
#endif

#define IRQ_LINE(offset)        offset
#define TABLE_INDEX(offset)     offset
#define TRIG_CHECK_SIZE		6
#else
#define ISR1_OFFSET	0
#define ISR2_OFFSET	1
#define ISR3_OFFSET	2
#define ISR4_OFFSET	3
#define ISR5_OFFSET	4
#define ISR6_OFFSET	5

#if defined(CONFIG_SOC_ARC_EMSDP)
/* ARC EMSDP' console will use irq 108 / irq 107, will conflict
 * with isr used here, so add a workaround
 */
#define TEST_NUM_IRQS	105
#elif defined(CONFIG_SOC_NRF5340_CPUAPP) || defined(CONFIG_SOC_NRF9160)
/* In nRF9160 and application core in nRF5340, not all interrupts with highest
 * numbers are implemented. Thus, limit the number of interrupts reported to
 * the test, so that it does not try to use some unavailable ones.
 */
#define TEST_NUM_IRQS	33
#elif defined(CONFIG_SOC_STM32G071XX)
/* In STM32G071XX limit the number of interrupts reported to
 * the test, so that it does not try to use some of the IRQs
 * at the end of the vector table that are already used by
 * the board.
 */
#define TEST_NUM_IRQS	26
#elif defined(CONFIG_SOC_SERIES_NPCX7) || defined(CONFIG_SOC_SERIES_NPCX9)
/* Both NPCX7 and NPCX9 series use the IRQs at the end of the vector table, for
 * example, the IRQ 60 and 61 used for Multi-Input Wake-Up Unit (MIWU) devices
 * by default, and conflicts with ISR used for testing. Move IRQs for this
 * test suite to solve the issue.
 */
#define TEST_NUM_IRQS	44
#elif defined(CONFIG_SOC_LPC55S16)
/* IRQ 57 is reserved in the NXP LPC55S16 SoC. Thus, limit the number
 * of interrupts reported to the test, so that it does not try to use
 * it.
 */
#define TEST_NUM_IRQS	57
#else
#define TEST_NUM_IRQS	CONFIG_NUM_IRQS
#endif

#define TEST_IRQ_TABLE_SIZE	(IRQ_TABLE_SIZE - \
				 (CONFIG_NUM_IRQS - TEST_NUM_IRQS))
#define IRQ_LINE(offset)	(TEST_NUM_IRQS - ((offset) + 1))
#define TABLE_INDEX(offset)	(TEST_IRQ_TABLE_SIZE - ((offset) + 1))
#define TRIG_CHECK_SIZE		6
#endif

#define ISR3_ARG	0xb01dface
#define ISR4_ARG	0xca55e77e
#define ISR5_ARG	0xf0ccac1a
#define ISR6_ARG	0xba5eba11

static volatile int trigger_check[TRIG_CHECK_SIZE];

#ifdef HAS_DIRECT_IRQS
#ifdef ISR1_OFFSET
ISR_DIRECT_DECLARE(isr1)
{
	printk("isr1 ran\n");
	trigger_check[ISR1_OFFSET]++;
	return 0;
}
#endif

#ifdef ISR2_OFFSET
ISR_DIRECT_DECLARE(isr2)
{
	printk("isr2 ran\n");
	trigger_check[ISR2_OFFSET]++;
	return 1;
}
#endif
#endif

#ifdef ISR3_OFFSET
void isr3(const void *param)
{
	printk("%s ran with parameter %p\n", __func__, param);
	trigger_check[ISR3_OFFSET]++;
}
#endif

#ifdef ISR4_OFFSET
void isr4(const void *param)
{
	printk("%s ran with parameter %p\n", __func__, param);
	trigger_check[ISR4_OFFSET]++;
}
#endif

#ifdef ISR5_OFFSET
void isr5(const void *param)
{
	printk("%s ran with parameter %p\n", __func__, param);
	trigger_check[ISR5_OFFSET]++;
}
#endif

#ifdef ISR6_OFFSET
void isr6(const void *param)
{
	printk("%s ran with parameter %p\n", __func__, param);
	trigger_check[ISR6_OFFSET]++;
}
#endif

#ifndef CONFIG_CPU_CORTEX_M
/* Need to turn optimization off. Otherwise compiler may generate incorrect
 * code, not knowing that trigger_irq() affects the value of trigger_check,
 * even if declared volatile.
 *
 * A memory barrier does not help, we need an 'instruction barrier' but GCC
 * doesn't support this; we need to tell the compiler not to reorder memory
 * accesses to trigger_check around calls to trigger_irq.
 */
__no_optimization
#endif
int test_irq(int offset)
{
#ifndef NO_TRIGGER_FROM_SW
	TC_PRINT("triggering irq %d\n", IRQ_LINE(offset));
	trigger_irq(IRQ_LINE(offset));
#ifdef CONFIG_CPU_CORTEX_M
	barrier_dsync_fence_full();
	barrier_isync_fence_full();
#endif
	if (trigger_check[offset] != 1) {
		TC_PRINT("interrupt %d didn't run once, ran %d times\n",
			 IRQ_LINE(offset),
			 trigger_check[offset]);
		return -1;
	}
#else
	/* This arch doesn't support triggering interrupts from software */
	ARG_UNUSED(offset);
#endif
	return 0;
}



#ifdef HAS_DIRECT_IRQS
static int check_vector(void *isr, int offset)
{
/*
 * The problem with an IRQ table where the entries are jump opcodes is that it
 * the destination address is encoded in the opcode and strictly depending on
 * the address of the instruction itself (and very much architecture
 * dependent). For the sake of simplicity just skip the checks.
 */
#ifndef CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE
	TC_PRINT("Checking _irq_vector_table entry %d for irq %d\n",
		 TABLE_INDEX(offset), IRQ_LINE(offset));

	if (_irq_vector_table[TABLE_INDEX(offset)] != (uint32_t)isr) {
		TC_PRINT("bad entry %d in vector table\n", TABLE_INDEX(offset));
		return -1;
	}
#endif /* !CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE */

	if (test_irq(offset)) {
		return -1;
	}

	return 0;
}
#endif

#ifdef CONFIG_GEN_SW_ISR_TABLE
static int check_sw_isr(void *isr, uintptr_t arg, int offset)
{
	struct _isr_table_entry *e = &_sw_isr_table[TABLE_INDEX(offset)];

	TC_PRINT("Checking _sw_isr_table entry %d for irq %d\n",
		 TABLE_INDEX(offset), IRQ_LINE(offset));

	if (e->arg != (void *)arg) {
		TC_PRINT("bad argument in SW isr table\n");
		TC_PRINT("expected %p got %p\n", (void *)arg, e->arg);
		return -1;
	}
	if (e->isr != isr) {
		TC_PRINT("Bad ISR in SW isr table\n");
		TC_PRINT("expected %p got %p\n", (void *)isr, e->isr);
		return -1;
	}
#if defined(CONFIG_GEN_IRQ_VECTOR_TABLE) && !defined(CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE)
	void *v = (void *)_irq_vector_table[TABLE_INDEX(offset)];
	if (v != _isr_wrapper) {
		TC_PRINT("Vector does not point to _isr_wrapper\n");
		TC_PRINT("expected %p got %p\n", _isr_wrapper, v);
		return -1;
	}
#endif /* CONFIG_GEN_IRQ_VECTOR_TABLE && !CONFIG_IRQ_VECTOR_TABLE_JUMP_BY_CODE */

	if (test_irq(offset)) {
		return -1;
	}
	return 0;
}
#endif

/**
 * @ingroup kernel_interrupt_tests
 * @brief test to validate direct interrupt
 *
 * @details initialize two direct interrupt handler using IRQ_DIRECT_CONNECT
 * api at build time. For ‘direct’ interrupts, address of handler function will
 * be placed in the irq vector table. And each entry contains the pointer to
 * isr and the corresponding parameters.
 *
 * At the end according to architecture, we manually trigger the interrupt.
 * And all irq handler should get called.
 *
 * @see IRQ_DIRECT_CONNECT(), irq_enable()
 *
 */
ZTEST(gen_isr_table, test_build_time_direct_interrupt)
{
#ifndef HAS_DIRECT_IRQS
	ztest_test_skip();
#else

#ifdef ISR1_OFFSET
	IRQ_DIRECT_CONNECT(IRQ_LINE(ISR1_OFFSET), 0, isr1, 0);
	irq_enable(IRQ_LINE(ISR1_OFFSET));
	TC_PRINT("isr1 isr=%p irq=%d\n", isr1, IRQ_LINE(ISR1_OFFSET));
	zassert_ok(check_vector(isr1, ISR1_OFFSET),
			"check direct interrpt isr1 failed");
#endif

#ifdef ISR2_OFFSET
	IRQ_DIRECT_CONNECT(IRQ_LINE(ISR2_OFFSET), 0, isr2, 0);
	irq_enable(IRQ_LINE(ISR2_OFFSET));
	TC_PRINT("isr2 isr=%p irq=%d\n", isr2, IRQ_LINE(ISR2_OFFSET));


	zassert_ok(check_vector(isr2, ISR2_OFFSET),
			"check direct interrpt isr2 failed");
#endif
#endif
}

/**
 * @ingroup kernel_interrupt_tests
 * @brief test to validate gen_isr_table and interrupt
 *
 * @details initialize two normal interrupt handler using IRQ_CONNECT api at
 * build time. For 'regular' interrupts, the address of the common software isr
 * table is placed in the irq vector table, and software ISR table is an array
 * of struct _isr_table_entry. And each entry contains the pointer to isr and
 * the corresponding parameters.
 *
 * At the end according to architecture, we manually trigger the interrupt.
 * And all irq handler should get called.
 *
 * @see IRQ_CONNECT(), irq_enable()
 *
 */
ZTEST(gen_isr_table, test_build_time_interrupt)
{
#ifndef CONFIG_GEN_SW_ISR_TABLE
	ztest_test_skip();
#else
	TC_PRINT("_sw_isr_table at location %p\n", _sw_isr_table);

#ifdef ISR3_OFFSET
	IRQ_CONNECT(IRQ_LINE(ISR3_OFFSET), 1, isr3, ISR3_ARG, 0);
	irq_enable(IRQ_LINE(ISR3_OFFSET));
	TC_PRINT("isr3 isr=%p irq=%d param=%p\n", isr3, IRQ_LINE(ISR3_OFFSET),
		 (void *)ISR3_ARG);

	zassert_ok(check_sw_isr(isr3, ISR3_ARG, ISR3_OFFSET),
			"check interrupt isr3 failed");
#endif

#ifdef ISR4_OFFSET
	IRQ_CONNECT(IRQ_LINE(ISR4_OFFSET), 1, isr4, ISR4_ARG, 0);
	irq_enable(IRQ_LINE(ISR4_OFFSET));
	TC_PRINT("isr4 isr=%p irq=%d param=%p\n", isr4, IRQ_LINE(ISR4_OFFSET),
		 (void *)ISR4_ARG);

	zassert_ok(check_sw_isr(isr4, ISR4_ARG, ISR4_OFFSET),
			"check interrupt isr4 failed");
#endif
#endif
}

/**
 * @ingroup kernel_interrupt_tests
 * @brief test to validate gen_isr_table and dynamic interrupt
 *
 * @details initialize two dynamic interrupt handler using irq_connect_dynamic
 * api at run time. For dynamic interrupts, the address of the common software
 * isr table is also placed in the irq vector table. Software ISR table is an
 * array of struct _isr_table_entry. And each entry contains the pointer to isr
 * and the corresponding parameters.
 *
 * At the end according to architecture, we manually trigger the interrupt.
 * And all irq handler should get called.
 *
 * @see irq_connect_dynamic(), irq_enable()
 *
 */
ZTEST(gen_isr_table, test_run_time_interrupt)
{

#ifndef CONFIG_GEN_SW_ISR_TABLE
	ztest_test_skip();
#else

#ifdef ISR5_OFFSET
	irq_connect_dynamic(IRQ_LINE(ISR5_OFFSET), 1, isr5,
			    (const void *)ISR5_ARG, 0);
	irq_enable(IRQ_LINE(ISR5_OFFSET));
	TC_PRINT("isr5 isr=%p irq=%d param=%p\n", isr5, IRQ_LINE(ISR5_OFFSET),
		 (void *)ISR5_ARG);
	zassert_ok(check_sw_isr(isr5, ISR5_ARG, ISR5_OFFSET),
			"test dynamic interrupt isr5 failed");
#endif

#ifdef ISR6_OFFSET
	irq_connect_dynamic(IRQ_LINE(ISR6_OFFSET), 1, isr6,
			    (const void *)ISR6_ARG, 0);
	irq_enable(IRQ_LINE(ISR6_OFFSET));
	TC_PRINT("isr6 isr=%p irq=%d param=%p\n", isr6, IRQ_LINE(ISR6_OFFSET),
		 (void *)ISR6_ARG);

	zassert_ok(check_sw_isr(isr6, ISR6_ARG, ISR6_OFFSET),
			"check dynamic interrupt isr6 failed");
#endif
#endif
}

static void *gen_isr_table_setup(void)
{
	TC_PRINT("IRQ configuration (total lines %d):\n", CONFIG_NUM_IRQS);

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-label"

	return NULL;
}

#ifdef CONFIG_MULTI_LEVEL_INTERRUPTS
ZTEST(gen_isr_table, test_multi_level_bit_masks_sec)
{
#if CONFIG_1ST_LEVEL_INTERRUPT_BITS < 10 && CONFIG_2ND_LEVEL_INTERRUPT_BITS < 10
	ztest_test_skip();
#endif
	/* 0x400 is an l2 interrupt */
	unsigned int irq = 0x400;
	unsigned int level = 0;
	unsigned int ret_irq = 0;

	level = irq_get_level(irq);

	zassert_equal(2, level);

	/* 0x40 is l1 interrupt since it is less than 10 bits */
	irq = 0x40;
	level = irq_get_level(irq);
	zassert_equal(1, level);

	/* this is an l2 interrupt since it is more than 10 bits */
	irq = 0x800;
	ret_irq = irq_from_level_2(irq);
	zassert_equal(1, ret_irq);

	/* convert l1 interrupt to l2 */
	irq = 0x1;
	ret_irq = irq_to_level_2(irq);
	zassert_equal(0x800, ret_irq);

	/* get the parent of this l2 interrupt */
	irq = 0x401;
	ret_irq = irq_parent_level_2(irq);
	zassert_equal(1, ret_irq);
}
#endif

#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
ZTEST(gen_isr_table, test_multi_level_bit_masks_thr)
{
#if CONFIG_2ND_LEVEL_INTERRUPT_BITS < 10 && CONFIG_3RD_LEVEL_INTERRUPT_BITS < 9
	ztest_test_skip();
# endif
	/* 0x400 is an l2 interrupt */
	unsigned int irq = 0x400;
	unsigned int level = 0;
	unsigned int ret_irq = 0;


	/* note the first part of this test is the same as the above
	 * test this is to ensure the values are true after enabling l3 interrupts
	 */
	level = irq_get_level(irq);

	zassert_equal(2, level);

	/* this irq is within 10 bits so it is a l1 interrupt */
	irq = 0x40;
	level = irq_get_level(irq);
	zassert_equal(1, level);

	/* this irq is in the second 10 bits so it is a l2 interrupt */
	irq = 0x800;
	ret_irq = irq_from_level_2(irq);
	zassert_equal(1, ret_irq);

	/* convert a l1 interrupt to an l2 0x1 is less than 10 bits so it is l1 */
	irq = 0x1;
	ret_irq = irq_to_level_2(irq);
	zassert_equal(0x800, ret_irq);

	/* get the parent of an l2 interrupt 0x401 is an l2 interrupt with parent 1 */
	irq = 0x401;
	ret_irq = irq_parent_level_2(irq);
	zassert_equal(1, ret_irq);

	/* get the irq from level 3 this value is an l3 interrupt */
	irq = 0x200000;
	ret_irq = irq_from_level_3(irq);
	zassert_equal(1, ret_irq);

	/* convert the zero interrupt to l3 */
	irq = 0x0;
	ret_irq = irq_to_level_3(irq);
	zassert_equal(0x100000, ret_irq);

	/* parent of the l3 interrupt */
	irq = 0x101000;
	ret_irq = irq_parent_level_3(irq);
	zassert_equal(0x4, ret_irq);
}
#endif

ZTEST_SUITE(gen_isr_table, NULL, gen_isr_table_setup, NULL, NULL, NULL);