aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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'