中断和异常

它支持16-4-1=11 种系统异常(同步)(保留了 4+1 个档位),外加 240 个外部中断输入(异步)。在 CM3 中取消了 FIQ 的概念(v7 前的 ARM 都有这个 FIQ,快中断请求),这是因为有了更新更好的机制——中断优先级管理以及嵌套中断支持,它们被纳入 CM3 的中断管理逻辑中。因此,支持嵌套中断的系统就更容易实现 FIQ。虽然 CM3 是支持 240 个外中断的,但具体使用了多少个是由芯片生产商决定。 CM3 还有一个NMI(不可屏蔽中断)输入脚。当它被置为有效(assert)时, NMI 服务例程会无条件地执行。 NMI 究竟被拿去做什么,还要视处理器的设计而定。在多数情况下, NMI 会被连接到一个看门狗定时器,有时也会是电压监视功能块,以便在电压掉至危险级别后警告处理器。 

ARM Cortex-M3权威指南-中断和异常(2)

  • 向量表

当 CM3 内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, CM3 使用了“向量表查表机制”。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 处必须包含一张向量表,用于初始时的异常分配。 举个例子,如果发生了异常 11(SVC),则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值。

CM3允许向量表重定位即从其它地址处开始执行各异常向量。这些地址对应的区域可以是代码区,但也可以是 RAM 区。在 RAM区就可以修改向量的入口地址了。为了实现这个功能, NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08 处),通过修改它的值就能定位向量表。

ARM Cortex-M3权威指南-中断和异常(2)

  • 异常堆栈操作

内核还会在异常处理的始末自动地执行 PUSH 与 POP 操作。注意:在寄存器列表中,不管寄存器的序号是以什么顺序给出的,汇编器都将把它们升序排序。然后先 push 序号大的寄存器,所以也就先 pop 序号小的寄存器。如果不按升序写寄存器,也许有些汇编器会给出语法错误。在进入 ESR 时, CM3 会自动把一些寄存器压栈,这里使用的是发生本异常的瞬间正在使用的 SP指针(MSP 或者是 PSP)。离开 ESR 后,只要 ESR 没有更改过 CONTROL[1],就依然使用发生本次异常的瞬间正在使用的 SP 指针来执行出栈操作。

  • 异常复位流程

复位流程:1)从地址 0x0000,0000 处取出 MSP 的初始值。2)从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。

请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。 在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。 向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令。

向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,图 3.18 中使用0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈

ARM Cortex-M3权威指南-中断和异常(2)

中断与异常的区别
中断对 CM3 核来说都是“意外突发事件” ——也就是说,该请求信号来自 CM3 内核的外面,来自各种片上外设和外扩的外设,
对 CM3 来说是“异步”的;而异常则是因 CM3 内核的活动产生的——在执行指令或访问存储器时产生, 因此对 CM3 来说是“同步”的。

pending悬起的功能

如果一个发生的异常不能被即刻响应,就称它被“悬起” (pending)。不过,少数 fault 异常是不允许被悬起的。一个异常被悬起的原因,可能是系统当前正在执行一个更高优先级异常的服务例程,或者因相关掩蔽位的设置导致该异常被除能。对于每个异常源,在被悬起的情况下,都会有一个对应的“悬起状态寄存器”保存其异常请求。待到该异常能够响应时,执行其服务例程,这与传统的 ARM 是完全不同的。在以前,是由产生中断的设备保持住请求信号;CM3 则由 NVIC 的悬起状态寄存器来解决这个问题。于是,哪怕设备在后来已经释放了请求信号,曾经的中断请求也不会错失。

中断优先级

CM3支持中断抢占。优先级的数值越小,则优先级越高有3个系统异常:复位, NMI 以及硬fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。所有其它异常的优先级则都是可编程的,但不能被编程为负数。
如果使用更多的位来表达优先级,则可以使用的值也更多,同时需要的门也更多——带来更多的成本和功耗。 CM3 允许的最少使用位数为 3 个位,亦即至少要支持 8 级优先级。

抢占优先级决定了抢占行为:当系统正在响应某异常 L 时,如果来了抢占优先级更高的异常 H,则 H 可以抢占 L。亚优先级则处理“内务”:当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常。在计算抢占优先级和亚优先级的有效位数时,芯片实际使用了多少位来表达优先级;优先级组是如何划分的。举个例子,如果只使用 3 个位来表达优先级([7:5]),并且优先级组的值是 5(从比特 5处分组),则你得到 4 级抢占优先级,且在每个抢占优先级的内部有 2 个亚优先级。如果优先级完全相同的多个异常同时悬起,则先响应异常编号最小的那一个

