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

從這一篇開始介紹幾個比較重要的源文件和應用程序,並且會對其中一些關鍵代碼進行說明。這些代碼流程都是本人親身測試可行的,但是由於當時調試時雜七雜八的東西加的太多,現在看起來有的地方的代碼風馬牛不相及,如果完全照搬的話可能行不通的,還是需要各位讀者自行理解然後加以改進的。當然如果有問題也歡迎各位讀者指出更正,謝謝!

driver.c

/*++
Module Name: driver.c
Abstract:This file contains the driver entry points and callbacks, sucn as DriverEnrty and EvtDeviceAdd function.
Environment: Kernel-mode Driver Framework
Time: 20181015
--*/

#include "driver.h"
/*
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, PCIe_EvtDeviceAdd)
#pragma alloc_text (PAGE, PCIe_EvtDriverContextCleanup)
#endif
*/

NTSTATUS InitializeDMA(IN WDFDEVICE Device);
#define MAXNLEN 4096 //DMA傳輸最大字節長度

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
/*++
Routine Description:
    DriverEntry initializes the driver and is the first routine called by the
    system after the driver is loaded. DriverEntry specifies the other entry
    points in the function driver, such as EvtDevice and DriverUnload.
    
Parameters Description:
    DriverObject - represents the instance of the function driver that is loaded
    into memory. DriverEntry must initialize members of DriverObject before it
    returns to the caller. DriverObject is allocated by the system before the
    driver is loaded, and it is released by the system after the system unloads
    the function driver from memory.
    
    RegistryPath - represents the driver specific path in the Registry.
    The function driver can use the path to store driver related data between
    reboots. The path does not store hardware instance specific data.

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise.
--*/
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status = STATUS_SUCCESS;
    WDF_OBJECT_ATTRIBUTES attributes;

	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'DriverEntry' begins\n"));
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);      //設備屬性初始化
    WDF_DRIVER_CONFIG_INIT(&config, PCIe_EvtDeviceAdd); //初始化WDF_DRIVER_CONFIG,並提供DeviceAdd函數

	//創建驅動對象
    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        return status;
    }

	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'DriverEntry' completes successfully and Create wdf driver object\n"));
	//attributes.EvtCleanupCallback = PCIe_EvtDriverContextCleanup;
    return status;
}

NTSTATUS
PCIe_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
/*++
Routine Description:
    EvtDeviceAdd is called by the framework in response to AddDevice
    call from the PnP manager. We create and initialize a device object to
    represent a new instance of the device.
Arguments:
    Driver - Handle to a framework driver object created in DriverEntry
    DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
Return Value:
    NTSTATUS
--*/
{
    NTSTATUS status = STATUS_SUCCESS;
	WDFDEVICE device;
	WDF_OBJECT_ATTRIBUTES deviceAttributes;
	PDEVICE_CONTEXT pDeviceContext;
	WDFQUEUE queue;
	WDF_IO_QUEUE_CONFIG queueConfig;
	WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
	WDF_INTERRUPT_CONFIG interruptConfig;

    //UNREFERENCED_PARAMETER(Driver);
	PAGED_CODE(); //表示該例程代碼佔用分頁內存,只能在PASSIVE_LEVEL中斷級別調用,否則會藍屏;如不說明,則佔用非分頁內存
    //status = PCIe_CreateDevice(DeviceInit);
	
	//採用WdfDeviceIoDirect方式
	WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);

	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDeviceAdd' begins\n"));

	//初始化即插即用例程
	WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
	pnpPowerCallbacks.EvtDevicePrepareHardware = PCIe_EvtDevicePrepareHardware;
	pnpPowerCallbacks.EvtDeviceReleaseHardware = PCIe_EvtDeviceReleaseHardware;
	pnpPowerCallbacks.EvtDeviceD0Entry = PCIe_EvtDeviceD0Entry;
	pnpPowerCallbacks.EvtDeviceD0Exit = PCIe_EvtDeviceD0Exit;
	WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

	//定義設備屬性
	WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
	deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice;

	//創建一個設備
	status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
	if (!NT_SUCCESS(status)) {
		KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Create device failed\n"));
		return status;
	}
	pDeviceContext = GetDeviceContext(device);

	//MSI中斷
	pDeviceContext->PhysicalDeviceObject = WdfDeviceWdmGetPhysicalDevice(device);
	
	//DMA模塊初始化
	status = InitializeDMA(device);
	if (!NT_SUCCESS(status))
	{
		return status;
	}
	
	//初始化隊列
	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "IO_QUEUE Initializing begins\n"));
	//用default初始化default隊列
	WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
		&queueConfig,
		WdfIoQueueDispatchParallel
	);
	queueConfig.EvtIoDeviceControl = PCIe_EvtIoDeviceControl;
	
	//創建隊列
	status = WdfIoQueueCreate(
		device,
		&queueConfig,
		WDF_NO_OBJECT_ATTRIBUTES,
		&queue
	);
	if (!NT_SUCCESS(status)) {
		KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "IO_DEFALUT_QUEUE Initializing failed\n"));
		return status;
	}

	//創建驅動程序接口與應用程序通信
	status = WdfDeviceCreateDeviceInterface(
		device,
		(LPGUID)&GUID_DEVINTERFACE_PCIe,
		NULL);
	if (!NT_SUCCESS(status)) {
		return status;
	}
	
	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'PCIe_EvtDeviceAdd' completes successfully\n"));
	return status;
}

