本文主要介绍了USB无线网卡的实现方案,涉及基于微软NDIS框架下的网卡驱动开发,802.11无线协议开发,USB协议开发部分。NDIS框架式微软专门针对网络设备设计的接口。由于是USB接口,所以需要一个WDM的底层作为NDIS miniport驱动的物理访问接口。作为无线网卡,对802.11协议的实现是本文很重要的一个内容。
关键词:NDIS WDM USB 802.11
1, 项目介绍
本项目是USB无线网卡项目。内容涉及到硬件开发,驱动框架开发,802.11协议开发几个部分。
首先简单介绍硬件结构,硬件的实现方案如下图1-1。网卡工作在半双工状态。USB接口依USB 2.0,采用ICSI的IC9211芯片。物理层芯片使用Intersil公司芯片。MAC层控制引擎需要使用微码编程或嵌入CPU Core。
作为一个新的项目,首先要列出开发的工作量,制定出合理的开发计划,将整个项目细分为几个小的项目,一步一步推进。下面介绍一下工作内容。
这些内容中,首先要解决的是USB通信问题,由于硬件接口为USB总线接口,所以必须要打通USB通道。微软windows系统中有专门的编程接口,我们只要将需要的功能封装成URB(USB Request Block)请求包,通过调用USBD class driver接口,将URB包发到下一层。最总由总线驱动与设备交互。
其次是开发NDIS miniport驱动框架。NDIS是微软的网络设备的开发库,提供了许多和网络设备相关的接口函数供调用。根据设计,本驱动为一个miniport类型的驱动,所以需要开发标准的miniport接口供系统调用。和传统的驱动一样,也是封装一系列的回调函数给系统,这些调用接口形式微软的NDIS规范已经定义好了。
接下来就是对这些接口的完善。加入802.11协议内容,作为驱动的实体。由于intersil的芯片没有集成802.11协议功能,所以我们需要在软件中开发协议中的MAC层规定的内容。这部分也是我们驱动的主要内容。
802.11协议有三个主要状态。根据协议规范只有在关联的状态下,才可以进行数据通信。认证,加密是安全相关的,理论上与协议没有必然关系,802.11无线协议为了安全,将加密和认证也作为协议的一部分,从而使得其具有等同有线网络的安全级别。同时,802.11协议定义很多命令,如Probe request/response,Beacon,Authentication,Deauthentication,Association,Deassociation等,程序的主体是一个状态机,只要发送相应的命令就可以进行状态切换,完成相应的功能。
协议中涉及到AP列表的搜索,认证过程,关联过程,信道的切换等需要分别实现,分别是一个更细节的状态机。关于802.11协议详细内容请参考相关的文档。这里不多说了。下图1-2是802.11协议的状态图。
按照功能划分:可分为管理、控制和数据3种不同类型帧。Class 1,Class 2&3 Frame的定义请参考802.11-1999。
1, Usb总线介绍
USB是Intel公司开发的通用串行总线架构,以简单的设计,易用性,热插拔特性受到了广泛的欢迎,很多设备都开始支持USB规范。
一个USB系统主要被定义为三个部分:USB的互连;USB的设备;USB的主机。
在任何USB系统中,只有一个主机。USB和主机系统的接口称作主机控制器,主机控制器可由硬件、固件和软件综合实现。根集线器是由主机系统整合的,用以提供更多的连接点。
图2-1 总线的拓扑结构 |
图2-1显示了USB总线的拓扑结构。
标准USB规范有四根线,分别是电压正负极,两根数据线。外观为扁平的方形接口。USB传送信号和电源是通过一种四线的电缆,图2-2中的两根线是用于发送信号。电缆中包括VBUS、GND二条线,向设备提供电源 。VBUS使用+5V电源。
USB总线属一种轮讯方式的总线,主机控制端口初始化所有的数据传输。在USB设备安装后,主机通过设备控制通道激活该端口并以预设的地址值给USB设备。主机对每个设备指定唯一的USB地址。
USB定义了一些请求命令,所有的USB设备在设备的缺省控制通道(Default Control Pipe)处对主机的请求发出响应。这些请求是通过使用控制传输来达到的,请求及请求的参数通过Setup包发向设备,由主机负责设置Setup包内的每个域的值。每个Setup包有8个字节。见表2-1。
bmRequestType域
这个域表明此请求的特性。特别地,这个域表明了第二阶段控制传输方向。如果wLength域被设作0的话,表明没有数据传送阶段,那Direction位就会被忽略。
USB说明定义了一系列所有设备必须支持的标准请求。这些请求被例举在表8-3中。另外,一个设备类可定义更多的请求。设备厂商也可定义设备支持的请求。
请求可被导引到设备,设备接口,或某一个设备端结点(endpoint)上。这个请求域也指定了接收者。当指定的是接口或端结点(endpoint)时,wIndex域指出那个接口或端节点。
bRequest域
这个域标识特别的请求。bmRequestType域的Type啦可修改此域的含义。本说明仅定义Type 字位为0即标准设备请求时bRequest域值的含义。
wValue域
此域用来传送当前请求的参数,随请求不同而变。
wIndex域
wIndex域用来表明是哪一个接口或端结点,图2-3表明wIndex的格式(当标识端结点时)。Direction位在设为0时表示出结点,设为1时表示是入结点,Endpoint Number是结点号。图2-4表明wIndex用于标识接口时的格式。
wLength域
这个域表明第二阶段的数据传输长度。传输方向由bmRequstType域的Direction位指出。wLength域为0则表明无数据传输。在输入请求下,设备返回的数据长度不应多于wLength,但可以少于。在输出请求下,wLength指出主机发出的确切数据量。如果主机发送多于wLength的数据,设备做出的响应是无定义的。
表2-2描述了所有USB设备都定义的标准设备请求将它们列出。不管设备是否被分配了非缺省地址或设备当前是被配置了的,它们都应当对标准请求产生响应。具体请参考USB规范。
讲到USB总线,就不得不讲USB总线的传输方式。
一个USB通道是设备上的一个端点和主机上软件之间的联系。USB设备的设计者可以决定设备上每个端点的能力。一旦为这个端点建立了一个通道,这个通道的绝大多数传送特征也就固定下来了,一直到这个通道被取消为止。
USB定义了4种传送类型:
·控制传送:可靠的、非周期性的、由主机软件发起的请求或者回应的传送,通常用于命令事务和状态事务。
·同步传送:在主机与设备之间的周期性的、连续的通信,一般用于传送与时间相关的信息。这种类型保留了将时间概念包含于数据中的能力。但这并不意味着,传送这样数据的时间总是很重要的,即传送并不一定很紧急。
·中断传送:小规模数据的、低速的、固定延迟的传送。
·批传送:非周期性的,大包的可靠的传送。典型地用于传送那些可以利用任何带宽的数据,而且这些数据当没有可用带宽时,可以容忍等待。
每一个端点Endpoint都有自己的传输方式,通过配置描述符,主机就可以知道每一个端点的传输方式,从而以该方式进行通信。
1, WDM框架介绍
WDM(windows driver model)是微软对于驱动开发定义的一整套规范。与原来的Vxd驱动相比,WDM更加封装完好,并且可以对Windows各版本的做到二进制兼容,在一个系统下完成了,可以很轻松的移植到其他windows系统下。本人有幸将windows xp下开发的驱动,移植到了windows 2000,window me和windows 98下。这是我很好的经验。
根据Walter oney的观点,设备驱动程序是一个包含了许多操作系统可调用例程的容器,这些例程可以使硬件设备执行相应的动作。
WDM模型使用了层次结构,如图3-1所示,左边是一个设备对象堆栈。设备对象是系统为帮助软件管理硬件而创建的数据结构。一个物理硬件可以有多个这样的数据结构。出于堆栈最底层的设备对象称为物理设备对象(physical device object),简称PDO。在设备对象堆栈的中间摸出有一个对象称为功能设备对象(function device object),简称FDO。在FDO的上面和下面还会有过滤器设备对象(filter device object),简称FiDO。
总线是个广义的定义,包括PCI,SCSI卡,并行口,串行口,USB集线器(hub),等等。实际上它可以是任何能插入多个设备的硬件设备。总线驱动程序的一个任务就是枚举总线上的设备,并为每一个设备创建一个PDO。
我们的miniport其实也是一个功能驱动程序。
1, 开发环境介绍
下面介绍一下开发环境,windows驱动开发使用VC工具,也可以使用脚本直接编译,关键是设置好环境变量,DDK库的路径设置好。编译出的驱动有checked和free两种,checked对应的debug版本,free对应release版本。
调试工具使用Softice,我使用的是2.7版本,这个工具非常强,可以看变量,内存,也可以看堆栈调用,很多棘手的问题手到擒来。
或者一般也可以通过debugview工具调试,需要的程序的关键路径上打印变量即可。当程序比较稳定了,就可以使用debugview观察逻辑上的问题,这样就可以比较快的定位,然后再使用softice进行细节调试。
2, WDM模型和USB底层的实现
微软windows实现了usb总线的驱动,和一部分USB类驱动,通过URB封装好以后,把URB包发送给下一层的驱动,最终由总线驱动与设备通信。
WDM模型提供了USB的编程架构。这个架构包含了多个概念,包括把设备夫做到计算机上的层次方法,电源管理的通用方案,硬件的多层次描述符的自识别标准。USB标准把定时帧(frame)分解成数据包(packet),从而实现设备和主机之间的数据传输。最后,在主机和设备上的端点之间,USB支持四种数据传输模式。一种模式称为等时传输(isochronous),它可以每个一毫秒传输固定量无错误校验的数据。其他模式为:控制传输,批量传输和中段传输,它们仅能传输少量(64字节或更少)带有错误校验的数据。
USB驱动高度依赖于总线驱动程序(USBD.sys),而不直接使用HAL函数与硬件通信。它仅靠创建URB(USB请求块)并把URB提交到总线驱动程序就可以完成硬件操作。USBD.sys可以理解为接受URB的实体,向USBD的调用被转化为带有主功能代码为IRP_MJ_INTERNAL_DEVICE_CONTROL的IRP。然后USBD再调度总线时间,发出URB中指定的操作。
一般我们可以按照如下的方法建立并提交一个URB给USBD驱动。
如当相应IRP_START_DEVICE消息时,首先需要读取设备描述符,然后
URB urb;
USB_DEVICE_DESCRIPTOR deviceDesc;
UsbBuildGetDescriptorRequest
(
&urb,
(USHORT) sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST),
USB_DEVICE_DESCRIPTOR_TYPE,
0,
0,
&deviceDesc,
NULL,
sizeof(USB_DEVICE_DESCRIPTOR),
NULL
);
UsbBuildGetDescriptorRequest其实是一个宏,在USBDLIB.H中声明,用于生成读描述符请求子结构各个域。
创建完URB后,需要发一个内部IO控制(IOCTL)请求到USBD驱动程序,USBD驱动程序位于驱动程序层次结构的低端,一般需要等待设备回应。
NTSTATUS MPUSB_CallUSBD
(
IN PDEVICE_OBJECT DeviceObject,
IN PURB Urb
)
{
KEVENT event;
IO_STATUS_BLOCK ioStatus;
PDEVICE_EXTENSION pDevExt;
pDevExt = DeviceObject->DeviceExtension;
// issue a synchronous request to read the UTB
KeInitializeEvent(&event, NotificationEvent, FALSE);
PIRP irp = IoBuildDeviceIoControlRequest
(
IOCTL_INTERNAL_USB_SUBMIT_URB,
pDevExt -> StackDeviceObject,
NULL,
0,
NULL,
0,
TRUE, /* INTERNAL */
&event,
&ioStatus
);
// Call the class driver to perform the operation. If the returned status
// is PENDING, wait for the request to complete.
PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation( irp );
nextStack->Parameters.Others.Argument1 = Urb;
NTSTATUS ntStatus = IoCallDriver( pDevExt -> StackDeviceObject, irp );
if (ntStatus == STATUS_PENDING)
{
ntStatus = KeWaitForSingleObject
(
&event,
Executive,
KernelMode,
FALSE,
NULL
);
}
// USBD maps the error code for us
ntStatus = ioStatus.Status;
return ntStatus;
}
这样我们就可以通过USB总线与设备通信了。
当然这个过程比较复杂,需要读取并配置设备描述符,配置描述符,接口描述符和端点描述符,这些描述符在USB协议规范中有讲述,在相应的USB类规范中也有详细的描述。
一般配置描述符格式如下,
这个示例表明这个设备有两个接口,对一个接口有两个端点,第二个接口也有两个端点。每一个接口可以是一个独立的功能,这个时候设备就是复符合设备,在配置描述符中就需要声明这是个复合设备。而每一个接口也需要分别声明他的类型。
比如本人曾经有一个项目中有三个接口,分别是modem,普通串口,USB mass storage。这三个接口都需要分别声明他的类型,这些类型在USB的类规范中有详细的定义。
一般由于配置描述符的长度是不定的,所以我们需要先获取这个长字符串的前面9个字节,即配置描述符(见规范)。格式如下,
typedef struct
{
byte length; // bLength: size in bytes
byte descriptor_type; // bDescriptorType
word total_length; // wTotalLength: in bytes
byte num_interfaces; // bNumInterfaces
byte configuration; // bConfigurationValue
byte config_index; // iConfiguration
byte attributes; // bmAttributes
byte max_power; // MaxPower: in 2mA units
} usbdc_configuration_descriptor_type;
这样,在这个字符串的第三、四字节会指明整个字符串的长度,然后再读取一次该长度,就可以完整地得到整个配置字符串。
1, NDIS模型和带有WDM底层的miniport驱动
NDIS(Network Device Interface Specification)是微软的网络设备的开发库。但是由于本设备是一个USB设备,需要通过WDM模型访问。故而我们在设计时,考虑为一个NDIS miniport上层接口,带有一个WDM访问接口的驱动,很幸运,微软支持这样的设计。
根据设计,本驱动为一个miniport类型的驱动,所以需要开发标准的miniport接口供系统调用。和传统的驱动一样,也是封装状一系列的调用函数给系统,这些调用接口形式微软的NDIS规范已经定义好了。入口函数为DriverEntry,
入口函数的功能是系统调用驱动的第一个函数,它负责初始化驱动,注册回调函数(call back)。这些函数包括如下,通过一个结构完成。
首先必须调用如下的函数,使得miniport驱动和NDIS相关联,通过调用这个函数存储miniport驱动的信息。返回的句柄NdisWrapperHandle,用于后面的回调函数注册。NDIS也通过NdisWrapperHandle来区分不同的驱动。
NdisMInitializeWrapper(
&NdisWrapperHandle,
DriverObject,
RegistryPath,
NULL
);
注册回调函数方法如下,注册结构类型为NDIS_MINIPORT_CHARACTERISTICS。
// and the entry points for driver-supplied MiniportXxx
NdisZeroMemory(&MPChar, sizeof(MPChar));
//
// The NDIS version number, in addition to being included in
// NDIS_MINIPORT_CHARACTERISTICS, must also be specified when the
// miniport driver source code is compiled.
//
MPChar.MajorNdisVersion = MP_NDIS_MAJOR_VERSION;
MPChar.MinorNdisVersion = MP_NDIS_MINOR_VERSION;
MPChar.InitializeHandler = MPInitialize;
MPChar.HaltHandler = MPHalt;
MPChar.SetInformationHandler = MPSetInformation;
MPChar.QueryInformationHandler = MPQueryInformation;
MPChar.SendPacketsHandler = MiniportTxPackets;
MPChar.ReturnPacketHandler = MiniportReturnPacket;
MPChar.ResetHandler = MPReset;
MPChar.CheckForHangHandler = MPCheckForHang; //optional
#ifdef NDIS51_MINIPORT
// MPChar.CancelSendPacketsHandler = MPCancelSendPackets;
MPChar.PnPEventNotifyHandler = MPPnPEventNotify;
MPChar.AdapterShutdownHandler = MPShutdown;
#endif
NdisMRegisterMiniport(
NdisWrapperHandle,
&MPChar,
sizeof(NDIS_MINIPORT_CHARACTERISTICS));
当注册好了后,系统会检测NDIS版本号,然后调用初始化接口,MPChar.InitializeHandler = MPInitialize; 初始化驱动的其他内容,如变量的初始化,注册表的读写,事件/互斥信号量的初始化等。
如果初始化完成,那么驱动就可以正常工作了。一般通过如下接口:
MPChar.SetInformationHandler = MPSetInformation;
MPChar.QueryInformationHandler = MPQueryInformation;
进行属性的获取和设置。通过如下接口:
MPChar.SendPacketsHandler = MiniportTxPackets;
MPChar.ReturnPacketHandler = MiniportReturnPacket;
进行数据的收发。
现在可以看到,NDIS和传统的驱动没有什么区别,但是作为网络的接口,它更加稳定,更加标准化,是开发更加简单。
1, 802.11协议的实现
1996年,ETSI提出了HiperLAN的无线局域网协议,1998年日本出现HomeRFSWAP。最近几年流行的Bluetooth(严格的说, Bluetooth不属于无线局域网,而是无线PAN(Personal Area Net-work) ,应该算作无线局域网的一个子集。1997年通过的IEEE 802.11是第一种无线以太网标准,已经成为无线局域网的代名词。1999年的修订版是当前的标准主要参考。
802.11包括MAC(Media Access Control)层和物理层
•3种不同物理层:
–DSSS or Direct Sequence Spread Spectrum
–FHSS or Frequency Hopping Spread Spetrum
–红线(IR or Infrared)
•所以MAC层可以同时支持3种不同的物理层
基本服务集(basic service set or BSS)
•无线局域网的最小单元,由2个或多个移动台Wireless station or STA)构成
•一个基本服务集(BSS)内的移动台(STA)间可以直接通信;
•一个基本服务集(BSS)实际上是一个自组织网络(ad hoc),在802.11中称成IBSS(independent BSS)
•STA到BSS的连接是动态的,自发的(non-preplanned)
扩展服务集(Extended service set or ESS)
AP+BSS可以组成任意大的无线局域网,称作扩展服务集网络,它的所有组成部分合称扩展服务集(ESS)
Infrastructure BSS:非IBSS都称为Infrastructure BSS
•802.11定义了9种服务类型:
移动台服务(Station Service or SS)有四种:
1.鉴别(Authentication)=who are you?
2.取消鉴别(Deauthentication)=I am about to leave, Stop communicating with me please
3.加密(privacy):数据怎能让别人偷听(Eavesdrop)
4.MSDU发送(MSDU delivery)
(MSDU是什么?MAC service data unit; SDU是来自(OSI)上一层的协议数据单元PDU,它定义了对下层协议的服务请求;而PDU协议数据单元(Protocol Data Unit)指对等层水平方向传送的数据单元)
分布系统服务(Station Service or DSS)
要提供全部9种服务:
•前4种:鉴别、取消鉴别、加密、MSDU发送
•后5种:
5.关联(Association):它是一个DSS(Distribution system service),STA如果希望将消息传输给其他BSS的STA,首先要与所在BSS的AP实现关联。
6.取消关联(Disassocation):关联的反过程
7.分配(Distribution):将BSS1的STA1的数据(也可以说是消息)经过DS传送到BSS2的STA2,即跨DS的数据传送功能
8.集成(Integration):完成与有线网之间互通
9.再关联(Reassociation):当STA从BSS1移动到BSS2时,要与BSS的AP实现再关联,以支持跨BSS消息传送
按照功能划分:可分为管理、控制和数据3种不同类型帧
所有MAC帧由一下3部分构成:
•帧头(MAC header)
•可变长帧体(Frame body):与帧类型有关
•校验序列(frame check sequence or FCS):CRC-32
SSID说白了就是IBSS或ESS的一个32字节的一个标识,一个字节对应一个字符,所有可以用32个以下的字符来表示某个公司或其他组织的无线网络,类似于有线网络的工作组名字,比如我们组就叫Eda(其它字节为0)
鉴别服务分为两种:
开放系统(Open System)和共享密钥(Shared Key)
开放系统:如果dot11AuthenticationType被置位为1,则为“开放系统鉴别”,要建立鉴别,共需要2帧就可完成任务:鉴别请求,鉴别应答
共享密钥:共享密钥鉴别需要4帧。首先requester发出请求帧,然后responder发送质问(challenge)文本,该文本是WEP PRNG生产的128字节随机数,然后requester对收到的质问(challenge)文本进行WEP算法加密,responder将返回的文本解密得到相应质问文本,如果双方(responder和requester)所用密钥相同,则responder将返回的文本解密得到原始质问文本,鉴别成功
WEP加密已经被认为是不安全的算法。所以WiFi有提出了更高安全级别的算法,如WAP等。
MAC层功能:
1.点协调功能(PCF)
2.分布协调功能(DCF)
3.分段(Fragmentation)
4.去分段(defragmentation)
5.MSDU重新排序和丢弃
长度大于aFramentationThreshold的MSDU和
MMPDU(MAC管理协议数据单元)都要分段,
以提高传输成功率分段后的数据长度为某一固
定偶数(不包括最后一段)。
分段后的数据可以进行突发传送。接收端
依靠Sequence和MoreFragments 域对数据进行去分段
•分布控制方式(DCF):多个STA争用无线信道,即所谓CSMA/CA方式(不同于CSMA/CD)
•点控制方式(PCF): AP作为控制中心,控制所有STA对信道的使用。AP就是所谓Point coordinator, PCF是基于DCF的。主要用于实时业务,在802.11中是可选的。
随机避退时间
BackoffTime = Random()×aSlotTime
Random()--返回在[0, CW]之间均匀分布的整数
aSlotTime--物理层特性决定的CW的最小单位或称CW的切片
CW--contention window。在[aCMin,aCMax] 之间取值。第一次发送时值为aCMin,以
后每重传一次倍增,达到aCMax后维持aCMax。成功发送后复位到aCMin。
检测到信道忙,则延迟(defer)
检测到空闲且经过一个IFS,再进行
避退(Backoff)。避退时间过后如果
信道空闲,就开始发送帧
信标帧(Beacons)