ipc: add support for userspace IPC handling#10994
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the Zephyr userspace LL execution model to IPC handling by forwarding selected IPC4 commands to a dedicated Zephyr user-space thread, enabling module init/config and some pipeline operations to run with reduced privileges. The changes integrate Zephyr syscall boundaries, userspace memory-domain grants (mailbox/HOSTBOX), and userspace-aware allocation paths to support privilege-separated IPC execution without affecting default kernel-space SOF builds.
Changes:
- Add a generic “IPC user thread” forwarding mechanism (kernel IPC task → userspace IPC thread) and wire IPC4 command subsets to use it under
CONFIG_SOF_USERSPACE_LL. - Introduce/adjust Zephyr syscall interfaces for IPC reply + IPC4 compound message helpers to support userspace callers.
- Update IPC4 helper allocation/core bookkeeping to support userspace execution and user-heap allocations.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| zephyr/lib/userspace_helper.c | Grants userspace access to HOSTBOX (cached + uncached mappings) for IPC4 module init/config payload reads. |
| zephyr/Kconfig | Adds SOF_IPC_USER_THREAD_STACK_SIZE Kconfig for the IPC userspace thread stack. |
| zephyr/include/rtos/sof.h | Hides sof->ipc when userspace LL is enabled (IPC context is no longer stored there). |
| zephyr/CMakeLists.txt | Adds syscall header generation for new IPC reply and IPC4 handler syscall declarations. |
| src/schedule/zephyr_ll.c | Adds user_ll_grant_access() to grant userspace thread access to LL scheduler locks. |
| src/ipc/ipc4/helper.c | Adds userspace component creation entrypoint and shifts several allocations/frees to user-heap helpers. |
| src/ipc/ipc4/handler-user.c | Forwards selected IPC4 commands to userspace and adds userspace-side dispatch/processing helpers. |
| src/ipc/ipc4/handler-kernel.c | Converts select IPC helpers to Zephyr syscall implementations with verification wrappers. |
| src/ipc/ipc3/helper.c | Renames IPC reply implementation to z_impl_ipc_msg_reply() for syscall compatibility. |
| src/ipc/ipc-helper.c | Adjusts buffer trace ctx init for userspace LL and updates component container free to user-heap free helper. |
| src/ipc/ipc-common.c | Implements IPC userspace thread infrastructure, forwarding API, and userspace-aware IPC init path. |
| src/include/sof/schedule/ll_schedule_domain.h | Exposes user_ll_grant_access() prototype. |
| src/include/sof/ipc/topology.h | Exposes IPC4 userspace creation helpers and ipc4_add_comp_dev() for userspace dispatch. |
| src/include/sof/ipc/ipc_reply.h | New header providing syscall-friendly ipc_msg_reply() declaration/mapping. |
| src/include/sof/ipc/common.h | Adds struct ipc_user, userspace IPC APIs, and wires IPC reply via the new header. |
| src/include/ipc4/handler.h | Exposes new IPC4 helpers and declares syscall variants for compound-message helpers under userspace LL. |
| ret = k_sem_take(pdata->sem, K_MSEC(10)); | ||
| if (ret) { | ||
| LOG_ERR("IPC user: sem error %d\n", ret); | ||
| return ret; | ||
| } |
| user_grant_dai_access_all(&ipc_user_thread); | ||
| user_grant_dma_access_all(&ipc_user_thread); | ||
| user_access_to_mailbox(zephyr_ll_mem_domain(), &ipc_user_thread); | ||
| user_ll_grant_access(&ipc_user_thread, PLATFORM_PRIMARY_CORE_ID); | ||
| k_mem_domain_add_thread(zephyr_ll_mem_domain(), &ipc_user_thread); |
There was a problem hiding this comment.
Ack, will fix in V2.
| if (!ipc->comp_data) { | ||
| tr_err(&ipc_tr, "Unable to allocate IPC component data"); | ||
| rfree(sof->ipc); | ||
| sof_heap_free(heap, ipc); | ||
| return -ENOMEM; |
| ipc_user_init(); | ||
|
|
||
| return platform_ipc_init(ipc); |
There was a problem hiding this comment.
Ack, will fix in V2.
| memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); | ||
| if (!cpu_is_me(mi.extension.r.core_id)) { |
There was a problem hiding this comment.
Ack, will fix in V2.
| case SOF_IPC4_MOD_BIND: { | ||
| struct ipc4_module_bind_unbind bu; | ||
|
|
||
| memcpy_s(&bu, sizeof(bu), &msg, sizeof(msg)); | ||
| result = ipc_comp_connect(ipc_user->ipc, | ||
| (ipc_pipe_comp_connect *)&bu); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Ack, will fix in V2.
|
|
||
| ipc = ipc_get(); | ||
| memset(ipc, 0, sizeof(*ipc)); | ||
| ipc->ll_alloc = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*ipc->ll_alloc), 0); |
There was a problem hiding this comment.
squashing: please make coherent as in the second hunk of c790496
There was a problem hiding this comment.
Will squash to V2.
|
|
||
| #ifdef CONFIG_SOF_USERSPACE_LL | ||
| #include <rtos/userspace_helper.h> | ||
| #include <sof/schedule/ll_schedule_domain.h> |
| void ipc_compound_pre_start(int msg_id); | ||
| void ipc_compound_msg_done(uint32_t msg_id, int error); | ||
|
|
||
| #if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) |
There was a problem hiding this comment.
Merged this to V2 and added your cdb+sob
| __cold int ipc_user_init(void) | ||
| { | ||
| struct ipc *ipc = ipc_get(); | ||
| struct ipc_user *ipc_user = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, |
| k_event_set(pdata->event, IPC_USER_EVENT_CMD); | ||
|
|
||
| /* Wait for user thread to complete */ | ||
| ret = k_sem_take(pdata->sem, K_MSEC(10)); |
There was a problem hiding this comment.
squashinng: looks like this isn't enough bf7c7ae
There was a problem hiding this comment.
I'll cherry-pick to the series.
| } | ||
| } | ||
|
|
||
| __cold int ipc_user_init(void) |
There was a problem hiding this comment.
Partially squashed in V2
| sizeof(((struct ipc_user *)0)->init_drv_data), | ||
| "ipc_user.init_drv_data too small for driver copy"); | ||
|
|
||
| memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); |
There was a problem hiding this comment.
Leaving this for a follow-up for now.
| * declared as syscalls in ipc4/handler.h — do not re-declare here with | ||
| * external linkage as that conflicts with the static inline syscall wrappers. | ||
| */ | ||
| #if !(defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL)) |
There was a problem hiding this comment.
these 3 under the #if seem unneeded - would be removed by squashing
There was a problem hiding this comment.
Ack, removed in V2.
| #endif | ||
| } | ||
|
|
||
| #ifdef CONFIG_SOF_USERSPACE_LL |
There was a problem hiding this comment.
I still think all the userspace IPC should go into a separate file, not into ipc-common.c, and supposedly under ipc4/. But this can be done later too.
| for (;;) { | ||
| uint32_t mask = k_event_wait_safe(ipc_user->event, | ||
| IPC_USER_EVENT_CMD | IPC_USER_EVENT_STOP, | ||
| false, K_MSEC(5000)); |
There was a problem hiding this comment.
True, changing to K_FOREVER to V2. This was just used in development when we had issues with the kernel-user mechanism.
| __cold int ipc_user_init(void) | ||
| { | ||
| struct ipc *ipc = ipc_get(); | ||
| struct ipc_user *ipc_user = sof_heap_alloc(sof_sys_user_heap_get(), SOF_MEM_FLAG_USER, |
| */ | ||
| const struct comp_driver *orig_drv = ipc_user->init_drv; | ||
| const struct comp_driver *drv_copy = | ||
| (const struct comp_driver *)ipc_user->init_drv_data; |
There was a problem hiding this comment.
Leaving for a follow-up at least for now.
| IPC4_MOD_ID(comp_id)); | ||
|
|
||
| if (!drv) { | ||
| ret = IPC4_MOD_NOT_INITIALIZED; |
There was a problem hiding this comment.
break; and save 1 indentation level below
| */ | ||
| struct comp_driver *drv_copy = | ||
| (struct comp_driver *)pdata->init_drv_data; | ||
| struct tr_ctx *tctx_copy = |
There was a problem hiding this comment.
I think trace context is IPC3-only
There was a problem hiding this comment.
Perhaps, but we are still having it around in IPC4 builds as well, so keeping it along.
kv2019i
left a comment
There was a problem hiding this comment.
Thanks you for reviews, will push V2 soon.
|
|
||
| #ifdef CONFIG_SOF_USERSPACE_LL | ||
| #include <rtos/userspace_helper.h> | ||
| #include <sof/schedule/ll_schedule_domain.h> |
|
|
||
| ipc = ipc_get(); | ||
| memset(ipc, 0, sizeof(*ipc)); | ||
| ipc->ll_alloc = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, sizeof(*ipc->ll_alloc), 0); |
There was a problem hiding this comment.
Will squash to V2.
| if (!ipc->comp_data) { | ||
| tr_err(&ipc_tr, "Unable to allocate IPC component data"); | ||
| rfree(sof->ipc); | ||
| sof_heap_free(heap, ipc); | ||
| return -ENOMEM; |
| ipc_user_init(); | ||
|
|
||
| return platform_ipc_init(ipc); |
There was a problem hiding this comment.
Ack, will fix in V2.
| void ipc_compound_pre_start(int msg_id); | ||
| void ipc_compound_msg_done(uint32_t msg_id, int error); | ||
|
|
||
| #if defined(__ZEPHYR__) && defined(CONFIG_SOF_USERSPACE_LL) |
There was a problem hiding this comment.
Merged this to V2 and added your cdb+sob
| */ | ||
| const struct comp_driver *orig_drv = ipc_user->init_drv; | ||
| const struct comp_driver *drv_copy = | ||
| (const struct comp_driver *)ipc_user->init_drv_data; |
There was a problem hiding this comment.
Leaving for a follow-up at least for now.
| sizeof(((struct ipc_user *)0)->init_drv_data), | ||
| "ipc_user.init_drv_data too small for driver copy"); | ||
|
|
||
| memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); |
There was a problem hiding this comment.
Leaving this for a follow-up for now.
| memcpy_s(&mi, sizeof(mi), ipc4, sizeof(*ipc4)); | ||
| if (!cpu_is_me(mi.extension.r.core_id)) { |
There was a problem hiding this comment.
Ack, will fix in V2.
| IPC4_MOD_ID(comp_id)); | ||
|
|
||
| if (!drv) { | ||
| ret = IPC4_MOD_NOT_INITIALIZED; |
| */ | ||
| struct comp_driver *drv_copy = | ||
| (struct comp_driver *)pdata->init_drv_data; | ||
| struct tr_ctx *tctx_copy = |
There was a problem hiding this comment.
Perhaps, but we are still having it around in IPC4 builds as well, so keeping it along.
…e LL When CONFIG_SOF_USERSPACE_LL is enabled the IPC context must live in user-accessible application memory rather than being allocated from the kernel heap and reached via sof->ipc. Place struct ipc in a dedicated K_APP_BMEM partition and provide an out-of-line ipc_get() returning it; drop the sof->ipc pointer in this configuration. Add the generic ll_alloc allocation context to struct ipc and set it up in ipc_init() from the LL user heap, so LL components (host, dai, chain_dma) can allocate their buffers from a user-accessible heap. IPC objects (the context itself and comp_data) are allocated accordingly. A no-op ipc_user_init() stub is added here and implemented later. No IPC-major-specific knowledge is added here. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add the struct ipc_user descriptor (and its ipc_user_pdata slot in struct ipc) that the IPC4 kernel handler uses to forward commands to the user-space IPC thread: the two IPC4 message words, the reply fields, and a user-readable copy of the resolved comp_driver + tr_ctx used by user-space component creation. In ipc4 helper, allow component buffers to be allocated from the LL user heap (ipc->ll_alloc) and create them on the component's configured core rather than the current CPU, so buffer creation works from the user thread when CONFIG_SOF_USERSPACE_LL is enabled. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
…stic Extract the per-message processing logic for module CONFIG_GET/SET and LARGE_CONFIG_GET/SET into standalone functions that return their results via output parameters instead of writing the global msg_reply directly, and give SET_PIPELINE_STATE external linkage. This is a pure refactor with no functional change. The new ipc4_process_module_config(), ipc4_process_large_config_get(), ipc4_process_large_config_set() and ipc4_set_pipeline_state() can be called from any execution context, which is needed to later dispatch these messages from a separate IPC user-space thread. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The IPC reply and compound-message completion entry points need to be callable from the IPC user-space thread. Declare ipc_msg_reply(), ipc_compound_pre_start(), ipc_compound_post_start() and ipc_wait_for_compound_msg() as Zephyr syscalls when CONFIG_SOF_USERSPACE_LL is enabled, renaming the implementations to z_impl_* and adding z_vrfy_* verification wrappers. A new ipc_reply.h carries the ipc_msg_reply declaration so it can be pulled in as a syscall header. Register the new syscall headers in CMakeLists.txt. No functional change for non-userspace builds, where the functions keep direct external linkage via z_impl_* defines. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Co-developed-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
The pipeline, component and chain-DMA bookkeeping objects must be reachable from the user-space IPC thread, so allocate them from the system user heap via sof_heap_alloc()/sof_heap_free() instead of the generic rzalloc()/rfree(). As sof_heap_alloc() does not zero the allocation, add an explicit memset() to preserve the previous behaviour. Give ipc4_add_comp_dev() external linkage (declared in topology.h) so the user thread can register a created component, and skip the buffer tr_ctx copy for user-space LL builds where the kernel tr_ctx is not mapped. No functional change for non-userspace builds. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
The user-space IPC thread reads the IPC4 module init parameter block directly from MAILBOX_HOSTBOX_BASE (comp_new_ipc4() / comp_new_ipc4_user()), so the HOSTBOX region must be reachable from the user LL memory domain. Map two partitions when CONFIG_SOF_USERSPACE_LL && CONFIG_IPC_MAJOR_4: an uncached read-only view for the parameter reads, and a cached RW view because the sys_cache_data_invd_range() syscall verifier requires write access to the invalidated range. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add function user_ll_grant_access() to allow other threads to access the scheduler mutex. This is needed if work is submitted from other threads to the scheduler. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add a dedicated user-space thread for handling IPC commands that operate on audio pipelines when CONFIG_SOF_USERSPACE_LL is enabled. This provides only the protocol-agnostic infrastructure: - ipc_user_init() creates the K_USER thread, allocates its k_sem/k_event handshake objects, sets up the memory domain and access grants, and spins up the LL scheduler context. - ipc_user_forward_cmd() forwards the two raw IPC message words from the kernel IPC handler to the user thread via k_event signaling, sets IPC_TASK_IN_THREAD so the host is not signaled until the user thread completes, and collects the result through a k_sem handshake. - ipc_user_thread_fn() runs the wait loop and delegates the actual command interpretation to ipc_user_thread_dispatch(), a __weak hook overridden by the active IPC major. No IPC-major-specific knowledge lives here; the IPC4 dispatch is added in a following commit. The generic infra can be extended to cover other IPC protocol variants. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Add comp_new_ipc4_user(), called from the user-space IPC thread to create a component from user context. It receives a pre-resolved driver pointer from the kernel handler (which does the privileged IMR manifest / driver list lookup), parses the IPC4 init-instance message, reads the module init parameter block from the HOSTBOX, and calls drv->ops.create() so untrusted module init code does not execute with kernel privileges. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Provide the IPC4 implementation of ipc_user_thread_dispatch(), the protocol-specific hook called from the generic user-space IPC thread. It reconstructs the IPC4 message from the forwarded primary/extension words and runs the privilege-separated portion of each supported command: pipeline create/delete/set-state, module config get/set, large-config get/set, bind/unbind, and init/delete instance. Component creation goes through comp_new_ipc4_user() so module code runs in user context. This moves the IPC4 command switch out of the protocol-agnostic ipc-common.c into the IPC4 handler where it belongs. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Route the IPC4 commands that operate on audio pipelines to the user-space IPC thread via ipc_user_forward_cmd() when CONFIG_SOF_USERSPACE_LL is enabled: create/delete/set-state pipeline, config get/set, large-config get/set, bind/unbind and init/delete instance. For MOD_INIT_INSTANCE the kernel still resolves the driver (needs IMR manifest access) and copies comp_driver + tr_ctx into the user-readable ipc_user buffer before forwarding; cross-core creation stays in kernel. LARGE_CONFIG get/set for base firmware (module_id 0) also stay in kernel because they touch the IMR manifest. CONFIG_GET / LARGE_CONFIG_GET copy the reply extension (and tx payload) back from the user thread. The now-unused ipc4_new_pipeline()/ipc4_delete_pipeline() kernel helpers are compiled out in the user-space configuration. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Sometimes 10ms aren't enough for userspace IPC processing, increase it to 100ms. Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com> Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
If CONFIG_SOF_BOOT_TEST_STANDALONE is set, ipc_init() is terminated early. This ensures SOF will not start to generate or respond to IPC messages that could potentially interfere with standalone test cases (some of which send and receive IPCs). The current implementation leaves the component list uninitialized and this can cause trouble to standalone tests that want to utilzie common IPC code to build messages. Fix this problem by executing more of ipc_init() also in the standalone mode. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
341fca5 to
ae6fade
Compare
|
V2 pushed:
|
This PR contains one of the remaining big subsets from #10558 and implements ability to handle a subset of IPC in a Zephyr user-space thread. This is an important enabler for running the SOF audio pipelines in user-space and allows us to run all audio module code in user context (including the module initialization and configuration functions).
This PR has no impact to default SOF builds where LL audio pipelines are run in kernel space.