如何枚举 USB 管道

本文概述了 USB 管道,并介绍了 USB 客户端驱动程序从 USB 驱动程序堆栈获取管道句柄所需的步骤。

USB 终结点是客户端驱动程序向设备发送数据或从中接收数据的缓冲区。 若要发送或接收数据,客户端驱动程序会将 I/O 传输请求提交到 USB 驱动程序堆栈,该堆栈将数据呈现给主机控制器。 然后,主机控制器遵循某些协议 (,具体取决于终结点的类型:批量、中断或常时等量) ,以生成向设备或从设备传输数据的请求。 数据传输的所有详细信息都从客户端驱动程序抽象化。 只要客户端驱动程序提交格式正确的请求,USB 驱动程序堆栈将处理请求并将数据传输到设备。

在设备配置期间,USB 驱动程序堆栈在主机端) 为 USB 接口中定义的每个设备的终结点及其活动备用设置创建 USB 管道 (。 USB 管道是主机控制器和终结点之间的通信通道。 对于客户端驱动程序,管道是终结点的逻辑抽象。 若要发送数据传输,驱动程序必须获取与传输目标的终结点关联的管道句柄。 当驱动程序想要中止传输或重置管道时(如果出现错误情况),也需要管道句柄。

管道的所有属性都派生自关联的终结点描述符。 例如,根据终结点的类型,USB 驱动程序堆栈会为管道分配类型。 对于批量终结点,USB 驱动程序堆栈会创建大容量管道;对于常时等量终结点,将创建常时等量管道,依此。 另一个重要属性是主机控制器可以发送到请求中的终结点的数据量。 根据该值,客户端驱动程序必须确定传输缓冲区的布局。

Windows Driver Foundation (WDF) 在 内核模式驱动程序框架 (KMDF) 用户模式驱动程序框架 (UMDF) 中提供专用 I/O 目标对象,可简化客户端驱动程序的许多配置任务。 通过使用这些对象,客户端驱动程序可以检索有关当前配置的信息,例如接口数、每个接口中的备用设置及其终结点。 其中一个对象称为 目标管道对象,执行与终结点相关的任务。 本文介绍如何使用目标管道对象获取管道信息。

对于 Windows 驱动程序模型 (WDM) 客户端驱动程序,USB 驱动程序堆栈返回 USBD_PIPE_INFORMATION 结构的数组。 数组中的元素数取决于为所选配置中接口的活动备用设置定义的终结点数。 每个元素都包含有关为特定终结点创建的管道的信息。 有关选择配置和获取管道信息数组的信息,请参阅 如何为 USB 设备选择配置

需要了解的事项

在客户端驱动程序可以枚举管道之前,请确保满足以下要求:

  • 客户端驱动程序必须已创建框架 USB 目标设备对象。

    如果使用 Microsoft Visual Studio Professional 2012 随附的 USB 模板,则模板代码会执行这些任务。 模板代码会获取目标设备对象的句柄并将其存储在设备上下文中。

    KMDF 客户端驱动程序:

    KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceCreateWithParameters 方法来获取 WDFUSBDEVICE 句柄。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (KMDF) 中的“设备源代码”。

    UMDF 客户端驱动程序:

    UMDF 客户端驱动程序必须通过查询框架目标设备对象获取 IWDFUsbTargetDevice 指针。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (UMDF) 中的“IPnpCallbackHardware 实现和特定于 USB 的任务”。

  • 设备必须具有活动配置。

    如果使用 USB 模板,则代码会选择每个接口中的第一个配置和默认备用设置。 有关如何更改该默认设置的信息,请参阅 如何在 USB 接口中选择备用设置

    KMDF 客户端驱动程序:

    KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceSelectConfig 方法。

    UMDF 客户端驱动程序:

    对于 UMDF 客户端驱动程序,框架为该配置中的每个接口选择第一个配置和默认备用设置。

在 KMDF 客户端驱动程序中获取 USB 管道句柄

框架将 USB 驱动程序堆栈打开的每个管道表示为 USB 目标管道对象。 KMDF 客户端驱动程序可以访问目标管道对象的方法,以获取有关管道的信息。 若要执行数据传输,客户端驱动程序必须具有 WDFUSBPIPE 管道句柄。 若要获取管道句柄,驱动程序必须枚举活动配置的接口和备用设置,然后枚举在每个设置中定义的终结点。 对每次数据传输执行枚举操作可能很昂贵。 因此,一种方法是在配置设备后获取管道句柄,并将其存储在驱动程序定义的设备上下文中。 当驱动程序收到数据传输请求时,驱动程序可以从设备上下文中检索所需的管道句柄,并使用它们发送请求。 例如,如果客户端驱动程序更改了设备的配置,则选择备用设置时,驱动程序还必须使用新的管道句柄刷新设备上下文。 否则,驱动程序可能会错误地在陈旧的管道句柄上发送传输请求。

控制传输不需要管道句柄。 为了发送控制传输请求,WDF 客户端驱动程序调用框架设备对象公开的 WdfUsbDevicexxxx 方法。 这些方法需要 WDFUSBDEVICE 句柄来启动针对默认终结点的控制传输。 对于此类传输,请求的 I/O 目标是默认终结点,由 WDFIOTARGET 句柄表示,该句柄由 WDFUSBPIPE 句柄抽象化。 在设备级别,WDFUSBDEVICE 句柄是默认终结点的 WDFUSBPIPE 句柄的抽象。

有关发送控制传输和 KMDF 方法的信息,请参阅 如何发送 USB 控制传输

  1. 扩展设备上下文结构以存储管道句柄。

    如果知道设备中的终结点,请通过添加 WDFUSBPIPE 成员来存储关联的 USB 管道句柄来扩展设备上下文结构。 例如,可以扩展设备上下文结构,如下所示:

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. 声明管道上下文结构。

    每个管道都可以将终结点相关的特征存储在另一个名为 管道上下文的结构中。 与设备上下文类似,管道上下文是一种数据结构, (由客户端驱动程序定义,) 用于存储有关与终结点关联的管道的信息。 在设备配置期间,客户端驱动程序将指向其管道上下文的指针传递给框架。 框架根据结构的大小分配内存块,并使用框架 USB 目标管道对象存储指向该内存位置的指针。 客户端驱动程序可以使用 指针访问管道信息,并在管道上下文的成员中存储管道信息。

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

    在此示例中,管道上下文存储一次传输中可以发送的最大字节数。 客户端驱动程序可以使用该值来确定传输缓冲区的大小。 声明还包括生成内联函数 GetPipeContext 的 WDF_DECLARE_CONTEXT_TYPE_WITH_NAME 宏。 客户端驱动程序可以调用该函数来检索指向存储管道上下文的内存块的指针。

    有关上下文的详细信息,请参阅 框架对象上下文空间

    若要将指针传递到框架,客户端驱动程序首先通过调用 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 初始化其管道上下文。 然后,在调用 WdfUsbTargetDeviceSelectConfig 时传递指向管道上下文的指针, (用于选择配置) 或 WdfUsbInterfaceSelectSetting (用于选择备用设置) 。

  3. 设备配置请求完成后,枚举接口并获取已配置管道的管道句柄。 需要这组信息:

    • 包含当前设置的接口的 WDFUSBINTERFACE 句柄。 可以通过枚举活动配置中的接口来获取该句柄。 或者,如果在 WdfUsbTargetDeviceSelectConfig 中提供了指向WDF_USB_DEVICE_SELECT_CONFIG_PARAMS结构的指针,则可以从 Type.SingleInterface.ConfigUsbInterface 成员 (获取句柄,该成员) 或多接口设备) 的 Type.MultiInterface.Pairs.UsbInterface 成员 (。
    • 在当前设置中为终结点打开的管道数。 可以通过调用 WdfUsbInterfaceGetNumConfiguredPipes 方法在特定接口上获取该数字。
    • 所有已配置管道的 WDFUSBPIPE 句柄。 可以通过调用 WdfUsbInterfaceGetConfiguredPipe 方法获取句柄。

    获取管道句柄后,客户端驱动程序可以调用方法来确定管道的类型和方向。 驱动程序可以在 WDF_USB_PIPE_INFORMATION 结构中获取有关终结点的信息。 驱动程序可以通过调用 WdfUsbTargetPipeGetInformation 方法获取填充结构。 或者,驱动程序可以提供指向 WdfUsbInterfaceGetConfiguredPipe 调用中的结构的指针。

下面的代码示例枚举当前设置中的管道。 它获取设备的批量和中断终结点的管道句柄,并将其存储在驱动程序的设备上下文结构中。 它将每个终结点的最大数据包大小存储在关联的管道上下文中。 如果终结点支持流,则它通过调用 OpenStreams 例程打开静态流。 OpenStreams 的实现如 如何在 USB 批量终结点中打开和关闭静态流中所示。

若要确定特定批量终结点是否支持静态流,客户端驱动程序会检查终结点描述符。 该代码在名为 RetrieveStreamInfoFromEndpointDesc 的帮助程序例程中实现,该例程显示在下一个代码块中。

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current alternate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

下面的代码示例演示客户端驱动程序在枚举管道时调用的名为 RetrieveStreamInfoFromEndpointDesc 的帮助程序例程。

在下面的代码示例中,客户端驱动程序在枚举管道时调用前面的帮助程序例程 RetrieveStreamInfoFromEndpointDesc。 例程首先检查获取配置描述符,并对其进行分析以检索终结点描述符。 如果管道的终结点描述符包含USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE描述符,驱动程序将检索终结点支持的最大流数。

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

获取 UMDF 客户端驱动程序中的管道句柄

UMDF 客户端驱动程序使用 COM 基础结构并实现与框架设备对象配对的 COM 回调类。 与 KMDF 驱动程序类似,UMDF 客户端驱动程序只能在配置设备后获取管道信息。 若要获取管道信息,客户端驱动程序必须获取指向包含活动设置的框架接口对象的 IWDFUsbTargetPipe 接口的指针。 通过使用接口指针,驱动程序可以枚举该设置中的管道,以获取由框架目标管道对象公开的 IWDFUsbTargetPipe 接口指针。

在驱动程序开始枚举管道之前,驱动程序必须了解设备配置和支持的终结点。 根据这些信息,驱动程序可以将管道对象存储为类成员变量。

下面的代码示例扩展了随 Visual Studio Professional 2012 提供的 USB UMDF 模板。 有关入门代码的说明,请参阅了解 USB 客户端驱动程序代码结构中的IPnpCallbackHardware 实现和特定于 USB 的任务” (UMDF) 。

扩展 CDevice 类声明,如下所示。 此示例代码假定设备是 OSR FX2 板。 有关其描述符布局的信息,请参阅 USB 设备布局

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:
    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:
    IWDFDevice *            m_FxDevice;
    CMyIoQueue *            m_IoQueue;
    IWDFUsbTargetDevice *   m_FxUsbDevice;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

private:
    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

在 CDevice 类定义中,实现名为 CreateUsbIoTargets 的帮助程序方法。 在驱动程序获取指向目标设备对象的指针后,将从 IPnpCallbackHardware::OnPrepareHardware 实现调用此方法。

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

在 UMDF 中,客户端驱动程序使用管道索引发送数据传输请求。 管道索引是 USB 驱动程序堆栈在设置中打开终结点的管道时分配的数字。 若要获取管道索引,请调用**IWDFUsbTargetPipe::GetInformation** 方法。 方法填充 WINUSB_PIPE_INFORMATION 结构。 PipeId 值指示管道索引。

在目标管道上执行读取和写入操作的一种方法是调用 IWDFUsbInterface::GetWinUsbHandle 以获取 WinUSB 句柄,然后调用 WinUSB 函数。 例如,驱动程序可以调用 WinUsb_ReadPipeWinUsb_WritePipe 函数。 在这些函数调用中,驱动程序必须指定管道索引。 有关详细信息,请参阅 如何使用 WinUSB 函数访问 USB 设备

基于 WDM 的客户端驱动程序的管道句柄

选择配置后,USB 驱动程序堆栈会设置指向每个设备终结点的管道。 USB 驱动程序堆栈返回 USBD_PIPE_INFORMATION 结构的数组。 数组中的元素数取决于为所选配置中接口的活动备用设置定义的终结点数。 每个元素都包含有关为特定终结点创建的管道的信息。 有关获取管道句柄的详细信息,请参阅 如何为 USB 设备选择配置

若要生成 I/O 传输请求,客户端驱动程序必须具有与该终结点关联的管道的句柄。 客户端驱动程序可以从数组中USBD_PIPE_INFORMATIONPipeHandle 成员获取管道句柄。

除了管道句柄,客户端驱动程序还需要管道类型。 客户端驱动程序可以通过检查 PipeType 成员来确定管道类型

根据终结点类型,USB 驱动程序堆栈支持不同类型的管道。 客户端驱动程序可以通过检查 USBD_PIPE_INFORMATION 的 PipeType 成员来确定 管道类型。 不同的管道类型需要不同类型的 USB 请求块 (URB) 来执行 I/O 事务。

然后,客户端驱动程序将 URB 提交到 USB 驱动程序堆栈。 USB 驱动程序堆栈处理请求,并将指定数据发送到请求的目标管道。

URB 包含有关请求的信息,例如目标管道句柄、传输缓冲区及其长度。 URB 联合中的每个结构共享某些成员:TransferFlagsTransferBufferTransferBufferLengthTransferBufferMDLTransferFlags 成员中存在对应于每个 URB 类型的特定于类型的标志。 对于所有数据传输 URL,TransferFlags 中的USBD_TRANSFER_DIRECTION_IN标志指定传输方向。 客户端驱动程序将 USBD_TRANSFER_DIRECTION_IN 标志设置为从设备读取数据。 驱动程序清除此标志以将数据发送到设备。 可以从驻留在内存中的缓冲区或 MDL 中读取或写入数据。 在任一情况下,驱动程序都指定 TransferBufferLength 成员中的缓冲区大小。 驱动程序在 TransferBuffer 成员中提供常驻缓冲区,在 TransferBufferMDL 成员中提供 MDL。 无论驱动程序提供哪一个,另一个必须为 NULL。