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