lv_st_ltdc: clean_dcache() hard-faults on Cortex-M7 when D-Cache is disabled (bootloader use case)
LVGL version
v9.6.0
Platform
Custom STM32H7-based board (Cortex-M7), bare-metal (no RTOS), GCC + CMake, built-in st_ltdc driver enabled (LV_USE_ST_LTDC 1).
Context: a second-stage bootloader uses LVGL to draw its splash screen. At this boot stage the D-cache is intentionally not enabled — there is no SCB_EnableDCache() call anywhere in the bootloader.
What happened?
After upgrading our vendored LVGL tree from v9.4 to v9.6.0 (no other display-path changes), the bootloader began hard-faulting on the first frame flush, rebooting in a loop on every cold power-up. The same code ran fine on v9.4.
Fault evidence
- HardFault with
CFSR = 0x400(PRECISERR). - The faulting PC sits inside the set/way loop of
SCB_CleanDCache(), reached fromclean_dcache()insrc/drivers/display/st_ltdc/lv_st_ltdc.c. (We recovered the true PC from a shadow stack copied to a.noinitregion at crash time, cross-checked with breadcrumb instrumentation — the raw stacked PC initially pointed at an unrelated frame-buffer address because the C fault wrapper pushes registers before the frame is captured.) - Behavior was not fully deterministic: every cold boot crashed, while some warm resets appeared to recover — consistent with UNPREDICTABLE rather than a guaranteed fault.
Root cause
clean_dcache() assumes the D-cache is enabled whenever the core is a Cortex-M7:
#if defined(__CORTEX_M) && __CORTEX_M == 7
SCB_CleanDCache();
#elif defined(__CORTEX_A) && __CORTEX_A == 7
L1C_CleanDCacheAll();
#endifOn targets that never call SCB_EnableDCache() (bootloaders and splash screens being the obvious case), the ARMv7-M Architecture Reference Manual marks cache maintenance operations executed while the cache is disabled as UNPREDICTABLE — on this MCU the DCCSW set/way loop takes a precise bus fault mid-loop. __DCACHE_PRESENT cannot guard this: it is a compile-time "this core implements a cache" constant, set to 1 by the vendor headers regardless of the runtime configuration.
Expected behavior
The in-tree driver should not assume the D-cache is enabled just because the core is an M7. With the cache disabled there are no dirty lines to clean, so skipping the maintenance is semantically a no-op and exactly matches the v9.4 behavior.
Note that this call was added to fix #8963 (stale LTDC reads with the cache enabled, PR #9192). The runtime gate below keeps that fix fully intact for cache-on users and only restores v9.4 behavior when the cache is off.
Suggested fix — one line; we carry it locally as a vendored-tree patch (5/5 cold power-on cycles clean since; previously every cold boot crashed):
static void clean_dcache(void)
{
#if defined(__CORTEX_M) && __CORTEX_M == 7
if((SCB->CCR & SCB_CCR_DC_Msk) == 0) return; /* cache off: no dirty lines to clean, and maintenance is UNPREDICTABLE */
SCB_CleanDCache();
#elif defined(__CORTEX_A) && __CORTEX_A == 7
L1C_CleanDCacheAll();
#endif
}Happy to send a PR with this change if you agree with the approach.
Corroborating reports of the same CPU-level behavior (vendor community):
- Hard fault calling SCB_DisableDCache() or SCB_CleanDCache()
- D-cache invalidation call causes Hard Fault when D-cache is disabled
How to reproduce?
Any Cortex-M7 target using the built-in st_ltdc driver with the D-cache left disabled:
- Enable the driver:
LV_USE_ST_LTDC 1. - Do not enable the D-cache (no
SCB_EnableDCache(), i.e.SCB->CCR.DC = 0) — e.g. a bootloader that has not brought up cache/MPU yet. lv_init(), create the LTDC display, render the first frame.- First flush →
clean_dcache()→SCB_CleanDCache()→ HardFault (CFSR = 0x400).
Observed on STM32H7: every cold power-up reboots in a loop; the identical application code with the v9.4 tree runs fine, since v9.4 has no cache maintenance call in this driver.
Source: lvgl/lvgl