From bc600ecc3bdf0f189861dfb840f70c2339a7a853 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Fri, 24 May 2024 13:24:27 +0300 Subject: 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 --- src/uapi/ipc.c | 348 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/uapi/ipc.c (limited to 'src/uapi/ipc.c') 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 +#include +#include +#include + +/** 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); +} -- cgit v1.3