Partilhar via


Noções básicas sobre a estrutura de código do driver do cliente USB (KMDF)

Neste tópico, você aprenderá sobre o código-fonte de um driver de cliente USB baseado em KMDF. Os exemplos de código são gerados pelo modelo de driver de modo de usuário USB incluído no Microsoft Visual Studio 2019.

Essas seções fornecem informações sobre o código do modelo.

Para obter instruções sobre como gerar o código de modelo KMDF, consulte Como escrever seu primeiro driver de cliente USB (KMDF).

Código-fonte do driver

O objeto driver representa a instância do driver cliente depois que Windows carrega o driver na memória. O código-fonte completo do objeto driver está em Driver.h e Driver.c.

Motorista.h

Antes de discutir os detalhes do código do modelo, vamos examinar algumas declarações no arquivo de cabeçalho (Driver.h) que são relevantes para o desenvolvimento do driver KMDF.

Driver.h, contém esses arquivos, incluídos no WDK (Windows Driver Kit).

#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>

#include "device.h"
#include "queue.h"
#include "trace.h"

Os arquivos de cabeçalho Ntddk.h e Wdf.h são sempre incluídos para o desenvolvimento do driver KMDF. O arquivo de cabeçalho inclui várias declarações e definições de métodos e estruturas que você precisa para compilar um driver KMDF.

Usb.h e Usbdlib.h incluem declarações e definições de estruturas e rotinas exigidas por um driver cliente para um dispositivo USB.

Wdfusb.h inclui declarações e definições de estruturas e métodos necessários para se comunicar com os objetos de destino de E/S USB fornecidos pela estrutura.

Device.h, Queue.h e Trace.h não estão incluídos no WDK. Esses arquivos de cabeçalho são gerados pelo modelo e são discutidos posteriormente neste tópico.

O próximo bloco em Driver.h fornece declarações de tipo de função para a rotina DriverEntry e rotinas de retorno de chamada de evento EvtDriverDeviceAdd e EvtCleanupCallback. Todas essas rotinas são implementadas pelo motorista. Os tipos de função ajudam o SDV (Verificador de Driver Estático) a analisar o código-fonte de um driver. Para obter mais informações sobre tipos de função, consulte Declarando funções usando tipos de função de função para drivers KMDF.

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

O arquivo de implementação, Driver.c, contém o seguinte bloco de código que usa alloc_text pragma para especificar se a função DriverEntry e as rotinas de retorno de chamada de evento estão na memória pageable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif

Observe que DriverEntry está marcado como INIT, enquanto as rotinas de retorno de chamada de evento são marcadas como PAGE. A seção INIT indica que o código executável para DriverEntry é paginável e descartado assim que o driver retorna de seu DriverEntry. A seção PAGE indica que o código não precisa permanecer na memória física o tempo todo; Ele pode ser gravado no arquivo de paginação quando não estiver em uso. Para obter mais informações, consulte Bloqueando código ou dados pagináveis.

Logo após o driver ser carregado, o Windows aloca uma estrutura DRIVER_OBJECT que representa o driver. Em seguida, ele chama a rotina de ponto de entrada do driver, DriverEntry, e passa um ponteiro para a estrutura. Como Windows procura a rotina por nome, cada driver deve implementar uma rotina chamada DriverEntry. A rotina executa as tarefas de inicialização do driver e especifica as rotinas de retorno de chamada de evento do driver para a estrutura.

O exemplo de código a seguir mostra a rotina DriverEntry gerada pelo modelo.

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    WDF_OBJECT_ATTRIBUTES attributes;

    //
    // Initialize WPP Tracing
    //
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;

    WDF_DRIVER_CONFIG_INIT(&config,
                           MyUSBDriver_EvtDeviceAdd
                           );

    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
        WPP_CLEANUP(DriverObject);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

A rotina DriverEntry tem dois parâmetros: um ponteiro para a estrutura DRIVER_OBJECT alocada pelo Windows e um caminho do Registro para o driver. O parâmetro RegistryPath representa o caminho específico do driver no Registro.

