Skip to main content

10. 异常与中断

参考资料:

  • ARM® Cortex™-A Series Programmer’s Guide version4.0
  • ARM® Architecture Reference Manual ARMv7-A and ARMv7-R edition
  • Cortex™-A7 MPCore™Technical Reference Manual Revision: r0p5
  • ARM® Generic Interrupt Controller Architecture Specification Architecture version 2.0
  • 芯片手册《Chapter 3 & Chapter28 : Ultra Secured Digital Host Controller (uSDHC)》。

本章处理器架构的内容主要来自于ARM® Cortex™-A Series Programmer’s Guide version4.0。

10.1 ARM处理器模式和寄存器

​ ARM体系结构是一种基于模式的体系结构。在引入安全扩展之前,它具有7种处理器模式,如上表所示。有六个特权模式和一个非特权用户模式。特权是执行用户(非特权)模式无法完成的某些任务的能力。在用户模式下,对影响整个系统配置的操作存在一些限制,例如,MMU的配置和缓存操作。模式与异常事件相关。

​ TrustZone安全性扩展的引入了两个独立于特权模式和处理器模式的安全状态,并加入监控器模式(monitor mode)进行安全状态和非安全状态的切换。如下图所示:

​ 对于当前实现TrustZone扩展的处理器,系统安全是通过划分设备的所有硬件和软件资源属于安全世界或者非安全世界来实现的。处理器处于非安全状态时,它无法访问为安全状态分配的内存。

​ 在这种情况下,安全监控器充当在这两个世界之间切换的网关。如果实施了安全扩展,则在监视器模式下执行的软件将控制安全和不安全处理器状态之间的转换。

​ ARMv7-A体系结构虚拟化扩展,在现有的特权模式外,还添加了hypervisor mode(Hyp)。虚拟化使多个操作系统可以共存并在同一系统上运行。因此,ARM虚拟化扩展使在同一平台上运行多个操作系统成为可能。下图展示了hypervisor mode。

​ 如果实施了虚拟化扩展,则存在与以前的体系结构不同的特权模型。在非安全状态下,可以有三个特权级别,分别为PL0、PL1和PL2。

  • PL0: PL0是在用户模式下执行的应用程序软件的特权级别。在用户模式下执行的软件称为非特权软件。该软件无法访问该体系结构的某些功能。特别是,它不能更改许多配置设置。在PL0执行的软件只能进行非特权内存访问。
  • PL1 :PL1是指除用户模式和Hyp模式以外的所有模式。通常,操作系统软件在PL1执行,应用程序将在PL0(用户模式)下执行。
  • PL2:hypervisor使用PL2 Hyp mode,该模式可以控制并切换在PL1执行的不同的guest OS。如果实施了虚拟化扩展,则hypervisor将在PL2(Hyp模式)下执行。hypervisor将控制并使多个操作系统能够在同一处理器系统上共存并执行。

​ 下图显示了不同处理器状态下处理器可以处于的模式以及特权级别:

​ 处理器模式和安全状态以及特权等级的关系如下表所示:

​ 这些特权级别与TrustZone安全和普通(非安全)设置是分开的。

​ 特权级别定义了在当前安全状态下访问资源的能力,并不暗含在其他安全状态下访问资源的能力。

​ 通用操作系统(例如Linux)及其应用程序应在非安全状态下运行。安全状态一般由供应商特定的固件或对安全性敏感的软件来运行。一般在安全状态下运行的软件比在非安全状态下运行的软件具有更高的特权。

​ 当前的处理器模式和执行状态包含在当前程序状态寄存器(CPSR)中。可以通过异常或者在特权软件下显式地修改来进入到不同处理器模式。

10.1.1 寄存器

​ ARM体系结构提供了十六个32位通用寄存器(R0-R15)供软件使用0。其中的15个(R0-R14)可用于通用数据存储,而R15是程序计数器,其值随处理器执行指令而改变。显式地写入R15可以更改程序流程。软件还可以访问CPSR和SPSR中保存的先前运行模式的CPSR的副本。

​ 同一个寄存器可能在不同模式下对应物理上不同的位置。只有特定模式下才能访问到这些位置的寄存器。

​ 有些寄存器,不同的工作模式下有自己的副本,当切换到另一个工作模式时,那个工作模式的寄存器副本将被使用,这些寄存器被称为备份寄存器。备份寄存器在物理上使用不同的存储,通常仅在特定模式下才可以访问它们。下图中带阴影标记的寄存器都是备份寄存器。

​ 在所有模式下,“低位寄存器”和R15共享相同的物理存储位置。图3-5显示了某些模式下的某些“高位寄存器”被备份。例如,FIQ模式下使用R8-R12备份寄存器,也就是说,FIQ模式下对其的访问将转到另一个物理存储位置。对于除用户和系统模式以外的所有模式,R13和SPSR都是备份寄存器。

​ 对于备份寄存器,软件通常不会指定要访问哪个模式下的寄存器,这是由当前运行的模式隐含的。例如,访问R13的程序在用户模式下将访问R13_usr,在SVC模式下将访问R13_svc。

​ R13(在所有模式下)是堆栈指针,但是当堆栈操作不需要时,它可以用作通用寄存器。

​ R14(链接寄存器)保存BL分支指令的下一条指令的地址。当它不支持子程序的返回时,它也可以用作通用寄存器。R14_svc,R14_irq,R14_fiq,R14_abt和R14_und同样用于在发生中断和异常时,或者执行转移和链接指令时,备份R15的返回值。

​ R15是程序计数器并保存当前程序地址(实际上,在ARM状态下,它始终指向当前指令之前的八个字节,而在Thumb状态下,它始终指向当前指令之前的四个字节,这是原始ARM1的三级流水线的遗留特性)。在ARM状态下读取R15时,位[1:0]为零,位[31:2]包含PC值。在Thumb状态下,位[0]始终读为零。

​ R0-R14的复位值是不定的。在使用堆栈之前,必须通过引导代码初始化SP(堆栈指针)(针对每种模式)。ARM体系结构过程调用标准(AAPCS)或ARM嵌入式ABI(AEABI)指定了软件应如何使用通用寄存器,以便在不同的工具链或编程语言之间进行互操作。

Hypervisor模式

​ 支持虚拟化扩展的实现在管理程序(Hyp)模式下具有可用的其他寄存器。 Hypervisor模式以PL2特权级别运行。它可以访问自己的R13(SP_hyp)和SPSR副本。它使用用户模式下的链接寄存器存储函数的返回地址,并具有专用寄存器ELR_hyp来存储异常返回地址。 “ Hyp”模式仅在“正常”世界中可用,并提供虚拟化功能。

10.1.2 状态寄存器

​ 程序状态寄存器(CPSR,current programmer status register)包含处理器的状态和一些控制标记位。

​ 条件标记,bits[31:28]

​ 根据指令的执行结果设置,这些标记位是

​ N,bit[31]负数标记位

​ Z,bit[30]零标记位

​ C,bit[29]进位标记位

​ V,bit[28]溢出标记位

​ 这些条件标记位可以在任何模式下读写

​ GE[3:0],bit[19:16]一些SIMD指令使用

