windows에서는 별다른 설정을 해주지 않으면 기본으로 레거시 인터럽트를 사용한다. 따라서 MSI-X 인터럽트를 사용하기 위해서는 INF파일에 추가로 설정해 줘야 한다.
[npu_driver_HW_Reg]
HKR, "Interrupt Management", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", "MSISupported", 0x00010001, 1
HKR, "Interrupt Management\MessageSignaledInterruptProperties", "MessageNumberLimit", 0x00010001, 4
INF 파일에 [npu_driver_HW_Reg]섹션에 위처럼 설정하였다. 레지스트리의 컴퓨터\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum Path에 저장이 된다.
Enum은 경로는 윈도우가 인식한 모든 하드웨어 장치의 정보가 열거되어 있는 핵심 레지스트리 위치이다.
하위 키 구조로는 버스 유형(PCI 등) -> 기기 식별자(ID) -> 인스턴스 ID 순으로 내려간다.

위 사진을 보면 실제로 google coral tpu 장치가 PCI로 인식이 되어서 하드웨어 하위 키로 인스턴스 ID를 생성하는 것을 확인할 수 있다.
HKR, "Interrupt Management", 0x00000010을 해석해 보면, "0x00000010이라는 플래그 명령에 따라 Device Parameters\Interrupt Management 라는 레지스트리 경로(키)를 만들어라" 이다.
HKR, "Interrupt Management\MessageSignaledInterruptProperties", 0x00000010 이것의 의미도 동일하게 Device Parameters\Interrupt Management\MessageSignaledInterruptProperties을 만들라는 것이다.
HKR, "Interrupt Management\MessageSignaledInterruptProperties", "MSISupported", 0x00010001, 1 이것의 의미는, "Device Parameters\Interrupt Management\MessageSignaledInterruptProperties으로 접근해서 MSISupported의 값을 만들고 DWORD타입으로 실제 값을 1로 주어라"라는 의미로 해석하면 된다.
HKR, "Interrupt Management\MessageSignaledInterruptProperties", "MessageNumberLimit", 0x00010001, 4 이것의 의미도 마찬가지로 MessageNumberLimit 값에 실제로 4라는 값을 설정해라는 것이다.
최종적으로 생성된 값들을 확인하면 아래와 같다.

그러면 이후에 PnP 매니저가 리소스 결정 및 할당할 때, MSISupproted값이 1로 되어있는 것을 확인하게 되면 레거시 인터럽트 방식 대신에 MSI리소스를 할당해 준다.
WdfInterruptCreate MSDN
위 WdfInterruptCreate함수의 MSDN을 확인해보니 아래와 같이 쓰여져 있다.

일반적으로는 EvtDeviceAdd에서 인터럽트 객체를 만들지만, EvtDevicePrepareHardware에서도 호출할 수 있다는 것 같다. 이 부분은 좀 더 학습해야 할 부분인 것 같다. 지금은 EvtDeviceAdd에서 생성하는 것을 다루도록 하겠다.
INF에 설정한 인터럽트 개수만큼 Object를 생성해 주었다.
{
deviceContext->IsrCallCount = 0;
for (ULONG i = 0; i < 4; ++i) {
WDF_INTERRUPT_CONFIG interruptConfig;
WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
npudriverEvtInterruptIsr,
npudriverEvtInterruptDpcNew);
interruptConfig.EvtInterruptEnable = npudriverEvtInterruptEnable;
interruptConfig.EvtInterruptDisable = npudriverEvtInterruptDisable;
status = WdfInterruptCreate(device, &interruptConfig,
WDF_NO_OBJECT_ATTRIBUTES, &deviceContext->Interrupts[i]);
if (!NT_SUCCESS(status)) {
DbgPrint("[%s] WdfInterruptCreate[%lu] failed: 0x%x\n",
__FUNCTION__, i, status);
return status;
}
DbgPrint("[%s] WdfInterruptCreate[%lu] OK (Interrupts[%lu]=%p)\n",
__FUNCTION__, i, i, deviceContext->Interrupts[i]);
}
}
이렇게 해두면 WDF가 내부적으로 인터럽트 오브젝트와 인터럽트 벡터를 연결해서 npu가 인터럽트를 발생시켰을 때, 등록한 ISR이 호출된다고 한다.
등록되는 과정은 추후에 더 자세히 다루도록 하겠다.
npu driver를 개발하며 MSI-X에 대한 개념을 익혔지만 내부적으로 어떻게 동작이 되고 사용이 되는지 좀 더 자세히 알아보았다.
추가로 ISR, DPC 내부에서 로직을 어떻게 처리하는지, WDF 내부에서 어떻게 미리 생성한 Interrupt Object와 연결해 주는지에 대해서 자세히 알아봐야겠다.
항상 잘 보고 있습니다!