如何获取配置描述符
配置描述符是通过标准设备请求 (GET_DESCRIPTOR) 从设备获取的,该请求由 USB 驱动程序堆栈作为控制传输发送。 USB 客户端驱动程序可以通过以下方式之一启动请求:
- 如果设备仅支持一种配置,最简单的方法是调用框架提供的 WdfUsbTargetDeviceRetrieveConfigDescriptor 方法;
- 对于支持多个配置的设备,如果客户端驱动程序想要获取第一个配置以外的配置描述符,则驱动程序必须提交 URB。 若要提交 URB,驱动程序必须分配、格式化 URB,然后将 URB 提交到 USB 驱动程序堆栈;
若要分配 URB,客户端驱动程序必须调用 WdfUsbTargetDeviceCreateUrb 方法。 方法接收指向 USB 驱动程序堆栈分配的 URB 的指针。
若要设置 URB 的格式,客户端驱动程序可以使用 UsbBuildGetDescriptorRequest 宏。 宏在 URB 中设置所有必要的信息,例如要检索其描述符的设备定义的配置编号。 URB 函数设置为 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (see _URB_CONTROL_DESCRIPTOR_REQUEST) ,并将描述符的类型设置为 USB_CONFIGURATION_DESCRIPTOR_TYPE。 通过使用 URB 中包含的信息,USB 驱动程序堆栈生成标准控制请求并将其发送到设备。
若要提交 URB,客户端驱动程序必须使用 WDF 请求对象。 若要以异步方式将请求对象发送到 USB 驱动程序堆栈,驱动程序必须调用 **WdfRequestSend** 方法。 若要以同步方式发送,请调用 WdfUsbTargetDeviceSendUrbSynchronously 方法。
WDM 驱动程序: Windows 驱动程序模型 (WDM) 客户端驱动程序只能通过提交 URB 来获取配置描述符。 若要分配 URB,驱动程序必须调用 USBD_UrbAllocate 例程。 若要设置 URB 的格式,驱动程序必须调用 UsbBuildGetDescriptorRequest 宏。 若要提交 URB,驱动程序必须将 URB 与 IRP 相关联,并将 IRP 提交到 USB 驱动程序堆栈。 有关详细信息,请参阅 如何提交 URB。
在 USB 配置中,接口数及其备用设置是可变的。 因此,很难预测保存配置描述符所需的缓冲区大小。 客户端驱动程序必须通过两个步骤收集所有这些信息。 首先,确定保存所有配置描述符所需的大小缓冲区,然后发出检索整个描述符的请求。 客户端驱动程序可以通过以下方式之一获取大小:
若要通过调用 WdfUsbTargetDeviceRetrieveConfigDescriptor 获取配置描述符,请执行以下步骤:
- 通过调用 WdfUsbTargetDeviceRetrieveConfigDescriptor 获取保存所有配置信息所需的缓冲区大小。 驱动程序必须在缓冲区中传递 NULL,并传递一个变量来保存缓冲区的大小;
- 根据通过上一个 WdfUsbTargetDeviceRetrieveConfigDescriptor 调用接收的大小分配更大的缓冲区;
- 再次调用 WdfUsbTargetDeviceRetrieveConfigDescriptor ,并指定指向步骤 2 中分配的新缓冲区的指针;
NTSTATUS RetrieveDefaultConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = -1;
USHORT sizeConfigDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PAGED_CODE();
*ConfigDescriptor = NULL;
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
NULL,
&sizeConfigDesc);
if (sizeConfigDesc == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
goto Exit;
}
else
{
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
sizeConfigDesc,
USBCLIENT_TAG);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
}
RtlZeroMemory (fullConfigDesc, sizeConfigDesc);
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
fullConfigDesc,
&sizeConfigDesc);
if (!NT_SUCCESS(ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
goto Exit;
}
*ConfigDescriptor = fullConfigDesc;
Exit:
return ntStatus;
}
若要通过提交 URB 获取配置描述符,请执行以下步骤:
- 通过调用 WdfUsbTargetDeviceCreateUrb 方法分配 URB ;
- 通过调用 UsbBuildGetDescriptorRequest 宏设置 URB 的格式。 URB 的传输缓冲区必须指向足以容纳 USB_CONFIGURATION_DESCRIPTOR 结构的缓冲区;
- 通过调用 WdfRequestSend 或 WdfUsbTargetDeviceSendUrbSynchronously 将 URB 作为 WDF 请求对象提交;
- 请求完成后,检查 USB_CONFIGURATION_DESCRIPTOR 的 wTotalLength 成员。 该值指示包含完整配置描述符所需的缓冲区大小;
- 根据 wTotalLength 中检索到的大小分配更大的缓冲区;
- 对较大的缓冲区发出相同的请求;
以下示例代码演示请求的 UsbBuildGetDescriptorRequest 调用,以获取第 i 个配置的配置信息:
NTSTATUS FX3_RetrieveConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_In_ PUCHAR ConfigurationIndex,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
USB_CONFIGURATION_DESCRIPTOR configDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PURB urb = NULL;
WDFMEMORY urbMemory = NULL;
PAGED_CODE();
RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
*ConfigDescriptor = NULL;
// Allocate an URB for the get-descriptor request.
// WdfUsbTargetDeviceCreateUrb returns the address of the
// newly allocated URB and the WDFMemory object that
// contains the URB.
ntStatus = WdfUsbTargetDeviceCreateUrb (
UsbDevice,
NULL,
&urbMemory,
&urb);
if (!NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate URB for an open-streams request.");
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb, // Points to the URB to be formatted
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ), // Size of the URB.
USB_CONFIGURATION_DESCRIPTOR_TYPE, // Type of descriptor
*ConfigurationIndex, // Index of the configuration
0, // Not used for configuration descriptors
&configDesc, // Points to a USB_CONFIGURATION_DESCRIPTOR structure
NULL, // Not required because we are providing a buffer not MDL
sizeof(USB_CONFIGURATION_DESCRIPTOR), // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
NULL // Reserved.
);
// Send the request synchronously.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if (configDesc.wTotalLength == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Allocate memory based on the retrieved size.
// The allocated memory is released by the caller.
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
configDesc.wTotalLength,
USBCLIENT_TAG);
RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb,
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),
USB_CONFIGURATION_DESCRIPTOR_TYPE,
*ConfigurationIndex,
0,
fullConfigDesc,
NULL,
configDesc.wTotalLength,
NULL
);
// Send the request again.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Return to the caller.
*ConfigDescriptor = fullConfigDesc;
Exit:
if (urbMemory)
{
WdfObjectDelete (urbMemory);
}
return ntStatus;
}
当设备返回配置描述符时,请求缓冲区中将填充所有备用设置的接口描述符,以及特定备用设置中所有端点的端点描述符。 对于 USB 设备布局中所述的设备,下图演示了配置信息在内存中的布局方式。
USB_INTERFACE_DESCRIPTOR的从零开始的 bInterfaceNumber 成员区分配置中的接口。 对于给定接口,从零开始的 bAlternateSetting 成员区分接口的备用设置。 设备按 bInterfaceNumber 值的顺序返回接口描述符,然后按 bAlternateSetting 值的顺序返回接口描述符。
若要在配置中搜索给定的接口描述符,客户端驱动程序可以调用 USBD_ParseConfigurationDescriptorEx。 在 调用中,客户端驱动程序在配置中提供起始位置。 (可选)驱动程序可以指定接口号、备用设置、类、子类或协议。 例程返回指向下一个匹配接口描述符的指针。
若要检查端点或字符串描述符的配置描述符,请使用 USBD_ParseDescriptors 例程。 调用方在配置中提供起始位置和描述符类型,例如USB_STRING_DESCRIPTOR_TYPE或USB_ENDPOINT_DESCRIPTOR_TYPE。 例程返回指向下一个匹配描述符的指针。