Windows Driver Model >> Pascal

AddDevice()


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

AddDevice()副程式定義如下:

function AddDevice(:PDRIVER_OBJECT; :PDEVICE_OBJECT):NTSTATUS; stdcall;

傳入的PDRIVER_OBJECT是該驅動程式的Driver Object,而PDEVICE_OBJECT則是位於下層的驅動程式Device Object,WDM驅動程式的架構是使用堆疊方式做驅動程式的添加、刪除,例如:如果使用者寫的是USB驅動程式,則下層可能就是USB Bus驅動程式,如果使用者寫的驅動程式只是一個虛擬的純軟體驅動程式,那麼下層驅動程式就是I/O Manager,由於使用堆疊的架構,所以WDM驅動程式又可以加入Upper Filter、Lower Filter Driver,Filter Driver的目的是提供I/O Request Packet(IRP)修改的功能,達到不需更改原始的驅動程式就可以做錯誤修正或者新增功能。

那在AddDevice()需要做什麼事情呢?一般會產生一個新的Device Object並為該Device Object建造一條Symbolic Link,該Symbolic Link就是提供給User Application開啟(僅能使用CreateFile() API開啟),還記得呼叫CreateFile()時會提供一個名字嗎?若記得的話,此名字就是驅動程式的Symbolic Link名稱。

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

最後還要附加到下層驅動程式並且初始化相關旗標,這樣I/O Manager才可以開始處理相關IRP,為何要附加到下層驅動程式呢?這是因為Windows驅動程式本身就是堆疊架構(從DOS驅動程式開始),因此,需要串接到這個架構中,傳遞資料的方式也是串接方式,因此,要取得下層驅動程式的IRP指標時,是透過指標加減運算而得,如:IoGetNextIrpStackLocation()就是透過減掉IO_STACK_LOCATION而拿到的指標位置,使用者大概知>道這個觀念就可以,細節在之後可以慢慢體會。

範例:

DEV_NAME = '\Device\MyDriver';
SYM_NAME = '\DosDevices\MyDriver';

function AddDevice(pOurDriver:PDRIVER_OBJECT; pPhyDevice:PDEVICE_OBJECT):NTSTATUS; stdcall;
var
  suDevName: UNICODE_STRING;
  suSymName: UNICODE_STRING;
  pOurDevice: PDEVICE_OBJECT;
  
begin
  // Step 1
  RtlInitUnicodeString(@suDevName, DEV_NAME);
  RtlInitUnicodeString(@suSymName, SYM_NAME);
  IoCreateDevice(pOurDriver, 0, @suDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, pOurDevice);
  Result:= IoCreateSymbolicLink(@suSymName, @suDevName);

  // Step 2
  pNextDevice:= IoAttachDeviceToDeviceStack(pOurDevice, pPhyDevice);

  // Step 3
  pOurDevice^.Flags:= pOurDevice^.Flags or DO_BUFFERED_IO;
  pOurDevice^.Flags:= pOurDevice^.Flags and not DO_DEVICE_INITIALIZING;
end;

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


返回上一頁