zephyr/tests/kernel/interrupt/src/regular_isr.c
Kumar Gala 1c8f1cd590 tests: kernel: interrupt: workaround qemu_x86 interrupt issue
qemu_x86 seems to take an extra instruction after the sti instruction
(irq_unlock) happens before it posts the interrupts.  This can issues
if the instruction after the sti ends up reading the state that is
suppose to be updated by the ISR handler.

We see this behavior when building with LLVM.  To workaround this issue
we add an arch_nop() to provide an extra instruction to allow the
interrupts to post.

Opened zephyrproject-rtos/sdk-ng#629 to track qemu issue.

Signed-off-by: Kumar Gala <kumar.gala@intel.com>
2023-02-21 16:17:20 -05:00

125 lines
3.1 KiB
C

/*
* Copyright (c) 2021 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#include <zephyr/interrupt_util.h>
#include <zephyr/irq.h>
/*
* Other arch has already been tested in testcase of gen_isr_table,
* so we only test x86 series here.
*/
#define TEST_IRQ_LINE_1 27
#define TEST_IRQ_LINE_2 28
#define TEST_IRQ_PRIO 2
volatile uint32_t reg_int_executed[2];
void isr_comm(const void *param)
{
int choice = POINTER_TO_INT(param);
switch (choice) {
case TEST_IRQ_LINE_1:
reg_int_executed[0]++;
break;
case TEST_IRQ_LINE_2:
reg_int_executed[1]++;
break;
default:
break;
}
}
/**
* @brief Test regular interrupt
*
* @details Validate regular interrupt works as expected.
* - Register two regular interrupt at build time.
* - Trigger interrupt and check if isr handler has executed or not.
* - Also check irq_enable and irq_disable works.
*
* @ingroup kernel_interrupt_tests
*
* @see IRQ_CONNECT(), irq_enable(), irq_disable(),
* irq_unlock(),
*/
ZTEST(interrupt_feature, test_isr_regular)
{
int trig_vec1, trig_vec2;
IRQ_CONNECT(TEST_IRQ_LINE_1, TEST_IRQ_PRIO, isr_comm, (void *)TEST_IRQ_LINE_1, 0);
IRQ_CONNECT(TEST_IRQ_LINE_2, TEST_IRQ_PRIO, isr_comm, (void *)TEST_IRQ_LINE_2, 0);
trig_vec1 = Z_IRQ_TO_INTERRUPT_VECTOR(TEST_IRQ_LINE_1);
trig_vec2 = Z_IRQ_TO_INTERRUPT_VECTOR(TEST_IRQ_LINE_2);
TC_PRINT("irq(%d)=vector(%d)\n", TEST_IRQ_LINE_1, trig_vec1);
TC_PRINT("irq(%d)=vector(%d)\n", TEST_IRQ_LINE_2, trig_vec2);
irq_enable(TEST_IRQ_LINE_1);
irq_enable(TEST_IRQ_LINE_2);
trigger_irq(trig_vec1);
zassert_true(reg_int_executed[0] == 1 &&
reg_int_executed[1] == 0,
"ISR1 should execute");
trigger_irq(trig_vec2);
zassert_true(reg_int_executed[0] == 1 &&
reg_int_executed[1] == 1,
"Both ISR should execute");
unsigned int key = irq_lock();
/* trigger under irq locked */
trigger_irq(trig_vec1);
trigger_irq(trig_vec2);
zassert_true(reg_int_executed[0] == 1 &&
reg_int_executed[1] == 1,
"Both ISR should not execute again(%d)(%d)",
reg_int_executed[0], reg_int_executed[1]);
irq_unlock(key);
#ifdef CONFIG_BOARD_QEMU_X86
/* QEMU seems to have an issue in that interrupts seem to post on
* the instruction after the 'sti' that is part of irq_unlock(). This
* can cause an issue if the instruction after the 'sti' ends up looking
* at the state that the ISR is suppose to update. This has been shown
* to happen when building this test for LLVM.
*
* Adding a nop instruction allows QEMU to post the ISR before any state
* gets examined as a workaround.
*
* See GitHub issue zephyrproject-rtos/sdk-ng#629 for the qemu bug.
*/
arch_nop();
#endif
/* interrupt serve after irq unlocked */
zassert_true(reg_int_executed[0] == 2 &&
reg_int_executed[1] == 2,
"Both ISR should execute again(%d)(%d)",
reg_int_executed[0], reg_int_executed[1]);
/* trigger after irq unlocked */
trigger_irq(trig_vec1);
trigger_irq(trig_vec2);
zassert_true(reg_int_executed[0] == 3 &&
reg_int_executed[1] == 3,
"Both ISR should execute again(%d)(%d)",
reg_int_executed[0], reg_int_executed[1]);
}