1. 中断控制器基础概念

各位同学好,我是你们的嵌入式Linux讲师。今天咱们开始聊中断控制器,这是设备树配置里最绕不开的一块。说实话,我早年刚接触驱动开发时,被中断搞得晕头转向——明明代码写对了,中断就是不来。后来才发现,是设备树里中断描述符配错了。

好,咱们从头捋一遍。

1.1 什么是中断

中断,说白了就是CPU停下当前工作,去处理一个紧急事件。

你想想看,CPU平时在流水线上跑指令,突然外设说「我这里有数据要处理」,或者「我出错了」。CPU就得暂停手头的活,跳到一个专门的地方去处理这个事件。处理完了,再回来接着干。

我习惯把中断比作你正在写代码,突然电话响了。你放下键盘接电话,处理完再继续写。这个「电话铃声」就是中断信号。

中断的好处很明显:

  • 提高效率:CPU不用轮询等待外设状态,省电又省时间
  • 实时响应:紧急事件能立刻得到处理
  • 多任务支持:不同外设可以共享CPU时间
我的经验:在嵌入式Linux里,中断处理函数要尽量短。我见过有人把耗时的打印操作放在中断里,结果系统卡死。记住,中断里只做最必要的事,耗时操作交给下半部(tasklet或workqueue)。

1.2 中断控制器的作用

一个SoC里可能有几十个外设,每个外设都可能产生中断。如果每个中断都直接连到CPU,那CPU的引脚根本不够用。这时候就需要中断控制器。

中断控制器就像个「总机接线员」。它的核心职责是:

  1. 收集中断:接收来自各个外设的中断请求
  2. 优先级管理:决定先处理哪个中断
  3. 分发中断:把中断信号送给CPU
  4. 中断屏蔽:可以临时关闭某些中断
  5. 中断状态记录:告诉CPU是哪个外设触发了中断

举个例子。我在调试一块i.MX6ULL板子时,GPIO按键和UART同时产生中断。如果没有中断控制器,CPU根本不知道先处理谁。有了GIC,它会根据优先级决定——通常UART优先级更高,因为串口数据可能丢失。

注意:中断控制器本身也是一个硬件模块,在设备树里需要描述它的寄存器地址、中断号范围等信息。配错了,整个中断系统都跑不起来。

1.3 GIC架构简介

GIC,全称Generic Interrupt Controller,是ARM体系里最常用的中断控制器。从Cortex-A系列到Cortex-R系列,基本都在用GIC。

GIC的版本有v1、v2、v3、v4。目前主流的是GICv2和GICv3。咱们重点讲GICv2,因为它在嵌入式Linux里最常见。

GICv2的架构分两大部分:

组件 全称 作用
Distributor 分发器 管理所有中断源,配置优先级、使能/屏蔽、触发方式
CPU Interface CPU接口 每个CPU核一个,负责把中断送给对应的CPU

GIC支持三种中断类型:

  • SPI(Shared Peripheral Interrupt):共享外设中断,可以路由到任意CPU核。比如UART、I2C这些外设的中断。
  • PPI(Private Peripheral Interrupt):私有外设中断,只发给特定的CPU核。比如每个核自己的定时器中断。
  • SGI(Software Generated Interrupt):软件触发的中断,一个CPU核可以给另一个核发中断。常用于核间通信。

中断号的分配是这样的:

ID0-ID15:  SGI(软件中断)
ID16-ID31: PPI(私有外设中断)
ID32-ID1019: SPI(共享外设中断)

嗯,这里要注意。设备树里描述中断时,interrupt-cells属性决定了用几个cell来描述一个中断。对于GICv2,通常是3个cell:

interrupt-cells = <3>;
第一个cell: 中断类型(0=SPI, 1=PPI)
第二个cell: 中断号(SPI从32开始编号,但设备树里通常从0开始)
第三个cell: 触发方式(0=电平触发, 1=边沿触发, 4=高电平有效, 8=低电平有效等)

举个例子。我在配置一个UART中断时,设备树里这样写:

uart1: serial@02020000 {
    compatible = "fsl,imx6ul-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&gic>;
};

这里GIC_SPI是宏定义,值为0,表示SPI中断。26是中断号(对应硬件上的ID 32+26=58)。IRQ_TYPE_LEVEL_HIGH表示高电平触发。

关键点:GIC的Distributor负责全局配置,CPU Interface负责把中断送给具体哪个核。在多核系统里,你可以通过interrupt-affinity属性指定中断发到哪个CPU核。

我曾经踩过一个坑。有次调试一个网卡驱动,中断一直不触发。查了半天,发现是设备树里interrupt-parent指向错了——我指向了另一个中断控制器,而不是GIC。这个错误在编译时不会报错,运行时也不崩溃,就是中断不工作。所以,interrupt-parent一定要确认指向正确的节点。

GICv3相比v2,主要改进是支持更多CPU核(超过8个),以及引入了Redistributor组件。不过对于咱们嵌入式Linux开发,GICv2已经够用了。我建议初学者先把GICv2吃透,再去看v3的扩展。

好了,这一节就到这里。中断控制器的概念是设备树配置的基础,后面咱们会深入每个属性的具体写法。记住一句话:中断配置错了,驱动写得再好也白搭。