/*
*By Neil Chiao (neilchiao at gmail.com)
*轉載請注明出處:neilengineer.cublog.cn
*本文部分內容來(lái)自網(wǎng)絡(luò )
*歡迎到“新星灣(www.xinxingwan.com)”指導
*/
1、Linux網(wǎng)絡(luò )設備驅動(dòng)的體系結構
Linux網(wǎng)絡(luò )驅動(dòng)程序的體系結構可劃分為4個(gè)層次。Linux內核源代碼中提供了網(wǎng)絡(luò )設備接口及以上層次的代碼,因此移植特定網(wǎng)絡(luò )硬件的驅動(dòng)程序的主要工作就是完成設備驅動(dòng)功能層的相應代碼,根據底層具體的硬件特性,定義網(wǎng)絡(luò )設備接口struct net_device類(lèi)型的結構體變量,并實(shí)現其中相應的操作函數及中斷處理程序。
Linux中所有的網(wǎng)絡(luò )設備都抽象為一個(gè)統一的接口,即網(wǎng)絡(luò )設備接口,通過(guò)struct net_device類(lèi)型的結構體變量表示網(wǎng)絡(luò )設備在內核中的運行情況,這里既包括回環(huán)(loopback)設備,也包括硬件網(wǎng)絡(luò )設備接口。內核通過(guò)以dev_base<drives/net/space.c>為頭指針的設備鏈表來(lái)管理所有的網(wǎng)絡(luò )設備。
net_device數據結構
struct net_device結構體是整個(gè)網(wǎng)絡(luò )驅動(dòng)結構的核心,其中定義了很多供網(wǎng)絡(luò )協(xié)議接口層調用設備的標準方法,該結構在<include/linux/netdevice.h>文件中定義,下面只列出其中主要的成員。
1)全局信息及底層硬件信息
name: 網(wǎng)絡(luò )設備名稱(chēng);
base_addr,irq: 網(wǎng)絡(luò )設備的I/O基地址,中斷號,ifconfig命令可顯示和修改;
hard_header_len: 硬件頭的長(cháng)度,以太網(wǎng)中值為14;
mtu: 最大傳輸單元,以太網(wǎng)中值為1500B;
dev_addr[MAX_ADDR_LEN]: MAC地址,ether_setup會(huì )對其進(jìn)行正確的設置;
2)主要的操作方法
int (*init)(struct net_device *dev); 設備初始化和向系統注冊的函數,僅調用一次;
int (*open)(struct net_device *dev); 設備打開(kāi)接口函數,當用ifconfig激活網(wǎng)絡(luò )設備時(shí)被調用,注冊所用的系統資源(I/O端口,IRQ,DMA等)同時(shí)激活硬件并增加使用計數;
int (*stop)(struct net_device *dev); 執行open方法的反操作;
*hard_start_xmit; 初始化數據包傳輸的函數;
網(wǎng)絡(luò )驅動(dòng)程序實(shí)現原理
Linux網(wǎng)絡(luò )系統各個(gè)層次之間的數據傳送都是通過(guò)套接字緩沖區sk_buff完成的,sk_buff數據結構是各層協(xié)議數據處理的對象。sk_buff<linux/skbuff.h>是驅動(dòng)程序與網(wǎng)絡(luò )之間交換數據的媒介,驅動(dòng)程序向網(wǎng)絡(luò )發(fā)送數據時(shí),必須從其中獲取數據源和數據長(cháng)度;驅動(dòng)程序從網(wǎng)絡(luò )上接收到數據后也要將數據保存到sk_buff中才能交給上層協(xié)議處理。
網(wǎng)絡(luò )驅動(dòng)程序主要的幾個(gè)操作如下:
1) 初始化(init)
驅動(dòng)程序必須有一個(gè)初始化方法。在把驅動(dòng)程序載入系統的時(shí)候會(huì )調用這個(gè)初始化程序,它做以下幾方面的工作。
檢測設備。配置和初始化硬件。配置或協(xié)商好硬件占用的資源以后,就可以向系統申請這些資源。有些資源是可以和別的設備共享的,如中斷。有些是不能共享的,如IO、DMA。接下來(lái)你要初始化device結構中的變量。最后,你可以讓硬件正式開(kāi)始工作。
2) 打開(kāi)(open)
open這個(gè)方法在網(wǎng)絡(luò )設備驅動(dòng)里是網(wǎng)絡(luò )設備被激活的時(shí)候被調用(即設備狀態(tài)由down-->up)。所以實(shí)際上很多在initialize中的工作可以放到這里來(lái)做。比如資源的申請,硬件的激活。如果dev->open返回非0(error),則硬件的狀態(tài)還是down。 open方法另一個(gè)作用是如果驅動(dòng)程序做為一個(gè)模塊被裝入,則要防止模塊卸載時(shí)設備處于打開(kāi)狀態(tài)。在open方法里要調用MOD_INC_USE_COUNT宏。
3)關(guān)閉(stop)
stop方法做和open相反的工作??梢葬尫拍承┵Y源以減少系統負擔。stop是在設備狀態(tài)由up轉為down時(shí)被調用的。另外如果是做為模塊裝入的驅動(dòng)程序,close里應該調用MOD_DEC_USE_COUNT,減少設備被引用的次數,以使驅動(dòng)程序可以被卸載。
4) 發(fā)送(hard_start_xmit)
所有的網(wǎng)絡(luò )設備驅動(dòng)都必須有這個(gè)發(fā)送方法。在系統調用驅動(dòng)程序的xmit時(shí),發(fā)送的數據放在一個(gè)sk_buff結構中。一般的驅動(dòng)把數據傳給硬件發(fā)出去。也有一些特殊的設備比如loopback把數據組成一個(gè)接收數據再回送給系統,或者dummy設備直接丟棄數據。如果發(fā)送成功,hard_start_xmit方法里釋放sk_buff,返回0(發(fā)送成功)。如果設備暫時(shí)無(wú)法處理,比如硬件忙,則返回1。這時(shí)如果dev->tbusy置為非0,則系統認為硬件忙,要等到dev->tbusy置0以后才會(huì )再次發(fā)送。tbusy的置0任務(wù)一般由中斷完成。硬件在發(fā)送結束后產(chǎn)生中斷,這時(shí)可以把tbusy置0,然后用mark_bh()調用通知系統可以再次發(fā)送。在發(fā)送不成功的情況下,也可以不置dev->tbusy為非0,這樣系統會(huì )不斷嘗試重發(fā)。如果hard_start_xmit發(fā)送不成功,則不要釋放sk_buff。傳送下來(lái)的sk_buff中的數據已經(jīng)包含硬件需要的幀頭。所以在發(fā)送方法里不需要再填充硬件幀頭,數據可以直接提交給硬件發(fā)送。sk_buff是被鎖住的(locked),確保其他程序不會(huì )存取它。
5) 接收(reception)
有數據收到時(shí),驅動(dòng)程序通知系統。一般設備收到數據后都會(huì )產(chǎn)生一個(gè)中斷,在中斷處理程序中驅動(dòng)程序申請一塊sk_buff(skb),從硬件讀出數據放置到申請好的緩沖區里。接下來(lái)填充sk_buff中的一些信息。skb->dev= dev,判斷收到幀的協(xié)議類(lèi)型,填入skb->protocol(多協(xié)議的支持)。把指針skb->mac.raw指向硬件數據然后丟棄硬件幀頭(skb_pull)。還要設置skb->pkt_type,標明第二層(鏈路層)數據類(lèi)型??梢允且韵骂?lèi)型:
PACKET_BROADCAST : 鏈路層廣播;
PACKET_MULTICAST : 鏈路層組播;
PACKET_SELF : 發(fā)給自己的幀;
PACKET_OTHERHOST : 發(fā)給別人的幀(監聽(tīng)模式時(shí)會(huì )有這種幀)。
最后調用netif_rx()把數據傳送給協(xié)議層。netif_rx()里數據放入處理隊列然后返回,真正的處理是在中斷返回以后,這樣可以減少中斷時(shí)間。調用netif_rx()以后,驅動(dòng)程序就不能再存取數據緩沖區skb。
2、dm9000網(wǎng)卡驅動(dòng)簡(jiǎn)析
Dm9000是嵌入式平臺上常用的MAC+PHY芯片。針對各種SoC,2.6內核引進(jìn)了platform設備模型來(lái)封裝相關(guān)驅動(dòng),Dm9000驅動(dòng)就用platform改寫(xiě)了。
在分析dm9000驅動(dòng)前,先看一下platform,其大致調用過(guò)程如下:
start_kernelàrest_initàkernel_initàdo_basic_setupà driver_inità platform_bus_init
int __init platform_bus_init(void)
{
。。。。。。
error = device_register(&platform_bus);
。。。。。。
error = bus_register(&platform_bus_type);
。。。。。。
}
dm9000驅動(dòng)模塊初始化函數dm9000_init使用了platform_driver_register來(lái)注冊:
static int __init dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);
return platform_driver_register(&dm9000_driver);
}
其中,&dm9000_driver如下定義:
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
.suspend = dm9000_drv_suspend,
.resume = dm9000_drv_resume,
};
使用platform_driver_register(&dm9000_driver) 注冊dm9000_driver,會(huì )調用其中的.probe也就是這里的dm9000_probe函數。其調用過(guò)程如下:
platform_driver_register(&dm9000_driver)à
driver_register(&drv->driver)à
bus_add_driver(drv)à
driver_attach(drv) à
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)à
__driver_attachà
driver_probe_device(drv, dev)à
really_probe(dev, drv) à
if (dev->bus->probe) dev->bus->probe(dev)
else if (drv->probe) drv->probe(dev) //調用dm9000_probe()
dm9000_probe函數分析
dm9000_probe函數實(shí)現如下:
static int __devinit dm9000_probe(struct platform_device *pdev)
{
......
ndev = alloc_etherdev(sizeof(struct board_info));
//分配空間,并初始化部分東西
......
db = ndev->priv;
memset(db, 0, sizeof(*db));
db->dev = &pdev->dev;
db->ndev = ndev;
INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
iosize = res_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,pdev->name);
db->io_addr = ioremap(db->addr_res->start, iosize);
iosize = res_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name);
db->io_data = ioremap(db->data_res->start, iosize);
//上述代碼初始化bd結構體,bd是dm9000.c中定義的board結構,其中db = ndev->priv;
db初始化為net device中的priv成員。
......
//初始化net_device結構體中的一些重要函數指針及變量
ether_setup(ndev);
ndev->open = &dm9000_open;
ndev->hard_start_xmit = &dm9000_start_xmit;
ndev->tx_timeout = &dm9000_timeout;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->stop = &dm9000_stop;
ndev->set_multicast_list = &dm9000_hash_table;
ndev->ethtool_ops = &dm9000_ethtool_ops;
ndev->do_ioctl = &dm9000_ioctl;
......
//初始化MII接口使用的最重要2個(gè)函數
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write;
......
//從E2PROM中讀取MAC地址,并驗證是否為合法MAC地址
for (i = 0; i < 6; i += 2)
dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);
if (!is_valid_ether_addr(ndev->dev_addr))
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
......
//最終注冊
ret = register_netdev(ndev);
}
DM9000的驅動(dòng)初始化基本是由probe函數進(jìn)行。
實(shí)際發(fā)送數據在dm9000_start_xmit函數中實(shí)現,而接收數據在中斷處理函數dm9000_interrupt實(shí)現。具體實(shí)現,請大家自己看代碼去:) 。