1 /*
2  * Arm SCP/MCP Software
3  * Copyright (c) 2015-2022, Arm Limited and Contributors. All rights reserved.
4  *
5  * SPDX-License-Identifier: BSD-3-Clause
6  *
7  * Description:
8  *     Interrupt management.
9  */
10 
11 #include <fwk_arch.h>
12 #include <fwk_assert.h>
13 #include <fwk_interrupt.h>
14 #include <fwk_macros.h>
15 #include <fwk_mm.h>
16 #include <fwk_noreturn.h>
17 #include <fwk_status.h>
18 
19 #include <arch_exceptions.h>
20 
21 #include <fmw_cmsis.h>
22 
23 #include <limits.h>
24 #include <stdbool.h>
25 #include <stddef.h>
26 #include <stdint.h>
27 #include <string.h>
28 
29 /* We use short enums, so avoid truncation of larger unsigned IRQ numbers */
30 #define IRQN_TYPE_MAX \
31     ((uint64_t)(((UINT64_C(1) << (sizeof(IRQn_Type) * CHAR_BIT)) - 1U) / 2U))
32 
33 static uint32_t isr_count;
34 static uint32_t irq_count;
35 
36 static_assert(
37     UINT32_MAX >= IRQN_TYPE_MAX,
38     "`uint32_t` cannot hold all possible IRQ numbers");
39 
40 static_assert(
41     UINT_MAX >= IRQN_TYPE_MAX,
42     "`unsigned int` cannot hold all possible IRQ numbers");
43 
44 static_assert(
45     sizeof(IRQn_Type) >= sizeof(int16_t),
46     "`IRQn_Type` cannot hold all possible IRQ numbers");
47 
48 /*
49  * For interrupts with parameters, their entry in the vector table points to a
50  * global handler that calls a registered function in the callback table with a
51  * corresponding parameter. Entries in the vector table for interrupts without
52  * parameters point directly to the handler functions.
53  *
54  * Entry indices are offset by -1 relative to their interrupt numbers, as no
55  * interrupt may have an interrupt number of zero.
56  */
57 struct irq_callback {
58     void (*func)(uintptr_t param);
59     uintptr_t param;
60 };
61 
62 static struct irq_callback *callback;
63 
irq_global(void)64 static void irq_global(void)
65 {
66     struct irq_callback *entry = &callback[__get_IPSR() - 1];
67 
68     entry->func(entry->param);
69 }
70 
global_enable(void)71 static int global_enable(void)
72 {
73     __enable_irq();
74 
75     return FWK_SUCCESS;
76 }
77 
global_disable(void)78 static int global_disable(void)
79 {
80     __disable_irq();
81 
82     return FWK_SUCCESS;
83 }
84 
is_enabled(unsigned int interrupt,bool * enabled)85 static int is_enabled(unsigned int interrupt, bool *enabled)
86 {
87     if (interrupt >= irq_count) {
88         return FWK_E_PARAM;
89     }
90 
91     *enabled = NVIC_GetEnableIRQ((enum IRQn)interrupt) != 0;
92 
93     return FWK_SUCCESS;
94 }
95 
enable(unsigned int interrupt)96 static int enable(unsigned int interrupt)
97 {
98     if (interrupt >= irq_count) {
99         return FWK_E_PARAM;
100     }
101 
102     NVIC_EnableIRQ((enum IRQn)interrupt);
103 
104     return FWK_SUCCESS;
105 }
106 
disable(unsigned int interrupt)107 static int disable(unsigned int interrupt)
108 {
109     if (interrupt >= irq_count) {
110         return FWK_E_PARAM;
111     }
112 
113     NVIC_DisableIRQ((enum IRQn)interrupt);
114 
115     return FWK_SUCCESS;
116 }
117 
is_pending(unsigned int interrupt,bool * pending)118 static int is_pending(unsigned int interrupt, bool *pending)
119 {
120     if (interrupt >= irq_count) {
121         return FWK_E_PARAM;
122     }
123 
124     *pending = NVIC_GetPendingIRQ((enum IRQn)interrupt) != 0;
125 
126     return FWK_SUCCESS;
127 }
128 
set_pending(unsigned int interrupt)129 static int set_pending(unsigned int interrupt)
130 {
131     if (interrupt >= irq_count) {
132         return FWK_E_PARAM;
133     }
134 
135     NVIC_SetPendingIRQ((enum IRQn)interrupt);
136 
137     return FWK_SUCCESS;
138 }
139 
clear_pending(unsigned int interrupt)140 static int clear_pending(unsigned int interrupt)
141 {
142     if (interrupt >= irq_count) {
143         return FWK_E_PARAM;
144     }
145 
146     NVIC_ClearPendingIRQ((enum IRQn)interrupt);
147 
148     return FWK_SUCCESS;
149 }
150 
set_isr_irq(unsigned int interrupt,void (* isr)(void))151 static int set_isr_irq(unsigned int interrupt, void (*isr)(void))
152 {
153     if (interrupt >= irq_count) {
154         return FWK_E_PARAM;
155     }
156 
157     NVIC_SetVector((enum IRQn)interrupt, (uint32_t)isr);
158 
159     return FWK_SUCCESS;
160 }
161 
set_isr_irq_param(unsigned int interrupt,void (* isr)(uintptr_t param),uintptr_t parameter)162 static int set_isr_irq_param(
163     unsigned int interrupt,
164     void (*isr)(uintptr_t param),
165     uintptr_t parameter)
166 {
167     struct irq_callback *entry;
168     if (interrupt >= irq_count) {
169         return FWK_E_PARAM;
170     }
171 
172     entry = &callback[NVIC_USER_IRQ_OFFSET + interrupt - 1];
173     entry->func = isr;
174     entry->param = parameter;
175 
176     NVIC_SetVector((enum IRQn)interrupt, (uint32_t)irq_global);
177 
178     return FWK_SUCCESS;
179 }
180 
set_isr_nmi(void (* isr)(void))181 static int set_isr_nmi(void (*isr)(void))
182 {
183     NVIC_SetVector(NonMaskableInt_IRQn, (uint32_t)isr);
184 
185     return FWK_SUCCESS;
186 }
187 
set_isr_nmi_param(void (* isr)(uintptr_t param),uintptr_t parameter)188 static int set_isr_nmi_param(void (*isr)(uintptr_t param), uintptr_t parameter)
189 {
190     struct irq_callback *entry;
191 
192     entry = &callback[NVIC_USER_IRQ_OFFSET + (int)NonMaskableInt_IRQn - 1];
193     entry->func = isr;
194     entry->param = parameter;
195 
196     NVIC_SetVector(NonMaskableInt_IRQn, (uint32_t)irq_global);
197 
198     return FWK_SUCCESS;
199 }
200 
set_isr_fault(void (* isr)(void))201 static int set_isr_fault(void (*isr)(void))
202 {
203     NVIC_SetVector(HardFault_IRQn, (uint32_t)isr);
204     NVIC_SetVector(MemoryManagement_IRQn, (uint32_t)isr);
205     NVIC_SetVector(BusFault_IRQn, (uint32_t)isr);
206     NVIC_SetVector(UsageFault_IRQn, (uint32_t)isr);
207 
208     return FWK_SUCCESS;
209 }
210 
get_current(unsigned int * interrupt)211 static int get_current(unsigned int *interrupt)
212 {
213     *interrupt = __get_IPSR();
214 
215     /* Not an interrupt */
216     if (*interrupt == 0) {
217         return FWK_E_STATE;
218     }
219 
220     if (*interrupt == (NVIC_USER_IRQ_OFFSET + (int)NonMaskableInt_IRQn)) {
221         *interrupt = FWK_INTERRUPT_NMI;
222     } else if (*interrupt < NVIC_USER_IRQ_OFFSET) {
223         *interrupt = FWK_INTERRUPT_EXCEPTION;
224     } else {
225         *interrupt -= NVIC_USER_IRQ_OFFSET;
226     }
227 
228     return FWK_SUCCESS;
229 }
230 
is_interrupt_context(void)231 static bool is_interrupt_context(void)
232 {
233     /* Not an interrupt */
234     if (__get_IPSR() == 0) {
235         return false;
236     }
237 
238     return true;
239 }
240 
241 static const struct fwk_arch_interrupt_driver arch_nvic_driver = {
242     .global_enable = global_enable,
243     .global_disable = global_disable,
244     .is_enabled = is_enabled,
245     .enable = enable,
246     .disable = disable,
247     .is_pending = is_pending,
248     .set_pending = set_pending,
249     .clear_pending = clear_pending,
250     .set_isr_irq = set_isr_irq,
251     .set_isr_irq_param = set_isr_irq_param,
252     .set_isr_nmi = set_isr_nmi,
253     .set_isr_nmi_param = set_isr_nmi_param,
254     .set_isr_fault = set_isr_fault,
255     .get_current = get_current,
256     .is_interrupt_context = is_interrupt_context,
257 };
258 
irq_invalid(void)259 static void irq_invalid(void)
260 {
261     (void)disable(__get_IPSR());
262 }
263 
arch_nvic_init(const struct fwk_arch_interrupt_driver ** driver)264 int arch_nvic_init(const struct fwk_arch_interrupt_driver **driver)
265 {
266     uint32_t ictr_intlinesnum;
267     uint32_t align_entries;
268     uint32_t align_word;
269     uint32_t *vector;
270     uint32_t irq;
271 
272     if (driver == NULL) {
273         return FWK_E_PARAM;
274     }
275 
276     /* Find the number of interrupt lines implemented in hardware */
277     ictr_intlinesnum = SCnSCB->ICTR & SCnSCB_ICTR_INTLINESNUM_Msk;
278     irq_count = (ictr_intlinesnum + 1) * 32;
279     isr_count = NVIC_USER_IRQ_OFFSET + irq_count;
280 
281     /*
282      * irq_count holds the amount of IRQ meanwhile IRQN_TYPE_MAX holds the
283      * maximum IRQ number (for type size), that is the reason there is a +1 in
284      * the comparison.
285      */
286     fwk_assert(irq_count <= (IRQN_TYPE_MAX + 1));
287 
288     /*
289      * Allocate and initialize a table for the callback functions and their
290      * corresponding parameters.
291      */
292     callback = fwk_mm_calloc(isr_count, sizeof(callback[0]));
293 
294     /*
295      * The base address for the vector table must align on the number of
296      * entries in the table, corresponding to a word boundary rounded up to the
297      * next power of two.
298      *
299      * For example, for a vector table with 48 entries, the base address must be
300      * on a 64-word boundary.
301      */
302 
303     /* Calculate the next power of two */
304     align_entries = UINT32_C(1) << (32U - __CLZ(isr_count - 1U));
305 
306     /* Calculate alignment on a word boundary */
307     align_word = align_entries * sizeof(vector[0]);
308 
309     /* Allocate and wipe the new vector table */
310     vector = fwk_mm_calloc_aligned(align_word, isr_count, sizeof(vector[0]));
311 
312     /* Copy the processor exception table over to the new vector table */
313     (void)memcpy(
314         vector,
315         (const void *)SCB->VTOR,
316         NVIC_USER_IRQ_OFFSET * sizeof(vector[0]));
317 
318     __DMB();
319 
320     __disable_irq();
321 
322     /* Switch to the new vector table */
323     SCB->VTOR = (uint32_t)vector;
324 
325     /* Initialize IRQs */
326     for (irq = 0; irq < irq_count; irq++) {
327         /* Ensure IRQs are disabled during boot sequence */
328         NVIC_DisableIRQ((IRQn_Type)irq);
329         NVIC_ClearPendingIRQ((IRQn_Type)irq);
330 
331         /* Initialize all IRQ entries to point to the irq_invalid() handler */
332         NVIC_SetVector((IRQn_Type)irq, (uint32_t)irq_invalid);
333     }
334 
335     __enable_irq();
336 
337     /* Enable the Usage, Bus and Memory faults which are disabled by default */
338     SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk |
339         SCB_SHCSR_USGFAULTENA_Msk;
340 
341     *driver = &arch_nvic_driver;
342 
343     return FWK_SUCCESS;
344 }
345