参考文档:Understanding When Remove IRPs Are Issued - Windows drivers / 让设备实现即插即用--福优学苑@音视频+流媒体 (hellotongtong.com)
Windows对于PnP(即插即用)设备,有一个专门的IRP Major Code定义:IRP_MJ_PNP,任何PnP设备驱动以及工作在PnP设备协议栈之上的驱动(如文件系统驱动)和过滤驱动都需要处理IRP_MJ_PNP请求,尤其是删除的过程,如果驱动没有针对这类请求进行处理,可能会导致系统挂起或者删除设备失败。一个常见的用户层面表现就是无法安全弹出U盘。
本文主要翻译自前面的链接,不保证内容一定正确(未进行完整验证)。
PnP设备有两种删除流程,一种是正常删除,也就是用户层面发起的“安全弹出”请求;另一种是用户直接拔出设备,这种是非正常删除。
对于正常安全删除的流程:
Windows会先发起一个IRP_MN_QUERY_REMOVE_DEVICE请求,去查询设备是否可以正常删除,如果此时设备忙,则可以通过Irp->IoStatus.Status返回一个STATUS_UNSUCCESSFUL,同时调用IoCompleteRequest,并且不传播Irp给下层驱动。这样在用户层面上,就会提示设备忙,无法删除。
如果驱动认为设备时可以删除的,那么收到此请求以后,就应该禁止新的其它请求进入,同时设置Irp->IoStatus.Status为STATUS_SUCCESS,并调用IoSkipCurrentIrpStackLocation和IoCallDriver传播这个Irp到更底层的设备。
通常来说,如果这个请求返回成功,那么驱动此时就应该释放一些资源,当然驱动也可以选择不释放,而是仅仅阻止更多的请求进入,具体驱动选择哪种行为,由驱动自己决定,但通常情况下,对设备加锁是必需的,也要阻止任何“创建”类型的请求进入驱动。
当IRP_MN_QUERY_REMOVE_DEVICE返回成功,并且整个设备栈上的所有驱动都返回成功时,Windows的PnP管理器会发送IRP_MN_REMOVE_DEVICE请求,完成对设备彻底删除,当收到IRP_MN_REMOVE_DEVICE请求时,驱动需要释放全部资源,关闭设备,停止还在执行中的其它Irp请求。同时,驱动也要传播这个请求到下层驱动。
Windows文档中要求:IRP_MN_REMOVE_DEVICE请求是必须成功的,不能失败,否则可能会造成设备状态信息不一致。
IRP_MN_REMOVE_DEVICE请求有可能是驱动被卸载前的最后一个请求,所以必须要保证所有资源被正确释放。
如果IRP_MN_QUERY_REMOVE_DEVICE返回失败,那么Windows会重新发送一个IRP_MN_CANCEL_REMOVE_DEVICE请求通知驱动以及整个设备栈取消之前的请求。
当驱动收到IRP_MN_CANCEL_REMOVE_DEVICE时,需要解开设备锁定的状态,并允许“创建”类型的请求进入驱动。类似地,IRP_MN_CANCEL_REMOVE_DEVICE也要被传播到底层驱动,通知底层设备驱动更新状态。
还有一种特例是,虽然IRP_MN_QUERY_REMOVE_DEVICE返回成功,但上层代码改变主意,不希望删除设备,那么也会发送一个IRP_MN_CANCEL_REMOVE_DEVICE请求,对于这个请求的处理跟前面描述的是一致的:更新状态,传播Irp到底层。
所以大致的流程图如下
IRP_MN_QUERY_REMOVE_DEVICE(查询是否可以删除) | +-----(失败)---> IRP_MN_CANCEL_REMOVE_DEVICE | +-----(用户层面取消请求)---> IRP_MN_CANCEL_REMOVE_DEVICE | (成功) v IRP_MN_REMOVE_DEVICE (可能是最后一个IRP)
对于非正常拔出设备的删除流程:
Windows PnP管理器会发送一个IRP_MN_SURPRISE_REMOVAL请求给驱动,通知驱动这个设备已经被强制删除了。
这个时候驱动需要做的事情如下:
1. 检查设备是否还存在,如果还存在,需要停止并删除设备(笔者注:这段状态很奇怪,但文档里是这么写的)
2. 释放全部资源,阻止新的IO请求。
3. 除了IRP_MJ_CLEANUP,IRP_MJ_CLOSE,IRP_MJ_POWER,IRP_MJ_PNP之外,其它IRP都需要被阻止。
4. 如果过滤驱动不能处理这个请求,向底层传播。
5. 设置Irp->IoStatus.Status为STATUS_SUCCESS,必须成功,不能失败。
6. 调用IoSkipCurrentIrpStackLocation和IoCallDriver传播IRP
7. 通过IoCallDriver获得返回值并返回IRP dispatch函数
8. 不要调用任何Complete函数
当IRP_MN_SURPRISE_REMOVAL请求返回以后,Windows会继续发送一个IRP_MN_REMOVE_DEVICE完成设备删除,流程与正常安全移除设备的方式一致。
=============================================================================
附注:
一、即插即用IRP表
二、启动与停止代码
#pragma PAGEDCODE NTSTATUS HandleStartDevice(PDEVICE_EXTENSION pdx, PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HandleStartDevice\n")); //转发IRP并等待返回 NTSTATUS status = ForwardAndWait(pdx,Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } //得到当前堆栈 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); //从当前堆栈得到资源信息 PCM_PARTIAL_RESOURCE_LIST raw; if (stack->Parameters.StartDevice.AllocatedResources) raw = &stack->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList; else raw = NULL; KdPrint(("Show raw resouces\n")); ShowResources(raw); //从当前堆栈得到翻译信息 PCM_PARTIAL_RESOURCE_LIST translated; if (stack->Parameters.StartDevice.AllocatedResourcesTranslated) translated = &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList; else translated = NULL; KdPrint(("Show translated resouces\n")); ShowResources(translated); //完成IRP Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); KdPrint(("Leave HandleStartDevice\n")); return status; } #pragma PAGEDCODE NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp) { PAGED_CODE(); KdPrint(("Enter HelloWDMPnp\n")); NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) = { HandleStartDevice,// IRP_MN_START_DEVICE DefaultPnpHandler,// IRP_MN_QUERY_REMOVE_DEVICE HandleRemoveDevice,// IRP_MN_REMOVE_DEVICE DefaultPnpHandler,// IRP_MN_CANCEL_REMOVE_DEVICE DefaultPnpHandler,// IRP_MN_STOP_DEVICE DefaultPnpHandler,// IRP_MN_QUERY_STOP_DEVICE DefaultPnpHandler,// IRP_MN_CANCEL_STOP_DEVICE DefaultPnpHandler,// IRP_MN_QUERY_DEVICE_RELATIONS DefaultPnpHandler,// IRP_MN_QUERY_INTERFACE DefaultPnpHandler,// IRP_MN_QUERY_CAPABILITIES DefaultPnpHandler,// IRP_MN_QUERY_RESOURCES DefaultPnpHandler,// IRP_MN_QUERY_RESOURCE_REQUIREMENTS DefaultPnpHandler,// IRP_MN_QUERY_DEVICE_TEXT DefaultPnpHandler,// IRP_MN_FILTER_RESOURCE_REQUIREMENTS DefaultPnpHandler,// DefaultPnpHandler,// IRP_MN_READ_CONFIG DefaultPnpHandler,// IRP_MN_WRITE_CONFIG DefaultPnpHandler,// IRP_MN_EJECT DefaultPnpHandler,// IRP_MN_SET_LOCK DefaultPnpHandler,// IRP_MN_QUERY_ID DefaultPnpHandler,// IRP_MN_QUERY_PNP_DEVICE_STATE DefaultPnpHandler,// IRP_MN_QUERY_BUS_INFORMATION DefaultPnpHandler,// IRP_MN_DEVICE_USAGE_NOTIFICATION DefaultPnpHandler,// IRP_MN_SURPRISE_REMOVAL }; ULONG fcn = stack->MinorFunction; if (fcn >= arraysize(fcntab)) {// 未知的子功能代码 status = DefaultPnpHandler(pdx, Irp); // some function we don't know about return status; } #if DBG static char* fcnname[] = { "IRP_MN_START_DEVICE", "IRP_MN_QUERY_REMOVE_DEVICE", "IRP_MN_REMOVE_DEVICE", "IRP_MN_CANCEL_REMOVE_DEVICE", "IRP_MN_STOP_DEVICE", "IRP_MN_QUERY_STOP_DEVICE", "IRP_MN_CANCEL_STOP_DEVICE", "IRP_MN_QUERY_DEVICE_RELATIONS", "IRP_MN_QUERY_INTERFACE", "IRP_MN_QUERY_CAPABILITIES", "IRP_MN_QUERY_RESOURCES", "IRP_MN_QUERY_RESOURCE_REQUIREMENTS", "IRP_MN_QUERY_DEVICE_TEXT", "IRP_MN_FILTER_RESOURCE_REQUIREMENTS", "", "IRP_MN_READ_CONFIG", "IRP_MN_WRITE_CONFIG", "IRP_MN_EJECT", "IRP_MN_SET_LOCK", "IRP_MN_QUERY_ID", "IRP_MN_QUERY_PNP_DEVICE_STATE", "IRP_MN_QUERY_BUS_INFORMATION", "IRP_MN_DEVICE_USAGE_NOTIFICATION", "IRP_MN_SURPRISE_REMOVAL", }; KdPrint(("PNP Request (%s)\n", fcnname[fcn])); #endif // DBG status = (*fcntab[fcn])(pdx, Irp); KdPrint(("Leave HelloWDMPnp\n")); return status; }
三、设备资源
#pragma PAGEDCODE VOID ShowResources(IN PCM_PARTIAL_RESOURCE_LIST list) { //枚举资源 PCM_PARTIAL_RESOURCE_DESCRIPTOR resource = list->PartialDescriptors; ULONG nres = list->Count; ULONG i; for (i = 0; i < nres; ++i, ++resource) {// for each resource ULONG type = resource->Type; static char* name[] = { "CmResourceTypeNull", "CmResourceTypePort", "CmResourceTypeInterrupt", "CmResourceTypeMemory", "CmResourceTypeDma", "CmResourceTypeDeviceSpecific", "CmResourceTypeBusNumber", "CmResourceTypeDevicePrivate", "CmResourceTypeAssignedResource", "CmResourceTypeSubAllocateFrom", }; KdPrint((" type %s", type < arraysize(name) ? name[type] : "unknown")); switch (type) {// select on resource type case CmResourceTypePort: case CmResourceTypeMemory: KdPrint((" start %8X%8.8lX length %X\n", resource->u.Port.Start.HighPart, resource->u.Port.Start.LowPart, resource->u.Port.Length)); break; case CmResourceTypeInterrupt: KdPrint((" level %X, vector %X, affinity %X\n", resource->u.Interrupt.Level, resource->u.Interrupt.Vector, resource->u.Interrupt.Affinity)); break; case CmResourceTypeDma: KdPrint((" channel %d, port %X\n", resource->u.Dma.Channel, resource->u.Dma.Port)); }// select on resource type }// for each resource }// ShowResources