os-workshop

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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:
Aproject/xos-kernel.app.mk | 11+++++++++++
Axos/inc/xos/status.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axos/src/entry.S | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axos/src/kernel.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axos/src/kernel.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axos/src/start.S | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axos/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; +} + +