1 /*
2 * Copyright (c) 2014 Wind River Systems, Inc.
3 * Copyright (c) 2017 Oticon A/S
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * SW side of the IRQ handling
8 */
9
10 #include <stdint.h>
11 #include "irq_handler.h"
12 #include <zephyr/irq_offload.h>
13 #include <zephyr/kernel_structs.h>
14 #include "kernel_internal.h"
15 #include "kswap.h"
16 #include "irq_ctrl.h"
17 #include "posix_core.h"
18 #include "board_soc.h"
19 #include <zephyr/sw_isr_table.h>
20 #include "soc.h"
21 #include <zephyr/tracing/tracing.h>
22
23 typedef void (*normal_irq_f_ptr)(const void *);
24 typedef int (*direct_irq_f_ptr)(void);
25
26 typedef struct _isr_list isr_table_entry_t;
27 static isr_table_entry_t irq_vector_table[N_IRQS] = { { 0 } };
28
29 static int currently_running_irq = -1;
30
vector_to_irq(int irq_nbr,int * may_swap)31 static inline void vector_to_irq(int irq_nbr, int *may_swap)
32 {
33 sys_trace_isr_enter();
34
35 if (irq_vector_table[irq_nbr].func == NULL) { /* LCOV_EXCL_BR_LINE */
36 /* LCOV_EXCL_START */
37 posix_print_error_and_exit("Received irq %i without a "
38 "registered handler\n",
39 irq_nbr);
40 /* LCOV_EXCL_STOP */
41 } else {
42 if (irq_vector_table[irq_nbr].flags & ISR_FLAG_DIRECT) {
43 *may_swap |= ((direct_irq_f_ptr)
44 irq_vector_table[irq_nbr].func)();
45 } else {
46 #ifdef CONFIG_PM
47 posix_irq_check_idle_exit();
48 #endif
49 ((normal_irq_f_ptr)irq_vector_table[irq_nbr].func)
50 (irq_vector_table[irq_nbr].param);
51 *may_swap = 1;
52 }
53 }
54
55 sys_trace_isr_exit();
56 }
57
58 /**
59 * When an interrupt is raised, this function is called to handle it and, if
60 * needed, swap to a re-enabled thread
61 *
62 * Note that even that this function is executing in a Zephyr thread, it is
63 * effectively the model of the interrupt controller passing context to the IRQ
64 * handler and therefore its priority handling
65 */
posix_irq_handler(void)66 void posix_irq_handler(void)
67 {
68 uint64_t irq_lock;
69 int irq_nbr;
70 static int may_swap;
71
72 irq_lock = hw_irq_ctrl_get_current_lock();
73
74 if (irq_lock) {
75 /* "spurious" wakes can happen with interrupts locked */
76 return;
77 }
78
79 if (_kernel.cpus[0].nested == 0) {
80 may_swap = 0;
81 }
82
83 _kernel.cpus[0].nested++;
84
85 while ((irq_nbr = hw_irq_ctrl_get_highest_prio_irq()) != -1) {
86 int last_current_running_prio = hw_irq_ctrl_get_cur_prio();
87 int last_running_irq = currently_running_irq;
88
89 hw_irq_ctrl_set_cur_prio(hw_irq_ctrl_get_prio(irq_nbr));
90 hw_irq_ctrl_clear_irq(irq_nbr);
91
92 currently_running_irq = irq_nbr;
93 vector_to_irq(irq_nbr, &may_swap);
94 currently_running_irq = last_running_irq;
95
96 hw_irq_ctrl_set_cur_prio(last_current_running_prio);
97 }
98
99 _kernel.cpus[0].nested--;
100
101 /* Call swap if all the following is true:
102 * 1) may_swap was enabled
103 * 2) We are not nesting irq_handler calls (interrupts)
104 * 3) Next thread to run in the ready queue is not this thread
105 */
106 if (may_swap
107 && (hw_irq_ctrl_get_cur_prio() == 256)
108 && (_kernel.ready_q.cache) && (_kernel.ready_q.cache != arch_current_thread())) {
109
110 (void)z_swap_irqlock(irq_lock);
111 }
112 }
113
114 /**
115 * Thru this function the IRQ controller can raise an immediate interrupt which
116 * will interrupt the SW itself
117 * (this function should only be called from the HW model code, from SW threads)
118 */
posix_irq_handler_im_from_sw(void)119 void posix_irq_handler_im_from_sw(void)
120 {
121 /*
122 * if a higher priority interrupt than the possibly currently running is
123 * pending we go immediately into irq_handler() to vector into its
124 * handler
125 */
126 if (hw_irq_ctrl_get_highest_prio_irq() != -1) {
127 if (!posix_is_cpu_running()) { /* LCOV_EXCL_BR_LINE */
128 /* LCOV_EXCL_START */
129 posix_print_error_and_exit("programming error: %s "
130 "called from a HW model thread\n",
131 __func__);
132 /* LCOV_EXCL_STOP */
133 }
134 posix_irq_handler();
135 }
136 }
137
138 /**
139 * @brief Disable all interrupts on the CPU
140 *
141 * This routine disables interrupts. It can be called from either interrupt,
142 * task or fiber level. This routine returns an architecture-dependent
143 * lock-out key representing the "interrupt disable state" prior to the call;
144 * this key can be passed to irq_unlock() to re-enable interrupts.
145 *
146 * The lock-out key should only be used as the argument to the irq_unlock()
147 * API. It should never be used to manually re-enable interrupts or to inspect
148 * or manipulate the contents of the source register.
149 *
150 * This function can be called recursively: it will return a key to return the
151 * state of interrupt locking to the previous level.
152 *
153 * WARNINGS
154 * Invoking a kernel routine with interrupts locked may result in
155 * interrupts being re-enabled for an unspecified period of time. If the
156 * called routine blocks, interrupts will be re-enabled while another
157 * thread executes, or while the system is idle.
158 *
159 * The "interrupt disable state" is an attribute of a thread. Thus, if a
160 * fiber or task disables interrupts and subsequently invokes a kernel
161 * routine that causes the calling thread to block, the interrupt
162 * disable state will be restored when the thread is later rescheduled
163 * for execution.
164 *
165 * @return An architecture-dependent lock-out key representing the
166 * "interrupt disable state" prior to the call.
167 *
168 */
posix_irq_lock(void)169 unsigned int posix_irq_lock(void)
170 {
171 return hw_irq_ctrl_change_lock(true);
172 }
173
174 /**
175 * @brief Enable all interrupts on the CPU
176 *
177 * This routine re-enables interrupts on the CPU. The @a key parameter is a
178 * board-dependent lock-out key that is returned by a previous invocation of
179 * board_irq_lock().
180 *
181 * This routine can be called from either interrupt, task or fiber level.
182 */
posix_irq_unlock(unsigned int key)183 void posix_irq_unlock(unsigned int key)
184 {
185 hw_irq_ctrl_change_lock(key);
186 }
187
posix_irq_full_unlock(void)188 void posix_irq_full_unlock(void)
189 {
190 hw_irq_ctrl_change_lock(false);
191 }
192
posix_irq_enable(unsigned int irq)193 void posix_irq_enable(unsigned int irq)
194 {
195 hw_irq_ctrl_enable_irq(irq);
196 }
197
posix_irq_disable(unsigned int irq)198 void posix_irq_disable(unsigned int irq)
199 {
200 hw_irq_ctrl_disable_irq(irq);
201 }
202
posix_irq_is_enabled(unsigned int irq)203 int posix_irq_is_enabled(unsigned int irq)
204 {
205 return hw_irq_ctrl_is_irq_enabled(irq);
206 }
207
posix_get_current_irq(void)208 int posix_get_current_irq(void)
209 {
210 return currently_running_irq;
211 }
212
213 /**
214 * Configure a static interrupt.
215 *
216 * posix_isr_declare will populate the interrupt table table with the
217 * interrupt's parameters, the vector table and the software ISR table.
218 *
219 * We additionally set the priority in the interrupt controller at
220 * runtime.
221 *
222 * @param irq_p IRQ line number
223 * @param flags [plug it directly (1), or as a SW managed interrupt (0)]
224 * @param isr_p Interrupt service routine
225 * @param isr_param_p ISR parameter
226 * @param flags_p IRQ options
227 */
posix_isr_declare(unsigned int irq_p,int flags,void isr_p (const void *),const void * isr_param_p)228 void posix_isr_declare(unsigned int irq_p, int flags, void isr_p(const void *),
229 const void *isr_param_p)
230 {
231 if (irq_p >= N_IRQS) {
232 posix_print_error_and_exit("Attempted to configure not existent interrupt %u\n",
233 irq_p);
234 return;
235 }
236 irq_vector_table[irq_p].irq = irq_p;
237 irq_vector_table[irq_p].func = isr_p;
238 irq_vector_table[irq_p].param = isr_param_p;
239 irq_vector_table[irq_p].flags = flags;
240 }
241
242 /**
243 * @internal
244 *
245 * @brief Set an interrupt's priority
246 *
247 * Lower values take priority over higher values.
248 */
posix_irq_priority_set(unsigned int irq,unsigned int prio,uint32_t flags)249 void posix_irq_priority_set(unsigned int irq, unsigned int prio, uint32_t flags)
250 {
251 hw_irq_ctrl_prio_set(irq, prio);
252 }
253
254 /**
255 * Similar to ARM's NVIC_SetPendingIRQ
256 * set a pending IRQ from SW
257 *
258 * Note that this will interrupt immediately if the interrupt is not masked and
259 * IRQs are not locked, and this interrupt has higher priority than a possibly
260 * currently running interrupt
261 */
posix_sw_set_pending_IRQ(unsigned int IRQn)262 void posix_sw_set_pending_IRQ(unsigned int IRQn)
263 {
264 hw_irq_ctrl_raise_im_from_sw(IRQn);
265 }
266
267 /**
268 * Similar to ARM's NVIC_ClearPendingIRQ
269 * clear a pending irq from SW
270 */
posix_sw_clear_pending_IRQ(unsigned int IRQn)271 void posix_sw_clear_pending_IRQ(unsigned int IRQn)
272 {
273 hw_irq_ctrl_clear_irq(IRQn);
274 }
275
276 #ifdef CONFIG_IRQ_OFFLOAD
277 /**
278 * Storage for functions offloaded to IRQ
279 */
280 static void (*off_routine)(const void *);
281 static const void *off_parameter;
282
283 /**
284 * IRQ handler for the SW interrupt assigned to irq_offload()
285 */
offload_sw_irq_handler(const void * a)286 static void offload_sw_irq_handler(const void *a)
287 {
288 ARG_UNUSED(a);
289 off_routine(off_parameter);
290 }
291
292 /**
293 * @brief Run a function in interrupt context
294 *
295 * Raise the SW IRQ assigned to handled this
296 */
posix_irq_offload(void (* routine)(const void *),const void * parameter)297 void posix_irq_offload(void (*routine)(const void *), const void *parameter)
298 {
299 off_routine = routine;
300 off_parameter = parameter;
301 posix_isr_declare(OFFLOAD_SW_IRQ, 0, offload_sw_irq_handler, NULL);
302 posix_irq_enable(OFFLOAD_SW_IRQ);
303 posix_sw_set_pending_IRQ(OFFLOAD_SW_IRQ);
304 posix_irq_disable(OFFLOAD_SW_IRQ);
305 }
306 #endif /* CONFIG_IRQ_OFFLOAD */
307