在本主题中,你将了解如何在一个通用串行总线 (USB) 设备中选择一种配置。
要为一个 USB 设备选择一种配置,该设备的客户端驱动程序必须选择至少一种支持的配置,并指定要使用的每个接口的备用设置。客户端驱动程序将这些选择封装在一个选择配置请求中并将该请求发送到 Microsoft 提供的 USB 驱动程序堆栈,具体来讲就是 USB 总线驱动程序(USB 集线器 PDO)。USB 总线驱动程序在指定的配置中选择每个接口,并为接口中的每个终结点设置一个通信通道或管道。 请求完成后,客户端驱动程序收到所选配置的一个句柄,以及在每个接口的有效备用设置中定义的终结点管道句柄。然后客户端驱动程序可以使用所收到的句柄来更改配置设置,并向一个特定终结点发送 I/O 读和写请求。
客户端驱动程序在一个 URB_FUNCTION_SELECT_CONFIGURATION 类型的 USB 请求块 (URB) 中发送选择配置请求。本主题中的过程介绍了如何使用 USBD_SelectConfigUrbAllocateAndBuild 例程来构建该 URB。该例程为 URB 分配内存,针对一个选择配置请求格式化该 URB,并将该 URB 的地址返回到客户端驱动程序。
也可以分配一个 URB 结构,然后手动或调用 UsbBuildSelectConfigurationRequest 宏来格式化该 URB。
客户端驱动程序在具有 USBD_INTERFACE_LIST_ENTRY 结构的数组中指定每个接口中要启用的备用设置。
关于 USB 复合设备的函数驱动程序:
如果 USB 设备是复合设备,则由 Microsoft 提供的 USB 通用父驱动程序 (Usbccgp.sys) 来选择配置。客户端驱动程序是复合设备的函数驱动程序之一,它不能更改配置,但仍可通过 Usbccgp.sys 发送 select-configuration 请求。
发送该请求之前,客户端驱动程序必须提交 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 请求。在响应中,Usbccgp.sys 将检索部分配置描述符,其中只包含接口描述符以及与客户端驱动程序为其加载的特定函数相关的描述符。部分配置描述符的 bNumInterfaces 字段中报告的接口数量小于为整个 USB 复合设备定义的接口总数。另外,在部分配置描述符中,接口描述符的 bInterfaceNumber 指示相对于整个设备的实际接口数量。例如,在 Usbccgp.sys 可能报告的第一个接口的部分配置描述符中,bNumInterfaces 值为 2,而 bInterfaceNumber 值为 4。请注意,接口数量大于所报告的接口数量。
在枚举部分配置中的接口时,请避免通过基于接口的数量计算接口数量来搜索接口。在上述示例中,如果在从零开始、以 (bNumInterfaces - 1)
结束,并且在每次迭代中按接口索引(在 InterfaceNumber 参数中指定)递增的循环中调用 USBD_ParseConfigurationDescriptorEx,则该例程将无法获取正确的接口。相反,请确保通过在 InterfaceNumber 中传递 -1 来搜索配置描述符中的所有接口。有关实现详细信息,请参阅本部分中的代码示例。
有关 Usbccgp.sys 如何处理由客户端驱动程序发送的 select-configuration 请求的信息,请参阅配置 Usbccgp.sys 以选择非默认 USB 配置。
接下来,通过指定要选择的配置和填充的 USBD_INTERFACE_LIST_ENTRY 结构数组,调用 USBD_SelectConfigUrbAllocateAndBuild。该例程执行以下任务:
注意 在 Windows 7 和早期版本中,客户端驱动程序通过调用 USBD_CreateConfigurationRequestEx 为 select-configuration 请求创建 URB。在 Windows 2000 中,USBD_CreateConfigurationRequestEx 将 Pipes[i].MaximumTransferSize 初始化为单个 URB 读/写请求的默认最大传输大小。客户端驱动程序可以在 Pipes[i].MaximumTransferSize 中指定不同的最大传输大小。在 Windows XP、Windows Server 2003 和更高版本的操作系统中,USB 堆栈将忽略此值。有关 MaximumTransferSize 的详细信息,请参阅设置 USB 传输和数据包大小。
要将 URB 提交到 USB 驱动程序堆栈,客户端驱动程序必须发送一个 IOCTL_INTERNAL_USB_SUBMIT_URB I/O 控制请求。有关提交 URB 的信息,请参阅如何提交 URB。
收到 URB 后,USB 驱动程序填充每个 USBD_INTERFACE_INFORMATION 结构的剩余成员。具体来讲,会为 Pipes 数组成员填入与接口终结点相关联的管道的信息。
USB 驱动程序堆栈完成请求的 IRP 后,堆栈在 USBD_INTERFACE_LIST_ENTRY 数组中返回备用设置和相关接口的列表。
每个 USBD_INTERFACE_INFORMATION 结构的 Pipes 成员指向一个 USBD_PIPE_INFORMATION 结构数组,该数组包含与该特定接口的每个终结点相关联的管道的信息。客户端驱动程序可从 Pipes[i].PipeHandle 获取管道句柄并使用它们将 I/O 请求发送到特定管道。Pipes[i].PipeType 成员指定该管道支持的终结点和传输类型。
在 URB 的 UrbSelectConfiguration 成员中,USB 驱动程序堆栈返回一个句柄,你可以使用该句柄通过提交另一个 URB_FUNCTION_SELECT_INTERFACE 类型的 URB(选择接口请求)来选择一个备用接口设置。要为该请求分配和构建 URB 结构,可以调用 USBD_SelectInterfaceUrbAllocateAndBuild。
如果没有足够的带宽来支持已启用的接口中的常时等量、控制和中断终结点,选择配置请求和选择接口请求可能失败。在此情况下,USB 总线驱动程序将 URB 头文件的 Status 成员设置为 USBD_STATUS_NO_BANDWIDTH。下面的示例代码展示了如何创建一个 USBD_INTERFACE_LIST_ENTRY 结构的数组并调用 USBD_SelectConfigUrbAllocateAndBuild。该示例通过调用 SubmitUrbSync 来同步发送请求。要查看 SubmitUrbSync 的代码示例,请参阅如何提交 URB。
/*++ Routine Description: This helper routine selects the specified configuration. Arguments: USBDHandle - USBD handle that is retrieved by the client driver in a previous call to the USBD_CreateHandle routine. ConfigurationDescriptor - Pointer to the configuration descriptor for the device. The caller receives this pointer from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request. Return Value: NT status value --*/ NTSTATUS SelectConfiguration (PDEVICE_OBJECT DeviceObject, PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor) { PDEVICE_EXTENSION deviceExtension; PIO_STACK_LOCATION nextStack; PIRP irp; PURB urb = NULL; KEVENT kEvent; NTSTATUS ntStatus; PUSBD_INTERFACE_LIST_ENTRY interfaceList = NULL; PUSB_INTERFACE_DESCRIPTOR interfaceDescriptor = NULL; PUSBD_INTERFACE_INFORMATION Interface = NULL; USBD_PIPE_HANDLE pipeHandle; ULONG interfaceIndex; PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor; deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; // Allocate an array for the list of interfaces // The number of elements must be one more than number of interfaces. interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool ( NonPagedPool, sizeof(USBD_INTERFACE_LIST_ENTRY) * (deviceExtension->NumInterfaces + 1)); if(!interfaceList) { //Failed to allocate memory ntStatus = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // Initialize the array by setting all members to NULL. RtlZeroMemory (interfaceList, sizeof ( USBD_INTERFACE_LIST_ENTRY) * (deviceExtension->NumInterfaces + 1)); // Enumerate interfaces in the configuration. for ( interfaceIndex = 0; interfaceIndex < deviceExtension->NumInterfaces; interfaceIndex++) { interfaceDescriptor = USBD_ParseConfigurationDescriptorEx( ConfigurationDescriptor, StartPosition, // StartPosition -1, // InterfaceNumber 0, // AlternateSetting -1, // InterfaceClass -1, // InterfaceSubClass -1); // InterfaceProtocol if (!interfaceDescriptor) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // Set the interface entry interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor; interfaceList[interfaceIndex].Interface = NULL; // Move the position to the next interface descriptor StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength; } // Make sure that the InterfaceDescriptor member of the last element to NULL. interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL; // Allocate and build an URB for the select-configuration request. ntStatus = USBD_SelectConfigUrbAllocateAndBuild( deviceExtension->UsbdHandle, ConfigurationDescriptor, interfaceList, &urb); if(!NT_SUCCESS(ntStatus)) { goto Exit; } // Allocate the IRP to send the buffer down the USB stack. // The IRP will be freed by IO manager. irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE); if (!irp) { //Irp could not be allocated. ntStatus = STATUS_INSUFFICIENT_RESOURCES; goto Exit; } ntStatus = SubmitUrbSync( deviceExtension->NextDeviceObject, irp, urb, CompletionRoutine); // Enumerate the pipes in the interface information array, which is now filled with pipe // information. for ( interfaceIndex = 0; interfaceIndex < deviceExtension->NumInterfaces; interfaceIndex++) { ULONG i; Interface = interfaceList[interfaceIndex].Interface; for(i=0; i < Interface->NumberOfPipes; i++) { pipeHandle = Interface->Pipes[i].PipeHandle; if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt) { deviceExtension->InterruptPipe = pipeHandle; } if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress)) { deviceExtension->BulkInPipe = pipeHandle; } if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress)) { deviceExtension->BulkOutPipe = pipeHandle; } } } Exit: if(interfaceList) { ExFreePool(interfaceList); interfaceList = NULL; } if (urb) { USBD_UrbFree( deviceExtension->UsbdHandle, urb); } return ntStatus; } NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context) { PKEVENT kevent; kevent = (PKEVENT) Context; if (Irp->PendingReturned == TRUE) { KeSetEvent(kevent, IO_NO_INCREMENT, FALSE); } KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" )); return STATUS_MORE_PROCESSING_REQUIRED; }
禁用 USB 设备的配置:
要禁用一个 USB 设备,可创建并提交一个具有 NULL 配置描述符的选择配置请求。 对于此类请求,可重用你为在设备中选择一种配置的请求而创建的 URB。也可以调用 USBD_UrbAllocate 来分配一个新的 URB。在提交请求之前,必须使用 UsbBuildSelectConfigurationRequest 宏格式化 URB,如以下示例代码所示。
URB Urb;
UsbBuildSelectConfigurationRequest(
&Urb,
sizeof(_URB_SELECT_CONFIGURATION),
NULL
);