Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/boards/native_sim.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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
18 changes: 16 additions & 2 deletions app/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -73,14 +73,28 @@ 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)
{
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)
Expand Down
112 changes: 82 additions & 30 deletions scripts/sof-qemu-run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})...")
Expand All @@ -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 = ""
Expand Down Expand Up @@ -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()
55 changes: 44 additions & 11 deletions scripts/sof-qemu-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,60 @@
# 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 <<EOF
Usage: $0 [OPTIONS] [TARGET]

Options:
-h, --help Show this help message and exit
--valgrind Run under valgrind

TARGET The QEMU target name (e.g., native_sim, qemu_xtensa, qemu_xtensa_mmu).
The build directory will be resolved to 'build-<TARGET>' 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"

# 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
4 changes: 4 additions & 0 deletions scripts/xtensa-build-zephyr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
21 changes: 17 additions & 4 deletions src/ipc/ipc-helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}

Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
8 changes: 4 additions & 4 deletions src/math/numbers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
Loading
Loading