commit 23aced7b0db54cc9239d2bd0912903ab5cd6081b
parent e1a2886254558c931f79ac5e6362d795a54f1fff
Author: Brian Swetland <swetland@frotz.net>
Date: Wed, 18 May 2022 17:16:45 -0700
xos: initial commit
- based on the example 25 kernel
- simplified/unified trap handling
- direct syscall dispatcher
- start of system headers, error codes
- organize code across multiple source files
Diffstat:
A | project/xos-kernel.app.mk | | | 11 | +++++++++++ |
A | xos/inc/xos/status.h | | | 65 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xos/src/entry.S | | | 165 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xos/src/kernel.c | | | 159 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xos/src/kernel.h | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xos/src/start.S | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | xos/src/vmm.c | | | 80 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 635 insertions(+), 0 deletions(-)
diff --git a/project/xos-kernel.app.mk b/project/xos-kernel.app.mk
@@ -0,0 +1,11 @@
+
+MOD_NAME := xos-kernel
+MOD_SRC := xos/src/start.S xos/src/entry.S
+MOD_SRC += xos/src/kernel.c xos/src/vmm.c
+MOD_SRC += hw/src/context-switch.S
+MOD_SRC += hw/src/debug-printf.c hw/src/debug-io.c hw/src/print-exception.c
+MOD_LIB := c
+MOD_INC := xos/inc
+MOD_EXT := out/ex25-user.bin
+MOD_LDSCRIPT := make/kernel.ram.ld
+include make/app.mk
diff --git a/xos/inc/xos/status.h b/xos/inc/xos/status.h
@@ -0,0 +1,65 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#pragma once
+
+//-------------------------------------------------------------
+// Success
+#define XOS_OK 0x0000
+
+//-------------------------------------------------------------
+// Timeout Expired
+#define XOS_TIMEOUT 0x0001
+
+// Not Ready Yet, Try Later (eg, no data to read from socket)
+#define XOS_WAIT 0x0002
+
+// Far Endpoint No Longer Available
+#define XOS_PEER_CLOSED 0x0003
+
+//-------------------------------------------------------------
+#define XOS_ERROR 0x0080
+
+//-------------------------------------------------------------
+// IO Error
+#define XOS_ERR_IO 0x0081
+
+// Cannot locate requested entity
+#define XOS_ERR_NOT_FOUND 0x0082
+
+// Cannot do this operation on this object
+#define XOS_ERR_NOT_SUPPORTED 0x0083
+
+// Out of memory, resources, etc
+#define XOS_ERR_NOT_AVAILABLE 0x0084
+
+// Already Exists, Already Bound, Already Mapped, etc
+#define XOS_ERR_CONFLICT 0x0085
+
+// Caller lacks permission for this operation
+#define XOS_ERR_PERMISSION 0x0086
+
+//-------------------------------------------------------------
+// Invalid Argument: Call Specific
+#define XOS_ERR_BAD_PARAM 0x0040
+
+// Invalid Argument: Bad Handle
+#define XOS_ERR_BAD_HANDLE 0x0041
+
+// Inavlid Argument: Wrong Type of Handle
+#define XOS_ERR_BAD_TYPE 0x0042
+
+// Invalid Argument: Bogus Pointer
+#define XOS_ERR_BAD_POINTER 0x0043
+
+// Invalid Argument: Out of Range
+#define XOS_ERR_BAD_RANGE 0x0044
+
+// Invalid Argument: Buffer Too Small
+#define XOS_ERR_BAD_BUFFER 0x0045
+
+// Invalid System Call Number
+#define XOS_ERR_BAD_SYSCALL 0x0046
+
+// Action Impossible
+#define XOS_ERR_BAD_STATE 0x0047
diff --git a/xos/src/entry.S b/xos/src/entry.S
@@ -0,0 +1,165 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#include <hw/riscv.h>
+#include <xos/status.h>
+
+.global trap_entry
+trap_entry:
+ // swap the user sp with the kernel sp
+ // stored in SSCRATCH
+ csrrw sp, sscratch, sp
+
+ // reserve stack space for a full eframe_t
+ addi sp, sp, - (32 * 4)
+
+ // save caller-save registers
+ sw ra, 0x00(sp)
+ sw t0, 0x04(sp)
+ sw t1, 0x08(sp)
+ sw t2, 0x0C(sp)
+ sw t3, 0x10(sp)
+ sw t4, 0x14(sp)
+ sw t5, 0x18(sp)
+ sw t6, 0x1C(sp)
+ sw a0, 0x20(sp)
+ sw a1, 0x24(sp)
+ sw a2, 0x28(sp)
+ sw a3, 0x2C(sp)
+ sw a4, 0x30(sp)
+ sw a5, 0x34(sp)
+ sw a6, 0x38(sp)
+ sw a7, 0x3C(sp)
+
+ // save user pc which hw stashed in SEPC
+ // and user sp which we stashed in SSCRATCH
+ csrr t1, sepc
+ csrr t2, sscratch
+ sw t1, 0x40(sp)
+ sw t2, 0x44(sp)
+
+ // save callee-save registers
+ sw gp, 0x48(sp)
+ sw tp, 0x4C(sp)
+ sw s0, 0x50(sp)
+ sw s1, 0x54(sp)
+ sw s2, 0x58(sp)
+ sw s3, 0x5C(sp)
+ sw s4, 0x60(sp)
+ sw s5, 0x64(sp)
+ sw s6, 0x68(sp)
+ sw s7, 0x6C(sp)
+ sw s8, 0x70(sp)
+ sw s9, 0x74(sp)
+ sw s10, 0x78(sp)
+ sw s11, 0x7C(sp)
+
+ // setup kernel thread pointer
+ // thread struct is at top of kstack
+ addi tp, sp, (32 * 4)
+
+ // if the scause high bit is clear, it is an exception
+ csrr t1, scause
+ bge t1, zero, exception_entry
+
+ // otherwise it is an interrupt, call interrupt_handler()
+ jal interrupt_handler
+
+.globl trap_exit
+trap_exit:
+ // TODO: check tp->reschedule flag
+
+ // restore initial kernel sp to sscratch
+ // (which is the same as the kernel tp)
+ csrw sscratch, tp
+
+ // restore callee-save registers
+ lw gp, 0x48(sp)
+ lw tp, 0x4C(sp)
+ lw s0, 0x50(sp)
+ lw s1, 0x54(sp)
+ lw s2, 0x58(sp)
+ lw s3, 0x5C(sp)
+ lw s4, 0x60(sp)
+ lw s5, 0x64(sp)
+ lw s6, 0x68(sp)
+ lw s7, 0x6C(sp)
+ lw s8, 0x70(sp)
+ lw s9, 0x74(sp)
+ lw s10, 0x78(sp)
+ lw s11, 0x7C(sp)
+
+ // user pc goes into sepc for sret
+ lw t1, 0x40(sp)
+ csrw sepc, t1
+
+ // restore the caller-save registers
+ lw ra, 0x00(sp)
+ lw t0, 0x04(sp)
+ lw t1, 0x08(sp)
+ lw t2, 0x0C(sp)
+ lw t3, 0x10(sp)
+ lw t4, 0x14(sp)
+ lw t5, 0x18(sp)
+ lw t6, 0x1C(sp)
+ lw a0, 0x20(sp)
+ lw a1, 0x24(sp)
+ lw a2, 0x28(sp)
+ lw a3, 0x2C(sp)
+ lw a4, 0x30(sp)
+ lw a5, 0x34(sp)
+ lw a6, 0x38(sp)
+ lw a7, 0x3C(sp)
+
+ // finally restore the user sp
+ lw sp, 0x44(sp)
+
+ // and return from trap
+ sret
+
+exception_entry:
+ // if scause is ECALL_UMODE, it is a system call
+ addi t2, zero, EXCn_ECALL_UMODE
+ andi t3, t1, 0xFF
+ beq t2, t3, syscall_entry
+
+ mv a0, sp
+ la ra, trap_exit
+ j exception_handler
+
+syscall_entry:
+ li t1, 2 // SYSCALL_COUNT
+ bgeu t0, t1, illegal_syscall
+
+ // fn = [syscall_table + (syscall_number << 2)]
+ slli t0, t0, 2
+ la t1, syscall_table
+ add t2, t1, t0
+ lw t3, 0(t2)
+
+ jalr t3
+
+ // result -> user.a0
+ sw a0, 0x20(sp)
+ // user.pc += 4
+ lw t0, 0x40(sp)
+ addi t0, t0, 4
+ sw t0, 0x40(sp)
+
+ j trap_exit
+
+syscall_table:
+ .word sys_exit
+ .word sys_xputc
+syscall_table_end:
+
+illegal_syscall:
+ addi t0, zero, XOS_ERR_BAD_SYSCALL
+ sw t0, 0x20(sp)
+ j trap_exit
+
+.globl user_mode_entry
+user_mode_entry:
+ mv sp, a0
+ addi tp, sp, (32 * 4)
+ j trap_exit
diff --git a/xos/src/kernel.c b/xos/src/kernel.c
@@ -0,0 +1,159 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#include "kernel.h"
+
+#include <hw/debug.h>
+#include <hw/platform.h>
+#include <hw/litex.h>
+
+#include <string.h>
+
+// kernel page directory
+static uint32_t *kpgdir;
+
+// load general registers from eframe
+// save ef + sizeof(eframe_t) as kernel sp and tp for next trap
+// enter user mode at ef->pc
+void user_mode_entry(eframe_t* ef);
+
+extern char __extra_start[];
+extern char __extra_end[];
+
+// the top bits on a kernel stack
+typedef struct {
+ eframe_t eframe;
+ thread_t thread;
+} kstack_top_t;
+
+void start_user_program(void) {
+ vaddr_t user_start = 0x10000000;
+ vaddr_t user_stack = 0x10400000;
+
+ xprintf("user.bin %u bytes\n", (__extra_end - __extra_start));
+
+ // allocate 16KB (4 pages) for user text/data
+ vm_map_4k(kpgdir, user_start + 0*PAGE_SIZE, ppage_alloc_z(), USER_RWX);
+ vm_map_4k(kpgdir, user_start + 1*PAGE_SIZE, ppage_alloc_z(), USER_RWX);
+ vm_map_4k(kpgdir, user_start + 2*PAGE_SIZE, ppage_alloc_z(), USER_RWX);
+ vm_map_4k(kpgdir, user_start + 3*PAGE_SIZE, ppage_alloc_z(), USER_RWX);
+
+ // allocate a 4KB (1 page) for user stack
+ vm_map_4k(kpgdir, user_stack - 1*PAGE_SIZE, ppage_alloc_z(), USER_RW);
+
+ // allow S-MODE writes to U-MODE pages
+ csr_set(CSR_SSTATUS, SSTATUS_SUM);
+
+ // copy embedded user.bin into user memory
+ memcpy((void*) user_start, __extra_start, __extra_end - __extra_start);
+
+ csr_clr(CSR_SSTATUS, SSTATUS_SUM);
+
+ // allocate a kernel stack page
+ // setup an eframe with the initial user register state
+ kstack_top_t *kst = kpage_alloc_z() + PAGE_SIZE - sizeof(kstack_top_t);
+ kst->eframe.pc = user_start;
+ kst->eframe.sp = user_stack;
+
+ // set previous privilege mode to user (0)
+ csr_clr(CSR_SSTATUS, SSTATUS_SPP);
+
+ xprintf("\n[ starting user mode program ]\n\n");
+
+ user_mode_entry(&kst->eframe);
+ // does not return
+}
+
+extern char __text_start[];
+extern char __rodata_start[];
+extern char __data_start[];
+extern char __bss_start[];
+extern char __bss_end[];
+extern char __image_end[];
+extern char __memory_top[];
+
+// On entry:
+// SP = __memory_top (top of ram)
+// SATP points at __memory_top - 8K
+
+void start(void) {
+ xprintf("X/OS Kernel v0.1\n\n");
+
+ xprintf("text %p %u\n", __text_start, __rodata_start - __text_start);
+ xprintf("rodata %p %u\n", __rodata_start, __data_start - __rodata_start);
+ xprintf("data %p %u\n", __data_start, __bss_start - __data_start);
+ xprintf("bss %p %u\n", __bss_start, __bss_end - __bss_start);
+ xprintf("free %p %u\n", __image_end, __memory_top - __image_end);
+ xprintf("memtop %p\n", __memory_top);
+
+ // set trap vector to trap_entry() in trap-entry-single.S
+ // it will call exception_handler() or interrupt_handler()
+ csr_write(CSR_STVEC, (uintptr_t) trap_entry);
+
+ // the topmost page is the boot stack
+ // the second topmost page is the boot page directory
+
+ // give all other pages from end-of-kernel to boot pagedir
+ // to the virtual memory manager
+ vmm_init(kva_to_pa(__image_end), kva_to_pa(__memory_top) - 2 * PAGE_SIZE);
+
+ // build a more correct page table
+ kpgdir = kpage_alloc_z();
+
+ char *va = __text_start;
+ // map kernel text RX
+ while (va < __rodata_start) {
+ vm_map_4k(kpgdir, (vaddr_t) va, kva_to_pa(va), KERNEL_RX);
+ va += PAGE_SIZE;
+ }
+ // map kernel rodata RO
+ while (va < __data_start) {
+ vm_map_4k(kpgdir, (vaddr_t) va, kva_to_pa(va), KERNEL_RO);
+ va += PAGE_SIZE;
+ }
+ // map kernel data and the rest of ram RW
+ char *end = (void*) ((((uint32_t) __image_end) + 0x003FFFFF) & 0xFFC00000);
+ while (va < end) {
+ vm_map_4k(kpgdir, (vaddr_t) va, kva_to_pa(va), KERNEL_RW);
+ va += PAGE_SIZE;
+ }
+ // map as much as possible as 4MB megapages
+ while (va < __memory_top) {
+ vm_map_4m(kpgdir, (vaddr_t) va, kva_to_pa(va), KERNEL_RW);
+ va += 4*1024*1024;
+ }
+ // map mmio region
+ vm_map_4m(kpgdir, 0xF0000000, 0xF0000000, KERNEL_RW);
+
+ csr_write(CSR_SATP, SATP_MODE_SV32 | (kva_to_pa(kpgdir) >> 12));
+ tlb_flush_all();
+
+ start_user_program();
+}
+
+status_t sys_xputc(uint32_t ch) {
+ xputc(ch);
+ return XOS_OK;
+}
+
+status_t sys_exit(int n) {
+ xprintf("\n[ user exit (status %d) ]\n", n);
+ for (;;) ;
+ return XOS_OK;
+}
+
+void interrupt_handler(void) {
+}
+
+// if an exception occurs, dump register state and halt
+void exception_handler(eframe_t *ef) {
+ xprintf("\n** SUPERVISOR EXCEPTION **\n");
+ xprint_s_exception(ef);
+ xprintf("\nHALT\n");
+ for (;;) ;
+}
+
+void panic(const char *msg) {
+ xprintf("\n** kernel panic: %s **\n", msg);
+ for (;;) ;
+}
diff --git a/xos/src/kernel.h b/xos/src/kernel.h
@@ -0,0 +1,61 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#pragma once
+
+#include <xos/status.h>
+
+#include <hw/riscv.h>
+#include <hw/context.h>
+#include <hw/intrinsics.h>
+
+#define PAGE_SIZE 4096
+
+#define KVA_BASE 0xC0000000
+#define KPA_BASE 0x40000000
+
+void panic(const char* msg);
+
+// vm.c
+typedef uint32_t paddr_t;
+typedef uint32_t vaddr_t;
+typedef uint32_t status_t;
+
+static inline void* pa_to_kva(paddr_t pa) {
+ return (void*) (pa + (KVA_BASE - KPA_BASE));
+}
+static inline paddr_t kva_to_pa(void *kva) {
+ return ((paddr_t) kva) - (KVA_BASE - KPA_BASE);
+}
+
+// allocate a (zero'd) physical page, return ppa
+paddr_t ppage_alloc_z(void);
+
+// allocate a (zero'd) physical page, return kva
+void *kpage_alloc_z(void);
+
+void vmm_init(paddr_t lo, paddr_t hi);
+status_t vm_map_4k(uint32_t *pdir, vaddr_t va, paddr_t pa, uint32_t attrs);
+status_t vm_map_4m(uint32_t *pdir, vaddr_t va, paddr_t pa, uint32_t attrs);
+
+#define USER_RW (PTE_D|PTE_A|PTE_U|PTE_R|PTE_W|PTE_V)
+#define USER_RX (PTE_D|PTE_A|PTE_U|PTE_R|PTE_X|PTE_V)
+#define USER_RWX (PTE_D|PTE_A|PTE_U|PTE_R|PTE_W|PTE_X|PTE_V)
+
+#define KERNEL_RO (PTE_D|PTE_A|PTE_R|PTE_V)
+#define KERNEL_RW (PTE_D|PTE_A|PTE_R|PTE_W|PTE_V)
+#define KERNEL_RX (PTE_D|PTE_A|PTE_R|PTE_X|PTE_V)
+
+// thread.c
+typedef struct {
+ uint32_t magic;
+ uint32_t id;
+ uint32_t *pgdir;
+ uint32_t reschedule;
+
+ uint32_t reserved0;
+ uint32_t reserved1;
+ uint32_t reserved2;
+ uint32_t reserved3;
+} thread_t;
+
diff --git a/xos/src/start.S b/xos/src/start.S
@@ -0,0 +1,94 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#include <hw/riscv.h>
+
+// We start running at the physical start of memory (0x40008000)
+// We are linked at the virtual address 0xC0000000
+
+// So we must first setup an initial page table that maps
+// ram to 0xC0000000, maps the first megapage of ram
+// to 0x40000000 (so we don't pull the rug out from under us
+// when we switch on the MMU), and maps the start of MMIO
+// space at 0xF0000000 so we can still talk to the serial port
+
+#define PTE_BITS_RWX (PTE_A | PTE_D | PTE_X | PTE_W | PTE_R | PTE_V)
+#define PTE_BITS_RW (PTE_A | PTE_D | PTE_W | PTE_R | PTE_V)
+
+.globl _start
+_start:
+ // zero BSS
+ la t0, __bss_start
+ la t1, __bss_end
+zero_loop:
+ beq t0, t1, zero_done
+ sw zero, 0(t0)
+ add t0, t0, 4
+ j zero_loop
+zero_done:
+
+ // physical memory top
+ // (since it's pc relative and pc is physical right now)
+ la sp, __memory_top
+
+ // use the next to last page (before the stack)
+ // for a bootstrap MMU page directory
+ li t4, 4096
+ sub t1, sp, t4 // t1 = end of pagetable
+ sub t0, t1, t4 // t0 = start of pagetable
+ mv t2, t0 // t2 = pagetable
+
+ // virtual memory top / boot stack
+ lw sp, start_sp
+
+ptbl_zero_loop:
+ sw zero, 0(t0)
+ addi t0, t0, 4
+ bne t0, t1, ptbl_zero_loop
+
+ // identity map a 4MB page where we're running now
+ li t0, (0x40000000 >> 2) | PTE_BITS_RWX
+ li t1, (0x40000000 >> 20) // offset of pte
+ add t1, t2, t1
+ sw t0, 0(t1)
+
+ // map ram at 0xC0000000 where the kernel is linked
+ li t1, (0xC0000000 >> 20) // offset of pte at start of ram
+ srli t3, sp, 20 // offset of pte at end of ram
+ li t4, (0x00400000 >> 2) // 4MB ppn increment
+ptbl_map_loop:
+ add t5, t2, t1 // t5 = pagetable + pte offset
+ sw t0, 0(t5) // store pte
+ add t1, t1, 4 // increment offset to next pte
+ add t0, t0, t4 // increment ppn to next megapage
+ bne t1, t3, ptbl_map_loop
+
+ // identity map the first 4MB of the mmio space
+ li t0, (0xF0000000 >> 2) | PTE_BITS_RW
+ li t1, (0xF0000000 >> 20) // offset of pte
+ add t1, t2, t1
+ sw t0, 0(t1)
+
+ // enable paging
+ srli t0, t2, 12
+ li t1, SATP_MODE_SV32
+ or t0, t0, t1
+ csrw satp, t0
+
+ // flush TLB
+ sfence.vma zero,zero
+
+ nop
+ nop
+ nop
+ nop
+ nop
+
+ // absolute jump to kernel start
+ lw t0, start_pc
+ jr t0
+
+start_sp:
+ .word __memory_top
+start_pc:
+ .word start
diff --git a/xos/src/vmm.c b/xos/src/vmm.c
@@ -0,0 +1,80 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#include "kernel.h"
+
+#include <string.h>
+
+static paddr_t pa_top;
+static paddr_t pa_bottom;
+
+void vmm_init(paddr_t start, paddr_t end) {
+ pa_bottom = start;
+ pa_top = end;
+}
+
+paddr_t ppage_alloc_z(void) {
+ if (pa_top == pa_bottom) {
+ panic("out of physical pages");
+ }
+ pa_top -= PAGE_SIZE;
+ memset(pa_to_kva(pa_top), 0, PAGE_SIZE);
+ return pa_top;
+}
+
+void *kpage_alloc_z(void) {
+ return pa_to_kva(ppage_alloc_z());
+}
+
+#define PTE_PA(pte) (((pte) & 0xFFFFFC00) << 2)
+
+status_t vm_map_4k(uint32_t* pdir, vaddr_t va, paddr_t pa, uint32_t flags) {
+ uint32_t idx1 = va >> 22;
+ uint32_t idx0 = (va >> 12) & 0x3FF;
+ uint32_t *ptbl;
+
+ if ((va & 0x00000FFF) || (pa & 0x00000FFF)) {
+ return XOS_ERR_BAD_PARAM;
+ }
+
+ uint32_t pte = pdir[idx1];
+ if (pte & PTE_V) { // valid pgdir entry
+ if (pte & (PTE_X|PTE_W|PTE_R)) {
+ return XOS_ERR_CONFLICT;
+ }
+ ptbl = pa_to_kva(PTE_PA(pte));
+ } else { // no entry, allocate a pagetable
+ uint32_t ptbl_pa = ppage_alloc_z();
+ ptbl = pa_to_kva(ptbl_pa);
+ pdir[idx1] = (ptbl_pa >> 2) | PTE_V;
+ }
+
+ pte = ptbl[idx0];
+ if (pte & PTE_V) {
+ return -1; // 4KB page already here
+ }
+
+ ptbl[idx0] = ((pa & 0xFFFFF000) >> 2) | (flags & 0x3FF);
+
+ tlb_flush_all();
+ return XOS_OK;
+}
+
+status_t vm_map_4m(uint32_t* pdir, vaddr_t va, paddr_t pa, uint32_t flags) {
+ uint32_t idx1 = va >> 22;
+
+ if ((va & 0x003FFFFF) || (pa & 0x003FFFFF)) {
+ return XOS_ERR_BAD_PARAM;
+ }
+
+ uint32_t pte = pdir[idx1];
+ if (pte & PTE_V) { // valid pgdir entry
+ return XOS_ERR_CONFLICT;
+ }
+ pdir[idx1] = ((pa & 0xFFFFF000) >> 2) | (flags & 0x3FF);
+
+ tlb_flush_all();
+ return XOS_OK;
+}
+
+