BatteryTimeRemaining.c   [plain text]


/*
 * Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 2002 Apple Computer, Inc.  All rights reserved. 
 *
 * HISTORY
 *
 * 29-Aug-02 ebold created
 *
 */
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <notify.h>
#include <mach/mach.h>
#include <mach/mach_port.h>
#include <servers/bootstrap.h>
#include <asl.h>

#include "powermanagementServer.h" // mig generated
#include "BatteryTimeRemaining.h"
#include "SetActive.h"
#include "PrivateLib.h"

#ifndef kIOPSFailureKey
#define kIOPSFailureKey "Failure"
#endif

#ifndef kIOPSDynamicStorePowerAdapterKey
#define kIOPSDynamicStorePowerAdapterKey "/IOKit/PowerAdapter"
#endif

#define kBatteryPermFailureString "Permanent Battery Failure"

/**** PMBattery configd plugin
  We clean up, massage, and re-package the data from the batteries and publish
  it in the more palatable form described in IOKit/Headers/IOPowerSource.h

  All kernel batteries conform to the IOPMPowerSource base class.
    
  We provide the following information in a CFDictionary and publish it for
  all user processes to see:
    Name
    CurrentCapacity
    MaxCapacity
    Remaining Time To Empty
    Remaining Time To Full Charge
    IsCharging
    IsPresent
    Type    
****/

#ifndef kIOPSDynamicStoreLowBattPathKey
#define kIOPSDynamicStoreLowBattPathKey "/IOKit/LowBatteryWarning"
#endif

extern CFMachPortRef            pmServerMachPort;

/* OpaqueIOPSPowerSourceID 
 *      == PSTracker (in this file) 
 */
typedef struct  {
    mach_port_t     connection;
    CFStringRef     scdsKey;
} OpaqueIOPSPowerSourceID;

/* kPSMaxTrackedPowerSources
 * Allow for 1 battery, 1 UPS, 16 other
 */
#define kPSMaxTrackedPowerSources   20

static      OpaqueIOPSPowerSourceID      gPSList[kPSMaxTrackedPowerSources];
typedef     OpaqueIOPSPowerSourceID     *PSTracker;

/* kPSMaxSCDSKeyLength
 * Estimated size of SCDynamicStoreKey strings
 */
#define kPSMaxSCDSKeyLength   100


// Return values from calculateTRWithCurrent
enum {
    kNothingToSeeHere = 0,
    kNoTimeEstimate,
};

// Battery health calculation constants
#define kSmartBattReserve_mAh    200.0

#define kMaxBattMinutes     600

// static global variables for tracking battery state
static CFAbsoluteTime   _estimatesInvalidUntil = 0.0;
static int              _systemBatteryWarningLevel = 0;
static bool             _warningsShouldResetForSleep = false;
static bool             _readACAdapterAgain = false;
static bool             _useBatteryTimeEstimate = false;
static bool             _ignoringTimeRemainingEstimates = false;
static CFRunLoopTimerRef    _timeSettledTimer = NULL;

// forward declarations
static void     _initializeBatteryCalculations(void);
static int      _populateTimeRemaining(IOPMBattery **batts);
static void     _packageBatteryInfo(CFDictionaryRef *);
static bool     _shouldTrustBatteryTimeEstimate(IOPMBattery *b);
static void     _timeRemainingMaybeValid(CFRunLoopTimerRef timer, void *info);
static void     _discontinuityOccurred(void);
static IOReturn _readAndPublishACAdapter(bool);

__private_extern__ void
BatteryTimeRemaining_prime(void)
{
    bzero(gPSList, sizeof(gPSList));
    
    // setup battery calculation global variables
    _initializeBatteryCalculations();
    return;
}

__private_extern__ void
BatteryTimeRemainingSleepWakeNotification(natural_t messageType)
{
    if (kIOMessageSystemWillPowerOn == messageType)
    {
        _warningsShouldResetForSleep = true;
        _readACAdapterAgain = true;
    }
}

/*
 * When we wake from sleep, we call this function to make note of the
 * battery time remaining discontinuity after the RTC resyncs with the CPU.
 */
__private_extern__ void
BatteryTimeRemainingRTCDidResync(void)
{
    _discontinuityOccurred();
}

/* 
 * A battery time remaining discontinuity has occurred
 * Make sure we don't publish a time remaining estimate at all
 * until a given period has elapsed.
 */
