目录
综述
硬件层--GICV3
中断类型
中断状态
Distributor组件
中断使能配置
中断触发方式配置
中断优先级配置
中断分组标记
GIC处理中断流程
综述
由上面的block图,我们可知linux kernel的中断子系统分成4个部分:
- 硬件层:最下层为硬件连接层,对应的是具体的外设与SoC的物理连接,中断信号是从外设到中断控制器,由中断控制器统一管理,再路由到处理器上;
- 硬件相关层:这个层包括两部分代码,一部分是架构相关的,比如ARM64处理器处理中断相关,另一部分是中断控制器的驱动代码;
- 通用层:这部分也可以认为是框架层,是硬件无关层,这部分代码在所有硬件平台上是通用的;
- 用户层:这部分也就是中断的使用者了,主要是各类设备驱动,通过中断相关接口来进行申请和注册,最终在外设触发中断时,进行相应的回调处理;
硬件层--GICV3
gic的核心功能,就是对soc中外设的中断源的管理,并且提供给软件,配置以及控制这些中断源。
◾distributor:SPI中断的管理,将中断发送给redistributor
◾redistributor:PPI,SGI,LPI中断的管理,将中断发送给cpu interface
◾cpu interface:传输中断给core. (实现在core内部的)
◾ITS (Interrupt Translation Service components ):用来解析LPI中断.
中断类型
- SGI (Software Genrated Interrupt, 0-15):通常用于核间通信,也可称作IPI(Inter-Processor Interrupt)。比如可以使处理器停止工作,唤醒处理器等
- PPI (Private Peripheral Interrupt, 16-31):属于单个PE的私有中断,因此不同PE可以有相同的PPI号,但是对应不同的中断源。例如cpu的定时器中断等
- SPI (Shared Peripheral Interrupt, 32-1019):所有PE共享的中断,或者叫不限定特定的Cpu的中断。常见的按键中断啥的大部分都是这种
- LPI (Locality-specific Peripheral Interrupt, irqnr > 8192):GICv3新增,可以通过读/写寄存器的方式发送/接收中断。PI是一种基于消息的边沿中断。也就是,中断信息,不在通过中断线,进行传递,而是通过memory。gic内部,提供一个寄存器,当外设往这个地址,写入数据时,就往gic发送了一个中断。在soc系统中,外设想要发送中断给gic,是需要一根中断线的。如果现在一个外设,需要增加一个中断,那么就要增加一根中断线,然后连接到gic。这样,就需要修改设计。而引入了LPI之后,当外设需要增加中断,只需要使用LPI方式,传输中断即可,不需要修改soc设计。
应用场景:
- IPI:IPI_WAKEUP以手机为例,屏幕是一个重要的用户交互界面,用户通过触摸、滑动等方式与屏幕进行交互。为了保证屏幕的响应速度和用户体验,需要在用户按下电源键时尽快唤醒屏幕。但是,在手机中,为了节省电量和延长电池寿命,cpu核心通常会进入睡眠状态,以降低功耗。因此在用户按下电源键时,需要唤醒处于睡眠状态的cpu核心来处理这个事件。在多个核的系统中,一个cpu核心需要唤醒其他核心时,可以使用IPI_WAKEUP中断,具体的说,当用户按下电源键时,手机会向内核发送该中断请求,内核会将这个请求转发给处于睡眠状态的cpu核心,该核心会执行相应的处理程序,初始化显卡、显示器和输入设备等,最终将屏幕的内容显示出来。
中断状态
- Inactive:当前未声明中断源,无中断状态;
- Pending:硬件或软件触发了中断,中断源已被置位,但尚未传递到目标CPU,在电平触发模式下,产生中断的同时保持pending状态;
- Active:中断已被 CPU 确认;
- Active and pending:;对于边沿触发方式,表示一个中断正在处理,又触发一个新中断
并发生了抢占(GIC角度的抢占,只要又中断在处理,又发中断信号给CPU就算抢占),则原中断就会变成AP状态。
Distributor组件
Distributor主要包含两部分功能:中断属性配置,和将中断路由到下一步的GIC组件cpu interface
以下将分别描述Distributor可以执行的重要中断属性设置。
中断使能配置
GICv3可以设置特定中断号对应的中断是否被使能,若未设置使能状态则该中断不会被发送到CPU interface。中断的使能可以通过GICD_ISENABLER<n>寄存器设置(n为0 - 32),中断的失能可以使用GICD_ICENABLER<n>寄存器设置,这两个寄存器都为32 bit寄存器,每个中断使用一个bit控制其使能 / 失能状态。其寄存器定义如下:
中断触发方式配置
中断的触发方式可分为边沿触发(上升沿、下降沿)和电平触发(高电平、低电平),两种触发方式的行为有所不同。
触发方式 | 特点 |
边沿触发 | 在检测到上升沿或下降沿后触发中断,软件应答该中断后,中断即为active状态 |
电平触发 | 在检测到特定电平后触发中断,之后软件必须再次deassert,中断才会进入active状态(防止反复触发) (后面中断注册代码中会看到) |
SPI的中断触发方式可用GICD_ICFGR<n>寄存器设置(n为0 - 63),它是一个32bit寄存器,用两个bit表示一个中断的触发方式,其中bit0为保留位,bit1位0表示电平触发,为1表示边沿触发。其寄存器定义如下:
中断优先级配置
在 gic 中,存在两种类型的优先级,一种是每个中断源都可配置为一个静态的中断优先级(由 distributor 管理),这个优先级可通过软件动态地修改,多个中断同时发生时,GIC选择将优先级最高的发给CPU。另一种是 CPU interface 的 运行优先级,如果已有中断在运行,只有当前中断的优先级高于正在处理的优先级时,中断才会将当前中断发送到 CPU 核,从GIC角度来看,这就是抢占了。也就是在 CPU 正在处理低优先级中断时,再次发送一个高优先级中断,不过发送归发送,具体的实现中 CPU 是不是会受理该新的中断,根据实现而定,比如 linux 中,即使配置 gic 支持抢占,也会因为 linux 处理中断时会关闭中断而禁止中断的抢占。
在 gic 中,优先级由 8 位表示,总共 256 个等级。优先级值的 value 越低,对应的执行优先级越高
ICC_BPRn_EL1
寄存器,该寄存器定义优先级值字段分成两部分的点,即组优先级字段和子优先级字段。 组优先级字段确定组 1 中断抢占。 换句白话来解释就是,中断优先级被分成了两部分,如下图所示,ICC_BPRn_EL1
寄存器的BIT[2:0]定义了下图中的N的值
对于抢占,仅考虑Group优先级位。 Subpriority优先级位被忽略. 然后举一个例子:
INTID A has priority 0x10
INTID B has priority 0x20
INTID C has priority 0x21
如上有三个中断,A可以抢占B,但B不可以抢占C,因为B和C的Group priority是一样的
在 GIC 的初始化阶段就已经为每个外部中断初始化了优先级,在外部中断传递的过程中,优先级和抢占的功能体现在下面的情况中:
首先,distributor 总是会将最高优先级的外部中断传递给 CPU interface,而 CPU interface 会维护当前自己正在处理或者正在 assert CPU IRQ 线的中断优先级。
第一种:CPU interface将中断信号发给cpu,CPU 再通过iar寄存器的读操作获取外部中断 ID.但是,在 CPU 的 IRQ 线被 assert 到 CPU 读取寄存器的过程中,产生了更高优先级的中断,会发生什么? distributor 会将新发生的高优先级的中断传递给 CPU interface,因此执行到 CPU 读取中断 ID 寄存器时,读取的也就是新中断的 ID 号,这种情况下,实现了高优先级中断的优先处理,从 CPU interface 的角度来说,这算是中断抢占,因为旧的中断已经提交,而新的高优先级中断横插一脚,而对于 CPU 来说,这并不算中断抢占,毕竟 CPU 还没有真正开始执行旧的中断,算是合理的优先级排序,毕竟抢占的概念通常用在正在执行的对象上。
第二种:当 CPU 读取中断 ID 寄存器时,同时意味着向 CPU interface应答 了该中断,并开始处理中断,如果在处理过程中产生了更高优先级的中断,此时这个中断在满足以下条件的情况下就会抢占当前的中断:一,GIC配置中断可抢占。二:再次将中断信号发给cpu(当然,这是硬件上表现抢占,但软件上实际并不一定会抢占)
中断分组标记
GICv3支持两种secure状态或单一secure状态,它可以通过配置寄存器GICD_CTRL.DS实现
有三个中断grounp。对于SPI类型的中断,可以通过寄存器GICD_IGROUPR和GICD_IGRPMOD配置其中断group。每种group中断都期望被下表特定的异常等级处理:
gicv3可以触发两种中断信号irq和fiq,对中断分组的目的就是使不同group的中断在不同状态下可以被分别分发到irq或fiq,在aarch64状态下,中断的分发方式如下:
从上表可知:
(1)group 0中断总是以FIQ方式触发
(2)secure group 1中断根据cpu的当前执行状态确定触发方式。若当前执行状态为secure EL0 、EL1,以irq方式触发。否则,以fiq方式触发
(3)non secure group 1中断,若当前执行状态为non secure EL0、EL1、EL2,以irq方式触发。否则,以fiq方式触发
GIC处理中断流程
下面以一个常见的低电平触发的中断为例,下面是一个中断的完整上报流程:
- 外设产生中断,拉低连接到 gic 的中断线,表示中断产生,此时中断位pending状态
- 如果该中断使能,distributor 根据该中断设置的 CPU mask 将中断传递到对应的单个或多个 CPU interface,一般来说,只会将中断传递给一个 CPU 处理,如果为一个中断设置多个 target CPU,中断源的触发会导致多个 CPU 的 IRQ 引脚被拉低从而进入中断模式,实际上又只会有一个 CPU 处理该中断。但是在 GIC 手册中,是支持这种做法的,也就是一个中断源配置多个 target CPU(1-N中断),第一个 CPU ack 了该中断之后,后续的 CPU 查询中断 ID 寄存器返回无效的中断号 1023,退出中断处理,如果存在多个中断,distributor 会将最高优先级的中断发送给 CPU interface。
- CPU interface 决定是否将中断发送给 CPU 核,也就是触发对应 CPU 的 irq 引脚,取决于中断传递是否使能以及中断是否有足够的优先级,如果 CPU 当前没有正在处理中断,自然是可以传递的,如果 CPU 正在处理中断,那就看新中断的优先级是否更高同时还取决于是否配置了中断的抢占。
- 在 arm linux 中,CPU interface 将中断信号传递给 CPU,实际上就是 assert CPU 的 IRQ 中断线,如果 CPU 没有屏蔽 irq 中断线,就会跳转到中断模式,处理中断,经过一系列的现场保存工作等准备工作之后,CPU 会读取 GICC_IAR 寄存器获取中断 ID,读这个寄存器也意味着 CPU ack 了该中断,因为是电平触发的模式,该中断在 gic 中记录的状态切换为 active and pending,因为即使 CPU ack 了中断,当前中断线的电平并没有被拉高,还是处于触发状态。 如果是边沿触发的中断,在 CPU ack 了之后,就会切换为 active 状态。
- 系统的中断代码会调用到用户自定义的中断处理程序,在自定义处理程序中,用户需要将该外部中断 deassert,也就是将中断线重新置为高,该 gic 的状态也就从 active and peding 切换为 active,如果没有这一个步骤,中断会被反复触发。
- 中断处理完成后(可能只是上半部处理结束),软件可以写寄存器ICC_EOIR0_EL1 / ICC_EOIR1_EL1以执行中断的优先级下降和deactive操作。中断deactive之后,将会变为inactive状态。此处重置优先级的原因:cpu在响应优先级为4的中断A,当中断A的上半部分完成后,通知GIC,优先级重置(drop priority),GIC将当前的最高优先级中断重置,重置到响应中断A之前的优先级,比如优先级6(中断优先级值越小,则优先级越高),那么此时优先级为5的中断B,就可以被cpu响应。最后中断A的下半部分完成后,通知GIC,将该中断A的状态,设置为inactive状态,此时中断A就真正的完成了。
- linux 内核中会重新开启中断(操作 CPSR 的 i bit???),gic 也就可以重新发送其它的 pending 中断到 CPU 上了。
这是一个 SPI 中断的处理流程,对于 SGI 来说,不同之处在于:
- 中断是软件触发的,通过向对应的寄存器中写入需要触发的中断号(0-15)以及对应的 CPU ID。
- distributor 并不再需要通过读取 CPU mask 寄存器来决定该中断需要发送给哪个 CPU,SGI 的 target CPU 在触发中断时已经决定了。
- SGI 中断可以发送给多个 CPU,而且经常作为 CPU 之间通信的方式,各个 CPU 之间处理 SGI 中断是独立的,比如对于 8 号 SGI 中断,每个 CPU 都可以独立处理,而且在处理的过程中,每个 CPU 对应该中断的中断状态也是独立的。