Gravar um driver de origem HID usando a VHF (Estrutura HID Virtual)

Este tópico explica como:

  • Escreva um driver de origem do KMDF (Kernel-Mode Driver Framework)HID que envie relatórios de leitura HID para o Windows.
  • Carregue o driver VHF como o filtro inferior para o driver de origem HID na pilha de dispositivos HID virtual.

Saiba mais sobre como escrever um driver de origem HID que relata dados HID para o sistema operacional.

Um dispositivo de entrada HID, como um teclado, mouse, caneta, toque ou botão, envia vários relatórios para o sistema operacional para que ele possa entender a finalidade do dispositivo e tomar as medidas necessárias. Os relatórios estão na forma de coleções HID e usos hid. O dispositivo envia esses relatórios por vários transportes, alguns dos quais têm suporte do Windows, como HID por I2C e HID por USB. Em alguns casos, o transporte pode não ter suporte do Windows ou os relatórios podem não ser mapeados diretamente para hardware real. Pode ser um fluxo de dados no formato HID que é enviado por outro componente de software para hardware virtual, como, para botões ou sensores não GPIO. Por exemplo, considere os dados do acelerômetro de um telefone que está se comportando como um controlador de jogo, enviado sem fio para um computador. Em outro exemplo, um computador pode receber entrada remota de um dispositivo Miracast usando o protocolo UIBC.

Em versões anteriores do Windows, para dar suporte a novos transportes (hardware ou software real), você precisava escrever um minidriver de transporte HID e associá-lo ao driver de classe in-box fornecido pela Microsoft, Hidclass.sys. O par de drivers de classe/mini forneceu as coleções HID, como Coleções de Nível Superior para drivers de nível superior e aplicativos de modo de usuário. Nesse modelo, o desafio era escrever o minidriver, que pode ser uma tarefa complexa.

A partir do Windows 10, o novo VHF (Virtual HID Framework) elimina a necessidade de escrever um minidriver de transporte. Em vez disso, você pode escrever um driver de origem HID usando interfaces de programação KMDF ou WDM. A estrutura consiste em uma biblioteca estática fornecida pela Microsoft que expõe elementos de programação usados pelo driver. Ele também inclui um driver in-box fornecido pela Microsoft que enumera um ou mais dispositivos filho e continua a criar e gerenciar uma árvore HID virtual.

Observação

Nesta versão, o VHF dá suporte a um driver de origem HID somente no modo kernel.

Este tópico descreve a arquitetura da estrutura, a árvore de dispositivos HID virtual e os cenários de configuração.

Árvore de dispositivos HID virtual

Nesta imagem, a árvore de dispositivos mostra os drivers e seus objetos de dispositivo associados.

Diagrama de uma árvore de dispositivo HID virtual.

Driver de origem HID (seu driver)

O driver de origem HID é vinculado ao Vhfkm.lib e inclui Vhf.h em seu projeto de build. O driver pode ser escrito usando o WDM (Modelo de Driver do Windows) ou o KMDF (Kernel-Mode Driver Framework) que faz parte do WDF (Windows Driver Frameworks). O driver pode ser carregado como um driver de filtro ou um driver de função na pilha do dispositivo.

Biblioteca estática VHF (vhfkm.lib)

A biblioteca estática está incluída no WDK (Kit de Driver do Windows) para Windows 10. A biblioteca expõe interfaces de programação, como rotinas e funções de retorno de chamada que são usadas pelo driver de origem HID. Quando o driver chama uma função, a biblioteca estática encaminha a solicitação para o driver VHF que manipula a solicitação.

Driver VHF (Vhf.sys)

Um driver interno fornecido pela Microsoft. Esse driver deve ser carregado como um driver de filtro inferior abaixo do driver na pilha do dispositivo de origem HID. O driver VHF enumera dinamicamente dispositivos filho e cria PDO (objetos de dispositivo físico) para um ou mais dispositivos HID especificados pelo driver de origem HID. Ele também implementa a funcionalidade de minigerenciador de transporte HID dos dispositivos filho enumerados.

Par de driver de classe HID (Hidclass.sys, Mshidkmdf.sys)

O par Hidclass/Mshidkmdf enumera TLC (Coleções de Nível Superior) semelhante à forma como enumera essas coleções para um dispositivo HID real. Um cliente HID pode continuar solicitando e consumindo os TLCs como um dispositivo HID real. Esse par de driver é instalado como o driver de função na pilha do dispositivo.

Observação

Em alguns cenários, um cliente HID pode precisar identificar a origem dos dados HID. Por exemplo, um sistema tem um sensor interno e recebe dados de um sensor remoto do mesmo tipo. O sistema pode querer escolher um sensor para ser mais confiável. Para diferenciar entre os dois sensores conectados ao sistema, o cliente HID consulta a ID do contêiner do TLC. Nesse caso, um driver de origem HID pode fornecer a ID do contêiner, que é relatada como a ID de contêiner do dispositivo HID virtual pelo VHF.

Cliente HID (aplicativo)

