一文搞懂網(wǎng)卡驅(qū)動的原理與移植方法

1、網(wǎng)卡設(shè)備驅(qū)動原理

1.1 層次結(jié)構(gòu)

Linux系統(tǒng)對網(wǎng)絡(luò)設(shè)備驅(qū)動定義了4個層次, 這4個層次有到下分為:

  • 1、網(wǎng)絡(luò)協(xié)議接口層:實現(xiàn)統(tǒng)一的數(shù)據(jù)包收發(fā)的協(xié)議。該層主要負責調(diào)用dev_queue_xmit()函數(shù)發(fā)送數(shù)據(jù), netif_rx()函數(shù)接收數(shù)據(jù)。當上層 ARP 或 IP 協(xié)議需要發(fā)送數(shù)據(jù)包時,它將調(diào)用網(wǎng)絡(luò)協(xié)議接口層的 dev_queue_xmit()函數(shù)發(fā)送該數(shù)據(jù)包,同時需傳遞給該函數(shù)一個指向 struct sk_buff數(shù)據(jù) 結(jié)構(gòu)的指針。dev_queue_xmit()函數(shù)的原型為:
dev_queue_xmit (struct sk_buff * skb ); 

同樣地,上層對數(shù)據(jù)包的接收也通過向 netif_rx()函數(shù)傳遞一個 struct sk_buff數(shù)據(jù) 結(jié)構(gòu)的指針來完成。netif_rx()函數(shù)的原型為:

 int netif_rx(struct sk_buff *skb); 
  • 2、網(wǎng)絡(luò)設(shè)備接口層:通過net_device結(jié)構(gòu)體來描述一個具體的網(wǎng)絡(luò)設(shè)備的信息,實現(xiàn)不同的硬件的統(tǒng)一
  • 3、設(shè)備驅(qū)動功能層:用來負責驅(qū)動網(wǎng)絡(luò)設(shè)備硬件來完成各個功能, 它通過hard_start_xmit() 函數(shù)啟動發(fā)送操作, 并通過網(wǎng)絡(luò)設(shè)備上的中斷觸發(fā)接收操作。
  • 4、網(wǎng)絡(luò)設(shè)備與媒介層:用來負責完成數(shù)據(jù)包發(fā)送和接收的物理實體, 設(shè)備驅(qū)動功能層的函數(shù)都在這物理上驅(qū)動的

層次結(jié)構(gòu)如下圖所示:

image-20210809230524324

設(shè)計具體的網(wǎng)絡(luò)設(shè)備驅(qū)動程序時,我們需要完成的主要工作是編寫設(shè)備驅(qū)動功 能層的相關(guān)函數(shù)以填充 net_device 數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將 net_device 注冊入內(nèi)核。

1.2 網(wǎng)卡驅(qū)動的初始化

網(wǎng)卡驅(qū)動程序,只需要編寫網(wǎng)絡(luò)設(shè)備接口層,填充net_device數(shù)據(jù)結(jié)構(gòu)的內(nèi)容并將net_device注冊入內(nèi)核,設(shè)置硬件相關(guān)操作,使能中斷處理等。

1.2.1 初始化網(wǎng)卡步驟

  • 1)使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體
  • 2)設(shè)置網(wǎng)卡硬件相關(guān)的寄存器
  • 3)設(shè)置net_device結(jié)構(gòu)體的成員
  • 4)使用register_netdev()注冊net_device結(jié)構(gòu)體

1.2.2 net_device結(jié)構(gòu)體

網(wǎng)絡(luò)設(shè)備接口層的主要功能是為千變?nèi)f化的網(wǎng)絡(luò)設(shè)備定義了統(tǒng)一、抽象的數(shù)據(jù)結(jié) 構(gòu) net_device 結(jié)構(gòu)體,以不變應(yīng)萬變,實現(xiàn)多種硬件在軟件層次上的統(tǒng)一。 net_device 結(jié)構(gòu)體在內(nèi)核中指代一個網(wǎng)絡(luò)設(shè)備,網(wǎng)絡(luò)設(shè)備驅(qū)動程序只需通過填充 net_device 的具體成員并注冊 net_device 即可實現(xiàn)硬件操作函數(shù)與內(nèi)核的掛接。 net_device 本身是一個巨型結(jié)構(gòu)體,包含網(wǎng)絡(luò)設(shè)備的屬性描述和操作接口。當我 們編寫網(wǎng)絡(luò)設(shè)備驅(qū)動程序時,只需要了解其中的一部分。

struct net_device
{
       char               name[IFNAMSIZ];      //網(wǎng)卡設(shè)備名稱
       unsigned long      mem_end;             //該設(shè)備的內(nèi)存結(jié)束地址
       unsigned long      mem_start;            //該設(shè)備的內(nèi)存起始地址
       unsigned long      base_addr;            //該設(shè)備的內(nèi)存I/O基地址
       unsigned int       irq;                  //該設(shè)備的中斷號

       unsigned char      if_port;              //該字段僅針對多端口設(shè)備,用于指定使用的端口類型
    unsigned char      dma;                  //該設(shè)備的DMA通道
       unsigned long      state;                //網(wǎng)絡(luò)設(shè)備和網(wǎng)絡(luò)適配器的狀態(tài)信息

       struct net_device_stats* (*get_stats)(struct net_device *dev); //獲取流量的統(tǒng)計信息,運行ifconfig便會調(diào)用該成員函數(shù),并返回一個net_device_stats結(jié)構(gòu)體獲取信息

      struct net_device_stats  stats;      //用來保存統(tǒng)計信息的net_device_stats結(jié)構(gòu)體

 
       unsigned long         features;        //接口特征,     
       unsigned int          flags; //flags指網(wǎng)絡(luò)接口標志,以IFF_開頭,包括:IFF_UP( 當設(shè)備被激活并可以開始發(fā)送數(shù)據(jù)包時, 內(nèi)核設(shè)置該標志)、 IFF_AUTOMEDIA(設(shè)置設(shè)備可在多種媒介間切換)、IFF_BROADCAST( 允許廣播)、IFF_DEBUG( 調(diào)試模式, 可用于控制printk調(diào)用的詳細程度) 、 IFF_LOOPBACK( 回環(huán))、IFF_MULTICAST( 允許組播) 、 IFF_NOARP( 接口不能執(zhí)行ARP,點對點接口就不需要運行 ARP) 和IFF_POINTOPOINT( 接口連接到點到點鏈路) 等。

 
       unsigned        mtu;        //最大傳輸單元,也叫最大數(shù)據(jù)包

       unsigned short  type;     //接口的硬件類型

       unsigned short   hard_header_len;     //硬件幀頭長度,在以太網(wǎng)設(shè)備的初始化函數(shù)中一般被賦為ETH_HLEN,即14
 
    unsigned char    *dev_addr;   //存放設(shè)備的MAC地址,需由驅(qū)動程序從硬件上讀出
      unsigned char    broadcast[MAX_ADDR_LEN];    //存放設(shè)備的廣播地址,對于以太網(wǎng)而言,地址長度為6個0XFF

       unsigned long    last_rx;    //接收數(shù)據(jù)包的時間戳,調(diào)用netif_rx()后賦上jiffies即可

       unsigned long    trans_start;   //發(fā)送數(shù)據(jù)包的時間戳,當要發(fā)送的時候賦上jiffies即可

       int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);//數(shù)據(jù)包發(fā)送函數(shù), 以使得驅(qū)動程序能獲取從上層傳遞下來的數(shù)據(jù)包。
                                   
    void  (*tx_timeout) (struct net_device *dev); //發(fā)包超時處理函數(shù),需采取重新啟動數(shù)據(jù)包發(fā)送過程或重新啟動硬件等策略來恢復(fù)網(wǎng)絡(luò)設(shè)備到正常狀態(tài)
    ... ...
}

1.2.3 net_device_stats結(jié)構(gòu)體

net_device_stats 結(jié)構(gòu)體定義在內(nèi)核的 include/linux/netdevice.h 文件中,其中重要成員如下所示:

struct net_device_stats
{
    unsigned long   rx_packets;     /*收到的數(shù)據(jù)包數(shù)*/
    unsigned long   tx_packets;     /*發(fā)送的數(shù)據(jù)包數(shù)    */
    unsigned long   rx_bytes;       /*收到的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/       unsigned long   tx_bytes;       /*發(fā)送的字節(jié)數(shù),可以通過sk_buff結(jié)構(gòu)體的成員len來獲取*/
    unsigned long   rx_errors;      /*收到的錯誤數(shù)據(jù)包數(shù)*/
    unsigned long   tx_errors;      /*發(fā)送的錯誤數(shù)據(jù)包數(shù)*/
    ... ...
}

net_device_stats 結(jié)構(gòu)體適宜包含在設(shè)備的私有信息結(jié)構(gòu)體中,而其中統(tǒng)計信息的 修改則應(yīng)該在設(shè)備驅(qū)動的與發(fā)送和接收相關(guān)的具體函數(shù)中完成,這些函數(shù)包括中斷處 理程序、數(shù)據(jù)包發(fā)送函數(shù)、數(shù)據(jù)包發(fā)送超時函數(shù)和數(shù)據(jù)包接收相關(guān)函數(shù)等。我們應(yīng)該 在這些函數(shù)中添加相應(yīng)的代碼:

/* 發(fā)送超時函數(shù) */ 
void xxx_tx_timeout(struct net_device *dev) 
{ 
    struct xxx_priv *priv = netdev_priv(dev); 
    ... 
    priv->stats.tx_errors++; /* 發(fā)送錯誤包數(shù)加 1 */ 
    ... 
} 

