Condividi tramite


Scrivere un driver di origine HID usando Virtual HID Framework (VHF)

Questo articolo spiega come:

  • Scrivere un driver sorgente HID del Driver Framework Kernel-Mode (KMDF) che invia report di lettura HID a Windows.
  • Carica il driver VHF come filtro di basso livello per il driver di origine HID nello stack di dispositivi HID virtuali.

Informazioni sulla scrittura di un driver di origine HID che segnala i dati HID al sistema operativo.

Un dispositivo di input HID, ad esempio una tastiera, un mouse, una penna, un tocco o un pulsante, invia vari report al sistema operativo in modo che possa comprendere lo scopo del dispositivo e intraprendere le azioni necessarie. I rapporti sono sotto forma di raccolte HID e usi HID . Il dispositivo invia tali report su vari trasporti, alcuni dei quali Windows supporta, ad esempio HID su I2C e HID su USB. In alcuni casi, Windows non supporta il trasporto o i report non vengono mappati direttamente all'hardware reale. Può trattarsi di un flusso di dati nel formato HID inviato da un altro componente software per l'hardware virtuale, ad esempio per pulsanti o sensori non GPIO. Si considerino, ad esempio, i dati dell'accelerometro di un telefono che si comportano come controller di gioco, inviati in modalità wireless a un PC. In un altro esempio, un computer può ricevere l'input remoto da un dispositivo Miracast usando il protocollo UIBC.

Nelle versioni precedenti di Windows, per supportare nuovi trasporti (hardware reale o software), è necessario scrivere un minidriver di trasporto HID e associarlo al driver della classe in-box fornito da Microsoft, Hidclass.sys. La coppia di driver classe/mini ha fornito le raccolte HID, come le raccolteTop-Level, ai driver superiori e alle applicazioni in modalità utente. In questo modello, la sfida era scrivere il minidriver, che può essere un'attività complessa.

A partire da Windows 10, il nuovo VHF (Virtual HID Framework) elimina la necessità di scrivere un minidriver di trasporto. È invece possibile scrivere un driver di origine HID usando KMDF o interfacce di programmazione WDM. Il framework è costituito da una libreria statica fornita da Microsoft che espone gli elementi di programmazione usati dal driver. Include anche un driver integrato fornito da Microsoft che enumera uno o più dispositivi figlio e procede alla compilazione e alla gestione di un albero HID virtuale.

Nota

In questa versione VHF supporta un driver di origine HID solo in modalità kernel.

Questo articolo descrive l'architettura del framework, l'albero dei dispositivi HID virtuale e gli scenari di configurazione.

Albero dei dispositivi HID virtuale

In questa immagine l'albero dei dispositivi mostra i driver e i relativi oggetti dispositivo associati.

Diagramma di un albero di dispositivi HID virtuale.

Driver di origine HID (il tuo driver)

Il driver di origine HID si collega a Vhfkm.lib e include Vhf.h nel progetto di compilazione. Il driver può essere scritto usando Windows Driver Model (WDM) o Kernel-Mode Driver Framework (KMDF) che fa parte di Windows Driver Framework (WDF). Il driver può essere caricato come driver di filtro o driver di funzione nello stack di dispositivi.

Libreria statica VHF (vhfkm.lib)

La libreria statica è inclusa in Windows Driver Kit (WDK) per Windows 10. La libreria espone interfacce di programmazione come routine e funzioni di callback usate dal driver di origine HID. Quando il driver chiama una funzione, la libreria statica inoltra la richiesta al driver VHF che gestisce la richiesta.

Driver VHF (Vhf.sys)

Driver di sistema fornito da Microsoft. Questo driver deve essere caricato come driver di filtro inferiore sotto il driver nello stack di dispositivi di origine HID. Il driver VHF enumera dinamicamente i sottodispositivi e crea oggetti dispositivo fisico (PDO) per uno o più dispositivi HID specificati dal driver di origine HID. Implementa inoltre la funzionalità mini-driver del trasporto HID dei dispositivi secondari enumerati.

Insieme di driver della classe HID (Hidclass.sys, Mshidkmdf.sys)

La coppia Hidclass/Mshidkmdf enumera Top-Level Collections (TLC) in modo analogo a come enumera tali raccolte per un dispositivo HID reale. Un client HID può continuare a richiedere e consumare i TLC proprio come farebbe un dispositivo HID reale. Questa coppia di driver viene installata come driver di funzione nello stack di dispositivi.

Nota

In alcuni scenari, un client HID potrebbe dover identificare l'origine dei dati HID. Ad esempio, un sistema ha un sensore predefinito e riceve i dati da un sensore remoto dello stesso tipo. Il sistema potrebbe voler scegliere un sensore per essere più affidabile. Per distinguere i due sensori connessi al sistema, il client HID esegue una query per l'ID contenitore del TLC. In questo caso, un driver di origine HID può fornire l'ID contenitore, il quale è riportato da VHF come ID contenitore del dispositivo HID virtuale.

Client HID (applicazione)

Interroga e utilizza i TLC segnalati dallo stack di dispositivi HID.

Requisiti di intestazione e libreria

Questa procedura descrive come scrivere un driver di origine HID che segnala i pulsanti del visore VR al sistema operativo. In questo caso, il driver che implementa questo codice può essere un driver audio KMDF esistente e modificato, fungendo da sorgente HID per la segnalazione dei pulsanti delle cuffie utilizzando VHF.

  1. Includi Vhf.h dal WDK.

  2. Collegamento a vhfkm.lib incluso nel WDK.

  3. Creare un descrittore di report HID che il dispositivo vuole segnalare al sistema operativo. In questo esempio, il descrittore di report HID descrive i pulsanti delle cuffie. Il report specifica un report di input HID, dimensione 8 bit (1 byte). I primi tre bit sono per i pulsanti centrale del visore, l'aumento del volume e la diminuzione del volume. I bit rimanenti sono inutilizzati.

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

Creare un dispositivo HID virtuale

Inizializzare una struttura VHF_CONFIG chiamando la macro VHF_CONFIG_INIT e quindi chiamare il metodo VhfCreate . Il driver deve chiamare VhfCreate in PASSIVE_LEVEL dopo la chiamata WdfDeviceCreate , in genere, nella funzione di callback EvtDriverDeviceAdd del driver.

Nella chiamata VhfCreate il driver può specificare determinate opzioni di configurazione, ad esempio le operazioni che devono essere elaborate in modo asincrono o impostando le informazioni sul dispositivo (ID fornitore/prodotto).

Ad esempio, un'applicazione richiede un TLC. Quando la coppia di driver di classe HID riceve tale richiesta, la coppia determina il tipo di richiesta e crea una richiesta IOCTL HID Minidriver appropriata, inoltrandola a VHF. Dopo aver ottenuto la richiesta IOCTL, VHF può gestire la richiesta, affidarsi al driver di origine HID per elaborarla o completare la richiesta con STATUS_NOT_SUPPORTED.

VHF gestisce questi IOCTLs:

Se la richiesta è GetFeature, SetFeature, WriteReport o GetInputReport e il driver di origine HID ha registrato una funzione di callback corrispondente, VHF richiama la funzione di callback. All'interno di tale funzione, il driver di origine HID può ottenere o impostare i dati HID per il dispositivo virtuale HID. Se il driver non registra un callback, VHF completa la richiesta con lo stato STATUS_NOT_SUPPORTED.

VHF richiama le funzioni di callback degli eventi implementate dal driver sorgente HID per questi IOCTL:

Per qualsiasi altro minidriver IOCTL HID, VHF completa la richiesta con STATUS_NOT_SUPPORTED.

Il dispositivo HID virtuale viene eliminato chiamando VhfDelete. Il callback EvtVhfCleanup è necessario se il driver ha allocato le risorse per il dispositivo HID virtuale. Il driver deve implementare la funzione EvtVhfCleanup e specificare un puntatore a tale funzione nel membro EvtVhfCleanup di VHF_CONFIG. EvtVhfCleanup viene richiamato prima del completamento della chiamata VhfDelete . Per altre informazioni, vedere Eliminare il dispositivo HID virtuale.

Nota

Al termine di un'operazione asincrona, il driver deve chiamare VhfAsyncOperationComplete per impostare i risultati dell'operazione. È possibile chiamare il metodo dal callback dell'evento o in un secondo momento dopo essere ritornati dal callback.

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

Inviare il rapporto di input HID

Inviare il report di input HID chiamando VhfReadReportSubmit.

Generalmente, un dispositivo HID invia informazioni sulle modifiche di stato attraverso la trasmissione di report di input tramite interrupt. Ad esempio, il dispositivo vr potrebbe inviare un report quando lo stato di un pulsante cambia. In questo caso, la routine del servizio interrupt (ISR) del driver viene richiamata. In tale routine, il driver potrebbe pianificare una chiamata di procedura posticipata (DPC) che elabora il report di input e lo invia a VHF, che invia le informazioni al sistema operativo. Per impostazione predefinita, VHF memorizza nel buffer il report e il driver di origine HID può iniziare a inviare i report di input HID man mano che arrivano. Questo buffering elimina la necessità del driver di origine HID di implementare una sincronizzazione complessa.

Il driver sorgente HID può inviare report di input implementando i criteri di buffering per i report in sospeso. Per evitare il buffering duplicato, il driver di origine HID può implementare la funzione di callback EvtVhfReadyForNextReadReport e tenere traccia del fatto che VHF abbia richiamato questo callback. Se è stato richiamato in precedenza, il driver di origine HID può chiamare VhfReadReportSubmit per inviare un report. Deve attendere che EvtVhfReadyForNextReadReport venga richiamato prima di poter chiamare nuovamente VhfReadReportSubmit .

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

Eliminare il dispositivo HID virtuale

Eliminare il dispositivo HID virtuale chiamando VhfDelete.

VhfDelete può essere chiamato sincrono o in modo asincrono specificando il parametro Wait. Per una chiamata sincrona, il metodo deve essere chiamato in PASSIVE_LEVEL, ad esempio da EvtCleanupCallback dell'oggetto dispositivo. VhfDelete restituisce dopo l'eliminazione del dispositivo HID virtuale. Se il driver chiama VhfDelete in modo asincrono, restituisce immediatamente e VHF richiama EvtVhfCleanup al termine dell'operazione di eliminazione. Il metodo può essere chiamato al massimo DISPATCH_LEVEL. In questo caso, il driver doveva registrare e implementare una funzione di callback EvtVhfCleanup quando in precedenza si chiamava VhfCreate. Ecco la sequenza di eventi quando il driver di origine HID vuole eliminare il dispositivo HID virtuale:

  1. Il driver di origine HID interrompe l'avvio delle chiamate in VHF.
  2. L'origine HID chiama VhfDelete con Wait impostato su FALSE.
  3. VHF smette di richiamare le funzioni di callback implementate dal driver di origine HID.
  4. VHF inizia a segnalare il dispositivo come mancante al PnP Manager. A questo punto, la chiamata VhfDelete potrebbe restituire.
  5. Quando il dispositivo viene segnalato come dispositivo mancante, VHF richiama EvtVhfCleanup se il driver di origine HID ha registrato l'implementazione.
  6. Dopo che EvtVhfCleanup restituisce, VHF esegue il processo di pulizia.
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);
    }
}

Installare il driver di origine HID

Nel file INF che installa il driver di origine HID assicurarsi di dichiarare Vhf.sys come driver di filtro inferiore al driver di origine HID usando la direttiva AddReg.

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

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