作者:罗宇哲,中国科学院软件研究所智能软件研究中心
上一期中我们介绍了ARM Linux内核中外设中断处理的部分流程,这一期我们将继续介绍ARM Linux内核的外设中断处理流程中与中断描述符相关的部分。
一、ARM Linux内核的中断描述符
上一期我们提到ARM Linux内核处理外设中断时会通过调用Linux中断号对应的中断描述符里的handle_irq()函数来处理相关中断。中断描述符在ARM Linux中由结构体irq_desc来表示,其代码在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/include/linux/irqdesc.h文件中可以找到:
struct irq_desc {
……
irq_flow_handler_t handle_irq;
……
struct irqaction *action; /* IRQ action list */
……
} ____cacheline_internodealigned_in_smp;
该结构体中定义了两层中断处理函数[1]:
- 第一层中断处理函数是handle_irq,irq_flow_handler_t是一个函数指针;
- 第二层中断处理函数保存在结构体irqaction组成的链表里,每一个链表元素都保存了一个设备驱动注册的中断处理函数。因为多个硬件设备可能共享同一个硬件中断号,所以irqaction组成的链表里可能有多个元素。
irq_flow_handler_t的定义代码可以在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/include/linux/irqhandler.h文件中可以找到:
该函数指针指向的函数输入为指向结构体irq_desc的指针类型,输出为void类型。我们在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/include/linux/irq.h文件中可以找到多个符合irq_flow_handler_t类型的替换函数:
当硬件中断号小于16时中断为软件产生的中断(SGI),中断处理程序调用handle_IPI()函数处理;当硬件中断号大于或等于16而小于32时中断为私有外设中断(PPI),handle_irq()函数被设置为handle_percpu_devid_irq();当硬件中断号大于等于32但小于gic_data.irq_nr(该值在irq-gic-v3.c文件中的gic_init_bases()函数中初始化,其最大值为1020,表示GICv3最多支持1020个中断源(SGI+PPI+SPI))时中断为共享外设中断,handle_irq()被设置为handle_fasteoi_irq();当硬件中断号大于等于8192但小于GIC_ID_NR时中断为局部特定外设中断(LPI),handle_irq()被设置为handle_fasteoi_irq()。有关中断类型的知识可以在第二十八期找到。设置handle_irq()的代码可以在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/drivers/irqchip/irq-gic-v3.c文件中可以找到:
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
struct irq_chip *chip = &gic_chip;
if (static_branch_likely(&supports_deactivate_key))
chip = &gic_eoimode1_chip;
/* SGIs are private to the core kernel */
if (hw < 16)
return -EPERM;
/* Nothing here */
if (hw >= gic_data.irq_nr && hw < 8192)
return -EPERM;
/* Off limits */
if (hw >= GIC_ID_NR)
return -EPERM;
/* PPIs */
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
}
/* SPIs */
if (hw >= 32 && hw < gic_data.irq_nr) {
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
}
/* LPIs */
if (hw >= 8192 && hw < GIC_ID_NR) {
if (!gic_dist_supports_lpis())
return -EPERM;
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
}
return 0;
}
handle_percpu_devid_irq()和handle_fasteoi_irq()两个函数最终都会调用中断描述符中irqaction结构体里面的handler()函数(action->handler())从而调用中断处理函数。其中handle_fasteoi_irq()->handle_irq_event()->__handle_irq_event_percpu()还会遍历中断处理描述符action的链表,调用所有中断处理描述符里的中断处理函数handler(),__handle_irq_event_percpu()的代码可以在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/kernel/irq/handle.c文件中可以找到:
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
……
struct irqaction *action;
……
for_each_action_of_desc(desc, action) {
……
res = action->handler(irq, action->dev_id);
……
}
return retval;
}
irqaction结构体的代码在openEuler源码仓库的/openeuler/kernel/blob/kernel-4.19/include/linux/interrupt.h文件中可以找到:
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @name: name of the device
* @dev_id: cookie to identify the device
* @percpu_dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @flags: flags (see IRQF_* above)
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @secondary: pointer to secondary irqaction (force threading)
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
该结构体通过next指针构成了链表,从而可以保存多个设备驱动注册的处理函数并使多个设备共享同一个硬件中断号。中断中驱动有关的知识我们将在设备驱动一章详细介绍。
二、结语
本期我们介绍了中断描述符及与其相关的重要函数,下一期我们将介绍软件产生的中断(SGI)的处理流程。
参考文献
[1] 《Linux内核深度解析》,余华兵著,2019