[Sound-open-firmware] [PATCH 2/3] scheduler: Add support for 64bit timer driven scheduling.
Liam Girdwood
liam.r.girdwood at linux.intel.com
Fri Sep 22 01:32:34 CEST 2017
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 at 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);
--
2.11.0
More information about the Sound-open-firmware
mailing list