文章目录
-
- 1、概述
- 2、中断硬件体系结构
-
- 1)中断硬件体系组成
- 2)中断控制器
- 3)GIC硬件框架详解
-
- 1)为什么要有GIC?
- 2)GIC引入的时间线
- 3)GIC出现之前的状况?
- 4)GIC架构的演变
- 5)GIC架构简图
- 6)GICv2架构图
- [7)GICv3 架构](#7)GICv3 架构)
-
- [1、GICv3 架构图](#1、GICv3 架构图)
- 2、GICv3的改进-MSI中断机制
- 3、GIV3的中断号扩展
- 4、LPI中断的触发方式
- 5、GICv2和GICv3的编程差异
- 8)GICv2m模块
- 9)小结
- 3、异常向量表
- 4、Linux中断子系统
-
- 1)中断子系统架构图
-
- 1、中断控制器的级联
-
- [1) 一级中断控制器处理流程](#1) 一级中断控制器处理流程)
- 2)多级中断控制器处理流程
- 3)irq_desc分配策略
- 2、GPIO控制器+GICv2控制器
- 3、PCIe控制+GICv3控制器
- 2)GIC控制器源码分析
- 3)基于SPI中断的GPIO中断控制器驱动实现
- 4)基于MSI机制的PCIe控制器驱动实现
- 5)设备申请中断
- 5、中断软件处理机制-响应中断
- 6、共享中断-扩展中断手段
-
- [1) 多级中断控制器的扩展](#1) 多级中断控制器的扩展)
- 2)软件对硬件的扩展
- 7、中断子系统调试
- 8、参考
1、概述
1)中断的设计思想
1、从微观的角度来看,中断就是为了能够让CPU处理具有优先级的多种任务,任务的单位是"一条指令";
--> CPU每执行完一条指令,都会去检查是否有中断/异常发生;
2、参照人的思维模式,在同时处理多个任务的策略,我们会先给任务排个优先级,然后依次处理;当有更高优先级的任务到来,我们也应该放下当前任务(保存好现场,方便后续快速接入),然后切换到更高优先级的任务处理,以此类推;
3、CPU是人所设计,当然也会使用这种机制(中断、异常机制)来处理多种任务;
4、中断是系统正常运行的基础,中断模块是系统必不可少的硬件模块(与GPIO\Timer等模块类似,都是最基础的设施),重要程度仅次于CPU和内存,程序设计及使用上虽然简单,但分布广泛,几乎是所有模块的必用资源,包括CPU自身,硬件设计上颇为复杂!
5、中断模块是外设?
除了CPU其它模块都算外设,中断模块的设计相较于I2C/UART/SPI这类标准外设(有规范约束),没有规范约束,因此不同厂家的实现比较繁杂,比如ST的NVIC(Nested vectored interrupt controller)、ARM 的GIC、X86上的PIC/APIC等等,但是机制都是一样的;
2)中断与异常的关系?
1)中断和异常:https://zhuanlan.zhihu.com/p/617507895
2)不要混淆中断和异常

我们常说中断是异常的一种,站在CPU的角度来看,确实都是发生"打断"了,但中断是合法的,而异常则是非法的!
但站在资源管理的角度,应当区分开中断和异常,他们在到达CPU有不同的"路径",编程也就不一样,虽然他们最终都是达到CPU;
3)中断控制器管辖的是哪种类型?
所有的中断 - 包括软件中断(CPU自己触发合法的打断)和 硬件中断(外部触发合法的打断)
2、中断硬件体系结构
中断硬件体系结构概念和分支组合较多,比较难啃!
但只有深刻理解中断硬件系统,才能在Linux内核中复杂的结构体(硬件抽象)和 流程配置中不迷失方向!
1)中断硬件体系组成
以IMUX6ULL为例

1)从模块的角度来看中断的传递控制有三级:
1、第一级是连接外部的模块,比如GPIO内部也有对应的中断控制器,有对应的寄存器可以设置(屏蔽中断、清除中断等),在某些芯片设计中,不是每一个GPIO引脚都可以触发中断(节约成本);
2、第二级是GIC,所有的模块的中断信号要经过GIC集中分发,中断控制功能更加复杂一些(优先级、路由控制等等);
3、第三级是CPU,CPU也有自己的寄存器,可以设置它来使能/禁止中断,这是中断处理的总开关。
CPU每执行完一条指令,都会判断一下是否有中断发生了。
2)最终GIC向CPU发送IRQ或FIQ;
3)共享中断属于软件机制,Linux框架也已提供了相应接口给开发者使用;
2)中断控制器
1、中断控制器与GPIO/UART/I2C一样,也是一个硬件IP模块,理所当然 不同厂商根据自己的产品应用情况可以有自己的设计;
2、中断是一种机制,厂商的实现就是策略,中断控制器有多种实现,比如:
- STM32F103中被称为NVIC:Nested vectored interrupt controller(嵌套向量中断控制器)
- ARM9中一般是芯片厂家自己实现的,没有统一标准
- Cortex A7中使用GIC(Generic Interrupt Controller)
3、这里介绍最常见的策略PIC和GIC
嵌入式系统MCU大多使用可编程中断控制器PIC-programmable interrupt controller
ARM多核处理器中最常用的是中断控制器GIC-Generic Interrupt Controller,GIC比PIC复杂不少
1、MCU/X86中断控制器PIC
PIC(单核)和 APIC(多核,与GIC类似)
https://zhuanlan.zhihu.com/p/603708401
linux中断源码分析 - 概述(一) PIC/APIC
https://www.cnblogs.com/tolimit/p/4390724.html
X86架构-高级可编程中断控制器APIC
https://blog.csdn.net/qq_39376747/article/details/113736525
1)PIC架构

2)APIC架构

2、ARM中断控制器-GIC
GIC-V2架构图

