Interrupts

An 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.

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 service routine that is invoked to handle the interrupt.

  • An argument value that is passed to that function.

An IDT 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 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 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 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, CONFIG_MULTI_LEVEL_INTERRUPTS should be enabled, and CONFIG_2ND_LEVEL_INTERRUPTS and 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:

          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:

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 reschedule point 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 CONFIG_ZERO_LATENCY_IRQS to be enabled. In addition to that, the flag IRQ_ZERO_LATENCY must be passed to IRQ_CONNECT or 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 Workqueue Threads.)

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.

Sharing interrupt lines

In the case of some hardware platforms, the same interrupt lines may be used by different IPs. For example, interrupt 17 may be used by a DMA controller to signal that a data transfer has been completed or by a DAI controller to signal that the transfer FIFO has reached its watermark. To make this work, one would have to either employ some special logic or find a workaround (for example, using the shared_irq interrupt controller), which doesn’t scale very well.

To solve this problem, one may use shared interrupts, which can be enabled using CONFIG_SHARED_INTERRUPTS. Whenever an attempt to register a second ISR/argument pair on the same interrupt line is made (using IRQ_CONNECT or irq_connect_dynamic()), the interrupt line will become shared, meaning the two ISR/argument pairs (previous one and the one that has just been registered) will be invoked each time the interrupt is triggered. The entities that make use of an interrupt line in the shared interrupt context are known as clients. The maximum number of allowed clients for an interrupt is controlled by CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS.

Interrupt sharing is transparent to the user. As such, the user may register interrupts using IRQ_CONNECT and irq_connect_dynamic() as they normally would. The interrupt sharing is taken care of behind the scenes.

Enabling the shared interrupt support and dynamic interrupt support will allow users to dynamically disconnect ISRs using irq_disconnect_dynamic(). After an ISR is disconnected, whenever the interrupt line for which it was register gets triggered, the ISR will no longer get invoked.

Please note that enabling CONFIG_SHARED_INTERRUPTS will result in a non-negligible increase in the binary size. Use with caution.

Implementation

Defining a regular ISR

An ISR is defined at runtime by calling IRQ_CONNECT. It must then be enabled by calling 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.

#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 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 irq_connect_dynamic(). It is used in exactly the same way as IRQ_CONNECT:

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 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 IRQ_DIRECT_CONNECT. These direct interrupts have some special implementation requirements and a reduced feature set; see the definition of IRQ_DIRECT_CONNECT for details.

The following code demonstrates a direct ISR:

#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.)

Sharing an interrupt line

The following code defines two ISRs using the same interrupt number.

#define MY_DEV_IRQ 24               /* device uses INTID 24 */
#define MY_DEV_IRQ_PRIO 2           /* device uses interrupt priority 2 */
/*  this argument may be anything */
#define MY_FST_ISR_ARG INT_TO_POINTER(1)
/*  this argument may be anything */
#define MY_SND_ISR_ARG INT_TO_POINTER(2)
#define MY_IRQ_FLAGS 0              /* IRQ flags */

void my_first_isr(void *arg)
{
   ... /* some magic happens here */
}

