aboutsummaryrefslogtreecommitdiff
path: root/common/uapi/ipc.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/uapi/ipc.c')
-rw-r--r--common/uapi/ipc.c348
1 files changed, 0 insertions, 348 deletions
diff --git a/common/uapi/ipc.c b/common/uapi/ipc.c
deleted file mode 100644
index cb2ce28..0000000
--- a/common/uapi/ipc.c
+++ /dev/null
@@ -1,348 +0,0 @@
-/* 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);
-}