Jflash 源碼分析

本文來自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有更多的瞭解。
   






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章