windows driver EvtDeviceAdd 탐구

wangki·2026년 6월 24일

windows_driver_npu

목록 보기
14/15

inf 등록 과정 포스팅에서 다루었던 과정 이후, I/O 매니저가 실제로 driver를 메모리에 로드하게 된다. DriverEntry가 호출이 되면서 WDF Driver 객체를 만들어준다. 그때 ConfigEvtDeviceAdd를 등록해 준다. 언제 그리고 누가 EvtDeviceAdd를 호출하는지 알아보겠다. 추가로 EvtDeviceAdd 내부에서는 어떤 작업을 해주는지 알아보겠다.


EvtDeviceAdd는 언제 호출이 될까?

DriverEntry가 끝나면 드라이버는 메모리에 로드되어있지만, 아직 이 드라이버가 제어할 실제 NPU 하드웨어 장치와는 연결되지 않은 상태이다.

  1. PDO의 존재
    드라이버가 로드되기 전, PCIe 버스 드라이버는 이미 NPU(디바이스)를 발견하고 커널 내부에 PDO(물리 장치 객체)라는 하드웨어 뼈대를 만들어둔다.
  2. PnP 매니저의 매칭
    PnP 매니저는 DriverEntry를 마친 NPU 드라이버 객체(Driver Object)와 이미 만들어진 PDO를 서로 매칭시켜준다.
  3. EvtDeviceAdd 호출
    PnP 매니저는 DriverEntrySTATUS_SUCCESS를 반환하면 EvtDeviceAdd를 호출해준다.

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

매개변수로 WDFDRIVERPWDFDEVICE_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);

디바이스 생성 전에만 쓰이는 임시 설정 바구니로 알고 있으면 된다.

WDFDRIVERDriverEntry에서 생성한 객체인데 EvtDeviceAdd에서는 크게 사용할 일이 없어서 UNREFERENCED_PARAMETER 처리해두었다.


EvtDeviceAdd 내부에서 해야하는 일

EvtDeviceAdd가 호출되었을 때, 해야 하는 일은 크게 아래와 같다.
1. WdfDevice 생성
2. 디바이스 컨텍스트 초기화
3. Interrupt 초기화
4. I/O Queue 생성
5. 인터페이스 생성(유저 모드에서 사용)

Interrupt 초기화를 하는 이유에 대해서는 인터럽트 MSI-X에 대한 고찰에서 다룬다.

유저 모드 어플리케이션에서 Device의 핸들을 얻어서 IRP를 발생시키는데, WDFIRPWDFREQUEST로 래핑을 해준다. 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;
	}
}

Device.c 소스


결론

EvtDeviceAdd가 어느 시점에 호출되고 내부에서 어떤 기능을 수행하는지 알아보았다. 일단 기능이 동작하도록 했었는데 내부적으로 Driver가 어떻게 동작하는지 자세히 알아볼 예정이다. 화이팅!

0개의 댓글