​ IT[7:2],bit[15:10]Thumb2指令集的If-then条件指令使用

​ J, bit[24] 处理器是否处于Jazelle状态,和T, bit[5]一起决定执行的指令集

​ E, bit[9] 大小端状态位,0表示小端,1表示大端。

​ Mask bits, bits[8:6]

​ A, bit[8] 异步中止禁止位Asynchronous abort mask bit.

​ I, bit[7] IRQ 禁止位

​ F, bit[6] FIQ 禁止位

​ Q,bit[27]为1的话,表明执行一些指令时出现饱和或者溢出,一般与DSP有关。

​ T, bit[5] Thumb指令位,和J,bit[24]位决定了处理器的指令集,ARM,Thumb,Jazzelle或者ThumbEE

​ M[4:0], bits[4:0]工作模式位,决定了处理器当前处于的工作模式。

​ 处理器可以使用直接写入CPSR模式位来实现模式之间切换。更常见的是,处理器会由于异常事件而自动切换模式。在用户模式下,无法改变处理器模式的PSR位[4:0]来切换模式和A,I和F位来使能或者禁止异步中止、IRQ和FIQ。

10.1.3 协处理器CP15

​ CP15是系统控制协处理器,可控制处理器核的许多功能。它可以包含16个32位主寄存器。对CP15的访问受权限控制,并且在用户模式下并非所有寄存器都可用。CP15寄存器访问指令指定所需的主寄存器,指令中的其他字段用于更精确地定义访问并增加CP15中的物理32位寄存器的数量。CP15中的16个主要寄存器的名称为c0至c15,但通常使用名称来引用。例如,CP15系统控制寄存器称为CP15.SCTLR。

​ 通过从一个通用寄存器(Rt)读取或写入位于CP15内的一组寄存器(CRn)中,可以控制系统架构的某些功能。该指令的Op1,Op2和CRm字段也可以用于选择寄存器或操作。 格式如下所示:

​ 从CP15寄存器读值到ARM寄存器

​ MRC p15, Op1, Rt, CRn, CRm, Op2 ; read a CP15 register into an ARM register

​ 从ARM寄存器写值到CP15寄存器

​ MCR p15, Op1, Rt, CRn, CRm, Op2 ; write a CP15 register from an ARM register

10.1.3.1 System control register (SCTLR)

​ SCTLR是通过CP15访问的寄存器,它控制存储器,系统功能,并提供反映处理器核中实现的功能的状态信息。

​ 系统控制寄存器只能从PL1或更高特权等级访问。

​ 各个位的描述如下:

​ •TE –Thumb异常使能。这可以控制异常进入ARM状态,还是Thumb状态。

​ •NMFI –是否支持不可屏蔽的FIQ(NMFI)支持。

​ •EE =异常字节序。这定义了在异常时的字节序,的CPSR.E位的值。

​ •U –使用对齐模型。

​ •FI – FIQ配置启用。

​ •V –该位选择异常向量表的基地址。

​ •I –指令缓存使能位。

​ •Z –分支预测使能位。

​ •C –缓存使能位。

​ •A –对齐检查使能位。

​ •M –启用MMU。

​ 引导代码序列的一部分通常将是设置CP15:SCTLR系统控制寄存器中的Z位,以启用分支预测功能。操作的代码如下:

MRC p15, 0, r0, c1, c0, 0 ; Read System Control Register configuration data

ORR r0, r0, #(1 << 2) ; Set C bit

ORR r0, r0, #(1 << 12) ; Set I bit

ORR r0, r0, #(1 << 11) ; Set Z bit

MCR p15, 0, r0, c1, c0, 0 ; Write System Control Register configuration data

10.2 异常处理

​ 异常(exception)是指处理器核中止正常的执行流程,并根据异常类型转去执行特定的软件例程(称为异常处理程序)。异常是通常需要采取补救措施或由特权软件更新系统状态以确保系统平稳运行的条件或系统事件。这称为处理异常。处理完异常后,特权软件将为处理器做好准备,以恢复发生异常之前的所有操作。其他体系结构可能会将ARM所谓的异常称为陷阱(traps)或中断(interrupts),但是,在ARM体系结构中,这些术语保留用于特定类型的异常。

​ 所有微处理器都必须响应外部异步事件,例如按下按钮或时钟达到某个值。通常,有专门的硬件可以激活处理器的输入线。这导致处理器核暂时停止当前的程序并执行特殊的特权处理程序例程。处理器核可以响应此类事件的速度可能是系统设计中的关键问题,称为中断等待时间(interrupt latency)。确实,在许多嵌入式系统中,并没有这样的主程序,系统的所有功能都由从中断代码来驱动,为这些中断分配优先级是设计的关键领域。系统不是通过不断地测试不同的标志位以查看是否有要做的事情,而是通过生成中断来通知处理器核,有事情必须要处理。复杂的系统有许多中断源,它们具有不同的优先级,并且支持中断嵌套,其中较高优先级的中断可以中断较低优先级的中断。

​ 在正常程序执行中,程序计数器在地址空间中递增,程序中的分支指令会修改执行流程,例如,函数调用,循环和条件代码。当发生异常时,此预定的执行顺序将中断,并暂时切换到异常处理程序以处理该异常。

​ 除了响应外部中断外,还有许多其他因素可能导致处理器核发生异常,包括外部(例如,复位),来自内存系统的异常终止以及内部(例如MMU生成的异常终止或通过SVC指令进行的OS调用)异常。处理异常会导致CPU核在模式之间切换并将某些寄存器复制到其他寄存器中。

10.2.1 异常的类型

​ ARMv7-A和ARMv7-R体系结构支持多种处理器模式,称为FIQ,IRQ,Supervisor,中止,未定义和系统的六种特权模式以及非特权的用户模式。如果实施了虚拟化扩展和安全扩展,则可以将Hyp模式和Monitor模式添加到列表中。当前模式可以在特权模式下改变,或者在发生异常时自动改变。

​ 非特权用户模式不能直接影响处理器核的异常行为,但是可以使用SVC异常以请求特权服务。这是用户应用程序请求操作系统来完成任务的方式。

​ 发生异常时,内核将保存当前状态和返回地址,进入特定模式,并可能禁用硬件中断。特定的异常程序处理从一个称为该异常的异常向量的固定内存地址开始执行。特权软件可以将异常向量表的起始位置编程到系统寄存器中,并且在获取相应的异常时会自动执行它们。

​ 存在以下异常类型:

​ (1)中断

​ ARMv7-A内核提供两种中断,称为IRQ和FIQ。

​ FIQ的优先级高于IRQ。由于FIQ在向量表中的位置以及FIQ模式下可用的更多的备份寄存器,因此FIQ还具有一些潜在的速度优势。这样可以节省将寄存器保存到堆栈时耗费的时钟周期。这两种异常通常都与处理器核上的输入引脚相关联-外部硬件会触发一条中断请求线,并在当前指令完成执行时引发相应的异常处理(假定不禁用该中断)。

