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