[Sound-open-firmware] [PATCH 1/3] ipc: add support for timer topology scheduling source.
Add an option to schedule based on timer or on DAI/DMA IRQ
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/include/uapi/ipc.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/src/include/uapi/ipc.h b/src/include/uapi/ipc.h index acfa05e..472fb41 100644 --- a/src/include/uapi/ipc.h +++ b/src/include/uapi/ipc.h @@ -678,6 +678,7 @@ struct sof_ipc_pipe_new { uint32_t mips; /* worst case instruction count per period */ uint32_t frames_per_sched; /* output frames of pipeline, 0 is variable */ uint32_t xrun_limit_usecs; /* report xruns greater than limit */ + uint32_t timer; /* non zero if timer scheduled otherwise DAI scheduled */ } __attribute__((packed));
/* pipeline construction complete - SOF_IPC_TPLG_PIPE_COMPLETE */
Currently the scheduler will schedule task based on DAI DMA interrupts. This patch also adds the option to also schedule based on a 64 bit timer.
The scheduler will now check the tasks in the queue and it will only run the tasks in the current window otherwise it will set a timer to call schedule() on the next task start time (in another window).
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/audio/dai.c | 2 +- src/audio/pipeline.c | 48 +++++++---- src/include/reef/audio/pipeline.h | 4 +- src/include/reef/schedule.h | 33 ++++--- src/lib/schedule.c | 175 ++++++++++++++++++++++++++++---------- 5 files changed, 182 insertions(+), 80 deletions(-)
diff --git a/src/audio/dai.c b/src/audio/dai.c index e22d21d..2101e00 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -133,7 +133,7 @@ static void dai_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next) }
/* notify pipeline that DAI needs it's buffer processed */ - pipeline_schedule_copy(dev->pipeline, dev); + pipeline_schedule_copy(dev->pipeline, 0);
return; } diff --git a/src/audio/pipeline.c b/src/audio/pipeline.c index c7b95f9..62c21b7 100644 --- a/src/audio/pipeline.c +++ b/src/audio/pipeline.c @@ -184,23 +184,29 @@ static void disconnect_downstream(struct pipeline *p, struct comp_dev *start, }
/* update pipeline state based on cmd */ -static void pipeline_cmd_update(struct pipeline *p, int cmd) +static void pipeline_cmd_update(struct pipeline *p, struct comp_dev *comp, + int cmd) { + if (p->sched_comp != comp) + return; + switch (cmd) { case COMP_CMD_PAUSE: - break; case COMP_CMD_STOP: - break; - case COMP_CMD_RELEASE: - p->xrun_bytes = 0; + pipeline_schedule_cancel(p); break; case COMP_CMD_START: + case COMP_CMD_RELEASE: p->xrun_bytes = 0; + + /* schedule pipeline */ + if (p->ipc_pipe.timer) + pipeline_schedule_copy(p, 0); + break; case COMP_CMD_SUSPEND: break; case COMP_CMD_RESUME: - p->xrun_bytes = 0; break; } } @@ -222,7 +228,9 @@ struct pipeline *pipeline_new(struct sof_ipc_pipe_new *pipe_desc,
/* init pipeline */ p->sched_comp = cd; - task_init(&p->pipe_task, pipeline_task, p); + schedule_task_init(&p->pipe_task, pipeline_task, p); + schedule_task_config(&p->pipe_task, pipe_desc->priority, + pipe_desc->core); list_init(&p->comp_list); list_init(&p->buffer_list); spinlock_init(&p->lock); @@ -243,7 +251,7 @@ int pipeline_free(struct pipeline *p) }
/* remove from any scheduling */ - task_free(&p->pipe_task); + schedule_task_free(&p->pipe_task);
/* disconnect components */ disconnect_downstream(p, p->sched_comp, p->sched_comp); @@ -337,7 +345,8 @@ static int component_op_downstream(struct op_data *op_data, /* send command to the component and update pipeline state */ err = comp_cmd(current, op_data->cmd, op_data->cmd_data); if (err == 0) - pipeline_cmd_update(current->pipeline, op_data->cmd); + pipeline_cmd_update(current->pipeline, current, + op_data->cmd); break; case COMP_OPS_PREPARE: /* prepare the component */ @@ -413,7 +422,8 @@ static int component_op_upstream(struct op_data *op_data, /* send command to the component and update pipeline state */ err = comp_cmd(current, op_data->cmd, op_data->cmd_data); if (err == 0) - pipeline_cmd_update(current->pipeline, op_data->cmd); + pipeline_cmd_update(current->pipeline, current, + op_data->cmd); break; case COMP_OPS_PREPARE: /* prepare the component */ @@ -957,15 +967,13 @@ void pipeline_xrun(struct pipeline *p, struct comp_dev *dev, }
/* notify pipeline that this component requires buffers emptied/filled */ -void pipeline_schedule_copy(struct pipeline *p, struct comp_dev *dev) +void pipeline_schedule_copy(struct pipeline *p, uint64_t start) { - schedule_task(&p->pipe_task, p->ipc_pipe.deadline, - p->ipc_pipe.priority, dev); - - schedule(); + if (p->sched_comp->state == COMP_STATE_ACTIVE) + schedule_task(&p->pipe_task, start, p->ipc_pipe.deadline); }
-void pipeline_schedule_cancel(struct pipeline *p, struct comp_dev *dev) +void pipeline_schedule_cancel(struct pipeline *p) { schedule_task_complete(&p->pipe_task); } @@ -973,8 +981,7 @@ void pipeline_schedule_cancel(struct pipeline *p, struct comp_dev *dev) static void pipeline_task(void *arg) { struct pipeline *p = arg; - struct task *task = &p->pipe_task; - struct comp_dev *dev = task->sdata; + struct comp_dev *dev = p->sched_comp;
tracev_pipe("PWs");
@@ -983,6 +990,11 @@ static void pipeline_task(void *arg) pipeline_copy_to_downstream(dev, dev);
tracev_pipe("PWe"); + + /* now reschedule the task */ + /* TODO: add in scheduling cost and any timer drift */ + if (p->ipc_pipe.timer) + pipeline_schedule_copy(p, p->ipc_pipe.deadline); }
/* init pipeline */ diff --git a/src/include/reef/audio/pipeline.h b/src/include/reef/audio/pipeline.h index 29c509f..909f905 100644 --- a/src/include/reef/audio/pipeline.h +++ b/src/include/reef/audio/pipeline.h @@ -112,8 +112,8 @@ int init_static_pipeline(struct ipc *ipc); int init_pipeline(void);
/* schedule a copy operation for this pipeline */ -void pipeline_schedule_copy(struct pipeline *p, struct comp_dev *dev); -void pipeline_schedule_cancel(struct pipeline *p, struct comp_dev *dev); +void pipeline_schedule_copy(struct pipeline *p, uint64_t start); +void pipeline_schedule_cancel(struct pipeline *p);
/* get time pipeline timestamps from host to dai */ void pipeline_get_timestamp(struct pipeline *p, struct comp_dev *host_dev, diff --git a/src/include/reef/schedule.h b/src/include/reef/schedule.h index b22fb44..6d94788 100644 --- a/src/include/reef/schedule.h +++ b/src/include/reef/schedule.h @@ -37,6 +37,7 @@ #include <reef/reef.h> #include <reef/lock.h> #include <reef/list.h> +#include <reef/work.h>
struct reef;
@@ -47,53 +48,59 @@ struct reef; #define TASK_STATE_PREEMPTED 3 #define TASK_STATE_COMPLETED 4 #define TASK_STATE_FREE 5 +#define TASK_STATE_CANCEL 6
/* task priorities - values same as Linux processes, gives scope for future.*/ #define TASK_PRI_LOW 19 #define TASK_PRI_MED 0 #define TASK_PRI_HIGH -20
+ +/* task descriptor */ struct task { uint16_t core; /* core id to run on */ int16_t priority; /* scheduling priority TASK_PRI_ */ - uint32_t deadline; /* scheduling deadline */ - uint32_t max_rtime; /* max time taken to run */ + uint64_t start; /* scheduling earliest start time */ + uint64_t deadline; /* scheduling deadline */ uint32_t state; /* TASK_STATE_ */ struct list_item list; /* list in scheduler */ + + /* task function and private data */ void *data; - void *sdata; void (*func)(void *arg); + + /* runtime duration in scheduling clock base */ + uint64_t max_rtime; /* max time taken to run */ };
void schedule(void);
-void schedule_task(struct task *task, uint32_t deadline, uint16_t priority, - void *data); - -void schedule_task_core(struct task *task, uint32_t deadline, - uint16_t priority, uint16_t core, void *data); +void schedule_task(struct task *task, uint64_t start, uint64_t deadline);
void schedule_task_complete(struct task *task);
-static inline void task_init(struct task *task, void (*func)(void *), +static inline void schedule_task_init(struct task *task, void (*func)(void *), void *data) { task->core = 0; - task->priority = TASK_PRI_MED; task->state = TASK_STATE_INIT; task->func = func; task->data = data; - task->sdata = NULL; }
-static inline void task_free(struct task *task) +static inline void schedule_task_free(struct task *task) { task->state = TASK_STATE_FREE; task->func = NULL; task->data = NULL; - task->sdata = NULL; }
+static inline void schedule_task_config(struct task *task, uint16_t priority, + uint16_t core) +{ + task->priority = priority; + task->core = core; +}
int scheduler_init(struct reef *reef);
diff --git a/src/lib/schedule.c b/src/lib/schedule.c index 147bfff..dbf278b 100644 --- a/src/lib/schedule.c +++ b/src/lib/schedule.c @@ -39,6 +39,7 @@ #include <reef/debug.h> #include <reef/clock.h> #include <reef/schedule.h> +#include <reef/work.h> #include <platform/timer.h> #include <platform/clk.h> #include <reef/audio/component.h> @@ -49,67 +50,115 @@ struct schedule_data { spinlock_t lock; struct list_item list; /* list of tasks in priority queue */ uint32_t clock; + struct work work; };
static struct schedule_data *sch;
-/* get time delta */ -static inline uint32_t schedule_time_diff(uint32_t current, uint32_t pipe_time) +#define SLOT_ALIGN_TRIES 10 + +/* + * Simple rescheduler to calculate tasks new start time and deadline if + * prevoius deadline was missed. Tries to align at first with current task + * timing, but will just add onto current if too far behind current. + * XRUNs will be propagated upto the host if we have to reschedule. + */ +static inline void edf_reschedule(struct task *task, uint64_t current) { - uint32_t max = MAX_INT; - - /* does work run in next cycle ? */ - if (pipe_time < current) { - max -= current; - max += pipe_time; - return max; - } else - return pipe_time - current; + uint64_t delta = (task->deadline - task->start) << 1; + int i; + + /* try and align task with current scheduling slots */ + for (i = 0; i < SLOT_ALIGN_TRIES; i++) { + + task->start += delta; + + if (task->start > current + delta) { + task->deadline = task->start + delta; + return; + } + } + + /* task has slipped a lot, so just add delay to current */ + task->start = current + delta; + task->deadline = task->start + delta; }
/* - * Find the task with the earliest deadline. This may be the running task or a - * queued task. TODO: Reduce cache invalidations by checking if the currently + * Find the first non running task with the earliest deadline. + * TODO: Reduce cache invalidations by checking if the currently * running task AND the earliest queued task will both complete before their * deadlines. If so, then schedule the earlier queued task after the currently * running task has completed. */ -static inline struct task *edf_get_next(void) +static inline struct task *edf_get_next(uint64_t current, + struct task *ignore) { struct task *task, *next_task = NULL; - struct list_item *clist; - uint32_t next_delta = MAX_INT, current, delta; + struct list_item *clist, *tlist; + uint64_t next_delta = UINT64_MAX, delta, deadline; + int reschedule = 0;
+ /* any tasks in the scheduler ? */ if (list_is_empty(&sch->list)) return NULL;
- /* get the current time */ - current = platform_timer_get(platform_timer); - /* check every queued or running task in list */ - list_for_item(clist, &sch->list) { + list_for_item_safe(clist, tlist, &sch->list) { task = container_of(clist, struct task, list);
+ /* only check queued tasks */ + if (task->state != TASK_STATE_QUEUED) + continue; + + /* include the length of task in deadline calc */ + deadline = task->deadline - task->max_rtime; + /* get earliest deadline */ - delta = schedule_time_diff(current, task->deadline); - if (delta < next_delta) { - next_delta = delta; - next_task = task; + if (current < deadline) { + delta = deadline - current; + + if (delta < next_delta) { + next_delta = delta; + next_task = task; + } + + } else { + /* missed scheduling - will be rescheduled */ + trace_pipe("ed!"); + + /* have we already tried to rescheule ? */ + if (reschedule++) + edf_reschedule(task, current); + else { + /* reschedule failed */ + list_item_del(&task->list); + task->state = TASK_STATE_CANCEL; + } } }
return next_task; }
+/* work set in the future when next task can be scheduled */ +static uint32_t sch_work(void *data, uint32_t delay) +{ + tracev_pipe("wrk"); + schedule(); + return 0; +} + /* * EDF Scheduler - Earliest Deadline First Scheduler. * * Schedule task with the earliest deadline from task list. * Can run in IRQ context. */ -void schedule_edf(void) +struct task *schedule_edf(void) { - struct task *task; + struct task *task, *next_plus1_task = NULL; + uint64_t current; uint32_t flags;
tracev_pipe("edf"); @@ -117,19 +166,34 @@ void schedule_edf(void) /* get next component scheduled */ spin_lock_irq(&sch->lock, flags);
+ /* get the current time */ + current = platform_timer_get(platform_timer); + /* get next task to be scheduled */ - task = edf_get_next(); + task = edf_get_next(current, NULL);
spin_unlock_irq(&sch->lock, flags); interrupt_clear(PLATFORM_SCHEDULE_IRQ);
- /* is task currently running ? */ - if (task == NULL || task->state == TASK_STATE_RUNNING) { - trace_pipe("ed0"); - return; + /* any tasks ? */ + if (task == NULL) + return NULL; + + /* can task be started now ? */ + if (task->start > current) { + /* no, then schedule wake up */ + next_plus1_task = task; + } else { + /* yes, get next task and run this one now */ + next_plus1_task = edf_get_next(current, task); + + /* run current task */ + task->start = current; + arch_run_task(task); }
- arch_run_task(task); + /* teall caller about next task (after current) */ + return next_plus1_task; }
/* delete task from scheduler */ @@ -157,38 +221,50 @@ out: return ret; }
-/* Add a new task to the scheduler to be run */ -void schedule_task(struct task *task, uint32_t deadline, uint16_t priority, - void *data) +/* + * Add a new task to the scheduler to be run and define a scheduling + * window in time for the task to be ran. i.e. task will run between start and + * deadline times. + * + * start is in microseconds relative to last task start time. + * deadline is in microseconds relative to start. + */ +void schedule_task(struct task *task, uint64_t start, uint64_t deadline) { - uint32_t flags, time, current, ticks; + uint32_t flags; + uint64_t current;
- tracev_pipe("add"); + tracev_pipe("ad!");
spin_lock_irq(&sch->lock, flags);
/* is task already running ? - not enough MIPS to complete ? */ if (task->state == TASK_STATE_RUNNING) { trace_pipe("tsk"); - goto out; + spin_unlock_irq(&sch->lock, flags); + return; }
/* get the current time */ current = platform_timer_get(platform_timer);
+ /* calculate start time - TODO: include MIPS */ + if (start == 0) + task->start = current; + else + task->start = task->start + clock_us_to_ticks(sch->clock, start) - + PLATFORM_SCHEDULE_COST; + /* calculate deadline - TODO: include MIPS */ - ticks = clock_us_to_ticks(sch->clock, deadline); - time = current + ticks - PLATFORM_SCHEDULE_COST; + task->deadline = task->start + clock_us_to_ticks(sch->clock, deadline);
/* add task to list */ - task->deadline = time; - task->priority = priority; - task->sdata = data; list_item_append(&task->list, &sch->list); task->state = TASK_STATE_QUEUED; - -out: spin_unlock_irq(&sch->lock, flags); + + /* rerun scheduler */ + schedule(); }
/* Remove a task from the scheduler when complete */ @@ -206,9 +282,15 @@ void schedule_task_complete(struct task *task)
void scheduler_run(void *unused) { + struct task *next_task; + tracev_pipe("run"); + /* EDF is only scheduler supported atm */ - schedule_edf(); + next_task = schedule_edf(); + if (next_task) { + work_reschedule_default_at(&sch->work, next_task->start); + } }
/* run the scheduler */ @@ -234,6 +316,7 @@ int scheduler_init(struct reef *reef) list_init(&sch->list); spinlock_init(&sch->lock); sch->clock = PLATFORM_SCHED_CLOCK; + work_init(&sch->work, sch_work, sch, WORK_ASYNC);
/* configure scheduler interrupt */ interrupt_register(PLATFORM_SCHEDULE_IRQ, scheduler_run, NULL);
Signed-off-by: Liam Girdwood liam.r.girdwood@linux.intel.com --- src/audio/dai.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/audio/dai.c b/src/audio/dai.c index 2101e00..84c8bba 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -83,7 +83,7 @@ static void dai_dma_cb(void *data, uint32_t type, struct dma_sg_elem *next) tracev_dai("irq");
/* is stream stopped or paused ? */ - if (dev->state == COMP_STATE_PAUSED) { + if (dev->state != COMP_STATE_ACTIVE) {
/* stop the DAI */ dai_trigger(dd->dai, COMP_CMD_STOP, dev->params.direction);
participants (1)
-
Liam Girdwood