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