]> git.saurik.com Git - apple/xnu.git/blobdiff - iokit/Drivers/platform/drvAppleRootDomain/RootDomain.cpp
xnu-201.tar.gz
[apple/xnu.git] / iokit / Drivers / platform / drvAppleRootDomain / RootDomain.cpp
index 680e40b2277fb0a9ebfba101665a9620a87ad937..f3373a0dc8c74bd1951105331b59e3949d108c84 100644 (file)
@@ -27,6 +27,7 @@
 #include <IOKit/pwr_mgt/IOPM.h>
 #include <IOKit/IOMessage.h>
 #include "RootDomainUserClient.h"
+#include "IOKit/pwr_mgt/IOPowerConnection.h"
 
 extern "C" {
 extern void kprintf(const char *, ...);
@@ -35,24 +36,32 @@ extern void kprintf(const char *, ...);
 extern const IORegistryPlane * gIOPowerPlane;
 
 void PMreceiveCmd ( OSObject *,  void *, void *, void *, void * );
-bool rootHasPMU( OSObject * us, void *, IOService * yourDevice );
+static void sleepTimerExpired(thread_call_param_t);
 
 
-#define number_of_power_states 3
+#define number_of_power_states 5
 #define OFF_STATE 0
-#define SLEEP_STATE 1
-#define ON_STATE 2
+#define RESTART_STATE 1
+#define SLEEP_STATE 2
+#define DOZE_STATE 3
+#define ON_STATE 4
 
-#define ON_POWER IOPMPowerOn
-#define SLEEP_POWER IOPMAuxPowerOn
+#define ON_POWER kIOPMPowerOn
+#define RESTART_POWER kIOPMRestart
+#define SLEEP_POWER kIOPMAuxPowerOn
+#define DOZE_POWER kIOPMDoze
 
 static IOPMPowerState ourPowerStates[number_of_power_states] = {
-    {1,0,0,0,0,0,0,0,0,0,0,0},
-    {1,0,0,SLEEP_POWER,0,0,0,0,0,0,0,0},
-    {1,IOPMPowerOn,IOPMPowerOn,ON_POWER,0,0,0,0,0,0,0,0},
+    {1,0,                      0,              0,0,0,0,0,0,0,0,0},             // state 0, off
+    {1,kIOPMRestartCapability, kIOPMRestart,   RESTART_POWER,0,0,0,0,0,0,0,0}, // state 1, restart
+    {1,kIOPMSleepCapability,   kIOPMSleep,     SLEEP_POWER,0,0,0,0,0,0,0,0},   // state 2, sleep
+    {1,kIOPMDoze,              kIOPMDoze,      DOZE_POWER,0,0,0,0,0,0,0,0},    // state 3, doze
+    {1,kIOPMPowerOn,           kIOPMPowerOn,   ON_POWER,0,0,0,0,0,0,0,0},      // state 4, on
 };
 
 static IOPMrootDomain * gRootDomain;
+static UInt32           gSleepOrShutdownPending = 0;
+
 
 #define super IOService
 OSDefineMetaClassAndStructors(IOPMrootDomain,IOService)
@@ -64,13 +73,108 @@ extern "C"
         return gRootDomain->registerInterest( gIOGeneralInterest, handler, self, ref );
     }
 
+    IONotifier * registerPrioritySleepWakeInterest(IOServiceInterestHandler handler, void * self, void * ref = 0)
+    {
+        return gRootDomain->registerInterest( gIOPriorityPowerStateInterest, handler, self, ref );
+    }
+
     IOReturn acknowledgeSleepWakeNotification(void * PMrefcon)
     {
         return gRootDomain->allowPowerChange ( (unsigned long)PMrefcon );
     }
 
+    IOReturn vetoSleepWakeNotification(void * PMrefcon)
+    {
+        return gRootDomain->cancelPowerChange ( (unsigned long)PMrefcon );
+    }
+    
+    IOReturn rootDomainRestart ( void )
+    {
+        return gRootDomain->restartSystem();
+    }
+    
+    IOReturn rootDomainShutdown ( void )
+    {
+        return gRootDomain->shutdownSystem();
+    }
+
+       void IOSystemShutdownNotification ( void )
+    {
+        for ( int i = 0; i < 100; i++ )
+        {
+            if ( OSCompareAndSwap( 0, 1, &gSleepOrShutdownPending ) ) break;
+            IOSleep( 100 );
+        }
+    }
+
+    int sync_internal(void);    
 }
 
+/*
+A device is always in the highest power state which satisfies its driver, its policy-maker, and any power domain
+children it has, but within the constraint of the power state provided by its parent.  The driver expresses its desire by
+calling changePowerStateTo(), the policy-maker expresses its desire by calling changePowerStateToPriv(), and the children
+express their desires by calling requestPowerDomainState().
+
+The Root Power Domain owns the policy for idle and demand sleep and doze for the system.  It is a power-managed IOService just
+like the others in the system.  It implements several power states which correspond to what we see as Sleep, Doze, etc.
+
+The sleep/doze policy is as follows:
+Sleep and Doze are prevented if the case is open so that nobody will think the machine is off and plug/unplug cards.
+Sleep and Doze are prevented if the sleep timeout slider in the preferences panel is at zero.
+The system cannot Sleep, but can Doze if some object in the tree is in a power state marked kIOPMPreventSystemSleep.
+
+These three conditions are enforced using the "driver clamp" by calling changePowerStateTo().  For example, if the case is
+opened, changePowerStateTo(ON_STATE) is called to hold the system on regardless of the desires of the children of the root or
+the state of the other clamp.
+
+Demand Sleep/Doze is initiated by pressing the front panel power button, closing the clamshell, or selecting the menu item.
+In this case the root's parent actually initiates the power state change so that the root has no choice and does not give
+applications the opportunity to veto the change.
+
+Idle Sleep/Doze occurs if no objects in the tree are in a state marked kIOPMPreventIdleSleep.  When this is true, the root's
+children are not holding the root on, so it sets the "policy-maker clamp" by calling changePowerStateToPriv(ON_STATE)
+to hold itself on until the sleep timer expires.  This timer is set for the difference between the sleep timeout slider and
+the larger of the display dim timeout slider and the disk spindown timeout slider in the Preferences panel.  For example, if
+the system is set to sleep after thirty idle minutes, and the display and disk are set to sleep after five idle minutes,
+when there is no longer an object in the tree holding the system out of Idle Sleep (via kIOPMPreventIdleSleep), the root
+sets its timer for 25 minutes (30 - 5).  When the timer expires, it releases its clamp and now nothing is holding it awake,
+so it falls asleep.
+
+Demand sleep is prevented when the system is booting.  When preferences are transmitted by the loginwindow at the end of
+boot, a flag is cleared, and this allows subsequent Demand Sleep.
+
+The system will not Sleep, but will Doze if some object calls setSleepSupported(kPCICantSleep) during a power change to the sleep state (this can be done by the PCI Aux Power Supply drivers, Slots99, MacRISC299, etc.).  This is not enforced with
+a clamp, but sets a flag which is noticed before actually sleeping the kernel.  If the flag is set, the root steps up
+one power state from Sleep to Doze, and any objects in the tree for which this is relevent will act appropriately (USB and
+ADB will turn on again so that they can wake the system out of Doze (keyboard/mouse activity will cause the Display Wrangler
+to be tickled)).
+*/
+
+
+// **********************************************************************************
+
+IOPMrootDomain * IOPMrootDomain::construct( void )
+{
+    IOPMrootDomain * root;
+
+    root = new IOPMrootDomain;
+    if( root)
+        root->init();
+
+    return( root );
+}
+
+// **********************************************************************************
+
+static void disk_sync_callout(thread_call_param_t p0, thread_call_param_t p1)
+{
+    IOService * rootDomain = (IOService *) p0;
+    unsigned long pmRef = (unsigned long) p1;
+
+    sync_internal();
+    rootDomain->allowPowerChange(pmRef);
+}
 
 // **********************************************************************************
 // start
@@ -78,6 +182,8 @@ extern "C"
 // We don't do much here.  The real initialization occurs when the platform
 // expert informs us we are the root.
 // **********************************************************************************
+
+
 bool IOPMrootDomain::start ( IOService * nub )
 {
     super::start(nub);
@@ -85,19 +191,26 @@ bool IOPMrootDomain::start ( IOService * nub )
     gRootDomain = this;
 
     PMinit();
+    setProperty("IOSleepSupported","");
     allowSleep = true;
-    sleepIsSupported = false;
-    idlePeriod = 0;
+    sleepIsSupported = true;
     systemBooting = true;
-//    systemBooting = false;   // temporary work-around for 2589847
-    ignoringClamshell = false;
-
+    ignoringClamshell = true;
+    sleepSlider = 0;
+    idleSleepPending = false;
+    canSleep = true;
+    wrangler = NULL;
+    sleepASAP = false;
+    
     pm_vars->PMworkloop = IOWorkLoop::workLoop();                              // make the workloop
     pm_vars->commandQueue = IOCommandQueue::commandQueue(this, PMreceiveCmd);  // make a command queue
     if (! pm_vars->commandQueue ||
         (  pm_vars->PMworkloop->addEventSource( pm_vars->commandQueue) != kIOReturnSuccess) ) {
         return IOPMNoErr;
     }
+    extraSleepTimer = thread_call_allocate((thread_call_func_t)sleepTimerExpired, (thread_call_param_t) this);
+
+    diskSyncCalloutEntry = thread_call_allocate(&disk_sync_callout, (thread_call_param_t) this);
 
     patriarch = new IORootParent;                               // create our parent
     patriarch->init();
@@ -109,10 +222,13 @@ bool IOPMrootDomain::start ( IOService * nub )
     
     registerPowerDriver(this,ourPowerStates,number_of_power_states);
 
-    // Clamp power on.  We will revisit this decision when the login window is displayed
-    // and we receive preferences via SetAggressiveness.
-    changePowerStateToPriv(ON_STATE);                          // clamp power on
-    powerOverrideOnPriv();
+    setPMRootDomain(this);
+    changePowerStateToPriv(ON_STATE);                          // set a clamp until we sleep
+
+    registerPrioritySleepWakeInterest( &sysPowerDownHandler, this, 0); // install power change handler
+
+    // Register for a notification when IODisplayWrangler is published
+    addNotification( gIOPublishNotification, serviceMatching("IODisplayWrangler"), &displayWranglerPublished, this, 0);
 
     registerService();                                         // let clients find us
 
@@ -133,7 +249,6 @@ IOReturn IOPMrootDomain::youAreRoot ( void )
     return IOPMNoErr;
 }
 
-
 // **********************************************************************************
 // command_received
 //
@@ -144,14 +259,36 @@ IOReturn IOPMrootDomain::youAreRoot ( void )
 void IOPMrootDomain::command_received ( void * command, void * x, void * y, void * z )
 {
     switch ( (int)command ) {
-        case kPMbroadcastAggressiveness:
+        case kIOPMBroadcastAggressiveness:
+        
+            super::setAggressiveness((unsigned long)x,(unsigned long)y);
+
+            // Save user's spin down timer to restore after we replace it for idle sleep
+            if( (int)x == kPMMinutesToSpinDown ) user_spindown = (unsigned int) y;
+
+            // Use longestNonSleepSlider to calculate dimming adjust idle sleep timer
+            longestNonSleepSlider = pm_vars->current_aggressiveness_values[kPMMinutesToDim];
+
+
             if ( (int)x == kPMMinutesToSleep ) {
-                idlePeriod = (int)y*60;
-                if ( allowSleep && sleepIsSupported ) {
-                    setIdleTimerPeriod(idlePeriod);            // set new timeout
+                if ( (sleepSlider == 0) && ((int)y != 0) ) {
+                    sleepSlider = (int)y;
+                    adjustPowerState();                                // idle sleep is now enabled, maybe sleep now
                 }
+                sleepSlider = (int)y;
+                if ( sleepSlider == 0 ) {                      
+                    adjustPowerState();                                // idle sleep is now disabled
+                    patriarch->wakeSystem();                   // make sure we're powered
+                }
+            }
+            if ( sleepSlider > longestNonSleepSlider ) {
+                extraSleepDelay = sleepSlider - longestNonSleepSlider ;
+            }
+            else {
+                extraSleepDelay = 0;
             }
             break;
+            
         default:
             super::command_received(command,x,y,z);
             break;
@@ -159,6 +296,33 @@ void IOPMrootDomain::command_received ( void * command, void * x, void * y, void
 }
 
 
+// **********************************************************************************
+// sleepTimerExpired
+//
+// **********************************************************************************
+static void sleepTimerExpired ( thread_call_param_t us)
+{
+    ((IOPMrootDomain *)us)->handleSleepTimerExpiration();
+    }
+    
+    
+// **********************************************************************************
+// handleSleepTimerExpiration
+//
+// The time between the sleep idle timeout and the next longest one has elapsed.
+// It's time to sleep.  Start that by removing the clamp that's holding us awake.
+// **********************************************************************************
+void IOPMrootDomain::handleSleepTimerExpiration ( void )
+{
+    // accelerate disk spin down if spin down timer is non-zero (zero = never spin down)  
+    if(0 != user_spindown)
+        setQuickSpinDownTimeout();
+
+    sleepASAP = true;
+    adjustPowerState();
+}
+
+
 //*********************************************************************************
 // setAggressiveness
 //
@@ -171,10 +335,13 @@ void IOPMrootDomain::command_received ( void * command, void * x, void * y, void
 
 IOReturn IOPMrootDomain::setAggressiveness ( unsigned long type, unsigned long newLevel )
 {
-    systemBooting = false;  // when the finder launches, this method gets called -- system booting is done.
+    if ( systemBooting && (type == kPMMinutesToDim) ) {
+        systemBooting = false;  // when the login window launches, this method gets called -- system booting is done.
+        IOLog("Root power domain receiving initial preferences\n");
+        adjustPowerState();
+    }
 
-    pm_vars->commandQueue->enqueueCommand(true, (void *)kPMbroadcastAggressiveness, (void *) type, (void *) newLevel );
-    super::setAggressiveness(type,newLevel);
+    pm_vars->commandQueue->enqueueCommand(true, (void *)kIOPMBroadcastAggressiveness, (void *) type, (void *) newLevel );
     
     return kIOReturnSuccess;
 }
@@ -189,7 +356,34 @@ IOReturn IOPMrootDomain::sleepSystem ( void )
     kprintf("sleep demand received\n");
     if ( !systemBooting && allowSleep && sleepIsSupported ) {
         patriarch->sleepSystem();
+        return kIOReturnSuccess;
     }
+    if ( !systemBooting && allowSleep && !sleepIsSupported ) {
+        patriarch->dozeSystem();
+        return kIOReturnSuccess;
+    }
+    return kIOReturnSuccess;
+}
+
+
+// **********************************************************************************
+// shutdownSystem
+//
+// **********************************************************************************
+IOReturn IOPMrootDomain::shutdownSystem ( void )
+{
+    patriarch->shutDownSystem();
+    return kIOReturnSuccess;
+}
+
+
+// **********************************************************************************
+// restartSystem
+//
+// **********************************************************************************
+IOReturn IOPMrootDomain::restartSystem ( void )
+{
+    patriarch->restartSystem();
     return kIOReturnSuccess;
 }
 
@@ -198,18 +392,96 @@ IOReturn IOPMrootDomain::sleepSystem ( void )
 // powerChangeDone
 //
 // This overrides powerChangeDone in IOService.
-// If we just finished switching to state zero, call the platform expert to
-// sleep the kernel.
-// Then later, when we awake, the kernel returns here and we wake the system.
+//
+// Finder sleep and idle sleep move us from the ON state to the SLEEP_STATE.
+// In this case:
+// If we just finished going to the SLEEP_STATE, and the platform is capable of true sleep,
+// sleep the kernel.  Otherwise switch up to the DOZE_STATE which will keep almost
+// everything as off as it can get.
+//
+// **********************************************************************************
+void IOPMrootDomain::powerChangeDone ( unsigned long previousState )
+{
+    OSNumber *         propertyPtr;
+    unsigned short     theProperty;
+    AbsoluteTime        deadline;
+    
+    switch ( pm_vars->myCurrentState ) {
+        case SLEEP_STATE:
+            if ( canSleep && sleepIsSupported ) {
+                idleSleepPending = false;                      // re-enable this timer for next sleep
+                IOLog("System Sleep\n");
+                pm_vars->thePlatform->sleepKernel();           // sleep now
+                
+                                                // now we're waking
+                clock_interval_to_deadline(30, kSecondScale, &deadline);       // stay awake for at least 30 seconds
+                thread_call_enter_delayed(extraSleepTimer, deadline);
+                idleSleepPending = true;                       // this gets turned off when we sleep again
+                gSleepOrShutdownPending = 0;        // sleep transition complete
+                patriarch->wakeSystem();                       // get us some power
+                
+                IOLog("System Wake\n");
+                systemWake();                                  // tell the tree we're waking 
+
+                propertyPtr = OSDynamicCast(OSNumber,getProperty("WakeEvent"));
+                if ( propertyPtr ) {                           // find out what woke us
+                    theProperty = propertyPtr->unsigned16BitValue();
+                    IOLog("Wake event %04x\n",theProperty);
+                    if ( (theProperty == 0x0008) ||    //lid
+                        (theProperty == 0x0800) ||     // front panel button
+                        (theProperty == 0x0020) ||     // external keyboard
+                        (theProperty == 0x0001) ) {    // internal keyboard
+                        reportUserInput();
+                    }
+                }
+                else {
+                    IOLog("Unknown wake event\n");
+                    reportUserInput();                         // don't know, call it user input then
+                }
+                
+                changePowerStateToPriv(ON_STATE);              // wake for thirty seconds
+                powerOverrideOffPriv();
+            }
+            else {
+                patriarch->sleepToDoze();              // allow us to step up a power state
+                changePowerStateToPriv(DOZE_STATE);    // and do it
+            }
+            break;
+
+        case DOZE_STATE:
+            if ( previousState != DOZE_STATE ) {
+                IOLog("System Doze\n");
+            }
+            idleSleepPending = false;                  // re-enable this timer for next sleep
+            gSleepOrShutdownPending = 0;
+            break;
+            
+       case RESTART_STATE:
+            IOLog("System Restart\n");
+            PEHaltRestart(kPERestartCPU);
+            break;
+            
+       case OFF_STATE:
+            IOLog("System Halt\n");
+            PEHaltRestart(kPEHaltCPU);
+            break;
+    }
+}
+
+
+// **********************************************************************************
+// wakeFromDoze
+//
+// The Display Wrangler calls here when it switches to its highest state.  If the 
+// system is currently dozing, allow it to wake by making sure the parent is
+// providing power.
 // **********************************************************************************
-void IOPMrootDomain::powerChangeDone ( unsigned long powerStateOrdinal )
+void IOPMrootDomain::wakeFromDoze( void )
 {
-    if ( powerStateOrdinal == SLEEP_STATE ) {
-        pm_vars->thePlatform->sleepKernel();
-       activityTickle(kIOPMSubclassPolicy);    // reset idle sleep
-        systemWake();                          // tell the tree we're waking 
-        patriarch->wakeSystem();               // make sure we have power
-        changePowerStateToPriv(ON_STATE);      // and wake
+    if ( pm_vars->myCurrentState == DOZE_STATE ) {
+        canSleep = true;                               // reset this till next attempt
+        powerOverrideOffPriv();
+        patriarch->wakeSystem();                       // allow us to wake if children so desire
     }
 }
 
@@ -252,10 +524,6 @@ IOReturn IOPMrootDomain::receivePowerNotification (UInt32 msg)
       (void) sleepSystem ();
     }
     
-    if (msg & kIOPMPowerButton) {
-      (void) sleepSystem ();
-    }
-
     if (msg & kIOPMPowerEmergency) {
       (void) sleepSystem ();
     }
@@ -266,27 +534,44 @@ IOReturn IOPMrootDomain::receivePowerNotification (UInt32 msg)
         }
     }
 
-    if (msg & kIOPMIgnoreClamshell) {
+    if (msg & kIOPMEnableClamshell) {
+        ignoringClamshell = false;
+    }
+    if (msg & kIOPMDisableClamshell) {
         ignoringClamshell = true;
     }
 
-    if (msg & kIOPMAllowSleep) {
-        if ( sleepIsSupported ) {
-            setIdleTimerPeriod(idlePeriod);
+    if (msg & kIOPMPowerButton) {                              // toggle state of sleep/wake
+        if ( pm_vars->myCurrentState == DOZE_STATE ) {         // are we dozing?
+            systemWake();                                      // yes, tell the tree we're waking 
+            reportUserInput();                                 // wake the Display Wrangler
+        }
+        else {
+            (void) sleepSystem ();
         }
+    }
+
+    // if the case has been closed, we allow
+    // the machine to be put to sleep or to idle sleep
+
+    if ( (msg & kIOPMAllowSleep) && !allowSleep ) {
        allowSleep = true;
-       changePowerStateTo (0);
+        adjustPowerState();
     }
 
-    // if the case is open on some machines, we must now
-    // allow the machine to be put to sleep or to idle sleep
+    // if the case has been opened, we disallow sleep/doze
 
     if (msg & kIOPMPreventSleep) {
-        if ( sleepIsSupported ) {
-            setIdleTimerPeriod(0);
-        }
        allowSleep = false;
-       changePowerStateTo (number_of_power_states-1);
+        if ( pm_vars->myCurrentState == DOZE_STATE ) {         // are we dozing?
+            systemWake();                                      // yes, tell the tree we're waking 
+            adjustPowerState();
+            reportUserInput();                                 // wake the Display Wrangler
+        }
+        else {
+            adjustPowerState();
+            patriarch->wakeSystem();                           // make sure we have power to clamp
+        }
     }
 
    return 0;
@@ -300,19 +585,79 @@ IOReturn IOPMrootDomain::receivePowerNotification (UInt32 msg)
 
 void IOPMrootDomain::setSleepSupported( IOOptionBits flags )
 {
-    platformSleepSupport = flags;
-    if ( flags & kRootDomainSleepSupported ) {
-        sleepIsSupported = true;
-        setProperty("IOSleepSupported","");
+    if ( flags & kPCICantSleep ) {
+        canSleep = false;
     }
-    else
-    {
-        sleepIsSupported = false;
-        removeProperty("IOSleepSupported");
+    else {
+        platformSleepSupport = flags;
+    }
+
+}
+
+//*********************************************************************************
+// requestPowerDomainState
+//
+// The root domain intercepts this call to the superclass.
+//
+// If the clamp bit is not set in the desire, then the child doesn't need the power
+// state it's requesting; it just wants it.  The root ignores desires but not needs.
+// If the clamp bit is not set, the root takes it that the child can tolerate no
+// power and interprets the request accordingly.  If all children can thus tolerate
+// no power, we are on our way to idle sleep.
+//*********************************************************************************
+
+IOReturn IOPMrootDomain::requestPowerDomainState ( IOPMPowerFlags desiredState, IOPowerConnection * whichChild, unsigned long specification )
+{
+    OSIterator *       iter;
+    OSObject *         next;
+    IOPowerConnection *        connection;
+    unsigned long      powerRequestFlag = 0;
+    IOPMPowerFlags     editedDesire = desiredState;
+
+    if ( !(desiredState & kIOPMPreventIdleSleep) ) {           // if they don't really need it, they don't get it
+        editedDesire = 0;
+    }
+
+
+    IOLockLock(pm_vars->childLock);                    // recompute sleepIsSupported
+                                                        // and see if all children are asleep
+    iter = getChildIterator(gIOPowerPlane);
+    sleepIsSupported = true;
+
+    if ( iter ) {
+        while ( (next = iter->getNextObject()) ) {
+            if ( (connection = OSDynamicCast(IOPowerConnection,next)) ) {
+                if ( connection == whichChild ) {
+                    powerRequestFlag += editedDesire;
+                    if ( desiredState & kIOPMPreventSystemSleep ) {
+                        sleepIsSupported = false;
+                    }
+                }
+                else {
+                    powerRequestFlag += connection->getDesiredDomainState();
+                    if ( connection->getPreventSystemSleepFlag() ) {
+                        sleepIsSupported = false;
+                    }
+                }
+            }
+        }
+        iter->release();
+    }
+
+    if ( (extraSleepDelay == 0) &&  (powerRequestFlag == 0) ) {
+        sleepASAP = true;
     }
+    
+    adjustPowerState();                                        // this may put the system to sleep
+    
+    IOLockUnlock(pm_vars->childLock);
+
+    editedDesire |= desiredState & kIOPMPreventSystemSleep;
 
+    return super::requestPowerDomainState(editedDesire,whichChild,specification);
 }
 
+
 //*********************************************************************************
 // getSleepSupported
 //
@@ -333,10 +678,16 @@ IOOptionBits IOPMrootDomain::getSleepSupported( void )
 
 bool IOPMrootDomain::tellChangeDown ( unsigned long stateNum )
 {
-    if ( stateNum == SLEEP_STATE ) {
-        return super::tellClientsWithResponse(kIOMessageSystemWillSleep);
+    switch ( stateNum ) {
+        case DOZE_STATE:
+        case SLEEP_STATE:
+            return super::tellClientsWithResponse(kIOMessageSystemWillSleep);
+        case RESTART_STATE:
+            return super::tellClientsWithResponse(kIOMessageSystemWillRestart);
+        case OFF_STATE:
+            return super::tellClientsWithResponse(kIOMessageSystemWillPowerOff);
     }
-    return super::tellChangeDown(stateNum);
+    return super::tellChangeDown(stateNum);            // this shouldn't execute
 }
 
 
@@ -345,14 +696,13 @@ bool IOPMrootDomain::tellChangeDown ( unsigned long stateNum )
 //
 // We override the superclass implementation so we can send a different message
 // type to the client or application being notified.
+//
+// This must be idle sleep since we don't ask apps during any other power change.
 //*********************************************************************************
 
-bool IOPMrootDomain::askChangeDown (unsigned long stateNum)
+bool IOPMrootDomain::askChangeDown ( unsigned long )
 {
-    if ( stateNum == SLEEP_STATE ) {
-        return super::tellClientsWithResponse(kIOMessageCanSystemSleep);
-    }
-    return super::askChangeDown(stateNum);
+    return super::tellClientsWithResponse(kIOMessageCanSystemSleep);
 }
 
 
@@ -364,6 +714,8 @@ bool IOPMrootDomain::askChangeDown (unsigned long stateNum)
 //
 // We override the superclass implementation so we can send a different message
 // type to the client or application being notified.
+//
+// This must be a vetoed idle sleep, since no other power change can be vetoed.
 //*********************************************************************************
 
 void IOPMrootDomain::tellNoChangeDown ( unsigned long )
@@ -386,19 +738,264 @@ void IOPMrootDomain::tellChangeUp ( unsigned long )
     return tellClients(kIOMessageSystemHasPoweredOn);
 }
 
+//*********************************************************************************
+// reportUserInput
+//
+//*********************************************************************************
+
+void IOPMrootDomain::reportUserInput ( void )
+{
+    OSIterator * iter;
 
-// **********************************************************************************
-// activityTickle
+    if(!wrangler) {
+        iter = getMatchingServices(serviceMatching("IODisplayWrangler"));
+        if(iter) {
+            wrangler = (IOService *) iter->getNextObject();
+            iter->release();
+        }
+    }
+
+    if(wrangler)
+        wrangler->activityTickle(0,0);
+}
+
+//*********************************************************************************
+// setQuickSpinDownTimeout
 //
-// This is called by the HID system and calls the superclass in turn.
-// **********************************************************************************
+//*********************************************************************************
+
+void IOPMrootDomain::setQuickSpinDownTimeout ( void )
+{
+    //IOLog("setQuickSpinDownTimeout\n");
+    super::setAggressiveness((unsigned long)kPMMinutesToSpinDown,(unsigned long)1);
+}
+
+//*********************************************************************************
+// restoreUserSpinDownTimeout
+//
+//*********************************************************************************
+
+void IOPMrootDomain::restoreUserSpinDownTimeout ( void )
+{
+    if(systemBooting) {
+        IOLog("!!!!! WARNING !!!!! restoreUserSpinDownTimeout called too early\n");
+    }
+    //IOLog("restoreUserSpinDownTimeout, user_spindown = %u\n", user_spindown);
+
+    super::setAggressiveness((unsigned long)kPMMinutesToSpinDown,(unsigned long)user_spindown);
+}
+
+
+//*********************************************************************************
+// sysPowerDownHandler
+//
+// Receives a notification when the RootDomain changes state. 
+//
+// Allows us to take action on system sleep, power down, and restart after
+// applications have received their power change notifications and replied,
+// but before drivers have powered down. We perform a vfs sync on power down.
+//*********************************************************************************
+
+IOReturn IOPMrootDomain::sysPowerDownHandler( void * target, void * refCon,
+                                    UInt32 messageType, IOService * service,
+                                    void * messageArgument, vm_size_t argSize )
+{
+    IOReturn ret;
+    IOPowerStateChangeNotification * params = (IOPowerStateChangeNotification *) messageArgument;
+    IOPMrootDomain *                rootDomain = OSDynamicCast(IOPMrootDomain, service);
+
+    if(!rootDomain)
+        return kIOReturnUnsupported;
+
+    switch (messageType) {
+        case kIOMessageSystemWillSleep:
+            rootDomain->powerOverrideOnPriv();         // start ignoring children's requests
+                                                        // (fall through to other cases)
+        case kIOMessageSystemWillPowerOff:
+        case kIOMessageSystemWillRestart:
+
+            // Interested applications have been notified of an impending power
+            // change and have acked (when applicable).
+            // This is our chance to save whatever state we can before powering
+            // down.
+            // We call sync_internal defined in xnu/bsd/vfs/vfs_syscalls.c,
+            // via callout
+
+            // We will ack within 20 seconds
+            params->returnValue = 20 * 1000 * 1000;
+
+            if ( ! OSCompareAndSwap( 0, 1, &gSleepOrShutdownPending ) )
+            {
+                // Purposely delay the ack and hope that shutdown occurs quickly.
+                // Another option is not to schedule the thread and wait for
+                // ack timeout...
+                AbsoluteTime deadline;
+                clock_interval_to_deadline( 15, kSecondScale, &deadline );
+                thread_call_enter1_delayed( rootDomain->diskSyncCalloutEntry, 
+                                            (thread_call_param_t)params->powerRef,
+                                            deadline );
+            }
+            else
+                thread_call_enter1(rootDomain->diskSyncCalloutEntry, (thread_call_param_t)params->powerRef);
+            ret = kIOReturnSuccess;
+            break;
+        default:
+            ret = kIOReturnUnsupported;
+            break;
+    }
+    return ret;
+}
+               
+//*********************************************************************************
+// displayWranglerNotification
+//
+// Receives a notification when the IODisplayWrangler changes state.
+//
+// Allows us to take action on display dim/undim.
+//
+// When the display goes dim we:
+// - Start the idle sleep timer
+// - set the quick spin down timeout
+//
+// On wake from display dim:
+// - Cancel the idle sleep timer
+// - restore the user's chosen spindown timer from the "quick" spin down value
+//*********************************************************************************
 
-bool IOPMrootDomain::activityTickle ( unsigned long, unsigned long x=0 )
+IOReturn IOPMrootDomain::displayWranglerNotification( void * target, void * refCon,
+                                    UInt32 messageType, IOService * service,
+                                    void * messageArgument, vm_size_t argSize )
 {
-    return super::activityTickle (kIOPMSuperclassPolicy1,ON_STATE);
+    IOPMrootDomain *                rootDomain = OSDynamicCast(IOPMrootDomain, (IOService *)target);
+    AbsoluteTime                 deadline;
+    static bool                  deviceAlreadyPoweredOff = false;
+
+    if(!rootDomain)
+        return kIOReturnUnsupported;
+
+    switch (messageType) {
+       case kIOMessageDeviceWillPowerOff:
+            // The IODisplayWrangler has powered off either because of idle display sleep
+            // or force system sleep.
+            
+            // The display wrangler will send the DeviceWillPowerOff message 4 times until
+            // it gets into its lowest state. We only want to act on the first of those 4.
+            if( deviceAlreadyPoweredOff ) return kIOReturnUnsupported;
+
+           deviceAlreadyPoweredOff = true;
+
+           if( rootDomain->extraSleepDelay ) {
+
+                // start the extra sleep timer
+                clock_interval_to_deadline(rootDomain->extraSleepDelay*60, kSecondScale, &deadline );
+                thread_call_enter_delayed(rootDomain->extraSleepTimer, deadline);
+                rootDomain->idleSleepPending = true;
+
+            } else {
+
+                // accelerate disk spin down if spin down timer is non-zero (zero = never spin down)
+                if(0 != rootDomain->user_spindown)
+                    rootDomain->setQuickSpinDownTimeout();
+            }
+
+             break;
+
+        case kIOMessageDeviceHasPoweredOn:
+
+            // The display has powered on either because of UI activity or wake from sleep/doze
+            deviceAlreadyPoweredOff = false;
+            rootDomain->adjustPowerState();
+            
+
+            // cancel any pending idle sleep
+            if(rootDomain->idleSleepPending) {
+                thread_call_cancel(rootDomain->extraSleepTimer);
+                rootDomain->idleSleepPending = false;
+            }
+
+            // Change the spindown value back to the user's selection from our accelerated setting
+            if(0 != rootDomain->user_spindown)
+                rootDomain->restoreUserSpinDownTimeout();
+
+            // Put on the policy maker's on clamp.
+
+            break;
+
+         default:
+             break;
+     }
+     return kIOReturnUnsupported;
+ }
+
+//*********************************************************************************
+// displayWranglerPublished
+//
+// Receives a notification when the IODisplayWrangler is published.
+// When it's published we install a power state change handler.
+//
+//*********************************************************************************
+
+bool IOPMrootDomain::displayWranglerPublished( void * target, void * refCon,
+                                    IOService * newService)
+{
+    IOPMrootDomain *                rootDomain = OSDynamicCast(IOPMrootDomain, (IOService *)target);
+
+    if(!rootDomain)
+        return false;
+
+       rootDomain->wrangler = newService;
+
+       // we found the display wrangler, now install a handler
+       if( !rootDomain->wrangler->registerInterest( gIOGeneralInterest, &displayWranglerNotification, target, 0) ) {
+               IOLog("IOPMrootDomain::displayWranglerPublished registerInterest failed\n");
+               return false;
+       }
+
+       return true;
 }
 
 
+//*********************************************************************************
+// adjustPowerState
+//
+// Some condition that affects our wake/sleep/doze decision has changed.
+//
+// If the sleep slider is in the off position, we cannot sleep or doze.
+// If the enclosure is open, we cannot sleep or doze.
+// If the system is still booting, we cannot sleep or doze.
+//
+// In those circumstances, we prevent sleep and doze by holding power on with
+// changePowerStateToPriv(ON).
+//
+// If the above conditions do not exist, and also the sleep timer has expired, we
+// allow sleep or doze to occur with either changePowerStateToPriv(SLEEP) or
+// changePowerStateToPriv(DOZE) depending on whether or not we already know the
+// platform cannot sleep.
+//
+// In this case, sleep or doze will either occur immediately or at the next time
+// that no children are holding the system out of idle sleep via the 
+// kIOPMPreventIdleSleep flag in their power state arrays.
+//*********************************************************************************
+
+void IOPMrootDomain::adjustPowerState( void )
+{
+    if ( (sleepSlider == 0) ||
+        ! allowSleep ||
+        systemBooting ) {
+        changePowerStateToPriv(ON_STATE);
+    }
+    else {
+        if ( sleepASAP ) {
+            sleepASAP = false;
+            if ( sleepIsSupported ) {
+                changePowerStateToPriv(SLEEP_STATE);
+            }
+            else {
+                changePowerStateToPriv(DOZE_STATE);
+            }
+        }
+    }
+}
 
 
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@@ -408,24 +1005,26 @@ bool IOPMrootDomain::activityTickle ( unsigned long, unsigned long x=0 )
 
 OSDefineMetaClassAndStructors(IORootParent, IOService)
 
-#define number_of_patriarch_power_states 3
+// This array exactly parallels the state array for the root domain.
+// Power state changes initiated by a device can be vetoed by a client of the device, and
+// power state changes initiated by the parent of a device cannot be vetoed by a client of the device,
+// so when the root domain wants a power state change that cannot be vetoed (e.g. demand sleep), it asks
+// its parent to make the change.  That is the reason for this complexity.
 
-static IOPMPowerState patriarchPowerStates[number_of_patriarch_power_states] = {
+static IOPMPowerState patriarchPowerStates[number_of_power_states] = {
     {1,0,0,0,0,0,0,0,0,0,0,0},                                          // off
+    {1,0,RESTART_POWER,0,0,0,0,0,0,0,0,0},                              // reset
     {1,0,SLEEP_POWER,0,0,0,0,0,0,0,0,0},                                // sleep
+    {1,0,DOZE_POWER,0,0,0,0,0,0,0,0,0},                                // doze
     {1,0,ON_POWER,0,0,0,0,0,0,0,0,0}                                    // running
 };
 
-#define PATRIARCH_OFF   0
-#define PATRIARCH_SLEEP 1
-#define PATRIARCH_ON    2
-
-
 bool IORootParent::start ( IOService * nub )
 {
+    mostRecentChange = ON_STATE;
     super::start(nub);
     PMinit();
-    registerPowerDriver(this,patriarchPowerStates,number_of_patriarch_power_states);
+    registerPowerDriver(this,patriarchPowerStates,number_of_power_states);
     powerOverrideOnPriv();
     return true;
 }
@@ -433,18 +1032,47 @@ bool IORootParent::start ( IOService * nub )
 
 void IORootParent::shutDownSystem ( void )
 {
-    changePowerStateToPriv(PATRIARCH_OFF);
+    mostRecentChange = OFF_STATE;
+    changePowerStateToPriv(OFF_STATE);
+}
+
+
+void IORootParent::restartSystem ( void )
+{
+    mostRecentChange = RESTART_STATE;
+    changePowerStateToPriv(RESTART_STATE);
 }
 
 
 void IORootParent::sleepSystem ( void )
 {
-    changePowerStateToPriv(PATRIARCH_SLEEP);
+    mostRecentChange = SLEEP_STATE;
+    changePowerStateToPriv(SLEEP_STATE);
+}
+
+
+void IORootParent::dozeSystem ( void )
+{
+    mostRecentChange = DOZE_STATE;
+    changePowerStateToPriv(DOZE_STATE);
+}
+
+// Called in demand sleep when sleep discovered to be impossible after actually attaining that state.
+// This brings the parent to doze, which allows the root to step up from sleep to doze.
+
+// In idle sleep, do nothing because the parent is still on and the root can freely change state.
+
+void IORootParent::sleepToDoze ( void )
+{
+    if ( mostRecentChange == SLEEP_STATE ) {
+        changePowerStateToPriv(DOZE_STATE);
+    }
 }
 
 
 void IORootParent::wakeSystem ( void )
 {
-    changePowerStateToPriv(PATRIARCH_ON);
+    mostRecentChange = ON_STATE;
+    changePowerStateToPriv(ON_STATE);
 }