Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2022 Baumer (www.baumer.com)
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/ztest.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/arch/arm/aarch32/cortex_m/cmsis.h>


#define EXECUTION_TRACE_LENGTH 6

#define IRQ_A_PRIO  1   /* lower priority */
#define IRQ_B_PRIO  0   /* higher priority */


#define CHECK_STEP(pos, val) zassert_equal(	\
	execution_trace[pos],			\
	val,					\
	"Expected %s for step %d but got %s",	\
	execution_step_str(val),		\
	pos,					\
	execution_step_str(execution_trace[pos]))


enum execution_step {
	STEP_MAIN_BEGIN,
	STEP_MAIN_END,
	STEP_ISR_A_BEGIN,
	STEP_ISR_A_END,
	STEP_ISR_B_BEGIN,
	STEP_ISR_B_END,
};

static volatile enum execution_step execution_trace[EXECUTION_TRACE_LENGTH];
static volatile int execution_trace_pos;

static int irq_a;
static int irq_b;

static const char *execution_step_str(enum execution_step s)
{
	const char *res = "invalid";

	switch (s) {
	case STEP_MAIN_BEGIN:
		res = "STEP_MAIN_BEGIN";
		break;
	case STEP_MAIN_END:
		res = "STEP_MAIN_END";
		break;
	case STEP_ISR_A_BEGIN:
		res = "STEP_ISR_A_BEGIN";
		break;
	case STEP_ISR_A_END:
		res = "STEP_ISR_A_END";
		break;
	case STEP_ISR_B_BEGIN:
		res = "STEP_ISR_B_BEGIN";
		break;
	case STEP_ISR_B_END:
		res = "STEP_ISR_B_END";
		break;
	default:
		break;
	}
	return res;
}


static void execution_trace_add(enum execution_step s)
{
	__ASSERT(execution_trace_pos < EXECUTION_TRACE_LENGTH,
		 "Execution trace overflow");
	execution_trace[execution_trace_pos] = s;
	execution_trace_pos++;
}



void isr_a_handler(const void *args)
{
	ARG_UNUSED(args);
	execution_trace_add(STEP_ISR_A_BEGIN);

	/* Set higher prior irq b pending */
	NVIC_SetPendingIRQ(irq_b);
	__DSB();
	__ISB();

	execution_trace_add(STEP_ISR_A_END);
}


void isr_b_handler(const void *args)
{
	ARG_UNUSED(args);
	execution_trace_add(STEP_ISR_B_BEGIN);
	execution_trace_add(STEP_ISR_B_END);
}


static int find_unused_irq(int start)
{
	int i;

	for (i = start - 1; i >= 0; i--) {
		if (NVIC_GetEnableIRQ(i) == 0) {
			/*
			 * Interrupts configured statically with IRQ_CONNECT(.)
			 * are automatically enabled. NVIC_GetEnableIRQ()
			 * returning false, here, implies that the IRQ line is
			 * either not implemented or it is not enabled, thus,
			 * currently not in use by Zephyr.
			 */

			/* Set the NVIC line to pending. */
			NVIC_SetPendingIRQ(i);

			if (NVIC_GetPendingIRQ(i)) {
				/*
				 * If the NVIC line is pending, it is
				 * guaranteed that it is implemented; clear the
				 * line.
				 */
				NVIC_ClearPendingIRQ(i);

				if (!NVIC_GetPendingIRQ(i)) {
					/*
					 * If the NVIC line can be successfully
					 * un-pended, it is guaranteed that it
					 * can be used for software interrupt
					 * triggering. Return the NVIC line
					 * number.
					 */
					break;
				}
			}
		}
	}

	zassert_true(i >= 0,
		     "No available IRQ line to configure as zero-latency\n");

	TC_PRINT("Available IRQ line: %u\n", i);
	return i;
}

ZTEST(arm_irq_zero_latency_levels, test_arm_zero_latency_levels)
{
	/*
	 * Confirm that a zero-latency interrupt with lower priority will be
	 * interrupted by a zero-latency interrupt with higher priority.
	 */

	if (!IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) {
		TC_PRINT("Skipped (Cortex-M Mainline only)\n");
		return;
	}

	/* Determine two NVIC IRQ lines that are not currently in use. */
	irq_a = find_unused_irq(CONFIG_NUM_IRQS);
	irq_b = find_unused_irq(irq_a);

	/* Configure IRQ A as zero-latency interrupt with prio 1 */
	arch_irq_connect_dynamic(irq_a, IRQ_A_PRIO, isr_a_handler,
				 NULL, IRQ_ZERO_LATENCY);
	NVIC_ClearPendingIRQ(irq_a);
	NVIC_EnableIRQ(irq_a);

	/* Configure irq_b as zero-latency interrupt with prio 0 */
	arch_irq_connect_dynamic(irq_b, IRQ_B_PRIO, isr_b_handler,
				 NULL, IRQ_ZERO_LATENCY);
	NVIC_ClearPendingIRQ(irq_b);
	NVIC_EnableIRQ(irq_b);

	/* Lock interrupts */
	unsigned int key = irq_lock();

	execution_trace_add(STEP_MAIN_BEGIN);

	/* Trigger irq_a */
	NVIC_SetPendingIRQ(irq_a);
	__DSB();
	__ISB();

	execution_trace_add(STEP_MAIN_END);

	/* Confirm that irq_a interrupted main and irq_b interrupted irq_a */
	CHECK_STEP(0, STEP_MAIN_BEGIN);
	CHECK_STEP(1, STEP_ISR_A_BEGIN);
	CHECK_STEP(2, STEP_ISR_B_BEGIN);
	CHECK_STEP(3, STEP_ISR_B_END);
	CHECK_STEP(4, STEP_ISR_A_END);
	CHECK_STEP(5, STEP_MAIN_END);

	/* Unlock interrupts */
	irq_unlock(key);
}

ZTEST_SUITE(arm_irq_zero_latency_levels, NULL, NULL, NULL, NULL, NULL);