Como enviar solicitações de transferência em massa USB
Este tópico fornece uma breve visão geral sobre transferências em massa USB. Ele também fornece instruções passo a passo sobre como um driver cliente pode enviar e receber dados em massa do dispositivo.
Sobre pontos de extremidade em massa
Um ponto de extremidade em massa USB pode transferir grandes quantidades de dados. As transferências em massa são confiáveis que permitem a detecção de erros de hardware e envolvem um número limitado de repetições no hardware. Para transferências para pontos de extremidade em massa, a largura de banda não é reservada no barramento. Quando há várias solicitações de transferência direcionadas a diferentes tipos de pontos de extremidade, o controlador primeiro agenda transferências para dados críticos de tempo, como pacotes isócronos e de interrupção. Somente se houver largura de banda não utilizado disponível no barramento, o controlador agenda as transferências em massa. Quando não há outro tráfego significativo no ônibus, a transferência em massa pode ser rápida. No entanto, quando o ônibus está ocupado com outras transferências, os dados em massa podem esperar indefinidamente.
Aqui estão os principais recursos de um ponto de extremidade em massa:
- Os pontos de extremidade em massa são opcionais. Eles têm suporte em um dispositivo USB que deseja transferir grandes quantidades de dados. Por exemplo, transferir arquivos para uma unidade flash, dados de ou para uma impressora ou um scanner.
- Dispositivos USB de velocidade total, alta velocidade e SuperSpeed dão suporte a pontos de extremidade em massa. Dispositivos de baixa velocidade não dão suporte a pontos de extremidade em massa.
- O ponto de extremidade é unidirecional e os dados podem ser transferidos em uma direção IN ou OUT. O ponto de extremidade BULK IN é usado para ler dados do dispositivo para o host e o ponto de extremidade bulk OUT é usado para enviar dados do host para o dispositivo.
- O ponto de extremidade tem bits crc para marcar para erros e, portanto, fornece integridade de dados. Para erros de CRC, os dados são retransmitidos automaticamente.
- Um ponto de extremidade em massa SuperSpeed pode dar suporte a fluxos. Os fluxos permitem que o host envie transferências para pipes de fluxo individuais.
- O tamanho máximo do pacote de um ponto de extremidade em massa depende da velocidade do barramento do dispositivo. Para velocidade total, alta velocidade e SuperSpeed; os tamanhos máximos de pacote são 64, 512 e 1024 bytes, respectivamente.
Transações em massa
Como todas as outras transferências USB, o host sempre inicia uma transferência em massa. A comunicação ocorre entre o host e o ponto de extremidade de destino. O protocolo USB não impõe nenhum formato nos dados enviados em uma transação em massa.
A forma como o host e o dispositivo se comunicam no barramento depende da velocidade em que o dispositivo está conectado. Esta seção descreve alguns exemplos de transferências em massa de alta velocidade e SuperSpeed que mostram a comunicação entre o host e o dispositivo.
Você pode ver a estrutura de transações e pacotes usando qualquer analisador USB, como Beagle, Ellisys, analisadores de protocolo USB LeCroy. Um dispositivo analisador mostra como os dados são enviados ou recebidos de um dispositivo USB pela transmissão. Neste exemplo, vamos examinar alguns rastreamentos capturados por um analisador USB LeCroy. Este exemplo serve apenas para informações. Isso não é um endosso da Microsoft.
Exemplo de transação bulk OUT
Esse rastreamento do analisador mostra um exemplo de transação bulk OUT em alta velocidade.
No rastreamento anterior, o host inicia uma transferência OUT em massa para um ponto de extremidade em massa de alta velocidade, enviando um pacote de token com PID definido como OUT (token OUT). O pacote contém o endereço do dispositivo e do ponto de extremidade de destino. Após o pacote OUT, o host envia um pacote de dados que contém o conteúdo em massa. Se o ponto de extremidade aceitar os dados de entrada, ele enviará um pacote ACK. Neste exemplo, podemos ver que o host enviou 31 bytes para o endereço do dispositivo:1; endereço do ponto de extremidade: 2.
Se o ponto de extremidade estiver ocupado no momento em que o pacote de dados chegar e não puder receber dados, o dispositivo poderá enviar um pacote NAK. Nesse caso, o host começa a enviar pacotes PING para o dispositivo. O dispositivo responde com pacotes NAK, desde que o dispositivo não esteja pronto para receber dados. Quando o dispositivo estiver pronto, ele responderá com um pacote ACK. Em seguida, o host pode retomar a transferência OUT.
Este rastreamento do analisador mostra um exemplo de transação OUT em massa superSpeed.
No rastreamento anterior, o host inicia uma transação OUT para um ponto de extremidade em massa SuperSpeed enviando um pacote de dados. O pacote de dados contém o conteúdo em massa, o dispositivo e os endereços do ponto de extremidade. Neste exemplo, podemos ver que o host enviou 31 bytes para o endereço do dispositivo:4; endereço do ponto de extremidade: 2.
O dispositivo recebe e reconhece o pacote de dados e envia um pacote ACK de volta para o host. Se o ponto de extremidade estiver ocupado no momento em que o pacote de dados chegar e não puder receber dados, o dispositivo poderá enviar um pacote NRDY. Ao contrário da alta velocidade, depois de receber o pacote NRDY, o host não sonda repetidamente o dispositivo. Em vez disso, o host aguarda um ERDY do dispositivo. Quando o dispositivo estiver pronto, ele enviará um pacote ERDY e o host poderá enviar dados para o ponto de extremidade.
Exemplo de transação BULK IN
Este rastreamento do analisador mostra um exemplo de transação EM massa em alta velocidade.
No rastreamento anterior, o host inicia a transação enviando um pacote de token com PID definido como IN (token IN). Em seguida, o dispositivo envia um pacote de dados com conteúdo em massa. Se o ponto de extremidade não tiver dados a serem enviados ou ainda não estiver pronto para enviar dados, o dispositivo poderá enviar um pacote de handshake NAK. O host tenta novamente a transferência IN até receber um pacote ACK do dispositivo. Esse pacote ACK implica que o dispositivo aceitou os dados.
Este rastreamento do analisador mostra um exemplo de transação EM massa SuperSpeed.
Para iniciar uma transferência IN em massa de um ponto de extremidade SuperSpeed, o host inicia uma transação em massa enviando um pacote ACK. A especificação USB versão 3.0 otimiza essa parte inicial da transferência mesclando pacotes ACK e IN em um pacote ACK. Em vez de um token IN, para SuperSpeed, o host envia um token ACK para iniciar uma transferência em massa. O dispositivo responde com um pacote de dados. Em seguida, o host reconhece o pacote de dados enviando um pacote ACK. Se o ponto de extremidade estiver ocupado e não puder enviar dados, o dispositivo poderá enviar status de NRDY. Nesse caso, o host aguarda até obter um pacote ERDY do dispositivo.
Tarefas de driver de cliente USB para uma transferência em massa
Um aplicativo ou um driver no host sempre inicia uma transferência em massa para enviar ou receber dados. O driver cliente envia a solicitação para a pilha de driver USB. O driver USB empilha a solicitação para o controlador de host e envia os pacotes de protocolo (conforme descrito na seção anterior) sobre a transmissão para o dispositivo.
Vamos ver como o driver do cliente envia a solicitação de uma transferência em massa como resultado da solicitação de um aplicativo ou de outro driver. Como alternativa, o driver pode iniciar a transferência por conta própria. Independentemente da abordagem, um driver deve ter o buffer de transferência e a solicitação para iniciar a transferência em massa.
Para um driver KMDF, a solicitação é descrita em um objeto de solicitação de estrutura (consulte Referência de objeto de solicitação WDF). O driver cliente chama métodos do objeto de solicitação especificando o identificador WDFREQUEST para enviar a solicitação para a pilha de driver USB. Se o driver cliente estiver enviando uma transferência em massa em resposta a uma solicitação de um aplicativo ou de outro driver, a estrutura criará um objeto de solicitação e entregará a solicitação ao driver cliente usando um objeto de fila de estrutura. Nesse caso, o driver do cliente pode usar essa solicitação para fins de envio da transferência em massa. Se o driver cliente iniciou a solicitação, o driver poderá optar por alocar seu próprio objeto de solicitação.
Se o aplicativo ou outro driver enviar ou solicitar dados, o buffer de transferência será passado para o driver pela estrutura. Como alternativa, o driver cliente pode alocar o buffer de transferência e criar o objeto de solicitação se o driver iniciar a transferência por conta própria.
Estas são as tarefas main para o driver cliente:
- Obtenha o buffer de transferência.
- Obter, formatar e enviar um objeto de solicitação de estrutura para a pilha de driver USB.
- Implemente uma rotina de conclusão para ser notificado quando a pilha do driver USB concluir a solicitação.
Este tópico descreve essas tarefas usando um exemplo no qual o driver inicia uma transferência em massa como resultado da solicitação de um aplicativo para enviar ou receber dados.
Para ler dados do dispositivo, o driver cliente pode usar o objeto de leitor contínuo fornecido pela estrutura. Para obter mais informações, consulte Como usar o leitor contínuo para ler dados de um pipe USB.
Exemplo de solicitação de transferência em massa
Considere um cenário de exemplo, em que um aplicativo deseja ler ou gravar dados em seu dispositivo. O aplicativo chama APIs do Windows para enviar essas solicitações. Neste exemplo, o aplicativo abre um identificador para o dispositivo usando o GUID da interface do dispositivo publicado pelo driver no modo kernel. Em seguida, o aplicativo chama ReadFile ou WriteFile para iniciar uma solicitação de leitura ou gravação. Nessa chamada, o aplicativo também especifica um buffer que contém os dados a serem lidos ou gravados e o comprimento desse buffer.
O Gerenciador de E/S recebe a solicitação, cria um IRP (Pacote de Solicitação de E/S) e o encaminha para o driver cliente.
A estrutura intercepta a solicitação, cria um objeto de solicitação de estrutura e o adiciona ao objeto de fila da estrutura. Em seguida, a estrutura notifica o driver cliente de que uma nova solicitação está aguardando para ser processada. Essa notificação é feita invocando as rotinas de retorno de chamada da fila do driver para EvtIoRead ou EvtIoWrite.
Quando a estrutura entrega a solicitação ao driver cliente, ela recebe estes parâmetros:
- Identificador WDFQUEUE para o objeto de fila de estrutura que contém a solicitação.
- Identificador WDFREQUEST para o objeto de solicitação de estrutura que contém detalhes sobre essa solicitação.
- O comprimento da transferência, ou seja, o número de bytes a serem lidos ou gravados.
Na implementação do driver cliente de EvtIoRead ou EvtIoWrite, o driver inspeciona os parâmetros de solicitação e, opcionalmente, pode executar verificações de validação.
Se você estiver usando fluxos de um ponto de extremidade em massa SuperSpeed, enviará a solicitação em um URB porque o KMDF não dá suporte a fluxos intrinsecamente. Para obter informações sobre como enviar uma solicitação de transferência para fluxos de um ponto de extremidade em massa, consulte Como abrir e fechar fluxos estáticos em um ponto de extremidade em massa USB.
Se você não estiver usando fluxos, poderá usar métodos definidos por KMDF para enviar a solicitação, conforme descrito no seguinte procedimento:
Pré-requisitos
Antes de começar, verifique se você tem estas informações:
O driver cliente deve ter criado o objeto de dispositivo de destino USB da estrutura e obtido o identificador WDFUSBDEVICE chamando o método WdfUsbTargetDeviceCreateWithParameters .
Se você estiver usando os modelos USB fornecidos com Microsoft Visual Studio Professional 2012, o código do modelo executará essas tarefas. O código de modelo obtém o identificador para o objeto de dispositivo de destino e armazena no contexto do dispositivo. Para obter mais informações, confira "Código-fonte do dispositivo" em Noções básicas sobre a estrutura de código do driver de cliente USB (KMDF).
Identificador WDFREQUEST para o objeto de solicitação de estrutura que contém detalhes sobre essa solicitação.
O número de bytes a serem lidos ou gravados.
O identificador WDFUSBPIPE para o objeto de pipe da estrutura associado ao ponto de extremidade de destino. Você deve ter obtido identificadores de pipe durante a configuração do dispositivo enumerando pipes. Para obter mais informações, consulte Como enumerar pipes USB.
Se o ponto de extremidade em massa der suporte a fluxos, você deverá ter o identificador de pipe para o fluxo. Para obter mais informações, consulte Como abrir e fechar fluxos estáticos em um ponto de extremidade em massa USB.
Etapa 1: Obter o buffer de transferência
O buffer de transferência ou o MDL do buffer de transferência contém os dados a serem enviados ou recebidos. Este tópico pressupõe que você esteja enviando ou recebendo dados em um buffer de transferência. O buffer de transferência é descrito em um objeto de memória WDF (consulte Referência de objeto de memória WDF). Para obter o objeto de memória associado ao buffer de transferência, chame um destes métodos:
- Para uma solicitação de transferência IN em massa, chame o método WdfRequestRetrieveOutputMemory .
- Para uma solicitação de transferência OUT em massa, chame o método WdfRequestRetrieveInputMemory .
O driver cliente não precisa liberar essa memória. A memória é associada ao objeto de solicitação pai e é liberada quando o pai é liberado.
Etapa 2: formatar e enviar um objeto de solicitação de estrutura para a pilha de driver USB
Você pode enviar a solicitação de transferência de forma assíncrona ou síncrona.
Estes são os métodos assíncronos:
Os métodos nesta lista formatam a solicitação. Se você enviar a solicitação de forma assíncrona, defina um ponteiro para a rotina de conclusão implementada pelo driver chamando o método WdfRequestSetCompletionRoutine (descrito na próxima etapa). Para enviar a solicitação, chame o método WdfRequestSend .
Se você enviar a solicitação de forma síncrona, chame estes métodos:
Para obter exemplos de código, consulte a seção Exemplos dos tópicos de referência para esses métodos.
Etapa 3: Implementar uma rotina de conclusão para a solicitação
Se a solicitação for enviada de forma assíncrona, você deverá implementar uma rotina de conclusão para ser notificado quando a pilha de driver USB concluir a solicitação. Após a conclusão, a estrutura invoca a rotina de conclusão do driver. A estrutura passa estes parâmetros:
- Identificador WDFREQUEST para o objeto de solicitação.
- Identificador WDFIOTARGET para o objeto de destino de E/S para a solicitação.
- Um ponteiro para uma estrutura WDF_REQUEST_COMPLETION_PARAMS que contém informações de conclusão. As informações específicas de USB estão contidas no membro CompletionParams-Parameters.Usb>.
- O identificador WDFCONTEXT para o contexto especificado pelo driver em sua chamada para WdfRequestSetCompletionRoutine.
Na rotina de conclusão, execute estas tarefas:
Verifique a status da solicitação obtendo o valor CompletionParams-IoStatus.Status>.
Verifique o status USBD definido pela pilha do driver USB.
No caso de erros de pipe, execute operações de recuperação de erro. Para obter mais informações, consulte Como se recuperar de erros de pipe USB.
Verifique o número de bytes transferidos.
Uma transferência em massa é concluída quando o número solicitado de bytes é transferido de ou para o dispositivo. Se você enviar o buffer de solicitação chamando o método KMDF, marcar o valor recebido nos membros CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> ou CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>>.
Em uma transferência simples em que a pilha de driver USB envia todos os bytes solicitados em um pacote de dados, você pode marcar comparar o valor Length com o número de bytes solicitados. Se a pilha de driver USB transferir a solicitação em vários pacotes de dados, você deverá acompanhar o número de bytes transferidos e o número restante de bytes.
Se o número total de bytes tiver sido transferido, conclua a solicitação. Se ocorreu uma condição de erro, conclua a solicitação com o código de erro retornado. Conclua a solicitação chamando o método WdfRequestComplete . Se você quiser definir informações, como o número de bytes transferidos, chame WdfRequestCompleteWithInformation.
Verifique se, ao concluir a solicitação com informações, o número de bytes deve ser igual ou menor que o número de bytes solicitados. A estrutura valida esses valores. Se o comprimento definido na solicitação concluída for maior que o comprimento da solicitação original, poderá ocorrer uma verificação de bugs.
Este código de exemplo mostra como o driver cliente pode enviar uma solicitação de transferência em massa. O driver define uma rotina de conclusão. Essa rotina é mostrada no próximo bloco de código.
/*++
Routine Description:
This routine sends a bulk write request to the
USB driver stack. The request is sent asynchronously and
the driver gets notified through a completion routine.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Return Value:
VOID
--*/
VOID Fx3EvtIoWrite(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t Length
)
{
NTSTATUS status;
WDFUSBPIPE pipe;
WDFMEMORY reqMemory;
PDEVICE_CONTEXT pDeviceContext;
pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));
pipe = pDeviceContext->BulkWritePipe;
status = WdfRequestRetrieveInputMemory(
Request,
&reqMemory
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
status = WdfUsbTargetPipeFormatRequestForWrite(
pipe,
Request,
reqMemory,
NULL
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
WdfRequestSetCompletionRoutine(
Request,
BulkWriteComplete,
pipe
);
if (WdfRequestSend( Request,
WdfUsbTargetPipeGetIoTarget(pipe),
WDF_NO_SEND_OPTIONS) == FALSE)
{
status = WdfRequestGetStatus(Request);
goto Exit;
}
Exit:
if (!NT_SUCCESS(status)) {
WdfRequestCompleteWithInformation(
Request,
status,
0
);
}
return;
}
Este código de exemplo mostra a implementação de rotina de conclusão para uma transferência em massa. O driver cliente conclui a solicitação na rotina de conclusão e define essas informações de solicitação: status e o número de bytes transferidos.
/*++
Routine Description:
This completion routine is invoked by the framework when
the USB drive stack completes the previously sent
bulk write request. The client driver completes the
the request if the total number of bytes were transferred
to the device.
In case of failure it queues a work item to start the
error recovery by resetting the target pipe.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Pipe - Handle to the pipe that is the target for this request.
Return Value:
VOID
--*/
VOID BulkWriteComplete(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
_In_ WDFCONTEXT Context
)
{
PDEVICE_CONTEXT deviceContext;
size_t bytesTransferred=0;
NTSTATUS status;
UNREFERENCED_PARAMETER (Target);
UNREFERENCED_PARAMETER (Context);
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"In completion routine for Bulk transfer.\n"));
// Get the device context. This is the context structure that
// the client driver provided when it sent the request.
deviceContext = (PDEVICE_CONTEXT)Context;
// Get the status of the request
status = CompletionParams->IoStatus.Status;
if (!NT_SUCCESS (status))
{
// Get the USBD status code for more information about the error condition.
status = CompletionParams->Parameters.Usb.Completion->UsbdStatus;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer failed. 0x%x\n",
status));
// Queue a work item to start the reset-operation on the pipe
// Not shown.
goto Exit;
}
// Get the actual number of bytes transferred.
bytesTransferred =
CompletionParams->Parameters.Usb.Completion->Parameters.PipeWrite.Length;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer completed. Transferred %d bytes. \n",
bytesTransferred));
Exit:
// Complete the request and update the request with
// information about the status code and number of bytes transferred.
WdfRequestCompleteWithInformation(Request, status, bytesTransferred);
return;
}