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

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

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

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

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

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

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

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

image-20210809230524324

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

其實(shí)盟蚣,end 指針?biāo)傅?址 數(shù) 據(jù) 緩 沖 區(qū) 的 末 尾 還 包 括 一 個(gè) skb_shared_info結(jié)構(gòu)體的空間,這個(gè)結(jié)構(gòu)體 存放分隔存儲(chǔ)的數(shù)據(jù)片段卖怜,意味著可以將數(shù) 據(jù)包的有效數(shù)據(jù)分成幾片存儲(chǔ)在不同的內(nèi)存空間中屎开。 每一個(gè)分片frags的長(zhǎng)度上限是一頁(yè)。

  • sk_buff結(jié)構(gòu)體的空間
  • image-20210809231316947
  1. 長(zhǎng)度信息 len马靠、data_len奄抽、truesize蔼两。

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

data_len 這個(gè)成員,它記錄分片的 數(shù)據(jù)長(zhǎng)度娩贷;

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

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

    • (1)分配

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 3匹舞、當(dāng)數(shù)據(jù)包發(fā)出成功,就會(huì)進(jìn)入TX接收中斷函數(shù)褐鸥,然后更新統(tǒng)計(jì)信息,調(diào)用netif_wake_queue()來(lái)喚醒赐稽,啟動(dòng)上層繼續(xù)發(fā)包下來(lái)叫榕;

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

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

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

image-20210809232214875

通過(guò)獲取的status標(biāo)志來(lái)判斷是什么中斷,如果是接收中斷,就進(jìn)入net_rx()尖昏。

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

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

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

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

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

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

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

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

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

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

void free_netdev(struct net_device *dev);

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

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

2.1 編寫步驟

  • init初始函數(shù)

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

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

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

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

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

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

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

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

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

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

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

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

  • 具體代碼

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

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

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

static struct net_device *vnet_dev;

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

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

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

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

    // 提交sk_buff
    netif_rx(rx_skb);
}

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

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

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


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

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

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

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

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

    /* 3. 注冊(cè) */
    //register_netdevice(vnet_dev);     //編譯會(huì)出錯(cuò)!
    register_netdev(vnet_dev);
    
    return 0;
}

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

module_init(virt_net_init);
module_exit(virt_net_exit);

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

2.2 測(cè)試

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

上圖的ping之所以成功虐唠,并是因?yàn)槲覀冊(cè)诎l(fā)包函數(shù)中偽造了一個(gè)來(lái)收包,而是因?yàn)閘inux中惰聂,ping自己的時(shí)候并不調(diào)用(不需要)發(fā)送函數(shù)向網(wǎng)絡(luò)設(shè)備發(fā)送sk_buff數(shù)據(jù)包疆偿。

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

ping其它ip地址之所以成功,是因?yàn)槲覀冊(cè)诎l(fā)包函數(shù)中利用emulator_rx_packet函數(shù)偽造了一個(gè)接收數(shù)據(jù)包庶近,并通過(guò)netif_rx()來(lái)將收包上傳給上層翁脆。

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

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

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

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

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

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

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

然后再搜索:mini2440_devices,找到:

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

這就是把結(jié)構(gòu)體mini2440_devices添加到內(nèi)核爵川,里面的關(guān)于網(wǎng)卡的結(jié)構(gòu)在里面敷鸦,最終匹配驅(qū)動(dòng)程序,就可以使用驅(qū)動(dòng)程序了寝贡。
(這就是所謂的平臺(tái)設(shè)備平臺(tái)驅(qū)動(dòng)的東西了扒披,把可變的東西抽象出來(lái)放到平臺(tái)相關(guān)的文件中定義,而我們的驅(qū)動(dòng)程序圃泡,基本上是不需要改變的碟案,它是穩(wěn)定的內(nèi)容,我們移植的時(shí)候颇蜡,只需要把平臺(tái)層可變的相關(guān)結(jié)構(gòu)體加上价说,需要修改的資源,進(jìn)行修改就可以了)风秤。

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

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


/* DM9000AEP 10/100 ethernet controller */

#define MACH_SMDK2440_DM9K_BASE (S3C2410_CS4 + 0x300)


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


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

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

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

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

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

添加頭文件:

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

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

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

S3C2440A # bootm 30000000

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void netif_start_queue(struct net_device *dev);  

void netif_stop_queue (struct net_device *dev);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 1; 
} 

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

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

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

image-20220113131412114

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

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

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

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

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

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

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

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

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

當(dāng) CS8900 處于 I/O 模式下時(shí)(這里所說(shuō)的 CS8900 處于 I/O 模式并非意味著它一定位于 CPU 的 I/O 空間,實(shí) 際上羽资,CS8900 I/O 模式下的寄存器仍然映射 ARM 處理器的內(nèi)存空間毫炉。因此, 我們直接通過(guò)讀/寫寄存器地址ioremap()之后的虛擬地址即可)削罩,可以通過(guò)以 下幾個(gè) PacketPage 空間內(nèi)的寄存器來(lái)控制 CS8900 的行為(括號(hào)內(nèi)給出的是寄存器地 址相對(duì)于 PacketPage 基地址的偏移):

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

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

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

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

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

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

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

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

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

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

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

(4)驅(qū)動(dòng)程序進(jìn)行數(shù)據(jù)包處理并傳遞給上層宛徊。

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

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

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

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

    當(dāng)芯片的版本字母不同時(shí)撵枢,net_local 結(jié)構(gòu)體中記錄的 send_cmd 命令將不同民晒。例如, 同樣是 CS8900 網(wǎng)卡锄禽,若芯片版本字母為大于等于“F”潜必,則發(fā)送命令為 TX_NOW,而 對(duì)于 CS8900A沃但,發(fā)送命令為 TX_AFTER_ALL磁滚。

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

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

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

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

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

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

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

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

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

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

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

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

修改該目錄Makefile文件:

#
# Makefile for the Davicom device drivers.
#

#obj-$(CONFIG_DM9000) += dm9000.o
obj-$(CONFIG_DM9000) += dm9dev9000c.o
  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è)置開機(jī)直接掛載網(wǎng)絡(luò)根文件系統(tǒng),這樣就可以直接開機(jī)啟動(dòng)網(wǎng)絡(luò)根文件系統(tǒng)了讶请。

    • uboot中設(shè)置:

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

ip=192.168.1.50:為單板ip祷嘶,

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

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

255.255.255.0為子網(wǎng)掩碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末论巍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子风响,更是在濱河造成了極大的恐慌嘉汰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件状勤,死亡現(xiàn)場(chǎng)離奇詭異鞋怀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)持搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門密似,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人葫盼,你說(shuō)我怎么就攤上這事残腌。” “怎么了贫导?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵抛猫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我孩灯,道長(zhǎng)闺金,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任钱反,我火速辦了婚禮掖看,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘面哥。我一直安慰自己,他們只是感情好毅待,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布尚卫。 她就那樣靜靜地躺著,像睡著了一般尸红。 火紅的嫁衣襯著肌膚如雪吱涉。 梳的紋絲不亂的頭發(fā)上刹泄,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音怎爵,去河邊找鬼特石。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鳖链,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼音诈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惕蹄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起灌侣,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤推捐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后侧啼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牛柒,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年痊乾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焰络。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡符喝,死狀恐怖闪彼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情协饲,我是刑警寧澤畏腕,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站茉稠,受9級(jí)特大地震影響描馅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜而线,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一铭污、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膀篮,春花似錦嘹狞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筷屡,卻和暖如春涧偷,著一層夾襖步出監(jiān)牢的瞬間簸喂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工燎潮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喻鳄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓确封,卻偏偏與公主長(zhǎng)得像除呵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子隅肥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容