​ FIQ和IRQ都是发给处理器核的物理信号,并且在触发时处理器核的FIQ和IRQ处于打开状态,它将处理相应的异常。几乎在所有系统上,通过使用中断控制器连接各种中断源。中断控制器对中断进行仲裁并确定优先级,然后依次提供串行化的单个信号,然后将其连接到内处理器核核的FIQ或IRQ引脚。

​ 由于IRQ和FIQ中断的发生与在任何给定时间内核所执行的软件都不直接相关,因此将它们分类为异步异常。

​ (2)中止

​ 中止可以在指令预取失败(预取中止)或数据访问失败(数据中止)时生成。它们可以来自外部存储器系统,在存储器访问时给出错误响应(可能表明指定的地址不对应于系统中的实际存储器)。另外,中止可以由内核的内存管理单元(MMU)生成。操作系统可以使用MMU中止来为应用程序动态分配内存。

​ 预取一条指令时,可以在指令流水线中中将其标记为已中止。仅当内核尝试执行它时,才导致预取中止异常。异常发生在指令执行之前。如果标记为中止的指令到达指令流水线的执行阶段之前刷新了指令流水线,则不会发生中止异常。数据中止异常发生在加载或存储指令执行时,并且是在尝试读取或写入数据之后发生的。

​ 如果中止是由于指令流的执行或尝试执行而产生的,则中止被描述为同步的,并且返回地址将提供导致该中止的指令的详细信息。

​ 异步的中止不是由执行指令生成,异步中止的返回地址可能不提供导致中止的原因的信息。

​ ARMv7体系结构分为精确的和不精确的异步中止。MMU产生的中止总是同步的。ARMv7体系结构不需要外部中止的类型是同步的。例如,在一个特定的实现上,页表翻译时报告的外部异常中止被认为是精确的,但这并不是所有处理器核都需要的。对于精确的异步中止,中止处理程序可以确定是哪条指令导致了中止,并且在该指令之后没有执行其他指令。这与不精确的异步异常中止相反,异步异常中止是外部存储器系统报告有关无法识别的访问的错误时的结果。在这种情况下,中止处理程序无法确定是哪条指令导致了问题,或者在产生中止的指令之后是否还会执行其他指令。

​ 例如,如果缓冲写入从外部存储系统接收到错误响应,则执行存储指令后很可能执行了其他指令。这意味着中止处理程序无法修复此问题并返回到应用程序。它所能做的就是杀死导致问题的应用程序。因此,设备探测需要特殊的处理,因为从外部报告的对不存在区域的读取中止将产生不精确的同步中止,即使将此类存储器标记为“strong odered”或“设备”。

​ 异步中止的检测由CPSR A位控制。如果将A位置1,CPU核将识别出外部存储系统的异步异常中止,但不会产生中止异常。取而代之的是,内核将中止挂起状态挂起,直到清除A位时才采取异常处理为止。内核代码将使用屏障指令来确保针对正确的应用程序识别未处理的异步中止。如果由于不精确的中止而不得不终止线程,则该线程必须是正确的线程。

​ (3)复位

​ 所有处理器核都有复位输入,并且在复位后将立即执行复位异常。它是最高优先级的异常,无法屏蔽。上电后,此异常用于在处理器核上执行代码以对其进行初始化。

​ (4)生成异常的指令

​ 某些指令的执行会产生异常。通常执行以下指令,以便从更高特权级别的软件中请求服务:

​ •Supervisor Call(SVC)指令使用户模式程序可以请求操作系统服务。

​ •如果实施了虚拟化扩展,则可以使用Hypervisor调用(HVC)指令,使虚拟机可以请求Hypervisor服务。

​ •如果实施了安全扩展,则可以使用(SMC)指令,使普通环境可以请求安全环境服务。

​ 任何试图执行处理器核无法识别的指令都会产生未定义的异常。

​ 发生异常时,CPU核将执行与该异常对应的处理程序。异常处理程序在内存中的存储位置称为异常向量。在ARM体系结构中,异常向量存储在称为异常向量表的表中。因此,用于特定异常的向量可以位于异常向量表起始位置的固定偏移处。该向量表的基地址由特权软件在系统寄存器中指定,以便处理器核可以在发生异常时找到相应的处理程序。

​ 可以为安全PL1,非安全PL1,安全监视器和非安全PL2特权级别分别配置单独的异常向量表。处理器核根据当前的特权等级和安全状态来查找对应的异常向量表。

​ 可以用ARM或Thumb代码编写异常处理程序。CP15 SCTLR.TE位用于指定异常处理程序将使用ARM还是Thumb指令集。处理异常时,必须保留处理器核先前的模式,状态和寄存器,以便可以在处理异常后恢复原来程序的执行。

10.2.2 异常优先级

​ 当异常同时发生时,将依次处理每个异常,然后返回原来执行的应用程序。所有异常不可能同时发生。例如,未定义指令(Undef)和supervisor call(SVC)异常是互斥的,因为它们都是由执行指令触发的。

​ 注意:ARM体系结构未定义何时采用异步异常。因此,异步异常相对于其他异常(同步和异步)的优先级由实现决定。

​ 所有异常均禁用IRQ,只有FIQ和复位禁用FIQ。这是由处理器核自动设置CPSR I(IRQ)和F(FIQ)位来完成的。

​ 可能同时产生多个异常,但是某些组合是互斥的。预取中止将一条指令标记为无效,因此不能与未定义的指令或SVC同时发生(当然,SVC指令也不能是未定义的指令)。这些指令不会导致任何内存访问,因此不会导致数据中止。该体系结构未定义何时必须采取异步异常,FIQ,IRQ或异步异常中止,但是采用IRQ或数据异常中止不会禁用FIQ异常这一事实意味着FIQ执行将优先于IRQ或异常中止异常。

​ 异常处理是通过使用称为向量表的内存区域来控制的。默认情况下,该地址位于字映射地址从0x00到0x1C的内存映射的底部。向量表可以从0x0移到0xFFFF0000。

​ 对于带有Security Extensions的内核,情况更加复杂。这里有三个向量表,非安全向量表,安全向量表和安全监视器向量表。对于带有Virtualization Extension的核心,有四个,添加了Hypervisor向量表。对于具有MMU的内核,所有这些向量地址都是虚拟的。下表总结了各种状态下异常的行为。

​ 进入异常时,CPSR的I和F设置如下表所示:

10.2.3 向量表

​ 向量表示触发异常时ARM核跳转到的指令表。这些指令位于内存中的特定位置。默认向量基址为0x00000000,但大多数ARM核允许将向量基址移至0xFFFF0000(或HIVECS)。所有Cortex-A系列处理器都允许这样做,这是Linux内核选择的默认地址。实现安全扩展的内核还可以使用CP15向量基地址寄存器为安全状态和非安全状态分别设置向量基地址。

​ 每种异常类型都有一个字的地址。因此,每个异常只能在向量表中放置一条指令(尽管从理论上讲,可以使用两条16位Thumb指令)。因此,向量表条目几乎总是包含以下两种形式的分支之一。

​ B

​ 这将执行PC相对跳转。它可以跳转到当前指令的前后32MB。

