From 24b4ce189fe0d3ab4b671baab7fa34bb0a751ec4 Mon Sep 17 00:00:00 2001 From: Henrik Lindblom Date: Tue, 29 Apr 2025 14:00:24 +0300 Subject: [PATCH] drivers: stm32: dma: fix external dcache support Several drivers checked for the presense and availability of data cache through Kconfig symbol. This is supported according to the current documentation, but the symbol DCACHE masks two types of cache devices: arch and external caches. The latter is present on some Cortex-M33 chips, like the STM32U5xx. The external dcache is bypassed when accessing internal SRAM and only used for external memories. In commit a2dd232410a8 ("drivers: adc: stm32: dma support") the rationale for gating dcache for adc_stm32 behind STM32H7X is only hinted at, but reason seems to be that it was the only SOC the change was tested on. The SOC configures DCACHE=y so it is most likely safe to swap the SOC gate for DCACHE. The DCACHE ifdefs are now hidden inside the shared stm32_buf_in_nocache() implementation. Signed-off-by: Henrik Lindblom --- drivers/adc/adc_stm32.c | 39 +++------------------ drivers/i2c/i2c_ll_stm32_v2.c | 44 ++++-------------------- drivers/serial/uart_stm32.c | 41 +++------------------- drivers/spi/spi_ll_stm32.c | 25 ++------------ soc/st/stm32/common/CMakeLists.txt | 2 ++ soc/st/stm32/common/stm32_cache.c | 55 ++++++++++++++++++++++++++++++ soc/st/stm32/common/stm32_cache.h | 39 +++++++++++++++++++++ 7 files changed, 113 insertions(+), 132 deletions(-) create mode 100644 soc/st/stm32/common/stm32_cache.c create mode 100644 soc/st/stm32/common/stm32_cache.h diff --git a/drivers/adc/adc_stm32.c b/drivers/adc/adc_stm32.c index 34d871509ba..9e2e6464072 100644 --- a/drivers/adc/adc_stm32.c +++ b/drivers/adc/adc_stm32.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -268,36 +269,6 @@ static int adc_stm32_dma_start(const struct device *dev, } #endif /* CONFIG_ADC_STM32_DMA */ -#if defined(CONFIG_ADC_STM32_DMA) && defined(CONFIG_SOC_SERIES_STM32H7X) -/* Returns true if given buffer is in a non-cacheable SRAM region. - * This is determined using the device tree, meaning the .nocache region won't work. - * The entire buffer must be in a single region. - * An example of how the SRAM region can be defined in the DTS: - * &sram4 { - * zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE) | ... )>; - * }; - */ -static bool buf_in_nocache(uintptr_t buf __maybe_unused, size_t len_bytes __maybe_unused) -{ - bool buf_within_nocache = false; - -#ifdef CONFIG_NOCACHE_MEMORY - buf_within_nocache = (buf >= ((uintptr_t)_nocache_ram_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)_nocache_ram_end)); - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_NOCACHE_MEMORY */ - -#ifdef CONFIG_MEM_ATTR - buf_within_nocache = mem_attr_check_buf( - (void *)buf, len_bytes, DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE)) == 0; -#endif /* CONFIG_MEM_ATTR */ - - return buf_within_nocache; -} -#endif /* defined(CONFIG_ADC_STM32_DMA) && defined(CONFIG_SOC_SERIES_STM32H7X) */ - static int check_buffer(const struct adc_sequence *sequence, uint8_t active_channels) { @@ -315,13 +286,13 @@ static int check_buffer(const struct adc_sequence *sequence, return -ENOMEM; } -#if defined(CONFIG_ADC_STM32_DMA) && defined(CONFIG_SOC_SERIES_STM32H7X) +#if defined(CONFIG_ADC_STM32_DMA) /* Buffer is forced to be in non-cacheable SRAM region to avoid cache maintenance */ - if (!buf_in_nocache((uintptr_t)sequence->buffer, needed_buffer_size)) { - LOG_ERR("Supplied buffer is not in a non-cacheable region according to DTS."); + if (!stm32_buf_in_nocache((uintptr_t)sequence->buffer, needed_buffer_size)) { + LOG_ERR("Supplied buffer is not in a non-cacheable region."); return -EINVAL; } -#endif +#endif /* CONFIG_ADC_STM32_DMA */ return 0; } diff --git a/drivers/i2c/i2c_ll_stm32_v2.c b/drivers/i2c/i2c_ll_stm32_v2.c index 380f9bf02bc..872383e310f 100644 --- a/drivers/i2c/i2c_ll_stm32_v2.c +++ b/drivers/i2c/i2c_ll_stm32_v2.c @@ -659,37 +659,6 @@ end: return -EIO; } -#if defined(CONFIG_DCACHE) && defined(CONFIG_I2C_STM32_V2_DMA) -static bool buf_in_nocache(uintptr_t buf, size_t len_bytes) -{ - bool buf_within_nocache = false; - -#ifdef CONFIG_NOCACHE_MEMORY - /* Check if buffer is in nocache region defined by the linker */ - buf_within_nocache = (buf >= ((uintptr_t)_nocache_ram_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)_nocache_ram_end)); - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_NOCACHE_MEMORY */ - -#ifdef CONFIG_MEM_ATTR - /* Check if buffer is in nocache memory region defined in DT */ - buf_within_nocache = mem_attr_check_buf( - (void *)buf, len_bytes, DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE)) == 0; - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_MEM_ATTR */ - - /* Check if buffer is in RO region (Flash..) */ - buf_within_nocache = (buf >= ((uintptr_t)__rodata_region_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)__rodata_region_end)); - - return buf_within_nocache; -} -#endif /* CONFIG_DCACHE && CONFIG_I2C_STM32_V2_DMA */ - static int i2c_stm32_msg_write(const struct device *dev, struct i2c_msg *msg, uint8_t *next_msg_flags, uint16_t slave) { @@ -705,13 +674,13 @@ static int i2c_stm32_msg_write(const struct device *dev, struct i2c_msg *msg, data->current.is_err = 0U; data->current.msg = msg; -#if defined(CONFIG_DCACHE) && defined(CONFIG_I2C_STM32_V2_DMA) - if (!buf_in_nocache((uintptr_t)msg->buf, msg->len)) { +#if defined(CONFIG_I2C_STM32_V2_DMA) + if (!stm32_buf_in_nocache((uintptr_t)msg->buf, msg->len)) { LOG_DBG("Tx buffer at %p (len %zu) is in cached memory; cleaning cache", msg->buf, msg->len); sys_cache_data_flush_range((void *)msg->buf, msg->len); } -#endif /* CONFIG_DCACHE && CONFIG_I2C_STM32_V2_DMA*/ +#endif /* CONFIG_I2C_STM32_V2_DMA */ msg_init(dev, msg, next_msg_flags, slave, LL_I2C_REQUEST_WRITE); @@ -783,14 +752,13 @@ static int i2c_stm32_msg_read(const struct device *dev, struct i2c_msg *msg, k_sem_take(&data->device_sync_sem, K_FOREVER); is_timeout = true; } - -#if defined(CONFIG_DCACHE) && defined(CONFIG_I2C_STM32_V2_DMA) - if (!buf_in_nocache((uintptr_t)msg->buf, msg->len)) { +#if defined(CONFIG_I2C_STM32_V2_DMA) + if (!stm32_buf_in_nocache((uintptr_t)msg->buf, msg->len)) { LOG_DBG("Rx buffer at %p (len %zu) is in cached memory; invalidating cache", msg->buf, msg->len); sys_cache_data_invd_range((void *)msg->buf, msg->len); } -#endif /* CONFIG_DCACHE && CONFIG_I2C_STM32_V2_DMA */ +#endif /* CONFIG_I2C_STM32_V2_DMA */ if (data->current.is_nack || data->current.is_err || data->current.is_arlo || is_timeout) { diff --git a/drivers/serial/uart_stm32.c b/drivers/serial/uart_stm32.c index 57c1ab134bd..d67513cae8b 100644 --- a/drivers/serial/uart_stm32.c +++ b/drivers/serial/uart_stm32.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1378,34 +1379,6 @@ static void uart_stm32_isr(const struct device *dev) #ifdef CONFIG_UART_ASYNC_API -#ifdef CONFIG_DCACHE -static bool buf_in_nocache(uintptr_t buf, size_t len_bytes) -{ - bool buf_within_nocache = false; - -#ifdef CONFIG_NOCACHE_MEMORY - buf_within_nocache = (buf >= ((uintptr_t)_nocache_ram_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)_nocache_ram_end)); - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_NOCACHE_MEMORY */ - -#ifdef CONFIG_MEM_ATTR - buf_within_nocache = mem_attr_check_buf( - (void *)buf, len_bytes, DT_MEM_ARM_MPU_RAM_NOCACHE) == 0; - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_MEM_ATTR */ - - buf_within_nocache = (buf >= ((uintptr_t)__rodata_region_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)__rodata_region_end)); - - return buf_within_nocache; -} -#endif /* CONFIG_DCACHE */ - static int uart_stm32_async_callback_set(const struct device *dev, uart_callback_t callback, void *user_data) @@ -1631,12 +1604,10 @@ static int uart_stm32_async_tx(const struct device *dev, return -EBUSY; } -#ifdef CONFIG_DCACHE - if (!buf_in_nocache((uintptr_t)tx_data, buf_size)) { + if (!stm32_buf_in_nocache((uintptr_t)tx_data, buf_size)) { LOG_ERR("Tx buffer should be placed in a nocache memory region"); return -EFAULT; } -#endif /* CONFIG_DCACHE */ #ifdef CONFIG_PM data->tx_poll_stream_on = false; @@ -1743,12 +1714,10 @@ static int uart_stm32_async_rx_enable(const struct device *dev, return -EBUSY; } -#ifdef CONFIG_DCACHE - if (!buf_in_nocache((uintptr_t)rx_buf, buf_size)) { + if (!stm32_buf_in_nocache((uintptr_t)rx_buf, buf_size)) { LOG_ERR("Rx buffer should be placed in a nocache memory region"); return -EFAULT; } -#endif /* CONFIG_DCACHE */ data->dma_rx.offset = 0; data->dma_rx.buffer = rx_buf; @@ -1874,12 +1843,10 @@ static int uart_stm32_async_rx_buf_rsp(const struct device *dev, uint8_t *buf, } else if (!data->dma_rx.enabled) { err = -EACCES; } else { -#ifdef CONFIG_DCACHE - if (!buf_in_nocache((uintptr_t)buf, len)) { + if (!stm32_buf_in_nocache((uintptr_t)buf, len)) { LOG_ERR("Rx buffer should be placed in a nocache memory region"); return -EFAULT; } -#endif /* CONFIG_DCACHE */ data->rx_next_buffer = buf; data->rx_next_buffer_len = len; } diff --git a/drivers/spi/spi_ll_stm32.c b/drivers/spi/spi_ll_stm32.c index df41f344aeb..366b59a9b00 100644 --- a/drivers/spi/spi_ll_stm32.c +++ b/drivers/spi/spi_ll_stm32.c @@ -13,6 +13,7 @@ LOG_MODULE_REGISTER(spi_ll_stm32); #include #include #include +#include #include #include #include @@ -1076,28 +1077,6 @@ static int wait_dma_rx_tx_done(const struct device *dev) } #ifdef CONFIG_DCACHE -static bool buf_in_nocache(uintptr_t buf __maybe_unused, size_t len_bytes __maybe_unused) -{ - bool buf_within_nocache = false; - -#ifdef CONFIG_NOCACHE_MEMORY - /* Check if buffer is in nocache region defined by the linker */ - buf_within_nocache = (buf >= ((uintptr_t)_nocache_ram_start)) && - ((buf + len_bytes - 1) <= ((uintptr_t)_nocache_ram_end)); - if (buf_within_nocache) { - return true; - } -#endif /* CONFIG_NOCACHE_MEMORY */ - -#ifdef CONFIG_MEM_ATTR - /* Check if buffer is in nocache memory region defined in DT */ - buf_within_nocache = mem_attr_check_buf( - (void *)buf, len_bytes, DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE)) == 0; -#endif /* CONFIG_MEM_ATTR */ - - return buf_within_nocache; -} - static bool is_dummy_buffer(const struct spi_buf *buf) { return buf->buf == NULL; @@ -1109,7 +1088,7 @@ static bool spi_buf_set_in_nocache(const struct spi_buf_set *bufs) const struct spi_buf *buf = &bufs->buffers[i]; if (!is_dummy_buffer(buf) && - !buf_in_nocache((uintptr_t)buf->buf, buf->len)) { + !stm32_buf_in_nocache((uintptr_t)buf->buf, buf->len)) { return false; } } diff --git a/soc/st/stm32/common/CMakeLists.txt b/soc/st/stm32/common/CMakeLists.txt index 6d97ebd9ac0..e532e17b7ba 100644 --- a/soc/st/stm32/common/CMakeLists.txt +++ b/soc/st/stm32/common/CMakeLists.txt @@ -33,3 +33,5 @@ SoC Power Management (CONFIG_PM) enabled but the DBGMCU is still enabled \ (CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP). The SoC will use more power than expected \ in STOP modes due to internal oscillators that remain active.") endif() + +zephyr_sources_ifdef(CONFIG_DCACHE stm32_cache.c) diff --git a/soc/st/stm32/common/stm32_cache.c b/soc/st/stm32/common/stm32_cache.c new file mode 100644 index 00000000000..ac8c2878e4c --- /dev/null +++ b/soc/st/stm32/common/stm32_cache.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Henrik Lindblom + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "stm32_cache.h" +#include +#include +#include +#include + +bool stm32_buf_in_nocache(uintptr_t buf, size_t len_bytes) +{ + uintptr_t buf_end; + + /* Note: (len_bytes - 1) is safe because a length of 0 would overflow to 4 billion. */ + if (u32_add_overflow(buf, len_bytes - 1, (uint32_t *)&buf_end)) { + return false; + } + +#ifdef CONFIG_EXTERNAL_CACHE + /* STM32 DCACHE is enabled but it does not cover internal SRAM. If the buffer is in SRAM it + * is not cached by DCACHE. + * + * NOTE: This incorrectly returns true if Zephyr image RAM resides in external memory. + */ + if (((uintptr_t)_image_ram_start) <= buf && buf_end < ((uintptr_t)_image_ram_end)) { + return true; + } +#endif /* CONFIG_EXTERNAL_CACHE */ + +#ifdef CONFIG_NOCACHE_MEMORY + /* Check if buffer is in NOCACHE region defined by the linker. */ + if ((uintptr_t)_nocache_ram_start <= buf && buf_end <= (uintptr_t)_nocache_ram_end) { + return true; + } +#endif /* CONFIG_NOCACHE_MEMORY */ + +#ifdef CONFIG_MEM_ATTR + /* Check if buffer is in a region marked NOCACHE in DT. */ + if (mem_attr_check_buf((void *)buf, len_bytes, DT_MEM_ARM_MPU_RAM_NOCACHE) == 0) { + return true; + } +#endif /* CONFIG_MEM_ATTR */ + + /* Check if buffer is in read-only region, which cannot be stale due to DCACHE because it is + * not writeable. + */ + if ((uintptr_t)__rodata_region_start <= buf && buf_end <= (uintptr_t)__rodata_region_end) { + return true; + } + + /* Not in any region known to be NOCACHE */ + return false; +} diff --git a/soc/st/stm32/common/stm32_cache.h b/soc/st/stm32/common/stm32_cache.h new file mode 100644 index 00000000000..a789f474fd0 --- /dev/null +++ b/soc/st/stm32/common/stm32_cache.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Henrik Lindblom + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef STM32_CACHE_H_ +#define STM32_CACHE_H_ + +#include +#include +#include +#include + +#if defined(CONFIG_DCACHE) +/** + * @brief Check if the given buffer resides in nocache memory + * + * Always returns true if CONFIG_DCACHE is not set. + * + * @param buf Buffer + * @param len_bytes Buffer size in bytes + * @return true if buf resides completely in nocache memory + */ +bool stm32_buf_in_nocache(uintptr_t buf, size_t len_bytes); + +#else /* !CONFIG_DCACHE */ + +static inline bool stm32_buf_in_nocache(uintptr_t buf, size_t len_bytes) +{ + ARG_UNUSED(buf); + ARG_UNUSED(len_bytes); + + /* Whole memory is nocache if DCACHE is disabled */ + return true; +} + +#endif /* CONFIG_DCACHE */ + +#endif /* STM32_CACHE_H_ */