diff -r 000000000000 -r a41df078684a kernel/eka/kernel/power.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kernel/eka/kernel/power.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -0,0 +1,792 @@ +// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// e32\kernel\power.cpp +// Generic power management +// +// + +#include +#include +#include "execs.h" +#include "msgqueue.h" + +// #define _DEBUG_POWER + + +/****************************************************************************** + * Power Manager - a Power Model implementation + ******************************************************************************/ +#ifndef __X86__ +DPowerController* TPowerController::ThePowerController = 0; +#endif + +class DPowerManager : public DPowerModel + { +public: + // from DPowerModel + void CpuIdle(); + void RegisterUserActivity(const TRawEvent& anEvent); + TInt PowerHalFunction(TInt aFunction, TAny* a1, TAny* a2); + void AbsoluteTimerExpired(); + void SystemTimeChanged(TInt anOldTime, TInt aNewTime); + TSupplyStatus MachinePowerStatus(); + +public: + + static DPowerManager* New(); + + TInt EnableWakeupEvents(TPowerState); + void DisableWakeupEvents(); + void RequestWakeupEventNotification(TRequestStatus*); + void CancelWakeupEventNotification(); + TInt PowerDown(); + + void AppendHandler(DPowerHandler*); + void RemoveHandler(DPowerHandler*); + void WakeupEvent(); + + DPowerController* iPowerController; + DBatteryMonitor* iBatteryMonitor; + DPowerHal* iPowerHal; + + TInt iTotalCurrent; + +private: + + DPowerManager(); + + // Acquire the feature lock + // Called in CS + void Lock() + { + Kern::MutexWait(*iFeatureLock); + } + + // Release the feature lock + // Called in CS + void Unlock() + { + Kern::MutexSignal(*iFeatureLock); + } +// +// Currently not used +// +// #ifdef _DEBUG +// TBool IsLocked() + // used in assertions only +// { return iFeatureLock->iCleanup.iThread == TheCurrentThread; } +// #endif + + void NotifyWakeupEvent(TInt aReason); + + DMutex* iFeatureLock; + DThread* iClient; // protected by the system lock + TClientRequest* iRequest; // protected by the system lock + DPowerHandler* iHandlers; // protected by the feature lock + }; + +static DPowerManager* PowerManager; + +class DummyPowerController : public DPowerController + // Used when the plaform doesn't support power management + { +public: // from DPowerController + void CpuIdle() {} + void EnableWakeupEvents() { WakeupEvent(); } + void DisableWakeupEvents() {} + void AbsoluteTimerExpired() {} + void PowerDown(TTimeK /* aWakeupTime */) { if (iTargetState == EPwOff) __PM_PANIC("Can't power off"); } + }; + +TInt PowerModelInit() + { +#ifdef _DEBUG_POWER + TInt maskIndex = KPOWER / 32; + TUint32 bitMask = 1 << (KPOWER % 32); + TheSuperPage().iDebugMask[maskIndex] |= bitMask; +#endif + __KTRACE_OPT2(KBOOT,KPOWER,Kern::Printf("PowerModelInit()")); + DPowerManager* pm = DPowerManager::New(); + if (!pm) + return KErrNoMemory; + PowerManager = pm; + return KErrNone; + } + +_LIT(KPowerMgrMutexName, "PwrMgrLock"); + +DPowerManager* DPowerManager::New() + { + DPowerManager* self = new DPowerManager(); + if (!self) + return NULL; + if (Kern::MutexCreate(self->iFeatureLock, KPowerMgrMutexName, KMutexOrdPowerMgr) != KErrNone) + return NULL; // Do not cleanup since kernel will panic eventually + self->iPowerController = new DummyPowerController(); + if (self->iPowerController == NULL) + return NULL; // Do not cleanup since kernel will panic eventually + TInt r = Kern::CreateClientRequest(self->iRequest); + if (r != KErrNone) + return NULL; // Do not cleanup since kernel will panic eventually + return self; + } + +DPowerManager::DPowerManager() + { + } + +void DPowerManager::CpuIdle() + { // from DPowerModel + __PM_ASSERT(iPowerController); + iPowerController->CpuIdle(); + } + +void DPowerManager::RegisterUserActivity(const TRawEvent&) + { // from DPowerModel + } + +void DPowerManager::SystemTimeChanged(TInt aOldTime, TInt aNewTime) + { // from DPowerModel + if (iBatteryMonitor) + iBatteryMonitor->SystemTimeChanged(aOldTime, aNewTime); + } + +TSupplyStatus DPowerManager::MachinePowerStatus() + { // from DPowerModel + TSupplyStatus s; + if (iBatteryMonitor) + s = iBatteryMonitor->MachinePowerStatus(); + else + s = EGood; + return s; + } + +TInt DPowerManager::PowerHalFunction(TInt aFunction, TAny* a1, TAny* a2) + { // from DPowerModel + __KTRACE_OPT(KPOWER,Kern::Printf(">DPowerManager::PowerHalFunction() func=0x%x, a1=0x%x, a2=0x%x", aFunction, a1, a2)); + TInt r; + if (iPowerHal) + r = iPowerHal->PowerHalFunction(aFunction, a1, a2); + else + r = KErrNotSupported; + + __KTRACE_OPT(KPOWER,Kern::Printf("iTargetState != EPwActive) + iPowerController->AbsoluteTimerExpired(); + } + +// Called in CS +void DPowerManager::AppendHandler(DPowerHandler* aPh) + { // called by drivers (power handler) + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::AppendHandler('%lS')", &aPh->iName)); + __ASSERT_CRITICAL; + //Check to ensure that handler is not added multiple times and not part of any other list + __PM_ASSERT(!(aPh->iPrev) && !(aPh->iNext)); + Lock(); + //Create circular doubly linked power handler list + if(iHandlers == NULL) + { //Empty list, create first entry + iHandlers = aPh; + aPh->iNext = iHandlers; + aPh->iPrev = iHandlers; + } + else + { //Append to end of the list. + aPh->iNext = iHandlers->iNext; + iHandlers->iNext->iPrev = aPh; + aPh->iPrev = iHandlers; + iHandlers->iNext = aPh; + iHandlers = aPh; + } + Unlock(); + } + +// Called in CS +void DPowerManager::RemoveHandler(DPowerHandler* aPh) + { // called by drivers (power handler) + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::RemoveHandler('%lS')", &aPh->iName)); + __ASSERT_CRITICAL; + Lock(); + __PM_ASSERT(aPh); + + if ((iHandlers == NULL) //If the list is empty + || (aPh->iNext == NULL || aPh->iPrev == NULL)) //or entry already removed then return immediately + { + aPh->iPrev = aPh->iNext = NULL; + Unlock(); + return; + } + + DPowerHandler *temp = aPh->iPrev; + if(aPh == temp) //Only one node in the list. + iHandlers = NULL; + else + { //Remove the entry from list + temp->iNext = aPh->iNext; + temp = aPh->iNext; + temp->iPrev = aPh->iPrev; + if(aPh == iHandlers) //If the node is the last node in the list then make iHandler point to last one. + iHandlers = aPh->iPrev; + } + //Make next and prev pointers to NULL + aPh->iPrev = aPh->iNext = NULL; + Unlock(); + } + +void DPowerManager::WakeupEvent() + { // called by power controller + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::WakeupEvent()")); + if (iPowerController->iTargetState != EPwActive) + { + NKern::ThreadEnterCS(); + NotifyWakeupEvent(KErrNone); + NKern::ThreadLeaveCS(); + } + } + +TInt DPowerManager::EnableWakeupEvents(TPowerState aState) + { // called by ExecHandler + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::EnableWakeupEvents(0x%x)", aState)); + if ((aState < 0) || (EPwLimit <= aState)) + return KErrArgument; + if (aState == EPwActive) + return KErrArgument; + Lock(); + if (iPowerController->iTargetState != EPwActive) + iPowerController->DisableWakeupEvents(); + iPowerController->iTargetState = aState; + iPowerController->EnableWakeupEvents(); + Unlock(); + return KErrNone; + } + +void DPowerManager::DisableWakeupEvents() + { // called by ExecHandler + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::DisableWakeupEvents()")); + Lock(); + if (iPowerController->iTargetState != EPwActive) + { + iPowerController->iTargetState = EPwActive; + iPowerController->DisableWakeupEvents(); + } + Unlock(); + } + +void DPowerManager::RequestWakeupEventNotification(TRequestStatus* aStatus) + { // called by ExecHandler + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::RequestWakeupEventNotification()")); + Lock(); // we aquire this lock to avoid new requests while in PowerDown + NKern::LockSystem(); + if (iClient || iRequest->SetStatus(aStatus) != KErrNone) + Kern::RequestComplete(aStatus, KErrInUse); + else + { + iClient = TheCurrentThread; + iClient->Open(); + } + NKern::UnlockSystem(); + Unlock(); + __KTRACE_OPT(KPOWER,Kern::Printf("Close(NULL); + } + +void DPowerManager::CancelWakeupEventNotification() + { // called by ExecHandler + __KTRACE_OPT(KPOWER,Kern::Printf("PowerManger::CancelWakeupEventNotification()")); + NKern::ThreadEnterCS(); + NotifyWakeupEvent(KErrCancel); + NKern::ThreadLeaveCS(); + } + +// Called in CS +TInt DPowerManager::PowerDown() + { // called by ExecHandler + __KTRACE_OPT(KPOWER,Kern::Printf(">PowerManger::PowerDown(0x%x) Enter", iPowerController->iTargetState)); + __ASSERT_CRITICAL; + + Lock(); + + + if (iPowerController->iTargetState == EPwActive) + { + Unlock(); + return KErrNotReady; + } + + __PM_ASSERT(iHandlers); + NFastSemaphore sem(0); + DPowerHandler* ph = iHandlers; + //Power down in reverse order of handle registration. + do + { +#ifdef _DEBUG_POWER + __PM_ASSERT(!(ph->iStatus & DPowerHandler::EDone)); +#endif + ph->iSem = &sem; + ph->PowerDown(iPowerController->iTargetState); +#ifdef _DEBUG_POWER + __PM_ASSERT(!(ph->iStatus & DPowerHandler::EDone)); + NKern::FSWait(&sem); // power down drivers one after another to simplify debug + __PM_ASSERT(!ph->iSem); + __PM_ASSERT(ph->iStatus & EDone); + ph->iStatus &= ~EDone; +#endif + ph = ph->iPrev; + }while(ph != iHandlers); + +#ifndef _DEBUG_POWER + ph = iHandlers; + do + { + NKern::FSWait(&sem); + ph = ph->iPrev; + }while(ph != iHandlers); +#endif + + TTickQ::Wait(); + + iPowerController->PowerDown(K::SecondQ->WakeupTime()); + __PM_ASSERT(iPowerController->iTargetState != EPwOff); + iPowerController->iTargetState = EPwActive; + + K::SecondQ->WakeUp(); + TTickQ::Signal(); + + ph = iHandlers->iNext; + //Power up in same order of handle registration. + do + { +#ifdef _DEBUG_POWER + __PM_ASSERT(!(ph->iStatus & DPowerHandler::EDone)); +#endif + ph->iSem = &sem; + ph->PowerUp(); +#ifdef _DEBUG_POWER + __PM_ASSERT(!(ph->iStatus & DPowerHandler::EDone)); + NKern::FSWait(&sem); // power down drivers one after another to simplify debug + __PM_ASSERT(!ph->iSem); + __PM_ASSERT(ph->iStatus & EDone); + ph->iStatus &= ~EDone; +#endif + ph = ph->iNext; + }while(ph != iHandlers->iNext); + +#ifndef _DEBUG_POWER + ph = iHandlers->iNext; + do + { + NKern::FSWait(&sem); + ph = ph->iNext; + }while(ph != iHandlers->iNext); +#endif + + // complete wakeup notification request if any + NotifyWakeupEvent(KErrNone); + + Unlock(); + + __KTRACE_OPT(KPOWER,Kern::Printf("AppendHandler(this); + } + + + + +/** +De-registers the power handler. + +After the call to this function completes, the power manager stops notifying +the device driver about kernel power state transitions. + +Note that the implementation of Remove() acquires an internal lock (a DMutex), +which is also held when the power manager calls PowerDown() and PowerUp(). +This means that the device driver cannot hold a lock over Remove() calls if +the same lock is acquired by PowerDown() and PowerUp() implementations. + +You can find an example of synchronization between outgoing Remove() +and incoming PowerDown() & PowerUp() in e32/drivers/ecomm/d_comm.cpp. + +@see DPowerHandler::Add() + +@pre Calling thread must be in a critical section. +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C void DPowerHandler::Remove() + { + CHECK_PRECONDITIONS(MASK_THREAD_CRITICAL,"DPowerHandler::Remove"); + PowerManager->RemoveHandler(this); + } + + +void DPowerHandler::Done() + { // private +#ifdef _DEBUG_POWER + __PM_ASSERT(!(iStatus & EDone)); + iStatus |= EDone; +#endif + NKern::Lock(); + __KTRACE_OPT(KPOWER,Kern::Printf("DPowerHandler::Done('%lS') sem=0x%x", &iName, iSem)); + NFastSemaphore* sem = (NFastSemaphore*)__e32_atomic_swp_ord_ptr(&iSem, 0); + if (sem) + sem->Signal(); + NKern::Unlock(); + } + + + + +/** +Signals power up completion. + +A device driver calls this function precisely once in response to a PowerUp() call. +It can be called by any thread at any time after PowerUp() has been entered. + +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C void DPowerHandler::PowerUpDone() + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"DPowerHandler::PowerUpDone"); + Done(); + } + + + +/** +Signals power down completion. + +A device driver calls this function precisely once in response to a PowerDown() call. +It can be called by any thread at any time after PowerDown() has been entered. + +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C void DPowerHandler::PowerDownDone() + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"DPowerHandler::PowerDownDone"); + Done(); + } + + + +/** @deprecated, no replacement */ +EXPORT_C void DPowerHandler::DeltaCurrentConsumption(TInt aDelta) + { + __e32_atomic_add_ord32(&iCurrent, aDelta); + __PM_ASSERT(iCurrent >= 0); + __e32_atomic_add_ord32(&PowerManager->iTotalCurrent, aDelta); + __PM_ASSERT(PowerManager->iTotalCurrent >= 0); + } + +/** @deprecated, no replacement */ +EXPORT_C void DPowerHandler::SetCurrentConsumption(TInt aCurrent) + { + __PM_ASSERT(aCurrent >= 0); + TInt old = (TInt)__e32_atomic_swp_ord32(&iCurrent, aCurrent); + TInt delta = aCurrent - old; + __e32_atomic_add_ord32(&PowerManager->iTotalCurrent, delta); + __PM_ASSERT(PowerManager->iTotalCurrent >= 0); + } + +/****************************************************************************** + * Power Controller + ******************************************************************************/ + + + + +/** +Default constructor. + +Typically a variant implementation creates a power controller object at +variant initialization time. + +Only one power controller can be created. + +The object can never be destroyed. + +@pre Calling thread must be in a critical section. +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C DPowerController::DPowerController() + { + CHECK_PRECONDITIONS(MASK_THREAD_CRITICAL,"DPowerController::DPowerController"); + iTargetState = EPwActive; +#ifndef __X86__ + iResourceControllerData.iResourceController = NULL; + iResourceControllerData.iClientId = -1; +#endif + } + +/** +Registers this power controller object with the power manager. + +The power manager can only use the power controller after registration. + +@pre Calling thread must be in a critical section. +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C void DPowerController::Register() + { + CHECK_PRECONDITIONS(MASK_THREAD_CRITICAL,"DPowerController::Register"); + __KTRACE_OPT2(KBOOT,KPOWER,Kern::Printf("DPowerController::Register()")); + delete PowerManager->iPowerController; + PowerManager->iPowerController = this; +#ifndef __X86__ + TPowerController::ThePowerController = this; +#endif + K::PowerModel = PowerManager; + } + +#ifndef __X86__ +/** +Registers resource controller with power controller +This function is called only by resource controller to register itself with power controller +*/ +EXPORT_C TInt DPowerController::RegisterResourceController(DBase* aController, TInt aClientId) + { + __KTRACE_OPT(KRESMANAGER, Kern::Printf("DPowerController::Register()")); + if(iResourceControllerData.iResourceController) + return KErrAlreadyExists; + //Store the Resource controller pointer + iResourceControllerData.iResourceController = aController; + //Store the client Id generated by Resource controller for Power controller to use. + iResourceControllerData.iClientId = aClientId; + return KErrNone; + } + +/** + Interface class to give access to the power controller to base port components. +*/ +EXPORT_C DPowerController* TPowerController::PowerController() + { + return TPowerController::ThePowerController; + } +#endif + +/** +Signals a wakeup event. + +The power controller must signal wakeup events when enabled, +through EnableWakeupEvents() and DisableWakeupEvents(), and +when iTargetState is not equal to EPwActive. + +@see DPowerController::iTargetState + +@pre Calling thread must be in a critical section. +@pre No fast mutex can be held. +@pre Call in a thread context. +@pre Kernel must be unlocked +@pre interrupts enabled +*/ +EXPORT_C void DPowerController::WakeupEvent() + { + CHECK_PRECONDITIONS(MASK_THREAD_CRITICAL,"DPowerController::WakeupEvent"); + PowerManager->WakeupEvent(); + } + + +/****************************************************************************** + * Battery Monitor + ******************************************************************************/ + +EXPORT_C DBatteryMonitor::DBatteryMonitor() + { + } + +EXPORT_C void DBatteryMonitor::Register() + { + __KTRACE_OPT2(KBOOT,KPOWER,Kern::Printf("DBatteryMonitor::Register()")); + PowerManager->iBatteryMonitor = this; + } + +/****************************************************************************** + * Power Hal + ******************************************************************************/ + +EXPORT_C DPowerHal::DPowerHal() + { + } + +EXPORT_C void DPowerHal::Register() + { + __KTRACE_OPT2(KBOOT,KPOWER,Kern::Printf("DPowerHal::Register()")); + PowerManager->iPowerHal = this; + } + +/****************************************************************************** + * Kern + ******************************************************************************/ + +/** Checks power status + @return ETrue if status is good, EFalse otherwise +*/ + +EXPORT_C TBool Kern::PowerGood() + { + return K::PowerGood; + } + +/****************************************************************************** + * Exec Handlers + ******************************************************************************/ + +TInt ExecHandler::PowerEnableWakeupEvents(TPowerState aState) + { + if(!Kern::CurrentThreadHasCapability(ECapabilityPowerMgmt,__PLATSEC_DIAGNOSTIC_STRING("Checked by Power::EnableWakeupEvents"))) + K::UnlockedPlatformSecurityPanic(); + NKern::ThreadEnterCS(); + TInt r = PowerManager->EnableWakeupEvents(aState); + NKern::ThreadLeaveCS(); + return r; + } + +void ExecHandler::PowerDisableWakeupEvents() + { + if(!Kern::CurrentThreadHasCapability(ECapabilityPowerMgmt,__PLATSEC_DIAGNOSTIC_STRING("Checked by Power::DisableWakeupEvents"))) + K::UnlockedPlatformSecurityPanic(); + NKern::ThreadEnterCS(); + PowerManager->DisableWakeupEvents(); + NKern::ThreadLeaveCS(); + } + +void ExecHandler::PowerRequestWakeupEventNotification(TRequestStatus* aStatus) + { + if(!Kern::CurrentThreadHasCapability(ECapabilityPowerMgmt,__PLATSEC_DIAGNOSTIC_STRING("Checked by Power::RequestWakeupEventNotification"))) + K::UnlockedPlatformSecurityPanic(); + NKern::ThreadEnterCS(); + PowerManager->RequestWakeupEventNotification(aStatus); + NKern::ThreadLeaveCS(); + } + +void ExecHandler::PowerCancelWakeupEventNotification() + { + if(!Kern::CurrentThreadHasCapability(ECapabilityPowerMgmt,__PLATSEC_DIAGNOSTIC_STRING("Checked by Power::CancelWakeupEventNotification"))) + K::UnlockedPlatformSecurityPanic(); + PowerManager->CancelWakeupEventNotification(); + } + +TInt ExecHandler::PowerDown() + { + if(!Kern::CurrentThreadHasCapability(ECapabilityPowerMgmt,__PLATSEC_DIAGNOSTIC_STRING("Checked by Power::PowerDown"))) + K::UnlockedPlatformSecurityPanic(); + NKern::ThreadEnterCS(); + TInt r = PowerManager->PowerDown(); + NKern::ThreadLeaveCS(); + return r; + } +