/* ----------------------------------------------------------------------------- * isr.c —— 中断服务程序:从 MMIO 读鼠标事件(**这就是"中断读鼠标操作的指令"**) * * 这颗核的中断硬件行为(见 srcs/rtl/cpu21_riscv_irq_ctrl.v): * * irq_i 上升沿 -> 挂起位置 1;响应中断时硬件自动清挂起位、清 ustatus[0](MIE)、 * 把返回地址压进内部 EPC 栈; * * 从 uepc 返回用 `uret`(编码 0x00200073,不在标准 C 里,只能内联汇编); * * **硬件不保存任何通用寄存器**,所以软件必须自己保证不破坏被打断的代码。 * * 因此这里分了三个固定地址的入口 + 一个纯 C 主体: * 1) isr1_entry(.text.isr1 = 0x100):**关卡(游戏)入口**,调 level_main()。 * 游戏是一整段会调用别的函数的 C 程序,所以入口把**所有调用者保存寄存器** * (ra、t0-t6、a0-a7)都存起来,并用 csrrsi 打开 MIE 让鼠标中断能嵌套; * 2) isr2_entry(.text.isr2 = 0x200):**鼠标读取**,只存 ra 后调下面的 isr_body; * 3) isr3_entry(.text.isr3 = 0x300):备用,直接 uret; * 4) isr_body:**纯 C**,本文件用 -ffixed-x1,x5..x7,x10..x17,x28..x31 编译, * 逼 GCC 只用 s0-s11(callee-saved,函数自己会存/恢复)和 sp,于是被打断的 * 代码的调用者保存寄存器绝对不会被破坏 —— 这样主体就是安全的纯 C 叶子函数。 * * 主体要做的事:把硬件事件 FIFO(深度 16)里的项全部搬到软件环形队列, * 主循环再从队列里取。FIFO 读空后中断线自动回 0,下一个事件会重新产生上升沿, * 所以不会丢事件。 * ---------------------------------------------------------------------------*/ #include "mmio.h" /* ---- 与 saolei.c 共用的软件事件队列(定义在这里,声明在 mmio.h) ---- */ volatile unsigned g_evq[EVQ_SIZE]; volatile unsigned g_evq_head; /* 主循环消费 */ volatile unsigned g_evq_tail; /* 中断生产 */ volatile unsigned g_evq_lost; /* 队列满丢弃计数(调试) */ /* 中断主体:纯 C 叶子函数(不调用任何函数),只使用 s 寄存器和 sp。 * 编译本文件时必须加 -ffixed-x1 -ffixed-x5..x7 -ffixed-x10..x17 -ffixed-x28..x31 * (见 tools/build.ps1),否则它会用到调用者保存寄存器从而破坏被打断的代码。 */ __attribute__((used, noinline)) void isr_body(void); void isr_body(void) { unsigned ev, next; /* 硬件 FIFO 非空就一直搬(STATUS[0] = 非空) */ while ((REG_R(REG_STATUS) & 1u) != 0u) { ev = REG_R(REG_MDATA); /* lw:{btn[20:16], dy[15:8], dx[7:0]} */ next = (g_evq_tail + 1u) & EVQ_MASK; if (next != g_evq_head) { g_evq[g_evq_tail] = ev; g_evq_tail = next; } else { g_evq_lost = g_evq_lost + 1u; /* 软件队列满:丢弃并计数 */ } REG_W(REG_MACK) = 1u; /* sw:弹出硬件 FIFO 一项 */ } } /* --------------------------------------------------------------------------- * 三个中断入口,分别在链接脚本固定住的 0x100 / 0x200 / 0x300。 * * IRQ1 = 0x100:**关卡(游戏)入口**。游戏是一整段会调用别的函数的 C 程序, * 所以入口必须把**所有调用者保存寄存器**(ra、t0-t6、a0-a7)都存起来, * 否则返回时会把被打断的背景程序弄坏。进入中断时硬件已把 MIE 清 0,这里再 * 重新打开,鼠标中断(IRQ2 优先级更高)才能嵌套进来;uret 时硬件会从内部 * 栈里恢复原来的 MIE。 * IRQ2 = 0x200:**鼠标读取**。C 主体用 -ffixed 编译(只用 s 寄存器和 sp), * 所以入口只要保存 ra。 * IRQ3 = 0x300:备用(保留给拨码开关等外部源)。 * ------------------------------------------------------------------------- */ extern void level_main(void); __attribute__((naked, section(".text.isr1"), used, noinline)) void isr1_entry(void) { __asm__ volatile( "addi sp, sp, -64\n\t" "sw ra, 0(sp)\n\t" "sw t0, 4(sp)\n\t" "sw t1, 8(sp)\n\t" "sw t2, 12(sp)\n\t" "sw t3, 16(sp)\n\t" "sw t4, 20(sp)\n\t" "sw t5, 24(sp)\n\t" "sw t6, 28(sp)\n\t" "sw a0, 32(sp)\n\t" "sw a1, 36(sp)\n\t" "sw a2, 40(sp)\n\t" "sw a3, 44(sp)\n\t" "sw a4, 48(sp)\n\t" "sw a5, 52(sp)\n\t" "sw a6, 56(sp)\n\t" "sw a7, 60(sp)\n\t" "csrrsi x0, 0x004, 1\n\t" /* ustatus[0]=MIE:允许鼠标中断嵌套 */ "jal ra, level_main\n\t" "lw a7, 60(sp)\n\t" "lw a6, 56(sp)\n\t" "lw a5, 52(sp)\n\t" "lw a4, 48(sp)\n\t" "lw a3, 44(sp)\n\t" "lw a2, 40(sp)\n\t" "lw a1, 36(sp)\n\t" "lw a0, 32(sp)\n\t" "lw t6, 28(sp)\n\t" "lw t5, 24(sp)\n\t" "lw t4, 20(sp)\n\t" "lw t3, 16(sp)\n\t" "lw t2, 12(sp)\n\t" "lw t1, 8(sp)\n\t" "lw t0, 4(sp)\n\t" "lw ra, 0(sp)\n\t" "addi sp, sp, 64\n\t" "uret\n\t"); } __attribute__((naked, section(".text.isr2"), used, noinline)) void isr2_entry(void) { __asm__ volatile( "addi sp, sp, -8\n\t" /* 保存被打断代码的 ra(jal 会覆盖它) */ "sw ra, 0(sp)\n\t" "jal ra, isr_body\n\t" /* 调纯 C 主体(它自己保存 s 寄存器) */ "lw ra, 0(sp)\n\t" "addi sp, sp, 8\n\t" "uret\n\t"); /* 从 uepc 返回:这颗核的中断返回指令 */ } __attribute__((naked, section(".text.isr3"), used, noinline)) void isr3_entry(void) { __asm__ volatile("uret\n\t"); }