3)GIC硬件框架详解
1)为什么要有GIC?
GIC的出现当然是为了满足更高需求,比如路由到多个处理器,支持更多的中断数,更强大的配置选项,这些都是传统架构所不具备的;
2)GIC引入的时间线
1)linux 3.7 内核版本开始支持GIC-v1
2)Linux 4.4 内核版本 开始支持GIC-V3
3)GIC出现之前的状况?
随着越来越复杂的SOC,中断管理越来越困难
1)中断源越来越多,有的系统高达几百个;
2)中断类型越来越多,普通的外设中断、软件触发的中断、多核CPU间的中断、PCIe上基于消息传递的中断
3)虚拟化的支持;
传统的中断架构无法满足以上需求,ARM为了解决以上需求,设计了GIC中断控制器
4)GIC架构的演变

1、GICV1和GICV2差别不大;
2、到了GIC-V3新增了基于消息的中断特性(使用软件消息代替GIC到CPU物理中断线路),硬件简化了,意味着软件就复杂许多!
3、GIC-V3和GIC-V4差别不大;
4、GIC存在多种架构,功能越来越强大,Linux内核都需要支持,这也正是中断子系统的复杂之处!对比I2C/UART/SPI这类标准的外设,简单多了!
5)GIC架构简图


① 分发器(Distributor) - 该模块是GIC管理功能主要实现
系统中的所有中断源都连接到该单元。可以通过仲裁单元的寄存器来控制各个中断源的属性,例如优先级、状态、安全性、路由信息和使能状态。分发器把中断输出到"CPU接口单元",后者决定将哪个中断转发给CPU核。
② CPU接口单元(CPU Interface)- 功能比较简单,开关功能和信息传递
CPU核通过控制器的CPU接口单元接收中断。CPU接口单元寄存器用于屏蔽,识别和控制转发到CPU核的中断的状态。系统中的每个CPU核心都有一个单独的CPU接口。
中断在软件中由一个称为中断ID的数字标识。中断ID唯一对应于一个中断源。软件可以使用中断ID来识别中断源并调用相应的处理程序来处理中断。呈现给软件的中断ID由系统设计确定,一般在SOC的数据手册有记录。
6)GICv2架构图

1、GIC提供了内存映射寄存器,可用于管理中断源和行为,以及(在多核系统中)用于将中断路由到各个CPU核。
2、它使软件能够屏蔽,启用和禁用来自各个中断源的中断,以(在硬件中)对各个中断源进行优先级排序和生成软件触发中断。
3、它还提供对TrustZone安全性扩展的支持。GIC接受系统级别中断的产生,并可以发信号通知给它所连接的每个内核,从而有可能导致IRQ或FIQ异常发生。
4、中断号的划分

1、GIC中有多种不同的中断类型
1)GIC定义了多种不同的中断类型,实现更加精细化的控制管理!
① 软件触发中断(SGI,Software Generated Interrupt)
这是由软件通过写入专用仲裁单元的寄存器即软件触发中断寄存器(ICDSGIR)显式生成的。它最常用于CPU核间通信。SGI既可以发给所有的核,也可以发送给系统中选定的一组核心。中断号0-15保留用于SGI的中断号。用于通信的确切中断号由软件决定。
常用的多核调度中断(Inter-Processor Interrupt):IPI_WAKEUP、IPI_TIMER、IPI_RESCHEDULE、IPI_CALL_FUNC、IPI_IRQ_WORK!
普通的驱动程序开放很少用到SGI,主要还是使用下面的PPI/SPI
② 私有外设中断(PPI,Private Peripheral Interrupt)
这是由单个CPU核私有的外设生成的。PPI的中断号为16-31。它们标识CPU核私有的中断源,并且独立于另一个内核上的相同中断源,比如,每个CPU的本地定时器(Local timer)。
③ 共享外设中断(SPI,Shared Peripheral Interrupt)
这是由外设生成的,中断控制器可以将其路由到多个核。中断号为32-1020。SPI用于从整个系统可访问的各种外围设备发出中断信号。
中断可以是边沿触发的(在中断控制器检测到相关输入的上升沿时认为中断触发,并且一直保持到清除为止)或电平触发(仅在中断控制器的相关输入为高时触发)。
ARM Linux中,共享中断默认在CPU0上产生,可以通过irq_set_affinity(irq, cpumask_of(i)),把中断irq设定到CPUi上去;
2)中断资源的分配案例
树莓派4B

2、GIC中断可以处于多种不同状态
1)除了中断类型,中断的状态也有多种,一般在寄存器中设置,一般硬件自动实现转换,不需要软件参与!有如下4中中断状态
① 非活动状态(Inactive)--这意味着该中断未触发。
② 挂起(Pending)--这意味着中断源已被触发,但正在等待CPU核处理。待处理的中断要通过转发到CPU接口单元,然后再由CPU接口单元转发到内核。
③ 活动(Active)--描述了一个已被内核接收并正在处理的中断。
④ 活动和挂起(Active and pending)--描述了一种情况,其中CPU核正在为中断服务,而GIC又收到来自同一源的中断。
2)状态变化过程
1、中断的优先级和可接收中断的核都在分发器(distributor)中配置。外设发给分发器的中断将标记为pending状态(或Active and Pending状态,如触发时的状态是active)。distributor确定可以传递给CPU核的优先级最高的pending中断,并将其转发给内核的CPU interface。通过CPU interface,该中断又向CPU核发出信号,此时CPU核将触发FIQ或IRQ异常。
2、作为响应,CPU核执行异常处理程序。异常处理程序必须从CPU interface寄存器查询中断ID,并开始为中断源提供服务。完成后,处理程序必须写入CPU interface寄存器以报告处理结束。然后CPU interface准备转发distributor发给它的下一个中断。
3、在处理中断时,中断的状态开始为pending,active,结束时变成inactive。中断状态保存在distributor寄存器中。
2)中断时序图案例

1、可以通过以上时序图来深刻理解GIC工作原理;
2、但这都是软件上不感知的,硬件流水线操作!
3、GIC的初始化

1)Distributor和CPU interface在复位时均被禁用。复位后,必须初始化GIC,才能将中断传递给CPU核。
2)在Distributor中,软件必须配置优先级、目标核、安全性并启用单个中断;随后必须通过其控制寄存器使能。
3)对于每个CPU interface,软件必须对优先级和抢占设置进行编程。每个CPU接口模块本身必须通过其控制寄存器使能。
4)在CPU核可以处理中断之前,软件会通过在向量表中设置有效的中断向量并清除CPSR中的中断屏蔽位来让CPU核可以接收中断。
5)可以通过禁用Distributor单元来禁用系统中的整个中断机制;可以通过禁用单个CPU的CPU接口模块或者在CPSR中设置屏蔽位来禁止向单个CPU核的中断传递。也可以在Distributor中禁用(或启用)单个中断。
6)为了使某个中断可以触发CPU核,必须将各个中断,Distributor和CPU interface全部使能,并将CPSR中断屏蔽位清零;
总的来说,打开各个部件,让中断能顺利达到CPU!
4、GIC的配置
GIC作为内存映射的外围设备,被软件访问。所有内核都可以访问公共的distributor单元,但是CPU interface是备份的,也就是说,每个CPU核都使用相同的地址来访问其专用CPU接口。一个CPU核不可能访问另一个CPU核的CPU接口。
Distributor拥有许多寄存器,可以通过它们配置各个中断的属性。这些可配置属性是:
-
中断优先级:Distributor使用它来确定接下来将哪个中断转发到CPU接口。
-
中断配置:这确定中断是对电平触发还是边沿触发。
-
中断目标:这确定了可以将中断发给哪些CPU核。
-
中断启用或禁用状态:只有Distributor中启用的那些中断变为挂起状态时,才有资格转发。
-
中断安全性:确定将中断分配给Secure还是Normal world软件。
-
中断状态。
Distributor还提供优先级屏蔽,可防止低于某个优先级的中断发送给CPU核。
每个CPU核上的CPU interface,专注于控制和处理发送给该CPU核的中断。
5、CPU处理GIC中断流程
1)当CPU核接收到中断时,它会跳转到中断向量表执行。
2)顶层中断处理程序读取CPU接口模块的Interrupt Acknowledge Register,以获取中断ID。除了返回中断ID之外,读取操作还会使该中断在Distributor中标记为active状态。一旦知道了中断ID(标识中断源),顶层处理程序现在就可以分派特定于设备的处理程序来处理中断。
3)当特定于设备的处理程序完成执行时,顶级处理程序将相同的中断ID写入CPU interface模块中的End of Interrupt register中断结束寄存器,指示中断处理结束。除了把当前中断移除active状态之外,这将使最终中断状态变为inactive或pending(如果状态为inactive and pending),这将使CPU interface能够将更多待处理pending的中断转发给CPU核。这样就结束了单个中断的处理。
4)同一CPU核上可能有多个中断等待服务,但是CPU interface一次只能发出一个中断信号。顶层中断处理程序重复上述顺序,直到读取特殊的中断ID值1023,表明该内核不再有任何待处理的中断。这个特殊的中断ID被称为伪中断ID(spurious interrupt ID)。伪中断ID是保留值,不能分配给系统中的任何设备。
7)GICv3 架构
1、GICv3 架构图

1)GICv3最大的变化是新增加了一类中断:LPI (Locality-specific Peripheral interrupts),以代替物理线路,这个对于ARM的中断扩展很重要,试想一下,1024个中断就需要布下1024根物理线路;
2)在GICv3中,对于原来的SPI,它也是可以使用MSI的方式传递的,这个功能是可选的。如果GICv3支持MSI方式的SPI,要产生/清除中断时,操作如下GIC寄存器:
- 产生中断:写寄存器GICD_SETSPI_NSR 或 GICD_SETSPI_SR
- 清除中断:写寄存器GICD_CLRSPI_NSR 或 GICD_CLRSPI_SR
3)对于LPI中断,有两种触发方式:
- 写寄存器GITS_TRANSLATER - 复杂灵活,一般使用这种
- 设备把数据写入GITS_TRANSLATER寄存器,写入的值被称为EventID
- 写寄存器GICR_SETLPIR - 简单直接
2、GICv3的改进-MSI中断机制
1)外设通过消息的方式传递中断信号给GIC,称为MSI (是MSI的扩展) 机制,GICv3中的SPI/LPC都支持MSI机制;
2)MSI-X (Enhanced MSI interrupt support)是MSI的扩展:特性
- 可以支持多大2048个中断
- 系统软件可以单独设置每个中断,不需要分配连续的中断号
- 每个中断可以单独设置:PCI设备使用的"地址/数据"可以单独设置
3)GICv2里,设备向中断控制器发出中断,使用物理上的线路:

4)在GICv3里,添加了MSI("message-based interrupts") 机制,设备往某个地址写入数值,即可触发中断:

5)PCI/PCIe设置使用MSI机制案例

1、MSI/MSI-X的消息包是硬件构造并发送,软件工作 - 配置寄存器;
2、MSI/MSI-X 消息包格式
1)MSI消息格式

2)MSI-X消息格式

3、GIV3的中断号扩展
0~1023跟GICv2保存一致。

4、LPI中断的触发方式
LPI有两种触发中断的方式:
- 把INTID直接写入GICR_SETLPIR寄存器
- 使用ITS把EventID 转换为LPI INTID,会用到"GITS_TRANSLATER"寄存器
这两种方法只能支持一种。
1)使用GICR_SETLPIR
这个寄存器格式如下:

把LPI的中断号写入这个寄存器即可触发中断。
2)使用ITS
ITS的意思是:Interrupt Translation Service,中断转换服务
有专门的设备驱动模块 drivers/irqchip/irq-gic-v3-its.c,内核一般都会使用这种方式来触发LPI中断

能产生MSI中断的设备,都有一个DeviceDI(设备ID),它产生的每一个MSI中断都有一个EventID(事件ID)。"DeviceID+EventID"组合被传入ITS,IDS会把它们转换为INTID。
过程如下:
-
外设发生中断消息(Interrupt Message)到ITS
-
外设只要写GITS_TRANSLATER就可以发送消息,这个寄存器格式如下:

-
消息里包含有:DeviceID(哪一个设备)、EventID(这个设备的哪一个中断)
-
-
ITS使用DeviceID在Device Table中找到一项
- 只有一个Device Table
- 每一个能发生MSI中断的设备在里面都有一项,它指向这个设备的Interrupt Translation Table(中断转换表,简称ITT)
- 每一个能发生MSI中断的设备,都有一个ITT
-
ITS使用EventID在ITT中找到一项,从中返回INTID和Collection ID
-
ITS使用Collection ID在Collection Table中找到一项,从中返回"Target Redistributor",即:中断发给哪个CPU
-
ITS把INTID发给Redistributor


上图中Device Table、Interrupt Translation Table、Collection Table都是在内存里,但是我们不能直接去设置内存。而是通过发送ITS命令来设置这些表格,这些都在ITS设备驱动中处理
5、GICv2和GICv3的编程差异
1)GIC中断主控器架构代码会有所不同,对外接口差异不大;
2)对于使用GIC(SPI/PPI/SGI)跟GICv2和GICv3的差异不大,GICv3另外多支持LPI中断申请;
3)MSI是一种全新机制,需要软硬件支持,当做扩展功能就好;
4)高端平台才会配置GICv3 或 GICv4,大部分产品还是GICv2;
8)GICv2m模块
由于GICv3架构变了!现代外设(比如PCIe)使用的是MSI/MSI-X中断机制,但主控仍然是GICv2 不支持MSI中断机制,如何处理?
ARM设计了过渡模块 - GICv2m,实现传统GICv2架构通过V2M模块来支持MSI

1)当PCIe设备向这些寄存器写入数据(发起MSI)时,V2M模块将这个内存写操作"翻译"成一个传统的、电平触发的SPI中断,并发送给GICv2。
2)GICv2像处理普通外设中断一样,将该中断分发给CPU。
9)小结
1、GIC架构非常杂乱(不断的迭代导致各种新旧代码并存),但这些复杂的实现都由强大的内核处理,开发者仅需要调用内核接口即可完成驱动业务逻辑;
2、GIC复杂之处是在配置阶段,配置好之后(大部分原厂配置好),与单片机使用中断没有差别(中断发生 -> 执行对应的钩子函数);
3、GIC存在多个层级和嵌套,软件设计需要 理清这些关系;
4、设备号的划分也要清晰!
3、异常向量表
1)CPU对中断处理流程
1、CPU执行完当前指令,会检查是否发生中断,若发生,则会跳转到异常向量表(硬件架构提供的机制);
2、保存现场、执行GIC提供的处理函数、完成后恢复现场,继续之前的任务;
2)异常向量表的初始化
1.映射向量表
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/kernel/head-common.S
__mmap_switched
/android/vendor/amlogic/common/kernel/common_5.4/init/main.c
--start_kernel
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/kernel/setup.c
----setup_arch(&command_line);
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/mm/mmu.c
------paging_init(mdesc);
--------devicemaps_init(mdesc);
----------vectors=early_alloc(PAGE_SIZE * 2); //1.分配新向量表
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/kernel/traps.c
----------early_trap_init(vectors); //2.从代码把向量表复制到新向量表
------------copy_from_lma(vectors_base, __vectors_start, __vectors_end);
------------copy_from_lma(vectors_base + 0x1000, __stubs_start, __stubs_end);
----------create_mapping(&map);//3.映射新的向量表到虚拟地址 mapped at 0xffff0000
2.vmlinux.lds.h中的向量表
1)
/android/vendor/amlogic/common/kernel/common_5.4/arch/arm/kernel/vmlinux.lds.h
#define ARM_VECTORS \
__vectors_lma = .; \
OVERLAY 0xffff0000 : NOCROSSREFS AT(__vectors_lma) { \
...
PROVIDE(vector_fiq_offset = vector_fiq - ADDR(.vectors));
2)
android/vendor/amlogic/common/kernel/common_5.4/arch/arm/kernel/entry-armv.S
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
3)在usr mode发生中断时,调用__irq_usr
__irq_usr:
usr_entry //保存现场
kuser_cmpxchg_check
irq_handler //处理
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq //恢复现场
UNWIND(.fnend )
ENDPROC(__irq_usr)
4)在kernel mode发生中断时,调用__irq_svc
__irq_svc:
svc_entry //保存现场
#ifdef CONFIG_AMLOGIC_VMAP
irq_handler vmap=1
#else
irq_handler //处理
#endif
...
svc_exit r5, irq = 1 //恢复现场
ENDPROC(__irq_svc)
5)irq_handler
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq //GIC驱动中提供此方法
#else
arch_irq_handler_default
#endif
4、Linux中断子系统
1)中断子系统架构图
不同厂家芯片,不同模块的组合,同一模块的不同版本(GIC),会导致整体中断子系统架构有所不同,这也是因为嵌入式的多样化导致,庆幸的是Linux内核对这些都有良好的支持,感慨Linux的强大!下面举两个例子来说明,先看看中断控制器的级联组合;
1、中断控制器的级联
1) 一级中断控制器处理流程

1)以UART模块为例,一个UART模块自身没有中断控制器,直接使用GIC的中断功能;
2)类似的还有I2C/SPI/I2S模块等;
2)多级中断控制器处理流程

