From 7f6b234625a53d2ceb9ec80dc0172aac1cedb358 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Fri, 22 Nov 2024 23:00:46 +0100 Subject: Enable exception handling from user mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows us to actually catch system calls and start to run user space programs. That being said, the whole thing is still pretty brittle, and the scheduler is not quite there yet. Signed-off-by: Miquel Sabaté Solà --- kernel/main.c | 25 +++++++++++++++++++++++-- kernel/printk.c | 9 +++++++-- kernel/trap.c | 43 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 66 insertions(+), 11 deletions(-) (limited to 'kernel') diff --git a/kernel/main.c b/kernel/main.c index 968c9d5..4ed99b5 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -13,6 +13,19 @@ struct task_struct tasks[4] = { [TASK_FIZZBUZZ] = { .stack = init_stack[3], .addr = nullptr, .entry_offset = 0, }, }; +int next_task; + +// TODO: this feels really brittle +__kernel void switch_to(int task_id) +{ + const char *ddr = (const char *)tasks[task_id].addr + tasks[task_id].entry_offset; + + asm volatile("csrc sstatus, %[mask]" : : [mask] "r"(1 << 8)); + asm volatile("mv ra, %0" : : "r"(ddr)); + asm volatile("csrw sepc, ra"); + asm volatile("sret"); +} + /* * This is the main entry point of the kernel after head.S is done. This * function can (and will) assume that everything has been reset and that we can @@ -26,15 +39,23 @@ __noreturn __kernel void start_kernel(void *dtb) struct initrd_addr addr = find_dt_initrd_addr(dtb); extract_initrd((unsigned char *)addr.start, addr.end - addr.start); + next_task = TASK_UNKNOWN; + // At this point everything has already been handled: setup the interrupt // vector and enable the timer to start ticking and scheduling the three // tasks at hand. seconds_elapsed = 0; setup_interrupts(); + idle(); +} + +__noreturn __kernel void idle(void) +{ for (;;) { - // Put the machine on low power consumption since we are not doing - // anything fancy here. + if (next_task != TASK_UNKNOWN && next_task != TASK_INIT) { + switch_to(next_task); + } asm volatile("wfi"); } } diff --git a/kernel/printk.c b/kernel/printk.c index 51c1a7a..b3c925a 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -12,14 +12,19 @@ void __noreturn __kernel die(const char *const message) ; } -void __kernel printk(const char *const message) +__kernel void printk(const char *const message) { size_t len = strlen(message); if (!len) { return; } - struct sbi_ret ret = sbi_ecall2(DBCN_EXT, DBCN_WRITE, len, (unsigned long)message); + write(message, len); +} + +__kernel void write(const char *const message, size_t n) +{ + struct sbi_ret ret = sbi_ecall2(DBCN_EXT, DBCN_WRITE, n, (unsigned long)message); if (ret.error != SBI_SUCCESS) { die(nullptr); } diff --git a/kernel/trap.c b/kernel/trap.c index 24b459b..84f8398 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -1,6 +1,7 @@ #include #include #include +#include // TODO: this is QEMU-specific. To obtain this: // - Parse the DTB and look for the 'cpus.timebase-frequency' property. @@ -15,6 +16,13 @@ // Mask for 'scause' to figure out if the interrupt was caused by the timer. #define TIMER_SCAUSE_MASK 0x05 +// Mask for 'scause' to figure out if the exception was cause by U privilege +// mode making an 'ecall'. +#define USER_ECALL_MASK 0x08 + +// Identifier for the 'write' system call. +#define NR_WRITE 0x01 + // Declared in include/fbos/init.h. uint64_t seconds_elapsed; @@ -38,6 +46,27 @@ __kernel void time_out_in_one_second(void) } } +__kernel __always_inline void exception_handler(uint64_t cause) +{ + register char *message asm("a0"); + register size_t n asm("a1"); + register uint64_t syscall_id asm("a7"); + + if ((cause & USER_ECALL_MASK) != USER_ECALL_MASK) { + die("Don't know how to handle this exception :D\n"); + } + if (syscall_id != NR_WRITE) { + die("Bad syscall\n"); + } + + // TODO: acknowledge 'sip' bit? + next_task = TASK_UNKNOWN; + write(message, n); + + // TODO: is it safe to jump to idle here? Any preparation to be done? + idle(); +} + /* * Direct interrupt handler. Handles interrupts such as the timer event and user * mode entries. @@ -57,13 +86,11 @@ __aligned(4) __s_interrupt __kernel void interrupt_handler(void) asm volatile("csrr %0, scause" : "=r"(cause)::); if (IS_EXCEPTION(cause)) { - die("Don't know how to handle exceptions :D\n"); + exception_handler(cause); } if ((cause & TIMER_SCAUSE_MASK) == TIMER_SCAUSE_MASK) { - // Clear timer interrupt pending bit from the 'sip' register. Also clear - // the timer interrupt enable so it's re-enabled after running the - // fizz/buzz logic. + // Clear timer interrupt pending bit from the 'sip' register. asm volatile("li t0, 32\n\t" "csrc sip, t0\n\t" "csrc sie, t0" @@ -74,11 +101,13 @@ __aligned(4) __s_interrupt __kernel void interrupt_handler(void) // BEHOLD! The fizz buzz logic! :D seconds_elapsed += 1; if ((seconds_elapsed % 15) == 0) { - printk("Should run fizzbuzz\n"); + next_task = TASK_FIZZBUZZ; } else if ((seconds_elapsed % 5) == 0) { - printk("Should run buzz\n"); + next_task = TASK_BUZZ; } else if ((seconds_elapsed % 3) == 0) { - printk("Should run fizz\n"); + next_task = TASK_FIZZ; + } else { + next_task = TASK_UNKNOWN; } // Re-enable timer interrupts. -- cgit v1.2.3