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 <ricardopaul@geotab.com>
This commit is contained in:
Ricardo Maurizio Paul 2026-10-05 14:44:04 +02:00 • committed by Alan C. Assis
parent 8374dc60bd
commit 7e3d6066df
3 changed files with 72 additions and 5 deletions

View file

@ -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)

View file

@ -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

View file

@ -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');