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;
}

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值