From 929f44617d020bec538d9875975dbc6b7d8dbe81 Mon Sep 17 00:00:00 2001 From: Daniel Gaston Ochoa Date: Tue, 25 Jul 2023 10:43:34 +0100 Subject: [PATCH] drivers: stm32: SPI: Check that SPI buffers are in a nocache region DMA only works with non-cached memory regions in H7. Check them and return an error if they don't match this condition. Signed-off-by: Daniel Gaston Ochoa --- drivers/spi/CMakeLists.txt | 2 + drivers/spi/spi.c | 19 +++++ drivers/spi/spi.h | 27 +++++++ drivers/spi/spi_ll_stm32.c | 8 ++ include/zephyr/devicetree/mem.h | 104 +++++++++++++++++++++++++ include/zephyr/drivers/spi.h | 1 + tests/lib/devicetree/api/src/main.c | 115 ++++++++++++++++++++++++++++ 7 files changed, 276 insertions(+) create mode 100644 drivers/spi/spi.c create mode 100644 drivers/spi/spi.h create mode 100644 include/zephyr/devicetree/mem.h diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 91d70e8cbeff43f..d29fdf11c9c631c 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -4,6 +4,8 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/spi.h) zephyr_library() +zephyr_library_sources(spi.c) + zephyr_library_sources_ifdef(CONFIG_SPI_TELINK_B91 spi_b91.c) zephyr_library_sources_ifdef(CONFIG_SPI_CC13XX_CC26XX spi_cc13xx_cc26xx.c) zephyr_library_sources_ifdef(CONFIG_SPI_DW spi_dw.c) diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c new file mode 100644 index 000000000000000..4314caac086c561 --- /dev/null +++ b/drivers/spi/spi.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Graphcore Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "spi.h" + +bool spi_buf_set_in_nocache(const struct spi_buf_set *bufs) +{ + for (size_t i = 0; i < bufs->count; i++) { + const struct spi_buf *buf = &bufs->buffers[i]; + + if (!dt_buf_in_nocache((uintptr_t)buf->buf, buf->len)) { + return false; + } + } + return true; +} diff --git a/drivers/spi/spi.h b/drivers/spi/spi.h new file mode 100644 index 000000000000000..c1690acc2f54ca4 --- /dev/null +++ b/drivers/spi/spi.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Graphcore Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SPI_SPI_H_ +#define ZEPHYR_DRIVERS_SPI_SPI_H_ + +#include +#include + +#include +#include + +/** + * @brief Checks that all buffers in `bufs` are in nocache memory region. This + * should be used in platforms where DMA cannot access cache memory, and so any + * buffer used for DMA operations must be in a nocache region. + * + * @param bufs Buffers to check + * + * @return True if all buffers in bufs are in a nocache region, false otherwise. + */ +bool spi_buf_set_in_nocache(const struct spi_buf_set *bufs); + +#endif /* ZEPHYR_DRIVERS_SPI_SPI_H_ */ diff --git a/drivers/spi/spi_ll_stm32.c b/drivers/spi/spi_ll_stm32.c index 8590cc6fd580568..8086ba8330da2a4 100644 --- a/drivers/spi/spi_ll_stm32.c +++ b/drivers/spi/spi_ll_stm32.c @@ -26,6 +26,7 @@ LOG_MODULE_REGISTER(spi_ll_stm32); #include #include +#include "spi.h" #include "spi_ll_stm32.h" #define WAIT_1US 1U @@ -744,6 +745,13 @@ static int transceive_dma(const struct device *dev, return -ENOTSUP; } +#ifdef CONFIG_SOC_SERIES_STM32H7X + if ((tx_bufs != NULL && !spi_buf_set_in_nocache(tx_bufs)) || + (rx_bufs != NULL && !spi_buf_set_in_nocache(rx_bufs))) { + return -EFAULT; + } +#endif /* CONFIG_SOC_SERIES_STM32H7X */ + spi_context_lock(&data->ctx, asynchronous, cb, userdata, config); k_sem_reset(&data->status_sem); diff --git a/include/zephyr/devicetree/mem.h b/include/zephyr/devicetree/mem.h new file mode 100644 index 000000000000000..bb33cfe68a4cd94 --- /dev/null +++ b/include/zephyr/devicetree/mem.h @@ -0,0 +1,104 @@ +/** + * @file + * @brief Memory Devicetree macro public API header file. + */ + +/* + * Copyright (c) 2023 Graphcore Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DEVICETREE_MEM_H_ +#define ZEPHYR_INCLUDE_DEVICETREE_MEM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include + +/** + * @defgroup devicetree-memory Devicetree memory API + * @ingroup devicetree + * @{ + */ + +/** + * @brief Container for memory-region information specified in devicetree + * + * This type contains pointers to the begin and end of the memory region and + * a string that specifies such region's properties. + * + */ +struct dt_mem_region { + const uintptr_t start; + const uintptr_t end; + bool isnocache; +}; + +#define _DT_IS_NOCACHE_MEM_REGION(node_id) \ + COND_CODE_1(DT_ENUM_HAS_VALUE(node_id, zephyr_memory_attr, RAM_NOCACHE), \ + (true), \ + (false)) + +/** + * @brief Can be used to initialize a @see{struct dt_mem_region}. + * + * @param node_id Node ID of the memory region declared in device tree from + * which the information will be obtained. + * + * @example + * + * struct dt_mem_region mem_r = { + * DT_GET_MEM_REGION(DT_NODELABEL()) + * }; + * + */ +#define DT_GET_MEM_REGION(node_id) \ + { \ + .start = (const uintptr_t)DT_REG_ADDR(node_id), \ + .end = \ + (const uintptr_t)(DT_REG_ADDR(node_id) + DT_REG_SIZE(node_id)) - 1, \ + .isnocache = _DT_IS_NOCACHE_MEM_REGION(node_id) \ + }, + +/** + * @brief Return true if buf is in a nocache memory region + * among mem_regions. + * + * @param buf Buffer to be checked + * @param len_bytes Length of the buffer above + * + */ +static inline bool dt_buf_in_nocache(uintptr_t buf, size_t len_bytes) +{ + const struct dt_mem_region mem_regions[] = { + DT_MEMORY_ATTR_FOREACH_NODE(DT_GET_MEM_REGION) + }; + + for (size_t i = 0; i < ARRAY_SIZE(mem_regions); i++) { + const struct dt_mem_region *mem_reg = &mem_regions[i]; + + const bool buf_within_bounds = + (buf >= mem_reg->start) && ((buf + len_bytes - 1) <= mem_reg->end); + if (buf_within_bounds && mem_reg->isnocache) { + return true; + } + } + return false; +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DEVICETREE_MEM_H_ */ diff --git a/include/zephyr/drivers/spi.h b/include/zephyr/drivers/spi.h index 37647a8d4edf7fe..3a1a3b0bcc6e05f 100644 --- a/include/zephyr/drivers/spi.h +++ b/include/zephyr/drivers/spi.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index adeee59bf1f2db4..8d792ca9924a2fe 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -95,6 +96,19 @@ #define TO_STRING(x) TO_STRING_(x) #define TO_STRING_(x) #x +static int find_mem_reg_by_start_add( + const struct dt_mem_region mem_regions[], + size_t num_regions, + uintptr_t addr) +{ + for (int i = 0; i < num_regions; i++) { + if (mem_regions[i].start == addr) { + return i; + } + } + return -1; +} + ZTEST(devicetree_api, test_path_props) { zassert_true(!strcmp(DT_LABEL(TEST_DEADBEEF), "TEST_GPIO_1"), ""); @@ -3162,4 +3176,105 @@ ZTEST(devicetree_api, test_reset) zassert_equal(DT_INST_RESET_ID(0), 10, ""); } +ZTEST(devicetree_api, test_dt_get_mem_region_finds_no_nocache_region) +{ + const struct dt_mem_region mem_regions[] = { + DT_MEMORY_ATTR_FOREACH_NODE(DT_GET_MEM_REGION) + }; + + int idx = find_mem_reg_by_start_add(mem_regions, + ARRAY_SIZE(mem_regions), + (uintptr_t)DT_REG_ADDR(DT_NODELABEL(test_mem_ram)) + ); + zassert_true(idx >= 0); +} + +ZTEST(devicetree_api, test_dt_get_mem_region_finds_nocache_region) +{ + const struct dt_mem_region mem_regions[] = { + DT_MEMORY_ATTR_FOREACH_NODE(DT_GET_MEM_REGION) + }; + + int idx = find_mem_reg_by_start_add(mem_regions, + ARRAY_SIZE(mem_regions), + (uintptr_t)DT_REG_ADDR(DT_NODELABEL(test_mem_ram_nocache)) + ); + zassert_true(idx >= 0); +} + +ZTEST(devicetree_api, test_dt_get_mem_region_no_nocache_correct_attr) +{ + const struct dt_mem_region mem_regions[] = { + DT_MEMORY_ATTR_FOREACH_NODE(DT_GET_MEM_REGION) + }; + + int idx = find_mem_reg_by_start_add(mem_regions, + ARRAY_SIZE(mem_regions), + (uintptr_t)DT_REG_ADDR(DT_NODELABEL(test_mem_ram)) + ); + + uintptr_t expec_start = (uintptr_t)DT_REG_ADDR(DT_NODELABEL(test_mem_ram)); + uintptr_t expec_end = (uintptr_t)(expec_start + + DT_REG_SIZE(DT_NODELABEL(test_mem_ram)) - 1); + zassert_equal(mem_regions[idx].start, expec_start); + zassert_equal(mem_regions[idx].end, expec_end); + zassert_false(mem_regions[idx].isnocache); +} + +ZTEST(devicetree_api, test_dt_get_mem_region_nocache_correct_attr) +{ + const struct dt_mem_region mem_regions[] = { + DT_MEMORY_ATTR_FOREACH_NODE(DT_GET_MEM_REGION) + }; + + int idx = find_mem_reg_by_start_add(mem_regions, + ARRAY_SIZE(mem_regions), + (uintptr_t)DT_REG_ADDR(DT_NODELABEL(test_mem_ram_nocache)) + ); + + uintptr_t expec_start = (uintptr_t)DT_REG_ADDR( + DT_NODELABEL(test_mem_ram_nocache)); + uintptr_t expec_end = (uintptr_t)(expec_start + + DT_REG_SIZE(DT_NODELABEL(test_mem_ram_nocache)) - 1); + zassert_equal(mem_regions[idx].start, expec_start); + zassert_equal(mem_regions[idx].end, expec_end); + zassert_true(mem_regions[idx].isnocache); +} + +ZTEST(devicetree_api, test_dt_buf_in_nocache_detects_nocache) +{ + const uintptr_t buf_nocahe = (const uintptr_t)DT_REG_ADDR( + DT_NODELABEL(test_mem_ram_nocache)); + const size_t buf_len_within_cache = (size_t)DT_REG_SIZE( + DT_NODELABEL(test_mem_ram_nocache)); + + bool isnocache = dt_buf_in_nocache(buf_nocahe, buf_len_within_cache); + + zassert_true(isnocache); +} + +ZTEST(devicetree_api, test_dt_buf_in_nocache_detects_no_nocache_too_long) +{ + const uintptr_t buf_nocahe = (const uintptr_t)DT_REG_ADDR( + DT_NODELABEL(test_mem_ram_nocache)); + const size_t buf_len_out_nocache = (size_t)(DT_REG_SIZE( + DT_NODELABEL(test_mem_ram_nocache)) + 1); + + bool isnocache = dt_buf_in_nocache(buf_nocahe, buf_len_out_nocache); + + zassert_false(isnocache); +} + +ZTEST(devicetree_api, test_dt_buf_in_nocache_detects_no_nocache) +{ + const uintptr_t buf_cache = (const uintptr_t)DT_REG_ADDR( + DT_NODELABEL(test_mem_ram)); + const size_t buf_cache_len = (size_t)(DT_REG_SIZE( + DT_NODELABEL(test_mem_ram)) - 1); + + bool isnocache = dt_buf_in_nocache(buf_cache, buf_cache_len); + + zassert_false(isnocache); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL);