initial commit

master
Anton Lydike 3 years ago
commit 8d0178aff1

3
.gitignore vendored

@ -0,0 +1,3 @@
out/
*.o
obj/

@ -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.

@ -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)

@ -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!

@ -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;
}
}
}

@ -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

@ -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

@ -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;
}

@ -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

@ -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();
}

@ -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

@ -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)
{
}

@ -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

@ -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;
}

@ -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

@ -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)
);
}

@ -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);
Loading…
Cancel
Save