# Interrupt Allocation

[中文]

## Overview

The ESP32-H4 has two cores, with 32 external asynchronous interrupts each. Each interrupt's priority is independently programmable. In addition, there are also 3 core local interrupt sources (CLINT) on each core. See ESP32-H4 Technical Reference Manual [PDF] for more details.

Because there are more interrupt sources than interrupts, sometimes it makes sense to share an interrupt in multiple drivers. The `esp_intr_alloc()` abstraction exists to hide all these implementation details.

A driver can allocate an interrupt for a certain peripheral by calling `esp_intr_alloc()`, `esp_intr_alloc_bind()`, `esp_intr_alloc_intrstatus()`, `esp_intr_alloc_intrstatus_bind()`, or `esp_intr_alloc_info()`. It can use the flags passed to this function to specify the type, priority, and trigger method of the interrupt to allocate. The interrupt allocation code will then find an applicable interrupt, use the interrupt matrix to hook it up to the peripheral, and install the given interrupt handler and ISR to it.

The interrupt allocator presents two different types of interrupts, namely shared interrupts and non-shared interrupts, both of which require different handling. Non-shared interrupts will allocate a separate interrupt for every `esp_intr_alloc()` call, and this interrupt is use solely for the peripheral attached to it, with only one ISR that will get called. Shared interrupts can have multiple peripherals triggering them, with multiple ISRs being called when one of the peripherals attached signals an interrupt. Thus, ISRs that are intended for shared interrupts should check the interrupt status of the peripheral they service in order to check if any action is required.

Non-shared interrupts can be either level- or edge-triggered. Shared interrupts can only be level interrupts due to the chance of missed interrupts when edge interrupts are used.

To illustrate why shared interrupts can only be level-triggered, take the scenario where peripheral A and peripheral B share the same edge-triggered interrupt. Peripheral B triggers an interrupt and sets its interrupt signal high, causing a low-to-high edge, which in turn latches the CPU's interrupt bit and triggers the ISR. The ISR executes, checks that peripheral A did not trigger an interrupt, and proceeds to handle and clear peripheral B's interrupt signal. Before the ISR returns, the CPU clears its interrupt bit latch. Thus, during the entire interrupt handling process, if peripheral A triggers an interrupt, it will be missed due the CPU clearing the interrupt bit latch.

- Allocating an external interrupt will always allocate it on the core that does the allocation.

- Freeing an external interrupt must always happen on the same core it was allocated on.

- Disabling and enabling external interrupts from another core is allowed.

- Multiple external interrupt sources can share an interrupt slot by passing `ESP_INTR_FLAG_SHARED` as a flag to `esp_intr_alloc()`.

Care should be taken when calling `esp_intr_alloc()` from a task which is not pinned to a core. During task switching, these tasks can migrate between cores. Therefore it is impossible to tell which CPU the interrupt is allocated on, which makes it difficult to free the interrupt handle and may also cause debugging difficulties. It is advised to use `xTaskCreatePinnedToCore()` with a specific CoreID argument to create tasks that allocate interrupts. In the case of internal interrupt sources, this is required.

## Multicore Considerations

Each core on ESP32-H4 provides internal interrupts that are triggered by the core itself and external interrupts that are triggered by peripherals. However, ESP-IDF only makes use of the external interrupts on ESP32-H4. Most ESP32-H4 interrupt sources are external interrupts.

Each external interrupt slot of each core is wired to the interrupt matrix. The interrupt matrix allows any interrupt slot to be connected to any external interrupt source. Mapping multiple external interrupts sources to a single slot is also supported. These external interrupt sources are defined in `soc/interrupts.h` as `ETS_*_INTR_SOURCE`.

## IRAM-Safe Interrupt Handlers

When performing write and erase operations on SPI flash, ESP32-H4 will disable the cache, making SPI flash and SPIRAM inaccessible for interrupt handlers. This is why there are two types of interrupt handlers in ESP-IDF, which have their advantages and disadvantages:

IRAM-safe interrupt handlers - only access code and data in internal memory (IRAM for code, DRAM for data).

- + Latency: They execute relatively fast and with low latency, since they are not blocked by slow flash write and erase operations (erases can take tens or hundreds of milliseconds to complete). This is useful for interrupts which need a guaranteed minimum execution latency.

- - Internal memory use: They consume precious internal memory that could otherwise be used for something else.

- + Cache misses: They do not rely on the cache with potential cache misses since the code and data are in internal memory already.

- Usage: To register such an interrupt via the interrupt allocator API, use the `ESP_INTR_FLAG_IRAM` flag.

Non-IRAM-safe interrupt handlers - may access code and (read-only) data in flash.

