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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
/*
* Trap MD Layer (RISC-V 64)
*
* All architecture-specific trap logic lives here:
* - scause/stval reading (CSR access)
* - scause → MI TrapCause translation
* - RISC-V specific fixups (ecall epc += 4, ebreak epc += 2)
* - stvec setup (trap_init / trap_init_ap)
* - kernel_trap_handler (traps from S-mode)
*
* MI layer (kernel/trap.cc) contains ZERO arch-specific code.
*
* Reference: docs/specs/trap.md §7
*/
#include <lume/trap.h>
#include <lume/irq.h>
#include <lume/klog.h>
#include <lume/console.h>
#include <lume/kprintf.h>
#include <lume/sched.h>
#include <lume/task.h>
#include <arch/trap.h>
#include <arch/trapframe.h>
#include <arch/cpu.h>
#include <arch/extable.h>
/* PLIC interface (drivers/plic.cc) */
uint32 plic_claim();
void plic_complete(uint32 irq);
/* trap_vector.S: assembly entry point */
extern "C" void trap_vector();
/* ========================================================================
* scause → TrapCause translation (RISC-V specific)
* ======================================================================== */
static TrapCause scause_to_trap_cause(uint64 scause)
{
if (arch::trap::is_interrupt(scause)) {
switch (arch::trap::cause_code(scause)) {
case IRQ_S_TIMER: return TrapCause::IrqTimer;
case IRQ_S_SOFTWARE: return TrapCause::IrqSoftware;
case IRQ_S_EXTERNAL: return TrapCause::IrqExternal;
default: return TrapCause::Unknown;
}
}
/* Exception (synchronous) */
switch (scause) {
case EXC_INST_PAGE_FAULT: return TrapCause::PageFaultExec;
case EXC_LOAD_PAGE_FAULT: return TrapCause::PageFaultLoad;
case EXC_STORE_PAGE_FAULT: return TrapCause::PageFaultStore;
case EXC_ECALL_FROM_U: return TrapCause::Syscall;
case EXC_ILLEGAL_INST: return TrapCause::IllegalInst;
case EXC_BREAKPOINT: return TrapCause::Breakpoint;
case EXC_INST_ACCESS_FAULT:
case EXC_LOAD_ACCESS_FAULT:
case EXC_STORE_ACCESS_FAULT:
return TrapCause::AccessFault;
case EXC_INST_MISALIGNED:
case EXC_LOAD_MISALIGNED:
case EXC_STORE_MISALIGNED:
return TrapCause::AlignFault;
default:
return TrapCause::Unknown;
}
}
TrapCause trap_translate_scause(uint64 scause)
{
return scause_to_trap_cause(scause);
}
static TrapOrigin origin_from_sstatus(uint64 sstatus)
{
constexpr uint64 kSstatusSPP = (1ULL << 8);
return (sstatus & kSstatusSPP) ? TrapOrigin::FromKernel
: TrapOrigin::FromUser;
}
static TrapClass classify_trap(uint64 scause, TrapCause cause)
{
if (arch::trap::is_interrupt(scause)) {
return TrapClass::Interrupt;
}
if (cause == TrapCause::Syscall) {
return TrapClass::Syscall;
}
if (cause == TrapCause::Unknown) {
return TrapClass::Unknown;
}
return TrapClass::Exception;
}
static TrapInfo build_trap_info(arch::TrapFrame* tf,
uint64 scause,
uint64 stval,
TrapOrigin origin)
{
TrapInfo info{};
info.cause = scause_to_trap_cause(scause);
info.cls = classify_trap(scause, info.cause);
info.origin = origin;
info.val = stval;
info.epc = tf ? tf->epc : 0;
info.scause = scause;
return info;
}
/* ========================================================================
* trap_dispatch — user-mode trap entry (called from trap_vector.S)
*
* Reads RISC-V CSRs, translates to MI TrapCause, applies arch-specific
* fixups, then calls MI trap_handle().
*
* After trap_handle returns, we must return to user mode via userret().
* The assembly jalr t3 that called us has NO return-to-user code after it,
* so trap_dispatch must never return normally.
* ======================================================================== */
extern "C" void userret(arch::TrapFrame* tf);
extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame* tf)
{
/* Switch stvec to kernelvec immediately so that any traps during
* kernel execution (timer IRQs, page faults from copy_from_user)
* go through the kernel trap entry, not the user trap entry.
* This matches Linux which sets stvec to handle_exception (kernel)
* at the top of the user trap path. */
extern void kernelvec();
arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
/* Enable SUM so S-mode can access user pages (PTE_U).
* Without this, any dereference of user-space pointers in syscall
* handlers (sys_write buf, sys_read buf, etc.) would page fault. */
arch::trap::enable_sum();
uint64 scause = arch::trap::get_cause();
uint64 stval = arch::trap::get_value();
TrapOrigin origin = origin_from_sstatus(tf->sstatus);
TrapInfo info = build_trap_info(tf, scause, stval, origin);
/* Per-trap trace from user mode. Stored at KLOG_DEBUG / KLOG_SUBSYS_TRAP
* so it lives in the ring buffer for postmortem but stays off the console
* by default. No more in-band 20-trap window — flip console_level or
* dump the ring on demand instead. */
if (origin == TrapOrigin::FromUser) {
pr_debug_s(KLOG_SUBSYS_TRAP,
"trap: cause=%u epc=%llx val=%llx\n",
static_cast<unsigned>(info.cause), info.epc, info.val);
}
/* Save trapframe in TCB for syscall handlers (like fork) */
TaskControlBlock* curr = current_task();
if (curr) {
curr->trapframe = tf;
}
/* RISC-V ecall fix: ecall does not increment sepc.
* Advance epc so sret returns to the instruction AFTER ecall. */
if (info.cause == TrapCause::Syscall) {
tf->epc += 4;
info.epc = tf->epc;
}
/* RISC-V ebreak fix: advance past the C.EBREAK instruction (2 bytes).
* This is arch-specific because instruction size depends on the ISA. */
if (info.cause == TrapCause::Breakpoint) {
tf->epc += 2;
info.epc = tf->epc;
}
/* Call MI-layer dispatcher (zero inline assembly from here on) */
trap_handle(tf, info);
/* Return to user mode.
* Set stvec back to trap_vector for the next user-mode trap,
* then jump to userret which restores registers and does sret. */
arch::trap::set_vector(reinterpret_cast<uint64>(&trap_vector));
userret(tf);
__builtin_unreachable();
}
/* ========================================================================
* kernel_trap_handler — traps taken while already in S-mode
*
* For Phase 2, only timer and external interrupts are expected.
* Exceptions in S-mode are always fatal.
*
* This belongs in MD because it reads scause directly.
* ======================================================================== */
extern "C" void kernel_trap_handler(arch::TrapFrame* tf)
{
uint64 scause = arch::trap::get_cause();
uint64 stval = arch::trap::get_value();
if (scause == EXC_LOAD_PAGE_FAULT || scause == EXC_STORE_PAGE_FAULT) {
uint64 fixup = search_extable(tf->epc);
if (fixup != 0) {
/* Exception table entry found: redirect execution to fixup code */
tf->epc = fixup;
return;
}
}
TrapInfo info = build_trap_info(tf, scause, stval, TrapOrigin::FromKernel);
trap_handle(tf, info);
}
/* kernelvec assembly is in trap_vector.S */
extern "C" void kernelvec();
/* ========================================================================
* trap_init() — BSP: set stvec, init IrqManager
* ======================================================================== */
void trap_init()
{
g_irq_manager.init();
arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
}
/* ========================================================================
* trap_init_ap() — AP: set this CPU's stvec
* ======================================================================== */
void trap_init_ap()
{
arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec));
}
|