Power Management
From:http://blog.csdn.net/Larry_Liang_cn/archive/2008/12/30/3645218.aspx
本文对Power Management这部分代码的研究是基于Wince5.0的(注:在最新的Wince 6.0上对电源管理的架构做了较大改变)。
这部分的代码在\PUBLIC\COMMON\OAK\DRIVERS\PM下,在OS中以PM.dll的形式存在。
一、PowerManagement Architecture
在\PUBLIC\COMMON\OAK\DRIVERS\PM下的代码有两套电源管理机制:
一种是Minimal的电源管理架构,在\PUBLIC\COMMON\OAK\DRIVERS\PM\PMSTUBS\下,用SYSGEN_PMSTUBS环境变量去使能这个架构;在这种架构下只支持消息接口类的电源管理API即PmRequestPowerNotifications和PmStopPowerNotifications,且PmSetSystemPowerState里面只做了Suspended/Resuming的简单处理。
另一种是Full的电源管理架构,在\PUBLIC\COMMON\OAK\DRIVERS\PM\下的MDD和PDD目录,用SYSGEN_PM环境变量去使能这个架构;在这种架构下支持所有类型的电源管理API。在Full的电源管理架构中,又分两个子类DEFAULT和PDA。在用SYSGEN_PM环境变量启用这个架构后默认使用DEFAULT,用SYSGEN_PM_PDA可以使能PDA这个子类。
在Windows Mobile 6(wince 5.0核心)中,无论是Windows Mobile 6 Professional/ Windows Mobile 6 Classic (在\PUBLIC\WPC\OAK\MISC\wpc.bat)还是Windows Mobile 6 Standard(在\PUBLIC\SMARTFON\OAK\MISC\smartfon.bat)都设置了SYSGEN_PM = 1,SYSGEN_PM_PDA = 1;
下面我将详细讨论PDA子类。
系统电源状态共有九种:
On,UserIdle,BacklightOff,ScreenOff,Unattended,Resuming,Suspended,ColdReboot,Reboot.
ColdReboot,Reboot这两个状态不能在电源管理的状态机中自由迁移,它们只能通过调用SetSystemPowerState来进入。
此外,Windows Mobile 6 Professional和Windows Mobile 6 Classic (即PPC)在\PUBLIC\WPC\OAK\FILES\下的project.reg里面做了如下设置
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
"ACUserIdle"=dword:0 ; in seconds
"BattUserIdle"=dword:0 ; in seconds
"BattSuspendTimeout"=dword:3c ; in seconds
这样就把自动超时进入UserIdle状态的方式给屏蔽了,无法在PPC里面自动超时迁移到UserIdle这个电源状态。
Windows Mobile 6 Standard(即SmartPhone)在\PUBLIC\SMARTFON\OAK\FILES\下的project.reg里面做了如下设置
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
"ACSuspendTimeout"=dword:0 ; in seconds
"BattSuspendTimeout"=dword:0 ; in seconds
这样就把自动超时进入Suspended状态的方式给屏蔽了,无法在SmartPhone里面自动超时迁移到Suspended这个电源状态。
根据文档:Windows Mobile 6 Professional和Windows Mobile 6 Classic (即PPC) 不支持UserIdle这个状态,Windows Mobile 6 Standard(即SmartPhone)不支持Unattended,Resuming,Suspended三个状态。
影响系统电源状态变迁的事件共有以下十一种:
Wake source event:
这是OEM自己定义的唤醒事件,它们都是特定选择的一些硬件中断(例如USB线或是充电器的插入拔出中断,Baseband的中断,键盘中断,闹钟中断等)。在PPC中这些唤醒事件用来把系统从Suspended状态变到Resuming状态。在SmartPhone中没有Suspended状态,相应的低功耗模式是进入OEMIdle()。在有些特定硬件平台中,为了降低功耗往往在OEMIdle()里将CPU配置进低功耗模式或者Stop掉。例如PXA27X,PXA3XX的SmartPhone平台下,在OEMIdle()里让CPU进Standby模式,把一些硬件中断配置为Wake source event以唤醒CPU并退出OEMIdle()。在PPC里进入Suspended状态或是关机都会调用OEMPowerOff(),但在SmartPhone里OEMPowerOff()只在关机时被调用。
On/off event:
Power Button被按下。这个事件只在PPC里有效。
在Resuming,Unattended,ScreenOff状态下,按下Power Button将系统迁移到On状态。在On,BacklightOff,UserIdle状态下,按下Power Button将系统迁移到Unattended状态。
On event:
Application Buttons被按下。这个事件也只在PPC里有效。
在On,BacklightOff,UserIdle,Resuming,Unattended,ScreenOff状态下,按下Application Buttons都会将系统迁移到On状态。
Enter unattended:
调用PowerPolicyNotify(PPN_UNATTENDEDMODE,TRUE)让系统进入unattended模式。这时候m_dwUnattendedModeRef加一。
Leave unattended:
调用PowerPolicyNotify(PPN_UNATTENDEDMODE,FALSE)让系统离开unattended模式。这时候m_dwUnattendedModeRef加一。
任何状态要迁移到Suspended状态肯定会先迁移unattended状态。如果m_dwUnattendedModeRef为0则直接进入Suspended状态。不为0时会将SystemIdleTimeout定时器复位,在SystemIdle Timeout后才会进入Suspended状态。
下面接下来是四个Timeout事件:
15-second timeout(即ResumingSuspendTimeout):
15S超时其实是代码中的ResumingSuspendTimeout,这是专门用于Resuming状态的。
当系统从Suspended唤醒到Resuming状态时,如果15s内没有人系统迁移出这状态,则系统电源状态就会变到Unattended状态。
这个15s的时间是系统默认的(在PUBLIC\COMMON\OAK\DRIVERS\PM\PDD\PDA\下的pwstates.h定义的),但它也是可以被改变的。
修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("Batt
ResumingSuspendTimeout")的值可以改变没有使用外部电源时的超时时间。修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("ACResumingSuspendTimeout")的值可以改变使用外部电源时的超时时间。
SystemIdle Timeout(即SuspendTimeout):
系统自动进入Suspended状态的超时时间,用户可以通过控制面板去改变它。在On,BacklightOff,UserIdle,ScreenOff状态下SystemIdle Timeout会让系统进入Unattended状态,在Unattended状态下SystemIdle Timeout会让系统进入Suspended状态。
这个SystemIdle Timeout时间也是可以通过修改注册表去改变的。这个时间分两个,在不使用外部电源时是m_dwBattSuspendTimeout,使用外部电源时是m_dwACSuspendTimeout。
从代码上看,m_dwBattSuspendTimeout的默认值是300s,m_dwACSuspendTimeout的默认值是600s.然后在\PUBLIC\WPC\OAK\FILES\下(适用于PPC)或是\PUBLIC\SMARTFON\OAK\FILES\(适用于SmartPhone)的project.reg里对此又作了设置修改。修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattSuspendTimeout")的值可以改变m_dwBattSuspendTimeout。修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("ACSuspendTimeout")的值可以改变m_dwACSuspendTimeout。
Backlight Timeout:
系统自动进入BacklightOff状态的超时时间。在On状态下Backlight Timeout会让系统进入BacklightOff状态,在BacklightOff,UserIdle,ScreenOff,Unattended状态下会屏蔽掉Backlight Timeout。
同样这个时间分两个:在不使用外部电源时是m_dwBattBacklightTimeout,默认是30s,使用外部电源时是m_dwACBacklightTimeout,默认是60s。用户同样可以通过控制面板去改变它。
这个Backlight Timeout时间也是可以通过修改注册表去改变的。
修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattBacklightTimeout")的值和TEXT("HKEY_CURRENT_USER\\ControlPanel\\Backlight")下面的_T("BatteryTimeout")的值中的任何一个都可以改变m_dwBattBacklightTimeout。
修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("ACBacklightTimeout")的值和TEXT("HKEY_CURRENT_USER\\ControlPanel\\Backlight")下面的_T("ACTimeout")的值中的任何一个都可以改变m_dwACBacklightTimeout。
注意的是:为了避免冲突,我们修改Backlight Timeout时,上面的两组注册表值最好只用一组,就是要么用TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattBacklightTimeout")和_T("ACBacklightTimeout")要么就用TEXT("HKEY_CURRENT_USER\\ControlPanel\\Backlight")下面的_T("BatteryTimeout")和_T("ACTimeout")。
如果两组同时用的话,根据pwstates.cpp文件里PowerStateManager::PlatformLoadTimeouts里的代码(先读取TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattBacklightTimeout")和_T("ACBacklightTimeout")后读取TEXT("HKEY_CURRENT_USER\\ControlPanel\\Backlight")下面的_T("BatteryTimeout")和_T("ACTimeout")),会以后一组的注册表值为实际值。
User Timeout:
这个超时机制只在Windows Mobile 6 Standard设备中用。在Windows Mobile 6 Professional 和Windows Mobile 6 Classic 设备没有UserIdle这个状态,也就没有使用这个超时机制。
在On,BacklightOff状态下UserTimeout会让系统进入UserIdle状态,在UserIdle,ScreenOff,Unattended状态下会屏蔽掉User Timeout。
在Windows Mobile 6 Standard设备中,这个时间也分两个m_dwBattUserIdleTimeout和m_dwACUserIdleTimeout。
修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattUserIdle")的值和TEXT("HKEY_CURRENT_USER\\ControlPanel\\Power")下面的_T("Display")的值中的任何一个都可以改变m_dwBattUserIdleTimeout。
修改TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("ACUserIdle")的值和TEXT("HKEY_CURRENT_USER\\ControlPanel\\Power")下面的_T("Display")的值中的任何一个都可以改变m_dwACUserIdleTimeout。
和Backlight Timeout一样,不要同时使用TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\Timeouts")下面的_T("BattUserIdle")和_T("ACUserIdle")与TEXT("HKEY_CURRENT_USER\\ControlPanel\\Power")下面的_T("Display")这两组值,否则会以后一组的注册表值为实际值。
User Active事件:
当有任何用户操作时激活此事件。
在BacklightOff,UserIdle状态下User Active事件会让系统进入On状态,在On,ScreenOff,Unattended状态下对User Active事件不做处理。
在HKEY_LOCAL_MACHINE\System\GWE\ActivityEvent下的键值就是当GWES发现有任何用户操作时要发送的事件--PowerManager/ActivityTimer/UserActivity。在pmtimer.cpp文件里的ActivityTimersThreadProc()函数里根据在超时时间内是否有PowerManager/ActivityTimer/UserActivity事件判断当前是UserActivity状态还是UserInactivity状态,并且分别再发PowerManager/UserActivity_Active和PowerManager/UserActivity_Inactive事件。
对应的ActivityTimers的注册表在TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\ActivityTimers")下。在PPC里这项注册表下只有_T("UserActivity")这个子项,这个子项下只有_T("TimeoutMs")=100值。也就是说从UserActivity状态转换到UserInactivity状态的超时时间是100ms,这样实际上PM模块就会忽略掉这里的PowerManager/UserActivity_Inactive事件,只会在GWE发PowerManager/ActivityTimer/UserActivity事件时通知PM当前是User Active事件。
SystemIdleTimerReset:
复位SystemIdleTimer,调用SystemIdleTimerReset()函数会发送_T("PowerManager/SystemIdleTimerReset")事件。此时PM在pwstates.cpp文件的PowerState::WaitForEvent()函数里会调用m_pPwrStateMgr->ResetSystemIdleTimeTimeout(TRUE);platEvent = SystemActivity;这样将m_dwCurSuspendTimeout复位并且platEvent = SystemActivity时系统电源状态不做迁移。
Power Manager API(SetSystemPowerState):
这里的PowerManager API特指能直接改变系统电源状态的SetSystemPowerState。
SetSystemPowerState的调用路径是PmSetSystemPowerState->PlatformSendSystemPowerState->g_pPowerStateManager->SendSystemPowerState.
先判断要设置的系统电源状态是否可以被SetSystemPowerState直接设置(调用AppsCanRequestState),其中On,UserIdle,Suspended,ScreenOff,ColdReboot,Reboot可以被设置,BacklightOff,Resuming,Unattended不可以被设置。其次依次调用pNewPowerState->EnterState();pNewPowerState = SetSystemState(pNewPowerState );m_pCurPowerState = pNewPowerState;SetEvent(m_hSystemApiCalled);将函数传入的状态设置为当前系统电源状态。
系统电源状态变迁示意图
1. Backlightoff Timeout
2. User Activity or AppButtonPressed
3. UserIdle Timeout
4. User Activity or AppButtonPressed
5. PowerButtonPressed or Suspend Timeout
6. PowerButtonPressed or AppButtonPressed
7. UserIdle Timeout
8. PowerButtonPressed or Suspend Timeout
9. PowerButtonPressed or Suspend Timeout
10. UnattendedRefCount == 0 or SuspendTimeout
11. Wakeup from Suspended status
12. ResumingSuspendTimeout
13. PowerButtonPressed or AppButtonPressed
14. Suspend Timeout
15. PowerButtonPressed or AppButtonPressed
SetSystemPowerState()
16. SetSystemPowerState(NULL,POWER_STATE_ON,POWER_FORCE) or SetSystemPowerState(L"on",0,0);
17. SetSystemPowerState(NULL,POWER_STATE_USERIDLE,POWER_FORCE) or SetSystemPowerState(L"useridle",0,0);
18. SetSystemPowerState (NULL,POWER_STATE_RESET,POWER_FORCE) or SetSystemPowerState(L"reboot",0,0);
19. SetSystemPowerState (NULL,POWER_STATE_SUSPEND,POWER_FORCE) or SetSystemPowerState(NULL,POWER_STATE_OFF,POWER_FORCE) or SetSystemPowerState(NULL,POWER_STATE_CRITICAL,POWER_FORCE) or SetSystemPowerState(L"suspend",0,0);
20. SetSystemPowerState(NULL,POWER_STATE_IDLE,POWER_FORCE) or SetSystemPowerState(L"screenoff",0,0);
21. SetSystemPowerState(L"coldreboot",POWER_STATE_RESET,POWER_FORCE)
注意:
1.Windows Mobile 6 Professional和Windows Mobile 6 Classic(即PocketPC)下,没有UserIdle状态,3,4,7,8,9,17这几个迁移线也就不存在。
2.Windows Mobile 6 Standard(即SmartPhone)下,没有Unattended,Resuming,Suspended三个状态,5,6,8,9,10,11,12,13,14这几个迁移线也不存在。
二、Power Management's Functions
我们可以在PM.def里面看到以下14个函数
系统电源管理相关:
PmSetSystemPowerState ------ 设置系统电源状态
PmGetSystemPowerState ------ 得到系统电源状态
设备电源管理相关:
PmDevicePowerNotify ------ 要求设备电源状态,设置pds->lastReqDx
PmSetDevicePower ------ 设置设备电源状态,设置pds->setDx
PmGetDevicePower ------ 得到设备电源状态
PmSetPowerRequirement ------ 设置设备电源需求,设置设备电源的最小值
PmReleasePowerRequirement ------ 释放设备电源需求,释放设备电源的最小值
PmRegisterPowerRelationship ------ 当总线驱动或者代理电源管理器要截获驱动与电源管理模块之间的IOCTL命令字时,应当调用此函数。也就是说,该设备的电源管理被代理了。
PmReleasePowerRelationship ------ 结束前一函数设置的电源管理代理关系
消息通知:
PmRequestPowerNotifications ------ 注册电源管理的通知事件
PmStopPowerNotifications ------ 撤消拦截来自电源管理模块的电源管理回调消息
其它:
PmInit ------ PM.dll的初始化函数
PmNotify ------ DLL_PROCESS_DETACH时调用以释放资源
PmPowerHandler ------ Resuming时的处理
下面我们就这些函数一个个的注释讲解:
三、PmSetSystemPowerState
当我们调用SetSystemPowerState最终会调用到PM.dll里的PmSetSystemPowerState函数。
系统电源状态会决定所有power-manageable设备驱动的最大的电源级别。
当然,这个函数也不是什么状态都能设置的。如前面所说的BacklightOff ,Resuming,Unattended这三个状态是不可以被设置的。
PmSetSystemPowerState(LPCWSTR pwsState, DWORD dwStateHint, DWORD dwOptions)
psState ------ 指向包含要设置的系统电源状态名字的字符串
StateFlags ------ 这个参数是可选的,如果psState=NULL则要设置的系统电源状态由StateFlags 决定。
Options ------ 这个POWER_FORCE标志位表示状态转换是紧急的。
下面是PmSetSystemPowerState函数的详细内容
PmSetSystemPowerState
{
PlatformSendSystemPowerState
{
SendSystemPowerState
{
PlatformMapPowerStateHint; //将StateFlags转化成电源状态名
{
对应关系如下:
POWER_STATE_ON -- _T("on");
POWER_STATE_IDLE -- _T("screenoff");
POWER_STATE_SUSPEND -- _T("suspend");
POWER_STATE_OFF -- _T("suspend");
POWER_STATE_CRITICAL -- _T("suspend");
POWER_STATE_RESET -- _T("reboot");
POWER_STATE_USERIDLE -- _T("useridle");
}
//下面两个函数由要设置的电源状态名得到相应的对象指针
SystemStateToActivityState
GetStateObject
AppsCanRequestState //判断要设置的电源状态是否可以被设置
if((dwOptions & POWER_DUMPDW)!=0)
{
//Options 参数带POWER_DUMPDW 时会产生Dr. Watson dump file.
CaptureDumpFileOnDevice;
}
pNewPowerState->EnterState();//这里是实质的设置函数
//将电源状态的状态机转至设置的系统电源状态
pNewPowerState = SetSystemState(pNewPowerState);
m_pCurPowerState = pNewPowerState;
//将ResumingSuspendTimeout,SuspendTimeout,BacklightTimeout,UserIdleTimeout这几个定时器复位
ReInitTimeOuts(FALSE);
//通知PowerStateManager::ThreadRun,这里没有做实质性内容
SetEvent(m_hSystemApiCalled);
}
}
}
下面是pNewPowerState->EnterState的具体内容
pNewPowerState->EnterState
{
PmSetSystemPowerState_I(GetStateString(),0 ,0, TRUE);
{
if (((!_tcsicmp(szStateName,_T("suspend"))) || (dwStateHint==POWER_STATE_OFF)) &&(fInternal==TRUE))
{
//将用户关闭系统的消息写入周日志里面
PMSQM_Set(PMSQM_DATAID_POWER_USER_SHUTDOWNS,1);
}
PlatformSetSystemPowerState
{
/*将TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\State\\$(SystemPowerState的Name)")下的_T("Default")值赋值给psps->defaultCeilingDx(在当前系统电源状态下的所有设备的默认的最大电源级别);_T("Flags")值赋值给psps->dwFlags;
TEXT("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Power\\State\\$(SystemPowerState的Name)\\$(Device的Name)")下的子键的名字和由它转化成的GUID赋值给pdpr->pDeviceId,子键的值赋值给pdpr->devDx
将{A32942B7-920C-486b-B0E6-92A702A99B35} (这个GUID表征generic power-managed devices)这个GUID赋值给pdpr->pDeviceId子键下的值赋值给pdpr->devDx(这是当前系统电源状态下的某一个或某一类设备的特定的最大电源级别)
*/
PmUpdateSystemPowerStatesIfChanged
RegReadSystemPowerState
//将PBT_TRANSITION消息发送给所有注册了相应电源管理通知事件的驱动
pbb.Message = PBT_TRANSITION;
pbb.Flags = pNewSystemPowerState->dwFlags;
pbb.Length = _tcslen(pNewSystemPowerState->pszName) + 1;
_tcsncpy(pbb.SystemPowerState,pNewSystemPowerState->pszName, pbb.Length);
pbb.Length *= sizeof(pbb.SystemPowerState[0]);
GenerateNotifications((PPOWER_BROADCAST) &pbb);
//更新所有Classes的设备的电源状态
UpdateAllDeviceStates();
{
//从gpDeviceLists中遍历各个Device interface classes
for(pdl = gpDeviceLists; pdl != NULL; pdl = pdl->pNext)
{
UpdateClassDeviceStates(pdl);
{
pds = pdl->pList; //pdl中再遍历各个Device
while(!fDeviceRemoved && pds != NULL)
{
UpdateDeviceState(pds)
pdsNext = pds->pNext;
}
}
}
}
//下面就SUSPEND和Resuming这两种特殊情况分别讨论
//1.Suspended
if((dwNewStateFlags &(POWER_STATE_SUSPEND|POWER_STATE_OFF|
POWER_STATE_CRITICAL|POWER_STATE_RESET)) != 0)
fSuspendSystem = TRUE;
GwesPowerDown() //关掉GWES
//关闭除了idBlockDevices Class的设备以外的所有设备电源状态。因为这个时候还要访问注册表,写文件。所以与此相关的idBlockDevices 的电源状态还应暂时不变.
for(pdl = gpDeviceLists;pdl != NULL;pdl = pdl->pNext)
{
if(*pdl->pGuid != idBlockDevices)
UpdateClassDeviceStates(pdl);
}
//这里会调用IOCTL_HAL_PRESUSPEND,在进入Suspended之前给OEM一个机会去设置一些东西。微软建议OEM实现IOCTL_HAL_PRESUSPEND时清掉唤醒标示。并且强制来一次线程调度
KernelIoControl(IOCTL_HAL_PRESUSPEND,NULL,0,NULL,0, NULL);
iCurrentPriority = CeGetThreadPriority(GetCurrentThread());
CeSetThreadPriority(GetCurrentThread(),giPreSuspendPriority);
Sleep(0);
CeSetThreadPriority(GetCurrentThread(),iCurrentPriority);
//如果[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power]下有L"PageOutAllModules"则
if(gfPageOutAllModules)
PageOutModule(GetCurrentProcess(),PAGE_OUT_ALL_DEPENDENT_DLL);
//这个用来找出哪个驱动错误的使用了pageable机制
FileSystemPowerFunction(FSNOTIFY_POWER_OFF); //关闭文件系统
//关闭block device的电源
pdl = GetDeviceListFromClass(&idBlockDevices);
UpdateClassDeviceStates(pdl);
//下面是重启的情况
if((dwNewStateFlags & POWER_STATE_RESET) != 0)
{
//如果是冷启动则CleanReBoot
if(_tcscmp(pszName, _T("coldreboot")) == 0)
SetCleanRebootFlag();
//调用OEM去实现的IOCTL_HAL_REBOOT,这个函数不应该返回。
KernelLibIoControl((HANDLE)KMOD_OAL,IOCTL_HAL_REBOOT,NULL,0,NULL,0,NULL);
}
//设置标志位并调PowerOffSystem,这个函数最终会调到OEMPowerOff()
gfSystemSuspended = TRUE;
PowerOffSystem();
Sleep(0); //CPU唤醒后强制来此系统调度
gfSystemSuspended = FALSE; // clear the suspend flag
gfPasswordOn = 0;
//2. Resuming
//打开block device的电源
pdl = GetDeviceListFromClass(&idBlockDevices);
UpdateClassDeviceStates(pdl);
//打开文件系统
FileSystemPowerFunction(FSNOTIFY_POWER_ON);
//打开除了idBlockDevices Class的设备以外的所有设备电源状态
for(pdl = gpDeviceLists; pdl != NULL; pdl = pdl->pNext)
{
if(*pdl->pGuid != idBlockDevices)
UpdateClassDeviceStates(pdl);
}
gpfnGwesPowerUp(fWantStartupScreen); //唤醒GWES
//将PBT_RESUME消息发送给所有注册了相应电源管理通知事件的驱动
pbb.Message = PBT_RESUME;
pbb.Flags = 0;
pbb.Length = 0;
pbb.SystemPowerState[0] = 0;
GenerateNotifications((PPOWER_BROADCAST) &pbb);
}
}
m_LastNewState = GetState();//更新电源状态标志
//激活UserActivity事件
m_dwEventArray[PM_USER_ACTIVITY_EVENT] = m_pPwrStateMgr->GetUserActivityTimer()->hevActive;
//重新校正BacklightTimeout,SuspendTimeout,UserIdleTimeout这三个超时器
m_pPwrStateMgr->ReAdjustTimeOuts();
}
下面在对UpdateDeviceState(pds)做具体说明
UpdateDeviceState
{
//获取当前设备的电源级别最大值(ceilingDx)和最小值(floorDx)
GetNewDeviceStateInfo
{
1.对于newCeilingDx
//首先每个系统电源状态都对应一个默认的所有设备的最大电源级别
newCeilingDx = psps->defaultCeilingDx;
//在链表gpCeilingDx里查找特定的一类设备的最大电源级别
devId.pGuid = pds->pListHead->pGuid;
devId.pszName = NULL;
if((pdpr = PowerRestrictionFindList(pCeilingDxList,&devId,NULL))!= NULL)
{
newCeilingDx = pdpr->devDx;
}
//在链表gpCeilingDx里寻找特定的某个设备的最大电源级别
devId.pszName = pds->pszName;
if((pdpr = PowerRestrictionFindList(pCeilingDxList,&devId,NULL)) != NULL)
{
newCeilingDx = pdpr->devDx;
}
2.对于newFloorDx
newFloorDx = D4;//先设置为最小电源级别D4
//在链表gpFloorDx 里去查找某一类设备的最小电源级别
devId.pszName = NULL;
pdpr = pFloorDxList;
while((pdpr = PowerRestrictionFindList(pdpr,&devId,NULL))!= NULL)
{
if(pdpr->devDx < newFloorDx) != 0))
newFloorDx = pdpr->devDx;
pdpr = pdpr->pNext;
}
//在链表gpFloorDx里寻找在特定系统电源状态下的某一类设备的最小电源级别
devId.pszName = NULL;
pdpr = pFloorDxList;
while((pdpr = PowerRestrictionFindList(pdpr,&devId,psps->pszName))!= NULL)
{
if(pdpr->devDx < newFloorDx)
newFloorDx = pdpr->devDx;
pdpr = pdpr->pNext;
}
//在链表gpFloorDx 里去查找某个设备的最小电源级别
devId.pszName = pds->pszName;
pdpr = pFloorDxList;
while((pdpr = PowerRestrictionFindList(pdpr,&devId,NULL))!= NULL)
{
if(pdpr->devDx < newFloorDx)
newFloorDx = pdpr->devDx;
pdpr = pdpr->pNext;
}
//在链表gpFloorDx里寻找在特定系统电源状态下的某个设备的最小电源级别
devId.pszName = pds->pszName;
pdpr = pFloorDxList;
while((pdpr = PowerRestrictionFindList(pdpr,&devId,psps->pszName)) != NULL)
{
if(pdpr->devDx < newFloorDx)
newFloorDx = pdpr->devDx;
pdpr = pdpr->pNext;
}
}
//决定设备电源级别最终是什么
GetNewDeviceDx
{
//如果setDx不是PwrDeviceUnspecified,则设备的最终电源级别就等于setDx。
if(setDx != PwrDeviceUnspecified)
{
newDx = setDx;
}
else{
//设备的最终电源级别由reqDx 来确定,但最终电源级别必须在最小值(floorDx)和最大值(ceilingDx)之间
newDx = reqDx;
if(newDx < ceilingDx)
newDx = ceilingDx;
if(floorDx < newDx)
newDx = floorDx;
}
//如果电源级别没有改变或是无效
if(curDx == newDx || ! VALID_DX(newDx))
{
newDx = PwrDeviceUnspecified;
}
}
//调用各个驱动的IOCTL_POWER_SET去设置电源级别
SetDevicePower
{
//根据设备支持电源级别的情况来重影射一下电源级别
reqDx = MapDevicePowerState(newDx,pds->caps.DeviceDx);
//如果要设置的电源级别与当前级别不一样就设置
if(reqDx != pds->actualDx || pds->dwNumPending != 0 || fForceSet)
fDoSet = TRUE;
//打开设备驱动并调用驱动的IOCTL_POWER_SET
hDevice = pds->pInterface->pfnOpenDevice(pds);
pds->pInterface->pfnRequestDevice(hDevice,IOCTL_POWER_SET,ppr,ppr == NULL?0:sizeof(*ppr),&reqDx,sizeof(reqDx),&dwBytesReturned);
//如果Backlight 在D0和非D0状态下转换
if(fOnToOther||fOtherToOn)
{
if(!memcmp(pds->pszName,TEXT("bkl1"),sizeof(TCHAR)*4))
{
gBacklightMs = GetTickCount()-gBacklightMs;
if(fOnToOther)
{
//在日志里记录Backlight 开启的时间
gBacklightMsTotal += gBacklightMs;
PMSQM_Set(PMSQM_DATAID_POWER_BKL_TOTAL,gBacklightMsTotal);
PMSQM_Set(PMSQM_DATAID_POWER_BKL_ON,gBacklightMs);
}
else if(fOtherToOn) {
//在日志里记录Backlight 关闭的时间
PMSQM_Set(PMSQM_DATAID_POWER_BKL_OFF,gBacklightMs);
}
gBacklightMs = GetTickCount();
}
}
}
}