From a428e8e5ed932c95f9250351d5105ff93b3a9fcd Mon Sep 17 00:00:00 2001 From: Matias Gibbons Date: Tue, 10 Mar 2026 09:34:56 -0300 Subject: [PATCH 1/3] fix(esp_lvgl_port): support CONFIG_LCD_RGB_ISR_IRAM_SAFE for RGB panels When CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled on ESP32-S3, the VSYNC/bounce buffer callback must be placed in IRAM. The current implementation calls lv_display_get_driver_data() from the callback, which resides in flash. When SPI flash operations disable cache, this causes a LoadProhibited crash. Fix: when CONFIG_LCD_RGB_ISR_IRAM_SAFE is set, pass the disp_ctx struct directly as user_ctx instead of the lv_display_t pointer. This avoids any flash function calls from the ISR context. Without CONFIG_LCD_RGB_ISR_IRAM_SAFE the behavior is unchanged. --- .../src/lvgl9/esp_lvgl_port_disp.c | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index 726e4f945..488ee8980 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -82,9 +82,14 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); #if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE +static IRAM_ATTR bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); +#else static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); #endif +#endif #if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); @@ -195,10 +200,18 @@ lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, #endif }; + /* When LCD_RGB_ISR_IRAM_SAFE is enabled, the callback must be in IRAM and + * cannot call lv_display_get_driver_data() (which resides in flash). + * Pass disp_ctx directly as user_ctx to avoid flash access from ISR. */ +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE + void *cb_user_ctx = disp_ctx; +#else + void *cb_user_ctx = disp_ctx->disp_drv; +#endif if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) { - ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, disp_ctx->disp_drv)); + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, cb_user_ctx)); } else { - ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, disp_ctx->disp_drv)); + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, cb_user_ctx)); } #else ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!"); @@ -509,6 +522,25 @@ static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t pane #endif #if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE +/* When LCD_RGB_ISR_IRAM_SAFE is enabled, this callback runs from IRAM. + * user_ctx is lvgl_port_display_ctx_t* to avoid calling lv_display_get_driver_data() + * which resides in flash and would crash when cache is disabled (e.g. during SPI flash ops). */ +static IRAM_ATTR bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t need_yield = pdFALSE; + + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)user_ctx; + assert(disp_ctx != NULL); + + if (disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); + } + + return (need_yield == pdTRUE); +} +#else static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) { @@ -525,6 +557,7 @@ static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t pane return (need_yield == pdTRUE); } +#endif /* CONFIG_LCD_RGB_ISR_IRAM_SAFE */ #endif #endif From 2bdec63e59c9fdc762b002164fd092d73cac6917 Mon Sep 17 00:00:00 2001 From: Matias Gibbons Date: Tue, 10 Mar 2026 09:49:36 -0300 Subject: [PATCH 2/3] fix: address review feedback for IRAM-safe callback - Replace assert() with null-check + early return in IRAM callback (assert references flash strings, defeating IRAM safety) - Allocate disp_ctx from internal RAM (MALLOC_CAP_INTERNAL) when CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled, ensuring the struct remains accessible when cache is disabled during SPI flash ops --- .../esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index 488ee8980..4ea97d18e 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -313,7 +313,14 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp } /* Display context */ +#if CONFIG_LCD_RGB_ISR_IRAM_SAFE + /* When ISR IRAM safety is enabled, the display context is passed directly + * to the ISR callback as user_ctx. It must reside in internal RAM so it + * remains accessible when the cache is disabled during SPI flash operations. */ + lvgl_port_display_ctx_t *disp_ctx = heap_caps_malloc(sizeof(lvgl_port_display_ctx_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); +#else lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); +#endif ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t)); disp_ctx->io_handle = disp_cfg->io_handle; @@ -532,7 +539,9 @@ static IRAM_ATTR bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_han BaseType_t need_yield = pdFALSE; lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)user_ctx; - assert(disp_ctx != NULL); + if (disp_ctx == NULL) { + return false; + } if (disp_ctx->trans_sem) { xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); From 432f11d43e0a3d1d8cb87668704c022c93f23fee Mon Sep 17 00:00:00 2001 From: Matias Gibbons Date: Wed, 8 Apr 2026 14:55:53 -0300 Subject: [PATCH 3/3] fix(esp_lvgl_port): replace malloc with heap_caps_malloc for pre-commit compliance Use heap_caps_malloc with MALLOC_CAP_DEFAULT in the non-IRAM-safe path instead of plain malloc(). Functionally equivalent but satisfies the ESP-IDF pre-commit hook that forbids bare malloc() calls. --- components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index 4ea97d18e..b7cab5138 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -319,7 +319,7 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp * remains accessible when the cache is disabled during SPI flash operations. */ lvgl_port_display_ctx_t *disp_ctx = heap_caps_malloc(sizeof(lvgl_port_display_ctx_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); #else - lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); + lvgl_port_display_ctx_t *disp_ctx = heap_caps_malloc(sizeof(lvgl_port_display_ctx_t), MALLOC_CAP_DEFAULT); #endif ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t));