--- /dev/null
+#include <stdint.h>
+#include <stddef.h>
+
+#include "kernel.h"
+#include "mem.h"
+#include "process.h"
+
+#include "timer.h"
+#include "interrupt.h"
+#include <spinlock.h>
+
+#include <syscall.h>
+#include <cpu.h>
+#include <tty.h>
+
+/* 8 MB stack */
+#define RLIMIT_STACK_VAL 0x800000
+
+/*
+ * memory map for processes
+ * 0xFFFF FFFF F800 0000 and up, kernel, can copy kernel pml4 entries
+ * program load at 4 MB
+ * kernel stack at 128 TB 7F...
+ * stack at 127 TB = 8000 0000 0000 (and down)/ thread stacks go somewhere
+ * stacks could be limited to physmem. perhaps it's ok for each address space
+ * to have it's own total stacks limit
+ * mmaps at 126 TB = 7E00 0000 0000 (and down)
+ * heap at 96 TB = 6000 0000 0000 (and up)
+ * heap at 2 TB?
+ * heap after program load...
+ * program at 4 MB = 40 0000
+ */
+
+/* so, to create a process,
+ * allocate 2 MB for stack,
+ * allocate whatever is needed for the code
+ * load in the code to the code base
+ * set up the registers
+ * set up the stack for the iretq or sysret
+ * iretq or sysret to start the task
+ */
+
+/* current process is per processor? */
+struct process *current_task = 0;
+static struct process *available = 0; /* a stack of unused task structs */
+static struct process mainTask;
+static struct process *idle_task;
+static void idle_task_main() {
+ while (1) {
+ halt();
+ schedule();
+ }
+}
+
+struct pqueue {
+ struct process *head;
+ struct process *tail;
+ struct spinlock_t lock;
+ unsigned int size;
+};
+
+static struct pqueue sleepqueue;
+static struct pqueue runqueue;
+static struct pqueue terminating;
+
+void initqueue(struct pqueue *q) {
+ q->lock = (struct spinlock_t){0};
+ q->head = 0;
+ q->tail = 0;
+ q->size = 0;
+}
+
+void dumpqueue(struct pqueue *q, char *name) {
+ struct process *task;
+
+ if (name) {
+ printk("%u %s:", timer_ticks, name);
+ } else {
+ printk("%u queue %llx:", timer_ticks, q);
+ }
+
+ for (task = q->head; task; task = task->next) {
+ printk(" %u", task->pid);
+ if (task->sleep) {
+ printk(":%u", task->sleep);
+ }
+ }
+ printk("\n");
+}
+
+/* dequeue sleeper is no different than a regular dequeue */
+void enqueue_sleeper(struct pqueue *q, struct process *task) {
+ struct process *sleeper;
+
+ spinlock_acquire(&q->lock);
+
+ task->next = 0;
+ task->prev = 0;
+
+ if (!q->head) {
+ q->head = task;
+ task->prev = task;
+ } else {
+ for (sleeper = q->head; sleeper; sleeper = sleeper->next) {
+ if (task->sleep < sleeper->sleep) {
+ /* insert before this one */
+ task->next = sleeper;
+ task->prev = sleeper->prev;
+ if (sleeper != q->head) {
+ sleeper->prev->next = task;
+ } else {
+ q->head = task;
+ }
+ sleeper->prev = task;
+ break;
+ }
+ }
+
+ if (!sleeper) {
+ /* if we got here, we're the last task */
+ task->next = 0;
+ task->prev = q->head->prev;
+ task->prev->next = task;
+ q->head->prev = task;
+ }
+ }
+
+ q->size++;
+
+ spinlock_release(&q->lock);
+}
+
+void enqueue(struct pqueue *q, struct process *task) {
+ spinlock_acquire(&q->lock);
+
+ task->next = 0;
+ if (q->head) {
+ task->prev = q->head->prev;
+ task->prev->next = task;
+ q->head->prev = task;
+ } else {
+ q->head = task;
+ task->prev = task;
+ }
+ q->size++;
+
+ spinlock_release(&q->lock);
+}
+
+struct process *dequeue(struct pqueue *q) {
+ struct process *task;
+
+ spinlock_acquire(&q->lock);
+ task = q->head;
+ if (task) {
+ q->head = task->next;
+ if (q->head) {
+ q->head->prev = task->prev;
+ }
+ task->next = 0;
+ task->prev = 0;
+ }
+ q->size--;
+ spinlock_release(&q->lock);
+ return task;
+}
+
+void enqueue_runnable(struct process *task) {
+ enqueue(&runqueue, task);
+}
+
+struct process *dequeue_runnable(void) {
+ return dequeue(&runqueue);
+}
+
+pid_t getpid() {
+ return current_task->pid;
+}
+
+void exit(int status) {
+ current_task->status = status;
+
+ /* put in parents process "exited" queue for wait() */
+ schedule();
+}
+
+void terminate(struct process *p) {
+
+ /* remove it from whatever queue it's in */
+ /* enqueue it into terminating queue */
+
+ /* push it on the top of the runqueue */
+ enqueue(&terminating, p);
+ /* free all memory, except maybe kernel stack */
+
+ /* remove from task queues */
+
+ /* add to available */
+ schedule();
+}
+
+void yield(void);
+
+void sleep(uint32_t ticks) {
+ if (current_task->sleep) {
+ panic("tried to sleep pid %u which was already sleeping until %u\n", current_task->pid, current_task->sleep);
+ }
+ current_task->sleep = timer_ticks + ticks;
+ enqueue_sleeper(&sleepqueue, current_task);
+ current_task->flags &= ~TM_RUNNABLE;
+ yield();
+}
+
+static void third_main() {
+ sleep(500);
+ while (1) {
+ printk("%llu: Hello from A pid %lu, cpl = %x\n", timer_ticks, getpid(), current_task->pl);
+ sleep(300+300 * getpid());
+ }
+}
+
+static void other_main() {
+ while (1) {
+ printk("%llu: Hello from B pid %lu\n", timer_ticks, getpid());
+ sleep(10000);
+ }
+}
+
+#define HOG
+#ifdef HOG
+static void hog_main() {
+ static int x = 0;
+ static volatile uint64_t donetil = 0;
+ printk("first scheduled hog\n");
+ while (1) {
+ x++;
+ if (timer_ticks >= donetil) {
+ printk("%llu hog %u running\n", timer_ticks,current_task->pid);
+ donetil += 1000;
+ }
+ }
+}
+#endif
+
+struct interrupt_handler pih;
+
+static void preemption_interrupt(struct interrupt_context *c, void *n) {
+ struct process *sleeper;
+
+ /* check for sleepers */
+ while (sleepqueue.head && sleepqueue.head->sleep <= timer_ticks) {
+ sleeper = dequeue(&sleepqueue);
+ sleeper->sleep = 0;
+ sleeper->flags |= TM_RUNNABLE;
+ enqueue_runnable(sleeper);
+ }
+
+ if (current_task != idle_task) {
+ current_task->quantum--;
+ } else {
+ schedule();
+ }
+
+ if (current_task->quantum == 0) {
+ if (current_task != idle_task) {
+ if (current_task->flags & TM_RUNNABLE) {
+ enqueue_runnable(current_task);
+ }
+ }
+ schedule();
+ }
+ return;
+}
+
+void usermain(void);
+
+void init_tasking() {
+ struct process *task;
+
+ initqueue(&runqueue);
+ initqueue(&sleepqueue);
+ initqueue(&terminating);
+
+ mainTask.reg.cr3 = getcr3();
+ mainTask.reg.rflags = getrflags();
+ mainTask.reg.cs = 0x10;
+ mainTask.reg.ss = 0x18;
+ mainTask.pl = 0x0;
+ mainTask.pid = 0;
+ mainTask.flags = 0;
+
+ idle_task = new_task(idle_task_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL|TASK_NOSCHED);
+
+ /* TODO move these into a dummy/test function */
+ task = new_task(other_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL);
+ task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL);
+ task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL);
+ task = new_task(third_main, mainTask.reg.rflags, MEM_KERNEL, TASK_KERNEL);
+#ifdef HOG
+ task = new_task(hog_main, mainTask.reg.rflags, mainTask.reg.cr3, TASK_KERNEL);
+#endif
+
+ task = new_task(usermain, mainTask.reg.rflags, create_addrspace(), 0);
+ current_task = &mainTask;
+ pih.handler = preemption_interrupt;
+ pih.context = 0;
+ interrupt_add_handler(IRQ0, &pih);
+ printk("set up tasking\n");
+}
+
+struct process *new_task(void (*main)(), uint64_t rflags, uint64_t pagedir, uint64_t flags) {
+ struct process *p;
+
+ p = koalloc(sizeof *p);
+ if (!p) {
+ panic("can't allocate memory for new task\n");
+ return 0;
+ }
+ create_task(p, main, rflags, pagedir, flags);
+ return p;
+}
+
+void setup_usertask(void);
+
+static pid_t next_pid = 2;
+
+pid_t create_task(struct process *task, void (*main)(), uint64_t rflags, uint64_t addrspace, uint64_t flags) {
+
+ task->reg = (struct registers){ 0 }; /* clear out all registers for new task */
+
+ /* roll over? re-use? */
+ /* could possible use bts on max nums. possibly wasteful, and still O(n) */
+ if (!available) {
+ task->pid = next_pid++;
+ }
+
+ task->reg.rflags = rflags;
+ task->reg.cr3 = addrspace;
+ task->kstack = (uint64_t)PHY2VIRTP(palloc()); /* new kernel stack */
+ task->reg.rsp = task->kstack + 0x1000;
+ task->reg.rip = (uint64_t)main;
+ task->reg.cs = 0x10; /* kernel code segment */
+ task->reg.ss = 0x18; /* kernel data segment */
+ task->pl = 0; /* not a user task */
+ task->quantum = 0; /* will get a quantum when it's scheduled */
+ task->flags = TM_RUNNABLE; /* new tasks are runnable */
+
+ if (! (flags & TASK_KERNEL)) {
+ task->main = (int (*)(int,char**))main;
+ task->reg.rdi = (uintptr_t)task->main;
+ task->reg.rip = (uintptr_t)setup_usertask;
+ task->flags |= TM_USER;
+ }
+
+ /* stacks will need thinking */
+ /* stack can't just go at fixed 128 TB, threads need their own */
+ if (! (flags & TASK_KERNEL)) {
+ printk("created user task %u %llx, space = %llx, entry = %llx\n", task->pid, task, task->reg.cr3, task->reg.rip);
+ }
+
+ if (!(flags & TASK_NOSCHED)) {
+ enqueue_runnable(task);
+ }
+
+ return task->pid;
+}
+
+
+void setup_usertask(void) {
+ struct process *task;
+ task = current_task;
+
+ /* need a sysret trampoline for user tasks */
+ //task->reg.rip = (uint64_t)usermodetrampoline;
+ //task->reg.rcx = (uint64_t)main;
+ // hmm. if this is in kernel space, it's going to fail after the sysret
+ // we need to copy the code into the user space, and hope it's PIC...
+
+ /* create a 1 meg map for the user code at the 8 GB mark */
+ printk("setting up usertask %u, space = %llx\n", task->pid, getcr3());
+ vmapz(task->reg.cr3, GB(8), MB(1), MEM_USERSPACE|MEM_RW);
+ printk("mapped 1 MB for user\n");
+ memmove((void *)GB(8), task->main, KB(4));
+ printk("copied main func from %llx to %llx\n", task->main, GB(8));
+ /* copy the user main function to the GB 8 mark */
+ /* hmm, not mapped here..., so have to map it in the trampoline
+ * so it has the user mappings
+ */
+ /* map it into kernel space? will need a mutex on that */
+ /* then just copy the tables? */
+
+ task->reg.rip = GB(8);
+ task->pl = 3;
+
+#if 0
+ task->reg.cs = 0x20;
+ task->reg.ss = 0x28;
+#endif
+
+ /* create the user mode stack */
+ task->stacks = TB(127);
+ vmapz(task->reg.cr3, task->stacks - MB(2), MB(2), MEM_USERSPACE);
+ task->usp = task->stacks; /* user stack pointer */
+
+ /* What if this fails? */
+ /* need a kernel mode stack for syscalls, and possibly interrupts */
+#if 0
+ task->kstack = TB(128);
+ printk("creating 4 KB kernel stack at virtual address %llx\n", task->kstack-KB(4));
+ vmapz(addrspace, task->kstack - KB(4), KB(4), MEM_RW|MAP_TRACE);
+ task->reg.rsp = task->kstack;
+#endif
+ printk("user task %u, kstack rsp = %llx\n", task->pid, task->kstack);
+ //print_decode(0, task->reg.cr3, task->reg.rsp-8);
+#if 0
+ test_address((void *)(task->reg.rsp - 8));
+ allstop();
+#endif
+ /* this won't return */
+ printk("user tramp\n");
+ /* we loaded to the 8 GB mark... */
+ usermodetrampoline( (void (*)()) GB(8), task->reg.rflags);
+}
+
+void schedule(void) {
+ current_task->flags |= TM_SCHEDULE;
+}
+
+/* set the current task for scheduling, and if we're not in an interrupt,
+ * immediately re-schedule. could just halt for now, which will wait for the
+ * next interrupt.
+ */
+void yield(void) {
+ schedule();
+ /* TODO check if in interrupt. if not, then call one? */
+ halt();
+}
+
+int need_schedule(void) {
+ return current_task ? current_task->flags & TM_SCHEDULE : 0;
+}
+
+/* probably want to use lock free queue with compare and swap
+ * see http://preshing.com/20120612/an-introduction-to-lock-free-programming/
+ */
+
+void do_schedule(void) {
+ struct process *prev, *next;
+
+ prev = current_task;
+
+ next = dequeue_runnable();
+ if (!next) {
+ /* no runnable task */
+ next = idle_task;
+ }
+
+ next->flags &= ~TM_SCHEDULE; /* clear the schedule flag */
+ next->quantum = 10; /* smaller slice for same task */
+
+ /* don't run though the trouble if we're not changing tasks */
+ if (next != prev) {
+ next->quantum = 20;
+ dumpqueue(&runqueue, "runqueue");
+ current_task = next;
+ switch_task(&prev->reg, &next->reg);
+ }
+}
+
+void preempt() {
+ schedule();
+}