linux内核编程之网卡驱动

【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】
每一个网络接口由一个net_device结构描述,定义在<linux/netdevice.h>中。
1.首先需要分配设备结构,使用函数:
struct net_device *alloc_netdev(int sizof_priv, const char *name, void (*setup)(struct net_device));
_priv是驱动程序的私有数据大小,这个区是同 net_device 结构一起分配的。name是接口的名字,该名字可以有一个printf风格的%d在里面,内核可以为该名字指定一个可用的接口号,比如使用name为“eth%d”时,内核为其指定接口号为eth0,eth1...。最后setup是一个初始化函数指针,被调用来设置net_device结构体内剩余部分,该函数需要我们自己实现。
网络子系统定义了一个上面函数的包裹函数:
struct net_device *alloc_etherdev(int sizeof_priv);
这个函数分配一个网络设备使用 eth%d 作为参数 name. 它提供了自己的初始化函数 ( ether_setup )来设置几个 net_device 字段, 使用对以太网设备合适的值。因此, 没有驱动提供的初始化函数给 alloc_etherdev; 驱动应当只完成它要求的初始化。

2.在注册设备之前需要完全初始化结构,我们需要实现上面的setup函数,使用函数ether_setup()函数可以设置许多结构体的大部分成员的默认值。
setup函数实现大致如下:
    ether_setup(dev);

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29))
    dev->netdev_ops = &sln_netdev_ops;
#else
    dev->open               = sln_open;
    dev->stop               = sln_stop;
    dev->hard_start_xmit    = sln_tx;
//    dev->get_stats          = sln_stats;
//    dev->change_mtu         = sln_change_mtu;
    ...
#endif


和字符设备类似,每个网络设备都要声明作用其上的函数,一般情况下,一些基本方法要实现:
int (*open)(struct net_device *dev);
打开接口ifconfig up接口时该方法会被调用。该方法需要注册需要的系统资源(IO,IRQ,DMA等),打开硬件及其他设置。
int (*stop)(struct net_device *dev);
停止接口用到的操作,恢复打开接口时的操作。
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
起始报文的发送的方法。完整的报文(协议头和所有)包含在一个 socket缓存区( sk_buff ) 结构.
int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr,
void *saddr, unsigned len);
用之前取到的源和目的硬件地址来建立硬件头的函数(在hard_start_xmit 前调用)。 它的工作是将作为参数传给它的信息组织成
一个合适的特定于设备的硬件头. eth_header 是以太网类型接口的缺省函数, ether_setup 针对性地对这个成员赋值。
int (*rebuild_header)(struct sk_buff *skb);
用来在 ARP 解析完成后但是在报文发送前重建硬件头的函数. 以太网设备使用的缺省的函数使用 ARP 支持代码来填充报文缺失的信息。
void (*tx_timeout)(struct net_device *dev);
由网络代码在一个报文发送没有在一个合理的时间内完成时调用的方法,可能是丢失一个中断或者接口被锁住. 它应当处理这个问题并恢复报文发送。
struct net_device_stats *(*get_stats)(struct net_device *dev);
任何时候当一个应用程序需要获取接口的统计信息, 调用这个方法. 例如,当 ifconfig 或者 netstat -i 运行时。
int (*set_config)(struct net_device *dev, struct ifmap *map);
改变接口配置. 这个方法是配置驱动的入口点。设备的 I/O 地址和中断号可以在运行时使用 set_config 来改变。这种能力可由系统管理员在接口没有探测到时使用。


3.结构初始化完成之后,需要注册设备,使用函数:
int register_netdev(struct net_device *dev);
传入参数为第一步返回的结构体。

4.实现设备方法。
open方法的实现:
设置MAC地址,将硬件地址拷贝到dev->dev_addr中。
启动接口发送队列,内核提供一个函数来启动队列:
void netif_start_queue(struct net_device *dev);
一般open实现大致如下:
int sln_open(struct net_device *dev)
{
    memcpy(dev->dev_addr, macaddr, ETH_ALEN);
    netif_start_queue(dev);

    return 0;
}

stop方法的实现:
void netif_stop_queue(struct net_device *dev);
是 netif_start_queue 的对立面; 它标志设备为不能再发送任何报文。这个函数必须在接口关闭( 在 stop 方法中 )时调用。

hard_start_xmit方法实现:
linux 网络子系统在发送数据包时,会调用驱动程序提供的 hard_start_xmit()函数,该函数用于启动数据包的发送。在设备初始化的时候,这个函数指针需被初始化指向设备的 xxx_tx()函数。
网络设备驱动完成数据包发送的流程如下。
(1)网络设备驱动程序从上层协议传递过来的 sk_buff 参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区。
(2)对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填充 0。
(3)设置硬件的寄存器,驱使网络设备进行数据发送操作。
当发送队列为满或因其他原因来不及发送当前上层传下来的包,则调用此函数阻止上层继续向网络设备驱动传递数据包。当忙于发送的数据包被发送完成后,TX 结束的中断处理中,应该调用 netif_wake_queue()唤醒被阻塞的上层,以启动它继续向网络设备驱动传送数据包。

数据接受的实现:
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx()函数将 sk_buffer 传递给上层协议。

get_status:
驱动程序还应提供 get_stats()函数用以向用户反馈设备状态和统计信息,该函数返回的是一个net_device_stats结构体.

你可能感兴趣的:(网卡,linux内核,以太网)