| /* |
| * Copyright (c) 2022 Baumer (www.baumer.com) |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/ztest.h> |
| #include <zephyr/arch/cpu.h> |
| #include <cmsis_core.h> |
| #include <zephyr/sys/barrier.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); |
| barrier_dsync_fence_full(); |
| barrier_isync_fence_full(); |
| |
| 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); |
| barrier_dsync_fence_full(); |
| barrier_isync_fence_full(); |
| |
| 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); |