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 * HISTORY
25 *
26 */
27
28
29#include <sys/cdefs.h>
30#include <notify.h>
31
32#include <CoreFoundation/CoreFoundation.h>
33#include "IOSystemConfiguration.h"
34#include "IOPSKeys.h"
35#include "IOPowerSources.h"
36#include "IOPowerSourcesPrivate.h"
37#include <IOKit/pwr_mgt/IOPMLibPrivate.h>
38#include <notify.h>
39
40#ifndef kIOPSDynamicStorePathKey
41#define kIOPSDynamicStorePathKey kIOPSDynamicStorePath
42#endif
43
44#ifndef kIOPSDynamicStoreLowBattPathKey
45#define kIOPSDynamicStoreLowBattPathKey "/IOKit/LowBatteryWarning"
46#endif
47
48#ifndef kIOPSDynamicStorePowerAdapterKey
49#define kIOPSDynamicStorePowerAdapterKey "/IOKit/PowerAdapter"
50#endif
51
52#if TARGET_OS_EMBEDDED
53#define kIOPSDynamicStoreFullPath "State:/IOKit/PowerSources/InternalBattery-0"
54#endif
55
56IOPSLowBatteryWarningLevel IOPSGetBatteryWarningLevel(void)
57{
58    SCDynamicStoreRef   store = NULL;
59    CFStringRef         key = NULL;
60    CFNumberRef         scWarnValue = NULL;
61    int                 return_level = kIOPSLowBatteryWarningNone;
62
63    store = SCDynamicStoreCreate(kCFAllocatorDefault,
64                            CFSTR("IOKit Power Source Copy"), NULL, NULL);
65    if (!store)
66        goto SAD_EXIT;
67
68    key = SCDynamicStoreKeyCreate(
69                            kCFAllocatorDefault,
70                            CFSTR("%@%@"),
71                            _io_kSCDynamicStoreDomainState,
72                            CFSTR(kIOPSDynamicStoreLowBattPathKey));
73    if (!key)
74        goto SAD_EXIT;
75
76    scWarnValue = isA_CFNumber(SCDynamicStoreCopyValue(store, key));
77    if (scWarnValue) {
78
79        CFNumberGetValue(scWarnValue, kCFNumberIntType, &return_level);
80
81        CFRelease(scWarnValue);
82        scWarnValue = NULL;
83    }
84
85SAD_EXIT:
86    if (store) CFRelease(store);
87    if (key) CFRelease(key);
88    return return_level;
89}
90
91
92// powerd uses these same constants to define the bitfields in the
93// kIOPSTimeRemainingNotificationKey key
94#define     _kPSTimeRemainingNotifyExternalBit       (1 << 16)
95#define     _kPSTimeRemainingNotifyChargingBit       (1 << 17)
96#define     _kPSTimeRemainingNotifyUnknownBit        (1 << 18)
97#define     _kPSTimeRemainingNotifyValidBit          (1 << 19)
98#define     _kSecondsPerMinute                       ((CFTimeInterval)60.0)
99CFTimeInterval IOPSGetTimeRemainingEstimate(void)
100{
101    int                 myNotifyToken = 0;
102    uint64_t            packedBatteryData = 0;
103    int                 myNotifyStatus = 0;
104
105    myNotifyStatus = notify_register_check(kIOPSTimeRemainingNotificationKey, &myNotifyToken);
106
107    if (NOTIFY_STATUS_OK != myNotifyStatus) {
108        // FAILURE: We return an optimistic unlimited time remaining estimate
109        // if we don't know the truth.
110        return kIOPSTimeRemainingUnlimited;
111    }
112
113    notify_get_state(myNotifyToken, &packedBatteryData);
114
115    notify_cancel(myNotifyToken);
116
117    if (!(packedBatteryData & _kPSTimeRemainingNotifyValidBit)
118        || (packedBatteryData & _kPSTimeRemainingNotifyExternalBit)) {
119        return kIOPSTimeRemainingUnlimited;
120    }
121
122    if (packedBatteryData & _kPSTimeRemainingNotifyUnknownBit) {
123        return kIOPSTimeRemainingUnknown;
124    }
125
126    return (_kSecondsPerMinute * (CFTimeInterval)(packedBatteryData & 0xFFFF));
127}
128
129
130CFDictionaryRef IOPSCopyExternalPowerAdapterDetails(void)
131{
132    SCDynamicStoreRef   store = NULL;
133    CFStringRef         key = NULL;
134    CFDictionaryRef     ret_dict = NULL;
135
136    store = SCDynamicStoreCreate(kCFAllocatorDefault,
137                            CFSTR("IOKit Power Source Copy"), NULL, NULL);
138    if (!store)
139        goto SAD_EXIT;
140
141    key = SCDynamicStoreKeyCreate(
142                            kCFAllocatorDefault,
143                            CFSTR("%@%@"),
144                            _io_kSCDynamicStoreDomainState,
145                            CFSTR(kIOPSDynamicStorePowerAdapterKey));
146    if (!key)
147        goto SAD_EXIT;
148
149    ret_dict = isA_CFDictionary(SCDynamicStoreCopyValue(store, key));
150
151SAD_EXIT:
152    if (store) CFRelease(store);
153    if (key) CFRelease(key);
154    return ret_dict;
155}
156
157static CFArrayRef CreatePSKeysArray(void)
158{
159    CFStringRef                 ps_match = NULL;
160    CFMutableArrayRef           ps_arr = NULL;
161
162#if TARGET_OS_EMBEDDED
163    // Doing a regex match on iOS is unnecessary as there is always only 1
164    // power source, whose identity is known.
165    // Optimization for <rdar://problem/11177160>
166    ps_match = SCDynamicStoreKeyCreate(kCFAllocatorDefault,
167                                       CFSTR(kIOPSDynamicStoreFullPath));
168#else
169    // Create regular expression to match all Power Sources
170    ps_match = SCDynamicStoreKeyCreate(
171                                       kCFAllocatorDefault,
172                                       CFSTR("%@%@/%@"),
173                                       _io_kSCDynamicStoreDomainState,
174                                       CFSTR(kIOPSDynamicStorePath),
175                                       _io_kSCCompAnyRegex);
176#endif
177
178    if(!ps_match) return NULL;
179    ps_arr = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
180    if(!ps_arr) return NULL;
181    CFArrayAppendValue(ps_arr, ps_match);
182    CFRelease(ps_match);
183
184    return (CFArrayRef)ps_arr;
185}
186
187
188/***
189 Returns a blob of Power Source information in an opaque CFTypeRef. Clients should
190 not actually look directly at data in the CFTypeRef - they should use the accessor
191 functions IOPSCopyPowerSourcesList and IOPSGetPowerSourceDescription, instead.
192 Returns NULL if errors were encountered.
193 Return: Caller must CFRelease() the return value when done.
194***/
195CFTypeRef IOPSCopyPowerSourcesInfo(void) {
196    SCDynamicStoreRef   store = NULL;
197    CFArrayRef          ps_arr = NULL;
198    CFDictionaryRef     power_sources = NULL;
199
200    // Open connection to SCDynamicStore
201    store = SCDynamicStoreCreate(kCFAllocatorDefault,
202                CFSTR("IOKit Power Source Copy"), NULL, NULL);
203    if(!store) {
204        goto exit;
205     }
206
207    ps_arr = CreatePSKeysArray();
208
209#if TARGET_OS_EMBEDDED
210    // No need to pattern check on embedded. There is always only one power source
211    // <rdar://problem/11177160>
212    power_sources = SCDynamicStoreCopyMultiple(store, ps_arr, NULL);
213#else
214    // Copy multiple Power Sources into dictionary
215    power_sources = SCDynamicStoreCopyMultiple(store, NULL, ps_arr);
216#endif
217
218exit:
219
220    if (ps_arr)
221        CFRelease(ps_arr);
222    if (store)
223        CFRelease(store);
224
225    if(!power_sources) {
226        // On failure, we return an empty dictionary instead of NULL
227        power_sources = CFDictionaryCreate( kCFAllocatorDefault,
228                                  NULL, NULL, 0,
229                                  &kCFTypeDictionaryKeyCallBacks,
230                                  &kCFTypeDictionaryValueCallBacks);
231    }
232
233    // Return CFDictionary as opaque CFTypeRef
234    return (CFTypeRef)power_sources;
235}
236
237/***
238 Arguments - Takes the CFTypeRef returned by IOPSCopyPowerSourcesInfo()
239 Returns a CFArray of Power Source handles, each of type CFTypeRef.
240 The caller shouldn't look directly at the CFTypeRefs, but should use
241 IOPSGetPowerSourceDescription on each member of the CFArray.
242 Returns NULL if errors were encountered.
243 Return: Caller must CFRelease() the returned CFArray.
244***/
245CFArrayRef IOPSCopyPowerSourcesList(CFTypeRef blob) {
246    int                 count;
247    void                **keys;
248    CFArrayRef          arr;
249    bool                failure = false;
250
251    // Check that the argument is actually a CFDictionary
252    if( !blob
253        || (CFGetTypeID(blob) != CFDictionaryGetTypeID()) )
254    {
255        failure = true;
256        goto exit;
257    }
258
259    // allocate buffers for keys and values
260    count = CFDictionaryGetCount((CFDictionaryRef)blob);
261    keys = (void **)malloc(count * sizeof(void *));
262    if(!keys) {
263        failure = true;
264        goto exit;
265    }
266
267    // Get keys and values from CFDictionary
268    CFDictionaryGetKeysAndValues((CFDictionaryRef)blob, (const void **)keys, NULL);
269
270    // Create CFArray from keys
271    arr = CFArrayCreate(kCFAllocatorDefault, (const void **)keys, count, &kCFTypeArrayCallBacks);
272
273    // free keys and values
274    free(keys);
275exit:
276    if(failure) {
277        // On failure, we return an empty array instead of NULL
278        arr = CFArrayCreate( 0, NULL, 0, &kCFTypeArrayCallBacks);
279    }
280    // Return CFArray
281    return arr;
282}
283
284/***
285 Arguments - Takes one of the CFTypeRefs in the CFArray returned by
286 IOPSCopyPowerSourcesList
287 Returns a CFDictionary with specific information about the power source.
288 See IOKit.framework/Headers/ups/IOUPSKeys.h for specific fields.
289 Return: Caller should not CFRelease the returned CFArray
290***/
291CFDictionaryRef IOPSGetPowerSourceDescription(CFTypeRef blob, CFTypeRef ps) {
292    // Check that the info is a CFDictionary
293    if( !(blob && (CFGetTypeID(blob)==CFDictionaryGetTypeID())) )
294        return NULL;
295
296    // Check that the Power Source is a CFString
297    if( !(ps && (CFGetTypeID(ps)==CFStringGetTypeID())) )
298        return NULL;
299
300    // Extract the CFDictionary of Battery Info
301    // and return
302    return CFDictionaryGetValue(blob, ps);
303}
304
305static CFStringRef getPowerSourceState(CFTypeRef blob, CFTypeRef id)
306{
307    CFDictionaryRef the_dict = IOPSGetPowerSourceDescription(blob, id);
308    return CFDictionaryGetValue(the_dict, CFSTR(kIOPSPowerSourceStateKey));
309}
310
311/* IOPSGetProvidingPowerSourceType
312 * Argument:
313 *  ps_blob: as returned from IOPSCopyPowerSourcesInfo()
314 * Returns:
315 *  The current system power source.
316 *  CFSTR("AC Power"), CFSTR("Battery Power"), CFSTR("UPS Power")
317 */
318CFStringRef IOPSGetProvidingPowerSourceType(CFTypeRef ps_blob)
319{
320    CFTypeRef       the_ups = NULL;
321    CFTypeRef       the_batt = NULL;
322    CFStringRef     ps_state = NULL;
323
324
325    if(kCFBooleanFalse == IOPSPowerSourceSupported(ps_blob, CFSTR(kIOPMBatteryPowerKey)))
326    {
327        if(kCFBooleanFalse == IOPSPowerSourceSupported(ps_blob, CFSTR(kIOPMUPSPowerKey))) {
328            // no batteries, no UPS -> AC Power
329            return CFSTR(kIOPMACPowerKey);
330        } else {
331            // optimization opportunity: needless loops inside IOPSGetActiveUPS
332            the_ups = IOPSGetActiveUPS(ps_blob);
333            if(!the_ups) return CFSTR(kIOPMACPowerKey);
334            ps_state = getPowerSourceState(ps_blob, the_ups);
335            if(ps_state && CFEqual(ps_state, CFSTR(kIOPSACPowerValue)))
336            {
337                // no batteries, yes UPS, UPS is running off of AC power -> AC Power
338                return CFSTR(kIOPMACPowerKey);
339            } else if(ps_state && CFEqual(ps_state, CFSTR(kIOPSBatteryPowerValue)))
340            {
341                // no batteries, yes UPS, UPS is running off of Battery power -> UPS Power
342                return CFSTR(kIOPMUPSPowerKey);
343            }
344
345        }
346        // Error in the data we were passed
347        return CFSTR(kIOPMACPowerKey);
348    } else {
349        // Optimization opportunity: needless loops inside IOPSGetActiveBattery
350        the_batt = IOPSGetActiveBattery(ps_blob);
351        if(!the_batt) return CFSTR(kIOPMACPowerKey);
352        ps_state = getPowerSourceState(ps_blob, the_batt);
353        if(ps_state && CFEqual(ps_state, CFSTR(kIOPSBatteryPowerValue)))
354        {
355            // Yes batteries, yes running on battery power -> Battery power
356            return CFSTR(kIOPMBatteryPowerKey);
357        } else {
358            // batteries are on AC power. let's check UPS.
359            // optimize.
360            if(kCFBooleanFalse == IOPSPowerSourceSupported(ps_blob, CFSTR(kIOPMUPSPowerKey)))
361            {
362                // yes batteries on AC power, no UPS -> AC Power
363                return CFSTR(kIOPMACPowerKey);
364            } else {
365                the_ups = IOPSGetActiveUPS(ps_blob);
366                if(!the_ups) return CFSTR(kIOPMACPowerKey);
367                ps_state = getPowerSourceState(ps_blob, the_ups);
368                if(ps_state && CFEqual(ps_state, CFSTR(kIOPSBatteryPowerValue)))
369                {
370                    // yes batteries on AC power, UPS is on battery power -> UPS Power
371                    return CFSTR(kIOPMUPSPowerKey);
372                } else if(ps_state && CFEqual(ps_state, CFSTR(kIOPSACPowerValue)))
373                {
374                    // yes batteries on AC Power, UPS is on AC Power -> AC Power
375                    return CFSTR(kIOPMACPowerKey);
376                }
377            }
378        }
379    }
380
381    // Should not reach this point. Return something safe.
382    return CFSTR(kIOPMACPowerKey);
383}
384
385
386/***
387 Support structures and functions for IOPSNotificationCreateRunLoopSource
388***/
389typedef struct {
390    IOPowerSourceCallbackType       callback;
391    void                            *context;
392    int                             token;
393    CFMachPortRef                   mpRef;
394} IOPSNotifyCallbackContext;
395
396static void IOPSRLSMachPortCallback (CFMachPortRef port __unused, void *msg __unused, CFIndex size __unused, void *info)
397{
398    IOPSNotifyCallbackContext	*c = (IOPSNotifyCallbackContext *)info;
399    IOPowerSourceCallbackType cb;
400
401    if (c && (cb = c->callback)) {
402        (*cb)(c->context);
403    }
404}
405
406static void IOPSRLSMachPortRelease(const void *info)
407{
408    IOPSNotifyCallbackContext	*c = (IOPSNotifyCallbackContext *)info;
409
410    if (c) {
411        if (0 != c->token) {
412            notify_cancel(c->token);
413        }
414        if (c->mpRef) {
415            CFMachPortInvalidate(c->mpRef);
416            CFRelease(c->mpRef);
417        }
418        free(c);
419    }
420}
421
422
423static CFRunLoopSourceRef doCreatePSRLS(const char *notify_type, IOPowerSourceCallbackType callback, void *context)
424{
425    int                             status = 0;
426    int                             token = 0;
427    mach_port_t                     mp = MACH_PORT_NULL;
428    CFMachPortRef                   mpRef = NULL;
429    CFMachPortContext               mpContext;
430    CFRunLoopSourceRef              mpRLS = NULL;
431    IOPSNotifyCallbackContext       *ioContext;
432    Boolean                         isReused = false;
433    int                             giveUpRetryCount = 5;
434
435    status = notify_register_mach_port(notify_type, &mp, 0, &token);
436    if (NOTIFY_STATUS_OK != status) {
437        return NULL;
438    }
439
440    ioContext = calloc(1, sizeof(IOPSNotifyCallbackContext));
441    ioContext->callback = callback;
442    ioContext->context = context;
443    ioContext->token = token;
444
445    bzero(&mpContext, sizeof(mpContext));
446    mpContext.info = (void *)ioContext;
447    mpContext.release = IOPSRLSMachPortRelease;
448
449    do {
450        if (mpRef) {
451            // CFMachPorts may be reused. We don't want to get a reused mach port; so if we're unlucky enough
452            // to get one, we'll pre-emptively invalidate it, throw them back in the pool, and retry.
453            CFMachPortInvalidate(mpRef);
454            CFRelease(mpRef);
455        }
456
457        mpRef = CFMachPortCreateWithPort(0, mp, IOPSRLSMachPortCallback, &mpContext, &isReused);
458    } while (!mpRef && isReused && (--giveUpRetryCount > 0));
459
460    if (mpRef) {
461        if (!isReused) {
462            // A reused mach port is a failure; it'll have an invalid callback pointer associated with it.
463            ioContext->mpRef = mpRef;
464            mpRLS = CFMachPortCreateRunLoopSource(0, mpRef, 0);
465        }
466        CFRelease(mpRef);
467    }
468
469    return mpRLS;
470}
471
472CFRunLoopSourceRef IOPSNotificationCreateRunLoopSource(IOPowerSourceCallbackType callback, void *context) {
473    return doCreatePSRLS(kIOPSNotifyTimeRemaining, callback, context);
474}
475
476CFRunLoopSourceRef IOPSCreateLimitedPowerNotification(IOPowerSourceCallbackType callback, void *context) {
477    return doCreatePSRLS(kIOPSNotifyPowerSource, callback, context);
478}
479
480