Jelajahi Sumber

esp_timer: Adds AFFINITY options for task and ISR

These new settings allow you to balance the load on cores.
Closes: https://github.com/espressif/esp-idf/issues/10457
KonstantinKondrashov 3 tahun lalu
induk
melakukan
449e4bcae7

+ 1 - 1
components/esp_system/system_init_fn.txt

@@ -14,7 +14,7 @@
 
 
 # esp_timer has to be initialized early, since it is used by several other components
-100: esp_timer_startup_init in components/esp_timer/src/esp_timer.c on BIT(0)
+100: esp_timer_startup_init in components/esp_timer/src/esp_timer.c on CONFIG_ESP_TIMER_ISR_AFFINITY
 
 # esp_sleep doesn't have init dependencies
 105: esp_sleep_startup_init in components/esp_hw_support/sleep_gpio.c on BIT(0)

+ 64 - 1
components/esp_timer/Kconfig

@@ -30,7 +30,7 @@ menu "High resolution timer (esp_timer)"
 
             Note that this is not the same as FreeRTOS timer task. To configure
             FreeRTOS timer task size, see "FreeRTOS timer task stack size" option
-            in "FreeRTOS" menu.
+            in "FreeRTOS".
 
     config ESP_TIMER_INTERRUPT_LEVEL
         int "Interrupt level"
@@ -41,6 +41,69 @@ menu "High resolution timer (esp_timer)"
             It sets the interrupt level for esp_timer ISR in range 1..3.
             A higher level (3) helps to decrease the ISR esp_timer latency.
 
+    config ESP_TIMER_SHOW_EXPERIMENTAL
+        bool "show esp_timer's experimental features"
+        help
+            This shows some hidden features of esp_timer.
+            Note that they may break other features, use them with care.
+
+    config ESP_TIMER_TASK_AFFINITY
+        hex
+        default 0x0 if ESP_TIMER_TASK_AFFINITY_CPU0
+        default 0x1 if ESP_TIMER_TASK_AFFINITY_CPU1
+        default FREERTOS_NO_AFFINITY if ESP_TIMER_TASK_AFFINITY_NO_AFFINITY
+
+    choice ESP_TIMER_TASK_AFFINITY
+        prompt "esp_timer task core affinity"
+        default ESP_TIMER_TASK_AFFINITY_CPU0
+        help
+            The default settings: timer TASK on CPU0 and timer ISR on CPU0.
+            Other settings may help in certain cases, but note that they may break
+            other features, use them with care.
+            - "CPU0": (default) esp_timer task is processed by CPU0.
+            - "CPU1": esp_timer task is processed by CPU1.
+            - "No affinity": esp_timer task can be processed by any CPU.
+
+        config ESP_TIMER_TASK_AFFINITY_CPU0
+            bool "CPU0"
+        config ESP_TIMER_TASK_AFFINITY_CPU1
+            bool "CPU1"
+            depends on !FREERTOS_UNICORE && ESP_TIMER_SHOW_EXPERIMENTAL
+        config ESP_TIMER_TASK_AFFINITY_NO_AFFINITY
+            bool "No affinity"
+            depends on !FREERTOS_UNICORE && ESP_TIMER_SHOW_EXPERIMENTAL
+    endchoice
+
+    config ESP_TIMER_ISR_AFFINITY
+        hex
+        default 0x1 if ESP_TIMER_ISR_AFFINITY_CPU0
+        default 0x2 if ESP_TIMER_ISR_AFFINITY_CPU1
+        default FREERTOS_NO_AFFINITY if ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+
+    choice ESP_TIMER_ISR_AFFINITY
+        prompt "timer interrupt core affinity"
+        default ESP_TIMER_ISR_AFFINITY_CPU0
+        help
+            The default settings: timer TASK on CPU0 and timer ISR on CPU0.
+            Other settings may help in certain cases, but note that they may break
+            other features, use them with care.
+            - "CPU0": (default) timer interrupt is processed by CPU0.
+            - "CPU1": timer interrupt is processed by CPU1.
+            - "No affinity": timer interrupt can be processed by any CPU. It helps
+            to reduce latency but there is a disadvantage it leads to the timer ISR
+            running on every core. It increases the CPU time usage for timer ISRs
+            by N on an N-core system.
+
+        config ESP_TIMER_ISR_AFFINITY_CPU0
+            bool "CPU0"
+        config ESP_TIMER_ISR_AFFINITY_CPU1
+            bool "CPU1"
+            depends on !FREERTOS_UNICORE && ESP_TIMER_SHOW_EXPERIMENTAL
+        config ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+            bool "No affinity"
+            depends on !FREERTOS_UNICORE && ESP_TIMER_SHOW_EXPERIMENTAL
+    endchoice
+
     config ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
         bool "Support ISR dispatch method"
         default n