/* 中斷處理函數(shù) */ 
static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{ 
    switch (status &ISQ_EVENT_MASK) 
    { 
        ... 
        case ISQ_TRANSMITTER_EVENT: / 
            priv->stats.tx_packets++; /* 數(shù)據(jù)包發(fā)送成功,tx_packets 信息加1 */ 
            netif_wake_queue(dev); /* 通知上層協(xié)議 */ 
            if ((status &(TX_OK | TX_LOST_CRS | TX_SQE_ERROR | 
                TX_LATE_COL | TX_16_COL)) != TX_OK) /*讀取硬件上的出錯標志*/ 
            { 
                /* 根據(jù)錯誤的不同情況,對 net_device_stats 的不同成員加 1 */ 
                if ((status &TX_OK) == 0) 
                    priv->stats.tx_errors++; 
                if (status &TX_LOST_CRS) 
                    priv->stats.tx_carrier_errors++; 
                if (status &TX_SQE_ERROR)
                    priv->stats.tx_heartbeat_errors++; 
                if (status &TX_LATE_COL) 
                    priv->stats.tx_window_errors++; 
                if (status &TX_16_COL) 
                    priv->stats.tx_aborted_errors++; 
            } 
            break; 
        case ISQ_RX_MISS_EVENT: 
            priv->stats.rx_missed_errors += (status >> 6); 
            break; 
        case ISQ_TX_COL_EVENT: 
            priv->stats.collisions += (status >> 6); 
            break; 
    } 
}

1.2.4 sk_buff結(jié)構(gòu)體

sk_buff 結(jié)構(gòu)體非常重要,它的含義為“套接字緩沖區(qū)”,用于在 Linux 網(wǎng)絡(luò)子系 統(tǒng)中的各層之間傳遞數(shù)據(jù),是 Linux 網(wǎng)絡(luò)子系統(tǒng)數(shù)據(jù)傳遞的“中樞神經(jīng)”。當發(fā)送數(shù)據(jù)包時,Linux 內(nèi)核的網(wǎng)絡(luò)處理模塊必須建立一個包含要傳輸?shù)臄?shù)據(jù)包 的 sk_buff,然后將 sk_buff 遞交給下層,各層在 sk_buff 中添加不同的協(xié)議頭直至交 給網(wǎng)絡(luò)設(shè)備發(fā)送。同樣地,當網(wǎng)絡(luò)設(shè)備從網(wǎng)絡(luò)媒介上接收到數(shù)據(jù)包后,它必須將接收 到的數(shù)據(jù)轉(zhuǎn)換為 sk_buff 數(shù)據(jù)結(jié)構(gòu)并傳遞給上層,各層剝?nèi)ハ鄳?yīng)的協(xié)議頭直至交給用 戶。

參看 linux/skbuff.h 中的源代碼,sk_buff 結(jié)構(gòu)體包含的主要成員如下:

/* /include/linux/skbuff.h */
struct sk_buff {
       /* These two members must be first. */
       struct sk_buff        *next;      //指向下一個sk_buff結(jié)構(gòu)體
       struct sk_buff        *prev;     //指向前一個sk_buff結(jié)構(gòu)體
    ktime_t             tstamp;
      struct sock       *sk;
      struct net_device *dev;
    /*
       * This is the control buffer. It is free to use for every
       * layer. Please put your private variables there. If you
       * want to keep them across layers you have to do a skb_clone()
       * first. This is owned by whoever has the skb queued ATM.
       */
       ......
       unsigned int         len,         //數(shù)據(jù)包的總長度,包括線性數(shù)據(jù)和非線性數(shù)據(jù)
                            data_len;        //非線性的數(shù)據(jù)長度
       __u16                mac_len,         //mac包頭長度
                            hdr_len;
      .....
    __u32      priority;          //該sk_buff結(jié)構(gòu)體的優(yōu)先級
      ......
    __be16    protocol;           //存放上層的協(xié)議類型,可以通過eth_type_trans()來獲取
       ... ...

      sk_buff_data_t              transport_header;    //傳輸層頭部的偏移值
      sk_buff_data_t              network_header;     //網(wǎng)絡(luò)層頭部的偏移值
      sk_buff_data_t              mac_header;          //MAC數(shù)據(jù)鏈路層頭部的偏移值
      /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t              tail;                    //指向緩沖區(qū)的數(shù)據(jù)包末尾
      sk_buff_data_t              end;                     //指向緩沖區(qū)的末尾
      unsigned char            *head,                   //指向緩沖區(qū)的協(xié)議頭開始位置
                                  *data;                   //指向緩沖區(qū)的數(shù)據(jù)包開始位置
      unsigned int                truesize;
      atomic_t                    users;
}
  1. 數(shù)據(jù)緩沖區(qū)指針 head、data、tail 和 end。

head 指針指向內(nèi)存中已分配的用于承載網(wǎng)絡(luò)數(shù)據(jù)的緩沖區(qū)的起始地址;

data 指針則指向?qū)?yīng)當前協(xié)議層有效數(shù)據(jù)的起始地址。各層的有效數(shù)據(jù)信息包含的內(nèi)容都不一樣:對于傳輸層而言,用戶數(shù)據(jù)和傳輸層協(xié)議頭屬于有效數(shù)據(jù)。 l 對于網(wǎng)絡(luò)層而言,用戶數(shù)據(jù)、傳輸層協(xié)議頭和網(wǎng)絡(luò)層協(xié)議頭是其有效數(shù)據(jù)。 l 對于數(shù)據(jù)鏈路層而言,用戶數(shù)據(jù)、傳輸層協(xié)議頭、網(wǎng)絡(luò)層協(xié)議頭和鏈路層頭 部都屬于有效數(shù)據(jù)。 因此,data 指針的值需隨著當前擁有 sk_buff 的協(xié)議層的變化進行相應(yīng)的移動。

tail 指針則指向?qū)?yīng)當前協(xié)議層有效數(shù)據(jù)負載的結(jié)尾地址,與 data 指針對應(yīng)。

end 指針指向內(nèi)存中分配的數(shù)據(jù)緩沖區(qū)的結(jié)尾,與 head 指針對應(yīng)。

其實,end 指針所指地 址 數(shù) 據(jù) 緩 沖 區(qū) 的 末 尾 還 包 括 一 個 skb_shared_info結(jié)構(gòu)體的空間,這個結(jié)構(gòu)體 存放分隔存儲的數(shù)據(jù)片段,意味著可以將數(shù) 據(jù)包的有效數(shù)據(jù)分成幾片存儲在不同的內(nèi)存空間中。 每一個分片frags的長度上限是一頁。

  • sk_buff結(jié)構(gòu)體的空間
  • image-20210809231316947
  1. 長度信息 len、data_len、truesize。

len是指數(shù)據(jù)包有 效數(shù)據(jù)的長度,包括協(xié)議頭和負載;

data_len 這個成員,它記錄分片的 數(shù)據(jù)長度;

truesize 表示緩存區(qū)的整體長度: sizeof(struct sk_buff) + “傳入 alloc_skb()或dev_alloc_skb()的長度“(但不包括結(jié)構(gòu)體 skb_shared_info 的長度)。

  • sk_buff-> data數(shù)據(jù)包格式
