Compartilhamento de tecnologia

Fluxos estáticos de processamento de desenvolvimento de driver de dispositivo USB do Windows de transferências em lote

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Em dispositivos USB 2.0 e anteriores, um endpoint em massa pode enviar ou receber um único fluxo de dados por meio do endpoint. Em dispositivos USB 3.0, o endpoint em massa é capaz de enviar e receber vários fluxos de dados por meio do endpoint.

A pilha de drivers USB fornecida pela Microsoft no Windows oferece suporte a vários fluxos. Isso permite que os drivers clientes enviem solicitações de E/S independentes para cada fluxo associado a um endpoint em massa em um dispositivo USB 3.0, sem serializar solicitações para fluxos diferentes.

Para um driver cliente, um fluxo representa vários pontos de extremidade lógicos com o mesmo conjunto de características. Para enviar uma solicitação a um fluxo específico, o driver cliente precisa de um identificador para esse fluxo (semelhante ao identificador de pipe de um ponto de extremidade). O URB para solicitações de E/S de streaming é semelhante ao URB para solicitações de E/S para terminais em massa. A única diferença é a alça do tubo. Para enviar uma solicitação de E/S para um fluxo, o driver especifica um identificador de pipe no fluxo.

Durante a configuração do dispositivo, o driver cliente envia uma solicitação de seleção de configuração e, opcionalmente, uma solicitação de seleção de interface. Essas solicitações recuperam um conjunto de identificadores de pipe para os pontos de extremidade definidos nas configurações de atividade da interface. Para pontos de extremidade com reconhecimento de fluxo, 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 abra o fluxo.

Se um 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 fluxo aberto especificando o número de fluxos a serem abertos. Depois que um driver cliente terminar de usar fluxos, o driver poderá optar por fechá-los enviando uma solicitação de fechamento de fluxo.

Kernel Mode Driver Framework (KMDF) não oferece suporte nativo a fluxos estáticos. Os drivers cliente devem usar o Windows Driver Model (WDM) para abrir e fechar fluxos. Os drivers cliente UMDF (User-mode Driver Framework) não podem usar o recurso de streaming estático.

O texto a seguir pode conter alguns comentários rotulados como Drivers WDM. Estas instruções descrevem rotinas para um driver cliente USB baseado em WDM que deseja enviar solicitações de streaming.

pré-requisitos

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

1. Chame o método WdfUsbTargetDeviceCreateWithParameters. O método requer a versão do protocolo do cliente USBD_CLIENT_CONTRACT_VERSION_602. Ao especificar esta versão, o driver cliente deve aderir a um conjunto de regras.

Chame para recuperar o identificador WDFUSBDEVICE do objeto de dispositivo de destino USB da estrutura. O identificador é necessário para chamadas subsequentes ao fluxo aberto. Normalmente, um driver cliente se registra na rotina de retorno de chamada de evento EVT_WDF_DEVICE_PREPARE_HARDWARE do driver.

Driver WDM: chame a rotina USBD_CreateHandle e obtenha o identificador USBD registrado pelo driver na pilha de drivers USB.

2. Configurei o dispositivo e obtive o identificador de pipe WDFUSBPIPE para o endpoint em massa que suporta streaming. Para obter um identificador de pipe, chame o método WdfUsbInterfaceGetConfiguredPipe nas configurações alternativas atuais da interface na configuração selecionada.

Driver WDM: obtém o identificador do pipe USBD enviando uma solicitação de configuração de seleção ou interface de seleção.

Como abrir um fluxo estático

1. Determine se a pilha de driver USB subjacente e o controlador de host suportam o recurso de streaming estático chamando o método WdfUsbTargetDeviceQueryUsbCapability. Normalmente, os drivers cliente chamam rotinas na rotina de retorno de chamada de evento EVT_WDF_DEVICE_PREPARE_HARDWARE do driver.

Driver WDM: chama a rotina USBD_QueryUsbCapability. Normalmente, um driver consulta a função a ser usada na rotina do dispositivo de inicialização do driver, (IRP_MN_START_DEVICE).

Providencie a seguinte informação:

  • O identificador para o objeto de dispositivo USB recuperado em uma chamada anterior para WdfUsbTargetDeviceCreateWithParameters, usado para registrar o driver cliente.

Driver WDM: passe o identificador USBD recuperado na chamada anterior para USBD_CreateHandle.

Se um driver cliente quiser usar um recurso específico, o driver deverá primeiro consultar a pilha de driver USB subjacente para determinar se a pilha de driver e o controlador de host oferecem suporte ao recurso. Se o recurso for compatível, somente então o driver deverá enviar uma solicitação para usar o recurso. Certas solicitações exigem URBs, como a funcionalidade de streaming discutida na Etapa 5. Para essas solicitações, certifique-se de usar o mesmo identificador para consultar a função e alocar o URB. Isso ocorre porque a pilha do driver usa identificadores para controlar os recursos com suporte que o driver pode usar.

Por exemplo, se USBD_HANDLE foi obtido chamando USBD_CreateHandle, a pilha do driver será consultada chamando USBD_QueryUsbCapability e o URB será alocado chamando USBD_UrbAllocate. Passe o mesmo USBD_HANDLE em ambas as chamadas.

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

  • GUID atribuído a GUID_USB_CAPABILITY_STATIC_STREAMS;
  • Ponteiro para o buffer de saída (apontando para USHORT). Depois de concluído, o buffer será preenchido com (número máximo de fluxos para) cada endpoint suportado pelo controlador host;
  • O comprimento do buffer de saída, em bytes. Para fluxos, o comprimento é sizeof (USHORT);

2. Avalie o valor NTSTATUS retornado. Se a rotina for concluída com êxito, STATUS_SUCCESS será retornado e a funcionalidade de streaming estático será suportada. Caso contrário, o método retornará o 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 suportados pelo controlador host. WdfUsbTargetDeviceQueryUsbCapability (recebe WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) no buffer de saída fornecido pelo chamador. A pilha de drivers USB fornecida pela Microsoft oferece suporte a até 255 fluxos. WdfUsbTargetDeviceQueryUsbCapability leva esse limite 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 streams suportados pelo endpoint no dispositivo. Para obter esse número, verifique o descritor complementar do endpoint (em Usbspec.h) para USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. Para obter o descritor complementar do terminal, o descritor de configuração deve ser analisado. Para obter o descritor de configuração, o driver cliente deve chamar o método WdfUsbTargetDeviceRetrieveConfigDescriptor. As rotinas auxiliares, USBD_ParseConfigurationDescriptorEx e USBD_ParseDescriptor, devem ser usadas.

Para determinar o número máximo de fluxos, escolha o menor dos dois valores suportados pelo controlador host e pelo endpoint.

4. Aloque uma matriz de estruturas USBD_STREAM_INFORMATION de n elementos, onde n é o número de fluxos a serem abertos. O driver cliente é responsável por liberar esse array após o driver terminar de usar o stream.

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

Driver WDM: chama a rotina USBD_UrbAllocate.

6. Defina o formato URB da solicitação de fluxo aberto. URB usa a estrutura _URB_OPEN_STATIC_STREAMS para definir solicitações. Para formatar um URB, você precisa de:

  • Identificador de pipe USBD apontando para o ponto final. Se houver um objeto de pipe WDF, você poderá obter o identificador de pipe USBD chamando o método WdfUsbTargetPipeWdmGetPipeHandle.
  • Criado na etapa 4 (stream array)
  • Ponteiro para a estrutura URB criada na (etapa 5).

Para formatar um URB, chame UsbBuildOpenStaticStreamsRequest e passe as informações necessárias como valores de parâmetro. Certifique-se de que o número de fluxos especificado para UsbBuildOpenStaticStreamsRequest não exceda o número máximo de fluxos suportados.

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.

Driver WDM: associa o URB ao IRP e envia o IRP à pilha de drivers USB.

8. Após a conclusão da solicitação, verifique o status da solicitação.Se a solicitação da pilha do driver USB falhar, o status do URB conterá o código de erro relevante.

Se o status da solicitação (objeto de solicitação IRP ou WDF) indicar USBD_STATUS_SUCCESS, a solicitação foi concluída com êxito. Matriz de estruturas USBD_STREAM_INFORMATION recebidas quando a verificação é concluída. A matriz é preenchida com informações sobre o fluxo solicitado. A pilha do driver USB preenche cada estrutura na matriz com informações de fluxo, como o identificador recebido USBD_PIPE_HANDLE, o identificador de fluxo e o tamanho máximo de transferência numérica. O stream agora pode transferir dados.

Para solicitações de fluxo aberto, URBs e arrays precisam ser alocados. Depois que a solicitação de fluxo aberto for concluída, o driver cliente deverá liberar o URB chamando o método WdfObjectDelete no objeto de memória WDF associado. Se o driver enviar a solicitação de forma síncrona chamando WdfUsbTargetDeviceSendUrbSynchronously, o objeto de memória WDF deverá ser liberado após o retorno do método. Se um driver cliente enviar uma 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 após o driver cliente terminar de usar o fluxo ou pode ser armazenada para solicitações de E/S. No exemplo de código incluído abaixo, o driver armazena uma matriz de fluxo no contexto do dispositivo. O driver libera o contexto do dispositivo antes de liberar o objeto do dispositivo.

Como transferir dados para um fluxo específico
Para enviar uma solicitação de transferência de dados para um fluxo específico, é necessário um objeto de solicitação WDF. Normalmente, os drivers cliente não precisam alocar objetos 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. O IRP foi interceptado pelo framework. A estrutura então aloca um objeto de solicitação WDF para representar o IRP. Posteriormente, a estrutura passa o objeto de solicitação WDF para o driver cliente. O driver cliente pode então associar o objeto de solicitação ao URB de transferência de dados e enviá-lo para a pilha do driver USB.

Se um 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.

No caso síncrono, passar o objeto de solicitação WDF é opcional.

Para transferir dados para um fluxo, o URB deve ser usado. O URB deve ser formatado chamando WdfUsbTargetPipeFormatRequestForUrb.

Os streams não oferecem suporte aos seguintes métodos WDF:

  • WdfUsbTargetPipeFormatRequestForRead
  • WdfUsbTargetPipeFormatRequestForWrite
  • WdfUsbTargetPipeReadSincronamente
  • WdfUsbTargetPipeWriteSincronamente

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

  1. Aloque o URB chamando WdfUsbTargetDeviceCreateUrb. Este método aloca um objeto de memória WDF contendo o URB recém-alocado. Os drivers clientes podem optar por alocar um URB para cada solicitação de E/S ou alocar um URB e usá-lo para solicitações do mesmo tipo.
  2. Formate o URB para transferência em massa chamando UsbBuildInterruptOrBulkTransferRequest. No parâmetro PipeHandle, especifique o identificador do fluxo. O identificador do stream é obtido na solicitação anterior, conforme descrito na seção Como abrir um stream estático.
  3. Formate o objeto de solicitação WDF chamando o método WdfUsbTargetPipeFormatRequestForUrb. Na chamada, especifique o objeto de memória WDF que contém o 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. O URB de transferência de dados deverá ser liberado na rotina de finalização.

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

Como fechar um fluxo estático

O driver cliente pode fechar o fluxo depois que o driver terminar de usá-lo. No entanto, fechar a solicitação de streaming é opcional. A pilha do driver USB fecha todos os fluxos quando o terminal associado ao fluxo não está configurado. Os endpoints são desconfigurados ao selecionar uma configuração ou interface alternativa, excluir um dispositivo, etc. Se o driver cliente quiser abrir um número diferente de fluxos, ele deverá fechar os fluxos. Envie uma solicitação de fechamento de stream:

1. Aloque a estrutura URB chamando WdfUsbTargetDeviceCreateUrb.

2. Defina o formato URB para fechar a solicitação de stream. O membro UrbPipeRequest da estrutura URB é a estrutura _URB_PIPE_REQUEST. Preencha seus membros da seguinte forma:

  • Membro Hdr obrigatório de URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST
  • O membro PipeHandle deve ser um identificador que contém o terminal que está sendo usado para abrir o fluxo.

3. Envie o URB como uma solicitação WDF chamando WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.

O identificador Close solicita o fechamento de todos os fluxos abertos anteriormente pelo driver cliente. O driver cliente não pode usar solicitações para fechar um fluxo específico no terminal.

Melhores práticas para enviar solicitações de streaming estático

A pilha de drivers USB realiza a verificação no URB recebido. Para evitar erros de validação, faça o seguinte:

  • Não envie solicitações de fluxo aberto ou fechado para terminais que não suportam fluxos. Chame WdfUsbTargetDeviceQueryUsbCapability (, USBD_QueryUsbCapability) do driver WDM para determinar o suporte de streaming estático e enviar solicitações de streaming somente se o ponto de extremidade for compatível.
  • Não solicite mais fluxos (abertos) do que o número máximo de fluxos suportados ou envie uma solicitação sem especificar o número de fluxos. O número de fluxos é determinado com base no número de fluxos suportados pela pilha do driver USB e pelo terminal do dispositivo.
  • Não envie uma solicitação de stream aberto para um endpoint que já tenha um stream aberto.
  • Não envie uma solicitação de fechamento de fluxo para um terminal que não tenha um fluxo aberto.
  • Depois de abrir um fluxo estático para um terminal, não envie solicitações de E/S usando o identificador de canal do terminal obtido por meio de uma configuração selecionada ou de uma solicitação de interface selecionada. Isso é verdade mesmo se o fluxo estático estiver fechado.
Redefinir e abortar operações de pipeline

Ocasionalmente, as transferências de ou para um terminal podem falhar. Essas falhas podem ser causadas por condições de erro no terminal ou no controlador host, como uma condição de parada ou parada. Para limpar a condição de erro, o driver cliente primeiro cancela a transferência pendente e, em seguida, redefine o canal associado ao terminal. Para cancelar uma transferência pendente, o driver cliente pode enviar uma solicitação de cancelamento de pipe. Para redefinir um pipe, o driver cliente deve enviar uma solicitação de redefinição de pipe.

Para streaming, as solicitações de abort-pipe e reset-pipe não são suportadas para streams individuais associados ao endpoint em massa. Se uma transferência em um canal de fluxo específico falhar, o controlador host interromperá as transferências em todos os outros canais para outros fluxos. Para se recuperar da condição de erro, o driver cliente deverá cancelar manualmente a transferência para cada fluxo. um identificador de pipe para enviar uma solicitação de redefinição de pipe para o terminal em massa. Para essa solicitação, o driver cliente deve especificar o identificador de pipe do terminal na 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 demonstra como abrir um fluxo.

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }