/* 【】 gic_handle_irq 函数
** tip1:
** 经证明,中断触发时,调用的 handle_arch_irq 入口地址。
** 因为此时,挂接的就是 gic_handle_irq 函数!gic_handle_irq 是个全局函数指针,
** static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
** 它是Linux内核中处理GIC中断的核心函数,响应来自GIC的中断请求,完成中断状态的读取、
** 确认(ACK)、处理(ISR调用)和结束(EOI)的完整生命周期。函数通过循环处理可能存在
** 的多个挂起中断。
** 形参 regs 是 long 类型的数组指针,用于保存CPU切换上下文时刻的CPU寄存器现场数据。
**
** tip2:
** ARM的GIC-V2架构手册内,对中断ID号段的类型划分如下:
** 0-15 软中断(SGI),核间私有;Software-generated interrupt (SGI) 。
** 16-31 外设私有中断(PPI),核间私有;Private Peripheral Interrupt (PPI) 。
** 32-1019 外设共享中断(SPI),核间共享;Shared Peripheral Interrupt (SPI) 。
** 1020-1023 当前仍被保留的中断编号。
**
** tip3:
** SGI是GIC(通用中断控制器)提供的一种硬件中断类型,物理上由 GIC 直接支持,通过写
** GIC 的寄存器(如 `GICD_SGIR`)触发。属于ARM的GIC硬件模块里的中断编号域(0~15),
** SoftIRQ(软中断)是Linux内核实现的延迟处理机制,完全由软件实现,没有直接的硬件支持。
** 所以,从中断控制器的角度上看ISR:
** 中断上半部就是真正的ISR,而中断下半部不是ISR,只是被OS内核优先安排的机制。
** 但是,从linux的角度看ISR:
** 中断上下半部都是ISR的完整体不可或缺的一部分!只是出于效率,才将下半部安排到
** 内核线程中执行的!这俩货少了谁,这ISR都是会出严重错误的!!!
*/
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
/* gic_data 是 struct gic_chip_data 类型的全局数组 */
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
/*
** 多中断处理:当多个中断同时到达时,GIC会通过优先级排序依次处理,直到无挂起中断。
** 抢占限制:Linux默认不启用中断抢占,当前中断处理完成后才会响应新中断。
** RTOS几乎都是中断抢占的!
*/
do {
/* 读取CPU接口的GICC_IAR(Interrupt Acknowledge Register),
该操作会返回当前最高优先级的中断ID,并将该中断状态从Pending转为Active。 */
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
/* 获得硬中断ID:GICC_IAR_INT_ID_MASK用于过滤保留位,仅保留有效中断号(0-1019) */
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
/* 外设中断处理(PPI和SPI:16-1019) */
if (likely(irqnr > 15 && irqnr < 1020)) {
if (static_branch_likely(&supports_deactivate_key))
/* EOI操作:向GICC_EOIR寄存器写入中断ID,
通知GIC中断处理完成,状态从Active转为Inactive */
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
isb();
/* 路由对应的ISR:handle_domain_irq通过中断描述符表
(gic->domain)找到对应的中断处理函数(ISR)。 */
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) { /* SGI的中断处理(SGI:0-15)专门处理CPUs的核间通信 */
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
if (static_branch_likely(&supports_deactivate_key))
writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP /* 若支持多核CPU的协同处理 */
smp_rmb();
handle_IPI(irqnr, regs); //核间通信
#endif
continue;
}
break;
} while (1);
}
两条路线的对比
这两条路线,最终都执行到了__handle_domain_irq 函数这里来,但是他们传入的参数不同。
实际采纳的路线 gic_handle_irq:
默认路线:
所以得出结论,不支持domain路由机制的旧版本的 linux generic interrupt layer 正如2.6.25版本说的,这种legacy,只是为了向前兼容,会慢慢过度,直到被废弃。
GIC-V3 重要的新特性 Affinity routing
这个比较重要,硬件层面新增了许多实用的特性来支持linux这种操作系统里中断管理层的工作,互相配合,提升性能。 Affinity 有关于实时性的支持。 GIC-V2 不支持 Affinity ,是V3版本后引入的。
在嵌入式linux中,IRQ编号是对机器上不同中断源的枚举。通常情况下,枚举的是系统中所有中断控制器的输入引脚。IRQ是一个虚拟的中断ID,与硬件无关。 linux IRQ号始终与 irq_desc 数据结构绑定,该数据结构代表了 IRQ 号。
irq_domain里关联着一些成对的硬件中断号 hwirq 与 逻辑中断号 virq 的路由关系。
某个层级的domain里的这些成对绑定的irq仅仅针对本层级有意义。
hwirqx 是从本层级硬件控制器的相关中断寄存器内读取的数值;对应的 virqx 身后的 virqx_desc 则是对 hwirqx 信号的响应,这种相应要么流控到下一级别的控制器,要么流控到 ISR ,当然前提是这些响应需要下一级提前注册好。
irq 与 irq_desc 的绑定关系存在于哪里?
在 Linux 5.4.31 内核中,当启用 CONFIG_SPARSE_IRQ(稀疏 IRQ 模式)时,所有动态分配的 irq_desc
实例确实存储在基数树 irq_desc_tree 中:
-
存储位置:\linux-5.4.31\kernel\irq\irqdesc.c (CONFIG_SPARSE_IRQ=y)
全局声明: static RADIX_TREE(irq_desc_tree, GFP_KERNEL); 作用域:全局。
IRQ 号与
irq_desc
的绑定关系通过 基数树(Radix Tree)或 XArray 存储在内核内存中。 -
访问方式:
-
通过 __irq_alloc_descs 函数的派生形式,如 irq_alloc 函数,分配并初始化一组中断描述符;
-
通过
irq_to_desc(irq)
函数查找指定irq的描述符。 -
通过 delete_irq_desc 函数,删除指定irq的描述符;
-
-
设计优势:动态分配、高效查找、支持多级中断控制器的复杂映射。
-
基数树角色:作为全局数据库,动态管理稀疏的
irq_desc
,优化内存使用。 -
操作效率:通过
O(k)
复杂度实现快速查找和动态扩展。 -
演进方向:XArray 逐步替代基数树,提供更高效的内存管理。
irq 与 hwirq 的绑定关系存在于哪里?
-
存储位置:
-
hwirq → irq 的映射存储在
irq_domain.revmap_tree
(基数树)。 -
irq → hwirq 的映射存储在
irq_data.hwirq
。
-
-
级联支持:多级中断控制器通过父子
irq_domain
的协作传递绑定关系。 -
关键数据结构:
irq_domain
和irq_data
共同维护中断号的双向映射。
在linux中断子系统内,关于中断域irq_domain:
-
每个独立的中断域对应一个中断控制器;
-
每个独立的中断域负责对应控制器在中断系统内的路由;
-
中断的路由(级联流控)过程中,需要:
-
中断域提供中断号间映射服务,同时也需要,
-
中断描述符提供其本层级的个性化路由流控服务(handle_riq);
-
-
每个独立的中断域内存放有独立的映射过程需要的数据,具体存放在域内的成员:
-
线性映射(Linear):linear_revmap[] ;
-
树形映射(Tree):revmap_tree (基于 Radix Tree 基数树实现);
-
其他类型:如
simple
(固定映射)、hierarchy
(层级映射)等。 -
注意:这些元数据是仅维护本层级中断号间映射的数据集合,是独立的。
-
-
每个独立的中断域,共享统一的映射算法:
-
线性映射:使用数组
linear_revmap[]
,复杂度 O(1)。 -
树形映射:使用基数树
revmap_tree
,复杂度 O(log n)。 -
简单映射:直接预定义 HW IRQ → VIRQ 的偏移(如
virq = hwirq + offset
)。
-
-
级联中断的路由是跨
irq_domain
的协作过程,需父域和子域共同完成。 当父控制器检测到子控制器的中断时,父域与子域的协作:-
父控制器通过
irq_domain
解析出子控制器的virq
。 -
调用
generic_handle_irq(child_virq)
,触发子控制器的流控逻辑。 -
子控制器的
irq_desc->handle_irq
处理具体中断(如handle_edge_irq
)。
-