Como enumerar pipes USB

Este artigo fornece uma visão geral dos pipes USB e descreve as etapas exigidas por um driver cliente USB para obter identificadores de pipe da pilha de driver USB.

Um ponto de extremidade USB é um buffer no dispositivo para o qual o driver cliente envia dados ou recebe dados. Para enviar ou receber dados, o driver cliente envia uma solicitação de transferência de E/S para a pilha de driver USB, que apresenta os dados para o controlador de host. Em seguida, o controlador host segue determinados protocolos (dependendo do tipo de ponto de extremidade: em massa, interrupção ou isócrono) para criar solicitações que transferem dados de ou para o dispositivo. Todos os detalhes da transferência de dados são abstraídos do driver cliente. Desde que o driver cliente envie uma solicitação bem formada, a pilha de driver USB processa a solicitação e transfere dados para o dispositivo.

Durante a configuração do dispositivo, a pilha de driver USB cria um pipe USB (no lado do host) para cada um dos pontos de extremidade do dispositivo definidos na interface USB e sua configuração alternativa ativa. Um pipe USB é um canal de comunicação entre o controlador de host e o ponto de extremidade. Para o driver cliente, um pipe é uma abstração lógica do ponto de extremidade. Para enviar transferências de dados, o driver deve obter o identificador de pipe associado ao ponto de extremidade que é o destino da transferência. As alças de pipe também são necessárias quando o driver deseja anular transferências ou redefinir o pipe, em caso de condições de erro.

Todos os atributos de um pipe são derivados do descritor de ponto de extremidade associado. Por exemplo, dependendo do tipo do ponto de extremidade, a pilha de driver USB atribui um tipo para o pipe. Para um ponto de extremidade em massa, a pilha de driver USB cria um pipe em massa; para um ponto de extremidade isócrono, um pipe isócrono é criado e assim por diante. Outro atributo importante é a quantidade de dados que o controlador host pode enviar para o ponto de extremidade em uma solicitação. Dependendo desse valor, o driver do cliente deve determinar o layout do buffer de transferência.

O WDF (Windows Driver Foundation) fornece objetos de destino de E/S especializados no KMDF (Kernel-Mode Driver Framework) e na UMDF (User-Mode Driver Framework) que simplificam muitas das tarefas de configuração para o driver cliente. Usando esses objetos, o driver do cliente pode recuperar informações sobre a configuração atual, como o número de interfaces, a configuração alternativa dentro de cada interface e seus pontos de extremidade. Um desses objetos, chamado de objeto pipe de destino, executa tarefas relacionadas ao ponto de extremidade. Este artigo descreve como obter informações de pipe usando o objeto pipe de destino.

Para drivers de cliente WDM (Modelo de Driver do Windows), a pilha de driver USB retorna uma matriz de estruturas de USBD_PIPE_INFORMATION . O número de elementos na matriz depende do número de pontos de extremidade definidos para a configuração alternativa ativa de uma interface na configuração selecionada. Cada elemento contém informações sobre o pipe criado para um ponto de extremidade específico. Para obter informações sobre como selecionar uma configuração e obter a matriz de informações de pipe, consulte Como selecionar uma configuração para um dispositivo USB.

O que você precisa saber

Antes que o driver cliente possa enumerar pipes, verifique se esses requisitos foram atendidos:

Obtendo identificadores de pipe USB em um driver cliente KMDF

