Skip to content

Commit

Permalink
drivers: stm32: SPI: Check that SPI buffers are in a nocache region
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dgastonochoa committed Jul 25, 2023
1 parent e1e4fcc commit 929f446
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 0 deletions.
2 changes: 2 additions & 0 deletions drivers/spi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions drivers/spi/spi.c
Original file line number Diff line number Diff line change
@@ -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;
}
27 changes: 27 additions & 0 deletions drivers/spi/spi.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <stdbool.h>

#include <zephyr/devicetree.h>
#include <zephyr/drivers/spi.h>

/**
* @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_ */
8 changes: 8 additions & 0 deletions drivers/spi/spi_ll_stm32.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ LOG_MODULE_REGISTER(spi_ll_stm32);
#include <zephyr/drivers/clock_control.h>
#include <zephyr/irq.h>

#include "spi.h"
#include "spi_ll_stm32.h"

#define WAIT_1US 1U
Expand Down Expand Up @@ -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);
Expand Down
104 changes: 104 additions & 0 deletions include/zephyr/devicetree/mem.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <string.h>
#include <stdbool.h>

#include <zephyr/devicetree/memory-attr.h>

/**
* @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(<node_label>))
* };
*
*/
#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_ */
1 change: 1 addition & 0 deletions include/zephyr/drivers/spi.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <zephyr/sys/__assert.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/stats/stats.h>
#include <zephyr/devicetree/mem.h>

#ifdef __cplusplus
extern "C" {
Expand Down
115 changes: 115 additions & 0 deletions tests/lib/devicetree/api/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <zephyr/ztest.h>
#include <zephyr/devicetree.h>
#include <zephyr/devicetree/mem.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/mbox.h>
Expand Down Expand Up @@ -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"), "");
Expand Down Expand Up @@ -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);

0 comments on commit 929f446

Please sign in to comment.