Repository navigation
[FEATURE] Partition SOF into DSP kernel and userspace source/include trees #11306
Description
Partitioning user/kernel code for trust boundary and later safety island. Claude generated.
Is your feature request related to a problem? Please describe.
SOF is moving LL pipelines, DP modules and IPC processing into Zephyr user
threads (CONFIG_SOF_USERSPACE_LL, CONFIG_SOF_USERSPACE_PROXY, the
handler-kernel.c / handler-user.c split, and the syscalls in zephyr/syscall/).
The source tree doesn't show that boundary, though:
- Kernel-only code (MMIO, IRQs,
k_thread_create,k_mem_domain_*, PM,
clocks, rawdma_*, IDC, LLEXT loading) sits next to code that must be
user-safe, often in the same file. Examples:ipc-common.c,
zephyr_domain.c,zephyr_ll.c,lib/dma.c,lib/dai.c. - User-facing headers pull in kernel headers.
component.hincludes
rtos/idc.h,sof/lib/dai.h(→zephyr/drivers/dai.h) and
schedule/schedule.h.audio_stream.hincludessof/lib/dma.h
(→zephyr/drivers/dma.h).rtos/alloc.hincludessof/lib/memory.h,
which includesplatform/lib/memory.h.sof/platform.his included by about 54
audio/math/ipc files. As a result, every component and every LLEXT module
(they inheritzephyr_interfaceinclude dirs) can see platform, DMA and DAI
internals. - There is no build-time enforcement, so you only find out that user code
calls a privileged API when it faults on hardware.
Describe the solution you'd like
Partition the sources and headers into DSP kernel and userspace parts,
and have the build enforce the split.
Definitions:
- Kernel = code that must run in supervisor mode.
- User = everything else. It must be user-safe: plain C, syscall-backed Zephyr
APIs, and SOF syscalls. - The directory says what the code is allowed to do, not where it runs in a
particular config. WithSOF_USERSPACE_LL=n, user code still runs in
supervisor mode, and that's fine.
Layout:
src/kernel/ # new: supervisor-only code, own CMake lib (sof_kernel)
include/sof/kernel/... # kernel-private headers -> #include <sof/kernel/...>
init/ idc/ library_manager/ logging/ platform/ drivers/ arch/ probe/
base_fw/ (base_fw*.c, intel_uaol.c) mic_privacy/
ipc/ (ipc-zephyr.c, ipc4/handler-kernel.c, notification*.c, ipc3/)
schedule/ lib/ debug/ audio/ # kernel halves of split files
src/{audio,math,module,samples,ipc,schedule,lib,debug}/ # user (what remains)
src/include/ # user-visible headers only
zephyr/ # Zephyr module root stays where it is (OS glue = kernel)
Decisions:
-
Include path. Kernel-private headers are included as
<sof/kernel/...>. They live insrc/kernel/include/, which is a PRIVATE
include dir of thesof_kernellibrary. It is not onzephyr_interface, so
neither user code nor LLEXT modules can include them, and a simple rule
(no<sof/kernel/in user sources) is checkable in CI and checkpatch. The host
ABI dirssrc/include/kernel/andsrc/include/user/(Linux driver and tools
ABI) are not touched. -
XTOS is deleted rather than moved:
xtos/, the XTOS firmware build,
XTOS-only platform, driver and arch code, and the olddebug/gdbstub.
Removing the legacy-driver path thatKconfig.xtosexists for
(dai-legacy.c,host-legacy.c, the DMA domain schedulers and
ll_schedule_xtos.c) depends on the remaining non-native platforms moving to
native Zephyr drivers and on the testbench no longer using the legacy
host/DAI components (see Phase 0b). -
zephyr/stays at the top level. It is the Zephyr module root and OS
glue, and is treated as kernel. -
IPC3 is kernel. There is no user IPC3 thread, and it uses raw DMA
(ipc3/dai.c,host-page-table.c). It moves tosrc/kernel/ipc/ipc3/. -
lib/notifier.cis deleted and replaced with Zephyr mechanisms. It
usesk_spin_lock+ IDC, so it can't be called from user mode, and its
callbacks run in the notifier's context. Per event:Event Live users after XTOS removal Replacement CPU_FREQ/SSP_FREQnone (ids are set in clock_info, but nothing is ever fired on Zephyr)delete DMA_COPY,DMA_IRQ,DMA_DOMAIN_CHANGElegacy drivers and DMA domains only ( dai-legacy.c,host-legacy.c,dw/dma.c, AMD/MTK legacy DMA,dma_*_chan_domain.c)deleted along with the legacy-driver path. Native drivers use Zephyr dma_config.dma_callbackDAI_TRIGGERdai-zephyr.cDAI-group sync trigger (irq_local_disable+notifier_event)the last DAI to trigger walks the group member list itself. The atomic section becomes a kernel syscall (e.g. sof_dai_group_trigger()) that holds the IRQ lockKPB_CLIENT_EVTgoogle_hotword_detect.c,detect_test.c→kpb.ck_msgq(ork_event+ shared payload) owned by kpb and granted to the pipeline memory domain. Both are syscall-backedMIC_PRIVACY_STATE_CHANGEmic_privacy_manager IRQ → copier.ck_event_post()from the ISR. Copier checks it withk_event_test()/K_NO_WAITin its copy path and reads the state through a syscall or a mapped partition. No callback runs in IRQ context
Enforcement: scripts/check_kernel_includes.py, which reads the ninja
dependency log of a build and reports kernel-only headers included by
user-space sources, classified in scripts/kernel_user_partition.txt. It starts
as report-only and becomes enforcing once the headers are moved.
Implementation is a series of PRs. Every commit builds in all CI configs. Directory
moves are git mv plus path fixups only, so history is preserved.
Checklist
Phase 0: guard rails, XTOS and ALSA plugin removal
0a: no platform loses support. Draft PR #11312 (one patch per item, under review):
- zephyr: remove the references to the deleted
hda-dma.candlps_wait.c - ci: remove the no-op XTOS reproducible build check (
test-repro-build.sh
exits 0 becausextensa-build-all.shno longer exists) - test: cmocka: remove the dead Xtensa (
xt-run) unit test build - test: cmocka: move the unit test defconfigs from
src/arch/xtensa/configs
totest/cmocka/configs - cmake: remove the legacy XTOS firmware build (top-level
add_executable(sof),
xtensa toolchain file, scan-build wrapper, XTOS-only per-platform/driver
CMake files, Chrome XTOS config overrides) - platform: amd: remove Renoir, Vangogh and ACP 6.3
- drivers: amd: remove the sources that no build uses
- platform: mediatek: remove the per-SoC XTOS dirs (
src/platform/mt81xx,
src/drivers/mediatek/mt8*,afe-sgen); Zephyr usessrc/platform/mtk - platform: imx: remove the XTOS DMA, DAI, interrupt and timer drivers
- drivers: dw: remove the DesignWare DMA, GPIO and SPI drivers (
CONFIG_DW
has no prompt and nothing selects it, soCONFIG_DW_DMAcannot be enabled) - drivers: remove the unused Intel HDA headers
- platform: posix: remove the unused
base_fw_platform.c - debug: remove the old SOF GDB stub (
CONFIG_GDB_DEBUG, cannot build for
any firmware target); the ZephyrCONFIG_GDBSTUBpath is unaffected - zephyr: move the legacy XTOS IRQ header into
zephyr/include; then remove
xtos/include - installer: remove the XTOS firmware installer
- docker: sof_builder: drop the XTOS crosstool-ng toolchains
- tools: plugin: remove the SOF ALSA plugin, its topology and the
shared-library module build (CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD) - library: always build the host library statically (drop
CONFIG_LIBRARY_STATIC) - scripts: add
check_kernel_includes.pyandkernel_user_partition.txt;
ci: run it report-only in the Zephyr workflow. Current baseline for PTL:
148 user-space sources include 28 kernel-only headers. -
src/trace: still built by the testbench (CONFIG_TRACEdefaults to y for
host builds). Decide whether to keep it as host-only trace or move the
testbench to a plain printf backend.
Verification for 0a: the non-debug sections of zephyr.elf are identical on all
15 platforms that build on main, and so are all 66 LLEXT modules. The libsof.a
symbol table is unchanged. testbench, tools and all cmocka configurations pass.
The full error sets of the platforms that already fail on main are unchanged.
0b: legacy driver path (needs the AMD and MediaTek maintainers). These platforms
do not build on main today, and CI does not build them:
acp_6_0,mt8186,mt8188,mt8195andmt8365fail inipc3/dai.cand
host-legacy.c.acp_7_xfails because Kconfig symbols are missing in the pinned Zephyr.mt8196fails because themediatek,sof-host-dmaDT node is missing.
Items:
- AMD
acp_6_0(Rembrandt): native Zephyr ACP DAI/DMA, andzephyr_ll.c
instead ofll_schedule_xtos.c+dma_multi_chan_domain.c - MTK
mt8186,mt8188,mt8195,mt8365:CONFIG_ZEPHYR_NATIVE_DRIVERS=y
(zephyr_mtk_*), as already done for mt8196 - testbench: stop building
host-legacy.canddai-legacy.c - Delete the legacy-driver path:
-
dai-legacy.c,host-legacy.c,dma-legacy.h - legacy AMD/MTK DMA drivers,
dummy-dma.c -
dma_*_chan_domain.c,zephyr_dma_domain.c,ll_schedule_xtos.c -
rtos/interrupt-legacy.h+src/drivers/interrupt.c -
Kconfig.xtos,ZEPHYR_NATIVE_DRIVERS
-
Phase 1: header untangle (no moves)
-
component.h: droprtos/idc.h,sof/lib/dai.h,schedule/schedule.h
(use forward declarations). -
audio_stream.h: dropsof/lib/dma.h. Splitsof/lib/dma.hinto a user
part (sg structs,dma_buffer_copy_*,sof_dma_*syscalls) and a kernel
part (struct dma,dma_plat_data,dmac_init). -
sof/lib/memory.h: keep the user-safe alignment/cache-line subset. The
platform memory map goes to kernel. -
sof/platform.h: move the constants user code needs to a user header or
Kconfig. The rest goes to kernel. - Split the mixed headers (user prototypes and
__syscalls stay user):-
dai-zephyr.h -
rtos/alloc.h -
schedule/schedule.h -
ll_schedule_domain.h -
dp_schedule.h -
lib_manager.h -
debug_stream_slot.h
-
-
rtos/: interrupt, clk, idc, timer, init, spinlock and userspace_helper
become kernel. Splitwait.hso thatwait_for_interruptgoes to kernel. - Remove the direct kernel includes from user
.cfiles:rtos/interrupt.h
(11 files),rtos/clk.h(4 files),sof/lib/memory.h(26 files). - Remove the relative
"../audio/copier/copier.h"includes
(lib/dma.c,pipeline-stream.c). - Exit criterion: the checker reports zero kernel-header leaks for USER files.
Phase 2: src/kernel + sof_kernel library, move the pure-kernel dirs
-
src/kernel/{CMakeLists.txt,Kconfig}andzephyr_library_named(sof_kernel)
with a PRIVATEsrc/kernel/include.add_local_sources()gets a
sof_kerneltarget. -
git mv, one PR per dir:-
init -
idc -
library_manager -
logging(+ipc4/logging.c) -
arch -
platform -
drivers -
probe -
safety(once the safety island lands) -
audio/base_fw*.c -
audio/intel_uaol.c -
audio/mic_privacy_manager -
module_adapter/library/userspace_proxy.c -
ipc/ipc3 -
ipc-zephyr.c -
ipc4/handler-kernel.c -
ipc4/notification.c -
notification_pool.c -
dma-copy.c -
lib/{clk,cpu-clk-manager,ams,agent}.c -
debug/telemetry -
debug/dram.c -
zephyr_ll_user.c
-
- Per move, update:
-
zephyr/CMakeLists.txtsource lists -
src/Kconfigrsource lines - root/
srcCMake (CONFIG_LIBRARY) - testbench, plugin, cmocka and ztest CMake
- CODEOWNERS
- fuzz dict scripts and
sdk-create-module.py - per-dir READMEs and docs
-
- Diff the
zephyr.mapsections andSYS_INITorder before and after (tgl, ptl, imx,
amd, mtk).
Phase 3: split mixed source files (handler-kernel.c pattern), move the kernel halves
-
ipc/ipc-common.c: kernel gets the cmd/work handler,ipc_user_init,
ipc_user_forward_cmdand theipc_msg_sendimpl. User gets
ipc_user_thread_fn. -
ipc4/handler-user.c: the routers and MOD_INIT / module_id 0 / CHAIN_DMA /
IPCGTW paths that run in the kernel IPC thread. -
ipc4/helper.c:ipc4_get_drv/get_comp_drvandipc_comp_free_remote
go to kernel. -
schedule/zephyr_domain.c: thread init/free, domain init and the timer
ISR go to kernel.thread_fnand register/unregister stay user. -
schedule/zephyr_ll.c:init_context,grant_accessand the sem syscall
impls go to kernel. -
schedule/zephyr_dp_schedule*.c,zephyr_twb_schedule.c,schedule.c:
split init and grant (kernel) from the thread bodies and dispatch (user). -
lib/dma.c,lib/dai.c: syscall impls and the legacy getters go to kernel. -
zephyr/lib/alloc.c,vregion.c,debug_stream_slot.c: check that the
z_impl/z_vrfy code is kernel-only.
Phase 4: move the kernel headers, turn on enforcement
-
git mvthe kernel headers tosrc/kernel/include/sof/kernel/...and
rewrite the kernel includes to<sof/kernel/...>. - Platform include dirs become PRIVATE to
sof_kernel, and the user-needed subset
is exported through user headers. - Rebuild every
sof_llext_buildmodule and the LMDK samples against the
reduced include set. - Install rules install only the user include set.
- Switch the checker to enforcing in CI, and add a checkpatch rule against
<sof/kernel/in user dirs.
Phase 5: remove the notifier and close the user-safety gaps
- DAI-group trigger: drop the notifier and add a
sof_dai_group_trigger()syscall
for the IRQ-locked section. - KPB client events: replace with a
k_msgq(ork_event) for the hotword
detector → kpb path, coveringgoogle_hotword_detect.canddetect_test.c. - Mic privacy: replace with an ISR
k_event_post()and copier polling. - Remove the
notification_id/notification_maskfields fromclock_info. - Delete
lib/notifier.c,notifier.h,IDC_MSG_NOTIFY,init_system_notify()
andsof->notify_data. - Remaining kernel calls from user-classified code:
-
idc_send_msg/ipc4_process_on_corefrom the user IPC thread -
k_cycle_get_64/sof_cycle_get_64in host-zephyr, kpb, probe and
perf_cnt (check whether this is wall-clock MMIO on intel_adsp) - kpb
pm_runtime_disableand its EDF task -
lib_notif_msg_send - probe raw
dma_*→sof_dma_*, which would let probe move back to user
-
- Re-enable the features
app/overlays/ptl/ll_userspace_overlay.confcurrently
turns off for user LL: telemetry,CROSS_CORE_STREAM,MIC_PRIVACY, xrun
notifications, cold-store debug.
Describe alternatives you've considered
- Path-preserving header move (kernel headers keep their
<sof/lib/...>
path but move to a private include root). This means no include churn, but you
can't tell from an include line which headers are kernel-only. We chose
<sof/kernel/...>so the boundary is grep- and checkpatch-able. - Renaming the host ABI
src/include/kernel/andsrc/include/user/to free
up the names. Rejected: about 245 include sites, the topologyget_abi.sh
parsing, and the hand-synced Linux copies. - Moving legacy XTOS code into
src/kernel/legacy/instead of deleting it.
Rejected, because it moves code no Zephyr build compiles. - Keeping the notifier and making it a syscall. Rejected: callbacks would
still run in the poster's context across the privilege boundary. Zephyr
k_event/k_msgqalready give syscall-backed, memory-domain-aware
signalling.
Additional context
- Related to the userspace LL / DP work:
CONFIG_SOF_USERSPACE_LL,
CONFIG_SOF_USERSPACE_PROXY,zephyr/syscall/,src/ipc/ipc4/handler-{kernel,user}.c. - Expect merge conflicts with in-flight PRs during the Phase 2 and Phase 4 moves. Moves
will be one dir per PR and announced ahead of time. - Validation per phase: CI build matrix (Intel cAVS2.5/ACE1.5/2.0/3.0/4.0, i.MX,
AMD, MTK, native_sim/fuzz, testbench, cmocka, ztest),zephyr.elfsection
diffs, and boot+playback on TGL and PTL/ARL hardware.
Activity
lgirdwood commentedon Oct 14, 2026
I've updated the Phase 0 checklist in the description after checking each item against main.
Corrections to the original plan
src/traceis still used: the testbench builds it (CONFIG_TRACEdefaults to y for host builds). It is now a decision item rather than a deletion.ll_schedule_xtos.cand the DMA domain schedulers are still built foracp_6_0on Zephyr, and the testbench buildshost-legacy.c/dai-legacy.c. Both moved to 0b, the legacy driver path.New findings
CONFIG_DWcannot be enabled, so all ofsrc/drivers/dwis dead, including the DMA driver thatzephyr/CMakeLists.txtstill lists.test-repro-build.shexits 0 whenxtensa-build-all.shis missing.sof_builderdocker image no longer builds on main: its alsa-lib/alsa-utils stage needs a newer autoconf than Ubuntu 22.04 provides. This is unrelated to the XTOS toolchain removal.Added to Phase 0: removal of the SOF ALSA plugin, together with the shared-library module build and
CONFIG_LIBRARY_STATIC, which only existed for it.UUID registry entries of removed code are kept, because the registry requires that distributed mappings never change.
lgirdwood commentedon Oct 14, 2026
Status: Phase 0a is up as draft PR #11312, with 23 commits and one patch per checklist item.
zephyr.elfis section-identical on all 15 platforms that build on main, and all 66 LLEXT modules are unchanged.Still open:
src/trace: keep it as host-only trace, or move the testbench to a printf backend.host-legacy.c/dai-legacy.c.