VHF(Virtual HID Framework)를 사용하여 HID 원본 드라이버 작성

이 항목에서는 다음 작업을 수행하는 방법을 설명합니다.

  • WINDOWS에 HID 읽기 보고서를 제출하는 KMDF(Kernel-Mode Driver Framework)HID 원본 드라이버를 작성합니다.
  • VHF 드라이버를 하위 필터로 가상 HID 디바이스 스택의 HID 원본 드라이버에 로드합니다.

HID 데이터를 운영 체제에 보고하는 HID 원본 드라이버를 작성하는 방법에 대해 알아봅니다.

키보드, 마우스, 펜, 터치 또는 단추와 같은 HID 입력 디바이스는 장치의 목적을 이해하고 필요한 작업을 수행할 수 있도록 운영 체제에 다양한 보고서를 보냅니다. 보고서는 HID 컬렉션HID 사용량 형식입니다. 디바이스는 다양한 전송을 통해 이러한 보고서를 전송하며, 그 중 일부는 I2C를 통해 HIDUSB를 통해 HID와 같은 Windows에서 지원됩니다. 경우에 따라 전송이 Windows에서 지원되지 않거나 보고서가 실제 하드웨어에 직접 매핑되지 않을 수 있습니다. 비 GPIO 단추 또는 센서와 같은 가상 하드웨어용 다른 소프트웨어 구성 요소에서 보내는 HID 형식의 데이터 스트림일 수 있습니다. 예를 들어 게임 컨트롤러로 동작하는 휴대폰의 가속도계 데이터를 PC에 무선으로 전송하는 것이 좋습니다. 또 다른 예제에서는 컴퓨터가 UIBC 프로토콜을 사용하여 Miracast 디바이스에서 원격 입력을 받을 수 있습니다.

이전 버전의 Windows에서는 새 전송(실제 하드웨어 또는 소프트웨어)을 지원하려면 HID 전송 미니드라이버 를 작성하고 Microsoft에서 제공하는 기본 제공 클래스 드라이버인 Hidclass.sys 바인딩해야 했습니다. 클래스/미니 드라이버 쌍은 최상위 드라이버 및 사용자 모드 애플리케이션에 최상위 컬렉션 과 같은 HID 컬렉션을 제공했습니다. 이 모델에서 문제는 복잡한 작업이 될 수 있는 미니드라이버를 작성하는 것이었습니다.

Windows 10 새 VHF(Virtual HID Framework)를 사용하면 전송 미니드라이버를 작성할 필요가 없습니다. 대신 KMDF 또는 WDM 프로그래밍 인터페이스를 사용하여 HID 원본 드라이버를 작성할 수 있습니다. 프레임워크는 드라이버에서 사용하는 프로그래밍 요소를 노출하는 Microsoft 제공 정적 라이브러리로 구성됩니다. 또한 하나 이상의 자식 디바이스를 열거하고 가상 HID 트리를 빌드하고 관리하기 위해 진행하는 Microsoft 제공 기본 제공 드라이버도 포함되어 있습니다.

참고

이 릴리스에서 VHF는 커널 모드에서만 HID 원본 드라이버를 지원합니다.

이 항목에서는 프레임워크의 아키텍처, 가상 HID 디바이스 트리 및 구성 시나리오에 대해 설명합니다.

Virtual HID 디바이스 트리

이 이미지에서 디바이스 트리는 드라이버와 연결된 디바이스 개체를 표시합니다.

가상 HID 디바이스 트리의 다이어그램.

HID 원본 드라이버(드라이버)

HID 원본 드라이버는 Vhfkm.lib에 연결되며 빌드 프로젝트에 Vhf.h를 포함합니다. WDM( Windows 드라이버 모델 ) 또는 WDF(Windows 드라이버 프레임워크)의 일부인 KMDF(Kernel-Mode 드라이버 프레임워크)를 사용하여 드라이버를 작성할 수 있습니다. 드라이버는 디바이스 스택에서 필터 드라이버 또는 함수 드라이버로 로드할 수 있습니다.

VHF 정적 라이브러리(vhfkm.lib)

정적 라이브러리는 Windows 10 위한 WDK(Windows 드라이버 키트)에 포함되어 있습니다. 라이브러리는 HID 원본 드라이버에서 사용하는 루틴 및 콜백 함수와 같은 프로그래밍 인터페이스를 노출합니다. 드라이버가 함수를 호출하면 정적 라이브러리는 요청을 처리하는 VHF 드라이버에 요청을 전달합니다.

VHF 드라이버(Vhf.sys)

Microsoft에서 제공하는 기본 제공 드라이버입니다. 이 드라이버는 HID 원본 디바이스 스택의 드라이버 아래에 있는 하위 필터 드라이버로 로드되어야 합니다. VHF 드라이버는 자식 디바이스를 동적으로 열거하고 HID 원본 드라이버에서 지정한 하나 이상의 HID 디바이스에 대해 PDO(물리적 디바이스 개체)를 만듭니다. 또한 열거된 자식 디바이스의 HID 전송 미니 드라이버 기능을 구현합니다.