1)以GPIO模块为例,GPIO模块自身也拥有自己的中断控制器;
2)GPIO以组为单位,组里若干个GPIO都支持中断,但通常的做法是占用一个GIC中断;
3)现代系统多级中断一般只有GPIO模块,再有的话就是 传统PIC(先进的已经使用基于消息中断代替) 或 外挂中断控制器(中断不够用时,但ARM一般都比较充足了);
3)irq_desc分配策略
对于irq_desc,内核有两种分配方法:
- 一次分配完所有的irq_desc - 使用内核方法 irq_domain_add_legacy()
- 按需分配,用到某个中断才分配它的irq_desc - 使用内核方法 irq_domain_add_linear()
现在的内核基本使用第2种方法,在解析interrupt设备树时分配,GIC和其它中断控制器(比如GPIO)的驱动中probe时映射使用;
2、GPIO控制器+GICv2控制器

1)Linux内核已经将各个模块分层设计,互相独立,实现"面向对象"编程;
1、GIC_driver提供接口给GPIO_driver使用,GPIO_driver提供接口给key driver使用;
2、解析翻译设备树的方法在 irq_domain 结构体实现;
2)各个模块配置后,当外部触发中断,硬件(注意控制器没有处理程序的能力,只能按照既定的寄存器设置执行对应的动作)自动路由到CPU,CPU调整到Vectors,开始软件处理,最终路由到用户的处理函数;
3、PCIe控制+GICv3控制器

