Skip to content

Commit 2c0cb8a

Browse files
committed
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 <kai.vehmanen@linux.intel.com>
1 parent dc38a1d commit 2c0cb8a

File tree

2 files changed

+348
-2
lines changed

2 files changed

+348
-2
lines changed

zephyr/test/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ if (CONFIG_SOF_BOOT_TEST)
55
endif()
66

77
if (CONFIG_SOF_BOOT_TEST_STANDALONE)
8-
if (CONFIG_DT_HAS_INTEL_ADSP_HDA_HOST_IN_ENABLED AND CONFIG_SOF_USERSPACE_INTERFACE_DMA)
9-
zephyr_library_sources(userspace/test_intel_hda_dma.c)
8+
if (CONFIG_DT_HAS_INTEL_ADSP_HDA_SSP_CAP_ENABLED AND CONFIG_USERSPACE)
9+
zephyr_library_sources(userspace/test_intel_ssp_dai.c)
1010
endif()
1111
endif()
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
/*
3+
* Copyright(c) 2025 Intel Corporation.
4+
*/
5+
6+
/*
7+
* Test case for user-space use of the SOF DMA interface. The tests
8+
* covers all key interfaces of DMA and DAI, testing their use from
9+
* a user-space threads. Due to hardware constraints, the actual DMA
10+
* transfers cannot be tested as this would require cooperation with a
11+
* host entity that would manage the HDA link DMA in sync with the DP
12+
* test case. Test does check all programming can be done and no
13+
* errors are raised from the drivers. Valid configuration blobs are
14+
* passed, to fully exercise the drivers interfaces.
15+
16+
* Requirements for host side test execution environment:
17+
* - IS2 offload must be enabled on host side (HDAMLI2S) to allow
18+
* the DAI driver to access hardware registers.
19+
*/
20+
21+
#include <sof/boot_test.h>
22+
23+
#include <zephyr/kernel.h>
24+
#include <zephyr/ztest.h>
25+
#include <zephyr/logging/log.h>
26+
#include <sof/lib/dai.h>
27+
#include <sof/lib/uuid.h>
28+
#include <sof/lib/dma.h>
29+
#include <sof/audio/component_ext.h>
30+
31+
#include "../../../src/audio/copier/dai_copier.h"
32+
#include "../../../../zephyr/drivers/dai/intel/ssp/ssp.h"
33+
34+
LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG);
35+
36+
#define USER_STACKSIZE 8192
37+
#define HD_DMA_BUF_ALIGN 128
38+
#define TEST_BUF_SIZE (2*HD_DMA_BUF_ALIGN)
39+
#define TEST_CHANNEL_OUT 1
40+
#define TEST_CHANNEL_IN 2
41+
#define SSP_DEVICE ssp00
42+
43+
static struct k_thread user_thread;
44+
static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE);
45+
46+
static K_SEM_DEFINE(ipc_sem_wake_user, 0, 1);
47+
static K_SEM_DEFINE(ipc_sem_wake_kernel, 0, 1);
48+
49+
static int call_dai_set_ssp_v3_config_48k_2ch_32bit(const struct device *dai_dev)
50+
{
51+
union hdalink_cfg link_cfg;
52+
const uint8_t stream_id = 0;
53+
54+
link_cfg.full = 0;
55+
link_cfg.part.dir = DAI_DIR_TX;
56+
link_cfg.part.stream = stream_id;
57+
58+
struct dai_config common_config = {
59+
.type = DAI_INTEL_SSP_NHLT,
60+
.dai_index = 0,
61+
.channels = 2,
62+
.rate = 48000,
63+
.format = DAI_CBC_CFC | DAI_PROTO_I2S | DAI_INVERSION_NB_NF,
64+
.options = 0,
65+
.word_size = 32,
66+
.block_size = 0,
67+
.link_config = link_cfg.full,
68+
.tdm_slot_group = 0
69+
};
70+
71+
/*
72+
* There are no suitable struct definitions to create these
73+
* config objects, so we have to define a custom type that
74+
* includes the common header, a single MDIV entry, one TLV
75+
* entry and the link_ctl struct. These are normally part of
76+
* ACPI NHLT and can be alternatively created with alsa-utils
77+
* nhlt plugin.
78+
*/
79+
struct {
80+
struct dai_intel_ipc4_ssp_configuration_blob_ver_3_0 b;
81+
uint32_t mdivr0;
82+
uint32_t type;
83+
uint32_t size;
84+
struct ssp_intel_link_ctl link_ctl;
85+
} __packed blob30;
86+
87+
memset(&blob30, 0, sizeof(blob30));
88+
/* DAI config blob header for SSP v3 */
89+
blob30.b.version = SSP_BLOB_VER_3_0;
90+
blob30.b.size = sizeof(blob30);
91+
/* I2S config */
92+
blob30.b.i2s_ssp_config.ssc0 = 0x81d0077f;
93+
blob30.b.i2s_ssp_config.ssc1 = 0xd0400004;
94+
blob30.b.i2s_ssp_config.sscto = 0;
95+
blob30.b.i2s_ssp_config.sspsp = 0x02200000;
96+
blob30.b.i2s_ssp_config.ssc2 = 0x00004002;
97+
blob30.b.i2s_ssp_config.sspsp2 = 0;
98+
blob30.b.i2s_ssp_config.ssc3 = 0;
99+
blob30.b.i2s_ssp_config.ssioc = 0x00000020;
100+
/* clock control settings */
101+
blob30.b.i2s_mclk_control.mdivctlr = 0x00010001;
102+
blob30.b.i2s_mclk_control.mdivrcnt = 1;
103+
/* variable-size section of clock control, one entry for mdivr */
104+
blob30.mdivr0 = 0xfff; /* 6/0xfff; */
105+
/* aux-data with one TLV entry for link-clk-source */
106+
blob30.type = SSP_LINK_CLK_SOURCE;
107+
blob30.size = sizeof(struct ssp_intel_link_ctl);
108+
blob30.link_ctl.clock_source = 1;
109+
110+
return dai_config_set(dai_dev, &common_config, &blob30, sizeof(blob30));
111+
}
112+
113+
static void intel_ssp_dai_user(void *p1, void *p2, void *p3)
114+
{
115+
struct dma_block_config dma_block_cfg;
116+
struct sof_dma *dma_in, *dma_out;
117+
uint8_t data_buf_out[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN);
118+
uint8_t data_buf_in[TEST_BUF_SIZE] __aligned(HD_DMA_BUF_ALIGN);
119+
uint32_t addr_align = 0;
120+
const struct device *dai_dev;
121+
struct dai_properties dai_props;
122+
struct dma_config config;
123+
struct dma_status stat;
124+
int err, channel_out, channel_in;
125+
126+
zassert_true(k_is_user_context());
127+
128+
/*
129+
* note: this gets a pointer to kernel memory this thread
130+
* cannot access
131+
*/
132+
dma_in = sof_dma_get(SOF_DMA_DIR_DEV_TO_MEM, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED);
133+
dma_out = sof_dma_get(SOF_DMA_DIR_MEM_TO_DEV, 0, SOF_DMA_DEV_SSP, SOF_DMA_ACCESS_SHARED);
134+
135+
k_sem_take(&ipc_sem_wake_user, K_FOREVER);
136+
137+
LOG_INF("create a DAI device for %s", STRINGIFY(SSP_DEVICE));
138+
139+
dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE));
140+
err = dai_probe(dai_dev);
141+
zassert_equal(err, 0);
142+
143+
channel_out = sof_dma_request_channel(dma_out, TEST_CHANNEL_OUT);
144+
LOG_INF("sof_dma_request_channel (out): ret ch %d", channel_out);
145+
channel_in = sof_dma_request_channel(dma_in, TEST_CHANNEL_IN);
146+
LOG_INF("sof_dma_request_channel (in): ret ch %d", channel_in);
147+
148+
err = sof_dma_get_attribute(dma_out, DMA_ATTR_BUFFER_ADDRESS_ALIGNMENT,
149+
&addr_align);
150+
zassert_equal(err, 0);
151+
zassert_true(addr_align == HD_DMA_BUF_ALIGN);
152+
153+
/* set up a DMA transfer */
154+
memset(&dma_block_cfg, 0, sizeof(dma_block_cfg));
155+
156+
err = dai_get_properties_copy(dai_dev, DAI_DIR_TX, 0, &dai_props);
157+
zassert_equal(err, 0);
158+
159+
LOG_INF("dai_get_properties_copy (TX), ret %d, fifo %u", err, dai_props.fifo_address);
160+
161+
dma_block_cfg.dest_address = dai_props.fifo_address; /* dai fifo */
162+
dma_block_cfg.source_address = (uintptr_t)data_buf_out;
163+
dma_block_cfg.block_size = sizeof(data_buf_out);
164+
165+
memset(&config, 0, sizeof(config));
166+
config.channel_direction = MEMORY_TO_PERIPHERAL;
167+
config.block_count = 1;
168+
config.head_block = &dma_block_cfg;
169+
config.source_data_size = 4;
170+
config.dest_data_size = 4;
171+
172+
err = sof_dma_config(dma_out, channel_out, &config);
173+
zassert_equal(err, 0);
174+
175+
err = dai_get_properties_copy(dai_dev, DAI_DIR_RX, 0, &dai_props);
176+
zassert_equal(err, 0);
177+
LOG_INF("dai_get_properties_copy (RX), ret %d, fifo %u", err, dai_props.fifo_address);
178+
179+
dma_block_cfg.dest_address = (uintptr_t)data_buf_in;
180+
dma_block_cfg.source_address = dai_props.fifo_address; /* dai fifo */
181+
dma_block_cfg.block_size = sizeof(data_buf_in);
182+
183+
config.channel_direction = PERIPHERAL_TO_MEMORY;
184+
config.block_count = 1;
185+
186+
err = sof_dma_config(dma_in, channel_in, &config);
187+
zassert_equal(err, 0, "dma-config error");
188+
189+
err = call_dai_set_ssp_v3_config_48k_2ch_32bit(dai_dev);
190+
zassert_equal(err, 0);
191+
LOG_INF("DAI configuration ready, sync with kernel on start");
192+
193+
k_sem_give(&ipc_sem_wake_kernel);
194+
k_sem_take(&ipc_sem_wake_user, K_FOREVER);
195+
LOG_INF("start DMA test and transfer data");
196+
197+
err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_PRE_START);
198+
zassert_equal(err, 0);
199+
200+
err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_PRE_START);
201+
zassert_equal(err, 0);
202+
LOG_INF("dai_trigger RX+TX PRE_START done");
203+
204+
err = sof_dma_get_status(dma_in, channel_in, &stat);
205+
zassert_equal(err, 0);
206+
LOG_INF("sof_dma_get_status ( dma_in/start):\tpend %3u free %3u",
207+
stat.pending_length, stat.free);
208+
209+
err = sof_dma_get_status(dma_out, channel_out, &stat);
210+
zassert_equal(err, 0);
211+
LOG_INF("sof_dma_get_status (dma_out/start):\tpend %3u free %3u",
212+
stat.pending_length, stat.free);
213+
214+
err = sof_dma_start(dma_in, channel_in);
215+
zassert_equal(err, 0);
216+
217+
err = sof_dma_start(dma_out, channel_out);
218+
zassert_equal(err, 0);
219+
220+
err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_START);
221+
zassert_equal(err, 0);
222+
223+
err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_START);
224+
zassert_equal(err, 0);
225+
LOG_INF("DMAs and DAIs started.");
226+
227+
k_sleep(K_USEC(10));
228+
229+
err = sof_dma_get_status(dma_in, channel_in, &stat);
230+
zassert_equal(err, 0);
231+
/* after start, there should be at least some free data */
232+
zassert_true(stat.free > 0);
233+
zassert_true(stat.pending_length < TEST_BUF_SIZE);
234+
LOG_INF("sof_dma_get_status ( dma_in/run):\tpend %3u free %3u",
235+
stat.pending_length, stat.free);
236+
237+
err = sof_dma_reload(dma_in, channel_in, sizeof(data_buf_in));
238+
zassert_equal(err, 0);
239+
240+
err = sof_dma_get_status(dma_in, channel_in, &stat);
241+
zassert_equal(err, 0);
242+
/* after reload, there should be at least some data pending */
243+
zassert_true(stat.free < TEST_BUF_SIZE);
244+
zassert_true(stat.pending_length > 0);
245+
246+
err = sof_dma_get_status(dma_out, channel_out, &stat);
247+
zassert_equal(err, 0);
248+
LOG_INF("sof_dma_get_status (dma_out/run):\tpend %3u free %3u",
249+
stat.pending_length, stat.free);
250+
zassert_true(stat.free < TEST_BUF_SIZE);
251+
zassert_true(stat.pending_length > 0);
252+
253+
LOG_INF("DMA setup done, asking host to clean up ");
254+
k_sem_give(&ipc_sem_wake_kernel);
255+
k_sem_take(&ipc_sem_wake_user, K_FOREVER);
256+
LOG_INF("Cleaning up resources");
257+
258+
err = sof_dma_stop(dma_out, channel_out);
259+
zassert_equal(err, 0);
260+
261+
err = sof_dma_stop(dma_in, channel_in);
262+
zassert_equal(err, 0);
263+
264+
err = dai_trigger(dai_dev, DAI_DIR_TX, DAI_TRIGGER_STOP);
265+
zassert_equal(err, 0);
266+
267+
err = dai_trigger(dai_dev, DAI_DIR_RX, DAI_TRIGGER_STOP);
268+
zassert_equal(err, 0);
269+
270+
sof_dma_release_channel(dma_out, channel_out);
271+
272+
sof_dma_release_channel(dma_in, channel_out);
273+
274+
err = dai_remove(dai_dev);
275+
zassert_equal(err, 0);
276+
277+
sof_dma_put(dma_in);
278+
sof_dma_put(dma_out);
279+
280+
LOG_INF("Cleanup successful, terminating user thread.");
281+
282+
k_sem_give(&ipc_sem_wake_kernel);
283+
}
284+
285+
static void intel_ssp_dai_kernel(void)
286+
{
287+
const struct device *dma_out, *dma_in;
288+
const struct device *dai_dev;
289+
290+
k_thread_create(&user_thread, user_stack, USER_STACKSIZE,
291+
intel_ssp_dai_user, NULL, NULL, NULL,
292+
-1, K_USER, K_FOREVER);
293+
294+
k_thread_access_grant(&user_thread, &ipc_sem_wake_user);
295+
k_thread_access_grant(&user_thread, &ipc_sem_wake_kernel);
296+
297+
dma_out = DEVICE_DT_GET(DT_NODELABEL(hda_link_out));
298+
dma_in = DEVICE_DT_GET(DT_NODELABEL(hda_link_in));
299+
dai_dev = DEVICE_DT_GET(DT_NODELABEL(SSP_DEVICE));
300+
301+
k_thread_access_grant(&user_thread, dma_out);
302+
k_thread_access_grant(&user_thread, dma_in);
303+
k_thread_access_grant(&user_thread, dai_dev);
304+
305+
k_thread_start(&user_thread);
306+
307+
LOG_INF("user started, waiting for it to be ready");
308+
309+
k_sem_give(&ipc_sem_wake_user);
310+
k_sem_take(&ipc_sem_wake_kernel, K_FOREVER);
311+
312+
LOG_INF("user ready, starting HDA test");
313+
314+
k_sem_give(&ipc_sem_wake_user);
315+
k_sem_take(&ipc_sem_wake_kernel, K_FOREVER);
316+
317+
LOG_INF("transfer done, grant permission to clean up");
318+
319+
k_sem_give(&ipc_sem_wake_user);
320+
k_sem_take(&ipc_sem_wake_kernel, K_FOREVER);
321+
322+
LOG_INF("test done, terminate user thread");
323+
324+
k_thread_join(&user_thread, K_FOREVER);
325+
}
326+
327+
ZTEST(userspace_intel_dai_ssp, dai_ssp_loopback_setup)
328+
{
329+
intel_ssp_dai_kernel();
330+
331+
ztest_test_pass();
332+
}
333+
334+
ZTEST_SUITE(userspace_intel_dai_ssp, NULL, NULL, NULL, NULL, NULL);
335+
336+
/**
337+
* SOF main has booted up and IPC handling is stopped.
338+
* Run test suites with ztest_run_all.
339+
*/
340+
static int run_tests(void)
341+
{
342+
ztest_run_all(NULL, false, 1, 1);
343+
return 0;
344+
}
345+
346+
SYS_INIT(run_tests, APPLICATION, 99);

0 commit comments

Comments
 (0)