inf 등록 과정 포스팅에서 다루었던 과정 이후, I/O 매니저가 실제로 driver를 메모리에 로드하게 된다. DriverEntry가 호출이 되면서 WDF Driver 객체를 만들어준다. 그때 Config로 EvtDeviceAdd를 등록해 준다. 언제 그리고 누가 EvtDeviceAdd를 호출하는지 알아보겠다. 추가로 EvtDeviceAdd 내부에서는 어떤 작업을 해주는지 알아보겠다.
DriverEntry가 끝나면 드라이버는 메모리에 로드되어있지만, 아직 이 드라이버가 제어할 실제 NPU 하드웨어 장치와는 연결되지 않은 상태이다.
DriverEntry를 마친 NPU 드라이버 객체(Driver Object)와 이미 만들어진 PDO를 서로 매칭시켜준다.DriverEntry가 STATUS_SUCCESS를 반환하면 EvtDeviceAdd를 호출해준다. 실제 npu_driver 소스를 보겠다.
NTSTATUS
npudriverEvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE();
DbgPrint("[%s] Entry\n", __FUNCTION__);
status = npudriverCreateDevice(DeviceInit);
DbgPrint("[%s] Exit\n", __FUNCTION__);
return status;
}
매개변수로 WDFDRIVER 와 PWDFDEVICE_INIT를 받는다.
특히 PWDFDEVICE_INIT은 생명 주기가 특이한데 WdfDeviceCreate을 호출하기 전에 사용된다. 아래와 같이 사용한다.
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDevicePrepareHardware = npudriverEvtDevicePrepareHardware;
pnpPowerCallbacks.EvtDeviceReleaseHardware= npudriverEvtDeviceReleaseHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
// Set up file object config with cleanup callback
WDF_FILEOBJECT_CONFIG_INIT(&fileObjectConfig, NULL, NULL, npudriverEvtFileCleanup);
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileObjectConfig, WDF_NO_OBJECT_ATTRIBUTES);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
디바이스 생성 전에만 쓰이는 임시 설정 바구니로 알고 있으면 된다.
WDFDRIVER는 DriverEntry에서 생성한 객체인데 EvtDeviceAdd에서는 크게 사용할 일이 없어서 UNREFERENCED_PARAMETER 처리해두었다.
EvtDeviceAdd가 호출되었을 때, 해야 하는 일은 크게 아래와 같다.
1. WdfDevice 생성
2. 디바이스 컨텍스트 초기화
3. Interrupt 초기화
4. I/O Queue 생성
5. 인터페이스 생성(유저 모드에서 사용)
Interrupt 초기화를 하는 이유에 대해서는 인터럽트 MSI-X에 대한 고찰에서 다룬다.
유저 모드 어플리케이션에서 Device의 핸들을 얻어서 IRP를 발생시키는데, WDF가 IRP를 WDFREQUEST로 래핑을 해준다. Device에 연결된 I/O Queue에 들어가게 되는데, 따라서 Device 생성 시점에, Queue를 만들어준다.
// 인터페이스 핸들 생성
status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_npudriver, NULL);
if (NT_SUCCESS(status))
{
// Initialize the I/O Queue
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoDeviceControl = npudriverEvtIoDeviceControl;
status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &deviceContext->IoQueue);
if (!NT_SUCCESS(status)) {
DbgPrint("[%s] WdfIoQueueCreate failed: 0x%x\n", __FUNCTION__, status);
return status;
}
}
EvtDeviceAdd가 어느 시점에 호출되고 내부에서 어떤 기능을 수행하는지 알아보았다. 일단 기능이 동작하도록 했었는데 내부적으로 Driver가 어떻게 동작하는지 자세히 알아볼 예정이다. 화이팅!