void my_second_isr(void *arg)
{
   ... /* even more magic happens here */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

The same restrictions regarding IRQ_CONNECT described in Defining a regular ISR are applicable here. If CONFIG_SHARED_INTERRUPTS is disabled, the above code will generate a build error. Otherwise, the above code will result in the two ISRs being invoked each time interrupt 24 is triggered.

If CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS is set to a value lower than 2 (current number of clients), a build error will be generated.

If dynamic interrupts are enabled, irq_connect_dynamic() will allow sharing interrupts during runtime. Exceeding the configured maximum number of allowed clients will result in a failed assertion.

Dynamically disconnecting an ISR

The following code defines two ISRs using the same interrupt number. The second ISR is disconnected during runtime.

#define MY_DEV_IRQ 24               /* device uses INTID 24 */
#define MY_DEV_IRQ_PRIO 2           /* device uses interrupt priority 2 */
/*  this argument may be anything */
#define MY_FST_ISR_ARG INT_TO_POINTER(1)
/*  this argument may be anything */
#define MY_SND_ISR_ARG INT_TO_POINTER(2)
#define MY_IRQ_FLAGS 0              /* IRQ flags */

void my_first_isr(void *arg)
{
   ... /* some magic happens here */
}

void my_second_isr(void *arg)
{
   ... /* even more magic happens here */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_second_isr, MY_SND_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

void my_isr_uninstaller(void)
{
   ...
   irq_disconnect_dynamic(MY_DEV_IRQ, MY_DEV_IRQ_PRIO, my_first_isr, MY_FST_ISR_ARG, MY_IRQ_FLAGS);
   ...
}

The irq_disconnect_dynamic() call will result in interrupt 24 becoming unshared, meaning the system will act as if the first IRQ_CONNECT call never happened. This behaviour is only allowed if CONFIG_DYNAMIC_INTERRUPTS is enabled, otherwise a linker error will be generated.

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.

The invocation of IRQ_CONNECT will declare an instance of struct _isr_list wich is placed in a special .intList section. This section is placed in compiled code on precompilation stages only. It is meant to be used by Zephyr script to generate interrupt tables and is removed from the final build. The script implements different parsers to process the data from .intList section and produce the required output.

The default parser generates C arrays filled with arguments and interrupt handlers in a form of addresses directly taken from .intList section entries. It works with all the architectures and compillers (with the exception mentioned above). The limitation of this parser is the fact that after the arrays are generated it is expected for the code not to relocate. Any relocation on this stage may lead to the situation where the entry in the interrupt array is no longer pointing to the function that was expected. It means that this parser, being more compatible is limiting us from using Link Time Optimization.

The local isr declaration parser uses different approach to construct the same arrays at binnary level. All the entries to the arrays are declared and defined locally, directly in the file where IRQ_CONNECT is used. They are placed in a section with the unique, synthetized name. The name of the section is then placed in .intList section and it is used to create linker script to properly place the created entry in the right place in the memory. This parser is now limited to the supported architectures and toolchains but in reward it keeps the information about object relations for linker thus allowing the Link Time Optimization.

Implementation using C arrays

This is the default configuration available for all Zephyr supported architectures.

Any invocation of IRQ_CONNECT will declare an instance of struct _isr_list which is placed in a special .intList section:

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:

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 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 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:

  1. For ‘direct’ interrupts declared with IRQ_DIRECT_CONNECT, the handler function will be placed here.

  2. For regular interrupts declared with 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.

  3. 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 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:

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.

Shared SW ISR Table

This is an array of struct z_shared_isr_table_entry:

struct z_shared_isr_table_entry {
    struct _isr_table_entry clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS];
    size_t client_num;
};

This table keeps track of the registered clients for each of the interrupt lines. Whenever an interrupt line becomes shared, z_shared_isr() will replace the currently registered ISR in _sw_isr_table. This special ISR will iterate through the list of registered clients and invoke the ISRs.

Implementation using linker script

This way of prepare and parse .isrList section to implement interrupt vectors arrays is called local isr declaration. The name comes from the fact that all the entries to the arrays that would create interrupt vectors are created locally in place of invocation of IRQ_CONNECT macro. Then automatically generated linker scripts are used to place it in the right place in the memory.

This option requires enabling by the choose of ISR_TABLES_LOCAL_DECLARATION. If this configuration is supported by the used architecture and toolchaing the ISR_TABLES_LOCAL_DECLARATION_SUPPORTED is set. See defails of this option for the information about currently supported configurations.

Any invocation of IRQ_CONNECT or IRQ_DIRECT_CONNECT will declare an instance of struct _isr_list_sname which is placde in a special .intList section:

struct _isr_list_sname {
    /** IRQ line number */
    int32_t irq;
    /** Flags for this IRQ, see ISR_FLAG_* definitions */
    int32_t flags;
    /** The section name */
    const char sname[];
};

Note that the section name is placed in flexible array member. It means that the size of the initialized structure will warry depending on the structure name length. The whole entry is used by the script during the build of the application and has all the information needed for proper interrupt placement.

Beside of the _isr_list_sname the IRQ_CONNECT macro generates an entry that would be the part of the interrupt array:

struct _isr_table_entry {
    const void *arg;
    void (*isr)(const void *);
};

This array is placed in a section with the name saved in _isr_list_sname structure.

The values created by IRQ_DIRECT_CONNECT macro depends on the architecture. It can be changed to variable that points to a interrupt handler:

static uintptr_t <unique name> = ((uintptr_t)func);

Or to actuall naked function that implements a jump to the interrupt handler:

static void <unique name>(void)
{
    __asm(ARCH_IRQ_VECTOR_JUMP_CODE(func));
}

Simillar like for IRQ_CONNECT, the created variable or function is placed in a section, saved in _isr_list_sname section.

Files generated by the script

The interrupt tables generator script creates 3 files: isr_tables.c, isr_tables_swi.ld, and isr_tables_vt.ld.

The isr_tables.c will contain all the structures for interrupts, direct interrupts and shared interrupts (if enabled). This file implements only all the structures that are not implemented by the application, leaving a comment where the interrupt not implemented here can be found.

Then two linker files are used. The isr_tables_vt.ld file is included in place where the interrupt vectors are required to be placed in the selected architecture. The isr_tables_swi.ld file describes the placement of the software interrupt table elements. The separated file is required as it might be placed in writable on nonwritable section, depending on the current configuration.

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 arch/x86/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 arch/x86/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 IRQ_CONNECT call creates a small assembly language function which calls the common interrupt code in _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 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. arch/x86/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 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 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.

Going Beyond the Default Supported Number of Interrupts

When generating interrupts in the multi-level configuration, 8-bits per level is the default mask used when determining which level a given interrupt code belongs to. This can become a problem when dealing with CPUs that support more than 255 interrupts per single aggregator. In this case it may be desirable to override these defaults and use a custom number of bits per level. Regardless of how many bits used for each level, the sum of the total bits used between all levels must sum to be less than or equal to 32-bits, fitting into a single 32-bit integer. To modify the bit total per level, override the default 8 in Kconfig.multilevel by setting CONFIG_1ST_LEVEL_INTERRUPT_BITS for the first level, CONFIG_2ND_LEVEL_INTERRUPT_BITS for the second level and CONFIG_3RD_LEVEL_INTERRUPT_BITS for the third level. These masks control the length of the bit masks and shift to apply when generating interrupt values, when checking the interrupts level and converting interrupts to a different level. The logic controlling this can be found in irq_multilevel.h

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:

Additional architecture-specific and device-specific configuration options also exist.

API Reference

group isr_apis

Defines

IRQ_CONNECT(irq_p, priority_p, isr_p, isr_param_p, flags_p)

Initialize an interrupt handler.

This routine initializes an interrupt handler for an IRQ. The IRQ must be subsequently enabled before the interrupt handler begins servicing interrupts.

Warning

Although this routine is invoked at run-time, all of its arguments must be computable by the compiler at build time.

Parameters:
  • irq_p – IRQ line number.

  • priority_p – Interrupt priority.

  • isr_p – Address of interrupt service routine.

  • isr_param_p – Parameter passed to interrupt service routine.

  • flags_p – Architecture-specific IRQ configuration flags..

IRQ_DIRECT_CONNECT(irq_p, priority_p, isr_p, flags_p)

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 written 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.

Parameters:
  • irq_p – IRQ line number.

  • priority_p – Interrupt priority.

  • isr_p – Address of interrupt service routine.

  • flags_p – Architecture-specific IRQ configuration flags.

ISR_DIRECT_HEADER()

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.

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 z_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, z_swap() should only be called if this was a non-nested interrupt.

Parameters:
  • check_reschedule – If nonzero, additionally invoke scheduling logic

ISR_DIRECT_PM()

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!

ISR_DIRECT_DECLARE(name)

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 z_swap()
        }

        k_sem_give(some_sem);
        return 1;
 }

Parameters:
  • name – symbol name of the ISR

irq_lock()

Lock interrupts.

This routine disables all interrupts on the CPU. It returns an unsigned integer “lock-out key”, which is an architecture-dependent indicator of whether interrupts were locked prior to the call. The lock-out key must be passed to irq_unlock() to re-enable interrupts.

This routine can be called recursively, as long as the caller keeps track of each lock-out key that is generated. Interrupts are re-enabled by passing each of the keys to irq_unlock() in the reverse order they were acquired. (That is, each call to irq_lock() must be balanced by a corresponding call to irq_unlock().)

This routine can only be invoked from supervisor mode. Some architectures (for example, ARM) will fail silently if invoked from user mode instead of generating an exception.

Note

This routine must also serve as a memory barrier to ensure the uniprocessor implementation of k_spinlock_t is correct.

Note

This routine can be called by ISRs or by threads. If it is called by a thread, the interrupt lock is thread-specific; this means that interrupts remain disabled only while the thread is running. If the thread performs an operation that allows another thread to run (for example, giving a semaphore or sleeping for N milliseconds), the interrupt lock no longer applies and interrupts may be re-enabled while other processing occurs. When the thread once again becomes the current thread, the kernel re-establishes its interrupt lock; this ensures the thread won’t be interrupted until it has explicitly released the interrupt lock it established.

