WinXP下USB驱动开发(七)

2019-07-14 03:40发布

3.3.2.6. 电源管理

3.3.2.6.1. WDM电源管理模型
Windows 2000Windows 98中,操作系统接管了大部分电源管理工作。当然,这是因为只有操作系统才能真正了解电源管理的内部过程。例如,系统BIOS负责的电源3.3.2.6.管理不能区分应用程序使用的屏幕和屏幕保护程序使用的屏幕之间的区别。但操作系统可以区分开这种不同,从而确定是否可以关闭显示器。 作为计算机全局电源策略,操作系统支持一些用户接口元素,用户可以通过这些接口元素控制最终的电源管理策略。这些用户接口元素包括控制面板、开始菜单上的命令、控制设备唤醒特征的API。通过向设备发送IRP,内核的电源管理部件实现了操作系统的电源策略。WDM驱动程序主要是作为响应这些IRP的被动角 {MOD}。
3.3.2.6.2. WDM驱动程序的角 {MOD}
设备的某个驱动程序需要充当设备电源策略的管理者。通常都是由功能驱动程序充当这个角 {MOD}。电源管理器可以改变整个系统的电源状态。功能驱动程序接收电源管理器发来的IRP(系统IRP),作为设备电源策略的管理者,功能驱动程序用设备理解的术语翻译这些IRP并引发新的IRP(设备IRP)。当响应设备IRP时,功能驱动程序需要关心设备专有的细节。设备硬件会有自己的上下文信息,不应该在设备处于低电源期间丢失这些信息。例如,键盘驱动程序会保存锁定键(CAPS-LOCKNUM-LOCKSCROLL-LOCK)LED等信息。功能驱动程序有责任保存并恢复这些上下文信息。某些设备带有唤醒特征,当外部事件发生时,这些设备可以唤醒系统;功能驱动程序应与用户协同工作以确保唤醒特征在需要时有效。许多功能驱动程序还管理含有大量IRP的队列(设备读写IRP),因此当设备电源状态转变时需要停止或释放这些队列。 处于设备堆栈底端的总线驱动程序有责任控制设备的电流和执行任何与设备唤醒特征相关的电气步骤。过滤器驱动程序通常作为电源管理IRP通过的管道,它们用专用的协议向下层驱动程序传递电源管理请求。
3.3.2.6.3. 设备电源状态与系统电源状态
WDM模型使用与ACPI(Advanced Configuration and Power Interface)规范(http://www.teleport.com/~acpi/spec.htm)相同的术语来描述电源状态。设备能呈现图3-3-2-0所描述的四种电源状态。在D0状态中,设备处于全供电状态。在D3状态中,设备处于无供电(或最小限度的电流)状态。中间的D1D2状态指出设备的两个不同睡眠状态。随着设备从D0状态变化到D3状态,设备将消耗越来越少的电力,同时需要保留的当前状态上下文信息也越来越少。而设备再转变回D0状态的延迟期则相应增加. Microsoft规定了不同类型设备的类专用电源需求。这个需求规范可以在http://www.microsoft.com/hwdev/specs/PMref/找到。例如,这个规范要求每个设备至少要支持D0D3两个状态。输入设备(键盘、鼠标等)还应该支持D1状态。Modem设备需要另外支持D2状态。设备类上的这些不同规定可能来源于设备的用途和工业上的实践。 操作系统不直接处理设备的电源状态,由设备驱动程序专门处理。系统使用一组与ACPI设备状态类似的系统电源状态来控制电源,见图3-3-2-1Working状态是全供电状态,计算机可以实现全部功能。程序仅能在Working状态下执行. 其它系统电源状态对应更小的电力需求配置,在这些系统电源状态中,计算机不能执行任何指令。Shutdown状态就是电源关闭状态。Hibernate状态是另一种Shutdown状态,它把计算机的整个状态都记录到硬盘上,因此在电源恢复供电时可以使计算机快速恢复到记录前的状态。在HibernateWorking状态之间是三个有不同电力消耗级别的中间状态。
1.1.1.1.4. 电源状态转换
系统初始化后即进入Working状态。大部分设备也以D0状态启动,但某些设备的驱动程序会在设备启动时使设备进入低电源消耗状态。在系统启动并正常运行后,这些设备的驱动程序才使设备进入一个稳定的状态,在这个状态中,系统电源处于Working状态,而设备处于的状态取决于具体活动和设备自身的能力。 用户的活动或外部事件会导致电源状态的改变。一个常见的电源状态转换情景是用户在开始菜单上选择关闭系统中的“standby”选项,使计算机进入等待状态。在响应这个命令过程中,电源管理器首先向每个驱动程序发送带有IRP_MN_QUERY_POWER副功能码的IRP_MJ_POWER请求以询问设备能否接受即将到来的电源关闭请求。如果所有驱动程序都同意,电源管理器将发送第二个带有IRP_MN_SET_POWER副功能码的电源管理IRP,然后驱动程序把其设备置入低电源状态以响应这个IRP。如果有任何一个驱动程序否决了这个查询,电源管理器仍旧发出这个IRP_MN_SET_POWER请求,但它用原来的电源级别换成了请求的电源级别。 系统并不总是发送IRP_MN_QUERY_POWER请求。某些事件(如电池电力将要耗尽)必须被无条件接受,并且操作系统也不再发出查询请求。如果查询发出后,并且驱动程序也接受了请求的电源状态,那么驱动程序将不再启动任何会妨碍未来电源状态设置请求的操作。例如,磁带机驱动程序在使一个进入低电源状态的查询请求成功返回前先确保当前没有执行备份操作。另外,该驱动程序还拒绝任何后来的备份命令,除非是另一个电源状态设置请求。
3.3.2.6.5. POWER_STATUS
该结构体在电源管理中占据很重要的地位,其主要进行细微的描述电源的状态和剩余时间等方面的信息,该结构体描述如下; typedef struct _POWER_STATUS { BYTE PS_AC_Line_Status; BYTE PS_Battery_Status; BYTE PS_Battery_Flag; BYTE PS_Battery_Life_Percentage; WORD PS_Battery_Life_Time; } POWER_STATUS; Ø PS_AC_Line_Status描述电源AC输入线的状态。 Ø PS_Battery_Status描述电池的状态。 Ø PS_Battery_Status描述电池先前的状态或者是改变时的状态。 Ø PS_Battery_Life_Percentage描述电池还剩余多少电,以百分计。 Ø PS_Battery_Life_Time描述电池还能支持多少时间,以秒钟计。
3.3.2.6.6. IRP_MN_QUERY_CAPABILITIES
通常,我们总是把设备设置成与设备的当前活动、设备的唤醒特征、设备的能力、以及迫近的系统状态,相一致的最低的电源状态。PnP管理器在启动设备后(也可能是其它时间)的很短时间内向设备发出查询设备能力请求——IRP_MN_QUERY_CAPABILITIES请求,该请求的参数是一个DEVICE_CAPABILITIES结构,该结构体将描述系统的电源状态信息,DEVICE_CAPABILITIES结构体描述。 typedef struct _DEVICE_CAPABILITIES { USHORT Size; USHORT Version; ULONG DeviceD1:1; ULONG DeviceD2:1; ULONG LockSupported:1; ULONG EjectSupported:1; ULONG Removable:1; ULONG DockDevice:1; ULONG UniqueID:1; ULONG SilentInstall:1; ULONG RawDeviceOK:1; ULONG SurpriseRemovalOK:1; ULONG WakeFromD0:1; ULONG WakeFromD1:1; ULONG WakeFromD2:1; ULONG WakeFromD3:1; ULONG HardwareDisabled:1; ULONG NonDynamic:1; ULONG WarmEjectSupported:1; ULONG NoDisplayInUI:1; ULONG Reserved:14; ULONG Address; ULONG UINumber; DEVICE_POWER_STATE DeviceState[PowerSystemMaximum]; SYSTEM_POWER_STATE SystemWake; DEVICE_POWER_STATE DeviceWake; ULONG D1Latency; ULONG D2Latency; ULONG D3Latency; } DEVICE_CAPABILITIES, *PDEVICE_CAPABILITIES; Ø DeviceState域为与每个系统状态对应的可能存在的设备最高级状态数组。 Ø SystemWake域为最低级的系统电源状态,在该状态中设备可以生成唤醒信号。PowerSystemUnspecified域指出设备不能唤醒系统。 Ø DeviceWake域为最低级的设备电源状态,在该状态中设备可以生成唤醒信号。PowerDeviceUnspecified指出设备不能生成唤醒信号 Ø D1Latency域为设备从D1切换到D0状态所需的最长时间(100微秒单位) Ø D2Latency域为设备从D2切换到D0状态所需的最长时间(100微秒单位) Ø D3Latency域为设备从D3切换到D0状态所需的最长时间(100微秒单位) Ø WakeFromD0WakeFromD1WakeFromD2WakeFromD3标志,指出当设备处于指定的状态中时,设备的系统唤醒特征是否是可操作的
3.3.2.6.7. IRP_MJ_POWER
WDM驱动当中,驱动程序响应IRP_MJ_POWER例程表示支持电源管理。电源状态描述保存在IO_STACK_LOCATIONParameters联合中。该联合提供了响应的四种电源状态,如下表3-3-2所示。 3-3-2 IO_STACK_LOCATIONParameters.Power子结构中的各个域 域名 描述 SystemContext 电源管理器内部使用的上下文值 Type DevicePowerStateSystemPowerState (POWER_STATE_TYPE类型的枚举值) State 电源状态,可为DEVICE_POWER_STATESYSTEM_POWER_STATE ShutdownType 指出转换到PowerSystemShutdown状态的原因代码 在管理器和驱动沟通使用了IRP_MJ_POWER类型的I/O请求包,该功能附带了四个辅助码。IRP_MN_QUERY_POWER确定预期的电源状态改变是否安全;IRP_MN_SET_POWER命令驱动程序改变电源状态;IRP_MN_WAIT_WAKE命令总线驱动程序使用唤醒特征、使功能驱动程序能了解唤醒信号何时发生;IRP_MN_POWER_SEQUENCE为上下文保存和恢复提供优化。 对于整个操作系统而言,所有驱动程序,包括过滤器驱动程序和功能驱动程序通常都向其下面的驱动程序传递电源管理请求,除IRP_MN_QUERY_POWER请求外。在功能驱动(例如、USB驱动),系统控制程序如何把电源管理请求传递到低级驱动程序有特殊的规则。图3-3-2-2显示了三种可能的处理过程。第一,在释放一个电源管理请求的控制之前,你必须调用PoStartNextPowerIrp,即使你以错误状态完成该IRP,也要这样做。做这个调用的原因是,电源管理器自己需要维持一个电源管理请求队列,所以必须通知它确实可以出队一个请求,以及向设备发送下一个请求。除了调用PoStartNextPowerIrp,你还必须调用专用例程PoCallDriver(代替IoCallDriver)来向下层驱动程序发送请求。 3-3-2-2 系统电源状态
3.3.2.6.8. IRP_MN_SET_POWERIRP_MN_QUERY_POWER
PNP管理器发出IRP_MN_SET_POWER后,例程函数可以设置系统和设备电源状态。在DDKSystemPowerStateDevicePowerState为系统和设备电源状态。在PNP中是如何区分那种类型的电源状态呢?DDKI/O参数Parameters.Power.Type标识电源状态类型。如果Parameters.Power.TypeSystemPowerState则重新设置的是系统电源状态,反之亦然。 IRP_MN_QUERY_POWER请求发生在IRP_MN_SET_POWER之前,主要用于确定是否具备修改电源状态的能力。
3.3.2.6.9. IRP_MN_POWER_SEQUENCE
PNP管理器发出IRP_MN_POWER_SEQUENCE请求,主要用于指出设备是否已经进入制定设电源状态。 IRP_MN_POWER_SEQUENCE请求制定三种电源状态,SequenceD1SequenceD2SequenceD2。该请求后电源状态保存在Parameters.PowerSequence当中。
3.3.2.6.10. IRP_MN_WAIT_WAKE
PNP管理器发出IRP_MN_WAIT_WAKE请求,指定设备是否具有硬件唤醒特征,这允许它们在外部事件发生时可以唤醒沉睡中的系统。当系统产生唤醒请求时,会调用PoRequestPowerIrp函数发出IRP_MN_WAIT_WAKE请求,其实其他IRP_MN_SET_POWERIRP_MN_QUERY_POWER等请求也都是由该函数发起,但IRP_MN_POWER_SEQUENCE除外,请求后由USB总线处理。PoRequestPowerIrp函数描述如下; NTSTATUS PoRequestPowerIrp( IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PREQUEST_POWER_COMPLETE CompletionFunction, IN PVOID Context, OUT PIRP *Irp OPTIONAL); Ø MinorFunction 于指定为IRP_MN_QUERY_POWERIRP_MN_SET_POWERIRP_MN_WAIT_WAKE之一。 Ø PowerState域特地为IRP_MN_SET_POWERIRP_MN_QUERY_POWER准备,指出需要请求的电源状态。 Ø CompletionFunction域为完成用例函数。 Ø Context域为需要向用例函数传递的参数。
3.3.2.6.11. AddDevice中设置的标志
设备对象中的两个标志位,见表3-3-3,控制电源管理的各个方面。当你在AddDevice函数中调用了IoCreateDevice例程之后,这两个位都将被置0,你可以根据具体环境设置这两个位。 3-3-3. DEVICE_OBJECT中的电源管理标志 标志 简要描述 DO_POWER_PAGABLE 驱动程序的IRP_MJ_POWER派遣例程必须运行在PASSIVE_LEVEL DO_POWER_INRUSH 该设备在上电时需要大电流 如果IRP_MJ_POWER请求的派遣函数必须运行在PASSIVE_LEVEL级别,你需要设置DO_POWER_PAGABLE标志。该标志的名称含有相关的含义,因为只有在PASSIVE_LEVEL级上才允许分页操作。如果你把该标志设置为0,则电源管理器可以在DISPATCH_LEVEL级上向你发送电源管理请求。实际上,在当前发行的Windows 2000中总应该这样做。 如果你的设备在上电时需要大电流,设置DO_POWER_INRUSH标志,这可以使多个有这样要求的设备不同时上电。另外,电源管理器会在DISPATCH_LEVEL级上向inrush设备发送电源管理请求,这意味着你不能同时设置DO_POWER_PAGABLE标志。 如果设备的ASL描述中这样指定,那么系统的ACPI过滤器驱动程序将在PDO中自动设置INRUSH标志,这样系统会正确地顺序化有INRUSH标志设备的电源供应,因此你并不需要在自己的设备对象中设置这个标志。但是,如果系统不能自动确定你的设备是否需要inrush对待,你需要自己设置这个标志。 设备的所有设备对象中的PAGABLEINRUSH标志设置都要一致。如果PDO设置了PAGABLE标志,则其它设备对象也应该设置PAGABLE标志。否则将发生代码为DRIVER_POWER_STATE_FAILUREbug check(一个PAGABLE设备在一个非PAGABLE设备之上是合法的,但相反则不行)。如果设备对象设置了INRUSH标志,那么它自己和任何它下面的低级设备对象都不应该设置PAGABLE标志,否则将发生代码为INTERNAL_POWER_ERRORbug check。如果你正写一个磁盘驱动程序,不要忘了为响应设备分页文件的PnP通知,你可以随时在分页和非分页状态间改变。
3.3.2.6.12. 设备电源管理流程
设备电源管理代码编写相当复杂,如果处理不当将会带来很严重的错误。在DDK环境下,支持NTWDM两种驱动模式,电源管理代码需要自己编写进行处理。在微软新推出的WDK环境下,新推出的WDF驱动模式很好的解决了这个问题。微软认为电源管理应该模块化,处理方式基本一致,而且技术不过关的工程师很难将其处理完美,因为微软提供了WDK选择支持电源管理的功能,帮助工程师完成电源管理。但是、我们的USB 驱动采用的是WDM模式,如下流程图(图3-3-2-3)将说明设备电源管理的流程。

3.3.2.7. IDLE

空闲处理同样是USB驱动非常重要的地方,当长时间未时候使用USB的时候,因该让设备进入空闲状态。在USB驱动中可以通过KeSeTimer设置定时器进行适时检测设备空闲状态。 USBD.SYS处理USB“空闲状态设置的时候,IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION可以提交空闲通知。USB_IDLE_CALLBACK_INFO用于设置IDLE的回调函数信息,其实也是一种完成用例的体现。 IDLE处理是一个独立而且频繁的过程,在处理IDLE的时候,IO_WORKITEM相关操作不得不将其引入,该结构体的意识是定义一个I/O工作项,Windows中将IDLE处理放入一个工作项当中进行,该工作项可以随时唤醒。工作项相关的几个函数有IoAllocateWorkItemIoQueueWorkItemIoFreeWorkItem。这些函数的功能参照DDK描述所示,如表3-3-4. 3-3-4. 函数描述 标志 简要描述 IoAllocateWorkItem allocates a work item. IoQueueWorkItem inserts the specified work item into a queue from which a system worker thread removes the item and gives control to the specified callback routine. IoFreeWorkItem frees the specified work item. IDLE处理过程一般是先申请一个Work Item(调用IoAllocateWorkItem函数),任务队列处理(调用IoQueueWorkItem函数),处理完成后释放申请的Work Item(调用IoFreeWorkItem)