Merge branch 'feature/esp_intr_dump' into 'master'

system: add esp_intr_dump API to debug interrupt allocation

Closes IDF-4281 and IDF-6066

See merge request espressif/esp-idf!23877
This commit is contained in:
Ivan Grokhotkov 2023-07-20 19:53:58 +08:00
commit e6c3f62281
16 changed files with 708 additions and 1 deletions

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "esp_err.h"
#ifdef __cplusplus
@ -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);
/**@}*/

View File

@ -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"
@ -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.
@ -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;
}

View File

@ -85,6 +85,8 @@ 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
----------------------------------
@ -92,10 +94,55 @@ Several handlers can be assigned to a same source, given that all handlers are a
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
-------------

View File

@ -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'"

View File

@ -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)

View File

@ -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`.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,36 @@
CPU 0 interrupt status:
Int Level Type Status
0 * * Reserved
1 * * Reserved
2 2 Level Used: LP_RTC_TIMER
3 * * Reserved
4 * * Reserved
5 * * Reserved
6 * * Reserved
7 * * Reserved
8 * * Reserved
9 2 Level Used: SYSTIMER_TARGET2
10 2 Level Used: CPU_FROM_CPU_0
11 2 Level Used: SYSTIMER_TARGET0
12 2 Level Used: TG0_WDT
13 2 Level Used: UART0
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: 15
Shared interrupts: 0

View File

@ -0,0 +1,36 @@
CPU 0 interrupt status:
Int Level Type Status
0 * * Reserved
1 * * Reserved
2 2 Level Used: LP_RTC_TIMER
3 * * Reserved
4 * * Reserved
5 * * Reserved
6 * * Reserved
7 * * Reserved
8 * * Reserved
9 2 Level Used: SYSTIMER_TARGET2
10 2 Level Used: CPUFROM_CPU_0
11 2 Level Used: SYSTIMER_TARGET0
12 2 Level Used: TG0_WDT
13 2 Level Used: UART0
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: 15
Shared interrupts: 0

View File

@ -0,0 +1,36 @@
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: FROM_CPU_INTR0
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: TG0_WDT_LEVEL
10 1 Edge Used: SYSTIMER_TARGET2
11 3 Level CPU-internal
12 1 Level Used: UART0
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 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
Interrupts available for general use: 7
Shared interrupts: 0

View File

@ -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: SYSTIMER_TARGET2
4 1 Level Reserved
5 1 Level Reserved
6 1 Level CPU-internal
7 1 Level CPU-internal
8 1 Level Reserved
9 1 Level Used: FROM_CPU_INTR0
10 1 Edge Free (not general-use)
11 3 Level CPU-internal
12 1 Level Used: SYSTIMER_TARGET0
13 1 Level Used: TG0_WDT_LEVEL
14 7 Level Reserved
15 3 Level CPU-internal
16 5 Level CPU-internal
17 1 Level Used: UART0
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_CPU_INTR1
3 1 Level Used: SYSTIMER_TARGET1
4 1 Level Free
5 1 Level Reserved
6 1 Level CPU-internal
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: 15
Shared interrupts: 0

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "test_esp_intr_dump_main.c"
PRIV_REQUIRES console
INCLUDE_DIRS ".")

View File

@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include "esp_intr_alloc.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "soc/interrupts.h"
static void start_console(void);
static void register_intr_commands(void);
void app_main(void)
{
start_console();
}
static void intr_handler_dummy(void* arg)
{
}
static void start_console(void)
{
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
repl_config.prompt = "test_intr_dump>";
repl_config.max_cmdline_length = 80;
/* Register commands */
esp_console_register_help_command();
register_intr_commands();
#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM)
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl));
#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));
#else
#error Unsupported console type
#endif
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}
static struct {
struct arg_str *source;
struct arg_str *flags;
struct arg_end *end;
} intr_alloc_args;
static int cmd_intr_alloc(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &intr_alloc_args);
if (nerrors != 0) {
arg_print_errors(stderr, intr_alloc_args.end, argv[0]);
return 1;
}
assert(intr_alloc_args.source->count == 1);
assert(intr_alloc_args.flags->count == 1);
const char* source_str = intr_alloc_args.source->sval[0];
const char* flags_str = intr_alloc_args.flags->sval[0];
// find the interrupt source number
int source_num = -1;
for (int i = 0; i < ETS_MAX_INTR_SOURCE; i++) {
if (strcmp(source_str, esp_isr_names[i]) == 0) {
source_num = i;
break;
}
}
if (source_num == -1) {
printf("Unknown interrupt source: %s. Please check components/soc/" CONFIG_IDF_TARGET "/interrupts.c for the list\n", source_str);
return 1;
}
// parse flags
typedef struct {
const char* name;
int mask;
} flag_desc_t;
const flag_desc_t flag_desc[] = {
{ .name = "LEVEL1", .mask = ESP_INTR_FLAG_LEVEL1 },
{ .name = "LEVEL2", .mask = ESP_INTR_FLAG_LEVEL2 },
{ .name = "LEVEL3", .mask = ESP_INTR_FLAG_LEVEL3 },
{ .name = "LEVEL4", .mask = ESP_INTR_FLAG_LEVEL4 },
{ .name = "LEVEL5", .mask = ESP_INTR_FLAG_LEVEL5 },
{ .name = "LEVEL6", .mask = ESP_INTR_FLAG_LEVEL6 },
{ .name = "NMI", .mask = ESP_INTR_FLAG_NMI },
{ .name = "SHARED", .mask = ESP_INTR_FLAG_SHARED },
{ .name = "EDGE", .mask = ESP_INTR_FLAG_EDGE },
{ .name = "IRAM", .mask = ESP_INTR_FLAG_IRAM },
{ .name = "LOWMED", .mask = ESP_INTR_FLAG_LOWMED },
{ .name = "HIGH", .mask = ESP_INTR_FLAG_HIGH },
};
// split flags_str string into tokens separated by | character and get the final bitmask
int flags = 0;
char* flags_str_copy = strdup(flags_str);
char* token = strtok(flags_str_copy, "|");
while (token != NULL) {
bool found = false;
for (int i = 0; i < sizeof(flag_desc)/sizeof(flag_desc[0]); i++) {
if (strcmp(token, flag_desc[i].name) == 0) {
flags |= flag_desc[i].mask;
found = true;
break;
}
}
if (!found) {
printf("Unknown flag: %s\n", token);
free(flags_str_copy);
return 1;
}
token = strtok(NULL, "|");
}
free(flags_str_copy);
// allocate the interrupt
esp_err_t ret = esp_intr_alloc(source_num, flags, intr_handler_dummy, NULL, NULL);
if (ret != ESP_OK) {
printf("Failed to allocate interrupt (source: %d, flags: 0x%x): %s\n", source_num, flags, esp_err_to_name(ret));
return 1;
}
return 0;
}
int cmd_intr_dump(int argc, char **argv)
{
esp_intr_dump(stdout);
return 0;
}
static void register_intr_commands(void)
{
intr_alloc_args.source = arg_str1(NULL, NULL, "<source>", "Interrupt source name");
intr_alloc_args.flags = arg_str1(NULL, NULL, "<flags>", "Interrupt flags separated by | character: LEVEL1|LEVEL2|...|LEVEL6|NMI|SHARED|EDGE|IRAM|LOWMED|HIGH");
intr_alloc_args.end = arg_end(2);
const esp_console_cmd_t cmd_intr_alloc_info = {
.command = "intr_alloc",
.help = "Allocate an interrupt",
.hint = NULL,
.func = &cmd_intr_alloc,
.argtable = &intr_alloc_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_intr_alloc_info));
const esp_console_cmd_t cmd_intr_dump_info = {
.command = "intr_dump",
.help = "Dump interrupt information",
.hint = NULL,
.func = &cmd_intr_dump,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_intr_dump_info));
}

View File

@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
import pytest
from pytest_embedded import Dut
PROMPT = 'test_intr_dump>'
@pytest.mark.esp32
@pytest.mark.qemu
def test_esp_intr_dump_nonshared(dut: Dut) -> None:
dut.expect_exact(PROMPT, timeout=10)
dut.write('intr_alloc GPIO LEVEL3\n')
dut.expect_exact(PROMPT)
dut.write('intr_dump\n')
dut.expect(r'(\d+)\s+3\s+Level\s+Used: GPIO')
@pytest.mark.esp32
@pytest.mark.qemu
def test_esp_intr_dump_shared(dut: Dut) -> None:
dut.expect_exact(PROMPT, timeout=10)
dut.write('intr_alloc GPIO SHARED\n')
dut.expect_exact(PROMPT)
dut.write('intr_dump\n')
dut.expect(r'(\d+)\s+1\s+Level\s+Shared: GPIO')
dut.expect_exact(PROMPT)
dut.write('intr_alloc UART1 SHARED\n')
dut.expect_exact(PROMPT)
dut.write('intr_dump\n')
dut.expect(r'(\d+)\s+1\s+Level\s+Shared: UART1 GPIO')
dut.expect_exact('Shared interrupts: 1')
dut.expect_exact(PROMPT)
@pytest.mark.supported_targets
@pytest.mark.generic
def test_esp_intr_dump_expected_output(dut: Dut) -> None:
dut.expect_exact(PROMPT, timeout=10)
dut.write('intr_dump\n')
exp_out_file = os.path.join(os.path.dirname(__file__), 'expected_output', f'{dut.target}.txt')
for line in open(exp_out_file, 'r').readlines():
dut.expect_exact(line.strip())