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.

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

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

Código fonte do driver

O objeto de driver representa a instância do driver do cliente depois que o Windows carrega o driver na memória. O código-fonte completo para o objeto de 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 Windows Driver Kit (WDK).

#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 que são exigidas por um driver de 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 para as rotinas de retorno de chamada de evento, EvtDriverDeviceAdd e EvtCleanupCallback. Todas estas rotinas são implementadas pelo motorista. Os tipos de função ajudam o Verificador de Driver Estático (SDV) 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 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 paginável.

#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 Bloqueio de código ou dados pagináveis.

Logo após o driver ser carregado, o Windows aloca uma estrutura DRIVER_OBJECT que representa o driver. Depois, chama a rotina do ponto de entrada do controlador, DriverEntry, e passa um ponteiro para a estrutura. Como o Windows procura a rotina pelo 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 eventos 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 de 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 que são necessários durante a vida útil do driver. Por exemplo, no código do modelo, o driver do cliente aloca os recursos necessários para o rastreamento de software WPP chamando a macro WPP_INIT_TRACING .

  • Regista certas rotinas de callbacks de eventos no framework.

    Para registar os callbacks de eventos, o driver do cliente primeiro especifica ponteiros para suas próprias implementações das rotinas EvtDriverXxx em determinadas estruturas WDF. Em seguida, o driver chama o método WdfDriverCreate e fornece essas estruturas (discutido na próxima etapa).

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

    Depois que o driver de cliente chama WdfDriverCreate, a estrutura cria um objeto de driver estrutural para representar o driver de cliente. Quando a chamada é concluída, o driver do 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 WDF Driver Object Reference).

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

Após a chamada WdfDriverCreate , a estrutura faz parceria com o driver do cliente para se comunicar com o Windows. A estrutura atua como uma camada de abstração entre o Windows e o driver, e lida com a maioria das tarefas complicadas do driver. O driver cliente se registra com a estrutura para eventos em que o driver está interessado. Quando determinados eventos ocorrem, o Windows notifica a estrutura. Se o driver registrar um callback de evento para um evento específico, o framework notificará o driver invocando o callback 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 prossegue com seu 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 do cliente é carregado (discutido posteriormente neste tópico).

Outras funções de retorno de eventos que o driver pode registrar são EvtDriverUnload, EvtCleanupCallback e EvtDestroyCallback.

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

Quando o Windows estiver pronto para liberar a estrutura DRIVER_OBJECT e descarregar o driver, a estrutura relata esse evento para o driver cliente invocando a implementação EvtCleanupCallback do driver. A estrutura invoca esse retorno de chamada imediatamente antes de excluir o objeto de driver da estrutura. O driver do cliente pode liberar todos os recursos globais que ele alocou em seu DriverEntry. Por exemplo, no código do modelo, o driver do 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 drivers USB, o driver de barramento cria um objeto de dispositivo físico (PDO) para o dispositivo e associa o PDO ao nó do dispositivo. O nó do dispositivo está em uma formação de pilha, onde o DOP está na parte inferior. Cada pilha deve ter um DOP 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 o driver de template.

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

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

O exemplo de código a seguir mostra a implementação do callback do 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 de 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 framework aloca a estrutura WDFDEVICE_INIT e passa um ponteiro para que o driver do cliente possa preencher a estrutura com parâmetros de inicialização para o objeto de dispositivo da framework a ser criado.

