1/*
2 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23/*
24 * Copyright (c) 2002 Apple Computer, Inc.  All rights reserved.
25 *
26 * HISTORY
27 *
28 * 29-Aug-02 ebold created
29 *
30 */
31
32#include <sys/stat.h>
33#include <sys/fcntl.h>
34#include <sys/sysctl.h>
35#include <sys/mount.h>
36#include <unistd.h>
37#include <dlfcn.h>
38#include <IOKit/pwr_mgt/IOPMLibPrivate.h>
39#include <IOKit/IOHibernatePrivate.h>
40#include <pthread.h>
41
42#include "PMSettings.h"
43#include "BatteryTimeRemaining.h"
44#include "PrivateLib.h"
45#include "PMStore.h"
46#include "PMAssertions.h"
47
48/* Arguments to CopyPMSettings functions */
49enum {
50    kIOPMUnabridgedSettings = false,
51    kIOPMRemoveUnsupportedSettings = true
52};
53
54/* Global - energySettings
55 * Keeps track of current Energy Saver settings.
56 */
57static CFDictionaryRef                  energySettings = NULL;
58
59/* Global - currentPowerSource
60 * Keeps track of current power - battery or AC
61 */
62static CFStringRef                      currentPowerSource = NULL;
63
64/* g_overrides
65 * Tracks active PM usage profiles
66 */
67static unsigned long                    g_overrides = 0;
68static unsigned long                    gLastOverrideState = 0;
69static unsigned long                    gSleepSetting = -1;
70
71static io_connect_t                     gPowerManager;
72
73/* Tracking sleeping state */
74static unsigned long                    deferredPSChangeNotify = 0;
75static unsigned long                    _pmcfgd_impendingSleep = 0;
76
77
78/* Forward Declarations */
79static CFDictionaryRef _copyPMSettings(bool removeUnsupported);
80static IOReturn activate_profiles(
81        CFDictionaryRef                 d,
82        CFStringRef                     s,
83        bool                            removeUnsupported);
84
85
86/* overrideSetting
87 * Must be followed by a call to activateSettingOverrides
88 */
89__private_extern__ void overrideSetting
90(
91    int             bit,
92    int             val
93)
94{
95    if(val) {
96        g_overrides |= bit;
97    } else {
98        g_overrides &= ~bit;
99    }
100}
101
102
103__private_extern__ bool
104GetPMSettingBool(CFStringRef which)
105{
106    CFDictionaryRef     current_settings;
107    CFNumberRef         n;
108    int                 nint = 0;
109    CFStringRef         pwrSrc;
110
111    if (!energySettings || !which)
112        return false;
113
114
115    if (_getPowerSource() == kBatteryPowered)
116       pwrSrc = CFSTR(kIOPMBatteryPowerKey);
117    else
118       pwrSrc = CFSTR(kIOPMACPowerKey);
119    // Don't use 'currentPowerSource' here as that gets updated
120    // little slowly after this function is called to get a setting
121    // on new power source.
122    current_settings = (CFDictionaryRef)isA_CFDictionary(
123                         CFDictionaryGetValue(energySettings, pwrSrc));
124
125    if (current_settings) {
126        n = CFDictionaryGetValue(current_settings, which);
127        if (n) {
128            CFNumberGetValue(n, kCFNumberIntType, &nint);
129        }
130        return (0 != nint);
131    }
132    return false;
133}
134
135
136
137__private_extern__ IOReturn
138GetPMSettingNumber(CFStringRef which, int64_t *value)
139{
140    CFDictionaryRef     current_settings;
141    CFNumberRef         n;
142    CFStringRef         pwrSrc;
143
144    if (!energySettings || !which)
145        return kIOReturnBadArgument;
146
147    if (_getPowerSource() == kBatteryPowered)
148       pwrSrc = CFSTR(kIOPMBatteryPowerKey);
149    else
150       pwrSrc = CFSTR(kIOPMACPowerKey);
151    // Don't use 'currentPowerSource' here as that gets updated
152    // little slowly after this function is called to get a setting
153    // on new power source.
154    current_settings = (CFDictionaryRef)isA_CFDictionary(
155                         CFDictionaryGetValue(energySettings, pwrSrc));
156
157    if (current_settings) {
158        n = CFDictionaryGetValue(current_settings, which);
159        if (isA_CFNumber(n)) {
160            CFNumberGetValue(n, kCFNumberSInt64Type, value);
161            return kIOReturnSuccess;
162        }
163    }
164    return kIOReturnError;
165}
166
167/* Returns Display sleep time in minutes */
168__private_extern__ IOReturn
169getDisplaySleepTimer(uint32_t *displaySleepTimer)
170{
171    CFDictionaryRef     current_settings;
172
173    if (!energySettings || !displaySleepTimer)
174        return kIOReturnError;
175
176    current_settings = (CFDictionaryRef)isA_CFDictionary(
177                            CFDictionaryGetValue(energySettings, currentPowerSource));
178    if (getAggressivenessValue(current_settings, CFSTR(kIOPMDisplaySleepKey),
179                                    kCFNumberSInt32Type, displaySleepTimer) ) {
180        return kIOReturnSuccess;
181    }
182
183    return kIOReturnError;
184}
185
186/* Returns Idle sleep time in minutes */
187__private_extern__ IOReturn
188getIdleSleepTimer(unsigned long *idleSleepTimer)
189{
190    CFDictionaryRef     current_settings;
191
192    if (!energySettings || !idleSleepTimer)
193        return kIOReturnError;
194
195    if (gSleepSetting != -1) {
196        *idleSleepTimer = gSleepSetting;
197        return kIOReturnSuccess;
198    }
199
200    current_settings = (CFDictionaryRef)isA_CFDictionary(
201                            CFDictionaryGetValue(energySettings, currentPowerSource));
202    if (getAggressivenessValue(current_settings, CFSTR(kIOPMSystemSleepKey),
203                                    kCFNumberSInt32Type, (uint32_t *)idleSleepTimer) ) {
204        return kIOReturnSuccess;
205    }
206
207    return kIOReturnError;
208}
209
210// Providing activateSettingsOverrides to PMAssertions.c
211// So that it may set multiple assertions without triggering a prefs
212// re-evaluate each time. PMAssertions.c can call overrideSetting() n times
213// and only call activateSettingsOverrides once.
214__private_extern__ void
215activateSettingOverrides(void)
216{
217    if (!energySettings)
218        return;
219
220    if (gLastOverrideState != g_overrides)
221    {
222        if ((kPMPreventIdleSleep == (gLastOverrideState ^ g_overrides))
223         && (-1 != gSleepSetting)) do
224        {
225            static io_connect_t gIOPMConnection = MACH_PORT_NULL;
226            IOReturn kr;
227
228            if (!gIOPMConnection) gIOPMConnection = IOPMFindPowerManagement(0);
229            if (!gIOPMConnection) break;
230            kr = IOPMSetAggressiveness(gIOPMConnection, kPMMinutesToSleep,
231                        (kPMPreventIdleSleep & g_overrides) ? 0 : gSleepSetting);
232            if (kIOReturnSuccess != kr)
233            {
234            gIOPMConnection = MACH_PORT_NULL;
235            break;
236            }
237            gLastOverrideState = g_overrides;
238            return;
239        }
240        while (false);
241
242        gLastOverrideState = g_overrides;
243        activate_profiles( energySettings,
244                            currentPowerSource,
245                            kIOPMRemoveUnsupportedSettings);
246    }
247}
248
249__private_extern__ void
250PMSettingsSleepWakeNotification(natural_t messageType)
251{
252    // note: The sleepwake handler in pmconfigd.c does all the dirty work like
253    // acknowledging this sleep notification with IOAllowPowerChange(). That's
254    // why we don't make that call here.
255
256    switch (messageType) {
257        case kIOMessageSystemWillSleep:
258            _pmcfgd_impendingSleep = 1;
259            break;
260
261        case kIOMessageSystemHasPoweredOn:
262            _pmcfgd_impendingSleep = 0;
263            if(deferredPSChangeNotify)
264            {
265                deferredPSChangeNotify = 0;
266                _pmcfgd_impendingSleep = 0;
267
268                if(currentPowerSource && CFEqual(currentPowerSource, CFSTR(kIOPMACPowerKey)))
269                {
270                    // ac power
271                    IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMExternalPower);
272                } else {
273                    // battery power
274                    IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMInternalPower);
275                }
276            }
277            break;
278    }
279
280    return;
281}
282
283__private_extern__ CFDictionaryRef
284PMSettings_CopyActivePMSettings(void)
285{
286    CFDictionaryRef         copy_all_settings;
287    CFDictionaryRef         energySettings;
288    CFDictionaryRef         return_val;
289
290    copy_all_settings = _copyPMSettings(kIOPMRemoveUnsupportedSettings);
291    if(!copy_all_settings) return NULL;
292    energySettings = isA_CFDictionary(CFDictionaryGetValue(copy_all_settings,currentPowerSource));
293    if(energySettings)
294        return_val = CFDictionaryCreateCopy(kCFAllocatorDefault, energySettings);
295    else
296        return_val = NULL;
297
298    CFRelease(copy_all_settings);
299    return return_val;
300}
301
302
303/* _DWBT_enabled() returns true if the system supports DWBT and if user has opted in */
304__private_extern__ bool _DWBT_enabled(void)
305{
306   CFDictionaryRef     current_settings;
307   CFNumberRef         n;
308   int                 nint = 0;
309
310
311   if (!energySettings)
312       return false;
313
314    current_settings = (CFDictionaryRef)isA_CFDictionary(
315                         CFDictionaryGetValue(energySettings, CFSTR(kIOPMACPowerKey)));
316    if (current_settings) {
317        n = CFDictionaryGetValue(current_settings, CFSTR(kIOPMDarkWakeBackgroundTaskKey));
318        if (n) {
319            CFNumberGetValue(n, kCFNumberIntType, &nint);
320        }
321        return (0 != nint);
322    }
323
324    return false;
325}
326
327/* _DWBT_allowed() tells if a DWBT wake can be scheduled at this moment */
328__private_extern__ bool
329_DWBT_allowed(void)
330{
331#if TARGET_OS_EMBEDDED
332    return false;
333#else
334    return ( (GetPMSettingBool(CFSTR(kIOPMDarkWakeBackgroundTaskKey))) &&
335             (kACPowered == _getPowerSource()) );
336#endif
337
338}
339
340/* Is Sleep Services allowed */
341__private_extern__ bool _SS_allowed(void)
342{
343#if TARGET_OS_EMBEDDED
344    return false;
345#else
346    if (_DWBT_allowed())
347        return true;
348
349    return ( (GetPMSettingBool(CFSTR(kIOPMDarkWakeBackgroundTaskKey))) &&
350             (kBatteryPowered == _getPowerSource()) );
351#endif
352
353}
354
355/* _copyPMSettings
356 * The returned dictionary represents the "currently selected"
357 * per-power source settings.
358 */
359static CFDictionaryRef
360_copyPMSettings(bool removeUnsupported)
361{
362    if(removeUnsupported) {
363        return IOPMCopyActivePMPreferences();
364    } else {
365        return IOPMCopyUnabridgedActivePMPreferences();
366    }
367}
368
369/**************************************************/
370
371 /* activate_profiles
372 *
373 * A wrapper for ActivatePMSettings. We get a chance here to apply modifications
374 * to the Energy Saver settings before sending them to the kernel.
375 * Profiles (like LidClosed or ForceLowSpeed) have affects like accelerating idle
376 * times or forcing ReduceProcessorSpeed on.
377 */
378static IOReturn
379activate_profiles(CFDictionaryRef d, CFStringRef s, bool removeUnsupported)
380{
381    CFDictionaryRef                     energy_settings;
382    CFDictionaryRef                     activePMPrefs = NULL;
383    CFMutableDictionaryRef              profiles_activated;
384    IOReturn                            ret;
385    CFNumberRef                         n1, n0;
386    CFNumberRef                         sleepSetting;
387    int                                 one = 1;
388    int                                 zero = 0;
389
390    if(NULL == d) {
391        return kIOReturnBadArgument;
392    }
393
394    if(NULL == s) {
395        s = CFSTR(kIOPMACPowerKey);
396    }
397
398    energy_settings = (CFDictionaryRef)isA_CFDictionary(CFDictionaryGetValue(d, s));
399    if (!energy_settings) {
400        return kIOReturnError;
401    }
402
403
404    sleepSetting = (CFNumberRef)isA_CFNumber(CFDictionaryGetValue(energy_settings, CFSTR(kIOPMSystemSleepKey)));
405    if (sleepSetting) {
406        CFNumberGetValue(sleepSetting, kCFNumberLongType, &gSleepSetting);
407    }
408
409    if(g_overrides)
410    {
411        profiles_activated = CFDictionaryCreateMutableCopy(kCFAllocatorDefault,
412            CFDictionaryGetCount(energy_settings), energy_settings);
413        if(!profiles_activated)
414            return kIOReturnError;
415
416        n1 = CFNumberCreate(0, kCFNumberIntType, &one);
417        n0 = CFNumberCreate(0, kCFNumberIntType, &zero);
418        // If the "force low speed" profile is set, flip the ReduceSpeed bit on
419        if(g_overrides & kPMForceLowSpeedProfile)
420        {
421            if(n1) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMReduceSpeedKey), n1);
422        }
423
424        if(g_overrides & kPMForceHighSpeed)
425        {
426            if(n0) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMReduceSpeedKey), n0);
427            if(n0) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMDynamicPowerStepKey), n0);
428        }
429
430        if(g_overrides & kPMPreventIdleSleep)
431        {
432            if(n0) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMSystemSleepKey), n0);
433        }
434
435        if(g_overrides & kPMPreventDisplaySleep)
436        {
437            if(n0) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMDisplaySleepKey), n0);
438        }
439        if (g_overrides & kPMPreventDiskSleep)
440        {
441            if (n0) CFDictionarySetValue(profiles_activated, CFSTR(kIOPMDiskSleepKey), n0);
442        }
443
444
445        if (n0)
446            CFRelease(n0);
447        if (n1)
448            CFRelease(n1);
449
450        ret = ActivatePMSettings(profiles_activated, removeUnsupported);
451
452        CFRelease(profiles_activated);
453    } else {
454        ret = ActivatePMSettings(energy_settings, removeUnsupported);
455    }
456
457    activePMPrefs = SCDynamicStoreCopyValue(_getSharedPMDynamicStore(),
458                                            CFSTR(kIOPMDynamicStoreSettingsKey));
459
460    // If there isn't currently a value for kIOPMDynamicStoreSettingsKey,
461    //   or the current value is different than the new value,
462    // Put the new settings in the SCDynamicStore for interested apps.
463
464    if( !isA_CFDictionary(activePMPrefs) || !CFEqual(activePMPrefs, energy_settings) )
465    {
466        PMStoreSetValue(CFSTR(kIOPMDynamicStoreSettingsKey), energy_settings);
467    }
468
469    if (activePMPrefs)
470        CFRelease(activePMPrefs);
471
472    return ret;
473}
474
475
476__private_extern__ void PMSettings_prime(void)
477{
478
479    // Open a connection to the Power Manager.
480    gPowerManager = IOPMFindPowerManagement(MACH_PORT_NULL);
481    if (gPowerManager == 0) return;
482
483    // Activate non-power source specific, PM settings
484    // namely disable sleep, where appropriate
485    IOPMActivateSystemPowerSettings();
486
487    /*
488     * determine current power source for separate Battery/AC settings
489     */
490    int powersource = getActivePSType();
491    if (kIOPSProvidedByExternalBattery == powersource) {
492        currentPowerSource = CFSTR(kIOPMUPSPowerKey);
493    } else if (kIOPSProvidedByBattery == powersource) {
494        currentPowerSource = CFSTR(kIOPMBatteryPowerKey);
495    } else {
496        currentPowerSource = CFSTR(kIOPMACPowerKey);
497    }
498
499    // load the initial configuration from the database
500    energySettings = _copyPMSettings(kIOPMRemoveUnsupportedSettings);
501
502    // send the initial configuration to the kernel
503    if(energySettings) {
504        activate_profiles( energySettings,
505                            currentPowerSource,
506                            kIOPMRemoveUnsupportedSettings);
507    }
508
509    // send initial power source info to drivers
510    if(CFEqual(currentPowerSource, CFSTR(kIOPMACPowerKey)))
511         IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMExternalPower);
512    else IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMInternalPower);
513}
514
515__private_extern__ void
516PMSettingsSupportedPrefsListHasChanged(void)
517{
518    // The "supported prefs have changed" notification is generated
519    // by a kernel driver annnouncing a new supported feature, or unloading
520    // and removing support. Let's re-evaluate our known settings.
521
522    PMSettingsPrefsHaveChanged();
523}
524
525
526/* ESPrefsHaveChanged
527 *
528 * Is the handler that configd calls when someone "applies" new Energy Saver
529 * Preferences. Since the preferences have probably changed, we re-read them
530 * from disk and transmit the new settings to the kernel.
531 */
532__private_extern__ void
533PMSettingsPrefsHaveChanged(void)
534{
535    // re-blast system-wide settings
536    IOPMActivateSystemPowerSettings();
537
538    // re-read preferences into memory
539    if(energySettings) CFRelease(energySettings);
540
541    energySettings = _copyPMSettings(kIOPMRemoveUnsupportedSettings);
542
543    // push new preferences out to the kernel
544    if(isA_CFDictionary(energySettings)) {
545        activate_profiles(energySettings,
546                            currentPowerSource,
547                            kIOPMRemoveUnsupportedSettings);
548    } else {
549        if (energySettings) {
550            CFRelease(energySettings);
551        }
552        energySettings = NULL;
553    }
554    PMAssertions_SettingsHaveChanged();
555
556    return;
557}
558
559
560/* PMSettingsPSChange
561 *
562 * A power source has changed. Has the current power provider changed?
563 * If so, get new settings down to the kernel.
564 */
565__private_extern__ void PMSettingsPSChange(void)
566{
567    CFStringRef     newPowerSource;
568
569    int powersource = getActivePSType();
570    if (kIOPSProvidedByExternalBattery == powersource) {
571        newPowerSource = CFSTR(kIOPMUPSPowerKey);
572    } else if (kIOPSProvidedByBattery == powersource) {
573        newPowerSource = CFSTR(kIOPMBatteryPowerKey);
574    } else {
575        newPowerSource = CFSTR(kIOPMACPowerKey);
576    }
577
578    if(!currentPowerSource
579       || !CFEqual(currentPowerSource, newPowerSource))
580    {
581        currentPowerSource = newPowerSource;
582
583        // Are we in the middle of a sleep?
584        if(!_pmcfgd_impendingSleep)
585        {
586            // If not, tell drivers that the power source changed
587            if(CFEqual(CFSTR(kIOPMACPowerKey), currentPowerSource))
588            {
589                // Running off of external power
590                IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMExternalPower);
591            } else {
592                // This is either battery power or UPS power, "internal power"
593                IOPMSetAggressiveness(gPowerManager, kPMPowerSource, kIOPMInternalPower);
594            }
595        } else {
596            // If we WERE in the middle of a sleep, delay notification until we're awake.
597            deferredPSChangeNotify = 1;
598        }
599
600        if(energySettings) {
601            activate_profiles( energySettings,
602                                currentPowerSource,
603                                kIOPMRemoveUnsupportedSettings);
604        }
605    }
606
607}
608
609/* activateForcedSettings
610 *
611 */
612__private_extern__ IOReturn
613_activateForcedSettings(CFDictionaryRef forceSettings)
614{
615    // Calls to "pmset force" end up here
616    return activate_profiles( forceSettings,
617                        currentPowerSource,
618                        kIOPMRemoveUnsupportedSettings);
619}
620
621
622