X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/b0d623f7f2ae71ed96e60569f61f9a9a27016e80..04b8595b18b1b41ac7a206e4b3d51a635f8413d7:/iokit/Kernel/IOPMrootDomain.cpp diff --git a/iokit/Kernel/IOPMrootDomain.cpp b/iokit/Kernel/IOPMrootDomain.cpp index c98da3b9f..73738c14b 100644 --- a/iokit/Kernel/IOPMrootDomain.cpp +++ b/iokit/Kernel/IOPMrootDomain.cpp @@ -27,9 +27,12 @@ */ #include #include +#include +#include #include #include #include +#include #include #include #include @@ -38,18 +41,24 @@ #include #include #include +#include #include "RootDomainUserClient.h" #include "IOKit/pwr_mgt/IOPowerConnection.h" #include "IOPMPowerStateQueue.h" #include -#include // IOServicePMPrivate +#include #if HIBERNATION #include #endif +#include #include #include +#include +#include +#include + #include -#include "IOServicePrivate.h" // _IOServiceInterestNotifier +#include "IOServicePrivate.h" // _IOServiceInterestNotifier #include "IOServicePMPrivate.h" __BEGIN_DECLS @@ -63,157 +72,181 @@ __END_DECLS #endif #define kIOPMrootDomainClass "IOPMrootDomain" +#define LOG_PREFIX "PMRD: " -#define LOG_PREFIX "PMRD: " -#define LOG(x...) do { \ - kprintf(LOG_PREFIX x); IOLog(x); } while (false) +#define MSG(x...) \ + do { kprintf(LOG_PREFIX x); IOLog(x); } while (false) -#define KLOG(x...) do { \ - kprintf(LOG_PREFIX x); } while (false) +#define LOG(x...) \ + do { kprintf(LOG_PREFIX x); } while (false) #define DLOG(x...) do { \ - if (kIOLogPMRootDomain & gIOKitDebug) \ - kprintf(LOG_PREFIX x); } while (false) + if (kIOLogPMRootDomain & gIOKitDebug) \ + kprintf(LOG_PREFIX x); \ +} while (false) + +#define DMSG(x...) do { \ + if (kIOLogPMRootDomain & gIOKitDebug) { \ + kprintf(LOG_PREFIX x); IOLog(x); \ + } \ +} while (false) + + +#define _LOG(x...) #define CHECK_THREAD_CONTEXT #ifdef CHECK_THREAD_CONTEXT static IOWorkLoop * gIOPMWorkLoop = 0; -#define ASSERT_GATED(x) \ +#define ASSERT_GATED() \ do { \ if (gIOPMWorkLoop && gIOPMWorkLoop->inGate() != true) { \ - panic("RootDomain: not inside PM gate"); \ + panic("RootDomain: not inside PM gate"); \ } \ } while(false) #else -#define ASSERT_GATED(x) +#define ASSERT_GATED() #endif /* CHECK_THREAD_CONTEXT */ +#define CAP_LOSS(c) \ + (((_pendingCapability & (c)) == 0) && \ + ((_currentCapability & (c)) != 0)) + +#define CAP_GAIN(c) \ + (((_currentCapability & (c)) == 0) && \ + ((_pendingCapability & (c)) != 0)) + +#define CAP_CHANGE(c) \ + (((_currentCapability ^ _pendingCapability) & (c)) != 0) + +#define CAP_CURRENT(c) \ + ((_currentCapability & (c)) != 0) + +#define CAP_HIGHEST(c) \ + ((_highestCapability & (c)) != 0) + +#if defined(__i386__) || defined(__x86_64__) +#define DARK_TO_FULL_EVALUATE_CLAMSHELL 1 +#endif + // Event types for IOPMPowerStateQueue::submitPowerEvent() enum { - kPowerEventFeatureChanged = 1, - kPowerEventReceivedPowerNotification, - kPowerEventSystemBootCompleted, - kPowerEventSystemShutdown, - kPowerEventUserDisabledSleep, - kPowerEventConfigdRegisteredInterest, - kPowerEventAggressivenessChanged + kPowerEventFeatureChanged = 1, // 1 + kPowerEventReceivedPowerNotification, // 2 + kPowerEventSystemBootCompleted, // 3 + kPowerEventSystemShutdown, // 4 + kPowerEventUserDisabledSleep, // 5 + kPowerEventRegisterSystemCapabilityClient, // 6 + kPowerEventRegisterKernelCapabilityClient, // 7 + kPowerEventPolicyStimulus, // 8 + kPowerEventAssertionCreate, // 9 + kPowerEventAssertionRelease, // 10 + kPowerEventAssertionSetLevel, // 11 + kPowerEventQueueSleepWakeUUID, // 12 + kPowerEventPublishSleepWakeUUID, // 13 + kPowerEventSetDisplayPowerOn // 14 +}; + +// For evaluatePolicy() +// List of stimuli that affects the root domain policy. +enum { + kStimulusDisplayWranglerSleep, // 0 + kStimulusDisplayWranglerWake, // 1 + kStimulusAggressivenessChanged, // 2 + kStimulusDemandSystemSleep, // 3 + kStimulusAllowSystemSleepChanged, // 4 + kStimulusDarkWakeActivityTickle, // 5 + kStimulusDarkWakeEntry, // 6 + kStimulusDarkWakeReentry, // 7 + kStimulusDarkWakeEvaluate, // 8 + kStimulusNoIdleSleepPreventers, // 9 + kStimulusEnterUserActiveState, // 10 + kStimulusLeaveUserActiveState // 11 }; extern "C" { IOReturn OSKextSystemSleepOrWake( UInt32 ); } - -extern const IORegistryPlane * gIOPowerPlane; +extern "C" ppnum_t pmap_find_phys(pmap_t pmap, addr64_t va); +extern "C" addr64_t kvtophys(vm_offset_t va); +extern "C" int stack_snapshot_from_kernel(pid_t pid, void *buf, uint32_t size, uint32_t flags, unsigned *bytesTraced); static void idleSleepTimerExpired( thread_call_param_t, thread_call_param_t ); -static void wakeupClamshellTimerExpired( thread_call_param_t us, thread_call_param_t ); static void notifySystemShutdown( IOService * root, unsigned long event ); -static bool clientMessageFilter( OSObject * object, void * context ); -static void handleAggressivesFunction( thread_call_param_t param1, thread_call_param_t param2 ); +static void handleAggressivesFunction( thread_call_param_t, thread_call_param_t ); +static void pmEventTimeStamp(uint64_t *recordTS); // "IOPMSetSleepSupported" callPlatformFunction name static const OSSymbol *sleepSupportedPEFunction = NULL; +static const OSSymbol *sleepMessagePEFunction = NULL; -#define kIOSleepSupportedKey "IOSleepSupported" +#define kIOSleepSupportedKey "IOSleepSupported" +#define kIOPMSystemCapabilitiesKey "System Capabilities" + +#define kIORequestWranglerIdleKey "IORequestIdle" +#define kDefaultWranglerIdlePeriod 25 // in milliseconds + +#define kIOSleepWakeDebugKey "Persistent-memory-note" #define kRD_AllPowerSources (kIOPMSupportedOnAC \ | kIOPMSupportedOnBatt \ | kIOPMSupportedOnUPS) -enum +enum { // not idle around autowake time, secs kAutoWakePreWindow = 45, kAutoWakePostWindow = 15 }; -#define kLocalEvalClamshellCommand (1 << 15) +#define kLocalEvalClamshellCommand (1 << 15) +#define kIdleSleepRetryInterval (3 * 60) + +enum { + kWranglerPowerStateMin = 0, + kWranglerPowerStateSleep = 2, + kWranglerPowerStateDim = 3, + kWranglerPowerStateMax = 4 +}; enum { - OFF_STATE = 0, - RESTART_STATE = 1, - SLEEP_STATE = 2, - DOZE_STATE = 3, - ON_STATE = 4, + OFF_STATE = 0, + RESTART_STATE = 1, + SLEEP_STATE = 2, + ON_STATE = 3, NUM_POWER_STATES }; #define ON_POWER kIOPMPowerOn #define RESTART_POWER kIOPMRestart #define SLEEP_POWER kIOPMAuxPowerOn -#define DOZE_POWER kIOPMDoze static IOPMPowerState ourPowerStates[NUM_POWER_STATES] = { {1, 0, 0, 0, 0,0,0,0,0,0,0,0}, - {1, kIOPMRestartCapability, kIOPMRestart, RESTART_POWER, 0,0,0,0,0,0,0,0}, + {1, kIOPMRestartCapability, kIOPMRestart, RESTART_POWER, 0,0,0,0,0,0,0,0}, {1, kIOPMSleepCapability, kIOPMSleep, SLEEP_POWER, 0,0,0,0,0,0,0,0}, - {1, kIOPMDoze, kIOPMDoze, DOZE_POWER, 0,0,0,0,0,0,0,0}, {1, kIOPMPowerOn, kIOPMPowerOn, ON_POWER, 0,0,0,0,0,0,0,0} }; -// Clients eligible to receive system power messages. -enum { - kMessageClientNone = 0, - kMessageClientAll, - kMessageClientConfigd -}; - -// Run states (R-state) defined within the ON power state. -enum { - kRStateNormal = 0, - kRStateDark, - kRStateMaintenance, - kRStateCount -}; - -// IOService in power plane can be tagged with following flags. -enum { - kServiceFlagGraphics = 0x01, - kServiceFlagNoPowerUp = 0x02, - kServiceFlagTopLevelPCI = 0x04 -}; - -// Flags describing R-state features and capabilities. -enum { - kRStateFlagNone = 0x00000000, - kRStateFlagSuppressGraphics = 0x00000001, - kRStateFlagSuppressMessages = 0x00000002, - kRStateFlagSuppressPCICheck = 0x00000004, - kRStateFlagDisableIdleSleep = 0x00000008 -}; - -#if ROOT_DOMAIN_RUN_STATES - -// Table of flags for each R-state. -static uint32_t gRStateFlags[ kRStateCount ] = -{ - kRStateFlagNone, - - /* Dark wake */ - kRStateFlagSuppressGraphics, - - /* Maintenance wake */ - kRStateFlagSuppressGraphics | - kRStateFlagSuppressMessages | - kRStateFlagSuppressPCICheck | - kRStateFlagDisableIdleSleep -}; - -static IONotifier * gConfigdNotifier = 0; - -#define kIOPMRootDomainRunStateKey "Run State" -#define kIOPMRootDomainWakeTypeMaintenance "Maintenance" - -#endif /* ROOT_DOMAIN_RUN_STATES */ +#define kIOPMRootDomainWakeTypeSleepService "SleepService" +#define kIOPMRootDomainWakeTypeMaintenance "Maintenance" +#define kIOPMRootDomainWakeTypeSleepTimer "SleepTimer" +#define kIOPMrootDomainWakeTypeLowBattery "LowBattery" +#define kIOPMRootDomainWakeTypeUser "User" +#define kIOPMRootDomainWakeTypeAlarm "Alarm" +#define kIOPMRootDomainWakeTypeNetwork "Network" +#define kIOPMRootDomainWakeTypeHIDActivity "HID Activity" +#define kIOPMRootDomainWakeTypeNotification "Notification" +#define kIOPMRootDomainWakeTypeHibernateError "HibernateError" // Special interest that entitles the interested client from receiving -// all system messages. Used by pmconfigd to support maintenance wake. +// all system messages. Only used by powerd. // -#define kIOPMPrivilegedPowerInterest "IOPMPrivilegedPowerInterest" +#define kIOPMSystemCapabilityInterest "IOPMSystemCapabilityInterest" -static IONotifier * gSysPowerDownNotifier = 0; +#define WAKEEVENT_LOCK() IOLockLock(wakeEventLock) +#define WAKEEVENT_UNLOCK() IOLockUnlock(wakeEventLock) /* * Aggressiveness @@ -223,8 +256,6 @@ static IONotifier * gSysPowerDownNotifier = 0; #define kAggressivesMinValue 1 -static uint32_t gAggressivesState = 0; - enum { kAggressivesStateBusy = 0x01, kAggressivesStateQuickSpindown = 0x02 @@ -261,18 +292,44 @@ enum { enum { kAggressivesRecordFlagModified = 0x00000001, kAggressivesRecordFlagMinValue = 0x00000002 - +}; + +// gDarkWakeFlags +enum { + kDarkWakeFlagHIDTickleEarly = 0x01, // hid tickle before gfx suppression + kDarkWakeFlagHIDTickleLate = 0x02, // hid tickle after gfx suppression + kDarkWakeFlagHIDTickleNone = 0x03, // hid tickle is not posted + kDarkWakeFlagHIDTickleMask = 0x03, + kDarkWakeFlagAlarmIsDark = 0x0100, + kDarkWakeFlagGraphicsPowerState1 = 0x0200, + kDarkWakeFlagAudioNotSuppressed = 0x0400 }; static IOPMrootDomain * gRootDomain; +static IONotifier * gSysPowerDownNotifier = 0; static UInt32 gSleepOrShutdownPending = 0; static UInt32 gWillShutdown = 0; -static uint32_t gMessageClientType = kMessageClientNone; +static UInt32 gPagingOff = 0; static UInt32 gSleepWakeUUIDIsSet = false; +static uint32_t gAggressivesState = 0; + +uuid_string_t bootsessionuuid_string; + +static uint32_t gDarkWakeFlags = kDarkWakeFlagHIDTickleNone; +static PMStatsStruct gPMStats; + +#if HIBERNATION +static IOPMSystemSleepPolicyHandler gSleepPolicyHandler = 0; +static IOPMSystemSleepPolicyVariables * gSleepPolicyVars = 0; +static void * gSleepPolicyTarget; +#endif struct timeval gIOLastSleepTime; struct timeval gIOLastWakeTime; +static char gWakeReasonString[128]; +static bool gWakeReasonSysctlRegistered = false; + // Constants used as arguments to IOPMrootDomain::informCPUStateChange #define kCPUUnknownIndex 9999999 enum { @@ -284,32 +341,71 @@ enum { const OSSymbol *gIOPMStatsApplicationResponseTimedOut; const OSSymbol *gIOPMStatsApplicationResponseCancel; const OSSymbol *gIOPMStatsApplicationResponseSlow; +const OSSymbol *gIOPMStatsApplicationResponsePrompt; +const OSSymbol *gIOPMStatsDriverPSChangeSlow; + +#define kBadPMFeatureID 0 + +/* + * PMSettingHandle + * Opaque handle passed to clients of registerPMSettingController() + */ +class PMSettingHandle : public OSObject +{ + OSDeclareFinalStructors( PMSettingHandle ) + friend class PMSettingObject; + +private: + PMSettingObject *pmso; + void free(void); +}; +/* + * PMSettingObject + * Internal object to track each PM setting controller + */ class PMSettingObject : public OSObject { - OSDeclareFinalStructors(PMSettingObject) + OSDeclareFinalStructors( PMSettingObject ) + friend class IOPMrootDomain; + private: + queue_head_t calloutQueue; + thread_t waitThread; IOPMrootDomain *parent; + PMSettingHandle *pmsh; IOPMSettingControllerCallback func; OSObject *target; uintptr_t refcon; uint32_t *publishedFeatureID; - int releaseAtCount; + uint32_t settingCount; + bool disabled; + + void free(void); + public: static PMSettingObject *pmSettingObject( - IOPMrootDomain *parent_arg, + IOPMrootDomain *parent_arg, IOPMSettingControllerCallback handler_arg, - OSObject *target_arg, - uintptr_t refcon_arg, - uint32_t supportedPowerSources, - const OSSymbol *settings[]); - - void setPMSetting(const OSSymbol *type, OSObject *obj); + OSObject *target_arg, + uintptr_t refcon_arg, + uint32_t supportedPowerSources, + const OSSymbol *settings[], + OSObject **handle_obj); + + void dispatchPMSetting(const OSSymbol *type, OSObject *object); + void clientHandleFreed(void); +}; - void taggedRelease(const void *tag, const int when) const; - void free(void); +struct PMSettingCallEntry { + queue_chain_t link; + thread_t thread; }; +#define PMSETTING_LOCK() IOLockLock(settingsCtrlLock) +#define PMSETTING_UNLOCK() IOLockUnlock(settingsCtrlLock) +#define PMSETTING_WAIT(p) IOLockSleep(settingsCtrlLock, p, THREAD_UNINT) +#define PMSETTING_WAKEUP(p) IOLockWakeup(settingsCtrlLock, p, true) /* * PMTraceWorker @@ -330,25 +426,82 @@ public: static PMTraceWorker *tracer( IOPMrootDomain * ); void tracePCIPowerChange(change_t, IOService *, uint32_t, uint32_t); void tracePoint(uint8_t phase); + void tracePoint(uint8_t phase, uint8_t data8); + void traceDetail(uint32_t detail); void traceLoginWindowPhase(uint8_t phase); int recordTopLevelPCIDevice(IOService *); void RTC_TRACE(void); - virtual bool serialize(OSSerialize *s) const; + virtual bool serialize(OSSerialize *s) const; IOPMTracePointHandler tracePointHandler; void * tracePointTarget; + uint64_t getPMStatusCode(); private: IOPMrootDomain *owner; IOLock *pciMappingLock; OSArray *pciDeviceBitMappings; + uint8_t addedToRegistry; uint8_t tracePhase; uint8_t loginWindowPhase; - uint8_t addedToRegistry; - uint8_t unused0; - uint32_t pciBusyBitMask; + uint8_t traceData8; + uint32_t traceData32; +}; + +/* + * PMAssertionsTracker + * Tracks kernel and user space PM assertions + */ +class PMAssertionsTracker : public OSObject +{ + OSDeclareFinalStructors(PMAssertionsTracker) +public: + static PMAssertionsTracker *pmAssertionsTracker( IOPMrootDomain * ); + + IOReturn createAssertion(IOPMDriverAssertionType, IOPMDriverAssertionLevel, IOService *, const char *, IOPMDriverAssertionID *); + IOReturn releaseAssertion(IOPMDriverAssertionID); + IOReturn setAssertionLevel(IOPMDriverAssertionID, IOPMDriverAssertionLevel); + IOReturn setUserAssertionLevels(IOPMDriverAssertionType); + + OSArray *copyAssertionsArray(void); + IOPMDriverAssertionType getActivatedAssertions(void); + IOPMDriverAssertionLevel getAssertionLevel(IOPMDriverAssertionType); + + IOReturn handleCreateAssertion(OSData *); + IOReturn handleReleaseAssertion(IOPMDriverAssertionID); + IOReturn handleSetAssertionLevel(IOPMDriverAssertionID, IOPMDriverAssertionLevel); + IOReturn handleSetUserAssertionLevels(void * arg0); + void publishProperties(void); + +private: + typedef struct { + IOPMDriverAssertionID id; + IOPMDriverAssertionType assertionBits; + uint64_t createdTime; + uint64_t modifiedTime; + const OSSymbol *ownerString; + IOService *ownerService; + uint64_t registryEntryID; + IOPMDriverAssertionLevel level; + } PMAssertStruct; + + uint32_t tabulateProducerCount; + uint32_t tabulateConsumerCount; + + PMAssertStruct *detailsForID(IOPMDriverAssertionID, int *); + void tabulate(void); + + IOPMrootDomain *owner; + OSArray *assertionsArray; + IOLock *assertionsArrayLock; + IOPMDriverAssertionID issuingUniqueID __attribute__((aligned(8))); /* aligned for atomic access */ + IOPMDriverAssertionType assertionsKernel; + IOPMDriverAssertionType assertionsUser; + IOPMDriverAssertionType assertionsCombined; }; +OSDefineMetaClassAndFinalStructors(PMAssertionsTracker, OSObject); + /* * PMHaltWorker * Internal helper object for Shutdown/Restart notifications. @@ -381,6 +534,19 @@ OSDefineMetaClassAndFinalStructors( PMHaltWorker, OSObject ) #define super IOService OSDefineMetaClassAndFinalStructors(IOPMrootDomain, IOService) +static void IOPMRootDomainWillShutdown(void) +{ + if (OSCompareAndSwap(0, 1, &gWillShutdown)) + { + OSKext::willShutdown(); + for (int i = 0; i < 100; i++) + { + if (OSCompareAndSwap(0, 1, &gSleepOrShutdownPending)) break; + IOSleep( 100 ); + } + } +} + extern "C" { IONotifier * registerSleepWakeInterest(IOServiceInterestHandler handler, void * self, void * ref) @@ -402,72 +568,69 @@ extern "C" { return gRootDomain->cancelPowerChange ( (unsigned long)PMrefcon ); } - + IOReturn rootDomainRestart ( void ) { return gRootDomain->restartSystem(); } - + IOReturn rootDomainShutdown ( void ) { return gRootDomain->shutdownSystem(); } - void IOSystemShutdownNotification ( void ) + void IOSystemShutdownNotification(void) { - if (OSCompareAndSwap(0, 1, &gWillShutdown)) - { - OSKext::willShutdown(); - for (int i = 0; i < 100; i++) - { - if (OSCompareAndSwap(0, 1, &gSleepOrShutdownPending)) break; - IOSleep( 100 ); - } - } + IOPMRootDomainWillShutdown(); + if (OSCompareAndSwap(0, 1, &gPagingOff)) + { + gRootDomain->handlePlatformHaltRestart(kPEPagingOff); + } } - int sync_internal(void); + 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)). +A device is always in the highest power state which satisfies its driver, +its policy-maker, and any power 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 for the system. +It is a power-managed IOService just like the others in the system. +It implements several power states which map to what we see as Sleep and On. + +The sleep policy is as follows: +1. Sleep is prevented if the case is open so that nobody will think the machine + is off and plug/unplug cards. +2. Sleep is prevented if the sleep timeout slider in the prefs panel is zero. +3. System cannot Sleep 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 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 domain has no +choice and does not give applications the opportunity to veto the change. + +Idle Sleep 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 +display dim timeout slider. 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. */ //****************************************************************************** @@ -487,34 +650,56 @@ IOPMrootDomain * IOPMrootDomain::construct( void ) 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; + IOService * rootDomain = (IOService *) p0; + uint32_t notifyRef = (uint32_t)(uintptr_t) p1; + uint32_t powerState = rootDomain->getPowerState(); - DLOG("disk_sync_callout start\n"); + DLOG("disk_sync_callout ps=%u\n", powerState); -#if HIBERNATION - IOHibernateSystemSleep(); + if (ON_STATE == powerState) + { + sync_internal(); + } +#if HIBERNATION + else + { + IOHibernateSystemPostWake(); + } #endif - sync_internal(); - rootDomain->allowPowerChange(pmRef); + + rootDomain->allowPowerChange(notifyRef); DLOG("disk_sync_callout finish\n"); } //****************************************************************************** +static void hib_debugSetup_callout( thread_call_param_t p0, thread_call_param_t p1 ) +{ + IOService * rootDomain = (IOService *) p0; + uint32_t notifyRef = (uint32_t)(uintptr_t) p1; + +#if HIBERNATION + IOHibernateOpenForDebugData(); +#endif + + rootDomain->allowPowerChange(notifyRef); + DLOG("hib_debugSetup_callout finish\n"); +} +//****************************************************************************** + static UInt32 computeDeltaTimeMS( const AbsoluteTime * startTime ) { - AbsoluteTime endTime; - UInt64 nano = 0; + AbsoluteTime endTime; + UInt64 nano = 0; - clock_get_uptime(&endTime); - if (CMP_ABSOLUTETIME(&endTime, startTime) > 0) - { - SUB_ABSOLUTETIME(&endTime, startTime); - absolutetime_to_nanoseconds(endTime, &nano); - } + clock_get_uptime(&endTime); + if (CMP_ABSOLUTETIME(&endTime, startTime) > 0) + { + SUB_ABSOLUTETIME(&endTime, startTime); + absolutetime_to_nanoseconds(endTime, &nano); + } - return (UInt32)(nano / 1000000ULL); + return (UInt32)(nano / 1000000ULL); } //****************************************************************************** @@ -526,7 +711,7 @@ sysctl_sleepwaketime SYSCTL_HANDLER_ARGS struct proc *p = req->p; if (p == kernproc) { - return sysctl_io_opaque(req, swt, sizeof(*swt), NULL); + return sysctl_io_opaque(req, swt, sizeof(*swt), NULL); } else if(proc_is64bit(p)) { struct user64_timeval t; t.tv_sec = swt->tv_sec; @@ -541,12 +726,12 @@ sysctl_sleepwaketime SYSCTL_HANDLER_ARGS } static SYSCTL_PROC(_kern, OID_AUTO, sleeptime, - CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN, - &gIOLastSleepTime, 0, sysctl_sleepwaketime, "S,timeval", ""); + CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + &gIOLastSleepTime, 0, sysctl_sleepwaketime, "S,timeval", ""); static SYSCTL_PROC(_kern, OID_AUTO, waketime, - CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN, - &gIOLastWakeTime, 0, sysctl_sleepwaketime, "S,timeval", ""); + CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + &gIOLastWakeTime, 0, sysctl_sleepwaketime, "S,timeval", ""); static int @@ -556,19 +741,18 @@ sysctl_willshutdown int new_value, changed; int error = sysctl_io_number(req, gWillShutdown, sizeof(int), &new_value, &changed); if (changed) { - if (!gWillShutdown && (new_value == 1)) { - IOSystemShutdownNotification(); - } else - error = EINVAL; + if (!gWillShutdown && (new_value == 1)) { + IOPMRootDomainWillShutdown(); + } else + error = EINVAL; } return(error); } static SYSCTL_PROC(_kern, OID_AUTO, willshutdown, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN, - 0, 0, sysctl_willshutdown, "I", ""); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + 0, 0, sysctl_willshutdown, "I", ""); -#if !CONFIG_EMBEDDED static int sysctl_progressmeterenable @@ -577,10 +761,9 @@ sysctl_progressmeterenable int error; int new_value, changed; - error = sysctl_io_number(req, vc_progress_meter_enable, sizeof(int), &new_value, &changed); + error = sysctl_io_number(req, vc_progressmeter_enable, sizeof(int), &new_value, &changed); - if (changed) - vc_enable_progressmeter(new_value); + if (changed) vc_enable_progressmeter(new_value); return (error); } @@ -592,60 +775,94 @@ sysctl_progressmeter int error; int new_value, changed; - error = sysctl_io_number(req, vc_progress_meter_value, sizeof(int), &new_value, &changed); + error = sysctl_io_number(req, vc_progressmeter_value, sizeof(int), &new_value, &changed); - if (changed) - vc_set_progressmeter(new_value); + if (changed) vc_set_progressmeter(new_value); return (error); } static SYSCTL_PROC(_kern, OID_AUTO, progressmeterenable, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN, - 0, 0, sysctl_progressmeterenable, "I", ""); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + 0, 0, sysctl_progressmeterenable, "I", ""); static SYSCTL_PROC(_kern, OID_AUTO, progressmeter, - CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN, - 0, 0, sysctl_progressmeter, "I", ""); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + 0, 0, sysctl_progressmeter, "I", ""); -#endif +static int +sysctl_wakereason SYSCTL_HANDLER_ARGS +{ + char wr[ sizeof(gWakeReasonString) ]; + + wr[0] = '\0'; + if (gRootDomain) + gRootDomain->copyWakeReasonString(wr, sizeof(wr)); + + return sysctl_io_string(req, wr, 0, 0, NULL); +} + +SYSCTL_PROC(_kern, OID_AUTO, wakereason, + CTLTYPE_STRING| CTLFLAG_RD | CTLFLAG_NOAUTO | CTLFLAG_KERN | CTLFLAG_LOCKED, + NULL, 0, sysctl_wakereason, "A", "wakereason"); + +static SYSCTL_INT(_debug, OID_AUTO, darkwake, CTLFLAG_RW, &gDarkWakeFlags, 0, ""); + +static const OSSymbol * gIOPMSettingAutoWakeCalendarKey; static const OSSymbol * gIOPMSettingAutoWakeSecondsKey; +static const OSSymbol * gIOPMSettingDebugWakeRelativeKey; static const OSSymbol * gIOPMSettingMaintenanceWakeCalendarKey; +static const OSSymbol * gIOPMSettingSleepServiceWakeCalendarKey; +static const OSSymbol * gIOPMSettingSilentRunningKey; +static const OSSymbol * gIOPMUserTriggeredFullWakeKey; +static const OSSymbol * gIOPMUserIsActiveKey; //****************************************************************************** // start // //****************************************************************************** -#define kRootDomainSettingsCount 16 +#define kRootDomainSettingsCount 17 bool IOPMrootDomain::start( IOService * nub ) { OSIterator *psIterator; OSDictionary *tmpDict; + IORootParent * patriarch; +#if defined(__i386__) || defined(__x86_64__) + IONotifier * notifier; +#endif super::start(nub); gRootDomain = this; + gIOPMSettingAutoWakeCalendarKey = OSSymbol::withCString(kIOPMSettingAutoWakeCalendarKey); gIOPMSettingAutoWakeSecondsKey = OSSymbol::withCString(kIOPMSettingAutoWakeSecondsKey); - gIOPMSettingMaintenanceWakeCalendarKey = - OSSymbol::withCString(kIOPMSettingMaintenanceWakeCalendarKey); + gIOPMSettingDebugWakeRelativeKey = OSSymbol::withCString(kIOPMSettingDebugWakeRelativeKey); + gIOPMSettingMaintenanceWakeCalendarKey = OSSymbol::withCString(kIOPMSettingMaintenanceWakeCalendarKey); + gIOPMSettingSleepServiceWakeCalendarKey = OSSymbol::withCString(kIOPMSettingSleepServiceWakeCalendarKey); + gIOPMSettingSilentRunningKey = OSSymbol::withCStringNoCopy(kIOPMSettingSilentRunningKey); + gIOPMUserTriggeredFullWakeKey = OSSymbol::withCStringNoCopy(kIOPMUserTriggeredFullWakeKey); + gIOPMUserIsActiveKey = OSSymbol::withCStringNoCopy(kIOPMUserIsActiveKey); gIOPMStatsApplicationResponseTimedOut = OSSymbol::withCString(kIOPMStatsResponseTimedOut); gIOPMStatsApplicationResponseCancel = OSSymbol::withCString(kIOPMStatsResponseCancel); gIOPMStatsApplicationResponseSlow = OSSymbol::withCString(kIOPMStatsResponseSlow); + gIOPMStatsApplicationResponsePrompt = OSSymbol::withCString(kIOPMStatsResponsePrompt); + gIOPMStatsDriverPSChangeSlow = OSSymbol::withCString(kIOPMStatsDriverPSChangeSlow); sleepSupportedPEFunction = OSSymbol::withCString("IOPMSetSleepSupported"); + sleepMessagePEFunction = OSSymbol::withCString("IOPMSystemSleepMessage"); - const OSSymbol *settingsArr[kRootDomainSettingsCount] = + const OSSymbol *settingsArr[kRootDomainSettingsCount] = { OSSymbol::withCString(kIOPMSettingSleepOnPowerButtonKey), gIOPMSettingAutoWakeSecondsKey, OSSymbol::withCString(kIOPMSettingAutoPowerSecondsKey), - OSSymbol::withCString(kIOPMSettingAutoWakeCalendarKey), + gIOPMSettingAutoWakeCalendarKey, OSSymbol::withCString(kIOPMSettingAutoPowerCalendarKey), - OSSymbol::withCString(kIOPMSettingDebugWakeRelativeKey), + gIOPMSettingDebugWakeRelativeKey, OSSymbol::withCString(kIOPMSettingDebugPowerRelativeKey), OSSymbol::withCString(kIOPMSettingWakeOnRingKey), OSSymbol::withCString(kIOPMSettingRestartOnPowerLossKey), @@ -655,67 +872,95 @@ bool IOPMrootDomain::start( IOService * nub ) OSSymbol::withCString(kIOPMSettingDisplaySleepUsesDimKey), OSSymbol::withCString(kIOPMSettingMobileMotionModuleKey), OSSymbol::withCString(kIOPMSettingGraphicsSwitchKey), - OSSymbol::withCString(kIOPMStateConsoleShutdown) + OSSymbol::withCString(kIOPMStateConsoleShutdown), + gIOPMSettingSilentRunningKey }; + PE_parse_boot_argn("darkwake", &gDarkWakeFlags, sizeof(gDarkWakeFlags)); + queue_init(&aggressivesQueue); aggressivesThreadCall = thread_call_allocate(handleAggressivesFunction, this); aggressivesData = OSData::withCapacity( sizeof(AggressivesRecord) * (kPMLastAggressivenessType + 4)); featuresDictLock = IOLockAlloc(); - settingsCtrlLock = IORecursiveLockAlloc(); + settingsCtrlLock = IOLockAlloc(); + wakeEventLock = IOLockAlloc(); setPMRootDomain(this); - + extraSleepTimer = thread_call_allocate( idleSleepTimerExpired, (thread_call_param_t) this); - clamshellWakeupIgnore = thread_call_allocate( - wakeupClamshellTimerExpired, - (thread_call_param_t) this); - diskSyncCalloutEntry = thread_call_allocate( &disk_sync_callout, (thread_call_param_t) this); - - canSleep = true; + hibDebugSetupEntry = thread_call_allocate( + &hib_debugSetup_callout, + (thread_call_param_t) this); + +#if DARK_TO_FULL_EVALUATE_CLAMSHELL + fullWakeThreadCall = thread_call_allocate( + OSMemberFunctionCast(thread_call_func_t, this, + &IOPMrootDomain::fullWakeDelayedWork), + (thread_call_param_t) this); +#endif + setProperty(kIOSleepSupportedKey, true); - bzero(&pmStats, sizeof(pmStats)); + bzero(&gPMStats, sizeof(gPMStats)); pmTracer = PMTraceWorker::tracer(this); - updateRunState(kRStateNormal); + pmAssertions = PMAssertionsTracker::pmAssertionsTracker(this); + userDisabledAllSleep = false; - allowSleep = true; - sleepIsSupported = true; systemBooting = true; sleepSlider = 0; idleSleepTimerPending = false; wrangler = NULL; - sleepASAP = false; - clamshellIsClosed = false; - clamshellExists = false; - ignoringClamshell = true; - ignoringClamshellOnWake = false; + clamshellClosed = false; + clamshellExists = false; + clamshellDisabled = true; acAdaptorConnected = true; + clamshellSleepDisabled = false; + gWakeReasonString[0] = '\0'; + + // Initialize to user active. + // Will never transition to user inactive w/o wrangler. + fullWakeReason = kFullWakeReasonLocalUser; + userIsActive = userWasActive = true; + setProperty(gIOPMUserIsActiveKey, kOSBooleanTrue); + + // Set the default system capabilities at boot. + _currentCapability = kIOPMSystemCapabilityCPU | + kIOPMSystemCapabilityGraphics | + kIOPMSystemCapabilityAudio | + kIOPMSystemCapabilityNetwork; + + _pendingCapability = _currentCapability; + _desiredCapability = _currentCapability; + _highestCapability = _currentCapability; + setProperty(kIOPMSystemCapabilitiesKey, _currentCapability, 64); queuedSleepWakeUUIDString = NULL; + initializeBootSessionUUID(); pmStatsAppResponses = OSArray::withCapacity(5); _statsNameKey = OSSymbol::withCString(kIOPMStatsNameKey); _statsPIDKey = OSSymbol::withCString(kIOPMStatsPIDKey); _statsTimeMSKey = OSSymbol::withCString(kIOPMStatsTimeMSKey); _statsResponseTypeKey = OSSymbol::withCString(kIOPMStatsApplicationResponseTypeKey); _statsMessageTypeKey = OSSymbol::withCString(kIOPMStatsMessageTypeKey); + _statsPowerCapsKey = OSSymbol::withCString(kIOPMStatsPowerCapabilityKey); + pmStatsLock = IOLockAlloc(); idxPMCPUClamshell = kCPUUnknownIndex; idxPMCPULimitedPower = kCPUUnknownIndex; - + tmpDict = OSDictionary::withCapacity(1); setProperty(kRootDomainSupportedFeatures, tmpDict); tmpDict->release(); - + settingsCallbacks = OSDictionary::withCapacity(1); // Create a list of the valid PM settings that we'll relay to @@ -724,8 +969,15 @@ bool IOPMrootDomain::start( IOService * nub ) (const OSObject **)settingsArr, kRootDomainSettingsCount, 0); - + + // List of PM settings that should not automatically publish itself + // as a feature when registered by a listener. + noPublishPMSettings = OSArray::withObjects( + (const OSObject **) &gIOPMSettingSilentRunningKey, 1, 0); + fPMSettingsDict = OSDictionary::withCapacity(5); + preventIdleSleepList = OSSet::withCapacity(8); + preventSystemSleepList = OSSet::withCapacity(2); PMinit(); // creates gIOPMWorkLoop @@ -747,8 +999,6 @@ bool IOPMrootDomain::start( IOService * nub ) patriarch->addPowerChild(this); registerPowerDriver(this, ourPowerStates, NUM_POWER_STATES); - - // set a clamp until we sleep changePowerStateToPriv(ON_STATE); // install power change handler @@ -758,24 +1008,38 @@ bool IOPMrootDomain::start( IOService * nub ) // Register for a notification when IODisplayWrangler is published if ((tmpDict = serviceMatching("IODisplayWrangler"))) { - _displayWranglerNotifier = addMatchingNotification( - gIOPublishNotification, tmpDict, - (IOServiceMatchingNotificationHandler) &displayWranglerPublished, + _displayWranglerNotifier = addMatchingNotification( + gIOPublishNotification, tmpDict, + (IOServiceMatchingNotificationHandler) &displayWranglerMatchPublished, this, 0); tmpDict->release(); } #endif - // Battery location published - ApplePMU support only - if ((tmpDict = serviceMatching("IOPMPowerSource"))) +#if defined(__i386__) || defined(__x86_64__) + + if ((tmpDict = serviceMatching("IODTNVRAM"))) { - _batteryPublishNotifier = addMatchingNotification( - gIOPublishNotification, tmpDict, - (IOServiceMatchingNotificationHandler) &batteryPublished, - this, this); + notifier = addMatchingNotification( + gIOFirstPublishNotification, tmpDict, + (IOServiceMatchingNotificationHandler) &IONVRAMMatchPublished, + this, 0); tmpDict->release(); } + wranglerIdleSettings = NULL; + OSNumber * wranglerIdlePeriod = NULL; + wranglerIdleSettings = OSDictionary::withCapacity(1); + wranglerIdlePeriod = OSNumber::withNumber(kDefaultWranglerIdlePeriod, 32); + + if(wranglerIdleSettings && wranglerIdlePeriod) + wranglerIdleSettings->setObject(kIORequestWranglerIdleKey, + wranglerIdlePeriod); + + if(wranglerIdlePeriod) + wranglerIdlePeriod->release(); +#endif + const OSSymbol *ucClassName = OSSymbol::withCStringNoCopy("RootDomainUserClient"); setProperty(gIOUserClientClassKey, (OSObject *) ucClassName); ucClassName->release(); @@ -797,21 +1061,19 @@ bool IOPMrootDomain::start( IOService * nub ) sysctl_register_oid(&sysctl__kern_sleeptime); sysctl_register_oid(&sysctl__kern_waketime); sysctl_register_oid(&sysctl__kern_willshutdown); -#if !CONFIG_EMBEDDED sysctl_register_oid(&sysctl__kern_progressmeterenable); sysctl_register_oid(&sysctl__kern_progressmeter); -#endif /* !CONFIG_EMBEDDED */ + sysctl_register_oid(&sysctl__kern_wakereason); -#if HIBERNATION +#if HIBERNATION IOHibernateSystemInit(this); #endif - registerService(); // let clients find us + registerService(); // let clients find us return true; } - //****************************************************************************** // setProperties // @@ -825,307 +1087,241 @@ IOReturn IOPMrootDomain::setProperties( OSObject * props_obj ) OSDictionary *dict = OSDynamicCast(OSDictionary, props_obj); OSBoolean *b; OSNumber *n; - OSString *str; - OSSymbol *type; + const OSSymbol *key; OSObject *obj; - unsigned int i; - - const OSSymbol *boot_complete_string = - OSSymbol::withCString("System Boot Complete"); - const OSSymbol *sys_shutdown_string = - OSSymbol::withCString("System Shutdown"); - const OSSymbol *stall_halt_string = - OSSymbol::withCString("StallSystemAtHalt"); - const OSSymbol *battery_warning_disabled_string = - OSSymbol::withCString("BatteryWarningsDisabled"); - const OSSymbol *idle_seconds_string = - OSSymbol::withCString("System Idle Seconds"); -#if HIBERNATION - const OSSymbol *hibernatemode_string = - OSSymbol::withCString(kIOHibernateModeKey); - const OSSymbol *hibernatefile_string = - OSSymbol::withCString(kIOHibernateFileKey); - const OSSymbol *hibernatefreeratio_string = - OSSymbol::withCString(kIOHibernateFreeRatioKey); - const OSSymbol *hibernatefreetime_string = - OSSymbol::withCString(kIOHibernateFreeTimeKey); + OSCollectionIterator * iter = 0; + + const OSSymbol *publish_simulated_battery_string = OSSymbol::withCString("SoftwareSimulatedBatteries"); + const OSSymbol *boot_complete_string = OSSymbol::withCString("System Boot Complete"); + const OSSymbol *sys_shutdown_string = OSSymbol::withCString("System Shutdown"); + const OSSymbol *stall_halt_string = OSSymbol::withCString("StallSystemAtHalt"); + const OSSymbol *battery_warning_disabled_string = OSSymbol::withCString("BatteryWarningsDisabled"); + const OSSymbol *idle_seconds_string = OSSymbol::withCString("System Idle Seconds"); + const OSSymbol *sleepdisabled_string = OSSymbol::withCString("SleepDisabled"); + const OSSymbol *ondeck_sleepwake_uuid_string = OSSymbol::withCString(kIOPMSleepWakeUUIDKey); + const OSSymbol *loginwindow_tracepoint_string = OSSymbol::withCString(kIOPMLoginWindowSecurityDebugKey); +#if HIBERNATION + const OSSymbol *hibernatemode_string = OSSymbol::withCString(kIOHibernateModeKey); + const OSSymbol *hibernatefile_string = OSSymbol::withCString(kIOHibernateFileKey); + const OSSymbol *hibernatefilemin_string = OSSymbol::withCString(kIOHibernateFileMinSizeKey); + const OSSymbol *hibernatefilemax_string = OSSymbol::withCString(kIOHibernateFileMaxSizeKey); + const OSSymbol *hibernatefreeratio_string = OSSymbol::withCString(kIOHibernateFreeRatioKey); + const OSSymbol *hibernatefreetime_string = OSSymbol::withCString(kIOHibernateFreeTimeKey); #endif - const OSSymbol *sleepdisabled_string = - OSSymbol::withCString("SleepDisabled"); - const OSSymbol *ondeck_sleepwake_uuid_string = - OSSymbol::withCString(kIOPMSleepWakeUUIDKey); - const OSSymbol *loginwindow_tracepoint_string = - OSSymbol::withCString(kIOPMLoginWindowSecurityDebugKey); - - if(!dict) + + if (!dict) { return_value = kIOReturnBadArgument; goto exit; } - if ((n = OSDynamicCast(OSNumber, dict->getObject(idle_seconds_string)))) - { - setProperty(idle_seconds_string, n); - idleSeconds = n->unsigned32BitValue(); - } - - if (boot_complete_string && dict->getObject(boot_complete_string)) - { - pmPowerStateQueue->submitPowerEvent( kPowerEventSystemBootCompleted ); - } - - if( battery_warning_disabled_string - && dict->getObject(battery_warning_disabled_string)) - { - setProperty( battery_warning_disabled_string, - dict->getObject(battery_warning_disabled_string)); - } - - if( sys_shutdown_string - && (b = OSDynamicCast(OSBoolean, dict->getObject(sys_shutdown_string)))) + iter = OSCollectionIterator::withCollection(dict); + if (!iter) { - pmPowerStateQueue->submitPowerEvent(kPowerEventSystemShutdown, (void *) b); - } - - if( stall_halt_string - && (b = OSDynamicCast(OSBoolean, dict->getObject(stall_halt_string))) ) - { - setProperty(stall_halt_string, b); + return_value = kIOReturnNoMemory; + goto exit; } -#if HIBERNATION - if ( hibernatemode_string - && (n = OSDynamicCast(OSNumber, dict->getObject(hibernatemode_string)))) + while ((key = (const OSSymbol *) iter->getNextObject()) && + (obj = dict->getObject(key))) { - setProperty(hibernatemode_string, n); - } - if ( hibernatefreeratio_string - && (n = OSDynamicCast(OSNumber, dict->getObject(hibernatefreeratio_string)))) - { - setProperty(hibernatefreeratio_string, n); - } - if ( hibernatefreetime_string - && (n = OSDynamicCast(OSNumber, dict->getObject(hibernatefreetime_string)))) - { - setProperty(hibernatefreetime_string, n); - } - if ( hibernatefile_string - && (str = OSDynamicCast(OSString, dict->getObject(hibernatefile_string)))) - { - setProperty(hibernatefile_string, str); - } + if (key->isEqualTo(publish_simulated_battery_string)) + { + if (OSDynamicCast(OSBoolean, obj)) + publishResource(key, kOSBooleanTrue); + } + else if (key->isEqualTo(idle_seconds_string)) + { + if ((n = OSDynamicCast(OSNumber, obj))) + { + setProperty(key, n); + idleSeconds = n->unsigned32BitValue(); + } + } + else if (key->isEqualTo(boot_complete_string)) + { + pmPowerStateQueue->submitPowerEvent(kPowerEventSystemBootCompleted); + } + else if (key->isEqualTo(sys_shutdown_string)) + { + if ((b = OSDynamicCast(OSBoolean, obj))) + pmPowerStateQueue->submitPowerEvent(kPowerEventSystemShutdown, (void *) b); + } + else if (key->isEqualTo(battery_warning_disabled_string)) + { + setProperty(key, obj); + } +#if HIBERNATION + else if (key->isEqualTo(hibernatemode_string) || + key->isEqualTo(hibernatefilemin_string) || + key->isEqualTo(hibernatefilemax_string) || + key->isEqualTo(hibernatefreeratio_string) || + key->isEqualTo(hibernatefreetime_string)) + { + if ((n = OSDynamicCast(OSNumber, obj))) + setProperty(key, n); + } + else if (key->isEqualTo(hibernatefile_string)) + { + OSString * str = OSDynamicCast(OSString, obj); + if (str) setProperty(key, str); + } #endif - - if( sleepdisabled_string - && (b = OSDynamicCast(OSBoolean, dict->getObject(sleepdisabled_string))) ) - { - setProperty(sleepdisabled_string, b); - pmPowerStateQueue->submitPowerEvent(kPowerEventUserDisabledSleep, (void *) b); - } - - if (ondeck_sleepwake_uuid_string - && (obj = dict->getObject(ondeck_sleepwake_uuid_string))) - { - // Clear the currently published UUID - if (kOSBooleanFalse == obj) + else if (key->isEqualTo(sleepdisabled_string)) + { + if ((b = OSDynamicCast(OSBoolean, obj))) + { + setProperty(key, b); + pmPowerStateQueue->submitPowerEvent(kPowerEventUserDisabledSleep, (void *) b); + } + } + else if (key->isEqualTo(ondeck_sleepwake_uuid_string)) + { + obj->retain(); + pmPowerStateQueue->submitPowerEvent(kPowerEventQueueSleepWakeUUID, (void *)obj); + } + else if (key->isEqualTo(loginwindow_tracepoint_string)) + { + if (pmTracer && (n = OSDynamicCast(OSNumber, obj))) + pmTracer->traceLoginWindowPhase(n->unsigned8BitValue()); + } + else if (key->isEqualTo(kIOPMDeepSleepEnabledKey) || + key->isEqualTo(kIOPMDestroyFVKeyOnStandbyKey) || + key->isEqualTo(kIOPMAutoPowerOffEnabledKey) || + key->isEqualTo(stall_halt_string)) + { + if ((b = OSDynamicCast(OSBoolean, obj))) + setProperty(key, b); + } + else if (key->isEqualTo(kIOPMDeepSleepDelayKey) || + key->isEqualTo(kIOPMAutoPowerOffDelayKey) || + key->isEqualTo(kIOPMAutoPowerOffTimerKey)) + { + if ((n = OSDynamicCast(OSNumber, obj))) + setProperty(key, n); + } + else if (key->isEqualTo(kIOPMUserWakeAlarmScheduledKey)) { - publishSleepWakeUUID(NULL); + if (kOSBooleanTrue == obj) + OSBitOrAtomic(kIOPMAlarmBitCalendarWake, &_userScheduledAlarm); + else + OSBitAndAtomic(~kIOPMAlarmBitCalendarWake, &_userScheduledAlarm); + DLOG("_userScheduledAlarm = 0x%x\n", (uint32_t) _userScheduledAlarm); } - // Cache UUID for an upcoming sleep/wake - if ((str = OSDynamicCast(OSString, obj))) + // Relay our allowed PM settings onto our registered PM clients + else if ((allowedPMSettings->getNextIndexOfObject(key, 0) != (unsigned int) -1)) { - if (queuedSleepWakeUUIDString) { - queuedSleepWakeUUIDString->release(); - queuedSleepWakeUUIDString = NULL; + if ((gIOPMSettingAutoWakeSecondsKey == key) && ((n = OSDynamicCast(OSNumber, obj)))) + { + UInt32 rsecs = n->unsigned32BitValue(); + if (!rsecs) + autoWakeStart = autoWakeEnd = 0; + else + { + AbsoluteTime deadline; + clock_interval_to_deadline(rsecs + kAutoWakePostWindow, kSecondScale, &deadline); + autoWakeEnd = AbsoluteTime_to_scalar(&deadline); + if (rsecs > kAutoWakePreWindow) + rsecs -= kAutoWakePreWindow; + else + rsecs = 0; + clock_interval_to_deadline(rsecs, kSecondScale, &deadline); + autoWakeStart = AbsoluteTime_to_scalar(&deadline); + } } - queuedSleepWakeUUIDString = str; - queuedSleepWakeUUIDString->retain(); - DLOG("SleepWake UUID queued: %s\n", - queuedSleepWakeUUIDString->getCStringNoCopy()); - } - } - - if (loginwindow_tracepoint_string - && (n = OSDynamicCast(OSNumber, dict->getObject(loginwindow_tracepoint_string))) - && pmTracer) - { - pmTracer->traceLoginWindowPhase( n->unsigned8BitValue() ); - } - - // Relay our allowed PM settings onto our registered PM clients - for(i = 0; i < allowedPMSettings->getCount(); i++) { - - type = (OSSymbol *)allowedPMSettings->getObject(i); - if(!type) continue; - - obj = dict->getObject(type); - if(!obj) continue; - - if ((gIOPMSettingAutoWakeSecondsKey == type) && ((n = OSDynamicCast(OSNumber, obj)))) - { - UInt32 rsecs = n->unsigned32BitValue(); - if (!rsecs) - autoWakeStart = autoWakeEnd = 0; - else - { - AbsoluteTime deadline; - clock_interval_to_deadline(rsecs + kAutoWakePostWindow, kSecondScale, &deadline); - autoWakeEnd = AbsoluteTime_to_scalar(&deadline); - if (rsecs > kAutoWakePreWindow) - rsecs -= kAutoWakePreWindow; - else - rsecs = 0; - clock_interval_to_deadline(rsecs, kSecondScale, &deadline); - autoWakeStart = AbsoluteTime_to_scalar(&deadline); - } - } - - return_value = setPMSetting(type, obj); - - if(kIOReturnSuccess != return_value) goto exit; + + return_value = setPMSetting(key, obj); + if (kIOReturnSuccess != return_value) + break; + + if (gIOPMSettingDebugWakeRelativeKey == key) + { + if ((n = OSDynamicCast(OSNumber, obj)) && + (_debugWakeSeconds = n->unsigned32BitValue())) + { + OSBitOrAtomic(kIOPMAlarmBitDebugWake, &_scheduledAlarms); + } + else + { + _debugWakeSeconds = 0; + OSBitAndAtomic(~kIOPMAlarmBitDebugWake, &_scheduledAlarms); + } + DLOG("_scheduledAlarms = 0x%x\n", (uint32_t) _scheduledAlarms); + } + else if (gIOPMSettingAutoWakeCalendarKey == key) + { + OSData * data; + if ((data = OSDynamicCast(OSData, obj)) && + (data->getLength() == sizeof(IOPMCalendarStruct))) + { + const IOPMCalendarStruct * cs = + (const IOPMCalendarStruct *) data->getBytesNoCopy(); + + if (cs->year) + OSBitOrAtomic(kIOPMAlarmBitCalendarWake, &_scheduledAlarms); + else + OSBitAndAtomic(~kIOPMAlarmBitCalendarWake, &_scheduledAlarms); + DLOG("_scheduledAlarms = 0x%x\n", (uint32_t) _scheduledAlarms); + } + } + } + else + { + DLOG("setProperties(%s) not handled\n", key->getCStringNoCopy()); + } } exit: + if(publish_simulated_battery_string) publish_simulated_battery_string->release(); if(boot_complete_string) boot_complete_string->release(); if(sys_shutdown_string) sys_shutdown_string->release(); if(stall_halt_string) stall_halt_string->release(); - if (battery_warning_disabled_string) battery_warning_disabled_string->release(); + if(battery_warning_disabled_string) battery_warning_disabled_string->release(); if(idle_seconds_string) idle_seconds_string->release(); if(sleepdisabled_string) sleepdisabled_string->release(); if(ondeck_sleepwake_uuid_string) ondeck_sleepwake_uuid_string->release(); -#if HIBERNATION + if(loginwindow_tracepoint_string) loginwindow_tracepoint_string->release(); +#if HIBERNATION if(hibernatemode_string) hibernatemode_string->release(); if(hibernatefile_string) hibernatefile_string->release(); if(hibernatefreeratio_string) hibernatefreeratio_string->release(); if(hibernatefreetime_string) hibernatefreetime_string->release(); #endif + if (iter) iter->release(); return return_value; } +// MARK: - +// MARK: Aggressiveness //****************************************************************************** -// aggressivenessChanged +// setAggressiveness // -// We are behind the command gate to examine changes to aggressives. +// Override IOService::setAggressiveness() //****************************************************************************** -void IOPMrootDomain::aggressivenessChanged( void ) +IOReturn IOPMrootDomain::setAggressiveness( + unsigned long type, + unsigned long value ) { - unsigned long minutesToSleep = 0; - unsigned long minutesToDisplayDim = 0; - - ASSERT_GATED(); - - // Fetch latest display and system sleep slider values. - getAggressiveness(kPMMinutesToSleep, &minutesToSleep); - getAggressiveness(kPMMinutesToDim, &minutesToDisplayDim); - DLOG("aggressiveness changed system %u, display %u\n", - (uint32_t) minutesToSleep, (uint32_t) minutesToDisplayDim); + return setAggressiveness( type, value, 0 ); +} - DLOG("idle time -> %ld secs (ena %d)\n", - idleSeconds, (minutesToSleep != 0)); +/* + * Private setAggressiveness() with an internal options argument. + */ +IOReturn IOPMrootDomain::setAggressiveness( + unsigned long type, + unsigned long value, + IOOptionBits options ) +{ + AggressivesRequest * entry; + AggressivesRequest * request; + bool found = false; - if (0x7fffffff == minutesToSleep) - minutesToSleep = idleSeconds; - - // How long to wait before sleeping the system once the displays turns - // off is indicated by 'extraSleepDelay'. - - if ( minutesToSleep > minutesToDisplayDim ) { - extraSleepDelay = minutesToSleep - minutesToDisplayDim; - } - else { - extraSleepDelay = 0; - } - - // system sleep timer was disabled, but not anymore. - if ( (sleepSlider == 0) && (minutesToSleep != 0) ) { - if (!wrangler) - { - sleepASAP = false; - changePowerStateToPriv(ON_STATE); - if (idleSeconds) - { - startIdleSleepTimer( idleSeconds ); - } - } - else - { - // Start idle sleep timer if wrangler went to sleep - // while system sleep was disabled. - - sleepASAP = false; - if (wranglerAsleep) - { - AbsoluteTime now; - uint64_t nanos; - uint32_t minutesSinceDisplaySleep = 0; - uint32_t sleepDelay; - - clock_get_uptime(&now); - if (CMP_ABSOLUTETIME(&now, &wranglerSleepTime) > 0) - { - SUB_ABSOLUTETIME(&now, &wranglerSleepTime); - absolutetime_to_nanoseconds(now, &nanos); - minutesSinceDisplaySleep = nanos / (60000000000ULL); - } - - if (extraSleepDelay > minutesSinceDisplaySleep) - { - sleepDelay = extraSleepDelay - minutesSinceDisplaySleep; - } - else - { - // 1 min idle sleep. - sleepDelay = 1; - } - - startIdleSleepTimer(sleepDelay * 60); - DLOG("display slept %u min, set idle timer to %u min\n", - minutesSinceDisplaySleep, sleepDelay); - } - } - } - - sleepSlider = minutesToSleep; - if ( sleepSlider == 0 ) { - cancelIdleSleepTimer(); - // idle sleep is now disabled - adjustPowerState(); - // make sure we're powered - patriarch->wakeSystem(); - } -} - - -//****************************************************************************** -// setAggressiveness -// -// Override IOService::setAggressiveness() -//****************************************************************************** - -IOReturn IOPMrootDomain::setAggressiveness( - unsigned long type, - unsigned long value ) -{ - return setAggressiveness( type, value, 0 ); -} - -/* - * Private setAggressiveness() with an internal options argument. - */ -IOReturn IOPMrootDomain::setAggressiveness( - unsigned long type, - unsigned long value, - IOOptionBits options ) -{ - AggressivesRequest * entry; - AggressivesRequest * request; - bool found = false; - - DLOG("setAggressiveness 0x%x = %u, options 0x%x\n", - (uint32_t) type, (uint32_t) value, (uint32_t) options); + DLOG("setAggressiveness(%x) 0x%x = %u\n", + (uint32_t) options, (uint32_t) type, (uint32_t) value); request = IONew(AggressivesRequest, 1); if (!request) @@ -1182,7 +1378,6 @@ IOReturn IOPMrootDomain::setAggressiveness( return kIOReturnSuccess; } - //****************************************************************************** // getAggressiveness // @@ -1255,8 +1450,8 @@ IOReturn IOPMrootDomain::getAggressiveness ( if (source) { - DLOG("getAggressiveness 0x%x = %u, source %d\n", - (uint32_t) type, value, source); + DLOG("getAggressiveness(%d) 0x%x = %u\n", + source, (uint32_t) type, value); *outLevel = (unsigned long) value; return kIOReturnSuccess; } @@ -1268,7 +1463,6 @@ IOReturn IOPMrootDomain::getAggressiveness ( } } - //****************************************************************************** // joinAggressiveness // @@ -1283,7 +1477,7 @@ IOReturn IOPMrootDomain::joinAggressiveness( if (!service || (service == this)) return kIOReturnBadArgument; - DLOG("joinAggressiveness %s (%p)\n", service->getName(), service); + DLOG("joinAggressiveness %s %p\n", service->getName(), OBFUSCATE(service)); request = IONew(AggressivesRequest, 1); if (!request) @@ -1304,7 +1498,6 @@ IOReturn IOPMrootDomain::joinAggressiveness( return kIOReturnSuccess; } - //****************************************************************************** // handleAggressivesRequests // @@ -1370,7 +1563,7 @@ void IOPMrootDomain::handleAggressivesRequests( void ) broadcast = true; record->flags |= (kAggressivesRecordFlagMinValue | kAggressivesRecordFlagModified); - DLOG("quick spindown accelerated, was %u min\n", + DLOG("disk spindown accelerated, was %u min\n", record->value); } } @@ -1472,11 +1665,12 @@ unlock_done: // Submit a power event to handle those changes on the PM work loop. if (pingSelf && pmPowerStateQueue) { - pmPowerStateQueue->submitPowerEvent( kPowerEventAggressivenessChanged ); + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusAggressivenessChanged ); } } - //****************************************************************************** // synchronizeAggressives // @@ -1491,6 +1685,7 @@ void IOPMrootDomain::synchronizeAggressives( IOService * service; AggressivesRequest * request; const AggressivesRecord * record; + IOPMDriverCallEntry callEntry; uint32_t value; int i; @@ -1507,7 +1702,7 @@ void IOPMrootDomain::synchronizeAggressives( if (service) { - if (service->assertPMThreadCall()) + if (service->assertPMDriverCall(&callEntry)) { for (i = 0, record = array; i < count; i++, record++) { @@ -1515,18 +1710,17 @@ void IOPMrootDomain::synchronizeAggressives( if (record->flags & kAggressivesRecordFlagMinValue) value = kAggressivesMinValue; - DLOG("synchronizeAggressives 0x%x = %u to %s\n", + _LOG("synchronizeAggressives 0x%x = %u to %s\n", record->type, value, service->getName()); service->setAggressiveness(record->type, value); } - service->deassertPMThreadCall(); + service->deassertPMDriverCall(&callEntry); } service->release(); // retained by joinAggressiveness() } } } - //****************************************************************************** // broadcastAggressives // @@ -1537,18 +1731,19 @@ void IOPMrootDomain::broadcastAggressives( const AggressivesRecord * array, int count ) { - IORegistryIterator * iter; - IORegistryEntry * entry; - IOPowerConnection * connect; + IORegistryIterator * iter; + IORegistryEntry * entry; + IOPowerConnection * connect; IOService * service; const AggressivesRecord * record; + IOPMDriverCallEntry callEntry; uint32_t value; int i; - iter = IORegistryIterator::iterateOver( - this, gIOPowerPlane, kIORegistryIterateRecursively); + iter = IORegistryIterator::iterateOver( + this, gIOPowerPlane, kIORegistryIterateRecursively); if (iter) - { + { do { iter->reset(); @@ -1560,7 +1755,7 @@ void IOPMrootDomain::broadcastAggressives( if ((service = (IOService *) connect->copyChildEntry(gIOPowerPlane))) { - if (service->assertPMThreadCall()) + if (service->assertPMDriverCall(&callEntry)) { for (i = 0, record = array; i < count; i++, record++) { @@ -1569,12 +1764,12 @@ void IOPMrootDomain::broadcastAggressives( value = record->value; if (record->flags & kAggressivesRecordFlagMinValue) value = kAggressivesMinValue; - DLOG("broadcastAggressives %x = %u to %s\n", + _LOG("broadcastAggressives %x = %u to %s\n", record->type, value, service->getName()); service->setAggressiveness(record->type, value); } } - service->deassertPMThreadCall(); + service->deassertPMDriverCall(&callEntry); } service->release(); } @@ -1585,6 +1780,8 @@ void IOPMrootDomain::broadcastAggressives( } } +// MARK: - +// MARK: System Sleep //****************************************************************************** // startIdleSleepTimer @@ -1598,14 +1795,17 @@ void IOPMrootDomain::startIdleSleepTimer( uint32_t inSeconds ) ASSERT_GATED(); if (inSeconds) { - clock_interval_to_deadline(inSeconds, kSecondScale, &deadline); + clock_interval_to_deadline(inSeconds, kSecondScale, &deadline); thread_call_enter_delayed(extraSleepTimer, deadline); idleSleepTimerPending = true; - DLOG("idle timer set for %u seconds\n", inSeconds); } + else + { + thread_call_enter(extraSleepTimer); + } + DLOG("idle timer set for %u seconds\n", inSeconds); } - //****************************************************************************** // cancelIdleSleepTimer // @@ -1614,7 +1814,7 @@ void IOPMrootDomain::startIdleSleepTimer( uint32_t inSeconds ) void IOPMrootDomain::cancelIdleSleepTimer( void ) { ASSERT_GATED(); - if (idleSleepTimerPending) + if (idleSleepTimerPending) { DLOG("idle timer cancelled\n"); thread_call_cancel(extraSleepTimer); @@ -1622,7 +1822,6 @@ void IOPMrootDomain::cancelIdleSleepTimer( void ) } } - //****************************************************************************** // idleSleepTimerExpired // @@ -1634,13 +1833,6 @@ static void idleSleepTimerExpired( ((IOPMrootDomain *)us)->handleSleepTimerExpiration(); } -static void wakeupClamshellTimerExpired( - thread_call_param_t us, thread_call_param_t ) -{ - ((IOPMrootDomain *)us)->stopIgnoringClamshellEventsDuringWakeup(); -} - - //****************************************************************************** // handleSleepTimerExpiration // @@ -1674,40 +1866,80 @@ void IOPMrootDomain::handleSleepTimerExpiration( void ) return; } - // accelerate disk spin down if spin down timer is non-zero setQuickSpinDownTimeout(); - - sleepASAP = true; - adjustPowerState(); + adjustPowerState(true); } - //****************************************************************************** -// stopIgnoringClamshellEventsDuringWakeup +// getTimeToIdleSleep // +// Returns number of seconds left before going into idle sleep. +// Caller has to make sure that idle sleep is allowed at the time of calling +// this function //****************************************************************************** -void IOPMrootDomain::stopIgnoringClamshellEventsDuringWakeup( void ) +uint32_t IOPMrootDomain::getTimeToIdleSleep( void ) { - if (!getPMworkloop()->inGate()) + + AbsoluteTime now, lastActivityTime; + uint64_t nanos; + uint32_t minutesSinceUserInactive = 0; + uint32_t sleepDelay = 0; + + if (sleepSlider == 0) + return 0xffffffff; + + if (userActivityTime) + lastActivityTime = userActivityTime; + else + lastActivityTime = userBecameInactiveTime; + + clock_get_uptime(&now); + if (CMP_ABSOLUTETIME(&now, &lastActivityTime) > 0) { - getPMworkloop()->runAction( - OSMemberFunctionCast(IOWorkLoop::Action, this, - &IOPMrootDomain::stopIgnoringClamshellEventsDuringWakeup), - this); - return; + SUB_ABSOLUTETIME(&now, &lastActivityTime); + absolutetime_to_nanoseconds(now, &nanos); + minutesSinceUserInactive = nanos / (60000000000ULL); + + if (minutesSinceUserInactive >= sleepSlider) + sleepDelay = 0; + else + sleepDelay = sleepSlider - minutesSinceUserInactive; + } + else + { + sleepDelay = sleepSlider; } - ASSERT_GATED(); + DLOG("user inactive %u min, time to idle sleep %u min\n", + minutesSinceUserInactive, sleepDelay); + + return (sleepDelay * 60); +} - // Allow clamshell-induced sleep now - ignoringClamshellOnWake = false; +//****************************************************************************** +// setQuickSpinDownTimeout +// +//****************************************************************************** - // Re-send clamshell event, in case it causes a sleep - if (clamshellIsClosed) - handlePowerNotification( kLocalEvalClamshellCommand ); +void IOPMrootDomain::setQuickSpinDownTimeout( void ) +{ + ASSERT_GATED(); + setAggressiveness( + kPMMinutesToSpinDown, 0, kAggressivesOptionQuickSpindownEnable ); } +//****************************************************************************** +// restoreUserSpinDownTimeout +// +//****************************************************************************** + +void IOPMrootDomain::restoreUserSpinDownTimeout( void ) +{ + ASSERT_GATED(); + setAggressiveness( + kPMMinutesToSpinDown, 0, kAggressivesOptionQuickSpindownDisable ); +} //****************************************************************************** // sleepSystem @@ -1723,814 +1955,3817 @@ IOReturn IOPMrootDomain::sleepSystem( void ) /* private */ IOReturn IOPMrootDomain::sleepSystemOptions( OSDictionary *options ) { - /* sleepSystem is a public function, and may be called by any kernel driver. - * And that's bad - drivers should sleep the system by calling + OSObject *obj = NULL; + OSString *reason = NULL; + /* sleepSystem is a public function, and may be called by any kernel driver. + * And that's bad - drivers should sleep the system by calling * receivePowerNotification() instead. Drivers should not use sleepSystem. * * Note that user space app calls to IOPMSleepSystem() will also travel * this code path and thus be correctly identified as software sleeps. */ - - if (options && options->getObject("OSSwitch")) - { + if (options && options->getObject("OSSwitch")) + { // Log specific sleep cause for OS Switch hibernation - return privateSleepSystem( kIOPMOSSwitchHibernationKey) ; - - } else { - - return privateSleepSystem( kIOPMSoftwareSleepKey); + return privateSleepSystem( kIOPMSleepReasonOSSwitchHibernate); + } + if (options && (obj = options->getObject("Sleep Reason"))) + { + reason = OSDynamicCast(OSString, obj); + if (reason && reason->isEqualTo(kIOPMDarkWakeThermalEmergencyKey)) + return privateSleepSystem(kIOPMSleepReasonDarkWakeThermalEmergency); } + + return privateSleepSystem( kIOPMSleepReasonSoftware); } /* private */ -IOReturn IOPMrootDomain::privateSleepSystem( const char *sleepReason ) +IOReturn IOPMrootDomain::privateSleepSystem( uint32_t sleepReason ) { - if ( userDisabledAllSleep ) - { - LOG("Sleep prevented by user disable\n"); - - /* Prevent sleep of all kinds if directed to by user space */ - return kIOReturnNotPermitted; - } + /* Called from both gated and non-gated context */ - if ( systemBooting || systemShutdown || !allowSleep ) + if (!checkSystemSleepEnabled() || !pmPowerStateQueue) { - LOG("Sleep prevented by SB %d, SS %d, AS %d\n", - systemBooting, systemShutdown, allowSleep); - - // Unable to sleep because system is in the process of booting or - // shutting down, or sleep has otherwise been disallowed. - return kIOReturnError; - } - - // Record sleep cause in IORegistry - if (sleepReason) { - setProperty(kRootDomainSleepReasonKey, sleepReason); + return kIOReturnNotPermitted; } - tracePoint(kIOPMTracePointSleepStarted); + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusDemandSystemSleep, + sleepReason); - patriarch->sleepSystem(); return kIOReturnSuccess; } - -//****************************************************************************** -// shutdownSystem -// -//****************************************************************************** - -IOReturn IOPMrootDomain::shutdownSystem( void ) -{ - //patriarch->shutDownSystem(); - return kIOReturnUnsupported; -} - - -//****************************************************************************** -// restartSystem -// -//****************************************************************************** - -IOReturn IOPMrootDomain::restartSystem( void ) -{ - //patriarch->restartSystem(); - return kIOReturnUnsupported; -} - - //****************************************************************************** // powerChangeDone // // This overrides powerChangeDone in IOService. -// -// Menu sleep and idle sleep move us from the ON state to the SLEEP_STATE. -// In this case: -// If we finished going to the SLEEP_STATE, and the platform is capable of -// true sleep, then 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 ) +void IOPMrootDomain::powerChangeDone( unsigned long previousPowerState ) { ASSERT_GATED(); DLOG("PowerChangeDone: %u->%u\n", - (uint32_t) previousState, (uint32_t) getPowerState()); + (uint32_t) previousPowerState, (uint32_t) getPowerState()); - switch ( getPowerState() ) { - case SLEEP_STATE: - if ( previousState != ON_STATE ) - break; + switch ( getPowerState() ) + { + case SLEEP_STATE: { + if (previousPowerState != ON_STATE) + break; - if ( canSleep ) - { - // re-enable this timer for next sleep - cancelIdleSleepTimer(); - wranglerTickled = true; + acceptSystemWakeEvents(true); - clock_sec_t secs; - clock_usec_t microsecs; - clock_get_calendar_microtime(&secs, µsecs); - logtime(secs); - gIOLastSleepTime.tv_sec = secs; - gIOLastSleepTime.tv_usec = microsecs; - gIOLastWakeTime.tv_sec = 0; - gIOLastWakeTime.tv_usec = 0; + // re-enable this timer for next sleep + cancelIdleSleepTimer(); -#if HIBERNATION - LOG("System %sSleep\n", gIOHibernateState ? "Safe" : ""); + clock_sec_t secs; + clock_usec_t microsecs; + clock_get_calendar_microtime(&secs, µsecs); + logtime(secs); + gIOLastSleepTime.tv_sec = secs; + gIOLastSleepTime.tv_usec = microsecs; + gIOLastWakeTime.tv_sec = 0; + gIOLastWakeTime.tv_usec = 0; - tracePoint(kIOPMTracePointSystemHibernatePhase); +#if HIBERNATION + LOG("System %sSleep\n", gIOHibernateState ? "Safe" : ""); + + IOHibernateSystemHasSlept(); - IOHibernateSystemHasSlept(); + evaluateSystemSleepPolicyFinal(); #else - LOG("System Sleep\n"); + LOG("System Sleep\n"); #endif + if (thermalWarningState) { + const OSSymbol *event = OSSymbol::withCString(kIOPMThermalLevelWarningKey); + if (event) { + systemPowerEventOccurred(event, kIOPMThermalLevelUnknown); + event->release(); + } + } + ((IOService *)this)->stop_watchdog_timer(); //14456299 + getPlatform()->sleepKernel(); - tracePoint(kIOPMTracePointSystemSleepPlatformPhase); - - getPlatform()->sleepKernel(); + // The CPU(s) are off at this point, + // Code will resume execution here upon wake. - // The CPU(s) are off at this point. When they're awakened by CPU interrupt, - // code will resume execution here. + clock_get_uptime(&systemWakeTime); + _highestCapability = 0; - // Now we're waking... - tracePoint(kIOPMTracePointSystemWakeDriversPhase); - -#if HIBERNATION - IOHibernateSystemWake(); + ((IOService *)this)->start_watchdog_timer(); //14456299 +#if HIBERNATION + IOHibernateSystemWake(); #endif - // sleep transition complete - gSleepOrShutdownPending = 0; - - // trip the reset of the calendar clock - clock_wakeup_calendar(); - - // get us some power - patriarch->wakeSystem(); + // sleep transition complete + gSleepOrShutdownPending = 0; - // Set indicator if UUID was set - allow it to be cleared. - if (getProperty(kIOPMSleepWakeUUIDKey)) - gSleepWakeUUIDIsSet = true; + // trip the reset of the calendar clock + clock_wakeup_calendar(); -#if !ROOT_DOMAIN_RUN_STATES - tellClients(kIOMessageSystemWillPowerOn, clientMessageFilter); +#if HIBERNATION + LOG("System %sWake\n", gIOHibernateState ? "SafeSleep " : ""); #endif -#if HIBERNATION - LOG("System %sWake\n", gIOHibernateState ? "SafeSleep " : ""); -#endif + // log system wake + PMDebug(kPMLogSystemWake, 0, 0); + lowBatteryCondition = false; + lastSleepReason = 0; - // log system wake - getPlatform()->PMLog(kIOPMrootDomainClass, kPMLogSystemWake, 0, 0); + _lastDebugWakeSeconds = _debugWakeSeconds; + _debugWakeSeconds = 0; + _scheduledAlarms = 0; #ifndef __LP64__ - // tell the tree we're waking - systemWake(); + systemWake(); #endif - #if defined(__i386__) || defined(__x86_64__) -#if ROOT_DOMAIN_RUN_STATES - OSString * wakeType = OSDynamicCast( - OSString, getProperty(kIOPMRootDomainWakeTypeKey)); - if (wakeType && wakeType->isEqualTo(kIOPMRootDomainWakeTypeMaintenance)) + wranglerTickled = false; + graphicsSuppressed = false; + darkWakePostTickle = false; + darkWakeHibernateError = false; + darkWakeToSleepASAP = true; + logGraphicsClamp = true; + sleepTimerMaintenance = false; + sleepToStandby = false; + wranglerTickleLatched = false; + userWasActive = false; + fullWakeReason = kFullWakeReasonNone; + + + OSString * wakeType = OSDynamicCast( + OSString, getProperty(kIOPMRootDomainWakeTypeKey)); + OSString * wakeReason = OSDynamicCast( + OSString, getProperty(kIOPMRootDomainWakeReasonKey)); + + if (wakeReason && (wakeReason->getLength() >= 2) && + gWakeReasonString[0] == '\0') + { + // Until the platform driver can claim its wake reasons + strlcat(gWakeReasonString, wakeReason->getCStringNoCopy(), + sizeof(gWakeReasonString)); + } + + if (wakeType && wakeType->isEqualTo(kIOPMrootDomainWakeTypeLowBattery)) + { + lowBatteryCondition = true; + darkWakeMaintenance = true; + } + else if ((gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) != 0) + { + OSNumber * hibOptions = OSDynamicCast( + OSNumber, getProperty(kIOHibernateOptionsKey)); + + if (hibernateAborted || ((hibOptions && + !(hibOptions->unsigned32BitValue() & kIOHibernateOptionDarkWake)))) { - updateRunState(kRStateMaintenance); - wranglerTickled = false; + // Hibernate aborted, or EFI brought up graphics + wranglerTickled = true; + DLOG("hibernation aborted %d, options 0x%x\n", + hibernateAborted, + hibOptions ? hibOptions->unsigned32BitValue() : 0); } else -#endif /* ROOT_DOMAIN_RUN_STATES */ + if (wakeType && ( + wakeType->isEqualTo(kIOPMRootDomainWakeTypeUser) || + wakeType->isEqualTo(kIOPMRootDomainWakeTypeAlarm))) { - updateRunState(kRStateNormal); - reportUserInput(); + // User wake or RTC alarm + wranglerTickled = true; } -#else /* !__i386__ && !__x86_64__ */ - // stay awake for at least 30 seconds - startIdleSleepTimer(30); - reportUserInput(); -#endif - - changePowerStateToPriv(ON_STATE); - } else { - updateRunState(kRStateNormal); - - // allow us to step up a power state - patriarch->sleepToDoze(); + else + if (wakeType && + wakeType->isEqualTo(kIOPMRootDomainWakeTypeSleepTimer)) + { + // SMC standby timer trumps SleepX + darkWakeMaintenance = true; + sleepTimerMaintenance = true; + } + else + if ((_lastDebugWakeSeconds != 0) && + ((gDarkWakeFlags & kDarkWakeFlagAlarmIsDark) == 0)) + { + // SleepX before maintenance + wranglerTickled = true; + } + else + if (wakeType && + wakeType->isEqualTo(kIOPMRootDomainWakeTypeMaintenance)) + { + darkWakeMaintenance = true; + } + else + if (wakeType && + wakeType->isEqualTo(kIOPMRootDomainWakeTypeSleepService)) + { + darkWakeMaintenance = true; + darkWakeSleepService = true; + if (kIOHibernateStateWakingFromHibernate == gIOHibernateState) { + sleepToStandby = true; + } + } + else + if (wakeType && + wakeType->isEqualTo(kIOPMRootDomainWakeTypeHibernateError)) + { + darkWakeMaintenance = true; + darkWakeHibernateError = true; + } + else + { + // Unidentified wake source, resume to full wake if debug + // alarm is pending. - // ignore children's request for higher power during doze. - changePowerStateWithOverrideTo(DOZE_STATE); + if (_lastDebugWakeSeconds && + (!wakeReason || wakeReason->isEqualTo(""))) + wranglerTickled = true; + } } - break; - - case DOZE_STATE: - if ( previousState != DOZE_STATE ) + else { - LOG("System Doze\n"); + if (wakeType && + wakeType->isEqualTo(kIOPMRootDomainWakeTypeSleepTimer)) + { + darkWakeMaintenance = true; + sleepTimerMaintenance = true; + } + else if (hibernateAborted || !wakeType || + !wakeType->isEqualTo(kIOPMRootDomainWakeTypeMaintenance) || + !wakeReason || !wakeReason->isEqualTo("RTC")) + { + // Post a HID tickle immediately - except for RTC maintenance wake. + wranglerTickled = true; + } + else + { + darkWakeMaintenance = true; + } } - // re-enable this timer for next sleep - cancelIdleSleepTimer(); - gSleepOrShutdownPending = 0; - - // Invalidate prior activity tickles to allow wake from doze. - if (wrangler) wrangler->changePowerStateTo(0); - break; -#if ROOT_DOMAIN_RUN_STATES - case ON_STATE: - // SLEEP -> ON (Maintenance) - // Go back to sleep, unless cancelled by a HID event. - - if ((previousState == SLEEP_STATE) && - (runStateIndex == kRStateMaintenance) && - !wranglerTickled) + if (wranglerTickled) { - setProperty(kRootDomainSleepReasonKey, kIOPMMaintenanceSleepKey); - changePowerStateWithOverrideTo(SLEEP_STATE); + darkWakeToSleepASAP = false; + fullWakeReason = kFullWakeReasonLocalUser; + reportUserInput(); } - - // ON -> ON triggered by R-state changes. - - if ((previousState == ON_STATE) && - (runStateIndex != nextRunStateIndex) && - (nextRunStateIndex < kRStateCount)) + else if (!darkWakeMaintenance) { - LOG("R-state changed %u->%u\n", - runStateIndex, nextRunStateIndex); - updateRunState(nextRunStateIndex); - - DLOG("kIOMessageSystemHasPoweredOn (%u)\n", - gMessageClientType); - tellClients(kIOMessageSystemHasPoweredOn, clientMessageFilter); + // Early/late tickle for non-maintenance wake. + if (((gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) == + kDarkWakeFlagHIDTickleEarly) || + ((gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) == + kDarkWakeFlagHIDTickleLate)) + { + darkWakePostTickle = true; + } } - - break; -#endif /* ROOT_DOMAIN_RUN_STATES */ +#else /* !__i386__ && !__x86_64__ */ + // stay awake for at least 30 seconds + wranglerTickled = true; + fullWakeReason = kFullWakeReasonLocalUser; + startIdleSleepTimer(30); +#endif + sleepCnt++; + + changePowerStateToPriv(ON_STATE); + } break; + } } - //****************************************************************************** -// wakeFromDoze +// requestPowerDomainState // -// 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. +// Extend implementation in IOService. Running on PM work loop thread. //****************************************************************************** -void IOPMrootDomain::wakeFromDoze( void ) +IOReturn IOPMrootDomain::requestPowerDomainState ( + IOPMPowerFlags childDesire, + IOPowerConnection * childConnection, + unsigned long specification ) { - if ( getPowerState() == DOZE_STATE ) - { - changePowerStateToPriv(ON_STATE); - patriarch->wakeSystem(); - } -} - - -//****************************************************************************** -// publishFeature -// -// Adds a new feature to the supported features dictionary -//****************************************************************************** + // Idle and system sleep prevention flags affects driver desire. + // Children desire are irrelevant so they are cleared. -void IOPMrootDomain::publishFeature( const char * feature ) -{ - publishFeature(feature, kRD_AllPowerSources, NULL); + return super::requestPowerDomainState(0, childConnection, specification); } - //****************************************************************************** -// publishFeature (with supported power source specified) +// updatePreventIdleSleepList // -// Adds a new feature to the supported features dictionary +// Called by IOService on PM work loop. +// Returns true if PM policy recognized the driver's desire to prevent idle +// sleep and updated the list of idle sleep preventers. Returns false otherwise //****************************************************************************** -void IOPMrootDomain::publishFeature( - const char *feature, - uint32_t supportedWhere, - uint32_t *uniqueFeatureID) +bool IOPMrootDomain::updatePreventIdleSleepList( + IOService * service, bool addNotRemove ) { - static uint16_t next_feature_id = 500; + unsigned int oldCount, newCount; - OSNumber *new_feature_data = NULL; - OSNumber *existing_feature = NULL; - OSArray *existing_feature_arr = NULL; - OSObject *osObj = NULL; - uint32_t feature_value = 0; + ASSERT_GATED(); - supportedWhere &= kRD_AllPowerSources; // mask off any craziness! +#if defined(__i386__) || defined(__x86_64__) + // Disregard disk I/O (besides the display wrangler) as a factor preventing + // idle sleep, except in the case of legacy disk I/O + if ((service != wrangler) && (service != this)) + { + return false; + } +#endif - if(!supportedWhere) { - // Feature isn't supported anywhere! - return; + oldCount = preventIdleSleepList->getCount(); + if (addNotRemove) + { + preventIdleSleepList->setObject(service); + DLOG("prevent idle sleep list: %s+ (%u)\n", + service->getName(), preventIdleSleepList->getCount()); } - - if(next_feature_id > 5000) { - // Far, far too many features! - return; + else if (preventIdleSleepList->member(service)) + { + preventIdleSleepList->removeObject(service); + DLOG("prevent idle sleep list: %s- (%u)\n", + service->getName(), preventIdleSleepList->getCount()); } + newCount = preventIdleSleepList->getCount(); - if(featuresDictLock) IOLockLock(featuresDictLock); + if ((oldCount == 0) && (newCount != 0)) + { + // Driver added to empty prevent list. + // Update the driver desire to prevent idle sleep. + // Driver desire does not prevent demand sleep. - OSDictionary *features = - (OSDictionary *) getProperty(kRootDomainSupportedFeatures); - - // Create new features dict if necessary - if ( features && OSDynamicCast(OSDictionary, features)) { - features = OSDictionary::withDictionary(features); - } else { - features = OSDictionary::withCapacity(1); + changePowerStateTo(ON_STATE); } - - // Create OSNumber to track new feature - - next_feature_id += 1; - if( uniqueFeatureID ) { - // We don't really mind if the calling kext didn't give us a place - // to stash their unique id. Many kexts don't plan to unload, and thus - // have no need to remove themselves later. - *uniqueFeatureID = next_feature_id; + else if ((oldCount != 0) && (newCount == 0)) + { + // Last driver removed from prevent list. + // Drop the driver clamp to allow idle sleep. + + changePowerStateTo(SLEEP_STATE); + evaluatePolicy( kStimulusNoIdleSleepPreventers ); } - feature_value = (uint32_t)next_feature_id; - feature_value <<= 16; - feature_value += supportedWhere; +#if defined(__i386__) || defined(__x86_64__) + if (addNotRemove && (service == wrangler) && !checkSystemCanSustainFullWake()) + { + return false; // do not idle-cancel + } +#endif - new_feature_data = OSNumber::withNumber( - (unsigned long long)feature_value, 32); + return true; +} - // Does features object already exist? - if( (osObj = features->getObject(feature)) ) +//****************************************************************************** +// preventSystemSleepListUpdate +// +// Called by IOService on PM work loop. +//****************************************************************************** + +void IOPMrootDomain::updatePreventSystemSleepList( + IOService * service, bool addNotRemove ) +{ + unsigned int oldCount; + + ASSERT_GATED(); + if (this == service) + return; + + oldCount = preventSystemSleepList->getCount(); + if (addNotRemove) { - if(( existing_feature = OSDynamicCast(OSNumber, osObj) )) - { - // We need to create an OSArray to hold the now 2 elements. - existing_feature_arr = OSArray::withObjects( - (const OSObject **)&existing_feature, 1, 2); - } else if(( existing_feature_arr = OSDynamicCast(OSArray, osObj) )) - { - // Add object to existing array - existing_feature_arr = OSArray::withArray( - existing_feature_arr, - existing_feature_arr->getCount() + 1); - } + preventSystemSleepList->setObject(service); + DLOG("prevent system sleep list: %s+ (%u)\n", + service->getName(), preventSystemSleepList->getCount()); + } + else if (preventSystemSleepList->member(service)) + { + preventSystemSleepList->removeObject(service); + DLOG("prevent system sleep list: %s- (%u)\n", + service->getName(), preventSystemSleepList->getCount()); - if (existing_feature_arr) + if ((oldCount != 0) && (preventSystemSleepList->getCount() == 0)) { - existing_feature_arr->setObject(new_feature_data); - features->setObject(feature, existing_feature_arr); - existing_feature_arr->release(); - existing_feature_arr = 0; + // Lost all system sleep preventers. + // Send stimulus if system sleep was blocked, and is in dark wake. + evaluatePolicy( kStimulusDarkWakeEvaluate ); } - } else { - // The easy case: no previously existing features listed. We simply - // set the OSNumber at key 'feature' and we're on our way. - features->setObject(feature, new_feature_data); } - - new_feature_data->release(); +} - setProperty(kRootDomainSupportedFeatures, features); +//****************************************************************************** +// tellChangeDown +// +// Override the superclass implementation to send a different message type. +//****************************************************************************** - features->release(); +bool IOPMrootDomain::tellChangeDown( unsigned long stateNum ) +{ + DLOG("tellChangeDown %u->%u\n", + (uint32_t) getPowerState(), (uint32_t) stateNum); - if(featuresDictLock) IOLockUnlock(featuresDictLock); + if (SLEEP_STATE == stateNum) + { + // Legacy apps were already told in the full->dark transition + if (!ignoreTellChangeDown) + tracePoint( kIOPMTracePointSleepApplications ); + else + tracePoint( kIOPMTracePointSleepPriorityClients ); + } - // Notify EnergySaver and all those in user space so they might - // re-populate their feature specific UI - if(pmPowerStateQueue) { - pmPowerStateQueue->submitPowerEvent( kPowerEventFeatureChanged ); + if ((SLEEP_STATE == stateNum) && !ignoreTellChangeDown) + { + userActivityAtSleep = userActivityCount; + hibernateAborted = false; + DLOG("tellChangeDown::userActivityAtSleep %d\n", userActivityAtSleep); + + // Direct callout into OSKext so it can disable kext unloads + // during sleep/wake to prevent deadlocks. + OSKextSystemSleepOrWake( kIOMessageSystemWillSleep ); + + IOService::updateConsoleUsers(NULL, kIOMessageSystemWillSleep); + + // Two change downs are sent by IOServicePM. Ignore the 2nd. + // But tellClientsWithResponse() must be called for both. + ignoreTellChangeDown = true; } -} + return super::tellClientsWithResponse( kIOMessageSystemWillSleep ); +} //****************************************************************************** -// removePublishedFeature +// askChangeDown // -// Removes previously published feature +// Override the superclass implementation to send a different message type. +// This must be idle sleep since we don't ask during any other power change. //****************************************************************************** -IOReturn IOPMrootDomain::removePublishedFeature( uint32_t removeFeatureID ) +bool IOPMrootDomain::askChangeDown( unsigned long stateNum ) { - IOReturn ret = kIOReturnError; - uint32_t feature_value = 0; - uint16_t feature_id = 0; - bool madeAChange = false; - - OSSymbol *dictKey = NULL; - OSCollectionIterator *dictIterator = NULL; - OSArray *arrayMember = NULL; - OSNumber *numberMember = NULL; - OSObject *osObj = NULL; - OSNumber *osNum = NULL; - OSArray *arrayMemberCopy; + DLOG("askChangeDown %u->%u\n", + (uint32_t) getPowerState(), (uint32_t) stateNum); - if(featuresDictLock) IOLockLock(featuresDictLock); + // Don't log for dark wake entry + if (kSystemTransitionSleep == _systemTransitionType) + tracePoint( kIOPMTracePointSleepApplications ); - OSDictionary *features = - (OSDictionary *) getProperty(kRootDomainSupportedFeatures); - - if ( features && OSDynamicCast(OSDictionary, features) ) - { - // Any modifications to the dictionary are made to the copy to prevent - // races & crashes with userland clients. Dictionary updated - // automically later. - features = OSDictionary::withDictionary(features); - } else { - features = NULL; - ret = kIOReturnNotFound; - goto exit; - } - - // We iterate 'features' dictionary looking for an entry tagged - // with 'removeFeatureID'. If found, we remove it from our tracking - // structures and notify the OS via a general interest message. - - dictIterator = OSCollectionIterator::withCollection(features); - if(!dictIterator) { - goto exit; - } - - while( (dictKey = OSDynamicCast(OSSymbol, dictIterator->getNextObject())) ) + return super::tellClientsWithResponse( kIOMessageCanSystemSleep ); +} + +//****************************************************************************** +// askChangeDownDone +// +// An opportunity for root domain to cancel the power transition, +// possibily due to an assertion created by powerd in response to +// kIOMessageCanSystemSleep. +// +// Idle sleep: +// full -> dark wake transition +// 1. Notify apps and powerd with kIOMessageCanSystemSleep +// 2. askChangeDownDone() +// dark -> sleep transition +// 1. Notify powerd with kIOMessageCanSystemSleep +// 2. askChangeDownDone() +// +// Demand sleep: +// full -> dark wake transition +// 1. Notify powerd with kIOMessageCanSystemSleep +// 2. askChangeDownDone() +// dark -> sleep transition +// 1. Notify powerd with kIOMessageCanSystemSleep +// 2. askChangeDownDone() +//****************************************************************************** + +void IOPMrootDomain::askChangeDownDone( + IOPMPowerChangeFlags * inOutChangeFlags, bool * cancel ) +{ + DLOG("askChangeDownDone(0x%x, %u) type %x, cap %x->%x\n", + *inOutChangeFlags, *cancel, + _systemTransitionType, + _currentCapability, _pendingCapability); + + if ((false == *cancel) && (kSystemTransitionSleep == _systemTransitionType)) { - osObj = features->getObject(dictKey); - - // Each Feature is either tracked by an OSNumber - if( osObj && (numberMember = OSDynamicCast(OSNumber, osObj)) ) - { - feature_value = numberMember->unsigned32BitValue(); - feature_id = (uint16_t)(feature_value >> 16); + // Dark->Sleep transition. + // Check if there are any deny sleep assertions. + // lastSleepReason already set by handleOurPowerChangeStart() - if( feature_id == (uint16_t)removeFeatureID ) - { - // Remove this node - features->removeObject(dictKey); - madeAChange = true; - break; - } - - // Or tracked by an OSArray of OSNumbers - } else if( osObj && (arrayMember = OSDynamicCast(OSArray, osObj)) ) + if (!checkSystemCanSleep(lastSleepReason)) { - unsigned int arrayCount = arrayMember->getCount(); - - for(unsigned int i=0; igetObject(i)); - if(!osNum) { - continue; - } - - feature_value = osNum->unsigned32BitValue(); - feature_id = (uint16_t)(feature_value >> 16); + // Cancel dark wake to sleep transition. + // Must re-scan assertions upon entering dark wake. - if( feature_id == (uint16_t)removeFeatureID ) - { - // Remove this node - if( 1 == arrayCount ) { - // If the array only contains one element, remove - // the whole thing. - features->removeObject(dictKey); - } else { - // Otherwise remove the element from a copy of the array. - arrayMemberCopy = OSArray::withArray(arrayMember); - if (arrayMemberCopy) - { - arrayMemberCopy->removeObject(i); - features->setObject(dictKey, arrayMemberCopy); - arrayMemberCopy->release(); - } - } + *cancel = true; + DLOG("cancel dark->sleep\n"); + } + } +} - madeAChange = true; - break; - } - } - } +//****************************************************************************** +// systemDidNotSleep +// +// Work common to both canceled or aborted sleep. +//****************************************************************************** + +void IOPMrootDomain::systemDidNotSleep( void ) +{ + if (!wrangler) + { + if (idleSeconds) + { + // stay awake for at least idleSeconds + startIdleSleepTimer(idleSeconds); + } } - - dictIterator->release(); - - if( madeAChange ) + else { - ret = kIOReturnSuccess; - - setProperty(kRootDomainSupportedFeatures, features); - - // Notify EnergySaver and all those in user space so they might - // re-populate their feature specific UI - if(pmPowerStateQueue) { - pmPowerStateQueue->submitPowerEvent( kPowerEventFeatureChanged ); + if (sleepSlider && !userIsActive) + { + // Manually start the idle sleep timer besides waiting for + // the user to become inactive. + startIdleSleepTimer( kIdleSleepRetryInterval ); } - } else { - ret = kIOReturnNotFound; } - -exit: - if(features) features->release(); - if(featuresDictLock) IOLockUnlock(featuresDictLock); - return ret; -} + preventTransitionToUserActive(false); + IOService::setAdvisoryTickleEnable( true ); +} //****************************************************************************** -// announcePowerSourceChange +// tellNoChangeDown +// +// Notify registered applications and kernel clients that we are not dropping +// power. // -// Notifies "interested parties" that the battery state has changed +// 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::announcePowerSourceChange( void ) +void IOPMrootDomain::tellNoChangeDown( unsigned long stateNum ) +{ + DLOG("tellNoChangeDown %u->%u\n", + (uint32_t) getPowerState(), (uint32_t) stateNum); + + // Sleep canceled, clear the sleep trace point. + tracePoint(kIOPMTracePointSystemUp); + + systemDidNotSleep(); + return tellClients( kIOMessageSystemWillNotSleep ); +} + +//****************************************************************************** +// tellChangeUp +// +// Notify registered applications and kernel clients that we are raising power. +// +// We override the superclass implementation so we can send a different message +// type to the client or application being notified. +//****************************************************************************** + +void IOPMrootDomain::tellChangeUp( unsigned long stateNum ) +{ + + DLOG("tellChangeUp %u->%u\n", + (uint32_t) getPowerState(), (uint32_t) stateNum); + + ignoreTellChangeDown = false; + + if ( stateNum == ON_STATE ) + { + // Direct callout into OSKext so it can disable kext unloads + // during sleep/wake to prevent deadlocks. + OSKextSystemSleepOrWake( kIOMessageSystemHasPoweredOn ); + + // Notify platform that sleep was cancelled or resumed. + getPlatform()->callPlatformFunction( + sleepMessagePEFunction, false, + (void *)(uintptr_t) kIOMessageSystemHasPoweredOn, + NULL, NULL, NULL); + + if (getPowerState() == ON_STATE) + { + // this is a quick wake from aborted sleep + systemDidNotSleep(); + tellClients( kIOMessageSystemWillPowerOn ); + } + + + tracePoint( kIOPMTracePointWakeApplications ); + tellClients( kIOMessageSystemHasPoweredOn ); + } +} + +//****************************************************************************** +// sysPowerDownHandler +// +// Perform a vfs sync before system sleep. +//****************************************************************************** + +IOReturn IOPMrootDomain::sysPowerDownHandler( + void * target, void * refCon, + UInt32 messageType, IOService * service, + void * messageArgs, vm_size_t argSize ) +{ + IOReturn ret = 0; + + DLOG("sysPowerDownHandler message %s\n", getIOMessageString(messageType)); + + if (!gRootDomain) + return kIOReturnUnsupported; + + if (messageType == kIOMessageSystemWillSleep) + { +#if HIBERNATION + uint32_t mem_only = 0; + IOPowerStateChangeNotification *notify = + (IOPowerStateChangeNotification *)messageArgs; + + PE_parse_boot_argn("swd_mem_only", &mem_only, sizeof(mem_only)); + if ((mem_only != 1) && (gRootDomain->sleepWakeDebugIsWdogEnabled())) + { + notify->returnValue = 30 * 1000 * 1000; + thread_call_enter1( + gRootDomain->hibDebugSetupEntry, + (thread_call_param_t)(uintptr_t) notify->powerRef); + } +#endif + } + else if (messageType == kIOMessageSystemCapabilityChange) + { + IOPMSystemCapabilityChangeParameters * params = + (IOPMSystemCapabilityChangeParameters *) messageArgs; + + // 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 + + DLOG("sysPowerDownHandler cap %x -> %x (flags %x)\n", + params->fromCapabilities, params->toCapabilities, + params->changeFlags); + + if ((params->changeFlags & kIOPMSystemCapabilityWillChange) && + (params->fromCapabilities & kIOPMSystemCapabilityCPU) && + (params->toCapabilities & kIOPMSystemCapabilityCPU) == 0) + { + // We will ack within 20 seconds + params->maxWaitForReply = 20 * 1000 * 1000; +#if HIBERNATION + gRootDomain->evaluateSystemSleepPolicyEarly(); + + // add in time we could spend freeing pages + if (gRootDomain->hibernateMode && !gRootDomain->hibernateDisabled) + { + params->maxWaitForReply = kCapabilityClientMaxWait; + } + DLOG("sysPowerDownHandler max wait %d s\n", + (int) (params->maxWaitForReply / 1000 / 1000)); +#endif + + // Notify platform that sleep has begun, after the early + // sleep policy evaluation. + getPlatform()->callPlatformFunction( + sleepMessagePEFunction, false, + (void *)(uintptr_t) kIOMessageSystemWillSleep, + NULL, NULL, NULL); + + 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( 30, kSecondScale, &deadline ); + thread_call_enter1_delayed( + gRootDomain->diskSyncCalloutEntry, + (thread_call_param_t)(uintptr_t) params->notifyRef, + deadline ); + } + else + thread_call_enter1( + gRootDomain->diskSyncCalloutEntry, + (thread_call_param_t)(uintptr_t) params->notifyRef); + } + else + if ((params->changeFlags & kIOPMSystemCapabilityDidChange) && + (params->toCapabilities & kIOPMSystemCapabilityCPU) && + (params->fromCapabilities & kIOPMSystemCapabilityCPU) == 0) + { +#if HIBERNATION + // We will ack within 110 seconds + params->maxWaitForReply = 110 * 1000 * 1000; + + thread_call_enter1( + gRootDomain->diskSyncCalloutEntry, + (thread_call_param_t)(uintptr_t) params->notifyRef); +#endif + } + ret = kIOReturnSuccess; + } + + return ret; +} + +//****************************************************************************** +// handleQueueSleepWakeUUID +// +// Called from IOPMrootDomain when we're initiating a sleep, +// or indirectly from PM configd when PM decides to clear the UUID. +// PM clears the UUID several minutes after successful wake from sleep, +// so that we might associate App spindumps with the immediately previous +// sleep/wake. +// +// @param obj has a retain on it. We're responsible for releasing that retain. +//****************************************************************************** + +void IOPMrootDomain::handleQueueSleepWakeUUID(OSObject *obj) +{ + OSString *str = NULL; + + if (kOSBooleanFalse == obj) + { + handlePublishSleepWakeUUID(NULL); + } + else if ((str = OSDynamicCast(OSString, obj))) + { + // This branch caches the UUID for an upcoming sleep/wake + if (queuedSleepWakeUUIDString) { + queuedSleepWakeUUIDString->release(); + queuedSleepWakeUUIDString = NULL; + } + queuedSleepWakeUUIDString = str; + queuedSleepWakeUUIDString->retain(); + + DLOG("SleepWake UUID queued: %s\n", queuedSleepWakeUUIDString->getCStringNoCopy()); + } + + if (obj) { + obj->release(); + } + return; + +} +//****************************************************************************** +// handlePublishSleepWakeUUID +// +// Called from IOPMrootDomain when we're initiating a sleep, +// or indirectly from PM configd when PM decides to clear the UUID. +// PM clears the UUID several minutes after successful wake from sleep, +// so that we might associate App spindumps with the immediately previous +// sleep/wake. +//****************************************************************************** + +void IOPMrootDomain::handlePublishSleepWakeUUID( bool shouldPublish ) +{ + ASSERT_GATED(); + + /* + * Clear the current UUID + */ + if (gSleepWakeUUIDIsSet) + { + DLOG("SleepWake UUID cleared\n"); + + gSleepWakeUUIDIsSet = false; + + removeProperty(kIOPMSleepWakeUUIDKey); + messageClients(kIOPMMessageSleepWakeUUIDChange, kIOPMMessageSleepWakeUUIDCleared); + } + + /* + * Optionally, publish a new UUID + */ + if (queuedSleepWakeUUIDString && shouldPublish) { + + OSString *publishThisUUID = NULL; + + publishThisUUID = queuedSleepWakeUUIDString; + publishThisUUID->retain(); + + if (publishThisUUID) + { + setProperty(kIOPMSleepWakeUUIDKey, publishThisUUID); + publishThisUUID->release(); + } + + gSleepWakeUUIDIsSet = true; + messageClients(kIOPMMessageSleepWakeUUIDChange, kIOPMMessageSleepWakeUUIDSet); + + queuedSleepWakeUUIDString->release(); + queuedSleepWakeUUIDString = NULL; + } +} + +//****************************************************************************** +// initializeBootSessionUUID +// +// Initialize the boot session uuid at boot up and sets it into registry. +//****************************************************************************** + +void IOPMrootDomain::initializeBootSessionUUID(void) +{ + uuid_t new_uuid; + uuid_string_t new_uuid_string; + + uuid_generate(new_uuid); + uuid_unparse_upper(new_uuid, new_uuid_string); + memcpy(bootsessionuuid_string, new_uuid_string, sizeof(uuid_string_t)); + + setProperty(kIOPMBootSessionUUIDKey, new_uuid_string); +} + +//****************************************************************************** +// changePowerStateTo & changePowerStateToPriv +// +// Override of these methods for logging purposes. +//****************************************************************************** + +IOReturn IOPMrootDomain::changePowerStateTo( unsigned long ordinal ) +{ + DLOG("changePowerStateTo(%lu)\n", ordinal); + + if ((ordinal != ON_STATE) && (ordinal != SLEEP_STATE)) + return kIOReturnUnsupported; + + return super::changePowerStateTo(ordinal); +} + +IOReturn IOPMrootDomain::changePowerStateToPriv( unsigned long ordinal ) +{ + DLOG("changePowerStateToPriv(%lu)\n", ordinal); + + if ((ordinal != ON_STATE) && (ordinal != SLEEP_STATE)) + return kIOReturnUnsupported; + + return super::changePowerStateToPriv(ordinal); +} + +//****************************************************************************** +// activity detect +// +//****************************************************************************** + +bool IOPMrootDomain::activitySinceSleep(void) +{ + return (userActivityCount != userActivityAtSleep); +} + +bool IOPMrootDomain::abortHibernation(void) +{ + bool ret = activitySinceSleep(); + + if (ret && !hibernateAborted && checkSystemCanSustainFullWake()) + { + DLOG("activitySinceSleep ABORT [%d, %d]\n", userActivityCount, userActivityAtSleep); + hibernateAborted = true; + } + return (ret); +} + +extern "C" int +hibernate_should_abort(void) +{ + if (gRootDomain) + return (gRootDomain->abortHibernation()); + else + return (0); +} + +//****************************************************************************** +// willNotifyPowerChildren +// +// Called after all interested drivers have all acknowledged the power change, +// but before any power children is informed. Dispatched though a thread call, +// so it is safe to perform work that might block on a sleeping disk. PM state +// machine (not thread) will block w/o timeout until this function returns. +//****************************************************************************** + +void IOPMrootDomain::willNotifyPowerChildren( IOPMPowerStateIndex newPowerState ) +{ +#if HIBERNATION + if (SLEEP_STATE == newPowerState) + { + IOHibernateSystemSleep(); + IOHibernateIOKitSleep(); + } +#endif +} + +//****************************************************************************** +// sleepOnClamshellClosed +// +// contains the logic to determine if the system should sleep when the clamshell +// is closed. +//****************************************************************************** + +bool IOPMrootDomain::shouldSleepOnClamshellClosed( void ) +{ + if (!clamshellExists) + return false; + + DLOG("clamshell closed %d, disabled %d, desktopMode %d, ac %d sleepDisabled %d\n", + clamshellClosed, clamshellDisabled, desktopMode, acAdaptorConnected, clamshellSleepDisabled); + + return ( !clamshellDisabled && !(desktopMode && acAdaptorConnected) && !clamshellSleepDisabled ); +} + +void IOPMrootDomain::sendClientClamshellNotification( void ) +{ + /* Only broadcast clamshell alert if clamshell exists. */ + if (!clamshellExists) + return; + + setProperty(kAppleClamshellStateKey, + clamshellClosed ? kOSBooleanTrue : kOSBooleanFalse); + + setProperty(kAppleClamshellCausesSleepKey, + shouldSleepOnClamshellClosed() ? kOSBooleanTrue : kOSBooleanFalse); + + /* Argument to message is a bitfiel of + * ( kClamshellStateBit | kClamshellSleepBit ) + */ + messageClients(kIOPMMessageClamshellStateChange, + (void *)(uintptr_t) ( (clamshellClosed ? kClamshellStateBit : 0) + | ( shouldSleepOnClamshellClosed() ? kClamshellSleepBit : 0)) ); +} + +//****************************************************************************** +// getSleepSupported +// +// Deprecated +//****************************************************************************** + +IOOptionBits IOPMrootDomain::getSleepSupported( void ) +{ + return( platformSleepSupport ); +} + +//****************************************************************************** +// setSleepSupported +// +// Deprecated +//****************************************************************************** + +void IOPMrootDomain::setSleepSupported( IOOptionBits flags ) +{ + DLOG("setSleepSupported(%x)\n", (uint32_t) flags); + OSBitOrAtomic(flags, &platformSleepSupport); +} + +//****************************************************************************** +// setDisableClamShellSleep +// +//****************************************************************************** + +void IOPMrootDomain::setDisableClamShellSleep( bool val ) +{ + if (gIOPMWorkLoop->inGate() == false) { + + gIOPMWorkLoop->runAction( + OSMemberFunctionCast(IOWorkLoop::Action, this, &IOPMrootDomain::setDisableClamShellSleep), + (OSObject *)this, + (void *)val); + + return; + } + else { + DLOG("setDisableClamShellSleep(%x)\n", (uint32_t) val); + if ( clamshellSleepDisabled != val ) + { + clamshellSleepDisabled = val; + // If clamshellSleepDisabled is reset to 0, reevaluate if + // system need to go to sleep due to clamshell state + if ( !clamshellSleepDisabled && clamshellClosed) + handlePowerNotification(kLocalEvalClamshellCommand); + } + } +} + +//****************************************************************************** +// wakeFromDoze +// +// Deprecated. +//****************************************************************************** + +void IOPMrootDomain::wakeFromDoze( void ) +{ + // Preserve symbol for familes (IOUSBFamily and IOGraphics) +} + +// MARK: - +// MARK: Features + +//****************************************************************************** +// publishFeature +// +// Adds a new feature to the supported features dictionary +//****************************************************************************** + +void IOPMrootDomain::publishFeature( const char * feature ) +{ + publishFeature(feature, kRD_AllPowerSources, NULL); +} + +//****************************************************************************** +// publishFeature (with supported power source specified) +// +// Adds a new feature to the supported features dictionary +//****************************************************************************** + +void IOPMrootDomain::publishFeature( + const char *feature, + uint32_t supportedWhere, + uint32_t *uniqueFeatureID) +{ + static uint16_t next_feature_id = 500; + + OSNumber *new_feature_data = NULL; + OSNumber *existing_feature = NULL; + OSArray *existing_feature_arr = NULL; + OSObject *osObj = NULL; + uint32_t feature_value = 0; + + supportedWhere &= kRD_AllPowerSources; // mask off any craziness! + + if(!supportedWhere) { + // Feature isn't supported anywhere! + return; + } + + if(next_feature_id > 5000) { + // Far, far too many features! + return; + } + + if(featuresDictLock) IOLockLock(featuresDictLock); + + OSDictionary *features = + (OSDictionary *) getProperty(kRootDomainSupportedFeatures); + + // Create new features dict if necessary + if ( features && OSDynamicCast(OSDictionary, features)) { + features = OSDictionary::withDictionary(features); + } else { + features = OSDictionary::withCapacity(1); + } + + // Create OSNumber to track new feature + + next_feature_id += 1; + if( uniqueFeatureID ) { + // We don't really mind if the calling kext didn't give us a place + // to stash their unique id. Many kexts don't plan to unload, and thus + // have no need to remove themselves later. + *uniqueFeatureID = next_feature_id; + } + + feature_value = (uint32_t)next_feature_id; + feature_value <<= 16; + feature_value += supportedWhere; + + new_feature_data = OSNumber::withNumber( + (unsigned long long)feature_value, 32); + + // Does features object already exist? + if( (osObj = features->getObject(feature)) ) + { + if(( existing_feature = OSDynamicCast(OSNumber, osObj) )) + { + // We need to create an OSArray to hold the now 2 elements. + existing_feature_arr = OSArray::withObjects( + (const OSObject **)&existing_feature, 1, 2); + } else if(( existing_feature_arr = OSDynamicCast(OSArray, osObj) )) + { + // Add object to existing array + existing_feature_arr = OSArray::withArray( + existing_feature_arr, + existing_feature_arr->getCount() + 1); + } + + if (existing_feature_arr) + { + existing_feature_arr->setObject(new_feature_data); + features->setObject(feature, existing_feature_arr); + existing_feature_arr->release(); + existing_feature_arr = 0; + } + } else { + // The easy case: no previously existing features listed. We simply + // set the OSNumber at key 'feature' and we're on our way. + features->setObject(feature, new_feature_data); + } + + new_feature_data->release(); + + setProperty(kRootDomainSupportedFeatures, features); + + features->release(); + + if(featuresDictLock) IOLockUnlock(featuresDictLock); + + // Notify EnergySaver and all those in user space so they might + // re-populate their feature specific UI + if(pmPowerStateQueue) { + pmPowerStateQueue->submitPowerEvent( kPowerEventFeatureChanged ); + } +} + +//****************************************************************************** +// removePublishedFeature +// +// Removes previously published feature +//****************************************************************************** + +IOReturn IOPMrootDomain::removePublishedFeature( uint32_t removeFeatureID ) +{ + IOReturn ret = kIOReturnError; + uint32_t feature_value = 0; + uint16_t feature_id = 0; + bool madeAChange = false; + + OSSymbol *dictKey = NULL; + OSCollectionIterator *dictIterator = NULL; + OSArray *arrayMember = NULL; + OSNumber *numberMember = NULL; + OSObject *osObj = NULL; + OSNumber *osNum = NULL; + OSArray *arrayMemberCopy; + + if (kBadPMFeatureID == removeFeatureID) + return kIOReturnNotFound; + + if(featuresDictLock) IOLockLock(featuresDictLock); + + OSDictionary *features = + (OSDictionary *) getProperty(kRootDomainSupportedFeatures); + + if ( features && OSDynamicCast(OSDictionary, features) ) + { + // Any modifications to the dictionary are made to the copy to prevent + // races & crashes with userland clients. Dictionary updated + // automically later. + features = OSDictionary::withDictionary(features); + } else { + features = NULL; + ret = kIOReturnNotFound; + goto exit; + } + + // We iterate 'features' dictionary looking for an entry tagged + // with 'removeFeatureID'. If found, we remove it from our tracking + // structures and notify the OS via a general interest message. + + dictIterator = OSCollectionIterator::withCollection(features); + if(!dictIterator) { + goto exit; + } + + while( (dictKey = OSDynamicCast(OSSymbol, dictIterator->getNextObject())) ) + { + osObj = features->getObject(dictKey); + + // Each Feature is either tracked by an OSNumber + if( osObj && (numberMember = OSDynamicCast(OSNumber, osObj)) ) + { + feature_value = numberMember->unsigned32BitValue(); + feature_id = (uint16_t)(feature_value >> 16); + + if( feature_id == (uint16_t)removeFeatureID ) + { + // Remove this node + features->removeObject(dictKey); + madeAChange = true; + break; + } + + // Or tracked by an OSArray of OSNumbers + } else if( osObj && (arrayMember = OSDynamicCast(OSArray, osObj)) ) + { + unsigned int arrayCount = arrayMember->getCount(); + + for(unsigned int i=0; igetObject(i)); + if(!osNum) { + continue; + } + + feature_value = osNum->unsigned32BitValue(); + feature_id = (uint16_t)(feature_value >> 16); + + if( feature_id == (uint16_t)removeFeatureID ) + { + // Remove this node + if( 1 == arrayCount ) { + // If the array only contains one element, remove + // the whole thing. + features->removeObject(dictKey); + } else { + // Otherwise remove the element from a copy of the array. + arrayMemberCopy = OSArray::withArray(arrayMember); + if (arrayMemberCopy) + { + arrayMemberCopy->removeObject(i); + features->setObject(dictKey, arrayMemberCopy); + arrayMemberCopy->release(); + } + } + + madeAChange = true; + break; + } + } + } + } + + dictIterator->release(); + + if( madeAChange ) + { + ret = kIOReturnSuccess; + + setProperty(kRootDomainSupportedFeatures, features); + + // Notify EnergySaver and all those in user space so they might + // re-populate their feature specific UI + if(pmPowerStateQueue) { + pmPowerStateQueue->submitPowerEvent( kPowerEventFeatureChanged ); + } + } else { + ret = kIOReturnNotFound; + } + +exit: + if(features) features->release(); + if(featuresDictLock) IOLockUnlock(featuresDictLock); + return ret; +} + +//****************************************************************************** +// publishPMSetting (private) +// +// Should only be called by PMSettingObject to publish a PM Setting as a +// supported feature. +//****************************************************************************** + +void IOPMrootDomain::publishPMSetting( + const OSSymbol * feature, uint32_t where, uint32_t * featureID ) +{ + if (noPublishPMSettings && + (noPublishPMSettings->getNextIndexOfObject(feature, 0) != (unsigned int)-1)) + { + // Setting found in noPublishPMSettings array + *featureID = kBadPMFeatureID; + return; + } + + publishFeature( + feature->getCStringNoCopy(), where, featureID); +} + +//****************************************************************************** +// setPMSetting (private) +// +// Internal helper to relay PM settings changes from user space to individual +// drivers. Should be called only by IOPMrootDomain::setProperties. +//****************************************************************************** + +IOReturn IOPMrootDomain::setPMSetting( + const OSSymbol *type, + OSObject *object ) +{ + PMSettingCallEntry *entries = 0; + OSArray *chosen = 0; + const OSArray *array; + PMSettingObject *pmso; + thread_t thisThread; + int i, j, count, capacity; + + if (NULL == type) + return kIOReturnBadArgument; + + PMSETTING_LOCK(); + + // Update settings dict so changes are visible from copyPMSetting(). + fPMSettingsDict->setObject(type, object); + + // Prep all PMSetting objects with the given 'type' for callout. + array = (const OSArray *) settingsCallbacks->getObject(type); + if (!array || ((capacity = array->getCount()) == 0)) + goto unlock_exit; + + // Array to retain PMSetting objects targeted for callout. + chosen = OSArray::withCapacity(capacity); + if (!chosen) + goto unlock_exit; // error + + entries = IONew(PMSettingCallEntry, capacity); + if (!entries) + goto unlock_exit; // error + memset(entries, 0, sizeof(PMSettingCallEntry) * capacity); + + thisThread = current_thread(); + + for (i = 0, j = 0; igetObject(i); + if (pmso->disabled) + continue; + entries[j].thread = thisThread; + queue_enter(&pmso->calloutQueue, &entries[j], PMSettingCallEntry *, link); + chosen->setObject(pmso); + j++; + } + count = j; + if (!count) + goto unlock_exit; + + PMSETTING_UNLOCK(); + + // Call each pmso in the chosen array. + for (i=0; igetObject(i); + pmso->dispatchPMSetting(type, object); + } + + PMSETTING_LOCK(); + for (i=0; igetObject(i); + queue_remove(&pmso->calloutQueue, &entries[i], PMSettingCallEntry *, link); + if (pmso->waitThread) + { + PMSETTING_WAKEUP(pmso); + } + } +unlock_exit: + PMSETTING_UNLOCK(); + + if (chosen) chosen->release(); + if (entries) IODelete(entries, PMSettingCallEntry, capacity); + + return kIOReturnSuccess; +} + +//****************************************************************************** +// copyPMSetting (public) +// +// Allows kexts to safely read setting values, without being subscribed to +// notifications. +//****************************************************************************** + +OSObject * IOPMrootDomain::copyPMSetting( + OSSymbol *whichSetting) +{ + OSObject *obj = NULL; + + if(!whichSetting) return NULL; + + PMSETTING_LOCK(); + obj = fPMSettingsDict->getObject(whichSetting); + if(obj) { + obj->retain(); + } + PMSETTING_UNLOCK(); + + return obj; +} + +//****************************************************************************** +// registerPMSettingController (public) +// +// direct wrapper to registerPMSettingController with uint32_t power source arg +//****************************************************************************** + +IOReturn IOPMrootDomain::registerPMSettingController( + const OSSymbol * settings[], + IOPMSettingControllerCallback func, + OSObject *target, + uintptr_t refcon, + OSObject **handle) +{ + return registerPMSettingController( + settings, + (kIOPMSupportedOnAC | kIOPMSupportedOnBatt | kIOPMSupportedOnUPS), + func, target, refcon, handle); +} + +//****************************************************************************** +// registerPMSettingController (public) +// +// Kexts may register for notifications when a particular setting is changed. +// A list of settings is available in IOPM.h. +// Arguments: +// * settings - An OSArray containing OSSymbols. Caller should populate this +// array with a list of settings caller wants notifications from. +// * func - A C function callback of the type IOPMSettingControllerCallback +// * target - caller may provide an OSObject *, which PM will pass as an +// target to calls to "func" +// * refcon - caller may provide an void *, which PM will pass as an +// argument to calls to "func" +// * handle - This is a return argument. We will populate this pointer upon +// call success. Hold onto this and pass this argument to +// IOPMrootDomain::deRegisterPMSettingCallback when unloading your kext +// Returns: +// kIOReturnSuccess on success +//****************************************************************************** + +IOReturn IOPMrootDomain::registerPMSettingController( + const OSSymbol * settings[], + uint32_t supportedPowerSources, + IOPMSettingControllerCallback func, + OSObject *target, + uintptr_t refcon, + OSObject **handle) +{ + PMSettingObject *pmso = NULL; + OSObject *pmsh = NULL; + OSArray *list = NULL; + int i; + + if (NULL == settings || + NULL == func || + NULL == handle) + { + return kIOReturnBadArgument; + } + + pmso = PMSettingObject::pmSettingObject( + (IOPMrootDomain *) this, func, target, + refcon, supportedPowerSources, settings, &pmsh); + + if (!pmso) { + *handle = NULL; + return kIOReturnInternalError; + } + + PMSETTING_LOCK(); + for (i=0; settings[i]; i++) + { + list = (OSArray *) settingsCallbacks->getObject(settings[i]); + if (!list) { + // New array of callbacks for this setting + list = OSArray::withCapacity(1); + settingsCallbacks->setObject(settings[i], list); + list->release(); + } + + // Add caller to the callback list + list->setObject(pmso); + } + PMSETTING_UNLOCK(); + + // Return handle to the caller, the setting object is private. + *handle = pmsh; + + return kIOReturnSuccess; +} + +//****************************************************************************** +// deregisterPMSettingObject (private) +// +// Only called from PMSettingObject. +//****************************************************************************** + +void IOPMrootDomain::deregisterPMSettingObject( PMSettingObject * pmso ) +{ + thread_t thisThread = current_thread(); + PMSettingCallEntry *callEntry; + OSCollectionIterator *iter; + OSSymbol *sym; + OSArray *array; + int index; + bool wait; + + PMSETTING_LOCK(); + + pmso->disabled = true; + + // Wait for all callout threads to finish. + do { + wait = false; + queue_iterate(&pmso->calloutQueue, callEntry, PMSettingCallEntry *, link) + { + if (callEntry->thread != thisThread) + { + wait = true; + break; + } + } + if (wait) + { + assert(0 == pmso->waitThread); + pmso->waitThread = thisThread; + PMSETTING_WAIT(pmso); + pmso->waitThread = 0; + } + } while (wait); + + // Search each PM settings array in the kernel. + iter = OSCollectionIterator::withCollection(settingsCallbacks); + if (iter) + { + while ((sym = OSDynamicCast(OSSymbol, iter->getNextObject()))) + { + array = (OSArray *) settingsCallbacks->getObject(sym); + index = array->getNextIndexOfObject(pmso, 0); + if (-1 != index) { + array->removeObject(index); + } + } + iter->release(); + } + + PMSETTING_UNLOCK(); + + pmso->release(); +} + +//****************************************************************************** +// informCPUStateChange +// +// Call into PM CPU code so that CPU power savings may dynamically adjust for +// running on battery, with the lid closed, etc. +// +// informCPUStateChange is a no-op on non x86 systems +// only x86 has explicit support in the IntelCPUPowerManagement kext +//****************************************************************************** + +void IOPMrootDomain::informCPUStateChange( + uint32_t type, + uint32_t value ) +{ +#if defined(__i386__) || defined(__x86_64__) + + pmioctlVariableInfo_t varInfoStruct; + int pmCPUret = 0; + const char *varNameStr = NULL; + int32_t *varIndex = NULL; + + if (kInformAC == type) { + varNameStr = kIOPMRootDomainBatPowerCString; + varIndex = &idxPMCPULimitedPower; + } else if (kInformLid == type) { + varNameStr = kIOPMRootDomainLidCloseCString; + varIndex = &idxPMCPUClamshell; + } else { + return; + } + + // Set the new value! + // pmCPUControl will assign us a new ID if one doesn't exist yet + bzero(&varInfoStruct, sizeof(pmioctlVariableInfo_t)); + varInfoStruct.varID = *varIndex; + varInfoStruct.varType = vBool; + varInfoStruct.varInitValue = value; + varInfoStruct.varCurValue = value; + strncpy( (char *)varInfoStruct.varName, + (const char *)varNameStr, + strlen(varNameStr) + 1 ); + + // Set! + pmCPUret = pmCPUControl( PMIOCSETVARINFO, (void *)&varInfoStruct ); + + // pmCPU only assigns numerical id's when a new varName is specified + if ((0 == pmCPUret) + && (*varIndex == kCPUUnknownIndex)) + { + // pmCPUControl has assigned us a new variable ID. + // Let's re-read the structure we just SET to learn that ID. + pmCPUret = pmCPUControl( PMIOCGETVARNAMEINFO, (void *)&varInfoStruct ); + + if (0 == pmCPUret) + { + // Store it in idxPMCPUClamshell or idxPMCPULimitedPower + *varIndex = varInfoStruct.varID; + } + } + + return; + +#endif /* __i386__ || __x86_64__ */ +} + +// MARK: - +// MARK: Deep Sleep Policy + +#if HIBERNATION + +//****************************************************************************** +// evaluateSystemSleepPolicy +//****************************************************************************** + +#define kIOPlatformSystemSleepPolicyKey "IOPlatformSystemSleepPolicy" + +// Sleep flags +enum { + kIOPMSleepFlagHibernate = 0x00000001, + kIOPMSleepFlagSleepTimerEnable = 0x00000002 +}; + +struct IOPMSystemSleepPolicyEntry +{ + uint32_t factorMask; + uint32_t factorBits; + uint32_t sleepFlags; + uint32_t wakeEvents; +} __attribute__((packed)); + +struct IOPMSystemSleepPolicyTable +{ + uint32_t signature; + uint16_t version; + uint16_t entryCount; + IOPMSystemSleepPolicyEntry entries[]; +} __attribute__((packed)); + +enum { + kIOPMSleepAttributeHibernateSetup = 0x00000001, + kIOPMSleepAttributeHibernateSleep = 0x00000002 +}; + +static uint32_t +getSleepTypeAttributes( uint32_t sleepType ) +{ + static const uint32_t sleepTypeAttributes[ kIOPMSleepTypeLast ] = + { + /* invalid */ 0, + /* abort */ 0, + /* normal */ 0, + /* safesleep */ kIOPMSleepAttributeHibernateSetup, + /* hibernate */ kIOPMSleepAttributeHibernateSetup | kIOPMSleepAttributeHibernateSleep, + /* standby */ kIOPMSleepAttributeHibernateSetup | kIOPMSleepAttributeHibernateSleep, + /* poweroff */ kIOPMSleepAttributeHibernateSetup | kIOPMSleepAttributeHibernateSleep, + /* deepidle */ 0 + }; + + if (sleepType >= kIOPMSleepTypeLast) + return 0; + + return sleepTypeAttributes[sleepType]; +} + +bool IOPMrootDomain::evaluateSystemSleepPolicy( + IOPMSystemSleepParameters * params, int sleepPhase, uint32_t * hibMode ) +{ + const IOPMSystemSleepPolicyTable * pt; + OSObject * prop = 0; + OSData * policyData; + uint64_t currentFactors = 0; + uint32_t standbyDelay = 0; + uint32_t powerOffDelay = 0; + uint32_t powerOffTimer = 0; + uint32_t mismatch; + bool standbyEnabled; + bool powerOffEnabled; + bool found = false; + + // Get platform's sleep policy table + if (!gSleepPolicyHandler) + { + prop = getServiceRoot()->copyProperty(kIOPlatformSystemSleepPolicyKey); + if (!prop) goto done; + } + + // Fetch additional settings + standbyEnabled = (getSleepOption(kIOPMDeepSleepDelayKey, &standbyDelay) + && (getProperty(kIOPMDeepSleepEnabledKey) == kOSBooleanTrue)); + powerOffEnabled = (getSleepOption(kIOPMAutoPowerOffDelayKey, &powerOffDelay) + && (getProperty(kIOPMAutoPowerOffEnabledKey) == kOSBooleanTrue)); + if (!getSleepOption(kIOPMAutoPowerOffTimerKey, &powerOffTimer)) + powerOffTimer = powerOffDelay; + + DLOG("phase %d, standby %d delay %u, poweroff %d delay %u timer %u, hibernate 0x%x\n", + sleepPhase, standbyEnabled, standbyDelay, + powerOffEnabled, powerOffDelay, powerOffTimer, *hibMode); + + // pmset level overrides + if ((*hibMode & kIOHibernateModeOn) == 0) + { + if (!gSleepPolicyHandler) + { + standbyEnabled = false; + powerOffEnabled = false; + } + } + else if (!(*hibMode & kIOHibernateModeSleep)) + { + // Force hibernate (i.e. mode 25) + // If standby is enabled, force standy. + // If poweroff is enabled, force poweroff. + if (standbyEnabled) + currentFactors |= kIOPMSleepFactorStandbyForced; + else if (powerOffEnabled) + currentFactors |= kIOPMSleepFactorAutoPowerOffForced; + else + currentFactors |= kIOPMSleepFactorHibernateForced; + } + + // Current factors based on environment and assertions + if (sleepTimerMaintenance) + currentFactors |= kIOPMSleepFactorSleepTimerWake; + if (standbyEnabled && sleepToStandby && !gSleepPolicyHandler) + currentFactors |= kIOPMSleepFactorSleepTimerWake; + if (!clamshellClosed) + currentFactors |= kIOPMSleepFactorLidOpen; + if (acAdaptorConnected) + currentFactors |= kIOPMSleepFactorACPower; + if (lowBatteryCondition) + currentFactors |= kIOPMSleepFactorBatteryLow; + if (!standbyDelay) + currentFactors |= kIOPMSleepFactorStandbyNoDelay; + if (!standbyEnabled) + currentFactors |= kIOPMSleepFactorStandbyDisabled; + if (getPMAssertionLevel(kIOPMDriverAssertionUSBExternalDeviceBit) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorUSBExternalDevice; + if (getPMAssertionLevel(kIOPMDriverAssertionBluetoothHIDDevicePairedBit) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorBluetoothHIDDevice; + if (getPMAssertionLevel(kIOPMDriverAssertionExternalMediaMountedBit) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorExternalMediaMounted; + if (getPMAssertionLevel(kIOPMDriverAssertionReservedBit5) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorThunderboltDevice; + if (_scheduledAlarms != 0) + currentFactors |= kIOPMSleepFactorRTCAlarmScheduled; + if (getPMAssertionLevel(kIOPMDriverAssertionMagicPacketWakeEnabledBit) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorMagicPacketWakeEnabled; +#define TCPKEEPALIVE 1 +#if TCPKEEPALIVE + if (getPMAssertionLevel(kIOPMDriverAssertionNetworkKeepAliveActiveBit) != + kIOPMDriverAssertionLevelOff) + currentFactors |= kIOPMSleepFactorNetworkKeepAliveActive; +#endif + if (!powerOffEnabled) + currentFactors |= kIOPMSleepFactorAutoPowerOffDisabled; + if (desktopMode) + currentFactors |= kIOPMSleepFactorExternalDisplay; + if (userWasActive) + currentFactors |= kIOPMSleepFactorLocalUserActivity; + if (darkWakeHibernateError && !CAP_HIGHEST(kIOPMSystemCapabilityGraphics)) + currentFactors |= kIOPMSleepFactorHibernateFailed; + if (thermalWarningState) + currentFactors |= kIOPMSleepFactorThermalWarning; + + DLOG("sleep factors 0x%llx\n", currentFactors); + + if (gSleepPolicyHandler) + { + uint32_t savedHibernateMode; + IOReturn result; + + if (!gSleepPolicyVars) + { + gSleepPolicyVars = IONew(IOPMSystemSleepPolicyVariables, 1); + if (!gSleepPolicyVars) + goto done; + bzero(gSleepPolicyVars, sizeof(*gSleepPolicyVars)); + } + gSleepPolicyVars->signature = kIOPMSystemSleepPolicySignature; + gSleepPolicyVars->version = kIOPMSystemSleepPolicyVersion; + gSleepPolicyVars->currentCapability = _currentCapability; + gSleepPolicyVars->highestCapability = _highestCapability; + gSleepPolicyVars->sleepFactors = currentFactors; + gSleepPolicyVars->sleepReason = lastSleepReason; + gSleepPolicyVars->sleepPhase = sleepPhase; + gSleepPolicyVars->standbyDelay = standbyDelay; + gSleepPolicyVars->poweroffDelay = powerOffDelay; + gSleepPolicyVars->scheduledAlarms = _scheduledAlarms | _userScheduledAlarm; + gSleepPolicyVars->poweroffTimer = powerOffTimer; + + if (kIOPMSleepPhase0 == sleepPhase) + { + // preserve hibernateMode + savedHibernateMode = gSleepPolicyVars->hibernateMode; + gSleepPolicyVars->hibernateMode = *hibMode; + } + else if (kIOPMSleepPhase1 == sleepPhase) + { + // use original hibernateMode for phase2 + gSleepPolicyVars->hibernateMode = *hibMode; + } + + result = gSleepPolicyHandler(gSleepPolicyTarget, gSleepPolicyVars, params); + + if (kIOPMSleepPhase0 == sleepPhase) + { + // restore hibernateMode + gSleepPolicyVars->hibernateMode = savedHibernateMode; + } + + if ((result != kIOReturnSuccess) || + (kIOPMSleepTypeInvalid == params->sleepType) || + (params->sleepType >= kIOPMSleepTypeLast) || + (kIOPMSystemSleepParametersVersion != params->version)) + { + MSG("sleep policy handler error\n"); + goto done; + } + + if ((getSleepTypeAttributes(params->sleepType) & + kIOPMSleepAttributeHibernateSetup) && + ((*hibMode & kIOHibernateModeOn) == 0)) + { + *hibMode |= (kIOHibernateModeOn | kIOHibernateModeSleep); + } + + DLOG("sleep params v%u, type %u, flags 0x%x, wake 0x%x, timer %u, poweroff %u\n", + params->version, params->sleepType, params->sleepFlags, + params->ecWakeEvents, params->ecWakeTimer, params->ecPoweroffTimer); + found = true; + goto done; + } + + // Policy table is meaningless without standby enabled + if (!standbyEnabled) + goto done; + + // Validate the sleep policy table + policyData = OSDynamicCast(OSData, prop); + if (!policyData || (policyData->getLength() <= sizeof(IOPMSystemSleepPolicyTable))) + goto done; + + pt = (const IOPMSystemSleepPolicyTable *) policyData->getBytesNoCopy(); + if ((pt->signature != kIOPMSystemSleepPolicySignature) || + (pt->version != 1) || (0 == pt->entryCount)) + goto done; + + if (((policyData->getLength() - sizeof(IOPMSystemSleepPolicyTable)) != + (sizeof(IOPMSystemSleepPolicyEntry) * pt->entryCount))) + goto done; + + for (uint32_t i = 0; i < pt->entryCount; i++) + { + const IOPMSystemSleepPolicyEntry * entry = &pt->entries[i]; + mismatch = (((uint32_t)currentFactors ^ entry->factorBits) & entry->factorMask); + + DLOG("mask 0x%08x, bits 0x%08x, flags 0x%08x, wake 0x%08x, mismatch 0x%08x\n", + entry->factorMask, entry->factorBits, + entry->sleepFlags, entry->wakeEvents, mismatch); + if (mismatch) + continue; + + DLOG("^ found match\n"); + found = true; + + params->version = kIOPMSystemSleepParametersVersion; + params->reserved1 = 1; + if (entry->sleepFlags & kIOPMSleepFlagHibernate) + params->sleepType = kIOPMSleepTypeStandby; + else + params->sleepType = kIOPMSleepTypeNormalSleep; + + params->ecWakeEvents = entry->wakeEvents; + if (entry->sleepFlags & kIOPMSleepFlagSleepTimerEnable) + { + if (kIOPMSleepPhase2 == sleepPhase) + { + clock_sec_t now_secs = gIOLastSleepTime.tv_sec; + + if (!_standbyTimerResetSeconds || + (now_secs <= _standbyTimerResetSeconds)) + { + // Reset standby timer adjustment + _standbyTimerResetSeconds = now_secs; + DLOG("standby delay %u, reset %u\n", + standbyDelay, (uint32_t) _standbyTimerResetSeconds); + } + else if (standbyDelay) + { + // Shorten the standby delay timer + clock_sec_t elapsed = now_secs - _standbyTimerResetSeconds; + if (standbyDelay > elapsed) + standbyDelay -= elapsed; + else + standbyDelay = 1; // must be > 0 + + DLOG("standby delay %u, elapsed %u\n", + standbyDelay, (uint32_t) elapsed); + } + } + params->ecWakeTimer = standbyDelay; + } + else if (kIOPMSleepPhase2 == sleepPhase) + { + // A sleep that does not enable the sleep timer will reset + // the standby delay adjustment. + _standbyTimerResetSeconds = 0; + } + break; + } + +done: + if (prop) + prop->release(); + + return found; +} + +static IOPMSystemSleepParameters gEarlySystemSleepParams; + +void IOPMrootDomain::evaluateSystemSleepPolicyEarly( void ) +{ + // Evaluate early (priority interest phase), before drivers sleep. + + DLOG("%s\n", __FUNCTION__); + removeProperty(kIOPMSystemSleepParametersKey); + + // Full wake resets the standby timer delay adjustment + if (_highestCapability & kIOPMSystemCapabilityGraphics) + _standbyTimerResetSeconds = 0; + + hibernateDisabled = false; + hibernateMode = 0; + getSleepOption(kIOHibernateModeKey, &hibernateMode); + + // Save for late evaluation if sleep is aborted + bzero(&gEarlySystemSleepParams, sizeof(gEarlySystemSleepParams)); + + if (evaluateSystemSleepPolicy(&gEarlySystemSleepParams, kIOPMSleepPhase1, + &hibernateMode)) + { + if (!hibernateRetry && + ((getSleepTypeAttributes(gEarlySystemSleepParams.sleepType) & + kIOPMSleepAttributeHibernateSetup) == 0)) + { + // skip hibernate setup + hibernateDisabled = true; + } + } + + // Publish IOPMSystemSleepType + uint32_t sleepType = gEarlySystemSleepParams.sleepType; + if (sleepType == kIOPMSleepTypeInvalid) + { + // no sleep policy + sleepType = kIOPMSleepTypeNormalSleep; + if (hibernateMode & kIOHibernateModeOn) + sleepType = (hibernateMode & kIOHibernateModeSleep) ? + kIOPMSleepTypeSafeSleep : kIOPMSleepTypeHibernate; + } + else if ((sleepType == kIOPMSleepTypeStandby) && + (gEarlySystemSleepParams.ecPoweroffTimer)) + { + // report the lowest possible sleep state + sleepType = kIOPMSleepTypePowerOff; + } + + setProperty(kIOPMSystemSleepTypeKey, sleepType, 32); +} + +void IOPMrootDomain::evaluateSystemSleepPolicyFinal( void ) +{ + IOPMSystemSleepParameters params; + OSData * paramsData; + + // Evaluate sleep policy after sleeping drivers but before platform sleep. + + DLOG("%s\n", __FUNCTION__); + + bzero(¶ms, sizeof(params)); + if (evaluateSystemSleepPolicy(¶ms, kIOPMSleepPhase2, &hibernateMode)) + { + if ((hibernateDisabled || hibernateAborted) && + (getSleepTypeAttributes(params.sleepType) & + kIOPMSleepAttributeHibernateSetup)) + { + // Final evaluation picked a state requiring hibernation, + // but hibernate setup was skipped. Arm a short sleep using + // the early non-hibernate sleep parameters. + // Set hibernateRetry flag to force hibernate setup on the + // next sleep. + + bcopy(&gEarlySystemSleepParams, ¶ms, sizeof(params)); + params.sleepType = kIOPMSleepTypeAbortedSleep; + params.ecWakeTimer = 1; + hibernateRetry = true; + DLOG("wake in %u secs for hibernateDisabled %d, hibernateAborted %d\n", + params.ecWakeTimer, hibernateDisabled, hibernateAborted); + } + else + { + hibernateRetry = false; + } + + paramsData = OSData::withBytes(¶ms, sizeof(params)); + if (paramsData) + { + setProperty(kIOPMSystemSleepParametersKey, paramsData); + paramsData->release(); + } + + if (getSleepTypeAttributes(params.sleepType) & + kIOPMSleepAttributeHibernateSleep) + { + // Disable sleep to force hibernation + gIOHibernateMode &= ~kIOHibernateModeSleep; + } + } +} + +bool IOPMrootDomain::getHibernateSettings( + uint32_t * hibernateModePtr, + uint32_t * hibernateFreeRatio, + uint32_t * hibernateFreeTime ) +{ + // Called by IOHibernateSystemSleep() after evaluateSystemSleepPolicyEarly() + // has updated the hibernateDisabled flag. + + bool ok = getSleepOption(kIOHibernateModeKey, hibernateModePtr); + getSleepOption(kIOHibernateFreeRatioKey, hibernateFreeRatio); + getSleepOption(kIOHibernateFreeTimeKey, hibernateFreeTime); + if (hibernateDisabled) + *hibernateModePtr = 0; + else if (gSleepPolicyHandler) + *hibernateModePtr = hibernateMode; + DLOG("hibernateMode 0x%x\n", *hibernateModePtr); + return ok; +} + +bool IOPMrootDomain::getSleepOption( const char * key, uint32_t * option ) +{ + OSObject * optionsProp; + OSDictionary * optionsDict; + OSObject * obj = 0; + OSNumber * num; + bool ok = false; + + optionsProp = copyProperty(kRootDomainSleepOptionsKey); + optionsDict = OSDynamicCast(OSDictionary, optionsProp); + + if (optionsDict) + { + obj = optionsDict->getObject(key); + if (obj) obj->retain(); + } + if (!obj) + { + obj = copyProperty(key); + } + if (obj) + { + if ((num = OSDynamicCast(OSNumber, obj))) + { + *option = num->unsigned32BitValue(); + ok = true; + } + else if (OSDynamicCast(OSBoolean, obj)) + { + *option = (obj == kOSBooleanTrue) ? 1 : 0; + ok = true; + } + } + + if (obj) + obj->release(); + if (optionsProp) + optionsProp->release(); + + return true; +} +#endif /* HIBERNATION */ + +IOReturn IOPMrootDomain::getSystemSleepType( uint32_t * sleepType ) +{ +#if HIBERNATION + IOPMSystemSleepParameters params; + uint32_t hibMode = 0; + bool ok; + + if (gIOPMWorkLoop->inGate() == false) + { + IOReturn ret = gIOPMWorkLoop->runAction( + OSMemberFunctionCast(IOWorkLoop::Action, this, + &IOPMrootDomain::getSystemSleepType), + (OSObject *) this, + (void *) sleepType); + return ret; + } + + getSleepOption(kIOHibernateModeKey, &hibMode); + bzero(¶ms, sizeof(params)); + + ok = evaluateSystemSleepPolicy(¶ms, kIOPMSleepPhase0, &hibMode); + if (ok) + { + *sleepType = params.sleepType; + return kIOReturnSuccess; + } +#endif + + return kIOReturnUnsupported; +} + +// MARK: - +// MARK: Shutdown and Restart + +//****************************************************************************** +// handlePlatformHaltRestart +// +//****************************************************************************** + +struct HaltRestartApplierContext { + IOPMrootDomain * RootDomain; + unsigned long PowerState; + IOPMPowerFlags PowerFlags; + UInt32 MessageType; + UInt32 Counter; +}; + +static void +platformHaltRestartApplier( OSObject * object, void * context ) +{ + IOPowerStateChangeNotification notify; + HaltRestartApplierContext * ctx; + AbsoluteTime startTime; + UInt32 deltaTime; + + ctx = (HaltRestartApplierContext *) context; + + memset(¬ify, 0, sizeof(notify)); + notify.powerRef = (void *)(uintptr_t)ctx->Counter; + notify.returnValue = 0; + notify.stateNumber = ctx->PowerState; + notify.stateFlags = ctx->PowerFlags; + + clock_get_uptime(&startTime); + ctx->RootDomain->messageClient( ctx->MessageType, object, (void *)¬ify ); + deltaTime = computeDeltaTimeMS(&startTime); + + if ((deltaTime > kPMHaltTimeoutMS) || + (gIOKitDebug & kIOLogPMRootDomain)) + { + _IOServiceInterestNotifier * notifier; + notifier = OSDynamicCast(_IOServiceInterestNotifier, object); + + // IOService children of IOPMrootDomain are not instrumented. + // Only IORootParent currently falls under that group. + + if (notifier) + { + LOG("%s handler %p took %u ms\n", + (ctx->MessageType == kIOMessageSystemWillPowerOff) ? "PowerOff" : + (ctx->MessageType == kIOMessageSystemPagingOff) ? "PagingOff" : "Restart", + OBFUSCATE(notifier->handler), (uint32_t) deltaTime ); + } + } + + ctx->Counter++; +} + +void IOPMrootDomain::handlePlatformHaltRestart( UInt32 pe_type ) +{ + HaltRestartApplierContext ctx; + AbsoluteTime startTime; + UInt32 deltaTime; + + memset(&ctx, 0, sizeof(ctx)); + ctx.RootDomain = this; + + clock_get_uptime(&startTime); + switch (pe_type) + { + case kPEHaltCPU: + case kPEUPSDelayHaltCPU: + ctx.PowerState = OFF_STATE; + ctx.MessageType = kIOMessageSystemWillPowerOff; + break; + + case kPERestartCPU: + ctx.PowerState = RESTART_STATE; + ctx.MessageType = kIOMessageSystemWillRestart; + break; + + case kPEPagingOff: + ctx.PowerState = ON_STATE; + ctx.MessageType = kIOMessageSystemPagingOff; + IOService::updateConsoleUsers(NULL, kIOMessageSystemPagingOff); +#if HIBERNATION + IOHibernateSystemRestart(); +#endif + break; + + default: + return; + } + + // Notify legacy clients + applyToInterested(gIOPriorityPowerStateInterest, platformHaltRestartApplier, &ctx); + + // For normal shutdown, turn off File Server Mode. + if (kPEHaltCPU == pe_type) + { + const OSSymbol * setting = OSSymbol::withCString(kIOPMSettingRestartOnPowerLossKey); + OSNumber * num = OSNumber::withNumber((unsigned long long) 0, 32); + if (setting && num) + { + setPMSetting(setting, num); + setting->release(); + num->release(); + } + } + + if (kPEPagingOff != pe_type) + { + // Notify in power tree order + notifySystemShutdown(this, ctx.MessageType); + } + + IOCPURunPlatformHaltRestartActions(pe_type); + + deltaTime = computeDeltaTimeMS(&startTime); + LOG("%s all drivers took %u ms\n", + (ctx.MessageType == kIOMessageSystemWillPowerOff) ? "PowerOff" : + (ctx.MessageType == kIOMessageSystemPagingOff) ? "PagingOff" : "Restart", + (uint32_t) deltaTime ); +} + +//****************************************************************************** +// shutdownSystem +// +//****************************************************************************** + +IOReturn IOPMrootDomain::shutdownSystem( void ) +{ + return kIOReturnUnsupported; +} + +//****************************************************************************** +// restartSystem +// +//****************************************************************************** + +IOReturn IOPMrootDomain::restartSystem( void ) +{ + return kIOReturnUnsupported; +} + +// MARK: - +// MARK: System Capability + +//****************************************************************************** +// tagPowerPlaneService +// +// Running on PM work loop thread. +//****************************************************************************** + +void IOPMrootDomain::tagPowerPlaneService( + IOService * service, + IOPMActions * actions ) +{ + uint32_t flags = 0; + bool isDisplayWrangler; + + memset(actions, 0, sizeof(*actions)); + actions->target = this; + + if (service == this) + { + actions->actionPowerChangeStart = + OSMemberFunctionCast( + IOPMActionPowerChangeStart, this, + &IOPMrootDomain::handleOurPowerChangeStart); + + actions->actionPowerChangeDone = + OSMemberFunctionCast( + IOPMActionPowerChangeDone, this, + &IOPMrootDomain::handleOurPowerChangeDone); + + actions->actionPowerChangeOverride = + OSMemberFunctionCast( + IOPMActionPowerChangeOverride, this, + &IOPMrootDomain::overrideOurPowerChange); + return; + } + +#if !NO_KERNEL_HID + isDisplayWrangler = (0 != service->metaCast("IODisplayWrangler")); + if (isDisplayWrangler) + { + wrangler = service; + } +#else + isDisplayWrangler = false; +#endif + +#if defined(__i386__) || defined(__x86_64__) + if (isDisplayWrangler) + flags |= kPMActionsFlagIsDisplayWrangler; + if (service->getProperty("IOPMStrictTreeOrder")) + flags |= kPMActionsFlagIsGraphicsDevice; + if (service->getProperty("IOPMUnattendedWakePowerState")) + flags |= kPMActionsFlagIsAudioDevice; +#endif + + // Find the power connection object that is a child of the PCI host + // bridge, and has a graphics/audio device attached below. Mark the + // power branch for delayed child notifications. + + if (flags) + { + IORegistryEntry * child = service; + IORegistryEntry * parent = child->getParentEntry(gIOPowerPlane); + + while (child != this) + { + if ((parent == pciHostBridgeDriver) || + (parent == this)) + { + if (OSDynamicCast(IOPowerConnection, child)) + { + IOPowerConnection * conn = (IOPowerConnection *) child; + conn->delayChildNotification = true; + } + break; + } + child = parent; + parent = child->getParentEntry(gIOPowerPlane); + } + } + + if (flags) + { + DLOG("%s tag flags %x\n", service->getName(), flags); + actions->parameter |= flags; + actions->actionPowerChangeOverride = + OSMemberFunctionCast( + IOPMActionPowerChangeOverride, this, + &IOPMrootDomain::overridePowerChangeForUIService); + + if (flags & kPMActionsFlagIsDisplayWrangler) + { + actions->actionActivityTickle = + OSMemberFunctionCast( + IOPMActionActivityTickle, this, + &IOPMrootDomain::handleActivityTickleForDisplayWrangler); + + actions->actionUpdatePowerClient = + OSMemberFunctionCast( + IOPMActionUpdatePowerClient, this, + &IOPMrootDomain::handleUpdatePowerClientForDisplayWrangler); + } + return; + } + + // Locate the first PCI host bridge for PMTrace. + if (!pciHostBridgeDevice && service->metaCast("IOPCIBridge")) + { + IOService * provider = service->getProvider(); + if (OSDynamicCast(IOPlatformDevice, provider) && + provider->inPlane(gIODTPlane)) + { + pciHostBridgeDevice = provider; + pciHostBridgeDriver = service; + DLOG("PMTrace found PCI host bridge %s->%s\n", + provider->getName(), service->getName()); + } + } + + // Tag top-level PCI devices. The order of PMinit() call does not + // change across boots and is used as the PCI bit number. + if (pciHostBridgeDevice && service->metaCast("IOPCIDevice")) + { + // Would prefer to check built-in property, but tagPowerPlaneService() + // is called before pciDevice->registerService(). + IORegistryEntry * parent = service->getParentEntry(gIODTPlane); + if ((parent == pciHostBridgeDevice) && service->getProperty("acpi-device")) + { + int bit = pmTracer->recordTopLevelPCIDevice( service ); + if (bit >= 0) + { + // Save the assigned bit for fast lookup. + actions->parameter |= (bit & kPMActionsPCIBitNumberMask); + + actions->actionPowerChangeStart = + OSMemberFunctionCast( + IOPMActionPowerChangeStart, this, + &IOPMrootDomain::handlePowerChangeStartForPCIDevice); + + actions->actionPowerChangeDone = + OSMemberFunctionCast( + IOPMActionPowerChangeDone, this, + &IOPMrootDomain::handlePowerChangeDoneForPCIDevice); + } + } + } +} + +//****************************************************************************** +// PM actions for root domain +//****************************************************************************** + +void IOPMrootDomain::overrideOurPowerChange( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex * inOutPowerState, + IOPMPowerChangeFlags * inOutChangeFlags, + IOPMRequestTag requestTag ) +{ + uint32_t powerState = (uint32_t) *inOutPowerState; + uint32_t changeFlags = *inOutChangeFlags; + uint32_t currentPowerState = (uint32_t) getPowerState(); + + if (changeFlags & kIOPMParentInitiated) + { + // Root parent is permanently pegged at max power, + // a parent initiated power change is unexpected. + *inOutChangeFlags |= kIOPMNotDone; + return; + } + + if (powerState < currentPowerState) + { + if (CAP_CURRENT(kIOPMSystemCapabilityGraphics)) + { + // Root domain is dropping power state ON->SLEEP. + // If system is in full wake, first enter dark wake by + // converting the power drop to a capability change. + // Once in dark wake, transition to sleep state ASAP. + + darkWakeToSleepASAP = true; + + // Drop graphics and audio capability + _desiredCapability &= ~( + kIOPMSystemCapabilityGraphics | + kIOPMSystemCapabilityAudio ); + + // Convert to capability change (ON->ON) + *inOutPowerState = ON_STATE; + *inOutChangeFlags |= kIOPMSynchronize; + + // Revert device desire from SLEEP to ON + changePowerStateToPriv(ON_STATE); + } + else + { + // System is in dark wake, ok to drop power state. + // Broadcast root powering down to entire tree. + *inOutChangeFlags |= kIOPMRootChangeDown; + } + } + else if (powerState > currentPowerState) + { + if ((_currentCapability & kIOPMSystemCapabilityCPU) == 0) + { + // Broadcast power up when waking from sleep, but not for the + // initial power change at boot by checking for cpu capability. + *inOutChangeFlags |= kIOPMRootChangeUp; + } + } +} + +void IOPMrootDomain::handleOurPowerChangeStart( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex powerState, + IOPMPowerChangeFlags * inOutChangeFlags, + IOPMRequestTag requestTag ) +{ + uint32_t changeFlags = *inOutChangeFlags; + uint32_t currentPowerState = (uint32_t) getPowerState(); + uint32_t sleepReason = requestTag ? requestTag : kIOPMSleepReasonIdle; + bool publishSleepReason = false; + + _systemTransitionType = kSystemTransitionNone; + _systemMessageClientMask = 0; + capabilityLoss = false; + + if (lowBatteryCondition) + { + // Low battery notification may arrive after the initial sleep request + // has been queued. Override the sleep reason so powerd and others can + // treat this as an emergency sleep. + sleepReason = kIOPMSleepReasonLowPower; + } + + // 1. Explicit capability change. + + if (changeFlags & kIOPMSynchronize) + { + if (powerState == ON_STATE) + { + if (changeFlags & kIOPMSyncNoChildNotify) + _systemTransitionType = kSystemTransitionNewCapClient; + else + _systemTransitionType = kSystemTransitionCapability; + } + } + + // 2. Going to sleep (cancellation still possible). + + else if (powerState < currentPowerState) + _systemTransitionType = kSystemTransitionSleep; + + // 3. Woke from (idle or demand) sleep. + + else if (!systemBooting && + (changeFlags & kIOPMSelfInitiated) && + (powerState > currentPowerState)) + { + _systemTransitionType = kSystemTransitionWake; + _desiredCapability = kIOPMSystemCapabilityCPU | + kIOPMSystemCapabilityNetwork; + + // Early exit from dark wake to full (e.g. LID open) + if (kFullWakeReasonNone != fullWakeReason) + { + _desiredCapability |= ( + kIOPMSystemCapabilityGraphics | + kIOPMSystemCapabilityAudio ); + } +#if HIBERNATION + IOHibernateSetWakeCapabilities(_desiredCapability); +#endif + } + + // Update pending wake capability at the beginning of every + // state transition (including synchronize). This will become + // the current capability at the end of the transition. + + if (kSystemTransitionSleep == _systemTransitionType) + { + _pendingCapability = 0; + capabilityLoss = true; + + // Clear previous stats + IOLockLock(pmStatsLock); + if (pmStatsAppResponses) + { + pmStatsAppResponses->release(); + pmStatsAppResponses = OSArray::withCapacity(5); + } + IOLockUnlock(pmStatsLock); + + } + else if (kSystemTransitionNewCapClient != _systemTransitionType) + { + _pendingCapability = _desiredCapability | + kIOPMSystemCapabilityCPU | + kIOPMSystemCapabilityNetwork; + + if (_pendingCapability & kIOPMSystemCapabilityGraphics) + _pendingCapability |= kIOPMSystemCapabilityAudio; + + if ((kSystemTransitionCapability == _systemTransitionType) && + (_pendingCapability == _currentCapability)) + { + // Cancel the PM state change. + _systemTransitionType = kSystemTransitionNone; + *inOutChangeFlags |= kIOPMNotDone; + } + if (__builtin_popcount(_pendingCapability) < + __builtin_popcount(_currentCapability)) + capabilityLoss = true; + } + + // 1. Capability change. + + if (kSystemTransitionCapability == _systemTransitionType) + { + // Dark to Full transition. + if (CAP_GAIN(kIOPMSystemCapabilityGraphics)) + { + tracePoint( kIOPMTracePointDarkWakeExit ); + + willEnterFullWake(); + } + + // Full to Dark transition. + if (CAP_LOSS(kIOPMSystemCapabilityGraphics)) + { + tracePoint( kIOPMTracePointDarkWakeEntry ); + *inOutChangeFlags |= kIOPMSyncTellPowerDown; + _systemMessageClientMask = kSystemMessageClientPowerd | + kSystemMessageClientLegacyApp; + + + // rdar://15971327 + // Prevent user active transitions before notifying clients + // that system will sleep. + preventTransitionToUserActive(true); + + IOService::setAdvisoryTickleEnable( false ); + + // Publish the sleep reason for full to dark wake + publishSleepReason = true; + lastSleepReason = fullToDarkReason = sleepReason; + + // Publish a UUID for the Sleep --> Wake cycle + handlePublishSleepWakeUUID(true); + } + } + + // 2. System sleep. + + else if (kSystemTransitionSleep == _systemTransitionType) + { + // Beginning of a system sleep transition. + // Cancellation is still possible. + tracePoint( kIOPMTracePointSleepStarted, sleepReason ); + + _systemMessageClientMask = kSystemMessageClientAll; + if ((_currentCapability & kIOPMSystemCapabilityGraphics) == 0) + _systemMessageClientMask &= ~kSystemMessageClientLegacyApp; + if ((_highestCapability & kIOPMSystemCapabilityGraphics) == 0) + _systemMessageClientMask &= ~kSystemMessageClientKernel; + + // Record the reason for dark wake back to sleep + // System may not have ever achieved full wake + + publishSleepReason = true; + lastSleepReason = sleepReason; + } + + // 3. System wake. + + else if (kSystemTransitionWake == _systemTransitionType) + { + tracePoint( kIOPMTracePointWakeWillPowerOnClients ); + // Clear stats about sleep + + if (_pendingCapability & kIOPMSystemCapabilityGraphics) + { + willEnterFullWake(); + } + else + { + // Message powerd only + _systemMessageClientMask = kSystemMessageClientPowerd; + tellClients(kIOMessageSystemWillPowerOn); + } + } + + // The only location where the sleep reason is published. At this point + // sleep can still be cancelled, but sleep reason should be published + // early for logging purposes. + + if (publishSleepReason) + { + static const char * IOPMSleepReasons[] = + { + kIOPMClamshellSleepKey, + kIOPMPowerButtonSleepKey, + kIOPMSoftwareSleepKey, + kIOPMOSSwitchHibernationKey, + kIOPMIdleSleepKey, + kIOPMLowPowerSleepKey, + kIOPMThermalEmergencySleepKey, + kIOPMMaintenanceSleepKey, + kIOPMSleepServiceExitKey, + kIOPMDarkWakeThermalEmergencyKey + }; + + // Record sleep cause in IORegistry + uint32_t reasonIndex = sleepReason - kIOPMSleepReasonClamshell; + if (reasonIndex < sizeof(IOPMSleepReasons)/sizeof(IOPMSleepReasons[0])) { + DLOG("sleep reason %s\n", IOPMSleepReasons[reasonIndex]); + setProperty(kRootDomainSleepReasonKey, IOPMSleepReasons[reasonIndex]); + } + } + + if ((kSystemTransitionNone != _systemTransitionType) && + (kSystemTransitionNewCapClient != _systemTransitionType)) + { + _systemStateGeneration++; + systemDarkWake = false; + + DLOG("=== START (%u->%u, 0x%x) type %u, gen %u, msg %x, " + "dcp %x:%x:%x\n", + currentPowerState, (uint32_t) powerState, *inOutChangeFlags, + _systemTransitionType, _systemStateGeneration, + _systemMessageClientMask, + _desiredCapability, _currentCapability, _pendingCapability); + } +} + +void IOPMrootDomain::handleOurPowerChangeDone( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex powerState, + IOPMPowerChangeFlags changeFlags, + IOPMRequestTag requestTag __unused ) +{ + if (kSystemTransitionNewCapClient == _systemTransitionType) + { + _systemTransitionType = kSystemTransitionNone; + return; + } + + if (_systemTransitionType != kSystemTransitionNone) + { + uint32_t currentPowerState = (uint32_t) getPowerState(); + + if (changeFlags & kIOPMNotDone) + { + // Power down was cancelled or vetoed. + _pendingCapability = _currentCapability; + lastSleepReason = 0; + + if (!CAP_CURRENT(kIOPMSystemCapabilityGraphics) && + CAP_CURRENT(kIOPMSystemCapabilityCPU)) + { + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusDarkWakeReentry, + _systemStateGeneration ); + } + + // Revert device desire to max. + changePowerStateToPriv(ON_STATE); + } + else + { + // Send message on dark wake to full wake promotion. + // tellChangeUp() handles the normal SLEEP->ON case. + + if (kSystemTransitionCapability == _systemTransitionType) + { + if (CAP_GAIN(kIOPMSystemCapabilityGraphics)) + { + lastSleepReason = 0; // stop logging wrangler tickles + tellClients(kIOMessageSystemHasPoweredOn); + } + if (CAP_LOSS(kIOPMSystemCapabilityGraphics)) + { + // Going dark, reset full wake state + // userIsActive will be cleared by wrangler powering down + wranglerTickled = false; + fullWakeReason = kFullWakeReasonNone; + } + } + + // Reset state after exiting from dark wake. + + if (CAP_GAIN(kIOPMSystemCapabilityGraphics) || + CAP_LOSS(kIOPMSystemCapabilityCPU)) + { + darkWakeMaintenance = false; + darkWakeToSleepASAP = false; + pciCantSleepValid = false; + darkWakeSleepService = false; + + if (CAP_LOSS(kIOPMSystemCapabilityCPU)) + { + // Remove the influence of display power assertion + // before next system wake. + if (wrangler) wrangler->changePowerStateForRootDomain( + kWranglerPowerStateMin ); + removeProperty(gIOPMUserTriggeredFullWakeKey); + } + } + + // Entered dark mode. + + if (((_pendingCapability & kIOPMSystemCapabilityGraphics) == 0) && + (_pendingCapability & kIOPMSystemCapabilityCPU)) + { + // Queue an evaluation of whether to remain in dark wake, + // and for how long. This serves the purpose of draining + // any assertions from the queue. + + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusDarkWakeEntry, + _systemStateGeneration ); + } + } + + DLOG("=== FINISH (%u->%u, 0x%x) type %u, gen %u, msg %x, " + "dcp %x:%x:%x, dbgtimer %u\n", + currentPowerState, (uint32_t) powerState, changeFlags, + _systemTransitionType, _systemStateGeneration, + _systemMessageClientMask, + _desiredCapability, _currentCapability, _pendingCapability, + _lastDebugWakeSeconds); + + if (_pendingCapability & kIOPMSystemCapabilityGraphics) + { + displayWakeCnt++; +#if DARK_TO_FULL_EVALUATE_CLAMSHELL + if (clamshellExists && fullWakeThreadCall && + CAP_HIGHEST(kIOPMSystemCapabilityGraphics)) + { + // Not the initial graphics full power, graphics won't + // send a power notification to trigger a lid state + // evaluation. + + AbsoluteTime deadline; + clock_interval_to_deadline(45, kSecondScale, &deadline); + thread_call_enter_delayed(fullWakeThreadCall, deadline); + } +#endif + } + else if (CAP_GAIN(kIOPMSystemCapabilityCPU)) + darkWakeCnt++; + + // Update current system capability. + if (_currentCapability != _pendingCapability) + _currentCapability = _pendingCapability; + + // Update highest system capability. + + _highestCapability |= _currentCapability; + + if (darkWakePostTickle && + (kSystemTransitionWake == _systemTransitionType) && + (gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) == + kDarkWakeFlagHIDTickleLate) + { + darkWakePostTickle = false; + reportUserInput(); + } + + // Reset tracepoint at completion of capability change, + // completion of wake transition, and aborted sleep transition. + + if ((_systemTransitionType == kSystemTransitionCapability) || + (_systemTransitionType == kSystemTransitionWake) || + ((_systemTransitionType == kSystemTransitionSleep) && + (changeFlags & kIOPMNotDone))) + { + setProperty(kIOPMSystemCapabilitiesKey, _currentCapability, 64); + tracePoint( kIOPMTracePointSystemUp, 0 ); + } + + _systemTransitionType = kSystemTransitionNone; + _systemMessageClientMask = 0; + + logGraphicsClamp = false; + } +} + +//****************************************************************************** +// PM actions for graphics and audio. +//****************************************************************************** + +void IOPMrootDomain::overridePowerChangeForUIService( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex * inOutPowerState, + IOPMPowerChangeFlags * inOutChangeFlags ) +{ + uint32_t powerState = (uint32_t) *inOutPowerState; + uint32_t changeFlags = (uint32_t) *inOutChangeFlags; + + if (kSystemTransitionNone == _systemTransitionType) + { + // Not in midst of a system transition. + // Do not modify power limit enable state. + } + else if ((actions->parameter & kPMActionsFlagLimitPower) == 0) + { + // Activate power limiter. + + if ((actions->parameter & kPMActionsFlagIsDisplayWrangler) && + ((_pendingCapability & kIOPMSystemCapabilityGraphics) == 0) && + (changeFlags & kIOPMSynchronize)) + { + actions->parameter |= kPMActionsFlagLimitPower; + } + else if ((actions->parameter & kPMActionsFlagIsAudioDevice) && + ((gDarkWakeFlags & kDarkWakeFlagAudioNotSuppressed) == 0) && + ((_pendingCapability & kIOPMSystemCapabilityAudio) == 0) && + (changeFlags & kIOPMSynchronize)) + { + actions->parameter |= kPMActionsFlagLimitPower; + } + else if ((actions->parameter & kPMActionsFlagIsGraphicsDevice) && + (_systemTransitionType == kSystemTransitionSleep)) + { + // For graphics devices, arm the limiter when entering + // system sleep. Not when dropping to dark wake. + actions->parameter |= kPMActionsFlagLimitPower; + } + + if (actions->parameter & kPMActionsFlagLimitPower) + { + DLOG("+ plimit %s %p\n", + service->getName(), OBFUSCATE(service)); + } + } + else + { + // Remove power limit. + + if ((actions->parameter & ( + kPMActionsFlagIsDisplayWrangler | + kPMActionsFlagIsGraphicsDevice )) && + (_pendingCapability & kIOPMSystemCapabilityGraphics)) + { + actions->parameter &= ~kPMActionsFlagLimitPower; + } + else if ((actions->parameter & kPMActionsFlagIsAudioDevice) && + (_pendingCapability & kIOPMSystemCapabilityAudio)) + { + actions->parameter &= ~kPMActionsFlagLimitPower; + } + + if ((actions->parameter & kPMActionsFlagLimitPower) == 0) + { + DLOG("- plimit %s %p\n", + service->getName(), OBFUSCATE(service)); + } + } + + if (actions->parameter & kPMActionsFlagLimitPower) + { + uint32_t maxPowerState = (uint32_t)(-1); + + if (changeFlags & (kIOPMDomainDidChange | kIOPMDomainWillChange)) + { + // Enforce limit for system power/cap transitions. + + maxPowerState = 0; + if ((service->getPowerState() > maxPowerState) && + (actions->parameter & kPMActionsFlagIsDisplayWrangler)) + { + maxPowerState++; + + // Remove lingering effects of any tickle before entering + // dark wake. It will take a new tickle to return to full + // wake, so the existing tickle state is useless. + + if (changeFlags & kIOPMDomainDidChange) + *inOutChangeFlags |= kIOPMExpireIdleTimer; + } + else if (actions->parameter & kPMActionsFlagIsGraphicsDevice) + { + maxPowerState++; + } + } + else + { + // Deny all self-initiated changes when power is limited. + // Wrangler tickle should never defeat the limiter. + + maxPowerState = service->getPowerState(); + } + + if (powerState > maxPowerState) + { + DLOG("> plimit %s %p (%u->%u, 0x%x)\n", + service->getName(), OBFUSCATE(service), powerState, maxPowerState, + changeFlags); + *inOutPowerState = maxPowerState; + + if (darkWakePostTickle && + (actions->parameter & kPMActionsFlagIsDisplayWrangler) && + (changeFlags & kIOPMDomainWillChange) && + ((gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) == + kDarkWakeFlagHIDTickleEarly)) + { + darkWakePostTickle = false; + reportUserInput(); + } + } + + if (!graphicsSuppressed && (changeFlags & kIOPMDomainDidChange)) + { + if (logGraphicsClamp) + { + AbsoluteTime now; + uint64_t nsec; + + clock_get_uptime(&now); + SUB_ABSOLUTETIME(&now, &systemWakeTime); + absolutetime_to_nanoseconds(now, &nsec); + if (kIOLogPMRootDomain & gIOKitDebug) + MSG("Graphics suppressed %u ms\n", + ((int)((nsec) / 1000000ULL))); + } + graphicsSuppressed = true; + } + } +} + +void IOPMrootDomain::handleActivityTickleForDisplayWrangler( + IOService * service, + IOPMActions * actions ) +{ +#if !NO_KERNEL_HID + // Warning: Not running in PM work loop context - don't modify state !!! + // Trap tickle directed to IODisplayWrangler while running with graphics + // capability suppressed. + + assert(service == wrangler); + + clock_get_uptime(&userActivityTime); + bool aborting = ((lastSleepReason == kIOPMSleepReasonIdle) + || (lastSleepReason == kIOPMSleepReasonMaintenance)); + if (aborting) { + userActivityCount++; + DLOG("display wrangler tickled1 %d lastSleepReason %d\n", + userActivityCount, lastSleepReason); + } + + if (!wranglerTickled && + ((_pendingCapability & kIOPMSystemCapabilityGraphics) == 0)) + { + DLOG("display wrangler tickled\n"); + if (kIOLogPMRootDomain & gIOKitDebug) + OSReportWithBacktrace("Dark wake display tickle"); + if (pmPowerStateQueue) + { + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusDarkWakeActivityTickle, + true /* set wake type */ ); + } + } +#endif +} + +void IOPMrootDomain::handleUpdatePowerClientForDisplayWrangler( + IOService * service, + IOPMActions * actions, + const OSSymbol * powerClient, + IOPMPowerStateIndex oldPowerState, + IOPMPowerStateIndex newPowerState ) +{ +#if !NO_KERNEL_HID + assert(service == wrangler); + + // This function implements half of the user active detection + // by monitoring changes to the display wrangler's device desire. + // + // User becomes active when either: + // 1. Wrangler's DeviceDesire increases to max, but wrangler is already + // in max power state. This desire change in absence of a power state + // change is detected within. This handles the case when user becomes + // active while the display is already lit by setDisplayPowerOn(). + // + // 2. Power state change to max, and DeviceDesire is also at max. + // Handled by displayWranglerNotification(). + // + // User becomes inactive when DeviceDesire drops to sleep state or below. + + DLOG("wrangler %s (ps %u, %u->%u)\n", + powerClient->getCStringNoCopy(), + (uint32_t) service->getPowerState(), + (uint32_t) oldPowerState, (uint32_t) newPowerState); + + if (powerClient == gIOPMPowerClientDevice) + { + if ((newPowerState > oldPowerState) && + (newPowerState == kWranglerPowerStateMax) && + (service->getPowerState() == kWranglerPowerStateMax)) + { + evaluatePolicy( kStimulusEnterUserActiveState ); + } + else + if ((newPowerState < oldPowerState) && + (newPowerState <= kWranglerPowerStateSleep)) + { + evaluatePolicy( kStimulusLeaveUserActiveState ); + } + } +#endif +} + +//****************************************************************************** +// User active state management +//****************************************************************************** + +void IOPMrootDomain::preventTransitionToUserActive( bool prevent ) +{ +#if !NO_KERNEL_HID + _preventUserActive = prevent; + if (wrangler && !_preventUserActive) + { + // Allowing transition to user active, but the wrangler may have + // already powered ON in case of sleep cancel/revert. Poll the + // same conditions checked for in displayWranglerNotification() + // to bring the user active state up to date. + + if ((wrangler->getPowerState() == kWranglerPowerStateMax) && + (wrangler->getPowerStateForClient(gIOPMPowerClientDevice) == + kWranglerPowerStateMax)) + { + evaluatePolicy( kStimulusEnterUserActiveState ); + } + } +#endif +} + +//****************************************************************************** +// Approve usage of delayed child notification by PM. +//****************************************************************************** + +bool IOPMrootDomain::shouldDelayChildNotification( + IOService * service ) +{ + if (((gDarkWakeFlags & kDarkWakeFlagHIDTickleMask) != 0) && + (kFullWakeReasonNone == fullWakeReason) && + (kSystemTransitionWake == _systemTransitionType)) + { + DLOG("%s: delay child notify\n", service->getName()); + return true; + } + return false; +} + +//****************************************************************************** +// PM actions for PCI device. +//****************************************************************************** + +void IOPMrootDomain::handlePowerChangeStartForPCIDevice( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex powerState, + IOPMPowerChangeFlags * inOutChangeFlags ) +{ + pmTracer->tracePCIPowerChange( + PMTraceWorker::kPowerChangeStart, + service, *inOutChangeFlags, + (actions->parameter & kPMActionsPCIBitNumberMask)); +} + +void IOPMrootDomain::handlePowerChangeDoneForPCIDevice( + IOService * service, + IOPMActions * actions, + IOPMPowerStateIndex powerState, + IOPMPowerChangeFlags changeFlags ) +{ + pmTracer->tracePCIPowerChange( + PMTraceWorker::kPowerChangeCompleted, + service, changeFlags, + (actions->parameter & kPMActionsPCIBitNumberMask)); +} + +//****************************************************************************** +// registerInterest +// +// Override IOService::registerInterest() to intercept special clients. +//****************************************************************************** + +class IOPMServiceInterestNotifier: public _IOServiceInterestNotifier +{ + + friend class IOPMrootDomain; + OSDeclareDefaultStructors(IOPMServiceInterestNotifier) + +protected: + uint32_t ackTimeoutCnt; + +}; + +OSDefineMetaClassAndStructors(IOPMServiceInterestNotifier, _IOServiceInterestNotifier) + +IONotifier * IOPMrootDomain::registerInterest( + const OSSymbol * typeOfInterest, + IOServiceInterestHandler handler, + void * target, void * ref ) +{ + IOPMServiceInterestNotifier *notifier = 0; + bool isSystemCapabilityClient; + bool isKernelCapabilityClient; + IOReturn rc = kIOReturnError;; + + isSystemCapabilityClient = + typeOfInterest && + typeOfInterest->isEqualTo(kIOPMSystemCapabilityInterest); + + isKernelCapabilityClient = + typeOfInterest && + typeOfInterest->isEqualTo(gIOPriorityPowerStateInterest); + + if (isSystemCapabilityClient) + typeOfInterest = gIOAppPowerStateInterest; + + notifier = new IOPMServiceInterestNotifier; + if (!notifier) return NULL; + + if (notifier->init()) { + rc = super::registerInterestForNotifer(notifier, typeOfInterest, handler, target, ref); + } + if (rc != kIOReturnSuccess) { + notifier->release(); + notifier = 0; + } + if (pmPowerStateQueue) + { + notifier->ackTimeoutCnt = 0; + if (isSystemCapabilityClient) + { + notifier->retain(); + if (pmPowerStateQueue->submitPowerEvent( + kPowerEventRegisterSystemCapabilityClient, notifier) == false) + notifier->release(); + } + + if (isKernelCapabilityClient) + { + notifier->retain(); + if (pmPowerStateQueue->submitPowerEvent( + kPowerEventRegisterKernelCapabilityClient, notifier) == false) + notifier->release(); + } + } + + return notifier; +} + +//****************************************************************************** +// systemMessageFilter +// +//****************************************************************************** + +bool IOPMrootDomain::systemMessageFilter( + void * object, void * arg1, void * arg2, void * arg3 ) +{ + const IOPMInterestContext * context = (const IOPMInterestContext *) arg1; + bool isCapMsg = (context->messageType == kIOMessageSystemCapabilityChange); + bool isCapClient = false; + bool allow = false; + + do { + if ((kSystemTransitionNewCapClient == _systemTransitionType) && + (!isCapMsg || !_joinedCapabilityClients || + !_joinedCapabilityClients->containsObject((OSObject *) object))) + break; + + // Capability change message for app and kernel clients. + + if (isCapMsg) + { + if ((context->notifyType == kNotifyPriority) || + (context->notifyType == kNotifyCapabilityChangePriority)) + isCapClient = true; + + if ((context->notifyType == kNotifyCapabilityChangeApps) && + (object == (void *) systemCapabilityNotifier)) + isCapClient = true; + } + + if (isCapClient) + { + IOPMSystemCapabilityChangeParameters * capArgs = + (IOPMSystemCapabilityChangeParameters *) arg2; + + if (kSystemTransitionNewCapClient == _systemTransitionType) + { + capArgs->fromCapabilities = 0; + capArgs->toCapabilities = _currentCapability; + capArgs->changeFlags = 0; + } + else + { + capArgs->fromCapabilities = _currentCapability; + capArgs->toCapabilities = _pendingCapability; + + if (context->isPreChange) + capArgs->changeFlags = kIOPMSystemCapabilityWillChange; + else + capArgs->changeFlags = kIOPMSystemCapabilityDidChange; + } + + // Capability change messages only go to the PM configd plugin. + // Wait for response post-change if capabilitiy is increasing. + // Wait for response pre-change if capability is decreasing. + + if ((context->notifyType == kNotifyCapabilityChangeApps) && arg3 && + ( (capabilityLoss && context->isPreChange) || + (!capabilityLoss && !context->isPreChange) ) ) + { + // app has not replied yet, wait for it + *((OSObject **) arg3) = kOSBooleanFalse; + } + + allow = true; + break; + } + + // Capability client will always see kIOMessageCanSystemSleep, + // even for demand sleep. It will also have a chance to veto + // sleep one last time after all clients have responded to + // kIOMessageSystemWillSleep + + if ((kIOMessageCanSystemSleep == context->messageType) || + (kIOMessageSystemWillNotSleep == context->messageType)) + { + if (object == (OSObject *) systemCapabilityNotifier) + { + allow = true; + break; + } + + // Not idle sleep, don't ask apps. + if (context->changeFlags & kIOPMSkipAskPowerDown) + { + break; + } + } + + if (kIOPMMessageLastCallBeforeSleep == context->messageType) + { + if ((object == (OSObject *) systemCapabilityNotifier) && + CAP_HIGHEST(kIOPMSystemCapabilityGraphics) && + (fullToDarkReason == kIOPMSleepReasonIdle)) + allow = true; + break; + } + + // Reject capability change messages for legacy clients. + // Reject legacy system sleep messages for capability client. + + if (isCapMsg || (object == (OSObject *) systemCapabilityNotifier)) + { + break; + } + + // Filter system sleep messages. + + if ((context->notifyType == kNotifyApps) && + (_systemMessageClientMask & kSystemMessageClientLegacyApp)) + { + IOPMServiceInterestNotifier *notify; + allow = true; + + if ((notify = OSDynamicCast(IOPMServiceInterestNotifier, (OSObject *)object)) + && arg3) { + + if (notify->ackTimeoutCnt >= 3) + *((OSObject **) arg3) = kOSBooleanFalse; + else + *((OSObject **) arg3) = kOSBooleanTrue; + } + } + else if ((context->notifyType == kNotifyPriority) && + (_systemMessageClientMask & kSystemMessageClientKernel)) + { + allow = true; + } + } + while (false); + + if (allow && isCapMsg && _joinedCapabilityClients) + { + _joinedCapabilityClients->removeObject((OSObject *) object); + if (_joinedCapabilityClients->getCount() == 0) + { + DLOG("destroyed capability client set %p\n", + OBFUSCATE(_joinedCapabilityClients)); + _joinedCapabilityClients->release(); + _joinedCapabilityClients = 0; + } + } + + return allow; +} + +//****************************************************************************** +// setMaintenanceWakeCalendar +// +//****************************************************************************** + +IOReturn IOPMrootDomain::setMaintenanceWakeCalendar( + const IOPMCalendarStruct * calendar ) +{ + OSData * data; + IOReturn ret = 0; + + if (!calendar) + return kIOReturnBadArgument; + + data = OSData::withBytesNoCopy((void *) calendar, sizeof(*calendar)); + if (!data) + return kIOReturnNoMemory; + + if (kPMCalendarTypeMaintenance == calendar->selector) { + ret = setPMSetting(gIOPMSettingMaintenanceWakeCalendarKey, data); + if (kIOReturnSuccess == ret) + OSBitOrAtomic(kIOPMAlarmBitMaintenanceWake, &_scheduledAlarms); + } else + if (kPMCalendarTypeSleepService == calendar->selector) + { + ret = setPMSetting(gIOPMSettingSleepServiceWakeCalendarKey, data); + if (kIOReturnSuccess == ret) + OSBitOrAtomic(kIOPMAlarmBitSleepServiceWake, &_scheduledAlarms); + } + DLOG("_scheduledAlarms = 0x%x\n", (uint32_t) _scheduledAlarms); + + data->release(); + return ret; +} + +// MARK: - +// MARK: Display Wrangler + +//****************************************************************************** +// displayWranglerNotification +// +// Handle the notification when the IODisplayWrangler changes power state. +//****************************************************************************** + +IOReturn IOPMrootDomain::displayWranglerNotification( + void * target, void * refCon, + UInt32 messageType, IOService * service, + void * messageArgument, vm_size_t argSize ) +{ +#if !NO_KERNEL_HID + int displayPowerState; + IOPowerStateChangeNotification * params = + (IOPowerStateChangeNotification *) messageArgument; + + if ((messageType != kIOMessageDeviceWillPowerOff) && + (messageType != kIOMessageDeviceHasPoweredOn)) + return kIOReturnUnsupported; + + ASSERT_GATED(); + if (!gRootDomain) + return kIOReturnUnsupported; + + displayPowerState = params->stateNumber; + DLOG("wrangler %s ps %d\n", + getIOMessageString(messageType), displayPowerState); + + switch (messageType) { + case kIOMessageDeviceWillPowerOff: + // Display wrangler has dropped power due to display idle + // or force system sleep. + // + // 4 Display ON kWranglerPowerStateMax + // 3 Display Dim kWranglerPowerStateDim + // 2 Display Sleep kWranglerPowerStateSleep + // 1 Not visible to user + // 0 Not visible to user kWranglerPowerStateMin + + if (displayPowerState <= kWranglerPowerStateSleep) + gRootDomain->evaluatePolicy( kStimulusDisplayWranglerSleep ); + break; + + case kIOMessageDeviceHasPoweredOn: + // Display wrangler has powered on due to user activity + // or wake from sleep. + + if (kWranglerPowerStateMax == displayPowerState) + { + gRootDomain->evaluatePolicy( kStimulusDisplayWranglerWake ); + + // See comment in handleUpdatePowerClientForDisplayWrangler + if (service->getPowerStateForClient(gIOPMPowerClientDevice) == + kWranglerPowerStateMax) + { + gRootDomain->evaluatePolicy( kStimulusEnterUserActiveState ); + } + } + break; + } +#endif + return kIOReturnUnsupported; +} + +//****************************************************************************** +// displayWranglerMatchPublished +// +// Receives a notification when the IODisplayWrangler is published. +// When it's published we install a power state change handler. +//****************************************************************************** + +bool IOPMrootDomain::displayWranglerMatchPublished( + void * target, + void * refCon, + IOService * newService, + IONotifier * notifier __unused) +{ +#if !NO_KERNEL_HID + // found the display wrangler, now install a handler + if( !newService->registerInterest( gIOGeneralInterest, + &displayWranglerNotification, target, 0) ) + { + return false; + } +#endif + return true; +} + +#if defined(__i386__) || defined(__x86_64__) + +bool IOPMrootDomain::IONVRAMMatchPublished( + void * target, + void * refCon, + IOService * newService, + IONotifier * notifier) +{ + unsigned int len = 0; + IOPMrootDomain *rd = (IOPMrootDomain *)target; + OSNumber *statusCode = NULL; + + if (PEReadNVRAMProperty(kIOSleepWakeDebugKey, NULL, &len)) + { + statusCode = OSDynamicCast(OSNumber, rd->getProperty(kIOPMSleepWakeFailureCodeKey)); + if (statusCode != NULL) { + if (statusCode->unsigned64BitValue() != 0) { + rd->swd_flags |= SWD_BOOT_BY_SW_WDOG; + MSG("System was rebooted due to Sleep/Wake failure\n"); + } + else { + rd->swd_flags |= SWD_BOOT_BY_OSX_WDOG; + MSG("System was non-responsive and was rebooted by watchdog\n"); + } + } + + rd->swd_logBufMap = rd->sleepWakeDebugRetrieve(); + } + if (notifier) notifier->remove(); + return true; +} + +#else +bool IOPMrootDomain::IONVRAMMatchPublished( + void * target, + void * refCon, + IOService * newService, + IONotifier * notifier __unused) +{ + return false; +} + +#endif + +//****************************************************************************** +// reportUserInput +// +//****************************************************************************** + +void IOPMrootDomain::reportUserInput( void ) { -#ifdef __ppc__ - IORegistryEntry *_batteryRegEntry = (IORegistryEntry *) getProperty("BatteryEntry"); - - // (if possible) re-publish power source state under IOPMrootDomain; - // only do so if the battery controller publishes an IOResource - // defining battery location. Called from ApplePMU battery driver. +#if !NO_KERNEL_HID + OSIterator * iter; - if(_batteryRegEntry) + if(!wrangler) { - OSArray *batt_info; - batt_info = (OSArray *) _batteryRegEntry->getProperty(kIOBatteryInfoKey); - if(batt_info) - setProperty(kIOBatteryInfoKey, batt_info); + iter = getMatchingServices(serviceMatching("IODisplayWrangler")); + if(iter) + { + wrangler = (IOService *) iter->getNextObject(); + iter->release(); + } } + + if(wrangler) + wrangler->activityTickle(0,0); #endif } - //****************************************************************************** -// setPMSetting (private) -// -// Internal helper to relay PM settings changes from user space to individual -// drivers. Should be called only by IOPMrootDomain::setProperties. +// latchDisplayWranglerTickle //****************************************************************************** -IOReturn IOPMrootDomain::setPMSetting( - const OSSymbol *type, - OSObject *obj) +bool IOPMrootDomain::latchDisplayWranglerTickle( bool latch ) { - OSArray *arr = NULL; - PMSettingObject *p_obj = NULL; - int count; - int i; - - if(NULL == type) return kIOReturnBadArgument; - - IORecursiveLockLock(settingsCtrlLock); - - fPMSettingsDict->setObject(type, obj); +#if !NO_KERNEL_HID + if (latch) + { + if (!(_currentCapability & kIOPMSystemCapabilityGraphics) && + !(_pendingCapability & kIOPMSystemCapabilityGraphics) && + !checkSystemCanSustainFullWake()) + { + // Currently in dark wake, and not transitioning to full wake. + // Full wake is unsustainable, so latch the tickle to prevent + // the display from lighting up momentarily. + wranglerTickleLatched = true; + } + else + { + wranglerTickleLatched = false; + } + } + else if (wranglerTickleLatched && checkSystemCanSustainFullWake()) + { + wranglerTickleLatched = false; - arr = (OSArray *)settingsCallbacks->getObject(type); - if(NULL == arr) goto exit; - count = arr->getCount(); - for(i=0; igetObject(i)); - if(p_obj) p_obj->setPMSetting(type, obj); + pmPowerStateQueue->submitPowerEvent( + kPowerEventPolicyStimulus, + (void *) kStimulusDarkWakeActivityTickle ); } -exit: - IORecursiveLockUnlock(settingsCtrlLock); - return kIOReturnSuccess; + return wranglerTickleLatched; +#else + return false; +#endif } - //****************************************************************************** -// copyPMSetting (public) +// setDisplayPowerOn // -// Allows kexts to safely read setting values, without being subscribed to -// notifications. +// For root domain user client //****************************************************************************** -OSObject * IOPMrootDomain::copyPMSetting( - OSSymbol *whichSetting) +void IOPMrootDomain::setDisplayPowerOn( uint32_t options ) { - OSObject *obj = NULL; - - if(!whichSetting) return NULL; - - IORecursiveLockLock(settingsCtrlLock); - obj = fPMSettingsDict->getObject(whichSetting); - if(obj) { - obj->retain(); + if (checkSystemCanSustainFullWake()) + { + pmPowerStateQueue->submitPowerEvent( kPowerEventSetDisplayPowerOn, + (void *) 0, options ); } - IORecursiveLockUnlock(settingsCtrlLock); - - return obj; } +// MARK: - +// MARK: Battery //****************************************************************************** -// registerPMSettingController (public) +// batteryPublished // -// direct wrapper to registerPMSettingController with uint32_t power source arg +// Notification on battery class IOPowerSource appearance //****************************************************************************** -IOReturn IOPMrootDomain::registerPMSettingController( - const OSSymbol * settings[], - IOPMSettingControllerCallback func, - OSObject *target, - uintptr_t refcon, - OSObject **handle) +bool IOPMrootDomain::batteryPublished( + void * target, + void * root_domain, + IOService * resourceService, + IONotifier * notifier __unused ) { - return registerPMSettingController( - settings, - (kIOPMSupportedOnAC | kIOPMSupportedOnBatt | kIOPMSupportedOnUPS), - func, target, refcon, handle); + // rdar://2936060&4435589 + // All laptops have dimmable LCD displays + // All laptops have batteries + // So if this machine has a battery, publish the fact that the backlight + // supports dimming. + ((IOPMrootDomain *)root_domain)->publishFeature("DisplayDims"); + + return (true); } +// MARK: - +// MARK: System PM Policy //****************************************************************************** -// registerPMSettingController (public) +// checkSystemSleepAllowed // -// Kexts may register for notifications when a particular setting is changed. -// A list of settings is available in IOPM.h. -// Arguments: -// * settings - An OSArray containing OSSymbols. Caller should populate this -// array with a list of settings caller wants notifications from. -// * func - A C function callback of the type IOPMSettingControllerCallback -// * target - caller may provide an OSObject *, which PM will pass as an -// target to calls to "func" -// * refcon - caller may provide an void *, which PM will pass as an -// argument to calls to "func" -// * handle - This is a return argument. We will populate this pointer upon -// call success. Hold onto this and pass this argument to -// IOPMrootDomain::deRegisterPMSettingCallback when unloading your kext -// Returns: -// kIOReturnSuccess on success //****************************************************************************** -IOReturn IOPMrootDomain::registerPMSettingController( - const OSSymbol * settings[], - uint32_t supportedPowerSources, - IOPMSettingControllerCallback func, - OSObject *target, - uintptr_t refcon, - OSObject **handle) +bool IOPMrootDomain::checkSystemSleepAllowed( IOOptionBits options, + uint32_t sleepReason ) { - PMSettingObject *pmso = NULL; - OSArray *list = NULL; - IOReturn ret = kIOReturnSuccess; - int i; + int err = 0; - if( NULL == settings || - NULL == func || - NULL == handle) - { - return kIOReturnBadArgument; - } + // Conditions that prevent idle and demand system sleep. - pmso = PMSettingObject::pmSettingObject( - (IOPMrootDomain *)this, func, target, - refcon, supportedPowerSources, settings); + do { + if (userDisabledAllSleep) + { + err = 1; // 1. user-space sleep kill switch + break; + } - if(!pmso) { - ret = kIOReturnInternalError; - goto bail_no_unlock; - } + if (systemBooting || systemShutdown || gWillShutdown) + { + err = 2; // 2. restart or shutdown in progress + break; + } - IORecursiveLockLock(settingsCtrlLock); - for(i=0; settings[i]; i++) - { - list = (OSArray *)settingsCallbacks->getObject(settings[i]); - if(!list) { - // New array of callbacks for this setting - list = OSArray::withCapacity(1); - settingsCallbacks->setObject(settings[i], list); - list->release(); + if (options == 0) + break; + + // Conditions above pegs the system at full wake. + // Conditions below prevent system sleep but does not prevent + // dark wake, and must be called from gated context. + +#if !CONFIG_SLEEP + err = 3; // 3. config does not support sleep + break; +#endif + + if (lowBatteryCondition || thermalWarningState) + { + break; // always sleep on low battery or when in thermal warning state } - // Add caller to the callback list - list->setObject(pmso); - } + if (sleepReason == kIOPMSleepReasonDarkWakeThermalEmergency) + { + break; // always sleep on dark wake thermal emergencies + } + + if (preventSystemSleepList->getCount() != 0) + { + err = 4; // 4. child prevent system sleep clamp + break; + } - IORecursiveLockUnlock(settingsCtrlLock); - - ret = kIOReturnSuccess; + if (getPMAssertionLevel( kIOPMDriverAssertionCPUBit ) == + kIOPMDriverAssertionLevelOn) + { + err = 5; // 5. CPU assertion + break; + } - // Track this instance by its OSData ptr from now on - *handle = pmso; + if (pciCantSleepValid) + { + if (pciCantSleepFlag) + err = 6; // 6. PCI card does not support PM (cached) + break; + } + else if (sleepSupportedPEFunction && + CAP_HIGHEST(kIOPMSystemCapabilityGraphics)) + { + IOReturn ret; + OSBitAndAtomic(~kPCICantSleep, &platformSleepSupport); + ret = getPlatform()->callPlatformFunction( + sleepSupportedPEFunction, false, + NULL, NULL, NULL, NULL); + pciCantSleepValid = true; + pciCantSleepFlag = false; + if ((platformSleepSupport & kPCICantSleep) || + ((ret != kIOReturnSuccess) && (ret != kIOReturnUnsupported))) + { + err = 6; // 6. PCI card does not support PM + pciCantSleepFlag = true; + break; + } + } + } + while (false); -bail_no_unlock: - if(kIOReturnSuccess != ret) + if (err) { - // Error return case - if(pmso) pmso->release(); - if(handle) *handle = NULL; + DLOG("System sleep prevented by %d\n", err); + return false; } - return ret; + return true; } - -//****************************************************************************** -// sleepOnClamshellClosed -// -// contains the logic to determine if the system should sleep when the clamshell -// is closed. -//****************************************************************************** - -bool IOPMrootDomain::shouldSleepOnClamshellClosed( void ) +bool IOPMrootDomain::checkSystemSleepEnabled( void ) { - DLOG("clamshell state %d, EX %d, IG %d, IW %d, DT %d, AC %d\n", - clamshellIsClosed, clamshellExists, ignoringClamshell, - ignoringClamshellOnWake, desktopMode, acAdaptorConnected); - - return ( !ignoringClamshell - && !ignoringClamshellOnWake - && !(desktopMode && acAdaptorConnected) ); + return checkSystemSleepAllowed(0, 0); } -void IOPMrootDomain::sendClientClamshellNotification( void ) +bool IOPMrootDomain::checkSystemCanSleep( uint32_t sleepReason ) { - /* Only broadcast clamshell alert if clamshell exists. */ - if (!clamshellExists) - return; + ASSERT_GATED(); + return checkSystemSleepAllowed(1, sleepReason); +} - setProperty(kAppleClamshellStateKey, - clamshellIsClosed ? kOSBooleanTrue : kOSBooleanFalse); +//****************************************************************************** +// checkSystemCanSustainFullWake +//****************************************************************************** - setProperty(kAppleClamshellCausesSleepKey, - shouldSleepOnClamshellClosed() ? kOSBooleanTrue : kOSBooleanFalse); +bool IOPMrootDomain::checkSystemCanSustainFullWake( void ) +{ +#if !NO_KERNEL_HID + if (lowBatteryCondition || thermalWarningState) + { + // Low battery wake, or received a low battery notification + // while system is awake. This condition will persist until + // the following wake. + return false; + } - /* Argument to message is a bitfiel of - * ( kClamshellStateBit | kClamshellSleepBit ) - */ - messageClients(kIOPMMessageClamshellStateChange, - (void *) ( (clamshellIsClosed ? kClamshellStateBit : 0) - | ( shouldSleepOnClamshellClosed() ? kClamshellSleepBit : 0)) ); -} + if (clamshellExists && clamshellClosed && !clamshellSleepDisabled) + { + // Graphics state is unknown and external display might not be probed. + // Do not incorporate state that requires graphics to be in max power + // such as desktopMode or clamshellDisabled. + if (!acAdaptorConnected) + { + DLOG("full wake check: no AC\n"); + return false; + } + } +#endif + return true; +} //****************************************************************************** -// informCPUStateChange -// -// Call into PM CPU code so that CPU power savings may dynamically adjust for -// running on battery, with the lid closed, etc. +// adjustPowerState // -// informCPUStateChange is a no-op on non x86 systems -// only x86 has explicit support in the IntelCPUPowerManagement kext +// Conditions that affect our wake/sleep decision has changed. +// If conditions dictate that the system must remain awake, clamp power +// state to max with changePowerStateToPriv(ON). Otherwise if sleepASAP +// is TRUE, then remove the power clamp and allow the power state to drop +// to SLEEP_STATE. //****************************************************************************** -void IOPMrootDomain::informCPUStateChange( - uint32_t type, - uint32_t value ) +void IOPMrootDomain::adjustPowerState( bool sleepASAP ) { -#if defined(__i386__) || defined(__x86_64__) + DLOG("adjustPowerState ps %u, asap %d, slider %ld\n", + (uint32_t) getPowerState(), sleepASAP, sleepSlider); - pmioctlVariableInfo_t varInfoStruct; - int pmCPUret = 0; - const char *varNameStr = NULL; - int32_t *varIndex = NULL; + ASSERT_GATED(); - if (kInformAC == type) { - varNameStr = kIOPMRootDomainBatPowerCString; - varIndex = &idxPMCPULimitedPower; - } else if (kInformLid == type) { - varNameStr = kIOPMRootDomainLidCloseCString; - varIndex = &idxPMCPUClamshell; - } else { - return; + if ((sleepSlider == 0) || !checkSystemSleepEnabled()) + { + changePowerStateToPriv(ON_STATE); } - - // Set the new value! - // pmCPUControl will assign us a new ID if one doesn't exist yet - bzero(&varInfoStruct, sizeof(pmioctlVariableInfo_t)); - varInfoStruct.varID = *varIndex; - varInfoStruct.varType = vBool; - varInfoStruct.varInitValue = value; - varInfoStruct.varCurValue = value; - strncpy( (char *)varInfoStruct.varName, - (const char *)varNameStr, - strlen(varNameStr) + 1 ); - - // Set! - pmCPUret = pmCPUControl( PMIOCSETVARINFO, (void *)&varInfoStruct ); - - // pmCPU only assigns numerical id's when a new varName is specified - if ((0 == pmCPUret) - && (*varIndex == kCPUUnknownIndex)) + else if ( sleepASAP ) { - // pmCPUControl has assigned us a new variable ID. - // Let's re-read the structure we just SET to learn that ID. - pmCPUret = pmCPUControl( PMIOCGETVARNAMEINFO, (void *)&varInfoStruct ); - - if (0 == pmCPUret) - { - // Store it in idxPMCPUClamshell or idxPMCPULimitedPower - *varIndex = varInfoStruct.varID; - } - } - - return; - -#endif /* __i386__ || __x86_64__ */ + changePowerStateToPriv(SLEEP_STATE); + } } - //****************************************************************************** // dispatchPowerEvent // @@ -2538,9 +5773,9 @@ void IOPMrootDomain::informCPUStateChange( //****************************************************************************** void IOPMrootDomain::dispatchPowerEvent( - uint32_t event, void * arg0, void * arg1 ) + uint32_t event, void * arg0, uint64_t arg1 ) { - DLOG("power event %x args %p %p\n", event, arg0, arg1); + DLOG("power event %u args %p 0x%llx\n", event, OBFUSCATE(arg0), arg1); ASSERT_GATED(); switch (event) @@ -2552,22 +5787,46 @@ void IOPMrootDomain::dispatchPowerEvent( case kPowerEventReceivedPowerNotification: handlePowerNotification( (UInt32)(uintptr_t) arg0 ); break; - + case kPowerEventSystemBootCompleted: if (systemBooting) { systemBooting = false; - adjustPowerState(); + if (lowBatteryCondition) + { + privateSleepSystem (kIOPMSleepReasonLowPower); + + // The rest is unnecessary since the system is expected + // to sleep immediately. The following wake will update + // everything. + break; + } + + if (swd_flags & SWD_VALID_LOGS) { + if (swd_flags & SWD_LOGS_IN_MEM) { + sleepWakeDebugDumpFromMem(swd_logBufMap); + swd_logBufMap->release(); + swd_logBufMap = 0; + } + else if (swd_flags & SWD_LOGS_IN_FILE) + sleepWakeDebugDumpFromFile(); + } + else if (swd_flags & (SWD_BOOT_BY_SW_WDOG|SWD_BOOT_BY_OSX_WDOG)) { + // If logs are invalid, write the failure code + sleepWakeDebugDumpFromMem(NULL); + } // If lid is closed, re-send lid closed notification // now that booting is complete. - if( clamshellIsClosed ) + if ( clamshellClosed ) { handlePowerNotification(kLocalEvalClamshellCommand); } + evaluatePolicy( kStimulusAllowSystemSleepChanged ); + } break; - + case kPowerEventSystemShutdown: if (kOSBooleanTrue == (OSBoolean *) arg0) { @@ -2575,53 +5834,124 @@ void IOPMrootDomain::dispatchPowerEvent( to prevent sleep at unexpected times while loginwindow is trying to shutdown apps and while the OS is trying to transition to complete power of. - + Set to true during shutdown, as soon as loginwindow shows the "shutdown countdown dialog", through individual app termination, and through black screen kernel shutdown. */ - LOG("systemShutdown true\n"); systemShutdown = true; } else { /* A shutdown was initiated, but then the shutdown was cancelled, clearing systemShutdown to false here. */ - LOG("systemShutdown false\n"); - systemShutdown = false; + systemShutdown = false; + } + break; + + case kPowerEventUserDisabledSleep: + userDisabledAllSleep = (kOSBooleanTrue == (OSBoolean *) arg0); + break; + + case kPowerEventRegisterSystemCapabilityClient: + if (systemCapabilityNotifier) + { + systemCapabilityNotifier->release(); + systemCapabilityNotifier = 0; + } + if (arg0) + { + systemCapabilityNotifier = (IONotifier *) arg0; + systemCapabilityNotifier->retain(); + } + /* intentional fall-through */ + + case kPowerEventRegisterKernelCapabilityClient: + if (!_joinedCapabilityClients) + _joinedCapabilityClients = OSSet::withCapacity(8); + if (arg0) + { + IONotifier * notify = (IONotifier *) arg0; + if (_joinedCapabilityClients) + { + _joinedCapabilityClients->setObject(notify); + synchronizePowerTree( kIOPMSyncNoChildNotify ); + } + notify->release(); + } + break; + + case kPowerEventPolicyStimulus: + if (arg0) + { + int stimulus = (uintptr_t) arg0; + evaluatePolicy( stimulus, (uint32_t) arg1 ); + } + break; + + case kPowerEventAssertionCreate: + if (pmAssertions) { + pmAssertions->handleCreateAssertion((OSData *)arg0); } break; - case kPowerEventUserDisabledSleep: - userDisabledAllSleep = (kOSBooleanTrue == (OSBoolean *) arg0); + + case kPowerEventAssertionRelease: + if (pmAssertions) { + pmAssertions->handleReleaseAssertion(arg1); + } break; -#if ROOT_DOMAIN_RUN_STATES - case kPowerEventConfigdRegisteredInterest: - if (gConfigdNotifier) - { - gConfigdNotifier->release(); - gConfigdNotifier = 0; + case kPowerEventAssertionSetLevel: + if (pmAssertions) { + pmAssertions->handleSetAssertionLevel(arg1, (IOPMDriverAssertionLevel)(uintptr_t)arg0); } - if (arg0) + break; + + case kPowerEventQueueSleepWakeUUID: + handleQueueSleepWakeUUID((OSObject *)arg0); + break; + case kPowerEventPublishSleepWakeUUID: + handlePublishSleepWakeUUID((bool)arg0); + break; + + case kPowerEventSetDisplayPowerOn: + if (!wrangler) break; + if (arg1 != 0) { - gConfigdNotifier = (IONotifier *) arg0; + // Force wrangler to max power state. If system is in dark wake + // this alone won't raise the wrangler's power state. + + wrangler->changePowerStateForRootDomain(kWranglerPowerStateMax); + + // System in dark wake, always requesting full wake should + // not have any bad side-effects, even if the request fails. + + if (!CAP_CURRENT(kIOPMSystemCapabilityGraphics)) + { + setProperty(kIOPMRootDomainWakeTypeKey, kIOPMRootDomainWakeTypeNotification); + requestFullWake( kFullWakeReasonDisplayOn ); + } } - break; -#endif + else + { + // Relenquish desire to power up display. + // Must first transition to state 1 since wrangler doesn't + // power off the displays at state 0. At state 0 the root + // domain is removed from the wrangler's power client list. - case kPowerEventAggressivenessChanged: - aggressivenessChanged(); + wrangler->changePowerStateForRootDomain(kWranglerPowerStateMin + 1); + wrangler->changePowerStateForRootDomain(kWranglerPowerStateMin); + } break; } } - //****************************************************************************** // systemPowerEventOccurred // // The power controller is notifying us of a hardware-related power management -// event that we must handle. +// event that we must handle. // // systemPowerEventOccurred covers the same functionality that // receivePowerNotification does; it simply provides a richer API for conveying @@ -2635,9 +5965,9 @@ IOReturn IOPMrootDomain::systemPowerEventOccurred( IOReturn attempt = kIOReturnSuccess; OSNumber *newNumber = NULL; - if (!event) + if (!event) return kIOReturnBadArgument; - + newNumber = OSNumber::withNumber(intValue, 8*sizeof(intValue)); if (!newNumber) return kIOReturnInternalError; @@ -2649,14 +5979,32 @@ IOReturn IOPMrootDomain::systemPowerEventOccurred( return attempt; } +void IOPMrootDomain::setThermalState(OSObject *value) +{ + OSNumber * num; + + if (gIOPMWorkLoop->inGate() == false) { + gIOPMWorkLoop->runAction( + OSMemberFunctionCast(IOWorkLoop::Action, this, &IOPMrootDomain::setThermalState), + (OSObject *)this, + (void *)value); + + return; + } + if (value && (num = OSDynamicCast(OSNumber, value))) { + thermalWarningState = ((num->unsigned32BitValue() == kIOPMThermalLevelWarning) || + (num->unsigned32BitValue() == kIOPMThermalLevelTrap)) ? 1 : 0; + } +} + IOReturn IOPMrootDomain::systemPowerEventOccurred( const OSSymbol *event, OSObject *value) { OSDictionary *thermalsDict = NULL; bool shouldUpdate = true; - - if (!event || !value) + + if (!event || !value) return kIOReturnBadArgument; // LOCK @@ -2666,9 +6014,9 @@ IOReturn IOPMrootDomain::systemPowerEventOccurred( if (featuresDictLock) IOLockLock(featuresDictLock); thermalsDict = (OSDictionary *)getProperty(kIOPMRootDomainPowerStatusKey); - + if (thermalsDict && OSDynamicCast(OSDictionary, thermalsDict)) { - thermalsDict = OSDictionary::withDictionary(thermalsDict); + thermalsDict = OSDictionary::withDictionary(thermalsDict); } else { thermalsDict = OSDictionary::withCapacity(1); } @@ -2688,13 +6036,17 @@ exit: // UNLOCK if (featuresDictLock) IOLockUnlock(featuresDictLock); - if (shouldUpdate) + if (shouldUpdate) { + if (event && + event->isEqualTo(kIOPMThermalLevelWarningKey)) { + setThermalState(value); + } messageClients (kIOPMMessageSystemPowerEventOccurred, (void *)NULL); + } return kIOReturnSuccess; } - //****************************************************************************** // receivePowerNotification // @@ -2706,7 +6058,7 @@ exit: IOReturn IOPMrootDomain::receivePowerNotification( UInt32 msg ) { pmPowerStateQueue->submitPowerEvent( - kPowerEventReceivedPowerNotification, (void *) msg ); + kPowerEventReceivedPowerNotification, (void *)(uintptr_t) msg ); return kIOReturnSuccess; } @@ -2727,67 +6079,80 @@ void IOPMrootDomain::handlePowerNotification( UInt32 msg ) /* * Overtemp */ - if (msg & kIOPMOverTemp) + if (msg & kIOPMOverTemp) { - LOG("PowerManagement emergency overtemp signal. Going to sleep!"); - privateSleepSystem (kIOPMThermalEmergencySleepKey); + MSG("PowerManagement emergency overtemp signal. Going to sleep!"); + privateSleepSystem (kIOPMSleepReasonThermalEmergency); } -#ifdef __ppc__ /* - * PMU Processor Speed Change + * Sleep if system is in dark wake */ - if (msg & kIOPMProcessorSpeedChange) + if (msg & kIOPMDWOverTemp) { - IOService *pmu = waitForService(serviceMatching("ApplePMU")); - pmu->callPlatformFunction("prepareForSleep", false, 0, 0, 0, 0); - getPlatform()->sleepKernel(); - pmu->callPlatformFunction("recoverFromSleep", false, 0, 0, 0, 0); + DLOG("DarkWake thermal limits message received!\n"); + + // Inform cap client that we're going to sleep + messageClients(kIOPMMessageDarkWakeThermalEmergency); + } -#endif /* * Sleep Now! */ - if (msg & kIOPMSleepNow) + if (msg & kIOPMSleepNow) { - privateSleepSystem (kIOPMSoftwareSleepKey); + privateSleepSystem (kIOPMSleepReasonSoftware); } - + /* * Power Emergency */ - if (msg & kIOPMPowerEmergency) + if (msg & kIOPMPowerEmergency) { - privateSleepSystem (kIOPMLowPowerSleepKey); + lowBatteryCondition = true; + privateSleepSystem (kIOPMSleepReasonLowPower); } /* * Clamshell OPEN */ - if (msg & kIOPMClamshellOpened) + if (msg & kIOPMClamshellOpened) { // Received clamshel open message from clamshell controlling driver // Update our internal state and tell general interest clients - clamshellIsClosed = false; + clamshellClosed = false; clamshellExists = true; - + + // Don't issue a hid tickle when lid is open and polled on wake + if (msg & kIOPMSetValue) + { + setProperty(kIOPMRootDomainWakeTypeKey, "Lid Open"); + reportUserInput(); + } + // Tell PMCPU informCPUStateChange(kInformLid, 0); - // Tell general interest clients + // Tell general interest clients sendClientClamshellNotification(); - } - /* + bool aborting = ((lastSleepReason == kIOPMSleepReasonClamshell) + || (lastSleepReason == kIOPMSleepReasonIdle) + || (lastSleepReason == kIOPMSleepReasonMaintenance)); + if (aborting) userActivityCount++; + DLOG("clamshell tickled %d lastSleepReason %d\n", userActivityCount, lastSleepReason); + } + + /* * Clamshell CLOSED - * Send the clamshell interest notification since the lid is closing. + * Send the clamshell interest notification since the lid is closing. */ if (msg & kIOPMClamshellClosed) { // Received clamshel open message from clamshell controlling driver // Update our internal state and tell general interest clients - clamshellIsClosed = true; + clamshellClosed = true; clamshellExists = true; // Tell PMCPU @@ -2795,8 +6160,8 @@ void IOPMrootDomain::handlePowerNotification( UInt32 msg ) // Tell general interest clients sendClientClamshellNotification(); - - // And set eval_clamshell = so we can attempt + + // And set eval_clamshell = so we can attempt eval_clamshell = true; } @@ -2805,7 +6170,7 @@ void IOPMrootDomain::handlePowerNotification( UInt32 msg ) * * -> reevaluate lid state */ - if (msg & kIOPMSetDesktopMode) + if (msg & kIOPMSetDesktopMode) { desktopMode = (0 != (msg & kIOPMSetValue)); msg &= ~(kIOPMSetDesktopMode | kIOPMSetValue); @@ -2813,18 +6178,15 @@ void IOPMrootDomain::handlePowerNotification( UInt32 msg ) sendClientClamshellNotification(); // Re-evaluate the lid state - if( clamshellIsClosed ) - { - eval_clamshell = true; - } + eval_clamshell = true; } - + /* * AC Adaptor connected * * -> reevaluate lid state */ - if (msg & kIOPMSetACAdaptorConnected) + if (msg & kIOPMSetACAdaptorConnected) { acAdaptorConnected = (0 != (msg & kIOPMSetValue)); msg &= ~(kIOPMSetACAdaptorConnected | kIOPMSetValue); @@ -2839,2290 +6201,3191 @@ void IOPMrootDomain::handlePowerNotification( UInt32 msg ) sendClientClamshellNotification(); // Re-evaluate the lid state - if( clamshellIsClosed ) - { - eval_clamshell = true; + eval_clamshell = true; + + // Lack of AC may have latched a display wrangler tickle. + // This mirrors the hardware's USB wake event latch, where a latched + // USB wake event followed by an AC attach will trigger a full wake. + latchDisplayWranglerTickle( false ); + +#if HIBERNATION + // AC presence will reset the standy timer delay adjustment. + _standbyTimerResetSeconds = 0; +#endif + if (!userIsActive) { + // Reset userActivityTime when power supply is changed(rdr 13789330) + clock_get_uptime(&userActivityTime); } } - + /* * Enable Clamshell (external display disappear) * * -> reevaluate lid state */ - if (msg & kIOPMEnableClamshell) + if (msg & kIOPMEnableClamshell) { // Re-evaluate the lid state // System should sleep on external display disappearance // in lid closed operation. - if( clamshellIsClosed && (true == ignoringClamshell) ) + if (true == clamshellDisabled) { eval_clamshell = true; } - ignoringClamshell = false; - + clamshellDisabled = false; sendClientClamshellNotification(); } - + /* * Disable Clamshell (external display appeared) * We don't bother re-evaluating clamshell state. If the system is awake, - * the lid is probably open. + * the lid is probably open. */ - if (msg & kIOPMDisableClamshell) + if (msg & kIOPMDisableClamshell) { - ignoringClamshell = true; - + clamshellDisabled = true; sendClientClamshellNotification(); } /* * Evaluate clamshell and SLEEP if appropiate */ - if ( eval_clamshell && shouldSleepOnClamshellClosed() ) + if (eval_clamshell && clamshellClosed) { - - - // SLEEP! - privateSleepSystem (kIOPMClamshellSleepKey); + if (shouldSleepOnClamshellClosed()) + privateSleepSystem (kIOPMSleepReasonClamshell); + else + evaluatePolicy( kStimulusDarkWakeEvaluate ); } /* * Power Button */ - if (msg & kIOPMPowerButton) + if (msg & kIOPMPowerButton) { - // toggle state of sleep/wake - // are we dozing? - if ( getPowerState() == DOZE_STATE ) + if (!wranglerAsleep) { -#ifndef __LP64__ - // yes, tell the tree we're waking - systemWake(); -#endif - // wake the Display Wrangler - reportUserInput(); - } - else { OSString *pbs = OSString::withCString("DisablePowerButtonSleep"); // Check that power button sleep is enabled if( pbs ) { if( kOSBooleanTrue != getProperty(pbs)) - privateSleepSystem (kIOPMPowerButtonSleepKey); + privateSleepSystem (kIOPMSleepReasonPowerButton); } } - } - - /* - * Allow Sleep - * - */ - if ( (msg & kIOPMAllowSleep) && !allowSleep ) - { - allowSleep = true; - adjustPowerState(); - } - - /* - * Prevent Sleep - * - */ - if (msg & kIOPMPreventSleep) { - allowSleep = false; - // are we dozing? - if ( getPowerState() == DOZE_STATE ) { -#ifndef __LP64__ - // yes, tell the tree we're waking - systemWake(); -#endif - adjustPowerState(); - // wake the Display Wrangler + else reportUserInput(); - } else { - adjustPowerState(); - // make sure we have power to clamp - patriarch->wakeSystem(); - } } } - //****************************************************************************** -// getSleepSupported +// evaluatePolicy // +// Evaluate root-domain policy in response to external changes. //****************************************************************************** -IOOptionBits IOPMrootDomain::getSleepSupported( void ) +void IOPMrootDomain::evaluatePolicy( int stimulus, uint32_t arg ) { - return( platformSleepSupport ); -} + union { + struct { + int idleSleepEnabled : 1; + int idleSleepDisabled : 1; + int displaySleep : 1; + int sleepDelayChanged : 1; + int evaluateDarkWake : 1; + int adjustPowerState : 1; + int userBecameInactive : 1; + } bit; + uint32_t u32; + } flags; + + DLOG("evaluatePolicy( %d, 0x%x )\n", stimulus, arg); + ASSERT_GATED(); + flags.u32 = 0; -//****************************************************************************** -// setSleepSupported -// -//****************************************************************************** + switch (stimulus) + { + case kStimulusDisplayWranglerSleep: + if (!wranglerAsleep) + { + // first transition to wrangler sleep or lower + wranglerAsleep = true; + flags.bit.displaySleep = true; + } + break; -void IOPMrootDomain::setSleepSupported( IOOptionBits flags ) -{ - DLOG("setSleepSupported(%x)\n", (uint32_t) flags); - OSBitOrAtomic(flags, &platformSleepSupport); -} + case kStimulusDisplayWranglerWake: + displayIdleForDemandSleep = false; + wranglerAsleep = false; + break; + case kStimulusEnterUserActiveState: + if (_preventUserActive) + { + DLOG("user active dropped\n"); + break; + } + if (!userIsActive) + { + userIsActive = true; + userWasActive = true; -//****************************************************************************** -// requestPowerDomainState -// -// The root domain intercepts this call to the superclass. -// Called on the PM work loop thread. -// -// 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. -//****************************************************************************** + // Stay awake after dropping demand for display power on + if (kFullWakeReasonDisplayOn == fullWakeReason) + fullWakeReason = fFullWakeReasonDisplayOnAndLocalUser; -IOReturn IOPMrootDomain::requestPowerDomainState ( - IOPMPowerFlags desiredFlags, - IOPowerConnection * whichChild, - unsigned long specification ) -{ - OSIterator *iter; - OSObject *next; - IOPowerConnection *connection; - IOPMPowerFlags powerRequestFlag = 0; - IOPMPowerFlags editedDesire; + setProperty(gIOPMUserIsActiveKey, kOSBooleanTrue); + messageClients(kIOPMMessageUserIsActiveChanged); + } + flags.bit.idleSleepDisabled = true; + break; - ASSERT_GATED(); + case kStimulusLeaveUserActiveState: + if (userIsActive) + { + userIsActive = false; + clock_get_uptime(&userBecameInactiveTime); + flags.bit.userBecameInactive = true; - if (kIOLogPMRootDomain & gIOKitDebug) - { - IOService * powerChild = - (IOService *) whichChild->getChildEntry(gIOPowerPlane); - DLOG("child %p, flags %lx, spec %lx - %s\n", - powerChild, desiredFlags, specification, - powerChild ? powerChild->getName() : "?"); - } + setProperty(gIOPMUserIsActiveKey, kOSBooleanFalse); + messageClients(kIOPMMessageUserIsActiveChanged); + } + break; - // Force the child's input power requirements to 0 unless the prevent - // idle-sleep flag is set. No input power flags map to our state 0. - // Our power clamp (deviceDesire) keeps the minimum power state at 2. + case kStimulusAggressivenessChanged: + { + unsigned long minutesToIdleSleep = 0; + unsigned long minutesToDisplayDim = 0; + unsigned long minutesDelta = 0; - if (desiredFlags & kIOPMPreventIdleSleep) - editedDesire = kIOPMPreventIdleSleep | kIOPMPowerOn; - else - editedDesire = 0; + // Fetch latest display and system sleep slider values. + getAggressiveness(kPMMinutesToSleep, &minutesToIdleSleep); + getAggressiveness(kPMMinutesToDim, &minutesToDisplayDim); + DLOG("aggressiveness changed: system %u->%u, display %u\n", + (uint32_t) sleepSlider, + (uint32_t) minutesToIdleSleep, + (uint32_t) minutesToDisplayDim); - // Recompute sleep supported flag (doze if not supported) - sleepIsSupported = true; + DLOG("idle time -> %ld secs (ena %d)\n", + idleSeconds, (minutesToIdleSleep != 0)); - iter = getChildIterator(gIOPowerPlane); - if ( iter ) - { - while ( (next = iter->getNextObject()) ) - { - if ( (connection = OSDynamicCast(IOPowerConnection, next)) ) + if (0x7fffffff == minutesToIdleSleep) + minutesToIdleSleep = idleSeconds; + + // How long to wait before sleeping the system once + // the displays turns off is indicated by 'extraSleepDelay'. + + if ( minutesToIdleSleep > minutesToDisplayDim ) + minutesDelta = minutesToIdleSleep - minutesToDisplayDim; + else if ( minutesToIdleSleep == minutesToDisplayDim ) + minutesDelta = 1; + + if ((sleepSlider == 0) && (minutesToIdleSleep != 0)) + flags.bit.idleSleepEnabled = true; + + if ((sleepSlider != 0) && (minutesToIdleSleep == 0)) + flags.bit.idleSleepDisabled = true; + + if (((minutesDelta != extraSleepDelay) || + (userActivityTime != userActivityTime_prev)) && + !flags.bit.idleSleepEnabled && !flags.bit.idleSleepDisabled) + flags.bit.sleepDelayChanged = true; + + if (systemDarkWake && !darkWakeToSleepASAP && + (flags.bit.idleSleepEnabled || flags.bit.idleSleepDisabled)) { - // Ignore child that are in the process of joining. - if (connection->getReadyFlag() == false) - continue; + // Reconsider decision to remain in dark wake + flags.bit.evaluateDarkWake = true; + } - // Is this connection attached to the child that called - // requestPowerDomainState()? + sleepSlider = minutesToIdleSleep; + extraSleepDelay = minutesDelta; + userActivityTime_prev = userActivityTime; + } break; - if (connection == whichChild) + case kStimulusDemandSystemSleep: + displayIdleForDemandSleep = true; + if (wrangler && wranglerIdleSettings) + { + // Request wrangler idle only when demand sleep is triggered + // from full wake. + if(CAP_CURRENT(kIOPMSystemCapabilityGraphics)) { - // OR in the child's input power requirements. - powerRequestFlag |= editedDesire; + wrangler->setProperties(wranglerIdleSettings); + DLOG("Requested wrangler idle\n"); + } + } + // arg = sleepReason + changePowerStateWithOverrideTo( SLEEP_STATE, arg ); + break; + + case kStimulusAllowSystemSleepChanged: + flags.bit.adjustPowerState = true; + break; - if ( desiredFlags & kIOPMPreventSystemSleep ) - sleepIsSupported = false; + case kStimulusDarkWakeActivityTickle: + // arg == true implies real and not self generated wrangler tickle. + // Update wake type on PM work loop instead of the tickle thread to + // eliminate the possibility of an early tickle clobbering the wake + // type set by the platform driver. + if (arg == true) + setProperty(kIOPMRootDomainWakeTypeKey, kIOPMRootDomainWakeTypeHIDActivity); + + if (false == wranglerTickled) + { + if (latchDisplayWranglerTickle(true)) + { + DLOG("latched tickle\n"); + break; } - else + + wranglerTickled = true; + DLOG("Requesting full wake after dark wake activity tickle\n"); + requestFullWake( kFullWakeReasonLocalUser ); + } + break; + + case kStimulusDarkWakeEntry: + case kStimulusDarkWakeReentry: + // Any system transitions since the last dark wake transition + // will invalid the stimulus. + + if (arg == _systemStateGeneration) + { + DLOG("dark wake entry\n"); + systemDarkWake = true; + + // Keep wranglerAsleep an invariant when wrangler is absent + if (wrangler) + wranglerAsleep = true; + + if (kStimulusDarkWakeEntry == stimulus) { - if (kIOLogPMRootDomain & gIOKitDebug) - { - IOService * powerChild = - (IOService *) connection->getChildEntry(gIOPowerPlane); - DLOG("child %p, state %ld, noIdle %d, noSleep %d - %s\n", - powerChild, - connection->getDesiredDomainState(), - connection->getPreventIdleSleepFlag(), - connection->getPreventSystemSleepFlag(), - powerChild ? powerChild->getName() : "?"); - } + clock_get_uptime(&userBecameInactiveTime); + flags.bit.evaluateDarkWake = true; + } + + // Always accelerate disk spindown while in dark wake, + // even if system does not support/allow sleep. + + cancelIdleSleepTimer(); + setQuickSpinDownTimeout(); + } + break; + + case kStimulusDarkWakeEvaluate: + if (systemDarkWake) + { + flags.bit.evaluateDarkWake = true; + } + break; + + case kStimulusNoIdleSleepPreventers: + flags.bit.adjustPowerState = true; + break; + + } /* switch(stimulus) */ + + if (flags.bit.evaluateDarkWake && (kFullWakeReasonNone == fullWakeReason)) + { + if (darkWakeToSleepASAP || + (clamshellClosed && !(desktopMode && acAdaptorConnected))) + { + uint32_t newSleepReason; + + if (CAP_HIGHEST(kIOPMSystemCapabilityGraphics)) + { + // System was previously in full wake. Sleep reason from + // full to dark already recorded in fullToDarkReason. + + if (lowBatteryCondition) + newSleepReason = kIOPMSleepReasonLowPower; + else + newSleepReason = fullToDarkReason; + } + else + { + // In dark wake from system sleep. + + if (darkWakeSleepService) + newSleepReason = kIOPMSleepReasonSleepServiceExit; + else + newSleepReason = kIOPMSleepReasonMaintenance; + } + + if (checkSystemCanSleep(newSleepReason)) + { + privateSleepSystem(newSleepReason); + } + } + else // non-maintenance (network) dark wake + { + if (checkSystemCanSleep(kIOPMSleepReasonIdle)) + { + // Release power clamp, and wait for children idle. + adjustPowerState(true); + } + else + { + changePowerStateToPriv(ON_STATE); + } + } + } + + if (systemDarkWake) + { + // The rest are irrelevant while system is in dark wake. + flags.u32 = 0; + } + + if ((flags.bit.displaySleep) && + (kFullWakeReasonDisplayOn == fullWakeReason)) + { + // kIOPMSleepReasonMaintenance? + changePowerStateWithOverrideTo( SLEEP_STATE, kIOPMSleepReasonMaintenance ); + } + + if (flags.bit.userBecameInactive || flags.bit.sleepDelayChanged) + { + bool cancelQuickSpindown = false; + + if (flags.bit.sleepDelayChanged) + { + // Cancel existing idle sleep timer and quick disk spindown. + // New settings will be applied by the idleSleepEnabled flag + // handler below if idle sleep is enabled. + + DLOG("extra sleep timer changed\n"); + cancelIdleSleepTimer(); + cancelQuickSpindown = true; + } + else + { + DLOG("user inactive\n"); + } + + if (!userIsActive && sleepSlider) + { + startIdleSleepTimer(getTimeToIdleSleep()); + } + + if (cancelQuickSpindown) + restoreUserSpinDownTimeout(); + } + + if (flags.bit.idleSleepEnabled) + { + DLOG("idle sleep timer enabled\n"); + if (!wrangler) + { + changePowerStateToPriv(ON_STATE); + if (idleSeconds) + { + startIdleSleepTimer( idleSeconds ); + } + } + else + { + // Start idle timer if prefs now allow system sleep + // and user is already inactive. Disk spindown is + // accelerated upon timer expiration. + + if (!userIsActive) + { + startIdleSleepTimer(getTimeToIdleSleep()); + } + } + } + + if (flags.bit.idleSleepDisabled) + { + DLOG("idle sleep timer disabled\n"); + cancelIdleSleepTimer(); + restoreUserSpinDownTimeout(); + adjustPowerState(); + } - // OR in the child's desired power state (0 or ON_STATE). - powerRequestFlag |= connection->getDesiredDomainState(); + if (flags.bit.adjustPowerState) + { + bool sleepASAP = false; - if ( connection->getPreventSystemSleepFlag() ) - sleepIsSupported = false; + if (!systemBooting && (preventIdleSleepList->getCount() == 0)) + { + if (!wrangler) + { + changePowerStateToPriv(ON_STATE); + if (idleSeconds) + { + // stay awake for at least idleSeconds + startIdleSleepTimer(idleSeconds); } } + else if (!extraSleepDelay && !idleSleepTimerPending && !systemDarkWake) + { + sleepASAP = true; + } } - iter->release(); + + adjustPowerState(sleepASAP); } +} - DLOG("childPowerFlags 0x%lx, extraSleepDelay %ld\n", - powerRequestFlag, extraSleepDelay); +//****************************************************************************** +// requestFullWake +// +// Request transition from dark wake to full wake +//****************************************************************************** - if ( !powerRequestFlag && !systemBooting ) +void IOPMrootDomain::requestFullWake( FullWakeReason reason ) +{ + uint32_t options = 0; + IOService * pciRoot = 0; + bool promotion = false; + + // System must be in dark wake and a valid reason for entering full wake + if ((kFullWakeReasonNone == reason) || + (kFullWakeReasonNone != fullWakeReason) || + (CAP_CURRENT(kIOPMSystemCapabilityGraphics))) { - if (!wrangler) - { - sleepASAP = false; - changePowerStateToPriv(ON_STATE); - if (idleSeconds) - { - // stay awake for at least idleSeconds - startIdleSleepTimer(idleSeconds); - } - } - else if (!extraSleepDelay && !idleSleepTimerPending) - { - sleepASAP = true; - } + return; } - // Drop our power clamp to SLEEP_STATE when all children became idle, - // and the system sleep and display sleep values are equal. + // Will clear reason upon exit from full wake + fullWakeReason = reason; - adjustPowerState(); + _desiredCapability |= (kIOPMSystemCapabilityGraphics | + kIOPMSystemCapabilityAudio); - // If our power clamp has already dropped to SLEEP_STATE, and no child - // is keeping us at ON_STATE, then this will trigger idle sleep. + if ((kSystemTransitionWake == _systemTransitionType) && + !(_pendingCapability & kIOPMSystemCapabilityGraphics) && + !graphicsSuppressed) + { + // Promote to full wake while waking up to dark wake due to tickle. + // PM will hold off notifying the graphics subsystem about system wake + // as late as possible, so if a HID tickle does arrive, graphics can + // power up on this same wake cycle. The latency to power up graphics + // on the next cycle can be huge on some systems. However, once any + // graphics suppression has taken effect, it is too late. All other + // graphics devices must be similarly suppressed. But the delay till + // the following cycle should be short. + + _pendingCapability |= (kIOPMSystemCapabilityGraphics | + kIOPMSystemCapabilityAudio); + + // Immediately bring up audio and graphics + pciRoot = pciHostBridgeDriver; + willEnterFullWake(); + promotion = true; + } - editedDesire |= (desiredFlags & kIOPMPreventSystemSleep); + // Unsafe to cancel once graphics was powered. + // If system woke from dark wake, the return to sleep can + // be cancelled. "awake -> dark -> sleep" transition + // can be canceled also, during the "dark --> sleep" phase + // *prior* to driver power down. + if (!CAP_HIGHEST(kIOPMSystemCapabilityGraphics) || + _pendingCapability == 0) { + options |= kIOPMSyncCancelPowerDown; + } - return super::requestPowerDomainState( - editedDesire, whichChild, specification); -} + synchronizePowerTree(options, pciRoot); + if (kFullWakeReasonLocalUser == fullWakeReason) + { + // IOGraphics doesn't light the display even though graphics is + // enabled in kIOMessageSystemCapabilityChange message(radar 9502104) + // So, do an explicit activity tickle + if (wrangler) + wrangler->activityTickle(0,0); + } + // Log a timestamp for the initial full wake request. + // System may not always honor this full wake request. + if (!CAP_HIGHEST(kIOPMSystemCapabilityGraphics)) + { + AbsoluteTime now; + uint64_t nsec; + + clock_get_uptime(&now); + SUB_ABSOLUTETIME(&now, &systemWakeTime); + absolutetime_to_nanoseconds(now, &nsec); + MSG("full wake %s (reason %u) %u ms\n", + promotion ? "promotion" : "request", + fullWakeReason, ((int)((nsec) / 1000000ULL))); + } +} //****************************************************************************** -// handlePlatformHaltRestart +// willEnterFullWake +// +// System will enter full wake from sleep, from dark wake, or from dark +// wake promotion. This function aggregate things that are in common to +// all three full wake transitions. // +// Assumptions: fullWakeReason was updated //****************************************************************************** -struct HaltRestartApplierContext { - IOPMrootDomain * RootDomain; - unsigned long PowerState; - IOPMPowerFlags PowerFlags; - UInt32 MessageType; - UInt32 Counter; -}; - -static void -platformHaltRestartApplier( OSObject * object, void * context ) +void IOPMrootDomain::willEnterFullWake( void ) { - IOPowerStateChangeNotification notify; - HaltRestartApplierContext * ctx; - AbsoluteTime startTime; - UInt32 deltaTime; + hibernateRetry = false; + sleepToStandby = false; + sleepTimerMaintenance = false; - ctx = (HaltRestartApplierContext *) context; - - memset(¬ify, 0, sizeof(notify)); - notify.powerRef = (void *)ctx->Counter; - notify.returnValue = 0; - notify.stateNumber = ctx->PowerState; - notify.stateFlags = ctx->PowerFlags; + _systemMessageClientMask = kSystemMessageClientPowerd | + kSystemMessageClientLegacyApp; - clock_get_uptime(&startTime); - ctx->RootDomain->messageClient( ctx->MessageType, object, (void *)¬ify ); - deltaTime = computeDeltaTimeMS(&startTime); - - if ((deltaTime > kPMHaltTimeoutMS) || (gIOKitDebug & kIOLogDebugPower)) - { - _IOServiceInterestNotifier * notifier; - notifier = OSDynamicCast(_IOServiceInterestNotifier, object); - - // IOService children of IOPMrootDomain are not instrumented. - // Only IORootParent currently falls under that group. - - if (notifier) - { - KLOG("%s handler %p took %u ms\n", - (ctx->MessageType == kIOMessageSystemWillPowerOff) ? - "PowerOff" : "Restart", - notifier->handler, (uint32_t) deltaTime ); - } - } + if ((_highestCapability & kIOPMSystemCapabilityGraphics) == 0) + { + // Initial graphics full power + _systemMessageClientMask |= kSystemMessageClientKernel; + + // Set kIOPMUserTriggeredFullWakeKey before full wake for IOGraphics + setProperty(gIOPMUserTriggeredFullWakeKey, + (kFullWakeReasonLocalUser == fullWakeReason) ? + kOSBooleanTrue : kOSBooleanFalse); + } +#if HIBERNATION + IOHibernateSetWakeCapabilities(_pendingCapability); +#endif - ctx->Counter++; + IOService::setAdvisoryTickleEnable( true ); + tellClients(kIOMessageSystemWillPowerOn); + preventTransitionToUserActive(false); } -void IOPMrootDomain::handlePlatformHaltRestart( UInt32 pe_type ) -{ - HaltRestartApplierContext ctx; - AbsoluteTime startTime; - UInt32 deltaTime; +//****************************************************************************** +// fullWakeDelayedWork +// +// System has already entered full wake. Invoked by a delayed thread call. +//****************************************************************************** - memset(&ctx, 0, sizeof(ctx)); - ctx.RootDomain = this; +void IOPMrootDomain::fullWakeDelayedWork( void ) +{ +#if DARK_TO_FULL_EVALUATE_CLAMSHELL + // Not gated, don't modify state + if ((kSystemTransitionNone == _systemTransitionType) && + CAP_CURRENT(kIOPMSystemCapabilityGraphics)) + { + receivePowerNotification( kLocalEvalClamshellCommand ); + } +#endif +} - clock_get_uptime(&startTime); - switch (pe_type) - { - case kPEHaltCPU: - case kPEUPSDelayHaltCPU: - ctx.PowerState = OFF_STATE; - ctx.MessageType = kIOMessageSystemWillPowerOff; - break; +//****************************************************************************** +// evaluateAssertions +// +//****************************************************************************** +void IOPMrootDomain::evaluateAssertions(IOPMDriverAssertionType newAssertions, IOPMDriverAssertionType oldAssertions) +{ + IOPMDriverAssertionType changedBits = newAssertions ^ oldAssertions; - case kPERestartCPU: - ctx.PowerState = RESTART_STATE; - ctx.MessageType = kIOMessageSystemWillRestart; - break; + messageClients(kIOPMMessageDriverAssertionsChanged); - default: - return; - } + if (changedBits & kIOPMDriverAssertionPreventDisplaySleepBit) { - // Notify legacy clients - applyToInterested(gIOPriorityPowerStateInterest, platformHaltRestartApplier, &ctx); + if (wrangler) { + bool value = (newAssertions & kIOPMDriverAssertionPreventDisplaySleepBit) ? true : false; - // For UPS shutdown leave File Server Mode intact, otherwise turn it off. - if (kPEUPSDelayHaltCPU != pe_type) - { - const OSSymbol * setting = OSSymbol::withCString(kIOPMSettingRestartOnPowerLossKey); - OSNumber * num = OSNumber::withNumber((unsigned long long) 0, 32); - if (setting && num) - { - setPMSetting(setting, num); - setting->release(); - num->release(); + DLOG("wrangler->setIgnoreIdleTimer\(%d)\n", value); + wrangler->setIgnoreIdleTimer( value ); } } - // Notify in power tree order - notifySystemShutdown(this, ctx.MessageType); + if (changedBits & kIOPMDriverAssertionCPUBit) + evaluatePolicy(kStimulusDarkWakeEvaluate); - deltaTime = computeDeltaTimeMS(&startTime); - KLOG("%s all drivers took %u ms\n", - (ctx.MessageType == kIOMessageSystemWillPowerOff) ? - "PowerOff" : "Restart", - (uint32_t) deltaTime ); + if (changedBits & kIOPMDriverAssertionReservedBit7) { + bool value = (newAssertions & kIOPMDriverAssertionReservedBit7) ? true : false; + if (value) { + DLOG("Driver assertion ReservedBit7 raised. Legacy IO preventing sleep\n"); + updatePreventIdleSleepList(this, true); + } + else { + DLOG("Driver assertion ReservedBit7 dropped\n"); + updatePreventIdleSleepList(this, false); + } + } } +// MARK: - +// MARK: Statistics //****************************************************************************** -// registerInterest +// pmStats // //****************************************************************************** -IONotifier * IOPMrootDomain::registerInterest( - const OSSymbol * typeOfInterest, - IOServiceInterestHandler handler, - void * target, void * ref ) +void IOPMrootDomain::pmStatsRecordEvent( + int eventIndex, + AbsoluteTime timestamp) { - IONotifier * notifier; - bool isConfigd; + bool starting = eventIndex & kIOPMStatsEventStartFlag ? true:false; + bool stopping = eventIndex & kIOPMStatsEventStopFlag ? true:false; + uint64_t delta; + uint64_t nsec; + OSData *publishPMStats = NULL; - isConfigd = typeOfInterest && - typeOfInterest->isEqualTo(kIOPMPrivilegedPowerInterest); + eventIndex &= ~(kIOPMStatsEventStartFlag | kIOPMStatsEventStopFlag); - if (isConfigd) - typeOfInterest = gIOAppPowerStateInterest; + absolutetime_to_nanoseconds(timestamp, &nsec); - notifier = super::registerInterest(typeOfInterest, handler, target, ref); + switch (eventIndex) { + case kIOPMStatsHibernateImageWrite: + if (starting) + gPMStats.hibWrite.start = nsec; + else if (stopping) + gPMStats.hibWrite.stop = nsec; -#if ROOT_DOMAIN_RUN_STATES - if (isConfigd && notifier && pmPowerStateQueue) - { - notifier->retain(); - if (pmPowerStateQueue->submitPowerEvent( - kPowerEventConfigdRegisteredInterest, notifier) == false) - notifier->release(); - } -#endif + if (stopping) { + delta = gPMStats.hibWrite.stop - gPMStats.hibWrite.start; + IOLog("PMStats: Hibernate write took %qd ms\n", delta/1000000ULL); + } + break; + case kIOPMStatsHibernateImageRead: + if (starting) + gPMStats.hibRead.start = nsec; + else if (stopping) + gPMStats.hibRead.stop = nsec; - return notifier; + if (stopping) { + delta = gPMStats.hibRead.stop - gPMStats.hibRead.start; + IOLog("PMStats: Hibernate read took %qd ms\n", delta/1000000ULL); + + publishPMStats = OSData::withBytes(&gPMStats, sizeof(gPMStats)); + setProperty(kIOPMSleepStatisticsKey, publishPMStats); + publishPMStats->release(); + bzero(&gPMStats, sizeof(gPMStats)); + } + break; + } } -static bool clientMessageFilter( OSObject * object, void * arg ) +/* + * Appends a record of the application response to + * IOPMrootDomain::pmStatsAppResponses + */ +void IOPMrootDomain::pmStatsRecordApplicationResponse( + const OSSymbol *response, + const char *name, + int messageType, + uint32_t delay_ms, + int app_pid, + OSObject *object, + IOPMPowerStateIndex powerState) { -#if ROOT_DOMAIN_RUN_STATES -#if LOG_INTEREST_CLIENTS - IOPMInterestContext * context = (IOPMInterestContext *) arg; -#endif - bool allow = false; + OSDictionary *responseDescription = NULL; + OSNumber *delayNum = NULL; + OSNumber *powerCaps = NULL; + OSNumber *pidNum = NULL; + OSNumber *msgNum = NULL; + const OSSymbol *appname; + const OSSymbol *sleep = NULL, *wake = NULL; + IOPMServiceInterestNotifier *notify = 0; - switch (gMessageClientType) + if (object && (notify = OSDynamicCast(IOPMServiceInterestNotifier, object))) { - case kMessageClientNone: - allow = false; - break; - - case kMessageClientAll: - allow = true; - break; + if (response->isEqualTo(gIOPMStatsApplicationResponseTimedOut)) + notify->ackTimeoutCnt++; + else + notify->ackTimeoutCnt = 0; - case kMessageClientConfigd: - allow = ((object == (OSObject *) gConfigdNotifier) || - (object == (OSObject *) gSysPowerDownNotifier)); - break; } -#if LOG_INTEREST_CLIENTS - if (allow) - DLOG("system message %x to %p\n", - context->msgType, object); -#endif + if (response->isEqualTo(gIOPMStatsApplicationResponsePrompt) || + (_systemTransitionType == kSystemTransitionNone) || (_systemTransitionType == kSystemTransitionNewCapClient)) + return; - return allow; -#else - return true; -#endif -} + responseDescription = OSDictionary::withCapacity(5); + if (responseDescription) + { + if (response) { + responseDescription->setObject(_statsResponseTypeKey, response); + } -//****************************************************************************** -// tellChangeDown -// -// We override the superclass implementation so we can send a different message -// type to the client or application being notified. -//****************************************************************************** + msgNum = OSNumber::withNumber(messageType, 32); + if (msgNum) { + responseDescription->setObject(_statsMessageTypeKey, msgNum); + msgNum->release(); + } -bool IOPMrootDomain::tellChangeDown( unsigned long stateNum ) -{ - bool done; + if (name && (strlen(name) > 0)) + { + appname = OSSymbol::withCString(name); + if (appname) { + responseDescription->setObject(_statsNameKey, appname); + appname->release(); + } + } - DLOG("tellChangeDown %u->%u, R-state %u\n", - (uint32_t) getPowerState(), (uint32_t) stateNum, runStateIndex); + if (app_pid != -1) { + pidNum = OSNumber::withNumber(app_pid, 32); + if (pidNum) { + responseDescription->setObject(_statsPIDKey, pidNum); + pidNum->release(); + } + } - switch ( stateNum ) { - case DOZE_STATE: - case SLEEP_STATE: + delayNum = OSNumber::withNumber(delay_ms, 32); + if (delayNum) { + responseDescription->setObject(_statsTimeMSKey, delayNum); + delayNum->release(); + } - if (!ignoreChangeDown) - { - // Direct callout into OSKext so it can disable kext unloads - // during sleep/wake to prevent deadlocks. - OSKextSystemSleepOrWake( kIOMessageSystemWillSleep ); + if (response->isEqualTo(gIOPMStatsDriverPSChangeSlow)) { + powerCaps = OSNumber::withNumber(powerState, 32); - if ( (SLEEP_STATE == stateNum) && sleepSupportedPEFunction ) - { - // Reset PCI prevent sleep flag before calling platform driver. - OSBitAndAtomic(~kPCICantSleep, &platformSleepSupport); +#if !defined(__i386__) && !defined(__x86_64__) + IOLog("%s::powerStateChange type(%d) to(%lu) async took %d ms\n", + name, messageType, + powerState, delay_ms); +#endif - // Skip PCI check for maintenance sleep. - if ((runStateFlags & kRStateFlagSuppressPCICheck) == 0) - { - // Determine if the machine supports sleep, or must doze. - getPlatform()->callPlatformFunction( - sleepSupportedPEFunction, false, - NULL, NULL, NULL, NULL); - } + } + else { + powerCaps = OSNumber::withNumber(_pendingCapability, 32); + } + if (powerCaps) { + responseDescription->setObject(_statsPowerCapsKey, powerCaps); + powerCaps->release(); + } - // If the machine only supports doze, the callPlatformFunction call - // boils down to IOPMrootDomain::setSleepSupported(kPCICantSleep), - // otherwise nothing. - } + sleep = OSSymbol::withCString("Sleep"); + wake = OSSymbol::withCString("Wake"); + if (_systemTransitionType == kSystemTransitionSleep) { + responseDescription->setObject(kIOPMStatsSystemTransitionKey, sleep); + } + else if (_systemTransitionType == kSystemTransitionWake) { + responseDescription->setObject(kIOPMStatsSystemTransitionKey, wake); + } + else if (_systemTransitionType == kSystemTransitionCapability) { + if (CAP_LOSS(kIOPMSystemCapabilityGraphics)) + responseDescription->setObject(kIOPMStatsSystemTransitionKey, sleep); + else if (CAP_GAIN(kIOPMSystemCapabilityGraphics)) + responseDescription->setObject(kIOPMStatsSystemTransitionKey, wake); + } + if (sleep) sleep->release(); + if (wake) wake->release(); - // Update canSleep and kIOSleepSupportedKey property so drivers - // can tell if platform is going to sleep versus doze. -#if CONFIG_SLEEP - canSleep = true; -#else - canSleep = false; -#endif - if (!sleepIsSupported) - canSleep = false; - if (platformSleepSupport & kPCICantSleep) - canSleep = false; - setProperty(kIOSleepSupportedKey, canSleep); - DLOG("canSleep %d\n", canSleep); - - // Publish the new sleep-wake UUID - publishSleepWakeUUID(true); - - // Two change downs are sent by IOServicePM. Ignore the 2nd. - ignoreChangeDown = true; - - tracePoint( kIOPMTracePointSystemSleepAppsPhase); - } - DLOG("kIOMessageSystemWillSleep (%d)\n", gMessageClientType); - done = super::tellClientsWithResponse( - kIOMessageSystemWillSleep, clientMessageFilter); - break; + IOLockLock(pmStatsLock); + if (pmStatsAppResponses && pmStatsAppResponses->getCount() < 50) { + pmStatsAppResponses->setObject(responseDescription); + } + IOLockUnlock(pmStatsLock); - default: - done = super::tellChangeDown(stateNum); - break; + responseDescription->release(); } - return done; + + return; } +// MARK: - +// MARK: PMTraceWorker //****************************************************************************** -// askChangeDown -// -// We override the superclass implementation so we can send a different message -// type to the client or application being notified. +// TracePoint support // -// This must be idle sleep since we don't ask during any other power change. //****************************************************************************** -bool IOPMrootDomain::askChangeDown( unsigned long stateNum ) +#define kIOPMRegisterNVRAMTracePointHandlerKey \ + "IOPMRegisterNVRAMTracePointHandler" + +IOReturn IOPMrootDomain::callPlatformFunction( + const OSSymbol * functionName, + bool waitForFunction, + void * param1, void * param2, + void * param3, void * param4 ) +{ + if (pmTracer && functionName && + functionName->isEqualTo(kIOPMRegisterNVRAMTracePointHandlerKey) && + !pmTracer->tracePointHandler && !pmTracer->tracePointTarget) + { + uint32_t tracePointPhases, tracePointPCI; + uint64_t statusCode; + + pmTracer->tracePointHandler = (IOPMTracePointHandler) param1; + pmTracer->tracePointTarget = (void *) param2; + tracePointPCI = (uint32_t)(uintptr_t) param3; + tracePointPhases = (uint32_t)(uintptr_t) param4; + statusCode = (((uint64_t)tracePointPCI) << 32) | tracePointPhases; + if ((tracePointPhases >> 24) != kIOPMTracePointSystemUp) + { + MSG("Sleep failure code 0x%08x 0x%08x\n", + tracePointPCI, tracePointPhases); + } + setProperty(kIOPMSleepWakeFailureCodeKey, statusCode, 64); + pmTracer->tracePointHandler( pmTracer->tracePointTarget, 0, 0 ); + + return kIOReturnSuccess; + } +#if HIBERNATION + else if (functionName && + functionName->isEqualTo(kIOPMInstallSystemSleepPolicyHandlerKey)) + { + if (gSleepPolicyHandler) + return kIOReturnExclusiveAccess; + if (!param1) + return kIOReturnBadArgument; + gSleepPolicyHandler = (IOPMSystemSleepPolicyHandler) param1; + gSleepPolicyTarget = (void *) param2; + setProperty("IOPMSystemSleepPolicyHandler", kOSBooleanTrue); + return kIOReturnSuccess; + } +#endif + + return super::callPlatformFunction( + functionName, waitForFunction, param1, param2, param3, param4); +} + +void IOPMrootDomain::tracePoint( uint8_t point ) { - DLOG("askChangeDown %u->%u, R-state %u\n", - (uint32_t) getPowerState(), (uint32_t) stateNum, runStateIndex); - DLOG("kIOMessageCanSystemSleep (%d)\n", gMessageClientType); + if (systemBooting) return; + + if (kIOPMTracePointWakeCapabilityClients == point) + acceptSystemWakeEvents(false); - return super::tellClientsWithResponse( - kIOMessageCanSystemSleep, - clientMessageFilter); + PMDebug(kPMLogSleepWakeTracePoint, point, 0); + pmTracer->tracePoint(point); } +void IOPMrootDomain::tracePoint( uint8_t point, uint8_t data ) +{ + if (systemBooting) return; -//****************************************************************************** -// tellNoChangeDown -// -// Notify registered applications and kernel clients that we are not dropping -// power. -// -// 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. -//****************************************************************************** + PMDebug(kPMLogSleepWakeTracePoint, point, data); + pmTracer->tracePoint(point, data); +} + +void IOPMrootDomain::traceDetail( uint32_t detail ) +{ + if (!systemBooting) + pmTracer->traceDetail( detail ); +} + + +IOReturn IOPMrootDomain::configureReport(IOReportChannelList *channelList, + IOReportConfigureAction action, + void *result, + void *destination) +{ + unsigned cnt; + if (action != kIOReportGetDimensions) goto exit; + + for (cnt = 0; cnt < channelList->nchannels; cnt++) { + if ( (channelList->channels[cnt].channel_id == kSleepCntChID) || + (channelList->channels[cnt].channel_id == kDarkWkCntChID) || + (channelList->channels[cnt].channel_id == kUserWkCntChID) ) { + SIMPLEREPORT_UPDATERES(kIOReportGetDimensions, result); + } + } + +exit: + return super::configureReport(channelList, action, result, destination); +} -void IOPMrootDomain::tellNoChangeDown( unsigned long stateNum ) + +IOReturn IOPMrootDomain::updateReport(IOReportChannelList *channelList, + IOReportUpdateAction action, + void *result, + void *destination) { - DLOG("tellNoChangeDown %u->%u, R-state %u\n", - (uint32_t) getPowerState(), (uint32_t) stateNum, runStateIndex); + uint32_t size2cpy; + void *data2cpy; + uint8_t buf[SIMPLEREPORT_BUFSIZE]; + IOBufferMemoryDescriptor *dest = OSDynamicCast(IOBufferMemoryDescriptor, (OSObject *)destination); + unsigned cnt; + uint64_t ch_id; - // Sleep canceled, clear the sleep trace point. - tracePoint(kIOPMTracePointSystemUp); + if (action != kIOReportCopyChannelData) goto exit; - if (idleSeconds && !wrangler) - { - // stay awake for at least idleSeconds - sleepASAP = false; - startIdleSleepTimer(idleSeconds); + for (cnt = 0; cnt < channelList->nchannels; cnt++) { + ch_id = channelList->channels[cnt].channel_id ; + + if ((ch_id == kSleepCntChID) || + (ch_id == kDarkWkCntChID) || (ch_id == kUserWkCntChID)) { + SIMPLEREPORT_INIT(buf, sizeof(buf), getRegistryEntryID(), ch_id, kIOReportCategoryPower); + } + else continue; + + if (ch_id == kSleepCntChID) + SIMPLEREPORT_SETVALUE(buf, sleepCnt); + else if (ch_id == kDarkWkCntChID) + SIMPLEREPORT_SETVALUE(buf, darkWakeCnt); + else if (ch_id == kUserWkCntChID) + SIMPLEREPORT_SETVALUE(buf, displayWakeCnt); + + SIMPLEREPORT_UPDATEPREP(buf, data2cpy, size2cpy); + SIMPLEREPORT_UPDATERES(kIOReportCopyChannelData, result); + dest->appendBytes(data2cpy, size2cpy); } - DLOG("kIOMessageSystemWillNotSleep (%d)\n", gMessageClientType); - return tellClients(kIOMessageSystemWillNotSleep, clientMessageFilter); + +exit: + return super::updateReport(channelList, action, result, destination); } //****************************************************************************** -// tellChangeUp -// -// Notify registered applications and kernel clients that we are raising power. +// PMTraceWorker Class // -// We override the superclass implementation so we can send a different message -// type to the client or application being notified. //****************************************************************************** -void IOPMrootDomain::tellChangeUp( unsigned long stateNum ) -{ - OSData *publishPMStats = NULL; +#undef super +#define super OSObject +OSDefineMetaClassAndStructors(PMTraceWorker, OSObject) - DLOG("tellChangeUp %u->%u, R-state %u\n", - (uint32_t) getPowerState(), (uint32_t) stateNum, runStateIndex); +#define kPMBestGuessPCIDevicesCount 25 +#define kPMMaxRTCBitfieldSize 32 - ignoreChangeDown = false; +PMTraceWorker *PMTraceWorker::tracer(IOPMrootDomain *owner) +{ + PMTraceWorker *me; - if ( stateNum == ON_STATE ) + me = OSTypeAlloc( PMTraceWorker ); + if (!me || !me->init()) { - // Direct callout into OSKext so it can disable kext unloads - // during sleep/wake to prevent deadlocks. - OSKextSystemSleepOrWake( kIOMessageSystemHasPoweredOn ); - - if (getPowerState() == ON_STATE) - { - // this is a quick wake from aborted sleep - if (idleSeconds && !wrangler) - { - // stay awake for at least idleSeconds - sleepASAP = false; - startIdleSleepTimer(idleSeconds); - } - DLOG("kIOMessageSystemWillPowerOn (%d)\n", gMessageClientType); - tellClients(kIOMessageSystemWillPowerOn, clientMessageFilter); - } -#if HIBERNATION - else - { - IOHibernateSystemPostWake(); - } -#endif - - tracePoint(kIOPMTracePointSystemWakeAppsPhase); - publishPMStats = OSData::withBytes(&pmStats, sizeof(pmStats)); - setProperty(kIOPMSleepStatisticsKey, publishPMStats); - publishPMStats->release(); - bzero(&pmStats, sizeof(pmStats)); + return NULL; + } - if (pmStatsAppResponses) - { - setProperty(kIOPMSleepStatisticsAppsKey, pmStatsAppResponses); - pmStatsAppResponses->release(); - pmStatsAppResponses = OSArray::withCapacity(5); - } - - DLOG("kIOMessageSystemHasPoweredOn (%d)\n", gMessageClientType); - tellClients(kIOMessageSystemHasPoweredOn, clientMessageFilter); + DLOG("PMTraceWorker %p\n", OBFUSCATE(me)); - tracePoint(kIOPMTracePointSystemUp); - } + // Note that we cannot instantiate the PCI device -> bit mappings here, since + // the IODeviceTree has not yet been created by IOPlatformExpert. We create + // this dictionary lazily. + me->owner = owner; + me->pciDeviceBitMappings = NULL; + me->pciMappingLock = IOLockAlloc(); + me->tracePhase = kIOPMTracePointSystemUp; + me->loginWindowPhase = 0; + me->traceData32 = 0; + return me; } +void PMTraceWorker::RTC_TRACE(void) +{ + if (tracePointHandler && tracePointTarget) + { + uint32_t wordA; -//****************************************************************************** -// reportUserInput -// -//****************************************************************************** + wordA = (tracePhase << 24) | (loginWindowPhase << 16) | + (traceData8 << 8); -void IOPMrootDomain::reportUserInput( void ) + tracePointHandler( tracePointTarget, traceData32, wordA ); + _LOG("RTC_TRACE wrote 0x%08x 0x%08x\n", traceData32, wordA); + } +} + +int PMTraceWorker::recordTopLevelPCIDevice(IOService * pciDevice) { -#if !NO_KERNEL_HID - OSIterator * iter; + const OSSymbol * deviceName; + int index = -1; - if(!wrangler) + IOLockLock(pciMappingLock); + + if (!pciDeviceBitMappings) { - iter = getMatchingServices(serviceMatching("IODisplayWrangler")); - if(iter) - { - wrangler = (IOService *) iter->getNextObject(); - iter->release(); - } + pciDeviceBitMappings = OSArray::withCapacity(kPMBestGuessPCIDevicesCount); + if (!pciDeviceBitMappings) + goto exit; } - if(wrangler) - wrangler->activityTickle(0,0); -#endif -} + // Check for bitmask overflow. + if (pciDeviceBitMappings->getCount() >= kPMMaxRTCBitfieldSize) + goto exit; + if ((deviceName = pciDevice->copyName()) && + (pciDeviceBitMappings->getNextIndexOfObject(deviceName, 0) == (unsigned int)-1) && + pciDeviceBitMappings->setObject(deviceName)) + { + index = pciDeviceBitMappings->getCount() - 1; + _LOG("PMTrace PCI array: set object %s => %d\n", + deviceName->getCStringNoCopy(), index); + } + if (deviceName) + deviceName->release(); + if (!addedToRegistry && (index >= 0)) + addedToRegistry = owner->setProperty("PCITopLevel", this); -//****************************************************************************** -// setQuickSpinDownTimeout -// -//****************************************************************************** +exit: + IOLockUnlock(pciMappingLock); + return index; +} -void IOPMrootDomain::setQuickSpinDownTimeout( void ) +bool PMTraceWorker::serialize(OSSerialize *s) const { - ASSERT_GATED(); - setAggressiveness( - kPMMinutesToSpinDown, 0, kAggressivesOptionQuickSpindownEnable ); + bool ok = false; + if (pciDeviceBitMappings) + { + IOLockLock(pciMappingLock); + ok = pciDeviceBitMappings->serialize(s); + IOLockUnlock(pciMappingLock); + } + return ok; } +void PMTraceWorker::tracePoint(uint8_t phase) +{ + // clear trace detail when phase begins + if (tracePhase != phase) + traceData32 = 0; + + tracePhase = phase; -//****************************************************************************** -// restoreUserSpinDownTimeout -// -//****************************************************************************** + DLOG("trace point 0x%02x\n", tracePhase); + RTC_TRACE(); +} -void IOPMrootDomain::restoreUserSpinDownTimeout( void ) +void PMTraceWorker::tracePoint(uint8_t phase, uint8_t data8) { - ASSERT_GATED(); - setAggressiveness( - kPMMinutesToSpinDown, 0, kAggressivesOptionQuickSpindownDisable ); + // clear trace detail when phase begins + if (tracePhase != phase) + traceData32 = 0; + + tracePhase = phase; + traceData8 = data8; + + DLOG("trace point 0x%02x 0x%02x\n", tracePhase, traceData8); + RTC_TRACE(); } +void PMTraceWorker::traceDetail(uint32_t detail) +{ + if (kIOPMTracePointSleepPriorityClients != tracePhase) + return; -//****************************************************************************** -// changePowerStateTo & changePowerStateToPriv -// -// Override of these methods for logging purposes. -//****************************************************************************** + traceData32 = detail; + DLOG("trace point 0x%02x detail 0x%08x\n", tracePhase, traceData32); -IOReturn IOPMrootDomain::changePowerStateTo( unsigned long ordinal ) + RTC_TRACE(); +} + +void PMTraceWorker::traceLoginWindowPhase(uint8_t phase) { - return kIOReturnUnsupported; // ignored + loginWindowPhase = phase; + + DLOG("loginwindow tracepoint 0x%02x\n", loginWindowPhase); + RTC_TRACE(); } -IOReturn IOPMrootDomain::changePowerStateToPriv( unsigned long ordinal ) +void PMTraceWorker::tracePCIPowerChange( + change_t type, IOService *service, uint32_t changeFlags, uint32_t bitNum) { - DLOG("changePowerStateToPriv(%lu)\n", ordinal); + uint32_t bitMask; + uint32_t expectedFlag; - if ( (getPowerState() == DOZE_STATE) && (ordinal != ON_STATE) ) - { - return kIOReturnSuccess; - } + // Ignore PCI changes outside of system sleep/wake. + if ((kIOPMTracePointSleepPowerPlaneDrivers != tracePhase) && + (kIOPMTracePointWakePowerPlaneDrivers != tracePhase)) + return; - if ( (userDisabledAllSleep || systemBooting || systemShutdown) && - (ordinal == SLEEP_STATE) ) + // Only record the WillChange transition when going to sleep, + // and the DidChange on the way up. + changeFlags &= (kIOPMDomainWillChange | kIOPMDomainDidChange); + expectedFlag = (kIOPMTracePointSleepPowerPlaneDrivers == tracePhase) ? + kIOPMDomainWillChange : kIOPMDomainDidChange; + if (changeFlags != expectedFlag) + return; + + // Mark this device off in our bitfield + if (bitNum < kPMMaxRTCBitfieldSize) { - DLOG("SLEEP rejected, forced to ON state (UD %d, SB %d, SS %d)\n", - userDisabledAllSleep, systemBooting, systemShutdown); + bitMask = (1 << bitNum); - super::changePowerStateToPriv(ON_STATE); - } + if (kPowerChangeStart == type) + { + traceData32 |= bitMask; + _LOG("PMTrace: Device %s started - bit %2d mask 0x%08x => 0x%08x\n", + service->getName(), bitNum, bitMask, traceData32); + } + else + { + traceData32 &= ~bitMask; + _LOG("PMTrace: Device %s finished - bit %2d mask 0x%08x => 0x%08x\n", + service->getName(), bitNum, bitMask, traceData32); + } - return super::changePowerStateToPriv(ordinal); + DLOG("trace point 0x%02x detail 0x%08x\n", tracePhase, traceData32); + RTC_TRACE(); + } } - -//****************************************************************************** -// updateRunState -// -//****************************************************************************** - -void IOPMrootDomain::updateRunState( uint32_t inRunState ) +uint64_t PMTraceWorker::getPMStatusCode( ) { -#if ROOT_DOMAIN_RUN_STATES - if (inRunState < kRStateCount) - { - runStateIndex = nextRunStateIndex = inRunState; - runStateFlags = gRStateFlags[inRunState]; + return (((uint64_t)traceData32 << 32) | (tracePhase << 24) | + (loginWindowPhase << 16) | (traceData8 << 8)); - setProperty( - kIOPMRootDomainRunStateKey, - (unsigned long long) inRunState, 32); - } -#endif } +// MARK: - +// MARK: PMHaltWorker -#if ROOT_DOMAIN_RUN_STATES //****************************************************************************** -// tagPowerPlaneService +// PMHaltWorker Class // -// Running on PM work loop thread. //****************************************************************************** -void IOPMrootDomain::tagPowerPlaneService( - IOService * service, - uint32_t * rdFlags ) +static unsigned int gPMHaltBusyCount; +static unsigned int gPMHaltIdleCount; +static int gPMHaltDepth; +static unsigned long gPMHaltEvent; +static IOLock * gPMHaltLock = 0; +static OSArray * gPMHaltArray = 0; +static const OSSymbol * gPMHaltClientAcknowledgeKey = 0; + +PMHaltWorker * PMHaltWorker::worker( void ) { - *rdFlags = 0; + PMHaltWorker * me; + IOThread thread; - if (service->getProperty("IOPMStrictTreeOrder") || - service->metaCast("IODisplayWrangler") || - OSDynamicCast(OSNumber, - service->getProperty("IOPMUnattendedWakePowerState"))) - { - *rdFlags |= kServiceFlagGraphics; - DLOG("tagged device %s %x\n", service->getName(), *rdFlags); - } + do { + me = OSTypeAlloc( PMHaltWorker ); + if (!me || !me->init()) + break; - // Locate the first PCI host bridge. - if (!pciHostBridgeDevice && service->metaCast("IOPCIBridge")) - { - IOService * provider = service->getProvider(); - if (OSDynamicCast(IOPlatformDevice, provider) && - provider->inPlane(gIODTPlane)) - { - pciHostBridgeDevice = provider; - DLOG("PMTrace found PCI host bridge %s->%s\n", - provider->getName(), service->getName()); - } - } + me->lock = IOLockAlloc(); + if (!me->lock) + break; - // Tag top-level PCI devices. The order of PMinit() call does not - // change across boots and is used as the PCI bit number. - if (pciHostBridgeDevice && service->metaCast("IOPCIDevice")) - { - // Would prefer to check built-in property, but tagPowerPlaneService() - // is called before pciDevice->registerService(). - IORegistryEntry * parent = service->getParentEntry(gIODTPlane); - if ((parent == pciHostBridgeDevice) && service->getProperty("acpi-device")) + DLOG("PMHaltWorker %p\n", OBFUSCATE(me)); + me->retain(); // thread holds extra retain + if (KERN_SUCCESS != kernel_thread_start(&PMHaltWorker::main, (void *) me, &thread)) { - int bit = pmTracer->recordTopLevelPCIDevice( service ); - if (bit >= 0) - { - // Save the assigned bit for fast lookup. - bit &= 0xff; - *rdFlags |= (kServiceFlagTopLevelPCI | (bit << 8)); - } + me->release(); + break; } - } -} + thread_deallocate(thread); + return me; + } while (false); -//****************************************************************************** -// handleActivityTickleForService -// -// Called by IOService::activityTickle() for a tickle that is requesting the -// service to raise power state. Called from driver thread. -//****************************************************************************** + if (me) me->release(); + return 0; +} -void IOPMrootDomain::handleActivityTickleForService( IOService * service ) +void PMHaltWorker::free( void ) { - // Tickle directed to IODisplayWrangler while graphics is disabled. - // Bring graphics online. - - if ((service == wrangler) && - (runStateIndex > kRStateNormal) && - (false == wranglerTickled)) + DLOG("PMHaltWorker free %p\n", OBFUSCATE(this)); + if (lock) { - DLOG("display wrangler tickled\n"); - wranglerTickled = true; - synchronizePowerTree(); + IOLockFree(lock); + lock = 0; } + return OSObject::free(); } +void PMHaltWorker::main( void * arg, wait_result_t waitResult ) +{ + PMHaltWorker * me = (PMHaltWorker *) arg; + + IOLockLock( gPMHaltLock ); + gPMHaltBusyCount++; + me->depth = gPMHaltDepth; + IOLockUnlock( gPMHaltLock ); -//****************************************************************************** -// handlePowerChangeStartForService -// -// Running on PM work loop thread. -//****************************************************************************** + while (me->depth >= 0) + { + PMHaltWorker::work( me ); -void IOPMrootDomain::handlePowerChangeStartForService( - IOService * service, - uint32_t * rdFlags, - uint32_t newPowerState, - uint32_t changeFlags ) + IOLockLock( gPMHaltLock ); + if (++gPMHaltIdleCount >= gPMHaltBusyCount) + { + // This is the last thread to finish work on this level, + // inform everyone to start working on next lower level. + gPMHaltDepth--; + me->depth = gPMHaltDepth; + gPMHaltIdleCount = 0; + thread_wakeup((event_t) &gPMHaltIdleCount); + } + else + { + // One or more threads are still working on this level, + // this thread must wait. + me->depth = gPMHaltDepth - 1; + do { + IOLockSleep(gPMHaltLock, &gPMHaltIdleCount, THREAD_UNINT); + } while (me->depth != gPMHaltDepth); + } + IOLockUnlock( gPMHaltLock ); + } + + // No more work to do, terminate thread + DLOG("All done for worker: %p (visits = %u)\n", OBFUSCATE(me), me->visits); + thread_wakeup( &gPMHaltDepth ); + me->release(); +} + +void PMHaltWorker::work( PMHaltWorker * me ) { - if (service == this) + IOService * service; + OSSet * inner; + AbsoluteTime startTime; + UInt32 deltaTime; + bool timeout; + + while (true) { - uint32_t currentPowerState = (uint32_t) getPowerState(); - uint32_t nextRunStateFlags; + service = 0; + timeout = false; + + // Claim an unit of work from the shared pool + IOLockLock( gPMHaltLock ); + inner = (OSSet *)gPMHaltArray->getObject(me->depth); + if (inner) + { + service = (IOService *)inner->getAnyObject(); + if (service) + { + service->retain(); + inner->removeObject(service); + } + } + IOLockUnlock( gPMHaltLock ); + if (!service) + break; // no more work at this depth + + clock_get_uptime(&startTime); - assert(nextRunStateIndex < kRStateCount); - nextRunStateFlags = gRStateFlags[nextRunStateIndex]; + if (!service->isInactive() && + service->setProperty(gPMHaltClientAcknowledgeKey, me)) + { + IOLockLock(me->lock); + me->startTime = startTime; + me->service = service; + me->timeout = false; + IOLockUnlock(me->lock); - gMessageClientType = kMessageClientNone; + service->systemWillShutdown( gPMHaltEvent ); - // Transition towards or away from ON power state. + // Wait for driver acknowledgement + IOLockLock(me->lock); + while (service->getProperty(gPMHaltClientAcknowledgeKey)) + { + IOLockSleep(me->lock, me, THREAD_UNINT); + } + me->service = 0; + timeout = me->timeout; + IOLockUnlock(me->lock); + } - if ((currentPowerState != newPowerState) && - ((ON_STATE == newPowerState) || (ON_STATE == currentPowerState))) + deltaTime = computeDeltaTimeMS(&startTime); + if ((deltaTime > kPMHaltTimeoutMS) || timeout || + (gIOKitDebug & kIOLogPMRootDomain)) { - if ((runStateFlags & kRStateFlagSuppressMessages) == 0) - gMessageClientType = kMessageClientAll; - else - gMessageClientType = kMessageClientConfigd; + LOG("%s driver %s (%p) took %u ms\n", + (gPMHaltEvent == kIOMessageSystemWillPowerOff) ? + "PowerOff" : "Restart", + service->getName(), OBFUSCATE(service), + (uint32_t) deltaTime ); } - // Transition caused by deassertion of system notification suppression. + service->release(); + me->visits++; + } +} + +void PMHaltWorker::checkTimeout( PMHaltWorker * me, AbsoluteTime * now ) +{ + UInt64 nano; + AbsoluteTime startTime; + AbsoluteTime endTime; + + endTime = *now; - if ((ON_STATE == newPowerState) && - (ON_STATE == currentPowerState) && - ((runStateFlags ^ nextRunStateFlags) & kRStateFlagSuppressMessages)) + IOLockLock(me->lock); + if (me->service && !me->timeout) + { + startTime = me->startTime; + nano = 0; + if (CMP_ABSOLUTETIME(&endTime, &startTime) > 0) { - gMessageClientType = kMessageClientAll; + SUB_ABSOLUTETIME(&endTime, &startTime); + absolutetime_to_nanoseconds(endTime, &nano); } - - if (ON_STATE == newPowerState) + if (nano > 3000000000ULL) { - DLOG("kIOMessageSystemWillPowerOn (%d)\n", - gMessageClientType); - tellClients(kIOMessageSystemWillPowerOn, clientMessageFilter); + me->timeout = true; + MSG("%s still waiting on %s\n", + (gPMHaltEvent == kIOMessageSystemWillPowerOff) ? + "PowerOff" : "Restart", + me->service->getName()); } } - - if (*rdFlags & kServiceFlagTopLevelPCI) - { - pmTracer->tracePCIPowerChange( - PMTraceWorker::kPowerChangeStart, - service, changeFlags, - (*rdFlags >> 8) & 0xff); - } + IOLockUnlock(me->lock); } //****************************************************************************** -// handlePowerChangeDoneForService +// acknowledgeSystemWillShutdown // -// Running on PM work loop thread. +// Acknowledgement from drivers that they have prepared for shutdown/restart. //****************************************************************************** -void IOPMrootDomain::handlePowerChangeDoneForService( - IOService * service, - uint32_t * rdFlags, - uint32_t newPowerState, - uint32_t changeFlags ) +void IOPMrootDomain::acknowledgeSystemWillShutdown( IOService * from ) { - if (*rdFlags & kServiceFlagTopLevelPCI) + PMHaltWorker * worker; + OSObject * prop; + + if (!from) + return; + + //DLOG("%s acknowledged\n", from->getName()); + prop = from->copyProperty( gPMHaltClientAcknowledgeKey ); + if (prop) + { + worker = (PMHaltWorker *) prop; + IOLockLock(worker->lock); + from->removeProperty( gPMHaltClientAcknowledgeKey ); + thread_wakeup((event_t) worker); + IOLockUnlock(worker->lock); + worker->release(); + } + else { - pmTracer->tracePCIPowerChange( - PMTraceWorker::kPowerChangeCompleted, - service, changeFlags, - (*rdFlags >> 8) & 0xff); + DLOG("%s acknowledged without worker property\n", + from->getName()); } } //****************************************************************************** -// overridePowerStateForService +// notifySystemShutdown // -// Runs on PM work loop thread. +// Notify all objects in PM tree that system will shutdown or restart //****************************************************************************** -void IOPMrootDomain::overridePowerStateForService( - IOService * service, - uint32_t * rdFlags, - unsigned long * powerState, - uint32_t changeFlags ) +static void +notifySystemShutdown( IOService * root, unsigned long event ) { - uint32_t inPowerState = (uint32_t) *powerState; - - if ((service == this) && (inPowerState == ON_STATE) && - (changeFlags & kIOPMSynchronize)) +#define PLACEHOLDER ((OSSet *)gPMHaltArray) + IORegistryIterator * iter; + IORegistryEntry * entry; + IOService * node; + OSSet * inner; + PMHaltWorker * workers[kPMHaltMaxWorkers]; + AbsoluteTime deadline; + unsigned int totalNodes = 0; + unsigned int depth; + unsigned int rootDepth; + unsigned int numWorkers; + unsigned int count; + int waitResult; + void * baseFunc; + bool ok; + + DLOG("%s event = %lx\n", __FUNCTION__, event); + + baseFunc = OSMemberFunctionCast(void *, root, &IOService::systemWillShutdown); + + // Iterate the entire PM tree starting from root + + rootDepth = root->getDepth( gIOPowerPlane ); + if (!rootDepth) goto done; + + // debug - for repeated test runs + while (PMHaltWorker::metaClass->getInstanceCount()) + IOSleep(1); + + if (!gPMHaltArray) { - DLOG("sync root domain %u->%u\n", - (uint32_t) getPowerState(), inPowerState); - - // Root Domain is in a reduced R-state, and a HID tickle has - // requested a PM tree sync. Begin R-state transition. + gPMHaltArray = OSArray::withCapacity(40); + if (!gPMHaltArray) goto done; + } + else // debug + gPMHaltArray->flushCollection(); - if (runStateIndex != kRStateNormal) - { - nextRunStateIndex = kRStateNormal; - setProperty( - kIOPMRootDomainRunStateKey, - (unsigned long long) kRStateNormal, 32); - } + if (!gPMHaltLock) + { + gPMHaltLock = IOLockAlloc(); + if (!gPMHaltLock) goto done; } - if (*rdFlags & kServiceFlagGraphics) + if (!gPMHaltClientAcknowledgeKey) { - DLOG("graphics device %s %u->%u (flags 0x%x)\n", - service->getName(), (uint32_t) service->getPowerState(), - inPowerState, changeFlags); + gPMHaltClientAcknowledgeKey = + OSSymbol::withCStringNoCopy("PMShutdown"); + if (!gPMHaltClientAcknowledgeKey) goto done; + } - if (inPowerState == 0) - { - // Graphics device is powering down, apply limit preventing - // device from powering back up later unless we consent. + gPMHaltEvent = event; - if ((*rdFlags & kServiceFlagNoPowerUp) == 0) - { - *rdFlags |= kServiceFlagNoPowerUp; - DLOG("asserted power limit for %s\n", - service->getName()); - } - } - else + // Depth-first walk of PM plane + + iter = IORegistryIterator::iterateOver( + root, gIOPowerPlane, kIORegistryIterateRecursively); + + if (iter) + { + while ((entry = iter->getNextObject())) { - uint32_t nextRunStateFlags; + node = OSDynamicCast(IOService, entry); + if (!node) + continue; + + if (baseFunc == + OSMemberFunctionCast(void *, node, &IOService::systemWillShutdown)) + continue; + + depth = node->getDepth( gIOPowerPlane ); + if (depth <= rootDepth) + continue; - assert(nextRunStateIndex < kRStateCount); - nextRunStateFlags = gRStateFlags[nextRunStateIndex]; - - // Graphics device is powering up. Release power limit at the - // did-change machine state. + ok = false; - if (changeFlags & kIOPMSynchronize) + // adjust to zero based depth + depth -= (rootDepth + 1); + + // gPMHaltArray is an array of containers, each container + // refers to nodes with the same depth. + + count = gPMHaltArray->getCount(); + while (depth >= count) { - if ((runStateFlags & kRStateFlagSuppressGraphics) && - ((nextRunStateFlags & kRStateFlagSuppressGraphics) == 0) && - (changeFlags & kIOPMDomainDidChange)) - { - // Woke up without graphics power, but - // HID event has tickled display wrangler. - *rdFlags &= ~kServiceFlagNoPowerUp; - DLOG("removed power limit for %s\n", - service->getName()); - } + // expand array and insert placeholders + gPMHaltArray->setObject(PLACEHOLDER); + count++; } - else if ((runStateFlags & kRStateFlagSuppressGraphics) == 0) + count = gPMHaltArray->getCount(); + if (depth < count) { - *rdFlags &= ~kServiceFlagNoPowerUp; - } + inner = (OSSet *)gPMHaltArray->getObject(depth); + if (inner == PLACEHOLDER) + { + inner = OSSet::withCapacity(40); + if (inner) + { + gPMHaltArray->replaceObject(depth, inner); + inner->release(); + } + } - if (*rdFlags & kServiceFlagNoPowerUp) - { - DLOG("limited %s to power state 0\n", - service->getName()); - *powerState = 0; + // PM nodes that appear more than once in the tree will have + // the same depth, OSSet will refuse to add the node twice. + if (inner) + ok = inner->setObject(node); } + if (!ok) + DLOG("Skipped PM node %s\n", node->getName()); } + iter->release(); } -} + // debug only + for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); i++) + { + count = 0; + if (inner != PLACEHOLDER) + count = inner->getCount(); + DLOG("Nodes at depth %u = %u\n", i, count); + } -//****************************************************************************** -// setMaintenanceWakeCalendar -// -//****************************************************************************** - -IOReturn IOPMrootDomain::setMaintenanceWakeCalendar( - const IOPMCalendarStruct * calendar ) -{ - OSData * data; - IOReturn ret; + // strip placeholders (not all depths are populated) + numWorkers = 0; + for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); ) + { + if (inner == PLACEHOLDER) + { + gPMHaltArray->removeObject(i); + continue; + } + count = inner->getCount(); + if (count > numWorkers) + numWorkers = count; + totalNodes += count; + i++; + } - if (!calendar) - return kIOReturnBadArgument; - - data = OSData::withBytesNoCopy((void *) calendar, sizeof(*calendar)); - if (!data) - return kIOReturnNoMemory; - - ret = setPMSetting(gIOPMSettingMaintenanceWakeCalendarKey, data); + if (gPMHaltArray->getCount() == 0 || !numWorkers) + goto done; - data->release(); - return ret; -} -#endif /* ROOT_DOMAIN_RUN_STATES */ + gPMHaltBusyCount = 0; + gPMHaltIdleCount = 0; + gPMHaltDepth = gPMHaltArray->getCount() - 1; + // Create multiple workers (and threads) -//****************************************************************************** -// 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. -//****************************************************************************** + if (numWorkers > kPMHaltMaxWorkers) + numWorkers = kPMHaltMaxWorkers; -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); + DLOG("PM nodes %u, maxDepth %u, workers %u\n", + totalNodes, gPMHaltArray->getCount(), numWorkers); - DLOG("sysPowerDownHandler message %x\n", (uint32_t) messageType); + for (unsigned int i = 0; i < numWorkers; i++) + workers[i] = PMHaltWorker::worker(); - if(!rootDomain) - return kIOReturnUnsupported; + // Wait for workers to exhaust all available work - switch (messageType) { - case kIOMessageSystemWillSleep: - // 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 + IOLockLock(gPMHaltLock); + while (gPMHaltDepth >= 0) + { + clock_interval_to_deadline(1000, kMillisecondScale, &deadline); - // We will ack within 20 seconds - params->returnValue = 20 * 1000 * 1000; -#if HIBERNATION - if (gIOHibernateState) - params->returnValue += gIOHibernateFreeTime * 1000; //add in time we could spend freeing pages -#endif + waitResult = IOLockSleepDeadline( + gPMHaltLock, &gPMHaltDepth, deadline, THREAD_UNINT); + if (THREAD_TIMED_OUT == waitResult) + { + AbsoluteTime now; + clock_get_uptime(&now); - if ( ! OSCompareAndSwap( 0, 1, &gSleepOrShutdownPending ) ) + IOLockUnlock(gPMHaltLock); + for (unsigned int i = 0 ; i < numWorkers; i++) { - // 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( 30, kSecondScale, &deadline ); - thread_call_enter1_delayed( rootDomain->diskSyncCalloutEntry, - (thread_call_param_t)params->powerRef, - deadline ); + if (workers[i]) + PMHaltWorker::checkTimeout(workers[i], &now); } - else - thread_call_enter1(rootDomain->diskSyncCalloutEntry, (thread_call_param_t)params->powerRef); - ret = kIOReturnSuccess; - break; + IOLockLock(gPMHaltLock); + } + } + IOLockUnlock(gPMHaltLock); - case kIOMessageSystemWillPowerOff: - case kIOMessageSystemWillRestart: - ret = kIOReturnUnsupported; - break; + // Release all workers - default: - ret = kIOReturnUnsupported; - break; + for (unsigned int i = 0; i < numWorkers; i++) + { + if (workers[i]) + workers[i]->release(); + // worker also retained by it's own thread } - return ret; + +done: + DLOG("%s done\n", __FUNCTION__); + return; } -//****************************************************************************** -// publishSleepWakeUUID -// -// -//****************************************************************************** -void IOPMrootDomain::publishSleepWakeUUID( bool shouldPublish ) +// MARK: - +// MARK: Kernel Assertion + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +IOPMDriverAssertionID IOPMrootDomain::createPMAssertion( + IOPMDriverAssertionType whichAssertionBits, + IOPMDriverAssertionLevel assertionLevel, + IOService *ownerService, + const char *ownerDescription) { - if (shouldPublish) - { - if (queuedSleepWakeUUIDString) - { - if (OSCompareAndSwap(/*old*/ true, /*new*/ false, &gSleepWakeUUIDIsSet)) - { - // Upon wake, it takes some time for userland to invalidate the - // UUID. If another sleep is initiated during that period, force - // a CLEAR message to balance the upcoming SET message. + IOReturn ret; + IOPMDriverAssertionID newAssertion; - messageClients( kIOPMMessageSleepWakeUUIDChange, - kIOPMMessageSleepWakeUUIDCleared ); + if (!pmAssertions) + return 0; - DLOG("SleepWake UUID forced clear\n"); - } + ret = pmAssertions->createAssertion(whichAssertionBits, assertionLevel, ownerService, ownerDescription, &newAssertion); - setProperty(kIOPMSleepWakeUUIDKey, queuedSleepWakeUUIDString); - DLOG("SleepWake UUID published: %s\n", queuedSleepWakeUUIDString->getCStringNoCopy()); - queuedSleepWakeUUIDString->release(); - queuedSleepWakeUUIDString = NULL; - messageClients(kIOPMMessageSleepWakeUUIDChange, - kIOPMMessageSleepWakeUUIDSet); - } - } else { - if (OSCompareAndSwap(/*old*/ true, /*new*/ false, &gSleepWakeUUIDIsSet)) - { - DLOG("SleepWake UUID cleared\n"); - removeProperty(kIOPMSleepWakeUUIDKey); - messageClients(kIOPMMessageSleepWakeUUIDChange, - kIOPMMessageSleepWakeUUIDCleared); - } - } + if (kIOReturnSuccess == ret) + return newAssertion; + else + return 0; } +IOReturn IOPMrootDomain::releasePMAssertion(IOPMDriverAssertionID releaseAssertion) +{ + if (!pmAssertions) + return kIOReturnInternalError; -//****************************************************************************** -// displayWranglerNotification -// -// Receives a notification when the IODisplayWrangler changes state. -// -// Allows us to take action on display dim/undim. -// -// When the display sleeps we: -// - Start the idle sleep timer -// - set the quick spin down timeout -// -// On wake from display sleep: -// - Cancel the idle sleep timer -// - restore the user's chosen spindown timer from the "quick" spin down value -//****************************************************************************** + return pmAssertions->releaseAssertion(releaseAssertion); +} -IOReturn IOPMrootDomain::displayWranglerNotification( - void * target, void * refCon, - UInt32 messageType, IOService * service, - void * messageArgument, vm_size_t argSize ) + +IOReturn IOPMrootDomain::setPMAssertionLevel( + IOPMDriverAssertionID assertionID, + IOPMDriverAssertionLevel assertionLevel) { -#if !NO_KERNEL_HID - int displayPowerState; - IOPowerStateChangeNotification * params = - (IOPowerStateChangeNotification *) messageArgument; + return pmAssertions->setAssertionLevel(assertionID, assertionLevel); +} - if ((messageType != kIOMessageDeviceWillPowerOff) && - (messageType != kIOMessageDeviceHasPoweredOn)) - return kIOReturnUnsupported; +IOPMDriverAssertionLevel IOPMrootDomain::getPMAssertionLevel(IOPMDriverAssertionType whichAssertion) +{ + IOPMDriverAssertionType sysLevels; - ASSERT_GATED(); - if (!gRootDomain) - return kIOReturnUnsupported; + if (!pmAssertions || whichAssertion == 0) + return kIOPMDriverAssertionLevelOff; - displayPowerState = params->stateNumber; - DLOG("DisplayWrangler message 0x%x, new power state %d\n", - (uint32_t) messageType, displayPowerState); + sysLevels = pmAssertions->getActivatedAssertions(); - switch (messageType) { - case kIOMessageDeviceWillPowerOff: + // Check that every bit set in argument 'whichAssertion' is asserted + // in the aggregate bits. + if ((sysLevels & whichAssertion) == whichAssertion) + return kIOPMDriverAssertionLevelOn; + else + return kIOPMDriverAssertionLevelOff; +} - // The display wrangler has dropped power because of idle display sleep - // or force system sleep. - // - // 4 Display ON - // 3 Display Dim - // 2 Display Sleep - // 1 Not visible to user - // 0 Not visible to user +IOReturn IOPMrootDomain::setPMAssertionUserLevels(IOPMDriverAssertionType inLevels) +{ + if (!pmAssertions) + return kIOReturnNotFound; - if (gRootDomain->wranglerAsleep || (displayPowerState > 2)) - break; + return pmAssertions->setUserAssertionLevels(inLevels); +} - // Record the time the display wrangler went to sleep. +bool IOPMrootDomain::serializeProperties( OSSerialize * s ) const +{ + if (pmAssertions) + { + pmAssertions->publishProperties(); + } + return( IOService::serializeProperties(s) ); +} - gRootDomain->wranglerAsleep = true; - clock_get_uptime(&gRootDomain->wranglerSleepTime); +OSObject * IOPMrootDomain::copyProperty( const char * aKey) const +{ + OSObject *obj = NULL; + obj = IOService::copyProperty(aKey); - // We start a timer here if the System Sleep timer is greater than the - // Display Sleep timer. We kick off this timer when the display sleeps. - // - // Note that, although Display Dim timings may change adaptively accordingly - // to the user's activity patterns, Display Sleep _always_ occurs at the - // specified interval since last user activity. + if (obj) return obj; - if ( gRootDomain->extraSleepDelay ) - { - gRootDomain->startIdleSleepTimer(gRootDomain->extraSleepDelay * 60); - } - else if ( gRootDomain->sleepSlider ) - { - // Accelerate disk spindown if system sleep and display sleep - // sliders are set to the same value (e.g. both set to 5 min), - // and display is about to go dark. Check that spin down timer - // is non-zero (zero = never spin down) and system sleep is - // not set to never sleep. + if (!strncmp(aKey, kIOPMSleepWakeWdogRebootKey, + sizeof(kIOPMSleepWakeWdogRebootKey))) { + if (swd_flags & SWD_BOOT_BY_SW_WDOG) + return OSBoolean::withBoolean(true); + else + return OSBoolean::withBoolean(false); - gRootDomain->setQuickSpinDownTimeout(); - } + } - break; + if (!strncmp(aKey, kIOPMSleepWakeWdogLogsValidKey, + sizeof(kIOPMSleepWakeWdogLogsValidKey))) { + if (swd_flags & SWD_VALID_LOGS) + return OSBoolean::withBoolean(true); + else + return OSBoolean::withBoolean(false); - case kIOMessageDeviceHasPoweredOn: + } - // The display wrangler has powered on either because of user activity - // or wake from sleep/doze. + /* + * XXX: We should get rid of "DesktopMode" property when 'kAppleClamshellCausesSleepKey' + * is set properly in darwake from sleep. For that, kIOPMEnableClamshell msg has to be + * issued by DisplayWrangler on darkwake. + */ + if (!strcmp(aKey, "DesktopMode")) { + if (desktopMode) + return OSBoolean::withBoolean(true); + else + return OSBoolean::withBoolean(false); + } + if (!strcmp(aKey, "DisplayIdleForDemandSleep")) { + if (displayIdleForDemandSleep) { + return OSBoolean::withBoolean(true); + } + else { + return OSBoolean::withBoolean(false); + } + } - if ( 4 != displayPowerState ) - break; + if (!strcmp(aKey, kIOPMDriverWakeEventsKey)) + { + OSArray * array = 0; + WAKEEVENT_LOCK(); + if (_systemWakeEventsArray && _systemWakeEventsArray->getCount()) + array = OSArray::withArray(_systemWakeEventsArray); + WAKEEVENT_UNLOCK(); + return array; + } - gRootDomain->wranglerAsleep = false; - gRootDomain->adjustPowerState(); - gRootDomain->cancelIdleSleepTimer(); + if (!strcmp(aKey, kIOPMSleepStatisticsAppsKey)) + { + OSArray * array = 0; + IOLockLock(pmStatsLock); + if (pmStatsAppResponses && pmStatsAppResponses->getCount()) { + array = OSArray::withArray(pmStatsAppResponses); + pmStatsAppResponses->flushCollection(); + } + IOLockUnlock(pmStatsLock); + return array; + } - // Change the spindown value back to the user's selection from our - // accelerated setting. - gRootDomain->restoreUserSpinDownTimeout(); + return NULL; +} - break; +// MARK: - +// MARK: Wake Event Reporting - default: - break; - } -#endif - return kIOReturnUnsupported; +void IOPMrootDomain::copyWakeReasonString( char * outBuf, size_t bufSize ) +{ + WAKEEVENT_LOCK(); + strlcpy(outBuf, gWakeReasonString, bufSize); + WAKEEVENT_UNLOCK(); } - //****************************************************************************** -// displayWranglerPublished +// acceptSystemWakeEvents // -// Receives a notification when the IODisplayWrangler is published. -// When it's published we install a power state change handler. +// Private control for the acceptance of driver wake event claims. //****************************************************************************** -bool IOPMrootDomain::displayWranglerPublished( - void * target, - void * refCon, - IOService * newService) +void IOPMrootDomain::acceptSystemWakeEvents( bool accept ) { -#if !NO_KERNEL_HID - if(!gRootDomain) - return false; - - gRootDomain->wrangler = newService; + bool logWakeReason = false; - // we found the display wrangler, now install a handler - if( !gRootDomain->wrangler->registerInterest( gIOGeneralInterest, - &displayWranglerNotification, target, 0) ) + WAKEEVENT_LOCK(); + if (accept) { - return false; + gWakeReasonString[0] = '\0'; + if (!_systemWakeEventsArray) + _systemWakeEventsArray = OSArray::withCapacity(4); + if ((_acceptSystemWakeEvents = (_systemWakeEventsArray != 0))) + _systemWakeEventsArray->flushCollection(); } -#endif - return true; -} + else + { + _acceptSystemWakeEvents = false; + } + WAKEEVENT_UNLOCK(); + if (logWakeReason) + MSG("system wake events:%s\n", gWakeReasonString); +} //****************************************************************************** -// batteryPublished +// claimSystemWakeEvent // -// Notification on battery class IOPowerSource appearance +// For a driver to claim a device is the source/conduit of a system wake event. //****************************************************************************** -bool IOPMrootDomain::batteryPublished( - void * target, - void * root_domain, - IOService * resourceService ) -{ - // rdar://2936060&4435589 - // All laptops have dimmable LCD displays - // All laptops have batteries - // So if this machine has a battery, publish the fact that the backlight - // supports dimming. - ((IOPMrootDomain *)root_domain)->publishFeature("DisplayDims"); +void IOPMrootDomain::claimSystemWakeEvent( + IOService * device, + IOOptionBits flags, + const char * reason, + OSObject * details ) +{ + const OSSymbol * deviceName = 0; + OSNumber * deviceRegId = 0; + OSNumber * claimTime = 0; + OSData * flagsData = 0; + OSString * reasonString = 0; + OSDictionary * d = 0; + uint64_t timestamp; + bool ok = false; + + pmEventTimeStamp(×tamp); + + if (!device || !reason) return; + + deviceName = device->copyName(gIOServicePlane); + deviceRegId = OSNumber::withNumber(device->getRegistryEntryID(), 64); + claimTime = OSNumber::withNumber(timestamp, 64); + flagsData = OSData::withBytes(&flags, sizeof(flags)); + reasonString = OSString::withCString(reason); + d = OSDictionary::withCapacity(5 + (details ? 1 : 0)); + if (!deviceName || !deviceRegId || !claimTime || !flagsData || !reasonString) + goto done; + + d->setObject(gIONameKey, deviceName); + d->setObject(gIORegistryEntryIDKey, deviceRegId); + d->setObject(kIOPMWakeEventTimeKey, claimTime); + d->setObject(kIOPMWakeEventFlagsKey, flagsData); + d->setObject(kIOPMWakeEventReasonKey, reasonString); + if (details) + d->setObject(kIOPMWakeEventDetailsKey, details); + + WAKEEVENT_LOCK(); + if (!gWakeReasonSysctlRegistered) + { + // Lazy registration until the platform driver stops registering + // the same name. + gWakeReasonSysctlRegistered = true; + } + if (_acceptSystemWakeEvents) + { + ok = _systemWakeEventsArray->setObject(d); + if (gWakeReasonString[0] != '\0') + strlcat(gWakeReasonString, " ", sizeof(gWakeReasonString)); + strlcat(gWakeReasonString, reason, sizeof(gWakeReasonString)); + } + WAKEEVENT_UNLOCK(); - return (true); +done: + if (deviceName) deviceName->release(); + if (deviceRegId) deviceRegId->release(); + if (claimTime) claimTime->release(); + if (flagsData) flagsData->release(); + if (reasonString) reasonString->release(); + if (d) d->release(); +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// MARK: - +// MARK: PMSettingHandle + +OSDefineMetaClassAndStructors( PMSettingHandle, OSObject ) + +void PMSettingHandle::free( void ) +{ + if (pmso) + { + pmso->clientHandleFreed(); + pmso->release(); + pmso = 0; + } + + OSObject::free(); } +// MARK: - +// MARK: PMSettingObject + +#undef super +#define super OSObject +OSDefineMetaClassAndFinalStructors( PMSettingObject, OSObject ) + +/* + * Static constructor/initializer for PMSettingObject + */ +PMSettingObject *PMSettingObject::pmSettingObject( + IOPMrootDomain *parent_arg, + IOPMSettingControllerCallback handler_arg, + OSObject *target_arg, + uintptr_t refcon_arg, + uint32_t supportedPowerSources, + const OSSymbol * settings[], + OSObject **handle_obj) +{ + uint32_t settingCount = 0; + PMSettingObject *pmso = 0; + PMSettingHandle *pmsh = 0; + + if ( !parent_arg || !handler_arg || !settings || !handle_obj ) + return NULL; -//****************************************************************************** -// 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. -//****************************************************************************** + // count OSSymbol entries in NULL terminated settings array + while (settings[settingCount]) { + settingCount++; + } + if (0 == settingCount) + return NULL; -void IOPMrootDomain::adjustPowerState( void ) -{ - DLOG("adjustPowerState " - "PS %u, ASAP %d, SL %ld, AS %d, SB %d, SS %d, UD %d\n", - (uint32_t) getPowerState(), sleepASAP, sleepSlider, - allowSleep, systemBooting, systemShutdown, userDisabledAllSleep); + pmso = new PMSettingObject; + if (!pmso || !pmso->init()) + goto fail; + + pmsh = new PMSettingHandle; + if (!pmsh || !pmsh->init()) + goto fail; + + queue_init(&pmso->calloutQueue); + pmso->parent = parent_arg; + pmso->func = handler_arg; + pmso->target = target_arg; + pmso->refcon = refcon_arg; + pmso->settingCount = settingCount; + + pmso->retain(); // handle holds a retain on pmso + pmsh->pmso = pmso; + pmso->pmsh = pmsh; + + pmso->publishedFeatureID = (uint32_t *)IOMalloc(sizeof(uint32_t)*settingCount); + if (pmso->publishedFeatureID) { + for (unsigned int i=0; ipublishPMSetting( settings[i], + supportedPowerSources, &pmso->publishedFeatureID[i] ); + } + } - ASSERT_GATED(); + *handle_obj = pmsh; + return pmso; - if ( (sleepSlider == 0) - || !allowSleep - || systemBooting - || systemShutdown - || userDisabledAllSleep - || (runStateFlags & kRStateFlagDisableIdleSleep) ) - { - changePowerStateToPriv(ON_STATE); - } else { - if ( sleepASAP ) - { - /* Convenient place to run any code at idle sleep time - * IOPMrootDomain initiates an idle sleep here - * - * Set last sleep cause accordingly. - */ - setProperty(kRootDomainSleepReasonKey, kIOPMIdleSleepKey); +fail: + if (pmso) pmso->release(); + if (pmsh) pmsh->release(); + return NULL; +} - tracePoint(kIOPMTracePointSleepStarted); - - sleepASAP = false; - changePowerStateToPriv(SLEEP_STATE); +void PMSettingObject::free( void ) +{ + if (publishedFeatureID) { + for (uint32_t i=0; iremovePublishedFeature( publishedFeatureID[i] ); + } } + + IOFree(publishedFeatureID, sizeof(uint32_t) * settingCount); } + + super::free(); } -void IOPMrootDomain::pmStatsRecordEvent( - int eventIndex, - AbsoluteTime timestamp) +void PMSettingObject::dispatchPMSetting( const OSSymbol * type, OSObject * object ) { - bool starting = eventIndex & kIOPMStatsEventStartFlag ? true:false; - bool stopping = eventIndex & kIOPMStatsEventStopFlag ? true:false; - uint64_t delta; - uint64_t nsec; + (*func)(target, type, object, refcon); +} - eventIndex &= ~(kIOPMStatsEventStartFlag | kIOPMStatsEventStopFlag); +void PMSettingObject::clientHandleFreed( void ) +{ + parent->deregisterPMSettingObject(this); +} - absolutetime_to_nanoseconds(timestamp, &nsec); +// MARK: - +// MARK: PMAssertionsTracker - switch (eventIndex) { - case kIOPMStatsHibernateImageWrite: - if (starting) - pmStats.hibWrite.start = nsec; - else if (stopping) - pmStats.hibWrite.stop = nsec; +//********************************************************************************* +//********************************************************************************* +//********************************************************************************* +// class PMAssertionsTracker Implementation - if (stopping) { - delta = pmStats.hibWrite.stop - pmStats.hibWrite.start; - IOLog("PMStats: Hibernate write took %qd ms\n", delta/1000000ULL); - } - break; - case kIOPMStatsHibernateImageRead: - if (starting) - pmStats.hibRead.start = nsec; - else if (stopping) - pmStats.hibRead.stop = nsec; +#define kAssertUniqueIDStart 500 - if (stopping) { - delta = pmStats.hibRead.stop - pmStats.hibRead.start; - IOLog("PMStats: Hibernate read took %qd ms\n", delta/1000000ULL); - } - break; +PMAssertionsTracker *PMAssertionsTracker::pmAssertionsTracker( IOPMrootDomain *rootDomain ) +{ + PMAssertionsTracker *myself; + + myself = new PMAssertionsTracker; + + if (myself) { + myself->init(); + myself->owner = rootDomain; + myself->issuingUniqueID = kAssertUniqueIDStart; + myself->assertionsArray = OSArray::withCapacity(5); + myself->assertionsKernel = 0; + myself->assertionsUser = 0; + myself->assertionsCombined = 0; + myself->assertionsArrayLock = IOLockAlloc(); + myself->tabulateProducerCount = myself->tabulateConsumerCount = 0; + + if (!myself->assertionsArray || !myself->assertionsArrayLock) + myself = NULL; } + + return myself; } -/* - * Appends a record of the application response to - * IOPMrootDomain::pmStatsAppResponses +/* tabulate + * - Update assertionsKernel to reflect the state of all + * assertions in the kernel. + * - Update assertionsCombined to reflect both kernel & user space. */ -void IOPMrootDomain::pmStatsRecordApplicationResponse( - const OSSymbol *response, - const char *name, - int messageType, - uint32_t delay_ms, - int app_pid) +void PMAssertionsTracker::tabulate(void) { - OSDictionary *responseDescription = NULL; - OSNumber *delayNum = NULL; - OSNumber *pidNum = NULL; - OSNumber *msgNum = NULL; - const OSSymbol *appname; - const OSSymbol *entryName; - OSObject *entryType; - int i; + int i; + int count; + PMAssertStruct *_a = NULL; + OSData *_d = NULL; + + IOPMDriverAssertionType oldKernel = assertionsKernel; + IOPMDriverAssertionType oldCombined = assertionsCombined; + + ASSERT_GATED(); - if (!pmStatsAppResponses || pmStatsAppResponses->getCount() > 50) + assertionsKernel = 0; + assertionsCombined = 0; + + if (!assertionsArray) return; - i = 0; - while ((responseDescription = (OSDictionary *) pmStatsAppResponses->getObject(i++))) + if ((count = assertionsArray->getCount())) { - entryType = responseDescription->getObject(_statsResponseTypeKey); - entryName = (OSSymbol *) responseDescription->getObject(_statsNameKey); - if (entryName && (entryType == response) && entryName->isEqualTo(name)) + for (i=0; igetObject(_statsTimeMSKey); - if (entryValue && (entryValue->unsigned32BitValue() < delay_ms)) - entryValue->setValue(delay_ms); - return; + _d = OSDynamicCast(OSData, assertionsArray->getObject(i)); + if (_d) + { + _a = (PMAssertStruct *)_d->getBytesNoCopy(); + if (_a && (kIOPMDriverAssertionLevelOn == _a->level)) + assertionsKernel |= _a->assertionBits; + } } } - responseDescription = OSDictionary::withCapacity(5); - if (responseDescription) + tabulateProducerCount++; + assertionsCombined = assertionsKernel | assertionsUser; + + if ((assertionsKernel != oldKernel) || + (assertionsCombined != oldCombined)) { - if (response) { - responseDescription->setObject(_statsResponseTypeKey, response); - } - - if (messageType != 0) { - msgNum = OSNumber::withNumber(messageType, 32); - if (msgNum) { - responseDescription->setObject(_statsMessageTypeKey, msgNum); - msgNum->release(); - } - } + owner->evaluateAssertions(assertionsCombined, oldCombined); + } +} - if (name && (strlen(name) > 0)) +void PMAssertionsTracker::publishProperties( void ) +{ + OSArray *assertionsSummary = NULL; + + if (tabulateConsumerCount != tabulateProducerCount) + { + IOLockLock(assertionsArrayLock); + + tabulateConsumerCount = tabulateProducerCount; + + /* Publish the IOPMrootDomain property "DriverPMAssertionsDetailed" + */ + assertionsSummary = copyAssertionsArray(); + if (assertionsSummary) { - appname = OSSymbol::withCString(name); - if (appname) { - responseDescription->setObject(_statsNameKey, appname); - appname->release(); - } + owner->setProperty(kIOPMAssertionsDriverDetailedKey, assertionsSummary); + assertionsSummary->release(); } - - if (app_pid != -1) { - pidNum = OSNumber::withNumber(app_pid, 32); - if (pidNum) { - responseDescription->setObject(_statsPIDKey, pidNum); - pidNum->release(); - } + else + { + owner->removeProperty(kIOPMAssertionsDriverDetailedKey); } - delayNum = OSNumber::withNumber(delay_ms, 32); - if (delayNum) { - responseDescription->setObject(_statsTimeMSKey, delayNum); - delayNum->release(); - } + /* Publish the IOPMrootDomain property "DriverPMAssertions" + */ + owner->setProperty(kIOPMAssertionsDriverKey, assertionsKernel, 64); - if (pmStatsAppResponses) { - pmStatsAppResponses->setObject(responseDescription); + IOLockUnlock(assertionsArrayLock); + } +} + +PMAssertionsTracker::PMAssertStruct *PMAssertionsTracker::detailsForID(IOPMDriverAssertionID _id, int *index) +{ + PMAssertStruct *_a = NULL; + OSData *_d = NULL; + int found = -1; + int count = 0; + int i = 0; + + if (assertionsArray + && (count = assertionsArray->getCount())) + { + for (i=0; igetObject(i)); + if (_d) + { + _a = (PMAssertStruct *)_d->getBytesNoCopy(); + if (_a && (_id == _a->id)) { + found = i; + break; + } + } } + } - responseDescription->release(); + if (-1 == found) { + return NULL; + } else { + if (index) + *index = found; + return _a; } - return; } +/* PMAssertionsTracker::handleCreateAssertion + * Perform assertion work on the PM workloop. Do not call directly. + */ +IOReturn PMAssertionsTracker::handleCreateAssertion(OSData *newAssertion) +{ + ASSERT_GATED(); -//****************************************************************************** -// TracePoint support -// -//****************************************************************************** + if (newAssertion) + { + IOLockLock(assertionsArrayLock); + assertionsArray->setObject(newAssertion); + IOLockUnlock(assertionsArrayLock); + newAssertion->release(); -#define kIOPMRegisterNVRAMTracePointHandlerKey \ - "IOPMRegisterNVRAMTracePointHandler" + tabulate(); + } + return kIOReturnSuccess; +} -IOReturn IOPMrootDomain::callPlatformFunction( - const OSSymbol * functionName, - bool waitForFunction, - void * param1, void * param2, - void * param3, void * param4 ) +/* PMAssertionsTracker::createAssertion + * createAssertion allocates memory for a new PM assertion, and affects system behavior, if + * appropiate. + */ +IOReturn PMAssertionsTracker::createAssertion( + IOPMDriverAssertionType which, + IOPMDriverAssertionLevel level, + IOService *serviceID, + const char *whoItIs, + IOPMDriverAssertionID *outID) { - if (pmTracer && functionName && - functionName->isEqualTo(kIOPMRegisterNVRAMTracePointHandlerKey) && - !pmTracer->tracePointHandler && !pmTracer->tracePointTarget) + OSData *dataStore = NULL; + PMAssertStruct track; + + // Warning: trillions and trillions of created assertions may overflow the unique ID. + track.id = OSIncrementAtomic64((SInt64*) &issuingUniqueID); + track.level = level; + track.assertionBits = which; + track.ownerString = whoItIs ? OSSymbol::withCString(whoItIs):0; + track.ownerService = serviceID; + track.registryEntryID = serviceID ? serviceID->getRegistryEntryID():0; + track.modifiedTime = 0; + pmEventTimeStamp(&track.createdTime); + + dataStore = OSData::withBytes(&track, sizeof(PMAssertStruct)); + if (!dataStore) { - uint32_t tracePointPhases, tracePointPCI; - uint64_t statusCode; + if (track.ownerString) + track.ownerString->release(); + return kIOReturnNoMemory; + } - pmTracer->tracePointHandler = (IOPMTracePointHandler) param1; - pmTracer->tracePointTarget = (void *) param2; - tracePointPCI = (uint32_t)(uintptr_t) param3; - tracePointPhases = (uint32_t)(uintptr_t) param4; - statusCode = (((uint64_t)tracePointPCI) << 32) | tracePointPhases; - if ((tracePointPhases >> 24) != kIOPMTracePointSystemUp) - { - LOG("Sleep failure code 0x%08x 0x%08x\n", - tracePointPCI, tracePointPhases); - } - setProperty(kIOPMSleepWakeFailureCodeKey, statusCode, 64); - pmTracer->tracePointHandler( pmTracer->tracePointTarget, 0, 0 ); + *outID = track.id; - return kIOReturnSuccess; + if (owner && owner->pmPowerStateQueue) { + owner->pmPowerStateQueue->submitPowerEvent(kPowerEventAssertionCreate, (void *)dataStore); } - return super::callPlatformFunction( - functionName, waitForFunction, param1, param2, param3, param4); + return kIOReturnSuccess; } -void IOPMrootDomain::tracePoint( uint8_t point ) +/* PMAssertionsTracker::handleReleaseAssertion + * Runs in PM workloop. Do not call directly. + */ +IOReturn PMAssertionsTracker::handleReleaseAssertion( + IOPMDriverAssertionID _id) { - pmTracer->tracePoint(point); -} + ASSERT_GATED(); -//****************************************************************************** -// PMTraceWorker Class -// -//****************************************************************************** + int index; + PMAssertStruct *assertStruct = detailsForID(_id, &index); -#undef super -#define super OSObject -OSDefineMetaClassAndStructors(PMTraceWorker, OSObject) + if (!assertStruct) + return kIOReturnNotFound; -#define kPMBestGuessPCIDevicesCount 25 -#define kPMMaxRTCBitfieldSize 32 + IOLockLock(assertionsArrayLock); + if (assertStruct->ownerString) + assertStruct->ownerString->release(); -PMTraceWorker *PMTraceWorker::tracer(IOPMrootDomain *owner) + assertionsArray->removeObject(index); + IOLockUnlock(assertionsArrayLock); + + tabulate(); + return kIOReturnSuccess; +} + +/* PMAssertionsTracker::releaseAssertion + * Releases an assertion and affects system behavior if appropiate. + * Actual work happens on PM workloop. + */ +IOReturn PMAssertionsTracker::releaseAssertion( + IOPMDriverAssertionID _id) { - PMTraceWorker *me; - - me = OSTypeAlloc( PMTraceWorker ); - if (!me || !me->init()) - { - return NULL; + if (owner && owner->pmPowerStateQueue) { + owner->pmPowerStateQueue->submitPowerEvent(kPowerEventAssertionRelease, 0, _id); } + return kIOReturnSuccess; +} - DLOG("PMTraceWorker %p\n", me); +/* PMAssertionsTracker::handleSetAssertionLevel + * Runs in PM workloop. Do not call directly. + */ +IOReturn PMAssertionsTracker::handleSetAssertionLevel( + IOPMDriverAssertionID _id, + IOPMDriverAssertionLevel _level) +{ + PMAssertStruct *assertStruct = detailsForID(_id, NULL); - // Note that we cannot instantiate the PCI device -> bit mappings here, since - // the IODeviceTree has not yet been created by IOPlatformExpert. We create - // this dictionary lazily. - me->owner = owner; - me->pciDeviceBitMappings = NULL; - me->pciMappingLock = IOLockAlloc(); - me->tracePhase = kIOPMTracePointSystemUp; - me->loginWindowPhase = 0; - me->pciBusyBitMask = 0; - return me; + ASSERT_GATED(); + + if (!assertStruct) { + return kIOReturnNotFound; + } + + IOLockLock(assertionsArrayLock); + pmEventTimeStamp(&assertStruct->modifiedTime); + assertStruct->level = _level; + IOLockUnlock(assertionsArrayLock); + + tabulate(); + return kIOReturnSuccess; } -void PMTraceWorker::RTC_TRACE(void) +/* PMAssertionsTracker::setAssertionLevel + */ +IOReturn PMAssertionsTracker::setAssertionLevel( + IOPMDriverAssertionID _id, + IOPMDriverAssertionLevel _level) { - if (tracePointHandler && tracePointTarget) - { - uint32_t wordA; - - wordA = tracePhase; // destined for bits 24-31 - wordA <<= 8; - wordA |= loginWindowPhase; // destined for bits 16-23 - wordA <<= 16; + if (owner && owner->pmPowerStateQueue) { + owner->pmPowerStateQueue->submitPowerEvent(kPowerEventAssertionSetLevel, + (void *)(uintptr_t)_level, _id); + } - tracePointHandler( tracePointTarget, pciBusyBitMask, wordA ); - DLOG("RTC_TRACE wrote 0x%08x 0x%08x\n", pciBusyBitMask, wordA); - } + return kIOReturnSuccess; } -int PMTraceWorker::recordTopLevelPCIDevice(IOService * pciDevice) +IOReturn PMAssertionsTracker::handleSetUserAssertionLevels(void * arg0) { - const OSSymbol * deviceName; - int index = -1; + IOPMDriverAssertionType new_user_levels = *(IOPMDriverAssertionType *) arg0; - IOLockLock(pciMappingLock); + ASSERT_GATED(); - if (!pciDeviceBitMappings) + if (new_user_levels != assertionsUser) { - pciDeviceBitMappings = OSArray::withCapacity(kPMBestGuessPCIDevicesCount); - if (!pciDeviceBitMappings) - goto exit; + assertionsUser = new_user_levels; + DLOG("assertionsUser 0x%llx\n", assertionsUser); } - // Check for bitmask overflow. - if (pciDeviceBitMappings->getCount() >= kPMMaxRTCBitfieldSize) + tabulate(); + return kIOReturnSuccess; +} + +IOReturn PMAssertionsTracker::setUserAssertionLevels( + IOPMDriverAssertionType new_user_levels) +{ + if (gIOPMWorkLoop) { + gIOPMWorkLoop->runAction( + OSMemberFunctionCast( + IOWorkLoop::Action, + this, + &PMAssertionsTracker::handleSetUserAssertionLevels), + this, + (void *) &new_user_levels, 0, 0, 0); + } + + return kIOReturnSuccess; +} + + +OSArray *PMAssertionsTracker::copyAssertionsArray(void) +{ + int count; + int i; + OSArray *outArray = NULL; + + if (!assertionsArray || + (0 == (count = assertionsArray->getCount())) || + (NULL == (outArray = OSArray::withCapacity(count)))) + { goto exit; + } + + for (i=0; igetObject(i)); + if (_d && (_a = (PMAssertStruct *)_d->getBytesNoCopy())) + { + OSNumber *_n = NULL; + + details = OSDictionary::withCapacity(7); + if (!details) + continue; + + outArray->setObject(details); + details->release(); + + _n = OSNumber::withNumber(_a->id, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionIDKey, _n); + _n->release(); + } + _n = OSNumber::withNumber(_a->createdTime, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionCreatedTimeKey, _n); + _n->release(); + } + _n = OSNumber::withNumber(_a->modifiedTime, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionModifiedTimeKey, _n); + _n->release(); + } + _n = OSNumber::withNumber((uintptr_t)_a->registryEntryID, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionRegistryEntryIDKey, _n); + _n->release(); + } + _n = OSNumber::withNumber(_a->level, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionLevelKey, _n); + _n->release(); + } + _n = OSNumber::withNumber(_a->assertionBits, 64); + if (_n) { + details->setObject(kIOPMDriverAssertionAssertedKey, _n); + _n->release(); + } - if ((deviceName = pciDevice->copyName()) && - (pciDeviceBitMappings->getNextIndexOfObject(deviceName, 0) == (unsigned int)-1) && - pciDeviceBitMappings->setObject(deviceName)) - { - index = pciDeviceBitMappings->getCount() - 1; - DLOG("PMTrace PCI array: set object %s => %d\n", - deviceName->getCStringNoCopy(), index); + if (_a->ownerString) { + details->setObject(kIOPMDriverAssertionOwnerStringKey, _a->ownerString); + } + } } - if (deviceName) - deviceName->release(); - if (!addedToRegistry && (index >= 0)) - addedToRegistry = owner->setProperty("PCITopLevel", this); exit: - IOLockUnlock(pciMappingLock); - return index; + return outArray; } -bool PMTraceWorker::serialize(OSSerialize *s) const +IOPMDriverAssertionType PMAssertionsTracker::getActivatedAssertions(void) { - bool ok = false; - if (pciDeviceBitMappings) - { - IOLockLock(pciMappingLock); - ok = pciDeviceBitMappings->serialize(s); - IOLockUnlock(pciMappingLock); - } - return ok; + return assertionsCombined; } -void PMTraceWorker::tracePoint(uint8_t phase) +IOPMDriverAssertionLevel PMAssertionsTracker::getAssertionLevel( + IOPMDriverAssertionType type) { - tracePhase = phase; - - DLOG("IOPMrootDomain: trace point 0x%02x\n", tracePhase); - RTC_TRACE(); + if (type && ((type & assertionsKernel) == assertionsKernel)) + { + return kIOPMDriverAssertionLevelOn; + } else { + return kIOPMDriverAssertionLevelOff; + } } -void PMTraceWorker::traceLoginWindowPhase(uint8_t phase) -{ - loginWindowPhase = phase; +//********************************************************************************* +//********************************************************************************* +//********************************************************************************* - DLOG("IOPMrootDomain: loginwindow tracepoint 0x%02x\n", loginWindowPhase); - RTC_TRACE(); -} -void PMTraceWorker::tracePCIPowerChange( - change_t type, IOService *service, uint32_t changeFlags, uint32_t bitNum) +static void pmEventTimeStamp(uint64_t *recordTS) { - uint32_t bitMask; - uint32_t expectedFlag; + clock_sec_t tsec; + clock_usec_t tusec; - // Ignore PCI changes outside of system sleep/wake. - if ((kIOPMTracePointSystemSleepDriversPhase != tracePhase) && - (kIOPMTracePointSystemWakeDriversPhase != tracePhase)) + if (!recordTS) return; - // Only record the WillChange transition when going to sleep, - // and the DidChange on the way up. - changeFlags &= (kIOPMDomainWillChange | kIOPMDomainDidChange); - expectedFlag = (kIOPMTracePointSystemSleepDriversPhase == tracePhase) ? - kIOPMDomainWillChange : kIOPMDomainDidChange; - if (changeFlags != expectedFlag) - return; + // We assume tsec fits into 32 bits; 32 bits holds enough + // seconds for 136 years since the epoch in 1970. + clock_get_calendar_microtime(&tsec, &tusec); - // Mark this device off in our bitfield - if (bitNum < kPMMaxRTCBitfieldSize) - { - bitMask = (1 << bitNum); - if (kPowerChangeStart == type) - { - pciBusyBitMask |= bitMask; - DLOG("PMTrace: Device %s started - bit %2d mask 0x%08x => 0x%08x\n", - service->getName(), bitNum, bitMask, pciBusyBitMask); - } - else - { - pciBusyBitMask &= ~bitMask; - DLOG("PMTrace: Device %s finished - bit %2d mask 0x%08x => 0x%08x\n", - service->getName(), bitNum, bitMask, pciBusyBitMask); - } + // Pack the sec & microsec calendar time into a uint64_t, for fun. + *recordTS = 0; + *recordTS |= (uint32_t)tusec; + *recordTS |= ((uint64_t)tsec << 32); - RTC_TRACE(); - } + return; } +// MARK: - +// MARK: IORootParent -//****************************************************************************** -// PMHaltWorker Class +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +OSDefineMetaClassAndFinalStructors(IORootParent, IOService) + +// The reason that root domain needs a root parent is to facilitate demand +// sleep, since a power change from the root parent cannot be vetoed. // -//****************************************************************************** +// The above statement is no longer true since root domain now performs +// demand sleep using overrides. But root parent remains to avoid changing +// the power tree stacking. Root parent is parked at the max power state. -static unsigned int gPMHaltBusyCount; -static unsigned int gPMHaltIdleCount; -static int gPMHaltDepth; -static unsigned long gPMHaltEvent; -static IOLock * gPMHaltLock = 0; -static OSArray * gPMHaltArray = 0; -static const OSSymbol * gPMHaltClientAcknowledgeKey = 0; -PMHaltWorker * PMHaltWorker::worker( void ) +static IOPMPowerState patriarchPowerStates[2] = { - PMHaltWorker * me; - IOThread thread; - - do { - me = OSTypeAlloc( PMHaltWorker ); - if (!me || !me->init()) - break; + {1,0,ON_POWER,0,0,0,0,0,0,0,0,0}, + {1,0,ON_POWER,0,0,0,0,0,0,0,0,0}, +}; - me->lock = IOLockAlloc(); - if (!me->lock) - break; +void IORootParent::initialize( void ) +{ +} - DLOG("PMHaltWorker %p\n", me); - me->retain(); // thread holds extra retain - if (KERN_SUCCESS != kernel_thread_start(&PMHaltWorker::main, (void *) me, &thread)) - { - me->release(); - break; - } - thread_deallocate(thread); - return me; +bool IORootParent::start( IOService * nub ) +{ + IOService::start(nub); + attachToParent( getRegistryRoot(), gIOPowerPlane ); + PMinit(); + registerPowerDriver(this, patriarchPowerStates, 2); + makeUsable(); + return true; +} - } while (false); +void IORootParent::shutDownSystem( void ) +{ +} - if (me) me->release(); - return 0; +void IORootParent::restartSystem( void ) +{ } -void PMHaltWorker::free( void ) +void IORootParent::sleepSystem( void ) { - DLOG("PMHaltWorker free %p\n", this); - if (lock) - { - IOLockFree(lock); - lock = 0; - } - return OSObject::free(); } -void PMHaltWorker::main( void * arg, wait_result_t waitResult ) +void IORootParent::dozeSystem( void ) { - PMHaltWorker * me = (PMHaltWorker *) arg; - - IOLockLock( gPMHaltLock ); - gPMHaltBusyCount++; - me->depth = gPMHaltDepth; - IOLockUnlock( gPMHaltLock ); - - while (me->depth >= 0) - { - PMHaltWorker::work( me ); - - IOLockLock( gPMHaltLock ); - if (++gPMHaltIdleCount >= gPMHaltBusyCount) - { - // This is the last thread to finish work on this level, - // inform everyone to start working on next lower level. - gPMHaltDepth--; - me->depth = gPMHaltDepth; - gPMHaltIdleCount = 0; - thread_wakeup((event_t) &gPMHaltIdleCount); - } - else - { - // One or more threads are still working on this level, - // this thread must wait. - me->depth = gPMHaltDepth - 1; - do { - IOLockSleep(gPMHaltLock, &gPMHaltIdleCount, THREAD_UNINT); - } while (me->depth != gPMHaltDepth); - } - IOLockUnlock( gPMHaltLock ); - } +} - // No more work to do, terminate thread - DLOG("All done for worker: %p (visits = %u)\n", me, me->visits); - thread_wakeup( &gPMHaltDepth ); - me->release(); +void IORootParent::sleepToDoze( void ) +{ } -void PMHaltWorker::work( PMHaltWorker * me ) +void IORootParent::wakeSystem( void ) { - IOService * service; - OSSet * inner; - AbsoluteTime startTime; - UInt32 deltaTime; - bool timeout; - - while (true) - { - service = 0; - timeout = false; - - // Claim an unit of work from the shared pool - IOLockLock( gPMHaltLock ); - inner = (OSSet *)gPMHaltArray->getObject(me->depth); - if (inner) - { - service = (IOService *)inner->getAnyObject(); - if (service) - { - service->retain(); - inner->removeObject(service); - } - } - IOLockUnlock( gPMHaltLock ); - if (!service) - break; // no more work at this depth - - clock_get_uptime(&startTime); - - if (!service->isInactive() && - service->setProperty(gPMHaltClientAcknowledgeKey, me)) - { - IOLockLock(me->lock); - me->startTime = startTime; - me->service = service; - me->timeout = false; - IOLockUnlock(me->lock); - - service->systemWillShutdown( gPMHaltEvent ); - - // Wait for driver acknowledgement - IOLockLock(me->lock); - while (service->getProperty(gPMHaltClientAcknowledgeKey)) - { - IOLockSleep(me->lock, me, THREAD_UNINT); - } - me->service = 0; - timeout = me->timeout; - IOLockUnlock(me->lock); - } - - deltaTime = computeDeltaTimeMS(&startTime); - if ((deltaTime > kPMHaltTimeoutMS) || timeout || - (gIOKitDebug & (kIOLogDebugPower | kIOLogPMRootDomain))) - { - KLOG("%s driver %s (%p) took %u ms\n", - (gPMHaltEvent == kIOMessageSystemWillPowerOff) ? - "PowerOff" : "Restart", - service->getName(), service, - (uint32_t) deltaTime ); - } - - service->release(); - me->visits++; - } } -void PMHaltWorker::checkTimeout( PMHaltWorker * me, AbsoluteTime * now ) +OSObject * IORootParent::copyProperty( const char * aKey) const { - UInt64 nano; - AbsoluteTime startTime; - AbsoluteTime endTime; - - endTime = *now; - - IOLockLock(me->lock); - if (me->service && !me->timeout) - { - startTime = me->startTime; - nano = 0; - if (CMP_ABSOLUTETIME(&endTime, &startTime) > 0) - { - SUB_ABSOLUTETIME(&endTime, &startTime); - absolutetime_to_nanoseconds(endTime, &nano); - } - if (nano > 3000000000ULL) - { - me->timeout = true; - LOG("%s still waiting on %s\n", - (gPMHaltEvent == kIOMessageSystemWillPowerOff) ? - "PowerOff" : "Restart", - me->service->getName()); - } - } - IOLockUnlock(me->lock); + return (IOService::copyProperty(aKey)); } -//****************************************************************************** -// acknowledgeSystemWillShutdown -// -// Acknowledgement from drivers that they have prepared for shutdown/restart. -//****************************************************************************** +#if defined(__i386__) || defined(__x86_64__) +IOReturn IOPMrootDomain::restartWithStackshot() +{ + if ((swd_flags & SWD_WDOG_ENABLED) == 0) + return kIOReturnError; -void IOPMrootDomain::acknowledgeSystemWillShutdown( IOService * from ) + takeStackshot(true, true); + + return kIOReturnSuccess; +} + +void IOPMrootDomain::sleepWakeDebugTrig(bool wdogTrigger) { - PMHaltWorker * worker; - OSObject * prop; - - if (!from) - return; - - //DLOG("%s acknowledged\n", from->getName()); - prop = from->copyProperty( gPMHaltClientAcknowledgeKey ); - if (prop) - { - worker = (PMHaltWorker *) prop; - IOLockLock(worker->lock); - from->removeProperty( gPMHaltClientAcknowledgeKey ); - thread_wakeup((event_t) worker); - IOLockUnlock(worker->lock); - worker->release(); - } - else - { - DLOG("%s acknowledged without worker property\n", - from->getName()); - } + takeStackshot(wdogTrigger, false); } +void IOPMrootDomain::takeStackshot(bool wdogTrigger, bool isOSXWatchdog) +{ + swd_hdr * hdr = NULL; + addr64_t data[3]; + uint32_t wdog_panic = 0; + int cnt = 0; + pid_t pid = 0; + uint32_t flags; + + char * dstAddr; + uint32_t size; + uint32_t bytesRemaining; + unsigned int len; + OSString * UUIDstring = NULL; + uint64_t code; + IOMemoryMap * logBufMap = NULL; + + swd_stackshot_hdr *stackshotHdr = NULL; + if ( kIOSleepWakeWdogOff & gIOKitDebug ) + return; + + if (wdogTrigger) { + if (PE_parse_boot_argn("swd_panic", &wdog_panic, sizeof(wdog_panic)) && + (wdog_panic == 1)) { + // If boot-arg is set to panic on sleep/wake hang, call panic + panic("Sleep/Wake hang detected\n"); + return; + } + else if (swd_flags & SWD_BOOT_BY_SW_WDOG) { + // If current boot is due to this watch dog trigger restart in previous boot, + // then don't trigger again until at least 1 successful sleep & wake. + sleepCnt = displayWakeCnt = 1; + if (!(sleepCnt && displayWakeCnt)) { + IOLog("Shutting down due to repeated Sleep/Wake failures\n"); + PEHaltRestart(kPEHaltCPU); + return; + } + } + + } + + if (sleepWakeDebugIsWdogEnabled() == false) + return; + + if (swd_buffer == NULL) { + sleepWakeDebugMemAlloc(); + if (swd_buffer == NULL) return; + } + + if (!OSCompareAndSwap(0, 1, &gRootDomain->swd_lock)) + return; + + + hdr = (swd_hdr *)swd_buffer; + memset(hdr->UUID, 0x20, sizeof(hdr->UUID)); + if ((UUIDstring = OSDynamicCast(OSString, getProperty(kIOPMSleepWakeUUIDKey))) != NULL ) { + + if (wdogTrigger || (!UUIDstring->isEqualTo(hdr->UUID))) { + const char *str = UUIDstring->getCStringNoCopy(); + snprintf(hdr->UUID, sizeof(hdr->UUID), "UUID: %s", str); + } + else { + DLOG("Data for current UUID already exists\n"); + goto exit; + } + } + + dstAddr = (char*)hdr + hdr->spindump_offset; + bytesRemaining = SWD_BUF_SIZE - hdr->spindump_offset; + + /* if AppleOSXWatchdog triggered the stackshot, set the flag in the heaer */ + hdr->is_osx_watchdog = isOSXWatchdog; + + DLOG("Taking snapshot. bytesRemaining: %d\n", bytesRemaining); + + while (bytesRemaining > sizeof(swd_stackshot_hdr)) { + + stackshotHdr = (swd_stackshot_hdr *)dstAddr; + stackshotHdr->magic = SWD_STACKSHOTHDR_MAGIC; + stackshotHdr->size = 0; + bytesRemaining -= sizeof(swd_stackshot_hdr); + dstAddr += sizeof(swd_stackshot_hdr); + + if (isOSXWatchdog) { + pid = -1; + size = bytesRemaining; + flags = STACKSHOT_SAVE_LOADINFO | STACKSHOT_SAVE_KEXT_LOADINFO; + } + else if (cnt == 0) { + /* + * Take stackshot of all process on first sample. Size is restricted + * to SWD_INITIAL_STACK_SIZE + */ + pid = -1; + size = (bytesRemaining > SWD_INITIAL_STACK_SIZE) ? SWD_INITIAL_STACK_SIZE : bytesRemaining; + flags = STACKSHOT_SAVE_LOADINFO | STACKSHOT_SAVE_KEXT_LOADINFO|STACKSHOT_SAVE_KERNEL_FRAMES_ONLY; + } + else { + /* Take sample of kernel threads only */ + pid = 0; + size = bytesRemaining; + flags = 0; + } + + stack_snapshot_from_kernel(pid, dstAddr, size, flags, &stackshotHdr->size); + + dstAddr += stackshotHdr->size; + bytesRemaining -= stackshotHdr->size; + + DLOG("Sample: %d size: %d bytesRemaining: %d\n", cnt, stackshotHdr->size, bytesRemaining); + if ((stackshotHdr->size == 0) || (++cnt == 10)) + break; + IOSleep(10); // 10 ms + } + + hdr->spindump_size = (SWD_BUF_SIZE - bytesRemaining - hdr->spindump_offset); + + + memset(hdr->cps, 0x20, sizeof(hdr->cps)); + snprintf(hdr->cps, sizeof(hdr->cps), "\ncps: %d", ((IOService*)this)->getPowerState()); + code = pmTracer->getPMStatusCode(); + memset(hdr->PMStatusCode, 0x20, sizeof(hdr->PMStatusCode)); + snprintf(hdr->PMStatusCode, sizeof(hdr->PMStatusCode), "\nCode: %08x %08x", + (uint32_t)((code >> 32) & 0xffffffff), (uint32_t)(code & 0xffffffff)); + memset(hdr->reason, 0x20, sizeof(hdr->reason)); + snprintf(hdr->reason, sizeof(hdr->reason), "\nStackshot reason: Watchdog\n\n"); + + + data[0] = round_page(sizeof(swd_hdr) + hdr->spindump_size); + /* Header & rootdomain log is constantly changing and is not covered by CRC */ + data[1] = hdr->crc = crc32(0, ((char*)swd_buffer+hdr->spindump_offset), hdr->spindump_size); + data[2] = kvtophys((vm_offset_t)swd_buffer); + len = sizeof(addr64_t)*3; + DLOG("bytes: 0x%llx crc:0x%llx paddr:0x%llx\n", + data[0], data[1], data[2]); + + if (PEWriteNVRAMProperty(kIOSleepWakeDebugKey, data, len) == false) + { + DLOG("Failed to update nvram boot-args\n"); + goto exit; + } -//****************************************************************************** -// notifySystemShutdown -// -// Notify all objects in PM tree that system will shutdown or restart -//****************************************************************************** +exit: -static void -notifySystemShutdown( IOService * root, unsigned long event ) + gRootDomain->swd_lock = 0; + + if (wdogTrigger) { + IOLog("Restarting to collect Sleep wake debug logs\n"); + PEHaltRestart(kPERestartCPU); + } + else { + logBufMap = sleepWakeDebugRetrieve(); + if (logBufMap) { + sleepWakeDebugDumpFromMem(logBufMap); + logBufMap->release(); + logBufMap = 0; + } + } +} + +void IOPMrootDomain::sleepWakeDebugMemAlloc( ) { -#define PLACEHOLDER ((OSSet *)gPMHaltArray) - IORegistryIterator * iter; - IORegistryEntry * entry; - IOService * node; - OSSet * inner; - PMHaltWorker * workers[kPMHaltMaxWorkers]; - AbsoluteTime deadline; - unsigned int totalNodes = 0; - unsigned int depth; - unsigned int rootDepth; - unsigned int numWorkers; - unsigned int count; - int waitResult; - void * baseFunc; - bool ok; - - DLOG("%s event = %lx\n", __FUNCTION__, event); - - baseFunc = OSMemberFunctionCast(void *, root, &IOService::systemWillShutdown); - - // Iterate the entire PM tree starting from root - - rootDepth = root->getDepth( gIOPowerPlane ); - if (!rootDepth) goto done; - - // debug - for repeated test runs - while (PMHaltWorker::metaClass->getInstanceCount()) - IOSleep(1); - - if (!gPMHaltArray) - { - gPMHaltArray = OSArray::withCapacity(40); - if (!gPMHaltArray) goto done; - } - else // debug - gPMHaltArray->flushCollection(); + vm_size_t size = SWD_BUF_SIZE; - if (!gPMHaltLock) - { - gPMHaltLock = IOLockAlloc(); - if (!gPMHaltLock) goto done; - } + swd_hdr *hdr = NULL; - if (!gPMHaltClientAcknowledgeKey) - { - gPMHaltClientAcknowledgeKey = - OSSymbol::withCStringNoCopy("PMShutdown"); - if (!gPMHaltClientAcknowledgeKey) goto done; - } + IOBufferMemoryDescriptor *memDesc = NULL; - gPMHaltEvent = event; - - // Depth-first walk of PM plane - - iter = IORegistryIterator::iterateOver( - root, gIOPowerPlane, kIORegistryIterateRecursively); - - if (iter) - { - while ((entry = iter->getNextObject())) - { - node = OSDynamicCast(IOService, entry); - if (!node) - continue; - - if (baseFunc == - OSMemberFunctionCast(void *, node, &IOService::systemWillShutdown)) - continue; - - depth = node->getDepth( gIOPowerPlane ); - if (depth <= rootDepth) - continue; - - ok = false; - - // adjust to zero based depth - depth -= (rootDepth + 1); - - // gPMHaltArray is an array of containers, each container - // refers to nodes with the same depth. - - count = gPMHaltArray->getCount(); - while (depth >= count) - { - // expand array and insert placeholders - gPMHaltArray->setObject(PLACEHOLDER); - count++; - } - count = gPMHaltArray->getCount(); - if (depth < count) - { - inner = (OSSet *)gPMHaltArray->getObject(depth); - if (inner == PLACEHOLDER) - { - inner = OSSet::withCapacity(40); - if (inner) - { - gPMHaltArray->replaceObject(depth, inner); - inner->release(); - } - } - - // PM nodes that appear more than once in the tree will have - // the same depth, OSSet will refuse to add the node twice. - if (inner) - ok = inner->setObject(node); - } - if (!ok) - DLOG("Skipped PM node %s\n", node->getName()); - } - iter->release(); - } - // debug only - for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); i++) - { - count = 0; - if (inner != PLACEHOLDER) - count = inner->getCount(); - DLOG("Nodes at depth %u = %u\n", i, count); - } + if ( kIOSleepWakeWdogOff & gIOKitDebug ) + return; - // strip placeholders (not all depths are populated) - numWorkers = 0; - for (int i = 0; (inner = (OSSet *)gPMHaltArray->getObject(i)); ) - { - if (inner == PLACEHOLDER) - { - gPMHaltArray->removeObject(i); - continue; - } - count = inner->getCount(); - if (count > numWorkers) - numWorkers = count; - totalNodes += count; - i++; - } + if (!OSCompareAndSwap(0, 1, &gRootDomain->swd_lock)) + return; - if (gPMHaltArray->getCount() == 0 || !numWorkers) - goto done; + // Try allocating above 4GB. If that fails, try at 2GB + memDesc = IOBufferMemoryDescriptor::inTaskWithPhysicalMask( + kernel_task, kIOMemoryPhysicallyContiguous|kIOMemoryMapperNone, + size, 0xFFFFFFFF00000000ULL); + if (!memDesc) { + memDesc = IOBufferMemoryDescriptor::inTaskWithPhysicalMask( + kernel_task, kIOMemoryPhysicallyContiguous|kIOMemoryMapperNone, + size, 0xFFFFFFFF10000000ULL); + } + + if (memDesc == NULL) + { + DLOG("Failed to allocate Memory descriptor for sleepWake debug\n"); + goto exit; + } - gPMHaltBusyCount = 0; - gPMHaltIdleCount = 0; - gPMHaltDepth = gPMHaltArray->getCount() - 1; - // Create multiple workers (and threads) + hdr = (swd_hdr *)memDesc->getBytesNoCopy(); + memset(hdr, 0, sizeof(swd_hdr)); - if (numWorkers > kPMHaltMaxWorkers) - numWorkers = kPMHaltMaxWorkers; + hdr->signature = SWD_HDR_SIGNATURE; + hdr->alloc_size = size; - DLOG("PM nodes = %u, maxDepth = %u, workers = %u\n", - totalNodes, gPMHaltArray->getCount(), numWorkers); + hdr->spindump_offset = sizeof(swd_hdr); + swd_buffer = (void *)hdr; + DLOG("SleepWake debug buffer size:0x%x spindump offset:0x%x\n", hdr->alloc_size, hdr->spindump_offset); - for (unsigned int i = 0; i < numWorkers; i++) - workers[i] = PMHaltWorker::worker(); +exit: + gRootDomain->swd_lock = 0; +} + +void IOPMrootDomain::sleepWakeDebugEnableWdog() +{ + swd_flags |= SWD_WDOG_ENABLED; + if (!swd_buffer) + sleepWakeDebugMemAlloc(); +} - // Wait for workers to exhaust all available work +bool IOPMrootDomain::sleepWakeDebugIsWdogEnabled() +{ + return ((swd_flags & SWD_WDOG_ENABLED) && + !systemBooting && !systemShutdown); +} - IOLockLock(gPMHaltLock); - while (gPMHaltDepth >= 0) - { - clock_interval_to_deadline(1000, kMillisecondScale, &deadline); +errno_t IOPMrootDomain::sleepWakeDebugSaveFile(const char *name, char *buf, int len) +{ + struct vnode *vp = NULL; + vfs_context_t ctx = vfs_context_create(vfs_context_current()); + kauth_cred_t cred = vfs_context_ucred(ctx); + struct vnode_attr va; + errno_t error = EIO; + + if (vnode_open(name, (O_CREAT | FWRITE | O_NOFOLLOW), + S_IRUSR|S_IRGRP|S_IROTH, VNODE_LOOKUP_NOFOLLOW, &vp, ctx) != 0) + { + IOLog("Failed to open the file %s\n", name); + goto exit; + } + VATTR_INIT(&va); + VATTR_WANTED(&va, va_nlink); + /* Don't dump to non-regular files or files with links. */ + if (vp->v_type != VREG || + vnode_getattr(vp, &va, ctx) || va.va_nlink != 1) { + IOLog("Bailing as this is not a regular file\n"); + goto exit; + } + VATTR_INIT(&va); + VATTR_SET(&va, va_data_size, 0); + vnode_setattr(vp, &va, ctx); - waitResult = IOLockSleepDeadline( - gPMHaltLock, &gPMHaltDepth, deadline, THREAD_UNINT); - if (THREAD_TIMED_OUT == waitResult) - { - AbsoluteTime now; - clock_get_uptime(&now); - IOLockUnlock(gPMHaltLock); - for (unsigned int i = 0 ; i < numWorkers; i++) - { - if (workers[i]) - PMHaltWorker::checkTimeout(workers[i], &now); - } - IOLockLock(gPMHaltLock); - } - } - IOLockUnlock(gPMHaltLock); + error = vn_rdwr(UIO_WRITE, vp, buf, len, 0, + UIO_SYSSPACE, IO_NODELOCKED|IO_UNIT, cred, (int *) 0, vfs_context_proc(ctx)); + if (error != 0) + IOLog("Failed to save sleep wake log. err 0x%x\n", error); + else + DLOG("Saved %d bytes to file %s\n",len, name); - // Release all workers +exit: + if (vp) vnode_close(vp, FWRITE, ctx); + if (ctx) vfs_context_rele(ctx); - for (unsigned int i = 0; i < numWorkers; i++) - { - if (workers[i]) - workers[i]->release(); - // worker also retained by it's own thread - } + return error; -done: - DLOG("%s done\n", __FUNCTION__); - return; } +errno_t IOPMrootDomain::sleepWakeDebugCopyFile( + struct vnode *srcVp, + vfs_context_t srcCtx, + char *tmpBuf, uint64_t tmpBufSize, + uint64_t srcOffset, + const char *dstFname, + uint64_t numBytes, + uint32_t crc) +{ + struct vnode *vp = NULL; + vfs_context_t ctx = vfs_context_create(vfs_context_current()); + struct vnode_attr va; + errno_t error = EIO; + uint64_t bytesToRead, bytesToWrite; + uint64_t readFileOffset, writeFileOffset, srcDataOffset; + uint32_t newcrc = 0; + + if (vnode_open(dstFname, (O_CREAT | FWRITE | O_NOFOLLOW), + S_IRUSR|S_IRGRP|S_IROTH, VNODE_LOOKUP_NOFOLLOW, &vp, ctx) != 0) + { + DLOG("Failed to open the file %s\n", dstFname); + goto exit; + } + VATTR_INIT(&va); + VATTR_WANTED(&va, va_nlink); + /* Don't dump to non-regular files or files with links. */ + if (vp->v_type != VREG || + vnode_getattr(vp, &va, ctx) || va.va_nlink != 1) { + DLOG("Bailing as this is not a regular file\n"); + goto exit; + } + VATTR_INIT(&va); + VATTR_SET(&va, va_data_size, 0); + vnode_setattr(vp, &va, ctx); + + writeFileOffset = 0; + while(numBytes) { + bytesToRead = (round_page(numBytes) > tmpBufSize) ? tmpBufSize : round_page(numBytes); + readFileOffset = trunc_page(srcOffset); + + DLOG("Read file (numBytes:0x%llx)\n", bytesToRead); + error = vn_rdwr(UIO_READ, srcVp, tmpBuf, bytesToRead, readFileOffset, + UIO_SYSSPACE, IO_SKIP_ENCRYPTION|IO_SYNC|IO_NODELOCKED|IO_UNIT|IO_NOCACHE, + vfs_context_ucred(srcCtx), (int *) 0, + vfs_context_proc(srcCtx)); + if (error) { + DLOG("Failed to read file(numBytes:0x%llx)\n", bytesToRead); + break; + } + + srcDataOffset = (uint64_t)tmpBuf + (srcOffset - readFileOffset); + bytesToWrite = bytesToRead - (srcOffset - readFileOffset); + if (bytesToWrite > numBytes) bytesToWrite = numBytes; + + if (crc) { + newcrc = crc32(newcrc, (void *)srcDataOffset, bytesToWrite); + } + error = vn_rdwr(UIO_WRITE, vp, (char *)srcDataOffset, bytesToWrite, writeFileOffset, + UIO_SYSSPACE, IO_SYNC|IO_NODELOCKED|IO_UNIT, + vfs_context_ucred(ctx), (int *) 0, + vfs_context_proc(ctx)); + if (error) { + DLOG("Failed to write file(numBytes:0x%llx)\n", bytesToWrite); + break; + } + + writeFileOffset += bytesToWrite; + numBytes -= bytesToWrite; + srcOffset += bytesToWrite; -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + } + if (crc != newcrc) { + swd_stackshot_hdr *shdr = (swd_stackshot_hdr *)tmpBuf;; + + /* Set statckshot size to 0 if crc doesn't match */ + shdr->magic = SWD_STACKSHOTHDR_MAGIC; + shdr->size = 0; + + assert(tmpBufSize > sizeof(swd_stackshot_hdr)); + bytesToWrite = round_page(sizeof(swd_stackshot_hdr)); + vn_rdwr(UIO_WRITE, vp, (char *)tmpBuf, bytesToWrite, 0, + UIO_SYSSPACE, IO_SYNC|IO_NODELOCKED|IO_UNIT, + vfs_context_ucred(ctx), (int *) 0, + vfs_context_proc(ctx)); + + DLOG("CRC check failed. expected:0x%x actual:0x%x\n", crc, newcrc); + error = EFAULT; + } +exit: + if (vp) { + error = vnode_close(vp, FWRITE, ctx); + DLOG("vnode_close returned 0x%x\n", error); + } + if (ctx) vfs_context_rele(ctx); + + return error; -#undef super -#define super OSObject -OSDefineMetaClassAndFinalStructors(PMSettingObject, OSObject) -void PMSettingObject::setPMSetting(const OSSymbol *type, OSObject *obj) -{ - (*func)(target, type, obj, refcon); } -/* - * Static constructor/initializer for PMSettingObject - */ -PMSettingObject *PMSettingObject::pmSettingObject( - IOPMrootDomain *parent_arg, - IOPMSettingControllerCallback handler_arg, - OSObject *target_arg, - uintptr_t refcon_arg, - uint32_t supportedPowerSources, - const OSSymbol * settings[]) +void IOPMrootDomain::sleepWakeDebugDumpFromFile( ) { - uint32_t objCount = 0; - PMSettingObject *pmso; - if( !parent_arg || !handler_arg || !settings ) return NULL; + int rc; + char hibernateFilename[MAXPATHLEN+1]; + char PMStatusCode[100]; + void *tmpBuf; + swd_hdr *hdr = NULL; + uint32_t stacksSize, logSize; + uint64_t tmpBufSize; + uint64_t hdrOffset, stacksOffset, logOffset; + errno_t error = EIO; + OSObject *obj = NULL; + OSString *str = NULL; + OSNumber *failStat = NULL; + struct vnode *vp = NULL; + vfs_context_t ctx = NULL; + + struct vnode_attr va; + IOBufferMemoryDescriptor *tmpBufDesc = NULL; + IOHibernateImageHeader *imageHdr; + + DLOG("sleepWakeDebugDumpFromFile\n"); + if ((swd_flags & SWD_LOGS_IN_FILE) == 0) + return; + + if (!OSCompareAndSwap(0, 1, &gRootDomain->swd_lock)) + return; - // count OSSymbol entries in NULL terminated settings array - while( settings[objCount] ) { - objCount++; - } - if(0 == objCount) return NULL; - pmso = new PMSettingObject; - if(!pmso || !pmso->init()) return NULL; - - pmso->parent = parent_arg; - pmso->func = handler_arg; - pmso->target = target_arg; - pmso->refcon = refcon_arg; - pmso->releaseAtCount = objCount + 1; // release when it has count+1 retains - - pmso->publishedFeatureID = (uint32_t *)IOMalloc(sizeof(uint32_t)*objCount); - if(pmso->publishedFeatureID) { - for(unsigned int i=0; ipublishFeature( settings[i]->getCStringNoCopy(), - supportedPowerSources, &pmso->publishedFeatureID[i] ); - } + hibernateFilename[0] = 0; + if ((obj = copyProperty(kIOHibernateFileKey))) + { + if ((str = OSDynamicCast(OSString, obj))) + strlcpy(hibernateFilename, str->getCStringNoCopy(), + sizeof(hibernateFilename)); + obj->release(); } - - return pmso; -} + if (!hibernateFilename[0]) { + DMSG("sleepWakeDebugDumpFromFile: Failed to hib file name\n"); + goto exit; + } + DLOG("sleepWakeDebugDumpFromFile: Hib file name %s\n", hibernateFilename); -void PMSettingObject::free(void) -{ - OSCollectionIterator *settings_iter; - OSSymbol *sym; - OSArray *arr; - int arr_idx; - int i; - int objCount = releaseAtCount - 1; - - if(publishedFeatureID) { - for(i=0; iremovePublishedFeature( publishedFeatureID[i] ); - } - } - - IOFree(publishedFeatureID, sizeof(uint32_t) * objCount); + /* Allocate a temp buffer to copy data between files */ + tmpBufSize = 2*4096; + tmpBufDesc = IOBufferMemoryDescriptor:: + inTaskWithOptions(kernel_task, kIODirectionOutIn | kIOMemoryMapperNone, + tmpBufSize, PAGE_SIZE); + + if (!tmpBufDesc) { + DMSG("sleepWakeDebugDumpFromFile: Fail to allocate temp buf\n"); + goto exit; } - - IORecursiveLockLock(parent->settingsCtrlLock); - - // Search each PM settings array in the kernel. - settings_iter = OSCollectionIterator::withCollection(parent->settingsCallbacks); - if(settings_iter) + + tmpBuf = tmpBufDesc->getBytesNoCopy(); + + ctx = vfs_context_create(vfs_context_current()); + if (vnode_open(hibernateFilename, (FREAD | O_NOFOLLOW), 0, + VNODE_LOOKUP_NOFOLLOW, &vp, ctx) != 0) { - while(( sym = OSDynamicCast(OSSymbol, settings_iter->getNextObject()) )) - { - arr = (OSArray *)parent->settingsCallbacks->getObject(sym); - arr_idx = arr->getNextIndexOfObject(this, 0); - if(-1 != arr_idx) { - // 'this' was found in the array; remove it - arr->removeObject(arr_idx); - } - } - - settings_iter->release(); + DMSG("sleepWakeDebugDumpFromFile: Failed to open the hibernate file %s\n", hibernateFilename); + goto exit; + } + VATTR_INIT(&va); + VATTR_WANTED(&va, va_nlink); + VATTR_WANTED(&va, va_data_alloc); + if (vp->v_type != VREG || + vnode_getattr(vp, &va, ctx) || va.va_nlink != 1) { + DMSG("sleepWakeDebugDumpFromFile: Bailing as this is not a regular file\n"); + goto exit; + } + + /* Read the sleepimage file header */ + rc = vn_rdwr(UIO_READ, vp, (char *)tmpBuf, round_page(sizeof(IOHibernateImageHeader)), 0, + UIO_SYSSPACE, IO_SKIP_ENCRYPTION|IO_SYNC|IO_NODELOCKED|IO_UNIT|IO_NOCACHE, + vfs_context_ucred(ctx), (int *) 0, + vfs_context_proc(ctx)); + if (rc != 0) { + DMSG("sleepWakeDebugDumpFromFile: Failed to read header size %lu(rc=%d)\n", round_page(sizeof(IOHibernateImageHeader)), rc); + goto exit; } - - IORecursiveLockUnlock(parent->settingsCtrlLock); - - super::free(); -} -void PMSettingObject::taggedRelease(const void *tag, const int when) const -{ - // We have n+1 retains - 1 per array that this PMSettingObject is a member - // of, and 1 retain to ourself. When we get a release with n+1 retains - // remaining, we go ahead and free ourselves, cleaning up array pointers - // in free(); + imageHdr = ((IOHibernateImageHeader *)tmpBuf); + if (imageHdr->signature != kIOHibernateHeaderDebugDataSignature) { + DMSG("sleepWakeDebugDumpFromFile: File header has unexpected value 0x%x\n", imageHdr->signature); + goto exit; + } - super::taggedRelease(tag, releaseAtCount); -} + /* Sleep/Wake debug header(swd_hdr) is at the beggining of the second block */ + hdrOffset = imageHdr->deviceBlockSize; + if (hdrOffset + sizeof(swd_hdr) >= va.va_data_alloc) { + DMSG("sleepWakeDebugDumpFromFile: header is crossing file size(0x%llx)\n", va.va_data_alloc); + goto exit; + } + DLOG("Reading swd_hdr len 0x%lx offset 0x%lx\n", round_page(sizeof(swd_hdr)), trunc_page(hdrOffset)); + /* Read the sleep/wake debug header(swd_hdr) */ + rc = vn_rdwr(UIO_READ, vp, (char *)tmpBuf, round_page(sizeof(swd_hdr)), trunc_page(hdrOffset), + UIO_SYSSPACE, IO_SKIP_ENCRYPTION|IO_SYNC|IO_NODELOCKED|IO_UNIT|IO_NOCACHE, + vfs_context_ucred(ctx), (int *) 0, + vfs_context_proc(ctx)); + if (rc != 0) { + DMSG("sleepWakeDebugDumpFromFile: Failed to debug read header size %lu. rc=%d\n", + round_page(sizeof(swd_hdr)), rc); + goto exit; + } + hdr = (swd_hdr *)((char *)tmpBuf + (hdrOffset - trunc_page(hdrOffset))); + if ((hdr->signature != SWD_HDR_SIGNATURE) || (hdr->alloc_size > SWD_BUF_SIZE) || + (hdr->spindump_offset > SWD_BUF_SIZE) || (hdr->spindump_size > SWD_BUF_SIZE)) { + DMSG("sleepWakeDebugDumpFromFile: Invalid data in debug header. sign:0x%x size:0x%x spindump_offset:0x%x spindump_size:0x%x\n", + hdr->signature, hdr->alloc_size, hdr->spindump_offset, hdr->spindump_size); + goto exit; + } + stacksSize = hdr->spindump_size; -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* Get stacks & log offsets in the image file */ + stacksOffset = hdrOffset + hdr->spindump_offset; + logOffset = hdrOffset + offsetof(swd_hdr, UUID); + logSize = sizeof(swd_hdr)-offsetof(swd_hdr, UUID); -#undef super -#define super IOService + error = sleepWakeDebugCopyFile(vp, ctx, (char *)tmpBuf, tmpBufSize, stacksOffset, + getDumpStackFilename(hdr), stacksSize, hdr->crc); + if (error == EFAULT) { + DMSG("sleepWakeDebugDumpFromFile: Stackshot CRC doesn't match\n"); + goto exit; + } + error = sleepWakeDebugCopyFile(vp, ctx, (char *)tmpBuf, tmpBufSize, logOffset, + getDumpLogFilename(hdr), logSize, 0); + if (error) { + DMSG("sleepWakeDebugDumpFromFile: Failed to write the log file(0x%x)\n", error); + goto exit; + } +exit: + if (error) { + // Write just the SleepWakeLog.dump with failure code + uint64_t fcode = 0; + const char *fname; + if (swd_flags & SWD_BOOT_BY_SW_WDOG) { + failStat = OSDynamicCast(OSNumber, getProperty(kIOPMSleepWakeFailureCodeKey)); + fcode = failStat->unsigned64BitValue(); + fname = kSleepWakeLogFilename; + } + else { + fname = kAppleOSXWatchdogLogFilename; + } + memset(PMStatusCode, 0x20, sizeof(PMStatusCode)); // Fill with spaces + PMStatusCode[sizeof(PMStatusCode)-1] = 0xa; // And an end-of-line at the end + snprintf(PMStatusCode, sizeof(PMStatusCode)-1, "Code: 0x%llx", fcode); + sleepWakeDebugSaveFile(fname, PMStatusCode, sizeof(PMStatusCode)); + } + gRootDomain->swd_lock = 0; -OSDefineMetaClassAndFinalStructors(IORootParent, IOService) + if (vp) vnode_close(vp, FREAD, ctx); + if (ctx) vfs_context_rele(ctx); + if (tmpBufDesc) tmpBufDesc->release(); -// 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[NUM_POWER_STATES] = +void IOPMrootDomain::sleepWakeDebugDumpFromMem(IOMemoryMap *logBufMap) { - {1,0,0,0,0,0,0,0,0,0,0,0}, // off (not used) - {1,0,RESTART_POWER,0,0,0,0,0,0,0,0,0}, // reset (not used) - {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 -}; + IOVirtualAddress srcBuf = NULL; + char *stackBuf = NULL, *logOffset = NULL; + int logSize = 0; -bool IORootParent::start( IOService * nub ) + errno_t error = EIO; + uint64_t bufSize = 0; + swd_hdr *hdr = NULL; + char PMStatusCode[100]; + OSNumber *failStat = NULL; + + if (!OSCompareAndSwap(0, 1, &gRootDomain->swd_lock)) + return; + + if ((logBufMap == 0) || ( (srcBuf = logBufMap->getVirtualAddress()) == 0) ) + { + DLOG("Nothing saved to dump to file\n"); + goto exit; + } + + hdr = (swd_hdr *)srcBuf; + bufSize = logBufMap->getLength(); + if (bufSize <= sizeof(swd_hdr)) + { + IOLog("SleepWake log buffer contents are invalid\n"); + goto exit; + } + + stackBuf = (char*)hdr+hdr->spindump_offset; + + error = sleepWakeDebugSaveFile(getDumpStackFilename(hdr), stackBuf, hdr->spindump_size); + if (error) goto exit; + + logOffset = (char*)hdr+offsetof(swd_hdr, UUID); + logSize = sizeof(swd_hdr)-offsetof(swd_hdr, UUID); + + error = sleepWakeDebugSaveFile(getDumpLogFilename(hdr), logOffset, logSize); + if (error) goto exit; + + hdr->spindump_size = 0; + error = 0; + +exit: + if (error) { + // Write just the SleepWakeLog.dump with failure code + uint64_t fcode = 0; + const char *sname, *lname; + swd_stackshot_hdr shdr; + + /* Try writing an empty stacks file */ + shdr.magic = SWD_STACKSHOTHDR_MAGIC; + shdr.size = 0; + + + if (swd_flags & SWD_BOOT_BY_SW_WDOG) { + failStat = OSDynamicCast(OSNumber, getProperty(kIOPMSleepWakeFailureCodeKey)); + fcode = failStat->unsigned64BitValue(); + lname = kSleepWakeLogFilename; + sname = kSleepWakeStackFilename; + } + else { + lname = kAppleOSXWatchdogLogFilename; + sname= kAppleOSXWatchdogStackFilename; + } + + sleepWakeDebugSaveFile(sname, (char*)(&shdr), sizeof(shdr)); + memset(PMStatusCode, 0x20, sizeof(PMStatusCode)); // Fill with spaces + PMStatusCode[sizeof(PMStatusCode)-1] = 0xa; // And an end-of-line at the end + snprintf(PMStatusCode, sizeof(PMStatusCode)-1, "Code: 0x%llx", fcode); + sleepWakeDebugSaveFile(lname, PMStatusCode, sizeof(PMStatusCode)); + } + gRootDomain->swd_lock = 0; +} + +IOMemoryMap *IOPMrootDomain::sleepWakeDebugRetrieve( ) { - mostRecentChange = ON_STATE; - super::start(nub); - attachToParent( getRegistryRoot(), gIOPowerPlane ); - PMinit(); - registerPowerDriver(this, patriarchPowerStates, NUM_POWER_STATES); - wakeSystem(); - powerOverrideOnPriv(); - return true; + IOVirtualAddress vaddr = NULL; + IOMemoryDescriptor * desc = NULL; + IOMemoryMap * logBufMap = NULL; + + uint32_t len; + addr64_t data[3]; + uint64_t bufSize = 0; + uint64_t crc = 0; + uint64_t newcrc = 0; + uint64_t paddr = 0; + swd_hdr *hdr = NULL; + bool ret = false; + char str[20]; + + + if (!OSCompareAndSwap(0, 1, &gRootDomain->swd_lock)) + return NULL; + + if (!PEReadNVRAMProperty(kIOSleepWakeDebugKey, 0, &len)) { + DLOG("No sleepWakeDebug note to read\n"); + goto exit; + } + + if (len == strlen("sleepimage")) { + str[0] = 0; + PEReadNVRAMProperty(kIOSleepWakeDebugKey, str, &len); + + if (!strncmp((char*)str, "sleepimage", strlen("sleepimage"))) { + DLOG("sleepWakeDebugRetrieve: in file logs\n"); + swd_flags |= SWD_LOGS_IN_FILE|SWD_VALID_LOGS; + goto exit; + } + } + else if (len == sizeof(addr64_t)*3) + PEReadNVRAMProperty(kIOSleepWakeDebugKey, data, &len); + else { + DLOG("Invalid sleepWakeDebug note length(%d)\n", len); + goto exit; + } + + + + DLOG("sleepWakeDebugRetrieve: data[0]:0x%llx data[1]:0x%llx data[2]:0x%llx\n", + data[0], data[1], data[2]); + DLOG("sleepWakeDebugRetrieve: in mem logs\n"); + bufSize = data[0]; + crc = data[1]; + paddr = data[2]; + if ( (bufSize <= sizeof(swd_hdr)) ||(bufSize > SWD_BUF_SIZE) || (crc == 0) ) + { + IOLog("SleepWake log buffer contents are invalid\n"); + return NULL; + } + + DLOG("size:0x%llx crc:0x%llx paddr:0x%llx\n", + bufSize, crc, paddr); + + + desc = IOMemoryDescriptor::withAddressRange( paddr, bufSize, + kIODirectionOutIn | kIOMemoryMapperNone, NULL); + if (desc == NULL) + { + IOLog("Fail to map SleepWake log buffer\n"); + goto exit; + } + + logBufMap = desc->map(); + + vaddr = logBufMap->getVirtualAddress(); + + + if ( (logBufMap->getLength() <= sizeof(swd_hdr)) || (vaddr == NULL) ) { + IOLog("Fail to map SleepWake log buffer\n"); + goto exit; + } + + hdr = (swd_hdr *)vaddr; + if (hdr->spindump_offset+hdr->spindump_size > bufSize) + { + IOLog("SleepWake log buffer contents are invalid\n"); + goto exit; + } + + hdr->crc = crc; + newcrc = crc32(0, (void *)((char*)vaddr+hdr->spindump_offset), + hdr->spindump_size); + if (newcrc != crc) { + IOLog("SleepWake log buffer contents are invalid\n"); + goto exit; + } + + ret = true; + swd_flags |= SWD_LOGS_IN_MEM | SWD_VALID_LOGS; + + +exit: + PERemoveNVRAMProperty(kIOSleepWakeDebugKey); + if (!ret) { + if (logBufMap) logBufMap->release(); + logBufMap = 0; + } + if (desc) desc->release(); + gRootDomain->swd_lock = 0; + + return logBufMap; } -void IORootParent::shutDownSystem( void ) +#else + +void IOPMrootDomain::sleepWakeDebugTrig(bool restart) { } -void IORootParent::restartSystem( void ) +void IOPMrootDomain::takeStackshot(bool restart, bool isOSXWatchdog) { +#pragma unused(restart) +#pragma unused(isOSXWatchdog) } -void IORootParent::sleepSystem( void ) +void IOPMrootDomain::sleepWakeDebugMemAlloc( ) { - mostRecentChange = SLEEP_STATE; - changePowerStateToPriv(SLEEP_STATE); +} +void IOPMrootDomain::sleepWakeDebugDumpFromMem(IOMemoryMap *map) +{ +} +errno_t IOPMrootDomain::sleepWakeDebugCopyFile( + struct vnode *srcVp, + vfs_context_t srcCtx, + char *tmpBuf, uint64_t tmpBufSize, + uint64_t srcOffset, + const char *dstFname, + uint64_t numBytes, + uint32_t crc) +{ + return EIO; } -void IORootParent::dozeSystem( void ) +void IOPMrootDomain::sleepWakeDebugDumpFromFile() { - 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. +IOMemoryMap *IOPMrootDomain::sleepWakeDebugRetrieve( ) +{ + return NULL; +} -// In idle sleep, do nothing because the parent is still on and the root can freely change state. +void IOPMrootDomain::sleepWakeDebugEnableWdog() +{ +} -void IORootParent::sleepToDoze( void ) +bool IOPMrootDomain::sleepWakeDebugIsWdogEnabled() { - if ( mostRecentChange == SLEEP_STATE ) { - changePowerStateToPriv(DOZE_STATE); - } + return false; } -void IORootParent::wakeSystem( void ) +errno_t IOPMrootDomain::sleepWakeDebugSaveFile(const char *name, char *buf, int len) { - mostRecentChange = ON_STATE; - changePowerStateToPriv(ON_STATE); + return 0; } + +#endif +