1、網(wǎng)卡設(shè)備驅(qū)動(dòng)原理
1.1 層次結(jié)構(gòu)
Linux系統(tǒng)對(duì)網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)定義了4個(gè)層次, 這4個(gè)層次有到下分為:
-
1、網(wǎng)絡(luò)協(xié)議接口層:實(shí)現(xiàn)統(tǒng)一的數(shù)據(jù)包收發(fā)的協(xié)議粒蜈。該層主要負(fù)責(zé)調(diào)用
dev_queue_xmit()
函數(shù)發(fā)送數(shù)據(jù),netif_rx()
函數(shù)接收數(shù)據(jù)术唬。當(dāng)上層 ARP 或 IP 協(xié)議需要發(fā)送數(shù)據(jù)包時(shí)薪伏,它將調(diào)用網(wǎng)絡(luò)協(xié)議接口層的dev_queue_xmit()
函數(shù)發(fā)送該數(shù)據(jù)包滚澜,同時(shí)需傳遞給該函數(shù)一個(gè)指向struct sk_buff
數(shù)據(jù) 結(jié)構(gòu)的指針粗仓。dev_queue_xmit()
函數(shù)的原型為:
dev_queue_xmit (struct sk_buff * skb );
同樣地,上層對(duì)數(shù)據(jù)包的接收也通過(guò)向 netif_rx()
函數(shù)傳遞一個(gè) struct sk_buff
數(shù)據(jù) 結(jié)構(gòu)的指針來(lái)完成。netif_rx()函數(shù)的原型為:
int netif_rx(struct sk_buff *skb);
-
2借浊、網(wǎng)絡(luò)設(shè)備接口層:通過(guò)
net_device
結(jié)構(gòu)體來(lái)描述一個(gè)具體的網(wǎng)絡(luò)設(shè)備的信息,實(shí)現(xiàn)不同的硬件的統(tǒng)一 -
3塘淑、設(shè)備驅(qū)動(dòng)功能層:用來(lái)負(fù)責(zé)驅(qū)動(dòng)網(wǎng)絡(luò)設(shè)備硬件來(lái)完成各個(gè)功能, 它通過(guò)
hard_start_xmit()
函數(shù)啟動(dòng)發(fā)送操作, 并通過(guò)網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作蚂斤。 - 4存捺、網(wǎng)絡(luò)設(shè)備與媒介層:用來(lái)負(fù)責(zé)完成數(shù)據(jù)包發(fā)送和接收的物理實(shí)體, 設(shè)備驅(qū)動(dòng)功能層的函數(shù)都在這物理上驅(qū)動(dòng)的
層次結(jié)構(gòu)如下圖所示:
設(shè)計(jì)具體的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序時(shí),我們需要完成的主要工作是編寫設(shè)備驅(qū)動(dòng)功 能層的相關(guān)函數(shù)以填充 net_device 數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將 net_device 注冊(cè)入內(nèi)核曙蒸。
1.2 網(wǎng)卡驅(qū)動(dòng)的初始化
網(wǎng)卡驅(qū)動(dòng)程序,只需要編寫網(wǎng)絡(luò)設(shè)備接口層,填充net_device
數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將net_device
注冊(cè)入內(nèi)核,設(shè)置硬件相關(guān)操作,使能中斷處理等捌治。
1.2.1 初始化網(wǎng)卡步驟
- 1)使用
alloc_netdev()
來(lái)分配一個(gè)net_device結(jié)構(gòu)體 - 2)設(shè)置網(wǎng)卡硬件相關(guān)的寄存器
- 3)設(shè)置
net_device
結(jié)構(gòu)體的成員 - 4)使用
register_netdev()
來(lái)注冊(cè)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)萬(wàn)變肖油,實(shí)現(xiàn)多種硬件在軟件層次上的統(tǒng)一。 net_device 結(jié)構(gòu)體在內(nèi)核中指代一個(gè)網(wǎng)絡(luò)設(shè)備臂港,網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序只需通過(guò)填充 net_device 的具體成員并注冊(cè) net_device 即可實(shí)現(xiàn)硬件操作函數(shù)與內(nèi)核的掛接森枪。 net_device 本身是一個(gè)巨型結(jié)構(gòu)體,包含網(wǎng)絡(luò)設(shè)備的屬性描述和操作接口审孽。當(dāng)我 們編寫網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序時(shí)县袱,只需要了解其中的一部分。
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è)備的中斷號(hào)
unsigned char if_port; //該字段僅針對(duì)多端口設(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)計(jì)信息,運(yùn)行ifconfig便會(huì)調(diào)用該成員函數(shù),并返回一個(gè)net_device_stats結(jié)構(gòu)體獲取信息
struct net_device_stats stats; //用來(lái)保存統(tǒng)計(jì)信息的net_device_stats結(jié)構(gòu)體
unsigned long features; //接口特征,
unsigned int flags; //flags指網(wǎng)絡(luò)接口標(biāo)志,以IFF_開頭式散,包括:IFF_UP( 當(dāng)設(shè)備被激活并可以開始發(fā)送數(shù)據(jù)包時(shí), 內(nèi)核設(shè)置該標(biāo)志)打颤、 IFF_AUTOMEDIA(設(shè)置設(shè)備可在多種媒介間切換)杂数、IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調(diào)試模式瘸洛, 可用于控制printk調(diào)用的詳細(xì)程度) 揍移、 IFF_LOOPBACK( 回環(huán))、IFF_MULTICAST( 允許組播) 反肋、 IFF_NOARP( 接口不能執(zhí)行ARP,點(diǎn)對(duì)點(diǎn)接口就不需要運(yùn)行 ARP) 和IFF_POINTOPOINT( 接口連接到點(diǎn)到點(diǎn)鏈路) 等那伐。
unsigned mtu; //最大傳輸單元,也叫最大數(shù)據(jù)包
unsigned short type; //接口的硬件類型
unsigned short hard_header_len; //硬件幀頭長(zhǎng)度,在以太網(wǎng)設(shè)備的初始化函數(shù)中一般被賦為ETH_HLEN,即14
unsigned char *dev_addr; //存放設(shè)備的MAC地址,需由驅(qū)動(dòng)程序從硬件上讀出
unsigned char broadcast[MAX_ADDR_LEN]; //存放設(shè)備的廣播地址,對(duì)于以太網(wǎng)而言,地址長(zhǎng)度為6個(gè)0XFF
unsigned long last_rx; //接收數(shù)據(jù)包的時(shí)間戳,調(diào)用netif_rx()后賦上jiffies即可
unsigned long trans_start; //發(fā)送數(shù)據(jù)包的時(shí)間戳,當(dāng)要發(fā)送的時(shí)候賦上jiffies即可
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);//數(shù)據(jù)包發(fā)送函數(shù), 以使得驅(qū)動(dòng)程序能獲取從上層傳遞下來(lái)的數(shù)據(jù)包石蔗。
void (*tx_timeout) (struct net_device *dev); //發(fā)包超時(shí)處理函數(shù)罕邀,需采取重新啟動(dòng)數(shù)據(jù)包發(fā)送過(guò)程或重新啟動(dòng)硬件等策略來(lái)恢復(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ù),可以通過(guò)sk_buff結(jié)構(gòu)體的成員len來(lái)獲取*/ unsigned long tx_bytes; /*發(fā)送的字節(jié)數(shù),可以通過(guò)sk_buff結(jié)構(gòu)體的成員len來(lái)獲取*/
unsigned long rx_errors; /*收到的錯(cuò)誤數(shù)據(jù)包數(shù)*/
unsigned long tx_errors; /*發(fā)送的錯(cuò)誤數(shù)據(jù)包數(shù)*/
... ...
}
net_device_stats 結(jié)構(gòu)體適宜包含在設(shè)備的私有信息結(jié)構(gòu)體中养距,而其中統(tǒng)計(jì)信息的 修改則應(yīng)該在設(shè)備驅(qū)動(dòng)的與發(fā)送和接收相關(guān)的具體函數(shù)中完成诉探,這些函數(shù)包括中斷處 理程序、數(shù)據(jù)包發(fā)送函數(shù)棍厌、數(shù)據(jù)包發(fā)送超時(shí)函數(shù)和數(shù)據(jù)包接收相關(guān)函數(shù)等寸谜。我們應(yīng)該 在這些函數(shù)中添加相應(yīng)的代碼:
/* 發(fā)送超時(shí)函數(shù) */
void xxx_tx_timeout(struct net_device *dev)
{
struct xxx_priv *priv = netdev_priv(dev);
...
priv->stats.tx_errors++; /* 發(fā)送錯(cuò)誤包數(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) /*讀取硬件上的出錯(cuò)標(biāo)志*/
{
/* 根據(jù)錯(cuò)誤的不同情況显晶,對(duì) 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)”。當(dāng)發(fā)送數(shù)據(jù)包時(shí),Linux 內(nèi)核的網(wǎng)絡(luò)處理模塊必須建立一個(gè)包含要傳輸?shù)臄?shù)據(jù)包 的 sk_buff憎亚,然后將 sk_buff 遞交給下層,各層在 sk_buff 中添加不同的協(xié)議頭直至交 給網(wǎng)絡(luò)設(shè)備發(fā)送弄慰。同樣地第美,當(dāng)網(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; //指向下一個(gè)sk_buff結(jié)構(gòu)體
struct sk_buff *prev; //指向前一個(gè)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ù)包的總長(zhǎng)度,包括線性數(shù)據(jù)和非線性數(shù)據(jù)
data_len; //非線性的數(shù)據(jù)長(zhǎng)度
__u16 mac_len, //mac包頭長(zhǎng)度
hdr_len;
.....
__u32 priority; //該sk_buff結(jié)構(gòu)體的優(yōu)先級(jí)
......
__be16 protocol; //存放上層的協(xié)議類型,可以通過(guò)eth_type_trans()來(lái)獲取
... ...
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)當(dāng)前協(xié)議層有效數(shù)據(jù)的起始地址贡必。各層的有效數(shù)據(jù)信息包含的內(nèi)容都不一樣:對(duì)于傳輸層而言兔港,用戶數(shù)據(jù)和傳輸層協(xié)議頭屬于有效數(shù)據(jù)。 l 對(duì)于網(wǎng)絡(luò)層而言仔拟,用戶數(shù)據(jù)衫樊、傳輸層協(xié)議頭和網(wǎng)絡(luò)層協(xié)議頭是其有效數(shù)據(jù)。 l 對(duì)于數(shù)據(jù)鏈路層而言利花,用戶數(shù)據(jù)科侈、傳輸層協(xié)議頭、網(wǎng)絡(luò)層協(xié)議頭和鏈路層頭 部都屬于有效數(shù)據(jù)炒事。 因此臀栈,data 指針的值需隨著當(dāng)前擁有 sk_buff 的協(xié)議層的變化進(jìn)行相應(yīng)的移動(dòng)。
tail 指針則指向?qū)?yīng)當(dāng)前協(xié)議層有效數(shù)據(jù)負(fù)載的結(jié)尾地址挠乳,與 data 指針對(duì)應(yīng)权薯。
end 指針指向內(nèi)存中分配的數(shù)據(jù)緩沖區(qū)的結(jié)尾,與 head 指針對(duì)應(yīng)睡扬。
其實(shí)盟蚣,end 指針?biāo)傅?址 數(shù) 據(jù) 緩 沖 區(qū) 的 末 尾 還 包 括 一 個(gè) skb_shared_info
結(jié)構(gòu)體的空間,這個(gè)結(jié)構(gòu)體 存放分隔存儲(chǔ)的數(shù)據(jù)片段卖怜,意味著可以將數(shù) 據(jù)包的有效數(shù)據(jù)分成幾片存儲(chǔ)在不同的內(nèi)存空間中屎开。 每一個(gè)分片frags的長(zhǎng)度上限是一頁(yè)。
- sk_buff結(jié)構(gòu)體的空間
- image-20210809231316947
- 長(zhǎng)度信息 len马靠、data_len奄抽、truesize蔼两。
len是指數(shù)據(jù)包有 效數(shù)據(jù)的長(zhǎng)度,包括協(xié)議頭和負(fù)載如孝;
data_len 這個(gè)成員,它記錄分片的 數(shù)據(jù)長(zhǎng)度娩贷;
truesize 表示緩存區(qū)的整體長(zhǎng)度: sizeof(struct sk_buff) + “傳入 alloc_skb()或dev_alloc_skb()的長(zhǎng)度“(但不包括結(jié)構(gòu)體 skb_shared_info 的長(zhǎng)度)第晰。
- sk_buff-> data數(shù)據(jù)包格式
-
套接字緩沖區(qū)操作
-
(1)分配
-
struct sk_buff *alloc_skb(unsigned int len,int priority);
alloc_skb()函數(shù)分配一個(gè)套接字緩沖區(qū)和一個(gè)數(shù)據(jù)緩沖區(qū),參數(shù) len 為數(shù)據(jù)緩沖區(qū) 的空間大小彬祖,以 16 字節(jié)對(duì)齊茁瘦,參數(shù) priority 為內(nèi)存分配的優(yōu)先級(jí) -
struct sk_buff *dev_alloc_skb(unsigned int len);
dev_alloc_skb()函數(shù)只是以 GFP_ATOMIC 優(yōu)先級(jí)(代表分配過(guò)程不能被中斷)調(diào) 用上面的 alloc_skb()函數(shù),并保存 skb->head 和 skb->data 之間的 16 個(gè)字節(jié)储笑。 - 分配成功之后甜熔,因?yàn)檫€沒有存放具體的網(wǎng)絡(luò)數(shù)據(jù)包,所以 sk_buff 的 data突倍、tail 指 針都指向存儲(chǔ)空間的起始地址 head腔稀,而 len 的大小則為 0。
-
-
(2)釋放
void kfree_skb(struct sk_buff *skb); //在內(nèi)核內(nèi)部使用羽历,而網(wǎng)絡(luò)設(shè)備 驅(qū)動(dòng)程序中則必須使用下面3個(gè)其一 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)指針移動(dòng)
套接字緩沖區(qū)中的數(shù)據(jù)緩沖區(qū)指針移動(dòng)操作包括 put(放置)焊虏、push(推)、 pull(拉)秕磷、reserve(保留)等诵闭。
-
put 操作(用于在緩沖區(qū)尾部添加數(shù)據(jù)):
-
unsigned char *skb_put(struct sk_buff *skb, unsigned int len); //會(huì)檢測(cè)放入緩沖區(qū)的數(shù)據(jù) unsigned char *__skb_put(struct sk_buff *skb, unsigned int len); //不會(huì)檢測(cè)放入緩沖區(qū)的數(shù)據(jù)
上述函數(shù)將 tail 指針下移,增加 sk_buff 的 len 值澎嚣,并返回 skb->tail 的當(dāng)前值疏尿。
-
-
push 操作(用于在數(shù)據(jù)包發(fā)送時(shí)添加頭部)
-
unsigned char *skb_push(struct sk_buff *skb, unsigned int len); //會(huì)檢測(cè)放入緩沖區(qū)的數(shù)據(jù) unsigned char *_ _skb_push(struct sk_buff *skb, unsigned int len); //不會(huì)檢測(cè)放入緩沖區(qū)的數(shù)據(jù)
push 操作在存儲(chǔ)空間的頭部增加一段可以存儲(chǔ)網(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 操作(用于在存儲(chǔ)空間 的頭部預(yù)留 len 長(zhǎng)度的空隙)
-
void skb_reserve(struct sk_buff *skb, unsigned int len);
skb_reserve()函數(shù)將 data 指針和 tail 指針同時(shí)下移
-
-
1.3 網(wǎng)卡驅(qū)動(dòng)發(fā)包過(guò)程
在內(nèi)核中,當(dāng)上層要發(fā)送一個(gè)數(shù)據(jù)包時(shí), 就會(huì)調(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);
這個(gè)函數(shù)中需要涉及到sk_buff結(jié)構(gòu)體,含義為(socket buffer)套接字緩沖區(qū),用來(lái)網(wǎng)絡(luò)各個(gè)層次之間傳遞數(shù)據(jù).
發(fā)包函數(shù)處理步驟:
1踩衩、把數(shù)據(jù)包發(fā)出去之前,需要使用netif_stop_queue()來(lái)停止上層傳下來(lái)的數(shù)據(jù)包;
2.1贩汉、設(shè)置寄存器驱富,通過(guò)網(wǎng)絡(luò)設(shè)備硬件來(lái)發(fā)送數(shù)據(jù)
2.2、當(dāng)數(shù)據(jù)包發(fā)出去后, 再調(diào)用dev_kfree_skb()函數(shù)來(lái)釋放sk_buff,該函數(shù)原型如下:
void dev_kfree_skb(struct sk_buff *skb);
3匹舞、當(dāng)數(shù)據(jù)包發(fā)出成功,就會(huì)進(jìn)入TX接收中斷函數(shù)褐鸥,然后更新統(tǒng)計(jì)信息,調(diào)用netif_wake_queue()來(lái)喚醒赐稽,啟動(dòng)上層繼續(xù)發(fā)包下來(lái)叫榕;
4浑侥、若數(shù)據(jù)包發(fā)出去超時(shí),一直進(jìn)不到TX中斷函數(shù),就會(huì)調(diào)用net_device結(jié)構(gòu)體的*tx_timeout超時(shí)成員函數(shù)晰绎,在該函數(shù)中更新統(tǒng)計(jì)信息寓落,并調(diào)用netif_wake_queue()來(lái)喚醒。
1.4 網(wǎng)卡驅(qū)動(dòng)收包過(guò)程
接收數(shù)據(jù)包主要是通過(guò)中斷函數(shù)處理,來(lái)判斷中斷類型荞下,如果等于ISQ_RECEIVER_EVENT表示為接收中斷,然后進(jìn)入接收數(shù)據(jù)函數(shù),通過(guò)netif_rx()
將數(shù)據(jù)上交給上層伶选。例如,下圖內(nèi)核中自帶的網(wǎng)卡驅(qū)動(dòng):/drivers/net/cs89x0.c
通過(guò)獲取的status標(biāo)志來(lái)判斷是什么中斷,如果是接收中斷,就進(jìn)入net_rx()尖昏。
-
收包函數(shù)處理步驟:
- 1仰税、使用
dev_alloc_skb()
來(lái)構(gòu)造一個(gè)新的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()來(lái)動(dòng)態(tài)擴(kuò)大sk_buff結(jié)構(gòu)體里中的數(shù)據(jù)區(qū)唉窃; - 5葵姥、使用
eth_type_trans()
來(lái)獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里; - 6句携、然后更新統(tǒng)計(jì)信息榔幸,最后使用netif_rx( )來(lái)將sk_fuffer傳遞給上層協(xié)議中。
- 1仰税、使用
-
skb_put()函數(shù)
- 原型:
static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
- 作用:將數(shù)據(jù)區(qū)向下擴(kuò)大len字節(jié)
- sk_buff緩沖區(qū)變化圖:
- image-20210809232643374
- 原型:
1.5 網(wǎng)卡驅(qū)動(dòng)的注冊(cè)與注銷
網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的注冊(cè)與注銷使用成對(duì)出現(xiàn)的register_netdev()
和unregister_netdev()
函數(shù)完成矮嫉,這兩個(gè)函數(shù)的原型為:
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
這兩個(gè)函數(shù)都接收一個(gè) net_device 結(jié)構(gòu)體指針為參數(shù)削咆, net_device 的生成和成員的賦值并非一定要由工程師逐個(gè)親自動(dòng)手完成,可以利 用下面的函數(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ù)生成一個(gè) net_device 結(jié)構(gòu)體蠢笋,對(duì)其成員賦值并返回該結(jié)構(gòu)體的指針拨齐。第一個(gè)參數(shù)為設(shè)備私有成員的大小,第二個(gè)參數(shù)為設(shè)備名昨寞,第三個(gè)參數(shù)為 net_device 的 setup()函數(shù)指針瞻惋。setup()函數(shù)接收的參數(shù)也為 net_device 指針,用 于預(yù)置 net_device 成員的值援岩。
alloc_etherdev()是 alloc_netdev()針對(duì)以太網(wǎng)的“快捷”函數(shù)歼狼,其中的ether_setup()是由 Linux 內(nèi)核提供的一個(gè)對(duì)以太網(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ū)動(dòng)
虛擬網(wǎng)卡驅(qū)動(dòng),也就是說(shuō)不需要硬件相關(guān)操作,所以就沒有中斷函數(shù),我們通過(guò)linux的ping命令來(lái)實(shí)現(xiàn)發(fā)包,然后在發(fā)包函數(shù)中偽造一個(gè)收的ping包函數(shù),實(shí)現(xiàn)能ping通任何ip地址享怀。
2.1 編寫步驟
-
init初始函數(shù)
- 1)使用alloc_netdev()來(lái)分配一個(gè)net_device結(jié)構(gòu)體
- 2)設(shè)置net_device結(jié)構(gòu)體的成員
- 3)使用register_netdev()來(lái)注冊(cè)net_device結(jié)構(gòu)體
-
發(fā)包函數(shù)
- 1)使用netif_stop_queue()來(lái)阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)層發(fā)送數(shù)據(jù)包
- 2)調(diào)用收包函數(shù),并代入發(fā)送的sk_buff緩沖區(qū), 里面來(lái)偽造一個(gè)收的ping包函數(shù)
- 3)使用dev_kfree_skb()函數(shù)來(lái)釋放發(fā)送的sk_buff緩存區(qū)
- 4)更新發(fā)送的統(tǒng)計(jì)信息
- 5)使用netif_wake_queue()來(lái)喚醒被阻塞的上層
-
收包函數(shù):修改發(fā)送的sk_buff里數(shù)據(jù)包的數(shù)據(jù),使它變?yōu)橐粋€(gè)接收的sk_buff羽峰。
1)需要對(duì)調(diào)上圖的ethhdr結(jié)構(gòu)體 ”源/目的”MAC地址
2)需要對(duì)調(diào)上圖的iphdr結(jié)構(gòu)體”源/目的” IP地址
3)使用ip_fast_csum()來(lái)重新獲取iphdr結(jié)構(gòu)體的校驗(yàn)碼
4)設(shè)置上圖數(shù)據(jù)包的數(shù)據(jù)類型,之前是發(fā)送ping包0x08,需要改為0x00,表示接收ping包
5)使用dev_alloc_skb()來(lái)構(gòu)造一個(gè)新的sk_buff
6)使用skb_reserve(rx_skb, 2);將sk_buff緩沖區(qū)里的數(shù)據(jù)包先后位移2字節(jié),來(lái)騰出sk_buff緩沖區(qū)里的頭部空間
7)使用memcpy()將之前修改好的sk_buff->data復(fù)制到新的sk_buff里的data成員指向的地址處:
8)設(shè)置新的sk_buff 其它成員
9)使用eth_type_trans()來(lái)獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里
10)然后更新接收統(tǒng)計(jì)信息,最后使用netif_rx( )來(lái)將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.對(duì)調(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.對(duì)調(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()來(lái)重新獲取iphdr結(jié)構(gòu)體的校驗(yàn)碼*/
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; /*原來(lái)0x8表示發(fā)送ping包,現(xiàn)在0表示接收ping包 */
/* 5.構(gòu)造一個(gè)新的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()擴(kuò)大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()來(lái)獲取上層協(xié)議 */
rx_skb->protocol = eth_type_trans(rx_skb, dev);
/* 10.更新接收統(tǒng)計(jì)信息,并向上層傳遞sk_fuffer收包 */
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
dev->last_rx = jiffies; //收包時(shí)間戳
// 提交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)卡的隊(duì)列梅屉,阻止上層向驅(qū)動(dòng)層繼續(xù)發(fā)送數(shù)據(jù)包 */
netif_stop_queue(dev);
/* 2.真實(shí)驅(qū)動(dòng)要把skb的數(shù)據(jù)寫入網(wǎng)卡 值纱,但在此先通過(guò)emulator_rx_packet模擬 */
emulator_rx_packet(skb, dev); /* 構(gòu)造一個(gè)假的sk_buff,上報(bào) */
/* 3.釋放發(fā)送的sk_buff緩存區(qū)*/
dev_kfree_skb (skb);
/* 4.更新統(tǒng)計(jì)信息 */
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
dev->trans_start = jiffies; //發(fā)送時(shí)間戳
/* 5.數(shù)據(jù)全部發(fā)送出去后,喚醒網(wǎng)卡的隊(duì)列 (真實(shí)網(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. 分配一個(gè)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è)置下面兩項(xiàng)才能ping通 */
vnet_dev->flags |= IFF_NOARP;
vnet_dev->features |= NETIF_F_IP_CSUM;
/* 3. 注冊(cè) */
//register_netdevice(vnet_dev); //編譯會(huì)出錯(cuò)!
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 測(cè)試
- 掛載驅(qū)動(dòng),可以看到net類下就有了這個(gè)網(wǎng)卡設(shè)備坯汤,并嘗試ping自己
- image-20210809233459142
上圖的ping之所以成功虐唠,并是因?yàn)槲覀冊(cè)诎l(fā)包函數(shù)中偽造了一個(gè)來(lái)收包,而是因?yàn)閘inux中惰聂,ping自己的時(shí)候并不調(diào)用(不需要)發(fā)送函數(shù)向網(wǎng)絡(luò)設(shè)備發(fā)送sk_buff數(shù)據(jù)包疆偿。
- ping同網(wǎng)段其它地址
- image-20210809233646706
ping其它ip地址之所以成功,是因?yàn)槲覀冊(cè)诎l(fā)包函數(shù)中利用emulator_rx_packet函數(shù)偽造了一個(gè)接收數(shù)據(jù)包庶近,并通過(guò)netif_rx()來(lái)將收包上傳給上層翁脆。
3眷蚓、移植內(nèi)核自帶的網(wǎng)卡驅(qū)動(dòng)程序
在移植之前鼻种,首先我們來(lái)看一下mini2440(對(duì)應(yīng)的機(jī)器ID為:set machid 7CF)中,是如何支持dm9000網(wǎng)卡的沙热。
進(jìn)入到入口函數(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),
};
一般是通過(guò).name這個(gè)成員進(jìn)行匹配的,搜索字符串“dm9000”篙贸,找到如下結(jié)構(gòu)體(在平臺(tái)文件中: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ū)動(dòng)程序,就可以使用驅(qū)動(dòng)程序了寝贡。
(這就是所謂的平臺(tái)設(shè)備平臺(tái)驅(qū)動(dòng)的東西了扒披,把可變的東西抽象出來(lái)放到平臺(tái)相關(guān)的文件中定義,而我們的驅(qū)動(dòng)程序圃泡,基本上是不需要改變的碟案,它是穩(wěn)定的內(nèi)容,我們移植的時(shí)候颇蜡,只需要把平臺(tái)層可變的相關(guān)結(jié)構(gòu)體加上价说,需要修改的資源,進(jìn)行修改就可以了)风秤。
而我們用的是smdk2440(對(duì)應(yīng)的機(jī)器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ū)動(dòng)程序
有時(shí)候,內(nèi)核自帶的網(wǎng)卡驅(qū)動(dòng)程序比較老惦费,而我們的硬件有可能比較新兵迅,那么我們就不能使用內(nèi)核的網(wǎng)卡驅(qū)動(dòng)程序了,就需要去移植最新的網(wǎng)卡驅(qū)動(dòng)程序薪贫,那么這種類型的恍箭,又該如何移植呢?
4.1 網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序的模塊加載和卸載函數(shù)
int xxx_init_module(void)
{
...
/* 分配 net_device 結(jié)構(gòu)體并對(duì)其成員賦值 */
xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d", xxx_init);
if (xxx_dev == NULL)
... /* 分配 net_device 失敗 */
/* 注冊(cè) 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è)備的初始化主要需要完成如下幾個(gè)方面的工作:
- 進(jìn)行硬件上的準(zhǔn)備工作瞧省,檢查網(wǎng)絡(luò)設(shè)備是否存在扯夭,如果存在,則檢測(cè)設(shè)備使用的硬件資源鞍匾;
- 進(jìn)行軟件接口上的準(zhǔn)備工作交洗,分配 net_device 結(jié)構(gòu)體并對(duì)其數(shù)據(jù)和函數(shù)指針 成員賦值;
- 獲得設(shè)備的私有信息指針并初始化其各成員的值橡淑。如果私有信息中包括自旋 鎖或信號(hào)量等并發(fā)或同步機(jī)制构拳,則需對(duì)其進(jìn)行初始化。
個(gè)網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)初始化函數(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è)備使用的硬件資源凫海,申請(qǐng) I/O 區(qū)域、中斷和 DMA 通道等男娄。
2. 調(diào)用 Linux 內(nèi)核提供的 netif_start_queue()函數(shù)行贪,激活設(shè)備發(fā)送隊(duì)列侦高。 網(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()兩個(gè)函數(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)
{
/* 申請(qǐng)端口翁授、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ū)動(dòng)程序從上層協(xié)議傳遞過(guò)來(lái)的 sk_buff 參數(shù)獲得數(shù)據(jù)包的有效數(shù) 據(jù)和長(zhǎng)度贮配,將有效數(shù)據(jù)放入臨時(shí)緩沖區(qū)。
(2)對(duì)于以太網(wǎng)塞赂,如果有效數(shù)據(jù)的長(zhǎng)度小于以太網(wǎng)沖突檢測(cè)所要求數(shù)據(jù)幀的最小 長(zhǎng)度 ETH_ZLEN泪勒,則給臨時(shí)緩沖區(qū)的末尾填充 0。
(3)設(shè)置硬件的寄存器,驅(qū)使網(wǎng)絡(luò)設(shè)備進(jìn)行數(shù)據(jù)發(fā)送操作圆存。
int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
/* 獲得有效數(shù)據(jù)指針和長(zhǎng)度 */
data = skb->data;
len = skb->len;
if (len < ETH_ZLEN)
{
/* 如果幀長(zhǎng)小于以太網(wǎng)幀最小長(zhǎng)度叼旋,補(bǔ) 0 */
memset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies; /* 記錄發(fā)送時(shí)間戳 */
/* 設(shè)置硬件寄存器讓硬件把數(shù)據(jù)包發(fā)送出去 */
xxx_hw_tx(data, len, dev);
...
}
當(dāng)數(shù)據(jù)傳輸超時(shí)時(shí),意味著當(dāng)前的發(fā)送操作失敗沦辙,此時(shí)夫植,數(shù)據(jù)包發(fā)送超時(shí)處理函 數(shù) xxx_tx_ timeout()將被調(diào)用。這個(gè)函數(shù)需要調(diào)用 Linux 內(nèi)核提供的 netif_wake_queue()函數(shù)重新啟動(dòng)設(shè)備發(fā)送隊(duì)列:
void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev); /* 重新啟動(dòng)設(shè)備發(fā)送隊(duì)列 */
}
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é)議兔综。完成這一過(guò)程的函數(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); /* 對(duì)齊 */
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);
/* 記錄接收時(shí)間戳 */
dev->last_rx = jiffies;
...
}
如果是 NAPI 兼容的設(shè)備驅(qū)動(dòng)饿凛,則可以通過(guò) poll 方式接收數(shù)據(jù)包。這種情況下邻奠, 我們需要為該設(shè)備驅(qū)動(dòng)提供 xxx_poll()函數(shù):
static int xxx_poll(struct net_device *dev, int *budget)
{
//dev->quota 是當(dāng)前 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)
{
/*從隊(duì)列中取出數(shù)據(jù)包*/
pkt = xxx_dequeue_buf(dev);
/*接下來(lái)的處理为居,和中斷觸發(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)出了中斷處理機(jī)制和輪詢機(jī)制之間的差別碌宴。
*/
netif_receive_skb(skb);
/*更改統(tǒng)計(jì)數(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); //把當(dāng)前指定的設(shè)備從 poll 隊(duì)列中清除
xxx_enable_rx_int (…); /* 再次使能網(wǎng)絡(luò)設(shè)備的接收中斷 */
return 0;
}
return 1;
}
雖然 NAPI 兼容的設(shè)備驅(qū)動(dòng)以 poll 方式接收數(shù)據(jù)包,但是仍然需要首次數(shù)據(jù)包接 收中斷來(lái)觸發(fā) poll 過(guò)程蒙畴。與數(shù)據(jù)包的中斷接收方式不同的是贰镣,以輪詢方式接收數(shù)據(jù)包 時(shí),當(dāng)?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ū)動(dòng)的中斷程序調(diào)用,將設(shè) 備的 poll 方法添加到網(wǎng)絡(luò)層的 poll 處理隊(duì)列中蹬音,排隊(duì)并且準(zhǔn)備接收數(shù)據(jù)包上煤,最終觸發(fā) 一個(gè) NET_RX_SOFTIRQ 軟中斷,通知網(wǎng)絡(luò)層接收數(shù)據(jù)包著淆。下圖為 NAPI 驅(qū)動(dòng) 程序各部分的調(diào)用關(guān)系:
4.4 網(wǎng)絡(luò)連接狀態(tài)
網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序中往往設(shè)置一個(gè)定時(shí)器來(lái)對(duì)鏈路狀態(tài)進(jìn)行周期性地檢查劫狠。當(dāng)定 時(shí)器到期之后,在定時(shí)器處理函數(shù)中讀取物理設(shè)備的相關(guān)寄存器獲得載波狀態(tài)永部,從而 更新設(shè)備的連接狀態(tài)独泞。
網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)可以通過(guò) netif_carrier_on()
和netif_carrier_off()
函數(shù)改變或通知內(nèi)核網(wǎng)絡(luò)設(shè)備的連接狀態(tài)。此外苔埋,函數(shù) netif_carrier_ok()
可用于向調(diào)用者返回鏈路上的載波信號(hà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ū)動(dòng)用定時(shí)器周期檢查鏈路狀態(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í)器處理函數(shù)會(huì)不停地利用第 31~35 行代 碼啟動(dòng)新的定時(shí)器以實(shí)現(xiàn)周期檢測(cè)的目的。那么最初啟動(dòng)定時(shí)器的地方在哪里呢荞膘?很 顯然罚随,它最適合在設(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ū)動(dòng)實(shí)例分析
當(dāng) CS8900 處于 I/O 模式下時(shí)(這里所說(shuō)的 CS8900 處于 I/O 模式并非意味著它一定位于 CPU 的 I/O 空間,實(shí) 際上羽资,CS8900 I/O 模式下的寄存器仍然映射 ARM 處理器的內(nèi)存空間毫炉。因此, 我們直接通過(guò)讀/寫寄存器地址ioremap()之后的虛擬地址即可)削罩,可以通過(guò)以 下幾個(gè) PacketPage 空間內(nèi)的寄存器來(lái)控制 CS8900 的行為(括號(hào)內(nèi)給出的是寄存器地 址相對(duì)于 PacketPage 基地址的偏移):
寄存器 | 作用 |
---|---|
LINECTL(0112H) | 決定 CS8900 的基本配置和物理接口瞄勾,可選擇使用 10BASE-T 接口、AUI 接口或者自動(dòng)選擇弥激。 |
RXCTL(0104H) | 控制 CS8900 接收特定數(shù)據(jù)包进陡,控制是否接收多播、廣播和單播包微服。 |
RXCFG(0102H) | RXCFG 控制 CS8900 接收到特定數(shù)據(jù)包后引發(fā)接收中斷趾疚,并控制是否使用接收 DMA 和 CRC 校驗(yàn)。 |
BUSCT(0116H) | BUSCT 可控制芯片的工作模式以蕴、DMA 方式糙麦、是否使能外部中斷引腳。 |
BUSST(0138H) | 標(biāo)志網(wǎng)絡(luò)設(shè)備的發(fā)送狀態(tài)丛肮,如設(shè)備是否準(zhǔn)備好發(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ù)長(zhǎng)度寫入 TXLENG 寄存器write_reg(TXLENG, send_len)
焚廊。
(3)讀取 PacketPage 空間內(nèi)的 BUSST 寄存器,確定其第 8 位被設(shè)置為 Rdy4TxNOW习劫,即設(shè)備處于準(zhǔn)備發(fā)送狀態(tài)reg(BusST)&0x100
咆瘟。
(4)將要發(fā)送的數(shù)據(jù)循環(huán)寫入 PORT0 寄存器write_reg(PORT0, data)
。
(5)將數(shù)據(jù)組織為以太網(wǎng)幀并添加填充位和 CRC 校驗(yàn)信息诽里,然后將數(shù)據(jù)轉(zhuǎn)化為比特流傳送到網(wǎng)絡(luò)媒介袒餐。
在 I/O 模式下,CS8900 接收數(shù)據(jù)包的方法如下:
(1)接收到網(wǎng)絡(luò)適配器產(chǎn)生的中斷谤狡,查詢相對(duì)于 I/O 基地址偏移 0008H 中斷狀態(tài) 隊(duì)列端口灸眼,判斷中斷類型為接收中斷。
(2)讀 PORT0 寄存器依次獲得接收狀態(tài) rxStatus豌汇、接收數(shù)據(jù)長(zhǎng)度 rxLength幢炸。
(3)循環(huán)繼續(xù)對(duì) PORT0 寄存器讀取 rxLength 次,獲得整個(gè)數(shù)據(jù)包拒贱。
(4)驅(qū)動(dòng)程序進(jìn)行數(shù)據(jù)包處理并傳遞給上層宛徊。
對(duì)于一種網(wǎng)絡(luò)設(shè)備的驅(qū)動(dòng)而言佛嬉,工程師主要需完成設(shè)備驅(qū)動(dòng)功能層的設(shè)計(jì)。在 16.2~ 16.8節(jié)已經(jīng)給出了設(shè)備驅(qū)動(dòng)功能層主要數(shù)據(jù)結(jié)構(gòu)和函數(shù)的設(shè)計(jì)模板闸天,因此暖呕,在編寫CS8900 的這些數(shù)據(jù)結(jié)構(gòu)和函數(shù)時(shí),實(shí)際要完成的工作就是用具體的針對(duì) CS8900 的操作來(lái)填充模 板苞氮,具體包括以下工作:
-
填充 CS8900 的私有信息結(jié)構(gòu)體湾揽,把 CS8900 特定的數(shù)據(jù)放入這個(gè)私有結(jié)構(gòu)體中。在 CS8900 驅(qū)動(dòng)程序中笼吟,這個(gè)數(shù)據(jù)結(jié)構(gòu)為
struct net_local
库物。在 CS8900 的設(shè)備驅(qū)動(dòng)程序中,核心數(shù)據(jù)結(jié)構(gòu) net_device 以全局變量的方式定義贷帮, 其數(shù)個(gè)成員的初始值都被置為空戚揭,私有信息結(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ā)控制自旋鎖 */ };
當(dāng)芯片的版本字母不同時(shí)撵枢,net_local 結(jié)構(gòu)體中記錄的 send_cmd 命令將不同民晒。例如, 同樣是 CS8900 網(wǎng)卡锄禽,若芯片版本字母為大于等于“F”潜必,則發(fā)送命令為 TX_NOW,而 對(duì)于 CS8900A沃但,發(fā)送命令為 TX_AFTER_ALL磁滚。
-
填充設(shè)備初始化模板,初始化 net_device 結(jié)構(gòu)體绽慈,將其注冊(cè)入內(nèi)核恨旱。net_device 的注冊(cè)與注銷在模塊加載與注銷函數(shù)中完成辈毯。在 CS8900 驅(qū)動(dòng)程序中坝疼,與此相關(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ù)完成,這個(gè)函數(shù)將在 net_device 被注冊(cè)時(shí)自動(dòng)被調(diào)用谆沃。init()函數(shù)在 CS8900 網(wǎng)卡的驅(qū)動(dòng)程序中對(duì)應(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) //驗(yàn)證網(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è)硬件平臺(tái)中網(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)當(dāng)芯片類型為 CS8900 時(shí)嗜诀,第 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è)備中斷號(hào) */ 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)體時(shí)隆敢,cs89x0_probe()函數(shù)會(huì)被自動(dòng)調(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)卡申請(qǐ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)體的注冊(cè) 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 申請(qǐng)的 I/O 地址區(qū)域 */ release_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT); } }
上述函數(shù)第 8~11 行設(shè)置 S3C2410A ARM 處理器的片選,第 13行設(shè)置 ARM 與 CS8900 網(wǎng)卡對(duì)應(yīng)的中斷拂蝎,第 15~18 行循環(huán)檢索 netcard_portlist[ ]數(shù)組中定義的基地址處 是否存在 CS8900 網(wǎng)卡
-
填充設(shè)備發(fā)送數(shù)據(jù)包函數(shù)模板穴墅,把真實(shí)的數(shù)據(jù)包發(fā)送硬件操作填充入
xxx_tx()
函數(shù),并填充發(fā)送超時(shí)函數(shù)xxx_tx_timeout()
温自。當(dāng)發(fā)送數(shù)據(jù)包超時(shí)時(shí)玄货,CS8900 驅(qū)動(dòng)程序的數(shù)據(jù)包發(fā)送超時(shí)函數(shù)將被調(diào)用,它重 新啟動(dòng)設(shè)備發(fā)送隊(duì)列悼泌。在初始化函數(shù)中誉结,CS8900 的數(shù)據(jù)包發(fā)送函數(shù)指針hard_ start_xmit
被賦值為 CS8900 驅(qū)動(dòng)程序中的net_send_packet()
,這個(gè)函數(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);/* 使用自旋鎖阻止多個(gè)數(shù)據(jù)包被同時(shí)寫入硬件*/ netif_stop_queue(dev); /* 初始化硬件發(fā)送序列 */ writeword(dev, TX_CMD_PORT, lp->send_cmd); writeword(dev, TX_LEN_PORT, skb->len); /* 檢測(cè)硬件是否處于發(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ā)送開始的時(shí)間戳 */ 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); //再次啟動(dòng)網(wǎng)卡 }
-
填充設(shè)備驅(qū)動(dòng)程序的中斷處理程序 xxx_interrupt()和具體的數(shù)據(jù)包接收函數(shù) xxx_rx()惩坑,填入真實(shí)的硬件操作。在 CS8900 驅(qū)動(dòng)程序中也拜,與此相關(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ù)包的長(zhǎng)度 */ /* 分配新的套接字緩沖區(qū)和數(shù)據(jù)緩沖區(qū) */ skb = dev_alloc_skb(length + 2); if (skb == NULL) { lp->stats.rx_dropped++; /* 分配失敗以舒,統(tǒng)計(jì)被丟棄的包數(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ù)包的時(shí)間戳 */ /* 統(tǒng)計(jì)接收數(shù)據(jù)包數(shù)和接收字節(jié)數(shù) */ lp->stats.rx_packets++; lp->stats.rx_bytes += length; }
-
填充設(shè)備打開 xxx_open()與釋放 xxx_release()函數(shù)代碼。在 CS8900 驅(qū)動(dòng)程序 中慢哈,與此相關(guān)的函數(shù)有:
int net_open(struct net_device *dev); int net_close(struct net_device *dev);
-
填充設(shè)備配置與數(shù)據(jù)統(tǒng)計(jì)的具體代碼蔓钟,填充返回設(shè)備沖突的
xxx_stats()
函數(shù)。
6. 網(wǎng)卡驅(qū)動(dòng)移植一般步驟
拿到一塊新的網(wǎng)卡卵贱,一般廠家會(huì)有自帶的驅(qū)動(dòng)程序給你滥沫,你所要做的就是以下幾個(gè)事情:
- 根據(jù)網(wǎng)卡與開發(fā)板的連接方式確定網(wǎng)卡的內(nèi)存映射地址iobase,也即確定網(wǎng)卡的片選信號(hào)所連接的CPU內(nèi)存的哪一個(gè)bank(nGCS键俱?)兰绣,然后根據(jù)網(wǎng)卡內(nèi)存的大小,在網(wǎng)卡驅(qū)動(dòng)的初始化函數(shù)中調(diào)用ioremap()進(jìn)行地址重映射编振;
- 根據(jù)網(wǎng)卡與開發(fā)板的硬件連接圖確定中斷號(hào)缀辩,并在初始化函數(shù)中利于request_irq()函數(shù),向內(nèi)核申請(qǐng)中斷(確定中斷觸發(fā)方式踪央、中斷處理函數(shù)等)臀玄;
- 根據(jù)網(wǎng)卡datasheet查看網(wǎng)卡的讀寫時(shí)序和位寬參數(shù),設(shè)置開發(fā)板相應(yīng)的內(nèi)存控制寄存器BWSCON和BANKCON*畅蹂。
- 將它拷貝到內(nèi)核源代碼的相關(guān)目錄并修改該目錄下的Makefile文件以添加修改后的網(wǎng)卡驅(qū)動(dòng)目標(biāo)文件健无。假設(shè)我們已經(jīng)改好的網(wǎng)卡驅(qū)動(dòng)程序?yàn)椋篸m9dev9000c.c,編譯也沒有錯(cuò)誤液斜。
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è)置開機(jī)直接掛載網(wǎng)絡(luò)根文件系統(tǒng),這樣就可以直接開機(jī)啟動(dò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)掩碼