espressif: stop leaking a Wi-Fi interrupt handle on every esp_wifi_start()

set_intr_wrapper() allocates a new intr_handle_data_t from the kernel
heap each time the Wi-Fi driver calls it, and the driver calls it on
every esp_wifi_start() -- twice per start on esp32s3 -- not only the
first time.  clear_intr_wrapper() is a no-op, so the IRQ still holds the
handle from the previous start: esp_set_handle() refuses to replace it
with -EINVAL, the return value is ignored, and the new block is lost.

Any application that stops and restarts Wi-Fi to save power therefore
loses a few bytes of kernel heap per cycle, without bound.

Look up the vector descriptor first, then reuse the handle already
registered for the IRQ and only allocate and register one when there is
none.  A failed descriptor lookup no longer touches the registered
handle.

The same code is present in the esp32, esp32s2, esp32s3, esp32c3 and
esp32c6 Wi-Fi adapters; all five are fixed the same way.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Moura 2026-09-29 18:20:08 -03:00 • committed by Alan C. Assis
parent b38b03072a
commit 1c6ed642bf
5 changed files with 135 additions and 66 deletions

View file

@ -558,6 +558,7 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
uint32_t intr_num, int32_t intr_prio)
{
intr_handle_t handle;
vector_desc_t *desc;
int irq = ESP_SOURCE2IRQ(intr_source);
esp_err_t err;
@ -569,26 +570,40 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
esprv_int_set_priority(intr_num, intr_prio);
esprv_int_set_type(intr_num, INTR_TYPE_LEVEL);
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
handle->vector_desc = get_desc_for_int(intr_num, cpu_no);
if (handle->vector_desc == NULL)
desc = get_desc_for_int(intr_num, cpu_no);
if (desc == NULL)
{
wlerr("get_desc_for_int failed\n");
kmm_free(handle);
return;
}
/* The Wi-Fi driver calls this on every esp_wifi_start(), not only on
* the first one, and nothing clears the handle in between (see
* clear_intr_wrapper()). Reuse the registered handle: a new one would
* be refused by esp_set_handle() and leaked.
*/
handle = esp_get_handle(cpu_no, irq);
if (handle == IRQ_UNMAPPED)
{
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
/* Register the handle - it contains all needed information
* (cpuint, cpu)
*/
esp_set_handle(cpu_no, irq, handle);
}
handle->vector_desc = desc;
handle->vector_desc->source = intr_source;
handle->shared_vector_desc = NULL;
esp_set_handle(cpu_no, irq, handle);
err = esp_intr_set_in_iram(handle, false);
if (err != ESP_OK)
{

View file

@ -591,6 +591,7 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
uint32_t intr_num, int32_t intr_prio)
{
intr_handle_t handle;
vector_desc_t *desc;
int irq = ESP_SOURCE2IRQ(intr_source);
esp_err_t err;
@ -602,26 +603,40 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
esprv_int_set_priority(intr_num, intr_prio);
esprv_int_set_type(intr_num, INTR_TYPE_LEVEL);
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
handle->vector_desc = get_desc_for_int(intr_num, cpu_no);
if (handle->vector_desc == NULL)
desc = get_desc_for_int(intr_num, cpu_no);
if (desc == NULL)
{
wlerr("get_desc_for_int failed\n");
kmm_free(handle);
return;
}
/* The Wi-Fi driver calls this on every esp_wifi_start(), not only on
* the first one, and nothing clears the handle in between (see
* clear_intr_wrapper()). Reuse the registered handle: a new one would
* be refused by esp_set_handle() and leaked.
*/
handle = esp_get_handle(cpu_no, irq);
if (handle == IRQ_UNMAPPED)
{
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
/* Register the handle - it contains all needed information
* (cpuint, cpu)
*/
esp_set_handle(cpu_no, irq, handle);
}
handle->vector_desc = desc;
handle->vector_desc->source = intr_source;
handle->shared_vector_desc = NULL;
esp_set_handle(cpu_no, irq, handle);
err = esp_intr_set_in_iram(handle, false);
if (err != ESP_OK)
{

View file

@ -1765,6 +1765,7 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
uint32_t intr_num, int32_t intr_prio)
{
intr_handle_t handle;
vector_desc_t *desc;
int irq = ESP_SOURCE2IRQ(intr_source);
esp_err_t err;
@ -1774,28 +1775,40 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
esp_rom_route_intr_matrix(cpu_no, intr_source, intr_num);
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
handle->vector_desc = get_desc_for_int(intr_num, cpu_no);
if (handle->vector_desc == NULL)
desc = get_desc_for_int(intr_num, cpu_no);
if (desc == NULL)
{
wlerr("get_desc_for_int failed\n");
kmm_free(handle);
return;
}
/* The Wi-Fi driver calls this on every esp_wifi_start(), not only on
* the first one, and nothing clears the handle in between (see
* clear_intr_wrapper()). Reuse the registered handle: a new one would
* be refused by esp_set_handle() and leaked.
*/
handle = esp_get_handle(cpu_no, irq);
if (handle == IRQ_UNMAPPED)
{
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
/* Register the handle - it contains all needed information
* (cpuint, cpu)
*/
esp_set_handle(cpu_no, irq, handle);
}
handle->vector_desc = desc;
handle->vector_desc->source = intr_source;
handle->shared_vector_desc = NULL;
/* Register the handle - it contains all needed information (cpuint, cpu) */
esp_set_handle(cpu_no, irq, handle);
err = esp_intr_set_in_iram(handle, false);
if (err != OK)
{

View file

@ -1623,6 +1623,7 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
uint32_t intr_num, int32_t intr_prio)
{
intr_handle_t handle;
vector_desc_t *desc;
int irq = ESP_SOURCE2IRQ(intr_source);
esp_err_t err;
@ -1632,28 +1633,40 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
esp_rom_route_intr_matrix(cpu_no, intr_source, intr_num);
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
handle->vector_desc = get_desc_for_int(intr_num, cpu_no);
if (handle->vector_desc == NULL)
desc = get_desc_for_int(intr_num, cpu_no);
if (desc == NULL)
{
wlerr("get_desc_for_int failed\n");
kmm_free(handle);
return;
}
/* The Wi-Fi driver calls this on every esp_wifi_start(), not only on
* the first one, and nothing clears the handle in between (see
* clear_intr_wrapper()). Reuse the registered handle: a new one would
* be refused by esp_set_handle() and leaked.
*/
handle = esp_get_handle(cpu_no, irq);
if (handle == IRQ_UNMAPPED)
{
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
/* Register the handle - it contains all needed information
* (cpuint, cpu)
*/
esp_set_handle(cpu_no, irq, handle);
}
handle->vector_desc = desc;
handle->vector_desc->source = intr_source;
handle->shared_vector_desc = NULL;
/* Register the handle - it contains all needed information (cpuint, cpu) */
esp_set_handle(cpu_no, irq, handle);
err = esp_intr_set_in_iram(handle, false);
if (err != OK)
{

View file

@ -1755,6 +1755,7 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
uint32_t intr_num, int32_t intr_prio)
{
intr_handle_t handle;
vector_desc_t *desc;
int irq = ESP_SOURCE2IRQ(intr_source);
esp_err_t err;
@ -1764,28 +1765,40 @@ static void set_intr_wrapper(int32_t cpu_no, uint32_t intr_source,
esp_rom_route_intr_matrix(cpu_no, intr_source, intr_num);
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
handle->vector_desc = get_desc_for_int(intr_num, cpu_no);
if (handle->vector_desc == NULL)
desc = get_desc_for_int(intr_num, cpu_no);
if (desc == NULL)
{
wlerr("get_desc_for_int failed\n");
kmm_free(handle);
return;
}
/* The Wi-Fi driver calls this on every esp_wifi_start(), not only on
* the first one, and nothing clears the handle in between (see
* clear_intr_wrapper()). Reuse the registered handle: a new one would
* be refused by esp_set_handle() and leaked.
*/
handle = esp_get_handle(cpu_no, irq);
if (handle == IRQ_UNMAPPED)
{
handle = kmm_calloc(1, sizeof(intr_handle_data_t));
if (handle == NULL)
{
wlerr("Failed to kmm_calloc\n");
return;
}
/* Register the handle - it contains all needed information
* (cpuint, cpu)
*/
esp_set_handle(cpu_no, irq, handle);
}
handle->vector_desc = desc;
handle->vector_desc->source = intr_source;
handle->shared_vector_desc = NULL;
/* Register the handle - it contains all needed information (cpuint, cpu) */
esp_set_handle(cpu_no, irq, handle);
err = esp_intr_set_in_iram(handle, false);
if (err != OK)
{