From 45cb707f25e1ec6de0df5933c6027a8656a34a50 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 16 Jun 2026 20:24:14 +0300 Subject: [PATCH 1/2] schedule: add support for LL userspace tasks Add support for registering user-space LL tasks, and ability to use the task scheduling functions from user-space. The implementation splits scheduler list into kernel and user portions if SOF is built with CONFIG_SOF_USERSPACE_LL. A scheduler type can be either maintained in kernel or user, never both. With this patch, the SOF_SCHEDULE_LL_TIMER is moved to user managed if CONFIG_SOF_USERSPACE_LL is used. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/schedule.h | 18 +++++----- src/schedule/schedule.c | 29 +++++++++++++++- zephyr/schedule.c | 54 ++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/include/sof/schedule/schedule.h b/src/include/sof/schedule/schedule.h index 9b16a3eff6b1..961994bcd2bf 100644 --- a/src/include/sof/schedule/schedule.h +++ b/src/include/sof/schedule/schedule.h @@ -180,6 +180,12 @@ struct schedulers { */ struct schedulers **arch_schedulers_get(void); +struct schedulers **arch_user_schedulers_get(void); + +#if CONFIG_SOF_USERSPACE_LL +struct schedulers **arch_user_schedulers_get_for_core(int core); +#endif + /** * Retrieves scheduler's data. * @param type SOF_SCHEDULE_ type. @@ -322,17 +328,13 @@ static inline void schedule_free(uint32_t flags) /** See scheduler_ops::scheduler_init_context */ static inline struct k_thread *scheduler_init_context(struct task *task) { - struct schedulers *schedulers = *arch_schedulers_get(); struct schedule_data *sch; - struct list_item *slist; - assert(schedulers); + assert(task && task->sch); + sch = task->sch; - list_for_item(slist, &schedulers->list) { - sch = container_of(slist, struct schedule_data, list); - if (task->type == sch->type && sch->ops->scheduler_init_context) - return sch->ops->scheduler_init_context(sch->data, task); - } + if (sch->ops->scheduler_init_context) + return sch->ops->scheduler_init_context(sch->data, task); return NULL; } diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 1848b603145e..35f3ff181f53 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -22,12 +22,25 @@ SOF_DEFINE_REG_UUID(schedule); DECLARE_TR_CTX(sch_tr, SOF_UUID(schedule_uuid), LOG_LEVEL_INFO); +#ifdef CONFIG_SOF_USERSPACE_LL +static inline bool scheduler_is_user(int type) +{ + /* + * currently only LL managed in user-space, but longterm + * goal is to move all audio application level scheduling + * to user-space and only keep Zephyr scheduler logic in + * kernel + */ + return type == SOF_SCHEDULE_LL_TIMER; +} +#endif + int schedule_task_init(struct task *task, const struct sof_uuid_entry *uid, uint16_t type, uint16_t priority, enum task_state (*run)(void *data), void *data, uint16_t core, uint32_t flags) { - struct schedulers *schedulers = *arch_schedulers_get(); + struct schedulers *schedulers; struct schedule_data *sch = NULL; struct list_item *slist; @@ -36,6 +49,15 @@ int schedule_task_init(struct task *task, return -EINVAL; } +#ifdef CONFIG_SOF_USERSPACE_LL + if (scheduler_is_user(type)) + schedulers = *arch_user_schedulers_get_for_core(core); + else + schedulers = *arch_schedulers_get(); +#else + schedulers = *arch_schedulers_get(); +#endif + if (!schedulers) return -ENODEV; @@ -69,6 +91,11 @@ static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); +#ifdef CONFIG_SOF_USERSPACE_LL + if (scheduler_is_user(scheduler->type)) + sch = arch_user_schedulers_get(); +#endif + if (!*sch) { /* init schedulers list */ *sch = rzalloc(SOF_MEM_FLAG_KERNEL, diff --git a/zephyr/schedule.c b/zephyr/schedule.c index 1e3971a33682..075bb30e3898 100644 --- a/zephyr/schedule.c +++ b/zephyr/schedule.c @@ -14,7 +14,18 @@ #include #include -static APP_SYSUSER_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; +/* Kernel-only scheduler list — depending on how SOF is built, + * either holds all or subset of scheduler types. + * Not accessible from user-space threads. + */ +static struct schedulers *_schedulers[CONFIG_CORE_COUNT]; + +#if CONFIG_SOF_USERSPACE_LL +/* User-accessible scheduler list — holds the subset of scheduler + * types that are managed in user-space + */ +static APP_SYSUSER_BSS struct schedulers *_schedulers_user[CONFIG_CORE_COUNT]; +#endif /** * Retrieves registered schedulers. @@ -22,6 +33,47 @@ static APP_SYSUSER_BSS struct schedulers *_schedulers[CONFIG_CORE_COUNT]; */ struct schedulers **arch_schedulers_get(void) { +#if CONFIG_SOF_USERSPACE_LL + /* user-space callers must use arch_user_schedulers_get() */ + assert(!k_is_user_context()); +#endif return _schedulers + cpu_get_id(); } EXPORT_SYMBOL(arch_schedulers_get); + +/** + * Retrieves registered user schedulers for the current core. + * + * Relies on cpu_get_id(), so it may invoke privileged functions and + * must not be called from a user-space context. User-space callers + * should use arch_user_schedulers_get_for_core() instead. + * + * @return List of registered schedulers. + */ +struct schedulers **arch_user_schedulers_get(void) +{ +#ifdef CONFIG_SOF_USERSPACE_LL + return _schedulers_user + cpu_get_id(); +#else + return NULL; +#endif +} +EXPORT_SYMBOL(arch_user_schedulers_get); + +/** + * Retrieves registered user schedulers for the given core. + * + * Unlike arch_user_schedulers_get(), this takes the core explicitly and + * is therefore safe to call from a user-space context. + * + * @return List of registered schedulers. + */ +struct schedulers **arch_user_schedulers_get_for_core(int core) +{ +#ifdef CONFIG_SOF_USERSPACE_LL + return _schedulers_user + core; +#else + return NULL; +#endif +} +EXPORT_SYMBOL(arch_user_schedulers_get_for_core); From 8892eb0da4401a67b388599c5f75ef9098526d38 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Mon, 22 Jun 2026 14:04:11 +0300 Subject: [PATCH 2/2] schedule: allocate the scheduler objects with sof_heap_alloc() Ensure the scheduler objects and lists of schedulers are allocated such that they can be used with both kernel and user-space LL scheduler implementations. The SOF_MEM_FLAG_KERNEL flag is removed. This flag has been a no-op for a while, and given scheduler list is not always in kernel anymore, it would be highly confusing to keep it. When CONFIG_SOF_USERSPACE_LL is set, the context of all schedulers is managed in the LL user-space domain. Signed-off-by: Kai Vehmanen --- src/schedule/schedule.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/schedule/schedule.c b/src/schedule/schedule.c index 35f3ff181f53..ba6785179011 100644 --- a/src/schedule/schedule.c +++ b/src/schedule/schedule.c @@ -90,20 +90,23 @@ int schedule_task_init(struct task *task, static void scheduler_register(struct schedule_data *scheduler) { struct schedulers **sch = arch_schedulers_get(); + struct k_heap *heap = NULL; #ifdef CONFIG_SOF_USERSPACE_LL - if (scheduler_is_user(scheduler->type)) + if (scheduler_is_user(scheduler->type)) { sch = arch_user_schedulers_get(); + heap = sof_sys_user_heap_get(); + } #endif if (!*sch) { /* init schedulers list */ - *sch = rzalloc(SOF_MEM_FLAG_KERNEL, - sizeof(**sch)); + *sch = sof_heap_alloc(heap, 0, sizeof(**sch), 0); if (!*sch) { tr_err(&sch_tr, "allocation failed"); return; } + memset(*sch, 0, sizeof(**sch)); list_init(&(*sch)->list); } @@ -113,16 +116,23 @@ static void scheduler_register(struct schedule_data *scheduler) void scheduler_init(int type, const struct scheduler_ops *ops, void *data) { struct schedule_data *sch; + struct k_heap *heap = NULL; + +#ifdef CONFIG_SOF_USERSPACE_LL + if (scheduler_is_user(type)) + heap = sof_sys_user_heap_get(); +#endif if (!ops || !ops->schedule_task || !ops->schedule_task_cancel || !ops->schedule_task_free) return; - sch = rzalloc(SOF_MEM_FLAG_KERNEL, sizeof(*sch)); + sch = sof_heap_alloc(heap, SOF_MEM_FLAG_KERNEL, sizeof(*sch), 0); if (!sch) { tr_err(&sch_tr, "allocation failed"); sof_panic(SOF_IPC_PANIC_IPC); } + memset(sch, 0, sizeof(*sch)); list_init(&sch->list); sch->type = type; sch->ops = ops;