​ LDR PC,[PC,#offset]

​ 这将从地址相对于异常指令offset偏移量的值加载到PC。这样就可以将异常处理程序放置在32位内存地址空间内的任意地址处(但相对于B指令,要多花一些额外的指令周期)。

​ 当处理器核以Hyp mode运行时,它使用Hyp mode向量入口地址,这些入口地址是从Hyp mode的专用向量表中获取的。通过Hyp trap entry的特定异常过程进入系统管理程序模式,该过程利用向量表中先前保留的0x14地址。专用寄存器(Hyp Syndrome Register)向hypervisor提供有关进入管理程序的异常或其他原因的信息(例如,陷阱CP15操作)。

​ 异常向量和异常基地址

​ 发生异常时,处理器根据强制到跟异常的类型相应的地址去执行。这个地址被称为异常向量。一组异常向量包括从一个异常向量基地址开始的连续的8个字对齐的地址空间。8个异常向量组成异常向量表。

​ 可能的异常向量基地址或者异常向量表的量由扩展类型和架构类型决定,这里只讨论包含安全扩展的类型

​ 包含安全扩展类型的实现有如下向量表

​ 一个转去secure monitor模式的表,MVBAR保存了异常向量基地址

​ 一个转去secure PL1模式(不包括monitor模式)的表,secure状态下SCTLR.V位决定了向量表的基地址,如果V==0的话,secure VBAR保存了异常向量基地址;如果v==1的话,异常向量基地址为0xFFFF0000。

​ 一个转去非安全PL1模式的表,这是非安全状态的向量表,Non-secure状态下SCTLR.V位决定了向量表的基地址,如果V==0的话,Non-secure VBAR保存了异常向量基地址;如果v==1的话,异常向量基地址为0xFFFF0000。

10.2.4 FIQ and IRQ

​ FIQ保留用于需要保证快速响应时间的单个高优先级中断源,而IRQ用于系统中的所有其他中断。

​ 由于FIQ是向量表中的最后一项,因此FIQ处理程序可以直接放置在向量入口位置,并从该地址开始顺序运行。这避免了分支指令和任何相关的延迟,从而加快了FIQ响应时间。相对于其他模式,FIQ模式下可用的备份寄存器数量比较多,从而避免要将寄存器的值保存到栈上,提高了执行速度。

​ Linux通常不使用FIQ。由于内核与体系结构无关,因此它不具有多种形式的中断的概念。某些运行Linux的系统仍可以使用FIQ,但是由于Linux内核从不禁用FIQ,因此它们比系统中的其他任何事物都具有优先权,因此需要格外小心。

10.2.5 返回指令

​ 处理异常后,链接寄存器(LR)用于为存储返回地址。下表提供了包含此调整的异常的返回指令。

10.2.6 异常处理

​ 发生异常时,ARM内核会自动执行以下操作:

​ 1.将CPSR复制到SPSR_ ,这是特定(非用户)操作模式的备份寄存器。

​ 2.将返回地址存储在新模式的链接寄存器(LR)中。

​ 3.将CPSR模式位修改为与异常类型相关联的模式。

​ •其他CPSR模式位设置由CP15系统控制寄存器的值确定。

​ •T位设置为CP15 TE位给定的值。

​ •J位被清除,E位(字节序)被设置为EE(异常字节序)位的值。

​ 这使异常始终以ARM或Thumb状态运行,并且以小端或大端运行,无论CPU核在异常之前处于何种状态。

​ 4.将PC设置为指向异常向量表中的相关指令。

​ 在新模式下,CPU核将访问与该模式关联的寄存器。

​ 异常处理程序软件几乎总是需要在进入异常处理程序时立即将寄存器保存到堆栈中。FIQ模式具有更多的备份寄存器,因此可以编写不使用堆栈的简单处理程序。

​ 提供了一种特殊的汇编语言指令来帮助保存必要的寄存器,称为SRS(store return state存储返回状态)。该指令将LR和SPSR压入任何模式的堆栈;所使用的堆栈由指令操作数指定。

10.2.6.1 从异常处理程序返回

​ 要从异常处理程序返回,必须进行两个单独的操作:

​ 1.从保存的SPSR中恢复CPSR。

​ 2.将返回地址偏移量设置到PC。

​ 在ARM体系结构中,这可以通过使用RFE指令或以PC作为目标寄存器的任何标志设置数据处理操作(带有S后缀)来实现,例如SUBS PC,LR,#offset(注意S)。异常返回(RFE)指令将链接寄存器和SPSR从当前模式堆栈弹出。

​ 有多种方法可以实现此目的。

​ •可以使用数据处理指令来调整LR并将其复制到PC中,例如:

SUBS PC, LR, #4

​ 指定S表示同时将SPSR复制到CPSR。

​ 如果异常处理程序入口代码使用堆栈来存储在处理异常时必须保留的寄存器,则它可以使用带有^限定符的加载指令返回。例如,异常处理程序可以使用以下命令在一条指令中返回:

LDMFD sp!, {pc} ^

LDMFD sp!, {R0-R12,pc} ^

​ 在此示例中,^限定符表示SPSR同时复制到CPSR。

​ 为此,异常处理程序必须将以下内容保存到堆栈中:

​ —调用处理程序时,所有需要使用的工作寄存器。

​ —修改链接寄存器以产生与数据处理指令相同的效果。

​ 注意

​ 不能使用16位Thumb指令从异常中返回,因为这些指令无法还原CPSR。RFE指令从当前模式的堆栈中恢复PC和SPSR。

RFEFD sp!

10.2.7 中止处理程序

​ 中止处理程序代码在系统之间可能有很大差异。在许多嵌入式系统中,异常中止表示意外错误,处理程序将记录所有诊断信息,报告错误并让应用程序(或系统)退出。

​ 在使用MMU支持虚拟内存的系统中,中止处理程序可以将所需的虚拟页加载到物理内存中。实际上,它尝试解决最初中止的原因,然后返回中止的指令并重新执行它。

​ CP15寄存器提供了导致中止的存储器访问地址(故障地址寄存器Fault Address Register)和中止的原因(故障状态寄存器Fault Status Register)。原因可能是缺少访问权限,外部中止或地址转换错误。此外,链接寄存器(进行了–8或–4调整,取决于中止是由指令获取还是数据访问引起的),给出了导致中止异常的指令的地址。通过检查这些寄存器,最后执行的指令以及系统中可能的其他内容(例如转换表条目),中止处理程序可以确定要采取的操作。

10.2.8 未定义的指令处理

​ 如果CPU核尝试使用操作码执行一条指令(在ARM体系结构规范中描述为UNDEFINED),或者执行了协处理器指令但没有协处理器将其识别为可以执行的指令,则会导致未定义的指令异常。

​ 在某些系统中,代码可能包含用于协处理器(例如VFP协处理器)的指令,但是系统中不存在相应的VFP硬件。另外,VFP硬件有可能无法处理特定指令,而是想调用软件来对其进行仿真。或者,可能会禁用VFP硬件,采用异常处理,以便可以启用它,然后重新执行指令。

​ 通过未定义的指令向量调用此类仿真器。他们检查导致异常的指令操作码,并确定要采取的措施(例如,在软件中执行适当的浮点运算)。在某些情况下,可能必须将这些处理程序以菊花链方式链接在一起(例如,可能要模拟多个协处理器)。

​ 如果没有软件使用未定义的指令或协处理器指令,则异常的处理程序必须记录适当的调试信息,并杀死由于应用程序。

​ 在某些情况下,未定义指令异常的另一个用途是实现用户断点。

10.2.9 SVC异常处理

​ supervisor call(SVC)通常用于使用户模式代码能够访问OS功能。例如,如果用户代码想要访问系统的特权部分(例如执行文件I / O),则通常将使用SVC指令执行此操作。

​ 可以使用寄存器或者操作码中某个字段将参数传递给SVC处理程序。

​ 发生异常时,异常处理程序可能必须确定内核是处于ARM还是Thumb状态。

​ 特别是SVC处理程序,可能必须读取指令集状态。这是通过检查SPSR T位完成的。该位设置为Thumb状态,清除为ARM状态。

​ ARM和Thumb指令集都具有SVC指令。从Thumb状态调用SVC时,必须考虑以下因素:

​ •指令地址位于LR-2,而不是LR-4。

​ •指令本身是16位的,因此需要半字加载,

​ • SVC编号为8位而不是ARM状态下的24位。

​ 例11-1中显示了说明Linux内核使用SVC的代码

​ SVC#0指令使ARM核采用SVC异常(一种访问内核功能的机制)。寄存器R0定义所需的系统调用(在本例中为sys_write)。其他参数在寄存器中传递。对于sys_write,您需要R0告诉要写入的位置,R1指向要写入的字符,R2给出字符串的长度。

10.3 und异常模示程序示例

10.3.1 代码分析

​ 此节代码所在**裸机Git仓库 NoosProgramProject/(10_异常与中断/008_exception_undef)**目录下。

​ 通过在代码段里里插入一个未定义指令(0xdeadc0de),从而产生未定义指令异常。在未定义异常指令异常的处理函数里,调用printException函数,打印出当前的CPSR值,和产生异常的原因的字符串。

​ 在复位Reset_Handler里要分别设置好SVC模式和und模式的栈,这样我们就可以在各自的模式里调用C代码。通过如下指令,设置好异常向量的基地址

mcr p15, 0, r0, c12, c0, 0

​ 在异常向量表里,通过如下指令跳转到Undefined_Handler标签处

ldr pc, =Undefined_Handler

​ 在Undefined_Handler里将r0-r12和lr保存在und模式的栈上,然后调用printException打印当前的CPSR值,和产生异常的原因的字符串。最后将r0-r12从栈上恢复,lr从栈上弹出到PC,并同时将SPSR恢复到CPSR,从而返回去执行出现未定义异常指令的下一条指令。

​ 代码如下008_exception_undef\start.S:

.text
.global  _start, _vector_table
_start:
_vector_table:
	ldr 	pc, =Reset_Handler			 /* Reset				   */
	ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
	//b Reset_Handler
	//b Undefined_Handler
	b halt//b SVC_Handler//ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
	b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
	b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
	.word	0							 /* RESERVED			   */
	b halt//ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
	b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */

.align 2
Undefined_Handler:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 2
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

    cps     #0x1B                /* Enter undef mode                */
    ldr     sp, =0x80300000     /* Set up undef mode stack      */

    cps     #0x13                /* Enter Supervisor mode         */
    ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */
	ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
	//mrc p15, 0, r0, c12, c0, 0  //read VBAR

	bl clean_bss
	
	bl system_init

und_code:
	.word 0xdeadc0de  /* undefine instruction */
	//.word 0xFFFFFFFF

	bl main

halt:
	b  halt

clean_bss:
	/* 清除BSS段 */
	ldr r1, =__bss_start
	ldr r2, =__bss_end
	mov r3, #0
clean:
	cmp r1, r2
	strlt r3, [r1]
	add r1, r1, #4
	blt clean
	
	mov pc, lr

10.3.2 参考章节《4-1.4编译程序》编译程序

10.3.3 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

10.4 swi异常模示程序示例

10.4.1 代码分析

​ 此节配套源码所在裸机Git仓库 NoosProgramProject/(10_异常与中断/008_exception_swi) 目录下。

​ 然后通过swi 123,执行管理模式异常,程序跳转到异常向量表偏移0x8的地方执行,在异常向量表里通过如下指令跳转到SVC_Handler标签处执行。

ldr pc, =SVC_Handler

​ 在SVC_Handler里将r0-r12和lr保存在SVC模式的栈上,然后将lr的值移动到R4,调用printException函数打印出当前的CPSR值,和产生异常的原因的字符串。将R4减去4,赋值给R0,也就是swi指令所在的地址,然后调用printSWIVal函数打印出swi指令的参数。最后将r0-r12从栈上恢复,lr从栈上弹出到PC,并同时将SPSR恢复到CPSR,从而返回去执行swi指令的下一条指令。代码如下(008_exception_swi\start.S):

.text
.global  _start, _vector_table
_start:
_vector_table:
	ldr 	pc, =Reset_Handler			 /* Reset				   */
	ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
	ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
	b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
	b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
	.word	0							 /* RESERVED			   */
	b halt//ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
	b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */

.align 2
Undefined_Handler:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr} 

	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 2
