Partilhar via


Como abrir e fechar fluxos estáticos em um ponto de extremidade em massa USB

Este artigo aborda a funcionalidade de fluxos estáticos e explica como um driver de cliente USB pode abrir e fechar fluxos em um ponto de extremidade em massa de um dispositivo USB 3.0.

Em dispositivos USB 2.0 e anteriores, um ponto de extremidade em massa pode enviar ou receber um único fluxo de dados por meio do ponto de extremidade. Em dispositivos USB 3.0, os pontos de extremidade em massa têm a capacidade de enviar e receber vários fluxos de dados por meio do ponto de extremidade.

A pilha de driver USB fornecida pela Microsoft no Windows dá suporte a vários fluxos. Isso permite que um driver cliente envie solicitações de E/S independentes para cada fluxo associado a um ponto de extremidade em massa em um dispositivo USB 3.0. As solicitações para fluxos diferentes não são serializadas.

Para um driver de cliente, os fluxos representam vários pontos de extremidade lógicos que têm o mesmo conjunto de características. Para enviar uma solicitação para um fluxo específico, o driver do cliente precisa de um identificador para esse fluxo (semelhante a um identificador de pipe para um ponto de extremidade). O URB para uma solicitação de E/S para um fluxo é semelhante a um URB para uma solicitação de E/S para um ponto de extremidade em massa. A única diferença é o identificador de pipe. Para enviar uma solicitação de E/S para um fluxo, o driver especifica o identificador de pipe para o fluxo.

Durante a configuração do dispositivo, o driver do cliente envia uma solicitação select-configuration e, opcionalmente, uma solicitação select-interface. Essas solicitações recuperam um conjunto de identificadores de pipe para os pontos de extremidade definidos na configuração ativa de uma interface. Para um ponto de extremidade que dá suporte a fluxos, o identificador de pipe do ponto de extremidade pode ser usado para enviar solicitações de E/S para o fluxo padrão (o primeiro fluxo) até que o driver tenha aberto fluxos (discutido em seguida).

Se o driver cliente quiser enviar solicitações para fluxos diferentes do fluxo padrão, o driver deverá abrir e obter identificadores para todos os fluxos. Para fazer isso, o driver cliente envia uma solicitação de fluxos abertos especificando o número de fluxos a serem abertos. Depois que o driver do cliente terminar de usar fluxos, o driver poderá, opcionalmente, fechá-los enviando uma solicitação de fluxos de fechamento.

O KMDF (Kernel Mode Driver Framework) não dá suporte a fluxos estáticos intrinsecamente. O driver cliente deve enviar URBs de estilo WDM (Modelo de Driver do Windows) que abrem e fecham fluxos. Este artigo descreve como formatar e enviar esses URBs. Um driver de cliente UMDF (User Mode Driver Framework) não pode usar a funcionalidade de fluxos estáticos.

O artigo contém algumas anotações rotuladas como drivers WDM. Essas anotações descrevem rotinas para um driver de cliente USB baseado em WDM que deseja enviar solicitações de fluxo.

Pré-requisitos

Antes que um driver cliente possa abrir ou fechar fluxos, o driver deve ter:

  • Chamado de método WdfUsbTargetDeviceCreateWithParameters .

    O método requer que a versão do contrato do cliente seja USBD_CLIENT_CONTRACT_VERSION_602. Ao especificar essa versão, o driver do cliente deve seguir um conjunto de regras. Para obter mais informações, consulte Práticas recomendadas: usando URBs.

    A chamada recupera um identificador WDFUSBDEVICE para o objeto de dispositivo de destino USB da estrutura. Esse identificador é necessário para fazer chamadas subsequentes para abrir fluxos. Normalmente, o driver cliente se registra na rotina de retorno de chamada de evento EVT_WDF_DEVICE_PREPARE_HARDWARE do driver.

    Drivers WDM: Chame o USBD_CreateHandle rotina e obtenha um identificador USBD para o registro do driver com a pilha do driver USB.

  • Configurou o dispositivo e obteve um identificador de pipe WDFUSBPIPE para o ponto de extremidade em massa que dá suporte a fluxos. Para obter o identificador de pipe, chame o método WdfUsbInterfaceGetConfiguredPipe na configuração alternativa atual de uma interface na configuração selecionada.

    Drivers WDM: Obtenha um identificador de pipe USBD enviando uma solicitação select-configuration ou select-interface. Para obter mais informações, consulte Como selecionar uma configuração para um dispositivo USB.

