1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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();
}
|