4.7电源管理
电源管理模块管理设备电源,从而全面改进操作系统的电源使用效率;它所有设备的电源使用,同时能与不支持电源管理的应用程序及驱动程序共存。
使用电源管理可以有效的减少目标设备的电源消耗,同时,能在运行、空闲、复位及挂起电源状态时维持并保护RAM中的文件系统。
电源管理具有以下特性:
使设备具有自行电源管理功能的框架;
建立一种在系统中使设备电源在挂起和恢复状态间切换的机制;
一个对系统环境、电源状态、设备电源状态有统一认识的模块。你可以定制电源管理模块,使全系统的电源分配适合您的设备;
当系统挂起或恢复时,使系统跳过所有代码调用PowerOffSystem函数。
对于支持电源管理的驱动程序,在设备枚举时应尽量准确的向电源管理模块报告其性能,然后将收到来自电源管理模块的获取或更改其电源状态的请求。对于自行管理电源的设备,必须通过DevicePowerNotify向电源管理模块发送请求来更改电源状态。
电源管理模块与设备在一个正常的线程执行环境中通过调用DeviceIoControl进行通讯。
4.7.1、
电源管理架构
在不依赖Windows CE电源管理模型的情况下,只能对设备进行简单的管理。电源管理器为OEM厂商提供了较大的灵活性,同时驱动开发人员也不用为了兼容电源管理模型而牺牲驱动程序性能。在基本的Windows
CE电源模型中,在操作系统挂起或恢复时,设备会收到标识信息(notification)。这一标识信息在中断处理时产生,所以设备在挂起状态时可以做什么工作,有多长时间做这一工作被严格限制。下图描述了Windows
CE电源管理的结构。
使用电源管理器时,设备会从I/O控制代码(IOCTLs)收到电源状态改变通知。因为IOCTLs在线程中执行,在线程中驱动开发者可以更为灵活的处理电源状态改变。使用IOCTLs还能够区分设备电源状态与系统电源状态。这样,在系统运行时可以将部分设备关闭,而某些设备也可以在系统挂起时一直开启。
除了管理设备电源,电源管理器还向应用程序通知其它与电源相关事件的发生。例如:当系统从挂起状态恢复运行时,电源管理器会向相关应用程序发出通知。
电源管理器是一个名为pm.dll的动态链接库,系统运行时被Device.exe进程直接调用。当电源管理API被调用时,Device.exe调用pm.dll的入口函数。在Platform
Builder 4.0及其后续版本中提供了Pm.dll的源代码,OEM厂商可以根据其设备修改Pm.dll。
电源管理器担任设备、应用程序及预定义的操作系统电源状态的仲裁者。电源管理器定义了下列规则,使上述三部分以这些规则进行通讯:
l
系统电源状态限定了所有设备的最大功耗;
l
应用程序以最小功耗来获取指定设备的最低性能等级。
l
当设备功耗在其最大功耗与最小功耗范围内时,电源管理器允许设备自行管理其电源。
l
如果最小功耗高于最大功耗,那么,在应用程序请求设备运行期间,设备电源将持续提升。
l
设备可以在一个或多个电源状态下运行。电源状态以有限的数字表示。详见设备电源状态章节。
l
系统转为挂起状态后,对应用程序有用的最小功耗将被取消。
l
系统电源状态是描述所有设备中最大的设备电源状态。系统电源状态由OEM厂商定义,并在注册表中描述,同时,在电源管理器中可以加入部分支持代码。OEM厂商可以定义任意数量的系统电源状态。详见系统电源状态章节。
在电源管理器框架内部,OEM厂商可以定义系统电源状态来设定最大设备电源状态。设备调用DevicePowerNotify来控制其电源等级,应用程序调用SetPowerRequirement来确认它们需要使用的设备是否正在合适的性能等级运行。
4.7.1.1
电源管理器和ACPI/APM
电源管理器并不涉及高级配置与电源接口(ACPI)或高级电源管理(APM)。ACPI规范将系统电源状态定义为一个从高功率/高性能到低功率/低性能的线性集。Windows
CE电源管理器允许OEM厂商定义任意数目的系统电源状态,但并不要求定义的系统电源状态是线性的。
我们鼓励开发者根据情况定义操作系统电源状态,并根据设备定位或环境控制设备电源。例如:开发者可以定义系统电源状态,据此可以做到在装置不在支架(cradle)中时可以关闭某些设备,或者当系统使用外接交流电源时允许设备运行在高功耗电源等级。同样的,开发者可以根据系统环境定义不同的空闲、挂起状态。开发者需要定制电源管理器并保证在适当或必须的时候在各种电源状态间切换。
系统电源状态与ACPI模式有显著的区别。然而,设备电源状态表面上看起来与ACPI规范中的设备电源状态类似,但实际上,他们仍有细微的区别。例如:在Windows
CE中D3电源状态被设定为在操作系统挂起时可使指定设备担任唤醒源的功能。
4.7.1.2
电源挂起、恢复回调函数
挂起和恢复电源事件的回调函数与电源管理器无关。当CPU被停止,操作系统将进入挂起状态,这时回调函数会被系统调用。并在OEMPowerOff被调用前立即执行。在电源管理器请求关闭系统电源并进入挂起状态之前,电源管理器会时常对设备驱动进行IOCTL调用。当然,情况并不总是这样。电源管理器框架允许设备在系统运行时被关闭,也允许设备在系统挂起时处于开启状态。
当设备电源状态为D0,D1或D2,如果掉电事件发生时,开发者可以自行决定进行何种处理。此时,通常关闭设备电源,并在加电事件发生时恢复供电。如果设备可以不依赖CPU即可运行,在挂起状态时或许可以使此设备运行。如果设备在挂起期间一直处于供电状态,那可能是电源管理器配置错误,或者是应用程序使用POWER_FORCE标记对此设备进行了SetPowerRequirement调用。
4.7.1.3
其他电源管理API
电源管理器创建于标准的Windows CE电源体系结构的上层。所以,不管电源管理器如何运行,流接口设备驱动程序在系统挂起或恢复时总是通过XXX_PowerUp和XXX_PowerDown获取通知。下表列出了与电源管理相关的API。
函数名
功能描述
GetSystemPowerStatusEx2
获取电池状态信息。
PowerPolicyNotify
以事件的形式通知电源管理器,以便执行必要的处理,从而实现OEM创建的电源策略。
OEMIdle
被系统内核调用。在没有线程需要运行时将处理器置为空闲状态。
OEMPowerOff
当关机按钮被按下,或图形、窗体、事件子系统(GWES)超时时被调用。使处理器进入挂起状态。
XXX_PowerDown (Device Manager)
挂起设备电源。仅用于能被软件控制关闭的设备。
XXX_PowerUp (Device Manager)
恢复设备电源。
Windows CE设备驱动开发之电源管理 第二部分
4.7.2、电源状态
电源管理器期望所有被管理的设备能支持一个或多个设备电源状态。设备电源状态的数量是有限的。设备必须通知电源管理器其功耗特性。设备常以功耗换取性能。
电源管理器在OEM定义的系统电源状态下管理设备电源状态。系统电源状态在注册表中定义,可以用任意数字定义。系统电源状态会给设备电源状态设置一个上限。
某些应用程序可能需要特定设备保持运行在指定的设备功率等级上。例如:当一个音频播放程序在播放音乐时,可能需要网卡及音频解码器保持运行在高功率等级。视频播放程序可能需要网络、音频,同时可能要使显示设备在进入屏幕保护模式后一直显示,并保持背光常亮。应用程序可以请求电源管理器设置最小设备电源状态,电源管理器会调用SetPowerRequirement和ReleasePowerRequirement系统API来进行设置。
4.7.2.1
设备电源状态
设备电源状态是预定义的静态值。电源管理器将设备状态传给驱动程序,驱动程序负责将其映射为自身的设备性能,然后在物理设备上进行状态转换。
下表是对各种设备电源状态的描述。
设备电源状态
注册表键值
描述
Full on
D0
此状态表示设备已开启或正在运行。设备将以系统允许的最大功耗及最高性能运行。
Low on
D1
此状态表示设备已开启或正在运行,但以低于D0状态的功耗及性能运行。D1状态适用于设备已经被使用,但以较低的性能运行即可,没有必要以最大性能运行,会产生额外的功率消耗。
Standby
D2
此状态表示设备被部分供电,保证设备在需要时能自动唤醒。
Sleep
D3
睡眠状态。保证唤醒的最小供电,在需要时能自动唤醒并初始化。
Off
D4
关闭状态,不供电。
一种物理设备并不能支持上述所有的设备电源状态。但是,所有的设备都必须支持D0设备电源状态。如果驱动程序收到请求,要求其将设备进入它不支持的电源状态,驱动程序应使设备进入下一个支持的电源状态。例如:电源管理器请求设备进入D2电源状态,但设备并不支持D2状态,这时如果设备支持D3或D4状态,驱动程序应使设备进入D3或D4状态。如果某一设备需要进入D3状态,但是此设备却不能唤醒系统,那么应使此设备进入D4状态。上述这些规则可以使驱动程序的执行简单化。
电源管理器有选择的将系统电源状态映射为对应的设备电源状态。例如:如果设备仅支持D0及D4电源状态,那么,电源管理器不会直接请求设备进入D4电源状态。如果D3或D4被设为此设备的最小电源状态,电源管理器会一直等待直到系统进入D3或D4状态时,再将设备设为D4状态。如果此设备的最小电源状态被设为D0、D1或D2,电源管理器将使设备一直运行于D0状态。
当设备驱动程序被加载时,应将设备设为D0状态。在驱动程序被卸载时,应将设备设为D4状态。如果在启动时设备进入了D0外的其他设备电源状态,那么可以在处理IOCTL_POWER_CAPABILITIES时发出一个DevicePowerNotify请求。
4.7.2.2
系统电源状态
系统电源状态由OEM定义,并由OEM引用。OEM可以将其命名为像On,SystemIdle,OnBattery,InCradle,OutOfCradle等名称。这些名称并没有被系统预定义,也不要求将其定义为线性序列。系统电源状态在系统配置注册表键中定义。Windows
CE并没有限制可以定义多少种系统电源状态。
当然,也可以创建系统电源状态与预定义的设备电源状态的显式映射。显式映射需要在注册表中定义。系统电源状态明确的制定系统中所有设备的最大设备电源状态。
电源管理器示例定义了On,UserIdle,SystemIdle及Suspend四种系统电源状态。当用户使用系统时,电源状态设为On。如果用户停止使用,电源状态被设为UserIdle。当用户在一定的周期内(如30s)不使用系统,则进入SystemIdle状态;只要设备驱动程序处于活动状态,系统将一直保持在SystemIdle状态。如果设备驱动程序停止活动,系统进入Suspend状态。
UserIdle状态用于用户正在使用设备,但却没有操作设备。例如,用户一直观看屏幕显示,但没有手动操作。SystemIdle状态被用于用户没有直接使用设备,但处理器仍在继续运行。例如,在传输文件期间,用户可能人为设备已处于空闲状态,但实际上处理器依旧在持续运行,直到文件传输完成。
电源管理器示例实现了根据UserActivity和SystemActivity定时器对用户及系统活动进行判断。在定时器超时后,根据当前系统供电状况(使用外接电源或电池)进行不同的系统电源状态切换。
Platform Builder提供的Windows CE运行时image示例均使用外接电源供电模式。你可以选择实现一套在使用电池供电时的电源状态。复制电源管理器示例代码的PDD目录,并对其做适当的修改即可。
4.7.2.2.1
将系统电源状态映射为设备电源状态
在注册表系统配置中明确定义系统电源状态名称。系统电源状态到设备电源状态的映射在注册表的每一个电源状态名称键值下被枚举。如下示例代码:
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/Example]
Default=dword:0; D0
Flags=dword:10000; POWER_STATE_ON
COM1:=dword1; D1
上面的注册表片段定义了名为Example的系统电源状态,并设置了除COM1:被限制为最高运行于D1设备电源状态外,其他的所有设备最高均可运行于D0设备电源状态。其中Flags列是一个标识码,用于表示Pm.h头文件中定义的POWER_STATE_ON标记。如果需要,OEM可以定义自己的电源状态标记。
下表列出了键值名称及其描述。
键名
描述
Name
系统电源状态名称
Flags
标识码。用于表示在Pm.h头文件中预定义的类似于POWER_STATE_ON的标记。
Default
表示在此系统电源状态下时,所有设备的默认设备电源状态。此键值用数字表示,0代表D0,1代表D1,以此类推。
DeviceName
设置在此系统电源状态下指定设备的最大设备电源状态。可以对任意数目的设备进行定义。
电源管理器支持多种设备类型的映射。例如:NDIS迷你接口及块设备驱动器设备类型在Pm.h中会被指定为其自身的GUID类型。其他类型被管理设备的默认值也可以在注册表中指定。例如:
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/Example/{98C5250D-C29A-4985-AE5F-AFE5367E5006}]
Default=dword:1; D1
"CISCO1"=dword:0; D0
上面注册表片段设置电源管理器限值所有NDIS迷你接口设备在Example系统电源状态下时均运行于D1设备电源状态,只有名为CISCO1的设备最高可运行于D0状态。
4.7.2.2.2
系统电源状态切换
电源管理器在下列情况下进行系统电源状态切换:
l
OEM定义的状态切换事件发生。
l
应用程序调用SetSystemPowerState。
OEM定义的事件可能包含设备供电从外接电源切换为使用电池供电,延长系统空闲周期,将设备插入底座(Cradle),电池电量低等。OEM需要根据情况修改电源管理器,以便判断两种系统电源状态间的切换是否合理,并在必要时切换系统电源状态。Platform
Builder提供的电源管理器源代码仅支持在设备从外接电源切换为使用电池供电时进行系统电源状态切换。
应用程序可以使用系统电源状态名或表示系统电源状态的数值为参数调用SetSystemPowerState函数。如果应用程序了解OEM定义的系统电源状态,那么可以选择使用电源状态名进行显式调用。对于独立于平台的应用程序,则设置数值进行调用,并允许电源管理器决定如何进行电源状态映射。电源管理器可对应用程序能请求的状态进行限制。
4.7.2.2.3
系统电源状态示例
下面的注册表片段是表示系统电源状态到设备电源状态映射的示例。
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/On]
"Default"=dword:0 ; D0
"Flags"=dword:10000 ; POWER_STATE_ON
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/UserIdle]
"Default"=dword:1 ; D1
"Flags"=dword:0
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/SystemIdle]
"Default"=dword:2 ; D2
"Flags"=dword:0
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/Suspend]
"Default"=dword:3 ; D3
"Flags"=dword:200000 ; POWER_STATE_SUSPEND
; @CESYSGEN IF CE_MODULES_NDIS
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/State/Suspend/{98C5250D-C29A-4985-AE5F-AFE5367E5006}]
"Default"=dword:4 ; D4
; @CESYSGEN ENDIF CE_MODULES_NDIS
下表描述了上面注册表片段的映射方式。
系统电源状态
设备电源状态
On
D0
UserIdle
D1
SystemIdle
D2
Suspend
D3。NDIS迷你接口单独被映射为D4。
当系统使用上面的注册表配置,并进入Suspend状态,除了NDIS迷你接口会被关闭外,其他可唤醒源都将处于可激活状态。如果有设备不支持D3状态,它将自动进入D4状态。
应用程序可以使用SetPowerRequirement创建设备电源要求。也可以使用电源管理器控制面板程序创建附加的设备电源要求。
4.7.2.3
设备及系统电源状态名称
电源管理器要求使用小写字母命名设备及系统电源状态名称。一些类似于wsprintf(buf,
"%u", n)
或不区分大小写的比较这样的操作,会涉及区位表(Locale table)查询。区位表(Locale
table)在Wince.nls内存映射文件中被实现。在挂起期间,电源管理器不能使用FileSystemPowerFunction访问文件系统。从挂起的线程中访问文件系统可能造成操作系统死锁。如果Wince.nls中必须的页面无法在内存中找到,就会产生死锁。
在处理API调用时,电源管理器将名称转换为小写字母。然而,在挂起及恢复状态时,电源管理器在文件系统操作被禁止后访问注册表。这时不能将注册表设置项转换为小写。所以必须将系统电源状态注册表设置改为小写字母。例如:WAV1:应被描述为wav1:。其他类似于注册表HKLM/Drivers/Builtin下的用于控制设备驱动加载的设置项不用修改。
Windows CE设备驱动开发之电源管理 第三部分
4.7.3、电源管理器接口
电源管理器有三种不同的使用者。
l
电源管理器能识别的设备的驱动程序。
l
可能需要更改系统电源状态或设备性能的应用程序。
l
需要电源事件通知的应用程序。
电源管理器使用不同的编程接口与这些使用者进行通讯。
4.7.3.1
设备驱动接口
电源管理器使用两种不同的机制与支持电源管理的设备驱动进行通讯。电源管理器向下调用设备驱动确定其设备性能,并更新其设备电源状态。设备向上调用电源管理器请求进行设备电源状态更改。向下调用在系统中以IOCTL方式实现。设备通过DevicePowerNotify
API函数向上调用电源管理器。
由于电源管理器使用DeviceIoControl与支持电源管理的设备进行通讯,所以设备需要实现一个外部(expose)流接口。在某些情况下,可以用电源管理代理来实现外部流接口。网络驱动接口规约(NDIS)中实现了一个外部流接口,从而能使用RegisterPowerRelationship
API函数进行对NDIS迷你接口的代理管理。电源管理器同时提供了一种与非流接口设备的通讯机制。这一方法由具有打开设备句柄、发送请求等功能的抽象层组成。例如:位于Public/Common/Oak/Drivers/Pm/Mdd/Pmdisplay.cpp的驱动程序实现了一个基于ExtEscape函数的通讯接口。
打开名称格式为COM1:、并实现了外部流接口的标准设备,可以对其进行读写。但是,电源管理器并不要求支持电源管理的设备必须使用这种命名格式;设备名称可以是任意唯一的字符串。例如:NDIS迷你接口可以被命名为VMINI1。
尽管Platform Builder提供的电源管理器仅支持流接口驱动,但OEM可以自由的实现其他的设备接口。可以为设备定义一个新的唯一类型全局标识符(GUID)来实现新的接口。不过,驱动程序在使用新接口时必须遵循标准电源管理设备驱动准则。
默认情况下,电源管理器可以发现下列GUID表示的设备类型:
l
{A32942B7-920C-486b-B0E6-92A702A99B35}
此GUID类型为普通被管理设备。
l
{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}
此GUID类型为块设备。
l
{98C5250D-C29A-4985-AE5F-AFE5367E5006}
此GUID类型为NDIS设备。
应用程序可以从HKEY_LOCAL_MACHINE/System/CurrentControlSet/Control/Power /Interfaces注册表键中获取可被管理的设备类型的列表。
当应用程序调用一个引用了指定设备的电源管理器API时,应用程序必须指定设备的GUID类型名。如果没有指定设备的GUID类型名,电源管理器会假定此设备为普通被管理设备类型。
有效的设备类型名需要有GUID前缀及反斜杠符号。例如:GUID
{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}/DSK1:表示引用一个名为DSK1的块设备。
电源管理器从注册表读取设备类型列表,使用RequestPowerNotifications决定此类型的设备在什么时候被载入。下面时设备类型列表的示例。
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Power/Interfaces]
"{A3292B7-920C-486b-B0E6-92A702A99B35}"="Generic power-manageable devices"
"{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}"="Power-manageable block devices"
"{98C5250D-C29A-4985-AE5F-AFE5367E5006}"="Power-manageable NDIS miniports"
Platform Builder附带的电源管理器以上述设置执行。如果不定义新的设备注册表键,在注册表中可以不包含上述设置。下面步骤说明了实现一个表示使用ExtEscape直接管理的显示驱动类型的过程。
1.
使用Guidgen.exe创建一个新的GUID类型,并将其添加到注册表中。
2.
为了识别GUID,需要修改电源管理器,并使用ExtEscape与设备通讯。
3.
调用AdvertiseInterface,使用新的GUID修改显示驱动。
4.7.3.1.1
IOCTL设备控制符
电源管理器使用下列IOCTL码与设备通讯:
IOCTL
功能
IOCTL_POWER_CAPABILITIES
要求设备通知电源管理器它所支持的电源状态及其特性。
IOCTL_POWER_SET
要求设备更新其设备电源状态。
IOCTL_POWER_QUERY
询问设备是否准备好可以进入另外一个设备电源状态。