Como abrir fluxos estáticos

  1. Determine se a pilha de driver USB subjacente e o controlador de host dão suporte à funcionalidade de fluxos estáticos chamando o método WdfUsbTargetDeviceQueryUsbCapability . Normalmente, o driver cliente chama a rotina na rotina de retorno de chamada de evento EVT_WDF_DEVICE_PREPARE_HARDWARE do driver.

    Drivers WDM: Chame a rotina de USBD_QueryUsbCapability . Normalmente, o driver consulta os recursos que deseja usar na rotina do dispositivo inicial do driver (IRP_MN_START_DEVICE). Para obter um exemplo de código, consulte USBD_QueryUsbCapability.

    Forneça as seguintes informações:

    • Um identificador para o objeto de dispositivo USB que foi recuperado, em uma chamada anterior para WdfUsbTargetDeviceCreateWithParameters, para registro de driver de cliente.

      Drivers WDM: Passe o identificador USBD recuperado na chamada anterior para USBD_CreateHandle.

      Se o driver cliente quiser usar uma funcionalidade específica, o driver deverá primeiro consultar a pilha de driver USB subjacente para determinar se a pilha do driver e o controlador de host dão suporte à funcionalidade. Se houver suporte para a funcionalidade, somente então, o driver deverá enviar uma solicitação para usar a funcionalidade. Algumas solicitações exigem URBs, como o recurso de fluxos (discutido na etapa 5). Para essas solicitações, certifique-se de usar o mesmo identificador para consultar recursos e alocar URBs. Isso ocorre porque a pilha de driver usa identificadores para rastrear os recursos com suporte que um driver pode usar.

      Por exemplo, se você obteve um USBD_HANDLE (chamando USBD_CreateHandle), consulte a pilha de driver chamando USBD_QueryUsbCapability e aloque o URB chamando USBD_UrbAllocate. Passe o mesmo USBD_HANDLE em ambas as chamadas.

      Se você chamar métodos KMDF, WdfUsbTargetDeviceQueryUsbCapability e WdfUsbTargetDeviceCreateUrb, especifique o mesmo identificador WDFUSBDEVICE para o objeto de destino da estrutura nessas chamadas de método.

    • O GUID atribuído a GUID_USB_CAPABILITY_STATIC_STREAMS.

    • Um buffer de saída (ponteiro para USHORT). Após a conclusão, o buffer é preenchido com o número máximo de fluxos (por ponto de extremidade) com suporte do controlador host.

    • O comprimento, em bytes, do buffer de saída. Para fluxos, o comprimento é sizeof (USHORT).

  2. Avalie o valor NTSTATUS retornado. Se a rotina for concluída com êxito, retornará STATUS_SUCCESS, a funcionalidade de fluxos estáticos terá suporte. Caso contrário, o método retorna um código de erro apropriado.

  3. Determine o número de fluxos a serem abertos. O número máximo de fluxos que podem ser abertos é limitado por:

    • O número máximo de fluxos com suporte pelo controlador de host. Esse número é recebido por WdfUsbTargetDeviceQueryUsbCapability (para drivers WDM, USBD_QueryUsbCapability), no buffer de saída fornecido pelo chamador. A pilha de driver USB fornecida pela Microsoft dá suporte a até 255 fluxos. WdfUsbTargetDeviceQueryUsbCapability leva essa limitação em consideração ao calcular o número de fluxos. O método nunca retorna um valor maior que 255.
    • O número máximo de fluxos com suporte pelo ponto de extremidade no dispositivo. Para obter esse número, inspecione o descritor complementar do ponto de extremidade (consulte USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR em Usbspec.h). Para obter o descritor complementar do ponto de extremidade, você deve analisar o descritor de configuração. Para obter o descritor de configuração, o driver cliente deve chamar o método WdfUsbTargetDeviceRetrieveConfigDescriptor . Você deve usar as rotinas auxiliares, USBD_ParseConfigurationDescriptorEx e USBD_ParseDescriptor. Para obter o exemplo de código, consulte a função de exemplo chamada RetrieveStreamInfoFromEndpointDesc em Como enumerar pipes USB.

    Para determinar o número máximo de fluxos, escolha o menor de dois valores compatíveis com o controlador host e o ponto de extremidade.

  4. Aloque uma matriz de estruturas de USBD_STREAM_INFORMATION com n elementos, em que n é o número de fluxos a serem abertos. O driver do cliente é responsável por liberar essa matriz depois que o driver terminar de usar fluxos.

  5. Aloque um URB para a solicitação de fluxos abertos chamando o método WdfUsbTargetDeviceCreateUrb . Se a chamada for concluída com êxito, o método recuperará um objeto de memória WDF e o endereço da estrutura URB alocada pela pilha de driver USB.

    Drivers WDM: Chame a rotina de USBD_UrbAllocate .

  6. Formate o URB para a solicitação de fluxo aberto. O URB usa a estrutura _URB_OPEN_STATIC_STREAMS para definir a solicitação. Para formatar o URB, você precisa de:

    • O identificador de pipe USBD para o ponto de extremidade. Se você tiver um objeto de pipe WDF, poderá obter o identificador de pipe USBD chamando o método WdfUsbTargetPipeWdmGetPipeHandle .
    • A matriz de fluxo (criada na etapa 4)
    • Um ponteiro para a estrutura URB (criada na etapa 5).

    Para formatar o URB, chame UsbBuildOpenStaticStreamsRequest e passe as informações necessárias como valores de parâmetro. Verifique se o número de fluxos especificados para UsbBuildOpenStaticStreamsRequest não excede o número máximo de fluxos com suporte.

  7. Envie o URB como um objeto de solicitação WDF chamando o método WdfRequestSend . Para enviar a solicitação de forma síncrona, chame o método WdfUsbTargetDeviceSendUrbSynchronously .

    Drivers WDM: Associe o URB a um IRP e envie o IRP para a pilha do driver USB. Para obter mais informações, confira Como enviar um URB.

  8. Depois que a solicitação for concluída, marcar o status da solicitação.

    Se a pilha do driver USB falhar na solicitação, o status URB conterá o código de erro relevante. Algumas condições comuns de falha são descritas na seção Comentários.

Se o status da solicitação (IRP ou o objeto de solicitação do WDF) indicar USBD_STATUS_SUCCESS, a solicitação foi concluída com êxito. Inspecione a matriz de estruturas de USBD_STREAM_INFORMATION recebidas após a conclusão. A matriz é preenchida com informações sobre os fluxos solicitados. A pilha de driver USB preenche cada estrutura na matriz com informações de fluxo, como identificadores (recebidos como USBD_PIPE_HANDLE), identificadores de fluxo e o tamanho máximo de transferência de número. Os fluxos agora estão abertos para transferir dados.

Para uma solicitação de fluxos abertos, você precisará alocar um URB e uma matriz. O driver cliente deve liberar o URB chamando o método WdfObjectDelete no objeto de memória WDF associado, após a conclusão da solicitação de fluxos abertos. Se o driver enviou a solicitação de forma síncrona chamando WdfUsbTargetDeviceSendUrbSynchronously, ele deverá liberar o objeto de memória WDF, depois que o método retornar. Se o driver cliente enviou a solicitação de forma assíncrona chamando WdfRequestSend, o driver deverá liberar o objeto de memória WDF na rotina de conclusão implementada pelo driver associada à solicitação.

A matriz de fluxo pode ser liberada depois que o driver do cliente terminar de usar fluxos ou armazená-los para solicitações de E/S. No exemplo de código incluído neste artigo, o driver armazena a matriz de fluxos no contexto do dispositivo. O driver libera o contexto do dispositivo pouco antes de liberar o objeto do dispositivo ser removido.

Como transferir dados para um fluxo específico

Para enviar uma solicitação de transferência de dados para um fluxo específico, você precisará de um objeto de solicitação do WDF. Normalmente, o driver cliente não é necessário para alocar um objeto de solicitação WDF. Quando o Gerenciador de E/S recebe uma solicitação de um aplicativo, o Gerenciador de E/S cria um IRP para a solicitação. Esse IRP é interceptado pela estrutura. Em seguida, a estrutura aloca um objeto de solicitação WDF para representar o IRP. Depois disso, a estrutura passa o objeto de solicitação do WDF para o driver cliente. O driver cliente pode associar o objeto de solicitação ao URB de transferência de dados e enviá-lo para a pilha de driver USB.

Se o driver cliente não receber um objeto de solicitação WDF da estrutura e quiser enviar a solicitação de forma assíncrona, o driver deverá alocar um objeto de solicitação WDF chamando o método WdfRequestCreate . Formate o novo objeto chamando WdfUsbTargetPipeFormatRequestForUrb e envie a solicitação chamando WdfRequestSend.

Nos casos síncronos, passar um objeto de solicitação WDF é opcional.

Para transferir dados para fluxos, você deve usar URBs. O URB deve ser formatado chamando WdfUsbTargetPipeFormatRequestForUrb.

Os seguintes métodos WDF não têm suporte para fluxos:

O procedimento a seguir pressupõe que o driver cliente recebe o objeto de solicitação da estrutura.

  1. Aloque um URB chamando WdfUsbTargetDeviceCreateUrb. Esse método aloca um objeto de memória WDF que contém o URB recém-alocado. O driver cliente pode optar por alocar um URB para cada solicitação de E/S ou alocar um URB e reutilizá-lo para o mesmo tipo de solicitação.

  2. Formate o URB para uma transferência em massa chamando UsbBuildInterruptOrBulkTransferRequest. No parâmetro PipeHandle , especifique o identificador para o fluxo. Os identificadores de fluxo foram obtidos em uma solicitação anterior, descrita na seção Como abrir fluxos estáticos .

  3. Formate o objeto de solicitação do WDF chamando o método WdfUsbTargetPipeFormatRequestForUrb . Na chamada, especifique o objeto de memória WDF que contém a URB de transferência de dados. O objeto de memória foi alocado na etapa 1.

  4. Envie o URB como uma solicitação WDF chamando WdfRequestSend ou WdfUsbTargetPipeSendUrbSynchronously. Se você chamar WdfRequestSend, deverá especificar uma rotina de conclusão chamando WdfRequestSetCompletionRoutine para que o driver cliente possa ser notificado quando a operação assíncrona for concluída. Você deve liberar a URB de transferência de dados na rotina de conclusão.

Drivers WDM: Aloque um URB chamando USBD_UrbAllocate e formate-o para transferência em massa (consulte _URB_BULK_OR_INTERRUPT_TRANSFER). Para formatar o URB, você pode chamar UsbBuildInterruptOrBulkTransferRequest ou formatar a estrutura URB manualmente. Especifique o identificador para o fluxo no membro UrbBulkOrInterruptTransfer.PipeHandle da URB .

Como fechar fluxos estáticos

O driver cliente pode fechar fluxos depois que o driver terminar de usá-los. No entanto, a solicitação close-stream é opcional. A pilha de driver USB fecha todos os fluxos quando o ponto de extremidade associado aos fluxos é descompfigurado. Um ponto de extremidade é desativado quando uma configuração ou interface alternativa é selecionada, o dispositivo é removido e assim por diante. Um driver cliente deve fechar fluxos se o driver quiser abrir um número diferente de fluxos. Para enviar uma solicitação de fluxo de fechamento:

  1. Aloque uma estrutura URB chamando WdfUsbTargetDeviceCreateUrb.

  2. Formate o URB para a solicitação close-streams. O membro UrbPipeRequest da estrutura URB é uma estrutura _URB_PIPE_REQUEST . Preencha seus membros da seguinte maneira:

    • O membro Hdr do _URB_PIPE_REQUEST deve ser URB_FUNCTION_CLOSE_STATIC_STREAMS
    • O membro PipeHandle deve ser o identificador para o ponto de extremidade que contém os fluxos abertos em uso.
  3. Envie o URB como uma solicitação WDF chamando WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.

A solicitação close-handle fecha todos os fluxos que foram abertos anteriormente pelo driver cliente. O driver cliente não pode usar a solicitação para fechar fluxos específicos no ponto de extremidade.

Práticas recomendadas para enviar uma solicitação de fluxos estáticos

A pilha de driver USB executa validações no URB recebido. Para evitar erros de validação:

  • Não envie uma solicitação de fluxo aberto ou de fluxo de fechamento para um ponto de extremidade que não dá suporte a fluxos. Chame WdfUsbTargetDeviceQueryUsbCapability (para drivers WDM, USBD_QueryUsbCapability) para determinar o suporte a fluxos estáticos e enviar apenas solicitações de fluxos se o ponto de extremidade der suporte a eles.
  • Não solicite um número (de fluxos a serem abertos) que exceda o número máximo de fluxos com suporte ou envie uma solicitação sem especificar o número de fluxos. Determine o número de fluxos com base no número de fluxos compatíveis com a pilha de driver USB e o ponto de extremidade do dispositivo.
  • Não envie uma solicitação de fluxo aberto para um ponto de extremidade que já tenha fluxos abertos.
  • Não envie uma solicitação de fluxo de fechamento para um ponto de extremidade que não tenha fluxos abertos.
  • Depois que os fluxos estáticos estiverem abertos para um ponto de extremidade, não envie solicitações de E/S usando o identificador de pipe do ponto de extremidade obtido por meio de solicitações select-configuration ou select-interface. Isso é verdadeiro mesmo se os fluxos estáticos tiverem sido fechados.

