aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/riscv64/kernel/entry.S4
-rw-r--r--arch/riscv64/kernel/except.c1
-rw-r--r--arch/riscv64/kernel/proc.c3
-rw-r--r--arch/riscv64/kernel/vmem.c90
-rw-r--r--benchmarks/ipc-req/init.c3
-rw-r--r--docs/visionfive2.md3
-rw-r--r--include/arch/tcb.h69
-rw-r--r--src/uapi/ipc.c38
-rw-r--r--src/vmem.c10
-rw-r--r--tests/Makefile4
-rw-r--r--tests/create-exhaustion/source.mk3
-rw-r--r--tests/create/source.mk3
-rw-r--r--tests/detach/source.mk3
-rw-r--r--tests/fork-exhaustion/source.mk3
-rw-r--r--tests/fork/source.mk3
-rw-r--r--tests/hello-world/source.mk3
-rw-r--r--tests/ipc-notify/source.mk3
-rw-r--r--tests/ipc-req/init.c28
-rw-r--r--tests/ipc-req/source.mk3
-rw-r--r--tests/malloc/source.mk3
-rw-r--r--tests/noop/source.mk3
-rwxr-xr-xtests/scripts/gen-simple4
-rwxr-xr-xtests/scripts/gen-tests29
23 files changed, 229 insertions, 87 deletions
diff --git a/arch/riscv64/kernel/entry.S b/arch/riscv64/kernel/entry.S
index f894e0d..48851d4 100644
--- a/arch/riscv64/kernel/entry.S
+++ b/arch/riscv64/kernel/entry.S
@@ -92,6 +92,9 @@ continue_trap:
save_callee
save_caller
+ csrr t0, CSR_SEPC
+ sr t0, offsetof_exec(tp)
+
mv a0, s10
/* get actual kernel stack into sp */
mv sp, tp
@@ -117,6 +120,7 @@ handle_exception:
csrr a0, CSR_SEPC
csrr a1, CSR_STVAL
csrr a2, CSR_SCAUSE
+ sr a0, offsetof_exec(tp)
mv sp, tp
call riscv_handle_exception
diff --git a/arch/riscv64/kernel/except.c b/arch/riscv64/kernel/except.c
index e46b584..2525716 100644
--- a/arch/riscv64/kernel/except.c
+++ b/arch/riscv64/kernel/except.c
@@ -1,5 +1,6 @@
#include <kmi/types.h>
#include <kmi/panic.h>
+#include <kmi/debug.h>
#include <kmi/vmem.h>
#include <kmi/bkl.h>
diff --git a/arch/riscv64/kernel/proc.c b/arch/riscv64/kernel/proc.c
index 3ae2e15..ba5c8e7 100644
--- a/arch/riscv64/kernel/proc.c
+++ b/arch/riscv64/kernel/proc.c
@@ -48,7 +48,8 @@ void run_init(struct tcb *t, vm_t fdt, vm_t initrd)
clone_uvmem(r->proc.vmem, t->rpc.vmem);
flush_tlb_all();
- vm_t stack_top = t->rpc_stack - BASE_PAGE_SIZE;
+ /* lore, should probably be codified somewhere */
+ vm_t stack_top = get_stack(t);
info("jumping to %lx with sp = %lx\n", (long)t->callback, stack_top);
bkl_unlock();
diff --git a/arch/riscv64/kernel/vmem.c b/arch/riscv64/kernel/vmem.c
index d66ddec..a1ca54e 100644
--- a/arch/riscv64/kernel/vmem.c
+++ b/arch/riscv64/kernel/vmem.c
@@ -12,6 +12,7 @@
#include <kmi/vmem.h>
#include <kmi/mem.h>
#include <kmi/debug.h>
+#include <arch/proc.h>
#include <arch/cpu.h>
#include "pages.h"
#include "arch.h"
@@ -598,7 +599,8 @@ stat_t setup_rpc_stack(struct tcb *t)
{
/* by default rpc stack is marked inaccessible to generate segfaults on
* access so as to ease stack usage tracking */
- vmflags_t flags = VM_V | VM_R | VM_W | VM_U;
+ vmflags_t flags = VM_V | VM_R |
+ VM_W /*| VM_U note how this is missing */;
for (size_t i = 0; i < rpc_pages; ++i) {
pm_t page = alloc_page(BASE_PAGE);
@@ -620,8 +622,12 @@ stat_t setup_rpc_stack(struct tcb *t)
t->arch.rpc_leaf = (struct vmem *)__find_vmem(t->rpc.vmem,
RPC_STACK_BASE,
NULL);
- /* we count downward in base pages */
- t->arch.rpc_idx = rpc_pages;
+ /* we count downward in base pages, the top page is *always* reserved */
+ t->arch.rpc_idx = rpc_pages - 1;
+
+ /* mark our stack region inaccessible to userspace */
+ vm_t stack_top = t->rpc_stack - BASE_PAGE_SIZE;
+ set_stack(t, stack_top);
return OK;
}
@@ -640,7 +646,7 @@ void destroy_rpc_stack(struct tcb *t)
void reset_rpc_stack(struct tcb *t)
{
t->rpc_stack = RPC_STACK_BASE + (BASE_PAGE_SIZE * rpc_pages);
- t->arch.rpc_idx = rpc_pages;
+ t->arch.rpc_idx = rpc_pages - 1;
}
bool rpc_stack_empty(pm_t addr)
@@ -658,37 +664,85 @@ vm_t rpc_position(struct tcb *t)
return RPC_STACK_BASE + (BASE_PAGE_SIZE * t->arch.rpc_idx);
}
-void mark_rpc_invalid(struct tcb *t, vm_t top)
+/* these following stack handling routines are pretty mind-bending
+ * unfortunately, might there be a better way? */
+
+bool in_rpc_stack(struct tcb *t, vm_t addr)
+{
+ return addr >= RPC_STACK_BASE
+ && addr < RPC_STACK_BASE + BASE_PAGE_SIZE * t->arch.rpc_idx;
+}
+
+void close_rpc(struct tcb *t)
{
struct vmem *b = t->arch.rpc_leaf;
int top_idx = t->arch.rpc_idx;
- int bottom_idx = (top - RPC_STACK_BASE) / BASE_PAGE_SIZE;
- assert(bottom_idx < top_idx);
- while (top_idx != bottom_idx) {
+ /* absolute max iter count */
+ for (; top_idx < (int)rpc_pages; --top_idx) {
+ /* mark inaccessible until we reach the first inaccessible
+ * 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));
- top_idx--;
}
+ /* make page following closed region accessible so we can avoid an
+ * unnecessary page fault */
+ pm_t *pte = (pm_t *)&b->leaf[top_idx];
+ set_bits(*pte, vp_flags(VM_U));
t->arch.rpc_idx = top_idx;
}
-void mark_rpc_valid(struct tcb *t, vm_t bottom)
+void shrink_rpc(struct tcb *t)
{
struct vmem *b = t->arch.rpc_leaf;
- int bottom_idx = t->arch.rpc_idx;
- int top_idx = (bottom - RPC_STACK_BASE) / BASE_PAGE_SIZE;
- assert(bottom_idx <= top_idx);
+ int top_idx = t->arch.rpc_idx;
+
+ for (; top_idx > 0; ++top_idx) {
+ pm_t *pte = (pm_t *)&b->leaf[top_idx];
+ if (!(pte_flags(*pte) & VM_U))
+ break;
+
+ clear_bits(*pte, vp_flags(VM_U));
+ }
+
+ t->arch.rpc_idx = top_idx;
+}
+
+void open_rpc(struct tcb *t, vm_t top)
+{
+ 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);
+
+ /* 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];
+ set_bits(*pte, vp_flags(VM_U));
+ }
+
+ t->arch.rpc_idx = new_idx - 1;
+}
+
+void grow_rpc(struct tcb *t, vm_t top)
+{
+ assert(is_aligned(top, BASE_PAGE_SIZE));
+ struct vmem *b = t->arch.rpc_leaf;
+ int top_idx = t->arch.rpc_idx;
+ int bottom_idx = (top - RPC_STACK_BASE) / BASE_PAGE_SIZE;
+ assert (top_idx >= bottom_idx);
- while (top_idx != bottom_idx) {
- pm_t *pte = (pm_t *)&b->leaf[bottom_idx];
+ for (int i = bottom_idx; i < top_idx; ++i) {
+ pm_t *pte = (pm_t *)&b->leaf[i];
/* make page accessible from userspace */
set_bits(*pte, vp_flags(VM_U));
- /* clear used bits */
- clear_bits(*pte, vp_flags(VM_A | VM_D));
- bottom_idx++;
}
t->arch.rpc_idx = bottom_idx;
diff --git a/benchmarks/ipc-req/init.c b/benchmarks/ipc-req/init.c
index 02064a6..910e018 100644
--- a/benchmarks/ipc-req/init.c
+++ b/benchmarks/ipc-req/init.c
@@ -12,8 +12,9 @@ START(pid, tid, d0, d1, d2, d3)
uint64_t timebase = sys_timebase();
uint64_t start = sys_ticks();
- for (size_t i = 0; i < 1000; ++i)
+ for (size_t i = 0; i < 1000; ++i) {
sys_ipc_req0(1);
+ }
uint64_t end = sys_ticks();
report(start, end, timebase);
diff --git a/docs/visionfive2.md b/docs/visionfive2.md
index 250a844..24677f8 100644
--- a/docs/visionfive2.md
+++ b/docs/visionfive2.md
@@ -13,6 +13,9 @@ point.
The following is how to boot with an SD card. The board seems to support
serial and ethernet booting, but I haven't looked into how they work.
+The SD card should have an empty GPT table, you can create one with
+`fdisk /dev/sdX/`.
+
1. Build `kmi` with `GENERIC_UBOOT=1`. At the moment, SMP is broken on the
VisionFive2, so you'll need to comment out `smp_bringup(d, fdt);` in
`main.c`.
diff --git a/include/arch/tcb.h b/include/arch/tcb.h
index 18386d3..709a3db 100644
--- a/include/arch/tcb.h
+++ b/include/arch/tcb.h
@@ -77,20 +77,71 @@ bool rpc_stack_empty(pm_t addr);
vm_t rpc_position(struct tcb *t);
/**
- * Mark RPC stack up to \p top accessible from userspace.
+ * Checks whether an address is in the rpc stack.
+ * Currently also serves double-duty to check that the address is currently
+ * inaccessible, but could be made accessible with \ref grow_rpc. Used by
+ * \ref handle_pagefault.
*
- * @param t Thread whose RPC stack is being modified.
- * @param top Address up to where stack should be accessible from userspace.
+ * @param t tcb to check.
+ * @param addr Address to check.
+ * @return \ref true if the above holds, \ref false otherwise.
*/
-void mark_rpc_valid(struct tcb *t, vm_t top);
+bool in_rpc_stack(struct tcb *t, vm_t addr);
/**
- * Mark RPC stack down to \p bottom inaccessible from userspace.
+ * Shrinks rpc stack down to where the previous reserved area is, so stack looks
+ * something like this:
*
- * @param t Thread whose RPC stack is being modified.
- * @param bottom Address down to where stack should be inaccessible from
- * userspace.
+ * ```
+ * ctx | user | <
+ * v
+ * ctx | <
+ * ```
+ *
+ * Implementation note: t->arch.rpc_idx now points to ctx.
+ *
+ * @param t tcb to shrink.
+ */
+void shrink_rpc(struct tcb *t);
+
+/**
+ * Opens back up a previous stack frame, so
+ * ```
+ * ctx | user | ctx | <
+ * v
+ * ctx | user | <
+ * ```
+ *
+ * @param t tcb to open up again.
+ * @param top Address to where previous ctx is.
+ */
+void open_rpc(struct tcb *t, vm_t top);
+
+/**
+ * Adds more accessible space on the stack, so
+ * ```
+ * ctx | user | <
+ * v
+ * ctx | user | user | <
+ * ```
+ *
+ * @param t tcb whose stack to grow.
+ * @param top Address to grow to.
+ */
+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 mark_rpc_invalid(struct tcb *t, vm_t bottom);
+void close_rpc(struct tcb *t);
#endif /* KMI_ARCH_TCB_H */
diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c
index 6f6862e..3e9c04a 100644
--- a/src/uapi/ipc.c
+++ b/src/uapi/ipc.c
@@ -53,7 +53,8 @@ static inline void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s)
t->pid = r->rid;
/* make sure updates are visible when swapping to the new virtual memory */
- mark_rpc_invalid(t, s);
+ close_rpc(t);
+ set_stack(t, s);
}
/**
@@ -84,25 +85,9 @@ static inline vm_t enter_rpc(struct tcb *t, struct sys_ret a,
ctx->pid = t->pid;
ctx->eid = t->eid;
ctx->notify = flags & IPC_NOTIFY;
- 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;
-
- /** @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;
- return new_stack;
+ ctx->rpc_stack = t->rpc_stack;
+ t->rpc_stack = rpc_stack;
+ return rpc_stack - BASE_PAGE_SIZE;
}
/**
@@ -215,9 +200,9 @@ void notify(struct tcb *t, enum notify_flag flags)
*/
static void leave_rpc(struct tcb *t, struct sys_ret a)
{
- vm_t rpc_stack = t->rpc_stack + BASE_PAGE_SIZE;
+ vm_t rpc_stack = t->rpc_stack;
struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
- t->regs = (vm_t)ctx;
+ t->regs = (vm_t)ctx->rpc_stack - sizeof(struct call_ctx);
/* again, get rid of args as fast as possible */
if (!ctx->notify)
@@ -232,9 +217,9 @@ static void leave_rpc(struct tcb *t, struct sys_ret a)
break;
}
- rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE;
+ rpc_stack = ctx->rpc_stack;
ctx = (struct call_ctx *)(rpc_stack) - 1;
- t->regs = (vm_t)ctx;
+ t->regs = (vm_t)ctx->rpc_stack - sizeof(struct call_ctx);
r = get_tcb(ctx->pid);
/* equivalent to return_args1 but without returning so we can
@@ -249,7 +234,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 */
- mark_rpc_valid(t, ctx->rpc_stack);
+ shrink_rpc(t);
+ open_rpc(t, ctx->rpc_stack);
+ flush_tlb_all();
+
t->rpc_stack = ctx->rpc_stack;
t->pid = ctx->pid;
t->eid = ctx->eid;
diff --git a/src/vmem.c b/src/vmem.c
index 0404202..8c23d4f 100644
--- a/src/vmem.c
+++ b/src/vmem.c
@@ -427,9 +427,17 @@ vmflags_t sanitize_uvflags(vmflags_t flags)
void handle_pagefault(vm_t addr)
{
+ info("page fault at %lx\n", addr);
struct tcb *t = cur_tcb();
- struct tcb *p = get_cproc(t);
+ if (in_rpc_stack(t, addr)) {
+ vm_t aligned = align_down(addr, BASE_PAGE_SIZE);
+ grow_rpc(t, aligned);
+ flush_tlb_all();
+ return;
+ }
+
+ struct tcb *p = get_cproc(t);
size_t ref = __page(addr);
struct mem_region *m = find_closest_used_region(&p->uvmem.region, addr);
diff --git a/tests/Makefile b/tests/Makefile
index 0a662f7..105b9bb 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -6,8 +6,12 @@ check:
SOURCES != echo */source.mk
DO != echo -n > tests.mk
+
+TESTS :=
include $(SOURCES)
+DO != ./scripts/gen-tests $(TESTS)
+
RM ?= rm
.PHONY: clean
diff --git a/tests/create-exhaustion/source.mk b/tests/create-exhaustion/source.mk
index d096746..5b90073 100644
--- a/tests/create-exhaustion/source.mk
+++ b/tests/create-exhaustion/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n create-exhaustion -p init init.c
-DO != ./scripts/gen-simple -n create-exhaustion -p init
+TESTS += create-exhaustion
diff --git a/tests/create/source.mk b/tests/create/source.mk
index b9f8efe..7aa5eb7 100644
--- a/tests/create/source.mk
+++ b/tests/create/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n create -p init init.c
-DO != ./scripts/gen-simple -n create -p init
+TESTS += create
diff --git a/tests/detach/source.mk b/tests/detach/source.mk
index 843562f..c248759 100644
--- a/tests/detach/source.mk
+++ b/tests/detach/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n detach -p init init.c
-DO != ./scripts/gen-simple -n detach -p init
+TESTS += detach
diff --git a/tests/fork-exhaustion/source.mk b/tests/fork-exhaustion/source.mk
index 458973d..7dc7d36 100644
--- a/tests/fork-exhaustion/source.mk
+++ b/tests/fork-exhaustion/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n fork-exhaustion -p init init.c
-DO != ./scripts/gen-simple -n fork-exhaustion -p init
+TESTS += fork-exhaustion
diff --git a/tests/fork/source.mk b/tests/fork/source.mk
index 47572e6..e78d467 100644
--- a/tests/fork/source.mk
+++ b/tests/fork/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n fork -p init init.c
-DO != ./scripts/gen-simple -n fork -p init
+TEST += fork
diff --git a/tests/hello-world/source.mk b/tests/hello-world/source.mk
index 5fd60ff..a24092c 100644
--- a/tests/hello-world/source.mk
+++ b/tests/hello-world/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n hello-world -p init init.c
-DO != ./scripts/gen-simple -n hello-world -p init
+TESTS += hello-world
diff --git a/tests/ipc-notify/source.mk b/tests/ipc-notify/source.mk
index e154996..635d335 100644
--- a/tests/ipc-notify/source.mk
+++ b/tests/ipc-notify/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n ipc-notify -p init init.c
-DO != ./scripts/gen-simple -n ipc-notify -p init
+TESTS += ipc-notify
diff --git a/tests/ipc-req/init.c b/tests/ipc-req/init.c
index 3439335..90e04a7 100644
--- a/tests/ipc-req/init.c
+++ b/tests/ipc-req/init.c
@@ -4,19 +4,25 @@ START(pid, tid, d0, d1, d2, d3)
{
check(pid == 0 || pid == 1, "illegal pid for init");
if (pid == 0) {
- /* send request to ourselves */
- printf("sending ipc req to ourselves\n");
- struct sys_ret r = sys_ipc_req4(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("doing nonsense response\n");
+ enum sys_status s = sys_ipc_resp0();
+ check(s != OK, "OK return?\n");
+
+ for (size_t i = 0; i < 3; ++i) {
+ printf("sending ipc req %zd to ourselves\n", i);
+ struct sys_ret r = sys_ipc_req4(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");
+ }
/* try to request to non-existing proc */
- r = sys_ipc_req0(200);
+ struct sys_ret r = sys_ipc_req0(200);
check(r.s != OK, "got OK return for illegal pid\n");
}
else if (pid == 1) {
diff --git a/tests/ipc-req/source.mk b/tests/ipc-req/source.mk
index 0e65407..4b77d99 100644
--- a/tests/ipc-req/source.mk
+++ b/tests/ipc-req/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n ipc-req -p init init.c
-DO != ./scripts/gen-simple -n ipc-req -p init
+TESTS += ipc-req
diff --git a/tests/malloc/source.mk b/tests/malloc/source.mk
index 9498682..01b4d6e 100644
--- a/tests/malloc/source.mk
+++ b/tests/malloc/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n malloc -p init init.c
-DO != ./scripts/gen-simple -n malloc -p init
+TESTS += malloc
diff --git a/tests/noop/source.mk b/tests/noop/source.mk
index cbb87b7..0663967 100644
--- a/tests/noop/source.mk
+++ b/tests/noop/source.mk
@@ -1,2 +1 @@
-DO != ./scripts/gen-prog -n noop -p init init.c
-DO != ./scripts/gen-simple -n noop -p init
+TESTS += noop
diff --git a/tests/scripts/gen-simple b/tests/scripts/gen-simple
index 418cc02..a942fb3 100755
--- a/tests/scripts/gen-simple
+++ b/tests/scripts/gen-simple
@@ -28,8 +28,10 @@ echo " timeout --foreground 30s \$(QEMU) \
echo "TESTS += $NAME" >> tests.mk
echo ".PHONY: $NAME" >> tests.mk
-echo "$NAME: 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 " || tail -n1 reports/$NAME/log | tr -d '\\\\r' \\" >> tests.mk
echo " > reports/$NAME/OK" >> tests.mk
+
+echo "$NAME: reports/$NAME/OK" >> tests.mk
diff --git a/tests/scripts/gen-tests b/tests/scripts/gen-tests
new file mode 100755
index 0000000..7095e30
--- /dev/null
+++ b/tests/scripts/gen-tests
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+mkdir -p $(for d in "${@}"; do echo $d; done | sed "s|^|build/|")
+mkdir -p $(for d in "${@}"; do echo $d; done | sed "s|^|reports/|")
+
+for NAME in "${@}"; do
+ echo "build/$NAME/init.d:" >> tests.mk
+ echo "-include build/$NAME/init.d" >> tests.mk
+
+ echo "build/$NAME/init: $NAME/init.c \$(COMMON) \$(KMI)" >> tests.mk
+ echo " \$(COMPILE_TEST) $NAME/init.c \$(COMMON) -o build/$NAME/init" >> tests.mk
+
+ echo "build/$NAME/initrd: build/$NAME/init" >> tests.mk
+ echo " echo build/$NAME/init | \$(GEN_INITRD) build/$NAME/initrd" >> tests.mk
+
+ echo "reports/$NAME/log: build/$NAME/initrd" >> tests.mk
+ echo " timeout --foreground 30s \$(QEMU) \
+ 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 " || tail -n1 reports/$NAME/log | tr -d '\\\\r' \\" >> tests.mk
+ echo " > reports/$NAME/OK" >> tests.mk
+
+ echo "TESTS += $NAME" >> tests.mk
+ echo ".PHONY: $NAME" >> tests.mk
+ echo "$NAME: reports/$NAME/OK" >> tests.mk
+done