| .. _interrupts_v2: |
| |
| Interrupts |
| ########## |
| |
| An :dfn:`interrupt service routine` (ISR) is a function that executes |
| asynchronously in response to a hardware or software interrupt. |
| An ISR normally preempts the execution of the current thread, |
| allowing the response to occur with very low overhead. |
| Thread execution resumes only once all ISR work has been completed. |
| |
| .. contents:: |
| :local: |
| :depth: 2 |
| |
| Concepts |
| ******** |
| |
| Any number of ISRs can be defined (limited only by available RAM), subject to |
| the constraints imposed by underlying hardware. |
| |
| An ISR has the following key properties: |
| |
| * An **interrupt request (IRQ) signal** that triggers the ISR. |
| * A **priority level** associated with the IRQ. |
| * An **interrupt handler function** that is invoked to handle the interrupt. |
| * An **argument value** that is passed to that function. |
| |
| An :abbr:`IDT (Interrupt Descriptor Table)` or a vector table is used |
| to associate a given interrupt source with a given ISR. |
| Only a single ISR can be associated with a specific IRQ at any given time. |
| |
| Multiple ISRs can utilize the same function to process interrupts, |
| allowing a single function to service a device that generates |
| multiple types of interrupts or to service multiple devices |
| (usually of the same type). The argument value passed to an ISR's function |
| allows the function to determine which interrupt has been signaled. |
| |
| The kernel provides a default ISR for all unused IDT entries. This ISR |
| generates a fatal system error if an unexpected interrupt is signaled. |
| |
| The kernel supports **interrupt nesting**. This allows an ISR to be preempted |
| in mid-execution if a higher priority interrupt is signaled. The lower |
| priority ISR resumes execution once the higher priority ISR has completed |
| its processing. |
| |
| An ISR's interrupt handler function executes in the kernel's **interrupt |
| context**. This context has its own dedicated stack area (or, on some |
| architectures, stack areas). The size of the interrupt context stack must be |
| capable of handling the execution of multiple concurrent ISRs if interrupt |
| nesting support is enabled. |
| |
| .. important:: |
| Many kernel APIs can be used only by threads, and not by ISRs. In cases |
| where a routine may be invoked by both threads and ISRs the kernel |
| provides the :c:func:`k_is_in_isr` API to allow the routine to |
| alter its behavior depending on whether it is executing as part of |
| a thread or as part of an ISR. |
| |
| .. _multi_level_interrupts: |
| |
| Multi-level Interrupt handling |
| ============================== |
| |
| A hardware platform can support more interrupt lines than natively-provided |
| through the use of one or more nested interrupt controllers. Sources of |
| hardware interrupts are combined into one line that is then routed to |
| the parent controller. |
| |
| If nested interrupt controllers are supported, :kconfig:option:`CONFIG_MULTI_LEVEL_INTERRUPTS` |
| should be set to 1, and :kconfig:option:`CONFIG_2ND_LEVEL_INTERRUPTS` and |
| :kconfig:option:`CONFIG_3RD_LEVEL_INTERRUPTS` configured as well, based on the |
| hardware architecture. |
| |
| A unique 32-bit interrupt number is assigned with information |
| embedded in it to select and invoke the correct Interrupt |
| Service Routine (ISR). Each interrupt level is given a byte within this 32-bit |
| number, providing support for up to four interrupt levels using this arch, as |
| illustrated and explained below: |
| |
| .. code-block:: none |
| |
| 9 2 0 |
| _ _ _ _ _ _ _ _ _ _ _ _ _ (LEVEL 1) |
| 5 | A | |
| _ _ _ _ _ _ _ _ _ _ _ _ _ _ (LEVEL 2) |
| | C B |
| _ _ _ _ _ _ _ (LEVEL 3) |
| D |
| |
| There are three interrupt levels shown here. |
| |
| * '-' means interrupt line and is numbered from 0 (right most). |
| * LEVEL 1 has 12 interrupt lines, with two lines (2 and 9) connected |
| to nested controllers and one device 'A' on line 4. |
| * One of the LEVEL 2 controllers has interrupt line 5 connected to |
| a LEVEL 3 nested controller and one device 'C' on line 3. |
| * The other LEVEL 2 controller has no nested controllers but has one |
| device 'B' on line 2. |
| * The LEVEL 3 controller has one device 'D' on line 2. |
| |
| Here's how unique interrupt numbers are generated for each |
| hardware interrupt. Let's consider four interrupts shown above |
| as A, B, C, and D: |
| |
| .. code-block:: none |
| |
| A -> 0x00000004 |
| B -> 0x00000302 |
| C -> 0x00000409 |
| D -> 0x00030609 |
| |
| .. note:: |
| The bit positions for LEVEL 2 and onward are offset by 1, as 0 means that |
| interrupt number is not present for that level. For our example, the LEVEL 3 |
| controller has device D on line 2, connected to the LEVEL 2 controller's line |
| 5, that is connected to the LEVEL 1 controller's line 9 (2 -> 5 -> 9). |
| Because of the encoding offset for LEVEL 2 and onward, device D is given the |
| number 0x00030609. |
| |
| Preventing Interruptions |
| ======================== |
| |
| In certain situations it may be necessary for the current thread to |
| prevent ISRs from executing while it is performing time-sensitive |
| or critical section operations. |
| |
| A thread may temporarily prevent all IRQ handling in the system using |
| an **IRQ lock**. This lock can be applied even when it is already in effect, |
| so routines can use it without having to know if it is already in effect. |
| The thread must unlock its IRQ lock the same number of times it was locked |
| before interrupts can be once again processed by the kernel while the thread |
| is running. |
| |
| .. important:: |
| The IRQ lock is thread-specific. If thread A locks out interrupts |
| then performs an operation that puts itself to sleep (e.g. sleeping |
| for N milliseconds), the thread's IRQ lock no longer applies once |
| thread A is swapped out and the next ready thread B starts to |
| run. |
| |
| This means that interrupts can be processed while thread B is |
| running unless thread B has also locked out interrupts using its own |
| IRQ lock. (Whether interrupts can be processed while the kernel is |
| switching between two threads that are using the IRQ lock is |
| architecture-specific.) |
| |
| When thread A eventually becomes the current thread once again, the kernel |
| re-establishes thread A's IRQ lock. This ensures thread A won't be |
| interrupted until it has explicitly unlocked its IRQ lock. |
| |
| If thread A does not sleep but does make a higher-priority thread B |
| ready, the IRQ lock will inhibit any preemption that would otherwise |
| occur. Thread B will not run until the next :ref:`reschedule point |
| <scheduling_v2>` reached after releasing the IRQ lock. |
| |
| Alternatively, a thread may temporarily **disable** a specified IRQ |
| so its associated ISR does not execute when the IRQ is signaled. |
| The IRQ must be subsequently **enabled** to permit the ISR to execute. |
| |
| .. important:: |
| Disabling an IRQ prevents *all* threads in the system from being preempted |
| by the associated ISR, not just the thread that disabled the IRQ. |
| |
| Zero Latency Interrupts |
| ----------------------- |
| |
| Preventing interruptions by applying an IRQ lock may increase the observed |
| interrupt latency. A high interrupt latency, however, may not be acceptable |
| for certain low-latency use-cases. |
| |
| The kernel addresses such use-cases by allowing interrupts with critical |
| latency constraints to execute at a priority level that cannot be blocked |
| by interrupt locking. These interrupts are defined as |
| *zero-latency interrupts*. The support for zero-latency interrupts requires |
| :kconfig:option:`CONFIG_ZERO_LATENCY_IRQS` to be enabled. In addition to that, the |
| flag :c:macro:`IRQ_ZERO_LATENCY` must be passed to :c:macro:`IRQ_CONNECT` or |
| :c:macro:`IRQ_DIRECT_CONNECT` macros to configure the particular interrupt |
| with zero latency. |
| |
| Zero-latency interrupts are expected to be used to manage hardware events |
| directly, and not to interoperate with the kernel code at all. They should |
| treat all kernel APIs as undefined behavior (i.e. an application that uses the |
| APIs inside a zero-latency interrupt context is responsible for directly |
| verifying correct behavior). Zero-latency interrupts may not modify any data |
| inspected by kernel APIs invoked from normal Zephyr contexts and shall not |
| generate exceptions that need to be handled synchronously (e.g. kernel panic). |
| |
| .. important:: |
| Zero-latency interrupts are supported on an architecture-specific basis. |
| The feature is currently implemented in the ARM Cortex-M architecture |
| variant. |
| |
| Offloading ISR Work |
| =================== |
| |
| An ISR should execute quickly to ensure predictable system operation. |
| If time consuming processing is required the ISR should offload some or all |
| processing to a thread, thereby restoring the kernel's ability to respond |
| to other interrupts. |
| |
| The kernel supports several mechanisms for offloading interrupt-related |
| processing to a thread. |
| |
| * An ISR can signal a helper thread to do interrupt-related processing |
| using a kernel object, such as a FIFO, LIFO, or semaphore. |
| |
| * An ISR can instruct the system workqueue thread to execute a work item. |
| (See :ref:`workqueues_v2`.) |
| |
| When an ISR offloads work to a thread, there is typically a single context |
| switch to that thread when the ISR completes, allowing interrupt-related |
| processing to continue almost immediately. However, depending on the |
| priority of the thread handling the offload, it is possible that |
| the currently executing cooperative thread or other higher-priority threads |
| may execute before the thread handling the offload is scheduled. |
| |
| Implementation |
| ************** |
| |
| Defining a regular ISR |
| ====================== |
| |
| An ISR is defined at runtime by calling :c:macro:`IRQ_CONNECT`. It must |
| then be enabled by calling :c:func:`irq_enable`. |
| |
| .. important:: |
| IRQ_CONNECT() is not a C function and does some inline assembly magic |
| behind the scenes. All its arguments must be known at build time. |
| Drivers that have multiple instances may need to define per-instance |
| config functions to configure each instance of the interrupt. |
| |
| The following code defines and enables an 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_ISR_ARG DEVICE_GET(my_device) |
| #define MY_IRQ_FLAGS 0 /* IRQ flags */ |
| |
| void my_isr(void *arg) |
| { |
| ... /* ISR code */ |
| } |
| |
| void my_isr_installer(void) |
| { |
| ... |
| IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS); |
| irq_enable(MY_DEV_IRQ); |
| ... |
| } |
| |
| Since the :c:macro:`IRQ_CONNECT` macro requires that all its parameters be |
| known at build time, in some cases this may not be acceptable. It is also |
| possible to install interrupts at runtime with |
| :c:func:`irq_connect_dynamic`. It is used in exactly the same way as |
| :c:macro:`IRQ_CONNECT`: |
| |
| .. code-block:: c |
| |
| void my_isr_installer(void) |
| { |
| ... |
| irq_connect_dynamic(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, |
| MY_IRQ_FLAGS); |
| irq_enable(MY_DEV_IRQ); |
| ... |
| } |
| |
| Dynamic interrupts require the :kconfig:option:`CONFIG_DYNAMIC_INTERRUPTS` option to |
| be enabled. Removing or re-configuring a dynamic interrupt is currently |
| unsupported. |
| |
| 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 */ |
| |
| 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); |
| ... |
| } |
| |
| Installation of dynamic direct interrupts is supported on an |
| architecture-specific basis. (The feature is currently implemented in |
| ARM Cortex-M architecture variant. Dynamic direct interrupts feature is |
| exposed to the user via an ARM-only API.) |
| |
| Implementation Details |
| ====================== |
| |
| Interrupt tables are set up at build time using some special build tools. The |
| details laid out here apply to all architectures except x86, which are |
| covered in the `x86 Details`_ section below. |
| |
| Any invocation of :c:macro:`IRQ_CONNECT` will declare an instance of |
| struct _isr_list which is placed in a special .intList section: |
| |
| .. code-block:: c |
| |
| struct _isr_list { |
| /** IRQ line number */ |
| int32_t irq; |
| /** Flags for this IRQ, see ISR_FLAG_* definitions */ |
| int32_t flags; |
| /** ISR to call */ |
| void *func; |
| /** Parameter for non-direct IRQs */ |
| void *param; |
| }; |
| |
| Zephyr is built in two phases; the first phase of the build produces |
| ``${ZEPHYR_PREBUILT_EXECUTABLE}``.elf which contains all the entries in |
| the .intList section preceded by a header: |
| |
| .. code-block:: c |
| |
| struct { |
| void *spurious_irq_handler; |
| void *sw_irq_handler; |
| uint32_t num_isrs; |
| uint32_t num_vectors; |
| struct _isr_list isrs[]; <- of size num_isrs |
| }; |
| |
| This data consisting of the header and instances of struct _isr_list inside |
| ``${ZEPHYR_PREBUILT_EXECUTABLE}``.elf is then used by the |
| gen_isr_tables.py script to generate a C file defining a vector table and |
| software ISR table that are then compiled and linked into the final |
| application. |
| |
| The priority level of any interrupt is not encoded in these tables, instead |
| :c:macro:`IRQ_CONNECT` also has a runtime component which programs the desired |
| priority level of the interrupt to the interrupt controller. Some architectures |
| do not support the notion of interrupt priority, in which case the priority |
| argument is ignored. |
| |
| Vector Table |
| ------------ |
| A vector table is generated when :kconfig:option:`CONFIG_GEN_IRQ_VECTOR_TABLE` is |
| enabled. This data structure is used natively by the CPU and is simply an |
| array of function pointers, where each element n corresponds to the IRQ handler |
| for IRQ line n, and the function pointers are: |
| |
| #. For 'direct' interrupts declared with :c:macro:`IRQ_DIRECT_CONNECT`, the |
| handler function will be placed here. |
| #. For regular interrupts declared with :c:macro:`IRQ_CONNECT`, the address |
| of the common software IRQ handler is placed here. This code does common |
| kernel interrupt bookkeeping and looks up the ISR and parameter from the |
| software ISR table. |
| #. For interrupt lines that are not configured at all, the address of the |
| spurious IRQ handler will be placed here. The spurious IRQ handler |
| causes a system fatal error if encountered. |
| |
| Some architectures (such as the Nios II internal interrupt controller) have a |
| common entry point for all interrupts and do not support a vector table, in |
| which case the :kconfig:option:`CONFIG_GEN_IRQ_VECTOR_TABLE` option should be |
| disabled. |
| |
| Some architectures may reserve some initial vectors for system exceptions |
| and declare this in a table elsewhere, in which case |
| CONFIG_GEN_IRQ_START_VECTOR needs to be set to properly offset the indices |
| in the table. |
| |
| SW ISR Table |
| ------------ |
| This is an array of struct _isr_table_entry: |
| |
| .. code-block:: c |
| |
| struct _isr_table_entry { |
| void *arg; |
| void (*isr)(void *); |
| }; |
| |
| This is used by the common software IRQ handler to look up the ISR and its |
| argument and execute it. The active IRQ line is looked up in an interrupt |
| controller register and used to index this table. |
| |
| x86 Details |
| ----------- |
| |
| The x86 architecture has a special type of vector table called the Interrupt |
| Descriptor Table (IDT) which must be laid out in a certain way per the x86 |
| processor documentation. It is still fundamentally a vector table, and the |
| :ref:`gen_idt.py` tool uses the .intList section to create it. However, on APIC-based |
| systems the indexes in the vector table do not correspond to the IRQ line. The |
| first 32 vectors are reserved for CPU exceptions, and all remaining vectors (up |
| to index 255) correspond to the priority level, in groups of 16. In this |
| scheme, interrupts of priority level 0 will be placed in vectors 32-47, level 1 |
| 48-63, and so forth. When the :ref:`gen_idt.py` tool is constructing the IDT, when it |
| configures an interrupt it will look for a free vector in the appropriate range |
| for the requested priority level and set the handler there. |
| |
| On x86 when an interrupt or exception vector is executed by the CPU, there is |
| no foolproof way to determine which vector was fired, so a software ISR table |
| indexed by IRQ line is not used. Instead, the :c:macro:`IRQ_CONNECT` call |
| creates a small assembly language function which calls the common interrupt |
| code in :c:func:`_interrupt_enter` with the ISR and parameter as arguments. |
| It is the address of this assembly interrupt stub which gets placed in the IDT. |
| For interrupts declared with :c:macro:`IRQ_DIRECT_CONNECT` the parameterless |
| ISR is placed directly in the IDT. |
| |
| On systems where the position in the vector table corresponds to the |
| interrupt's priority level, the interrupt controller needs to know at |
| runtime what vector is associated with an IRQ line. :ref:`gen_idt.py` additionally |
| creates an _irq_to_interrupt_vector array which maps an IRQ line to its |
| configured vector in the IDT. This is used at runtime by :c:macro:`IRQ_CONNECT` |
| to program the IRQ-to-vector association in the interrupt controller. |
| |
| For dynamic interrupts, the build must generate some 4-byte dynamic interrupt |
| stubs, one stub per dynamic interrupt in use. The number of stubs is controlled |
| by the :kconfig:option:`CONFIG_X86_DYNAMIC_IRQ_STUBS` option. Each stub pushes an |
| unique identifier which is then used to fetch the appropriate handler function |
| and parameter out of a table populated when the dynamic interrupt was |
| connected. |
| |
| Suggested Uses |
| ************** |
| |
| 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, |
| should be handed off to a thread. See `Offloading ISR Work`_ for |
| a description of various techniques that can be used in an application. |
| |
| Configuration Options |
| ********************* |
| |
| Related configuration options: |
| |
| * :kconfig:option:`CONFIG_ISR_STACK_SIZE` |
| |
| Additional architecture-specific and device-specific configuration options |
| also exist. |
| |
| API Reference |
| ************* |
| |
| .. doxygengroup:: isr_apis |