Redefinir e anular operações de pipe

Às vezes, as transferências de ou para um ponto de extremidade podem falhar. Essas falhas podem resultar de uma condição de erro no ponto de extremidade ou no controlador de host, como uma condição de parada ou parada. Para limpar a condição de erro, o driver do cliente primeiro cancela transferências pendentes e, em seguida, redefine o pipe ao qual o ponto de extremidade está associado. Para cancelar transferências pendentes, o driver cliente pode enviar uma solicitação de pipe de anulação. Para redefinir um pipe, o driver cliente deve enviar uma solicitação de pipe de redefinição.

Para transferências de fluxo, não há suporte para solicitações de pipe de anulação e de pipe de redefinição para fluxos individuais associados ao ponto de extremidade em massa. Se uma transferência falhar em um pipe de fluxo específico, o controlador host interromperá as transferências em todos os outros pipes (para outros fluxos). Para se recuperar da condição de erro, o driver do cliente deve cancelar manualmente as transferências para cada fluxo. Em seguida, o driver cliente deve enviar uma solicitação de pipe de redefinição usando a alça de pipe para o ponto de extremidade em massa. Para essa solicitação, o driver cliente deve especificar o identificador de pipe para o ponto de extremidade em uma estrutura _URB_PIPE_REQUEST e definir a função URB (Hdr.Function) como URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Exemplo completo

O exemplo de código a seguir mostra como abrir fluxos.

NTSTATUS
    OpenStreams (
    _In_ WDFDEVICE Device,
    _In_ WDFUSBPIPE Pipe)
{
    NTSTATUS status;
    PDEVICE_CONTEXT deviceContext;
    PPIPE_CONTEXT pipeContext;
    USHORT cStreams = 0;
    USBD_PIPE_HANDLE usbdPipeHandle;
    WDFMEMORY urbMemory = NULL;
    PURB      urb = NULL;

    PAGED_CODE();

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

    if (deviceContext->MaxStreamsController == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported.");

        status = STATUS_NOT_SUPPORTED;
        goto Exit;
    }

    // If static streams are not supported, number of streams supported is zero.

    if (pipeContext->MaxStreamsSupported == 0)
    {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported by the endpoint.");

        goto Exit;
    }

    // Determine the number of streams to open.
    // Compare the number of streams supported by the endpoint with the
    // number of streams supported by the host controller, and choose the
    // lesser of the two values. The deviceContext->MaxStreams value was
    // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
    // that determined whether or not static streams is supported and
    // retrieved the maximum number of streams supported by the
    // host controller. The device context stores the values for IN and OUT
    // endpoints.

    // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
    // The number of elements in the array is the number of streams to open.
    // The code snippet stores the array in its device context.

    cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);

    // Allocate an array of streams associated with the IN bulk endpoint
    // This array is released in CloseStreams.

    pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
        NonPagedPool,
        sizeof (USBD_STREAM_INFORMATION) * cStreams,
        USBCLIENT_TAG);

    if (pipeContext->StreamInfo == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate stream information array.");

        goto Exit;
    }

    RtlZeroMemory (pipeContext->StreamInfo,
        sizeof (USBD_STREAM_INFORMATION) * cStreams);

    // Get USBD pipe handle from the WDF target pipe object. The client driver received the
    // endpoint pipe handles during device configuration.

    usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);

    // Allocate an URB for the open streams request.
    // WdfUsbTargetDeviceCreateUrb returns the address of the
    // newly allocated URB and the WDFMemory object that
    // contains the URB.

    status = WdfUsbTargetDeviceCreateUrb (
        deviceContext->UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (status != STATUS_SUCCESS)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

    // Format the URB for the open-streams request.
    // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
    // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.

    UsbBuildOpenStaticStreamsRequest (
        urb,
        usbdPipeHandle,
        (USHORT)cStreams,
        pipeContext->StreamInfo);

    // Send the request synchronously.
    // Upon completion, the USB driver stack populates the array of with handles to streams.

    status = WdfUsbTargetPipeSendUrbSynchronously (
        Pipe,
        NULL,
        NULL,
        urb);

    if (status != STATUS_SUCCESS)
    {
        goto Exit;
    }

Exit:
    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return status;
}