--- /dev/null
+#include <lwk/kernel.h>
+#include <lwk/spinlock.h>
+#include <lwk/percpu.h>
+#include <lwk/aspace.h>
+#include <lwk/sched.h>
+#include <lwk/xcall.h>
+
+struct run_queue {
+ spinlock_t lock;
+ size_t num_tasks;
+ struct list_head task_list;
+ struct task_struct * idle_task;
+};
+
+static DEFINE_PER_CPU(struct run_queue, run_queue);
+
+static void
+idle_task_loop(void) {
+ while (1) {
+ arch_idle_task_loop_body();
+ schedule();
+ }
+}
+
+int __init
+sched_subsys_init(void)
+{
+ id_t cpu_id;
+ struct run_queue *runq;
+ struct task_struct *idle_task;
+ start_state_t start_state;
+ int status;
+
+ /* Reserve the idle tasks' ID. All idle tasks share the same ID. */
+ status = __task_reserve_id(IDLE_TASK_ID);
+ if (status)
+ panic("Failed to reserve IDLE_TASK_ID (status=%d).", status);
+
+ /* Initialize each CPU's run queue */
+ for_each_cpu_mask(cpu_id, cpu_present_map) {
+ runq = &per_cpu(run_queue, cpu_id);
+
+ spin_lock_init(&runq->lock);
+ runq->num_tasks = 0;
+ list_head_init(&runq->task_list);
+
+ /*
+ * Create this CPU's idle task. When a CPU has no
+ * other work to do, it runs the idle task.
+ */
+ start_state.uid = 0;
+ start_state.gid = 0;
+ start_state.aspace_id = KERNEL_ASPACE_ID;
+ start_state.entry_point = (vaddr_t)idle_task_loop;
+ start_state.stack_ptr = 0; /* will be set automatically */
+ start_state.cpu_id = cpu_id;
+ start_state.cpumask = NULL;
+
+ status = __task_create(IDLE_TASK_ID, "idle_task", &start_state,
+ &idle_task);
+ if (status)
+ panic("Failed to create idle_task (status=%d).",status);
+
+ runq->idle_task = idle_task;
+ }
+
+ return 0;
+}
+
+void
+sched_add_task(struct task_struct *task)
+{
+ id_t cpu = task->cpu_id;
+ struct run_queue *runq;
+ unsigned long irqstate;
+
+ runq = &per_cpu(run_queue, cpu);
+ spin_lock_irqsave(&runq->lock, irqstate);
+ list_add_tail(&task->sched_link, &runq->task_list);
+ ++runq->num_tasks;
+ spin_unlock_irqrestore(&runq->lock, irqstate);
+
+ if (cpu != this_cpu)
+ xcall_reschedule(cpu);
+}
+
+void
+sched_del_task(struct task_struct *task)
+{
+ struct run_queue *runq;
+ unsigned long irqstate;
+
+ runq = &per_cpu(run_queue, task->cpu_id);
+ spin_lock_irqsave(&runq->lock, irqstate);
+ list_del(&task->sched_link);
+ --runq->num_tasks;
+ spin_unlock_irqrestore(&runq->lock, irqstate);
+}
+
+int
+sched_wakeup_task(struct task_struct *task, taskstate_t valid_states)
+{
+ id_t cpu;
+ struct run_queue *runq;
+ int status;
+ unsigned long irqstate;
+
+ /* Protect against the task being migrated to a different CPU */
+repeat_lock_runq:
+ cpu = task->cpu_id;
+ runq = &per_cpu(run_queue, cpu);
+ spin_lock_irqsave(&runq->lock, irqstate);
+ if (cpu != task->cpu_id) {
+ spin_unlock_irqrestore(&runq->lock, irqstate);
+ goto repeat_lock_runq;
+ }
+ if (task->state & valid_states) {
+ set_mb(task->state, TASKSTATE_READY);
+ status = 0;
+ } else {
+ status = -EINVAL;
+ }
+ spin_unlock_irqrestore(&runq->lock, irqstate);
+
+ if (!status && (cpu != this_cpu))
+ xcall_reschedule(cpu);
+
+ return status;
+}
+
+static void
+context_switch(struct task_struct *prev, struct task_struct *next)
+{
+ /* Switch to the next task's address space */
+ if (prev->aspace != next->aspace)
+ arch_aspace_activate(next->aspace);
+
+ /**
+ * Switch to the next task's register state and kernel stack.
+ * There are three tasks involved in a context switch:
+ * 1. The previous task
+ * 2. The next task
+ * 3. The task that was running when next was suspended
+ * arch_context_switch() returns 1 so that we can maintain
+ * the correct value of prev. Otherwise, the restored register
+ * state of next would have prev set to 3, which we don't care
+ * about (it may have moved CPUs, been destroyed, etc.).
+ */
+ prev = arch_context_switch(prev, next);
+
+ /* Prevent compiler from optimizing beyond this point */
+ barrier();
+}
+
+void
+schedule(void)
+{
+ struct run_queue *runq = &per_cpu(run_queue, this_cpu);
+ struct task_struct *prev = current, *next = NULL, *task;
+
+ spin_lock_irq(&runq->lock);
+
+ /* Move the currently running task to the end of the run queue */
+ if (!list_empty(&prev->sched_link)) {
+ list_del(&prev->sched_link);
+ /* If the task has exited, don't re-link it */
+ if (prev->state != TASKSTATE_EXIT_ZOMBIE)
+ list_add_tail(&prev->sched_link, &runq->task_list);
+ }
+
+ /* Look for a ready to execute task */
+ list_for_each_entry(task, &runq->task_list, sched_link) {
+ if (task->state == TASKSTATE_READY) {
+ next = task;
+ break;
+ }
+ }
+
+ /* If no tasks are ready to run, run the idle task */
+ if (next == NULL)
+ next = runq->idle_task;
+
+ if (prev != next) {
+ context_switch(prev, next);
+ /* next is now running, since it may have changed CPUs while
+ * it was sleeping, we need to refresh local variables */
+ runq = &per_cpu(run_queue, this_cpu);
+ }
+
+ spin_unlock_irq(&runq->lock);
+}
+
+void
+schedule_new_task_tail(void)
+{
+ struct run_queue *runq = &per_cpu(run_queue, this_cpu);
+ BUG_ON(irqs_enabled());
+ spin_unlock(&runq->lock); /* keep IRQs disabled, arch code will
+ * re-enable IRQs as part of starting
+ * the new task */
+}