Linux網絡設備驅動專題

網絡設備驅動是完成用戶數據包在網絡媒介上發送和接收的設備,他將上層協議傳遞下來的數據包已特定的媒介訪問控制方式發送,並將接收到的數據包傳遞給上層協議。Linux系統對網絡設備驅動定義了4個層次,分別爲:網絡協議接口層、網絡設備接口層、提供實際功能的設備驅動層和網絡設備與媒介層。

1)網絡協議接口層 向網絡層協議提供統一的數據包收發接口,不論上層協議是ARP,還是IP,都通過dev_queue_xmit()函數發送數據,並通過netif_rx()函數接收數據。這一層的存在,使得上層協議獨立於具體的設備。

2)網絡設備接口層 向協議接口層提供統一的用於描述具體網絡設備屬性和操作的結構體net_device,給結構體是設備驅動功能層中各函數的容器。實際上,網絡設備接口層從宏觀上規劃了具體操作硬件設備驅動功能層的結構。

3)設備驅動功能層的各函數是網絡設備接樓層net_device數據結構體的具體成員,是驅動網絡設備硬件完成相應動作的程序,它通過hard_start_xmit()函數啓動發送操作,並通過網絡設備上的中斷觸發接收操作。

4)網絡設備與媒介層是完成數據包發送和接收的物理實現,包括網絡適配器和具體的傳輸媒介,網絡適配器被設備驅動功能層中的函數在物理上驅動。對Linux系統而言,網絡設備和媒介都可以是虛擬的。

在設計具體的網絡設備驅動程序時,需要完成的主要工作是編寫設備驅動功能層的相關函數以填充net_device數據結構體的內容並將net_device註冊入內核。

一、網絡協議接口層

網絡協議接口層最主要的功能是向上層協議提供透明的數據包發送和接收接口。當上層ARP或IP需要發送數據包時,它將調用網絡協議接口層的dev_queue_xmit()函數發送數據包,同時續傳遞給函數一個指向struct sk_buff數據結構體的指針。dev_queue_xmit()函數原型爲:

int dev_queue_xmit(struct sk_buff *skb);

同樣的,上層數據包的接收也通過向netif_rx()函數傳遞一個struct sk_buff數據結構體的指針來完成。netif_rx()函數原型爲:

int netif_rx(struct sk_buff *skb);

sk_buff結構體非常重要,它定義與include/linux.skbuff.h文件中,含義爲“套接字緩衝區”,用於linux網絡子系統中的各層之間傳遞數據,linux網絡子系統數據傳輸的“中樞神經”。

當發送數據包時,linux內核的網絡處理模塊必須建立一個包含要傳輸的數據包的sk_buff,然後將sk_buff遞交給下層,各層在sk_buff中添加不同協議頭直接交給網絡設備發送,。同樣的,當網絡設備從網絡媒介上接收到數據包後,它將接收到的數據包轉換爲sk_buff數據結構體並傳遞給上層,各層剝去相應協議頭直至交給用戶。下面列出了sk_buff結構體的幾個關鍵數據成員:

struct sk_buff{
    struct sk_buff *next;
    struct sk_buff *prev;
    ...
    unsigned int len,data_len;
    __u16 mac_len;
    __u16 hdr_len;
    ...
    __u32 priority;
    ...
    __be16 protocol;
    ...    
    __be16 inner_protocol;
    __u16 inner_transport_header;
    __u16 inner_network_header;
    __u16 inner_mac_header;
    __u16 transpirt_header;
    __u16 network_header;
    __u16 nac_heaader;

    sk_buff_data_t tail;
    sk_buff_data_t end;
    unsigned cahr *head, *data;
    ...
}

尤其注意的是head和end指向緩衝區的頭部和尾部,而data和tail 指向實際數據的頭部和尾部。每一層會在head和data之間填充協議頭,或者在tail和end之間添加新的協議數據。

下面分析套接字緩衝區涉及的操作函數,linux套接字緩衝區支持分配、釋放、變更等功能函數。

(1)分配

Linux內核中用於分配套接字緩衝區的函數有:

struct sk_buff *alloc_skb(unsigned int len,gfp_t priority);
struct sk)buff *dev_alloc_skb(unsigned int len);

alloc_skb()函數分配一個套接字緩衝區和一個數據緩衝區,參數len爲數據緩衝區的大小,通常以Ll_CACHE_BYTES字節(對於ARM爲32)對齊,參數priority爲內存分配的優先級。dev_alloc_skb()函數以GFP_ATOMIC優先級進行skb的分配,原因是該函數經常在設備驅動的接收中斷裏被調用。

(2)釋放

Linux內核中用於釋放套接字緩衝區的函數有:

void kfree_skb(struct sk_buff *skb);
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);

上述函數用於釋放白alloc_skb()函數分配的套接字緩衝區和數據緩衝區。

Linux內核內部使用kfree_skb()函數,而在網絡設備驅動程序中則最好使用dev_kfree_skb()等剩餘函數進行套接字緩衝區和數據緩衝區的釋放。其中,dev_kfree_skb()函數用於非中斷上下文,dev_kfree_skb_irq()函數用於中斷上下文,而dev_kfree_skb_any()函數在中斷和非中斷上下文中皆可採用,他其實是做一個簡單的上下文判斷,然後調用__dev_kfree_dkb_irq()或者dev_kfree_skb(),這從其代碼中也可以看出:

void __dev_kfree_skb_any(struct sk_buff *skb,enmu skb_free_reason reason)
{
    if(in_irq() || irqs_disabled())
        __dev_kfree_skb_irq(skb,reason);
    else
        dev_kfree_skb(skb);
}

(3)變更

在Linux內核中可以使用下列函數在緩衝區尾部添加數據:

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

它會導致skb->tail後移len(skb->tail+=len),而skb->len會增加len的大小(skb->len+=len)。通常,在設備驅動的接收數據處理中會調用此函數。

在Linux內核中可以使用下列函數在緩衝區開頭增加數據:

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

它會導致skd->data前移len(skb->data-=len),而skb->len會增加len的大小(skb->len+=len)。與該函數功能相反的函數是skb_pull(),它可以在緩衝區開頭移除數據,執行的動作是skb->len-=len、skb->data+=len。

對於一個空的緩衝區而言,調動以下函數可以調整緩衝區的頭部:

static inline void skb_reserve(struct sk_buff *skb,int len);

他將skb->data和skb->tail同時 後移len,執行skb->data+=len、skb->tail+=len。內核中存在許多這樣的代碼:

skb=alloc_skb(len+headspace,GFP_KERNEL);
skb_reserve(skb,headspace);
skb_put(skb,len);
memcopy_fromfs(skb->data,tada,len);
pass_to_m_promotcol(skb);

上述代碼先鋒配一個全新的sk_buff,接着調用skb_reserve()騰出頭部空間,之後調用skb_put()騰出數據空間然後把數據複製進來,最後把skb_buff傳遞給協議棧。

二、網絡設備接口層

網絡設備接口層的只要功能是爲千變萬化的網絡設備定義統一、抽象的數據結構體net_device結構體,以不變應萬變,實現多種硬件在軟件層次上的統一。 net_device結構體在內核中指代一個網絡設備,它定義在include/linux/netdevice.h文件中,網路設備驅動程序只需要填充net_device的具體成員並註冊net_device即可實現硬件操作函數與內核的掛接。 net_device是一個巨大的結構體,包含網絡設備的屬性和操作接口,下面介紹一些關鍵成員。

(1)全局信息

cahr name[IFNAMESIZ];  #name是網絡設備的名字

(2)硬件信息

unsigned long mem_end;
unsigend long mem_start;

mem_start和mem_end分別定義了設備所使用的的共享內存的開始和結束地址。

unsigned long base_addr;
unsigned char irq;
unsigned cahr if_port;
unsigned cahr dma;

base_addr爲網絡設備的I/O基地址。irq爲設備使用的中斷號。if_port指定多端口設備使用那一個端口,該字段僅針對多端口設備。例如設備同時支持IF_PORT_10BASE2(同軸電纜)和IF_PORT_10BASET(雙絞線),則可使用該字段。dam指定分配給設備的dma通道。

(3)接口信息

unsigned short hard_header_len; #網絡設備的硬件長度,在以太網設備的初始化函數中,該成員被賦值爲ETH_HLEH,即14
unsigned short type; #type是硬件類型
unsigned char *dev_addr; #用於存放設備的硬件地址,驅動可能提供了設置MAC地址的接口,這會導致用戶設置的MAC地址等存入該成員。
unsigned short flags;

flags至網絡接口標誌,以IFF_(Interface Flags)開頭,部分標誌由內核管理,其他的在接口初始化時被設置以說明設備接口的功能和特性。接口標誌包括IFF_UP(當設備被激活並可以開始發送數據包時,內核設置該標誌)、IFF_AUTOMEDIA(設備可在多種媒介間切換)、IFF_BROADCAST(允許廣播)、IFF_DEBUG、IFF_LOOPBACK(迴環)、IFF_MULTICAST(允許組播)、IFF_NOARP(接口不能執行ARP)和IFF_POINTOPOINT(接口連接到點對點鏈路)等。

(4)設備操作函數

const struct net_device_ops *netdev_ops;

該結構體是網絡設備的一系列硬件操作函數的集合,它定義在include/linux/netdevice.h文件中,這個結構體很大,下面爲其中一些基礎部分:

struct net_device_ops{
    int (*ndo_init)(struct net_device *dev);
    void (*ndo_uninit)(strucr net_device *dev);
    int (*ndo_open)(struct net_device *dev);
    int (*ndo_stop)(struct net_device *dev);
    netdev_t (*ndo_start_xmit)(struct sk_buff *skb,struct net_device *dev);
    u16 (*ndo_select_queue)(struct net_device *dev,struct sk_buff *skb,void *accel_priv,select_queue_fallback_t fallback);
    void (*ndo_change_rx_flags)(struct net_device *dev,int flags);
    void (*ndo_set_rx_mode)(struct net_device *dev);
    int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
    int (*ndo_validate_addr)(struct net_device *dev);
    int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr,int cmd);
    ...
}

ndo_open()函數的作用是打開網絡接口設備,或的設備需要的I/O地址、IRQ、DMA通道等。stop()函數的作用是停止網絡接口設備,與open()函數的作用相反。

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

該函數會啓動數據包的發送,當系統調用驅動程序的xmit函數時,需要向其傳入一個sk_buff結構體指針,以使得驅動程序能從上層傳遞下來的數據包。

viod (*ndo_tx_timeout)(stract net_device *dev);

當數據包的發送超時時,該函數將會被調用,該函數需重新啓動數據包發送過程或重新啓動硬件等措施來恢復網絡設備到正常狀態。

struct net_device_stats *(*ndo_get_stats)(struct net_device *dev);

該函數用於獲得網絡設備的狀態信息,他返回一個net_device_stats結構體指針。net_device_stats結構體保存了詳細的網絡設備流量統計信息,如發送和接收的數據包數、字節數等。

#進行設備特定的I/O控制
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr,int cmd);
#用於配置接口,也可用於改變設備的I/O地址和中斷號
int (*ndo_set_config)(struct net_device *dev,struct ifmap *map);
#用於設置設備的MAC地址
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);

(5)輔助成員

unsigned long trans_start;
unsigned long last_rx;

trans_start記錄最後的數據包開始發送時的時間戳,last_rx記錄左後一次接到數據包時的時間戳,這兩時間戳記錄的都是jiffies,驅動程序應維護這兩個成員。

同城情況下,網絡設備以中斷方式接收數據包,而poll_controller()則採用純輪詢方式,另一種數據接收方式是NAPI(New API),其數據接收流程爲“接收中斷來臨--->關閉接收中斷--->以輪詢的方式接受所有數據包直到接收空---->開啓接收中斷---->接收來臨中斷......”內核提供瞭如下與NAPI相關的API:

static inline void netif_napi_add(struct net_device *dev,struct napi_struct *napi,int(*poll)(struct napi_struct *,int),int weight);
ststic inline void netif_napi_del(struct napi_struct *napi);

以上函數分別用於初始化和移除一個NAPI,netif_napi_add()的poll參數是NAPI要調度執行的輪詢函數。

static inline void napi_enable(struct napi_struct *n);
static inline void napi_disable(struct napi_struct *n);

以上兩個函數用來使能和禁止NAPI調度。

static inline int napi_schedule_prep(struct napi_struct *n);
static inline void napi_schedoule(struct napi_struct *n);

該函數用於檢查NAPI是否可以調度,而napi_schedule()函數用於調度輪詢實例的運行。

static inline void napi_complete(struct napi_struct *n);

該函數在NAPI處理完成的時候調用。

三、設備驅動功能層

net_device結構體成員(屬性和net_device_ops結構體中的函數指針)需要被設備驅動層賦予具體的數值和函數。對於具體的設備XXX,工程師應該編寫相應的設備驅動功能層的函數,這些函數如xxx_open()、xxx-stop()、xxx_tx()等。

由於網絡數據報的接收可由中斷引發,設備驅動功能層的另一個主體將是中斷處理函數,它負責讀取硬件上接收到的數據包並傳送給上層協議,因此可能包含xxx_interrupt()和xxx_rx()函數,前者完成中斷類型判斷等基本工作,後者則需要完成數據包的生成及將其遞交給上層等複雜工作。

對於特定的設備,還可以定義相關的私有數據和操作,並封裝成一個私有信息結構體xxx_private,讓其指針賦值給net_device的私有成員。在xxx_private結構體中可包含設備的特殊屬性和操作、自旋鎖與限號量、定時器以及統計信息等,這都由工程師自定義。在驅動中,需用到私有數據的時候,則使用netdevixce.h中定義的接口:

static inline void *netdev_priv(const struct net_device *dev);

 四、網絡設備驅動的註冊與註銷

網絡設備的註冊與註銷由register_netdev()和unregister_netdev()函數完成,這兩函數的原型爲:

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

 這兩個函數都就收一個net_device結構體指針爲參數,可見net_device數據結構體在網絡設備驅動中的核心地位。

net_device的生成和成員的賦值並不一定要有工程師親自動手逐個完成,可以利用下面的宏幫助填充:

#define alloc_netdev(sizeof_priv,name,setup) \ alloc_netdev(sizeof_priv,name,setup,1,1);
#define alloc_etherdev(sizeof_priv) allco_etherdev_mq(sizeof_priv,1)
#define alloc_etherdev_mq(sizeof_priv,count)  alloc_etherdev_mqs(sizeof_priv,count,count)

alloc_netdev以及alloc_etherdev宏引用的alloc_netdev_mqs()函數原型爲:

struct net_device *alloc_netdev_mqs(int sizeof_priver,count char *name,void (*setup)(struct net_device *),unsigned int txqs,unsigned int rxqs);

alloc_netdev_mqs()函數生成一個net_device結構體,對於成員函數賦值並返回該結構體的指針。第一個參數作爲設備私有成員的大小,第二個參數爲設備名第三個參數爲net_device的setup()函數指針,第四、五個參數爲要分配的發送和接收自隊列的數量。setup()函數的參數也爲struct net_device指針,用於預置net_device成員的值。

free_netdev()完成與alloc_netdev()和alloc_etherdev()函數相反的功能,即釋放net_device結構體的函數:

void free_netdev(struct net_device *dev);

net_device結構體的分配和網絡設備驅動的註冊需要再網絡設備驅動程序初始化時進行,而dev_device結構體的釋放和網絡設備驅動的註銷在設備或驅動被移除的時候執行,如:

static int xxx_register(void)
{
    ...
    //分配net_devicr結構體並對其成員賦值
    xxx_dev = alloc_netdev(sizeof(struct xxx_priv),"sn%d",xxx_init);
    if(xxx_dev == NULL)
        ... //分配net_device失敗
    //註冊net_device結構體
    if(result = register_netdev(xxx_dev))
        ...
}

static void xxx_unregister(void)
{
    ...
    //註銷net_device結構體
    unregister_netdev(xxx_dev);
    //釋放net_device結構體
    free_netdev(xxx_dev);
}

五、網絡設備的額初始化

網絡設備的初始化主要包括如下幾個方面的工作:

  • 進行硬件上的準備工作,檢查網絡設備是否存在,如果存在,則檢測設備所使用的硬件資源。

  • 進行軟件接口上的準備工作,分配net_device結構體並對其數據和函數指針成員賦值。

  • 獲得設備的私有數據指針並初始化各成員的值。如果私有信息中包括自旋鎖或信號量等併發好同步機制,則對其進行初始化。

  • 探測xxx網絡設備是否存在。探測方法類似於數學上的“反證法”,即先假設存在設備xxx,訪問該設備,如果設備的表現與預期一致,就確定設備存在,否則,假設錯誤,xxx設備不存在。

  • 探測設備的具體硬件配置。一些設備驅動編寫的非常通用,對於同類的設備使用統一的驅動,需要再初始時探測設備的具體型號。另外,即便是同一設備,在硬件上的配置也可能不同,也可以探測設備所使用的的硬件資源。

  • 申請設備所需要的硬件資源,如用request_region()函數進行I/O端口的申請等,但這過程可以放在設備的打開函數xxx_open()中完成。

