From bc600ecc3bdf0f189861dfb840f70c2339a7a853 Mon Sep 17 00:00:00 2001 From: Kimplul Date: Fri, 24 May 2024 13:24:27 +0300 Subject: 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 --- Makefile | 12 +- common/bits.c | 62 ---- common/canary.c | 42 --- common/debug.c | 786 ------------------------------------------------- common/dispatch.c | 35 --- common/dmem.c | 154 ---------- common/elf.c | 156 ---------- common/fdt.c | 15 - common/initrd.c | 167 ----------- common/ipi.c | 35 --- common/irq.c | 67 ----- common/main.c | 63 ---- common/mem.c | 59 ---- common/mem_nodes.c | 40 --- common/mem_regions.c | 587 ------------------------------------ common/nodes.c | 221 -------------- common/panic.c | 25 -- common/pmem.c | 587 ------------------------------------ common/proc.c | 56 ---- common/sp_tree.c | 295 ------------------- common/string.c | 463 ----------------------------- common/tcb.c | 310 ------------------- common/timer.c | 204 ------------- common/uapi/cap.c | 94 ------ common/uapi/conf.c | 115 -------- common/uapi/dispatch.c | 89 ------ common/uapi/ipc.c | 348 ---------------------- common/uapi/irq.c | 29 -- common/uapi/mem.c | 158 ---------- common/uapi/proc.c | 188 ------------ common/uapi/timers.c | 120 -------- common/vmem.c | 378 ------------------------ docs/doxygen.conf | 2 +- src/bits.c | 62 ++++ src/canary.c | 42 +++ src/debug.c | 786 +++++++++++++++++++++++++++++++++++++++++++++++++ src/dispatch.c | 35 +++ src/dmem.c | 154 ++++++++++ src/elf.c | 156 ++++++++++ src/fdt.c | 15 + src/initrd.c | 167 +++++++++++ src/ipi.c | 35 +++ src/irq.c | 67 +++++ src/main.c | 63 ++++ src/mem.c | 59 ++++ src/mem_nodes.c | 40 +++ src/mem_regions.c | 587 ++++++++++++++++++++++++++++++++++++ src/nodes.c | 221 ++++++++++++++ src/panic.c | 25 ++ src/pmem.c | 587 ++++++++++++++++++++++++++++++++++++ src/proc.c | 56 ++++ src/sp_tree.c | 295 +++++++++++++++++++ src/string.c | 463 +++++++++++++++++++++++++++++ src/tcb.c | 310 +++++++++++++++++++ src/timer.c | 204 +++++++++++++ src/uapi/cap.c | 94 ++++++ src/uapi/conf.c | 115 ++++++++ src/uapi/dispatch.c | 89 ++++++ src/uapi/ipc.c | 348 ++++++++++++++++++++++ src/uapi/irq.c | 29 ++ src/uapi/mem.c | 158 ++++++++++ src/uapi/proc.c | 188 ++++++++++++ src/uapi/timers.c | 120 ++++++++ src/vmem.c | 378 ++++++++++++++++++++++++ 64 files changed, 5954 insertions(+), 5956 deletions(-) delete mode 100644 common/bits.c delete mode 100644 common/canary.c delete mode 100644 common/debug.c delete mode 100644 common/dispatch.c delete mode 100644 common/dmem.c delete mode 100644 common/elf.c delete mode 100644 common/fdt.c delete mode 100644 common/initrd.c delete mode 100644 common/ipi.c delete mode 100644 common/irq.c delete mode 100644 common/main.c delete mode 100644 common/mem.c delete mode 100644 common/mem_nodes.c delete mode 100644 common/mem_regions.c delete mode 100644 common/nodes.c delete mode 100644 common/panic.c delete mode 100644 common/pmem.c delete mode 100644 common/proc.c delete mode 100644 common/sp_tree.c delete mode 100644 common/string.c delete mode 100644 common/tcb.c delete mode 100644 common/timer.c delete mode 100644 common/uapi/cap.c delete mode 100644 common/uapi/conf.c delete mode 100644 common/uapi/dispatch.c delete mode 100644 common/uapi/ipc.c delete mode 100644 common/uapi/irq.c delete mode 100644 common/uapi/mem.c delete mode 100644 common/uapi/proc.c delete mode 100644 common/uapi/timers.c delete mode 100644 common/vmem.c create mode 100644 src/bits.c create mode 100644 src/canary.c create mode 100644 src/debug.c create mode 100644 src/dispatch.c create mode 100644 src/dmem.c create mode 100644 src/elf.c create mode 100644 src/fdt.c create mode 100644 src/initrd.c create mode 100644 src/ipi.c create mode 100644 src/irq.c create mode 100644 src/main.c create mode 100644 src/mem.c create mode 100644 src/mem_nodes.c create mode 100644 src/mem_regions.c create mode 100644 src/nodes.c create mode 100644 src/panic.c create mode 100644 src/pmem.c create mode 100644 src/proc.c create mode 100644 src/sp_tree.c create mode 100644 src/string.c create mode 100644 src/tcb.c create mode 100644 src/timer.c create mode 100644 src/uapi/cap.c create mode 100644 src/uapi/conf.c create mode 100644 src/uapi/dispatch.c create mode 100644 src/uapi/ipc.c create mode 100644 src/uapi/irq.c create mode 100644 src/uapi/mem.c create mode 100644 src/uapi/proc.c create mode 100644 src/uapi/timers.c create mode 100644 src/vmem.c diff --git a/Makefile b/Makefile index 13fd1b5..161290e 100644 --- a/Makefile +++ b/Makefile @@ -25,10 +25,8 @@ setup: ARCH ?= riscv64 ARCH_SOURCE = arch/$(ARCH) -# might consider renaming common, currently it refers to stuff common -# to all arches but clearly there are bits that are common to init and kernel -KERNEL_SOURCES != echo common/*.c common/uapi/*.c lib/*.c -INIT_SOURCES != echo lib/fdt*.c common/fdt.c common/string.c +KERNEL_SOURCES != echo src/*.c src/uapi/*.c lib/*.c +INIT_SOURCES != echo lib/fdt*.c src/fdt.c src/string.c CLEANUP := build deps.mk kernel.* init.* kmi.bin CLEANUP_CMD := @@ -37,17 +35,17 @@ include arch/$(ARCH)/source.mk .PHONY: format format: - find arch lib common include -iname '*.[ch]' |\ + find arch lib src include -iname '*.[ch]' |\ xargs uncrustify -c uncrustify.conf --no-backup -F - .PHONY: license license: - find arch lib common include -iname '*.[ch]' |\ + find arch lib src include -iname '*.[ch]' |\ xargs ./scripts/license .PHONY: docs docs: - find arch lib common include -iname '*.[ch]' -not -path */gen/* |\ + find arch lib src include -iname '*.[ch]' -not -path */gen/* |\ xargs ./scripts/warn-undocumented doxygen docs/doxygen.conf diff --git a/common/bits.c b/common/bits.c deleted file mode 100644 index 6462d21..0000000 --- a/common/bits.c +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 -#include -#include -#include - -#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/common/canary.c b/common/canary.c deleted file mode 100644 index 4f1f8ba..0000000 --- a/common/canary.c +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include -#include - -/** - * @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/common/debug.c b/common/debug.c deleted file mode 100644 index fcec537..0000000 --- a/common/debug.c +++ /dev/null @@ -1,786 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include - -#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/common/dispatch.c b/common/dispatch.c deleted file mode 100644 index 2786678..0000000 --- a/common/dispatch.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include -#include - -/** - * @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/common/dmem.c b/common/dmem.c deleted file mode 100644 index bbabe09..0000000 --- a/common/dmem.c +++ /dev/null @@ -1,154 +0,0 @@ -/* 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 -#include - -/** 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/common/elf.c b/common/elf.c deleted file mode 100644 index da47502..0000000 --- a/common/elf.c +++ /dev/null @@ -1,156 +0,0 @@ -/* 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 -#include -#include -#include -#include - -/** - * 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/common/fdt.c b/common/fdt.c deleted file mode 100644 index 677be7b..0000000 --- a/common/fdt.c +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 - -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/common/initrd.c b/common/initrd.c deleted file mode 100644 index caff8b3..0000000 --- a/common/initrd.c +++ /dev/null @@ -1,167 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include - -/** 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/common/ipi.c b/common/ipi.c deleted file mode 100644 index 59f9fd1..0000000 --- a/common/ipi.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include -#include - -#include - -/** - * @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/common/irq.c b/common/irq.c deleted file mode 100644 index db90d34..0000000 --- a/common/irq.c +++ /dev/null @@ -1,67 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include - -/** 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/common/main.c b/common/main.c deleted file mode 100644 index 4647fed..0000000 --- a/common/main.c +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * 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/common/mem.c b/common/mem.c deleted file mode 100644 index e3f98e7..0000000 --- a/common/mem.c +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 -#include -#include -#include - -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/common/mem_nodes.c b/common/mem_nodes.c deleted file mode 100644 index 358a68d..0000000 --- a/common/mem_nodes.c +++ /dev/null @@ -1,40 +0,0 @@ -/* 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 - * common/mem_regions.c. - * - * Each region of memory is allocated through a \ref mem_region node, which is - * allocated through the node subsystem. - */ - -#include -#include -#include -#include -#include - -/** 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/common/mem_regions.c b/common/mem_regions.c deleted file mode 100644 index 9468774..0000000 --- a/common/mem_regions.c +++ /dev/null @@ -1,587 +0,0 @@ -/* 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 -#include -#include -#include -#include - -/** - * 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/common/nodes.c b/common/nodes.c deleted file mode 100644 index 5edb783..0000000 --- a/common/nodes.c +++ /dev/null @@ -1,221 +0,0 @@ -/* 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 -#include -#include -#include -#include - -/* 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/common/panic.c b/common/panic.c deleted file mode 100644 index 03c0de8..0000000 --- a/common/panic.c +++ /dev/null @@ -1,25 +0,0 @@ -/* 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 -#include - -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/common/pmem.c b/common/pmem.c deleted file mode 100644 index 4da3f4c..0000000 --- a/common/pmem.c +++ /dev/null @@ -1,587 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include /* memset */ -#include /* is_nset etc */ -#include - -/* \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/common/proc.c b/common/proc.c deleted file mode 100644 index e6c2769..0000000 --- a/common/proc.c +++ /dev/null @@ -1,56 +0,0 @@ -/* 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 common/tcb.c. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -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/common/sp_tree.c b/common/sp_tree.c deleted file mode 100644 index 9f69158..0000000 --- a/common/sp_tree.c +++ /dev/null @@ -1,295 +0,0 @@ -/* 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 - -/** - * 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/common/string.c b/common/string.c deleted file mode 100644 index 8c88e65..0000000 --- a/common/string.c +++ /dev/null @@ -1,463 +0,0 @@ -/* 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 -#include -#include - -/* 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/common/tcb.c b/common/tcb.c deleted file mode 100644 index c0f82cc..0000000 --- a/common/tcb.c +++ /dev/null @@ -1,310 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/* 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/common/timer.c b/common/timer.c deleted file mode 100644 index 272ba02..0000000 --- a/common/timer.c +++ /dev/null @@ -1,204 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include - -/** 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/common/uapi/cap.c b/common/uapi/cap.c deleted file mode 100644 index 157ef47..0000000 --- a/common/uapi/cap.c +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2023 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -#include -#include -#include - -/** - * @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/common/uapi/conf.c b/common/uapi/conf.c deleted file mode 100644 index 894b98a..0000000 --- a/common/uapi/conf.c +++ /dev/null @@ -1,115 +0,0 @@ -/* 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 -#include -#include -#include - -#include - -/** \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/common/uapi/dispatch.c b/common/uapi/dispatch.c deleted file mode 100644 index 5b523cc..0000000 --- a/common/uapi/dispatch.c +++ /dev/null @@ -1,89 +0,0 @@ -/* SPDX-License-Identifier: copyleft-next-0.3.1 */ -/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ - -/** - * @file dispatch.c - * Syscall dispatch. - */ - -#include -#include -#include - -/* 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/common/uapi/ipc.c b/common/uapi/ipc.c deleted file mode 100644 index cb2ce28..0000000 --- a/common/uapi/ipc.c +++ /dev/null @@ -1,348 +0,0 @@ -/* 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 -#include -#include -#include - -/** 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/common/uapi/irq.c b/common/uapi/irq.c deleted file mode 100644 index 6687826..0000000 --- a/common/uapi/irq.c +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 -#include - -/** - * 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/common/uapi/mem.c b/common/uapi/mem.c deleted file mode 100644 index d54390a..0000000 --- a/common/uapi/mem.c +++ /dev/null @@ -1,158 +0,0 @@ -/* 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 -#include -#include -#include - -/** - * 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/common/uapi/proc.c b/common/uapi/proc.c deleted file mode 100644 index e7fa49d..0000000 --- a/common/uapi/proc.c +++ /dev/null @@ -1,188 +0,0 @@ -/* 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 -#include -#include -#include -#include - -/** - * 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/common/uapi/timers.c b/common/uapi/timers.c deleted file mode 100644 index b119fcc..0000000 --- a/common/uapi/timers.c +++ /dev/null @@ -1,120 +0,0 @@ -/* 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 -#include - -#include - -/** - * 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/common/vmem.c b/common/vmem.c deleted file mode 100644 index f52f43e..0000000 --- a/common/vmem.c +++ /dev/null @@ -1,378 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include - -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; -} diff --git a/docs/doxygen.conf b/docs/doxygen.conf index 8735a27..af3be32 100644 --- a/docs/doxygen.conf +++ b/docs/doxygen.conf @@ -945,7 +945,7 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = arch \ - common \ + src \ include \ lib \ docs \ 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 +#include +#include +#include + +#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 +#include + +/** + * @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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +/** + * @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 +#include + +/** 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 +#include +#include +#include +#include + +/** + * 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 + +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 +#include +#include +#include +#include +#include +#include + +/** 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 +#include + +#include + +/** + * @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 +#include +#include +#include +#include +#include + +/** 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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 +#include +#include +#include + +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 +#include +#include +#include +#include + +/** 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 +#include +#include +#include +#include + +/** + * 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 +#include +#include +#include +#include + +/* 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 +#include + +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 +#include +#include +#include +#include +#include /* memset */ +#include /* is_nset etc */ +#include + +/* \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 +#include +#include +#include +#include +#include +#include +#include + +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 + +/** + * 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 +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include +#include + +/** 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 +#include +#include + +/** + * @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 +#include +#include +#include + +#include + +/** \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 +#include +#include + +/* 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 +#include +#include +#include + +/** 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 +#include + +/** + * 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 +#include +#include +#include + +/** + * 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 +#include +#include +#include +#include + +/** + * 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 +#include + +#include + +/** + * 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 +#include +#include +#include +#include +#include +#include + +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; +} -- cgit v1.3