中断处理时序图

当中断输入脚被 assert后,该中断就被悬起。当某中断的服务例程开始执行时,就称此中断进入了“活跃”状态,并且其悬起位会被硬件自动清除,在一个中断活跃后,直到其服务例程执行完毕,并且返回了,才能对该中断的新请求予以响应。当然,新请求的响应亦是由硬件自动清零悬起标志位。

ARM Cortex-M3权威指南-中断和异常(2)

如果中断源咬住请求信号不放,该中断就会在其上次服务例程返回后再次被置为悬起状态。

ARM Cortex-M3权威指南-中断和异常(2)

异常类型

总线faults异常

当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总线faults,产生的场合可以是,1)取指,通常被称作“预取流产”(prefetch abort);2)数据读/写,通常被称作“数据流产”(data abort)

哪些因素会导致 AHB 回复一个错误信号?
AHB 回复的错误信号会触发总线 fault,诱因可以是:

  1.  企图访问无效的存储器 region。常见于访问的地址没有相对应的存储器。
  2. 设备还没有作好传送数据的准备。比如,在尚未初始化 SDRAM 控制器的时候试图访问 SDRAM。
  3.  在企图启动一次数据传送时,传送的尺寸不能为目标设备所支持。例如,某设备只接受字型数据,却试图送给它字节型数据。
  4.  因为某些原因,设备不能接受数据传送。例如,某些设备只有在特权级下才允许访问,可当前却是用户级。

使能总线fault需要在NVIC中配置BUSFAULTENA位,并且配置好中断向量表。发生总线fault后,NVIC提供了事故原因的状态寄存器,总线fault状态寄存器(BFSR):是在数据访问时,在取指时,还是在中断的堆栈操作时。总线 fault 地址寄存器(BFAR):存储异常指令地址。

数据访问产生的总线 fault可分为精确的总线fault不精确的总线fault:例如缓冲区写入就是不精确总线fault,启动缓冲区写入时指令早已执行,中途触发了总线fault。

ARM Cortex-M3权威指南-中断和异常(2)

存储器管理fault

存储器管理 faults 多与 MPU 有关,其诱因常常是某次访问触犯了 MPU 设置的保护规范。另外,某些非法访问,例如,在不可执行的存储器区域试图取指,也会触发一个 MemManage fault,而且在这种场合下,即使没有 MPU 也会触发 MemMange fault。
MemManage faults 的常见诱因如下所示:
 访问了所有 MPU regions 覆盖范围之外的地址
 访问了没有存储器与之对应的空地址
 往只读 region 写数据
 用户级下访问了只允许在特权级下访问的地址
在 MemManage fault 发生后,如果其服务例程是使能的,则执行服务例程。如果同时还发生了其它高优先级异常,则优先处理这些高优先级的异常, MemManage 异常被悬起。如果 MemMange fault是被同级或高优先级异常的服务例程引发的,或者MemManage fault被除能,则和总线fault一样:上访成硬 fault,最终执行的是硬 fault 的服务例程。如果硬 fault 服务例程或 NMI 服务例程的执行也导致了 MemManage fault,那就不可救要了——内核将被锁定。

为了调查 MemManage fault 的案发现场, NVIC 中有一个“存储器管理 fault 状态寄存器(MFSR)”,它指出导致 MemManage fault 的原因。如果是因为一个数据访问违例(DACCVIOL 位)或是一个取指访问违例(IACCVIOL 位),则违例指令的地址已经被压入栈中。如果还有 MMARVALID位被置位,则还能进一步查出引发此 fault 时访问的地址——读取 NVIC“存储器管理地址寄存器(MMAR)”的值。

ARM Cortex-M3权威指南-中断和异常(2)

用法fault

用法 faults 发生的场合可以是:

  1. 执行了协处理器指令。 Cortex-M3 本身并不支持协处理器,但是通过 fault 异常机制,可以建立一套“软件模拟”的机制,来执行一段程序模拟协处理器的功能,从而可以方便地在其它 Cortex 处理器间移植。
  2. 执行了未定义的指令。同上一点的道理,亦可以软件模拟未定义指令的功能。
  3. 尝试进入 ARM 状态。因为 CM3 不支持 ARM 状态,所以用法 fault 会在切换时产生。软件可以利用此机制来测试某处理器是否支持 ARM 状态。
  4. 无效的中断返回(LR 中包含了无效/错误的值)
  5. 使用多重加载/存储指令时,地址没有对齐。

