Skip to content

Commit 04e2cf3

Browse files
committed
schedule: zephyr_ll: implement user/kernel split with init_context()
Use the new scheduler_init_context() and domain_thread_init/free() interfaces to separate LL scheduler logic into privileged and unprivileged parts. The latter can be used by the audio pipeline to add, remove and schedule tasks from the user-space audio thread. The privileged interfaces are used to set up the scheduler during firmware boot. Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
1 parent 306553a commit 04e2cf3

2 files changed

Lines changed: 158 additions & 62 deletions

File tree

src/schedule/zephyr_domain.c

Lines changed: 121 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ static void zephyr_domain_thread_fn(void *p1, void *p2, void *p3)
126126
}
127127
#endif
128128

129-
dt->handler(dt->arg);
129+
if (dt->handler)
130+
dt->handler(dt->arg);
130131

131132
#ifdef CONFIG_SCHEDULE_LL_STATS_LOG
132133
cycles1 = k_cycle_get_32();
@@ -289,63 +290,65 @@ static int zephyr_domain_unregister(struct ll_schedule_domain *domain,
289290

290291
#else /* CONFIG_SOF_USERSPACE_LL */
291292

292-
/* User-space implementation for register/unregister */
293-
294-
static int zephyr_domain_register_user(struct ll_schedule_domain *domain,
295-
struct task *task,
296-
void (*handler)(void *arg), void *arg)
293+
/*
294+
* Privileged thread initialization for userspace LL scheduling.
295+
* Creates the scheduling thread, sets up timer, grants access to kernel
296+
* objects. Must be called from kernel context before any user-space
297+
* domain_register() calls.
298+
*/
299+
static int zephyr_domain_thread_init(struct ll_schedule_domain *domain,
300+
struct task *task)
297301
{
298302
struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain);
299-
int core = cpu_get_id();
300-
struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core;
303+
struct zephyr_domain_thread *dt;
301304
char thread_name[] = "ll_thread0";
302305
k_tid_t thread;
306+
int core = task->core;
303307

304-
tr_dbg(&ll_tr, "entry");
308+
tr_dbg(&ll_tr, "thread_init entry");
305309

306-
/* domain work only needs registered once on each core */
307-
if (dt->handler)
308-
return 0;
310+
if (core >= CONFIG_CORE_COUNT)
311+
return -EINVAL;
309312

310-
__ASSERT_NO_MSG(task->core == core);
313+
dt = zephyr_domain->domain_thread + core;
311314

312-
dt->handler = handler;
313-
dt->arg = arg;
315+
/* thread only needs to be created once per core */
316+
if (dt->ll_thread)
317+
return 0;
318+
319+
dt->handler = NULL;
320+
dt->arg = NULL;
314321

315322
/* 10 is rather random, we better not accumulate 10 missed timer interrupts */
316323
k_sem_init(dt->sem, 0, 10);
317324

318325
thread_name[sizeof(thread_name) - 2] = '0' + core;
319326

327+
/* Allocate thread structure dynamically */
328+
dt->ll_thread = k_object_alloc(K_OBJ_THREAD);
320329
if (!dt->ll_thread) {
321-
/* Allocate thread structure dynamically */
322-
dt->ll_thread = k_object_alloc(K_OBJ_THREAD);
323-
if (!dt->ll_thread) {
324-
tr_err(&ll_tr, "Failed to allocate thread object for core %d", core);
325-
dt->handler = NULL;
326-
dt->arg = NULL;
327-
return -ENOMEM;
328-
}
330+
tr_err(&ll_tr, "Failed to allocate thread object for core %d", core);
331+
return -ENOMEM;
332+
}
329333

330-
thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE,
331-
zephyr_domain_thread_fn, zephyr_domain,
332-
INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY,
333-
K_USER, K_FOREVER);
334+
thread = k_thread_create(dt->ll_thread, ll_sched_stack[core], ZEPHYR_LL_STACK_SIZE,
335+
zephyr_domain_thread_fn, zephyr_domain,
336+
INT_TO_POINTER(core), NULL, CONFIG_LL_THREAD_PRIORITY,
337+
K_USER, K_FOREVER);
334338

335339
#ifdef CONFIG_SCHED_CPU_MASK
336-
k_thread_cpu_mask_clear(thread);
337-
k_thread_cpu_mask_enable(thread, core);
340+
k_thread_cpu_mask_clear(thread);
341+
k_thread_cpu_mask_enable(thread, core);
338342
#endif
339-
k_thread_name_set(thread, thread_name);
343+
k_thread_name_set(thread, thread_name);
340344

341-
k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread);
342-
k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer);
343-
user_grant_dai_access_all(thread);
344-
user_grant_dma_access_all(thread);
345-
tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core);
345+
k_mem_domain_add_thread(zephyr_ll_mem_domain(), thread);
346+
k_thread_access_grant(thread, dt->sem, domain->lock, zephyr_domain->timer);
347+
user_grant_dai_access_all(thread);
348+
user_grant_dma_access_all(thread);
349+
tr_dbg(&ll_tr, "granted LL access to thread %p (core %d)", thread, core);
346350

347-
k_thread_start(thread);
348-
}
351+
k_thread_start(thread);
349352

350353
k_mutex_lock(domain->lock, K_FOREVER);
351354
if (!k_timer_user_data_get(zephyr_domain->timer)) {
@@ -368,6 +371,43 @@ static int zephyr_domain_register_user(struct ll_schedule_domain *domain,
368371
return 0;
369372
}
370373

374+
/*
375+
* User-space register: bookkeeping only. The privileged thread setup has
376+
* already been done by domain_thread_init() called from kernel context.
377+
*/
378+
static int zephyr_domain_register_user(struct ll_schedule_domain *domain,
379+
struct task *task,
380+
void (*handler)(void *arg), void *arg)
381+
{
382+
struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain);
383+
struct zephyr_domain_thread *dt;
384+
int core;
385+
386+
tr_dbg(&ll_tr, "register_user entry");
387+
388+
if (task->core >= CONFIG_CORE_COUNT)
389+
return -EINVAL;
390+
391+
core = task->core;
392+
dt = zephyr_domain->domain_thread + core;
393+
394+
if (!dt->ll_thread) {
395+
tr_err(&ll_tr, "domain_thread_init() not called for core %d", core);
396+
return -EINVAL;
397+
}
398+
399+
__ASSERT_NO_MSG(!dt->handler || dt->handler == handler);
400+
if (dt->handler)
401+
return 0;
402+
403+
dt->handler = handler;
404+
dt->arg = arg;
405+
406+
tr_info(&ll_tr, "task registered on core %d", core);
407+
408+
return 0;
409+
}
410+
371411
static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain,
372412
struct task *task, uint32_t num_tasks)
373413
{
@@ -382,30 +422,58 @@ static int zephyr_domain_unregister_user(struct ll_schedule_domain *domain,
382422

383423
k_mutex_lock(domain->lock, K_FOREVER);
384424

385-
if (!atomic_read(&domain->total_num_tasks)) {
386-
/* Disable the watchdog */
425+
zephyr_domain->domain_thread[core].handler = NULL;
426+
427+
k_mutex_unlock(domain->lock);
428+
429+
/*
430+
* In this user thread implementation, the timer is left
431+
* running until privileged domain_thread_free() is called
432+
* to clean up resources.
433+
*/
434+
435+
tr_dbg(&ll_tr, "exit");
436+
437+
return 0;
438+
}
439+
440+
/*
441+
* Free resources acquired by zephyr_domain_thread_init().
442+
* Stops the timer, aborts the scheduling thread and frees the thread object.
443+
* Must be called from kernel context.
444+
*/
445+
static void zephyr_domain_thread_free(struct ll_schedule_domain *domain,
446+
uint32_t num_tasks)
447+
{
448+
struct zephyr_domain *zephyr_domain = ll_sch_domain_get_pdata(domain);
449+
int core = cpu_get_id();
450+
struct zephyr_domain_thread *dt = zephyr_domain->domain_thread + core;
451+
452+
tr_dbg(&ll_tr, "thread_free entry, core %d, num_tasks %u", core, num_tasks);
453+
454+
/* Still tasks on other cores, only clean up this core's thread */
455+
k_mutex_lock(domain->lock, K_FOREVER);
456+
457+
if (!num_tasks && !atomic_read(&domain->total_num_tasks)) {
458+
/* Last task globally: stop the timer and watchdog */
387459
watchdog_disable(core);
388460

389461
k_timer_stop(zephyr_domain->timer);
390462
k_timer_user_data_set(zephyr_domain->timer, NULL);
391463
}
392464

393-
zephyr_domain->domain_thread[core].handler = NULL;
465+
dt->handler = NULL;
466+
dt->arg = NULL;
394467

395468
k_mutex_unlock(domain->lock);
396469

397-
tr_info(&ll_tr, "domain->type %d domain->clk %d",
398-
domain->type, domain->clk);
399-
400-
/* Thread not removed here, only the timer is stopped.
401-
* Thread object cleanup would require k_thread_abort() which cannot
402-
* be safely called from this context. The thread remains allocated
403-
* but dormant until next registration or system shutdown.
404-
*/
405-
406-
tr_dbg(&ll_tr, "exit");
470+
if (dt->ll_thread) {
471+
k_thread_abort(dt->ll_thread);
472+
k_object_free(dt->ll_thread);
473+
dt->ll_thread = NULL;
474+
}
407475

408-
return 0;
476+
tr_info(&ll_tr, "thread_free done, core %d", core);
409477
}
410478

411479
struct k_thread *zephyr_domain_thread_tid(struct ll_schedule_domain *domain)
@@ -450,6 +518,8 @@ APP_TASK_DATA static const struct ll_schedule_domain_ops zephyr_domain_ops = {
450518
#ifdef CONFIG_SOF_USERSPACE_LL
451519
.domain_register = zephyr_domain_register_user,
452520
.domain_unregister = zephyr_domain_unregister_user,
521+
.domain_thread_init = zephyr_domain_thread_init,
522+
.domain_thread_free = zephyr_domain_thread_free,
453523
#else
454524
.domain_register = zephyr_domain_register,
455525
.domain_unregister = zephyr_domain_unregister,

src/schedule/zephyr_ll.c

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -360,17 +360,7 @@ static int zephyr_ll_task_schedule_common(struct zephyr_ll *sch, struct task *ta
360360

361361
ret = domain_register(sch->ll_domain, task, &schedule_ll_callback, sch);
362362
if (ret < 0)
363-
tr_err(&ll_tr, "cannot register domain %d",
364-
ret);
365-
366-
#if CONFIG_SOF_USERSPACE_LL
367-
k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock);
368-
369-
tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock,
370-
zephyr_domain_thread_tid(sch->ll_domain));
371-
tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock,
372-
zephyr_domain_thread_tid(sch->ll_domain));
373-
#endif
363+
tr_err(&ll_tr, "cannot register domain %d", ret);
374364

375365
return 0;
376366
}
@@ -509,15 +499,51 @@ static void zephyr_ll_scheduler_free(void *data, uint32_t flags)
509499
if (sch->n_tasks)
510500
tr_err(&ll_tr, "%u tasks are still active!",
511501
sch->n_tasks);
502+
503+
#if CONFIG_SOF_USERSPACE_LL
504+
domain_thread_free(sch->ll_domain, sch->n_tasks);
505+
#endif
512506
}
513507

