1. 程式人生 > >一步步講解如何除錯vxworks網絡卡驅動-適合嵌入式初學者

一步步講解如何除錯vxworks網絡卡驅動-適合嵌入式初學者

http://xiyong8260.blog.163.com/blog/
注:本文為原創,如有轉載,請註明出處,謝謝合作。
引言:
前一段時間在21ic上發了一個帖子,關於除錯LAN91C111 vxworks網絡卡驅動的碰到的問題的,後來除錯完了之後,一直想把這個除錯過程寫出來,供大家參考。不當之處也歡迎大家指出。

一.概述
根據專案需求,需要設計一個嵌入式資料轉發板,CPU讀取FPGA的採集到的資料,然後通過網路進行轉發,使用TCP協議,最終的轉發速率要求在12Mbps以上。另外,該設計需要滿足工業級溫度要求。操作系統使用vxworks。
以前設計的BCNG2440開發板,採用的是DM9000網絡卡晶片,DM9000網絡卡晶片是商業級的溫度範圍,因此,這次使用了SMC公司
的LAN91C111晶片。與DM9000一樣,LAN91C111也是一個10M/100M自適應的網絡卡晶片,然而,功能比DM9000多,記憶體管理比DM9000複雜,程式設計涉及的東西也多。更重要的是,LAN91C111使用的人較少,網路資料也較少,開發難度因此大了許多。
電路連線上,LAN91C111使用S3C2440的nGCS4片選線,基地址為0x20000000,中斷號為EINT4,32位的資料線,不使用EEPROM,使用內部PHY。
有過除錯vxworks下DM9000、8260網絡卡驅動的經驗,也有過linux下除錯網絡卡的經驗,自覺得除錯應該會比較順利的。

除錯的基本步驟是:1)暫存器基本讀寫除錯(配合硬體除錯)2)網絡卡暫存器配置與初始化除錯3)基本的傳送除錯4)基本的接收除錯5)中斷除錯6)和作業系統結合起來除錯7)應用
程式測試。

然而,好事多磨,原計劃一個月的除錯託了兩個多月才除錯完,歷經了些曲折,利用假期把他些出來,希望對大家有所幫助,尤其是對於嵌入式的初學者。

從這個除錯記錄中可以看到,嵌入式的除錯不但需要細緻細心,而且需要毅力恆心,只要堅持下去,總是能夠找到辦法的。我想做任何事情都是如此。

二.    設計程式碼
從smc公司網站上下載的vxworks驅動程式碼為x86平臺的,而我們的CPU為S3C2440需要進行程式碼移植。
程式碼修改總結如下:
1)新增修改底層的IO讀寫函式
/*xiyong add the lower layer read and write function*/
#define    sysInByte(port)     (*((volatile char *)(port)))
#define    sysInWord(port)     (*((volatile short *)(port)))
#define    sysInLong(port)     (*((volatile long *)(port)))
#define    sysOutByte(addr,value)    (*((volatile char *)(addr)) = value)
#define    sysOutWord(addr,value)    (*((volatile short *)(addr)) = value)
#define    sysOutLong(addr,value)    (*((volatile long *)(addr)) = value)

2)新增對cpu的記憶體bank設定
   /*xiyong add cpu memory bank control*/
   BWSCON=0x221a99a2;
   BANKCON4=((0<<13)+(1<<11)+(3<<8)+(0<<6)+(0<<4)+(3<<2)+0);

3)新增對CPU中斷和管腿的設定
void cpuForLAN911Init(void)
{    
     rGPFCON &= 0xfeff;  /* set pin GPF4 as EINT4 [9:8]=10*/
    rGPFCON |= 0x200;    
    /* high level triggered, [18..16]=001 high level*/
    rEXTINT0 &= 0xfff9ffff;
    rEXTINT0 |= 0x10000;
    
    rSRCPND = (rSRCPND|(1<<4));/*clear status for int4-7*/ 
    rEINTPEND =( rEINTPEND|(1<<4));/*clear status for int4*/        
    rINTPND = (rINTPND|(1<<4));/*clear status for int4-7*/  

    rEINTMASK=(rEINTMASK&(~( 1<<4) ));   /*enable eint4*/    
}

