Commit Diff


commit - /dev/null
commit + 77fdb85750d58975361feae858277cc65c1906d5
blob - /dev/null
blob + 8d8ef3c2eba2dcd44f5d2ff4f5832380787b6a47 (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,24 @@
+BSD 2-Clause License
+
+Copyright (c) 2025, nastonen
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
blob - /dev/null
blob + 50a8320ca074316ca0d0f01836c3890a5b2dfa43 (mode 644)
--- /dev/null
+++ README.md
@@ -0,0 +1,8 @@
+# SystemN
+My take on RISC-V UNIX clone.<br />
+<br />
+Install qemu-system-riscv64<br />
+cd kernel<br />
+make clean<br />
+./build_shell.sh<br />
+make run [NODEBUG=1]<br />
blob - /dev/null
blob + 96e84e62e4bcdaa099ff01f9dad5d103b73050a3 (mode 644)
--- /dev/null
+++ kernel/Makefile
@@ -0,0 +1,70 @@
+# Toolchain prefix
+CROSS = riscv64-elf-
+
+# Tools
+CC = $(CROSS)gcc
+LD = $(CROSS)ld
+OBJCOPY = $(CROSS)objcopy
+OBJDUMP = $(CROSS)objdump
+
+# Files
+OBJS = entry.o start.o uart.o spinlock.o proc.o trap/trap.o \
+       trap/trap_vec.o string.o sched.o swtch.o shell.o mm/mem.o \
+       mm/snub.o mm/pagetable.o syscall.o fs/vfs.o
+TARGET = kernel
+LINKER = kernel.ld
+
+# Flags
+CFLAGS = -march=rv64g -mabi=lp64 -nostdlib -fno-pic -mno-relax \
+         -mcmodel=medany -Wall -Wextra -O0 $(DEBUG_FLAGS)
+#-g
+
+ifeq ($(NODEBUG), 1)
+DEBUG_FLAGS =
+else
+DEBUG_FLAGS = -DDEBUG
+endif
+
+LDFLAGS = -T $(LINKER)
+
+#all: $(TARGET).bin
+
+# Compile .c and .S files
+%.o: %.c
+	$(CC) $(CFLAGS) -c $< -o $@
+
+%.o: %.S
+	$(CC) $(CFLAGS) -c $< -o $@
+
+# Link the ELF
+$(TARGET).elf: $(OBJS) $(LINKER)
+	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS)
+
+# Convert to raw binary
+#$(TARGET).bin: $(TARGET).elf
+#	$(OBJCOPY) -O binary $< $@
+
+# Disassemble (optional)
+dump: $(TARGET).elf
+	$(OBJDUMP) -D $(TARGET).elf > $(TARGET).asm
+
+# Run in QEMU
+run: $(TARGET).elf
+	qemu-system-riscv64 -machine virt \
+                        -smp 4 \
+                        -bios none \
+                        -nographic \
+                        -cpu rv64,zihintpause=true \
+                        -monitor none \
+                        -serial stdio \
+                        -display none \
+                        -kernel $(TARGET).elf
+#                        -d int -D log.txt
+#                        -d int,cpu -D log.txt
+#                        -S -gdb tcp::1234
+
+clean:
+	rm -f $(shell find . -name '*.o' -o -name '*.elf' -o -name '*.bin' -o -name '*.asm')
+
+
+.PHONY: all clean dump run
blob - /dev/null
blob + 33070ad973dc761fb7297d6a155f8c18ef66ed5e (mode 644)
--- /dev/null
+++ kernel/TODO
@@ -0,0 +1,4 @@
+- 'write' syscall to print whole string at a time
+- schedule on timer interrupts
+- console driver + buffered printk
+- non-linear mm mapping
blob - /dev/null
blob + 9a3ad2fdac2044ddf23e56e538675405cced97a9 (mode 755)
--- /dev/null
+++ kernel/build_shell.sh
@@ -0,0 +1,9 @@
+#!/bin/zsh
+
+riscv64-elf-gcc -nostdlib -T user.ld -o shell.elf shell.c
+riscv64-elf-objcopy -O binary -j .text shell.elf shell.bin
+riscv64-elf-ld -r -b binary -o shell.o shell.bin
+
+#riscv64-elf-nm shell.elf | grep ' T '
+#-T user.ld
+#-Ttext=0x80200000 -e _start
blob - /dev/null
blob + 296c044be5939c8f28ea1a638288d826cdbb5668 (mode 644)
--- /dev/null
+++ kernel/entry.S
@@ -0,0 +1,23 @@
+.section .text
+.global _entry
+_entry:
+    # Set up a stack for C
+    # sp = _stack + ((hartId + 1) * 4096)
+    csrr  t0, mhartid
+    addi  t0, t0, 1
+    li    t1, 4096
+    mul   t0, t0, t1
+    la    t1, _stack
+    add   sp, t1, t0
+
+    call start
+
+spin:
+    j spin
+
+# Stack section: 4KB × 4 harts = 16KB
+.section .bss
+.align 16
+.global _stack
+_stack:
+    .space 4096 * 4
blob - /dev/null
blob + d59d6cfe8e223c7acc5d7ac01e7ee972982ddfe8 (mode 644)
--- /dev/null
+++ kernel/fs/ramfs.c
@@ -0,0 +1,135 @@
+#include "ramfs.h"
+
+struct ramfs_file {
+    char name[MAX_NAME_LEN];
+    char *data;
+    ulong size;
+    uint mode;  // VFS_MODE_FILE or VFS_MODE_DIR
+    inode_t *inode;
+} ramfs_file_t;
+
+static ramfs_file_t file_table[MAX_OPEN_FILES];
+static ulong next_inode_no = 1;
+
+static filesystem_t ramfs_fs;
+static inode_t root_inode;
+
+static ramfs_file_t *
+ramfs_find(const char *name)
+{
+    for (int i = 0; i < RAMFS_MAX_FILES; i++)
+        if (file_table[i].inode && strcmp(file_table[i].name, name) == 0)
+            return &file_table[i];
+
+    return NULL;
+}
+
+static inode_t *
+ramfs_lookup(inode_t *dir, const char *name)
+{
+    ramfs_file_t *f = ramfs_find(name);
+    return f ? f->inode : NULL;
+}
+
+static int
+ramfs_read(inode_t *inode, ulong offset, void *buf, ulong len)
+{
+    ramfs_file_t *f = (ramfs_file_t *)inode->internal;
+    if (!f || f->mode != VFS_MODE_FILE)
+        return -1;
+
+    if (offset >= f->size)
+        return 0;
+
+    ulong to_read = (offset + len > f->size) ? (f->size - offset) : len;
+    memcpy(buf, f->data + offset, to_read);
+
+    return to_read;
+}
+
+static int
+ramfs_write(inode_t *inode, ulong offset, const void *buf, ulong len)
+{
+    ramfs_file_t *f = (ramfs_file_t *)inode->internal;
+    if (!f || f->mode != VFS_MODE_FILE)
+        return -1;
+
+    ulong new_size = offset + len;
+    if (new_size > f->size) {
+        char *new_data = realloc(f->data, new_size);
+        if (!new_data)
+            return -1;
+
+        f->data = new_data;
+        f->size = new_size;
+        inode->size = new_size;
+    }
+
+    memcpy(f->data + offset, buf, len);
+
+    return len;
+}
+
+static int
+ramfs_create(inode_t *dir, const char *name, uint mode)
+{
+    for (int i = 0; i < RAMFS_MAX_FILES; i++) {
+        if (!file_table[i].inode) {
+            ramfs_file_t *f = &file_table[i];
+            strncpy(f->name, name, RAMFS_NAME_LEN);
+            f->mode = mode;
+            f->data = NULL;
+            f->size = 0;
+
+            inode_t *inode = malloc(sizeof(inode_t));
+            if (!inode)
+                return -1;
+
+            inode->inode_no = next_inode_no++;
+            inode->mode = mode;
+            inode->size = 0;
+            inode->fs = &ramfs_fs;
+            inode->internal = f;
+
+            f->inode = inode;
+
+            return 0;
+        }
+    }
+
+    return -1; // No space
+}
+
+static int
+ramfs_mkdir(inode_t *dir, const char *name)
+{
+    // For now, don't support nested directories
+    return -1;
+}
+
+static filesystem_ops_t ramfs_ops = {
+    .lookup = ramfs_lookup,
+    .read   = ramfs_read,
+    .write  = ramfs_write,
+    .create = ramfs_create,
+    .mkdir  = ramfs_mkdir
+};
+
+filesystem_t *
+ramfs_create(void)
+{
+    memset(&file_table, 0, sizeof(file_table));
+    memset(&root_inode, 0, sizeof(inode_t));
+
+    root_inode.inode_no = 0;
+    root_inode.mode = VFS_MODE_DIR;
+    root_inode.size = 0;
+    root_inode.fs = &ramfs_fs;
+    root_inode.internal = NULL;
+
+    ramfs_fs.name = "ramfs";
+    ramfs_fs.ops = &ramfs_ops;
+    ramfs_fs.root = &root_inode;
+
+    return &ramfs_fs;
+}
blob - /dev/null
blob + c21e2c2107084ed33dd6e58aa89c9a5d32e1cddd (mode 644)
--- /dev/null
+++ kernel/fs/ramfs.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "vfs.h"
+
+filesystem_t *ramfs_create(void);
blob - /dev/null
blob + 4ea43ad095fb441e96d52332837c4906668bf68b (mode 644)
--- /dev/null
+++ kernel/fs/vfs.c
@@ -0,0 +1,129 @@
+#include "vfs.h"
+#include "../mm/snub.h"
+
+static filesystem_t *root_fs;
+static file_t *open_files[MAX_OPEN_FILES];
+
+int
+vfs_install_fd(file_t *f)
+{
+    for (int i = 0; i < MAX_OPEN_FILES; i++) {
+        if (open_files[i] == NULL) {
+            open_files[i] = f;
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+file_t *
+vfs_get_file(int fd)
+{
+    if (fd < 0 || fd >= MAX_OPEN_FILES)
+        return NULL;
+
+    return open_files[fd];
+}
+
+void
+vfs_init(void)
+{
+    // Currently nothing
+}
+
+int
+vfs_mount(filesystem_t *fs)
+{
+    if (!fs || !fs->root)
+        return -1;
+
+    root_fs = fs;
+
+    return 0;
+}
+
+inode_t *
+vfs_lookup(const char *path)
+{
+    if (!path || path[0] != '/' || !root_fs)
+        return NULL;
+
+    inode_t *curr = root_fs->root;
+    char temp[MAX_PATH_LEN];
+    strlcpy(temp, path, MAX_PATH_LEN);
+    char *token = strtok(temp, "/");
+
+    while (token && curr && (curr->mode & VFS_MODE_DIR)) {
+        curr = curr->fs->ops->lookup(curr, token);
+        token = strtok(NULL, "/");
+    }
+
+    return curr;
+}
+
+int
+vfs_open(const char *path)
+{
+    inode_t *node = vfs_lookup(path);
+    if (!node)
+        return -1;
+
+    file_t *f = (file_t *)kmalloc(sizeof(file_t));
+    if (!f)
+        return -1;
+
+    f->inode = node;
+    f->offset = 0;
+    f->flags = 0;
+
+    return vfs_install_fd(f);
+}
+
+int
+vfs_read(int fd, void* buf, ulong len)
+{
+    file_t *f = vfs_get_file(fd);
+    if (!f || !f->inode || !(f->inode->mode & VFS_MODE_FILE))
+        return -1;
+
+    int n = f->inode->fs->ops->read(f->inode, f->offset, buf, len);
+    if (n > 0)
+        f->offset += n;
+
+    return n;
+}
+
+int
+vfs_write(int fd, const void *buf, ulong len)
+{
+    file_t *f = vfs_get_file(fd);
+    if (!f || !f->inode || !(f->inode->mode & VFS_MODE_FILE))
+        return -1;
+
+    int n = f->inode->fs->ops->write(f->inode, f->offset, buf, len);
+    if (n > 0)
+        f->offset += n;
+
+    return n;
+}
+
+int
+vfs_create(const char *path, uint mode)
+{
+    if (!path || !root_fs)
+        return -1;
+
+    const char *name = path + 1;  // Assume single-level path for now
+    return root_fs->ops->create(root_fs->root, name, mode);
+}
+
+int
+vfs_mkdir(const char *path)
+{
+    if (!path || !root_fs)
+        return -1;
+
+    const char *name = path + 1;
+    return root_fs->ops->mkdir(root_fs->root, name);
+}
blob - /dev/null
blob + 6e83c0638b3493093cfb91bb7d054e212255855f (mode 644)
--- /dev/null
+++ kernel/fs/vfs.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "../string.h"
+
+#define MAX_NAME_LEN     64
+#define MAX_PATH_LEN    256
+#define MAX_OPEN_FILES   64
+
+// File types and mode flags
+#define VFS_MODE_DIR  0x4000
+#define VFS_MODE_FILE 0x8000
+
+// Filesystem operation table
+typedef struct filesystem_ops {
+    struct inode *(*lookup)(struct inode *dir, const char *name);
+    int (*read)(struct inode *inode, ulong offset, void *buf, ulong len);
+    int (*write)(struct inode *inode, ulong offset, const void *buf, ulong len);
+    int (*create)(struct inode *dir, const char *name, uint mode);
+    int (*mkdir)(struct inode *dir, const char *name);
+} filesystem_ops_t;
+
+// Core inode structure
+typedef struct inode {
+    ulong inode_no;
+    ulong size;
+    uint mode;
+    struct filesystem *fs;
+    void *internal;  // FS-specific pointer
+} inode_t;
+
+// Directory entry
+typedef struct dentry {
+    char name[MAX_NAME_LEN];
+    struct inode *inode;
+} dentry_t;
+
+// Open file handle
+typedef struct file {
+    struct inode *inode;
+    ulong offset;
+    int flags;
+} file_t;
+
+// Filesystem mountpoint
+typedef struct filesystem {
+    const char *name;
+    struct filesystem_ops *ops;
+    struct inode *root;
+} filesystem_t;
+
+void vfs_init(void);
+int vfs_mount(struct filesystem *fs);
+
+struct inode *vfs_lookup(const char *path);
+int vfs_open(const char *path);
+int vfs_read(int fd, void *buf, ulong len);
+int vfs_write(int fd, const void *buf, ulong len);
+int vfs_create(const char *path, uint mode);
+int vfs_mkdir(const char *path);
+
+// FD helpers
+int vfs_install_fd(struct file *f);
+struct file *vfs_get_file(int fd);
blob - /dev/null
blob + 7506650800767704be1adcdaebe47cfdf46b8555 (mode 644)
--- /dev/null
+++ kernel/kernel.ld
@@ -0,0 +1,29 @@
+ENTRY(_entry)
+
+SECTIONS
+{
+    . = 0xffffffff80000000;  /* Starting address of the kernel */
+
+    .text : AT(0x80000000) {
+        _kernel_text_start = .;
+        *(.text .text.*)  /* All code goes here */
+        _kernel_text_end = .;
+    }
+
+    .rodata : ALIGN(0x1000) {
+        _kernel_rodata_start = .;
+        *(.rodata .rodata.*)  /* Read-only data goes here */
+        _kernel_rodata_end = .;
+    }
+
+    .data : ALIGN(0x1000) {
+        _kernel_data_start = .;
+        *(.*data .*data.*)  /* All initialized data goes here */
+    }
+
+    .bss : ALIGN(0x1000) {
+        *(.*bss .*bss.*)  /* All uninitialized data goes here */
+    }
+
+    _kernel_end = .;
+}
blob - /dev/null
blob + 947c539792d3e3a577600c0ad8457c15faaab5e4 (mode 644)
--- /dev/null
+++ kernel/list.h
@@ -0,0 +1,67 @@
+#pragma once
+
+typedef struct list_node {
+    struct list_node *prev;
+    struct list_node *next;
+} list_node_t;
+
+#define LIST_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD_INIT(ptr) do { \
+    (ptr)->next = (ptr);         \
+    (ptr)->prev = (ptr);         \
+} while (0)
+
+// Add node after head
+static inline void
+list_add(list_node_t *new, list_node_t *head)
+{
+    new->next = head->next;
+    new->prev = head;
+    head->next->prev = new;
+    head->next = new;
+}
+
+// Add node after tail
+static inline void
+list_add_tail(list_node_t *new, list_node_t *head)
+{
+    new->next = head;
+    new->prev = head->prev;
+    head->prev->next = new;
+    head->prev = new;
+}
+
+static inline void
+list_del(list_node_t *entry)
+{
+    entry->next->prev = entry->prev;
+    entry->prev->next = entry->next;
+    entry->next = entry->prev = NULL;
+}
+
+static inline int
+list_empty(const list_node_t *head)
+{
+    return head->next == head;
+}
+
+static inline int
+list_in_queue(list_node_t *node)
+{
+    return node->next && node->prev;
+}
+
+
+// Iterate over list
+#define list_for_each(pos, head) \
+    for (pos = (head)->next; pos != (head); pos = pos->next)
+
+// Iterate over list safely during deletion
+#define list_for_each_safe(pos, tmp, head) \
+    for (pos = (head)->next, tmp = pos->next; pos != (head); \
+         pos = tmp, tmp = pos->next)
+
+// Get the containing struct from node
+#define container_of(ptr, type, member) \
+    ((type *)((char *)(ptr) - (char *)(&((type *)NULL)->member)))
blob - /dev/null
blob + b29094bf7efc879c49d53c4ff7b8f8b98a649d6d (mode 644)
--- /dev/null
+++ kernel/mm/mapping.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#define KERNEL_START        0x80000000
+#define KERNEL_END          0x80800000                                      // 8MB for kernel space
+#define USER_START          KERNEL_END
+#define USER_END            0x88000000                                      // 120MB for user space
+
+#define KERNEL_START_VA     0xffffffff80000000                              // Map kernel to high virtual memory
+#define KERNEL_END_VA       0xffffffff80800000                              // 8MB for kernel space
+#define USER_START_VA       0x0
+#define USER_END_VA         0x7800000                                       // 120MB for user space
+
+#define VA_OFFSET           (KERNEL_START_VA - KERNEL_START)
blob - /dev/null
blob + 97a32a17cfacab9599ad7f31b99d76cb33ee43d6 (mode 644)
--- /dev/null
+++ kernel/mm/mem.c
@@ -0,0 +1,261 @@
+#include "mem.h"
+#include "../spinlock.h"
+#include "../uart.h"
+
+extern char _kernel_end[];
+static spinlock_t buddy_lock = SPINLOCK_INIT;
+static ulong num_pages;
+static page_t *page_array;
+static page_t *free_lists[MAX_ORDER + 1];
+ulong va_offset;
+ulong buddy_base_phys;
+pte_t *kernel_pagetable;
+
+static inline page_t *
+get_page_struct(void *pa)
+{
+    ulong idx = ((ulong)pa - buddy_base_phys) / PAGE_SIZE;
+    if (idx >= num_pages) {
+        uart_puts("get_page_struct: INVALID idx: ");
+        uart_putlong(idx);
+        uart_putc('\n');
+        while (1)
+            asm volatile("wfi");
+    }
+    return &page_array[idx];
+}
+
+
+static inline void *
+page_addr(ulong idx)
+{
+    if (idx >= num_pages) {
+        spin_lock(&uart_lock);
+        uart_puts("page_addr: INVALID IDX ");
+        uart_putlong(idx);
+        uart_putc('\n');
+        spin_unlock(&uart_lock);
+        while (1)
+            asm volatile("wfi");
+    }
+    return (void *)(buddy_base_phys + idx * PAGE_SIZE);
+}
+
+static inline void *
+buddy_of(void *addr, int order)
+{
+    ulong block = (ulong)addr - buddy_base_phys;
+    ulong size = PAGE_SIZE << order;
+    ulong buddy = block ^ size;
+
+    return (void *)(buddy + buddy_base_phys);
+}
+
+static inline void *
+early_alloc(ulong size)
+{
+    // Align size
+    size = ALIGN_UP(size);
+
+    if (!buddy_base_phys)
+        buddy_base_phys = ALIGN_UP((ulong)_kernel_end);
+
+    void *addr = (void *)buddy_base_phys;
+    buddy_base_phys += size;
+    return addr;
+}
+
+int
+buddy_allocator_init()
+{
+    /*
+    DEBUG_PRINT(
+        uart_puts("Initializing buddy allocator...\n");
+    );
+    */
+
+    // Allocate kernel pagetable
+    kernel_pagetable = early_alloc(PAGE_SIZE);
+    memset((void *)kernel_pagetable, 0, PAGE_SIZE);
+
+    // Calculate memory range
+    ulong phys_start = buddy_base_phys;
+    ulong phys_end = USER_END;
+
+    // Estimate max pages, accounting for metadata per page
+    ulong per_page_cost = PAGE_SIZE + sizeof(page_t);
+    ulong estimated_pages = (phys_end - phys_start) / per_page_cost;
+
+    // Allocate page_array metadata
+    page_array = early_alloc(estimated_pages * sizeof(page_t));
+    memset(page_array, 0, estimated_pages * sizeof(page_t));
+
+    // Align and update the start of page memory
+    buddy_base_phys = ALIGN_UP(buddy_base_phys);
+    phys_start = buddy_base_phys;
+
+    // Calculate how many pages fit now
+    num_pages = (USER_END - buddy_base_phys) / PAGE_SIZE;
+
+    // Initialize free lists
+    for (int i = 0; i <= MAX_ORDER; i++)
+        free_lists[i] = NULL;
+
+    // Populate initial free blocks
+    ulong curr = phys_start;
+    while (curr + PAGE_SIZE <= phys_end) {
+        int order = MAX_ORDER;
+
+        // Align block and ensure space
+        while (order > 0 &&
+               (curr + (PAGE_SIZE << order) > phys_end ||
+                curr & ((PAGE_SIZE << order) - 1))) {
+            order--;
+        }
+
+        page_t *pg = get_page_struct((void *)curr);
+        pg->order = order;
+        pg->is_free = true;
+        pg->next = free_lists[order];
+        free_lists[order] = pg;
+
+        curr += PAGE_SIZE << order;
+    }
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Buddy allocator ready.\n");
+    );
+    */
+
+    return 0;
+}
+
+void *
+alloc_page(void)
+{
+    return alloc_pages(0);
+}
+
+void *
+alloc_pages(int order)
+{
+    if (order > MAX_ORDER)
+        return NULL;
+
+    // Lock spinlock
+    spin_lock(&buddy_lock);
+
+    for (int o = order; o <= MAX_ORDER; o++) {
+        if (!free_lists[o])
+            continue;
+
+        // Pop from freelist
+        page_t *block = free_lists[o];
+        page_t *block_va = (page_t *)PHYS_TO_VIRT(free_lists[o]);
+        block_va->order = order;
+        block_va->is_free = false;
+        free_lists[o] = (page_t *)block_va->next;
+
+        // Split higher orders
+        int block_idx = block - page_array;
+
+        for (int curr = o; curr > order; curr--) {
+            uint buddy_idx = block_idx + (1 << (curr - 1));
+            if (buddy_idx >= num_pages) {
+                uart_puts("ERROR: buddy_idx out of bounds\n");
+                while (1)
+                    asm volatile("wfi");
+            }
+
+            page_t *buddy = (page_t *)PHYS_TO_VIRT(&page_array[buddy_idx]);
+            buddy->order = curr - 1;
+            buddy->is_free = true;
+            buddy->next = free_lists[curr - 1];
+            free_lists[curr - 1] = (page_t *)VIRT_TO_PHYS(buddy);
+        }
+
+        // Get the address
+        void *addr = page_addr(block_idx);
+
+        // Unlock spinlock
+        spin_unlock(&buddy_lock);
+
+        // Zero the memory
+        memset(PHYS_TO_VIRT(addr), 0, PAGE_SIZE << order);
+
+        return addr;
+    }
+
+    // Unlock spinlock
+    spin_unlock(&buddy_lock);
+
+    return NULL;
+}
+
+void
+free_page(void *ptr)
+{
+    free_pages(ptr);
+}
+
+void
+free_pages(void *addr)
+{
+    if (!addr)
+        return;
+
+    // Lock spinlock
+    spin_lock(&buddy_lock);
+
+    page_t *pg = get_page_struct(addr);
+
+    if (pg->is_free) {
+        spin_unlock(&buddy_lock);
+        return;
+
+        /*
+        spin_lock(&uart_lock);
+        uart_puts("Double free detected at: ");
+        uart_puthex((ulong)addr);
+        uart_putc('\n');
+        spin_unlock(&uart_lock);
+        while (1)
+            asm volatile("wfi");
+        */
+    }
+
+    int order = pg->order;
+    pg->is_free = true;
+
+    while (order < MAX_ORDER) {
+        void *buddy_addr = buddy_of(addr, order);
+        page_t *buddy = get_page_struct(buddy_addr);
+
+        if (!buddy->is_free || buddy->order != order)
+            break;
+
+        // Remove buddy from freelist
+        page_t **p = &free_lists[order];
+        while (*p && *p != buddy)
+            p = &(*p)->next;
+
+        if (*p == buddy)
+            *p = buddy->next;
+
+        buddy->is_free = false;
+        if ((ulong)addr > (ulong)buddy_addr)
+            addr = buddy_addr;
+
+        order++;
+    }
+
+    page_t *merged = get_page_struct(addr);
+    merged->order = order;
+    merged->is_free = true;
+    merged->next = free_lists[order];
+    free_lists[order] = merged;
+
+    // Unlock spinlock
+    spin_unlock(&buddy_lock);
+}
blob - /dev/null
blob + 8a8a4dab597b949f1e1073a7a9ea65d31dfb1fb3 (mode 644)
--- /dev/null
+++ kernel/mm/mem.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "../types.h"
+#include "../string.h"
+#include "mapping.h"
+
+#define PAGE_SIZE           4096
+
+#define VA_OFFSET           (KERNEL_START_VA - KERNEL_START)
+
+#define PHYS_TO_VIRT(pa)    ((void *)((ulong)(pa) + va_offset))
+#define VIRT_TO_PHYS(va)    ((ulong)(va) - va_offset)
+
+#define KSTACK_SIZE         PAGE_SIZE
+#define USER_STACK_SIZE     PAGE_SIZE
+#define USER_STACK_TOP      USER_END_VA
+#define USER_STACK_BASE     (USER_END_VA - USER_STACK_SIZE)
+#define USER_HEAP_END       (USER_STACK_BASE - PAGE_SIZE)                   // Leave 4KB guard
+
+#define MAX_ORDER           10                                              // Max 2^10 pages = 4MB
+#define PAGE_ENTRIES        (PAGE_SIZE / sizeof(pte_t))                     // 4096 / 8 = 512
+#define ALIGN_UP(addr)      (((addr) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))
+
+// Extract physical address from a PTE and vice versa
+#define PAGE_SHIFT          12
+#define PTE2PA(pte)         (((pte) >> 10) << 12)
+#define PA2PTE(pa)          (((ulong)(pa) >> PAGE_SHIFT) << 10)
+
+// Extract permission/flag bits from a PTE
+#define PTE_FLAGS(pte)      ((pte) & 0x3FF)  // bits [9:0]
+
+typedef struct page {
+    uchar order;            // Power-of-two order (0–10)
+    uchar is_free;
+    struct page *next;
+} page_t;
+
+int buddy_allocator_init();
+void *alloc_page(void);
+void *alloc_pages(int order);
+void free_page(void *ptr);
+void free_pages(void *addr);
+
+extern pte_t *kernel_pagetable;
+extern ulong buddy_base_phys;
+extern ulong va_offset;
blob - /dev/null
blob + 7de7cff36c5825c82714ca2d4b2a9cc62ee63baa (mode 644)
--- /dev/null
+++ kernel/mm/pagetable.c
@@ -0,0 +1,117 @@
+#include "pagetable.h"
+#include "../riscv.h"
+#include "../uart.h"
+
+static inline int
+vpn_level(ulong va, int level)
+{
+    return (va >> (PAGE_SHIFT + 9 * level)) & 0x1FF;
+}
+
+/*
+ * Virtual Address (Sv39): 39 bits
+ *   | 9 bits | 9 bits | 9 bits | 12 bits |
+ *   |  VPN2  |  VPN1  |  VPN0  |  Offset |
+ *
+ * Levels:   L2     ->    L1    ->    L0
+ *           pagetable
+ */
+pte_t *
+walk(pte_t *pagetable, ulong va, int alloc)
+{
+    for (int level = 2; level > 0; level--) {
+        uint idx = vpn_level(va, level);
+        pte_t *pte = &pagetable[idx];
+
+        if (*pte & PTE_V) {
+            pagetable = (pte_t *)PHYS_TO_VIRT(PTE2PA(*pte));
+        } else {
+            //uart_puts("current level not valid!\n");
+            if (!alloc){
+                DEBUG_PRINT(uart_puts("walk(): access to pagetable failed\n"););
+                return NULL;
+            }
+
+            void *new_pg = alloc_page();
+            if (!new_pg) {
+                DEBUG_PRINT(uart_puts("alloc_page failed in walk()\n"););
+                return NULL;
+            }
+
+            *pte = PA2PTE(new_pg) | PTE_V;
+            pagetable = (pte_t *)PHYS_TO_VIRT(new_pg);
+        }
+    }
+
+    return &pagetable[vpn_level(va, 0)];
+}
+
+int
+map_page(pte_t *pagetable, ulong va, ulong pa, int perm)
+{
+    // Convert to virtual address
+    pagetable = PHYS_TO_VIRT(pagetable);
+
+    pte_t *pte = walk(pagetable, va, 1);
+    if (!pte)
+        return -1;
+
+    if (!(*pte & PTE_V))
+        *pte = PA2PTE(pa) | perm | PTE_V | PTE_A | PTE_D;
+
+    return 0;
+}
+
+int
+unmap_page(pte_t *pagetable, ulong va)
+{
+    // Convert to virtual address
+    pagetable = PHYS_TO_VIRT(pagetable);
+
+    pte_t *pte = walk(pagetable, va, 0);
+    if (!pte || !(*pte & PTE_V))
+        return -1;
+
+    ulong pa = PTE2PA(*pte);
+    free_page((void *)pa);
+
+    // Clear the PTE
+    *pte = 0;
+
+    // Flush TLB for this address
+    asm volatile("sfence.vma %0, zero" :: "r"(va) : "memory");
+
+    return 0;
+}
+
+
+pte_t *
+alloc_pagetable(void)
+{
+    return (pte_t *)alloc_page();
+}
+
+void
+load_pagetable(pte_t *pagetable)
+{
+    write_csr(satp, MAKE_SATP(pagetable));
+
+    // Flush TLB
+    asm volatile ("sfence.vma zero, zero");
+}
+
+void
+free_pagetable(pte_t *pagetable)
+{
+    // Convert to virtual address
+    pagetable = PHYS_TO_VIRT(pagetable);
+
+    for (int i = 0; i < PTE_COUNT; i++) {
+        pte_t pte = pagetable[i];
+        if ((pte & PTE_V) && !(pte & (PTE_R | PTE_W | PTE_X))) {
+            pte_t *child = (pte_t *)PTE2PA(pte);
+            free_pagetable(child);
+        }
+    }
+    free_page(pagetable);
+}
blob - /dev/null
blob + cf8f08e90d98e8e6d300ebb67d9518dfa3fd4174 (mode 644)
--- /dev/null
+++ kernel/mm/pagetable.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "../types.h"
+#include "mem.h"
+
+#define PTE_COUNT           512  // Entries per page table level (9 bits)
+
+#define SATP_MODE_SV39      (8UL << 60)
+#define MAKE_SATP(pgtbl)    (SATP_MODE_SV39 | (((ulong)pgtbl >> PAGE_SHIFT) & 0xFFFFFFFFFFFUL))
+
+#define PTE_V (1L << 0)
+#define PTE_R (1L << 1)
+#define PTE_W (1L << 2)
+#define PTE_X (1L << 3)
+#define PTE_U (1L << 4)
+#define PTE_A (1L << 6)
+#define PTE_D (1L << 7)
+
+pte_t *alloc_pagetable(void);
+pte_t *walk(pte_t *pagetable, ulong va, int alloc);
+int map_page(pte_t *pagetable, ulong va, ulong pa, int perm);
+int unmap_page(pte_t *pagetable, ulong va);
+void load_pagetable(pte_t *pagetable);
+void free_pagetable(pte_t *pagetable);
blob - /dev/null
blob + 7f8c9f7bb2c2cb705860edd5cd0d47228645d642 (mode 644)
--- /dev/null
+++ kernel/mm/snub.c
@@ -0,0 +1,105 @@
+#include "snub.h"
+
+static snub_cache_t caches[SNUB_NUM_CLASSES];
+
+static inline int
+size_to_idx(ulong size)
+{
+    int shift = SNUB_MIN_SHIFT;
+    while ((1UL << shift) < size && shift <= SNUB_MAX_SHIFT)
+        shift++;
+
+    return (shift > SNUB_MAX_SHIFT) ? -1 : (shift - SNUB_MIN_SHIFT);
+}
+
+void
+snub_init(void)
+{
+    for (int i = 0; i < SNUB_NUM_CLASSES; i++) {
+        caches[i].object_size = 1 << (SNUB_MIN_SHIFT + i);
+        caches[i].partial = NULL;
+        spinlock_init(&caches[i].lock);
+    }
+}
+
+void *
+kmalloc(ulong size)
+{
+    int idx = size_to_idx(size);
+    if (idx < 0 || idx >= SNUB_NUM_CLASSES)
+        return NULL;
+
+    snub_cache_t *cache = &caches[idx];
+
+    // Lock spinlock
+    spin_lock(&cache->lock);
+
+    if (!cache->partial) {
+        // Allocate new snub page
+        void *page = PHYS_TO_VIRT(alloc_page());
+        if (!page) {
+            // Unlock spinlock
+            spin_unlock(&cache->lock);
+            return NULL;
+        }
+
+        int objs_per_page = PAGE_SIZE / cache->object_size;
+        void *head = NULL;
+
+        for (int i = 0; i < objs_per_page; i++) {
+            void *obj = (char *)page + i * cache->object_size;
+            *(void **)obj = head;
+            head = obj;
+        }
+
+        cache->partial = head;
+    }
+
+    void *obj = cache->partial;
+    cache->partial = *(void **)obj;
+
+    // Unlock spinlock
+    spin_unlock(&cache->lock);
+
+    return obj;
+}
+
+void *
+kzalloc(ulong size)
+{
+    void *ptr = kmalloc(size);
+    if (ptr)
+        memset(ptr, 0, size);
+
+    return ptr;
+}
+
+void
+kfree(void *ptr)
+{
+    ulong addr = (ulong)ptr;
+    ulong offset = addr - buddy_base_phys;
+    int page_idx = offset / PAGE_SIZE;
+    ulong page_base = buddy_base_phys + (page_idx * PAGE_SIZE);
+    ulong diff = addr - page_base;
+
+    // Guess object size by nearest size class
+    for (int i = 0; i < SNUB_NUM_CLASSES; i++) {
+        int obj_size = caches[i].object_size;
+        if (diff % obj_size == 0 && diff < PAGE_SIZE) {
+            // Lock spinlock
+            spin_lock(&caches[i].lock);
+
+            *(void **)ptr = caches[i].partial;
+            caches[i].partial = ptr;
+
+            // Unlock spinlock
+            spin_unlock(&caches[i].lock);
+
+            return;
+        }
+    }
+
+    // Not a known cache — maybe full page allocation
+    free_pages((void *)VIRT_TO_PHYS(ptr));
+}
blob - /dev/null
blob + 79a8fc8fcaccfa01f61afee13af3da09e4910903 (mode 644)
--- /dev/null
+++ kernel/mm/snub.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "../spinlock.h"
+#include "../types.h"
+#include "mem.h"
+
+#define SNUB_MIN_SHIFT      4   // 2^4 = 16 bytes
+#define SNUB_MAX_SHIFT      12  // 2^12 = 4096 bytes
+#define SNUB_NUM_CLASSES    (SNUB_MAX_SHIFT - SNUB_MIN_SHIFT + 1)
+
+typedef struct snub_cache {
+    int object_size;
+    void *partial;          // Freelist of objects
+    spinlock_t lock;
+} snub_cache_t;
+
+void snub_init(void);
+void *kmalloc(ulong size);
+void *kzalloc(ulong size);
+void kfree(void *ptr);
blob - /dev/null
blob + 286c61cd6c3b26aacdc2cef15d8bd1868aed96ad (mode 644)
--- /dev/null
+++ kernel/mm/user_copy.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "mem.h"
+
+static inline uint
+is_user_addr(ulong va, uint len)
+{
+    return len && (va < USER_END_VA) && (len < (USER_END_VA - va));
+}
+
+static inline int
+copy_from_user(void *kernel_dst, const void *user_src, uint len)
+{
+    if (!is_user_addr((ulong)user_src, len))
+        return -1;
+
+    set_csr(sstatus, SSTATUS_SUM);
+    __builtin_memcpy(kernel_dst, user_src, len);
+    clear_csr(sstatus, SSTATUS_SUM);
+
+    return 0;
+}
+
+static inline int
+copy_to_user(void *user_dst, const void *kernel_src, uint len)
+{
+    if (!is_user_addr((ulong)user_dst, len))
+        return -1;
+
+    set_csr(sstatus, SSTATUS_SUM);
+    __builtin_memcpy(user_dst, kernel_src, len);
+    clear_csr(sstatus, SSTATUS_SUM);
+
+    return 0;
+}
blob - /dev/null
blob + 6f60918b42a67562e3d2e15eb9c775ba6122da64 (mode 644)
--- /dev/null
+++ kernel/proc.c
@@ -0,0 +1,291 @@
+#include "proc.h"
+#include "uart.h"
+#include "mm/snub.h"
+#include "list.h"
+#include "mm/pagetable.h"
+#include "trap/trap.h"
+
+static long next_pid;
+
+cpu_t cpus[NCPU];
+proc_t idle_procs[NCPU];
+extern char _kernel_end[];
+
+void
+idle_loop()
+{
+    DEBUG_PRINT(
+        uart_puts("Hart ");
+        uart_putc('0' + curr_cpu()->id);
+        uart_puts(": idle loop\n");
+    );
+
+    // Set simplified trap vector
+    write_csr(stvec, idle_trap_vector);
+
+    // Enable interrupts
+    set_csr(sstatus, SSTATUS_SIE);
+
+    while (1)
+        asm volatile("wfi");  // Wait for interrupt
+}
+
+void
+dump_mappings(pte_t *pagetable, ulong start_va, ulong end_va)
+{
+    for (ulong va = start_va; va < end_va; va += PAGE_SIZE) {
+        pte_t *pte = walk(pagetable, va, 0);
+        if (!pte || !(*pte & PTE_V)) {
+            uart_puts("VA not mapped: ");
+            uart_puthex(va);
+            uart_putc('\n');
+            continue;
+        }
+
+        uart_puts("VA: ");
+        uart_puthex(va);
+        uart_puts(" -> PA: ");
+        uart_puthex(PTE2PA(*pte));
+        uart_puts(" Flags: ");
+        uart_puthex(PTE_FLAGS(*pte));
+        uart_putc('\n');
+    }
+}
+
+void
+dump_pagetable_recursive(pte_t *pagetable, int level, ulong va_base)
+{
+    for (int i = 0; i < 512; i++) {
+        if (pagetable[i] & PTE_V) {
+            ulong va = va_base | ((ulong)i << (12 + 9 * level));
+            ulong pte = pagetable[i];
+
+            uart_puts("L");
+            uart_putc('0' + level);
+            uart_puts(" Entry ");
+            uart_puthex(i);
+            uart_puts(": VA ");
+            uart_puthex(va);
+            uart_puts(" -> PTE ");
+            uart_puthex(pte);
+
+            if (pte & (PTE_R | PTE_W | PTE_X)) {
+                // It's a leaf entry, calculate physical address
+                ulong pa = PTE2PA(pte);
+                uart_puts(" -> PA ");
+                uart_puthex(pa);
+            }
+
+            uart_putc('\n');
+
+            if ((pte & (PTE_R | PTE_W | PTE_X)) == 0) {
+                // This PTE points to a lower-level page table
+                ulong next_pa = PTE2PA(pte);
+                pte_t *next = (pte_t *)PHYS_TO_VIRT(next_pa);
+                dump_pagetable_recursive(next, level - 1, va);
+            }
+        }
+    }
+}
+
+void
+copy_kernel_mappings(pte_t *dst, pte_t *src)
+{
+    for (ulong va = KERNEL_START_VA; va < (ulong)_kernel_end; va += PAGE_SIZE) {
+        pte_t *src_pte = walk(src, va, 0);
+        if (!src_pte || !(*src_pte & PTE_V)) {
+            uart_puts("copy_kernel_mappings: missing PTE at ");
+            uart_puthex(va);
+            uart_putc('\n');
+            continue;
+        }
+
+        ulong pa = PTE2PA(*src_pte);
+        ulong flags = PTE_FLAGS(*src_pte);
+
+        if (map_page(dst, va, pa, flags) != 0) {
+            uart_puts("copy_kernel_mappings: map_page failed at ");
+            uart_puthex(va);
+            uart_putc('\n');
+        }
+    }
+}
+
+proc_t *
+create_proc(void *binary, ulong binary_size)
+{
+    //DEBUG_PRINT(uart_puts("create_proc()\n"););
+
+    proc_t *p = (proc_t *)kzalloc(sizeof(proc_t));
+    if (!p)
+        goto fail;
+
+    p->pid = ATOMIC_INC_AND_FETCH(&next_pid);
+    p->bound_cpu = -1;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Proc allocated\n");
+        uart_puts("Allocating proc pagetable...\n");
+    );
+    */
+
+    // Allocate user page table
+    p->pagetable = alloc_pagetable();
+    if (!p->pagetable)
+        goto fail1;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Proc pagetable allocated\n");
+        uart_puts("Allocating kstack for the proc\n");
+    );
+    */
+
+    p->kstack = (char *)kzalloc(KSTACK_SIZE);
+    if (!p->kstack)
+        goto fail1;
+
+    // Map proc_t
+    if (map_page(p->pagetable, (ulong)p, VIRT_TO_PHYS(p), PTE_R | PTE_W) == -1)
+        goto fail1;
+
+    // Map kstack
+    if (map_page(p->pagetable, (ulong)p->kstack, VIRT_TO_PHYS(p->kstack), PTE_R | PTE_W) == -1)
+        goto fail1;
+
+    p->tf = (trap_frame_t *)(p->kstack + KSTACK_SIZE - sizeof(trap_frame_t));
+    p->tf->regs[2] = USER_STACK_TOP;
+    p->tf->sepc = USER_START_VA;
+    p->tf->sstatus = SSTATUS_SPIE | SSTATUS_SUM;
+    p->ctx.sp = (ulong)p->tf;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Kernel stack allocated and mapped\n");
+        uart_puts("Loading user binary...\n");
+    );
+    */
+
+    // Load user binary
+    ulong va = USER_START_VA;
+    ulong remaining = binary_size;
+    uchar *src = (uchar *)binary;
+
+    while (remaining > 0) {
+        ulong user_bin_pa = (ulong)alloc_page();
+        if (!user_bin_pa)
+            goto fail1;
+
+        ulong to_copy = (remaining > PAGE_SIZE) ? PAGE_SIZE : remaining;
+        memcpy(PHYS_TO_VIRT(user_bin_pa), src, to_copy);
+
+        if (map_page(p->pagetable, va, user_bin_pa, PTE_R | PTE_X | PTE_U) == -1)
+            goto fail1;
+
+        va += PAGE_SIZE;
+        src += to_copy;
+        remaining -= to_copy;
+    }
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("User binary loaded\n");
+        uart_puts("Allocating user heap and stack...\n");
+    );
+    */
+
+    // Set user heap
+    p->heap_start = ALIGN_UP(va);
+    p->heap_end = p->heap_start;
+
+    // User stack (1 page)
+    p->ustack = alloc_page();
+    if (!p->ustack)
+        goto fail1;
+
+    // Map user stack
+    if (map_page(p->pagetable, USER_STACK_TOP - USER_STACK_SIZE, (ulong)p->ustack, PTE_R | PTE_W | PTE_U) == -1)
+        goto fail1;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("User stack allocated and mapped\n");
+        uart_puts("Mapping kernel pagetable into user pagetable\n");
+    );
+    */
+
+    // Map kernel segments
+    copy_kernel_mappings(p->pagetable, PHYS_TO_VIRT(kernel_pagetable));
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Kernel pagetable into user pagetable mapped\n");
+        uart_puts("Mapping UART to user pagetable\n");
+    );
+    */
+
+    // Map UART
+    if (map_page(p->pagetable, UART0, VIRT_TO_PHYS(UART0), PTE_R | PTE_W) == -1) {
+        uart_puts("map_page() failed for UART, halting...\n");
+        while (1)
+            asm volatile("wfi");
+    }
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("UART mapped into user pagetable\n");
+    );
+    */
+
+    //dump_mappings(p->pagetable, USER_STACK_TOP - USER_STACK_SIZE, USER_STACK_TOP);
+    //dump_mappings(p->pagetable, KERNEL_START_VA, (ulong)_kernel_end);
+    //dump_mappings(p->pagetable, UART0, UART0 + PAGE_SIZE);
+
+    // Put into RUNNABLE queue
+    p->state = RUNNABLE;
+    list_add_tail(&p->q_node, &cpus[curr_cpu()->id].run_queue);
+
+    //dump_pagetable(p->pagetable);
+    //uart_puts("User Page Table Dump:\n");
+    //dump_pagetable_recursive(p->pagetable, 2, KERNEL_START_VA);
+    //dump_pagetable_range(p->pagetable, USER_START_VA, USER_END_VA);
+    //uart_puts("And kernel range:\n");
+    //dump_pagetable_range(p->pagetable, KERNEL_START_VA, (ulong)_kernel_end);
+
+    return p;
+
+fail1:
+    free_proc(p);
+fail:
+    DEBUG_PRINT(uart_puts("Process creation failed. Return NULL.\n"););
+    return NULL;
+}
+
+void
+free_proc(proc_t *p)
+{
+    if (!p)
+        return;
+
+    // If it's in a queue, remove it
+    if (list_in_queue(&p->q_node))
+        list_del(&p->q_node);
+
+    // Free user stack
+    if (p->ustack)
+        free_page(p->ustack);
+
+    // TODO: properly free user binary
+
+    // Free user page table
+    if (p->pagetable)
+        free_pagetable(p->pagetable);
+
+    // Free kernel stack
+    if (p->kstack)
+        kfree(p->kstack);
+
+    // Free the process
+    kfree(p);
+}
blob - /dev/null
blob + aba5d6ed6e12dfb24fae252639c72e7cefaf2fad (mode 644)
--- /dev/null
+++ kernel/proc.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "riscv.h"
+#include "list.h"
+
+#define NCPU            4  // number of CPUs
+
+typedef struct trap_frame {
+    ulong regs[32];     // x0 - x31
+    ulong sepc;         // saved program counter
+    ulong sstatus;      // status register
+    ulong scause;       // cause of trap
+    ulong stval;        // trap value (like faulting addr)
+} trap_frame_t;
+
+typedef struct context {
+    ulong ra;
+    ulong sp;
+    ulong s[12];
+} context_t;
+
+enum proc_state {
+    UNUSED,
+    SLEEPING,
+    RUNNABLE,
+    RUNNING,
+    ZOMBIE
+};
+
+typedef struct proc {
+    int pid;
+    enum proc_state state;
+    int is_idle;
+    int bound_cpu;
+    ulong sleep_until;
+    trap_frame_t *tf;
+    context_t ctx;
+    list_node_t q_node;
+    //list_node_t user_bin;
+    pte_t *pagetable;
+    ulong heap_start;
+    ulong heap_end;
+    char *kstack;
+    char *ustack;
+} proc_t;
+
+typedef struct cpu {
+    uint id;            // hard ID
+    uint lock_depth;    // depth of nested spinlocks
+    ulong sstatus;      // interrupt state before first lock
+    int needs_sched;
+    proc_t *proc;       // current process
+    list_node_t run_queue;
+    list_node_t sleep_queue;
+} cpu_t;
+
+static inline cpu_t *
+curr_cpu()
+{
+    return (cpu_t *)read_tp();
+}
+
+extern cpu_t cpus[NCPU];
+extern proc_t idle_procs[NCPU];
+
+void idle_loop();
+proc_t *create_proc(void *binary, ulong binary_size);
+void free_proc(proc_t *p);
blob - /dev/null
blob + 0f4db90794f081faadac9928f446bcb57116871d (mode 755)
--- /dev/null
+++ kernel/rerun.sh
@@ -0,0 +1,5 @@
+#!/bin/zsh
+
+make clean
+./build_shell.sh
+make run
blob - /dev/null
blob + 9cb003ef5862deced721e40aae5a613eb3274258 (mode 644)
--- /dev/null
+++ kernel/riscv.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "types.h"
+
+#define SSTATUS_SIE         (1UL << 1)
+#define SSTATUS_SPP         (1UL << 8)
+#define SSTATUS_SPIE        (1UL << 5)
+#define SSTATUS_UPIE        (0UL << 8)
+#define SSTATUS_SUM         (1UL << 18)
+
+#define SIE_STIE            (1UL << 5)
+#define SIE_SEIE            (1UL << 9)
+
+#define MENVCFG_FDT         (1UL << 63)
+#define MCOUNTEREN_TIME     (1UL << 1)
+#define MSTATUS_MPP_MASK    (3UL << 11)  // Mask for MPP (bits 11-12)
+#define MSTATUS_MPP_S_MODE  (1UL << 11)  // MPP set to S-mode (01)
+
+#define read_csr(reg) ({                           \
+    ulong __tmp;                                   \
+    asm volatile("csrr %0, " #reg : "=r"(__tmp));  \
+    __tmp;                                         \
+})
+
+#define write_csr(reg, val) ({                      \
+    ulong __v = (ulong)(val);                       \
+    asm volatile("csrw " #reg ", %0" :: "rK"(__v)); \
+})
+
+#define set_csr(reg, bit) ({                        \
+    asm volatile("csrs " #reg ", %0" :: "rK"(bit)); \
+})
+
+#define clear_csr(reg, bit) ({                      \
+    asm volatile("csrc " #reg ", %0" :: "rK"(bit)); \
+})
+
+#define ATOMIC_FETCH_AND_INC(ptr) ({   \
+    long __old_val;                    \
+    asm volatile (                     \
+        "li t0, 1\n\t"                 \
+        "amoadd.d.aq %0, t0, (%1)\n\t" \
+        : "=r"(__old_val)              \
+        : "r"(ptr)                     \
+        : "t0", "memory");             \
+    __old_val;                         \
+})
+
+#define ATOMIC_INC_AND_FETCH(ptr) ({        \
+    long __old_val;                         \
+    asm volatile (                          \
+        "li t0, 1\n\t"                      \
+        "amoadd.d.aq %0, t0, (%1)\n\t"      \
+        : "=r"(__old_val)                   \
+        : "r"(ptr)                          \
+        : "t0", "memory");                  \
+    __old_val + 1;                          \
+})
+
+static inline ulong
+read_tp()
+{
+    ulong val;
+    asm volatile("mv %0, tp" : "=r"(val));
+    return val;
+}
+
+static inline void
+write_tp(ulong val)
+{
+    asm volatile("mv tp, %0" :: "r"(val));
+}
blob - /dev/null
blob + a06589801d4ec723085203b39403ab033a1478d9 (mode 644)
--- /dev/null
+++ kernel/sched.c
@@ -0,0 +1,72 @@
+#include "sched.h"
+#include "proc.h"
+#include "uart.h"
+#include "list.h"
+#include "mm/mem.h"
+#include "trap/trap.h"
+
+// Forward declaration
+void swtch(context_t *old, context_t *new);
+
+void
+schedule()
+{
+    cpu_t *c = curr_cpu();
+    proc_t *old = c->proc;
+    proc_t *new = NULL;
+
+    DEBUG_PRINT(
+        uart_putc('0' + c->id);
+        uart_puts(" schedule() run...\n");
+    );
+
+    list_node_t *head = &c->run_queue;
+    list_node_t *node = head->next;
+
+    // Next process found
+    if (node != head) {
+        new = container_of(node, proc_t, q_node);
+        list_del(&new->q_node);
+        new->state = RUNNING;
+
+        DEBUG_PRINT(
+            uart_puts("new proc ");
+            uart_putc('0' + new->pid);
+            uart_puts(" found, switching to user code at ");
+            uart_puthex(new->tf->sepc);
+            uart_putc('\n');
+        );
+
+        if (!old->is_idle) {
+            old->state = RUNNABLE;
+            list_add_tail(&old->q_node, &c->run_queue);
+        }
+        c->proc = new;
+
+        // Set normal trap vector
+        write_csr(stvec, trap_vector);
+
+        // Load page table for new process
+        load_pagetable(c->proc->pagetable);
+
+        // Save trap frame
+        write_csr(sscratch, c->proc->tf);
+
+        // Kernel context switch
+        swtch(&old->ctx, &c->proc->ctx);
+    } else if (!old->is_idle) {
+        // No runnable process, run idle
+        DEBUG_PRINT(
+            uart_putc('0' + c->id);
+            uart_puts(" switching to idle\n");
+        );
+
+        // Idle process always stays the same
+        c->proc = &idle_procs[c->id];
+        c->proc->ctx.ra = (ulong)idle_loop;
+        c->proc->ctx.sp = (ulong)(c->proc->kstack + KSTACK_SIZE);
+
+        load_pagetable(kernel_pagetable);
+        swtch(&old->ctx, &c->proc->ctx);
+    }
+}
blob - /dev/null
blob + 9617289c3f977c8db884d70099e5e08de79ba31c (mode 644)
--- /dev/null
+++ kernel/sched.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "spinlock.h"
+#include "mm/pagetable.h"
+
+extern spinlock_t sched_lock;
+
+void schedule();
blob - /dev/null
blob + 1e401a346c6745aac3ff827f5cbae9f339b09ed9 (mode 644)
--- /dev/null
+++ kernel/shell.c
@@ -0,0 +1,43 @@
+#include "user.h"
+
+#define INPUT_BUF_SIZE 128
+
+int
+streq(const char *s1, const char *s2, int n)
+{
+    for (int i = 0; i < n; i++)
+        if (!s1[i] || !s2[i] || s1[i] != s2[i])
+            return 0;
+
+    return 1;
+}
+
+__attribute__((section(".text.user_shell_main"), used))
+void
+user_shell_main()
+{
+    char buf[INPUT_BUF_SIZE] = "$ ";
+    const char cmd_yield[] = "yield";
+
+    while (1) {
+        // Print prompt
+        const char prompt[] = "$ ";
+        write(prompt, 2);
+
+        // Read input (blocking)
+        int n = read(&buf[2], sizeof(buf) - 1);
+        if (n <= 0)
+            continue;
+
+        sleep(1000);
+
+        if (streq(&buf[2], cmd_yield, sizeof(cmd_yield) - 1))
+            yield();
+
+        // Echo back
+        write(buf, n + 2);  // \0 and \n
+
+        //yield();
+        //exit();
+    }
+}
blob - /dev/null
blob + ca7bc8013cbfc70e823a0200b19ade1686b51db3 (mode 644)
--- /dev/null
+++ kernel/spinlock.c
@@ -0,0 +1,60 @@
+#include "spinlock.h"
+#include "proc.h"
+#include "uart.h"
+
+void
+spin_lock(spinlock_t *lock)
+{
+    cpu_t *c = curr_cpu();
+    if (c->lock_depth == 0) {
+        // Save current interrupt state and disable interrupts
+        c->sstatus = read_csr(sstatus);
+        clear_csr(sstatus, SSTATUS_SIE); // Clear SIE
+
+    }
+    c->lock_depth++;
+
+    while (__atomic_test_and_set(&lock->locked, __ATOMIC_ACQUIRE))
+        asm volatile("nop");
+}
+
+int
+spin_trylock(spinlock_t *lock)
+{
+    cpu_t *c = curr_cpu();
+    if (c->lock_depth == 0) {
+        // Save current interrupt state and disable interrupts
+        c->sstatus = read_csr(sstatus);
+        clear_csr(sstatus, SSTATUS_SIE); // Clear SIE
+
+    }
+    c->lock_depth++;
+
+    int expected = 0;
+    return __atomic_compare_exchange_n(&lock->locked, &expected, 1,
+                                       0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED);
+}
+
+void
+spin_unlock(spinlock_t *lock)
+{
+    cpu_t *c = curr_cpu();
+
+    // Sanity check: lock should not be unlocked if not locked
+    if (c->lock_depth == 0) {
+        uart_puts("[panic] spin_unlock: lock_depth underflow\n");
+        while (1)
+            asm volatile("wfi");
+    }
+
+    // Decrement lock nesting depth
+    c->lock_depth--;
+
+    // Release the lock — __ATOMIC_RELEASE ensures memory ordering
+    __atomic_clear(&lock->locked, __ATOMIC_RELEASE);
+
+    // Only restore interrupt state when outermost lock is released
+    // Restore sstatus (e.g., re-enable interrupts if they were on before)
+    if (c->lock_depth == 0)
+        write_csr(sstatus, c->sstatus);
+}
blob - /dev/null
blob + 6e5a457a1fd626cb5d097c8223e5e18bdf993ab4 (mode 644)
--- /dev/null
+++ kernel/spinlock.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "types.h"
+
+typedef struct {
+    volatile uint locked;
+} spinlock_t;
+
+#define SPINLOCK_INIT { .locked = 0 }
+
+static inline void spinlock_init(spinlock_t *lock) {
+    lock->locked = 0;
+}
+
+void spin_lock(spinlock_t *lock);
+int spin_trylock(spinlock_t *lock);
+void spin_unlock(spinlock_t *lock);
blob - /dev/null
blob + ea4cd04273f4e453fbd71610f046da034d2a9bf5 (mode 644)
--- /dev/null
+++ kernel/start.c
@@ -0,0 +1,256 @@
+#include "uart.h"
+#include "spinlock.h"
+#include "riscv.h"
+#include "trap/trap.h"
+#include "types.h"
+#include "proc.h"
+#include "timer.h"
+#include "syscall.h"
+#include "string.h"
+#include "sched.h"
+#include "mm/snub.h"
+#include "mm/pagetable.h"
+
+volatile int allocator_ready = 0;
+
+extern char _stack[];
+
+extern char _binary_shell_bin_start[];
+extern char _binary_shell_bin_end[];
+
+extern char _kernel_text_start[];
+extern char _kernel_text_end[];
+extern char _kernel_rodata_start[];
+extern char _kernel_rodata_end[];
+extern char _kernel_data_start[];
+
+void
+shell_init()
+{
+    ulong size = _binary_shell_bin_end - _binary_shell_bin_start;
+    proc_t *shell = create_proc(_binary_shell_bin_start, size);
+
+    list_del(&shell->q_node);
+    shell->state = RUNNING;
+    shell->bound_cpu = 0;
+
+    curr_cpu()->proc = shell;
+}
+
+void
+setup_idle_proc()
+{
+    cpu_t *c = curr_cpu();
+    proc_t *idle = &idle_procs[c->id];
+
+    memset(idle, 0, sizeof(proc_t));
+    //idle->pid = 0;
+    idle->is_idle = 1;
+    idle->state = RUNNING;
+    idle->pagetable = kernel_pagetable;
+
+    // Reuse boot stack as idle stack
+    idle->kstack = _stack + (c->id * KSTACK_SIZE);
+
+    // Set context
+    idle->ctx.ra = (ulong)idle_loop;
+    idle->ctx.sp = (ulong)(idle->kstack + KSTACK_SIZE);
+
+    if (c->id)
+        c->proc = idle;
+}
+
+void jump_to_user_shell()
+{
+    DEBUG_PRINT(uart_puts("Jumping to user shell\n"););
+
+    proc_t *p = curr_cpu()->proc;
+
+    // Setup SSTATUS: SPIE=1 (enable interrupts)
+    write_csr(sstatus, SSTATUS_SPIE);
+    // Set return mode = user
+    clear_csr(sstatus, SSTATUS_SPP);
+    // Save trap frame
+    write_csr(sscratch, (ulong)p->tf);
+
+    load_pagetable(p->pagetable);
+
+    // Set up sp (toggle SUM to touch user memory)
+    set_csr(sstatus, SSTATUS_SUM);
+    register ulong sp asm("sp") = (ulong)USER_STACK_TOP;
+    clear_csr(sstatus, SSTATUS_SUM);
+
+    asm volatile(
+        "mv sp, %0\n"
+        "sret\n"
+        :
+        : "r"(sp)
+        : "memory"
+    );
+}
+
+void
+s_mode_main()
+{
+    // Just print hello for now
+    DEBUG_PRINT(
+        uart_puts("Hart ");
+        uart_putc('0' + curr_cpu()->id);
+        uart_puts(": Hello from S-mode!\n");
+    );
+
+    // Create per-CPU process queues
+    LIST_HEAD_INIT(&cpus[curr_cpu()->id].run_queue);
+    LIST_HEAD_INIT(&cpus[curr_cpu()->id].sleep_queue);
+
+    // Enable timer irqs only after queues are set up
+    timer_init();
+
+    // Create idle process for each CPU
+    setup_idle_proc();
+
+    // Jump to shell (future init) on core0
+    if (curr_cpu()->id == 0) {
+        shell_init();
+        jump_to_user_shell();
+    } else {
+        idle_loop();
+    }
+
+    DEBUG_PRINT(uart_puts("End of s_mode_main()\n"););
+    while (1)
+        asm volatile("wfi");
+}
+
+void
+setup_kernel_pagetable()
+{
+    /*
+    DEBUG_PRINT(
+        uart_puts("calling map_page() on KERNEL_START - KERNEL_END\n");
+    );
+    */
+
+    // Map kernel code
+    for (ulong pa = (ulong)_kernel_text_start; pa < (ulong)_kernel_text_end; pa += PAGE_SIZE) {
+        if (map_page(kernel_pagetable, pa + VA_OFFSET, pa, PTE_R | PTE_X) == -1) {
+            uart_puts("map_page() failed for kernel text, halting...\n");
+            while (1)
+                asm volatile("wfi");
+        }
+    }
+
+    // Map rodata
+    for (ulong pa = (ulong)_kernel_rodata_start; pa < (ulong)_kernel_rodata_end; pa += PAGE_SIZE) {
+        if (map_page(kernel_pagetable, pa + VA_OFFSET, pa, PTE_R) == -1) {
+            uart_puts("map_page() failed for kernel rodata, halting...\n");
+            while (1)
+                asm volatile("wfi");
+        }
+    }
+
+    // Map (s)data, (s)bss and heap
+    for (ulong pa = (ulong)_kernel_data_start; pa < (ulong)KERNEL_END; pa += PAGE_SIZE) {
+        if (map_page(kernel_pagetable, pa + VA_OFFSET, pa, PTE_R | PTE_W) == -1) {
+            uart_puts("map_page() failed for kernel data + heap, halting...\n");
+            while (1)
+                asm volatile("wfi");
+        }
+    }
+
+    // Map UART
+    if (map_page(kernel_pagetable, UART0, UART0 - VA_OFFSET, PTE_R | PTE_W) == -1) {
+        uart_puts("map_page() failed for UART, halting...\n");
+        while (1)
+            asm volatile("wfi");
+    }
+
+    // Wait for the changes
+    asm volatile ("sfence.vma zero, zero");
+}
+
+void
+mm_init()
+{
+    // Initialize global memory allocator
+    buddy_allocator_init();
+
+    // Map phys mem to virt in kernel
+    setup_kernel_pagetable();
+
+    // Use kernel page table
+    load_pagetable(kernel_pagetable);
+
+    // SystemN Unified Buddy allocator :)
+    snub_init();
+
+    // Adjust addrs to virtual for S-mode
+    va_offset = VA_OFFSET;
+}
+
+void
+start()
+{
+    // Set struct cpu to tp register
+    uint hart_id = read_csr(mhartid);
+    write_tp((ulong)&cpus[hart_id]);
+
+    // Set hart_id to struct cpu
+    curr_cpu()->id = hart_id;
+
+    // Halt all harts except 0
+    //if (hart_id != 0)
+      //  while (1)
+        //    asm volatile("wfi");
+
+    // Initialize memory system
+    if (hart_id == 0) {
+        mm_init();
+        __sync_synchronize();
+        allocator_ready = 1;
+    } else {
+        // Wait for hart 0 to finish memory init
+        while (!allocator_ready)
+            asm volatile("nop");
+
+        // Use kernel page table on all cores
+        __sync_synchronize();
+        load_pagetable(kernel_pagetable);
+    }
+
+    // Delegate exceptions and interrupts to S-mode
+    write_csr(medeleg, 0xffff);
+    write_csr(mideleg, 0xffff);
+
+    // Setup PMP to give S-mode access to all memory
+    write_csr(pmpaddr0, -1L);
+    write_csr(pmpcfg0, 0x0f);   // R/W/X permissions, TOR mode
+
+    // Set S-mode trap vector
+    write_csr(stvec, trap_vector + VA_OFFSET);
+
+    // Set up mstatus to enter S-mode
+    clear_csr(mstatus, MSTATUS_MPP_MASK);  // Clear MPP (bits 12-11)
+    set_csr(mstatus, MSTATUS_MPP_S_MODE);  // Set MPP = S-mode (01)
+
+    // Enable interrupts in S-mode
+    set_csr(sstatus, SSTATUS_SIE);
+
+    // Supervisor External Interrupt Enable
+    //set_csr(sie, SIE_SEIE);
+
+    // Init timer interrupts
+    set_csr(sie, SIE_STIE);
+    set_csr(menvcfg, MENVCFG_FDT);
+    set_csr(mcounteren, MCOUNTEREN_TIME);
+
+    // Set mepc to the address of S-mode entry point
+    write_csr(mepc, (ulong)s_mode_main + VA_OFFSET);
+
+    // Adjust registers to virtual for S-mode
+    write_tp((ulong)&cpus[hart_id] + VA_OFFSET);
+    asm volatile("add sp, sp, %0" :: "r"(VA_OFFSET));
+
+    // Drop to S-mode!
+    asm volatile("mret");
+}
blob - /dev/null
blob + bbdef7debda8dedb8c4e2195864962429d6f3ca6 (mode 644)
--- /dev/null
+++ kernel/string.c
@@ -0,0 +1,116 @@
+#include "string.h"
+
+void *
+memcpy(void *dest, const void *src, ulong n)
+{
+    char *d = dest;
+    const char *s = src;
+
+    while (n--)
+        *d++ = *s++;
+
+    return dest;
+}
+
+void *
+memset(void *dst, int c, ulong n)
+{
+    uchar *p = (uchar *)dst;
+    for (uint i = 0; i < n; i++)
+        p[i] = (uchar)c;
+
+    return dst;
+}
+
+ulong
+strlen(const char *s)
+{
+    ulong len = 0;
+    while (*s++)
+        len++;
+
+    return len;
+}
+
+ulong
+strnlen(const char *s, ulong maxlen)
+{
+    ulong len = 0;
+    while (len < maxlen && s[len] != '\0')
+        len++;
+
+    return len;
+}
+
+/*
+ * $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ */
+ulong
+strlcpy(char *dst, const char *src, ulong size)
+{
+	const char *s = src;
+	ulong n = size;
+
+	if (n != 0)
+		while (--n != 0)
+			if ((*dst++ = *src++) == '\0')
+				break;
+
+	if (n == 0) {
+		if (size != 0)
+			*dst = '\0';
+		while (*src++)
+			;
+	}
+
+	return src - s - 1;
+}
+
+char *
+strchr(const char *s, int c)
+{
+    while (*s) {
+        if (*s == (char)c)
+            return (char *)s;
+        s++;
+    }
+
+    return NULL;
+}
+
+char *
+strtok(char *str, const char *delim)
+{
+    static char *next;
+    char *start;
+
+    if (str)
+        next = str; // Start new tokenization
+    if (!next)
+        return NULL;
+
+    // Skip leading delimiters
+    while (*next && strchr(delim, *next))
+        next++;
+
+    if (*next == '\0') {
+        next = NULL;
+        return NULL;
+    }
+
+    start = next;
+
+    // Find end of token
+    while (*next && !strchr(delim, *next))
+        next++;
+
+    if (*next) {
+        *next = '\0';  // Null-terminate current token
+        next++;        // Advance to next token
+    } else {
+        next = NULL;   // No more tokens
+    }
+
+    return start;
+}
blob - /dev/null
blob + 2d328be32509ab6b0891b41f04da0e2b89b8c56b (mode 644)
--- /dev/null
+++ kernel/string.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "types.h"
+
+#define MAXLEN 64
+
+void *memcpy(void *dest, const void *src, ulong n);
+void *memset(void *dst, int c, ulong n);
+ulong strlen(const char *s);
+ulong strnlen(const char *s, ulong maxlen);
+ulong strlcpy(char *dst, const char *src, ulong size);
+char *strtok(char *str, const char *delim);
blob - /dev/null
blob + 916852f48c681b59bf1ddb4f25ef4f5542541f49 (mode 644)
--- /dev/null
+++ kernel/swtch.S
@@ -0,0 +1,45 @@
+.section .text
+.global swtch
+swtch:
+    # Save ra, sp, s0-s11 into old
+    sd ra, 0(a0)
+    sd sp, 8(a0)
+    sd s0, 16(a0)
+    sd s1, 24(a0)
+    sd s2, 32(a0)
+    sd s3, 40(a0)
+    sd s4, 48(a0)
+    sd s5, 56(a0)
+    sd s6, 64(a0)
+    sd s7, 72(a0)
+    sd s8, 80(a0)
+    sd s9, 88(a0)
+    sd s10, 96(a0)
+    sd s11, 104(a0)
+
+    #srli    t0, a2, 12             # convert to physical PPN (assuming identity mapping)
+    #li      t1, 8
+    #slli    t1, t1, 60             # t1 = SATP_MODE_SV39 (8 << 60)
+    #or      t0, t0, t1             # t0 = final satp value
+    #sfence.vma zero, zero          # flush TLB
+    #csrw    satp, t0               # load satp
+    #sfence.vma zero, zero          # flush TLB
+
+    # Load ra, sp, s0-s11 from new
+    ld ra, 0(a1)
+    ld sp, 8(a1)
+    ld s0, 16(a1)
+    ld s1, 24(a1)
+    ld s2, 32(a1)
+    ld s3, 40(a1)
+    ld s4, 48(a1)
+    ld s5, 56(a1)
+    ld s6, 64(a1)
+    ld s7, 72(a1)
+    ld s8, 80(a1)
+    ld s9, 88(a1)
+    ld s10, 96(a1)
+    ld s11, 104(a1)
+
+    # return to ra from new context
+    ret
blob - /dev/null
blob + c1b4b0434a0a0765702d2c648ae068103bdde098 (mode 644)
--- /dev/null
+++ kernel/syscall.c
@@ -0,0 +1,117 @@
+#include "syscall.h"
+#include "uart.h"
+#include "timer.h"
+#include "mm/mem.h"
+#include "mm/pagetable.h"
+#include "mm/user_copy.h"
+
+long
+sys_write(int fd, void *user_buf, uint len)
+{
+    if (fd != 1 && fd != 2)
+        return -1;
+
+    if (len > MAXLEN)
+        len = MAXLEN;
+
+    char buf[MAXLEN];
+    if (copy_from_user(buf, user_buf, len) < 0)
+        return -1;
+
+    int written = 0;
+    spin_lock(&uart_lock);
+    written = uart_putsn(buf, len);
+    spin_unlock(&uart_lock);
+
+    // return number of bytes written
+    return written;
+}
+
+long
+sys_read(int fd, void *user_buf, uint len)
+{
+    if (fd)
+        return -1;
+
+    if (len > MAXLEN)
+        len = MAXLEN;
+
+    DEBUG_PRINT(uart_puts("waiting for uart_gets()...\n"););
+
+    char buf[MAXLEN];
+    int read = uart_gets(buf, len);
+    if (copy_to_user(user_buf, buf, read) < 0)
+        return -1;
+
+    return read;
+}
+
+long
+sys_sleep_ms(cpu_t *c, proc_t *p, ulong ms)
+{
+    if (ms == 0 || ms > MAX_SLEEP_MS)
+        return -1;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Proc id ");
+        uart_putc('0' + p->pid);
+        uart_puts(" going to sleep for ");
+        uart_putlong(ms / 1000);
+        uart_puts(" seconds\n");
+    );
+    */
+
+    p->state = SLEEPING;
+    list_add_tail(&p->q_node, &c->sleep_queue);
+    p->sleep_until = read_time() + MS_TO_TIME(ms);
+
+    return 0;
+}
+
+long
+sys_sbrk(proc_t *p, long size)
+{
+    if (!p || !size)
+        return -1L;
+
+    ulong old_brk = ALIGN_UP(p->heap_end);
+    size = ALIGN_UP(size);
+
+    ulong new_brk = old_brk + size;
+    if (new_brk < p->heap_start || new_brk > USER_HEAP_END)
+        return -1L;
+
+    // Map new pages
+    if (size > 0) {
+        for (ulong va = old_brk; va < new_brk; va += PAGE_SIZE) {
+            void *pa = alloc_page();
+            if (!pa)
+                return -1L;
+
+            if (map_page(p->pagetable, va, (ulong)pa, PTE_R | PTE_W | PTE_U) == -1)
+                return -1L;
+        }
+    } else {
+        for (ulong va = new_brk; va < old_brk; va += PAGE_SIZE)
+            unmap_page(p->pagetable, va);
+    }
+
+    p->heap_end = new_brk;
+
+    return old_brk;
+}
+
+long
+sys_munmap(proc_t *p, ulong addr, ulong size)
+{
+    if (!p || !size || addr % PAGE_SIZE != 0)
+        return -1;
+
+    ulong end = ALIGN_UP(addr + size);
+    for (ulong va = addr; va < end; va += PAGE_SIZE)
+        if (unmap_page(p->pagetable, va) == -1)
+            return -1;
+
+    return 0;
+}
blob - /dev/null
blob + bdedd6169e97cad809d05ddab8aef24e1483a56c (mode 644)
--- /dev/null
+++ kernel/syscall.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "types.h"
+#include "proc.h"
+
+long sys_write(int fd, void *user_buf, uint len);
+long sys_read(int fd, void *user_buf, uint len);
+long sys_sleep_ms(cpu_t *c, proc_t *p, ulong ms);
+long sys_sbrk(proc_t *p, long size);
+long sys_munmap(proc_t *p, ulong addr, ulong size);
blob - /dev/null
blob + bfda256f9a20772472b196933619495f48e79cee (mode 644)
--- /dev/null
+++ kernel/timer.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "sched.h"
+#include "list.h"
+
+#define TIME_FREQ      10000000UL                   // 10 MHz
+#define TIMER_INTERVAL (TIME_FREQ / 100)            // 100 Hz
+#define MS_TO_TIME(ms) ((ms) * (TIME_FREQ / 1000))
+#define MAX_SLEEP_MS   60 * 1000                    // 1 minute
+
+static inline ulong
+read_time()
+{
+    return read_csr(time);
+}
+
+static inline void
+timer_init()
+{
+    write_csr(stimecmp, read_time() + TIMER_INTERVAL);
+}
+
+static inline void
+timer_handle()
+{
+    write_csr(stimecmp, read_time() + TIMER_INTERVAL);
+
+    //DEBUG_PRINT(
+      //  uart_puts("timer_handle()\n");
+    //);
+
+    cpu_t *c = curr_cpu();
+    int need_sched = 0;
+    ulong now = read_time();
+
+    list_node_t *pos, *tmp;
+    list_for_each_safe(pos, tmp, &c->sleep_queue) {
+        proc_t *p = container_of(pos, proc_t, q_node);
+        if (now >= p->sleep_until) {
+            list_del(&p->q_node);
+            p->state = RUNNABLE;
+            list_add_tail(&p->q_node, &c->run_queue);
+            need_sched = 1;
+
+            DEBUG_PRINT(
+                uart_puts("CPU ");
+                uart_putc('0' + curr_cpu()->id);
+                uart_puts(": proc id ");
+                uart_putc('0' + p->pid);
+                uart_puts(" became runnable\n");
+            );
+        }
+    }
+
+
+    if (need_sched)
+        curr_cpu()->needs_sched = 1;
+}
blob - /dev/null
blob + 0674f8e94b718b049e16f886ca8234c37929f41a (mode 644)
--- /dev/null
+++ kernel/trap/trap.c
@@ -0,0 +1,236 @@
+#include "trap.h"
+#include "../riscv.h"
+#include "../uart.h"
+#include "../types.h"
+#include "../proc.h"
+#include "../timer.h"
+#include "../syscall.h"
+#include "../user_syscall.h"
+#include "../string.h"
+#include "../sched.h"
+
+void
+syscall_handler(proc_t *p)
+{
+    cpu_t *c = curr_cpu();
+    int syscall_num = p->tf->regs[17]; // a7 (syscall number)
+
+    switch (syscall_num) {
+    case SYS_write: {
+        DEBUG_PRINT(uart_puts("SYS_write\n"););
+        p->tf->regs[10] = sys_write(p->tf->regs[10], (void *)p->tf->regs[11], p->tf->regs[12]);
+        break;
+    }
+    case SYS_read: {
+        DEBUG_PRINT(uart_puts("SYS_read\n"););
+        p->tf->regs[10] = sys_read(p->tf->regs[10], (void *)p->tf->regs[11], p->tf->regs[12]);
+        break;
+    }
+    case SYS_exit:
+        DEBUG_PRINT(uart_puts("SYS_exit\n"););
+        p->state = ZOMBIE;
+        c->needs_sched = 1;
+        break;
+    case SYS_getpid:
+        DEBUG_PRINT(uart_puts("SYS_getpid\n"););
+        p->tf->regs[10] = p->pid;
+        break;
+    case SYS_yield:
+        DEBUG_PRINT(uart_puts("SYS_yield\n"););
+        c->needs_sched = 1;
+        break;
+    case SYS_sleep_ms:
+        DEBUG_PRINT(uart_puts("SYS_sleep_ms\n"););
+        p->tf->regs[10] = sys_sleep_ms(c, p, p->tf->regs[10]);
+        c->needs_sched = 1;
+        break;
+    case SYS_sbrk:
+        p->tf->regs[10] = sys_sbrk(p, p->tf->regs[10]);
+        break;
+    case SYS_munmap:
+        p->tf->regs[10] = sys_munmap(p, p->tf->regs[10], p->tf->regs[11]);
+        break;
+    default:
+        DEBUG_PRINT(
+            uart_puts("Unknown syscall number: ");
+            uart_puthex(syscall_num);
+            uart_putc('\n');
+        );
+        p->tf->regs[10] = -1;
+        break;
+    }
+}
+
+void
+trap_handler(trap_frame_t *tf)
+{
+    cpu_t *c = curr_cpu();
+    proc_t *p = c->proc;
+
+    ulong cause = read_csr(scause);
+    ulong code  = SCAUSE_CODE(cause);
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Trap: ");
+        uart_puthex(code);
+        uart_putc('\n');
+
+        uart_puts("sepc: ");
+        uart_puthex(read_csr(sepc));
+        uart_puts("\n");
+
+        uart_puts("stval: ");
+        uart_puthex(read_csr(stval));
+        uart_puts("\n");
+    );
+    */
+
+    // Must not access tf on idle process, it is NULL!
+    if (p && p->is_idle) {
+        // Timer interrupt on idle core
+        if ((cause & SCAUSE_IRQ_BIT) && code == SCAUSE_TIMER_INTERRUPT) {
+            timer_handle();
+            goto end;
+        } else {
+            DEBUG_PRINT(
+                uart_puts("Trap on idle core? Should not happen, Halting...\n");
+            );
+            while (1)
+                asm volatile("wfi");
+        }
+    }
+
+    if (SCAUSE_CODE(tf->scause) == 5 && !(tf->scause & SCAUSE_IRQ_BIT)) {
+        DEBUG_PRINT(
+            uart_puts("Trap: ");
+            uart_puthex(SCAUSE_CODE(tf->scause));
+            uart_puts("\n");
+        );
+        while (1)
+            ;
+    }
+
+    // This shouldn't happen: every CPU should have a proc (idle or otherwise)
+    if (!p) {
+        // Timer interrupt
+        if ((cause & SCAUSE_IRQ_BIT) && code == SCAUSE_TIMER_INTERRUPT) {
+            timer_handle();
+            return;
+        }
+
+        // Weird - should not happen
+        DEBUG_PRINT(
+            uart_puts("No current process for CPU ");
+            uart_putc('0' + c->id);
+            uart_puts(" during trap!\n");
+        );
+        while (1)
+            asm volatile("wfi");
+    }
+
+    // Capture original process
+    //proc_t *orig_proc = p;
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Before saving tf: p->tf.sepc = ");
+        uart_puthex(p->tf.sepc);
+        uart_putc('\n');
+    );
+    */
+
+    //p->tf = tf;
+
+    // Enable interrupts (allow preemption)
+    //set_csr(sstatus, SSTATUS_SIE);
+
+    // Debug
+    /*
+    DEBUG_PRINT(
+        uart_puts("Trap frame at: ");
+        uart_puthex((ulong)&p->tf);
+        uart_putc('\n');
+        uart_puts("scause: ");
+        uart_puthex(p->tf.scause);
+        uart_putc('\n');
+    );
+    */
+
+    switch (code) {
+    case SCAUSE_USER_ECALL:
+        // User-mode (U-mode)
+        DEBUG_PRINT(
+            uart_puts("[S] U-mode system call received\n");
+        );
+        tf->sepc += 4;
+        syscall_handler(p);
+        break;
+    case SCAUSE_SUPERVISOR_ECALL:
+        // Supervisor-mode (S-mode)
+        DEBUG_PRINT(
+            uart_puts("[S] S-mode system call received\n");
+            //uart_puts("TF a7 = ");
+            //uart_puthex(p->tf.regs[17]);  // a7
+            //uart_putc('\n');
+        );
+        tf->sepc += 4;
+        syscall_handler(p);
+        break;
+    case SCAUSE_TIMER_INTERRUPT:
+        // Timer interrupt
+        if (cause & SCAUSE_IRQ_BIT) {
+            /*
+            DEBUG_PRINT(
+                uart_putc('0' + c->id);
+                uart_puts("Normal timer interrupt\n");
+            );
+            */
+
+            timer_handle();
+        }
+        break;
+    case SCAUSE_SUPERVISOR_IRQ:
+        DEBUG_PRINT(
+            uart_putc('0' + c->id);
+            uart_puts(": [S] Supervisor Software Interrupt\n");
+        );
+        while(1);
+        break;
+    case SCAUSE_ILLEGAL_INSTR:
+        DEBUG_PRINT(
+            uart_puts("CPU ");
+            uart_putc('0' + c->id);
+            uart_puts(": [S] Illegal instruction at ");
+            uart_puthex(tf->sepc);
+            uart_putc('\n');
+        );
+        while(1);
+        break;
+    default:
+        DEBUG_PRINT(
+            uart_puts("[S] Unknown trap! cause=");
+            uart_puthex(cause);
+            uart_puts("\nHalting.\n");
+        );
+        while (1)
+            asm volatile("wfi");
+    }
+
+    // Disable interrupts
+    //clear_csr(sstatus, SSTATUS_SIE);
+
+    /*
+    DEBUG_PRINT(
+        uart_puts("Before restoring tf: p->tf->sepc = ");
+        uart_puthex(p->tf.sepc);
+        uart_putc('\n');
+    );
+    */
+
+end:
+    if (curr_cpu()->needs_sched) {
+        curr_cpu()->needs_sched = 0;
+        schedule();
+    }
+}
blob - /dev/null
blob + 80a4601e9bf172a42c1adcfc13b81360d4c557fe (mode 644)
--- /dev/null
+++ kernel/trap/trap.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "../types.h"
+#include "../proc.h"
+
+#define SCAUSE_IRQ_BIT           (1UL << 63)
+#define SCAUSE_CODE(scause)      ((scause) & ~SCAUSE_IRQ_BIT)
+
+#define SCAUSE_SUPERVISOR_IRQ    1  // Supervisor software interrupt
+#define SCAUSE_ILLEGAL_INSTR     2  // Illegal instruction
+#define SCAUSE_TIMER_INTERRUPT   5  // Timer interrupt
+#define SCAUSE_USER_ECALL        8  // U-mode system call
+#define SCAUSE_SUPERVISOR_ECALL  9  // S-mode system call
+
+void trap_handler(trap_frame_t *tf);
+void trap_vector();                 // asm entry point
+void idle_trap_vector();            // idle entry point
blob - /dev/null
blob + 108eab0f1001d2fa8f69f96aae6ee87213679f8d (mode 644)
--- /dev/null
+++ kernel/trap/trap_vec.S
@@ -0,0 +1,150 @@
+.section .text
+.global trap_vector
+trap_vector:
+    # Save trap cause early
+    csrr t1, scause
+
+    # Check SPP bit in sstatus: 0 = user, 1 = supervisor
+    csrr t0, sstatus
+    andi t0, t0, (1 << 8)   # Check SPP bit (bit 8)
+    beqz t0, .from_user     # If SPP == 0 -> from user mode
+    j .from_kernel
+
+.from_user:
+    # Load trap frame pointer from sscratch
+    csrr t0, sscratch
+
+    # Save user sp into trap frame (x2), offset 16
+    sd sp, 16(t0)
+
+    # Move trap frame to sp
+    mv sp, t0
+    j .save_regs
+
+.from_kernel:
+    # We're already on kernel stack, use current sp directly
+    # Reserve space for trap frame (36 x 8 = 288 bytes)
+    addi sp, sp, -288
+
+.save_regs:
+    # Save x1–x31 (excluding x0, and x2 already saved if from_user)
+    # sd x0     0(sp)   # x0 (hardwired to zero)
+    sd x1,    8(sp)     # ra
+    # x2 (sp) was saved manually (at 16) in .from_user
+    sd x3,   24(sp)     # gp
+    sd x4,   32(sp)     # tp
+    sd x5,   40(sp)     # t0
+    sd x6,   48(sp)     # t1
+    sd x7,   56(sp)     # t2
+    sd x8,   64(sp)     # s0/fp
+    sd x9,   72(sp)     # s1
+    sd x10,  80(sp)     # a0
+    sd x11,  88(sp)     # a1
+    sd x12,  96(sp)     # a2
+    sd x13, 104(sp)     # a3
+    sd x14, 112(sp)     # a4
+    sd x15, 120(sp)     # a5
+    sd x16, 128(sp)     # a6
+    sd x17, 136(sp)     # a7
+    sd x18, 144(sp)     # s2
+    sd x19, 152(sp)     # s3
+    sd x20, 160(sp)     # s4
+    sd x21, 168(sp)     # s5
+    sd x22, 176(sp)     # s6
+    sd x23, 184(sp)     # s7
+    sd x24, 192(sp)     # s8
+    sd x25, 200(sp)     # s9
+    sd x26, 208(sp)     # s10
+    sd x27, 216(sp)     # s11
+    sd x28, 224(sp)     # t3
+    sd x29, 232(sp)     # t4
+    sd x30, 240(sp)     # t5
+    sd x31, 248(sp)     # t6
+
+    # Save CSRs
+    csrr t0, sepc
+    sd t0, 256(sp)
+    csrr t0, sstatus
+    sd t0, 264(sp)
+    sd t1, 272(sp)       # scause (already read)
+    csrr t0, stval
+    sd t0, 280(sp)
+
+    # Pass trap_frame_t* in a0
+    mv a0, sp
+
+    call trap_handler
+
+    # Restore registers
+    # sd x0     0(sp)   # x0 (hardwired to zero)
+    ld x1,    8(sp)     # ra
+    # x2 (sp) handle separately
+    ld x3,   24(sp)     # gp
+    ld x4,   32(sp)     # tp
+    ld x5,   40(sp)     # t0
+    ld x6,   48(sp)     # t1
+    ld x7,   56(sp)     # t2
+    ld x8,   64(sp)     # s0/fp
+    ld x9,   72(sp)     # s1
+    ld x10,  80(sp)     # a0
+    ld x11,  88(sp)     # a1
+    ld x12,  96(sp)     # a2
+    ld x13, 104(sp)     # a3
+    ld x14, 112(sp)     # a4
+    ld x15, 120(sp)     # a5
+    ld x16, 128(sp)     # a6
+    ld x17, 136(sp)     # a7
+    ld x18, 144(sp)     # s2
+    ld x19, 152(sp)     # s3
+    ld x20, 160(sp)     # s4
+    ld x21, 168(sp)     # s5
+    ld x22, 176(sp)     # s6
+    ld x23, 184(sp)     # s7
+    ld x24, 192(sp)     # s8
+    ld x25, 200(sp)     # s9
+    ld x26, 208(sp)     # s10
+    ld x27, 216(sp)     # s11
+    ld x28, 224(sp)     # t3
+    ld x29, 232(sp)     # t4
+    ld x30, 240(sp)     # t5
+    ld x31, 248(sp)     # t6
+
+    # Restore CSRs
+    ld t0, 256(sp)
+    csrw sepc, t0
+    ld t0, 264(sp)
+    csrw sstatus, t0
+
+    # If we entered from kernel mode, deallocate trap frame
+    csrr t0, sstatus
+    andi t0, t0, (1 << 8)
+    bnez t0, .restore_kernel_stack
+
+    # Restore user sp (x2)
+    ld x2, 16(sp)
+    sret
+
+.restore_kernel_stack:
+    addi sp, sp, 288
+    sret
+
+
+.global idle_trap_vector
+idle_trap_vector:
+    # Minimal context save
+    #addi sp, sp, -16
+    #sd ra, 0(sp)
+
+    csrr a0, scause
+    csrr a1, sepc
+    csrr a2, sstatus
+    csrr a3, stval
+
+    call trap_handler
+
+    #ld ra, 0(sp)
+    #addi sp, sp, 16
+
+    csrw sepc, a1
+    csrw sstatus, a2
+    sret
blob - /dev/null
blob + 5844b873e77732d2d514110a790de7428a6b6798 (mode 644)
--- /dev/null
+++ kernel/types.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#define NULL 0
+
+typedef unsigned int    uint;
+typedef unsigned long   ulong;
+typedef unsigned short  ushort;
+typedef unsigned char   uchar;
+typedef ulong pte_t;
+
+#ifdef DEBUG
+#define DEBUG_PRINT(...)                    \
+    do {                                    \
+        spin_lock(&uart_lock);              \
+        __VA_ARGS__;                        \
+        spin_unlock(&uart_lock);            \
+    } while (0)
+#else
+#define DEBUG_PRINT(...) do {} while (0)
+#endif
blob - /dev/null
blob + 06019d39d62c24cd95b519bda815c0bc2ba66290 (mode 644)
--- /dev/null
+++ kernel/uart.c
@@ -0,0 +1,101 @@
+#include "uart.h"
+
+spinlock_t uart_lock = SPINLOCK_INIT;
+
+void
+uart_putc(char c)
+{
+    *(volatile char *)UART0 = c;
+}
+
+void
+uart_puts(const char *s)
+{
+    while (*s)
+        uart_putc(*s++);
+}
+
+ulong
+uart_putsn(const char *s, ulong len)
+{
+    ulong written = 0;
+    while (len-- && *s) {
+        uart_putc(*s++);
+        written++;
+    }
+
+    return written;
+}
+
+void
+uart_puthex(ulong x)
+{
+    uart_puts("0x");
+    for (int i = (sizeof(x) * 2) - 1; i >= 0; i--) {
+        int nibble = (x >> (i * 4)) & 0xf;
+        uart_putc(nibble < 10 ? '0' + nibble : 'a' + nibble - 10);
+    }
+}
+
+void
+uart_putlong(ulong val)
+{
+    char buf[21]; // Max for 64-bit decimal is 20 digits + null
+    int i = 20;
+    buf[i--] = '\0';
+
+    if (val == 0) {
+        uart_putc('0');
+        return;
+    }
+
+    while (val > 0 && i >= 0) {
+        buf[i--] = '0' + (val % 10);
+        val /= 10;
+    }
+
+    uart_puts(&buf[i + 1]);
+}
+
+char uart_getc(void)
+{
+    volatile char *uart = (volatile char *)UART0;
+
+    /*
+    while (1) {
+        if (uart[UART_LSR] & UART_LSR_RX_READY) {
+            return uart[UART_RHR];  // return valid char
+        }
+
+        // Give up CPU while waiting — cooperative multitasking
+        syscall0(SYS_yield);
+    }
+    */
+
+    // Wait until data is available in receive buffer
+    while ((uart[UART_LSR] & UART_LSR_RX_READY) == 0)
+        ;
+
+    return uart[UART_RHR];
+}
+
+int
+uart_gets(char *buf, int maxlen)
+{
+    int i = 0;
+    while (i < maxlen - 1) {
+        char c = uart_getc();
+        if (c == '\r') // Enter key is usually CR
+            c = '\n';
+        uart_putc(c); // Echo back
+
+        if (c == '\n') {
+            buf[i++] = c;
+            break;
+        }
+        buf[i++] = c;
+    }
+    buf[i] = 0; // Null-terminate
+
+    return i;
+}
blob - /dev/null
blob + d0a9fdb3f60205ef88835e3c2d2dc3e90c8a8ec5 (mode 644)
--- /dev/null
+++ kernel/uart.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "spinlock.h"
+#include "mm/mapping.h"
+
+#define UART0               (0x10000000 + VA_OFFSET)
+#define UART_RHR            0           // Receive Holding Register (read only)
+#define UART_LSR            5           // Line Status Register
+#define UART_LSR_RX_READY   (1 << 0)    // Bit 0: Receiver data ready
+
+//extern ulong UART0;
+extern spinlock_t uart_lock;
+
+void uart_putc(char c);
+void uart_puts(const char* s);
+ulong uart_putsn(const char* s, ulong len);
+void uart_puthex(ulong x);
+void uart_putlong(ulong val);
+
+char uart_getc(void);
+int uart_gets(char *buf, int maxlen);
blob - /dev/null
blob + 4eb1d7055db17d4e6466ee0e69c2b029633fa65f (mode 644)
--- /dev/null
+++ kernel/user.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "user_syscall.h"
+
+static inline long
+write(const void *buf, unsigned long cnt)
+{
+    // only stdout for now
+    return syscall3(SYS_write, 1, (long)buf, cnt);
+}
+
+static inline void
+exit(int status)
+{
+    syscall1(SYS_exit, status);
+    //while (1) {} // Should never return
+}
+
+static inline long
+getpid()
+{
+    return syscall0(SYS_getpid);
+}
+
+static inline void
+yield()
+{
+    syscall0(SYS_yield);
+}
+
+static inline long
+read(void *buf, unsigned long cnt)
+{
+    return syscall3(SYS_read, 0, (long)buf, cnt);
+}
+
+static inline long
+sleep(unsigned long ms)
+{
+    if (ms == 0 || ms > (60 * 1000))
+        return 0;
+
+    return syscall1(SYS_sleep_ms, ms);
+}
+
+static inline long
+sbrk(unsigned long size)
+{
+    return syscall1(SYS_sbrk, size);
+}
+
+static inline long
+munmap(void *addr, unsigned long size)
+{
+    return syscall2(SYS_munmap, (long)addr, size);
+}
+
+// Move to stdlib
+void *
+malloc(unsigned long size)
+{
+    long mem = sbrk(size);
+    if (mem == -1)
+        return 0; //NULL;
+
+    return (void *)mem;
+}
blob - /dev/null
blob + ac61b53e1c52a32f9ba7b43cd6f9b7c8b23ace43 (mode 644)
--- /dev/null
+++ kernel/user.ld
@@ -0,0 +1,24 @@
+ENTRY(user_shell_main)
+
+SECTIONS {
+  . = 0x84000000;
+
+  .text : {
+    PROVIDE(user_shell_main = .);
+    *(.text.user_shell_main)
+    *(.text*)
+  }
+
+  .rodata : {
+    *(.rodata*)
+  }
+
+  .data : {
+    *(.data*)
+  }
+
+  .bss : {
+    *(.bss*)
+    *(COMMON)
+  }
+}
blob - /dev/null
blob + 5b4c0faadacf3147c4d5585ad3a17e82a32fcac5 (mode 644)
--- /dev/null
+++ kernel/user_syscall.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#define SYS_write       1 //64
+#define SYS_exit        2 //93
+#define SYS_getpid      3 //172
+#define SYS_yield       4 //124
+#define SYS_read        5 //63
+#define SYS_sleep_ms    6
+#define SYS_sbrk        7
+#define SYS_munmap      8
+
+//#define SYS_fork    220
+//#define SYS_open    102
+//#define SYS_close   57
+
+static inline long
+syscall(long num, long arg0, long arg1, long arg2,
+                  long arg3, long arg4, long arg5)
+{
+    register long a0 asm("a0") = arg0;
+    register long a1 asm("a1") = arg1;
+    register long a2 asm("a2") = arg2;
+    register long a3 asm("a3") = arg3;
+    register long a4 asm("a4") = arg4;
+    register long a5 asm("a5") = arg5;
+    register long a7 asm("a7") = num;
+
+    asm volatile (
+        "ecall"
+        : "+r"(a0)
+        : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a7)
+        : "memory"
+    );
+
+    return a0;
+}
+
+static inline long
+syscall0(long num)
+{
+    return syscall(num, 0, 0, 0, 0, 0, 0);
+}
+
+static inline long
+syscall1(long num, long arg0)
+{
+    return syscall(num, arg0, 0, 0, 0, 0, 0);
+}
+
+static inline long
+syscall2(long num, long arg0, long arg1)
+{
+    return syscall(num, arg0, arg1, 0, 0, 0, 0);
+}
+
+static inline long
+syscall3(long num, long arg0, long arg1, long arg2)
+{
+    return syscall(num, arg0, arg1, arg2, 0, 0, 0);
+}