這一篇文章說明了在device.c文件裏,驅動程序怎樣枚舉設備,然後獲得設備資源,並將其顯示出來供開發者使用。
device.c
/*++
Module Name: device.c - Device handling events for example driver.
Abstract: This file contains the device entry points and callbacks, sucn as EvtDevicePrepareHardware and EvtDeviceReleaseHardware function.
Environment: Kernel-mode Driver Framework
Time: 20181015
--*/
#include "driver.h"
//#pragma warning(disable:4013)
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, PCIe_EvtDevicePrepareHardware)
#pragma alloc_text(PAGE, PCIe_EvtDeviceReleaseHardware)
#endif
NTSTATUS
PCIe_EvtDevicePrepareHardware(
IN WDFDEVICE Device,
IN WDFCMRESLIST ResourceList,
IN WDFCMRESLIST ResourceListTranslated
)
{
ULONG i;
PDEVICE_CONTEXT pDeviceContext; //設備上下文結構體的句柄
PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor; //設備資源描述符
PINTERRUPT_CONTEXT pInterruptContext; //中斷上下文結構體的句柄
NTSTATUS status;
PAGED_CODE();
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDevicePrepareHardware' begins\n"));
pDeviceContext = GetDeviceContext(Device);
pDeviceContext->MemBaseAddress0 = NULL;
pDeviceContext->MemBaseAddress1 = NULL;
pDeviceContext->MemBaseAddress2 = NULL;
pDeviceContext->Counter_i = 0;
for (i = 0; i < WdfCmResourceListGetCount(ResourceListTranslated); i++) {
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "i is %0d\n", i));
//獲取到設備資源描述符
descriptor = WdfCmResourceListGetDescriptor(ResourceListTranslated, i);
if (!descriptor) {
return STATUS_DEVICE_CONFIGURATION_ERROR;
}
//根據不同類型,將設備資源描述符轉化爲開發者可用的類型,這裏主要用到了memory和interrupt兩種資源
switch (descriptor->Type)
{
case CmResourceTypeMemory:
if (i == 0) {
//獲取到設備存儲資源的物理地址
pDeviceContext->PhysicalAddressRegister0 = descriptor->u.Memory.Start.LowPart;
//獲取到設備存儲資源在系統內核模式下的虛擬地址(後續對於寄存器的訪問也將用到這個地址)
pDeviceContext->MemBaseAddress0 = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
if (!pDeviceContext->MemBaseAddress0) {
return STATUS_INSUFFICIENT_RESOURCES;
}
pDeviceContext->MemLength0 = descriptor->u.Memory.Length;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "MemBaseAddress0 is 0x%p\n", pDeviceContext->MemBaseAddress0));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.Memory.Length is %0x\n", (ULONG*)descriptor->u.Memory.Length));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "PhysicalAddressRegister0 is %0x\n", (ULONG*)pDeviceContext->PhysicalAddressRegister0));
}
if (i == 2) {
pDeviceContext->PhysicalAddressRegister1 = descriptor->u.Memory.Start.LowPart;
pDeviceContext->MemBaseAddress1 = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
if (!pDeviceContext->MemBaseAddress1) {
return STATUS_INSUFFICIENT_RESOURCES;
}
pDeviceContext->MemLength1 = descriptor->u.Memory.Length;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "MemBaseAddress1 is 0x%p\n", pDeviceContext->MemBaseAddress1));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.Memory.Length is %0x\n", (ULONG*)descriptor->u.Memory.Length));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "PhysicalAddressRegister1 is %0x\n", (ULONG*)pDeviceContext->PhysicalAddressRegister1));
}
if (i == 4) {
pDeviceContext->PhysicalAddressRegister2 = descriptor->u.Memory.Start.LowPart;
pDeviceContext->MemBaseAddress2 = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
if (!pDeviceContext->MemBaseAddress2) {
return STATUS_INSUFFICIENT_RESOURCES;
}
pDeviceContext->MemLength2 = descriptor->u.Memory.Length;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "MemBaseAddress2 is 0x%p\n", pDeviceContext->MemBaseAddress2));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.Memory.Length is %0x\n", (ULONG*)descriptor->u.Memory.Length));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "PhysicalAddressRegister2 is %0x\n", (ULONG*)pDeviceContext->PhysicalAddressRegister2));
}
break;
case CmResourceTypeInterrupt:
if (CM_RESOURCE_INTERRUPT_MESSAGE & descriptor->Flags)//判斷是否爲MSI中斷
{
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.MessageInterrupt.Translated.Level is %x\n", descriptor->u.MessageInterrupt.Translated.Level));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.MessageInterrupt.Translated.Vector is %x\n", descriptor->u.MessageInterrupt.Translated.Vector));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.MessageInterrupt.Raw.MessageCount is %x\n", descriptor->u.MessageInterrupt.Raw.MessageCount));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "descriptor->u.MessageInterrupt.Raw.Vector is %x\n", descriptor->u.MessageInterrupt.Raw.Vector));
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "The Interrupt descriptor analysis begins!\n"));
//設置IoConnectInterruotEx函數所需的變量
IO_CONNECT_INTERRUPT_PARAMETERS params;
RtlZeroMemory(¶ms, sizeof(params));
params.Version = CONNECT_MESSAGE_BASED;
params.MessageBased.PhysicalDeviceObject = pDeviceContext->PhysicalDeviceObject;
params.MessageBased.MessageServiceRoutine = PCIe_InterruptMessageService;
params.MessageBased.ServiceContext = (PVOID)&pDeviceContext->InterruptContext;
params.MessageBased.SynchronizeIrql = 0;
params.MessageBased.FloatingSave = FALSE;
params.MessageBased.ConnectionContext.InterruptObject = &pDeviceContext->Interrupt;
params.MessageBased.SpinLock = &pDeviceContext->SpinLock;
//將設備上下文賦給中斷上下文(後續使用MSI時很重要)
pDeviceContext->InterruptContext.DeviceContext = pDeviceContext;
//Default state is low
pDeviceContext->State = 0;
status = IoConnectInterruptEx(¶ms);
if (NT_SUCCESS(status))
{
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "The MSI gets interrupt rountine successfully!\n"));
}
else
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "The MSI gets interrupt rountine falied!\n"));
break;
}
else
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "The Interrupt type is not MSI !\n"));
default:
break;
}
}
pDeviceContext->Counter_i = i;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDevicePrepareHardware' completes successfully\n"));
return STATUS_SUCCESS;
}
//在拔出設備時,最後需要將獲得的設備資源全部解映射,不然下次使用會出錯
NTSTATUS
PCIe_EvtDeviceReleaseHardware(
IN WDFDEVICE Device,
IN WDFCMRESLIST ResourceListTranslated
)
{
PDEVICE_CONTEXT pDeviceContext;
PAGED_CODE();
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDeviceReleaseHardware' begins\n"));
pDeviceContext = GetDeviceContext(Device);
if (pDeviceContext->MemBaseAddress0) {
MmUnmapIoSpace(pDeviceContext->MemBaseAddress0, pDeviceContext->MemLength0);
pDeviceContext->MemBaseAddress0 = NULL;
}
if (pDeviceContext->MemBaseAddress1) {
MmUnmapIoSpace(pDeviceContext->MemBaseAddress1, pDeviceContext->MemLength1);
pDeviceContext->MemBaseAddress1 = NULL;
}
if (pDeviceContext->MemBaseAddress2) {
MmUnmapIoSpace(pDeviceContext->MemBaseAddress2, pDeviceContext->MemLength2);
pDeviceContext->MemBaseAddress2 = NULL;
}
if (pDeviceContext->Interrupt != NULL)
{
IO_DISCONNECT_INTERRUPT_PARAMETERS params;
params.Version = CONNECT_MESSAGE_BASED;
params.ConnectionContext.InterruptObject = pDeviceContext->Interrupt;
IoDisconnectInterruptEx(¶ms);
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Interrupt disconnnects successfully!\n"));
}
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDeviceReleaseHardware' completes successfully!\n"));
return STATUS_SUCCESS;
}
//即插即用的電源回調例程
NTSTATUS PCIe_EvtDeviceD0Entry(
IN WDFDEVICE Device,
IN WDF_POWER_DEVICE_STATE PreviousState
)
{
NTSTATUS status = STATUS_SUCCESS;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'EvtDeviceD0Entry' completes successfully\n"));
return STATUS_SUCCESS;
}
NTSTATUS PCIe_EvtDeviceD0Exit(
IN WDFDEVICE Device,
IN WDF_POWER_DEVICE_STATE TargetState
)
{
NTSTATUS status = STATUS_SUCCESS;
KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'EvtDeviceD0Exit' completes successfully\n"));
return STATUS_SUCCESS;
}
- device.c包含了PCIe_EvtDevicePrepareHardware、PCIe_EvtDeviceReleaseHardwar兩個回調例程,這兩個例程可以讓硬件設別的資源在windows操作系統上映射並分配出來。DevicePrepareHardware例程任務包括獲得內存資源,內存物理地址和系統內核虛擬地址的映射,中斷資源的分配等。
- 需要說明的是,在這份代碼裏,我分配了三個BAR的內存資源,這是因爲在FPGA板卡設計時就預先分配了三個BAR,而且每個BAR後續都需要使用到,所以需要得到這三個BAR對應的系統內核態虛擬地址的首地址,才做了針對性的地址映射,所以讀者可以根據自己的需求來選擇映射哪段內存資源。
另外一點是中斷資源的分配,前一篇文章已經提到了,使用WDF的MSI中斷流程我們沒有獲取到中斷信號,但是FPGA寄存器的狀態又顯示中斷已經觸發,所以也是很煩。這個過程其實應當由KMDF框架來做,MSDN裏有相關介紹,但是在我們的設備上好像沒有起作用,所以最後選擇基於WDM自行設計,原理在MSDN上有講。如果讀者可以基於KMDF框架實現的話,就不用看我這一部分的代碼了。
首先需要將MSI中斷所需參數配置進IoConnectInterruptEx例程,同時也要將MSI中斷觸發後的回調例程配置進來,就是如下的兩句代碼。
params.MessageBased.MessageServiceRoutine = PCIe_InterruptMessageService;
status = IoConnectInterruptEx(¶ms);
這樣當MSI中斷觸發後,回調例程PCIe_InterruptMessageService開始執行,甚至在其中可以加上InterruptDpc例程,中斷例程最好不要執行過多內容,僅僅用來查看MSI中斷狀態和清除中斷位即可,其餘操作可以放入Dpc例程執行,詳細過程queue.c裏再說明。