Skip to content

Commit 021a27e

Browse files
zephyr: add userspace work queue
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>
1 parent 69ea9d3 commit 021a27e

3 files changed

Lines changed: 118 additions & 0 deletions

File tree

zephyr/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,10 @@ zephyr_library_sources(
599599
lib.c
600600
)
601601

602+
zephyr_library_sources_ifndef(CONFIG_SOF_USERSPACE_MOD_IPC_BY_DP_THREAD
603+
lib/user_work.c
604+
)
605+
602606
# SOF module interface functions
603607
add_subdirectory(../src/module module_unused_install/)
604608

zephyr/include/rtos/user_work.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* SPDX-License-Identifier: BSD-3-Clause
2+
*
3+
* Copyright 2026 Intel Corporation. All rights reserved.
4+
*/
5+
6+
#ifndef __ZEPHYR_RTOS_USER_WORK_H__
7+
#define __ZEPHYR_RTOS_USER_WORK_H__
8+
9+
#include <zephyr/kernel.h>
10+
11+
/**
12+
* Create a userspace work queue without starting its worker thread.
13+
*
14+
* Based on Zephyr's k_work_user_queue_create(), but does not start its
15+
* worker thread. The caller must configure the thread, for example, pin it
16+
* to the appropriate core, and call k_thread_start() when it is ready to
17+
* process work.
18+
*
19+
* @param work_q Work queue to initialize.
20+
* @param stack Worker thread stack.
21+
* @param stack_size Size of @p stack in bytes.
22+
* @param prio Worker thread priority.
23+
* @param name Optional worker thread name.
24+
*/
25+
void sof_work_user_queue_create(struct k_work_user_q *work_q, k_thread_stack_t *stack,
26+
size_t stack_size, int prio, const char *name);
27+
28+
#endif /* __ZEPHYR_RTOS_USER_WORK_H__ */

zephyr/lib/user_work.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) 2018 Intel Corporation
3+
* Copyright (c) 2016 Wind River Systems, Inc.
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
/*
9+
* This file is mostly copied from Zephyr's user_work.c. Retain the original
10+
* license header above.
11+
*/
12+
13+
/*
14+
* On Xtensa, with CONFIG_SCHED_CPU_MASK_PIN_ONLY=y, k_thread_cpu_pin()
15+
* cannot safely change a thread's core after it has been active on another
16+
* core. Enabling CONFIG_SCHED_CPU_MASK_PIN_ONLY also enables an optimization
17+
* in the thread-switch code: because Zephyr assumes that a thread will resume
18+
* on the same core, it does not write back the thread's cached stack.
19+
*
20+
* If k_thread_cpu_pin() subsequently moves the thread to another core, the
21+
* cached stack on the new core contains garbage. The cached stack was not
22+
* written back on the previous core and is not invalidated on the new core.
23+
* With CONFIG_SCHED_CPU_MASK_PIN_ONLY disabled, Zephyr writes back the stack
24+
* when the thread becomes inactive and invalidates it when the thread becomes
25+
* active on another core.
26+
*
27+
* k_work_user_queue_create() starts its worker thread on core 0, leaving no
28+
* safe way to re-pin it when CONFIG_SCHED_CPU_MASK_PIN_ONLY=y. Because we do
29+
* not want to disable CONFIG_SCHED_CPU_MASK_PIN_ONLY or use an uncached stack,
30+
* this file implements a copy of k_work_user_queue_create() that does not
31+
* start the worker thread. The caller configures the thread, for example by
32+
* pinning it to the required core, and then calls k_thread_start().
33+
*/
34+
35+
#include <zephyr/kernel.h>
36+
37+
/* This is an intact copy of Zephyr's z_work_user_q_main(). */
38+
static void z_work_user_q_main(void *work_q_ptr, void *p2, void *p3)
39+
{
40+
struct k_work_user_q *work_q = work_q_ptr;
41+
42+
ARG_UNUSED(p2);
43+
ARG_UNUSED(p3);
44+
45+
while (true) {
46+
struct k_work_user *work;
47+
k_work_user_handler_t handler;
48+
49+
work = k_queue_get(&work_q->queue, K_FOREVER);
50+
if (work == NULL) {
51+
continue;
52+
}
53+
54+
handler = work->handler;
55+
__ASSERT(handler != NULL, "handler must be provided");
56+
57+
/* Reset pending state so it can be resubmitted by handler */
58+
if (atomic_test_and_clear_bit(&work->flags,
59+
K_WORK_USER_STATE_PENDING)) {
60+
handler(work);
61+
}
62+
63+
/* Make sure we don't hog up the CPU if the FIFO never (or
64+
* very rarely) gets empty.
65+
*/
66+
k_yield();
67+
}
68+
}
69+
70+
/* This is Zephyr's k_work_user_queue_create() with k_thread_start() removed. */
71+
void sof_work_user_queue_create(struct k_work_user_q *work_q, k_thread_stack_t *stack,
72+
size_t stack_size, int prio, const char *name)
73+
{
74+
k_queue_init(&work_q->queue);
75+
76+
/* Created worker thread will inherit object permissions and memory
77+
* domain configuration of the caller
78+
*/
79+
k_thread_create(&work_q->thread, stack, stack_size, z_work_user_q_main,
80+
work_q, NULL, NULL, prio, K_USER | K_INHERIT_PERMS,
81+
K_FOREVER);
82+
k_object_access_grant(&work_q->queue, &work_q->thread);
83+
if (name != NULL) {
84+
k_thread_name_set(&work_q->thread, name);
85+
}
86+
}

0 commit comments

Comments
 (0)