From 318aecb86f0e37134da4fa93ed30558de4819702 Mon Sep 17 00:00:00 2001 From: Andy Ross Date: Fri, 14 Jan 2022 10:11:08 -0800 Subject: [PATCH] tests/boards: Add intel_adsp board integration/smoke test As Zephyr begins to absorb drivers for these platforms that had previously been managed by the SOF app, there's a need for a rapid board-specific smoke test to use during development. This starts with the smp_boot_delay test (itself a unit test for a SOF-derived feature) and adds a host IPC case (that needs to match code in cavstool.py on the other side of the PCI bus!). It will grow more features over time as needed. Signed-off-by: Andy Ross --- soc/xtensa/intel_adsp/tools/cavstool.py | 38 ++++++- tests/boards/intel_adsp/smoke/CMakeLists.txt | 7 ++ tests/boards/intel_adsp/smoke/prj.conf | 4 + tests/boards/intel_adsp/smoke/src/hostipc.c | 108 +++++++++++++++++++ tests/boards/intel_adsp/smoke/src/main.c | 17 +++ tests/boards/intel_adsp/smoke/src/smpboot.c | 92 ++++++++++++++++ tests/boards/intel_adsp/smoke/src/tests.h | 24 +++++ tests/boards/intel_adsp/smoke/testcase.yaml | 3 + 8 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 tests/boards/intel_adsp/smoke/CMakeLists.txt create mode 100644 tests/boards/intel_adsp/smoke/prj.conf create mode 100644 tests/boards/intel_adsp/smoke/src/hostipc.c create mode 100644 tests/boards/intel_adsp/smoke/src/main.c create mode 100644 tests/boards/intel_adsp/smoke/src/smpboot.c create mode 100644 tests/boards/intel_adsp/smoke/src/tests.h create mode 100644 tests/boards/intel_adsp/smoke/testcase.yaml diff --git a/soc/xtensa/intel_adsp/tools/cavstool.py b/soc/xtensa/intel_adsp/tools/cavstool.py index b809088a875..3cc692e62f4 100755 --- a/soc/xtensa/intel_adsp/tools/cavstool.py +++ b/soc/xtensa/intel_adsp/tools/cavstool.py @@ -82,7 +82,12 @@ def map_regs(): (bar4_mem, bar4_mmap) = bar_map(pcidir, 4) dsp = Regs(bar4_mem) dsp.ADSPCS = 0x00004 + dsp.HIPCTDR = 0x00040 if cavs15 else 0x000c0 + dsp.HIPCTDA = 0x000c4 # 1.8+ only + dsp.HIPCTDD = 0x00044 if cavs15 else 0x000c8 dsp.HIPCIDR = 0x00048 if cavs15 else 0x000d0 + dsp.HIPCIDA = 0x000d4 # 1.8+ only + dsp.HIPCIDD = 0x0004c if cavs15 else 0x000d8 dsp.SRAM_FW_STATUS = 0x80000 # Start of first SRAM window dsp.freeze() @@ -246,7 +251,7 @@ def load_firmware(fw_file): | (0x01 << 24) # type = PURGE_FW | (1 << 14) # purge_fw = 1 | (stream_idx << 9)) # dma_id - log.info(f"Sending IPC command, HIPCR = 0x{ipcval:x}") + log.info(f"Sending IPC command, HIPIDR = 0x{ipcval:x}") dsp.HIPCIDR = ipcval log.info(f"Starting DMA, FW_STATUS = 0x{dsp.SRAM_FW_STATUS:x}") @@ -315,6 +320,32 @@ def winstream_read(last_seq): if start1 == start and seq1 == seq: return (seq, result.decode("utf-8")) +async def ipc_delay_done(): + await asyncio.sleep(0.1) + dsp.HIPCTDA = 1<<31 + +# Super-simple command language, driven by the test code on the DSP +def ipc_command(data, ext_data): + send_msg = False + done = True + if data == 0: # noop, with synchronous DONE + pass + elif data == 1: # async command: signal DONE after a delay (on 1.8+) + if not cavs15: + done = False + asyncio.ensure_future(ipc_delay_done()) + elif data == 2: # echo back ext_data as a message command + send_msg = True + else: + log.warning(f"cavstool: Unrecognized IPC command 0x{data:x} ext 0x{ext_data:x}") + + dsp.HIPCTDR = 1<<31 # Ack local interrupt, also signals DONE on v1.5 + if done and not cavs15: + dsp.HIPCTDA = 1<<31 # Signal done + if send_msg: + dsp.HIPCIDD = ext_data + dsp.HIPCIDR = (1<<31) | ext_data + async def main(): global hda, sd, dsp, hda_ostream_id try: @@ -345,6 +376,11 @@ async def main(): if output: sys.stdout.write(output) sys.stdout.flush() + if dsp.HIPCTDR & 0x80000000: + ipc_command(dsp.HIPCTDR & ~0x80000000, dsp.HIPCTDD) + if dsp.HIPCIDA & 0x80000000: + dsp.HIPCIDA = 1<<31 # must ACK any DONE interrupts that arrive! + ap = argparse.ArgumentParser(description="DSP loader/logger tool") ap.add_argument("-q", "--quiet", action="store_true", diff --git a/tests/boards/intel_adsp/smoke/CMakeLists.txt b/tests/boards/intel_adsp/smoke/CMakeLists.txt new file mode 100644 index 00000000000..1e5d474eea1 --- /dev/null +++ b/tests/boards/intel_adsp/smoke/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(intel_adsp) + +target_sources(app PRIVATE src/main.c src/smpboot.c src/hostipc.c) diff --git a/tests/boards/intel_adsp/smoke/prj.conf b/tests/boards/intel_adsp/smoke/prj.conf new file mode 100644 index 00000000000..30b23341a23 --- /dev/null +++ b/tests/boards/intel_adsp/smoke/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ZTEST=y +CONFIG_SMP=y +CONFIG_SMP_BOOT_DELAY=y +CONFIG_SCHED_CPU_MASK=y diff --git a/tests/boards/intel_adsp/smoke/src/hostipc.c b/tests/boards/intel_adsp/smoke/src/hostipc.c new file mode 100644 index 00000000000..bf0013634cb --- /dev/null +++ b/tests/boards/intel_adsp/smoke/src/hostipc.c @@ -0,0 +1,108 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "tests.h" + +/* The cavstool.py script that launched us listens for a very simple + * set of IPC commands to help test. Pass one of the following values + * as the "data" argument to cavs_ipc_send_message(): + * + * 0: The host takes no action, but signals DONE to complete the message + * 1: The host returns done after a short timeout + * 2: The host issues a new message with the ext_data value as its "data" + */ +enum cavstool_cmd { SIGNAL_DONE, ASYNC_DONE_DELAY, RETURN_MSG }; + +static volatile bool done_flag, msg_flag; + +#define RETURN_MSG_SYNC_VAL 0x12345 +#define RETURN_MSG_ASYNC_VAL 0x54321 + +static bool ipc_message(const struct device *dev, void *arg, + uint32_t data, uint32_t ext_data) +{ + zassert_is_null(arg, "wrong message arg"); + zassert_equal(data, ext_data, "unequal message data/ext_data"); + zassert_true(data == RETURN_MSG_SYNC_VAL || + data == RETURN_MSG_ASYNC_VAL, "unexpected msg data"); + msg_flag = true; + return data == RETURN_MSG_SYNC_VAL; +} + +static void ipc_done(const struct device *dev, void *arg) +{ + zassert_is_null(arg, "wrong done arg"); + zassert_false(done_flag, "done called unexpectedly"); + done_flag = true; +} + +void test_host_ipc(void) +{ + bool ret; + + cavs_ipc_set_message_handler(CAVS_HOST_DEV, ipc_message, NULL); + cavs_ipc_set_done_handler(CAVS_HOST_DEV, ipc_done, NULL); + + /* Just send a message and wait for it to complete */ + printk("Simple message send...\n"); + done_flag = false; + ret = cavs_ipc_send_message(CAVS_HOST_DEV, SIGNAL_DONE, 0); + zassert_true(ret, "send failed"); + WAIT_FOR(cavs_ipc_is_complete(CAVS_HOST_DEV)); + WAIT_FOR(done_flag); + + /* Request the host to return a message which we will complete + * immediately. + */ + printk("Return message request...\n"); + done_flag = false; + msg_flag = false; + ret = cavs_ipc_send_message(CAVS_HOST_DEV, RETURN_MSG, + RETURN_MSG_SYNC_VAL); + zassert_true(ret, "send failed"); + WAIT_FOR(done_flag); + WAIT_FOR(cavs_ipc_is_complete(CAVS_HOST_DEV)); + WAIT_FOR(msg_flag); + + /* Do exactly the same thing again to check for state bugs + * (e.g. failing to signal done on one side or the other) + */ + printk("Return message request 2...\n"); + done_flag = false; + msg_flag = false; + ret = cavs_ipc_send_message(CAVS_HOST_DEV, RETURN_MSG, + RETURN_MSG_SYNC_VAL); + zassert_true(ret, "send failed"); + WAIT_FOR(done_flag); + WAIT_FOR(cavs_ipc_is_complete(CAVS_HOST_DEV)); + WAIT_FOR(msg_flag); + + /* Same, but we'll complete it asynchronously (1.8+ only) */ + if (!IS_ENABLED(CONFIG_SOC_SERIES_INTEL_CAVS_V15)) { + printk("Return message request, async...\n"); + done_flag = false; + msg_flag = false; + ret = cavs_ipc_send_message(CAVS_HOST_DEV, RETURN_MSG, + RETURN_MSG_ASYNC_VAL); + zassert_true(ret, "send failed"); + WAIT_FOR(done_flag); + WAIT_FOR(cavs_ipc_is_complete(CAVS_HOST_DEV)); + WAIT_FOR(msg_flag); + cavs_ipc_complete(CAVS_HOST_DEV); + } + + /* Now make a synchronous call with (on the host) a delayed + * completion and make sure the interrupt fires and wakes us + * up. (On 1.5 a delay isn't possible and this will complete + * immediately). + */ + printk("Synchronous message send...\n"); + done_flag = false; + ret = cavs_ipc_send_message_sync(CAVS_HOST_DEV, ASYNC_DONE_DELAY, 0, K_FOREVER); + zassert_true(ret, "send failed"); + zassert_true(done_flag, "done interrupt failed to fire"); + zassert_true(cavs_ipc_is_complete(CAVS_HOST_DEV), "sync message incomplete"); +} diff --git a/tests/boards/intel_adsp/smoke/src/main.c b/tests/boards/intel_adsp/smoke/src/main.c new file mode 100644 index 00000000000..e596805413a --- /dev/null +++ b/tests/boards/intel_adsp/smoke/src/main.c @@ -0,0 +1,17 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "tests.h" + +void test_main(void) +{ + ztest_test_suite(intel_adsp, + ztest_unit_test(test_smp_boot_delay), + ztest_unit_test(test_post_boot_ipi), + ztest_unit_test(test_host_ipc) + ); + + ztest_run_test_suite(intel_adsp); +} diff --git a/tests/boards/intel_adsp/smoke/src/smpboot.c b/tests/boards/intel_adsp/smoke/src/smpboot.c new file mode 100644 index 00000000000..993477a1c7a --- /dev/null +++ b/tests/boards/intel_adsp/smoke/src/smpboot.c @@ -0,0 +1,92 @@ +/* Copyright (c) 2022 Intel Corporation. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "tests.h" + +/* Experimentally 10ms is enough time to get the second CPU to run on + * all known platforms. + */ +#define CPU_START_DELAY 10000 + +/* IPIs happen much faster than CPU startup */ +#define CPU_IPI_DELAY 100 + +BUILD_ASSERT(CONFIG_SMP); +BUILD_ASSERT(CONFIG_SMP_BOOT_DELAY); + +#define STACKSZ 2048 +char stack[STACKSZ]; + +volatile bool mp_flag; + +struct k_thread cpu_thr; +K_THREAD_STACK_DEFINE(thr_stack, STACKSZ); + +extern void z_smp_start_cpu(int id); + +static void thread_fn(void *a, void *b, void *c) +{ + int cpuid = (int) a; + + zassert_true(cpuid == 0 || cpuid == arch_curr_cpu()->id, + "running on wrong cpu"); + mp_flag = true; +} + +/* Needless to say: since this is starting the SMP CPUs, it needs to + * be the first test run! + */ +void test_smp_boot_delay(void) +{ + if (CONFIG_MP_NUM_CPUS < 2) { + ztest_test_skip(); + } + + for (int i = 1; i < CONFIG_MP_NUM_CPUS; i++) { + printk("Launch cpu%d\n", i); + mp_flag = false; + k_thread_create(&cpu_thr, thr_stack, K_THREAD_STACK_SIZEOF(thr_stack), + thread_fn, (void *)i, NULL, NULL, + 0, 0, K_FOREVER); + k_thread_cpu_mask_clear(&cpu_thr); + k_thread_cpu_mask_enable(&cpu_thr, i); + k_thread_start(&cpu_thr); + + /* Make sure that thread has not run (because the cpu is halted) */ + k_busy_wait(CPU_START_DELAY); + zassert_false(mp_flag, "cpu %d must not be running yet", i); + + /* Start the second CPU */ + z_smp_start_cpu(i); + + /* Verify the thread ran */ + k_busy_wait(CPU_START_DELAY); + zassert_true(mp_flag, "cpu %d did not start", i); + + k_thread_abort(&cpu_thr); + } +} + +void test_post_boot_ipi(void) +{ + if (CONFIG_MP_NUM_CPUS < 2) { + ztest_test_skip(); + } + + /* Spawn the same thread to do the same thing, but this time + * expect that the thread is going to run synchronously on + * another CPU as soon as its created. Intended to test + * whether IPIs were correctly set up on the runtime-launched + * CPU. + */ + mp_flag = false; + k_thread_create(&cpu_thr, thr_stack, K_THREAD_STACK_SIZEOF(thr_stack), + thread_fn, (void *)0, NULL, NULL, + 1, 0, K_NO_WAIT); + + k_busy_wait(CPU_IPI_DELAY); + zassert_true(mp_flag, "cpu did not start thread via IPI"); +} diff --git a/tests/boards/intel_adsp/smoke/src/tests.h b/tests/boards/intel_adsp/smoke/src/tests.h new file mode 100644 index 00000000000..72bf19c4bfe --- /dev/null +++ b/tests/boards/intel_adsp/smoke/src/tests.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_TESTS_INTEL_ADSP_TESTS_H +#define ZEPHYR_TESTS_INTEL_ADSP_TESTS_H + +/* Helper to escape from infinite polling loops with a test failure + * instead of a hang. Spins with a relaxation loop so that it works + * in interrupt context and doesn't stress shared resources like SRAM + */ +#define WAIT_FOR(expr) do { \ + int i; \ + for (i = 0; !(expr) && i < 10000; i++) { \ + for (volatile int j = 0; j < 1000; j++) { \ + } \ + } \ + zassert_true(i < 10000, "timeout waiting for %s", #expr); \ + } while (0) + +void test_post_boot_ipi(void); +void test_smp_boot_delay(void); +void test_host_ipc(void); + +#endif /* ZEPHYR_TESTS_INTEL_ADSP_TESTS_H */ diff --git a/tests/boards/intel_adsp/smoke/testcase.yaml b/tests/boards/intel_adsp/smoke/testcase.yaml new file mode 100644 index 00000000000..4f9aab4dda1 --- /dev/null +++ b/tests/boards/intel_adsp/smoke/testcase.yaml @@ -0,0 +1,3 @@ +tests: + boards.intel_adsp: + platform_allow: intel_adsp_cavs15 intel_adsp_cavs18 intel_adsp_cavs20 intel_adsp_cavs25