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