32、即插即用
即插即用
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
http://msdn.microsoft.com/en-us/library/ff544248%28VS.85%29.aspx#
Handling IRPs
http://msdn.microsoft.com/en-us/library/ff546847%28VS.85%29.aspx
具体的子功能号讲解见MSDN
http://msdn.microsoft.com/en-us/library/ff558807%28VS.85%29.aspx
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
15
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;
52
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驱动中使用是翻译资源里的中断号。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <windows.h>
2 #include <stdio.h>
3 #include <winioctl.h>
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 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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);
20
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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;
8
9 for (i = 0; i < nres; ++i, ++resource)
10 { // for each resource
11 ULONG type = resource->Type;
12
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 };
25
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 }
一个设备一般需要加载两个WDM程序,一个是总线驱动程序(它提供PDO),另一个是功能驱动程序(它提供FDO),PDO和FDO这两个设备对象形成一个设备栈。非即插即用功能的IRP一般在FDO中处理,而即插即用功能相关的IRP会被转发到PDO中处理。
WDM驱动程序是NT式驱动程序的一个特例。