diff --git a/components/esp_hw_support/include/esp_intr_alloc.h b/components/esp_hw_support/include/esp_intr_alloc.h index 60e3576187..b2f1b7bfc6 100644 --- a/components/esp_hw_support/include/esp_intr_alloc.h +++ b/components/esp_hw_support/include/esp_intr_alloc.h @@ -94,7 +94,7 @@ extern "C" { * the int can be left enabled while the flash cache is disabled. * * @return ESP_ERR_INVALID_ARG if cpu or intno is invalid - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_mark_shared(int intno, int cpu, bool is_in_iram); @@ -108,7 +108,7 @@ esp_err_t esp_intr_mark_shared(int intno, int cpu, bool is_in_iram); * @param cpu CPU on which the interrupt should be marked as shared (0 or 1) * * @return ESP_ERR_INVALID_ARG if cpu or intno is invalid - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_reserve(int intno, int cpu); @@ -131,9 +131,10 @@ esp_err_t esp_intr_reserve(int intno, int cpu); * @param flags An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the * choice of interrupts that this routine can choose from. If this value * is 0, it will default to allocating a non-shared interrupt of level - * 1, 2 or 3. If this is ESP_INTR_FLAG_SHARED, it will allocate a shared - * interrupt of level 1. Setting ESP_INTR_FLAG_INTRDISABLED will return - * from this function with the interrupt disabled. + * 1, 2 or 3. If ESP_INTR_FLAG_SHARED mask is provided, a shared interrupt of + * the given level will be allocated (or level 1 if not specified). + * Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the + * interrupt disabled. * @param handler The interrupt handler. Must be NULL when an interrupt of level >3 * is requested, because these types of interrupts aren't C-callable. * @param arg Optional argument for passed to the interrupt handler @@ -143,13 +144,13 @@ esp_err_t esp_intr_reserve(int intno, int cpu); * * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. * ESP_ERR_NOT_FOUND No free interrupt found with the specified flags - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t *ret_handle); /** - * @brief Allocate an interrupt with the given parameters. + * @brief Allocate an interrupt with the given parameters, including an interrupt status register. * * * This essentially does the same as esp_intr_alloc, but allows specifying a register and mask @@ -165,9 +166,10 @@ esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *ar * @param flags An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the * choice of interrupts that this routine can choose from. If this value * is 0, it will default to allocating a non-shared interrupt of level - * 1, 2 or 3. If this is ESP_INTR_FLAG_SHARED, it will allocate a shared - * interrupt of level 1. Setting ESP_INTR_FLAG_INTRDISABLED will return - * from this function with the interrupt disabled. + * 1, 2 or 3. If ESP_INTR_FLAG_SHARED mask is provided, a shared interrupt of + * the given level will be allocated (or level 1 if not specified). + * Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the + * interrupt disabled. * @param intrstatusreg The address of an interrupt status register * @param intrstatusmask A mask. If a read of address intrstatusreg has any of the bits * that are 1 in the mask set, the ISR will be called. If not, it will be @@ -181,11 +183,92 @@ esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *ar * * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. * ESP_ERR_NOT_FOUND No free interrupt found with the specified flags - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, void *arg, intr_handle_t *ret_handle); +/** + * @brief Allocate an interrupt with the given parameters that can be bound to an existing interrupt handler. + * + * + * This function does the same as esp_intr_alloc, but allows specifying a previously allocated handler as + * the interrupt to share with the given source. This can be very handy to treat two pre-determined interrupt + * sources in the same interrupt handler. The interrupt will be allocated on the same core as the given + * `shared_handle`. Moreover, make sure to specify the same interrupt level as the one being used by `shared_handle` + * to prevent any failure from this function. + * + * @param source The interrupt source. One of the ETS_*_INTR_SOURCE interrupt mux + * sources, as defined in soc/soc.h, or one of the internal + * ETS_INTERNAL_*_INTR_SOURCE sources as defined in this header. + * @param flags An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the + * choice of interrupts that this routine can choose from. If this value + * is 0, it will default to allocating a non-shared interrupt of level + * 1, 2 or 3. If ESP_INTR_FLAG_SHARED mask is provided, a shared interrupt of + * the given level will be allocated (or level 1 if not specified). + * Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the + * interrupt disabled. + * @param handler The interrupt handler. Must be NULL when an interrupt of level >3 + * is requested, because these types of interrupts aren't C-callable. + * @param arg Optional argument for passed to the interrupt handler + * @param shared_handle Previously allocated interrupt to share the CPU interrupt line with. If NULL, + * calling this function equivalent to esp_intr_alloc, else, ESP_INTR_FLAG_SHARED must + * be provided in the flags parameter. + * @param ret_handle Pointer to an intr_handle_t to store a handle that can later be + * used to request details or free the interrupt. Can be NULL if no handle + * is required. + * + * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. + * ESP_ERR_NOT_FOUND No free interrupt found with the specified flasg or the given level is different + * from the one assigned to the share_handle parameter. + * ESP_OK on success + */ +esp_err_t esp_intr_alloc_bind(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t shared_handle, intr_handle_t *ret_handle); + + +/** + * @brief Allocate an interrupt with the given parameters, including an interrupt status register, that can + * be bound to an existing interrupt handler + * + * + * This function does the same as esp_intr_alloc_intrstatus, but allows specifying a previously allocated handler as + * the interrupt to share with the given source. This can be very handy to treat two pre-determined interrupt + * sources in the same interrupt handler. The interrupt will be allocated on the same core as the given + * `shared_handle`. Moreover, make sure to specify the same interrupt level as the one being used by `shared_handle` + * to prevent any failure from this function. + * + * @param source The interrupt source. One of the ETS_*_INTR_SOURCE interrupt mux + * sources, as defined in soc/soc.h, or one of the internal + * ETS_INTERNAL_*_INTR_SOURCE sources as defined in this header. + * @param flags An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the + * choice of interrupts that this routine can choose from. If this value + * is 0, it will default to allocating a non-shared interrupt of level + * 1, 2 or 3. If ESP_INTR_FLAG_SHARED mask is provided, a shared interrupt of + * the given level will be allocated (or level 1 if not specified). + * Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the + * interrupt disabled. + * @param intrstatusreg The address of an interrupt status register + * @param intrstatusmask A mask. If a read of address intrstatusreg has any of the bits + * that are 1 in the mask set, the ISR will be called. If not, it will be + * skipped. + * @param handler The interrupt handler. Must be NULL when an interrupt of level >3 + * is requested, because these types of interrupts aren't C-callable. + * @param arg Optional argument for passed to the interrupt handler + * @param shared_handle Previously allocated interrupt to share the CPU interrupt line with. If NULL, + * calling this function equivalent to esp_intr_alloc, else, ESP_INTR_FLAG_SHARED must + * be provided in the flags parameter. + * @param ret_handle Pointer to an intr_handle_t to store a handle that can later be + * used to request details or free the interrupt. Can be NULL if no handle + * is required. + * + * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. + * ESP_ERR_NOT_FOUND No free interrupt found with the specified flasg or the given level is different + * from the one assigned to the share_handle parameter. + * ESP_OK on success + */ +esp_err_t esp_intr_alloc_intrstatus_bind(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, + void *arg, intr_handle_t shared_handle, intr_handle_t *ret_handle); + /** * @brief Disable and free an interrupt. * @@ -202,7 +285,7 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre * * @return ESP_ERR_INVALID_ARG the handle is NULL * ESP_FAIL failed to release this handle - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_free(intr_handle_t handle); @@ -239,7 +322,7 @@ int esp_intr_get_intno(intr_handle_t handle); * @param handle The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus * * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_disable(intr_handle_t handle); @@ -252,7 +335,7 @@ esp_err_t esp_intr_disable(intr_handle_t handle); * @param handle The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus * * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_enable(intr_handle_t handle); @@ -266,7 +349,7 @@ esp_err_t esp_intr_enable(intr_handle_t handle); * Handlers residing in IRAM can be called when cache is disabled. * * @return ESP_ERR_INVALID_ARG if the combination of arguments is invalid. - * ESP_OK otherwise + * ESP_OK on success */ esp_err_t esp_intr_set_in_iram(intr_handle_t handle, bool is_in_iram); diff --git a/components/esp_hw_support/intr_alloc.c b/components/esp_hw_support/intr_alloc.c index be5f5e4c99..39f19b24a6 100644 --- a/components/esp_hw_support/intr_alloc.c +++ b/components/esp_hw_support/intr_alloc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -72,6 +72,7 @@ struct shared_vector_desc_t { #define VECDESC_FL_INIRAM (1<<1) #define VECDESC_FL_SHARED (1<<2) #define VECDESC_FL_NONSHARED (1<<3) +#define VECDESC_FL_TYPE_MASK (0xf) #if SOC_CPU_HAS_FLEXIBLE_INTC /* On targets that have configurable interrupts levels, store the assigned level in the flags */ @@ -221,7 +222,7 @@ esp_err_t esp_intr_mark_shared(int intno, int cpu, bool is_int_ram) portEXIT_CRITICAL(&spinlock); return ESP_ERR_NO_MEM; } - vd->flags = VECDESC_FL_SHARED; + vd->flags = (vd->flags & ~VECDESC_FL_TYPE_MASK) | VECDESC_FL_SHARED; if (is_int_ram) { vd->flags |= VECDESC_FL_INIRAM; } @@ -499,8 +500,8 @@ bool esp_intr_ptr_in_isr_region(void* ptr) //We use ESP_EARLY_LOG* here because this can be called before the scheduler is running. -esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, - void *arg, intr_handle_t *ret_handle) +esp_err_t esp_intr_alloc_intrstatus_bind(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, + void *arg, intr_handle_t shared_handle, intr_handle_t *ret_handle) { intr_handle_data_t *ret=NULL; int force = -1; @@ -528,7 +529,10 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre if ((flags & ESP_INTR_FLAG_IRAM) && handler && !esp_intr_ptr_in_isr_region(handler)) { return ESP_ERR_INVALID_ARG; } - + //Shared handler must be passed with share interrupt flag + if (shared_handle != NULL && (flags & ESP_INTR_FLAG_SHARED) == 0) { + return ESP_ERR_INVALID_ARG; + } //Default to prio 1 for shared interrupts. Default to prio 1, 2 or 3 for non-shared interrupts. if ((flags & ESP_INTR_FLAG_LEVELMASK) == 0) { if (flags & ESP_INTR_FLAG_SHARED) { @@ -568,7 +572,17 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre portENTER_CRITICAL(&spinlock); uint32_t cpu = esp_cpu_get_core_id(); - //See if we can find an interrupt that matches the flags. + if (shared_handle != NULL) { + /* Sanity check, should not occur */ + if (shared_handle->vector_desc == NULL) { + portEXIT_CRITICAL(&spinlock); + return ESP_ERR_INVALID_ARG; + } + /* If a shared vector was given, force the current interrupt source to same CPU interrupt line */ + force = shared_handle->vector_desc->intno; + /* Allocate the interrupt on the same core as the given handle */ + cpu = shared_handle->vector_desc->cpu; + } int intr = get_available_int(flags, cpu, force, source); if (intr == -1) { //None found. Bail out. @@ -691,6 +705,13 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre return ESP_OK; } +esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, + void *arg, intr_handle_t *ret_handle) +{ + return esp_intr_alloc_intrstatus_bind(source, flags, intrstatusreg, intrstatusmask, handler, arg, NULL, ret_handle); +} + + esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t *ret_handle) { /* @@ -701,6 +722,13 @@ esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *ar return esp_intr_alloc_intrstatus(source, flags, 0, 0, handler, arg, ret_handle); } + +esp_err_t esp_intr_alloc_bind(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t shared_handle, intr_handle_t *ret_handle) +{ + return esp_intr_alloc_intrstatus_bind(source, flags, 0, 0, handler, arg, shared_handle, ret_handle); +} + + esp_err_t IRAM_ATTR esp_intr_set_in_iram(intr_handle_t handle, bool is_in_iram) { if (!handle) { diff --git a/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_intr_alloc.c b/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_intr_alloc.c index aabf0d7c59..4ffb1117b0 100644 --- a/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_intr_alloc.c +++ b/components/esp_hw_support/test_apps/esp_hw_support_unity_tests/main/test_intr_alloc.c @@ -130,13 +130,13 @@ TEST_CASE("Intr_alloc test, shared interrupts don't affect level", "[intr_alloc] intr_handle_t handle_lvl_2; /* Allocate an interrupt of level 1 that will be shared with another source */ - esp_err_t err = esp_intr_alloc(ETS_SHA_INTR_SOURCE, + esp_err_t err = esp_intr_alloc(ETS_FROM_CPU_INTR2_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_SHARED, test_isr, NULL, &handle_lvl_1); TEST_ESP_OK(err); /* Allocate a shared interrupt of a different level */ - err = esp_intr_alloc(ETS_AES_INTR_SOURCE, + err = esp_intr_alloc(ETS_FROM_CPU_INTR3_SOURCE, ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_SHARED, test_isr, NULL, &handle_lvl_2); TEST_ESP_OK(err); @@ -163,7 +163,7 @@ TEST_CASE("Intr_alloc test, shared interrupts custom level cleared", "[intr_allo { intr_handle_t handle; - esp_err_t err = esp_intr_alloc(ETS_SHA_INTR_SOURCE, + esp_err_t err = esp_intr_alloc(ETS_FROM_CPU_INTR2_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_SHARED, test_isr, NULL, &handle); TEST_ESP_OK(err); @@ -174,7 +174,7 @@ TEST_CASE("Intr_alloc test, shared interrupts custom level cleared", "[intr_allo /* Free the shared interrupt and try to reallocate it with another level */ TEST_ESP_OK(esp_intr_free(handle)); - err = esp_intr_alloc(ETS_AES_INTR_SOURCE, + err = esp_intr_alloc(ETS_FROM_CPU_INTR3_SOURCE, ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_SHARED, test_isr, NULL, &handle); TEST_ESP_OK(err); @@ -191,6 +191,39 @@ TEST_CASE("Intr_alloc test, shared interrupts custom level cleared", "[intr_allo #endif +/** + * Make sure we can map two given sources to the same interrupt line when their levels match. + */ +TEST_CASE("Intr_alloc test, shared interrupt line for two sources", "[intr_alloc]") +{ + intr_handle_t handle_1; + intr_handle_t handle_2; + + esp_err_t err = esp_intr_alloc(ETS_FROM_CPU_INTR2_SOURCE, + ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_SHARED, + test_isr, NULL, &handle_1); + TEST_ESP_OK(err); + + /* Map another source to the exact same interrupt line */ + err = esp_intr_alloc_bind(ETS_FROM_CPU_INTR3_SOURCE, + ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_SHARED, + test_isr, NULL, handle_1, &handle_2); + TEST_ESP_OK(err); + /* Make sure they are both using the same interrupt line */ + TEST_ASSERT_EQUAL(esp_intr_get_intno(handle_1), esp_intr_get_intno(handle_2)); + + /* Reallocate the second interrupt source with a higher level, it must fail */ + TEST_ESP_OK(esp_intr_free(handle_2)); + err = esp_intr_alloc_bind(ETS_FROM_CPU_INTR3_SOURCE, + ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_SHARED, + test_isr, NULL, handle_1, &handle_2); + TEST_ASSERT(err != ESP_OK); + + /* Free the remaining handler */ + TEST_ESP_OK(esp_intr_free(handle_1)); +} + + TEST_CASE("Allocate previously freed interrupt, with different flags", "[intr_alloc]") { intr_handle_t intr; diff --git a/docs/en/api-reference/system/intr_alloc.rst b/docs/en/api-reference/system/intr_alloc.rst index 6effc5d7df..67193a6394 100644 --- a/docs/en/api-reference/system/intr_alloc.rst +++ b/docs/en/api-reference/system/intr_alloc.rst @@ -32,7 +32,7 @@ Overview Because there are more interrupt sources than interrupts, sometimes it makes sense to share an interrupt in multiple drivers. The :cpp:func:`esp_intr_alloc` abstraction exists to hide all these implementation details. -A driver can allocate an interrupt for a certain peripheral by calling :cpp:func:`esp_intr_alloc` (or :cpp:func:`esp_intr_alloc_intrstatus`). 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. +A driver can allocate an interrupt for a certain peripheral by calling :cpp:func:`esp_intr_alloc`, :cpp:func:`esp_intr_alloc_bind`, :cpp:func:`esp_intr_alloc_intrstatus`, or :cpp:func:`esp_intr_alloc_intrstatus_bind`. 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 :cpp:func:`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. @@ -138,9 +138,7 @@ 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. -.. only:: not SOC_CPU_HAS_FLEXIBLE_INTC - - 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. +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. 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. @@ -150,6 +148,8 @@ Though the framework supports this feature, you have to use it **very carefully* 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 :cpp:func:`esp_intr_alloc` or :cpp:func:`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 :cpp:func:`esp_intr_alloc_bind` or :cpp:func:`esp_intr_alloc_intrstatus_bind`, you can explicitly specify the interrupt handler to be shared with the given interrupt source. + Troubleshooting Interrupt Allocation ------------------------------------ diff --git a/tools/test_apps/system/panic/CMakeLists.txt b/tools/test_apps/system/panic/CMakeLists.txt index e9db4327f5..a398484561 100644 --- a/tools/test_apps/system/panic/CMakeLists.txt +++ b/tools/test_apps/system/panic/CMakeLists.txt @@ -36,8 +36,8 @@ if(NOT CONFIG_TEST_MEMPROT AND NOT CONFIG_ESP_COREDUMP_CAPTURE_DRAM) esp_common esp_hw_support soc hal freertos) if(CONFIG_ESP_COREDUMP_CHECKSUM_SHA256) if(CONFIG_IDF_TARGET_ESP32S2) - # due to the ram limitation, coredump is removed from esp32s2 built - list(REMOVE_ITEM ubsan_components espcoredump) + # due to the ram limitation, coredump and freertos are removed from esp32s2 built + list(REMOVE_ITEM ubsan_components espcoredump freertos) endif() endif() foreach(component IN LISTS ubsan_components)