blob: 6cee43147de9750a1d58ab39aad691c2de28ec75 [file] [log] [blame]
/*
* 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>
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
__DSB();
__ISB();
#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_START("Test gen_isr_tables");
TC_PRINT("IRQ configuration (total lines %d):\n", CONFIG_NUM_IRQS);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-label"
return NULL;
}
ZTEST_SUITE(gen_isr_table, NULL, gen_isr_table_setup, NULL, NULL, NULL);