Na rotina DriverEntry, o driver executa estas tarefas:

  • Aloca recursos globais necessários durante o tempo de vida do driver. Por exemplo, no código do modelo, o driver cliente aloca os recursos necessários para o rastreamento de software WPP chamando a macro WPP_INIT_TRACING.

  • Registra determinadas rotinas de retorno de chamada de evento com a estrutura.

    Para registrar os retornos de chamada de evento, o driver cliente primeiro especifica ponteiros para suas implementações das rotinas EvtDriverXxx em determinadas estruturas WDF. Em seguida, o driver chama o método WdfDriverCreate e fornece essas estruturas (discutidas na próxima etapa).

  • Chama o método WdfDriverCreate e recupera um identificador para o objeto de driver de estrutura.

    Depois que o driver cliente chama WdfDriverCreate, a estrutura cria um objeto de driver de estrutura para representar o driver cliente. Quando a chamada é concluída, o driver cliente recebe um identificador WDFDRIVER e pode recuperar informações sobre o driver, como seu caminho do Registro, informações de versão e assim por diante (consulte Referência de objeto de driver WDF).

    Observe que o objeto de driver de estrutura é diferente do objeto de driver do Windows descrito por DRIVER_OBJECT. A qualquer momento, o driver cliente pode obter um ponteiro para a estrutura do WindowsDRIVER_OBJECT usando o identificador WDFDRIVER e chamando o método WdfGetDriver .

Após a chamada WdfDriverCreate , a estrutura faz parceria com o driver cliente para se comunicar com Windows. A estrutura atua como uma camada de abstração entre Windows e o driver e lida com a maioria das tarefas complicadas do driver. O driver cliente se registra na estrutura para eventos nos quais o driver está interessado. Quando determinados eventos ocorrem, o Windows notifica a estrutura. Se o driver registrou um retorno de chamada de evento para um evento específico, a estrutura notificará o driver invocando o retorno de chamada de evento registrado. Ao fazer isso, o motorista tem a oportunidade de lidar com o evento, se necessário. Se o driver não registrou seu retorno de chamada de evento, a estrutura continuará com o tratamento padrão do evento.

Um dos retornos de chamada de evento que o driver deve registrar é EvtDriverDeviceAdd. A estrutura invoca a implementação EvtDriverDeviceAdd do driver quando a estrutura está pronta para criar um objeto de dispositivo. No Windows, um objeto de dispositivo é uma representação lógica da função do dispositivo físico para o qual o driver cliente é carregado (discutido posteriormente neste tópico).

Outros retornos de chamada de evento que o driver pode registrar são EvtDriverUnload, EvtCleanupCallback e EvtDestroyCallback.

No código do modelo, o driver cliente se registra para dois eventos: EvtDriverDeviceAdd e EvtCleanupCallback. O driver especifica um ponteiro para sua implementação de EvtDriverDeviceAdd na estrutura WDF_DRIVER_CONFIG e o retorno de chamada de evento EvtCleanupCallback na estrutura WDF_OBJECT_ATTRIBUTES.

Quando o Windows estiver pronto para liberar a estrutura DRIVER_OBJECT e descarregar o driver, a estrutura relatará esse evento ao driver cliente invocando a implementação EvtCleanupCallback do driver. A estrutura invoca esse retorno de chamada antes de excluir o objeto do driver da estrutura. O driver cliente pode liberar todos os recursos globais alocados em seu DriverEntry. Por exemplo, no código do modelo, o driver cliente interrompe o rastreamento WPP que foi ativado em DriverEntry.

O exemplo de código a seguir mostra a implementação de retorno de chamada de evento EvtCleanupCallback do driver cliente.

VOID MyUSBDriver_EvtDriverContextCleanup(
    _In_ WDFDRIVER Driver
    )
{
    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE ();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Stop WPP Tracing
    //
    WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );

}

Depois que o dispositivo é reconhecido pela pilha de driver USB, o driver de barramento cria um PDO (objeto de dispositivo físico) para o dispositivo e associa o PDO ao nó do dispositivo. O nó do dispositivo está em uma formação de pilha, onde o PDO está na parte inferior. Cada pilha deve ter um PDO e pode ter objetos de dispositivo de filtro (DOs de filtro) e um objeto de dispositivo de função (FDO) acima dele. Para obter mais informações, consulte Nós de dispositivo e pilhas de dispositivos.

Esta ilustração mostra a pilha de dispositivos para o driver de modelo, MyUSBDriver_.sys.