static void _discontinuityOccurred(void)
{
    IOPMBattery             **b = _batteries();
    CFAbsoluteTime          lastDiscontinuity = 0.0;

    // Pick a time X seconds into the future. Until then, all TimeRemaining
    // estimates shall be considered invalid.
    // We will check the current timestamp against:
    // _lastWake + b[i]->invalidWakeSecs
    // and ignore time remaining until that moment has past.
    
    if (!b || !b[0])
        return;
    
    lastDiscontinuity = CFAbsoluteTimeGetCurrent();
    _estimatesInvalidUntil = lastDiscontinuity + (double)b[0]->invalidWakeSecs;
    
    _ignoringTimeRemainingEstimates = true;
    
    // After the timeout has elapsed, re-read battery state & the now-valid
    // time remaining.
    if (_timeSettledTimer) {
        CFRunLoopTimerInvalidate(_timeSettledTimer);
        _timeSettledTimer = NULL;
    }
    _timeSettledTimer = CFRunLoopTimerCreate(
                    NULL, _estimatesInvalidUntil, 0.0, 
                    0, 0, _timeRemainingMaybeValid, NULL);
    CFRunLoopAddTimer( CFRunLoopGetCurrent(), _timeSettledTimer, 
                    kCFRunLoopDefaultMode);
    CFRelease(_timeSettledTimer);
}

static void _timeRemainingMaybeValid(CFRunLoopTimerRef timer, void *info)
{
    // The timer has fired. Settings are probably valid.
    _timeSettledTimer = NULL;
    _ignoringTimeRemainingEstimates = false;

    // Trigger battery time remaining re-calculation 
    // now that current reading is valid.
    BatteryTimeRemainingBatteriesHaveChanged(NULL);
}

static void     _initializeBatteryCalculations(void)
{
    int                 batCount;
        
    // Batteries detected, get their initial state
    batCount = _batteryCount();
    if (batCount == 0) {
        return;
    }

    // make initial call to populate array and publish state
    BatteryTimeRemainingBatteriesHaveChanged(_batteries());

    return;
}


__private_extern__ void
BatteryTimeRemainingBatteriesHaveChanged(IOPMBattery **batteries)
{
    SCDynamicStoreRef           store = NULL;
    static CFStringRef          lowBatteryKey = NULL;
    static CFDictionaryRef      *old_battery = NULL;
    CFDictionaryRef             *result = NULL;
    int                         i;
    int                         batCount = _batteryCount();
    IOPMBattery                 *b = NULL;
    bool                        external = false;
    static bool                 _lastExternal = false;
    
    if (!batteries) batteries = _batteries();

    result = (CFDictionaryRef *) calloc(1, batCount * sizeof(CFDictionaryRef));
    if ( NULL == old_battery ) {
        old_battery = (CFDictionaryRef *) calloc(1, batCount * sizeof(CFDictionaryRef));
    }    
    if ( NULL == old_battery || NULL == result ) {
        return;
    }
    
    /* First, we have to determine if AC has changed since our last reading,
     * since this effects our time remaining estimate.
     */
    b = batteries[0];
    if (b->externalConnected) {
        external = true;
    }
    if (_lastExternal != external) {
        // If AC has changed, we must invalidate time remaining.
        _discontinuityOccurred();
    }
    _lastExternal = external;

    /*
     * AC attached or detached
     * Code on new AC attach goes here.
     */
    _readAndPublishACAdapter(external);
    
    /*
     * Estimate N minutes until battery empty/full
     */
    _populateTimeRemaining(batteries);


    /* Display a system low battery warning?
     * 
     * No Warning == AC Power or >= 22% on battery
     * Early Warning == On Battery with < 22%
     * Final Warning == On Battery with < 10 Minutes
     *
     * Once we enter a "warning" state, we can only leave "warning"
     * state by (1) having AC power re-applied, or (2) hibernating
     * and waking with a new battery.
     *     
     * This prevents fluctuations in battery capacity from causing
     * multiple battery warnings.
     *
     */
    int             combinedTime    = 0;
    int             newWarningLevel = 0;
    int             combinedLevel   = 0;
    bool            isPresent       = false;
    static bool     _lastIsPresent  = false;

    // clear warning history on wake from sleep
    if (_warningsShouldResetForSleep)
    {
        _warningsShouldResetForSleep = false;
        _systemBatteryWarningLevel = 0;
    }

    for(i=0; i<batCount; i++) 
    {
        b = batteries[i];
        if (b->isPresent) {
            isPresent = true;
            if (0 != b->maxCap) {
                combinedLevel += (100 * b->currentCap)/b->maxCap;
            }
            combinedTime += b->swCalculatedTR;
        }
    }
    
    /*
     * Battery Inserted - new battery detected code here here
     */ 
    if (isPresent && !_lastIsPresent) 
    {

        // On boot, and on insertion of a new battery, we need to check whether
        // we can trust this battery's estimate of time remaining.
        _useBatteryTimeEstimate = _shouldTrustBatteryTimeEstimate(b);
    }
    _lastIsPresent = isPresent;
    
    if (external)
    {
        // If we have AC power, then the warnings come down.
        newWarningLevel = kIOPSLowBatteryWarningNone;
    } else if (combinedLevel && combinedTime)
    {
        // non-zero data in combinedLevel && combinedTime
        // implies a correct reading - continue.
    
        // It's invalid to go from showing any warning
        // to then showing no warning, without application of AC power,
        // or switching to a new battery.
        if ( (combinedLevel >= 22) 
            && (_systemBatteryWarningLevel != kIOPSLowBatteryWarningEarly)
            && (_systemBatteryWarningLevel != kIOPSLowBatteryWarningFinal))
        {
            newWarningLevel = kIOPSLowBatteryWarningNone;
        } else if (combinedTime < 10) {
            newWarningLevel = kIOPSLowBatteryWarningFinal;
        } else if (_systemBatteryWarningLevel != kIOPSLowBatteryWarningFinal)
        {
            // Early warning level if combinedLevel < 22 && combinedTime >= 10
            // Also we disallow the warning level to popup from Final
            // into early. The only ways out of Final are to (1) attach AC
            // or (2) wake from sleep/hibernation with a battery.
            newWarningLevel = kIOPSLowBatteryWarningEarly;
        }
    }
    
    // At this point our algorithm above has populated the time remaining estimate
    // We'll package that info into user-consumable dictionaries below.

    _packageBatteryInfo(result);

    if (!lowBatteryKey) {
        lowBatteryKey = SCDynamicStoreKeyCreate(
                            kCFAllocatorDefault, 
                            CFSTR("%@%@"),
                            kSCDynamicStoreDomainState, 
                            CFSTR(kIOPSDynamicStoreLowBattPathKey));
    }

    store = _getSharedPMDynamicStore();
    
    // And publish the new warning level.        
    if ( (newWarningLevel != _systemBatteryWarningLevel)
        && (0 != newWarningLevel) ) 
    {
        CFNumberRef newWarningLevelNumber = 
            CFNumberCreate(0, kCFNumberIntType, &newWarningLevel);
        
        if (newWarningLevelNumber) 
        {
            SCDynamicStoreSetValue( store, lowBatteryKey, newWarningLevelNumber );
            CFRelease(newWarningLevelNumber); 
            
            notify_post( kIOPSNotifyLowBattery );
        }
        _systemBatteryWarningLevel = newWarningLevel;
    }

    for(i=0; i<batCount; i++) {
        if(result[i]) {   
            // Determine if CFDictionary is new or has changed...
            // Only do SCDynamicStoreSetValue if the dictionary is different
            if(!old_battery[i]) {
                SCDynamicStoreSetValue(store, batteries[i]->dynamicStoreKey, result[i]);
            } else {
                if(!CFEqual(old_battery[i], result[i])) {
                    SCDynamicStoreSetValue(store, batteries[i]->dynamicStoreKey, result[i]);
                }
                CFRelease(old_battery[i]);
            }
            old_battery[i] = result[i];
        }
    }
    
    if(result) free(result);
}

