Linux网络设备驱动

Linux网络设备驱动

网络设备驱动和字符设备驱动、块设备驱动主要有两点不同:

1、  网络驱动程序不再是对文件进行操作,而是由专门的网络接口struct net_device来实现。应用程序不能直接访问网络驱动程序,只能由网络字系统与它交互。此外,不像字符设备和块设备在/dev目录下有一个特殊文件来表示该设备,网络设备没有这样的入口点。

2、  网络接口在系统初始化时实时生成,对于核心支持的,但不存在的物理网络设备,将不可能有与之对应的net_device结构。

另外,在内核中存在着一张网络接口管理表dev_base,它是指向net_device结构的指针。因为网络设备是通过net_device数据结构来表示的,所以dev_base实际上是一条net_device结构链表的表头,在系统初始化完成以后,系统检测到的网络设备将自动地保存在这张链表中,其中每一个链表单元表示一个存在的物理网络设备,为此,要通过register_netdevnet_device加入到dev_base所指向的指针链。当要发送数据时,网络子系统将根据系统路由表选择相应的网络接口进行数据传输,而当接收到数据包时,通过驱动程序登记的中断服务程序进行数据的接收处理。

以下是网络设备工作原理图:

Linux网络设备驱动

网络设备工作原理图

 

网络接口的结构

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_devicesk_buff

net_device

net_device是网络设备驱动的核心,包含网络适配器的硬件信息(中断、端口、驱动程序函数等)和高层网络协议的网络配置信息(IP地址、子网掩码等)。该结构的定义位于include/linux/netdevice.h。每个net_device结构表示一个网络设备,如eth0eth1等,这些网络设备通过dev_base线性表链接起来。

net_device可分为5个部分:

Ø         全局成员

Ø         硬件相关成员

Ø         接口相关成员

Ø         设备方法成员

Ø         工具相关成员

net_device通过alloc_netdev函数分配,它位于net/core/dev.c文件中。函数原型是:

struct net_device *alloc_netdev(int sizeof_priv, const char *name,

              void (*setup)(struct net_device *))

sizeof_priv:私有数据结构的大小;

name:设备名,eth0eth1等;

setup配置例程,这些例程会初始化部分net_device字段。

sk_buff

Linux内核的网络实现中,使用了一个缓存结构(struct sk_buff)来管理网络报文,这个缓存区也叫套接字缓存。Linux网络各层之间的数据传送都是通过sk_buff它贯穿网络报文收发的整个周期。该结构在内核源码的include/linux/skbuff.h文件中定义。

一个套接字缓存由两部份组成:

Ø         报文数据:存储实际需要通过网络发送和接收的数据。

Ø         管理数据:管理报文所需的数据,在sk_buff结构中有一个head指针指向内存中报文数据开始的位置,有一个data指针指向报文数据在内存中的具体地址。headdata之间申请有足够多的空间用来存放报文头信息。

Linux网络设备驱动

sk_buff结构在内存中的结构示意图

 

针对sk_buff有一组操作函数:

Ø         alloc_skb()dev_alloc_skb():为套接字缓存申请内存空间。这两个函数的定义位于net/core/skbuff.c文件内。

Ø         skb_reserve():为skb_buff缓存结构预留足够的空间来存放各层网络协议的头信息。

运行skb_reserve()sk_buff结构如下图:

Linux网络设备驱动

    运行skb_reserve()sk_buff结构如下图:

Linux网络设备驱动

Ø         skb_put():向后扩大数据区空间,tailroom空间减少,skb->data指针不变,skb->tail指针下移。

Ø         skb_push():向前扩大数据区空间,headroom空间减少,skb->tail指针不变,skb->data指针上移

Ø         skb_pull():缩小数据区空间,headroom空间增大,skb->data指针下移,skb->tail指针不变。

Ø         skb_cloneskb_copy可拷贝一个sk_buff结构,skb_clone方式是clone,只生成新的sk_buff内存区,不会生成新的data内存区,新sk_buffskb->data指向旧data内存区。skb_copy方式是完全拷贝,生成新的sk_buff内存区和data内存区。

 

网卡驱动程序初始化

在网卡驱动的注册过程中,利用了platform2.6内核中将每个设备的资源用结构platform_device来描述,该结构体定义在kernel/include/linux/platform_device.h中:

struct platform_device {

  const char * name;

  u32  id;

  struct device dev;

  u32  num_resources;

  struct resource * resource;

};

该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel/include/linux/ioport.h中:

struct resource {

  const char *name;

  unsigned long start, end;

  unsigned long flags;

  struct resource *parent, *sibling, *child;

};

现在针对smc91.c驱动介绍flatform的注册过程。

kernel/arch/arm/mach-pxa/mainstone.c定义了
static struct resource smc91x_resources[] = {

  [0] = {

    .start  = (MST_ETH_PHYS + 0x300),

    .end    = (MST_ETH_PHYS + 0xfffff),

    .flags  = IORESOURCE_MEM,

    },

