诊断之美-一个经典的调试例子

来源:互联网 发布:陕西新华电脑软件学校 编辑:程序博客网 时间:2024/05/06 21:48

这是一个我看到过的一个比较经典的系统调试分析过程,牵涉到整个片上系统的方方面面的知识.写下来免得以后忘了.

症状:上电后,处理器能够出reset,并跳转到第一条指令,但是BIOS跑到某些Legacy模块,,比如8259,8254初始化的地方,会死掉.

初步分析:改动bios代码,在处理器还能动的时候去访问那些会导致死机模块的寄存器,发现一访问就死机.通常如果一个模块的寄存器读取数据不正确的话,可能被读取的模块有连接障碍或者逻辑功能问题.而如果是一访问就死机(hang),那很有可能是通过总线访问时,从处理器到访问模块的通路上某处时钟或者电源出了问题.可以排除总线和接口的逻辑功能设计失误,因为这些都是很成熟的模块,除非制造过程出了问题,造成芯片和设计不符.如果是制程引起的,那可以通过多换几个芯片来试试看,不会所有的芯片都在这个地方出问题.

在进一步分析之前需要讲下背景知识,包括了片上系统的时钟,电源,总线,JTAG,DFX(Design For Test/Debug/Validation),系统启动流程.

电源.在片上系统里,通常会从主板拿三个输入电压:一般模块的电压,处理器模块电压,输入输出电压.他们分别供给不同的模块,而每个模块可能使用多个电压作为不同用途.在系统里,通常会有好几个电源岛(Power Island),分别供给不同模块.同时会有一个专门的模块A负责控制这些电源岛的打开或者关闭.其中必定会有一个电源岛是一直打开的,让我们称它0岛,连到这个岛上的包含了处理器,模块A,时钟控制模块等至关重要功能.其他电源岛不会都打开,不然就不能做电源管理了.模块A通常是一个8051之类的内置控制器,以及相应的闪存和代码.这些都是的独立于处理器之外的.系统的重置(Reset)是不会影响到0岛以及它的附属模块的.

上电次序

有时候需要多个电压供电的模块会对上电次序有要求.如果主板上的上电次序不符合要求,那可能造成某些模块启动不正常.例如PLL所产生的时钟信号不能锁定等.就整个片上系统来看,这个上电次序更为复杂,因为片内有更多的信号和模块需要按照次序来启动.但是只要主板电源上电按照要求设计,那片内就会按照设计正确依次上电.如果这个部分出了问题,那需要把相关模块和信号拉出来一个个看才能解决.

时钟.

时钟是一个系统的灵魂,时钟信号的稳定与否直接关系到系统的功能.片上系统每个模块可能都有不同的时钟需求.比如,有些模块的电源管理功能就要求把时钟降低一半,或者完全关闭.于是,在接受主板的某个时钟信号后,系统会使用倍频器和分频器产生所需的多个时钟域,并将之传给各个模块.同时,还会有一个寄存器组来保存一些值,用于时钟频率的管理.所有的这些时钟域应该是边界对齐的,也就是说,虽然频率不一样,一个是N,一个是M,在N乘以M的最小公倍数的那个时钟周期上,他们的信号上升沿或者下降沿是重合的.如果没有重合,那么一个使用Nhz的时钟的总线访问Mhz的模块的时候,他们的时序就会和设计不一样,从而无法读取数据,更有可能造成"死机.

总线

通常程序员所了解的系统总线,尤其是在x86上,就是PCI总线.通过配置PCI空间,规定设备的内存映射地址,并且定义好中断,然后进行DMA数据访问.在片上系统上,从逻辑角度看,这个并没有变化.但是在真正的硬件结构上,完全不一样了.取而代之的是新定义的高速片上互联总线,他们之间用数据包和路由等方式通讯.而且通常会有多于一个种类的总线存在,有的负责高速数据传送,有的负责控制,有的提供了除掉处理器访问之外的另一套控制系统.其余的功能模块如输入输出,视频音频,图形处理等,都是加上一个转换接口,连到这个网络上.这个总线通常是可以通过处理器访问的,此外还可以通过JTAG访问.不过访问的方法是不对外公布的,且可以通过熔丝等手段屏蔽.

JTAG,DFX

这个请参看以前的一篇文章 调试的手段

系统启动流程(本段细节有待纠正)