/* _shouldTrustBatteryTimeEstimate
 *  - Intel Smart batteries provide a good time remaining to empty/to full estimate.
 *  - Our older PPC batteries do not.
 *  - Certain batteries (as indicated by the 'BALG' SMC key) can be trusted to
 *      provide a reliable time remaining estimate. Other batteries shall not be 
 *      trusted.
 */
static bool _shouldTrustBatteryTimeEstimate(IOPMBattery *b)
{
    bool            keyFound;
    uint32_t        outVal;

    keyFound = (kIOReturnSuccess == _getSystemManagementKeyInt32('BALG', &outVal));

    return  (keyFound && _batteryHas(b, CFSTR(kIOPMPSTimeRemainingKey)));
}

/* _populateTimeRemaining
 * Implicit inputs: battery state; battery's own time remaining estimate
 * Implicit output: estimated time remaining placed in b->swCalculatedTR; or -1 if indeterminate
 *   returns true if we reached a valid estimate
 *   returns false if we're still calculating
 */
static int _populateTimeRemaining(IOPMBattery **batts)
{
    int             ret_val = kNothingToSeeHere;
    int             i;
    IOPMBattery     *b;
    int             batCount = _batteryCount();
    
    double          lowerAmperageBound;
    double          upperAmperageBound;
    double          absValAvgCurrent;
    double          absValInstantCurrent;

    
    for(i=0; i<batCount; i++)
    {
        b = batts[i];

        absValAvgCurrent = abs(b->avgAmperage);
        absValInstantCurrent = abs(b->instantAmperage);
        
        // If the battery's instantaneous amperage differs wildly from the battery's
        // average amperage over the past minute, we will not use it.
        
        if (_batteryHas(b, CFSTR("InstantAmperage")))
        {
            lowerAmperageBound = (double) absValInstantCurrent * 0.5;
            upperAmperageBound = (double) absValInstantCurrent * 2.0;
        } else {
            // If instant amperage isn't available to rea from this battery we'll just use
            // some loose bounds for this comparison to prevent divide-by-zero in our 
            // calculations below.
            lowerAmperageBound = 5;
            upperAmperageBound = 15000;
        }
    
    
        // The following conditions invalidate a time remaining estimate.
        // (1) If current is zero, finding a time remaining estimate is irrelevant
        //       (in the case of being fully charged) or impossible (in the case
        //       of having just plugged into AC).
        // (2) We also check that average current is within a reasonable range 
        //       of the instant current. We want to avoid 500 hour time remainings 
        //       on wake from sleep; so we make sure the average amperage readings 
        //       are sane.
        // (3) For X seconds after wake from sleep, we cannot trust the time 
        //       remaining estimate provided, whether we provide it ourselves
        //       in SW, or we receive it from the battery.
        //       The battery's kext may specify this time with the 
        //       kIOPMPSInvalidWakeSecondsKey key.
        if(    (0 == b->avgAmperage) 
            || (absValAvgCurrent < lowerAmperageBound) 
            || (absValAvgCurrent > upperAmperageBound) 
            || _ignoringTimeRemainingEstimates)
        {
            b->swCalculatedTR = -1;
            continue;
        }
                
        // We proceed to actually calculating the time remaining now...
        if (_useBatteryTimeEstimate)
        {
            /* Battery time remaining estimate is provided directly by the battery
             * firmware (only on supported hardware).
             */

            b->swCalculatedTR = b->hwAverageTR;

        } else {

            /* Manually calculate battery time remaining.
             * Expect this path on PPC.
             */

            if (0 == b->avgAmperage) {
                b->swCalculatedTR = -1;
            } else {
                if(b->isCharging) {
                    // h = -mAh/mA
                    b->swCalculatedTR = 60*((double)(b->maxCap - b->currentCap)
                                        / (double)b->avgAmperage);                                
                } else { // discharging
                    // h = mAh/mA
                    b->swCalculatedTR = -60*((double)b->currentCap
                                        / (double)b->avgAmperage);
                }
            }
        }

        // Did our calculation come out negative? 
        // The average current must still be out of whack!
        if (b->swCalculatedTR < 0) {
            b->swCalculatedTR = -1;
        }

        // Cap all times remaining to 10 hours. We don't ship any
        // 44 hour batteries just yet.
        if (kMaxBattMinutes < b->swCalculatedTR) {
            b->swCalculatedTR = kMaxBattMinutes;
        }
    }
    return ret_val;
}


