Repository navigation
Conversation
|
FYI @ujfalusi @lgirdwood with this, compressed playback can be supported also with full userspace (both LL pipes and the DP codecs running in user-space). |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical multicore userspace defects in state handling, locking, access grants, event signaling, and component dispatch can cause failed transitions, panics, unauthorized access, and deadlocks.
5 open issues
Handle mixed-core pipeline state requests in userspace · New Post event bits to preserve concurrent CMD and XCORE requests · New Use kernel-only scheduler-lock handles for access grants · New Dispatch components by core, not cross-core buffer ownership · New Include core 0 in cross-core buffer locking safely · New
What changed in this PR
Adds initial cross-core pipeline support to full userspace builds, allowing DP modules such as compressed codecs to run on a different core from their pipeline.
Changes:
- Routes component IDC requests through per-core userspace IPC threads.
- Adds shared request storage, access grants, and userspace scheduler locking.
- Enables cross-core streams while keeping LL modules on their pipeline’s core.
| File | Description |
|---|---|
zephyr/include/rtos/idc.h |
Declares userspace cross-core messaging interfaces. |
src/schedule/zephyr_domain.c |
Excludes kernel-style scheduler blocking from userspace LL builds. |
src/ipc/ipc4/helper.c |
Allows remote DP modules and adjusts binding and teardown locks. |
src/ipc/ipc-common.c |
Implements cross-core request transport, dispatch, and access grants. |
src/include/sof/ipc/common.h |
Defines per-core cross-core request slots. |
src/include/sof/audio/component_ext.h |
Allocates remote attribute results in user-accessible memory. |
src/idc/zephyr_idc.c |
Routes userspace IDC sends through userspace IPC threads. |
src/idc/idc.c |
Adds userspace component dispatch and explicit payload handling. |
app/ll_userspace_overlay.conf |
Removes the forced disabling of cross-core streams. |
Review effort: Balanced
PR 11397: test resultsRun date: 2026-11-05 14:25 UTC Tested commit: 6d2890d20df1608c4b88eed81d5e6fd6a3b5a893 |
zephyr_ll_locks[] is in a user-accessible partition, so user threads can read the lock handles for the lock syscalls. user_ll_grant_access() runs in kernel context, but took the handle to grant from that same array. A user thread could overwrite an entry and make the kernel grant it access to an arbitrary kernel object. This is reachable whenever grants are done while user threads already run, e.g. when a secondary core and its IPC user thread are brought up, and with cross-core streams also for the locks of other cores. Keep a kernel-only copy of the lock handles and use it for the grants. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Pass the message payload to the component IDC message handlers as an argument, instead of fetching it from the per-core IDC payload area, which is kernel memory. Add idc_cmd_user() to let user-space threads (with CONFIG_SOF_USERSPACE_LL) process component messages, sent from another core, with a payload in user-accessible memory. Only the messages, which can originate from the user-space IPC and LL threads, are supported. LL modules on a core different from their pipeline core aren't supported with user-space LL, so fail the prepare instead of trying to set up an LL task for them. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
72b5b67 to
0c12b46
Compare
|
V2 pushed:
|
0c12b46 to
ba5a057
Compare
|
V3 pushed:
|
There was a problem hiding this comment.
🟡 Changes recommended
Two critical deadlock paths remain in synchronous cross-core IPC waits, where LL threads hold scheduler mutexes while awaiting IPC handlers that need those locks during BIND/UNBIND or remote DP startup.
6 open issues
Remote DP startup can deadlock concurrent LL pipeline starts · New Cross-core IPC can deadlock against LL scheduler locks · New Include core 0 in cross-core buffer locking safely Dispatch components by core, not cross-core buffer ownership Use kernel-only scheduler-lock handles for access grants Handle mixed-core pipeline state requests in userspace
1 resolved since last review
Review effort: Balanced
With CONFIG_SOF_USERSPACE_LL, component operations on a module on another core (cross-core bind, remote get_attribute, free, params, prepare, trigger, reset) are called from user-space IPC and LL threads. They use idc_send_msg(), which relies on kernel objects and data and faults in user mode. Add a cross-core request slot per core to struct ipc_user. When called in user context, idc_send_msg() now copies the message and its payload to the slot of the target core and wakes the user-space IPC thread of that core, which processes it with idc_cmd_user() and signals completion. The IPC4 bind / unbind message, referenced by the bind payload, usually is on the sender's stack, so it's copied as well. The user-space IPC thread of a core now has two independent producers, the kernel IPC handler (IPC_USER_EVENT_CMD) and user threads on other cores (IPC_USER_EVENT_XCORE). Post the events with k_event_post() instead of k_event_set(), which would clear a pending request of the other producer. An LL thread holds the LL scheduler lock of its core for the whole LL cycle, also when it calls a remote module, e.g. from a timer-driven pipeline trigger task or when a failed copy resets the pipeline. Waiting for the target IPC thread with the lock held could deadlock, if that thread needs the lock, e.g. for a bind locking both cores, or needs the LL lock of its own core, whose LL thread may in turn wait for a request to this core. Release all holds of an LL thread on its LL lock while it waits for a cross-core request, and re-take them afterwards, like the kernel-mode IDC path does implicitly. Track the LL lock owner and depth also without CONFIG_ASSERT for this. Grant all user-space IPC and LL threads access to the request slots and LL scheduler locks of all initialized cores. The grants are done from kernel-only copies of the thread pointers, so keep one for the LL audio threads as well. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
comp_ipc4_get_attribute_remote() allocates the buffer for the remote core to fill with rzalloc(), which takes a spinlock and fails in user mode. With CONFIG_SOF_USERSPACE_LL it's called from user-space threads and the buffer is filled by the user-space IPC thread of the remote core, so allocate it from the coherent LL user heap instead. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
With CONFIG_SOF_USERSPACE_LL, ipc_pipeline_module_free() takes the LL scheduler lock of the core of the first module in the pipeline. That is supposed to be the pipeline core, but a module can be on another core (e.g. an offloaded DP module). Freeing such a remote module is done on its core, where ipc_comp_free() takes the LL lock of that core, which would deadlock, if it's the lock taken here. Pass the pipeline core to ipc_pipeline_module_free() and lock that. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Cross-core binding was rejected at build time with CONFIG_SOF_USERSPACE_LL. Implement the LL blocking for it: The user-space LL thread holds its scheduler lock for the whole LL cycle, so taking the LL lock of a core both blocks its next cycle and waits for the current one to finish. ll_block() / ll_unblock() now take the two modules and lock the cores of both modules and of their pipelines, in ascending order to avoid deadlocks. ll_wait_finished_on_core() doesn't need to send an IDC anymore. pipeline_connect() / pipeline_disconnect() asserted the LL lock of the buffer core. Cross-core buffers always belong to the primary core, which isn't locked for e.g. a bind between cores 1 and 2, nor when ipc_pipeline_module_free() disconnects a DP module on core 0 in a pipeline on core 1. The modified lists are walked by the LL thread of the pipeline core, so assert the lock of that core instead. struct pipeline is in cached memory of the pipeline core, so its core field can be stale on other cores, e.g. on the primary core, which processes cross-core binds. Record the pipeline core in the coherent comp_dev, in the new ipc_config.ppl_core, and read it with comp_ppl_core(). The remote parts of the bind (get_attribute, bind, unbind) go through idc_send_msg(), which forwards them to the user-space IPC thread of the remote core. The domain block / unblock in zephyr_domain.c uses a mutex and a condition variable, embedded in user memory, and isn't used with user-space LL, so don't build it then. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
With CONFIG_CROSS_CORE_STREAM and CONFIG_SOF_USERSPACE_LL, a DP module on a different core than its pipeline can now be bound and operated across cores. With user-space LL, keep rejecting only LL modules on another core than their pipeline. Such an LL module would need its own LL task on that core, which is only set up by the kernel-mode IDC handlers. comp_buffer_connect() marked a component shared only when the buffer is on another core. Cross-core buffers always belong to the primary core, so e.g. a DP module on core 0 in a pipeline on core 1 was never marked shared, and its operations, called on the pipeline core, were run there instead of on core 0. Also mark a component shared, if it's on another core than its pipeline. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Cross-core streams are now supported with user-space LL. Stop disabling CONFIG_CROSS_CORE_STREAM in the LL user-space overlay. This allows e.g. the compressed playback topology, which runs the decoder (DP) on core 1, bound to the host copier on core 0. The overlay is used by all ACE3.x and newer targets (e.g. PTL, WCL, NVL) built with os_linux_overlay.conf. Tested on PTL. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
ba5a057 to
6d2890d
Compare
|
V4 pushed:
|
There was a problem hiding this comment.
🟡 Changes recommended
Critical IPC locking, graph-lifetime, and buffer-sharing issues can cause deadlocks, use-after-free during remote waits, and stalled or corrupted cross-core audio.
3 open issues
6 resolved since last review
Cross-core IPC can deadlock against LL scheduler locks Remote DP startup can deadlock concurrent LL pipeline starts Include core 0 in cross-core buffer locking safely Dispatch components by core, not cross-core buffer ownership Use kernel-only scheduler-lock handles for access grants Handle mixed-core pipeline state requests in userspace
Review effort: Balanced
| */ | ||
| for (ll_core = 0; ll_core < CONFIG_CORE_COUNT; ll_core++) { | ||
| if (ipc_user->audio_thread[ll_core] == k_current_get()) { | ||
| ll_depth = user_ll_unlock_sched_all(ll_core); |
| } | ||
| } | ||
|
|
||
| k_sem_take(xc->lock, K_FOREVER); |
| if (comp_ppl_core(comp) != comp_core && !comp->is_shared) | ||
| comp_make_shared(comp); |

Initial implementation of cross-core pipeline support to full userspace builds. This covers the common use-case of running compressed codec module code in a different core (as DP module).