image-20210809231423958
  • 套接字緩沖區(qū)操作

    • (1)分配

      • struct sk_buff *alloc_skb(unsigned int len,int priority);alloc_skb()函數(shù)分配一個套接字緩沖區(qū)和一個數(shù)據(jù)緩沖區(qū),參數(shù) len 為數(shù)據(jù)緩沖區(qū) 的空間大小,以 16 字節(jié)對齊,參數(shù) priority 為內(nèi)存分配的優(yōu)先級
      • struct sk_buff *dev_alloc_skb(unsigned int len);dev_alloc_skb()函數(shù)只是以 GFP_ATOMIC 優(yōu)先級(代表分配過程不能被中斷)調(diào) 用上面的 alloc_skb()函數(shù),并保存 skb->head 和 skb->data 之間的 16 個字節(jié)。
      • 分配成功之后,因為還沒有存放具體的網(wǎng)絡(luò)數(shù)據(jù)包,所以 sk_buff 的 data、tail 指 針都指向存儲空間的起始地址 head,而 len 的大小則為 0。
    • (2)釋放

      • void kfree_skb(struct sk_buff *skb);  //在內(nèi)核內(nèi)部使用,而網(wǎng)絡(luò)設(shè)備 驅(qū)動程序中則必須使用下面3個其一
        void dev_kfree_skb(struct sk_buff *skb); //用于非中斷上下文
        void dev_kfree_skb_irq(struct sk_buff *skb); //用于中斷上下文
        void dev_kfree_skb_any(struct sk_buff *skb); //在中斷和非中斷上下文中皆可采用
        
  • (3)指針移動

    套接字緩沖區(qū)中的數(shù)據(jù)緩沖區(qū)指針移動操作包括 put(放置)、push(推)、 pull(拉)、reserve(保留)等。

    • put 操作(用于在緩沖區(qū)尾部添加數(shù)據(jù)):

      • unsigned char *skb_put(struct sk_buff *skb, unsigned int len); //會檢測放入緩沖區(qū)的數(shù)據(jù)
        unsigned char *__skb_put(struct sk_buff *skb, unsigned int len); //不會檢測放入緩沖區(qū)的數(shù)據(jù)
        

        上述函數(shù)將 tail 指針下移,增加 sk_buff 的 len 值,并返回 skb->tail 的當前值。

    • push 操作(用于在數(shù)據(jù)包發(fā)送時添加頭部

      • unsigned char *skb_push(struct sk_buff *skb, unsigned int len); //會檢測放入緩沖區(qū)的數(shù)據(jù)
        unsigned char *_ _skb_push(struct sk_buff *skb, unsigned int len); //不會檢測放入緩沖區(qū)的數(shù)據(jù)
        

        push 操作在存儲空間的頭部增加一段可以存儲網(wǎng)絡(luò)數(shù)據(jù)包的空間。

    • pull 操作(用于下層協(xié)議向上層協(xié)議移交數(shù)據(jù)包,使 data 指針指向上一層協(xié)議的協(xié)議頭)

      • unsigned char * skb_pull(struct sk_buff *skb, unsigned int len);
        

        skb_pull()函數(shù)將 data 指針下移,并減小 skb 的 len 值。

    • reserve 操作(用于在存儲空間 的頭部預(yù)留 len 長度的空隙)

      • void skb_reserve(struct sk_buff *skb, unsigned int len);
        

        skb_reserve()函數(shù)將 data 指針和 tail 指針同時下移

1.3 網(wǎng)卡驅(qū)動發(fā)包過程

在內(nèi)核中,當上層要發(fā)送一個數(shù)據(jù)包時, 就會調(diào)用網(wǎng)絡(luò)設(shè)備層里net_device數(shù)據(jù)結(jié)構(gòu)的成員hard_start_xmit()將數(shù)據(jù)包發(fā)送出去。

hard_start_xmit()發(fā)包函數(shù)需要我們自己構(gòu)建,該函數(shù)原型如下所示:

int    (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);

這個函數(shù)中需要涉及到sk_buff結(jié)構(gòu)體,含義為(socket buffer)套接字緩沖區(qū),用來網(wǎng)絡(luò)各個層次之間傳遞數(shù)據(jù).

發(fā)包函數(shù)處理步驟:

  • 1、把數(shù)據(jù)包發(fā)出去之前,需要使用netif_stop_queue()來停止上層傳下來的數(shù)據(jù)包;

  • 2.1、設(shè)置寄存器,通過網(wǎng)絡(luò)設(shè)備硬件來發(fā)送數(shù)據(jù)

  • 2.2、當數(shù)據(jù)包發(fā)出去后, 再調(diào)用dev_kfree_skb()函數(shù)來釋放sk_buff,該函數(shù)原型如下:void dev_kfree_skb(struct sk_buff *skb);

  • 3、當數(shù)據(jù)包發(fā)出成功,就會進入TX接收中斷函數(shù),然后更新統(tǒng)計信息,調(diào)用netif_wake_queue()來喚醒,啟動上層繼續(xù)發(fā)包下來;

  • 4、若數(shù)據(jù)包發(fā)出去超時,一直進不到TX中斷函數(shù),就會調(diào)用net_device結(jié)構(gòu)體的*tx_timeout超時成員函數(shù),在該函數(shù)中更新統(tǒng)計信息,并調(diào)用netif_wake_queue()來喚醒。

1.4 網(wǎng)卡驅(qū)動收包過程

接收數(shù)據(jù)包主要是通過中斷函數(shù)處理,來判斷中斷類型,如果等于ISQ_RECEIVER_EVENT表示為接收中斷,然后進入接收數(shù)據(jù)函數(shù),通過netif_rx()將數(shù)據(jù)上交給上層。例如,下圖內(nèi)核中自帶的網(wǎng)卡驅(qū)動:/drivers/net/cs89x0.c

image-20210809232214875

通過獲取的status標志來判斷是什么中斷,如果是接收中斷,就進入net_rx()。

  • 收包函數(shù)處理步驟
    • 1、使用dev_alloc_skb()來構(gòu)造一個新的sk_buff;
    • 2、使用skb_reserve(rx_skb, 2) 將sk_buff緩沖區(qū)里的數(shù)據(jù)包先向后位移2字節(jié),騰出sk_buff緩沖區(qū)里的頭部空間;
    • 3、讀取網(wǎng)絡(luò)設(shè)備硬件上接收到的數(shù)據(jù);
    • 4、使用memcpy()將數(shù)據(jù)復(fù)制到新的sk_buff里的data成員指向的地址處,可以使用skb_put()來動態(tài)擴大sk_buff結(jié)構(gòu)體里中的數(shù)據(jù)區(qū);
    • 5、使用eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里;
    • 6、然后更新統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中。
  • skb_put()函數(shù)
    • 原型:static inline unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
    • 作用:將數(shù)據(jù)區(qū)向下擴大len字節(jié)
    • sk_buff緩沖區(qū)變化圖:
    • image-20210809232643374

1.5 網(wǎng)卡驅(qū)動的注冊與注銷

網(wǎng)絡(luò)設(shè)備驅(qū)動的注冊與注銷使用成對出現(xiàn)的register_netdev()unregister_netdev()函數(shù)完成,這兩個函數(shù)的原型為:

int register_netdev(struct net_device *dev); 
void unregister_netdev(struct net_device *dev); 

這兩個函數(shù)都接收一個 net_device 結(jié)構(gòu)體指針為參數(shù), net_device 的生成和成員的賦值并非一定要由工程師逐個親自動手完成,可以利 用下面的函數(shù)幫助我們填充:

struct net_device *alloc_netdev(int sizeof_priv, const char *name,  
                                void(*setup)(struct net_device*));  

struct net_device *alloc_etherdev(int sizeof_priv)
{ 
    /* 以 ether_setup 為 alloc_netdev 的 setup 參數(shù) */ 
    return alloc_netdev(sizeof_priv, "eth%d", ether_setup); 
} 

alloc_netdev()函數(shù)生成一個 net_device 結(jié)構(gòu)體,對其成員賦值并返回該結(jié)構(gòu)體的指針。第一個參數(shù)為設(shè)備私有成員的大小,第二個參數(shù)為設(shè)備名,第三個參數(shù)為 net_device 的 setup()函數(shù)指針。setup()函數(shù)接收的參數(shù)也為 net_device 指針,用 于預(yù)置 net_device 成員的值。

alloc_etherdev()是 alloc_netdev()針對以太網(wǎng)的“快捷”函數(shù),其中的ether_setup()是由 Linux 內(nèi)核提供的一個對以太網(wǎng)設(shè)備 net_device 結(jié)構(gòu)體中公有成員 快速賦值的函數(shù)

釋放 net_device 結(jié)構(gòu)體 的函數(shù)為:

void free_netdev(struct net_device *dev);

2、編寫虛擬網(wǎng)卡驅(qū)動

虛擬網(wǎng)卡驅(qū)動,也就是說不需要硬件相關(guān)操作,所以就沒有中斷函數(shù),我們通過linux的ping命令來實現(xiàn)發(fā)包,然后在發(fā)包函數(shù)中偽造一個收的ping包函數(shù),實現(xiàn)能ping通任何ip地址。

2.1 編寫步驟

  • init初始函數(shù)

    • 1)使用alloc_netdev()來分配一個net_device結(jié)構(gòu)體
    • 2)設(shè)置net_device結(jié)構(gòu)體的成員
    • 3)使用register_netdev()來注冊net_device結(jié)構(gòu)體
  • 發(fā)包函數(shù)

    • 1)使用netif_stop_queue()來阻止上層向網(wǎng)絡(luò)設(shè)備驅(qū)動層發(fā)送數(shù)據(jù)包
    • 2)調(diào)用收包函數(shù),并代入發(fā)送的sk_buff緩沖區(qū), 里面來偽造一個收的ping包函數(shù)
    • 3)使用dev_kfree_skb()函數(shù)來釋放發(fā)送的sk_buff緩存區(qū)
    • 4)更新發(fā)送的統(tǒng)計信息
    • 5)使用netif_wake_queue()來喚醒被阻塞的上層
  • 收包函數(shù):修改發(fā)送的sk_buff里數(shù)據(jù)包的數(shù)據(jù),使它變?yōu)橐粋€接收的sk_buff。

    • 1)需要對調(diào)上圖的ethhdr結(jié)構(gòu)體 ”源/目的”MAC地址

    • 2)需要對調(diào)上圖的iphdr結(jié)構(gòu)體”源/目的” IP地址

    • 3)使用ip_fast_csum()來重新獲取iphdr結(jié)構(gòu)體的校驗碼

    • 4)設(shè)置上圖數(shù)據(jù)包的數(shù)據(jù)類型,之前是發(fā)送ping包0x08,需要改為0x00,表示接收ping包

    • 5)使用dev_alloc_skb()來構(gòu)造一個新的sk_buff

    • 6)使用skb_reserve(rx_skb, 2);將sk_buff緩沖區(qū)里的數(shù)據(jù)包先后位移2字節(jié),來騰出sk_buff緩沖區(qū)里的頭部空間

    • 7)使用memcpy()將之前修改好的sk_buff->data復(fù)制到新的sk_buff里的data成員指向的地址處:

    • 8)設(shè)置新的sk_buff 其它成員

    • 9)使用eth_type_trans()來獲取上層協(xié)議,將返回值賦給sk_buff的protocol成員里

    • 10)然后更新接收統(tǒng)計信息,最后使用netif_rx( )來將sk_fuffer傳遞給上層協(xié)議中

  • 具體代碼

