aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/riscv64/kernel/vmem.c71
-rw-r--r--include/arch/tcb.h52
-rw-r--r--src/uapi/ipc.c57
-rw-r--r--tests/ipc-fwd/init.c63
-rw-r--r--tests/ipc-fwd/source.mk1
-rw-r--r--tests/ipc-kick/init.c54
-rw-r--r--tests/ipc-kick/source.mk1
-rw-r--r--tests/ipc-req-exhaustion/init.c27
-rw-r--r--tests/ipc-req-exhaustion/source.mk1
-rw-r--r--tests/ipc-root/init.c51
-rw-r--r--tests/ipc-root/source.mk1
-rw-r--r--tests/ipc-tail/init.c54
-rw-r--r--tests/ipc-tail/source.mk1
-rwxr-xr-xtests/scripts/gen-tests4
14 files changed, 358 insertions, 80 deletions
diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c
index fa3c1c2..4986c1f 100644
--- a/arch/riscv64/kernel/vmem.c
+++ b/arch/riscv64/kernel/vmem.c
@@ -623,7 +623,7 @@ stat_t setup_rpc_stack(struct tcb *t)
NULL);
/* we count downward in base pages, the top page is *always* reserved */
t->arch.rpc_idx = rpc_pages - 1;
-
+ /** @todo we could mark the first stack page accessible here already */
/* point registers to stack */
t->regs = t->rpc_stack - sizeof(struct call_ctx);
@@ -672,62 +672,85 @@ bool in_rpc_stack(struct tcb *t, vm_t addr)
&& addr < RPC_STACK_BASE + BASE_PAGE_SIZE * t->arch.rpc_idx;
}
-void close_rpc(struct tcb *t)
+void reuse_rpc(struct tcb *t)
{
struct vmem *b = t->arch.rpc_leaf;
int top_idx = t->arch.rpc_idx;
- /* absolute max iter count */
- for (; top_idx < (int)rpc_pages; --top_idx) {
- /* mark inaccessible until we reach the first inaccessible
- * region */
+ const int max = rpc_pages - 1;
+ for (; top_idx < max; --top_idx) {
pm_t *pte = (pm_t *)&b->leaf[top_idx];
if (!(pte_flags(*pte) & VM_U))
break;
- /* make page not accessible from userspace */
clear_bits(*pte, vp_flags(VM_U));
}
- /* make page following closed region accessible so we can avoid an
- * unnecessary page fault */
- pm_t *pte = (pm_t *)&b->leaf[top_idx];
+ /* top_idx is our 'current' kernel region, so mark page following it
+ * user accessible to avoid page fault on entry */
+ t->arch.rpc_idx = top_idx + 1;
+ pm_t *pte = (pm_t *)&b->leaf[t->arch.rpc_idx];
set_bits(*pte, vp_flags(VM_U));
- t->arch.rpc_idx = top_idx;
}
-void shrink_rpc(struct tcb *t)
+void new_rpc(struct tcb *t)
{
struct vmem *b = t->arch.rpc_leaf;
int top_idx = t->arch.rpc_idx;
- for (; top_idx > 0; ++top_idx) {
+ int max = rpc_pages - 1;
+ for (; top_idx < max; --top_idx) {
+ /* mark inaccessible until we reach the first inaccessible
+ * region, which must be our previous kernel data region */
pm_t *pte = (pm_t *)&b->leaf[top_idx];
if (!(pte_flags(*pte) & VM_U))
break;
+ /* make page not accessible from userspace */
clear_bits(*pte, vp_flags(VM_U));
}
- t->arch.rpc_idx = top_idx;
+ /* kernel region, skip */
+ t->arch.rpc_idx--;
+
+ /* user stack start, mark accessible to avoid pagefault on entry */
+ t->arch.rpc_idx--;
+ pm_t *pte = (pm_t *)&b->leaf[t->arch.rpc_idx];
+ set_bits(*pte, vp_flags(VM_U));
}
-void open_rpc(struct tcb *t, vm_t top)
+void destroy_rpc(struct tcb *t)
{
- assert(is_aligned(top, BASE_PAGE_SIZE));
struct vmem *b = t->arch.rpc_leaf;
- int old_idx = t->arch.rpc_idx;
- int new_idx = (top - RPC_STACK_BASE) / BASE_PAGE_SIZE;
- assert(new_idx >= old_idx);
+ int top_idx = t->arch.rpc_idx;
- /* always stop one before the page index indicated, might be kind of
- * easy to mess up */
- for (int i = old_idx; i < new_idx - 1; ++i) {
- pm_t *pte = (pm_t *)&b->leaf[i];
+ /* first, mark all 'current' accessible pages unaccessible */
+ int max = rpc_pages - 1;
+ for (; top_idx < max; ++top_idx) {
+ /* mark inaccessible until we reach the first inaccessible
+ * region, which must be our previous kernel data region */
+ pm_t *pte = (pm_t *)&b->leaf[top_idx];
+ if (!(pte_flags(*pte) & VM_U))
+ break;
+
+ /* make page not accessible from userspace */
+ clear_bits(*pte, vp_flags(VM_U));
+ }
+
+ /* current kernel data region, skip */
+ top_idx++;
+
+ int ctx = (t->rpc_stack - RPC_STACK_BASE) / BASE_PAGE_SIZE;
+
+ /* now mark previous rpc user region accessible again */
+ /* prev_ctx is page where our kernel context is, so we need to stop one
+ * before it */
+ for (; top_idx < ctx - 1; ++top_idx) {
+ pm_t *pte = (pm_t *)&b->leaf[top_idx];
set_bits(*pte, vp_flags(VM_U));
}
- t->arch.rpc_idx = new_idx - 1;
+ t->arch.rpc_idx = ctx - 1;
}
void grow_rpc(struct tcb *t, vm_t top)
diff --git a/include/arch/tcb.h b/include/arch/tcb.h
index b04dc56..3f99ae9 100644
--- a/include/arch/tcb.h
+++ b/include/arch/tcb.h
@@ -89,33 +89,31 @@ vm_t rpc_position(struct tcb *t);
bool in_rpc_stack(struct tcb *t, vm_t addr);
/**
- * Shrinks rpc stack down to where the previous reserved area is, so stack looks
- * something like this:
+ * Reuse current rpc activation.
+ * Effectively marks all stack pages except the first in the 'new' stack
+ * activation inaccessible.
*
- * ```
- * ctx | user | <
- * v
- * ctx | <
- * ```
- *
- * Implementation note: t->arch.rpc_idx now points to ctx.
+ * @param t Current tcb.
+ */
+void reuse_rpc(struct tcb *t);
+
+/**
+ * Create new rpc activation.
+ * Marks all current stack pages inaccessible, marks the first in the new stack
+ * accessible.
*
- * @param t tcb to shrink.
+ * @param t Current tcb.
*/
-void shrink_rpc(struct tcb *t);
+void new_rpc(struct tcb *t);
/**
- * Opens back up a previous stack frame, so
- * ```
- * ctx | user | ctx | <
- * v
- * ctx | user | <
- * ```
+ * Destroy a stack activation.
+ * Marks all current stack pages inaccessible, and marks all user stack pages in
+ * the previous activation accessible again.
*
- * @param t tcb to open up again.
- * @param top Address to where previous ctx is.
+ * @param t Current tcb.
*/
-void open_rpc(struct tcb *t, vm_t top);
+void destroy_rpc(struct tcb *t);
/**
* Adds more accessible space on the stack, so
@@ -134,18 +132,4 @@ void open_rpc(struct tcb *t, vm_t top);
*/
void grow_rpc(struct tcb *t, vm_t top);
-/**
- * Marks all user accessible areas inaccessible, so
- * ```
- * ctx | user | <
- * ctx | ---- | <
- * ```
- *
- * Used by \ref do_ipc to make sure different processes can't mess with
- * eachother's stacks.
- *
- * @param t tcb whose stack should be closed.
- */
-void close_rpc(struct tcb *t);
-
#endif /* KMI_ARCH_TCB_H */
diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c
index 3e9c04a..e380e1f 100644
--- a/src/uapi/ipc.c
+++ b/src/uapi/ipc.c
@@ -44,16 +44,22 @@ enum ipc_flags {
* @param t Thread to migrate.
* @param r Process to migrate to.
* @param s RPC stack regions to mark inaccessible.
+ * @param flags What kind of IPC we're doing.
*/
-static inline void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s)
+static inline void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s,
+ enum ipc_flags flags)
{
clone_uvmem(r->proc.vmem, t->rpc.vmem);
set_return(t, r->callback);
reference_thread(r);
t->pid = r->rid;
- /* make sure updates are visible when swapping to the new virtual memory */
- close_rpc(t);
+ if (is_set(flags, IPC_TAIL))
+ reuse_rpc(t);
+ else
+ new_rpc(t);
+
+ flush_tlb_all();
set_stack(t, s);
}
@@ -72,8 +78,10 @@ static inline vm_t enter_rpc(struct tcb *t, struct sys_ret a,
{
/* reuse current rpc stack location if we're being kicked */
vm_t rpc_stack = (is_set(flags, IPC_TAIL) && is_rpc(t))
- ? t->rpc_stack : rpc_position(t);
+ ? t->rpc_stack
+ : rpc_position(t);
+ /* rpc_stack points to the top of the region */
struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
t->regs = (vm_t)ctx;
@@ -81,12 +89,17 @@ static inline vm_t enter_rpc(struct tcb *t, struct sys_ret a,
* later use */
set_ret(t, 6, a);
- ctx->exec = t->exec;
- ctx->pid = t->pid;
- ctx->eid = t->eid;
- ctx->notify = flags & IPC_NOTIFY;
- ctx->rpc_stack = t->rpc_stack;
- t->rpc_stack = rpc_stack;
+ if (!is_set(flags, IPC_TAIL)) {
+ /* if we're doing a tail call, we don't need to update any of
+ * these things */
+ ctx->rpc_stack = t->rpc_stack;
+ t->rpc_stack = rpc_stack;
+ ctx->exec = t->exec;
+ ctx->pid = t->pid;
+ ctx->eid = t->eid;
+ ctx->notify = flags & IPC_NOTIFY;
+ }
+
return rpc_stack - BASE_PAGE_SIZE;
}
@@ -141,7 +154,7 @@ static __noreturn void __run_notify(struct tcb *t, struct tcb *r)
SYS_RET5(0, t->tid, code, flags, t->eid),
IPC_NOTIFY);
- finalize_rpc(t, r, s);
+ finalize_rpc(t, r, s, 0);
clear_bits(t->notify_flags, flags);
@@ -234,11 +247,10 @@ static void leave_rpc(struct tcb *t, struct sys_ret a)
set_return(t, ctx->exec);
/* if we're returning from a failed rpc, this should essentially be a
* no-op */
- shrink_rpc(t);
- open_rpc(t, ctx->rpc_stack);
+ t->rpc_stack = ctx->rpc_stack;
+ destroy_rpc(t);
flush_tlb_all();
- t->rpc_stack = ctx->rpc_stack;
t->pid = ctx->pid;
t->eid = ctx->eid;
@@ -282,10 +294,11 @@ static void do_ipc(struct tcb *t,
sys_arg_t d3,
enum ipc_flags flags)
{
- if (unlikely(!__enough_rpc_stack(t)))
+ if (!is_set(flags, IPC_TAIL) && !__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), flags);
+ id_t id = is_set(flags, IPC_FORWARD) ? t->eid : t->pid;
+ vm_t s = enter_rpc(t, SYS_RET6(id, t->tid, d0, d1, d2, d3), flags);
struct tcb *r = get_tcb(pid);
if (unlikely(!r || !is_proc(r))) {
@@ -306,7 +319,7 @@ static void do_ipc(struct tcb *t,
if (!is_set(flags, IPC_FORWARD))
t->eid = t->pid;
- finalize_rpc(t, r, s);
+ finalize_rpc(t, r, s, flags);
/* I tested out passing the return values as arguments to
* ret_userspace_fast, but apparently that causes enough stack shuffling
* to be slower overall. */
@@ -345,7 +358,11 @@ SYSCALL_DEFINE5(ipc_req)(struct tcb *t, sys_arg_t pid,
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_FORWARD);
+ /* I guess it's fine to check our rpc status and choose flags based on
+ * that, though the path is starting to get a bit convoluted with a
+ * bunch of conditionals that I might want to have a look at getting rid
+ * of at some point, to speed things up a little bit. */
+ do_ipc(t, pid, d0, d1, d2, d3, is_rpc(t) ? IPC_FORWARD : 0);
}
/**
@@ -363,7 +380,7 @@ SYSCALL_DEFINE5(ipc_tail)(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_TAIL);
+ do_ipc(t, pid, d0, d1, d2, d3, is_rpc(t) ? IPC_TAIL : 0);
}
/**
@@ -381,7 +398,7 @@ 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_FORWARD | IPC_TAIL);
+ do_ipc(t, pid, d0, d1, d2, d3, is_rpc(t) ? IPC_FORWARD | IPC_TAIL : 0);
}
/**
diff --git a/tests/ipc-fwd/init.c b/tests/ipc-fwd/init.c
new file mode 100644
index 0000000..2f04f3f
--- /dev/null
+++ b/tests/ipc-fwd/init.c
@@ -0,0 +1,63 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ check(pid == 0 || pid == 1, "illegal pid for init");
+ if (pid == 0) {
+ printf("creating pid 2\n");
+ id_t id1 = sys_fork(NULL);
+ check(id1 == 2, "unexpected pid?\n");
+
+ printf("creating pid 3\n");
+ id_t id2 = sys_fork(NULL);
+ check(id2 == 3, "unexpected pid?\n");
+
+ struct sys_ret r = sys_ipc_req4(2, 1, 2, 3, 4);
+ check(r.s == 0, "failed request?\n");
+ check(r.id == 2, "wrong responder?\n");
+ check(r.a0 == 1, "wrong a0?\n");
+ check(r.a1 == 2, "wrong a1?\n");
+ check(r.a2 == 3, "wrong a2?\n");
+ check(r.a3 == 4, "wrong a3?\n");
+ }
+ else if (pid == 1 && d0 == 1) {
+ printf("pid 2 caught ipc req\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 1, "illegal d0\n");
+ check(d1 == 2, "illegal d1\n");
+ check(d2 == 3, "illegal d2\n");
+ check(d3 == 4, "illegal d3\n");
+
+ printf("pid 2 forwarding\n");
+ struct sys_ret r = sys_ipc_fwd4(3, 5, 6, 7, 8);
+ check(r.s == 0, "failed request?\n");
+ check(r.id == 3, "wrong responder?\n");
+ check(r.a0 == 5, "wrong a0?\n");
+ check(r.a1 == 6, "wrong a1?\n");
+ check(r.a2 == 7, "wrong a2?\n");
+ check(r.a3 == 8, "wrong a3?\n");
+
+ printf("pid 2 responding\n");
+ sys_ipc_resp4(1, 2, 3, 4);
+ error("pid 2 ipc_resp failed\n");
+ }
+ else if (pid == 1 && d0 == 5) {
+ printf("pid 3 caught ipc fwd\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 5, "illegal d0\n");
+ check(d1 == 6, "illegal d1\n");
+ check(d2 == 7, "illegal d2\n");
+ check(d3 == 8, "illegal d3\n");
+
+ printf("pid 3 responding\n");
+ sys_ipc_resp4(5, 6, 7, 8);
+ error("pid 3 ipc_resp failed\n");
+ }
+ else {
+ error("weird pid: %ld\n", pid);
+ }
+
+ ok();
+}
diff --git a/tests/ipc-fwd/source.mk b/tests/ipc-fwd/source.mk
new file mode 100644
index 0000000..ed0078f
--- /dev/null
+++ b/tests/ipc-fwd/source.mk
@@ -0,0 +1 @@
+TESTS += ipc-fwd
diff --git a/tests/ipc-kick/init.c b/tests/ipc-kick/init.c
new file mode 100644
index 0000000..3be4dee
--- /dev/null
+++ b/tests/ipc-kick/init.c
@@ -0,0 +1,54 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ check(pid == 0 || pid == 1, "illegal pid for init\n");
+ if (pid == 0) {
+ printf("creating pid 2\n");
+ id_t id1 = sys_fork(NULL);
+ check(id1 == 2, "unexpected pid?\n");
+
+ printf("creating pid 3\n");
+ id_t id2 = sys_fork(NULL);
+ check(id2 == 3, "unexpected pid?\n");
+
+ struct sys_ret r = sys_ipc_req4(2, 1, 2, 3, 4);
+ check(r.s == 0, "failed request?\n");
+ check(r.id == 3, "wrong responder?\n");
+ check(r.a0 == 5, "wrong a0?\n");
+ check(r.a1 == 6, "wrong a1?\n");
+ check(r.a2 == 7, "wrong a2?\n");
+ check(r.a3 == 8, "wrong a3?\n");
+ }
+ else if (pid == 1 && d0 == 1) {
+ printf("pid 2 caught ipc req\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 1, "illegal d0\n");
+ check(d1 == 2, "illegal d1\n");
+ check(d2 == 3, "illegal d2\n");
+ check(d3 == 4, "illegal d3\n");
+
+ printf("pid 2 kicking\n");
+ sys_ipc_kick4(3, 5, 6, 7, 8);
+ error("pid 2 ipc_tail failed\n");
+ }
+ else if (pid == 1 && d0 == 5) {
+ printf("pid 3 caught ipc kick\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 5, "illegal d0\n");
+ check(d1 == 6, "illegal d1\n");
+ check(d2 == 7, "illegal d2\n");
+ check(d3 == 8, "illegal d3\n");
+
+ printf("pid 3 responding\n");
+ sys_ipc_resp4(5, 6, 7, 8);
+ error("pid 3 ipc_resp failed\n");
+ }
+ else {
+ error("weird pid: %ld\n", pid);
+ }
+
+ ok();
+}
diff --git a/tests/ipc-kick/source.mk b/tests/ipc-kick/source.mk
new file mode 100644
index 0000000..838a651
--- /dev/null
+++ b/tests/ipc-kick/source.mk
@@ -0,0 +1 @@
+TESTS += ipc-kick
diff --git a/tests/ipc-req-exhaustion/init.c b/tests/ipc-req-exhaustion/init.c
new file mode 100644
index 0000000..6dd1a04
--- /dev/null
+++ b/tests/ipc-req-exhaustion/init.c
@@ -0,0 +1,27 @@
+#include <common/test.h>
+
+long counter = 0;
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ (void)tid;
+ (void)d0;
+ (void)d1;
+ (void)d2;
+ (void)d3;
+
+ check(pid == 0 || pid == 1, "illegal pid for init");
+ printf("sending ipc req %zd to ourselves\n", counter++);
+ struct sys_ret r = sys_ipc_req4(1, 1, 2, 3, 4);
+ if (r.s) {
+ printf("ran out of stack space at %ld\n", counter);
+ printf("starting to unwind\n");
+ }
+
+ printf("unwinding at %ld\n", counter--);
+
+ /* the final resp0 should fail, since we're back in our root process */
+ sys_ipc_resp0();
+
+ ok();
+}
diff --git a/tests/ipc-req-exhaustion/source.mk b/tests/ipc-req-exhaustion/source.mk
new file mode 100644
index 0000000..3dc2ff3
--- /dev/null
+++ b/tests/ipc-req-exhaustion/source.mk
@@ -0,0 +1 @@
+TESTS += ipc-req-exhaustion
diff --git a/tests/ipc-root/init.c b/tests/ipc-root/init.c
new file mode 100644
index 0000000..8b43b4f
--- /dev/null
+++ b/tests/ipc-root/init.c
@@ -0,0 +1,51 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ check(pid == 0 || pid == 1, "illegal pid for init");
+ if (pid == 0) {
+ printf("sending ipc fwd from root\n");
+ struct sys_ret r = sys_ipc_fwd4(1, 1, 2, 3, 4);
+ check(r.s == OK, "not OK return\n");
+ check(r.id == 1, "not OK response ID\n");
+ check(r.a0 == 1, "not OK d0 response\n");
+ check(r.a1 == 2, "not OK d1 response\n");
+ check(r.a2 == 3, "not OK d2 response\n");
+ check(r.a3 == 4, "not OK d3 response\n");
+
+ printf("sending ipc tail from root\n");
+ r = sys_ipc_tail4(1, 1, 2, 3, 4);
+ printf("returned ipc req to ourselves\n");
+ check(r.s == OK, "not OK return\n");
+ check(r.id == 1, "not OK response ID\n");
+ check(r.a0 == 1, "not OK d0 response\n");
+ check(r.a1 == 2, "not OK d1 response\n");
+ check(r.a2 == 3, "not OK d2 response\n");
+ check(r.a3 == 4, "not OK d3 response\n");
+
+ printf("sending ipc kick from root\n");
+ r = sys_ipc_kick4(1, 1, 2, 3, 4);
+ printf("returned ipc req to ourselves\n");
+ check(r.s == OK, "not OK return\n");
+ check(r.id == 1, "not OK response ID\n");
+ check(r.a0 == 1, "not OK d0 response\n");
+ check(r.a1 == 2, "not OK d1 response\n");
+ check(r.a2 == 3, "not OK d2 response\n");
+ check(r.a3 == 4, "not OK d3 response\n");
+ }
+ else if (pid == 1) {
+ printf("caught ipc\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 1, "illegal d0\n");
+ check(d1 == 2, "illegal d1\n");
+ check(d2 == 3, "illegal d2\n");
+ check(d3 == 4, "illegal d3\n");
+
+ printf("sending response\n");
+ sys_ipc_resp4(1, 2, 3, 4);
+ error("ipc resp failed\n");
+ }
+
+ ok();
+}
diff --git a/tests/ipc-root/source.mk b/tests/ipc-root/source.mk
new file mode 100644
index 0000000..fe98f64
--- /dev/null
+++ b/tests/ipc-root/source.mk
@@ -0,0 +1 @@
+TESTS += ipc-root
diff --git a/tests/ipc-tail/init.c b/tests/ipc-tail/init.c
new file mode 100644
index 0000000..8f3efdd
--- /dev/null
+++ b/tests/ipc-tail/init.c
@@ -0,0 +1,54 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ check(pid == 0 || pid == 1 || pid == 2, "illegal pid for init\n");
+ if (pid == 0) {
+ printf("creating pid 2\n");
+ id_t id1 = sys_fork(NULL);
+ check(id1 == 2, "unexpected pid?\n");
+
+ printf("creating pid 3\n");
+ id_t id2 = sys_fork(NULL);
+ check(id2 == 3, "unexpected pid?\n");
+
+ struct sys_ret r = sys_ipc_req4(2, 1, 2, 3, 4);
+ check(r.s == 0, "failed request?\n");
+ check(r.id == 3, "wrong responder?\n");
+ check(r.a0 == 5, "wrong a0?\n");
+ check(r.a1 == 6, "wrong a1?\n");
+ check(r.a2 == 7, "wrong a2?\n");
+ check(r.a3 == 8, "wrong a3?\n");
+ }
+ else if (pid == 1) {
+ printf("pid 2 caught ipc req\n");
+ check(pid == 1, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 1, "illegal d0\n");
+ check(d1 == 2, "illegal d1\n");
+ check(d2 == 3, "illegal d2\n");
+ check(d3 == 4, "illegal d3\n");
+
+ printf("pid 2 tailing\n");
+ sys_ipc_tail4(3, 5, 6, 7, 8);
+ error("pid 2 ipc_tail failed\n");
+ }
+ else if (pid == 2) {
+ printf("pid 3 caught ipc tail\n");
+ check(pid == 2, "illegal pid source\n");
+ check(tid == 1, "illegal tid source\n");
+ check(d0 == 5, "illegal d0\n");
+ check(d1 == 6, "illegal d1\n");
+ check(d2 == 7, "illegal d2\n");
+ check(d3 == 8, "illegal d3\n");
+
+ printf("pid 3 responding\n");
+ sys_ipc_resp4(5, 6, 7, 8);
+ error("pid 3 ipc_resp failed\n");
+ }
+ else {
+ error("weird pid: %ld\n", pid);
+ }
+
+ ok();
+}
diff --git a/tests/ipc-tail/source.mk b/tests/ipc-tail/source.mk
new file mode 100644
index 0000000..e91a864
--- /dev/null
+++ b/tests/ipc-tail/source.mk
@@ -0,0 +1 @@
+TESTS += ipc-tail
diff --git a/tests/scripts/gen-tests b/tests/scripts/gen-tests
index 7095e30..3be204d 100755
--- a/tests/scripts/gen-tests
+++ b/tests/scripts/gen-tests
@@ -18,8 +18,8 @@ for NAME in "${@}"; do
build/$NAME/initrd > reports/$NAME/log" >> tests.mk
echo "reports/$NAME/OK: reports/$NAME/log" >> tests.mk
- echo " grep 'BUG' reports/$NAME/log \\" >> tests.mk
- echo " && echo 'BUG' > reports/$NAME/log \\" >> tests.mk
+ echo " grep 'BUG|ERROR' reports/$NAME/log \\" >> tests.mk
+ echo " && echo 'NOTOK' > reports/$NAME/log \\" >> tests.mk
echo " || tail -n1 reports/$NAME/log | tr -d '\\\\r' \\" >> tests.mk
echo " > reports/$NAME/OK" >> tests.mk