pilha de dispositivos para driver de modelo.

Observe a pilha de dispositivos chamada "Meu dispositivo USB". A pilha de driver USB cria o PDO para a pilha de dispositivos. No exemplo, o PDO está associado ao Usbhub3.sys, que é um dos drivers incluídos na pilha de driver USB. Como o driver de função do dispositivo, o driver cliente deve primeiro criar o FDO para o dispositivo e, em seguida, anexá-lo à parte superior da pilha do dispositivo.

Para um driver cliente baseado em KMDF, a estrutura executa essas tarefas em nome do driver cliente. Para representar o FDO para o dispositivo, a estrutura cria um objeto de dispositivo de estrutura. No entanto, o driver cliente pode especificar determinados parâmetros de inicialização que a estrutura usa para configurar o novo objeto. Essa oportunidade é dada ao driver cliente quando a estrutura invoca a implementação EvtDriverDeviceAdd do driver. Depois que o objeto é criado e o FDO é anexado à parte superior da pilha de dispositivos, a estrutura fornece ao driver cliente um identificador WDFDEVICE para o objeto de dispositivo da estrutura. Usando esse identificador, o driver cliente pode executar várias operações relacionadas ao dispositivo.

O exemplo de código a seguir mostra a implementação de retorno de chamada de evento EvtDriverDeviceAdd do driver cliente.

NTSTATUS
MyUSBDriver_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = MyUSBDriver_CreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Durante o tempo de execução, a implementação de EvtDriverDeviceAdd usa a macro PAGED_CODE para verificar se a rotina está sendo chamada em um ambiente apropriado para código paginável. Certifique-se de chamar a macro depois de declarar todas as suas variáveis; caso contrário, a compilação falhará porque os arquivos de origem gerados são arquivos .c e não arquivos .cpp.

A implementação EvtDriverDeviceAdd do driver cliente chama a função auxiliar MyUSBDriver_CreateDevice para executar as tarefas necessárias.

O exemplo de código a seguir mostra a função auxiliar MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice é definido em Device.c.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
        // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
        // device.h header file. This function will do the type checking and return
        // the device context. If you pass a wrong object  handle
        // it will return NULL and assert if run under framework verifier mode.
        //
        deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
        deviceContext->PrivateDeviceData = 0;

        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_MyUSBDriver_,
            NULL // ReferenceString
            );

        if (NT_SUCCESS(status)) {
            //
            // Initialize the I/O Package and any Queues
            //
            status = MyUSBDriver_QueueInitialize(device);
        }
    }

    return status;
}

EvtDriverDeviceAdd tem dois parâmetros: um identificador para o objeto de driver de estrutura criado na chamada anterior para DriverEntry e um ponteiro para uma estrutura WDFDEVICE_INIT. A estrutura aloca a estrutura WDFDEVICE_INIT e passa um ponteiro para que o driver cliente possa preencher a estrutura com parâmetros de inicialização para o objeto de dispositivo de estrutura a ser criado.

