KMDF(USB 클라이언트 드라이버 코드 구조) 이해
이 항목에서는 KMDF 기반 USB 클라이언트 드라이버의 소스 코드에 대해 알아봅니다. 코드 예제는 Microsoft Visual Studio 2019에 포함된 USB 사용자 모드 드라이버 템플릿에 의해 생성됩니다.
이 섹션에서는 템플릿 코드에 대한 정보를 제공합니다.
KMDF 템플릿 코드 생성에 대한 지침은 첫 번째 KMDF(USB 클라이언트 드라이버)를 작성하는 방법을 참조하세요.
드라이버 소스 코드
드라이버 개체는 Windows에서 드라이버를 메모리에 로드한 후 클라이언트 드라이버의 인스턴스를 나타냅니다. 드라이버 개체의 전체 소스 코드는 Driver.h 및 Driver.c에 있습니다.
Driver.h
템플릿 코드의 세부 정보를 논의하기 전에 KMDF 드라이버 개발과 관련된 헤더 파일(Driver.h)의 일부 선언을 살펴보겠습니다.
Driver.h는 WDK(Windows 드라이버 키트)에 포함된 이러한 파일을 포함합니다.
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>
#include "device.h"
#include "queue.h"
#include "trace.h"
Ntddk.h 및 Wdf.h 헤더 파일은 KMDF 드라이버 개발을 위해 항상 포함됩니다. 헤더 파일에는 KMDF 드라이버를 컴파일하는 데 필요한 메서드 및 구조체에 대한 다양한 선언과 정의가 포함되어 있습니다.
Usb.h 및 Usbdlib.h에는 USB 디바이스에 대한 클라이언트 드라이버에 필요한 구조 및 루틴의 선언 및 정의가 포함됩니다.
Wdfusb.h에는 프레임워크에서 제공하는 USB I/O 대상 개체와 통신하는 데 필요한 구조체 및 메서드의 선언 및 정의가 포함되어 있습니다.
Device.h, Queue.h 및 Trace.h는 WDK에 포함되지 않습니다. 이러한 헤더 파일은 템플릿에서 생성되며 이 항목의 뒷부분에서 설명합니다.
Driver.h의 다음 블록은 DriverEntry 루틴 및 EvtDriverDeviceAdd 및 EvtCleanupCallback 이벤트 콜백 루틴에 대한 함수 역할 형식 선언을 제공합니다. 이러한 모든 루틴은 드라이버에 의해 구현됩니다. 역할 유형은 SDV(정적 드라이버 검증 도구)가 드라이버의 소스 코드를 분석하는 데 도움이 됩니다. 역할 형식에 대한 자세한 내용은 KMDF 드라이버에 함수 역할 형식을 사용하여 함수 선언을 참조 하세요.
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;
구현 파일인 Driver.c에는 pragma를 사용하여 alloc_text
DriverEntry 함수 및 이벤트 콜백 루틴이 페이징 가능한 메모리에 있는지 여부를 지정하는 다음 코드 블록이 포함되어 있습니다.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif
DriverEntry는 INIT로 표시된 반면 이벤트 콜백 루틴은 PAGE로 표시됩니다. INIT 섹션은 드라이버가 DriverEntry에서 반환되는 즉시 DriverEntry에 대한 실행 코드가 페이지 가능하고 삭제됨을 나타냅니다. PAGE 섹션은 코드가 항상 실제 메모리에 남아 있을 필요가 없음을 나타냅니다. 사용하지 않을 때 페이지 파일에 쓸 수 있습니다. 자세한 내용은 페이지 가능 코드 또는 데이터 잠금을 참조 하세요.
드라이버가 로드된 직후 Windows는 드라이버를 나타내는 DRIVER_OBJECT 구조를 할당합니다. 그런 다음 드라이버의 진입점 루틴인 DriverEntry를 호출하고 구조체에 대한 포인터를 전달합니다. Windows는 이름으로 루틴을 찾으므로 모든 드라이버는 DriverEntry라는 루틴을 구현해야 합니다. 루틴은 드라이버의 초기화 작업을 수행하고 프레임워크에 대한 드라이버의 이벤트 콜백 루틴을 지정합니다.
다음 코드 예제에서는 템플릿에서 생성된 DriverEntry 루틴을 보여줍니다.
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES attributes;
//
// Initialize WPP Tracing
//
WPP_INIT_TRACING( DriverObject, RegistryPath );
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Register a cleanup callback so that we can call WPP_CLEANUP when
// the framework driver object is deleted during driver unload.
//
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;
WDF_DRIVER_CONFIG_INIT(&config,
MyUSBDriver_EvtDeviceAdd
);
status = WdfDriverCreate(DriverObject,
RegistryPath,
&attributes,
&config,
WDF_NO_HANDLE
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
WPP_CLEANUP(DriverObject);
return status;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
DriverEntry 루틴에는 Windows에서 할당하는 DRIVER_OBJECT 구조체에 대한 포인터와 드라이버에 대한 레지스트리 경로라는 두 가지 매개 변수가 있습니다. RegistryPath 매개 변수는 레지스트리의 드라이버별 경로를 나타냅니다.
DriverEntry 루틴에서 드라이버는 다음 작업을 수행합니다.
드라이버의 수명 동안 필요한 전역 리소스를 할당합니다. 예를 들어 템플릿 코드에서 클라이언트 드라이버는 WPP_INIT_TRACING 매크로를 호출하여 WPP 소프트웨어 추적에 필요한 리소스를 할당합니다.
프레임워크에 특정 이벤트 콜백 루틴을 등록합니다.
이벤트 콜백을 등록하기 위해 클라이언트 드라이버는 먼저 특정 WDF 구조에서 EvtDriverXxx 루틴의 구현에 대한 포인터를 지정합니다. 그런 다음 드라이버는 WdfDriverCreate 메서드를 호출하고 해당 구조를 제공합니다(다음 단계에서 설명).
WdfDriverCreate 메서드를 호출하고 프레임워크 드라이버 개체에 대한 핸들을 검색합니다.
클라이언트 드라이버가 WdfDriverCreate를 호출한 후 프레임워크는 클라이언트 드라이버를 나타내는 프레임워크 드라이버 개체를 만듭니다. 호출이 완료되면 클라이언트 드라이버는 WDFDRIVER 핸들을 수신하고 해당 레지스트리 경로, 버전 정보 등 드라이버에 대한 정보를 검색할 수 있습니다(WDF 드라이버 개체 참조 참조).
프레임워크 드라이버 개체는 DRIVER_OBJECT 설명한 Windows 드라이버 개체와 다릅니다. 언제든지 클라이언트 드라이버는 WDFDRIVER 핸들을 사용하고 WdfGetDriver 메서드를 호출하여 WindowsDRIVER_OBJECT 구조에 대한 포인터를 가져올 수 있습니다.
WdfDriverCreate 호출 후 프레임워크는 클라이언트 드라이버와 협력하여 Windows와 통신합니다. 프레임워크는 Windows와 드라이버 간의 추상화 계층 역할을 하며 대부분의 복잡한 드라이버 작업을 처리합니다. 클라이언트 드라이버는 드라이버가 관심 있는 이벤트에 대한 프레임워크에 등록합니다. 특정 이벤트가 발생하면 Windows에서 프레임워크를 알 수 있습니다. 드라이버가 특정 이벤트에 대한 이벤트 콜백을 등록한 경우 프레임워크는 등록된 이벤트 콜백을 호출하여 드라이버에 알깁니다. 이렇게 하면 드라이버에 필요한 경우 이벤트를 처리할 수 있는 기회가 제공됩니다. 드라이버가 이벤트 콜백을 등록하지 않은 경우 프레임워크는 이벤트의 기본 처리를 진행합니다.
드라이버가 등록해야 하는 이벤트 콜백 중 하나는 EvtDriverDeviceAdd입니다. 프레임워크는 프레임워크가 디바이스 개체를 만들 준비가 되면 드라이버의 EvtDriverDeviceAdd 구현을 호출합니다. Windows에서 디바이스 개체는 클라이언트 드라이버가 로드되는 물리적 디바이스의 함수를 논리적으로 표현한 것입니다(이 항목의 뒷부분에서 설명).
드라이버가 등록할 수 있는 다른 이벤트 콜백은 EvtDriverUnload, EvtCleanupCallback 및 EvtDestroyCallback입니다.
템플릿 코드에서 클라이언트 드라이버는 EvtDriverDeviceAdd 및 EvtCleanupCallback의 두 이벤트를 등록합니다. 드라이버는 WDF_DRIVER_CONFIG 구조의 EvtDriverDeviceAdd 구현 및 WDF_OBJECT_ATTRIBUTES 구조의 EvtCleanupCallback 이벤트 콜백에 대한 포인터를 지정합니다.
Windows에서 DRIVER_OBJECT 구조를 해제하고 드라이버를 언로드할 준비가 되면 프레임워크는 드라이버의 EvtCleanupCallback 구현을 호출하여 해당 이벤트를 클라이언트 드라이버에 보고합니다. 프레임워크는 프레임워크 드라이버 개체를 삭제하기 직전에 해당 콜백을 호출합니다. 클라이언트 드라이버는 DriverEntry에 할당된 모든 전역 리소스를 해제할 수 있습니다. 예를 들어 템플릿 코드에서 클라이언트 드라이버는 DriverEntry에서 활성화된 WPP 추적을 중지합니다.
다음 코드 예제에서는 클라이언트 드라이버의 EvtCleanupCallback 이벤트 콜백 구현을 보여 줍니다.
VOID MyUSBDriver_EvtDriverContextCleanup(
_In_ WDFDRIVER Driver
)
{
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE ();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Stop WPP Tracing
//
WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );
}
USB 드라이버 스택에서 디바이스를 인식한 후 버스 드라이버는 디바이스에 대한 PDO(물리적 디바이스 개체)를 만들고 PDO를 디바이스 노드와 연결합니다. 디바이스 노드는 PDO가 맨 아래에 있는 스택 형성에 있습니다. 각 스택에는 하나의 PDO가 있어야 하며 필터 디바이스 개체(필터 DO)와 FDO(함수 디바이스 개체)가 있을 수 있습니다. 자세한 내용은 디바이스 노드 및 디바이스 스택을 참조 하세요.
이 그림은 템플릿 드라이버 MyUSBDriver_.sys 대한 디바이스 스택을 보여 줍니다.
"내 USB 디바이스"라는 디바이스 스택을 확인합니다. USB 드라이버 스택은 디바이스 스택에 대한 PDO를 만듭니다. 이 예제에서 PDO는 USB 드라이버 스택에 포함된 드라이버 중 하나인 Usbhub3.sys 연결됩니다. 디바이스의 함수 드라이버인 클라이언트 드라이버는 먼저 디바이스에 대한 FDO를 만든 다음 디바이스 스택의 맨 위에 연결해야 합니다.
KMDF 기반 클라이언트 드라이버의 경우 프레임워크는 클라이언트 드라이버를 대신하여 이러한 작업을 수행합니다. 디바이스에 대한 FDO를 나타내기 위해 프레임워크는 프레임워크 디바이스 개체를 만듭니다. 그러나 클라이언트 드라이버는 프레임워크가 새 개체를 구성하는 데 사용하는 특정 초기화 매개 변수를 지정할 수 있습니다. 이 기회는 프레임워크가 드라이버의 EvtDriverDeviceAdd 구현을 호출할 때 클라이언트 드라이버에 제공됩니다. 개체가 만들어지고 FDO가 디바이스 스택의 맨 위에 연결되면 프레임워크는 클라이언트 드라이버에 WDFDEVICE 핸들을 프레임워크 디바이스 개체에 제공합니다. 클라이언트 드라이버는 이 핸들을 사용하여 다양한 디바이스 관련 작업을 수행할 수 있습니다.
다음 코드 예제에서는 클라이언트 드라이버의 EvtDriverDeviceAdd 이벤트 콜백 구현을 보여줍니다.
NTSTATUS
MyUSBDriver_EvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = MyUSBDriver_CreateDevice(DeviceInit);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
런타임 동안 EvtDriverDeviceAdd 구현은 PAGED_CODE 매크로를 사용하여 페이징 가능한 코드에 적합한 환경에서 루틴이 호출되고 있는지 확인합니다. 모든 변수를 선언한 후 매크로를 호출해야 합니다. 그렇지 않으면 생성된 소스 파일이 .c 파일이며 .cpp 파일이 아니므로 컴파일이 실패합니다.
클라이언트 드라이버의 EvtDriverDeviceAdd 구현은 MyUSBDriver_CreateDevice 도우미 함수를 호출하여 필요한 작업을 수행합니다.
다음 코드 예제에서는 MyUSBDriver_CreateDevice 도우미 함수를 보여 주세요. MyUSBDriver_CreateDevice Device.c에 정의되어 있습니다.
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PDEVICE_CONTEXT deviceContext;
WDFDEVICE device;
NTSTATUS status;
PAGED_CODE();
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (NT_SUCCESS(status)) {
//
// Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
// inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
// device.h header file. This function will do the type checking and return
// the device context. If you pass a wrong object handle
// it will return NULL and assert if run under framework verifier mode.
//
deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
deviceContext->PrivateDeviceData = 0;
//
// Create a device interface so that applications can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&GUID_DEVINTERFACE_MyUSBDriver_,
NULL // ReferenceString
);
if (NT_SUCCESS(status)) {
//
// Initialize the I/O Package and any Queues
//
status = MyUSBDriver_QueueInitialize(device);
}
}
return status;
}
EvtDriverDeviceAdd에는 이전 DriverEntry 호출에서 만든 프레임워크 드라이버 개체에 대한 핸들과 WDFDEVICE_INIT 구조체에 대한 포인터라는 두 가지 매개 변수가 있습니다. 프레임워크는 WDFDEVICE_INIT 구조를 할당하고 클라이언트 드라이버가 만들 프레임워크 디바이스 개체에 대한 초기화 매개 변수로 구조를 채울 수 있도록 포인터를 전달합니다.
EvtDriverDeviceAdd 구현에서 클라이언트 드라이버는 다음 작업을 수행해야 합니다.
WdfDeviceCreate 메서드를 호출하여 새 디바이스 개체에 대한 WDFDEVICE 핸들을 검색합니다.
WdfDeviceCreate 메서드를 사용하면 프레임워크가 FDO에 대한 프레임워크 디바이스 개체를 만들고 디바이스 스택의 맨 위에 연결합니다. WdfDeviceCreate 호출에서 클라이언트 드라이버는 다음 작업을 수행해야 합니다.
- 프레임워크 지정 WDFDEVICE_INIT 구조에서 클라이언트 드라이버의 PnP(플러그 앤 플레이) 전원 콜백 루틴에 대한 포인터를 지정합니다. 루틴은 먼저 WDF_PNPPOWER_EVENT_CALLBACKS 구조에서 설정되고 WdfDeviceInitSetPnpPowerEventCallbacks 메서드를 호출하여 WDFDEVICE_INIT 연결됩니다.
Windows 구성 요소, PnP 및 전원 관리자는 PnP 상태(예: 시작, 중지 및 제거)와 전원 상태(예: 작업 또는 일시 중단)의 변경에 대한 응답으로 드라이버에 디바이스 관련 요청을 보냅니다. KMDF 기반 드라이버의 경우 프레임워크는 해당 요청을 가로채는 것입니다. 클라이언트 드라이버는 WdfDeviceCreate 호출을 사용하여 PnP 전원 이벤트 콜백이라는 콜백 루틴을 프레임워크에 등록하여 요청에 대한 알림을 받을 수 있습니다. Windows 구성 요소가 요청을 보낼 때 프레임워크는 요청을 처리하고 클라이언트 드라이버가 등록된 경우 해당 PnP 전원 이벤트 콜백을 호출합니다.
클라이언트 드라이버가 구현해야 하는 PnP 전원 이벤트 콜백 루틴 중 하나는 EvtDevicePrepareHardware입니다. 해당 이벤트 콜백은 PnP 관리자가 디바이스를 시작할 때 호출됩니다. EvtDevicePrepareHardware에 대한 구현은 다음 섹션에서 설명합니다.
- 드라이버의 디바이스 컨텍스트 구조에 대한 포인터를 지정합니다. 포인터는 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 매크로를 호출하여 초기화되는 WDF_OBJECT_ATTRIBUTES 구조체에서 설정해야 합니다.
디바이스 컨텍스트(디바이스 확장이라고도 함)는 특정 디바이스 개체에 대한 정보를 저장하기 위한 데이터 구조(클라이언트 드라이버에 의해 정의됨)입니다. 클라이언트 드라이버는 해당 디바이스 컨텍스트에 대한 포인터를 프레임워크에 전달합니다. 프레임워크는 구조의 크기에 따라 메모리 블록을 할당하고 프레임워크 디바이스 개체에 해당 메모리 위치에 대한 포인터를 저장합니다. 클라이언트 드라이버는 포인터를 사용하여 디바이스 컨텍스트의 멤버에 액세스하고 정보를 저장할 수 있습니다. 디바이스 컨텍스트에 대한 자세한 내용은 Framework 개체 컨텍스트 공간을 참조 하세요.
WdfDeviceCreate 호출이 완료되면 클라이언트 드라이버는 새 프레임워크 디바이스 개체에 대한 핸들을 수신합니다. 이 개체는 디바이스 컨텍스트에 대해 프레임워크에서 할당한 메모리 블록에 대한 포인터를 저장합니다. 이제 클라이언트 드라이버는 WdfObjectGet_DEVICE_CONTEXT 매크로를 호출하여 디바이스 컨텍스트에 대한 포인터를 가져올 수 있습니다.
WdfDeviceCreateDeviceInterface 메서드를 호출하여 클라이언트 드라이버에 대한 디바이스 인터페이스 GUID를 등록합니다. 애플리케이션은 이 GUID를 사용하여 드라이버와 통신할 수 있습니다. GUID 상수는 헤더 public.h에 선언됩니다.
디바이스로의 I/O 전송에 대한 큐를 설정합니다. 템플릿 코드는 큐 소스 코드 섹션에서 설명하는 큐를 설정하기 위한 도우미 루틴인 MyUSBDriver_QueueInitialize 정의합니다.
디바이스 원본 코드
디바이스 개체는 클라이언트 드라이버가 메모리에 로드되는 디바이스의 인스턴스를 나타냅니다. 디바이스 개체의 전체 소스 코드는 Device.h 및 Device.c에 있습니다.
Device.h
Device.h 헤더 파일에는 프로젝트의 모든 파일에서 사용하는 일반적인 선언이 포함된 public.h가 포함되어 있습니다.
Device.h의 다음 블록은 클라이언트 드라이버에 대한 디바이스 컨텍스트를 선언합니다.
typedef struct _DEVICE_CONTEXT
{
WDFUSBDEVICE UsbDevice;
ULONG PrivateDeviceData; // just a placeholder
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)
DEVICE_CONTEXT 구조는 클라이언트 드라이버에 의해 정의되고 프레임워크 디바이스 개체에 대한 정보를 저장합니다. Device.h에서 선언되며 프레임워크의 USB 대상 디바이스 개체에 대한 핸들(나중에 설명)과 자리 표시자의 두 멤버를 포함합니다. 이 구조는 이후 연습에서 확장됩니다.
Device.h에는 WdfObjectGet_DEVICE_CONTEXT 인라인 함수를 생성하는 WDF_DECLARE_CONTEXT_TYPE 매크로도 포함되어 있습니다. 클라이언트 드라이버는 해당 함수를 호출하여 프레임워크 디바이스 개체에서 메모리 블록에 대한 포인터를 검색할 수 있습니다.
다음 코드 줄은 USB 대상 디바이스 개체에 대한 WDFUSBDEVICE 핸들을 검색하는 도우미 함수인 MyUSBDriver_CreateDevice 선언합니다.
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
);
USBCreate는 WDFDEVICE_INIT 구조체에 대한 포인터를 해당 매개 변수로 사용합니다. 이는 클라이언트 드라이버의 EvtDriverDeviceAdd 구현을 호출할 때 프레임워크에서 전달한 것과 동일한 포인터입니다. 기본적으로 MyUSBDriver_CreateDevice EvtDriverDeviceAdd의 작업을 수행합니다. EvtDriverDeviceAdd 구현에 대한 소스 코드는 이전 섹션에서 설명합니다.
Device.h의 다음 줄은 EvtDevicePrepareHardware 이벤트 콜백 루틴에 대한 함수 역할 형식 선언을 선언합니다. 이벤트 콜백은 클라이언트 드라이버에 의해 구현되며 USB 디바이스 구성과 같은 작업을 수행합니다.
EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;
Device.c
Device.c 구현 파일에는 pragma를 사용하여 alloc_text
드라이버의 EvtDevicePrepareHardware 구현이 페이징 가능한 메모리에 있음을 지정하는 다음 코드 블록이 포함되어 있습니다.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif
EvtDevicePrepareHardware 구현에서 클라이언트 드라이버는 USB 관련 초기화 작업을 수행합니다. 이러한 작업에는 클라이언트 드라이버 등록, USB별 I/O 대상 개체 초기화, USB 구성 선택 등이 포함됩니다. 다음 표에서는 프레임워크에서 제공하는 특수한 I/O 대상 개체를 보여줍니다. 자세한 내용은 USB I/O 대상을 참조하세요.
USB I/O 대상 개체(핸들) | 호출하여 핸들 가져오기... | 설명 |
---|---|---|
USB 대상 디바이스 개체 (WDFUSBDEVICE) | WdfUsbTargetDeviceCreateWithParameters | USB 디바이스를 나타내며 디바이스 설명자를 검색하고 디바이스에 제어 요청을 보내는 메서드를 제공합니다. |
USB 대상 인터페이스 개체 (WDFUSBINTERFACE) | WdfUsbTargetDeviceGetInterface | 개별 인터페이스를 나타내며 클라이언트 드라이버가 대체 설정을 선택하고 설정에 대한 정보를 검색하기 위해 호출할 수 있는 메서드를 제공합니다. |
USB 대상 파이프 개체 (WDFUSBPIPE) | WdfUsbInterfaceGetConfiguredPipe | 인터페이스의 현재 대체 설정에 구성된 엔드포인트에 대한 개별 파이프를 나타냅니다. USB 드라이버 스택은 선택한 구성에서 각 인터페이스를 선택하고 인터페이스 내의 각 엔드포인트에 대한 통신 채널을 설정합니다. USB 용어에서 해당 통신 채널을 파이프라고 합니다. |
이 코드 예제에서는 EvtDevicePrepareHardware에 대한 구현을 보여줍니다.
NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourceList,
_In_ WDFCMRESLIST ResourceListTranslated
)
{
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
WDF_USB_DEVICE_CREATE_CONFIG createParams;
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
UNREFERENCED_PARAMETER(ResourceList);
UNREFERENCED_PARAMETER(ResourceListTranslated);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = STATUS_SUCCESS;
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
if (pDeviceContext->UsbDevice == NULL) {
//
// Specifying a client contract version of 602 enables us to query for
// and use the new capabilities of the USB driver stack for Windows 8.
// It also implies that we conform to rules mentioned in the documentation
// documentation for WdfUsbTargetDeviceCreateWithParameters.
//
WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
USBD_CLIENT_CONTRACT_VERSION_602
);
status = WdfUsbTargetDeviceCreateWithParameters(Device,
&createParams,
WDF_NO_OBJECT_ATTRIBUTES,
&pDeviceContext->UsbDevice
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
return status;
}
//
// Select the first configuration of the device, using the first alternate
// setting of each interface
//
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
0,
NULL
);
status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
return status;
}
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
템플릿 코드에 의해 구현된 클라이언트 드라이버의 작업을 자세히 살펴보겠습니다.
Windows에서 로드한 기본 USB 드라이버 스택에 등록하기 위해 클라이언트 드라이버의 계약 버전을 지정합니다.
Windows는 USB 디바이스가 연결된 호스트 컨트롤러에 따라 USB 3.0 또는 USB 2.0 드라이버 스택을 로드할 수 있습니다. USB 3.0 드라이버 스택은 Windows 8의 새로운 기능이며 USB 3.0 사양에 정의된 몇 가지 새로운 기능(예: 스트림 기능)을 지원합니다. 또한 새 드라이버 스택은 새로운 URB 루틴 집합을 통해 사용할 수 있는 USB 요청 블록(URL)의 더 나은 추적 및 처리와 같은 몇 가지 개선 사항을 구현합니다. 이러한 기능을 사용하거나 새 루틴을 호출하려는 클라이언트 드라이버는 USBD_CLIENT_CONTRACT_VERSION_602 계약 버전을 지정해야 합니다. USBD_CLIENT_CONTRACT_VERSION_602 클라이언트 드라이버는 특정 규칙 집합을 준수해야 합니다. 이러한 규칙에 대한 자세한 내용은 모범 사례: URL 사용을 참조 하세요.
계약 버전을 지정하려면 클라이언트 드라이버가 WDF_USB_DEVICE_CREATE_CONFIG_INIT 매크로를 호출하여 계약 버전으로 WDF_USB_DEVICE_CREATE_CONFIG 구조를 초기화해야 합니다.
WdfUsbTargetDeviceCreateWithParameters 메서드를 호출합니다. 이 메서드는 클라이언트 드라이버가 EvtDriverDeviceAdd의 드라이버 구현에서 WdfDeviceCreate를 호출하여 이전에 가져온 프레임워크 디바이스 개체에 대한 핸들이 필요합니다. WdfUsbTargetDeviceCreateWithParameters 메서드:
- 클라이언트 드라이버를 기본 USB 드라이버 스택에 등록합니다.
- 프레임워크에서 만든 USB 대상 디바이스 개체에 대한 WDFUSBDEVICE 핸들을 검색합니다. 템플릿 코드는 디바이스 컨텍스트에서 USB 대상 디바이스 개체에 대한 핸들을 저장합니다. 클라이언트 드라이버는 해당 핸들을 사용하여 디바이스에 대한 USB 관련 정보를 가져올 수 있습니다.
다음과 같은 경우 WdfUsbTargetDeviceCreateWithParameters 대신 WdfUsbTargetDeviceCreate를 호출해야 합니다.
클라이언트 드라이버는 WDK의Windows 8 버전에서 사용할 수 있는 새 URB 루틴 집합을 호출하지 않습니다.
클라이언트 드라이버가 WdfUsbTargetDeviceCreateWithParameters를 호출하는 경우 USB 드라이버 스택은 WdfUsbTargetDeviceCreateUrb 또는 WdfUsbTargetDeviceCreateIsochUrb를 호출하여 모든 URL이 할당된다고 가정합니다. 이러한 메서드에 의해 할당된 URL에는 더 빠른 처리를 위해 USB 드라이버 스택에서 사용하는 불투명 URB 컨텍스트 블록이 있습니다. 클라이언트 드라이버가 이러한 메서드에 의해 할당되지 않은 URB를 사용하는 경우 USB 드라이버는 버그 검사를 생성합니다.
URB 할당에 대한 자세한 내용은 URL 할당 및 빌드를 참조 하세요.
클라이언트 드라이버는 모범 사례: URL 사용에 설명된 규칙 집합을 준수하지 않습니다.
이러한 드라이버는 클라이언트 계약 버전을 지정할 필요가 없으므로 1단계를 건너뛰어야 합니다.
USB 구성을 선택합니다.
템플릿 코드에서 클라이언트 드라이버는 USB 디바이스에서 기본 구성 을 선택합니다. 기본 구성에는 디바이스의 구성 0과 해당 구성 내의 각 인터페이스의 대체 설정 0이 포함됩니다.
기본 구성을 선택하기 위해 클라이언트 드라이버는 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES 함수를 호출하여 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS 구조를 구성합니다. 이 함수는 Type 멤버를 WdfUsbTargetDeviceSelectConfigTypeMultiInterface로 초기화하여 여러 인터페이스를 사용할 수 있는 경우 각 인터페이스의 대체 설정을 선택해야 함을 나타냅니다. 호출에서 기본 구성을 선택해야 하므로 클라이언트 드라이버는 SettingPairs 매개 변수에서 NULL을, NumberInterfaces 매개 변수에서 0을 지정합니다. 완료되면 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS MultiInterface.NumberOfConfiguredInterfaces 멤버는 대체 설정 0이 선택된 인터페이스 수를 나타냅니다. 다른 멤버는 수정되지 않습니다.
참고 클라이언트 드라이버가 기본 설정 이외의 대체 설정을 선택하려는 경우 드라이버는 WDF_USB_INTERFACE_SETTING_PAIR 구조의 배열을 만들어야 합니다. 배열의 각 요소는 디바이스 정의 인터페이스 번호와 선택할 대체 설정의 인덱스를 지정합니다. 해당 정보는 WdfUsbTargetDeviceRetrieveConfigDescriptor 메서드를 호출하여 가져올 수 있는 디바이스의 구성 및 인터페이스 설명자에 저장됩니다. 그런 다음 클라이언트 드라이버는 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES 호출하고 WDF_USB_INTERFACE_SETTING_PAIR 배열을 프레임워크에 전달해야 합니다.
큐 소스 코드
프레임워크 큐 개체는 특정 프레임워크 디바이스 개체 에 대한 I/O 큐를 나타냅니다. 큐 개체의 전체 소스 코드는 Queue.h 및 Queue.c에 있습니다.
Queue.h
프레임워크의 큐 개체에서 발생하는 이벤트에 대한 이벤트 콜백 루틴을 선언합니다.
Queue.h의 첫 번째 블록은 큐 컨텍스트를 선언합니다.
typedef struct _QUEUE_CONTEXT {
ULONG PrivateDeviceData; // just a placeholder
} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
디바이스 컨텍스트와 마찬가지로 큐 컨텍스트는 특정 큐에 대한 정보를 저장하기 위해 클라이언트에서 정의한 데이터 구조입니다.
다음 코드 줄은 프레임워크 큐 개체를 만들고 초기화하는 도우미 함수인 MyUSBDriver_QueueInitialize 함수를 선언합니다.
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
);
다음 코드 예제에서는 EvtIoDeviceControl 이벤트 콜백 루틴에 대한 함수 역할 형식 선언을 선언합니다. 이벤트 콜백은 클라이언트 드라이버에 의해 구현되며 프레임워크가 디바이스 I/O 제어 요청을 처리할 때 호출됩니다.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;
Queue.c
구현 파일인 Queue.c에는 pragma를 사용하여 alloc_text
드라이버의 MyUSBDriver_QueueInitialize 구현이 페이징 가능한 메모리에 있음을 지정하는 다음 코드 블록이 포함되어 있습니다.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif
WDF는 클라이언트 드라이버에 대한 요청 흐름을 처리하는 프레임워크 큐 개체를 제공합니다. 프레임워크는 클라이언트 드라이버가 WdfIoQueueCreate 메서드를 호출할 때 프레임워크 큐 개체를 만듭니다. 이 호출에서 클라이언트 드라이버는 프레임워크가 큐를 생성하기 전에 특정 구성 옵션을 지정할 수 있습니다. 이러한 옵션에는 큐가 전원 관리되는지, 길이가 0인 요청을 허용하는지 또는 드라이버의 기본 큐인지 여부가 포함됩니다. 단일 프레임워크 큐 개체는 읽기, 쓰기 및 디바이스 I/O 컨트롤과 같은 여러 유형의 요청을 처리할 수 있습니다. 클라이언트 드라이버는 각 요청에 대한 이벤트 콜백을 지정할 수 있습니다.
클라이언트 드라이버도 디스패치 유형을 지정해야 합니다. 큐 개체의 디스패치 유형은 프레임워크가 클라이언트 드라이버에 요청을 전달하는 방법을 결정합니다. 배달 메커니즘은 순차적, 병렬 또는 클라이언트 드라이버에서 정의한 사용자 지정 메커니즘에 의해 될 수 있습니다. 순차 큐의 경우 클라이언트 드라이버가 이전 요청을 완료할 때까지 요청이 배달되지 않습니다. 병렬 디스패치 모드에서 프레임워크는 I/O 관리자로부터 도착하는 즉시 요청을 전달합니다. 즉, 클라이언트 드라이버는 다른 요청을 처리하는 동안 하나의 요청을 받을 수 있습니다. 사용자 지정 메커니즘에서 클라이언트는 드라이버가 처리할 준비가 되면 프레임워크 큐 개체에서 다음 요청을 수동으로 끌어냅니다.
일반적으로 클라이언트 드라이버는 드라이버의 EvtDriverDeviceAdd 이벤트 콜백에서 큐를 설정해야 합니다. 템플릿 코드는 프레임워크 큐 개체를 초기화하는 도우미 루틴(MyUSBDriver_QueueInitialize)을 제공합니다.
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
)
{
WDFQUEUE queue;
NTSTATUS status;
WDF_IO_QUEUE_CONFIG queueConfig;
PAGED_CODE();
//
// Configure a default queue so that requests that are not
// configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
// other queues get dispatched here.
//
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchParallel
);
queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;
status = WdfIoQueueCreate(
Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue
);
if( !NT_SUCCESS(status) ) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
return status;
}
return status;
}
큐를 설정하기 위해 클라이언트 드라이버는 다음 작업을 수행합니다.
- WDF_IO_QUEUE_CONFIG 구조에서 큐의 구성 옵션을 지정합니다. 템플릿 코드는 WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE 함수를 사용하여 구조를 초기화합니다. 이 함수는 큐 개체를 기본 큐 개체로 지정하고, 전원 관리되며, 병렬로 요청을 받습니다.
- 큐에 대한 I/O 요청에 대한 클라이언트 드라이버의 이벤트 콜백을 추가합니다. 템플릿에서 클라이언트 드라이버는 디바이스 I/O 제어 요청에 대한 이벤트 콜백에 대한 포인터를 지정합니다.
- WdfIoQueueCreate를 호출하여 프레임워크에서 만든 프레임워크 큐 개체에 대한 WDFQUEUE 핸들을 검색합니다.
큐 메커니즘의 작동 방식은 다음과 같습니다. USB 디바이스와 통신하기 위해 애플리케이션은 먼저 SetDixxx 루틴 및 CreateHandle을 호출하여 디바이스에 대한 핸들을 엽니다. 이 핸들을 사용하여 애플리케이션은 특정 제어 코드를 사용하여 DeviceIoControl 함수를 호출합니다. 제어 코드의 유형에 따라 애플리케이션은 해당 호출에서 입력 및 출력 버퍼를 지정할 수 있습니다. 호출은 결국 I/O 관리자가 수신한 다음, IRP(요청)를 만들어 클라이언트 드라이버에 전달합니다. 프레임워크는 요청을 가로채 프레임워크 요청 개체를 만들고 프레임워크 큐 개체에 추가합니다. 이 경우 클라이언트 드라이버가 디바이스 I/O 제어 요청에 대한 이벤트 콜백을 등록했기 때문에 프레임워크는 콜백을 호출합니다. 또한 WdfIoQueueDispatchParallel 플래그를 사용하여 큐 개체를 만들었으므로 요청이 큐에 추가되는 즉시 콜백이 호출됩니다.
VOID
MyUSBDriver_EvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
TraceEvents(TRACE_LEVEL_INFORMATION,
TRACE_QUEUE,
"!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d",
Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);
WdfRequestComplete(Request, STATUS_SUCCESS);
return;
}
프레임워크는 클라이언트 드라이버의 이벤트 콜백을 호출할 때 애플리케이션에서 보낸 요청(및 해당 입력 및 출력 버퍼)을 보유하는 프레임워크 요청 개체에 핸들을 전달합니다. 또한 요청을 포함하는 프레임워크 큐 개체에 핸들을 보냅니다. 이벤트 콜백에서 클라이언트 드라이버는 필요에 따라 요청을 처리합니다. 템플릿 코드는 요청을 완료하기만 하면 됩니다. 클라이언트 드라이버는 더 많은 관련 작업을 수행할 수 있습니다. 예를 들어 애플리케이션이 특정 디바이스 정보를 요청하는 경우 이벤트 콜백에서 클라이언트 드라이버는 USB 제어 요청을 만들고 USB 드라이버 스택으로 보내 요청된 디바이스 정보를 검색할 수 있습니다. USB 제어 요청은 USB 제어 전송에서 설명합니다.