STATUS lan91c111Start
    (
    END_OBJ * pEnd    /* device ID */
    )
    {
    LAN91C111END_DEVICE      *pDrvCtrl = (LAN91C111END_DEVICE *)pEnd;

       /*xiyong add here, for interrupt*/
       cpuForLAN911Init();
       SYS_INT_ENABLE (pDrvCtrl);

4)修改幾個函式的ushort型別為ulong型別
UINT ReadPhyRegister( USHORT IOBase, UCHAR  phyaddr, UCHAR  phyreg);
VOID WritePhyRegister( USHORT IOBase, UCHAR  phyaddr, UCHAR  phyreg, ULONG  phyregdat );
LOCAL USHORT InputMDO( USHORT IOAddress );
LOCAL VOID OutputMDO( USHORT IOAddress, UCHAR lev );
LOCAL UINT DetectPHY(LAN91C111END_DEVICE *Adapter ,USHORT IOBase);

5)修改confignet.h中的load引數:
#define SMC911_LOAD_STRING  "0x20000300:0x4:0x4:0x2:0x3100"

6)修改中斷巨集定義
/*xiyong modify here*/
#define SYS_INT_ENABLE(pDrvCtrl) { intEnable(pDrvCtrl->ilevel);}
#define SYS_INT_DISABLE(pDrvCtrl) { intDisable(pDrvCtrl->ilevel);}

#define LAN91C111_DEV_NAME       "smc"  /*xiyong modify here*/

8)修改傳送中斷,遮蔽netjobadd函式
//if(pDrvCtrl->pTxReadIndex->pMblk != NULL)
//              netJobAdd ((FUNCPTR)lan91c111Send,(int)pDrvCtrl,0,0,0,0);

9)修改smc.h
定義
TX_PACKET 0x20
RX_PACKET 0x20

10)syslib.c新增對LAN91C111的記憶體對映,採用的是實地址對映
…..    
{
        (void *) 0x20000000,        /* LAN91C111  */
        (void *) 0x20000000,
        ROUND_UP (SZ_4M, PAGE_SIZE),
        VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
        VM_STATE_VALID    | VM_STATE_WRITABLE     | VM_STATE_CACHEABLE_NOT
    },
……
三.    除錯記錄
1.下載原始碼
老實說,我除錯了很多的驅動程式,沒有一個是自己重頭編寫的。有兩種途徑,從晶片的官方網站上下載提供的程式碼,然後根據自己的硬體修改,因此,工作量的多少就取決與自己的硬體和官方硬體的差別多大了;還有些晶片,比較新,沒有提供vxworks程式碼,但是linux是開源的,任何一款新的晶片出來,幾乎立刻就會有linux的驅動,因此,可以下載linux的程式碼,然後移植到vxworks中,這也是我慣用的方法。一直認為,linux是嵌入式之源。
從smc公司網站上下載的vxworks驅動程式碼為x86平臺的,而我們的CPU為S3C2440需要進行程式碼移植。

2、硬體和暫存器讀寫除錯
預計兩三天完成此步驟,實際用了一週多。
一般來說,每個晶片都有一個ID號,暫存器測試就是看是否能夠將此ID號讀出來,如果能夠讀出來,說明硬體的基本連線是正確的。
如果沒有讀出來,可能的原因有:1)硬體設計有問題,連線不正確2)器件有問題,買到了壞的晶片3)焊接的原因,例如虛焊,或短路4)軟體的原因,例如讀寫時序配置不正確。
所以如果ID號讀不出來,是相當麻煩的事情,因為各種原因都有可能,這也是嵌入式除錯的難點所在,需要軟硬體都非常的熟悉才行。
在設計硬體的時候,參考了網路上一些DSP和LAN91C111的電路圖,基本上都是16位的,而我們為了加快訪問速度,使用了32位的介面,沒有現成的例子,只能自己推理了,設計的時候,覺得應該沒有問題了。而當調試出了問題的時候,就開始懷疑自己的設計是否正確。
1)硬體設計
下面,我們來看看LAN91C111 datasheet手冊。
LAN91C111晶片頗為複雜,與CPU的介面,可以同步傳輸,也可以非同步傳輸,可以以8/16/32位的方式訪問,手冊上給出了三種匯流排介面例子,有兩個是是用於同步傳輸的(使用了LCLK的時鐘訊號),還有一個是16位的ISA總結介面的,我們的設計與此類似。
各個訊號的管腿接法如下:
LAN91C111      CPU S3C2440
A1             (懸空)
A2-A15     接CPU的A2-A15
解釋一下地址線的接法,為什麼這裡A1是懸空的?一般來說,地址線都是從A0開始的,我們通常來說,每個記憶體多大,都是指位元組大小,也就是8位的;如果以16位,word為單位,那麼就從A1開始算起,如果以32位為單位,就從A2算起了,因此A0和A1就不使用了,而LAN91C111是沒有A0管腿的,因此A1懸空。
D0-D31     接CPU的D0-D31
我們使用32位的,正好可以一一對應上。
這裡,有兩個問題需要說明一些。1)有些CPU,例如Motorola的PowerPC,它的CPU的地址線和資料線的順序是反的,需要反著接,也就是CPU的D0接網絡卡的D31。
2)我們現在是以雙字(32位元組)為單位進行訪問,地址線A0,A1是沒有使用的,那就存在一個問題,是否能夠只訪問32位的其中某個位元組或者某兩個位元組呢?這就需要通過位元組選擇管腿來實現,nBE0-nBE3就是完成這個任務的。
nBE0-nBE3        接CPU的WBE0-WBE3
nRD        接CPU的nOE
nWR        接CPU的new
AEN            接CPU的nGCS4
一般來說,非同步記憶體讀寫基本、最重要的幾根線就是資料線、地址線、讀寫控制線以及片選線。CPU對外圍裝置的讀寫控制時序,是學習嵌入式系統需要掌握的基本概念之一。對於實驗室新進入的同學,我通常讓他們使用FPGA/CPLD編寫一段VHDL程式碼,實現CPU對FPGA的非同步匯流排讀寫,以加深對概念的理解。
讀寫控制線還需要說明一些,有些晶片是具有單獨的讀使能和寫使能控制線,而有些晶片的讀寫控制線是一根線,例如R/W線,高電平是讀,而低電平寫;現在很多的CPU都同時具備兩種機制。
ARDY            接CPU的nWAIT
額外的補充,ARDY訊號,一般來說,CPU的速度比較快,而外圍裝置的速度比較慢,CPU的記憶體控制暫存器可以設定等待多長時間,以獲得外設的資料;如果外設資料準備的時間比較長,可以使用這個訊號,通知CPU見到這個訊號的時候才可以讀寫外設的資料。
INTR0            接CPU的EINT4
中斷訊號,需要注意的是這是高電平有效的中斷,一般來說,外設的中斷都是低電平有效的,所以軟體程式設計的時候需要額外注意。
RESET            CPU的nRESET通過反向得到
這又是不一樣的地方,通常晶片都是低電平復位,而這個晶片是高電平復位,因此,需要把CPU的復位反向一下得到?我也搞不清楚這個晶片為什麼總是這麼多的不一樣:)
不使用的幾個管腿的處理
nCYCLE、W/nR 、nRDYRTN、 LCLK            接VCC
nADS            接GND
還有幾個訊號懸空即可。
這些訊號是不使用的,參考資料手冊的建議,有些接高電平處理,有些接低電平處理。
這是一個值得注意的細節,並不是不使用的管腿不管他即可,為了防止干擾或誤觸發,特別是輸入管腿,是需要上拉活著下拉的,資料手冊一般會有建議的。
以上是基本的與CPU介面相關的訊號,還有其他的訊號。例如與乙太網變壓器的介面訊號、時鐘訊號、電源訊號等等。
對於時鐘訊號,一般都提供兩種介面,有源25M時鐘,或者無源晶體,本設計中使用了有源時鐘,從XTAL1輸入,XTAL2懸空;如果使用兩腿的無源晶體,就直接接上XTAL1和XTAL2即可。
還有就是EEPROM,網絡卡晶片一般都可以接一個93C56之類的EEPROM訊號,用於基本資訊的儲存,例如MAC地址之類。但是也可以不使用,這些基本資訊可以在初始化網絡卡的時候直接在程式中給出。嵌入式系統設計往往不使用這個晶片(可以節約成本啊),需要將ENEEP訊號接低電平,告訴LAN91C111不使用EEPROM。另外,還有幾個IOS0-IOS1也接低電平,告訴LAN91C111使用預設的地址空間。


2)軟體設計
下面再回到如何讀取網絡卡的ID號?軟體如何編寫。
LAN91C111的記憶體分為IO空間和內部RAM空間,IO空間主要是用來對暫存器訪問的,而內部SRAM空間是傳送和接收資料的暫時存放區域。IO空間可以直接訪問,而內部SRAM空間是通過位於IO空間的指標訪問的。
LAN91C111只設置了16個位元組的IO空間,每個IO空間的暫存器都是16位的,這樣,實際上只夠放8個暫存器,而實際上,LAN91C111有將近30個16位的暫存器,怎麼辦呢?通過BANK來解決,設定4個BANK,每個BANK都是16位元組,因此,訪問某個暫存器是先選擇BANK,然後再選擇偏移量。某個BANK的最高位都是BANK選擇暫存器,因此,不管如何設定,總是能夠訪問到BANK選擇暫存器。
BANK選擇暫存器為16位,而實際上只要3個bit就可以訪問8個bank了(雖然現在為4個,但是內部擴充套件了幾個,我們沒有使用),豈不是浪費了很多bit?確實是這樣,為了方便除錯,該晶片將高8位固定為0x33,因此,只要讀出BANK選擇暫存器高8位為0x33,就可以認為能夠正確讀出網絡卡暫存器了。當然,這個晶片的確是有ID暫存器的,位於BANK3的C地址。但是讀取BANK選擇暫存器更為容易,就以它來替代了。
我們已經知道,這個網絡卡使用了nGCS4作為片選,nGCS4在S3C2440的基地址為0x20000000,如何讀出ID號呢?那就要讀出IO空間的地址。IO的空間地址也是有偏移量的,可以通過EEPROM和IOS0-IOS2管腿配置,在本設計中,採用預設配置,偏移量為0x300。因此,只要以WORD的方式訪問0x2000030E空間,獲取其高8位,就應該讀出0x33的。
至少可以使用三種方式讀取BANK選擇暫存器的值。
1)    在uboot中使用md檢視記憶體的命令讀出;
2)    在ADS(AXD)環境下編寫C語言通過模擬器下載除錯執行;
3)    在vxworks tornado環境下編寫C語言下載除錯執行;我們的板子設計了兩個網絡卡,一個是DM9000,已經除錯通過了,因此,可以執行tornado來除錯,不需要模擬器了。
用C語言編寫如下:
unsignd short BANKUP;
BANKUP =((*(unsignd short *)(0x2000030E))&0xff00)>>8;
printf(“BANK SELECT UPPER is %x ”, BANKUP);

這裡有必要談到C語言。嵌入式開發,C語言是必須的,絕大部分程式碼使用C編寫,還有少量程式碼是用匯編實現,例如啟動程式碼。C語言的最大好處是對記憶體的訪問,使用指標就可以直接訪問記憶體了。
滿懷希望懷著激動的心情將程式下載執行,希望能夠看到打印出來的0x33,卻發現顯示的是0xff,沒有讀出來。前面已經說過,這表明了若干種可能的情況,是除錯中一開始最不想遇到的問題,然而,必須除錯下去。