第三十三期-ARM Linux内核的中断(3)

作者:罗宇哲,中国科学院软件研究所智能软件研究中心

上一期我们从处理器的视角介绍了ARM Linux内核的中断流程,这一期我们从内核的视角来考察ARM Linux中的中断流程。

一、ARM Linux内核的中断流程:内核的视角

在第二十九期的异常向量表中我们可以看到,当发生IRQ中断时,内核会通过异常向量表中的IRQ相关项进入中断处理流程,相关代码在openEuler源码库中的/kernel/blob/kernel-4.19/arch/arm64/kernel/entry.S文件里可以找到:
第三十三期-ARM Linux内核的中断(3)_第1张图片
我们以64位用户程序在异常级别EL0运行时的中断处理流程为例,程序通过异常向量表跳转到entry.S文件的el0_irq处执行汇编代码:
第三十三期-ARM Linux内核的中断(3)_第2张图片
这段代码使用kernel_entry宏将进程的寄存器值保存到内核栈中,然后调用了irq_handler宏,最后跳转到ret_to_user处使用内核栈保存的寄存器值恢复进程的寄存器并返回用户模式。在entry.S文件中我们可以找到irq_handler宏的汇编代码:
第三十三期-ARM Linux内核的中断(3)_第3张图片
这段代码通过irq_stack_entry将进程从内核栈切换到中断栈,并调用函数指针handle_arch_irq所指向的函数,然后通过irq_stack_exit从中断栈切换到内核栈。

handle_arch_irq会在GIC初始化时被设置为gic_handle_irq,设置handle_arch_irq的函数调用过程为gic_of_init()->gic_init_bases()->set_handle_irq()。这部分调用过程可以在在openEuler源码仓库的/kernel/blob/kernel-4.19/drivers/irqchip/irq-gic-v3.c文件中找到,该过程和设备驱动相关,中断控制器驱动的相关知识会在设备驱动一章详细介绍。GICv3版本的gic_handle_irq可以在irq-gic-v3.c文件中找到:

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)

{

u32 irqnr;

irqnr = gic_read_iar();//读取处理器接口中的中断确认寄存器得到硬件中断号

if (gic_supports_nmi() &&

unlikely(gic_read_rpr() == GICD_INT_NMI_PRI)) {

gic_handle_nmi(irqnr, regs);

return;

}

if (gic_prio_masking_enabled()) {

gic_pmr_mask_irqs();

gic_arch_enable_irqs();

}

if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {

int err;

if (static_branch_likely(&supports_deactivate_key))

gic_write_eoir(irqnr);

else

isb();

err = handle_domain_irq(gic_data.domain, irqnr, regs);

if (err) {

WARN_ONCE(true, "Unexpected interrupt received!\n");

gic_deactivate_unhandled(irqnr);

}

return;

}

if (irqnr < 16) {

gic_write_eoir(irqnr);

if (static_branch_likely(&supports_deactivate_key))

gic_write_dir(irqnr);

#ifdef CONFIG_SMP

/*

* Unlike GICv2, we don't need an smp_rmb() here.

* The control dependency from gic_read_iar to

* the ISB in gic_write_eoir is enough to ensure

* that any shared data read by handle_IPI will

* be read after the ACK.

*/

handle_IPI(irqnr, regs);

#else

WARN_ONCE(true, "Unexpected SGI received!\n");

#endif

}

}

该函数首先读取处理器接口中的中断确认寄存器得到硬件中断号。当硬件中断号大于15且小于1020或硬件中断号大于或等于8192时,中断来自外设,则调用函数handle_domain_irq()。

handle_domain_irq()函数实际上是调用了__handle_domain_irq()函数,该函数的实现可以在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/kernel/irq/irqdesc.c文件中可以找到:


/**

* __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain

* @domain: The domain where to perform the lookup

* @hwirq: The HW irq number to convert to a logical one

* @lookup: Whether to perform the domain lookup or not

* @regs: Register file coming from the low-level handling code

*

* Returns: 0 on success, or -EINVAL if conversion has failed

*/

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,

bool lookup, struct pt_regs *regs)

{

struct pt_regs *old_regs = set_irq_regs(regs);

unsigned int irq = hwirq;

int ret = 0;

irq_enter(); //进入中断上下文

#ifdef CONFIG_IRQ_DOMAIN

if (lookup)

irq = irq_find_mapping(domain, hwirq); //根据硬件中断号查找Linux中断号

#endif

/*

* Some hardware gives randomly wrong interrupts. Rather

* than crashing, do something sensible.

*/

if (unlikely(!irq || irq >= nr_irqs)) {

ack_bad_irq(irq);

ret = -EINVAL;

} else {

generic_handle_irq(irq);

}

irq_exit(); //退出中断上下文

set_irq_regs(old_regs);

return ret;

}

该函数首先调用函数irq_enter()进入中断上下文,接着调用函数irq_find_mapping()根据硬件中断号查找Linux中断号[1],然后调用generic_handle_irq()函数处理中断,最后调用irq_exit()函数退出中断上下文。generic_handle_irq()函数在irqdesc.c文件中也可以找到,它通过Linux中断号找到了对应的中断描述符,并通过generic_handle_irq_desc()函数调用中断描述符对应的handle_irq()函数。generic_handle_irq_desc()函数可以在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/include/linux/irqdesc.h文件中可以找到:
第三十三期-ARM Linux内核的中断(3)_第4张图片

二、结语

本期我们介绍了ARM Linux内核中外设中断的部分处理流程,下一期我们将继续介绍该流程与中断描述符有关的部分。

参考文献
[1] 《Linux内核深度解析》,余华兵著,2019

你可能感兴趣的:(第三十三期-ARM Linux内核的中断(3))