aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-07-09 22:12:27 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-07-09 22:12:27 +0300
commite8a1df103cc28419dd7d4439c0b1d1739d110f78 (patch)
tree2b4646f262a9008d80ca58fb2ecab2b5bf897873
parent89d7cf197b2cae130565467bdfad5d5ef5fed2dd (diff)
downloadkmi-e8a1df103cc28419dd7d4439c0b1d1739d110f78.tar.gz
kmi-e8a1df103cc28419dd7d4439c0b1d1739d110f78.zip
start working on tests
+ Current setup will likely not last long, just a stopgap until I figure out how I want to build each testcase etc. Probably dir based, but I'll probably add some scripts that generate the build rules for each test case (or binary?). Also, should probably put ouput files in a build directory and keep the source clean, but again, good enough for now. Will have to start implementing more extensive tests, and probably come up with some way to compare textual output etc.
-rw-r--r--.gitignore3
-rw-r--r--Makefile4
-rwxr-xr-xarch/riscv64/conf/initbin3968 -> 0 bytes
-rw-r--r--arch/riscv64/conf/init.c397
-rw-r--r--arch/riscv64/conf/initrdbin4608 -> 0 bytes
-rw-r--r--arch/riscv64/kernel/main.c1
-rw-r--r--arch/riscv64/kernel/pmem.c1
-rw-r--r--arch/riscv64/kernel/power.c6
-rw-r--r--arch/riscv64/kernel/proc.c10
-rw-r--r--include/kmi/caps.h51
-rw-r--r--include/kmi/power.h14
-rw-r--r--include/kmi/syscalls.h89
-rw-r--r--include/kmi/tcb.h2
-rw-r--r--include/kmi/types.h32
-rw-r--r--include/kmi/uapi.h18
-rw-r--r--src/panic.c3
-rw-r--r--src/proc.c2
-rw-r--r--src/regions.c1
-rw-r--r--src/uapi/cap.c52
-rw-r--r--src/uapi/conf.c10
-rw-r--r--src/uapi/dispatch.c9
-rw-r--r--src/uapi/irq.c13
-rw-r--r--src/uapi/mem.c6
-rw-r--r--src/uapi/proc.c17
-rw-r--r--tests/Makefile34
-rw-r--r--tests/common/arch/riscv64/syscall.h50
-rw-r--r--tests/common/printf.c583
-rw-r--r--tests/common/test.h324
-rw-r--r--tests/hello-world/init.c14
-rw-r--r--tests/hello-world/source.mk8
-rw-r--r--tests/noop/init.c13
-rw-r--r--tests/noop/source.mk8
32 files changed, 1201 insertions, 574 deletions
diff --git a/.gitignore b/.gitignore
index 8904930..9cf038c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,9 @@
docs/output
deps.mk
*.img
+initrd
+init
+build
# keep gitkeep files wherever they are
!.gitkeep
diff --git a/Makefile b/Makefile
index 206b811..6358e29 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,10 @@
all: setup
$(MAKE) -f scripts/makefile
+.PHONY: check
+check: all
+ $(MAKE) -C tests check
+
# this kicks all unrecognised targets to the client script.
# note that trying to compile individual files, e.g.
#
diff --git a/arch/riscv64/conf/init b/arch/riscv64/conf/init
deleted file mode 100755
index b378582..0000000
--- a/arch/riscv64/conf/init
+++ /dev/null
Binary files differ
diff --git a/arch/riscv64/conf/init.c b/arch/riscv64/conf/init.c
deleted file mode 100644
index c4e8064..0000000
--- a/arch/riscv64/conf/init.c
+++ /dev/null
@@ -1,397 +0,0 @@
-/* SPDX-License-Identifier: copyleft-next-0.3.1 */
-/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
-
-/**
- * Header for silencing scripts/warn-undocumented
- *
- * @file init.c
- *
- * Test init program.
- *
- * This file is only temporarily in this tree.
- * At some point in the (hopefully) near future, I intend to move the
- * kernel code and initrd generation stuff into separate repositories, to make
- * things easier for myself. For now though, this is good enough.
- *
- * Compile with
- * riscv64-unknown-elf-gcc -Driscv64 -ffreestanding -nostdlib
- *
- * Create initrd with
- * echo init | cpio -H newc -o > initrd
- */
-
-#include <stdint.h>
-#include <stddef.h>
-#include "../../../include/kmi/syscalls.h"
-
-char *strcpy(char * restrict dst, const char * restrict src)
-{
- const char *s1 = src;
- char *s2 = dst;
-
- while (*s1)
- *(s2++) = *(s1++);
-
- return dst;
-}
-
-static inline struct sys_ret ecall(size_t n,
- sys_arg_t arg0, sys_arg_t arg1,
- sys_arg_t arg2, sys_arg_t arg3,
- sys_arg_t arg4, sys_arg_t arg5)
-{
- /* here a static assert of n <= 6 && n >= 1 would be ideal */
-
- register long a0 asm ("a0") = arg0;
- register long a1 asm ("a1") = arg1;
- register long a2 asm ("a2") = arg2;
- register long a3 asm ("a3") = arg3;
- register long a4 asm ("a4") = arg4;
- register long a5 asm ("a5") = arg5;
-
-#define OUTPUTS "+r" (a0), "=r" (a1), "=r" (a2), "=r" (a3), "=r" (a4), "=r" (a5)
-
- if (n == 1)
- asm volatile ("ecall" : OUTPUTS : "r" (a0));
-
- else if (n == 2)
- asm volatile ("ecall" : OUTPUTS : "r" (a0), "r" (a1));
-
- else if (n == 3)
- asm volatile ("ecall" : OUTPUTS : "r" (a0), "r" (a1), "r" (a2));
-
- else if (n == 4)
- asm volatile ("ecall" : OUTPUTS
- : "r" (a0), "r" (a1), "r" (a2), "r" (a3));
-
- else if (n == 5)
- asm volatile ("ecall" : OUTPUTS
- : "r" (a0), "r" (a1), "r" (a2), "r" (a3),
- "r" (a4));
-
- else if (n == 6)
- asm volatile ("ecall"
- : OUTPUTS
- : "r" (a0), "r" (a1), "r" (a2), "r" (a3),
- "r" (a4), "r" (a5));
-
-#undef OUTPUTS
- return (struct sys_ret){a0, a1, a2, a3, a4, a5};
-}
-
-#define ecall1(a) ecall(1, a, 0, 0, 0, 0, 0)
-#define ecall2(a, b) ecall(2, a, b, 0, 0, 0, 0)
-#define ecall3(a, b, c) ecall(3, a, b, c, 0, 0, 0)
-#define ecall4(a, b, c, d) ecall(4, a, b, c, d, 0, 0)
-#define ecall5(a, b, c, d, e) ecall(5, a, b, c, d, e, 0)
-#define ecall6(a, b, c, d, e, f) ecall(6, a, b, c, d, e, f)
-
-static void sys_noop()
-{
- ecall1(SYS_NOOP);
-}
-
-static void sys_putch(char c)
-{
- ecall2(SYS_PUTCH, c);
-}
-
-static uint64_t sys_timebase()
-{
- struct sys_ret r = ecall1(SYS_TIMEBASE);
-#if defined(_LP64)
- return r.ar0;
-#else
- uint64_t t = r.ar0;
- t <<= 32;
- return t + r.ar1;
-#endif
-}
-
-static uint64_t sys_ticks()
-{
- struct sys_ret r = ecall1(SYS_TICKS);
-#if defined(_LP64)
- return r.ar0;
-#else
- uint64_t t = r.ar0;
- t <<= 32;
- return t + r.ar1;
-#endif
-}
-
-static void puts(const char *s)
-{
- while (*s)
- sys_putch(*s++);
-}
-
-static void _print_number(uint64_t v)
-{
- if (v == 0)
- return;
-
- _print_number(v / 10);
- sys_putch((v % 10) + '0');
-}
-
-static void print_number(uint64_t v)
-{
- if (v == 0) {
- sys_putch('0');
- return;
- }
-
- _print_number(v);
-}
-
-static void print_value(const char *s, uint64_t v)
-{
- puts(s);
- puts(": ");
-
- print_number(v);
- sys_putch('\n');
-}
-
-static uint64_t sys_fork()
-{
- struct sys_ret r = ecall1(SYS_FORK);
-
- if (r.s != 0) {
- print_value("fork() failed with error ", r.s);
- return r.s;
- }
-
- return r.ar0;
-}
-
-static uint64_t sys_swap(long tid)
-{
- struct sys_ret r = ecall2(SYS_SWAP, tid);
-
- if (r.s != 0) {
- print_value("swap() failed with error ", r.s);
- return r.s;
- }
-
- return 0;
-}
-
-static inline struct sys_ret sys_ipc_req(sys_arg_t pid,
- sys_arg_t d0, sys_arg_t d1,
- sys_arg_t d2,
- sys_arg_t d3)
-{
- struct sys_ret r = ecall6(SYS_IPC_REQ, pid, d0, d1, d2, d3);
-
- if (r.s)
- print_value("ipc_req() failed with error ", r.s);
-
- return r;
-}
-
-static void sys_ipc_resp(sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
-{
- ecall5(SYS_IPC_RESP, d0, d1, d2, d3);
-}
-
-static void sys_poweroff(long type)
-{
- ecall2(SYS_POWEROFF, type);
-}
-
-static void *sys_req_mem(size_t count)
-{
- struct sys_ret r = ecall3(SYS_REQ_MEM, count, VM_R | VM_W);
- if (r.s) {
- print_value("sys_req_mem() failed with error ", r.s);
- return NULL;
- }
-
- return (void *)r.ar0;
-}
-
-static size_t get_ram_usage()
-{
- struct sys_ret r = ecall2(SYS_CONF_GET, CONF_RAM_USAGE);
- if (r.s) {
- print_value("getting RAM usage failed with error ", r.s);
- return 0;
- }
-
- return r.ar0;
-}
-
-static size_t get_ram_size()
-{
- struct sys_ret r = ecall2(SYS_CONF_GET, CONF_RAM_SIZE);
- if (r.s) {
- print_value("getting RAM size failed with error ", r.s);
- return 0;
- }
-
- return r.ar0;
-}
-
-static void sys_free_mem(void *p)
-{
- struct sys_ret r = ecall2(SYS_FREE_MEM, (long)p);
-
- if (r.s)
- print_value("sys_free_mem() failed with error ", r.s);
-}
-
-static void *sys_req_sharedmem(long tid, unsigned long size, void **cbuf)
-{
- struct sys_ret r = ecall3(SYS_REQ_SHAREDMEM, size, VM_R | VM_W);
- if (r.s) {
- print_value("sys_req_sharedmem() failed with error ", r.s);
- return NULL;
- }
-
- void *rw_buf = (void *)r.ar0;
-
- r = ecall4(SYS_REF_SHAREDMEM, tid, (sys_arg_t)rw_buf, VM_R | VM_W);
- if (r.s) {
- print_value("sys_ref_sharedmem() failed with error ", r.s);
- return NULL;
- }
-
- *cbuf = (void *)r.ar0;
- return rw_buf;
-}
-
-/* I'm guessing my elf parser doesn't handle data pages correctly yet... */
-static char *rw_buf = 0;
-static size_t rw_buf_size = 4096;
-
-static void sys_sleep()
-{
- ecall1(SYS_SLEEP);
-}
-
-#define CSR_TIME "0xc01"
-#define csr_read(csr, \
- res) __asm__ volatile ("csrr %0, " csr : "=r" (res) :: "memory")
-
-#define CSR_CYCLE "0xc00"
-
-static void handle_kernel(long pid, long tid, long d0, long d1, long d2,
- long d3)
-{
- if (d0 != SYS_USER_BOOTED)
- return;
-
- if (tid != 1) {
- puts("Woo, more cores!\n");
- while (1)
- sys_sleep();
- }
-
- /* otherwise */
- sys_noop();
- puts("Hello, world!\n");
-
- uint64_t second = sys_timebase();
- print_value("Timebase", second);
-
- uint64_t n = 0, i, start, cn, cstart, cend;
- start = i = sys_ticks();
- csr_read(CSR_CYCLE, cstart);
- while (i < start + second) {
- i = sys_ticks();
- n++;
- }
- csr_read(CSR_CYCLE, cend);
-
- print_value("Start ticks", start);
- print_value("End ticks", i);
- print_value("Syscalls per second", n);
- print_value("Executed cycles per second", cend - cstart);
-
- puts("Starting fork():\n");
- long cid = sys_fork();
- if (cid != 0) {
- print_value("Child pid", cid);
- puts("Swapping to child...\n");
- while(1) sys_swap(cid);
- }
-
- puts("Hello from child!\n");
-
- puts("Doing swaps...\n");
- csr_read(CSR_TIME, i);
- start = i; n = 0;
- while (i < start + second) {
- sys_swap(1);
- csr_read(CSR_TIME, i);
- n++;
- }
-
- print_value("Swaps (both ways) per second", n);
-
- puts("Doing ipc requests...\n");
- csr_read(CSR_TIME, i);
- start = i; n = 0;
- while (i < start + second) {
- sys_ipc_req(1, 0, 0, 0, 0);
- csr_read(CSR_TIME, i);
- n++;
- }
-
- print_value("IPC requests per second", n);
-
- puts("Doing memory allocations...\n");
-
- size_t ram = get_ram_usage();
- size_t size = get_ram_size();
- print_value("Memory usage before allocations: ", ram);
- print_value("Memory size in total: ", size);
- for (i = 0; i < 1000000; ++i) {
- char *p = sys_req_mem(10);
- *p = 'c';
- sys_free_mem(p);
- }
-
- ram = get_ram_usage();
- print_value("Memory usage after allocations: ", ram);
-
- puts("Checking shared memory\n");
- struct sys_ret r = sys_ipc_req(1, 1, 0, 0, 0);
- rw_buf = (char *)r.ar1;
- rw_buf_size = (size_t)r.ar2;
- print_value("Response from", r.ar0);
- print_value("Shared memory ptr", (uintptr_t)rw_buf);
- print_value("Shared memory size", rw_buf_size);
-
- rw_buf[0] = 0;
- puts("Sending string...\n");
- strcpy(rw_buf, "Hello from the other side!\n");
- sys_ipc_req(1, 2, 0, 0, 0);
-
- sys_poweroff(0);
-}
-
-void _start(long pid, long tid, long d0, long d1, long d2, long d3)
-{
- if (pid == 0)
- handle_kernel(pid, tid, d0, d1, d2, d3);
-
- /* otherwise, implement test functionality */
- if (d0 == 1) {
- void *cbuf = 0;
- rw_buf = sys_req_sharedmem(pid, rw_buf_size, &cbuf);
- sys_ipc_resp((long)cbuf, rw_buf_size, 0, 0);
- __builtin_unreachable();
-
- } else if (d0 == 2) {
- puts("Received string: ");
- puts(rw_buf);
- sys_ipc_resp(0, 0, 0, 0);
- __builtin_unreachable();
- }
-
- sys_ipc_resp(0, 0, 0, 0);
- __builtin_unreachable();
-}
diff --git a/arch/riscv64/conf/initrd b/arch/riscv64/conf/initrd
deleted file mode 100644
index 4647812..0000000
--- a/arch/riscv64/conf/initrd
+++ /dev/null
Binary files differ
diff --git a/arch/riscv64/kernel/main.c b/arch/riscv64/kernel/main.c
index 01b8957..c6b5e4a 100644
--- a/arch/riscv64/kernel/main.c
+++ b/arch/riscv64/kernel/main.c
@@ -6,6 +6,7 @@
* riscv64 main
*/
+#include <kmi/syscalls.h>
#include <kmi/utils.h>
#include <arch/arch.h>
#include "csr.h"
diff --git a/arch/riscv64/kernel/pmem.c b/arch/riscv64/kernel/pmem.c
index 56769c6..36f0284 100644
--- a/arch/riscv64/kernel/pmem.c
+++ b/arch/riscv64/kernel/pmem.c
@@ -6,6 +6,7 @@
* riscv64 implementation of arch-specific physical memory handling
*/
+#include <kmi/syscalls.h>
#include <arch/pmem.h>
stat_t stat_pmem_conf(void *fdt, size_t *max_order, size_t *base_bits,
diff --git a/arch/riscv64/kernel/power.c b/arch/riscv64/kernel/power.c
index 21e4957..2238eb1 100644
--- a/arch/riscv64/kernel/power.c
+++ b/arch/riscv64/kernel/power.c
@@ -34,15 +34,15 @@ stat_t poweroff(enum poweroff_type type)
/** \todo this only shuts down the cpu itself, but may leave the SOC
* active. Should read from fdt poweroff and syscon-poweroff etc */
switch (type) {
- case SHUTDOWN:
+ case SYS_SHUTDOWN:
sbi_system_reset(SBI_SHUTDOWN, SBI_NO_REASON);
return ERR_MISC;
- case COLD_REBOOT:
+ case SYS_COLD_REBOOT:
sbi_system_reset(SBI_COLD_REBOOT, SBI_NO_REASON);
return ERR_MISC;
- case WARM_REBOOT:
+ case SYS_WARM_REBOOT:
sbi_system_reset(SBI_WARM_REBOOT, SBI_NO_REASON);
return ERR_MISC;
};
diff --git a/arch/riscv64/kernel/proc.c b/arch/riscv64/kernel/proc.c
index 19bd52f..aa33ec7 100644
--- a/arch/riscv64/kernel/proc.c
+++ b/arch/riscv64/kernel/proc.c
@@ -62,11 +62,11 @@ void set_args(struct tcb *t, size_t n, struct sys_ret a)
{
struct riscv_regs *r = (struct riscv_regs *)(t->regs) - 1;
if (n >= 1) r->a0 = a.s;
- if (n >= 2) r->a1 = a.ar0;
- if (n >= 3) r->a2 = a.ar1;
- if (n >= 4) r->a3 = a.ar2;
- if (n >= 5) r->a4 = a.ar3;
- if (n >= 6) r->a5 = a.ar4;
+ if (n >= 2) r->a1 = a.a0;
+ if (n >= 3) r->a2 = a.a1;
+ if (n >= 4) r->a3 = a.a2;
+ if (n >= 5) r->a4 = a.a3;
+ if (n >= 6) r->a5 = a.a4;
}
struct sys_ret get_args(struct tcb *t)
diff --git a/include/kmi/caps.h b/include/kmi/caps.h
index 02f6b3e..cc54dbe 100644
--- a/include/kmi/caps.h
+++ b/include/kmi/caps.h
@@ -16,51 +16,13 @@
#include <kmi/bits.h>
-/** Helper typedef for capabilities. */
-typedef unsigned char capflags_t;
-
-enum {
- /** Thread is allowed to set capabilities of other threads. */
- CAP_CAPS = (1 << 0),
-
- /** Thread is allowed to modify process statuses, create/exec/fork/etc. */
- CAP_PROC = (1 << 1),
-
- /** Thread is allowed to force notification in other thread. */
- CAP_NOTIFY = (1 << 2),
-
- /** Thread is allowed to shut down system. */
- CAP_POWER = (1 << 3),
-
- /** Thread is allowed to access configuration parameters. */
- CAP_CONF = (1 << 4),
-
- /** Thread is allowed to request to handle IRQs. */
- CAP_IRQ = (1 << 5),
-
- /** Thread is allowed to request notification handler. */
- CAP_SIGNAL = (1 << 6),
-
- /** Thread is allowed to request shared memory */
- CAP_SHARED = (1 << 7)
-};
-
-/**
- * Check that offset is OK.
- *
- * @param o Offset to check.
- * @return \ref true is OK, \ref false otherwise.
- */
-#define cap_off_ok(o) (o == 0)
-
/**
* Set capabilities.
*
* @param x Capabilities to modify.
- * @param o Offset.
* @param c Capabilities to set.
*/
-#define set_caps(x, o, c) set_bits(x, c)
+#define set_caps(x, c) set_bits(x, c)
/**
* Clear capabilities.
@@ -69,7 +31,7 @@ enum {
* @param o Offset.
* @param c Capabilities to set.
*/
-#define clear_caps(x, o, c) clear_bits(x, c)
+#define clear_caps(x, c) clear_bits(x, c)
/**
* Copy capabilities.
@@ -80,15 +42,6 @@ enum {
#define copy_caps(x, y) (x = y)
/**
- * Get capabilities at offset \p o.
- *
- * @param x Capabilities to get from.
- * @param o Offset to get capabilities from.
- * @return Capabilities at offset \p o.
- */
-#define get_caps(x, o) (x)
-
-/**
* Check if something has capability.
*
* @param x Capabilities to check in.
diff --git a/include/kmi/power.h b/include/kmi/power.h
index db00999..d0ebeca 100644
--- a/include/kmi/power.h
+++ b/include/kmi/power.h
@@ -10,22 +10,10 @@
* host machines.
*/
+#include <kmi/syscalls.h>
#include <kmi/types.h>
#include <kmi/attrs.h>
-/** Types of powering off. Still unclear what difference there is between warm
- * and cold reboot. */
-enum poweroff_type {
- /** Shut down. */
- SHUTDOWN,
-
- /** Cold or complete reboot. */
- COLD_REBOOT,
-
- /** Warm or partial reboot. */
- WARM_REBOOT
-};
-
/**
* Power off the system.
*
diff --git a/include/kmi/syscalls.h b/include/kmi/syscalls.h
index 1efd0d4..1ca2113 100644
--- a/include/kmi/syscalls.h
+++ b/include/kmi/syscalls.h
@@ -130,10 +130,10 @@ enum sys_code {
/** @name Kernel management. */
/** @{ */
/** Configure system parameters (stack size etc.). */
- SYS_CONF_SET,
+ SYS_SET_CONF,
/** Get system parameters. */
- SYS_CONF_GET,
+ SYS_GET_CONF,
/** Set capability of thread. */
SYS_SET_CAP,
@@ -153,8 +153,11 @@ enum sys_code {
/** Request to handle IRQ. */
SYS_IRQ_REQ,
+ /** Retract IRQ. */
+ SYS_FREE_IRQ,
+
/** Request a notification handler. */
- SYS_REQ_NOTIFICATION,
+ SYS_SET_HANDLER,
/** Request a thread exits. */
SYS_EXIT,
@@ -197,6 +200,19 @@ enum notify_flag {
NOTIFY_ORPHANED = (1 << 3),
};
+/** Types of powering off. Still unclear what difference there is between warm
+ * and cold reboot. */
+enum poweroff_type {
+ /** Shut down. */
+ SYS_SHUTDOWN,
+
+ /** Cold or complete reboot. */
+ SYS_COLD_REBOOT,
+
+ /** Warm or partial reboot. */
+ SYS_WARM_REBOOT
+};
+
/* function declarations should be somewhere else, this file could be used in
* userspace applications as well */
@@ -244,6 +260,63 @@ enum conf_param {
CONF_PAGE_SIZE,
};
+enum sys_cap {
+ /** Thread is allowed to set capabilities of other threads. */
+ CAP_CAPS = (1 << 0),
+
+ /** Thread is allowed to modify process statuses, create/exec/fork/etc. */
+ CAP_PROC = (1 << 1),
+
+ /** Thread is allowed to force notification in other thread. */
+ CAP_NOTIFY = (1 << 2),
+
+ /** Thread is allowed to shut down system. */
+ CAP_POWER = (1 << 3),
+
+ /** Thread is allowed to access configuration parameters. */
+ CAP_CONF = (1 << 4),
+
+ /** Thread is allowed to request to handle IRQs. */
+ CAP_IRQ = (1 << 5),
+
+ /** Thread is allowed to request notification handler. */
+ CAP_SIGNAL = (1 << 6),
+
+ /** Thread is allowed to request shared memory */
+ CAP_SHARED = (1 << 7)
+};
+
+/**
+ * Status codes.
+ * Negative error codes are reserved for general usage, positive error codes are
+ * allowed to be function-specific, although that's sort of difficult to keep
+ * track of.
+ */
+enum sys_status {
+ /** Permission error. */
+ ERR_PERM = -10,
+ /** Internal error, should probably halt */
+ ERR_INT = -9,
+ /** Something went wrong :/ */
+ ERR_MISC = -8,
+ /** Not initialized. */
+ ERR_NOINIT = -7,
+ /** Invalid value. */
+ ERR_INVAL = -6,
+ /** Already exists. */
+ ERR_EXT = -5,
+ /** Out of memory. */
+ ERR_OOMEM = -4,
+ /** Illegal address. */
+ ERR_ADDR = -3,
+ /** Wrong alignment. */
+ ERR_ALIGN = -2,
+ /** Not found. */
+ ERR_NF = -1,
+ /** OK. */
+ OK = 0,
+};
+
/**
* Return structure of syscall.
* \note Field names are generic, and can be used for whatever,
@@ -255,19 +328,19 @@ struct sys_ret {
sys_arg_t s;
/** First argument. */
- sys_arg_t ar0;
+ sys_arg_t a0;
/** Second argument. */
- sys_arg_t ar1;
+ sys_arg_t a1;
/** Third argument. */
- sys_arg_t ar2;
+ sys_arg_t a2;
/** Fourth argument. */
- sys_arg_t ar3;
+ sys_arg_t a3;
/** Fifth argument. */
- sys_arg_t ar4;
+ sys_arg_t a4;
};
#endif /* KMI_SYSCALLS_H */
diff --git a/include/kmi/tcb.h b/include/kmi/tcb.h
index a06d801..a6522f8 100644
--- a/include/kmi/tcb.h
+++ b/include/kmi/tcb.h
@@ -184,7 +184,7 @@ struct tcb {
enum notify_flag notify_flags;
/** Capabilities of thread. */
- capflags_t caps;
+ enum sys_cap caps;
/** Queue that connects together threads waiting for an ipi */
struct queue_head ipi_queue;
diff --git a/include/kmi/types.h b/include/kmi/types.h
index 1da5248..040d3c6 100644
--- a/include/kmi/types.h
+++ b/include/kmi/types.h
@@ -459,38 +459,6 @@ typedef int_fast32_t id_t;
/** Memory region flags. */
typedef uint_fast16_t vmflags_t;
-/**
- * Status codes.
- * Negative error codes are reserved for general usage, positive error codes are
- * allowed to be function-specific, although that's sort of difficult to keep
- * track of.
- */
-/* should this enum be somewhere else? */
-enum status_codes {
- /** Permission error. */
- ERR_PERM = -10,
- /** Internal error, should probably halt */
- ERR_INT = -9,
- /** Something went wrong :/ */
- ERR_MISC = -8,
- /** Not initialized. */
- ERR_NOINIT = -7,
- /** Invalid value. */
- ERR_INVAL = -6,
- /** Already exists. */
- ERR_EXT = -5,
- /** Out of memory. */
- ERR_OOMEM = -4,
- /** Illegal address. */
- ERR_ADDR = -3,
- /** Wrong alignment. */
- ERR_ALIGN = -2,
- /** Not found. */
- ERR_NF = -1,
- /** OK. */
- OK = 0,
-};
-
#include <arch/types.h> /* arch-specific type definitions (pm_t/vm_t etc) */
#endif /* KMI_TYPES_H */
diff --git a/include/kmi/uapi.h b/include/kmi/uapi.h
index 350285f..3f03879 100644
--- a/include/kmi/uapi.h
+++ b/include/kmi/uapi.h
@@ -697,7 +697,7 @@ SYSCALL_DECLARE1(swap, tid);
*
* Returns \ref OK and 0.
*/
-SYSCALL_DECLARE2(conf_set, param, val);
+SYSCALL_DECLARE2(set_conf, param, val);
/**
* Get configuration syscall.
@@ -713,49 +713,49 @@ SYSCALL_DECLARE2(conf_set, param, val);
*
* Returns \ref OK.
*/
-SYSCALL_DECLARE2(conf_get, param, d0);
+SYSCALL_DECLARE2(get_conf, param, d0);
/**
* Set capabilities.
*
* @param t Current tcb.
* @param tid Thread ID whose capabilities to set.
- * @param off Offset of capability, multiple of \c bits(cap).
* @param caps Mask of capabilities to set.
+ * @param c Unused.
* @param d Unused.
* @param e Unused.
*
* Returns \ref OK on success, \ref ERR_INVAL on invalid input.
*/
-SYSCALL_DECLARE3(set_cap, tid, off, caps);
+SYSCALL_DECLARE2(set_cap, tid, caps);
/**
* Get capabilities.
*
* @param t Current tcb.
* @param tid Thread ID whose capabilities to get.
- * @param off Offset of capability, multiple of \c bits(cap).
+ * @param b Unused.
* @param c Unused.
* @param d Unused.
* @param e Unused.
*
* Returns \ref OK, capabilities.
*/
-SYSCALL_DECLARE2(get_cap, tid, off);
+SYSCALL_DECLARE1(cap_get, tid);
/**
* Clear capabilities.
*
* @param t Current tcb.
* @param tid Thread ID whose capabilities to clear.
- * @param off Offset of capability, multiple of \c bits(cap).
* @param cap Mask of capabilities to clear.
+ * @param c Unused.
* @param d Unused.
* @param e Unused.
*
* Returns \ref OK.
*/
-SYSCALL_DECLARE3(clear_cap, tid, off, cap);
+SYSCALL_DECLARE2(clear_cap, tid, cap);
/**
* Set which process to move notifications to for thread.
@@ -771,7 +771,7 @@ SYSCALL_DECLARE3(clear_cap, tid, off, cap);
*
* Returns \ref OK.
*/
-SYSCALL_DECLARE2(req_notification, tid, pid);
+SYSCALL_DECLARE2(set_handler, tid, pid);
/**
* Power off syscall.
diff --git a/src/panic.c b/src/panic.c
index 03c0de8..5495ebe 100644
--- a/src/panic.c
+++ b/src/panic.c
@@ -6,6 +6,7 @@
* Kernel panic handler implementation.
*/
+#include <kmi/syscalls.h>
#include <kmi/power.h>
#include <kmi/debug.h>
@@ -17,7 +18,7 @@ void kernel_panic(void *pc, void *addr, long cause)
info("attempting to reboot\n");
- poweroff(COLD_REBOOT);
+ poweroff(SYS_COLD_REBOOT);
/* spin if poweroff failed for some reason */
error("reboot failed, spinning in place\n");
diff --git a/src/proc.c b/src/proc.c
index 09dcc5f..5753659 100644
--- a/src/proc.c
+++ b/src/proc.c
@@ -49,7 +49,7 @@ stat_t init_proc(void *fdt, vm_t *proc_fdt, vm_t *proc_initrd)
t->notify_id = t->tid;
/* init process has all capabilities */
- set_caps(t->caps, 0,
+ set_caps(t->caps,
CAP_CAPS | CAP_PROC | CAP_SIGNAL | CAP_POWER | CAP_NOTIFY |
CAP_SHARED);
diff --git a/src/regions.c b/src/regions.c
index 1aa318d..4d82420 100644
--- a/src/regions.c
+++ b/src/regions.c
@@ -7,6 +7,7 @@
* subsystems.
*/
+#include <kmi/syscalls.h>
#include <kmi/regions.h>
#include <kmi/assert.h>
#include <kmi/pmem.h>
diff --git a/src/uapi/cap.c b/src/uapi/cap.c
index 157ef47..050d181 100644
--- a/src/uapi/cap.c
+++ b/src/uapi/cap.c
@@ -11,25 +11,6 @@
*/
/**
- * Check that values are legal for thread ID and offset.
- *
- * @param tid Thread ID of thread whose caps should be fetched.
- * @param off Offset of capabilities.
- * @return Pointer to capabilities of \p tid, \c 0 otherwise.
- */
-static capflags_t *__get_tcb_caps(id_t tid, size_t off)
-{
- if (!cap_off_ok(off))
- return NULL;
-
- struct tcb *t = get_tcb(tid);
- if (!t)
- return NULL;
-
- return &t->caps;
-}
-
-/**
* Set capabilities.
*
* @param t Current tcb.
@@ -38,17 +19,16 @@ static capflags_t *__get_tcb_caps(id_t tid, size_t off)
* @param caps Mask of capabilities to set.
* @return \ref OK on success, \ref ERR_INVAL on invalid input.
*/
-SYSCALL_DEFINE3(set_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off,
- sys_arg_t caps)
+SYSCALL_DEFINE2(set_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t caps)
{
if (!is_set(t->caps, CAP_CAPS))
return_args1(t, ERR_PERM);
- capflags_t *c;
- if (!(c = __get_tcb_caps(tid, off)))
- return_args1(t, ERR_INVAL);
+ struct tcb *c = get_tcb(tid);
+ if (!c)
+ return_args1(t, ERR_NF);
- set_caps(*c, off, caps);
+ set_caps(c->caps, caps);
return_args1(t, OK);
}
@@ -60,13 +40,13 @@ SYSCALL_DEFINE3(set_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off,
* @param off Offset of capability, multiple of \c bits(cap).
* @return \ref OK, capabilities.
*/
-SYSCALL_DEFINE2(get_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off)
+SYSCALL_DEFINE1(get_cap)(struct tcb *t, sys_arg_t tid)
{
- capflags_t *c;
- if (!(c = __get_tcb_caps(tid, off)))
- return_args1(t, ERR_INVAL);
+ struct tcb *c = get_tcb(tid);
+ if (!c)
+ return_args1(t, ERR_NF);
- return_args2(t, OK, get_caps(*c, off));
+ return_args2(t, OK, c->caps);
}
/**
@@ -74,21 +54,19 @@ SYSCALL_DEFINE2(get_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off)
*
* @param t Current tcb.
* @param tid Thread ID whose capabilities to clear.
- * @param off Offset of capability, multiple of \c bits(cap).
* @param caps Mask of capabilities to clear.
* @return ERR_LERM if invalid permissions, ERR_INVAL if \p tid doesn't exist,
* otherwise OK.
*/
-SYSCALL_DEFINE3(clear_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t off,
- sys_arg_t caps)
+SYSCALL_DEFINE2(clear_cap)(struct tcb *t, sys_arg_t tid, sys_arg_t caps)
{
if (!is_set(t->caps, CAP_CAPS))
return_args1(t, ERR_PERM);
- capflags_t *c;
- if (!(c = __get_tcb_caps(tid, off)))
- return_args1(t, ERR_INVAL);
+ struct tcb *c = get_tcb(tid);
+ if (!c)
+ return_args1(t, ERR_NF);
- clear_caps(*c, off, caps);
+ clear_caps(c->caps, caps);
return_args1(t, OK);
}
diff --git a/src/uapi/conf.c b/src/uapi/conf.c
index c8ec964..6a801f3 100644
--- a/src/uapi/conf.c
+++ b/src/uapi/conf.c
@@ -45,7 +45,7 @@ size_t rpc_stack_size()
* @param d0 Optional data argument for parameter.
* @return \ref OK and parameter value.
*/
-SYSCALL_DEFINE2(conf_get)(struct tcb *t, sys_arg_t param, sys_arg_t d0)
+SYSCALL_DEFINE2(get_conf)(struct tcb *t, sys_arg_t param, sys_arg_t d0)
{
/* anyone can read any current parameter, I don't think they should be
* hidden. */
@@ -93,7 +93,7 @@ SYSCALL_DEFINE2(conf_get)(struct tcb *t, sys_arg_t param, sys_arg_t d0)
* @param val Value to set \c param to.
* @return \ref OK and \c 0.
*/
-SYSCALL_DEFINE2(conf_set)(struct tcb *t, sys_arg_t param, sys_arg_t val)
+SYSCALL_DEFINE2(set_conf)(struct tcb *t, sys_arg_t param, sys_arg_t val)
{
if (!has_cap(t->caps, CAP_CONF))
return_args1(t, ERR_PERM);
@@ -133,9 +133,9 @@ SYSCALL_DEFINE1(poweroff)(struct tcb *t, sys_arg_t type)
return_args1(t, ERR_PERM);
switch (type) {
- case SHUTDOWN:
- case COLD_REBOOT:
- case WARM_REBOOT:
+ case SYS_SHUTDOWN:
+ case SYS_COLD_REBOOT:
+ case SYS_WARM_REBOOT:
return_args1(t, poweroff(type));
};
diff --git a/src/uapi/dispatch.c b/src/uapi/dispatch.c
index 4135171..bbebd9c 100644
--- a/src/uapi/dispatch.c
+++ b/src/uapi/dispatch.c
@@ -57,15 +57,15 @@ void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b,
case SYS_FREE_MEM: sys_free_mem(t, a, b, c, d, e); break;
case SYS_TIMEBASE: sys_timebase(t, a, b, c, d, e); break;
case SYS_TICKS: sys_ticks(t, a, b, c, d, e); break;
- case SYS_REQ_NOTIFICATION: sys_req_notification(t, a, b, c, d, e);
- break;
case SYS_REQ_REL_TIMER: sys_req_rel_timer(t, a, b, c, d, e); break;
case SYS_REQ_ABS_TIMER: sys_req_abs_timer(t, a, b, c, d, e); break;
+ case SYS_FREE_TIMER: sys_free_timer(t, a, b, c, d, e); break;
case SYS_IPC_REQ: sys_ipc_req(t, a, b, c, d, e); break;
case SYS_IPC_FWD: sys_ipc_fwd(t, a, b, c, d, e); break;
case SYS_IPC_KICK: sys_ipc_kick(t, a, b, c, d, e); break;
case SYS_IPC_RESP: sys_ipc_resp(t, a, b, c, d, e); break;
case SYS_IPC_GHOST: sys_ipc_ghost(t, a, b, c, d, e); break;
+ case SYS_SET_HANDLER: sys_set_handler(t, a, b, c, d, e); break;
case SYS_NOTIFY: sys_notify(t, a, b, c, d, e); break;
case SYS_CREATE: sys_create(t, a, b, c, d, e); break;
case SYS_FORK: sys_fork(t, a, b, c, d, e); break;
@@ -73,14 +73,15 @@ void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b,
case SYS_SPAWN: sys_spawn(t, a, b, c, d, e); break;
case SYS_KILL: sys_kill(t, a, b, c, d, e); break;
case SYS_SWAP: sys_swap(t, a, b, c, d, e); break;
- case SYS_CONF_SET: sys_conf_set(t, a, b, c, d, e); break;
- case SYS_CONF_GET: sys_conf_get(t, a, b, c, d, e); break;
+ case SYS_SET_CONF: sys_set_conf(t, a, b, c, d, e); break;
+ case SYS_GET_CONF: sys_get_conf(t, a, b, c, d, e); break;
case SYS_SET_CAP: sys_set_cap(t, a, b, c, d, e); break;
case SYS_GET_CAP: sys_get_cap(t, a, b, c, d, e); break;
case SYS_CLEAR_CAP: sys_clear_cap(t, a, b, c, d, e); break;
case SYS_POWEROFF: sys_poweroff(t, a, b, c, d, e); break;
case SYS_SLEEP: sys_sleep(t, a, b, c, d, e); break;
case SYS_IRQ_REQ: sys_irq_req(t, a, b, c, d, e); break;
+ case SYS_FREE_IRQ: sys_free_irq(t, a, b, c, d, e); break;
case SYS_DETACH: sys_detach(t, a, b, c, d, e); break;
case SYS_EXIT: sys_exit(t, a, b, c, d, e); break;
default:
diff --git a/src/uapi/irq.c b/src/uapi/irq.c
index 9f3931c..69602af 100644
--- a/src/uapi/irq.c
+++ b/src/uapi/irq.c
@@ -28,16 +28,25 @@ SYSCALL_DEFINE1(irq_req)(struct tcb *t, sys_arg_t id)
return_args1(t, register_irq(t, id));
}
+SYSCALL_DEFINE1(free_irq)(struct tcb *t, sys_arg_t id)
+{
+ if (!has_cap(t->caps, CAP_IRQ))
+ return_args1(t, ERR_PERM);
+
+ return_args1(t, unregister_irq(t, id));
+}
+
/**
* Actual notification handler setter.
*
* @param t Current tcb.
- * @param tid Thread whose handler to set.
+ * @param tid Thread whose handler to set. Still not sure if this should just be
+ * the current thread, but I guess this might be a bit easier?
* @param pid Process that is willing to handle notifications for the thread.
*
* @return OK on success, non-zero otherwise.
*/
-SYSCALL_DEFINE2(req_notification)(struct tcb *t, sys_arg_t tid, sys_arg_t pid)
+SYSCALL_DEFINE2(set_handler)(struct tcb *t, sys_arg_t tid, sys_arg_t pid)
{
if (!has_cap(t->caps, CAP_SIGNAL))
return_args1(t, ERR_PERM);
diff --git a/src/uapi/mem.c b/src/uapi/mem.c
index b4b2df8..aeff7c5 100644
--- a/src/uapi/mem.c
+++ b/src/uapi/mem.c
@@ -140,7 +140,7 @@ SYSCALL_DEFINE2(req_page)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
* @param t Current tcb.
* @param size Minimum size of allocation.
* @param flags Flags of allocation.
- * @return \ref OK, start and size
+ * @return \ref OK, start
* in that order, \ref ERR_OOMEM otherwise.
*/
SYSCALL_DEFINE2(req_sharedmem)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
@@ -154,7 +154,7 @@ SYSCALL_DEFINE2(req_sharedmem)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
if (ERR_CODE(start))
return_args1(t, start);
- return_args3(t, OK, start, size);
+ return_args2(t, OK, start);
}
/**
@@ -185,5 +185,5 @@ SYSCALL_DEFINE3(ref_sharedmem)(struct tcb *t, sys_arg_t tid, sys_arg_t addr,
if (ERR_CODE(start))
return_args1(t, start);
- return_args3(t, OK, start, size);
+ return_args2(t, OK, start);
}
diff --git a/src/uapi/proc.c b/src/uapi/proc.c
index eddbab8..74f211c 100644
--- a/src/uapi/proc.c
+++ b/src/uapi/proc.c
@@ -47,7 +47,7 @@ SYSCALL_DEFINE5(create)(struct tcb *t, sys_arg_t func,
set_return(c, func);
c->notify_id = t->notify_id;
- return_args2(t, OK, c->tid);
+ return_args1(t, c->tid);
}
/**
@@ -78,10 +78,10 @@ SYSCALL_DEFINE0(fork)(struct tcb *t)
/* prepare args for when we eventually swap to the new proc, giving
* parent ID as third return value */
- set_args3(n, OK, 0, get_eproc(t)->pid);
+ set_args2(n, 0, get_eproc(t)->pid);
n->notify_id = c->notify_id;
- return_args2(t, OK, n->pid);
+ return_args1(t, n->pid);
}
/**
@@ -161,8 +161,17 @@ SYSCALL_DEFINE2(spawn)(struct tcb *t, sys_arg_t bin, sys_arg_t interp)
if (!n)
return_args1(t, ERR_OOMEM);
+ /** @todo copy over bin and interp into new process, this is not enough */
n->notify_id = c->notify_id;
- return_args2(t, prepare_proc(n, bin, interp), n->pid);
+ stat_t ret = OK;
+ if ((ret = prepare_proc(n, bin, interp))) {
+ /* this kills the thread */
+ orphanize(t);
+ unorphanize(t);
+ return_args1(t, ret);
+ }
+
+ return_args1(t, n->pid);
}
/**
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..2012107
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,34 @@
+TESTS :=
+
+ARCH ?= riscv64
+CROSS_COMPILE ?= $(ARCH)-unknown-elf-
+
+LLVM ?= 0
+COMPILER != [ "$(LLVM)" != "0" ] \
+ && echo clang --target="$(CROSS_COMPILE)" \
+ || echo $(CROSS_COMPILE)gcc
+
+OBFLAGS := -ffreestanding -nostdlib -std=c17 -g
+INCLUDEFLAGS := -I ../include -I.
+WARNFLAGS := -Wall -Wextra
+COMPILE := $(COMPILER) $(WARNFLAGS) $(INCLUDEFLAGS) $(OBFLAGS)
+
+GEN_INITRD := cpio -H newc -o >
+QEMU := qemu-system-$(ARCH) -machine virt -kernel ../kmi.bin \
+ -serial stdio \
+ -monitor none \
+ -nographic \
+ -no-reboot \
+ -initrd
+
+COMMON := build/printf.o ../kmi.bin ../include/kmi/syscalls.h
+
+include noop/source.mk
+include hello-world/source.mk
+
+.PHONY: check
+check: $(TESTS)
+
+build/printf.o: common/printf.c
+ mkdir -p build
+ $(COMPILE) -c common/printf.c -o build/printf.o
diff --git a/tests/common/arch/riscv64/syscall.h b/tests/common/arch/riscv64/syscall.h
new file mode 100644
index 0000000..aafda82
--- /dev/null
+++ b/tests/common/arch/riscv64/syscall.h
@@ -0,0 +1,50 @@
+#ifndef KMI_TESTS_ARCH_RISCV64_SYSCALL_H
+#define KMI_TESTS_ARCH_RISCV64_SYSCALL_H
+
+#include <kmi/syscalls.h>
+#include <kmi/attrs.h>
+
+static inline struct sys_ret syscall(size_t n,
+ long arg0, long arg1, long arg2, long arg3,
+ long arg4,
+ long arg5)
+{
+ register long a0 __asm__ ("a0") = arg0;
+ register long a1 __asm__ ("a1") = arg1;
+ register long a2 __asm__ ("a2") = arg2;
+ register long a3 __asm__ ("a3") = arg3;
+ register long a4 __asm__ ("a4") = arg4;
+ register long a5 __asm__ ("a5") = arg5;
+
+#define OUTPUTS "+r" (a0), "=r" (a1), "=r" (a2), "=r" (a3), "=r" (a4), "=r" (a5)
+
+ if (n == 1)
+ __asm__ volatile ("ecall" : OUTPUTS : "r" (a0));
+
+ else if (n == 2)
+ __asm__ volatile ("ecall" : OUTPUTS : "r" (a0), "r" (a1));
+
+ else if (n == 3)
+ __asm__ volatile ("ecall" : OUTPUTS : "r" (a0), "r" (a1),
+ "r" (a2));
+
+ else if (n == 4)
+ __asm__ volatile ("ecall" : OUTPUTS
+ : "r" (a0), "r" (a1), "r" (a2), "r" (a3));
+
+ else if (n == 5)
+ __asm__ volatile ("ecall" : OUTPUTS
+ : "r" (a0), "r" (a1), "r" (a2), "r" (a3),
+ "r" (a4));
+
+ else if (n == 6)
+ __asm__ volatile ("ecall" : OUTPUTS
+ : "r" (a0), "r" (a1), "r" (a2), "r" (a3),
+ "r" (a4), "r" (a5));
+
+#undef OUTPUTS
+
+ return (struct sys_ret){a0, a1, a2, a3, a4, a5};
+}
+
+#endif /* KMI_TESTS_ARCH_RISCV64_SYSCALL_H */
diff --git a/tests/common/printf.c b/tests/common/printf.c
new file mode 100644
index 0000000..0ad084b
--- /dev/null
+++ b/tests/common/printf.c
@@ -0,0 +1,583 @@
+#include <stdarg.h>
+#include "test.h"
+
+/* Directly copied from src/debug.c, which was copied from
+ * https://github.com/mpaland/printf/blob/master/printf.c
+ */
+
+/** Ask kernel to print out one character for us. Only works if kernel was
+ * compiled with DEBUG=1. */
+static void __putchar(char c)
+{
+ sys_putch(c);
+}
+
+/** Printf formatting left align flag. */
+#define LEFT_FLAG (1 << 0)
+
+/** Printf formatting explicit sign flag. */
+#define SIGN_FLAG (1 << 1)
+
+/** Printf formatting hash sign flag. */
+#define HASH_FLAG (1 << 2)
+
+/** Printf formatting zero padding flag. */
+#define ZERO_FLAG (1 << 3)
+
+/** Printf formatting ' flag. */
+#define FMT_FLAG (1 << 4)
+
+/** Printf formatting space flag. */
+#define SPACE_FLAG (1 << 5)
+
+/** Printf formatting long specifier flag. */
+#define LONG_FLAG (1 << 6)
+
+/** Printf formatting long long specifier flag. */
+#define LLONG_FLAG (1 << 7)
+
+/** Printf formatting short flag. */
+#define SHORT_FLAG (1 << 8)
+
+/** Printf formatting char flag. */
+#define CHAR_FLAG (1 << 9)
+
+/** Printf precision flag. */
+#define PRECS_FLAG (1 << 11)
+
+/** Printf unsigned flag. */
+#define UNSIGN_FLAG (1 << 12)
+
+/** Printf width flag. */
+#define WIDTH_FLAG (1 << 13)
+
+/** Printf padding flag. */
+#define PAD_FLAG (1 << 14)
+
+/** Printf continue flag. */
+#define CONT 1
+
+/** Printf stop flag. */
+#define STOP 0
+
+/**
+ * Check if character is ASCII decimal digit.
+ *
+ * @param c Character to check.
+ * @return \c true if character is ASCII decimal digit, \c false otherwise.
+ */
+static bool __is_digit(char c)
+{
+ return (c >= '0') && (c <= '9');
+}
+
+/**
+ * Convert string to corresponding number (assuming int).
+ *
+ * @param s Number string.
+ * @return Corresponding number.
+ */
+static int __atoi(const char *s)
+{
+ unsigned int i = 0;
+ while (__is_digit(*s)) {
+ i = i * 10 + (unsigned int)(*(s++) - '0');
+ }
+
+ return i;
+}
+
+/**
+ * Calculate signed char from value using type interpretation.
+ *
+ * @param x Type to interpret value as.
+ * @param value Value to interpret.
+ * @param base Base to interpret value in.
+ */
+#define handle_type(x, value, base) \
+ c = (x)value % (x)base; \
+ value = (x)value / (x)base;
+/**
+ * Convert number to string length.
+ *
+ * @param value Number to print.
+ * @param base Base to print in.
+ * @param flags Flags to output.
+ * @param print Print number as well.
+ * @return Length of corresponding string.
+ */
+static size_t __integral_val(ssize_t value, size_t base, size_t flags,
+ bool print)
+{
+ /* assume ascii numbers, which is why 'signed char' is probably fine */
+ size_t ret = 0;
+ signed char c = 0;
+
+
+ if (flags & UNSIGN_FLAG) {
+ /* signed values, only with i format */
+ if (flags & LLONG_FLAG) {
+ handle_type(signed long long, value, base);
+ } else if (flags & LONG_FLAG) {
+ handle_type(signed long, value, base);
+ } else if (flags & SHORT_FLAG) {
+ handle_type(signed short, value, base);
+ } else if (flags & CHAR_FLAG) {
+ handle_type(signed char, value, base);
+ } else {
+ handle_type(signed int, value, base);
+ }
+
+ /* convert negative results into actual characters */
+ c = c < 0 ? -c : c;
+
+ } else {
+ /* unsigned values, everything else */
+ if (flags & LLONG_FLAG) {
+ handle_type(unsigned long long, value, base);
+ } else if (flags & LONG_FLAG) {
+ handle_type(unsigned long, value, base);
+ } else if (flags & SHORT_FLAG) {
+ handle_type(unsigned short, value, base);
+ } else if (flags & CHAR_FLAG) {
+ handle_type(unsigned char, value, base);
+ } else {
+ handle_type(unsigned int, value, base);
+ }
+ }
+
+ if (base == 16)
+ c += c > 9 ? 'a' - 10 : '0';
+ else
+ c += '0';
+
+ if (value != 0)
+ ret = __integral_val(value, base, flags, print);
+
+ if (print)
+ __putchar(c);
+
+ return ret + 1;
+}
+
+/**
+ * Print out a string.
+ *
+ * @param s String to print out.
+ * @return Bytes printed.
+ */
+static size_t __puts(const char *s)
+{
+ size_t i = 0;
+ while (s[i]) {
+ __putchar(s[i++]);
+ }
+
+ return i;
+}
+
+/**
+ * Print prefix corresponding to \c base.
+ *
+ * @param base Base to integer.
+ * @return Length of prefix as string.
+ */
+static size_t __print_prefix(size_t base)
+{
+ switch (base) {
+ case 16: return __puts("0x");
+ case 8: return __puts("0");
+ case 2: return __puts("0b");
+ default: break;
+ }
+
+ return 0;
+}
+
+/**
+ * Print padding.
+ *
+ * @param pad Number of characters to print.
+ * @param pad_char Character to use as padding.
+ * @return Number of characters printed.
+ */
+static size_t __print_padding(size_t pad, char pad_char)
+{
+ size_t i = 0;
+ for (; i < pad; ++i) {
+ __putchar(pad_char);
+ }
+
+ return i;
+}
+
+/**
+ * Print signed value.
+ *
+ * @param value Value to print.
+ * @param flags Flags to printing.
+ * @return Number of characters written.
+ */
+static size_t __print_sign(ssize_t value, size_t flags)
+{
+ if (flags & LLONG_FLAG)
+ value = (signed long long)value;
+ else if (flags & LONG_FLAG)
+ value = (signed long)value;
+ else if (flags & SHORT_FLAG)
+ value = (signed short)value;
+ else if (flags & CHAR_FLAG)
+ value = (signed char)value;
+ else
+ value = (signed int)value;
+
+ if (value < 0) {
+ __putchar('-');
+ return 1;
+ } else if (flags & SIGN_FLAG) {
+ __putchar('+');
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Length of integral value as string.
+ *
+ * @param value Value to convert to string.
+ * @param base Base to interpret value as.
+ * @param flags Formatting flags.
+ * @return \see __integral_val().
+ */
+#define __integral_len(value, base, flags) __integral_val((value), (base), \
+ (flags), false)
+
+/**
+ * Print integral value as string.
+ *
+ * @param value Value to convert to string.
+ * @param base Base to interpret value as.
+ * @param flags Formatting flags.
+ * @return \see __integral_val().
+ */
+#define __integral_print(value, base, flags) __integral_val((value), (base), \
+ (flags), true)
+
+/**
+ * Print integral value.
+ *
+ * @param value Value to print.
+ * @param base Base to print value in.
+ * @param flags Flags to printing.
+ * @param width Minimum width of printing.
+ * @return Number of characters written.
+ */
+static size_t __print_integral(ssize_t value, size_t base, size_t flags,
+ size_t width)
+{
+ size_t ret = 0;
+ size_t raw_len = __integral_len(value, base, flags);
+ ssize_t pad = (flags & PAD_FLAG) ? width - raw_len : 0;
+
+ /* depending on which flags are set, the prefix, sign and right justify has to
+ * be ordereder differently. */
+ if (flags & ZERO_FLAG) {
+ if (!(flags & UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (flags & HASH_FLAG)
+ ret += __print_prefix(base);
+
+ if (pad > 0 && !(flags & LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+
+ } else if (flags & SPACE_FLAG) {
+ if (pad > 0 && !(flags & LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+
+ if (flags & UNSIGN_FLAG)
+ ret += __print_sign(value, flags);
+
+ if (flags & HASH_FLAG)
+ ret += __print_prefix(base);
+ } else {
+ if (!(flags & UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (flags & HASH_FLAG)
+ ret += __print_prefix(base);
+ }
+
+ /* print value itself */
+ ret += __integral_print(value, base, flags);
+
+ /* left-justify */
+ if (flags & ZERO_FLAG) {
+ if (pad > 0 && (flags & LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+ } else if (flags & SPACE_FLAG) {
+ if (pad > 0 && (flags & LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+ }
+
+ return ret;
+}
+
+int printf(const char *fmt, ...)
+{
+ /* largely inspired by
+ * https://github.com/mpaland/printf/blob/master/printf.c
+ */
+
+ /* Note that X is binary formatting, because who uses uppercase hex? */
+
+ va_list vl;
+ va_start(vl, fmt);
+
+ size_t chars_written = 0;
+
+ while (*fmt) {
+ if (*fmt != '%') {
+ __putchar(*fmt++);
+ chars_written++;
+ continue;
+ }
+
+ fmt++;
+ if (*fmt == '%') {
+ /* literal percent sign */
+ __putchar('%');
+ chars_written++;
+ fmt++;
+ continue;
+ }
+
+ /* check flags */
+ size_t flags = 0;
+ int a = STOP;
+ do {
+ switch (*fmt) {
+ case ' ':
+ flags |= SPACE_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ case '-':
+ flags |= LEFT_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ case '+':
+ flags |= SIGN_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ case '#':
+ flags |= HASH_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ case '0':
+ flags |= ZERO_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ case '\'':
+ flags |= FMT_FLAG;
+ fmt++;
+ a = CONT;
+ break;
+
+ default:
+ a = STOP;
+ break;
+ }
+ } while (a != STOP);
+
+ /* check width */
+ size_t width = 0;
+ if (__is_digit(*fmt)) {
+ width = __atoi(fmt++);
+ flags |= WIDTH_FLAG | PAD_FLAG | SPACE_FLAG;
+ } else if (*fmt == '*') {
+ int w = va_arg(vl, int);
+ if (w < 0) {
+ width = -w;
+ flags |= LEFT_FLAG;
+ } else {
+ width = w;
+ }
+ flags |= WIDTH_FLAG | PAD_FLAG | SPACE_FLAG;
+ fmt++;
+ }
+
+ /* check precision */
+ size_t precision = 0;
+ if (*fmt == '.') {
+ fmt++;
+ flags |= PRECS_FLAG | PAD_FLAG | ZERO_FLAG;
+ if (__is_digit(*fmt)) {
+ precision = __atoi(fmt++);
+ } else if (*fmt == '*') {
+ precision = va_arg(vl, int);
+ fmt++;
+ }
+ }
+
+ /* check length */
+ switch (*fmt) {
+ case 'l':
+ fmt++;
+ if (*fmt == 'l') {
+ flags |= LLONG_FLAG;
+ fmt++;
+ } else {
+ flags |= LONG_FLAG;
+ }
+ break;
+
+ case 'h':
+ fmt++;
+ if (*fmt == 'h') {
+ flags |= CHAR_FLAG;
+ fmt++;
+ } else {
+ flags |= SHORT_FLAG;
+ }
+ break;
+
+ case 'j':
+ fmt++;
+ if (sizeof(intmax_t) == sizeof(long))
+ flags |= LONG_FLAG;
+ else
+ flags |= LLONG_FLAG;
+ break;
+
+ case 'z':
+ fmt++;
+ if (sizeof(size_t) == sizeof(long))
+ flags |= LONG_FLAG;
+ else
+ flags |= LLONG_FLAG;
+ break;
+
+ case 't':
+ fmt++;
+ if (sizeof(ptrdiff_t) == sizeof(long))
+ flags |= LONG_FLAG;
+ else
+ flags |= LLONG_FLAG;
+ break;
+ }
+
+ /* read actual specifier */
+ size_t base = 10;
+ size_t value = 0;
+ int i = -1;
+ const char *s = 0;
+ void *p = 0;
+ int *n = 0;
+ char c = 0;
+
+ switch (*fmt) {
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'x':
+ case 'X':
+ case 'o':
+ case 'b':
+ /* integer handling */
+ switch (*fmt) {
+ case 'x':
+ base = 16;
+ break;
+ case 'X':
+ base = 2;
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ base = 10;
+ break;
+ }
+
+ if (base == 10)
+ flags &= ~HASH_FLAG;
+
+ /* precision takes precedence */
+ if (flags & PRECS_FLAG)
+ width = precision;
+
+ /* formatting doesn't apply to decimal integers
+ * */
+ if (*fmt != 'i' && *fmt != 'd') {
+ flags &= ~SIGN_FLAG;
+ flags |= UNSIGN_FLAG;
+ }
+
+ if (flags & LLONG_FLAG)
+ value = va_arg(vl, long long);
+ else if (flags & LONG_FLAG)
+ value = va_arg(vl, long);
+ else
+ value = va_arg(vl, int);
+
+ chars_written +=
+ __print_integral(value, base, flags, width);
+ fmt++;
+ break;
+
+ case 'c':
+ c = va_arg(vl, int);
+ __putchar(c);
+ chars_written++;
+ fmt++;
+ break;
+
+ case 's':
+ s = va_arg(vl, const char *);
+
+ if (flags & PRECS_FLAG)
+ i = precision;
+
+ for (; *s && i--;) {
+ __putchar(*s++);
+ chars_written++;
+ }
+ fmt++;
+ break;
+
+ case 'p':
+ p = va_arg(vl, void *);
+ flags |= UNSIGN_FLAG | HASH_FLAG;
+
+ if (sizeof(void *) == sizeof(long))
+ flags |= LONG_FLAG;
+ else
+ flags |= LLONG_FLAG;
+
+ chars_written +=
+ __print_integral((ssize_t)p, 16, flags, width);
+ fmt++;
+ break;
+
+ case 'n':
+ n = va_arg(vl, int *);
+ *n = chars_written;
+ fmt++;
+ break;
+ }
+ }
+
+ va_end(vl);
+ return chars_written;
+}
diff --git a/tests/common/test.h b/tests/common/test.h
new file mode 100644
index 0000000..3b591e0
--- /dev/null
+++ b/tests/common/test.h
@@ -0,0 +1,324 @@
+#ifndef KMI_TESTS_H
+#define KMI_TESTS_H
+
+#include <kmi/attrs.h>
+#include <kmi/types.h>
+
+#if defined(__riscv)
+# if __riscv_xlen == 64
+#include "arch/riscv64/syscall.h"
+# endif
+#endif
+
+#define syscall0(op) syscall(1, op, 0, 0, 0, 0, 0)
+#define syscall1(op, a0) syscall(2, op, a0, 0, 0, 0, 0)
+#define syscall2(op, a0, a1) syscall(3, op, a0, a1, 0, 0, 0)
+#define syscall3(op, a0, a1, a2) syscall(4, op, a0, a1, a2, 0, 0)
+#define syscall4(op, a0, a1, a2, a3) syscall(5, op, a0, a1, a2, a3, 0)
+#define syscall5(op, a0, a1, a2, a3, a4) syscall(6, op, a0, a1, a2, a3, a4)
+
+#define UNUSED(x) (void)x
+#define START(pid, tid, d0, d1, d2, d3)\
+void _start(sys_arg_t pid, sys_arg_t tid,\
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+
+static inline void sys_noop()
+{
+ syscall0(SYS_NOOP);
+}
+
+static inline void sys_putch(char c)
+{
+ syscall1(SYS_PUTCH, c);
+}
+
+static inline void *sys_req_mem(size_t size, vmflags_t flags)
+{
+ struct sys_ret r = syscall2(SYS_REQ_MEM, size, flags);
+ if (r.s)
+ return NULL;
+
+ return (void *)r.a0;
+}
+
+static inline void *sys_req_fixmem(uintptr_t fixed, size_t size, vmflags_t flags)
+{
+ struct sys_ret r = syscall3(SYS_REQ_FIXMEM, fixed, size, flags);
+ if (r.s)
+ return NULL;
+
+ return (void *)r.a0;
+}
+
+static inline void *sys_req_pmem(uintptr_t addr, size_t size, vmflags_t flags)
+{
+ struct sys_ret r = syscall3(SYS_REQ_PMEM, addr, size, flags);
+ if (r.s)
+ return NULL;
+
+ return (void *)r.a0;
+}
+
+static inline void *sys_req_page(size_t size, vmflags_t flags, uintptr_t *addr, size_t *asize)
+{
+ struct sys_ret r = syscall2(SYS_REQ_PAGE, size, flags);
+ if (r.s)
+ return NULL;
+
+ if (addr)
+ *addr = r.a1;
+
+ if (asize)
+ *asize = r.a2;
+
+ return (void *)r.a0;
+}
+
+static inline void *sys_req_sharedmem(size_t size, vmflags_t flags)
+{
+ struct sys_ret r = syscall2(SYS_REQ_SHAREDMEM, size, flags);
+ if (r.s)
+ return NULL;
+
+ return (void *)r.a0;
+}
+
+static inline void *sys_ref_sharedmem(id_t tid, uintptr_t addr, vmflags_t flags)
+{
+ struct sys_ret r = syscall3(SYS_REF_SHAREDMEM, tid, addr, flags);
+ if (r.s)
+ return NULL;
+
+ return (void *)r.a0;
+}
+
+static inline void sys_free_mem(uintptr_t start)
+{
+ syscall1(SYS_FREE_MEM, start);
+}
+
+static inline uint64_t sys_timebase()
+{
+ struct sys_ret r = syscall0(SYS_TIMEBASE);
+ if (r.s)
+ return 0;
+
+#if defined(_LP64)
+ return r.a0;
+#else
+ return ((uint64_t)r.a1 << 32) | r.a0
+#endif
+}
+
+static inline uint64_t sys_ticks()
+{
+ struct sys_ret r = syscall0(SYS_TICKS);
+ if (r.s)
+ return 0;
+
+#if defined(_LP64)
+ return r.a0;
+#else
+ return ((uint64_t)r.a1 << 32) | r.a0;
+#endif
+}
+
+static inline id_t sys_req_rel_timer(uint64_t ticks)
+{
+ struct sys_ret r;
+#if defined(_LP64)
+ r = syscall2(SYS_REQ_REL_TIMER, ticks >> 32, ticks);
+#else
+ r = syscall1(SYS_REQ_REL_TIMER, ticks);
+#endif
+
+ if (r.s)
+ return -1;
+
+ return r.a0;
+}
+
+static inline id_t sys_req_abs_timer(uint64_t ticks)
+{
+ struct sys_ret r;
+#if defined(_LP64)
+ r = syscall2(SYS_REQ_ABS_TIMER, ticks >> 32, ticks);
+#else
+ r = syscall1(SYS_REQ_ABS_TIMER, ticks);
+#endif
+
+ if (r.s)
+ return -1;
+
+ return r.a0;
+}
+
+#define sys_ipc_req0(pid) syscall1(SYS_IPC_REQ, pid)
+#define sys_ipc_req1(pid, d0) syscall2(SYS_IPC_REQ, pid, d0)
+#define sys_ipc_req2(pid, d0, d1) syscall3(SYS_IPC_REQ, pid, d0, d1)
+#define sys_ipc_req3(pid, d0, d1, d2) syscall4(SYS_IPC_REQ, pid, d0, d1, d2)
+#define sys_ipc_req4(pid, d0, d1, d2, d3) syscall5(SYS_IPC_REQ, pid, d0, d1, d2, d3)
+
+#define sys_ipc_fwd0(pid) syscall1(SYS_IPC_FWD, pid)
+#define sys_ipc_fwd1(pid, d0) syscall2(SYS_IPC_FWD, pid, d0)
+#define sys_ipc_fwd2(pid, d0, d1) syscall3(SYS_IPC_FWD, pid, d0, d1)
+#define sys_ipc_fwd3(pid, d0, d1, d2) syscall4(SYS_IPC_FWD, pid, d0, d1, d2)
+#define sys_ipc_fwd4(pid, d0, d1, d2, d3) syscall5(SYS_IPC_FWD, pid, d0, d1, d2, d3)
+
+#define sys_ipc_kick0(pid) syscall1(SYS_IPC_KICK, pid)
+#define sys_ipc_kick1(pid, d0) syscall2(SYS_IPC_KICK, pid, d0)
+#define sys_ipc_kick2(pid, d0, d1) syscall3(SYS_IPC_KICK, pid, d0, d1)
+#define sys_ipc_kick3(pid, d0, d1, d2) syscall4(SYS_IPC_KICK, pid, d0, d1, d2)
+#define sys_ipc_kick4(pid, d0, d1, d2, d3) syscall5(SYS_IPC_KICK, pid, d0, d1, d2, d3)
+
+#define sys_ipc_resp0(pid) syscall1(SYS_IPC_RESP, pid)
+#define sys_ipc_resp1(pid, d0) syscall2(SYS_IPC_RESP, pid, d0)
+#define sys_ipc_resp2(pid, d0, d1) syscall3(SYS_IPC_RESP, pid, d0, d1)
+#define sys_ipc_resp3(pid, d0, d1, d2) syscall4(SYS_IPC_RESP, pid, d0, d1, d2)
+#define sys_ipc_resp4(pid, d0, d1, d2, d3) syscall5(SYS_IPC_RESP, pid, d0, d1, d2, d3)
+
+#define sys_ipc_ghost0(pid) syscall1(SYS_IPC_GHOST, pid)
+#define sys_ipc_ghost1(pid, d0) syscall2(SYS_IPC_GHOST, pid, d0)
+#define sys_ipc_ghost2(pid, d0, d1) syscall3(SYS_IPC_GHOST, pid, d0, d1)
+#define sys_ipc_ghost3(pid, d0, d1, d2) syscall4(SYS_IPC_GHOST, pid, d0, d1, d2)
+#define sys_ipc_ghost4(pid, d0, d1, d2, d3) syscall5(SYS_IPC_GHOST, pid, d0, d1, d2, d3)
+
+static inline enum sys_status sys_set_handler(id_t tid, id_t pid)
+{
+ struct sys_ret r = syscall2(SYS_SET_HANDLER, tid, pid);
+ return r.s;
+}
+
+static inline enum sys_status sys_notify(id_t tid)
+{
+ struct sys_ret r = syscall1(SYS_NOTIFY, tid);
+ return r.s;
+}
+
+static inline id_t sys_create(uintptr_t func, long d0, long d1, long d2, long d3)
+{
+ struct sys_ret r = syscall5(SYS_CREATE, func, d0, d1, d2, d3);
+ return r.s;
+}
+
+/* note that negative IDs are error values */
+static inline id_t sys_fork(id_t *new_id)
+{
+ struct sys_ret r = syscall0(SYS_FORK);
+ if (new_id)
+ *new_id = r.a0;
+
+ return r.s;
+}
+
+static inline enum sys_status sys_exec(uintptr_t bin, uintptr_t interp)
+{
+ struct sys_ret r = syscall2(SYS_EXEC, bin, interp);
+ /* if we reach this point, something's gone wrong */
+ return r.s;
+}
+
+/* negative IDs mean errors */
+static inline id_t sys_spawn(uintptr_t bin, uintptr_t interp)
+{
+ struct sys_ret r = syscall2(SYS_EXEC, bin, interp);
+ return r.s;
+}
+
+static inline enum sys_status sys_kill(id_t pid)
+{
+ struct sys_ret r = syscall1(SYS_KILL, pid);
+ return r.s;
+}
+
+static inline enum sys_status sys_swap(id_t tid)
+{
+ struct sys_ret r = syscall1(SYS_SWAP, tid);
+ return r.s;
+}
+
+static inline enum sys_status sys_conf_set(enum conf_param param, long arg)
+{
+ struct sys_ret r = syscall2(SYS_SET_CONF, param, arg);
+ return r.s;
+}
+
+static inline long sys_conf_get(enum conf_param param, long arg)
+{
+ struct sys_ret r = syscall2(SYS_GET_CONF, param, arg);
+ return r.a0;
+}
+
+static inline enum sys_status sys_set_cap(id_t tid, enum sys_cap cap)
+{
+ struct sys_ret r = syscall2(SYS_SET_CAP, tid, cap);
+ return r.s;
+}
+
+static inline enum sys_status sys_get_cap(id_t tid, enum sys_cap *cap)
+{
+ struct sys_ret r = syscall1(SYS_GET_CAP, tid);
+ *cap = r.a0;
+ return r.s;
+}
+
+static inline enum sys_status sys_clear_cap(id_t tid, enum sys_cap cap)
+{
+ struct sys_ret r = syscall2(SYS_CLEAR_CAP, tid, cap);
+ return r.s;
+}
+
+static inline enum sys_status sys_poweroff(enum poweroff_type type)
+{
+ struct sys_ret r = syscall1(SYS_POWEROFF, type);
+ return r.s;
+}
+
+static inline enum sys_status sys_sleep()
+{
+ struct sys_ret r = syscall0(SYS_SLEEP);
+ return r.s;
+}
+
+/** note that negative IDs mean errors */
+static inline id_t sys_irq_req(long irq)
+{
+ struct sys_ret r = syscall1(SYS_IRQ_REQ, irq);
+ return r.s;
+}
+
+static inline enum sys_status sys_free_irq(long irq)
+{
+ struct sys_ret r = syscall1(SYS_FREE_IRQ, irq);
+ return r.s;
+}
+
+static inline enum sys_status sys_detach(id_t tid)
+{
+ struct sys_ret r = syscall1(SYS_DETACH, tid);
+ return r.s;
+}
+
+static inline enum sys_status sys_exit()
+{
+ struct sys_ret r = syscall0(SYS_EXIT);
+ return r.s;
+}
+
+int printf(const char *fmt, ...) __printf;
+
+#define error(x, ...)\
+ printf("ERROR: " x # __VA_ARGS__)
+
+#define check(x, y, ...)\
+ if (!(x)) {\
+ error(y #__VA_ARGS__);\
+ sys_poweroff(SYS_SHUTDOWN);\
+ }
+
+static inline void ok() {
+ printf("OK\n");
+ sys_poweroff(SYS_SHUTDOWN);
+}
+
+#endif /* KMI_TESTS_H */
diff --git a/tests/hello-world/init.c b/tests/hello-world/init.c
new file mode 100644
index 0000000..fc882ec
--- /dev/null
+++ b/tests/hello-world/init.c
@@ -0,0 +1,14 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ UNUSED(pid);
+ UNUSED(tid);
+ UNUSED(d0);
+ UNUSED(d1);
+ UNUSED(d2);
+ UNUSED(d3);
+
+ printf("Hello, world!\n");
+ ok();
+}
diff --git a/tests/hello-world/source.mk b/tests/hello-world/source.mk
new file mode 100644
index 0000000..203a42e
--- /dev/null
+++ b/tests/hello-world/source.mk
@@ -0,0 +1,8 @@
+TESTS := $(TESTS) hello-world
+
+.PHONY: hello-world
+hello-world: hello-world/init.c $(COMMON)
+ mkdir -p build/hello-world
+ $(COMPILE) hello-world/init.c build/printf.o -o build/hello-world/init
+ echo build/hello-world/init | $(GEN_INITRD) hello-world/initrd
+ $(QEMU) hello-world/initrd | grep 'Hello, world!'
diff --git a/tests/noop/init.c b/tests/noop/init.c
new file mode 100644
index 0000000..a7e66f8
--- /dev/null
+++ b/tests/noop/init.c
@@ -0,0 +1,13 @@
+#include <common/test.h>
+
+START(pid, tid, d0, d1, d2, d3)
+{
+ UNUSED(pid);
+ UNUSED(tid);
+ UNUSED(d0);
+ UNUSED(d1);
+ UNUSED(d2);
+ UNUSED(d3);
+ sys_noop();
+ ok();
+}
diff --git a/tests/noop/source.mk b/tests/noop/source.mk
new file mode 100644
index 0000000..087ce9b
--- /dev/null
+++ b/tests/noop/source.mk
@@ -0,0 +1,8 @@
+TESTS := $(TESTS) noop
+
+.PHONY: noop
+noop: noop/init.c $(COMMON)
+ mkdir -p build/noop
+ $(COMPILE) noop/init.c build/printf.o -o build/noop/init
+ echo build/noop/init | $(GEN_INITRD) noop/initrd
+ $(QEMU) noop/initrd | grep 'OK'