From dcda5731ecbbce70edeaa7662e9ea0fc190a1943 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Fri, 22 Nov 2024 15:46:59 +0100 Subject: Enable the timer from the SBI interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A basic interrupt handler has been implemented which tracks the seconds that have been elapsing along the way. Depending on the value of these seconds, then a task has to be called. Signed-off-by: Miquel Sabaté Solà --- kernel/head.S | 4 ++ kernel/main.c | 14 +++---- kernel/trap.c | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 kernel/trap.c (limited to 'kernel') 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 +#include +#include + +// 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(); +} -- cgit v1.2.3