/*
 * 參考 drivers\net\cs89x0.c
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/ip.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>

static struct net_device *vnet_dev;

static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
{
    /* 參考LDD3 */
    unsigned char *type;
    struct iphdr *ih;
    __be32 *saddr, *daddr, tmp;
    unsigned char   tmp_dev_addr[ETH_ALEN];
    struct ethhdr *ethhdr;
    
    struct sk_buff *rx_skb;
        
    /* 1.對調(diào)ethhdr結(jié)構(gòu)體的"源/目的"的mac地址 */
    ethhdr = (struct ethhdr *)skb->data;
    memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
    memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
    memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);

    /* 2.對調(diào)iphdr結(jié)構(gòu)體的"源/目的"的ip地址 */    
    ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
    saddr = &ih->saddr;
    daddr = &ih->daddr;

    tmp = *saddr;
    *saddr = *daddr;
    *daddr = tmp;
    
    /* 3.使用ip_fast_csum()來重新獲取iphdr結(jié)構(gòu)體的校驗碼*/
    ih->check = 0;         /* and rebuild the checksum (ip needs it) */
    ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
    
    /* 4.設(shè)置數(shù)據(jù)類型*/
    type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
    *type = 0; /*原來0x8表示發(fā)送ping包,現(xiàn)在0表示接收ping包 */
    
    /* 5.構(gòu)造一個新的sk_buff */
    rx_skb = dev_alloc_skb(skb->len + 2);
    
    /* 6.使用skb_reserve騰出2字節(jié)頭部空間*/
    skb_reserve(rx_skb, 2); /* align IP on 16B boundary */  
    
    /* 7.將之前修改好的sk_buff->data復(fù)制到新的sk_buff里 */
    memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len); //用skb_put()擴大sk_buff的數(shù)據(jù)區(qū),避免溢出

    /* 8.設(shè)置新sk_buff的其它成員*/
    rx_skb->dev = dev;
    rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    
    /* 9.使用eth_type_trans()來獲取上層協(xié)議 */
    rx_skb->protocol = eth_type_trans(rx_skb, dev);
    
    /* 10.更新接收統(tǒng)計信息,并向上層傳遞sk_fuffer收包 */
    dev->stats.rx_packets++;
    dev->stats.rx_bytes += skb->len;
    dev->last_rx = jiffies;     //收包時間戳

    // 提交sk_buff
    netif_rx(rx_skb);
}

static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
    static int cnt = 0;
    printk("virt_net_send_packet cnt = %d\n", ++cnt);

    /* 1.停止該網(wǎng)卡的隊列,阻止上層向驅(qū)動層繼續(xù)發(fā)送數(shù)據(jù)包 */
    netif_stop_queue(dev); 
    
    /* 2.真實驅(qū)動要把skb的數(shù)據(jù)寫入網(wǎng)卡 ,但在此先通過emulator_rx_packet模擬 */
    emulator_rx_packet(skb, dev);   /* 構(gòu)造一個假的sk_buff,上報 */

    /* 3.釋放發(fā)送的sk_buff緩存區(qū)*/
    dev_kfree_skb (skb);
    
    /* 4.更新統(tǒng)計信息 */
    dev->stats.tx_packets++;
    dev->stats.tx_bytes += skb->len;
    dev->trans_start = jiffies; //發(fā)送時間戳
    
    /* 5.數(shù)據(jù)全部發(fā)送出去后,喚醒網(wǎng)卡的隊列 (真實網(wǎng)卡應(yīng)在中斷函數(shù)里喚醒)*/
    netif_wake_queue(dev); 
    
    return 0;
}


static const struct net_device_ops vnetdev_ops = {
    .ndo_start_xmit     = virt_net_send_packet,
};

static int virt_net_init(void)
{
    /* 1. 分配一個net_device結(jié)構(gòu)體 */
    vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_ether_dev */

    /* 2. 設(shè)置 */
    //vnet_dev->hard_start_xmit = virt_net_send_packet;
    vnet_dev->netdev_ops    = &vnetdev_ops;

    /* 設(shè)置MAC地址 */
    vnet_dev->dev_addr[0] = 0x08;
    vnet_dev->dev_addr[1] = 0x89;
    vnet_dev->dev_addr[2] = 0x66;
    vnet_dev->dev_addr[3] = 0x77;
    vnet_dev->dev_addr[4] = 0x88;
    vnet_dev->dev_addr[5] = 0x99;

    /* 設(shè)置下面兩項才能ping通 */
    vnet_dev->flags           |= IFF_NOARP;
    vnet_dev->features        |= NETIF_F_IP_CSUM;

    /* 3. 注冊 */
    //register_netdevice(vnet_dev);     //編譯會出錯!
    register_netdev(vnet_dev);
    
    return 0;
}

static void virt_net_exit(void)
{
    unregister_netdev(vnet_dev);
    free_netdev(vnet_dev);
}

module_init(virt_net_init);
module_exit(virt_net_exit);

MODULE_AUTHOR("liangzc1124@163.com");
MODULE_LICENSE("GPL");

2.2 測試

  • 掛載驅(qū)動,可以看到net類下就有了這個網(wǎng)卡設(shè)備,并嘗試ping自己
  • image-20210809233459142

上圖的ping之所以成功,并是因為我們在發(fā)包函數(shù)中偽造了一個來收包,而是因為linux中,ping自己的時候并不調(diào)用(不需要)發(fā)送函數(shù)向網(wǎng)絡(luò)設(shè)備發(fā)送sk_buff數(shù)據(jù)包。

  • ping同網(wǎng)段其它地址
  • image-20210809233646706

ping其它ip地址之所以成功,是因為我們在發(fā)包函數(shù)中利用emulator_rx_packet函數(shù)偽造了一個接收數(shù)據(jù)包,并通過netif_rx()來將收包上傳給上層。

3、移植內(nèi)核自帶的網(wǎng)卡驅(qū)動程序

在移植之前,首先我們來看一下mini2440(對應(yīng)的機器ID為:set machid 7CF)中,是如何支持dm9000網(wǎng)卡的。
進入到入口函數(shù),找到結(jié)構(gòu)體:

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

一般是通過.name這個成員進行匹配的,搜索字符串“dm9000”,找到如下結(jié)構(gòu)體(在平臺文件中:arch\arm\mach-s3c24xx\Mach-mini2440.c):

static struct platform_device mini2440_device_eth = {
    .name       = "dm9000",
    .id     = -1,
    .num_resources  = ARRAY_SIZE(mini2440_dm9k_resource),
    .resource   = mini2440_dm9k_resource,
    .dev        = {
        .platform_data  = &mini2440_dm9k_pdata,
    },
};

然后搜索結(jié)構(gòu)體mini2440_device_eth,找到:

static struct platform_device *mini2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_rtc,
    &s3c_device_usbgadget,
    &mini2440_device_eth,  //在這里
    &mini2440_led1,
    &mini2440_led2,
    &mini2440_led3,
    &mini2440_led4,
    &mini2440_button_device,
    &s3c_device_nand,
    &s3c_device_sdi,
    &s3c_device_iis,
    &uda1340_codec,
    &mini2440_audio,
    &samsung_asoc_dma,
};

然后再搜索:mini2440_devices,找到:

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

這就是把結(jié)構(gòu)體mini2440_devices添加到內(nèi)核,里面的關(guān)于網(wǎng)卡的結(jié)構(gòu)在里面,最終匹配驅(qū)動程序,就可以使用驅(qū)動程序了。
(這就是所謂的平臺設(shè)備平臺驅(qū)動的東西了,把可變的東西抽象出來放到平臺相關(guān)的文件中定義,而我們的驅(qū)動程序,基本上是不需要改變的,它是穩(wěn)定的內(nèi)容,我們移植的時候,只需要把平臺層可變的相關(guān)結(jié)構(gòu)體加上,需要修改的資源,進行修改就可以了)。

而我們用的是smdk2440(對應(yīng)的機器ID為:set machid 16a),然后我在Mach-smdk2440.c中添加以下函數(shù):

/* 以下為liangzc1124@163.com 添加
 * The DM9000 has no eeprom, and it's MAC address is set by
 * the bootloader before starting the kernel.
 */


/* DM9000AEP 10/100 ethernet controller */

#define MACH_SMDK2440_DM9K_BASE (S3C2410_CS4 + 0x300)


static struct resource smdk2440_dm9k_resource[] = {
    [0] = {
        .start = MACH_SMDK2440_DM9K_BASE,
        .end   = MACH_SMDK2440_DM9K_BASE + 3,
        .flags = IORESOURCE_MEM
    },
    [1] = {
        .start = MACH_SMDK2440_DM9K_BASE + 4,
        .end   = MACH_SMDK2440_DM9K_BASE + 7,
        .flags = IORESOURCE_MEM
    },
    [2] = {
        .start = IRQ_EINT7,
        .end   = IRQ_EINT7,
        .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE,
    }
};


static struct dm9000_plat_data smdk2440_dm9k_pdata = {
    .flags      = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};

static struct platform_device smdk2440_device_eth = {
    .name       = "dm9000",
    .id     = -1,
    .num_resources  = ARRAY_SIZE(smdk2440_dm9k_resource),
    .resource   = smdk2440_dm9k_resource,
    .dev        = {
        .platform_data  = &smdk2440_dm9k_pdata,
    },
};

/* 以下為liangzc1124@163.com 添加 */

在結(jié)構(gòu)體smdk2440_devices中添加網(wǎng)卡成員:

static struct platform_device *smdk2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_iis,
    &smdk2440_device_eth, /* lyy:添加 */
};

添加頭文件:

#include <linux/dm9000.h>  /* 以下為liangzc1124@163.com 添加*/

然后重新編譯內(nèi)核。成功。燒寫新內(nèi)核:

S3C2440A # nfs 30000000 192.168.1.101:/home/leon/nfs_root/first_fs/uImage;

S3C2440A # bootm 30000000

然后掛載網(wǎng)絡(luò)文件系統(tǒng):
mount -t nfs -o nolock 192.168.1.101:/home/leon/nfs_root/first_fs /mnt

成功掛載網(wǎng)絡(luò)文件系統(tǒng)。

4、自己編寫網(wǎng)卡驅(qū)動程序

有時候,內(nèi)核自帶的網(wǎng)卡驅(qū)動程序比較老,而我們的硬件有可能比較新,那么我們就不能使用內(nèi)核的網(wǎng)卡驅(qū)動程序了,就需要去移植最新的網(wǎng)卡驅(qū)動程序,那么這種類型的,又該如何移植呢?

4.1 網(wǎng)絡(luò)設(shè)備驅(qū)動程序的模塊加載和卸載函數(shù)

int xxx_init_module(void) 
{ 
    ... 
    /* 分配 net_device 結(jié)構(gòu)體并對其成員賦值 */ 
    xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d", xxx_init); 
    if (xxx_dev == NULL) 
    ... /* 分配 net_device 失敗 */ 

    /* 注冊 net_device 結(jié)構(gòu)體 */ 
    if ((result = register_netdev(xxx_dev))) 
    ... 
} 

