Skip to content

Commit

Permalink
Merge branch 'feature/esp_intr_dump' into 'master'
Browse files Browse the repository at this point in the history
system: add esp_intr_dump API to debug interrupt allocation

Closes IDF-4281 and IDF-6066

See merge request espressif/esp-idf!23877
  • Loading branch information
igrr committed Jul 20, 2023
2 parents 9d2eb96 + 0ebe2ce commit e6c3f62
Show file tree
Hide file tree
Showing 16 changed files with 708 additions and 1 deletion.
8 changes: 8 additions & 0 deletions components/esp_hw_support/include/esp_intr_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "esp_err.h"

#ifdef __cplusplus
Expand Down Expand Up @@ -307,6 +308,13 @@ static inline int esp_intr_flags_to_level(int flags)
return __builtin_ffs((flags & ESP_INTR_FLAG_LEVELMASK) >> 1) + 1;
}

/**
* @brief Dump the status of allocated interrupts
* @param stream The stream to dump to, if NULL then stdout is used
* @return ESP_OK on success
*/
esp_err_t esp_intr_dump(FILE *stream);

/**@}*/


Expand Down
85 changes: 85 additions & 0 deletions components/esp_hw_support/intr_alloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#include "esp_attr.h"
#include "esp_cpu.h"
#include "esp_private/rtc_ctrl.h"
#include "soc/interrupts.h"
#include "soc/soc_caps.h"
#include "sdkconfig.h"

#if !CONFIG_FREERTOS_UNICORE
#include "esp_ipc.h"
Expand Down Expand Up @@ -539,6 +542,7 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre
//None found. Bail out.
portEXIT_CRITICAL(&spinlock);
free(ret);
ESP_LOGE(TAG, "No free interrupt inputs for %s interrupt (flags 0x%X)", esp_isr_names[source], flags);
return ESP_ERR_NOT_FOUND;
}
//Get an int vector desc for int.
Expand Down Expand Up @@ -902,3 +906,84 @@ void IRAM_ATTR esp_intr_disable_source(int inum)
{
esp_cpu_intr_disable(1 << inum);
}

esp_err_t esp_intr_dump(FILE *stream)
{
if (stream == NULL) {
stream = stdout;
}
#ifdef CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
const int cpu_num = 1;
#else
const int cpu_num = SOC_CPU_CORES_NUM;
#endif

int general_use_ints_free = 0;
int shared_ints = 0;

for (int cpu = 0; cpu < cpu_num; ++cpu) {
fprintf(stream, "CPU %d interrupt status:\n", cpu);
fprintf(stream, " Int Level Type Status\n");
for (int i_num = 0; i_num < 32; ++i_num) {
fprintf(stream, " %2d ", i_num);
esp_cpu_intr_desc_t intr_desc;
esp_cpu_intr_get_desc(cpu, i_num, &intr_desc);
bool is_general_use = true;
vector_desc_t *vd = find_desc_for_int(i_num, cpu);

#ifndef SOC_CPU_HAS_FLEXIBLE_INTC
fprintf(stream, " %d %s ",
intr_desc.priority,
intr_desc.type == ESP_CPU_INTR_TYPE_EDGE ? "Edge " : "Level");

is_general_use = (intr_desc.type == ESP_CPU_INTR_TYPE_LEVEL) && (intr_desc.priority <= XCHAL_EXCM_LEVEL);
#else // SOC_CPU_HAS_FLEXIBLE_INTC
if (vd == NULL) {
fprintf(stream, " * * ");
} else {
// esp_cpu_intr_get_* functions need to be extended with cpu parameter.
// Showing info for the current cpu only, in the meantime.
if (esp_cpu_get_core_id() == cpu) {
fprintf(stream, " %d %s ",
esp_cpu_intr_get_priority(i_num),
esp_cpu_intr_get_type(i_num) == ESP_CPU_INTR_TYPE_EDGE ? "Edge " : "Level");
} else {
fprintf(stream, " ? ? ");
}
}
#endif // SOC_CPU_HAS_FLEXIBLE_INTC

if (intr_desc.flags & ESP_CPU_INTR_DESC_FLAG_RESVD) {
fprintf(stream, "Reserved");
} else if (intr_desc.flags & ESP_CPU_INTR_DESC_FLAG_SPECIAL) {
fprintf(stream, "CPU-internal");
} else {
if (vd == NULL || (vd->flags & (VECDESC_FL_RESERVED | VECDESC_FL_NONSHARED | VECDESC_FL_SHARED)) == 0) {
fprintf(stream, "Free");
if (is_general_use) {
++general_use_ints_free;
} else {
fprintf(stream, " (not general-use)");
}
} else if (vd->flags & VECDESC_FL_RESERVED) {
fprintf(stream, "Reserved (run-time)");
} else if (vd->flags & VECDESC_FL_NONSHARED) {
fprintf(stream, "Used: %s", esp_isr_names[vd->source]);
} else if (vd->flags & VECDESC_FL_SHARED) {
fprintf(stream, "Shared: ");
for (shared_vector_desc_t *svd = vd->shared_vec_info; svd != NULL; svd = svd->next) {
fprintf(stream, "%s ", esp_isr_names[svd->source]);
}
++shared_ints;
} else {
fprintf(stream, "Unknown, flags = 0x%x", vd->flags);
}
}

fprintf(stream, "\n");
}
}
fprintf(stream, "Interrupts available for general use: %d\n", general_use_ints_free);
fprintf(stream, "Shared interrupts: %d\n", shared_ints);
return ESP_OK;
}
49 changes: 48 additions & 1 deletion docs/en/api-reference/system/intr_alloc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,64 @@ It can also be useful to keep an interrupt handler in IRAM if it is called very

