Scrivere un driver di origine HID usando Virtual HID Framework (VHF)
In questo argomento viene descritto come:
- Scrivere un driver di origine HID (KMDF) Kernel-Mode Driver Framework (KMDF) che invia report di lettura HID a Windows.
- Caricare il driver VHF come filtro inferiore al driver di origine HID nello stack di dispositivi HID virtuale.
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 azioni necessarie. I report sono sotto forma di raccolte HID e utilizzo HID. Il dispositivo invia tali report su vari trasporti, alcuni dei quali supportati da Windows, ad esempio HID su I2C e HID tramite USB. In alcuni casi, il trasporto potrebbe non essere supportato da Windows o i report potrebbero non essere mappati direttamente all'hardware reale. Può essere un flusso di dati nel formato HID inviato da un altro componente software per hardware virtuale, ad esempio, per pulsanti non GPIO o sensori. Si considerino, ad esempio, i dati dell'accelerometro da un telefono che si comporta 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 di classe in box fornito da Microsoft, Hidclass.sys. La coppia di driver di classe/mini ha fornito le raccolte HID, ad esempio Raccolte di primo livello a driver di livello superiore e applicazioni in modalità utente. In questo modello la sfida è stata la scrittura del 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 le interfacce di programmazione KMDF o WDM. Il framework è costituito da una libreria statica fornita da Microsoft che espone gli elementi di programmazione usati dal driver. Include anche un driver in box 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 argomento 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.
Driver di origine HID (driver)
Il driver di origine HID collega 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, ad esempio 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 in box 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 dispositivi figlio e crea oggetti dispositivo fisici (PDO) per uno o più dispositivi HID specificati dal driver di origine HID. Implementa anche la funzionalità mini-driver di trasporto HID dei dispositivi figlio enumerati.
Coppia di driver di classe HID (Hidclass.sys, Mshidkmdf.sys)
La coppia Hidclass/Mshidkmdf enumera raccolte di primo livello (TLC) simili a come enumera tali raccolte per un dispositivo HID reale. Un client HID può continuare a richiedere e utilizzare le TLC come 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 dispone di 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 tra i due sensori connessi al sistema, il client HID esegue query per l'ID contenitore del TLC. In questo caso, un driver di origine HID può fornire l'ID contenitore, segnalato come ID contenitore del dispositivo HID virtuale da VHF.
Client HID (applicazione)
Query e utilizza le TLC segnalate dallo stack di dispositivi HID.
Requisiti di intestazione e libreria
Questa procedura descrive come scrivere un semplice driver di origine HID che segnala i pulsanti visore al sistema operativo. In questo caso, il driver che implementa questo codice può essere un driver audio kmDF esistente che è stato modificato per fungere da pulsanti visori di creazione di report di origine HID usando VHF.
Includere Vhf.h, incluso in WDK per Windows 10.
Collegamento a vhfkm.lib, incluso in WDK.
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 visore. Il report specifica un report di input HID, dimensione 8 bit (1 byte). I primi tre bit sono per i pulsanti centro visore, volume-up e volume-down. 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 VhfCrea in PASSIVE_LEVEL dopo la chiamata WdfDeviceCreate , in genere, nella funzione evtDriverDeviceAdd callback 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 appropriata e la inoltra a VHF. Dopo aver ottenuto la richiesta IOCTL, VHF può gestire la richiesta, basarsi sul driver di origine HID per elaborarlo o completare la richiesta con STATUS_NOT_SUPPORTED.
VHF gestisce questi IOCTLs:
- IOCTL_HID_GET_STRING
- IOCTL_HID_GET_DEVICE_ATTRIBUTES
- IOCTL_HID_GET_DEVICE_DESCRIPTOR
- IOCTL_HID_GET_REPORT_DESCRIPTOR
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 stato STATUS_NOT_SUPPORTED.
VHF richiama le funzioni di callback del driver di origine HID implementate per questi IOCTL:
-
Se il driver vuole gestire i criteri di buffering durante l'invio di un buffer per ottenere il report di input HID, deve implementare EvtVhfReadyForNextReadReport e specificare un puntatore nel membro EvtVhfAsyncOperationGetInputReport . Per altre informazioni, vedere Inviare il report di input HID.
IOCTL_HID_GET_FEATURE o IOCTL_HID_SET_FEATURE
Se il driver vuole ottenere o impostare un report delle funzionalità HID in modo asincrono, il driver deve implementare la funzione EvtVhfAsyncOperation e specificare un puntatore alla funzione di implementazione get o set nella funzione di implementazione EvtVhfAsyncOperationGetFeature o EvtVhFAsyncOperationSetFeature membro di VHF_CONFIG.
-
Se il driver vuole ottenere un report di input HID in modo asincrono, il driver deve implementare la funzione EvtVhfAsyncOperation e specificare un puntatore alla funzione nel membro EvtVhfAsyncOperationGetInputReport di VHF_CONFIG.
-
Se il driver vuole ottenere un report di input HID in modo asincrono, il driver deve implementare la funzione EvtVhfAsyncOperation e specificare un puntatore alla funzione nel membro EvtVhfAsyncOperationWriteReport di VHF_CONFIG.
Per qualsiasi altro minidriver HID IOCTL, VHF completa la richiesta con STATUS_NOT_SUPPORTED.
Il dispositivo HID virtuale viene eliminato chiamando VhfDelete. Il callback EvtVhfCleanup è necessario se il driver allocato 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 aver restituito il 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 report di input HID
Inviare il report di input HID chiamando VhfReadReportSubmit.
In genere, un dispositivo HID invia informazioni sulle modifiche dello stato inviando report di input tramite interruzioni. Ad esempio, il dispositivo visore potrebbe inviare un report quando lo stato di un pulsante cambia. In questo caso, viene richiamata la routine del servizio di interruzione (ISR) del driver. 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 report di input HID man mano che arrivano. In questo modo si elimina la necessità del driver di origine HID di implementare una sincronizzazione complessa.
Il driver di origine 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 in modo sincrono o 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, viene restituito 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 deve avere registrato e implementato una funzione di callback EvtVhfCleanup quando in precedenza ha chiamato VhfCreate. Ecco la sequenza di eventi quando il driver di origine HID vuole eliminare il dispositivo HID virtuale:
- Il driver di origine HID interrompe l'avvio delle chiamate in VHF.
- L'origine HID chiama VhfDelete con Wait impostato su FALSE.
- VHF arresta la chiamata di funzioni di callback implementate dal driver di origine HID.
- VHF avvia la segnalazione del dispositivo come mancante a PnP Manager. A questo punto, la chiamata VhfDelete potrebbe restituire.
- Quando il dispositivo viene segnalato come dispositivo mancante, VHF richiama EvtVhfCleanup se il driver di origine HID ha registrato l'implementazione.
- Dopo la restituzione di EvtVhfCleanup , VHF esegue la 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"