USB 设备以一系列称为 USB 配置的接口的形式公开其功能。 每个接口由一个或多个备用设置组成,每个备用设置由一组终结点组成。 设备必须至少提供一个配置,但它可以提供多个配置,这些配置是设备可以执行的操作的互斥定义。 有关配置描述符的详细信息,请参阅 USB 配置描述符。
设备配置指的是客户端驱动程序执行的任务,用于选择 USB 配置和每个接口中的备用接口。 在向设备发送 I/O 请求之前,客户端驱动程序必须读取设备的配置、分析信息并选择合适的配置。 客户端驱动程序必须至少选择一个受支持的配置才能使设备正常工作。
基于 WDM 的客户端驱动程序可以选择 USB 设备中的任何配置。
如果客户端驱动程序基于 内核模式驱动程序框架 或 用户模式驱动程序框架,则应使用相应的框架接口来配置 USB 设备。 如果使用随 Microsoft Visual Studio Professional 2012 提供的 USB 模板,模板代码将在每个接口中选择第一个配置和默认备用设置。
选择配置的限制
如果客户端驱动程序使用 WDF 对象,或者设备是具有单个接口还是多个接口,则某些限制适用。 更改默认配置之前,请考虑以下限制:
- 通过 USB 通用父 驱动程序 (Usbccgp.sys) 管理接口或接口集合的复合设备的客户端驱动程序无法更改设备的配置值。 但是,客户端驱动程序可以将 Usbccgp.sys 配置为选择除第一个 (默认) 配置之外的配置。 有关详细信息,请参阅 配置 Usbccgp.sys 以选择非默认 USB 配置;
- 使用框架的 USB I/O 目标的 基于 KMDF 的客户端驱动程序只能选择第一个配置;
- WinUSB 仅支持第一个配置;
- 类驱动程序通常缺少对多个配置的支持。 如果设备实现了由 USB 类规范定义的类,请参阅 USB 技术 网站,了解有关设备类和类规范的信息。 Microsoft 为支持的 USB 设备类提供类驱动程序。
如何选择 USB 设备的配置
若要为 USB 设备选择配置,设备的客户端驱动程序必须至少选择一个受支持的配置,并指定要使用的每个接口的备用设置。 客户端驱动程序将这些选项打包在 选择配置请求 中,并将请求发送到 Microsoft 提供的 USB 驱动程序堆栈,特别是 USB 总线驱动程序。 USB 总线驱动程序选择指定配置中的每个接口,并设置到接口内每个终结点的信道或 管道。 请求完成后,客户端驱动程序会收到所选配置的句柄,以及每个接口的活动备用设置中定义的终结点的管道句柄。 然后,客户端驱动程序可以使用收到的句柄更改配置设置,并将 I/O 读取和写入请求发送到特定终结点。
客户端驱动程序在 类型为 URB_FUNCTION_SELECT_CONFIGURATION 的 USB 请求块 (URB) 发送选择配置请求。 本主题中的过程介绍如何使用 USBD_SelectConfigUrbAllocateAndBuild 例程来生成该 URB。 例程为 URB 分配内存,为选择配置请求设置 URB 的格式,并将 URB 的地址返回到客户端驱动程序;或者,可以分配 URB 结构,然后手动或调用 UsbBuildSelectConfigurationRequest 宏格式化 URB 。
先决条件
- 从 Windows 8 开始,USBD_SelectConfigUrbAllocateAndBuild替换USBD_CreateConfigurationRequestEx;
- 在发送选择配置请求之前,必须具有一个 USBD 句柄,以便将客户端驱动程序注册到 USB 驱动程序堆栈。 创建 USBD 句柄调用 USBD_CreateHandle;
- 确保已获取要选择的配置 (USB_CONFIGURATION_DESCRIPTOR 结构) 的配置描述符。 通常,提交类型的 URB URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 查看 _URB_CONTROL_DESCRIPTOR_REQUEST来检索有关设备配置的信息;
步骤 1:创建USBD_INTERFACE_LIST_ENTRY结构的数组
1.获取配置中的接口数。 此信息包含在 USB_CONFIGURATION_DESCRIPTOR 结构的 bNumInterfaces 成员中。
2.创建 USBD_INTERFACE_LIST_ENTRY 结构的数组。 数组中的元素数必须比接口数多一个。 通过调用 RtlZeroMemory 初始化数组。
客户端驱动程序在 USBD_INTERFACE_LIST_ENTRY 结构数组中指定要启用的每个接口中的备用设置。
- 每个 结构的 InterfaceDescriptor 成员指向包含备用设置的接口描述符;
- 每个结构的 Interface 成员指向在其 Pipes 成员中包含管道信息的 USBD_INTERFACE_INFORMATION 结构。 管道 存储有关备用设置中定义的每个终结点的信息;
3.获取配置中每个接口 (或其备用设置) 的接口描述符。 可以通过调用 USBD_ParseConfigurationDescriptorEx 获取这些接口描述符。
关于 USB 复合设备的功能驱动程序:如果 USB 设备是复合设备,则配置由 Microsoft 提供的 USB 通用父驱动程序 (Usbccgp.sys) 选择。 客户端驱动程序是复合设备的功能驱动程序之一,无法更改配置,但驱动程序仍可以通过 Usbccgp.sys 发送选择配置请求。
在发送该请求之前,客户端驱动程序必须提交URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE请求。 作为响应,Usbccgp.sys 检索仅包含接口描述符的部分 配置描述 符,以及与为其加载客户端驱动程序的特定函数相关的其他描述符。 部分配置描述符的 bNumInterfaces 字段中报告的接口数小于为整个 USB 复合设备定义的接口总数。 此外,在部分配置描述符中,接口描述符的 bInterfaceNumber 指示相对于整个设备的实际接口编号。 例如,对于第一个接口,Usbccgp.sys 可能会报告部分配置描述符,其中 bNumfaces 值为 2,bInterfaceNumber 值为 4。 请注意,接口编号大于报告的接口数。
枚举部分配置中的接口时,请根据接口数计算接口编号,避免搜索接口。 在前面的示例中,如果在循环中调用 USBD_ParseConfigurationDescriptorEx ,该循环从零开始,在 (bNumInterfaces - 1)处结束,并在每次迭代中递增 InterfaceNumber 参数中指定的接口索引,则例程无法获取正确的接口。 相反,请确保通过在 InterfaceNumber 中传递 -1 来搜索配置描述符中的所有接口。
4.对于除数组中最后一个元素之外的每个元素,请将 InterfaceDescriptor 成员设置为接口描述符的地址。 对于数组中的第一个元素,将 InterfaceDescriptor 成员设置为表示配置中第一个接口的接口描述符的地址。 同样,对于数组中的 第 n个元素,将 InterfaceDescriptor 成员设置为表示配置中 第 n个接口的接口描述符的地址。
5.最后一个元素的 InterfaceDescriptor 成员必须设置为 NULL。
步骤 2:获取指向 USB 驱动程序堆栈分配的 URB 的指针
接下来 ,通过指定要 选择的配置和填充USBD_INTERFACE_LIST_ENTRY结构的数组来调用 USBD_SelectConfigUrbAllocateAndBuild 。 例程执行以下任务:
- 创建一个 URB,并在其中填充有关指定配置、其接口和终结点的信息,并将请求类型设置为URB_FUNCTION_SELECT_CONFIGURATION;
- 在该 URB 中, 为客户端驱动程序指定的每个接口描述符分配 USBD_INTERFACE_INFORMATION 结构;
- 将调用方提供的USBD_INTERFACE_LIST_ENTRY数组的第 n个元素的 Interface 成员设置为 URB 中相应USBD_INTERFACE_INFORMATION结构的地址;
- 初始化 InterfaceNumber、 AlternateSetting、 NumberOfPipes、 Pipes[i]。MaximumTransferSize 和 Pipes[i]。PipeFlags 成员;
在 Windows 7 和 ealier 中,客户端驱动程序通过调用 USBD_CreateConfigurationRequestEx 为选择配置请求创建了 URB。 在 Windows 2000 USBD_CreateConfigurationRequestEx 初始化 Pipes[i]。MaximumTransferSize 为单个 URB 读/写请求的默认最大传输大小。 客户端驱动程序可以在 Pipes[i] 中指定不同的最大传输大小。MaximumTransferSize。 USB 堆栈在 Windows XP、Windows Server 2003 和更高版本的操作系统中忽略此值。
步骤 3:将 URB 提交到 USB 驱动程序堆栈
若要将 URB 提交到 USB 驱动程序堆栈,客户端驱动程序必须发送 IOCTL_INTERNAL_USB_SUBMIT_URB I/O 控制请求 。
收到 URB 后,USB 驱动程序堆栈将填充每个 USBD_INTERFACE_INFORMATION 结构的其余成员。 具体而言, Pipes 数组成员将填充与接口终结点关联的管道的相关信息。
步骤 4:请求完成后,检查USBD_INTERFACE_INFORMATION结构和 URB
USB 驱动程序堆栈完成请求的 IRP 后,堆栈将返回 USBD_INTERFACE_LIST_ENTRY 数组中的备用设置和相关接口的列表。
1.每个USBD_INTERFACE_INFORMATION结构的 Pipes 成员指向USBD_PIPE_INFORMATION结构的数组,该数组包含与该特定接口的每个终结点关联的管道的相关信息。 客户端驱动程序可以从 Pipes[i] 获取管道句柄。PipeHandle 并使用它们向特定管道发送 I/O 请求。 Pipes[i]。PipeType 成员指定该管道支持的终结点和传输的类型。
2.在 URB 的 UrbSelectConfiguration 成员中,USB 驱动程序堆栈返回一个句柄,该句柄可用于通过提交另一个类型的 URB URB_FUNCTION_SELECT_INTERFACE (选择接口请求) 来选择备用接口设置。 若要为该请求分配和生成 URB 结构,请调用 USBD_SelectInterfaceUrbAllocateAndBuild。
如果没有足够的带宽支持已启用接口中的常时等量、控制和中断终结点,则选择配置请求和选择接口请求可能会失败。 在这种情况下,USB 总线驱动程序将 URB 标头的 Status 成员设置为USBD_STATUS_NO_BANDWIDTH。
下面的示例代码演示如何创建 USBD_INTERFACE_LIST_ENTRY 结构的数组并调用 USBD_SelectConfigUrbAllocateAndBuild。 该示例通过调用 SubmitUrbSync 以同步方式发送请求。
/*++
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
);