xv6, line by line
kernel/trampoline.S

kernel/trampoline.S

RISC-V assembly · 153 lines · annotated 100% · kernel · upstream

About this file

Every trap from a user program, whether a system call, a device interrupt, a timer tick or a fault, enters the kernel through uservec in this file, and every return to user mode leaves through userret. Together they are the only code that runs while a hart is half-way between a user program and the kernel.

The difficulty is that the RISC-V trap hardware does very little. It switches to supervisor mode, records the cause and the interrupted PC, and jumps to stvec. It does not change the page table, the stack pointer, or any general-purpose register. So uservec starts with every register still holding the user program’s values and the user page table still installed. It must save all 31 user registers without destroying any of them, then load the kernel’s stack, hart ID and page table, and only then call C code (usertrap in kernel/trap.c). userret does the reverse.

To survive the page-table switch, this code lives on its own page, the trampoline page, mapped at the same virtual address in the kernel and in every process. The registers are saved in the process’s trapframe; the byte offsets used below are the field offsets of struct trapframe.

Read before: kernel/proc.h (struct trapframe), kernel/memlayout.h (TRAMPOLINE, TRAPFRAME). Read next: kernel/trap.c.

1 #
2 # low-level code to handle traps from user space into
3 # the kernel, and returns from kernel to user.
4 #
5 # the kernel maps the page holding this code
6 # at the same virtual address (TRAMPOLINE)
7 # in user and kernel space so that it continues
8 # to work when it switches page tables.
9 # kernel.ld causes this code to start at
10 # a page boundary.
11 #
13#include "riscv.h"
16.section trampsec
18.globl usertrap
20.align 4
21.globl uservec
23 #
24 # trap.c sets stvec to point here, so
25 # traps from user space start here,
26 # in supervisor mode, but with a
27 # user page table.
28 #
30 # save user a0 in sscratch so
31 # a0 can be used to get at TRAPFRAME.
32 csrw sscratch, a0
34 # each process has a separate p->trapframe memory area,
35 # but it's mapped to the same virtual address
36 # (TRAPFRAME) in every process's user page table.
37 li a0, TRAPFRAME
39 # save the user registers in TRAPFRAME
40 sd ra, 40(a0)
41 sd sp, 48(a0)
42 sd gp, 56(a0)
43 sd tp, 64(a0)
44 sd t0, 72(a0)
45 sd t1, 80(a0)
46 sd t2, 88(a0)
47 sd s0, 96(a0)
48 sd s1, 104(a0)
49 sd a1, 120(a0)
50 sd a2, 128(a0)
51 sd a3, 136(a0)
52 sd a4, 144(a0)
53 sd a5, 152(a0)
54 sd a6, 160(a0)
55 sd a7, 168(a0)
56 sd s2, 176(a0)
57 sd s3, 184(a0)
58 sd s4, 192(a0)
59 sd s5, 200(a0)
60 sd s6, 208(a0)
61 sd s7, 216(a0)
62 sd s8, 224(a0)
63 sd s9, 232(a0)
64 sd s10, 240(a0)
65 sd s11, 248(a0)
66 sd t3, 256(a0)
67 sd t4, 264(a0)
68 sd t5, 272(a0)
69 sd t6, 280(a0)
71 # save the user a0 in p->trapframe->a0
72 csrr t0, sscratch
73 sd t0, 112(a0)
75 # initialize kernel stack pointer, from p->trapframe->kernel_sp
76 ld sp, 8(a0)
78 # make tp hold the current hartid, from p->trapframe->kernel_hartid
79 ld tp, 32(a0)
81 # load the address of usertrap(), from p->trapframe->kernel_trap
82 ld t0, 16(a0)
84 # fetch the kernel page table address, from p->trapframe->kernel_satp.
85 ld t1, 0(a0)
87 # wait for any previous memory operations to complete, so that
88 # they use the user page table.
89 sfence.vma zero, zero
91 # install the kernel page table.
92 csrw satp, t1
94 # flush now-stale user entries from the TLB.
95 sfence.vma zero, zero
97 # call usertrap()
98 jalr t0
100.globl userret
102 # usertrap() returns here, with user satp in a0.
103 # return from kernel to user.
105 # flush icache, in case this is the first time
106 # we're running this proc on this hart.
107 fence.i
109 # switch to the user page table.
110 sfence.vma zero, zero
111 csrw satp, a0
112 sfence.vma zero, zero
114 li a0, TRAPFRAME
116 # restore all but a0 from TRAPFRAME
117 ld ra, 40(a0)
118 ld sp, 48(a0)
119 ld gp, 56(a0)
120 ld tp, 64(a0)
121 ld t0, 72(a0)
122 ld t1, 80(a0)
123 ld t2, 88(a0)
124 ld s0, 96(a0)
125 ld s1, 104(a0)
126 ld a1, 120(a0)
127 ld a2, 128(a0)
128 ld a3, 136(a0)
129 ld a4, 144(a0)
130 ld a5, 152(a0)
131 ld a6, 160(a0)
132 ld a7, 168(a0)
133 ld s2, 176(a0)
134 ld s3, 184(a0)
135 ld s4, 192(a0)
136 ld s5, 200(a0)
137 ld s6, 208(a0)
138 ld s7, 216(a0)
139 ld s8, 224(a0)
140 ld s9, 232(a0)
141 ld s10, 240(a0)
142 ld s11, 248(a0)
143 ld t3, 256(a0)
144 ld t4, 264(a0)
145 ld t5, 272(a0)
146 ld t6, 280(a0)
148 # restore user a0
149 ld a0, 112(a0)
151 # return to user mode and user pc.
152 # prepare_return() sets up sstatus and sepc.
153 sret