潘曉嵐 楊 斌 王?;?/p>
文章以S3C4510B的以太網(wǎng)驅(qū)動(dòng)程序?yàn)槔?,給出了驅(qū)動(dòng)程序的一般設(shè)計(jì)方法,具體描述了驅(qū)動(dòng)程序的初始化,數(shù)據(jù)接收和數(shù)據(jù)發(fā)送過程。
本文設(shè)計(jì)了基于S3C4510B的以太網(wǎng)驅(qū)動(dòng)程序,并通過串口輸出。文章給出了對(duì)一般性網(wǎng)卡驅(qū)動(dòng)程序的編寫,但在調(diào)試中,有兩點(diǎn)需要注意:一是ARM板是處于大端方式還是小端方式;二是注意字符串的定義,如設(shè)備名等。本文適用于所有與NE2000兼容的以太網(wǎng)控制器在uclinux操作系統(tǒng)上驅(qū)動(dòng)程序的開發(fā),并可以供嵌入式系統(tǒng)中驅(qū)動(dòng)程序的開發(fā)者參考。
S3c4510b內(nèi)嵌一個(gè)以太網(wǎng)控制器,支持媒體獨(dú)立接口(Media Independent Interface MII)和帶緩沖DMA接口(Buffer DMA Interface,BDI)。可在半雙工或全雙工模式下提供10M/100Mbps的以太網(wǎng)接入。在半雙工模式下,控制器支持CSMA/CD協(xié)議,在全雙工模式下支持IEEE802.3MAC控制層協(xié)議。
因此,S3C4510B內(nèi)部實(shí)際上已經(jīng)包含了以太網(wǎng)MAC控制,但并未提供物理層接口,因此,需外接一片物理芯片以提供以太網(wǎng)的接入通道。在該系統(tǒng)中,使用RTL8201作為以太網(wǎng)的物理層接口。
以太幀格式
以太網(wǎng)采用廣播機(jī)制,所有與網(wǎng)絡(luò)連接的工作站都可以看到網(wǎng)絡(luò)上傳遞的數(shù)據(jù)。它們通過查看包含在幀中的目標(biāo)地址,確定是否進(jìn)行接受或放棄。如果確定數(shù)據(jù)是發(fā)給自己的,工作站就會(huì)接受數(shù)據(jù)并傳遞給高層協(xié)議進(jìn)行處理。
標(biāo)準(zhǔn)IEEE802.3幀結(jié)構(gòu)由以下幾部分組成:幀頭(Preamble)、幀的起始定界標(biāo)志(SFD-Start of Frame Delimiter)、目的地址(Destination)、源地址(source)、數(shù)據(jù)長(zhǎng)度(Length)、數(shù)據(jù)(Data)和幀校驗(yàn)序列(FCS)
在幀結(jié)構(gòu)中,除了數(shù)據(jù)域的長(zhǎng)度不固定外,其他域的長(zhǎng)度都是固定不變的。在數(shù)據(jù)發(fā)送時(shí),幀頭、幀起始定界符與校驗(yàn)和都是由NIC自動(dòng)填加的。在接收數(shù)據(jù)過程中,幀頭和幀起始定界符將由NIC跳過,即NIC一旦檢測(cè)到有效幀頭和幀起始定界符,就認(rèn)為有效數(shù)據(jù)開始,并將有效數(shù)據(jù)存入接收緩沖環(huán)。存入接收緩沖環(huán)的數(shù)據(jù)包括:目的地址、源地址、數(shù)據(jù)域長(zhǎng)度、數(shù)據(jù)域及校驗(yàn)和。
幀頭是62位的1、0交替的位序列,即1010101010……10共62位。使用這一序列的目的是為了取得接收的串行數(shù)據(jù)的位同步信號(hào)。提取位同步信號(hào)的功能由SNI完成。
當(dāng)發(fā)送禎時(shí),每一禎都包含了62位的幀頭,在接收禎時(shí),幀頭的62位1、0序列則跳過。即使在網(wǎng)絡(luò)數(shù)據(jù)傳輸時(shí)丟掉一些1、0序列,也不會(huì)影響有效數(shù)據(jù)的正確接收。
禎起始定界符負(fù)責(zé)檢測(cè)有效幀的字節(jié)起始位置,由連續(xù)2位1組成。一旦NIC的定界邏輯檢測(cè)到兩個(gè)連續(xù)的1,就認(rèn)為有效幀已到,把接收到的串行數(shù)據(jù)以字節(jié)方式計(jì)數(shù),并將數(shù)據(jù)送入FIFO(First In First Out)先入先出寄存器。
在網(wǎng)絡(luò)上傳輸數(shù)據(jù)時(shí),由于某種原因,使幀頭中的某一位由0變?yōu)?,NIC就會(huì)接收到錯(cuò)誤的幀(由CRC校驗(yàn)邏輯完成),從而拒絕接收該幀數(shù)據(jù)。
下面定義了兩個(gè)結(jié)構(gòu)體來(lái)描述以太幀頭和以太網(wǎng)幀。
/*以太網(wǎng)幀頭*/
typedef struct {
BYTE dest[MACLEN];
BYTE srce[MACLEN];
WORD ptype;
}ETHERHDR;
/*以太網(wǎng)接收幀的最大長(zhǎng)度,包括校驗(yàn)和CRC在內(nèi)*/
#define MAXFRAMEC 1518 /*最大幀長(zhǎng)度(包括CRC)*/
#define MINFRAMEC 64 /*最小幀長(zhǎng)度(包括CRC)*/
/*高層驅(qū)動(dòng)采用以太網(wǎng)的幀長(zhǎng)度減去幀頭和校驗(yàn)和的長(zhǎng)度*/
#define ETHERMTU (MAXFRAME-sizeof(ETHERHDR)) //數(shù)據(jù)長(zhǎng)度
type struct {
ETHERHDR h; /*幀頭*/
BYTE data[ETHERMTU]; /*數(shù)據(jù)*/
LWORD crc; /*CRC*/
}ETHERFRAME;
以太網(wǎng)卡初始化
驅(qū)動(dòng)程序必須有一個(gè)初始化方法。在把驅(qū)動(dòng)程序載入系統(tǒng)的時(shí)候會(huì)調(diào)用這個(gè)初始化程序。它做以下幾方面的工作:檢測(cè)設(shè)備,在初始化程序里可以根據(jù)硬件的特征檢查硬件是否存在,然后決定是否啟動(dòng)這個(gè)驅(qū)動(dòng)程序。配置和初始化硬件,在初始化程序可以完成對(duì)硬件資源的配置配置或協(xié)商好硬件占用的資源以后,就可以向系統(tǒng)申請(qǐng)這些資源。有些資源是可以和別的設(shè)備共享的,如中斷。有些是不能共享的,如IO、DMA。接下來(lái)要初始化device結(jié)構(gòu)中的變量。最后,可以讓硬件正式開始工作。
為了使網(wǎng)卡處于在線工作狀態(tài),能夠接收或發(fā)送數(shù)據(jù),首先必須對(duì)相關(guān)的寄存器進(jìn)行初始化。這些寄存器包括BDMATXCON、BDMARXCON、BDMATXPTR、BDMARXPTR、BDMARXLST、BDMASTAT、CAM、BDMATXBUF、BDMARXBUF等。
首先對(duì)以太網(wǎng)卡的寄存器進(jìn)行初始化,并設(shè)置以太物理地址,參考程序如下:
int s3c4510_eth_init(unsigned char *mac_addr)
{
int i;
// reset BDMA and MAC
outl(BRxRS, BDMARXCON);
outl(BTxRS, BDMATXCON);
outl(MaxRxFrameSize, BDMARXLSZ);
outl(Reset, MACON);
outl(gMACCON, MACON);
s3c4510_eth_fd_init();
for(i = 0; i < 4; i++)
CAM_Reg(0) = (CAM_Reg(0) < < 8) | mac_addr[i];
for(i = 4; i < 6; i++)
CAM_Reg(1) = (CAM_Reg(1) < < 8) | mac_addr[i];
CAM_Reg(1) = (CAM_Reg(1) < < 16);
outl(0x0001, CAMEN);
outl(gCAMCON, CAMCON);
outl(gBDMATXCON, BDMATXCON);
outl(gMACTXCON, MACTXCON);
outl(gBDMARXCON, BDMARXCON);
outl(gMACRXCON, MACRXCON);
return 0;
}
數(shù)據(jù)發(fā)送
在網(wǎng)絡(luò)中,數(shù)據(jù)傳輸?shù)倪^程是,發(fā)送方將待發(fā)送的數(shù)據(jù)按幀格式要求封裝成幀,然后通過網(wǎng)卡將幀發(fā)送到網(wǎng)絡(luò)的傳輸線上,接收方根據(jù)接收到的幀的目的地址來(lái)確定時(shí)候?qū)⒃搸峤唤o上層應(yīng)用程序。本地DMA通道使用緩沖環(huán)結(jié)構(gòu)(Buffer Ring Structure)來(lái)提供對(duì)接收的幀進(jìn)行緩存。該緩沖環(huán)由一系列固定長(zhǎng)度的緩沖區(qū)組成,每一個(gè)緩沖區(qū)的長(zhǎng)度位256字節(jié),并將它稱為一頁(yè)。因此,也可以說緩沖環(huán)是由一系列的頁(yè)組成,每頁(yè)的容量為256字節(jié)。緩沖環(huán)用來(lái)存放接收到的幀。接收緩沖環(huán)的地址可以由起始頁(yè)(PAGE STAR)和終止頁(yè)(PAGE STOP)寄存器來(lái)指定。為了將待發(fā)送的幀送入網(wǎng)卡的發(fā)送緩沖區(qū),必須使用NIC的遠(yuǎn)程DMA寫操作來(lái)完成。
幀的發(fā)送是指將待發(fā)送的數(shù)據(jù)以幀的形式發(fā)送到網(wǎng)絡(luò)傳輸線上的過程,因此,數(shù)據(jù)的發(fā)送過程應(yīng)包括以下幾個(gè)大步驟:得到Tx幀描述符;裝入以太幀;發(fā)送以太幀;改變BDMA所有權(quán),能夠接收下一個(gè)幀。其流程如圖所示。參考程序如下:
int s3c4510_eth_send(unsigned char *data, int len)
{
struct frame_desc_struct *fd_ptr;
volatile unsigned long *fb_ptr;
unsigned char *fb_data;
int i;
// 1. Get Tx frame descriptor & data pointer
fd_ptr = (struct frame_desc_struct *)gtx_ptr;
fb_ptr = (unsigned long *)&fd;_ptr-> frame_data_ptr;
fb_data = (unsigned char *)fd_ptr->frame_data_ptr;
// 2. Check BDMA ownership
if(*fb_ptr & BDMA_owner)
return -1;
// 3. Prepare Tx Frame data to Frame buffer
memcpy(fb_data, data, len);
if (len < 60) {
for (i = len; i < 60; i++)
fb_data[i] = 0x00;
len = 60;
}
// 4. Set Tx Frame flag & Length Field
fd_ptr->reserved = (Padding | CRCMode | FrameDataPtrInc | LittleEndian | WA00 | MACTxIntEn);
fd_ptr->status_and_frame_lenght = (len & 0xFFFF);
// 5. Change ownership to BDMA
fd_ptr->frame_data_ptr |= BDMA_owner;
// 6. Enable MAC and BDMA Tx control register
outl(gBDMATXCON, BDMATXCON);
outl(gMACTXCON, MACTXCON);
// 7. Change the Tx frame descriptor for next use
gtx_ptr = (unsigned long)(fd_ptr-> next_frame_desc);
return 0;
}
數(shù)據(jù)接收
數(shù)據(jù)接收是指將網(wǎng)絡(luò)上的數(shù)據(jù)幀接收并緩存于網(wǎng)卡的接收緩沖環(huán)中,然后由主機(jī)程序?qū)⒕彺姝h(huán)的幀讀走并存入內(nèi)存中以備程序使用。從中可以看出,幀的接收過程分成兩步:第一步通過本地DMA將幀存入接收緩沖環(huán);第二步是通過遠(yuǎn)程DMA并在主機(jī)的配合下將接收緩沖環(huán)中的幀讀入內(nèi)存。
一般設(shè)備收到數(shù)據(jù)后都會(huì)產(chǎn)生一個(gè)中斷,在中斷處理程序中驅(qū)動(dòng)程序申請(qǐng)一塊sk_buff(skb),從硬件讀出數(shù)據(jù)放置到申請(qǐng)好的緩沖區(qū)里。接下來(lái)填充sk_buff中的一些信息。skb->dev = dev,判斷收到幀的協(xié)議類型,填入skb->protocol(多協(xié) 議的支持)。把指針skb->mac.raw指向硬件數(shù)據(jù)然后丟棄硬件幀頭(skb_pull)。還要設(shè)置skb->pkt_type,標(biāo)明第二層(鏈路層)數(shù)據(jù)類型??梢允且韵骂愋停篜ACKET_BROADCAST,鏈路層廣播;PACKET_MULTICAST,鏈路層組播;PACKET_SELF,發(fā)給自己的幀;PACKET_OTHERHOST,發(fā)給別人的幀(監(jiān)聽模式時(shí)會(huì)有這種幀);最后調(diào)用netif_rx()把數(shù)據(jù)傳送給協(xié)議層。netif_rx()里數(shù)據(jù)放入處理隊(duì)列然后返回,真正的處理是在中斷返回以后,這樣可以減少中斷時(shí)間 (下面的參考程序只是中斷之后的部分程序)。部分參考程序如下:
int s3c4510_eth_rcv(unsigned char *data, int *len)
{
struct frame_desc_struct *fd_ptr;
unsigned long rx_status;
unsigned long bdma_status;
unsigned char *tmp;
// 1. Get Rx Frame Descriptor
fd_ptr = (struct frame_desc_struct *)grx_ptr;
if (fd_ptr->frame_data_ptr & BDMA_owner)
return -1;
rx_status = (fd_ptr->status_and_frame_lenght >> 16) & 0xffff;
// 2. Get current frame descriptor and status
bdma_status = inl(BDMASTAT);
// 3. Clear BDMA status register bit by write 1
outl(bdma_status | S_BRxRDF, BDMASTAT);
// 4. If Rx frame is good, then process received frame
*len = 0;
if (rx_status & Good) {
*len = (fd_ptr->status_and_frame_lenght & 0xffff) - 4;
tmp = (unsigned char *)fd_ptr->frame_data_ptr + 2;
// 6. Get received frame to memory buffer
memcpy(data, tmp, *len);
}
// 5. Change ownership to BDMA for next use
fd_ptr->frame_data_ptr |= BDMA_owner;
// Save Current Status and Frame Length field, and clear
fd_ptr->status_and_frame_lenght = 0x0;
// 6. Get Next Frame Descriptor pointer to process
grx_ptr = (unsigned long)(fd_ptr->next_frame_desc);
// 7. Check Notowner status
if (inl(BDMASTAT) & S_BRxNO) {
outl(S_BRxNO, BDMASTAT);
}
if ((inl(MACRXSTAT) & 0x400) == 0x400) {
outl(gBDMARXCON, BDMARXCON);
outl(gMACRXCON, MACRXCON);
}
return 0;
}
到此程序設(shè)計(jì)部分已經(jīng)基本完成。
燒寫入內(nèi)核
最后,我把程序燒寫入內(nèi)核來(lái)驗(yàn)證本次設(shè)計(jì)。首先將上述文件拷貝到drivers/net,然后編譯uClinux內(nèi)核: 鍵入命令:make menuconfig,內(nèi)核配置; 鍵入命令:make dep,來(lái)尋找依存關(guān)系;鍵入命令:make clean, 清除以前構(gòu)造內(nèi)核時(shí)生成的所有目標(biāo)文件,模塊文件和一些臨時(shí)文件; 鍵入命令:make lib_only,編輯庫(kù)文件;鍵入命令:make user_only,編輯用戶應(yīng)用程序;鍵入命令:make romfs,生成rom文件;鍵入命令:make image ,做到這一步的時(shí)候可能會(huì)出現(xiàn)錯(cuò)誤的信息提示,這是因?yàn)榈谝淮尉幾g時(shí)還沒有romfs.o,所以出錯(cuò),等romfs.o編譯好了以后,如果再進(jìn)行內(nèi)核的編譯,就不會(huì)出現(xiàn)這個(gè)錯(cuò)誤信息了,它完全不影響內(nèi)核的編譯,可以完全不必理會(huì)這個(gè)錯(cuò)誤信息,繼續(xù)進(jìn)行編譯工作; 鍵入命令:make,通過各個(gè)目錄的Makefile文件進(jìn)行,會(huì)在各目錄下生成一大堆目標(biāo)文件。
上述步驟完成后,就完成了對(duì)uClinux源碼的編譯工作。