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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
/*
* Trap Vector Entry / Exit (RISC-V 64, S-mode)
*
* On trap entry (interrupt / exception / ecall):
* sscratch holds the TrapFrame pointer (set up by kernel before sret).
* We swap sscratch <-> tp, save all 31 GPRs + sstatus + sepc into TrapFrame,
* then load kernel metadata (sp, satp, tp, trap handler) and jump to C++.
*
* On trap return (userret):
* Restore all GPRs + sstatus + sepc from TrapFrame, swap sscratch <-> tp,
* then sret back to user mode.
*
* Layout must match arch::TrapFrame in arch/riscv/include/arch/trapframe.h
* Reference: docs/specs/arch_abstraction.md §7.2
*/
#include <arch/trapframe.h>
.section .text
.globl trap_vector
.globl userret
.align 4
/*
* trap_vector — unified S-mode trap entry point (Direct mode)
*
* Precondition: sscratch = pointer to current task's TrapFrame
*/
trap_vector:
/* Step 1: swap sscratch <-> tp. Now tp = TrapFrame*, sscratch = old tp */
csrrw tp, sscratch, tp
/* Step 2: save all 31 general-purpose registers into TrapFrame */
sd ra, TF_RA(tp)
sd sp, TF_SP(tp)
sd gp, TF_GP(tp)
/* tp (x4) is in sscratch — save it later */
sd t0, TF_T0(tp)
sd t1, TF_T1(tp)
sd t2, TF_T2(tp)
sd s0, TF_S0(tp)
sd s1, TF_S1(tp)
sd a0, TF_A0(tp)
sd a1, TF_A1(tp)
sd a2, TF_A2(tp)
sd a3, TF_A3(tp)
sd a4, TF_A4(tp)
sd a5, TF_A5(tp)
sd a6, TF_A6(tp)
sd a7, TF_A7(tp)
sd s2, TF_S2(tp)
sd s3, TF_S3(tp)
sd s4, TF_S4(tp)
sd s5, TF_S5(tp)
sd s6, TF_S6(tp)
sd s7, TF_S7(tp)
sd s8, TF_S8(tp)
sd s9, TF_S9(tp)
sd s10, TF_S10(tp)
sd s11, TF_S11(tp)
sd t3, TF_T3(tp)
sd t4, TF_T4(tp)
sd t5, TF_T5(tp)
sd t6, TF_T6(tp)
/* Save the real tp (user's tp) which is now in sscratch */
csrr t0, sscratch
sd t0, TF_TP(tp)
/* Step 3: save sstatus */
csrr t0, sstatus
sd t0, TF_SSTATUS(tp)
/* Step 4: save sepc (interrupted PC) */
csrr t0, sepc
sd t0, TF_EPC(tp)
/* Step 5: load kernel context from TrapFrame metadata */
ld sp, TF_KERNEL_SP(tp) /* switch to kernel stack */
ld t1, TF_KERNEL_SATP(tp) /* kernel page table */
ld t2, TF_KERNEL_CPUID(tp) /* CPU ID for tp */
ld t3, TF_KERNEL_TRAP(tp) /* C++ trap_handler address */
/* Switch to kernel page table */
csrw satp, t1
/* Step 6: flush TLB after page table switch */
sfence.vma zero, zero
/* Pass TrapFrame* to C++ handler (a0 = tf) */
mv a0, tp
/* Set tp = CPU ID (kernel convention) */
mv tp, t2
/* Step 7: jump to C++ trap_handler */
/* Step 7: jump to C++ trap_handler */
jalr t3
/* ========================================================================
* kernelvec — unified S-mode trap entry point for traps FROM S-MODE
*
* Traps from S-mode already have a valid kernel stack pointer in sp.
* We just allocate a TrapFrame on the stack and save registers.
* ======================================================================== */
.globl kernelvec
.align 4
kernelvec:
/* Allocate TrapFrame on stack */
addi sp, sp, -296
/* Save all 31 general-purpose registers into TrapFrame */
sd ra, TF_RA(sp)
/* Save the OLD sp (before we subtracted 296) */
addi t0, sp, 296
sd t0, TF_SP(sp)
sd gp, TF_GP(sp)
sd tp, TF_TP(sp)
sd t0, TF_T0(sp)
sd t1, TF_T1(sp)
sd t2, TF_T2(sp)
sd s0, TF_S0(sp)
sd s1, TF_S1(sp)
sd a0, TF_A0(sp)
sd a1, TF_A1(sp)
sd a2, TF_A2(sp)
sd a3, TF_A3(sp)
sd a4, TF_A4(sp)
sd a5, TF_A5(sp)
sd a6, TF_A6(sp)
sd a7, TF_A7(sp)
sd s2, TF_S2(sp)
sd s3, TF_S3(sp)
sd s4, TF_S4(sp)
sd s5, TF_S5(sp)
sd s6, TF_S6(sp)
sd s7, TF_S7(sp)
sd s8, TF_S8(sp)
sd s9, TF_S9(sp)
sd s10, TF_S10(sp)
sd s11, TF_S11(sp)
sd t3, TF_T3(sp)
sd t4, TF_T4(sp)
sd t5, TF_T5(sp)
sd t6, TF_T6(sp)
/* Save sstatus and sepc */
csrr t0, sstatus
sd t0, TF_SSTATUS(sp)
csrr t0, sepc
sd t0, TF_EPC(sp)
/* Call C++ kernel_trap_handler(TrapFrame* tf) */
mv a0, sp
call kernel_trap_handler
/* Restore sstatus and sepc */
ld t0, TF_SSTATUS(sp)
csrw sstatus, t0
ld t0, TF_EPC(sp)
csrw sepc, t0
/* Restore all 31 general-purpose registers */
ld ra, TF_RA(sp)
ld gp, TF_GP(sp)
ld tp, TF_TP(sp)
ld t0, TF_T0(sp)
ld t1, TF_T1(sp)
ld t2, TF_T2(sp)
ld s0, TF_S0(sp)
ld s1, TF_S1(sp)
ld a0, TF_A0(sp)
ld a1, TF_A1(sp)
ld a2, TF_A2(sp)
ld a3, TF_A3(sp)
ld a4, TF_A4(sp)
ld a5, TF_A5(sp)
ld a6, TF_A6(sp)
ld a7, TF_A7(sp)
ld s2, TF_S2(sp)
ld s3, TF_S3(sp)
ld s4, TF_S4(sp)
ld s5, TF_S5(sp)
ld s6, TF_S6(sp)
ld s7, TF_S7(sp)
ld s8, TF_S8(sp)
ld s9, TF_S9(sp)
ld s10, TF_S10(sp)
ld s11, TF_S11(sp)
ld t3, TF_T3(sp)
ld t4, TF_T4(sp)
ld t5, TF_T5(sp)
ld t6, TF_T6(sp)
/* Restore sp last */
ld sp, TF_SP(sp)
sret
/*
* userret — return to user mode
*
* Called from C++ as: userret(TrapFrame* tf)
* a0 = pointer to TrapFrame (with user satp already written by caller)
*
* Precondition: interrupts must be disabled by caller.
*/
userret:
/* a0 = TrapFrame pointer, use it as base */
/* Step 1: write back epc -> sepc */
ld t0, TF_EPC(a0)
csrw sepc, t0
/* Step 2: write back sstatus */
ld t0, TF_SSTATUS(a0)
csrw sstatus, t0
/* Step 3: caller is responsible for switching satp to user page table
* and flushing TLB before calling userret. */
/* Store TrapFrame address into sscratch for next trap entry */
csrw sscratch, a0
/* Use a0 as base to restore all registers */
mv tp, a0
/* Step 4: restore all 31 general-purpose registers */
ld ra, TF_RA(tp)
ld sp, TF_SP(tp)
ld gp, TF_GP(tp)
/* defer tp restore — we're using it as base */
ld t0, TF_T0(tp)
ld t1, TF_T1(tp)
ld t2, TF_T2(tp)
ld s0, TF_S0(tp)
ld s1, TF_S1(tp)
ld a0, TF_A0(tp)
ld a1, TF_A1(tp)
ld a2, TF_A2(tp)
ld a3, TF_A3(tp)
ld a4, TF_A4(tp)
ld a5, TF_A5(tp)
ld a6, TF_A6(tp)
ld a7, TF_A7(tp)
ld s2, TF_S2(tp)
ld s3, TF_S3(tp)
ld s4, TF_S4(tp)
ld s5, TF_S5(tp)
ld s6, TF_S6(tp)
ld s7, TF_S7(tp)
ld s8, TF_S8(tp)
ld s9, TF_S9(tp)
ld s10, TF_S10(tp)
ld s11, TF_S11(tp)
ld t3, TF_T3(tp)
ld t4, TF_T4(tp)
ld t5, TF_T5(tp)
ld t6, TF_T6(tp)
/* Step 5: restore user tp (swap sscratch <-> tp) */
/* sscratch currently = TrapFrame address, tp = TrapFrame address */
/* We need: tp = user's tp, sscratch = TrapFrame address */
/* Load user tp from TrapFrame first, but we can't use any temp reg now.
* Use the sscratch swap trick: */
ld tp, TF_TP(tp)
/* sscratch already holds TrapFrame address (set above with csrw) */
/* Step 6: sret — return to user mode */
sret
|