diff options
| -rw-r--r-- | include/fbos/compiler.h | 2 | ||||
| -rw-r--r-- | include/fbos/init.h | 9 | ||||
| -rw-r--r-- | include/fbos/sbi.h | 7 | ||||
| -rw-r--r-- | kernel/head.S | 4 | ||||
| -rw-r--r-- | kernel/main.c | 14 | ||||
| -rw-r--r-- | kernel/trap.c | 125 |
6 files changed, 153 insertions, 8 deletions
diff --git a/include/fbos/compiler.h b/include/fbos/compiler.h index c26c05c..95596dd 100644 --- a/include/fbos/compiler.h +++ b/include/fbos/compiler.h @@ -6,6 +6,8 @@ */ #define __noreturn __attribute__((__noreturn__)) +#define __s_interrupt __attribute__((interrupt("supervisor"))) +#define __aligned(x) __attribute__((aligned(x))) /* * Compiler attributes specific to linker sections. diff --git a/include/fbos/init.h b/include/fbos/init.h index 978eebe..711d2cf 100644 --- a/include/fbos/init.h +++ b/include/fbos/init.h @@ -3,10 +3,19 @@ #include <fbos/compiler.h> +// Tracks the amount of seconds that have elapsed since activating timer +// interrupts. +// +// Instantiated in kernel/trap.c, initialized in main.c. +extern uint64_t seconds_elapsed; + // Extract the executables from the initrd that is located at `base_addr` and // has the given `size`. void extract_initrd(const unsigned char *const base_addr, uint64_t size); +// Setup the interrupt vectors and the SBI timer. +void setup_interrupts(void); + // The entry point for the kernel. void start_kernel(void *dtb); diff --git a/include/fbos/sbi.h b/include/fbos/sbi.h index 2575415..b1c99ea 100644 --- a/include/fbos/sbi.h +++ b/include/fbos/sbi.h @@ -18,6 +18,7 @@ enum sbi_ret_error { // SBI extensions supported by this kernel. enum sbi_ext { DBCN_EXT = 0x4442434E, + TIME_EXT = 0x54494D45, }; // Function IDs for the Debug Console Extension "DBCN". @@ -25,6 +26,11 @@ enum dbcn_actions { DBCN_WRITE = 0x00, }; +// Function IDs for the Timer Extension "TIME". +enum time_actions { + TIME_SET_TIMER = 0x00, +}; + // Return value for any SBI ecall. struct sbi_ret { long error; @@ -37,6 +43,7 @@ extern struct sbi_ret __sbi_ecall(unsigned long arg0, unsigned long arg1, unsign unsigned long arg3, unsigned long arg4, unsigned long arg5, int fid, int ext); +#define sbi_ecall1(ext, fid, arg0) __sbi_ecall(arg0, 0, 0, 0, 0, 0, fid, ext) #define sbi_ecall2(ext, fid, arg0, arg1) __sbi_ecall(arg0, arg1, 0, 0, 0, 0, fid, ext) #endif // __FBOS_SBI_H_ diff --git a/kernel/head.S b/kernel/head.S index 9018a8e..c8f523d 100644 --- a/kernel/head.S +++ b/kernel/head.S @@ -53,3 +53,7 @@ _start: // Start the kernel. tail start_kernel + + // We really shouldn't reach this point, but just in case, just loop + // infinitely here. + j . diff --git a/kernel/main.c b/kernel/main.c index 089614f..9cdb61e 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -20,19 +20,17 @@ struct task_struct tasks[4] = { */ __noreturn __kernel void start_kernel(void *dtb) { - // TODO: disable irqs, etc. - printk("Welcome to FizzBuzz OS!\n"); + // Extract information from the DTB blob. struct initrd_addr addr = find_dt_initrd_addr(dtb); - extract_initrd((unsigned char *)addr.start, addr.end - addr.start); - // TODO: this is the jump address for the first task. - const char *ddr = (const char *)tasks[1].addr + tasks[1].entry_offset; - __unused(ddr); - - // TODO: reenable stuff + // 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(); for (;;) ; diff --git a/kernel/trap.c b/kernel/trap.c new file mode 100644 index 0000000..24b459b --- /dev/null +++ b/kernel/trap.c @@ -0,0 +1,125 @@ +#include <fbos/init.h> +#include <fbos/sbi.h> +#include <fbos/printk.h> + +// TODO: this is QEMU-specific. To obtain this: +// - Parse the DTB and look for the 'cpus.timebase-frequency' property. +// - If the system is on ACPI (e.g. VisionFive2), then the frequency has to be +// picked up from somewhere else (constant on known boards?). +#define TICKS_PER_SECOND 10000000 + +// Mask for 'scause' to check whether it came from an interrupt or an exception. +#define INTERRUPT_MASK 0x8000000000000000 +#define IS_EXCEPTION(x) ((x & INTERRUPT_MASK) == 0) + +// Mask for 'scause' to figure out if the interrupt was caused by the timer. +#define TIMER_SCAUSE_MASK 0x05 + +// Declared in include/fbos/init.h. +uint64_t seconds_elapsed; + +// Set up a timer through the SBI interface that sends an interrupt in one +// second from the time this function is called. +__kernel void time_out_in_one_second(void) +{ + struct sbi_ret ret; + register uint64_t one_second asm("a0"); + + asm volatile("rdtime t0\n\t" + "li t1, %1\n\t" + "add %0, t0, t1" + : "=r"(one_second) + : "i"(TICKS_PER_SECOND) + : "t0", "t1"); + + ret = sbi_ecall1(TIME_EXT, TIME_SET_TIMER, one_second); + if (ret.error != SBI_SUCCESS) { + die("Could not set timer\n"); + } +} + +/* + * Direct interrupt handler. Handles interrupts such as the timer event and user + * mode entries. + * + * NOTE: as per RISC-V specification, the handler's address as set on the + * 'stvec' register *must* be aligned on a 4-byte boundary. Hence, ensuring a + * proper alignment is mandatory. + * + * NOTE: the '__s_interrupt' attribute already handles the saving/restoring of + * all registers. It's probably a bit over the top since it also does that for + * registers we never care on this kernel (e.g. floating point registers), but + * it's convenient. + */ +__aligned(4) __s_interrupt __kernel void interrupt_handler(void) +{ + uint64_t cause; + asm volatile("csrr %0, scause" : "=r"(cause)::); + + if (IS_EXCEPTION(cause)) { + die("Don't know how to handle exceptions :D\n"); + } + + 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. + asm volatile("li t0, 32\n\t" + "csrc sip, t0\n\t" + "csrc sie, t0" + : + : + : "t0"); + + // BEHOLD! The fizz buzz logic! :D + seconds_elapsed += 1; + if ((seconds_elapsed % 15) == 0) { + printk("Should run fizzbuzz\n"); + } else if ((seconds_elapsed % 5) == 0) { + printk("Should run buzz\n"); + } else if ((seconds_elapsed % 3) == 0) { + printk("Should run fizz\n"); + } + + // Re-enable timer interrupts. + asm volatile("li t0, 32\n\t" + "csrs sie, t0" + : + : + : "t0"); + + // Reset the timer one second from now. + time_out_in_one_second(); + } else { + printk("WARN: unknown interrupt just came in...\n"); + } +} + +__kernel void setup_interrupts(void) +{ + /* + * - stvec: point to our interrupt handler. The two least-significant bits are + * going to be '00', meaning we are using direct mode. + * - sstatus: set the SIE (S Interrupt Enable) bit. Interrupts are now on! + */ + asm volatile("csrw stvec, %0\n\t" + "csrsi sstatus, 2" + : + : "r"(&interrupt_handler) + :); + + /* + * - sie: set bit 5 (STIE: S Timer Interrupt Enable). + * + * NOTE: head.S zeroes out both 'sip' and 'sie' registers. Hence, there are + * no pending interrupts. + */ + asm volatile("li t0, 32\n\t" + "csrs sie, t0" + : + : + : "t0"); + + // And initialize the timer to send an interrupt in one second from now. + time_out_in_one_second(); +} |