508+
#if CONFIG_SOF_USERSPACE_LL
509+
struct k_thread *zephyr_ll_init_context(void *data, struct task *task)
510+
{
511+
struct zephyr_ll *sch = data;
512+
int ret;
513+
514+
/*
515+
* Use domain_thread_init() for privileged setup (thread creation,
516+
* timer, access grants). domain_register() is now bookkeeping only
517+
* and will be called later from user context when scheduling tasks.
518+
*/
519+
ret = domain_thread_init(sch->ll_domain, task);
520+
if (ret < 0) {
521+
tr_err(&ll_tr, "cannot init_context %d", ret);
522+
return NULL;
523+
}
524+
525+
assert(!k_is_user_context());
526+
k_thread_access_grant(zephyr_domain_thread_tid(sch->ll_domain), sch->lock);
527+
528+
tr_dbg(&ll_tr, "granting access to lock %p for thread %p", sch->lock,
529+
zephyr_domain_thread_tid(sch->ll_domain));
530+
tr_dbg(&ll_tr, "granting access to domain lock %p for thread %p", &sch->ll_domain->lock,
531+
zephyr_domain_thread_tid(sch->ll_domain));
532+
533+
return zephyr_domain_thread_tid(sch->ll_domain);
534+
}
535+
#endif
536+
514537
static const struct scheduler_ops zephyr_ll_ops = {
515538
.schedule_task = zephyr_ll_task_schedule,
516539
.schedule_task_before = zephyr_ll_task_schedule_before,
517540
.schedule_task_after = zephyr_ll_task_schedule_after,
518541
.schedule_task_free = zephyr_ll_task_free,
519542
.schedule_task_cancel = zephyr_ll_task_cancel,
520543
.scheduler_free = zephyr_ll_scheduler_free,
544+
#if CONFIG_SOF_USERSPACE_LL
545+
.scheduler_init_context = zephyr_ll_init_context,
546+
#endif
521547
};
522548

523549
#if CONFIG_SOF_USERSPACE_LL

0 commit comments

Comments
 (0)