diff --git a/doc/kernel/other/interrupts.rst b/doc/kernel/other/interrupts.rst index c3fca31f26c..d8e3c3c8951 100644 --- a/doc/kernel/other/interrupts.rst +++ b/doc/kernel/other/interrupts.rst @@ -124,8 +124,8 @@ may execute before the thread handling the offload is scheduled. Implementation ************** -Defining an ISR -=============== +Defining a regular ISR +====================== An ISR is defined at run-time by calling :c:macro:`IRQ_CONNECT`. It must then be enabled by calling :cpp:func:`irq_enable()`. @@ -159,11 +159,58 @@ The following code defines and enables an ISR. ... } +Defining a 'direct' ISR +======================= + +Regular Zephyr interrupts introduce some overhead which may be unacceptable +for some low-latency use-cases. Specifically: + +* The argument to the ISR is retrieved and passed to the ISR + +* If power management is enabled and the system was idle, all the hardware + will be resumed from low-power state before the ISR is executed, which can be + very time-consuming + +* Although some architectures will do this in hardware, other architectures + need to switch to the interrupt stack in code + +* After the interrupt is serviced, the OS then performs some logic to + potentially make a scheduling decision. + +Zephyr supports so-called 'direct' interrupts, which are installed via +:c:macro:`IRQ_DIRECT_CONNECT`. These direct interrupts have some special +implementation requirements and a reduced feature set; see the definition +of :c:macro:`IRQ_DIRECT_CONNECT` for details. + +The following code demonstrates a direct ISR: + +.. code-block:: c + + #define MY_DEV_IRQ 24 /* device uses IRQ 24 */ + #define MY_DEV_PRIO 2 /* device uses interrupt priority 2 */ + /* argument passed to my_isr(), in this case a pointer to the device */ + #define MY_IRQ_FLAGS 0 /* IRQ flags. Unused on non-x86 */ + + ISR_DIRECT_DECLARE(my_isr) + { + do_stuff(); + ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency */ + return 1; /* We should check if scheduling decision should be made */ + } + + void my_isr_installer(void) + { + ... + IRQ_DIRECT_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_IRQ_FLAGS); + irq_enable(MY_DEV_IRQ); + ... + } + Suggested Uses ************** -Use an ISR to perform interrupt processing that requires a very rapid -response, and can be done quickly without blocking. +Use a regular or direct ISR to perform interrupt processing that requires a +very rapid response, and can be done quickly without blocking. .. note:: Interrupt processing that is time consuming, or involves blocking, @@ -186,6 +233,11 @@ APIs The following interrupt-related APIs are provided by :file:`irq.h`: * :c:macro:`IRQ_CONNECT` +* :c:macro:`IRQ_DIRECT_CONNECT` +* :c:macro:`ISR_DIRECT_HEADER` +* :c:macro:`ISR_DIRECT_FOOTER` +* :c:macro:`ISR_DIRECT_PM` +* :c:macro:`ISR_DIRECT_DECLARE` * :cpp:func:`irq_lock()` * :cpp:func:`irq_unlock()` * :cpp:func:`irq_enable()` diff --git a/include/irq.h b/include/irq.h index b1a948e7d6b..661babf065f 100644 --- a/include/irq.h +++ b/include/irq.h @@ -49,6 +49,116 @@ extern "C" { #define IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) \ _ARCH_IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p) +/** + * @brief Initialize a 'direct' interrupt handler. + * + * This routine initializes an interrupt handler for an IRQ. The IRQ must be + * subsequently enabled via irq_enable() before the interrupt handler begins + * servicing interrupts. + * + * These ISRs are designed for performance-critical interrupt handling and do + * not go through common interrupt handling code. They must be implemented in + * such a way that it is safe to put them directly in the vector table. For + * ISRs written in C, The ISR_DIRECT_DECLARE() macro will do this + * automatically. For ISRs wriiten in assembly it is entirely up to the + * developer to ensure that the right steps are taken. + * + * This type of interrupt currently has a few limitations compared to normal + * Zephyr interrupts: + * - No parameters are passed to the ISR. + * - No stack switch is done, the ISR will run on the interrupted context's + * stack, unless the architecture automatically does the stack switch in HW. + * - Interrupt locking state is unchanged from how the HW sets it when the ISR + * runs. On arches that enter ISRs with interrupts locked, they will remain + * locked. + * - Scheduling decisions are now optional, controlled by the return value of + * ISRs implemented with the ISR_DIRECT_DECLARE() macro + * - The call into the OS to exit power management idle state is now optional. + * Normal interrupts always do this before the ISR is run, but when it runs + * is now controlled by the placement of a ISR_DIRECT_PM() macro, or omitted + * entirely. + * + * @warning + * Although this routine is invoked at run-time, all of its arguments must be + * computable by the compiler at build time. + * + * @param irq_p IRQ line number. + * @param priority_p Interrupt priority. + * @param isr_p Address of interrupt service routine. + * @param flags_p Architecture-specific IRQ configuration flags. + * + * @return Interrupt vector assigned to this interrupt. + */ +#define IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p) \ + _ARCH_IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p) + +/** + * @brief Common tasks before executing the body of an ISR + * + * This macro must be at the beginning of all direct interrupts and performs + * minimal architecture-specific tasks before the ISR itself can run. It takes + * no arguments and has no return value. + */ +#define ISR_DIRECT_HEADER() _ARCH_ISR_DIRECT_HEADER() + +/** + * @brief Common tasks before exiting the body of an ISR + * + * This macro must be at the end of all direct interrupts and performs + * minimal architecture-specific tasks like EOI. It has no return value. + * + * In a normal interrupt, a check is done at end of interrupt to invoke + * _Swap() logic if the current thread is preemptible and there is another + * thread ready to run in the kernel's ready queue cache. This is now optional + * and controlled by the check_reschedule argument. If unsure, set to nonzero. + * On systems that do stack switching and nested interrupt tracking in software, + * _Swap() should only be called if this was a non-nested interrupt. + * + * @param check_reschedule If nonzero, additionally invoke scheduling logic + */ +#define ISR_DIRECT_FOOTER(check_reschedule) \ + _ARCH_ISR_DIRECT_FOOTER(check_reschedule) + +/** + * @brief Perform power management idle exit logic + * + * This macro may optionally be invoked somewhere in between IRQ_DIRECT_HEADER() + * and IRQ_DIRECT_FOOTER() invocations. It performs tasks necessary to + * exit power management idle state. It takes no parameters and returns no + * arguments. It may be omitted, but be careful! + */ +#define ISR_DIRECT_PM() _ARCH_ISR_DIRECT_PM() + +/** + * @brief Helper macro to declare a direct interrupt service routine. + * + * This will declare the function in a proper way and automatically include + * the ISR_DIRECT_FOOTER() and ISR_DIRECT_HEADER() macros. The function should + * return nonzero status if a scheduling decision should potentially be made. + * See ISR_DIRECT_FOOTER() for more details on the scheduling decision. + * + * For architectures that support 'regular' and 'fast' interrupt types, where + * these interrupt types require different assembly language handling of + * registers by the ISR, this will always generate code for the 'fast' + * interrupt type. + * + * Example usage: + * + * ISR_DIRECT_DECLARE(my_isr) + * { + * bool done = do_stuff(); + * ISR_DIRECT_PM(); <-- done after do_stuff() due to latency concerns + * if (!done) { + * return 0; <-- Don't bother checking if we have to _Swap() + * } + * k_sem_give(some_sem); + * return 1; + * } + * + * @param name symbol name of the ISR + */ +#define ISR_DIRECT_DECLARE(name) _ARCH_ISR_DIRECT_DECLARE(name) + /** * @brief Lock interrupts. *