diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index 71f2bb2f7de70..4a1e171d81551 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -119,6 +119,10 @@ New APIs and options * :kconfig:option:`CONFIG_SDL_DISPLAY_DEFAULT_PIXEL_FORMAT_AL_88` * :kconfig:option:`CONFIG_SDL_DISPLAY_COLOR_TINT` +* Flash + + * :kconfig:option:`CONFIG_FLASH_STM32_ASYNC` + * Kernel * :kconfig:option:`CONFIG_HW_SHADOW_STACK` diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index f3eaeade2257e..ae4e5c4166a0c 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -17,6 +17,12 @@ config FLASH_HAS_EX_OP This option is selected by drivers that support flash extended operations. +config FLASH_HAS_ASYNC_OPS + bool + help + This option is selected by drivers that support asynchronous + flash operations. + config FLASH_HAS_EXPLICIT_ERASE bool help diff --git a/drivers/flash/Kconfig.stm32 b/drivers/flash/Kconfig.stm32 index caa28854331b7..e5f7114160ab3 100644 --- a/drivers/flash/Kconfig.stm32 +++ b/drivers/flash/Kconfig.stm32 @@ -25,6 +25,9 @@ menuconfig SOC_FLASH_STM32 select MPU_ALLOW_FLASH_WRITE if ARM_MPU select USE_STM32_HAL_FLASH if BT_STM32WBA select USE_STM32_HAL_FLASH_EX if BT_STM32WBA + select FLASH_HAS_ASYNC_OPS if SOC_SERIES_STM32L4X || \ + SOC_SERIES_STM32F4X || \ + SOC_SERIES_STM32H7X help Enable flash driver for STM32 series @@ -100,4 +103,13 @@ config USE_MICROCHIP_QSPI_FLASH_WITH_STM32 the Global Block Protection Unlock instruction (ULBPR - 98H), and write with SPI_NOR_CMD_PP_1_1_4 on 4 lines +config FLASH_STM32_ASYNC + bool "Use asynchronous flash operations" + depends on MULTITHREADING && FLASH_HAS_ASYNC_OPS + select USE_STM32_HAL_FLASH + select USE_STM32_HAL_FLASH_EX + help + Use asynchronous flash operations to unblock other threads while + flash is busy. + endif # SOC_FLASH_STM32 diff --git a/drivers/flash/flash_stm32.c b/drivers/flash/flash_stm32.c index 94fb936331870..2b38f254179bd 100644 --- a/drivers/flash/flash_stm32.c +++ b/drivers/flash/flash_stm32.c @@ -161,8 +161,16 @@ static int flash_stm32_read(const struct device *dev, off_t offset, LOG_DBG("Read offset: %ld, len: %zu", (long int) offset, len); + if (IS_ENABLED(CONFIG_FLASH_STM32_ASYNC)) { + flash_stm32_sem_take(dev); + } + memcpy(data, (uint8_t *) FLASH_STM32_BASE_ADDRESS + offset, len); + if (IS_ENABLED(CONFIG_FLASH_STM32_ASYNC)) { + flash_stm32_sem_give(dev); + } + return 0; } @@ -416,6 +424,36 @@ static DEVICE_API(flash, flash_stm32_api) = { #endif }; +#if defined(CONFIG_FLASH_STM32_ASYNC) +/* STM32 HAL functions do not permit pass a cookie to the interrupt callback functions and therefore + * only support a single flash and require a static pointer to the flash device. + */ +static struct flash_stm32_priv *flash_dev; + +/* IRQ handler function for async flash mode */ +void flash_stm32_irq_handler(void) +{ + HAL_FLASH_IRQHandler(); + if (flash_dev->async_complete || flash_dev->async_error) { + k_sem_give(&flash_dev->async_sem); + } +} + +/* STM32 HAL function called by HAL_FLASH_IRQHandler() when a flash op completes successfully */ +void HAL_FLASH_EndOfOperationCallback(uint32_t op_ret_val) +{ + flash_dev->async_complete = true; + flash_dev->async_ret = op_ret_val; +} + +/* STM32 HAL function called by HAL_FLASH_IRQHandler() when a flash op completes with an error */ +void HAL_FLASH_OperationErrorCallback(uint32_t op_ret_val) +{ + flash_dev->async_error = true; + flash_dev->async_ret = op_ret_val; +} +#endif /* CONFIG_FLASH_STM32_ASYNC */ + static int stm32_flash_init(const struct device *dev) { int rc; @@ -458,6 +496,13 @@ static int stm32_flash_init(const struct device *dev) flash_stm32_sem_init(dev); +#if defined(CONFIG_FLASH_STM32_ASYNC) + flash_dev = FLASH_STM32_PRIV(dev); + flash_stm32_async_sem_init(dev); + IRQ_CONNECT(FLASH_IRQn, 0, flash_stm32_irq_handler, NULL, 0); + irq_enable(FLASH_IRQn); +#endif /* CONFIG_FLASH_STM32_ASYNC */ + LOG_DBG("Flash @0x%x initialized. BS: %zu", FLASH_STM32_BASE_ADDRESS, flash_stm32_parameters.write_block_size); diff --git a/drivers/flash/flash_stm32.h b/drivers/flash/flash_stm32.h index 8d724e2ccb8fc..206cb42a81a64 100644 --- a/drivers/flash/flash_stm32.h +++ b/drivers/flash/flash_stm32.h @@ -29,6 +29,12 @@ struct flash_stm32_priv { struct stm32_pclken pclken; #endif struct k_sem sem; +#if defined(CONFIG_FLASH_STM32_ASYNC) + struct k_sem async_sem; + bool async_complete; + bool async_error; + uint32_t async_ret; +#endif /* CONFIG_FLASH_STM32_ASYNC */ }; #if DT_PROP(DT_INST(0, soc_nv_flash), write_block_size) @@ -310,6 +316,9 @@ static inline void _flash_stm32_sem_give(const struct device *dev) #define flash_stm32_sem_init(dev) k_sem_init(&FLASH_STM32_PRIV(dev)->sem, 1, 1) #define flash_stm32_sem_take(dev) _flash_stm32_sem_take(dev) #define flash_stm32_sem_give(dev) _flash_stm32_sem_give(dev) +#if defined(CONFIG_FLASH_STM32_ASYNC) +#define flash_stm32_async_sem_init(dev) k_sem_init(&FLASH_STM32_PRIV(dev)->async_sem, 0, 1) +#endif /* CONFIG_FLASH_STM32_ASYNC */ #else #define flash_stm32_sem_init(dev) #define flash_stm32_sem_take(dev) diff --git a/drivers/flash/flash_stm32f4x.c b/drivers/flash/flash_stm32f4x.c index c828da49363ce..948b622c067af 100644 --- a/drivers/flash/flash_stm32f4x.c +++ b/drivers/flash/flash_stm32f4x.c @@ -36,6 +36,20 @@ typedef uint8_t flash_prg_t; #error Write block size must be a power of 2, from 1 to 8 #endif +#if defined(CONFIG_FLASH_STM32_ASYNC) +#if FLASH_STM32_WRITE_BLOCK_SIZE == 8 +#define FLASH_TYPEPROGRAM_SIZE FLASH_TYPEPROGRAM_DOUBLEWORD +#elif FLASH_STM32_WRITE_BLOCK_SIZE == 4 +#define FLASH_TYPEPROGRAM_SIZE FLASH_TYPEPROGRAM_WORD +#elif FLASH_STM32_WRITE_BLOCK_SIZE == 2 +#define FLASH_TYPEPROGRAM_SIZE FLASH_TYPEPROGRAM_HALFWORD +#elif FLASH_STM32_WRITE_BLOCK_SIZE == 1 +#define FLASH_TYPEPROGRAM_SIZE FLASH_TYPEPROGRAM_BYTE +#else +#error Write block size must be a power of 2, from 1 to 8 +#endif +#endif /* CONFIG_FLASH_STM32_ASYNC */ + bool flash_stm32_valid_range(const struct device *dev, off_t offset, uint32_t len, bool write) @@ -82,6 +96,27 @@ static inline void flush_cache(FLASH_TypeDef *regs) static int write_value(const struct device *dev, off_t offset, flash_prg_t val) { +#if defined(CONFIG_FLASH_STM32_ASYNC) + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + + HAL_FLASH_Program_IT(FLASH_TYPEPROGRAM_SIZE, offset + FLASH_STM32_BASE_ADDRESS, val); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash write successful. Wrote 0x%x at 0x%lx", val, + offset + FLASH_STM32_BASE_ADDRESS); + return 0; + } + + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash write failed at 0x%x", FLASH_STM32_PRIV(dev)->async_ret); + return -EIO; + } + + /* Should never be reached */ + return -EFAULT; +#else /* CONFIG_FLASH_STM32_ASYNC */ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); #if defined(FLASH_OPTCR_DB1M) bool dcache_enabled = false; @@ -132,10 +167,40 @@ static int write_value(const struct device *dev, off_t offset, flash_prg_t val) #endif /* FLASH_OPTCR_DB1M */ return rc; +#endif /* CONFIG_FLASH_STM32_ASYNC */ } static int erase_sector(const struct device *dev, uint32_t sector) { +#if defined(CONFIG_FLASH_STM32_ASYNC) + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + + FLASH_EraseInitTypeDef erase_init = { + .TypeErase = FLASH_TYPEERASE_SECTORS, + .Banks = 1, /* dual bank flash not supported */ + .Sector = sector, + .NbSectors = 1, + .VoltageRange = FLASH_VOLTAGE_RANGE_4, + }; + + HAL_FLASHEx_Erase_IT(&erase_init); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash erase successful. Erased sector %d at 0x%x", sector, + FLASH_STM32_PRIV(dev)->async_ret); + return = 0; + } + + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash erase failed at 0x%x", FLASH_STM32_PRIV(dev)->async_ret); + return = -EIO; + } + + /* Should never be reached */ + return -EFAULT; +#else /* CONFIG_FLASH_STM32_ASYNC */ + FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); uint32_t tmp; int rc; @@ -182,6 +247,7 @@ static int erase_sector(const struct device *dev, uint32_t sector) regs->CR &= ~(FLASH_CR_SER | FLASH_CR_SNB); return rc; +#endif /* CONFIG_FLASH_STM32_ASYNC */ } int flash_stm32_block_erase_loop(const struct device *dev, diff --git a/drivers/flash/flash_stm32h7x.c b/drivers/flash/flash_stm32h7x.c index c30d685d55c35..80abc173cc6bc 100644 --- a/drivers/flash/flash_stm32h7x.c +++ b/drivers/flash/flash_stm32h7x.c @@ -512,9 +512,39 @@ static struct flash_stm32_sector_t get_sector(const struct device *dev, off_t of static int erase_sector(const struct device *dev, int offset) { - int rc; struct flash_stm32_sector_t sector = get_sector(dev, offset); +#if defined(CONFIG_FLASH_STM32_ASYNC) + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + + FLASH_EraseInitTypeDef erase_init = { + .TypeErase = FLASH_TYPEERASE_SECTORS, + .Banks = sector.bank, + .Sector = sector.sector_index, + .NbSectors = 1, + .VoltageRange = FLASH_VOLTAGE_RANGE_4, + }; + + HAL_FLASHEx_Erase_IT(&erase_init); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash erase successful. Erased %lu bytes at 0x%x", FLASH_SECTOR_SIZE, + FLASH_STM32_PRIV(dev)->async_ret); + return 0; + } + + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash erase failed at 0x%x", FLASH_STM32_PRIV(dev)->async_ret); + return -EIO; + } + + /* Should never be reached */ + return -EFAULT; +#else /* CONFIG_FLASH_STM32_ASYNC */ + + int rc; + if (sector.bank == 0) { LOG_ERR("Offset %ld does not exist", (long)offset); @@ -541,6 +571,7 @@ static int erase_sector(const struct device *dev, int offset) *(sector.cr) &= ~(FLASH_CR_SER | FLASH_CR_SNB); return rc; +#endif /* CONFIG_FLASH_STM32_ASYNC */ } int flash_stm32_block_erase_loop(const struct device *dev, unsigned int offset, unsigned int len) @@ -557,6 +588,7 @@ int flash_stm32_block_erase_loop(const struct device *dev, unsigned int offset, return rc; } +#if !defined(CONFIG_FLASH_STM32_ASYNC) static int wait_write_queue(const struct flash_stm32_sector_t *sector) { int64_t timeout_time = k_uptime_get() + 100; @@ -570,11 +602,35 @@ static int wait_write_queue(const struct flash_stm32_sector_t *sector) return 0; } +#endif /* !CONFIG_FLASH_STM32_ASYNC */ static int write_ndwords(const struct device *dev, off_t offset, const uint64_t *data, uint8_t n) { + int rc = 0; + +#if defined(CONFIG_FLASH_STM32_ASYNC) + for (size_t i = 0; i < n; i += FLASH_NB_32BITWORD_IN_FLASHWORD) { + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + uint32_t wordAddr = (uint32_t)&data[i]; + + HAL_FLASH_Program_IT(FLASH_TYPEPROGRAM_FLASHWORD, offset + FLASH_STM32_BASE_ADDRESS, + wordAddr); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash write successful. Wrote %u bytes to 0x%x", + FLASH_STM32_WRITE_BLOCK_SIZE, FLASH_STM32_PRIV(dev)->async_ret); + } else { + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash write failed at 0x%x", + FLASH_STM32_PRIV(dev)->async_ret); + } + return -EIO; + } + } +#else /* CONFIG_FLASH_STM32_ASYNC */ + volatile uint64_t *flash = (uint64_t *)(offset + FLASH_STM32_BASE_ADDRESS); - int rc; int i; struct flash_stm32_sector_t sector = get_sector(dev, offset); @@ -623,6 +679,7 @@ static int write_ndwords(const struct device *dev, off_t offset, const uint64_t /* Clear the PG bit */ *(sector.cr) &= (~FLASH_CR_PG); +#endif /* CONFIG_FLASH_STM32_ASYNC */ return rc; } @@ -812,6 +869,10 @@ static int flash_stm32h7_read(const struct device *dev, off_t offset, void *data LOG_DBG("Read offset: %ld, len: %zu", (long)offset, len); + if (IS_ENABLED(CONFIG_FLASH_STM32_ASYNC)) { + flash_stm32_sem_take(dev); + } + /* During the read we mask bus errors and only allow NMI. * * If the flash has a double ECC error then there is normally @@ -832,7 +893,13 @@ static int flash_stm32h7_read(const struct device *dev, off_t offset, void *data barrier_isync_fence_full(); irq_unlock(irq_lock_key); - return flash_stm32_check_status(dev); + int rc = flash_stm32_check_status(dev); + + if (IS_ENABLED(CONFIG_FLASH_STM32_ASYNC)) { + flash_stm32_sem_give(dev); + } + + return rc; } static const struct flash_parameters flash_stm32h7_parameters = { @@ -927,6 +994,36 @@ static DEVICE_API(flash, flash_stm32h7_api) = { #endif }; +#if defined(CONFIG_FLASH_STM32_ASYNC) +/* STM32 HAL functions do not permit pass a cookie to the interrupt callback functions and therefore + * only support a single flash and require a static pointer to the flash device. + */ +static struct flash_stm32_priv *flash_dev; + +/* IRQ handler function for async flash mode */ +void flash_stm32_irq_handler(void) +{ + HAL_FLASH_IRQHandler(); + if (flash_dev->async_complete || flash_dev->async_error) { + k_sem_give(&flash_dev->async_sem); + } +} + +/* STM32 HAL function called by HAL_FLASH_IRQHandler() when a flash op completes successfully */ +void HAL_FLASH_EndOfOperationCallback(uint32_t op_ret_val) +{ + flash_dev->async_complete = true; + flash_dev->async_ret = op_ret_val; +} + +/* STM32 HAL function called by HAL_FLASH_IRQHandler() when a flash op completes with an error */ +void HAL_FLASH_OperationErrorCallback(uint32_t op_ret_val) +{ + flash_dev->async_error = true; + flash_dev->async_ret = op_ret_val; +} +#endif /* CONFIG_FLASH_STM32_ASYNC */ + static int stm32h7_flash_init(const struct device *dev) { #if DT_NODE_HAS_PROP(DT_INST(0, st_stm32h7_flash_controller), clocks) @@ -947,6 +1044,13 @@ static int stm32h7_flash_init(const struct device *dev) #endif flash_stm32_sem_init(dev); +#if defined(CONFIG_FLASH_STM32_ASYNC) + flash_dev = FLASH_STM32_PRIV(dev); + flash_stm32_async_sem_init(dev); + IRQ_CONNECT(FLASH_IRQn, 0, flash_stm32_irq_handler, NULL, 0); + irq_enable(FLASH_IRQn); +#endif /* CONFIG_FLASH_STM32_ASYNC */ + LOG_DBG("Flash initialized. BS: %zu", flash_stm32h7_parameters.write_block_size); #if ((CONFIG_FLASH_LOG_LEVEL >= LOG_LEVEL_DBG) && CONFIG_FLASH_PAGE_LAYOUT) diff --git a/drivers/flash/flash_stm32l4x.c b/drivers/flash/flash_stm32l4x.c index 91f496d81d98c..58e4fcfb0ae60 100644 --- a/drivers/flash/flash_stm32l4x.c +++ b/drivers/flash/flash_stm32l4x.c @@ -69,6 +69,27 @@ static unsigned int get_page(off_t offset) static int write_dword(const struct device *dev, off_t offset, uint64_t val) { +#if defined(CONFIG_FLASH_STM32_ASYNC) + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + + HAL_FLASH_Program_IT(FLASH_TYPEPROGRAM_DOUBLEWORD, offset + FLASH_STM32_BASE_ADDRESS, val); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash write successful. Wrote 0x%llx at 0x%lx", val, + offset + FLASH_STM32_BASE_ADDRESS); + return 0; + } + + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash write failed at 0x%x", FLASH_STM32_PRIV(dev)->async_ret); + return -EIO; + } + + /* Should never be reached */ + return -EFAULT; +#else /* CONFIG_FLASH_STM32_ASYNC */ + volatile uint32_t *flash = (uint32_t *)(offset + FLASH_STM32_BASE_ADDRESS); FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); #ifdef CONTROL_DCACHE @@ -136,6 +157,7 @@ static int write_dword(const struct device *dev, off_t offset, uint64_t val) #endif /* CONTROL_DCACHE */ return rc; +#endif /* !CONFIG_FLASH_STM32_ASYNC */ } #define SOC_NV_FLASH_SIZE DT_REG_SIZE(DT_INST(0, soc_nv_flash)) @@ -145,7 +167,6 @@ static int erase_page(const struct device *dev, unsigned int page) FLASH_TypeDef *regs = FLASH_STM32_REGS(dev); uint32_t tmp; uint16_t pages_per_bank; - int rc; #if !defined(FLASH_OPTR_DUALBANK) && !defined(FLASH_STM32_DBANK) /* Single bank device. Each page is of 2KB size */ @@ -174,6 +195,42 @@ static int erase_page(const struct device *dev, unsigned int page) } #endif +#if defined(CONFIG_FLASH_STM32_ASYNC) + FLASH_STM32_PRIV(dev)->async_complete = false; + FLASH_STM32_PRIV(dev)->async_error = false; + + FLASH_EraseInitTypeDef erase_init = { + .TypeErase = FLASH_TYPEERASE_PAGES, +#if defined(FLASH_OPTR_DUALBANK) || defined(FLASH_STM32_DBANK) + .Banks = page >= pages_per_bank ? FLASH_BANK_2 : FLASH_BANK_1, + .Page = page % pages_per_bank, +#else + .Banks = FLASH_BANK_1, + .Page = page, +#endif + .NbPages = 1, + }; + + HAL_FLASHEx_Erase_IT(&erase_init); + k_sem_take(&FLASH_STM32_PRIV(dev)->async_sem, K_FOREVER); + if (FLASH_STM32_PRIV(dev)->async_complete) { + LOG_DBG("Flash erase successful. Erased %d bytes at 0x%x", FLASH_PAGE_SIZE, + FLASH_STM32_PRIV(dev)->async_ret); + return 0; + } + + if (FLASH_STM32_PRIV(dev)->async_error) { + LOG_ERR("Flash erase failed at 0x%x", FLASH_STM32_PRIV(dev)->async_ret); + return -EIO; + } + + /* Should never be reached */ + return -EFAULT; +#else /* CONFIG_FLASH_STM32_ASYNC */ + + int rc; + uint32_t tmp; + /* if the control register is locked, do not fail silently */ if (regs->CR & FLASH_CR_LOCK) { return -EIO; @@ -211,6 +268,7 @@ static int erase_page(const struct device *dev, unsigned int page) regs->CR &= ~FLASH_CR_PER; return rc; +#endif /* !CONFIG_FLASH_STM32_ASYNC */ } int flash_stm32_block_erase_loop(const struct device *dev,