Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

NVIC与CPU的关系:

 

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

NVIC嵌入Cortex-M处理器内,两者可以并行处理很多异常响应任务。

 

“异常”类型:

复位(Reset)、不可屏蔽中断(NonMaskable Interrupt, NMI)、硬件故障(Hard Fault)、存储管理故障(Memory Management Fault)、总线故障(Bus Fault)、使用故障(Usage Fault)、监管者调用(Supervisor Call, SVC)、挂起服务(Pend Service, PendSV)、系统时钟(SysTick)、中断请求(IRQ)

 

> 在OS中,引用程序可通过SVC指令来访问OS内核以及硬件驱动程序。

 

> 异常处理程序(Exception Handler, EH)Fault HandlerSystem Handler以及Interrupt Service Routine(ISR)的统称,后文都用EH指代!

 

“异常”状态

未**(Inactive)、挂起(Pending)、**(Active)、**并挂起(Active and Pending)

 

> 异常的**标志位标志了异常的EH是否在执行。只要CPU进入了一个EH并还未退出,相应异常的**标志位就会一直置1,即便该异常被更高优先级的异常所抢占。因此,如果发生了嵌套中断异常,我们将无法通过读NVIC_IABRx的标志位或核心寄存器PSR的IPSR来判断当前CPU在运行的是哪一个异常的EH

 

> 异常的挂起标志位是相当“机械”的,也就是说,只要异常事件发生,无论相关异常的响应机制是否被使能,其挂起标志位都会被硬件置1

 

> 异常的挂起标志位只能被以下2种情况清0:1)CPU进入相关异常的EH,此时,该异常的挂起标志将由硬件清0,同时,其**标志位被置1;2)用户的特权级程序直接通过指令向NVIC_ICERx寄存器的标志位写1,将相应异常的挂起标志位清0。

 

> **并挂起态指的是:异常请求发生时,其状态从未**态转变为挂起态,其挂起标志位被置1,引发CPU运行相应的EH;CPU进入EH时,异常转变为**态;当其EH在执行时,若再次发生相同的异常请求,此时该异常的挂起标志位再次被硬件置1,其状态就变为**并挂起态,在此状态下,CPU退出EH后将立即再次进入该EH。

 

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

向量表(Vector Table)

位于可寻址空间中某固定位置(其基地址可配置),储存了所有异常EH的头地址,以及主栈指针的初始化值。

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

异常发生时,CPU开始在数据总线上进行“入栈作业(Stacking)”,同时在指令总线上进行“向量抓取(Vector Fetch)”,既根据向量表存储的EH头地址(向量)向CPU的PC寄存器值赋,从而将CPU跳转到相应的EH中(如图红框所示为CPU进行向量抓取)。

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

> PC每次被直接赋值时,都会将其最低位赋给EPSR的T位,来设置CPU当前的指令编码状态(ARM/Thumb State)。因此,在向量表内所有向量的最低为位都必须为1,以将CPU保持在Thumb State。

 

异常响应的指令“过冲”

不是由指令的执行直接激发的异常都是“异步(Asynchronous)”异常(如大部分的外部Event以及SysTick系统中断等)。CPU在进入除Reset之外的异步异常EH之前,会继续完成当前正在执行的指令的下一条指令。

 

进入异常程序(Exception Entrance Sequence):

(1)Stacking作业(Data Bus)

硬件完成部分CPU核心寄存器(R0-R3、R12、LR、PC、PSR)值的入栈操作体现了Cortex-M处理器对C语言的支持,其中的PC(R15)被最先入栈,记录了被打断的指令的地址。

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

(2)抓取向量(Instruction Bus)(PC被最先入栈后,其他寄存器的Stacking进行时)

从向量表中抓取相应EH指令的首地址,赋给并PC。

(3)抓取EH指令(Instruction Bus)(Stacking进行时)

一旦EH指令头地址确认,CPU立即抓取指令(是CPU三级流水线中的第一步),以加快响应。

(4)刷新NVIC寄存器和CPU核心寄存器(Stacking完成后)

NVIC的异常挂起标志寄存器、**标志等寄存器,CPU核心寄存器的PSR、PC、SP,并根据进入异常之前CPU的模式(Threads或EHs)及其使用的栈(MS或PS)创建相应的EXC_RETURN值,赋给核心寄存器LR(Link Register),用于在CPU退出EH时,指导其操作。

 

> EXC_RETURN向开发人员提供了通过C语言编译出EH返回指令的机制

