1、網(wǎng)卡設(shè)備驅(qū)動原理
1.1 層次結(jié)構(gòu)
Linux系統(tǒng)對網(wǎng)絡(luò)設(shè)備驅(qū)動定義了4個層次, 這4個層次有到下分為:
-
1、網(wǎng)絡(luò)協(xié)議接口層:實現(xiàn)統(tǒng)一的數(shù)據(jù)包收發(fā)的協(xié)議。該層主要負責調(diào)用
dev_queue_xmit()函數(shù)發(fā)送數(shù)據(jù),netif_rx()函數(shù)接收數(shù)據(jù)。當上層 ARP 或 IP 協(xié)議需要發(fā)送數(shù)據(jù)包時,它將調(diào)用網(wǎng)絡(luò)協(xié)議接口層的dev_queue_xmit()函數(shù)發(fā)送該數(shù)據(jù)包,同時需傳遞給該函數(shù)一個指向struct sk_buff數(shù)據(jù) 結(jié)構(gòu)的指針。dev_queue_xmit()函數(shù)的原型為:
dev_queue_xmit (struct sk_buff * skb );
同樣地,上層對數(shù)據(jù)包的接收也通過向 netif_rx()函數(shù)傳遞一個 struct sk_buff數(shù)據(jù) 結(jié)構(gòu)的指針來完成。netif_rx()函數(shù)的原型為:
int netif_rx(struct sk_buff *skb);
-
2、網(wǎng)絡(luò)設(shè)備接口層:通過
net_device結(jié)構(gòu)體來描述一個具體的網(wǎng)絡(luò)設(shè)備的信息,實現(xiàn)不同的硬件的統(tǒng)一 -
3、設(shè)備驅(qū)動功能層:用來負責驅(qū)動網(wǎng)絡(luò)設(shè)備硬件來完成各個功能, 它通過
hard_start_xmit()函數(shù)啟動發(fā)送操作, 并通過網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作。 - 4、網(wǎng)絡(luò)設(shè)備與媒介層:用來負責完成數(shù)據(jù)包發(fā)送和接收的物理實體, 設(shè)備驅(qū)動功能層的函數(shù)都在這物理上驅(qū)動的
層次結(jié)構(gòu)如下圖所示:

設(shè)計具體的網(wǎng)絡(luò)設(shè)備驅(qū)動程序時,我們需要完成的主要工作是編寫設(shè)備驅(qū)動功 能層的相關(guān)函數(shù)以填充 net_device 數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將 net_device 注冊入內(nèi)核。
1.2 網(wǎng)卡驅(qū)動的初始化
網(wǎng)卡驅(qū)動程序,只需要編寫網(wǎng)絡(luò)設(shè)備接口層,填充net_device數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將net_device注冊入內(nèi)核,設(shè)置硬件相關(guān)操作,使能中斷處理等。
1.2.1 初始化網(wǎng)卡步驟
- 1)使用
alloc_netdev()來分配一個net_device結(jié)構(gòu)體 - 2)設(shè)置網(wǎng)卡硬件相關(guān)的寄存器
- 3)設(shè)置
net_device結(jié)構(gòu)體的成員 - 4)使用
register_netdev()來注冊net_device結(jié)構(gòu)體
1.2.2 net_device結(jié)構(gòu)體
網(wǎng)絡(luò)設(shè)備接口層的主要功能是為千變?nèi)f化的網(wǎng)絡(luò)設(shè)備定義了統(tǒng)一、抽象的數(shù)據(jù)結(jié) 構(gòu) net_device 結(jié)構(gòu)體,以不變應(yīng)萬變,實現(xiàn)多種硬件在軟件層次上的統(tǒng)一。 net_device 結(jié)構(gòu)體在內(nèi)核中指代一個網(wǎng)絡(luò)設(shè)備,網(wǎng)絡(luò)設(shè)備驅(qū)動程序只需通過填充 net_device 的具體成員并注冊 net_device 即可實現(xiàn)硬件操作函數(shù)與內(nèi)核的掛接。 net_device 本身是一個巨型結(jié)構(gòu)體,包含網(wǎng)絡(luò)設(shè)備的屬性描述和操作接口。當我 們編寫網(wǎng)絡(luò)設(shè)備驅(qū)動程序時,只需要了解其中的一部分。
struct net_device
{
char name[IFNAMSIZ]; //網(wǎng)卡設(shè)備名稱
unsigned long mem_end; //該設(shè)備的內(nèi)存結(jié)束地址
unsigned long mem_start; //該設(shè)備的內(nèi)存起始地址
unsigned long base_addr; //該設(shè)備的內(nèi)存I/O基地址
unsigned int irq; //該設(shè)備的中斷號
unsigned char if_port; //該字段僅針對多端口設(shè)備,用于指定使用的端口類型
unsigned char dma; //該設(shè)備的DMA通道
unsigned long state; //網(wǎng)絡(luò)設(shè)備和網(wǎng)絡(luò)適配器的狀態(tài)信息
struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統(tǒng)計信息,運行ifconfig便會調(diào)用該成員函數(shù),并返回一個net_device_stats結(jié)構(gòu)體獲取信息
struct net_device_stats stats; //用來保存統(tǒng)計信息的net_device_stats結(jié)構(gòu)體
unsigned long features; //接口特征,
unsigned int flags; //flags指網(wǎng)絡(luò)接口標志,以IFF_開頭,包括:IFF_UP( 當設(shè)備被激活并可以開始發(fā)送數(shù)據(jù)包時, 內(nèi)核設(shè)置該標志)、 IFF_AUTOMEDIA(設(shè)置設(shè)備可在多種媒介間切換)、IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調(diào)試模式, 可用于控制printk調(diào)用的詳細程度) 、 IFF_LOOPBACK( 回環(huán))、IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執(zhí)行ARP,點對點接口就不需要運行 ARP) 和IFF_POINTOPOINT( 接口連接到點到點鏈路) 等。
unsigned mtu; //最大傳輸單元,也叫最大數(shù)據(jù)包
unsigned short type; //接口的硬件類型
unsigned short hard_header_len; //硬件幀頭長度,在以太網(wǎng)設(shè)備的初始化函數(shù)中一般被賦為ETH_HLEN,即14
unsigned char *dev_addr; //存放設(shè)備的MAC地址,需由驅(qū)動程序從硬件上讀出
unsigned char broadcast[MAX_ADDR_LEN]; //存放設(shè)備的廣播地址,對于以太網(wǎng)而言,地址長度為6個0XFF
unsigned long last_rx; //接收數(shù)據(jù)包的時間戳,調(diào)用netif_rx()后賦上jiffies即可
unsigned long trans_start; //發(fā)送數(shù)據(jù)包的時間戳,當要發(fā)送的時候賦上jiffies即可
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);//數(shù)據(jù)包發(fā)送函數(shù), 以使得驅(qū)動程序能獲取從上層傳遞下來的數(shù)據(jù)包。
void (*tx_timeout) (struct net_device *dev); //發(fā)包超時處理函數(shù),需采取重新啟動數(shù)據(jù)包發(fā)送過程或重新啟動硬件等策略來恢復(fù)網(wǎng)絡(luò)設(shè)備到正常狀態(tài)
... ...
}
1.2.3 net_device_stats結(jié)構(gòu)體
net_device_stats 結(jié)構(gòu)體定義在內(nèi)核的 include/linux/netdevice.h 文件中,其中重要成員如下所示:
struct net_device_stats
{
unsigned long rx_packets; /*收到的數(shù)據(jù)包數(shù)*/
unsigned long tx_packets; /*發(fā)送的數(shù)據(jù)包數(shù) */
unsigned long rx_bytes; /*收到的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/ unsigned long tx_bytes; /*發(fā)送的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/
unsigned long rx_errors; /*收到的錯誤數(shù)據(jù)包數(shù)*/
unsigned long tx_errors; /*發(fā)送的錯誤數(shù)據(jù)包數(shù)*/
... ...
}
net_device_stats 結(jié)構(gòu)體適宜包含在設(shè)備的私有信息結(jié)構(gòu)體中,而其中統(tǒng)計信息的 修改則應(yīng)該在設(shè)備驅(qū)動的與發(fā)送和接收相關(guān)的具體函數(shù)中完成,這些函數(shù)包括中斷處 理程序、數(shù)據(jù)包發(fā)送函數(shù)、數(shù)據(jù)包發(fā)送超時函數(shù)和數(shù)據(jù)包接收相關(guān)函數(shù)等。我們應(yīng)該 在這些函數(shù)中添加相應(yīng)的代碼:
/* 發(fā)送超時函數(shù) */
void xxx_tx_timeout(struct net_device *dev)
{
struct xxx_priv *priv = netdev_priv(dev);
...
priv->stats.tx_errors++; /* 發(fā)送錯誤包數(shù)加 1 */
...
}
/* 中斷處理函數(shù) */
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
switch (status &ISQ_EVENT_MASK)
{
...
case ISQ_TRANSMITTER_EVENT: /
priv->stats.tx_packets++; /* 數(shù)據(jù)包發(fā)送成功,tx_packets 信息加1 */
netif_wake_queue(dev); /* 通知上層協(xié)議 */
if ((status &(TX_OK | TX_LOST_CRS | TX_SQE_ERROR |
TX_LATE_COL | TX_16_COL)) != TX_OK) /*讀取硬件上的出錯標志*/
{
/* 根據(jù)錯誤的不同情況,對 net_device_stats 的不同成員加 1 */
if ((status &TX_OK) == 0)
priv->stats.tx_errors++;
if (status &TX_LOST_CRS)
priv->stats.tx_carrier_errors++;
if (status &TX_SQE_ERROR)
priv->stats.tx_heartbeat_errors++;
if (status &TX_LATE_COL)
priv->stats.tx_window_errors++;
if (status &TX_16_COL)
priv->stats.tx_aborted_errors++;
}
break;
case ISQ_RX_MISS_EVENT:
priv->stats.rx_missed_errors += (status >> 6);
break;
case ISQ_TX_COL_EVENT:
priv->stats.collisions += (status >> 6);
break;
}
}
1.2.4 sk_buff結(jié)構(gòu)體
sk_buff 結(jié)構(gòu)體非常重要,它的含義為“套接字緩沖區(qū)”,用于在 Linux 網(wǎng)絡(luò)子系 統(tǒng)中的各層之間傳遞數(shù)據(jù),是 Linux 網(wǎng)絡(luò)子系統(tǒng)數(shù)據(jù)傳遞的“中樞神經(jīng)”。當發(fā)送數(shù)據(jù)包時,Linux 內(nèi)核的網(wǎng)絡(luò)處理模塊必須建立一個包含要傳輸?shù)臄?shù)據(jù)包 的 sk_buff,然后將 sk_buff 遞交給下層,各層在 sk_buff 中添加不同的協(xié)議頭直至交 給網(wǎng)絡(luò)設(shè)備發(fā)送。同樣地,當網(wǎng)絡(luò)設(shè)備從網(wǎng)絡(luò)媒介上接收到數(shù)據(jù)包后,它必須將接收 到的數(shù)據(jù)轉(zhuǎn)換為 sk_buff 數(shù)據(jù)結(jié)構(gòu)并傳遞給上層,各層剝?nèi)ハ鄳?yīng)的協(xié)議頭直至交給用 戶。
參看 linux/skbuff.h 中的源代碼,sk_buff 結(jié)構(gòu)體包含的主要成員如下:
/* /include/linux/skbuff.h */
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next; //指向下一個sk_buff結(jié)構(gòu)體
struct sk_buff *prev; //指向前一個sk_buff結(jié)構(gòu)體
ktime_t tstamp;
struct sock *sk;
struct net_device *dev;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
......
unsigned int len, //數(shù)據(jù)包的總長度,包括線性數(shù)據(jù)和非線性數(shù)據(jù)
data_len; //非線性的數(shù)據(jù)長度
__u16 mac_len, //mac包頭長度
hdr_len;
.....
__u32 priority; //該sk_buff結(jié)構(gòu)體的優(yōu)先級
......
__be16 protocol; //存放上層的協(xié)議類型,可以通過eth_type_trans()來獲取
... ...
sk_buff_data_t transport_header; //傳輸層頭部的偏移值
sk_buff_data_t network_header; //網(wǎng)絡(luò)層頭部的偏移值
sk_buff_data_t mac_header; //MAC數(shù)據(jù)鏈路層頭部的偏移值
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail; //指向緩沖區(qū)的數(shù)據(jù)包末尾
sk_buff_data_t end; //指向緩沖區(qū)的末尾
unsigned char *head, //指向緩沖區(qū)的協(xié)議頭開始位置
*data; //指向緩沖區(qū)的數(shù)據(jù)包開始位置
unsigned int truesize;
atomic_t users;
}
- 數(shù)據(jù)緩沖區(qū)指針 head、data、tail 和 end。
head 指針指向內(nèi)存中已分配的用于承載網(wǎng)絡(luò)數(shù)據(jù)的緩沖區(qū)的起始地址;
data 指針則指向?qū)?yīng)當前協(xié)議層有效數(shù)據(jù)的起始地址。各層的有效數(shù)據(jù)信息包含的內(nèi)容都不一樣:對于傳輸層而言,用戶數(shù)據(jù)和傳輸層協(xié)議頭屬于有效數(shù)據(jù)。 l 對于網(wǎng)絡(luò)層而言,用戶數(shù)據(jù)、傳輸層協(xié)議頭和網(wǎng)絡(luò)層協(xié)議頭是其有效數(shù)據(jù)。 l 對于數(shù)據(jù)鏈路層而言,用戶數(shù)據(jù)、傳輸層協(xié)議頭、網(wǎng)絡(luò)層協(xié)議頭和鏈路層頭 部都屬于有效數(shù)據(jù)。 因此,data 指針的值需隨著當前擁有 sk_buff 的協(xié)議層的變化進行相應(yīng)的移動。
tail 指針則指向?qū)?yīng)當前協(xié)議層有效數(shù)據(jù)負載的結(jié)尾地址,與 data 指針對應(yīng)。
end 指針指向內(nèi)存中分配的數(shù)據(jù)緩沖區(qū)的結(jié)尾,與 head 指針對應(yīng)。
其實,end 指針所指地 址 數(shù) 據(jù) 緩 沖 區(qū) 的 末 尾 還 包 括 一 個 skb_shared_info結(jié)構(gòu)體的空間,這個結(jié)構(gòu)體 存放分隔存儲的數(shù)據(jù)片段,意味著可以將數(shù) 據(jù)包的有效數(shù)據(jù)分成幾片存儲在不同的內(nèi)存空間中。 每一個分片frags的長度上限是一頁。
- sk_buff結(jié)構(gòu)體的空間
- image-20210809231316947
- 長度信息 len、data_len、truesize。
len是指數(shù)據(jù)包有 效數(shù)據(jù)的長度,包括協(xié)議頭和負載;
data_len 這個成員,它記錄分片的 數(shù)據(jù)長度;
truesize 表示緩存區(qū)的整體長度: sizeof(struct sk_buff) + “傳入 alloc_skb()或dev_alloc_skb()的長度“(但不包括結(jié)構(gòu)體 skb_shared_info 的長度)。
- sk_buff-> data數(shù)據(jù)包格式

