2.3.5 irq_handler

Interrupt Pipeline系列文章大纲-CSDN博客

        2.3 el0_irq

                2.3.1 el0_irq代码框架
                2.3.2 kernel_entry 0 与kernel_exit 0
                2.3.3 el0_irq_naked与enable_da_f
                2.3.4 trace_hardirqs_off与trace_hardirqs_on
                2.3.5 irq_handler
                2.3.6 返回用户空间

2.3.5 irq_handler

2.3.5.1 概要

        在原生irq_handler中,通过ldr_l加载函数handle_arch_irq的地址到通用寄存器X1。其中,ldr_l并不是ARM64指令,而是内核引入宏定义,定义在arch/arm64/include/asm/assembler.h,通过adrp和ldr指令的组合,加载当前PC值一定范围[-4GB~4GB]内的符号地址到寄存器。

        如果打开了CONFIG_IPIPE配置项,那么通过ldr伪指令,加载函数handle_arch_irq_pipelined到通用寄存器X1中。

        通过mov x0, sp指令,把当前的栈指针存入通用寄存器X0,作为函数的入参。之前已经调用kernel_entry 0,将进程运行的用户态现场按照struct pt_regs的格式存入进程内核栈。所以函数的入参就是struct pt_regs *regs。

        调用irq_stack_entry,切换到内核中断栈;通过blr x1执行handle_arch_irq_pipelined函数,函数返回后,返回值存在W0;然后调用irq_stack_exit退出内核中断栈。

2.3.5.2 irq_stack_entry/irq_stack_exit

        ARM64的Linux内核在2015年之前中断没有自己独立的栈,都是借用线程的内核栈。独立的中断栈是在commit 8e23dacd12 arm64: Add do_softirq_own_stack() and enable irq_stacks中引入的。

        如果打开了CONFIG_IPIPE配置项,实时内核co-kernel(Xenomai)允许在中断栈上进行上下文切换,并且允许更多的中断发生在sibling栈上下文中。那么原先判断当前是否在中断栈的汇编代码将不可用,所以I-pipe实现了一套新的方法,如果per cpu变量irq_nesting为0,则说明此时不在中断栈中,否则已经在中断栈中。

  • irq_stack_entry

1

.macro

irq_stack_entry

2

mov

x19, sp

// preserve the original sp

3

4

#ifdef CONFIG_IPIPE

5

/*

6

 * When the pipeline is enabled, context switches over the irq

7

 * stack are allowed (for the co-kernel), and more interrupts

8

 * can be taken over sibling stack contexts. So we need a not so

9

 * subtle way of figuring out whether the irq stack was actually

10

 * exited, which cannot depend on the current task pointer.

11

 */

12

adr_this_cpu x25, irq_nesting, x26

13

ldr

w26, [x25]

14

cmp

w26, #0

15

add

w26, w26, #1

16

str

w26, [x25]

17

b.ne

9998f

18

#else

19

/*

20

 * Compare sp with the base of the task stack.

21

 * If the top ~(THREAD_SIZE - 1) bits match, we are on a task stack,

22

 * and should switch to the irq stack.

23

 */

24

ldr

x25, [tsk, TSK_STACK]

25

eor

x25, x25, x19

26

and

x25, x25, #~(THREAD_SIZE - 1)

27

cbnz

x25, 9998f

28

#endif

29

30

ldr_this_cpu x25, irq_stack_ptr, x26

31

mov

x26, #IRQ_STACK_SIZE

32

add

x26, x25, x26

33

34

/* switch to the irq stack */

35

mov

sp, x26

36

9998:

37

.endm

第2行,保存当前栈指针SP_EL1到X19.

第12行,加载per cpu变量irq_nesting的地址到X25. 变量定义在arch/arm64/kernel/ipipe.c,用于对每个CPU核进出中断栈进行计数。

/* irq_nesting tracks the interrupt nesting level for a CPU. */

DEFINE_PER_CPU(int, irq_nesting);

第13行,把per cpu变量irq_nesting的值加载到W26,即X26的低32位。

第14行,用cmp指令比较W26和立即数0,比较结果影响CPSR中的条件标志位Z。如果二者相等,则条件标志位Z为1;否则为0.

第15~16行,对per cpu变量irq_nesting进行加1。使用了ADD指令,对CPSR中的条件标志位Z无影响。

第17行,如果第14行cmp指令比较结果为二者相等(条件标志位Z为1),则说明per cpu变量irq_nesting的值为0,即从未进入到中断栈,那么继续向下执行第30行。per cpu变量irq_nesting的值大于或等于1,即已经进入中断栈,不需要进行进程内核栈到中断栈的切换,直接向前(forward)跳转到9998f,退出宏。

第30行,加载per cpu变量irq_stack_ptr的地址到X25。irq_stack_ptr定义和初始化在arch/arm64/kernel/irq.c,它指向中断栈的栈顶(低地址),因此X25里面存放的是中断栈栈顶地址。如果定义了CONFIG_VMAP_STACK,那么中断栈是运行时动态申请的;否则,中断栈是编译时就定义的per cpu数组irq_stack。

DEFINE_PER_CPU(unsigned long *, irq_stack_ptr);

第31~32行,ARM64的中断栈的栈底地址到栈顶地址,是由高到低的。将X25存放的中断栈栈顶地址向上偏移#IRQ_STACK_SIZE,得到中断栈栈底地址,存入X26。

