diff options
Diffstat (limited to 'common/uapi/ipc.c')
| -rw-r--r-- | common/uapi/ipc.c | 216 |
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)); } /** |
