Chapter9 Exception and Interrupt Handling

嵌入式系统的核心是异常处理程序。它们负责处理外部系统产生的错误、中断和其他事件。高效的处理程序可以显著提高系统性能。确定一个良好的处理方法的过程可能会复杂、具有挑战性,但也是有趣的。
在本章中,我们将介绍异常处理的理论和实践,特别是在ARM处理器上处理中断的方法。ARM处理器有七种异常类型,可以中止正常的指令顺序执行:数据中止、快速中断请求、中断请求、预取中止、软件中断、复位和未定义指令。
本章分为三个主要部分:
■ 异常处理。异常处理涵盖了ARM处理器处理异常的具体细节。
■ 中断。ARM将中断定义为一种特殊类型的异常。本节讨论了中断请求的使用,以及介绍了与中断处理相关的一些常见术语、特性和机制。
■ 中断处理方案。最后一节提供了一组中断处理方法。每种方法都附带了一个示例实现。
9.1 Exception Handling
异常是指任何需要中止正常顺序执行指令的条件。例如,当ARM核心复位时,当指令获取或内存访问失败时,当遇到未定义指令时,当执行软件中断指令时,或者当外部中断被触发时。异常处理是处理这些异常的方法。
大多数异常都有一个关联的软件异常处理程序——当异常发生时执行的软件例程。例如,数据中止异常将有一个数据中止处理程序。处理程序首先确定异常的原因,然后处理异常。处理可以在处理程序内部进行,也可以通过跳转到特定的服务例程来进行。复位异常是一个特殊情况,因为它用于初始化嵌入式系统。
本节涵盖以下异常处理主题:
■ ARM处理器模式和异常
■ 向量表
■ 异常优先级
■ 链接寄存器偏移量
9.1.1 ARM Processor Exceptions and Modes

表9.1列出了ARM处理器的异常。每个异常都会导致核心进入特定的模式。此外,通过更改cpsr,可以手动进入ARM处理器的任何模式。用户模式和系统模式是唯一不由相应异常进入的两种模式,换句话说,要进入这些模式,必须修改cpsr。当异常引起模式改变时,核心会自动执行以下操作:
■ 将cpsr保存到异常模式的spsr中
■ 将pc保存到异常模式的lr中
■ 将cpsr设置为异常模式
■ 将pc设置为异常处理程序的地址

图9.1显示了异常及其关联模式的简化视图。请注意,当异常发生时,ARM处理器总是切换到ARM状态。
9.1.2 Vector Table
第2章介绍了向量表——一张地址表,ARM核心在引发异常时会跳转到其中的地址。这些地址通常包含以下形式的分支指令:
■ B <address>—此分支指令相对于pc提供一个分支。
■ LDR pc, [pc, #offset]—此加载寄存器指令将处理程序地址从内存加载到pc。该地址是一个存储在向量表附近的绝对32位值。加载这个绝对字面值会导致在分支到特定处理程序时稍微延迟,因为需要额外的内存访问。但是,您可以分支到内存中的任何地址。
■ LDR pc, [pc, #-0xff0]—此加载寄存器指令从地址0xfffff030加载特定的中断服务例程地址到pc。当存在向量中断控制器(VIC PL190)时,才会使用此特定指令。
■ MOV pc, #immediate—此移动指令将立即值复制到pc。它允许您跨越整个地址空间,但受限于对齐方式。地址必须是通过右移偶数位得到的8位即时数。
向量表还可以包含其他类型的指令。例如,FIQ处理程序可能从偏移量 +0x1c 处开始。因此,FIQ处理程序可以立即从FIQ向量位置开始,因为它位于向量表的末尾。分支指令使pc跳转到可以处理特定异常的特定位置。

表9.2显示了每个异常的异常、模式和向量表偏移量。
Example 9.1

图9.2展示了一个典型的向量表。未定义指令项是一个分支指令,用于跳转到未定义处理程序。其他向量使用带有LDR加载到pc指令的间接地址跳转。
请注意,FIQ处理程序也使用了LDR加载到pc指令,并没有利用处理程序可以放置在FIQ向量入口位置的优势。
9.1.3 Exception Priorities
异常可以同时发生,因此处理器必须采用优先级机制。表9.3显示了在ARM处理器上发生的各种异常及其关联的优先级。例如,复位异常是最高优先级的异常,在处理器通电时发生。因此,当发生复位时,它优先于所有其他异常。同样,当发生数据中止异常时,它优先于除复位异常以外的所有其他异常。最低的优先级由两个异常共享,即软件中断和未定义指令异常。某些异常还通过在cpsr中设置I或F位来禁用中断,如表9.3所示。

每个异常的处理方式根据表9.3中列出的优先级来进行。以下是对异常及其处理方式的摘要,从最高优先级开始。
复位异常是最高优先级的异常,并且只要它被触发,就会始终被执行。复位处理程序初始化系统,包括设置内存和缓存。在启用IRQ或FIQ中断之前,应初始化外部中断源,以避免适当的处理程序设置之前出现虚假中断的可能性。复位处理程序还必须为所有处理器模式设置堆栈指针。在处理程序的前几条指令中,假定不会发生异常或中断。代码应设计成避免SWI、未定义指令和可能中止的内存访问,即要小心实现处理程序,以避免进一步触发异常。
当内存控制器或MMU指示已访问无效内存地址(例如,对于某个地址没有物理内存)或当前代码尝试读取或写入未具有正确访问权限的内存时,将发生数据中止异常。在数据中止处理程序内部可以引发FIQ异常,因为FIQ异常未被禁用。当完全服务于FIQ后,控制权将返回到数据中止处理程序。
当外部外设将FIQ引脚置为nFIQ时,将发生快速中断请求(FIQ)异常。FIQ异常是最高优先级的中断。核心在进入FIQ处理程序时禁用IRQ和FIQ异常。因此,除非软件重新启用IRQ和/或FIQ异常,否则没有外部源可以中断处理器。希望FIQ处理程序(以及中止、SWI和IRQ处理程序)经过精心设计以有效地处理异常。
当外部外设将IRQ引脚置为nIRQ时,将发生中断请求(IRQ)异常。IRQ异常是第二高优先级的中断。如果既没有FIQ异常也没有数据中止异常发生,则将进入IRQ处理程序。进入IRQ处理程序时,IRQ异常被禁用,并且应在当前中断源被清除之前保持禁用状态。
当尝试提取指令时发生预取中止异常,表示发生了内存错误。当指令在流水线的执行阶段且没有发生更高优先级的其他异常时,会引发此异常。进入处理程序时,IRQ异常将被禁用,但FIQ异常将保持不变。如果启用了FIQ并发生了FIQ异常,则在服务预取中止时可以采取该异常。
当执行SWI指令并且没有标记任何其他更高优先级的异常时,将发生软件中断(SWI)异常。进入处理程序时,cpsr将被设置为超级用户模式。
如果系统使用嵌套的SWI调用,则在跳转到嵌套SWI之前必须存储链接寄存器r14和spsr,以避免链接寄存器和spsr可能被破坏。
当ARM或Thumb指令集之外的指令达到流水线的执行阶段且没有标记任何其他异常时,将发生未定义指令异常。ARM处理器会向协处理器“询问”它们是否能够处理此作为协处理器指令的指令。由于协处理器遵循流水线,因此指令识别可以在核心的执行阶段进行。如果没有协处理器认领该指令,则会引发未定义指令异常。
由于SWI指令和未定义指令不能同时发生(换句话说,正在执行的指令不能同时是SWI指令和未定义指令),它们具有相同的优先级。
9.1.4 Link Register Offsets
当异常发生时,链接寄存器(LR)会根据当前PC设置为特定的地址。例如,当引发IRQ异常时,链接寄存器LR指向上一条已执行指令的地址加上8。必须小心确保异常处理程序不会破坏LR寄存器,因为LR用于从异常处理程序返回。IRQ异常只在当前指令执行后发生,因此返回地址必须指向下一条指令,即LR-4。表9.4提供了不同异常的一些有用地址。

接下来的三个示例展示了从IRQ或FIQ异常处理程序中返回的不同方法。
示例9.2
这个示例展示了从IRQ和FIQ处理程序中返回的典型方法是使用SUBS指令:

handler
<handler code>
...
SUBS pc, r14, #4
; pc=r14-4

由于SUB指令末尾有一个S,且PC是目标寄存器,所以CPSR将自动从SPSR寄存器中恢复。
示例9.3
这个示例展示了另一种方法,在处理程序开始时从链接寄存器R14中减去偏移量。

handler
SUB r14, r14, #4
; r14-=4
...
<handler code>
...
MOVS pc, r14
; return

服务完成后,通过将链接寄存器R14移动到PC并从SPSR中恢复CPSR来返回到正常执行。
示例9.4
最后一个示例使用中断栈来存储链接寄存器。这种方法首先从链接寄存器中减去一个偏移量,然后将其存储到中断栈中。

handler
SUB r14, r14, #4
; r14-=4
STMFD r13!,{r0-r3, r14}
; store context
...
<handler code>
...
LDMFD r13!,{r0-r3, pc}ˆ
; return

为了返回到正常执行,使用LDM指令来加载PC。指令中的^符号强制将CPSR从SPSR中恢复。
9.2 Interrupts
在ARM处理器上有两种类型的中断可用。第一种类型的中断是由外部外围设备引发的异常,即IRQ和FIQ。第二种类型是由特定指令引发的异常,即SWI指令。这两种类型都会暂停程序的正常流程。
在本节中,我们主要关注IRQ和FIQ中断。我们将涵盖以下内容:
■ 分配中断
■ 中断延迟
■ IRQ和FIQ异常
■ 基本中断栈设计和实现
9.2.1 Assigning Interrupts
系统设计人员可以决定哪个硬件外设可以产生哪个中断请求。这个决策可以在硬件或软件(或两者兼有)中实现,具体取决于使用的嵌入式系统。
中断控制器将多个外部中断连接到两个ARM中的一个中断请求。复杂的控制器可以编程,允许外部中断源引发IRQ或FIQ异常。
在分配中断方面,系统设计人员采用了标准的设计实践:
■ 软件中断通常保留用于调用特权操作系统例程。例如,可以使用SWI指令将正在用户模式下运行的程序切换到特权模式。关于SWI处理程序示例,请参阅第11章。
■ 中断请求通常分配给通用中断。例如,定期定时器中断用于强制进行上下文切换,往往是IRQ异常。IRQ异常的优先级较低,中断延迟较高(将在下一节中讨论),而FIQ异常则相反。
■ 快速中断请求通常保留给需要快速响应时间的单个中断源,例如专门用于移动内存块的直接内存访问。因此,在嵌入式操作系统设计中,FIQ异常用于特定应用,将IRQ异常用于更通用的操作系统活动。
9.2.2 Interrupt Latency
在基于中断驱动的嵌入式系统中,必须解决中断延迟的问题,即从外部中断请求信号被触发到特定中断服务例程(ISR)的第一条指令被获取的时间间隔。
中断延迟取决于硬件和软件的组合。系统架构师必须平衡系统设计,以处理多个同时发生的中断源,并尽量最小化中断延迟。如果中断没有及时处理,系统响应时间将变慢。

软件处理程序有两种主要方法来最小化中断延迟。第一种方法是使用嵌套中断处理程序,即使当前正在处理一个现有的中断,也允许进一步的中断发生(参见图9.3)。这通过在中断源得到服务后立即重新启用中断(以防止生成更多的中断),但在中断处理完成之前实现。一旦处理完嵌套中断,就会将控制权交还给原始的中断服务例程。
第二种方法涉及优先级设置。您可以编程中断控制器忽略与您正在处理的中断相同或较低优先级的中断,以便仅有优先级更高的任务才能中断您的处理程序。然后您可以重新启用中断。
处理器会在较低优先级的中断上花费时间,直到出现一个更高优先级的中断。因此,高优先级的中断比低优先级的中断具有更低的平均中断延迟,通过加速关键的时序敏感中断的完成时间来减少延迟。
9.2.3 IRQ and FIQ Exceptions
只有在cpsr中清除了特定中断屏蔽位时,IRQ和FIQ异常才会发生。ARM处理器在处理中断之前会继续执行当前流水线里的执行阶段的指令——这是设计确定性中断处理程序的一个重要因素,因为某些指令需要更多的周期来完成执行阶段。
当未屏蔽中断引起IRQ或FIQ异常时,处理器硬件会经过标准的过程:
1. 处理器切换到特定的中断请求模式,这反映了所引发的中断。
2. 上一个模式的cpsr保存到新的中断请求模式的spsr中。
3. pc保存在新的中断请求模式的lr中。
4. 中断被禁用,即在cpsr中禁用IRQ或同时禁用IRQ和FIQ异常。 这立即停止引发相同类型的另一个中断请求。
5. 处理器跳转到向量表中的特定入口。
该过程根据引发的中断类型略有不同。我们将以示例说明两种中断。第一个示例展示了当引发IRQ异常时会发生什么,而第二个示例展示了引发FIQ异常时会发生什么。
示例9.5

图9.4显示了当处理器处于用户模式时引发IRQ异常时会发生的情况。处理器从状态1开始。在这个示例中,cpsr中的IRQ和FIQ异常位都已启用。
当发生IRQ时,处理器进入状态2。这个转换会自动将IRQ位设置为1,禁止任何进一步的IRQ异常。然而,FIQ异常保持启用,因为FIQ具有更高的优先级,所以当引发低优先级的IRQ异常时,它不会被禁用。cpsr处理器模式切换到IRQ模式。用户模式cpsr会自动复制到spsr_irq中。
当中断发生时,寄存器r14_irq被赋予中断发生时的pc值。然后,pc被设置为向量表中的IRQ入口+0x18的地址。
在状态3中,软件处理程序接管并调用适当的中断服务例程来处理中断来源。完成后,处理器模式回退到状态1中的原始用户模式代码。
示例9.6

图9.5展示了一个FIQ异常的示例。处理器经历了与IRQ异常类似的过程,但是与只屏蔽进一步的IRQ异常不同,处理器还屏蔽了进一步的FIQ异常。这意味着在状态3中进入软件处理程序时,两个中断都被禁用了。
切换到FIQ模式意味着无需保存寄存器r8到r12,因为这些寄存器在FIQ模式下是分段的。这些寄存器可以用来保存临时数据,例如缓冲区指针或计数器。这使得FIQ非常适合处理单源、高优先级、低延迟的中断。
9.2.3.1 Enabling and Disabling FIQ and IRQ Exceptions
ARM处理器核心有一个简单的过程来手动启用和禁用中断,涉及到在处理器处于特权模式下修改cpsr。

表9.5展示了如何启用IRQ和FIQ中断。该过程使用了三条ARM指令。
第一条指令MRS将cpsr的内容复制到寄存器r1中。第二条指令清除IRQ或FIQ掩码位。第三条指令将更新后的内容从寄存器r1复制回cpsr,从而启用中断请求。后缀"_c"标识被更新的位域是cpsr的控制字段位[7:0]。(更多细节请参考第2章。)表9.6展示了禁用或屏蔽中断请求的类似过程。
重要的是要理解,在MSR指令完成流水线的执行阶段之后,中断请求才会被启用或禁用。在MSR完成这个阶段之前,中断仍然可以被触发或屏蔽。
要同时启用和禁用IRQ和FIQ异常,需要对第二条指令进行轻微修改。数据处理BIC或ORR指令上的立即数值必须更改为0xc0,以启用或禁用两个中断。
9.2.4 Basic Interrupt Stack Design and Implementation
异常处理程序广泛使用堆栈,每种模式都有一个专用的寄存器保存堆栈指针。异常堆栈的设计取决于以下因素:
- 操作系统要求:每个操作系统对堆栈设计都有自己的需求。
- 目标硬件:目标硬件为堆栈在内存中的大小和位置提供了物理限制。
对于堆栈,需要做两个设计决策:
- 位置决定了堆栈在内存映射中开始的位置。大多数基于ARM的系统设计中,堆栈向下扩展,堆栈的顶部位于较高的内存地址。
- 堆栈大小取决于处理程序的类型,是嵌套中断还是非嵌套中断。嵌套中断处理程序需要更多的内存空间,因为堆栈随着嵌套中断的数量而增长。
良好的堆栈设计试图避免堆栈溢出,即堆栈扩展到分配的内存之外,因为这会导致嵌入式系统不稳定。有软件技术可以识别溢出,并在不可修复的内存损坏发生之前采取纠正措施来修复堆栈。两种主要方法是:(1)使用内存保护和(2)在每个程序开始时调用堆栈检查函数。
在启用中断之前,必须设置IRQ模式堆栈,通常是在系统的初始化代码中完成。在简单的嵌入式系统中,了解堆栈的最大大小非常重要,因为在引导程序的初始阶段,固件会预留堆栈大小。

图9.6显示了线性地址空间中两种典型的内存布局。布局A显示了传统的堆栈布局,其中中断堆栈存储在代码段下方。布局B将中断堆栈放置在内存的顶部,用户堆栈上方。布局B相比于布局A的主要优点是,在发生堆栈溢出时不会破坏向量表,因此系统可以在识别到溢出后进行自我纠正。

每种处理器模式都需要设置一个堆栈。这是每次处理器重置时都要执行的操作。图9.7显示了使用布局A的实现方式。为了帮助设置内存布局,声明了一组定义,将内存区域名称映射到绝对地址。
例如,用户堆栈被赋予标签USR_Stack,并设置为地址0x20000。监管员堆栈设置在IRQ堆栈的下方128字节的地址处。

USR_Stack EQU 0x20000
IRQ_Stack EQU 0x8000
SVC_Stack EQU IRQ_Stack-128

为了帮助切换到不同的处理器模式,我们声明了一组定义,将每个处理器模式映射到特定的模式位模式。然后可以使用这些标签将cpsr设置为新的模式。

Usr32md EQU 0x10
; User mode
FIQ32md EQU 0x11
; FIQ mode
IRQ32md EQU 0x12
; IRQ mode
SVC32md EQU 0x13
; Supervisor mode
Abt32md EQU 0x17
; Abort mode
Und32md EQU 0x1b
; Undefined instruction mode
Sys32md EQU 0x1f
; System mode

为了安全起见,声明了一个定义来禁用cpsr中的IRQ和FIQ异常:
NoInt EQU 0xc0 ; 禁用中断
NoInt通过将屏蔽位设置为1来屏蔽两个中断。
初始化代码从为每个处理器模式设置堆栈寄存器开始。堆栈寄存器r13是在模式切换发生时始终会切换的寄存器之一。代码首先初始化IRQ堆栈。出于安全原因,最好使用NoInt和新模式之间的按位或运算来确保中断已禁用。
每种模式都必须设置堆栈。下面是在处理器内核从复位状态退出时如何设置三个不同堆栈的示例。请注意,由于这是一个基本示例,因此我们没有实现中止、FIQ和未定义指令模式的堆栈。如果需要这些堆栈,则使用非常相似的代码。
■ 监管者模式堆栈——处理器内核以监管者模式启动,因此SVC堆栈设置涉及将寄存器r13_svc加载到由SVC_NewStack指向的地址。对于此示例,该值为SVC_Stack。

LDR r13, SVC_NewStack ; r13_svc
...
SVC_NewStack
DCD SVC_Stack

■ IRQ模式堆栈——要设置IRQ堆栈,必须将处理器模式更改为IRQ模式。这是通过将cpsr位模式存储到寄存器r2中实现的。然后将寄存器r2复制到cpsr中,将处理器置于IRQ模式。此操作立即使寄存器r13_irq可见,并且可以将其分配给IRQ_Stack值。

MOV r2, #NoInt|IRQ32md
MSR cpsr_c, r2
LDR r13, IRQ_NewStack ; r13_irq
...
IRQ_NewStack
DCD IRQ_Stack

■ 用户模式堆栈——通常,用户模式堆栈是最后设置的,因为当处理器处于用户模式时,没有直接的方法来修改cpsr。另一种方法是强制处理器进入系统模式以设置用户模式堆栈,因为这两种模式共享相同的寄存器。

MOV r2, #Sys32md
MSR cpsr_c, r2
LDR r13, USR_NewStack ; r13_usr
...
USR_NewStack
DCD USR_Stack

对于每种模式使用单独的堆栈而不是使用单一堆栈进行处理有一个主要的优势:可以对错误的任务进行调试并与系统的其余部分隔离开来。这样可以更容易地定位和解决特定模式下的问题,同时保持其他模式的稳定性。
9.3 Interrupt Handling Schemes
在本节中,我们将介绍多种不同的中断处理方案,从简单的非嵌套中断处理程序到更复杂的分组优先级中断处理程序。每个方案都以一个通用描述和示例实现的形式呈现。
所涵盖的方案包括以下内容:
■ 非嵌套中断处理程序按顺序处理和服务各个中断。它是最简单的中断处理程序。
■ 嵌套中断处理程序处理多个中断,没有优先级分配。
■ 可重入中断处理程序处理可以优先级排序的多个中断。
■ 优先级简单中断处理程序处理优先级中断。
■ 优先级标准中断处理程序比低优先级中断更快地处理高优先级中断。
■ 优先级直接中断处理程序比低优先级中断更快地处理高优先级中断,并直接进入特定的服务例程。
■ 优先级分组中断处理程序是一种处理按不同优先级分组的中断的机制。
■ 基于VIC PL190的中断服务例程展示了向量中断控制器(VIC)如何改变中断服务例程的设计。
9.3.1 Nonnested Interrupt Handler
//非嵌套中断处理程序
最简单的中断处理程序是非嵌套方式,即在控制权返回到被中断的任务或进程之前,禁用中断。由于非嵌套中断处理程序一次只能处理一个中断,这种形式的处理程序不适用于需要处理多个具有不同优先级的中断的复杂嵌入式系统。

图9.8显示了在实现了简单非嵌套中断处理程序的系统中引发中断时发生的各个阶段:
1. 禁用中断 - 当IRQ异常引发时,ARM处理器将禁止进一步发生IRQ异常。处理器模式设置为适当的中断请求模式,并将先前的cpsr复制到新可用的spsr_{interrupt request mode}中。然后,处理器将设置pc指向向量表中正确的条目,并执行指令。该指令将更改pc以指向特定的中断处理程序。
2. 保存上下文 - 在进入处理程序代码时,它保存当前处理器模式的非银行寄存器的子集。
3. 中断处理程序 - 处理程序然后识别外部中断源并执行适当的中断服务例程(ISR)。
4. 中断服务例程 - ISR服务外部中断源并复位中断。
5. 恢复上下文 - ISR返回到中断处理程序,中断处理程序恢复上下文。
6. 启用中断 - 最后,为了从中断处理程序返回,将spsr_{interrupt request mode}恢复回cpsr。然后将pc设置为引发中断后的下一条指令。
在这个示例中,假设IRQ堆栈已经由初始化代码正确设置。

interrupt_handler
SUB r14,r14,#4
; adjust lr
STMFD r13!,{r0-r3,r12,r14}
; save context
<interrupt service routine>
LDMFD r13!,{r0-r3,r12,pc}ˆ
; return

第一条指令将链接寄存器r14_irq设置为返回到被中断的任务或进程的正确位置。根据第9.1.4节的描述,由于流水线的原因,在进入IRQ处理程序时,链接寄存器指向返回地址后四个字节,因此处理程序必须从链接寄存器减去四来补偿这个差异。链接寄存器存储在堆栈上。为了返回到被中断的任务,链接寄存器的内容从堆栈中恢复,并移动到pc寄存器。
注意,由于ATPCS(ARM Procedure Call Standard),也需要保留寄存器r0到r3和寄存器r12。这允许在处理程序内部调用符合ATPCS的子程序。
STMFD指令通过将一部分寄存器保存到堆栈上来保存上下文。为了减少中断延迟,我们只保存了最少数量的寄存器,因为执行STMFD或LDMFD指令的时间与传输的寄存器数量成比例。这些寄存器保存到由寄存器r13_{interrupt request mode}指向的堆栈中。
如果您在系统中使用高级语言,重要的是要了解编译器的过程调用约定,因为它会影响保存的寄存器及其保存顺序。例如,ARM编译器在子程序调用中保留了寄存器r4到r11,因此除非它们将被中断处理程序使用,否则没必要保留它们。如果没有调用C例程,则可能不需要保存所有寄存器。只有在寄存器已经保存到中断堆栈上时,才能安全地调用C函数。
在非嵌套中断处理程序中,不需要保存spsr,因为它不会被后续的中断破坏。
处理程序的最后,LDMFD指令将恢复上下文并从中断处理程序返回。LDMFD指令末尾的^表示cpsr将从spsr中恢复,这仅在同时加载了pc时有效。如果没有加载pc,则^将恢复用户银行寄存器。
在该处理程序中,所有处理都在中断处理程序内完成,并直接返回到应用程序。
一旦进入中断处理程序并保存了上下文,处理程序必须确定中断源。以下代码显示了如何简单确定中断源的示例。IRQStatus是中断状态寄存器的地址。如果无法确定中断源,则可以将控制权传递给另一个处理程序。在本示例中,我们将控制权传递给调试监视器。或者我们也可以忽略该中断。

interrupt_handler
SUB r14,r14,#4
; r14-=4
STMFD sp!,{r0-r3,r12,r14} ; save context
LDR r0,=IRQStatus ; interrupt status addr
LDR r0,[r0]
; get interrupt status
TST r0,#0x0080
; if counter timer
BNE timer_isr
; then branch to ISR
TST r0,#0x0001
; else if button press
BNE button_isr
; then call button ISR
LDMFD sp!,{r0-r3,r12,r14} ; restore context
LDR pc,=debug_monitor ; else debug monitor

在上述代码中,有两个中断服务程序(ISR):timer_isr和button_isr。它们分别映射到IRQStatus寄存器中的特定位,分别是0x0080和0x0001。
非嵌套中断处理程序的总结:
- 按顺序处理和服务各个中断。
- 中断延迟较高,在服务中断时无法处理其他发生的中断。
- 优点:相对容易实现和调试。
- 缺点:不能用于处理具有多个优先级中断的复杂嵌入式系统。
9.3.2 Nested Interrupt Handler
嵌套中断处理程序允许在当前调用的处理程序内发生另一个中断。这是通过在处理程序完全服务当前中断之前重新启用中断来实现的。
对于实时系统,这个特性增加了系统的复杂性,但也提高了性能。额外的复杂性引入了可能导致系统故障的微妙时间问题,而这些微妙问题可能极难解决。嵌套中断方法被精心设计,以避免这些类型的问题。这是通过保护上下文恢复不受中断干扰,以防止下一个中断填满堆栈(导致堆栈溢出)或破坏任何寄存器来实现的。
任何嵌套中断处理程序的首要目标是快速响应中断,使处理程序既不等待异步异常,也不强制它们等待处理程序。第二个目标是在处理各种中断的同时,不延迟常规同步代码的执行。
复杂性的增加意味着设计人员必须在效率和安全性之间取得平衡,采用一种防御性的编码风格,假设问题会发生。处理程序必须检查堆栈并在可能的情况下保护寄存器免受破坏。

图9.9展示了一个嵌套中断处理程序。从图中可以看出,与第9.3.1节中描述的简单非嵌套中断处理程序相比,处理程序要复杂得多。
嵌套中断处理程序的入口代码与简单非嵌套中断处理程序相同,只是在退出时,处理程序会测试一个由ISR更新的标志位。该标志位指示是否需要进一步处理。如果不需要进一步处理,则中断服务例程完成,处理程序可以退出。如果需要进一步处理,处理程序可能会执行以下几个操作:重新使能中断和/或进行上下文切换。
重新使能中断涉及从IRQ模式切换到SVC模式或系统模式。在IRQ模式下,不能简单地重新使能中断,因为这可能会导致链接寄存器r14_irq的破坏,特别是在执行BL指令后发生中断的情况下。这个问题将在第9.3.3节中更详细地讨论。
执行上下文切换意味着清空IRQ堆栈,因为只有当IRQ堆栈上有数据时,处理程序才不执行上下文切换。保存在IRQ堆栈上的所有寄存器必须转移到任务的堆栈上,通常是在SVC堆栈上。然后,剩余的寄存器必须保存在任务堆栈上。它们被转移到堆栈上的一个保留内存块,称为堆栈帧。
示例9.9
这个嵌套中断处理程序示例基于图9.9中的流程图。本节的其余部分将逐步介绍处理程序,并详细描述各个阶段。

Maskmd
EQU 0x1f
; processor mode mask
SVC32md
EQU 0x13
; SVC mode
I_Bit
EQU 0x80
; IRQ bit
FRAME_R0
EQU 0x00
FRAME_R1
EQU FRAME_R0+4
FRAME_R2
EQU FRAME_R1+4
FRAME_R3
EQU FRAME_R2+4
FRAME_R4
EQU FRAME_R3+4
FRAME_R5
EQU FRAME_R4+4
FRAME_R6
EQU FRAME_R5+4
FRAME_R7
EQU FRAME_R6+4
FRAME_R8
EQU FRAME_R7+4
FRAME_R9
EQU FRAME_R8+4
FRAME_R10 EQU FRAME_R9+4
FRAME_R11 EQU FRAME_R10+4
FRAME_R12 EQU FRAME_R11+4
FRAME_PSR EQU FRAME_R12+4
FRAME_LR
EQU FRAME_PSR+4
FRAME_PC
EQU FRAME_LR+4
FRAME_SIZE EQU FRAME_PC+4
IRQ_Entry ; instruction
state : comment
SUB r14,r14,#4
; 2 :
STMDB r13!,{r0-r3,r12,r14} ; 2 : save context
<service interrupt>
BL read_RescheduleFlag ; 3 : more processing
CMP r0,#0
; 3 : if processing?
LDMNEIA r13!,{r0-r3,r12,pc}ˆ ; 4 : else return
MRS r2,spsr
; 5 : copy spsr_irq
MOV r0,r13
; 5 : copy r13_irq
ADD r13,r13,#6*4
; 5 : reset stack
MRS r1,cpsr
; 6 : copy cpsr
BIC r1,r1,#Maskmd
; 6 :
ORR r1,r1,#SVC32md
; 6 :
MSR cpsr_c,r1
; 6 : change to SVC
SUB r13,r13,#FRAME_SIZE-FRAME_R4;7: make space
STMIA r13,{r4-r11}
; 7 : save r4-r11
LDMIA r0,{r4-r9}
; 7 : restore r4-r9
BIC r1,r1,#I_Bit
; 8 :
MSR cpsr_c,r1
; 8 : enable IRA
STMDB r13!,{r4-r7}
; 9 : save r4-r7 SVC
STR r2,[r13,#FRAME_PSR] ; 9 : save PSR
STR r8,[r13,#FRAME_R12] ; 9 : save r12
STR r9,[r13,#FRAME_PC]
; 9 : save pc
STR r14,[r13,#FRAME_LR] ; 9 : save lr
<complete interrupt service routine>
LDMIA r13!,{r0-r12,r14}
; 11 : restore context
MSR spsr_cxsf,r14
; 11 : restore spsr
LDMIA r13!,{r14,pc}ˆ
; 11 : return

这个示例使用了一个堆栈帧结构。除了堆栈寄存器r13之外,所有寄存器都保存在帧上。寄存器的顺序并不重要,只有FRAME_LR和FRAME_PC应该是帧中的最后两个寄存器,因为我们将使用一条指令返回:
LDMIA r13!, {r14, pc}
根据所使用的操作系统或应用程序的要求,可能还需要将其他寄存器保存到堆栈帧中。例如:
■ 当操作系统需要支持用户模式和SVC模式时,会保存寄存器r13_usr和r14_usr。
■ 当系统使用硬件浮点数时,会保存浮点寄存器。
这个示例中声明了一些预定义的宏。这些宏将cpsr/spsr的各种变化映射到特定的标签(例如I_Bit)。
还声明了一组预定义的宏,将各种帧寄存器引用映射到帧指针偏移量。当重新使能中断并且需要将寄存器存储到堆栈帧中时,这非常有用。在这个示例中,我们将堆栈帧存储在SVC堆栈上。
这个示例处理程序的入口点使用了与简单的非嵌套中断处理程序相同的代码。首先修改链接寄存器r14,使其指向正确的返回地址,然后将上下文和链接寄存器r14保存到IRQ堆栈上。
然后,中断服务例程对中断进行处理。当处理完成或部分完成时,控制权被传递回处理程序。处理程序然后调用一个名为read_RescheduleFlag的函数,该函数确定是否需要进一步处理。如果不需要进一步处理,它会在寄存器r0中返回一个非零值;否则返回零。请注意,我们没有包含read_RescheduleFlag的源代码,因为它是与实现相关的。
然后测试寄存器r0中的返回标志。如果寄存器不等于零,处理程序将恢复上下文并将控制权返回给挂起的任务。
将寄存器r0设置为零,表示需要进一步处理。首先保存spsr,将spsr_irq的副本移动到寄存器r2中。然后处理程序可以在后面的代码中将spsr存储在堆栈帧中。
将寄存器r13_irq指向的IRQ堆栈地址复制到寄存器r0中以备后用。下一步是将IRQ堆栈扁平化(清空)。这通过在栈顶添加6 * 4字节来实现,因为栈是向下增长的,可以使用ADD指令设置栈。
处理程序无需担心IRQ堆栈上的数据被另一个嵌套中断破坏,因为中断仍处于禁用状态,直到IRQ堆栈上的数据恢复完毕后,处理程序才会重新使能中断。
然后处理程序切换到SVC模式;中断仍处于禁用状态。将cpsr复制到寄存器r1并修改以将处理器模式设置为SVC。然后将寄存器r1写回cpsr,并将当前模式更改为SVC模式。新的cpsr的副本留在寄存器r1中供以后使用。
下一阶段是通过扩展栈来创建一个堆栈帧。寄存器r4到r11可以保存在堆栈帧中,这将释放足够的寄存器空间,以便从寄存器r0中还原IRQ堆栈中的其余寄存器。

此阶段,堆栈帧将包含表9.7中显示的信息。不在帧中的唯一寄存器是进入IRQ处理程序时存储的寄存器。
表9.8显示了与现有IRQ寄存器对应的SVC模式下的寄存器。现在处理程序可以从IRQ堆栈检索所有数据,并且可以安全地重新使能中断。

允许重新使能IRQ异常,处理程序已保存了所有重要的寄存器。现在可以完成堆栈帧。表9.9显示了一个完整的堆栈帧,可用于上下文切换或处理嵌套中断。
此阶段可以处理剩余的中断服务。通过将当前任务的控制块中的寄存器r13的当前值保存起来,并从新任务的控制块加载一个新值,可以执行上下文切换。
现在可以返回到被中断的任务/处理程序,或者如果发生了上下文切换,可以返回到另一个任务。
9.3.3 Reentrant Interrupt Handler
//可重入中断处理程序

可重入中断处理程序是一种处理多个中断的方法,其中中断根据优先级进行过滤,这对于要求具有较低延迟的高优先级中断非常重要。传统的嵌套中断处理程序无法实现这种优先级过滤。
可重入中断处理程序与嵌套中断处理程序的基本区别在于,可重入中断处理程序在早期阶段重新使能中断,这可以减少中断延迟。在稍后的部分中,将详细描述关于早期重新使能中断的一些问题。
在ARM处理器上,所有中断都必须在SVC、系统、未定义指令或中止模式下进行服务。
如果在中断模式下重新使能中断,并且中断例程执行了BL子程序调用指令,那么子程序的返回地址将设置在寄存器r14_irq中。该地址将随后被中断破坏,中断会覆盖返回地址到r14_irq中。为了避免这种情况,中断例程应该切换到SVC或系统模式。然后,BL指令可以使用寄存器r14_svc来存储子程序的返回地址。在通过cpsr重新使能中断之前,必须通过在中断控制器中设置一个位来禁用中断的源。
这样,可重入中断处理程序可以实现中断的优先级过滤,并且能够减少中断的延迟。这对于要求高可靠性和响应性的系统来说非常重要,特别是在嵌入式系统和实时操作系统中。
如果在处理完成之前在cpsr中重新使能中断,并且未禁用中断源,则会立即重新生成中断,从而导致无限中断序列或竞态条件。大多数中断控制器都有一个中断屏蔽寄存器,允许屏蔽一个或多个中断,但是其余的中断仍然是使能的。
由于中断在SVC模式下服务(例如,在任务的堆栈上),因此中断堆栈未被使用。相反,IRQ堆栈寄存器r13被用来指向一个12字节的数据结构,该结构将用于在中断进入时临时存储一些寄存器。
示例9.10
假设已经设置寄存器r13_irq以指向一个12字节的数据结构,并且不指向标准的IRQ堆栈。诸如IRQ_SPSR之类的偏移量用于指向数据结构内的位置。与所有中断处理程序一样,需要一些标准的定义来修改cpsr和spsr寄存器。

IRQ_R0 EQU 0
IRQ_spsr EQU 4
IRQ_R14 EQU 8
Maskmd EQU 0x1f
; mask mode
SVC32md EQU 0x13
; SVC mode
I_Bit
EQU 0x80
; IRQ bit
ic_Base EQU 0x80000000
IRQStatus EQU 0x0
IRQRawStatus EQU 0x4
IRQEnable EQU 0x8
IRQEnableSet EQU 0x8
IRQEnableClear EQU 0xc
IRQ_Entry ; instruction
state : comment
SUB r14, r14, #4
; 2 : r14_irq-=4
STR r14, [r13, #IRQ_R14] ; 2 : save r14_irq
MRS r14, spsr
; 2 : copy spsr
STR r14, [r13, #IRQ_spsr] ; 2 : save spsr
STR r0, [r13, #IRQ_R0] ; 2 : save r0
MOV r0, r13
; 2 : copy r13_irq
MRS r14, cpsr
; 3 : copy cpsr
BIC r14, r14, #Maskmd ; 3 :
ORR r14, r14, #SVC32md ; 3 :
MSR cpsr_c, r14
; 3 : enter SVC mode
STR r14, [r13, #-8]!
; 4 : save r14
LDR r14, [r0, #IRQ_R14] ; 4 : r14_svc=r14_irq
STR r14, [r13, #4]
; 4 : save r14_irq
LDR r14, [r0, #IRQ_spsr] ; 4 : r14_svc=spsr_irq
LDR r0, [r0, #IRQ_R0] ; 4 : restore r0
STMDB r13!, {r0-r3,r8,r12,r14} ; 4 : save context
LDR r14, =ic_Base
; 5 : int crtl address
LDR r8, [r14, #IRQStatus] ; 5 : get int status
STR r8, [r14, #IRQEnableClear];5: clear interrupts
MRS r14, cpsr
; 6 : r14_svc=cpsr
BIC r14, r14, #I_Bit
; 6 : clear I-Bit
MSR cpsr_c, r14
; 6 : enable IRQ int
BL process_interrupt ; 7 : call ISR
LDR r14, =ic_Base
; 9 : int ctrl address
STR r8, [r14, #IRQEableSet] ; 9 : enable ints
BL read_RescheduleFlag ; 9 : more processing
CMP r0, #0
; 8 : if processing
LDMNEIA r13!, {r0-r3,r8,r12,r14} ; 8 : then load context
MSRNE spsr_cxsf, r14
; 8 : update spsr
LDMNEIA r13!, {r14, pc}ˆ
; 8 : return
LDMIA r13!, {r0-r3, r8} ; 10 : else load reg
STMDB r13!, {r0-r11}
; 10 : save context
BL continue_servicing ; 11 : continue service
LDMIA r13!, {r0-r12, r14} ; 12 : restore context
MSR spsr_cxsf, r14
; 12 : update spsr
LDMIA r13!, {r14, pc}ˆ
; 12 : return

处理的开始包括一个普通的中断入口,其中从寄存器r14_irq中减去了4。
现在重要的是为由寄存器r13_irq指向的数据结构中的各个字段分配值。记录的寄存器包括r14_irq、spsr_irq和r0。寄存器r0用于在切换到SVC模式时传递指向数据结构的指针,因为寄存器r0不会被分组保存。这就是为什么不能使用寄存器r13_irq来进行此操作:它在SVC模式下不可见。
将指向数据结构的指针通过将寄存器r13_irq复制到r0中来保存。

处理程序将使用操作cpsr的标准过程将处理器设置为SVC模式。SVC模式下的链接寄存器r14被保存在SVC堆栈上。减去8为堆栈提供了两个32位字的空间。
然后,恢复并将寄存器r14_irq存储在SVC堆栈上。现在,IRQ和SVC的链接寄存器r14都存储在SVC堆栈上。
从传递到SVC模式的数据结构中恢复剩下的IRQ上下文。现在,寄存器r14_svc将包含IRQ模式的spsr。
然后,寄存器被保存到SVC堆栈上。寄存器r8用于保存在中断处理程序中被禁用的中断的中断屏蔽位。它们将在稍后重新启用。
然后禁用中断源。嵌入式系统此时会对中断进行优先级排序,并禁用低于当前优先级的所有中断,以防止低优先级中断锁定高优先级中断。中断优先级排序将在本章后面讨论。
由于中断源已经清除,现在可以安全地重新启用IRQ异常。通过清除cpsr中的i位来实现。请注意,中断控制器仍然禁用外部中断。
现在可以处理中断了。中断处理不应尝试进行上下文切换,因为外部源中断已被禁用。如果在中断处理过程中需要进行上下文切换,则应设置一个标志,稍后由中断处理程序接收。现在可以安全地重新启用外部中断。
处理程序需要检查是否需要进一步处理。如果寄存器r0中的返回值非零,则不需要进一步处理。如果为零,则处理程序恢复上下文,然后将控制权返回到挂起的任务。
现在必须创建一个堆栈帧,以便服务例程可以完成。这是通过恢复一部分上下文,然后将完整的上下文保存回SVC堆栈来实现的。
调用完成中断服务的子例程continue_servicing。此例程未提供,因为它与具体实现有关。
中断例程完成服务后,可以将控制权交还给挂起的任务。
总结: 可重入中断处理程序
■ 处理多个可以优先级排序的中断。
■ 中断延迟低。
■ 优点:处理具有不同优先级的中断。
■ 缺点:趋向于更复杂。
9.3.4 Prioritized Simple Interrupt Handler
非嵌套中断处理程序和嵌套中断处理程序都按先到先服务的顺序处理中断。相比之下,优先级中断处理程序将特定的中断源与优先级级别相关联。优先级级别用于确定中断的处理顺序。因此,较高优先级的中断将优先于较低优先级的中断,这在许多嵌入式系统中是一种特别理想的特性。
处理优先级的方法可以在硬件或软件中实现。对于硬件优先级,处理程序设计更简单,因为中断控制器将提供需要服务的当前最高优先级中断。这些系统在启动时需要更多的初始化代码,因为必须在系统启动之前构建中断和相关优先级级别表;另一方面,软件优先级需要额外的外部中断控制器的帮助。这个中断控制器必须提供一组最小功能,包括能够设置和取消屏蔽、读取中断状态和源。
本节的其余部分将介绍一个软件优先级技术,因为它是一种通用方法,并不依赖于专门的中断控制器。为了描述优先级中断处理程序,我们将介绍一个基于ARM标准中断控制器的虚构中断控制器。该控制器接收多个中断源,并根据特定中断源的使能状态生成IRQ和/或FIQ信号。

图9.11显示了基于可重入中断处理程序的简单优先级中断处理程序的流程图。
示例 9.11
中断控制器有一个寄存器(IRQRawStatus),用于保存原始中断状态-在被控制器屏蔽之前的中断信号状态。IRQEnable寄存器确定了哪些中断被从处理器屏蔽。只能使用IRQEnableSet和IRQEnableClear来设置或清除该寄存器。表9.10显示了中断控制器寄存器名称、与控制器基地址的偏移量、读/写操作以及寄存器的描述。

I_Bit
EQU 0x80
PRIORITY_0 EQU 2
; Comms Rx
PRIORITY_1 EQU 1
; Comms Tx
PRIORITY_2 EQU 0
; Timer 1
PRIORITY_3 EQU 3
; Timer 2
BINARY_0 EQU 1 << PRIORITY_0 ; 1 << 2 0x00000004
BINARY_1 EQU 1 << PRIORITY_1 ; 1 << 1 0x00000002
BINARY_2 EQU 1 << PRIORITY_2 ; 1 << 0 0x00000001
BINARY_3 EQU 1 << PRIORITY_3 ; 1 << 3 0x00000008
MASK_3 EQU BINARY_3
MASK_2 EQU MASK_3+BINARY_2
MASK_1 EQU MASK_2+BINARY_1
MASK_0 EQU MASK_1+BINARY_0
ic_Base EQU 0x80000000
IRQStatus EQU 0x0
IRQRawStatus EQU 0x4
IRQEnable EQU 0x8
IRQEnableSet EQU 0x8
IRQEnableClear EQU 0xc
IRQ_Handler ; instruction
state : comment
SUB r14, r14, #4
; 2 : r14_irq -= 4
STMFD r13!, {r14}
; 2 : save r14_irq
MRS r14, spsr
; 2 : copy spsr_irq
STMFD r13!, {r10,r11,r12,r14} ; 2 : save context
LDR r14, =ic_Base
; 3 : int crtl addr
MOV r11, #PRIORITY_3
; 3 : default priority
LDR r10, [r14, #IRQStatus] ; 3 : load IRQ status
TST r10, #BINARY_3
; 4 : if Timer 2
MOVNE r11, #PRIORITY_3
; 4 : then P3(lo)
TST r10, #BINARY_2
; 4 : if Timer 1
MOVNE r11, #PRIORITY_2
; 4 : then P2
TST r10, #BINARY_1
; 4 : if Comm Tx
MOVNE r11, #PRIORITY_1
; 4 : then P1
TST r10, #BINARY_0
; 4 : if Comm Rx
MOVNE r11, #PRIORITY_0
; 4 : then P0(hi)
LDR r12, [r14,#IRQEnable] ; 4 : IRQEnable reg
ADR r10, priority_masks ; 4 : mask address
LDR r10, [r10,r11,LSL #2] ; 4 : priority value
AND r12, r12,r10
; 4 : AND enable reg
STR r12, [r14,#IRQEnableClear];4: disable ints
MRS r14, cpsr
; 4 : copy cpsr
BIC r14, r14, #I_Bit
; 4 : clear I-bit
MSR cpsr_c, r14
; 4 : enable IRQ ints
LDR pc, [pc, r11, LSL#2] ; 5 : jump to an ISR
NOP
;
DCD service_timer1
; timer1 ISR
DCD service_commtx
; commtx ISR
DCD service_commrx
; commrx ISR
DCD service_timer2
; timer2 ISR
priority_masks
DCD MASK_2
; priority mask 2
DCD MASK_1
; priority mask 1
DCD MASK_0
; priority mask 0
DCD MASK_3
; priority mask 3
...
service_timer1
STMFD r13!, {r0-r9}
; 6 : save context
<service routine>
LDMFD r13!, {r0-r10}
; 7 : restore context
MRS r11, cpsr
; 8 : copy cpsr
ORR r11, r11, #I_Bit
; 8 : set I-bit
MSR cpsr_c, r11
; 8 : disable IRQ
LDR r11, =ic_Base
; 8 : int ctrl addr
STR r12, [r11, #IRQEnableSet] ; 8 : enable ints
LDMFD r13!, {r11, r12, r14} ; 9 : restore context
MSR spsr_cxsf, r14
; 9 : set spsr
LDMFD r13!, {pc}ˆ
; 9 : return

大多数中断控制器还具有相应的寄存器用于 FIQ 异常,并且甚至允许将个别中断源连接到发送到核心的特定中断信号上。因此,通过对控制器进行编程,可以使特定的中断源引发 IRQ 或 FIQ 异常。
这些寄存器相对于内存中的基地址进行偏移。表 9.10 显示了从中断控制器基地址 `ic_Base` 开始的各个寄存器的所有偏移量。请注意,偏移量 `0x08` 同时用于 `IRQEnable` 和 `IRQEnableSet`。
在中断控制器中,每个位与特定的中断源相关联(参见图 9.12)。例如,位 2 与串行通信的接收中断源相关联。

PRIORITY_x 定义了四个中断源的优先级,用于示例中,其中 PRIORITY_0 是最高优先级中断,PRIORITY_3 是最低优先级中断。
BINARY_x 定义了每个优先级级别的位模式。例如,对于 PRIORITY_0 中断,二进制模式将是 0x00000004(或 1 乘以 2)。对于每个优先级级别,都有一个相应的掩码,用于屏蔽与或低于该优先级的所有中断。例如,MASK_2 将屏蔽来自 Timer2(优先级为 3)和 CommRx(优先级为 2)的中断。
中断控制器寄存器的定义也已列出。ic_Base 是基地址,其余的定义(例如,IRQStatus)都是相对于该基地址的偏移量。
优先级中断处理程序从标准进入开始,但首先只存储 IRQ 链接寄存器到 IRQ 栈上。
接下来,处理程序获取 spsr 并将内容放入寄存器 r14_irq,并释放一组寄存器以用于处理优先级。
处理程序需要获取中断控制器的状态。这可以通过将中断控制器的基地址加载到寄存器 r14,并将寄存器 r10 加载为 ic_Base(寄存器 r14)偏移 IRQStatus(0x00)来实现。
然后,处理程序需要通过测试状态信息来确定最高优先级中断。如果特定的中断源与优先级级别匹配,则将优先级级别设置在寄存器 r11 中。该方法从最低优先级开始,依次比较中断源与所有设置的优先级级别,直到最高优先级。
在此代码片段之后,寄存器 r14_irq 将包含中断控制器的基地址,寄存器 r11 将包含最高优先级中断的位号。现在重要的是禁用低优先级和相等优先级中断,以便高优先级中断仍然可以中断处理程序。
请注意,这种方法更具确定性,因为发现优先级所需的时间始终相同。
要设置控制器中的中断屏蔽位,处理程序必须确定当前的 IRQ enable 寄存器,并获取优先级屏蔽表的起始地址。priority_masks 在处理程序末尾定义。
寄存器 r12 现在将包含当前的 IRQ enable 寄存器,寄存器 r10 将包含优先级表的起始地址。为了获取正确的掩码,将寄存器 r11 左移两位(使用移位器左移 #2)。这将使地址乘以四,并加上优先级表的起始地址。
寄存器 r10 包含新的掩码。下一步是使用掩码清除低优先级中断,通过将掩码与寄存器 r12(IRQEnable 寄存器)进行二进制 AND 运算,然后通过将新的掩码存储到 IRQEnableClear 寄存器中来清除位。
现在可以通过清除 cpsr 中的 i 位来启用 IRQ 异常。
最后,处理程序需要通过修改寄存器 r11(其中仍包含最高优先级中断)和 pc 来跳转到正确的服务例程。将寄存器 r11 左移两位(乘以四),并将其加到 pc 上,使处理程序可以通过直接加载服务例程的地址到 pc 中来跳转到正确的例程。
跳转表必须跟随加载PC指令。在跳转表和操作PC指令之间有一个NOP指令,因为PC将指向两条指令之后(或八个字节)。优先级屏蔽表按照中断源位的顺序排列。每个中断服务例程(ISR)都遵循相同的入口风格。下面是针对timer1中断服务例程的示例。
在上面的头部之后插入ISR。完成ISR后,必须重置中断源并将控制传递回被中断的任务。
处理程序在重新打开中断之前必须禁用IRQs。现在可以将外部中断恢复到其原始值,这是可能的,因为服务例程没有修改寄存器r12,所以它仍然包含原始值。
为了返回到被中断的任务,恢复上下文,并将原始SPSR复制回SPSR_IRQ。
优先级简单中断处理程序总结:
- 处理具有优先级的中断。
- 低中断延迟。
- 优点:确定性的中断延迟,因为首先确定优先级级别,然后在屏蔽较低优先级中断后调用服务例程。
- 缺点:处理低优先级服务例程的时间与处理高优先级服务例程的时间相同。
9.3.5 Prioritized Standard Interrupt Handler
延续优先级简单中断处理程序,下一个处理程序增加了一层复杂性。优先级简单中断处理程序通过测试所有中断来确定最高优先级,这是一种效率低下的确定优先级的方法,但它具有确定性,因为每个中断优先级确定所需的时间相同。

另一种方法是在确定最高优先级中断时尽早跳转(见图9.13),即在确定优先级后立即设置PC并进行跳转。这意味着针对优先级标准中断处理程序的代码中的识别部分比优先级简单中断处理程序更复杂。该识别部分将确定优先级,并立即跳转到一个处理低优先级中断屏蔽的例程,然后通过跳转表再次跳转到适当的ISR。
示例9.12:
优先级标准中断处理程序的起始部分与优先级简单中断处理程序相同,但是会更早地拦截具有更高优先级的中断。寄存器r14被赋予指向中断控制器基地址,并将中断控制器状态寄存器的值加载到寄存器r10中。为了使处理程序可以重定位,将PC指向的当前地址记录到寄存器r11中。

I_Bit
EQU 0x80
PRIORITY_0 EQU 2
; Comms Rx
PRIORITY_1 EQU 1
; Comms Tx
PRIORITY_2 EQU 0
; Timer 1
PRIORITY_3 EQU 3
; Timer 2
BINARY_0
EQU 1 << PRIORITY_0 ; 1 << 2 0x00000004
BINARY_1
EQU 1 << PRIORITY_1 ; 1 << 1 0x00000002
BINARY_2
EQU 1 << PRIORITY_2 ; 1 << 0 0x00000001
BINARY_3
EQU 1 << PRIORITY_3 ; 1 << 3 0x00000008
MASK_3
EQU BINARY_3
MASK_2
EQU MASK_3+BINARY_2
MASK_1
EQU MASK_2+BINARY_1
MASK_0
EQU MASK_1+BINARY_0
ic_Base
EQU 0x80000000
IRQStatus EQU 0x0
IRQRawStatus EQU 0x4
IRQEnable EQU 0x8
IRQEnableSet EQU 0x8
IRQEnableClear EQU 0xc
IRQ_Handler ; instruction
state : comment
SUB r14, r14, #4
; 2 : r14_irq -= 4
STMFD r13!, {r14}
; 2 : save r14_irq
MRS r14, spsr
; 2 : copy spsr_irq
STMFD r13!,{r10,r11,r12,r14} ; 2 : save context
LDR r14, =ic_Base
; 3 : int crtl addr
LDR r10, [r14, #IRQStatus] ; 3 : load IRQ status
MOV r11, pc
; 4 : copy pc
TST r10, #BINARY_0
; 5 : if CommRx
BLNE disable_lower
; 5 : then branch
TST r10, #BINARY_1
; 5 : if CommTx
BLNE disable_lower
; 5 : then branch
TST r10, #BINARY_2
; 5 : if Timer1
BLNE disable_lower
; 5 : then branch
TST r10, #BINARY_3
; 5 : if Timer2
BLNE disable_lower
; 5 : then branch
disable_lower
SUB r11, r14, r11
; 5 : r11=r14-copy of pc
LDR r12,=priority_table ; 5 : priority table
LDRB r11,[r12,r11,LSR #3] ; 5 : mem8[tbl+(r11 >> 3)]
ADR r10, priority_masks ; 5 : priority mask
LDR r10, [r10,r11,LSL #2] ; 5 : load mask
LDR r14, =ic_Base
; 6 : int crtl addr
LDR r12, [r14,#IRQEnable] ; 6 : IRQ enable reg
AND r12, r12, r10
; 6 : AND enable reg
STR r12, [r14,#IRQEnableClear] ; 6 : disable ints
MRS r14, cpsr
; 7 : copy cpsr
BIC r14, r14, #I_Bit
; 7 : clear I-bit
MSR cpsr_c, r14
; 7 : enable IRQ
LDR pc, [pc, r11, LSL#2] ; 8 : jump to an ISR
NOP
;
DCD service_timer1
; timer1 ISR
DCD service_commtx
; commtx ISR
DCD service_commrx
; commrx ISR
DCD service_timer2
; timer2 ISR
priority_masks
DCD MASK_2
; priority mask 2
DCD MASK_1
; priority mask 1
DCD MASK_0
; priority mask 0
DCD MASK_3
; priority mask 3
priority_table
DCB PRIORITY_0
; priority 0
DCB PRIORITY_1
; priority 1
DCB PRIORITY_2
; priority 2
DCB PRIORITY_3
; priority 3
ALIGN

现在可以通过比较最高优先级和最低优先级来测试中断源。第一个与中断源匹配的优先级确定传入中断的优先级,因为每个中断都有预设的优先级。一旦匹配成功,处理程序就可以跳转到屏蔽低优先级中断的例程。
为了禁用相同或更低优先级的中断,处理程序进入一个例程,首先使用寄存器r11和链接寄存器r14中的基地址计算优先级级别。在SUB指令之后,寄存器r11将包含值4、12、20或28。这些值对应于中断的优先级乘以8再加上4。然后将寄存器r11除以8并加上优先级表的地址。LDRB指令执行后,寄存器r11将等于其中一个优先级中断号(0、1、2或3)。
可以使用左移两位的技术将优先级掩码确定为寄存器r10加上的值,该值包含优先级掩码的地址。中断控制器的基地址被复制到寄存器r14_irq中,并用于获取控制器中的IRQEnable寄存器并将其放入寄存器r12中。寄存器r10包含新的掩码。下一步是使用此掩码清除低优先级中断,方法是对掩码和r12(IRQEnable寄存器)执行二进制AND并将结果存储到IRQEnableClear寄存器中。现在可以通过清除cpsr中的i位来启用IRQ异常。
最后,处理程序需要通过修改r11(仍包含最高优先级中断)和pc来跳转到正确的服务例程。将寄存器r11左移两位(将r11乘以4)并将其添加到pc中,使处理程序能够通过直接将服务例程的地址加载到pc中跳转到正确的例程。跳转表必须跟随加载pc的指令。跳转表和修改pc的LDR指令之间有一个NOP,因为pc指向前两个指令(或八个字节)。
请注意,优先级掩码表按照中断位顺序排列,优先级表按照优先级顺序排列。
总结:优先级标准中断处理程序
- 相对于低优先级中断,处理高优先级中断的时间更短。
- 中断延迟较低。
- 优点:对待高优先级中断更加紧急,无需重复设置外部中断掩码的代码。
- 缺点:由于该处理程序需要进行两次跳转,因此会有一定的时间损耗,每次跳转时都会清空流水线。
9.3.6 Prioritized Direct Interrupt Handler
优先级直接中断处理程序和优先级标准中断处理程序之间的一个区别是,一些处理过程被移到了各个中断服务例程(ISR)中。这些移动的代码用于屏蔽低优先级中断。每个ISR都必须根据特定优先级屏蔽低优先级中断,该优先级可以是一个固定的数字,因为优先级已经在之前确定过了。
第二个区别是,优先级直接中断处理程序直接跳转到相应的ISR。每个ISR在修改cpsr重新启用中断之前负责禁用低优先级中断。这种类型的处理程序相对简单,因为屏蔽是由各个ISR完成的,但是由于每个中断服务例程实际上执行相同的任务,会有一小部分代码重复。
例如,bit_x定义将中断源与中断控制器中的位位置相关联,在ISR内部帮助屏蔽低优先级中断。保存上下文后,需要将ISR表的基地址加载到寄存器r12中。一旦为中断源确定了优先级,该寄存器将用于跳转到正确的ISR。

I_Bit
EQU 0x80
PRIORITY_0 EQU 2
; Comms Rx
PRIORITY_1 EQU 1
; Comms Tx
PRIORITY_2 EQU 0
; Timer 1
PRIORITY_3 EQU 3
; Timer 2
BINARY_0 EQU 1 << PRIORITY_0
; 1 << 2 0x00000004
BINARY_1 EQU 1 << PRIORITY_1
; 1 << 1 0x00000002
BINARY_2 EQU 1 << PRIORITY_2
; 1 << 0 0x00000001
BINARY_3 EQU 1 << PRIORITY_3
; 1 << 3 0x00000008
MASK_3 EQU BINARY_3
MASK_2 EQU MASK_3+BINARY_2
MASK_1 EQU MASK_2+BINARY_1
MASK_0 EQU MASK_1+BINARY_0
ic_Base EQU 0x80000000
IRQStatus EQU 0x0
IRQRawStatus EQU 0x4
IRQEnable EQU 0x8
IRQEnableSet EQU 0x8
IRQEnableClear EQU 0xc
bit_timer1 EQU 0
bit_commtx EQU 1
bit_commrx EQU 2
bit_timer2 EQU 3
IRQ_Handler ; instruction
comment
SUB r14, r14, #4
; r14_irq-=4
STMFD r13!, {r14}
; save r14_irq
MRS r14, spsr
; copy spsr_irq
STMFD r13!,{r10,r11,r12,r14} ; save context
LDR r14, =ic_Base
; int crtl addr
LDR r10, [r14, #IRQStatus] ; load IRQ status
ADR r12, isr_table
; obtain ISR table
TST r10, #BINARY_0
; if CommRx
LDRNE pc, [r12, #PRIORITY_0 << 2] ; then CommRx ISR
TST r10, #BINARY_1
; if CommTx
LDRNE pc, [r12, #PRIORITY_1 << 2] ; then CommTx ISR
TST r10, #BINARY_2
; if Timer1
LDRNE pc, [r12, #PRIORITY_2 << 2] ; then Timer1 ISR
TST r10, #BINARY_3
; if Timer2
LDRNE pc, [r12, #PRIORITY_3 << 2] ; then Timer2 ISR
B service_none
isr_table
DCD service_timer1
; timer1 ISR
DCD service_commtx
; commtx ISR
DCD service_commrx
; commrx ISR
DCD service_timer2
; timer2 ISR
priority_masks
DCD MASK_2
; priority mask 2
DCD MASK_1
; priority mask 1
DCD MASK_0
; priority mask 0
DCD MASK_3
; priority mask 3
...
service_timer1
MOV r11, #bit_timer1
; copy bit_timer1
LDR r14, =ic_Base
; int ctrl addr
LDR r12, [r14,#IRQEnable] ; IRQ enable register
ADR r10, priority_masks ; obtain priority addr
LDR r10, [r10,r11,LSL#2] ; load priority mask
AND r12, r12, r10
; AND enable reg
STR r12, [r14, #IRQEnableClear] ; disable ints
MRS r14, cpsr
; copy cpsr
BIC r14, r14, #I_Bit
; clear I-bit
MSR cpsr_c, r14
; enable IRQ
<rest of the ISR>

优先级中断是通过首先检查最高优先级中断,然后逐级向下检查来确定的。一旦确定了优先级中断,pcis将被加载为相应ISR的地址。间接地址存储在isr_table的地址加上优先级级别向左移动两位(乘以四)的位置。或者您可以使用条件分支指令BNE。
ISR跳转表isr_table按照最高优先级中断在表的开头排序。
service_timer1条目展示了优先级直接中断处理程序中使用的ISR的示例。每个ISR都是独特的,取决于特定的中断源。
中断控制器的基地址的副本被放入寄存器r14_irq中。这个地址加上一个偏移量用于将IRQEnable寄存器复制到寄存器r12中。
优先级屏蔽表的地址需要被复制到寄存器r10中,以便用于计算实际屏蔽的地址。寄存器r11向左移动两个位置,得到一个偏移量为0、4、8或12。偏移量加上优先级屏蔽表的地址用于将屏蔽加载到寄存器r10中。优先级屏蔽表与前一节中的优先级中断处理程序相同。
寄存器r10将包含ISR屏蔽,寄存器r12将包含当前屏蔽。使用二进制AND操作将两个屏蔽合并。然后使用新的屏蔽配置中断控制器,使用IRQEnableClear寄存器。现在可以通过清除cpsr中的i位来安全地启用IRQ异常。
处理程序可以继续服务当前的中断,除非发生了一个优先级更高的中断,这种情况下该中断将优先于当前的中断。■
总结:优先级直接中断处理程序
■ 在较短时间内处理高优先级中断。直接跳转到特定的ISR。
■ 中断延迟低。
■ 优点:使用单个跳转,节省宝贵的周期进入ISR。
■ 缺点:每个ISR都需要设置外部中断屏蔽机制,以防止低优先级中断中断当前ISR,这会为每个ISR增加额外的代码。
9.3.7 Prioritized Grouped Interrupt Handler
最后,优先级分组中断处理程序与其他优先级中断处理程序不同,因为它设计用于处理大量的中断。这是通过将中断分组并形成一个子集来实现的,然后可以为该子集分配一个优先级级别。
嵌入式系统的设计者必须识别出每个中断源的子集,并为该子集分配一个群组优先级级别。在选择中断源的子集时要小心,因为群组可以决定系统的特性。将中断源进行分组可以降低处理程序的复杂性,因为不需要扫描每个中断来确定优先级级别。如果优先级分组中断处理程序设计良好,它将显著提高整个系统的响应时间。
示例 9.14
此处理程序被设计为具有两个优先级分组。计时器源被分组到群组0中,通信源被分组到群组1中(见表9.11)。群组0中断的优先级高于群组1中断。

I_Bit
EQU 0x80
PRIORITY_0 EQU 2
; Comms Rx
PRIORITY_1 EQU 1
; Comms Tx
PRIORITY_2 EQU 0
; Timer 1
PRIORITY_3 EQU 3
; Timer 2
BINARY_0 EQU 1 << PRIORITY_0 ; 1 << 2 0x00000004
BINARY_1 EQU 1 << PRIORITY_1 ; 1 << 1 0x00000002
BINARY_2 EQU 1 << PRIORITY_2 ; 1 << 0 0x00000001
BINARY_3 EQU 1 << PRIORITY_3 ; 1 << 3 0x00000008
GROUP_0 EQU BINARY_2|BINARY_3
GROUP_1 EQU BINARY_0|BINARY_1
GMASK_1 EQU GROUP_1
GMASK_0 EQU GMASK_1+GROUP_0
MASK_TIMER1 EQU GMASK_0
MASK_COMMTX EQU GMASK_1
MASK_COMMRX EQU GMASK_1
MASK_TIMER2 EQU GMASK_0
ic_Base EQU 0x80000000
IRQStatus EQU 0x0
IRQRawStatus EQU 0x4
IRQEnable EQU 0x8
IRQEnableSet EQU 0x8
IRQEnableClear EQU 0xc
interrupt_handler
SUB r14, r14,#4
; r14_irq-=4
STMFD r13!, {r14}
; save r14_irq
MRS r14, spsr
; copy spsr_irq
STMFD r13!, {r10,r11,r12,r14} ; save context
LDR r14, =ic_Base
; int ctrl addr
LDR r10, [r14, #IRQStatus] ; load IRQ status
ANDS r11, r10, #GROUP_0 ; belong to GROUP_0
ANDEQS r11, r10, #GROUP_1 ; belong to GROUP_1
AND r10, r11, #0xf
; mask off top 24-bit
ADR r11, lowest_significant_bit ; load LSB addr
LDRB r11, [r11, r10]
; load byte
B disable_lower_priority ; jump to routine
lowest_significant_bit
; 0 123456789abcdef
DCB 0xff,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0
disable_lower_priority
CMP r11, #0xff
; if unknown
BEQ unknown_condition
; then jump
LDR r12, [r14, #IRQEnable] ; load IRQ enable reg
ADR r10, priority_mask ; load priority addr
LDR r10, [r10, r11, LSL #2] ; mem32[r10+r11 << 2]
AND r12, r12, r10
; AND enable reg
STR r12, [r14, #IRQEnableClear] ; disable ints
MRS r14, cpsr
; copy cpsr
BIC r14, r14, #I_Bit
; clear I-bit
MSR cpsr_c, r14
; enable IRQ ints
LDR pc, [pc, r11, LSL #2] ; jump to an ISR
NOP
DCD service_timer1
; timer1 ISR
DCD service_commtx
; commtx ISR
DCD service_commrx
; commrx ISR
DCD service_timer2
; timer2 ISR
priority_mask
DCD MASK_TIMER1
; mask GROUP 0
DCD MASK_COMMTX
; mask GROUP 1
DCD MASK_COMMRX
; mask GROUP 1
DCD MASK_TIMER2
; mask GROUP 0

GROUP_x定义通过对二进制模式进行二进制OR操作将各种中断源分配到它们的特定优先级级别。GMASK_x定义为分组中断分配掩码。MASK_x定义将每个GMASK_x连接到特定的中断源,然后可以在优先级掩码表中使用。
在保存上下文后,中断处理程序使用中断控制器基地址的偏移量加载IRQ状态寄存器。
然后,处理程序通过对源进行二进制AND操作来确定中断源所属的组别。指令后缀字母S表示更新cpsr中的条件标志。
现在,寄存器r11将包含最高优先级的组别0或1。处理程序现在通过与0xf进行二进制AND操作来屏蔽其他中断源。
最低有效位表的地址随后加载到寄存器r11中。使用寄存器r10中的值(0、1、2或3,参见表9.12)从表的起始位置加载一个字节。

一旦最低有效位位置加载到寄存器r11中,处理程序将跳转到一个例程。
disable_lower_priority中断例程首先检查是否存在虚拟中断(不再存在)。如果中断是虚拟的,则调用unknown_condition例程。然后,处理程序加载IRQEnable寄存器,并将结果放入寄存器r12中。
通过加载优先级掩码表的地址,可以找到优先级掩码,然后将寄存器r11中的数据左移两位。将结果(0、4、8或12)加到优先级掩码地址上。寄存器r10然后包含一个屏蔽,用于禁止提升较低优先级组中断。
下一步是使用屏蔽来清除较低优先级中断,通过在寄存器r10和r12(IRQEnable寄存器)中进行二进制AND操作,然后将结果保存到IRQEnableClear寄存器中以清除位。此时,通过在cpsr中清除i位,现在可以安全地启用IRQ异常。
最后,处理程序通过修改寄存器r11(仍包含最高优先级中断)和pc来跳转到正确的中断服务例程。通过将寄存器r11左移两位并将结果加到pc上,确定ISR的地址。然后将此地址直接加载到pc中。请注意,跳转表必须在LDR指令之后。
由于ARM流水线的存在,NOP指令是必要的。
总结:优先分组中断处理程序
- 机制:处理按不同优先级分组的中断。
- 低中断延迟。
- 优点:在嵌入式系统需要处理大量中断时非常有用,同时减少了响应时间,因为确定优先级级别的过程较短。
- 缺点:确定如何将中断分组在一起。
9.3.8 VIC PL190 Based Interrupt Service Routine
为了充分利用向量中断控制器,必须修改IRQ向量入口。
0x00000018 LDR pc,[pc,#-0xff0] ; IRQ pc=mem32[0xfffff030]
这条指令从内存映射位置0xffffff030加载一个ISR地址到pc寄存器中,绕过任何软件中断处理程序,因为可以直接从硬件中获取中断源。它还减少了中断延迟,因为只有一个跳转到特定的ISR。
这里是VIC服务例程的一个示例:

INTON
EQU 0x0000
; enable interrupts
SYS32md EQU 0x1f
; system mode
IRQ32md EQU 0x12
; IRQ mode
I_Bit
EQU 0x80
VICBaseAddr EQU 0xfffff000 ; addr of VIC ctrl
VICVectorAddr EQU VICBaseAddr+0x30 ; isr address of int
vector_service_routine
SUB r14,r14,#4
; r14-=4
STMFD r13!, {r0-r3,r12,r14} ; save context
MRS r12, spsr
; copy spsr
STMFD r13!,{r12}
; save spsr
<clear the interrupt source>
MSR cpsr_c, #INTON|SYS32md ; cpsr_c=ift_sys
<interrupt service code>
MSR cpsr_c, #I_Bit|IRQ32md ; cpsr_c=Ift_irq
LDMFD r13!, {r12}
; restore (spsr_irq)
MSR spsr_cxsf, r12 ; restore spsr
LDR r1,=VICVectorAddr ; load VectorAddress
STR r0, [r1]
; servicing complete
LDMFD r13!, {r0-r3,r12,pc}ˆ ; return

该例程在清除中断源之前保存了上下文和spsr_irq。完成后,可以通过清除i位重新启用IRQ异常,并将处理器模式设置为系统模式。然后,服务例程可以在系统模式下处理中断。
完成后,通过设置i位禁用IRQ异常,并将处理器模式切换回IRQ模式。
spsr_irq从IRQ堆栈中恢复,准备好返回到被中断的任务。
然后,服务例程将写入控制器中的VICVectorAddr寄存器。向该地址写入表示优先级硬件已处理完中断。
请注意,由于VIC基本上是一个硬件中断处理程序,在激活之前必须预先将ISR地址数组编程到VIC中。
9.4 Summary
异常改变了指令的正常顺序执行。ARM处理器有七种异常:数据终止异常、快速中断请求异常、中断请求异常、预取终止异常、软件中断异常、复位异常和未定义指令异常。每个异常都有一个关联的处理器模式。当引发异常时,处理器进入特定模式并跳转到向量表中的一个条目。每个异常也有一个优先级级别。
中断是一种特殊类型的异常,由外部设备引起。IRQ异常用于一般操作系统活动。FIQ异常通常保留给单个中断源。中断延迟是从外部中断请求信号引发到特定中断服务例程(ISR)的第一条指令被提取之间的时间间隔。
我们介绍了八种中断处理方案,从非嵌套的简单中断处理程序,用于处理和服务单个中断,到高级的优先级分组中断处理程序,用于处理分组成不同优先级级别的中断。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值