blob: 211f6d02b986d7aa4d007c025ed7faf04dca2299 [file] [log] [blame]
Austin Schuh208337d2022-01-01 14:29:11 -08001/*
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#include "hardware/irq.h"
8#include "hardware/regs/m0plus.h"
9#include "hardware/platform_defs.h"
10#include "hardware/structs/scb.h"
11
12#include "pico/mutex.h"
13#include "pico/assert.h"
14
15extern void __unhandled_user_irq(void);
16
17static inline irq_handler_t *get_vtable(void) {
18 return (irq_handler_t *) scb_hw->vtor;
19}
20
21static inline void *add_thumb_bit(void *addr) {
22 return (void *) (((uintptr_t) addr) | 0x1);
23}
24
25static inline void *remove_thumb_bit(void *addr) {
26 return (void *) (((uintptr_t) addr) & (uint)~0x1);
27}
28
29static void set_raw_irq_handler_and_unlock(uint num, irq_handler_t handler, uint32_t save) {
30 // update vtable (vtable_handler may be same or updated depending on cases, but we do it anyway for compactness)
31 get_vtable()[16 + num] = handler;
32 __dmb();
33 spin_unlock(spin_lock_instance(PICO_SPINLOCK_ID_IRQ), save);
34}
35
36void irq_set_enabled(uint num, bool enabled) {
37 check_irq_param(num);
38 irq_set_mask_enabled(1u << num, enabled);
39}
40
41bool irq_is_enabled(uint num) {
42 check_irq_param(num);
43 return 0 != ((1u << num) & *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)));
44}
45
46void irq_set_mask_enabled(uint32_t mask, bool enabled) {
47 if (enabled) {
48 // Clear pending before enable
49 // (if IRQ is actually asserted, it will immediately re-pend)
50 *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICPR_OFFSET)) = mask;
51 *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISER_OFFSET)) = mask;
52 } else {
53 *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ICER_OFFSET)) = mask;
54 }
55}
56
57void irq_set_pending(uint num) {
58 check_irq_param(num);
59 *((io_rw_32 *) (PPB_BASE + M0PLUS_NVIC_ISPR_OFFSET)) = 1u << num;
60}
61
62#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
63// limited by 8 bit relative links (and reality)
64static_assert(PICO_MAX_SHARED_IRQ_HANDLERS >= 1 && PICO_MAX_SHARED_IRQ_HANDLERS < 0x7f, "");
65
66// note these are not real functions, they are code fragments (i.e. don't call them)
67extern void irq_handler_chain_first_slot(void);
68extern void irq_handler_chain_remove_tail(void);
69
70extern struct irq_handler_chain_slot {
71 // first 3 half words are executable code (raw vtable handler points to one slot, and inst3 will jump to next
72 // in chain (or end of chain handler)
73 uint16_t inst1;
74 uint16_t inst2;
75 uint16_t inst3;
76 union {
77 // when a handler is removed while executing, it needs an extra instruction, which overwrites
78 // the link and the priority; this is ok because no one else is modifying the chain, as
79 // the chain is effectively core local, and the user code which might still need this link
80 // disable the IRQ in question before updating, which means we aren't executing!
81 struct {
82 int8_t link;
83 uint8_t priority;
84 };
85 uint16_t inst4;
86 };
87 irq_handler_t handler;
88} irq_handler_chain_slots[PICO_MAX_SHARED_IRQ_HANDLERS];
89
90static int8_t irq_hander_chain_free_slot_head;
91
92static inline bool is_shared_irq_raw_handler(irq_handler_t raw_handler) {
93 return (uintptr_t)raw_handler - (uintptr_t)irq_handler_chain_slots < sizeof(irq_handler_chain_slots);
94}
95#else
96#define is_shared_irq_raw_handler(h) false
97#endif
98
99irq_handler_t irq_get_vtable_handler(uint num) {
100 check_irq_param(num);
101 return get_vtable()[16 + num];
102}
103
104void irq_set_exclusive_handler(uint num, irq_handler_t handler) {
105 check_irq_param(num);
106#if !PICO_NO_RAM_VECTOR_TABLE
107 spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
108 uint32_t save = spin_lock_blocking(lock);
109 __unused irq_handler_t current = irq_get_vtable_handler(num);
110 hard_assert(current == __unhandled_user_irq || current == handler);
111 set_raw_irq_handler_and_unlock(num, handler, save);
112#else
113 panic_unsupported();
114#endif
115}
116
117irq_handler_t irq_get_exclusive_handler(uint num) {
118 check_irq_param(num);
119#if !PICO_NO_RAM_VECTOR_TABLE
120 spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
121 uint32_t save = spin_lock_blocking(lock);
122 irq_handler_t current = irq_get_vtable_handler(num);
123 spin_unlock(lock, save);
124 if (current == __unhandled_user_irq || is_shared_irq_raw_handler(current)) {
125 return NULL;
126 }
127 return current;
128#else
129 panic_unsupported();
130#endif
131}
132
133
134#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
135static uint16_t make_branch(uint16_t *from, void *to) {
136 uint32_t ui_from = (uint32_t)from;
137 uint32_t ui_to = (uint32_t)to;
138 uint32_t delta = (ui_to - ui_from - 4) / 2;
139 assert(!(delta >> 11u));
140 return (uint16_t)(0xe000 | (delta & 0x7ff));
141}
142
143static void insert_branch_and_link(uint16_t *from, void *to) {
144 uint32_t ui_from = (uint32_t)from;
145 uint32_t ui_to = (uint32_t)to;
146 uint32_t delta = (ui_to - ui_from - 4) / 2;
147 assert(!(delta >> 11u));
148 from[0] = (uint16_t)(0xf000 | ((delta >> 11u) & 0x7ffu));
149 from[1] = (uint16_t)(0xf800 | (delta & 0x7ffu));
150}
151
152static inline void *resolve_branch(uint16_t *inst) {
153 assert(0x1c == (*inst)>>11u);
154 int32_t i_addr = (*inst) << 21u;
155 i_addr /= (int32_t)(1u<<21u);
156 return inst + 2 + i_addr;
157}
158
159// GCC produces horrible code for subtraction of pointers here, and it was bugging me
160static inline int8_t slot_diff(struct irq_handler_chain_slot *to, struct irq_handler_chain_slot *from) {
161 static_assert(sizeof(struct irq_handler_chain_slot) == 12, "");
162 int32_t result = 0xaaaa;
163 // return (to - from);
164 // note this implementation has limited range, but is fine for plenty more than -128->127 result
165 asm (".syntax unified\n"
166 "subs %1, %2\n"
167 "adcs %1, %1\n" // * 2 (and + 1 if negative for rounding)
168 "muls %0, %1\n"
169 "lsrs %0, 20\n"
170 : "+l" (result), "+l" (to)
171 : "l" (from)
172 :
173 );
174 return (int8_t)result;
175}
176
177static inline int8_t get_slot_index(struct irq_handler_chain_slot *slot) {
178 return slot_diff(slot, irq_handler_chain_slots);
179}
180#endif
181
182void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority) {
183 check_irq_param(num);
184#if PICO_NO_RAM_VECTOR_TABLE
185 panic_unsupported()
186#elif PICO_DISABLE_SHARED_IRQ_HANDLERS
187 irq_set_exclusive_handler(num, handler);
188#else
189 spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
190 uint32_t save = spin_lock_blocking(lock);
191 hard_assert(irq_hander_chain_free_slot_head >= 0); // we must have a slot
192 struct irq_handler_chain_slot *slot = &irq_handler_chain_slots[irq_hander_chain_free_slot_head];
193 int8_t slot_index = irq_hander_chain_free_slot_head;
194 irq_hander_chain_free_slot_head = slot->link;
195 irq_handler_t vtable_handler = get_vtable()[16 + num];
196 if (!is_shared_irq_raw_handler(vtable_handler)) {
197 // start new chain
198 hard_assert(vtable_handler == __unhandled_user_irq);
199 struct irq_handler_chain_slot slot_data = {
200 .inst1 = 0xa100, // add r1, pc, #0
201 .inst2 = make_branch(&slot->inst2, irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
202 .inst3 = 0xbd00, // pop {pc}
203 .link = -1,
204 .priority = order_priority,
205 .handler = handler
206 };
207 *slot = slot_data;
208 vtable_handler = (irq_handler_t)add_thumb_bit(slot);
209 } else {
210 assert(!((((uintptr_t)vtable_handler) - ((uintptr_t)irq_handler_chain_slots) - 1)%sizeof(struct irq_handler_chain_slot)));
211 struct irq_handler_chain_slot *prev_slot = NULL;
212 struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler);
213 struct irq_handler_chain_slot *cur_slot = existing_vtable_slot;
214 while (cur_slot->priority > order_priority) {
215 prev_slot = cur_slot;
216 if (cur_slot->link < 0) break;
217 cur_slot = &irq_handler_chain_slots[cur_slot->link];
218 }
219 if (prev_slot) {
220 // insert into chain
221 struct irq_handler_chain_slot slot_data = {
222 .inst1 = 0x4801, // ldr r0, [pc, #4]
223 .inst2 = 0x4780, // blx r0
224 .inst3 = prev_slot->link >= 0 ?
225 make_branch(&slot->inst3, resolve_branch(&prev_slot->inst3)) : // b next_slot
226 0xbd00, // pop {pc}
227 .link = prev_slot->link,
228 .priority = order_priority,
229 .handler = handler
230 };
231 // update code and data links
232 prev_slot->inst3 = make_branch(&prev_slot->inst3, slot),
233 prev_slot->link = slot_index;
234 *slot = slot_data;
235 } else {
236 // update with new chain head
237 struct irq_handler_chain_slot slot_data = {
238 .inst1 = 0xa100, // add r1, pc, #0
239 .inst2 = make_branch(&slot->inst2, irq_handler_chain_first_slot), // b irq_handler_chain_first_slot
240 .inst3 = make_branch(&slot->inst3, existing_vtable_slot), // b existing_slot
241 .link = get_slot_index(existing_vtable_slot),
242 .priority = order_priority,
243 .handler = handler
244 };
245 *slot = slot_data;
246 // fixup previous head slot
247 existing_vtable_slot->inst1 = 0x4801; // ldr r0, [pc, #4]
248 existing_vtable_slot->inst2 = 0x4780; // blx r0
249 vtable_handler = (irq_handler_t)add_thumb_bit(slot);
250 }
251 }
252 set_raw_irq_handler_and_unlock(num, vtable_handler, save);
253#endif
254}
255
256void irq_remove_handler(uint num, irq_handler_t handler) {
257#if !PICO_NO_RAM_VECTOR_TABLE
258 spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
259 uint32_t save = spin_lock_blocking(lock);
260 irq_handler_t vtable_handler = get_vtable()[16 + num];
261 if (vtable_handler != __unhandled_user_irq && vtable_handler != handler) {
262#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
263 if (is_shared_irq_raw_handler(vtable_handler)) {
264 // This is a bit tricky, as an executing IRQ handler doesn't take a lock.
265
266 // First thing to do is to disable the IRQ in question; that takes care of calls from user code.
267 // Note that a irq handler chain is local to our own core, so we don't need to worry about the other core
268 bool was_enabled = irq_is_enabled(num);
269 irq_set_enabled(num, false);
270 __dmb();
271
272 // It is possible we are being called while an IRQ for this chain is already in progress.
273 // The issue we have here is that we must not free a slot that is currently being executed, because
274 // inst3 is still to be executed, and inst3 might get overwritten if the slot is re-used.
275
276 // By disallowing other exceptions from removing an IRQ handler (which seems fair)
277 // we now only have to worry about removing a slot from a chain that is currently executing.
278
279 // Note we expect that the slot we are deleting is the one that is executing.
280 // In particular, bad things happen if the caller were to delete the handler in the chain
281 // before it. This is not an allowed use case though, and I can't imagine anyone wanting to in practice.
282 // Sadly this is not something we can detect.
283
284 uint exception = __get_current_exception();
285 hard_assert(!exception || exception == num + 16);
286
287 struct irq_handler_chain_slot *prev_slot = NULL;
288 struct irq_handler_chain_slot *existing_vtable_slot = remove_thumb_bit(vtable_handler);
289 struct irq_handler_chain_slot *to_free_slot = existing_vtable_slot;
290 int8_t to_free_slot_index = get_slot_index(to_free_slot);
291 while (to_free_slot->handler != handler) {
292 prev_slot = to_free_slot;
293 if (to_free_slot->link < 0) break;
294 to_free_slot = &irq_handler_chain_slots[to_free_slot->link];
295 }
296 if (to_free_slot->handler == handler) {
297 int8_t next_slot_index = to_free_slot->link;
298 if (next_slot_index >= 0) {
299 // There is another slot in the chain, so copy that over us, so that our inst3 points at something valid
300 // Note this only matters in the exception case anyway, and it that case, we will skip the next handler,
301 // however in that case it's IRQ cause should immediately cause re-entry of the IRQ and the only side
302 // effect will be that there was potentially brief out of priority order execution of the handlers
303 struct irq_handler_chain_slot *next_slot = &irq_handler_chain_slots[next_slot_index];
304 to_free_slot->handler = next_slot->handler;
305 to_free_slot->priority = next_slot->priority;
306 to_free_slot->link = next_slot->link;
307 to_free_slot->inst3 = next_slot->link >= 0 ?
308 make_branch(&to_free_slot->inst3, resolve_branch(&next_slot->inst3)) : // b mext_>slot->next_slot
309 0xbd00; // pop {pc}
310
311 // add old next slot back to free list
312 next_slot->link = irq_hander_chain_free_slot_head;
313 irq_hander_chain_free_slot_head = next_slot_index;
314 } else {
315 // Slot being removed is at the end of the chain
316 if (!exception) {
317 // case when we're not in exception, we physically unlink now
318 if (prev_slot) {
319 // chain is not empty
320 prev_slot->link = -1;
321 prev_slot->inst3 = 0xbd00; // pop {pc}
322 } else {
323 // chain is not empty
324 vtable_handler = __unhandled_user_irq;
325 }
326 // add slot back to free list
327 to_free_slot->link = irq_hander_chain_free_slot_head;
328 irq_hander_chain_free_slot_head = to_free_slot_index;
329 } else {
330 // since we are the last slot we know that our inst3 hasn't executed yet, so we change
331 // it to bl to irq_handler_chain_remove_tail which will remove the slot.
332 // NOTE THAT THIS TRASHES PRIORITY AND LINK SINCE THIS IS A 4 BYTE INSTRUCTION
333 // BUT THEY ARE NOT NEEDED NOW
334 insert_branch_and_link(&to_free_slot->inst3, irq_handler_chain_remove_tail);
335 }
336 }
337 } else {
338 assert(false); // not found
339 }
340 irq_set_enabled(num, was_enabled);
341 }
342#else
343 assert(false); // not found
344#endif
345 } else {
346 vtable_handler = __unhandled_user_irq;
347 }
348 set_raw_irq_handler_and_unlock(num, vtable_handler, save);
349#else
350 panic_unsupported();
351#endif
352}
353
354void irq_set_priority(uint num, uint8_t hardware_priority) {
355 check_irq_param(num);
356
357 // note that only 32 bit writes are supported
358 io_rw_32 *p = (io_rw_32 *)((PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET) + (num & ~3u));
359 *p = (*p & ~(0xffu << (8 * (num & 3u)))) | (((uint32_t) hardware_priority) << (8 * (num & 3u)));
360}
361
362uint irq_get_priority(uint num) {
363 check_irq_param(num);
364
365 // note that only 32 bit reads are supported
366 io_rw_32 *p = (io_rw_32 *)((PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET) + (num & ~3u));
367 return (uint8_t)(*p >> (8 * (num & 3u)));
368}
369
370#if !PICO_DISABLE_SHARED_IRQ_HANDLERS
371// used by irq_handler_chain.S to remove the last link in a handler chain after it executes
372// note this must be called only with the last slot in a chain (and during the exception)
373void irq_add_tail_to_free_list(struct irq_handler_chain_slot *slot) {
374 irq_handler_t slot_handler = (irq_handler_t) add_thumb_bit(slot);
375 assert(is_shared_irq_raw_handler(slot_handler));
376
377 uint exception = __get_current_exception();
378 assert(exception);
379 spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_IRQ);
380 uint32_t save = spin_lock_blocking(lock);
381 int8_t slot_index = get_slot_index(slot);
382 if (slot_handler == get_vtable()[exception]) {
383 get_vtable()[exception] = __unhandled_user_irq;
384 } else {
385 bool __unused found = false;
386 // need to find who points at the slot and update it
387 for(uint i=0;i<count_of(irq_handler_chain_slots);i++) {
388 if (irq_handler_chain_slots[i].link == slot_index) {
389 irq_handler_chain_slots[i].link = -1;
390 irq_handler_chain_slots[i].inst3 = 0xbd00; // pop {pc}
391 found = true;
392 break;
393 }
394 }
395 assert(found);
396 }
397 // add slot to free list
398 slot->link = irq_hander_chain_free_slot_head;
399 irq_hander_chain_free_slot_head = slot_index;
400 spin_unlock(lock, save);
401}
402#endif
403
404void irq_init_priorities() {
405#if PICO_DEFAULT_IRQ_PRIORITY != 0
406 static_assert(!(NUM_IRQS & 3), "");
407 uint32_t prio4 = (PICO_DEFAULT_IRQ_PRIORITY & 0xff) * 0x1010101u;
408 io_rw_32 * p = (io_rw_32 *)(PPB_BASE + M0PLUS_NVIC_IPR0_OFFSET);
409 for (uint i = 0; i < NUM_IRQS / 4; i++) {
410 *p++ = prio4;
411 }
412#endif
413}