Skip to content

Commit

Permalink
Enable the timer from the SBI interface
Browse files Browse the repository at this point in the history
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à <[email protected]>
  • Loading branch information
mssola committed Nov 22, 2024
1 parent 8231af9 commit dcda573
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 8 deletions.
2 changes: 2 additions & 0 deletions include/fbos/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions include/fbos/init.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions include/fbos/sbi.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ 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".
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;
Expand All @@ -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_
4 changes: 4 additions & 0 deletions kernel/head.S
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
14 changes: 6 additions & 8 deletions kernel/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 (;;)
;
Expand Down
125 changes: 125 additions & 0 deletions kernel/trap.c
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit dcda573

Please sign in to comment.