很多人都知道,x86处理器上电后,在初始化完他自己后,会进入实模式,并且跳转到0xf000:fff0(实际上是0:0xfffffff0)这个地址.这个地址会被硬件映射到Nor闪存上的第一个64K块,从此bios开始执行.如果是Nand启动,那么系统里会有额外的一个微控制器,上电后他第一个起来而不是处理器,他起来后,根据自己内部一个闪存里的微码驱动,读取nand的64K启动块的代码,然后拷贝到自己的另一个ram里,然后做一些加密验证之类的工作.如果通过,那就打开处理器的电源控制,让它上电.处理器读取的地址被映射到微控制器的ram.(至于这两种情况下闪存是怎么映射的,物理上是怎么实现的,有待研究,我知道的不太精确).BIOS代码在执行完前64K时,除了初始化处理器,中断向量,内存控制器,电源控制器,跳转保护模式,检验内存,设置堆栈以便C代码执行外,还把自身代码拷贝到第一个1MB内存用于跳转执行.跳转之后无非是初始化芯片组和主板,完成ACPI等协议所需数据结构的填写,为装载操作系统做准备.

大家不知道的是,在这个过程之前,处理器和芯片组本身已经做了很多事情.电源岛的上电,信号和模块的上电次序,模块的重置,时钟信号的产生,总线的初始化,南北桥的初始化,处理器本身的初始化等等事情,其中任何一个出错,都会导致第一条指令都运行不到.或者就算跑到了,也会像本文所遇到的情况一样.因此我认为有必要在执行第一条指令前,利用JTAG和DFX所提供的手段,确认在读取第一条指令的关键路径上所有的模块都是完好的,包括其电压是不是在误差范围内,时钟是不是稳定,有没有毛刺,不同时钟域是不是对齐,上电次序是不是正确,重置位是不是有效等.这样我们才能确定一个最小的系统是可靠的.不然,现在不检验,问题拖到越后面越复杂,越难以定位和分析.

至此,一个片上系统里重要的组成部分都包括了,我们可以开始分析和定位症结所在了.


前面已经说过,访问Legacy模块会死机,可能是访问通路上某个时钟和电源出了问题.我们可以利用JTAG和DFX,把时钟信号和电压都连到示波器上看.结果发现,出问题的模块使用同一个时钟,并且这个时钟和总的时钟信号没有对齐.接下去就需要让纠正这个错误,看看是不是能解决问题.

怎么纠正?利用系统提供的时钟管理寄存器模块,调整寄存器值,改变时钟因子.不过调整的时候又发现,这个控制模块本身所使用的时钟也是连到那个有问题的时钟信号的.这样就无法通过处理器这条通路来改变值,因为一访问也死机.幸好在设计系统的时候,还可以通过JTAG这个后备系统来访问寄存器.如此,就可以改变时钟因子了.不过问题又来了,改变完以后,需要做一次系统重置,输出时钟才会正常.不过,如果重置系统,通常寄存器值也会被改成默认值,也就是说白改了.幸好,时钟管理模块是连接到前文所说的0号电源岛的,系统重置不影响它的电压,所以被改变的寄存器值可以保留.从而我们看到了正确的时钟信号.经过实验,在解决了这个问题之后,bios代码就能正常的往下走了.

不过事情没有完.在给客户的主板上,是没有JTAG引脚的.那客户那边怎么解决这个问题呢?我们可以使用某些不用的GPIO管脚,把它和JTAG连起来.然后,记录下JTAG管脚上为了解决时钟问题而产生的信号序列,将这一堆0/1信号保存在bios代码里.最后,在BIOS还没有初始化有问题的模块前,将这堆信号输出,然后重置.这样就模拟了我们之前的手动操作,从而一举解决问题.

再多说几句.这个时钟问题的根本原因是电路设计者在做仿真的时候,错误的估计了那根时钟信号的延迟,从而导致了错误的结果.要从根本上解决问题,必须重新流片.不过在流片之前,为了确保设计者给出的重新设计的电路正确性(其实就是砍掉一个同步器,把导线直连),动用了电子显微镜,把芯片从顶部切开,并且穿过上面的六层结构,直接在芯片内进行切割和焊接.能这么做的原因主要是因为芯片内部晶体管其实还是单层结构,上面的只是类似于电路板的多层结构,可以轻易传过去而不会破坏其他电路.

看了这篇文章,想必之前的一个问题,怎样在系统第一条指令都没跑到的情况下找出系统症结,有答案了吧.


原创粉丝点击