Na implementação EvtDriverDeviceAdd , o driver de 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 do cliente deve executar estas tarefas:

    Os componentes do Windows, PnP e gerenciadores de energia, enviam solicitações relacionadas ao dispositivo para drivers em resposta a alterações no estado PnP (como iniciado, interrompido e removido) e no estado de energia (como trabalho ou suspensão). Para drivers baseados em KMDF, a estrutura interceta essas solicitações. O driver do cliente pode ser notificado sobre as solicitações ao registar rotinas de retorno de chamada chamadas retornos de chamada de eventos de energia PnP com o framework, usando a chamada WdfDeviceCreate. Quando os componentes do Windows enviam solicitações, o framework as manipula e chama o callback de evento de energia PnP correspondente, se o driver do cliente estiver registrado.

    Uma das rotinas de retorno de chamada de evento de energia PnP que o driver do cliente deve implementar é EvtDevicePrepareHardware. O callback de evento é chamado quando o gerenciador PnP inicia o dispositivo. A implementação para 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 do cliente) para armazenar informações sobre um objeto de dispositivo específico. O driver do cliente passa um ponteiro para o contexto do dispositivo ao framework. O framework 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 do framework. O driver do 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.

    Após a conclusão da chamada WdfDeviceCreate, o driver cliente recebe um identificador para o novo objeto do dispositivo no framework, que contém um ponteiro para o bloco de memória alocado pelo framework para o contexto do dispositivo. O driver do cliente agora poderá obter um ponteiro para o contexto do dispositivo chamando a macro WdfObjectGet_DEVICE_CONTEXT.

  • Registre um GUID de interface de dispositivo para o driver do 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 do dispositivo representa a instância do dispositivo para o qual o driver do cliente é carregado na memória. O código-fonte completo para o 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 do 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 do cliente e armazena informações sobre um objeto de dispositivo da 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 mais tarde) e um espaço reservado. Esta estrutura será ampliada 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 do cliente pode chamar essa função para recuperar um ponteiro para o bloco de memória do objeto de dispositivo da 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 leva um ponteiro para uma estrutura WDFDEVICE_INIT como parâmetro. Este é o mesmo ponteiro que foi passado pela estrutura quando invocou a implementação EvtDriverDeviceAdd do driver cliente. Basicamente, MyUSBDriver_CreateDevice executa as tarefas de EvtDriverDeviceAdd. O código-fonte para a implementação 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 callback de evento é implementado pelo driver do cliente e executa tarefas, como a configuração do dispositivo USB.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Dispositivo.c

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

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

Na implementação para EvtDevicePrepareHardware, o driver do cliente executa as tarefas de inicialização específicas do USB. Essas tarefas incluem registrar o driver do cliente, inicializar objetos de destino de E/S específicos do 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 (handle) Obtenha controle ao ligar... 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 de cliente pode chamar para selecionar uma configuração alternativa e recuperar informações sobre a configuração.
Objeto de tubo USB de destino (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Representa um pipe individual para um ponto de extremidade definido na configuração alternativa atual para uma interface. A pilha de drivers USB seleciona cada interface na configuração selecionada e configura um canal de comunicação para cada ponto final dentro da interface. Na terminologia USB, esse canal de comunicação é conhecido como pipe.

Este exemplo de código mostra a implementação para 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 do cliente conforme implementado pelo código do modelo:

  1. Especifica a versão do contrato do driver do 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 principal a que o dispositivo USB está conectado. A pilha de drivers USB 3.0 é nova no Windows 8 e suporta vários novos recursos definidos pela especificação USB 3.0, como a capacidade de streams. A nova pilha de drivers também implementa várias melhorias, como um melhor rastreamento e processamento de blocos de solicitação USB (URBs), que estão disponíveis através de um novo conjunto de rotinas URB. Um driver de cliente que pretende usar esses recursos ou chamar as novas rotinas deve especificar a versão USBD_CLIENT_CONTRACT_VERSION_602 contrato. Um driver de 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 do 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 de cliente obteve anteriormente chamando WdfDeviceCreate na implementação do driver de EvtDriverDeviceAdd. O método WdfUsbTargetDeviceCreateWithParameters :

    • Registra o driver do cliente com a pilha de driver USB subjacente.
    • Recupera um identificador WDFUSBDEVICE para o objeto de dispositivo USB de destino criado pelo framework. O código do modelo armazena o identificador para o objeto de dispositivo de destino USB em seu contexto de dispositivo. Usando essa alça, o driver do 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 de contrato de cliente e, portanto, devem pular a Etapa 1.

  3. Seleciona uma configuração USB.

    No código do modelo, o driver do 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 do 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 para WdfUsbTargetDeviceSelectConfigTypeMultiInterface para indicar que, se várias interfaces estiverem disponíveis, uma configuração alternativa em cada uma dessas interfaces deve ser selecionada. Como a chamada deve selecionar a configuração padrão, o driver do cliente especifica NULL no parâmetro SettingPairs e 0 no parâmetro NumberInterfaces . Após a conclusão, o membro MultiInterface.NumberOfConfiguredInterfaces de 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 do cliente quiser selecionar configurações alternativas diferentes da configuração padrão, o driver deve 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 WdfUsbTargetDeviceRetrieveConfigDescritor. O driver do cliente deve então chamar WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES e passar a matriz WDF_USB_INTERFACE_SETTING_PAIR para o framework.

Código-fonte da fila

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

Fila.h

Declara uma rotina de callback de evento para o evento gerado pelo objeto de fila do framework.

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 a função MyUSBDriver_QueueInitialize, a função auxiliar, que cria e inicializa o objeto de fila da estrutura de trabalho.

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 do 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 ficheiro de implementação, Queue.c, contém o seguinte bloco de código que utiliza a pragma alloc_text que especifica que a implementação do driver de MyUSBDriver_QueueInitialize está na memória paginada.

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

WDF fornece o objeto de fila do framework para gerir o fluxo de pedidos para o driver do cliente. A estrutura cria um objeto de fila de framework quando o driver cliente chama o método WdfIoQueueCreate. Nessa chamada, o driver do cliente pode especificar determinadas opções de configuração antes que a estrutura crie filas. Essas opções incluem se a fila utiliza gestão de energia, permite pedidos de tamanho 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 de dispositivo. O driver do cliente pode especificar retornos de chamada de eventos para cada uma dessas solicitações.

O driver do cliente também deve especificar o tipo de despacho. O tipo de distribuição de um objeto de fila determina como o framework entrega pedidos ao driver do cliente. O mecanismo de entrega pode ser sequencial, em paralelo, ou por um mecanismo personalizado definido pelo driver do cliente. Para uma fila sequencial, uma solicitação não é entregue até que o driver do 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 do cliente pode receber uma solicitação enquanto processa outra. No mecanismo personalizado, o cliente extrai manualmente a próxima solicitação do objeto de fila da estrutura quando o driver estiver pronto para processá-la.

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

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 do 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, é gerenciado por energia e recebe solicitações em paralelo.
  2. Adiciona os retornos de chamada de eventos do driver cliente para solicitações de E/S para a fila. No modelo, o driver do 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 do framework que é criado pelo framework.

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 solicitação (IRP) e a encaminha para o driver do cliente. O framework interceta a solicitação, cria um objeto de solicitação do framework e adiciona-o ao objeto de fila do framework. Nesse caso, como o driver do 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 de fila foi criado com o sinalizador WdfIoQueueDispatchParallel, o callback é 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 o framework invoca o callback de evento do driver cliente, ele passa um identificador para o objeto de pedido do framework que contém o pedido (e os seus buffers de entrada e saída) enviado pela aplicação. Além disso, ele envia um identificador para o objeto de fila do framework que contém a solicitação. No caso de retorno de chamada, o driver do cliente processa a solicitação conforme necessário. O código do modelo simplesmente conclui a solicitação. O driver do cliente pode executar mais tarefas envolvidas. Por exemplo, se um aplicativo solicitar determinadas informações do dispositivo, no caso de retorno de chamada, o driver do cliente pode criar uma solicitação de controle USB e enviá-la para a pilha de drivers USB para recuperar as informações do dispositivo solicitadas. As solicitações de controle USB são discutidas em USB Control Transfer.