1 /*
2  * Copyright (c) 2017 Oticon A/S
3  * Copyright (c) 2023 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  *
7  * HW IRQ controller model
8  */
9 
10 #include <stdint.h>
11 #include <stdbool.h>
12 #include "nsi_internal.h"
13 #include "nsi_cpu_if.h"
14 #include "nsi_cpu0_interrupts.h"
15 #include "irq_ctrl.h"
16 #include "nsi_tasks.h"
17 #include "nsi_hws_models_if.h"
18 
19 static uint64_t irq_ctrl_timer = NSI_NEVER;
20 
21 static uint64_t irq_status;  /* pending interrupts */
22 static uint64_t irq_premask; /* interrupts before the mask */
23 
24 /*
25  * Mask of which interrupts will actually cause the cpu to vector into its
26  * irq handler
27  * If an interrupt is masked in this way, it will be pending in the premask in
28  * case it is enabled later before clearing it.
29  * If the irq_mask enables and interrupt pending in irq_premask, it will cause
30  * the controller to raise the interrupt immediately
31  */
32 static uint64_t irq_mask;
33 
34 /*
35  * Interrupts lock/disable. When set, interrupts are registered
36  * (in the irq_status) but do not awake the cpu. if when unlocked,
37  * irq_status != 0 an interrupt will be raised immediately
38  */
39 static bool irqs_locked;
40 static bool lock_ignore; /* For the hard fake IRQ, temporarily ignore lock */
41 
42 static uint8_t irq_prio[N_IRQS]; /* Priority of each interrupt */
43 /* note that prio = 0 == highest, prio=255 == lowest */
44 
45 static int currently_running_prio = 256; /* 255 is the lowest prio interrupt */
46 
hw_irq_ctrl_init(void)47 static void hw_irq_ctrl_init(void)
48 {
49 	irq_mask = 0U; /* Let's assume all interrupts are disable at boot */
50 	irq_premask = 0U;
51 	irqs_locked = false;
52 	lock_ignore = false;
53 
54 	for (int i = 0 ; i < N_IRQS; i++) {
55 		irq_prio[i] = 255U;
56 	}
57 }
58 
59 NSI_TASK(hw_irq_ctrl_init, HW_INIT, 10);
60 
hw_irq_ctrl_set_cur_prio(int new)61 void hw_irq_ctrl_set_cur_prio(int new)
62 {
63 	currently_running_prio = new;
64 }
65 
hw_irq_ctrl_get_cur_prio(void)66 int hw_irq_ctrl_get_cur_prio(void)
67 {
68 	return currently_running_prio;
69 }
70 
hw_irq_ctrl_prio_set(unsigned int irq,unsigned int prio)71 void hw_irq_ctrl_prio_set(unsigned int irq, unsigned int prio)
72 {
73 	irq_prio[irq] = prio;
74 }
75 
hw_irq_ctrl_get_prio(unsigned int irq)76 uint8_t hw_irq_ctrl_get_prio(unsigned int irq)
77 {
78 	return irq_prio[irq];
79 }
80 
81 /**
82  * Get the currently pending highest priority interrupt which has a priority
83  * higher than a possibly currently running interrupt
84  *
85  * If none, return -1
86  */
hw_irq_ctrl_get_highest_prio_irq(void)87 int hw_irq_ctrl_get_highest_prio_irq(void)
88 {
89 	if (irqs_locked) {
90 		return -1;
91 	}
92 
93 	uint64_t irq_status = hw_irq_ctrl_get_irq_status();
94 	int winner = -1;
95 	int winner_prio = 256;
96 
97 	while (irq_status != 0U) {
98 		int irq_nbr = nsi_find_lsb_set64(irq_status) - 1;
99 
100 		irq_status &= ~((uint64_t) 1 << irq_nbr);
101 		if ((winner_prio > (int)irq_prio[irq_nbr])
102 		   && (currently_running_prio > (int)irq_prio[irq_nbr])) {
103 			winner = irq_nbr;
104 			winner_prio = irq_prio[irq_nbr];
105 		}
106 	}
107 	return winner;
108 }
109 
110 
hw_irq_ctrl_get_current_lock(void)111 uint32_t hw_irq_ctrl_get_current_lock(void)
112 {
113 	return irqs_locked;
114 }
115 
116 /*
117  * Change the overall interrupt controller "interrupt lock"
118  * The interrupt lock is a flag that provisionally disables all interrupts
119  * without affecting their status or their ability to be pended in the meanwhile
120  */
hw_irq_ctrl_change_lock(uint32_t new_lock)121 uint32_t hw_irq_ctrl_change_lock(uint32_t new_lock)
122 {
123 	uint32_t previous_lock = irqs_locked;
124 
125 	irqs_locked = new_lock;
126 
127 	if ((previous_lock == true) && (new_lock == false)) {
128 		if (irq_status != 0U) {
129 			nsif_cpu0_irq_raised_from_sw();
130 		}
131 	}
132 	return previous_lock;
133 }
134 
hw_irq_ctrl_get_irq_status(void)135 uint64_t hw_irq_ctrl_get_irq_status(void)
136 {
137 	return irq_status;
138 }
139 
hw_irq_ctrl_clear_all_enabled_irqs(void)140 void hw_irq_ctrl_clear_all_enabled_irqs(void)
141 {
142 	irq_status  = 0U;
143 	irq_premask &= ~irq_mask;
144 }
145 
hw_irq_ctrl_clear_all_irqs(void)146 void hw_irq_ctrl_clear_all_irqs(void)
147 {
148 	irq_status  = 0U;
149 	irq_premask = 0U;
150 }
151 
hw_irq_ctrl_disable_irq(unsigned int irq)152 void hw_irq_ctrl_disable_irq(unsigned int irq)
153 {
154 	irq_mask &= ~((uint64_t)1<<irq);
155 }
156 
hw_irq_ctrl_is_irq_enabled(unsigned int irq)157 int hw_irq_ctrl_is_irq_enabled(unsigned int irq)
158 {
159 	return (irq_mask & ((uint64_t)1 << irq))?1:0;
160 }
161 
162 /**
163  * Get the current interrupt enable mask
164  */
hw_irq_ctrl_get_irq_mask(void)165 uint64_t hw_irq_ctrl_get_irq_mask(void)
166 {
167 	return irq_mask;
168 }
169 
170 /*
171  * Un-pend an interrupt from the interrupt controller.
172  *
173  * This is an API between the MCU model/IRQ handling side and the IRQ controller
174  * model
175  */
hw_irq_ctrl_clear_irq(unsigned int irq)176 void hw_irq_ctrl_clear_irq(unsigned int irq)
177 {
178 	irq_status  &= ~((uint64_t)1<<irq);
179 	irq_premask &= ~((uint64_t)1<<irq);
180 }
181 
182 
183 /**
184  * Enable an interrupt
185  *
186  * This function may only be called from SW threads
187  *
188  * If the enabled interrupt is pending, it will immediately vector to its
189  * interrupt handler and continue (maybe with some swap() before)
190  */
hw_irq_ctrl_enable_irq(unsigned int irq)191 void hw_irq_ctrl_enable_irq(unsigned int irq)
192 {
193 	irq_mask |= ((uint64_t)1<<irq);
194 	if (irq_premask & ((uint64_t)1<<irq)) { /* if the interrupt is pending */
195 		hw_irq_ctrl_raise_im_from_sw(irq);
196 	}
197 }
198 
hw_irq_ctrl_irq_raise_prefix(unsigned int irq)199 static inline void hw_irq_ctrl_irq_raise_prefix(unsigned int irq)
200 {
201 	if (irq < N_IRQS) {
202 		irq_premask |= ((uint64_t)1<<irq);
203 
204 		if (irq_mask & ((uint64_t)1 << irq)) {
205 			irq_status |= ((uint64_t)1<<irq);
206 		}
207 	} else if (irq == PHONY_HARD_IRQ) {
208 		lock_ignore = true;
209 	}
210 }
211 
212 /**
213  * Set/Raise/Pend an interrupt
214  *
215  * This function is meant to be used by either the SW manual IRQ raising
216  * or by HW which wants the IRQ to be raised in one delta cycle from now
217  */
hw_irq_ctrl_set_irq(unsigned int irq)218 void hw_irq_ctrl_set_irq(unsigned int irq)
219 {
220 	hw_irq_ctrl_irq_raise_prefix(irq);
221 	if ((irqs_locked == false) || (lock_ignore)) {
222 		/*
223 		 * Awake CPU in 1 delta
224 		 * Note that we awake the CPU even if the IRQ is disabled
225 		 * => we assume the CPU is always idling in a WFE() like
226 		 * instruction and the CPU is allowed to awake just with the irq
227 		 * being marked as pending
228 		 */
229 		irq_ctrl_timer = nsi_hws_get_time();
230 		nsi_hws_find_next_event();
231 	}
232 }
233 
234 
irq_raising_from_hw_now(void)235 static void irq_raising_from_hw_now(void)
236 {
237 	/*
238 	 * We always awake the CPU even if the IRQ was masked,
239 	 * but not if irqs are locked unless this is due to a
240 	 * PHONY_HARD_IRQ
241 	 */
242 	if ((irqs_locked == false) || (lock_ignore)) {
243 		lock_ignore = false;
244 		nsif_cpu0_irq_raised();
245 	}
246 }
247 
248 /**
249  * Set/Raise/Pend an interrupt immediately.
250  * Like hw_irq_ctrl_set_irq() but awake immediately the CPU instead of in
251  * 1 delta cycle
252  *
253  * Call only from HW threads; Should be used with care
254  */
hw_irq_ctrl_raise_im(unsigned int irq)255 void hw_irq_ctrl_raise_im(unsigned int irq)
256 {
257 	hw_irq_ctrl_irq_raise_prefix(irq);
258 	irq_raising_from_hw_now();
259 }
260 
261 /**
262  * Like hw_irq_ctrl_raise_im() but for SW threads
263  *
264  * Call only from SW threads; Should be used with care
265  */
hw_irq_ctrl_raise_im_from_sw(unsigned int irq)266 void hw_irq_ctrl_raise_im_from_sw(unsigned int irq)
267 {
268 	hw_irq_ctrl_irq_raise_prefix(irq);
269 
270 	if (irqs_locked == false) {
271 		nsif_cpu0_irq_raised_from_sw();
272 	}
273 }
274 
hw_irq_ctrl_timer_triggered(void)275 static void hw_irq_ctrl_timer_triggered(void)
276 {
277 	irq_ctrl_timer = NSI_NEVER;
278 	irq_raising_from_hw_now();
279 	nsi_hws_find_next_event();
280 }
281 
282 NSI_HW_EVENT(irq_ctrl_timer, hw_irq_ctrl_timer_triggered, 900);
283