0 引言
隨著人們對開放源代碼軟件熱情的日益增高,,Linux作為一個功能強大而穩(wěn)定的開源操作系統(tǒng),,越來越受到成千上萬的計算機專家和愛好者的青睞,。在嵌入式領域,通過對Linux進行小型化裁剪后,,使其能夠固化在容量只有幾十兆字節(jié)的存儲器芯片或單片機中,成為應用于特定場合的嵌入式Linux系統(tǒng),。Linux強大的網(wǎng)絡支持功能實現(xiàn)了對包括TCP/IP在內(nèi)的多種協(xié)議的支持,,滿足了面向21世紀的嵌入式系統(tǒng)應用聯(lián)網(wǎng)的需求。因此,,在嵌入式系統(tǒng)開發(fā)調(diào)試時,,網(wǎng)絡接口幾乎成為不可或缺的模塊。
1 嵌入式Linux網(wǎng)絡驅動程序介紹
Linux網(wǎng)絡驅動程序作為Linux網(wǎng)絡子系統(tǒng)的一部分,,位于TCP/IP網(wǎng)絡體系結構的網(wǎng)絡接口層,,主要實現(xiàn)上層協(xié)議棧與網(wǎng)絡設備的數(shù)據(jù)交換。Linux的網(wǎng)絡系統(tǒng)主要是基于BSD Unix的套接字(socket)機制,,網(wǎng)絡設備與字符設備和塊設備不同,,沒有對應地映射到文件系統(tǒng)中的設備節(jié)點。
通常,,Linux驅動程序有兩種加載方式:一種是靜態(tài)地編譯進內(nèi)核,,內(nèi)核啟動時自動加載;另一種是編寫為內(nèi)核模塊,使用insmod命令將模塊動態(tài)加載到正在運行的內(nèi)核,,不需要時可用rmmod命令將模塊卸載,。Linux 2.6內(nèi)核引入了kbuild機制,將外部內(nèi)核模塊的編譯同內(nèi)核源碼樹的編譯統(tǒng)一起來,,大大簡化了特定的參數(shù)和宏的設置,。這樣將編寫好的驅動模塊加入內(nèi)核源碼樹,,只需要修改相應目錄的Kconfig文件,把新的驅動加入內(nèi)核的配置菜單,,然后需要修改相應子目錄中與模塊編譯相關的Kbuild Makefile,即可使新的驅動在內(nèi)核源碼樹中被編譯,。在嵌入式系統(tǒng)驅動開發(fā)時,常常將驅動程序編寫為內(nèi)核模塊,,方便開發(fā)調(diào)試,。調(diào)試完畢后,就可以將驅動模塊編譯進內(nèi)核,,并重新編譯出支持特定物理設備的Linux內(nèi)核,。
2 嵌入式Linux網(wǎng)絡驅動程序的體系結構和實現(xiàn)原理
2.1 Linux網(wǎng)絡設備驅動的體系結構
如圖1所示,Linux網(wǎng)絡驅動程序的體系結構可劃分為4個層次,。Linux內(nèi)核源代碼中提供了網(wǎng)絡設備接口及以上層次的代碼,,因此移植特定網(wǎng)絡硬件的驅動程序的主要工作就是完成設備驅動功能層的相應代碼,根據(jù)底層具體的硬件特性,,定義網(wǎng)絡設備接口struct net_device類型的結構體變量,,并實現(xiàn)其中相應的操作函數(shù)及中斷處理程序。
Linux中所有的網(wǎng)絡設備都抽象為一個統(tǒng)一的接口,,即網(wǎng)絡設備接口,,通過struct net_device類型的結構體變量表示網(wǎng)絡設備在內(nèi)核中的運行情況,這里既包括回環(huán)(loopback)設備,,也包括硬件網(wǎng)絡設備接口,。內(nèi)核通過以dev_base
2.2 net_device 數(shù)據(jù)結構
struct net_device結構體是整個網(wǎng)絡驅動結構的核心,,其中定義了很多供網(wǎng)絡協(xié)議接口層調(diào)用設備的標準方法,,該結構在2.6內(nèi)核源碼樹
2.2.1全局信息及底層硬件信息
name:網(wǎng)絡設備名稱,,默認是以太網(wǎng);
*next:指向全局鏈表下一個設備的指針,,驅動程序中不修改,;
mem_,rmem_:發(fā)送和接收緩沖區(qū)的起始,結束位置,;
base_addr,irq:網(wǎng)絡設備的I/O基地址,,中斷號,ifconfig命令可顯示和修改,;
hard_header_len:硬件頭的長度,,以太網(wǎng)中值為14;
mtu:最大傳輸單元,以太網(wǎng)中值為1500B,;
dev_addr[MAX_ADDR_LEN]:硬件(MAC)地址長度及設備硬件地址,,以太網(wǎng)地址長度是48bit,ether_setup會對其進行正確的設置,;
2.2.2 主要的操作方法
int (*init)(struct net_device *dev); 設備初始化和向系統(tǒng)注冊的函數(shù),,僅調(diào)用一次;
int (*open)(struct net_device *dev);設備打開接口函數(shù),,當用ifconfig激活網(wǎng)絡設備時被調(diào)用,,注冊所用的系統(tǒng)資源(I/O端口,IRQ,,DMA等)同時激活硬件并增加使用計數(shù),;
int (*stop)(struct net_device *dev);執(zhí)行open方法的反操作;
*hard_start_xmit,;初始化數(shù)據(jù)包傳輸?shù)暮瘮?shù),;
*hard_header;該函數(shù)(在hard_start_xmit前被調(diào)用)根據(jù)先前檢索到的源和目標硬件地址建立硬件頭。 eth_header是以太網(wǎng)類型接口的默認函數(shù),;
2.3網(wǎng)絡驅動程序的編寫及實現(xiàn)原理
Linux網(wǎng)絡系統(tǒng)各個層次之間的數(shù)據(jù)傳送都是通過套接字緩沖區(qū)sk_buff完成的,sk_buff數(shù)據(jù)結構是各層協(xié)議數(shù)據(jù)處理的對象,。sk_buff
對于實際開發(fā)以太網(wǎng)驅動程序,可以參照內(nèi)核源碼樹中的相應模板程序,,重點理解網(wǎng)絡驅動的實現(xiàn)原理和程序的結構框架,,然后針對開發(fā)的特定硬件改寫代碼,實現(xiàn)相應的操作函數(shù),。下面結合作者利用Linux2.6.18內(nèi)核在深圳優(yōu)龍公司的FS2410開發(fā)板(SAMSUNG S3C2410處理器)上移植編寫嵌入式CS8900A網(wǎng)卡驅動程序的實例,,說明網(wǎng)絡驅動程序的實現(xiàn)原理。
2.3.1網(wǎng)絡設備初始化
網(wǎng)絡設備的初始化是由net_device結構中的init函數(shù)實現(xiàn)的,,內(nèi)核加載網(wǎng)絡驅動模塊后,,就會調(diào)用初始化過程。實例中初始化函數(shù)_init cs8900_probe中主要完成的工作:
a.調(diào)用內(nèi)核中通用的設置以太網(wǎng)接口的函數(shù)ether_setup();
b.填充net_device結構體變量dev中其它大部分成員,;
c.調(diào)用check_mem_region()檢測I/O地址空間,,然后調(diào)用request_mem_region()申請以dev->base_addr為起始地址的16個連續(xù)的 I/O地址空間;
d.通過cs8900_read()探測網(wǎng)卡CS8900A,讀取ID信息,;
e.設置CS8900A的INTRQ0作為中斷信號輸出引腳,;
f.將MAC地址寫入CS8900A的IA寄存器中;
g.通過register_netdev()將CS8900A注冊到Linux全局網(wǎng)絡設備鏈表中;
2.3.2打開(或關閉)網(wǎng)絡設備
系統(tǒng)響應ifconfig命令時,,打開(關閉)一個網(wǎng)絡接口,。ifconfig命令開始會調(diào)用ioctl(SIOCSIFADDR)來將地址賦予接口。響應SIOCSIFADDR由內(nèi)核來完成,,與設備無關,。接著,ifconfig命令會調(diào)用ioctl(SIOCSIFFLAGS)設置dev->flag的IFF_UP位來打開設備,,這個調(diào)用會使設備的open方法得到調(diào)用,。(當ifconfig調(diào)用ioctl(SIOCSIFFLAGS)清除dev->flag的IFF_UP位時,設備的stop方法將被調(diào)用)
實例中利用cs8900_start()函數(shù)打開網(wǎng)絡設備,,主要完成的工作:
a.通過set_irq_type()向內(nèi)核注冊網(wǎng)絡設備的中斷處理程序,;
b.通過cs8900_set()設置CS8900A網(wǎng)卡中各控制寄存器和配置寄存器;
c.通過內(nèi)核中netif_start_queue()函數(shù)開啟網(wǎng)絡接口的數(shù)據(jù)傳輸隊列,;
2.3.3網(wǎng)絡數(shù)據(jù)包的發(fā)送
數(shù)據(jù)包的發(fā)送和接收是網(wǎng)絡驅動程序中實現(xiàn)的兩個最重要的任務,。當網(wǎng)絡設備被激活時,net_device結構中的open方法被調(diào)用,,它負責打開設備并調(diào)用net_device結構中的hard_header函數(shù)指針建立硬件幀頭信息,。最后通過函數(shù)dev_queue_xmit()
實例中,,hard_start_xmit方法即為網(wǎng)絡設備數(shù)據(jù)發(fā)送函數(shù)cs8900_send_start(),該函數(shù)實現(xiàn)把數(shù)據(jù)發(fā)送到以太網(wǎng)上,,由網(wǎng)絡協(xié)議接口層函數(shù)dev_queue_xmit()對其調(diào)用,。cs8900_send_start()中主要完成的工作:
a.發(fā)送數(shù)據(jù)前關閉中斷,中止網(wǎng)絡設備的數(shù)據(jù)傳輸隊列;
b.向CS8900A寄存器TxCMD中寫入傳送數(shù)據(jù)命令控制字,,向寄存器TxLength中寫入待發(fā)送數(shù)據(jù)幀長度,;
c.通過cs8900_read()反復讀取CS8900A總線狀態(tài)寄存器BusST信息,直到其已經(jīng)準備好接收來自主機的數(shù)據(jù);
d.調(diào)用cs8900_frame_write()將待發(fā)數(shù)據(jù)送入CS8900A的sk_buff中,,硬件設備會將數(shù)據(jù)幀發(fā)送到以太網(wǎng)上,;
e.記錄數(shù)據(jù)幀的發(fā)送時刻,打開中斷,釋放sk_buff緩存,函數(shù)返回0;
2.3.4網(wǎng)絡數(shù)據(jù)包的接收和中斷處理
網(wǎng)絡設備是異步地接收外來的數(shù)據(jù)包并且主動的“請求”將硬件獲得的數(shù)據(jù)包壓入內(nèi)核,。網(wǎng)絡設備接收數(shù)據(jù)包是通過中斷實現(xiàn)的,。對于網(wǎng)絡接口,接收到新數(shù)據(jù)包,發(fā)送完成或者報告錯誤信息及連接狀態(tài)等都會觸發(fā)中斷,,通常中斷處理程序通過檢測硬件狀態(tài)寄存器判斷是哪種情況,。
當設備收到數(shù)據(jù)后會產(chǎn)生一個中斷,由硬件通知驅動程序有數(shù)據(jù)包到達,。在中斷處理程序中驅動程序申請一塊sk_buff(一般定義為skb)緩沖區(qū),,然后從硬件讀出數(shù)據(jù)放到申請好的緩沖區(qū)里,接下來填充sk_buff中的部分信息:包括接收到數(shù)據(jù)的設備結構體指針填入skb->dev,;收到數(shù)據(jù)幀的類型填入skb->protocol,;把指針skb->mac.raw指向硬件數(shù)據(jù)并丟棄硬件針頭(skb_pull);設置skb->pkt_type,,標明鏈路層數(shù)據(jù)類型,。最后調(diào)用協(xié)議接口層函數(shù)netif_rx()
實例中數(shù)據(jù)接收函數(shù)cs8900_receive()由網(wǎng)絡驅動的中斷處理函數(shù)調(diào)用,,主要完成如下工作:
a.通過從I/O口讀取RxStatus和RxLength的值,,確定接收數(shù)據(jù)幀的狀態(tài)信息和長度;
b.判斷接收數(shù)據(jù)幀的狀態(tài)是否正常,,若異常則記錄相關錯誤信息,然后函數(shù)返回,;
c.正常情況下,,在內(nèi)存中申請一塊sk_buff緩存,并將數(shù)據(jù)從CS8900A的片內(nèi)存儲器傳送到sk_buff緩存中,;d.從數(shù)據(jù)幀中獲取協(xié)議頭并賦給skb->protocol,;
e.通過調(diào)用netif_rx()函數(shù)將接收到的數(shù)據(jù)送往上層協(xié)議棧進行處理;
f.記錄接收數(shù)據(jù)的時間并更新統(tǒng)計信息,;
3將設備驅動模塊編譯進內(nèi)核
設計好模塊化的網(wǎng)絡驅動程序后,,我們就可以編譯這個內(nèi)核模塊,并將這個自定義的內(nèi)核模塊作為Linux系統(tǒng)源碼的一部分編譯出新的系統(tǒng),。下面介紹的內(nèi)容均在Linux2.6.18內(nèi)核上編譯通過,,可以在2.6.x版本內(nèi)核中通用。如前所述,,由于Linux2.6內(nèi)核引入了kbuild的新機制,,使得編譯新的內(nèi)核模塊或者將自己編寫的內(nèi)核模塊集成到內(nèi)核源碼中都變得非常簡單了。
Linux2.6內(nèi)核中,編譯內(nèi)核模塊首先要在/usr/src下正確配置和構造內(nèi)核源碼樹,,即把需要版本的內(nèi)核源碼解壓在/usr/src/,,并在內(nèi)核源碼的主目錄下(這里為/usr/src/linux-2.6.18.3),使用make menuconfig或者make gconfig命令配置內(nèi)核,,然后使用make all完整編譯內(nèi)核,。
下面以作者開發(fā)的CS8900A網(wǎng)卡驅動為實例,介紹如何將網(wǎng)絡設備驅動模塊編譯進內(nèi)核,。
a.在系統(tǒng)源碼樹drivers目錄下創(chuàng)建新目錄Cs8900,;
b.將編寫好的文件cs8900.c和cs8900.h拷貝到drivers/Cs8900目錄下;
c.在drivers/Cs8900目錄下,,編寫Makefile文件:
#Makefile for CS8900A Network Driver
obj -$(CONFIG_DRIVER_CS8900A) +=cs8900.o
d.在drivers/Cs8900目錄下,,編寫Kconfig文件:
#Just for CS8900A Network Interface
menu "CS8900A Network Interface support"
config DRIVER_CS8900A
tristate "CS8900A support"
--------help--------
This is a network driver module for CS8900A.
endmenu
e.在driver目錄下的Kconfig文件endmenu語句前,加入一行:
source "drivers/Cs8900/Kconfig"
這樣在內(nèi)核源碼樹的主目錄下,,通過make menuconfig或者make gconfig命令就可以在Device Drivers選項的下面找到CS8900A Network Interface support選項,,并找到CS8900A support的選擇菜單,它有三種狀態(tài):未選中(不編譯),、選中(M)一編譯為模塊,、選中(*)一編譯為新系統(tǒng)一部分。
重新編譯內(nèi)核即可得到支持CS8900A網(wǎng)卡的內(nèi)核,,然后將內(nèi)核下載到FS2410的開發(fā)板上,,通過配置網(wǎng)絡參數(shù),就可以測試網(wǎng)卡驅動程序的行為了,。
4 結束語
在這個信息爆炸的時代,,人們對于網(wǎng)絡的需求愈發(fā)強烈,越來越多的嵌入式設備都需要具有以太網(wǎng)的接入功能,,因此開發(fā)網(wǎng)絡驅動程序對于很多嵌入式產(chǎn)品的研發(fā)至關重要,。具體開發(fā)嵌入式Linux網(wǎng)絡驅動程序時,可以參照內(nèi)核中已經(jīng)支持的網(wǎng)絡驅動源代碼,,在重點理解Linux網(wǎng)絡驅動實現(xiàn)原理的基礎上,,按照模塊設計較為固定的開發(fā)模式,結合具體物理設備的硬件手冊,,移植編寫需要的模塊化的網(wǎng)絡驅動程序,。