驅動程序(5) WDF下DMA傳輸的驅動程序代碼詳細說明之device.c

這一篇文章說明了在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(&params, 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(&params);
				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(&params);
		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;
}
  1. device.c包含了PCIe_EvtDevicePrepareHardware、PCIe_EvtDeviceReleaseHardwar兩個回調例程,這兩個例程可以讓硬件設別的資源在windows操作系統上映射並分配出來。DevicePrepareHardware例程任務包括獲得內存資源,內存物理地址和系統內核虛擬地址的映射,中斷資源的分配等。
  2. 需要說明的是,在這份代碼裏,我分配了三個BAR的內存資源,這是因爲在FPGA板卡設計時就預先分配了三個BAR,而且每個BAR後續都需要使用到,所以需要得到這三個BAR對應的系統內核態虛擬地址的首地址,才做了針對性的地址映射,所以讀者可以根據自己的需求來選擇映射哪段內存資源。
    另外一點是中斷資源的分配,前一篇文章已經提到了,使用WDF的MSI中斷流程我們沒有獲取到中斷信號,但是FPGA寄存器的狀態又顯示中斷已經觸發,所以也是很煩。這個過程其實應當由KMDF框架來做,MSDN裏有相關介紹,但是在我們的設備上好像沒有起作用,所以最後選擇基於WDM自行設計,原理在MSDN上有講。如果讀者可以基於KMDF框架實現的話,就不用看我這一部分的代碼了。
    首先需要將MSI中斷所需參數配置進IoConnectInterruptEx例程,同時也要將MSI中斷觸發後的回調例程配置進來,就是如下的兩句代碼。
params.MessageBased.MessageServiceRoutine = PCIe_InterruptMessageService;
status = IoConnectInterruptEx(&params);

這樣當MSI中斷觸發後,回調例程PCIe_InterruptMessageService開始執行,甚至在其中可以加上InterruptDpc例程,中斷例程最好不要執行過多內容,僅僅用來查看MSI中斷狀態和清除中斷位即可,其餘操作可以放入Dpc例程執行,詳細過程queue.c裏再說明。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章