Interrupt Pipeline系列文章大纲-CSDN博客
2.5.1 el1_irq代码框架
2.5.2 kernel_entry 1与kernel_exit 1
2.5.2.1 概要
调用kernel_entry 1将进程运行的内核态现场按照struct pt_regs的格式存入进程内核栈。对于el1_irq来说,中断发生时,进程运行在内核态,正在使用进程的内核栈并且可能在进程内核栈中存在多级函数调用关系。因此,kernel_entry 1保存struct pg_regs的位置,并不是进程内核栈的栈底位置,而是根据实际情况,在进程内核栈中间的某个位置。
调用kernel_exit 1将已压入进程内核栈的内核态现场恢复到对应的寄存器。
在I-pipe中,没有对kernel_entry和kernel_exit做修改。如果已经对二者比较熟悉,可以跳过本节剩余章节。
2.5.2.2 kernel entry 1
去掉32位的干扰,只保留入参el为1的情况,核心代码如下:
1 | .macro | kernel_entry, el, regsize = 64 |
2 | stp | x0, x1, [sp, #16 * 0] |
3 | stp | x2, x3, [sp, #16 * 1] |
4 | stp | x4, x5, [sp, #16 * 2] |
5 | stp | x6, x7, [sp, #16 * 3] |
6 | stp | x8, x9, [sp, #16 * 4] |
7 | stp | x10, x11, [sp, #16 * 5] |
8 | stp | x12, x13, [sp, #16 * 6] |
9 | stp | x14, x15, [sp, #16 * 7] |
10 | stp | x16, x17, [sp, #16 * 8] |
11 | stp | x18, x19, [sp, #16 * 9] |
12 | stp | x20, x21, [sp, #16 * 10] |
13 | stp | x22, x23, [sp, #16 * 11] |
14 | stp | x24, x25, [sp, #16 * 12] |
15 | stp | x26, x27, [sp, #16 * 13] |
16 | stp | x28, x29, [sp, #16 * 14] |
17 | ||
18 | add | x21, sp, #S_FRAME_SIZE |
19 | get_thread_info tsk | |
20 | /* Save the task's original addr_limit and set USER_DS */ | |
21 | ldr | x20, [tsk, #TSK_TI_ADDR_LIMIT] |
22 | str | x20, [sp, #S_ORIG_ADDR_LIMIT] |
23 | mov | x20, #USER_DS |
24 | str | x20, [tsk, #TSK_TI_ADDR_LIMIT] |
25 | /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */ | |
26 | ||
27 | mrs | x22, elr_el1 |
28 | mrs | x23, spsr_el1 |
29 | stp | lr, x21, [sp, #S_LR] |
30 | ||
31 | stp | x29, x22, [sp, #S_STACKFRAME] |
32 | add | x29, sp, #S_STACKFRAME |
33 | ||
34 | stp | x22, x23, [sp, #S_PC] |
35 | ||
36 | .endm |
第1行,宏kernel_entry有两个参数el和regsize,其中regsize默认为64.
第2~16行,将X0~X29共30个通用寄存器压栈。栈空间是在kernel_ventry中按照struct pt_regs的大小预留好的,所以相当于压入struct pt_regs的reg[0]~reg[29]。
第18行,在kernel_ventry中,SP_EL1减去了#S_FRAME_SIZE。此处将SP_EL1加上#S_FRAME_SIZE,得到了中断发生之前的SP_EL1的值。
第19行,将当前进程的task_struct结构指针存入tsk。其中tsk就是X28,定义在entry.S:
tsk .req x28 // current thread_info
get_thread_info定义在arch/arm64/include/asm/assembler.h,从SP_EL0中直接得到当前进程的task_struct结构指针。在EL1执行态中,SP_EL0总是存着当前进程的task_struct指针,可以参考kernel_entry 0的分析。
/* * Return the current thread_info. */ .macro get_thread_info, rd mrs \rd, sp_el0 .endm |
第20~25行,修改tast_struct.thread_info.addr_limit的值。这个修改来自commit e19a6ee246 arm64: kernel: Save and restore UAO and addr_limit on exception entry。重置addr_limit和PSTATE.UAO,防止被异常处理继承原来进程上下文的addr_limit和PSTATE.UAO。
第
第27行,将elr_el1存入X22。当异常发生时,ARM64自动把返回地址保存在elr_e1寄存器即异常链接寄存器。当调用eret时,自动将elr放到pc寄存器。
第28行,将spsr_el1存入X23。当异常发生时,ARM64自动把PSTATE寄存器的值保存到SPSR_EL1中。
第29行,其中lr就是X30寄存器,X21是在第18行得到的原SP_EL1的值。S_PC定义在arch/arm64/kernel/asm-offsets.c:
DEFINE(S_LR, offsetof(struct pt_regs, regs[30]));
将X30压入struct pt_regs的regs[30],将SP_EL0压入struct pt_regs的变量sp。
第32行,在pt_regs中定义的u64 stackframe[2]中,把FP(X29)存入stackframe[0],把ELR存入stackframe[1]。
第33行,X29为Frame Pointer寄存器。让X29指向SP+ S_STACKFRAME的位置,即图中的FP’’的位置。栈帧寄存器,应该指向上一个caller的栈帧寄存器在callee中保持的位置。
第34行,将X22和X23压栈,就是将elr_el1和spsr_el1的值压入struct pt_regs的pc变量和pstate变量。
2.5.2.3 kernel_exit 1
1 | .macro | kernel_exit, el | |
2 | .if | \el != 0 | |
3 | disable_daif | ||
4 | |||
5 | /* Restore the task's original addr_limit. */ | ||
6 | ldr | x20, [sp, #S_ORIG_ADDR_LIMIT] | |
7 | str | x20, [tsk, #TSK_TI_ADDR_LIMIT] | |
8 | |||
9 | /* No need to restore UAO, it will be restored from SPSR_EL1 */ | ||
10 | .endif | ||
11 | |||
12 | ldp | x21, x22, [sp, #S_PC] | |
13 | |||
14 | msr | elr_el1, x21 | |
15 | msr | spsr_el1, x22 | |
16 | ldp | x0, x1, [sp, #16 * 0] | |
17 | ldp | x2, x3, [sp, #16 * 1] | |
18 | ldp | x4, x5, [sp, #16 * 2] | |
19 | ldp | x6, x7, [sp, #16 * 3] | |
20 | ldp | x8, x9, [sp, #16 * 4] | |
21 | ldp | x10, x11, [sp, #16 * 5] | |
22 | ldp | x12, x13, [sp, #16 * 6] | |
23 | ldp | x14, x15, [sp, #16 * 7] | |
24 | ldp | x16, x17, [sp, #16 * 8] | |
25 | ldp | x18, x19, [sp, #16 * 9] | |
26 | ldp | x20, x21, [sp, #16 * 10] | |
27 | ldp | x22, x23, [sp, #16 * 11] | |
28 | ldp | x24, x25, [sp, #16 * 12] | |
29 | ldp | x26, x27, [sp, #16 * 13] | |
30 | ldp | x28, x29, [sp, #16 * 14] | |
31 | ldr | lr, [sp, #S_LR] | |
32 | add | sp, sp, #S_FRAME_SIZE | |
33 | |||
34 | eret | ||
35 | .endm |
第2~10行,是对kernel_entry 1的第20~25行的反向操作,还原tast_struct.thread_info.addr_limit的值。
第12~15行,从内核栈中的pt_regs.pc恢复ELR到X21。从 pt_regs.pstate恢复SPSR到X22寄存器。然后分别赋值到elr_el1和spsr_el1。
第16~30行,从内核栈中的pt_regs.reg[0]~reg[29]恢复到X0~X29通用寄存器。
第31行,从内核栈中的pt_regs.reg[30]恢复到lr即X30通用寄存器。
第32行,内核栈指针sp_el1弹出,向上走#S_FRAME_SIZE,与kernel_entry压栈的大小一致。
第34行,通过eret从EL1异常级别返回到EL1,此时sp依然会使用sp_el1, ARM64会自动把elr_el1寄存器的值赋值到PC寄存器,并从PC指针继续执行。
2.5.3 irq_handler返回值
在el0_irq分析过程中,已经分析过irq_handler的返回值:
场景 | __ipipe_root_p | IPIPE_STALL_FLAG | irq_handler返回值 |
位于root域, | 1 | 0 | 1 |
位于root域, | 1 | 1 | 0 |
位于head域 | 0 | x | 0 |
如果irq_handler的返回值为1,那么保持原来的逻辑。
如果irq_handler的返回值为0,那么向前跳转到标签2,直接调用kernel_exit 1。从表格中分析,两种情况都是I-pipe引入后出现的特殊情况。
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!