Refer to the :ref:`SPI flash API documentation <iram-safe-interrupt-handlers>` for more details.

.. _intr-alloc-shared-interrupts:

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.

Though the framework support 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*. IDF only handles enabling and disabling of the source itself, leaving status and mask bits to be handled by users.
.. only:: not SOC_CPU_HAS_FLEXIBLE_INTC

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

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*. 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, either be masked and then properly handled in another enabled interrupt**.
Please note that 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.

Troubleshooting Interrupt Allocation
------------------------------------

On most Espressif SoCs CPU interrupts are a limited resource. Therefore it is possible to run a program which runs 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 :cpp:func:`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:

.. list::

- ``Int``: CPU interrupt input number. This is typically not used in software directly, and is provided for reference only.
:not SOC_CPU_HAS_FLEXIBLE_INTC: - ``Level``: Interrupt level (1-7) of the CPU interrupt. This level is fixed in hardware, and cannot be changed.
:SOC_CPU_HAS_FLEXIBLE_INTC: - ``Level``: For interrupts which have been allocated, the level (priority) of the interrupt. For free interrupts ``*`` is printed.
:not SOC_CPU_HAS_FLEXIBLE_INTC: - ``Type``: Interrupt type (Level or Edge) of the CPU interrupt. This type is fixed in hardware, and cannot be changed.
:SOC_CPU_HAS_FLEXIBLE_INTC: - ``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 :cpp:func:`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 :ref:`intr-alloc-shared-interrupts` above.
- ``Free``: The interrupt is not allocated and can be used by :cpp:func:`esp_intr_alloc`.
:not SOC_CPU_HAS_FLEXIBLE_INTC: - ``Free (not general-use)``: The interrupt is not allocated, but is either a high-level interrupt (level 4-7) or and edge-triggered interrupt. High-level interrupts can be allocated using :cpp:func:`esp_intr_alloc` but require the handlers to be written in Assembly, see :doc:`../../api-guides/hlinterrupts`. Edge-triggered low- and medium- level interrupts can also be allocated using :cpp:func:`esp_intr_alloc`, but are not used often since most peripheral interrupts are level-triggered.

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

.. list::

:not CONFIG_FREERTOS_UNICORE: - On multi-core SoCs, 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 :ref:`intr-alloc-shared-interrupts` above.
:not SOC_CPU_HAS_FLEXIBLE_INTC: - Some peripheral driver may default to allocating interrupts with ``ESP_INTR_FLAG_LEVEL1`` flag, so level 2 and 3 interrupts won't get used by default. If :cpp:func:`esp_intr_dump` shows that some level 2 or 3 interrupts are available, try changing the interrupt allocation flags when initializing the driver to ``ESP_INTR_FLAG_LEVEL2`` or ``ESP_INTR_FLAG_LEVEL3``.
- Check if some of the peripheral drivers do not need to be used all the time, and initialize/deinitialize them on demand. This can reduce the number of simultaneously allocated interrupts.


API Reference
-------------

Expand Down
4 changes: 4 additions & 0 deletions tools/idf_py_actions/hints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,7 @@
-
re_variables: ['ESP_HF_CME_MEMEORY_FAILURE']
hint_variables: ['ESP_HF_CME_MEMEORY_FAILURE', 'ESP_HF_CME_MEMORY_FAILURE ']

-
re: "intr_alloc: No free interrupt inputs for [_\\w]+ interrupt"
hint: "For troubleshooting instructions related to interrupt allocation, run 'idf.py docs -sp api-reference/system/intr_alloc.html'"
5 changes: 5 additions & 0 deletions tools/test_apps/system/esp_intr_dump/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.16)

set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_intr_dump)
14 changes: 14 additions & 0 deletions tools/test_apps/system/esp_intr_dump/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |

# Test for esp_intr_dump

