aboutsummaryrefslogtreecommitdiff
path: root/src/tcb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tcb.c')
-rw-r--r--src/tcb.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/tcb.c b/src/tcb.c
new file mode 100644
index 0000000..c0f82cc
--- /dev/null
+++ b/src/tcb.c
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file tcb.c
+ * Thread control block handling implementation.
+ */
+
+#include <kmi/tcb.h>
+#include <kmi/mem.h>
+#include <kmi/conf.h>
+#include <kmi/pmem.h>
+#include <kmi/vmem.h>
+#include <kmi/nodes.h>
+#include <kmi/types.h>
+#include <kmi/assert.h>
+#include <kmi/string.h>
+#include <kmi/canary.h>
+
+#include <arch/cpu.h>
+#include <arch/vmem.h>
+#include <arch/proc.h>
+
+/* arguably exessively many globals... */
+/** Thread ID to start looking from when allocating new ID. */
+static id_t start_tid = 0;
+
+/** Total number of possible thread IDs. */
+static id_t num_tids;
+
+/** Pointer to array of \ref tcb structures. Length of the array is \c num_tids.*/
+static struct tcb **tcbs;
+
+/**
+ * Array of thread control block associated with each cpu.
+ *
+ * \todo If we ever support systems with massive amounts of cpus, this should probably
+ * be allocated at runtime.
+ */
+static struct tcb *__cpu_tcb[MAX_CPUS] = { 0 };
+
+void init_tcbs()
+{
+ /* MM_O1 is 2MiB on riscv64, so 262144 different possible thread ids.
+ * Should be enough, if we're really strapped for memory I might try
+ * something smaller but this is fine for now. */
+ tcbs = (struct tcb **)alloc_page(MM_O1);
+ num_tids = order_size(MM_O1) / sizeof(struct tcb *);
+ catastrophic_assert(is_powerof2(num_tids));
+ memset(tcbs, 0, order_size(MM_O1));
+}
+
+void destroy_tcbs()
+{
+ free_page(MM_O1, (pm_t)tcbs);
+}
+
+/**
+ * Allocate a new thread ID.
+ *
+ * @param t Thread to allocate new ID to.
+ * @return Allocated ID.
+ */
+static id_t __alloc_tid(struct tcb *t)
+{
+ id_t stop_tid = start_tid - 1;
+ /** \todo this would need some locking or something... */
+ for (id_t i = start_tid;; ++i) {
+ if (i <= 0)
+ i = 1;
+
+ /* we're completely full */
+ if (i == stop_tid)
+ return ERR_NF;
+
+ if (get_tcb(i) || i == 0)
+ continue;
+
+ tcbs[i & (num_tids - 1)] = t;
+ start_tid = i + 1;
+ return i;
+ }
+
+ return ERR_NF;
+}
+
+/**
+ * Setup thread stack.
+ *
+ * @param t Thread to setup stack for.
+ * @param bytes Minimum size of stack.
+ * @return Base of allocated stack.
+ */
+static vm_t __setup_thread_stack(struct tcb *t, size_t bytes)
+{
+ return alloc_uvmem(t, bytes, VM_V | VM_R | VM_W | VM_U);
+}
+
+stat_t alloc_stack(struct tcb *t)
+{
+ /* get parent process */
+ struct tcb *p = get_tcb(t->eid);
+
+ t->thread_stack = __setup_thread_stack(p, __thread_stack_size);
+ if (!t->thread_stack)
+ return ERR_OOMEM;
+
+ /** \todo this only allows for a global stack size, what if a user wants
+ * per thread stack sizes? */
+ t->thread_stack_top = t->thread_stack + __thread_stack_size;
+ return OK;
+}
+
+struct tcb *create_thread(struct tcb *p)
+{
+ hard_assert(tcbs, 0);
+
+ vm_t bottom = alloc_page(KERNEL_STACK_PAGE_ORDER);
+ /* move tcb to top of kernel stack, keeping alignment in check
+ * (hopefully) */
+ /** \todo check alignment */
+ struct tcb *t = (struct tcb *)align_down(
+ bottom + order_size(MM_O0) - sizeof(struct tcb), sizeof(long));
+ memset(t, 0, sizeof(struct tcb));
+
+ id_t tid = __alloc_tid(t);
+ tcbs[tid] = t;
+ t->tid = tid;
+ t->dead = false;
+
+ if (likely(p)) {
+ t->pid = p->pid;
+ /** @todo I'm assuming two threads can share the same vmem
+ * structure, this works on riscv but in the event that other
+ * systems don't we can easily turn this into a clone_uvmem. */
+ t->proc.vmem = p->proc.vmem;
+ } else {
+ init_uvmem(t, UVMEM_START, UVMEM_END);
+ t->proc.vmem = create_vmem();
+ t->pid = t->tid;
+ t->rid = t->tid;
+ p = t;
+ }
+
+ t->eid = t->pid;
+ t->rid = p->rid;
+ t->rpc.vmem = create_vmem();
+ setup_rpc_stack(t);
+ reference_proc(p);
+
+ t->regs = (vm_t)t;
+
+ set_canary(t);
+ return t;
+}
+
+/**
+ * Copy process.
+ *
+ * @param p Parent process.
+ * @param n New process.
+ * @return \ref OK.
+ */
+static stat_t __copy_proc(struct tcb *p, struct tcb *n)
+{
+ /** @todo setup rpc stack stuff */
+ /** @todo I think keeping track of userspace stack stuff is unnecessary,
+ * unless we want unlimited stack size but that sounds dumb. Anycase, we
+ * need to duplicate stack info, whatever we do. */
+ n->exec = p->exec;
+ n->callback = p->callback;
+ n->thread_stack = p->thread_stack;
+ n->thread_stack_top = p->thread_stack_top;
+
+ clone_regs(n, p);
+ copy_caps(n->caps, p->caps);
+ return clone_mem_regions(n, p);
+}
+
+struct tcb *create_proc(struct tcb *p)
+{
+ hard_assert(tcbs, 0);
+
+ /* create a new thread outside the current process */
+ struct tcb *n = create_thread(NULL);
+ if (!n)
+ return 0;
+
+ if (p)
+ __copy_proc(p, n); /* we have a parent thread */
+
+ return n;
+}
+
+/**
+ * Destroy data associated with thread.
+ *
+ * @param t Thread whose data to destroy.
+ * @return \ref OK.
+ */
+static stat_t __destroy_thread_data(struct tcb *t)
+{
+ /* free rpc vmem */
+ destroy_vmem(t->rpc.vmem);
+
+ /* free associated kernel stack and the structure itself */
+ vm_t bottom = align_down((vm_t)t, order_size(MM_O0));
+ free_page(MM_O0, (pm_t)bottom);
+
+ /** \todo free stacks */
+
+ return OK;
+}
+
+stat_t destroy_thread(struct tcb *t)
+{
+ hard_assert(tcbs, ERR_NOINIT);
+ hard_assert(!is_proc(t), ERR_INVAL);
+
+ /* remove thread id from list */
+ /** @todo what about if thread is in rpc? should it rather just be
+ * marked dead? */
+ tcbs[t->tid] = 0;
+
+ /* remove reference to root process */
+ unreference_proc(get_rproc(t));
+
+ return __destroy_thread_data(t);
+}
+
+stat_t destroy_proc(struct tcb *p)
+{
+ hard_assert(tcbs, ERR_NOINIT);
+ hard_assert(is_proc(p), ERR_INVAL);
+
+ p->dead = true;
+ /* unreference ourselves */
+ unreference_proc(p);
+
+ catastrophic_assert(destroy_uvmem(p));
+ return __destroy_thread_data(p);
+}
+
+void reference_proc(struct tcb *p)
+{
+ hard_assert(is_proc(p), RETURN_VOID);
+ p->refcount++;
+}
+
+void unreference_proc(struct tcb *p)
+{
+ hard_assert(is_proc(p), RETURN_VOID);
+ p->refcount--;
+ if (p->dead && p->refcount == 0) {
+ dbg("thread %d is completely destroyed\n", p->tid);
+ /** @todo actually destroy */
+ }
+}
+
+/* weak to allow optimisation on risc-v, but provide fallback for future */
+__weak struct tcb *cur_tcb()
+{
+ return cpu_tcb(cpu_id());
+}
+
+struct tcb *cpu_tcb(id_t cpu_id)
+{
+ return __cpu_tcb[cpu_id];
+}
+
+struct tcb *cur_proc()
+{
+ struct tcb *t = cur_tcb();
+ return get_tcb(t->pid);
+}
+
+struct tcb *eff_proc()
+{
+ struct tcb *t = cur_tcb();
+ return get_tcb(t->eid);
+}
+
+void use_tcb(struct tcb *t)
+{
+ cpu_assign(t);
+
+ __cpu_tcb[t->cpu_id] = t;
+
+ use_vmem(t->proc.vmem);
+}
+
+struct tcb *get_tcb(id_t tid)
+{
+ hard_assert(tcbs, 0);
+
+ if (tid <= 0)
+ return NULL;
+
+ return tcbs[tid & (num_tids - 1)];
+}
+
+void set_return(struct tcb *t, vm_t v)
+{
+ t->exec = v;
+}
+
+bool running(struct tcb *t)
+{
+ return cpu_tcb(t->cpu_id) == t;
+}