GICv2中断控制器配置:从硬件到设备树的实战解析
大家好,我是你们的嵌入式Linux讲师。今天咱们来聊聊GICv2中断控制器在设备树中的配置。说实话,中断控制器这块儿,很多工程师都觉得头疼。我刚开始接触时也踩过不少坑,尤其是地址映射搞错,系统直接起不来。嗯,今天咱们就把这事儿彻底讲明白。
GICv2的硬件架构:先搞清楚它长什么样
GICv2,全称Generic Interrupt Controller version 2,是ARM架构里最经典的中断控制器。你想想看,几乎所有的Cortex-A系列处理器都在用。它的核心部件就两个:Distributor和CPU Interface。
Distributor负责中断的分配、优先级管理、使能控制。CPU Interface则负责把中断信号送给具体的CPU核。说白了,Distributor是调度中心,CPU Interface是最后一公里的快递员。
我在项目中遇到过一个问题:某款芯片的GICv2地址映射文档写错了,Distributor和CPU Interface的基地址差了0x1000。结果呢?中断能注册,但从来不触发。查了三天才发现是地址问题。所以啊,地址映射一定要对着芯片手册反复核对。
设备树中的GICv2节点结构
咱们直接看一个标准的GICv2设备树节点。这是我个人习惯的写法,清晰明了:
gic: interrupt-controller@f1000000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x0 0xf1000000 0x0 0x10000>,
<0x0 0xf1001000 0x0 0x10000>;
interrupts = <1 9 0xf04>;
};
我来拆解一下每个字段的含义:
| 属性 | 含义 | 我的经验 |
|---|---|---|
| compatible | 兼容性字符串,驱动匹配用 | 不同内核版本可能有差异,建议用"arm,cortex-a15-gic"最通用 |
| #interrupt-cells | 中断描述符的单元数,GICv2固定为3 | 这个值千万别改,改了内核解析会乱 |
| interrupt-controller | 标记这是一个中断控制器 | 空属性,但必须有 |
| reg | 地址映射,两段:Distributor和CPU Interface | 地址范围要跟芯片手册完全一致 |
| interrupts | GIC自身的中断,用于级联 | 主GIC通常不需要,从GIC才配 |
重点提醒:reg属性有两段地址,第一段是Distributor,第二段是CPU Interface。地址偏移量通常是0x1000,但不同芯片可能不同。我见过某厂商把CPU Interface放在0x2000偏移处,结果内核panic了。
Distributor和CPU Interface的地址映射详解
咱们深入看看这两个部件的地址空间。Distributor的基地址通常是0x1000对齐,CPU Interface紧跟着偏移0x1000。但这不是绝对的,我建议你养成查手册的习惯。
Distributor的寄存器空间主要包含:
- GICD_CTLR:控制器使能寄存器
- GICD_TYPER:类型寄存器,告诉你支持多少中断
- GICD_ISENABLERn:中断使能寄存器组
- GICD_ICPENDRn:中断挂起清除寄存器组
- GICD_IPRIORITYRn:中断优先级寄存器组
CPU Interface的寄存器空间则包括:
- GICC_CTLR:CPU接口控制寄存器
- GICC_PMR:优先级掩码寄存器
- GICC_IAR:中断应答寄存器
- GICC_EOIR:中断结束寄存器
我曾经调试过一个奇怪的问题:中断能触发,但ISR执行完后系统卡死。后来发现是GICC_EOIR没写对地址。CPU Interface的基地址我配错了,导致EOIR写到了Distributor的地址上。嗯,这种低级错误,犯过一次就再也不会犯了。
中断类型:SPI、PPI、SGI的配置
GICv2支持三种中断类型,咱们一个一个说。
SPI(Shared Peripheral Interrupt)
SPI是共享外设中断,中断号范围是32到1019。这是最常用的中断类型,所有CPU核都能收到。设备树中配置SPI的格式是:
interrupts = <0 42 4>;
第一个字段0表示SPI类型,第二个字段42是中断号(注意:设备树中的中断号要减去32),第三个字段是触发类型和优先级标志。
我的小技巧:SPI的中断号在设备树里写的是硬件中断号减去32。比如硬件中断号74,设备树里就写42。这个转换我刚开始也搞混过,后来干脆写了个脚本自动转换。
PPI(Private Peripheral Interrupt)
PPI是私有外设中断,中断号范围是16到31。每个CPU核独享,常用于本地定时器、性能监测等。配置格式:
interrupts = <1 13 0xf04>;
第一个字段1表示PPI类型,第二个字段13是中断号(对应硬件中断号29),第三个字段是触发类型。PPI的触发类型我建议用0xf04,这是内核推荐的默认值。
SGI(Software Generated Interrupt)
SGI是软件触发的中断,中断号范围是0到15。用于核间通信,比如IPI。SGI在设备树中通常不直接配置,而是由内核在运行时动态使用。但如果你要配置,格式是:
interrupts = <1 8 0xf04>;
第一个字段1表示PPI类型(SGI在设备树中被归类为PPI),第二个字段8是中断号(对应硬件中断号8),第三个字段是触发类型。
注意:SGI的触发类型必须是边缘触发,不能是电平触发。我曾经看到有人配成了电平触发,结果SGI发出去后CPU一直处于中断状态,根本停不下来。这个坑我替你们踩过了。
实战:配置一个完整的GICv2节点
好了,理论说完了,咱们来个完整的例子。假设你有一款四核Cortex-A15芯片,GICv2的Distributor基地址是0xf1000000,CPU Interface基地址是0xf1001000。设备树节点这样写:
gic: interrupt-controller@f1000000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x0 0xf1000000 0x0 0x10000>,
<0x0 0xf1001000 0x0 0x2000>;
interrupts = <1 9 0xf04>;
};
然后,一个UART外设使用SPI中断,硬件中断号是74:
uart0: serial@f1010000 {
compatible = "ns16550a";
reg = <0x0 0xf1010000 0x0 0x1000>;
interrupts = <0 42 4>;
interrupt-parent = <&gic>;
};
这里interrupt-parent指向gic节点,告诉内核这个外设的中断由GIC管理。中断号42对应硬件中断号74(42+32=74),触发类型4表示高电平触发。
我个人习惯在设备树中把interrupt-parent显式写出来,虽然有些情况下可以省略,但显式写更安全,也方便别人阅读你的代码。
避坑指南:我踩过的那些坑
最后,分享几个我实际项目中遇到的坑:
- 地址范围别写小了:Distributor的地址范围至少0x1000,CPU Interface至少0x1000。写小了内核访问寄存器时会触发异常。
- #interrupt-cells必须是3:GICv2固定是3,别自作聪明改成2或4。内核的GIC驱动写死了这个值。
- 中断号别重复:SPI的中断号范围是32-1019,但实际芯片可能只支持到几百。重复的中断号会导致驱动混乱。
- 触发类型要匹配硬件:电平触发还是边缘触发,一定要看外设的数据手册。配错了中断要么不触发,要么一直触发。
我曾经在一个项目中,把某个GPIO的中断触发类型从边缘触发改成了电平触发,结果按键按一下,中断触发了十几次。嗯,从那以后我每次配中断都会先看硬件手册。
好了,GICv2的设备树配置就讲到这里。下一章咱们聊聊GICv3,那个更复杂,但原理是相通的。有什么问题欢迎在评论区交流,我看到会回复。