SVC_Handler:
	/* 执行到这里之前:
	 * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_svc保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
	 * 4. 跳到0x08的地方执行程序 
	 */

	/* 保存现场 */
	/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  

	mov r4, lr
	
	/* 处理swi异常 */
	mrs r0, cpsr
	ldr r1, =swi_string
	bl printException

	sub r0, r4, #4
	bl printSWIVal
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
swi_string:
	.string "swi exception"

.align 2
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

   cps     #0x1B                /* Enter undef mode                */
   ldr     sp, =0x80300000     /* Set up undef mode stack      */

   cps     #0x13                /* Enter Supervisor mode         */
   ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */

	ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
	//mrc p15, 0, r0, c12, c0, 0  //read VBAR

	bl clean_bss
	
	bl system_init

und_code:
	.word 0xdeadc0de  /* undefine instruction */
	//.word 0xFFFFFFFF

swi_code:
	swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

	bl main

halt:
	b  halt
       
clean_bss:
	/* 清除BSS段 */
	ldr r1, =__bss_start
	ldr r2, =__bss_end
	mov r3, #0
clean:
	cmp r1, r2
	strlt r3, [r1]
	add r1, r1, #4
	blt clean
	
	mov pc, lr

10.4.2 参考章节《4-1.4编译程序》编译程序

10.4.3 参考章节《4-1.4映像文件烧写、运行》烧写、运行程序

​ 此时观察串口打印

10.5 中断处理

​ 较旧的ARM体系结构版本使实现者在设计外部中断控制器时具有很大的自由度,而无需就中断的数量或类型或用于与中断控制器模块接口的软件模型达成协议。通用中断控制器v2(GIC)架构提供了更为严格的规范,不同厂商的中断控制器之间具有更高的一致性。这使中断处理程序代码更易于移植。

10.5.1 外部中断请求