-
套接字緩沖區(qū)操作
-
(1)分配
-
struct sk_buff *alloc_skb(unsigned int len,int priority);alloc_skb()函數(shù)分配一個套接字緩沖區(qū)和一個數(shù)據(jù)緩沖區(qū),參數(shù) len 為數(shù)據(jù)緩沖區(qū) 的空間大小,以 16 字節(jié)對齊,參數(shù) priority 為內(nèi)存分配的優(yōu)先級 -
struct sk_buff *dev_alloc_skb(unsigned int len);dev_alloc_skb()函數(shù)只是以 GFP_ATOMIC 優(yōu)先級(代表分配過程不能被中斷)調(diào) 用上面的 alloc_skb()函數(shù),并保存 skb->head 和 skb->data 之間的 16 個字節(jié)。 - 分配成功之后,因為還沒有存放具體的網(wǎng)絡(luò)數(shù)據(jù)包,所以 sk_buff 的 data、tail 指 針都指向存儲空間的起始地址 head,而 len 的大小則為 0。
-
-
(2)釋放
void kfree_skb(struct sk_buff *skb); //在內(nèi)核內(nèi)部使用,而網(wǎng)絡(luò)設(shè)備 驅(qū)動程序中則必須使用下面3個其一 void dev_kfree_skb(struct sk_buff *skb); //用于非中斷上下文 void dev_kfree_skb_irq(struct sk_buff *skb); //用于中斷上下文 void dev_kfree_skb_any(struct sk_buff *skb); //在中斷和非中斷上下文中皆可采用
-
-
(3)指針移動
套接字緩沖區(qū)中的數(shù)據(jù)緩沖區(qū)指針移動操作包括 put(放置)、push(推)、 pull(拉)、reserve(保留)等。
-
put 操作(用于在緩沖區(qū)尾部添加數(shù)據(jù)):
-
unsigned char *skb_put(struct sk_buff *skb, unsigned int len); //會檢測放入緩沖區(qū)的數(shù)據(jù) unsigned char *__skb_put(struct sk_buff *skb, unsigned int len); //不會檢測放入緩沖區(qū)的數(shù)據(jù)上述函數(shù)將 tail 指針下移,增加 sk_buff 的 len 值,并返回 skb->tail 的當前值。
-
-
push 操作(用于在數(shù)據(jù)包發(fā)送時添加頭部)
-
unsigned char *skb_push(struct sk_buff *skb, unsigned int len); //會檢測放入緩沖區(qū)的數(shù)據(jù) unsigned char *_ _skb_push(struct sk_buff *skb, unsigned int len); //不會檢測放入緩沖區(qū)的數(shù)據(jù)push 操作在存儲空間的頭部增加一段可以存儲網(wǎng)絡(luò)數(shù)據(jù)包的空間。
-
-
pull 操作(用于下層協(xié)議向上層協(xié)議移交數(shù)據(jù)包,使 data 指針指向上一層協(xié)議的協(xié)議頭)
-
unsigned char * skb_pull(struct sk_buff *skb, unsigned int len);skb_pull()函數(shù)將 data 指針下移,并減小 skb 的 len 值。
-
-
reserve 操作(用于在存儲空間 的頭部預(yù)留 len 長度的空隙)
-
void skb_reserve(struct sk_buff *skb, unsigned int len);skb_reserve()函數(shù)將 data 指針和 tail 指針同時下移
-
-
1.3 網(wǎng)卡驅(qū)動發(fā)包過程
在內(nèi)核中,當上層要發(fā)送一個數(shù)據(jù)包時, 就會調(diào)用網(wǎng)絡(luò)設(shè)備層里net_device數(shù)據(jù)結(jié)構(gòu)的成員hard_start_xmit()將數(shù)據(jù)包發(fā)送出去。
hard_start_xmit()發(fā)包函數(shù)需要我們自己構(gòu)建,該函數(shù)原型如下所示:
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
這個函數(shù)中需要涉及到sk_buff結(jié)構(gòu)體,含義為(socket buffer)套接字緩沖區(qū),用來網(wǎng)絡(luò)各個層次之間傳遞數(shù)據(jù).
發(fā)包函數(shù)處理步驟:
1、把數(shù)據(jù)包發(fā)出去之前,需要使用netif_stop_queue()來停止上層傳下來的數(shù)據(jù)包;
2.1、設(shè)置寄存器,通過網(wǎng)絡(luò)設(shè)備硬件來發(fā)送數(shù)據(jù)
2.2、當數(shù)據(jù)包發(fā)出去后, 再調(diào)用dev_kfree_skb()函數(shù)來釋放sk_buff,該函數(shù)原型如下:
void dev_kfree_skb(struct sk_buff *skb);3、當數(shù)據(jù)包發(fā)出成功,就會進入TX接收中斷函數(shù),然后更新統(tǒng)計信息,調(diào)用netif_wake_queue()來喚醒,啟動上層繼續(xù)發(fā)包下來;
4、若數(shù)據(jù)包發(fā)出去超時,一直進不到TX中斷函數(shù),就會調(diào)用net_device結(jié)構(gòu)體的*tx_timeout超時成員函數(shù),在該函數(shù)中更新統(tǒng)計信息,并調(diào)用netif_wake_queue()來喚醒。
1.4 網(wǎng)卡驅(qū)動收包過程
接收數(shù)據(jù)包主要是通過中斷函數(shù)處理,來判斷中斷類型,如果等于ISQ_RECEIVER_EVENT表示為接收中斷,然后進入接收數(shù)據(jù)函數(shù),通過netif_rx()將數(shù)據(jù)上交給上層。例如,下圖內(nèi)核中自帶的網(wǎng)卡驅(qū)動:/drivers/net/cs89x0.c