1)与GPIO控制器+GICv2控制器的差异在于,1、PCIe使用ITS;2、PCIe设备没有对应的设备树,设备信息存放在PCI设备的内存中;
2)gic和pcie的设备树一般都由厂家BSP负责配置,驱动开发无需配置;
2)GIC控制器源码分析
1、源码目录
1)中断控制器gic v2
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic.c
2)中断控制器gic v3
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3.c
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3-its.c //v3开始支持its基于消息中断
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3-its-pci-msi.c //pci使用的msi
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3-its-platform-msi.c
3)中断控制器gic v4
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v4.c
4)中断控制器gic-pm
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-pm.c
5)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irqchip.c
6)设备树解析
/android/vendor/amlogic/common/kernel/common_5.4/drivers/of/irq.c
7) 内核irq框架代码
/android/vendor/amlogic/common/kernel/common_5.4/kernel/irq/*
2、数据结构-硬件的软件抽象
1.irq_desc -- 代表一个具体的中断描述,动态分配
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/irqdesc.h
struct irq_desc {
struct irq_data irq_data;
irq_flow_handler_t handle_irq;//void (*irq_flow_handler_t)(struct irq_desc *desc);
struct irqaction *action;
struct module *owner;
const char *name;
}
2.irqaction
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/interrupt.h
struct irqaction {
//interrupt handler function
irq_handler_t handler; //typedef irqreturn_t (*irq_handler_t)(int, void *);
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq; //interrupt number
const char *name;
struct proc_dir_entry *dir; //pointer to the proc/irq/NN/name entry
}
3.irq_data
struct irq_data {
u32 mask;
int irq; //由Linux内核统一管理分配,整个系统唯一,给内核其他子系统(如驱动)一个统一、稳定的中断标识来操作中断。
long hwirq; //硬件中断号,GIC和GPIO控制器都有自己的hwirq,序号只限于模块内使用;
struct irq_common_data *common;
struct irq_chip *chip;
//mapping between hwirq number and linux irq number.
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY //层级中断,简单来说就是连续分配多个中断
struct irq_data *parent_data;
#endif
void *chip_data;
}
4.irq_chip -- 代表GIC控制器、GPIO控制器等,提供操作控制器的方法
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/irq.h
struct irq_chip {
struct device *parent_device;
char *name;
int (*irq_startup)(struct irq_data *data);
void (*irq_startup)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
//屏蔽中断,清楚中断
void (*irq_mask)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data); // end of interrupt
int (*irq_set_affinity)(struct irq_data *data, struct cpumask *dest, bool force);
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_suspend)(struct irq_data *data);
//msg interrupt
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
//Virtual cpu, Hypervisor
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
}
5.irq_domain --
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/irqdomain.h
struct irq_domain {
struct irq_domain_ops *ops;
irq_hw_number_t hwirq_max;
int revmap_size;
int linear_revmap[]; // (hwirq,irq)
}
6.irq_domain_ops -- 解析设备树,映射
/android/vendor/amlogic/common/kernel/common_5.4/include/linux/irqdomain.h
struct irq_domain_ops {
//链式中断控制器方法 - 申请一个hw中断,使用这些方法来解析设备树和map
int (*match)(struct irq_domain *d, struct device_node *node,...);
int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
int (*xlate)(struct irq_domain *d, struct device_node *node, u32 *intspec, int intsize, long *out_hwirq, int *out_type);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY //层级中断控制器方法 - 连续申请多个hw中断,使用这些方法来解析设备树和map
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
#endif
}
7. irq_fwspec -- generic IRQ specifier structure
struct irq_fwspec {
struct fwnode_handle *fwnode;
int param_count;
u32 param[IRQ_DOMAIN_IRQ_SPEC_PARAMS];
}
8.gic_chip_data
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3.c
struct gic_chip_data {
struct fwnode_handle *fwnode;
void __iomem *dist_base;
struct redist_region *redist_regions;
struct rdists rdists;
struct irq_domain *domain;
u64 redist_stride;
u32 nr_redist_regions;
u64 flags;
bool has_rss;
unsigned int ppi_nr;
struct partition_desc **ppi_descs;
};
结构体联系:

3、源码分析
1)设备树
1、gic中断控制器对应的设备树
intc: interrupt-controller&00a01000 {
compatible = "arm, cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
}
2、GPIO中断控制器对应的设备树 - 不唯一,根据实际情况配置
gpio1: gpio_intc {
compatible = "arm, gpio controller";
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&intc>;
interrupts = <GIC-SPI 122 IRQ_TYPE_LEVEL_HIGH>;
}
3、gpio-key设备对应的设备树 - 不唯一,根据实际情况配置
gpio-key {
compatible = "arm, gpio_key";
interrupt-parent = <&gpio1>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
<1 IRQ_TYPE_LEVEL_HIGH>,
<2 IRQ_TYPE_LEVEL_HIGH>,
<3 IRQ_TYPE_LEVEL_HIGH>,
}
2)GICv3控制器初始化
1.创建irqchip platform_device
start_kernel (init\main.c)
--init_IRQ (arch\arm\kernel\irq.c)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irqchip.c
----irqchip_init(void)
/android/vendor/amlogic/common/kernel/common_5.4/drivers/of/irq.c
------of_irq_init(__irq_chip_of_table) //Scan and init matching interrupt controllers in DT
//根据设备树,找到__irqchip_of_table树组中对应的项,调用它的初始化函数
--------desc->irq_init_cb = match->data;
--------desc->irq_init_cb(desc->dev, desc->interrupt_parent);
2.IRQCHIP_DECLARE
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3.c
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);
展开后
include\linux\irqchip.h
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__irqchip_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
3.gicv3_of_init
/android/vendor/amlogic/common/kernel/common_5.4/drivers/irqchip/irq-gic-v3.c
gicv3_of_init(struct device_node *node, struct device_node *parent)
--rdist_regs[i].phys_base = res.start; //读取设备树中的Distributor/cpu interface的基地址存放在rdist_regs
--gic_init_bases()
//static struct gic_chip_data gic_data __read_mostly;
----gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data);
/android/vendor/amlogic/common/kernel/common_5.4/kernel/irq/irqdomain.c
------__irq_domain_add()
--------domain->ops = ops;//分配填充domain并返回
----set_handle_irq(gic_handle_irq); //挂接到异常向量表
------handle_arch_irq = handle_irq;
----gic_cpu_init();
----its_init(handle, &gic_data.rdists, gic_data.domain); //如果支持lpi中断,则初始化its
4.gic_irq_domain_ops的重点函数
static const struct irq_domain_ops gic_irq_domain_ops = {
.translate = gic_irq_domain_translate, //解析下一级控制器或设备的设备树,比如GPIO控制器设备树节点(用到GIC中断)
.alloc = gic_irq_domain_alloc, //建立hwirq virq chip的联系
.free = gic_irq_domain_free,
.select = gic_irq_domain_select,
};
5.gic_irq_domain_alloc -- 建立hwirq virq chip的联系
gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *arg)
--gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
--gic_irq_domain_map(domain, virq + i, hwirq + i);
----struct irq_chip *chip = &gic_chip;
/android/vendor/amlogic/common/kernel/common_5.4/kernel/irq/irqdomain.c
----irq_domain_set_info(d, irq, hw, chip, d->host_data,handle_percpu_devid_fasteoi_ipi,NULL, NULL);
------irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data); //建立联系
------__irq_set_handler(virq, handler, 0, handler_name);
------irq_set_handler_data(virq, handler_data);
6.gic_irq_domain_ops在哪里使用?
/android/vendor/amlogic/common/kernel/common_5.4/drivers/of/platform.c
of_device_alloc (drivers/of/platform.c)
--dev = platform_device_alloc("", -1); // 分配 platform_device
--num_irq = of_irq_count(np); // 计算中断数
/android/vendor/amlogic/common/kernel/common_5.4/drivers/of/irq.c
----of_irq_to_resource_table(np, res, num_irq) --根据设备节点中的中断信息, 构造中断资源
------of_irq_to_resource // drivers\of\irq.c
---------int irq = of_irq_get(dev, index); // 获得virq, 中断号
-----------of_irq_parse_one(dev, index, &oirq); //解析设备树中的中断信息, 保存在of_phandle_args结构体中
/android/vendor/amlogic/common/kernel/common_5.4/kernel/irq/irqdomain.c
-----------irq_create_of_mapping(&oirq); //创建中断映射
-------------irq_create_fwspec_mapping(&fwspec);
---------------domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED) //找到gic对应的domain
---------------irq_domain_translate(domain, fwspec, &hwirq, &type)
-----------------d->ops->translate(d, fwspec, hwirq, type); //将设备树中的hwirq,type读取出来
---------------virq = irq_find_mapping(domain, hwirq); //检查hwirq是否已经映射, 如果virq非0就直接返回
---------------if (irq_domain_is_hierarchy(domain))
//分配irq_desc,返回未占用的virq, 并用irq_domain->ops->alloc函数设置irq_desc
-----------------virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
---------------else
//分配irq_desc,返回未占用的virq, 并通过irq_domain_associate调用irq_domain->ops->map设置irq_desc
-----------------virq = irq_create_mapping(domain, hwirq);
7.gic_handle_irq
gic_handle_irq(struct pt_regs *regs)
--irqnr = gic_read_iar(); //读gic寄存器确定发生了哪个中断
/android/vendor/amlogic/common/kernel/common_5.4/kernel/irq/irqdesc.c
----handle_domain_irq(gic_data.domain, irqnr, regs)
------__handle_domain_irq(domain, hwirq, true, regs);
--------irq = irq_find_mapping(domain, hwirq); //找到virq
--------generic_handle_irq(irq);
----------generic_handle_irq_desc(desc);
------------desc->handle_irq(desc); //处理下一级中断处理函数
3)GICv3-ITS模块初始化
1. ITS模块设备树:
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
2.ITS模块驱动代码
drivers\irqchip\irq-gic-v3-its.c
3.PCI MSI与ITS驱动一致
drivers\irqchip\irq-gic-v3-its-pci-msi.c`,它只是在ITS下面再增加了一个处理层:
3)基于SPI中断的GPIO中断控制器驱动实现
以GPIO中断控制器为例,看看第二级中断控制驱动的实现
1、GIC连接与分配方式

1)通常外设与GIC的连接有如上两种方式,分别为chained intc (一对多关系,类似共享GIC中断)和hierarchy intc(连续的一对一关系,类似独享GIC中断);
2)两种不同的连接方式,虽然都是为了响应中断,但逻辑上有差异,以致于内核使用不同的函数处理:
1、设备树的表达不同 - 解析设备树的函数也就不同;
2、响应中断后的处理流程也不同,chained方式比hierarchy方式多处理一个步骤,chained还需要分辨是哪个中断号,并找到对应的处理函数;
2、设备树实现
1、gic中断控制器对应的设备树
intc: interrupt-controller&00a01000 {
compatible = "arm, cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>
}
2、GPIO中断控制器对应的设备树
1)对于chained方式的设备树
gpio1: gpio_intc {
compatible = "arm, gpio controller";
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&intc>;
interrupts = <GIC-SPI 122 IRQ_TYPE_LEVEL_HIGH>;
}
2)对于hierarchy方式的设备树
gpio1: gpio_intc {
compatible = "arm, gpio controller";
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&intc>;
upper_hwirq_base = <122>;
}
3、gpio-key设备对应的设备树
gpio-key {
compatible = "arm, gpio_key";
interrupt-parent = <&gpio1>;
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
<1 IRQ_TYPE_LEVEL_HIGH>,
<2 IRQ_TYPE_LEVEL_HIGH>,
<3 IRQ_TYPE_LEVEL_HIGH>,
}
3、chain方式的驱动实现
static struct irq_domain *gpio_intc_domain;
static void gpio_intc_irq_handler(struct irq_desc *desc)
{
/* 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
int hwirq;
struct irq_chip *chip = irq_desc_get_chip(desc);
chained_irq_enter(chip, desc);
/* a. 分辨中断 */
hwirq = gpio_intc_get_hwirq();
/* b. 调用irq_desc[].handle_irq(handleC) */
generic_handle_irq(irq_find_mapping(virtual_intc_domain, hwirq));
chained_irq_exit(chip, desc);
}
static struct irq_chip gpio_intc_irq_chip = {
.name = "gpio_intc",
.irq_mask = virtual_intc_irq_mask ,
.irq_unmask = virtual_intc_irq_unmask ,
};
static int gpio_intc_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
1. 给virq提供处理函数
irq_set_chip_data(virq, h->host_data);
2. 提供irq_chip用来mask/unmask中断
irq_set_chip_and_handler(virq, &gpio_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC
return 0;
}
static const struct irq_domain_ops gpio_intc_domain_ops = {
.xlate = irq_domain_xlate_onetwocell, //解析设备树
.map = gpio_intc_irq_map,
};
static int gpio_intc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int irq_to_parent;
/* 1. virutal intc 会向GIC发出n号中断 */
/* 1.1 从设备树里获得virq_n */
irq_to_parent = platform_get_irq(pdev, 0);
/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
irq_set_chained_handler_and_data(irq_to_parent, gpio_intc_irq_handler, NULL);
/* Usage:
* a. dts: 定义使用哪个hwirq
* b. 内核解析设备树时分配irq_desc,得到virq
* c. (hwirq, virq) ==>存入domain
*/
gpio_intc_domain = irq_domain_add_linear(np, 4,
&gpio_intc_domain_ops, NULL);
return 0;
}
4、hierarchy方式的驱动实现
static struct irq_domain *gpio_intc_domain;
static u32 upper_hwirq_base;
static struct irq_chip gpio_intc_irq_chip = {
.name = "gpio_intc",
.irq_mask = virtual_intc_irq_mask ,
.irq_unmask = virtual_intc_irq_unmask ,
};
static int gpio_intc_domain_alloc(struct irq_domain *domain,
unsigned int irq,
unsigned int nr_irqs, void *data)
{
struct irq_fwspec *fwspec = data;
struct irq_fwspec parent_fwspec;
irq_hw_number_t hwirq;
int i;
/* 设置irq_desc[irq] */
/* 1. 设置irq_desc[irq].irq_data, 里面含有virtual_intc irq_chip */
hwirq = fwspec->param[0];
for (i = 0; i < nr_irqs; i++)
irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
&gpio_intc_irq_chip, NULL);
/* 2. 设置irq_desc[irq].handle_irq, 来自GIC */
parent_fwspec.fwnode = domain->parent->fwnode;
parent_fwspec.param_count = 3;
parent_fwspec.param[0] = 0; //GIC_SPI;
parent_fwspec.param[1] = fwspec->param[0] + upper_hwirq_base;
parent_fwspec.param[2] = fwspec->param[1];
return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
&parent_fwspec);
}
static int gpio_intc_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (is_of_node(fwspec->fwnode)) {
if (fwspec->param_count != 2)
return -EINVAL;
*hwirq = fwspec->param[0];
*type = fwspec->param[1];
return 0;
}
return -EINVAL;
}
static const struct irq_domain_ops gpio_intc_domain_ops = {
.translate = gpio_intc_domain_translate,
.alloc = gpio_intc_domain_alloc,
};
static int gpio_intc_probe(struct platform_device *pdev)
{
struct irq_domain *parent_domain;
struct device_node *parent;
of_property_read_u32(pdev->dev.of_node, "upper_hwirq_base", &upper_hwirq_base);
parent = of_irq_find_parent(pdev->dev.of_node);
parent_domain = irq_find_host(parent);
/* 分配/设置/注册irq_domain */
virtual_intc_domain = irq_domain_add_hierarchy(parent_domain, 0, 4,
pdev->dev.of_node, &gpio_intc_domain_ops,
NULL);
return 0;
}
5、chained方式处理流程

