| /* |
| * Copyright (c) 2014 Wind River Systems, Inc. |
| * Copyright (c) 2017 Oticon A/S |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * SW side of the IRQ handling |
| */ |
| |
| #include <stdint.h> |
| #include <zephyr/irq_offload.h> |
| #include <zephyr/kernel_structs.h> |
| #include "kernel_internal.h" |
| #include "kswap.h" |
| #include "irq_ctrl.h" |
| #include "posix_core.h" |
| #include <zephyr/sw_isr_table.h> |
| #include "soc.h" |
| #include <zephyr/tracing/tracing.h> |
| #include "irq_handler.h" |
| #include "board_soc.h" |
| #include "nsi_cpu_if.h" |
| |
| typedef void (*normal_irq_f_ptr)(const void *); |
| typedef int (*direct_irq_f_ptr)(void); |
| |
| static struct _isr_list irq_vector_table[N_IRQS] = { { 0 } }; |
| |
| static int currently_running_irq = -1; |
| |
| static inline void vector_to_irq(int irq_nbr, int *may_swap) |
| { |
| sys_trace_isr_enter(); |
| |
| if (irq_vector_table[irq_nbr].func == NULL) { /* LCOV_EXCL_BR_LINE */ |
| /* LCOV_EXCL_START */ |
| posix_print_error_and_exit("Received irq %i without a " |
| "registered handler\n", |
| irq_nbr); |
| /* LCOV_EXCL_STOP */ |
| } else { |
| if (irq_vector_table[irq_nbr].flags & ISR_FLAG_DIRECT) { |
| *may_swap |= ((direct_irq_f_ptr) |
| irq_vector_table[irq_nbr].func)(); |
| } else { |
| #ifdef CONFIG_PM |
| posix_irq_check_idle_exit(); |
| #endif |
| ((normal_irq_f_ptr)irq_vector_table[irq_nbr].func) |
| (irq_vector_table[irq_nbr].param); |
| *may_swap = 1; |
| } |
| } |
| |
| sys_trace_isr_exit(); |
| } |
| |
| /** |
| * When an interrupt is raised, this function is called to handle it and, if |
| * needed, swap to a re-enabled thread |
| * |
| * Note that even that this function is executing in a Zephyr thread, it is |
| * effectively the model of the interrupt controller passing context to the IRQ |
| * handler and therefore its priority handling |
| */ |
| void posix_irq_handler(void) |
| { |
| uint64_t irq_lock; |
| int irq_nbr; |
| static int may_swap; |
| |
| irq_lock = hw_irq_ctrl_get_current_lock(); |
| |
| if (irq_lock) { |
| /* "spurious" wakes can happen with interrupts locked */ |
| return; |
| } |
| |
| irq_nbr = hw_irq_ctrl_get_highest_prio_irq(); |
| |
| if (irq_nbr == -1) { |
| /* This is a phony interrupt during a busy wait, no need for more */ |
| return; |
| } |
| |
| if (_kernel.cpus[0].nested == 0) { |
| may_swap = 0; |
| } |
| |
| _kernel.cpus[0].nested++; |
| |
| do { |
| int last_current_running_prio = hw_irq_ctrl_get_cur_prio(); |
| int last_running_irq = currently_running_irq; |
| |
| hw_irq_ctrl_set_cur_prio(hw_irq_ctrl_get_prio(irq_nbr)); |
| hw_irq_ctrl_clear_irq(irq_nbr); |
| |
| currently_running_irq = irq_nbr; |
| vector_to_irq(irq_nbr, &may_swap); |
| currently_running_irq = last_running_irq; |
| |
| hw_irq_ctrl_set_cur_prio(last_current_running_prio); |
| } while ((irq_nbr = hw_irq_ctrl_get_highest_prio_irq()) != -1); |
| |
| _kernel.cpus[0].nested--; |
| |
| /* Call swap if all the following is true: |
| * 1) may_swap was enabled |
| * 2) We are not nesting irq_handler calls (interrupts) |
| * 3) Next thread to run in the ready queue is not this thread |
| */ |
| if (may_swap |
| && (hw_irq_ctrl_get_cur_prio() == 256) |
| && (_kernel.ready_q.cache) && (_kernel.ready_q.cache != _current)) { |
| |
| (void)z_swap_irqlock(irq_lock); |
| } |
| } |
| |
| /** |
| * Thru this function the IRQ controller can raise an immediate interrupt which |
| * will interrupt the SW itself |
| * (this function should only be called from the HW model code, from SW threads) |
| */ |
| void nsif_cpu0_irq_raised_from_sw(void) |
| { |
| /* |
| * if a higher priority interrupt than the possibly currently running is |
| * pending we go immediately into irq_handler() to vector into its |
| * handler |
| */ |
| if (hw_irq_ctrl_get_highest_prio_irq() != -1) { |
| if (!posix_is_cpu_running()) { /* LCOV_EXCL_BR_LINE */ |
| /* LCOV_EXCL_START */ |
| posix_print_error_and_exit("programming error: %s " |
| "called from a HW model thread\n", |
| __func__); |
| /* LCOV_EXCL_STOP */ |
| } |
| posix_irq_handler(); |
| } |
| } |
| |
| /** |
| * @brief Disable all interrupts on the CPU |
| * |
| * This routine disables interrupts. It can be called from either interrupt, |
| * task or fiber level. This routine returns an architecture-dependent |
| * lock-out key representing the "interrupt disable state" prior to the call; |
| * this key can be passed to irq_unlock() to re-enable interrupts. |
| * |
| * The lock-out key should only be used as the argument to the irq_unlock() |
| * API. It should never be used to manually re-enable interrupts or to inspect |
| * or manipulate the contents of the source register. |
| * |
| * This function can be called recursively: it will return a key to return the |
| * state of interrupt locking to the previous level. |
| * |
| * WARNINGS |
| * Invoking a kernel routine with interrupts locked may result in |
| * interrupts being re-enabled for an unspecified period of time. If the |
| * called routine blocks, interrupts will be re-enabled while another |
| * thread executes, or while the system is idle. |
| * |
| * The "interrupt disable state" is an attribute of a thread. Thus, if a |
| * fiber or task disables interrupts and subsequently invokes a kernel |
| * routine that causes the calling thread to block, the interrupt |
| * disable state will be restored when the thread is later rescheduled |
| * for execution. |
| * |
| * @return An architecture-dependent lock-out key representing the |
| * "interrupt disable state" prior to the call. |
| * |
| */ |
| unsigned int posix_irq_lock(void) |
| { |
| return hw_irq_ctrl_change_lock(true); |
| } |
| |
| /** |
| * @brief Enable all interrupts on the CPU |
| * |
| * This routine re-enables interrupts on the CPU. The @a key parameter is a |
| * board-dependent lock-out key that is returned by a previous invocation of |
| * board_irq_lock(). |
| * |
| * This routine can be called from either interrupt, task or fiber level. |
| */ |
| void posix_irq_unlock(unsigned int key) |
| { |
| hw_irq_ctrl_change_lock(key); |
| } |
| |
| void posix_irq_full_unlock(void) |
| { |
| hw_irq_ctrl_change_lock(false); |
| } |
| |
| void posix_irq_enable(unsigned int irq) |
| { |
| hw_irq_ctrl_enable_irq(irq); |
| } |
| |
| void posix_irq_disable(unsigned int irq) |
| { |
| hw_irq_ctrl_disable_irq(irq); |
| } |
| |
| int posix_irq_is_enabled(unsigned int irq) |
| { |
| return hw_irq_ctrl_is_irq_enabled(irq); |
| } |
| |
| int posix_get_current_irq(void) |
| { |
| return currently_running_irq; |
| } |
| |
| /** |
| * Configure a static interrupt. |
| * |
| * posix_isr_declare will populate the interrupt table table with the |
| * interrupt's parameters, the vector table and the software ISR table. |
| * |
| * We additionally set the priority in the interrupt controller at |
| * runtime. |
| * |
| * @param irq_p IRQ line number |
| * @param flags [plug it directly (1), or as a SW managed interrupt (0)] |
| * @param isr_p Interrupt service routine |
| * @param isr_param_p ISR parameter |
| * @param flags_p IRQ options |
| */ |
| void posix_isr_declare(unsigned int irq_p, int flags, void isr_p(const void *), |
| const void *isr_param_p) |
| { |
| if (irq_p >= N_IRQS) { |
| posix_print_error_and_exit("Attempted to configure not existent interrupt %u\n", |
| irq_p); |
| return; |
| } |
| irq_vector_table[irq_p].irq = irq_p; |
| irq_vector_table[irq_p].func = isr_p; |
| irq_vector_table[irq_p].param = isr_param_p; |
| irq_vector_table[irq_p].flags = flags; |
| } |
| |
| /** |
| * @internal |
| * |
| * @brief Set an interrupt's priority |
| * |
| * Lower values take priority over higher values. |
| */ |
| void posix_irq_priority_set(unsigned int irq, unsigned int prio, uint32_t flags) |
| { |
| hw_irq_ctrl_prio_set(irq, prio); |
| } |
| |
| /** |
| * Similar to ARM's NVIC_SetPendingIRQ |
| * set a pending IRQ from SW |
| * |
| * Note that this will interrupt immediately if the interrupt is not masked and |
| * IRQs are not locked, and this interrupt has higher priority than a possibly |
| * currently running interrupt |
| */ |
| void posix_sw_set_pending_IRQ(unsigned int IRQn) |
| { |
| hw_irq_ctrl_raise_im_from_sw(IRQn); |
| } |
| |
| /** |
| * Similar to ARM's NVIC_ClearPendingIRQ |
| * clear a pending irq from SW |
| */ |
| void posix_sw_clear_pending_IRQ(unsigned int IRQn) |
| { |
| hw_irq_ctrl_clear_irq(IRQn); |
| } |
| |
| #ifdef CONFIG_IRQ_OFFLOAD |
| /** |
| * Storage for functions offloaded to IRQ |
| */ |
| static void (*off_routine)(const void *); |
| static const void *off_parameter; |
| |
| /** |
| * IRQ handler for the SW interrupt assigned to irq_offload() |
| */ |
| static void offload_sw_irq_handler(const void *a) |
| { |
| ARG_UNUSED(a); |
| off_routine(off_parameter); |
| } |
| |
| /** |
| * @brief Run a function in interrupt context |
| * |
| * Raise the SW IRQ assigned to handled this |
| */ |
| void posix_irq_offload(void (*routine)(const void *), const void *parameter) |
| { |
| off_routine = routine; |
| off_parameter = parameter; |
| posix_isr_declare(OFFLOAD_SW_IRQ, 0, offload_sw_irq_handler, NULL); |
| posix_irq_enable(OFFLOAD_SW_IRQ); |
| posix_sw_set_pending_IRQ(OFFLOAD_SW_IRQ); |
| posix_irq_disable(OFFLOAD_SW_IRQ); |
| } |
| #endif /* CONFIG_IRQ_OFFLOAD */ |