Partilhar via


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.

  1. Chame UdecxInitializeWdfDeviceInit passando a referência para WDFDEVICE_INIT passada pela estrutura.

  2. 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.

  3. Chame WdfDeviceCreate para criar o objeto de dispositivo da estrutura.

  4. 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_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.

  1. 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.

  2. 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.

  3. Chame UdecxUsbDeviceInitSetSpeed para definir a velocidade do dispositivo USB e também o tipo de dispositivo, USB 2.0 ou um dispositivo SuperSpeed.

  4. 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.

  5. Chame qualquer um desses métodos para adicionar os descritores necessários ao dispositivo.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • 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.

  6. Chame UdecxUsbDeviceCreate para criar o objeto de dispositivo UDE e recuperar o identificador UDECXUSBDEVICE.

  7. Crie pontos de extremidade estáticos chamando UdecxUsbEndpointCreate. Consulte Criar pontos de extremidade simples.

  8. 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.

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.

  1. Chame UdecxUsbSimpleEndpointInitAllocate para obter um ponteiro para os parâmetros de inicialização alocados pela extensão de classe.

  2. Chame UdecxUsbEndpointInitSetEndpointAddress para definir o endereço do ponto de extremidade nos parâmetros de inicialização.

  3. 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.

  4. Chame UdecxUsbEndpointCreate para criar o objeto de ponto de extremidade e recuperar o identificador UDECXUSBENDPOINT.

  5. 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.

  1. Chame UdecxUsbEndpointInitSetEndpointAddress para definir o endereço do ponto de extremidade nos parâmetros de inicialização.

  2. 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:

  3. Chame UdecxUsbEndpointCreate para criar o objeto de ponto de extremidade e recuperar o identificador UDECXUSBENDPOINT.

  4. 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.