commit 77fdb85750d58975361feae858277cc65c1906d5 from: Niko Nastonen date: Mon Oct 5 17:46:25 2026 UTC Initial import 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.
+
+Install qemu-system-riscv64
+cd kernel
+make clean
+./build_shell.sh
+make run [NODEBUG=1]
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 + */ +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); +}