NTSTATUS
InitializeDMA(
	IN WDFDEVICE Device
)
{
	NTSTATUS status;
	PDEVICE_CONTEXT pDeviceContext;
	WDF_DMA_ENABLER_CONFIG dmaConfig;

	pDeviceContext = GetDeviceContext(Device);

	//設置DMA數據緩衝區地址邊界:16字節對齊
	WdfDeviceSetAlignmentRequirement(Device, FILE_OCTA_ALIGNMENT);
	//初始化DMA_ENABLER
	WDF_DMA_ENABLER_CONFIG_INIT(&dmaConfig, WdfDmaProfileScatterGather64Duplex, MAXNLEN);


	//創建一個DMA適配器
	status = WdfDmaEnablerCreate(Device, &dmaConfig, WDF_NO_OBJECT_ATTRIBUTES, &pDeviceContext->DmaEnabler);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "WdfDmaEnabler Create Failed !\n"));
		return status;
	}

	
	//分配DMA公用緩衝區
	status = WdfCommonBufferCreate(pDeviceContext->DmaEnabler, MAXNLEN, WDF_NO_OBJECT_ATTRIBUTES, &pDeviceContext->CommonBuffer);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "WdfCommonBuffer Create Failed !\n"));
		return status;
	}
	//獲取申請到buffer的虛擬地址和邏輯地址
	pDeviceContext->CommonBufferBase = WdfCommonBufferGetAlignedVirtualAddress(pDeviceContext->CommonBuffer);
	pDeviceContext->CommonBufferBaseLA = WdfCommonBufferGetAlignedLogicalAddress(pDeviceContext->CommonBuffer);
	//將申請到的buffer全部用0來填充
	RtlZeroMemory(pDeviceContext->CommonBufferBase, MAXNLEN);
	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "CommonBuffer VA is 0x%p, LA is 0x%016I64x, Len is 0x%0x !\n", 
			 pDeviceContext->CommonBufferBase, pDeviceContext->CommonBufferBaseLA.QuadPart, WdfCommonBufferGetLength(pDeviceContext->CommonBuffer)));


	//創建一個DMA傳輸
	status = WdfDmaTransactionCreate(pDeviceContext->DmaEnabler, WDF_NO_OBJECT_ATTRIBUTES, &pDeviceContext->DmaTransaction);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "WdfDmaDmaTransaction Create Failed !\n"));
		return status;
	}
	
	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "DMA Initialize Successfullly !\n\n"));
	return status;
}



//這個函數雖然聲明瞭,但是KMDF框架會在設備拔出時回收一些資源,所以看起來這個函數沒有特別大的作用
VOID
PCIe_EvtDriverContextCleanup(
    _In_ WDFOBJECT DriverObject
    )
{
    UNREFERENCED_PARAMETER(DriverObject);
    PAGED_CODE();

}


VOID PCIe_EvtDeviceFileCreate(
	IN WDFDEVICE Device,
	IN WDFREQUEST Request,
	IN WDFFILEOBJECT FileObject)
{
	NTSTATUS status = STATUS_SUCCESS;
	KdPrintEx((DPFLTR_IHVAUDIO_ID, DPFLTR_ERROR_LEVEL, "Function 'EvtDeviceFileCreate' completes successfully\n"));
	WdfRequestComplete(Request, status);
}
  1. driver.c文件時整個驅動程序的入口,包含了DriverEntry、EvtDeviceAdd、InitializeDMA幾個主要的回調例程。
  2. DriverEntry例程相當於C語言裏的main函數,所有的代碼執行都是從這個例程開始,在這裏我們註冊了驅動程序的PCIe_EvtDeviceAdd例程,創建一個驅動對象,告訴框架現在有一個設備的驅動程序要註冊進來了。
  3. PCIe_EvtDeviceAdd例程會在操作系統枚舉設備時被調用,主要任務包括:初始化即插即用例程(WDF框架已經做好了很多工作,註冊下該例程就行了)、定義設備屬性(選擇了同步模式)、創建一個設備、聲明一個MSI中斷的物理設備(這裏我們沒有采用WDF框架下的MSI中斷流程,因爲實際測試時發現中斷沒觸發到,迫不得已花了好久時間基於WDM重新做了一下MSI中斷的流程)、DMA模塊初始化(創建DMA傳輸流程的必備對象和申請buffer)、初始化消息請求隊列(用來和應用程序通信的)、創建驅動程序與應用程序通信(只有做了這一步,應用程序才能找到對應的驅動程序設備)。
  4. InitializeDMA例程主要是爲DMA傳輸做好準備工作,包括:創建DMAEnabler、CommonBuffer、DMATransaction三個關鍵對象,也設置了DMA數據緩衝區地址邊界對齊數,同時由申請到的buffer可以取得對應的虛擬地址首地址和物理地址首地址以及長度。MSDN詳細介紹了DMA傳輸的相關流程是怎樣操作的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章