中断描述符 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)的中断处理函数。
参考文献
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华