赞
踩
严格来讲,一个interrupt会中断软件执行的流程。但是,以ARM术语来看,它实际上是一个exception。Exception为要求特权软件采取措施来保证系统平滑功能的条件或系统event。对于每个exception类型有一个与之相关的exception handler。一旦exception被处理,特权软件让core准备唤醒在exception之前的行为。
存在以下类型的exception:
Interrupts 存在两种类型的中断:IRQ和FIQ。
FIQ比IRQ优先级更高。这两种类型的exception通常与core中输入pin对应。假定中断没有禁用,当当前指令完成执行时,外部硬件发出中断请求线并发出对应的exception类型。
FIQ和IRQ为发往core的物理信号,当发出后,如果当前被使能,core将采取对应的exception。在大多数系统中,各种中断源通过一个中断控制器相连。中断控制器仲裁并提升中断优先级,并提供一个串行单个信号,该信号将连接到core的FIQ或IRQ信号。
因为在任意时刻IRQ和FIQ中断的产生不会直接与core执行的软件相关,它们被分类为asynchronous exception。
Aborts 指令获取失败或数据访问失败时会产生aborts。它们可以来自于外部内存系统对内存访问给出一个错误回复(表明给定的地址在系统中没有对应真实的内存)。
另外,core的MMU也可以产生aborts。OS可以使用MMU abort来动态分配内存给应用。
流水线中的指令当被获取时可以被指标为aborted。仅当core尝试去执行指令时才会产生指令abort exception。在指令执行之前exception产生。如果在aborted指令到达流水线的执行阶段之前流水线被冲刷,abort exception不会产生。由于load或store指令,数据abort exception产生,在尝试数据读或写之后异常考虑产生。
如果abort的产生是由于执行或尝试执行指令流,该abort被认为synchronous,返回的地址提供指令的详细信息。
一个asynchronous abort并不是由正在执行的指令产生,且返回的地址通常也不能提供造成abort的细节。在ARMv8-A中,指令和数据abort为synchronous。Asynchronous exception为IRQ/FIQ和SError。
Reset Reset被认为是实现的最高异常级别的特殊vector。它是当发出异常时,ARM处理器跳转到的指令位置。vector使用实现定义的地址。
RVBAR_ELn包含reset vector地址,其中n表示最高异常级别。
所有的core都有一个reset输入,在它们复位时立即采用reset exception。它为最高优先级的exception且不能屏蔽。在上电后exception用来执行代码初始化core。
异常产生指令 某些指令的执行能够产生异常。这些指令通常用于来自更高权限级别的软件的请求服务。
如果由于EL0的指令获取产生异常,该异常作为exception被陷入到EL1,如果在非安全状态HCR_EL2.TGE没有被设置。如果被设置,该异常会被陷入到EL2。
如果由于其他异常级别的指令获取产生异常,异常级别保持不变。
我们知道ARMv8-A架构有四个异常级别。处理器执行仅通过异常的陷入或返回在不同异常级别之间变化。当处理器从一个更高的异常级别到一个更低的异常级别时,执行状态可以保持不变,或它将从AArch64切换到AArch32。当处理器从一个更低的异常级别到一个更高的异常级别时,执行状态保持不变,或从AArch32切换到AArch64。
上图描述了当运行应用时产生异常相关的程序流程。处理器跳到包含每个异常类型项的vector table。Vector table包含下发代码,这些代码通常会区分异常产生的原因,选择并调用合适的函数处理它们。代码完成执行然后返回通过执行ERET指令返回到应用。
第4章描述了处理器的当前状态是如何被保存在PSTATE域中。如果异常产生,PSTATE信息被保存在SPSR_ELn中。
SPSR.M域用于记录执行状态(0表明AArch64,1表明AArch32)。
异常位屏蔽位DAIF允许异常事件被屏蔽。当这些位被设置时将不产生异常。
D 调试异常屏蔽
A SError中断处理状态屏蔽,比如异常外部abort
I IRQ中断处理状态屏蔽
F FIQ中断处理状态屏蔽
SPSel域选择当前异常级别Stack Pointer或SP_EL0是否被使用。这可以在任意异常级别,除了EL0。
IL域,当被设置时,会导致下一条指令的执行会触发一个异常。它在非法执行返回时被使用,比如当它被配置为AArch32时,它尝试作为AArch64返回到EL2。
SS域在第18章介绍。调试者使用它来执行单个指令并在下列指令时产生调试异常。
一些单独域(CurrentEL, DAIF, NZCV)在产生异常时会被拷贝到SPSR_ELn。
当造成异常的event产生时,处理器硬件自动执行某种操作。SPSP_ELn被更新用来保存PSTATE信息,并要求在异常处理的结尾返回。PSTATE被更新反映最新处理器状态。在异常处理结尾使用的返回地址被保存在ELR_ELn。
记住_ELn结尾的寄存器名表明在不同的异常级别存在不同的寄存器拷贝。比如,SPSR_EL1与SPSR_EL2是不同的物理寄存器。另外,对于synchronous或SError异常,ESR_ELn也被更新表明异常的原因。
当从异常返回时由软件告诉处理器。这是由ERET指令完成的。这将恢复之前从SPSR_ELn的PSTATE,并通过从ELR_ELn恢复PC来返回到原来的位置。
我们已经看到SPSR是如何记录异常返回的必要状态信息。我们将看到Link寄存器用于保存程序地址信息。架构为函数调用提供Link寄存器。
在第6章我们看到A64指令集,寄存器X30用于从子路径返回。当我们使用BL或BLR执行分支时,它的值由指令的地址更新。
ELR_ELn寄存器用于从异常返回地址。在该寄存器中的值被自动写到一个异常中并写入到PC中,它作为ERET指令的效果用于返回异常。
ELR_ELn包含某些异常类型的返回地址。对于相同的异常,它为产生异常指令的下一个指令的地址。比如,当执行SVC指令时,我们希望返回到应用的下一个指令。其它情况,我们可能希望重新执行产生异常的指令。
对于异步异常,ELR_ELn指向还没有执行的第一个指令的地址。如果在产生一个异步异常之后有必要返回指令,处理代码允许修改ELR_ELn。ARMv8-A架构明显比ARMv7-A简单。
另外对于SPSR和ELR寄存器,每个异常级别有自己的Stack Pointer寄存器。它们被命名为SP_EL0,SP_EL1, SP_EL2, SP_EL3。这些寄存器用于指向stack,用于保存寄存器,因此它们在返回原始代码之前恢复他们的原始值。
在AArch64中,异常可能为同步或异步。如果它由于执行或尝试执行指令流而产生异常,该异常被描述为synchronous,且返回地址提供造成异常指令的细节。asynchronous异常并不是由执行指令产生,且返回地址并不能提供造成指令的细节。
asynchronous异常源为IRQ或FIQ或SError。系统error有很多可能原因,最常见的asynchronous Data Aborts(比如,来自cache line到外部内存的脏数据的回写触发abort)。
Synchronous异常源有如下:
Synchronous异常由于一些可能的原因产生:
这些异常可能为OS的正常操作的一部分。比如,在LINUX中,当一个task想要请求一个新的内存页分配,这是通过MMU abort机制来处理的。
在ARMv7-A架构中,prefetch abort,Data abort和未定义异常为分开的事务。在AArch64中,所有的event产生一个synchronous abort。异常处理读取syndrome和FAR寄存器来获取信息。
寄存器将synchronous异常的原因提供给异常处理。ESR_ELn寄存器给出异常的原因信息。FAR_ELn寄存器给出所有synchronous指令和Data abort以及对齐fault的虚拟地址。
ELR_ELn寄存器保持造成数据访问的指令地址。在一个内存fault后进行更新,比如由非对齐的地址分支设置。
如果异常从使用AArch32的异常级别陷入到使用AArch64的异常级别,异常写目标的异常级别的FAR寄存器,FAR_ELn的top 32位被设置为0。
对于实现了EL2或EL3时,synchronous异常被陷入到当前或更高异常级别。Asynchronous异常可以被路由到一个更高异常级别,由hypervisor或安全kernel处理。SCR_EL3寄存器指定哪个异常被路由到EL3,HCR_EL2指定哪个异常被路由到EL2。存在单独的位允许控制IRQ, FIQ和SError的路由控制。
一些指令或系统函数可以被执行在特定的异常级别。如果运行在更低的异常级别的代码需要发出一个权限操作,比如当应用代码从kernel请求功能,一种方法是使用SVC指令。这允许应用产生一个异常。通过寄存器传递参数,或由系统调用编码。
前面我们知道SVC是如何从EL0的用户应用调用到EL1的kernel。HVC和SMC系统调用指令以类似的方式将处理器移到EL2和EL3。当处理运行在EL0时,它不能直接调用到EL2或EL3。这仅可能从EL1或更高级别。应用必须使用SVC调用到kernel,然后允许kernel调用到更高异常级别。
来自OS kernel, 软件使用HVC指令调用到EL2,或使用SMC指令调用到EL3。如果处理器实现了EL3,需要提供EL2陷入的SMC指令。如果没有EL3,SMC没有分配且在当前异常级别被触发。
类似的,来自EL2,程序使用SMC指令调用到EL3。如果当在EL2或EL3你使用SVC调用时它将在相同的异常级别上产生synchronous异常,异常级别的处理决定如何回复。
未分配的指令在AArch64中会导致synchronous abort。当处理器执行以下时产生异常类型:
ESR寄存器ESR_ELn,包含用于决定异常的原因的异常处理的信息。仅在synchronous异常和SError时更新。对于IRQ和FIQ时不会更新,因此这些中断通常由GIC中寄存器获取状态信息。寄存器的位如下:
当异常产生时,处理器可能会修改异常级别或保持相同的执行状态。比如,一个外部源在AArch32模式下执行应用时产生了一个IRQ中断,这里让运行在AArch64模式中OS内核执行IRQ handler。
SPSR包含执行状态和将返回的异常级别。当异常产生时这自动由处理器设置。但是,每个异常级别的执行状态如下控制:
考虑运行在EL0的一个应用,如图10-5所示被一个IRQ打断。内核IRQ handler运行在EL1。当处理IRQ异常时处理器决定设置哪个执行状态。通过异常级别的控制寄存器的RW位来完成。因此,在这个例子中,当异常到达EL1时,由HCR_EL2.RW控制handler的执行状态。
我们必须考虑异常进入哪个异常级别。当异常再次产生时,异常级别可能保持相同,或它到达更高异常级别。异常不会到达EL0。
synchronous异常通常会进入到当前或更高异常级别。但是,asynchronous异常可以被路由到更高异常级别。对于源码,SCR_EL3指定哪个异常被路由到EL3。对于hypervisor代码,HCR_EL2指定哪个异常被路由到EL2。
对于这两种情况,存在单独的位来控制IRQ/FIQ/SError的控制路由。处理器仅将异常进入到它要路由的异常级别。产生异常不会导致异常级别下降。当中断产生时中断通常在异常级别里被屏蔽。
当异常从AArch32到AArch64时,这里需要特殊的考虑。AArch64 handler代码可能要求访问AArch32寄存器且架构定义允许访问AArch32寄存器的映射。
AArch32寄存器R0到R12以X0到X12被访问。在AArch32模式下SP和LR的各种备份版本通过X13到X23访问,备份的R8到R12 FIQ寄存器以X24到X29被访问。这些寄存器的bit[63:32]在AArch32状态无效且包含或0或最后写入的值。没有架构保证是哪个值。通常作为W寄存器访问寄存器。
当异常产生时,处理器必须执行与异常对应的handler代码。handler保存在内存中的位置称为异常向量。在ARM架构中,异常向量被保存在一个称为异常向量表中。每个异常级别存在属于自己的异常表,即EL3/EL2/EL1分别存在一个。该表包含需要执行的指令,而不是一组地址。每个异常的地址在表开始位置的固定偏移处。每个表基地址的虚拟地址由VBAR_ELn设置。
向量表中每个项为16指令长度。与ARMv7相比,这是一个明显的变化,ARMv7的每个项为4byte。在AArch64中,向量的大小更宽,因此top-level handler可以在向量表中直接写入。
表10-2显示了一个向量表。基地址由VBAR_ELn指定,然后每个项对于基地址有固定的偏移。每个表有16项,每个项有128byte。该表包含4组4个项。
Address | Exception type | Description |
VBAR_ELn + 0x000 | Synchronous | Current EL with SP0 |
+0x080 | IRQ/vIRQ | |
+0x100 | FIQ/vFIQ | |
+0x180 | SError/vSError | |
VBAR_ELn + 0x200 | Synchronous | Current EL with SPx |
+0x280 | IRQ/vIRQ | |
+0x300 | FIQ/vFIQ | |
+0x380 | SError/vSError | |
VBAR_ELn + 0x400 | Synchronous | Lower EL using AArch64 |
+0x480 | IRQ/vIRQ | |
+0x500 | FIQ/vFIQ | |
+0x580 | SError/vSError | |
VBAR_ELn + 0x600 | Synchronous | Lower EL using AArch32 |
+0x680 | IRQ/vIRQ | |
+0x700 | FIQ/vFIQ | |
+0x780 | SError/vSError |
考虑一个例子可能使这些更容易理解。
如果内核代码执行在EL1且产生了IRQ中断,产生一个IRQ异常。这个中断并不与hypervisor或安全环境相关,且它也在内核中被处理,在SP_EL1,SPSel被设置,因此你使用SP_EL1。异常因此产生于地址VBAR_EL1+0x280。
在ARMv8-A架构中缺少LDR PC, [PC, #offset],你必须使用更多指令使能将要读取的目的地址。向量空间大小的选择设计是为了避免未使用的向量的cache line造成的cache污染。Reset地址是一个完全独立的地址,它为实现定义,通常core中的硬接线配置决定。这个地址在RVBAR_EL1/2/3寄存器可见。
每个异常有一个单独的异常向量,或来自当前异常级别,或来自更低异常级别,由OS或Hypervisor来决定更低异常级别的AArch64和AArch32状态。SP_ELn用于更低级别产生的异常。但是,软件在handler中能够切换到SP_EL0。
ARM通常使用中断来表示中断信号。在ARM-A或ARM-R处理器中,这意味着外部的IRQ或FIQ中断信号。架构不会指定如何使用这些信号。FIQ通常为安全中断源保留。在早期的版本中,FIQ和IRQ被用于表明高和标准中断优先级,但在ARMv8-A中并不是这样。
当处理器将一个异常带到AArch64执行状态时,PSTATE中断屏蔽自动设置。这意味着禁用了更多的异常。如果软件支持nested异常,比如允许一个更高优先级中断来中断一个低优先级源,软件需要明确重新使能中断。
对于下列指令:
MSR DAIFClr, #imm
该immediate value实际上为4bit域,因为它也可以屏蔽:
ARM对ARMv8-A系统提供了标准的中断控制。中断控制器的编程接口在GIC架构中定义。GIC支持多Core系统中软件产生中断,私有中断和共享外设中断在core之间的路由。
GIC架构提供寄存器用于管理中断源和行为对单个Core进行中断的路由。它使能软件进行屏蔽,使能和禁用中断,提高单独源的优先级并产生软件中断。GIC接受系统级产生的中断,并将它们发送给每个core,并导致IRQ或FIQ异常。
从软件角度看,一个GIC主要有两个功能模块:
Distributor 系统中所有的中断源都连接到distributor上。它有寄存器控制单个中断的属性,如优先级,状态,安全性,路由信息和使能状态。Distributor决定中断通过CPU接口被发送到哪个core上。
CPU Interface core通过CPU Interface接受中断。CPU接口有寄存器用于屏蔽,区分和控制中断的状态。在系统中每个core有一个独立的CPU接口。
在软件中中断通过interrupt ID区分中断。一个interrupt ID唯一对应一个中断源。软件可以使用interrupt ID来区分中断源并调用对应的handler来处理中断。Interrupt ID由系统设计决定。
中断可以分为如下几类型:
SGI 它是由软件写Distributor寄存器GICD_SGIR产生。通常用于core间通信。SGI可以产生到所有core,或系统中一组特定的core。Interrupt ID 0-15保留用于SGI。软件设置interrupt ID产生中断。
PPI 它为全局外部中断,Distributor可以将它路由到一个特定的core。Interrupt ID 16-31保留用于PPI。这些中断源对于core是私有的,对于每个core它并不是相同的中断源,比如,基于core的timer。
SPI 它由外设产生,GIC可以将其路由到超过一个core。Interrupt ID 32-1020用于SPI。SPI用于从各种外设发出中断。
LPI 它为基于message的中断,可以被路由到特定的core。GICv2或GICv1不支持LPI。
中断或为边沿中断,或为电平中断。
一个中断有如下不同状态:
中断被传递到的Core的优先级和列表被配置在Distributor中。发往Distributor的中断处于pending状态。Distributor决定最高优先级pending中断,这些中断被传递给core并将其传递给core的CPU interface。对于CPU interface,中断被发往core,这时core产生FIQ或IRQ异常。
core执行异常handler。Handler必须从CPU接口寄存器查询interrupt ID并开始处理中断。当完成处理,handler必须写CPU接口寄存器来报告处理结束。
对于给定的中断,典型的时序为:
Inactive -> pending
当外设发出中断时
Pending -> active
当handler acknowledge 中断
Active -> inactive
当完成中断处理
Distributor提供寄存器报告不同中断ID的当前状态。
在多core/多处理器系统中,单个GIC可能被多个core共享。GIC提供寄存器控制SPI发往哪个core。这个机制使OS能够在不同core间共享和分发中断。
GIC可以以内存映射外设被访问。所有core可以访问公共Distributor,每个core使用相同的地址访问它自己私有的CPU interface。一个core不能访问其他core的CPU interface。
Distributor存在一组寄存器,你可能使用它们配置单个中断的属性。这些配置属性:
Distributor也提供中断优先级屏蔽,中断低于某个优先级被阻止发送到core。Distributor使用它决定pending中断是否发送给某个core。
每个core的CPU interface帮助该core上的中断控制和处理。
在reset时Distributor和CPU interface都是disable的。在reset之后GIC必须在它传递中断给core之前进行初始化。
在Distributor中,软件必须配置优先级,目标core,安全性和使能单个中断。Distributor必须通过它的控制寄存器GICD_CTLR被使能。对于每个CPU interface,软件必须编程优先级屏蔽和抢占设置。
每个CPU interface必须通过它的控制器GICD_CTLR被使能。这为GIC传递中断给Core作准备。
在中断传递给core之前,软件通过设置vector table中的有效中断vector,并清PSTATE中中断屏蔽,并设置路由控制。
整个中断机制可以通过disable Distributor被禁用。中断的传递也可以 通过disable CPU interface被禁用。单个中断也可以被disable。
对于到达core的中断,单个中断,Distributor和CPU interface必须被使能。同时中断也需要有足够的优先级,要高于core的优先级屏蔽。
当core获取到中断,它会跳转到vector table中top-level中断向量,并开始执行。
Top-level中断handler读取CPU interface的IAR寄存器来获取interrupt ID。
在返回interrupt ID同时,该读取会导致中断被标记为active。一旦知道interrupt ID,top-level handler可以发出设备特定的handler处理中断。
当设备特定的handler完成执行,top-level handler写interrupt ID给EOI寄存器,表明完成中断处理。
除了移除active状态,这会将最后中断状态标记为inactive,或pending,这使得CPU interface将更多pending中断发往core。这总结了单个中断的处理流程。
也有可能在相同的core上超过一个中断等待被处理,但CPU interface一次仅发出一个中断。Top-level中断handler重复上述时序直到它读取到特定的interrupt ID 1023,这表明在这个core上没有其他的pending中断。这个特定的interrupt ID被称为spurious interrupt ID。
spurious interrupt ID为保留值,在系统中可以被赋值给任何设备。当top-level handler读取到spurious interrupt ID时,它可以完成执行,并在处理中断前准备让core唤醒task。
GIC管理多个中断源的输入并将他们传递给IRQ或FIQ请求。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。