|
| 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