Linux網卡驅動


<網絡知識>

a:網絡模型
              OSI模型               TCP模型
雖然OSI模型看着挺完美的,但是過於復雜,這樣就會導致不實用,在Linux系 統中用的是TCP模型。每一層都是一種協議,系統對要發送的數據進行層層數據封裝,就像洋蔥層層刨去還原數據。
 
<Linux網絡驅動特點>
a:對於網絡接口的常用文件操作(讀,寫等)是沒有意義,在Linux系統中,網絡驅動是唯一一個無法體現UNIX/linux 的一切皆是文件的思想,因為網絡設備咩有對應的設備文件。並且網絡接口函數都存在於自己的名字空間中(設備結構體中,有相應的指向ops的指針)。但是注意,當應用程序使用套接字的時候,任然使用"read""write"的調用但是這些調用作用於軟件對象上,他們與網絡接口完全不同。
 
<網絡數據發送過程>
a:示意圖如圖所示
(1) 從應用層 write()->sock_write() (net/socket.c)->inet_write() (net/inet/af_inet.c)->tcp_write() (net/inet/tcp.c)真正處理。 前兩個如前所說只是作一些檢查。在傳輸層進行處理所原因在於:數據的封裝只有在傳輸層才行。
 
(2)tcp_write()函數完成數據的封裝,將用戶緩沖區賦值到內核緩沖區封裝到sk_buff結構。如果網絡擁塞,則暫時緩存到 write_queue隊列中,稍后發送;否則則可以不經過write_queue直接發送出去。傳輸層協議調用ip_queue_xmit()函數將數 據包發送到網絡層進行處理。
 
(3)ip_queue_xmit()函數 對數據幀進行完善,調用dev_queue_xmit()函數將數據包送往鏈路層進行處理。同時將此數據包緩存到sock_send隊列,保證可靠傳輸, 此時,數據包已經從write_queue中刪除。write_queue:從用戶層結束新的數據包,沒有用ip_queue_xmit傳輸出去。而 send_queue則調用了ip_queue_xmit傳輸。ip_queue_xmit()直接調用dev_queue_xmit()函數進行發送。
 
(4)dev_queue_xmit()完成本層處理后,調用設備結構device的hard_start_xmit函數指針指向的具體硬件發送函數。對於 NE系列網絡設備的ei_start_xmit()函數,其首先將數據從內核賦值到網卡設備硬件緩沖區,操作具體的硬件寄存器,最終完成發送。
 
<網卡驅動中的一些重要數據結構>
a:net_device{}中的部分重要數據
stuct net_device
{
//全局信息
char name[IFNAMSIZ];
/* 設備名子. 如果名子由驅動設置, 包含一個 %d 格式串,register_netdev用一個數替換它來形成一個唯一的名子; 分配的編號從0 開始.*/
unsigned long state;
/* 設備狀態. 這個成員包括幾個標志. 驅動正常情況下不直接操作這些標 志;相反, 提供了一套實用函數. 這些函數在我們進入驅動操作后馬上討論這些函數.*/
struct net_device *next; 
/* 全局列表中指向下一個設備的指針. 這個成員驅動不能動*/
int (*init)(struct net_device *dev);
/* 一個初始化函數. 如果設置了這個指針, 這個函數被 register_netdev調用來完成對 net_device結構的初始化. 大部分現代的網絡驅動不再使用這個函數; 相反, 初始化在注冊接口前進行.*/
//硬件信息
unsigned long rmem_end;
unsigned long rmem_start;
unsigned long mem_end;
unsigned long mem_start;

/*設備內存信息. 這些成員持有設備使用的共享內存的開始和結束地址. 如果設備有不同的接收和發送內存, mem 成員由發送內存使用, rmem 成員接收內存使用. rmem 成員在驅動之外從不被引用. 慣例上, 設置 end 成員, 所以 end - start 是可用的板上內存的數量.*/
unsigned long base_addr;
/*網絡接口的 I/O 基地址. 這個成員, 如同前面的, 由驅動在設備探測時賦值. ifconfig 目可用來顯示或修改當前值. base_addr 可以當系統啟動時在內核命令行中顯式賦值( 通過 netdev=參數), 或者在模塊加載時.這個成員, 象上面描述過的內存成員, 內核不使用它們.*/
unsigned char irq;
/*安排的中斷號. 當接口被列出時 ifconfig 打印出 dev->irq 的值. 這個值常常在啟動或者加載時間設置並且在后來由 ifconfig 打印.*/
unsigned char if_port;
/*在多端口設備中使用的端口. 例如, 這個成員用在同時支持同軸線(IF_PORT_10BASE2)和雙絞線(IF_PORT_100BSAET)以太網連接. 完整的已知端口類型設置定義在 <linux/netdevie.h>.*/
unsigned char dma;
/*設備分配的 DMA 通道. 這個成員只在某些外設總線時有意義, 例如 ISA.它不在設備驅動自身以外使用, 只是為了信息目的( 在 ifconfig ) 中.*/
//接口信息
/*有關接口的大部分信息由 ether_setup 函數正確設置(或者任何其他對給定硬件類型適合的設置函數). 以太網卡可以依賴這個通用的函數設置大部分這些成員,但是 flags 和 dev_addr 成員是特定設備的, 必須在初始化時間明確指定.一些非以太網接口可以使用類似 ether_setup 的幫助函數.*/
void ltalk_setup(struct net_device *dev);
/*設置一個 LocalTalk 設備的成員*/
void fc_setup(struct net_device *dev);
/*初始化光通道設備的成員*/
void fddi_setup(struct net_device *dev);
/*配置一個光纖分布數據接口 (FDDI) 網絡的接口*/
void hippi_setup(struct net_device *dev);
/*預備給一個高性能並行接口 (HIPPI) 的高速互連驅動的成員*/
void tr_setup(struct net_device *dev);
/*處理令牌環網絡接口的設置大部分設備會歸於這些類別中的一類. 如果你的是全新和不同的, 但是, 你需要手工賦值下面的成員*/
unsigned short hard_header_len;
/*硬件頭部長度, 就是, 被發送報文前面在 IP 頭之前的字節數, 或者別的協議信息. 對於以太網接口 hard_header_len 值是 14 (ETH_HLEN).
unsigned mtu;
/*最大傳輸單元 (MTU). 這個成員是網絡層用作驅動報文傳輸. 以太網有一個 1500 字節的 MTU (ETH_DATA_LEN). 這個值可用 ifconfig 改變.*/
unsigned long tx_queue_len;
/*設備發送隊列中可以排隊的最大幀數. 這個值由 ether_setup 設置為1000, 但是你可以改它. 例如, plip 使用 10 來避免浪費系統內存( 相比真實以太網接口, plip 有一個低些的吞吐量).*/
unsigned short type;
/*接口的硬件類型. 這個 type 成員由 ARP 用來決定接口支持什么樣的硬件地址. 對以太網接口正確的值是 ARPHRD_ETHER, 這是由 ether_setup設置的值. 可認識的類型定義於 <linux/if_arp.h>.*/
unsigned char addr_len;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char dev_addr[MAX_ADDR_LEN];

/*硬件 (MAC) 地址長度和設備硬件地址. 以太網地址長度是 6 個字節( 我們指的是接口板的硬件 ID ), 廣播地址由 6 個 0xff 字節組成;ether_setup 安排成正確的值. 設備地址, 另外, 必須以特定於設備的方式從接口板讀出, 驅動應當將它拷貝到 dev_addr. 硬件地址用來產生正確的以太網頭, 在報文傳遞給驅動發送之前. snull 設備不使用物理接口,它創造自己的硬件接口.*/
unsigned short flags;
int features;
/*接口標志(下面詳述)這個 flags 成員是一個位掩碼, 包括下面的位值. IFF_ 前綴代表 "interfaceflags". 有些標志由內核管理, 有些由接口在初始化時設置來表明接口的能力和其他特性. 有效的標志, 對應於 <linux/if.h>(詳情見/linux內核驅動/網絡接口標志*/
 
//設備方法
如同在字符和塊驅動的一樣, 每個網絡設備聲明能操作它的函數. 本節列出能夠對網絡接口進行的操作. 有些操作可以留作 NULL, 別的常常是不被觸動的, 因為 ether_setup 給它們安排了合適的方法.網絡接口的設備方法可分為 2 組: 基本的和可選的. 基本方法包括那些必需的
能夠使用接口的; 可選的方法實現更多高級的不是嚴格要求的功能.
*/
基本方法:
int (*open)(struct net_device *dev);
/*打開接口. 任何時候 ifconfig 激活它, 接口被打開. open 方法應當注冊它需要的任何系統資源( I/O 口, IRQ, DMA, 等等), 打開硬件, 進行任何別的你的設備要求的設置.*/
int (*stop)(struct net_device *dev);
/*停止接口. 接口停止當它被關閉. 這個函數應當恢復在打開時進行的操作.*/
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
/*起始報文的發送的方法. 完整的報文(協議頭和所有)包含在一個 socket緩存區( sk_buff ) 結構. socket 緩存在本章后面介紹. */
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,void *saddr, unsigned len);
/*用之前取到的源和目的硬件地址來建立硬件頭的函數(在hard_start_xmit 前調用). 它的工作是將作為參數傳給它的信息組織成一個合適的特定於設備的硬件頭. eth_header 是以太網類型接口的缺省函數, ether_setup 針對性地對這個成員賦值.*/
int (*rebuild_header)(struct sk_buff *skb);
/*用來在 ARP 解析完成后但是在報文發送前重建硬件頭的函數. 以太網設備使用的缺省的函數使用 ARP 支持代碼來填充報文缺失的信息.*/
void (*tx_timeout)(struct net_device *dev);
/*由網絡代碼在一個報文發送沒有在一個合理的時間內完成時調用的方法,可能是丟失一個中斷或者接口被鎖住. 它應當處理這個問題並恢復報文發送.*/
struct net_device_stats *(*get_stats)(struct net_device *dev);
/*任何時候當一個應用程序需要獲取接口的統計信息, 調用這個方法. 例如,當 ifconfig 或者 netstat -i 運行時. snull 的一個例子實現在"統計信息"一節中介紹.*/
int (*set_config)(struct net_device *dev, struct ifmap *map);
/*改變接口配置. 這個方法是配置驅動的入口點. 設備的 I/O 地址和中斷號可以在運行時使用 set_config 來改變. 這種能力可由系統管理員在接口沒有探測到時使用. 現代硬件正常的驅動一般不需要實現這個方法. */
 
可選操作:
int weight;
int (*poll)(struct net_device *dev; int *quota);

/*由適應 NAPI 的驅動提供的方法, 用來在查詢模式下操作接口, 中斷關閉着. NAPI ( 以及 weight 成員) 在"接收中斷緩解"一節中涉及.*/
void (*poll_controller)(struct net_device *dev);
/*在中斷關閉的情況下, 要求驅動檢查接口上的事件的函數. 它用於特殊的內核中的網絡任務, 例如遠程控制台和使用網絡的內核調試.*/
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
/*處理特定於接口的 ioctl 命令. (這些命令的實現在"定制 ioclt 命令"一節中描述)相應的 net_device 結構中的成員可留為 NULL, 如果接口不需要任何特定於接口的命令.*/
void (*set_multicast_list)(struct net_device *dev);
/*當設備的組播列表改變和當標志改變時調用的方法. */
int (*set_mac_address)(struct net_device *dev, void *addr);
/*如果接口支持改變它的硬件地址的能力, 可以實現這個函數. 很多接口根本不支持這個能力. 其他的使用缺省的 eth_mac_adr 實現(在deivers/net/net_init.c). eth_mac_addr 只拷貝新地址到dev->dev_addr, 只在接口沒有運行時作這件事. 使用 eth_mac_addr 的驅動應當在它們的 open 方法中自 dev->dev_addr 里設置硬件 MAC 地址.*/
int (*change_mtu)(struct net_device *dev, int new_mtu);
/*當接口的最大傳輸單元 (MTU) 改變時動作的函數. 如果用戶改變 MTU 時驅動需要做一些特殊的事情, 它應當聲明它的自己的函數; 否則, 缺省的會將事情做對. snull 有對這個函數的一個模板, 如果你有興趣.*/
int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);
/*header_cache 被調用來填充 hh_cache 結構, 使用一個 ARP 請求的結果.幾乎全部類似以太網的驅動可以使用缺省的 eth_header_cache 實現.*/
int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);
/*在響應一個變化中, 更新 hh_cache 結構中的目的地址的方法. 以太網設備使用 eth_header_cache_update.*/
int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);
/*hard_header_parse 方法從包含在 skb 中的報文中抽取源地址, 拷貝到haddr 的緩存區. 函數的返回值是地址的長度. 以太網設備通常使用eth_header_parse.*/
公用成員
/*結構 net_device 剩下的數據成員由接口使用來持有有用的狀態信息. 有些是ifconfig 和 netstat 用來提供給用戶關於當前配置的信息. 因此, 接口應當給這些成員賦值: */
unsigned long trans_start;
unsigned long last_rx;

/*保存一個 jiffy 值的成員. 驅動負責分別更新這些值, 當開始發送和收到一個報文時. trans_start 值被網絡子系統用來探測發送器加鎖.last_rx 目前沒有用到, 但是驅動應當盡量維護這個成員以備將來使用.*/
int watchdog_timeo;
/*網絡層認為一個傳送超時發生前應當過去的最小時間(按 jiffy 計算),調用驅動的 tx_timeout 函數.*/
void *priv;
/*filp->private_data 的對等者. 在現代的驅動里, 這個成員由alloc_netdev 設置, 不應當直接存取; 使用 netdev_priv 代替.*/
struct dev_mc_list *mc_list;
int mc_count;
/*處理組播發送的成員. mc_count 是 mc_list 中的項數目. 更多細節見"組播"一節.*/
spinlock_t xmit_lock;
int xmit_lock_owner;

/*xmit_lock 用來避免對驅動的 hard_start_xmit 函數多個同時調用.xmit_lock_owner 是已獲得 xmit_lock 的 CPU 號. 驅動應當不改變這些成員的值.結構 net_device 中有其他的成員, 但是網絡驅動用不着它們.*/  
}   
 

b:struct sk_buff{}

Linux系統中每個網絡數據包都由一個套接字緩沖結構struct sk_buff 描述,即一個sk_buff結構就是一個網絡包指向sk_buff指針通常被稱作skb

struct sk_buff {

struct sk_buff*next;

struct sk_buff*prev;

ktime_ttstamp;

struct sock*sk;

struct net_device*dev;

charcb[48] __aligned(8);

unsigned long_skb_refdst;

#ifdef CONFIG_XFRM

structsec_path*sp;

#endif

unsigned intlen,

data_len;

__u16mac_len,

hdr_len;

union {

__wsumcsum;

struct {

__u16csum_start;

__u16csum_offset;

};

};

__u32priority;

kmemcheck_bitfield_begin(flags1);

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1,

peeked:1,

nf_trace:1;

kmemcheck_bitfield_end(flags1);

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack*nfct;

#endif

#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED

struct sk_buff*nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

intskb_iif;

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

__u32rxhash;

kmemcheck_bitfield_begin(flags2);

__u16queue_mapping:16;

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8ndisc_nodetype:2;

#endif

__u8ooo_okay:1;

kmemcheck_bitfield_end(flags2);

#ifdef CONFIG_NET_DMA

dma_cookie_tdma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

union {

__u32mark;

__u32dropcount;

};

__u16vlan_tci;

sk_buff_data_ttransport_header;

sk_buff_data_tnetwork_header;

sk_buff_data_tmac_header;

sk_buff_data_ttail;

sk_buff_data_tend;

unsigned char*head,*data;

unsigned inttruesize;

atomic_tusers;

};

head:用戶指向數據包的開始

data:用於指向數據包載荷的開始

tail:用戶指向數據包載荷的結尾

end:用於指向數據包的結尾

len:用於記錄數據包包含的數據量

 

<部分重要函數>

(1)分配net_device 

結構//調用函數alloc_etherdev()/alloc_etherdev()

(2)釋放net_device

free_netdev()  

(3)linux系統注冊網卡驅動

register_netdev()

(4)向Linux系統注銷網卡驅動

unregister_netdev() 

(5)為sk_buff分配內存,並為其關聯數據到數據包負載緩沖區

dev_alloc_skb()
(6)釋放sk_buff緩沖區
dev_kfree_skb()
(7)為數據包緩沖區和有效負載開頭之間增加填充
skb_reserve() 
 
<網卡驅動實例>
typedef struct board_info {

 void __iomem *io_addr;/* 寄存器 I/O 基地址*/
 void __iomem *io_data;/* 數據 I/O 基地址 */
 u16 irq; /* IRQ */

 u16 tx_pkt_cnt;
 ...
 } board_info_t;

 static int dm9000_ioctl(struct net_device *dev, struct ifreq *req, int cmd){...}

 static const struct ethtool_ops dm9000_ethtool_ops = {
 .get_drvinfo = dm9000_get_drvinfo,
 .get_settings = dm9000_get_settings,
 .set_settings = dm9000_set_settings,
 .get_msglevel = dm9000_get_msglevel,
 .set_msglevel = dm9000_set_msglevel,
 .nway_reset = dm9000_nway_reset,
 .get_link = dm9000_get_link,
 .get_eeprom_len = dm9000_get_eeprom_len,
 .get_eeprom = dm9000_get_eeprom,
 .set_eeprom = dm9000_set_eeprom,
 };

 /* 設置 DM9000 多播地址 */
 static void dm9000_hash_table(struct net_device *dev)

 /* 看門狗超時,網絡層將調用該函數 */
 static void dm9000_timeout(struct net_device *dev)
 {
 ...
 netif_stop_queue(dev);
 netif_wake_queue(dev);
 ...
 }

 static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
 {
 ...
 /* 將發送數據包移至 DM9000 的 TX RAM */
 writeb(DM9000_MWCMD, db->io_addr);

 (db->outblk)(db->io_data, skb->data, skb->len);
 dev->stats.tx_bytes += skb->len;
 ...
 }

 /* 數據發送完成 */
 static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
 {
 ...
 netif_wake_queue(dev);
 }

 /* 接收數據並傳遞給上層 */
 static void dm9000_rx(struct net_device *dev)
 {
 ...
 netif_rx(skb);
 dev->stats.rx_packets++;
 ...
 }

 static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
 {
 ...
 return IRQ_HANDLED;
 }

 /* 打開網卡接口 */
 static int dm9000_open(struct net_device *dev)
 {
 ..
 netif_start_queue(dev);
 ...
 return 0;
 }
 /* 從 phyxcer 讀一個 word */
static int dm9000_phy_read(struct net_device *dev, int phy_reg_unused, int reg){...};

 /* 向 phyxcer 寫一個 word */
 static void dm9000_phy_write(struct net_device *dev,int phyaddr_unused, int reg,int value)
{...}

 static int __devinit dm9000_probe(struct platform_device *pdev)
 {
 ...

 /* Init network device */
 ndev = alloc_etherdev(sizeof(struct board_info));

 SET_NETDEV_DEV(ndev, &pdev->dev);

ether_setup(ndev);
 ndev->open = &dm9000_open;
 ndev->hard_start_xmit = &dm9000_start_xmit;
 ndev->tx_timeout = &dm9000_timeout;
 ndev->stop = &dm9000_stop;
 ndev->set_multicast_list = &dm9000_hash_table;
 ndev->ethtool_ops = &dm9000_ethtool_ops;
 ndev->do_ioctl = &dm9000_ioctl;

#ifdef CONFIG_NET_POLL_CONTROLLER
ndev->poll_controller = &dm9000_poll_controller;
 #endif

 db->msg_enable = NETIF_MSG_LINK;
...
 db->mii.mdio_read = dm9000_phy_read;
 db->mii.mdio_write = dm9000_phy_write;

platform_set_drvdata(pdev, ndev);
 ret = register_netdev(ndev);

...
 }

 static int __devexit dm9000_drv_remove(struct platform_device *pdev)
 {
 struct net_device *ndev = platform_get_drvdata(pdev);

 platform_set_drvdata(pdev, NULL);

 unregister_netdev(ndev);
 free_netdev(ndev); /* free device structure */

 return 0;
 }

 static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
 .owner = THIS_MODULE,
 },
 .probe = dm9000_probe,
.remove = __devexit_p(dm9000_drv_remove),
 };

 static int __init dm9000_init(void)
 {
 return platform_driver_register(&dm9000_driver);
 }

 static void __exit dm9000_cleanup(void)
 {
 platform_driver_unregister(&dm9000_driver);
 }

module_init(dm9000_init);
 module_exit(dm9000_cleanup);

static struct resource ldd6410_dm9000_resource[] = {
 [0] = {
 .start = 0x18000000,
.end = 0x18000000 + 3,
    .flags = IORESOURCE_MEM
},
[1] = {
 .start = 0x18000000 + 0x4,
.end = 0x18000000 + 0x7,
 .flags = IORESOURCE_MEM
 },
 [2] = {
 .start = IRQ_EINT(7),
 .end = IRQ_EINT(7),
 .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
 }

};

 static struct dm9000_plat_data ldd6410_dm9000__platdata = {
 .flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO__EEPROM,
 .dev_addr = { 0x0, 0x16, 0xd4, 0x9f, 0xed, 0xa4 },
 };

 static struct platform__device ldd6410_dm9000 = {
.name = "dm9000",
 .id = 0,
 .num_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
.resource = ldd6410_dm9000_resource,
 .dev = {
 .platform_data = &ldd6410_dm9000_platdata,
 }
 };

 

 









 
 

 

 

<網絡知識>

a:網絡模型
              OSI模型               TCP模型
雖然OSI模型看着挺完美的,但是過於復雜,這樣就會導致不實用,在Linux系 統中用的是TCP模型。每一層都是一種協議,系統對要發送的數據進行層層數據封裝,就像洋蔥層層刨去還原數據。
 
<Linux網絡驅動特點>
a:對於網絡接口的常用文件操作(讀,寫等)是沒有意義,在Linux系統中,網絡驅動是唯一一個無法體現UNIX/linux 的一切皆是文件的思想,因為網絡設備咩有對應的設備文件。並且網絡接口函數都存在於自己的名字空間中(設備結構體中,有相應的指向ops的指針)。但是注意,當應用程序使用套接字的時候,任然使用"read""write"的調用但是這些調用作用於軟件對象上,他們與網絡接口完全不同。
 
<網絡數據發送過程>
a:示意圖如圖所示
(1) 從應用層 write()->sock_write() (net/socket.c)->inet_write() (net/inet/af_inet.c)->tcp_write() (net/inet/tcp.c)真正處理。 前兩個如前所說只是作一些檢查。在傳輸層進行處理所原因在於:數據的封裝只有在傳輸層才行。
 
(2)tcp_write()函數完成數據的封裝,將用戶緩沖區賦值到內核緩沖區封裝到sk_buff結構。如果網絡擁塞,則暫時緩存到 write_queue隊列中,稍后發送;否則則可以不經過write_queue直接發送出去。傳輸層協議調用ip_queue_xmit()函數將數 據包發送到網絡層進行處理。
 
(3)ip_queue_xmit()函數 對數據幀進行完善,調用dev_queue_xmit()函數將數據包送往鏈路層進行處理。同時將此數據包緩存到sock_send隊列,保證可靠傳輸, 此時,數據包已經從write_queue中刪除。write_queue:從用戶層結束新的數據包,沒有用ip_queue_xmit傳輸出去。而 send_queue則調用了ip_queue_xmit傳輸。ip_queue_xmit()直接調用dev_queue_xmit()函數進行發送。
 
(4)dev_queue_xmit()完成本層處理后,調用設備結構device的hard_start_xmit函數指針指向的具體硬件發送函數。對於 NE系列網絡設備的ei_start_xmit()函數,其首先將數據從內核賦值到網卡設備硬件緩沖區,操作具體的硬件寄存器,最終完成發送。
 
<網卡驅動中的一些重要數據結構>
a:net_device{}中的部分重要數據
stuct net_device
{
//全局信息
char name[IFNAMSIZ];
/* 設備名子. 如果名子由驅動設置, 包含一個 %d 格式串,register_netdev用一個數替換它來形成一個唯一的名子; 分配的編號從0 開始.*/
unsigned long state;
/* 設備狀態. 這個成員包括幾個標志. 驅動正常情況下不直接操作這些標 志;相反, 提供了一套實用函數. 這些函數在我們進入驅動操作后馬上討論這些函數.*/
struct net_device *next; 
/* 全局列表中指向下一個設備的指針. 這個成員驅動不能動*/
int (*init)(struct net_device *dev);
/* 一個初始化函數. 如果設置了這個指針, 這個函數被 register_netdev調用來完成對 net_device結構的初始化. 大部分現代的網絡驅動不再使用這個函數; 相反, 初始化在注冊接口前進行.*/
//硬件信息
unsigned long rmem_end;
unsigned long rmem_start;
unsigned long mem_end;
unsigned long mem_start;

/*設備內存信息. 這些成員持有設備使用的共享內存的開始和結束地址. 如果設備有不同的接收和發送內存, mem 成員由發送內存使用, rmem 成員接收內存使用. rmem 成員在驅動之外從不被引用. 慣例上, 設置 end 成員, 所以 end - start 是可用的板上內存的數量.*/
unsigned long base_addr;
/*網絡接口的 I/O 基地址. 這個成員, 如同前面的, 由驅動在設備探測時賦值. ifconfig 目可用來顯示或修改當前值. base_addr 可以當系統啟動時在內核命令行中顯式賦值( 通過 netdev=參數), 或者在模塊加載時.這個成員, 象上面描述過的內存成員, 內核不使用它們.*/
unsigned char irq;
/*安排的中斷號. 當接口被列出時 ifconfig 打印出 dev->irq 的值. 這個值常常在啟動或者加載時間設置並且在后來由 ifconfig 打印.*/
unsigned char if_port;
/*在多端口設備中使用的端口. 例如, 這個成員用在同時支持同軸線(IF_PORT_10BASE2)和雙絞線(IF_PORT_100BSAET)以太網連接. 完整的已知端口類型設置定義在 <linux/netdevie.h>.*/
unsigned char dma;
/*設備分配的 DMA 通道. 這個成員只在某些外設總線時有意義, 例如 ISA.它不在設備驅動自身以外使用, 只是為了信息目的( 在 ifconfig ) 中.*/
//接口信息
/*有關接口的大部分信息由 ether_setup 函數正確設置(或者任何其他對給定硬件類型適合的設置函數). 以太網卡可以依賴這個通用的函數設置大部分這些成員,但是 flags 和 dev_addr 成員是特定設備的, 必須在初始化時間明確指定.一些非以太網接口可以使用類似 ether_setup 的幫助函數.*/
void ltalk_setup(struct net_device *dev);
/*設置一個 LocalTalk 設備的成員*/
void fc_setup(struct net_device *dev);
/*初始化光通道設備的成員*/
void fddi_setup(struct net_device *dev);
/*配置一個光纖分布數據接口 (FDDI) 網絡的接口*/
void hippi_setup(struct net_device *dev);
/*預備給一個高性能並行接口 (HIPPI) 的高速互連驅動的成員*/
void tr_setup(struct net_device *dev);
/*處理令牌環網絡接口的設置大部分設備會歸於這些類別中的一類. 如果你的是全新和不同的, 但是, 你需要手工賦值下面的成員*/
unsigned short hard_header_len;
/*硬件頭部長度, 就是, 被發送報文前面在 IP 頭之前的字節數, 或者別的協議信息. 對於以太網接口 hard_header_len 值是 14 (ETH_HLEN).
unsigned mtu;
/*最大傳輸單元 (MTU). 這個成員是網絡層用作驅動報文傳輸. 以太網有一個 1500 字節的 MTU (ETH_DATA_LEN). 這個值可用 ifconfig 改變.*/
unsigned long tx_queue_len;
/*設備發送隊列中可以排隊的最大幀數. 這個值由 ether_setup 設置為1000, 但是你可以改它. 例如, plip 使用 10 來避免浪費系統內存( 相比真實以太網接口, plip 有一個低些的吞吐量).*/
unsigned short type;
/*接口的硬件類型. 這個 type 成員由 ARP 用來決定接口支持什么樣的硬件地址. 對以太網接口正確的值是 ARPHRD_ETHER, 這是由 ether_setup設置的值. 可認識的類型定義於 <linux/if_arp.h>.*/
unsigned char addr_len;
unsigned char broadcast[MAX_ADDR_LEN];
unsigned char dev_addr[MAX_ADDR_LEN];

/*硬件 (MAC) 地址長度和設備硬件地址. 以太網地址長度是 6 個字節( 我們指的是接口板的硬件 ID ), 廣播地址由 6 個 0xff 字節組成;ether_setup 安排成正確的值. 設備地址, 另外, 必須以特定於設備的方式從接口板讀出, 驅動應當將它拷貝到 dev_addr. 硬件地址用來產生正確的以太網頭, 在報文傳遞給驅動發送之前. snull 設備不使用物理接口,它創造自己的硬件接口.*/
unsigned short flags;
int features;
/*接口標志(下面詳述)這個 flags 成員是一個位掩碼, 包括下面的位值. IFF_ 前綴代表 "interfaceflags". 有些標志由內核管理, 有些由接口在初始化時設置來表明接口的能力和其他特性. 有效的標志, 對應於 <linux/if.h>(詳情見/linux內核驅動/網絡接口標志*/
 
//設備方法
如同在字符和塊驅動的一樣, 每個網絡設備聲明能操作它的函數. 本節列出能夠對網絡接口進行的操作. 有些操作可以留作 NULL, 別的常常是不被觸動的, 因為 ether_setup 給它們安排了合適的方法.網絡接口的設備方法可分為 2 組: 基本的和可選的. 基本方法包括那些必需的
能夠使用接口的; 可選的方法實現更多高級的不是嚴格要求的功能.
*/
基本方法:
int (*open)(struct net_device *dev);
/*打開接口. 任何時候 ifconfig 激活它, 接口被打開. open 方法應當注冊它需要的任何系統資源( I/O 口, IRQ, DMA, 等等), 打開硬件, 進行任何別的你的設備要求的設置.*/
int (*stop)(struct net_device *dev);
/*停止接口. 接口停止當它被關閉. 這個函數應當恢復在打開時進行的操作.*/
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
/*起始報文的發送的方法. 完整的報文(協議頭和所有)包含在一個 socket緩存區( sk_buff ) 結構. socket 緩存在本章后面介紹. */
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,void *saddr, unsigned len);
/*用之前取到的源和目的硬件地址來建立硬件頭的函數(在hard_start_xmit 前調用). 它的工作是將作為參數傳給它的信息組織成一個合適的特定於設備的硬件頭. eth_header 是以太網類型接口的缺省函數, ether_setup 針對性地對這個成員賦值.*/
int (*rebuild_header)(struct sk_buff *skb);
/*用來在 ARP 解析完成后但是在報文發送前重建硬件頭的函數. 以太網設備使用的缺省的函數使用 ARP 支持代碼來填充報文缺失的信息.*/
void (*tx_timeout)(struct net_device *dev);
/*由網絡代碼在一個報文發送沒有在一個合理的時間內完成時調用的方法,可能是丟失一個中斷或者接口被鎖住. 它應當處理這個問題並恢復報文發送.*/
struct net_device_stats *(*get_stats)(struct net_device *dev);
/*任何時候當一個應用程序需要獲取接口的統計信息, 調用這個方法. 例如,當 ifconfig 或者 netstat -i 運行時. snull 的一個例子實現在"統計信息"一節中介紹.*/
int (*set_config)(struct net_device *dev, struct ifmap *map);
/*改變接口配置. 這個方法是配置驅動的入口點. 設備的 I/O 地址和中斷號可以在運行時使用 set_config 來改變. 這種能力可由系統管理員在接口沒有探測到時使用. 現代硬件正常的驅動一般不需要實現這個方法. */
 
可選操作:
int weight;
int (*poll)(struct net_device *dev; int *quota);

/*由適應 NAPI 的驅動提供的方法, 用來在查詢模式下操作接口, 中斷關閉着. NAPI ( 以及 weight 成員) 在"接收中斷緩解"一節中涉及.*/
void (*poll_controller)(struct net_device *dev);
/*在中斷關閉的情況下, 要求驅動檢查接口上的事件的函數. 它用於特殊的內核中的網絡任務, 例如遠程控制台和使用網絡的內核調試.*/
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
/*處理特定於接口的 ioctl 命令. (這些命令的實現在"定制 ioclt 命令"一節中描述)相應的 net_device 結構中的成員可留為 NULL, 如果接口不需要任何特定於接口的命令.*/
void (*set_multicast_list)(struct net_device *dev);
/*當設備的組播列表改變和當標志改變時調用的方法. */
int (*set_mac_address)(struct net_device *dev, void *addr);
/*如果接口支持改變它的硬件地址的能力, 可以實現這個函數. 很多接口根本不支持這個能力. 其他的使用缺省的 eth_mac_adr 實現(在deivers/net/net_init.c). eth_mac_addr 只拷貝新地址到dev->dev_addr, 只在接口沒有運行時作這件事. 使用 eth_mac_addr 的驅動應當在它們的 open 方法中自 dev->dev_addr 里設置硬件 MAC 地址.*/
int (*change_mtu)(struct net_device *dev, int new_mtu);
/*當接口的最大傳輸單元 (MTU) 改變時動作的函數. 如果用戶改變 MTU 時驅動需要做一些特殊的事情, 它應當聲明它的自己的函數; 否則, 缺省的會將事情做對. snull 有對這個函數的一個模板, 如果你有興趣.*/
int (*header_cache) (struct neighbour *neigh, struct hh_cache *hh);
/*header_cache 被調用來填充 hh_cache 結構, 使用一個 ARP 請求的結果.幾乎全部類似以太網的驅動可以使用缺省的 eth_header_cache 實現.*/
int (*header_cache_update) (struct hh_cache *hh, struct net_device *dev, unsigned char *haddr);
/*在響應一個變化中, 更新 hh_cache 結構中的目的地址的方法. 以太網設備使用 eth_header_cache_update.*/
int (*hard_header_parse) (struct sk_buff *skb, unsigned char *haddr);
/*hard_header_parse 方法從包含在 skb 中的報文中抽取源地址, 拷貝到haddr 的緩存區. 函數的返回值是地址的長度. 以太網設備通常使用eth_header_parse.*/
公用成員
/*結構 net_device 剩下的數據成員由接口使用來持有有用的狀態信息. 有些是ifconfig 和 netstat 用來提供給用戶關於當前配置的信息. 因此, 接口應當給這些成員賦值: */
unsigned long trans_start;
unsigned long last_rx;

/*保存一個 jiffy 值的成員. 驅動負責分別更新這些值, 當開始發送和收到一個報文時. trans_start 值被網絡子系統用來探測發送器加鎖.last_rx 目前沒有用到, 但是驅動應當盡量維護這個成員以備將來使用.*/
int watchdog_timeo;
/*網絡層認為一個傳送超時發生前應當過去的最小時間(按 jiffy 計算),調用驅動的 tx_timeout 函數.*/
void *priv;
/*filp->private_data 的對等者. 在現代的驅動里, 這個成員由alloc_netdev 設置, 不應當直接存取; 使用 netdev_priv 代替.*/
struct dev_mc_list *mc_list;
int mc_count;
/*處理組播發送的成員. mc_count 是 mc_list 中的項數目. 更多細節見"組播"一節.*/
spinlock_t xmit_lock;
int xmit_lock_owner;

/*xmit_lock 用來避免對驅動的 hard_start_xmit 函數多個同時調用.xmit_lock_owner 是已獲得 xmit_lock 的 CPU 號. 驅動應當不改變這些成員的值.結構 net_device 中有其他的成員, 但是網絡驅動用不着它們.*/  
}   
 

b:struct sk_buff{}

Linux系統中每個網絡數據包都由一個套接字緩沖結構struct sk_buff 描述,即一個sk_buff結構就是一個網絡包指向sk_buff指針通常被稱作skb

struct sk_buff {

struct sk_buff*next;

struct sk_buff*prev;

ktime_ttstamp;

struct sock*sk;

struct net_device*dev;

charcb[48] __aligned(8);

unsigned long_skb_refdst;

#ifdef CONFIG_XFRM

structsec_path*sp;

#endif

unsigned intlen,

data_len;

__u16mac_len,

hdr_len;

union {

__wsumcsum;

struct {

__u16csum_start;

__u16csum_offset;

};

};

__u32priority;

kmemcheck_bitfield_begin(flags1);

__u8local_df:1,

cloned:1,

ip_summed:2,

nohdr:1,

nfctinfo:3;

__u8pkt_type:3,

fclone:2,

ipvs_property:1,

peeked:1,

nf_trace:1;

kmemcheck_bitfield_end(flags1);

__be16protocol;

void(*destructor)(struct sk_buff *skb);

#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)

struct nf_conntrack*nfct;

#endif

#ifdef NET_SKBUFF_NF_DEFRAG_NEEDED

struct sk_buff*nfct_reasm;

#endif

#ifdef CONFIG_BRIDGE_NETFILTER

struct nf_bridge_info*nf_bridge;

#endif

intskb_iif;

#ifdef CONFIG_NET_SCHED

__u16tc_index;/* traffic control index */

#ifdef CONFIG_NET_CLS_ACT

__u16tc_verd;/* traffic control verdict */

#endif

#endif

__u32rxhash;

kmemcheck_bitfield_begin(flags2);

__u16queue_mapping:16;

#ifdef CONFIG_IPV6_NDISC_NODETYPE

__u8ndisc_nodetype:2;

#endif

__u8ooo_okay:1;

kmemcheck_bitfield_end(flags2);

#ifdef CONFIG_NET_DMA

dma_cookie_tdma_cookie;

#endif

#ifdef CONFIG_NETWORK_SECMARK

__u32secmark;

#endif

union {

__u32mark;

__u32dropcount;

};

__u16vlan_tci;

sk_buff_data_ttransport_header;

sk_buff_data_tnetwork_header;

sk_buff_data_tmac_header;

sk_buff_data_ttail;

sk_buff_data_tend;

unsigned char*head,*data;

unsigned inttruesize;

atomic_tusers;

};

head:用戶指向數據包的開始

data:用於指向數據包載荷的開始

tail:用戶指向數據包載荷的結尾

end:用於指向數據包的結尾

len:用於記錄數據包包含的數據量

 

<部分重要函數>

(1)分配net_device 

結構//調用函數alloc_etherdev()/alloc_etherdev()

(2)釋放net_device

free_netdev()  

(3)linux系統注冊網卡驅動

register_netdev()

(4)向Linux系統注銷網卡驅動

unregister_netdev() 

(5)為sk_buff分配內存,並為其關聯數據到數據包負載緩沖區

dev_alloc_skb()
(6)釋放sk_buff緩沖區
dev_kfree_skb()
(7)為數據包緩沖區和有效負載開頭之間增加填充
skb_reserve() 
 
<網卡驅動實例>

2 typedef struct board _ info {
3
4 void
__ iomem * io _ addr;/ * 寄存器 I/O 基地址 * /
5 void
__ iomem * io _ data;/ * 數據 I/O 基地址 * /
6 u16 irq; /
* IRQ * /
7
8 u16 tx
_ pkt _ cnt;
9 ...
10 } board
_ info _ t;
11
12 static int dm9000
_ ioctl(struct net _ device * dev, struct ifreq * req, int cmd){...}
13
14 static const struct ethtool
_ops dm9000_ethtool_ ops = {
15 .get
_ drvinfo = dm9000 _ get _ drvinfo,
16 .get
_ settings = dm9000 _ get _ settings,
17 .set
_ settings = dm9000 _ set _ settings,
18 .get
_ msglevel = dm9000 _ get _ msglevel,
19 .set
_ msglevel = dm9000 _ set _ msglevel,
20 .nway
_ reset = dm9000 _ nway _ reset,
21 .get
_ link = dm9000 _ get _ link,
22 .get
_ eeprom _ len = dm9000 _ get _ eeprom _ len,
23 .get
_ eeprom = dm9000 _ get _ eeprom,
24 .set
_ eeprom = dm9000 _ set _ eeprom,
25 };
26
27 /
* 設置 DM9000 多播地址 * /
28 static void dm9000
_hash_ table(struct net _ device * dev)
29
30 /
* 看門狗超時,網絡層將調用該函數 * /
31 static void dm9000
_ timeout(struct net _ device * dev)
32 {
33 ...
34 netif
_ stop _ queue(dev);
35 netif
_ wake _ queue(dev);
36 ...
37 }
38
39 static int dm9000
_start_ xmit(struct sk _ buff * skb, struct net _ device * dev)
40 {
41 ...
42 /
* 將發送數據包移至 DM9000 TX RAM * /
43 writeb(DM9000
_ MWCMD, db->io _ addr);
44
45 (db->outblk)(db->io_data, skb->data, skb->len);
46 dev->stats.tx
_bytes += skb->len;
47 ...
48 }
49
50 /
* 數據發送完成 */
51 static void dm9000
_tx_done(struct net_device *dev, board_info_t *db)
52 {
53 ...
54 netif
_wake_queue(dev);
55 }
56
57 /
* 接收數據並傳遞給上層 */
58 static void dm9000
_rx(struct net_device *dev)
60 {
61 ...
62 netif
_rx(skb);
63 dev->stats.rx
_packets++;
64 ...
65 }
66
67 static irqreturn
_t dm9000_interrupt(int irq, void *dev_id)
68 {
69 ...
70 return IRQ
_HANDLED;
71 }
72
73 /
* 打開網卡接口 */
74 static int dm9000
_open(struct net_device *dev)
75 {
76 ..
77 netif
_start_queue(dev);
78 ...
79 return 0;
80 }
81 /
* phyxcer 讀一個 word */
82 static int dm9000
_phy_read(struct net_device *dev, int phy_reg_unused, int reg){...};

83 /
* phyxcer 寫一個 word */
84 static void dm9000
_phy_write(struct net_device *dev,int phyaddr_unused, int reg,int value)
{...}
87
88 static int
__devinit dm9000_probe(struct platform_device *pdev)
90 {
91 ...
92
93 /
* Init network device */
94 ndev = alloc
_etherdev(sizeof(struct board_info));
95
96 SET
_NETDEV_DEV(ndev, &pdev->dev);
97
98 ether
_setup(ndev);
100 ndev->open = &dm9000_open;
101 ndev->hard
_start_xmit = &dm9000_start_xmit;
102 ndev->tx
_timeout = &dm9000_timeout;
103 ndev->stop = &dm9000
_stop;
104 ndev->set
_multicast_list = &dm9000_hash_table;
105 ndev->ethtool
_ops = &dm9000_ethtool_ops;
106 ndev->do
_ioctl = &dm9000_ioctl;
107
108 #ifdef CONFIG
_NET_POLL_CONTROLLER
109 ndev->poll
_controller = &dm9000_poll_controller;
110 #endif
111
112 db->msg
_enable = NETIF_MSG_LINK;
113 ...
114 db->mii.mdio
_read = dm9000_phy_read;
115 db->mii.mdio
_write = dm9000_phy_write;
116
117 platform
_set_drvdata(pdev, ndev);
118 ret = register
_netdev(ndev);
119
120 ...
121 }
122
123 static int
__devexit dm9000_drv_remove(struct platform_device *pdev)
125 {
126 struct net
_device *ndev = platform_get_drvdata(pdev);
127
128 platform
_set_drvdata(pdev, NULL);
129
130 unregister
_netdev(ndev);
131 free
_netdev(ndev); /* free device structure */
132
133 return 0;
134 }
135
136 static struct platform
_driver dm9000_driver = {
137 .driver = {
138 .name = "dm9000",
139 .owner = THIS
_MODULE,
140 },
141 .probe = dm9000
_probe,
142 .remove =
__devexit_p(dm9000_drv_remove),
143 };
144
145 static int
__init dm9000_init(void)
146 {
147 return platform
_driver_register(&dm9000_driver);
148 }
149
150 static void
__exit dm9000_cleanup(void)
151 {
152 platform
_driver_unregister(&dm9000_driver);
153 }
154
155 module_init(dm9000_init);
156 module
_exit(dm9000_cleanup);

1 static struct resource ldd6410_dm9000_resource[] = {
2 [0] = {
3 .start = 0x18000000,
4 .end = 0x18000000 + 3,
5 .flags = IORESOURCE
_MEM
6 },
7 [1] = {
8 .start = 0x18000000 + 0x4,
9 .end = 0x18000000 + 0x7,
10 .flags = IORESOURCE
_MEM
11 },
12 [2] = {
13 .start = IRQ
_EINT(7),
14 .end = IRQ
_EINT(7),
15 .flags = IORESOURCE
_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
16 }
17
18 };
19
20 static struct dm9000
_plat_data ldd6410_dm9000__platdata = {
21 .flags = DM9000
_PLATF_16BITONLY | DM9000_PLATF_NO__EEPROM,
22 .dev
_addr = { 0x0, 0x16, 0xd4, 0x9f, 0xed, 0xa4 },
23 };
24
25 static struct platform
__device ldd6410_dm9000 = {
26 .name = "dm9000",
27 .id = 0,
28 .num
_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
29 .resource = ldd6410
_dm9000_resource,
30 .dev = {
31 .platform
_data = &ldd6410_dm9000_platdata,
32 }
33 };










 
 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM