Schedulers

Scheduler Registration

The Schedule API is an abstract layer that allows for scheduler registration, task creation, and scheduling. New schedulers can be added by extending a list of pre-defined schedule types. Currently supported types are: SOF_SCHEDULE_EDF, SOF_SCHEDULE_LL_TIMER and SOF_SCHEDULE_LL_DMA. Every newly-added scheduler should implement at least a mandatory subset of scheduler_ops.

class "scheduler_ops" {
   .. Mandatory ..
   + schedule_task()
   + schedule_task_cancel()
   + schedule_task_free()
   .. Optional ..
   + schedule_task_running()
   + schedule_task_complete()
   + reschedule_task()
   + scheduler_free()
   + scheduler_run()
}

enum schedule_types {
   SOF_SCHEDULE_EDF
   SOF_SCHEDULE_LL_TIMER
   SOF_SCHEDULE_LL_DMA
}
hide schedule_types methods

Figure 16 Scheduler operations

The scheduler_init function must called in order to register the scheduler with a given type, scheduler_ops, and the custom scheduler’s data. Scheduling is as simple as initializing a task with schedule_task_init and passing such an object later on to scheduler operations.

Low Latency Scheduler

The low latency scheduler executes all registered tasks concurrently based on their initial priorities and periods of execution. This task chain is a critical section which removes any possibility of a system interrupt preemption. Thus, every client of the scheduler should be aware of the task’s expected DSP utilization and try not to register long-running processings which can lead to system instability.

The low latency scheduler requires a low latency schedule domain in order to be initialized. Each domain includes a different type of interrupt source that runs the scheduler. Three domains are supported: timer, DMA multiple channels, and DMA single channel. The timer domain is a simple timer-based interrupt that occurs after a specified number of cycles. Schedulers for the DMA multiple channels domain run after every channel interrupt. DMA single channels run only on interrupts coming from one of the channels. The appropriate DMA channel is selected based on the order of task registration and also the task’s period.

Note that even though the domains are shared among all DSP cores, the low latency schedulers are instantiated per core.

class "ll_schedule_data" as lsd {
   - tasks : list
   - num_tasks
   - pcd
   - domain
}
hide lsd methods

class "ll_schedule_domain" as lsdom {
   - last_tick
   - lock
   - total_num_tasks
   - num_clients
   - ticks_per_ms
   - type
   - clk
   - synchronous
   - priv_data
   - registered : array
   - enabled : array
   + domain_register()
   + domain_unregister()
   + domain_enable()
   + domain_disable()
   + domain_set()
   + domain_clear()
   + domain_is_pending()
}

lsd *-- lsdom : contains

Figure 17 Low latency scheduler dependencies

actor client as c

participant ll_scheduler as ls
participant ll_schedule_domain as lsd

-> lsd : domain_init
<-- lsd : domain

-> ls : scheduler_init(&domain)
<-- ls
...
c -> ls : schedule_task(&task)
    activate ls
    ls -> lsd : domain_register(schedule_ll_tasks_run)
    ls -> lsd : domain_enable()
    deactivate ls
c <-- ls
...
ls <- lsd : schedule_ll_tasks_run()
    activate ls
    ls -> lsd : domain_disable()
    loop schedule_ll_is_pending()
        ls -> ls : schedule_ll_tasks_execute()
    end loop
    ls -> lsd : domain_enable()
    deactivate ls

Figure 18 Basic low latency scheduler flow

EDF Scheduler

The EDF scheduler executes all registered tasks based on their deadlines. Every EDF task has its own private stack which allows for full preemption support. The task with an earlier deadline can easily pause the execution of the task with a higher deadline, execute first, and return to the preempted task after that. Since EDF tasks run on a passive irq level, they can be preempted by every interrupt.

The EDF scheduler is instantiated per core.

class "edf_schedule_data" as edf {
   - list : list
   - clock
   - irq
}
hide edf methods

Figure 19 EDF scheduler structure

actor client as c

participant edf_scheduler as edf
participant interrupt_driver as int

-> edf : scheduler_init()
    activate edf
    edf -> int : interrupt_register(edf_scheduler_run)
    deactivate edf
<-- edf
...
c -> edf : schedule_task(&task)
    activate edf
    edf -> int : interrupt_set()
    deactivate edf
c <-- edf

edf <- int : edf_scheduler_run()
    activate edf
    edf -> edf : schedule_task_running()
    edf -> edf : schedule_task_complete()

Figure 20 Basic EDF scheduler flow