From 7e3d6066df4e186fd18290bf6f390eebee3c97f9 Mon Sep 17 00:00:00 2001 From: Ricardo Maurizio Paul Date: Mon, 5 Oct 2026 14:44:04 +0200 Subject: [PATCH] arch/arm/stm32h5: start the ICACHE from a clean state and bound its waits. A bootloader may hand over with the ICACHE enabled, and nothing waits for the invalidate that runs when the ICACHE is disabled. The driver also polled BUSYF without a bound, so a stuck flag would hang the boot, and it ignored an invalidate that never finished. - stm32_enable_icache(): on first use, disable and invalidate the ICACHE before the associativity and region registers are written (they are only writable while EN=0), and wait for any pending invalidate before enabling it (RM0481 8.4.5). - stm32_disable_icache(): wait for the invalidate that EN=0 starts and clear BSYENDF and ERRF. - Bound every BUSYF wait with STM32_ICACHE_BUSY_TIMEOUT. RM0481 gives no invalidate duration, so the value is a margin, not a measured limit. - stm32_enable_icache() now returns OK or -ETIMEDOUT instead of void. On a timeout the ICACHE is left disabled: with BUSYF stuck it would not cache anything anyway (RM0481 8.4.5). Existing callers ignore the result and keep working. - __start: when CONFIG_STM32_ICACHE is not set, disable an ICACHE left on by a bootloader, so reads of the OTP and UID cannot fault. On an STM32H563 with the ICACHE left enabled this way, a 16-bit read of the UID raised a precise bus fault and up_progmem_write() failed its read-back check with -EIO. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ricardo Maurizio Paul --- arch/arm/src/stm32h5/stm32_icache.c | 45 ++++++++++++++++++++++++++++- arch/arm/src/stm32h5/stm32_icache.h | 18 +++++++++--- arch/arm/src/stm32h5/stm32_start.c | 14 +++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/arch/arm/src/stm32h5/stm32_icache.c b/arch/arm/src/stm32h5/stm32_icache.c index c6837213bbc..625c5dee787 100644 --- a/arch/arm/src/stm32h5/stm32_icache.c +++ b/arch/arm/src/stm32h5/stm32_icache.c @@ -248,6 +248,26 @@ static void stm32_icache_setup_region(struct stm32_icache_region region) } #endif +static bool stm32_icache_wait_invalidate(void) +{ + uint32_t timeout = STM32_ICACHE_BUSY_TIMEOUT; + + /* Wait for a running invalidate (after reset, CACHEINV or EN=0) to end; + * the ICACHE should not be enabled before (RM0481 8.4.5). Returns false + * on timeout. + */ + + while ((getreg32(STM32_ICACHE_SR) & ICACHE_SR_BUSYF) != 0) + { + if (--timeout == 0) + { + return false; + } + } + + return true; +} + static void stm32_icache_mpu_setup(void) { /* Non-cacheable, execute-never. stm32_mpuinitialize() enabled the MPU. */ @@ -362,6 +382,14 @@ void stm32_disable_icache(void) regval = getreg32(STM32_ICACHE_CR); regval &= ~(ICACHE_CR_EN); putreg32(regval, STM32_ICACHE_CR); + + /* Disabling the ICACHE starts a full invalidate, wait for it to finish. + * The ICACHE is disabled whether or not the wait times out, so there is + * nothing to report: stm32_enable_icache() waits again before enabling. + */ + + stm32_icache_wait_invalidate(); + putreg32(ICACHE_FCR_CBSYENDF | ICACHE_FCR_CERRF, STM32_ICACHE_FCR); } bool stm32_icache_enabled(void) @@ -369,21 +397,36 @@ bool stm32_icache_enabled(void) return (getreg32(STM32_ICACHE_CR) & ICACHE_CR_EN) != 0; } -void stm32_enable_icache(void) +int stm32_enable_icache(void) { uint32_t regval; if (icache1.initialized != true) { + /* A bootloader may have left the ICACHE enabled. Disable it, which + * also invalidates it: WAYSEL and the region registers can only be + * written while EN=0 (RM0481 8.4.4, 8.4.7). + */ + + stm32_disable_icache(); + stm32_icache_initialize(); icache1.initialized = true; } + /* If the invalidate times out, leave the ICACHE disabled */ + + if (!stm32_icache_wait_invalidate()) + { + return -ETIMEDOUT; + } + /* Enable the ICACHE */ regval = getreg32(STM32_ICACHE_CR); regval |= ICACHE_CR_EN; putreg32(regval, STM32_ICACHE_CR); + return OK; } void stm32_invalidate_icache(void) diff --git a/arch/arm/src/stm32h5/stm32_icache.h b/arch/arm/src/stm32h5/stm32_icache.h index 07b85969889..50824dae38c 100644 --- a/arch/arm/src/stm32h5/stm32_icache.h +++ b/arch/arm/src/stm32h5/stm32_icache.h @@ -38,6 +38,12 @@ * Pre-processor Definitions ****************************************************************************/ +/* Bound on the BUSYF polling loops. RM0481 gives no invalidate duration, so + * this is a margin, not a measured limit. + */ + +#define STM32_ICACHE_BUSY_TIMEOUT 100000 + #ifndef __ASSEMBLY__ #undef EXTERN @@ -89,23 +95,27 @@ size_t stm32_get_icache_size(void); * Name: stm32_enable_icache * * Description: - * Initializes the STM32H5 ICACHE + * Initializes (on first call) and enables the STM32H5 ICACHE. The first + * call disables and invalidates it first, in case a bootloader left it + * enabled, and maps the uncacheable flash areas with the MPU. * * Input Parameters: * None * * Returned Value: - * None + * OK, or -ETIMEDOUT if the invalidate did not complete; the ICACHE is + * then left disabled. * ****************************************************************************/ -void stm32_enable_icache(void); +int stm32_enable_icache(void); /**************************************************************************** * Name: stm32_disable_icache * * Description: - * Disables the STM32H5 ICACHE. + * Disables the STM32H5 ICACHE and waits, for a bounded time, for the + * invalidate that this starts. * * Input Parameters: * None diff --git a/arch/arm/src/stm32h5/stm32_start.c b/arch/arm/src/stm32h5/stm32_start.c index d5384b05172..403125ae88c 100644 --- a/arch/arm/src/stm32h5/stm32_start.c +++ b/arch/arm/src/stm32h5/stm32_start.c @@ -240,6 +240,20 @@ void __start(void) #ifdef CONFIG_STM32_ICACHE stm32_enable_icache(); +#else + /* Disable an ICACHE left enabled by a bootloader: the OTP and RO flash + * areas cannot be read through it (RM0481 7.3.2). + */ + + if ((getreg32(STM32_ICACHE_CR) & ICACHE_CR_EN) != 0) + { + uint32_t regval; + + regval = getreg32(STM32_ICACHE_CR); + regval &= ~(ICACHE_CR_EN); + putreg32(regval, STM32_ICACHE_CR); + } + #endif showprogress('G');