让设备实现即插即用


***【在线视频教程】***

好文章,来自【福优学苑@音视频+流媒体】



让设备实现即插即用

即插即用的概念

注:详情请参考《Windows驱动程序设计详解--张帆》


image.png

即插即用IRP


IRP_MJ_PNP:Major(MJ), Minor(MN)



image.png



设备接口

IoCreateDevice(....,DeviceName,......);

符号链接: IoCreateSymbolicLink(....)


image.png



//创建设备接口

status = IoRegisterDeviceInterface(PhysicalDeviceObject, &MY_WDM_DEVICE, NULL, &pdx->interfaceName);

IoSetDeviceInterfaceState(&pdx->interfaceName, TRUE);


WDM驱动程序中:xxxxxxAddDevice


1、创建设备对象,不用设备名称

2、绑定设备栈

3、注册设备接口(guid)

4、设置标志位

IoCreateDevice(

IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);

IoRegisterDeviceInterface

IoSetDeviceInterfaceState

//创建设备接口

status = IoRegisterDeviceInterface(

fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;

fdo->Flags &= ~DO_DEVICE_INITIALIZING;






启动与停止设备



IRP_MN_START_DEVICE

IRP_MN_REMOVE_REVICE


#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


即插即用的状态转换



Windows驱动之PNP状态转换

即插即用(Plug and Play – PnP)管理器使用主功能码为IRP_MJ_PNP的IRP与设备驱动程序交换信息和请求。


在WDM中,PnP请求扮演了两个角色。


在第一个角色中,这些请求指示驱动程序何时以及如何配置或取消其硬件或自身的设置。

PnP管理器使用IRP_MN_START_DEVICE来通知功能驱动程序其硬件被赋予了什么I/O资源,以及指导功能驱动程序做任何必要的硬件或软件设置以便设备能正常工作。

IRP_MN_STOP_DEVICE告诉功能驱动程序关闭设备。

IRP_MN_REMOVE_DEVICE告诉功能驱动程序关闭设备并释放与之关联的设备对象

PnP请求的第二个角色是指导驱动程序完成一系列状态转换。

WORKING和STOPPED是设备的两个基本状态。

当你创建设备对象后,设备就立即进入STOPPED状态。

WORKING状态指出设备是全部可操作的。

此外,还有两个中间状态,PENDINGSTOP和PENDINGREMOVE,它们出现在WORKING状态前。

SURPRISEREMOVED发生在物理硬件突然被移去的情况下。

我们先来看IRP_MJ_PNP的副功能码.


1. IRP_MN_XXX

IRP副功能码 描述

IRP_MN_START_DEVICE 配置并初始化设备

IRP_MN_QUERY_REMOVE_DEVICE 设备可以被安全地删除吗?

IRP_MN_REMOVE_DEVICE 关闭并删除设备

IRP_MN_CANCEL_REMOVE_DEVICE 忽略以前的QUERY_REMOVE

IRP_MN_STOP_DEVICE 关闭设备

IRP_MN_QUERY_STOP_DEVICE 设备可以被安全地关闭吗?

IRP_MN_CANCEL_STOP_DEVICE 忽略以前的QUERY_STOP

IRP_MN_QUERY_DEVICE_RELATIONS 给出与指定特征相关的设备列表

IRP_MN_QUERY_INTERFACE 获得直接调用函数地址

IRP_MN_QUERY_CAPABILITIES 取设备能力

IRP_MN_QUERY_RESOURCES* 取引导配置

IRP_MN_QUERY_RESOURCE_REQUIREMENTS* 取I/O资源需求

IRP_MN_QUERY_DEVICE_TEXT* 获得描述信息或位置串

IRP_MN_FILTER_RESOURCE_REQUIREMENTS 修改I/O资源需求列表

IRP_MN_READ_CONFIG* 读配置空间

IRP_MN_WRITE_CONFIG* 写配置空间

IRP_MN_EJECT* 弹出设备

IRP_MN_SET_LOCK* 设备弹出锁定/解除

IRP_MN_QUERY_ID* 取设备硬件ID

IRP_MN_QUERY_PNP_DEVICE_STATE 取设备状态

IRP_MN_QUERY_BUS_INFORMATION* 取父总线类型

IRP_MN_DEVICE_USAGE_NOTIFICATION 通知分页、dump、睡眠文件被创建或删除

IRP_MN_SURPRISE_REMOVAL 通知设备已经被删除

IRP_MJ_PNP的副功能码有如下这些:


2. 状态转换

PNP设备的状态图如下:



2.1 Not Present

首先,当设备没有插入的时候,属于NotPresent状态,这个时候这个状态唯一可以转换的是插入设备,进入Stopped状态。

其次,如果设备插入之后被拔出了,那么这个状态也会回到NotPresent状态,拔出的时候会受到REMOVE_DEVICE的消息。

2.2 Stopped

当设备插入的时候,调用AddDevice创建一个功能性的设备对象来处理功能,这个时候,设备的状态就是Stopped,表明这个设备并没有开始工作。

如果一个正在处于工作状态的设备,如果停止的话,就会收到一个STOP_DEVICE的消息。

在working和stop中间,有一个pendingstop的状态,表明开始请求停止设备,在pendingstop状态下,可以cancel_stop_device,也可以stop_device.

2.3 working

当一个stop的设备,调用Start_Device之后,这个设备就处于working状态了,说明这个设备可以相应所有请求了。

一个woring的设备,可以stop_device将设备停止,此时设备状态为stopped状态。

当然如果一个working的设备之间被拔出,那么通过remove_device将设备变成拔出状态。


PnP(即插即用)


  即插即用是一个用于自动处理PC机硬件设备安装的工业标准,由Intel和Microsoft联合制定。通常,当您需要安装新的硬件时,往往要考虑到该设备所使用的DMA和IRQ资源,以避免设备之间因竞争而出现冲突,甚至导致机器无法正常工作。

  有了“即插即用”(PnP),它使得硬件设备的安装大大简化,您无须再做跳线,也不必使用软件配置程序,但是您所安装的新硬件必须是符合PnP规范的,否则是行不通的。

  即插即用代表着最近接口技术的主要进展。但它不是一个全新的概念。它是MCA与EISA接口设计的关键特性,但是MCA和EISA有限的吸引力使得它没有成为行业标准。因此,主流PC用户仍然为I/O地址,DMA通道以及IRQ的设置担忧。早期基于PCI的系统也使用了一种PnP配置的方式,但由于没有提供PCI插卡和ISA插卡冲突的管理措施,许多用户仍然为一些配置问题而烦恼。但现在即插即用规范可用于基于ISA、PCI、SCSI、IDE和PCMCIA的系统中,所有新计算机的购买者不用再担心硬件设置了。

  为了使即插即用正常工作,需要以下的部件:

  ·即插即用硬件。

  ·即插即用BIOS。

  ·即插即用操作系统。

  这些部件都要求兼容即插即用,意味着它们要服从即插即用规范。

  1.硬件部件

  硬件部件包括计算机系统与适配卡。这并不意味着在即插即用系统中不能使用较老的ISA适配卡。可以使用这些插卡,实际上,即插即用BIOS自动围绕存在的遗留部件重新指定即插即用兼容插卡的设置。另外,许多后期的ISA插卡也可以转换到即插即用模式下。即插即用适配卡同系统BIOS和操作系统通信来传播关于所需系统资源的信息。然后,BIOS和操作系统解决冲突(如果有的话)并通知适配卡应当使用哪些特定资源。适配卡便改变其自身的配置以使用特定的资源。

  2.BIOS部件

   BIOS部件意味着多数较老PC机的使用者需要升级他们的BIOS,或者购买新的具有PnP BIOS的机器。BIOS要成为兼容的,必须支持13个附加的系统功能调用,它们可以被即插即用系统的操作系统部件使用。PnP BIOS规范由Compaq、Intel和Phoenix Technologies共同发展。

  BIOS的PnP特性通过一个扩展的POST实现。BIOS负责鉴别、隔离和配置PnP适配卡。BIOS通过以下的步骤实现这些任务:

  (1)禁用所有主板和适配卡上配置的设备。

  (2)鉴别所有PnP PCI或ISA设备。

  (3)为端口、IRQ、DMA及存储器生成一个最初的资源分配图。

  (4)启用I/O设备。

  (5)扫描ISA设备的ROM。

  (6)配置最初的载人程序设备,用于后来启动系统。

  (7)通过通知分配了哪些资源来启用可配置的设备。

  (8)开始载人启动程序。

  (9)将控制传递给操作系统。

  3.操作系统部件

  操作系统部件可以通过多数新系统实现,如Windows 9x/Me/2000/XP。有时,系统供应商为其指定的硬件提供了对操作系统的扩展,如在笔记本系统中更是如此。如果系统需要,要保证这些扩展已经安装在系统中。

  通知用户不能由BIOS解决的冲突是操作系统的责任。依靠使用操作系统的经验,用户可以手工设置冲突的插卡,或者关闭系统并在插卡上设置开关。当系统重启后,系统检测保持的(或新的)冲突,这些都提示用户要注意。通过这样的过程,可以解决所有的系统冲突。

好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***