再上一篇:附录D
上一篇:附录E
主页
下一篇:E.3 在 C 中上报入栈的寄存器和各 fault 状态寄存器
再下一篇:E.4 理解发生 fault 的原因
文章列表

E.2 设计 Fault 服务例程

《Cortex-M3 权威指南》,嵌入式处理器开发教程。

用于开发阶段的 fault服务例程,与用于实际系统中的服务例程,在绝大多数场合下是截然不 同的。对于软件开发,fault服务例程应关注于准确及时地上报发生 fault时上下文;而实际系统 中的 fault服务例程则要把这当作是危急关头来处理,它要尽可能地想办法来恢复系统,实在不可 救要时可能只有重启。这里我们主要讨论前者,因为后者是比较有技术含量的,而且不同的应用需 要不同的策略。
对于比较复杂的软件,通常不直接在 fault 服务例程中报告与 fault 相关的状态,而是把它 倾倒(专业术语:dump)到一块专用的内存中(主要包括 fault状态寄存器,通用寄存器,自动入 栈的内容等),接着悬起 PendSV。等到 PendSV 服务例程执行后,再上报问题。这是因为上报过程 执行的工作可能比较多,夜长梦多——有可能在上报过程中又不小心触发了其它的 fault,使得处 理器被锁死。因此先暂记下来,稍后转交 PendSV 处理。如果软件很简单,则可以斟情简化 fault 的处理过程,甚至直接在服务例程中上报。

E.2.1 上报 fault状态寄存器

Fault服务例程最基本的工作就是上报 fault状态寄存器的值,即上图所列出的那些。

E.2.2 上报入栈的 PC

定位入栈 PC的流程如下图所示

图 E.2 定位入栈 PC 的流程图
上图所示的工作流程可以由如下代码来演示: TST LR, #0x4 ; EXC_RETURN.2=0? ITTEE EQ ; 如是为零,则

MRSEQ R0, MSP ; 把MSP加载入R0中

LDREQ R0,[R0,#24] ; 从MSP中获取入栈的PC MRSNE R0, PSP ; 否则, 就把PSP加载入R0中 LDRNE R0,[R0,#24] ; 从PSP中获取入栈的PC

为了辅助调试,应该同时创建一个反汇编的指令列表(如果使用 RVMDK,则自动创建),从而 可以容易地定位问题所在。

E.2.3 上报 fault地址寄存器

如果 MMARVALID 或 BFARVALID 为 1,则可以提供出事时的地址。但要注意的是,当 MMARVALID/BFAVRALID 被清除后,fault 地址寄存器中的值可能被擦除。因此,必须先读 BFAR/MMAR,再读 BFARVALID/MMARVALID。如果后者为零,则丢弃读出的地址值。最后一步再清 除 BFARVALID/MMARVALID。
如果先读取了状态位,则有可能在下一步操作前被其它 fault异常抢占,这也是一种紊乱危象, 有可能导致下述的错误处理序列:

1. 读取 BFARVALID/MMARVALID

2. 发现 VALID位有效,于是准备读 BFAR/MMAR

3. 高优先级异常抢占了 fault服务例程,而且它又触发了另一个 fault,导致另一个 fault服务 例程被执行

4. 高优先级 fault服务例程清除了 BFARVALID/MMARVALID,导致 BFAR/MMAR被擦除。

5. 回到先前的 fault服务例程,此时再读取 BFAR/MMAR时,内容已经丢失了。

可见,后读取 VALID位,可以减少这种紊乱危象出现的概率(但如果还没来得及读取 BFAR/MMAR 就出现了被抢占的情况,则只能看人品了,所以最好第一件事就是读取 BFAR/MMAR——译者注)。 清除 fault状态位
在 fault上报完毕后,一定不要忘记清除 FSR中的 fault状态位。否则下次再发生 fault时, 就分不清 FSR 中的状态位是反映新来的 fault,还是反映以前的 fault 了。而且,如果 fault 地
址有效位没有清除,下次发生 fault时,BFAR/MMAR的值就无法更新。

E.2.4其它注意事项

我们经常需要在 fault服务例程的开始处保存 LR的值。然而,如果 fault是由于堆栈操作错 误导致的,此时再把 LR压栈就更添乱了。但我们已经知道,R3-R0 以及 R12 的值已经被保存,因 此我们可以在呼叫其它函数之前先把 LR的值拷贝到它们中去(事实上在出现堆栈错误时,是无法保证寄 存器已经正常入栈了的。此时的问题比较棘手。可能行得通的作法是,在 SRAM 中专门开出一个块服务于 fault 服 务例程,并把通用寄存器的值保存到那里,但这种办法无法用于嵌套的情况——这已经很钻牛角尖了)。