對net_device結構體成員及私有數據的賦值都可能需要與硬件初始化工作協同進行,即硬件檢測出了相應的資源,需要根據檢測結果填充net_device結構體成員和私有數據。

網絡設備驅動的初始化函數模板如下:

void xxx_init(struct net_device *dev)
{
    //設備的私有數據信息結構體
    struct xxx_priv *priv;
    //檢查設備是否存在和設備所使用的的硬件資源
    xxx_hw_init();
    //初始化以太網設備的共用成員
    ether_setup(dev);
    //設置設備成員函數指針
    dev->netdev_ops = &xxx_netdev_ops;
    dev->ethtool_ops = &xxx_ethtool_ops;
    dev->watchdog_timeout = timeout;

    //獲取私有信息,並初始化
    priv = netdev_priv(dev);
    ...
    //初始化設備私有數據區
}

六、網絡設備的打開已釋放

網絡設備的打開函數需要完成如下工作:

  • 使能設備使用的硬件資源,申請I/O區域、中斷和DMA通道等。

  • 調用Linux內核提供的netif_start_queue()函數,激活設備發送隊列。

網絡設備的關閉函數西藥完成如下工作:

  • 調用Linux內核提供的netif_stop_queue()函數,停止設備傳輸包。
  • 釋放設備所使用的I/O區域、中斷和DMA資源。

Linux內核提供的netif_start_queue()和netif_stop_queue()函數的原型爲:

void netif_start_queue(struct net_device *dev);
void netif_stop_queue(struct net_device *dev);

 網絡設備的打開和釋放模板:

static 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);
    ...
}

static int xxx_release(struct net_device *dev)
{
    //釋放端口、IRQ等,類似於fops->close *
    free_irq(dev->irq,dev);
    ...
    netif_stop_queue(dev);
    ...
}

七、數據發送流程

 從網絡設備驅動程序的結構分析可知,Linux網絡子系統在發送數據包時,會調用驅動程序提供的hard_start_transmit()函數,該函數用於啓動數據包的發送。在設備初始化的時候,這個函數指針需要初始化以指向設備的xxx_tx()函數。、

網絡設備驅動完成數據包發送的流程如下:

  1. 網絡設備驅動程序從上層協議傳遞過來的sk_buff參數獲得數據包的有效數據和長度,將有效數據放入臨時緩衝區。

  2. 對於以太網,如果有效數據的長度小於以太網衝突檢測所需要數據幀的最小長度ETH_ZLEN,則給臨時緩衝區的末尾填充0.

  3. 設置硬件寄存器,驅使網絡設備進行數據發送操作。

完成以上3個步驟的網絡設備驅動程序的數據包發送函數模板如下:

int xxx_tx(struct sk_buff *skb,struct net_device *dev)
{
    int len;
    char *data,shortpkt[ETH_ZLEN];
    if(xxx_send_available(...)) //發送隊列爲滿,可以發送
    {
        //獲得有效數據指針和長度 
        data = skb->data;
        len = skb->len;
        if(len < ETH_ZLEN) //如果幀長小於以太網幀最小長度,補0
        {
            memset(shortpkt,0,ETH_ZLEN);
            memcopy(shortpkt,skb->data,skb->len);
            len = ETH_ZLEN;
            data = shortpkt;
        }
    }
    //記錄發送時間戳
    dev-.trans_start = jiffies;
    //設置硬件寄存器,讓硬件把數據包發送出去
    if(avail){
        xxx_hw_tx(data,len,dev);
    }else{
        netif_stop_queue(dev);
        ...
    }
}

這裏強調對netif_stop_queue()的調用,當發送隊列爲滿或其他原因來不及發送當前上層傳下來的數據包時,則調用此函數組織上層繼續向網絡設備驅動傳遞數據包。當忙於發送的數據包被髮送完成後,再以TX結束的中斷處理中,應調用netif_wake_queue()喚醒被阻塞的上層,已啓動它繼續向設備區驅動傳送數據包。

當數據傳輸超時時,意味着當前的發送操作失敗或硬件已陷入未知狀態,此時,數據包發送超時函數xxx_tx_timeout()將被調用。這個函數也需要調用Linux內核提供的netif_wake_queue()函數以重新啓動設備發送隊列。netif_wake_queue()和netif_stop_queue()是數據發送流程中要調用的兩個非常重要的函數,分別用於喚醒和阻止上層向下傳遞數據包,它們定義在include/linux/netdevice.h中:

static inline void netif_wake_queue(struct net_device *dev);
ststic inline void netif_stop_queue(struct net_device *dev);

八、數據接收流

網絡設備接收數據的主要方法是有中斷引發設備的中斷處理函數,中斷處理函數判斷中斷類型,如果爲接收中斷,則讀取接收到的數據,分配sk_buffer數據結構和數據緩衝區,將接收到的數據複製到數據緩衝區,並調用netif_rx()函數將sk_buffer傳遞給上層協議。代碼模板如下:

static void xxx_interupt(int irq,void *dev_id)
{
    ...
    switch(status &ISQ_EVENT_MASK)
    {
        case ISQ_RECEIVER_EVENT:
            //獲取數據包
            xxx_rx(dev);
            //其他類型的中斷
    }
}

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

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

從代碼中可以看出,當設備的中斷處理程序判斷中斷類型爲數據包接收中斷時,它調用定義的xxx_rx()函數完成更深入的數據包接收工作。xxx_rx()函數從硬件讀取到接收數據包有效數據的長度,分配sk_buff和數據緩衝區,然後讀取硬件上接收到的數據並放入數據緩衝區。

九、網咯連接狀態

網絡適配器硬件電路可以檢測出鏈路上書否有載波,載波反應了網絡的連接是否正常。網絡設備驅動可以通過netif_carrier_on()和netif_carrier_off()函數改變設備的連接狀態,如果驅動檢測到連接狀態發生變化,也可以通過cetif_carrier_on()和netif_carrier_off()函數顯示地通知內核。

除了netif_carrier_on()和netif_carrier_off()函數以外,另一個函數netif_carrier_ok()可用於向調用者返回鏈路上的載波信號是否存在。這幾個函數都接收一個net_device設備結構體指針作爲參數,原型分別爲:

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

在網絡設備驅動中可採取一定的手段來檢測和報告鏈路狀態,最常見的方法是採用中斷,其次可以設置一個定時器來對鏈路狀態進行週期性檢查。當定時器到期之後,在定時器處理函數中讀取物理設備相關寄存器以獲得載波狀態,從而更新設備的連接狀態,代碼如下:

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;       
    
    //獲取物理上的連接狀態
    if(link xxx_chk_link(dev)){
        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;//定時器處理函數
    add_timer(&priv->timer);
}

十、參數設置和統計數據

網絡設備的驅動還提供了一些供系統對設備的參數進行設置或讀取設備相關信息的方法。

當用戶調用ioctl()函數,並指向SIOCSIFHWADDR命令時,意味着要設置這個設備的MAC地址。其代碼模板如下:

static int set_mac_address(struct net_device *dev,void *addr)
{
    if(netif_running(dev))
        return -EBUSY; //設備忙
    //設置以太網的MAC地址
    xxx_set_mac(dev,addr);
    return 0;
}

當用戶調用ioctl()函數時,若命令爲SIOCSIFMAP(如在控制檯中運行網絡配置命令ifconfig就會引起之一調用),系統會調用驅動程序的set_config()函數。系統會向set_config()函數傳遞一個ifmap結構體,該結構體主要包含用戶欲設置的設備要使用的I/O地址、中斷信息等。set_config()的函數模板如下:

ststic int xxx_config(struct net_device *dev,struct ifmap *map)
{
    if(netif_running(dev))
        return -EBUSY;
    //假設不允許改變I/O地址
    if(map->base_addr != dev->base_addr){
        printk(KERN_WARNING"xxx: Cant change I/O address\n");
        return -EOPNOTSUPP;
    }
    //假設允許改變IRQ
    if(map->irq != dev->irq)
        dev->irq = map ->irq;
    return 0;
}

當用戶調用ioctl()時,命令類型在SIOCDEVPRIVATE和SIOCDEVPRIVSTE+15之間,系統會調用驅動程序的do_ioctl()函數。以進行設備專用數據的設置。這個設置大多數情況下也並不需要。

驅動程序還應提供get_stats()函數以向用戶反饋設備狀態和統計信息,該函數返回的是一個net_device_stats結構體,代碼如下:

struct net_device_stats *xxx_stats(struct net_device *dev)
{
    ...
    return &dev->stats;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章