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