A estrutura representa cada pipe, que é aberto pela pilha do driver USB, como um objeto de pipe de destino USB. Um driver cliente KMDF pode acessar os métodos do objeto de pipe de destino para obter informações sobre o pipe. Para executar transferências de dados, o driver do cliente deve ter identificadores de pipe WDFUSBPIPE. Para obter as alças de pipe, o driver deve enumerar as interfaces e as configurações alternativas da configuração ativa e, em seguida, enumerar os pontos de extremidade definidos em cada configuração. Executar operações de enumeração, para cada transferência de dados, pode ser caro. Portanto, uma abordagem é obter identificadores de pipe depois que o dispositivo for configurado e armazená-los no contexto do dispositivo definido pelo driver. Quando o driver recebe solicitações de transferência de dados, o driver pode recuperar as alças de pipe necessárias do contexto do dispositivo e usá-las para enviar a solicitação. Se o driver do cliente alterar a configuração do dispositivo, por exemplo, selecionar uma configuração alternativa, o driver também deverá atualizar o contexto do dispositivo com as novas alças de pipe. Caso contrário, o driver pode enviar erroneamente solicitações de transferência em identificadores de pipe obsoletos.

As alças de pipe não são necessárias para transferências de controle. Para enviar solicitações de transferência de controle, um driver cliente WDF chama métodos WdfUsbDevicexxxx expostos pelo objeto de dispositivo da estrutura. Esses métodos exigem um identificador WDFUSBDEVICE para iniciar transferências de controle direcionadas ao ponto de extremidade padrão. Para essas transferências, o destino de E/S para a solicitação é o ponto de extremidade padrão e é representado pelo identificador WDFIOTARGET, que é abstraído pelo identificador WDFUSBPIPE. No nível do dispositivo, o identificador WDFUSBDEVICE é uma abstração do identificador WDFUSBPIPE para o ponto de extremidade padrão.

Para obter informações sobre como enviar transferências de controle e os métodos KMDF, consulte Como enviar uma transferência de controle USB.

  1. Estenda a estrutura de contexto do dispositivo para armazenar identificadores de pipe.

    Se você souber os pontos de extremidade em seu dispositivo, estenda a estrutura de contexto do dispositivo adicionando membros WDFUSBPIPE para armazenar as alças de pipe USB associadas. Por exemplo, você pode estender a estrutura de contexto do dispositivo, conforme mostrado aqui:

    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. Declare uma estrutura de contexto de pipe.

    Cada pipe pode armazenar características relacionadas ao ponto de extremidade em outra estrutura chamada de contexto de pipe. Semelhante a um contexto de dispositivo, um contexto de pipe é uma estrutura de dados (definida pelo driver do cliente) para armazenar informações sobre pipes associados a pontos de extremidade. Durante a configuração do dispositivo, o driver cliente passa um ponteiro para seu contexto de pipe para a estrutura. A estrutura aloca um bloco de memória com base no tamanho da estrutura e armazena um ponteiro para esse local de memória com o objeto de pipe de destino USB da estrutura. O driver cliente pode usar o ponteiro para acessar e armazenar informações de pipe em membros do contexto de pipe.

    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)
    
    

    Neste exemplo, o contexto de pipe armazena o número máximo de bytes que podem ser enviados em uma transferência. O driver cliente pode usar esse valor para determinar o tamanho do buffer de transferência. A declaração também inclui a macro WDF_DECLARE_CONTEXT_TYPE_WITH_NAME , que gera uma função embutida, GetPipeContext. O driver cliente pode chamar essa função para recuperar um ponteiro para o bloco de memória que armazena o contexto de pipe.

    Para obter mais informações sobre contextos, consulte Espaço de contexto do objeto framework.

    Para passar um ponteiro para a estrutura, o driver do cliente primeiro inicializa seu contexto de pipe chamando WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE. Em seguida, passa um ponteiro para o contexto de pipe ao chamar WdfUsbTargetDeviceSelectConfig (para selecionar uma configuração) ou WdfUsbInterfaceSelectSetting (para selecionar uma configuração alternativa).

  3. Depois que a solicitação de configuração do dispositivo for concluída, enumere a interface e obtenha as alças de pipe para os pipes configurados. Você precisará desse conjunto de informações:

    • Identificador WDFUSBINTERFACE para a interface que contém a configuração atual. Você pode obter esse identificador enumerando as interfaces na configuração ativa. Como alternativa, se você forneceu um ponteiro para uma estrutura WDF_USB_DEVICE_SELECT_CONFIG_PARAMS no membro WdfUsbTargetDeviceSelectConfig, poderá obter o identificador do membro Type.SingleInterface.ConfiguredUsbInterface (para dispositivos de interface única) ou membro Type.MultiInterface.Pairs.UsbInterface (para dispositivos de várias interfaces).
    • Número de pipes abertos para os pontos de extremidade na configuração atual. Você pode obter esse número em uma interface específica chamando o método WdfUsbInterfaceGetNumConfiguredPipes .
    • Identificadores WDFUSBPIPE para todos os pipes configurados. Você pode obter o identificador chamando o método WdfUsbInterfaceGetConfiguredPipe .

    Depois de obter o identificador de pipe, o driver cliente pode chamar métodos para determinar o tipo e a direção do pipe. O driver pode obter informações sobre o ponto de extremidade, em uma estrutura WDF_USB_PIPE_INFORMATION . O driver pode obter a estrutura preenchida chamando o método WdfUsbTargetPipeGetInformation . Como alternativa, o driver pode fornecer um ponteiro para a estrutura na chamada WdfUsbInterfaceGetConfiguredPipe .

