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