第35行,让SP_EL1栈指针指向中断栈栈底。接下来调用irq_handler函数,函数里面会自动把SP_EL1向下压栈。

  • irq_stack_exit

1

/*

2

 * x19 should be preserved between irq_stack_entry and

3

 * irq_stack_exit. I-pipe: have to preserve w0 too.

4

 */

5

.macro

irq_stack_exit

6

mov

sp, x19

7

#ifdef CONFIG_IPIPE

8

adr_this_cpu x1, irq_nesting, x2

9

ldr

w2, [x1]

10

add

w2, w2, #-1

11

str

w2, [x1]

12

#endif

13

.endm

第3行,此处注释提到了I-pipe必须避免在irq_stack_exit中修改W0。W0中存放着blr x1执行后的返回值,会影响后续代码的执行。

第6行,从通用寄存器X19中,恢复原来的内核进程栈到SP_EL0。

第8~11行,对per cpu变量irq_nesting的值进行减1操作,可以参考irq_stack_entry的解读。

2.3.5.3 handle_arch_irq_pipelined函数

       如果打开了CONFIG_IPIPE配置项,那么函数调用顺序irq_handler->handle_arch_irq变成了irq_handler->handle_arch_irq_pipelined->handle_arch_irq。

//arch/arm64/kernel/irq.c

#ifdef CONFIG_IPIPE

asmlinkage int handle_arch_irq_pipelined(struct pt_regs *regs)

{

    handle_arch_irq(regs); //仍然调用了原来该调用的函数

    return __ipipe_root_p && !irqs_disabled(); //注意返回值

}

#endif

        新增的handle_arch_irq_pipelined除了调用原先的handle_arch_irq之外,就是改变了irq_handler的返回值。根据返回值的不同,el0_irq后续的代码走向是不同的,后面章节会相信分析,这里重点分析handle_arch_irq。

        全局函数指针handle_arch_irq(),是中断处理程序C语言部分的入口,由函数set_handle_irq进行初始化。

void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))

{

       if (handle_arch_irq)

              return -EBUSY;

       handle_arch_irq = handle_irq;

       return 0;

}

以ARM的GIC V3中断控制器为例,irq-gic-v3.c中调用set_handle_irq(gic_handle_irq),设置handle_arch_irq指向gic_handle_irq

start_kernel <init/main.c>

->

init_IRQ <arch/arm64/kernel/irq.c>

->

irqchip_init <drivers/irqchip/irqchip.c>

->

of_irq_init(__irqchip_of_table) <drivers/of/irq.c>

->

gic_of_init <drivers/irqchip/irq-gic-v3.c>

->

gic_init_bases <drivers/irqchip/irq-gic-v3.c>

->

set_handle_irq(gic_handle_irq)

2.3.5.4 irq_handler返回值

        W0寄存器里面存放着irq_handler的返回值。根据返回值不同将走向不同的返回用户空间流程。

        __ipipe_root_p用于判断当前是在root domain还是head domain。如果el0_irq发生时,用户进程是非实时进程,或实时进程切换到了root domain运行,此时__ipipe_root_p为1;否则代表实时进程在head domain运行,此时__ipipe_root_p为0。

#define __ipipe_root_p (__ipipe_current_domain == ipipe_root_domain)

        函数irqs_disabled()是一个宏定义,定义在include/linux/irqflags.h。为简化分析,在关闭CONFIG_TRACE_IRQFLAGS_SUPPORT的情况下,展开分析。

#define irqs_disabled() raw_irqs_disabled()

#define raw_irqs_disabled()        (arch_irqs_disabled())

        I-pipe没有使用默认的函数arch_irqs_disabled(),直接定义了此函数,在arch/arm64/include/asm/ipipe_hwirq.h:

#define arch_irqs_disabled()       ipipe_test_root()

ipipe_test_root()在kernel/ipipe/core.c中定义:

1

unsigned long ipipe_test_root(void)

2

{

3

unsigned long flags;

4

int x;

5

6

flags = hard_smp_local_irq_save();

7

x = test_bit(IPIPE_STALL_FLAG, &__ipipe_root_status);

8

hard_smp_local_irq_restore(flags);

9

10

return x;

11

}

12

EXPORT_SYMBOL(ipipe_test_root);

       第7行,对__ipipe_root_status中的IPIPE_STALL_FLAG(bit 0)进行判断,如果是1则返回1,否则返回0。IPIPE_STALL_FLAG是root域的虚拟中断标识,如果是1,代表虚拟中断屏蔽;如果是0,代表虚拟中断打开。

       #define IPIPE_STALL_FLAG      0 /* interrupts (virtually) disabled. */

        ipipe_stall_root:__set_bit(IPIPE_STALL_FLAG, &__ipipe_root_status); //屏蔽

        ipipe_unstall_root:__clear_bit(IPIPE_STALL_FLAG, &p->status); //打开

        综合二者,可知返回值有如下组合:

场景

__ipipe_root_p

IPIPE_STALL_FLAG

irq_handler返回值

位于root域,
虚拟中断打开

1

0

1

位于root域,
虚拟中断屏蔽

1

1

0

位于head域

0

x

0

        接下来根据返回值进行分析返回用户空间的流程。

点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值