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