1.GIC中断控制器介绍
ARM处理器中最常用的中断控制器是GIC(Global Interrupt Controller)。GIC支持3中中断类型。
(1)SGI(Software Generated Interrupt):软件产生的中断,通常用于多核之间的通信。一个CPU可通过写GIC的寄存器给另外一个CPU产生中断(IPI_WAKEUP、IPI_TIMER等)。SGI中断通常在Linux内核里被称为IPI中断(interprocess interrupts)。
(2)PPI(Private Peripheral Interrupt):某个CPU私有外设的中断,这类外设的中断只能发给绑定的CPU,如CPU本地时钟中断。
(3)SPI(Shared Peripheral interrupt):共享外设中断,这类外设的中断可以发送给任何一个CPU。
GIC中断控制器主要由三部分组成,分别为distributor模块、CPU Interface模块和Virtual CPU interfaces模块。distributor模块为每个中断源维护一个状态机,支持的状态有inactive、pending、active和active and pending状态,distributor模块会记录和比较出当前优先级最高的处于pending状态的中断,然后将高优先级的中断发送给CPU Interface模块。每个CPU对应一个CPU Interface模块,CPU Interface模块决定这个中断是否可以发送给对应的CPU。这里不讨论Virtual CPU interfaces模块。
PPI和SPI的触发方式有边沿触发(Edge-triggered)和水平触发(Level-sensitive)方式。
GIC处理中断的流程如下:
(1)当GIC检测到一个中断发生时,会将该中断标记为pending状态。
(2)对处于pending状态的中断,仲裁单元会将中断请求发送到目标CPU。
(3)对于每个CPU,仲裁单元从众多pending状态的中断中选择一个优先级最高的中断,发送到CPU Interface模块上。
(4)CPU Interface模块决定这个中断是否可以发送给CPU。如果这个中断的优先级满足要求,GIC会给CPU发送一个中断请求。
(5)当CPU收到中断请求后,会进入异常处理模式,读取GICC_IAR寄存器获取硬件中断号来响应来响应中断(通常是Linux内核中断处理程序读取寄存器)。在硬件中断处理完成之前(写GICC_EOIR寄存器表示硬件中断处理完成)GIC不会再向此cpu发送任何中断。对于SPI和PPI中断,返回的是硬件中断号,对于SGI中断,返回的是发出中断CPU的ID和硬件中断号。当GIC感知到软件读取了该寄存器后,又分为如下情况(详细的中断状态变化可参考《ARM® Generic Interrupt Controller Architecture version 2.0》第39页):
(a.)如果该中断源是pending状态且是边缘触发,那么状态将变成active,如果该中断源是pending状态且是电平触发,那么状态将变成active and pending。对于状态为active and pending的中断,gic不会再将此中断发给任何cpu,直到状态变为inactive后,gic才会重新发送中断。
(b.)如果该中断是active状态,将变成active and pending状态。
(6)当处理器完成中断服务后,软件写GICC_EOIR寄存器,表示中断处理完成。
下图是gic-400中断控制器的时序图,图中的中断N和M都是SPI类型的FIQ中断,高电平触发,N的优先级比M高,目标CPU相同。
(1)T1时刻:GIC distributor模块检测到中断M的电平变化。
(2)T2时刻:distributor模块设置中断M的状态为pending。
(3)T17时刻:CPU Interface模块拉低nFIQCPU[n]通知CPU发生了中断。
(4)T42时刻:GIC distributor模块检测到了另外一个优先级更高的中断N。
(5)T43时刻:GIC distributor模块用中断N替换了当前pending状态下优先级最高的中断,并设置中断N为pending状态。
(6)T58时刻:CPU Interface模块将中断N的硬件中断号更新到GICC_IAR寄存器Interrupt ID区域。
(7)T61时刻:CPU读取GICC_IAR寄存器获取了硬件中断号同时响应了中断N,distributor模块将中断N的状态由pending状态变为active and pending状态(电平触发的中断cpu响应后中断状态将会从pending直接变为active and pending)。
(8)T61-T131时刻:中断处理程序处理中断N。
(a)CPU响应中断N 3个时钟后,CPU Interface模块拉高nFIQCPU[n]。
(b)T126时刻cpu清除了外设的中断N,则外设的中断N引脚电平被拉低。
(c)T128时刻distributor模块将中断的N状态由active and pending更新为active状态。
(d)T131时刻cpu将中断N的硬件中断号写入GICC_EOIR寄存器,表示中断N处理完毕。distributor模块将中断的N状态由active更新为inactive状态。
(9)T146时刻:中断N完成后,经过tph时钟,distributor模块选择下一个最高优先级的中断,即将中断M发送给CPU Interface模块,CPU Interface模块拉低nFIQCPU[n]通知CPU发生了中断。
(10)T211时刻:CPU读取GICC_IAR寄存器获取了硬件中断号同时响应了中断M,distributor模块将中断M的状态由pending状态变为active and pending状态。
(11)T214时刻:CPU响应中断M 3个时钟后,CPU Interface模块拉高nFIQCPU[n]。
2.zynq7k中断控制器
2.1.简介
zynq7020有两个Cortex-A9内核,采用GIC pl390(属于GIC V1版本)中断控制器。zynq7020中断控制器有16个SGI中断,硬件中断号范围0-15,5个PPI中断,硬件中断号范围16-31,其中16-26保留,没有使用,60个SPI中断,硬件中断号范围32-95,93-95保留,没有使用。SPI中断可通过读取spi_status_0和spi_status_1寄存器,获取发生中断的硬件中断号。
2.2.设备树节点
zynq7k设备树中的中断控制器描述如下。兼容属性compatible
为arm,cortex-a9-gic
,用来与驱动程序匹配,匹配成功后,驱动会被正确加载。#interrupt-cells = <3>
表示要用3个32位整数描述中断,第一个整数说明中断类型,0表示SPI中断,1表示PPI中断;第二个整数说明中断号;第三个整数说明中断触发类型。interrupt-controller
属性说明此节点是一个中断控制器节点,reg
表示中断控制器寄存器基地址及范围,0xF8F01000
为distributor
模块寄存器基地址,长度为0x1000
,0xF8F00100
为CPU Interface
模块寄存器的基地址,长度为0x100
。intc节点无interrupt-parent
属性,说明此中断控制器为根(root)中断控制器,直接连接cpu。内核启动的时候会解析设备树节点,将设备树节点转换为device_node
结构体,然后将这些device_node
以树的形式组织起来,若某个驱动的属性和设备树节点中的属性一致,则驱动会被初始化并被加载到系统中。
intc: interrupt-controller@f8f01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xF8F01000 0x1000>, // ICDXX
<0xF8F00100 0x100>; // ICCXX
};
gpio0也是一个中断控制器,具有interrupt-controller
属性,其父中断控制器为intc
,即上面提到的zynq7k的根中断控制器。
gpio0: gpio@e000a000 {
compatible = "xlnx,zynq-gpio-1.0";
#gpio-cells = <2>;
clocks = <&clkc 42>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&intc>; // 说明父中断控制器为intc
interrupts = <0 20 4>; // SPI中断,硬件中断号为20
reg = <0xe000a000 0x1000>;
};
3.中断控制器的初始化过程
Linux内核使用struct irq_desc
描述中断,每个中断都对应一个irq_desc
结构体。irq_desc
有两种组织形式,如果定义了CONFIG_SPARSE_IRQ
,则采用radix-tree
组织irq_desc
,初始化时需要动态分配irq_desc
,并将其插入到irq_desc_tree
中,此种组织形式中断号不连续;反之,使用静态定义的数组irq_desc
组织irq_desc
,只需要初始化数组元素即可,不需要动态分配irq_desc
,此种组织形式中断号连续。
[kernel/irq/irqdesc.c] // 静态定义的数组,存放所有irq_desc,元素数量为NR_IRQS
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp =
{
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
[inlcude/asm-generic/irq.h]
#define NR_IRQS 64
[kernel/irq/irqdesc.c] // 定义的`radix-tree`根节点
static RADIX_TREE(irq_desc_tree, GFP_KERNEL)
Linux内核启动期间,会初始化中断控制器。根据是否定义CONFIG_SPARSE_IRQ
,初始化流程有所区别。
首先分析定义了CONFIG_SPARSE_IRQ
的初始化流程。调用early_irq_init
获取中断数量,为每一个中断动态分配一个irq_desc
结构体,同时设置位图,将对应的bit设置为1,最后将分配的irq_desc
插入到irq_desc_tree
中,可以看出所有的中断都是通过irq_desc_tree
进行管理。接着调用init_IRQ
,将设备树中的节点与__irqchip_of_table
段中保存的中断控制器信息进行匹配,只有兼容属性一致且设备树节点含有interrupt-controller
属性才能匹配成功,将匹配成功的中断控制器初始化函数保存到irq_init_cb
中,匹配完成后调用初始化函数,需要注意的是只有父控制器的初始化函数会被调用。对于zynq7k,父中断控制器始化函数是gic_of_init
。
start_kernel // 内核入口函数
->local_irq_disable() // 如果没有禁止中断,则会禁止中断
->early_irq_init
->init_irq_default_affinity // 设置中断对每个cpu的亲和度
->arch_probe_nr_irqs // 获取中断数量
// 默认从machine_desc结构体中的nr_irqs中获取,若为0则使用默认值NR_IRQS
nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS
->alloc_desc // 为每个中断分配一个irq_desc结构体并初始化
->kzalloc_node // 分配irq_desc
->esc->kstat_irqs = alloc_percpu(unsigned int) // 分配每cpu变量
->desc_set_defaults // 使用默认值初始化irq_desc
->set_bit(i, allocated_irqs); // 设置位图,每一位表示一个irq_desc,将对应的bit设置为1
->irq_insert_desc // 将分配的irq_desc插入到irq_desc_tree中
->init_IRQ
->irqchip_init
// __irqchip_of_table在链接脚本中定义,存放中断控制器信息,可与设备树中的中断控制器节点匹配
->of_irq_init(__irqchip_of_table)
// 遍历所有设备节点,并与__irqchip_of_table中定义的中断控制器信息进行匹配,并判断是否具有
// interrupt-controller属性
->for_each_matching_node_and_match
kzalloc // 满足要求,则分配一个of_intc_desc结构体,以链表的形式组织起来
// 设置irq_init_cb指向匹配成功的中断控制器的初始化函数
desc->irq_init_cb = match->data
// 匹配完成开始遍历匹配成功的of_intc_desc结构体链表
->while (!list_empty(&intc_desc_list))
// 调用父中断控制器的初始化函数
desc->irq_init_cb(desc->dev, desc->interrupt_parent)
->acpi_probe_device_table // 中断控制器电源相关,忽略
...
->local_irq_enable() // 开启本地中断
接着分析没有定义CONFIG_SPARSE_IRQ
的初始化流程。可以看出,是否定义CONFIG_SPARSE_IRQ
,只在early_irq_init
函数中不同。没有定义CONFIG_SPARSE_IRQ
,内核会初始化静态定义的irq_desc
数组,不会动态分配irq_desc
结构体。
start_kernel // 内核入口函数
->local_irq_disable() // 如果没有禁止中断,则会禁止中断
->early_irq_init
->init_irq_default_affinity // 设置中断对每个cpu的亲和度
desc = irq_desc // 获取保存irq_desc数组的首地址
count = ARRAY_SIZE(irq_desc) // 获取数组元素数量,即中断数量
->desc_set_defaults // 初始化数组元素
->init_IRQ
->irqchip_init // irqchip_init的初始化流程与定义CONFIG_SPARSE_IRQ一致
...
...
->local_irq_enable() // 开启本地中断
中断的初始化函数最终调用到了父中断控制器的初始化函数。zynq7k的父中断控制器为intc
,驱动程序位于irq-gic.c文件中。IRQCHIP_DECLARE
定义了与设备树匹配的匹配表。IRQCHIP_DECLARE
定义的内容编译器会将其放在__irqchip_of_table
段中。gic驱动程序定义的"arm,cortex-a9-gic"
属性和intc
设备树节点定义的兼容属性一致,两者可以匹配,匹配成功后调用的irq_init_cb
函数就是gic_of_init
函数。
[drivers/irqchip/irq-gic.c]
// 定义中断控制器属性及入口函数,属性和设备树intc节点的属性一致
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
[include/linux/irqchip.h]
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
下面详细分析一下gic_of_init
函数的调用流程。init_IRQ
传入两个device_node
参数,第一个为中断控制器的device_node
,第二个为中断控制器父节点的device_node
,由于intc
为根中断控制器,无父节点,因此第二个参数为NULL
。Linux设备驱动程序的初始化都围绕着设备结构体进行,gic中断控制器定义了struct gic_chip_data
设备结构体。gic_of_init
需要初始化设备结构体中两个比较重要的成员,分别为chip
和domain
,chip
指向了 gic_chip
,domain
的ops
指向 gic_irq_domain_hierarchy_ops
,分配的domain
挂到了全局链表irq_domain_list
。最后设置底层中断处理函数指针handle_arch_irq
指向 gic_handle_irq
。
[drivers/irqchip/irq-gic.c]
struct gic_chip_data { // gic驱动定义的设备结构体,gic初始化函数都围绕此结构体展开
struct irq_chip chip; // gic中断控制器操作函数集合
union gic_base dist_base; // distributor模块映射的虚拟基地址
union gic_base cpu_base; // CPU Interface模块映射的虚拟基地址
......
struct irq_domain *domain; // 硬件中断号映射为软件中断号
unsigned int gic_irqs; // 此中断控制器支持的中断数量
......
};
// 静态定义的gic设备结构体数组,有多少个中断控制器,就会初始化多少个元素
static struct gic_chip_data gic_data[CONFIG_ARM_GIC_MAX_NR] __read_mostly;
[drivers/irqchip/irq-gic.c]
gic_of_init
->of_iomap // 映射distributor模块的寄存器
->of_iomap // 映射CPU Interface模块寄存器地址
->gic_check_eoimode // 中断分步相关,gic v2可以将中断分为两步,gic v1只能是一步
->of_property_read_u32 // 获取cpu-offset属性,zynq7k设备树无此属性
->__gic_init_bases // gic的初始化函数,重要
gic = &gic_data[gic_nr] // 获取gic设备结构体数组的地址,gic_nr为中断控制器序号
gic->chip = gic_chip // 设置chip
gic->chip.irq_set_affinity = gic_set_affinity // 如是SMP系统,还需要设置cpu亲和度函数
// 获取中断控制器ICDICTR寄存器的低五位,通过低五位可计算出支持的中断数量
// zynq7k ICDICTR寄存器的低五位默认值为0x2,有64条外部中断线,总共64个外部中断
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f
// gic_irqs = (0x2 + 1) * 32 = 96
gic_irqs = (gic_irqs + 1) * 32
gic->gic_irqs = gic_irqs // 设置支持的中断数量为96
->irq_domain_create_linear // 初始化irq_domain
->__irq_domain_add // 分配irq_domain
->kzalloc_node // 分配irq_domain结构体内存
INIT_RADIX_TREE // 初始化revmap_tree
domain->ops = ops // 设置ops,ops为gic_irq_domain_hierarchy_ops
domain->host_data = host_data // 设置私有数据,指向中断控制器设备结构体
domain->hwirq_max = hwirq_max // 设置支持的最大硬件中断号,为96
domain->revmap_size = size // 设置线性映射中断号的数量,size为96
domain->revmap_direct_max_irq = direct_max // 设置为0
->irq_domain_check_hierarchy // 检查中断控制器是否级联
// 如果ops含有alloc函数,则设置支持级联的标志
if (domain->ops->alloc)
domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY; // 支持级联
->list_add // 将irq_domain添加到全局irq_domain_list链表中
// 在根中断控制器中,设置SMP系统的回调函数,调用gic_raise_softirq可触发IPI(SGI)中断,
// 用于多核之间的通信
->set_smp_cross_call(gic_raise_softirq)
// 注册通知函数,当cpu状态变化时,需要调用gic_cpu_notifier通知gic
->register_cpu_notifier(&gic_cpu_notifier)
// 设置中断处理函数,底层发生中断,最终要调用gic_handle_irq处理中断
// 这就是底层handle_arch_irq指向的函数
->set_handle_irq(gic_handle_irq)
->gic_dist_init // 初始化distributor模块
->gic_cpu_init // 初始化CPU Interface模块
->gic_pm_init // gic电源管理初始化
4.中断号的处理过程
gic_of_init
函数中设置了irq_domain
的ops
成员,指向了gic_irq_domain_hierarchy_ops
结构体。此结构体主要用于映射中断号。
[drivers/irqchip/irq-gic.c]
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
.translate = gic_irq_domain_translate, // 将中断引脚号转换为硬件中断号
.alloc = gic_irq_domain_alloc, // 完成硬件中断号向软件中断号的映射
.free = irq_domain_free_irqs_top, // 解除硬件中断号和软件中断号的映射关系
};
4.1.中断引脚号到硬件中断号的转换过程分析
首先分析一下gic_irq_domain_translate
函数是如何将中断引脚号转换成硬件中断号的。由于设备树中提供的是中断引脚号,没有考虑SGI和PPI中断,因此需要将中断引脚号加上SGI和PPI预留的硬件中断号范围,才能得到实际的硬件中断号。
gic_irq_domain_translate
if (fwspec->param_count < 3) // 如果interrupts属性值数量小于3个,返回-EINVAL
return -EINVAL;
*hwirq = fwspec->param[1] + 16 // 由于0-15中断号预留给SGI中断,因此需要加16
if (!fwspec->param[0]) // 对于SPI中断,需要再加16以跳过给PPI预留的中断号
*hwirq += 16; // 则串口最终的硬件中断号为27+16+16=59,和手册中一致
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK // 获取中断类型
4.2.硬件中断号到软件中断号的映射过程分析
接着分析一下gic_irq_domain_alloc
函数是如何将硬件中断号映射为软件中断号的。每个软件中断号对应一个irq_desc
,irq_desc
中嵌入了一个和中断控制器有关的结构体irq_data
,将硬件中断号填入irq_data
的hwirq
变量中就完成了中断号的映射工作。得到软件中断号的过程将在下一章分析。
// 传入的参数为中断控制器的指针domain、软件中断号、映射的中断号数量及参数
gic_irq_domain_alloc
// 将设备树中的中断引脚号转换为硬件中断号,获取中断触发类型
->gic_irq_domain_translate
->gic_irq_domain_map // 进行中断映射
gic_chip_data *gic = d->host_data // 获取gic设备结构体
->irq_domain_set_info
->irq_domain_set_hwirq_and_chip
->irq_domain_get_irq_data // 获取软件中断号对应的irq_data
irq_data->hwirq = hwirq // 设置硬件中断号
irq_data->chip = chip // 设置chip,chip为gic设备结构体中的chip成员,即gic_chip结构体
irq_data->chip_data = chip_data // 设置gic设备结构体
->__irq_set_handler
->__irq_do_set_handler
desc->handle_irq = handle // 设置中断处理函数为handle_fasteoi_irq
->irq_set_handler_data
desc->irq_common_data.handler_data = data
->irq_set_probe // 设置一些标志
// gic chip结构体,即irq_data中chip指向的结构体,包含了gic控制器操作函数集合
static struct irq_chip gic_chip = {
.irq_mask = gic_mask_irq, // 屏蔽中断
.irq_unmask = gic_unmask_irq, // 解除屏蔽
.irq_eoi = gic_eoi_irq, // 中断结束时使用此接口通知gic中断控制器
.irq_set_type = gic_set_type, // 设置中断触发类型
.irq_get_irqchip_state = gic_irq_get_irqchip_state, // 获取中断控制器状态
.irq_set_irqchip_state = gic_irq_set_irqchip_state, // 设置中断控制器状态
// 中断控制器标志
.flags = IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
4.数据结构介绍
4.1.中断描述符irq_desc
每一个外设的中断都对应一个中断描述符,Linux内核使用irq_desc
结构体描述中断。成员irq_data
和具体硬件相关,内部包含了irq_chip
,是中断控制器操作函数的集合;成员handle_irq
在中断发生时首先调用,然后根据不同的中断类型执行不同的中断处理函数,同一个中断控制器,其handle_irq
是一样的;action
是中断处理函数,设备驱动使用request_irq
和request_threaded_irq
注册的中断处理函数就保存在此处,如果是共享中断,则action
是一个链表,保存了多个中断处理函数。
[include/linux/irq.h]
struct irq_desc // 中断描述符
{
struct irq_common_data irq_common_data;
struct irq_data irq_data; // 包含了irq_chip,和中断控制器相关
unsigned int __percpu *kstat_irqs; // 中断的统计信息
irq_flow_handler_t handle_irq; // 中断产生后,首先执行此函数指针指向的函数,然后再执行action
......
struct irqaction *action; // 具体设备的中断处理函数,是一个链表,共享中断时有多个处理函数
unsigned int status_use_accessors; // 中断描述符状态
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
......
int parent_irq;
const char *name; // 产生中断的设备名称
} ____cacheline_internodealigned_in_smp
struct irq_desc
在Linux内核中有两种组织方式,第一种为数组形式,应用于线性映射,另一种是基数树,应用于非线性映射。
4.2.硬件相关的irq_data和irq_chip
irq_data
将硬件相关的内容进行了封装,主要成员有Linux软件(虚拟)中断号irq
、硬件中断号hwirq
、中断控制器操作函数集合chip
及中断号映射结构体domain
。
[include/linux/irq.h]
struct irq_data // 中断控制器对应的数据
{
u32 mask;
unsigned int irq; // 中断号
unsigned long hwirq; // 硬件中断号
struct irq_common_data *common; // 所有中断控制器共享的数据
struct irq_chip *chip; // 包含了底层中断控制器的操作函数
struct irq_domain *domain; // 中断号映射结构体,可将硬件中断号映射成linux的软件中断号
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data; // 指向父中断控制器的irq_data
#endif
void *chip_data; //irq_chip的私有数据
};
irq_chip
是中断控制器操作函数的集合,主要成员有启动中断控制器irq_startup
、关闭中断控制器irq_shutdown
、使能中断irq_enable
、禁止中断irq_disable
、响应中断irq_ack
、屏蔽中断irq_mask
、响应并屏蔽某个中断irq_mask_ack
等。cpu调用这些函数控制中断控制器。
[include/linux/irq.h]
struct irq_chip { // 中断控制器操作函数集合
const char *name; // 中断控制器的名称,可在/proc/interrupts中查看
unsigned int (*irq_startup)(struct irq_data *data); // 启动中断,如为NULL则默认启动
void (*irq_shutdown)(struct irq_data *data); // 关闭中断,如为NULL则默认关闭
void (*irq_enable)(struct irq_data *data); // 使能中断
void (*irq_disable)(struct irq_data *data); // 禁止中断
// 响应中断,有的中断控制器在中断响应后(清除pending的中断)才可以重新触发中断
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data); // 屏蔽某个中断
void (*irq_mask_ack)(struct irq_data *data); // 响应并屏蔽某个中断
void (*irq_unmask)(struct irq_data *data); // 解除屏蔽中断
void (*irq_eoi)(struct irq_data *data); // 中断结束后调用
// 在smp系统中,设置中断对某个cpu的亲和度
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
// 重新触发中断,当丢失硬件中断后,可使用软件再次触发中断
int (*irq_retrigger)(struct irq_data *data);
// 设置中断触发方式
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
// 使能 禁止电源管理中的唤醒功能
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
// 使用慢速总线时,需要lock总线
void (*irq_bus_lock)(struct irq_data *data);
// unlock慢速总线
void (*irq_bus_sync_unlock)(struct irq_data *data);
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
......
unsigned long flags;
};
4.3.中断号映射相关的irq_domain和irq_domain_ops
中断发生时,只能从中断控制器得到硬件中断号,但Linux内核使用软件(虚拟)中断号描述中断,因此需要将两者联系起来。irq_domain
是硬件中断号和软件中断号的桥梁,可以将硬件中断号映射为软件中断号。每个中断控制器都对应一个irq_domain
。irq_domain
的主要成员有操作函数集合ops
、最大硬件中断号hwirq_max
、线性映射的中断号数量revmap_size
、radix tree映射的根节点revmap_tree
及线性映射的查找表linear_revmap
.
[include/linux/irqdomain.h]
struct irq_domain // 将硬件中断号映射为Linux软件中断号,每个中断控制器都对应一个irq_domain
{
struct list_head link; // 挂接到全局的irq_domain_list链表
const char *name; // interrupt domain的名称
const struct irq_domain_ops *ops; // irq_domain的操作函数,包含映射和解除映射函数
void *host_data; // irq_domain私有数据
unsigned int flags;
struct fwnode_handle *fwnode; // 指向和irq_domain相关联的设备树节点
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent; // 配置级联,则指向父中断控制器的irq_domain
#endif
irq_hw_number_t hwirq_max; // irq_domain中最大的硬件中断号
unsigned int revmap_direct_max_irq; // 线性映射时可写入中断控制器的最大硬件中断号
unsigned int revmap_size; // 线性映射中断号的数量
struct radix_tree_root revmap_tree; // radix tree映射的根节点
unsigned int linear_revmap[]; // 线性映射用到的查找表
};
[include/linux/irqdomain.h]
struct irq_domain_ops // irq_domain映射中断号时用到的操作函数
{
// 中断控制器设备树节点和irq_domain匹配
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
// 映射中断号
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
// 取消映射
void (*unmap)(struct irq_domain *d, unsigned int virq);
// 将设备树提供的中断号转换为真实的硬件中断号,如zynq7k gpio设备树节点中的中断号为20,
// 由于0-31的硬件中断号被PPI和SGI中断占用,gpio真实的硬件中断号为52,需要加上一个偏移量
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
......
};
[kernel/irq/irqdesc.c]
// 定义了一个位图,如果定义CONFIG_SPARSE_IRQ则大小为(NR_IRQS + 8196)bit,否则为NR_IRQS bit
static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);
5.总结
Linux中断子系统关键数据结构及其之间的联系可总结如下:
参考资料
- Linux kernel V4.6版本源码
- https://www.cnblogs.com/LoyenWang/p/12996812.html
- 《奔跑吧 Linux内核:基于Linux 4.x内核源代码问题分析》
- 《Zynq-7000 SoC Technical Reference Manual》
- 《ARM® Generic Interrupt Controller Architecture version 2.0》
- 《CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual》