该博客主要为个人学习,通过阅读官网手册整理而来(个人觉得阅读官网的英文文档非常有助于理解各个IP特性)。若有不对之处请参考参考文档,以官网参考文档为准。
Arm Generic Interrupt Controller v3 and v4学习一共分为三章,这是第一章
中断是发送给处理器的一个信号,表明已经发生了需要处理的事件。中断通常是由外围设备产生的。
小型系统可能只有几个中断源和一个处理器。然而,较大的系统可能有更多潜在的中断源和处理器。GIC执行中断管理、优先级和路由等关键任务。GIC整理来自整个系统的所有中断,对它们设置优先级,并将它们发送到一个core进行处理。GIC主要用于提高处理器效率和启用中断虚拟化。
GIC是基于Arm GIC架构实现的。这个体系结构已经从GICv1发展到最新版本的GICv3和GICv4。Arm有几个通用的中断控制器,它们为所有类型的Arm Coretex多核处理器系统提供了一系列的中断管理解决方案。这些控制器的范围从具有具有小CPU内核数量的系统的最简单的GIC-400到具有高性能和多芯片系统的GIC-600。GIC-600AE为ASIL D系统增加了针对高性能ASIL B的额外安全特性。
本章涵盖了GICv3和v4的基本操作,以及共享外设中断(SPIs)、私有外设中断(PPIs)和软件生成中断(SGIs)的使用。
GICv3和GICv4允许几种不同的配置和用例。为简单起见,本章集中介绍这些配置和用例中的一个子集,其中:
通用中断控制器(GIC)从外设中获取中断,对它们进行优先排序,并将它们传递到适当的处理器core。下图显示了一个GIC从n个不同的外设获取中断,并将它们分发到两个不同的处理器。
GIC是Arm Cortex-A和Arm Cortex-R配置文件处理器的标准中断控制器。GIC提供了一种灵活和可扩展的方法来中断管理,支持具有单核的系统到具有数百个核的大型多芯片设计。
与Arm架构一样,GIC架构也随着时间的推移而发展。下表总结了GIC规范的主要版本以及它们通常一起使用的处理器。
本文章涵盖了大多数Armv9-A、Armv8-A和Armv8-R设计中使用的Arm CoreLink GICv3和GICv4。
自发布后,GICv3和GICv4也有一些小的更新。
GICv2m是对GICv2的一个扩展,用于添加对消息信号中断的支持。
在本节中,我们将介绍Arm GICv3和v4中断控制器的基本操作。
GIC可以处理四种不同类型的中断源:
LPI配置详见:Locality-Specific Peripheral Interrupts Arm Generic Interrupt Controller v3 and v4
每个中断源都由一个ID号标识,被称为INTID。前面列表中介绍的中断类型是根据中断的范围定义的:
GIC体系结构为特殊目的保留的中断号如下:
传统上,系统使用专用硬件信号从外围设备到中断控制器发出中断信号,如下图所示:
Arm CoreLink GICv3支持这个模型,但也提供了一个额外的信号机制:消息信号中断(message-signaled interrupts,MSI)。MSI通过写入传输到中断控制器中的一个寄存器,如下图所示:
使用消息将中断从外围设备转发到中断控制器,消除了对每个中断源的专用信号的要求。这对于大型系统的设计者来说可能是一个优势,在那里,数百甚至数千个信号可能通过SoC路由并收敛到中断控制器上。
无论中断是作为消息发送的,还是使用专用信号,对中断处理代码处理都没有什么影响。可能需要一些外设的配置。例如,可能需要指定中断控制器的地址。
在Arm CoreLink GICv3中,SPI可以是发出消息信号的中断。LPI总是具有消息信号的中断。不同的中断类型用于不同的寄存器,如下表所示:
中断控制器为每个SPI、PPI和SGI中断源维护一个状态机。此状态机包括以下四种状态:
中断的生命周期取决于它是被配置为电平敏感还是边缘触发:
Inactive to Pending: 中断源置起,此时 GIC 置起中断信号到 PE(前提是中断使能并且优先级够)
Pending to Active: PE 认可中断(读IAR寄存器),中断从Pending状态转换为Active状态。此读取通常是在发生中断异常后执行的中断处理例程的一部分。然而,软件也可以轮询IAR。此时,此时,GIC取消断言(de-asserts)中断信号。
Active to Active and Pending: 外设再次置起中断信号
Active and Pending to Pending: PE完成上一个中断处理(CPU interface写EOIR寄存器),GIC再次置起中断信号到PE。
下图显示了GIC-400如何处理两个具有不同优先级的物理中断。在本示例中为中断N和M(M优先级低于N):
下面开始解释该图:
Distributor需要几个周期来计算未决中断的最高优先级。如果在计算进行时中断,它只会影响下一次计算的结果。这意味着中断延迟可能会发生变化。因此,虽然tph通常是12个周期,但它通常可能在10到20个周期之间。
上图中的时间仅供说明。它们是典型的值,不能得到保证,也不能被依赖。
Arm架构为每个PE分配了一个称为亲和性(Affinity)的层次结构标识符。GIC使用亲和性值(Affinity values)来定位特定core上的中断。
Affinity是一个32位的值,它被分为四个字段:
<affinity level 3>.<affinity level 2>.<affinity level 1>.<affinity level 0>
在Affinity级别0处,有一个重新分配器(Redistributor)。每个Redistributor都连接到单个CPU interface。Redistributor控制SGI、PPI和LPI。
Affinity方案在ARMv8-A中,每个PE的affinity存储在MPIDR_EL1寄存器中。系统设计人员必须确保MPIDR_EL1指示的Affinity value与连接到PE的Redistributor中的GICR_TYPER寄存器指示的Affinity value相同。
不同Affinity级别的确切含义是由特定的处理器和SoC来定义的。例子如下:
<group of groups>. <group of processors>.<processor>.<core>
<group of processors>.<processor>.<core>.<thread>
所有可能的节点都不太可能存在于单个实现中。例如,移动设备的SoC可以有类似的布局:
0.0.0.[0:3] Cores 0 to 3 of a Cortex-A53 processor
0.0.1.[0:1] Cores 0 to 1 of a Cortex-A57 processor
在ARMv8-A中,AArch64支持4层Affinity路由。但是AArch32和ARMv7只支持3层中断路由(0, x, y, z)。GICD_TYPER.A3V表示中断控制器是否支持level 3。为1表示支持Level 3。
尽管level 0理论最大可以支持256个PE,但是实际上最大可支持16个或者更少。因为PE要适应SGI的编码。
Arm GICv3架构支持Arm TrustZone技术。每个INTID必须由软件分配一个组(group)和安全(security)设置。GICv3支持三种设置组合,如下表所示:
Group 0的中断总是被标记为FIQ。group 1的中断被标记为IRQ或FIQ,这取决于PE的当前安全状态和异常级别,您可以在这里看到:
Group0/EL3总是FIQ,Seure切换(Secure到Non-secure,Non-secure到Secure)为FIQ
这些规则旨在补充AArch64安全状态和异常级路由控制。下图显示了一个简化的软件堆栈,以及当在EL0执行时发出不同类型的中断信号时会发生什么:
在本例中,IRQ被路由到EL1 (SCR_EL3.IRQ0)和FIQ路由到EL3 (SCR_EL3.FIQ1) .考虑到上述规则,当在EL1或EL0处执行时,针对当前安全状态的Group 1中断被视为IRQ。
控制PE状态切换其实由三个因素决定:
- 中断产生的时候中断类型,中断类型的意思是中断期望在哪一个状态下得到处理。如上图左上角Non-secure Group1,表示该中断希望在Non-secure的状态下得到处理。Secure Group1,表示该中断希望在Secure状态下得到处理(这里特指Secure EL1状态下得到处理)。Group0表示表示该中断希望在EL3状态下得到处理。
2.目前PE所处的状态,是在Non-secure还是Secure状态下或者是EL3状态
3.SCR寄存器中FIQ和IRQ位,这两位分别控制中断发出来的行为。如果FIQ等于1,表示不管发出来是什么,直接切换到EL3。如果FIQ等于0,那么如果发出来的中断是FIQ,那么会去找一个最适合处于该请求的状态,然后再切换到该状态下。同理IRQ也是一样的,如果发出来是IRQ(如左上角第一个信号),如果SCR_EL3.IRQ等于1,则只要发出请求是IRQ,直接切换到EL3处理。如果SCR_EL3.IRQ等于0,则会去找一个最适合处于该请求的状态,然后再切换到该状态下进行处理。
现在,分析该图:
◦ 左上角Non-secure Group1:该中断希望自己在Non-secure状态下得到处理,并且现在PE处于Non-secure状态。按照之前理论,不需要EL3介入。因此IRQ中断发出来之后,由于SCR_EL3.IRQ等于0,PE会找一个可以对IRQ进行处理的状态,然后切换到该状态。显然,因为PE是Non-secure状态,在Rich OS kernel的状态下就可以对该状态处理。(如果SCR_EL3.IRQ等于1,直接切换到EL3处理。)
当实现Armv9-A领域管理扩展(Realm Management Extensions, RME)时,GIC将领域状态视为Non-secure state的扩展。
在配置中断控制器时,软件控制对中断组(interrupt groups)的分配。只有在安全状态(Secure state)下执行的软件才能分配INTIDs来中断组。
通常,只有在安全状态下(Secure state)执行的软件才应该能够访问安全中断(Secure interrupts)的设置和状态:Group 0和Secure Group 1。
也可以使能从非安全状态到安全中断设置和状态的访问。这使用GICD_NSACRn和GICR_NSACR寄存器对每个INTID进行单独控制。
LPIs总是被视为Non-secure Group 1中断
GICv3支持Arm TrustZone技术,但使用TrustZone是可选的。这意味着您配置为一个安全状态或两个安全状态:
GICv3中断控制器的寄存器接口可分为三组:
Distributor寄存器为Memory-mapped。并且包括了全局设置:影响所有的PE。Distributor提供了以下编程接口:
◦ Interrupt prioritization and distribution of SPIs.SPI中断优先级和分发
◦ Enabling and disabling SPIs.
◦ Setting the priority level of each SPI.
◦ Routing information for each SPI.(与GICR最大差别)
◦ Setting each SPI to be level-sensitive or edge-triggered.
◦ Generating message-based SPIs.
◦ Controlling the active and pending state of SPIs.
◦ Controls to determine the programmers’model that is used in each Security state (affinity routing or legacy).设置GIC为GICv2还是GICv3(ARE)
每个Redistributor连接一个PE。Redistributors提供了以下编程接口:
◦ Enabling and disabling SGIs and PPIs.
◦ Setting the priority level of SGIs and PPIs.
◦ Setting each PPI to be level-sensitive or edge-triggered. SGI默认为边缘触发
◦ Assigning each SGI and PPI to an interrupt group. group: group0, security group1, non-security group1
◦ Controlling the state of SGIs and PPIs.
◦ Base address control for the data structures in memory that support the associated interrupt properties and pending state for LPIs.控制内存中支持相关中断属性和LPI的挂起状态的数据结构的基本地址
◦ Power management support for the connected PE
每个Redistributor连接一个CPU interface。CPU interface提供了以下编程接口:
◦ General control and configuration to enable interrupt handling.
◦ Acknowledging(应答) an interrupt.
◦ Performing(执行) a priority drop(恢复) and deactivation(下拉) of interrupts.
◦ Setting an interrupt priority mask for the PE.
◦ Defining the preemption policy(抢占策略) for the PE.
◦ Determining the highest priority pending interrupt for the PE.
GICv3中CPU interface register是以System registers(ICC_*_ELn)访问。
在使用这些寄存器前,软件必须使能 System register interface. ICC_SRE_ELn.SRE ==1 ,n表示Exception Level(EL1-EL3)
GICv1和GICv2中CPU interface register是以memory mapped(GICC_*)访问。
对于PE而言,可以通过读取ID_AA64PFR0_EL1来判定是否支持GIC system register
该节讲述SPI,PPI和SGI配置。
打开Distributor control register(GICD_CTLR)中断组(interrupt groups)和路由模型(routing mode)
Secure和Non-Secure模式下都可以设置
每个core都有自己的Redistributor,如下图所示
Redistributor包含一个名为GICR_WAKER的寄存器,它用于记录所连接的PE是online/wake-up还是offline/sleeping。中断只被转发到GIC认为是在线的PE。
reset时,Redistributor认为连接PE是睡眠模式(sleeping)。需要使用GICR_WAKER寄存器唤醒(Wake-up)
软件在配置CPU接口之前执行上述步骤是很重要的,否则行为可能是不可预测的。
写CPU interface寄存器时,除了ICC_SRE_ELn(4.1.10.3节)。当GICR_WAKER.ProcessorSleep == 1或者GICR_WAKER.ChildrenAsleep == 1时,会导致不可预知情况发生。
当PE离线时(GICR_WAKER.ProcessorSleep ==1),一个针对PE的中断将导致一个唤醒请求信号被断言。通常,这个信号将转到系统的电源控制器。然后,电源控制器打开PE。在醒来时,该PE上的软件会清除进程或睡眠位,允许唤醒PE的中断被转发。
CPU接口负责传递中断到连接的PE。若要启用CPU接口,软件必须配置以下内容
许多最近的Arm Cortex处理器不支持之前版本操作,并且SRE位被固定下来。在这些处理器上,可以跳过此步骤。
这里会在End of interrupt章节描述
一些PE的配置也需要允许接收和处理中断。这里只描述在AArch64状态下,ARMv8-A兼容的PE执行的基本步骤
可以采用PE的SCR_EL3和HCR_EL2控制中断路由。路由控制位决定了该中断在哪个Exception Level中处理。这些寄存器的路由位(routing bits)必须采用软件初始化,因为在reset状态下处于Unknown值
PE在PSTATE也有异常屏蔽位。当设置这些位,中断会被屏蔽。当reset时,这些位会被设置,也就是说不响应外部中断
通过设置VBAR_ELn寄存器设置PE的Vector table位置。
在reset时,SCR_EL3和HCR_EL2,VBAR_ELn寄存器也是处于UNKNOWN值。需要通过软件设置VBAR_ELn寄存器指向memory中的合适Vector tables
PE要去处理一个中断,首先要开打路由控制,然后打开中断响应,最后准备好Vector table。通过Vector table进行中断处理。
到目前为止,我们已经研究了配置中断控制器本身。现在,我们将讨论各个中断源的配置。
对于任意一个INTID,软件必须配置以下信息:
每个INTID都有相应的优先级,使用8-bit unsigned value表示。0x00表示最高优先级,0xff表示最低优先级。GICD_IPRIORITYn 和GICR_IPRIORTYn 的设置描述了怎样屏蔽低优先级和控制抢占
实际上,中断控制器不需要将8bit用满。如果GIC支持两种Security状态(GICD_CTLR.DS==0),则最少需要5bit表示优先级。如果GIC支持一种Security状态,则最少需要4bit表示优先级。
中断分组:GROUP0,Secure Group1 和 Non-secure Group1
每个INTID都有这样的使能位。Set-enable寄存器和Clear-enable寄存器。
这里如果只采用一个寄存器就会涉及到read-modify-write,会影响到其他位的中断
对于bare metal(裸机)环境,在初始配置后通常无需更改设置。但是,如果必须重新配置一个中断,例如要更改Group设置,则应首先disable该中断,然后再更改其配置。
大多数配置寄存器的重置值都由定义实现(IMPLEMENTATION DEFINED)。这意味着中断控制器的设计者决定值是什么,并且值可能在系统之间有所不同。
Arm GICv3.1增加了对extended SPI和PPI INTIDs的支持。配置这些中断的寄存器与原来的中断范围相同,只是它们有一个E后缀。例如:
对于SPI,其中断的目标必须通过GICD_IROUTERn寄存器额外配置。每个SPI都有一个GICD_IROUTERn寄存器,并且由Interrupt_Routing_Mode位来控制路由策略。
每个PE都有对应的Redistributor。每个Redistributor都有GICR_CTLR寄存器。如果一个PE不想接受1-of-N中断类型,那么可以通过GICR_CTLR中的DPG1S,DPG1NS和DPG0位设置
本节描述中断发生时发生的事情:中断如何路由到PE,中断如何相互优先排序,以及在中断结束时发生了什么。
在4.1.3 Interrupt state machine章节描述了在断言中断源时,中断如何从inactive状态过渡到pending状态。当中断处于pending状态时,中断控制器根据以下条件决定将中断发送到已连接的PE之一。
当一个中断变成Pending时,中断控制器决定是否要发送中断到连接的PE上。一个PE是否被中断控制器选中,依赖于以下条件:
当进入异常处理程序时,软件不知道发生了哪个中断。处理程序必须读取其中一个中断确认寄存器(Interrupt Acknowledge Registers,IARs),以获得中断的INTID。
CPU interface有两个IAR寄存器。读取该IAR寄存器就会返回INTID。GIC会自动推进中断状态机:由Pending变成active。在一个典型的中断处理任务里,第一步就是去读IAR中断状态寄存器中的值。然而,软件可以在任何时候读取寄存器的值。
有时,IAR不能返回一个有效的INTID。例如,软件读取ICC_IAR0_EL1,应答 Group 0中断,但Pending的中断Group于Group 1。在这种情况下,读取返回其中一个reserved INTIDs,如下表所示:
当读取IAR,如果存在上述中断,则不承认存在中断。
一个手机系统有一个modem(调制解调器)中断信号,该信号希望在Rich OS(linux系统)Non-secure状态下得到处理
其实FIQ和IRQ本质上在ARMv8架构下已经没有区别了,只不过是软件上人为的分开当作两种不同的应用场景使用。以前ARMv7架构下,FIQ和IRQ是两种不同的执行模式,FIQ有自己的寄存器,可以直接拿来用,但是如果是在IRQ下,有些寄存器不能用,因为已经被其他的任务再用。如果直接拿来用会把别人的数据覆盖掉。所以在IRQ模式下,需要先把别人的状态先保存,然后再去用,用完再恢复。但是在ARMv8架构下,已经没有FIQ,IRQ的执行模式了,只有异常等级(Exception Level)。FIQ没有自己专有的寄存器,Fast便体现不出来,但是F可以理解为Forward
上图展示了,Non-secure Group1中断直接从EL1(Trusted OS)切换到EL3。但是该行为可能不是想要的。下图展示了中断最初在Secure EL1
不同点:
running priority: CPU不处理中断时,running priority表示最低优先级的数值0xff,当PE响应某个中断后,该CPU的running priority表示该中断源的优先级。
PMR用于设置一个中断能够传递到PE的最小优先级。当PE响应到一个中断,则该中断会被赋值到running priority这个寄存器。如果PE向EOI寄存器写值,running priority会恢复到写入之前的值。
running priority被存储在CPU interface的running priority寄存器里(ICC_RPR_EL1)
running priority的概念在抢占的时候比较重要。抢占只有在高优先级向正在处理低优先级的PE发送时候会发生。抢占引入了一些软件的额外复杂性,但是它能阻止低优先级的中断阻塞高优先级中断的处理。
PE去读IAR寄存器导致编码状态数据的变化。那PE什么时候会去读IAR寄存器,很显然,只有PE能看到Pending的状态并且能响应外部中断。这样它才能跳到中断处理函数里,然后才来读IAR寄存器。然后如果PE自动去响应中断,然后跳到中断向量表的过程中,处理器自动把中断响应位关闭。也就是说,在中断处理函数里,还要想要对外部中断进行响应,软件上需要打开该位(中断)操作。PSTAT里I那位要软件清零。在linux看来,不会出现该情况,因此不会去清零。
上图展现了一级抢占。然而,抢占有多级,在一些情况下,有些抢占并不是想要的或者不是必须的。GICv3在抢占的时候允许通过Binary Point register(ICC_BRRn_EL1)定义优先级。Binary Point寄存器将优先级分为两个字段,组优先级和子优先级,如下图所示:
对于抢占来说,只有Group优先级是被考虑的,Subpriority是被忽略的。例如以下三个中断:
在该场景中,A可以抢占B和C,B不能抢占C,因为B和C有相同的优先级。为了实现该目标,可以设置N=4
抢占要求中断处理支持中断嵌套
当中断被处理完,需要软件通知中断控制器任务已被处理完,让状态机切换到下一个状态。
GICv3将该事件分为两个任务
大多数软件将使用EOIMode0。EOImode1最常被管理程序(hypervisors)使用。
当写这些寄存器,软件必须写INTID。
正如它们的名称所示,最高优先级等待中断(Highest Priority Pending Interrupt)寄存器ICC_HPPIR0_EL1和ICC_HPPIR1_EL1报告了一个PE的最高优先级等待中断的INTID。
运行优先级在6.3 Runing priority and preemption节已介绍,并由运行优先级寄存器(ICC_RPR_EL1)报告。
Distributor指示了每个SPI的当前状态的寄存器。同理,Redistributors提供了PPIs和SGIs的状态的寄存器。
这些寄存器提供了将一个中断改变为一个特定的中断。如果在没有一个外设assert一个中断时,测试一个配置是否正确。这会变得非常有用。
这些寄存器可以分为active state和pending state。下表列出了active state。pending state具有相同的格式:
当affinity routing打开时,GICD_ISACTIVER0和GICD_ICACTIVER0的结果将被视作RES0(reserved)。这是因为GICD_ISACTIVER0和GICD_ICACTIVER0表示INTIDs 0到31,属于SGI和PPI
软件执行在Non-secure状态下,是看不到Group0和Secure Group1的中断。除非通过GICD_NASCRn或者GICR_NASCRn
软件生成中断(Software Generated Interrupts ,SGIs)是软件通过写入中断控制器中的一个寄存器来触发的中断。
生成SGI:SGI是通过写入CPU接口中的以下SGI寄存器之一而生成的:
SGI ID字段控制生成的INTID。如Interrupt types章节中所述,INTIDs 0-15用于SGIs。
SGI寄存器中的中断路由模式(Interrupt Routing Mode,IRM)字段控制一个SGI被发送到哪个PE或哪些PEs。有两个选项:
SGIs的安全状态(Security state)和分组(Group)由:
此表假设为GICD_CTLR.DS0.当GICD_CTLR.DS1,标记为(*)的SGIs也会被转发。
在GICv2中,SGI INIIDs由原始PE和目标PE存储。这意味着一个给定的PE可以有相同的SGI INTID,pending最多8次。
在GICv3组中,SGIs只由目标PE存储。这意味着给定的PE只能有一个pending的SGI INTID。
例如,PEs A和B同时将SGI INTID 5发送到PE C,如下图所示:
C将收到多少个中断?
在遗留操作期间,也就是当GICD_CTLR.ARE = 0,SGIs的行为与Arm CoreLink GICv2中相同。