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