userspace: proxy: fix multicore work queue handling#11024
Open
serhiy-katsyuba-intel wants to merge 3 commits into
Open
userspace: proxy: fix multicore work queue handling#11024serhiy-katsyuba-intel wants to merge 3 commits into
serhiy-katsyuba-intel wants to merge 3 commits into
Conversation
On Xtensa, with CONFIG_SCHED_CPU_MASK_PIN_ONLY=y, k_thread_cpu_pin() cannot safely change a thread's core after it has been active on another core. Enabling CONFIG_SCHED_CPU_MASK_PIN_ONLY also enables an optimization in the thread-switch code: because Zephyr assumes that a thread will resume on the same core, it does not write back the thread's cached stack. If k_thread_cpu_pin() subsequently moves the thread to another core, the cached stack on the new core contains garbage. The cached stack was not written back on the previous core and is not invalidated on the new core. With CONFIG_SCHED_CPU_MASK_PIN_ONLY disabled, Zephyr writes back the stack when the thread becomes inactive and invalidates it when the thread becomes active on another core. k_work_user_queue_create() starts its worker thread on core 0, leaving no safe way to re-pin it when CONFIG_SCHED_CPU_MASK_PIN_ONLY=y. Because we do not want to disable CONFIG_SCHED_CPU_MASK_PIN_ONLY or use an uncached stack, this file implements a copy of k_work_user_queue_create() that does not start the worker thread. The caller configures the thread, for example by pinning it to the required core, and then calls k_thread_start(). Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
serhiy-katsyuba-intel
requested review from
abonislawski,
dbaluta,
iuliana-prodan,
kv2019i,
lbetlej,
lgirdwood,
lyakh,
mmaka1 and
plbossart
as code owners
July 22, 2026 15:52
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses an Xtensa multicore issue when CONFIG_SCHED_CPU_MASK_PIN_ONLY=y by avoiding post-start CPU re-pinning of Zephyr userspace workqueue threads and restructuring the SOF userspace proxy to use a dedicated (pinned) worker per core.
Changes:
- Add a SOF-local variant of Zephyr
k_work_user_queue_create()that creates (but does not start) the worker thread, allowing the caller to pin it beforek_thread_start(). - Rework the userspace proxy to maintain one userspace IPC worker/workqueue per core instead of a single shared worker.
- Wire the new helper into the Zephyr library build when DP-thread IPC handling is not enabled.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| zephyr/lib/user_work.c | Adds sof_work_user_queue_create() and a local copy of the userspace workqueue thread entry to allow “create then pin then start”. |
| zephyr/include/rtos/user_work.h | Exposes sof_work_user_queue_create() to SOF code. |
| zephyr/CMakeLists.txt | Conditionally includes lib/user_work.c in the Zephyr library build. |
| src/audio/module_adapter/library/userspace_proxy.c | Switches from a single shared userspace IPC worker to per-core workers pinned before start, and removes runtime re-pinning. |
When CONFIG_SCHED_CPU_MASK_PIN_ONLY=y, re-pinning a thread to a new core works only if the thread has never been active on another core. To avoid re-pinning, rework the userspace proxy to use a separate worker per core. Each worker is pinned to its core once before it starts. Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
The proxy now uses a separate worker per core. Each work item is allocated, submitted and processed on the same core, so cross-core coherency is no longer required. The same applies when per-core DP threads process work items. Signed-off-by: Serhiy Katsyuba <serhiy.katsyuba@intel.com>
serhiy-katsyuba-intel
force-pushed
the
pin_only_wa
branch
from
July 22, 2026 16:09
a48d10d to
b38db2d
Compare
lgirdwood
reviewed
Jul 22, 2026
lgirdwood
left a comment
Member
There was a problem hiding this comment.
LGTM, but is there a use case or UT where it is tested ?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On Xtensa, with CONFIG_SCHED_CPU_MASK_PIN_ONLY=y, k_thread_cpu_pin() cannot safely change a thread's core after it has been active on another core. Enabling CONFIG_SCHED_CPU_MASK_PIN_ONLY also enables an optimization in the thread-switch code: because Zephyr assumes that a thread will resume on the same core, it does not write back the thread's cached stack.
If k_thread_cpu_pin() subsequently moves the thread to another core, the cached stack on the new core contains garbage. The cached stack was not written back on the previous core and is not invalidated on the new core. With CONFIG_SCHED_CPU_MASK_PIN_ONLY disabled, Zephyr writes back the stack when the thread becomes inactive and invalidates it when the thread becomes active on another core.
See arch_cohere_stacks() impl here zephyrproject-rtos/zephyr@3606493
k_work_user_queue_create() starts its worker thread on core 0, leaving no safe way to re-pin it when CONFIG_SCHED_CPU_MASK_PIN_ONLY=y. Because we do not want to disable CONFIG_SCHED_CPU_MASK_PIN_ONLY or use an uncached stack, we implement a copy of k_work_user_queue_create() that does not start the worker thread. The caller configures the thread, for example by pinning it to the required core, and then calls k_thread_start().
To avoid re-pinning, rework the userspace proxy to use a separate worker per core. Each worker is pinned to its core once before it starts.