仮想 HID フレームワーク (VHF) を使って HID ソース ドライバーを作成する

このトピックでは、次の方法を説明します。

  • Windows に HID 読み取りレポートを送信するカーネル モード ドライバー フレームワーク (KMDF) HID ソース ドライバーを記述します。
  • 下位フィルターとして、VHF ドライバーを仮想 HID デバイス スタックの HID ソース ドライバーに読み込みます。

オペレーティング システムに HID データを報告する HID ソース ドライバーの記述について学習します。

HID 入力デバイス (キーボード、マウス、ペン、タッチ、ボタンなど) は、オペレーティング システムにさまざまなレポートを送信します。それによって、デバイスの目的を理解し、必要なアクションを実行できるようになります。 レポートは、HID コレクションHID 使用法の形式です。 デバイスは、さまざまなトランスポートを介してこれらのレポートを送信します。その一部は Windows でサポートされています。たとえば、HID over I2CHID over USB です。 トランスポートが Windows でサポートされていない場合や、レポートが実際のハードウェアに直接マップされない場合もあります。 GPIO 以外のボタンやセンサーなど、仮想ハードウェア用の別のソフトウェア コンポーネントによって送信される HID 形式のデータ ストリームの場合もあります。 たとえば、PC にワイヤレスで送信される、ゲーム コントローラーとして動作している電話からの加速度計データについて考えてみます。 別の例としては、コンピューターは UIBC プロトコルを使用して Miracast デバイスからリモート入力を受信できます。

以前のバージョンの Windows では、新しいトランスポート (実際のハードウェアまたはソフトウェア) をサポートするために、HID トランスポート ミニドライバーを記述し、Microsoft が提供するインボックス クラス ドライバー、Hidclass.sys にバインドする必要がありました。 クラス/ミニ ドライバーのペアは、最上位のコレクションなどの HID コレクションを上位のドライバーやユーザー モード アプリケーションに提供しました。 そのモデルでは、ミニドライバーの記述が課題でした。複雑な作業になる可能性があるからです。

Windows 10 以降では、新しい仮想 HID フレームワーク (VHF) を使用することで、トランスポート ミニドライバーを記述する必要がなくなります。 代わりに、KMDF または WDM プログラミング インターフェイスを使用して HID ソース ドライバーを記述できます。 このフレームワークは、ドライバーによって使用されるプログラミング要素を公開する Microsoft 提供のスタティック ライブラリで構成されています。 また、Microsoft 提供のインボックス ドライバーも含まれています。これは 1 つまたは複数の子デバイスを列挙して仮想 HID ツリーの構築と管理を行います。

Note

このリリースでは、VHF はカーネル モードでのみ HID ソース ドライバーをサポートしています。

このトピックでは、フレームワークのアーキテクチャ、仮想 HID デバイス ツリー、および構成シナリオについて説明します。

仮想 HID デバイス ツリー

この画像では、ドライバーとそれに関連付けられているデバイス オブジェクトがデバイス ツリーに表示されます。

Diagram of a virtual HID device tree.

HID ソース ドライバー (お使いのドライバー)

HID ソース ドライバーは Vhfkm.lib にリンクし、ビルド プロジェクトに Vhf.h が含まれています。 ドライバーは、Windows Driver Model (WDM) または Windows Driver Frameworks (WDF) の一部であるカーネル モード ドライバー フレームワーク (KMDF) を使用して記述できます。 ドライバーは、フィルター ドライバーまたはデバイス スタックのファンクション ドライバーとして読み込むことができます。

VHF スタティック ライブラリ (vhfkm.lib)

スタティック ライブラリは、Windows 10 用 Windows Driver Kit (WDK) に含まれています。 このライブラリは、HID ソース ドライバーによって使用されるルーチンやコールバック関数などのプログラミング インターフェイスを公開します。 ドライバーが関数を呼び出すと、スタティック ライブラリは要求を処理する VHF ドライバーに要求を転送します。

VHF ドライバー (Vhf.sys)

Microsoft 提供のインボックス ドライバー。 このドライバーは、HID ソース デバイス スタックのドライバーの下に下位フィルター ドライバーとして読み込む必要があります。 VHF ドライバーは、子デバイスを動的に列挙し、HID ソース ドライバーによって指定されている 1 つまたは複数の HID デバイスの物理デバイス オブジェクト (PDO) を作成します。 また、列挙子デバイスの HID トランスポート ミニドライバー機能も実装します。

HID クラス ドライバー ペア (Hidclass.sys、Mshidkmdf.sys)

Hidclass/Mshidkmdf ペアは、実際の HID デバイスのコレクションの列挙方法と同様に、最上位コレクション (TLC) を列挙します。 HID クライアントは、実際の HID デバイスと同様に、引き続き TLC を要求して使用できます。 このドライバー ペアは、デバイス スタックのファンクション ドライバーとしてインストールされます。

Note

シナリオによっては、HID クライアントは HID データのソースを特定する必要があります。 たとえば、システムにはセンサーが組み込まれており、同じ種類のリモート センサーからデータを受信します。 システムは、より信頼性の高いセンサーを選択する場合があります。 システムに接続されている 2 つのセンサーを区別するために、HID クライアントは TLC のコンテナー ID を照会します。 この場合、HID ソース ドライバーは、VHF によって仮想 HID デバイスのコンテナー ID として報告されるコンテナー ID を提供できます。

HID クライアント (アプリケーション)

HID デバイス スタックによって報告される TLC を照会して使用します。

ヘッダーとライブラリの要件

この手順では、オペレーティング システムにヘッドセット ボタンを報告する簡単な HID ソース ドライバーを記述する方法について説明します。 この場合、このコードを実装するドライバーは、VHF を使用してヘッドセット ボタンを報告する HID ソースとして機能するように変更された既存の KMDF オーディオ ドライバーにすることができます。

  1. Windows 10 用 WDK に含まれる Vhf.h が含まれます。

  2. WDK に含まれる vhfkm.lib にリンクします。

  3. デバイスがオペレーティング システムに報告する HID レポート記述子を作成します。 この例では、HID レポート記述子は、ヘッドセット ボタンを記述します。 このレポートでは、HID 入力レポートのサイズを 8 ビット (1 バイト) に指定します。 最初の 3 ビットは、ヘッドセットの中央のボタン、音量を上げるボタン、音量を下げるボタン用です。 残りのビットは使用されません。

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

仮想 HID デバイスを作成する

VHF_CONFIG_INIT マクロを呼び出して VHF_CONFIG 構造体を初期化し、VhfCreate メソッドを呼び出します。 ドライバーは、通常、ドライバーの EvtDriverDeviceAdd コールバック関数で、WdfDeviceCreate 呼び出しの後、PASSIVE_LEVEL で VhfCreate を呼び出す必要があります。

VhfCreate 呼び出しでは、ドライバーは、非同期的に処理する必要がある操作やデバイス情報 (ベンダー/製品 ID) を設定する操作など、特定の構成オプションを指定できます。

たとえば、アプリケーションが TLC を要求します。 HID クラス ドライバー ペアがその要求を受信すると、ペアは要求の種類を決定し、適切な HID ミニドライバー IOCTL 要求を作成して VHF に転送します。 IOCTL 要求を取得すると、VHF は、その要求を処理したり、HID ソース ドライバーに要求の処理を任せたり、STATUS_NOT_SUPPORTED を使用して要求を完了したりできます。

VHF は、次の IOCTL を処理します。

要求が GetFeatureSetFeatureWriteReport、または GetInputReport で、HID ソース ドライバーが対応するコールバック関数を登録した場合、VHF はそのコールバック関数を呼び出します。 その関数内で、HID ソース ドライバーは HID 仮想デバイスの HID データを取得または設定できます。 ドライバーがコールバックを登録しない場合、VHF は状態 STATUS_NOT_SUPPORTED で要求を完了します。

VHF は、次の IOCTL の HID ソース ドライバー実装イベント コールバック関数を呼び出します。

  • IOCTL_HID_READ_REPORT

    ドライバーが HID 入力レポートを取得するバッファーを送信するときにバッファリング ポリシーを処理する場合は、EvtVhfReadyForNextReadReport を実装し、EvtVhfAsyncOperationGetInputReport メンバーにポインターを指定する必要があります。 詳細については、「HID 入力レポートを送信する」を参照してください。

  • IOCTL_HID_GET_FEATURE または IOCTL_HID_SET_FEATURE

    ドライバーが HID 機能レポートを非同期的に取得または設定する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGEvtVhfAsyncOperationSetFeature または EvtVhfAsyncOperationSetFeature メンバーの get または set 実装関数へのポインターを指定する必要があります。

  • IOCTL_HID_GET_INPUT_REPORT

    ドライバーが HID 入力レポートを非同期的に取得する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGEvtVhfAsyncOperationGetInputReport メンバーの関数へのポインターを指定する必要があります。

  • IOCTL_HID_WRITE_REPORT

    ドライバーが HID 入力レポートを非同期的に記述する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGEvtVhfAsyncOperationWriteReport メンバーの関数へのポインターを指定する必要があります。

その他の HID ミニドライバー IOCTL の場合、VHF は STATUS_NOT_SUPPORTED で要求を完了します。

仮想 HID デバイスは、VhfDelete を呼び出すことによって削除されます。 ドライバーが仮想 HID デバイスのリソースを割り当てた場合は、EvtVhfCleanup コールバックが必要です。 ドライバーは、EvtVhfCleanup 関数を実装し、VHF_CONFIGEvtVhfCleanup メンバーのその関数へのポインターを指定する必要があります。 EvtVhfCleanup は、VhfDelete 呼び出しが完了する前に呼び出されます。 詳細については、「仮想 HID デバイスを削除する」を参照してください。

Note

非同期操作が完了した後、ドライバーは VhfAsyncOperationComplete を呼び出して操作の結果を設定する必要があります。 イベント コールバックからメソッドを呼び出すか、コールバックから戻った後でメソッドを呼び出すことができます。

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

HID 入力レポートを送信する

VhfReadReportSubmit を呼び出して HID 入力レポートを送信します。

通常、HID デバイスは、割り込みを介して入力レポートを送信することによって、状態の変更に関する情報を送信します。 たとえば、ヘッドセット デバイスは、ボタンの状態が変化したときにレポートを送信する場合があります。 このような場合は、ドライバーの割り込みサービス ルーチン (ISR) が呼び出されます。 そのルーチンでは、ドライバーは、入力レポートを処理して VHF に送信する遅延プロシージャ呼び出し (DPC) をスケジュールする可能性があります。これはオペレーティング システムに情報を送信します。 既定では、VHF はレポートをバッファリングし、HID ソース ドライバーは、受信した HID 入力レポートの送信を開始できます。 これにより、HID ソース ドライバーが複雑な同期を実装する必要がなくなります。

HID ソース ドライバーは、保留中のレポートのバッファリング ポリシーを実装することで、入力レポートを送信できます。 バッファリングの重複を回避するために、HID ソース ドライバーは EvtVhfReadyForNextReadReport コールバック関数を実装し、VHF がこのコールバックを呼び出したかどうかを追跡できます。 以前に呼び出されていた場合、HID ソース ドライバーは VhfReadReportSubmit を呼び出してレポートを送信できます。 EvtVhfReadyForNextReadReport が呼び出されるまで待機してから、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);
    }
}

仮想 HID デバイスを削除する

VhfDelete を呼び出して、仮想 HID デバイスを削除します。

VhfDelete は、Wait パラメーターを指定して同期または非同期で呼び出すことができます。 同期呼び出しの場合、デバイス オブジェクトの EvtCleanupCallback など、PASSIVE_LEVEL でメソッドを呼び出す必要があります。 VhfDelete は、仮想 HID デバイスを削除した後に返されます。 ドライバーが VhfDelete を非同期的に呼び出す場合、すぐに制御を戻し、VHF は、削除操作が完了してから EvtVhfCleanup を呼び出します。 このメソッドは、最大の DISPATCH_LEVEL で呼び出すことができます。 この場合、ドライバーは、以前に VhfCreate を呼び出したときに EvtVhfCleanup コールバック関数を登録して実装する必要があります。 HID ソース ドライバーが仮想 HID デバイスを削除する場合のイベントの順序を次に示します。

  1. HID ソース ドライバーは、VHF への呼び出しの開始を停止します。
  2. HID ソースは、Wait が FALSE に設定された状態で VhfDelete を呼び出します。
  3. VHF は、HID ソース ドライバーによって実装されたコールバック関数の呼び出しを停止します。
  4. VHF は、PnP マネージャーにデバイスが見つからないという報告を開始します。 この時点で、VhfDelete 呼び出しが返される可能性があります。
  5. 見つからないとしてデバイスが報告されると、HID ソース ドライバーがその実装を登録した場合に VHF は EvtVhfCleanup を呼び出します。
  6. EvtVhfCleanup が返ったら、VHF はそのクリーンアップを実行します。
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);
    }

}

HID ソース ドライバーをインストールする

HID ソース ドライバーをインストールする INF ファイルで、AddReg ディレクティブを使用して、HID ソース ドライバーの下位フィルター ドライバーとして Vhf.sys を宣言していることを確認します。

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

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