// Set health & confidence
void _setBatteryHealthConfidence(
    CFMutableDictionaryRef  outDict, 
    IOPMBattery             *b)
{
    CFMutableArrayRef       permanentFailures = NULL;

    if(!outDict || !b) return;

    // no battery present? no health & confidence then!
    // If we return without setting the health and confidence values in
    // outDict, that is OK, it just means they were indeterminate.
    if( !b->isPresent ) return;


    /** Report any failure status from the PFStatus register                          **/
    /***********************************************************************************/
    /***********************************************************************************/
    if ( 0!= b->pfStatus) {
        permanentFailures = CFArrayCreateMutable(kCFAllocatorDefault, 0,
                                                &kCFTypeArrayCallBacks);
        if (!permanentFailures)
            return;

        if (kSmartBattPFExternalInput & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureExternalInput) );
        }

        if (kSmartBattPFSafetyOverVoltage & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureSafetyOverVoltage) );
        }

        if (kSmartBattPFChargeSafeOverTemp & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureChargeOverTemp) );
        }

        if (kSmartBattPFDischargeSafeOverTemp & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureDischargeOverTemp) );
        }

        if (kSmartBattPFCellImbalance & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureCellImbalance) );
        }

        if (kSmartBattPFChargeFETFailure & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureChargeFET) );
        }

        if (kSmartBattPFDischargeFETFailure & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureDischargeFET) );
        }

        if (kSmartBattPFDataFlushFault & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureDataFlushFault) );
        }

        if (kSmartBattPFPermanentAFECommFailure & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailurePermanentAFEComms) );
        }

        if (kSmartBattPFPeriodicAFECommFailure & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailurePeriodicAFEComms) );
        }

        if (kSmartBattPFChargeSafetyOverCurrent & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureChargeOverCurrent) );
        }

        if (kSmartBattPFDischargeSafetyOverCurrent & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureDischargeOverCurrent) );
        }

        if (kSmartBattPFOpenThermistor & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureOpenThermistor) );
        }

        if (kSmartBattPFFuseBlown & b->pfStatus) {
            CFArrayAppendValue( permanentFailures, CFSTR(kIOPSFailureFuseBlown) );
        }

        CFDictionarySetValue( outDict, 
                CFSTR(kIOPSBatteryFailureModesKey), permanentFailures);
        CFRelease(permanentFailures);
    }



    // Permanent failure -> Poor health
    if (_batteryHas(b, CFSTR(kIOPMPSErrorConditionKey)))
    {
        if (CFEqual(b->failureDetected, CFSTR(kBatteryPermFailureString))) 
        {
            CFDictionarySetValue(outDict, 
                    CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSPoorValue));
            CFDictionarySetValue(outDict, 
                    CFSTR(kIOPSHealthConfidenceKey), CFSTR(kIOPSGoodValue));
            
            // Specifically log that the battery condition is permanent failure
            CFDictionarySetValue(outDict,
                    CFSTR(kIOPSBatteryHealthConditionKey), CFSTR(kIOPSPermanentFailureValue));
            
            return;
        }
    }

    double compareRatioTo = 0.80;
    double capRatio = 1.0; 

    if (0 != b->designCap)
    {
        capRatio =  ((double)b->maxCap + kSmartBattReserve_mAh) / (double)b->designCap;
    }
    bool cyclesExceedStandard = false;

    if (b->markedDeclining) {
        // The battery status should not fluctuate as battery re-learns and adjusts
        // its FullChargeCapacity. This number may fluctuate in normal operation.
        // Hysteresis: a battery that has previously been marked as 'declining'
        // will continue to be marked as declining until capacity ratio exceeds 83%. 
        compareRatioTo = 0.83;
    } else {
        compareRatioTo = 0.80;
    }

    if (capRatio > 1.5) {
        // Poor|Perm Failure = max-capacity is more than 1.5x of the design-capacity.
        CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSPoorValue));
        CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthConditionKey), CFSTR(kIOPSPermanentFailureValue));
    } else if (capRatio >= compareRatioTo) {
        b->markedDeclining = 0;
        // Good = CapRatio > 80% (plus or minus the 3% hysteresis mentioned above)
        CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSGoodValue));
    } else {
        b->markedDeclining = 1;
        if (cyclesExceedStandard) {
			if (capRatio >= 0.50)
			{
				// Fair = ExceedingCycles && CapRatio >= 50% && CapRatio < 80%
				CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSFairValue));
			} else {
				// Poor = ExceedingCycles && CapRatio < 50%
				CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSPoorValue));
			}
			// HealthCondition == CheckBattery to distinguish the Fair & Poor cases from from permanent
			// failure (above), where HealthCondition == PermanentFailure
            CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthConditionKey), CFSTR(kIOPSCheckBatteryValue));
        } else {
            // Check battery = NOT ExceedingCycles && CapRatio < 80%
            CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSCheckBatteryValue));
        }    
    }

    return;
}

