控制器和多功能設備驅動

 

控制器和多功能設備
在第六章提到過,有兩種類型的設備不太符合PNP的框架。它們是控制器設備和多功能設備。控制器設備管理一些子設備,而多功能設備在同一個設備上有幾種功能。它們的共同之處在於,必須使用獨立的I/O資源來創建多個設備對象。
在Windows XP 下,支持那些遵守各自總線標準的設備很容易,例如: PCI, PCMCIA,USB設備等。 PCI 總線驅動可自動識別 PCI 多功能卡。對PCMCIA 設備,可以參展DDK中的 MF.sys 驅動的詳細說明,該驅動是一個多功能卡的功能驅動,MF.sys 可以枚舉卡的各個功能,然後爲每個功能加載各自的功能驅動。對具有一個配置的USB設備,USB總線驅動會爲該配置的每個接口分別加載相應的驅動。
Windows 98 和 Windows XP 相比,只提供了對USB多功能設備的支持。在Windows 98中,爲了支持多功能設備,開發者必須做更多的工作。不僅需要爲主設備提供一個功能驅動還要爲連在上面的所有子設備提供單獨的功能驅動。主設備的功能驅動需要枚舉子設備,提供對子設備PNP和電源的缺省處理,它有點像一個微型總線驅動。寫一個完整的總線驅動是一個相當大的工程,本文並不試圖描述其詳細過程。本文會描述一些基本的處理機制,以使讀者可以枚舉子設備,完成那些不太符合微軟模型的多功能設備的驅動。
總體結構
下圖顯示了一個有子設備的父設備(就像總線設備)的設備對象的拓撲圖。控制器和多功能設備有相似的拓撲結構。
 
