Linux网络设备驱动
网络设备驱动和字符设备驱动、块设备驱动主要有两点不同:
1、
2、
另外,在内核中存在着一张网络接口管理表dev_base,它是指向net_device结构的指针。因为网络设备是通过net_device数据结构来表示的,所以dev_base实际上是一条net_device结构链表的表头,在系统初始化完成以后,系统检测到的网络设备将自动地保存在这张链表中,其中每一个链表单元表示一个存在的物理网络设备,为此,要通过register_netdev把net_device加入到dev_base所指向的指针链。当要发送数据时,网络子系统将根据系统路由表选择相应的网络接口进行数据传输,而当接收到数据包时,通过驱动程序登记的中断服务程序进行数据的接收处理。
以下是网络设备工作原理图:
网络设备工作原理图
网络接口的结构
Linux的网络接口分为四部份:网络设备接口部份,网络接口核心部份,网络协议部份,以及网络接口socket层。
网络设备接口部份主要负责从物理介质接收和发送数据。实现的文件是linux/driver/net/smc91x.c。发送数据时通过smc_hard_start_xmit来实现。接收数据包时在中断函数smc_interrupt中实现,最后在中断中调用网络接口核心层的函数netif_rx把数据包送到上一层,在netif_rx()函数执行完后,这个包的数据就脱离了网卡驱动范畴。
网络接口核心部份是整个网络接口的关键部分,它的主要实现文件在linux/net/core目录下,其中linux/net/core/dev.c为主要管理文件。它的上层是具体的网络协议,下层是驱动程序。它通过函数dev_queue_xmit()(net/core/dev.c)向上层提供统一的发送接口,也就是说无论是什么协议(是IP,还是ARP协议),想发送数据的时,调用这个函数就可以了,dev_queue_xmit()做的工作最后会落实到dev->hard_start_xmit(),而dev->hard_start_xmit()会调用实际的驱动程序smc_hard_start_xmit来完成发送的任务。同时网络接口核心层又负责把来自下层的包向合适的协议配送,会根据不同的协议选择不同的接收函数。
网络协议部份是各种具体协议实现的部份。各种具体协议实现的源码在linux/net/目录下相应的名称。例如:TCP/IP(IPv4)协议,实现的源码在linux/net/ipv4,其中linux/net/ipv4/af_inet.c是主要的管理文件。
网络接口Socket层为用户提供的网络服务的编程接口。主要的源码在linux/net/socket.c。
重要数据结构
在网络驱动程序中有两个重要的数据结构,分别是net_device和sk_buff。
net_device
net_device是网络设备驱动的核心,包含网络适配器的硬件信息(中断、端口、驱动程序函数等)和高层网络协议的网络配置信息(IP地址、子网掩码等)。该结构的定义位于include/linux/netdevice.h。每个net_device结构表示一个网络设备,如eth0、eth1等,这些网络设备通过dev_base线性表链接起来。
net_device可分为5个部分:
Ø
Ø
Ø
Ø
Ø
net_device通过alloc_netdev函数分配,它位于net/core/dev.c文件中。函数原型是:
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
sizeof_priv:私有数据结构的大小;
name:设备名,eth0,eth1等;
setup:配置例程,这些例程会初始化部分net_device字段。
sk_buff
在Linux内核的网络实现中,使用了一个缓存结构(struct sk_buff)来管理网络报文,这个缓存区也叫套接字缓存。Linux网络各层之间的数据传送都是通过sk_buff,它贯穿网络报文收发的整个周期。该结构在内核源码的include/linux/skbuff.h文件中定义。
一个套接字缓存由两部份组成:
Ø
Ø
sk_buff结构在内存中的结构示意图
针对sk_buff有一组操作函数:
Ø
Ø
运行skb_reserve()前sk_buff结构如下图:
Ø
Ø
Ø
Ø
网卡驱动程序初始化
在网卡驱动的注册过程中,利用了platform。在2.6内核中将每个设备的资源用结构platform_device来描述,该结构体定义在kernel/include/linux/platform_device.h中:
struct platform_device {
};
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel/include/linux/ioport.h中:
struct resource {
};
现在针对smc91.c驱动介绍flatform的注册过程。
在kernel/arch/arm/mach-pxa/mainstone.c定义了
static struct resource smc91x_resources[] = {
};
这里定义了两组resource,它描述了一个网络设备的资源,第1组描述了这个网络设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个网络设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。
有了resource信息,就可以定义platform_device了:
static struct platform_device smc91x_device = {
};
有了platform_device就可以调用函数platform_add_devices向系统中添加该设备了,这里的实现是:
static int __init mainstone_init (void)
{
}
这里的mainstone_init必须在设备驱动加载之前被调用。
这时在smc91.c驱动程序中需要实现结构体struct platform_driver:
static struct platform_driver smc_driver = {
};
在驱动初始化函数static int __init smc_init(void)中调用platform_driver_register()注册smc_driver,需要注意的是smc91x_device结构中name元素和smc_driver结构中driver.name必须是相同的,这样在platform_driver_register()注册时会对所有已注册的platform_device中的name和当前注册的smc_driver中的driver.name进行比较,只有找到相同的名称的platform_device才能注册成功,当注册成功后会调用platform_driver结构元素probe函数指针,这里就是smc_drv_probe。当进入probe函数后,需要获取设备的资源信息,获取资源的函数有:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
//根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num)
//获取资源中的中断号。
struct resource * platform_get_resource_byname(struct platform_device *dev, unsigned int type, char *name)
//根据参数name所指定的名称,来获取指定的资源。
int platform_get_irq_byname(struct platform_device *dev, char *name)
//根据参数name所指定的名称,来获取资源中的中断号。
接下来用一个流程图表示smc_drv_probe的整个过程:
网络设备的打开与关闭
网络设备初始化完毕后,为了使用网络设备,必须先打开初始化好的网络设备。
当执行ifconfig eth0 up时会打开网络设备dev_open(net/core/dev.c),dev_open是net_device的一个方法成员,在初始化时,把smc_open赋给了dev_open,所以随后会执行smc_open函数。
当执行ifconfig eth0 down时会关闭网络设备dev_close(net/core/dev.c),dev_close也是net_device的一个方法成员,在初始化时,把smc_close赋给了dev_close,所以随后会执行smc_close函数。
发送数据包
通过函数smc_hard_start_xmit发送数据包,该函数在smc_probe中被赋予了net_device的hard_start_xmit域。当内核需要发送数据包时,将调用dev-> hard_start_xmit完成最后的数据包发送。该函数被调用的前提是内核已经将数据包放入了sk_buff缓冲区中。
LAN91C111发送数据包的流程:
1、ARM向控制器发送ALLOCATE MEMORY命令(设置MMUCOM寄存器,通常设置0x0020)。MMU为待发送包在控制器内部的packet buffer中分配存储空间。
2、ARM查询中断状态寄存器的ALLOC INT位,直到该位被置成1,也可以设置Interrupt Mask中的ALLOC INT位,然后等待硬件中断,这时MMU已经分配好存储空间。而且TX packet number放在Allocation Result寄存器中。
3、将Allocation Result寄存器中的packet number拷贝到Packet Number:寄存器中,设置Pointer寄存器(设置为TX,WR,AUTOINC,即0x4000)。然后将包的数据从upper layer发送队列传送到控制器的数据寄存器中。要求依次写人Status Word, Byte Count, destination address,source address,packet size,packet data,control word。
4、ARM向控制器发送"ENQUEUE PACKET NUMBER TO TX FIFO“命令(设置MMUCOM寄存器,通常设置0x00C0),这个命令将Packet Number寄存器中的packet number拷贝到TX FIFO,说明发送的包已经放入队列中。同时设置Transmit control寄存器中的TXENA位,启动transmitter。到目前为止,ARM的设置工作完成,它可以IDLE,直到接收到一个控制器产生的发送中断。
5、当控制器传送完包以后,memory中的第一个字(16bit)被CSMA/CD写入相应的Status Word,然后将TX FIFO中的packet number移到TX completion FIFO,当TX completion FIFO不为空时产生中断。
6、DSP接收到中断后,开始执行中断处理程序,它读入中断状态寄存器,如果产生发送中断,则从FIFO ports寄存器读入发送的包的Packet Number,并将它写到Packet Number寄存器。然后从内存中读人状态字(包括设置Pointer寄存器为TX,RD,AUTOINC,即0x6000,然后从数据寄存器中读入包的状态字),它是EPH寄存器的镜像,根据状态字判断包发送是否成功。如果成功则DSP向控制器发布RELEASE命令(设置MMUCOM寄存器,设置为0x00A0),控制器将释放发送包所使用的存储空间,同时设置TX INT Acknowledge寄存器,它将TX completion FIFO中的packet number清除。
smc91x.c的整个发送的过程的流程图如下:
接收数据包
数据包的接收是在中断服务程序smc_interrupt中实现的。当物理网络设备接收到新的数据时,便会发送一个硬件中断请求给系统。这时便会进入smc_interrupt。它首先会读取中断的类型,然后根据不同的类修做出相应的处理。
LAN91C111接收数据包的流程:
1、含有正确地址的包被接收到,从MMU请求存储空间,并分派一个packet number,内部的DMA逻辑产生连续的地址,并将接收到的字写到memory中,如果超界,包被丢弃,存储空间被释放。当检测到包的结束,状态字被写到接收包的最前面,byte count写到第二个字。如果CRC校验正确,packet number被写到RX FIFO,由于RX FIFO非空,产生RCV INT中断,如果CRC校验不正确,存储空间被释放,而且不产生中断。
2、ARM接收到中断后开始执行中断处理程序,它读入中断状态寄存器,如果产生接收中断(RCV INT位为1),则可以从FIFO ports寄存器得到接收的包的packet number,而且可以从数据寄存器将接收包传送到ARM的内存或外存中。当处理结束,ARM向处理器发布REMOVE AND RELEASE FROM TOP OF RX命令(即设置寄存器MMUCOM,即0x0060),释放使用的存储空间和packet number.
Smc91x.c的接收数据过程如下图所示: