/* * Copyright (c) 1998-2006 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Required for notification instrumentation #include "IOServicePrivate.h" #include "IOServicePMPrivate.h" #include "IOKitKernelInternal.h" static void settle_timer_expired(thread_call_param_t, thread_call_param_t); static void PM_idle_timer_expired(OSObject *, IOTimerEventSource *); void tellAppWithResponse(OSObject * object, void * context) { /*empty*/ } void tellClientWithResponse(OSObject * object, void * context) { /*empty*/ } void tellClient(OSObject * object, void * context); IOReturn serializedAllowPowerChange(OSObject *, void *, void *, void *, void *); static uint64_t computeTimeDeltaNS( const AbsoluteTime * start ) { AbsoluteTime now; uint64_t nsec; clock_get_uptime(&now); SUB_ABSOLUTETIME(&now, start); absolutetime_to_nanoseconds(now, &nsec); return nsec; } OSDefineMetaClassAndStructors(IOPMprot, OSObject) // log setPowerStates longer than (ns): #define LOG_SETPOWER_TIMES (50ULL * 1000ULL * 1000ULL) // log app responses longer than (ns): #define LOG_APP_RESPONSE_TIMES (100ULL * 1000ULL * 1000ULL) //********************************************************************************* // Globals //********************************************************************************* static bool gIOPMInitialized = false; static IOItemCount gIOPMBusyCount = 0; static IOWorkLoop * gIOPMWorkLoop = 0; static IOPMRequestQueue * gIOPMRequestQueue = 0; static IOPMRequestQueue * gIOPMReplyQueue = 0; static IOPMRequestQueue * gIOPMFreeQueue = 0; //********************************************************************************* // Macros //********************************************************************************* #define PM_ERROR(x...) do { kprintf(x); IOLog(x); } while (false) #define PM_DEBUG(x...) do { kprintf(x); } while (false) #define PM_TRACE(x...) do { \ if (kIOLogDebugPower & gIOKitDebug) kprintf(x); } while (false) #define PM_CONNECT(x...) #define PM_ASSERT_IN_GATE(x) \ do { \ assert(gIOPMWorkLoop->inGate()); \ } while(false) #define PM_LOCK() IOLockLock(fPMLock) #define PM_UNLOCK() IOLockUnlock(fPMLock) #define ns_per_us 1000 #define k30seconds (30*1000000) #define kMinAckTimeoutTicks (10*1000000) #define kIOPMTardyAckSPSKey "IOPMTardyAckSetPowerState" #define kIOPMTardyAckPSCKey "IOPMTardyAckPowerStateChange" #define kPwrMgtKey "IOPowerManagement" #define OUR_PMLog(t, a, b) \ do { fPlatform->PMLog( fName, t, a, b); } while(0) #define NS_TO_MS(nsec) ((int)((nsec) / 1000000ULL)) #if CONFIG_EMBEDDED #define SUPPORT_IDLE_CANCEL 1 #endif //********************************************************************************* // PM machine states //********************************************************************************* enum { kIOPM_OurChangeTellClientsPowerDown = 1, kIOPM_OurChangeTellPriorityClientsPowerDown = 2, kIOPM_OurChangeNotifyInterestedDriversWillChange = 3, kIOPM_OurChangeSetPowerState = 4, kIOPM_OurChangeWaitForPowerSettle = 5, kIOPM_OurChangeNotifyInterestedDriversDidChange = 6, kIOPM_OurChangeFinish = 7, kIOPM_ParentDownTellPriorityClientsPowerDown = 8, kIOPM_ParentDownNotifyInterestedDriversWillChange = 9, /* 10 not used */ kIOPM_ParentDownNotifyDidChangeAndAcknowledgeChange = 11, kIOPM_ParentDownSetPowerState = 12, kIOPM_ParentDownWaitForPowerSettle = 13, kIOPM_ParentDownAcknowledgeChange = 14, kIOPM_ParentUpSetPowerState = 15, /* 16 not used */ kIOPM_ParentUpWaitForSettleTime = 17, kIOPM_ParentUpNotifyInterestedDriversDidChange = 18, kIOPM_ParentUpAcknowledgePowerChange = 19, kIOPM_Finished = 20, kIOPM_DriverThreadCallDone = 21, kIOPM_NotifyChildrenDone = 22 }; /* Power Management defines a few roles that drivers can play in their own, and other drivers', power management. We briefly define those here. Many drivers implement their policy maker and power controller within the same IOService object, but that is not required. == Policy Maker == * Virtual IOService PM methods a "policy maker" may implement * maxCapabilityForDomainState() * initialPowerStateForDomainState() * powerStateForDomainState() * Virtual IOService PM methods a "policy maker" may CALL * PMinit() == Power Controller == * Virtual IOService PM methods a "power controller" may implement * setPowerState() * Virtual IOService PM methods a "power controller" may CALL * joinPMtree() * registerPowerDriver() ======================= There are two different kinds of power state changes. * One is initiated by a subclassed device object which has either decided to change power state, or its controlling driver has suggested it, or some other driver wants to use the idle device and has asked it to become usable. * The second kind of power state change is initiated by the power domain parent. The two are handled through different code paths. We maintain a queue of "change notifications," or change notes. * Usually the queue is empty. * When it isn't, usually there is one change note in it * It's possible to have more than one power state change pending at one time, so a queue is implemented. Example: * The subclass device decides it's idle and initiates a change to a lower power state. This causes interested parties to be notified, but they don't all acknowledge right away. This causes the change note to sit in the queue until all the acks are received. During this time, the device decides it isn't idle anymore and wants to raise power back up again. This change can't be started, however, because the previous one isn't complete yet, so the second one waits in the queue. During this time, the parent decides to lower or raise the power state of the entire power domain and notifies the device, and that notification goes into the queue, too, and can't be actioned until the others are. == SelfInitiated == This is how a power change initiated by the subclass device is handled: -> First, all interested parties are notified of the change via their powerStateWillChangeTo method. If they all don't acknowledge via return code, then we have to wait. If they do, or when they finally all acknowledge via our acknowledgePowerChange method, then we can continue. -> We call the controlling driver, instructing it to change to the new state -> Then we wait for power to settle. If there is no settling-time, or after it has passed, -> we notify interested parties again, this time via their powerStateDidChangeTo methods. -> When they have all acked, we're done. If we lowered power and don't need the power domain to be in its current power state, we suggest to the parent that it lower the power domain state. == PowerDomainDownInitiated == How a change to a lower power domain state initiated by the parent is handled: -> First, we figure out what power state we will be in when the new domain state is reached. -> Then all interested parties are notified that we are moving to that new state. -> When they have acknowledged, we call the controlling driver to assume that state and we wait for power to settle. -> Then we acknowledge our preparedness to our parent. When all its interested parties have acknowledged, -> it lowers power and then notifies its interested parties again. -> When we get this call, we notify our interested parties that the power state has changed, and when they have all acknowledged, we're done. == PowerDomainUpInitiated == How a change to a higher power domain state initiated by the parent is handled: -> We figure out what power state we will be in when the new domain state is reached. -> If it is different from our current state we acknowledge the parent. -> When all the parent's interested parties have acknowledged, it raises power in the domain and waits for power to settle. -> Then it notifies everyone that the new state has been reached. -> When we get this call, we call the controlling driver, instructing it to assume the new state, and wait for power to settle. -> Then we notify our interested parties. When they all acknowledge we are done. In either of the two power domain state cases above, it is possible that we will not be changing state even though the domain is. Examples: * A change to a lower domain state may not affect us because we are already in a low enough state, * We will not take advantage of a change to a higher domain state, because we have no need of the higher power. In such cases, there is nothing to do but acknowledge the parent. So when the parent calls our powerDomainWillChange method, and we decide that we will not be changing state, we merely acknowledge the parent, via return code, and wait. When the parent subsequently calls powerStateDidChange, we acknowledge again via return code, and the change is complete. == 4 Paths Through State Machine == Power state changes are processed in a state machine, and since there are four varieties of power state changes, there are four major paths through the state machine. == 5. No Need To change == The fourth is nearly trivial. In this path, the parent is changing the domain state, but we are not changing the device state. The change starts when the parent calls powerDomainWillChange. All we do is acknowledge the parent. When the parent calls powerStateDidChange, we acknowledge the parent again, and we're done. == 1. OurChange Down == XXX gvdl The first is fairly simple. It starts: * when a power domain child calls requestPowerDomainState and we decide to change power states to accomodate the child, * or if our power-controlling driver calls changePowerStateTo, * or if some other driver which is using our device calls makeUsable, * or if a subclassed object calls changePowerStateToPriv. These are all power changes initiated by us, not forced upon us by the parent. -> We start by notifying interested parties. -> If they all acknowledge via return code, we can go on to state "msSetPowerState". -> Otherwise, we start the ack timer and wait for the stragglers to acknowlege by calling acknowledgePowerChange. -> We move on to state "msSetPowerState" when all the stragglers have acknowledged, or when the ack timer expires on all those which didn't acknowledge. In "msSetPowerState" we call the power-controlling driver to change the power state of the hardware. -> If it returns saying it has done so, we go on to state "msWaitForPowerSettle". -> Otherwise, we have to wait for it, so we set the ack timer and wait. -> When it calls acknowledgeSetPowerState, or when the ack timer expires, we go on. In "msWaitForPowerSettle", we look in the power state array to see if there is any settle time required when changing from our current state to the new state. -> If not, we go right away to "msNotifyInterestedDriversDidChange". -> Otherwise, we set the settle timer and wait. When it expires, we move on. In "msNotifyInterestedDriversDidChange" state, we notify all our interested parties via their powerStateDidChange methods that we have finished changing power state. -> If they all acknowledge via return code, we move on to "msFinish". -> Otherwise we set the ack timer and wait. When they have all acknowledged, or when the ack timer has expired for those that didn't, we move on to "msFinish". In "msFinish" we remove the used change note from the head of the queue and start the next one if one exists. == 2. Parent Change Down == Start at Stage 2 of OurChange Down XXX gvdl == 3. Change Up == Start at Stage 4 of OurChange Down XXX gvdl Note all parent requested changes need to acknowledge the power has changed to the parent when done. */ //********************************************************************************* // [public virtual] PMinit // // Initialize power management. //********************************************************************************* void IOService::PMinit ( void ) { if ( !initialized ) { if ( !gIOPMInitialized ) { gIOPMWorkLoop = IOWorkLoop::workLoop(); if (gIOPMWorkLoop) { gIOPMRequestQueue = IOPMRequestQueue::create( this, OSMemberFunctionCast(IOPMRequestQueue::Action, this, &IOService::servicePMRequestQueue)); gIOPMReplyQueue = IOPMRequestQueue::create( this, OSMemberFunctionCast(IOPMRequestQueue::Action, this, &IOService::servicePMReplyQueue)); gIOPMFreeQueue = IOPMRequestQueue::create( this, OSMemberFunctionCast(IOPMRequestQueue::Action, this, &IOService::servicePMFreeQueue)); if (gIOPMWorkLoop->addEventSource(gIOPMRequestQueue) != kIOReturnSuccess) { gIOPMRequestQueue->release(); gIOPMRequestQueue = 0; } if (gIOPMWorkLoop->addEventSource(gIOPMReplyQueue) != kIOReturnSuccess) { gIOPMReplyQueue->release(); gIOPMReplyQueue = 0; } if (gIOPMWorkLoop->addEventSource(gIOPMFreeQueue) != kIOReturnSuccess) { gIOPMFreeQueue->release(); gIOPMFreeQueue = 0; } } if (gIOPMRequestQueue && gIOPMReplyQueue && gIOPMFreeQueue) gIOPMInitialized = true; } if (!gIOPMInitialized) return; pwrMgt = new IOServicePM; pwrMgt->init(); setProperty(kPwrMgtKey, pwrMgt); fOwner = this; fWeAreRoot = false; fPMLock = IOLockAlloc(); fInterestedDrivers = new IOPMinformeeList; fInterestedDrivers->initialize(); fDesiredPowerState = 0; fDriverDesire = 0; fDeviceDesire = 0; fInitialChange = true; fNeedToBecomeUsable = false; fPreviousRequest = 0; fDeviceOverrides = false; fMachineState = kIOPM_Finished; fIdleTimerEventSource = NULL; fIdleTimerMinPowerState = 0; fActivityLock = IOLockAlloc(); fClampOn = false; fStrictTreeOrder = false; fActivityTicklePowerState = -1; fControllingDriver = NULL; fPowerStates = NULL; fNumberOfPowerStates = 0; fCurrentPowerState = 0; fParentsCurrentPowerFlags = 0; fMaxCapability = 0; fName = getName(); fPlatform = getPlatform(); fParentsKnowState = false; fSerialNumber = 0; fResponseArray = NULL; fDoNotPowerDown = true; fCurrentPowerConsumption = kIOPMUnknown; for (unsigned int i = 0; i <= kMaxType; i++) { fAggressivenessValue[i] = 0; fAggressivenessValid[i] = false; } fAckTimer = thread_call_allocate( &IOService::ack_timer_expired, (thread_call_param_t)this); fSettleTimer = thread_call_allocate( &settle_timer_expired, (thread_call_param_t)this); fDriverCallEntry = thread_call_allocate( (thread_call_func_t) &IOService::pmDriverCallout, this); assert(fDriverCallEntry); #if PM_VARS_SUPPORT IOPMprot * prot = new IOPMprot; if (prot) { prot->init(); prot->ourName = fName; prot->thePlatform = fPlatform; fPMVars = prot; pm_vars = prot; } #else pm_vars = (IOPMprot *) true; #endif initialized = true; } } //********************************************************************************* // [public] PMfree // // Free up the data created in PMinit, if it exists. //********************************************************************************* void IOService::PMfree ( void ) { initialized = false; pm_vars = 0; if ( pwrMgt ) { assert(fMachineState == kIOPM_Finished); assert(fInsertInterestSet == NULL); assert(fRemoveInterestSet == NULL); assert(fNotifyChildArray == NULL); if ( fIdleTimerEventSource != NULL ) { getPMworkloop()->removeEventSource(fIdleTimerEventSource); fIdleTimerEventSource->release(); fIdleTimerEventSource = NULL; } if ( fSettleTimer ) { thread_call_cancel(fSettleTimer); thread_call_free(fSettleTimer); fSettleTimer = NULL; } if ( fAckTimer ) { thread_call_cancel(fAckTimer); thread_call_free(fAckTimer); fAckTimer = NULL; } if ( fDriverCallEntry ) { thread_call_free(fDriverCallEntry); fDriverCallEntry = NULL; } if ( fPMLock ) { IOLockFree(fPMLock); fPMLock = NULL; } if ( fActivityLock ) { IOLockFree(fActivityLock); fActivityLock = NULL; } if ( fInterestedDrivers ) { fInterestedDrivers->release(); fInterestedDrivers = NULL; } if ( fPMWorkQueue ) { getPMworkloop()->removeEventSource(fPMWorkQueue); fPMWorkQueue->release(); fPMWorkQueue = 0; } if (fDriverCallParamSlots && fDriverCallParamPtr) { IODelete(fDriverCallParamPtr, DriverCallParam, fDriverCallParamSlots); fDriverCallParamPtr = 0; fDriverCallParamSlots = 0; } if ( fResponseArray ) { fResponseArray->release(); fResponseArray = NULL; } if (fPowerStates && fNumberOfPowerStates) { IODelete(fPowerStates, IOPMPowerState, fNumberOfPowerStates); fNumberOfPowerStates = 0; fPowerStates = NULL; } #if PM_VARS_SUPPORT if (fPMVars) { fPMVars->release(); fPMVars = 0; } #endif pwrMgt->release(); pwrMgt = 0; } } //********************************************************************************* // [public virtual] joinPMtree // // A policy-maker calls its nub here when initializing, to be attached into // the power management hierarchy. The default function is to call the // platform expert, which knows how to do it. This method is overridden // by a nub subclass which may either know how to do it, or may need to // take other action. // // This may be the only "power management" method used in a nub, // meaning it may not be initialized for power management. //********************************************************************************* void IOService::joinPMtree ( IOService * driver ) { IOPlatformExpert * platform; platform = getPlatform(); assert(platform != 0); platform->PMRegisterDevice(this, driver); } //********************************************************************************* // [public virtual] youAreRoot // // Power Managment is informing us that we are the root power domain. // The only difference between us and any other power domain is that // we have no parent and therefore never call it. //********************************************************************************* IOReturn IOService::youAreRoot ( void ) { fWeAreRoot = true; fParentsKnowState = true; attachToParent( getRegistryRoot(), gIOPowerPlane ); return IOPMNoErr; } //********************************************************************************* // [public virtual] PMstop // // Immediately stop driver callouts. Schedule an async stop request to detach // from power plane. //********************************************************************************* void IOService::PMstop ( void ) { IOPMRequest * request; if (!initialized) return; // Schedule an async PMstop request, but immediately stop any further // calls to the controlling or interested drivers. This device will // continue to exist in the power plane and participate in power state // changes until the PMstop async request is processed. PM_LOCK(); fWillPMStop = true; if (fDriverCallBusy) PM_DEBUG("%s::PMstop() driver call busy\n", getName()); PM_UNLOCK(); request = acquirePMRequest( this, kIOPMRequestTypePMStop ); if (request) { PM_TRACE("[%s] %p PMstop\n", getName(), this); submitPMRequest( request ); } } //********************************************************************************* // handlePMstop // // Disconnect the node from its parents and children in the Power Plane. //********************************************************************************* void IOService::handlePMstop ( IOPMRequest * request ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; IOService * theChild; IOService * theParent; PM_ASSERT_IN_GATE(); PM_TRACE("[%s] %p %s start\n", getName(), this, __FUNCTION__); // remove the property removeProperty(kPwrMgtKey); // detach parents iter = getParentIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { theParent = (IOService *)connection->copyParentEntry(gIOPowerPlane); if ( theParent ) { theParent->removePowerChild(connection); theParent->release(); } } } iter->release(); } // detach IOConnections detachAbove( gIOPowerPlane ); // no more power state changes fParentsKnowState = false; // detach children iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { theChild = ((IOService *)(connection->copyChildEntry(gIOPowerPlane))); if ( theChild ) { // detach nub from child connection->detachFromChild(theChild, gIOPowerPlane); theChild->release(); } // detach us from nub detachFromChild(connection, gIOPowerPlane); } } iter->release(); } // Remove all interested drivers from the list, including the power // controlling driver. // // Usually, the controlling driver and the policy-maker functionality // are implemented by the same object, and without the deregistration, // the object will be holding an extra retain on itself, and cannot // be freed. if ( fInterestedDrivers ) { IOPMinformeeList * list = fInterestedDrivers; IOPMinformee * item; PM_LOCK(); while ((item = list->firstInList())) { list->removeFromList(item->whatObject); } PM_UNLOCK(); } // Tell PM_idle_timer_expiration() to ignore idle timer. fIdleTimerPeriod = 0; fWillPMStop = false; PM_TRACE("[%s] %p %s done\n", getName(), this, __FUNCTION__); } //********************************************************************************* // [public virtual] addPowerChild // // Power Management is informing us who our children are. //********************************************************************************* IOReturn IOService::addPowerChild ( IOService * child ) { IOPowerConnection * connection = 0; IOPMRequest * requests[3] = {0, 0, 0}; OSIterator * iter; bool ok = true; if (!child) return kIOReturnBadArgument; if (!initialized || !child->initialized) return IOPMNotYetInitialized; OUR_PMLog( kPMLogAddChild, 0, 0 ); do { // Is this child already one of our children? iter = child->getParentIterator( gIOPowerPlane ); if ( iter ) { IORegistryEntry * entry; OSObject * next; while ((next = iter->getNextObject())) { if ((entry = OSDynamicCast(IORegistryEntry, next)) && isChild(entry, gIOPowerPlane)) { ok = false; break; } } iter->release(); } if (!ok) { PM_DEBUG("[%s] %s (%p) is already a child\n", getName(), child->getName(), child); break; } // Add the child to the power plane immediately, but the // joining connection is marked as not ready. // We want the child to appear in the power plane before // returning to the caller, but don't want the caller to // block on the PM work loop. connection = new IOPowerConnection; if (!connection) break; // Create a chain of PM requests to perform the bottom-half // work from the PM work loop. requests[0] = acquirePMRequest( /* target */ this, /* type */ kIOPMRequestTypeAddPowerChild1 ); requests[1] = acquirePMRequest( /* target */ child, /* type */ kIOPMRequestTypeAddPowerChild2 ); requests[2] = acquirePMRequest( /* target */ this, /* type */ kIOPMRequestTypeAddPowerChild3 ); if (!requests[0] || !requests[1] || !requests[2]) break; requests[0]->setParentRequest( requests[1] ); requests[1]->setParentRequest( requests[2] ); connection->init(); connection->start(this); connection->setAwaitingAck(false); connection->setReadyFlag(false); attachToChild( connection, gIOPowerPlane ); connection->attachToChild( child, gIOPowerPlane ); // connection needs to be released requests[0]->fArg0 = connection; requests[1]->fArg0 = connection; requests[2]->fArg0 = connection; submitPMRequest( requests, 3 ); return kIOReturnSuccess; } while (false); if (connection) connection->release(); if (requests[0]) releasePMRequest(requests[0]); if (requests[1]) releasePMRequest(requests[1]); if (requests[2]) releasePMRequest(requests[2]); // silent failure, to prevent platform drivers from adding the child // to the root domain. return IOPMNoErr; } //********************************************************************************* // [private] addPowerChild1 // // Called on the power parent. //********************************************************************************* void IOService::addPowerChild1 ( IOPMRequest * request ) { unsigned long tempDesire = 0; // Make us temporary usable before adding the child. PM_ASSERT_IN_GATE(); OUR_PMLog( kPMLogMakeUsable, kPMLogMakeUsable, fDeviceDesire ); if (fControllingDriver && inPlane(gIOPowerPlane) && fParentsKnowState) { tempDesire = fNumberOfPowerStates - 1; } if (tempDesire && (fWeAreRoot || (fMaxCapability >= tempDesire))) { computeDesiredState( tempDesire ); changeState(); } } //********************************************************************************* // [private] addPowerChild2 // // Called on the joining child. Blocked behind addPowerChild1. //********************************************************************************* void IOService::addPowerChild2 ( IOPMRequest * request ) { IOPowerConnection * connection = (IOPowerConnection *) request->fArg0; IOService * parent; IOPMPowerFlags powerFlags; bool knowsState; unsigned long powerState; unsigned long tempDesire; PM_ASSERT_IN_GATE(); parent = (IOService *) connection->getParentEntry(gIOPowerPlane); if (!parent || !inPlane(gIOPowerPlane)) { PM_DEBUG("[%s] addPowerChild2 not in power plane\n", getName()); return; } // Parent will be waiting for us to complete this stage, safe to // directly access parent's vars. knowsState = (parent->fPowerStates) && (parent->fParentsKnowState); powerState = parent->fCurrentPowerState; if (knowsState) powerFlags = parent->fPowerStates[powerState].outputPowerCharacter; else powerFlags = 0; // Set our power parent. OUR_PMLog(kPMLogSetParent, knowsState, powerFlags); setParentInfo( powerFlags, connection, knowsState ); connection->setReadyFlag(true); if ( fControllingDriver && fParentsKnowState ) { fMaxCapability = fControllingDriver->maxCapabilityForDomainState(fParentsCurrentPowerFlags); // initially change into the state we are already in tempDesire = fControllingDriver->initialPowerStateForDomainState(fParentsCurrentPowerFlags); computeDesiredState(tempDesire); fPreviousRequest = 0xffffffff; changeState(); } } //********************************************************************************* // [private] addPowerChild3 // // Called on the parent. Blocked behind addPowerChild2. //********************************************************************************* void IOService::addPowerChild3 ( IOPMRequest * request ) { IOPowerConnection * connection = (IOPowerConnection *) request->fArg0; IOService * child; unsigned int i; PM_ASSERT_IN_GATE(); child = (IOService *) connection->getChildEntry(gIOPowerPlane); if (child && inPlane(gIOPowerPlane)) { if (child->getProperty("IOPMStrictTreeOrder")) { PM_DEBUG("[%s] strict ordering enforced\n", getName()); fStrictTreeOrder = true; } for (i = 0; i <= kMaxType; i++) { if ( fAggressivenessValid[i] ) { child->setAggressiveness(i, fAggressivenessValue[i]); } } } else { PM_DEBUG("[%s] addPowerChild3 not in power plane\n", getName()); } connection->release(); } //********************************************************************************* // [public virtual deprecated] setPowerParent // // Power Management is informing us who our parent is. // If we have a controlling driver, find out, given our newly-informed // power domain state, what state it would be in, and then tell it // to assume that state. //********************************************************************************* IOReturn IOService::setPowerParent ( IOPowerConnection * theParent, bool stateKnown, IOPMPowerFlags powerFlags ) { return kIOReturnUnsupported; } //********************************************************************************* // [public virtual] removePowerChild // // Called on a parent whose child is being removed by PMstop(). //********************************************************************************* IOReturn IOService::removePowerChild ( IOPowerConnection * theNub ) { IORegistryEntry * theChild; PM_ASSERT_IN_GATE(); OUR_PMLog( kPMLogRemoveChild, 0, 0 ); theNub->retain(); // detach nub from child theChild = theNub->copyChildEntry(gIOPowerPlane); if ( theChild ) { theNub->detachFromChild(theChild, gIOPowerPlane); theChild->release(); } // detach from the nub detachFromChild(theNub, gIOPowerPlane); // Are we awaiting an ack from this child? if ( theNub->getAwaitingAck() ) { // yes, pretend we got one theNub->setAwaitingAck(false); if (fHeadNotePendingAcks != 0 ) { // that's one fewer ack to worry about fHeadNotePendingAcks--; // is that the last? if ( fHeadNotePendingAcks == 0 ) { stop_ack_timer(); } } } theNub->release(); // Schedule a request to re-scan child desires and clamp bits. if (!fWillAdjustPowerState) { IOPMRequest * request; request = acquirePMRequest( this, kIOPMRequestTypeAdjustPowerState ); if (request) { submitPMRequest( request ); fWillAdjustPowerState = true; } } return IOPMNoErr; } //********************************************************************************* // [public virtual] registerPowerDriver // // A driver has called us volunteering to control power to our device. //********************************************************************************* IOReturn IOService::registerPowerDriver ( IOService * powerDriver, IOPMPowerState * powerStates, unsigned long numberOfStates ) { IOPMRequest * request; IOPMPowerState * powerStatesCopy = 0; if (!initialized) return IOPMNotYetInitialized; // Validate arguments. if (!powerStates || (numberOfStates < 2)) { OUR_PMLog(kPMLogControllingDriverErr5, numberOfStates, 0); return kIOReturnBadArgument; } if (!powerDriver) { OUR_PMLog(kPMLogControllingDriverErr4, 0, 0); return kIOReturnBadArgument; } if (powerStates[0].version != kIOPMPowerStateVersion1) { OUR_PMLog(kPMLogControllingDriverErr1, powerStates[0].version, 0); return kIOReturnBadArgument; } do { // Make a copy of the supplied power state array. powerStatesCopy = IONew(IOPMPowerState, numberOfStates); if (!powerStatesCopy) break; bcopy( powerStates, powerStatesCopy, sizeof(IOPMPowerState) * numberOfStates ); request = acquirePMRequest( this, kIOPMRequestTypeRegisterPowerDriver ); if (!request) break; powerDriver->retain(); request->fArg0 = (void *) powerDriver; request->fArg1 = (void *) powerStatesCopy; request->fArg2 = (void *) numberOfStates; submitPMRequest( request ); return kIOReturnSuccess; } while (false); if (powerStatesCopy) IODelete(powerStatesCopy, IOPMPowerState, numberOfStates); return kIOReturnNoMemory; } //********************************************************************************* // [private] handleRegisterPowerDriver //********************************************************************************* void IOService::handleRegisterPowerDriver ( IOPMRequest * request ) { IOService * powerDriver = (IOService *) request->fArg0; IOPMPowerState * powerStates = (IOPMPowerState *) request->fArg1; unsigned long numberOfStates = (unsigned long) request->fArg2; unsigned long i; IOService * root; PM_ASSERT_IN_GATE(); assert(powerStates); assert(powerDriver); assert(numberOfStates > 1); if ( !fNumberOfPowerStates ) { OUR_PMLog(kPMLogControllingDriver, (unsigned long) numberOfStates, (unsigned long) powerStates[0].version); fPowerStates = powerStates; fNumberOfPowerStates = numberOfStates; fControllingDriver = powerDriver; fCurrentCapabilityFlags = fPowerStates[0].capabilityFlags; // make a mask of all the character bits we know about fOutputPowerCharacterFlags = 0; for ( i = 0; i < numberOfStates; i++ ) { fOutputPowerCharacterFlags |= fPowerStates[i].outputPowerCharacter; } // Register powerDriver as interested, unless already done. // We don't want to register the default implementation since // it does nothing. One ramification of not always registering // is the one fewer retain count held. root = getPlatform()->getProvider(); assert(root); if (!root || ((OSMemberFunctionCast(void (*)(void), root, &IOService::powerStateDidChangeTo)) != ((OSMemberFunctionCast(void (*)(void), this, &IOService::powerStateDidChangeTo)))) || ((OSMemberFunctionCast(void (*)(void), root, &IOService::powerStateWillChangeTo)) != ((OSMemberFunctionCast(void (*)(void), this, &IOService::powerStateWillChangeTo))))) { if (fInterestedDrivers->findItem(powerDriver) == NULL) { PM_LOCK(); fInterestedDrivers->appendNewInformee(powerDriver); PM_UNLOCK(); } } if ( fNeedToBecomeUsable ) { fNeedToBecomeUsable = false; fDeviceDesire = fNumberOfPowerStates - 1; } if ( inPlane(gIOPowerPlane) && fParentsKnowState ) { unsigned long tempDesire; fMaxCapability = fControllingDriver->maxCapabilityForDomainState(fParentsCurrentPowerFlags); // initially change into the state we are already in tempDesire = fControllingDriver->initialPowerStateForDomainState(fParentsCurrentPowerFlags); computeDesiredState(tempDesire); changeState(); } } else { OUR_PMLog(kPMLogControllingDriverErr2, numberOfStates, 0); IODelete(powerStates, IOPMPowerState, numberOfStates); } powerDriver->release(); } //********************************************************************************* // [public virtual] registerInterestedDriver // // Add the caller to our list of interested drivers and return our current // power state. If we don't have a power-controlling driver yet, we will // call this interested driver again later when we do get a driver and find // out what the current power state of the device is. //********************************************************************************* IOPMPowerFlags IOService::registerInterestedDriver ( IOService * driver ) { IOPMRequest * request; bool signal; if (!initialized || !fInterestedDrivers) return IOPMNotPowerManaged; PM_LOCK(); signal = (!fInsertInterestSet && !fRemoveInterestSet); if (fInsertInterestSet == NULL) fInsertInterestSet = OSSet::withCapacity(4); if (fInsertInterestSet) fInsertInterestSet->setObject(driver); PM_UNLOCK(); if (signal) { request = acquirePMRequest( this, kIOPMRequestTypeInterestChanged ); if (request) submitPMRequest( request ); } // This return value cannot be trusted, but return a value // for those clients that care. OUR_PMLog(kPMLogInterestedDriver, kIOPMDeviceUsable, 2); return kIOPMDeviceUsable; } //********************************************************************************* // [public virtual] deRegisterInterestedDriver //********************************************************************************* IOReturn IOService::deRegisterInterestedDriver ( IOService * driver ) { IOPMinformeeList * list; IOPMinformee * item; IOPMRequest * request; bool signal; if (!initialized || !fInterestedDrivers) return IOPMNotPowerManaged; PM_LOCK(); signal = (!fRemoveInterestSet && !fInsertInterestSet); if (fRemoveInterestSet == NULL) fRemoveInterestSet = OSSet::withCapacity(4); if (fRemoveInterestSet) { fRemoveInterestSet->setObject(driver); list = fInterestedDrivers; item = list->findItem(driver); if (item && item->active) { item->active = false; } if (fDriverCallBusy) PM_DEBUG("%s::deRegisterInterestedDriver() driver call busy\n", getName()); } PM_UNLOCK(); if (signal) { request = acquirePMRequest( this, kIOPMRequestTypeInterestChanged ); if (request) submitPMRequest( request ); } return IOPMNoErr; } //********************************************************************************* // [private] handleInterestChanged // // Handle interest added or removed. //********************************************************************************* void IOService::handleInterestChanged( IOPMRequest * request ) { IOService * driver; IOPMinformee * informee; IOPMinformeeList * list = fInterestedDrivers; PM_LOCK(); if (fInsertInterestSet) { while ((driver = (IOService *) fInsertInterestSet->getAnyObject())) { if ((list->findItem(driver) == NULL) && (!fRemoveInterestSet || !fRemoveInterestSet->containsObject(driver))) { informee = list->appendNewInformee(driver); } fInsertInterestSet->removeObject(driver); } fInsertInterestSet->release(); fInsertInterestSet = 0; } if (fRemoveInterestSet) { while ((driver = (IOService *) fRemoveInterestSet->getAnyObject())) { informee = list->findItem(driver); if (informee) { if (fHeadNotePendingAcks && informee->timer) { informee->timer = 0; fHeadNotePendingAcks--; } list->removeFromList(driver); } fRemoveInterestSet->removeObject(driver); } fRemoveInterestSet->release(); fRemoveInterestSet = 0; } PM_UNLOCK(); } //********************************************************************************* // [public virtual] acknowledgePowerChange // // After we notified one of the interested drivers or a power-domain child // of an impending change in power, it has called to say it is now // prepared for the change. If this object is the last to // acknowledge this change, we take whatever action we have been waiting // for. // That may include acknowledging to our parent. In this case, we do it // last of all to insure that this doesn't cause the parent to call us some- // where else and alter data we are relying on here (like the very existance // of a "current change note".) //********************************************************************************* IOReturn IOService::acknowledgePowerChange ( IOService * whichObject ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; if (!whichObject) return kIOReturnBadArgument; request = acquirePMRequest( this, kIOPMRequestTypeAckPowerChange ); if (!request) { PM_ERROR("%s::%s no memory\n", getName(), __FUNCTION__); return kIOReturnNoMemory; } whichObject->retain(); request->fArg0 = whichObject; submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [private] handleAcknowledgePowerChange //********************************************************************************* bool IOService::handleAcknowledgePowerChange ( IOPMRequest * request ) { IOPMinformee * informee; unsigned long childPower = kIOPMUnknown; IOService * theChild; IOService * whichObject; bool all_acked = false; PM_ASSERT_IN_GATE(); whichObject = (IOService *) request->fArg0; assert(whichObject); // one of our interested drivers? informee = fInterestedDrivers->findItem( whichObject ); if ( informee == NULL ) { if ( !isChild(whichObject, gIOPowerPlane) ) { OUR_PMLog(kPMLogAcknowledgeErr1, 0, 0); goto no_err; } else { OUR_PMLog(kPMLogChildAcknowledge, fHeadNotePendingAcks, 0); } } else { OUR_PMLog(kPMLogDriverAcknowledge, fHeadNotePendingAcks, 0); } if ( fHeadNotePendingAcks != 0 ) { assert(fPowerStates != NULL); // yes, make sure we're expecting acks if ( informee != NULL ) { // it's an interested driver // make sure we're expecting this ack if ( informee->timer != 0 ) { #if LOG_SETPOWER_TIMES if (informee->timer > 0) { uint64_t nsec = computeTimeDeltaNS(&informee->startTime); if (nsec > LOG_SETPOWER_TIMES) PM_DEBUG("%s::powerState%sChangeTo(%p, %s, %lu -> %lu) async took %d ms\n", informee->whatObject->getName(), (fDriverCallReason == kDriverCallInformPreChange) ? "Will" : "Did", informee->whatObject, fName, fCurrentPowerState, fHeadNoteState, NS_TO_MS(nsec)); } #endif // mark it acked informee->timer = 0; // that's one fewer to worry about fHeadNotePendingAcks--; } else { // this driver has already acked OUR_PMLog(kPMLogAcknowledgeErr2, 0, 0); } } else { // it's a child // make sure we're expecting this ack if ( ((IOPowerConnection *)whichObject)->getAwaitingAck() ) { // that's one fewer to worry about fHeadNotePendingAcks--; ((IOPowerConnection *)whichObject)->setAwaitingAck(false); theChild = (IOService *)whichObject->copyChildEntry(gIOPowerPlane); if ( theChild ) { childPower = theChild->currentPowerConsumption(); theChild->release(); } if ( childPower == kIOPMUnknown ) { fPowerStates[fHeadNoteState].staticPower = kIOPMUnknown; } else { if ( fPowerStates[fHeadNoteState].staticPower != kIOPMUnknown ) { fPowerStates[fHeadNoteState].staticPower += childPower; } } } } if ( fHeadNotePendingAcks == 0 ) { // yes, stop the timer stop_ack_timer(); // and now we can continue all_acked = true; } } else { OUR_PMLog(kPMLogAcknowledgeErr3, 0, 0); // not expecting anybody to ack } no_err: if (whichObject) whichObject->release(); return all_acked; } //********************************************************************************* // [public virtual] acknowledgeSetPowerState // // After we instructed our controlling driver to change power states, // it has called to say it has finished doing so. // We continue to process the power state change. //********************************************************************************* IOReturn IOService::acknowledgeSetPowerState ( void ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; request = acquirePMRequest( this, kIOPMRequestTypeAckSetPowerState ); if (!request) { PM_ERROR("%s::%s no memory\n", getName(), __FUNCTION__); return kIOReturnNoMemory; } submitPMRequest( request ); return kIOReturnSuccess; } //********************************************************************************* // [private] adjustPowerState // // Child has signaled a change - child changed it's desire, new child added, // existing child removed. Adjust our power state accordingly. //********************************************************************************* void IOService::adjustPowerState( void ) { PM_ASSERT_IN_GATE(); if (inPlane(gIOPowerPlane)) { rebuildChildClampBits(); computeDesiredState(); if ( fControllingDriver && fParentsKnowState ) changeState(); } else { PM_DEBUG("[%s] %s: not in power tree\n", getName(), __FUNCTION__); return; } fWillAdjustPowerState = false; } //********************************************************************************* // [public deprecated] powerDomainWillChangeTo // // Called by the power-hierarchy parent notifying of a new power state // in the power domain. // We enqueue a parent power-change to our queue of power changes. // This may or may not cause us to change power, depending on what // kind of change is occuring in the domain. //********************************************************************************* IOReturn IOService::powerDomainWillChangeTo ( IOPMPowerFlags newPowerFlags, IOPowerConnection * whichParent ) { assert(false); return kIOReturnUnsupported; } //********************************************************************************* // [private] handlePowerDomainWillChangeTo //********************************************************************************* void IOService::handlePowerDomainWillChangeTo ( IOPMRequest * request ) { IOPMPowerFlags newPowerFlags = (IOPMPowerFlags) request->fArg0; IOPowerConnection * whichParent = (IOPowerConnection *) request->fArg1; bool powerWillDrop = (bool) request->fArg2; OSIterator * iter; OSObject * next; IOPowerConnection * connection; unsigned long newPowerState; IOPMPowerFlags combinedPowerFlags; bool savedParentsKnowState; IOReturn result = IOPMAckImplied; PM_ASSERT_IN_GATE(); OUR_PMLog(kPMLogWillChange, newPowerFlags, 0); if (!inPlane(gIOPowerPlane) || !whichParent || !whichParent->getAwaitingAck()) { PM_DEBUG("[%s] %s: not in power tree\n", getName(), __FUNCTION__); goto exit_no_ack; } savedParentsKnowState = fParentsKnowState; // Combine parents' power flags to determine our maximum state // within the new power domain combinedPowerFlags = 0; iter = getParentIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { if ( connection == whichParent ) combinedPowerFlags |= newPowerFlags; else combinedPowerFlags |= connection->parentCurrentPowerFlags(); } } iter->release(); } if ( fControllingDriver ) { newPowerState = fControllingDriver->maxCapabilityForDomainState( combinedPowerFlags); result = enqueuePowerChange( /* flags */ IOPMParentInitiated | IOPMDomainWillChange, /* power state */ newPowerState, /* domain state */ combinedPowerFlags, /* connection */ whichParent, /* parent state */ newPowerFlags); } // If parent is dropping power, immediately update the parent's // capability flags. Any future merging of parent(s) combined // power flags should account for this power drop. if (powerWillDrop) { setParentInfo(newPowerFlags, whichParent, true); } // Parent is expecting an ACK from us. If we did not embark on a state // transition, when enqueuePowerChang() returns IOPMAckImplied. We are // still required to issue an ACK to our parent. if (IOPMAckImplied == result) { IOService * parent; parent = (IOService *) whichParent->copyParentEntry(gIOPowerPlane); assert(parent); if ( parent ) { parent->acknowledgePowerChange( whichParent ); parent->release(); } } // If the parent registers it's power driver late, then this is the // first opportunity to tell our parent about our desire. if (!savedParentsKnowState && fParentsKnowState) { PM_TRACE("[%s] powerDomainWillChangeTo: parentsKnowState = true\n", getName()); ask_parent( fDesiredPowerState ); } exit_no_ack: // Drop the retain from notifyChild(). if (whichParent) whichParent->release(); } //********************************************************************************* // [public deprecated] powerDomainDidChangeTo // // Called by the power-hierarchy parent after the power state of the power domain // has settled at a new level. // We enqueue a parent power-change to our queue of power changes. // This may or may not cause us to change power, depending on what // kind of change is occuring in the domain. //********************************************************************************* IOReturn IOService::powerDomainDidChangeTo ( IOPMPowerFlags newPowerFlags, IOPowerConnection * whichParent ) { assert(false); return kIOReturnUnsupported; } //********************************************************************************* // [private] handlePowerDomainDidChangeTo //********************************************************************************* void IOService::handlePowerDomainDidChangeTo ( IOPMRequest * request ) { IOPMPowerFlags newPowerFlags = (IOPMPowerFlags) request->fArg0; IOPowerConnection * whichParent = (IOPowerConnection *) request->fArg1; unsigned long newPowerState; bool savedParentsKnowState; IOReturn result = IOPMAckImplied; PM_ASSERT_IN_GATE(); OUR_PMLog(kPMLogDidChange, newPowerFlags, 0); if (!inPlane(gIOPowerPlane) || !whichParent || !whichParent->getAwaitingAck()) { PM_DEBUG("[%s] %s: not in power tree\n", getName(), __FUNCTION__); goto exit_no_ack; } savedParentsKnowState = fParentsKnowState; setParentInfo(newPowerFlags, whichParent, true); if ( fControllingDriver ) { newPowerState = fControllingDriver->maxCapabilityForDomainState( fParentsCurrentPowerFlags); result = enqueuePowerChange( /* flags */ IOPMParentInitiated | IOPMDomainDidChange, /* power state */ newPowerState, /* domain state */ fParentsCurrentPowerFlags, /* connection */ whichParent, /* parent state */ 0); } // Parent is expecting an ACK from us. If we did not embark on a state // transition, when enqueuePowerChang() returns IOPMAckImplied. We are // still required to issue an ACK to our parent. if (IOPMAckImplied == result) { IOService * parent; parent = (IOService *) whichParent->copyParentEntry(gIOPowerPlane); assert(parent); if ( parent ) { parent->acknowledgePowerChange( whichParent ); parent->release(); } } // If the parent registers it's power driver late, then this is the // first opportunity to tell our parent about our desire. if (!savedParentsKnowState && fParentsKnowState) { PM_TRACE("[%s] powerDomainDidChangeTo: parentsKnowState = true\n", getName()); ask_parent( fDesiredPowerState ); } exit_no_ack: // Drop the retain from notifyChild(). if (whichParent) whichParent->release(); } //********************************************************************************* // [private] setParentInfo // // Set our connection data for one specific parent, and then combine all the parent // data together. //********************************************************************************* void IOService::setParentInfo ( IOPMPowerFlags newPowerFlags, IOPowerConnection * whichParent, bool knowsState ) { OSIterator * iter; OSObject * next; IOPowerConnection * conn; PM_ASSERT_IN_GATE(); // set our connection data whichParent->setParentCurrentPowerFlags(newPowerFlags); whichParent->setParentKnowsState(knowsState); // recompute our parent info fParentsCurrentPowerFlags = 0; fParentsKnowState = true; iter = getParentIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (conn = OSDynamicCast(IOPowerConnection, next)) ) { fParentsKnowState &= conn->parentKnowsState(); fParentsCurrentPowerFlags |= conn->parentCurrentPowerFlags(); } } iter->release(); } } //********************************************************************************* // [private] rebuildChildClampBits // // The ChildClamp bits (kIOPMChildClamp & kIOPMChildClamp2) in our capabilityFlags // indicate that one of our children (or grandchildren or great-grandchildren ...) // doesn't support idle or system sleep in its current state. Since we don't track // the origin of each bit, every time any child changes state we have to clear // these bits and rebuild them. //********************************************************************************* void IOService::rebuildChildClampBits ( void ) { unsigned long i; OSIterator * iter; OSObject * next; IOPowerConnection * connection; unsigned long powerState; // A child's desires has changed. We need to rebuild the child-clamp bits in // our power state array. Start by clearing the bits in each power state. for ( i = 0; i < fNumberOfPowerStates; i++ ) { fPowerStates[i].capabilityFlags &= ~(kIOPMChildClamp | kIOPMChildClamp2); } // Loop through the children. When we encounter the calling child, save the // computed state as this child's desire. And set the ChildClamp bits in any // of our states that some child has clamp on. iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { if (connection->getReadyFlag() == false) { PM_CONNECT("[%s] %s: connection not ready\n", getName(), __FUNCTION__); continue; } powerState = connection->getDesiredDomainState(); if (powerState < fNumberOfPowerStates) { if ( connection->getPreventIdleSleepFlag() ) fPowerStates[powerState].capabilityFlags |= kIOPMChildClamp; if ( connection->getPreventSystemSleepFlag() ) fPowerStates[powerState].capabilityFlags |= kIOPMChildClamp2; } } } iter->release(); } } //********************************************************************************* // [public virtual] requestPowerDomainState // // The child of a power domain calls it parent here to request power of a certain // character. //********************************************************************************* IOReturn IOService::requestPowerDomainState ( IOPMPowerFlags desiredState, IOPowerConnection * whichChild, unsigned long specification ) { unsigned long i; unsigned long computedState; unsigned long theDesiredState; IOService * child; IOPMRequest * childRequest; if (!initialized) return IOPMNotYetInitialized; if (gIOPMWorkLoop->onThread() == false) { PM_DEBUG("[%s] called requestPowerDomainState\n", getName()); return kIOReturnSuccess; } theDesiredState = desiredState & ~(kIOPMPreventIdleSleep | kIOPMPreventSystemSleep); OUR_PMLog(kPMLogRequestDomain, desiredState, specification); if (!isChild(whichChild, gIOPowerPlane)) return kIOReturnNotAttached; if (fControllingDriver == NULL || !fPowerStates) return IOPMNotYetInitialized; child = (IOService *) whichChild->getChildEntry(gIOPowerPlane); assert(child); switch (specification) { case IOPMLowestState: i = 0; while ( i < fNumberOfPowerStates ) { if ( ( fPowerStates[i].outputPowerCharacter & theDesiredState) == (theDesiredState & fOutputPowerCharacterFlags) ) { break; } i++; } if ( i >= fNumberOfPowerStates ) { return IOPMNoSuchState; } break; case IOPMNextLowerState: i = fCurrentPowerState - 1; while ( (int) i >= 0 ) { if ( ( fPowerStates[i].outputPowerCharacter & theDesiredState) == (theDesiredState & fOutputPowerCharacterFlags) ) { break; } i--; } if ( (int) i < 0 ) { return IOPMNoSuchState; } break; case IOPMHighestState: i = fNumberOfPowerStates; while ( (int) i >= 0 ) { i--; if ( ( fPowerStates[i].outputPowerCharacter & theDesiredState) == (theDesiredState & fOutputPowerCharacterFlags) ) { break; } } if ( (int) i < 0 ) { return IOPMNoSuchState; } break; case IOPMNextHigherState: i = fCurrentPowerState + 1; while ( i < fNumberOfPowerStates ) { if ( ( fPowerStates[i].outputPowerCharacter & theDesiredState) == (theDesiredState & fOutputPowerCharacterFlags) ) { break; } i++; } if ( i == fNumberOfPowerStates ) { return IOPMNoSuchState; } break; default: return IOPMBadSpecification; } computedState = i; // Clamp removed on the initial power request from a new child. if (fClampOn && !whichChild->childHasRequestedPower()) { PM_TRACE("[%s] %p power clamp removed (child = %p)\n", getName(), this, whichChild); fClampOn = false; fDeviceDesire = 0; } // Record the child's desires on the connection. #if SUPPORT_IDLE_CANCEL bool attemptCancel = ((kIOPMPreventIdleSleep & desiredState) && !whichChild->getPreventIdleSleepFlag()); #endif whichChild->setDesiredDomainState( computedState ); whichChild->setPreventIdleSleepFlag( desiredState & kIOPMPreventIdleSleep ); whichChild->setPreventSystemSleepFlag( desiredState & kIOPMPreventSystemSleep ); whichChild->setChildHasRequestedPower(); if (whichChild->getReadyFlag() == false) return IOPMNoErr; // Issue a ping for us to re-evaluate all children desires and // possibly change power state. if (!fWillAdjustPowerState && !fDeviceOverrides) { childRequest = acquirePMRequest( this, kIOPMRequestTypeAdjustPowerState ); if (childRequest) { submitPMRequest( childRequest ); fWillAdjustPowerState = true; } } #if SUPPORT_IDLE_CANCEL if (attemptCancel) { childRequest = acquirePMRequest( this, kIOPMRequestTypeIdleCancel ); if (childRequest) { submitPMRequest( childRequest ); } } #endif return IOPMNoErr; } //********************************************************************************* // [public virtual] temporaryPowerClampOn // // A power domain wants to clamp its power on till it has children which // will thendetermine the power domain state. // // We enter the highest state until addPowerChild is called. //********************************************************************************* IOReturn IOService::temporaryPowerClampOn ( void ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; request = acquirePMRequest( this, kIOPMRequestTypeTemporaryPowerClamp ); if (!request) return kIOReturnNoMemory; submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [public virtual] makeUsable // // Some client of our device is asking that we become usable. Although // this has not come from a subclassed device object, treat it exactly // as if it had. In this way, subsequent requests for lower power from // a subclassed device object will pre-empt this request. // // We treat this as a subclass object request to switch to the // highest power state. //********************************************************************************* IOReturn IOService::makeUsable ( void ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; OUR_PMLog(kPMLogMakeUsable, 0, 0); request = acquirePMRequest( this, kIOPMRequestTypeMakeUsable ); if (!request) return kIOReturnNoMemory; submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [private] handleMakeUsable // // Handle a request to become usable. //********************************************************************************* void IOService::handleMakeUsable ( IOPMRequest * request ) { PM_ASSERT_IN_GATE(); if ( fControllingDriver ) { fDeviceDesire = fNumberOfPowerStates - 1; computeDesiredState(); if ( inPlane(gIOPowerPlane) && fParentsKnowState ) { changeState(); } } else { fNeedToBecomeUsable = true; } } //********************************************************************************* // [public virtual] currentCapability //********************************************************************************* IOPMPowerFlags IOService::currentCapability ( void ) { if (!initialized) return IOPMNotPowerManaged; return fCurrentCapabilityFlags; } //********************************************************************************* // [public virtual] changePowerStateTo // // For some reason, our power-controlling driver has decided it needs to change // power state. We enqueue the power change so that appropriate parties // will be notified, and then we will instruct the driver to make the change. //********************************************************************************* IOReturn IOService::changePowerStateTo ( unsigned long ordinal ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; OUR_PMLog(kPMLogChangeStateTo, ordinal, 0); request = acquirePMRequest( this, kIOPMRequestTypeChangePowerStateTo ); if (!request) return kIOReturnNoMemory; request->fArg0 = (void *) ordinal; request->fArg1 = (void *) false; // Avoid needless downwards power transitions by clamping power in // computeDesiredState() until the delayed request is processed. if (gIOPMWorkLoop->inGate()) { fTempClampPowerState = max(fTempClampPowerState, ordinal); fTempClampCount++; request->fArg1 = (void *) true; } submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [private] handleChangePowerStateTo //********************************************************************************* void IOService::handleChangePowerStateTo ( IOPMRequest * request ) { unsigned long ordinal = (unsigned long) request->fArg0; PM_ASSERT_IN_GATE(); if (request->fArg1) { assert(fTempClampCount != 0); if (fTempClampCount) fTempClampCount--; if (!fTempClampCount) fTempClampPowerState = 0; } if ( fControllingDriver && (ordinal < fNumberOfPowerStates)) { fDriverDesire = ordinal; computeDesiredState(); if ( inPlane(gIOPowerPlane) && fParentsKnowState ) { changeState(); } } } //********************************************************************************* // [public virtual] changePowerStateToPriv // // For some reason, a subclassed device object has decided it needs to change // power state. We enqueue the power change so that appropriate parties // will be notified, and then we will instruct the driver to make the change. //********************************************************************************* IOReturn IOService::changePowerStateToPriv ( unsigned long ordinal ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; request = acquirePMRequest( this, kIOPMRequestTypeChangePowerStateToPriv ); if (!request) return kIOReturnNoMemory; request->fArg0 = (void *) ordinal; request->fArg1 = (void *) false; // Avoid needless downwards power transitions by clamping power in // computeDesiredState() until the delayed request is processed. if (gIOPMWorkLoop->inGate()) { fTempClampPowerState = max(fTempClampPowerState, ordinal); fTempClampCount++; request->fArg1 = (void *) true; } submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [private] handleChangePowerStateToPriv //********************************************************************************* void IOService::handleChangePowerStateToPriv ( IOPMRequest * request ) { unsigned long ordinal = (unsigned long) request->fArg0; PM_ASSERT_IN_GATE(); OUR_PMLog(kPMLogChangeStateToPriv, ordinal, 0); if (request->fArg1) { assert(fTempClampCount != 0); if (fTempClampCount) fTempClampCount--; if (!fTempClampCount) fTempClampPowerState = 0; } if ( fControllingDriver && (ordinal < fNumberOfPowerStates)) { fDeviceDesire = ordinal; computeDesiredState(); if ( inPlane(gIOPowerPlane) && fParentsKnowState ) { changeState(); } } } //********************************************************************************* // [private] computeDesiredState //********************************************************************************* void IOService::computeDesiredState ( unsigned long tempDesire ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; unsigned long newDesiredState = 0; unsigned long childDesire = 0; unsigned long deviceDesire; if (tempDesire) deviceDesire = tempDesire; else deviceDesire = fDeviceDesire; // If clamp is on, always override deviceDesire to max. if (fClampOn && fNumberOfPowerStates) deviceDesire = fNumberOfPowerStates - 1; // Compute the maximum of our children's desires, // our controlling driver's desire, and the subclass device's desire. if ( !fDeviceOverrides ) { iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { if (connection->getReadyFlag() == false) { PM_CONNECT("[%s] %s: connection not ready\n", getName(), __FUNCTION__); continue; } if (connection->getDesiredDomainState() > childDesire) childDesire = connection->getDesiredDomainState(); } } iter->release(); } fChildrenDesire = childDesire; newDesiredState = max(childDesire, fDriverDesire); } newDesiredState = max(deviceDesire, newDesiredState); if (fTempClampCount && (fTempClampPowerState < fNumberOfPowerStates)) newDesiredState = max(fTempClampPowerState, newDesiredState); fDesiredPowerState = newDesiredState; // Limit check against number of power states. if (fNumberOfPowerStates == 0) fDesiredPowerState = 0; else if (fDesiredPowerState >= fNumberOfPowerStates) fDesiredPowerState = fNumberOfPowerStates - 1; // Restart idle timer if stopped and deviceDesire has increased. if (fDeviceDesire && fActivityTimerStopped) { fActivityTimerStopped = false; start_PM_idle_timer(); } // Invalidate cached tickle power state when desires change, and not // due to a tickle request. This invalidation must occur before the // power state change to minimize races. We want to err on the side // of servicing more activity tickles rather than dropping one when // the device is in a low power state. if (fPMRequest && (fPMRequest->getType() != kIOPMRequestTypeActivityTickle) && (fActivityTicklePowerState != -1)) { IOLockLock(fActivityLock); fActivityTicklePowerState = -1; IOLockUnlock(fActivityLock); } PM_TRACE(" NewState %ld, Child %ld, Driver %ld, Device %ld, Clamp %d (%ld)\n", fDesiredPowerState, childDesire, fDriverDesire, deviceDesire, fClampOn, fTempClampCount ? fTempClampPowerState : 0); } //********************************************************************************* // [private] changeState // // A subclass object, our controlling driver, or a power domain child // has asked for a different power state. Here we compute what new // state we should enter and enqueue the change (or start it). //********************************************************************************* IOReturn IOService::changeState ( void ) { IOReturn result; PM_ASSERT_IN_GATE(); assert(inPlane(gIOPowerPlane)); assert(fParentsKnowState); assert(fControllingDriver); result = enqueuePowerChange( /* flags */ IOPMWeInitiated, /* power state */ fDesiredPowerState, /* domain state */ 0, /* connection */ 0, /* parent state */ 0); return result; } //********************************************************************************* // [public virtual] currentPowerConsumption // //********************************************************************************* unsigned long IOService::currentPowerConsumption ( void ) { if (!initialized) return kIOPMUnknown; return fCurrentPowerConsumption; } //********************************************************************************* // [public virtual] getPMworkloop //********************************************************************************* IOWorkLoop * IOService::getPMworkloop ( void ) { return gIOPMWorkLoop; } //********************************************************************************* // [public virtual] activityTickle // // The tickle with parameter kIOPMSuperclassPolicy1 causes the activity // flag to be set, and the device state checked. If the device has been // powered down, it is powered up again. // The tickle with parameter kIOPMSubclassPolicy is ignored here and // should be intercepted by a subclass. //********************************************************************************* bool IOService::activityTickle ( unsigned long type, unsigned long stateNumber ) { IOPMRequest * request; bool noPowerChange = true; if ( initialized && stateNumber && (type == kIOPMSuperclassPolicy1) ) { IOLockLock(fActivityLock); // Record device activity for the idle timer handler. fDeviceActive = true; clock_get_uptime(&fDeviceActiveTimestamp); // Record the last tickle power state. // This helps to filter out redundant tickles as // this function may be called from the data path. if (fActivityTicklePowerState < (long)stateNumber) { fActivityTicklePowerState = stateNumber; noPowerChange = false; request = acquirePMRequest( this, kIOPMRequestTypeActivityTickle ); if (request) { request->fArg0 = (void *) stateNumber; // power state request->fArg1 = (void *) true; // power rise submitPMRequest(request); } } IOLockUnlock(fActivityLock); } // Returns false if the activityTickle might cause a transition to a // higher powered state, true otherwise. return noPowerChange; } //********************************************************************************* // [public virtual] setIdleTimerPeriod // // A subclass policy-maker is going to use our standard idleness // detection service. Make a command queue and an idle timer and // connect them to the power management workloop. Finally, // start the timer. //********************************************************************************* IOReturn IOService::setIdleTimerPeriod ( unsigned long period ) { IOWorkLoop * wl = getPMworkloop(); if (!initialized || !wl) return IOPMNotYetInitialized; OUR_PMLog(PMsetIdleTimerPeriod, period, 0); fIdleTimerPeriod = period; if ( period > 0 ) { // make the timer event if ( fIdleTimerEventSource == NULL ) { IOTimerEventSource * timerSrc; timerSrc = IOTimerEventSource::timerEventSource( this, PM_idle_timer_expired); if (timerSrc && (wl->addEventSource(timerSrc) != kIOReturnSuccess)) { timerSrc->release(); timerSrc = 0; } fIdleTimerEventSource = timerSrc; } start_PM_idle_timer(); } return IOPMNoErr; } //****************************************************************************** // [public virtual] nextIdleTimeout // // Returns how many "seconds from now" the device should idle into its // next lowest power state. //****************************************************************************** SInt32 IOService::nextIdleTimeout( AbsoluteTime currentTime, AbsoluteTime lastActivity, unsigned int powerState) { AbsoluteTime delta; UInt64 delta_ns; SInt32 delta_secs; SInt32 delay_secs; // Calculate time difference using funky macro from clock.h. delta = currentTime; SUB_ABSOLUTETIME(&delta, &lastActivity); // Figure it in seconds. absolutetime_to_nanoseconds(delta, &delta_ns); delta_secs = (SInt32)(delta_ns / NSEC_PER_SEC); // Be paranoid about delta somehow exceeding timer period. if (delta_secs < (int) fIdleTimerPeriod ) delay_secs = (int) fIdleTimerPeriod - delta_secs; else delay_secs = (int) fIdleTimerPeriod; return (SInt32)delay_secs; } //****************************************************************************** // [public virtual] start_PM_idle_timer // // The parameter is a pointer to us. Use it to call our timeout method. //****************************************************************************** void IOService::start_PM_idle_timer ( void ) { static const int maxTimeout = 100000; static const int minTimeout = 1; AbsoluteTime uptime; SInt32 idle_in = 0; if (!initialized || !fIdleTimerEventSource) return; IOLockLock(fActivityLock); clock_get_uptime(&uptime); // Subclasses may modify idle sleep algorithm idle_in = nextIdleTimeout(uptime, fDeviceActiveTimestamp, fCurrentPowerState); // Check for out-of range responses if (idle_in > maxTimeout) { // use standard implementation idle_in = IOService::nextIdleTimeout(uptime, fDeviceActiveTimestamp, fCurrentPowerState); } else if (idle_in < minTimeout) { idle_in = fIdleTimerPeriod; } IOLockUnlock(fActivityLock); fIdleTimerEventSource->setTimeout(idle_in, NSEC_PER_SEC); } //********************************************************************************* // [private] PM_idle_timer_expired // // The parameter is a pointer to us. Use it to call our timeout method. //********************************************************************************* void PM_idle_timer_expired ( OSObject * ourSelves, IOTimerEventSource * ) { ((IOService *)ourSelves)->PM_idle_timer_expiration(); } //********************************************************************************* // [public virtual] PM_idle_timer_expiration // // The idle timer has expired. If there has been activity since the last // expiration, just restart the timer and return. If there has not been // activity, switch to the next lower power state and restart the timer. //********************************************************************************* void IOService::PM_idle_timer_expiration ( void ) { IOPMRequest * request; bool restartTimer = true; if ( !initialized || !fIdleTimerPeriod ) return; IOLockLock(fActivityLock); // Check for device activity (tickles) over last timer period. if (fDeviceActive) { // Device was active - do not drop power, restart timer. fDeviceActive = false; } else { // No device activity - drop power state by one level. // Decrement the cached tickle power state when possible. // This value may be (-1) before activityTickle() is called, // but the power drop request must be issued regardless. if (fActivityTicklePowerState > 0) { fActivityTicklePowerState--; } request = acquirePMRequest( this, kIOPMRequestTypeActivityTickle ); if (request) { request->fArg0 = (void *) 0; // power state (irrelevant) request->fArg1 = (void *) false; // power drop submitPMRequest( request ); // Do not restart timer until after the tickle request has been // processed. restartTimer = false; } } IOLockUnlock(fActivityLock); if (restartTimer) start_PM_idle_timer(); } //********************************************************************************* // [public virtual] command_received // //********************************************************************************* void IOService::command_received ( void *statePtr , void *, void * , void * ) { } //********************************************************************************* // [public virtual] setAggressiveness // // Pass on the input parameters to all power domain children. All those which are // power domains will pass it on to their children, etc. //********************************************************************************* IOReturn IOService::setAggressiveness ( unsigned long type, unsigned long newLevel ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; IOService * child; if (!initialized) return IOPMNotYetInitialized; if (getPMRootDomain() == this) OUR_PMLog(kPMLogSetAggressiveness, type, newLevel); if ( type <= kMaxType ) { fAggressivenessValue[type] = newLevel; fAggressivenessValid[type] = true; } iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { if (connection->getReadyFlag() == false) { PM_CONNECT("[%s] %s: connection not ready\n", getName(), __FUNCTION__); continue; } child = ((IOService *)(connection->copyChildEntry(gIOPowerPlane))); if ( child ) { child->setAggressiveness(type, newLevel); child->release(); } } } iter->release(); } return IOPMNoErr; } //********************************************************************************* // [public virtual] getAggressiveness // // Called by the user client. //********************************************************************************* IOReturn IOService::getAggressiveness ( unsigned long type, unsigned long * currentLevel ) { if ( !initialized || (type > kMaxType) ) return kIOReturnBadArgument; if ( !fAggressivenessValid[type] ) return kIOReturnInvalid; *currentLevel = fAggressivenessValue[type]; return kIOReturnSuccess; } //********************************************************************************* // [public] getPowerState // //********************************************************************************* UInt32 IOService::getPowerState ( void ) { if (!initialized) return 0; return fCurrentPowerState; } //********************************************************************************* // [public virtual] systemWake // // Pass this to all power domain children. All those which are // power domains will pass it on to their children, etc. //********************************************************************************* IOReturn IOService::systemWake ( void ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; IOService * theChild; OUR_PMLog(kPMLogSystemWake, 0, 0); iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { if (connection->getReadyFlag() == false) { PM_CONNECT("[%s] %s: connection not ready\n", getName(), __FUNCTION__); continue; } theChild = (IOService *)connection->copyChildEntry(gIOPowerPlane); if ( theChild ) { theChild->systemWake(); theChild->release(); } } } iter->release(); } if ( fControllingDriver != NULL ) { if ( fControllingDriver->didYouWakeSystem() ) { makeUsable(); } } return IOPMNoErr; } //********************************************************************************* // [public virtual] temperatureCriticalForZone //********************************************************************************* IOReturn IOService::temperatureCriticalForZone ( IOService * whichZone ) { IOService * theParent; IOService * theNub; OUR_PMLog(kPMLogCriticalTemp, 0, 0); if ( inPlane(gIOPowerPlane) && !fWeAreRoot ) { theNub = (IOService *)copyParentEntry(gIOPowerPlane); if ( theNub ) { theParent = (IOService *)theNub->copyParentEntry(gIOPowerPlane); theNub->release(); if ( theParent ) { theParent->temperatureCriticalForZone(whichZone); theParent->release(); } } } return IOPMNoErr; } //********************************************************************************* // [public] powerOverrideOnPriv //********************************************************************************* IOReturn IOService::powerOverrideOnPriv ( void ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; if (gIOPMWorkLoop->inGate()) { fDeviceOverrides = true; return IOPMNoErr; } request = acquirePMRequest( this, kIOPMRequestTypePowerOverrideOnPriv ); if (!request) return kIOReturnNoMemory; submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [public] powerOverrideOffPriv //********************************************************************************* IOReturn IOService::powerOverrideOffPriv ( void ) { IOPMRequest * request; if (!initialized) return IOPMNotYetInitialized; if (gIOPMWorkLoop->inGate()) { fDeviceOverrides = false; return IOPMNoErr; } request = acquirePMRequest( this, kIOPMRequestTypePowerOverrideOffPriv ); if (!request) return kIOReturnNoMemory; submitPMRequest( request ); return IOPMNoErr; } //********************************************************************************* // [private] handlePowerOverrideChanged //********************************************************************************* void IOService::handlePowerOverrideChanged ( IOPMRequest * request ) { PM_ASSERT_IN_GATE(); if (request->getType() == kIOPMRequestTypePowerOverrideOnPriv) { OUR_PMLog(kPMLogOverrideOn, 0, 0); fDeviceOverrides = true; } else { OUR_PMLog(kPMLogOverrideOff, 0, 0); fDeviceOverrides = false; } if (fControllingDriver && inPlane(gIOPowerPlane) && fParentsKnowState) { computeDesiredState(); changeState(); } } //********************************************************************************* // [private] enqueuePowerChange //********************************************************************************* IOReturn IOService::enqueuePowerChange ( unsigned long flags, unsigned long whatStateOrdinal, unsigned long domainState, IOPowerConnection * whichParent, unsigned long singleParentState ) { changeNoteItem changeNote; IOPMPowerState * powerStatePtr; PM_ASSERT_IN_GATE(); assert( fMachineState == kIOPM_Finished ); assert( whatStateOrdinal < fNumberOfPowerStates ); if (whatStateOrdinal >= fNumberOfPowerStates) return IOPMAckImplied; powerStatePtr = &fPowerStates[whatStateOrdinal]; // Initialize the change note changeNote.flags = flags; changeNote.newStateNumber = whatStateOrdinal; changeNote.outputPowerCharacter = powerStatePtr->outputPowerCharacter; changeNote.inputPowerRequirement = powerStatePtr->inputPowerRequirement; changeNote.capabilityFlags = powerStatePtr->capabilityFlags; changeNote.parent = NULL; if (flags & IOPMParentInitiated ) { changeNote.domainState = domainState; changeNote.parent = whichParent; changeNote.singleParentState = singleParentState; } if (flags & IOPMWeInitiated ) { start_our_change(&changeNote); return 0; } else { return start_parent_change(&changeNote); } } //********************************************************************************* // [private] notifyInterestedDrivers //********************************************************************************* bool IOService::notifyInterestedDrivers ( void ) { IOPMinformee * informee; IOPMinformeeList * list = fInterestedDrivers; DriverCallParam * param; IOItemCount count; PM_ASSERT_IN_GATE(); assert( fDriverCallBusy == false ); assert( fDriverCallParamCount == 0 ); assert( fHeadNotePendingAcks == 0 ); count = list->numberOfItems(); if (!count) goto done; // no interested drivers // Allocate an array of interested drivers and their return values // for the callout thread. Everything else is still "owned" by the // PM work loop, which can run to process acknowledgePowerChange() // responses. param = (DriverCallParam *) fDriverCallParamPtr; if (count > fDriverCallParamSlots) { if (fDriverCallParamSlots) { assert(fDriverCallParamPtr); IODelete(fDriverCallParamPtr, DriverCallParam, fDriverCallParamSlots); fDriverCallParamPtr = 0; fDriverCallParamSlots = 0; } param = IONew(DriverCallParam, count); if (!param) goto done; // no memory fDriverCallParamPtr = (void *) param; fDriverCallParamSlots = count; } informee = list->firstInList(); assert(informee); for (IOItemCount i = 0; i < count; i++) { informee->timer = -1; param[i].Target = informee; informee->retain(); informee = list->nextInList( informee ); } fDriverCallParamCount = count; fHeadNotePendingAcks = count; // Machine state will be blocked pending callout thread completion. PM_LOCK(); fDriverCallBusy = true; PM_UNLOCK(); thread_call_enter( fDriverCallEntry ); return true; done: // no interested drivers or did not schedule callout thread due to error. return false; } //********************************************************************************* // [private] notifyInterestedDriversDone //********************************************************************************* void IOService::notifyInterestedDriversDone ( void ) { IOPMinformee * informee; IOItemCount count; DriverCallParam * param; IOReturn result; PM_ASSERT_IN_GATE(); param = (DriverCallParam *) fDriverCallParamPtr; count = fDriverCallParamCount; assert( fDriverCallBusy == false ); assert( fMachineState == kIOPM_DriverThreadCallDone ); if (param && count) { for (IOItemCount i = 0; i < count; i++, param++) { informee = (IOPMinformee *) param->Target; result = param->Result; if ((result == IOPMAckImplied) || (result < 0)) { // child return IOPMAckImplied informee->timer = 0; fHeadNotePendingAcks--; } else if (informee->timer) { assert(informee->timer == -1); // Driver has not acked, and has returned a positive result. // Enforce a minimum permissible timeout value. // Make the min value large enough so timeout is less likely // to occur if a driver misinterpreted that the return value // should be in microsecond units. And make it large enough // to be noticeable if a driver neglects to ack. if (result < kMinAckTimeoutTicks) result = kMinAckTimeoutTicks; informee->timer = (result / (ACK_TIMER_PERIOD / ns_per_us)) + 1; } // else, child has already acked or driver has removed interest, // and head_note_pendingAcks decremented. // informee may have been removed from the interested drivers list, // thus the informee must be retained across the callout. informee->release(); } fDriverCallParamCount = 0; if ( fHeadNotePendingAcks ) { OUR_PMLog(kPMLogStartAckTimer, 0, 0); start_ack_timer(); } } // Hop back to original machine state path (from notifyAll) fMachineState = fNextMachineState; notifyChildren(); } //********************************************************************************* // [private] notifyChildren //********************************************************************************* void IOService::notifyChildren ( void ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; OSArray * children = 0; if (fStrictTreeOrder) children = OSArray::withCapacity(8); // Sum child power consumption in notifyChild() fPowerStates[fHeadNoteState].staticPower = 0; iter = getChildIterator(gIOPowerPlane); if ( iter ) { while ((next = iter->getNextObject())) { if ((connection = OSDynamicCast(IOPowerConnection, next))) { if (connection->getReadyFlag() == false) { PM_CONNECT("[%s] %s: connection not ready\n", getName(), __FUNCTION__); continue; } if (children) children->setObject( connection ); else notifyChild( connection, fDriverCallReason == kDriverCallInformPreChange ); } } iter->release(); } if (children) { if (children->getCount() == 0) { children->release(); children = 0; } else { assert(fNotifyChildArray == 0); fNotifyChildArray = children; fNextMachineState = fMachineState; fMachineState = kIOPM_NotifyChildrenDone; } } } //********************************************************************************* // [private] notifyChildrenDone //********************************************************************************* void IOService::notifyChildrenDone ( void ) { PM_ASSERT_IN_GATE(); assert(fNotifyChildArray); assert(fMachineState == kIOPM_NotifyChildrenDone); // Interested drivers have all acked (if any), ack timer stopped. // Notify one child, wait for it's ack, then repeat for next child. // This is a workaround for some drivers with multiple instances at // the same branch in the power tree, but the driver is slow to power // up unless the tree ordering is observed. Problem observed only on // system wake, not on system sleep. // // We have the ability to power off in reverse child index order. // That works nicely on some machines, but not on all HW configs. if (fNotifyChildArray->getCount()) { IOPowerConnection * connection; connection = (IOPowerConnection *) fNotifyChildArray->getObject(0); fNotifyChildArray->removeObject(0); notifyChild( connection, fDriverCallReason == kDriverCallInformPreChange ); } else { fNotifyChildArray->release(); fNotifyChildArray = 0; fMachineState = fNextMachineState; } } //********************************************************************************* // [private] notifyAll //********************************************************************************* IOReturn IOService::notifyAll ( bool is_prechange ) { // Save the next machine_state to be restored by notifyInterestedDriversDone() PM_ASSERT_IN_GATE(); fNextMachineState = fMachineState; fMachineState = kIOPM_DriverThreadCallDone; fDriverCallReason = is_prechange ? kDriverCallInformPreChange : kDriverCallInformPostChange; if (!notifyInterestedDrivers()) notifyInterestedDriversDone(); return IOPMWillAckLater; } //********************************************************************************* // [private, static] pmDriverCallout // // Thread call context //********************************************************************************* IOReturn IOService::actionDriverCalloutDone ( OSObject * target, void * arg0, void * arg1, void * arg2, void * arg3 ) { IOServicePM * pwrMgt = (IOServicePM *) arg0; PM_LOCK(); fDriverCallBusy = false; PM_UNLOCK(); if (gIOPMReplyQueue) gIOPMReplyQueue->signalWorkAvailable(); return kIOReturnSuccess; } void IOService::pmDriverCallout ( IOService * from ) { assert(from); switch (from->fDriverCallReason) { case kDriverCallSetPowerState: from->driverSetPowerState(); break; case kDriverCallInformPreChange: case kDriverCallInformPostChange: from->driverInformPowerChange(); break; default: IOPanic("IOService::pmDriverCallout bad machine state"); } gIOPMWorkLoop->runAction(actionDriverCalloutDone, /* target */ from, /* arg0 */ (void *) from->pwrMgt ); } //********************************************************************************* // [private] driverSetPowerState // // Thread call context //********************************************************************************* void IOService::driverSetPowerState ( void ) { IOService * driver; unsigned long powerState; DriverCallParam * param; IOReturn result; AbsoluteTime end; assert( fDriverCallBusy ); param = (DriverCallParam *) fDriverCallParamPtr; assert( param ); assert( fDriverCallParamCount == 1 ); driver = fControllingDriver; powerState = fHeadNoteState; if (!fWillPMStop) { OUR_PMLog( kPMLogProgramHardware, (UInt32) this, powerState); clock_get_uptime(&fDriverCallStartTime); result = driver->setPowerState( powerState, this ); clock_get_uptime(&end); OUR_PMLog((UInt32) -kPMLogProgramHardware, (UInt32) this, (UInt32) result); #if LOG_SETPOWER_TIMES if ((result == IOPMAckImplied) || (result < 0)) { uint64_t nsec; SUB_ABSOLUTETIME(&end, &fDriverCallStartTime); absolutetime_to_nanoseconds(end, &nsec); if (nsec > LOG_SETPOWER_TIMES) PM_DEBUG("%s::setPowerState(%p, %lu -> %lu) took %d ms\n", fName, this, fCurrentPowerState, powerState, NS_TO_MS(nsec)); } #endif } else result = kIOPMAckImplied; param->Result = result; } //********************************************************************************* // [private] driverInformPowerChange // // Thread call context //********************************************************************************* void IOService::driverInformPowerChange ( void ) { IOItemCount count; IOPMinformee * informee; IOService * driver; IOReturn result; IOPMPowerFlags powerFlags; unsigned long powerState; DriverCallParam * param; AbsoluteTime end; assert( fDriverCallBusy ); param = (DriverCallParam *) fDriverCallParamPtr; count = fDriverCallParamCount; assert( count && param ); powerFlags = fHeadNoteCapabilityFlags; powerState = fHeadNoteState; for (IOItemCount i = 0; i < count; i++) { informee = (IOPMinformee *) param->Target; driver = informee->whatObject; if (!fWillPMStop && informee->active) { if (fDriverCallReason == kDriverCallInformPreChange) { OUR_PMLog(kPMLogInformDriverPreChange, (UInt32) this, powerState); clock_get_uptime(&informee->startTime); result = driver->powerStateWillChangeTo(powerFlags, powerState, this); clock_get_uptime(&end); OUR_PMLog((UInt32)-kPMLogInformDriverPreChange, (UInt32) this, result); } else { OUR_PMLog(kPMLogInformDriverPostChange, (UInt32) this, powerState); clock_get_uptime(&informee->startTime); result = driver->powerStateDidChangeTo(powerFlags, powerState, this); clock_get_uptime(&end); OUR_PMLog((UInt32)-kPMLogInformDriverPostChange, (UInt32) this, result); } #if LOG_SETPOWER_TIMES if ((result == IOPMAckImplied) || (result < 0)) { uint64_t nsec; SUB_ABSOLUTETIME(&end, &informee->startTime); absolutetime_to_nanoseconds(end, &nsec); if (nsec > LOG_SETPOWER_TIMES) PM_DEBUG("%s::powerState%sChangeTo(%p, %s, %lu -> %lu) took %d ms\n", driver->getName(), (fDriverCallReason == kDriverCallInformPreChange) ? "Will" : "Did", driver, fName, fCurrentPowerState, powerState, NS_TO_MS(nsec)); } #endif } else result = kIOPMAckImplied; param->Result = result; param++; } } //********************************************************************************* // [private] notifyChild // // Notify a power domain child of an upcoming power change. // If the object acknowledges the current change, we return TRUE. //********************************************************************************* bool IOService::notifyChild ( IOPowerConnection * theNub, bool is_prechange ) { IOReturn k = IOPMAckImplied; unsigned long childPower; IOService * theChild; IOPMRequest * childRequest; int requestType; PM_ASSERT_IN_GATE(); theChild = (IOService *)(theNub->copyChildEntry(gIOPowerPlane)); if (!theChild) { assert(false); return true; } // Unless the child handles the notification immediately and returns // kIOPMAckImplied, we'll be awaiting their acknowledgement later. fHeadNotePendingAcks++; theNub->setAwaitingAck(true); requestType = is_prechange ? kIOPMRequestTypePowerDomainWillChange : kIOPMRequestTypePowerDomainDidChange; childRequest = acquirePMRequest( theChild, requestType ); if (childRequest) { theNub->retain(); childRequest->fArg0 = (void *) fHeadNoteOutputFlags; childRequest->fArg1 = (void *) theNub; childRequest->fArg2 = (void *) (fHeadNoteState < fCurrentPowerState); theChild->submitPMRequest( childRequest ); k = IOPMWillAckLater; } else { k = IOPMAckImplied; fHeadNotePendingAcks--; theNub->setAwaitingAck(false); childPower = theChild->currentPowerConsumption(); if ( childPower == kIOPMUnknown ) { fPowerStates[fHeadNoteState].staticPower = kIOPMUnknown; } else { if ( fPowerStates[fHeadNoteState].staticPower != kIOPMUnknown ) { fPowerStates[fHeadNoteState].staticPower += childPower; } } } theChild->release(); return (k == IOPMAckImplied); } //********************************************************************************* // [private] OurChangeTellClientsPowerDown // // All registered applications and kernel clients have positively acknowledged our // intention of lowering power. Here we notify them all that we will definitely // lower the power. If we don't have to wait for any of them to acknowledge, we // carry on by notifying interested drivers. Otherwise, we do wait. //********************************************************************************* void IOService::OurChangeTellClientsPowerDown ( void ) { fMachineState = kIOPM_OurChangeTellPriorityClientsPowerDown; tellChangeDown1(fHeadNoteState); } //********************************************************************************* // [private] OurChangeTellPriorityClientsPowerDown // // All registered applications and kernel clients have positively acknowledged our // intention of lowering power. Here we notify "priority" clients that we are // lowering power. If we don't have to wait for any of them to acknowledge, we // carry on by notifying interested drivers. Otherwise, we do wait. //********************************************************************************* void IOService::OurChangeTellPriorityClientsPowerDown ( void ) { fMachineState = kIOPM_OurChangeNotifyInterestedDriversWillChange; tellChangeDown2(fHeadNoteState); } //********************************************************************************* // [private] OurChangeNotifyInterestedDriversWillChange // // All registered applications and kernel clients have acknowledged our notification // that we are lowering power. Here we notify interested drivers. If we don't have // to wait for any of them to acknowledge, we instruct our power driver to make the // change. Otherwise, we do wait. //********************************************************************************* void IOService::OurChangeNotifyInterestedDriversWillChange ( void ) { fMachineState = kIOPM_OurChangeSetPowerState; notifyAll( true ); } //********************************************************************************* // [private] OurChangeSetPowerState // // All interested drivers have acknowledged our pre-change notification of a power // change we initiated. Here we instruct our controlling driver to make // the change to the hardware. If it does so, we continue processing // (waiting for settle and notifying interested parties post-change.) // If it doesn't, we have to wait for it to acknowledge and then continue. //********************************************************************************* void IOService::OurChangeSetPowerState ( void ) { fNextMachineState = kIOPM_OurChangeWaitForPowerSettle; fMachineState = kIOPM_DriverThreadCallDone; fDriverCallReason = kDriverCallSetPowerState; if (notifyControllingDriver() == false) notifyControllingDriverDone(); } //********************************************************************************* // [private] OurChangeWaitForPowerSettle // // Our controlling driver has changed power state on the hardware // during a power change we initiated. Here we see if we need to wait // for power to settle before continuing. If not, we continue processing // (notifying interested parties post-change). If so, we wait and // continue later. //********************************************************************************* void IOService::OurChangeWaitForPowerSettle ( void ) { fMachineState = kIOPM_OurChangeNotifyInterestedDriversDidChange; fSettleTimeUS = compute_settle_time(); if ( fSettleTimeUS ) { startSettleTimer(fSettleTimeUS); } } //********************************************************************************* // [private] OurChangeNotifyInterestedDriversDidChange // // Power has settled on a power change we initiated. Here we notify // all our interested parties post-change. If they all acknowledge, we're // done with this change note, and we can start on the next one. // Otherwise we have to wait for acknowledgements and finish up later. //********************************************************************************* void IOService::OurChangeNotifyInterestedDriversDidChange ( void ) { fMachineState = kIOPM_OurChangeFinish; notifyAll(false); } //********************************************************************************* // [private] OurChangeFinish // // Power has settled on a power change we initiated, and // all our interested parties have acknowledged. We're // done with this change note, and we can start on the next one. //********************************************************************************* void IOService::OurChangeFinish ( void ) { all_done(); } //********************************************************************************* // [private] ParentDownTellPriorityClientsPowerDown // // All applications and kernel clients have been notified of a power lowering // initiated by the parent and we had to wait for responses. Here // we notify any priority clients. If they all ack, we continue with the power change. // If at least one doesn't, we have to wait for it to acknowledge and then continue. //********************************************************************************* void IOService::ParentDownTellPriorityClientsPowerDown ( void ) { fMachineState = kIOPM_ParentDownNotifyInterestedDriversWillChange; tellChangeDown2(fHeadNoteState); } //********************************************************************************* // [private] ParentDownNotifyInterestedDriversWillChange // // All applications and kernel clients have been notified of a power lowering // initiated by the parent and we had to wait for their responses. Here we notify // any interested drivers and power domain children. If they all ack, we continue // with the power change. // If at least one doesn't, we have to wait for it to acknowledge and then continue. //********************************************************************************* void IOService::ParentDownNotifyInterestedDriversWillChange ( void ) { fMachineState = kIOPM_ParentDownSetPowerState; notifyAll( true ); } //********************************************************************************* // [private] ParentDownSetPowerState // // We had to wait for it, but all parties have acknowledged our pre-change // notification of a power lowering initiated by the parent. // Here we instruct our controlling driver // to put the hardware in the state it needs to be in when the domain is // lowered. If it does so, we continue processing // (waiting for settle and acknowledging the parent.) // If it doesn't, we have to wait for it to acknowledge and then continue. //********************************************************************************* void IOService::ParentDownSetPowerState ( void ) { fNextMachineState = kIOPM_ParentDownWaitForPowerSettle; fMachineState = kIOPM_DriverThreadCallDone; fDriverCallReason = kDriverCallSetPowerState; if (notifyControllingDriver() == false) notifyControllingDriverDone(); } //********************************************************************************* // [private] ParentDownWaitForPowerSettle // // Our controlling driver has changed power state on the hardware // during a power change initiated by our parent. We have had to wait // for acknowledgement from interested parties, or we have had to wait // for the controlling driver to change the state. Here we see if we need // to wait for power to settle before continuing. If not, we continue // processing (acknowledging our preparedness to the parent). // If so, we wait and continue later. //********************************************************************************* void IOService::ParentDownWaitForPowerSettle ( void ) { fMachineState = kIOPM_ParentDownNotifyDidChangeAndAcknowledgeChange; fSettleTimeUS = compute_settle_time(); if ( fSettleTimeUS ) { startSettleTimer(fSettleTimeUS); } } //********************************************************************************* // [private] ParentDownNotifyDidChangeAndAcknowledgeChange // // Power has settled on a power change initiated by our parent. Here we // notify interested parties. //********************************************************************************* void IOService::ParentDownNotifyDidChangeAndAcknowledgeChange ( void ) { fMachineState = kIOPM_ParentDownAcknowledgeChange; notifyAll(false); } //********************************************************************************* // [private] ParentDownAcknowledgeChange // // We had to wait for it, but all parties have acknowledged our post-change // notification of a power lowering initiated by the parent. // Here we acknowledge the parent. // We are done with this change note, and we can start on the next one. //********************************************************************************* void IOService::ParentDownAcknowledgeChange ( void ) { IORegistryEntry * nub; IOService * parent; nub = fHeadNoteParent; nub->retain(); all_done(); parent = (IOService *)nub->copyParentEntry(gIOPowerPlane); if ( parent ) { parent->acknowledgePowerChange((IOService *)nub); parent->release(); } nub->release(); } //********************************************************************************* // [private] ParentUpSetPowerState // // Our parent has informed us via powerStateDidChange that it has // raised the power in our power domain, and we have had to wait // for some interested party to acknowledge our notification. // Here we instruct our controlling // driver to program the hardware to take advantage of the higher domain // power. If it does so, we continue processing // (waiting for settle and notifying interested parties post-change.) // If it doesn't, we have to wait for it to acknowledge and then continue. //********************************************************************************* void IOService::ParentUpSetPowerState ( void ) { fNextMachineState = kIOPM_ParentUpWaitForSettleTime; fMachineState = kIOPM_DriverThreadCallDone; fDriverCallReason = kDriverCallSetPowerState; if (notifyControllingDriver() == false) notifyControllingDriverDone(); } //********************************************************************************* // [private] ParentUpWaitForSettleTime // // Our controlling driver has changed power state on the hardware // during a power raise initiated by the parent, but we had to wait for it. // Here we see if we need to wait for power to settle before continuing. // If not, we continue processing (notifying interested parties post-change). // If so, we wait and continue later. //********************************************************************************* void IOService::ParentUpWaitForSettleTime ( void ) { fMachineState = kIOPM_ParentUpNotifyInterestedDriversDidChange; fSettleTimeUS = compute_settle_time(); if ( fSettleTimeUS ) { startSettleTimer(fSettleTimeUS); } } //********************************************************************************* // [private] ParentUpNotifyInterestedDriversDidChange // // Power has settled on a power raise initiated by the parent. // Here we notify all our interested parties post-change. If they all acknowledge, // we're done with this change note, and we can start on the next one. // Otherwise we have to wait for acknowledgements and finish up later. //********************************************************************************* void IOService::ParentUpNotifyInterestedDriversDidChange ( void ) { fMachineState = kIOPM_ParentUpAcknowledgePowerChange; notifyAll(false); } //********************************************************************************* // [private] ParentUpAcknowledgePowerChange // // All parties have acknowledged our post-change notification of a power // raising initiated by the parent. Here we acknowledge the parent. // We are done with this change note, and we can start on the next one. //********************************************************************************* void IOService::ParentUpAcknowledgePowerChange ( void ) { IORegistryEntry * nub; IOService * parent; nub = fHeadNoteParent; nub->retain(); all_done(); parent = (IOService *)nub->copyParentEntry(gIOPowerPlane); if ( parent ) { parent->acknowledgePowerChange((IOService *)nub); parent->release(); } nub->release(); } //********************************************************************************* // [private] all_done // // A power change is complete, and the used post-change note is at // the head of the queue. Remove it and set myCurrentState to the result // of the change. Start up the next change in queue. //********************************************************************************* void IOService::all_done ( void ) { unsigned long previous_state; fMachineState = kIOPM_Finished; // our power change if ( fHeadNoteFlags & IOPMWeInitiated ) { // could our driver switch to the new state? if ( !( fHeadNoteFlags & IOPMNotDone) ) { // we changed, tell our parent if ( !fWeAreRoot ) { ask_parent(fHeadNoteState); } // yes, did power raise? if ( fCurrentPowerState < fHeadNoteState ) { // yes, inform clients and apps tellChangeUp (fHeadNoteState); } previous_state = fCurrentPowerState; // either way fCurrentPowerState = fHeadNoteState; #if PM_VARS_SUPPORT fPMVars->myCurrentState = fCurrentPowerState; #endif OUR_PMLog(kPMLogChangeDone, fCurrentPowerState, 0); // inform subclass policy-maker if (!fWillPMStop && fParentsKnowState) powerChangeDone(previous_state); else PM_DEBUG("%s::powerChangeDone() skipped\n", getName()); } } // parent's power change if ( fHeadNoteFlags & IOPMParentInitiated) { if (((fHeadNoteFlags & IOPMDomainWillChange) && (fCurrentPowerState >= fHeadNoteState)) || ((fHeadNoteFlags & IOPMDomainDidChange) && (fCurrentPowerState < fHeadNoteState))) { // did power raise? if ( fCurrentPowerState < fHeadNoteState ) { // yes, inform clients and apps tellChangeUp (fHeadNoteState); } // either way previous_state = fCurrentPowerState; fCurrentPowerState = fHeadNoteState; #if PM_VARS_SUPPORT fPMVars->myCurrentState = fCurrentPowerState; #endif fMaxCapability = fControllingDriver->maxCapabilityForDomainState(fHeadNoteDomainState); OUR_PMLog(kPMLogChangeDone, fCurrentPowerState, 0); // inform subclass policy-maker if (!fWillPMStop && fParentsKnowState) powerChangeDone(previous_state); else PM_DEBUG("%s::powerChangeDone() skipped\n", getName()); } } if (fCurrentPowerState < fNumberOfPowerStates) { const IOPMPowerState * powerStatePtr = &fPowerStates[fCurrentPowerState]; fCurrentCapabilityFlags = powerStatePtr->capabilityFlags; if (fCurrentCapabilityFlags & kIOPMStaticPowerValid) fCurrentPowerConsumption = powerStatePtr->staticPower; } // When power rises enough to satisfy the tickle's desire for more power, // the condition preventing idle-timer from dropping power is removed. if (fCurrentPowerState >= fIdleTimerMinPowerState) { fIdleTimerMinPowerState = 0; } } //********************************************************************************* // [public] settleTimerExpired // // Power has settled after our last change. Notify interested parties that // there is a new power state. //********************************************************************************* void IOService::settleTimerExpired ( void ) { fSettleTimeUS = 0; } //********************************************************************************* // [private] compute_settle_time // // Compute the power-settling delay in microseconds for the // change from myCurrentState to head_note_state. //********************************************************************************* unsigned long IOService::compute_settle_time ( void ) { unsigned long totalTime; unsigned long i; PM_ASSERT_IN_GATE(); // compute total time to attain the new state totalTime = 0; i = fCurrentPowerState; // we're lowering power if ( fHeadNoteState < fCurrentPowerState ) { while ( i > fHeadNoteState ) { totalTime += fPowerStates[i].settleDownTime; i--; } } // we're raising power if ( fHeadNoteState > fCurrentPowerState ) { while ( i < fHeadNoteState ) { totalTime += fPowerStates[i+1].settleUpTime; i++; } } return totalTime; } //********************************************************************************* // [private] startSettleTimer // // Enter a power-settling delay in microseconds and start a timer for that delay. //********************************************************************************* IOReturn IOService::startSettleTimer ( unsigned long delay ) { AbsoluteTime deadline; boolean_t pending; retain(); clock_interval_to_deadline(delay, kMicrosecondScale, &deadline); pending = thread_call_enter_delayed(fSettleTimer, deadline); if (pending) release(); return IOPMNoErr; } //********************************************************************************* // [public] ackTimerTick // // The acknowledgement timeout periodic timer has ticked. // If we are awaiting acks for a power change notification, // we decrement the timer word of each interested driver which hasn't acked. // If a timer word becomes zero, we pretend the driver aknowledged. // If we are waiting for the controlling driver to change the power // state of the hardware, we decrement its timer word, and if it becomes // zero, we pretend the driver acknowledged. // // Returns true if the timer tick made it possible to advance to the next // machine state, false otherwise. //********************************************************************************* void IOService::ack_timer_ticked ( void ) { assert(false); } bool IOService::ackTimerTick( void ) { IOPMinformee * nextObject; bool done = false; PM_ASSERT_IN_GATE(); switch (fMachineState) { case kIOPM_OurChangeWaitForPowerSettle: case kIOPM_ParentDownWaitForPowerSettle: case kIOPM_ParentUpWaitForSettleTime: // are we waiting for controlling driver to acknowledge? if ( fDriverTimer > 0 ) { // yes, decrement timer tick fDriverTimer--; if ( fDriverTimer == 0 ) { // controlling driver is tardy uint64_t nsec = computeTimeDeltaNS(&fDriverCallStartTime); OUR_PMLog(kPMLogCtrlDriverTardy, 0, 0); setProperty(kIOPMTardyAckSPSKey, kOSBooleanTrue); PM_ERROR("%s::setPowerState(%p, %lu -> %lu) timed out after %d ms\n", fName, this, fCurrentPowerState, fHeadNoteState, NS_TO_MS(nsec)); if (gIOKitDebug & kIOLogDebugPower) { panic("%s::setPowerState(%p, %lu -> %lu) timed out after %d ms", fName, this, fCurrentPowerState, fHeadNoteState, NS_TO_MS(nsec)); } else { // Unblock state machine and pretend driver has acked. done = true; } } else { // still waiting, set timer again start_ack_timer(); } } break; case kIOPM_OurChangeSetPowerState: case kIOPM_OurChangeFinish: case kIOPM_ParentDownSetPowerState: case kIOPM_ParentDownAcknowledgeChange: case kIOPM_ParentUpSetPowerState: case kIOPM_ParentUpAcknowledgePowerChange: case kIOPM_NotifyChildrenDone: // are we waiting for interested parties to acknowledge? if ( fHeadNotePendingAcks != 0 ) { // yes, go through the list of interested drivers nextObject = fInterestedDrivers->firstInList(); // and check each one while ( nextObject != NULL ) { if ( nextObject->timer > 0 ) { nextObject->timer--; // this one should have acked by now if ( nextObject->timer == 0 ) { uint64_t nsec = computeTimeDeltaNS(&nextObject->startTime); OUR_PMLog(kPMLogIntDriverTardy, 0, 0); nextObject->whatObject->setProperty(kIOPMTardyAckPSCKey, kOSBooleanTrue); PM_ERROR("%s::powerState%sChangeTo(%p, %s, %lu -> %lu) timed out after %d ms\n", nextObject->whatObject->getName(), (fDriverCallReason == kDriverCallInformPreChange) ? "Will" : "Did", nextObject->whatObject, fName, fCurrentPowerState, fHeadNoteState, NS_TO_MS(nsec)); // Pretend driver has acked. fHeadNotePendingAcks--; } } nextObject = fInterestedDrivers->nextInList(nextObject); } // is that the last? if ( fHeadNotePendingAcks == 0 ) { // yes, we can continue done = true; } else { // no, set timer again start_ack_timer(); } } break; case kIOPM_ParentDownTellPriorityClientsPowerDown: case kIOPM_ParentDownNotifyInterestedDriversWillChange: case kIOPM_OurChangeTellClientsPowerDown: case kIOPM_OurChangeTellPriorityClientsPowerDown: case kIOPM_OurChangeNotifyInterestedDriversWillChange: // apps didn't respond in time cleanClientResponses(true); OUR_PMLog(kPMLogClientTardy, 0, 1); // tardy equates to approval done = true; break; default: PM_TRACE("[%s] unexpected ack timer tick (state = %ld)\n", getName(), fMachineState); break; } return done; } //********************************************************************************* // [private] start_ack_timer //********************************************************************************* void IOService::start_ack_timer ( void ) { start_ack_timer( ACK_TIMER_PERIOD, kNanosecondScale ); } void IOService::start_ack_timer ( UInt32 interval, UInt32 scale ) { AbsoluteTime deadline; boolean_t pending; clock_interval_to_deadline(interval, scale, &deadline); retain(); pending = thread_call_enter_delayed(fAckTimer, deadline); if (pending) release(); } //********************************************************************************* // [private] stop_ack_timer //********************************************************************************* void IOService::stop_ack_timer ( void ) { boolean_t pending; pending = thread_call_cancel(fAckTimer); if (pending) release(); } //********************************************************************************* // [static] settleTimerExpired // // Inside PM work loop's gate. //********************************************************************************* IOReturn IOService::actionAckTimerExpired ( OSObject * target, void * arg0, void * arg1, void * arg2, void * arg3 ) { IOService * me = (IOService *) target; bool done; // done will be true if the timer tick unblocks the machine state, // otherwise no need to signal the work loop. done = me->ackTimerTick(); if (done && gIOPMReplyQueue) gIOPMReplyQueue->signalWorkAvailable(); return kIOReturnSuccess; } //********************************************************************************* // ack_timer_expired // // Thread call function. Holds a retain while the callout is in flight. //********************************************************************************* void IOService::ack_timer_expired ( thread_call_param_t arg0, thread_call_param_t arg1 ) { IOService * me = (IOService *) arg0; if (gIOPMWorkLoop) { gIOPMWorkLoop->runAction(&actionAckTimerExpired, me); } me->release(); } //********************************************************************************* // settleTimerExpired // // Inside PM work loop's gate. //********************************************************************************* static IOReturn settleTimerExpired ( OSObject * target, void * arg0, void * arg1, void * arg2, void * arg3 ) { IOService * me = (IOService *) target; me->settleTimerExpired(); return kIOReturnSuccess; } //********************************************************************************* // settle_timer_expired // // Thread call function. Holds a retain while the callout is in flight. //********************************************************************************* static void settle_timer_expired ( thread_call_param_t arg0, thread_call_param_t arg1 ) { IOService * me = (IOService *) arg0; if (gIOPMWorkLoop && gIOPMReplyQueue) { gIOPMWorkLoop->runAction(settleTimerExpired, me); gIOPMReplyQueue->signalWorkAvailable(); } me->release(); } //********************************************************************************* // [private] start_parent_change // // Here we begin the processing of a power change initiated by our parent. //********************************************************************************* IOReturn IOService::start_parent_change ( const changeNoteItem * changeNote ) { fHeadNoteFlags = changeNote->flags; fHeadNoteState = changeNote->newStateNumber; fHeadNoteOutputFlags = changeNote->outputPowerCharacter; fHeadNoteDomainState = changeNote->domainState; fHeadNoteParent = changeNote->parent; fHeadNoteCapabilityFlags = changeNote->capabilityFlags; PM_ASSERT_IN_GATE(); OUR_PMLog( kPMLogStartParentChange, fHeadNoteState, fCurrentPowerState ); // Power domain is lowering power if ( fHeadNoteState < fCurrentPowerState ) { setParentInfo( changeNote->singleParentState, fHeadNoteParent, true ); // tell apps and kernel clients fInitialChange = false; fMachineState = kIOPM_ParentDownTellPriorityClientsPowerDown; tellChangeDown1(fHeadNoteState); return IOPMWillAckLater; } // Power domain is raising power if ( fHeadNoteState > fCurrentPowerState ) { IOPMPowerState * powerStatePtr; if ( fDesiredPowerState > fCurrentPowerState ) { if ( fDesiredPowerState < fHeadNoteState ) { // We power up, but not all the way fHeadNoteState = fDesiredPowerState; powerStatePtr = &fPowerStates[fHeadNoteState]; fHeadNoteOutputFlags = powerStatePtr->outputPowerCharacter; fHeadNoteCapabilityFlags = powerStatePtr->capabilityFlags; OUR_PMLog(kPMLogAmendParentChange, fHeadNoteState, 0); } } else { // We don't need to change fHeadNoteState = fCurrentPowerState; powerStatePtr = &fPowerStates[fHeadNoteState]; fHeadNoteOutputFlags = powerStatePtr->outputPowerCharacter; fHeadNoteCapabilityFlags = powerStatePtr->capabilityFlags; OUR_PMLog(kPMLogAmendParentChange, fHeadNoteState, 0); } } if ((fHeadNoteState > fCurrentPowerState) && (fHeadNoteFlags & IOPMDomainDidChange)) { // Parent did change up - start our change up fInitialChange = false; fMachineState = kIOPM_ParentUpSetPowerState; notifyAll( true ); return IOPMWillAckLater; } all_done(); return IOPMAckImplied; } //********************************************************************************* // [private] start_our_change // // Here we begin the processing of a power change initiated by us. //********************************************************************************* void IOService::start_our_change ( const changeNoteItem * changeNote ) { fHeadNoteFlags = changeNote->flags; fHeadNoteState = changeNote->newStateNumber; fHeadNoteOutputFlags = changeNote->outputPowerCharacter; fHeadNoteCapabilityFlags = changeNote->capabilityFlags; PM_ASSERT_IN_GATE(); OUR_PMLog( kPMLogStartDeviceChange, fHeadNoteState, fCurrentPowerState ); // can our driver switch to the new state? if (( fHeadNoteCapabilityFlags & IOPMNotAttainable ) || ((fMaxCapability < fHeadNoteState) && (!fWeAreRoot))) { // mark the change note un-actioned fHeadNoteFlags |= IOPMNotDone; // no, ask the parent to do it then if ( !fWeAreRoot ) { ask_parent(fHeadNoteState); } all_done(); return; } if ( !fInitialChange ) { if ( fHeadNoteState == fCurrentPowerState ) { // we initiated a null change; forget it all_done(); return; } } fInitialChange = false; // dropping power? if ( fHeadNoteState < fCurrentPowerState ) { // yes, in case we have to wait for acks fMachineState = kIOPM_OurChangeTellClientsPowerDown; fDoNotPowerDown = false; // ask apps and kernel clients if we can drop power fOutOfBandParameter = kNotifyApps; askChangeDown(fHeadNoteState); } else { // in case they don't all ack fMachineState = kIOPM_OurChangeSetPowerState; // notify interested drivers and children notifyAll(true); } } //********************************************************************************* // [private] ask_parent // // Call the power domain parent to ask for a higher power state in the domain // or to suggest a lower power state. //********************************************************************************* IOReturn IOService::ask_parent ( unsigned long requestedState ) { OSIterator * iter; OSObject * next; IOPowerConnection * connection; IOService * parent; const IOPMPowerState * powerStatePtr; unsigned long ourRequest; PM_ASSERT_IN_GATE(); if (requestedState >= fNumberOfPowerStates) return IOPMNoErr; powerStatePtr = &fPowerStates[requestedState]; ourRequest = powerStatePtr->inputPowerRequirement; if ( powerStatePtr->capabilityFlags & (kIOPMChildClamp | kIOPMPreventIdleSleep) ) { ourRequest |= kIOPMPreventIdleSleep; } if ( powerStatePtr->capabilityFlags & (kIOPMChildClamp2 | kIOPMPreventSystemSleep) ) { ourRequest |= kIOPMPreventSystemSleep; } // is this a new desire? if ( fPreviousRequest == ourRequest ) { // no, the parent knows already, just return return IOPMNoErr; } if ( fWeAreRoot ) { return IOPMNoErr; } fPreviousRequest = ourRequest; iter = getParentIterator(gIOPowerPlane); if ( iter ) { while ( (next = iter->getNextObject()) ) { if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) { parent = (IOService *)connection->copyParentEntry(gIOPowerPlane); if ( parent ) { if ( parent->requestPowerDomainState( ourRequest, connection, IOPMLowestState) != IOPMNoErr ) { OUR_PMLog(kPMLogRequestDenied, fPreviousRequest, 0); } parent->release(); } } } iter->release(); } return IOPMNoErr; } //********************************************************************************* // [private] notifyControllingDriver //********************************************************************************* bool IOService::notifyControllingDriver ( void ) { DriverCallParam * param; unsigned long powerState; PM_ASSERT_IN_GATE(); assert( fDriverCallBusy == false ); assert( fDriverCallParamCount == 0 ); assert( fControllingDriver ); powerState = fHeadNoteState; if (fPowerStates[powerState].capabilityFlags & IOPMNotAttainable ) return false; // state not attainable param = (DriverCallParam *) fDriverCallParamPtr; if (!param) { param = IONew(DriverCallParam, 1); if (!param) return false; // no memory fDriverCallParamPtr = (void *) param; fDriverCallParamSlots = 1; } param->Target = fControllingDriver; fDriverCallParamCount = 1; fDriverTimer = -1; // Machine state for this object will stall waiting for a reply // from the callout thread. PM_LOCK(); fDriverCallBusy = true; PM_UNLOCK(); thread_call_enter( fDriverCallEntry ); return true; } //********************************************************************************* // [private] notifyControllingDriverDone //********************************************************************************* void IOService::notifyControllingDriverDone( void ) { DriverCallParam * param; IOReturn result; PM_ASSERT_IN_GATE(); param = (DriverCallParam *) fDriverCallParamPtr; assert( fDriverCallBusy == false ); assert( fMachineState == kIOPM_DriverThreadCallDone ); if (param) { assert(fDriverCallParamCount == 1); // the return value from setPowerState() result = param->Result; if ((result == IOPMAckImplied) || (result < 0)) { // child return IOPMAckImplied fDriverTimer = 0; } else if (fDriverTimer) { assert(fDriverTimer == -1); // Driver has not acked, and has returned a positive result. // Enforce a minimum permissible timeout value. // Make the min value large enough so timeout is less likely // to occur if a driver misinterpreted that the return value // should be in microsecond units. And make it large enough // to be noticeable if a driver neglects to ack. if (result < kMinAckTimeoutTicks) result = kMinAckTimeoutTicks; fDriverTimer = (result / (ACK_TIMER_PERIOD / ns_per_us)) + 1; } // else, child has already acked and driver_timer reset to 0. fDriverCallParamCount = 0; if ( fDriverTimer ) { OUR_PMLog(kPMLogStartAckTimer, 0, 0); start_ack_timer(); } } // Hop back to original machine state path. fMachineState = fNextMachineState; } //********************************************************************************* // [public virtual] askChangeDown // // Ask registered applications and kernel clients if we can change to a lower // power state. // // Subclass can override this to send a different message type. Parameter is // the destination state number. // // Return true if we don't have to wait for acknowledgements //********************************************************************************* bool IOService::askChangeDown ( unsigned long stateNum ) { return tellClientsWithResponse( kIOMessageCanDevicePowerOff ); } //********************************************************************************* // [public] tellChangeDown1 // // Notify registered applications and kernel clients that we are definitely // dropping power. // // Return true if we don't have to wait for acknowledgements //********************************************************************************* bool IOService::tellChangeDown1 ( unsigned long stateNum ) { fOutOfBandParameter = kNotifyApps; return tellChangeDown(stateNum); } //********************************************************************************* // [public] tellChangeDown2 // // Notify priority clients that we are definitely dropping power. // // Return true if we don't have to wait for acknowledgements //********************************************************************************* bool IOService::tellChangeDown2 ( unsigned long stateNum ) { fOutOfBandParameter = kNotifyPriority; return tellChangeDown(stateNum); } //********************************************************************************* // [public virtual] tellChangeDown // // Notify registered applications and kernel clients that we are definitely // dropping power. // // Subclass can override this to send a different message type. Parameter is // the destination state number. // // Return true if we don't have to wait for acknowledgements //********************************************************************************* bool IOService::tellChangeDown ( unsigned long stateNum ) { return tellClientsWithResponse( kIOMessageDeviceWillPowerOff ); } //********************************************************************************* // cleanClientResponses // //********************************************************************************* static void logAppTimeouts ( OSObject * object, void * context) { struct context *theContext = (struct context *)context; OSObject *flag; if( !OSDynamicCast( IOService, object) ) { flag = theContext->responseFlags->getObject(theContext->counter); if (kOSBooleanTrue != flag) { OSString * clientID = 0; theContext->us->messageClient(theContext->msgType, object, &clientID); PM_ERROR(theContext->errorLog, clientID ? clientID->getCStringNoCopy() : ""); if (clientID) clientID->release(); } theContext->counter += 1; } } void IOService::cleanClientResponses ( bool logErrors ) { struct context theContext; if (logErrors && fResponseArray) { theContext.responseFlags = fResponseArray; theContext.serialNumber = fSerialNumber; theContext.counter = 0; theContext.msgType = kIOMessageCopyClientID; theContext.us = this; theContext.maxTimeRequested = 0; theContext.stateNumber = fHeadNoteState; theContext.stateFlags = fHeadNoteCapabilityFlags; theContext.errorLog = "PM notification timeout (%s)\n"; switch ( fOutOfBandParameter ) { case kNotifyApps: applyToInterested(gIOAppPowerStateInterest, logAppTimeouts, (void *) &theContext); case kNotifyPriority: default: break; } } if (fResponseArray) { // get rid of this stuff fResponseArray->release(); fResponseArray = NULL; } return; } //********************************************************************************* // [public] tellClientsWithResponse // // Notify registered applications and kernel clients that we are definitely // dropping power. // // Return true if we don't have to wait for acknowledgements //********************************************************************************* bool IOService::tellClientsWithResponse ( int messageType ) { struct context theContext; PM_ASSERT_IN_GATE(); fResponseArray = OSArray::withCapacity( 1 ); fSerialNumber += 1; theContext.responseFlags = fResponseArray; theContext.serialNumber = fSerialNumber; theContext.counter = 0; theContext.msgType = messageType; theContext.us = this; theContext.maxTimeRequested = 0; theContext.stateNumber = fHeadNoteState; theContext.stateFlags = fHeadNoteCapabilityFlags; switch ( fOutOfBandParameter ) { case kNotifyApps: applyToInterested(gIOAppPowerStateInterest, pmTellAppWithResponse, (void *)&theContext); applyToInterested(gIOGeneralInterest, pmTellClientWithResponse, (void *)&theContext); break; case kNotifyPriority: applyToInterested(gIOPriorityPowerStateInterest, pmTellClientWithResponse, (void *)&theContext); break; } // do we have to wait for somebody? if ( !checkForDone() ) { OUR_PMLog(kPMLogStartAckTimer,theContext.maxTimeRequested, 0); start_ack_timer( theContext.maxTimeRequested / 1000, kMillisecondScale ); return false; } // everybody responded fResponseArray->release(); fResponseArray = NULL; // cleanClientResponses(false); return true; } //********************************************************************************* // [static private] pmTellAppWithResponse // // We send a message to an application, and we expect a response, so we compute a // cookie we can identify the response with. //********************************************************************************* void IOService::pmTellAppWithResponse ( OSObject * object, void * context ) { struct context * theContext = (struct context *) context; IOServicePM * pwrMgt = theContext->us->pwrMgt; AbsoluteTime now; if( OSDynamicCast( IOService, object) ) { // Automatically 'ack' in kernel clients theContext->responseFlags->setObject(theContext->counter, kOSBooleanTrue); const char *who = ((IOService *) object)->getName(); fPlatform->PMLog(who, kPMLogClientAcknowledge, theContext->msgType, * (UInt32 *) object); } else { UInt32 refcon = ((theContext->serialNumber & 0xFFFF)<<16) + (theContext->counter & 0xFFFF); OUR_PMLog(kPMLogAppNotify, theContext->msgType, refcon); #if LOG_APP_RESPONSE_TIMES OSNumber * num; clock_get_uptime(&now); num = OSNumber::withNumber(AbsoluteTime_to_scalar(&now), sizeof(uint64_t) * 8); if (num) { theContext->responseFlags->setObject(theContext->counter, num); num->release(); } else #endif theContext->responseFlags->setObject(theContext->counter, kOSBooleanFalse); theContext->us->messageClient(theContext->msgType, object, (void *)refcon); if ( theContext->maxTimeRequested < k30seconds ) { theContext->maxTimeRequested = k30seconds; } theContext->counter += 1; } } //********************************************************************************* // [static private] pmTellClientWithResponse // // We send a message to an in-kernel client, and we expect a response, so we compute a // cookie we can identify the response with. // If it doesn't understand the notification (it is not power-management savvy) // we won't wait for it to prepare for sleep. If it tells us via a return code // in the passed struct that it is currently ready, we won't wait for it to prepare. // If it tells us via the return code in the struct that it does need time, we will chill. //********************************************************************************* void IOService::pmTellClientWithResponse ( OSObject * object, void * context ) { struct context *theContext = (struct context *)context; IOPowerStateChangeNotification notify; UInt32 refcon; IOReturn retCode; OSObject *theFlag; refcon = ((theContext->serialNumber & 0xFFFF)<<16) + (theContext->counter & 0xFFFF); theContext->responseFlags->setObject(theContext->counter, kOSBooleanFalse); IOServicePM * pwrMgt = theContext->us->pwrMgt; if (gIOKitDebug & kIOLogPower) { OUR_PMLog(kPMLogClientNotify, refcon, (UInt32) theContext->msgType); if (OSDynamicCast(IOService, object)) { const char *who = ((IOService *) object)->getName(); fPlatform->PMLog(who, kPMLogClientNotify, * (UInt32 *) object, (UInt32) object); } else if (OSDynamicCast(_IOServiceInterestNotifier, object)) { _IOServiceInterestNotifier *n = (_IOServiceInterestNotifier *) object; OUR_PMLog(kPMLogClientNotify, (UInt32) n->handler, 0); } } notify.powerRef = (void *)refcon; notify.returnValue = 0; notify.stateNumber = theContext->stateNumber; notify.stateFlags = theContext->stateFlags; retCode = theContext->us->messageClient(theContext->msgType,object,(void *)¬ify); if ( retCode == kIOReturnSuccess ) { if ( notify.returnValue == 0 ) { // client doesn't want time to respond theContext->responseFlags->replaceObject(theContext->counter, kOSBooleanTrue); OUR_PMLog(kPMLogClientAcknowledge, refcon, (UInt32) object); } else { // it does want time, and it hasn't responded yet theFlag = theContext->responseFlags->getObject(theContext->counter); if ( kOSBooleanTrue != theFlag ) { // so note its time requirement if ( theContext->maxTimeRequested < notify.returnValue ) { theContext->maxTimeRequested = notify.returnValue; } } } } else { OUR_PMLog(kPMLogClientAcknowledge, refcon, 0); // not a client of ours // so we won't be waiting for response theContext->responseFlags->replaceObject(theContext->counter, kOSBooleanTrue); } theContext->counter += 1; } //********************************************************************************* // [public virtual] tellNoChangeDown // // Notify registered applications and kernel clients that we are not // dropping power. // // Subclass can override this to send a different message type. Parameter is // the aborted destination state number. //********************************************************************************* void IOService::tellNoChangeDown ( unsigned long ) { return tellClients( kIOMessageDeviceWillNotPowerOff ); } //********************************************************************************* // [public virtual] tellChangeUp // // Notify registered applications and kernel clients that we are raising power. // // Subclass can override this to send a different message type. Parameter is // the aborted destination state number. //********************************************************************************* void IOService::tellChangeUp ( unsigned long ) { return tellClients( kIOMessageDeviceHasPoweredOn ); } //********************************************************************************* // [public] tellClients // // Notify registered applications and kernel clients of something. //********************************************************************************* void IOService::tellClients ( int messageType ) { struct context theContext; theContext.msgType = messageType; theContext.us = this; theContext.stateNumber = fHeadNoteState; theContext.stateFlags = fHeadNoteCapabilityFlags; applyToInterested(gIOPriorityPowerStateInterest,tellClient,(void *)&theContext); applyToInterested(gIOAppPowerStateInterest,tellClient, (void *)&theContext); applyToInterested(gIOGeneralInterest,tellClient, (void *)&theContext); } //********************************************************************************* // [global] tellClient // // Notify a registered application or kernel client of something. //********************************************************************************* void tellClient ( OSObject * object, void * context ) { struct context * theContext = (struct context *) context; IOPowerStateChangeNotification notify; notify.powerRef = (void *) 0; notify.returnValue = 0; notify.stateNumber = theContext->stateNumber; notify.stateFlags = theContext->stateFlags; theContext->us->messageClient(theContext->msgType, object, ¬ify); } //********************************************************************************* // [private] checkForDone //********************************************************************************* bool IOService::checkForDone ( void ) { int i = 0; OSObject * theFlag; if ( fResponseArray == NULL ) { return true; } for ( i = 0; ; i++ ) { theFlag = fResponseArray->getObject(i); if ( theFlag == NULL ) { break; } if ( kOSBooleanTrue != theFlag ) { return false; } } return true; } //********************************************************************************* // [public] responseValid //********************************************************************************* bool IOService::responseValid ( unsigned long x, int pid ) { UInt16 serialComponent; UInt16 ordinalComponent; OSObject * theFlag; unsigned long refcon = (unsigned long) x; serialComponent = (refcon >> 16) & 0xFFFF; ordinalComponent = (refcon & 0xFFFF); if ( serialComponent != fSerialNumber ) { return false; } if ( fResponseArray == NULL ) { return false; } theFlag = fResponseArray->getObject(ordinalComponent); if ( theFlag == 0 ) { return false; } OSNumber * num; if ((num = OSDynamicCast(OSNumber, theFlag))) { #if LOG_APP_RESPONSE_TIMES AbsoluteTime now; AbsoluteTime start; uint64_t nsec; clock_get_uptime(&now); AbsoluteTime_to_scalar(&start) = num->unsigned64BitValue(); SUB_ABSOLUTETIME(&now, &start); absolutetime_to_nanoseconds(now, &nsec); // > 100 ms if (nsec > LOG_APP_RESPONSE_TIMES) { OSString * name = IOCopyLogNameForPID(pid); PM_DEBUG("PM response took %d ms (%s)\n", NS_TO_MS(nsec), name ? name->getCStringNoCopy() : ""); if (name) name->release(); } #endif theFlag = kOSBooleanFalse; } if ( kOSBooleanFalse == theFlag ) { fResponseArray->replaceObject(ordinalComponent, kOSBooleanTrue); } return true; } //********************************************************************************* // [public virtual] allowPowerChange // // Our power state is about to lower, and we have notified applications // and kernel clients, and one of them has acknowledged. If this is the last to do // so, and all acknowledgements are positive, we continue with the power change. // // We serialize this processing with timer expiration with a command gate on the // power management workloop, which the timer expiration is command gated to as well. //********************************************************************************* IOReturn IOService::allowPowerChange ( unsigned long refcon ) { IOPMRequest * request; if ( !initialized ) { // we're unloading return kIOReturnSuccess; } request = acquirePMRequest( this, kIOPMRequestTypeAllowPowerChange ); if (!request) { PM_ERROR("%s::%s no memory\n", getName(), __FUNCTION__); return kIOReturnNoMemory; } request->fArg0 = (void *) refcon; request->fArg1 = (void *) proc_selfpid(); submitPMRequest( request ); return kIOReturnSuccess; } IOReturn serializedAllowPowerChange ( OSObject *owner, void * refcon, void *, void *, void *) { // [deprecated] public return kIOReturnUnsupported; } IOReturn IOService::serializedAllowPowerChange2 ( unsigned long refcon ) { // [deprecated] public return kIOReturnUnsupported; } //********************************************************************************* // [public virtual] cancelPowerChange // // Our power state is about to lower, and we have notified applications // and kernel clients, and one of them has vetoed the change. If this is the last // client to respond, we abandon the power change. // // We serialize this processing with timer expiration with a command gate on the // power management workloop, which the timer expiration is command gated to as well. //********************************************************************************* IOReturn IOService::cancelPowerChange ( unsigned long refcon ) { IOPMRequest * request; if ( !initialized ) { // we're unloading return kIOReturnSuccess; } OSString * name = IOCopyLogNameForPID(proc_selfpid()); PM_ERROR("PM notification cancel (%s)\n", name ? name->getCStringNoCopy() : ""); if (name) name->release(); request = acquirePMRequest( this, kIOPMRequestTypeCancelPowerChange ); if (!request) { PM_ERROR("%s::%s no memory\n", getName(), __FUNCTION__); return kIOReturnNoMemory; } request->fArg0 = (void *) refcon; request->fArg1 = (void *) proc_selfpid(); submitPMRequest( request ); return kIOReturnSuccess; } IOReturn serializedCancelPowerChange ( OSObject *owner, void * refcon, void *, void *, void *) { // [deprecated] public return kIOReturnUnsupported; } IOReturn IOService::serializedCancelPowerChange2 ( unsigned long refcon ) { // [deprecated] public return kIOReturnUnsupported; } #if 0 //********************************************************************************* // c_PM_clamp_Timer_Expired (C Func) // // Called when our clamp timer expires...we will call the object method. //********************************************************************************* static void c_PM_Clamp_Timer_Expired ( OSObject * client, IOTimerEventSource * ) { if (client) ((IOService *)client)->PM_Clamp_Timer_Expired (); } #endif //********************************************************************************* // PM_Clamp_Timer_Expired // // called when clamp timer expires...set power state to 0. //********************************************************************************* void IOService::PM_Clamp_Timer_Expired ( void ) { #if 0 if ( ! initialized ) { // we're unloading return; } changePowerStateToPriv (0); #endif } //********************************************************************************* // clampPowerOn // // Set to highest available power state for a minimum of duration milliseconds //********************************************************************************* #define kFiveMinutesInNanoSeconds (300 * NSEC_PER_SEC) void IOService::clampPowerOn ( unsigned long duration ) { #if 0 changePowerStateToPriv (fNumberOfPowerStates-1); if ( pwrMgt->clampTimerEventSrc == NULL ) { pwrMgt->clampTimerEventSrc = IOTimerEventSource::timerEventSource(this, c_PM_Clamp_Timer_Expired); IOWorkLoop * workLoop = getPMworkloop (); if ( !pwrMgt->clampTimerEventSrc || !workLoop || ( workLoop->addEventSource( pwrMgt->clampTimerEventSrc) != kIOReturnSuccess) ) { } } pwrMgt->clampTimerEventSrc->setTimeout(300*USEC_PER_SEC, USEC_PER_SEC); #endif } //********************************************************************************* // [public virtual] setPowerState // // Does nothing here. This should be implemented in a subclass driver. //********************************************************************************* IOReturn IOService::setPowerState ( unsigned long powerStateOrdinal, IOService * whatDevice ) { return IOPMNoErr; } //********************************************************************************* // [public virtual] maxCapabilityForDomainState // // Finds the highest power state in the array whose input power // requirement is equal to the input parameter. Where a more intelligent // decision is possible, override this in the subclassed driver. //********************************************************************************* unsigned long IOService::maxCapabilityForDomainState ( IOPMPowerFlags domainState ) { int i; if (fNumberOfPowerStates == 0 ) { return 0; } for ( i = fNumberOfPowerStates - 1; i >= 0; i-- ) { if ( (domainState & fPowerStates[i].inputPowerRequirement) == fPowerStates[i].inputPowerRequirement ) { return i; } } return 0; } //********************************************************************************* // [public virtual] initialPowerStateForDomainState // // Finds the highest power state in the array whose input power // requirement is equal to the input parameter. Where a more intelligent // decision is possible, override this in the subclassed driver. //********************************************************************************* unsigned long IOService::initialPowerStateForDomainState ( IOPMPowerFlags domainState ) { int i; if (fNumberOfPowerStates == 0 ) { return 0; } for ( i = fNumberOfPowerStates - 1; i >= 0; i-- ) { if ( (domainState & fPowerStates[i].inputPowerRequirement) == fPowerStates[i].inputPowerRequirement ) { return i; } } return 0; } //********************************************************************************* // [public virtual] powerStateForDomainState // // Finds the highest power state in the array whose input power // requirement is equal to the input parameter. Where a more intelligent // decision is possible, override this in the subclassed driver. //********************************************************************************* unsigned long IOService::powerStateForDomainState ( IOPMPowerFlags domainState ) { int i; if (fNumberOfPowerStates == 0 ) { return 0; } for ( i = fNumberOfPowerStates - 1; i >= 0; i-- ) { if ( (domainState & fPowerStates[i].inputPowerRequirement) == fPowerStates[i].inputPowerRequirement ) { return i; } } return 0; } //********************************************************************************* // [public virtual] didYouWakeSystem // // Does nothing here. This should be implemented in a subclass driver. //********************************************************************************* bool IOService::didYouWakeSystem ( void ) { return false; } //********************************************************************************* // [public virtual] powerStateWillChangeTo // // Does nothing here. This should be implemented in a subclass driver. //********************************************************************************* IOReturn IOService::powerStateWillChangeTo ( IOPMPowerFlags, unsigned long, IOService * ) { return kIOPMAckImplied; } //********************************************************************************* // [public virtual] powerStateDidChangeTo // // Does nothing here. This should be implemented in a subclass driver. //********************************************************************************* IOReturn IOService::powerStateDidChangeTo ( IOPMPowerFlags, unsigned long, IOService * ) { return kIOPMAckImplied; } //********************************************************************************* // [public virtual] powerChangeDone // // Called from PM work loop thread. // Does nothing here. This should be implemented in a subclass policy-maker. //********************************************************************************* void IOService::powerChangeDone ( unsigned long ) { } //********************************************************************************* // [public virtual] newTemperature // // Does nothing here. This should be implemented in a subclass driver. //********************************************************************************* IOReturn IOService::newTemperature ( long currentTemp, IOService * whichZone ) { return IOPMNoErr; } //********************************************************************************* // [public virtual] systemWillShutdown // // System shutdown and restart notification. //********************************************************************************* void IOService::systemWillShutdown( IOOptionBits specifier ) { IOPMrootDomain * rootDomain = IOService::getPMRootDomain(); if (rootDomain) rootDomain->acknowledgeSystemWillShutdown( this ); } //********************************************************************************* // [private static] acquirePMRequest //********************************************************************************* IOPMRequest * IOService::acquirePMRequest( IOService * target, IOOptionBits requestType ) { IOPMRequest * request; assert(target); request = IOPMRequest::create(); if (request) { request->init( target, requestType ); } return request; } //********************************************************************************* // [private static] releasePMRequest //********************************************************************************* void IOService::releasePMRequest( IOPMRequest * request ) { if (request) { request->reset(); request->release(); } } //********************************************************************************* // [private] submitPMRequest //********************************************************************************* void IOService::submitPMRequest( IOPMRequest * request ) { assert( request ); assert( gIOPMReplyQueue ); assert( gIOPMRequestQueue ); PM_TRACE("[+ %02lx] %p [%p %s] %p %p %p\n", request->getType(), request, request->getTarget(), request->getTarget()->getName(), request->fArg0, request->fArg1, request->fArg2); if (request->isReply()) gIOPMReplyQueue->queuePMRequest( request ); else gIOPMRequestQueue->queuePMRequest( request ); } void IOService::submitPMRequest( IOPMRequest ** requests, IOItemCount count ) { assert( requests ); assert( count > 0 ); assert( gIOPMRequestQueue ); for (IOItemCount i = 0; i < count; i++) { IOPMRequest * req = requests[i]; PM_TRACE("[+ %02lx] %p [%p %s] %p %p %p\n", req->getType(), req, req->getTarget(), req->getTarget()->getName(), req->fArg0, req->fArg1, req->fArg2); } gIOPMRequestQueue->queuePMRequestChain( requests, count ); } //********************************************************************************* // [private] servicePMRequestQueue //********************************************************************************* bool IOService::servicePMRequestQueue( IOPMRequest * request, IOPMRequestQueue * queue ) { // Calling PM methods without PMinit() is not allowed, fail the requests. if (!initialized) { PM_DEBUG("[%s] %s: PM not initialized\n", getName(), __FUNCTION__); goto done; } // Create an IOPMWorkQueue on demand, when the initial PM request is // received. if (!fPMWorkQueue) { // Allocate and attach an IOPMWorkQueue on demand to avoid taking // the work loop lock in PMinit(), which may deadlock with certain // drivers / families. fPMWorkQueue = IOPMWorkQueue::create( /* target */ this, /* Work */ OSMemberFunctionCast(IOPMWorkQueue::Action, this, &IOService::servicePMRequest), /* Done */ OSMemberFunctionCast(IOPMWorkQueue::Action, this, &IOService::retirePMRequest) ); if (fPMWorkQueue && (gIOPMWorkLoop->addEventSource(fPMWorkQueue) != kIOReturnSuccess)) { PM_ERROR("[%s] %s: addEventSource failed\n", getName(), __FUNCTION__); fPMWorkQueue->release(); fPMWorkQueue = 0; } if (!fPMWorkQueue) { PM_ERROR("[%s] %s: not ready (type %02lx)\n", getName(), __FUNCTION__, request->getType()); goto done; } } fPMWorkQueue->queuePMRequest(request); return false; // do not signal more done: gIOPMFreeQueue->queuePMRequest( request ); return false; // do not signal more } //********************************************************************************* // [private] servicePMFreeQueue // // Called by IOPMFreeQueue to recycle a completed request. //********************************************************************************* bool IOService::servicePMFreeQueue( IOPMRequest * request, IOPMRequestQueue * queue ) { bool more = request->hasParentRequest(); releasePMRequest( request ); return more; } //********************************************************************************* // [private] retirePMRequest // // Called by IOPMWorkQueue to retire a completed request. //********************************************************************************* bool IOService::retirePMRequest( IOPMRequest * request, IOPMWorkQueue * queue ) { assert(request && queue); PM_TRACE("[- %02lx] %p [%p %s] State %ld, Busy %ld\n", request->getType(), request, this, getName(), fMachineState, gIOPMBusyCount); // Catch requests created by PM_idle_timer_expiration(). if ((request->getType() == kIOPMRequestTypeActivityTickle) && (request->fArg1 == (void *) false)) { // Idle timer power drop request completed. // Restart the idle timer if deviceDesire can go lower, otherwise set // a flag so we know to restart idle timer when deviceDesire goes up. if (fDeviceDesire > 0) start_PM_idle_timer(); else fActivityTimerStopped = true; } gIOPMFreeQueue->queuePMRequest( request ); return true; } //********************************************************************************* // [private] isPMBlocked // // Check if machine state transition is blocked. //********************************************************************************* bool IOService::isPMBlocked ( IOPMRequest * request, int count ) { int reason = 0; do { if (kIOPM_Finished == fMachineState) break; if (kIOPM_DriverThreadCallDone == fMachineState) { // 5 = kDriverCallInformPreChange // 6 = kDriverCallInformPostChange // 7 = kDriverCallSetPowerState if (fDriverCallBusy) reason = 5 + fDriverCallReason; break; } // Waiting on driver's setPowerState() timeout. if (fDriverTimer) { reason = 1; break; } // Child or interested driver acks pending. if (fHeadNotePendingAcks) { reason = 2; break; } // Waiting on apps or priority power interest clients. if (fResponseArray) { reason = 3; break; } // Waiting on settle timer expiration. if (fSettleTimeUS) { reason = 4; break; } } while (false); fWaitReason = reason; if (reason) { if (count) { PM_TRACE("[B %02lx] %p [%p %s] State %ld, Reason %d\n", request->getType(), request, this, getName(), fMachineState, reason); } return true; } return false; } //********************************************************************************* // [private] servicePMRequest // // Service a request from our work queue. //********************************************************************************* bool IOService::servicePMRequest( IOPMRequest * request, IOPMWorkQueue * queue ) { bool done = false; int loop = 0; assert(request && queue); while (isPMBlocked(request, loop++) == false) { PM_TRACE("[W %02lx] %p [%p %s] State %ld\n", request->getType(), request, this, getName(), fMachineState); fPMRequest = request; // Every PM machine states must be handled in one of the cases below. switch ( fMachineState ) { case kIOPM_Finished: executePMRequest( request ); break; case kIOPM_OurChangeTellClientsPowerDown: // our change, was it vetoed? if (!fDoNotPowerDown) { // no, we can continue OurChangeTellClientsPowerDown(); } else { OUR_PMLog(kPMLogIdleCancel, (uintptr_t) this, fMachineState); PM_ERROR("%s: idle cancel\n", fName); // yes, rescind the warning tellNoChangeDown(fHeadNoteState); // mark the change note un-actioned fHeadNoteFlags |= IOPMNotDone; // and we're done all_done(); } break; case kIOPM_OurChangeTellPriorityClientsPowerDown: // our change, should it be acted on still? #if SUPPORT_IDLE_CANCEL if (fDoNotPowerDown) { OUR_PMLog(kPMLogIdleCancel, (uintptr_t) this, fMachineState); PM_ERROR("%s: idle revert\n", fName); // no, tell clients we're back in the old state tellChangeUp(fCurrentPowerState); // mark the change note un-actioned fHeadNoteFlags |= IOPMNotDone; // and we're done all_done(); } else #endif { // yes, we can continue OurChangeTellPriorityClientsPowerDown(); } break; case kIOPM_OurChangeNotifyInterestedDriversWillChange: OurChangeNotifyInterestedDriversWillChange(); break; case kIOPM_OurChangeSetPowerState: OurChangeSetPowerState(); break; case kIOPM_OurChangeWaitForPowerSettle: OurChangeWaitForPowerSettle(); break; case kIOPM_OurChangeNotifyInterestedDriversDidChange: OurChangeNotifyInterestedDriversDidChange(); break; case kIOPM_OurChangeFinish: OurChangeFinish(); break; case kIOPM_ParentDownTellPriorityClientsPowerDown: ParentDownTellPriorityClientsPowerDown(); break; case kIOPM_ParentDownNotifyInterestedDriversWillChange: ParentDownNotifyInterestedDriversWillChange(); break; case kIOPM_ParentDownNotifyDidChangeAndAcknowledgeChange: ParentDownNotifyDidChangeAndAcknowledgeChange(); break; case kIOPM_ParentDownSetPowerState: ParentDownSetPowerState(); break; case kIOPM_ParentDownWaitForPowerSettle: ParentDownWaitForPowerSettle(); break; case kIOPM_ParentDownAcknowledgeChange: ParentDownAcknowledgeChange(); break; case kIOPM_ParentUpSetPowerState: ParentUpSetPowerState(); break; case kIOPM_ParentUpWaitForSettleTime: ParentUpWaitForSettleTime(); break; case kIOPM_ParentUpNotifyInterestedDriversDidChange: ParentUpNotifyInterestedDriversDidChange(); break; case kIOPM_ParentUpAcknowledgePowerChange: ParentUpAcknowledgePowerChange(); break; case kIOPM_DriverThreadCallDone: if (fDriverCallReason == kDriverCallSetPowerState) notifyControllingDriverDone(); else notifyInterestedDriversDone(); break; case kIOPM_NotifyChildrenDone: notifyChildrenDone(); break; default: IOPanic("servicePMWorkQueue: unknown machine state"); } fPMRequest = 0; if (fMachineState == kIOPM_Finished) { //PM_TRACE("[%s] PM End: Request %p (type %02lx)\n", // getName(), request, request->getType()); done = true; break; } } return done; } //********************************************************************************* // [private] executePMRequest //********************************************************************************* void IOService::executePMRequest( IOPMRequest * request ) { assert( kIOPM_Finished == fMachineState ); switch (request->getType()) { case kIOPMRequestTypePMStop: handlePMstop( request ); break; case kIOPMRequestTypeAddPowerChild1: addPowerChild1( request ); break; case kIOPMRequestTypeAddPowerChild2: addPowerChild2( request ); break; case kIOPMRequestTypeAddPowerChild3: addPowerChild3( request ); break; case kIOPMRequestTypeRegisterPowerDriver: handleRegisterPowerDriver( request ); break; case kIOPMRequestTypeAdjustPowerState: adjustPowerState(); break; case kIOPMRequestTypeMakeUsable: handleMakeUsable( request ); break; case kIOPMRequestTypeTemporaryPowerClamp: fClampOn = true; handleMakeUsable( request ); break; case kIOPMRequestTypePowerDomainWillChange: handlePowerDomainWillChangeTo( request ); break; case kIOPMRequestTypePowerDomainDidChange: handlePowerDomainDidChangeTo( request ); break; case kIOPMRequestTypeChangePowerStateTo: handleChangePowerStateTo( request ); break; case kIOPMRequestTypeChangePowerStateToPriv: handleChangePowerStateToPriv( request ); break; case kIOPMRequestTypePowerOverrideOnPriv: case kIOPMRequestTypePowerOverrideOffPriv: handlePowerOverrideChanged( request ); break; case kIOPMRequestTypeActivityTickle: if (request) { bool setDeviceDesire = false; if (request->fArg1) { // Power rise from activity tickle. unsigned long ticklePowerState = (unsigned long) request->fArg0; if ((fDeviceDesire < ticklePowerState) && (ticklePowerState < fNumberOfPowerStates)) { setDeviceDesire = true; fIdleTimerMinPowerState = ticklePowerState; } } else if (fDeviceDesire > fIdleTimerMinPowerState) { // Power drop from idle timer expiration. request->fArg0 = (void *) (fDeviceDesire - 1); setDeviceDesire = true; } if (setDeviceDesire) { // handleChangePowerStateToPriv() does not check the // request type, as long as the args are appropriate // for kIOPMRequestTypeChangePowerStateToPriv. request->fArg1 = (void *) false; handleChangePowerStateToPriv( request ); } } break; default: IOPanic("executePMRequest: unknown request type"); } } //********************************************************************************* // [private] servicePMReplyQueue //********************************************************************************* bool IOService::servicePMReplyQueue( IOPMRequest * request, IOPMRequestQueue * queue ) { bool more = false; assert( request && queue ); assert( request->isReply() ); PM_TRACE("[A %02lx] %p [%p %s] State %ld\n", request->getType(), request, this, getName(), fMachineState); switch ( request->getType() ) { case kIOPMRequestTypeAllowPowerChange: case kIOPMRequestTypeCancelPowerChange: // Check if we are expecting this response. if (responseValid((unsigned long) request->fArg0, (int) request->fArg1)) { if (kIOPMRequestTypeCancelPowerChange == request->getType()) fDoNotPowerDown = true; if (checkForDone()) { stop_ack_timer(); if ( fResponseArray ) { fResponseArray->release(); fResponseArray = NULL; } more = true; } } break; case kIOPMRequestTypeAckPowerChange: more = handleAcknowledgePowerChange( request ); break; case kIOPMRequestTypeAckSetPowerState: if (fDriverTimer == -1) { // driver acked while setPowerState() call is in-flight. // take this ack, return value from setPowerState() is irrelevant. OUR_PMLog(kPMLogDriverAcknowledgeSet, (UInt32) this, fDriverTimer); fDriverTimer = 0; } else if (fDriverTimer > 0) { // expected ack, stop the timer stop_ack_timer(); #if LOG_SETPOWER_TIMES uint64_t nsec = computeTimeDeltaNS(&fDriverCallStartTime); if (nsec > LOG_SETPOWER_TIMES) PM_DEBUG("%s::setPowerState(%p, %lu -> %lu) async took %d ms\n", fName, this, fCurrentPowerState, fHeadNoteState, NS_TO_MS(nsec)); #endif OUR_PMLog(kPMLogDriverAcknowledgeSet, (UInt32) this, fDriverTimer); fDriverTimer = 0; more = true; } else { // unexpected ack OUR_PMLog(kPMLogAcknowledgeErr4, (UInt32) this, 0); } break; case kIOPMRequestTypeInterestChanged: handleInterestChanged( request ); more = true; break; #if SUPPORT_IDLE_CANCEL case kIOPMRequestTypeIdleCancel: if ((fMachineState == kIOPM_OurChangeTellClientsPowerDown) || (fMachineState == kIOPM_OurChangeTellPriorityClientsPowerDown)) { OUR_PMLog(kPMLogIdleCancel, (uintptr_t) this, 0); fDoNotPowerDown = true; if (fMachineState == kIOPM_OurChangeTellPriorityClientsPowerDown) cleanClientResponses(false); more = true; } break; #endif default: IOPanic("servicePMReplyQueue: unknown reply type"); } releasePMRequest( request ); return more; } //********************************************************************************* // IOPMRequest Class // // Requests from PM clients, and also used for inter-object messaging within PM. //********************************************************************************* OSDefineMetaClassAndStructors( IOPMRequest, IOCommand ); IOPMRequest * IOPMRequest::create( void ) { IOPMRequest * me = OSTypeAlloc(IOPMRequest); if (me && !me->init(0, kIOPMRequestTypeInvalid)) { me->release(); me = 0; } return me; } bool IOPMRequest::init( IOService * target, IOOptionBits type ) { if (!IOCommand::init()) return false; fType = type; fTarget = target; fParent = 0; fChildCount = 0; fArg0 = fArg1 = fArg2 = 0; if (fTarget) fTarget->retain(); return true; } void IOPMRequest::reset( void ) { assert( fChildCount == 0 ); fType = kIOPMRequestTypeInvalid; if (fParent) { fParent->fChildCount--; fParent = 0; } if (fTarget) { fTarget->release(); fTarget = 0; } } //********************************************************************************* // IOPMRequestQueue Class // // Global queues. As PM-aware drivers load and unload, their IOPMWorkQueue's are // created and deallocated. IOPMRequestQueue are created once and never released. //********************************************************************************* OSDefineMetaClassAndStructors( IOPMRequestQueue, IOEventSource ); IOPMRequestQueue * IOPMRequestQueue::create( IOService * inOwner, Action inAction ) { IOPMRequestQueue * me = OSTypeAlloc(IOPMRequestQueue); if (me && !me->init(inOwner, inAction)) { me->release(); me = 0; } return me; } bool IOPMRequestQueue::init( IOService * inOwner, Action inAction ) { if (!inAction || !IOEventSource::init(inOwner, (IOEventSourceAction)inAction)) return false; queue_init(&fQueue); fLock = IOLockAlloc(); return (fLock != 0); } void IOPMRequestQueue::free( void ) { if (fLock) { IOLockFree(fLock); fLock = 0; } return IOEventSource::free(); } void IOPMRequestQueue::queuePMRequest( IOPMRequest * request ) { assert(request); IOLockLock(fLock); queue_enter(&fQueue, request, IOPMRequest *, fCommandChain); IOLockUnlock(fLock); if (workLoop) signalWorkAvailable(); } void IOPMRequestQueue::queuePMRequestChain( IOPMRequest ** requests, IOItemCount count ) { IOPMRequest * next; assert(requests && count); IOLockLock(fLock); while (count--) { next = *requests; requests++; queue_enter(&fQueue, next, IOPMRequest *, fCommandChain); } IOLockUnlock(fLock); if (workLoop) signalWorkAvailable(); } bool IOPMRequestQueue::checkForWork( void ) { Action dqAction = (Action) action; IOPMRequest * request; IOService * target; bool more = false; IOLockLock( fLock ); while (!queue_empty(&fQueue)) { queue_remove_first( &fQueue, request, IOPMRequest *, fCommandChain ); IOLockUnlock( fLock ); target = request->getTarget(); assert(target); more |= (*dqAction)( target, request, this ); IOLockLock( fLock ); } IOLockUnlock( fLock ); return more; } void IOPMRequestQueue::signalWorkAvailable( void ) { IOEventSource::signalWorkAvailable(); } //********************************************************************************* // IOPMWorkQueue Class // // Every object in the power plane that has handled a PM request, will have an // instance of IOPMWorkQueue allocated for it. //********************************************************************************* OSDefineMetaClassAndStructors( IOPMWorkQueue, IOEventSource ); IOPMWorkQueue * IOPMWorkQueue::create( IOService * inOwner, Action work, Action retire ) { IOPMWorkQueue * me = OSTypeAlloc(IOPMWorkQueue); if (me && !me->init(inOwner, work, retire)) { me->release(); me = 0; } return me; } bool IOPMWorkQueue::init( IOService * inOwner, Action work, Action retire ) { if (!work || !retire || !IOEventSource::init(inOwner, (IOEventSourceAction)0)) return false; queue_init(&fWorkQueue); fWorkAction = work; fRetireAction = retire; return true; } void IOPMWorkQueue::queuePMRequest( IOPMRequest * request ) { assert( request ); assert( onThread() ); gIOPMBusyCount++; queue_enter(&fWorkQueue, request, IOPMRequest *, fCommandChain); checkForWork(); } bool IOPMWorkQueue::checkForWork( void ) { IOPMRequest * request; IOService * target = (IOService *) owner; bool done; while (!queue_empty(&fWorkQueue)) { request = (IOPMRequest *) queue_first(&fWorkQueue); assert(request->getTarget() == target); if (request->hasChildRequest()) break; done = (*fWorkAction)( target, request, this ); if (!done) break; assert(gIOPMBusyCount > 0); if (gIOPMBusyCount) gIOPMBusyCount--; queue_remove_first(&fWorkQueue, request, IOPMRequest *, fCommandChain); (*fRetireAction)( target, request, this ); } return false; } OSDefineMetaClassAndStructors(IOServicePM, OSObject) //********************************************************************************* // serialize // // Serialize IOServicePM for debugging. //********************************************************************************* static void setPMProperty( OSDictionary * dict, const char * key, unsigned long value ) { OSNumber * num = OSNumber::withNumber(value, sizeof(value) * 8); if (num) { dict->setObject(key, num); num->release(); } } bool IOServicePM::serialize( OSSerialize * s ) const { OSDictionary * dict; bool ok = false; dict = OSDictionary::withCapacity(8); if (dict) { setPMProperty( dict, "CurrentPowerState", CurrentPowerState ); if (DesiredPowerState != CurrentPowerState) setPMProperty( dict, "DesiredPowerState", DesiredPowerState ); if (kIOPM_Finished != MachineState) setPMProperty( dict, "MachineState", MachineState ); if (ChildrenDesire) setPMProperty( dict, "ChildrenPowerState", ChildrenDesire ); if (DeviceDesire) setPMProperty( dict, "DeviceChangePowerState", DeviceDesire ); if (DriverDesire) setPMProperty( dict, "DriverChangePowerState", DriverDesire ); if (DeviceOverrides) dict->setObject( "PowerOverrideOn", kOSBooleanTrue ); ok = dict->serialize(s); dict->release(); } return ok; }