引言
为提高网络应用的可移植性,通用网络软件体制将无线节点中的程序分为两部分:一部分为与底层硬件相关的接收和发送程序,针对不同的无线节点,编写相对应的底层收发函数;另一部分则为与硬件无关的应用程序,如直接针对网络接口来编写上层应用程序,就可以通过公用接口在TCP/IP协议的应用层中实现。当底层无线节点的硬件电路发生变化时,只需重新编写节点的发送和接收函数即可实现原有功能,而无线节点的上层应用程序均可复用。要将TCP/IP协议栈上的应用程序和无线节点关联起来,则必须为无线节点编写相应的驱动程序。因此,为带有USB接口的无线网络节点编写一个通用的网卡驱动程序,将无线节点在Linux嵌入式系统中映射为无线网卡就显得十分必要。
1 系统设计方案
本系统从功能结构上可分为:硬件层、设备驱动层、Linux操作系统、应用层4部分。硬件层中无线节点由STM32F103处理器、Chirp通信模块组成,并通过USB接口与嵌入式硬件平台相连。无线网卡驱动的主要功能为:屏蔽设备硬件细节,为网络协议栈提供统一的数据接口,根据802.11协议对数据进行相关处理,并驱动硬件设备完成数据接收、发送等功能[2]。无线网络节点系统设计方案如图1所示[3]。

图1 无线网络节点系统结构图
2 开发环境
开发环境分为软件和硬件两部分。软件平台选择免费开源且网络资源丰富的Linux操作系统。
硬件平台选择基于S3C6410处理器的mini6410开发板,无线节点则由STM32F103处理器、Chirp通信模块构成。无线节点结构如图1硬件层部分。
如图1所示,STM32F103处理器通过SPI接口与Chirp通信模块进行数据交互。发送过程中:嵌入式模块将数据通过USB接口发送给STM32F103处理器,STM32F103处理器对数据进行相关处理后,再通过SPI接口发送给Chirp通信模块,最后通过天线发送出去。接收过程与发送过程类似:天线收到数据后,Chirp通信模块对数据进行分析校验、处理等,然后由SPI接口传给微处理器,最后通过USB接口提交给嵌入式模块。
无线节点软件部分主要包括:微处理器和射频模块两者的初始化、发送和接收函数、差错重传、接收确认等。图2为无线节点软件流程图。

图2 无线节点软件流程图
3 USB无线网卡驱动
3.1 USB设备
USB设备在逻辑上分为设备、配置、接口和端点4个层次。接口是由一个或多个端点组成,代表USB设备的一个基本功能。端点是USB设备之间通信的基本方式,每个端点具有唯一的地址。在硬件上,主机和设备之间通过端点进行通信[3]。而在驱动软件中,USB请求块(USB request block,urb)是USB设备驱动中用来描述USB主机与USB设备通信所用的核心数据结构,也是用来传递数据的基本单位。在USB驱动程序中,主要利用usb_alloc_urb()、usb_fill_bulk_urb()、usb_submit_urb()等函数来完成urb结构体的初始化,填充和提交。
3.2 网卡驱动
网卡是无线局域网的重要组成部分,主要完成数据包的发送和接收。网卡将上层网络协议栈传递下来的数据进行封装处理、调制并发送出去,同时将收到的数据包解调后,向上传递给协议栈[4]。
网卡驱动程序主要实现的功能是:为上层协议栈提供统一的、透明的数据包发送和接收接口[4]。编写Linux网卡驱动主要分为三个步骤:首先,分配得到一个net_device结构体;其次,对这个结构体进行设置和填充;最后,向Linux内核注册驱动程序。
带USB接口的无线网卡通过USB总线与主机相连,从嵌入式系统来看,USB无线网卡包括USB设备和网卡设备这两部分,因此USB无线网卡驱动首先要实现USB驱动,然后再实现网卡驱动[1,5]。USB接口的无线网卡软件结构层次如图1所示。无线节点通过USB接口接入嵌入式系统,其设备驱动程序在上层为网络协议栈提供接口函数,同时在下层通过USB接口与无线节点进行数据交互。
3.3 无线网卡驱动的实现
根据USB无线网卡收发数据的流程和Linux设备驱动的编写方法,USB无线网卡驱动的工作流程如图3所示[3]。

图3 无线网卡驱动流程图
USB无线网卡驱动首先应注册USB设备,在Linux系统中通过构建usb_driver数据结构来描述USB设备驱动。
static struct usb_driver stm32_802_driver = {
.name= "stm32_802",
.id_table= products,
.probe= stm32_802_probe,
.disconnect= stm32_802_disconnect
};
id_table主要包括驱动所支持的USB设备的列表,列表中包含vendor_id(厂商号)和product_id(产品号)。因此,在驱动程序中需要将STM32F103处理器中USB模块的vendor_id(0x0483)和product_id(0x5740),添加到id_table列表中。当无线节点硬件发生改变时,只需要将相应的USB设备的厂商号和产品号添加到设备列表中,就能得到驱动程序的支持。当USB设备插入主机后,若USB设备的属性和id_table中的信息匹配正确,则进一步调用probe函数。当USB设备被拔出主机或断开时,则调用disconnect()函数。
探测过程:该过程由stm32_802_probe函数来实现,包括创建网络设备,构建网络设备与USB设备的关联,初始化接收和发送函数队列,设置网卡MAC地址,注册网络设备。探测函数实现举例如下:
dev=ieee80211_alloc_hw(sizeof(*priv32), &stm32_802_ops); &stm32_802_ops);//分配网络设备
SET_IEEE80211_DEV(dev, &intf->dev);
priv32->udev = xdev;
skb_queue_head_init(&priv32->rxq);//初始化接收队列
skb_queue_head_init(&priv32->txq);//初始化发送队列
usb_set_intfdata(intf, dev);
SET_IEEE80211_PERM_ADDR(dev, mac_addr);
err = ieee80211_register_hw(dev);//注册网络设备
网卡驱动里的操作函数主要包括:发送、接收、增加接口、移除接口等。probe函数中分配并注册网络设备,同时也给出了这些操作函数的接口:
static const struct ieee80211_ops stm32_802_ops = {
.tx= stm32_802_tx,//发送函数
.start= stm32_802_start,//开始函数
.stop= stm32_802_stop,//停止函数
.add_interface= stm32_802_add_interface,
.remove_interface= stm32_802_remove_interface,
.config= stm32_802_config,//配置函数
};
发送过程:当主机上应用程序发送数据时,先经过网络协议栈,然后到达网卡驱动。协议接口层的dev_queue_xmit()函数会调用驱动中的发送函数stm32_802_tx(),发送函数首先将相关数据从网络数据包中提取出来,同时填充协议头部的有效数据;其次,将数据转换为USB格式的数据,并放入缓冲区中,通过USB无线接口传送到无线节点里的微处理器,并利用ieee80211_tx_status_irqsafe(hw, skb)函数将发送状态信息回传给网络上层。最后,由射频模块发射出去。发送函数主要代码如下:
static int stm32_802_txcstruct ieee8021_hw*dev,struct sk_buff*skb
{
……
urb = usb_alloc_urb(0, GFP_ATOMIC);//申请urb
usb_fill_bulk_urb(urb, priv32->udev, priv32->out,
buf, skb->len, stm32_tx_cb, skb);//填充urb,其中stm32_tx_cb是回调函数
rc = usb_submit_urb(urb, GFP_ATOMIC);//提交urb
}
stm32_tx_cb(){
skb = (struct sk_buff *)urb->context
ieee80211_tx_status_irqsafe(hw, skb);//发送接口函数
}
接收过程:由于USB总线没有IO/MEM映射、中断资源,在接收数据时USB无线网卡是通过查询机制来完成的[6]。在接收函数里,USB主机会循环地向USB设备查询是否有数据到来。如果USB设备收到数据,则对urb数据包的状态和长度进行检验,若检验通过,则将收到的数据放入接收队列,同时将urb格式的数据包转换为网络数据包,最后通过ieee80211_rx_irqsafe(dev, skb)接口函数向上层协议栈传输[7]。
打开函数:当使用ifconfig命令配置无线网卡时,stm32_802_start()函数被调用,该函数主要完成分配资源,激活设备的发送队列和接收队列等工作[8]。
停止函数:该函数用于关闭接收队列和发送队列,释放系统资源。当网卡从up状态转换为down状态时被调用。
4 编译与测试
4.1 编译驱动
要使该网卡设备在嵌入式平台下工作,必须先将驱动程序加载到嵌入式目标机系统中。本系统中,宿主机使用的是Linux2.6.36内核版本与armlinuxgcc 4.3.2编译器。目标机使用的Linux2.6.36内核版本、mini6410开发板、ARM11处理器。修改宿主机的makefile文件,编译生成stm32.ko无线网卡驱动模块,将stm32.ko下载到目标机,并加载到Linux内核中,网卡就可以正常运行。
4.2 测试
4.2.1 组网方式
在测试中,通过3个节点来组建一个两跳的无线网络。如图4所示,分别为每个节点配置IP地址:节点A为202.193.52.231;B为202.193.52.232;C为202.193.52.233。在测试实验中,通过iptalbes等工具过滤掉来自特定节点的数据包,以避免节点A和节点C之间不能直接通信的问题,[11]因此,节点A发出的数据包将通过节点B转发到达节点C。

图4 网络拓扑图
4.2.2 运行步骤
加载无线网卡驱动:insmod stm32.ko。
组建Adhoc网络。
① 发起方(节点A):
iwconfig wlan0 mode adhoc
iwconfig wlan0 essid aodvnet
ifconfig wlan0 up
ifconfig wlan0 202.193.52.231
② 接入方(节点C):
iwconfig wlan0 mode adhoc
ifconfig wlan0 up
ifconfig wlan0 202.193.52.233
iwlist wlan0 scan
iwconfig wlan0 essid aodvnet
iwconfig wlan0
查看cell是否为iwlist搜索出aodvnet对应的cell,如果一致则接入成功。
③ 节点B运行和节点C相同的操作(IP地址除外)。
④ 加载AODV模块:insmod kaodv.ko。
⑤ 运行AODV协议:./aodvd。
4.3 测试结果
各节点运行aodvd程序后,在节点C使用ping命令,即ping 202.193.52.231,结果如图5所示。

图5 节点C ping命令测试结果
实验结果显示,该网卡驱动能支持无线节点的正常工作,节点之间的网络是互通的,并且节点可以顺利完成转发,可应用于多跳网络。
结语
本文从工程研发和应用的角度出发,给出了利用嵌入式Linux系统将Chirp收发通信设备接入为自组织无线网络网卡的设计;分析了USB设备驱动、无线网卡驱动的基本架构;编写了USB无线网卡驱动,同时在此基础上组建支持TCP/IP和AODV路由的Adhoc无线网络,通过实验验证了方法的可行性和可靠性。