Warning

The lock-out key should never be used to manually re-enable interrupts or to inspect or manipulate the contents of the CPU’s interrupt bits.

Returns:

An architecture-dependent lock-out key representing the “interrupt disable state” prior to the call.

irq_unlock(key)

Unlock interrupts.

This routine reverses the effect of a previous call to irq_lock() using the associated lock-out key. The caller must call the routine once for each time it called irq_lock(), supplying the keys in the reverse order they were acquired, before interrupts are enabled.

This routine can only be invoked from supervisor mode. Some architectures (for example, ARM) will fail silently if invoked from user mode instead of generating an exception.

Note

This routine must also serve as a memory barrier to ensure the uniprocessor implementation of k_spinlock_t is correct.

Note

Can be called by ISRs.

Parameters:
irq_enable(irq)

Enable an IRQ.

This routine enables interrupts from source irq.

Parameters:
  • irq – IRQ line.

irq_disable(irq)

Disable an IRQ.

This routine disables interrupts from source irq.

Parameters:
  • irq – IRQ line.

irq_is_enabled(irq)

Get IRQ enable state.

This routine indicates if interrupts from source irq are enabled.

Parameters:
  • irq – IRQ line.

Returns:

interrupt enable state, true or false

Functions

static inline int irq_connect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)

Configure a dynamic interrupt.

Use this instead of IRQ_CONNECT() if arguments cannot be known at build time.

Parameters:
  • irq – IRQ line number

  • priority – Interrupt priority

  • routine – Interrupt service routine

  • parameter – ISR parameter

  • flags – Arch-specific IRQ configuration flags

Returns:

The vector assigned to this interrupt

static inline int irq_disconnect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)

Disconnect a dynamic interrupt.

Use this in conjunction with shared interrupts to remove a routine/parameter pair from the list of clients using the same interrupt line. If the interrupt is not being shared then the associated _sw_isr_table entry will be replaced by (NULL, z_irq_spurious) (default entry).

Parameters:
  • irq – IRQ line number

  • priority – Interrupt priority

  • routine – Interrupt service routine

  • parameter – ISR parameter

  • flags – Arch-specific IRQ configuration flags

Returns:

0 in case of success, negative value otherwise

bool k_is_in_isr(void)

Determine if code is running at interrupt level.

This routine allows the caller to customize its actions, depending on whether it is a thread or an ISR.

Function properties (list may not be complete)

isr-ok

Returns:

false if invoked by a thread.

Returns:

true if invoked by an ISR.

int k_is_preempt_thread(void)

Determine if code is running in a preemptible thread.

This routine allows the caller to customize its actions, depending on whether it can be preempted by another thread. The routine returns a ‘true’ value if all of the following conditions are met:

  • The code is running in a thread, not at ISR.

  • The thread’s priority is in the preemptible range.

  • The thread has not locked the scheduler.

Function properties (list may not be complete)

isr-ok

Returns:

0 if invoked by an ISR or by a cooperative thread.

Returns:

Non-zero if invoked by a preemptible thread.

static inline bool k_is_pre_kernel(void)

Test whether startup is in the before-main-task phase.

This routine allows the caller to customize its actions, depending on whether it being invoked before the kernel is fully active.

Function properties (list may not be complete)

isr-ok

Returns:

true if invoked before post-kernel initialization

Returns:

false if invoked during/after post-kernel initialization