From 1ec212a10f623dd12912703e38ee3088c78e0793 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 21 Nov 2025 15:08:08 +0200 Subject: [PATCH 1/8] ipc4: do not declare union/structs with very generic names in headers gateway.h defines "union flags" and "struct bits" to global namespace, which is not good in a public interface header file. There's no functional need to tag these struct definitions, so make the definitions anonymous. Signed-off-by: Kai Vehmanen --- src/include/ipc4/gateway.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/ipc4/gateway.h b/src/include/ipc4/gateway.h index f78c21caf979..9398bb44be0e 100644 --- a/src/include/ipc4/gateway.h +++ b/src/include/ipc4/gateway.h @@ -162,8 +162,8 @@ struct ipc4_ipc_gateway_config_blob { uint32_t buffer_size; /**< Flags */ - union flags { - struct bits { + union { + struct { /**< Activates high threshold notification */ /*! * Indicates whether notification should be sent to the host From f771da4175773a0378cf832447eca9d3c5f25dcd Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 8 Dec 2025 12:24:02 +0200 Subject: [PATCH 2/8] lib: dma: add user-space support for dma_suspend/resume calls Extend coverage of the Zephyr DAI interface to include dma_suspend() and dma_resume() calls exposed as syscalls. This allows e.g. to run the DAI module in a user thread. Signed-off-by: Kai Vehmanen --- zephyr/include/sof/lib/sof_dma.h | 24 ++++++++++++++++++++++++ zephyr/syscall/sof_dma.c | 16 ++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/zephyr/include/sof/lib/sof_dma.h b/zephyr/include/sof/lib/sof_dma.h index 74b7d4289083..bdd64aeac9fa 100644 --- a/zephyr/include/sof/lib/sof_dma.h +++ b/zephyr/include/sof/lib/sof_dma.h @@ -51,6 +51,10 @@ __syscall int sof_dma_get_status(struct sof_dma *dma, uint32_t channel, struct d __syscall int sof_dma_reload(struct sof_dma *dma, uint32_t channel, size_t size); +__syscall int sof_dma_suspend(struct sof_dma *dma, uint32_t channel); + +__syscall int sof_dma_resume(struct sof_dma *dma, uint32_t channel); + static inline int z_impl_sof_dma_get_attribute(struct sof_dma *dma, uint32_t type, uint32_t *value) { return dma_get_attribute(dma->z_dev, type, value); @@ -95,6 +99,16 @@ static inline int z_impl_sof_dma_reload(struct sof_dma *dma, uint32_t channel, s return dma_reload(dma->z_dev, channel, 0, 0, size); } +static inline int z_impl_sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + return dma_suspend(dma->z_dev, channel); +} + +static inline int z_impl_sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + return dma_resume(dma->z_dev, channel); +} + #ifdef CONFIG_SOF_USERSPACE_INTERFACE_DMA /* include definitions from generated file */ @@ -164,6 +178,16 @@ static inline int sof_dma_reload(struct sof_dma *dma, uint32_t channel, size_t s return z_impl_sof_dma_reload(dma, channel, size); } +static inline int sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + return z_impl_sof_dma_suspend(dma, channel); +} + +static inline int sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + return z_impl_sof_dma_resume(dma, channel); +} + #endif /* CONFIG_SOF_USERSPACE_INTERFACE_DMA */ #endif diff --git a/zephyr/syscall/sof_dma.c b/zephyr/syscall/sof_dma.c index 84fd2f24aee4..ed69ffc78423 100644 --- a/zephyr/syscall/sof_dma.c +++ b/zephyr/syscall/sof_dma.c @@ -235,4 +235,20 @@ static inline int z_vrfy_sof_dma_reload(struct sof_dma *dma, uint32_t channel, s } #include +static inline int z_vrfy_sof_dma_suspend(struct sof_dma *dma, uint32_t channel) +{ + K_OOPS(!sof_dma_is_valid(dma)); + + return z_impl_sof_dma_suspend(dma, channel); +} +#include + +static inline int z_vrfy_sof_dma_resume(struct sof_dma *dma, uint32_t channel) +{ + K_OOPS(!sof_dma_is_valid(dma)); + + return z_impl_sof_dma_resume(dma, channel); +} +#include + #endif /* CONFIG_SOF_USERSPACE_INTERFACE_DMA */ From 3c0b1a7b2eff63b8aed76aad2297a1f3fa22af8e Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 10 Dec 2025 21:27:41 +0200 Subject: [PATCH 3/8] zephyr: only build sof_dma.h syscalls if enabled in build The sof_dma.h syscalls are emitted to build whenever CONFIG_USERSPACE is set. Limit this so that the syscalls are emitted only if CONFIG_SOF_USERSPACE_INTERFACE_DMA is set. Signed-off-by: Kai Vehmanen --- zephyr/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 534b30300ed5..b1af82ae856b 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -490,11 +490,10 @@ if(NOT DEFINED PLATFORM) endif() zephyr_include_directories(${SOF_PLATFORM_PATH}/${PLATFORM}/include) -zephyr_library_sources_ifdef(CONFIG_USERSPACE - syscall/sof_dma.c -) - -zephyr_syscall_header(include/sof/lib/sof_dma.h) +if(CONFIG_SOF_USERSPACE_INTERFACE_DMA) + zephyr_library_sources(syscall/sof_dma.c) + zephyr_syscall_header(include/sof/lib/sof_dma.h) +endif() # Mandatory Files used on all platforms. # Commented files will be added/removed as integration dictates. From 6a495502e883d67ab6ed07e70015e6c373badd0f Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 5 Dec 2025 12:48:40 +0200 Subject: [PATCH 4/8] audio: dai: move dai-zephyr to use SOF DMA wrapper Use the sof/lib/dma.h wrapper interface to access DMA devices. This allows to run this code also from user-space. The commit has no impact to builds where userspace is not used. Also this will not impact builds where user-space is enabled for some functionality, but the dai/copier module is not run in user space. Signed-off-by: Kai Vehmanen --- src/audio/dai-zephyr.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/audio/dai-zephyr.c b/src/audio/dai-zephyr.c index 0d597434fc5d..39138cc9e6e6 100644 --- a/src/audio/dai-zephyr.c +++ b/src/audio/dai-zephyr.c @@ -607,7 +607,7 @@ __cold void dai_common_free(struct dai_data *dd) dai_group_put(dd->group); if (dd->chan) { - dma_release_channel(dd->dma->z_dev, dd->chan->index); + sof_dma_release_channel(dd->dma, dd->chan->index); dd->chan->dev_data = NULL; } @@ -798,7 +798,7 @@ static int dai_set_sg_config(struct dai_data *dd, struct comp_dev *dev, uint32_t comp_dbg(dev, "fifo 0x%x", fifo); - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_MAX_BLOCK_COUNT, &max_block_count); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_MAX_BLOCK_COUNT, &max_block_count); if (err < 0) { comp_err(dev, "can't get max block count, err = %d", err); @@ -954,14 +954,14 @@ static int dai_set_dma_buffer(struct dai_data *dd, struct comp_dev *dev, return -EINVAL; } - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, &addr_align); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, &addr_align); if (err < 0) { comp_err(dev, "can't get dma buffer addr align, err = %d", err); return err; } - err = dma_get_attribute(dd->dma->z_dev, DMA_ATTR_BUFFER_SIZE_ALIGNMENT, &align); + err = sof_dma_get_attribute(dd->dma, DMA_ATTR_BUFFER_SIZE_ALIGNMENT, &align); if (err < 0 || !align) { comp_err(dev, "no valid dma align, err = %d, align = %u", err, align); @@ -1155,7 +1155,7 @@ int dai_common_config_prepare(struct dai_data *dd, struct comp_dev *dev) } /* get DMA channel */ - channel = dma_request_channel(dd->dma->z_dev, &channel); + channel = sof_dma_request_channel(dd->dma, channel); if (channel < 0) { comp_err(dev, "dma_request_channel() failed"); dd->chan = NULL; @@ -1199,7 +1199,7 @@ int dai_common_prepare(struct dai_data *dd, struct comp_dev *dev) return 0; } - ret = dma_config(dd->chan->dma->z_dev, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); if (ret < 0) comp_set_state(dev, COMP_TRIGGER_RESET); @@ -1286,7 +1286,7 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { - ret = dma_start(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_start(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; @@ -1324,16 +1324,16 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, /* only start the DAI if we are not XRUN handling */ if (dd->xrun == 0) { /* recover valid start position */ - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; /* dma_config needed after stop */ - ret = dma_config(dd->chan->dma->z_dev, dd->chan->index, dd->z_config); + ret = sof_dma_config(dd->chan->dma, dd->chan->index, dd->z_config); if (ret < 0) return ret; - ret = dma_start(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_start(dd->chan->dma, dd->chan->index); if (ret < 0) return ret; @@ -1361,11 +1361,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, * as soon as possible. */ #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = dma_stop(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_stop(dd->chan->dma, dd->chan->index); if (ret) { comp_warn(dev, "dma was stopped earlier"); ret = 0; @@ -1375,11 +1375,11 @@ static int dai_comp_trigger_internal(struct dai_data *dd, struct comp_dev *dev, case COMP_TRIGGER_PAUSE: comp_dbg(dev, "PAUSE"); #if CONFIG_COMP_DAI_STOP_TRIGGER_ORDER_REVERSE - ret = dma_suspend(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); dai_trigger_op(dd->dai, cmd, dev->direction); #else dai_trigger_op(dd->dai, cmd, dev->direction); - ret = dma_suspend(dd->chan->dma->z_dev, dd->chan->index); + ret = sof_dma_suspend(dd->chan->dma, dd->chan->index); #endif break; case COMP_TRIGGER_PRE_START: @@ -1471,7 +1471,7 @@ static int dai_comp_trigger(struct comp_dev *dev, int cmd) /* get status from dma and check for xrun */ static int dai_get_status(struct comp_dev *dev, struct dai_data *dd, struct dma_status *stat) { - int ret = dma_get_status(dd->chan->dma->z_dev, dd->chan->index, stat); + int ret = sof_dma_get_status(dd->chan->dma, dd->chan->index, stat); #if CONFIG_XRUN_NOTIFICATIONS_ENABLE if (ret == -EPIPE && !dd->xrun_notification_sent) { struct ipc_msg *notify = ipc_notification_pool_get(IPC4_RESOURCE_EVENT_SIZE); @@ -1582,7 +1582,7 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, #endif for (i = 0; i < num_endpoints; i++) { - ret = dma_reload(dd[i]->chan->dma->z_dev, dd[i]->chan->index, 0, 0, 0); + ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, 0); if (ret < 0) { dai_report_xrun(dd[i], dev, 0); return ret; @@ -1608,10 +1608,10 @@ int dai_zephyr_multi_endpoint_copy(struct dai_data **dd, struct comp_dev *dev, status = dai_dma_multi_endpoint_cb(dd[i], dev, frames, multi_endpoint_buffer); if (status == SOF_DMA_CB_STATUS_END) - dma_stop(dd[i]->chan->dma->z_dev, dd[i]->chan->index); + sof_dma_stop(dd[i]->chan->dma, dd[i]->chan->index); copy_bytes = frames * audio_stream_frame_bytes(&dd[i]->dma_buffer->stream); - ret = dma_reload(dd[i]->chan->dma->z_dev, dd[i]->chan->index, 0, 0, copy_bytes); + ret = sof_dma_reload(dd[i]->chan->dma, dd[i]->chan->index, copy_bytes); if (ret < 0) { dai_report_xrun(dd[i], dev, copy_bytes); return ret; @@ -1800,7 +1800,7 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "nothing to copy, src_frames: %u, sink_frames: %u", src_frames, sink_frames); #endif - dma_reload(dd->chan->dma->z_dev, dd->chan->index, 0, 0, 0); + sof_dma_reload(dd->chan->dma, dd->chan->index, 0); return 0; } @@ -1810,9 +1810,9 @@ int dai_common_copy(struct dai_data *dd, struct comp_dev *dev, pcm_converter_fun comp_warn(dev, "dai trigger copy failed"); if (dai_dma_cb(dd, dev, copy_bytes, converter) == SOF_DMA_CB_STATUS_END) - dma_stop(dd->chan->dma->z_dev, dd->chan->index); + sof_dma_stop(dd->chan->dma, dd->chan->index); - ret = dma_reload(dd->chan->dma->z_dev, dd->chan->index, 0, 0, copy_bytes); + ret = sof_dma_reload(dd->chan->dma, dd->chan->index, copy_bytes); if (ret < 0) { dai_report_xrun(dd, dev, copy_bytes); return ret; From c4df7a8962b6dd75231a4f9195a66d30e532be39 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 10 Dec 2025 21:01:54 +0200 Subject: [PATCH 5/8] zephyr: test: userspace: intel_hda_dma: add check for DMA channel Add test check that correct channel is retrieved with sof_dma_request_channel(). Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_intel_hda_dma.c | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/test/userspace/test_intel_hda_dma.c b/zephyr/test/userspace/test_intel_hda_dma.c index 4b5a3e80cdf2..971e23b83508 100644 --- a/zephyr/test/userspace/test_intel_hda_dma.c +++ b/zephyr/test/userspace/test_intel_hda_dma.c @@ -62,6 +62,7 @@ static void intel_hda_dma_user(void *p1, void *p2, void *p3) LOG_INF("configure DMA channel"); channel = sof_dma_request_channel(dma, TEST_CHANNEL); + zassert_equal(channel, TEST_CHANNEL); LOG_INF("sof_dma_request_channel: ret %d", channel); err = sof_dma_get_attribute(dma, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, From 5e1416cefc561bc200ae1e66bf0c7dc948ecec16 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 10 Dec 2025 21:03:30 +0200 Subject: [PATCH 6/8] zephyr: test: userspace: intel_hda_dma: limit which test suites are run Prepare for other users of the standalone boot test infrastructure and do not run all tests in the sys init hook of "userspace_intel_hda_dma". Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_intel_hda_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr/test/userspace/test_intel_hda_dma.c b/zephyr/test/userspace/test_intel_hda_dma.c index 971e23b83508..dd54e6d85f2a 100644 --- a/zephyr/test/userspace/test_intel_hda_dma.c +++ b/zephyr/test/userspace/test_intel_hda_dma.c @@ -235,7 +235,7 @@ ZTEST_SUITE(userspace_intel_hda_dma, NULL, NULL, NULL, NULL, NULL); */ static int run_tests(void) { - ztest_run_all(NULL, false, 1, 1); + ztest_run_test_suite(userspace_intel_hda_dma, false, 1, 1, NULL); return 0; } From 0a2d5021c6f4cb436b32b4d5fdfd360ec8f87929 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 28 Nov 2025 13:28:47 +0200 Subject: [PATCH 7/8] zephyr: test: userspace: add test for Intel SSP DAI use from user-space Test the SOF SSP DAI interface from a user-space thread. Implement a similar flow to configure a pair of DMA instances and a DAI device set up for both TX and RX, as is done in src/audio/dai-zephyr.c. The test does not cover running data through the DAI, as this requires separate programming of the DMA from host side, which cannot be done in this level of tests. Signed-off-by: Kai Vehmanen --- zephyr/test/CMakeLists.txt | 7 +- zephyr/test/userspace/test_intel_ssp_dai.c | 348 +++++++++++++++++++++ 2 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 zephyr/test/userspace/test_intel_ssp_dai.c diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index 767d0984d1c5..d7a2bbece5bd 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -4,8 +4,11 @@ if (CONFIG_SOF_BOOT_TEST) ) endif() -if (CONFIG_SOF_BOOT_TEST_STANDALONE) - if (CONFIG_DT_HAS_INTEL_ADSP_HDA_HOST_IN_ENABLED AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) +if (CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) + if (CONFIG_DT_HAS_INTEL_ADSP_HDA_HOST_IN_ENABLED) zephyr_library_sources(userspace/test_intel_hda_dma.c) endif() + if (CONFIG_DT_HAS_INTEL_ADSP_HDA_SSP_CAP_ENABLED) + zephyr_library_sources(userspace/test_intel_ssp_dai.c) + endif() endif() diff --git a/zephyr/test/userspace/test_intel_ssp_dai.c b/zephyr/test/userspace/test_intel_ssp_dai.c new file mode 100644 index 000000000000..6c700c3839bb --- /dev/null +++ b/zephyr/test/userspace/test_intel_ssp_dai.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2025 Intel Corporation. + */ + +/* + * Test case for user-space use of the SOF DMA interface. The tests + * covers all key interfaces of DMA and DAI, testing their use from + * a user-space threads. Due to hardware constraints, the actual DMA + * transfers cannot be tested as this would require cooperation with a + * host entity that would manage the HDA link DMA in sync with the DP + * test case. Test does check all programming can be done and no + * errors are raised from the drivers. Valid configuration blobs are + * passed, to fully exercise the drivers interfaces. + + * Requirements for host side test execution environment: + * - I2S offload must be enabled on host side (HDAMLI2S) to allow + * the DAI driver to access hardware registers. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../../../src/audio/copier/dai_copier.h" +#include "../../../../zephyr/drivers/dai/intel/ssp/ssp.h" + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 8192 +#define HD_DMA_BUF_ALIGN 128 +#define TEST_BUF_SIZE (2*HD_DMA_BUF_ALIGN) +#define TEST_CHANNEL_OUT 3 +#define TEST_CHANNEL_IN 4 +#define SSP_DEVICE ssp00 + +static struct k_thread user_thread; +static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); + +static K_SEM_DEFINE(ipc_sem_wake_user, 0, 1); +static K_SEM_DEFINE(ipc_sem_wake_kernel, 0, 1); + +static int call_dai_set_ssp_v3_config_48k_2ch_32bit(const struct device *dai_dev) +{ + union hdalink_cfg link_cfg; + const uint8_t stream_id = 0; + + link_cfg.full = 0; + link_cfg.part.dir = DAI_DIR_TX; + link_cfg.part.stream = stream_id; + + struct dai_config common_config = { + .type = DAI_INTEL_SSP_NHLT, + .dai_index = 0, + .channels = 2, + .rate = 48000, + .format = DAI_CBC_CFC | DAI_PROTO_I2S | DAI_INVERSION_NB_NF, + .options = 0, + .word_size = 32, + .block_size = 0, + .link_config = link_cfg.full, + .tdm_slot_group = 0 + }; + + /* + * There are no suitable struct definitions to create these + * config objects, so we have to define a custom type that + * includes the common header, a single MDIV entry, one TLV + * entry and the link_ctl struct. These are normally part of + * ACPI NHLT and can be alternatively created with alsa-utils + * nhlt plugin. + */ + struct { + struct dai_intel_ipc4_ssp_configuration_blob_ver_3_0 b; + uint32_t mdivr0; + uint32_t type; + uint32_t size; + struct ssp_intel_link_ctl link_ctl; + } __packed blob30; + + memset(&blob30, 0, sizeof(blob30)); + /* DAI config blob header for SSP v3 */ + blob30.b.version = SSP_BLOB_VER_3_0; + blob30.b.size = sizeof(blob30); + /* I2S config matching sof-ptl-nocodec.tplg (32bit/48kHz/2ch) */ + blob30.b.i2s_ssp_config.ssc0 = 0x81d0077f; + blob30.b.i2s_ssp_config.ssc1 = 0xd0400004; + blob30.b.i2s_ssp_config.sscto = 0; + blob30.b.i2s_ssp_config.sspsp = 0x02200000; + blob30.b.i2s_ssp_config.ssc2 = 0x00004002; + blob30.b.i2s_ssp_config.sspsp2 = 0; + blob30.b.i2s_ssp_config.ssc3 = 0; + blob30.b.i2s_ssp_config.ssioc = 0x00000020; + /* clock control settings matching sof-ptl-nocodec.tplg */ + blob30.b.i2s_mclk_control.mdivctlr = 0x00010001; + blob30.b.i2s_mclk_control.mdivrcnt = 1; + /* variable-size section of clock control, one entry for mdivr */ + blob30.mdivr0 = 0xfff; + /* aux-data with one TLV entry for link-clk-source */ + blob30.type = SSP_LINK_CLK_SOURCE; + blob30.size = sizeof(struct ssp_intel_link_ctl); + blob30.link_ctl.clock_source = 1; + + return dai_config_set(dai_dev, &common_config, &blob30, sizeof(blob30)); +} + +static void intel_ssp_dai_user(void *p1, void *p2, void *p3) +{ + struct dma_block_config dma_block_cfg; + struct sof_dma *dma_in, *dma_out; + uint8_t data_buf_out[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN); + uint8_t data_buf_in[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN); + uint32_t addr_align = 0; + const struct device *dai_dev; + struct dai_properties dai_props; + struct dma_config config; + struct dma_status stat; + int err, channel_out, channel_in; + + zassert_true(k_is_user_context()); + + /* + * note: this gets a pointer to kernel memory this thread + * cannot access + */ + dma_in = sof_dma_get(SOF_DMA_DIR_DEV_TO_MEM, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED); + dma_out = sof_dma_get(SOF_DMA_DIR_MEM_TO_DEV, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED); + + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + + LOG_INF("create a DAI device for %s", STRINGIFY(SSP_DEVICE)); + + dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE)); + err = dai_probe(dai_dev); + zassert_equal(err, 0); + + channel_out = sof_dma_request_channel(dma_out, TEST_CHANNEL_OUT); + zassert_equal(channel_out, TEST_CHANNEL_OUT); + LOG_INF("sof_dma_request_channel (out): ret ch %d", channel_out); + channel_in = sof_dma_request_channel(dma_in, TEST_CHANNEL_IN); + zassert_equal(channel_in, TEST_CHANNEL_IN); + LOG_INF("sof_dma_request_channel (in): ret ch %d", channel_in); + + err = sof_dma_get_attribute(dma_out, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT, + &addr_align); + zassert_equal(err, 0); + zassert_true(addr_align == HD_DMA_BUF_ALIGN); + + /* set up a DMA transfer */ + memset(&dma_block_cfg, 0, sizeof(dma_block_cfg)); + + err = dai_get_properties_copy(dai_dev, DAI_DIR_TX, 0, &dai_props); + zassert_equal(err, 0); + + LOG_INF("dai_get_properties_copy (TX), ret %d, fifo %u", err, dai_props.fifo_address); + + dma_block_cfg.dest_address = dai_props.fifo_address; /* dai fifo */ + dma_block_cfg.source_address = (uintptr_t)data_buf_out; + dma_block_cfg.block_size = sizeof(data_buf_out); + + memset(&config, 0, sizeof(config)); + config.channel_direction = MEMORY_TO_PERIPHERAL; + config.block_count = 1; + config.head_block = &dma_block_cfg; + config.source_data_size = 4; + config.dest_data_size = 4; + + err = sof_dma_config(dma_out, channel_out, &config); + zassert_equal(err, 0); + + err = dai_get_properties_copy(dai_dev, DAI_DIR_RX, 0, &dai_props); + zassert_equal(err, 0); + LOG_INF("dai_get_properties_copy (RX), ret %d, fifo %u", err, dai_props.fifo_address); + + dma_block_cfg.dest_address = (uintptr_t)data_buf_in; + dma_block_cfg.source_address = dai_props.fifo_address; /* dai fifo */ + dma_block_cfg.block_size = sizeof(data_buf_in); + + config.channel_direction = PERIPHERAL_TO_MEMORY; + config.block_count = 1; + + err = sof_dma_config(dma_in, channel_in, &config); + zassert_equal(err, 0, "dma-config error"); + + err = call_dai_set_ssp_v3_config_48k_2ch_32bit(dai_dev); + zassert_equal(err, 0); + LOG_INF("DAI configuration ready, sync with kernel on start"); + + k_sem_give(&ipc_sem_wake_kernel); + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + LOG_INF("start DMA test and transfer data"); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_PRE_START); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_PRE_START); + zassert_equal(err, 0); + LOG_INF("dai_trigger RX+TX PRE_START done"); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status ( dma_in/start):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_get_status(dma_out, channel_out, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status (dma_out/start):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_start(dma_in, channel_in); + zassert_equal(err, 0); + + err = sof_dma_start(dma_out, channel_out); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_START); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_START); + zassert_equal(err, 0); + LOG_INF("DMAs and DAIs started."); + + k_sleep(K_USEC(10)); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + /* after start, there should be at least some free data */ + zassert_true(stat.free > 0); + zassert_true(stat.pending_length < TEST_BUF_SIZE); + LOG_INF("sof_dma_get_status ( dma_in/run):\tpend %3u free %3u", + stat.pending_length, stat.free); + + err = sof_dma_reload(dma_in, channel_in, sizeof(data_buf_in)); + zassert_equal(err, 0); + + err = sof_dma_get_status(dma_in, channel_in, &stat); + zassert_equal(err, 0); + /* after reload, there should be at least some data pending */ + zassert_true(stat.free < TEST_BUF_SIZE); + zassert_true(stat.pending_length > 0); + + err = sof_dma_get_status(dma_out, channel_out, &stat); + zassert_equal(err, 0); + LOG_INF("sof_dma_get_status (dma_out/run):\tpend %3u free %3u", + stat.pending_length, stat.free); + zassert_true(stat.free < TEST_BUF_SIZE); + zassert_true(stat.pending_length > 0); + + LOG_INF("DMA setup done, asking host to clean up "); + k_sem_give(&ipc_sem_wake_kernel); + k_sem_take(&ipc_sem_wake_user, K_FOREVER); + LOG_INF("Cleaning up resources"); + + err = sof_dma_stop(dma_out, channel_out); + zassert_equal(err, 0); + + err = sof_dma_stop(dma_in, channel_in); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_STOP); + zassert_equal(err, 0); + + err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_STOP); + zassert_equal(err, 0); + + sof_dma_release_channel(dma_out, channel_out); + + sof_dma_release_channel(dma_in, channel_in); + + err = dai_remove(dai_dev); + zassert_equal(err, 0); + + sof_dma_put(dma_in); + sof_dma_put(dma_out); + + LOG_INF("Cleanup successful, terminating user thread."); + + k_sem_give(&ipc_sem_wake_kernel); +} + +static void intel_ssp_dai_kernel(void) +{ + const struct device *dma_out, *dma_in; + const struct device *dai_dev; + + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + intel_ssp_dai_user, NULL, NULL, NULL, + -1, K_USER, K_FOREVER); + + k_thread_access_grant(&user_thread, &ipc_sem_wake_user); + k_thread_access_grant(&user_thread, &ipc_sem_wake_kernel); + + dma_out = DEVICE_DT_GET(DT_NODELABEL(hda_link_out)); + dma_in = DEVICE_DT_GET(DT_NODELABEL(hda_link_in)); + dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE)); + + k_thread_access_grant(&user_thread, dma_out); + k_thread_access_grant(&user_thread, dma_in); + k_thread_access_grant(&user_thread, dai_dev); + + k_thread_start(&user_thread); + + LOG_INF("user started, waiting for it to be ready"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("user ready, starting HDA test"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("transfer done, grant permission to clean up"); + + k_sem_give(&ipc_sem_wake_user); + k_sem_take(&ipc_sem_wake_kernel, K_FOREVER); + + LOG_INF("test done, terminate user thread"); + + k_thread_join(&user_thread, K_FOREVER); +} + +ZTEST(userspace_intel_dai_ssp, dai_ssp_loopback_setup) +{ + intel_ssp_dai_kernel(); + + ztest_test_pass(); +} + +ZTEST_SUITE(userspace_intel_dai_ssp, NULL, NULL, NULL, NULL, NULL); + +/** + * SOF main has booted up and IPC handling is stopped. + * Run test suites with ztest_run_all. + */ +static int run_tests(void) +{ + ztest_run_test_suite(userspace_intel_dai_ssp, false, 1, 1, NULL); + return 0; +} + +SYS_INIT(run_tests, APPLICATION, 99); From d5ec70d6084f2b9532bfe983173c4a125a491358 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 10 Dec 2025 21:41:49 +0200 Subject: [PATCH 8/8] zephyr: test: userspace: update README to cover new tests Update the README to cover common steps for building and running tests, and a separate section to introduce the available test cases. Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/zephyr/test/userspace/README.md b/zephyr/test/userspace/README.md index 55807eecb329..46119f3753db 100644 --- a/zephyr/test/userspace/README.md +++ b/zephyr/test/userspace/README.md @@ -1,10 +1,19 @@ -intel_hda_dma test ------------------- +User-space interface tests for Intel ADSP +----------------------------------------- -This is a standalone test to exercise the Intel HDA DMA host interface -from a userspace Zephyr thread. -Build with ("ptl" example): +This folder contains multiple tests to exercise Intel DSP device interfaces +from a user-space Zephyr thread. +Available tests: +- test_intel_hda_dma.c + - Test Intel HDA DMA host interface from a userspace + Zephyr thread. Use cavstool.py as host runner. +- test_intel_ssp_dai.c + - Test Zephyr DAI interface, together with SOF DMA + wrapper from a user thread. Mimics the call flows done in + sof/src/audio/dai-zephyr.c. Use cavstool.py as host runner. + +Building for Intel Panther Lake: ./scripts/xtensa-build-zephyr.py --cmake-args=-DCONFIG_SOF_BOOT_TEST_STANDALONE=y \ --cmake-args=-DCONFIG_SOF_USERSPACE_INTERFACE_DMA=y \ -o app/overlays/ptl/userspace_overlay.conf -o app/winconsole_overlay.conf ptl