void xxx_cleanup(void) 
{ 
    ... 
    /* 注銷 net_device 結(jié)構(gòu)體 */ 
    unregister_netdev(xxx_dev); 
    /* 釋放 net_device 結(jié)構(gòu)體 */ 
    free_netdev(xxx_dev); 
} 

4.2 網(wǎng)絡(luò)設(shè)備的初始化

網(wǎng)絡(luò)設(shè)備的初始化主要需要完成如下幾個方面的工作:

  1. 進行硬件上的準備工作,檢查網(wǎng)絡(luò)設(shè)備是否存在,如果存在,則檢測設(shè)備使用的硬件資源;
  2. 進行軟件接口上的準備工作,分配 net_device 結(jié)構(gòu)體并對其數(shù)據(jù)和函數(shù)指針 成員賦值;
  3. 獲得設(shè)備的私有信息指針并初始化其各成員的值。如果私有信息中包括自旋 鎖或信號量等并發(fā)或同步機制,則需對其進行初始化。

個網(wǎng)絡(luò)設(shè)備驅(qū)動初始化函數(shù)的模板如下所示:

void xxx_init(struct net_device *dev) 
{ 
    /*設(shè)備的私有信息結(jié)構(gòu)體*/ 
    struct xxx_priv *priv; 
 
    /* 檢查設(shè)備是否存在、具體硬件配置和設(shè)置設(shè)備所使用的硬件資源 */ 
    xxx_hw_init(); 
 
    /* 初始化以太網(wǎng)設(shè)備的公用成員 */ 
    ether_setup(dev); 
 
    /*設(shè)置設(shè)備的成員函數(shù)指針*/ 
    dev->open = xxx_open; 
    dev->stop = xxx_release; 
    dev->set_config = xxx_config; 
    dev->hard_start_xmit = xxx_tx; 
    dev->do_ioctl = xxx_ioctl; 
    dev->get_stats = xxx_stats; 
    dev->change_mtu = xxx_change_mtu; 
    dev->rebuild_header = xxx_rebuild_header; 
    dev->hard_header = xxx_header; 
    dev->tx_timeout = xxx_tx_timeout; 
    dev->watchdog_timeo = timeout; 
 
    /*如果使用 NAPI,設(shè)置 pool 函數(shù)*/ 
    if (use_napi) 
    { 
        dev->poll = xxx_poll; 
    } 

    /* 取得私有信息,并初始化它*/ 
    priv = netdev_priv(dev);
    ... /* 初始化設(shè)備私有數(shù)據(jù)區(qū) */ 
} 

4.3 網(wǎng)絡(luò)設(shè)備的打開與釋放

網(wǎng)絡(luò)設(shè)備的打開函數(shù)需要完成如下工作。

1.  使能設(shè)備使用的硬件資源,申請 I/O 區(qū)域、中斷和 DMA 通道等。

2.  調(diào)用 Linux 內(nèi)核提供的 netif_start_queue()函數(shù),激活設(shè)備發(fā)送隊列。 網(wǎng)絡(luò)設(shè)備的關(guān)閉函數(shù)需要完成如下工作。

3.  調(diào)用 Linux 內(nèi)核提供的 netif_stop_queue()函數(shù),停止設(shè)備傳輸包。

4. 釋放設(shè)備所使用的 I/O 區(qū)域、中斷和 DMA 資源。

Linux 內(nèi)核提供的 netif_start_queue()和 netif_stop_queue()兩個函數(shù)的原型為:

void netif_start_queue(struct net_device *dev);  

void netif_stop_queue (struct net_device *dev);

根據(jù)以上分析,可得出網(wǎng)絡(luò)設(shè)備打開和釋放函數(shù)的模板:

int xxx_open(struct net_device *dev) 
{ 
    /* 申請端口、IRQ 等,類似于 fops->open */ 
    ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev); 
    ... 
    netif_start_queue(dev); 
    ... 
} 
 
int xxx_release(struct net_device *dev) 
{ 
    /* 釋放端口、IRQ 等,類似于 fops->close */ 
    free_irq(dev->irq, dev); 
    ... 
    netif_stop_queue(dev); /* can't transmit any more */ 
    ... 
} 

4.3 數(shù)據(jù)發(fā)送流程

(1)網(wǎng)絡(luò)設(shè)備驅(qū)動程序從上層協(xié)議傳遞過來的 sk_buff 參數(shù)獲得數(shù)據(jù)包的有效數(shù) 據(jù)和長度,將有效數(shù)據(jù)放入臨時緩沖區(qū)。

(2)對于以太網(wǎng),如果有效數(shù)據(jù)的長度小于以太網(wǎng)沖突檢測所要求數(shù)據(jù)幀的最小 長度 ETH_ZLEN,則給臨時緩沖區(qū)的末尾填充 0。

(3)設(shè)置硬件的寄存器,驅(qū)使網(wǎng)絡(luò)設(shè)備進行數(shù)據(jù)發(fā)送操作。

int xxx_tx(struct sk_buff *skb, struct net_device *dev) 
{ 
    int len; 
    char *data, shortpkt[ETH_ZLEN]; 
    /* 獲得有效數(shù)據(jù)指針和長度 */ 
    data = skb->data; 
    len = skb->len; 
    if (len < ETH_ZLEN) 
    { 
        /* 如果幀長小于以太網(wǎng)幀最小長度,補 0 */ 
    memset(shortpkt, 0, ETH_ZLEN); 
    memcpy(shortpkt, skb->data, skb->len); 
    len = ETH_ZLEN; 
    data = shortpkt; 
    } 
 
    dev->trans_start = jiffies; /* 記錄發(fā)送時間戳 */ 
 
    /* 設(shè)置硬件寄存器讓硬件把數(shù)據(jù)包發(fā)送出去 */ 
    xxx_hw_tx(data, len, dev); 
    ... 
} 

當數(shù)據(jù)傳輸超時時,意味著當前的發(fā)送操作失敗,此時,數(shù)據(jù)包發(fā)送超時處理函 數(shù) xxx_tx_ timeout()將被調(diào)用。這個函數(shù)需要調(diào)用 Linux 內(nèi)核提供的 netif_wake_queue()函數(shù)重新啟動設(shè)備發(fā)送隊列:

void xxx_tx_timeout(struct net_device *dev) 
{ 
    ... 
    netif_wake_queue(dev); /* 重新啟動設(shè)備發(fā)送隊列 */ 
}

4.3 數(shù)據(jù)接收流程

網(wǎng)絡(luò)設(shè)備接收數(shù)據(jù)的主要方法是由中斷引發(fā)設(shè)備的中斷處理函數(shù),中斷處理函數(shù) 判斷中斷類型,如果為接收中斷,則讀取接收到的數(shù)據(jù),分配 sk_buffer 數(shù)據(jù)結(jié)構(gòu)和數(shù) 據(jù)緩沖區(qū),將接收到的數(shù)據(jù)復(fù)制到數(shù)據(jù)緩沖區(qū),并調(diào)用 netif_rx()函數(shù)將 sk_buffer 傳 遞給上層協(xié)議。完成這一過程的函數(shù)模板:

static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{ 
    ... 
    switch (status &ISQ_EVENT_MASK) 
    { 
        case ISQ_RECEIVER_EVENT: /* 獲取數(shù)據(jù)包 */
            xxx_rx(dev); 
            break; 
            /* 其他類型的中斷 */ 
    } 
} 

static void xxx_rx(struct xxx_device *dev) 
{ 
    ... 
    length = get_rev_len (...); 
    /* 分配新的套接字緩沖區(qū) */ 
    skb = dev_alloc_skb(length + 2); 
    skb_reserve(skb, 2); /* 對齊 */ 
    skb->dev = dev; 

    /* 讀取硬件上接收到的數(shù)據(jù) */ 
    insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1); 
    if (length &1) 
        skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT); 
 
    /* 獲取上層協(xié)議類型 */ 
    skb->protocol = eth_type_trans(skb, dev); 
 
    /* 把數(shù)據(jù)包交給上層 */ 
    netif_rx(skb); 
 
    /* 記錄接收時間戳 */ 
    dev->last_rx = jiffies; 
    ... 
} 

如果是 NAPI 兼容的設(shè)備驅(qū)動,則可以通過 poll 方式接收數(shù)據(jù)包。這種情況下, 我們需要為該設(shè)備驅(qū)動提供 xxx_poll()函數(shù):

static int xxx_poll(struct net_device *dev, int *budget) 
{ 
    //dev->quota 是當前 CPU 能夠從所有接口中接收數(shù)據(jù)包的最大數(shù)目,budget 是在初始化階段分配給接口的 weight 值
    int npackets = 0, quota = min(dev->quota, *budget); 
    struct sk_buff *skb; 
    struct xxx_priv *priv = netdev_priv(dev); 
    struct xxx_packet *pkt; 

    while (npackets < quota && priv->rx_queue) 
    { 
        /*從隊列中取出數(shù)據(jù)包*/ 
        pkt = xxx_dequeue_buf(dev); 
 
        /*接下來的處理,和中斷觸發(fā)的數(shù)據(jù)包接收一致*/ 
        skb = dev_alloc_skb(pkt->datalen + 2); 
        if (!skb) 
        { 
            ... 
            continue; 
        } 
        skb_reserve(skb, 2); 
        memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen); 
        skb->dev = dev; 
        skb->protocol = eth_type_trans(skb, dev); 
        /*調(diào)用 netif_receive_skb 而不是 net_rx 將數(shù)據(jù)包交給上層協(xié)議
          這里體現(xiàn)出了中斷處理機制和輪詢機制之間的差別。
         */ 
        netif_receive_skb(skb); 

        /*更改統(tǒng)計數(shù)據(jù) */ 
        priv->stats.rx_packets++; 
        priv->stats.rx_bytes += pkt->datalen; 
        xxx_release_buffer(pkt); 
    } 
    /* 網(wǎng)絡(luò)設(shè)備接收緩沖區(qū)中的數(shù)據(jù)包都被讀取完了*/ 
    *budget -= npackets; 
    dev->quota -= npackets; 

    if (!priv->rx_queue) 
    { 
        netif_rx_complete(dev); //把當前指定的設(shè)備從 poll 隊列中清除
        xxx_enable_rx_int (…); /* 再次使能網(wǎng)絡(luò)設(shè)備的接收中斷 */ 
        return 0; 
    } 

    return 1; 
} 

雖然 NAPI 兼容的設(shè)備驅(qū)動以 poll 方式接收數(shù)據(jù)包,但是仍然需要首次數(shù)據(jù)包接 收中斷來觸發(fā) poll 過程。與數(shù)據(jù)包的中斷接收方式不同的是,以輪詢方式接收數(shù)據(jù)包 時,當?shù)谝淮沃袛喟l(fā)生后,中斷處理程序要禁止設(shè)備的數(shù)據(jù)包接收中斷。poll 中斷處理函數(shù)如下:

static void xxx_poll_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{ 
    switch (status &ISQ_EVENT_MASK) 
    { 
    case ISQ_RECEIVER_EVENT:
        .../* 獲取數(shù)據(jù)包 */ 
        xxx_disable_rx_int(...); /* 禁止接收中斷 */ 
        netif_rx_schedule(dev); 
        break;
    .../* 其他類型的中斷 */ 
    } 
} 

上述代碼的 netif_rx_schedule()函數(shù)被輪詢方式驅(qū)動的中斷程序調(diào)用,將設(shè) 備的 poll 方法添加到網(wǎng)絡(luò)層的 poll 處理隊列中,排隊并且準備接收數(shù)據(jù)包,最終觸發(fā) 一個 NET_RX_SOFTIRQ 軟中斷,通知網(wǎng)絡(luò)層接收數(shù)據(jù)包。下圖為 NAPI 驅(qū)動 程序各部分的調(diào)用關(guān)系:

image-20220113131412114

4.4 網(wǎng)絡(luò)連接狀態(tài)

網(wǎng)絡(luò)設(shè)備驅(qū)動程序中往往設(shè)置一個定時器來對鏈路狀態(tài)進行周期性地檢查。當定 時器到期之后,在定時器處理函數(shù)中讀取物理設(shè)備的相關(guān)寄存器獲得載波狀態(tài),從而 更新設(shè)備的連接狀態(tài)。

網(wǎng)絡(luò)設(shè)備驅(qū)動可以通過 netif_carrier_on()netif_carrier_off()函數(shù)改變或通知內(nèi)核網(wǎng)絡(luò)設(shè)備的連接狀態(tài)。此外,函數(shù) netif_carrier_ok() 可用于向調(diào)用者返回鏈路上的載波信號是否存在。

void netif_carrier_on(struct net_device *dev); 
void netif_carrier_off(struct net_device *dev); 
int netif_carrier_ok(struct net_device *dev); 

以下代碼顯示了網(wǎng)絡(luò)設(shè)備驅(qū)動用定時器周期檢查鏈路狀態(tài):

static void xxx_timer(unsigned long data) 
{ 
    struct net_device *dev = (struct net_device*)data; 
    u16 link; 
    ...
    if (!(dev->flags &IFF_UP)) 
    { 
        goto set_timer; 
    }
    /* 獲得物理上的連接狀態(tài) */ 
    if (link = xxx_chk_link(dev)) //讀取網(wǎng)絡(luò)適配器硬件的相關(guān)寄存器以獲得鏈路連接狀態(tài)
    { 
        if (!(dev->flags &IFF_RUNNING)) 
        { 
            netif_carrier_on(dev); 
            dev->flags |= IFF_RUNNING; 
            printk(KERN_DEBUG "%s: link up\n", dev->name); 
        } 
    } 
    else    
    { 
        if (dev->flags &IFF_RUNNING) 
        { 
            netif_carrier_off(dev); 
            dev->flags &= ~IFF_RUNNING; 
            printk(KERN_DEBUG "%s: link down\n", dev->name); 
        } 
    } 
 
    set_timer: 
    priv->timer.expires = jiffies + 1 * HZ; 
    priv->timer.data = (unsigned long)dev; 
    priv->timer.function = &xxx_timer; /* timer handler */ 
    add_timer(&priv->timer); 
} 

從上述源代碼還可以看出,定時器處理函數(shù)會不停地利用第 31~35 行代 碼啟動新的定時器以實現(xiàn)周期檢測的目的。那么最初啟動定時器的地方在哪里呢?很 顯然,它最適合在設(shè)備的打開函數(shù)中完成:

 static int xxx_open(struct net_device *dev) 
{ 
    struct xxx_priv *priv = (struct xxx_priv*)dev->priv; 
 
    ... 
    priv->timer.expires = jiffies + 3 * HZ; 
    priv->timer.data = (unsigned long)dev; 
    priv->timer.function = &xxx_timer; /* timer handler */ 
    add_timer(&priv->timer); 
    ...
}

5. CS8900 網(wǎng)卡設(shè)備驅(qū)動實例分析

當 CS8900 處于 I/O 模式下時(這里所說的 CS8900 處于 I/O 模式并非意味著它一定位于 CPU 的 I/O 空間,實 際上,CS8900 I/O 模式下的寄存器仍然映射 ARM 處理器的內(nèi)存空間。因此, 我們直接通過讀/寫寄存器地址ioremap()之后的虛擬地址即可),可以通過以 下幾個 PacketPage 空間內(nèi)的寄存器來控制 CS8900 的行為(括號內(nèi)給出的是寄存器地 址相對于 PacketPage 基地址的偏移):

寄存器 作用
LINECTL(0112H) 決定 CS8900 的基本配置和物理接口,可選擇使用 10BASE-T 接口、AUI 接口或者自動選擇。
RXCTL(0104H) 控制 CS8900 接收特定數(shù)據(jù)包,控制是否接收多播、廣播和單播包。
RXCFG(0102H) RXCFG 控制 CS8900 接收到特定數(shù)據(jù)包后引發(fā)接收中斷,并控制是否使用接收 DMA 和 CRC 校驗。
BUSCT(0116H) BUSCT 可控制芯片的工作模式、DMA 方式、是否使能外部中斷引腳。
BUSST(0138H) 標志網(wǎng)絡(luò)設(shè)備的發(fā)送狀態(tài),如設(shè)備是否準備好發(fā)送。
ISQ(0120H) 網(wǎng)卡芯片的中斷狀態(tài)寄存器

在 I/O 模式下,CS8900 發(fā)送數(shù)據(jù)包的步驟如下:

(1)向控制寄存器 TXCMD 寄存器寫入發(fā)送命令write_reg(TXCMD, send_cmd);。

(2)將發(fā)送數(shù)據(jù)長度寫入 TXLENG 寄存器write_reg(TXLENG, send_len)。

(3)讀取 PacketPage 空間內(nèi)的 BUSST 寄存器,確定其第 8 位被設(shè)置為 Rdy4TxNOW,即設(shè)備處于準備發(fā)送狀態(tài)reg(BusST)&0x100。

(4)將要發(fā)送的數(shù)據(jù)循環(huán)寫入 PORT0 寄存器write_reg(PORT0, data)

(5)將數(shù)據(jù)組織為以太網(wǎng)幀并添加填充位和 CRC 校驗信息,然后將數(shù)據(jù)轉(zhuǎn)化為比特流傳送到網(wǎng)絡(luò)媒介。

在 I/O 模式下,CS8900 接收數(shù)據(jù)包的方法如下:

(1)接收到網(wǎng)絡(luò)適配器產(chǎn)生的中斷,查詢相對于 I/O 基地址偏移 0008H 中斷狀態(tài) 隊列端口,判斷中斷類型為接收中斷。

(2)讀 PORT0 寄存器依次獲得接收狀態(tài) rxStatus、接收數(shù)據(jù)長度 rxLength。

(3)循環(huán)繼續(xù)對 PORT0 寄存器讀取 rxLength 次,獲得整個數(shù)據(jù)包。

(4)驅(qū)動程序進行數(shù)據(jù)包處理并傳遞給上層。

