目标
在windows 2000及以上版本的操作系统中,开发usb读卡器驱动。实现数据批量传输功能,同时需要根据特定的协议(协议可自定义)进行通信。实现一个基于此usb驱动的动态库(统一接口),供上层应用程序调用。
具体功能包括:查询IC卡类型、读扇区数据、写扇区数据、删除IC卡数据、USB热插拔。
开发环境
操作系统:windows xp
开发软件:DDK,driverStudio
调试工具:Ellisys USB分析仪,Debug view, irptrace, driverMonitor, SymLinks
基本技能
熟悉usb2.0协议,熟悉windows下驱动开发流程(包括windows驱动调试技术、内存管理、驱动程序的同步、IRP、分层驱动模型、设备的即插即用等)。
与传统PC总线(如PCI总线)设备的驱动程序相比,USB设备驱动程序从不直接与硬件对话。相反,它仅靠创建URB(USB请求块)并把URB提交到总线驱动程序就可完成硬件操作。
可以把USBD.SYS看作是接受URB的实体,向USBD的调用被转化为带有主功能代码为IRP_MJ_INTERNAL_DEVICE_CONTROL的IRP。然后USBD再调度总线时间,发出URB中指定的操作。
初始化请求
为了创建一个URB,你首先应该为URB分配内存,然后调用初始化例程把URB结构中的各个域填入请求要求的内容,例如,当你为响应IRP_START_DEVICE请求而配置设备时,首要的任务就是读取该设备的设备描述符。下面代码片段可以完成这个任务:
USB_DEVICE_DESCRIPTOR dd; URB urb; UsbBuildGetDescriptorRequest(&urb, sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, &dd, NULL, sizeof(dd), NULL); |
我们首先声明一个局部变量urb来保存URB数据结构。URB在USBDI.H中声明,是一个多子结构的联合,我们将使用UrbControlDescriptorRequest子结构,它的类型是_URB_CONTROL_DESCRIPTOR_REQUEST。使用自动变量当然是可以的,但你事先必须了解系统堆栈上是否有足够的空间装下最大的URB,并且在这个URB被完成前,你不能离开当前函数,否则自动变量将被释放。
当然,你还可以在系统堆上为URB动态地分配内存:
PURB urb = (PURB) ExAllocatePool(NonPagedPool, sizeof(_URB_CONTROL_DESCRIPTOR_REQUEST)); if (!urb) return STATUS_INSUFFICIENT_RESOURCES; UsbBuildGetDescriptorRequest(urb, ...); ... ExFreePool(urb); |
UsbBuildGetDescriptorRequest看上去象一个正常的服务例程,实际上它是一个宏(在USBDLIB.H中声明),用于生成“读描述符请求子结构”各个域的初始化语句。在DDK头文件中定义了这些创建各种URB的宏,见表1。因为它们是预处理宏,所以在它们的参数中应避免使用有侧效的表达式。
表1用于创建URB的辅助宏
辅助宏 |
事务类型 |
UsbBuildInterruptOrBulkTransferRequest |
对中断或批量端点的输入和输出 |
UsbBuildGetDescriptorRequest |
端点0的GET_DESCRIPTOR控制请求 |
UsbBuildGetStatusRequest |
对设备、接口、端点的GET_STATUS请求 |
UsbBuildFeatureRequest |
对设备、接口、端点的SET_FEATURE或CLEAR_FEATURE请求 |
UsbBuildSelectConfigurationRequest |
SET_CONFIGURATION |
UsbBuildSelectInterfaceRequest |
SET_INTERFACE |
UsbBuildVendorRequest |
任何厂商定义的控制请求 |
在前面的代码片段中,我们指定把设备描述符信息接收到一个局部变量(dd)中,其地址和长度我们在参数中都提供了。涉及到数据传输的URB可以指定两种方式的非分页数据缓冲区。你可以象上面代码那样指定缓冲区的虚拟地址和长度。另一种方法是提供一个内存描述符列表(MDL),但事先必须调用MmProbeAndLockPages函数对这个MDL进行探测并锁定(probe-and-lock)。
发送URB
创建完URB后,你需要创建并发送一个内部I/O控制(IOCTL)请求到USBD驱动程序,USBD驱动程序位于驱动程序层次结构的低端。在大多数情况下,你需要等待设备回应,可以使用下面辅助函数:
NTSTATUS usb_icSubmitUrbSynch( IN PUSB_IC_DEVICE_EXTENSION DeviceExtension, IN PURB Urb ) { NTSTATUS status; PIRP irp; IO_STATUS_BLOCK ioStatus; KEVENT event; PIO_STACK_LOCATION irpStack;
usb_icDebugPrint(DBG_IO, DBG_INFO, __FUNCTION__"++");
KeInitializeEvent(&event, NotificationEvent, FALSE); ß 1
irp = IoBuildDeviceIoControlRequest( ß 2 IOCTL_INTERNAL_USB_SUBMIT_URB, DeviceExtension->LowerDeviceObject, NULL, 0, NULL, 0, TRUE, &event, &ioStatus );
if (irp != NULL) { irpStack = IoGetNextIrpStackLocation(irp); irpStack->Parameters.Others.Argument1 = Urb; ß 3
status = IoCallDriver(DeviceExtension->LowerDeviceObject, irp); ß 4 if (status == STATUS_PENDING) { KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL );
status = ioStatus.Status; } } else { status = STATUS_INSUFFICIENT_RESOURCES; }
usb_icDebugPrint(DBG_IO, DBG_INFO, __FUNCTION__"--. STATUS %x", status);
return status; } |
1. 我们将等待URB完成,所以我们必须先创建一个内核事件对象。
2. 创建内部IOCTL请求最简单的的方法是调用IoBuildDeviceIoControlRequest函数,第一个参数(IOCTL_INTERNAL_USB_SUBMIT_URB)指出I/O控制代码。第二个参数(DeviceExtension->LowerDeviceObject)指定接收请求的设备对象;IoBuildDeviceIoControlRequest使用这个指针来决定需要接收多少个堆栈单元。接下来的四个参数描述输入输出缓冲区,提交这种URB并不需要这些信息,所以例子中将它们置成NULL或0值。第七个参数为TRUE,它指出我们创建的是IRP_MJ_INTERNAL_DEVICE_CONTROL请求而不是IRP_MJ_DEVICE_CONTROL请求。最后两个参数指出等待URB完成的事件和一个接收该操作最终状态的结构IO_STATUS_BLOCK。
3. 被提交的URB的地址被填入Parameters.Others的Argument1域。对于普通的IOCTL请求,该偏移对应OutputBufferLength域。
4. 我们用IoCallDriver把请求发送到下一层驱动程序。USBD将处理该URB请求并完成,然后I/O管理器将那个IRP删除并置事件信号。由于我们没有提供自己的完成例程,所以不能确定I/O管理器在所有可能的完成情况下都置事件信号。我们仅当低级派遣例程返回STATUS_PENDING时才等待那个事件。
配置
USB