1、对于KEY,注册中断时就是:request_irq(102, ...)
2、对于GPIO模块中0~3这四个hwirq,分配四个irq_desc
-
可以使用老方法一下子分配4个:irq_domain_add_legacy()
-
也可以使用新方法用到时再分配:irq_domain_add_linear()
6、hierarchy方式处理流程

7、chained与hierarchy响应中断的流程对比

4)基于MSI机制的PCIe控制器驱动实现
PCIe在演进过程中有三种中断方式:INTx(基于TLP消息的虚拟中断)、MSI\MSI-X(基于MSI消息的虚拟中断)
1、使用MSI/MSI-X机制的PCIe系统

1)PCIe控制器中主要用到2个函数来使用GIC的MSI中断机制:
c
int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec);
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec);
2、驱动代码
1)设备树
1.GIC-ITS设备树节点
gic: interrupt-controller@fee00000 {
compatible = "arm,gic-v3";
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
}
2.PCIe控制器设备树节点
pcie0: pcie@f8000000 {
compatible = "rockchip,rk3399-pcie";
#address-cells = <3>;
#size-cells = <2>;
aspm-no-l0s;
clocks = <&cru ACLK_PCIE>, <&cru ACLK_PERF_PCIE>,
<&cru PCLK_PCIE>, <&cru SCLK_PCIE_PM>;
clock-names = "aclk", "aclk-perf",
"hclk", "pm";
bus-range = <0x0 0x1f>;
max-link-speed = <1>;
linux,pci-domain = <0>;
1.把PCIe设备映射到MSI控制器,格式<rid-base &msi-controller msi-base length>;
msi-map = <0x0 &its 0x0 0x1000>;
2.msi-map格式说明
* rid-base:第1个Request ID,就是使用<bus, dev, function>组成的一个数字
* msi-controller:这个PCIe设备映射到哪个MSI控制器?
* msi-base:第1个PCIe设备映射到MSI控制器哪个中断?
* length:能映射多少个设备
2)驱动代码
c
1.
drivers\nvme\host\pci.c
nvme_probe > nvme_probe_work > nvme_setup_io_queues
pci_enable_msix_range
pci_enable_msix(dev, entries, nvec);
msix_capability_init(dev, entries, nvec); //调用栈很深
pci_enable_msi_range
msi_capability_init(dev, nvec);
2.
// request_irq: 中断号都保存在dev->entry[i].vector里
for (i = 0; i < vecs; i++)
request_irq(dev->entry[i].vector, ...);
注意 ,在pci_enable_msix_range或者pci_enable_msi_range函数中:
- minvec从1开始
- 对于pci_enable_msix_range,中断号保存在entries[i].vector里
- 对于pci_enable_msi_range,第1个中断号保存在pdev->irq里
5)设备申请中断
复杂的实现内核已经实现,开发者申请中断是相当简单的
1.对于GPIO控制器+GICv2控制器模型,申请gpio口中断
int irq = gpio_to_irq(int gpio);
request_irq(irq, handle_functions, IRQF_TRIGGER_RISING, "name", struct gpio_key);
2.对于PCIe控制+GICv3控制器模型,PCIe设备申请LPI中断,在PCIe控制器中完成申请
5、中断软件处理机制-响应中断
preface
1、无论在何种架构,何种内核,都必须遵循中断处理函数不能处理耗时任务;
1)因此我们统一使用Linux的概念上半部(中断处理函数)、下半部(执行耗时任务)来描述;
2、为什么不能在中断处理耗时的工作,如果过长会怎么样?
1、系统变卡、响应迟缓;
2、硬件数据丢失;
3、看门狗复位;
5、系统完全死锁;
1)裸机的中断处理机制
1、因为没有内核提供的成熟机制,只能自行实现下半部;
2、一般使用标志位,比如在中断处理函数中设置Ture,然后延迟到下一次主循环中处理判断True后执行相应逻辑,执行完后重新设为False;
int flag_handle = False;
void irq_handle(void){
//关中断
flag_handle = True;
//开中段
}
void main(void) {
if (flag_handle = True) {
//处理耗时工作
flag_handle = False;
}
}
2)RTOS的中断处理机制
1、freeRTOS中断管理: https://zhuanlan.zhihu.com/p/439113270
2、RTOS的中断处理机制
保存现场:Task1被打断,需要先保存Task1的运行环境,比如各类寄存器的值
分辨中断、调用处理函数(这个函数就被称为ISR,interrupt service routine)
恢复现场:继续运行Task1,或者运行其他优先级更高的任务
3、下半部实现
由于ISR中处理必须尽可能快,因此可以使用内核提供的机制来进行耗时工作,ISR中只做触发动作,也就是上半部(ISR)和下半部(使用任务来完成),在ISR中使用带ISR的内核方法来触发下半部的处理

3)Linux的中断处理机制
1、Linux中的上半部和下半部

2、下半部一般使用中断下半部 tasklet / work_queue工作队列 机制;
6、共享中断-扩展中断手段
随着系统功能需求的复杂化,系统所需要的中断资源也在线性扩展,硬件所支持的中断资源有限(即使现在还能满足一段时间,说不定很快就会被消耗完),硬件资源成本也相对昂贵?那怎么处理?
我们不难发现,采用虚拟化技术来扩展硬件资源(虚拟机扩展CPU资源、虚拟内存扩展内存资源)是一个有效的途径;中断也一样,可以通过"软件虚拟中断"手法来扩充硬件中断资源,但实现相较于虚拟机和虚拟内容简单得多;
共享中断就是"虚拟"的一种手段,有如下两种形式
1) 多级中断控制器的扩展
GPIO控制器 共享 GIC中断

1、GPIO控制器 共享 GIC中断
2、GIC中断发生时,进入GPIO中断处理函数,读取GPIO寄存器确定是哪一路发生中断,再调用该路中断的处理函数;
2)软件对硬件的扩展
外部设备共享GPIO中断

1、对于GPIO中断,需要注册多个处理函数,当中断发生时,会调用所有的中断处理函数,每个中断处理函数与外设交互,确定是否自己的中断;
7、中断子系统调试
1.查看中断的统计信息
cat /proc/interrupts
2.中断节点
cat /sys/kernel/interrupt
8、参考
1)arm64体系结构编程与实践
2)Linux设备驱动开发详解:基于最新的Linux 4.0内核
3)韦东山-interrupt子系统
4)Linux内核源码