EXC_RETURN的前27位全为1(对应的可寻址空间为具有XN属性的“开发者定义内外设空间”),当EH中的指令(LDM、POP、LDR和BX等)将EXC_RETURN值赋给PC时,CPU将认为收到了退出当前EH程序的指令,并开始执行EH退出操作;

EXC_RETURN的后5位记录了CPU进入EH前的模式(Thread/Handler Mode),以及CPU所使用的栈是MS还是PS(第2位决定)。

 

> 进行Stacking作业时,CPU原来工作在哪个栈,Stacking作业就把数据PUSH到哪个栈内。而当CPU进入EH时都会自己变为Handler Mode(使用MS),从而若要再进入任何嵌套异常时Stacking都会在MS中进行。

  Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

> R4-R11寄存器不会被硬件自动入栈,被称为“被调者保存寄存器(Callee-saved Registers)”,需要由EH程序保存这些寄存器的值,并在退出EH前将其复原。

 

退出异常程序(Exception Entrance Sequence):

  1. EXC_RETURN入PC

EH程序通过指令将EXC_RETURN转入PC寄存器,激发CPU根据EXC_RETURN后5位指示的运行模式执行EH退出操作。

  1. Unstacking作业(Data Bus)

由硬件将Stack中保存的寄存器信息返回给CPU核心寄存器。

  1. CPU返回(Unstacking进行时)

根据原PC返回原程序位置并开始抓取指令。

  1. 刷新NVIC寄存器和CPU核心寄存器(Unstacking进行时)

等到CPU抓取的指令译码完成时,该步操作也已经完成,指令就可正常执行。

 

末尾连锁(Tail Chaining)技术

用于加快CPU进入EH的速度(既切换到下一个被挂起的EH的速度)。

若存在其他异常被挂起,则当CPU完成当前EH并退出时,将跳过原有的Unstacking和再次Stacking操作,直接转到下一个需要执行的被挂起的EH中。直到再也没有EH被挂起再统一Unstacking。

 

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

迟到(Late Arrival)技术

用于加速异常的抢占速度。

当CPU在进行进入某个EH的Stacking作业时,若发生了一个更高优先级的异常,则Stacking不被打断继续进行,而在抓取向量时,CPU将直接抓取新异常的EH向量,随之直接执行新异常的EH程序。而原异常将被挂起。

 

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

懒惰入栈(lazy Stacking)技术

专门用于处理FPU核心寄存器的Stacking作业的优化技术。

FPU核心寄存器数量较多(最多可达26字),这将直接导致系统对异常的响应延迟29个时钟周期。然而,实际上大多数的EH程序并不需要使用FPU操作,更不会访问FPU寄存器,就没有必要每次进入EH都将所有FPU寄存器的值入栈。

为此,系统默认使能Lazy Stacking功能,该技术在FPU使能的前提下,使得CPU进入EH时仅按照FPU未使能时的方式来由硬件完成Stacking工作(仅入栈8个字的数据),同时在栈中留下空间,并将这些空间的地址存入FPCAR( Floating Point Context Address Register)寄存器中。

当EH程序也要用到FPU操作时,再暂停运行EH转而去执行FPU寄存器的Stacking作业,完成后再返回EH继续执行。该过程也有可能会被更高优先级的异常所中断,只要正常返回就不会出问题。此外,若在后来异常的EH中也要用到FPU操作,系统将重新在stack的新区域再次执行Lazy Stacking流程。

 

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记     Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

出栈抢占(POP Preemption)技术

用于加速异常的抢占速度。

当CPU在进行退出某个EH的Unstacking作业时,若发生了另外一个异常,则Unstacking作业被打断,CPU将直接开始抓取新异常的EH向量,随之执行新异常的EH程序。

Cortex-M4异常(Exception)模型与NVIC(Nested Vectored Interrupt Controller)杂记

 

> 如果这样操作的话,那些已经被POP了的寄存器的值不就丢失了吗?(好像不推荐使用)

 

故障升级到硬件故障

当CPU在执行故障或异常的FH或EH时(注意,可以是异常也可以是故障),引发了与之相同的故障或优先级较低的故障时(注意,被引发的都是故障而不是其他),后者将被升级为优先级更高的硬件故障,而直接中断当前的EH或FH。

特殊情况当系统在进入总线FH的Stacking阶段再次发生了总线故障,则该总线故障不会被升级为硬件故障,而原FH会继续被执行,但是此时的Stack已经破坏了!若按照错误的stack数据来返回,可能会造成严重的后果!

 

> 这个问题怎么解决呢?是否需要在Bus Fault的FH程序中将CPU锁死?从而等待复位呢?