即插即用
1、即插即用(IRP_MJ_PNP)功能能夠通過作業系統協調自動配置設定裝置上的資源,如中斷号,I/O位址,DMA資源,裝置實體記憶體等。
WDM架構程式是分層驅動,WDM處于分層的高端,而總線驅動程式處于分層的低端。大部分即插即用IRP都會被WDM驅動發送到底層的驅動程式處理。

圖示 P35 2
IRP_MJ_PNP類似于Win32中的消息,而IRP_MJ_PNP的派遣函數類似于Win32程式設計中的視窗函數。
所有的即插即用的事件,都會引發即插即用管理器向WDM驅動程式發送一個IRP_MJ_PNP。IRP_MJ_PNP是這個IRP的主功能代碼,不同的即插即用事件會有不同的子功能代碼。
圖示 MSDN中End-User I/O Requests and File Objects
<a href="http://msdn.microsoft.com/en-us/library/ff544248%28VS.85%29.aspx#">http://msdn.microsoft.com/en-us/library/ff544248%28VS.85%29.aspx#</a>
Handling IRPs
<a href="http://msdn.microsoft.com/en-us/library/ff546847%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff546847%28VS.85%29.aspx</a>
具體的子功能号講解見MSDN
<a href="http://msdn.microsoft.com/en-us/library/ff558807%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff558807%28VS.85%29.aspx</a>
代碼
1 /************************************************************************
2 * 函數名稱:HelloWDMAddDevice
3 * 功能描述:添加新裝置
4 * 參數清單:
5 DriverObject:從I/O管理器中傳進來的驅動對象
6 PhysicalDeviceObject:從I/O管理器中傳進來的實體裝置對象
7 * 傳回 值:傳回添加新裝置狀态
8 *************************************************************************/
9 #pragma PAGEDCODE
10 NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
11 IN PDEVICE_OBJECT PhysicalDeviceObject)
12 {
13 PAGED_CODE();
14 KdPrint(("Enter HelloWDMAddDevice\n"));
15
16 NTSTATUS status;
17 PDEVICE_OBJECT fdo;
18 status = IoCreateDevice(
19 DriverObject,
20 sizeof(DEVICE_EXTENSION),
21 NULL,//沒有指定裝置名
22 FILE_DEVICE_UNKNOWN,
23 0,
24 FALSE,
25 &fdo);
26 if( !NT_SUCCESS(status))
27 return status;
28 PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
29 pdx->fdo = fdo;
30 pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
31
32 //建立裝置接口
33 status = IoRegisterDeviceInterface(PhysicalDeviceObject, &MY_WDM_DEVICE, NULL, &pdx->interfaceName);
34 if( !NT_SUCCESS(status))
35 {
36 IoDeleteDevice(fdo);
37 return status;
38 }
39 KdPrint(("%wZ\n",&pdx->interfaceName));
40 IoSetDeviceInterfaceState(&pdx->interfaceName, TRUE);
41
42 if( !NT_SUCCESS(status))
43 {
44 if( !NT_SUCCESS(status))
45 {
46 return status;
47 }
48 }
49
50 fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
51 fdo->Flags &= ~DO_DEVICE_INITIALIZING;
52
53 KdPrint(("Leave HelloWDMAddDevice\n"));
54 return STATUS_SUCCESS;
55 }
56
57 /************************************************************************
58 * 函數名稱:HelloWDMPnp
59 * 功能描述:對即插即用IRP進行處理
60 * 參數清單:
61 fdo:功能裝置對象
62 Irp:從IO請求包
63 * 傳回 值:傳回狀态
64 *************************************************************************/
65 #pragma PAGEDCODE
66 NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
67 IN PIRP Irp)
68 {
69 PAGED_CODE();
70
71 KdPrint(("Enter HelloWDMPnp\n"));
72 NTSTATUS status = STATUS_SUCCESS;
73 PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
74 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
75 static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) =
76 {
77 HandleStartDevice, // IRP_MN_START_DEVICE
78 DefaultPnpHandler, // IRP_MN_QUERY_REMOVE_DEVICE
79 HandleRemoveDevice, // IRP_MN_REMOVE_DEVICE
80 DefaultPnpHandler, // IRP_MN_CANCEL_REMOVE_DEVICE
81 DefaultPnpHandler, // IRP_MN_STOP_DEVICE
82 DefaultPnpHandler, // IRP_MN_QUERY_STOP_DEVICE
83 DefaultPnpHandler, // IRP_MN_CANCEL_STOP_DEVICE
84 DefaultPnpHandler, // IRP_MN_QUERY_DEVICE_RELATIONS
85 DefaultPnpHandler, // IRP_MN_QUERY_INTERFACE
86 DefaultPnpHandler, // IRP_MN_QUERY_CAPABILITIES
87 DefaultPnpHandler, // IRP_MN_QUERY_RESOURCES
88 DefaultPnpHandler, // IRP_MN_QUERY_RESOURCE_REQUIREMENTS
89 DefaultPnpHandler, // IRP_MN_QUERY_DEVICE_TEXT
90 DefaultPnpHandler, // IRP_MN_FILTER_RESOURCE_REQUIREMENTS
91 DefaultPnpHandler, //
92 DefaultPnpHandler, // IRP_MN_READ_CONFIG
93 DefaultPnpHandler, // IRP_MN_WRITE_CONFIG
94 DefaultPnpHandler, // IRP_MN_EJECT
95 DefaultPnpHandler, // IRP_MN_SET_LOCK
96 DefaultPnpHandler, // IRP_MN_QUERY_ID
97 DefaultPnpHandler, // IRP_MN_QUERY_PNP_DEVICE_STATE
98 DefaultPnpHandler, // IRP_MN_QUERY_BUS_INFORMATION
99 DefaultPnpHandler, // IRP_MN_DEVICE_USAGE_NOTIFICATION
100 DefaultPnpHandler, // IRP_MN_SURPRISE_REMOVAL
101 };
102
103 ULONG fcn = stack->MinorFunction;
104 if (fcn >= arraysize(fcntab))
105 { // 未知的子功能代碼
106 status = DefaultPnpHandler(pdx, Irp); // some function we don't know about
107 return status;
108 }
109
110 #if DBG
111 static char* fcnname[] =
112 {
113 "IRP_MN_START_DEVICE",
114 "IRP_MN_QUERY_REMOVE_DEVICE",
115 "IRP_MN_REMOVE_DEVICE",
116 "IRP_MN_CANCEL_REMOVE_DEVICE",
117 "IRP_MN_STOP_DEVICE",
118 "IRP_MN_QUERY_STOP_DEVICE",
119 "IRP_MN_CANCEL_STOP_DEVICE",
120 "IRP_MN_QUERY_DEVICE_RELATIONS",
121 "IRP_MN_QUERY_INTERFACE",
122 "IRP_MN_QUERY_CAPABILITIES",
123 "IRP_MN_QUERY_RESOURCES",
124 "IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
125 "IRP_MN_QUERY_DEVICE_TEXT",
126 "IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
127 "",
128 "IRP_MN_READ_CONFIG",
129 "IRP_MN_WRITE_CONFIG",
130 "IRP_MN_EJECT",
131 "IRP_MN_SET_LOCK",
132 "IRP_MN_QUERY_ID",
133 "IRP_MN_QUERY_PNP_DEVICE_STATE",
134 "IRP_MN_QUERY_BUS_INFORMATION",
135 "IRP_MN_DEVICE_USAGE_NOTIFICATION",
136 "IRP_MN_SURPRISE_REMOVAL",
137 };
138
139 KdPrint(("PNP Request (%s)\n", fcnname[fcn]));
140 #endif // DBG
141
142 status = (*fcntab[fcn])(pdx, Irp);
143 KdPrint(("Leave HelloWDMPnp\n"));
144 return status;
145 }
146
147 /************************************************************************
148 * 函數名稱:HelloWDMDispatchRoutine
149 * 功能描述:對預設IRP進行處理
150 * 參數清單:
151 fdo:功能裝置對象
152 Irp:從IO請求包
153 * 傳回 值:傳回狀态
154 *************************************************************************/
155 #pragma PAGEDCODE
156 NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,
157 IN PIRP Irp)
158 {
159 PAGED_CODE();
160 KdPrint(("Enter HelloWDMDispatchRoutine\n"));
161 Irp->IoStatus.Status = STATUS_SUCCESS;
162 Irp->IoStatus.Information = 0; // no bytes xfered
163 IoCompleteRequest( Irp, IO_NO_INCREMENT );
164 KdPrint(("Leave HelloWDMDispatchRoutine\n"));
165 return STATUS_SUCCESS;
166 }
代碼示例 P354
2、通過裝置接口尋找裝置
在WDM中,一般是通過裝置接口來定位一個驅動程式的。不同NT驅動通過裝置名或者符号連結來定位。
裝置接口就一組全局辨別,一個128位組成的數字。
不用IoCreateSymbolicLink ,而用IoRegisterDeviceInterface為裝置建立裝置連結。
見上面的示例代碼中
HelloWDMAddDevice
示例代碼 P358
新的符号連結包括四部分組成:何種總線,類裝置名稱,該裝置類型的第幾個裝置,指定裝置的UUID。
A driver registers an interface instance once and then calls IoSetDeviceinterfaceState to enable and disable the interface.
1)應用程式尋找接口
應用程式尋找接口,是通過裝置接口和裝置号決定的。
一般通過SetupDiXX系統函數來獲得裝置接口。
http://msdn.microsoft.com/en-us/library/ff551015%28VS.85%29.aspx
1 #include <windows.h>
2 #include <stdio.h>
3 #include <winioctl.h>
4 #include "function.h"
5
6 HANDLE GetDeviceViaInterface( GUID* pGuid, DWORD instance)
7 {
8 // Get handle to relevant device information set
9 HDEVINFO info = SetupDiGetClassDevs(pGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
10 if(info==INVALID_HANDLE_VALUE)
11 {
12 printf("No HDEVINFO available for this GUID\n");
13 return NULL;
14 }
16 // Get interface data for the requested instance
17 SP_INTERFACE_DEVICE_DATA ifdata;
18 ifdata.cbSize = sizeof(ifdata);
19 if(!SetupDiEnumDeviceInterfaces(info, NULL, pGuid, instance, &ifdata))
20 {
21 printf("No SP_INTERFACE_DEVICE_DATA available for this GUID instance\n");
22 SetupDiDestroyDeviceInfoList(info);
23 return NULL;
24 }
25
26 // Get size of symbolic link name
27 DWORD ReqLen;
28 SetupDiGetDeviceInterfaceDetail(info, &ifdata, NULL, 0, &ReqLen, NULL);
29 PSP_INTERFACE_DEVICE_DETAIL_DATA ifDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)(new char[ReqLen]);
30 if( ifDetail==NULL)
31 {
32 SetupDiDestroyDeviceInfoList(info);
33 return NULL;
34 }
35
36 // Get symbolic link name
37 ifDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
38 if( !SetupDiGetDeviceInterfaceDetail(info, &ifdata, ifDetail, ReqLen, NULL, NULL))
39 {
40 SetupDiDestroyDeviceInfoList(info);
41 delete ifDetail;
42 return NULL;
43 }
44
45 printf("Symbolic link is %s\n",ifDetail->DevicePath);
46 // Open file
47 HANDLE rv = CreateFile( ifDetail->DevicePath,
48 GENERIC_READ | GENERIC_WRITE,
49 FILE_SHARE_READ | FILE_SHARE_WRITE,
50 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
51 if( rv==INVALID_HANDLE_VALUE) rv = NULL;
53 delete ifDetail;
54 SetupDiDestroyDeviceInfoList(info);
55 return rv;
56 }
57
58 DWORD ReadPort(HANDLE hDevice,DWORD port)
59 {
60 DWORD dwOutput;
61 DWORD dwRead;
62 DeviceIoControl(hDevice, IOCTL_READ_PORT_ULONG, &port, 4, &dwOutput, 4, &dwRead, NULL);
63 return dwOutput;
64 }
65 VOID WritePort(HANDLE hDevice,DWORD port,DWORD value)
66 {
67 PVOID buffer[2];
68 buffer[0] = (PVOID)port;
69 buffer[1] = (PVOID)value;
70 DWORD dwWrite;
71 DeviceIoControl(hDevice, IOCTL_WRITE_PORT_ULONG, &port, 8, NULL, 0, &dwWrite, NULL);
72 }
73 VOID TestDriver(HANDLE hDevice)
74 {
75 DWORD dwOutput;
76 DeviceIoControl(hDevice, IOCTL_TEST, NULL, 0, NULL, 0, &dwOutput, NULL);
77 }
78
79
示例代碼 P359
INI檔案注意:VendorID and ProductID。
2)啟動裝置
啟動裝置時,裝置管理器向WDM驅動發一個IRP_MN_START_DEVICE的子功能代碼的即插即用IRP。WDM一般将這個IRP轉發到底層PDO來完成。PDO根據裝置的類型枚舉該裝置的所有資源。PDO可以認為是一個NT驅動。
在裝置管理器看到的是配置設定資源中的中斷号,而WDM驅動中使用是翻譯資源裡的中斷号。
主函數
4
5 #include "function.h"
6
7 #include "../MyDriver/guid.h"
8
9 int main()
10 {
11 HANDLE hDevice = GetDeviceViaInterface((LPGUID)&MY_WDM_DEVICE,0);
12
13 if (hDevice == INVALID_HANDLE_VALUE)
14 {
15 printf("Failed to obtain file handle to device: "
16 "%s with Win32 error code: %d\n",
17 "MyWDMDevice", GetLastError() );
18 return 1;
19 }
20
21 CloseHandle(hDevice);
22 return 0;
23 }
1 #pragma LOCKEDCODE
2 NTSTATUS OnRequestComplete(PDEVICE_OBJECT junk, PIRP Irp, PKEVENT pev)
3 { // OnRequestComplete
4 //在完成例程中設定等待事件
5 KeSetEvent(pev, 0, FALSE);
6 //标志本IRP還需要再次被完成
7 return STATUS_MORE_PROCESSING_REQUIRED;
8 }
9
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #pragma PAGEDCODE
13 NTSTATUS ForwardAndWait(PDEVICE_EXTENSION pdx, PIRP Irp)
14 { // ForwardAndWait
15 PAGED_CODE();
16
17 KEVENT event;
18 //初始化事件
19 KeInitializeEvent(&event, NotificationEvent, FALSE);
21 //将本層堆棧拷貝到下一層堆棧
22 IoCopyCurrentIrpStackLocationToNext(Irp);
23 //設定完成例程
24 IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnRequestComplete,
25 (PVOID) &event, TRUE, TRUE, TRUE);
26
27 //調用底層驅動,即PDO
28 IoCallDriver(pdx->NextStackDevice, Irp);
29 //等待PDO完成
30 KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
31 return Irp->IoStatus.Status;
32 }
轉發并等待示例 P366
枚舉相關資源
PDO完成IRP_MN_START_DEVICE後,會将獲得的裝置資源存儲在IRP的裝置棧中,并且存儲中stack中的AllocatedResourcesTranslated中。
圖示裝置資源 P370
1 #pragma PAGEDCODE
2 VOID ShowResources(IN PCM_PARTIAL_RESOURCE_LIST list)
3 {
4 //枚舉資源
5 PCM_PARTIAL_RESOURCE_DESCRIPTOR resource = list->PartialDescriptors;
6 ULONG nres = list->Count;
7 ULONG i;
9 for (i = 0; i < nres; ++i, ++resource)
10 { // for each resource
11 ULONG type = resource->Type;
13 static char* name[] = {
14 "CmResourceTypeNull",
15 "CmResourceTypePort",
16 "CmResourceTypeInterrupt",
17 "CmResourceTypeMemory",
18 "CmResourceTypeDma",
19 "CmResourceTypeDeviceSpecific",
20 "CmResourceTypeBusNumber",
21 "CmResourceTypeDevicePrivate",
22 "CmResourceTypeAssignedResource",
23 "CmResourceTypeSubAllocateFrom",
24 };
26 KdPrint((" type %s", type < arraysize(name) ? name[type] : "unknown"));
27
28 switch (type)
29 { // select on resource type
30 case CmResourceTypePort:
31 case CmResourceTypeMemory:
32 KdPrint((" start %8X%8.8lX length %X\n",
33 resource->u.Port.Start.HighPart, resource->u.Port.Start.LowPart,
34 resource->u.Port.Length));
35 break;
36
37 case CmResourceTypeInterrupt:
38 KdPrint((" level %X, vector %X, affinity %X\n",
39 resource->u.Interrupt.Level, resource->u.Interrupt.Vector,
40 resource->u.Interrupt.Affinity));
41 break;
42
43 case CmResourceTypeDma:
44 KdPrint((" channel %d, port %X\n",
45 resource->u.Dma.Channel, resource->u.Dma.Port));
46 } // select on resource type
47 } // for each resource
48 }
示例代碼 P371
一個裝置一般需要加載兩個WDM程式,一個是總線驅動程式(它提供PDO),另一個是功能驅動程式(它提供FDO),PDO和FDO這兩個裝置對象形成一個裝置棧。非即插即用功能的IRP一般在FDO中處理,而即插即用功能相關的IRP會被轉發到PDO中處理。
WDM驅動程式是NT式驅動程式的一個特例。