From 8d0178aff1e7284dca8c8a289f7edf8f963ac2ad Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Mon, 21 Jun 2021 11:12:33 +0200 Subject: [PATCH] initial commit --- .gitignore | 3 + LICENSE | 21 +++++++ Makefile | 45 +++++++++++++++ README.md | 14 +++++ kernel.c | 45 +++++++++++++++ kernel.h | 24 ++++++++ kinclude/boot.S | 103 +++++++++++++++++++++++++++++++++++ kinclude/csr.c | 40 ++++++++++++++ kinclude/csr.h | 38 +++++++++++++ kinclude/ecall.c | 80 +++++++++++++++++++++++++++ kinclude/ecall.h | 21 +++++++ kinclude/mutex.c | 37 +++++++++++++ kinclude/mutex.h | 13 +++++ kinclude/sched.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++ kinclude/sched.h | 37 +++++++++++++ lib/stdlib.c | 69 +++++++++++++++++++++++ lib/stdlib.h | 23 ++++++++ 17 files changed, 752 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 kernel.c create mode 100644 kernel.h create mode 100644 kinclude/boot.S create mode 100644 kinclude/csr.c create mode 100644 kinclude/csr.h create mode 100644 kinclude/ecall.c create mode 100644 kinclude/ecall.h create mode 100644 kinclude/mutex.c create mode 100644 kinclude/mutex.h create mode 100644 kinclude/sched.c create mode 100644 kinclude/sched.h create mode 100644 lib/stdlib.c create mode 100644 lib/stdlib.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..defab47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +out/ +*.o +obj/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e3f96f8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Anton Lydike + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee83dbf --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# simple makefile for this kernel project +# This is BAD. it should be reworked before anyone else uses this. + +# kernel lib dir +KLIBDIR=kinclude +# object file dir +ODIR=obj +# target dir +TARGET=out + +GCC_PREF=riscv32-unknown-elf- + +CC=$(GCC_PREF)gcc +OBJDUMP=$(GCC_PREF)objdump +CFLAGS=-I$(KLIBDIR) -march=rv32im -O3 +KERNEL_CFLAGS=-nostdlib + +# dependencies that need to be built: +_DEPS = ecall.c csr.c mutex.c sched.c + +# dependencies as object files: +_OBJ = ecall.o mutex.o sched.o boot.o csr.o + + +DEPS = $(patsubst %,$(KLIBDIR)/%,$(_DEPS)) +OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) + + +$(ODIR)/boot.o: + $(CC) -c -o $@ $(KLIBDIR)/boot.S $(CFLAGS) + +$(ODIR)/%.o: + $(CC) -c -o $@ $(KLIBDIR)/$*.c $(CFLAGS) + +kernel: $(OBJ) + mkdir -p $(TARGET) + $(CC) -o $(TARGET)/$@ $^ kernel.c $(CFLAGS) $(KERNEL_CFLAGS) + +kernel-dump: kernel + $(OBJDUMP) -SFlDf $(TARGET)/kernel > $(TARGET)/kernel-objects + +.PHONY: clean + +clean: + rm -rf $(ODIR) *~ $(KLIBDIR)/*~ $(TARGET) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..20af3dd --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# EMBARK: An Educational and Modifiable BAsic RISC-V Kernel + +EMBARK is a small kernel, designed for educational projects. It has very limited scope and is designed to be extensible. + + + + +## The toolchain: + +I am using the [riscv-gnu-toolchain](https://github.com/riscv/riscv-gnu-toolchain), configured with `--with-arch=rv32im --disable-linux --disable-gdb --disable-multilib`. + +## The Makefile: + +You can build the kernel using `make kernel`. Make sure the toolchain is in your path! diff --git a/kernel.c b/kernel.c new file mode 100644 index 0000000..b8cd188 --- /dev/null +++ b/kernel.c @@ -0,0 +1,45 @@ +#include "kernel.h" +#include "ecall.h" +#include "sched.h" +#include "mutex.h" + +void thread_1(); + +extern ProcessControlBlock processes[PROCESS_COUNT]; + +extern void init() +{ + // set up processes + processes[0].pid = 1; + processes[0].pc = (int) thread_1; + processes[0].regs[2] = 128; + processes[0].status = PROC_RDY; + processes[0].requested_lock = 0; + + processes[1].pid = 2; + processes[1].pc = (int) thread_1; + processes[1].regs[2] = 256; + processes[1].status = PROC_RDY; + processes[1].requested_lock = 0; + + scheduler_run_next(); +} + +void thread_1() { + int a = 0; // a4 + int b = 0; // a5 + + while (true) { + a++; + if (a > 1000000) { + __asm__ __volatile__ ( + "ebreak" + ); + b++; + a = 0; + } + if (b > 1000000) { + b = 0; + } + } +} \ No newline at end of file diff --git a/kernel.h b/kernel.h new file mode 100644 index 0000000..0d872de --- /dev/null +++ b/kernel.h @@ -0,0 +1,24 @@ +#ifndef H_KERNEL +#define H_KERNEL + +#define true 1 +#define false 0 + +#define XLEN 32 // 32 bit system +#define MUTEX_COUNT 64 // must be multiple of xlen +#define PROCESS_COUNT 64 +#define MAX_INT 0x7FFFFFFF // max 32 bit signed int + +// memory layout: +#define ROM_START 0x00100 +#define IO_START 0x10000 +#define NVM_START 0x20000 +#define RAM_START 0x50000 + +// scheduler settings +#define TIME_SLICE_LEN 100 // number of cpu time ticks per slice + +// init function +extern __attribute__((__noreturn__)) void init(); + +#endif \ No newline at end of file diff --git a/kinclude/boot.S b/kinclude/boot.S new file mode 100644 index 0000000..cfba7b8 --- /dev/null +++ b/kinclude/boot.S @@ -0,0 +1,103 @@ +.section .stack + +stack_bottom: +.space 4096 +stack_top: + + +.section .text + + +// Set up all the CSR mstatus_offsets +.set mstatus, 0x300 // machine status +.set mscratch, 0x340 +.set mtvec, 0x305 // machine trap handler +.set mcause, 0x342 // machine trap cause +.set mtval, 0x343 // machine bad address or instruction +// .set misa, 0x301 // machine ISA +// .set mie, 0x304 // machine interrupt enable +// .set mip, 0x344 // machine interrupt pending + +.extern init +.type init, @function + +.extern trap_handle +.type trap_handle, @function + + +.global _start +_start: + // setup a0 to hold |trap tbl addr|mode| + // len:| 30 | 2 | + la a0, trap_vector + csrrw zero, mtvec, a0 // write a0 into mtvec csr entry + // enable interrupts in mstatus + // this is the setting loaded: + // [07] MPIE = 1 - we want to enable interrupts with mret + // [03] MIE = 0 - we don't want interrupts now + // [11:12] MPP = 0 - we want to return into user mode + // all other bits should be zero + li a0, 0x80 + csrrw zero, mstatus, a0 // write to mstatus + // write +.option push +.option norelax + // init sp and gp + la sp, stack_top + la gp, __global_pointer$ +.option pop + jal init + + // halt machine + +.align 4 +trap_vector: + // save all registers into the PCB struct + // switch contents of t6 with contents of mscratch + // mscratch holds the PCBs regs field address + csrrw t6, mscratch, t6 + sw ra, 0(t6) + sw sp, 4(t6) + sw gp, 8(t6) + sw tp, 12(t6) + sw t0, 16(t6) + sw t1, 20(t6) + sw t2, 24(t6) + sw s0, 28(t6) + sw s1, 32(t6) + sw a0, 36(t6) + sw a1, 40(t6) + sw a2, 44(t6) + sw a3, 48(t6) + sw a4, 52(t6) + sw a5, 56(t6) + sw a6, 60(t6) + sw a7, 64(t6) + sw s2, 68(t6) + sw s3, 72(t6) + sw s4, 76(t6) + sw s5, 80(t6) + sw s6, 84(t6) + sw s7, 88(t6) + sw s8, 92(t6) + sw s9, 96(t6) + sw s10, 100(t6) + sw s11, 104(t6) + sw t3, 108(t6) + sw t4, 112(t6) + sw t5, 116(t6) + mv a0, t6 // save struct address to already saved register + csrrw t6, mscratch, t6 // load original t6 register from mscratch + sw t6, 120(a0) // save original t6 register + csrr a1, mcause + srli a0, a1, 31 + slli a1, a1, 1 + srli a1, a1, 1 + csrr a2, mtval + // reinit sp and gp +.option push +.option norelax + la sp, stack_top + la gp, __global_pointer$ +.option pop + jal trap_handle diff --git a/kinclude/csr.c b/kinclude/csr.c new file mode 100644 index 0000000..9bc459c --- /dev/null +++ b/kinclude/csr.c @@ -0,0 +1,40 @@ +#include "csr.h" + +#ifdef TIMECMP_IN_MEMORY + +void write_mtimecmp(unsigned long long int mtimecmp) { + unsigned int lower = mtimecmp; + unsigned int higher = mtimecmp >> 32; + __asm__( + "sw %2, %0\n" + "sw %3, %1" :: + "I"(TIMECMP_MEM_ADDR),"I"(TIMECMP_MEM_ADDR + 4), + "r"(lower), "r"(higher) + ); +} + +#else + +void write_mtimecmp(unsigned long long int mtimecmp) { + unsigned int lower = mtimecmp; + unsigned int higher = mtimecmp >> 32; + __asm__( + "csrw %0, %2\n" + "csrw %1, %3" :: + "I"(CSR_MTIMECMP),"I"(CSR_MTIMECMPH), + "r"(lower), "r"(higher) + ); +} + +#endif + + +unsigned long long int read_time() { + unsigned int lower, higher; + __asm__( + "csrr %0, 0xC01\n" + "csrr %1, 0xC81\n" + : "=r"(lower), "=r"(higher) + ); + return (unsigned long long) higher << 32 | lower; +} diff --git a/kinclude/csr.h b/kinclude/csr.h new file mode 100644 index 0000000..3531b7b --- /dev/null +++ b/kinclude/csr.h @@ -0,0 +1,38 @@ +#ifndef H_CSR +#define H_CSR + +#define CSR_MSTATUS 0x300 // machine status +#define CSR_MISA 0x301 // machine ISA +#define CSR_MIE 0x304 // machine interrupt enable +#define CSR_MTVEC 0x305 // machine trap handler +#define CSR_MSCRATCH 0x340 // machine scratch register +#define CSR_MEPC 0x341 // machine exception program counter +#define CSR_MCAUSE 0x342 // machine trap cause +#define CSR_MTVAL 0x343 // machine bad address or instruction +#define CSR_MIP 0x344 // machine interrupt pending +#define CSR_TIME 0xC01 // time csr +#define CSR_TIMEH 0xC81 // high bits of time + +#define CSR_MTIMECMP 0x780 // mtimecmp register for timer interrupts +#define CSR_MTIMECMPH 0x781 // mtimecmph register for timer interrupts + +#define CSR_HALT 0x789 // writing nonzero value here will halt the cpu + +#define TIMECMP_MEM_ADDR 0xffff + +#define CSR_READ(csr_id, result) {\ + __asm__ ("csrr %0, %1" : "=r"((result)) : "I"((csr_id))); \ +} + +#define CSR_WRITE(csr_id, val) {\ + __asm__ ("csrw %0, %1" :: "I"((csr_id)), "r"((val))); \ +} + +#define HALT(code) {\ + __asm__("csrw %0, %1" :: "I"(CSR_HALT), "I"(code)); \ +} + +void write_mtimecmp(unsigned long long int mtimecmp); +unsigned long long int read_time(); + +#endif \ No newline at end of file diff --git a/kinclude/ecall.c b/kinclude/ecall.c new file mode 100644 index 0000000..a99bd22 --- /dev/null +++ b/kinclude/ecall.c @@ -0,0 +1,80 @@ +#include "ecall.h" +#include "sched.h" +#include "csr.h" + +void ecall_handle_fork() +{ + +} + +void ecall_handle_sleep(int until) +{ + +} + +void ecall_handle_wait(int pid, int timeout) +{ + +} + +void ecall_handle_kill(int pid) +{ + +} + +void ecall_handle_exit(int status) +{ + +} + +void ecall_handle_m_create() +{ + +} + +void ecall_handle_m_lock(int mutex_id) +{ + +} + +void ecall_handle_m_unlock(int mutex_id) +{ + +} + +void ecall_handle_m_destroy(int mutex_id) +{ + +} + +void trap_handle_ecall() { + int *regs = get_current_process_registers(); + int code = regs[16]; + __asm__("ebreak"); + HALT(18); +} + +void trap_handle(int interrupt_bit, int code, int mtval) +{ + if (interrupt_bit) { + switch (code) { + case 7: + scheduler_run_next(); + break; + default: + // impossible + HALT(12) + break; + } + } else { + switch (code) { + case 8: // user ecall + trap_handle_ecall(); + break; + default: + HALT(13); + } + } + HALT(1); + __builtin_unreachable(); +} diff --git a/kinclude/ecall.h b/kinclude/ecall.h new file mode 100644 index 0000000..a92639c --- /dev/null +++ b/kinclude/ecall.h @@ -0,0 +1,21 @@ +#ifndef H_ECALL +#define H_ECALL + +// syscall handlers, are setup in the mtvec csr +void ecall_handle_fork(); +void ecall_handle_sleep(int until); +void ecall_handle_wait(int pid, int timeout); +void ecall_handle_kill(int pid); +void ecall_handle_exit(int status); +void ecall_handle_m_create(); +void ecall_handle_m_lock(int mutex_id); +void ecall_handle_m_unlock(int mutex_id); +void ecall_handle_m_destroy(int mutex_id); + +void __attribute__((__noreturn__)) trap_handle(int interrupt_bit, int code, int mtval); + + + + + +#endif \ No newline at end of file diff --git a/kinclude/mutex.c b/kinclude/mutex.c new file mode 100644 index 0000000..dc2dd26 --- /dev/null +++ b/kinclude/mutex.c @@ -0,0 +1,37 @@ +#include "mutex.h" +#include "../kernel.h" + +// mutex lock structure: +// this is a dict describing which process instantiated the lock +// it maps mutex_id -> pid (or zero for unused locks) +int locks[MUTEX_COUNT]; +int locks_bitfield[MUTEX_COUNT / XLEN]; // each bit representing if the lock is + // engaged + + +int mutex_is_locked(int mutex_id) +{ + int offset = mutex_id % XLEN; + + return locks[mutex_id / XLEN] & (1 << offset); +} + +int mutex_create() +{ + return 0; +} + +void mutex_lock(int mutex_id) +{ + +} + +void mutex_unlock(int mutex_id) +{ + +} + +void mutex_destroy(int mutex_id) +{ + +} diff --git a/kinclude/mutex.h b/kinclude/mutex.h new file mode 100644 index 0000000..96a92c6 --- /dev/null +++ b/kinclude/mutex.h @@ -0,0 +1,13 @@ +#ifndef H_MUTEX +#define H_MUTEX + +// mutex operations (modifies data, no checks) +int mutex_create(); +void mutex_lock(int mutex_id); +void mutex_unlock(int mutex_id); +void mutex_destroy(int mutex_id); +// mutex helpers +int mutex_is_locked(int mutex_id); + + +#endif \ No newline at end of file diff --git a/kinclude/sched.c b/kinclude/sched.c new file mode 100644 index 0000000..8410d96 --- /dev/null +++ b/kinclude/sched.c @@ -0,0 +1,139 @@ +#include "../kernel.h" +#include "sched.h" +#include "mutex.h" +#include "csr.h" + + +// scheduling data: +ProcessControlBlock processes[PROCESS_COUNT]; +int current_process_index = 1; + +void scheduler_run_next () +{ + current_process_index = scheduler_select_free(); + // set up timer interrupt + unsigned long long int mtimecmp = read_time() + TIME_SLICE_LEN; + write_mtimecmp(mtimecmp); + scheduler_switch_to(current_process_index); +} + +int scheduler_select_free() +{ + long long int mtime; + int i; + int timeout_available = false; // note if a timeout is available + + while (true) { + mtime = read_time(); + + for (i=1; i < PROCESS_COUNT; i++) { + ProcessControlBlock *pcb = processes + ((current_process_index + i) % PROCESS_COUNT); + + if (pcb->status == PROC_RDY) + return (current_process_index + i) % PROCESS_COUNT; + + if (pcb->status == PROC_WAIT_SLEEP) { + if (pcb->asleep_until < mtime) { + return (current_process_index + i) % PROCESS_COUNT; + } + timeout_available = true; + } + + if (pcb->status == PROC_WAIT_PROC) { + if (pcb->asleep_until != 0) { + if (pcb->asleep_until < mtime) { + // set process return args! + return (current_process_index + i) % PROCESS_COUNT; + } + timeout_available = true; + } + } + + if (pcb->status == PROC_WAIT_LOCK) { + if (pcb->asleep_until != 0) { + if (pcb->asleep_until < mtime) { + // set process return args! + return (current_process_index + i) % PROCESS_COUNT; + } + timeout_available = true; + } + + if (!mutex_is_locked(pcb->requested_lock)) { + return (current_process_index + i) % PROCESS_COUNT; + } + } + } + + if (timeout_available == false) { + // either process deadlock or no processes alive. + //TODO: handle missing executable thread + HALT(22); + } + } +} + +void scheduler_switch_to(int proc_index) +{ + ProcessControlBlock *pcb = processes + proc_index; + + CSR_WRITE(CSR_MEPC, pcb->pc); + + // set up registers + __asm__( + "mv x31, %0\n" + "csrrw zero, %1, x31\n" + "lw x1, 0(x31)\n" + "lw x2, 4(x31)\n" + "lw x3, 8(x31)\n" + "lw x4, 12(x31)\n" + "lw x5, 16(x31)\n" + "lw x6, 20(x31)\n" + "lw x7, 24(x31)\n" + "lw x8, 28(x31)\n" + "lw x9, 32(x31)\n" + "lw x10, 36(x31)\n" + "lw x11, 40(x31)\n" + "lw x12, 44(x31)\n" + "lw x13, 48(x31)\n" + "lw x14, 52(x31)\n" + "lw x15, 56(x31)\n" + "lw x16, 60(x31)\n" + "lw x17, 64(x31)\n" + "lw x18, 68(x31)\n" + "lw x19, 72(x31)\n" + "lw x20, 76(x31)\n" + "lw x21, 80(x31)\n" + "lw x22, 84(x31)\n" + "lw x23, 88(x31)\n" + "lw x24, 92(x31)\n" + "lw x25, 96(x31)\n" + "lw x26, 100(x31)\n" + "lw x27, 104(x31)\n" + "lw x28, 108(x31)\n" + "lw x29, 112(x31)\n" + "lw x30, 116(x31)\n" + "lw x31, 120(x31)\n" + "mret \n" + :: "r"(pcb->regs), "I"(CSR_MSCRATCH) + ); + __builtin_unreachable(); +} + +int scheduler_index_from_pid(int pid) +{ + for (int i = 0; i < PROCESS_COUNT; i++) { + if (processes[i].pid == pid) + return i; + } + return -1; +} + +int scheduler_create_process(int binid) +{ + return 0; +} + +int* get_current_process_registers() +{ + return processes[current_process_index].regs; +} \ No newline at end of file diff --git a/kinclude/sched.h b/kinclude/sched.h new file mode 100644 index 0000000..264a3b7 --- /dev/null +++ b/kinclude/sched.h @@ -0,0 +1,37 @@ +#ifndef H_SCHED +#define H_SCHED + +#include "../kernel.h" + +// process statuses: +#define PROC_DEAD 0 +#define PROC_RDY 1 +#define PROC_WAIT_LOCK 2 +#define PROC_WAIT_PROC 3 +#define PROC_WAIT_SLEEP 4 + +// process structure: +typedef struct ProcessControlBlock ProcessControlBlock; +struct ProcessControlBlock { + int pid; + int pc; + int regs[31]; + // scheduling information + int status; + int requested_lock; + ProcessControlBlock *waiting_for_process; + unsigned long long int asleep_until; +}; + +// scheduling data: +extern ProcessControlBlock processes[PROCESS_COUNT]; + +// scheduler methods +int scheduler_select_free(); +int scheduler_create_process(int binid); +void __attribute__((noreturn)) scheduler_run_next(); +void __attribute__((noreturn)) scheduler_switch_to(int proc_index); +int scheduler_index_from_pid(int pid); +int* get_current_process_registers(); + +#endif \ No newline at end of file diff --git a/lib/stdlib.c b/lib/stdlib.c new file mode 100644 index 0000000..61e2360 --- /dev/null +++ b/lib/stdlib.c @@ -0,0 +1,69 @@ +#include "stdlib.h" + +inline void __attribute__((always_inline)) ecall() { + asm volatile("ecall"); +} + +// process control +int spawn(void (*child)(void*), void* args); + +void sleep(int length) { + asm volatile( + "li a7, 1\n" + "ecall\n" + "mv a0, %0" + :: "r"(length) + ); +} + +int join(int pid, int timeout) { + return 0; +} + +int kill(int pid) { + return 0; +} + +void __attribute__((noreturn)) exit(int code); + +// locks +m_lock mutex_create() { + m_lock result; + asm ( + "li a7, 8\n" + "ecall\n" + "mv %0, a0" : + "=r"(result) + ); + return result; +} + +int mutex_lock(m_lock lock, int timeout) { + int result; + asm ( + "li a7, 9\n" + "mv a0, %1\n" + "mv a1, %2\n" + "ecall\n" + "mv %0, a0" + : "=r"(result) + : "r"(lock), "r"(timeout) + ); + return result; +} + +void mutex_unlock(m_lock lock) { + asm ( + "li a7, 10\n" + "mv a0, %0" :: + "r"(lock) + ); +} + +void mutex_destroy(m_lock lock) { + asm ( + "li a7, 11\n" + "mv a0, %0" :: + "r"(lock) + ); +} diff --git a/lib/stdlib.h b/lib/stdlib.h new file mode 100644 index 0000000..0c7565c --- /dev/null +++ b/lib/stdlib.h @@ -0,0 +1,23 @@ +#pragma once + +// process control +int spawn(void (*child)(void*), void* args); + +void sleep(int length); + +int join(int pid, int timeout); + +int kill(int pid); + +void __attribute__((noreturn)) exit(int code); + +// locks +typedef int m_lock; + +m_lock mutex_create(); + +int mutex_lock(m_lock lock, int timeout); + +void mutex_unlock(m_lock lock); + +void mutex_destroy(m_lock lock);