userspace: dp: Add option for executing userspace module IPC in DP thread#10579
userspace: dp: Add option for executing userspace module IPC in DP thread#10579softwarecki wants to merge 1 commit intothesofproject:mainfrom
Conversation
…read Add the SOF_USERSPACE_MOD_IPC_BY_DP_THREAD Kconfig option and implement support for executing userspace module IPC in its DP thread instead of the shared user worker thread. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
There was a problem hiding this comment.
Pull request overview
Adds a Zephyr Kconfig option to route userspace module IPC handling through each module’s DP thread (instead of the shared userspace worker), and wires DP-thread event/work-item plumbing through the DP scheduler and userspace proxy layers.
Changes:
- Add
SOF_USERSPACE_MOD_IPC_BY_DP_THREADKconfig option. - Extend DP scheduler thread/event handling to wake on IPC events and run the userspace proxy IPC handler in the DP thread.
- Update userspace proxy to optionally signal/await IPC via the DP thread’s
k_event, plus adjust system-agent startup timing for this mode.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
zephyr/Kconfig |
Adds the new configuration option controlling IPC execution context. |
src/schedule/zephyr_dp_schedule_thread.c |
Handles IPC events in DP thread and registers per-module IPC handler/event linkage. |
src/schedule/zephyr_dp_schedule.h |
Adds per-task storage for the IPC work item pointer. |
src/include/sof/audio/module_adapter/library/userspace_proxy.h |
Extends userspace context with DP-thread event pointer and declares IPC handler registration API. |
src/audio/module_adapter/module_adapter.c |
Adjusts DP task/free sequencing around userspace application teardown. |
src/audio/module_adapter/module/generic.c |
Tweaks DP task cancellation behavior under the new IPC-by-DP-thread mode. |
src/audio/module_adapter/library/userspace_proxy.c |
Implements DP-thread IPC signaling path, IPC handler registration, and defers agent startup in this mode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| user_ctx->dp_event = event; | ||
| user_ctx->work_item->event = event; | ||
|
|
||
| assert(user_ctx->work_item); |
There was a problem hiding this comment.
userspace_proxy_register_ipc_handler() dereferences user_ctx->work_item before validating it (user_ctx->work_item->event = event; happens before assert(user_ctx->work_item)). This can NULL-deref if work item allocation/init failed. Assert/check user_ctx->work_item before using it and return NULL/error if missing.
| user_ctx->dp_event = event; | |
| user_ctx->work_item->event = event; | |
| assert(user_ctx->work_item); | |
| if (!user_ctx->work_item) { | |
| tr_err(&userspace_proxy_tr, | |
| "userspace_proxy_register_ipc_handler(): work_item is NULL for module %p", | |
| (void *)mod); | |
| return NULL; | |
| } | |
| user_ctx->dp_event = event; | |
| user_ctx->work_item->event = event; |
| /* Start the system agent, if provided. */ | ||
| if (start_fn) { | ||
| params->ext.agent.params = *agent_params; | ||
| params->ext.agent.mod_cfg = *mod_cfg; | ||
|
|
||
| *agent_interface = params->ext.agent.out_interface; | ||
| return params->status; | ||
| /* In case of processing modules ipc in the DP thread, the agent will be started in the | ||
| * init function. At this point the DP thread does not exist yet. | ||
| */ | ||
| #if !IS_ENABLED(CONFIG_SOF_USERSPACE_MOD_IPC_BY_DP_THREAD) | ||
| int ret = userspace_proxy_invoke(user_ctx, USER_PROXY_MOD_CMD_AGENT_START, true); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| *agent_interface = params->ext.agent.out_interface; | ||
| return params->status; | ||
| #endif | ||
| } | ||
| return 0; |
There was a problem hiding this comment.
With CONFIG_SOF_USERSPACE_MOD_IPC_BY_DP_THREAD enabled, userspace_proxy_start_agent() no longer invokes start_fn and does not populate *agent_interface. This breaks the LMDK path where native_system_agent_start() is what initializes ops (via agent_iface = (const void **)&ops), and ops is used immediately after userspace_proxy_create() returns. Either keep starting the agent here for cases that must produce ops/agent_interface, or provide an alternative initialization path that guarantees ops is valid before returning from userspace_proxy_create().
| bool ipc_payload_access) | ||
| { | ||
| #if IS_ENABLED(CONFIG_SOF_USERSPACE_MOD_IPC_BY_DP_THREAD) | ||
| struct k_event * const event = user_ctx->dp_event; |
There was a problem hiding this comment.
In the DP-thread IPC mode, userspace_proxy_invoke() uses user_ctx->dp_event. dp_event is set later via userspace_proxy_register_ipc_handler(), but the userspace_context is allocated with k_heap_alloc() (not zeroed), so dp_event can contain garbage until explicitly set. Initialize dp_event to NULL when creating the context and fail fast (return an error) if dp_event is still NULL when invoking IPC.
| struct k_event * const event = user_ctx->dp_event; | |
| struct k_event * const event = user_ctx->dp_event; | |
| if (!event) { | |
| tr_err(&userspace_proxy_tr, | |
| "userspace_proxy_invoke(): DP event not registered"); | |
| return -EINVAL; | |
| } |
| ret = module_free(mod); | ||
| if (ret) | ||
| comp_err(dev, "failed with error: %d", ret); | ||
|
|
||
| if (dev->task) | ||
| schedule_task_free(dev->task); | ||
|
|
There was a problem hiding this comment.
The ordering here contradicts the comment and can keep the DP thread alive while module_free(mod) runs. schedule_task_free() joins/aborts the DP thread; moving it after module_free() risks use-after-free if the thread is still runnable or if any late events occur. Restore the safe order: run the thread-context .free IPC, then schedule_task_free(dev->task), then module_free(mod) / remaining cleanup.
| ret = module_free(mod); | |
| if (ret) | |
| comp_err(dev, "failed with error: %d", ret); | |
| if (dev->task) | |
| schedule_task_free(dev->task); | |
| if (dev->task) | |
| schedule_task_free(dev->task); | |
| ret = module_free(mod); | |
| if (ret) | |
| comp_err(dev, "failed with error: %d", ret); |
| config SOF_USERSPACE_MOD_IPC_BY_DP_THREAD | ||
| bool "Handle modules IPC in DP thread" | ||
| depends on USERSPACE | ||
| help | ||
| When enabled, IPC requests targeted to a userspace module are executed | ||
| in its DP thread instead of the shared user worker thread. | ||
|
|
There was a problem hiding this comment.
SOF_USERSPACE_MOD_IPC_BY_DP_THREAD can be enabled when SOF_USERSPACE_PROXY is disabled, but the implementation relies on userspace proxy headers/symbols (e.g., userspace_proxy_register_ipc_handler() / userspace_proxy_worker_handler()). Add a dependency on SOF_USERSPACE_PROXY (and any other required options) to avoid invalid configurations / build failures.
| bool task_stop; | ||
| uint32_t event; | ||
|
|
There was a problem hiding this comment.
task_stop is not initialized on each loop iteration. If the DP thread wakes up only for DP_TASK_EVENT_IPC (no PROCESS/CANCEL), task_stop keeps an indeterminate/stale value but is still used in while (!task_stop), which can lead to premature exit or an infinite loop. Initialize task_stop (e.g., to false) before the loop or at the top of each iteration, and make sure it’s set on all event paths.
| k_thread_start(pdata->thread_id); | ||
|
|
||
| #if IS_ENABLED(CONFIG_SOF_USERSPACE_MOD_IPC_BY_DP_THREAD) | ||
| pdata->ipc_work_item = userspace_proxy_register_ipc_handler(mod, pdata->event); |
There was a problem hiding this comment.
The return value of userspace_proxy_register_ipc_handler() is not checked. If it returns NULL (e.g., missing/failed work item setup), the thread will later hit assert(task_pdata->ipc_work_item) on an IPC event and crash. Handle the NULL return here (propagate an error, disable IPC-by-DP-thread for this task, or avoid asserting later).
| pdata->ipc_work_item = userspace_proxy_register_ipc_handler(mod, pdata->event); | |
| pdata->ipc_work_item = userspace_proxy_register_ipc_handler(mod, pdata->event); | |
| if (!pdata->ipc_work_item) { | |
| tr_err(&dp_tr, "userspace_proxy_register_ipc_handler() failed"); | |
| ret = -ENOMEM; | |
| goto e_thread; | |
| } |
Add the
SOF_USERSPACE_MOD_IPC_BY_DP_THREADKconfig option and implement support for executing userspace module IPC in its DP thread instead of the shared user worker thread.