vectors.S 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include "soc/soc.h"
  7. #include "soc/interrupt_reg.h"
  8. #include "riscv/rvruntime-frames.h"
  9. #include "soc/soc_caps.h"
  10. #include "sdkconfig.h"
  11. #define STORE sw
  12. #define LOAD lw
  13. #define REGBYTES 4
  14. .equ SAVE_REGS, 32
  15. .equ CONTEXT_SIZE, (SAVE_REGS * 4)
  16. .equ panic_from_exception, xt_unhandled_exception
  17. .equ panic_from_isr, panicHandler
  18. /* Macro which first allocates space on the stack to save general
  19. * purpose registers, and then save them. GP register is excluded.
  20. * The default size allocated on the stack is CONTEXT_SIZE, but it
  21. * can be overridden. */
  22. .macro save_general_regs cxt_size=CONTEXT_SIZE
  23. addi sp, sp, -\cxt_size
  24. sw ra, RV_STK_RA(sp)
  25. sw tp, RV_STK_TP(sp)
  26. sw t0, RV_STK_T0(sp)
  27. sw t1, RV_STK_T1(sp)
  28. sw t2, RV_STK_T2(sp)
  29. sw s0, RV_STK_S0(sp)
  30. sw s1, RV_STK_S1(sp)
  31. sw a0, RV_STK_A0(sp)
  32. sw a1, RV_STK_A1(sp)
  33. sw a2, RV_STK_A2(sp)
  34. sw a3, RV_STK_A3(sp)
  35. sw a4, RV_STK_A4(sp)
  36. sw a5, RV_STK_A5(sp)
  37. sw a6, RV_STK_A6(sp)
  38. sw a7, RV_STK_A7(sp)
  39. sw s2, RV_STK_S2(sp)
  40. sw s3, RV_STK_S3(sp)
  41. sw s4, RV_STK_S4(sp)
  42. sw s5, RV_STK_S5(sp)
  43. sw s6, RV_STK_S6(sp)
  44. sw s7, RV_STK_S7(sp)
  45. sw s8, RV_STK_S8(sp)
  46. sw s9, RV_STK_S9(sp)
  47. sw s10, RV_STK_S10(sp)
  48. sw s11, RV_STK_S11(sp)
  49. sw t3, RV_STK_T3(sp)
  50. sw t4, RV_STK_T4(sp)
  51. sw t5, RV_STK_T5(sp)
  52. sw t6, RV_STK_T6(sp)
  53. .endm
  54. .macro save_mepc
  55. csrr t0, mepc
  56. sw t0, RV_STK_MEPC(sp)
  57. .endm
  58. /* Restore the general purpose registers (excluding gp) from the context on
  59. * the stack. The context is then deallocated. The default size is CONTEXT_SIZE
  60. * but it can be overriden. */
  61. .macro restore_general_regs cxt_size=CONTEXT_SIZE
  62. lw ra, RV_STK_RA(sp)
  63. lw tp, RV_STK_TP(sp)
  64. lw t0, RV_STK_T0(sp)
  65. lw t1, RV_STK_T1(sp)
  66. lw t2, RV_STK_T2(sp)
  67. lw s0, RV_STK_S0(sp)
  68. lw s1, RV_STK_S1(sp)
  69. lw a0, RV_STK_A0(sp)
  70. lw a1, RV_STK_A1(sp)
  71. lw a2, RV_STK_A2(sp)
  72. lw a3, RV_STK_A3(sp)
  73. lw a4, RV_STK_A4(sp)
  74. lw a5, RV_STK_A5(sp)
  75. lw a6, RV_STK_A6(sp)
  76. lw a7, RV_STK_A7(sp)
  77. lw s2, RV_STK_S2(sp)
  78. lw s3, RV_STK_S3(sp)
  79. lw s4, RV_STK_S4(sp)
  80. lw s5, RV_STK_S5(sp)
  81. lw s6, RV_STK_S6(sp)
  82. lw s7, RV_STK_S7(sp)
  83. lw s8, RV_STK_S8(sp)
  84. lw s9, RV_STK_S9(sp)
  85. lw s10, RV_STK_S10(sp)
  86. lw s11, RV_STK_S11(sp)
  87. lw t3, RV_STK_T3(sp)
  88. lw t4, RV_STK_T4(sp)
  89. lw t5, RV_STK_T5(sp)
  90. lw t6, RV_STK_T6(sp)
  91. addi sp,sp, \cxt_size
  92. .endm
  93. .macro restore_mepc
  94. lw t0, RV_STK_MEPC(sp)
  95. csrw mepc, t0
  96. .endm
  97. .global rtos_int_enter
  98. .global rtos_int_exit
  99. .global _global_interrupt_handler
  100. .section .exception_vectors.text
  101. /* This is the vector table. MTVEC points here.
  102. *
  103. * Use 4-byte intructions here. 1 instruction = 1 entry of the table.
  104. * The CPU jumps to MTVEC (i.e. the first entry) in case of an exception,
  105. * and (MTVEC & 0xfffffffc) + (mcause & 0x7fffffff) * 4, in case of an interrupt.
  106. *
  107. * Note: for our CPU, we need to place this on a 256-byte boundary, as CPU
  108. * only uses the 24 MSBs of the MTVEC, i.e. (MTVEC & 0xffffff00).
  109. */
  110. .balign 0x100
  111. .global _vector_table
  112. .type _vector_table, @function
  113. _vector_table:
  114. .option push
  115. .option norvc
  116. j _panic_handler /* exception handler, entry 0 */
  117. .rept (ETS_T1_WDT_INUM - 1)
  118. j _interrupt_handler /* 24 identical entries, all pointing to the interrupt handler */
  119. .endr
  120. j _panic_handler /* Call panic handler for ETS_T1_WDT_INUM interrupt (soc-level panic)*/
  121. j _panic_handler /* Call panic handler for ETS_CACHEERR_INUM interrupt (soc-level panic)*/
  122. #ifdef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
  123. j _panic_handler /* Call panic handler for ETS_MEMPROT_ERR_INUM interrupt (soc-level panic)*/
  124. .rept (ETS_MAX_INUM - ETS_MEMPROT_ERR_INUM)
  125. #else
  126. .rept (ETS_MAX_INUM - ETS_CACHEERR_INUM)
  127. #endif //CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
  128. j _interrupt_handler /* 6 identical entries, all pointing to the interrupt handler */
  129. .endr
  130. .option pop
  131. .size _vector_table, .-_vector_table
  132. /* Exception handler.*/
  133. .type _panic_handler, @function
  134. _panic_handler:
  135. /* Allocate space on the stack and store general purpose registers */
  136. save_general_regs RV_STK_FRMSZ
  137. /* As gp register is not saved by the macro, save it here */
  138. sw gp, RV_STK_GP(sp)
  139. /* Same goes for the SP value before trapping */
  140. addi t0, sp, RV_STK_FRMSZ /* restore sp with the value when trap happened */
  141. /* Save CSRs */
  142. sw t0, RV_STK_SP(sp)
  143. csrr t0, mepc
  144. sw t0, RV_STK_MEPC(sp)
  145. csrr t0, mstatus
  146. sw t0, RV_STK_MSTATUS(sp)
  147. csrr t0, mtvec
  148. sw t0, RV_STK_MTVEC(sp)
  149. csrr t0, mtval
  150. sw t0, RV_STK_MTVAL(sp)
  151. csrr t0, mhartid
  152. sw t0, RV_STK_MHARTID(sp)
  153. /* Call panic_from_exception(sp) or panic_from_isr(sp)
  154. * depending on whether we have a pseudo excause or not.
  155. * If mcause's highest bit is 1, then an interrupt called this routine,
  156. * so we have a pseudo excause. Else, it is due to a exception, we don't
  157. * have an pseudo excause */
  158. mv a0, sp
  159. csrr a1, mcause
  160. /* Branches instructions don't accept immediates values, so use t1 to
  161. * store our comparator */
  162. li t0, 0x80000000
  163. bgeu a1, t0, _call_panic_handler
  164. sw a1, RV_STK_MCAUSE(sp)
  165. /* exception_from_panic never returns */
  166. jal panic_from_exception
  167. /* We arrive here if the exception handler has returned. */
  168. j _return_from_exception
  169. _call_panic_handler:
  170. /* Remove highest bit from mcause (a1) register and save it in the
  171. * structure */
  172. not t0, t0
  173. and a1, a1, t0
  174. sw a1, RV_STK_MCAUSE(sp)
  175. jal panic_from_isr
  176. /* We arrive here if the exception handler has returned. This means that
  177. * the exception was handled, and the execution flow should resume.
  178. * Restore the registers and return from the exception.
  179. */
  180. _return_from_exception:
  181. restore_mepc
  182. /* MTVEC and SP are assumed to be unmodified.
  183. * MSTATUS, MHARTID, MTVAL are read-only and not restored.
  184. */
  185. lw gp, RV_STK_GP(sp)
  186. restore_general_regs RV_STK_FRMSZ
  187. mret
  188. .size _panic_handler, .-_panic_handler
  189. /* This is the interrupt handler.
  190. * It saves the registers on the stack,
  191. * prepares for interrupt nesting,
  192. * re-enables the interrupts,
  193. * then jumps to the C dispatcher in interrupt.c.
  194. */
  195. .global _interrupt_handler
  196. .type _interrupt_handler, @function
  197. #ifndef CONFIG_IDF_RTOS_RTTHREAD
  198. _interrupt_handler:
  199. /* Start by saving the general purpose registers and the PC value before
  200. * the interrupt happened. */
  201. save_general_regs
  202. save_mepc
  203. /* Though it is not necessary we save GP and SP here.
  204. * SP is necessary to help GDB to properly unwind
  205. * the backtrace of threads preempted by interrupts (OS tick etc.).
  206. * GP is saved just to have its proper value in GDB. */
  207. /* As gp register is not saved by the macro, save it here */
  208. sw gp, RV_STK_GP(sp)
  209. /* Same goes for the SP value before trapping */
  210. addi t0, sp, CONTEXT_SIZE /* restore sp with the value when interrupt happened */
  211. /* Save SP */
  212. sw t0, RV_STK_SP(sp)
  213. /* Before doing anythig preserve the stack pointer */
  214. /* It will be saved in current TCB, if needed */
  215. mv a0, sp
  216. call rtos_int_enter
  217. /* If this is a non-nested interrupt, SP now points to the interrupt stack */
  218. /* Before dispatch c handler, restore interrupt to enable nested intr */
  219. csrr s1, mcause
  220. csrr s2, mstatus
  221. /* Save the interrupt threshold level */
  222. la t0, INTERRUPT_CORE0_CPU_INT_THRESH_REG
  223. lw s3, 0(t0)
  224. /* Increase interrupt threshold level */
  225. li t2, 0x7fffffff
  226. and t1, s1, t2 /* t1 = mcause & mask */
  227. slli t1, t1, 2 /* t1 = mcause * 4 */
  228. la t2, INTC_INT_PRIO_REG(0)
  229. add t1, t2, t1 /* t1 = INTC_INT_PRIO_REG + 4 * mcause */
  230. lw t2, 0(t1) /* t2 = INTC_INT_PRIO_REG[mcause] */
  231. addi t2, t2, 1 /* t2 = t2 +1 */
  232. sw t2, 0(t0) /* INTERRUPT_CORE0_CPU_INT_THRESH_REG = t2 */
  233. fence
  234. li t0, 0x8
  235. csrrs t0, mstatus, t0
  236. /* MIE set. Nested interrupts can now occur */
  237. #ifdef CONFIG_PM_TRACE
  238. li a0, 0 /* = ESP_PM_TRACE_IDLE */
  239. #if SOC_CPU_CORES_NUM == 1
  240. li a1, 0 /* No need to check core ID on single core hardware */
  241. #else
  242. csrr a1, mhartid
  243. #endif
  244. la t0, esp_pm_trace_exit
  245. jalr t0 /* absolute jump, avoid the 1 MiB range constraint */
  246. #endif
  247. #ifdef CONFIG_PM_ENABLE
  248. la t0, esp_pm_impl_isr_hook
  249. jalr t0 /* absolute jump, avoid the 1 MiB range constraint */
  250. #endif
  251. /* call the C dispatcher */
  252. mv a0, sp /* argument 1, stack pointer */
  253. mv a1, s1 /* argument 2, interrupt number (mcause) */
  254. /* mask off the interrupt flag of mcause */
  255. li t0, 0x7fffffff
  256. and a1, a1, t0
  257. jal _global_interrupt_handler
  258. /* After dispatch c handler, disable interrupt to make freertos make context switch */
  259. li t0, 0x8
  260. csrrc t0, mstatus, t0
  261. /* MIE cleared. Nested interrupts are disabled */
  262. /* restore the interrupt threshold level */
  263. la t0, INTERRUPT_CORE0_CPU_INT_THRESH_REG
  264. sw s3, 0(t0)
  265. fence
  266. /* Yield to the next task is needed: */
  267. mv a0, sp
  268. call rtos_int_exit
  269. /* If this is a non-nested interrupt, context switch called, SP now points to back to task stack. */
  270. /* The next (or current) stack pointer is returned in a0 */
  271. mv sp, a0
  272. /* restore the rest of the registers */
  273. csrw mcause, s1
  274. csrw mstatus, s2
  275. restore_mepc
  276. restore_general_regs
  277. /* exit, this will also re-enable the interrupts */
  278. mret
  279. .size _interrupt_handler, .-_interrupt_handler
  280. #else
  281. _interrupt_handler:
  282. /* 此时CPU的sp = from_thread->sp */
  283. /* 注意: 在这里,并没有将mepc的值赋值为from_thread栈中的epc,但后面会赋值 */
  284. addi sp, sp, -32 * REGBYTES /* sp = sp - 32 * 4 栈指针向下偏移32个寄存器长度,用来将CPU的寄存器保存到from_thread的栈中*/
  285. STORE x1, 1 * REGBYTES(sp) /* 将CPU的x1寄存器,即ra寄存器,保存到from_thread->栈中 */
  286. li t0, 0x80 /* t0 = 0x80 */
  287. STORE t0, 2 * REGBYTES(sp) /* mstatus = t0, 即关闭全局中断 */
  288. /* 将 CPU 的其他寄存器的值,保存到from_thread的任务栈中 */
  289. STORE x4, 4 * REGBYTES(sp)
  290. STORE x5, 5 * REGBYTES(sp)
  291. STORE x6, 6 * REGBYTES(sp)
  292. STORE x7, 7 * REGBYTES(sp)
  293. STORE x8, 8 * REGBYTES(sp)
  294. STORE x9, 9 * REGBYTES(sp)
  295. STORE x10, 10 * REGBYTES(sp)
  296. STORE x11, 11 * REGBYTES(sp)
  297. STORE x12, 12 * REGBYTES(sp)
  298. STORE x13, 13 * REGBYTES(sp)
  299. STORE x14, 14 * REGBYTES(sp)
  300. STORE x15, 15 * REGBYTES(sp)
  301. STORE x16, 16 * REGBYTES(sp)
  302. STORE x17, 17 * REGBYTES(sp)
  303. STORE x18, 18 * REGBYTES(sp)
  304. STORE x19, 19 * REGBYTES(sp)
  305. STORE x20, 20 * REGBYTES(sp)
  306. STORE x21, 21 * REGBYTES(sp)
  307. STORE x22, 22 * REGBYTES(sp)
  308. STORE x23, 23 * REGBYTES(sp)
  309. STORE x24, 24 * REGBYTES(sp)
  310. STORE x25, 25 * REGBYTES(sp)
  311. STORE x26, 26 * REGBYTES(sp)
  312. STORE x27, 27 * REGBYTES(sp)
  313. STORE x28, 28 * REGBYTES(sp)
  314. STORE x29, 29 * REGBYTES(sp)
  315. STORE x30, 30 * REGBYTES(sp)
  316. STORE x31, 31 * REGBYTES(sp)
  317. /* 备份 CPU 的 sp (这时,CPU的sp其实就是from thread的sp指针) 寄存器的值到 s0 寄存器中,下面会使用s0,恢复 CPU 的寄存器 */
  318. move s0, sp /* s0 = sp */
  319. /* 在中断函数中,中断函数中调用的C函数,需要使用 sp, 这里,在中断函数中,使用的 sp 为,系统的栈资源 */
  320. /* switch to interrupt stack */
  321. la sp, __stack_end__ /* sp = _sp */
  322. /* interrupt handle */
  323. /* 注意: 在调用C函数之前,比如sp的值为0x30001000, 在执行完C函数后,sp的值还是会变成 0x30001000 */
  324. call rt_interrupt_enter /* 执行所有的中断函数前,调用该函数 */
  325. csrr s1, mcause
  326. csrr s2, mstatus
  327. /* Save the interrupt threshold level */
  328. la t0, INTERRUPT_CORE0_CPU_INT_THRESH_REG
  329. lw s3, 0(t0)
  330. li t2, 0x7fffffff
  331. and t1, s1, t2 /* t1 = mcause & mask */
  332. slli t1, t1, 2 /* t1 = mcause * 4 */
  333. la t2, INTC_INT_PRIO_REG(0)
  334. add t1, t2, t1 /* t1 = INTC_INT_PRIO_REG + 4 * mcause */
  335. lw t2, 0(t1) /* t2 = INTC_INT_PRIO_REG[mcause] */
  336. addi t2, t2, 1 /* t2 = t2 +1 */
  337. sw t2, 0(t0) /* INTERRUPT_CORE0_CPU_INT_THRESH_REG = t2 */
  338. fence
  339. li t0, 0x8
  340. csrrs t0, mstatus, t0
  341. /* call the C dispatcher */
  342. mv a0, sp /* argument 1, stack pointer */
  343. mv a1, s1 /* argument 2, interrupt number (mcause) */
  344. /* mask off the interrupt flag of mcause */
  345. li t0, 0x7fffffff
  346. and a1, a1, t0
  347. jal _global_interrupt_handler
  348. li t0, 0x8
  349. csrrc t0, mstatus, t0
  350. /* restore the interrupt threshold level */
  351. la t0, INTERRUPT_CORE0_CPU_INT_THRESH_REG
  352. sw s3, 0(t0)
  353. fence
  354. call rt_interrupt_leave /* 执行所有的中断函数后,调用该函数 */
  355. /* 上面,将保存执行中断服务函数之前的CPU的sp寄存器到了s0所指向的位置处,当执行完中断服务函数,需要将之前的CPU寄存器,恢复一下,此时sp又变成了from thread的sp了 */
  356. move sp, s0 /* sp = s0 */
  357. /* 下面两句话,相当于将 rt_thread_switch_interrupt_flag 值,赋值给了s2 */
  358. /* 将 rt_thread_switch_interrupt_flag 的地址值,赋值给 s0 寄存器*/
  359. la s0, rt_thread_switch_interrupt_flag /* s0 = &rt_thread_switch_interrupt_flag */
  360. /* 将 s0 所指向的地址处的内容,取出来,赋值给 s2 寄存器,其实就是将 rt_thread_switch_interrupt_flag 的值,赋值给了 s2 寄存器*/
  361. lw s2, 0(s0) /* s2 = *s0 = rt_thread_switch_interrupt_flag */
  362. /* 如果 s2的值,即 rt_thread_switch_interrupt_flag 值,如果不为0,则需要继续执行下一条指令,如果为0,则需要跳转到 spurious_interrupt 标号处 执行 */
  363. /* 如果 s2的值等于0,rt_thread_switch_interrupt_flag等于0, 则不需要在中断处理函数中,进行上下文切换,反之则需要 */
  364. /* 如果不需要上下文切换, */
  365. /* 在这里,跳转到 spurious_interrupt的话,是不会进行上下文切换的,因为,此时CPU的sp指针还是from线程的*/
  366. beqz s2, spurious_interrupt /* if (s2 == 0) goto spurious_interrupt; else 执行下一条语句*/
  367. /* 需要上下文切换: 主要目的是将CPU的sp指针,赋值为to_thread的sp */
  368. /* 将 s0 所执向的地址的内容设置为0, 也就是,将变量 rt_thread_switch_interrupt_flag 赋值为了 0 */
  369. /* s0存放的值是 rt_thread_switch_interrupt_flag 变量的地址*/
  370. sw zero, 0(s0) /* *s0 = 0; 也就是 rt_thread_switch_interrupt_flag = 0 */
  371. /* 将 mepc 的值,赋值给 a0 寄存器,mepc 的值是,跳转到中断函数执行之前的 PC 指针 */
  372. /* 这时的mpec其实,还是from线程,在跳转到中断执行前的一个PC地址 */
  373. csrr a0, mepc /* a0 = mepc */
  374. /* 将 mpec 的值写回到freom thread任务栈中的 epc 中,待后续,恢复from线程时,使用 */
  375. STORE a0, 0 * REGBYTES(sp) /* from_thread->sp->epc = a0 ,中断入口处*/
  376. /* 将from_thread的sp指针,赋值为CPU的sp指针 */
  377. la s0, rt_interrupt_from_thread /* s0 = &rt_interrupt_from_thread 注意: rt_interrupt_from_thread = &(from_thread->sp) */
  378. LOAD s1, 0(s0) /* s1 = rt_interrupt_from_thread,也就是s1 = &(from_thread->sp) */
  379. STORE sp, 0(s1) /* from_thread->sp = sp*/
  380. /* 接下来,需要开始恢复CPU的sp为to_thread的sp了 */
  381. la s0, rt_interrupt_to_thread /* s0 = &rt_interrupt_to_thread 注意: rt_interrupt_to_thread = &(to_thred->sp)*/
  382. LOAD s1, 0(s0) /* s1 = rt_interrupt_to_thread, 也就是s1 = &(to_thred->sp) */
  383. LOAD sp, 0(s1) /* sp = (to_thred->sp)*/
  384. /* 将CPU的 mepc设置为to_thred的mepc,待中断退出,执行mret指令后,将从该地址开始执行 */
  385. LOAD a0, 0 * REGBYTES(sp) /* a0 = to_thread的mepc的值*/
  386. csrw mepc, a0 /* mepc = a0 */
  387. spurious_interrupt:
  388. LOAD x1, 1 * REGBYTES(sp)
  389. /* Remain in M-mode after mret */
  390. li t0, 0x00001800
  391. csrs mstatus, t0
  392. LOAD t0, 2 * REGBYTES(sp)
  393. csrs mstatus, t0
  394. LOAD x4, 4 * REGBYTES(sp)
  395. LOAD x5, 5 * REGBYTES(sp)
  396. LOAD x6, 6 * REGBYTES(sp)
  397. LOAD x7, 7 * REGBYTES(sp)
  398. LOAD x8, 8 * REGBYTES(sp)
  399. LOAD x9, 9 * REGBYTES(sp)
  400. LOAD x10, 10 * REGBYTES(sp)
  401. LOAD x11, 11 * REGBYTES(sp)
  402. LOAD x12, 12 * REGBYTES(sp)
  403. LOAD x13, 13 * REGBYTES(sp)
  404. LOAD x14, 14 * REGBYTES(sp)
  405. LOAD x15, 15 * REGBYTES(sp)
  406. LOAD x16, 16 * REGBYTES(sp)
  407. LOAD x17, 17 * REGBYTES(sp)
  408. LOAD x18, 18 * REGBYTES(sp)
  409. LOAD x19, 19 * REGBYTES(sp)
  410. LOAD x20, 20 * REGBYTES(sp)
  411. LOAD x21, 21 * REGBYTES(sp)
  412. LOAD x22, 22 * REGBYTES(sp)
  413. LOAD x23, 23 * REGBYTES(sp)
  414. LOAD x24, 24 * REGBYTES(sp)
  415. LOAD x25, 25 * REGBYTES(sp)
  416. LOAD x26, 26 * REGBYTES(sp)
  417. LOAD x27, 27 * REGBYTES(sp)
  418. LOAD x28, 28 * REGBYTES(sp)
  419. LOAD x29, 29 * REGBYTES(sp)
  420. LOAD x30, 30 * REGBYTES(sp)
  421. LOAD x31, 31 * REGBYTES(sp)
  422. addi sp, sp, 32 * REGBYTES
  423. mret
  424. .size _interrupt_handler, .-_interrupt_handler
  425. #endif