From 5f2d96afd9a0273e769943b3558e3c91a81bccee 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. The task's LL scheduler is resolved via task->sch (bound in schedule_task_init() using the core-explicit user scheduler list), because zephyr_ll_domain()/cpu_get_id()-based helpers are unreliable in a syscall context: zephyr_ll_domain() reads the kernel scheduler list and returns NULL for the user-space LL scheduler. Add zephyr_domain_thread_tid_for_core() to look up the LL thread for an explicit core without relying on cpu_get_id(). 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 | 17 ++++++ src/schedule/zephyr_ll.c | 54 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index b4b58e6923e2..ffa954bf1e59 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(struct ll_schedule_domain *domain, 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..f6ddcf28165d 100644 --- a/src/schedule/zephyr_domain.c +++ b/src/schedule/zephyr_domain.c @@ -498,6 +498,23 @@ 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. + * + * Unlike zephyr_domain_thread_tid(), this does not rely on cpu_get_id() and + * is therefore safe to call from a syscall context that may run on a core + * different from the task's target core. + */ +struct k_thread *zephyr_domain_thread_tid_for_core(struct ll_schedule_domain *domain, int core) +{ + struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain); + + if (core < 0 || core >= CONFIG_CORE_COUNT) + return NULL; + + return zephyr_domain->domain_thread[core].ll_thread; +} + #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..a4f18893e812 100644 --- a/src/schedule/zephyr_ll.c +++ b/src/schedule/zephyr_ll.c @@ -458,6 +458,36 @@ struct zephyr_ll_task_sem { struct list_item list; }; +#if CONFIG_SOF_USERSPACE_LL +/** + * Resolve the user-space LL scheduler instance for an explicit core. + * + * @param core Target core of the task. + * @return Pointer to the LL scheduler private data, or NULL if not found. + */ +static struct zephyr_ll *zephyr_ll_sch_get_for_core(int core) +{ + struct schedulers *schedulers; + struct schedule_data *sch; + struct list_item *slist; + + if (core < 0 || core >= CONFIG_CORE_COUNT) + return NULL; + + schedulers = *arch_user_schedulers_get_for_core(core); + if (!schedulers) + return NULL; + + list_for_item(slist, &schedulers->list) { + sch = container_of(slist, struct schedule_data, list); + if (sch->type == SOF_SCHEDULE_LL_TIMER) + return sch->data; + } + + return NULL; +} +#endif /* CONFIG_SOF_USERSPACE_LL */ + int z_impl_zephyr_ll_task_sem_alloc(struct task *task) { struct zephyr_ll_pdata *pdata = task->priv_data; @@ -474,6 +504,30 @@ 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. + * + * The scheduler instance is resolved from the kernel-maintained + * per-core user scheduler list rather than from the untrusted + * task->sch pointer, which lives in user-writable memory. + */ + struct zephyr_ll *sch = zephyr_ll_sch_get_for_core(task->core); + + if (sch && sch->ll_domain) { + struct k_thread *ll_tid = + zephyr_domain_thread_tid_for_core(sch->ll_domain, 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 */