Jflash 源码分析

来源:互联网 发布:什么叫大数据分析 编辑:程序博客网 时间:2024/05/01 12:58

本文来自http://www.dzkf.cn/html/qianrushixitong/2007/0403/1858.html 

常常是板子出了问题就手足无措,常常要给板子上面信号的时候要用ADS写长长的程序(我用ARM),常常看到Jflash的程序出错就只知道重起板子,于是我就常常想阅读一下Jflash的源代码。

今天,我终于祭起长久不用的Source Insight,建立工程,开始阅读Jflash,所谓打蛇打七寸,读程序先读main,我就从main开始对jflash进行解剖。我读的代码是windows版本的,用VC进行编译,我想Linux版本的应该也差不多,就是要定义一个宏吧,这个问题暂且不关注,先关注程序本身。

程序一开始就是一大堆没有注释的变量,也许我是才疏学浅的原因,我硬是看不懂那些变量是做什么用的,暂且跳过吧,先看后面的程序
#ifdef __windows__
//Test operating system, if  WinNT or  Win2000 then get device driver handle
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
{
HANDLE h;
h = CreateFile("////.//giveio", GENERIC_READ, 0, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(h == INVALID_HANDLE_VALUE)
error_out("Couldn't access giveio device");
CloseHandle(h);
}
#endif

版权信息就不说了,下面就是检测giveio是否已经安装好了,如果没有安装好,就提示Couldn't access giveio device。接着调用test_port()函数,以寻找一个可以用并口。

在分析test_port()之前,我们首先对并口编程先进行一些介绍,我们的PC机一般有三个并口,他们的IO地址范围通常是:
0x3bc-0x3be
0x378-0x37a
0x278-0x27a
在很多电脑里面,通常连接Jtag的并口是以0x378为基地址的并口。
可以看到一个并口有三个IO地址,第一个是数据寄存器地址,第二个是控制寄存器地址,第三个是状态寄存器地址。

JTAG原理
      上篇文章刚刚提到 test_logic_reset函数,这个函数是用来reset Jtag链的,继续分析之前,还是先让我们来了解JTAG的工作状况。为了测试我们的PCB板的方便,JTAG这个东西被搞了出来。如果想更多的了解 JTAG,大家可以去看看IEEE 1149.1的标准,如果只是和我一样,想了解一下的话,大家可以看看Mark Zwolinski著《VHDL数字系统设计》,电子工业出版社出版了他的中文版。
      每一个JTAG兼容的元件都有一个共用的测试结构,这种结构基本单元如下:
1、测试存取端口
测试存取端口包括4个或5个为测试增加的引脚。这些引脚是:

  • TDI和TDO(测试数据输入和输出)。数据和指令通过扫描路径送至IC。没有办法从指令中区分数据,或者判断一系列位的目标是到达哪个特定的IC。因此,下面的引脚用来控制数据流向。
  • TMS(测试模式选择)。与TCK引脚一起,TMS引脚用来控制一个状态机以决定每位通过TDI到达目的地。
  • TCK(测试时钟)
  • TRST(测试复位),这是可选的异步复位信号,很多的JTAG电路中没有这个信号。

2、TAP控制器

       TAP控制器是一个具有16个状态的状态机,它用来控制测试。状态机的输入是TCK和TMS,输出是其它寄存器的控制信号。下面链接是我在一个网站上找到的他的状态图,大家也可以在google的图片里面搜索tap controller,就可以搜索到这个状态图。

 通过这个图可以看出,TMS脚上保持5个时钟周期的高电平,会使得状态机从任何状态进入Test-Logic-Reset。TAP控制器发出的控制信号用来启动器件中的其它寄存器。这样,如果到达TDI的位序列合适,就将被送到指令寄存器或者特别的数据寄存器。

3、测试数据寄存器(Test Data Registers)
      一个与边界扫描兼容的元件必须将其所有的输入和输出连接至扫描路径。一下描述的特殊单元用来实现扫描寄存器。另外,必须有一位的旁路寄存器,这样可以通过绕开元件的边界扫描寄存器来缩短扫描路径。另外还需要一些其他的寄存器,例如,一个IC可能需要一个标志寄存器,这个寄存器的内容可以通过扫描访问来确定 PCB板上是否装配了正确的IC。同样,我们可以通过边界扫描接口访问器件的内部扫描路径。某些可编程逻辑生产商允许使用边界扫描器件来对器件进行编程,因此,另一种可能的数据寄存器是配置寄存器。

4、指令寄存器(Instruction Register)
      指令寄存器至少有2位,这依赖于实现的测试数目。它定义了测试数据寄存器的使用。指令寄存器还产生进一步的控制信号。
边界扫描单元有四种操作模式:

  1. 普通模式。一般的系统数据从In传输至OUT。
  2. 扫描模式。shfiterDR选择SCAN_IN引脚,ClockDR提供扫描路径时钟。ShifterDR值由Tap控制器中相似的名称的状态得来。当TAP控制器处于状态capture-DR或者shifter-DR时,断言ClockDR。
  3. 捕捉模式。ShiftDR选择In引脚,数据由ClockDR时钟送入扫描路径寄存器来对系统进行快照。
  4. 更新模式。在捕捉或者扫描之后,数依据通过UpdateDR一个时钟沿从左边沿触发送至OUT。

test_logic_rest函数分析
好,这里说了这么多的JTAG,下面我们继续分析源代码, test_logic_reset的代码如下:

void test_logic_reset(void)
{
putp(1,1,IGNORE_PORT);// keep TMS set to 1 force a test logic reset
putp(1,1,IGNORE_PORT);// no matter where you are in the TAP controller
putp(1,1,IGNORE_PORT);
putp(1,1,IGNORE_PORT);
putp(1,1,IGNORE_PORT);
putp(1,1,IGNORE_PORT);
}

这个函数的目的是用来对JTAG逻辑进行重置的,函数调用了6个putp函数。
putp函数源代码如下:

int putp(int tdi, int tms, int rp)
{
int tdo = -1;
// TMS is D2, TDI is D1, and TCK is D0, so construct an output by creating a
// rising edge on TCK with TMS and TDI data set.
_outp(lpt_address, tms*4+tdi*2+8);// TCK low
_outp(lpt_address, tms*4+tdi*2+1+8);// TCK high

// if we want to read the port, set TCK low because TDO is sampled on the
// TCK falling edge.
if(rp == READ_PORT)
_outp(lpt_address, tms*4+tdi*2+8);// TCK low
if(rp == READ_PORT)
tdo = !((int)_inp(lpt_address + 1) >> 7);// get TDO data

      这里的代码是使用并口做JTAG访问的代码,可以看出,这个函数是产生一次TCK脉冲,同时发送数据和接受数据的。tdo最后返回的是TDO的状态值,使用了一个!是因为前面说过最高位的逻辑是与信号线上相反的。知道的putp代码的作用,我们就可以看出来,test_logic_reset的作用是让 TMS保持6个高电平,前面说过,TMS 5个电平就会使得器件进入重置状态。

jtag_test()函数分析
接下来,jtag_test()函数被调用,我们再来对他进行分析
void jtag_test()
{
// set all devices into bypass mode as a safe instruction

pre_IRSCAN();

if (controller_scan_code(COT_BYPASS, READ_PORT, CONTINUE) != 0x1)
{
error_out("Jtag test failure. Check connections and power./n");
}

post_IRSCAN();
printf("JTAG Test Passed/n");
}
首先, pre_IRSCAN()被调用, pre_IRSCAN()的代码如下:

void pre_IRSCAN()
{
putp(1,0,IGNORE_PORT);//Run-Test/Idle
putp(1,0,IGNORE_PORT);//Run-Test/Idle
putp(1,0,IGNORE_PORT);//Run-Test/Idle
putp(1,0,IGNORE_PORT);//Run-Test/Idle
putp(1,1,IGNORE_PORT);
putp(1,1,IGNORE_PORT);//select IR scan
putp(1,0,IGNORE_PORT);//capture IR
putp(1,0,IGNORE_PORT);//shift IR
}

      可以看出来,TAP状态机从Run_test/IDL到Selet_DR-Scan到Select-IR-SCAN再进入Capture-IR,最后进入Shift-IR,从函数返回的时候,器件进入等待数据移位进入IR的状态
      然后,controller_scan_code函数被调用,该函数则完成将一个BYPASS指令移进IR当中,然后为什么会在TDO上得到一个高电平我就不清楚了,可能这是对BYPASS命令的应答。然后Post_IRSCAN被调用,状态机返回到Run-Test/Idle模式。
Jtag-test完成之后,就开始真正的flash烧写过程了

昨天分析到jtag_test了,今天继续往下看
char filename[MAX_IN_LENGTH];
if(argc >= 2)
strcpy(filename,argv[1]);
else
{

printf("enter file: ");

gets(filename);
}
程序接着检查了是否有指定文件名,如果没有,则获取文件名
test_logic_reset();
再次重置Jtag逻辑,使得系统进入可靠状态。
id_command();
执行id_command,该函数首先使TAP控制器进入ShiftIR状态,然后向IR中移入COT_IDCODE(0x1E)指令,然后使TAP控制器进入shiftDR状态,往TDI信号置1,将DR值移出来,与系统的ID想比较,如果相应,则函数执行成功返回,否则就打印错误信息,退出程序。
bypass_all();
接着bypass_all()被调用,该函数同样通过控制TAP控制器来向器件发送COT_BYPASS(0x1F)指令,使得器件进入bypass_all状态。
test_logic_reset()
接着继续调用test_logic_reset()使系统进入可靠状态。
check_rom_info(&max_erase_time, &dsize, &max_write_buffer, &block_size, &nblocks);
该函数调用了一连串的access_rom函数,access_rom函数是完成烧写的核心函数,时间已经晚了,明天继续分析

     昨天分析到了check_rom_info函数,提到access_rom是整个的核心。
    其实这么说也不大准确,应该说access_rom 是最底层操作的函数,它首先将TAP控制器状态移到ShiftDR,然后把准备好的各个引脚的电平状态设置好(没怎么搞懂高阻态是如何动作的,也许扫描链比实际引脚会多几个脚),移入扫描链中,然后把TAP控制器状态移到ShiftIR,把extest指令移入,使器件进入外部逻辑测试状态,刚才为扫描链中移入的电平就放到了引脚上。access_rom把参数addr放到器件的地址引脚上,把数据放到数据引脚上,同时把引脚原本的引脚信号移出TDO,就可以把flash返回在数据线上信号返回。
正如代码里面的那段注释:
To read data from the Flash Memory you must first fill the processor JTAG chain with the Address, then pump the entire chain out.however while pumping data out you can be pumping the next cycle's Address in Therefore the JTAG chain looks like a pipeline, valid read data always coming
out one cycle late.
      当前flash返回的数据要下一次扫描的时候才能返回,所以access_rom函数返回的值是上次地址读到的数据。
      check_rom_info的代码比较长,而且都是一些通过flash的CFI(Common Flash Interface)对flash信息简单的读取操作,这里就不再贴出来了。
      check_rom_info成功返回之后,程序开始检查将要写进flash的文件的合法性,主要是大小是否合法,如果比flash还大,就返回错误信息并退出。如果成功,经过一些简单的界面交互过程之后,程序开始调用test_lock_flash来检查相应的块是否已经被lock,如果被锁定,则发送命令将其unlock,然后返回之后,程序调用erase_flash和program,函数同样是使用acess_rom调用来在flash引脚上产生相应的时序来完成相应的操作。大家可以参考相应的flash芯片的资料。
      最后,程序再把flash里面的数据读取出来进行与文件进行验证,检查烧写是否成功。

(全文完)

后记:第一次接触JTAG是大学学习数字逻辑的时候,那个时候在maxplus里面画好原理图或者用HDL写好描述,编译之后,就使用JTAG下载到alter 的芯片里面,那个芯片就按照我们的原理动起来了!!真是神奇,当时觉得那是大学里面最好玩的试验课。正是那门课,让我走进嵌入式的世界。之后开始做 DSP,是TI公司的C5402的芯片,使用的闻亭的仿真器,当时更是疑惑,为什么一个通过JTAG就能够控制住芯片的行为呢?带着一知半解,继续学习了 ARM系列的芯片,好像跟JTAG有仇一样,每个芯片(当中其实使用过一款AVR的AT90S8515,使用ISP进行烧写,不过听说现在已经停产了)都有JTAG(实际上是因为JTAG确实很优秀)。于是断断续续的对JTAG有一些了解。使得我对JFlash有进行分析的原因是我们有一块44b0的板子出了问题,烧写老是出毛病,我真的就束手无策了,bootloader下不去,我天大的本事也是枉然。于是我想到了JTAG控制,既然程序是用JTAG烧写进去的,那么我用JTAG去操作、检查总应该是对的。就这样,我开始断断续续的看一下JFlash的源代码。刚开始的时候,觉得是个好复杂的问题,前面的几个函数看得还真有点吃力,不过随着对JTAG的了解增多,到后面基本上没甚么障碍了。看代码的同时,把分析的过程记录下来,希望网友们可以对Jflash有更多的了解。
   






原创粉丝点击