  [1] = {

    .start  = IRQ_GPIO(1),

    .end   = IRQ_GPIO(1),

    .flags  = IORESOURCE_IRQ,

    },

};

这里定义了两组resource,它描述了一个网络设备的资源,第1组描述了这个网络设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个网络设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。

有了resource信息,就可以定义platform_device了:

static struct platform_device smc91x_device = {

  .name = "smc91x",

  .id = 0,

  .num_resources = ARRAY_SIZE(smc91x_resources),

  .resource = smc91x_resources,

};

有了platform_device就可以调用函数platform_add_devices向系统中添加该设备了,这里的实现是:

static int __init mainstone_init (void)
{

  platform_add_devices(platform_devices, ARRAY_SIZE(platform_devices));

}

这里的mainstone_init必须在设备驱动加载之前被调用。

这时在smc91.c驱动程序中需要实现结构体struct platform_driver

static struct platform_driver smc_driver = {

  .probe = smc_drv_probe,

  .remove      = smc_drv_remove,

  .suspend = smc_drv_suspend,

  .resume       = smc_drv_resume,

  .driver = {

    .name      = CARDNAME,

    },

};

在驱动初始化函数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_driverdriver.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的整个过程:

Linux网络设备驱动

 

网络设备的打开与关闭

网络设备初始化完毕后,为了使用网络设备,必须先打开初始化好的网络设备。

当执行ifconfig eth0 up时会打开网络设备dev_opennet/core/dev.c),dev_opennet_device的一个方法成员,在初始化时,把smc_open赋给了dev_open,所以随后会执行smc_open函数。

当执行ifconfig eth0 down时会关闭网络设备dev_closenet/core/dev.c),dev_close也是net_device的一个方法成员,在初始化时,把smc_close赋给了dev_close,所以随后会执行smc_close函数。

 

 

发送数据包

通过函数smc_hard_start_xmit发送数据包,该函数在smc_probe中被赋予了net_devicehard_start_xmit域。当内核需要发送数据包时,将调用dev-> hard_start_xmit完成最后的数据包发送。该函数被调用的前提是内核已经将数据包放入了sk_buff缓冲区中。

LAN91C111发送数据包的流程:

1ARM向控制器发送ALLOCATE MEMORY命令设置MMUCOM寄存器,通常设置0x0020MMU为待发送包在控制器内部的packet buffer中分配存储空间。

2ARM查询中断状态寄存器的ALLOC INT位,直到该位被置成1,也可以设置Interrupt Mask中的ALLOC INT位,然后等待硬件中断,这时MMU已经分配好存储空间。而且TX packet number放在Allocation Result寄存器中。

3Allocation Result寄存器中的packet number拷贝到Packet Number:寄存器中,设置Pointer寄存器(设置为TXWRAUTOINC,即0x4000)。然后将包的数据从upper layer发送队列传送到控制器的数据寄存器中。要求依次写人Status Word, Byte Count, destination addresssource addresspacket sizepacket datacontrol word

4ARM向控制器发送"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不为空时产生中断。

6DSP接收到中断后,开始执行中断处理程序,它读入中断状态寄存器,如果产生发送中断,则从FIFO ports寄存器读入发送的包的Packet Number,并将它写到Packet Number寄存器。然后从内存中读人状态字包括设置Pointer寄存器为TXRDAUTOINC,即0x6000,然后从数据寄存器中读入包的状态字,它是EPH寄存器的镜像,根据状态字判断包发送是否成功。如果成功则DSP向控制器发布RELEASE命令设置MMUCOM寄存器,设置为0x00A0,控制器将释放发送包所使用的存储空间,同时设置TX INT Acknowledge寄存器,它将TX completion FIFO中的packet number清除。

smc91x.c的整个发送的过程的流程图如下:

Linux网络设备驱动

接收数据包

数据包的接收是在中断服务程序smc_interrupt中实现的。当物理网络设备接收到新的数据时,便会发送一个硬件中断请求给系统。这时便会进入smc_interrupt。它首先会读取中断的类型,然后根据不同的类修做出相应的处理。

LAN91C111接收数据包的流程:

1含有正确地址的包被接收到,从MMU请求存储空间,并分派一个packet number内部的DMA逻辑产生连续的地址,并将接收到的字写到memory中,如果超界,包被丢弃,存储空间被释放。当检测到包的结束,状态字被写到接收包的最前面,byte count写到第二个字。如果CRC校验正确,packet number被写到RX FIFO,由于RX FIFO非空,产生RCV INT中断如果CRC校验不正确,存储空间被释放,而且不产生中断。

2ARM接收到中断后开始执行中断处理程序,它读入中断状态寄存器,如果产生接收中断(RCV INT位为1),则可以从FIFO ports寄存器得到接收的包的packet number,而且可以从数据寄存器将接收包传送到ARM的内存或外存中。当处理结束,ARM向处理器发布REMOVE AND RELEASE FROM TOP OF RX命令即设置寄存器MMUCOM,即0x0060,释放使用的存储空间和packet number.

Smc91x.c的接收数据过程如下图所示:

Linux网络设备驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值