在我工作中曾经帮助一些客户定位和解决芯片SoC硬件,CPU挂死问题这类疑难杂症。这类问题在被报告的时候,通常表现为软件不正常工作,例如Linux kernel RCU stall(CPU多核系统中其中一个核挂死,其RCU资源无法释放,不能响应中断,传导到其他核,进而导致整个Linux系统RCU hang)。再如系统多次开关机后,系统运行不正常。
在进行一些问题分析核之后,软件工程师可能会怀疑是硬件挂死。但在芯片公司工作过的人都应该清楚,软件工程师要将这类复杂和偶发问题推到硬件工程师那里,寻求帮助,需要明确的现象和分析说服硬件设计工程师。在这篇文章中,我会分享一些芯片CPU及系统硬件挂死问题的定位方式。
常见导致芯片挂死的原因
先分享一下通常导致芯片挂死的原因:
最常见的是总线挂死导致的。这可能有多种原因:
(a) 访问了memory系统中不存在的内存或是外设的地址(通常叫memory hole。而interconnect里没有default slave来响应这些访问),导致CPU发出的访问无response。(b) CPU的speculative access (推测性访问,即software代码没有这个访问指令,而CPU硬件基于提升performance考虑,会推测性地发出这个访问) 让这一类问题让软件工程师更难调试,因为看软件代码,设置breakpoint和watch point都无法抓到speculative access。如果speculative access 访问了memory hole,就有可能导致总线挂死。
(c) Power up/down软件代码和控制电源的硬件/firmware没有完整,干净处理,导致CPU被下电时,CPU core还有未完成的 transaction残留在总线上。多次上下电导致残留的transaction越来越多,消耗完总线支持的outstanding transaction数量,从而导致总线挂死。
(d) 客户自己Interconnect设计不完善,导致某些类的transaction(一般是比较少用到的transaction)不被支持。曾经碰到过DVM transaction出错的情况。
(e) 系统设计的bug,类如AXI ID的宽度与interconnect的不匹配。
对于这类问题,如果系统设计上支持了bus monitor硬件,会对问题定位很有帮助。
- CPU硬件的errata导致的硬件挂死。CPU的硬件设计越来越复杂庞大,难免在CPU产品的初期可能会出现一些导致挂死的问题。这一类问题极少见。Arm CPU产品如果发现有这样的问题,会给客户发出erratum通知,并在其中描述能导致问题的具体条件和可能的处理方法。在后续产品版本中会fix这些问题。
如果怀疑与CPU errata有关,我们会和客户详细讨论问题现象和条件是否满足errata的条件,有可能的话,我们还可能给出一些CPU未公开的寄存器设置来workaround这类问题。 - 与芯片的RAM实现或是系统时序(timing)有关。这类问题往往随机,难于复现,需要很多时间来复现和分析。
以上只是部分系统挂死的原因,还有一些个例就不一一列举。
如何确定是系统硬件挂死问题
有以下方式可以帮助定位:
- 如果你的系统支持硬件trace,那么通过指令流的trace,可以帮助了解CPU执行指令的情况,从而定位CPU是不是异常停止执行指令了。
不是所有系统都支持硬件trace功能,因为完善的硬件trace需要较大的coresight debug/trace设计,特别是在像server这样的大系统中。另外,开发板可能会留trace port接口,但是产品化的系统一般不会留trace port,如果出现的问题需要在产品化的系统上复现,那么trace 功能可能就无法使用。
另外,对于难于复现或是需要很长时间才能随机复现的问题, 硬件trace可能会导致极大的trace data,使trace data分析困难。上下电过程也会使trace的抓取中断,更加困难。
2. 如果系统设计上完善的bus monitor,那么bus monitor可以track transaction的状态,有助于定位bus hang的问题。随着bus从简单的AXI到CHI的进化,bus monitor的设计更加复杂,需要更多logic来实现。
3. 利用CPU核debug logic提供的PC sampling/snapshot功能来观察CPU的PC(Program Count)的变化情况,并用external debug 状态控制寄存器来观察pipeline是否还在动。这个方式只需要debug port(如JTAG debug port)就可以实施,具有较好的可操作性。
下面介绍方式3,并通过一个例子来演示。
要完全理解本文,需要一些Coresight和Arm Debug构架的知识。
PC sampling/snapshot功能
为了提升系统的可调试性,armv7 debug构架就引入了PC sampling/snapshot功能。它提供了外部调试工具通过 debug port(如JTAG)方式读取Program Count(PC)值的能力。这种方式在CPU正常运行时读取PC sampling/snapshot值,而不会影响CPU的执行(non-intrusive ),可以用于观察CPU执行指令的情况。更重要的是,在CPU核已经硬件挂死时,PC sampling/snapshot功能依然可以读取PC值(这时debug已经不能正常读取CPU寄存器值和CPU的状态),从而帮助工程师判断CPU核是否已经硬件挂死。
在不同构架上,支持PC sampling/snapshot功能的debug 寄存器有所不同:
- 在Armv7-AR构架上,这个寄存器为DBGPCSR (Program Counter Sampling Register)
- 在Armv8-AR构架上,这个寄存器为EDPCSR (External Debug Program Counter Sample Register)
- 在Armv8.2-A及以上的构架上,这个寄存器为PMPCSR (PMU Program Counter Sample Register)
需要注意的是,构架规定如果CPU已经正常进入了debug状态,读取PMPCSR会得到0xFFFFFFFF。
Arm构架提供了EDDEVID(External Debug Device ID register)寄存器,读取EDDEVID.PCSample来确定CPU是否能通过DBGPCSR/ EDPCSR得到PC Sampling值。
Arm构架提供了PMDEVID (Performance Monitors Device ID register)寄存器,读取PMDEVID.PCSample来确定CPU是否能通过PMPCSR得到PC Sampling值。
这些寄存器属于Coresight debug logic, 外部调试工具(如arm DSTREAM, TRAC32等)通过访问coresight的Debug APB总线来访问这些CPU core的debug寄存器。
需要注意的是,Arm构架上并未规定读取这些PC sampling/snapshot寄存器返回的PC值应该是什么。但是这个值通常在正在执行的指令附近,或是在挂死CPU硬件的指令附近。这也是它为什么叫PC sampling/snapshot的原因。
例如一些CPU小核,如Cortex-A53, 它会在每条retire的指令更新EDPCSR的值,因而读取EDPCSR可以得到较精准的CPU执行时的PC。而后面的大多数CPU核,特别是大核,读取这些寄存器并不能精准反映CPU执行时的PC,而是更粗粒度的PC值,通常可以认为是最近的跳转(包括跳转,函数返回,异常进入退出)的指令的PC值snapshot。即使这样,这些debug寄存器也能帮助我们分析问题。
EDSCR和EDRCR寄存器
除了PC sampling/snapshot寄存器,还有EDSCR(External Debug Status and Control Register)和EDRCR(External Debug Reserve Control Register)寄存器可以我们帮助我们分析CPU pipeline硬件挂死的问题。这两个寄存器也是通过Coresight debug APB可以访问的CPU core debug寄存器,在CPU硬件挂死时也可以访问。
- EDSCR.PipeAdv bit (bit25): 这个pipeline Advance Information寄存器bit表明是否有指令retire,因而pipeine是活着的,可以向前。观察这个bit之前,需要先通过debug工具手动清一下EDRCR寄存器。
- EDRCR.CSPA bit (bit 3):设置它,用于清EDSCR.PipeAdv
利用它们的步骤为:
- 通过debug tool设置EDRCR.CSPA。
- 持续通过debug tool读取EDSCR.PipeAdv,如果这个bit能变为1, 表明 pipeline是活着的,如果这个bit持续为0, 表明pipeline已经挂死了。
通过结合EDSCR, EDRCR和PC sampling/snapshot寄存器,可以分析CPU核是否挂死和挂死在哪里。
如何访问EDSCR, EDRCR和PC sampling/snapshot寄存器
这需要理解Coresight和Arm debug构架, 这篇文章不花时间来解释,只简单提一下:每个CPU core debug logic寄存器在Coresight系统的debug APB bus上都占用一块地址空间(有自己的base address,根据不同SoC Coresight设计的会不同,这个地址空间不是CPU通常看到的memory map)。Arm debug构架规定了这些寄存器在这块地址空间中的offset。
如何快速找到这些寄存器在debug APB bus上的地址呢?以Arm DS调试工具+Arm DSTREAM调试适配器为例,通过工具自动扫描coresight系统,可以得到整个coresight系统的结构以及各个coresight component在debug APB bus上的基址。以一个基于Armv8.2-A CPU的SoC硬件平台为例,得到如下信息(这些信息存储在一个.sdf的描述文件中)。
在这个平台上,Cortex-A核debug logic在debug APB bus上的基地址为0x02090000, Cortex-A核PMU在debug APB bus上基地址为0x020A0000。通过读取EDDEVID和PMDEVID, 可知需要通过PMPCSR来得到PC sampling值(也符合这个CPU是Armv8.2-A构架的情况)。
那么在这个平台上:
- EDSCR在debug APB bus上的地址为0x02090088(0x02090000+0x088)
- EDRCR在debug APB bus上的地址为0x02090090(0x02090000+0x090)
- PMPCSR在debug APB bus上的地址为0x020A0200(0x020A0000+0x200)和0x020A0200(0x020A0004+0x204)
使用Arm DS调试工具,可以使用 “info mem”命令得到这个平台上支持的通过Coresight memory AP可以访问内存的方式(其中包含debug APB)。有关Arm DS使用和命令,请参考Arm DS文档。
我们找debug APB访问方式 - APB4。
接下来我们就可以通过以下这样的命令来通过debug APB bus访问CPU核的debug寄存器:
- 读命令:”x /4x APB4<PROT=0,verify=0>:0xXXXXXXXXX “,其中0xXXXXXXXXX为要读取的debug寄存器地址, /4x表示读取4个word。
- 写命令:”memory set_typed APB4: 0xXXXXXXXXX (unsigned int) value”, 其中0xXXXXXXXXX为要写的debug寄存器地址, value为要写的值。
示例
系统正常情况
CPU正常运行的情况(CPU没有被debugger停下来进入debug状态),通过Arm DS调试工具经debug APB读取PMPCSR的情况。通过“x /4x APB4<PROT=0,verify=0>:0x020A0200”命令多次读取PMPCSR的值。
可以发现得到PC Sampling值是一直在变的,CPU的pipeline正常工作。
按照前面介绍,先设置一下EDRCR. CSPA (bit3),这可以通过debugger命令 “memory set_typed APB4: 0x02090090 (unsigned int) (1<<3)” 来实现。然后通过命令“x /1x APB4<PROT=0,verify=0>:0x02090088”多次读取EDSCR.PipeAdv (bit25)来判断CPU pipeline是否还活着。这个过程得到结果如图:
可以看到EDSCR.PipeAdv被设为1, 因而pipeline是活着的。
如果通过debug tool将CPU停下来,进入debug状态。通过“x /4x APB4<PROT=0,verify=0>:0x020A0200”命令多次读取PMPCSR的值。
可以看到读到的PC Sampling值是0xFFFFFFFF,这符合构架规定。
系统异常情况
接下来我将通过一个疑似CPU核硬件挂死的现象,如何确定真的挂死。
在软件运行一段时间后,发现运行不正常,我们试图通过debugger工具来调试一下,却发现原本可以正常停下来的CPU核已经无法正常停下来进入debug状态。Debugger显示,
并且CPU的寄存器也不可读了,通过CPU不能在debugger的Memory view读取内存内容:
这时,是该通过前面的方式确认是否CPU硬件挂死,挂死在哪里的时候了。
首先通过读写EDSCR,EDRCR看看CPU的pipeline是否还活着。
按照前面介绍,先设置一下EDRCR. CSPA (bit3),这可以通过debugger命令 “memory set_typed APB4: 0x02090090 (unsigned int) (1<<3)” 来实现。然后通过命令“x /1x APB4<PROT=0,verify=0>:0x02090088”多次读取EDSCR.PipeAdv (bit25)来判断CPU pipeline是否还活着。这个过程得到结果如图:
可以看到EDSCR.PipeAdv为0,因而可判断CPU pipeline已经挂死。
接下来通过命令“x /4x APB4<PROT=0,verify=0>:0x020A0200”命令多次读取PMPCSR的值, 得到CPU挂死的地方,得到的结果如下:
可以看到PMPCSR的值是不变的,为0x02000334。
有了这些信息,我们可以通过对软件的反汇编看看挂死的指令对应的代码:
我们看到是一个内存LDR指令,这样就定位到了挂死的地方。
把这些信息提供给硬件工程师,和他们一起确实发现了一个外部内存访问导致CPU核挂死的SoC问题。
总结
CPU,SoC硬件挂死的问题往往难于确认和定位,费时费力。本文通过分享一些常见的挂死原因,经验和分析方法,特别是一种更容易实施的通过debug寄存器和PC sampling/snapshot方式确定和定位挂死问题的方式。希望对大家在Arm平台上解决问题有所帮助。