引言
??網(wǎng)絡(luò)驅(qū)動(dòng)是 linux 里面驅(qū)動(dòng)三巨頭之一,linux 下的網(wǎng)絡(luò)功能非常強(qiáng)大,嵌入式 linux 中也常常用到網(wǎng)絡(luò)功能。前面我們已經(jīng)講過了字符設(shè)備驅(qū)動(dòng)和塊設(shè)備驅(qū)動(dòng),本章我們就來學(xué)習(xí)一下 linux 里面的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)。
一、Linux網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)的結(jié)構(gòu)
??網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序的體系結(jié)構(gòu)分為4層,依次為網(wǎng)絡(luò)協(xié)議驅(qū)動(dòng)層、網(wǎng)絡(luò)設(shè)備接口層、設(shè)備驅(qū)動(dòng)功能層、網(wǎng)絡(luò)設(shè)備與媒介層。
??(1)網(wǎng)絡(luò)協(xié)議接口層向網(wǎng)絡(luò)層協(xié)議提供統(tǒng)一的數(shù)據(jù)包收發(fā)接口,不論上層協(xié)議是ARP還是IP,都通過dev_queue_xmit函數(shù)發(fā)送數(shù)據(jù)、并通過netif_rx函數(shù)接收數(shù)據(jù)。這一層的存在使得上層協(xié)議獨(dú)立于具體的設(shè)備。
??(2)網(wǎng)絡(luò)設(shè)備接口層向協(xié)議接口層提供統(tǒng)一的用于描述具體網(wǎng)絡(luò)設(shè)備屬性和操作的結(jié)構(gòu)體net_device,該結(jié)構(gòu)體是設(shè)備驅(qū)動(dòng)功能層中的各函數(shù)的容器。實(shí)際上,網(wǎng)絡(luò)設(shè)備接口層從宏觀上規(guī)劃了具體操作硬件的設(shè)備驅(qū)動(dòng)功能層的結(jié)構(gòu)。
??(3)設(shè)備驅(qū)動(dòng)功能層的各函數(shù)是網(wǎng)絡(luò)設(shè)備接口層net_device數(shù)據(jù)結(jié)構(gòu)的具體成員,是驅(qū)使網(wǎng)絡(luò)設(shè)備硬件完成相應(yīng)動(dòng)作的程序,它通過hard_start_xmit函數(shù)啟動(dòng)發(fā)送操作,并通過網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作。
??(4)網(wǎng)絡(luò)設(shè)備與媒介層是完成數(shù)據(jù)包發(fā)送和接收的物理實(shí)體,包括網(wǎng)絡(luò)適配器和具體的傳輸媒介,網(wǎng)絡(luò)適配器被設(shè)備驅(qū)動(dòng)功能層中的函數(shù)在物理上驅(qū)動(dòng),對(duì)于linux系統(tǒng)而言,網(wǎng)絡(luò)設(shè)備和媒介都可以是虛擬的。
??在設(shè)計(jì)具體的網(wǎng)絡(luò)驅(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)核。
二、網(wǎng)絡(luò)協(xié)議接口層
??網(wǎng)絡(luò)協(xié)議接口層最主要的功能是給上層協(xié)議提供透明的數(shù)據(jù)包發(fā)送和接收接口。當(dāng)上層ARP或IP需要發(fā)送數(shù)據(jù)包時(shí),它將調(diào)用網(wǎng)絡(luò)接口層的dev_queue_xmit函數(shù)發(fā)送改數(shù)據(jù)包。同樣地,上層對(duì)數(shù)據(jù)包的接收也通過向netif_rx函數(shù)傳遞struct sk_buff數(shù)據(jù)結(jié)構(gòu)的指針來完成。
1、dev_queue_xmit 函數(shù)
此函數(shù)用于將網(wǎng)絡(luò)數(shù)據(jù)發(fā)送出去,函數(shù)定義在 include/linux/netdevice.h 中,函數(shù)原型如下:
static inline int dev_queue_xmit(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
??skb:要發(fā)送的數(shù)據(jù),這是一個(gè) sk_buff 結(jié)構(gòu)體指針,sk_buff 是 Linux 網(wǎng)絡(luò)驅(qū)動(dòng)中一個(gè)非常重要的結(jié)構(gòu)體,網(wǎng)絡(luò)數(shù)據(jù)就是以 sk_buff 保存的,各個(gè)協(xié)議層在 sk_buff 中添加自己的協(xié)議頭,最終由底層驅(qū)動(dòng)講 sk_buff 中的數(shù)據(jù)發(fā)送出去。網(wǎng)絡(luò)數(shù)據(jù)的接收過程恰好相反,網(wǎng)絡(luò)底層驅(qū)動(dòng)將接收到的原始數(shù)據(jù)打包成 sk_buff,然后發(fā)送給上層協(xié)議,上層會(huì)取掉相應(yīng)的頭部,然后將最終的數(shù)據(jù)發(fā)送給用戶。
??返回值:0 發(fā)送成功,負(fù)值發(fā)送失敗。
2、netif_rx 函數(shù)
??上層接收數(shù)據(jù)的話使用 netif_rx 函數(shù),但是最原始的網(wǎng)絡(luò)數(shù)據(jù)一般是通過輪詢、中斷或 NAPI的方式來接收。netif_rx 函數(shù)定義在 net/core/dev.c 中,函數(shù)原型如下:
int netif_rx(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
??skb:保存接收數(shù)據(jù)的 sk_buff。
??返回值:NET_RX_SUCCESS 成功,NET_RX_DROP 數(shù)據(jù)包丟棄。
struct sk_buff {
...
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
...
}
??sk_buff結(jié)構(gòu)體中,尤其注意的是,head和end指向緩沖區(qū)的頭部和尾部,而data和tail指向?qū)嶋H數(shù)據(jù)的頭部和尾部。
??針對(duì)sk_buff,內(nèi)核提供了一系列的操作與管理函數(shù),我們簡單看一些常見的 API 函數(shù):
3、分配 sk_buff
??要使用 sk_buff 必須先分配,首先來看一下 alloc_skb 這個(gè)函數(shù),此函數(shù)定義在include/linux/skbuff.h 中,函數(shù)原型如下:
static inline struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)
函數(shù)參數(shù)和返回值含義如下:
??size:要分配的大小,也就是 skb 數(shù)據(jù)段大小。
??priority:內(nèi)存分配優(yōu)先級(jí),為 GFP MASK 宏,比如 GFP_KERNEL、GFP_ATOMIC 等。
??返回值:分配成功的話就返回申請(qǐng)到的 sk_buff 首地址,失敗的話就返回 NULL。
??在網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)中常常使用 netdev_alloc_skb 來為某個(gè)設(shè)備申請(qǐng)一個(gè)用于接收的 skb_buff,此函數(shù)也定義在 include/linux/skbuff.h 中,函數(shù)原型如下:
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,unsigned int length)
函數(shù)參數(shù)和返回值含義如下:
??dev:要給哪個(gè)設(shè)備分配 sk_buff。
??length:要分配的大小。
??返回值:分配成功的話就返回申請(qǐng)到的 sk_buff 首地址,失敗的話就返回 NULL。
??注意的是,該函數(shù)內(nèi)存分配優(yōu)先級(jí)設(shè)置為GFP_ATOMIC。原因是該函數(shù)經(jīng)常在設(shè)備驅(qū)動(dòng)的接收中斷里被調(diào)用。
4、釋放 sk_buff
??當(dāng)使用完成以后就要釋放掉 sk_buff,釋放函數(shù)可以使用 kfree_skb,函數(shù)定義在include/linux/skbuff.c 中,函數(shù)原型如下:
void kfree_skb(struct sk_buff *skb)
函數(shù)參數(shù)和返回值含義如下:
??skb:要釋放的 sk_buff。
??返回值:無。
??對(duì)于網(wǎng)絡(luò)設(shè)備而言最好使用如下所示釋放函數(shù):
void dev_kfree_skb (struct sk_buff *skb)
??函數(shù)只要一個(gè)參數(shù) skb,就是要釋放的 sk_buff。
5、skb_put、skb_push、sbk_pull 和 skb_reserve
這四個(gè)函數(shù)用于變更 sk_buff,先來看一下 skb_put 函數(shù),此函數(shù)用于在尾部擴(kuò)展 skb_buff的數(shù)據(jù)區(qū),也就將 skb_buff 的 tail 后移 n 個(gè)字節(jié),從而導(dǎo)致 skb_buff 的 len 增加 n 個(gè)字節(jié),原型如下:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
??skb:要操作的 sk_buff。
??len:要增加多少個(gè)字節(jié)。
??返回值:擴(kuò)展出來的那一段數(shù)據(jù)區(qū)首地址。
skb_put 操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
skb_push 函數(shù)用于在頭部擴(kuò)展 skb_buff 的數(shù)據(jù)區(qū),函數(shù)原型如下所示:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
??skb:要操作的 sk_buff。
??len:要增加多少個(gè)字節(jié)。
??返回值:擴(kuò)展完成以后新的數(shù)據(jù)區(qū)首地址。
skb_push 操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
sbk_pull 函數(shù)用于從 sk_buff 的數(shù)據(jù)區(qū)起始位置刪除數(shù)據(jù),函數(shù)原型如下所示:
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
函數(shù)參數(shù)和返回值含義如下:
??skb:要操作的 sk_buff。
??len:要?jiǎng)h除的字節(jié)數(shù)。
??返回值:刪除以后新的數(shù)據(jù)區(qū)首地址。
skb_pull 操作之前和操作之后的數(shù)據(jù)區(qū)如下圖所示:
sbk_reserve 函數(shù)用于調(diào)整緩沖區(qū)的頭部大小,方法很簡單講 skb_buff 的 data 和 tail 同時(shí)后移 n 個(gè)字節(jié)即可,函數(shù)原型如下所示:
static inline void skb_reserve(struct sk_buff *skb, int len)
函數(shù)參數(shù)和返回值含義如下:
??skb:要操作的 sk_buff。
??len:要增加的緩沖區(qū)頭部大小。
??返回值:無。
三、網(wǎng)絡(luò)設(shè)備接口層
??網(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)萬變,實(shí)現(xiàn)多種硬件在軟件層次上的統(tǒng)一。
??net_device 結(jié)構(gòu)體在內(nèi)核中指代一個(gè)網(wǎng)絡(luò)設(shè)備,網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序只需要填充net_device 的具體成員并注冊(cè) net_device 即可實(shí)現(xiàn)硬件操作函數(shù)與內(nèi)核的掛接。
net_device 是一個(gè)龐大的結(jié)構(gòu)體,介紹一些關(guān)鍵成員:
成員 | 說明 |
---|---|
name | 網(wǎng)絡(luò)設(shè)備的名字 |
mem_end | 共享內(nèi)存結(jié)束地址 |
mem_start | 共享內(nèi)存起始地址 |
base_addr | 網(wǎng)絡(luò)設(shè)備 I/O 地址 |
irq | 網(wǎng)絡(luò)設(shè)備的中斷號(hào) |
netdev_ops | 網(wǎng)絡(luò)設(shè)備的操作集函數(shù),類似字符設(shè)備中的 file_operations |
ethtool_ops | 網(wǎng)絡(luò)管理工具相關(guān)函數(shù)集,用戶空間網(wǎng)絡(luò)管理工具會(huì)調(diào)用此結(jié)構(gòu)體中的相關(guān)函數(shù)獲取網(wǎng)卡狀態(tài)或者配置網(wǎng)卡 |
header_ops | 頭部的相關(guān)操作函數(shù)集,比如創(chuàng)建、解析、緩沖等 |
dma | 網(wǎng)絡(luò)設(shè)備所使用的 DMA 通道 |
mtu | 網(wǎng)絡(luò)最大傳輸單元 |
last_rx | 最后接收的數(shù)據(jù)包時(shí)間戳,記錄的是 jiffies |
dev_addr | 當(dāng)前分配的 MAC 地址,可以通過軟件修改 |
_rx | 接收隊(duì)列 |
num_rx_queues | 接收隊(duì)列數(shù)量,調(diào)用 register_netdev 時(shí)會(huì)分配指定數(shù)量的接收隊(duì)列 |
real_num_rx_queues | 當(dāng)前活動(dòng)的隊(duì)列數(shù)量 |
_tx | 發(fā)送隊(duì)列 |
num_tx_queues | 發(fā)送隊(duì)列數(shù)量,通過 alloc_netdev_mq 函數(shù)分配指定數(shù)量的發(fā)送隊(duì)列 |
real_num_tx_queues | 當(dāng)前有效的發(fā)送隊(duì)列數(shù)量 |
trans_start | 最后的數(shù)據(jù)包發(fā)送的時(shí)間戳,記錄的是 jiffies |
phydev | 對(duì)應(yīng)的 PHY 設(shè)備 |
1、申請(qǐng) net_device
編寫網(wǎng)絡(luò)驅(qū)動(dòng)的時(shí)候首先要申請(qǐng) net_device,使用 alloc_netdev 函數(shù)來申請(qǐng) net_device,這是一個(gè)宏,宏定義如下:
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
??alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
可以看出 alloc_netdev 的本質(zhì)是 alloc_netdev_mqs 函數(shù),此函數(shù)原型如下:
struct net_device * alloc_netdev_mqs ( int sizeof_priv, const char *name,
??????????????????void (*setup) (struct net_device *)),
??????????????????unsigned int txqs, unsigned int rxqs);
函數(shù)參數(shù)和返回值含義如下:
??sizeof_priv:私有數(shù)據(jù)塊大小。
??name:設(shè)備名字。
??setup:回調(diào)函數(shù),初始化設(shè)備的設(shè)備后調(diào)用此函數(shù)。
??txqs:分配的發(fā)送隊(duì)列數(shù)量。
??rxqs:分配的接收隊(duì)列數(shù)量。
??返回值:如果申請(qǐng)成功的話就返回申請(qǐng)到的 net_device 指針,失敗的話就返回 NULL。
??事實(shí)上網(wǎng)絡(luò)設(shè)備有多種,大家不要以為就只有以太網(wǎng)一種。Linux 內(nèi)核內(nèi)核支持的網(wǎng)絡(luò)接口有很多,比如光纖分布式數(shù)據(jù)接口(FDDI)、以太網(wǎng)設(shè)備(Ethernet)、紅外數(shù)據(jù)接口(InDA)、高性能并行接口(HPPI)、CAN 網(wǎng)絡(luò)等。內(nèi)核針對(duì)不同的網(wǎng)絡(luò)設(shè)備在 alloc_netdev 的基礎(chǔ)上提供了一層封裝,比如我們本章講解的以太網(wǎng),針對(duì)以太網(wǎng)封裝的 net_device 申請(qǐng)函數(shù)是 alloc_etherdev,這也是一個(gè)宏,內(nèi)容如下:
#define alloc_etherdev(sizeof_priv)??alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count)??alloc_etherdev_mqs(sizeof_priv, count, count)
??可以看出,alloc_etherdev 最終依靠的是 alloc_etherdev_mqs 函數(shù),此函數(shù)就是對(duì)alloc_netdev_mqs 的簡單封裝,函數(shù)內(nèi)容如下:
struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, unsigned int rxqs)
{
return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN, ether_setup, txqs, rxqs);
}
??調(diào)用 alloc_netdev_mqs 來申請(qǐng) net_device,注意這里設(shè)置網(wǎng)卡的名字為“eth%d”,這是格式化字符串,大家進(jìn)入開發(fā)板的 linux 系統(tǒng)以后看到的“eth0”、“eth1”這樣的網(wǎng)卡名字就是從這里來的。同樣的,這里設(shè)置了以太網(wǎng)的 setup 函數(shù)為 ether_setup,不同的網(wǎng)絡(luò)設(shè)備其 setup函數(shù)不同,比如 CAN 網(wǎng)絡(luò)里面 setup 函數(shù)就是 can_setup。
ether_setup 函數(shù)會(huì)對(duì) net_device 做初步的初始化,函數(shù)內(nèi)容如下所示:
void ether_setup(struct net_device *dev)
{
dev->header_ops = ð_header_ops;
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN;
dev->mtu = ETH_DATA_LEN;
dev->addr_len = ETH_ALEN;
dev->tx_queue_len = 1000; /* Ethernet wants good queues */
dev->flags = IFF_BROADCAST|IFF_MULTICAST;
dev->priv_flags |= IFF_TX_SKB_SHARING;
eth_broadcast_addr(dev->broadcast);
}
2、刪除 net_device
當(dāng)我們注銷網(wǎng)絡(luò)驅(qū)動(dòng)的時(shí)候需要釋放掉前面已經(jīng)申請(qǐng)到的 net_device,釋放函數(shù)為free_netdev,函數(shù)原型如下:
void free_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
??dev:要釋放掉的 net_device 指針。
??返回值:無。
3、注冊(cè) net_device
net_device 申請(qǐng)并初始化完成以后就需要向內(nèi)核注冊(cè) net_device,要用到函數(shù) register_netdev,函數(shù)原型如下:
int register_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
??dev:要注冊(cè)的 net_device 指針。
??返回值:0 注冊(cè)成功,負(fù)值 注冊(cè)失敗。
4、注銷 net_device
既然有注冊(cè),那么必然有注銷,注銷 net_device 使用函數(shù) unregister_netdev,函數(shù)原型如下:
void unregister_netdev(struct net_device *dev)
函數(shù)參數(shù)和返回值含義如下:
??dev:要注銷的 net_device 指針。
??返回值:無。
四、網(wǎng)絡(luò) NAPI 處理機(jī)制
??NAPI 是一種高效的網(wǎng)絡(luò)處理技術(shù)。NAPI 的核心思想就是不全部采用中斷來讀取網(wǎng)絡(luò)數(shù)據(jù),而是采用中斷來喚醒數(shù)據(jù)接收服務(wù)程序,在接收服務(wù)程序中采用 POLL 的方法來輪詢處理數(shù)據(jù)。這種方法的好處就是可以提高短數(shù)據(jù)包的接收效率,減少中斷處理的時(shí)間。目前 NAPI 已經(jīng)在 Linux 的網(wǎng)絡(luò)驅(qū)動(dòng)中得到了大量的應(yīng)用,NXP 官方編寫的網(wǎng)絡(luò)驅(qū)動(dòng)都是采用的 NAPI 機(jī)制。
1、初始化 NAPI
首先要初始化一個(gè) napi_struct 實(shí)例,使用 netif_napi_add 函數(shù),此函數(shù)定義在 net/core/dev.c中,函數(shù)原型如下:
void netif_napi_add(struct net_device *dev, struct napi_struct *napi,int (*poll)(struct napi_struct *, int), int weight)
函數(shù)參數(shù)和返回值含義如下:
??dev:每個(gè) NAPI 必須關(guān)聯(lián)一個(gè)網(wǎng)絡(luò)設(shè)備,此參數(shù)指定 NAPI 要關(guān)聯(lián)的網(wǎng)絡(luò)設(shè)備。
??napi:要初始化的 NAPI 實(shí)例。
??poll:NAPI 所使用的輪詢函數(shù),非常重要,一般在此輪詢函數(shù)中完成網(wǎng)絡(luò)數(shù)據(jù)接收的工作。
??weight:NAPI 默認(rèn)權(quán)重(weight),一般為 NAPI_POLL_WEIGHT。
??返回值:無。
2、刪除 NAPI
如果要?jiǎng)h除 NAPI,使用 netif_napi_del 函數(shù)即可,函數(shù)原型如下:
void netif_napi_del(struct napi_struct *napi)
函數(shù)參數(shù)和返回值含義如下:
??napi:要?jiǎng)h除的 NAPI。
??返回值:無。
3、使能 NAPI
初始化完 NAPI 以后,必須使能才能使用,使用函數(shù) napi_enable,函數(shù)原型如下:
inline void napi_enable(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
??n:要使能的 NAPI。
??返回值:無。
4、關(guān)閉 NAPI
關(guān)閉 NAPI 使用 napi_disable 函數(shù)即可,函數(shù)原型如下:
void napi_disable(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
??n:要關(guān)閉的 NAPI。
??返回值:無。
5、檢查 NAPI 是否可以進(jìn)行調(diào)度
使用 napi_schedule_prep 函數(shù)檢查 NAPI 是否可以進(jìn)行調(diào)度,函數(shù)原型如下:
inline bool napi_schedule_prep(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
??n:要檢查的 NAPI。
??返回值:如果可以調(diào)度就返回真,如果不可調(diào)度就返回假。
6、NAPI 調(diào)度
如果可以調(diào)度的話就進(jìn)行調(diào)度,使用__napi_schedule 函數(shù)完成 NAPI 調(diào)度,函數(shù)原型如下:
void __napi_schedule(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
??n:要調(diào)度的 NAPI。
??返回值:無。
??我們也可以使用 napi_schedule 函數(shù)來一次完成 napi_schedule_prep 和__napi_schedule 這兩個(gè)函數(shù)的工作,napi_schedule 函數(shù)內(nèi)容如下所示:
static inline void napi_schedule(struct napi_struct *n)
{
if (napi_schedule_prep(n))
__napi_schedule(n);
}
??從示例代碼可以看出,napi_schedule 函數(shù)就是對(duì)napi_schedule_prep和__napi_schedule 的簡單封裝,一次完成判斷和調(diào)度。
7、NAPI 處理完成
NAPI 處理完成以后需要調(diào)用 napi_complete 函數(shù)來標(biāo)記 NAPI 處理完成,函數(shù)原型如下:
inline void napi_complete(struct napi_struct *n)
函數(shù)參數(shù)和返回值含義如下:
??n:處理完成的 NAPI。
??返回值:無。
五、設(shè)備驅(qū)動(dòng)功能層
??net_device結(jié)構(gòu)體的成員(屬性和net_device_ops結(jié)構(gòu)體中的函數(shù)指針)需要被設(shè)備驅(qū)動(dòng)功能層賦予具體的數(shù)值和函數(shù)。對(duì)于具體的設(shè)備xxx,應(yīng)該編寫相應(yīng)的設(shè)備驅(qū)動(dòng)功能層函數(shù),這些函數(shù)如xxx_open()、xxx_stop()等。
??net_device_ops 結(jié)構(gòu)體定義在 include/linux/netdevice.h 文件中,net_device_ops 結(jié)構(gòu)體里面都是一些以“ndo_”開頭的函數(shù),這些函數(shù)就需要網(wǎng)絡(luò)驅(qū)動(dòng)編寫人員去實(shí)現(xiàn),不需要全部都實(shí)現(xiàn),根據(jù)實(shí)際驅(qū)動(dòng)情況實(shí)現(xiàn)其中一部分即可。結(jié)構(gòu)體內(nèi)容如下所示(結(jié)構(gòu)體比較大,這里有縮減):
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
void (*ndo_uninit)(struct net_device *dev);
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb,void *accel_priv,select_queue_fallback_t fallback);
void (*ndo_change_rx_flags)(struct net_device *dev,int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
int (*ndo_set_config)(struct net_device *dev,struct ifmap *map);
int (*ndo_change_mtu)(struct net_device *dev,int new_mtu);
int (*ndo_neigh_setup)(struct net_device *dev,
struct neigh_parms *);
void (*ndo_tx_timeout) (struct net_device *dev);
......
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*ndo_poll_controller)(struct net_device *dev);
int (*ndo_netpoll_setup)(struct net_device *dev,
struct netpoll_info *info);
void (*ndo_netpoll_cleanup)(struct net_device *dev);
#endif
......
int (*ndo_set_features)(struct net_device *dev,netdev_features_t features);
......
};
??fec_probe 函數(shù)設(shè)置了網(wǎng)卡驅(qū)動(dòng)的 net_dev_ops 操作集為 fec_netdev_ops,fec_netdev_ops 內(nèi)容如下:
static const struct net_device_ops fec_netdev_ops = {
.ndo_open = fec_enet_open,
.ndo_stop = fec_enet_close,
.ndo_start_xmit = fec_enet_start_xmit,
.ndo_select_queue = fec_enet_select_queue,
.ndo_set_rx_mode = set_multicast_list,
.ndo_change_mtu = eth_change_mtu,
ndo_validate_addr = eth_validate_addr,
ndo_tx_timeout = fec_timeout,
.ndo_set_mac_address = fec_set_mac_address,
.ndo_do_ioctl = fec_enet_ioctl,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = fec_poll_controller,
#endif
.ndo_set_features = fec_set_features,
};
1、fec_enet_open 函數(shù)簡析
打開一個(gè)網(wǎng)卡的時(shí)候 fec_enet_open 函數(shù)就會(huì)執(zhí)行,函數(shù)源碼如下所示(限于篇幅原因,有省略):
static int fec_enet_open(struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
const struct platform_device_id *id_entry = platform_get_device_id(fep->pdev);
int ret;
pinctrl_pm_select_default_state(&fep->pdev->dev);
ret = fec_enet_clk_enable(ndev, true);
if (ret)
return ret;
/* I should reset the ring buffers here, but I don't yet know
* a simple way to do that.
*/
ret = fec_enet_alloc_buffers(ndev);
if (ret)
goto err_enet_alloc;
/* Init MAC prior to mii bus probe */
fec_restart(ndev);
/* Probe and connect to PHY when open the interface */
ret = fec_enet_mii_probe(ndev);
if (ret)
goto err_enet_mii_probe;
napi_enable(&fep->napi);
phy_start(fep->phy_dev);
netif_tx_start_all_queues(ndev);
......
return 0;
err_enet_mii_probe:
fec_enet_free_buffers(ndev);
err_enet_alloc:
fep->miibus_up_failed = true;
if (!fep->mii_bus_share)
pinctrl_pm_select_sleep_state(&fep->pdev->dev);
return ret;
}
第 9 行,調(diào)用 fec_enet_clk_enable 函數(shù)使能 enet 時(shí)鐘。
第 17 行,調(diào)用 fec_enet_alloc_buffers 函數(shù)申請(qǐng)環(huán)形緩沖區(qū) buffer,此函數(shù)里面會(huì)調(diào)用fec_enet_alloc_rxq_buffers 和 fec_enet_alloc_txq_buffers 這兩個(gè)函數(shù)分別實(shí)現(xiàn)發(fā)送隊(duì)列和接收隊(duì)列緩沖區(qū)的申請(qǐng)。
第 22 行,重啟網(wǎng)絡(luò),一般連接狀態(tài)改變、傳輸超時(shí)或者配置網(wǎng)絡(luò)的時(shí)候都會(huì)調(diào)用 fec_restart函數(shù)。
第 25 行,打開網(wǎng)卡的時(shí)候調(diào)用 fec_enet_mii_probe 函數(shù)來探測并連接對(duì)應(yīng)的 PHY 設(shè)備。
第 29 行,調(diào)用 napi_enable 函數(shù)使能 NAPI 調(diào)度。
第 30 行,調(diào)用 phy_start 函數(shù)開啟 PHY 設(shè)備。
第 31 行,調(diào)用 netif_tx_start_all_queues 函數(shù)來激活發(fā)送隊(duì)列。
2、fec_enet_close 函數(shù)簡析
關(guān)閉網(wǎng)卡的時(shí)候 fec_enet_close 函數(shù)就會(huì)執(zhí)行,函數(shù)內(nèi)容如下:
static int fec_enet_close(struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
phy_stop(fep->phy_dev);
if (netif_device_present(ndev))
{
napi_disable(&fep->napi);
netif_tx_disable(ndev);
fec_stop(ndev);
}
phy_disconnect(fep->phy_dev);
fep->phy_dev = NULL;
fec_enet_clk_enable(ndev, false);
pm_qos_remove_request(&fep->pm_qos_req);
pinctrl_pm_select_sleep_state(&fep->pdev->dev);
pm_runtime_put_sync_suspend(ndev->dev.parent);
fec_enet_free_buffers(ndev);
return 0;
}
第 5 行,調(diào)用 phy_stop 函數(shù)停止 PHY 設(shè)備。
第 8 行,調(diào)用 napi_disable 函數(shù)關(guān)閉 NAPI 調(diào)度。
第 9 行,調(diào)用 netif_tx_disable 函數(shù)關(guān)閉 NAPI 的發(fā)送隊(duì)列。
第 10 行,調(diào)用 fec_stop 函數(shù)關(guān)閉 I.MX6ULL 的 ENET 外設(shè)。
第 13 行,調(diào)用 phy_disconnect 函數(shù)斷開與 PHY 設(shè)備的連接。
第 16 行,調(diào)用 fec_enet_clk_enable 函數(shù)關(guān)閉 ENET 外設(shè)時(shí)鐘。
第 20 行,調(diào)用 fec_enet_free_buffers 函數(shù)釋放發(fā)送和接收的環(huán)形緩沖區(qū)內(nèi)存。
3、fec_enet_start_xmit 函數(shù)簡析
I.MX6ULL 的網(wǎng)絡(luò)數(shù)據(jù)發(fā)送是通過 fec_enet_start_xmit 函數(shù)來完成的,這個(gè)函數(shù)將上層傳遞過來的 sk_buff 中的數(shù)據(jù)通過硬件發(fā)送出去,函數(shù)源碼如下:
static netdev_tx_t fec_enet_start_xmit(struct sk_buff *skb,struct net_device *ndev)
{
struct fec_enet_private *fep = netdev_priv(ndev);
int entries_free;
unsigned short queue;
struct fec_enet_priv_tx_q *txq;
struct netdev_queue *nq;
int ret;
queue = skb_get_queue_mapping(skb);
txq = fep->tx_queue[queue];
nq = netdev_get_tx_queue(ndev, queue);
if (skb_is_gso(skb))
ret = fec_enet_txq_submit_tso(txq, skb, ndev);
else
ret = fec_enet_txq_submit_skb(txq, skb, ndev);
if (ret)
return ret;
entries_free = fec_enet_get_free_txdesc_num(fep, txq);
if (entries_free <= txq->tx_stop_threshold)
netif_tx_stop_queue(nq);
return NETDEV_TX_OK;
}
此函數(shù)的參數(shù)第一個(gè)參數(shù) skb 就是上層應(yīng)用傳遞下來的要發(fā)送的網(wǎng)絡(luò)數(shù)據(jù),第二個(gè)參數(shù)ndev 就是要發(fā)送數(shù)據(jù)的設(shè)備。
第 14 行,判斷 skb 是否為 GSO(Generic Segmentation Offload),如果是 GSO 的話就通過fec_enet_txq_submit_tso 函數(shù)發(fā)送,如果不是的話就通過 fec_enet_txq_submit_skb 發(fā)送。這里簡單講一下 TSO 和 GSO:
TSO:全稱是 TCP Segmentation Offload,利用網(wǎng)卡對(duì)大數(shù)據(jù)包進(jìn)行自動(dòng)分段處理,降低 CPU負(fù)載。
GSO:全稱是 Generic Segmentation Offload,在發(fā)送數(shù)據(jù)之前先檢查一下網(wǎng)卡是否支持 TSO,如果支持的話就讓網(wǎng)卡分段,不過不支持的話就由協(xié)議棧進(jìn)行分段處理,分段處理完成以后再交給網(wǎng)卡去發(fā)送。
第 21 行,通過 fec_enet_get_free_txdesc_num 函數(shù)獲取剩余的發(fā)送描述符數(shù)量。
第 23 行,如果剩余的發(fā)送描述符的數(shù)量小于設(shè)置的閾值(tx_stop_threshold)的話就調(diào)用函數(shù)netif_tx_stop_queu 來暫停發(fā)送,通過暫停發(fā)送來通知應(yīng)用層停止向網(wǎng)絡(luò)發(fā)送 skb,發(fā)送中斷中會(huì)重新開啟的。
4、fec_enet_interrupt 中斷服務(wù)函數(shù)簡析
前面說了 I.MX6ULL 的網(wǎng)絡(luò)數(shù)據(jù)接收采用 NAPI 框架,所以肯定要用到中斷。fec_probe 函數(shù)會(huì)初始化網(wǎng)絡(luò)中斷,中斷服務(wù)函數(shù)為 fec_enet_interrupt,函數(shù)內(nèi)容如下:
static irqreturn_t fec_enet_interrupt(int irq, void *dev_id)
{
struct net_device *ndev = dev_id;
struct fec_enet_private *fep = netdev_priv(ndev);
uint int_events;
irqreturn_t ret = IRQ_NONE;
int_events = readl(fep->hwp + FEC_IEVENT);
writel(int_events, fep->hwp + FEC_IEVENT);
fec_enet_collect_events(fep, int_events);
if ((fep->work_tx || fep->work_rx) && fep->link)
{
ret = IRQ_HANDLED;
if (napi_schedule_prep(&fep->napi))
{
/* Disable the NAPI interrupts */
writel(FEC_ENET_MII, fep->hwp + FEC_IMASK);
__napi_schedule(&fep->napi);
}
}
if (int_events & FEC_ENET_MII)
{
ret = IRQ_HANDLED;
complete(&fep->mdio_done);
}
if (fep->ptp_clock)
fec_ptp_check_pps_event(fep);
return ret;
}
??可以看出中斷服務(wù)函數(shù)非常短!而且也沒有見到有關(guān)數(shù)據(jù)接收的處理過程,那是因?yàn)镮.MX6ULL 的網(wǎng)絡(luò)驅(qū)動(dòng)使用了 NAPI,具體的網(wǎng)絡(luò)數(shù)據(jù)收發(fā)是在 NAPI 的 poll 函數(shù)中完成的,中斷里面只需要進(jìn)行 napi 調(diào)度即可,這個(gè)就是中斷的上半部和下半部處理機(jī)制。
第 8 行,讀取 NENT 的中斷狀態(tài)寄存器 EIR,獲取中斷狀態(tài),
第 9 行,將第 8 行獲取到的中斷狀態(tài)值又寫入 EIR 寄存器,用于清除中斷狀態(tài)寄存器。
第 10 行,調(diào)用 fec_enet_collect_events 函數(shù)統(tǒng)計(jì)中斷信息,也就是統(tǒng)計(jì)都發(fā)生了哪些中斷。fep 中成員變量 work_tx 和 work_rx 的 bit0、bit1 和 bit2 用來做不同的標(biāo)記,work_rx 的 bit2 表示接收到數(shù)據(jù)幀,work_tx 的 bit2 表示發(fā)送完數(shù)據(jù)幀。
第 15 行,調(diào)用 napi_schedule_prep 函數(shù)檢查 NAPI 是否可以進(jìn)行調(diào)度。
第 17 行,如果使能了相關(guān)中斷就要先關(guān)閉這些中斷,向 EIMR 寄存器的 bit23 寫 1 即可關(guān)閉相關(guān)中斷。
第 18 行,調(diào)用__napi_schedule函數(shù)來啟動(dòng) NAPI 調(diào)度,這個(gè)時(shí)候 napi 的 poll 函數(shù)就會(huì)執(zhí)行,在本網(wǎng)絡(luò)驅(qū)動(dòng)中就是 fec_enet_rx_napi 函數(shù)。
__napi_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ù)包。
fec_enet_init 函數(shù)初始化網(wǎng)絡(luò)的時(shí)候會(huì)調(diào)用 netif_napi_add 來設(shè)置 NAPI 的 poll 函數(shù)為 fec_enet_rx_napi,函數(shù)內(nèi)容如下:文章來源:http://www.zghlxwxcb.cn/news/detail-415862.html
static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
{
struct net_device *ndev = napi->dev;
struct fec_enet_private *fep = netdev_priv(ndev);
int pkts;
pkts = fec_enet_rx(ndev, budget);
fec_enet_tx(ndev);
if (pkts < budget) {
napi_complete(napi);
writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
}
return pkts;
}
第 7 行,調(diào)用 fec_enet_rx 函數(shù)進(jìn)行真正的數(shù)據(jù)接收。
第 9 行,調(diào)用 fec_enet_tx 函數(shù)進(jìn)行數(shù)據(jù)發(fā)送。
第 12 行,調(diào)用 napi_complete 函數(shù)來宣布一次輪詢結(jié)束,
第 13 行,設(shè)置 ENET 的 EIMR 寄存器,重新使能中斷。文章來源地址http://www.zghlxwxcb.cn/news/detail-415862.html
到了這里,關(guān)于I.MX6ULL ARM驅(qū)動(dòng)開發(fā)---網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!