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.c216
1 files changed, 194 insertions, 22 deletions
diff --git a/common/uapi/ipc.c b/common/uapi/ipc.c
index 1498cfb..d71f53c 100644
--- a/common/uapi/ipc.c
+++ b/common/uapi/ipc.c
@@ -9,6 +9,186 @@
#include <kmi/uapi.h>
#include <kmi/tcb.h>
#include <kmi/ipi.h>
+#include <kmi/conf.h>
+
+/**
+ * Mark rpc stack between \p start and \p end inaccessible.
+ *
+ * @param t Thread whose rpc stack to modify.
+ * @param start Start address of rpc stack to mark inaccessible.
+ * @param end End address of rpc stack to mark inaccessible.
+ */
+static void mark_rpc_inaccessible(struct tcb *t, vm_t start, vm_t end)
+{
+ size_t page_size = BASE_PAGE_SIZE;
+ size_t size = end - start;
+ size_t pages = size / page_size;
+ while (pages--)
+ clear_vpage_flags(t->rpc.vmem, start + pages * page_size, VM_U);
+}
+
+/**
+ * Mark rpc stack between \p start and \p end accessible.
+ *
+ * @param t Thread whose rpc stack to modify.
+ * @param start Start address of rpc stack to mark accessible.
+ * @param end End address of rpc stack to mark accessible.
+ */
+static void mark_rpc_accessible(struct tcb *t, vm_t start, vm_t end)
+{
+ size_t page_size = BASE_PAGE_SIZE;
+ size_t size = end - start;
+ size_t pages = size / page_size;
+ while (pages--)
+ set_vpage_flags(t->rpc.vmem, start + pages * page_size, VM_U);
+}
+
+/** Structure for maintaingin 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;
+};
+
+/**
+ * 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;
+};
+
+/**
+ * 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 sd RPC stack regions to mark inaccessible.
+ */
+static void finalize_rpc(struct tcb *t, struct tcb *r, struct stack_diff sd)
+{
+ clone_uvmem(r->proc.vmem, t->rpc.vmem);
+ set_return(t, r->callback);
+ attach_rpc(r, t);
+ t->pid = r->rid;
+
+ /* make sure updates are visible when swapping to the new virtual memory */
+ mark_rpc_inaccessible(t, sd.start, sd.end);
+ 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.
+ * @return RPC stack difference that should be passed to finalize_rpc().
+ */
+static struct stack_diff enter_rpc(struct tcb *t, struct sys_ret a)
+{
+ vm_t rpc_stack = t->rpc_stack;
+ if (is_rpc(t))
+ /** @todo what if user uses their own stack? Or is a dick and
+ * sets the stack pointer to RPC_STACK_TOP or something? It'll
+ * likely only cause a fuckup in the process who did the dumb
+ * thing, so maybe just consider it user error? Except by
+ * causing the stack of the next rpc to run out of memory... */
+ rpc_stack = align_down(get_stack(t), BASE_PAGE_SIZE);
+
+
+ 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, a);
+
+ ctx->exec = t->exec;
+ ctx->pid = t->pid;
+ ctx->eid = t->eid;
+ ctx->rpc_stack = rpc_stack;
+
+ /** @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;
+ struct stack_diff sd = {new_stack, t->rpc_stack};
+
+ /** @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 sd;
+}
+
+/**
+ * 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;
+ t->regs = ctx->regs;
+ /* again, get rid of args as fast as possible */
+ set_args(t, a);
+
+ set_return(t, ctx->exec);
+ /* if we're returning from a failed rpc, this should essentially be a
+ * no-op */
+ mark_rpc_accessible(t, t->rpc_stack, ctx->rpc_stack);
+ 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)
+{
+ vm_t top = RPC_STACK_BASE + __call_stack_size;
+ vm_t rpc_stack = t->rpc_stack + BASE_PAGE_SIZE;
+
+ return top - rpc_stack >= __call_stack_size / RPC_STACK_RATIO;
+}
/**
* IPC server notification syscall handler.
@@ -47,30 +227,29 @@ static void do_ipc(struct tcb *t,
sys_arg_t d3,
bool fwd)
{
- if (!enough_rpc_stack(t))
+ if (unlikely(!enough_rpc_stack(t)))
return_args(t, SYS_RET1(ERR_OOMEM));
+ struct stack_diff sd =
+ enter_rpc(t, SYS_RET6(OK, t->eid, d0, d1, d2, d3));
+
struct tcb *r = get_tcb(pid);
- if (!r)
- return_args(t, SYS_RET1(ERR_INVAL));
+ if (unlikely(!r)) {
+ leave_rpc(t, SYS_RET1(ERR_INVAL));
+ return;
+ }
r = get_rproc(r);
- if (!r->callback)
- return_args(t, SYS_RET1(ERR_NOINIT));
-
- clone_uvmem(r->proc.vmem, t->rpc.vmem);
- enter_rpc(t);
-
- set_return(t, r->callback);
- attach_rpc(r, t);
+ if (unlikely(!r->callback)) {
+ leave_rpc(t, SYS_RET1(ERR_NOINIT));
+ return;
+ }
if (!fwd)
t->eid = t->pid;
- t->pid = r->rid;
-
- return_args(t, SYS_RET6(OK, t->eid, d0, d1, d2, d3));
+ finalize_rpc(t, r, sd);
}
/**
* IPC request syscall handler.
@@ -121,15 +300,8 @@ SYSCALL_DEFINE4(ipc_resp)(struct tcb *t, sys_arg_t d0, sys_arg_t d1,
sys_arg_t d3)
{
struct tcb *r = get_cproc(t);
- leave_rpc(t);
+ leave_rpc(t, SYS_RET6(OK, t->tid, d0, d1, d2, d3));
detach_rpc(r, t);
-
- if (is_rpc(t))
- use_vmem(t->rpc.vmem);
- else
- use_vmem(t->proc.vmem);
-
- return_args(t, SYS_RET6(OK, t->tid, d0, d1, d2, d3));
}
/**