This test app serves two purposes:
1. Sanity-checking `esp_intr_dump` function. These tests run in QEMU and make sure that `esp_intr_dump` produces expected output when e.g. a shared interrupt is allocated.
2. Making unintended changes to the default interrupt allocations more visible in MRs. The way this works is, the output of `esp_intr_dump` is compared to the expected output, for example [expected_output/esp32.txt](expected_output/esp32.txt). If you change IDF startup code so that it allocates an additional interrupt, you will need to update the expected output file. MR reviewers will see the modification of the expected output file and will evaluate the impact of the change.

## When adding support for a new chip target

1. Build the test app for the new target, flash it to the board.
2. Enter `intr_dump` command in the console.
3. Copy the output and save it in `expected_output/<target>.txt`.
70 changes: 70 additions & 0 deletions tools/test_apps/system/esp_intr_dump/expected_output/esp32.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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
4 1 Level Reserved
5 1 Level Reserved
6 1 Level Reserved
7 1 Level CPU-internal
8 1 Level Reserved
9 1 Level Used: FROM_CPU0
10 1 Edge Free (not general-use)
11 3 Level CPU-internal
12 1 Level Used: TG0_WDT_LEVEL
13 1 Level Used: UART0
14 7 Level Reserved
15 3 Level CPU-internal
16 5 Level CPU-internal
17 1 Level Free
18 1 Level Free
19 2 Level Free
20 2 Level Free
21 2 Level Free
22 3 Edge Reserved
23 3 Level Free
24 4 Level Reserved
25 4 Level Reserved
26 5 Level Free (not general-use)
27 3 Level Reserved
28 4 Edge Free (not general-use)
29 3 Level CPU-internal
30 4 Edge Reserved
31 5 Level Reserved
CPU 1 interrupt status:
Int Level Type Status
0 1 Level Reserved
1 1 Level Reserved
2 1 Level Used: FROM_CPU1
3 1 Level Free
4 1 Level Free
5 1 Level Reserved
6 1 Level Reserved
7 1 Level CPU-internal
8 1 Level Reserved
9 1 Level Free
10 1 Edge Free (not general-use)
11 3 Level CPU-internal
12 1 Level Free
13 1 Level Free
14 7 Level Reserved
15 3 Level CPU-internal
16 5 Level CPU-internal
17 1 Level Free
18 1 Level Free
19 2 Level Free
20 2 Level Free
21 2 Level Free
22 3 Edge Free (not general-use)
23 3 Level Free
24 4 Level Free (not general-use)
25 4 Level Reserved
26 5 Level Reserved
27 3 Level Reserved
28 4 Edge Free (not general-use)
29 3 Level CPU-internal
30 4 Edge Reserved
31 5 Level Reserved
Interrupts available for general use: 17
Shared interrupts: 0
36 changes: 36 additions & 0 deletions tools/test_apps/system/esp_intr_dump/expected_output/esp32c2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
CPU 0 interrupt status:
Int Level Type Status
0 * * Reserved
1 * * Reserved
2 2 Level Used: RTC_CORE
3 2 Level Used: SYSTIMER_TARGET2_EDGE
4 2 Level Used: ETS_FROM_CPU_INTR0
5 * * Reserved
6 * * Reserved
7 2 Level Used: SYSTIMER_TARGET0_EDGE
8 * * Reserved
9 2 Level Used: UART
10 * * Free
11 * * Free
12 * * Free
13 * * Free
14 * * Free
15 * * Free
16 * * Free
17 * * Free
18 * * Free
19 * * Free
20 * * Free
21 * * Free
22 * * Free
23 * * Free
24 * * Reserved
25 * * Reserved
26 * * Free
27 * * Reserved
28 * * Free
29 * * Free
30 * * Free
31 * * Free
Interrupts available for general use: 19
Shared interrupts: 0
36 changes: 36 additions & 0 deletions tools/test_apps/system/esp_intr_dump/expected_output/esp32c3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
CPU 0 interrupt status:
Int Level Type Status
0 * * Reserved
1 * * Reserved
2 2 Level Used: RTC_CORE
3 2 Level Used: SYSTIMER_TARGET2_EDGE
4 2 Level Used: FROM_CPU_INTR0
5 * * Reserved
6 * * Reserved
7 2 Level Used: SYSTIMER_TARGET0_EDGE
8 * * Reserved
9 2 Level Used: TG0_WDT_LEVEL
10 2 Level Used: UART0
11 * * Free
12 * * Free
13 * * Free
14 * * Free
15 * * Free
16 * * Free
17 * * Free
18 * * Free
19 * * Free
20 * * Free
21 * * Free
22 * * Free
23 * * Free
24 * * Reserved
25 * * Reserved
26 * * Reserved
27 * * Reserved
28 * * Free
29 * * Free
30 * * Free
31 * * Free
Interrupts available for general use: 17
Shared interrupts: 0
Loading

0 comments on commit e6c3f62

Please sign in to comment.