Escribir un controlador de origen HID mediante Virtual HID Framework (VHF)

En este tema se explican los procedimientos para:

  • Escriba un controlador de origen Kernel-Mode Driver Framework (KMDF)HID que envíe informes de lectura hid a Windows.
  • Cargue el controlador VHF como filtro inferior en el controlador de origen HID en la pila de dispositivos HID virtual.

Obtenga información sobre cómo escribir un controlador de origen HID que informe de datos HID al sistema operativo.

Un dispositivo de entrada HID, como : un teclado, mouse, lápiz, entrada táctil o botón, envía varios informes al sistema operativo para que pueda comprender el propósito del dispositivo y tomar las medidas necesarias. Los informes están en forma de colecciones HID y usos de HID. El dispositivo envía esos informes a través de varios transportes, algunos de los cuales son compatibles con Windows, como HID a través de I2C y HID a través de USB. En algunos casos, es posible que Windows no admita el transporte o que los informes no se asignen directamente al hardware real. Puede ser un flujo de datos en formato HID enviado por otro componente de software para hardware virtual como, por ejemplo, para los botones o sensores que no son GPIO. Por ejemplo, considere los datos del acelerómetro de un teléfono que se comporta como un controlador de juego, enviado de forma inalámbrica a un EQUIPO. En otro ejemplo, un equipo puede recibir entradas remotas desde un dispositivo Miracast mediante el protocolo UIBC.

En versiones anteriores de Windows, para admitir nuevos transportes (hardware o software real), tenía que escribir un minidriver de transporte HID y enlazarlo al controlador de clase integrada proporcionado por Microsoft, Hidclass.sys. El par de controladores de clase/mini proporcionó las colecciones HID, como colecciones de nivel superior a controladores de nivel superior y aplicaciones en modo de usuario. En ese modelo, el desafío era escribir el minidriver, que puede ser una tarea compleja.

A partir de Windows 10, el nuevo marco HID virtual (VHF) elimina la necesidad de escribir un minidriver de transporte. En su lugar, puede escribir un controlador de origen HID mediante interfaces de programación KMDF o WDM. El marco consta de una biblioteca estática proporcionada por Microsoft que expone los elementos de programación usados por el controlador. También incluye un controlador integrada proporcionado por Microsoft que enumera uno o varios dispositivos secundarios y continúa con la compilación y administración de un árbol HID virtual.

Nota

En esta versión, VHF solo admite un controlador de origen HID en modo kernel.

En este tema se describe la arquitectura del marco, el árbol de dispositivos HID virtuales y los escenarios de configuración.

Árbol de dispositivos HID virtuales

En esta imagen, el árbol de dispositivos muestra los controladores y sus objetos de dispositivo asociados.

Diagrama de un árbol de dispositivo HID virtual.

Controlador de origen HID (el controlador)

El controlador de origen HID se vincula a Vhfkm.lib e incluye Vhf.h en su proyecto de compilación. El controlador se puede escribir mediante windows Driver Model (WDM) o Kernel-Mode Driver Framework (KMDF) que forma parte de Windows Driver Frameworks (WDF). El controlador se puede cargar como un controlador de filtro o un controlador de función en la pila de dispositivos.

Biblioteca estática VHF (vhfkm.lib)

La biblioteca estática se incluye en el Kit de controladores de Windows (WDK) para Windows 10. La biblioteca expone interfaces de programación como rutinas y funciones de devolución de llamada que usa el controlador de origen HID. Cuando el controlador llama a una función, la biblioteca estática reenvía la solicitud al controlador VHF que controla la solicitud.

Controlador VHF (Vhf.sys)

Un controlador in-box proporcionado por Microsoft. Este controlador debe cargarse como un controlador de filtro inferior debajo del controlador en la pila de dispositivos de origen HID. El controlador VHF enumera dinámicamente los dispositivos secundarios y crea objetos de dispositivo físico (PDO) para uno o varios dispositivos HID especificados por el controlador de origen HID. También implementa la funcionalidad de mini driver HID Transport de los dispositivos secundarios enumerados.

