Escrever um driver de cliente UDE
Este artigo descreve o comportamento da extensão da classe de emulação de dispositivo USB (UDE) e tarefas que um driver de cliente deve executar para um controlador de host emulado e os dispositivos conectados a ele. Ele fornece informações sobre como o driver de classe e a extensão de classe se comunicam com cada um por meio de um conjunto de rotinas e funções de retorno de chamada. Ele também descreve os recursos que o driver de cliente deve implementar.
Resumo
- Objetos e identificadores UDE usados pela extensão de classe e pelo driver de cliente.
- Criar um controlador de host emulado com recursos para consultar os recursos do controlador e redefinir o controlador.
- Criar um dispositivo USB virtual, configurando-o para gerenciamento de energia e transferências de dados por meio de pontos de extremidade.
APIs importantes
Antes de começar
- Instale o WDK (Kit de Driver do Windows) mais recente no computador de desenvolvimento. O kit tem as bibliotecas e os arquivos de cabeçalho necessários para escrever um driver de cliente UDE. Especificamente, você precisará do seguinte:
- A biblioteca de stub, (Udecxstub.lib). A biblioteca converte as chamadas feitas pelo driver do cliente e as transmite para a UdeCx.
- O arquivo de cabeçalho, Udecx.h.
- Instale o Windows 10 no computador de destino.
- Familiarize-se com o UDE. Consulte Arquitetura: emulação de dispositivo USB (UDE).
- Familiarize-se com o Windows Driver Foundation (WDF). Leitura recomendada: Desenvolver drivers com o Windows Driver Foundation, escrito por Penny Orwick e Guy Smith.
Objetos e identificadores UDE
A extensão de classe UDE e o driver de cliente usam objetos WDF específicos que representam o controlador de host emulado e o dispositivo virtual, incluindo seus pontos de extremidade e URBs usados para transferir dados entre o dispositivo e o host. O driver de cliente solicita a criação dos objetos, e o tempo de vida do objeto é gerenciado pela extensão de classe.
Objeto do controlador de host emulado (WDFDEVICE)
Representa o controlador de host emulado e é o identificador principal entre a extensão de classe UDE e o driver de cliente.
Objeto de dispositivo UDE (UDECXUSBDEVICE)
Representa um dispositivo USB virtual que está conectado a uma porta no controlador de host emulado.
Objeto de ponto de extremidade UDE (UDECXUSBENDPOINT)
Representam pipes de dados sequenciais de dispositivos USB. Usado para receber solicitações de software para enviar ou receber dados em um ponto de extremidade.
Inicializar o controlador de host emulado
Confira a seguir o resumo da sequência na qual o driver de cliente recupera um identificador WDFDEVICE para o controlador de host emulado. Recomendamos que o driver execute essas tarefas em sua função de retorno de chamada EvtDriverDeviceAdd.
Chame UdecxInitializeWdfDeviceInit passando a referência para WDFDEVICE_INIT passada pela estrutura.
Inicialize o WDFDEVICE_INIT com informações de configuração para que este dispositivo pareça semelhante a outros controladores de host USB. Por exemplo, atribua um nome FDO e um link simbólico, registre uma interface de dispositivo com o GUID de GUID_DEVINTERFACE_USB_HOST_CONTROLLER fornecido pela Microsoft como o GUID de interface do dispositivo para que os aplicativos possam abrir um identificador para o dispositivo.
Chame WdfDeviceCreate para criar o objeto de dispositivo da estrutura.
Chame UdecxWdfDeviceAddUsbDeviceEmulation e registre as funções de retorno de chamada do driver do cliente.
Confira a seguir as funções de retorno de chamada associadas ao objeto do controlador de host, que são invocadas pela extensão de classe UDE. Essas funções devem ser implementadas pelo driver do cliente.
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Determina os recursos compatíveis com o controlador de host que o driver de cliente deve informar à extensão de classe.
-
Opcional. Redefine o controlador do host e/ou os dispositivos conectados.
EVT_WDF_DRIVER_DEVICE_ADD Controller_WdfEvtDeviceAdd; #define BASE_DEVICE_NAME L"\\Device\\USBFDO-" #define BASE_SYMBOLIC_LINK_NAME L"\\DosDevices\\HCD" #define DeviceNameSize sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE #define SymLinkNameSize sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE NTSTATUS Controller_WdfEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT WdfDeviceInit ) { NTSTATUS status; WDFDEVICE wdfDevice; WDF_PNPPOWER_EVENT_CALLBACKS wdfPnpPowerCallbacks; WDF_OBJECT_ATTRIBUTES wdfDeviceAttributes; WDF_OBJECT_ATTRIBUTES wdfRequestAttributes; UDECX_WDF_DEVICE_CONFIG controllerConfig; WDF_FILEOBJECT_CONFIG fileConfig; PWDFDEVICE_CONTEXT pControllerContext; WDF_IO_QUEUE_CONFIG defaultQueueConfig; WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; UNICODE_STRING refString; ULONG instanceNumber; BOOLEAN isCreated; DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize); DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize); UNREFERENCED_PARAMETER(Driver); ... WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT); WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes); // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig // with FileObjectClass WdfFileObjectWdfXxx. WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK // No cleanup callback function ); ... WdfDeviceInitSetFileObjectConfig(WdfDeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES); ... // Do additional setup required for USB controllers. status = UdecxInitializeWdfDeviceInit(WdfDeviceInit); ... WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT); wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback; // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks // exactly like other USB host controllers. isCreated = FALSE; for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) { status = RtlUnicodeStringPrintf(&uniDeviceName, L"%ws%d", BASE_DEVICE_NAME, instanceNumber); ... status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName); ... status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice); if (status == STATUS_OBJECT_NAME_COLLISION) { // This is expected to happen at least once when another USB host controller // already exists on the system. ... } else if (!NT_SUCCESS(status)) { ... } else { isCreated = TRUE; break; } } if (!isCreated) { ... } // Create the symbolic link (also for compatibility). status = RtlUnicodeStringPrintf(&uniSymLinkName, L"%ws%d", BASE_SYMBOLIC_LINK_NAME, instanceNumber); ... status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName); ... // Create the device interface. RtlInitUnicodeString(&refString, USB_HOST_DEVINTERFACE_REF_STRING); status = WdfDeviceCreateDeviceInterface(wdfDevice, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, &refString); ... UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability); status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice, &controllerConfig); // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through // in separate USB device queues.) // Shown later in this topic. WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential); defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl; defaultQueueConfig.PowerManaged = WdfFalse; status = WdfIoQueueCreate(wdfDevice, &defaultQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &pControllerContext->DefaultQueue); ... // Initialize virtual USB device software objects. // Shown later in this topic. status = Usb_Initialize(wdfDevice); ... exit: return status; } ```1.
Manipular solicitações IOCTL de modo de usuário enviadas ao controlador de host
Durante a inicialização, o driver de cliente UDE expõe o GUID da interface do dispositivo GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Isso permite que o driver receba solicitações IOCTL de um aplicativo que abre um identificador de dispositivo usando esse GUID. Para obter uma lista de códigos de controle IOCTL, consulte IOCTLs USB com a GUID de interface de dispositivo: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.
Para manipular essas solicitações, o driver do cliente registra o retorno de chamada do evento EvtIoDeviceControl. Na implementação, em vez de lidar com a solicitação, o driver pode encaminhá-la para a extensão de classe UDE para processamento. Para encaminhar a solicitação, o driver deve chamar UdecxWdfDeviceTryHandleUserIoctl. Se o código de controle IOCTL recebido corresponder a uma solicitação padrão, como recuperar descritores de dispositivo, a extensão de classe processará e concluirá a solicitação com êxito. Nesse caso, UdecxWdfDeviceTryHandleUserIoctl é concluído com TRUE como o valor de retorno. Caso contrário, a chamada retornará FALSE, e o driver deverá determinar como concluir a solicitação. Em uma implementação mais simples, o driver pode concluir a solicitação com um código de falha apropriado chamando WdfRequestComplete.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Controller_EvtIoDeviceControl;
VOID
Controller_EvtIoDeviceControl(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
)
{
BOOLEAN handled;
NTSTATUS status;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
Request);
if (handled) {
goto exit;
}
// Unexpected control code.
// Fail the request.
status = STATUS_INVALID_DEVICE_REQUEST;
WdfRequestComplete(Request, status);
exit:
return;
}
Relatar os recursos do controlador de host
Antes que os drivers de camada superior possam usar os recursos de um controlador de host USB, os drivers devem determinar se esses recursos são compatíveis com o controlador. Os drivers fazem essas consultas chamando WdfUsbTargetDeviceQueryUsbCapability e USBD_QueryUsbCapability. Essas chamadas são encaminhadas para a extensão de classe de emulação de dispositivo USB (UDE). Ao obter a solicitação, a extensão de classe invoca a implementação EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY do driver de cliente. Essa chamada é feita somente após a conclusão de EvtDriverDeviceAdd, normalmente em EvtDevicePrepareHardware, e não após EvtDeviceReleaseHardware. Essa função de retorno de chamada é necessária.
Na implementação, o driver de cliente deve relatar se ele dá suporte ao recurso solicitado. Certos recursos não são compatíveis com o UDE, como fluxos estáticos.
NTSTATUS
Controller_EvtControllerQueryUsbCapability(
WDFDEVICE UdeWdfDevice,
PGUID CapabilityType,
ULONG OutputBufferLength,
PVOID OutputBuffer,
PULONG ResultLength
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(UdeWdfDevice);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(OutputBuffer);
*ResultLength = 0;
if (RtlCompareMemory(CapabilityType,
&GUID_USB_CAPABILITY_CHAINED_MDLS,
sizeof(GUID)) == sizeof(GUID)) {
//
// TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
// If supported, status = STATUS_SUCCESS
// Otherwise, status = STATUS_NOT_SUPPORTED
}
else {
status = STATUS_NOT_IMPLEMENTED;
}
return status;
}
Criar um dispositivo USB virtual
Um dispositivo USB virtual se comporta de maneira semelhante a um dispositivo USB. Ele dá suporte a uma configuração com várias interfaces, e cada interface dá suporte a configurações alternativas. Cada configuração pode ter mais um ponto de extremidade que é usado para transferências de dados. Todos os descritores (dispositivo, configuração, interface, ponto de extremidade) são definidos pelo driver de cliente UDE para que o dispositivo possa relatar informações muito semelhantes a um dispositivo USB real.
Observação
O driver de cliente UDE não dá suporte a hubs externos
Confira a seguir o resumo da sequência na qual o driver de cliente cria um identificador UDECXUSBDEVICE para um objeto de dispositivo UDE. O driver deve executar essas etapas depois de recuperar o identificador WDFDEVICE do controlador de host emulado. Recomendamos que o driver execute essas tarefas em sua função de retorno de chamada EvtDriverDeviceAdd.
Chame UdecxUsbDeviceInitAllocate para obter um ponteiro para os parâmetros de inicialização necessários para criar o dispositivo. Essa estrutura é alocada pela extensão de classe UDE.
Registre funções de retorno de chamada de evento definindo membros de UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS e chamando UdecxUsbDeviceInitSetStateChangeCallbacks. Confira a seguir as funções de retorno de chamada associadas ao objeto de dispositivo UDE, que são invocadas pela extensão de classe UDE.
Essas funções são implementadas pelo driver do cliente para criar ou configurar pontos de extremidade.
Chame UdecxUsbDeviceInitSetSpeed para definir a velocidade do dispositivo USB e também o tipo de dispositivo, USB 2.0 ou um dispositivo SuperSpeed.
Chame UdecxUsbDeviceInitSetEndpointsType para especificar o tipo de ponto de extremidade que o dispositivo aceita: simples ou dinâmico. Se o driver de cliente optar por criar pontos de extremidade simples, o driver deverá criar todos os objetos de ponto de extremidade antes de conectar o dispositivo. O dispositivo deve ter apenas uma configuração e apenas uma configuração de interface por interface. No caso de pontos de extremidade dinâmicos, o driver pode criar pontos de extremidade a qualquer momento depois de conectar o dispositivo quando ele recebe um retorno de chamada de evento EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE. Consulte Criar pontos de extremidade dinâmicos.
Chame qualquer um desses métodos para adicionar os descritores necessários ao dispositivo.
UdecxUsbDeviceInitAddStringDescriptorRaw
Se a extensão de classe UDE receber uma solicitação de um descritor padrão que o driver de cliente forneceu durante a inicialização usando um dos métodos anteriores, a extensão de classe concluirá automaticamente a solicitação. A extensão de classe não encaminha essa solicitação para o driver de cliente. Esse design reduz o número de solicitações que o driver precisa processar para solicitações de controle. Além disso, ele elimina a necessidade de o driver implementar a lógica do descritor que requer análise extensiva do pacote de instalação e manipulação wLength e TransferBufferLength corretamente. Esta lista inclui as solicitações padrão. O driver de cliente não precisa verificar essas solicitações (somente se os métodos anteriores foram chamados para adicionar o descritor):
USB_REQUEST_GET_DESCRIPTOR
USB_REQUEST_SET_CONFIGURATION
USB_REQUEST_SET_INTERFACE
USB_REQUEST_SET_ADDRESS
USB_REQUEST_SET_FEATURE
USB_FEATURE_FUNCTION_SUSPEND
USB_FEATURE_REMOTE_WAKEUP
USB_REQUEST_CLEAR_FEATURE
USB_FEATURE_ENDPOINT_STALL
USB_REQUEST_SET_SEL
USB_REQUEST_ISOCH_DELAY
No entanto, a extensão de classe UDE encaminha solicitações para a interface, classe específica ou descritor definido pelo fornecedor para o driver de cliente. O driver deve lidar com essas solicitações GET_DESCRIPTOR.
Chame UdecxUsbDeviceCreate para criar o objeto de dispositivo UDE e recuperar o identificador UDECXUSBDEVICE.
Crie pontos de extremidade estáticos chamando UdecxUsbEndpointCreate. Consulte Criar pontos de extremidade simples.
Chame UdecxUsbDevicePlugIn para indicar à extensão de classe UDE que o dispositivo está conectado e pode receber solicitações de E/S em pontos de extremidade. Após essa chamada, a extensão de classe também pode invocar funções de retorno de chamada em pontos de extremidade e no dispositivo USB. Observação Se o dispositivo USB precisar ser removido no tempo de execução, o driver de cliente poderá chamar UdecxUsbDevicePlugOutAndDelete. Se o driver quiser usar o dispositivo, ele deve criá-lo chamando UdecxUsbDeviceCreate.
Neste exemplo, as declarações do descritor são assumidas como variáveis globais, declaradas como mostrado aqui para um dispositivo HID apenas como um exemplo:
const UCHAR g_UsbDeviceDescriptor[] = {
// Device Descriptor
0x12, // Descriptor Size
0x01, // Device Descriptor Type
0x00, 0x03, // USB 3.0
0x00, // Device class
0x00, // Device sub-class
0x00, // Device protocol
0x09, // Maxpacket size for EP0 : 2^9
0x5E, 0x04, // Vendor ID
0x39, 0x00, // Product ID
0x00, // LSB of firmware version
0x03, // MSB of firmware version
0x01, // Manufacture string index
0x03, // Product string index
0x00, // Serial number string index
0x01 // Number of configurations
};
Confira a seguir um exemplo em que o driver de cliente especifica parâmetros de inicialização registrando funções de retorno de chamada, definindo a velocidade do dispositivo, indicando o tipo de ponto de extremidade e, por fim, definindo alguns descritores de dispositivo.
NTSTATUS
Usb_Initialize(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT usbContext; //Client driver declared context for the host controller object
PUDECX_USBDEVICE_CONTEXT deviceContext; //Client driver declared context for the UDE device object
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
WDF_OBJECT_ATTRIBUTES attributes;
UDECX_USB_DEVICE_PLUG_IN_OPTIONS pluginOptions;
usbContext = WdfDeviceGetUsbContext(WdfDevice);
usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);
if (usbContext->UdecxUsbDeviceInit == NULL) {
...
goto exit;
}
// State changed callbacks
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;
UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);
// Set required attributes.
UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);
#ifdef SIMPLEENDPOINTS
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif
// Add device descriptor
//
status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbDeviceDescriptor,
sizeof(g_UsbDeviceDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef USB30
// Add BOS descriptor for a SuperSpeed device
status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbBOSDescriptor,
sizeof(g_UsbBOSDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
// String descriptors
status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_LanguageDescriptor,
sizeof(g_LanguageDescriptor),
0);
if (!NT_SUCCESS(status)) {
goto exit;
}
status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
&g_ManufacturerStringEnUs,
g_ManufacturerIndex,
US_ENGLISH);
if (!NT_SUCCESS(status)) {
goto exit;
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);
status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
&attributes,
&usbContext->UdecxUsbDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef SIMPLEENDPOINTS
// Create the default control endpoint
// Shown later in this topic.
status = UsbCreateControlEndpoint(WdfDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
pluginOptions.Usb30PortNumber = 2;
#else
pluginOptions.Usb20PortNumber = 1;
#endif
status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);
exit:
if (!NT_SUCCESS(status)) {
UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
usbContext->UdecxUsbDeviceInit = NULL;
}
return status;
}
Gerenciamento de energia do dispositivo USB
A extensão de classe UDE invoca as funções de retorno de chamada do driver de cliente quando recebe uma solicitação para enviar o dispositivo para o estado de baixa energia ou trazê-lo de volta ao estado de funcionamento. Essas funções de retorno de chamada são necessárias para dispositivos USB com suporte para ativação. O driver de cliente registrou sua implementação na chamada anterior para UdecxUsbDeviceInitSetStateChangeCallbacks.
Para obter mais informações, consulte Estados de energia do dispositivo USB.
EVT_UDECX_USB_DEVICE_D0_ENTRY: o driver de cliente faz a transição do dispositivo de um estado Dx para um estado D0.
EVT_UDECX_USB_DEVICE_D0_EXIT: o driver de cliente faz a transição do dispositivo de um estado D0 para um estado Dx.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE: o driver de cliente altera o estado da função da interface especificada do dispositivo USB 3.0 virtual.
Um dispositivo USB 3.0 permite que funções individuais entrem no estado de baixa energia. Cada função também é capaz de enviar um sinal de despertar. A extensão de classe UDE notifica o driver de cliente invocando EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Esse evento indica uma alteração de estado de energia da função e informa ao driver de cliente se a função pode ser ativada do novo estado. Na função, a extensão de classe passa o número da interface da função que está sendo ativada.
O driver de cliente pode simular a ação de um dispositivo USB virtual iniciando seu próprio despertar a partir de um estado de baixa energia de link, suspensão de função ou ambos. Para um dispositivo USB 2.0, o driver deve chamar UdecxUsbDeviceSignalWake, se o driver ativado despertar no dispositivo na EVT_UDECX_USB_DEVICE_D0_EXIT mais recente. Para um dispositivo USB 3.0, o driver deve chamar UdecxUsbDeviceSignalFunctionWake porque o recurso de ativação USB 3.0 é por função. Se o dispositivo inteiro estiver em um estado de baixa energia, ou entrando em tal estado, UdecxUsbDeviceSignalFunctionWake ativará o dispositivo.
Criar pontos de extremidade simples
O driver de cliente cria objetos de ponto de extremidade UDE para manipular transferências de dados de e para o dispositivo USB. O driver cria pontos de extremidade simples depois de criar o dispositivo UDE e antes de relatar o dispositivo como conectado.
Confira a seguir o resumo da sequência na qual o driver de cliente cria um identificador UDECXUSBENDPOINT para um objeto de ponto de extremidade UDE. O driver deve seguir estas etapas depois de recuperar o identificador UDECXUSBDEVICE do dispositivo USB virtual. Recomendamos que o driver execute essas tarefas em sua função de retorno de chamada EvtDriverDeviceAdd.
Chame UdecxUsbSimpleEndpointInitAllocate para obter um ponteiro para os parâmetros de inicialização alocados pela extensão de classe.
Chame UdecxUsbEndpointInitSetEndpointAddress para definir o endereço do ponto de extremidade nos parâmetros de inicialização.
Chame UdecxUsbEndpointInitSetCallbacks para registrar as funções de retorno de chamada implementadas pelo driver do cliente.
Essas funções são implementadas pelo driver de cliente para manipular filas e solicitações em um ponto de extremidade.
EVT_UDECX_USB_ENDPOINT_RESET: redefine um ponto de extremidade do dispositivo USB virtual.
EVT_UDECX_USB_ENDPOINT_START: opcional. Inicia o processamento de solicitações de E/S
EVT_UDECX_USB_ENDPOINT_PURGE: opcional. Pare de enfileirar solicitações de E/S para a fila do ponto de extremidade e cancele solicitações não processadas.
Chame UdecxUsbEndpointCreate para criar o objeto de ponto de extremidade e recuperar o identificador UDECXUSBENDPOINT.
Chame UdecxUsbEndpointSetWdfIoQueue para associar um objeto de fila de estrutura ao ponto de extremidade. Se aplicável, ele pode definir o objeto de ponto de extremidade como o objeto pai WDF da fila definindo atributos apropriados.
Cada objeto de ponto de extremidade tem um objeto de fila de estrutura para lidar com solicitações de transferência. Para cada solicitação de transferência que a extensão de classe recebe, ela coloca em fila um objeto de solicitação de estrutura. O estado da fila (iniciado, limpo) é gerenciado pela extensão de classe UDE, e o driver de cliente não deve alterar esse estado. Cada objeto de solicitação contém um URB (bloco de solicitação USB) que contém detalhes da transferência.
Neste exemplo, o driver de cliente cria o ponto de extremidade de controle padrão.
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;
NTSTATUS
UsbCreateControlEndpoint(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT pUsbContext;
WDF_IO_QUEUE_CONFIG queueConfig;
WDFQUEUE controlQueue;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
PUDECXUSBENDPOINT_INIT endpointInit;
pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
endpointInit = NULL;
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);
if (endpointInit == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;
status = UdecxUsbEndpointCreate(&endpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&pUsbContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
if (endpointInit != NULL) {
NT_ASSERT(!NT_SUCCESS(status));
UdecxUsbEndpointInitFree(endpointInit);
endpointInit = NULL;
}
return status;
}
Criar pontos de extremidade dinâmicos
O driver de cliente pode criar pontos de extremidade dinâmicos a pedido da extensão de classe UDE (em nome do driver de hub e dos drivers de cliente). A extensão de classe faz a solicitação invocando qualquer uma destas funções de retorno de chamada:
*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD O driver de cliente cria o ponto de extremidade de controle padrão (Ponto de extremidade 0)
*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD O driver de cliente cria um ponto de extremidade dinâmico.
*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE O driver de cliente altera a configuração selecionando uma configuração alternativa, desabilitando os pontos de extremidade atuais ou adicionando pontos de extremidade dinâmicos.
O driver de cliente registrou o retorno de chamada anterior durante sua chamada para UdecxUsbDeviceInitSetStateChangeCallbacks. Consulte Criar dispositivo USB virtual. Esse mecanismo permite que o driver de cliente altere dinamicamente a configuração USB e as configurações de interface no dispositivo. Por exemplo, quando um objeto de ponto de extremidade é necessário ou um objeto de ponto de extremidade existente deve ser liberado, a extensão de classe chama o EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.
Confira a seguir o resumo da sequência na qual o driver de cliente cria um identificador UDECXUSBENDPOINT para um objeto de ponto de extremidade em sua implementação da função de retorno de chamada.
Chame UdecxUsbEndpointInitSetEndpointAddress para definir o endereço do ponto de extremidade nos parâmetros de inicialização.
Chame UdecxUsbEndpointInitSetCallbacks para registrar as funções de retorno de chamada implementadas pelo driver do cliente. Semelhante aos pontos de extremidade simples, o driver pode registrar estas funções de retorno de chamada:
Chame UdecxUsbEndpointCreate para criar o objeto de ponto de extremidade e recuperar o identificador UDECXUSBENDPOINT.
Chame UdecxUsbEndpointSetWdfIoQueue para associar um objeto de fila de estrutura ao ponto de extremidade.
Neste exemplo de implementação, o driver de cliente cria um ponto de extremidade de controle padrão dinâmico.
NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
_In_
UDECXUSBDEVICE UdecxUsbDevice,
_In_
PUDECXUSBENDPOINT_INIT UdecxUsbEndpointInit
)
{
NTSTATUS status;
PUDECX_USBDEVICE_CONTEXT deviceContext;
WDFQUEUE controlQueue;
WDF_IO_QUEUE_CONFIG queueConfig;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (deviceContext->WdfDevice,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);
status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&deviceContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
return status;
}
Executar a recuperação de erros redefinindo um ponto de extremidade
Às vezes, as transferências de dados podem falhar devido a diversos motivos, como uma condição de paralisação no ponto de extremidade. No caso de transferências com falha, o ponto de extremidade não pode processar solicitações até que a condição de erro seja limpa. Quando a extensão de classe UDE experimenta transferências de dados com falha, ela invoca a função de retorno de chamada EVT_UDECX_USB_ENDPOINT_RESET do driver de dispositivo, que o driver registrou na chamada anterior para UdecxUsbEndpointInitSetCallbacks. Na implementação, o driver pode optar por limpar o estado HALT do tubo e tomar outras medidas necessárias para limpar a condição de erro.
Essa chamada é assíncrona. Depois que o cliente terminar a operação de redefinição, o driver deverá concluir a solicitação com um código de falha apropriado chamando WdfRequestComplete. Essa chamada notifica a extensão do cliente UDE sobre a conclusão da operação de redefinição com status.
Observação Se uma solução complexa for necessária para a recuperação de erros, o driver do cliente terá a opção de redefinir o controlador do host. Essa lógica pode ser implementada na função de retorno de chamada EVT_UDECX_WDF_DEVICE_RESET que o driver registrou em sua chamada UdecxWdfDeviceAddUsbDeviceEmulation. Se aplicável, o driver pode redefinir o controlador do host e todos os dispositivos downstream. Se o driver de cliente não precisar redefinir o controlador, mas redefinir todos os dispositivos downstream, o driver deverá especificar UdeWdfDeviceResetActionResetEachUsbDevice nos parâmetros de configuração durante o registro. Nesse caso, a extensão de classe invoca EVT_UDECX_WDF_DEVICE_RESET para cada dispositivo conectado.
Implementar o gerenciamento de estado da fila
O estado do objeto de fila de estrutura associado a um objeto de ponto de extremidade UDE é gerenciado pela extensão de classe UDE. No entanto, se o driver do cliente encaminhar solicitações de filas de ponto de extremidade para outras filas internas, o cliente deverá implementar uma lógica para lidar com alterações no fluxo de E/S do ponto de extremidade. Essas funções de retorno de chamada são registradas com UdecxUsbEndpointInitSetCallbacks.
Operação de limpeza de ponto de extremidade
Um driver de cliente UDE com uma fila por ponto de extremidade pode implementar EVT_UDECX_USB_ENDPOINT_PURGE como mostrado neste exemplo:
Na implementação do EVT_UDECX_USB_ENDPOINT_PURGE, o driver de cliente é necessário para garantir que toda a E/S encaminhada da fila do ponto de extremidade tenha sido concluída e que a E/S recém-encaminhada também falhe até que o EVT_UDECX_USB_ENDPOINT_START do driver de cliente seja invocado. Esses requisitos são atendidos chamando UdecxUsbEndpointPurgeComplete, que garante que toda a E/S encaminhada seja concluída e que as futuras E/S encaminhadas estejam com falha.
Operação de início de ponto de extremidade
Na implementação do EVT_UDECX_USB_ENDPOINT_START, o driver de cliente é necessário para começar a processar a E/S na fila do ponto de extremidade e em qualquer fila que receba a E/S encaminhada do ponto de extremidade.. Depois que um ponto de extremidade é criado, ele não recebe nenhuma E/S até que essa função de retorno de chamada retorne. Esse retorno de chamada retorna o ponto de extremidade para um estado de processamento de E/S após a conclusão do EVT_UDECX_USB_ENDPOINT_PURGE.
Tratamento de solicitações de transferência de dados (URBs)
Para processar solicitações de E/S USB enviadas aos pontos de extremidade do dispositivo cliente, intercepte o retorno de chamada do EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL no objeto de fila usado com UdecxUsbEndpointInitSetCallbacks ao associar a fila ao ponto de extremidade. Nesse retorno de chamada, processe a E/S do IoControlCode IOCTL_INTERNAL_USB_SUBMIT_URB (consulte a amostra de código em Métodos de tratamento URB).
Métodos de tratamento URB
Como parte do processamento de URBs por meio do IOCTL_INTERNAL_USB_SUBMIT_URB de uma fila associada a um ponto de extremidade em um dispositivo virtual, um driver de cliente UDE pode obter um ponteiro para o buffer de transferência de uma solicitação de E/S usando estes métodos:
Essas funções são implementadas pelo driver de cliente para manipular filas e solicitações em um ponto de extremidade.
UdecxUrbRetrieveControlSetupPacket Recupera um pacote de instalação de controle USB de um objeto de solicitação de estrutura especificado.
UdecxUrbRetrieveBuffer Recupera o buffer de transferência de um URB do objeto de solicitação de estrutura especificado enviado para a fila de ponto de extremidade.
UdecxUrbSetBytesCompleted Define o número de bytes transferidos para o URB contido em um objeto de solicitação de estrutura.
UdecxUrbComplete Conclui a solicitação URB com um código de status de conclusão específico para USB.
UdecxUrbCompleteWithNtStatus Conclui a solicitação URB com um código NTSTATUS.
Confira a seguir o fluxo de processamento de E/S típico para o URB de uma transferência USB OUT.
static VOID
IoEvtSampleOutUrb(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
PENDPOINTQUEUE_CONTEXT pEpQContext;
NTSTATUS status = STATUS_SUCCESS;
PUCHAR transferBuffer;
ULONG transferBufferLength = 0;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
// one possible way to get context info
pEpQContext = GetEndpointQueueContext(Queue);
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
{
LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
Request, IoControlCode, status);
status = STATUS_INVALID_PARAMETER;
goto exit;
}
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
if (!NT_SUCCESS(status))
{
LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
Request, status);
goto exit;
}
if (transferBufferLength >= 1)
{
//consume one byte of output data
pEpQContext->global_storage = transferBuffer[0];
}
exit:
// writes never pended, always completed
UdecxUrbSetBytesCompleted(Request, transferBufferLength);
UdecxUrbCompleteWithNtStatus(Request, status);
return;
}
O driver de cliente pode concluir uma solicitação de E/S separadamente com um DPC. Siga estas práticas recomendadas:
- Para garantir a compatibilidade com drivers USB existentes, o cliente UDE deve chamar WdfRequestComplete em DISPATCH_LEVEL.
- Se o URB foi adicionado à fila de um ponto de extremidade e o driver começa a processá-lo de forma síncrona no thread ou DPC do driver de chamada, a solicitação não deve ser concluída de forma síncrona. Um DPC separado é necessário para essa finalidade, que o driver enfileira chamando WdfDpcEnqueue.
- Quando a extensão de classe UDE invoca EvtIoCanceledOnQueue ou EvtRequestCancel, o driver de cliente deve concluir o URB recebido em um DPC separado do thread ou DPC do autor da chamada. Para fazer isso, o driver deve fornecer um retorno de chamada EvtIoCanceledOnQueue para suas filas URB.