| /** |
| * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include "pico/stdlib.h" |
| #include "pico/test.h" |
| #include "pico/time.h" |
| #include "hardware/irq.h" |
| #include "hardware/dma.h" |
| #include "hardware/structs/scb.h" |
| |
| PICOTEST_MODULE_NAME("IRQ", "IRQ Handler test"); |
| |
| extern void __unhandled_user_irq(void); |
| |
| static bool remove_handler1_in_dma; |
| static bool remove_handler2_in_dma; |
| static bool remove_handler3_in_dma; |
| static dma_channel_config config; |
| |
| #define MAX_FIRE_COUNT 4 |
| static int fire_count; |
| static int fired[MAX_FIRE_COUNT]; |
| |
| static uint32_t dma_to = 0; |
| static uint32_t dma_from = 0xaaaa5555; |
| |
| int record_fire(int which) { |
| PICOTEST_CHECK(fire_count < MAX_FIRE_COUNT, "too many firings"); |
| fired[fire_count++] = which; |
| return 0; |
| } |
| |
| void __isr handler1(void) { |
| record_fire(1); |
| dma_channel_acknowledge_irq0(0); |
| if (remove_handler1_in_dma) { |
| irq_remove_handler(DMA_IRQ_0, handler1); |
| remove_handler1_in_dma = false; |
| } |
| } |
| |
| void __isr handler2(void) { |
| record_fire(2); |
| dma_channel_acknowledge_irq0(0); |
| if (remove_handler2_in_dma) { |
| irq_remove_handler(DMA_IRQ_0, handler2); |
| remove_handler2_in_dma = false; |
| } |
| } |
| |
| void __isr handler3(void) { |
| record_fire(3); |
| dma_channel_acknowledge_irq0(0); |
| if (remove_handler3_in_dma) { |
| irq_remove_handler(DMA_IRQ_0, handler3); |
| remove_handler3_in_dma = false; |
| } |
| } |
| |
| static inline irq_handler_t *get_vtable(void) { |
| #ifdef __riscv |
| return (irq_handler_t *) (riscv_read_csr(RVCSR_MTVEC_OFFSET) & ~0x3u); |
| #else |
| return (irq_handler_t *) scb_hw->vtor; |
| #endif |
| } |
| |
| // Only used for dumping the chain for post-mortem: |
| #define SLOT_SIZE 12 |
| extern uint8_t irq_handler_chain_slots[SLOT_SIZE * PICO_MAX_SHARED_IRQ_HANDLERS]; |
| #ifdef __riscv |
| #define SLOT_LINK_OFFSET 10 |
| #else |
| #define SLOT_LINK_OFFSET 6 |
| #endif |
| |
| int dma_check(int expected, ...) { |
| if (expected == 0) { |
| // doing the DMA if there are no IRQ handlers will cause a hard fault, so we just check we are pointing at the handler which does this. |
| PICOTEST_CHECK_AND_ABORT(get_vtable()[VTABLE_FIRST_IRQ + DMA_IRQ_0] == __unhandled_user_irq, "Expected there to be no IRQ handlers"); |
| return 0; |
| } |
| fire_count = 0; |
| dma_channel_configure(0, &config, &dma_to, &dma_from, 1, true); |
| sleep_ms(100); |
| va_list args; |
| va_start(args, expected); |
| bool ok = expected == fire_count; |
| for(int i=0;ok && i<expected;i++) { |
| if (fired[i] != va_arg(args, int)) { |
| ok = false; |
| break; |
| } |
| } |
| va_end(args); |
| if (!ok) { |
| PICOTEST_CHECK(ok, "DMA handlers were not called in the order expected"); |
| printf(" EXPECTED handlers: "); |
| va_start(args, expected); |
| for(int i=0;i<expected;i++) { |
| if (i) printf(", "); |
| printf("%d", va_arg(args, int)); |
| } |
| printf("\n"); |
| va_end(args); |
| printf(" CALLED handlers: "); |
| for(int i=0;i<fire_count;i++) { |
| if (i) printf(", "); |
| printf("%d", fired[i]); |
| } |
| printf("\n"); |
| printf("Dump of slot chain:\n"); |
| uint8_t *head = (uint8_t*)get_vtable()[VTABLE_FIRST_IRQ + DMA_IRQ_0]; |
| printf(" Head at %p\n", head); |
| while (head >= irq_handler_chain_slots && head <= &irq_handler_chain_slots[SLOT_SIZE * (PICO_MAX_SHARED_IRQ_HANDLERS - 1)]) { |
| printf( |
| " %p (slot %3u): %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x\n", |
| head, ((uintptr_t)head - (uintptr_t)irq_handler_chain_slots) / SLOT_SIZE, |
| head[1], head[0], |
| head[3], head[2], |
| head[5], head[4], |
| head[7], head[6], |
| head[9], head[8], |
| head[11], head[10] |
| ); |
| head = &irq_handler_chain_slots[head[SLOT_LINK_OFFSET] * SLOT_SIZE]; |
| } |
| printf("Handlers at: %p, %p, %p\n", handler1, handler2, handler3); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int main() { |
| stdio_init_all(); |
| |
| PICOTEST_START(); |
| config = dma_channel_get_default_config(0); |
| dma_channel_set_irq0_enabled(0, true); |
| irq_set_enabled(DMA_IRQ_0, true); |
| |
| // part I, just add/remove two handlers |
| PICOTEST_START_SECTION("I: Add handler1 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(1, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("I: Add handler2 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(2, 2, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("I: Remove handler1"); |
| irq_remove_handler(DMA_IRQ_0, handler1); |
| dma_check(1, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("I: Remove handler2"); |
| irq_remove_handler(DMA_IRQ_0, handler2); |
| dma_check(0); |
| PICOTEST_END_SECTION(); |
| |
| // part II, add/remove three handlers including one in the middle |
| |
| PICOTEST_START_SECTION("II: Add handler3 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(1, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("II: Add handler2 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(2, 2, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("II: Add handler1 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(3, 1, 2, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("II: Remove handler2"); |
| irq_remove_handler(DMA_IRQ_0, handler2); |
| dma_check(2, 1, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("II: Remove handler3"); |
| irq_remove_handler(DMA_IRQ_0, handler3); |
| dma_check(1, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("II: Remove handler1"); |
| irq_remove_handler(DMA_IRQ_0, handler1); |
| dma_check(0); |
| PICOTEST_END_SECTION(); |
| |
| // part III, the same as part II, but removing the handlers during the IRQ |
| |
| PICOTEST_START_SECTION("III: Add handler3 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(1, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("III: Add handler2 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(2, 2, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("III: Add handler1 default priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(3, 1, 2, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("III: Remove handler2"); |
| remove_handler2_in_dma = true; |
| // note that this is the defined behavior, that any handlers after the removed handler are not called. the |
| // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever |
| // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same |
| // interrupt handler in our case, this does not happen. |
| dma_check(2, 1, 2); |
| // but we call again to check |
| dma_check(2, 1, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("III: Remove handler3"); |
| remove_handler3_in_dma = true; |
| // note that this is the defined behavior, that any handlers after the removed handler are not called. the |
| // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever |
| // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same |
| // interrupt handler in our case, this does not happen. |
| dma_check(2, 1, 3); |
| // but we call again to check |
| dma_check(1, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("III: Remove handler3"); |
| remove_handler1_in_dma = true; |
| // note that this is the defined behavior, that any handlers after the removed handler are not called. the |
| // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever |
| // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same |
| // interrupt handler in our case, this does not happen. |
| dma_check(1, 1); |
| // but we call again to check |
| dma_check(0); |
| PICOTEST_END_SECTION(); |
| |
| // part IV, checking priorities |
| PICOTEST_START_SECTION("IV: Add handler1 high priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); |
| dma_check(1, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Add handler2 normal priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(2, 1, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Add handler3 lowest priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY); |
| dma_check(3, 1, 2, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Remove handler3"); |
| irq_remove_handler(DMA_IRQ_0, handler3); |
| dma_check(2, 1, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Add handler3 normal priority"); |
| irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); |
| dma_check(3, 1, 3, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Remove handler2"); |
| irq_remove_handler(DMA_IRQ_0, handler2); |
| dma_check(2, 1, 3); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Add handler2 normal priority - 2"); |
| irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY - 2); |
| dma_check(3, 1, 3, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Remove handler1"); |
| irq_remove_handler(DMA_IRQ_0, handler1); |
| dma_check(2, 3, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Add handler1 normal priority - 1"); |
| irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY - 1); |
| dma_check(3, 3, 1, 2); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Remove handler2 again"); |
| irq_remove_handler(DMA_IRQ_0, handler2); |
| dma_check(2, 3, 1); |
| |
| PICOTEST_END_SECTION(); |
| PICOTEST_START_SECTION("IV: Remove handler3 again"); |
| irq_remove_handler(DMA_IRQ_0, handler3); |
| dma_check(1, 1); |
| PICOTEST_END_SECTION(); |
| |
| PICOTEST_START_SECTION("IV: Remove handler1 again"); |
| irq_remove_handler(DMA_IRQ_0, handler1); |
| dma_check(0); |
| PICOTEST_END_SECTION(); |
| PICOTEST_END_TEST(); |
| } |
| |