From 8d35a01419323ffbc81e131dcc27bb1230092540 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 12 Mar 2026 20:09:27 +0000 Subject: [PATCH 1/7] zephyr: app: board: add native_sim board target Add native_sim board configuration and support in the build script. This allows building and running tests on the host using Zephyr's native_sim target. native_sim leverages the POSIX architecture, but the libfuzzer support specifically requires CONFIG_ARCH_POSIX_LIBFUZZER to be set. Therefore, this wraps fuzzer-specific code in ipc.c and the build of fuzz.c behind this config to allow clean compilation on the standard native_sim board. Signed-off-by: Liam Girdwood --- app/boards/native_sim.conf | 4 ++++ scripts/xtensa-build-zephyr.py | 4 ++++ src/platform/posix/ipc.c | 6 ++++++ zephyr/CMakeLists.txt | 3 +++ 4 files changed, 17 insertions(+) create mode 100644 app/boards/native_sim.conf diff --git a/app/boards/native_sim.conf b/app/boards/native_sim.conf new file mode 100644 index 000000000000..6df3eb89b648 --- /dev/null +++ b/app/boards/native_sim.conf @@ -0,0 +1,4 @@ +CONFIG_ZEPHYR_POSIX=y +CONFIG_SYS_HEAP_BIG_ONLY=y +CONFIG_ZEPHYR_NATIVE_DRIVERS=y +CONFIG_ZEPHYR_LOG=y diff --git a/scripts/xtensa-build-zephyr.py b/scripts/xtensa-build-zephyr.py index 8a7a866d7aad..12a61e0e3ccc 100755 --- a/scripts/xtensa-build-zephyr.py +++ b/scripts/xtensa-build-zephyr.py @@ -234,6 +234,10 @@ class PlatformConfig: "zephyr", "qemu_xtensa/dc233c/mmu", "", "", "zephyr" ), + "native_sim" : PlatformConfig( + "zephyr", "native_sim", + "", "", "zephyr" + ), } platform_configs = platform_configs_all.copy() diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 701686e6ff97..14e56da530db 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -14,6 +14,7 @@ SOF_DEFINE_REG_UUID(ipc_task_posix); static struct ipc *global_ipc; +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER // Not an ISR, called from the native_posix fuzz interrupt. Left // alone for general hygiene. This is how a IPC interrupt would look // if we had one. @@ -131,6 +132,7 @@ static void fuzz_isr(const void *arg) posix_ipc_isr(NULL); } +#endif // This API is... confounded by its history. With IPC3, the job of // this function is to get a newly-received IPC message header (!) @@ -172,12 +174,14 @@ int ipc_platform_compact_read_msg(struct ipc_cmd_hdr *hdr, int words) // Re-raise the interrupt if there's still fuzz data to process void ipc_platform_complete_cmd(struct ipc *ipc) { +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER extern void posix_sw_set_pending_IRQ(unsigned int IRQn); if (fuzz_in_sz > 0) { posix_fuzz_sz = 0; posix_sw_set_pending_IRQ(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); } +#endif } int ipc_platform_send_msg(const struct ipc_msg *msg) @@ -200,8 +204,10 @@ void ipc_platform_send_msg_direct(const struct ipc_msg *msg) int platform_ipc_init(struct ipc *ipc) { +#ifdef CONFIG_ARCH_POSIX_LIBFUZZER IRQ_CONNECT(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ, 0, fuzz_isr, NULL, 0); irq_enable(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); +#endif global_ipc = ipc; schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(ipc_task_posix_uuid), diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 233c93f3e070..be87d9759228 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -484,6 +484,9 @@ zephyr_library_sources_ifdef(CONFIG_ZEPHYR_POSIX ${SOF_PLATFORM_PATH}/posix/dai.c ${SOF_PLATFORM_PATH}/posix/ipc.c ${SOF_PLATFORM_PATH}/posix/posix.c +) + +zephyr_library_sources_ifdef(CONFIG_ARCH_POSIX_LIBFUZZER ${SOF_PLATFORM_PATH}/posix/fuzz.c ) From ce9d6188f327284d91682b4bc491c686ba4e682d Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Thu, 12 Mar 2026 20:18:15 +0000 Subject: [PATCH 2/7] scripts: sof-qemu-run: add native_sim and valgrind support Add native_sim board target to the sof-qemu-run scripts, and add an option to additionally run it under valgrind. The default build directory is set to ../build-native_sim Signed-off-by: Liam Girdwood --- scripts/sof-qemu-run.py | 112 +++++++++++++++++++++++++++++----------- scripts/sof-qemu-run.sh | 55 ++++++++++++++++---- 2 files changed, 126 insertions(+), 41 deletions(-) diff --git a/scripts/sof-qemu-run.py b/scripts/sof-qemu-run.py index fc985e254d51..0344639955a8 100755 --- a/scripts/sof-qemu-run.py +++ b/scripts/sof-qemu-run.py @@ -76,9 +76,12 @@ def main(): parser = argparse.ArgumentParser(description="Run QEMU via west and automatically decode crashes.") parser.add_argument("--build-dir", default="build", help="Path to the build directory containing zephyr.elf, linker.cmd, etc. Defaults to 'build'.") parser.add_argument("--log-file", default="qemu-run.log", help="Path to save the QEMU output log. Defaults to 'qemu-run.log'.") + parser.add_argument("--valgrind", action="store_true", help="Run the executable under Valgrind (only valid for native_sim).") args = parser.parse_args() # Make absolute path just in case + # The shell script cd's into `args.build_dir` before executing us, so `args.build_dir` might be relative to the shell script's pwd. + # We resolve it relative to the python script's original invocation cwd. build_dir = os.path.abspath(args.build_dir) print(f"Starting QEMU test runner. Monitoring for crashes (Build Dir: {args.build_dir})...") @@ -91,7 +94,53 @@ def main(): print("Please ensure you have sourced the Zephyr environment (e.g., source zephyr-env.sh).") sys.exit(1) - child = pexpect.spawn(west_path, ["-v", "build", "-t", "run"], encoding='utf-8') + # Detect the board configuration from CMakeCache.txt + is_native_sim = False + + cmake_cache = os.path.join(build_dir, "CMakeCache.txt") + + if os.path.isfile(cmake_cache): + with open(cmake_cache, "r") as f: + for line in f: + if line.startswith("CACHED_BOARD:STRING=") or line.startswith("BOARD:STRING="): + if "native_sim" in line.split("=", 1)[1].strip(): + is_native_sim = True + break + + # Determine execution command + # If the user is running the python script directly from outside the workspace, we need to provide the source directory. + # But if west finds it automatically (or we are in the build dir), providing `-s` might clear the CACHED_BOARD config. + run_cmd = [west_path, "-v", "build", "-d", build_dir] + + # Check if we are physically sitting inside the build directory + if os.path.abspath(".") != os.path.abspath(build_dir): + # We need to explicitly supply the app source to prevent west from crashing + app_source_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "app")) + run_cmd.extend(["-s", app_source_dir]) + + run_cmd.extend(["-t", "run"]) + + if args.valgrind: + if not is_native_sim: + print("[sof-qemu-run] Error: --valgrind is only supported for the native_sim board.") + sys.exit(1) + + print("[sof-qemu-run] Rebuilding before valgrind...") + subprocess.run([west_path, "build", "-d", build_dir], check=True) + + valgrind_path = shutil.which("valgrind") + if not valgrind_path: + print("[sof-qemu-run] Error: 'valgrind' command not found in PATH.") + sys.exit(1) + + exe_path = os.path.join(build_dir, "zephyr", "zephyr.exe") + if not os.path.isfile(exe_path): + print(f"[sof-qemu-run] Error: Executable not found at {exe_path}") + sys.exit(1) + + run_cmd = [valgrind_path, exe_path] + + child = pexpect.spawn(run_cmd[0], run_cmd[1:], encoding='utf-8') # We will accumulate output to check for crashes full_output = "" @@ -157,36 +206,39 @@ def main(): run_sof_crash_decode(build_dir, full_output) else: - print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...") - - # We need to send Ctrl-A c to enter the monitor - if child.isalive(): - child.send("\x01c") # Ctrl-A c - try: - # Wait for (qemu) prompt - child.expect(r"\(qemu\)", timeout=5) - # Send "info registers" - child.sendline("info registers") - # Wait for the next prompt - child.expect(r"\(qemu\)", timeout=5) - - info_regs_output = child.before - print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n") - - # Quit qemu safely - child.sendline("quit") - child.expect(pexpect.EOF, timeout=2) - child.close() - - # Run the decoder on the intercepted register output - run_sof_crash_decode(build_dir, info_regs_output) - except pexpect.TIMEOUT: - print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?") - child.close(force=True) - except pexpect.EOF: - print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.") + if is_native_sim: + print("\n[sof-qemu-run] No crash detected. (Skipping QEMU monitor interaction for native_sim)") else: - print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.") + print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...") + + # We need to send Ctrl-A c to enter the monitor + if child.isalive(): + child.send("\x01c") # Ctrl-A c + try: + # Wait for (qemu) prompt + child.expect(r"\(qemu\)", timeout=5) + # Send "info registers" + child.sendline("info registers") + # Wait for the next prompt + child.expect(r"\(qemu\)", timeout=5) + + info_regs_output = child.before + print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n") + + # Quit qemu safely + child.sendline("quit") + child.expect(pexpect.EOF, timeout=2) + child.close() + + # Run the decoder on the intercepted register output + run_sof_crash_decode(build_dir, info_regs_output) + except pexpect.TIMEOUT: + print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?") + child.close(force=True) + except pexpect.EOF: + print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.") + else: + print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.") if __name__ == "__main__": main() diff --git a/scripts/sof-qemu-run.sh b/scripts/sof-qemu-run.sh index e1ece1dd5125..ef915fbb1318 100755 --- a/scripts/sof-qemu-run.sh +++ b/scripts/sof-qemu-run.sh @@ -2,17 +2,54 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2026 Intel Corporation. All rights reserved. -# Define the build directory from the first argument (or default) -BUILD_DIR="${1:-build}" +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")" + +TARGET="native_sim" +VALGRIND_ARG="" + +usage() { + cat <' relative + to the workspace. (default: native_sim) +EOF + exit 0 +} + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + usage + ;; + --valgrind) + VALGRIND_ARG="--valgrind" + ;; + *) + # Allow users who pass the directory name directly out of habit to still work + if [[ "$1" == build-* ]]; then + TARGET="${1#build-}" + else + TARGET="$1" + fi + ;; + esac + shift +done + +BUILD_DIR="${SOF_WORKSPACE}/build-${TARGET}" # Find and source the zephyr environment script, typically via the sof-venv wrapper # or directly if running in a known zephyrproject layout. # We will use the existing helper sof-venv.sh to get the right environment. -# Get the directory of this script -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")" - # Use the SOF workspace to locate the virtual environment VENV_DIR="$SOF_WORKSPACE/.venv" echo "Using SOF environment at $SOF_WORKSPACE" @@ -20,9 +57,5 @@ echo "Using SOF environment at $SOF_WORKSPACE" # start the virtual environment source ${VENV_DIR}/bin/activate -# Execute the QEMU runner from within the correct build directory -cd "${BUILD_DIR}" || exit 1 - # Finally run the python script which will now correctly inherit 'west' from the sourced environment. -python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}" - +python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}" $VALGRIND_ARG From 15e156dea071f57d7d6f3fd21089f4b51cdd7d07 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Mar 2026 12:12:28 +0000 Subject: [PATCH 3/7] tests: ztest: execute unit tests on native_sim boot Currently, native_sim standalone boot tests were skipped because the main application overrode test_main() without invoking the test suite runner for non-QEMU targets. Also, the math arithmetic unit tests were not compiled into the main image structure natively, reducing test coverage for native execution. This patch: - Injects standalone basic arithmetic math unit tests into the zephyr test library when building in standalone boot test mode. - Modifies CMake definitions and math configuration macros to ensure the underlying vector algorithms are compiled globally when Ztest is enabled. - Updates the main test loop invocation to explicitly execute Ztests on all boards including native_sim. - Enables standalone boot tests in the native_sim Kconfig. Signed-off-by: Liam Girdwood --- app/boards/native_sim.conf | 2 ++ app/src/main.c | 4 +++- src/math/CMakeLists.txt | 6 ++++++ src/math/numbers.c | 8 ++++---- src/platform/posix/ipc.c | 3 ++- zephyr/test/CMakeLists.txt | 17 +++++++++++++++++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/boards/native_sim.conf b/app/boards/native_sim.conf index 6df3eb89b648..225f109009e3 100644 --- a/app/boards/native_sim.conf +++ b/app/boards/native_sim.conf @@ -2,3 +2,5 @@ CONFIG_ZEPHYR_POSIX=y CONFIG_SYS_HEAP_BIG_ONLY=y CONFIG_ZEPHYR_NATIVE_DRIVERS=y CONFIG_ZEPHYR_LOG=y +CONFIG_ZTEST=y +CONFIG_SOF_BOOT_TEST_STANDALONE=y diff --git a/app/src/main.c b/app/src/main.c index 12cc3ffdc73a..e3149202b3b1 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -77,10 +77,12 @@ static inline void qemu_xtensa_exit(int status) void test_main(void) { sof_app_main(); -#if CONFIG_SOF_BOOT_TEST && defined(QEMU_BOOT_TESTS) +#if CONFIG_SOF_BOOT_TEST sof_run_boot_tests(); +#if defined(QEMU_BOOT_TESTS) qemu_xtensa_exit(0); #endif +#endif } #else int main(void) diff --git a/src/math/CMakeLists.txt b/src/math/CMakeLists.txt index a52263006295..e3825fad6a03 100644 --- a/src/math/CMakeLists.txt +++ b/src/math/CMakeLists.txt @@ -106,6 +106,12 @@ if(zephyr) ### Zephyr ### ${base_files} ) + if(CONFIG_ZTEST) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/numbers.c + PROPERTIES COMPILE_DEFINITIONS "CONFIG_NUMBERS_VECTOR_FIND=1;CONFIG_NUMBERS_NORM=1" + ) + endif() + else() ### library, e.g. testbench or plugin ### add_local_sources(sof ${base_files}) diff --git a/src/math/numbers.c b/src/math/numbers.c index df4f822c749a..c391f5925bb2 100644 --- a/src/math/numbers.c +++ b/src/math/numbers.c @@ -79,7 +79,7 @@ int gcd(int a, int b) EXPORT_SYMBOL(gcd); #endif /* USE_SOF_GCD */ -#if CONFIG_NUMBERS_VECTOR_FIND +#if CONFIG_NUMBERS_VECTOR_FIND || CONFIG_ZTEST /* This function searches from vec[] (of length vec_length) integer values * of n. The indexes to equal values is returned in idx[]. The function @@ -133,9 +133,9 @@ int32_t find_max_abs_int32(int32_t vec[], int vec_length) return SATP_INT32(amax); /* Amax is always a positive value */ } -#endif /* CONFIG_VECTOR_FIND */ +#endif /* CONFIG_VECTOR_FIND || CONFIG_ZTEST */ -#if CONFIG_NUMBERS_NORM +#if CONFIG_NUMBERS_NORM || CONFIG_ZTEST /* Count the left shift amount to normalize a 32 bit signed integer value * without causing overflow. Input value 0 will result to 31. @@ -155,7 +155,7 @@ int norm_int32(int32_t val) } EXPORT_SYMBOL(norm_int32); -#endif /* CONFIG_NORM */ +#endif /* CONFIG_NORM || CONFIG_ZTEST */ /** * Basic CRC-32 implementation, based on pseudo-code from diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 14e56da530db..52e291abe12c 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -24,7 +24,8 @@ static void posix_ipc_isr(void *arg) } // External symbols set up by the fuzzing layer -extern uint8_t *posix_fuzz_buf, posix_fuzz_sz; +extern const uint8_t *posix_fuzz_buf; +extern size_t posix_fuzz_sz; // Lots of space. Should really synchronize with the -max_len // parameter to libFuzzer (defaults to 4096), but that requires diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index c5b66c83bbaa..6cffcca131cc 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -23,3 +23,20 @@ endif() if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_LL) zephyr_library_sources(userspace/test_ll_task.c) endif() + +if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_ZTEST) + set(MATH_ZTEST_SOURCES + ../../test/ztest/unit/math/basic/arithmetic/test_crc32_ztest.c + ../../test/ztest/unit/math/basic/arithmetic/test_find_equal_int16_ztest.c + ../../test/ztest/unit/math/basic/arithmetic/test_find_max_abs_int32_ztest.c + ../../test/ztest/unit/math/basic/arithmetic/test_find_min_int16_ztest.c + ../../test/ztest/unit/math/basic/arithmetic/test_gcd_ztest.c + ../../test/ztest/unit/math/basic/arithmetic/test_norm_int32_ztest.c + ) + + zephyr_library_sources(${MATH_ZTEST_SOURCES}) + + set_source_files_properties(${MATH_ZTEST_SOURCES} + PROPERTIES COMPILE_DEFINITIONS "CONFIG_NUMBERS_VECTOR_FIND=1;CONFIG_NUMBERS_NORM=1;UNIT_TEST=1" + ) +endif() From dda21425e0e7019316cb9d23c2c79f334f2e58e8 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Mar 2026 14:06:34 +0000 Subject: [PATCH 4/7] zephyr: alloc: Map native_sim heap to the C library When building the firmware for native_sim, debugging allocations with host machine tools like Valgrind is constrained due to Zephyr's internal minimal libc tracking the heap manually via static pools. By bypassing Zephyr's memory interception on native_sim using nsi_host_malloc, dynamically tracked memory can surface appropriately to Valgrind memory checkers without causing a libc heap pool panic. Signed-off-by: Liam Girdwood --- zephyr/lib/alloc.c | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index f95f66fbdb7f..c44d309c2500 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -153,6 +153,7 @@ extern char _end[], _heap_sentry[]; static struct k_heap sof_heap; +#if !defined(CONFIG_BOARD_NATIVE_SIM) /** * Checks whether pointer is from a given heap memory. * @param heap Pointer to a heap. @@ -171,6 +172,7 @@ static bool is_heap_pointer(const struct k_heap *heap, void *ptr) return ((POINTER_TO_UINT(ptr) >= heap_start) && (POINTER_TO_UINT(ptr) < heap_end)); } +#endif #if CONFIG_SOF_USERSPACE_USE_SHARED_HEAP static struct k_heap shared_buffer_heap; @@ -382,6 +384,7 @@ struct k_heap *sof_sys_heap_get(void) return &sof_heap; } +#if !defined(CONFIG_BOARD_NATIVE_SIM) static void *heap_alloc_aligned(struct k_heap *h, size_t min_align, size_t bytes) { k_spinlock_key_t key; @@ -451,7 +454,106 @@ static void heap_free(struct k_heap *h, void *mem) k_spin_unlock(&h->lock, key); } +#endif + +#if defined(CONFIG_BOARD_NATIVE_SIM) +#include +#include + +void *rmalloc_align(uint32_t flags, size_t bytes, uint32_t alignment) +{ + void *ptr; + void *raw; + + if (alignment < sizeof(void *)) + alignment = sizeof(void *); + + raw = nsi_host_malloc(bytes + alignment + sizeof(void *)); + if (!raw) + return NULL; + + ptr = (void *)(((uintptr_t)raw + sizeof(void *) + alignment - 1) & ~(alignment - 1)); + ((void **)ptr)[-1] = raw; + + return ptr; +} +EXPORT_SYMBOL(rmalloc_align); + +void *rmalloc(uint32_t flags, size_t bytes) +{ + return rmalloc_align(flags, bytes, 0); +} +EXPORT_SYMBOL(rmalloc); + +void *rbrealloc_align(void *ptr, uint32_t flags, size_t bytes, + size_t old_bytes, uint32_t alignment) +{ + void *new_ptr; + + if (!ptr) + return rmalloc_align(flags, bytes, alignment); + + if (!bytes) { + void *raw = ((void **)ptr)[-1]; + + nsi_host_free(raw); + return NULL; + } + + new_ptr = rmalloc_align(flags, bytes, alignment); + if (!new_ptr) + return NULL; + + if (!(flags & SOF_MEM_FLAG_NO_COPY)) + memcpy_s(new_ptr, bytes, ptr, MIN(bytes, old_bytes)); + + void *raw_old = ((void **)ptr)[-1]; + + nsi_host_free(raw_old); + + return new_ptr; +} + +void *rzalloc(uint32_t flags, size_t bytes) +{ + void *ptr = rmalloc_align(flags, bytes, 0); + + if (ptr) + memset(ptr, 0, bytes); + return ptr; +} +EXPORT_SYMBOL(rzalloc); + +void *rballoc_align(uint32_t flags, size_t bytes, uint32_t align) +{ + return rmalloc_align(flags, bytes, align); +} +EXPORT_SYMBOL(rballoc_align); + +void rfree(void *ptr) +{ + if (!ptr) + return; + + void *raw = ((void **)ptr)[-1]; + + nsi_host_free(raw); +} +EXPORT_SYMBOL(rfree); + +void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) +{ + return rmalloc_align(flags, bytes, alignment); +} + +void sof_heap_free(struct k_heap *heap, void *addr) +{ + rfree(addr); +} + +#else void *rmalloc_align(uint32_t flags, size_t bytes, uint32_t alignment) { @@ -642,6 +744,8 @@ void sof_heap_free(struct k_heap *heap, void *addr) rfree(addr); } +#endif /* CONFIG_BOARD_NATIVE_SIM */ + static int heap_init(void) { sys_heap_init(&sof_heap.heap, heapmem, HEAPMEM_SIZE - SHARED_BUFFER_HEAP_MEM_SIZE); From 702de1cdc161a110bba62efbc891d39d60ff6d12 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 27 Mar 2026 17:26:21 +0000 Subject: [PATCH 5/7] shell: fix build error In file included from /home/lrg/work/sof2/sof/zephyr/sof_shell.c:14: /home/lrg/work/sof2/sof/zephyr/sof_shell.c: In function 'cmd_sof_module_heap_usage': /home/lrg/work/sof2/sof/zephyr/sof_shell.c:66:77: error: 'struct module_config' has no member named 'heap_bytes' 66 | icd->id, usage, hwm, comp_mod(icd->cd)->priv.cfg.heap_bytes); | ^ /home/lrg/work/sof2/zephyr/include/zephyr/shell/shell.h:1292:47: note: in definition of macro 'shell_print' 1292 | shell_fprintf_normal(_sh, _ft "\n", ##__VA_ARGS__) | ^~~~~~~~~~~ Signed-off-by: Liam Girdwood --- zephyr/sof_shell.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zephyr/sof_shell.c b/zephyr/sof_shell.c index f10a2c9275b5..9e90abe417bb 100644 --- a/zephyr/sof_shell.c +++ b/zephyr/sof_shell.c @@ -62,8 +62,8 @@ static int cmd_sof_module_heap_usage(const struct shell *sh, continue; usage = module_adapter_heap_usage(comp_mod(icd->cd), &hwm); - shell_print(sh, "comp id 0x%08x%9zu usage%9zu hwm %9zu max\tbytes", - icd->id, usage, hwm, comp_mod(icd->cd)->priv.cfg.heap_bytes); + shell_print(sh, "comp id 0x%08x%9zu usage%9zu hwm\tbytes", + icd->id, usage, hwm); } return 0; } From 9c5a57785aea744c1387456bca0b7ccd9d0e3c14 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 27 Mar 2026 18:56:53 +0000 Subject: [PATCH 6/7] qemu: exit for boot test only otherwise spin Keep spinning in case user needs to inspect status via monitor. Signed-off-by: Liam Girdwood --- app/src/main.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main.c b/app/src/main.c index e3149202b3b1..2135e7f27dca 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -57,7 +57,7 @@ static int sof_app_main(void) return 0; } -#if CONFIG_SOF_BOOT_TEST && defined(QEMU_BOOT_TESTS) +#if defined(QEMU_BOOT_TESTS) /* cleanly exit qemu so CI can continue and check test results */ static inline void qemu_xtensa_exit(int status) { @@ -73,6 +73,18 @@ static inline void qemu_xtensa_exit(int status) } #endif +#ifdef CONFIG_REBOOT +void sys_arch_reboot(int type) +{ +#if defined(QEMU_BOOT_TESTS) + qemu_xtensa_exit(type); +#endif + while (1) { + k_cpu_idle(); + } +} +#endif + #if CONFIG_ZTEST void test_main(void) { From 94ca99b070a9b9ca4f3cba2751eda62be997f9af Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 27 Mar 2026 21:26:15 +0000 Subject: [PATCH 7/7] ipc: fix component teardown crashes and memory leaks Fuzzing identified several segmentation faults and memory corruptions in ipc_comp_free() when processing malformed IPC messages or handling uninitialized component lists. This patch implements three defensive structural fixes to harden teardown logic: 1. Validates that `icd->cd` is not NULL before checking component state to avoid early NULL pointer dereferences on broken components. 2. Resolves a deliberate memory leak ("eat the resulting memory leak on error") when encountering uninitialized buffer lists. Instead of leaking the structure and leaving it hanging in `ipc->comp_list` (which triggers secondary use-after-free assertions and segfaults in subsequent IPC calls), the component is now properly removed and freed. 3. Moves `list_item_del(&icd->list)` to occur immediately before `comp_free(icd->cd)`. This guarantees that other tasks or scheduling interrupts cannot discover and access a partially destructed component via the global IPC list. Signed-off-by: Liam Girdwood --- src/ipc/ipc-helper.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/ipc/ipc-helper.c b/src/ipc/ipc-helper.c index 2f685b551747..c9107d9584c2 100644 --- a/src/ipc/ipc-helper.c +++ b/src/ipc/ipc-helper.c @@ -303,6 +303,13 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) return -ENODEV; } + if (!icd->cd) { + tr_err(&ipc_tr, "comp id: 0x%x cd is NULL", comp_id); + list_item_del(&icd->list); + rfree(icd); + return -EINVAL; + } + /* check core */ if (!cpu_is_me(icd->core)) return ipc_process_on_core(icd->core, false); @@ -324,12 +331,16 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) * (which is an invalid list!) if the component's * lifecycle hasn't reached that point. There's no * single place to ensure a valid/empty list, so we - * have to do it here and eat the resulting memory - * leak on error. Bug-free host drivers won't do - * this, this was found via fuzzing. + * have to do it here. + * Bug-free host drivers won't do this, this was found + * via fuzzing. */ tr_err(&ipc_tr, "uninitialized buffer lists on comp 0x%x\n", icd->id); + list_item_del(&icd->list); + comp_free(icd->cd); + icd->cd = NULL; + rfree(icd); return -EINVAL; } @@ -348,12 +359,14 @@ __cold int ipc_comp_free(struct ipc *ipc, uint32_t comp_id) irq_local_enable(flags); + /* remove from list first so it cannot be found while being freed */ + list_item_del(&icd->list); + /* free component and remove from list */ comp_free(icd->cd); icd->cd = NULL; - list_item_del(&icd->list); rfree(icd); return 0;