|
Introduction
|
|
Windows CE provides flexible power management driver for OEM to customize each peripherals in devices to switch between various device power states for each system power states. In addition to that it is extendable too. It provides way for OEM to add their own power states based on the customer requirement. This document explains the various power states and tips to customizing the power manager driver for our requirements. |
|
Scope
|
|
This article won't deal with the theoretical part of the power management instead this will help engineers who are all facing the practical issues and challenges in understanding the power manager and the power states. This article will not explore the basic power management. To know more about the power management, please have a look on the MSDN. All explanations are based on the source code that will be explored here. |
|
Overview
|
|
This document explores the Windows CE power manager and power states deeply with the source code given by the Microsoft. These source codes are customizable and even new states can be added depends on the requirements. |
|
Power Manager
|
|
Pm.dll is the power manager driver contains all the power manager code including the API sets. The source code of the power manager is in the \WINCE600\public\common\OAK\drivers\PM directory. To customize the PM, it has to be cloned to the BSP using sysgen capture tool. Also you can view the blog for the usage of sysgen capture tool. |
|
Power Manager Architecture
|
|
Figure A explains the various power manager blocks. Starting from the power manager initialization, each blocks usage has been explained below. |
|
|
|
Figure A - Power Manager Driver Architecture
|
|
Power Management Initialization
|
|
Power management initialization process has been done in the pminit() function of pminit.cpp file.
|
Steps involved in the power management initialization: |
1.
|
Create and read the list of interface types (power manager device classes) that has to be monitored by power manager. Power manager device classes are explained below sections. |
2.
|
Start the PnPThreadProc, ResumeThreadProc, ActivityTimersThreadProc and SystemThread Proc. These four threads are controlling the entire power management system and thread functionalities are explained in the upcoming sections. |
3.
|
Set the system state to power ON. |
|
|
Plug and Play Device Management
|
|
Plug and Play device management plays the role of maintaining the list of devices enrolled under the power management device classes. This process is done by the PnPThreadProc thread. This thread waits for the notification sent from the device driver belongs to the power management device classes, add or remove the devices from the device list maintained by the power manager..
|
|
Power Management device classes
|
|
Power manager maintaining the device in the form of device classes and each device class is having the unique GUID. There are four types of power management device classes currently supported by the Windows CE 6.0.
-
- Generic Devices:
- All streaming devices are come under this class. (Ex) serial port driver (com1)
-
- NDIS Miniport Devices:
- All Network devices are come under this class. (Ex) Ethernet.
-
- Block Devices:
- Block devices like PCMCIA CF cards are example for this class.
-
- Display Devices:
- Display devices, overlay devices are come under this class.
|
|
The devices drivers whoever need the power management facility have to register themselves while driver initialization. This registration process has done through device notifications. While registration all devices registered under the above mentioned classes. |
|
Resume Power Management
|
|
System suspend/resume is not the same as a system power state transition. System power states transitions may or may not involve a system suspend/ resume. The automatic system power state transitions through the power state can be avoided through the registry settings. |
|
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
|
"ACSuspend"=dword:0 ; in seconds
|
|
Setting the “ACSuspend = 0” avoid the suspend state automatically through the power state transitions when the system Idle timeout is happened. A system suspend/resume event occurs when PowerOffSystem() is called, device drivers are notified via PowerUp()/PowerDown() entry points, and OEMPowerOff() is invoked. However this PowerOffSystem() and SetSystemPowerOff() are done through the “DefaultPowerStateManager”. This SetSystemPowerOff() will call “PmSetSystemPowerState_I()” function, this will call “DefaultPowerStateManager” functions to maintain the control. So this APIs won’t create chaos in the power state machine. “DefaultPowerStateManager” will be explained in the upcoming sections.
During the resume process "ResumeThreadProc "is responsible for determining why the system woke up and initiating the appropriate system power state transition and restart the activity timers.
|
|
System Power State Management
|
|
System power state management is the main operation in the power management driver. This is the state machine and this is responsible for switching from states to states. "SystemThreadProc" is the thread invoked by the power manager driver during the initialization. This thread initializes the "DefaultPowerStateManager" which is responsible for maintaining the power states. "DefaultPowerManager" and "PowerState" are the two classes play the vital role of handling the power manager. |
|
DefaultPowerManager Class
|
|
During the DefaultPowerManager initialization, it will create the list of power states maintained by the state machine. See the createPowerStatelist() function in the pwstates.cpp. This function adds all PowerState derived classes in the list maintained by the "DefaultPowerManager" class. |
|
PowerState Class
|
|
This is the base class of the power states. All the power state that can be implemented only by inherits this class. Each power state can wait for the timeout event and each can hold the next power state to enter and each power states can notify the state changes through "PmSetSystemPowerStates_I()" function to all the registered devices. |
Example: |
|
class PowerStateUserIdle : public PowerState
{
public:
...
...
}; |
|
|
Let us see how the power manager handles the state changes. After initialize all the power states the "DefaultPowerStateManager" starts handling the power states. This class is having the "PowerState* pcurPowerState" pointer Object. During the initialization the ThreadRun() member function is called and this will fill the "pcurPowerState" object by calling the GetFirstPowerState() function. The initial power state is the Power ON state. Then it will enter in to the current power state. "PowerState" class is already explained above. Since each class is capable of entering to the current power state as well as it is maintain the next power state that it will enter. Also it will wait for the activity event. The activity event may be the activity timeouts, user activity, system activity. All these activity events are handled depends on the "Powerstate" derived classes property. For every change of state, the "DefaultPowerManager" update the "pcurPowerState" object to handle the new power state. |
|
For example consider the class PowerStateUserIdle. This power state class handles the two activity events.
|
|
class PowerStateUserIdle : public PowerState
{
...
...
PLATFORM_ACTIVITY_EVENT activeEvent = PowerState::WaitForEvent
(dwTimeout ,dwNumOfExternEvent, pExternEventArray) ;
switch (activeEvent)
{
case UserActivity:
m_LastNewState = On;
break;
case Timeout:
{
switch (TimeoutItem)
{
case SystemActivityTimeout:
m_LastNewState = SystemIdle;
break;
default:
ASSERT(FALSE);
break;
}
break;
}
}
|
|
|
One is user activity and the other is system activity timeout event. If user activity event occurs then it will switch to "On" state and if system activity timeout event occurs then it will switch to "SystemIdle" state. See the values in the "m_LastNewState". |
|
|
|
|
Figure B - Power Manager Operations
|
|
Activity Timer Management
|
|
Activity timer management is done by the ActivityTimersThreadProc Thread. All the timeout events are generated by this thread. An activity timer list is prepared during the initialization of this thread. These Activity timers are periodically reset by the GWES or other components related to power management. See the activity timer management in the figure a. |
|
Customizing the Power Manager
|
|
Few customizations on power manager is explained one by one below. |
|
Direct switching to System Idle state through the SetSystemPowerState() API
|
|
In the normal case, switching directly to System Idle state using the SetSystemPowerState() API is not allowed. In some cases we may need to do this through our application. For example your requirement is only to shutdown the GWES but not to enter the suspend state and this may be controlled through your application instead of through the automatic state transitions. In this case there is no direct support in the power manager. So the power manager driver is needed to be customized for our requirement. Let us see the implementation of switching directly to user idle state.
SetSystemPowerState() API will call the PmSetSystemPowerState_I() function in the pmsysstate.cpp file. This function will call the DefaultPowerStateManager's PlatformMapPowerStateHint() function in the pwsdef.cpp file to get the current power state, but in this function this current power state is checked for the eligibility to allow an application to change the state through the AppsCanRequestState() function. For the default case of system idle this function is not implemented for the PowerStateSystemIdle class. In the base PowerState class this function is implemented as follows,
|
|
class PowerState
{
......
......
virtual BOOL AppsCanRequestState() { return FALSE; } ;
} ; |
|
|
In this class the AppsCanRequestState() function is simply return FALSE, means default case all the power states will not be eligible to allow the SetSystemPowerState() to change the state. To enable this option this function has to be overridden in the derived classes. For example, |
|
The following PowerState derived classes implement this function by overriding the base class. See the bellow given source code. |
|
class PowerStateSuspended : public PowerState
{
......
......
virtual BOOL AppsCanRequestState() { return TRUE; }
};
class PowerStateResuming : public PowerState
{
......
......
virtual BOOL AppsCanRequestState() { return TRUE; }
}; |
|
|
For the case of system idle state this function has to be implemented to allow SetSystemPowerState() API to change the state to system idle. This is explained in the given blog link. |
|
Dynamically Changing the User Idle and System Idle Timeouts:
|
|
Changing the User Idle and System Idle Timeout through the application can be achieved by simple changing the following registry settings through the registry functions.
|
|
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\Timeouts]
|
"ACUserIdle"=dword:3c ; in seconds
"ACSystemIdle"=dword:12c ; in seconds
"ACSuspend"=dword:0 ; in seconds
"BattUserIdle"=dword:3c ; in seconds
"BattSystemIdle"=dword:b4 ; in seconds
"BattSuspend"=dword:12c ; in seconds
After changing that you have to use the following code to make it update immediately. |
|
HANDLE hevReloadActivityTimeouts = OpenEvent(EVENT_ALL_ACCESS, FALSE,
_T("PowerManager/ReloadActivityTimeouts"));
if (hevReloadActivityTimeouts)
{
SetEvent(hevReloadActivityTimeouts);
CloseHandle(hevReloadActivityTimeouts);
} |
|
|
This event is captured by the DefaultPowerManager "ThreadRun()" function and it will reload the timeouts. The following code explains this operation. |
|
virtual DWORD ThreadRun()
{
...
...
switch (activityEvent)
{
case PmShutDown :
fDone = TRUE;
break;
case PmReloadActivityTimeouts:
PlatformLoadTimeouts(); // No break we need run ReInitTimeouts.
...
...
} |
|
|
The PlatformLoadTimeouts(); will load the timeout value from the registry settings and the timeouts will be reinitialized. This is explained in the following blog link. |
|
Adding New System Power State
|
|
This is an example of adding new power state for a custom device which needs a low power state for USB charge mode. Since the USB charge current is restricted to 500mA some of the peripherals are needed to be powered off and the device charges the battery with the remaining current. To accomplish this, a new power state is created, which is triggered when the USB cable is inserted. In this state we restrict some of the device driver's (peripherals) power consumption. Adding new power state is explained step-by-step method with code snippets for easy understanding. |
|
1. Each power state is represented as class in pwstates.cpp, add your new power state by creating a new class structure.
Example: I have created PowerStateUsbCharge class.
|
|
class PowerStateUsbCharge : public PowerState
{
public:
PowerStateUsbCharge(PowerStateManager *pPwrStateMgr,
PowerState * pNextPowerState = NULL )
:PowerState(pPwrStateMgr,pNextPowerState)
{
};
}; |
|
|
2. Implement a virtual function "EnterState"; this function will be getting called before the system enters the power state corresponds to this class.
|
|
Virtual void EnterState ()
{
PowerState::EnterState();
//Your required codes while entering this power state can be
//added here
} |
|
|
3. Add an Activity state for the new power state in PLATFORM_ACTIVITY_STATE enum in pwstates.cpp
Example: I have added "Usbcharge" state for my new power state
|
|
typedef enum
{
On, // system is running normally with UI enabled
UserIdle, // User Idle state.
SystemIdle,
Resuming, // system is determining what to do after a resume
Suspend, // system suspended, all devices off(or wake-enabled)
ColdReboot,
Reboot,
Usbcharge,
UnknownState = PM_UNKNOWN_POWER_STATE,
} PLATFORM_ACTIVITY_STATE, *PPLATFORM_ACTIVITY_STATE; |
|
|
4. Implement GetState( ) and GetStateString( ) functions with corresponding return value in the new power state class.
Example: GetState function returns the "Usbcharge" enum value.
GetStateString returns the name of the current power state in wide char.
|
|
5. Implement AppsCanRequestState() with return value TRUE or FALSE. This depends on the power state you are creating. If the return value is FALSE, user cannot force fully enter in to this state from the application. |
|
6. Implement WaitForEvent function to wait for the required events for switching to next power state.
|
|
Virtual PLATFORM_ACTIVITY_EVENT WaitForEvent()
{
DWORD dwTimeout = INFINITE;
PLATFORM_ACTIVITY_EVENT activeEvent;
//Disable unnecessary timeout events like useridle timeout, etc.
//Wait for the required events using WaitForMultipleObject or by
//calling PowerState::WaitForEvent
return activeEvent;
} |
|
|
7. After implementing all the required functions we need to add this power state class to the power state list, this can be done by modifying PowerStateManager::CreatePowerStateList function in pwstates.cpp.
Example: I have added PowerStateUsbCharge class to the power state list.
|
|
BOOL PowerStateManager::CreatePowerStateList()
{
if (m_pPowerStateList == NULL)
{
m_pPowerStateList = new PowerStateOn(this,
new PowerStateUserIdle(this,
new PowerStateSystemIdle(this,
new PowerStateUsbCharge(this,
new PowerStateResuming(this,
new PowerStateSuspended(this,
new PowerStateSuspended(this,
new PowerStateColdReboot(this))))))));
}
..
..
} |
|
|
You can just insert the new Power State Class in any position, this is not depending on the actual power state flow, and this is just a list of all power states. |
|
8. Next step is to create an event for entering into the new state, this you have to do in PM\SRC\MDD\pminit.cpp using CreateEvent API. You can create a string event to trigger the event from some other driver. |
|
9. After creating the event for entering the power state we need to add the event created to the event array. This event array is used by each power state's WaitForEvent function for switching to new power state. |
|
10. For adding the event to event array, increment PM_BASE_TOTAL_EVENT in pwsdef.h by 1 for accompanying the new event and add a definition for this new event.
Example: #define PM_USBCHARGE_EVENT 8
|
|
11. In pwstates.cpp GetEventHandle( ) function add a case for the new event created in pminit.cpp.
|
|
HANDLE PowerStateManager::GetEventHandle(DWORD dwIndex)
{
switch (dwIndex)
{
..
..
..
case PM_USBCHARGE_EVENT:
return ghUsbPowerMode;
}
return NULL;
} |
|
|
12. In PowerState Class constructor add a call to GetEventHandle with index as PM_USBCHARGE_EVENT to get the event handle.
|
|
PowerState::PowerState(DefaultPowerStateManager *pPwrStateMgr,
PowerState * pNextPowerState)
: m_pPwrStateMgr (pPwrStateMgr)
, m_pNextPowerState (pNextPowerState)
{
m_dwEventArray [ PM_USBCHARGE_EVENT] =
pPwrStateMgr->GetEventHandle(PM_USBCHARGE_EVENT);
m_dwNumOfEvent = PM_BASE_TOTAL_EVENT;
} |
|
|
13. Now the event created is added to event array which is used by WaitforEvent function for switching to your new power state when the corresponding event is triggered. |
|
14. In WaitforEvent ( ) function of PowerState Class (pwsdef.cpp) add a new case for the custom event, and return the PLATFORM _EVENT specific to the new power state.
Example: In this case I am mapping the event to PowerSourceChange platform event.
|
|
case PM_USBCHARGE_EVENT:
platEvent = PowerSourceChange;
break; |
|
|
15. Now add a new case in WaitforEvent function (in all the required power state class) to handle our new state.
Example: We are assigning the next state as "Usbcharge" based on the activity event.
|
|
switch (activeEvent)
{
case UserActivity:
m_LastNewState = On;
break;
..
..
..
case PowerSourceChange:
m_LastNewState = Usbcharge;
break;
} |
|
|
Now you can enter the new power state by triggering the event created in pminit.cpp. You can also restrict particular device driversto a defined power state by adding registry key. |
|
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State\usbcharge]
"hcd1:"=dword:3 ; D3 |
|
|
Note: Here "usbcharge" is the string returned by the GetStateString function of PowerStateUsbCharge
Above registry setting sets the USB host device to D3 state when in usbcharge mode is entered.
|
|
Conclusion
|
|
This article will help to understand the hidden treasures of Windows Embedded CE 6.0 power management. |
|
|
|