/* 
 * Implicit argument: All the global variables that track battery state
 */
void _packageBatteryInfo(CFDictionaryRef *ret)
{
    CFNumberRef     n, n0;
    CFMutableDictionaryRef  mutDict = NULL;
    int             i;
    int             temp;
    int             minutes;
    int             set_capacity, set_charge;
    bool            is_charged;
    IOPMBattery     *b;
    IOPMBattery     **batts = _batteries();
    int             batCount = _batteryCount();

    // Stuff battery info into CFDictionaries
    for(i=0; i<batCount; i++) {
        b = batts[i];

        // Create the battery info dictionary
        mutDict = NULL;
        mutDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        if(!mutDict) return;
        
        // Does the battery provide its own time remaining estimate?
        if (_useBatteryTimeEstimate) {
            CFDictionarySetValue(mutDict,
                        CFSTR("Battery Provides Time Remaining"),
                        kCFBooleanTrue);
        }

        // Are we in a time remaining black-out period due to a recent
        // discontinuity?
        if (_ignoringTimeRemainingEstimates) {
            CFDictionarySetValue(mutDict,
                        CFSTR("Waiting For Time Remaining Estimates"),
                        kCFBooleanTrue);
        }
        
        // Was there an error/failure? Set that.
        if (b->failureDetected) {
            CFDictionarySetValue(mutDict,
                        CFSTR(kIOPSFailureKey),
                        b->failureDetected);
        }
        
        // Is there a charging problem?
        if (b->chargeStatus) {
            CFDictionarySetValue(mutDict,
                        CFSTR(kIOPMPSBatteryChargeStatusKey),
                        b->chargeStatus);
        }
        
        // Battery provided serial number
        if (b->batterySerialNumber) {
            CFDictionarySetValue(mutDict,
                        CFSTR(kIOPSHardwareSerialNumberKey),
                        b->batterySerialNumber);
        }
        
        // Set transport type to "Internal"
        CFDictionarySetValue(mutDict, 
                        CFSTR(kIOPSTransportTypeKey), 
                        CFSTR(kIOPSInternalType));

        // Set Power Source State to AC/Battery
        CFDictionarySetValue(mutDict, 
                        CFSTR(kIOPSPowerSourceStateKey), 
                        (b->externalConnected ? CFSTR(kIOPSACPowerValue):
                                                CFSTR(kIOPSBatteryPowerValue)));

        // round charge and capacity down to a % scale
        if(0 != b->maxCap)
        {
            set_capacity = 100;
            set_charge = (int)lround((double)b->currentCap*100.0/(double)b->maxCap);

            if( (100 == set_charge) && b->isCharging)
            {
                // We will artificially cap the percentage to 99% while charging
                // Batteries may take 10-20 min beyond 100% of charging to
                // relearn their absolute maximum capacity. Leave cap at 99%
                // to indicate we're not done charging. (4482296, 3285870)
                set_charge = 99;
            }
        } else {
            // Bad battery or bad reading => 0 capacity
            set_capacity = set_charge = 0;
        }

        // Set maximum capacity
        n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_capacity);
        if(n) {
            CFDictionarySetValue(mutDict, CFSTR(kIOPSMaxCapacityKey), n);
            CFRelease(n);
        }
        
        // Set current charge
        n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_charge);
        if(n) {
            CFDictionarySetValue(mutDict, CFSTR(kIOPSCurrentCapacityKey), n);
            CFRelease(n);
        }

        // Set isPresent flag
        CFDictionarySetValue(mutDict, CFSTR(kIOPSIsPresentKey), 
                    b->isPresent ? kCFBooleanTrue:kCFBooleanFalse);
        
        // Set _isCharging and time remaining
        minutes = b->swCalculatedTR;
        temp = 0;
        n0 = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &temp);
        temp = -1;
        if( !b->isPresent ) {
            // remaining time calculations only have meaning if the battery is present
            CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse);
            CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
            CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0);
        } else {
            // There IS a battery installed.
            if(b->isCharging) {
                // Set _isCharging to True
                CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanTrue);
                // Set IsFinishingCharge
                CFDictionarySetValue(mutDict, CFSTR(kIOPSIsFinishingChargeKey), 
                        (b->maxCap && (99 <= (100*b->currentCap/b->maxCap))) ? kCFBooleanTrue:kCFBooleanFalse);
                n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes);
                if(n) {
                    CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n);
                    CFRelease(n);
                }
                CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0);
            } else {
                // Not Charging
                // Set _isCharging to False
                CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse);
                // But are we plugged in?
                if(b->externalConnected)
                {
                    // plugged in but not charging == fully charged
                    CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
                    CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0);
                    
                    // Set IsCharged if capacity >= 95% and not charging and plugged in.
                    // - Some portables will not initiate a battery charge if AC is 
                    //   connected when copacity is >= 95%. 
                    // - We consider > 95% to be fully charged; the battery will not charge 
                    //   any higher until AC is unplugged and re-attached.
                    // - IsCharged should be true when the external power adapter LED is Green; 
                    //   should be false when the external power adapter LED is Orange.
                    if (0 != b->maxCap) {
                        is_charged = ((100*b->currentCap/b->maxCap) >= 95);
                    } else { 
                        is_charged = false;
                    }
                    CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargedKey), 
                        is_charged ? kCFBooleanTrue:kCFBooleanFalse);
                } else {
                    // not charging, not plugged in == d_isCharging
                    n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes);
                    if(n) {
                        CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n);
                        CFRelease(n);
                    }
                    CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
                }
            }
        
        }
        CFRelease(n0);

        // Set health & confidence
        _setBatteryHealthConfidence(mutDict, b);


        // Set name
        if(b->name) {
            CFDictionarySetValue(mutDict, CFSTR(kIOPSNameKey), b->name);
        } else {
            CFDictionarySetValue(mutDict, CFSTR(kIOPSNameKey), CFSTR("Unnamed"));
        }
        ret[i] = mutDict;
    }

    return;
}

