diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-05-24 13:24:27 +0300 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-05-24 18:13:43 +0300 |
| commit | bc600ecc3bdf0f189861dfb840f70c2339a7a853 (patch) | |
| tree | d9840b1dc1b865442a028c03ad7109ac67894f6e /src/uapi | |
| parent | 6a7073e5f262db9a4578ff00b5b28e34335564ce (diff) | |
| download | kmi-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.c | 94 | ||||
| -rw-r--r-- | src/uapi/conf.c | 115 | ||||
| -rw-r--r-- | src/uapi/dispatch.c | 89 | ||||
| -rw-r--r-- | src/uapi/ipc.c | 348 | ||||
| -rw-r--r-- | src/uapi/irq.c | 29 | ||||
| -rw-r--r-- | src/uapi/mem.c | 158 | ||||
| -rw-r--r-- | src/uapi/proc.c | 188 | ||||
| -rw-r--r-- | src/uapi/timers.c | 120 |
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); +} |