​ ARM核如何具有两个外部中断请求FIQ和IRQ。这两个都是对电平触发,对低电平有效。各个不同的实现都有中断控制器,这些控制器接受来自各种外部源的中断请求并将它们映射为FIQ或IRQ,从而导致ARM核发生异常。

​ 通常,只有当相应的CPSR禁止位(分别为F和I位)清零并且相应的输入为有效时,才可以产生中断异常。

​ CPS指令提供了一种简单的机制来启用或禁用由CPSR A,I和F位(分别为异步中止,IRQ和FIQ)控制的异常。

​ CPS IE或CPS ID将分别启用或禁用异常。使用字母A,I和F中的一个或多个指定要启用或禁用的异常。省略了相应字母的异常将不会被修改。

​ 在Cortex-A系列处理器中,可以配置CPU核,以使FIQ不能被软件屏蔽。这被称为不可屏蔽FIQ,并由CPU核复位时采样的硬件配置的输入信号控制。发生FIQ异常后,它们仍将自动被屏蔽。

10.5.2 分配中断

​ 中断控制器接受和仲裁来自各种源的中断。控制器通常包含多个寄存器,这些寄存器使运行的软件能够屏蔽各个中断源,确认来自外部设备的中断,为各个中断源分配优先级并确定当前需要处理的中断源。

​ 此中断控制器可以是特定于系统的设计,也可以是ARM通用中断控制器(GIC)架构的实现。

10.5.3 简单的中断处

​ 这代表了最简单的中断处理程序。发生中断时,将禁用其他同类中断,直到稍后显式启用。我们只能在第一个中断请求完成时才能处理其他中断,并且在此期间没有更高优先级或更紧急的中断需要处理。这通常不适用于复杂的嵌入式系统,但是解释更复杂的的示例之前了解是很有用的,在这种情况下是不可重入的中断处理程序。

​ 处理中断所采取的步骤如下:

​ 1.外部硬件引发IRQ异常。ARM核自动执行几个步骤。当前模式下PC的内容存储在LR_IRQ中。CPSR寄存器被复制到SPSR_IRQ。CPSR内容被更新,设置模式位为IRQ模式,并且将I位设置为屏蔽其他IRQ。PC被设置为向量表中的IRQ入口。

​ 2.执行向量表中IRQ入口处(中断异常的分支)的指令。

​ 3.中断处理程序保存被中断程序的上下文,它将被该中断处理程序损坏的所有寄存器压入堆栈。当中断处理程序完成执行时,这些寄存器将从堆栈中弹出以恢复。

​ 4.中断处理程序确定中断源,然后调用响应的处理程序。

​ 5.通过将SPSR_IRQ复制到CPSR,并还原先前保存的上下文,准备将CPU核切换到先前的执行状态,最后从LR_IRQ恢复PC。

​ 相同的顺序也适用于FIQ中断。

10.5.4 嵌套中断处理

​ 嵌套中断处理是软件可以在完成对当前中断的处理之前接受另一个中断。这可以将中断进行优先级分级,降低高优先级事件的响应延迟,代价是增加了软件的复杂性。值得注意的是,嵌套中断处理是由软件根据中断优先级配置和中断控制而不是由硬件完成的。

​ 可重入中断处理程序在跳转到启用了中断的嵌套子程序或C函数之前,必须保存IRQ状态,然后切换CPU核模式,并为新的核心模式保存状态。这是因为新中断随时可能发生,这将导致CPU核存储新中断的返回地址并覆盖原始中断。当原始中断尝试返回主程序时,它将导致系统故障。为了防止这种情况,嵌套中断处理程序必须在重新启用中断之前更改CPU核模式。

​ 注意:如果一个程序可以在执行过程中中断,然后在先前执行完成之前再次调用,则该该程序是可重入的。

​ 必须在重新启用中断之前保留SPSR的值。如果不是,则任何新的中断都会覆盖SPSR_irq的值。解决方案是使用以下方法在重新启用中断之前将SPSR保存到栈上:

​ SRSFD sp !,#0x12

​ 此外,在中断处理程序代码中使用BL指令将导致LR_IRQ损坏。解决方法是在使用BL指令之前切换到Supervisor模式。因此,可重入的中断处理程序必须在引发IRQ异常必须采取以下步骤:

​ 1.中断处理程序保存被中断程序的上下文(即,它将被该处理程序破坏的所有寄存器(包括返回地址和SPSR_IRQ)压入备用CPU核模式堆栈中)。

​ 2.它确定必须处理的中断源,并清除外部硬件中的中断源(防止其立即触发另一个中断)。

​ 3.中断处理程序更改为CPU核为SVC模式,将CPSR I位置1(中断仍被禁用)。

​ 4.中断处理程序将异常返回地址保存在新模式的堆栈中,并重新启用中断。

​ 5.它调用适当的处理程序代码。

​ 6.完成后,中断处理程序将禁用IRQ并从堆栈中弹出异常返回地址。

​ 7.它直接从堆栈中恢复被中断程序的上下文。这包括还原PC和CPSR,CPSR切换回先前的执行模式。如果SPSR的I位未设置,则该操作还将重新使能中断。

10.6 通用中断处理器(GIC, Generic Interrupt Controller)

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

​ 从软件角度来看,GIC具有两个主要功能模块:

​ ① 仲裁单元(Distributor)

​ 系统中的所有中断源都连接到该单元。仲裁单元具有寄存器来控制各个中断源的属性,例如优先级、状态、安全性、路由信息和使能状态。仲裁单元通过连接的CPU接口单元确定将哪个中断转发给内核。

​ ② CPU接口单元(CPU Interface)

​ CPU核通过控制器的CPU接口单元接收中断。CPU接口单元寄存器用于屏蔽,识别和控制转发到CPU核的中断的状态。系统中的每个CPU核心都有一个单独的CPU接口。

​ 中断在软件中由一个称为中断ID的数字标识。中断ID唯一对应于一个中断源。软件可以使用中断ID来识别中断源并调用相应的处理程序来处理中断。呈现给软件的中断ID由系统设计确定,一般在SOC的数据手册有记录。

​ 中断可以有多种不同的类型:

​ ① 软件触发中断(SGI,Software Generated Interrupt)

​ 这是由软件通过写入专用仲裁单元的寄存器即软件触发中断寄存器(ICDSGIR)显式生成的。它最常用于CPU核间通信。SGI既可以发给所有的核,也可以发送给系统中选定的一组核心。中断号0-15保留用于SGI的中断号。用于通信的确切中断号由软件决定。

​ ② 专用外设中断(PPI,Private Peripheral Interrupt)

​ 这是由单个CPU核专用的外设生成的。PPI的中断号为16-31。它们标识CPU核专用的中断源,并且独立于另一个内核上的相同中断源,比如,每个核的计时器。

​ ③ 共享外设中断(SPI,Shared Peripheral Interrupt)

​ 这是由外设生成的,中断控制器可以将其路由到多个核。中断号为32-1020。SPI用于从整个系统可访问的各种外围设备发出中断信号。

​ 中断可以是边沿触发的(在中断控制器检测到相关输入的上升沿时认为中断触发,并且一直保持到清除为止)或电平触发(仅在中断控制器的相关输入为高时触发)。