通過獲取的status標志來判斷是什么中斷,如果是接收中斷,就進入net_rx()。
-
收包函數(shù)處理步驟:
- 1、使用
dev_alloc_skb()來構(gòu)造一個新的sk_buff; - 2、使用
skb_reserve(rx_skb, 2)將sk_buff緩沖區(qū)里的數(shù)據(jù)包先向后位移2字節(jié),騰出sk_buff緩沖區(qū)里的頭部空間; - 3、讀取網(wǎng)絡(luò)設(shè)備硬件上接收到的數(shù)據(jù);
- 4、使用
memcpy()將數(shù)據(jù)復(fù)制到新的sk_buff里的data成員指向的地址處,可以使用skb_put()來動態(tài)擴大sk_buff結(jié)構(gòu)體里中的數(shù)據(jù)區(qū); - 5、使用
eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里; - 6、然后更新統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中。
- 1、使用
-
skb_put()函數(shù)
- 原型:
static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len); - 作用:將數(shù)據(jù)區(qū)向下擴大len字節(jié)
- sk_buff緩沖區(qū)變化圖:
- image-20210809232643374
- 原型:
1.5 網(wǎng)卡驅(qū)動的注冊與注銷
網(wǎng)絡(luò)設(shè)備驅(qū)動的注冊與注銷使用成對出現(xiàn)的register_netdev()和unregister_netdev()函數(shù)完成,這兩個函數(shù)的原型為:
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
這兩個函數(shù)都接收一個 net_device 結(jié)構(gòu)體指針為參數(shù), net_device 的生成和成員的賦值并非一定要由工程師逐個親自動手完成,可以利 用下面的函數(shù)幫助我們填充:
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
void(*setup)(struct net_device*));
struct net_device *alloc_etherdev(int sizeof_priv)
{
/* 以 ether_setup 為 alloc_netdev 的 setup 參數(shù) */
return alloc_netdev(sizeof_priv, "eth%d", ether_setup);
}
alloc_netdev()函數(shù)生成一個 net_device 結(jié)構(gòu)體,對其成員賦值并返回該結(jié)構(gòu)體的指針。第一個參數(shù)為設(shè)備私有成員的大小,第二個參數(shù)為設(shè)備名,第三個參數(shù)為 net_device 的 setup()函數(shù)指針。setup()函數(shù)接收的參數(shù)也為 net_device 指針,用 于預(yù)置 net_device 成員的值。
alloc_etherdev()是 alloc_netdev()針對以太網(wǎng)的“快捷”函數(shù),其中的ether_setup()是由 Linux 內(nèi)核提供的一個對以太網(wǎng)設(shè)備 net_device 結(jié)構(gòu)體中公有成員 快速賦值的函數(shù)
釋放 net_device 結(jié)構(gòu)體 的函數(shù)為:
void free_netdev(struct net_device *dev);
2、編寫虛擬網(wǎng)卡驅(qū)動
虛擬網(wǎng)卡驅(qū)動,也就是說不需要硬件相關(guān)操作,所以就沒有中斷函數(shù),我們通過linux的ping命令來實現(xiàn)發(fā)包,然后在發(fā)包函數(shù)中偽造一個收的ping包函數(shù),實現(xiàn)能ping通任何ip地址。
2.1 編寫步驟
-
init初始函數(shù)
- 1)使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體
- 2)設(shè)置net_device結(jié)構(gòu)體的成員
- 3)使用register_netdev()來注冊net_device結(jié)構(gòu)體
-
發(fā)包函數(shù)
- 1)使用netif_stop_queue()來阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包
- 2)調(diào)用收包函數(shù),并代入發(fā)送的sk_buff緩沖區(qū), 里面來偽造一個收的ping包函數(shù)
- 3)使用dev_kfree_skb()函數(shù)來釋放發(fā)送的sk_buff緩存區(qū)
- 4)更新發(fā)送的統(tǒng)計信息
- 5)使用netif_wake_queue()來喚醒被阻塞的上層
-
收包函數(shù):修改發(fā)送的sk_buff里數(shù)據(jù)包的數(shù)據(jù),使它變?yōu)橐粋€接收的sk_buff。
1)需要對調(diào)上圖的ethhdr結(jié)構(gòu)體 ”源/目的”MAC地址
2)需要對調(diào)上圖的iphdr結(jié)構(gòu)體”源/目的” IP地址
3)使用ip_fast_csum()來重新獲取iphdr結(jié)構(gòu)體的校驗碼
4)設(shè)置上圖數(shù)據(jù)包的數(shù)據(jù)類型,之前是發(fā)送ping包0x08,需要改為0x00,表示接收ping包
5)使用dev_alloc_skb()來構(gòu)造一個新的sk_buff
6)使用skb_reserve(rx_skb, 2);將sk_buff緩沖區(qū)里的數(shù)據(jù)包先后位移2字節(jié),來騰出sk_buff緩沖區(qū)里的頭部空間
7)使用memcpy()將之前修改好的sk_buff->data復(fù)制到新的sk_buff里的data成員指向的地址處:
8)設(shè)置新的sk_buff 其它成員
9)使用eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里
10)然后更新接收統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中
具體代碼
/*
* 參考 drivers\net\cs89x0.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/ip.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
static struct net_device *vnet_dev;
static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
{
/* 參考LDD3 */
unsigned char *type;
struct iphdr *ih;
__be32 *saddr, *daddr, tmp;
unsigned char tmp_dev_addr[ETH_ALEN];
struct ethhdr *ethhdr;
struct sk_buff *rx_skb;
/* 1.對調(diào)ethhdr結(jié)構(gòu)體的"源/目的"的mac地址 */
ethhdr = (struct ethhdr *)skb->data;
memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
/* 2.對調(diào)iphdr結(jié)構(gòu)體的"源/目的"的ip地址 */
ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;
tmp = *saddr;
*saddr = *daddr;
*daddr = tmp;
/* 3.使用ip_fast_csum()來重新獲取iphdr結(jié)構(gòu)體的校驗碼*/
ih->check = 0; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
/* 4.設(shè)置數(shù)據(jù)類型*/
type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
*type = 0; /*原來0x8表示發(fā)送ping包,現(xiàn)在0表示接收ping包 */
/* 5.構(gòu)造一個新的sk_buff */
rx_skb = dev_alloc_skb(skb->len + 2);
/* 6.使用skb_reserve騰出2字節(jié)頭部空間*/
skb_reserve(rx_skb, 2); /* align IP on 16B boundary */
/* 7.將之前修改好的sk_buff->data復(fù)制到新的sk_buff里 */
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); //用skb_put()擴大sk_buff的數(shù)據(jù)區(qū),避免溢出
/* 8.設(shè)置新sk_buff的其它成員*/
rx_skb->dev = dev;
rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
/* 9.使用eth_type_trans()來獲取上層協(xié)議 */
rx_skb->protocol = eth_type_trans(rx_skb, dev);
/* 10.更新接收統(tǒng)計信息,并向上層傳遞sk_fuffer收包 */
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
dev->last_rx = jiffies; //收包時間戳
// 提交sk_buff
netif_rx(rx_skb);
}
static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
static int cnt = 0;
printk("virt_net_send_packet cnt = %d\n", ++cnt);
/* 1.停止該網(wǎng)卡的隊列,阻止上層向驅(qū)動層繼續(xù)發(fā)送數(shù)據(jù)包 */
netif_stop_queue(dev);
/* 2.真實驅(qū)動要把skb的數(shù)據(jù)寫入網(wǎng)卡 ,但在此先通過emulator_rx_packet模擬 */
emulator_rx_packet(skb, dev); /* 構(gòu)造一個假的sk_buff,上報 */
/* 3.釋放發(fā)送的sk_buff緩存區(qū)*/
dev_kfree_skb (skb);
/* 4.更新統(tǒng)計信息 */
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
dev->trans_start = jiffies; //發(fā)送時間戳
/* 5.數(shù)據(jù)全部發(fā)送出去后,喚醒網(wǎng)卡的隊列 (真實網(wǎng)卡應(yīng)在中斷函數(shù)里喚醒)*/
netif_wake_queue(dev);
return 0;
}
static const struct net_device_ops vnetdev_ops = {
.ndo_start_xmit = virt_net_send_packet,
};
static int virt_net_init(void)
{
/* 1. 分配一個net_device結(jié)構(gòu)體 */
vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);; /* alloc_ether_dev */
/* 2. 設(shè)置 */
//vnet_dev->hard_start_xmit = virt_net_send_packet;
vnet_dev->netdev_ops = &vnetdev_ops;
/* 設(shè)置MAC地址 */
vnet_dev->dev_addr[0] = 0x08;
vnet_dev->dev_addr[1] = 0x89;
vnet_dev->dev_addr[2] = 0x66;
vnet_dev->dev_addr[3] = 0x77;
vnet_dev->dev_addr[4] = 0x88;
vnet_dev->dev_addr[5] = 0x99;
/* 設(shè)置下面兩項才能ping通 */
vnet_dev->flags |= IFF_NOARP;
vnet_dev->features |= NETIF_F_IP_CSUM;
/* 3. 注冊 */
//register_netdevice(vnet_dev); //編譯會出錯!
register_netdev(vnet_dev);
return 0;
}
static void virt_net_exit(void)
{
unregister_netdev(vnet_dev);
free_netdev(vnet_dev);
}
module_init(virt_net_init);
module_exit(virt_net_exit);
MODULE_AUTHOR("liangzc1124@163.com");
MODULE_LICENSE("GPL");
2.2 測試
- 掛載驅(qū)動,可以看到net類下就有了這個網(wǎng)卡設(shè)備,并嘗試ping自己
- image-20210809233459142
上圖的ping之所以成功,并是因為我們在發(fā)包函數(shù)中偽造了一個來收包,而是因為linux中,ping自己的時候并不調(diào)用(不需要)發(fā)送函數(shù)向網(wǎng)絡(luò)設(shè)備發(fā)送sk_buff數(shù)據(jù)包。
- ping同網(wǎng)段其它地址
- image-20210809233646706
ping其它ip地址之所以成功,是因為我們在發(fā)包函數(shù)中利用emulator_rx_packet函數(shù)偽造了一個接收數(shù)據(jù)包,并通過netif_rx()來將收包上傳給上層。
3、移植內(nèi)核自帶的網(wǎng)卡驅(qū)動程序
在移植之前,首先我們來看一下mini2440(對應(yīng)的機器ID為:set machid 7CF)中,是如何支持dm9000網(wǎng)卡的。
進入到入口函數(shù),找到結(jié)構(gòu)體:
static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
},
.probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
};
一般是通過.name這個成員進行匹配的,搜索字符串“dm9000”,找到如下結(jié)構(gòu)體(在平臺文件中:arch\arm\mach-s3c24xx\Mach-mini2440.c):
static struct platform_device mini2440_device_eth = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(mini2440_dm9k_resource),
.resource = mini2440_dm9k_resource,
.dev = {
.platform_data = &mini2440_dm9k_pdata,
},
};
然后搜索結(jié)構(gòu)體mini2440_device_eth,找到:
static struct platform_device *mini2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_rtc,
&s3c_device_usbgadget,
&mini2440_device_eth, //在這里
&mini2440_led1,
&mini2440_led2,
&mini2440_led3,
&mini2440_led4,
&mini2440_button_device,
&s3c_device_nand,
&s3c_device_sdi,
&s3c_device_iis,
&uda1340_codec,
&mini2440_audio,
&samsung_asoc_dma,
};
然后再搜索:mini2440_devices,找到:
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
這就是把結(jié)構(gòu)體mini2440_devices添加到內(nèi)核,里面的關(guān)于網(wǎng)卡的結(jié)構(gòu)在里面,最終匹配驅(qū)動程序,就可以使用驅(qū)動程序了。
(這就是所謂的平臺設(shè)備平臺驅(qū)動的東西了,把可變的東西抽象出來放到平臺相關(guān)的文件中定義,而我們的驅(qū)動程序,基本上是不需要改變的,它是穩(wěn)定的內(nèi)容,我們移植的時候,只需要把平臺層可變的相關(guān)結(jié)構(gòu)體加上,需要修改的資源,進行修改就可以了)。
而我們用的是smdk2440(對應(yīng)的機器ID為:set machid 16a),然后我在Mach-smdk2440.c中添加以下函數(shù):
/* 以下為liangzc1124@163.com 添加
* The DM9000 has no eeprom, and it's MAC address is set by
* the bootloader before starting the kernel.
*/
/* DM9000AEP 10/100 ethernet controller */
#define MACH_SMDK2440_DM9K_BASE (S3C2410_CS4 + 0x300)
static struct resource smdk2440_dm9k_resource[] = {
[0] = {
.start = MACH_SMDK2440_DM9K_BASE,
.end = MACH_SMDK2440_DM9K_BASE + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = MACH_SMDK2440_DM9K_BASE + 4,
.end = MACH_SMDK2440_DM9K_BASE + 7,
.flags = IORESOURCE_MEM
},
[2] = {
.start = IRQ_EINT7,
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
}
};
static struct dm9000_plat_data smdk2440_dm9k_pdata = {
.flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};
static struct platform_device smdk2440_device_eth = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(smdk2440_dm9k_resource),
.resource = smdk2440_dm9k_resource,
.dev = {
.platform_data = &smdk2440_dm9k_pdata,
},
};
/* 以下為liangzc1124@163.com 添加 */
在結(jié)構(gòu)體smdk2440_devices中添加網(wǎng)卡成員:
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&smdk2440_device_eth, /* lyy:添加 */
};
添加頭文件:
#include <linux/dm9000.h> /* 以下為liangzc1124@163.com 添加*/
然后重新編譯內(nèi)核。成功。燒寫新內(nèi)核:
S3C2440A # nfs 30000000 192.168.1.101:/home/leon/nfs_root/first_fs/uImage;
S3C2440A # bootm 30000000
然后掛載網(wǎng)絡(luò)文件系統(tǒng):
mount -t nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt
成功掛載網(wǎng)絡(luò)文件系統(tǒng)。
4、自己編寫網(wǎng)卡驅(qū)動程序
有時候,內(nèi)核自帶的網(wǎng)卡驅(qū)動程序比較老,而我們的硬件有可能比較新,那么我們就不能使用內(nèi)核的網(wǎng)卡驅(qū)動程序了,就需要去移植最新的網(wǎng)卡驅(qū)動程序,那么這種類型的,又該如何移植呢?
4.1 網(wǎng)絡(luò)設(shè)備驅(qū)動程序的模塊加載和卸載函數(shù)
int xxx_init_module(void)
{
...
/* 分配 net_device 結(jié)構(gòu)體并對其成員賦值 */
xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d", xxx_init);
if (xxx_dev == NULL)
... /* 分配 net_device 失敗 */
/* 注冊 net_device 結(jié)構(gòu)體 */
if ((result = register_netdev(xxx_dev)))
...
}
void xxx_cleanup(void)
{
...
/* 注銷 net_device 結(jié)構(gòu)體 */
unregister_netdev(xxx_dev);
/* 釋放 net_device 結(jié)構(gòu)體 */
free_netdev(xxx_dev);
}
4.2 網(wǎng)絡(luò)設(shè)備的初始化
網(wǎng)絡(luò)設(shè)備的初始化主要需要完成如下幾個方面的工作:
- 進行硬件上的準備工作,檢查網(wǎng)絡(luò)設(shè)備是否存在,如果存在,則檢測設(shè)備使用的硬件資源;
- 進行軟件接口上的準備工作,分配 net_device 結(jié)構(gòu)體并對其數(shù)據(jù)和函數(shù)指針 成員賦值;
- 獲得設(shè)備的私有信息指針并初始化其各成員的值。如果私有信息中包括自旋 鎖或信號量等并發(fā)或同步機制,則需對其進行初始化。
個網(wǎng)絡(luò)設(shè)備驅(qū)動初始化函數(shù)的模板如下所示:
void xxx_init(struct net_device *dev)
{
/*設(shè)備的私有信息結(jié)構(gòu)體*/
struct xxx_priv *priv;
/* 檢查設(shè)備是否存在、具體硬件配置和設(shè)置設(shè)備所使用的硬件資源 */
xxx_hw_init();
/* 初始化以太網(wǎng)設(shè)備的公用成員 */
ether_setup(dev);
/*設(shè)置設(shè)備的成員函數(shù)指針*/
dev->open = xxx_open;
dev->stop = xxx_release;
dev->set_config = xxx_config;
dev->hard_start_xmit = xxx_tx;
dev->do_ioctl = xxx_ioctl;
dev->get_stats = xxx_stats;
dev->change_mtu = xxx_change_mtu;
dev->rebuild_header = xxx_rebuild_header;
dev->hard_header = xxx_header;
dev->tx_timeout = xxx_tx_timeout;
dev->watchdog_timeo = timeout;
/*如果使用 NAPI,設(shè)置 pool 函數(shù)*/
if (use_napi)
{
dev->poll = xxx_poll;
}
/* 取得私有信息,并初始化它*/
priv = netdev_priv(dev);
... /* 初始化設(shè)備私有數(shù)據(jù)區(qū) */
}
4.3 網(wǎng)絡(luò)設(shè)備的打開與釋放
網(wǎng)絡(luò)設(shè)備的打開函數(shù)需要完成如下工作。
1. 使能設(shè)備使用的硬件資源,申請 I/O 區(qū)域、中斷和 DMA 通道等。
2. 調(diào)用 Linux 內(nèi)核提供的 netif_start_queue()函數(shù),激活設(shè)備發(fā)送隊列。 網(wǎng)絡(luò)設(shè)備的關(guān)閉函數(shù)需要完成如下工作。
3. 調(diào)用 Linux 內(nèi)核提供的 netif_stop_queue()函數(shù),停止設(shè)備傳輸包。
4. 釋放設(shè)備所使用的 I/O 區(qū)域、中斷和 DMA 資源。
Linux 內(nèi)核提供的 netif_start_queue()和 netif_stop_queue()兩個函數(shù)的原型為:
void netif_start_queue(struct net_device *dev);
void netif_stop_queue (struct net_device *dev);
根據(jù)以上分析,可得出網(wǎng)絡(luò)設(shè)備打開和釋放函數(shù)的模板:
int xxx_open(struct net_device *dev)
{
/* 申請端口、IRQ 等,類似于 fops->open */
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
...
netif_start_queue(dev);
...
}
int xxx_release(struct net_device *dev)
{
/* 釋放端口、IRQ 等,類似于 fops->close */
free_irq(dev->irq, dev);
...
netif_stop_queue(dev); /* can't transmit any more */
...
}
4.3 數(shù)據(jù)發(fā)送流程
(1)網(wǎng)絡(luò)設(shè)備驅(qū)動程序從上層協(xié)議傳遞過來的 sk_buff 參數(shù)獲得數(shù)據(jù)包的有效數(shù) 據(jù)和長度,將有效數(shù)據(jù)放入臨時緩沖區(qū)。
(2)對于以太網(wǎng),如果有效數(shù)據(jù)的長度小于以太網(wǎng)沖突檢測所要求數(shù)據(jù)幀的最小 長度 ETH_ZLEN,則給臨時緩沖區(qū)的末尾填充 0。
(3)設(shè)置硬件的寄存器,驅(qū)使網(wǎng)絡(luò)設(shè)備進行數(shù)據(jù)發(fā)送操作。
int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
/* 獲得有效數(shù)據(jù)指針和長度 */
data = skb->data;
len = skb->len;
if (len < ETH_ZLEN)
{
/* 如果幀長小于以太網(wǎng)幀最小長度,補 0 */
memset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies; /* 記錄發(fā)送時間戳 */
/* 設(shè)置硬件寄存器讓硬件把數(shù)據(jù)包發(fā)送出去 */
xxx_hw_tx(data, len, dev);
...
}
當數(shù)據(jù)傳輸超時時,意味著當前的發(fā)送操作失敗,此時,數(shù)據(jù)包發(fā)送超時處理函 數(shù) xxx_tx_ timeout()將被調(diào)用。這個函數(shù)需要調(diào)用 Linux 內(nèi)核提供的 netif_wake_queue()函數(shù)重新啟動設(shè)備發(fā)送隊列:
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev); /* 重新啟動設(shè)備發(fā)送隊列 */
}
4.3 數(shù)據(jù)接收流程
網(wǎng)絡(luò)設(shè)備接收數(shù)據(jù)的主要方法是由中斷引發(fā)設(shè)備的中斷處理函數(shù),中斷處理函數(shù) 判斷中斷類型,如果為接收中斷,則讀取接收到的數(shù)據(jù),分配 sk_buffer 數(shù)據(jù)結(jié)構(gòu)和數(shù) 據(jù)緩沖區(qū),將接收到的數(shù)據(jù)復(fù)制到數(shù)據(jù)緩沖區(qū),并調(diào)用 netif_rx()函數(shù)將 sk_buffer 傳 遞給上層協(xié)議。完成這一過程的函數(shù)模板:
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
...
switch (status &ISQ_EVENT_MASK)
{
case ISQ_RECEIVER_EVENT: /* 獲取數(shù)據(jù)包 */
xxx_rx(dev);
break;
/* 其他類型的中斷 */
}
}
static void xxx_rx(struct xxx_device *dev)
{
...
length = get_rev_len (...);
/* 分配新的套接字緩沖區(qū) */
skb = dev_alloc_skb(length + 2);
skb_reserve(skb, 2); /* 對齊 */
skb->dev = dev;
/* 讀取硬件上接收到的數(shù)據(jù) */
insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
if (length &1)
skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
/* 獲取上層協(xié)議類型 */
skb->protocol = eth_type_trans(skb, dev);
/* 把數(shù)據(jù)包交給上層 */
netif_rx(skb);
/* 記錄接收時間戳 */
dev->last_rx = jiffies;
...
}
如果是 NAPI 兼容的設(shè)備驅(qū)動,則可以通過 poll 方式接收數(shù)據(jù)包。這種情況下, 我們需要為該設(shè)備驅(qū)動提供 xxx_poll()函數(shù):
static int xxx_poll(struct net_device *dev, int *budget)
{
//dev->quota 是當前 CPU 能夠從所有接口中接收數(shù)據(jù)包的最大數(shù)目,budget 是在初始化階段分配給接口的 weight 值
int npackets = 0, quota = min(dev->quota, *budget);
struct sk_buff *skb;
struct xxx_priv *priv = netdev_priv(dev);
struct xxx_packet *pkt;
while (npackets < quota && priv->rx_queue)
{
/*從隊列中取出數(shù)據(jù)包*/
pkt = xxx_dequeue_buf(dev);
/*接下來的處理,和中斷觸發(fā)的數(shù)據(jù)包接收一致*/
skb = dev_alloc_skb(pkt->datalen + 2);
if (!skb)
{
...
continue;
}
skb_reserve(skb, 2);
memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
/*調(diào)用 netif_receive_skb 而不是 net_rx 將數(shù)據(jù)包交給上層協(xié)議
這里體現(xiàn)出了中斷處理機制和輪詢機制之間的差別。
*/
netif_receive_skb(skb);
/*更改統(tǒng)計數(shù)據(jù) */
priv->stats.rx_packets++;
priv->stats.rx_bytes += pkt->datalen;
xxx_release_buffer(pkt);
}
/* 網(wǎng)絡(luò)設(shè)備接收緩沖區(qū)中的數(shù)據(jù)包都被讀取完了*/
*budget -= npackets;
dev->quota -= npackets;
if (!priv->rx_queue)
{
netif_rx_complete(dev); //把當前指定的設(shè)備從 poll 隊列中清除
xxx_enable_rx_int (…); /* 再次使能網(wǎng)絡(luò)設(shè)備的接收中斷 */
return 0;
}
return 1;
}
雖然 NAPI 兼容的設(shè)備驅(qū)動以 poll 方式接收數(shù)據(jù)包,但是仍然需要首次數(shù)據(jù)包接 收中斷來觸發(fā) poll 過程。與數(shù)據(jù)包的中斷接收方式不同的是,以輪詢方式接收數(shù)據(jù)包 時,當?shù)谝淮沃袛喟l(fā)生后,中斷處理程序要禁止設(shè)備的數(shù)據(jù)包接收中斷。poll 中斷處理函數(shù)如下:
static void xxx_poll_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
switch (status &ISQ_EVENT_MASK)
{
case ISQ_RECEIVER_EVENT:
.../* 獲取數(shù)據(jù)包 */
xxx_disable_rx_int(...); /* 禁止接收中斷 */
netif_rx_schedule(dev);
break;
.../* 其他類型的中斷 */
}
}
上述代碼的 netif_rx_schedule()函數(shù)被輪詢方式驅(qū)動的中斷程序調(diào)用,將設(shè) 備的 poll 方法添加到網(wǎng)絡(luò)層的 poll 處理隊列中,排隊并且準備接收數(shù)據(jù)包,最終觸發(fā) 一個 NET_RX_SOFTIRQ 軟中斷,通知網(wǎng)絡(luò)層接收數(shù)據(jù)包。下圖為 NAPI 驅(qū)動 程序各部分的調(diào)用關(guān)系:

4.4 網(wǎng)絡(luò)連接狀態(tài)
網(wǎng)絡(luò)設(shè)備驅(qū)動程序中往往設(shè)置一個定時器來對鏈路狀態(tài)進行周期性地檢查。當定 時器到期之后,在定時器處理函數(shù)中讀取物理設(shè)備的相關(guān)寄存器獲得載波狀態(tài),從而 更新設(shè)備的連接狀態(tài)。
網(wǎng)絡(luò)設(shè)備驅(qū)動可以通過 netif_carrier_on()和netif_carrier_off()函數(shù)改變或通知內(nèi)核網(wǎng)絡(luò)設(shè)備的連接狀態(tài)。此外,函數(shù) netif_carrier_ok() 可用于向調(diào)用者返回鏈路上的載波信號是否存在。
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
int netif_carrier_ok(struct net_device *dev);
以下代碼顯示了網(wǎng)絡(luò)設(shè)備驅(qū)動用定時器周期檢查鏈路狀態(tài):
static void xxx_timer(unsigned long data)
{
struct net_device *dev = (struct net_device*)data;
u16 link;
...
if (!(dev->flags &IFF_UP))
{
goto set_timer;
}
/* 獲得物理上的連接狀態(tài) */
if (link = xxx_chk_link(dev)) //讀取網(wǎng)絡(luò)適配器硬件的相關(guān)寄存器以獲得鏈路連接狀態(tài)
{
if (!(dev->flags &IFF_RUNNING))
{
netif_carrier_on(dev);
dev->flags |= IFF_RUNNING;
printk(KERN_DEBUG "%s: link up\n", dev->name);
}
}
else
{
if (dev->flags &IFF_RUNNING)
{
netif_carrier_off(dev);
dev->flags &= ~IFF_RUNNING;
printk(KERN_DEBUG "%s: link down\n", dev->name);
}
}
set_timer:
priv->timer.expires = jiffies + 1 * HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; /* timer handler */
add_timer(&priv->timer);
}
從上述源代碼還可以看出,定時器處理函數(shù)會不停地利用第 31~35 行代 碼啟動新的定時器以實現(xiàn)周期檢測的目的。那么最初啟動定時器的地方在哪里呢?很 顯然,它最適合在設(shè)備的打開函數(shù)中完成:
static int xxx_open(struct net_device *dev)
{
struct xxx_priv *priv = (struct xxx_priv*)dev->priv;
...
priv->timer.expires = jiffies + 3 * HZ;
priv->timer.data = (unsigned long)dev;
priv->timer.function = &xxx_timer; /* timer handler */
add_timer(&priv->timer);
...
}
5. CS8900 網(wǎng)卡設(shè)備驅(qū)動實例分析
當 CS8900 處于 I/O 模式下時(這里所說的 CS8900 處于 I/O 模式并非意味著它一定位于 CPU 的 I/O 空間,實 際上,CS8900 I/O 模式下的寄存器仍然映射 ARM 處理器的內(nèi)存空間。因此, 我們直接通過讀/寫寄存器地址ioremap()之后的虛擬地址即可),可以通過以 下幾個 PacketPage 空間內(nèi)的寄存器來控制 CS8900 的行為(括號內(nèi)給出的是寄存器地 址相對于 PacketPage 基地址的偏移):
| 寄存器 | 作用 |
|---|---|
| LINECTL(0112H) | 決定 CS8900 的基本配置和物理接口,可選擇使用 10BASE-T 接口、AUI 接口或者自動選擇。 |
| RXCTL(0104H) | 控制 CS8900 接收特定數(shù)據(jù)包,控制是否接收多播、廣播和單播包。 |
| RXCFG(0102H) | RXCFG 控制 CS8900 接收到特定數(shù)據(jù)包后引發(fā)接收中斷,并控制是否使用接收 DMA 和 CRC 校驗。 |
| BUSCT(0116H) | BUSCT 可控制芯片的工作模式、DMA 方式、是否使能外部中斷引腳。 |
| BUSST(0138H) | 標志網(wǎng)絡(luò)設(shè)備的發(fā)送狀態(tài),如設(shè)備是否準備好發(fā)送。 |
| ISQ(0120H) | 網(wǎng)卡芯片的中斷狀態(tài)寄存器 |
在 I/O 模式下,CS8900 發(fā)送數(shù)據(jù)包的步驟如下:
(1)向控制寄存器 TXCMD 寄存器寫入發(fā)送命令write_reg(TXCMD, send_cmd);。
(2)將發(fā)送數(shù)據(jù)長度寫入 TXLENG 寄存器write_reg(TXLENG, send_len)。
(3)讀取 PacketPage 空間內(nèi)的 BUSST 寄存器,確定其第 8 位被設(shè)置為 Rdy4TxNOW,即設(shè)備處于準備發(fā)送狀態(tài)reg(BusST)&0x100。
(4)將要發(fā)送的數(shù)據(jù)循環(huán)寫入 PORT0 寄存器write_reg(PORT0, data)。
(5)將數(shù)據(jù)組織為以太網(wǎng)幀并添加填充位和 CRC 校驗信息,然后將數(shù)據(jù)轉(zhuǎn)化為比特流傳送到網(wǎng)絡(luò)媒介。
在 I/O 模式下,CS8900 接收數(shù)據(jù)包的方法如下:
(1)接收到網(wǎng)絡(luò)適配器產(chǎn)生的中斷,查詢相對于 I/O 基地址偏移 0008H 中斷狀態(tài) 隊列端口,判斷中斷類型為接收中斷。
(2)讀 PORT0 寄存器依次獲得接收狀態(tài) rxStatus、接收數(shù)據(jù)長度 rxLength。
(3)循環(huán)繼續(xù)對 PORT0 寄存器讀取 rxLength 次,獲得整個數(shù)據(jù)包。
(4)驅(qū)動程序進行數(shù)據(jù)包處理并傳遞給上層。
對于一種網(wǎng)絡(luò)設(shè)備的驅(qū)動而言,工程師主要需完成設(shè)備驅(qū)動功能層的設(shè)計。在 16.2~ 16.8節(jié)已經(jīng)給出了設(shè)備驅(qū)動功能層主要數(shù)據(jù)結(jié)構(gòu)和函數(shù)的設(shè)計模板,因此,在編寫CS8900 的這些數(shù)據(jù)結(jié)構(gòu)和函數(shù)時,實際要完成的工作就是用具體的針對 CS8900 的操作來填充模 板,具體包括以下工作:
-
填充 CS8900 的私有信息結(jié)構(gòu)體,把 CS8900 特定的數(shù)據(jù)放入這個私有結(jié)構(gòu)體中。在 CS8900 驅(qū)動程序中,這個數(shù)據(jù)結(jié)構(gòu)為
struct net_local。在 CS8900 的設(shè)備驅(qū)動程序中,核心數(shù)據(jù)結(jié)構(gòu) net_device 以全局變量的方式定義, 其數(shù)個成員的初始值都被置為空,私有信息結(jié)構(gòu)體為 net_local:
static struct net_device dev_cs89x0 = { "", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL }; struct net_local { struct net_device_stats stats; /* 網(wǎng)絡(luò)設(shè)備狀態(tài)結(jié)構(gòu)體 */ int chip_type; /* 區(qū)分芯片類型:CS89x0 */ char chip_revision; /* 芯片版本字母,如"A" */ int send_cmd; /* 發(fā)送命令: TX_NOW, TX_AFTER_381 或 TX_AFTER_ALL */ ... spinlock_t lock; /* 并發(fā)控制自旋鎖 */ };當芯片的版本字母不同時,net_local 結(jié)構(gòu)體中記錄的 send_cmd 命令將不同。例如, 同樣是 CS8900 網(wǎng)卡,若芯片版本字母為大于等于“F”,則發(fā)送命令為 TX_NOW,而 對于 CS8900A,發(fā)送命令為 TX_AFTER_ALL。
-
填充設(shè)備初始化模板,初始化 net_device 結(jié)構(gòu)體,將其注冊入內(nèi)核。net_device 的注冊與注銷在模塊加載與注銷函數(shù)中完成。在 CS8900 驅(qū)動程序中,與此相關(guān)的函數(shù)有:
struct net_device * _ _init cs89x0_probe(int unit); int cs89x0_probe1(struct net_device *dev, int ioaddr, int modular); int init_module(void); void cleanup_module(void);設(shè)備的初始化由 net_device 結(jié)構(gòu)體中的 init()函數(shù)完成,這個函數(shù)將在 net_device 被注冊時自動被調(diào)用。init()函數(shù)在 CS8900 網(wǎng)卡的驅(qū)動程序中對應(yīng) 于 cs89x0_probe()函數(shù):
int __init cs89x0_probe(struct net_device *dev) { int i; SET_MODULE_OWNER(dev); DPRINTK(1, "cs89x0:cs89x0_probe(0x%x)\n", base_addr); BWSCON = (BWSCON & ~(BWSCON_ST3 | BWSCON_WS3 | BWSCON_DW3)) | (BWSCON_ST3 | BWSCON_WS3 | BWSCON_DW(3, BWSCON_DW_16)); BANKCON3= BANKCON_Tacs0 | BANKCON_Tcos4 | BANKCON_Tacc14 | BANKCON_Toch1 | BANKCON_Tcah4 | BANKCON_Tacp6 | BANKCON_PMC1; set_external_irq(IRQ_CS8900, EXT_RISING_EDGE, GPIO_PULLUP_DIS); for (i = 0; netcard_portlist[i]; i++) { if (cs89x0_probe1(dev, netcard_portlist[i]) == 0) //驗證網(wǎng)卡的存在,并獲取 CS8900所使用的硬件資源 return 0; } printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected." "Be sure to disable PnP with SETUP\n"); return -ENODEV; } static unsigned int netcard_portlist[] __initdata = { vCS8900_BASE + 0x300, //假設(shè)硬件平臺中網(wǎng)卡的基地址為 vCS8900_BASE + 0x300 0 }; /* *上述 cs89x0_probe1()函數(shù)的流程如下。 *(1)第 8~20 行分配設(shè)備的私有信息結(jié)構(gòu)體內(nèi)存并初始化,若分配失敗,則直接跳入第 78 行的代碼返回。 *(2)第 24~26 行從寄存器中讀取芯片的具體類型。 *(3)第 27~32 行判斷芯片類型,若不是 CS8900 則直接跳入第 77 行的代碼,釋放私有信息結(jié)構(gòu)體并返回。 *(4)當芯片類型為 CS8900 時,第 34~69 行完成 net_device 設(shè)備結(jié)構(gòu)體的初始化,賦值其屬性和函數(shù)指針。 */ static int __init cs89x0_probe1(struct net_device *dev, int ioaddr) { struct net_local *lp; unsigned rev_type = 0; int ret; /* 初始化設(shè)備結(jié)構(gòu)體私有信息 */ if (dev->priv == NULL) { dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); if (dev->priv == 0) { ret = - ENOMEM; goto before_kmalloc; } lp = (struct net_local*)dev->priv; memset(lp, 0, sizeof(*lp)); spin_lock_init(&lp->lock); } lp = (struct net_local*)dev->priv; dev->base_addr = ioaddr; /* 讀取芯片類型 */ rev_type = readreg(dev, PRODUCT_ID_ADD); lp->chip_type = rev_type &~REVISON_BITS; lp->chip_revision = ((rev_type &REVISON_BITS) >> 8) + 'A';30 if (lp->chip_type != CS8900) { printk(_ _FILE_ _ ": wrong device driver!\n"); ret = - ENODEV; goto after_kmalloc; } /* 根據(jù)芯片類型和版本確定正確的發(fā)送命令 */ lp->send_cmd = TX_AFTER_ALL; if (lp->chip_type == CS8900 && lp->chip_revision >= 'F') lp->send_cmd = TX_NOW; reset_chip(dev); lp->adapter_cnf = A_CNF_10B_T | A_CNF_MEDIA_10B_T; lp->auto_neg_cnf = EE_AUTO_NEG_ENABLE; printk(KERN_INFO "cs89x0 media %s%s", (lp->adapter_cnf &A_CNF_10B_T) ? "RJ-45": "", (lp->adapter_cnf &A_CNF_AUI) ? "AUI" : ""); /* 設(shè)置 CS8900 的 MAC 地址 */ dev->dev_addr[0] = 0x00; dev->dev_addr[1] = 0x00; dev->dev_addr[2] = 0xc0; dev->dev_addr[3] = 0xff; dev->dev_addr[4] = 0xee; dev->dev_addr[5] = 0x08; set_mac_address(dev, dev->dev_addr); /* 設(shè)置設(shè)備中斷號 */ dev->irq = IRQ_LAN; printk(", IRQ %d", dev->irq); /* 填充設(shè)備結(jié)構(gòu)體的成員函數(shù)指針 */ dev->open = net_open; dev->stop = net_close; dev->tx_timeout = net_timeout; dev->watchdog_timeo = 3 * HZ; dev->hard_start_xmit = net_send_packet; dev->get_stats = net_get_stats; dev->set_multicast_list = set_multicast_list; dev->set_mac_address = set_mac_address; /* 填充以太網(wǎng)公用數(shù)據(jù)和函數(shù)指針 */ ether_setup(dev); printk("\n"); DPRINTK(1, "cs89x0_probe1() successful\n"); return 0; after_kmalloc: kfree(dev->priv); before_kmalloc: return ret; } static int __init init_cs8900a_s3c2410(void) { struct net_local *lp; int ret = 0; dev_cs89x0.irq = irq; dev_cs89x0.base_addr = io; dev_cs89x0.init = cs89x0_probe; //在使用 register_netdev()函數(shù)注net_device 設(shè)備結(jié)構(gòu)體時,cs89x0_probe()函數(shù)會被自動調(diào)用以完成 net_device 結(jié)構(gòu)體的初始化。 dev_cs89x0.priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); if (dev_cs89x0.priv == 0) { printk(KERN_ERR "cs89x0.c: Out of memory.\n"); return - ENOMEM; } memset(dev_cs89x0.priv, 0, sizeof(struct net_local)); lp = (struct net_local*)dev_cs89x0.priv; //為 CS8900 網(wǎng)卡申請了 NETCARD_IO_EXTENT大小的I/O 地址區(qū)域 request_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT, "cs8900a"); spin_lock_init(&lp->lock); /* 設(shè)置物理接口的正確類型*/ if (!strcmp(media, "rj45")) lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; else if (!strcmp(media, "aui")) lp->adapter_cnf = A_CNF_MEDIA_AUI | A_CNF_AUI; else if (!strcmp(media, "bnc")) lp->adapter_cnf = A_CNF_MEDIA_10B_2 | A_CNF_10B_2; else lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; if (duplex == - 1) lp->auto_neg_cnf = AUTO_NEG_ENABLE; if (io == 0) { printk(KERN_ERR "cs89x0.c: Module autoprobing not allowed.\n"); printk(KERN_ERR "cs89x0.c: Append io=0xNNN\n"); ret = - EPERM; goto out; } //net_device 設(shè)備結(jié)構(gòu)體的注冊 if (register_netdev(&dev_cs89x0) != 0) { printk(KERN_ERR "cs89x0.c: No card found at 0x%x\n", io); ret = - ENXIO; goto out; } out: if (ret) kfree(dev_cs89x0.priv); return ret; } static void _ _exit cleanup_cs8900a_s3c2410(void) { if (dev_cs89x0.priv != NULL) { /* 釋放私有信息結(jié)構(gòu)體 */ unregister_netdev(&dev_cs89x0); outw(PP_ChipID, dev_cs89x0.base_addr + ADD_PORT); kfree(dev_cs89x0.priv); dev_cs89x0.priv = NULL; /* 釋放 CS8900 申請的 I/O 地址區(qū)域 */ release_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT); } }上述函數(shù)第 8~11 行設(shè)置 S3C2410A ARM 處理器的片選,第 13行設(shè)置 ARM 與 CS8900 網(wǎng)卡對應(yīng)的中斷,第 15~18 行循環(huán)檢索 netcard_portlist[ ]數(shù)組中定義的基地址處 是否存在 CS8900 網(wǎng)卡
-
填充設(shè)備發(fā)送數(shù)據(jù)包函數(shù)模板,把真實的數(shù)據(jù)包發(fā)送硬件操作填充入
xxx_tx()函數(shù),并填充發(fā)送超時函數(shù)xxx_tx_timeout()。當發(fā)送數(shù)據(jù)包超時時,CS8900 驅(qū)動程序的數(shù)據(jù)包發(fā)送超時函數(shù)將被調(diào)用,它重 新啟動設(shè)備發(fā)送隊列。在初始化函數(shù)中,CS8900 的數(shù)據(jù)包發(fā)送函數(shù)指針hard_ start_xmit被賦值為 CS8900 驅(qū)動程序中的net_send_packet(),這個函數(shù)完成硬件發(fā)送序列。具體代碼如下:static int net_send_packet(struct sk_buff *skb, struct net_device *dev) { struct net_local *lp = (struct net_local*)dev->priv; writereg(dev, PP_BusCTL, 0x0); writereg(dev, PP_BusCTL, readreg(dev, PP_BusCTL) | ENABLE_IRQ); spin_lock_irq(&lp->lock);/* 使用自旋鎖阻止多個數(shù)據(jù)包被同時寫入硬件*/ netif_stop_queue(dev); /* 初始化硬件發(fā)送序列 */ writeword(dev, TX_CMD_PORT, lp->send_cmd); writeword(dev, TX_LEN_PORT, skb->len); /* 檢測硬件是否處于發(fā)送 READY 狀態(tài) */ if ((readreg(dev, PP_BusST) &READY_FOR_TX_NOW) == 0) { spin_unlock_irq(&lp->lock); DPRINTK(1, "cs89x0: Tx buffer not free!\n"); return 1; } writeblock(dev, skb->data, skb->len); /* 將數(shù)據(jù)寫入硬件 */ spin_unlock_irq(&lp->lock); /* 解鎖自旋鎖 */ dev->trans_start = jiffies; /* 記錄發(fā)送開始的時間戳 */ dev_kfree_skb(skb); /* 釋放 sk_buff 和數(shù)據(jù)緩沖區(qū) */ return 0; } static void net_timeout(struct net_device *dev) { DPRINTK(1, "%s: transmit timed out, %s?\n", dev->name, tx_done(dev) ? "IRQ conflict ?" : "network cable problem"); net_close(dev); //停止網(wǎng)卡 writereg(dev, PP_SelfCTL, readreg(dev, PP_SelfCTL) | POWER_ON_RESET); //網(wǎng)卡硬復(fù)位 net_open(dev); //再次啟動網(wǎng)卡 }
-
填充設(shè)備驅(qū)動程序的中斷處理程序 xxx_interrupt()和具體的數(shù)據(jù)包接收函數(shù) xxx_rx(),填入真實的硬件操作。在 CS8900 驅(qū)動程序中,與此相關(guān)的函數(shù)有:
irqreturn_t net_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = dev_id; struct net_local *lp; int ioaddr, status; ioaddr = dev->base_addr; lp = (struct net_local*)dev->priv; /* 讀取中斷事件類型 */ while ((status = readword(dev, ISQ_PORT))) { DPRINTK(4, "%s: event=%04x\n", dev->name, status); switch (status &ISQ_EVENT_MASK) { case ISQ_RECEIVER_EVENT: /* 獲得數(shù)據(jù)包 */ net_rx(dev); break; ... /* 其他類型中斷 */ } } } static void net_rx(struct net_device *dev) { struct net_local *lp = (struct net_local*)dev->priv; struct sk_buff *skb; int status, length; int ioaddr = dev->base_addr; status = inw(ioaddr + RX_FRAME_PORT); if ((status &RX_OK) == 0) { count_rx_errors(status, lp); return ; } length = inw(ioaddr + RX_FRAME_PORT);/* 讀取接收數(shù)據(jù)包的長度 */ /* 分配新的套接字緩沖區(qū)和數(shù)據(jù)緩沖區(qū) */ skb = dev_alloc_skb(length + 2); if (skb == NULL) { lp->stats.rx_dropped++; /* 分配失敗,統(tǒng)計被丟棄的包數(shù) */ return ; } skb_reserve(skb, 2); skb->len = length; skb->dev = dev; readblock(dev, skb->data, skb->len); /* 從硬件中讀取數(shù)據(jù)包放入數(shù)據(jù)緩沖區(qū) */ skb->protocol = eth_type_trans(skb, dev);/* 解析收到數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類型 */ netif_rx(skb); /* 傳遞給上層協(xié)議 */ dev->last_rx = jiffies; /* 記錄最后收到數(shù)據(jù)包的時間戳 */ /* 統(tǒng)計接收數(shù)據(jù)包數(shù)和接收字節(jié)數(shù) */ lp->stats.rx_packets++; lp->stats.rx_bytes += length; }
-
填充設(shè)備打開 xxx_open()與釋放 xxx_release()函數(shù)代碼。在 CS8900 驅(qū)動程序 中,與此相關(guān)的函數(shù)有:
int net_open(struct net_device *dev); int net_close(struct net_device *dev);
-
填充設(shè)備配置與數(shù)據(jù)統(tǒng)計的具體代碼,填充返回設(shè)備沖突的
xxx_stats()函數(shù)。
6. 網(wǎng)卡驅(qū)動移植一般步驟
拿到一塊新的網(wǎng)卡,一般廠家會有自帶的驅(qū)動程序給你,你所要做的就是以下幾個事情:
- 根據(jù)網(wǎng)卡與開發(fā)板的連接方式確定網(wǎng)卡的內(nèi)存映射地址iobase,也即確定網(wǎng)卡的片選信號所連接的CPU內(nèi)存的哪一個bank(nGCS?),然后根據(jù)網(wǎng)卡內(nèi)存的大小,在網(wǎng)卡驅(qū)動的初始化函數(shù)中調(diào)用ioremap()進行地址重映射;
- 根據(jù)網(wǎng)卡與開發(fā)板的硬件連接圖確定中斷號,并在初始化函數(shù)中利于request_irq()函數(shù),向內(nèi)核申請中斷(確定中斷觸發(fā)方式、中斷處理函數(shù)等);
- 根據(jù)網(wǎng)卡datasheet查看網(wǎng)卡的讀寫時序和位寬參數(shù),設(shè)置開發(fā)板相應(yīng)的內(nèi)存控制寄存器BWSCON和BANKCON*。
- 將它拷貝到內(nèi)核源代碼的相關(guān)目錄并修改該目錄下的Makefile文件以添加修改后的網(wǎng)卡驅(qū)動目標文件。假設(shè)我們已經(jīng)改好的網(wǎng)卡驅(qū)動程序為:dm9dev9000c.c,編譯也沒有錯誤。
cp dm9dev9000c.c /home/leon/linux-3.4.2/drivers/net/ethernet/davicom/
修改該目錄Makefile文件:
#
# Makefile for the Davicom device drivers.
#
#obj-$(CONFIG_DM9000) += dm9000.o
obj-$(CONFIG_DM9000) += dm9dev9000c.o
-
重新編譯內(nèi)核,燒寫新的uImage文件到開發(fā)板中,看看是否可以掛載網(wǎng)絡(luò)根文件系統(tǒng)或者可以設(shè)置IP地址及ping通網(wǎng)絡(luò)。如果可以成功掛載網(wǎng)絡(luò)根文件系統(tǒng),所以網(wǎng)卡移植是成功的。
nfs 30000000 192.168.1.101:/work/nfs_root/uImage_net_new; bootm 30000000 mount -t nfs -o nolock,vers=2 192.168.1.101:/work/nfs_root/fs_mini_mdev_new /mnt
-
我們也可以設(shè)置開機直接掛載網(wǎng)絡(luò)根文件系統(tǒng),這樣就可以直接開機啟動網(wǎng)絡(luò)根文件系統(tǒng)了。
-
uboot中設(shè)置:
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.101:/home/leon/nfs_root/first_fs ip=192.168.1.50:192.168.1.101:192.168.1.1:255.255.255.0::eth0:off save tftp 30000000 uImage bootm 30000000
-
ip=192.168.1.50:為單板ip,
192.168.1.101:為服務(wù)器ip,
192.168.1.1為網(wǎng)關(guān),
255.255.255.0為子網(wǎng)掩碼