ARM Cortex-M3权威指南-中断和异常(2)

硬fault

硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。在取向量(异常处理时对异常向量表的读取)时产生的总线 fault 也按硬 fault 处理。在 NVIC中有一个硬 fault 状态寄存器(HFSR),它指出产生硬 fault 的原因。如果不是由于取向量造成的,则硬 fault 服务例程必须检查其它的 fault 状态寄存器,以最终决定是谁上访的。硬 fault 状态寄存器(地址: 0xE000_ED2C):

ARM Cortex-M3权威指南-中断和异常(2)

 应对fault策略

在软件开发过程中,我们可以根据各种 fault 状态寄存器的值来判定程序错误,并且改正它们。
在一个RTOS实时系统中,发生了 Faults 后,如果不加以处理常会危及系统的运行。因此在找出了导致 fault 的原因后,软件必须决定下一步该怎么办。下面就给出一些应付 fault 的常用方法。

  1. 复位:这通常是最后一招。通过设置 NVIC“应用程序中断及复位控制寄存器”中的 VECTRESET位,将只复位处理器内核而不复位其它片上设施。取决于芯片的复位设计,有些 CM3 芯片可以使用该寄存器的 SYSRESETREQ 位来复位。这种只限于内核中的复位不会殃及其它的系统部件。
  2. 恢复:在一些场合下,还是有希望解决产生 fault 的问题的。例如,如果程序尝试访问了协处理器,可以通过一个协处理器的软件模拟器来解决此问题——当然是以牺牲性能为代价的,要不然还要硬件加速干啥。
  3. 中止相关任务:如果系统运行了一个 RTOS,则相关的任务可以被终结或者重新开始。各个 fault 状态寄存器(FSRs)都保持住它们的状态,直到手工清除。 Fault 服务例程在处理了相应的 fault 后不要忘记清除这些状态,否则如果下次又有新的 fault 发生,服务例程在检视fault 源时,又将看到早先已经处理的 fault 遗留下来的状态标志。此时,将无法判断哪个 fault是新发生的。 FSRs 采用一个写时清除机制(写 1 时清除)。芯片厂商也可以再添加自己的 FSR,以表示其它 fault 情况。

CM3 中的 fault 状态寄存器组

ARM Cortex-M3权威指南-中断和异常(2)

SVC和PendSV

SVC用于产生系统函数的调用,目的是不让用户程序直接访问硬件,而是通过一些系统服务函数,让用户程序使用SVC发出对系统服务函数的呼叫请求。好处是:

  1. 解耦用户程序与底层驱动,由os控制具体的硬件,用户程序移植更方便。
  2. 使用特权等级优势,由os操作硬件,提高了系统的健壮性和可靠性。

SVC指令需要一个立即数来充当系统调用代号,从而获取响应的服务函数。在 SVC 服务例程中不能嵌套使用 SVC 指令,因为同优先级的异常不能抢占自身。这种作法会产生一个用法 fault。同理,在 NMI服务例程中也不得使用 SVC,否则将触发硬 fault。

PendSV是让请求缓期执行,当其他中断完毕后再执行,因此PendSV优先级最低。

ARM Cortex-M3权威指南-中断和异常(2)

NVIC控制器与中断控制

NVIC 的寄存器以存储器映射的方式来访问,除了包含控制寄存器和中断处理的控制逻辑之外, NVIC 还包含了 MPU、 SysTick 定时器以及调试控制相关的寄存器。NVIC 的访问地址是 0xE000_E000。

中断配置基础

每个外部中断都在 NVIC 的下列寄存器中“挂号”:

  • 使能与除能寄存器
  • 悬起与“解悬”寄存器
  • 优先级寄存器
  • 活动状态寄存器

另外,下列寄存器也对中断处理有重大影响

  • 异常掩蔽寄存器(PRIMASK, FAULTMASK 以及 BASEPRI)
  • 向量表偏移量寄存器
  • 软件触发中断寄存器
  • 优先级分组位段

中断使能与除能

CM3 中可以有 240 对使能位/除能位,每个中断拥有一对。这 240 个对子分布在 8 对 32 位寄存器中(最后一对没有用完)。欲使能一个中断,你需要写 1 到对应 SETENA 的位中;欲除能一个中断,你需要写 1 到对应的 CLRENA 位中。

