aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 13:24:27 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 18:13:43 +0300
commitbc600ecc3bdf0f189861dfb840f70c2339a7a853 (patch)
treed9840b1dc1b865442a028c03ad7109ac67894f6e /src
parent6a7073e5f262db9a4578ff00b5b28e34335564ce (diff)
downloadkmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.tar.gz
kmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.zip
rename common to src
+ I keep starting to type src and wondering why autocomplete won't work, I guess src is just uncounciously a better name
Diffstat (limited to 'src')
-rw-r--r--src/bits.c62
-rw-r--r--src/canary.c42
-rw-r--r--src/debug.c786
-rw-r--r--src/dispatch.c35
-rw-r--r--src/dmem.c154
-rw-r--r--src/elf.c156
-rw-r--r--src/fdt.c15
-rw-r--r--src/initrd.c167
-rw-r--r--src/ipi.c35
-rw-r--r--src/irq.c67
-rw-r--r--src/main.c63
-rw-r--r--src/mem.c59
-rw-r--r--src/mem_nodes.c40
-rw-r--r--src/mem_regions.c587
-rw-r--r--src/nodes.c221
-rw-r--r--src/panic.c25
-rw-r--r--src/pmem.c587
-rw-r--r--src/proc.c56
-rw-r--r--src/sp_tree.c295
-rw-r--r--src/string.c463
-rw-r--r--src/tcb.c310
-rw-r--r--src/timer.c204
-rw-r--r--src/uapi/cap.c94
-rw-r--r--src/uapi/conf.c115
-rw-r--r--src/uapi/dispatch.c89
-rw-r--r--src/uapi/ipc.c348
-rw-r--r--src/uapi/irq.c29
-rw-r--r--src/uapi/mem.c158
-rw-r--r--src/uapi/proc.c188
-rw-r--r--src/uapi/timers.c120
-rw-r--r--src/vmem.c378
31 files changed, 5948 insertions, 0 deletions
diff --git a/src/bits.c b/src/bits.c
new file mode 100644
index 0000000..6462d21
--- /dev/null
+++ b/src/bits.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file bits.c
+ * Bit manipulation helper implementations, currently just byte swaps.
+ */
+
+#include <kmi/types.h>
+#include <kmi/attrs.h>
+#include <kmi/bits.h>
+#include <kmi/builtin.h>
+
+#undef __bswap16
+__weak uint16_t __bswap16(const uint16_t u)
+{
+ return (u & 0xff00) >> 8 | (u & 0x00ff) << 8;
+}
+
+#undef __bswap32
+__weak uint32_t __bswap32(const uint32_t u)
+{
+ return (u & 0xff000000) >> 24 | (u & 0x00ff0000) >> 8 |
+ (u & 0x0000ff00) << 8 | (u & 0x000000ff) << 24;
+}
+
+#undef __bswap64
+__weak uint64_t __bswap64(const uint64_t u)
+{
+ return (u & 0xff00000000000000ULL) >> 56 |
+ (u & 0x00ff000000000000ULL) >> 40 |
+ (u & 0x0000ff0000000000ULL) >> 24 |
+ (u & 0x000000ff00000000ULL) >> 8 |
+ (u & 0x00000000ff000000ULL) << 8 |
+ (u & 0x0000000000ff0000ULL) << 24 |
+ (u & 0x000000000000ff00ULL) << 40 |
+ (u & 0x00000000000000ffULL) << 56;
+}
+
+#undef ffs
+__weak int ffs(int v)
+{
+ /* http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightParallel */
+ if (v == 0)
+ return 0;
+
+ /* silence ubsan warning */
+ if (v == INT_MIN)
+ return 32;
+
+ int c = 32;
+ v &= -v;
+
+ if (v) c--;
+ if (v & 0x0000FFFF) c -= 16;
+ if (v & 0x00FF00FF) c -= 8;
+ if (v & 0x0F0F0F0F) c -= 4;
+ if (v & 0x33333333) c -= 2;
+ if (v & 0x55555555) c -= 1;
+
+ return c + 1;
+}
diff --git a/src/canary.c b/src/canary.c
new file mode 100644
index 0000000..4f1f8ba
--- /dev/null
+++ b/src/canary.c
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#include <kmi/canary.h>
+#include <kmi/mem.h>
+
+/**
+ * @file canary.c
+ * Kernel stack canary implementation.
+ */
+
+/** Typedef for canary value type. */
+typedef uint32_t canary_t;
+
+/** Canary magic value. */
+static const canary_t canary = 0xb00b1e5;
+
+/**
+ * Helper for calculating the canary location of \p t.
+ *
+ * @param t \ref tcb to calculate canary position of.
+ * @return Pointer to canary location, that is bottom of stack.
+ * \todo This assumes that all stacks grow downwards. Unlikely to ever be
+ * ported to a platform that doesn't abide by this, but keep it in mind anyway.
+ */
+static canary_t *get_canary(struct tcb *t)
+{
+ size_t s = order_size(KERNEL_STACK_PAGE_ORDER);
+ return (canary_t *)align_down((uintptr_t)t, s);
+}
+
+void set_canary(struct tcb *t)
+{
+ canary_t *c = get_canary(t);
+ *c = canary;
+}
+
+bool check_canary(struct tcb *t)
+{
+ canary_t *c = get_canary(t);
+ return *c != canary;
+}
diff --git a/src/debug.c b/src/debug.c
new file mode 100644
index 0000000..fcec537
--- /dev/null
+++ b/src/debug.c
@@ -0,0 +1,786 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file debug.c
+ * Handle printing to serial. Note that the serial drivers are only included
+ * when running a debug build to save space in release mode.
+ */
+
+#include <kmi/types.h>
+#include <kmi/debug.h>
+#include <kmi/bits.h>
+#include <kmi/vmem.h>
+#include <kmi/pmem.h>
+#include <arch/vmem.h>
+#include <libfdt.h>
+#include <stdarg.h>
+
+#if defined(DEBUG)
+
+/** Debug context structure. */
+struct dbg_info {
+ /** Address of serial device in virtual memory. Always access registers
+ * through this address rather than \p addr. */
+ pm_t base;
+ /** Shift count between registers of serial device. */
+ size_t shift;
+ /** Type of serial device. */
+ enum serial_dev dev;
+ /** Physical address of serial device. */
+ pm_t addr;
+};
+
+/** Static debugging information. */
+static struct dbg_info dbg_info = (struct dbg_info){ 0 };
+
+/* forward declarations. */
+static struct dbg_info __dbg_from_fdt(const void *fdt);
+
+void init_dbg(const void *fdt)
+{
+ dbg_info = __dbg_from_fdt(fdt);
+}
+
+void setup_dmap_dbg()
+{
+ /* noop with the new 8250 driver, though might still be useful in the
+ * future if I add in some other kind of simple uart */
+}
+
+void setup_io_dbg(struct vmem *b)
+{
+ dbg_info.base = map_io_dbg(b);
+}
+
+vm_t map_io_dbg(struct vmem *b)
+{
+ return setup_kernel_io(b, dbg_info.addr);
+}
+
+/** 8250 data register index. */
+#define UART_8250_DATA 0
+/** 8250 irq register idex. */
+#define UART_8250_IRQ 1
+/** 8250 ird_id register index. */
+#define UART_8250_IRQ_ID 2
+/** 8250 lcr register index. */
+#define UART_8250_LCR 3
+/** 8250 mcr register index. */
+#define UART_8250_MCR 4
+/** 8250 lsr register index. */
+#define UART_8250_LSR 5
+/** 8250 msr register index. */
+#define UART_8250_MSR 6
+/** 8250 scr register index. */
+#define UART_8250_SCR 7
+
+/* if there arises a need for more supported serial drivers, I should probably
+ * try to implement some kind of basic driver subsystem, but this is good enough
+ * for now. */
+
+/** Line status data ready. */
+#define LSR_DR (1 << 0)
+
+/** Line status overrun error. */
+#define LSR_OE (1 << 1)
+
+/** Line status parity error. */
+#define LSR_PE (1 << 2)
+
+/** Line status framing error. */
+#define LSR_FE (1 << 3)
+
+/** Line status break interrupt. */
+#define LSR_BI (1 << 4)
+
+/** Line status transmitter holding register. */
+#define LSR_THRE (1 << 5)
+
+/** Line status transmitter empty. */
+#define LSR_TEMT (1 << 6)
+
+/** Line status error in RCVR FIFO. */
+#define LSR_ERR (1 << 7)
+
+/**
+ * 8250 transmitter empty.
+ *
+ * @return \c 0 if not empty, non-zero otherwise.
+ */
+static int __8250_tx_empty()
+{
+ volatile uint8_t *lsr = (uint8_t *)dbg_info.base +
+ (UART_8250_LSR << dbg_info.shift);
+ return (*lsr) & LSR_THRE;
+}
+
+/**
+ * Put character out onto 8250 serial lines.
+ *
+ * @param c Character to output.
+ */
+static void __8250_putchar(char c)
+{
+ if (!dbg_info.base)
+ return;
+
+ while (__8250_tx_empty() == 0)
+ ;
+
+ volatile uint8_t *data = (uint8_t *)dbg_info.base +
+ (UART_8250_DATA << dbg_info.shift);
+ *data = c;
+}
+
+/**
+ * Put one character out on the serial lines.
+ * Automatically handles newlines.
+ *
+ * @param c Character to put.
+ */
+static void __putchar(char c)
+{
+ if (c == '\n')
+ __putchar('\r');
+
+ switch (dbg_info.dev) {
+ case UART_8250: __8250_putchar(c); return;
+ }
+}
+
+/**
+ * Convert serial device name (from FDT) to serial device enumerator.
+ *
+ * @param dev_name Device name string.
+ * @return Corresponding enumerator value.
+ */
+static enum serial_dev __serial_dev_enum(const char *dev_name)
+{
+ /* qemu, for example */
+ if (strncmp("ns16550", dev_name, 7) == 0)
+ return UART_8250;
+
+ /* mentioned in dtc documentation as an example */
+ if (strncmp("ns8250", dev_name, 7) == 0)
+ return UART_8250;
+
+ /* starfive visionfive 2, for example (hopefully works) */
+ if (strncmp("snps,dw-apb-uart", dev_name, 16) == 0)
+ return UART_8250;
+
+ return -1;
+}
+
+/**
+ * Get debugging info from FDT.
+ *
+ * @param fdt Global FDT pointer.
+ * @return Filled \ref dbg_info structure.
+ */
+static struct dbg_info __dbg_from_fdt(const void *fdt)
+{
+ int chosen_offset = fdt_path_offset(fdt, "/chosen");
+ const char *stdout =
+ fdt_getprop(fdt, chosen_offset, "stdout-path", NULL);
+
+ /* discard options */
+ size_t baselen = strlen(stdout);
+ const char *options = strchr(stdout, ':');
+ if (options)
+ baselen = options - stdout;
+
+ int stdout_offset = fdt_path_offset_namelen(fdt, stdout, baselen);
+
+ /* get serial device type */
+ const char *dev_name = (const char *)fdt_getprop(fdt, stdout_offset,
+ "compatible", NULL);
+
+ enum serial_dev dev = __serial_dev_enum(dev_name);
+
+ /* get serial device address */
+ const void *reg_ptr = fdt_getprop(fdt, stdout_offset, "reg", NULL);
+ struct cell_info ci = get_cellinfo(fdt, stdout_offset);
+ pm_t dbg_ptr = (pm_t)fdt_load_reg_addr(ci, reg_ptr, 0);
+
+ /* get serial device offset if present */
+ size_t shift = 0;
+ const void *shift_ptr = fdt_getprop(fdt, stdout_offset, "reg-shift",
+ NULL);
+
+ if (shift_ptr)
+ shift = (size_t)fdt_load_int32_ptr(shift_ptr);
+
+ /* while in direct map, base == addr, and this changes only when we jump
+ * into virtually mapped io */
+ return (struct dbg_info){ dbg_ptr, shift, dev, dbg_ptr };
+}
+
+/** 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 (!is_set(flags, UNSIGN_FLAG)) {
+ /* signed values, only with i format */
+ if (is_set(flags, LLONG_FLAG)) {
+ handle_type(signed long long, value, base);
+ } else if (is_set(flags, LONG_FLAG)) {
+ handle_type(signed long, value, base);
+ } else if (is_set(flags, SHORT_FLAG)) {
+ handle_type(signed short, value, base);
+ } else if (is_set(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 (is_set(flags, LLONG_FLAG)) {
+ handle_type(unsigned long long, value, base);
+ } else if (is_set(flags, LONG_FLAG)) {
+ handle_type(unsigned long, value, base);
+ } else if (is_set(flags, SHORT_FLAG)) {
+ handle_type(unsigned short, value, base);
+ } else if (is_set(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 prefix corresponding to \c base.
+ *
+ * @param base Base to integer.
+ * @return Length of prefix as string.
+ */
+static size_t __print_prefix(size_t base)
+{
+ size_t i = 0;
+
+ const char *hex = "0x";
+ const char *oct = "0";
+ const char *bin = "0b";
+ const char *empty = "";
+
+ const char *prefix;
+
+ if (base == 16)
+ prefix = hex;
+ else if (base == 8)
+ prefix = oct;
+ else if (base == 2)
+ prefix = bin;
+ else
+ prefix = empty;
+
+ for (; *prefix; ++i)
+ __putchar(*prefix++);
+
+ return i;
+}
+
+/**
+ * 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 (is_set(flags, LLONG_FLAG))
+ value = (signed long long)value;
+ else if (is_set(flags, LONG_FLAG))
+ value = (signed long)value;
+ else if (is_set(flags, SHORT_FLAG))
+ value = (signed short)value;
+ else if (is_set(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 = is_set(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 (is_set(flags, ZERO_FLAG)) {
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+
+ if (pad > 0 && !is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+
+ } else if (is_set(flags, SPACE_FLAG)) {
+ if (pad > 0 && !is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+ } else {
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+ }
+
+ /* print value itself */
+ ret += __integral_print(value, base, flags);
+
+ /* left-justify */
+ if (is_set(flags, ZERO_FLAG)) {
+ if (pad > 0 && is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+ } else if (is_set(flags, SPACE_FLAG)) {
+ if (pad > 0 && is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+ }
+
+ return ret;
+}
+
+void dbg(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 ' ':
+ set_bit(flags, SPACE_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '-':
+ set_bit(flags, LEFT_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '+':
+ set_bit(flags, SIGN_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '#':
+ set_bit(flags, HASH_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '0':
+ set_bit(flags, ZERO_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '\'':
+ set_bit(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++);
+ set_bit(flags, WIDTH_FLAG | PAD_FLAG | SPACE_FLAG);
+ } else if (*fmt == '*') {
+ int w = va_arg(vl, int);
+ if (w < 0) {
+ width = -w;
+ set_bit(flags, LEFT_FLAG);
+ } else {
+ width = w;
+ }
+ set_bit(flags, WIDTH_FLAG | PAD_FLAG | SPACE_FLAG);
+ fmt++;
+ }
+
+ /* check precision */
+ size_t precision = 0;
+ if (*fmt == '.') {
+ fmt++;
+ set_bit(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') {
+ set_bit(flags, LLONG_FLAG);
+ fmt++;
+ } else {
+ set_bit(flags, LONG_FLAG);
+ }
+ break;
+
+ case 'h':
+ fmt++;
+ if (*fmt == 'h') {
+ set_bit(flags, CHAR_FLAG);
+ fmt++;
+ } else {
+ set_bit(flags, SHORT_FLAG);
+ }
+ break;
+
+ case 'j':
+ fmt++;
+ if (sizeof(intmax_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+ break;
+
+ case 'z':
+ fmt++;
+ if (sizeof(size_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+ break;
+
+ case 't':
+ fmt++;
+ if (sizeof(ptrdiff_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(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)
+ clear_bit(flags, HASH_FLAG);
+
+ /* precision takes precedence */
+ if (is_set(flags, PRECS_FLAG))
+ width = precision;
+
+ /* formatting doesn't apply to decimal integers
+ * */
+ if (*fmt != 'i' && *fmt != 'd') {
+ clear_bit(flags, SIGN_FLAG);
+ set_bit(flags, UNSIGN_FLAG);
+ }
+
+ if (is_set(flags, LLONG_FLAG))
+ value = va_arg(vl, long long);
+ else if (is_set(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 (is_set(flags, PRECS_FLAG))
+ i = precision;
+
+ for (; *s && i--;) {
+ __putchar(*s++);
+ chars_written++;
+ }
+ fmt++;
+ break;
+
+ case 'p':
+ p = va_arg(vl, void *);
+ set_bit(flags, UNSIGN_FLAG | HASH_FLAG);
+
+ if (sizeof(void *) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(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);
+}
+
+#endif /* DEBUG */
diff --git a/src/dispatch.c b/src/dispatch.c
new file mode 100644
index 0000000..2786678
--- /dev/null
+++ b/src/dispatch.c
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#include <kmi/uapi.h>
+#include <kmi/ipi.h>
+
+/**
+ * @file dispatch.c
+ *
+ * Interface between assembly and C for dispatching syscalls/IPIs.
+ * In kmi, IPIs are a higher level concept and is not expected to be handled by
+ * the underlying architecture. Instead, we only require that there is some way
+ * to trigger an interrupt in some other core, and check if the interrupt was an
+ * IPI or syscall in C.
+ */
+
+/**
+ * Syscall/IPI handler.
+ * If a syscall was triggered, parameters have the meaning they are given.
+ * Otherwise, they are meaningless and unused.
+ *
+ * @param a Syscall number.
+ * @param b Argument 0.
+ * @param c Argument 1.
+ * @param d Argument 2.
+ * @param e Argument 3.
+ * @param f Argument 4.
+ *
+ * Returns value of taken action.
+ */
+void dispatch(sys_arg_t a, sys_arg_t b, sys_arg_t c,
+ sys_arg_t d, sys_arg_t e, sys_arg_t f)
+{
+ handle_syscall(a, b, c, d, e, f, cur_tcb());
+}
diff --git a/src/dmem.c b/src/dmem.c
new file mode 100644
index 0000000..bbabe09
--- /dev/null
+++ b/src/dmem.c
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file dmem.c
+ * Handle device memory, i.e. anything outside the physical RAM.
+ *
+ * \todo Handle NUMA.
+ */
+
+#include <kmi/assert.h>
+#include <kmi/dmem.h>
+
+/** Region before RAM. */
+static struct mem_region_root pre_ram = { 0 };
+
+/** Region after RAM. */
+static struct mem_region_root post_ram = { 0 };
+
+pm_t __pre_base = 0;
+pm_t __pre_top = 0;
+pm_t __post_base = 0;
+pm_t __post_top = 0;
+
+stat_t init_devmem(pm_t ram_base, pm_t ram_top)
+{
+ pm_t mem_top = (pm_t)-1;
+
+ __pre_base = 0;
+ __pre_top = ram_base - 1;
+
+ __post_base = ram_top;
+ __post_top = mem_top;
+
+ size_t pre_pages = __pages(__pre_top);
+ size_t post_pages = __pages(__post_top) - __pages(__post_base);
+
+ init_region(&pre_ram, __pre_base, pre_pages);
+ init_region(&post_ram, __post_base, post_pages);
+
+ return OK;
+}
+
+/**
+ * Device virtual memory worker callback for \ref map_fill_region().
+ *
+ * @param b Virtual memory to work in.
+ * @param offset Hint for \ref alloc_page().
+ * @param vaddr Current virtual address.
+ * @param flags Flags of region.
+ * @param order Suggested page order.
+ * @param data Pointer to \ref stat_t.
+ * @return \see alloc_uvmem_wrapper().
+ *
+ * \see alloc_uvmem_wrapper().
+ */
+static stat_t dev_alloc_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order,
+ void *data)
+{
+ stat_t *status = (stat_t *)data;
+ /** \todo remember to do something with this status info */
+ *status = map_vpage(b, *offset, vaddr, flags, order);
+ *offset += order_size(order);
+ return OK;
+}
+
+/**
+ * Device virtual memory freeing worker callback for \ref map_fill_region().
+ *
+ * @param b Virtual memory to work in.
+ * @param offset Hint for \ref alloc_page().
+ * @param vaddr Current virtual address.
+ * @param flags Flags of region.
+ * @param order Suggested page order.
+ * @param data Pointer to \ref stat_t.
+ * @return \see alloc_uvmem_wrapper().
+ *
+ * \see alloc_uvmem_wrapper().
+ */
+static stat_t dev_free_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order, void *data)
+{
+ UNUSED(offset);
+ UNUSED(flags);
+ pm_t paddr = 0;
+ enum mm_order v_order = 0;
+ stat_vpage(b, vaddr, &paddr, &v_order, 0);
+ if (order != v_order)
+ return INFO_TRGN;
+
+ stat_t *status = (stat_t *)data;
+ *status = unmap_vpage(b, vaddr);
+ return OK;
+}
+
+vm_t alloc_devmem(struct tcb *t, pm_t dev_start, size_t bytes, vmflags_t flags)
+{
+ hard_assert(t && is_proc(t), ERR_INVAL);
+
+ vm_t region = 0;
+ if (dev_start < __pre_top)
+ region = alloc_region(&pre_ram, bytes, 0, flags);
+
+ if (dev_start > __post_base)
+ region = alloc_region(&post_ram, bytes, 0, flags);
+
+ if (!region)
+ return NULL;
+
+ stat_t status = OK;
+ const vm_t w = map_fill_region(t->proc.vmem, &dev_alloc_wrapper,
+ dev_start, region,
+ bytes, flags, &status);
+
+ if (status)
+ return NULL;
+
+ return w;
+}
+
+stat_t free_devmem(struct tcb *t, vm_t dev_start)
+{
+ hard_assert(t && is_proc(t), ERR_INVAL);
+
+ pm_t dev_paddr = 0;
+ stat_vpage(t->proc.vmem, dev_start, &dev_paddr, 0, 0);
+
+ if (dev_paddr >= __pre_top && dev_paddr <= __post_base)
+ return ERR_ADDR;
+
+ struct mem_region *m = 0;
+ if (dev_paddr < __pre_top)
+ m = find_used_region(&pre_ram, dev_paddr);
+
+ if (dev_paddr > __post_base)
+ m = find_used_region(&post_ram, dev_paddr);
+
+ if (!m)
+ return ERR_NF;
+
+ size_t region_size = __addr(m->end - m->start);
+ stat_t status = OK;
+ map_fill_region(t->proc.vmem, &dev_free_wrapper, dev_paddr, dev_start,
+ region_size, 0, &status);
+
+ if (dev_paddr < __pre_top)
+ free_region(&pre_ram, dev_paddr);
+
+ if (dev_paddr > __post_base)
+ free_region(&post_ram, dev_paddr);
+
+ return status;
+}
diff --git a/src/elf.c b/src/elf.c
new file mode 100644
index 0000000..da47502
--- /dev/null
+++ b/src/elf.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file elf.c
+ * Handle elf executables, set up requested memory mappings etc.
+ */
+
+#include <kmi/elf.h>
+#include <kmi/vmem.h>
+#include <kmi/bits.h>
+#include <kmi/string.h>
+#include <kmi/assert.h>
+
+/**
+ * Convert ELF flags to page flags.
+ *
+ * @param elf_flags ELF flags to convert.
+ * @return Corresponding page flags.
+ */
+static uint8_t __elf_to_uvflags(uint8_t elf_flags)
+{
+ uint8_t uvflags = VM_V | VM_U;
+ if (elf_flags & PF_X)
+ uvflags |= VM_X;
+
+ if (elf_flags & PF_W)
+ uvflags |= VM_W;
+
+ if (elf_flags & PF_R)
+ uvflags |= VM_R;
+
+ return uvflags;
+}
+
+/**
+ * Map ELF executable.
+ *
+ * @param t Thread space to work in.
+ * @param bin Address of binary to map.
+ * @param ei_c ELF identity class.
+ * @param phstart Program header start.
+ * @param phnum Number of program header entries.
+ * @param phsize Size of page header entry.
+ */
+static void __map_exec(struct tcb *t, vm_t bin, uint8_t ei_c, vm_t phstart,
+ size_t phnum, size_t phsize)
+{
+ hard_assert(t && is_proc(t), RETURN_VOID);
+
+ /** \todo take alignment into consideration? */
+ /** \todo take overlapping memory regions into account, probably mostly
+ * by keeping track of previously allocated area and seeing if the
+ * segment fits into it */
+ /** \todo check if p_memsz is larger than p_filesz, the segment should be
+ * filled with zeroes. */
+ /** \todo in general, make this a lot more clean. */
+ /* useful bit of info: all segments are sorted in ascending order of p_vaddr */
+ vm_t runner = phstart;
+ vmflags_t default_flags = VM_V | VM_R | VM_W | VM_X | VM_U;
+ for (size_t i = 0; i < phnum; ++i, runner += phsize) {
+ if (program_header_prop(ei_c, runner, p_type) != PT_LOAD)
+ continue;
+
+ vm_t va = program_header_prop(ei_c, runner, p_vaddr);
+ size_t vsz = program_header_prop(ei_c, runner, p_memsz);
+
+ vm_t start = alloc_fixed_region(&t->sp_r, va, vsz, &vsz,
+ default_flags);
+ if (!start)
+ return; /* out of memory or something */
+
+ uint8_t elf_flags = program_header_prop(ei_c, runner, p_flags);
+ uint8_t uvflags = __elf_to_uvflags(elf_flags);
+
+ map_allocd_region(t->proc.vmem, start, vsz, default_flags, 0);
+ memset((void *)start, 0, vsz);
+
+ vm_t vo = bin + program_header_prop(ei_c, runner, p_offset);
+ vm_t vfz = program_header_prop(ei_c, runner, p_filesz);
+ memcpy((void *)va, (void *)vo, vfz);
+
+ /* skip while testing
+ * \todo: also fix, this modifies only the first region. Create new
+ * function?
+ *
+ pm_t paddr = 0;
+ stat_vpage(t->b_r, va, &paddr, 0, 0);
+ mod_vpage(t->b_r, va, paddr, uvflags);
+ */
+ }
+}
+
+/**
+ * Map ELF dynamic object.
+ *
+ * @param t Thread space to work in.
+ * @param bin Address of binary to map.
+ * @param ei_c ELF identity class.
+ * @param phstart Program header start.
+ * @param phnum Number of program header entries.
+ * @param phsize Size of page header entry.
+ * @return Base of dynamic mapping.
+ */
+static vm_t __map_dyn(struct tcb *t, vm_t bin, uint8_t ei_c, vm_t phstart,
+ size_t phnum, size_t phsize)
+{
+ /** \todo this path should only be taken when no PT_INTERP is defined, as
+ * making sure ld is loaded should be done in userspace. Maybe a bit
+ * hacky, I know.*/
+}
+
+/**
+ * Map binary and optional interpreter.
+ *
+ * \todo Implement interpeter handling.
+ *
+ * @param t Thread space to work in.
+ * @param ei_c ELF identity class. (Of binary, should do one for interp?)
+ * @param elf ELF binary.
+ * @param interp ELF interpeter.
+ * @return Entry address.
+ */
+static vm_t __prepare_proc(struct tcb *t, uint8_t ei_c, vm_t elf, vm_t interp)
+{
+ short e_type = elf_header_prop(ei_c, elf, e_type);
+ if (e_type != ET_DYN && e_type != ET_EXEC)
+ return 0;
+
+ vm_t phstart = ptradd(elf, elf_header_prop(ei_c, elf, e_phoff));
+ size_t phnum = elf_header_prop(ei_c, elf, e_phnum);
+ size_t phsize = elf_header_prop(ei_c, elf, e_phentsize);
+
+ vm_t entry = elf_header_prop(ei_c, elf, e_entry);
+ if (e_type == ET_EXEC) {
+ __map_exec(t, elf, ei_c, phstart, phnum, phsize);
+ return entry;
+ } else {
+ vm_t o = __map_dyn(t, elf, ei_c, phstart, phnum, phsize);
+ return o + entry;
+ }
+}
+
+/* sets up all memory regions etc, returns the entry address */
+vm_t load_elf(struct tcb *t, vm_t elf, vm_t interp)
+{
+ struct elf_ident *i = (struct elf_ident *)elf;
+ if (i->ei_magic != cpu_to_be32(EI_MAGIC))
+ return 0;
+
+ if (i->ei_class != ELFCLASS32 && i->ei_class != ELFCLASS64)
+ return 0;
+
+ /* more sanity checks? */
+ return __prepare_proc(t, i->ei_class, elf, interp);
+}
diff --git a/src/fdt.c b/src/fdt.c
new file mode 100644
index 0000000..677be7b
--- /dev/null
+++ b/src/fdt.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file fdt.c
+ * Helper functions for handling the global FDT.
+ */
+
+#include <libfdt.h>
+
+struct cell_info get_cellinfo(const void *fdt, const int offset)
+{
+ return (struct cell_info){ fdt_size_cells(fdt, offset),
+ fdt_address_cells(fdt, offset) };
+}
diff --git a/src/initrd.c b/src/initrd.c
new file mode 100644
index 0000000..caff8b3
--- /dev/null
+++ b/src/initrd.c
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file initrd.c
+ * Handle initrd, implement cpio with newc format.
+ */
+
+#include <kmi/initrd.h>
+#include <kmi/vmem.h>
+#include <kmi/string.h>
+#include <kmi/utils.h>
+#include <kmi/attrs.h>
+#include <kmi/debug.h>
+#include <libfdt.h>
+
+/** GNU cpio, POSIX 'newc' format header. */
+struct __packed cpio_header {
+ /** Magic bytes. */
+ char c_magic[6];
+
+ /** File inode. */
+ char c_ino[8];
+
+ /** File type and permissions. */
+ char c_mode[8];
+
+ /** User ID. */
+ char c_uid[8];
+
+ /** Group ID. */
+ char c_gid[8];
+
+ /** Number of links to this file. */
+ char c_nlink[8];
+
+ /** Modification time. */
+ char c_mtime[8];
+
+ /** Size of file. */
+ char c_filesize[8];
+
+ /** Device major. */
+ char c_devmajor[8];
+
+ /** Device minor. */
+ char c_devminor[8];
+
+ /** Block device major. */
+ char c_rdevmajor[8];
+
+ /** Block device minor. */
+ char c_rdevminor[8];
+
+ /** Length of filename. */
+ char c_namesize[8];
+
+ /** CRC check. */
+ char c_check[8];
+};
+
+/**
+ * Get next file in archive.
+ * Does not check for out of bounds.
+ *
+ * @param cp Pointer to current file header.
+ * @return Pointer to next file header.
+ */
+static struct cpio_header *__next_entry(struct cpio_header *cp)
+{
+ size_t blen = align_up(
+ sizeof(struct cpio_header) + convnum(cp->c_namesize, 8, 16), 4);
+ size_t tlen = align_up(convnum(cp->c_filesize, 8, 16), 4);
+
+ return (struct cpio_header *)(((char *)cp) + blen + tlen);
+}
+
+/**
+ * Get file with name in archive.
+ *
+ * @param c Pointer to initrd.
+ * @param fname Filename to look for.
+ * @param fname_len Length of filename.
+ * @return Pointer to corresponding file header if found, \c NULL otherwise.
+ */
+static struct cpio_header *__find_file(const char *c, const char *fname,
+ size_t fname_len)
+{
+ struct cpio_header *cp = (struct cpio_header *)c;
+ for (; cp; cp = __next_entry(cp)) {
+ size_t namelen = convnum(cp->c_namesize, 8, 16);
+ if (namelen == 0)
+ return NULL;
+
+ if (namelen < fname_len)
+ continue;
+
+ char *name = (char *)(cp + 1);
+ if (fname[0] != '/')
+ name += namelen - (fname_len + 1); /* match ending */
+
+ if (strncmp(name, fname, fname_len) == 0)
+ return cp;
+ }
+
+ return NULL;
+}
+
+/** Name of \c init program. */
+static char init_n[] = "init";
+
+/** Length of \c init name. */
+static size_t init_nlen = ARRAY_SIZE(init_n) - 1; /* ignore trailing NULL */
+
+pm_t get_initrdtop(const void *fdt)
+{
+ int chosen_offset = fdt_path_offset(fdt, "/chosen");
+ struct cell_info ci = get_cellinfo(fdt, chosen_offset);
+
+ void *initrd_end_ptr = (void *)fdt_getprop(fdt, chosen_offset,
+ "linux,initrd-end", NULL);
+
+ /* fdt is only aware of physical memory pointers */
+ return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_end_ptr));
+}
+
+pm_t get_initrdbase(const void *fdt)
+{
+ const int chosen_offset = fdt_path_offset(fdt, "/chosen");
+ const struct cell_info ci = get_cellinfo(fdt, chosen_offset);
+
+ void *initrd_base_ptr = (void *)fdt_getprop(fdt, chosen_offset,
+ "linux,initrd-start", NULL);
+
+ return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_base_ptr));
+}
+
+
+size_t get_init_size(const void *fdt)
+{
+ char *c = (char *)get_initrdbase(fdt);
+ struct cpio_header *cp = __find_file(c, init_n, init_nlen);
+ return convnum(cp->c_filesize, 8, 16);
+}
+
+vm_t get_init_base(const void *fdt)
+{
+ char *c = (char *)get_initrdbase(fdt);
+ struct cpio_header *cp = __find_file(c, init_n, init_nlen);
+ size_t name_len = convnum(cp->c_namesize, 8, 16);
+ return ((vm_t)cp) + align_up(sizeof(struct cpio_header) + name_len, 4);
+}
+
+stat_t move_init(const void *fdt, void *target)
+{
+ const char *c = (const char *)get_initrdbase(fdt);
+
+ const struct cpio_header *cp = __find_file(c, init_n, init_nlen);
+ size_t name_len = convnum(cp->c_namesize, 8, 16);
+ size_t file_len = convnum(cp->c_filesize, 8, 16);
+
+ char *fp = (char *)cp;
+ fp += align_up(sizeof(struct cpio_header) + name_len, 4);
+
+ memmove(target, fp, file_len);
+ return OK;
+}
diff --git a/src/ipi.c b/src/ipi.c
new file mode 100644
index 0000000..59f9fd1
--- /dev/null
+++ b/src/ipi.c
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#include <kmi/ipi.h>
+#include <arch/cpu.h>
+
+#include <arch/proc.h>
+
+/**
+ * @file ipi.c
+ *
+ * IPI function implementations.
+ */
+
+bool clear_ipi(struct tcb *t)
+{
+ bool r = t->ipi;
+ t->ipi = false;
+ return r;
+}
+
+void send_ipi(struct tcb *t)
+{
+ t->ipi = true;
+ cpu_send_ipi(t->cpu_id);
+}
+
+void handle_ipi()
+{
+ struct tcb *t = cur_tcb();
+ adjust_ipi(t);
+
+ /** @todo use rpc stack */
+ set_return(t, t->callback);
+}
diff --git a/src/irq.c b/src/irq.c
new file mode 100644
index 0000000..db90d34
--- /dev/null
+++ b/src/irq.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file irq.c
+ * Common IRQ handling stuff implementations.
+ */
+
+#include <kmi/irq.h>
+#include <kmi/pmem.h>
+#include <kmi/debug.h>
+#include <kmi/assert.h>
+#include <kmi/string.h>
+#include <arch/irq.h>
+
+/** Hold maximum IRQ id supported by system. */
+static size_t max_irq;
+
+/** Hold map of IRD ID -> thread id. @todo process id? */
+static id_t *irq_map;
+
+void init_irq(void *fdt)
+{
+ /** @todo check arch max irq and adjust accordingly */
+ irq_map = (id_t *)alloc_page(MM_O0);
+ memset(irq_map, 0, order_size(MM_O0));
+ max_irq = order_size(MM_O0) / sizeof(irq_map[0]);
+
+ setup_irq(fdt);
+}
+
+stat_t register_irq(struct tcb *t, irq_t id)
+{
+ if (id >= max_irq)
+ return ERR_INVAL;
+
+ if (irq_map[id])
+ return ERR_EXT;
+
+ irq_map[id] = t->tid;
+ return activate_irq(id);
+}
+
+stat_t unregister_irq(struct tcb *t, irq_t id)
+{
+ id_t tid = irq_map[id];
+ if (tid != t->tid)
+ return ERR_PERM;
+
+ irq_map[id] = 0;
+ return deactivate_irq(id);
+}
+
+void handle_irq()
+{
+ irq_t id = get_irq();
+ hard_assert(id < max_irq, RETURN_VOID);
+
+ id_t tid = irq_map[id];
+
+ if (!tid) {
+ bug("unregistered irq: %llu\n", (unsigned long long)id);
+ return;
+ }
+
+ /** @todo switch to thread */
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..4647fed
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file main.c
+ * Entry point for actual kernel setup.
+ */
+
+#include <kmi/mem_nodes.h>
+#include <kmi/initrd.h>
+#include <kmi/timer.h>
+#include <kmi/attrs.h>
+#include <kmi/proc.h>
+#include <kmi/debug.h>
+#include <kmi/vmem.h>
+#include <kmi/irq.h>
+#include <arch/arch.h>
+#include <arch/proc.h>
+#include <arch/smp.h>
+#include <libfdt.h>
+
+/**
+ * Boot entry of kernel actual.
+ *
+ * Sets up all kernel subsystems and jumps into \c init program, does not
+ * return.
+ *
+ * @param fdt Global FDT pointer in physical memory.
+ * @param ram_base RAM base.
+ * @return Should not.
+ */
+void __main main(void *fdt, uintptr_t ram_base)
+{
+ set_ram_base(ram_base);
+
+ /* convert physical address to virtual address */
+ fdt = __va(fdt);
+
+ /* dbg uses direct mapping at this point */
+ init_dbg(fdt);
+ setup_dmap_dbg();
+ dbg_fdt(fdt);
+
+ setup_arch(fdt);
+
+ init_pmem(fdt);
+ /* setup temporary virtual memory */
+ struct vmem *b = init_vmem(fdt);
+
+ /* start up debugging in kernel IO */
+ setup_io_dbg(b);
+
+ init_irq(fdt);
+ init_timer(fdt);
+ init_proc(fdt);
+
+ /* try to bring up other cores on system */
+ smp_bringup(b, fdt);
+
+ /* start running init program */
+ void *initrd = (void *)get_initrdbase(fdt);
+ run_init(cur_tcb(), fdt, initrd);
+}
diff --git a/src/mem.c b/src/mem.c
new file mode 100644
index 0000000..e3f98e7
--- /dev/null
+++ b/src/mem.c
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file mem.c
+ * Generic memory handling, used both by physical and virtual memory.
+ */
+
+#include <kmi/types.h>
+#include <kmi/mem.h>
+#include <kmi/vmem.h>
+#include <libfdt.h>
+
+size_t __mm_shifts[10];
+size_t __mm_widths[10];
+size_t __mm_sizes[10];
+size_t __mm_page_shift;
+enum mm_order __mm_max_order;
+
+/**
+ * RAM base address. Not sure if it should be provided through a macro
+ * like __mm_*.
+ */
+pm_t ram_base;
+
+enum mm_order nearest_order(size_t size)
+{
+ for (enum mm_order order = max_order(); order >= MM_MIN; --order)
+ if (order_size(order) >= size)
+ return order;
+
+ return MM_O0;
+}
+
+void init_mem(size_t max_order, size_t bits[10], size_t page_shift)
+{
+ __mm_max_order = max_order;
+ __mm_page_shift = page_shift;
+
+ __mm_shifts[0] = page_shift;
+ __mm_widths[0] = 1 << bits[0];
+ __mm_sizes[0] = 1 << __mm_page_shift;
+
+ for (enum mm_order i = MM_O1; i <= max_order(); ++i) {
+ __mm_widths[i] = 1 << bits[i];
+ __mm_shifts[i] = __mm_shifts[i - 1] + bits[i - 1];
+ __mm_sizes[i] = 1UL << __mm_shifts[i];
+ }
+}
+
+void set_ram_base(uintptr_t base)
+{
+ ram_base = base;
+}
+
+uintptr_t get_ram_base()
+{
+ return ram_base;
+}
diff --git a/src/mem_nodes.c b/src/mem_nodes.c
new file mode 100644
index 0000000..625b1a3
--- /dev/null
+++ b/src/mem_nodes.c
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file mem_nodes.c
+ * Memory node wrapper around the node subsystem, used by \ref
+ * src/mem_regions.c.
+ *
+ * Each region of memory is allocated through a \ref mem_region node, which is
+ * allocated through the node subsystem.
+ */
+
+#include <kmi/vmem.h>
+#include <kmi/pmem.h>
+#include <kmi/mem.h>
+#include <kmi/string.h>
+#include <kmi/mem_nodes.h>
+
+/** Memory node subsystem instance. */
+static struct node_root root;
+
+void init_mem_nodes()
+{
+ init_nodes(&root, sizeof(struct mem_region));
+}
+
+void destroy_mem_nodes()
+{
+ destroy_nodes(&root);
+}
+
+struct mem_region *get_mem_node()
+{
+ return (struct mem_region *)get_node(&root);
+}
+
+void free_mem_node(struct mem_region *m)
+{
+ free_node(&root, (void *)m);
+}
diff --git a/src/mem_regions.c b/src/mem_regions.c
new file mode 100644
index 0000000..9468774
--- /dev/null
+++ b/src/mem_regions.c
@@ -0,0 +1,587 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file mem_regions.c
+ * Memory region handling, used by both device memory and user virtual memory
+ * subsystems.
+ */
+
+#include <kmi/mem_regions.h>
+#include <kmi/mem_nodes.h>
+#include <kmi/pmem.h>
+#include <kmi/bits.h>
+#include <kmi/mem.h>
+
+/**
+ * Readability wrapper for marking region used.
+ *
+ * @param r Region flags to set.
+ */
+#define mark_region_used(r) set_bit(r, MR_USED)
+
+/**
+ * Readability wrapper for marking region unused.
+ *
+ * @param r Region flags to clear.
+ */
+#define mark_region_unused(r) clear_bit(r, MR_USED)
+
+/* pretty major slowdown when we get to some really massive numbers, not
+ * entirely sure why. Will need to check up on this at some point, have I
+ * somehow managed to come up with a _very_ bad situation for my sp_trees?
+ *
+ * EDIT: apparently, yeah. Max depth of 106 with a million entries, interesting.
+ * I guess since in this scenario all sizes are 1, and I just shove everything
+ * to the right? Maybe?
+ *
+ * EDIT upon EDIT: yeah, when taking the start position of the region into
+ * account we get a much more sensible max depth of 39 for 5 million entries.
+ * Seems I have found a weakness in sp_trees :D
+ *
+ * Duplicate entries don't work well with any trees, I think. Good to know,
+ * maybe not even anything with sp_trees but more a weakness of binary trees in
+ * general?
+ */
+
+/**
+ * Insert free memory region.
+ *
+ * @param r Memory region root to insert \c m into.
+ * @param m Free memory region to insert.
+ * @return \c m.
+ */
+static struct mem_region *__insert_free_region(struct mem_region_root *r,
+ struct mem_region *m)
+{
+ struct sp_node *n = sp_root(&r->free_regions), *p = NULL;
+ vm_t start = m->start;
+ size_t size = m->end - m->start;
+ enum sp_dir d = LEFT;
+
+ m->sp_n = (struct sp_node){ 0 };
+
+ while (n) {
+ struct mem_region *t = mem_container(n);
+ size_t nsize = t->end - t->start;
+ p = n;
+
+ if (size < nsize) {
+ n = sp_left(n);
+ d = LEFT;
+ }
+
+ else if (size > nsize) {
+ n = sp_right(n);
+ d = RIGHT;
+ }
+
+ else if (start < t->start) {
+ n = sp_left(n);
+ d = LEFT;
+ }
+
+ else {
+ n = sp_right(n);
+ d = RIGHT;
+ }
+ }
+
+ if (sp_root(&r->free_regions))
+ sp_insert(&sp_root(&r->free_regions), p, &m->sp_n, d);
+ else
+ sp_root(&r->free_regions) = &m->sp_n;
+
+ return m;
+}
+
+/**
+ * Insert used memory region.
+ *
+ * @param r Memory region root to insert \c m into.
+ * @param m Memory region to insert.
+ * @return \c m.
+ */
+static struct mem_region *__insert_used_region(struct mem_region_root *r,
+ struct mem_region *m)
+{
+ struct sp_node *n = sp_root(&r->used_regions), *p = NULL;
+ vm_t start = m->start;
+ enum sp_dir d = LEFT;
+
+ m->sp_n = (struct sp_node){ 0 };
+
+ while (n) {
+ struct mem_region *t = mem_container(n);
+
+ p = n;
+
+ if (start < t->start) {
+ n = sp_left(n);
+ d = LEFT;
+ }
+
+ else {
+ /* we should never encounter a situation where start =
+ * t->start */
+ n = sp_right(n);
+ d = RIGHT;
+ }
+ }
+
+ if (sp_root(&r->used_regions))
+ sp_insert(&sp_root(&r->used_regions), p, &m->sp_n, d);
+ else
+ sp_root(&r->used_regions) = &m->sp_n;
+
+ return m;
+}
+
+stat_t init_region(struct mem_region_root *r, vm_t start, size_t arena_size)
+{
+ /* convert bytes to pages */
+ start = __page(start);
+ arena_size = __page(arena_size);
+ struct mem_region *m = get_mem_node();
+ m->start = start;
+ m->end = start + arena_size;
+ __insert_free_region(r, m);
+
+ return OK;
+}
+
+/**
+ * Destroy memory region and all its children.
+ *
+ * @param n \ref sp_node of memory region to destroy.
+ */
+static void __destroy_region(struct sp_node *n)
+{
+ if (!n)
+ return;
+
+ if (sp_left(n))
+ __destroy_region(sp_left(n));
+
+ if (sp_right(n))
+ __destroy_region(sp_right(n));
+
+ struct mem_region *m = mem_container(n);
+ free_mem_node(m);
+}
+
+stat_t destroy_region(struct mem_region_root *r)
+{
+ __destroy_region(sp_root(&r->free_regions));
+ __destroy_region(sp_root(&r->used_regions));
+ /** \todo error checking? */
+ return OK;
+}
+
+/* interestingly this is now the main bottleneck :D
+ *
+ * eh, it's not a massive thing I guess, maybe the code could be a bit quicker
+ * but I mean 10 000 000 memory allocations in 20 s is good enough for now
+ * */
+struct mem_region *find_used_region(struct mem_region_root *r, vm_t start)
+{
+ /** @todo check that start is aligned to page boundary? */
+ vm_t ref = __page(start);
+ struct sp_node *n = sp_root(&r->used_regions);
+ while (n) {
+ struct mem_region *t = mem_container(n);
+ if (ref == t->start)
+ return t;
+
+ if (ref < t->start)
+ n = sp_left(n);
+ else
+ n = sp_right(n);
+ }
+
+ return 0;
+}
+
+/**
+ * Create memory region.
+ *
+ * @param start Start of region.
+ * @param end End of region.
+ * @param prev Previous region.
+ * @param next Next region.
+ * @return Created region.
+ */
+static struct mem_region *__create_region(vm_t start, vm_t end,
+ struct mem_region *prev,
+ struct mem_region *next)
+{
+ struct mem_region *m = get_mem_node();
+ m->start = start;
+ m->end = end;
+ m->prev = prev;
+ m->next = next;
+ return m;
+}
+
+/**
+ * Get first order size smaller than \c s in bytes.
+ *
+ * @param s Size to look for.
+ * @return Size of first order smaller than \c s.
+ */
+static size_t po_align(size_t s)
+{
+ for (size_t o = __mm_max_order; o > 0; --o) {
+ if (s >= order_size(o))
+ return order_size(o);
+ }
+
+ return 0;
+}
+
+struct mem_region *find_closest_used_region(struct mem_region_root *r,
+ vm_t start)
+{
+ struct mem_region *closest = 0;
+ size_t md = (size_t)(-1);
+ struct sp_node *n = sp_root(&r->used_regions);
+ if (!n)
+ return mem_container(sp_root(&r->free_regions));
+
+ while (n) {
+ struct mem_region *t = mem_container(n);
+ size_t d = ABS((ssize_t)start - (ssize_t)t->start);
+
+ if (d == 0) /* exact match */
+ return t;
+
+ if (d < md) { /* closest so far */
+ closest = t;
+ md = d;
+ }
+
+ if (start < t->start)
+ n = sp_left(n);
+ else
+ n = sp_right(n);
+ }
+
+ return closest;
+}
+
+/* should probably document this a bit better but in short, look for the "best"
+ * free block, meaning one that is hopefully aligned so as to allow us to later
+ * map it to higher order pages. If no block is found such that that is
+ * possible, also keep track of the smallest block that we found that the region
+ * still fits in, unaligned. If none of these criteria are met, a NULL is
+ * returned. Note that this does not check *all* possible memory blocks, only
+ * going up in increasing size so as to save time. */
+struct mem_region *find_free_region(struct mem_region_root *r, size_t size,
+ size_t *align)
+{
+ *align = 0;
+ size_t offset = __page(po_align(__addr(size)));
+ struct mem_region *quick_best = 0;
+ struct sp_node *n = sp_root(&r->free_regions);
+ while (n) {
+ struct mem_region *t = mem_container(n);
+ vm_t start = align_up(t->start, offset);
+
+ size_t qsize = t->end - t->start;
+ size_t bsize = t->end - start;
+
+ if (!quick_best && size <= qsize)
+ quick_best = t;
+
+ if (size <= bsize) {
+ *align = start - t->start;
+ return t;
+ }
+
+ n = sp_right(n);
+ }
+
+ return quick_best;
+}
+
+struct mem_region *find_first_region(struct mem_region_root *r)
+{
+ /* get used region with smallest address, likely also close to the start
+ * of the linked list */
+ struct mem_region *m = find_closest_used_region(r, 0);
+ while (m->prev) {
+ m = m->prev;
+ }
+
+ return m;
+}
+
+/**
+ * Carve out new used memory region from free memory region.
+ *
+ * @param r Memory region root to work in.
+ * @param m Free memory region to carve used memory region out of.
+ * @param pages Number of base order pages to give used region.
+ * @param align Alignment of used region. In this case, start of used region
+ * @param pid Process ID to associate with region if shared. 0 if private.
+ * from start of free region.
+ * @param flags Flags of used region.
+ * @return Start address of used region.
+ */
+static vm_t __partition_region(struct mem_region_root *r, struct mem_region *m,
+ size_t pages, size_t align, vmflags_t flags,
+ id_t pid)
+{
+ sp_remove(&sp_root(&r->free_regions), &m->sp_n);
+
+ vm_t pre_start = m->start;
+ vm_t pre_end = pre_start + align;
+
+ vm_t start = pre_end;
+ vm_t end = start + pages;
+
+ vm_t post_start = end;
+ vm_t post_end = m->end;
+
+ if (pre_start != pre_end) {
+ struct mem_region *n =
+ __create_region(pre_start, pre_end, m->prev, m);
+ m->prev = n;
+ if (n->prev)
+ n->prev->next = n;
+
+ __insert_free_region(r, n);
+ }
+
+ if (post_start != post_end) {
+ struct mem_region *n =
+ __create_region(post_start, post_end, m, m->next);
+ m->next = n;
+ if (n->next)
+ n->next->prev = n;
+
+ __insert_free_region(r, n);
+ }
+
+ m->end = end;
+ m->start = start;
+ m->flags = flags;
+ m->pid = pid;
+ mark_region_used(m->flags);
+ __insert_used_region(r, m);
+ return __addr(start);
+}
+
+/* apparently Linux doesn't necessarily give a shit about mmap hints, so I'll
+ * just ignore them for now. Note that alloc_region should only be used when
+ * mmap is called with MAP_ANON, all other situations should be handled in some
+ * fs server */
+vm_t alloc_shared_region(struct mem_region_root *r, size_t size,
+ size_t *actual_size,
+ vmflags_t flags, id_t pid)
+{
+ size_t asize = align_up(size, BASE_PAGE_SIZE);
+ if (actual_size)
+ *actual_size = asize;
+
+ size_t pages = __page(asize);
+
+ /* find best fitting, alignment etc. */
+ size_t align = 0;
+ struct mem_region *m = find_free_region(r, pages, &align);
+ if (!m)
+ return 0;
+
+ return __partition_region(r, m, pages, align, flags, pid);
+}
+
+vm_t alloc_region(struct mem_region_root *r, size_t size, size_t *actual_size,
+ vmflags_t flags)
+{
+ return alloc_shared_region(r, size, actual_size, flags, 0);
+}
+
+vm_t alloc_fixed_region(struct mem_region_root *r, vm_t start, size_t size,
+ size_t *actual_size, vmflags_t flags)
+{
+ size_t asize = align_up(size, BASE_PAGE_SIZE);
+ if (actual_size)
+ *actual_size = asize;
+
+ size_t pages = __page(asize);
+ start = __page(start);
+
+ struct mem_region *m = find_closest_used_region(r, start);
+ if (!m)
+ return 0;
+
+ /* locate actual region where start is between the region start and end */
+ while (!((m->start <= start) && (start < m->end))) {
+ if (start > m->start)
+ m = m->next;
+ else
+ m = m->prev;
+ }
+
+ /* if region is already in use, forget it */
+ if (is_region_used(m))
+ return 0;
+
+ /* region is too small */
+ if (start + pages > m->end)
+ return 0;
+
+ /* actually start marking region used */
+ return __partition_region(r, m, pages, start - m->start, flags, 0);
+}
+
+/**
+ * Try to coalesce two adjacent memory regions, iterating left.
+ *
+ * @param r Memory region root to work in.
+ * @param m Memory region to start trying to coalesce.
+ */
+static void __try_coalesce_prev(struct mem_region_root *r, struct mem_region *m)
+{
+ while (m) {
+ if (!m || is_region_used(m))
+ return;
+
+ struct mem_region *p = m->prev;
+ if (!p || is_region_used(p))
+ return;
+
+ m->start = p->start;
+ m->prev = p->prev;
+
+ if (m->prev)
+ m->prev->next = m;
+
+ sp_remove(&sp_root(&r->free_regions), &p->sp_n);
+ free_mem_node(p);
+
+ m = m->prev;
+ }
+}
+
+/**
+ * Try to coalesce two adjacent memory region, iterating right.
+ *
+ * @param r Memory region root to work in.
+ * @param m Memory region to start trying to coalesce.
+ */
+static void __try_coalesce_next(struct mem_region_root *r, struct mem_region *m)
+{
+ while (m) {
+ if (!m || is_region_used(m))
+ return;
+
+ struct mem_region *n = m->next;
+ if (!n || is_region_used(n))
+ return;
+
+ m->end = n->end;
+ m->next = n->next;
+
+ if (m->next)
+ m->next->prev = m;
+
+ sp_remove(&sp_root(&r->free_regions), &n->sp_n);
+ free_mem_node(n);
+
+ m = m->next;
+ }
+}
+
+/**
+ * Try coalescing memory regions.
+ *
+ * @param r Memory region root to work in.
+ * @param m Memory region to start trying to coalesce.
+ */
+static void __try_coalesce_regions(struct mem_region_root *r,
+ struct mem_region *m)
+{
+ __try_coalesce_prev(r, m);
+ __try_coalesce_next(r, m);
+}
+
+stat_t free_region(struct mem_region_root *r, vm_t start)
+{
+ /* addr not aligned to page boundary, corrupted or incorrect pointer */
+ if (!is_aligned(start, BASE_PAGE_SIZE))
+ return ERR_ALIGN;
+
+ struct mem_region *m = find_used_region(r, start);
+ if (!m)
+ return ERR_NF;
+
+ return free_known_region(r, m);
+}
+
+stat_t free_known_region(struct mem_region_root *r, struct mem_region *m)
+{
+ sp_remove(&sp_root(&r->used_regions), &m->sp_n);
+ mark_region_unused(m->flags);
+
+ __try_coalesce_regions(r, m);
+ __insert_free_region(r, m);
+ return OK;
+}
+
+void set_alt_region_addr(struct mem_region_root *r, vm_t va, vm_t alt_va)
+{
+ struct mem_region *m = find_used_region(r, va);
+ if (!m)
+ return;
+
+ /* not shared region */
+ if (m->pid == 0)
+ return;
+
+ m->alt_va = alt_va;
+}
+
+/* assuming start is chosen to start on an aligned border, this should choose
+ * the 'optimal' fit for the mapping.
+ *
+ * NOTE: not actually optimal, this doesn't bother to go through possible
+ * permutations etc. which would be slow and I don't want to implement it.
+ */
+vm_t map_fill_region(struct vmem *b, region_callback_t *mem_handler,
+ pm_t offset, vm_t start, size_t bytes, vmflags_t flags,
+ void *data)
+{
+ pm_t runner = __page(start);
+ size_t pages = __pages(bytes);
+ enum mm_order top = __mm_max_order;
+
+ /* actual start might not be the same as the user specified start */
+ start = __addr(runner);
+
+ for (; pages; top--) {
+ size_t o_size = order_size(top);
+ size_t o_pages = __pages(o_size);
+
+ /* NULL does pass this check, so technically all NULL pages are
+ * aligned, but they're caught in the while expr so this should
+ * work even if someone tries to map NULL */
+ if (!is_aligned(runner, o_pages))
+ continue;
+
+ while (pages >= o_pages) {
+ stat_t res = mem_handler(b, &offset, __addr(runner),
+ flags, top, data);
+ if (res > 0)
+ break;
+
+ if (res < 0)
+ return 0;
+
+ pages -= o_pages;
+ runner += o_pages;
+ }
+ }
+
+ return start;
+}
diff --git a/src/nodes.c b/src/nodes.c
new file mode 100644
index 0000000..5edb783
--- /dev/null
+++ b/src/nodes.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file nodes.c
+ * The node subsystem. Each client has to initialize their own node system,
+ * after which they can request nodes of a size specified at init.
+ *
+ * Node allocation is implemented through a similar system used by jemalloc
+ * (https://github.com/jemalloc/jemalloc), but instead of having a number of
+ * different sized buckets there is only the one size specified by the user.
+ * This cuts down on complexity and improves performance, at a somewhat major
+ * flexibility cost. Still, this kernel generally only allocates nodes of the
+ * same size again and again, and this approach seems sensible.
+ *
+ *
+ * Quick overview of the allocator, all nodes live in memory pages. Each memory
+ * page has a small header at the front, with some metadata about number of free
+ * and used node slots. When a memory page is filled, a new one is allocated by
+ * the physical memory subsystem and the pages are linked together in a common
+ * list. At the same time, a second linked list is maintained which maintains
+ * which pages have empty slots. When a node is freed, the page it belonged to
+ * is added to the free list (if it didn't already exist there) and when a new
+ * node is requested, the free list is looked through first.
+ *
+ * \todo More in-depth documentation about the node algorithm.
+ */
+
+#include <kmi/mem.h>
+#include <kmi/pmem.h>
+#include <kmi/bits.h>
+#include <kmi/nodes.h>
+#include <kmi/string.h>
+
+/* the structure of each node_region is approximately
+ *
+ * struct node_region | bitmap | array of node_size nodes
+ *
+ * where array starts on a multiple of node_size to ensure alignment and
+ * bitmap is a bitmap of whether node at index is free or used (1 being used, 0
+ * being free)
+ */
+
+/**
+ * Get start of node region from pointer.
+ *
+ * @param r Pointer to node inside node region.
+ * @return Corresponding node region.
+ */
+#define node_region(r) \
+ ((struct node_region *)((uintptr_t)(r) & ~(BASE_PAGE_SIZE - 1)))
+
+/**
+ * Create new node region.
+ *
+ * @return Pointer to created region.
+ */
+static struct node_region *__create_region()
+{
+ struct node_region *r = (struct node_region *)alloc_page(BASE_PAGE);
+ memset(r, FREE, BASE_PAGE_SIZE);
+ return r;
+}
+
+void init_nodes(struct node_root *r, size_t node_size)
+{
+ r->head = __create_region();
+ r->av_head = r->head;
+ r->node_size = node_size;
+ r->bitmap = sizeof(struct node_region);
+
+ /* ideal values */
+ size_t max_nodes = BASE_PAGE_SIZE / node_size;
+ /* make sure not to truncate division */
+ size_t bitmap_size = (max_nodes / 8) + 1;
+ uintptr_t first_node = r->bitmap + bitmap_size;
+ /* actual values */
+ r->first_node = align_up(first_node, node_size);
+ r->max_nodes = max_nodes - (r->first_node / node_size);
+}
+
+void destroy_nodes(struct node_root *r)
+{
+ struct node_region *nr = r->head;
+ while (nr) {
+ struct node_region *d = nr;
+ nr = nr->prev;
+ free_page(BASE_PAGE, (pm_t)d);
+ }
+}
+
+/**
+ * Find free node in node region.
+ *
+ * @param r Node root to work in.
+ * @param nr Node region to look in.
+ * @return Pointer to free node.
+ */
+static void *__find_free_node(struct node_root *r, struct node_region *nr)
+{
+ uint8_t *bitmap = r->bitmap + (uint8_t *)nr;
+ for (size_t i = 0; i < r->max_nodes; ++i) {
+ if (bitmap_is_set(bitmap, i))
+ continue;
+
+ bitmap_set(bitmap, i);
+ return (i * r->node_size) + (r->first_node + (uint8_t *)nr);
+ }
+
+ return 0;
+}
+
+/**
+ * Pop free list head.
+ *
+ * @param r Node region root to work in.
+ */
+static void __pop_av_head(struct node_root *r)
+{
+ struct node_region *t = r->av_head;
+ r->av_head = r->av_head->next;
+ if (r->av_head)
+ r->av_head->av_prev = 0;
+
+ t->av_next = 0;
+ t->av_prev = 0;
+}
+
+void *get_node(struct node_root *r)
+{
+ if (!r)
+ return 0;
+
+ if (!r->av_head) {
+ r->av_head = __create_region();
+
+ r->av_head->prev = r->head;
+ r->head->next = r->av_head;
+
+ r->head = r->av_head;
+ }
+
+ void *p = __find_free_node(r, r->av_head);
+ if (++r->av_head->used_nodes == r->max_nodes)
+ __pop_av_head(r);
+
+ return p;
+}
+
+/**
+ * Push free list head.
+ *
+ * @param r Node region root to work in.
+ * @param nr Node region to push.
+ */
+static void __push_av_head(struct node_root *r, struct node_region *nr)
+{
+ nr->av_prev = 0;
+ nr->av_next = r->av_head;
+ if (r->av_head)
+ r->av_head->av_prev = nr;
+
+ r->av_head = nr;
+}
+
+/**
+ * Free a node region.
+ *
+ * @param r Node region root to work in.
+ * @param nr Node region to free.
+ */
+static void __free_region(struct node_root *r, struct node_region *nr)
+{
+ struct node_region *av_n = nr->av_next;
+ struct node_region *av_p = nr->av_prev;
+
+ if (av_n)
+ av_n->av_prev = av_p;
+
+ if (av_p)
+ av_p->av_next = av_n;
+
+ if (nr == r->av_head)
+ __pop_av_head(r);
+
+ struct node_region *n = nr->next;
+ struct node_region *p = nr->prev;
+
+ if (n)
+ n->prev = p;
+
+ if (p)
+ p->next = n;
+
+ if (nr == r->head) {
+ if (r->head->prev) {
+ r->head->next = 0;
+ r->head = r->head->prev;
+ } else
+ return;
+ }
+
+ free_page(BASE_PAGE, (pm_t)nr);
+}
+
+void free_node(struct node_root *r, void *p)
+{
+ struct node_region *nr = node_region(p);
+ uint8_t *bitmap = r->bitmap + (uint8_t *)nr;
+ size_t i =
+ ((uintptr_t)p - (r->first_node + (uintptr_t)nr)) / r->node_size;
+ bitmap_clear(bitmap, i);
+
+ if (--nr->used_nodes == 0) {
+ __free_region(r, nr);
+ return;
+ }
+
+ else if (!nr->av_next && !nr->av_prev)
+ __push_av_head(r, nr);
+}
diff --git a/src/panic.c b/src/panic.c
new file mode 100644
index 0000000..03c0de8
--- /dev/null
+++ b/src/panic.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file panic.c
+ * Kernel panic handler implementation.
+ */
+
+#include <kmi/power.h>
+#include <kmi/debug.h>
+
+void kernel_panic(void *pc, void *addr, long cause)
+{
+ /* could be useful to print out register values as well? */
+ error("kernel paniced at pc: %p with address %p and cause %lx\n",
+ pc, addr, cause);
+
+ info("attempting to reboot\n");
+
+ poweroff(COLD_REBOOT);
+
+ /* spin if poweroff failed for some reason */
+ error("reboot failed, spinning in place\n");
+ while (1);
+}
diff --git a/src/pmem.c b/src/pmem.c
new file mode 100644
index 0000000..4da3f4c
--- /dev/null
+++ b/src/pmem.c
@@ -0,0 +1,587 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file pmem.c
+ * Physical memory subsystem. Allocates physical memory pages, with support for
+ * different ordered pages, depending on the underlying architecture.
+ *
+ * Effectively, each page order (4096, 2M, 1G...) has a free list of bitmaps for
+ * each page size. When an order runs out of free nodes, it just 'allocates' a
+ * node from a higher order list, and gives out those maps. This turned out to
+ * be around 50% faster than the previous method, with about half the necessary
+ * code. Could still probably be cleaned up a little bit, in particular I don't
+ * really care for probe_pmap() vs populate_pmap() but I suppose it's fine for
+ * now.
+ */
+
+#include <kmi/mem_nodes.h>
+#include <kmi/pmem.h>
+#include <kmi/dmem.h>
+#include <kmi/debug.h>
+#include <kmi/initrd.h>
+#include <kmi/string.h> /* memset */
+#include <kmi/bits.h> /* is_nset etc */
+#include <libfdt.h>
+
+/* \todo add memory page counting?
+ * To make sure memory is not overcommited at clone, for example.
+ */
+
+/**
+ * Loop over orders in reverse, starting with highest, giving the iterator the
+ * name \p iter.
+ *
+ * @param iter Name of iterator.
+ */
+#define reverse_foreach_order(iter) \
+ for (enum mm_order iter = max_order(); iter != MM_MIN; --iter)
+
+/** Page bitmap node */
+struct mm_bmap {
+ /** How many bits this node has. This is generally the same as \c bits in
+ * mm_bucket, but could be used for trailing nodes with some irregular
+ * number of bits. */
+ size_t size;
+
+ /** How many pages are currently used */
+ size_t used;
+
+ /** Next node in freelist */
+ struct mm_bmap *next;
+
+ /** Previous node in freelist */
+ struct mm_bmap *prev;
+
+ /** Actual bitmap */
+ uint8_t bits[];
+};
+
+/** Bucket of bitmaps for some order of pages */
+struct mm_bucket {
+ /** How many bits per (regular) bitmap, see \c size in mm_bmap */
+ size_t bits;
+
+ /** Size in bytes of a page of this order */
+ size_t page_size;
+
+ /** Current head of freelist */
+ struct mm_bmap *head;
+
+ /** Bitmaps in contiguous array, to make populating easier. */
+ struct mm_bmap bmap[];
+};
+
+/** Physical map. */
+struct mm_pmap {
+ /** Base address of our map. Note that this should be the virtual base
+ * address of the physical ram. */
+ pm_t base;
+ /** Buckets, one per order up to maximum order. */
+ struct mm_bucket *buckets[MM_NUM];
+};
+
+/** Static physical map address. \note If I support NUMA, this should probably not be
+ * static, rather one physical map per NUMA region. */
+static struct mm_pmap *pmap = 0;
+
+/**
+ * Zero out memory if \p populate is true.
+ * Helper for populate_pmap(), makes it a bit more easy to follow when we're
+ * just calculating the size of out physical map versus actually building it.
+ *
+ * @param populate Whether to write anything.
+ * @param cont Where to write.
+ * @param size How many bytes to write.
+ * @return Address following last written byte.
+ */
+static pm_t __zero_if(bool populate, pm_t cont, size_t size)
+{
+ if (populate)
+ memset((void *)cont, 0, size);
+
+ return cont + size;
+}
+
+/**
+ * Get size in bytes of (regular) bitmap node in this bucket.
+ *
+ * @param bucket Bucket.
+ * @return Size in bytes of (regular) bitmap node.
+ */
+static size_t __get_set_size(struct mm_bucket *bucket)
+{
+ return sizeof(bucket->bmap[0]) + bucket->bits / 8;
+}
+
+/**
+ * Get pointer to bitmap node at index \p set.
+ *
+ * @param bucket Bucket.
+ * @param set Index of bitmap node to get.
+ * @return Pointer to bitmap node.
+ */
+static struct mm_bmap *__get_set(struct mm_bucket *bucket, size_t set)
+{
+ uintptr_t bmap = (uintptr_t)bucket->bmap;
+ return (struct mm_bmap *)(bmap + __get_set_size(bucket) * set);
+}
+
+/**
+ * Get index from pointer to \p bmap within \p bucket.
+ *
+ * @param bucket Bucket.
+ * @param bmap Bitmap node.
+ * @return Index of \p bmap within \p bucket.
+ */
+static size_t __get_set_index(struct mm_bucket *bucket, struct mm_bmap *bmap)
+{
+ size_t s = (uintptr_t)bmap - (uintptr_t)bucket->bmap;
+ return s / __get_set_size(bucket);
+}
+
+/**
+ * Attach bitmap \p bmap to freelist within \p bucket.
+ *
+ * @param bucket Bucket.
+ * @param bmap Bitmap node.
+ */
+static void __attach_set(struct mm_bucket *bucket, struct mm_bmap *bmap)
+{
+ /* already attached */
+ if (bmap->next)
+ return;
+
+ bmap->next = bucket->head;
+ bucket->head = bmap;
+ if (bmap->next)
+ bmap->next->prev = bmap;
+}
+
+/**
+ * Remove bitmap \p bmap from freelist within \p bucket.
+ *
+ * @param bucket Bucket.
+ * @param bmap Bitmap node.
+ */
+static void __detach_set(struct mm_bucket *bucket, struct mm_bmap *bmap)
+{
+ if (bucket->head == bmap)
+ bucket->head = bmap->next;
+
+ if (bmap->next)
+ bmap->next->prev = bmap->prev;
+
+ if (bmap->prev)
+ bmap->prev->next = bmap->next;
+}
+
+/**
+ * Calculate address of page within bucket.
+ *
+ * @param bucket Bucket.
+ * @param s Index of bitmap node.
+ * @param b Bit within bitmap.
+ * @return Address of corresponding page.
+ */
+static pm_t __page_addr(struct mm_bucket *bucket, size_t s, size_t b)
+{
+ return pmap->base
+ + s * bucket->page_size * bucket->bits
+ + b * bucket->page_size;
+}
+
+/**
+ * Calculate which bitmap node index and bit within bitmap an address
+ * corresponds to.
+ *
+ * @param bucket Bucket.
+ * @param a Address.
+ * @param s Corresponding bitmap node index.
+ * @param b Bitmap node bit index.
+ */
+static void __get_bit(struct mm_bucket *bucket, pm_t a, size_t *s, size_t *b)
+{
+ a -= pmap->base;
+ size_t p = a / bucket->page_size;
+ *s = p / bucket->bits;
+ *b = p % bucket->bits;
+}
+
+void free_page(enum mm_order order, pm_t addr)
+{
+ struct mm_bucket *bucket = pmap->buckets[order];
+ if (!bucket)
+ return;
+
+ size_t set = 0, bit = 0;
+ __get_bit(bucket, addr, &set, &bit);
+
+ struct mm_bmap *bmap = __get_set(bucket, set);
+ bmap->used--;
+
+ bitmap_clear(bmap->bits, bit);
+ __attach_set(bucket, bmap);
+
+ if (bmap->used == 0) {
+ __detach_set(bucket, bmap);
+
+ if (bmap->size == order_width(order + 1))
+ free_page(order + 1, __page_addr(bucket, set, bit));
+ }
+}
+
+pm_t alloc_page(enum mm_order order)
+{
+ struct mm_bucket *bucket = pmap->buckets[order];
+ if (!bucket)
+ return 0;
+
+ struct mm_bmap *bmap = bucket->head;
+ if (!bmap) {
+ pm_t a = alloc_page(order + 1);
+ if (!a)
+ return 0;
+
+ size_t set = 0, bit = 0;
+ __get_bit(bucket, a, &set, &bit);
+
+ bmap = __get_set(bucket, set);
+ bmap->used = 0;
+ bitmap_clear_all(bmap->bits, bmap->size);
+ __attach_set(bucket, bmap);
+ return alloc_page(order);
+ }
+
+ bmap->used++;
+
+ size_t set = __get_set_index(bucket, bmap);
+ size_t bit = bitmap_find_first_unset(bmap->bits, bmap->size);
+ bitmap_set(bmap->bits, bit);
+
+ if (bmap->used == bmap->size)
+ __detach_set(bucket, bmap);
+
+ return __page_addr(bucket, set, bit);
+}
+
+void mark_used(enum mm_order order, pm_t addr)
+{
+ struct mm_bucket *bucket = pmap->buckets[order];
+ if (!bucket)
+ return;
+
+ size_t set = 0, bit = 0;
+ __get_bit(bucket, addr, &set, &bit);
+
+ struct mm_bmap *bmap = __get_set(bucket, set);
+ if (bmap->used == 0) {
+ bitmap_clear_all(bmap->bits, bmap->size);
+ __attach_set(bucket, bmap);
+ mark_used(order + 1, addr);
+ }
+
+ /* a page already in use can just be left alone. This MIGHT hide some
+ * bugs in case two separate things overlap in memory during
+ * initialization, but that scenario should probably be handled outside
+ * of this function anyway. */
+ if (bitmap_is_set(bmap->bits, bit))
+ return;
+
+ bmap->used++;
+ bitmap_set(bmap->bits, bit);
+
+ if (bmap->used == bmap->size)
+ __detach_set(bucket, bmap);
+}
+
+/**
+ * Helper for probing/populating a bucket.
+ *
+ * @param n How many pages in total to account for.
+ * @todo currently may cut off some pages if \p n is larger than but not a multiple of order
+ * width.
+ * @param cont Where to place bucket.
+ * @param order Order of bucket to populate.
+ * @param first First bucket being populated. Top bucket, owns all pages to
+ * start with.
+ * @param populate Whether to actually write bucket to memory.
+ * @return Address right after where last byte of bycket would be.
+ */
+static pm_t __maybe_populate_bucket(size_t n, pm_t cont, enum mm_order order,
+ bool first, bool populate)
+{
+ struct mm_bucket *bucket = (struct mm_bucket *)cont;
+ /* todo max order? */
+ size_t bits = order_width(order + 1);
+ if (bits == 0)
+ bits = n;
+
+ if (populate) {
+ bucket->bits = bits;
+ bucket->page_size = order_size(order);
+ bucket->head = NULL;
+ }
+
+ size_t set_size = sizeof(struct mm_bmap) + bits / 8;
+
+ cont += sizeof(struct mm_bucket);
+
+ size_t sets = n / bits;
+ for (size_t i = 0; i < sets; ++i) {
+ struct mm_bmap *bmap = (struct mm_bmap *)cont;
+ if (populate) {
+ memset(bmap, 0, set_size);
+ bmap->size = bits;
+ }
+
+ if (first && populate)
+ __attach_set(bucket, bmap);
+
+ n -= bits;
+ cont += set_size;
+ }
+
+ if (n) {
+ struct mm_bmap *bmap = (struct mm_bmap *)cont;
+ if (populate)
+ bmap->size = n;
+
+ if (first && populate)
+ __attach_set(bucket, bmap);
+
+ cont += set_size;
+ }
+
+ return cont;
+}
+
+/**
+ * Probe how many bytes the physical map would take up, optionally populate
+ * empty physical map if \p populate is given.
+ *
+ * I realize it sounds like two
+ * different functions, and that it might be a good idea to split in twine,
+ * but my thinking was that using the same algorithm with a
+ * flag to enable writing to memory would decrease chances that I would
+ * calculate the size differently from what is actually needed. We need an
+ * accurate estimate of the pmap size to know whether we can place it somewhere
+ * and not overwrite something else.
+ *
+ * @param ram_base Address in kernel space where the physical RAM starts.
+ * @param ram_size Size of RAM in bytes.
+ * @param start Where to start building pmap.
+ * @param populate Whether to actually write anything out to memory.
+ * @return Size of pmap in bytes.
+ */
+static pm_t __maybe_populate_pmap(pm_t ram_base, size_t ram_size, pm_t start,
+ bool populate)
+{
+ pm_t cont = start;
+
+ pmap = (struct mm_pmap *)start;
+ cont = __zero_if(populate, cont, sizeof(*pmap));
+ if (populate)
+ pmap->base = ram_base;
+
+ bool first = true;
+ reverse_foreach_order(iter) {
+ size_t num = ram_size / order_size(iter);
+ if (num == 0)
+ continue;
+
+ if (populate)
+ pmap->buckets[iter] = (struct mm_bucket *)cont;
+
+ cont = __maybe_populate_bucket(num, cont, iter, first,
+ populate);
+ first = false;
+ }
+
+ return cont - start;
+}
+
+pm_t populate_pmap(pm_t ram_base, size_t ram_size, pm_t start)
+{
+ return __maybe_populate_pmap(ram_base, ram_size, start, true);
+}
+
+pm_t probe_pmap(pm_t ram_base, size_t ram_size, pm_t start)
+{
+ return __maybe_populate_pmap(ram_base, ram_size, start, false);
+}
+
+/**
+ * Helper function for marking area used.
+ *
+ * @param base Base address of area.
+ * @param top Top address of top.
+ */
+static void __mark_area_used(pm_t base, pm_t top)
+{
+ if (top < base) {
+ bug("top < base: %lx < %lx\n", top, base);
+ return;
+ }
+
+ size_t area_left = top - base;
+ pm_t runner = base;
+ while (area_left >= BASE_PAGE_SIZE) {
+ mark_used(BASE_PAGE, runner);
+ runner += BASE_PAGE_SIZE;
+ area_left -= BASE_PAGE_SIZE;
+ }
+
+ if (area_left != 0)
+ mark_used(BASE_PAGE, runner);
+}
+
+/**
+ * Mark reserved memory region used, to avoid it getting accidentally allocated.
+ *
+ * @param fdt Global FDT pointer.
+ */
+static void __mark_reserved_mem(void *fdt)
+{
+ int rmem_offset = fdt_path_offset(fdt, "/reserved-memory");
+ struct cell_info ci = get_cellinfo(fdt, rmem_offset);
+
+ int node = 0;
+ fdt_for_each_subnode(node, fdt, rmem_offset) {
+ uint8_t *rmem_reg =
+ (uint8_t *)fdt_getprop(fdt, node, "reg", NULL);
+
+ pm_t base = (pm_t)fdt_load_reg_addr(ci, rmem_reg, 0);
+
+ /** @todo make sure the top of a reserved memory area doesn't go
+ * against our assumptions in FW_MAX_SIZE? */
+ pm_t top = (pm_t)fdt_load_reg_size(ci, rmem_reg, 0) + base;
+ __mark_area_used((pm_t)__va(base), (pm_t)__va(top));
+ info("marked [%lx - %lx] reserved\n",
+ (pm_t)__va(base), (pm_t)__va(top));
+ }
+}
+
+/**
+ * Read top of RAM from FDT.
+ *
+ * @param fdt Global FDT pointer.
+ * @return Physical address of top of RAM.
+ */
+static pm_t __get_ramtop(void *fdt)
+{
+ int mem_offset = fdt_path_offset(fdt, "/memory");
+ const void *mem_reg = fdt_getprop(fdt, mem_offset, "reg", NULL);
+
+ /* here we actually want the root offset because /memory itself doesn't
+ * have children, I guess? */
+ struct cell_info ci = get_cellinfo(fdt, fdt_path_offset(fdt, "/"));
+ pm_t base = (pm_t)fdt_load_reg_addr(ci, mem_reg, 0);
+ return (pm_t)fdt_load_reg_size(ci, mem_reg, 0) + base;
+}
+
+/**
+ * Read top of FDT.
+ *
+ * @param fdt Global FDT pointer.
+ * @return Physical address of top of FDT.
+ */
+static pm_t __get_fdttop(void *fdt)
+{
+ const char *b = (const char *)fdt;
+ return (pm_t)(b + fdt_totalsize(fdt));
+}
+
+/**
+ * Return base of FDT.
+ *
+ * Technically pretty useless, but here mainly for cohesion.
+ *
+ * @param fdt Global FDT pointer.
+ * @return \c fdt.
+ */
+static pm_t __get_fdtbase(void *fdt)
+{
+ /* lol */
+ return (pm_t)fdt;
+}
+
+void init_pmem(void *fdt)
+{
+ /** @todo should I keep the info outputs? I suppose it's nice to see
+ * if any assumption is being broken in the serial log, but in that case
+ * I should really try adding more of them to other parts of the
+ * codebase as well, the pmem subsystem isn't really especially complex.
+ */
+ info("initializing pmem\n");
+
+ size_t max_order = 0;
+ size_t base_bits = 0;
+ size_t bits[NUM_ORDERS] = { 0 };
+ stat_pmem_conf(fdt, &max_order, &base_bits, bits);
+ init_mem(max_order, bits, base_bits);
+
+ pm_t ram_size = __get_ramtop(fdt) - get_ram_base();
+ pm_t ram_base = (pm_t)__va(get_ram_base());
+
+ info("using ram range [%lx - %lx]\n",
+ ram_base, ram_base + ram_size);
+
+ /** @todo could probably improve error messages on failing to get fdt
+ * values */
+ pm_t initrd_base = get_initrdbase(fdt);
+ pm_t initrd_top = get_initrdtop(fdt);
+ info("found initrd at [%lx - %lx]\n", initrd_base, initrd_top);
+
+ pm_t fdt_top = __get_fdttop(fdt);
+ pm_t fdt_base = __get_fdtbase(fdt);
+ info("found fdt at [%lx - %lx]\n", fdt_base, fdt_top);
+
+ /* find probably most suitable contiguous region of ram for our physical
+ * ram map */
+ /** @todo this really should check that there's enough space in RAM
+ * instead of just forcing the pmap to be populated */
+ pm_t pmap_base = align_up(MAX(initrd_top, fdt_top), BASE_PAGE_SIZE);
+ info("choosing to place pmem map at %lx\n", pmap_base);
+
+ size_t probe_size = probe_pmap(ram_base, ram_size, pmap_base);
+ info("pmem map probe size returned %lu\n", probe_size);
+
+ size_t actual_size = populate_pmap(ram_base, ram_size, pmap_base);
+ info("pmem map actual size %lu\n", actual_size);
+
+ if (probe_size != actual_size) {
+ bug("probe_size (%#lx) != actual_size (%#lx)\n", probe_size,
+ actual_size);
+ }
+
+ /* mark init stack, this should be unmapped once we get to executing
+ * processes */
+ __mark_area_used(VM_STACK_BASE, VM_STACK_TOP);
+ info("marked stack [%lx - %lx] used\n", VM_STACK_BASE, VM_STACK_TOP);
+
+ /* mark kernel */
+ /* this could be made more explicit, I suppose. */
+ __mark_area_used(VM_KERN, VM_KERN + PM_KERN_SIZE);
+ info("marked kernel [%lx - %lx] used\n", VM_KERN,
+ VM_KERN + PM_KERN_SIZE);
+
+ /* mark fdt and initrd */
+ __mark_area_used(initrd_base, initrd_top);
+ info("marked initrd [%lx - %lx] used\n", initrd_base, initrd_top);
+
+ __mark_area_used(fdt_base, fdt_top);
+ info("marked fdt [%lx - %lx] used\n", fdt_base, fdt_top);
+
+ /* mark pmap */
+ __mark_area_used(pmap_base, pmap_base + actual_size);
+ info("marked pmap [%lx - %lx] used\n", pmap_base,
+ pmap_base + actual_size);
+
+ /* mark reserved mem */
+ __mark_reserved_mem(fdt);
+
+ init_mem_nodes();
+
+ init_devmem((pm_t)__pa(ram_base), (pm_t)__pa(ram_base + ram_size));
+}
diff --git a/src/proc.c b/src/proc.c
new file mode 100644
index 0000000..b91c486
--- /dev/null
+++ b/src/proc.c
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file proc.c
+ * Process handling, might be merged into \ref src/tcb.c.
+ */
+
+#include <kmi/elf.h>
+#include <kmi/proc.h>
+#include <kmi/conf.h>
+#include <kmi/string.h>
+#include <kmi/initrd.h>
+#include <arch/arch.h>
+#include <arch/proc.h>
+#include <arch/cpu.h>
+
+stat_t prepare_proc(struct tcb *t, vm_t bin, vm_t interp)
+{
+ vm_t entry = load_elf(t, bin, interp);
+ if (!entry)
+ return ERR_INVAL;
+
+ alloc_stack(t);
+ set_thread(t);
+ set_return(t, entry);
+ return OK;
+}
+
+stat_t init_proc(void *fdt)
+{
+ init_tcbs();
+
+ /** \todo cleanup or something */
+ struct tcb *t = create_proc(NULL);
+ if (!t)
+ return ERR_OOMEM;
+
+ /* we're the first cpu, so we always have ID 0 */
+ t->cpu_id = 0;
+
+ /* force tcb for core */
+ tcb_assign(t);
+
+ /* set current tcb */
+ use_tcb(t);
+
+ /* init process has all capabilities */
+ set_caps(t->caps, 0, CAP_CAPS | CAP_PROC | CAP_CALL | CAP_POWER);
+
+ /* allocate stacks after ELF file to make sure nothing of importance
+ * clashes */
+ return prepare_proc(t, get_init_base(fdt), 0);
+ /** \todo start one thread per core, with special handling for init in
+ * that each thread starts at the entry point of init? */
+}
diff --git a/src/sp_tree.c b/src/sp_tree.c
new file mode 100644
index 0000000..9f69158
--- /dev/null
+++ b/src/sp_tree.c
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file sp_tree.c
+ * Implementation of my sp_trees. An sp_tree is a mix of rb-trees and avl-trees,
+ * with slightly faster insertion but worse tree depth on average.
+ *
+ * See https://github.com/Kimplul/sptree
+ *
+ * \todo Document sp_tree algorithm better.
+ */
+
+#include <kmi/sp_tree.h>
+
+/**
+ * Basic BST left turn.
+ * Drop node \c n down to the left side of the right node, letting it take
+ * the place of \c n.
+ *
+ * @param n Node to turn left.
+ */
+static void __sp_turn_left(struct sp_node *n)
+{
+ struct sp_node *l = sp_left(n);
+ struct sp_node *p = sp_paren(n);
+
+ sp_paren(l) = sp_paren(n);
+ sp_left(n) = sp_right(l);
+ sp_paren(n) = l;
+ sp_right(l) = n;
+
+ if (p && sp_left(p) == n)
+ sp_left(p) = l;
+ else if (p)
+ sp_right(p) = l;
+
+ if (sp_left(n))
+ sp_lparen(n) = n;
+}
+
+/**
+ * Basic BST right turn.
+ * Drop node \c n down to the right side of the left node, letting it take the
+ * place of \c n.
+ *
+ * Does not check if right node exists.
+ *
+ * @param n Node to turn right.
+ */
+static void __sp_turn_right(struct sp_node *n)
+{
+ struct sp_node *r = sp_right(n);
+ struct sp_node *p = sp_paren(n);
+
+ sp_paren(r) = sp_paren(n);
+ sp_right(n) = sp_left(r);
+ sp_paren(n) = r;
+ sp_left(r) = n;
+
+ if (p && sp_left(p) == n)
+ sp_left(p) = r;
+ else if (p)
+ sp_right(p) = r;
+
+ if (sp_right(n))
+ sp_rparen(n) = n;
+}
+
+/**
+ * Calculate approximate balance of node, based on height hints.
+ *
+ * @param n Node to calculate balance for.
+ * @return Balance of node.
+ */
+static int_fast16_t __sp_balance(struct sp_node *n)
+{
+ int_fast16_t l = 0;
+ int_fast16_t r = 0;
+
+ if (sp_left(n))
+ l = sp_left(n)->hint + 1;
+
+ if (sp_right(n))
+ r = sp_right(n)->hint + 1;
+
+ return l - r;
+}
+
+/**
+ * Get highest hint.
+ *
+ * @param n Node to calculate highest hint for.
+ * @return Highest hint.
+ */
+static int_fast16_t __sp_max_hint(struct sp_node *n)
+{
+ int_fast16_t l = 0;
+ int_fast16_t r = 0;
+
+ if (sp_left(n))
+ l = sp_left(n)->hint + 1;
+
+ if (sp_right(n))
+ r = sp_right(n)->hint + 1;
+
+ if (l > r)
+ return l;
+ else
+ return r;
+}
+
+/**
+ * Balance tree, moving up from c n.
+ *
+ * @param root Root of tree.
+ * @param n Node to start balancing operation from.
+ */
+static void __sp_update(struct sp_node **root, struct sp_node *n)
+{
+ while (n) {
+ int b = __sp_balance(n);
+ int prev_hint = n->hint;
+ struct sp_node *p = sp_paren(n);
+
+ if (b < -1) {
+ /* leaning to the right */
+ if (n == *root)
+ *root = sp_right(n);
+
+ __sp_turn_right(n);
+ }
+
+ else if (b > 1) {
+ /* leaning to the left */
+ if (n == *root)
+ *root = sp_left(n);
+
+ __sp_turn_left(n);
+ }
+
+ n->hint = __sp_max_hint(n);
+ if (n->hint == 0 || n->hint != prev_hint)
+ n = p;
+ else
+ return;
+ }
+}
+
+void sp_insert(struct sp_node **root, struct sp_node *p, struct sp_node *n,
+ enum sp_dir d)
+{
+ if (!*root) {
+ *root = n;
+ return;
+ }
+
+ if (d == LEFT)
+ sp_left(p) = n;
+ else
+ sp_right(p) = n;
+
+ sp_paren(n) = p;
+ __sp_update(root, n);
+}
+
+/**
+ * Replace node \c n with \c l, pulling of the righthand side of \n.
+ *
+ * @param n Node to replace.
+ * @param r Node to replace with.
+ */
+static void __sp_replace_right(struct sp_node *n, struct sp_node *r)
+{
+ struct sp_node *p = sp_paren(n);
+ struct sp_node *rp = sp_paren(r);
+
+ if (sp_left(rp) == r) {
+ sp_left(rp) = sp_right(r);
+ if (sp_right(r))
+ sp_rparen(r) = rp;
+ }
+
+ if (sp_paren(rp) == n)
+ sp_paren(rp) = r;
+
+ sp_paren(r) = p;
+ sp_left(r) = sp_left(n);
+
+ if (sp_right(n) != r) {
+ sp_right(r) = sp_right(n);
+ sp_rparen(n) = r;
+ }
+
+ if (p && sp_left(p) == n)
+ sp_left(p) = r;
+ else if (p)
+ sp_right(p) = r;
+
+ if (sp_left(n))
+ sp_lparen(n) = r;
+}
+
+/**
+ * Replace node \c n with node \c l, pulling up the lefthand side of \c n.
+ *
+ * @param n Node to replace.
+ * @param l Node to replace with.
+ */
+static void __sp_replace_left(struct sp_node *n, struct sp_node *l)
+{
+ struct sp_node *p = sp_paren(n);
+ struct sp_node *lp = sp_paren(l);
+
+ if (sp_right(lp) == l) {
+ sp_right(lp) = sp_left(l);
+ if (sp_left(l))
+ sp_lparen(l) = lp;
+ }
+
+ if (sp_paren(lp) == n)
+ sp_paren(lp) = l;
+
+ sp_paren(l) = p;
+ sp_right(l) = sp_right(n);
+
+ if (sp_left(n) != l) {
+ sp_left(l) = sp_left(n);
+ sp_lparen(n) = l;
+ }
+
+ if (p && sp_left(p) == n)
+ sp_left(p) = l;
+ else if (p)
+ sp_right(p) = l;
+
+ if (sp_right(n))
+ sp_rparen(n) = l;
+}
+
+void sp_remove(struct sp_node **root, struct sp_node *del)
+{
+ if (sp_right(del)) {
+ struct sp_node *least = sp_first(sp_right(del));
+
+ if (del == *root)
+ *root = least;
+
+ __sp_replace_right(del, least);
+ __sp_update(root, sp_right(least));
+ return;
+ }
+
+ if (sp_left(del)) {
+ struct sp_node *most = sp_last(sp_left(del));
+
+ if (del == *root)
+ *root = most;
+
+ __sp_replace_left(del, most);
+ __sp_update(root, sp_left(most));
+ return;
+ }
+
+ if (del == *root) {
+ *root = 0;
+ return;
+ }
+
+ /* empty node */
+ struct sp_node *paren = sp_paren(del);
+
+ if (sp_left(paren) == del)
+ sp_left(paren) = 0;
+ else
+ sp_right(paren) = 0;
+
+ __sp_update(root, paren);
+}
+
+struct sp_node *sp_first(struct sp_node *n)
+{
+ if (!sp_left(n))
+ return n;
+
+ return sp_first(sp_left(n));
+}
+
+struct sp_node *sp_last(struct sp_node *n)
+{
+ if (!sp_right(n))
+ return n;
+
+ return sp_last(sp_right(n));
+}
diff --git a/src/string.c b/src/string.c
new file mode 100644
index 0000000..8c88e65
--- /dev/null
+++ b/src/string.c
@@ -0,0 +1,463 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file string.c
+ * Implementations of some string.h stdlib functions.
+ */
+
+#include <kmi/string.h>
+#include <kmi/types.h>
+#include <kmi/attrs.h>
+
+/* we need to undef the macros in string.h, otherwise the names get mangled */
+#undef strcpy
+__weak char *strcpy(char * restrict dst, const char * restrict src)
+{
+ const char *s1 = src;
+ char *s2 = dst;
+
+ while (*s1)
+ *(s2++) = *(s1++);
+
+ return dst;
+}
+
+#undef strncpy
+__weak char *strncpy(char * restrict dst, const char * restrict src, size_t num)
+{
+ const char *s1 = src;
+ char *s2 = dst;
+
+ /* copy s1 into s2 */
+ while (num-- && *s1)
+ *(s2++) = *(s1++);
+
+ /* the previous loop always overshoots by one */
+ num++;
+
+ /* pad with zeroes if num is not yet zero */
+ while (num--)
+ *(s2++) = 0;
+
+ return dst;
+}
+
+#undef strcat
+__weak char *strcat(char * restrict dst, const char * restrict src)
+{
+ const char *s1 = src;
+ size_t l1 = strlen(s1);
+ char *s2 = dst + l1;
+
+ while (*s1)
+ *(s2++) = *(s1++);
+
+ /* append null character */
+ *s2 = 0;
+
+ return dst;
+}
+
+#undef strncat
+__weak char *strncat(char * restrict dst, const char * restrict src, size_t num)
+{
+ const char *s1 = src;
+ size_t l1 = strlen(s1);
+ char *s2 = dst + l1;
+
+ while (num-- && *s1)
+ *(s2++) = *(s1++);
+
+ /* append null character */
+ *s2 = 0;
+
+ return dst;
+}
+
+#undef strcmp
+__weak int strcmp(const char *str1, const char *str2)
+{
+ const char *s1 = (const char *)str1;
+ const char *s2 = (const char *)str2;
+
+ while ((*(s1++) == *(s2++)) && *s1 && *s2)
+ ;
+
+ return (int)(s1[-1] - s2[-1]);
+}
+
+#undef strncmp
+__weak int strncmp(const char *str1, const char *str2, size_t num)
+{
+ const char *s1 = (const char *)str1;
+ const char *s2 = (const char *)str2;
+
+ while ((*(s1++) == *(s2++)) && *s1 && *s2 && --num)
+ ;
+
+ return (int)(s1[-1] - s2[-1]);
+}
+
+#undef strchr
+__weak char *strchr(const char *str, int chr)
+{
+ const char *s1 = str;
+ ssize_t num = strlen(s1);
+
+ while (num-- && *(s1++) != chr)
+ ;
+
+ if (num < 0)
+ return 0;
+
+ return (char *)(s1 - 1);
+}
+
+#undef strtok
+__weak char *strtok(char * restrict str, const char * restrict delims)
+{
+ static char *cont = 0;
+ const char *s1 = str;
+
+ if (!s1)
+ s1 = cont;
+
+ if (!s1)
+ return 0;
+
+ s1 = strpbrk(s1, delims);
+
+ if (!s1)
+ cont = 0;
+ else
+ cont = (char *)s1 + 1;
+
+ return (char *)s1;
+}
+
+/* should probably test out these functions somehwere, blergh */
+#undef strstr
+__weak char *strstr(const char *str1, const char *str2)
+{
+ /* boyer-moore-horspool */
+ char table[256] = { 0 };
+ size_t sl = strlen(str1);
+ size_t pl = strlen(str2);
+
+ const unsigned char *s1 = (const unsigned char *)str1;
+ const unsigned char *haystack = (const unsigned char *)s1;
+ const unsigned char *needle = (const unsigned char *)str2;
+
+ for (size_t i = 0; i < 256; ++i)
+ table[i] = pl;
+
+ /* generate deltas */
+ for (size_t i = 0; i < pl - 1; ++i)
+ table[needle[i]] = pl - i - 1;
+
+ size_t skip = 0;
+ while (sl - skip >= pl) {
+ s1 = &haystack[skip];
+
+ if (!memcmp(s1, needle, pl))
+ return (char *)s1;
+
+ skip += table[haystack[skip + pl - 1]];
+ }
+
+ return 0;
+}
+
+#undef strrchr
+__weak char *strrchr(const char *str, int chr)
+{
+ ssize_t num = strlen(str);
+ const char *s1 = (str + num) - 1;
+
+ while (num-- && *(s1--) != chr)
+ ;
+
+ if (num < 0)
+ return 0;
+
+ return (char *)(s1 + 1);
+}
+
+#undef strpbrk
+__weak char *strpbrk(const char *str1, const char *str2)
+{
+ size_t i = strcspn(str1, str2);
+
+ if (!i)
+ return 0;
+
+ return (char *)(str1 + i);
+}
+
+#undef strcspn
+__weak size_t strcspn(const char *str1, const char *str2)
+{
+ char table[256] = { 0 };
+ const unsigned char *s1 = (const unsigned char *)str1;
+ const unsigned char *s2 = (const unsigned char *)s1;
+ const unsigned char *t1 = (const unsigned char *)str2;
+
+ /* populate table */
+ while (*(t1++))
+ table[*t1] = 1;
+
+ for (;;) {
+ if (table[*(s2++)])
+ break;
+ }
+
+ /* the for loop overshoots by one */
+ return (size_t)(s2 - s1) - 1;
+}
+
+#undef strspn
+__weak size_t strspn(const char *str1, const char *str2)
+{
+ char table[256] = { 0 };
+ const unsigned char *s1 = (const unsigned char *)str1;
+ const unsigned char *s2 = (const unsigned char *)s1;
+ const unsigned char *t1 = (const unsigned char *)str2;
+
+ /* populate table */
+ while (*(t1++))
+ table[*t1] = 1;
+
+ for (;;) {
+ if (!table[*(s2++)])
+ break;
+ }
+
+ /* the for loop overshoots by one */
+ return (size_t)(s2 - s1) - 1;
+}
+
+#undef strlen
+__weak size_t strlen(const char *str)
+{
+ const char *s1 = str;
+ while (*(s1++))
+ ;
+
+ /* the loop overshoots by one */
+ return (size_t)(s1 - str) - 1;
+}
+
+/* not a macro */
+__weak size_t strnlen(const char *str, size_t num)
+{
+ const char *s1 = str;
+ while (num-- && *(s1++))
+ ;
+
+ return (size_t)(s1 - str) - 1;
+}
+
+#undef memset
+__weak void *memset(void *ptr, int value, size_t num)
+{
+ char *p = ptr;
+ char c = value;
+
+ while (num--)
+ *(p++) = c;
+
+ return ptr;
+}
+
+#undef memchr
+__weak void *memchr(const void *ptr, int val, size_t num)
+{
+ const char *p1 = (char *)ptr;
+ ssize_t n = num;
+ char c = (char)val;
+
+ while (n-- && *(p1++) != c)
+ ;
+
+ if (n < 0)
+ return 0;
+
+ return (void *)(p1 - 1);
+}
+
+#undef memcpy
+__weak void *memcpy(void * restrict dst, const void * restrict src, size_t num)
+{
+ const char *m1 = (const char *)src;
+ char *m2 = (char *)dst;
+
+ while (num--)
+ *(m2++) = *(m1++);
+
+ return dst;
+}
+
+#undef memmove
+__weak void *memmove(void *dst, const void *src, size_t num)
+{
+ const char *m1 = (const char *)src;
+ char *m2 = (char *)dst;
+
+ m1 += num;
+ m2 += num;
+
+ /* doesn't really take into account aliasing yet */
+ while (num--)
+ *(--m2) = *(--m1);
+
+ return dst;
+}
+
+#undef memcmp
+__weak int memcmp(const void *ptr1, const void *ptr2, size_t num)
+{
+ const char *p1 = (const char *)ptr1;
+ const char *p2 = (const char *)ptr2;
+
+ while ((*(p1++) == *(p2++)) && --num)
+ ;
+
+ return (int)(p1[-1] - p2[-1]);
+}
+
+/**
+ * Convert ASCII hex character to integer.
+ * Allows both upper- and lowercase letters.
+ *
+ * @param c Character to convert.
+ * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc.
+ * \c -1 if conversion failed.
+ */
+static int __hexval(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return 10 + c - 'a';
+
+ if (c >= 'A' && c <= 'F')
+ return 10 + c - 'A';
+
+ return -1;
+}
+
+/**
+ * Convert string assumed to represent hex
+ * value to corresponding pointer.
+ *
+ * @param s String to convert to value.
+ * @return Corresponding pointer value.
+ */
+static uintptr_t __hexuintptr(const char *s)
+{
+ uintptr_t res = 0;
+ int val = 0;
+ while ((val = __hexval(*(s++))) != -1) {
+ res *= 16;
+ res += val;
+ }
+
+ return res;
+}
+
+/**
+ * Convert ASCII decimal character to integer.
+ *
+ * @param c Character to convert.
+ * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc.
+ * \c -1 if conversion failed.
+ */
+static int __decval(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ return -1;
+}
+
+/**
+ * Convert string assumed to represent decimal
+ * value to corresponding pointer.
+ *
+ * @param s String to convert to value.
+ * @return Corresponding pointer value.
+ */
+static uintptr_t __decuintptr(const char *s)
+{
+ uintptr_t res = 0;
+ int val = 0;
+ while ((val = __decval(*(s++))) != -1) {
+ res *= 10;
+ res += val;
+ }
+
+ return res;
+}
+
+/**
+ * Convert ASCII octal character to integer.
+ *
+ * @param c Character to convert.
+ * @return Corresponding integer value. That is, '1' => 1, '2' => 2, etc.
+ * \c -1 if conversion failed.
+ */
+static int __octval(char c)
+{
+ if (c >= '0' && c <= '7')
+ return c - '0';
+
+ return -1;
+}
+
+/**
+ * Convert string assumed to represent octal
+ * value to corresponding pointer.
+ *
+ * @param s String to convert to value.
+ * @return Corresponding pointer value.
+ */
+static uintptr_t __octuintptr(const char *s)
+{
+ uintptr_t res = 0;
+ int val = 0;
+ while ((val = __octval(*(s++))) != -1) {
+ res *= 8;
+ res += val;
+ }
+
+ return res;
+}
+
+uintptr_t strtouintptr(const char *s)
+{
+ if (!s)
+ return 0;
+
+ if (s[0] == 0)
+ return 0;
+
+ if (s[0] == '0') {
+ if (s[1] == 0)
+ return 0;
+
+ if (s[1] == 'x' || s[1] == 'X')
+ return __hexuintptr(s + 2);
+
+ return __octuintptr(s + 1);
+ }
+
+ if (s[0] == '-')
+ return -__decuintptr(s + 1);
+
+ if (s[0] == '+')
+ return __decuintptr(s + 1);
+
+ return __decuintptr(s);
+}
diff --git a/src/tcb.c b/src/tcb.c
new file mode 100644
index 0000000..c0f82cc
--- /dev/null
+++ b/src/tcb.c
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file tcb.c
+ * Thread control block handling implementation.
+ */
+
+#include <kmi/tcb.h>
+#include <kmi/mem.h>
+#include <kmi/conf.h>
+#include <kmi/pmem.h>
+#include <kmi/vmem.h>
+#include <kmi/nodes.h>
+#include <kmi/types.h>
+#include <kmi/assert.h>
+#include <kmi/string.h>
+#include <kmi/canary.h>
+
+#include <arch/cpu.h>
+#include <arch/vmem.h>
+#include <arch/proc.h>
+
+/* arguably exessively many globals... */
+/** Thread ID to start looking from when allocating new ID. */
+static id_t start_tid = 0;
+
+/** Total number of possible thread IDs. */
+static id_t num_tids;
+
+/** Pointer to array of \ref tcb structures. Length of the array is \c num_tids.*/
+static struct tcb **tcbs;
+
+/**
+ * Array of thread control block associated with each cpu.
+ *
+ * \todo If we ever support systems with massive amounts of cpus, this should probably
+ * be allocated at runtime.
+ */
+static struct tcb *__cpu_tcb[MAX_CPUS] = { 0 };
+
+void init_tcbs()
+{
+ /* MM_O1 is 2MiB on riscv64, so 262144 different possible thread ids.
+ * Should be enough, if we're really strapped for memory I might try
+ * something smaller but this is fine for now. */
+ tcbs = (struct tcb **)alloc_page(MM_O1);
+ num_tids = order_size(MM_O1) / sizeof(struct tcb *);
+ catastrophic_assert(is_powerof2(num_tids));
+ memset(tcbs, 0, order_size(MM_O1));
+}
+
+void destroy_tcbs()
+{
+ free_page(MM_O1, (pm_t)tcbs);
+}
+
+/**
+ * Allocate a new thread ID.
+ *
+ * @param t Thread to allocate new ID to.
+ * @return Allocated ID.
+ */
+static id_t __alloc_tid(struct tcb *t)
+{
+ id_t stop_tid = start_tid - 1;
+ /** \todo this would need some locking or something... */
+ for (id_t i = start_tid;; ++i) {
+ if (i <= 0)
+ i = 1;
+
+ /* we're completely full */
+ if (i == stop_tid)
+ return ERR_NF;
+
+ if (get_tcb(i) || i == 0)
+ continue;
+
+ tcbs[i & (num_tids - 1)] = t;
+ start_tid = i + 1;
+ return i;
+ }
+
+ return ERR_NF;
+}
+
+/**
+ * Setup thread stack.
+ *
+ * @param t Thread to setup stack for.
+ * @param bytes Minimum size of stack.
+ * @return Base of allocated stack.
+ */
+static vm_t __setup_thread_stack(struct tcb *t, size_t bytes)
+{
+ return alloc_uvmem(t, bytes, VM_V | VM_R | VM_W | VM_U);
+}
+
+stat_t alloc_stack(struct tcb *t)
+{
+ /* get parent process */
+ struct tcb *p = get_tcb(t->eid);
+
+ t->thread_stack = __setup_thread_stack(p, __thread_stack_size);
+ if (!t->thread_stack)
+ return ERR_OOMEM;
+
+ /** \todo this only allows for a global stack size, what if a user wants
+ * per thread stack sizes? */
+ t->thread_stack_top = t->thread_stack + __thread_stack_size;
+ return OK;
+}
+
+struct tcb *create_thread(struct tcb *p)
+{
+ hard_assert(tcbs, 0);
+
+ vm_t bottom = alloc_page(KERNEL_STACK_PAGE_ORDER);
+ /* move tcb to top of kernel stack, keeping alignment in check
+ * (hopefully) */
+ /** \todo check alignment */
+ struct tcb *t = (struct tcb *)align_down(
+ bottom + order_size(MM_O0) - sizeof(struct tcb), sizeof(long));
+ memset(t, 0, sizeof(struct tcb));
+
+ id_t tid = __alloc_tid(t);
+ tcbs[tid] = t;
+ t->tid = tid;
+ t->dead = false;
+
+ if (likely(p)) {
+ t->pid = p->pid;
+ /** @todo I'm assuming two threads can share the same vmem
+ * structure, this works on riscv but in the event that other
+ * systems don't we can easily turn this into a clone_uvmem. */
+ t->proc.vmem = p->proc.vmem;
+ } else {
+ init_uvmem(t, UVMEM_START, UVMEM_END);
+ t->proc.vmem = create_vmem();
+ t->pid = t->tid;
+ t->rid = t->tid;
+ p = t;
+ }
+
+ t->eid = t->pid;
+ t->rid = p->rid;
+ t->rpc.vmem = create_vmem();
+ setup_rpc_stack(t);
+ reference_proc(p);
+
+ t->regs = (vm_t)t;
+
+ set_canary(t);
+ return t;
+}
+
+/**
+ * Copy process.
+ *
+ * @param p Parent process.
+ * @param n New process.
+ * @return \ref OK.
+ */
+static stat_t __copy_proc(struct tcb *p, struct tcb *n)
+{
+ /** @todo setup rpc stack stuff */
+ /** @todo I think keeping track of userspace stack stuff is unnecessary,
+ * unless we want unlimited stack size but that sounds dumb. Anycase, we
+ * need to duplicate stack info, whatever we do. */
+ n->exec = p->exec;
+ n->callback = p->callback;
+ n->thread_stack = p->thread_stack;
+ n->thread_stack_top = p->thread_stack_top;
+
+ clone_regs(n, p);
+ copy_caps(n->caps, p->caps);
+ return clone_mem_regions(n, p);
+}
+
+struct tcb *create_proc(struct tcb *p)
+{
+ hard_assert(tcbs, 0);
+
+ /* create a new thread outside the current process */
+ struct tcb *n = create_thread(NULL);
+ if (!n)
+ return 0;
+
+ if (p)
+ __copy_proc(p, n); /* we have a parent thread */
+
+ return n;
+}
+
+/**
+ * Destroy data associated with thread.
+ *
+ * @param t Thread whose data to destroy.
+ * @return \ref OK.
+ */
+static stat_t __destroy_thread_data(struct tcb *t)
+{
+ /* free rpc vmem */
+ destroy_vmem(t->rpc.vmem);
+
+ /* free associated kernel stack and the structure itself */
+ vm_t bottom = align_down((vm_t)t, order_size(MM_O0));
+ free_page(MM_O0, (pm_t)bottom);
+
+ /** \todo free stacks */
+
+ return OK;
+}
+
+stat_t destroy_thread(struct tcb *t)
+{
+ hard_assert(tcbs, ERR_NOINIT);
+ hard_assert(!is_proc(t), ERR_INVAL);
+
+ /* remove thread id from list */
+ /** @todo what about if thread is in rpc? should it rather just be
+ * marked dead? */
+ tcbs[t->tid] = 0;
+
+ /* remove reference to root process */
+ unreference_proc(get_rproc(t));
+
+ return __destroy_thread_data(t);
+}
+
+stat_t destroy_proc(struct tcb *p)
+{
+ hard_assert(tcbs, ERR_NOINIT);
+ hard_assert(is_proc(p), ERR_INVAL);
+
+ p->dead = true;
+ /* unreference ourselves */
+ unreference_proc(p);
+
+ catastrophic_assert(destroy_uvmem(p));
+ return __destroy_thread_data(p);
+}
+
+void reference_proc(struct tcb *p)
+{
+ hard_assert(is_proc(p), RETURN_VOID);
+ p->refcount++;
+}
+
+void unreference_proc(struct tcb *p)
+{
+ hard_assert(is_proc(p), RETURN_VOID);
+ p->refcount--;
+ if (p->dead && p->refcount == 0) {
+ dbg("thread %d is completely destroyed\n", p->tid);
+ /** @todo actually destroy */
+ }
+}
+
+/* weak to allow optimisation on risc-v, but provide fallback for future */
+__weak struct tcb *cur_tcb()
+{
+ return cpu_tcb(cpu_id());
+}
+
+struct tcb *cpu_tcb(id_t cpu_id)
+{
+ return __cpu_tcb[cpu_id];
+}
+
+struct tcb *cur_proc()
+{
+ struct tcb *t = cur_tcb();
+ return get_tcb(t->pid);
+}
+
+struct tcb *eff_proc()
+{
+ struct tcb *t = cur_tcb();
+ return get_tcb(t->eid);
+}
+
+void use_tcb(struct tcb *t)
+{
+ cpu_assign(t);
+
+ __cpu_tcb[t->cpu_id] = t;
+
+ use_vmem(t->proc.vmem);
+}
+
+struct tcb *get_tcb(id_t tid)
+{
+ hard_assert(tcbs, 0);
+
+ if (tid <= 0)
+ return NULL;
+
+ return tcbs[tid & (num_tids - 1)];
+}
+
+void set_return(struct tcb *t, vm_t v)
+{
+ t->exec = v;
+}
+
+bool running(struct tcb *t)
+{
+ return cpu_tcb(t->cpu_id) == t;
+}
diff --git a/src/timer.c b/src/timer.c
new file mode 100644
index 0000000..272ba02
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file timer.c
+ * Timer handling implementation. Currently we only expect an architecture to
+ * support a single timer per core.
+ *
+ * By keeping all timers in a binary search
+ * tree ordered by time, we can just set the single timer to interrupt us when
+ * the next timer is due and with thread info call the thread that set the
+ * timer. From what I can tell, this is largely what Linux does.
+ *
+ * \todo Figure out if there are any advantages to having multiple concurrent
+ * timers.
+ */
+
+#include <kmi/sp_tree.h>
+#include <kmi/string.h>
+#include <kmi/nodes.h>
+#include <kmi/utils.h>
+#include <kmi/timer.h>
+#include <kmi/debug.h>
+#include <arch/timer.h>
+#include <arch/cpu.h>
+
+/** Timer resolution. */
+static ticks_t ticks_per_sec = 0;
+
+/** Array of timer maps for each cpu. */
+static struct sp_root cpu_timers[MAX_CPUS] = { 0 };
+
+/** Timer node subsystem instance. */
+static struct node_root node_root;
+
+/** Node in timer map. */
+struct timer_node {
+ /** Sp tree node. */
+ struct sp_node sp_n;
+
+ /** Corresponding timer. */
+ struct timer timer;
+};
+
+/**
+ * Get \ref timer_node from \ref sp_node.
+ *
+ * @param ptr \ref sp_node whose parent \ref timer_node to get.
+ * @return Corresponding \ref timer_node.
+ */
+#define timer_container(ptr) container_of(ptr, struct timer_node, sp_n)
+
+/**
+ * Get \ref timer_node from \ref timer.
+ *
+ * @param ptr \ref timer whose parent \ref timer_node to get.
+ * @return Corresponding \ref timer_node.
+ */
+#define timer_node_container(ptr) container_of(ptr, struct timer_node, timer)
+
+/**
+ * Get timer map of current cpu.
+ *
+ * @return Root of current cpu's timer map.
+ */
+static struct sp_root *__cpu_timers()
+{
+ return &cpu_timers[cpu_id()];
+}
+
+void init_timer(const void *fdt)
+{
+ ticks_per_sec = stat_timer(fdt);
+ info("ticks_per_sec: %" PRIu64 "\n", ticks_per_sec);
+ info("current ticks: %" PRIu64 "\n", current_ticks());
+ init_nodes(&node_root, sizeof(struct timer_node));
+}
+
+/**
+ * Insert timer into current cpu's timer map.
+ *
+ * \note \c ti.cid might change during the insertion if there already is a node
+ * with identical \c cid to avoid collisions. Very unlikely though.
+ *
+ * @param ti Timer node to insert.
+ * @return \c cid of timer node.
+ */
+static id_t __insert_timer(struct timer_node *ti)
+{
+ struct sp_root *root = __cpu_timers();
+ struct sp_node *n = sp_root(root), *p = NULL;
+ enum sp_dir d = LEFT;
+ while (n) {
+ struct timer_node *t = container_of(n, struct timer_node, sp_n);
+ if (ti->timer.cid == t->timer.cid) {
+ /* if there's an identical ID, we'll just increment our
+ * ID until we get and ID that doesn't exist yet. There
+ * is a very small possibility that this will set a
+ * timer that's very slightly ahead of some other timer
+ * to be handled after the one that's very close, but
+ * the timescales that we're dealing with are probably
+ * tiny enough that this won't matter, even if it
+ * occurs. */
+ ti->timer.cid++;
+ }
+
+ p = n;
+
+ if (ti->timer.cid < t->timer.cid) {
+ n = sp_left(n);
+ d = LEFT;
+ } else {
+ n = sp_right(n);
+ d = RIGHT;
+ }
+ }
+
+ if (sp_root(root))
+ sp_insert(&sp_root(root), p, &ti->sp_n, d);
+ else
+ sp_root(root) = &ti->sp_n;
+
+ return ti->timer.cid;
+}
+
+/**
+ * Create timer at absolute timepoint.
+ *
+ * @param tid Requesting thread ID.
+ * @param ticks Absolute timepoint.
+ * @return \c cid of created timer.
+ */
+static id_t __new_timer(id_t tid, ticks_t ticks)
+{
+ struct timer_node *ti = (struct timer_node *)get_node(&node_root);
+ ti->timer.ticks = ticks;
+ /* preliminary ID, may change after actual insertion */
+ ti->timer.cid = ticks;
+ ti->timer.tid = tid;
+ return __insert_timer(ti);
+}
+
+/* these are likely not perfectly accurate timers due to some random delay from
+ * function calls etc, but probably good enough. */
+id_t new_rel_timer(id_t tid, ticks_t ticks)
+{
+ return new_abs_timer(tid, ticks + current_ticks());
+}
+
+id_t new_abs_timer(id_t tid, ticks_t ticks)
+{
+ id_t id = __new_timer(tid, ticks);
+ set_timer(ticks);
+ return id;
+}
+
+struct timer *newest_timer()
+{
+ struct sp_node *t = sp_first(sp_root(__cpu_timers()));
+ return &timer_container(t)->timer;
+}
+
+struct timer *find_timer(id_t cid)
+{
+ struct sp_node *n = sp_root(__cpu_timers());
+ while (n) {
+ struct timer_node *t = timer_container(n);
+ if (t->timer.cid == cid)
+ return &t->timer;
+
+ if (t->timer.cid < cid)
+ n = sp_left(n);
+ else
+ n = sp_right(n);
+ }
+
+ return 0;
+}
+
+stat_t remove_timer(struct timer *t)
+{
+ if (!t)
+ return ERR_INVAL;
+
+ struct sp_node *n = &timer_node_container(t)->sp_n;
+ sp_remove(&sp_root(__cpu_timers()), n);
+
+ return OK;
+}
+
+ticks_t nsecs_to_ticks(tunit_t nsecs)
+{
+ ticks_t t = (nsecs * ticks_per_sec) / 1000000000;
+ return t == 0 ? 1 : t;
+}
+
+/* call to this function from exception handlers */
+void handle_timer()
+{
+ struct timer *t = newest_timer();
+ remove_timer(t);
+
+ /** \todo handle timer thread ID */
+}
diff --git a/src/uapi/cap.c b/src/uapi/cap.c
new file mode 100644
index 0000000..157ef47
--- /dev/null
+++ b/src/uapi/cap.c
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+#include <kmi/uapi.h>
+#include <kmi/caps.h>
+#include <kmi/tcb.h>
+
+/**
+ * @file cap.c
+ * Sycalls handlers for thread capabilities.
+ */
+
+/**
+ * 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.
+ * @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.
+ * @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)
+{
+ 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);
+
+ set_caps(*c, off, caps);
+ return_args1(t, OK);
+}
+
+/**
+ * Get capabilities.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID whose capabilities to get.
+ * @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)
+{
+ capflags_t *c;
+ if (!(c = __get_tcb_caps(tid, off)))
+ return_args1(t, ERR_INVAL);
+
+ return_args2(t, OK, get_caps(*c, off));
+}
+
+/**
+ * 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 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)
+{
+ 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);
+
+ clear_caps(*c, off, caps);
+ return_args1(t, OK);
+}
diff --git a/src/uapi/conf.c b/src/uapi/conf.c
new file mode 100644
index 0000000..894b98a
--- /dev/null
+++ b/src/uapi/conf.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file conf.c
+ * Runtime configuration sycall implementations.
+ *
+ * At the moment there are not runtime configuration parameters.
+ */
+
+#include <kmi/power.h>
+#include <kmi/sizes.h>
+#include <kmi/uapi.h>
+#include <kmi/conf.h>
+
+#include <arch/proc.h>
+
+/** \todo stack size should really be set on a per-thread basis, and are the
+ * conf*-syscalls even necessary? */
+size_t __thread_stack_size = SZ_2M;
+size_t __rpc_stack_size = SZ_512K;
+
+/** IDs for configuration parameters. */
+/** @todo should probably be moved somewhere so it can be shared with userspace */
+enum conf_param {
+ CONF_THREAD_STACK = 0,
+ CONF_RPC_STACK,
+};
+
+/**
+ * Configuration parameter read syscall handler.
+ *
+ * \todo Implement parameters.
+ *
+ * @param t Current tcb.
+ * @param param Parameter to read.
+ * @return \ref OK and parameter value.
+ */
+SYSCALL_DEFINE1(conf_get)(struct tcb *t, sys_arg_t param)
+{
+ if (!has_cap(t->caps, CAP_CONF))
+ return_args1(t, ERR_PERM);
+
+ long val = 0;
+ switch (param) {
+ case CONF_THREAD_STACK:
+ val = __thread_stack_size;
+ break;
+
+ case CONF_RPC_STACK:
+ val = __rpc_stack_size;
+ break;
+
+ default:
+ return_args1(t, ERR_NF);
+ }
+
+ return_args2(t, OK, val);
+}
+
+/**
+ * Configuration parameter write syscall handler.
+ *
+ * \todo Implement parameters.
+ *
+ * @param t Current tcb.
+ * @param param Parameter to write.
+ * @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)
+{
+ if (!has_cap(t->caps, CAP_CONF))
+ return_args1(t, ERR_PERM);
+
+ size_t size = 0;
+ switch (param) {
+ case CONF_THREAD_STACK:
+ __thread_stack_size = align_up(val, BASE_PAGE_SIZE);
+ break;
+
+ case CONF_RPC_STACK:
+ size = align_up(val, BASE_PAGE_SIZE);
+ if (size > max_rpc_size())
+ return_args1(t, ERR_MISC);
+
+ __rpc_stack_size = size;
+ break;
+ }
+
+ return_args1(t, OK);
+}
+
+/**
+ * Poweroff syscall handler.
+ *
+ * @param t Current tcb.
+ * @param type Type of poweroff.
+ * @return \ref ERR_INVAL and \c 0 if incorrect poweroff \c type give, otherwise
+ * does not return.
+ */
+SYSCALL_DEFINE1(poweroff)(struct tcb *t, sys_arg_t type)
+{
+ if (!(has_cap(t->caps, CAP_POWER)))
+ return_args1(t, ERR_PERM);
+
+ switch (type) {
+ case SHUTDOWN:
+ case COLD_REBOOT:
+ case WARM_REBOOT:
+ return_args2(t, OK, poweroff(type));
+ };
+
+ return_args1(t, ERR_INVAL);
+}
diff --git a/src/uapi/dispatch.c b/src/uapi/dispatch.c
new file mode 100644
index 0000000..5b523cc
--- /dev/null
+++ b/src/uapi/dispatch.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file dispatch.c
+ * Syscall dispatch.
+ */
+
+#include <kmi/canary.h>
+#include <kmi/debug.h>
+#include <kmi/uapi.h>
+
+/* not sure why doxygen requires these two definitions to state their return
+ * values, when they don't actually return anything but eh */
+
+/**
+ * Noop syscall handler.
+ *
+ * @param t Current tcb.
+ *
+ * @return \ref OK and \c 0.
+ */
+SYSCALL_DEFINE0(noop)(struct tcb *t)
+{
+ info("sys_noop\n");
+ set_args1(t, OK);
+}
+
+/**
+ * Putch syscall handler.
+ *
+ * @param t Current tcb.
+ * @param a Character to put.
+ *
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE1(putch)(struct tcb *t, sys_arg_t a)
+{
+ dbg("%c", (char)a);
+ set_args1(t, OK);
+}
+
+void handle_syscall(sys_arg_t syscall, sys_arg_t a, sys_arg_t b,
+ sys_arg_t c, sys_arg_t d, sys_arg_t e, struct tcb *t)
+{
+ adjust_syscall(t);
+
+ switch (syscall) {
+ case SYS_NOOP: sys_noop(t, a, b, c, d, e); break;
+ case SYS_PUTCH: sys_putch(t, a, b, c, d, e); break;
+ case SYS_REQ_MEM: sys_req_mem(t, a, b, c, d, e); break;
+ case SYS_REQ_PAGE: sys_req_page(t, a, b, c, d, e); break;
+ case SYS_REQ_PMEM: sys_req_pmem(t, a, b, c, d, e); break;
+ case SYS_REQ_FIXMEM: sys_req_fixmem(t, a, b, c, d, e); break;
+ case SYS_REQ_SHAREDMEM: sys_req_sharedmem(t, a, b, c, d, e); break;
+ 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_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_IPC_SERVER: sys_ipc_server(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_NOTIFY: sys_ipc_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;
+ case SYS_EXEC: sys_exec(t, a, b, c, d, e); break;
+ 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_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_IRQ_REQ: sys_irq_req(t, a, b, c, d, e); break;
+ default:
+ error("Syscall %zu outside allowed range [0 - %i]\n", syscall,
+ SYS_NUM - 1);
+ set_args1(t, ERR_INVAL);
+ };
+
+ if (check_canary(t)) {
+ bug("Syscall %zu overwrote stack canary\n", syscall);
+ }
+}
diff --git a/src/uapi/ipc.c b/src/uapi/ipc.c
new file mode 100644
index 0000000..cb2ce28
--- /dev/null
+++ b/src/uapi/ipc.c
@@ -0,0 +1,348 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file ipc.c
+ * Interprocess communication syscall implementations.
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/tcb.h>
+#include <kmi/ipi.h>
+#include <kmi/conf.h>
+
+/** Structure for maintaining the required context data for an rpc call. */
+struct call_ctx {
+ /** Execution continuation point. */
+ vm_t exec;
+
+ /** Register save area. */
+ vm_t regs;
+
+ /** Position in rpc stack. */
+ vm_t rpc_stack;
+
+ /** Effective process ID. */
+ id_t eid;
+
+ /** Current process ID. */
+ id_t pid;
+
+ /** Whether this context should be skipped when responding. */
+ bool kick;
+};
+
+/**
+ * Represents difference between where rpc stack was before rpc call and during.
+ * Used to figure out which areas should be marked inaccessible.
+ */
+struct stack_diff {
+ /** Current stack start. Keep in mind that stacks grow down. */
+ vm_t start;
+ /** Difference end, i.e. previous stack start. */
+ vm_t end;
+};
+
+/** Enumerator for IPC kind. Used by do_ipc(). */
+enum ipc_kind {
+ IPC_REQ, IPC_FWD, IPC_KICK
+};
+
+/**
+ * Now that we know we're doing an rpc, clone virtual memories and do
+ * the slow stuff.
+ *
+ * @param t Thread to migrate.
+ * @param r Process to migrate to.
+ * @param s RPC stack regions to mark inaccessible.
+ */
+static void finalize_rpc(struct tcb *t, struct tcb *r, vm_t s)
+{
+ clone_uvmem(r->proc.vmem, t->rpc.vmem);
+ set_return(t, r->callback);
+ reference_proc(r);
+ t->pid = r->rid;
+
+ /* make sure updates are visible when swapping to the new virtual memory */
+ mark_rpc_invalid(t, s);
+ use_vmem(t->rpc.vmem);
+}
+
+/**
+ * Optimistically assume we're going to take the rpc and do some preparations
+ * for it. Most notably, write the arguments as early as possible to
+ * free up registers for the compiler to play with.
+ *
+ * @param t Thread to migrate.
+ * @param a RPC arguments.
+ * @param kind Kind of IPC we're doing. Essentially toggles kick boolean.
+ * @return RPC stack difference that should be passed to finalize_rpc().
+ */
+static vm_t enter_rpc(struct tcb *t, struct sys_ret a,
+ enum ipc_kind kind)
+{
+ vm_t rpc_stack = rpc_position(t);
+
+ struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
+ ctx->regs = t->regs;
+ t->regs = (vm_t)ctx;
+
+ /* try to get rid of args as fast as possible to free up registers for
+ * later use */
+ set_args(t, 6, a);
+
+ ctx->exec = t->exec;
+ ctx->pid = t->pid;
+ ctx->eid = t->eid;
+ ctx->rpc_stack = rpc_stack;
+
+ /* only rpcs can be kicked forward */
+ ctx->kick = kind == IPC_KICK && is_rpc(t);
+
+ /** @todo if we run out of rpc_stack space we should just stop, likely
+ * return a status? except it shouldn't happen after we've run
+ * enough_rpc_stack(). */
+ vm_t new_stack = rpc_stack - BASE_PAGE_SIZE;
+
+ /** @todo what if each stack is only some number of pages, and if a proc
+ * goes over the limit is is seen as programming error? Possibly user
+ * configurable number as well, might actually use the config subsystem
+ * :D
+ * In such a case it would probably be smarter to mark all pages
+ * inaccessible at first, and then mark the first page accessible. If
+ * the process needs more stack space it'll cause a paging exception,
+ * we'll handle it separately and if the process isn't going over the
+ * limit just give it more.
+ * */
+ t->rpc_stack = new_stack;
+ set_stack(t, new_stack);
+ return new_stack;
+}
+
+/**
+ * Jump back to process where rpc came from, assuming such a thing exists.
+ *
+ * @param t Thread to do return migration on.
+ * @param a Arguments to pass along.
+ */
+static void leave_rpc(struct tcb *t, struct sys_ret a)
+{
+ vm_t rpc_stack = t->rpc_stack + BASE_PAGE_SIZE;
+ struct call_ctx *ctx = (struct call_ctx *)(rpc_stack) - 1;
+ vm_t top = ctx->rpc_stack;
+
+ /* find first instance of not kicked context */
+ while (ctx->kick) {
+ rpc_stack = ctx->rpc_stack + BASE_PAGE_SIZE;
+ ctx = (struct call_ctx *)(rpc_stack) - 1;
+ unreference_proc(get_tcb(ctx->pid));
+ }
+
+ t->regs = ctx->regs;
+ /* again, get rid of args as fast as possible */
+ set_args(t, 6, a);
+
+ set_return(t, ctx->exec);
+ /* if we're returning from a failed rpc, this should essentially be a
+ * no-op */
+ mark_rpc_valid(t, top);
+ t->rpc_stack = ctx->rpc_stack;
+ t->pid = ctx->pid;
+ t->eid = ctx->eid;
+
+ if (is_rpc(t))
+ use_vmem(t->rpc.vmem);
+ else
+ use_vmem(t->proc.vmem);
+}
+
+/**
+ * Check that there's enough stack left for an rpc invocation.
+ *
+ * @param t Thread whose migration to check.
+ * @return \c true if there's enough stack left to safely do migration,
+ * \c false otherwise.
+ */
+static bool enough_rpc_stack(struct tcb *t)
+{
+ /* get top of call stack */
+ vm_t top = rpc_position(t);
+
+ /* if we can still fit an rpc stack into the call stack, we can safely
+ * do the migration. */
+ return top - BASE_PAGE_SIZE - __rpc_stack_size >= RPC_STACK_BASE;
+}
+
+/**
+ * IPC server notification syscall handler.
+ *
+ * @param t Current tcb.
+ * @param callback Address of server callback.
+ * @return \ref OK and \c 0.
+ */
+SYSCALL_DEFINE1(ipc_server)(struct tcb *t, sys_arg_t callback)
+{
+ get_cproc(t)->callback = callback;
+ return_args1(t, OK);
+}
+
+/**
+ * Actual IPC syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @param kind Which kind of IPC to perform.
+ *
+ * Returns \ref ERR_OOMEM if there isn't enough IPC stack left, \ref ERR_INVAL
+ * if the the target process doesn't exist, \ref ERR_NOINIT if the target
+ * process hasn't defined a callback. Otherwise \ref OK and whatever the target
+ * process sends back.
+ *
+ * @todo should all static functions have double underscores? I seem to be
+ * inconsistent.
+ */
+static void do_ipc(struct tcb *t,
+ sys_arg_t pid,
+ sys_arg_t d0,
+ sys_arg_t d1,
+ sys_arg_t d2,
+ sys_arg_t d3,
+ enum ipc_kind kind)
+{
+ if (unlikely(!enough_rpc_stack(t)))
+ return_args1(t, ERR_OOMEM);
+
+ vm_t s = enter_rpc(t, SYS_RET6(t->eid, t->tid, d0, d1, d2, d3), kind);
+
+ struct tcb *r = get_tcb(pid);
+ if (unlikely(!r)) {
+ leave_rpc(t, SYS_RET1(ERR_INVAL));
+ return;
+ }
+
+ r = get_rproc(r);
+ if (unlikely(r->dead)) {
+ leave_rpc(t, SYS_RET1(ERR_INVAL));
+ return;
+ }
+
+ if (unlikely(!r->callback)) {
+ leave_rpc(t, SYS_RET1(ERR_NOINIT));
+ return;
+ }
+
+ if (kind != IPC_REQ)
+ t->eid = t->pid;
+
+ finalize_rpc(t, r, s);
+ /* I tested out passing the return values as arguments to
+ * ret_userspace_fast, but apparently that causes enough stack shuffling
+ * to be slower overall. */
+ ret_userspace_fast();
+}
+/**
+ * IPC request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_req)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_REQ);
+}
+
+/**
+ * IPC forwarding syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_fwd)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_FWD);
+}
+
+/**
+ * IPC kicking syscall handler.
+ *
+ * @param t Current tcb.
+ * @param pid Process to request RPC to.
+ * @param d0 IPC argument 0.
+ * @param d1 IPC argument 1.
+ * @param d2 IPC argument 2.
+ * @param d3 IPC argument 3.
+ * @return When succesful: OK, thread id of the caller and the arguments as-is.
+ */
+SYSCALL_DEFINE5(ipc_kick)(struct tcb *t, sys_arg_t pid,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2,
+ sys_arg_t d3)
+{
+ do_ipc(t, pid, d0, d1, d2, d3, IPC_KICK);
+}
+
+/**
+ * IPC response syscall handler.
+ *
+ * @param t Current tcb.
+ * @param d0 IPC return value 0.
+ * @param d1 IPC return value 1.
+ * @param d2 IPC return value 2.
+ * @param d3 IPC return value 3.
+ * @return \c d0 and \c d1.
+ */
+SYSCALL_DEFINE4(ipc_resp)(struct tcb *t, sys_arg_t d0, sys_arg_t d1,
+ sys_arg_t d2,
+ sys_arg_t d3)
+{
+ /* if we're not in an rpc, the user messed something up. */
+ /** @todo choose or come up with more fitting error value. */
+ if (unlikely(!is_rpc(t)))
+ return_args1(t, ERR_MISC);
+
+ leave_rpc(t, SYS_RET6(OK, t->tid, d0, d1, d2, d3));
+}
+
+/**
+ * Notify syscall handler.
+ *
+ * \todo Implement.
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID to notify.
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE1(ipc_notify)(struct tcb *t, sys_arg_t tid){
+ if (!has_cap(t->caps, CAP_CALL))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *r = get_tcb(tid);
+ if (r->notify_state == NOTIFY_QUEUED)
+ return_args1(t, OK);
+
+ if (r->notify_state == NOTIFY_RUNNING) {
+ t->notify_state = NOTIFY_QUEUED;
+ return_args1(t, OK);
+ }
+
+ r->notify_state = NOTIFY_QUEUED;
+ if (running(r))
+ send_ipi(r);
+
+ return_args1(t, OK);
+}
diff --git a/src/uapi/irq.c b/src/uapi/irq.c
new file mode 100644
index 0000000..6687826
--- /dev/null
+++ b/src/uapi/irq.c
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2023, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file irq.c
+ * IRQ request syscall handling implementation.
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/irq.h>
+
+/**
+ * Actual IRQ handling request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param id IRQ id to request to handle.
+ *
+ * @return OK on success, non-zero otherwise.
+ */
+SYSCALL_DEFINE1(irq_req)(struct tcb *t, sys_arg_t id)
+{
+ if (!has_cap(t->caps, CAP_IRQ))
+ return_args1(t, ERR_PERM);
+
+ if (!t->callback)
+ return_args1(t, ERR_NF);
+
+ return_args1(t, register_irq(t, id));
+}
diff --git a/src/uapi/mem.c b/src/uapi/mem.c
new file mode 100644
index 0000000..d54390a
--- /dev/null
+++ b/src/uapi/mem.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file mem.c
+ * Memory handling syscall implementations.
+ * \todo Should we return more error information?
+ */
+
+#include <kmi/uapi.h>
+#include <kmi/utils.h>
+#include <kmi/vmem.h>
+#include <kmi/dmem.h>
+
+/**
+ * Memory request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param size Minimum size of allocation.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE2(req_mem)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ /** @todo expose flags to users */
+ if (!(start = alloc_uvmem(r, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Allocate single page to program.
+ *
+ * @param t Current tcb.
+ * @param size Size of the allocation.
+ * @param flags Flags of allocation.
+ * @return \ref ERR_OOMEM if unsucessful, otherwise \ref OK, virtual address,
+ * actual size, physical address, in that order.
+ */
+SYSCALL_DEFINE2(req_page)(struct tcb *t, sys_arg_t size, sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0; pm_t paddr = 0; size_t asize = size;
+ if (!(start = alloc_uvpage(r, asize, flags, &asize, &paddr)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args4(t, OK, start, asize, paddr);
+}
+
+/**
+ * Fixed memory request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param fixed Address which should be included in allocation.
+ * @param size Minimum size of allocation after \c start.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE3(req_fixmem)(struct tcb *t, sys_arg_t fixed, sys_arg_t size,
+ sys_arg_t flags)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ if (!(start = alloc_fixed_uvmem(r, fixed, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Free memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param start Start of allocation to free.
+ * @return \ref OK and \c 0 when succesful, \ref ERR_NF and \c 0 otherwise.
+ */
+SYSCALL_DEFINE1(free_mem)(struct tcb *t, sys_arg_t start)
+{
+ struct tcb *r = get_cproc(t);
+ vm_t vm_start = (vm_t)start;
+
+ stat_t status = OK;
+ /* try freeing normal user memory first, if that fails, try device
+ * memory, otherwise just assume the address is borked. */
+ if (!(status = free_uvmem(r, vm_start)))
+ return_args1(t, OK);
+
+ if (!(status = free_devmem(r, vm_start)))
+ return_args1(t, OK);
+
+ return_args1(t, status);
+}
+
+/**
+ * Request physical memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param paddr Physical address to map.
+ * @param size Minimum size of allocation.
+ * @param flags Flags of allocation.
+ * @return \ref OK and start of allocation when succesful,
+ * \ref ERR_OOMEM and \c NULL otherwise.
+ */
+SYSCALL_DEFINE3(req_pmem)(struct tcb *t, sys_arg_t paddr, sys_arg_t size,
+ sys_arg_t flags)
+{
+ /* this will require some pondering, but essentially this syscall should
+ * only be used for device access, so any addresses requested should be
+ * outside the RAM area, and I'll probably have to implement some method
+ * that keeps track of used regions outside of RAM. We'll see.
+ */
+ struct tcb *r = get_cproc(t);
+ vm_t start = 0;
+ if (!(start = alloc_devmem(r, paddr, size, flags)))
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, OK, start);
+}
+
+/**
+ * Request shared memory syscall handler.
+ *
+ * @param t Current tcb.
+ * @param tid Thread to share memory with.
+ * @param size Minimum size of allocation.
+ * @param sflags Flags of allocation for \p t.
+ * @param cflags Flags of allocation for \p tid.
+ * @return \ref OK and start of \p t allocation and start of \p tid allocation,
+ * in that order, \ref ERR_OOMEM otherwise.
+ *
+ * @todo should we also take the thread who should get the other end of the
+ * memory?
+ */
+SYSCALL_DEFINE4(req_sharedmem)(struct tcb *t, sys_arg_t tid,
+ sys_arg_t size, sys_arg_t sflags,
+ sys_arg_t cflags)
+{
+ /** @todo check capability for shared memory */
+ struct tcb *u = get_tcb(tid);
+ if (!u)
+ return_args1(t, ERR_INVAL);
+
+ struct tcb *s = get_cproc(t);
+ struct tcb *c = get_rproc(u);
+
+ vm_t sstart, cstart;
+ if (alloc_shared_uvmem(s, c, size, sflags, cflags, &sstart, &cstart))
+ return_args1(t, ERR_OOMEM);
+
+ return_args3(t, OK, sstart, cstart);
+}
+
+/** \todo add some way to specify who gets to access the shared memory? */
diff --git a/src/uapi/proc.c b/src/uapi/proc.c
new file mode 100644
index 0000000..e7fa49d
--- /dev/null
+++ b/src/uapi/proc.c
@@ -0,0 +1,188 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file proc.c
+ * Process/thread handling syscall implementations.
+ */
+
+#include <kmi/elf.h>
+#include <kmi/uapi.h>
+#include <kmi/proc.h>
+#include <kmi/bits.h>
+#include <kmi/mem_regions.h>
+
+/**
+ * Create syscall handler.
+ *
+ * @param t Current tcb.
+ * @param func Function to jump to at thread creation.
+ * @param d0 Argument 0.
+ * @param d1 Argument 1.
+ * @param d2 Argument 2.
+ * @param d3 Argument 3.
+ *
+ * @return ERR_OOMEM if thread creation was unsuccessful, otherwise OK and the
+ * thread id.
+ */
+SYSCALL_DEFINE5(create)(struct tcb *t, sys_arg_t func,
+ sys_arg_t d0, sys_arg_t d1, sys_arg_t d2, sys_arg_t d3)
+{
+ struct tcb *c = create_thread(t);
+ if (!c)
+ return_args1(t, ERR_OOMEM);
+
+ alloc_stack(c);
+
+ set_args5(c, c->tid, d0, d1, d2, d3);
+ set_return(c, func);
+
+ return_args2(t, OK, c->tid);
+}
+
+/**
+ * Fork syscall handler.
+ *
+ * \todo Not entirely sure how I should handle forks/execs etc, mostly whether I
+ * should allow forks/execs to be called directly or only though the process
+ * manager. Probably though the process manager, although that will add in a
+ * slight bit of delay.
+ *
+ * I suppose I could add in a runtime parameter
+ * that would allow them to be called directly, and then the process manager
+ * would have to periodically ask the kernel about all threads it is aware of
+ * via sys_sync. Dunno.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and 0.
+ */
+SYSCALL_DEFINE0(fork)(struct tcb *t)
+{
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *n = create_proc(get_eproc(t));
+ if (!n)
+ return_args1(t, ERR_OOMEM);
+
+ /* 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);
+
+ return_args2(t, OK, n->pid);
+}
+
+/**
+ * Exec syscall handler.
+ *
+ * @param t Current tcb.
+ * @param bin Binary to execute.
+ * @param interp Optional interpreter binary.
+ *
+ * @return \see prepare_proc().
+ */
+SYSCALL_DEFINE2(exec)(struct tcb *t, sys_arg_t bin, sys_arg_t interp)
+{
+ /** @todo probably make sure thread is root thread of process? */
+ /* mark binary to be kept */
+ struct mem_region *b = find_used_region(&t->sp_r, bin);
+ if (!b)
+ return_args1(t, ERR_INVAL);
+
+ set_bit(b->flags, MR_KEEP);
+
+ struct mem_region *i = 0;
+ if (interp) {
+ /* mark interpreter to be kept */
+ i = find_used_region(&t->sp_r, interp);
+ if (!i)
+ return_args1(t, ERR_INVAL);
+
+ set_bit(i->flags, MR_KEEP);
+ }
+
+ /* free everything except regions to be kept */
+ clear_uvmem(t);
+
+ /* restore to normal */
+ clear_bit(b->flags, MR_KEEP);
+ if (interp)
+ clear_bit(b->flags, MR_KEEP);
+
+ return_args1(t, prepare_proc(t, bin, interp));
+}
+
+/**
+ * Spawn syscall handler.
+ *
+ * @param t Current tcb.
+ * @param bin Binary to execute.
+ * @param interp Optional interpreter binary.
+ *
+ * @return \see prepare_proc() and process id of the new process.
+ */
+SYSCALL_DEFINE2(spawn)(struct tcb *t, sys_arg_t bin, sys_arg_t interp)
+{
+ struct tcb *c = get_proc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *n = create_proc(NULL);
+ if (!n)
+ return_args1(t, ERR_OOMEM);
+
+ return_args2(t, prepare_proc(n, bin, interp), n->pid);
+}
+
+/**
+ * Kill syscall handler.
+ *
+ * @param t Current tcb.
+ * @param tid Thread to kill.
+ * \todo Implement.
+ *
+ * @return ERR_PERM if not capable to kill, otherwise OK.
+ */
+SYSCALL_DEFINE1(kill)(struct tcb *t, sys_arg_t tid)
+{
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ /** @todo implement */
+ /** @todo remember to unregister IRQ handlers */
+
+ return_args1(t, OK);
+}
+
+/**
+ * Swap syscall handler.
+ *
+ * \todo Implement.
+ * \todo Should swap return the registers of the new thread that would be used
+ * for message passing?
+ *
+ * @param t Current tcb.
+ * @param tid Thread ID to swap to.
+ *
+ * @return \ref OK.
+ */
+SYSCALL_DEFINE1(swap)(struct tcb *t, sys_arg_t tid){
+ struct tcb *c = get_cproc(t);
+ if (!(has_cap(c->caps, CAP_PROC)))
+ return_args1(t, ERR_PERM);
+
+ struct tcb *s = get_tcb(tid);
+ if (!s)
+ return_args1(t, ERR_INVAL);
+
+ /* switch over to new thread */
+ use_tcb(s);
+
+ /* set return value for current thread */
+ set_args1(t, OK);
+
+ /* get register state for new thread */
+ return_args(s, get_args(s));
+}
diff --git a/src/uapi/timers.c b/src/uapi/timers.c
new file mode 100644
index 0000000..b119fcc
--- /dev/null
+++ b/src/uapi/timers.c
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file timers.c
+ * Timer syscall implementations.
+ */
+
+#include <kmi/timer.h>
+#include <kmi/uapi.h>
+
+#include <arch/timer.h>
+
+/**
+ * Convert arch-specific register values \p ticks and \p mult to \ref ticks_t.
+ *
+ * If we're on a 32bit system, one register can't contain a tick value,
+ * so we use two registers and combine them into one value and let the compiler
+ * handle the rest.
+ *
+ * @param ticks Register width tick value.
+ * @param mult Register width repeat value.
+ * @return Corresponding \ref ticks_t value.
+ */
+static ticks_t scaled_ticks(sys_arg_t ticks, sys_arg_t mult)
+{
+#if defined(_LP64)
+ UNUSED(mult);
+ return ticks;
+#else
+ return (ticks_t)ticks * (ticks_t)mult;
+#endif
+}
+
+/**
+ * Timebase syscall handler.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and timebase in second argument if 64bit, otherwise high 32
+ * bits of timebase in second argument and low 32 bits in third argument.
+ */
+SYSCALL_DEFINE0(timebase)(struct tcb *t)
+{
+ ticks_t tm = secs_to_ticks(1);
+#if defined(_LP64)
+ return_args2(t, OK, tm);
+#else
+ return_args3(t, OK, tm >> 32, tm);
+#endif
+}
+
+/**
+ * Current ticks syscall handler.
+ *
+ * Note that most platforms allow user level read access to hardware timers, and
+ * should be preferred over this syscall on such platforms. Still, for
+ * completeness sake.
+ *
+ * @param t Current tcb.
+ * @return \ref OK and the current ticks when on 64bit systems, otherwise high
+ * 32 bits of ticks in second argument and low 32 bits in third.
+ */
+SYSCALL_DEFINE0(ticks)(struct tcb *t)
+{
+ ticks_t tm = current_ticks();
+#if defined(_LP64)
+ return_args2(t, OK, tm);
+#else
+ return_args3(t, OK, tm >> 32, tm);
+#endif
+}
+
+/**
+ * Relative timer request syscall handler.
+ *
+ * \note On 64bit systems, \p mult is ignored as \p ticks register is large
+ * enough to contain essentially any timepoint we want. A couple thousand years
+ * when the clock runs at 5GHz, if I'm not completely mistaken.
+ *
+ * @param t Current tcb.
+ * @param ticks Number of ticks from now.
+ * @param mult Multiply \c ticks by this value.
+ * @return \ref OK and \c cid of created timer.
+ */
+SYSCALL_DEFINE2(req_rel_timer)(struct tcb *t, sys_arg_t ticks, sys_arg_t mult)
+{
+ return_args2(t, OK, new_rel_timer(t->tid, scaled_ticks(ticks, mult)));
+}
+
+/**
+ * Absolute timer request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param ticks Absolute timepoint relative to some start point defined at boot.
+ * @param mult Multiply \c ticks by this value.
+ * @return \ref OK and \c cid of created timer.
+ * \see req_rel_timer().
+ */
+SYSCALL_DEFINE2(req_abs_timer)(struct tcb *t, sys_arg_t ticks, sys_arg_t mult)
+{
+ return_args2(t, OK, new_abs_timer(t->tid, scaled_ticks(ticks, mult)));
+}
+
+/**
+ * Free timer request syscall handler.
+ *
+ * @param t Current tcb.
+ * @param cid \c cid of timer to free.
+ * @return \ref ERR_NF and \c 0if no timer could be found with \c cid, \ref OK
+ * and 0 otherwise.
+ */
+SYSCALL_DEFINE1(free_timer)(struct tcb *t, sys_arg_t cid)
+{
+ struct timer *timer = find_timer(cid);
+ if (!timer)
+ return_args1(t, ERR_NF);
+
+ remove_timer(timer);
+ return_args1(t, OK);
+}
diff --git a/src/vmem.c b/src/vmem.c
new file mode 100644
index 0000000..f52f43e
--- /dev/null
+++ b/src/vmem.c
@@ -0,0 +1,378 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file vmem.c
+ * Virtual memory handling, mainly userspace virtual memory.
+ */
+
+#include <kmi/mem_regions.h>
+#include <kmi/assert.h>
+#include <kmi/string.h>
+#include <kmi/debug.h>
+#include <kmi/bits.h>
+#include <kmi/vmem.h>
+#include <arch/vmem.h>
+
+stat_t init_uvmem(struct tcb *t, vm_t base, vm_t top)
+{
+ return init_region(&t->sp_r, base, top);
+}
+
+/**
+ * Clone process memory region.
+ *
+ * @param d Destination tcb.
+ * @param s Source tcb.
+ * @param m Memory region to clone.
+ * @return \ref ERR_MISC if clone failed, otherwise \ref OK.
+ *
+ * @todo check shared memory regions.
+ */
+static stat_t __clone_mapped_region(struct tcb *d, struct tcb *s,
+ struct mem_region *m)
+{
+ vm_t start = m->start * order_size(BASE_PAGE);
+ vm_t end = m->end * order_size(BASE_PAGE);
+
+ size_t size = end - start, actual_size = 0;
+ vm_t va = alloc_fixed_region(&d->sp_r, start, size,
+ &actual_size, m->flags);
+
+ catastrophic_assert(va == start);
+
+ if (!copy_allocd_region(d->proc.vmem, va, size, m->flags, s->proc.vmem))
+ return ERR_MISC;
+
+ return OK;
+}
+
+/**
+ * Unmap and free private memory region.
+ *
+ * @param t Current thread.
+ * @param m Memory region to free.
+ * @return \see unmap_freed_region().
+ */
+static stat_t __free_mapped_private_region(struct tcb *t, struct mem_region *m)
+{
+ stat_t status = OK;
+ pm_t start = __addr(m->start);
+ pm_t end = __addr(m->end);
+ if (!unmap_freed_region(t->proc.vmem, start, end - start, m->flags,
+ &status))
+ return ERR_MISC;
+
+ return status;
+}
+
+/**
+ * Check whether process associated with shared memory is still using it.
+ *
+ * @param pid Process to check.
+ * @param start Start of memory region
+ * @return \ref true if it is still in use, \ref false otherwise.
+ */
+static bool __proc_has_region(id_t pid, vm_t start)
+{
+ /** @todo this has a slight potential to have a race condition, where
+ * both threads want to free the same shared region at the same time. */
+ struct tcb *p = get_tcb(pid);
+ if (!p)
+ return false;
+
+ struct mem_region *m = find_used_region(&p->sp_r, start);
+ if (!m)
+ return false;
+
+ return true;
+}
+
+/**
+ * Unmap shared region and free associated physical pages if they're not being
+ * used by the other process.
+ *
+ * @param t Current thread.
+ * @param m Memory region to free.
+ * @return \see unmap_vpage().
+ */
+static stat_t __free_mapped_shared_region(struct tcb *t, struct mem_region *m)
+{
+ vm_t start = __addr(m->start);
+ bool in_use = __proc_has_region(m->pid, m->alt_va);
+
+ size_t osize = order_size(BASE_PAGE);
+ size_t pages = m->start - m->end;
+
+ stat_t status = OK;
+ for (size_t i = 0; i < pages; ++i) {
+ vm_t va = start + i * osize;
+
+ pm_t pa = 0;
+ stat_vpage(t->proc.vmem, va, &pa, 0, 0);
+ status = unmap_vpage(t->proc.vmem, va);
+
+ if (!in_use)
+ free_page(pa, BASE_PAGE);
+ }
+
+ return status;
+}
+
+/**
+ * Convenience function for freeing mapped regions.
+ *
+ * @param t Thread to work in.
+ * @param m Memory region to free.
+ * @return \ref OK
+ */
+static stat_t __free_mapped_region(struct tcb *t, struct mem_region *m)
+{
+ if (m->pid != 0)
+ return __free_mapped_shared_region(t, m);
+
+ return __free_mapped_private_region(t, m);
+}
+
+stat_t clear_uvmem(struct tcb *t)
+{
+ struct mem_region *m = find_first_region(&t->sp_r);
+ while (m) {
+ if (!is_region_kept(m))
+ __free_mapped_region(t, m);
+
+ m = m->next;
+ }
+
+ return OK;
+}
+
+stat_t purge_uvmem(struct tcb *t)
+{
+ struct mem_region *m = find_first_region(&t->sp_r);
+ while (m) {
+ __free_mapped_region(t, m);
+ m = m->next;
+ }
+
+ return OK;
+}
+
+stat_t destroy_uvmem(struct tcb *t)
+{
+ /* force clear all regions */
+ purge_uvmem(t);
+ /* destroy region tree itself */
+ return destroy_region(&t->sp_r);
+}
+
+stat_t clone_mem_regions(struct tcb *d, struct tcb *s)
+{
+ /** @todo implement some way to only iterate used regions, this loops
+ * through all regions which is likely a slight bit slower. */
+ struct mem_region *m = find_first_region(&s->sp_r);
+ while (m) {
+ if (is_region_used(m))
+ __clone_mapped_region(d, s, m);
+
+ m = m->next;
+ }
+
+ return OK;
+}
+
+vm_t alloc_uvmem(struct tcb *t, size_t size, vmflags_t flags)
+{
+ /* t exists and is the process tcb of the current process */
+ hard_assert(t && is_proc(t), ERR_INVAL);
+
+ stat_t status = OK;
+ const vm_t v = alloc_region(&t->sp_r, size, &size, flags);
+ const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status);
+ return w;
+}
+
+vm_t alloc_uvpage(struct tcb *t, size_t size, vmflags_t flags, size_t *asize,
+ pm_t *paddr)
+{
+ hard_assert(t && is_proc(t), ERR_INVAL);
+
+ enum mm_order order = nearest_order(size);
+ size_t actual_size = order_size(order);
+ stat_t status = OK;
+
+ const vm_t v = alloc_region(&t->sp_r, size, &size, flags);
+ const vm_t w = __addr(__page(v));
+
+ pm_t addr = alloc_page(order);
+ /** @todo should free region */
+ if (!addr)
+ return NULL;
+
+ status = map_vpage(t->proc.vmem, addr, w, flags, order);
+ if (status)
+ return NULL;
+
+ if (asize)
+ *asize = actual_size;
+
+ if (paddr)
+ *paddr = addr;
+
+ return w;
+}
+
+vm_t alloc_fixed_uvmem(struct tcb *t, vm_t start, size_t size, vmflags_t flags)
+{
+ hard_assert(t && is_proc(t), ERR_INVAL);
+
+ stat_t status = OK;
+ const vm_t v = alloc_fixed_region(&t->sp_r, start, size, &size, flags);
+ const vm_t w = map_allocd_region(t->proc.vmem, v, size, flags, &status);
+ return w;
+}
+
+/* free_shared_uvmem shouldn't be needed, likely to work with free_uvmem */
+stat_t alloc_shared_uvmem(struct tcb *s, struct tcb *c,
+ size_t size, vmflags_t sflags, vmflags_t cflags,
+ vm_t *sstart, vm_t *cstart)
+{
+ hard_assert(sstart, ERR_INVAL);
+ hard_assert(cstart, ERR_INVAL);
+ hard_assert(s && is_proc(s), ERR_INVAL);
+ hard_assert(c && is_proc(c), ERR_INVAL);
+
+ size_t ssize, csize;
+ vm_t sv = alloc_shared_region(&s->sp_r, size, &ssize, sflags, c->rid);
+ vm_t cv = alloc_shared_region(&c->sp_r, size, &csize, cflags, s->rid);
+
+ /* not exactly optimal but good enough for now, I can start worrying
+ * about hyperoptimizations whenever. */
+ set_alt_region_addr(&s->sp_r, sv, cv);
+ set_alt_region_addr(&c->sp_r, cv, sv);
+
+ if (csize != ssize) {
+ /** @todo cleanup, better errors? */
+ return ERR_INVAL;
+ }
+
+ stat_t cstatus = OK, sstatus = OK;
+ size_t osize = order_size(BASE_PAGE);
+ size_t pages = ssize / osize;
+ for (size_t i = 0; i < pages; ++i) {
+ pm_t p = alloc_page(BASE_PAGE);
+ sstatus = map_vpage(s->proc.vmem, p, sv + i * osize, sflags,
+ BASE_PAGE);
+ cstatus = map_vpage(c->proc.vmem, p, cv + i * osize, cflags,
+ BASE_PAGE);
+ }
+
+ *sstart = sv;
+ *cstart = cv;
+
+ if (sstatus)
+ return sstatus;
+
+ if (cstatus)
+ return cstatus;
+
+ return OK;
+}
+
+stat_t free_uvmem(struct tcb *r, vm_t va)
+{
+ /** \todo assume tcb is root tcb? */
+ struct mem_region *m = find_used_region(&r->sp_r, va);
+ if (!m)
+ return ERR_NF;
+
+ stat_t status = __free_mapped_region(r, m);
+ if (status)
+ return ERR_MISC;
+
+ return free_known_region(&r->sp_r, m);
+}
+
+stat_t alloc_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order, void *data)
+{
+ *offset = alloc_page(order);
+ if (!*offset)
+ return INFO_TRGN; /* try again */
+
+ stat_t *status = (stat_t *)data, ret;
+ ret = map_vpage(b, *offset, vaddr, flags, order);
+ if (status)
+ *status = ret;
+
+ return ret;
+}
+
+stat_t alloc_shared_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order, void *data)
+{
+ if (order != MM_O0)
+ return INFO_TRGN;
+
+ *offset = alloc_page(MM_O0);
+
+ stat_t *status = (stat_t *)data, ret;
+ ret = map_vpage(b, *offset, vaddr, flags, order);
+ if (status)
+ *status = ret;
+
+ return ret;
+}
+
+stat_t copy_allocd_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order, void *data)
+{
+ struct vmem *s = (struct vmem *)data;
+
+ pm_t paddr = 0;
+ enum mm_order v_order = 0;
+ stat_vpage(s, vaddr, &paddr, &v_order, 0);
+ /** @todo what if we could combine multiple pages into one in the new
+ * process? */
+ if (order > v_order)
+ return INFO_TRGN;
+
+ pm_t new_page = alloc_page(order);
+ if (!new_page)
+ return INFO_TRGN;
+
+ map_vpage(b, new_page, vaddr, flags, order);
+ memcpy((void *)new_page, (void *)(paddr + *offset), order_size(order));
+
+ if (v_order > order)
+ *offset += order_size(order);
+ else
+ *offset = 0;
+
+ return OK;
+}
+
+stat_t free_uvmem_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr,
+ vmflags_t flags, enum mm_order order, void *data)
+{
+ UNUSED(flags);
+ UNUSED(offset);
+
+ pm_t paddr = 0;
+ enum mm_order v_order = 0;
+ stat_vpage(b, vaddr, &paddr, &v_order, 0);
+ if (order != v_order)
+ return INFO_TRGN;
+
+ /** @todo we might need to cause an ipi to flush the tlb for other
+ * cores */
+
+ stat_t *status = (stat_t *)data, ret;
+ ret = unmap_vpage(b, vaddr);
+ if (status)
+ *status = ret;
+
+ free_page(order, paddr);
+
+ return ret;
+}