可以看到,父設備連在一個標準的總線上,總線驅動檢測到了這個父設備,然後像對任何普通設備一樣配置它。當啓動父設備後, PNP 管理器發出一個次功能碼是 IRP_MN_QUERY_DEVICE_RELATIONS 的IRP給父設備以獲得所謂的總線關係。這個請求會對任何設備發出,事實上,PNP管理器不知道某個設備是否有子設備。
爲了對這個請求作出迴應,父設備的功能驅動會查找或者創建額外的設備對象,所有這些設備對象會成爲子設備棧棧底的PDO。然後,PNP管理器接着爲子設備加載功能設備和過濾設備。
父設備的驅動必須擔當兩種角色:對多功能設備擔當FDO, 對其子設備,擔當PDO。當它做FDO時,需要處理PNP IRP 和 Power IRP。當它做PDO時,它被當作 PNP IRP 和Power IRP 最後處理的地方。
子設備對象
父設備必須爲自設備創建PDO,有兩種方法:
Ø 對支持熱插拔的設備,應該維護一個PDO的列表,在PNP管理器每次發出得到總線關係的請求時更新。同時必須有硬件枚舉的功能以檢測新插入的設備,和被移開的設備,動態的更新PDO的列表。
Ø 對於有固定功能的父設備來講,可以創建一個固定數目的PDO列表。
設備擴展結構
FDO 和所有的PDO都屬於一個多功能設備驅動,這可能是驅動比較複雜的一點。這意味着所有對PDO和FDO的請求都會由同一組dispatch例程處理。驅動需要自己處理對PDO和對FDO的PNP請求的差別之處。處理這個問題的一個方法是定義公共的設備擴展結構,下面是示例代碼:
// The FDO extension
typedef struct _DEVICE_EXTENSION {
ULONG  flags;
.........
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
//The PDO extension
typedef struct _PDO_EXTENSION {
ULONG  flags;
........
} PDO_EXTENSION, *PPDO_EXTENSION;
// The Commen part
typedef  struct _COMMON_EXTENSION {
ULONG  flags;
.......
} COMMON_EXTENSION, *PCOMMON_EXTENSION;
#define ISPDO 0x00000001
那麼,dispatch例程看起來會是這樣:
NTSTATUS DispatchSomething(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PCOMMON_EXTENSION pcx = (PCOMMON_EXTENSION)DeviceObject->DeviceObject;
if (pcx->flags & ISPDO)
{
return DispatchSomethingPdo(DeiveceObject, Irp);
}
else
{
return DispatchSomethingFdo(DeiveceObject, Irp);
}
}
程序需要測試設備擴展公共部分的標誌,以確定這個請求是按照PDO規則處理,還是按FDO規則處理。
創建子設備的例子
假定MULFUNC是一個多功能設備,他有兩個功能,就是會擁有兩個子設備。假定這兩個子設備叫 A 和 B。 MULFUNC 會在響應 IRP_MN_START_DEVICE 的請求時爲子設備創建PDO.
NTSTATUS StartDevice(DEVICE_PBJECT fdo, ... )
{
PDEVICE_EXTENSION pdx = 
(PDEVICE_EXTENSION) fdo->DeivceExtension;
CreateChild(pdx, CHILDTYPEA, &pdx->ChildA);
CreateChild(pdx, CHILDTYPEB, &pdx->ChildB);
return  STATUS_SUCCESS;
}
NTSTATUS CreateChild(PDEVICE_EXTENSION pdx, ULONG flags, PDEVICE_OBJECT* pdo)
{
PDEVICE_OBJECT child;
IoCreateDevice(pdx->DriverObject, sizeof(PDO_EXTENSION), NULL, 
FILE_DEVICE_UNKNOWN, FILE_AUTOGENERATED_DEVICE_NAME,
    FALSE, &child);
PPDO_EXTENSION px = (PDO_EXTENSION) child->DeiveExtension;
px->flags = ISPDO | flags;
px->DeiveObject = child;
px->Fdo = pdx->DeiviceObject;
child->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
① CHILDTYPEA 和 CHILDTYPEB 是設備擴展公共部分的額外標誌位。在一個這是的總線驅動中,一般會在 IRP_MN_QUERY_DEVICE_RELATIONS 的處理中枚舉實際的硬件設備。
② 創建了一個命名的設備對象,但是使用了 FILE_AUTOGENERATED_DEVICE_NAME 這個屬性讓系統爲設備自動產生設備名。
最終,在父設備FDO的設備擴展中有指向兩個PDO的指針。
處理 PNP 請求
控制器或多功能設備的驅動有兩組子例程來處理 IRP_MJ_PNP 一類的請求。一組處理當它作爲 FDO 時的系統的 PNP請求, 一組處理當它作爲 PDO 時的請求。下圖說明了設備驅動不同身份時,對系統PNP請求應採取的動作。
表中使用了幾個縮略符,含義如下:
Ø Normal: 像其他功能驅動一樣去處理
Ø Succeed: 以 STATUS_SUCCESS 來完成這個IRP
Ø Ignore: 以在原來 IRP 的 IoStatus 域的值來完成這個IRP
Ø Delegate: 在父設備的設備棧中重複這個 IRP, 然後用在父設備棧中返回的IRP狀態來完成原來發送到子設備PDO設備棧的這個IRP
向PNP管理器報告子設備
PNP 管理器向設備發送一個帶有 BusRelations 碼的 IRP_MN_QUERY_DEVICE_RELATIONS 的請求來獲得設備的子設備列表。當驅動作爲FDO時,對該請求的處理如下:
NTSTATUS HandleQueryRelations(PDEVICE_OBJECT  fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx = ...;
PIO_STACK_LOCATION stack = ...;
if (stack->Paramters.QueryDeviceRelations.Type != BusRelations)
{
return DefaultPnpHandler(fdo, Irp);
}
PDEVICE_RELATIONS newrel = (PDEVICE_RELATIONS)
ExAllocatePool(PagedPool, sizeof(DEVICE_RELATIONS)+sizeof(PDEVICE_OBJECT) );
newrel->count = 2;
newrel->Object[0] = pdx->ChildA;
newrel->Object[1] = pdx->ChildB;
ObReferenceObject(pdx->ChildA);
ObReferenceObject(pdx->ChildB);
Irp->IoStatus.Information = (ULONG_PTR)newrel;
Irp->IoStatus.status = STATUS_SUCCESS;
return DefaultPnpHandler(fdo, Irp);
}
① 這裏設備關係的類型有好幾種,我們只關心這一種;
② 這裏申請一個可容納兩個設備對象指針的結構。結構DEVICE_RELATION 的尾部是一個下標爲1,類型爲PDEVICE_OBJECT 的數組,所以這裏我們額外再加上一個就好了;  
③ 在把子設備指針返回給PNP管理器之前,對子設備增加一次引用。Pnp管理器會在適當的時機減少引用計數的。
④ 將該請求向下發送,也許下層設備還需要做點什麼我們不知道的。我們返回結果的方法是設置 IoStatus的Information 域。
在這裏,②的處理不完全正確,有可能上層過濾驅動已經往DEVICE_RELAIONS 中增加了一些設備對象,這時應該分配一個足夠容納以前設備對象指針和新增的兩個設備對象指針的結構,然後把以前的拷貝過來,把新的兩個加進去,並返回這個新分配的設備結構指針。
在開始的時候,PNP管理器對每個設備發出這個請求。可以通過調用以下函數,使PNP管理器重新發送這個請求以更新設備關係列表。
IoInvalidateDeviceRelations(pdx->Pdo, BusRelations);
一個支持熱插拔的總線驅動在檢測到一個新插入的設備時,調用此函數以通知PNP管理重新獲得設備關係。
PDO 對PNP請求的處理
下面解釋上面圖中 “PDO Hat” 這一列。
The Succeed Action
對大多數的PNP請求不做過多的處理,直接返回成功;
NTSTATUS SucceedRequest(PDEVICE_OBJECT pdo, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
需要注意的是,以上的代碼沒有改變 IoStatus.Information域, PNP管理在發起一個IRP前可能對這個域進行了某種特定的初始化,在某種情況下,設備棧中的過濾驅動有可能改變這個域的值以使它指向某個數據結構。
The Ignore Action
驅動可以忽略某種IRP.忽略類似如使IRP以某個錯誤碼失敗,唯一不同的是,忽略IRP並不改變IRP的狀態域。
NTSTATUS IgnoreRequest(PDEVICE_OBJECT pdo, PIRP Irp)
{
NTSTATUS status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
很顯然的, 這裏以本來的 IoStatus.Status 和 IoStatus.Information 的值來完成這個IRP.這樣做是有道理的。PNP 請求的發起者會把這兩個域分別設置爲 STATUS_NOT_SUPPORTED 和 0. PNP 管理器通過這兩個域來收集信息: 如果最終返回的IRP的這兩個域還是原來的值,那麼PNP 管理器認爲設備棧中的驅動沒有對這個IRP做過什麼,否則的,認爲某個驅動處理了它。在DDK中關於功能驅動和過濾驅動的章節中指出,如果某個驅動想要處理某個PNP請求,那麼應該把 IoStatus.Status 設置成 STATUS_SUCCESS, 然後把IRP向下傳。這裏,我們不處理這個IRP,但是不能修改上層某個驅動對這個驅動所作的改變,所以不要改變這兩個域的值。
The Delegate Action
驅動作爲FDO時,可以簡單將IRP傳給下面的總線驅動。但是驅動作爲PDO 時,就不那麼簡單了。因爲此時,驅動棧已經被耗盡了(PDO是作爲驅動棧的最底層)。這樣應該構造一箇中繼IRP,把它發給驅動作爲FDO的那個驅動棧來處理。
NTSTATUS RepeatRequest(PDEVICE_OBJECT pdo, PIRP irp)
{
PDO_EXTENSION pdx = (PPDO_EXTENSION) pdo->DeviceExtension;
PDEVICE_OBJECT fdo = pdx->Fdo;
PDEVICE_EXTENSION pfx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
PDEVICE_OBJECT tdo = IoGetAttatchedDeviceReference(fdo);
PIRP  subirp = IoAllocateIrp(tdo->StackSize + 1, FALSE);
PIO_STACK_LOCATION substack = IoGetNextIrpStackLocation(subirp);
substack->DeviceObject = tdo;
substack->Paramters.Others.Argument1 = (PVOID)Irp;
IoSetNextIrpStackLocation(subirp);
substack = IoGetNextStackLocation(subirp);
RtlCopyMemory(substack, stack, 
FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine) );
substack->Control = 0;
BOOLEAN  needsvote = < I 'll explain later >;
IoSetCompletionRoutine(subirp, OnRepeaterComplete,(PVOID)needsvote,
TRUE, TRUE, TRUE);
subirp->IoStatus.Status = STATUS_NOT_SUPPORTED;
IoMarkIrpPending(irp);
IoCallDriver(tdo, subirp);
return STATUS_PENDING;
}
NTSTATUS OnRepeaterComplete(PDEVICE_OBJECT tdo, PIRP subirp, PVOID needsvote)
{
ObDereferenceObject(tdo);
PIO_STACK_LOCATION substack = IoGetCurrentIrpStackLocation(subirp);
PIRP irp = (PIRP) substack->Paramters.Others.Argument1;
if (subirp->IoStatus.Status == STATUS_NOT_SUPPORTED)
{
if (needvote)
{
    irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
}
else
{
irp->IoStatus = subirp->IoStatus;
}
IoFreeIrp(subirp);
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_MORE_PROCESSING_REQUIRED;
}
 我們要把中繼IRP發送給驅動FDO所在的棧的棧頂過濾驅動,這個系統函數得到最上層的設備對象,同時增加該設備對象的計數,防止對象管理器刪除它。
 申請IRP的時候,額外的多申請一個棧。我們在這個棧裏存放一些參數信息,已備下面要安裝的完成例程使用。我們放入的DeviceObject指針會成爲完成例程的第一個參數。
 初始化真正的棧,這個棧是給FDO設備棧最頂層的設備對象用的。這裏,我們不使用IoCopyCurrentIrpStackLocationToNext 的原因是這裏的兩個棧是屬於兩個不同的IRP的。
 我們必須提早預料到這個中繼IRP不被處理的情況,這個參數就是爲這種情況預備,後面會有描述。
 按照約定,初始化這個域爲 STATUS_NOT_SUPPORT.
 減少計數, 參照的說明。
 保存原始IRP的地址
 設置原始IRP 的狀態,下面會解釋爲什麼這樣做。
 自己分配的,自己釋放;
 返回 STATUS_MORE_PROCESSING_REQUIRED, 這樣IoCompleteRequest就不會管這個中繼IRP了。
上面的代碼處理了父設備驅動必須重複向FDO設備棧轉發PNP IRP 帶來的各種問題。PNP 管理器將IRP的初始狀態設置爲 STATUS_NOT_SUPPORTED.通過測試IRP最終的狀態,PNP 管理器可以得知IRP有否被設備棧中的某個驅動處理過。如果最終返回的狀態還是 STATUS_NOT_SUPPORTED, 那麼PNP管理器認爲沒有驅動動過這個IRP,如果返回的是其他的狀態,那麼PNP管理器認爲至少有某個驅動故意使這個IRP成功或者失敗,而不是簡單的忽略它。
創建PNP IRP的驅動必須遵守這一約定,創建的時候就把IRP的狀態設爲STATUS_NOT_SUPPORTED. 這樣做初始化可能會引出一個問題:如果在PDO上層的某個設備驅動改變了最初的IRP的狀態,然後這個IRP發送到我們的PDO,這時,我們要創建一箇中繼IRP,初始化這個中繼IRP的狀態爲STATUS_NOT_SUPPORTED,然後向父設備FDO設備棧向下傳這個IRP,如果這個中繼IRP 以STATUS_NOT_SUPPORTED的狀態被完成,那麼應該以什麼狀態來完成原始的IRP呢?當然,不應該是 STATUS_NOT_SUPPORTED,否則的話,那需要“PDO上層的設備驅動沒有動過原始IRP”這個假設成立。現在就要用到needsvote 這個標誌來解決這個問題了。
對某些原始IRP而言,我們並不關心FDO設備棧是否處理這些IRP,對這些IRP,我們的父設備驅動不需要投票,就是 needsvote = 0. 如果你仔細看上面的代碼,你會發現在OnRepeaterComplete中,在這種情況下,我們並不改變原始IRP中的狀態。對另外一些IRP,如果FDO設備棧決定不處理中繼IRP的話,我們也不能決定確切的答案,但是又需要投票的話(needsvote = 1),那麼就使原始的IRP以 STATUS_UNSUCCESSFUL 的狀態失敗。那些IRP需要投票,那些IRP不需要投票,在上面的表中都給出了。順便提一下,在上面的表中對於給N/A建議的IRP,首先就不應該中繼他們,直接以默認值返回就OK了。
如果FDO設備棧處理了中繼IRP,那麼我們將中繼IRP的 IoStatus 複製給原始IRP,包括域 Status 和 Information,Information 可能含有對某一請求的答案,我們就是通過這種方式將結果返回去。
對於原始PNP IRP還有一點特別的處理,我設置IRP 爲 pending, 然後返回了狀態STATUS_PENDING.大多數的PNP都是同步完成的,以便對IoCallDriver的調用能夠立刻完成IRP.這裏爲什麼要使得PNP管理器花費額外的代價排隊一個APC來完成原始IRP呢?原因在於,RepeatRequest運行在爲IRP_MJ_PNP服務的dispatch的一個子例程中,如果我們在dispatch例程中不返回 STATUS_PENDING的話,那麼必須返回我們最終完成IRP的那個狀態值。在我們安裝的完成例程中,通過對STATUS_NOT_SUPPORTED的檢測,然後結合 needsvote這個標誌的值才能確定這個狀態值。然而,沒有有效的方式把這個值從完成例程返傳給dispatch 例程。
處理設備刪除
PNP管理器知道一個多功能設備的FDO和它的子PDO,在用戶刪除父設備的時候,PNP管理器自動刪除他上面所有子設備。但是奇怪的時,在收到 IRP_MN_REMOVE_DEVICE 的時候,父設備驅動不能刪除他的子PDO。設備管理器假定,這些PDO一直存在直到底層的硬件被移除。一個多功能驅動在被告知刪除它的FDO的時候才能刪除其子PDO.一個支持熱插拔的總線驅動在枚舉其上設備失敗之後,才能在對IRP_MN_REMOVE_DEVICE的應答中刪除那個PDO.
處理 IRP_MN_QUERY_ID
父設備驅動要處理的一個重要的IRP就是IRP_MN_QUERY_ID. PNP管理器以好幾種方式來處理這個請求,以決定採用那個設備標誌符來尋找INF文件和加載子設備驅動。對這個請求的處理中,應該在 IoStatus.Information 中返回一個 MUL_SZ 的之,這個值是必要的設備標誌符。 假定 MULFUNC 設備的連個子設備的標誌符爲 *WCO1104 和 *WCO1105, 那麼這樣做:
NTSTATUS HandleQueryId(PDEVICE_OBJECT pdo, PIRP irp)
{
PPDO_EXTENSION pdx = (PPDO_EXTENSION) pdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLoation(irp);
PWCHAR idstring;
switch(stack->Pramters.QueryId.IdType)
{
case BusQueryInstanceID:
idstring = L"0000";
break;
case BusQueryDeviceID:
if (pdx->flags & CHILDTYPEA)
{
idstring = LDRIVERNAME L"//*WCO1104";
}
else
{
idstring = LDRIVERNAME L"//*WCO1105";
}
break;
case BusQueryHardwareIDs:
if (pdx->flags & CHILDTYPEA)
{
idstring = L"*WCO1104";
}
else
{
idstring = L"*WCO1105";
}
break;
default:
return CompleteRequest(irp, STATUS_NOT_SUPPORTED, 0);
}
ULONG nchars = wcslen(idstring);
ULONG size = (nchars + 2) *sizeof(WCHAR);
PWCHAR id = (PWCHAR)ExAllocatePool(PagedPool, size);
wcscpy(id, idstring);
id[nchars + 1] = 0;
return CompleteRequest(irp, STATUS_SUCCESS, (ULONG_PTR)id);
}
 返回設備實例ID.
 返回設備ID.
 返回硬件ID.
返回的這些字符串用來標誌某一設備,PNP 管理器會根據PDO返回的硬件ID(hardware ID)和兼容ID(compatible ID)加載相應的驅動。設備ID(device ID)唯一的標識了一種設備,硬件ID是一組標識符,通常設備ID也是硬件ID的一個成員。PNP管理器在缺少硬件ID的情況下,會使用兼容ID來尋找INF文件,安裝驅動。兼容ID和硬件ID的格式相同,只是比硬件ID更通用,一個設備可以沒有或有多個兼容ID.設備ID的通常包含某些特定的標識,這些標識是存在設備的固件程序中,並且由總線驅動負責得到。同一種設備A和B,通過實例ID(instance ID)可以區分。設備實例ID(device instance ID)是系統提供的標識設備實例的標識符,通常,這個標識符由設備ID和實例ID組成。
ID的格式:
device ID:
Ø <enumerator>/<enumerator-specific-device-ID>
hardware ID:
Ø <enumerator>/<enumerator-specific-device-ID>
這是典型的硬件ID格式
Ø *<enumerator-specific-ID>
*號表示,這個設備可以被不止一個枚舉器支持,例如:ISAPNP 和 BIOS.
Ø <device-class-specific-ID>
對於一個已經存在並且有建立了自己命名約定的設備類可以使用這種格式一個設備可以有一個或者多個硬件ID. <enumerator>的值可以是註冊表分支HKLM/SYSTEM/CurrentControlSet/Enum 下面的一種,PNP管理器把爲每個設備對象的設備ID建立一個分支: HKLM/SYSTEM/CurrentControlSet/Enum/enumerator/deviceID,在這個分支下的子鍵是實例ID,這個子鍵裏面存儲有該設備的硬件信息,通常有硬件ID,兼容ID,設備描述等。enumerator 可以看成是系統提供的一些總線驅動。
instance ID:
Ø 一個字符串
device instance ID:
Ø <enumerator>/<enumerator-specific-device-ID>/<instance-specific-ID>
一個例子,機器上的移動硬盤,枚舉器是 USBSTOR.
device ID: 
USBSTOR/Disk&Ven_SAMSUNG&Prod_MP0402H&Rev_
competible ID: 
USBSTOR/Disk
USBSTOR/RAW
hardware ID:
USBSTOR/DiskSAMSUNG_MP0402H__________
USBSTOR/DiskSAMSUNG_MP0402_______
USBSTOR/DiskSAMSUNG
USBSTOR/SAMSUNG_MP0402H_____
SAMSUNG_MP0402H____
USBSTOR/GenDisk
GenDisk
device instance ID:
USBSTOR/DISK&VEN_SAMSUNG&PROD_MP0402H&REV_/6&493A32C&0&____
處理 IRP_MN_QUERY_DEVICE_RELATIONS
最後一個要考慮的PNP請求是 IRP_MN_QUERY_DEVICE_RELATIONS。之前有提到過,FDO提供了子設備對象的列表來處理這個請求。驅動作爲PDO的身份時,只要處理一個類型爲目標設備的請求,相應代碼如下:
NTSTATUS HandleQueryRelations(PDEVICE_OBJECT pdo, PIRP irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
NTSTATUS   status = irp->IoStatus.Status;
if (stack->Parameters.QueryDeviceRelations.Type == 
TargetDeviceRelations)
{
PDEVICE_RELATIONS newrel = (PDEVICE_RELATIONS)
ExAllocatePool(PagedPool, sizeof(DEVICE_RELATIONS) );
newrel->Count = 1;
newrel->Object[0] = pdo;
ObReferenceObject(pdo);
status = STATUS_SUCCESS;
irp->IoStatus.Information = (ULONG_PTR)newrel;
}
irp->IoStatus.Status = status;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return status;
}
處理 IRP_MN_QUERY_INTERFACE
IRP_MN_QUERY_INTERFACE 可以讓在同一個設備棧的驅動得到下層驅動的直接調用接口。所謂直接調用接口,就是說驅動可以直接調用,而不必先構造IRP.關於直接調用接口的機制的基本概念如下:
Ø 每個直接調用接口由一個GUID來標識。接口本身是一個結構體,結構體中含有一組指針,這些指針指向接口實現方法
Ø 想得到直接調用接口的驅動需要先構造一個IRP, IRP的次功能碼爲 IRP_MN_QUERY_INTERFACE, 這個IRP參數應該有標識接口的GUID和一個接口結構體。獲得之後,可以直接使用接口結構體中的指針來調用。當驅動不再需要直接調用接口的話,調用InterfaceDereference函數取消對接口的引用
Ø 導出接口的驅動在QUERY_INTERFACE的請求中,查找IRP中指定的GUID,如果匹配的話,將接口實現方法的地址填到接口結構體中去
標識一個接口
需要創建一個全局的GUID和一個結構體來標識一個接口。按照慣例,GUID的符號鏈接的名字會是 GUID_XXX_STANDARD 這種形式。MULFUNC 用下面的GUID 來導出接口。
DEFINE_GUID(GUID_RESOURCE_SUBALLOCATE_STANDARD, 0xaa04540,
0x6fd1, 0x11d3, 0x81, 0xb5, 0x0, 0xc0, 0x4f, 0xa3, 0x30, 0xa6);
RESOURCE_SUBALLCATE 接口的目的是和子設備分享 I/O 屬於父設備的資源。與RESOURCE_SUBALLOCATE 接口相關的結構體如下:
typedef struct _INTERFACE {
USHORT Size;
USHORT Version;
PVOID Context;
PINTERFACE_REFERENCE  InterfaceReference;
PINTERFACE_DEREFERENCE  InterfaceDereference;
// 接口相關方法的接口
} INTERFACE, *PINTERFACE;
struct _RESOURCE_SUBALLOCATE_STANDARD : public INTERFACE {
PGETRESOURCES GetResources;
};
typdef struct _RESOURCE_SUBALLOCATE_STANDARD
RESOURCE_SUBALLOCATE_STANDARD, *PRESOURCE_SUBALLOCATE_STANDARD;
查找和使用接口
想使用下層驅動導出的直接接口的驅動需要發送一個 QUERY_INTERFACE 的請求。下面展示了在 Paramters.QueryInterface 中的參數:
參數
含義
InterfaceType
指向標識接口的GUID
Size
參數 Interface 指向的接口結構體的大小
Version
接口結構體的版本號
Interface
指向一個接口結構體的指針
InterfaceSpecificData
導出接口的驅動導出的附加數據,跟具體接口相關
下面是示例代碼:
RESOURCE_SUBALLOCATE_STANDARD suballoc; // 存放最終的結果
KEVENT  event;
KeInitializeEvent(&event, NotificationEvent, FALSE);
IO_STATUS_BLOCK  iosb;
PIRP  irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
pdx->LowerDeviceObject, NULL, 0, NULL, &event, &iosb);
PIRP  stack = IoGetNextIrpStackLocation(irp);
stack->MinorFunction = IRP_MN_QUERY_INTERFACE;
stack->Paramters.QueryInterface.InterfaceType =
&GUID_RESOURCE_SUBALLOCATE_STANDARD;
stack->Paramters.QueryInterface.Size = sizeof(suballoc);
stack->Paramters.QueryInterface.Version = RESOURCE_SUBALLOCATE_STANDARD_VERSION;
stack->Paramters.QueryInterface.Interface = &suballoc;
stack->Paramters.QueryInterface.InterfaceSpecificData = NULL;
NTSTATUS status = IoCallDriver(pdx->LowerDeviceObject, irp);
if (status == STATUS_PENDING)
{
KeWaitForSingalObject(&event, Executive, KernelMode, FALSE, NULL);
status = irp->IoStatus.Status;
}
在上面的示例代碼,我們構造一個了同步IRP,然後底層驅動發送。我們期望下層設備填充suballoc 結構,然後在IRP的狀態中返回成功。
如果我們IRP可以成功的返回,那麼我們可以直接調用接口中的成員函數了。通常,每個接口函數需要一個上下文參數,這個參數來自返回的接口結構體。例如:
PCM_RESOURCE_LIST raw, translated;
status = suballoc.GetResources(suballoc.Context, pdx->Pdo, &raw, &translated);
當不再需要這個接口時,調用:
suballoc.InterfaceDereference(suballoc.Context);
導出直接調用接口
驅動要導出直接調用接口,需要處理 IRP_MN_QUERY_INTERFACE 請求。第一步要做的就是測試接口GUID,看調用者是否在查找自己支持的接口。如下:
if (*stack->Paramters.QueryInterface.InterfaceType != 
GUID_RESOURCE_SUBALLOCATE_STANDARD)
< default handling >
// 注意,C++ 支持這樣比較,如果是C的話,請使用 IsEqualGuid 來比較
DDK中說,一個總線驅動在作爲PDO時,收到這種請求,如果請求的是未知的接口的話,那麼使IRP失敗返回:“驅動應該避免將此請求向下一個設備棧發送以獲得請求的接口。這種行爲會增加不同設備棧之間的依賴性,從而使設備棧難以管理。例如:第二個設備棧的設備無法移除,如果某個驅動正在使用其導出的接口。”我不贊同這樣,我建議對於控制器和多功能設備驅動可以這麼做。如果不這樣做,上層的子設備就無法使用下層總線導出的功能了。反正在子設備被移走之前,父設備棧不能刪除,只要子設備驅動能在處理關閉例程中去掉對直接調用接口的引用,這樣做也是可以的。
如果接口發展到多於一個版本,那麼下一步就是決定要提供那一個版本的接口了。按照約定,初始的版本爲1,以後遇到重要的改變就加1.可以在定義接口GUID的頭文件裏寫一個顯示定義的常量來代表當前使用的版本號,然後在構造IRP時,就可是使用這個常量了,這個常量是在編譯後,就固定了。下面是示例代碼:
USHORT version = RESOURCE_SUBALLOCATE_STANDARD_VERSION;
if (version > stack->Paramters.QueryInterface.Version)
{
version = stack->Parameters.QueryInterface.Version;
}
if (version == 0)
{
return CompleteRequest(irp, irp->IoStatus.Status);
}
這裏,如果你的版本號從1開始,而調用者需要版本號爲0的接口,那麼以IRP 原來的狀態值返回該IRP, 這個初始值很可能是 STATUS_NOT_SUPPORTED.
第三步就是初始化接口結構體了,如下:
if (stack->Parameters.QueryInterface.Size < 
sizeof(RESOURCE_SUBALLOCATE_STANDARD) )
{
return CompleteRequest(irp, STATUS_INVALID_PARAMTER);
}
PRESOURCE_SUBALLOCATE_STANDRD ifp = (PRESOURCE_SUBALLOCATE_STANDARD)
stack->Parameters.QueryInterface.Interface;
ifp->Size = sizeof(RESOURCE_SUBALLOCATE_STANDARD);
ifp->Version = 1;
ifp->Context = (PVOID)fdx;
ifp->InterfaceReference = (PINTERFACE_REFERENCE)
SuballocInterfaceReference; 
ifp->InterfaceDereference = (PINTERFACE_DEREFERENCE)
SuballocInterfaceDereference;
ifp->GetResources = (PGETRESOURCE)GetChildResources;
最後,需要調用一下 SuballocInterfaceReference 使驅動可以在調用者調用 InterfaceDereference 之前不被卸載。
處理 IRP_MN_QUERY_PNP_DEVICE_STATE
有時候,你可能不想設備管理器顯示一個或者所有的子設備。要想禁止設備管理顯示,在迴應 IRP_MN_QUERY_PNP_DEVICE_STATUS 中,在設備標誌位置位 PNP_DEVICE_DONT_DISPLAY_IN_UI. 

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