​ 中断可以处于多种不同状态:

​ ① 非活动状态(Inactive)–这意味着该中断未触发。

​ ② 挂起(Pending)–这意味着中断源已被触发,但正在等待CPU核处理。待处理的中断要通过转发到CPU接口单元,然后再由CPU接口单元转发到内核。

​ ③ 活动(Active)–描述了一个已被内核接收并正在处理的中断。

​ ④ 活动和挂起(Active and pending)–描述了一种情况,其中CPU核正在为中断服务,而GIC又收到来自同一源的中断。

​ 中断的优先级和可接收中断的核都在仲裁单元(distributor)中配置。外设向仲裁单元触发的中断将标记为pending状态(或Active and Pending状态,如触发时果状态是active)。distributor确定可以传递给CPU核的优先级最高的pending中断,并将其转发给内核的CPU interface。通过CPU interface,该中断又向CPU核发出信号,此时CPU核将触发FIQ或IRQ异常。

​ 作为响应,CPU核执行异常处理程序。异常处理程序必须从CPU interface寄存器查询中断ID,并开始为中断源提供服务。完成后,处理程序必须写入CPU interface寄存器以报告处理结束。然后CPU interface准备转发distributor发给它的下一个中断。

​ 在处理中断时,中断的状态开始为pending,active,结束时变成inactive。中断状态反映在distributor寄存器中。

​ 下图是GIC控制器的逻辑结构:

10.6.1 配置

​ GIC作为内存映射的外围设备进行访问。所有内核都可以访问公共的distributor单元,但是CPU interface是备份的,也就是说,每个CPU核都使用相同的地址来访问其自己的专用CPU接口。一个CPU核不可能访问另一个CPU核的CPU接口。

​ Distributor拥有许多寄存器,可以通过它们配置各个中断的属性。这些可配置属性是:

​ •中断优先级。Distributor使用它来确定接下来将哪个中断转发到CPU接口。

​ •中断配置。这确定中断是对电平触发还是边沿触发。

​ •中断目标。这确定了可以将中断发给哪些CPU核。

​ •中断启用或禁用状态。只有Distributor中启用的那些中断变为挂起状态时,才有资格转发。

​ •中断安全性确定将中断分配给Secure还是Normal world软件。

​ •中断状态。

​ distributor还提供优先级屏蔽,通过该屏蔽可防止低于某个优先级的中断发送给CPU核。Distributor通过此方法确定是否可以将pending中断转发到特定的CPU核。

​ 每个CPU核上的CPU interface有助于发送给该CPU核的中断控制和处理。

10.6.2 初始化

​ distributor和CPU interface在复位时均被禁用。复位后,必须将GIC初始化,然后才能将中断传递给CPU核。

​ 在distributor中,软件必须配置优先级、目标核、安全性并启用单个中断。随后必须通过其控制寄存器使能。对于每个CPU接口,软件必须对优先级和抢占设置进行编程。每个CPU接口模块本身必须通过其控制寄存器使能。这为GIC做好了向内核传递中断的准备。

​ 在CPU核可以处理中断之前,软件会通过在向量表中设置有效的中断向量并清除CPSR中的中断屏蔽位来让CPU核可以接收中断。

​ 可以通过禁用distributor单元来禁用系统中的整个中断机制。可以通过禁用单个CPU的CPU接口模块或者在CPSR中设置屏蔽位来禁止向单个CPU核的中断传递。也可以在distributor中禁用(或启用)单个中断。

​ 为了使某个中断可以触发CPU核,必须将各个中断,distributor和CPU接口全部使能,并将CPSR中断屏蔽位清零。

10.6.3 GIC中断处理

​ 当CPU核接收到中断时,它会跳转到中断向量表执行。顶层中断处理程序读取CPU接口模块的Interrupt Acknowledge Register,以获取中断ID。

​ 除了返回中断ID之外,读取还会使该中断在distributor中标记为active状态。一旦知道了中断ID(标识中断源),顶层处理程序现在就可以分派特定于设备的处理程序来处理中断。

​ 当特定于设备的处理程序完成执行时,顶级处理程序将相同的中断ID写入CPU interface模块中的End of Interrupt register中断结束寄存器,指示中断处理结束。

​ 除了移除active状态之外,这将使最终中断状态变为inactive或pending(如果状态为inactive and pending),这将使CPU interface能够将更多待处理pending的中断转发给CPU核。这样就结束了单个中断的处理。

​ 同一内核上可能有多个中断等待服务,但是CPU interface一次只能发出一个中断信号。顶层中断处理程序重复上述顺序,直到读取特殊的中断ID值1023,表明该内核不再有任何待处理的中断。这个特殊的中断ID被称为伪中断ID(spurious interrupt ID)。

​ 伪中断ID是保留值,不能分配给系统中的任何设备。当顶级处理程序读取了伪中断ID时,它可以完成其执行,并为CPU核做好准备以继续执行被中断的任务。

10.7 中断控制器寄存器

10.7.1 Distributor 寄存器描述

10.7.1.1 Distributor Control Register, GICD_CTLR

​ [1] EnableGrp1使能,用于将pending Group 1中断从Distributor转发到CPU interfaces:

​ 0 Group 1中断不转发。

​ 1根据优先级规则转发Group 1中断。

​ [0] EnableGrp0使能,用于将pending Group 0中断从Distributor转发到CPU interfaces:

​ 0 Group 0中断不转发。

​ 1根据优先级规则转发Group 0中断。

10.7.1.2 Interrupt Controller Type Register, GICD_TYPER

​ [15:11] LSPI如果GIC实现了安全扩展,则此字段的值是已实现的可锁定SPI的最大数量,范围为0(0b00000)到31(0b11111)。如果此字段为0b00000,则GIC不会实现配置锁定。如果GIC没有实现安全扩展,则保留该字段。

​ [10] SecurityExtn指示GIC是否实施安全扩展。

​ 0未实施安全扩展。

​ 1实施了安全扩展。

​ [7:5] CPUNumber表示已实现的CPU interfaces的数量。已实现的CPU interfaces数量比该字段的值大1,例如,如果此字段为0b011,则有四个CPU interfaces。如果GIC实现了虚拟化扩展,则这也是虚拟CPU接口的数量。

​ [4:0] ITLinesNumber指示GIC支持的最大中断数。如果ITLinesNumber = N,则

​ 最大中断数为32*(N+1)。中断ID的范围是0到(ID的数量– 1)。例如:

​ 0b00011最多128条中断线,中断ID 0-127。

​ 中断的最大数量为1020(0b11111)。无论此字段定义的中断ID的范围如何,都将中断ID 1020-1023保留用于特殊目的。

10.7.1.3 Distributor Implementer Identification Register, GICD_IIDR

​ [31:24] ProductID产品标识ID。

​ [23:20]-保留。

​ [19:16] Variant Variant编号。通常是产品的主要版本号。

​ [15:12] Revision Revision编号。通常此字段用于区分产品的次版本号。

​ [11:0] 实现者包含实施GIC分销商的公司的JEP106代码:

​ [11:8] 实现者的JEP106 continuation code。对于ARM实现,此字段为0x4。