+ 5 - 0
components/esp_timer/include/esp_timer.h

@@ -98,6 +98,11 @@ esp_err_t esp_timer_early_init(void);
  * Before calling this function, esp_timer_early_init must be called by the
  * startup code.
  *
+ * This function will be called from startup code on every core
+ * if CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY is enabled,
+ * It allocates the timer ISR on MULTIPLE cores and
+ * creates the timer task which can be run on any core.
+ *
  * @return
  *      - ESP_OK on success
  *      - ESP_ERR_NO_MEM if allocation has failed

+ 43 - 23
components/esp_timer/src/esp_timer.c

@@ -15,6 +15,7 @@
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/semphr.h"
+#include "esp_ipc.h"
 #include "esp_timer.h"
 #include "esp_timer_impl.h"
 
@@ -479,37 +480,58 @@ esp_err_t esp_timer_early_init(void)
     return ESP_OK;
 }
 
-esp_err_t esp_timer_init(void)
+static esp_err_t init_timer_task(void)
 {
-    esp_err_t err;
+    esp_err_t err = ESP_OK;
     if (is_initialized()) {
-        return ESP_ERR_INVALID_STATE;
-    }
-
-    int ret = xTaskCreatePinnedToCore(&timer_task, "esp_timer",
-            ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO, &s_timer_task, PRO_CPU_NUM);
-    if (ret != pdPASS) {
-        err = ESP_ERR_NO_MEM;
-        goto out;
-    }
-
-    err = esp_timer_impl_init(&timer_alarm_handler);
-    if (err != ESP_OK) {
-        goto out;
+        ESP_EARLY_LOGE(TAG, "Task is already initialized");
+        err = ESP_ERR_INVALID_STATE;
+    } else {
+        int ret = xTaskCreatePinnedToCore(
+                    &timer_task, "esp_timer",
+                    ESP_TASK_TIMER_STACK, NULL, ESP_TASK_TIMER_PRIO,
+                    &s_timer_task, CONFIG_ESP_TIMER_TASK_AFFINITY);
+        if (ret != pdPASS) {
+            ESP_EARLY_LOGE(TAG, "Not enough memory to create timer task");
+            err = ESP_ERR_NO_MEM;
+        }
     }
+    return err;
+}
 
-    return ESP_OK;
-
-out:
+static void deinit_timer_task(void)
+{
     if (s_timer_task) {
         vTaskDelete(s_timer_task);
         s_timer_task = NULL;
     }
+}
 
-    return ESP_ERR_NO_MEM;
+esp_err_t esp_timer_init(void)
+{
+    esp_err_t err = ESP_OK;
+#ifndef CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+    err = init_timer_task();
+#else
+    /* This function will be run on all cores if CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY is enabled,
+     * We do it that way because we need to allocate the timer ISR on MULTIPLE cores.
+     * timer task will be created by CPU0.
+     */
+    if (xPortGetCoreID() == 0) {
+        err = init_timer_task();
+    }
+#endif // CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+    if (err == ESP_OK) {
+        err = esp_timer_impl_init(&timer_alarm_handler);
+        if (err != ESP_OK) {
+            ESP_EARLY_LOGE(TAG, "ISR init failed");
+            deinit_timer_task();
+        }
+    }
+    return err;
 }
 
