Power Management

本文對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();
                  }
            }
       }
}

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/ymzhou117/archive/2009/11/30/4909380.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章