GICV2
1、gicv2 的中断模型
在 SOC 中,中断产生后,怎么讲信息发送给 CPU 的呢,如下图所示,画了一个简要说明:
ARM CORE 只有 4 根线用于接受中断,nIRQ、nFIQ、nvIRQ、nvFIQ. 未 enable hypevisor 时,我们只看 nIRQ、nFIQ 就可以了;
SOC 中的所有中断都接到 gic 上,然后 gic 再输出 nIRQ、nFIQ、nvIRQ、nvFIQ 四根信号给 ARM core;
ARM CORE 在收到中断信号后,会通过 AXI 总线去读写 GIC 的寄存器(软件上是通过 memory-map 的方式去读写),继而获取是哪个中断号产生的中断。
当一个 device 需要使用中断,该 device 会输出一个中断信号线,该中断线会接到 gic 上,gic 中的 cpu interface 组件中的 GICC_IAR 寄存器就会跟着发生变化, 例如:
指纹模组产生了一个中断,该中断线接到了 gic 上,gic 收到该中断后,相应的 GICC_IAR 寄存器就会发送变化。这些都是由 ASIC 来设计的,例如我们由一个指纹中断,中断号是 59,所谓的该中断号是 59,其实就是该引脚产生中断信号后,相应的 GICC_IAR 寄存器会被写入 59 数值。
2、gicv2 寄存器
(1)、Distributor register
(2)、CPU interface register
3、中断处理的过程
在中断产生后,ARM Core 接收到 IRQ 或 FIQ 信号会跳转到 ARM 的 IRQ/FIQ 异步异常向量表.
该向量表是在 linux kernel 开机初始化时设置,在该向量表的处理中,会跳转到 gicv2 的 hander 函数:gic_handle_irq()
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
if (static_key_true(&supports_deactivate))
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
if (static_key_true(&supports_deactivate))
writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
/*
* Ensure any shared data written by the CPU sending
* the IPI is read after we've read the ACK register
* on the GIC.
*
* Pairs with the write barrier in gic_raise_softirq
*/
smp_rmb();
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
我们剖析一下这段代码:
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
// GIC_CPU_INTACK 是 0X0C,对应 gicv2 文档中的 GICC_IAR,也就是读取gic 中的物理中断号
if (likely(irqnr > 15 && irqnr < 1021)) // 如果是 PPI/SPI 中断,则执行 handle_domain_irq()
if (irqnr < 16) // 如果是 SGI 中断,则会执行后面的 handle_IPI()
GICV3
1、gicv3 的中断模型
在 gicv3 中,发生了很大的变化,原 gicv2 组件中的 cpu interface 移到了 ARM Core 中,
ARM Core 通过系统寄存器的方式访问 cpu interface 寄存器(msr/mrs 指令),不再是 memory-map 方式了。
当然了,访问 destributor/redestributor 还是通过 memory-map 方式.
gic 同 ARM Core 的接口,也变成了 AXI stream.
当有中断进来时,gic 组件会通过 AXI Stream 传输信息给 cpu interface, cpu interface 仲裁后,再将 FIQ/IRQ 信号发送给真正的 ARM CORE.
2、gicv3 的寄存器
gicv3 有组件中:detributor、redetributor、its 再 gicv3 中, cpu interface 挪到了 ARM Core 中
gicv3 的寄存器有两种访问方式:一类是通过 memory-map 方式访问、一类是通过系统寄存器方式访问;
- GICD 开头的寄存器是 detributor 寄存器,只能通过 memory-map 方式
- GICR 开头的寄存器是 redetributor 寄存器,只能通过 memory-map 方式
- GITS 开头的寄存器是 its 寄存器,只能通过 memory-map 方式
- GICC 开头的寄存器是 cpu interface 寄存器,可以通过 memory-map 方式
- ICC 开头的寄存器是 cpu interface 寄存器,可以通过系统寄存器方式访问 —— 对于 cpu
interface 寄存器,我们一般采取这种访问
3、中断处理的过程
在中断产生后,ARM Core 接收到 IRQ 或 FIQ 信号会跳转到 ARM 的 IRQ/FIQ 异步异常向量表.
该向量表是在 linux kernel 开机初始化时设置,在该向量表的处理中,会跳转到 gicv2 的 hander 函数:gic_handle_irq()
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
do {
irqnr = gic_read_iar();
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
int err;
if (static_key_true(&supports_deactivate))
gic_write_eoir(irqnr);
err = handle_domain_irq(gic_data.domain, irqnr, regs);
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_key_true(&supports_deactivate)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) {
gic_write_eoir(irqnr);
if (static_key_true(&supports_deactivate))
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
continue;
}
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}
我们剖析一下这段代码:
irqnr = gic_read_iar() 使用 mrs 指令读取 cpu interface 寄存器中的 ICC_IAR_EL1 寄存器,该寄存器记录着物理中断号;
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) 如果是 PPI、SPI、LPI 中断 if (irqnr < 16)如果是 SGI 中断,则会执行后面的 handle_IPI()