diff --git a/drivers/dma/dma_emul.c b/drivers/dma/dma_emul.c index 6320d1c1620b7..be9ac68bcd098 100644 --- a/drivers/dma/dma_emul.c +++ b/drivers/dma/dma_emul.c @@ -176,7 +176,7 @@ static const char *dma_emul_block_config_to_string(const struct dma_block_config "\n\tflow_control_mode: %u" "\n\t_reserved: %u" "\n}", - (void *)cfg->source_address, (void *)cfg->dest_address, + (void *)(uintptr_t)cfg->source_address, (void *)(uintptr_t)cfg->dest_address, cfg->source_gather_interval, cfg->dest_scatter_interval, cfg->dest_scatter_count, cfg->source_gather_count, cfg->block_size, cfg->next_block, cfg->source_gather_en, cfg->dest_scatter_en, cfg->source_addr_adj, cfg->dest_addr_adj, diff --git a/include/zephyr/drivers/dma.h b/include/zephyr/drivers/dma.h index 7a0954e248a27..6b05a4d4d4416 100644 --- a/include/zephyr/drivers/dma.h +++ b/include/zephyr/drivers/dma.h @@ -784,6 +784,125 @@ static inline uint32_t dma_burst_index(uint32_t burst) */ #define DMA_COPY_ALIGNMENT(node) DT_PROP(node, dma_copy_alignment) + +/** + * @brief A handle around a DMA channel provided as a kernel object + * + * In order to provide per channel permissions access on a DMA a kobject + * per dma channel is provided. This could also be used for convienence around + * instantiation of dma channels from device tree using some macros + * that understand a particular nodes DMA channel configuration. This isn't + * always a single channel or commonly named property though. + */ +struct dma_channel { + const struct device *dma; + uint32_t channel; +}; + +/** + * @brief A handle around a hardware FIFO address provided as a kernel object + * + * In order to provide secure transfers to/from buffers and to/from hardware fifos + * in a syscall, the hardware FIFO needs to be a well understood kernel object with + * access control. Effectively "just" a memory address for the machine, this kobject + * can be checked against for each usermode thread wishing to send/recv data to it. + */ +struct dma_fifo { + uint32_t hwfifo; +}; + +/** + * @brief Start a DMA channel + * + * @see `dma_start` for details + * + * @param dma_chan A DMA channel to start + * @retval 0 Success + * @retval -errno Error + */ +__syscall int dma_channel_start(const struct dma_channel *dma_chan); + +static inline int z_impl_dma_channel_start(const struct dma_channel *dma_chan) +{ + return dma_start(dma_chan->dma, dma_chan->channel); +} + +/** + * @brief Stop a DMA channel + * + * @see `dma_stop` for details + * + * @param dma_chan A DMA channel to stop + * @retval 0 Success + * @retval -errno Error + */ +__syscall int dma_channel_stop(const struct dma_channel *dma_chan); + +static inline int z_impl_dma_channel_stop(const struct dma_channel *dma_chan) +{ + return dma_stop(dma_chan->dma, dma_chan->channel); +} + +/** + * @brief Obtain the status of a DMA channel + * + * @param dma_chan A DMA channel to stop + * @retval 0 Success + * @retval -errno Error + */ +__syscall int dma_channel_get_status(const struct dma_channel *dma_chan, struct dma_status *status); + +static inline int z_impl_dma_channel_get_status(const struct dma_channel *dma_chan, + struct dma_status *status) +{ + return dma_get_status(dma_chan->dma, dma_chan->channel, status); +} + + +/** + * @brief Reload a DMA channel transfer from a hardware FIFO + * + * @see `dma_reload` for details + * + * @param dma_chan A DMA channel to stop + * @param src_fifo A hardware FIFO handle + * @param dst A buffer address + * @param len Length of the transfer + * @retval 0 Success + * @retval -errno Error + */ +__syscall int dma_channel_reload_from_fifo(const struct dma_channel *dma_chan, + struct dma_fifo *src_fifo, uint32_t dst, size_t len); + +static inline int z_impl_dma_channel_reload_from_fifo(const struct dma_channel *dma_chan, + struct dma_fifo *src_fifo, + uint32_t dst, size_t len) +{ + return dma_reload(dma_chan->dma, dma_chan->channel, src_fifo->hwfifo, dst, len); +} + +/** + * @brief Reload a DMA channel transfer to a hardware FIFO + * + * @see `dma_reload` for details + * + * @param dma_chan A DMA channel to stop + * @param src A buffer address + * @param dst_fifo A hardware FIFO handle + * @param len Length of the transfer + * @retval 0 Success + * @retval -errno Error + */ +__syscall int dma_channel_reload_to_fifo(const struct dma_channel *dma_chan, uint32_t src, + struct dma_fifo *dst_fifo, size_t len); + +static inline int z_impl_dma_channel_reload_to_fifo(const struct dma_channel *dma_chan, + uint32_t src, struct dma_fifo *dst_fifo, + size_t len) +{ + return dma_reload(dma_chan->dma, dma_chan->channel, src, dst_fifo->hwfifo, len); +} + /** * @} */ @@ -792,4 +911,6 @@ static inline uint32_t dma_burst_index(uint32_t burst) } #endif +#include + #endif /* ZEPHYR_INCLUDE_DRIVERS_DMA_H_ */ diff --git a/scripts/build/gen_kobject_list.py b/scripts/build/gen_kobject_list.py index 6a68468901f69..ff92ed7fac5d2 100755 --- a/scripts/build/gen_kobject_list.py +++ b/scripts/build/gen_kobject_list.py @@ -116,7 +116,9 @@ ("rtio_iodev", ("CONFIG_RTIO", False, False)), ("rtio_pool", ("CONFIG_RTIO", False, False)), ("adc_decoder_api", ("CONFIG_ADC_STREAM", True, False)), - ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)) + ("sensor_decoder_api", ("CONFIG_SENSOR_ASYNC_API", True, False)), + ("dma_channel", ("CONFIG_DMA", False, True)), + ("dma_fifo", ("CONFIG_DMA", False, True)), ]) def kobject_to_enum(kobj): diff --git a/tests/drivers/dma/usermode/CMakeLists.txt b/tests/drivers/dma/usermode/CMakeLists.txt new file mode 100644 index 0000000000000..cbd621da6fff4 --- /dev/null +++ b/tests/drivers/dma/usermode/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(dma_usermode) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/dma/usermode/app.overlay b/tests/drivers/dma/usermode/app.overlay new file mode 100644 index 0000000000000..c94afcef0e0fc --- /dev/null +++ b/tests/drivers/dma/usermode/app.overlay @@ -0,0 +1,9 @@ +/ { + dma: dma { + compatible = "zephyr,dma-emul"; + #dma-cells = <1>; + dma-channels = <8>; + stack-size = <4096>; + status = "okay"; + }; +}; diff --git a/tests/drivers/dma/usermode/prj.conf b/tests/drivers/dma/usermode/prj.conf new file mode 100644 index 0000000000000..7fe24e3e9d95e --- /dev/null +++ b/tests/drivers/dma/usermode/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ZTEST=y +CONFIG_USERSPACE=y +CONFIG_LOG=y +CONFIG_DMA=y +CONFIG_DMA_EMUL=y +CONFIG_DMA_LOG_LEVEL_INF=y diff --git a/tests/drivers/dma/usermode/src/test_dma_usermode.c b/tests/drivers/dma/usermode/src/test_dma_usermode.c new file mode 100644 index 0000000000000..56358d389ec21 --- /dev/null +++ b/tests/drivers/dma/usermode/src/test_dma_usermode.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2025 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Verify usermode APIs for DMA + */ + +#include "zephyr/fatal_types.h" +#include +#include +#include + +ZTEST_BMEM static volatile bool expect_fault; +ZTEST_BMEM static volatile unsigned int expected_reason; +ZTEST_BMEM static volatile bool faulted; + +static void clear_fault(void) +{ + expect_fault = false; + compiler_barrier(); +} + +static void set_fault(unsigned int reason) +{ + faulted = false; + expect_fault = true; + expected_reason = reason; + compiler_barrier(); +} + +void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *pEsf) +{ + printk("Caught system error -- reason %d\n", reason); + faulted = true; + + if (expect_fault) { + if (expected_reason == reason) { + printk("System error was expected\n"); + clear_fault(); + } else { + printk("Wrong fault reason, expecting %d\n", + expected_reason); + TC_END_REPORT(TC_FAIL); + k_fatal_halt(reason); + } + } else { + printk("Unexpected fault during test\n"); + TC_END_REPORT(TC_FAIL); + k_fatal_halt(reason); + } +} + +const struct device *dma = DEVICE_DT_GET(DT_NODELABEL(dma)); + +ZTEST_USER(dma_usermode, test_invalid_chan_filter) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_chan_filter(dma, 0, NULL); + + TC_END_REPORT(TC_FAIL); +} + + +ZTEST_USER(dma_usermode, test_invalid_config) +{ + zassert_true(k_is_user_context()); + + struct dma_config cfg; + + set_fault(K_ERR_CPU_EXCEPTION); + dma_config(dma, 0, &cfg); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_start) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_start(dma, 0); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_stop) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_stop(dma, 0); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_suspend) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_suspend(dma, 0); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_resume) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_resume(dma, 0); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_reload) +{ + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_reload(dma, 0, 0, 1, 1); + + TC_END_REPORT(TC_FAIL); +} + +ZTEST_USER(dma_usermode, test_invalid_get_status) +{ + struct dma_status status; + + zassert_true(k_is_user_context()); + + set_fault(K_ERR_CPU_EXCEPTION); + dma_get_status(dma, 0, &status); + + TC_END_REPORT(TC_FAIL); +} + +void *userspace_setup(void) +{ + return NULL; +} + + +ZTEST_SUITE(dma_usermode, NULL, userspace_setup, NULL, NULL, NULL); diff --git a/tests/drivers/dma/usermode/testcase.yaml b/tests/drivers/dma/usermode/testcase.yaml new file mode 100644 index 0000000000000..84ca3c69c545c --- /dev/null +++ b/tests/drivers/dma/usermode/testcase.yaml @@ -0,0 +1,7 @@ +tests: + drivers.dma.usermode: + filter: CONFIG_ARCH_HAS_USERSPACE + tags: + - drivers + - userspace + - dma