Consulta e consome os TLCs relatados pela pilha de dispositivos HID.

Requisitos de cabeçalho e biblioteca

Este procedimento descreve como escrever um driver de origem HID simples que relata botões de fone de ouvido para o sistema operacional. Nesse caso, o driver que implementa esse código pode ser um driver de áudio KMDF existente que foi modificado para atuar como um fone de ouvido de relatório de origem HID usando VHF.

  1. Inclua Vhf.h, incluído no WDK para Windows 10.

  2. Link para vhfkm.lib, incluído no WDK.

  3. Crie um Descritor de Relatório HID que seu dispositivo deseja relatar ao sistema operacional. Neste exemplo, o Descritor de Relatório HID descreve os botões do headset. O relatório especifica um Relatório de Entrada HID, tamanho 8 bits (1 byte). Os três primeiros bits são para os botões do meio do fone de ouvido, do volume para cima e para baixo do volume. Os bits restantes não são utilizados.

    UCHAR HeadSetReportDescriptor[] = {
        0x05, 0x01,         // USAGE_PAGE (Generic Desktop Controls)
        0x09, 0x0D,         // USAGE (Portable Device Buttons)
        0xA1, 0x01,         // COLLECTION (Application)
        0x85, 0x01,         //   REPORT_ID (1)
        0x05, 0x09,         //   USAGE_PAGE (Button Page)
        0x09, 0x01,         //   USAGE (Button 1 - HeadSet : middle button)
        0x09, 0x02,         //   USAGE (Button 2 - HeadSet : volume up button)
        0x09, 0x03,         //   USAGE (Button 3 - HeadSet : volume down button)
        0x15, 0x00,         //   LOGICAL_MINIMUM (0)
        0x25, 0x01,         //   LOGICAL_MAXIMUM (1)
        0x75, 0x01,         //   REPORT_SIZE (1)
        0x95, 0x03,         //   REPORT_COUNT (3)
        0x81, 0x02,         //   INPUT (Data,Var,Abs)
        0x95, 0x05,         //   REPORT_COUNT (5)
        0x81, 0x03,         //   INPUT (Cnst,Var,Abs)
        0xC0,               // END_COLLECTION
    };
    

Criar um dispositivo HID virtual

Inicialize uma estrutura VHF_CONFIG chamando a macro VHF_CONFIG_INIT e, em seguida, chame o método VhfCreate . O driver deve chamar VhfCreate em PASSIVE_LEVEL após a chamada WdfDeviceCreate , normalmente, na função de retorno de chamada EvtDriverDeviceAdd do driver.

Na chamada VhfCreate , o driver pode especificar determinadas opções de configuração, como operações que devem ser processadas de forma assíncrona ou definir informações do dispositivo (IDs de fornecedor/produto).

Por exemplo, um aplicativo solicita um TLC. Quando o par de driver de classe HID recebe essa solicitação, o par determina o tipo de solicitação e cria uma solicitação IOCTL do Minidriver HID apropriada e a encaminha para o VHF. Ao obter a solicitação IOCTL, o VHF pode lidar com a solicitação, contar com o driver de origem HID para processá-la ou concluir a solicitação com STATUS_NOT_SUPPORTED.

O VHF lida com essas IOCTLs:

Se a solicitação for GetFeature, SetFeature, WriteReport ou GetInputReport e o driver de origem HID tiver registrado uma função de retorno de chamada correspondente, o VHF invocará a função de retorno de chamada. Dentro dessa função, o driver de origem HID pode obter ou definir dados HID para o dispositivo virtual HID. Se o driver não registrar um retorno de chamada, o VHF concluirá a solicitação com status STATUS_NOT_SUPPORTED.

O VHF invoca funções de retorno de chamada de evento implementadas pelo driver de origem HID para estas IOCTLs:

Para qualquer outro IOCTL do Minidriver HID, o VHF conclui a solicitação com STATUS_NOT_SUPPORTED.

O dispositivo HID virtual é excluído chamando o VhfDelete. O retorno de chamada EvtVhfCleanup será necessário se o driver alocar recursos para o dispositivo HID virtual. O driver deve implementar a função EvtVhfCleanup e especificar um ponteiro para essa função no membro EvtVhfCleanup de VHF_CONFIG. EvtVhfCleanup é invocado antes da conclusão da chamada VhfDelete . Para obter mais informações, consulte Excluir o dispositivo HID virtual.

Observação

Após a conclusão de uma operação assíncrona, o driver deve chamar VhfAsyncOperationComplete para definir os resultados da operação. Você pode chamar o método do retorno de chamada de evento ou posteriormente depois de retornar do retorno de chamada.

NTSTATUS
VhfSourceCreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
)

{
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    VHF_CONFIG vhfConfig;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
    deviceAttributes.EvtCleanupCallback = VhfSourceDeviceCleanup;

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

    if (NT_SUCCESS(status))
    {
        deviceContext = DeviceGetContext(device);

        VHF_CONFIG_INIT(&vhfConfig,
            WdfDeviceWdmGetDeviceObject(device),
            sizeof(VhfHeadSetReportDescriptor),
            VhfHeadSetReportDescriptor);

        status = VhfCreate(&vhfConfig, &deviceContext->VhfHandle);

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "VhfCreate failed %!STATUS!", status);
            goto Error;
        }

        status = VhfStart(deviceContext->VhfHandle);
        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "VhfStart failed %!STATUS!", status);
            goto Error;
        }

    }

Error:
    return status;
}

Enviar o relatório de entrada HID

Envie o relatório de entrada HID chamando VhfReadReportSubmit.

Normalmente, um dispositivo HID envia informações sobre alterações de estado enviando relatórios de entrada por meio de interrupções. Por exemplo, o dispositivo de headset pode enviar um relatório quando o estado de um botão é alterado. Nesse caso, a ISR (rotina de serviço de interrupção) do driver é invocada. Nessa rotina, o driver pode agendar uma DPC (chamada de procedimento adiado) que processa o relatório de entrada e o envia ao VHF, que envia as informações para o sistema operacional. Por padrão, o VHF armazena em buffer o relatório e o driver de origem HID pode começar a enviar relatórios de entrada HID conforme eles entram. Isso e elimina a necessidade de o driver de origem HID implementar a sincronização complexa.

O driver de origem HID pode enviar relatórios de entrada implementando a política de buffer para relatórios pendentes. Para evitar buffer duplicado, o driver de origem HID pode implementar a função de retorno de chamada EvtVhfReadyForNextReadReport e controlar se o VHF invocou esse retorno de chamada. Se ele tiver sido invocado anteriormente, o driver de origem HID poderá chamar VhfReadReportSubmit para enviar um relatório. Ele deve aguardar que EvtVhfReadyForNextReadReport seja invocado antes de poder chamar VhfReadReportSubmit novamente.

VOID
MY_SubmitReadReport(
    PMY_CONTEXT  Context,
    BUTTON_TYPE  ButtonType,
    BUTTON_STATE ButtonState
    )
{
    PDEVICE_CONTEXT deviceContext = (PDEVICE_CONTEXT)(Context);

    if (ButtonState == ButtonStateUp) {
        deviceContext->VhfHidReport.ReportBuffer[0] &= ~(0x01 << ButtonType);
    } else {
        deviceContext->VhfHidReport.ReportBuffer[0] |=  (0x01 << ButtonType);
    }

    status = VhfReadReportSubmit(deviceContext->VhfHandle, &deviceContext->VhfHidReport);

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,"VhfReadReportSubmit failed %!STATUS!", status);
    }
}

Excluir o dispositivo HID virtual

Exclua o dispositivo HID virtual chamando VhfDelete.

VhfDelete pode ser chamado de síncrono ou assíncrono especificando o parâmetro Wait. Para uma chamada síncrona, o método deve ser chamado em PASSIVE_LEVEL, como de EvtCleanupCallback do objeto do dispositivo. VhfDelete retorna depois de excluir o dispositivo HID virtual. Se o driver chamar VhfDelete de forma assíncrona, ele retornará imediatamente e o VHF invocará EvtVhfCleanup após a conclusão da operação de exclusão. O método pode ser chamado no máximo DISPATCH_LEVEL. Nesse caso, o driver deve ter registrado e implementado uma função de retorno de chamada EvtVhfCleanup quando chamado anteriormente de VhfCreate. Esta é a sequência de eventos em que o driver de origem HID deseja excluir o dispositivo HID virtual:

  1. O driver de origem HID para de iniciar chamadas no VHF.
  2. A origem HID chama VhfDelete com Wait definido como FALSE.
  3. O VHF para de invocar funções de retorno de chamada implementadas pelo driver de origem HID.
  4. O VHF começa a relatar o dispositivo como ausente para o Gerenciador de PnP. Neste ponto, a chamada VhfDelete pode retornar.
  5. Quando o dispositivo é relatado como um dispositivo ausente, o VHF invoca EvtVhfCleanup se o driver de origem HID registrou sua implementação.
  6. Depois que EvtVhfCleanup retorna, o VHF executa sua limpeza.
VOID
VhfSourceDeviceCleanup(
_In_ WDFOBJECT DeviceObject
)
{
    PDEVICE_CONTEXT deviceContext;

    PAGED_CODE();

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

    deviceContext = DeviceGetContext(DeviceObject);

    if (deviceContext->VhfHandle != WDF_NO_HANDLE)
    {
        VhfDelete(deviceContext->VhfHandle, TRUE);
    }

}

Instalar o driver de origem HID

No arquivo INF que instala o driver de origem HID, declare Vhf.sys como um driver de filtro inferior para o driver de origem HID usando a Diretiva AddReg.

[HIDVHF_Inst.NT.HW]
AddReg = HIDVHF_Inst.NT.AddReg

[HIDVHF_Inst.NT.AddReg]
HKR,,"LowerFilters",0x00010000,"vhf"