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