- - Latency: In case of flash operations, these interrupt handlers are postponed, which makes their average latency longer and less predictable.

- + Internal memory use: They do not use any or not as much memory in internal RAM as IRAM-safe interrupts.

- Usage: To register such an interrupt via the interrupt allocator API, do not use the `ESP_INTR_FLAG_IRAM` flag.

Note that there is nothing that explicitly marks an interrupt handler as IRAM-safe. An interrupt handler is IRAM-safe implicitly if and only if the code and data it may access are placed in internal memory. The term "IRAM-safe" is actually a bit misleading, since there are more requirements than just placing the handler's code in IRAM memory. Examples of interrupt handlers that are not IRAM-safe include:

- A handler that has some of its code placed in flash memory.

- A handler that is placed in IRAM but calls functions placed in flash memory.

- A handler that accesses a read-only variable placed in flash, even though the handler's code is actually placed in IRAM.

For details on placing code and data in IRAM or DRAM, see How to Place Code in IRAM.

For more details about SPI flash operations and their interactions with interrupt handlers, see the SPI flash API documentation.

Note

Never register an interrupt handler with `ESP_INTR_FLAG_IRAM` flag if you are not 100% sure that all the code and data that the interrupt ever accesses are in IRAM (code) or DRAM (data). Disregarding this will lead to (sometimes spurious) cache errors. This must also be true for code and data accessed indirectly through function calls.

## Multiple Handlers Sharing A Source

Several handlers can be assigned to a same source, given that all handlers are allocated using the `ESP_INTR_FLAG_SHARED` flag. They will all be allocated to the interrupt, which the source is attached to, and called sequentially when the source is active. The handlers can be disabled and freed individually. The source is attached to the interrupt (enabled), if one or more handlers are enabled, otherwise detached. A handler will never be called when disabled, while its source may still be triggered if any one of its handler enabled.

Sources attached to non-shared interrupt do not support this feature.

By default, when `ESP_INTR_FLAG_SHARED` flag is specified, the interrupt allocator will allocate only priority level 1 interrupts. Use `ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED` to also allow allocating shared interrupts at priority levels 2 and 3.

Private shared interrupts (`ESP_INTR_FLAG_SHARED_PRIVATE`) behave like `ESP_INTR_FLAG_SHARED` in that they allow multiple sources to share the same CPU interrupt line. However, unlike regular shared interrupts, these lines are never selected automatically by the interrupt allocator.

In other words, even when using `ESP_INTR_FLAG_SHARED`, no allocation function will ever choose an interrupt line that was created with `ESP_INTR_FLAG_SHARED_PRIVATE`. These lines are effectively reserved and can only be used by explicitly binding new sources to them (e.g., via `esp_intr_alloc_bind()` or related APIs).

This allows you to define a fixed set of interrupt sources sharing a single CPU line (for example, multiple GPIO sources) without the risk of the allocator reusing that line for unrelated interrupts.

Though the framework supports this feature, you have to use it very carefully. There usually exist two ways to stop an interrupt from being triggered: disable the source or mask peripheral interrupt status. ESP-IDF only handles enabling and disabling of the source itself, leaving status and mask bits to be handled by users.

Status bits shall either be masked before the handler responsible for it is disabled, or be masked and then properly handled in another enabled interrupt.

Note

Leaving some status bits unhandled without masking them, while disabling the handlers for them, will cause the interrupt(s) to be triggered indefinitely, resulting therefore in a system crash.

When calling `esp_intr_alloc()` or `esp_intr_alloc_intrstatus()`, the interrupt allocator selects the first interrupt that meets the level requirements for mapping the specified source, without considering other sources already mapped to the shared interrupt line. However, by using the functions `esp_intr_alloc_bind()` or `esp_intr_alloc_intrstatus_bind()`, you can explicitly specify the interrupt handler to be shared with the given interrupt source.

For private shared interrupts, the first allocation uses `ESP_INTR_FLAG_SHARED_PRIVATE` (with `esp_intr_alloc()` or `esp_intr_alloc_intrstatus()`), and subsequent sources are attached via the same functions, still with `ESP_INTR_FLAG_SHARED_PRIVATE` flag and the returned handle.

### Named Groups

Shared interrupts groups, public or private, can be defined by name. This is done via the `bind_by.name` field of the `esp_intr_alloc_info_t` structure. If a shared interrupt group with the specified name already exists (created by an earlier call using the same name), the new source is attached to that group. Otherwise, a new shared interrupt group is allocated and tagged with that name. Only one of `bind_by.handle` or `bind_by.name` may be set. The usual flag rules (e.g., interrupt level, SHARED vs SHARED_PRIVATE) still apply. This does not affect the behavior of public shared interrupts: interrupt allocation functions can still attach new sources to existing public shared lines without specifying a name or handle.