Par de controladores de clase HID (Hidclass.sys, Mshidkmdf.sys)

El par Hidclass/Mshidkmdf enumera colecciones de nivel superior (TLC) similar a cómo enumera esas colecciones para un dispositivo HID real. Un cliente HID puede seguir solicitando y consumir los TLC igual que un dispositivo HID real. Este par de controladores se instala como controlador de función en la pila de dispositivos.

Nota

En algunos escenarios, es posible que un cliente HID necesite identificar el origen de datos HID. Por ejemplo, un sistema tiene un sensor integrado y recibe datos de un sensor remoto del mismo tipo. Es posible que el sistema quiera elegir un sensor para que sea más confiable. Para diferenciar entre los dos sensores conectados al sistema, el cliente HID consulta el identificador de contenedor del TLC. En este caso, un controlador de origen HID puede proporcionar el identificador de contenedor, que se notifica como el identificador de contenedor del dispositivo HID virtual por VHF.

Cliente HID (aplicación)

Consulta y consume los TLC notificados por la pila de dispositivos HID.

Requisitos de encabezado y biblioteca

En este procedimiento se describe cómo escribir un controlador de origen HID simple que informa de los botones de auriculares al sistema operativo. En este caso, el controlador que implementa este código puede ser un controlador de audio KMDF existente que se ha modificado para actuar como botones de auriculares de informes de origen HID mediante VHF.

  1. Incluya Vhf.h, incluido en el WDK para Windows 10.

  2. Vínculo a vhfkm.lib, incluido en el WDK.

  3. Cree un descriptor de informe HID que el dispositivo quiera informar al sistema operativo. En este ejemplo, el descriptor de informe HID describe los botones de auriculares. El informe especifica un informe de entrada HID, tamaño de 8 bits (1 byte). Los tres primeros bits son para los botones middle, volume-up y volume-down del casco. Los bits restantes no se usan.

    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
    };
    

Creación de un dispositivo HID virtual

Inicialice una estructura de VHF_CONFIG llamando a la macro VHF_CONFIG_INIT y, a continuación, llame al método VhfCreate . El controlador debe llamar a VhfCreate en PASSIVE_LEVEL después de la llamada a WdfDeviceCreate , normalmente, en la función de devolución de llamada EvtDriverDeviceAdd del controlador.

En la llamada VhfCreate , el controlador puede especificar determinadas opciones de configuración, como las operaciones que se deben procesar de forma asincrónica o establecer la información del dispositivo (identificadores de proveedor o producto).

Por ejemplo, una aplicación solicita un TLC. Cuando el par de controladores de clase HID recibe esa solicitud, el par determina el tipo de solicitud y crea una solicitud IOCTL de Minidriver HID adecuada y la reenvía a VHF. Al obtener la solicitud IOCTL, VHF puede controlar la solicitud, confiar en el controlador de origen HID para procesarla o completar la solicitud con STATUS_NOT_SUPPORTED.

VHF controla estas IOCTLs:

Si la solicitud es GetFeature, SetFeature, WriteReport o GetInputReport, y el controlador de origen HID registró una función de devolución de llamada correspondiente, VHF invoca la función de devolución de llamada. Dentro de esa función, el controlador de origen HID puede obtener o establecer datos HID para el dispositivo virtual HID. Si el controlador no registra una devolución de llamada, VHF completa la solicitud con el estado STATUS_NOT_SUPPORTED.

VHF invoca funciones de devolución de llamada de eventos implementadas por el controlador de origen HID para estas IOCTLs:

Para cualquier otro IOCTL de Minidriver HID, VHF completa la solicitud con STATUS_NOT_SUPPORTED.

El dispositivo HID virtual se elimina llamando a VhfDelete. La devolución de llamada EvtVhfCleanup es necesaria si el controlador asignó recursos para el dispositivo HID virtual. El controlador debe implementar la función EvtVhfCleanup y especificar un puntero a esa función en el miembro EvtVhfCleanup de VHF_CONFIG. Se invoca EvtVhfCleanup antes de que se complete la llamada de VhfDelete . Para más información, consulte Eliminación del dispositivo HID virtual.

Nota

Una vez completada una operación asincrónica, el controlador debe llamar a VhfAsyncOperationComplete para establecer los resultados de la operación. Puede llamar al método desde la devolución de llamada del evento o en un momento posterior después de volver desde la devolución de llamada.

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;
}

Envío del informe de entrada HID

Envíe el informe de entrada HID llamando a VhfReadReportSubmit.

Normalmente, un dispositivo HID envía información sobre los cambios de estado mediante el envío de informes de entrada a través de interrupciones. Por ejemplo, el dispositivo de auriculares podría enviar un informe cuando cambia el estado de un botón. En tal caso, se invoca la rutina de servicio de interrupción (ISR) del controlador. En esa rutina, el controlador podría programar una llamada a procedimiento diferido (DPC) que procesa el informe de entrada y la envía a VHF, que envía la información al sistema operativo. De forma predeterminada, VHF almacena en búfer el informe y el controlador de origen HID pueden empezar a enviar informes de entrada HID a medida que entran. Esto y elimina la necesidad de que el controlador de origen HID implemente una sincronización compleja.

El controlador de origen HID puede enviar informes de entrada mediante la implementación de la directiva de almacenamiento en búfer para los informes pendientes. Para evitar el almacenamiento en búfer duplicado, el controlador de origen HID puede implementar la función de devolución de llamada EvtVhfReadyForNextReadReport y realizar un seguimiento de si VHF invocó esta devolución de llamada. Si se invocó anteriormente, el controlador de origen HID puede llamar a VhfReadReportSubmit para enviar un informe. Debe esperar a que EvtVhfReadyForNextReadReport se invoque para poder llamar a VhfReadReportSubmit de nuevo.

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);
    }
}

Eliminación del dispositivo HID virtual

Elimine el dispositivo HID virtual llamando a VhfDelete.

VhfDelete se puede llamar de forma sincrónica o asincrónica especificando el parámetro Wait. Para una llamada sincrónica, se debe llamar al método en PASSIVE_LEVEL, como desde EvtCleanupCallback del objeto de dispositivo. VhfDelete devuelve después de eliminar el dispositivo HID virtual. Si el controlador llama a VhfDelete de forma asincrónica, devuelve inmediatamente y VHF invoca EvtVhfCleanup una vez completada la operación de eliminación. Se puede llamar al método al máximo DISPATCH_LEVEL. En este caso, el controlador debe haber registrado e implementado una función de devolución de llamada EvtVhfCleanup cuando anteriormente llamó a VhfCreate. Esta es la secuencia de eventos cuando el controlador de origen HID desea eliminar el dispositivo HID virtual:

  1. El controlador de origen HID deja de iniciar llamadas a VHF.
  2. El origen HID llama a VhfDelete con Wait establecido en FALSE.
  3. VHF deja de invocar funciones de devolución de llamada implementadas por el controlador de origen HID.
  4. VHF inicia la notificación del dispositivo como falta en el Administrador de PnP. En este momento, la llamada de VhfDelete podría devolverse.
  5. Cuando el dispositivo se notifica como un dispositivo que falta, VHF invoca EvtVhfCleanup si el controlador de origen HID registró su implementación.
  6. Después de que EvtVhfCleanup vuelva, VHF realiza su limpieza.
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);
    }

}

Instalación del controlador de origen HID

En el archivo INF que instala el controlador de origen HID, asegúrese de declarar Vhf.sys como controlador de filtro inferior al controlador de origen HID mediante la Directiva AddReg.

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

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