1 /**
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <stdio.h>
8 #include <stdarg.h>
9 #include "pico/stdlib.h"
10 #include "pico/test.h"
11 #include "pico/time.h"
12 #include "hardware/irq.h"
13 #include "hardware/dma.h"
14 #include "hardware/structs/scb.h"
15 
16 PICOTEST_MODULE_NAME("IRQ", "IRQ Handler test");
17 
18 extern void __unhandled_user_irq(void);
19 
20 static bool remove_handler1_in_dma;
21 static bool remove_handler2_in_dma;
22 static bool remove_handler3_in_dma;
23 static dma_channel_config config;
24 
25 #define MAX_FIRE_COUNT 4
26 static int fire_count;
27 static int fired[MAX_FIRE_COUNT];
28 
29 static uint32_t dma_to = 0;
30 static uint32_t dma_from = 0xaaaa5555;
31 
record_fire(int which)32 int record_fire(int which) {
33     PICOTEST_CHECK(fire_count < MAX_FIRE_COUNT, "too many firings");
34     fired[fire_count++] = which;
35     return 0;
36 }
37 
handler1(void)38 void __isr handler1(void) {
39     record_fire(1);
40     dma_channel_acknowledge_irq0(0);
41     if (remove_handler1_in_dma) {
42         irq_remove_handler(DMA_IRQ_0, handler1);
43         remove_handler1_in_dma = false;
44     }
45 }
46 
handler2(void)47 void __isr handler2(void) {
48     record_fire(2);
49     dma_channel_acknowledge_irq0(0);
50     if (remove_handler2_in_dma) {
51         irq_remove_handler(DMA_IRQ_0, handler2);
52         remove_handler2_in_dma = false;
53     }
54 }
55 
handler3(void)56 void __isr handler3(void) {
57     record_fire(3);
58     dma_channel_acknowledge_irq0(0);
59     if (remove_handler3_in_dma) {
60         irq_remove_handler(DMA_IRQ_0, handler3);
61         remove_handler3_in_dma = false;
62     }
63 }
64 
get_vtable(void)65 static inline irq_handler_t *get_vtable(void) {
66     return (irq_handler_t *) scb_hw->vtor;
67 }
68 
dma_check(int expected,...)69 int dma_check(int expected, ...) {
70     if (expected == 0) {
71         // 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.
72         PICOTEST_CHECK_AND_ABORT(get_vtable()[16 + DMA_IRQ_0] == __unhandled_user_irq, "Expected there to be no IRQ handlers");
73         return 0;
74     }
75     fire_count = 0;
76     dma_channel_configure(0, &config, &dma_to, &dma_from, 1, true);
77     sleep_ms(100);
78     va_list args;
79     va_start(args, expected);
80     bool ok = expected == fire_count;
81     for(int i=0;ok && i<expected;i++) {
82         if (fired[i] != va_arg(args, int)) {
83             ok = false;
84             break;
85         }
86     }
87     va_end(args);
88     if (!ok) {
89         PICOTEST_CHECK(ok, "DMA handlers were not called in the order expected");
90         printf("  EXPECTED handlers: ");
91         va_start(args, expected);
92         for(int i=0;i<expected;i++) {
93             if (i) printf(", ");
94             printf("%d", va_arg(args, int));
95         }
96         printf("\n");
97         va_end(args);
98         printf("  CALLED handlers:   ");
99         for(int i=0;i<fire_count;i++) {
100             if (i) printf(", ");
101             printf("%d", fired[i]);
102         }
103         printf("\n");
104         return -1;
105     }
106     return 0;
107 }
108 
main()109 int main() {
110     stdio_init_all();
111 
112     PICOTEST_START();
113     config = dma_channel_get_default_config(0);
114     dma_channel_set_irq0_enabled(0, true);
115     irq_set_enabled(DMA_IRQ_0, true);
116 
117     // part I, just add/remove two handlers
118     PICOTEST_START_SECTION("I: Add handler1 default priority");
119         irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
120         dma_check(1, 1);
121     PICOTEST_END_SECTION();
122 
123     PICOTEST_START_SECTION("I: Add handler2 default priority");
124         irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
125         dma_check(2, 2, 1);
126     PICOTEST_END_SECTION();
127 
128     PICOTEST_START_SECTION("I: Remove handler1");
129         irq_remove_handler(DMA_IRQ_0, handler1);
130         dma_check(1, 2);
131     PICOTEST_END_SECTION();
132 
133     PICOTEST_START_SECTION("I: Remove handler2");
134         irq_remove_handler(DMA_IRQ_0, handler2);
135         dma_check(0);
136     PICOTEST_END_SECTION();
137 
138     // part II, add/remove three handlers including one in the middle
139 
140     PICOTEST_START_SECTION("II: Add handler3 default priority");
141         irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
142         dma_check(1, 3);
143     PICOTEST_END_SECTION();
144 
145     PICOTEST_START_SECTION("II: Add handler2 default priority");
146         irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
147         dma_check(2, 2, 3);
148     PICOTEST_END_SECTION();
149 
150     PICOTEST_START_SECTION("II: Add handler1 default priority");
151         irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
152         dma_check(3, 1, 2, 3);
153     PICOTEST_END_SECTION();
154 
155     PICOTEST_START_SECTION("II: Remove handler2");
156         irq_remove_handler(DMA_IRQ_0, handler2);
157         dma_check(2, 1, 3);
158     PICOTEST_END_SECTION();
159 
160     PICOTEST_START_SECTION("II: Remove handler3");
161         irq_remove_handler(DMA_IRQ_0, handler3);
162         dma_check(1, 1);
163     PICOTEST_END_SECTION();
164 
165     PICOTEST_START_SECTION("II: Remove handler1");
166         irq_remove_handler(DMA_IRQ_0, handler1);
167         dma_check(0);
168     PICOTEST_END_SECTION();
169 
170     // part III, the same as part II, but removing the handlers during the IRQ
171 
172     PICOTEST_START_SECTION("III: Add handler3 default priority");
173         irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
174         dma_check(1, 3);
175     PICOTEST_END_SECTION();
176 
177     PICOTEST_START_SECTION("III: Add handler2 default priority");
178         irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
179         dma_check(2, 2, 3);
180     PICOTEST_END_SECTION();
181 
182     PICOTEST_START_SECTION("III: Add handler1 default priority");
183         irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
184         dma_check(3, 1, 2, 3);
185     PICOTEST_END_SECTION();
186 
187     PICOTEST_START_SECTION("III: Remove handler2");
188         remove_handler2_in_dma = true;
189         // note that this is the defined behavior, that any handlers after the removed handler are not called. the
190         // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever
191         // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same
192         // interrupt handler in our case, this does not happen.
193         dma_check(2, 1, 2);
194         // but we call again to check
195         dma_check(2, 1, 3);
196     PICOTEST_END_SECTION();
197 
198     PICOTEST_START_SECTION("III: Remove handler3");
199         remove_handler3_in_dma = true;
200         // note that this is the defined behavior, that any handlers after the removed handler are not called. the
201         // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever
202         // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same
203         // interrupt handler in our case, this does not happen.
204         dma_check(2, 1, 3);
205         // but we call again to check
206         dma_check(1, 1);
207     PICOTEST_END_SECTION();
208 
209     PICOTEST_START_SECTION("III: Remove handler3");
210         remove_handler1_in_dma = true;
211         // note that this is the defined behavior, that any handlers after the removed handler are not called. the
212         // reasoning is that the same IRQ will immediately be re-entered, as we have missed clearing an interrupt (in whichever
213         // handlers were not called), and we will call any remaining handlers then. Because each IRQ handler is clearing the same
214         // interrupt handler in our case, this does not happen.
215         dma_check(1, 1);
216         // but we call again to check
217         dma_check(0);
218     PICOTEST_END_SECTION();
219 
220     // part IV, checking priorities
221     PICOTEST_START_SECTION("IV: Add handler1 high priority");
222         irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY);
223         dma_check(1, 1);
224     PICOTEST_END_SECTION();
225 
226     PICOTEST_START_SECTION("IV: Add handler2 normal priority");
227         irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
228         dma_check(2, 1, 2);
229     PICOTEST_END_SECTION();
230 
231     PICOTEST_START_SECTION("IV: Add handler3 lowest priority");
232         irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY);
233         dma_check(3, 1, 2, 3);
234     PICOTEST_END_SECTION();
235 
236     PICOTEST_START_SECTION("IV: Remove handler3");
237         irq_remove_handler(DMA_IRQ_0, handler3);
238         dma_check(2, 1, 2);
239     PICOTEST_END_SECTION();
240 
241     PICOTEST_START_SECTION("IV: Add handler3 normal priority");
242         irq_add_shared_handler(DMA_IRQ_0, handler3, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
243         dma_check(3, 1, 3, 2);
244     PICOTEST_END_SECTION();
245 
246     PICOTEST_START_SECTION("IV: Remove handler2");
247         irq_remove_handler(DMA_IRQ_0, handler2);
248         dma_check(2, 1, 3);
249     PICOTEST_END_SECTION();
250 
251     PICOTEST_START_SECTION("IV: Add handler2 normal priority - 2");
252         irq_add_shared_handler(DMA_IRQ_0, handler2, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY - 2);
253         dma_check(3, 1, 3, 2);
254     PICOTEST_END_SECTION();
255 
256     PICOTEST_START_SECTION("IV: Remove handler1");
257         irq_remove_handler(DMA_IRQ_0, handler1);
258         dma_check(2, 3, 2);
259     PICOTEST_END_SECTION();
260 
261     PICOTEST_START_SECTION("IV: Add handler1 normal priority - 1");
262         irq_add_shared_handler(DMA_IRQ_0, handler1, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY - 1);
263         dma_check(3, 3, 1, 2);
264     PICOTEST_END_SECTION();
265 
266     PICOTEST_START_SECTION("IV: Remove handler2 again");
267         irq_remove_handler(DMA_IRQ_0, handler2);
268         dma_check(2, 3, 1);
269 
270     PICOTEST_END_SECTION();
271     PICOTEST_START_SECTION("IV: Remove handler3 again");
272         irq_remove_handler(DMA_IRQ_0, handler3);
273         dma_check(1, 1);
274     PICOTEST_END_SECTION();
275 
276     PICOTEST_START_SECTION("IV: Remove handler1 again");
277         irq_remove_handler(DMA_IRQ_0, handler1);
278         dma_check(0);
279     PICOTEST_END_SECTION();
280     PICOTEST_END_TEST();
281 }
282 
283