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 */