# trap进入处理函数的入口__alltraps, 以及第一次进入时的出口__restore

.altmacro
.macro SAVE_GP n
    sd x\n, \n*8(sp)    # 将寄存器 x_n 的值保存到栈空间中
.endm

.macro LOAD_GP n
    ld x\n, \n*8(sp)    # 从栈空间中加载寄存器 x_n 的值
.endm

.section .text.trampoline        # 进入 .text.trampoline 段
.globl __alltraps     # 声明全局符号 __alltraps
.globl __restore      # 声明全局符号 __restore
.align 2               # 对齐到 2^2 = 4 字节

__alltraps:           # __alltraps 符号的实现
    # 交换 sp 和 sscratch 寄存器的值
    # sscratch在陷入之前是用户空间 次高的页, 是trap context
    # 这里和sp交换之后, sscratch变成了用户栈顶, sp变成了trap context的位置
    csrrw sp, sscratch, sp

    # 保存通用寄存器
    sd x1, 1*8(sp)          # 保存寄存器 x1 的值  (这一步是为了跳过x0寄存器, 方便下面循环)
    # 跳过 sp(x2)，后面会再次保存
    sd x3, 3*8(sp)          # 保存寄存器 x3 的值  (这一步是为了跳过x4寄存器, 方便下面循环)
    # 跳过 tp(x4)，应用程序不使用该寄存器
    # 保存 x5~x31
    .set n, 5               # 定义变量 n 的初始值为 5
    .rept 27                # 循环 27 次
        SAVE_GP %n          # 保存寄存器 x_n 的值到trap context中
        .set n, n+1         # 将 n 加 1
    .endr                   # 结束指令块

    # 我们可以自由使用 t0/t1/t2，因为它们已保存在 trap context中
    # 保存CSR寄存器
    csrr t0, sstatus        # 读取 sstatus 寄存器的值
    csrr t1, sepc           # 读取 sepc 寄存器的值
    sd t0, 32*8(sp)         # 保存 sstatus 寄存器的值到trap context中
    sd t1, 33*8(sp)         # 保存 sepc 寄存器的值到trap context中

    # 从 sscratch 中读取用户栈，并将其保存到trap context中
    csrr t2, sscratch       # 读取 sscratch 寄存器的值
    sd t2, 2*8(sp)          # 保存用户栈的地址到 trap context中

    # 以上 通过用户空间 就保存了所有的寄存器, 他们保存在 用户空间的次高虚拟页
    # 下面开始恢复 用户空间和 内核栈 并跳转到处理函数, 这是在内核初始化该应用的时候, 设置好的

    # 等下需要加载内核的地址空间, 它是 KERNEL_SPACE.exclusive_access().token(), 他保存在了trap context中
    ld t0, 34*8(sp)

    # 等下jmp 到我们自己的trap 处理函数 他是 trap_handler as usize(这里直接as usize 转为地址, 还可以跳转到这个地址, 是因为我们是在切换内存空间之后jmp的)
    ld t1, 36*8(sp)

    # 切换栈, 上面 sp 是 trap context, 现在换成 trap context内保存的 用户内核栈
    ld sp, 35*8(sp)

    # 切换页表
    csrw satp, t0
    sfence.vma

    # 防止编译期使用相对位置call, 这时候, 已经进入内核的地址空间了, 防止编译器在链接的时候, 对call进行相对位置偏移call, 那肯定是不对的
    jr t1

__restore:                  # __restore 符号的实现
    # 这个是有 trap_return 调用的, 调用的时候把 trap_cx_ptr(每个用户程序都统一的地址TRAP_CONTEXT), 和 user_satp传进来

    # 恢复用户地址空间
    csrw satp, a1
    sfence.vma

    # sscratch当做临时寄存器 保存是用户空间 次高的页, 也就是是trap context
    csrw sscratch, a0
    # 把trap context 也copy到sp中, 下面会进行恢复
    mv sp, a0

    # 恢复 sstatus/sepc
    ld t0, 32*8(sp)         # 从trap context中读取 sstatus 寄存器的值
    ld t1, 33*8(sp)         # 从trap context中读取 sepc 寄存器的值
    csrw sstatus, t0        # 恢复 sstatus 寄存器的值
    csrw sepc, t1           # 恢复 sepc 寄存器的值

    # 恢复通用寄存器，除了 sp/tp 以外的寄存器
    # 跳过x0
    ld x1, 1*8(sp)          # 从栈空间中读取寄存器 x1 的值
    # 跳过x2
    ld x3, 3*8(sp)          # 从栈空间中读取寄存器 x3 的值
    # 循环恢复
    .set n, 5               # 定义变量 n 的初始值为 5
    .rept 27                # 循环 27 次
        LOAD_GP %n          # 从栈空间中加载寄存器 x_n 的值
        .set n, n+1        # 将 n 加 1
    .endr                   # 结束指令块

    # 恢复用户栈, 他在 trap context 中
    ld sp, 2*8(sp)
    sret                    # 返回指令, 根据sstatus(陷入/异常之前的特权级) 和 sepc(陷入/异常 之前的pc)返回