summaryrefslogtreecommitdiff
path: root/na/arch/riscv/trap/trap_vector.S
blob: f0b65b57b0759aaa711b65e5a8d4a04d487a0c2e (plain)
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
/* 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

    /* Set tp = CPU ID (kernel convention) */
    mv tp, t2

    /* Step 7: jump to C++ trap_handler */
    jalr t3


/*
 * 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