O exemplo de código a seguir enumera os pipes na configuração atual. Ele obtém identificadores de pipe para os pontos de extremidade em massa e interrupção do dispositivo e os armazena na estrutura de contexto do dispositivo do driver. Ele armazena o tamanho máximo do pacote de cada ponto de extremidade no contexto de pipe associado. Se o ponto de extremidade der suporte a fluxos, ele abrirá fluxos estáticos chamando a rotina OpenStreams. A implementação do OpenStreams é mostrada em Como abrir e fechar fluxos estáticos em um ponto de extremidade em massa USB.

Para determinar se um ponto de extremidade em massa específico dá suporte a fluxos estáticos, o driver cliente examina o descritor de ponto de extremidade. Esse código é implementado em uma rotina auxiliar chamada RetrieveStreamInfoFromEndpointDesc, mostrada no próximo bloco de código.

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;
}

O exemplo de código a seguir mostra uma rotina auxiliar chamada RetrieveStreamInfoFromEndpointDesc, que o driver do cliente chama ao enumerar pipes.

No exemplo de código a seguir, o driver do cliente chama a rotina auxiliar anterior, RetrieveStreamInfoFromEndpointDesc, ao enumerar pipes. A rotina examina primeiro obtém o descritor de configuração e o analisa para recuperar descritores de ponto de extremidade. Se o descritor de ponto de extremidade para o pipe contiver um descritor USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE, o driver recuperará o número máximo de fluxos com suporte pelo ponto de extremidade.

/*++
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;
}

Obtendo identificadores de pipe em um driver cliente UMDF

Um driver cliente UMDF usa a infraestrutura COM e implementa classes de retorno de chamada COM que são emparelhada com objetos de dispositivo de estrutura. Semelhante a um driver KMDF, um driver cliente UMDF só pode obter informações de pipe depois que o dispositivo é configurado. Para obter informações de pipe, o driver do cliente deve obter um ponteiro para a interface IWDFUsbTargetPipe do objeto de interface da estrutura que contém a configuração ativa. Usando o ponteiro de interface, o driver pode enumerar os pipes nessa configuração para obter ponteiros de interface IWDFUsbTargetPipe expostos pelos objetos de pipe de destino da estrutura.

Antes que o driver comece a enumerar os pipes, o driver deve saber sobre a configuração do dispositivo e os pontos de extremidade com suporte. Com base nessas informações, o driver pode armazenar objetos de pipe como variáveis de membro de classe.

O exemplo de código a seguir estende o modelo USB UMDF fornecido com Visual Studio Professional 2012. Para obter uma explicação do código inicial, consulte "Implementação de IPnpCallbackHardware e tarefas específicas de USB" em Noções básicas sobre a estrutura de código do driver de cliente USB (UMDF).

Estenda a declaração de classe CDevice, conforme mostrado aqui. Este código de exemplo pressupõe que o dispositivo seja a placa OSR FX2. Para obter informações sobre seu layout de descritor, consulte Layout do dispositivo 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
        );

};

Na definição da classe CDevice, implemente um método auxiliar chamado CreateUsbIoTargets. Esse método é chamado da implementação IPnpCallbackHardware::OnPrepareHardware depois que o driver tiver obtido um ponteiro para o objeto de dispositivo de destino.

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;
}

No UMDF, o driver do cliente usa um índice de pipe para enviar solicitações de transferência de dados. Um índice de pipe é um número atribuído pela pilha do driver USB quando abre pipes para os pontos de extremidade em uma configuração. Para obter o índice de pipe, chame o método **IWDFUsbTargetPipe::GetInformation**. O método preenche uma estrutura de WINUSB_PIPE_INFORMATION . O valor PipeId indica o índice de pipe.

Uma maneira de executar operações de leitura e gravação no pipe de destino é chamar IWDFUsbInterface::GetWinUsbHandle para obter um identificador WinUSB e, em seguida, chamar funções WinUSB. Por exemplo, o driver pode chamar a função WinUsb_ReadPipe ou WinUsb_WritePipe . Nessas chamadas de função, o driver deve especificar o índice de pipe. Para obter mais informações, consulte Como acessar um dispositivo USB usando funções WinUSB.

Identificadores de pipe para drivers de cliente baseados em WDM

Depois que uma configuração é selecionada, a pilha do driver USB configura um pipe para cada um dos pontos de extremidade do dispositivo. A pilha do driver USB retorna uma matriz de estruturas de USBD_PIPE_INFORMATION . O número de elementos na matriz depende do número de pontos de extremidade definidos para a configuração alternativa ativa de uma interface na configuração selecionada. Cada elemento contém informações sobre o pipe criado para um ponto de extremidade específico. Para obter mais informações sobre como obter identificadores de pipe, consulte Como selecionar uma configuração para um dispositivo USB.

Para criar uma solicitação de transferência de E/S, o driver do cliente deve ter um identificador para o pipe associado a esse ponto de extremidade. O driver cliente pode obter o identificador de pipe do membro PipeHandle de USBD_PIPE_INFORMATION na matriz.

Além do identificador de pipe, o driver cliente também requer o tipo de pipe. O driver do cliente pode determinar o tipo de pipe examinando o membro PipeType .

Com base no tipo de ponto de extremidade, a pilha de driver USB dá suporte a diferentes tipos de pipes. O driver do cliente pode determinar o tipo de pipe examinando o membro PipeType de USBD_PIPE_INFORMATION. Os diferentes tipos de pipe exigem tipos diferentes de URBs (blocos de solicitação USB) para executar transações de E/S.

Em seguida, o driver cliente envia o URB para a pilha de driver USB. A pilha de driver USB processa a solicitação e envia os dados especificados para o pipe de destino solicitado.

O URB contém informações sobre a solicitação, como o identificador de pipe de destino, o buffer de transferência e seu comprimento. Cada estrutura dentro do sindicato URB compartilha determinados membros: TransferFlags, TransferBuffer, TransferBufferLength e TransferBufferMDL. Há sinalizadores específicos de tipo no membro TransferFlags que correspondem a cada tipo de URB. Para todos os URBs de transferência de dados, o sinalizador USBD_TRANSFER_DIRECTION_IN em TransferFlags especifica a direção da transferência. Os drivers de cliente definem o sinalizador USBD_TRANSFER_DIRECTION_IN para ler dados do dispositivo. Os drivers limpam esse sinalizador para enviar dados para o dispositivo. Os dados podem ser lidos ou gravados em um residente de buffer na memória ou em um MDL. Em ambos os casos, o driver especifica o tamanho do buffer no membro TransferBufferLength . O driver fornece um buffer residente no membro TransferBuffer e um MDL no membro TransferBufferMDL . Seja qual for o driver que o driver fornecer, o outro deve ser NULL.