转载请标明是引用于 http://blog.csdn.net/chenyujing1234
欢迎大家提出意见,一起讨论!
1、USB设备驱动程序(WDM模型)
1、1 分类
USB设备驱动程序的设计是基于微软件的WDM。
WDM采用分层驱动程序模型,对于USB设备来说,可作来两个部分:
USB总线驱动程序
它由操作系统提供,它位于USB功能驱动程序的下面,负责与实现的硬件打交道,实现烦琐的低层通信。
USB功能驱动程序
由设备开发者编写,位于USB总线驱动程序的上面,不与实现的硬件打交道,而是通过USB总线驱动程序
发送包含URB(USB Request Block, USB请求块)的IRP(I/O Request Packet, I/O请求包)来祥瑞对USB设备
信息的发送或接收。
注意: USB功能驱动程序调用的API函数都不能与开发Window 应用程序的API(工作于CLR上面)一样,得用自己WDK SDK包里特有的内核API函数才可以。
在WDM模式中,完成一个设备的操作,至少有两个设备对象共同完成。
(1)物理设备对象(PDO)
(2)另一个是功能设备对象(FDO)
其关系是“附加”与“被附加”关系。
1、2 对象栈数据结构
下图的左边是WMD模型的设备对象栈。(设备对象是系统为帮助软件管理而创建的数据结构,一个物理硬件可以有多个这样的数据结构)。
PDO(physical device object): 处于堆栈最底层的设备对象。
FDO(functional device object):功能设备对象。
FIDO(filter device object): 过滤器设备对象
当PC插入某个设备进,PDO会自动创建,确切说由总线驱动创建的。
PDO不能单独操作设备,需要配合FDO一起使用。
系统会提示检测到新设备。这时要求安装驱动程序。
驱动程序指的就是WDM程序,此驱动程序负责创建FDO,并附加到PDO上。
当一个FDO附加在PDO上的时候,PDO设备对象的子域AttackedDevice会记录FDO的位置。
PDO被称作底层驱动或下层驱动。而FDO称为上层驱动。
1、3 描述符的概念
USB设备硬件中的数据结构称为描述符,可以被主机软件识别。
每个描述符开始于一个两字节的头,头中指出该描述符的字节长度(包括头)和描述符类型。
描述符的长度对于相同的描述符类型是固定的。
1、4 设备描述符
每个设备都有一个唯一 的设备描述符,它向主机软件标识该设备。主机使用GET_DESCRIPTOR控制事务直接
从设备的0号端点读取该描述符。
typedef struct _USB_DEVICE_DESCRIPTOR {
UCHAR bLength; // 设备描述符的bLength域等于18
UCHAR bDescriptorType; // 域为1, 以指出该结构是一个设备描述符还是一个管道描述符
USHORT bcdUSB; // 包含描述符遵循的USB规范的版本号( 以BCD编码),
// 现在,设备可以使用值0x0100 或 0x0110 来指出它是1.0版还是1.1版本.
UCHAR bDeviceClass; // 指出设备类型
UCHAR bDeviceSubClass; // 指出设备的子类型
UCHAR bDeviceProtocol; // 指出设备所使用的协议
UCHAR bMaxPacketSize0; // 给出了默认控制端点(端点0)上的数据包容量的最大值
USHORT idVendor; // 厂商代码
USHORT idProduct; // 厂商专用的产品标识
USHORT bcdDevice; // 指出设备的发行版本号(0x0100对应版本1.0)
UCHAR iManufacturer; // iManufacturer iProduct iSerialNumber指向一个串描述符,
// 该串用人可读语言描述设备生产厂商。
UCHAR iProduct;
UCHAR iSerialNumber;
UCHAR bNumConfigurations;// 指出该设备能实现多少种配置。
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR;
我们已经知道在控制管道中,传输分为三个阶段.
第一阶段是令牌阶段,这里Host向设备发送"80 06 00 02 00 00 20 00" 8个字节。
第二阶段是数据传输阶段,方向是由设备传给主机,在我们的例子中给主机传递了18个字节。
这18字节就是对应于USB_DEVICE_DESCRIPTOR数据结构的.
第三阶段是握手阶段.
1、5 配置描述符
每个设备有一个或多个配置描述符,它描述了设备能实行的各种配置方式。
typedef struct _USB_CONFIGURATION_DESCRIPTOR {
UCHAR bLength; // 应该是9
UCHAR bDescriptorType; // 应该是2,即是一个9字节的配置描述符
USHORT wTotalLength; // 为该配置描述符长度加上该配置内所有接口和
// 端点描述符长度的总和。
// 通常,主机在发出一个GET_DESCRIPTOR请求并正确接收到9字节长的
// 配置描述符后,就会再发出一个GET_DESCRIPTOR请求并指定这个总长度。
// 第二个请求把这个大联合描述符传输回来
UCHAR bNumInterfaces; // 指出该配置有多少个接口
UCHAR bConfigurationValue;// 该配置的索引值
UCHAR iConfiguration; // 是一个可选的串描述符索引,指向描述该配置的Unicode字符串
UCHAR bmAttributes;
UCHAR MaxPower; // 指出要从USB总线上获取的最大电流量
} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR;
1、6 接口描述符
typedef struct _USB_INTERFACE_DESCRIPTOR {
UCHAR bLength; // 应该是9
UCHAR bDescriptorType; //
UCHAR bInterfaceNumber; // 是索引值
UCHAR bAlternateSetting;// 是索引值。用在SET_INTERFACE控制事务中以指定要激活的接口
UCHAR bNumEndpoints; // 指出该接口有多少端点,不包括端点0.
UCHAR bInterfaceClass; // 为接口类
UCHAR bInterfaceSubClass;// 为子接口类
UCHAR bInterfaceProtocol;// 为协议
UCHAR iInterface; // 一个串描述符。
} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;
1、7 端点描述符
typedef struct _USB_ENDPOINT_DESCRIPTOR {
UCHAR bLength;
UCHAR bDescriptorType;
UCHAR bEndpointAddress;
UCHAR bmAttributes;
USHORT wMaxPacketSize;
UCHAR bInterval;
} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR;
1、8 USB请求使用总线驱动程序
1、8、0 USB设备请求
USB的请求是通过管道传输的,请求是8个字节。
其中bRequest代表不同的USB请求.定义在USB100.h中
1、8、1 初始化请求
为了创建一个URB,首先应该为URB分配内存,然后调用初始化例和把URB结构中的各个域填入内容。
eg: 当你为响应IRP_START_DEVICE请求而配置设备时,首要的任务就是读取该设备的设备描述符.
urb = ExAllocatePool(NonPagedPool,
sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST));
if(urb)
{
siz = sizeof(USB_DEVICE_DESCRIPTOR);
deviceDescriptor = ExAllocatePool(NonPagedPool, siz);
if(deviceDescriptor)
{
// 构造请求
UsbBuildGetDescriptorRequest(
urb,
(USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
USB_DEVICE_DESCRIPTOR_TYPE,
0,
0,
deviceDescriptor,
NULL,
siz,
NULL);
#define UsbBuildGetDescriptorRequest(urb, \
length, \
descriptorType, \
descriptorIndex, \
languageId, \
transferBuffer, \
transferBufferMDL, \
transferBufferLength, \
link) { \
(urb)->UrbHeader.Function = URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE; \
(urb)->UrbHeader.Length = (length); \
(urb)->UrbControlDescriptorRequest.TransferBufferLength = (transferBufferLength); \
(urb)->UrbControlDescriptorRequest.TransferBufferMDL = (transferBufferMDL); \
(urb)->UrbControlDescriptorRequest.TransferBuffer = (transferBuffer); \
(urb)->UrbControlDescriptorRequest.DescriptorType = (descriptorType); \
(urb)->UrbControlDescriptorRequest.Index = (descriptorIndex); \
(urb)->UrbControlDescriptorRequest.LanguageId = (languageId); \
(urb)->UrbControlDescriptorRequest.UrbLink = (link); }
Urb: 用来输出的URB结构的指针
Length: 用来描述该UR结构的大小
DecscriptorType: 描述该URB的类型。它可以是USB_DEVICE_DESCRIPTOR_TYPE\
USB_CONFIGURATION_DESCRIPTOR_TYPE和USB_STRING_DESCRIPTOR_TYPE
Index : 用来描述设备描述符的索引
LanguageId: 用来描述语言的ID
TransferBuffer: 如果用缓冲区读取设备,TransferBuffer是缓冲区内存指针
TransferBufferMDL: 如果用直接读取内存时,TransferBufferMDL是直接读取内存指针时MDL的指针
transferBufferLength : 对于该URB所操作内存的大小
1、8、2 发送URB
创建完URB后,需要创建并发送一个内部I/O控制(IOCTL)请求到USBD驱动程序,
USBD驱动程序位于驱动程序层次结构的低端。需要等待设备回应。
NTSTATUS SendAwaitUrb(PDEVICE_OBJECT fdo, PURB urb)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
KEVENT event;
KeInitializeEvent(&event, NotificationEvent, FALSE):
IO_STATUS_BLCOK iostatus;
PIRRIrp = IoBuildDeviceIoControlRequest(IOCTRL_INTERNAL_USB_SUBMIT_URB,
pdx->LowerDeviceObject, NULL, 0, TRUE, &event, &iostatus);
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->Parameters.Others.Argumentl = (PVOID)urb;
NTSTATUS status = IoCallDriver(pdx->LowerDeviceObject, Irp);
if(status == STATUS_PENDING)
{
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = iostatus.Status;
}
return status;
}
1、8、3 URB返回的状态
当提交一个URB到USB总线驱动程序时,我们将收到一个描述该操作结果的NTSTATUS代码。状态码的定义你可以在DDK的头文件NTSTATUS.H中找到)
当USBD完成一个URB时,它就把URB的UrbHeader.Status域设置为某个USBD_STATUS值,
DDK中的URB_STATUS宏可以简化这个值的提取:
NTSTATUS status = SendAwaitUrb(fdo, &urb);
USBD_STATUS ustatus = URB_STATUS(&urb);
1、9 关于USB设备的配置
1、9、1
USB总线驱动程序自动检测新插入的USB设备。然后读取设备内的设备描述符以查明插入的是何种设备,
描述符中的厂商和产品标识及其它描述符一同决定具体安装哪一个驱动程序。
1、9、2
配置管理器调用驱动程序的AddDevice函数,AddDevice做所有你已知的任务:
创建设备对象、
把设备对象连接到驱动程序堆栈上
。。。。。
1、9、3
配置管理器向驱动程序发送一个即插即用请求IRP_MN_START_DEVICE.
1、9、4
通过调用StartDevice函数并传递一些参数,这些参数描述了赋予设备的经过
转换的和未经转换的I/O资源。
StartDevice执行过程:
首先为设备选择一个配置。
接着选择配置中的一个或多个接口。
然后向总线驱动程序发送配置选择URB。
最后,总线驱动程序向设备发出命令使能选定的配置和接口。
补充:总线驱动程序负责驱动程序与选定接口端点之间的通信,
它同时还创建配置句柄和接口句柄,我们可以从完成的URB中提取这些句柄并保存为以后使用。
也可以通过URB成员UrbSelectConfiguration.ConfigurationHandle返回该配置句柄;
USBD_INTERFACE_INFORMATION结构中的InterfaceHandle返回接口句柄;
每个USBD_PIPE_INFORMATION结构中都含有与每个端点对应的管道句柄PipeHandle.
NTSTATUS StartDevice(PDEVICE_OBJECT fdo)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
<configure device)
return STATUS_SUCCESS;
}
2、应用软件设计
2、1 分类
应用软件分两部分组成:
链接库程序
它负责与USB功能驱动程序通信并接受应用程序的各种操作请求
应用程序
负责对所采集的数据进行分析处理