對于一種網(wǎng)絡(luò)設(shè)備的驅(qū)動而言,工程師主要需完成設(shè)備驅(qū)動功能層的設(shè)計。在 16.2~ 16.8節(jié)已經(jīng)給出了設(shè)備驅(qū)動功能層主要數(shù)據(jù)結(jié)構(gòu)和函數(shù)的設(shè)計模板,因此,在編寫CS8900 的這些數(shù)據(jù)結(jié)構(gòu)和函數(shù)時,實際要完成的工作就是用具體的針對 CS8900 的操作來填充模 板,具體包括以下工作:

  1. 填充 CS8900 的私有信息結(jié)構(gòu)體,把 CS8900 特定的數(shù)據(jù)放入這個私有結(jié)構(gòu)體中。在 CS8900 驅(qū)動程序中,這個數(shù)據(jù)結(jié)構(gòu)為 struct net_local。

    在 CS8900 的設(shè)備驅(qū)動程序中,核心數(shù)據(jù)結(jié)構(gòu) net_device 以全局變量的方式定義, 其數(shù)個成員的初始值都被置為空,私有信息結(jié)構(gòu)體為 net_local:

    static struct net_device dev_cs89x0 = 
    { 
     "", 
         0, 0, 0, 0, 
         0, 0, 
         0, 0, 0, NULL, NULL 
    };
    
    struct net_local 
    { 
     struct net_device_stats stats; /* 網(wǎng)絡(luò)設(shè)備狀態(tài)結(jié)構(gòu)體 */ 
     int chip_type; /* 區(qū)分芯片類型:CS89x0 */ 
     char chip_revision; /* 芯片版本字母,如"A" */ 
     int send_cmd; /* 發(fā)送命令: TX_NOW, TX_AFTER_381 或 TX_AFTER_ALL */ 
     ... 
        spinlock_t lock; /* 并發(fā)控制自旋鎖 */ 
    }; 
    

    當芯片的版本字母不同時,net_local 結(jié)構(gòu)體中記錄的 send_cmd 命令將不同。例如, 同樣是 CS8900 網(wǎng)卡,若芯片版本字母為大于等于“F”,則發(fā)送命令為 TX_NOW,而 對于 CS8900A,發(fā)送命令為 TX_AFTER_ALL。

  2. 填充設(shè)備初始化模板,初始化 net_device 結(jié)構(gòu)體,將其注冊入內(nèi)核。net_device 的注冊與注銷在模塊加載與注銷函數(shù)中完成。在 CS8900 驅(qū)動程序中,與此相關(guān)的函數(shù)有:

    struct net_device * _ _init cs89x0_probe(int unit);
    
    int cs89x0_probe1(struct net_device *dev, int ioaddr, int modular); 
    
    int init_module(void);  
    
    void cleanup_module(void); 
    

    設(shè)備的初始化由 net_device 結(jié)構(gòu)體中的 init()函數(shù)完成,這個函數(shù)將在 net_device 被注冊時自動被調(diào)用。init()函數(shù)在 CS8900 網(wǎng)卡的驅(qū)動程序中對應(yīng) 于 cs89x0_probe()函數(shù):

    int __init cs89x0_probe(struct net_device *dev) 
    { 
     int i; 
     
     SET_MODULE_OWNER(dev); 
     DPRINTK(1, "cs89x0:cs89x0_probe(0x%x)\n", base_addr); 
    
     BWSCON = (BWSCON & ~(BWSCON_ST3 | BWSCON_WS3 | BWSCON_DW3)) | 
                 (BWSCON_ST3 | BWSCON_WS3 | BWSCON_DW(3, BWSCON_DW_16)); 
     BANKCON3= BANKCON_Tacs0 | BANKCON_Tcos4 | BANKCON_Tacc14 | 
                 BANKCON_Toch1 | BANKCON_Tcah4 | BANKCON_Tacp6 | BANKCON_PMC1; 
     
     set_external_irq(IRQ_CS8900, EXT_RISING_EDGE, GPIO_PULLUP_DIS); 
    
         for (i = 0; netcard_portlist[i]; i++) 
        { 
             if (cs89x0_probe1(dev, netcard_portlist[i]) == 0) //驗證網(wǎng)卡的存在,并獲取 CS8900所使用的硬件資源
             return 0; 
     } 
     printk(KERN_WARNING "cs89x0: no cs8900 or cs8920 detected." 
           "Be sure to disable PnP with SETUP\n"); 
     return -ENODEV; 
    }
    
    
    static unsigned int netcard_portlist[] __initdata = 
    { 
        vCS8900_BASE + 0x300,    //假設(shè)硬件平臺中網(wǎng)卡的基地址為 vCS8900_BASE + 0x300
        0
    }; 
    
    /*
     *上述 cs89x0_probe1()函數(shù)的流程如下。
     *(1)第 8~20 行分配設(shè)備的私有信息結(jié)構(gòu)體內(nèi)存并初始化,若分配失敗,則直接跳入第 78 行的代碼返回。
     *(2)第 24~26 行從寄存器中讀取芯片的具體類型。
     *(3)第 27~32 行判斷芯片類型,若不是 CS8900 則直接跳入第 77 行的代碼,釋放私有信息結(jié)構(gòu)體并返回。
     *(4)當芯片類型為 CS8900 時,第 34~69 行完成 net_device 設(shè)備結(jié)構(gòu)體的初始化,賦值其屬性和函數(shù)指針。
     */
    
    static int __init cs89x0_probe1(struct net_device *dev, int ioaddr)
    { 
     struct net_local *lp; 
     unsigned rev_type = 0; 
     int ret; 
     
         /* 初始化設(shè)備結(jié)構(gòu)體私有信息 */ 
         if (dev->priv == NULL) 
     { 
             dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); 
         if (dev->priv == 0) 
         { 
             ret = - ENOMEM; 
             goto before_kmalloc; 
         } 
     lp = (struct net_local*)dev->priv; 
     memset(lp, 0, sizeof(*lp)); 
     spin_lock_init(&lp->lock); 
     } 
     lp = (struct net_local*)dev->priv; 
    
     dev->base_addr = ioaddr; 
     /* 讀取芯片類型 */ 
     rev_type = readreg(dev, PRODUCT_ID_ADD); 
     lp->chip_type = rev_type &~REVISON_BITS;
         lp->chip_revision = ((rev_type &REVISON_BITS) >> 8) + 'A';30 
     if (lp->chip_type != CS8900) 
     { 
         printk(_ _FILE_ _ ": wrong device driver!\n"); 
         ret = - ENODEV; 
         goto after_kmalloc;
     } 
     /* 根據(jù)芯片類型和版本確定正確的發(fā)送命令 */ 
     lp->send_cmd = TX_AFTER_ALL; 
     if (lp->chip_type == CS8900 && lp->chip_revision >= 'F') 
         lp->send_cmd = TX_NOW; 
    
     reset_chip(dev); 
    
     lp->adapter_cnf = A_CNF_10B_T | A_CNF_MEDIA_10B_T; 
     lp->auto_neg_cnf = EE_AUTO_NEG_ENABLE;
     printk(KERN_INFO "cs89x0 media %s%s", (lp->adapter_cnf &A_CNF_10B_T) ? "RJ-45": "", (lp->adapter_cnf &A_CNF_AUI) ? "AUI" : ""); 
    
         /* 設(shè)置 CS8900 的 MAC 地址 */ 
     dev->dev_addr[0] = 0x00; 
     dev->dev_addr[1] = 0x00; 
     dev->dev_addr[2] = 0xc0; 
     dev->dev_addr[3] = 0xff; 
     dev->dev_addr[4] = 0xee; 
     dev->dev_addr[5] = 0x08; 
         set_mac_address(dev, dev->dev_addr); 
     
     /* 設(shè)置設(shè)備中斷號 */ 
     dev->irq = IRQ_LAN; 
     printk(", IRQ %d", dev->irq); 
     
     /* 填充設(shè)備結(jié)構(gòu)體的成員函數(shù)指針 */ 
     dev->open = net_open; 
     dev->stop = net_close; 
     dev->tx_timeout = net_timeout; 
     dev->watchdog_timeo = 3 * HZ; 
     dev->hard_start_xmit = net_send_packet; 
     dev->get_stats = net_get_stats; 
     dev->set_multicast_list = set_multicast_list; 
     dev->set_mac_address = set_mac_address; 
     
     /* 填充以太網(wǎng)公用數(shù)據(jù)和函數(shù)指針 */ 
     ether_setup(dev); 
    
     printk("\n"); 
     DPRINTK(1, "cs89x0_probe1() successful\n"); 
     return 0;
         after_kmalloc: kfree(dev->priv); 
     before_kmalloc: return ret; 
    } 
    
    
    static int __init init_cs8900a_s3c2410(void) 
    { 
     struct net_local *lp; 
     int ret = 0; 
     
     dev_cs89x0.irq = irq; 
     dev_cs89x0.base_addr = io; 
     dev_cs89x0.init = cs89x0_probe; //在使用 register_netdev()函數(shù)注net_device 設(shè)備結(jié)構(gòu)體時,cs89x0_probe()函數(shù)會被自動調(diào)用以完成 net_device 結(jié)構(gòu)體的初始化。
     dev_cs89x0.priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); 
     if (dev_cs89x0.priv == 0) 
     { 
         printk(KERN_ERR "cs89x0.c: Out of memory.\n"); 
         return - ENOMEM; 
     } 
     memset(dev_cs89x0.priv, 0, sizeof(struct net_local)); 
     lp = (struct net_local*)dev_cs89x0.priv; 
        
         //為 CS8900 網(wǎng)卡申請了 NETCARD_IO_EXTENT大小的I/O 地址區(qū)域
     request_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT, "cs8900a");
        spin_lock_init(&lp->lock); 
     
     /* 設(shè)置物理接口的正確類型*/ 
     if (!strcmp(media, "rj45")) 
     lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; 
     else if (!strcmp(media, "aui")) 
         lp->adapter_cnf = A_CNF_MEDIA_AUI | A_CNF_AUI; 
     else if (!strcmp(media, "bnc")) 
         lp->adapter_cnf = A_CNF_MEDIA_10B_2 | A_CNF_10B_2; 
     else 
         lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; 
     
     if (duplex == - 1) 
         lp->auto_neg_cnf = AUTO_NEG_ENABLE; 
     
     if (io == 0) 
     { 
         printk(KERN_ERR "cs89x0.c: Module autoprobing not allowed.\n"); 
         printk(KERN_ERR "cs89x0.c: Append io=0xNNN\n"); 
         ret = - EPERM; 
         goto out; 
     } 
         //net_device 設(shè)備結(jié)構(gòu)體的注冊
     if (register_netdev(&dev_cs89x0) != 0) 
     { 
         printk(KERN_ERR "cs89x0.c: No card found at 0x%x\n", io); 
         ret = - ENXIO; 
         goto out; 
     } 
    out: if (ret) 
         kfree(dev_cs89x0.priv); 
     return ret; 
    } 
    
    
    static void _ _exit cleanup_cs8900a_s3c2410(void) 
    { 
     if (dev_cs89x0.priv != NULL) 
     { 
         /* 釋放私有信息結(jié)構(gòu)體 */ 
         unregister_netdev(&dev_cs89x0); 
         outw(PP_ChipID, dev_cs89x0.base_addr + ADD_PORT); 
         kfree(dev_cs89x0.priv); 
         dev_cs89x0.priv = NULL; 
         /* 釋放 CS8900 申請的 I/O 地址區(qū)域 */ 
            release_region(dev_cs89x0.base_addr, NETCARD_IO_EXTENT); 
     } 
    } 
    

    上述函數(shù)第 8~11 行設(shè)置 S3C2410A ARM 處理器的片選,第 13行設(shè)置 ARM 與 CS8900 網(wǎng)卡對應(yīng)的中斷,第 15~18 行循環(huán)檢索 netcard_portlist[ ]數(shù)組中定義的基地址處 是否存在 CS8900 網(wǎng)卡

  3. 填充設(shè)備發(fā)送數(shù)據(jù)包函數(shù)模板,把真實的數(shù)據(jù)包發(fā)送硬件操作填充入 xxx_tx() 函數(shù),并填充發(fā)送超時函數(shù) xxx_tx_timeout()。當發(fā)送數(shù)據(jù)包超時時,CS8900 驅(qū)動程序的數(shù)據(jù)包發(fā)送超時函數(shù)將被調(diào)用,它重 新啟動設(shè)備發(fā)送隊列。在初始化函數(shù)中,CS8900 的數(shù)據(jù)包發(fā)送函數(shù)指針 hard_ start_xmit被賦值為 CS8900 驅(qū)動程序中的 net_send_packet(),這個函數(shù)完成硬件發(fā)送序列。具體代碼如下:

    static int net_send_packet(struct sk_buff *skb, struct net_device  *dev)
    {
        struct net_local *lp = (struct net_local*)dev->priv; 
     
        writereg(dev, PP_BusCTL, 0x0); 
        writereg(dev, PP_BusCTL, readreg(dev, PP_BusCTL) | ENABLE_IRQ); 
     
        spin_lock_irq(&lp->lock);/* 使用自旋鎖阻止多個數(shù)據(jù)包被同時寫入硬件*/ 
        netif_stop_queue(dev); 
     
        /* 初始化硬件發(fā)送序列 */ 
        writeword(dev, TX_CMD_PORT, lp->send_cmd); 
        writeword(dev, TX_LEN_PORT, skb->len); 
     
        /* 檢測硬件是否處于發(fā)送 READY 狀態(tài) */ 
        if ((readreg(dev, PP_BusST) &READY_FOR_TX_NOW) == 0) 
        { 
            spin_unlock_irq(&lp->lock); 
            DPRINTK(1, "cs89x0: Tx buffer not free!\n"); 
            return 1; 
        } 
     
        writeblock(dev, skb->data, skb->len);   /* 將數(shù)據(jù)寫入硬件 */ 
     
        spin_unlock_irq(&lp->lock);     /* 解鎖自旋鎖 */ 
        dev->trans_start = jiffies;     /* 記錄發(fā)送開始的時間戳 */ 
        dev_kfree_skb(skb);             /* 釋放 sk_buff 和數(shù)據(jù)緩沖區(qū) */ 
     
        return 0; 
    }
    
    static void net_timeout(struct net_device *dev)
    { 
        DPRINTK(1, "%s: transmit timed out, %s?\n", dev->name,
            tx_done(dev) ? "IRQ conflict ?" : "network cable problem"); 
     
        net_close(dev); //停止網(wǎng)卡
        writereg(dev, PP_SelfCTL, readreg(dev, PP_SelfCTL) | POWER_ON_RESET); //網(wǎng)卡硬復(fù)位
        net_open(dev); //再次啟動網(wǎng)卡
    } 
    
  1. 填充設(shè)備驅(qū)動程序的中斷處理程序 xxx_interrupt()和具體的數(shù)據(jù)包接收函數(shù) xxx_rx(),填入真實的硬件操作。在 CS8900 驅(qū)動程序中,與此相關(guān)的函數(shù)有:

    irqreturn_t net_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    { 
     struct net_device *dev = dev_id; 
     struct net_local *lp; 
     int ioaddr, status; 
     
     ioaddr = dev->base_addr; 
     lp = (struct net_local*)dev->priv; 
     
     /* 讀取中斷事件類型 */ 
     while ((status = readword(dev, ISQ_PORT))) 
     { 
         DPRINTK(4, "%s: event=%04x\n", dev->name, status); 
         switch (status &ISQ_EVENT_MASK) 
         { 
             case ISQ_RECEIVER_EVENT: 
                 /* 獲得數(shù)據(jù)包 */ 
                 net_rx(dev); 
                 break; 
             ... /* 其他類型中斷 */ 
         } 
     } 
    } 
    
    
    static void net_rx(struct net_device *dev)
    {
        struct net_local *lp = (struct net_local*)dev->priv; 
     struct sk_buff *skb; 
     int status, length; 
     
     int ioaddr = dev->base_addr; 
     
     status = inw(ioaddr + RX_FRAME_PORT); 
     if ((status &RX_OK) == 0) 
     { 
         count_rx_errors(status, lp); 
         return ; 
     } 
     
     length = inw(ioaddr + RX_FRAME_PORT);/* 讀取接收數(shù)據(jù)包的長度 */ 
     
     /* 分配新的套接字緩沖區(qū)和數(shù)據(jù)緩沖區(qū) */ 
     skb = dev_alloc_skb(length + 2); 
     if (skb == NULL) 
     { 
         lp->stats.rx_dropped++; /* 分配失敗,統(tǒng)計被丟棄的包數(shù) */ 
         return ; 
     } 
     skb_reserve(skb, 2); 
     skb->len = length; 
     skb->dev = dev;
     readblock(dev, skb->data, skb->len); /* 從硬件中讀取數(shù)據(jù)包放入數(shù)據(jù)緩沖區(qū) */
        skb->protocol = eth_type_trans(skb, dev);/* 解析收到數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類型 */ 
     
     netif_rx(skb); /* 傳遞給上層協(xié)議 */ 
     
     dev->last_rx = jiffies; /* 記錄最后收到數(shù)據(jù)包的時間戳 */ 
     /* 統(tǒng)計接收數(shù)據(jù)包數(shù)和接收字節(jié)數(shù) */ 
     lp->stats.rx_packets++; 
     lp->stats.rx_bytes += length; 
    } 
    
  1. 填充設(shè)備打開 xxx_open()與釋放 xxx_release()函數(shù)代碼。在 CS8900 驅(qū)動程序 中,與此相關(guān)的函數(shù)有:

    int net_open(struct net_device *dev);  
    
    int net_close(struct net_device *dev);
    
  1. 填充設(shè)備配置與數(shù)據(jù)統(tǒng)計的具體代碼,填充返回設(shè)備沖突的 xxx_stats()函數(shù)。

