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