Na implementação EvtDriverDeviceAdd , o driver cliente deve executar estas tarefas:

  • Chame o método WdfDeviceCreate para recuperar um identificador WDFDEVICE para o novo objeto de dispositivo.

    O método WdfDeviceCreate faz com que a estrutura crie um objeto de dispositivo de estrutura para o FDO e anexe-o à parte superior da pilha de dispositivos. Na chamada WdfDeviceCreate , o driver cliente deve executar estas tarefas:

    • Especifique ponteiros para as rotinas de retorno de chamada de energia PnP (Plug and play) do driver cliente na estrutura de WDFDEVICE_INIT especificada pela estrutura. As rotinas são definidas primeiro na estrutura WDF_PNPPOWER_EVENT_CALLBACKS e, em seguida, associadas a WDFDEVICE_INIT chamando o método WdfDeviceInitSetPnpPowerEventCallbacks .

    Os componentes do Windows, PnP e gerenciadores de energia, enviam solicitações relacionadas ao dispositivo aos drivers em resposta a alterações no estado PnP (como iniciado, interrompido e removido) e no estado de energia (como funcionando ou suspendendo). Para drivers baseados em KMDF, a estrutura intercepta essas solicitações. O driver cliente pode ser notificado sobre as solicitações registrando rotinas de retorno de chamada chamadas de retorno de chamada de evento de energia PnP com a estrutura, usando a chamada WdfDeviceCreate . Quando os componentes do Windows enviam solicitações, a estrutura as manipula e chama o retorno de chamada de evento de energia PnP correspondente, se o driver cliente tiver sido registrado.

    Uma das rotinas de retorno de chamada de evento de energia PnP que o driver cliente deve implementar é EvtDevicePrepareHardware. Esse retorno de chamada de evento é invocado quando o gerenciador PnP inicia o dispositivo. A implementação de EvtDevicePrepareHardware é discutida na seção a seguir.

    Um contexto de dispositivo (às vezes chamado de extensão de dispositivo) é uma estrutura de dados (definida pelo driver cliente) para armazenar informações sobre um objeto de dispositivo específico. O driver cliente passa um ponteiro para o contexto do dispositivo para a estrutura. A estrutura aloca um bloco de memória com base no tamanho da estrutura e armazena um ponteiro para esse local de memória no objeto de dispositivo da estrutura. O driver cliente pode usar o ponteiro para acessar e armazenar informações em membros do contexto do dispositivo. Para obter mais informações sobre contextos de dispositivo, consulte Espaço de contexto de objeto do Framework.

    Depois que a chamada WdfDeviceCreate é concluída, o driver cliente recebe um identificador para o novo objeto de dispositivo de estrutura, que armazena um ponteiro para o bloco de memória alocado pela estrutura para o contexto do dispositivo. O driver cliente agora pode obter um ponteiro para o contexto do dispositivo chamando a macro WdfObjectGet_DEVICE_CONTEXT .

  • Registre um GUID de interface de dispositivo para o driver cliente chamando o método WdfDeviceCreateDeviceInterface . Os aplicativos podem se comunicar com o driver usando esse GUID. A constante GUID é declarada no cabeçalho, public.h.

  • Configure filas para transferências de E/S para o dispositivo. O código do modelo define MyUSBDriver_QueueInitialize, uma rotina auxiliar para configurar filas, que é discutida na seção Código-fonte da fila.

Código-fonte do dispositivo

O objeto de dispositivo representa a instância do dispositivo para a qual o driver cliente é carregado na memória. O código-fonte completo do objeto de dispositivo está em Device.h e Device.c.

Dispositivo.h

O arquivo de cabeçalho Device.h inclui public.h, que contém declarações comuns usadas por todos os arquivos no projeto.

O próximo bloco em Device.h declara o contexto do dispositivo para o driver cliente.

typedef struct _DEVICE_CONTEXT
{
    WDFUSBDEVICE UsbDevice;
    ULONG PrivateDeviceData;  // just a placeholder

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

A estrutura DEVICE_CONTEXT é definida pelo driver cliente e armazena informações sobre um objeto de dispositivo de estrutura. Ele é declarado em Device.h e contém dois membros: um identificador para o objeto de dispositivo de destino USB de uma estrutura (discutido posteriormente) e um espaço reservado. Essa estrutura será expandida em exercícios posteriores.

Device.h também inclui a macro WDF_DECLARE_CONTEXT_TYPE , que gera uma função embutida, WdfObjectGet_DEVICE_CONTEXT. O driver cliente pode chamar essa função para recuperar um ponteiro para o bloco de memória do objeto de dispositivo de estrutura.

A linha de código a seguir declara MyUSBDriver_CreateDevice, uma função auxiliar que recupera um identificador WDFUSBDEVICE para o objeto de dispositivo de destino USB.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate usa um ponteiro para uma estrutura WDFDEVICE_INIT como seu parâmetro. Esse é o mesmo ponteiro que foi passado pela estrutura quando ela invocou a implementação EvtDriverDeviceAdd do driver cliente. Basicamente, MyUSBDriver_CreateDevice executa as tarefas de EvtDriverDeviceAdd. O código-fonte da implementação de EvtDriverDeviceAdd é discutido na seção anterior.

A próxima linha em Device.h declara uma declaração de tipo de função para a rotina de retorno de chamada de evento EvtDevicePrepareHardware. O retorno de chamada de evento é implementado pelo driver cliente e executa tarefas como configurar o dispositivo USB.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Dispositivo.c

O arquivo de implementação Device.c contém o seguinte bloco de código que usa alloc_text pragma para especificar que a implementação do driver de EvtDevicePrepareHardware está na memória pageable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif

Na implementação de EvtDevicePrepareHardware, o driver cliente executa as tarefas de inicialização específicas do USB. Essas tarefas incluem registrar o driver cliente, inicializar objetos de destino de E/S específicos de USB e selecionar uma configuração USB. A tabela a seguir mostra os objetos de destino de E/S especializados fornecidos pela estrutura. Para obter mais informações, consulte Destinos de E/S USB.

Objeto de destino de E/S USB (identificador) Fique por conta de tudo ligando... Descrição
Objeto de dispositivo de destino USB (WDFUSBDEVICE ) WdfUsbTargetDeviceCreateWithParameters Representa um dispositivo USB e fornece métodos para recuperar o descritor do dispositivo e enviar solicitações de controle para o dispositivo.
Objeto de interface de destino USB (WDFUSBINTERFACE) WdfUsbTargetDeviceGetInterface Representa uma interface individual e fornece métodos que um driver cliente pode chamar para selecionar uma configuração alternativa e recuperar informações sobre a configuração.
Objeto de pipe de destino USB (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Representa um pipe individual para um endpoint que está configurado na configuração alternativa atual de uma interface. A pilha de driver USB seleciona cada interface na configuração selecionada e configura um canal de comunicação para cada ponto de extremidade dentro da interface. Na terminologia USB, esse canal de comunicação é conhecido como pipe.

Este exemplo de código mostra a implementação de EvtDevicePrepareHardware.

NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
    )
{
    NTSTATUS status;
    PDEVICE_CONTEXT pDeviceContext;
    WDF_USB_DEVICE_CREATE_CONFIG createParams;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;

    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(ResourceListTranslated);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = STATUS_SUCCESS;
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);

    if (pDeviceContext->UsbDevice == NULL) {

        //
        // Specifying a client contract version of 602 enables us to query for
        // and use the new capabilities of the USB driver stack for Windows 8.
        // It also implies that we conform to rules mentioned in the documentation
        // documentation for WdfUsbTargetDeviceCreateWithParameters.
        //
        WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
                                         USBD_CLIENT_CONTRACT_VERSION_602
                                         );

        status = WdfUsbTargetDeviceCreateWithParameters(Device,
                                                    &createParams,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &pDeviceContext->UsbDevice
                                                    );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
            return status;
        }

        //
        // Select the first configuration of the device, using the first alternate
        // setting of each interface
        //
        WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
                                                                     0,
                                                                     NULL
                                                                     );
        status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
                                                WDF_NO_OBJECT_ATTRIBUTES,
                                                &configParams
                                                );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
            return status;
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Aqui está uma análise mais detalhada das tarefas do driver cliente, conforme implementadas pelo código do modelo:

  1. Especifica a versão do contrato do driver cliente em preparação para se registrar na pilha de driver USB subjacente, carregada pelo Windows.

    O Windows pode carregar a pilha de drivers USB 3.0 ou USB 2.0, dependendo do controlador de host ao qual o dispositivo USB está conectado. A pilha de driver USB 3.0 é nova no Windows 8 e dá suporte a vários novos recursos definidos pela especificação USB 3.0, como a funcionalidade de fluxos. A nova pilha de drivers também implementa várias melhorias, como melhor rastreamento e processamento de URBs (Blocos de Solicitação USB), que estão disponíveis por meio de um novo conjunto de rotinas URB. Um driver cliente que pretende usar esses recursos ou chamar as novas rotinas deve especificar a versão USBD_CLIENT_CONTRACT_VERSION_602 contrato. Um driver cliente USBD_CLIENT_CONTRACT_VERSION_602 deve aderir a um determinado conjunto de regras. Para obter mais informações sobre essas regras, consulte Práticas recomendadas: usando URBs.

    Para especificar a versão do contrato, o driver cliente deve inicializar uma estrutura WDF_USB_DEVICE_CREATE_CONFIG com a versão do contrato chamando a macro WDF_USB_DEVICE_CREATE_CONFIG_INIT .

  2. Chama o método WdfUsbTargetDeviceCreateWithParameters . O método requer um identificador para o objeto de dispositivo de estrutura que o driver cliente obteve anteriormente chamando WdfDeviceCreate na implementação do driver de EvtDriverDeviceAdd. O método WdfUsbTargetDeviceCreateWithParameters :

    • Registra o driver cliente com a pilha de driver USB subjacente.
    • Recupera um identificador WDFUSBDEVICE para o objeto de dispositivo de destino USB criado pela estrutura. O código do modelo armazena o identificador para o objeto de dispositivo de destino USB em seu contexto de dispositivo. Usando esse identificador, o driver cliente pode obter informações específicas do USB sobre o dispositivo.

    Você deve chamar WdfUsbTargetDeviceCreate em vez de WdfUsbTargetDeviceCreateWithParameters se:

    Esses drivers não são necessários para especificar uma versão do contrato do cliente e, portanto, devem ignorar a Etapa 1.

  3. Seleciona uma configuração USB.

    No código do modelo, o driver cliente seleciona a configuração padrão no dispositivo USB. A configuração padrão inclui a Configuração 0 do dispositivo e a Configuração Alternativa 0 de cada interface dentro dessa configuração.

    Para selecionar a configuração padrão, o driver cliente configura a estrutura WDF_USB_DEVICE_SELECT_CONFIG_PARAMS chamando a função WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES . A função inicializa o membro Type como WdfUsbTargetDeviceSelectConfigTypeMultiInterface para indicar que, se várias interfaces estiverem disponíveis, uma configuração alternativa em cada uma dessas interfaces deverá ser selecionada. Como a chamada deve selecionar a configuração padrão, o driver cliente especifica NULL no parâmetro SettingPairs e 0 no parâmetro NumberInterfaces . Após a conclusão, o membro MultiInterface.NumberOfConfiguredInterfaces do WDF_USB_DEVICE_SELECT_CONFIG_PARAMS indica o número de interfaces para as quais a Configuração Alternativa 0 foi selecionada. Outros membros não são modificados.

    Observação Se o driver cliente quiser selecionar configurações alternativas diferentes da configuração padrão, o driver deverá criar uma matriz de estruturas WDF_USB_INTERFACE_SETTING_PAIR. Cada elemento na matriz especifica o número da interface definida pelo dispositivo e o índice da configuração alternativa a ser selecionada. Essas informações são armazenadas nos descritores de configuração e interface do dispositivo que podem ser obtidos chamando o método WdfUsbTargetDeviceRetrieveConfigDescriptor . Em seguida, o driver cliente deve chamar WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES e passar a matriz WDF_USB_INTERFACE_SETTING_PAIR para a estrutura.

Código-fonte da fila

O objeto de fila de estrutura representa a fila de E/S para um objeto de dispositivo de estrutura específico. O código-fonte completo do objeto queue está em Queue.h e Queue.c.

Fila.h

Declara uma rotina de retorno de chamada de evento para o evento gerado pelo objeto queue da estrutura.

O primeiro bloco em Queue.h declara um contexto de fila.

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

Semelhante a um contexto de dispositivo, um contexto de fila é uma estrutura de dados definida pelo cliente para armazenar informações sobre uma fila específica.

A próxima linha de código declara MyUSBDriver_QueueInitialize função, a função auxiliar que cria e inicializa o objeto de fila de estrutura.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

O próximo exemplo de código declara uma declaração de tipo de função para a rotina de retorno de chamada de evento EvtIoDeviceControl . O retorno de chamada de evento é implementado pelo driver cliente e é invocado quando a estrutura processa uma solicitação de controle de E/S do dispositivo.

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Fila.c

O arquivo de implementação, Queue.c, contém o seguinte bloco de código que usa alloc_text pragma para especificar que a implementação do driver de MyUSBDriver_QueueInitialize está na memória pageable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif

O WDF fornece o objeto de fila de estrutura para lidar com o fluxo de solicitação para o driver cliente. A estrutura cria um objeto de fila de estrutura quando o driver cliente chama o método WdfIoQueueCreate . Nessa chamada, o driver cliente pode especificar determinadas opções de configuração antes que a estrutura crie filas. Essas opções incluem se a fila é gerenciada por energia, permite solicitações de comprimento zero ou é a fila padrão para o driver. Um único objeto de fila de estrutura pode lidar com vários tipos de solicitações, como leitura, gravação e controle de E/S do dispositivo. O driver cliente pode especificar retornos de chamada de evento para cada uma dessas solicitações.