6. 網(wǎng)卡驅(qū)動移植一般步驟

拿到一塊新的網(wǎng)卡,一般廠家會有自帶的驅(qū)動程序給你,你所要做的就是以下幾個事情:

  1. 根據(jù)網(wǎng)卡與開發(fā)板的連接方式確定網(wǎng)卡的內(nèi)存映射地址iobase,也即確定網(wǎng)卡的片選信號所連接的CPU內(nèi)存的哪一個bank(nGCS?),然后根據(jù)網(wǎng)卡內(nèi)存的大小,在網(wǎng)卡驅(qū)動的初始化函數(shù)中調(diào)用ioremap()進行地址重映射;
  2. 根據(jù)網(wǎng)卡與開發(fā)板的硬件連接圖確定中斷號,并在初始化函數(shù)中利于request_irq()函數(shù),向內(nèi)核申請中斷(確定中斷觸發(fā)方式、中斷處理函數(shù)等);
  3. 根據(jù)網(wǎng)卡datasheet查看網(wǎng)卡的讀寫時序和位寬參數(shù),設(shè)置開發(fā)板相應(yīng)的內(nèi)存控制寄存器BWSCON和BANKCON*。
  4. 將它拷貝到內(nèi)核源代碼的相關(guān)目錄并修改該目錄下的Makefile文件以添加修改后的網(wǎng)卡驅(qū)動目標文件。假設(shè)我們已經(jīng)改好的網(wǎng)卡驅(qū)動程序為:dm9dev9000c.c,編譯也沒有錯誤。
cp dm9dev9000c.c /home/leon/linux-3.4.2/drivers/net/ethernet/davicom/

修改該目錄Makefile文件:

#
# Makefile for the Davicom device drivers.
#

#obj-$(CONFIG_DM9000) += dm9000.o
obj-$(CONFIG_DM9000) += dm9dev9000c.o
  1. 重新編譯內(nèi)核,燒寫新的uImage文件到開發(fā)板中,看看是否可以掛載網(wǎng)絡(luò)根文件系統(tǒng)或者可以設(shè)置IP地址及ping通網(wǎng)絡(luò)。如果可以成功掛載網(wǎng)絡(luò)根文件系統(tǒng),所以網(wǎng)卡移植是成功的。

    nfs 30000000 192.168.1.101:/work/nfs_root/uImage_net_new; 
    
    bootm 30000000
        
    mount -t nfs -o nolock,vers=2 192.168.1.101:/work/nfs_root/fs_mini_mdev_new /mnt
    
  • 我們也可以設(shè)置開機直接掛載網(wǎng)絡(luò)根文件系統(tǒng),這樣就可以直接開機啟動網(wǎng)絡(luò)根文件系統(tǒng)了。

    • uboot中設(shè)置:

      set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.101:/home/leon/nfs_root/first_fs ip=192.168.1.50:192.168.1.101:192.168.1.1:255.255.255.0::eth0:off
          
      save
      
      tftp 30000000 uImage
      
      bootm 30000000
      

ip=192.168.1.50:為單板ip,

192.168.1.101:為服務(wù)器ip,

192.168.1.1為網(wǎng)關(guān),

255.255.255.0為子網(wǎng)掩碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容