一、中断控制器
外围设备不是把中断请求 直接发给处理器,而是发给中断控制器,由中断控制器转发给处理器。ARM公司提供一种标准的中断控制器,称为通用中断控制器,如GIC v2。GIC架构规范有四个版本:V1-V4。GIC v2最多支持8个处理器,GIC v3最多支持128个处理器,GIC v3和GIC v4只支持ARM处理器。
厂商研发自己的ARM处理器,向ARM公司购买GIC的控制权,ARM公司提供的GIC型号有:GIC-400,GIC-500,GIC-600。其中GIC-400遵循GIC v2规范,GIC-500和GIC-600遵循GIC v3规范。厂商直接向ARM公司购买处理器的授权,这些处理器包含了GIC。
从软件角度看,GIC v2控制器有两个主要的功能:分发器(Distributor)和处理器接口(CPU Interface)。分发器:系统中所有的中断源连接分发器,分发器的寄存器用来控制单个中断的属性:优先级、状态、安全、转发信息(可以被发送到哪些处理器)和使能状态。分发器决定哪个中断应该通过处理器接口转发到哪个处理器;处理器接口:处理器通过处理器接口接收中断。处理器接口提供的寄存器用来屏蔽和识别中断,控制中断的状态。每个处理器有一个单独的处理器接口。
回顾中断3种类型:软件生成中断、私有外设中断、共享外设中断。软件生成中断(SGI,Software Generated Interrupt),通常用来实现处理器中断 IPI(Inter-Processor Interrupt),中断号 0-15。这种中断是由软件写分配器的软件生成中断寄存器(GICD_SGIR)生成的。私有外设中断(PPI,Private Peripheral Interrupt),中断号 16-31。处理器私有的中断源,不同处理器的相同中断源没有关系,比如每个处理器的定时器。共享外设中断(SPI,Shared Peripheral Interrupt),中断号 32-1019。这种中断可以被中断控制器转发到多个处理器。
中断可以是边沿触发,也可以是电平触发。边沿触发是在电压变化的一瞬间触发,电压由高到低变化处罚的中断称为下降沿触发,电压由低到高变化触发的中断称为上升沿触发。
在ARM架构中,中断管理通过**通用中断控制器(GIC)**实现,其核心作用是为外设中断提供集中管理和分发。以下是对GIC v2及其相关概念的详细解析:
1. 中断控制器的作用
外设不直接将中断发送给处理器,而是通过**中断控制器(GIC)**中转,主要原因包括:
集中管理:统一处理多外设的中断请求,避免处理器直接面对大量中断源。
优先级控制:对中断进行优先级排序,确保高优先级事件优先处理。
多核调度:在SMP系统中,动态分配中断到不同处理器核心,实现负载均衡。
2. GIC v2的核心模块
GIC v2从软件视角分为两个关键模块:
(1) 分发器(Distributor)
功能:
中断收集:接收所有外设的中断请求(包括SPI、PPI、SGI)。
优先级排序:根据配置的优先级对中断进行排序。
路由决策:决定将中断发送到哪个处理器(针对共享中断)。
中断使能/屏蔽:全局控制中断的使能与禁用。
触发类型配置:设置中断为电平触发或边沿触发。
配置对象:作用于所有处理器,属于全局资源。
(2) 处理器接口(CPU Interface)
功能:
中断传递:将分发器分配的中断传递给特定处理器核心。
中断确认(ACK):处理器读取中断ID后,通知GIC已接收中断。
中断结束(EOI):处理器处理完成后,通知GIC可释放中断。
优先级屏蔽:动态调整处理器可接受的中断优先级阈值。
配置对象:每个处理器核心有独立的接口,属于核心本地资源。
3. 中断的三种类型
在GIC v2中,中断分为以下三类(用户提到的第四种可能是描述误差或特定扩展):
(1) 软件生成中断(SGI, Software Generated Interrupt)
ID范围:0-15。
触发方式:由软件写入GIC寄存器触发(如核间通信)。
特点:用于多核间同步,例如核心A通过SGI唤醒核心B。
(2) 私有外设中断(PPI, Private Peripheral Interrupt)
ID范围:16-31。
触发源:每个处理器独有的外设(如本地定时器、性能监控单元)。
特点:仅对所属处理器有效,不可共享。
(3) 共享外设中断(SPI, Shared Peripheral Interrupt)
ID范围:32-1019。
触发源:全局外设(如GPU、网卡、USB控制器)。
特点:可路由到任意处理器,由分发器动态分配。
4. 中断处理流程示例
外触发中断:网卡(SPI)发送中断请求至GIC分发器。
分发器处理:检查中断使能状态、优先级,并决定路由到CPU0。
处理器接口响应:CPU0的接口接收中断,置位中断请求信号。
处理器处理:CPU0跳转到中断处理程序,读取中断ID并处理。
中断结束:处理完成后,CPU0向GIC发送EOI,GIC清除中断状态。
中断的状态分为以下四种,Inactive:中断源没有发送中断;Pending:中断源已经发送中断,等待处理器处理;Active:处理器已经确认中断,正在处理;Active and Pending:处理器正在处理中断,相同的中断源又发送了一个中断。
中断状态转换过程:Inactive-->Pending:外围设备发送了中断;Pending-->Active:处理器确认了中断;Active-->Inactive:处理器处理完中断。
GIC v2控制器的描述符:
该结构体用于描述中断控制器(如GIC, Generic Interrupt Controller)的操作接口,是Linux内核中管理中断的核心数据结构之一。每个这样的结构体实例代表一个具体的中断控制器驱动,通过函数指针定义了控制器与内核中断子系统交互的底层操作。
关键字段解析
.inq_mask
函数指针,指向屏蔽特定中断的硬件操作。例如,当需要暂时禁用某个外设的中断时,内核会调用此函数对中断控制器编程,使其不再响应该中断。
.inq_unmask
函数指针,指向取消中断屏蔽的操作。当中断处理完成后,内核通过此函数重新启用中断,允许后续中断触发。
.inq_eoi
函数指针,指向“中断结束”(End Of Interrupt)操作。内核在处理完中断后调用此函数,通知中断控制器当前中断已处理完毕,控制器可以准备接收新的中断。
.inq_set_type
函数指针,用于配置中断的触发类型(如边沿触发或电平触发)。例如,按键中断通常需要配置为边沿触发,而某些硬件错误中断可能需要电平触发。
.inq_retrigger
函数指针,用于重新触发中断。此函数通常用于调试或强制重新处理某个中断。
具体案例:处理一个GPIO按键中断
假设系统中有一个GPIO按键连接到GIC控制器,其工作流程如下:
中断触发
用户按下按键,GPIO硬件检测到电平变化,向GIC发送中断信号。中断屏蔽
GIC通知CPU有中断发生,内核首先调用.inq_mask
函数屏蔽该中断线,防止按键抖动导致多次触发。中断处理
CPU执行按键对应的中断处理程序(如记录按键事件)。中断结束
处理完成后,内核调用.inq_eoi
通知GIC中断已处理完毕。此时GIC可以重新响应此中断。取消屏蔽
内核调用.inq_unmask
重新启用该中断线,允许下次按键再次触发中断。
二、中断域
在复杂系统中,可能存在多个级联的中断控制器(如GIC作为根控制器,其他控制器如GPIO或PCIe控制器级联到GIC)。每个中断控制器有自己本地的硬件中断号(如GPIO控制器的0~31号中断),但这些本地中断号在全局范围内可能冲突。
irq_domain是Linux内核为统一管理中断号映射而设计的机制,其核心作用是将本地硬件(当前中断控制器)中断号(HW IRQ)转换为全局唯一的Linux虚拟中断号(Virq)。每个中断控制器对应一个irq_domain
,负责其所属中断号的映射和管理。
irq_domain结构体关键字段解析
字段 | 作用 |
---|---|
ops | 指向irq_domain_ops 结构体的指针,定义中断号映射的核心操作(如xlate 、map )。这是最关键的字段。 |
host_data | 指向中断控制器私有数据的指针(如寄存器基地址、设备树节点信息等),在映射过程中供ops 函数使用。 |
fwnode | 关联设备树(Device Tree)或ACPI的固件节点,用于描述中断控制器的硬件拓扑关系。 |
name | 中断域的名称(如"gic400"或"gpio-controller"),用于调试和日志。 |
创建中断域:中断控制器的驱动程序使用分配函数irq_domain_add_*()
创建和注册中断域。每种映射方法提供不同的分配函数,调用者必须给分配函数提供irq_domain_ops
结构体,分配函数在执行成功的时候返回irq_domain
的指针。
创建映射:内核提供函数irq_create_mapping()
查找映射:内核提供函数irq_find_mapping()
中断域支持的映射方法
1. 线性映射(Linear Map)
核心思想:
使用固定大小的数组(表)存储硬件中断号(HW IRQ)到Linux虚拟中断号(Virq)的映射关系。数组的索引直接对应硬件中断号。适用场景:
硬件中断号范围固定且较小(如0~255)。
例如:GPIO控制器(通常支持32~256个中断)、简单的I2C/SPI控制器。
函数与参数:
struct irq_domain *irq_domain_add_linear( struct device_node *of_node, // 设备树节点(关联中断控制器) unsigned int size, // 支持的最大硬件中断号数量 const struct irq_domain_ops *ops, // 操作函数集合(xlate、map等) void *host_data // 控制器私有数据(如寄存器地址) );
示例:GPIO控制器
假设GPIO控制器支持32个中断(HW IRQ 0~31),使用线性映射:// 从设备树获取GPIO节点 struct device_node *gpio_node = of_find_node_by_name(NULL, "gpio_controller"); // 创建线性映射的irq_domain struct irq_domain *gpio_domain = irq_domain_add_linear( gpio_node, 32, // 最大硬件中断号+1(0~31共32个) &gpio_domain_ops, // 自定义的xlate和map函数 gpio_priv_data // GPIO控制器的寄存器地址 );
映射过程:
硬件中断号
5
映射到Virqgpio_domain->linear_revmap[5]
(例如Virq 105)。直接通过数组索引实现快速查找。
2. 树映射(Tree Map)
核心思想:
使用基数树(Radix Tree)动态管理硬件中断号到Virq的映射,适用于中断号范围大且稀疏的场景。适用场景:
硬件中断号范围大(如0~65535)。
例如:PCIe控制器(支持大量设备中断)、复杂SoC中的多功能中断控制器。
函数与参数:
struct irq_domain *irq_domain_add_tree( struct device_node *of_node, // 设备树节点 const struct irq_domain_ops *ops, void *host_data );
示例:PCIe控制器
假设PCIe控制器支持1024个中断(HW IRQ 0~1023),使用树映射:// 创建树映射的irq_domain struct irq_domain *pcie_domain = irq_domain_add_tree( pcie_node, // PCIe控制器的设备树节点 &pcie_domain_ops, // 自定义的xlate和map函数 pcie_priv_data // PCIe控制器的私有数据 );
映射过程:
硬件中断号
128
动态插入基数树,映射到Virq512
。树结构节省内存,避免预先分配大数组。
3. 不映射(No Map)
核心思想:
硬件中断号(HW IRQ)直接作为Linux虚拟中断号(Virq)使用,无需转换。适用场景:
中断控制器本身支持灵活的中断号配置,且硬件中断号与Virq一一对应。
例如:PowerPC的MPIC(Multi-Processor Interrupt Controller)。
函数与参数:
struct irq_domain *irq_domain_add_nomap( struct device_node *of_node, unsigned int max_irq, // 最大硬件中断号 const struct irq_domain_ops *ops, void *host_data );
示例:PowerPC MPIC
// 创建不映射的irq_domain struct irq_domain *mpic_domain = irq_domain_add_nomap( mpic_node, // MPIC的设备树节点 1024, // 最大硬件中断号 &mpic_domain_ops, // 空操作或简单配置 mpic_priv_data );
映射过程:
硬件中断号
200
直接作为Virq200
使用。内核直接操作硬件中断号,无需额外映射表。
映射方法对比
特性 线性映射 树映射 不映射 数据结构 固定大小数组 基数树 无 内存占用 与最大中断号成正比 动态分配,节省内存 无额外内存 适用场景 中断号范围小且密集 中断号范围大或稀疏 硬件中断号与Virq直接对应 查找速度 O(1)(数组索引) O(log n)(树查找) O(1) 典型应用 GPIO、I2C控制器 PCIe、复杂SoC中断控制器 PowerPC MPIC