## Troubleshooting Interrupt Allocation

On most Espressif SoCs, CPU interrupts are a limited resource. Therefore it is possible for a program to run out of CPU interrupts, for example by initializing several peripheral drivers. Typically, this will result in the driver initialization function returning `ESP_ERR_NOT_FOUND` error code.

If this happens, you can use `esp_intr_dump()` function to print the list of interrupts along with their status. The output of this function typically looks like this:

    CPU 0 interrupt status:
    Int  Level  Type   Status
    0     1    Level  Reserved
    1     1    Level  Reserved
    2     1    Level  Used: RTC_CORE
    3     1    Level  Used: TG0_LACT_LEVEL
    ...

The columns of the output have the following meaning:

- `Int`: CPU interrupt input number. This is typically not used in software directly, and is provided for reference only.

- `Level`: For interrupts which have been allocated, the priority of the interrupt. For free interrupts `*` is printed.

- `Type`: For interrupts which have been allocated, the type (Level or Edge) of the interrupt. For free interrupts `*` is printed.

- `Status`: One of the possible statuses of the interrupt:

    - `Reserved`: The interrupt is reserved either at hardware level, or by one of the parts of ESP-IDF. It can not be allocated using `esp_intr_alloc()`.

    - `Used: <source>`: The interrupt is allocated and connected to a single peripheral.

    - `Shared: <source1> <source2> ...`: The interrupt is allocated and connected to multiple peripherals. See Multiple Handlers Sharing A Source above.

    - `Free`: The interrupt is not allocated and can be used by `esp_intr_alloc()`.

If you have confirmed that the application is indeed running out of interrupts, a combination of the following suggestions can help resolve the issue:

- On multi-core targets, try initializing some of the peripheral drivers from a task pinned to the second core. Interrupts are typically allocated on the same core where the peripheral driver initialization function runs. Therefore by running the initialization function on the second core, more interrupt inputs can be used.

- Determine the interrupts which can tolerate higher latency, and allocate them using `ESP_INTR_FLAG_SHARED` flag (optionally ORed with `ESP_INTR_FLAG_LOWMED`). Using this flag for two or more peripherals will let them use a single interrupt input, and therefore save interrupt inputs for other peripherals. See Multiple Handlers Sharing A Source above.

- Check if some of the peripheral drivers do not need to be used all the time, and initialize or deinitialize them on demand. This can reduce the number of simultaneously allocated interrupts.

## API Reference

### Header File

- components/esp_hw_support/include/esp_intr_types.h

- This header file can be included with:

  >     #include "esp_intr_types.h"

### Macros

ESP_INTR_CPU_AFFINITY_TO_CORE_ID(cpu_affinity)

  Convert esp_intr_cpu_affinity_t to CPU core ID.

### Type Definitions

typedef void (*intr_handler_t)(void *arg)

  Function prototype for interrupt handler function

typedef struct intr_handle_data_t *intr_handle_t

  Handle to an interrupt handler

### Enumerations

enum esp_intr_cpu_affinity_t

  Interrupt CPU core affinity.

  This type specify the CPU core that the peripheral interrupt is connected to.

  Values:

  enumerator ESP_INTR_CPU_AFFINITY_AUTO

    Install the peripheral interrupt to ANY CPU core, decided by on which CPU the interrupt allocator is running.

  enumerator ESP_INTR_CPU_AFFINITY_0

    Install the peripheral interrupt to CPU core 0.

  enumerator ESP_INTR_CPU_AFFINITY_1

    Install the peripheral interrupt to CPU core 1.

### Header File

- components/esp_hw_support/include/esp_intr_alloc.h

- This header file can be included with:

  >     #include "esp_intr_alloc.h"

### Structures

struct esp_intr_alloc_info_t

  Structure used to specify all the information needed to allocate an interrupt. This structure can be passed to the function esp_intr_alloc_info.

  Public Members

  int source

    Interrupt source to map

  int flags

    ORred mask of the ESP_INTR_FLAG_* defines

  uint32_t intrstatusreg

    Address of the interrupt status register

  uint32_t intrstatusmask

    Mask of the interrupt status register

  intr_handler_t handler

    Interrupt handler to invoke upon triggered interrupt

  void *arg

    Argument to pass to the interrupt handler

  intr_handle_t handle

    Handle of the interrupt to share with

  const char *name

    Name of the interrupt to share with

  struct esp_intr_alloc_info_t bind_by

    Interrupt to share with, to be populated when flags are set to ESP_INTR_FLAG_SHARED or ESP_INTR_FLAG_SHARED_PRIVATE, and only provide one of `handle` or `name`.

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.