O driver cliente também deve especificar o tipo de expedição. O tipo de expedição de um objeto queue determina como a estrutura entrega solicitações ao driver cliente. O mecanismo de entrega pode ser sequencial, em paralelo ou por um mecanismo personalizado definido pelo driver cliente. Para uma fila sequencial, uma solicitação não é entregue até que o driver cliente conclua a solicitação anterior. No modo de despacho paralelo, a estrutura encaminha as solicitações assim que elas chegam do gerenciador de E/S. Isso significa que o driver cliente pode receber uma solicitação enquanto processa outra. No mecanismo personalizado, o cliente efetua pull manualmente da próxima solicitação do objeto de fila de estrutura quando o driver está pronto para processá-la.

Normalmente, o driver cliente deve configurar filas no retorno de chamada de evento EvtDriverDeviceAdd do driver. O código do modelo fornece a rotina auxiliar, MyUSBDriver_QueueInitialize, que inicializa o objeto de fila da estrutura.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    )
{
    WDFQUEUE queue;
    NTSTATUS status;
    WDF_IO_QUEUE_CONFIG    queueConfig;

    PAGED_CODE();
    
    //
    // Configure a default queue so that requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
    // other queues get dispatched here.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
         &queueConfig,
        WdfIoQueueDispatchParallel
        );

    queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;

    status = WdfIoQueueCreate(
                 Device,
                 &queueConfig,
                 WDF_NO_OBJECT_ATTRIBUTES,
                 &queue
                 );

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    return status;
}

Para configurar filas, o driver cliente executa estas tarefas:

  1. Especifica as opções de configuração da fila em uma estrutura WDF_IO_QUEUE_CONFIG . O código do modelo usa a função WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE para inicializar a estrutura. A função especifica o objeto de fila como o objeto de fila padrão, é gerenciada por energia e recebe solicitações em paralelo.
  2. Adiciona os retornos de chamada de evento do driver cliente para solicitações de E/S para a fila. No modelo, o driver cliente especifica um ponteiro para seu retorno de chamada de evento para uma solicitação de controle de E/S do dispositivo.
  3. Chama WdfIoQueueCreate para recuperar um identificador WDFQUEUE para o objeto de fila da estrutura criado pela estrutura.

Veja como funciona o mecanismo de fila. Para se comunicar com o dispositivo USB, um aplicativo primeiro abre um identificador para o dispositivo chamando as rotinas SetDixxx e CreateHandle. Usando esse identificador, o aplicativo chama a função DeviceIoControl com um código de controle específico. Dependendo do tipo de código de controle, o aplicativo pode especificar buffers de entrada e saída nessa chamada. A chamada é eventualmente recebida pelo Gerenciador de E/S, que cria uma IRP (solicitação) e a 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. Nesse caso, como o driver cliente registrou seu retorno de chamada de evento para a solicitação de controle de E/S do dispositivo, a estrutura invoca o retorno de chamada. Além disso, como o objeto queue foi criado com o sinalizador WdfIoQueueDispatchParallel, o retorno de chamada é invocado assim que a solicitação é adicionada à fila.

VOID
MyUSBDriver_EvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, 
                TRACE_QUEUE, 
                "!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", 
                Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);

    WdfRequestComplete(Request, STATUS_SUCCESS);

    return;
}

Quando a estrutura invoca o retorno de chamada de evento do driver cliente, ela passa um identificador para o objeto de solicitação de estrutura que contém a solicitação (e seus buffers de entrada e saída) enviada pelo aplicativo. Além disso, ele envia um identificador para o objeto de fila da estrutura que contém a solicitação. No retorno de chamada do evento, o driver cliente processa a solicitação conforme necessário. O código do modelo simplesmente conclui a solicitação. O driver cliente pode executar tarefas mais envolvidas. Por exemplo, se um aplicativo solicitar determinadas informações do dispositivo, no retorno de chamada de evento, o driver cliente poderá criar uma solicitação de controle USB e enviá-la para a pilha de driver USB para recuperar as informações solicitadas do dispositivo. As solicitações de controle USB são discutidas em Transferência de controle USB.