// _readAndPublicACAdapter
// These keys describe the bit-layout of the 64-bit AC info structure.
/*
#define kACCRCBit       0   // size 8
#define kACIDBit        8   // size 12
#define kACPowerBit     20  // size 8
#define kACRevisionBit  28  // size 4
#define kACSerialBit    32  // size 24
#define kACFamilyBit    56  // size 8
*/
#define kACCRCBit       56   // size 8
#define kACIDBit        44  // size 12
#define kACPowerBit     36  // size 8
#define kACRevisionBit  32  // size 4
#define kACSerialBit    8   // size 24
#define kACFamilyBit    0   // size 8


static IOReturn _readAndPublishACAdapter(bool adapterExists)
{
    SCDynamicStoreRef           store = NULL;
    CFStringRef                 key = NULL;
    CFMutableDictionaryRef      acDict = NULL;
    CFNumberRef     stuffNum = NULL;
    uint32_t        valCRC = 0;
    uint32_t        valID = 0;
    uint32_t        valPower = 0;
    uint32_t        valRevision = 0;
    uint32_t        valSerial = 0;
    uint32_t        valFamily = 0;
    uint64_t        acBits = 0;
    IOReturn        ret = kIOReturnSuccess;
    Boolean         success = FALSE;
    static bool     adapterInfoPublished = false;

    // Make sure we re-read the adapter on wake from sleep
    if (_readACAdapterAgain) {
        adapterInfoPublished = false;
        _readACAdapterAgain = false;
    }

    // don't re-publish AC info until the adapter changes
    if (adapterExists && adapterInfoPublished) {
        return kIOReturnSuccess;
    }

    if (adapterExists)
    {
        ret = _getACAdapterInfo(&acBits);
        if (kIOReturnSuccess != ret) {
            return ret;
        }
        
        // Decode SMC key
        valFamily = (acBits >> kACFamilyBit) & 0xFF;
        valSerial = (acBits >> kACSerialBit) & 0xFFFFFF;
        valRevision = (acBits >> kACRevisionBit) & 0xF;
        valPower = (acBits >> kACPowerBit) & 0xFF;
        valID = (acBits >> kACIDBit) & 0xFFF;
        valCRC = (acBits >> kACCRCBit) & 0xFF;
        
        // Publish values in dictionary
        acDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        if (!acDict) {
            ret = kIOReturnNoMemory;
            goto exit;
        }
        
        stuffNum = CFNumberCreate(0, kCFNumberSInt32Type, &valSerial);
        if (stuffNum) {
            CFDictionarySetValue(acDict, CFSTR(kIOPSPowerAdapterSerialNumberKey), stuffNum);
            CFRelease(stuffNum);
        }
        stuffNum = CFNumberCreate(0, kCFNumberSInt32Type, &valFamily);
        if (stuffNum) {
            CFDictionarySetValue(acDict, CFSTR(kIOPSPowerAdapterFamilyKey), stuffNum);
            CFRelease(stuffNum);
        }
        stuffNum = CFNumberCreate(0, kCFNumberSInt32Type, &valRevision);
        if (stuffNum) {
            CFDictionarySetValue(acDict, CFSTR(kIOPSPowerAdapterRevisionKey), stuffNum);
            CFRelease(stuffNum);
        }
        stuffNum = CFNumberCreate(0, kCFNumberSInt32Type, &valPower);
        if (stuffNum) {
            CFDictionarySetValue(acDict, CFSTR(kIOPSPowerAdapterWattsKey), stuffNum);
            CFRelease(stuffNum);
        }
        stuffNum = CFNumberCreate(0, kCFNumberSInt32Type, &valID);
        if (stuffNum) {
            CFDictionarySetValue(acDict, CFSTR(kIOPSPowerAdapterIDKey), stuffNum);
            CFRelease(stuffNum);
        }
        
    }

    // Write dictionary into dynamic store
    key = SCDynamicStoreKeyCreate(
                            kCFAllocatorDefault, 
                            CFSTR("%@%@"),
                            kSCDynamicStoreDomainState, 
                            CFSTR(kIOPSDynamicStorePowerAdapterKey));
    if (!key) {
        ret = kIOReturnError;
        goto exit;
    }

    store = _getSharedPMDynamicStore();
    if (store) {
        if (!adapterExists) {
            success = SCDynamicStoreRemoveValue(store, key);
            adapterInfoPublished = false;
        } else {
            success = SCDynamicStoreSetValue(store, key, acDict);
            adapterInfoPublished = true;
        }
    }

    if (success)
        ret = kIOReturnSuccess;
    else
        ret = kIOReturnLockedRead;

    notify_post("com.apple.system.powermanagement.poweradapter");

exit:
    if (acDict) 
        CFRelease(acDict);
    if (key)
        CFRelease(key);
    return ret;
}

/**** User-space power source code lives below here ********************************/
/***********************************************************************************/
/***********************************************************************************/
/***********************************************************************************/


/***********************************************************************************/
/* newKeyForType
 * Assigns a unique string as the key for the power source type.
 * The name should reflect the power source's type, and should have a unique
 * integer appended to unique it within the system.
 */
static CFStringRef _newKeyForType(char *type)
{
    CFStringRef         scKey = NULL;
    CFStringRef         typeString = NULL;
    static const unsigned long long kCounterWrapAround = 100000;
    static unsigned long long psCounter = 1000;

    typeString = CFStringCreateWithCString(0, type, kCFStringEncodingMacRoman);
    if (!typeString) 
        return NULL;

    /* Note: There is a small chance of issuing a name that is already in use on the system.
     * This is only if psCounter exceeds 100,000; which would imply that around 99,000 power sources
     * were created and destroyed. That's unlikely.
     */
     
    if (psCounter > kCounterWrapAround)
        psCounter = 1000;
     
    scKey = SCDynamicStoreKeyCreate(
                            kCFAllocatorDefault, 
                            CFSTR("%@%@/%@-%ld"),
                            kSCDynamicStoreDomainState, 
                            CFSTR(kIOPSDynamicStorePath), 
                            typeString,
                            psCounter++);

    CFRelease(typeString);
    return scKey;
}

/***********************************************************************************/
static IOReturn _new_psTracker(PSTracker *new_ps)
{
    int             i = 0;

    *new_ps = NULL;

    for (i=0; i<kPSMaxTrackedPowerSources; i++)
    {
        if (MACH_PORT_NULL == gPSList[i].connection)
            break;    
    }

    if (i >= kPSMaxTrackedPowerSources) {
        return kIOReturnNoMemory;
    }

    *new_ps = &gPSList[i];
    
    return kIOReturnSuccess;
}

static PSTracker _psTrackerForPort(mach_port_t target_port)
{
    int i;

    for (i=0; i<kPSMaxTrackedPowerSources; i++)
    {
        if (target_port == gPSList[i].connection)
            break;    
    }

    if (i >= kPSMaxTrackedPowerSources) {
        return NULL;
    }

    return &gPSList[i];
}



/***********************************************************************************/
/*** Destroy an existing power source ***/
/***********************************************************************************/

__private_extern__ bool BatteryHandleDeadName(mach_port_t deadName)
{
    SCDynamicStoreRef           ds = NULL;
    PSTracker                   reap_me = _psTrackerForPort(deadName);

    if (!reap_me) {
        // Nothing to be done. This is not a battery-tracked port. 
        // Return false to indicate we didn't handle it.
        return false;
    }

    if (reap_me->scdsKey)
    {
        ds = _getSharedPMDynamicStore();
        SCDynamicStoreRemoveValue(ds, reap_me->scdsKey);
        CFRelease(reap_me->scdsKey); 
    }
        
    if (reap_me->connection != MACH_PORT_NULL)
    {
        __MACH_PORT_DEBUG(true, "_io_pm_new_pspowersource deadname", reap_me->connection);
        mach_port_deallocate(mach_task_self(), reap_me->connection);
    }
    
    reap_me->scdsKey = NULL;
    reap_me->connection = MACH_PORT_NULL;

    return true;
}

/***********************************************************************************/
// MIG handler - back end for IOKit API IOPSCreatePowerSource
kern_return_t _io_pm_new_pspowersource(
    mach_port_t                 server,
    mach_port_t                 clientport,
    string_t                    clienttype,         // in
    string_t                    dskey,              // out
    int                         *result)
{
    PSTracker                   new_tracker = NULL;
    static const int            kDSKeyMIGBufferSize = 1024;
    mach_port_t                 oldNotify;

    if (MACH_PORT_NULL == clientport 
        || NULL == clienttype
        || NULL == result 
        || NULL == dskey) 
    {
        if (result) *result = kIOReturnBadArgument;
        goto exit;
    }

    if (kIOReturnSuccess != _new_psTracker(&new_tracker)) 
    {
        *result = kIOReturnNoSpace;
        goto exit;
    }

    __MACH_PORT_DEBUG(true, "_io_pm_new_pspowersource client", clientport);
    new_tracker->connection = clientport;

    mach_port_request_notification(
                mach_task_self(),           // task
                clientport,                 // port that will die
                MACH_NOTIFY_DEAD_NAME,      // msgid
                1,                          // make-send count
                CFMachPortGetPort(pmServerMachPort),        // notify port
                MACH_MSG_TYPE_MAKE_SEND_ONCE,               // notifyPoly
                &oldNotify);                                // previous
  
    new_tracker->scdsKey = _newKeyForType(clienttype);
    
    if (new_tracker->scdsKey) 
    {
        // We copy the string directly into the reply mach mesage at the address provided at 'dskey'
        CFStringGetCString(new_tracker->scdsKey, (void *)dskey, 
                                kDSKeyMIGBufferSize, kCFStringEncodingUTF8);
    }

    *result = kIOReturnSuccess;
    
exit:
    __MACH_PORT_DEBUG(true, "_io_pm_new_pspowersource client - exit", clientport);
    return KERN_SUCCESS;
}