HID 클래스 드라이버 쌍(Hidclass.sys, Mshidkmdf.sys)

Hidclass/Mshidkmdf 쌍은 실제 HID 디바이스에 대한 컬렉션을 열거하는 방법과 유사한 TLC(최상위 컬렉션) 를 열거합니다. HID 클라이언트는 실제 HID 디바이스와 마찬가지로 TCC를 계속 요청하고 사용할 수 있습니다. 이 드라이버 쌍은 디바이스 스택에서 함수 드라이버로 설치됩니다.

참고

일부 시나리오에서는 HID 클라이언트가 HID 데이터의 원본을 식별해야 할 수 있습니다. 예를 들어 시스템에는 기본 제공 센서가 있으며 동일한 유형의 원격 센서에서 데이터를 받습니다. 시스템에서 더 신뢰할 수 있도록 하나의 센서를 선택할 수 있습니다. 시스템에 연결된 두 센서를 구분하기 위해 HID 클라이언트는 TLC의 컨테이너 ID를 쿼리합니다. 이 경우 HID 원본 드라이버는 VHF를 통해 가상 HID 디바이스의 컨테이너 ID로 보고되는 컨테이너 ID를 제공할 수 있습니다.

HID 클라이언트(애플리케이션)

HID 디바이스 스택에서 보고하는 TCC를 쿼리하고 사용합니다.

헤더 및 라이브러리 요구 사항

이 절차에서는 운영 체제에 헤드셋 단추를 보고하는 간단한 HID 원본 드라이버를 작성하는 방법을 설명합니다. 이 경우 이 코드를 구현하는 드라이버는 VHF를 사용하여 HID 소스 보고 헤드셋 단추로 작동하도록 수정된 기존 KMDF 오디오 드라이버일 수 있습니다.

  1. Windows 10 WDK에 포함된 Vhf.h를 포함합니다.

  2. WDK에 포함된 vhfkm.lib에 대한 링크입니다.

  3. 디바이스가 운영 체제에 보고하려는 HID 보고서 설명자를 만듭니다. 이 예제에서 HID 보고서 설명자는 헤드셋 단추를 설명합니다. 보고서는 HID 입력 보고서 크기 8비트(1 바이트)를 지정합니다. 처음 세 비트는 헤드셋 중간, 볼륨 업 및 볼륨 다운 버튼용입니다. 나머지 비트는 사용되지 않습니다.

    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 Minidriver IOCTL 요청을 만들어 VHF에 전달합니다. IOCTL 요청을 받으면 VHF는 요청을 처리하거나 HID 원본 드라이버를 사용하여 처리하거나 STATUS_NOT_SUPPORTED 사용하여 요청을 완료할 수 있습니다.

VHF는 다음 IOCTL을 처리합니다.

요청이 GetFeature, SetFeature, WriteReport 또는 GetInputReport이고 HID 원본 드라이버가 해당 콜백 함수를 등록한 경우 VHF는 콜백 함수를 호출합니다. 해당 함수 내에서 HID 원본 드라이버는 HID 가상 디바이스에 대한 HID 데이터를 얻거나 설정할 수 있습니다. 드라이버가 콜백을 등록하지 않으면 VHF는 상태 STATUS_NOT_SUPPORTED 사용하여 요청을 완료합니다.

VHF는 다음 IOCTL에 대해 HID 원본 드라이버 구현 이벤트 콜백 함수를 호출합니다.

다른 HID Minidriver IOCTL의 경우 VHF는 STATUS_NOT_SUPPORTED 사용하여 요청을 완료합니다.

가상 HID 디바이스는 VhfDelete를 호출하여 삭제됩니다. 드라이버가 가상 HID 디바이스에 대한 리소스를 할당한 경우 EvtVhfCleanup 콜백이 필요합니다. 드라이버는 EvtVhfCleanup 함수를 구현하고 VHF_CONFIGEvtVhfCleanup 멤버에서 해당 함수에 대한 포인터를 지정해야 합니다. VhfDelete 호출이 완료되기 전에 EvtVhfCleanup이 호출됩니다. 자세한 내용은 가상 HID 디바이스 삭제를 참조하세요.

참고

비동기 작업이 완료되면 드라이버는 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 를 호출하여 보고서를 제출할 수 있습니다. VhfReadReportSubmit를 다시 호출하기 전에 EvtVhfReadyForNextReadReport가 호출될 때까지 기다려야 합니다.

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 원본은 대기가 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 지시문을 사용하여 Vhf.sys HID 원본 드라이버에 대한 하위 필터 드라이버로 선언해야 합니다.

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

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