​ [7]始终为0。

​ [6:0] 实现者的JEP106code。对于ARM实现,位[7:0]为0x3B。

10.7.1.4 Interrupt Group Registers, GICD_IGROUPRn

​ [31:0] 组状态位,对于每个位:

​ 0相应的中断为Group 0。

​ 1相应的中断为Group 1。

10.7.1.5 Interrupt Set-Enable Registers, GICD_ISENABLERn

​ [31:0] 设置使能位对于SPI和PPI,每个位控制相应的中断从Distributor到CPU interfaces的转发:

​ 读到0 表明转发相应的中断被禁止,读到1表明可以转发相应的中断

​ 写0 没有效果,写1表示使能相应中断的转发

10.7.1.6 Interrupt Clear-Enable Registers, GICD_ICENABLERn

​ [31:0]清除SPI和PPI的使能位,每个位控制相应的中断从Distributor到CPU interfaces的转发:

​ 读到0 表明转发相应的中断被禁止,读到1表明可以转发相应的中断

​ 写0 没有效果,写1表示禁止相应中断的转发

10.7.1.7 Interrupt Set-Active Registers, GICD_ISACTIVERn

​ [31:0] Set-active的每个位:

​ 读取0相应的中断not active。

​ 1相应的中断active。

​ 写0无效。

​ 1 Activates相应的中断(如果尚未Activates)。 如果中断已处于Activates状态,则写入无效。向该位写入1后,随后读取该位将返回值1。

10.7.1.8 Interrupt Clear-Active Registers, GICD_ICACTIVERn

​ [31:0] Clear-active的每个位:

​ 读取0相应的中断not active。

​ 1相应的中断active。

​ 写0无效。

​ 1如果中断处于active状态,则Deactivates相应的中断。 如果中断已被Deactivates,写入无效。向该位写入1后,随后对该位的读取将返回值0。

10.7.1.9 Interrupt Priority Registers, GICD_IPRIORITYRn

​ [31:24]优先级,byte offset 3,每个优先级字段都具有一个优先级值,值越小,相应中断的优先级越高。

​ [23:16]优先级,byte offset 2

​ [15:8优先级,byte offset 1

​ [7:0]优先级,byte offset 0

10.7.1.10 Interrupt Processor Targets Registers, GICD_ITARGETSRn

​ [31:24] CPU目标,byte offset 3,处理器编号从0开始,并且CPU目标字段中的每个位均指代相应的处理器。例如,值0x3表示将挂起中断发送到处理器0和1。对于GICD_ITARGETSR0到GICD_ITARGETSR7,任何CPU目标字段的读取都将返回执行读取的处理器的编号。

​ [23:16] CPU目标,byte offset 2

​ [15:8] CPU目标,byte offset 1

​ [7:0] CPU目标,byte offset 0

10.7.1.11 Interrupt Configuration Registers, GICD_ICFGRn

​ [2F + 1:2F] Int_config,field F对于Int_config [1],即最高有效位[2F + 1],编码为:

​ 0相应的中断对电平敏感。

​ 1相应的中断沿触发。

​ Int_config [0]是最低有效位,即位[2F],但保留位。

​ 对于SGI:

​ Int_config [1]不可编程,RAO / WI。

​ 对于PPI和SPI:

​ Int_config [1]对于SPI,此位是可编程的。对于PPI,是否可编程由实现决定。对该位的读取始终正确反映相应的中断是电平敏感还是边沿触发。

10.7.1.12 Identification registers

​ [31:8]- 由实现定义。CoreLink和CoreSight外围设备ID寄存器方案要求保留这些位,RAZ,ARM强烈建议实现遵循此方案。

​ [7:4] GIC体系结构的ArchRev修订版字段。该字段的值取决于GIC架构版本:

​ •GICv1为0x1

​ •GICv2为0x2。

​ [3:0]-由实现定义。

10.7.2 CPU interface寄存器描述

10.7.2.1 CPU Interface Control Register, GICC_CTLR

​ [9] EOImodeNS 控制对GICC_EOIR和GICC_DIR寄存器的非安全访问:

​ 0 GICC_EOIR具有降低优先级和deactivate中断功能。对GICC_DIR的访问是未定义的。

​ 1 GICC_EOIR仅具有降低优先级功能。 GICC_DIR寄存器具有deactivate中断功能。

​ [6] IRQBypDisGrp1当CPU interface的IRQ信号被禁用时,该位控制是否向处理器发送bypass IRQ信号:

​ 0将bypass IRQ信号发送给处理器

​ 1 将bypass IRQ信号不发送到处理器。

​ 有关更多信息,请参阅第2-27页的中断信号旁路和GICv2旁路禁用。

​ [5] FIQBypDisGrp1当CPU interface的FIQ信号被禁用时,该位控制是否向处理器发送bypass FIQ信号:

​ 0将旁路FIQ信号发送给处理器

​ 1旁路FIQ信号不发送到处理器。

​ 有关更多信息,请参阅第2-27页的中断信号旁路和GICv2旁路禁用。

​ [0] EnableGrp1使能CPU interface向连接的处理器发出的组1中断的信号。

​ 0禁用中断信号

​ 1使能中断信号。

​ 注意

​ 当该位设置为0时,CPU interface将忽略转发给它的任何pending的组1中断。当该位置1时,CPU接口开始处理转发给它的pending的组1中断。更改生效需要一个很小但有限的时间。

10.7.2.2 Interrupt Priority Mask Register, GICC_PMR

​ [7:0]优先级 CPU interface的优先级屏蔽级别。如果中断的优先级高于此字段值,接口将中断信号通知处理器。如果GIC支持的优先级少于256个,则某些位为RAZ / WI,如下所示:

​ 128个级别 bit[0] = 0。

​ 64个级别bit [1:0] = 0b00。

​ 32个级别bit [2:0] = 0b000。

​ 16个级别bit [3:0] = 0b0000。

​ PS:imx6ull最多为32个级别

10.7.2.3 Binary Point Register, GICC_BPR

​ [2:0] Binary point 此字段的值控制如何将8bit中断优先级字段拆分为组优先级和子优先级,组优先级用来决定中断抢占。有关此字段如何确定分配给组优先级字段的中断优先级位的信息,请参见:

​ 当GICC_CTLR.CBPR位设置为1时,用于在GIC上处理第group 1中断可支持中断分组。

​ PS:imx6ull 上BPR的最小值为2

10.7.2.4 Interrupt Acknowledge Register, GICC_IAR

​ [12:10] CPUID对于多处理器中的SGI,此字段标识请求中断的处理器。 它返回发出请求的CPU interface的编号,例如,值为3表示该请求是通过对CPU interface 3上的GICD_SGIR的写操作生成的。对于所有其他中断,此字段为RAZ。

​ [9:0]中断ID中断ID。

10.7.2.5 Interrupt Register, GICC_EOIR

​ [12:10] CPUID在多处理器实现中,如果写入引用SGI,则此字段包含来自相应GICC_IAR访问的CPUID值。 在所有其他情况下,此字段为SBZ。

​ [9:0] EOIINTID来自相应GICC_IAR访问的中断ID值。