aboutsummaryrefslogtreecommitdiff
path: root/src/uapi
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 13:24:27 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 18:13:43 +0300
commitbc600ecc3bdf0f189861dfb840f70c2339a7a853 (patch)
treed9840b1dc1b865442a028c03ad7109ac67894f6e /src/uapi
parent6a7073e5f262db9a4578ff00b5b28e34335564ce (diff)
downloadkmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.tar.gz
kmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.zip
rename common to src
+ I keep starting to type src and wondering why autocomplete won't work, I guess src is just uncounciously a better name
Diffstat (limited to 'src/uapi')
-rw-r--r--src/uapi/cap.c94
-rw-r--r--src/uapi/conf.c115
-rw-r--r--src/uapi/dispatch.c89
-rw-r--r--src/uapi/ipc.c348
-rw-r--r--src/uapi/irq.c29
-rw-r--r--src/uapi/mem.c158
-rw-r--r--src/uapi/proc.c188
-rw-r--r--src/uapi/timers.c120
8 files changed, 1141 insertions, 0 deletions
diff --git a/src/uapi/cap.c b/src/uapi/cap.c
new file mode 100644
index 0000000..157ef47
--- /dev/null
+++ b/src/uapi/cap.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#include <kmi/uapi.h>
+#include <kmi/caps.h>
+#include <kmi/tcb.h>
+
+/**
+ * @file cap.c
+ * Sycalls handlers for thread capabilities.
+ */
+
+/**
+ * Check that values are legal for thread ID and offset.
+ *
+ * @param tid Thread ID of thread whose caps should be fetched.
+ * @param off Offset of capabilities.
+ * @return Pointer to capabilities of \p tid, \c 0 otherwise.
+ */
+static capflags_t *__get_tcb_caps(id_t tid, size_t off)
+{
+ if (!cap_off_ok(off))
+ return NULL;
+
+ struct tcb *t = get_tcb(tid);
+ if (!t)
+ return NULL;
+
+ return &t->caps;
+}
+
+/**
+ * Set capabilities.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID whose capabilities to set.
+ * @param off Offset of capability, multiple of \c bits(cap).
+ * @param caps Mask of capabilities to set.
+ * @return \ref OK on success, \ref ERR_INVAL on invalid input.
+ */
+SYSCALL_DEFINE3(set_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off,
+ sys_arg_t caps)
+{
+ if (!is_set(t->caps, CAP_CAPS))
+ return_args1(t, ERR_PERM);
+
+ capflags_t *c;
+ if (!(c = __get_tcb_caps(tid, off)))
+ return_args1(t, ERR_INVAL);
+
+ set_caps(*c, off, caps);
+ return_args1(t, OK);
+}
+
+/**
+ * Get capabilities.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID whose capabilities to get.
+ * @param off Offset of capability, multiple of \c bits(cap).
+ * @return \ref OK, capabilities.
+ */
+SYSCALL_DEFINE2(get_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off)
+{
+ capflags_t *c;
+ if (!(c = __get_tcb_caps(tid, off)))
+ return_args1(t, ERR_INVAL);
+
+ return_args2(t, OK, get_caps(*c, off));
+}
+
+/**
+ * Clear capabilities.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID whose capabilities to clear.
+ * @param off Offset of capability, multiple of \c bits(cap).
+ * @param caps Mask of capabilities to clear.
+ * @return ERR_LERM if invalid permissions, ERR_INVAL if \p tid doesn't exist,
+ * otherwise OK.
+ */
+SYSCALL_DEFINE3(clear_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off,
+ sys_arg_t caps)
+{
+ if (!is_set(t->caps, CAP_CAPS))
+ return_args1(t, ERR_PERM);
+
+ capflags_t *c;
+ if (!(c = __get_tcb_caps(tid, off)))
+ return_args1(t, ERR_INVAL);
+
+ clear_caps(*c, off, caps);
+ return_args1(t, OK);
+}
diff --git a/src/uapi/conf.c b/src/uapi/conf.c
new file mode 100644
index 0000000..894b98a
--- /dev/null
+++ b/src/uapi/conf.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file conf.c
+ * Runtime configuration sycall implementations.
+ *
+ * At the moment there are not runtime configuration parameters.
+ */
+
+#include <kmi/power.h>
+#include <kmi/sizes.h>
+#include <kmi/uapi.h>
+#include <kmi/conf.h>
+
+#include <arch/proc.h>
+
+/** \todo stack size should really be set on a per-thread basis, and are the
+ * conf*-syscalls even necessary? */
+size_t __thread_stack_size = SZ_2M;
+size_t __rpc_stack_size = SZ_512K;
+
+/** IDs for configuration parameters. */
+/** @todo should probably be moved somewhere so it can be shared with userspace */
+enum conf_param {
+ CONF_THREAD_STACK = 0,
+ CONF_RPC_STACK,
+};
+
+/**
+ * Configuration parameter read syscall handler.
+ *
+ * \todo Implement parameters.
+ *
+ * @param t Current tcb.
+ * @param param Parameter to read.
+ * @return \ref OK and parameter value.
+ */
+SYSCALL_DEFINE1(conf_get)(struct tcb *t, sys_arg_t param)
+{
+ if (!has_cap(t->caps, CAP_CONF))
+ return_args1(t, ERR_PERM);
+
+ long val = 0;
+ switch (param) {
+ case CONF_THREAD_STACK:
+ val = __thread_stack_size;
+ break;
+
+ case CONF_RPC_STACK:
+ val = __rpc_stack_size;
+ break;
+
+ default:
+ return_args1(t, ERR_NF);
+ }
+
+ return_args2(t, OK, val);
+}
+
+/**
+ * Configuration parameter write syscall handler.
+ *
+ * \todo Implement parameters.
+ *
+ * @param t Current tcb.
+ * @param param Parameter to write.
+ * @param val Value to set \c param to.
+ * @return \ref OK and \c 0.
+ */
+SYSCALL_DEFINE2(conf_set)(struct tcb *t, sys_arg_t param, sys_arg_t val)
+{
+ if (!has_cap(t->caps, CAP_CONF))
+ return_args1(t, ERR_PERM);
+
+ size_t size = 0;
+ switch (param) {
+ case CONF_THREAD_STACK:
+ __thread_stack_size = align_up(val, BASE_PAGE_SIZE);
+ break;
+
+ case CONF_RPC_STACK:
+ size = align_up(val, BASE_PAGE_SIZE);
+ if (size > max_rpc_size())
+ return_args1(t, ERR_MISC);
+
+ __rpc_stack_size = size;
+ break;
+ }
+
+ return_args1(t, OK);
+}
+
+/**
+ * Poweroff syscall handler.
+ *
+ * @param t Current tcb.
+ * @param type Type of poweroff.
+ * @return \ref ERR_INVAL and \c 0 if incorrect poweroff \c type give, otherwise
+ * does not return.
+ */
+SYSCALL_DEFINE1(poweroff)(struct tcb *t, sys_arg_t type)
+{
+ if (!(has_cap(t->caps, CAP_POWER)))
+ return_args1(t, ERR_PERM);
+
+ switch (type) {
+ case SHUTDOWN:
+ case COLD_REBOOT:
+ case WARM_REBOOT:
+ return_args2(t, OK, poweroff(type));
+ };
+
+ return_args1(t, ERR_INVAL);
+}
diff --git a/src/uapi/dispatch.c b/src/uapi/dispatch.c
new file mode 100644
index 0000000..5b523cc
--- /dev/null
+++ b/src/uapi/dispatch.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file dispatch.c
+ * Syscall dispatch.
+ */
+
+#include <kmi/canary.h>
+#include <kmi/debug.h>
+#include <kmi/uapi.h>
+
+/* not sure why doxygen requires these two definitions to state their return
+ * values, when they don't actually return anything but eh */
+
+/**
+ * Noop syscall handler.
+ *
+ * @param t Current tcb.
+ *
+ * @return \ref OK and \c 0.
+ */
+SYSCALL_DEFINE0(noop)(struct tcb *t)
+{
+ info("sys_noop\n");
+ set_args1(t, OK);
+}
+
+/**
+ * Putch syscall handler.
+ *
+ * @param t Current tcb.
+ * @param a Character to put.
+ *
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE1(putch)(struct tcb *t, sys_arg_t a)
+{
+ dbg("%c", (char)a);
+ set_args1(t, OK);
+}
+
+void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b,
+ sys_arg_t c, sys_arg_t d, sys_arg_t e, struct tcb *t)
+{
+ adjust_syscall(t);
+
+ switch (syscall) {
+ case SYS_NOOP: sys_noop(t, a, b, c, d, e); break;
+ case SYS_PUTCH: sys_putch(t, a, b, c, d, e); break;
+ case SYS_REQ_MEM: sys_req_mem(t, a, b, c, d, e); break;
+ case SYS_REQ_PAGE: sys_req_page(t, a, b, c, d, e); break;
+ case SYS_REQ_PMEM: sys_req_pmem(t, a, b, c, d, e); break;
+ case SYS_REQ_FIXMEM: sys_req_fixmem(t, a, b, c, d, e); break;
+ case SYS_REQ_SHAREDMEM: sys_req_sharedmem(t, a, b, c, d, e); break;
+ case SYS_FREE_MEM: sys_free_mem(t, a, b, c, d, e); break;
+ case SYS_TIMEBASE: sys_timebase(t, a, b, c, d, e); break;
+ case SYS_TICKS: sys_ticks(t, a, b, c, d, e); break;
+ case SYS_REQ_REL_TIMER: sys_req_rel_timer(t, a, b, c, d, e); break;
+ case SYS_REQ_ABS_TIMER: sys_req_abs_timer(t, a, b, c, d, e); break;
+ case SYS_IPC_SERVER: sys_ipc_server(t, a, b, c, d, e); break;
+ case SYS_IPC_REQ: sys_ipc_req(t, a, b, c, d, e); break;
+ case SYS_IPC_FWD: sys_ipc_fwd(t, a, b, c, d, e); break;
+ case SYS_IPC_KICK: sys_ipc_kick(t, a, b, c, d, e); break;
+ case SYS_IPC_RESP: sys_ipc_resp(t, a, b, c, d, e); break;
+ case SYS_IPC_NOTIFY: sys_ipc_notify(t, a, b, c, d, e); break;
+ case SYS_CREATE: sys_create(t, a, b, c, d, e); break;
+ case SYS_FORK: sys_fork(t, a, b, c, d, e); break;
+ case SYS_EXEC: sys_exec(t, a, b, c, d, e); break;
+ case SYS_SPAWN: sys_spawn(t, a, b, c, d, e); break;
+ case SYS_KILL: sys_kill(t, a, b, c, d, e); break;
+ case SYS_SWAP: sys_swap(t, a, b, c, d, e); break;
+ case SYS_CONF_SET: sys_conf_set(t, a, b, c, d, e); break;
+ case SYS_CONF_GET: sys_conf_get(t, a, b, c, d, e); break;
+ case SYS_SET_CAP: sys_set_cap(t, a, b, c, d, e); break;
+ case SYS_GET_CAP: sys_get_cap(t, a, b, c, d, e); break;
+ case SYS_CLEAR_CAP: sys_clear_cap(t, a, b, c, d, e); break;
+ case SYS_POWEROFF: sys_poweroff(t, a, b, c, d, e); break;
+ case SYS_IRQ_REQ: sys_irq_req(t, a, b, c, d, e); break;
+ default:
+ error("Syscall %zu outside allowed range [0 - %i]\n", syscall,
+ SYS_NUM - 1);
+ set_args1(t, ERR_INVAL);
+ };
+
+ if (check_canary(t)) {
+ bug("Syscall %zu overwrote stack canary\n", syscall);
+ }
+}
diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c
new file mode 100644
index 0000000..cb2ce28
--- /dev/null
+++ b/src/uapi/ipc.c
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file ipc.c
+ * Interprocess communication syscall implementations.
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/tcb.h>
+#include <kmi/ipi.h>
+#include <kmi/conf.h>
+
+/** Structure for maintaining the required context data for an rpc call. */
+struct call_ctx {
+ /** Execution continuation point. */
+ vm_t exec;
+
+ /** Register save area. */
+ vm_t regs;
+
+ /** Position in rpc stack. */
+ vm_t rpc_stack;
+
+ /** Effective process ID. */
+ id_t eid;
+
+ /** Current process ID. */
+ id_t pid;
+
+ /** Whether this context should be skipped when responding. */
+ bool kick;
+};
+
+/**
+ * Represents difference between where rpc stack was before rpc call and during.
+ * Used to figure out which areas should be marked inaccessible.
+ */
+struct stack_diff {
+ /** Current stack start. Keep in mind that stacks grow down. */
+ vm_t start;
+ /** Difference end, i.e. previous stack start. */
+ vm_t end;
+};
+
+/** Enumerator for IPC kind. Used by do_ipc(). */
+enum ipc_kind {
+ IPC_REQ, IPC_FWD, IPC_KICK
+};
+
+/**
+ * Now that we know we're doing an rpc, clone virtual memories and do
+ * the slow stuff.
+ *
+ * @param t Thread to migrate.
+ * @param r Process to migrate to.
+ * @param s RPC stack regions to mark inaccessible.
+ */
+static void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s)
+{
+ clone_uvmem(r->proc.vmem, t->rpc.vmem);
+ set_return(t, r->callback);
+ reference_proc(r);
+ t->pid = r->rid;
+
+ /* make sure updates are visible when swapping to the new virtual memory */
+ mark_rpc_invalid(t, s);
+ use_vmem(t->rpc.vmem);
+}
+
+/**
+ * Optimistically assume we're going to take the rpc and do some preparations
+ * for it. Most notably, write the arguments as early as possible to
+ * free up registers for the compiler to play with.
+ *
+ * @param t Thread to migrate.
+ * @param a RPC arguments.
+ * @param kind Kind of IPC we're doing. Essentially toggles kick boolean.
+ * @return RPC stack difference that should be passed to finalize_rpc().
+ */
+static vm_t enter_rpc(struct tcb *t, struct sys_ret a,
+ enum ipc_kind kind)
+{
+ vm_t rpc_stack = rpc_position(t);
+
+ struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
+ ctx->regs = t->regs;
+ t->regs = (vm_t)ctx;
+
+ /* try to get rid of args as fast as possible to free up registers for
+ * later use */
+ set_args(t, 6, a);
+
+ ctx->exec = t->exec;
+ ctx->pid = t->pid;
+ ctx->eid = t->eid;
+ ctx->rpc_stack = rpc_stack;
+
+ /* only rpcs can be kicked forward */
+ ctx->kick = kind == IPC_KICK && is_rpc(t);
+
+ /** @todo if we run out of rpc_stack space we should just stop, likely
+ * return a status? except it shouldn't happen after we've run
+ * enough_rpc_stack(). */
+ vm_t new_stack = rpc_stack - BASE_PAGE_SIZE;
+
+ /** @todo what if each stack is only some number of pages, and if a proc
+ * goes over the limit is is seen as programming error? Possibly user
+ * configurable number as well, might actually use the config subsystem
+ * :D
+ * In such a case it would probably be smarter to mark all pages
+ * inaccessible at first, and then mark the first page accessible. If
+ * the process needs more stack space it'll cause a paging exception,
+ * we'll handle it separately and if the process isn't going over the
+ * limit just give it more.
+ * */
+ t->rpc_stack = new_stack;
+ set_stack(t, new_stack);
+ return new_stack;
+}
+
+/**
+ * Jump back to process where rpc came from, assuming such a thing exists.
+ *
+ * @param t Thread to do return migration on.
+ * @param a Arguments to pass along.
+ */
+static void leave_rpc(struct tcb *t, struct sys_ret a)
+{
+ vm_t rpc_stack = t->rpc_stack + BASE_PAGE_SIZE;
+ struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
+ vm_t top = ctx->rpc_stack;
+
+ /* find first instance of not kicked context */
+ while (ctx->kick) {
+ rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE;
+ ctx = (struct call_ctx *)(rpc_stack) - 1;
+ unreference_proc(get_tcb(ctx->pid));
+ }
+
+ t->regs = ctx->regs;
+ /* again, get rid of args as fast as possible */
+ set_args(t, 6, a);
+
+ set_return(t, ctx->exec);
+ /* if we're returning from a failed rpc, this should essentially be a
+ * no-op */
+ mark_rpc_valid(t, top);
+ t->rpc_stack = ctx->rpc_stack;
+ t->pid = ctx->pid;
+ t->eid = ctx->eid;
+
+ if (is_rpc(t))
+ use_vmem(t->rpc.vmem);
+ else
+ use_vmem(t->proc.vmem);
+}
+
+/**
+ * Check that there's enough stack left for an rpc invocation.
+ *
+ * @param t Thread whose migration to check.
+ * @return \c true if there's enough stack left to safely do migration,
+ * \c false otherwise.
+ */
+static bool enough_rpc_stack(struct tcb *t)
+{
+ /* get top of call stack */
+ vm_t top = rpc_position(t);
+
+ /* if we can still fit an rpc stack into the call stack, we can safely
+ * do the migration. */
+ return top - BASE_PAGE_SIZE - __rpc_stack_size >= RPC_STACK_BASE;
+}
+
+/**
+ * IPC server notification syscall handler.
+ *
+ * @param t Current tcb.
+ * @param callback Address of server callback.
+ * @return \ref OK and \c 0.
+ */
+SYSCALL_DEFINE1(ipc_server)(struct tcb *t, sys_arg_t callback)
+{
+ get_cproc(t)->callback = callback;
+ return_args1(t, OK);
+}
+
+/**
+ * Actual IPC syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @param kind Which kind of IPC to perform.
+ *
+ * Returns \ref ERR_OOMEM if there isn't enough IPC stack left, \ref ERR_INVAL
+ * if the the target process doesn't exist, \ref ERR_NOINIT if the target
+ * process hasn't defined a callback. Otherwise \ref OK and whatever the target
+ * process sends back.
+ *
+ * @todo should all static functions have double underscores? I seem to be
+ * inconsistent.
+ */
+static void do_ipc(struct tcb *t,
+ sys_arg_t pid,
+ sys_arg_t d0,
+ sys_arg_t d1,
+ sys_arg_t d2,
+ sys_arg_t d3,
+ enum ipc_kind kind)
+{
+ if (unlikely(!enough_rpc_stack(t)))
+ return_args1(t, ERR_OOMEM);
+
+ vm_t s = enter_rpc(t, SYS_RET6(t->eid, t->tid, d0, d1, d2, d3), kind);
+
+ struct tcb *r = get_tcb(pid);
+ if (unlikely(!r)) {
+ leave_rpc(t, SYS_RET1(ERR_INVAL));
+ return;
+ }
+
+ r = get_rproc(r);
+ if (unlikely(r->dead)) {
+ leave_rpc(t, SYS_RET1(ERR_INVAL));
+ return;
+ }
+
+ if (unlikely(!r->callback)) {
+ leave_rpc(t, SYS_RET1(ERR_NOINIT));
+ return;
+ }
+
+ if (kind != IPC_REQ)
+ t->eid = t->pid;
+
+ finalize_rpc(t, r, s);
+ /* I tested out passing the return values as arguments to
+ * ret_userspace_fast, but apparently that causes enough stack shuffling
+ * to be slower overall. */
+ ret_userspace_fast();
+}
+/**
+ * IPC request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_req)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_REQ);
+}
+
+/**
+ * IPC forwarding syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_fwd)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_FWD);
+}
+
+/**
+ * IPC kicking syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_kick)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2,
+ sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_KICK);
+}
+
+/**
+ * IPC response syscall handler.
+ *
+ * @param t Current tcb.
+ * @param d0 IPC return value 0.
+ * @param d1 IPC return value 1.
+ * @param d2 IPC return value 2.
+ * @param d3 IPC return value 3.
+ * @return \c d0 and \c d1.
+ */
+SYSCALL_DEFINE4(ipc_resp)(struct tcb *t, sys_arg_t d0, sys_arg_t d1,
+ sys_arg_t d2,
+ sys_arg_t d3)
+{
+ /* if we're not in an rpc, the user messed something up. */
+ /** @todo choose or come up with more fitting error value. */
+ if (unlikely(!is_rpc(t)))
+ return_args1(t, ERR_MISC);
+
+ leave_rpc(t, SYS_RET6(OK, t->tid, d0, d1, d2, d3));
+}
+
+/**
+ * Notify syscall handler.
+ *
+ * \todo Implement.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID to notify.
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE1(ipc_notify)(struct tcb *t, sys_arg_t tid){
+ if (!has_cap(t->caps, CAP_CALL))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *r = get_tcb(tid);
+ if (r->notify_state == NOTIFY_QUEUED)
+ return_args1(t, OK);
+
+ if (r->notify_state == NOTIFY_RUNNING) {
+ t->notify_state = NOTIFY_QUEUED;
+ return_args1(t, OK);
+ }
+
+ r->notify_state = NOTIFY_QUEUED;
+ if (running(r))
+ send_ipi(r);
+
+ return_args1(t, OK);
+}
diff --git a/src/uapi/irq.c b/src/uapi/irq.c
new file mode 100644
index 0000000..6687826
--- /dev/null
+++ b/src/uapi/irq.c
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file irq.c
+ * IRQ request syscall handling implementation.
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/irq.h>
+
+/**
+ * Actual IRQ handling request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param id IRQ id to request to handle.
+ *
+ * @return OK on success, non-zero otherwise.
+ */
+SYSCALL_DEFINE1(irq_req)(struct tcb *t, sys_arg_t id)
+{
+ if (!has_cap(t->caps, CAP_IRQ))
+ return_args1(t, ERR_PERM);
+
+ if (!t->callback)
+ return_args1(t, ERR_NF);
+
+ return_args1(t, register_irq(t, id));
+}
diff --git a/src/uapi/mem.c b/src/uapi/mem.c
new file mode 100644
index 0000000..d54390a
--- /dev/null
+++ b/src/uapi/mem.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file mem.c
+ * Memory handling syscall implementations.
+ * \todo Should we return more error information?
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/utils.h>
+#include <kmi/vmem.h>
+#include <kmi/dmem.h>
+
+/**
+ * Memory request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param size Minimum size of allocation.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE2(req_mem)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ /** @todo expose flags to users */
+ if (!(start = alloc_uvmem(r, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Allocate single page to program.
+ *
+ * @param t Current tcb.
+ * @param size Size of the allocation.
+ * @param flags Flags of allocation.
+ * @return \ref ERR_OOMEM if unsucessful, otherwise \ref OK, virtual address,
+ * actual size, physical address, in that order.
+ */
+SYSCALL_DEFINE2(req_page)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0; pm_t paddr = 0; size_t asize = size;
+ if (!(start = alloc_uvpage(r, asize, flags, &asize, &paddr)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args4(t, OK, start, asize, paddr);
+}
+
+/**
+ * Fixed memory request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param fixed Address which should be included in allocation.
+ * @param size Minimum size of allocation after \c start.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE3(req_fixmem)(struct tcb *t, sys_arg_t fixed, sys_arg_t size,
+ sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ if (!(start = alloc_fixed_uvmem(r, fixed, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Free memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param start Start of allocation to free.
+ * @return \ref OK and \c 0 when succesful, \ref ERR_NF and \c 0 otherwise.
+ */
+SYSCALL_DEFINE1(free_mem)(struct tcb *t, sys_arg_t start)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t vm_start = (vm_t)start;
+
+ stat_t status = OK;
+ /* try freeing normal user memory first, if that fails, try device
+ * memory, otherwise just assume the address is borked. */
+ if (!(status = free_uvmem(r, vm_start)))
+ return_args1(t, OK);
+
+ if (!(status = free_devmem(r, vm_start)))
+ return_args1(t, OK);
+
+ return_args1(t, status);
+}
+
+/**
+ * Request physical memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param paddr Physical address to map.
+ * @param size Minimum size of allocation.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE3(req_pmem)(struct tcb *t, sys_arg_t paddr, sys_arg_t size,
+ sys_arg_t flags)
+{
+ /* this will require some pondering, but essentially this syscall should
+ * only be used for device access, so any addresses requested should be
+ * outside the RAM area, and I'll probably have to implement some method
+ * that keeps track of used regions outside of RAM. We'll see.
+ */
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ if (!(start = alloc_devmem(r, paddr, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Request shared memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param tid Thread to share memory with.
+ * @param size Minimum size of allocation.
+ * @param sflags Flags of allocation for \p t.
+ * @param cflags Flags of allocation for \p tid.
+ * @return \ref OK and start of \p t allocation and start of \p tid allocation,
+ * in that order, \ref ERR_OOMEM otherwise.
+ *
+ * @todo should we also take the thread who should get the other end of the
+ * memory?
+ */
+SYSCALL_DEFINE4(req_sharedmem)(struct tcb *t, sys_arg_t tid,
+ sys_arg_t size, sys_arg_t sflags,
+ sys_arg_t cflags)
+{
+ /** @todo check capability for shared memory */
+ struct tcb *u = get_tcb(tid);
+ if (!u)
+ return_args1(t, ERR_INVAL);
+
+ struct tcb *s = get_cproc(t);
+ struct tcb *c = get_rproc(u);
+
+ vm_t sstart, cstart;
+ if (alloc_shared_uvmem(s, c, size, sflags, cflags, &sstart, &cstart))
+ return_args1(t, ERR_OOMEM);
+
+ return_args3(t, OK, sstart, cstart);
+}
+
+/** \todo add some way to specify who gets to access the shared memory? */
diff --git a/src/uapi/proc.c b/src/uapi/proc.c
new file mode 100644
index 0000000..e7fa49d
--- /dev/null
+++ b/src/uapi/proc.c
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file proc.c
+ * Process/thread handling syscall implementations.
+ */
+
+#include <kmi/elf.h>
+#include <kmi/uapi.h>
+#include <kmi/proc.h>
+#include <kmi/bits.h>
+#include <kmi/mem_regions.h>
+
+/**
+ * Create syscall handler.
+ *
+ * @param t Current tcb.
+ * @param func Function to jump to at thread creation.
+ * @param d0 Argument 0.
+ * @param d1 Argument 1.
+ * @param d2 Argument 2.
+ * @param d3 Argument 3.
+ *
+ * @return ERR_OOMEM if thread creation was unsuccessful, otherwise OK and the
+ * thread id.
+ */
+SYSCALL_DEFINE5(create)(struct tcb *t, sys_arg_t func,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ struct tcb *c = create_thread(t);
+ if (!c)
+ return_args1(t, ERR_OOMEM);
+
+ alloc_stack(c);
+
+ set_args5(c, c->tid, d0, d1, d2, d3);
+ set_return(c, func);
+
+ return_args2(t, OK, c->tid);
+}
+
+/**
+ * Fork syscall handler.
+ *
+ * \todo Not entirely sure how I should handle forks/execs etc, mostly whether I
+ * should allow forks/execs to be called directly or only though the process
+ * manager. Probably though the process manager, although that will add in a
+ * slight bit of delay.
+ *
+ * I suppose I could add in a runtime parameter
+ * that would allow them to be called directly, and then the process manager
+ * would have to periodically ask the kernel about all threads it is aware of
+ * via sys_sync. Dunno.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE0(fork)(struct tcb *t)
+{
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *n = create_proc(get_eproc(t));
+ if (!n)
+ return_args1(t, ERR_OOMEM);
+
+ /* prepare args for when we eventually swap to the new proc, giving
+ * parent ID as third return value */
+ set_args3(n, OK, 0, get_eproc(t)->pid);
+
+ return_args2(t, OK, n->pid);
+}
+
+/**
+ * Exec syscall handler.
+ *
+ * @param t Current tcb.
+ * @param bin Binary to execute.
+ * @param interp Optional interpreter binary.
+ *
+ * @return \see prepare_proc().
+ */
+SYSCALL_DEFINE2(exec)(struct tcb *t, sys_arg_t bin, sys_arg_t interp)
+{
+ /** @todo probably make sure thread is root thread of process? */
+ /* mark binary to be kept */
+ struct mem_region *b = find_used_region(&t->sp_r, bin);
+ if (!b)
+ return_args1(t, ERR_INVAL);
+
+ set_bit(b->flags, MR_KEEP);
+
+ struct mem_region *i = 0;
+ if (interp) {
+ /* mark interpreter to be kept */
+ i = find_used_region(&t->sp_r, interp);
+ if (!i)
+ return_args1(t, ERR_INVAL);
+
+ set_bit(i->flags, MR_KEEP);
+ }
+
+ /* free everything except regions to be kept */
+ clear_uvmem(t);
+
+ /* restore to normal */
+ clear_bit(b->flags, MR_KEEP);
+ if (interp)
+ clear_bit(b->flags, MR_KEEP);
+
+ return_args1(t, prepare_proc(t, bin, interp));
+}
+
+/**
+ * Spawn syscall handler.
+ *
+ * @param t Current tcb.
+ * @param bin Binary to execute.
+ * @param interp Optional interpreter binary.
+ *
+ * @return \see prepare_proc() and process id of the new process.
+ */
+SYSCALL_DEFINE2(spawn)(struct tcb *t, sys_arg_t bin, sys_arg_t interp)
+{
+ struct tcb *c = get_proc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *n = create_proc(NULL);
+ if (!n)
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, prepare_proc(n, bin, interp), n->pid);
+}
+
+/**
+ * Kill syscall handler.
+ *
+ * @param t Current tcb.
+ * @param tid Thread to kill.
+ * \todo Implement.
+ *
+ * @return ERR_PERM if not capable to kill, otherwise OK.
+ */
+SYSCALL_DEFINE1(kill)(struct tcb *t, sys_arg_t tid)
+{
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ /** @todo implement */
+ /** @todo remember to unregister IRQ handlers */
+
+ return_args1(t, OK);
+}
+
+/**
+ * Swap syscall handler.
+ *
+ * \todo Implement.
+ * \todo Should swap return the registers of the new thread that would be used
+ * for message passing?
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID to swap to.
+ *
+ * @return \ref OK.
+ */
+SYSCALL_DEFINE1(swap)(struct tcb *t, sys_arg_t tid){
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *s = get_tcb(tid);
+ if (!s)
+ return_args1(t, ERR_INVAL);
+
+ /* switch over to new thread */
+ use_tcb(s);
+
+ /* set return value for current thread */
+ set_args1(t, OK);
+
+ /* get register state for new thread */
+ return_args(s, get_args(s));
+}
diff --git a/src/uapi/timers.c b/src/uapi/timers.c
new file mode 100644
index 0000000..b119fcc
--- /dev/null
+++ b/src/uapi/timers.c
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file timers.c
+ * Timer syscall implementations.
+ */
+
+#include <kmi/timer.h>
+#include <kmi/uapi.h>
+
+#include <arch/timer.h>
+
+/**
+ * Convert arch-specific register values \p ticks and \p mult to \ref ticks_t.
+ *
+ * If we're on a 32bit system, one register can't contain a tick value,
+ * so we use two registers and combine them into one value and let the compiler
+ * handle the rest.
+ *
+ * @param ticks Register width tick value.
+ * @param mult Register width repeat value.
+ * @return Corresponding \ref ticks_t value.
+ */
+static ticks_t scaled_ticks(sys_arg_t ticks, sys_arg_t mult)
+{
+#if defined(_LP64)
+ UNUSED(mult);
+ return ticks;
+#else
+ return (ticks_t)ticks * (ticks_t)mult;
+#endif
+}
+
+/**
+ * Timebase syscall handler.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and timebase in second argument if 64bit, otherwise high 32
+ * bits of timebase in second argument and low 32 bits in third argument.
+ */
+SYSCALL_DEFINE0(timebase)(struct tcb *t)
+{
+ ticks_t tm = secs_to_ticks(1);
+#if defined(_LP64)
+ return_args2(t, OK, tm);
+#else
+ return_args3(t, OK, tm >> 32, tm);
+#endif
+}
+
+/**
+ * Current ticks syscall handler.
+ *
+ * Note that most platforms allow user level read access to hardware timers, and
+ * should be preferred over this syscall on such platforms. Still, for
+ * completeness sake.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and the current ticks when on 64bit systems, otherwise high
+ * 32 bits of ticks in second argument and low 32 bits in third.
+ */
+SYSCALL_DEFINE0(ticks)(struct tcb *t)
+{
+ ticks_t tm = current_ticks();
+#if defined(_LP64)
+ return_args2(t, OK, tm);
+#else
+ return_args3(t, OK, tm >> 32, tm);
+#endif
+}
+
+/**
+ * Relative timer request syscall handler.
+ *
+ * \note On 64bit systems, \p mult is ignored as \p ticks register is large
+ * enough to contain essentially any timepoint we want. A couple thousand years
+ * when the clock runs at 5GHz, if I'm not completely mistaken.
+ *
+ * @param t Current tcb.
+ * @param ticks Number of ticks from now.
+ * @param mult Multiply \c ticks by this value.
+ * @return \ref OK and \c cid of created timer.
+ */
+SYSCALL_DEFINE2(req_rel_timer)(struct tcb *t, sys_arg_t ticks, sys_arg_t mult)
+{
+ return_args2(t, OK, new_rel_timer(t->tid, scaled_ticks(ticks, mult)));
+}
+
+/**
+ * Absolute timer request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param ticks Absolute timepoint relative to some start point defined at boot.
+ * @param mult Multiply \c ticks by this value.
+ * @return \ref OK and \c cid of created timer.
+ * \see req_rel_timer().
+ */
+SYSCALL_DEFINE2(req_abs_timer)(struct tcb *t, sys_arg_t ticks, sys_arg_t mult)
+{
+ return_args2(t, OK, new_abs_timer(t->tid, scaled_ticks(ticks, mult)));
+}
+
+/**
+ * Free timer request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param cid \c cid of timer to free.
+ * @return \ref ERR_NF and \c 0if no timer could be found with \c cid, \ref OK
+ * and 0 otherwise.
+ */
+SYSCALL_DEFINE1(free_timer)(struct tcb *t, sys_arg_t cid)
+{
+ struct timer *timer = find_timer(cid);
+ if (!timer)
+ return_args1(t, ERR_NF);
+
+ remove_timer(timer);
+ return_args1(t, OK);
+}