地址:SETENAs: xE000_E100 – 0xE000_E11C ; CLRENAs:0xE000E180 - 0xE000_E19C

ARM Cortex-M3权威指南-中断和异常(2)

中断的悬起与解悬

如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。

地址:SETPENDs:0xE000_E200 – 0xE000_E21C ; CLRPENDs:0xE000E280 - 0xE000_E29C

优先级

每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 bit,但是允许最少只使用最高 3 bit。 4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。

地址:中断优先级寄存器阵列 0xE000_E400 – 0xE000_E4EF    系统异常优先级寄存器阵列 0xE000_ED18 - 0xE000_ED23

活动状态

每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。

地址 ACTIVE 寄存器族 0xE000_E300_0xE000_E31C

PRIMASK 与 FAULTMASK 特殊功能寄存器

PRIMASK 用于除能在 NMI 和硬 fault 之外的所有异常,它有效地把当前优先级改为 0;FAULTMASK更绝,它把当前优先级改为‐1,这么一来,连硬fault都被掩蔽了。NMI不会被掩蔽。

系统异常中断配置寄存器

各种fault异常的使能是通过系统handler控制及状态寄存器(SHCSR)来实现的。

ARM Cortex-M3权威指南-中断和异常(2)

软件中断

软件中断,能以多种方式产生。最简单的就是使用相应的SETPEND寄存器;而更专业更快捷的作法,则是通过使用软件触发中断寄存器STIR,如表8.8所示。

ARM Cortex-M3权威指南-中断和异常(2)

systick定时器

SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基,提供定时器、任务切换等作用。CM3芯片内部都带有一个定时器,软件在不同CM3器件间移植工作得以简化。该定时器的时钟源可以是内部时钟(FCLK,CM3的自由时钟)、或者外部时钟(CM3处理器的STCLK信号)。

ARM Cortex-M3权威指南-中断和异常(2)

校准值寄存器提供了这样一个解决方案:TENMS值为芯片产生10ms中断的默认值。最简单的作法就是:直接把TENMS的值写入重装载寄存器,这样一来,就能做到每10ms来一次 SysTick异常。如果需要其它的SysTick异常周期,则可以根据TENMS的值加以比例计算。

ARM Cortex-M3权威指南-中断和异常(2)

异常的具体行为

异常的响应序列

入栈->取向量->选择堆栈指针MSP/PSP

  • 入栈

响应异常的第一个动作就是要保护现场,CM3自动的将寄存器放到正确的位置。先把PC与xPSR值保存起来就可以更好的启动服务例程指令的预取和执行。根据ARM函数调用约定,保护异常时的中间结果R0-R3以及R12。

Cortex-M3 在进入异常服务例程时,自动压栈了 R0-R3, R12, LR, PSR 和 PC,并且在返回时自动弹出它们,这多清爽!既加速了中断的响应,也再不需要汇编语言代码了

ARM Cortex-M3权威指南-中断和异常(2)

  •  取向量

在数据总线忙着入栈操作时,指令总线在同时从向量表中找出正确的异常向量。

  • 更新寄存器

在入栈和取向量完毕之后执行服务程序之前。会更新SP,PSR、PC、LR等寄存器。

  • 异常返回

异常服务程序执行完成之后,需要一个正式的返回指令,目的是改变PC值。出栈、更新NVIC寄存器。

ARM Cortex-M3权威指南-中断和异常(2)

 嵌套的中断

CM3内核和NVIC全力支持了中断嵌套的功能,。表现在1)自动编排异常优先级,相同或者低优先级的异常无法抢占,只能阻塞,因此对于SVC里面再次SVC会发生fault错误。2)自动入栈和出栈。

风险:如果嵌套过深而且此时住堆栈所剩无几使,很容易发生堆栈被踩的风险,从而导致系统紊乱跑飞。

  • 咬尾中断

当处理器在响应某异常时,如果又发生其它异常,但它们优先级不够高,则被阻塞——这个我们已经知道。那么在当前的异常执行返回后,系统处理悬起的异常时,倘若还是先POP然后又把POP出来的内容PUSH回去,这不是白白浪费CPU时间吗。为了改进这种铺张浪费行为,引入了咬尾中断机制。

ARM Cortex-M3权威指南-中断和异常(2)

晚到的高优先级异常