-ESP_SYSTEM_INIT_FN(esp_timer_startup_init, BIT(0), 100)
+ESP_SYSTEM_INIT_FN(esp_timer_startup_init, CONFIG_ESP_TIMER_ISR_AFFINITY, 100)
 {
     return esp_timer_init();
 }
@@ -539,9 +561,7 @@ esp_err_t esp_timer_deinit(void)
 #endif
 
     esp_timer_impl_deinit();
-
-    vTaskDelete(s_timer_task);
-    s_timer_task = NULL;
+    deinit_timer_task();
     return ESP_OK;
 }
 

+ 84 - 24
components/esp_timer/src/esp_timer_impl_lac.c

@@ -83,8 +83,15 @@ typedef struct {
 
 static const char* TAG = "esp_timer_impl";
 
+#define NOT_USED 0xBAD00FAD
+
 /* Interrupt handle returned by the interrupt allocator */
-static intr_handle_t s_timer_interrupt_handle;
+#ifdef CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+#define ISR_HANDLERS (portNUM_PROCESSORS)
+#else
+#define ISR_HANDLERS (1)
+#endif
+static intr_handle_t s_timer_interrupt_handle[ISR_HANDLERS] = { NULL };
 
 /* Function from the upper layer to be called when the interrupt happens.
  * Registered in esp_timer_impl_init.
@@ -180,10 +187,47 @@ void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
 
 static void IRAM_ATTR timer_alarm_isr(void *arg)
 {
+#if ISR_HANDLERS == 1
     /* Clear interrupt status */
     REG_WRITE(INT_CLR_REG, TIMG_LACT_INT_CLR);
-    /*  Call the upper layer handler */
+    /* Call the upper layer handler */
     (*s_alarm_handler)(arg);
+#else
+    static volatile uint32_t processed_by = NOT_USED;
+    static volatile bool pending_alarm = false;
+    /* CRITICAL section ensures the read/clear is atomic between cores */
+    portENTER_CRITICAL_ISR(&s_time_update_lock);
+    if (REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST)) {
+        // Clear interrupt status
+        REG_WRITE(INT_CLR_REG, TIMG_LACT_INT_CLR);
+        // Is the other core already processing a previous alarm?
+        if (processed_by == NOT_USED) {
+            // Current core is not processing an alarm yet
+            processed_by = xPortGetCoreID();
+            do {
+                pending_alarm = false;
+                // Clear interrupt status
+                REG_WRITE(INT_CLR_REG, TIMG_LACT_INT_CLR);
+                portEXIT_CRITICAL_ISR(&s_time_update_lock);
+
+                (*s_alarm_handler)(arg);
+
+                portENTER_CRITICAL_ISR(&s_time_update_lock);
+               // Another alarm could have occurred while were handling the previous alarm.
+               // Check if we need to call the s_alarm_handler again:
+               //   1) if the alarm has already been fired, it helps to handle it immediately without an additional ISR call.
+               //   2) handle pending alarm that was cleared by the other core in time when this core worked with the current alarm.
+            } while (REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) || pending_alarm);
+            processed_by = NOT_USED;
+        } else {
+            // Current core arrived at ISR but the other core is still handling a previous alarm.
+            // Once we already cleared the ISR status we need to let the other core know that it was.
+            // Set the flag to handle the current alarm by the other core later.
+            pending_alarm = true;
+        }
+    }
+    portEXIT_CRITICAL_ISR(&s_time_update_lock);
+#endif // ISR_HANDLERS != 1
 }
 
 void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
@@ -232,34 +276,46 @@ esp_err_t esp_timer_impl_early_init(void)
 
 esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
 {
-    s_alarm_handler = alarm_handler;
+    if (s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()] != NULL) {
+        ESP_EARLY_LOGE(TAG, "timer ISR is already initialized");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    int isr_flags = ESP_INTR_FLAG_INTRDISABLED
+                    | ((1 << CONFIG_ESP_TIMER_INTERRUPT_LEVEL) & ESP_INTR_FLAG_LEVELMASK)
+                    | ESP_INTR_FLAG_IRAM;
 
-    const int interrupt_lvl = (1 << CONFIG_ESP_TIMER_INTERRUPT_LEVEL) & ESP_INTR_FLAG_LEVELMASK;
-    esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT,
-            ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM | interrupt_lvl,
-            &timer_alarm_isr, NULL, &s_timer_interrupt_handle);
+    esp_err_t err = esp_intr_alloc(INTR_SOURCE_LACT, isr_flags,
+                                   &timer_alarm_isr, NULL,
+                                   &s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()]);
 
     if (err != ESP_OK) {
-        ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%0x)", err);
+        ESP_EARLY_LOGE(TAG, "Can not allocate ISR handler (0x%0x)", err);
         return err;
     }
 
-    /* In theory, this needs a shared spinlock with the timer group driver.
-     * However since esp_timer_impl_init is called early at startup, this
-     * will not cause issues in practice.
-     */
-    REG_SET_BIT(INT_ENA_REG, TIMG_LACT_INT_ENA);
+    if (s_alarm_handler == NULL) {
+        s_alarm_handler = alarm_handler;
+        /* In theory, this needs a shared spinlock with the timer group driver.
+        * However since esp_timer_impl_init is called early at startup, this
+        * will not cause issues in practice.
+        */
+        REG_SET_BIT(INT_ENA_REG, TIMG_LACT_INT_ENA);
 
-    esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
+        esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000);
 
-    // Set the step for the sleep mode when the timer will work
-    // from a slow_clk frequency instead of the APB frequency.
-    uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * TICKS_PER_US;
-    REG_SET_FIELD(RTC_STEP_REG, TIMG_LACT_RTC_STEP_LEN, slowclk_ticks_per_us);
+        // Set the step for the sleep mode when the timer will work
+        // from a slow_clk frequency instead of the APB frequency.
+        uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * TICKS_PER_US;
+        REG_SET_FIELD(RTC_STEP_REG, TIMG_LACT_RTC_STEP_LEN, slowclk_ticks_per_us);
+    }
 
-    ESP_ERROR_CHECK( esp_intr_enable(s_timer_interrupt_handle) );
+    err = esp_intr_enable(s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()]);
+    if (err != ESP_OK) {
+        ESP_EARLY_LOGE(TAG, "Can not enable ISR (0x%0x)", err);
+    }
 
-    return ESP_OK;
+    return err;
 }
 
 void esp_timer_impl_deinit(void)
@@ -267,10 +323,14 @@ void esp_timer_impl_deinit(void)
     REG_WRITE(CONFIG_REG, 0);
     REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR);
     /* TODO: also clear TIMG_LACT_INT_ENA; however see the note in esp_timer_impl_init. */
-
-    esp_intr_disable(s_timer_interrupt_handle);
-    esp_intr_free(s_timer_interrupt_handle);
-    s_timer_interrupt_handle = NULL;
+    for (unsigned i = 0; i < ISR_HANDLERS; i++) {
+        if (s_timer_interrupt_handle[i] != NULL) {
+            esp_intr_disable(s_timer_interrupt_handle[i]);
+            esp_intr_free(s_timer_interrupt_handle[i]);
+            s_timer_interrupt_handle[i] = NULL;
+        }
+    }
+    s_alarm_handler = NULL;
 }
 
 /* FIXME: This value is safe for 80MHz APB frequency, should be modified to depend on clock frequency. */

+ 77 - 31
components/esp_timer/src/esp_timer_impl_systimer.c

@@ -36,8 +36,15 @@
 
 static const char *TAG = "esp_timer_systimer";
 
+#define NOT_USED 0xBAD00FAD
+
 /* Interrupt handle returned by the interrupt allocator */
-static intr_handle_t s_timer_interrupt_handle;
+#ifdef CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY
+#define ISR_HANDLERS (portNUM_PROCESSORS)
+#else
+#define ISR_HANDLERS (1)
+#endif
+static intr_handle_t s_timer_interrupt_handle[ISR_HANDLERS] = { NULL };
 
 /* Function from the upper layer to be called when the interrupt happens.
  * Registered in esp_timer_impl_init.
@@ -91,10 +98,47 @@ void IRAM_ATTR esp_timer_impl_set_alarm(uint64_t timestamp)
 
 static void IRAM_ATTR timer_alarm_isr(void *arg)
 {
+#if ISR_HANDLERS == 1
     // clear the interrupt
     systimer_ll_clear_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER);
     /* Call the upper layer handler */
     (*s_alarm_handler)(arg);
+#else
+    static volatile uint32_t processed_by = NOT_USED;
+    static volatile bool pending_alarm = false;
+    /* CRITICAL section ensures the read/clear is atomic between cores */
+    portENTER_CRITICAL_ISR(&s_time_update_lock);
+    if (systimer_ll_is_alarm_int_fired(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER)) {
+        // Clear interrupt status
+        systimer_ll_clear_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER);
+        // Is the other core already processing a previous alarm?
+        if (processed_by == NOT_USED) {
+            // Current core is not processing an alarm yet
+            processed_by = xPortGetCoreID();
+            do {
+                pending_alarm = false;
+                // Clear interrupt status
+                systimer_ll_clear_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER);
+                portEXIT_CRITICAL_ISR(&s_time_update_lock);
+
+                (*s_alarm_handler)(arg);
+
+                portENTER_CRITICAL_ISR(&s_time_update_lock);
+               // Another alarm could have occurred while were handling the previous alarm.
+               // Check if we need to call the s_alarm_handler again:
+               //   1) if the alarm has already been fired, it helps to handle it immediately without an additional ISR call.
+               //   2) handle pending alarm that was cleared by the other core in time when this core worked with the current alarm.
+            } while (systimer_ll_is_alarm_int_fired(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER) || pending_alarm);
+            processed_by = NOT_USED;
+        } else {
+            // Current core arrived at ISR but the other core is still handling a previous alarm.
+            // Once we already cleared the ISR status we need to let the other core know that it was.
+            // Set the flag to handle the current alarm by the other core later.
+            pending_alarm = true;
+        }
+    }
+    portEXIT_CRITICAL_ISR(&s_time_update_lock);
+#endif // ISR_HANDLERS != 1
 }
 
 void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
@@ -148,53 +192,55 @@ esp_err_t esp_timer_impl_early_init(void)
 
 esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler)
 {
-    s_alarm_handler = alarm_handler;
-    const int interrupt_lvl = (1 << CONFIG_ESP_TIMER_INTERRUPT_LEVEL) & ESP_INTR_FLAG_LEVELMASK;
-#if SOC_SYSTIMER_INT_LEVEL
-    int int_type = 0;
-#else
-    int int_type = ESP_INTR_FLAG_EDGE;
-#endif // SOC_SYSTIMER_INT_LEVEL
-    esp_err_t err = esp_intr_alloc(ETS_SYSTIMER_TARGET2_EDGE_INTR_SOURCE,
-                                   ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM | int_type | interrupt_lvl,
-                                   &timer_alarm_isr, NULL, &s_timer_interrupt_handle);
+    if (s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()] != NULL) {
+        ESP_EARLY_LOGE(TAG, "timer ISR is already initialized");
+        return ESP_ERR_INVALID_STATE;
+    }
 
+    int isr_flags = ESP_INTR_FLAG_INTRDISABLED
+                    | ((1 << CONFIG_ESP_TIMER_INTERRUPT_LEVEL) & ESP_INTR_FLAG_LEVELMASK)
+#if !SOC_SYSTIMER_INT_LEVEL
+                    | ESP_INTR_FLAG_EDGE
+#endif
+                    | ESP_INTR_FLAG_IRAM;
+
+    esp_err_t err = esp_intr_alloc(ETS_SYSTIMER_TARGET2_EDGE_INTR_SOURCE, isr_flags,
+                                   &timer_alarm_isr, NULL,
+                                   &s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()]);
     if (err != ESP_OK) {
         ESP_EARLY_LOGE(TAG, "esp_intr_alloc failed (0x%x)", err);
-        goto err_intr_alloc;
+        return err;
     }
 
-    /* TODO: if SYSTIMER is used for anything else, access to SYSTIMER_INT_ENA_REG has to be
-    * protected by a shared spinlock. Since this code runs as part of early startup, this
-    * is practically not an issue.
-    */
-    systimer_hal_enable_alarm_int(&systimer_hal, SYSTIMER_ALARM_ESPTIMER);
+    if (s_alarm_handler == NULL) {
+        s_alarm_handler = alarm_handler;
+        /* TODO: if SYSTIMER is used for anything else, access to SYSTIMER_INT_ENA_REG has to be
+        * protected by a shared spinlock. Since this code runs as part of early startup, this
+        * is practically not an issue.
+        */
+        systimer_hal_enable_alarm_int(&systimer_hal, SYSTIMER_ALARM_ESPTIMER);
+    }
 
-    err = esp_intr_enable(s_timer_interrupt_handle);
+    err = esp_intr_enable(s_timer_interrupt_handle[(ISR_HANDLERS == 1) ? 0 : xPortGetCoreID()]);
     if (err != ESP_OK) {
-        ESP_EARLY_LOGE(TAG, "esp_intr_enable failed (0x%x)", err);
-        goto err_intr_en;
+        ESP_EARLY_LOGE(TAG, "Can not enable ISR (0x%0x)", err);
     }
-    return ESP_OK;
 
-err_intr_en:
-    systimer_ll_enable_alarm(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER, false);
-    /* TODO: may need a spinlock, see the note related to SYSTIMER_INT_ENA_REG in systimer_hal_init */
-    systimer_ll_enable_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER, false);
-    esp_intr_free(s_timer_interrupt_handle);
-err_intr_alloc:
-    s_alarm_handler = NULL;
     return err;
 }
 
 void esp_timer_impl_deinit(void)
 {
-    esp_intr_disable(s_timer_interrupt_handle);
     systimer_ll_enable_alarm(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER, false);
     /* TODO: may need a spinlock, see the note related to SYSTIMER_INT_ENA_REG in systimer_hal_init */
     systimer_ll_enable_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_ESPTIMER, false);
-    esp_intr_free(s_timer_interrupt_handle);
-    s_timer_interrupt_handle = NULL;
+    for (unsigned i = 0; i < ISR_HANDLERS; i++) {
+        if (s_timer_interrupt_handle[i] != NULL) {
+            esp_intr_disable(s_timer_interrupt_handle[i]);
+            esp_intr_free(s_timer_interrupt_handle[i]);
+            s_timer_interrupt_handle[i] = NULL;
+        }
+    }
     s_alarm_handler = NULL;
 }
 

+ 40 - 1
components/esp_timer/test_apps/main/test_esp_timer.c

@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -1177,4 +1177,43 @@ TEST_CASE("Test ESP_TIMER_ISR, stop API cleans alarm reg if ISR timer list is em
     vSemaphoreDelete(done);
     printf("timer deleted\n");
 }
