Skip to content

Commit

Permalink
Enable exception handling from user mode
Browse files Browse the repository at this point in the history
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à <[email protected]>
  • Loading branch information
mssola committed Nov 22, 2024
1 parent 224ed7b commit 7f6b234
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 11 deletions.
3 changes: 3 additions & 0 deletions include/fbos/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/

#define __noreturn __attribute__((__noreturn__))
#ifndef __always_inline
#define __always_inline __attribute__((__always_inline__)) inline
#endif
#define __s_interrupt __attribute__((interrupt("supervisor")))
#define __aligned(x) __attribute__((aligned(x)))

Expand Down
3 changes: 3 additions & 0 deletions include/fbos/printk.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef __FBOS_PRINTK_H_
#define __FBOS_PRINTK_H_

#include <fbos/compiler.h>

/*
* This file might be pulled from user space tests. Hence, define alternatives
* for this functions from glibc.
Expand All @@ -9,6 +11,7 @@
#ifdef __KERNEL__
extern void die(const char *const message);
extern void printk(const char *const message);
extern void write(const char *const message, size_t n);
#else
#include <stdio.h>
#include <stdlib.h>
Expand Down
10 changes: 10 additions & 0 deletions include/fbos/sched.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <fbos/compiler.h>

// All the possible IDs for the tasks on this kernel.
enum task_id {
TASK_UNKNOWN = -1,
TASK_INIT = 0,
Expand All @@ -11,12 +12,21 @@ enum task_id {
TASK_FIZZBUZZ = 3,
};

// TODO: if we only care about the absolute address, it can be further
// simplified.
struct task_struct {
void *stack;
const void *addr;
uint64_t entry_offset;
};

// Tasks available on this kernel.
extern struct task_struct tasks[4];

// Identifier for the next task to be run.
extern int next_task;

// Bring the machine to idle mode.
__noreturn __kernel void idle(void);

#endif // __FBOS_SCHED_H_
25 changes: 23 additions & 2 deletions kernel/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
}
}
9 changes: 7 additions & 2 deletions kernel/printk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
43 changes: 36 additions & 7 deletions kernel/trap.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <fbos/init.h>
#include <fbos/sbi.h>
#include <fbos/printk.h>
#include <fbos/sched.h>

// TODO: this is QEMU-specific. To obtain this:
// - Parse the DTB and look for the 'cpus.timebase-frequency' property.
Expand All @@ -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;

Expand All @@ -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.
Expand All @@ -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"
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions usr/src/buzz.S
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
_start:
li a7, 1
la a0, buzz
li a1, 5
ecall
.Loop:
j .Loop
Expand Down
1 change: 1 addition & 0 deletions usr/src/fizz.S
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
_start:
li a7, 1
la a0, fizz
li a1, 5
ecall
.Loop:
j .Loop
Expand Down
1 change: 1 addition & 0 deletions usr/src/fizzbuzz.S
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
_start:
li a7, 1
la a0, fizzbuzz
li a1, 9
ecall
.Loop:
j .Loop
Expand Down

0 comments on commit 7f6b234

Please sign in to comment.