Linux irq_chip中断控制器一级抽象与ack_mask_eoi
irq_chip是Linux中断子系统中对底层中断控制器的软件抽象,定义在include/linux/irq.h。它将不同中断控制器(如APIC、GIC、NVIC等)的操作封装为一组函数指针,使得上层的irq处理逻辑无需关心硬件寄存器细节。每个irq_desc实例通过irq_data.chip指针绑定到一个irq_chip上。
irq_chip结构体包含了中断生命周期中所有关键操作:
```c
struct irq_chip {
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data,
const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);
...
};
```
ack操作在边沿触发中断处理中扮演关键角色。在handle_edge_irq中,desc->irq_data.chip->irq_ack被调用,用于清除中断控制器中的pending位,释放硬件锁存,从而允许后续同类型中断到达CPU。对于x86 APIC来说,ack的实现是向APIC写入EOI寄存器:
```c
static void apic_ack_edge(struct irq_data *d)
{
irq_complete_move(d);
ack_APIC_irq();
}
```
mask操作用于屏蔽中断源。中断处理过程中,电平触发模式要求在进入handler之前先mask中断,防止中断处理函数尚未返回时同一中断再次触发导致递归。mask_ack操作是mask和ack的组合,在handle_level_irq和handle_fasteoi_irq中高频使用。其典型实现如下:
```c
void mask_ack_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
chip->irq_mask(&desc->irq_data);
if (chip->irq_ack)
chip->irq_ack(&desc->irq_data);
}
```
某些中断控制器(如GICv2/v3)引入了EOI(End Of Interrupt)机制,替代了传统的ack/mask组合。eoi操作在中断处理函数执行完毕后调用,通知控制器中断处理已完成。对于支持EOI的控制器,chip->irq_eoi在handle_fasteoi_irq中统一点用:
```c
void handle_fasteoi_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
raw_spin_lock(&desc->lock);
if (!desc->action || irqd_irq_disabled(&desc->irq_data)) {
chip->irq_mask(&desc->irq_data);
chip->irq_eoi(&desc->irq_data);
raw_spin_unlock(&desc->lock);
return;
}
descistate = IRQS_ONESHOT | IRQS_ONESHOT;
desc->istate &= ~IRQS_WAITING;
raw_spin_unlock(&desc->lock);
chip->irq_eoi(&desc->irq_data);
handle_irq_event(desc);
}
```
enable/disable管理中断控制器的全局使能状态。enable与unmask的区别在于:unmask仅解除该中断在控制器层面的屏蔽,而enable还涉及电源管理状态恢复和时钟门控。现代内核推荐直接使用unmask/mask替代enable/disable,因为enable在irq_disable后恢复时涉及额外的状态重建。
set_type操作配置中断触发条件:
```c
static int apic_set_type(struct irq_data *data, unsigned int type)
{
struct IO_APIC_route_entry *entry;
unsigned int irq = data->irq;
entry = &ioapic_entries[irq_to_ioapic(irq)];
switch (type) {
case IRQ_TYPE_EDGE_RISING:
entry->trigger = 0;
entry->polarity = 0;
break;
case IRQ_TYPE_LEVEL_HIGH:
entry->trigger = 1;
entry->polarity = 1;
break;
...
}
ioapic_write_entry(irq_to_ioapic(irq), irq_to_pin(irq), *entry);
return 0;
}
```
irq_chip的bus_lock/bus_sync_unlock用于慢速中断总线(如I2C、SPI级联的中断控制器)。当对中断控制器寄存器进行操作时,先通过bus_lock获取总线访问权限,累积多个寄存器操作后,由bus_sync_unlock一次提交。这种设计减少了总线事务次数,在嵌入式SoC的GPIO中断控制器中非常普遍。
irq_chip的chip_data指针存储控制器私有数据。apic_chip指向io_apic_irq_chip结构体,x86调用链中detect_io_apic后将芯片操作函数填入该结构体,并在mp_register_handler中绑定到irq_desc。arm64的GIC驱动则定义gic_chip_data结构体包含dist_base、cpu_base等MMIO基地址,通过irq_set_chip_data存入chip_data。
irq_chip函数指针的调用路径上有严格的锁保护。desc->lock在操作chip前获取,防止SMP环境下多个CPU同时操作同一中断控制器的寄存器。对于支持MSI-X的消息中断,chip->irq_set_affinity涉及对PCI配置空间的写操作,需要配合pci_channel恢复机制处理写失败场景。
总结来看,irq_chip通过ack、mask、unmask、eoi、set_type等操作函数为上层提供硬件无关的中断控制器接口。不同架构只需实现各自的irq_chip回调,即可无缝接入Linux中断子系统框架。
Linux irq_chip中断控制器一级抽象与ack_mask_eoi