基于ARM-Linux的网络驱动程序设计
2012-09-25孟传良
常 锋, 孟传良
0 引言
为了实现 Linux操作系统的诸多网络功能,需要设计出合适的网络驱动程序。为了更好地设计出适合的网络驱动程序,需要对其运行机理有所了解[1]。由于Linux是开放源代码的,这使得在Linux内核中对相关的网络设备驱动部分进行修改提供了方便,可以对其网络驱动程序部分进行分析和改造,使其满足特殊的应用需要。这里就对 Linux内核中的网络驱动程序部分进行了详细的讨论,并结合一个具体的实例,探讨了如何设计出适合的网络驱动程序。
1 网络设备驱动的运行机理
Linux中将设备分为3种类型:字符设备、块设备和网络设备。它们的编写方法大致相同,其中字符设备和块设备可以像文件一样被访问,网络设备的驱动是 Linux操作系统中一种比较特殊的应用,它通常是通过套接字(Socket)这种接口来实现的[2]。在套接字机制中,系统和驱动程序之间通过专门的网络接口负责数据的传递。应用程序不能直接访问网络驱动程序,而只能通过网络子系统与它进行交互,网络驱动程序异步地接收来自外界的数据[3]。
1.1 网络设备驱动的体系结构
Linux系统的优点之一是它具有丰富而稳定的网络协议栈,不同于网络理论中一般的OSI模型,Linux系统的网络协议栈一般分为4层。从上到下分别是:网络协议接口层、网络设备接口层、设备驱动功能层,以及网络设备和网络媒介层[4]。
在这里,网络设备和网络媒介层提供访问物理设备的驱动程序。设备驱动功能层的作用是为上层程序提供访问外部设备的操作接口,并且实现设备的驱动程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是网络设备驱动功能层中各函数的容器。网络设备接口层负责对硬件设备驱动功能层的宏观规划。网络协议接口层为网络层协议提供了一个统一的数据包收发接口,而无需考虑上层协议类型,其中dev_queue_xmit()函数用来发送数据,netif_rx()函数用来接受数据。这一层的设计可以使得上层协议无需考虑具体硬件的实现[5]。
图1 Linux网络驱动体系结构
1.2 网络驱动程序的主要功能
网络驱动程序完成的主要功能包括系统的初始化、数据包的发送和接收等[6]。系统的初始化是通过定义数据结构net_device中的初始化函数init()实现的。初始化函数init()会检测设备的硬件信息来确定是否对该设备分配资源空间,检测完成后会往数据结构 net_device中填充该设备的信息,并将该新设备添加到新的设备列表中。
数据包的发送与接收是网络设备驱动程序的设计中最主要的内容,对该部分驱动程序编写的好坏会直接影响到驱动程序的整体质量。网络设备的发送需要用到协议接口层函数dev_queue_xmit(),通过它来调用数据结构net_device中的hard_header函数指针,读取存放在套接字缓冲区sk_buff里的数据,并将数据发送到物理设备上。数据包的接收通常使用的是 netif_rx()函数,当底层硬件设备驱动程序收到数据包时,硬件会产生一个中断信号,随后系统就会通过调用netif_rx()函数将套接字缓冲区sk_buff里的数据上传至网络层。然后,套接字缓冲区sk_buff中的数据会被 netif_rx_schedule函数在上层协议队列里进行排队,以便接下来的处理。
2 基于ARM的CS8900A驱动程序设计
2.1 CS8900A网卡的介绍
实例中,网络控制器选用的是CS8900A局域网处理芯片。CS8900A是一款由CIRRUS LOGIC公司生产的16位以太网控制器,它具有功能强大、功耗较低、性能优越等特点。该芯片的突出优点是适应性强、配置方便。它可对物理接口、数据传输模式和工作模式等根据不同的需要进行动态的调整,亦可通过对其内部寄存器的调整使之满足不同应用环境的需求。
该芯片的内部功能模块所使用的访问控制块(MAC)的设计完全依照以太网IEEE 802.3标准,具有通用性好的优点,可与当今主流的嵌入式系统兼容。该芯片在处理发送和接收以太网数据帧时主要完成的工作有:检测数据帧的发送和接收是否存在冲突,对发送的数据帧生成帧头信息,对收到的数据帧的帧头信息进行检测。此外,它还可对数据帧进行CSC校验码的生成和检测。当对其发送控制寄存器(TxCMD)进行初始化配置后,可以使得访问控制块能够自动检测数据帧是否有冲突否则要求重传。对于不符合 802.3标准的数据帧,比如少于46 Byte的数据帧,它可自动填充字段以补足不足部分使之满足标准需求[7]。另外,CS8900A网卡还支持多种传输模式,包括I/O模式(该模式为CS8900A存储区的默认模式,配置较方便)和 Memory模式以及DMA模式。
2.2 CS8900A网卡驱动设计分析
网络设备驱动程序的设计需要完成的主要工作是网络设备的注册、网络设备的初始化和注销,以及对发送和接收的数据进行处理等,当遇到传输超时和中断的情况时还要求其能即时做出处理。由于Linux系统是开源的,可以很方便地查看其内核的设备驱动源代码,其中数据结构和函数的模板已由Linux提供。因此,设计驱动程序时,要做的主要工作就是根据具体的网络设备填充相应的操作即可。这里,以CS8900A网卡驱动程序设计的实例来具体说明网络设备驱动程序的具体实现过程。开发环境使用了arm-linux-gcc交叉工具链,采用Linux2.6.26内核以及S3C2410开发板。
2.2.1 网络设备的初始化
网络设备的初始化是设计网络驱动程序的第一步,该过程主要是通过结构体net_device中的init()函数实现的。下面就以实例来说明初始化函数要完成的主要工作,这里将初始化函数设定为__init cs8900_probe():
首先,通过以太网接口的函数ether_setup()的设置,告知系统需要使用的以太网接口,这里选择默认值即可;接下来,需要对 net_device结构体中的大部分的设备成员进行填充;然后,调用 check_mem_region()函数,用来检测要使用的 I/O地址空间,并向系统申请地址空间,这里使用的是以base_addr为起始地址的16个连续的I/O地址空间;接着还需要调用cs8900_read()函数,用以检测CS8900A的网卡信息,这里,设置网卡的INTRQ0为中断信号的输出引脚,并将网卡的 MAC地址写入到CS8900A网卡的IA寄存器之中;最后,通过对初始化函数的定义在 register_netdev时的调用完成CS8900A网卡在Linux系统的设备链表中的注册。
2.2.2 网络设备的打开与关闭
网络设备的打开和关闭是网络驱动程序设计中必不可少的一环。由于Linux系统会响应ifconfig命令,打开或者关闭一个网络接口。这时,ifconfig命令会调用控制I/O设备的ioctl函数获得设备信息和向设备发送控制参数,该调用会设置flag的IFF_UP位对设备进行打开操作,它可通过驱动程序的open方法来实现。这里,就以 cs8900_start()函数为实例来说明如何打开网络设备,该函数要完成的主要工作包括如下:
首先,使用request_irp()函数申请中断,选择中断类型以及要申请中断的网络设备;然后,通过cs8900_set()函数对 CS8900A网卡中的各个寄存器进行设置,并启动该设备;最后,利用 netif_start_queue()函数指定要启动发送队列的设备,以便实现网络的数据传输。
2.2.3 网络数据包的发送
网络数据包的发送和接收是驱动程序设计中要实现的两个最重要的任务。发送数据包的一般过程是:首先,当网络设备加载到 Linux系统上时,会使用到结构体 net_device中的 open方法,并通过hard_header()函数的调用来建立所用到的网络设备的硬件帧头信息。当需要发送一个数据包时,它会调用hard_start_transmit函数,该函数将会使用结构体net_device中的hard_start_xmit()函数,这是一个指向缓冲区sk_buff的指针,用以实现将缓冲区里的数据取出并发送到网络设备上去。如果数据发送成功,hard_start_xmit()方法会释放缓冲区sk_buff里的数据,并返回0;如若硬件设备暂时无法处理,比如硬件忙,则返回为1。这时,如果tbusy的值是非0,则被系统任务认为是硬件忙,需要等到 tbusy为 0时才可发送数据。一般,tbusy的置0任务是由系统的中断来完成的。当硬件发送数据任务结束后,会产生一个中断,这时可以将tbusy置为0,以便通知系统进行下次发送。这里以cs8900_send_start()发送函数为实例,介绍发送数据时所要完成的主要工作:
首先,尝试获取自旋锁,随后中止网络设备的发送队列;然后,告知CS8900A需要发送数据,以及数据的长度;接下来,通过读取 PP_BusST寄存器,获得CS8900A的状态,这要分三种情况,若寄存器TxBidErr置位,表示数据长度不合法,则返回错误,若寄存器Rdy4TxNOW没被置位,表示硬件还没准备好,则返回错误,若一切正常,则开始发送数据帧;还要调用 cs8900_frame_write()函数发送数据;最后,记录数据帧的发送时间,以及释放缓冲区。
2.2.4 网络数据包的接受和中断处理
数据包的接收过程一般是,当数据包到达网络设备时,会由硬件产生一个中断,通知驱动程序进行数据包的接收。中断处理程序会向系统申请一段地址作为sk_buff缓冲区,用以存放接收到的数据。当接收到的数据存放到缓冲区后,还需要对其部分信息进行填充,比如指定设备的结构体、数据帧类型以及链路层数据类型等。最后,通过协议接口层函数 netif_rx()的调用,把数据包交由上层来处理。这里,以接收函数cs8900_receive()为实例,说明网络设备的接收数据过程:
首先,通过读取硬件寄存器,获得CS8900A的状态,以及接收的数据长度;然后,状态解析,若不是RxOK状态,还需判断错误类型,分为三种情况:若为Runt或者Extradata状态,则表示数据包长度不合法,若为CRCerror状态,则表示数据包校验错误,若一切正常,则开始数据的接收;接下来,申请一块缓冲区,并调用 cs8900_frame_read()函数来接收数据;最后,当数据接收完成后,调用netif_rx()函数通知上层。
3 CS8900A驱动程序的编译于测试
3.1 将设备驱动模块编译进内核
网络驱动程序设计好之后,还需要对这个内核模块进行编译,以自定义的这个内核模块作为Linux系统源码的一部分编译出新的系统内核。下面就以Linux2.6.26内核的开发环境为例,介绍如何将CS8900的网卡驱动模块编译进Linux系统内核。
首先,在Linux2.6.26内核的源码主目录(这里为/usr/src/linux2.6.26)中的drivers目录下创建新的目录 cs8900;然后,将之前编写好的 cs8900.c和cs8900.h这两个文件拷贝到该目录下;接着,就可以编写Makefile文件:
#CS8900的Makefile文件
obj -$(CONFIG_DRIVER_CS8900) +=cs8900.o
同样,Kconfig文件的编写如下:
#CS8900的网络接口
menu "网络接口支持"
config DRIVER_CS8900
tristate "CS8900支持"
source "drivers/cs8900/Kconfig"
endmenu
设置好之后,当使用make menuconfig命令时,可以发现Device Drivers选项下会出现“网络接口支持”选项,选择里面的 “CS8900支持”项,可以看到它包括三种状态:即未选中,表示不编译;选中(M),表示编译该模块;选中(*),表示将该模块编译为新系统的一部分。
接下来,对该Linux2.6.26内核进行重新编译,使得该内核可以支持CS8900网卡驱动,然后,将编译后的Linux内核上载到S3C2410的开发板上,配置好相应的网络参数,就可以对网卡驱动程序进行测试了。
3.2 测试结果
网络设备驱动测试主要是从底层硬件测试和上层网络测试这两个方面进行。底层硬件测试的方法如下:在内存中开辟两块区域M1和M2,并按照网络数据包的格式设置初始值,将区域 M1中的数据通过网卡发送出去,通过回环的方式,最终由主机端接收到,存放到区域M2中;再将区域M2中的数据也通过网卡发送出去,并且接收回来。经过多次的循环,比较最终得到的数据包和之前设置的数据包是否是一致的。经过测试后可以发现,最底层的数据发送和接收是正确无误的。
上层网络测试的方法如下:通过网线将目标机和PC主机连接起来,并在目标机端开启tftp服务,主机端则使用FlashFXP软件,从主机分别传送数个小于100 MB的大小不同的文件到目标机,再从目标机将文件传回主机,这个过程进行数次。最后查看平均的网络传输速度。测试结果表明网卡驱动的工作正常,速度稳定在5 Mbit/s左右。
4 结语
该设计结合CS8900A网络芯片为例,分析了网络设备驱动程序运行的机理,详细介绍了实现Linux网络设备驱动程序的一般步骤和关键过程,重点说明每步中关键步骤设置和处理。一般 Linux网络设备驱动程序的设计都有一定的模式[8-9],遵循这个模式,可以快速地设计出需要的驱动程序。分析了Linux内核中的网络驱动程序的工作机理,并结合一个实例,说明如何具体设计网络驱动程序使之满足应用要求。通过对CS8900A网络驱动程序设计的研究,对于其他相类似的网络驱动程序的设计具有一定的参考价值。
[1] 宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2006.
[2] 唐六华,唐建明,向红权.嵌入式Linux下OLED显示功能模块实现[J] .信息安全与通信保密,2009(06): 55-57.
[3] 韦东山.嵌入式Linux应用开发完全手册[M].北京:人民邮电出版社,2008.
[4] 李善平,刘文蜂. Linux内核2.4版源代码分析大全[M].北京:机械工业出版社,2002.
[5] 科尔特.LINUX设备驱动程序[M].北京:中国电力出版社,2005.
[6] 朱小可.尚观科技[M].北京:尚观科技,2008.
[7] 沈金河.Web协议与实现[M].北京:科学出版社,2003.
[8] 陈新, 翁秋华.基于Linux+ARM9的Wi-Fi网络图形化设计与实现[J].通信技术,2012,45(03):60-62,65.
[9] 杨永,万晓榆,樊自甫..基于 ARM9嵌入式网管系统的设计与实现[J].通信技术,2008,41(03):68-70.