驅動程式 - Kernel Mode Driver Framework (KMDF) - 教學說明 - 2. 系統透過呼叫AddDevice()來新增裝置



參考資訊:
https://wasm.in/
http://four-f.narod.ru/
https://github.com/steward-fu/ddk

當系統找到對應的裝置(透過INF檔案安裝)且驅動程式被系統載入後,AddDevice()就會被系統呼叫,而AddDevice()是在DriverEntry()裡面註冊的,所以系統才會知道AddDevice()位於何處,名稱不一定要用AddDevice,但是參數跟回傳值必須遵照Microsoft的定義,否則會有問題

AddDevice()定義如下

NTSTATUS AddDevice(WDFDRIVER, PWDFDEVICE_INIT);

相較於WDM驅動程式,KMDF驅動程式傳入的參數已經改成WDF專用的Object,WDFDRIVER是在DriverEntry()時產生的Object,而PWDFDEVICE_INIT則是一個由WDF配置以及初始化的Object,相關初始化的設定都依賴這個Object做設定,雖然Microsoft官方並沒有對此Object做說明,不過司徒翻了一下定義,發現只有一個PVOID定義

那在AddDevice()需要做什麼事情呢?

1. 初始化File相關旗標以及Callback,如:FileOpen、FileClose
2. 產生新的Device Object
3. 建造一條Symbolic Link
4. 初始化其餘Callback,如:FileRead、FileWrite、IOCTL

該Symbolic Link就是提供給User Application開啟(僅能使用CreateFile() API開啟),還記得呼叫CreateFile()時會提供一個名字嗎?若記得的話,此名字就是驅動程式的Symbolic Link名稱,那問題又來了,有沒有可能其它驅動程式也使用同一個Symbolic Link名字呢?答案是,肯定會發生的,所以Microsoft建議大家使用GUID的方式註冊,使用者可以使用工具產生新的GUID名稱,並使用該GUID註冊裝置,避免名稱衝突,如果是使用GUID方式註冊,那User Application該如何開啟該驅動程式呢?這時候就必須使用Setup API做GUID列舉並取得Symbolic Link名稱,哪一種方式比較好呢?如果是使用Symbolic Link註冊名稱,User Application比較好寫,因為名稱已經知道了,反之,使用GUID註冊的話,User Application需要列舉判斷後才能開啟,所以會比較不好寫,但是優點則是名稱不會衝突

範例:

#define DEV_NAME L"\\Device\\MyDriver"
#define SYM_NAME L"\\DosDevices\\MyDriver"

NTSTATUS AddDevice(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{
    WDFDEVICE device;
    UNICODE_STRING suDevName;
    UNICODE_STRING szSymName;
    WDF_FILEOBJECT_CONFIG file_cfg;
    WDF_IO_QUEUE_CONFIG ioqueue_cfg;

    // Step 1
    WdfDeviceInitSetIoType(pDeviceInit, WdfDeviceIoBuffered);
    WDF_FILEOBJECT_CONFIG_INIT(&file_cfg, IrpFileCreate, IrpFileClose, NULL);
    WdfDeviceInitSetFileObjectConfig(pDeviceInit, &file_cfg, WDF_NO_OBJECT_ATTRIBUTES);

    // Step 2
    RtlInitUnicodeString(&suDevName, DEV_NAME);
    RtlInitUnicodeString(&szSymName, SYM_NAME);
    WdfDeviceInitAssignName(pDeviceInit, &suDevName);
    WdfDeviceCreate(&pDeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
    WdfDeviceCreateSymbolicLink(device, &szSymName);

    // Step 3 
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioqueue_cfg, WdfIoQueueDispatchSequential);
    ioqueue_cfg.EvtIoRead = IrpRead;
    ioqueue_cfg.EvtIoWrite = IrpWrite;
    return WdfIoQueueCreate(device, &ioqueue_cfg, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
}

Step 1:初始化File Callback以及相關旗標,比較需要注意的是WdfDeviceIoBuffered旗標,因為在做裝置讀寫時,User Application跟驅動程式是否共用同一塊Buffer是取決於該旗標,如果使用者設定成WdfDeviceIoBuffered,則代表驅動程式有自己獨立一塊Buffer,驅動程式讀取完硬體資料後,會複製到它自己的Buffer,然後再複製到User Application的Buffer,所以速度會比較慢一些,如果要共用同一塊Buffer的話,則把旗標設定成WdfDeviceIoDirect即可
Step 2:產生一個Device Object(可自己決定名稱),然後建立一條Symbolic Link(可自己決定名稱),Device Object名稱一般是放在Windows特殊資料夾中的Device資料夾,使用者可以使用WinObj程式去查看有哪些Device Object,而Symbolic Link的名稱則是放在DosDevices資料夾(GLOBAL??),那User Application該如何把完整路徑名稱給CreateFile()呢?答案是加上\.\\關鍵字,有印象開啟COM Port時,需要使用這樣CreateFile("\\.\\\\COM1", ...);的方式嗎?這就是代表完整路徑的意思,在寫COM Port程式時,不一定說是要大於COM9才能加\.\\路徑,其實從COM1就可以開始使用,因為那是Global的名稱表示方式
Step 3:初始化其餘Callback