drivers: flash: Fix timeout handling in STM32 flash driver

Fix timeout error that can occur in rare case.
When the thread writing to flash is pre-emptive it can be scheduled
out after reading the status register, but before checking if timeout
has expired. In this case it will report timeout without re-checking
the status register.

When writing a lot to flash, for example a firmware update process
then this situation is very likely to occur.

Signed-off-by: Joakim Andersson <joerchan@gmail.com>
This commit is contained in:
Joakim Andersson 2025-06-17 11:47:42 +02:00 committed by Benjamin Cabé
commit e5093f41b2
2 changed files with 20 additions and 6 deletions

View file

@ -81,7 +81,8 @@ static int flash_stm32_check_status(const struct device *dev)
int flash_stm32_wait_flash_idle(const struct device *dev)
{
int64_t timeout_time = k_uptime_get() + STM32_FLASH_TIMEOUT;
k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(STM32_FLASH_TIMEOUT));
bool expired = false;
int rc;
uint32_t busy_flags;
@ -98,10 +99,16 @@ int flash_stm32_wait_flash_idle(const struct device *dev)
#endif
while ((FLASH_STM32_REGS(dev)->FLASH_STM32_SR & busy_flags)) {
if (k_uptime_get() > timeout_time) {
LOG_ERR("Timeout! val: %d", STM32_FLASH_TIMEOUT);
if (expired) {
LOG_ERR("Timeout! val: %d ms", STM32_FLASH_TIMEOUT);
return -EIO;
}
/* Check if expired, but always read status register one more time.
* If the calling thread is pre-emptive we may have been scheduled out after reading
* the status register, and scheduled back after timeout has expired.
*/
expired = sys_timepoint_expired(timeout);
}
return 0;

View file

@ -427,7 +427,8 @@ static int flash_stm32_check_status(const struct device *dev)
int flash_stm32_wait_flash_idle(const struct device *dev)
{
int64_t timeout_time = k_uptime_get() + STM32H7_FLASH_TIMEOUT;
k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(STM32H7_FLASH_TIMEOUT));
bool expired = false;
int rc;
rc = flash_stm32_check_status(dev);
@ -441,10 +442,16 @@ int flash_stm32_wait_flash_idle(const struct device *dev)
while (FLASH_STM32_REGS(dev)->SR1 & FLASH_SR_QW)
#endif
{
if (k_uptime_get() > timeout_time) {
LOG_ERR("Timeout! val: %d", STM32H7_FLASH_TIMEOUT);
if (expired) {
LOG_ERR("Timeout! val: %d ms", STM32H7_FLASH_TIMEOUT);
return -EIO;
}
/* Check if expired, but always read status register one more time.
* If the calling thread is pre-emptive we may have been scheduled out after reading
* the status register, and scheduled back after timeout has expired.
*/
expired = sys_timepoint_expired(timeout);
}
return 0;