+
+#ifndef CONFIG_FREERTOS_UNICORE
+static void task_callback3(void* arg)
+{
+    int *data = (int *)arg;
+    ++*data;
+    esp_rom_printf("callback from CPU%d\n", xPortGetCoreID());
+#if defined(CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY) || defined(CONFIG_ESP_TIMER_ISR_AFFINITY_CPU1)
+    TEST_ASSERT_EQUAL_INT(1, xPortGetCoreID());
+#endif // CONFIG_ESP_TIMER_AFFINITY_NO_AFFINITY
+}
+
+TEST_CASE("Test that CPU1 can handle esp_timer ISR even when CPU0 is blocked", "[esp_timer][isr_dispatch]")
+{
+    int data = 0;
+
+    esp_timer_handle_t timer;
+    const esp_timer_create_args_t timer_args = {
+        .callback = &task_callback3,
+        .dispatch_method = ESP_TIMER_ISR,
+        .arg = &data,
+        .name = "test",
+    };
+    TEST_ESP_OK(esp_timer_create(&timer_args, &timer));
+    TEST_ESP_OK(esp_timer_start_periodic(timer, 10000));
+
+    portDISABLE_INTERRUPTS();
+    TEST_ASSERT_EQUAL_INT(0, xPortGetCoreID());
+    esp_rom_printf("CPU%d is blocked\n", xPortGetCoreID());
+    esp_rom_delay_us(100000);
+    esp_rom_printf("CPU%d is released\n", xPortGetCoreID());
+    portENABLE_INTERRUPTS();
+
+    TEST_ESP_OK(esp_timer_stop(timer));
+    TEST_ESP_OK(esp_timer_dump(stdout));
+    TEST_ASSERT_INT_WITHIN(3, 10, data);
+    TEST_ESP_OK(esp_timer_delete(timer));
+}
+#endif // not CONFIG_FREERTOS_UNICORE
 #endif // CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD

+ 4 - 0
components/esp_timer/test_apps/pytest_esp_timer_ut.py

@@ -10,6 +10,10 @@ CONFIGS = [
     pytest.param('single_core', marks=[pytest.mark.esp32]),
     pytest.param('freertos_compliance', marks=[pytest.mark.esp32]),
     pytest.param('isr_dispatch_esp32', marks=[pytest.mark.esp32]),
+    pytest.param('cpu1_esp32', marks=[pytest.mark.esp32]),
+    pytest.param('any_cpu_esp32', marks=[pytest.mark.esp32]),
+    pytest.param('cpu1_esp32s3', marks=[pytest.mark.esp32s3]),
+    pytest.param('any_cpu_esp32s3', marks=[pytest.mark.esp32s3]),
 ]
 
 

+ 5 - 0
components/esp_timer/test_apps/sdkconfig.ci.any_cpu_esp32

@@ -0,0 +1,5 @@
+CONFIG_IDF_TARGET="esp32"
+CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
+CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
+CONFIG_ESP_TIMER_TASK_AFFINITY_NO_AFFINITY=y
+CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY=y

+ 5 - 0
components/esp_timer/test_apps/sdkconfig.ci.any_cpu_esp32s3

@@ -0,0 +1,5 @@
+CONFIG_IDF_TARGET="esp32s3"
+CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
+CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
+CONFIG_ESP_TIMER_TASK_AFFINITY_NO_AFFINITY=y
+CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY=y

+ 5 - 0
components/esp_timer/test_apps/sdkconfig.ci.cpu1_esp32

@@ -0,0 +1,5 @@
+CONFIG_IDF_TARGET="esp32"
+CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
+CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
+CONFIG_ESP_TIMER_TASK_AFFINITY_CPU1=y
+CONFIG_ESP_TIMER_ISR_AFFINITY_CPU1=y

+ 5 - 0
components/esp_timer/test_apps/sdkconfig.ci.cpu1_esp32s3

@@ -0,0 +1,5 @@
+CONFIG_IDF_TARGET="esp32s3"
+CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
+CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
+CONFIG_ESP_TIMER_TASK_AFFINITY_CPU1=y
+CONFIG_ESP_TIMER_ISR_AFFINITY_CPU1=y