Bootstrap

中断处理流程

中断描述符 irq_desc

对于中断控制器的每个中断源,向中断域添加硬件中断号到Linux中断号的映射时,内核分配一个Linux中断号和一个中断描述符irq_desc。

irq_desc中的处理函数

如下图所示,中断描述符有两个层次的中断处理函数。

(1)第一层处理函数是中断描述符irq_desc的成员handle_irq()。

(2)第二层处理函数是设备驱动程序注册的处理函数。中断描述符有一个中断处理链表(irq_desc.action),每个中断处理描述符(irqaction)保存设备驱动程序注册的处理函数。因为多个设备可以共享同一个硬件中断号,所以中断处理链表可能挂载多个中断处理描述符,根据dev_id区分设备

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Linux中断号到irq_desc的映射

在中断域irq_domain中介绍过硬件中断号到Linux中断号的映射。那么Linux中断号到中断描述符是如何映射的呢?

  • 如果中断编号是稀疏的(即不连续),那么使用基数树(radix tree)存储。需要开启配置宏CONFIG_SPARSE_IRQ。
  • 如果中断编号是连续的,就使用数组存储。
#ifdef CONFIG_SPARSE_IRQ
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);

#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};

中断处理函数设置

irq_desc.handle_irq

把硬件中断号映射到Linux中断号的时候,根据硬件中断的类型设置中断描述符的成员handle_irq();以GICv2控制器为例,使用gic_irq_domain_map进行硬件中断号到Linux中断号的映射,并设置handle_irq,处理如下所示。

irq_create_mapping() -> irq_domain_associate() -> domain->ops->map() -> gic_irq_domain_map()

(1)如果硬件中断号小于32,说明是软件生成的中断或私有外设中断,那么把中断描述符的成员handle_irq()设置为函数handle_percpu_devid_irq。

(2)如果硬件中断号大于或等于32,说明是共享外设中断,那么把中断描述符的成员handle_irq()设置为函数handle_fasteoi_irq。

static const struct irq_domain_ops gic_irq_domain_ops = {
	.map = gic_irq_domain_map,
	.unmap = gic_irq_domain_unmap,
};

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
				irq_hw_number_t hw)
{
	struct gic_chip_data *gic = d->host_data;

	if (hw < 32) {
		irq_set_percpu_devid(irq);
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_percpu_devid_irq, NULL, NULL);
		irq_set_status_flags(irq, IRQ_NOAUTOEN);
	} else {
		irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
				    handle_fasteoi_irq, NULL, NULL);
		irq_set_probe(irq);
	}
	return 0;
}

irqaction.handler

设备驱动程序可以使用函数request_irq()注册中断处理函数:

#define IRQF_SHARED		0x00000080
#define IRQF_PROBE_SHARED	0x00000100
#define __IRQF_TIMER		0x00000200
#define IRQF_PERCPU		0x00000400
#define IRQF_NOBALANCING	0x00000800
#define IRQF_IRQPOLL		0x00001000
#define IRQF_ONESHOT		0x00002000
#define IRQF_NO_SUSPEND		0x00004000
#define IRQF_FORCE_RESUME	0x00008000
#define IRQF_NO_THREAD		0x00010000
#define IRQF_EARLY_RESUME	0x00020000
#define IRQF_COND_SUSPEND	0x00040000

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

(1)参数irq是Linux中断号。

(2)参数handler是处理函数。

(3)参数flags是标志位,可以是0或者以下标志位的组合。

- IRQF SHARED:允许多个设备共享同一个中断号。
- __IRQF_TIMER:定时器中断。
- IRQF_PERCPU:中断是每个处理器私有的。
- IRQF_NOBALANCING:不允许该中断在处理器之间负载均衡。
- IRQF_NO_THREAD:中断不能线程化。

(4)参数name是设备名称。

(5)参数dev是传给处理函数(由参数handler指定)的参数。

中断处理流程

在ARM64架构下,在异常级别1的异常向量表中,中断的入口有3个。

(1)如果处理器处在内核模式(异常级别1),中断的入口是el1_irq。

(2)如果处理器正在用户模式(异常级别0)下执行64位应用程序,中断的入口是el0_irq。

(3)如果处理器正在用户模式(异常级别0)下执行32位应用程序,中断的入口是el0_irq_compat。

ARM64异常级别1的异常向量表定义如下:

/*
 * Exception vectors.
 */
	.pushsection ".entry.text", "ax"

	.align	11
ENTRY(vectors)
	ventry	el1_sync_invalid		// Synchronous EL1t
	ventry	el1_irq_invalid			// IRQ EL1t
	ventry	el1_fiq_invalid			// FIQ EL1t
	ventry	el1_error_invalid		// Error EL1t

	ventry	el1_sync			// Synchronous EL1h
	ventry	el1_irq				// IRQ EL1h
	ventry	el1_fiq_invalid			// FIQ EL1h
	ventry	el1_error_invalid		// Error EL1h

	ventry	el0_sync			// Synchronous 64-bit EL0
	ventry	el0_irq				// IRQ 64-bit EL0
	ventry	el0_fiq_invalid			// FIQ 64-bit EL0
	ventry	el0_error_invalid		// Error 64-bit EL0

假设处理器正在用户模式(异常级别0)下执行64位应用程序,中断控制器是GIC v2控制器,Linux中断处理流程如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面的硬件中断处理(中断号15~1019),最终到desc->handle_irq(desc),即中断描述符(struct irq_desc)的中断处理函数;在desc->handle_irq(desc)中的处理,会继续调用中断处理描述符(struct irqcation)的中断处理函数。

参考文献

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
;