CM3的中断处理还有另一个机制,它强调了优先级的作用,这就是“晚到的异常处理”。当CM3对某异常的响应序列还处在早期:入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,则本次入栈就成了为高优先级中断所做的入栈。可见,它虽然来晚了,却还是因优先级高而受到偏袒,低优先级的异常为它“火中取栗”。

异常的返回值

在进入异常服务程序后, LR的值被自动更新为特殊的EXC_RETURN,这是一个高28位全为1的值,只有[3:0]的值有特殊含义,如表9.3所示。当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。因为LR的值是由CM3自动设置的,所以只要没有特殊需求,就不要改动它。

ARM Cortex-M3权威指南-中断和异常(2)

中断延迟

在设计实时系统时,必须对中断延迟进行严肃和仔细地估算。在这里,中断延迟的定义是:从检测到某中断请求,到执行了其服务例程的第一条指令时,已经流逝了的时间。在CM3中,若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应,则中断延迟是雷打不动的12周期(满足硬实时所要求的确定性)。在与时间赛跑的这12个周期里,处理器内部一直开足马力,进行了入栈、取向量、更新寄存器以及服务例程取指的一系列操作。但若存储器太慢以至于引入等待周期,或者还有其它因素,则会引入额外的延时。反正CM3内核是决不会拖后腿的。

几个特殊情况:

  • 当处理咬尾中断时,省去了堆栈操作,因此切入新异常服务例程的耗时可以短至6周期。
  • 如果在总线接口上还有未完成的(outstanding)数据传送,例如有一个带缓冲的写操作未完成,处理器也只能等待此传送完成。这是迫不得以的——只有这样,才能保证在发生了总线fault时,其服务例程能够安全地抢占其它程序。
  • 当多个中断同时请求时,也会发生中断延迟,这表现在只有优先级最高的得到立即响应。
  • 如果中断被掩蔽(也就是俗称的关中,在多任务系统下满地都有),则在掩蔽期间也会附加中断延迟。

异常响应期间的faults

中断响应的每步骤都可以触发faults。

入栈错误:

  1. 如果在入栈期间引起了总线fault,则本次入栈操作将被强行中止,并且把总线异常悬起或者在允许时立即响应。若除能了总线fault,则此次故障将成为“硬伤”——上访至硬fault。
  2. 在总线fault被使能的情况下,如果它的优先级比正在响应的异常高,则抢占之;否则将悬起直到引起fault的异常执行完毕。如果入栈操作引起MPU访问违例,则产生存储管理fault,并且必须立即执行MemFault服务例程,否则将无条件上访成硬fault。
  3. 入栈是自动完成的,因此不可能产生用法fault。

出栈错误:

如果在中断返回时的出栈期间引起了总线fault,则本次出栈操作将被强行中止,并且把总线异常悬起或立即响应。若除能了总线fault,则此次故障将成为“硬伤”——上访至硬fault。其它情况下,只要总线fault的优先级比当前的高(也包括比当前最深嵌套的优先级高),则可以立即响应。类似地,如果是因MPU访问违例造成的MemManage fault,且MemManage ault的服务例程必须能立即执行,否则无条件硬fault。

取向量期间:

取向量期间若是在取向量期间发生总线fault,则比较罕见,这也是最严重的,因此直接上硬fault。

无效返回时:

如果LR中的EXC_RETURN不是合法的值,则引起用法fault。如果用法fault被除能,也上访成硬fault。

处理器锁定(lockup)

在下列场合会导致锁定

  1. 在硬fault服务程序中产生fault(双重fault)
  2. 在NMI服务程序中缠身fault
  3. 在复位序列中产生总线fault

在锁定状态下寄存器和存储器都被“冻结”, PC的值被强制为0xFFFF_FFFx,并且原地打转地定死在那里一直取指。与此同时, CM3的另一条名叫“LOCKUP”的输出信号线将被置为有效,芯片厂商可以检测此信号,并且在系统复位发生器上触发一个复位。因在双重fault下的优先级为-1,NMI优先级为-2,NMI还能响应(再次证明了它的第一优先地位)。然而在NMI服务例程退出后,又回到锁定状态。

如何避免锁定状态

避免不必要的堆栈访问,对于NMI来说,进入NMI常常是在危急关头,比如掉电、短路等硬件故障,这时存储系统可能已经失效,而对于硬fault来说,有可能SP指针已经出错了(干扰,堆栈溢出),再次操作fault会重蹈覆辙。在设计硬fault、总线fault,存储管理fault的服务例程时,要检查下SP的值是否在合理范围,再做后续工作。

相关文章: