From 076e5e16e993143c9fdc266b195245c0a2341ec2 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 10 Aug 2026 15:00:42 +0300 Subject: [PATCH] schedule: zephyr_ll: grant LL thread access to per-task semaphores In CONFIG_SOF_USERSPACE_LL builds the LL scheduler thread runs unprivileged, so every Zephyr kernel object it accesses must be explicitly granted to it. In commit ffa52dfc247e ("schedule: ll: dynamically allocate the semaphore"), task semaphores were converted to dynamically allocated objects. Only the bootstrap task's semaphore was granted to the LL thread (in zephyr_ll_init_context()); tasks created later (e.g. chain_dma) were not, so pausing/stopping such a task while it was running crashed the DSP. Fix the issue by grant the LL scheduling thread access to the task's semaphore at allocation time, from the syscall implementation which runs in privileged context. Add zephyr_domain_thread_tid_for_core() to look up the LL thread for an explicit core without relying on cpu_get_id(), and without dereferencing user-accessible task struct. Fixes: ffa52dfc247e ("schedule: ll: dynamically allocate the semaphore") Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 1 + src/schedule/zephyr_domain.c | 27 +++++++++++++++++++ src/schedule/zephyr_ll.c | 19 +++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index b4b58e6923e2..03991debb0cb 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -328,6 +328,7 @@ struct ll_schedule_domain *zephyr_domain_init(int clk); #define timer_domain_init(timer, clk) zephyr_domain_init(clk) #ifdef CONFIG_SOF_USERSPACE_LL struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain); +struct k_thread *zephyr_domain_thread_tid_for_core(int core); struct k_mem_domain *zephyr_ll_mem_domain(void); #endif /* CONFIG_SOF_USERSPACE_LL */ #ifdef CONFIG_SOF_FULL_ZEPHYR_APPLICATION diff --git a/src/schedule/zephyr_domain.c b/src/schedule/zephyr_domain.c index 681b0c872f58..b37f91285854 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -289,6 +289,14 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain, #else /* CONFIG_SOF_USERSPACE_LL */ +/* + * Kernel-owned per-core LL thread table. Populated from the privileged + * domain-thread init path and consulted by z_impl_zephyr_ll_task_sem_alloc() + * so that privileged code never has to traverse the user-accessible + * scheduler/domain objects to find the LL thread of a given core. + */ +static struct k_thread *ll_thread_tid[CONFIG_CORE_COUNT]; + /* * Privileged thread initialization for userspace LL scheduling. * Creates the scheduling thread, sets up timer, grants access to kernel @@ -343,6 +351,9 @@ static int zephyr_domain_thread_init(struct ll_schedule_domain *domain, INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY, K_USER, K_FOREVER); + /* record in the kernel-only table for syscall-context lookups */ + ll_thread_tid[core] = dt->ll_thread; + #ifdef CONFIG_SCHED_CPU_MASK k_thread_cpu_pin(thread, core); #endif @@ -477,6 +488,7 @@ static void zephyr_domain_thread_free(struct ll_schedule_domain *domain, k_thread_abort(dt->ll_thread); k_object_free(dt->ll_thread); dt->ll_thread = NULL; + ll_thread_tid[core] = NULL; } if (dt->sem) { @@ -498,6 +510,21 @@ struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain) return dt->ll_thread; } +/* + * Return the LL scheduling thread for an explicitly given core. + * + * Reads a kernel-only table keyed by core, so it is safe to call from a + * privileged syscall context without dereferencing any user-accessible + * scheduler or domain object, and without relying on cpu_get_id(). + */ +struct k_thread *zephyr_domain_thread_tid_for_core(int core) +{ + if (core < 0 || core >= CONFIG_CORE_COUNT) + return NULL; + + return ll_thread_tid[core]; +} + #endif /* CONFIG_SOF_USERSPACE_LL */ #if CONFIG_CROSS_CORE_STREAM diff --git a/src/schedule/zephyr_ll.c b/src/schedule/zephyr_ll.c index fd70a4fe31df..145416afad3e 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -474,6 +474,25 @@ int z_impl_zephyr_ll_task_sem_alloc(struct task *task) k_sem_init(ts->sem, 0, 1); +#if CONFIG_SOF_USERSPACE_LL + /* + * The per-task semaphore is signalled from zephyr_ll_task_done(), + * which runs in the (unprivileged) LL scheduler thread when a task is + * freed while it is still running. k_object_alloc() only grants access + * to the calling thread (the IPC handler that creates the task), so the + * LL thread must be granted access explicitly, otherwise its + * k_sem_give() traps with a userspace permission fault. + * + * Resolve the LL thread from kernel-only per-core state keyed by the + * task's target core; never traverse the user-accessible scheduler or + * domain objects from privileged context. + */ + struct k_thread *ll_tid = zephyr_domain_thread_tid_for_core(task->core); + + if (ll_tid) + k_thread_access_grant(ll_tid, ts->sem); +#endif + ts->task = task; pdata->sem_p = ts->sem; /* List is protected by IPC serialization */