2.5 el1_irq

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博客

原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值