-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtrap.c
165 lines (144 loc) · 4.62 KB
/
trap.c
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#include <fbos/init.h>
#include <fbos/sbi.h>
#include <fbos/printk.h>
// 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
// 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;
// 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"
"mv t1, %1\n\t"
"add %0, t0, t1"
: "=r"(one_second)
: "r"(info.cpu_freq)
: "t0", "t1");
ret = sbi_ecall1(TIME_EXT, TIME_SET_TIMER, one_second);
if (ret.error != SBI_SUCCESS) {
die("Could not set timer\n");
}
}
/*
* Exception handler. For this kernel, it only ensures that the 'write' system
* call is the one responsible for this exception and handles it; otherwise it
* will die.
*/
__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");
}
sys_write(message, 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)) {
exception_handler(cause);
prepare_switch_to(TASK_INIT);
goto end;
}
if ((cause & TIMER_SCAUSE_MASK) == TIMER_SCAUSE_MASK) {
// Clear timer interrupt pending bit from the 'sip' register.
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) {
prepare_switch_to(TASK_FIZZBUZZ);
} else if ((seconds_elapsed % 5) == 0) {
prepare_switch_to(TASK_BUZZ);
} else if ((seconds_elapsed % 3) == 0) {
prepare_switch_to(TASK_FIZZ);
}
// 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");
}
end:
/*
* Here the restoring of the stack will happen, so it's actually not empty.
*
* NOTE: the restore of registers when we switch to another process here is
* actually pretty pointless (we are not 'restoring' anything in the point
* of view of the process we are about to schedule), but we keep the 'sp'
* register sane, at least.
*
* Anyways, when we schedule a process in this kernel we don't actually
* schedule it in the proper sense: we don't return to the last 'pc' for
* that process, but we actually run it from the entry address again. This
* is not relevant for this kernel because in the end our processes only do
* one thing, and 're-starting' is effectively the same in this (silly)
* kernel of ours :)
*/
;
}
__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();
}