BatteryTimeRemaining.c [plain text]
#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 <bsm/libbsm.h>
#include <sys/time.h>
#include <IOKit/ps/IOPowerSourcesPrivate.h>
#include "powermanagementServer.h" // mig generated
#include "BatteryTimeRemaining.h"
#include "PMSettings.h"
#include "UPSLowPower.h"
#include "PMAssertions.h"
#include "PrivateLib.h"
#include "PMStore.h"
#include "IOUPSPrivate.h"
typedef enum {
kPSTypeUnknown = 0,
kPSTypeIntBattery = 1,
kPSTypeUPS = 2,
kPSTypeAccessory = 3
} psTypes_t;
typedef struct {
long psid;
psTypes_t psType;
int pid;
dispatch_source_t procdeathsrc;
CFDictionaryRef description;
CFMutableArrayRef log;
CFIndex logIdx; uint64_t logUpdate_ts; } PSStruct;
#define kBattLogMaxEntries 64
#define kBattLogUpdateFreq (5*60) // 5 mins
#define kPSMaxCount 16
static PSStruct gPSList[kPSMaxCount];
#define kBattNotCharging 0xffff
#define kSlewStepMin 2
#define kSlewStepMax 10
#define kDiscontinuitySettle 60
typedef struct {
int showingTime;
bool settled;
} SlewStruct;
SlewStruct *slew = NULL;
#define kSmartBattReserve_mAh 200.0
#define kMaxBattMinutes 1200
typedef struct {
CFAbsoluteTime lastDiscontinuity;
int systemWarningLevel;
bool warningsShouldResetForSleep;
bool readACAdapterAgain;
bool selectionHasSwitched;
int psTimeRemainingNotifyToken;
int psPercentChangeNotifyToken;
bool noPoll;
bool needsNotifyAC;
PSStruct *internal;
} BatteryControl;
static BatteryControl control;
static PSStruct *iops_newps(int pid, int psid);
static void _initializeBatteryCalculations(void);
static void checkTimeRemainingValid(IOPMBattery **batts);
static CFDictionaryRef packageKernelPowerSource(IOPMBattery *b, PSStruct *ps);
static void _discontinuityOccurred(void);
static void _readAndPublishACAdapter(bool, CFDictionaryRef);
static void publish_IOPSBatteryGetWarningLevel(IOPMBattery *b,
int combinedTime,
int percent);
static bool publish_IOPSGetTimeRemainingEstimate(int timeRemaining,
bool external,
bool rawExternal,
bool timeRemainingUnknown,
bool isCharging,
bool showChargingUI,
bool noPoll);
static void publish_IOPSGetPercentRemaining(int percent,
bool external,
bool isCharging,
bool fullyCharged,
IOPMBattery *b);
static void HandlePublishAllPowerSources(void);
static IOReturn HandleAccessoryPowerSources(PSStruct *ps, CFDictionaryRef update);
static CFDictionaryRef getPSByType(CFStringRef type);
typedef enum {
kPeriodicPoll = 0,
kImmediateFullPoll = 1
} PollCommand;
static bool startBatteryPoll(PollCommand x);
__private_extern__ void
BatteryTimeRemaining_prime(void)
{
bzero(gPSList, sizeof(gPSList));
bzero(&control, sizeof(BatteryControl));
notify_register_check(kIOPSTimeRemainingNotificationKey,
&control.psTimeRemainingNotifyToken);
notify_register_check(kIOPSNotifyPercentChange,
&control.psPercentChangeNotifyToken);
recordFDREvent(kFDRInit, false, NULL);
#if !TARGET_OS_EMBEDDED
#endif
_initializeBatteryCalculations();
startBatteryPoll(kPeriodicPoll);
return;
}
__private_extern__ void
BatteryTimeRemainingSleepWakeNotification(natural_t messageType)
{
if (kIOMessageSystemWillPowerOn == messageType)
{
control.warningsShouldResetForSleep = true;
control.readACAdapterAgain = true;
_discontinuityOccurred();
}
}
__private_extern__ void
BatteryTimeRemainingRTCDidResync(void)
{
_discontinuityOccurred();
}
static void _discontinuityOccurred(void)
{
if (slew) {
bzero(slew, sizeof(SlewStruct));
}
control.lastDiscontinuity = CFAbsoluteTimeGetCurrent();
startBatteryPoll(kImmediateFullPoll);
}
static void _initializeBatteryCalculations(void)
{
if (_batteryCount() == 0) {
return;
}
const int kSpecialInternalBatteryID = 99;
control.internal = iops_newps(getpid(), kSpecialInternalBatteryID);
control.internal->psType = kPSTypeIntBattery;
control.lastDiscontinuity = CFAbsoluteTimeGetCurrent();
kernelPowerSourcesDidChange(kInternalBattery);
return;
}
#if !TARGET_OS_EMBEDDED
static CFAbsoluteTime getASBMPropertyCFAbsoluteTime(CFStringRef key)
{
CFNumberRef secSince1970 = NULL;
IOPMBattery **b = _batteries();
uint32_t secs = 0;
CFAbsoluteTime return_val = 0.0;
if (b && b[0] && b[0]->properties)
{
secSince1970 = CFDictionaryGetValue(b[0]->properties, key);
if (secSince1970) {
CFNumberGetValue(secSince1970, kCFNumberIntType, &secs);
return_val = (CFAbsoluteTime)secs - kCFAbsoluteTimeIntervalSince1970;
}
}
return return_val;
}
static CFTimeInterval mostRecent(CFTimeInterval a, CFTimeInterval b, CFTimeInterval c)
{
if ((a >= b) && (a >= c) && a!= 0.0) {
return a;
} else if ((b >= a) && (b>= c) && b!= 0.0) {
return b;
} else return c;
}
static dispatch_source_t batteryPollingTimer = NULL;
#endif
static void updateLogBuffer(PSStruct *ps, bool asyncEvent)
{
uint64_t curTime = getMonotonicTime();
CFTypeRef n;
CFDateRef date = NULL;
CFTimeZoneRef tz = NULL;
CFTimeInterval diff = 0;
CFAbsoluteTime absTime;
CFMutableDictionaryRef entry = NULL;
if ((ps == NULL) || (isA_CFDictionary(ps->description) == NULL)) return;
if ((!asyncEvent) && (curTime - ps->logUpdate_ts < kBattLogUpdateFreq))
return;
if (ps->log == NULL) {
ps->log = CFArrayCreateMutable(NULL, kBattLogMaxEntries, &kCFTypeArrayCallBacks);
if (ps->log == NULL) return;
}
entry = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!entry) {
goto exit;
}
tz = CFTimeZoneCopySystem();
if (tz == NULL) {
goto exit;
}
absTime = CFAbsoluteTimeGetCurrent();
date = CFDateCreate(0, absTime);
if (date == NULL) {
goto exit;
}
CFDictionarySetValue(entry, CFSTR(kIOPSBattLogEntryTime), date);
diff = CFTimeZoneGetSecondsFromGMT(tz, absTime);
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &diff);
if (n) {
CFDictionarySetValue(entry, CFSTR(kIOPSBattLogEntryTZ), n);
CFRelease(n);
}
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSCurrentCapacityKey));
if (n) CFDictionarySetValue(entry, CFSTR(kIOPSCurrentCapacityKey), n);
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSMaxCapacityKey));
if (n) CFDictionarySetValue(entry, CFSTR(kIOPSMaxCapacityKey), n);
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSPowerSourceStateKey));
if (n) CFDictionarySetValue(entry, CFSTR(kIOPSPowerSourceStateKey), n);
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSIsChargingKey));
if (n) CFDictionarySetValue(entry, CFSTR(kIOPSIsChargingKey), n);
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSCurrentKey));
if (n) CFDictionarySetValue(entry, CFSTR(kIOPSCurrentKey), n);
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSIsChargedKey));
if (n)
CFDictionarySetValue(entry, CFSTR(kIOPSIsChargedKey), n);
else
CFDictionarySetValue(entry, CFSTR(kIOPSIsChargedKey), kCFBooleanFalse);
CFArraySetValueAtIndex(ps->log, ps->logIdx , entry);
ps->logIdx = (++ps->logIdx) % kBattLogMaxEntries;
ps->logUpdate_ts = curTime;
exit:
if (entry) CFRelease(entry);
if (tz) CFRelease(tz);
if (date) CFRelease(date);
}
#ifndef kBootPathKey
#define kBootPathKey "BootPathUpdated"
#define kFullPathKey "FullPathUpdated"
#define kUserVisPathKey "UserVisiblePathUpdated"
#endif
static bool startBatteryPoll(PollCommand doCommand)
{
#if !TARGET_OS_EMBEDDED
const static CFTimeInterval kUserVisibleMinFrequency = 55.0;
const static CFTimeInterval kFullMinFrequency = 595.0;
const static uint64_t kPollIntervalNS = 60ULL * NSEC_PER_SEC;
CFAbsoluteTime lastBootUpdate = 0.0;
CFAbsoluteTime lastUserVisibleUpdate = 0.0;
CFAbsoluteTime lastFullUpdate = 0.0;
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime lastUpdateTime;
CFTimeInterval sinceUserVisible = 0.0;
CFTimeInterval sinceFull = 0.0;
bool doUserVisible = false;
bool doFull = false;
if (!_batteries())
return false;
if (control.noPoll)
{
asl_log(0, 0, ASL_LEVEL_ERR, "Battery polling is disabled. powerd is skipping this battery udpate request.");
return false;
}
if (kImmediateFullPoll == doCommand) {
doFull = true;
} else {
lastUpdateTime = getASBMPropertyCFAbsoluteTime(CFSTR(kBootPathKey));
if (lastUpdateTime < now) lastBootUpdate = lastUpdateTime;
lastUpdateTime = getASBMPropertyCFAbsoluteTime(CFSTR(kFullPathKey));
if (lastUpdateTime < now) lastFullUpdate = lastUpdateTime;
lastUpdateTime = getASBMPropertyCFAbsoluteTime(CFSTR(kUserVisPathKey));
if (lastUpdateTime < now) lastUserVisibleUpdate = lastUpdateTime;
sinceUserVisible = now - mostRecent(lastBootUpdate, lastFullUpdate, lastUserVisibleUpdate);
if (sinceUserVisible > kUserVisibleMinFrequency) {
doUserVisible = true;
}
sinceFull = now - mostRecent(lastBootUpdate, lastFullUpdate, 0);
if (sinceFull > kFullMinFrequency) {
doFull = true;
}
}
if (doFull) {
IOPSRequestBatteryUpdate(kIOPSReadAll);
} else if (doUserVisible) {
IOPSRequestBatteryUpdate(kIOPSReadUserVisible);
} else {
uint64_t checkAgainNS = kPollIntervalNS - (sinceUserVisible*NSEC_PER_SEC);
if (checkAgainNS > kPollIntervalNS) {
checkAgainNS = kPollIntervalNS;
}
if (!batteryPollingTimer) {
batteryPollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(batteryPollingTimer, ^() { startBatteryPoll(kPeriodicPoll); });
dispatch_resume(batteryPollingTimer);
}
dispatch_source_set_timer(batteryPollingTimer, dispatch_time(DISPATCH_TIME_NOW, checkAgainNS), kPollIntervalNS, 0);
}
#endif
return true;
}
__private_extern__ void BatterySetNoPoll(bool noPoll)
{
if (control.noPoll != noPoll)
{
control.noPoll = noPoll;
if (!noPoll) {
startBatteryPoll(kImmediateFullPoll);
} else {
kernelPowerSourcesDidChange(kInternalBattery);
}
ERROR_LOG("Battery polling is now %s\n", noPoll ? "disabled." : "enabled. Initiating a battery poll.");
}
}
#define kTimeThresholdEarly 20
#define kTimeThresholdFinal 10
#define kPercentThresholdFinal 5
static void publish_IOPSBatteryGetWarningLevel(
IOPMBattery *b,
int combinedTime,
int percent)
{
static CFStringRef lowBatteryKey = NULL;
static int prevLoggedLevel = kIOPSLowBatteryWarningNone;
int newWarningLevel = kIOPSLowBatteryWarningNone;
if (control.warningsShouldResetForSleep || b->externalConnected)
{
control.warningsShouldResetForSleep = false;
if (control.systemWarningLevel != kIOPSLowBatteryWarningNone) {
control.systemWarningLevel = 0;
newWarningLevel = kIOPSLowBatteryWarningNone;
}
}
#if !TARGET_OS_EMBEDDED
else if (percent <= kPercentThresholdFinal)
{
newWarningLevel = kIOPSLowBatteryWarningFinal;
}
#endif
else if (combinedTime > 0)
{
if (combinedTime < kTimeThresholdFinal)
{
newWarningLevel = kIOPSLowBatteryWarningFinal;
} else if (combinedTime < kTimeThresholdEarly)
{
newWarningLevel = kIOPSLowBatteryWarningEarly;
}
}
if (newWarningLevel < control.systemWarningLevel) {
newWarningLevel = control.systemWarningLevel;
}
if ( (newWarningLevel != control.systemWarningLevel)
&& (0 != newWarningLevel) )
{
CFNumberRef newlevel = CFNumberCreate(0, kCFNumberIntType, &newWarningLevel);
if (newlevel)
{
if (!lowBatteryKey) {
lowBatteryKey = SCDynamicStoreKeyCreate(
kCFAllocatorDefault, CFSTR("%@%@"),
kSCDynamicStoreDomainState, CFSTR(kIOPSDynamicStoreLowBattPathKey));
}
PMStoreSetValue(lowBatteryKey, newlevel );
CFRelease(newlevel);
notify_post(kIOPSNotifyLowBattery);
if ((newWarningLevel != prevLoggedLevel) && (newWarningLevel != kIOPSLowBatteryWarningNone)) {
logASLLowBatteryWarning(newWarningLevel, combinedTime, b->currentCap);
prevLoggedLevel = newWarningLevel;
}
}
control.systemWarningLevel = newWarningLevel;
}
return;
}
static bool publish_IOPSGetTimeRemainingEstimate(
int timeRemaining,
bool external,
bool rawExternal,
bool timeRemainingUnknown,
bool isCharging,
bool showChargingUI,
bool noPoll)
{
uint64_t powerSourcesBitsForNotify = (uint64_t)(timeRemaining & 0xFFFF);
static uint64_t lastPSBitsNotify = 0;
bool posted = false;
uint32_t rc;
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyValidBit;
if (external) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyExternalBit;
}
#if TARGET_OS_EMBEDDED
if (rawExternal) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyRawExternalBit;
}
if (showChargingUI) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyShowChargingUIBit;
}
#endif
if (timeRemainingUnknown) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyUnknownBit;
}
if (isCharging) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyChargingBit;
}
if (control.noPoll) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyNoPollBit;
}
if (getActiveBatteryDictionary()) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyBattSupportBit;
}
if (getActiveUPSDictionary()) {
powerSourcesBitsForNotify |= kPSTimeRemainingNotifyUPSSupportBit;
}
uint64_t activePS = getActivePSType();
powerSourcesBitsForNotify |=
(activePS & 0xFF) << kPSTimeRemainingNotifyActivePS8BitsStarts;
if (lastPSBitsNotify != powerSourcesBitsForNotify)
{
lastPSBitsNotify = powerSourcesBitsForNotify;
notify_set_state(control.psTimeRemainingNotifyToken, powerSourcesBitsForNotify);
rc = notify_post(kIOPSNotifyTimeRemaining);
if (rc != NOTIFY_STATUS_OK) {
ERROR_LOG("Failed to post notification for time remaining. rc:0x%x\n", rc);
}
else {
INFO_LOG("Battery time remaining posted with value 0x%llx\n", powerSourcesBitsForNotify);
}
posted = true;
}
return posted;
}
static void publish_IOPSGetPercentRemaining(
int percentRemaining,
bool isExternal,
bool isCharging,
bool fullyCharged,
IOPMBattery *b)
{
uint64_t currentStateBits, changedStateBits;
static uint64_t lastStateBits = 0;
uint64_t ignoreBits;
currentStateBits = kPSTimeRemainingNotifyValidBit;
if ((percentRemaining >= 0) && (percentRemaining <= 100))
currentStateBits |= percentRemaining;
if (isExternal)
currentStateBits |= kPSTimeRemainingNotifyExternalBit;
if (isCharging)
currentStateBits |= kPSTimeRemainingNotifyChargingBit;
if (fullyCharged)
currentStateBits |= kPSTimeRemainingNotifyFullyChargedBit;
#if TARGET_OS_EMBEDDED
if (b && b->isCritical)
currentStateBits |= kPSCriticalLevelBit;
if (b && b->isRestricted)
currentStateBits |= kPSRestrictedLevelBit;
#endif
changedStateBits = lastStateBits ^ currentStateBits;
if (changedStateBits)
{
lastStateBits = currentStateBits;
notify_set_state(control.psPercentChangeNotifyToken, currentStateBits);
ignoreBits = (kPSTimeRemainingNotifyChargingBit
|kPSTimeRemainingNotifyFullyChargedBit
#if TARGET_OS_EMBEDDED
|kPSCriticalLevelBit
|kPSRestrictedLevelBit
#endif
);
if (changedStateBits & ~ignoreBits)
{
notify_post(kIOPSNotifyPercentChange);
INFO_LOG("Battery capacity change posted with value 0x%llx\n", currentStateBits);
}
#if TARGET_OS_EMBEDDED
if (changedStateBits & kPSCriticalLevelBit)
notify_post(kIOPSNotifyCriticalLevel);
if (changedStateBits & kPSRestrictedLevelBit)
notify_post(kIOPSNotifyRestrictedMode);
#endif
if ((changedStateBits & kPSTimeRemainingNotifyExternalBit) && control.internal)
updateLogBuffer(control.internal, true);
}
}
__private_extern__ void
kernelPowerSourcesDidChange(IOPMBattery *b)
{
static int _lastExternalConnected = -1;
int _nowExternalConnected = 0;
int percentRemaining = 0;
IOPMBattery **_batts = _batteries();
startBatteryPoll(kPeriodicPoll);
if (0 == _batteryCount()) {
return;
}
if (!b) {
b = _batts[0];
}
_nowExternalConnected = (b->externalConnected ? 1 : 0) | (b->rawExternalConnected ? 1 : 0);
if (_lastExternalConnected != _nowExternalConnected) {
_discontinuityOccurred();
control.needsNotifyAC = true;
_lastExternalConnected = _nowExternalConnected;
}
_readAndPublishACAdapter(b->externalConnected,
CFDictionaryGetValue(b->properties, CFSTR(kIOPMPSAdapterDetailsKey)));
checkTimeRemainingValid(_batts);
if (b->maxCap) {
double percent = (double)(b->currentCap * 100) / (double)b->maxCap;
percentRemaining = (int) lround(percent);
if (percentRemaining > 100)
percentRemaining = 100;
}
b->swCalculatedPR = percentRemaining;
if (control.internal) {
CFDictionaryRef update = packageKernelPowerSource(b, control.internal);
if (control.internal->description) {
CFRelease(control.internal->description);
}
control.internal->description = update;
updateLogBuffer(control.internal, false);
}
HandlePublishAllPowerSources();
}
static void HandlePublishAllPowerSources(void)
{
IOPMBattery **batteries = _batteries();
IOPMBattery *b = NULL;
int combinedTime = 0;
int percentRemaining = 0;
static int prev_percentRemaining = 0;
bool tr_posted;
int ups_externalConnected = 0;
static int ups_prevExternalConnected = -1;
bool externalConnected, tr_unknown, is_charging, fully_charged;
bool rawExternalConnected = false;
bool showChargingUI = false;
CFDictionaryRef ups = NULL;
int ups_tr = -1;
CFDictionaryRef battery_case = NULL;
int battcase_percentRemaining = 0;
static int battcase_prevPercentRemaining = 0;
ups = getActiveUPSDictionary();
if ((0 == _batteryCount()) && (ups == NULL)) {
return;
}
if (_batteryCount())
b = batteries[0];
is_charging = fully_charged = false;
for(int i=0; i<_batteryCount(); i++)
{
if (batteries[i]->isPresent) {
combinedTime += batteries[i]->swCalculatedTR;
}
}
battery_case = getPSByType(CFSTR(kIOPSPrivateBatteryCaseType));
if (battery_case) {
CFNumberRef battcase_ccap_cf = CFDictionaryGetValue(battery_case, CFSTR(kIOPSCurrentCapacityKey));
CFNumberRef battcase_mcap_cf = CFDictionaryGetValue(battery_case, CFSTR(kIOPSMaxCapacityKey));
if (battcase_ccap_cf && battcase_mcap_cf) {
int battcase_ccap = 0, battcase_mcap = 0;
CFNumberGetValue(battcase_ccap_cf, kCFNumberIntType, &battcase_ccap);
CFNumberGetValue(battcase_mcap_cf, kCFNumberIntType, &battcase_mcap);
if (battcase_mcap) {
battcase_percentRemaining = (battcase_ccap * 100) / battcase_mcap;
}
}
}
if (ups) {
CFNumberRef num_cf = CFDictionaryGetValue(ups, CFSTR(kIOPSTimeToEmptyKey));
if (num_cf) {
CFNumberGetValue(num_cf, kCFNumberIntType, &ups_tr);
if (ups_tr != -1) combinedTime += ups_tr;
}
CFStringRef src = CFDictionaryGetValue(ups, CFSTR(kIOPSPowerSourceStateKey));
if (src && (CFStringCompare(src, CFSTR(kIOPSACPowerValue), kNilOptions) == kCFCompareEqualTo)) {
ups_externalConnected = 1;
}
if (ups_prevExternalConnected != ups_externalConnected) {
control.needsNotifyAC = true;
ups_prevExternalConnected = ups_externalConnected;
}
}
if (b) {
tr_unknown = b->isTimeRemainingUnknown;
is_charging = b->isCharging;
percentRemaining = b->swCalculatedPR;
fully_charged = isFullyCharged(b);
if (ups) {
externalConnected = b->externalConnected && ups_externalConnected;
}
else {
externalConnected = b->externalConnected;
}
rawExternalConnected = b->rawExternalConnected;
showChargingUI = b->showChargingUI;
}
else {
int mcap = 0, ccap = 0;
CFNumberRef mcap_cf = NULL, ccap_cf = NULL;
externalConnected = ups_externalConnected;
if (!externalConnected && (ups_tr == -1)) {
tr_unknown = false;
}
else {
tr_unknown = true;
}
if (CFDictionaryGetValue(ups, CFSTR(kIOPSIsChargingKey)) == kCFBooleanTrue)
is_charging = true;
ccap_cf = CFDictionaryGetValue(ups, CFSTR(kIOPSCurrentCapacityKey));
if (ccap_cf)
CFNumberGetValue(ccap_cf, kCFNumberIntType, &ccap);
mcap_cf = CFDictionaryGetValue(ups, CFSTR(kIOPSMaxCapacityKey));
if (mcap_cf)
CFNumberGetValue(mcap_cf, kCFNumberIntType, &mcap);
if (ccap && mcap)
percentRemaining = (ccap*100)/mcap;
if ((percentRemaining >= 95) && externalConnected && (!is_charging))
fully_charged = true;
}
tr_posted = publish_IOPSGetTimeRemainingEstimate(combinedTime,
externalConnected,
rawExternalConnected,
tr_unknown,
is_charging,
showChargingUI,
control.noPoll);
if (b) {
publish_IOPSBatteryGetWarningLevel(b, combinedTime, percentRemaining);
}
publish_IOPSGetPercentRemaining(percentRemaining,
externalConnected,
is_charging,
fully_charged,
b);
if ((percentRemaining != prev_percentRemaining || battcase_percentRemaining != battcase_prevPercentRemaining)
&& !tr_posted) {
uint32_t rc = notify_post(kIOPSNotifyTimeRemaining);
if (rc != NOTIFY_STATUS_OK) {
ERROR_LOG("Failed to post notification for battery time remaining. rc:0x%x\n", rc);
}
else {
INFO_LOG("Battery time remaining posted. Capacity:0x%x\n", percentRemaining);
}
}
prev_percentRemaining = percentRemaining;
battcase_prevPercentRemaining = battcase_percentRemaining;
#if !TARGET_OS_EMBEDDED
UPSLowPowerPSChange();
PMSettingsPSChange();
#endif
if (control.needsNotifyAC) {
control.needsNotifyAC = false;
recordFDREvent(kFDRACChanged, false, batteries);
INFO_LOG("Power Source change. External connected:%d", externalConnected);
notify_post(kIOPSNotifyPowerSource);
}
notify_post(kIOPSNotifyAnyPowerSource);
recordFDREvent(kFDRBattEventPeriodic, false, batteries);
return;
}
static void checkTimeRemainingValid(IOPMBattery **batts)
{
int i;
IOPMBattery *b;
int batCount = _batteryCount();
for(i=0; i<batCount; i++)
{
b = batts[i];
if ((b->swCalculatedTR < 0) || (false == b->isPresent)) {
b->swCalculatedTR = -1;
}
if (kMaxBattMinutes < b->swCalculatedTR) {
b->swCalculatedTR = kMaxBattMinutes;
}
}
if (-1 == batts[0]->swCalculatedTR) {
batts[0]->isTimeRemainingUnknown = true;
} else {
batts[0]->isTimeRemainingUnknown = false;
}
}
void _setBatteryHealthConfidence(
CFMutableDictionaryRef outDict,
IOPMBattery *b)
{
CFMutableArrayRef permanentFailures = NULL;
if(!outDict || !b || !b->isPresent)
return;
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);
}
static char* batteryHealth = "";
if (_batteryHas(b, CFSTR(kIOPMPSErrorConditionKey)))
{
if (CFEqual(b->failureDetected, CFSTR(kBatteryPermFailureString)))
{
CFDictionarySetValue(outDict,
CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSPoorValue));
CFDictionarySetValue(outDict,
CFSTR(kIOPSHealthConfidenceKey), CFSTR(kIOPSGoodValue));
CFDictionarySetValue(outDict,
CFSTR(kIOPSBatteryHealthConditionKey), CFSTR(kIOPSPermanentFailureValue));
if (strncmp(batteryHealth, kIOPSPoorValue, sizeof(kIOPSPoorValue))) {
logASLBatteryHealthChanged(kIOPSPoorValue,
batteryHealth,
kIOPSPermanentFailureValue);
batteryHealth = kIOPSPoorValue;
}
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) {
compareRatioTo = 0.83;
} else {
compareRatioTo = 0.80;
}
time_t currentTime = 0;
bool canCompareTime = true;
#if !TARGET_OS_EMBEDDED
struct timeval t;
if (gettimeofday(&t, NULL) == -1) {
canCompareTime = false; }
else {
currentTime = t.tv_sec;
}
#else
canCompareTime = false; #endif
if (capRatio > 1.2) {
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSPoorValue));
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthConditionKey),
CFSTR(kIOPSPermanentFailureValue));
if (strncmp(batteryHealth, kIOPSPoorValue, sizeof(kIOPSPoorValue))) {
logASLBatteryHealthChanged(kIOPSPoorValue,
batteryHealth,
kIOPSPermanentFailureValue);
batteryHealth = kIOPSPoorValue;
}
if (b->hasLowCapRatio == true) {
b->hasLowCapRatio = false;
_setLowCapRatioTime(b->batterySerialNumber,
false,
0);
}
} else if (capRatio >= compareRatioTo) {
b->markedDeclining = 0;
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey), CFSTR(kIOPSGoodValue));
if ((batteryHealth[0] != 0) && (strncmp(batteryHealth, kIOPSGoodValue, sizeof(kIOPSGoodValue)))) {
logASLBatteryHealthChanged(kIOPSGoodValue,
batteryHealth,
"");
}
batteryHealth = kIOPSGoodValue;
if (b->hasLowCapRatio == true) {
b->hasLowCapRatio = false;
_setLowCapRatioTime(b->batterySerialNumber,
false,
0);
}
} else {
if (b->hasLowCapRatio == false) {
b->hasLowCapRatio = true;
b->lowCapRatioSinceTime = currentTime;
_setLowCapRatioTime(b->batterySerialNumber,
true,
currentTime);
}
b->markedDeclining = 1;
if (canCompareTime && (currentTime - b->lowCapRatioSinceTime <= 604800)) {
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey),
CFSTR(kIOPSGoodValue));
if (strncmp(batteryHealth, kIOPSGoodValue, sizeof(kIOPSGoodValue))) {
logASLBatteryHealthChanged(kIOPSGoodValue,
batteryHealth,
"");
batteryHealth = kIOPSGoodValue;
}
}
else {
if (cyclesExceedStandard) {
if (capRatio >= 0.50) {
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey),
CFSTR(kIOPSFairValue));
if (strncmp(batteryHealth, kIOPSFairValue, sizeof(kIOPSFairValue))) {
logASLBatteryHealthChanged(kIOPSFairValue,
batteryHealth,
kIOPSCheckBatteryValue);
batteryHealth = kIOPSFairValue;
}
} else {
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey),
CFSTR(kIOPSPoorValue));
if (strncmp(batteryHealth, kIOPSPoorValue, sizeof(kIOPSPoorValue))) {
logASLBatteryHealthChanged(kIOPSPoorValue,
batteryHealth,
kIOPSCheckBatteryValue);
batteryHealth = kIOPSPoorValue;
}
}
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthConditionKey),
CFSTR(kIOPSCheckBatteryValue));
} else {
CFDictionarySetValue(outDict, CFSTR(kIOPSBatteryHealthKey),
CFSTR(kIOPSCheckBatteryValue));
if (strncmp(batteryHealth, kIOPSCheckBatteryValue,
sizeof(kIOPSCheckBatteryValue))) {
logASLBatteryHealthChanged(kIOPSCheckBatteryValue,
batteryHealth,
"");
batteryHealth = kIOPSCheckBatteryValue;
}
}
}
}
return;
}
bool isFullyCharged(IOPMBattery *b)
{
bool is_charged = false;
if (!b) return false;
if (b->isPresent && (0 != b->maxCap)) {
is_charged = ((100*b->currentCap/b->maxCap) >= 95);
}
return is_charged;
}
CFDictionaryRef packageKernelPowerSource(IOPMBattery *b, PSStruct *ps)
{
CFNumberRef n, n0;
CFMutableDictionaryRef mDict = NULL;
int temp;
int minutes;
int set_capacity, set_charge;
int psID;
if (!b) {
IOPMBattery **batts = _batteries();
b = batts[0];
}
mDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if(!mDict)
return NULL;
CFDictionarySetValue(mDict, CFSTR("Battery Provides Time Remaining"), kCFBooleanTrue);
if (b->failureDetected) {
CFDictionarySetValue(mDict, CFSTR(kIOPSFailureKey), b->failureDetected);
}
if (b->chargeStatus) {
CFDictionarySetValue(mDict, CFSTR(kIOPMPSBatteryChargeStatusKey), b->chargeStatus);
}
if (b->batterySerialNumber) {
CFDictionarySetValue(mDict, CFSTR(kIOPSHardwareSerialNumberKey), b->batterySerialNumber);
}
CFDictionarySetValue(mDict, CFSTR(kIOPSTransportTypeKey), CFSTR(kIOPSInternalType));
CFDictionarySetValue(mDict, CFSTR(kIOPSTypeKey), CFSTR(kIOPSInternalBatteryType));
CFDictionarySetValue(mDict, CFSTR(kIOPSPowerSourceStateKey),
(b->externalConnected ? CFSTR(kIOPSACPowerValue):CFSTR(kIOPSBatteryPowerValue)));
#if TARGET_OS_EMBEDDED
CFDictionarySetValue(mDict, CFSTR(kIOPSRawExternalConnectivityKey),
(b->rawExternalConnected ? kCFBooleanTrue : kCFBooleanFalse));
bool caseConnected = false;
CFDictionaryRef bcase = getPSByType(CFSTR(kIOPSPrivateBatteryCaseType));
if (bcase != NULL) {
int caseCapacity = 0;
CFNumberRef caseCapacityRef = CFDictionaryGetValue(bcase, CFSTR(kIOPSCurrentCapacityKey));
CFNumberGetValue(caseCapacityRef, kCFNumberIntType, &caseCapacity);
if (caseCapacity > 0) caseConnected = true;
}
b->showChargingUI = b->rawExternalConnected || caseConnected;
CFDictionarySetValue(mDict, CFSTR(kIOPSShowChargingUIKey),
(b->showChargingUI ? kCFBooleanTrue : kCFBooleanFalse));
#endif
if(0 != b->maxCap)
{
set_capacity = 100;
set_charge = b->swCalculatedPR;
#if !TARGET_OS_EMBEDDED
if( (100 == set_charge) && b->isCharging)
{
set_charge = 99;
}
#endif
} else {
set_capacity = set_charge = 0;
}
if (control.noPoll) {
set_charge = 55;
}
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_capacity);
if(n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSMaxCapacityKey), n);
CFRelease(n);
}
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_charge);
if(n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSCurrentCapacityKey), n);
CFRelease(n);
}
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &b->avgAmperage);
if(n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSCurrentKey), n);
CFRelease(n);
}
CFDictionarySetValue(mDict, CFSTR(kIOPSIsPresentKey),
b->isPresent ? kCFBooleanTrue:kCFBooleanFalse);
if (control.noPoll) {
minutes = 355;
} else {
minutes = b->swCalculatedTR;
}
temp = 0;
n0 = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &temp);
if( !b->isPresent ) {
CFDictionarySetValue(mDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse);
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToEmptyKey), n0);
} else {
if(b->isCharging) {
CFDictionarySetValue(mDict, CFSTR(kIOPSIsChargingKey), kCFBooleanTrue);
CFDictionarySetValue(mDict, CFSTR(kIOPSIsFinishingChargeKey),
(b->maxCap && (99 <= (100*b->currentCap/b->maxCap))) ? kCFBooleanTrue:kCFBooleanFalse);
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes);
if(n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToFullChargeKey), n);
CFRelease(n);
}
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToEmptyKey), n0);
} else {
CFDictionarySetValue(mDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse);
if(b->externalConnected)
{
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToEmptyKey), n0);
CFDictionarySetValue(mDict, CFSTR(kIOPSIsChargedKey),
isFullyCharged(b) ? kCFBooleanTrue:kCFBooleanFalse);
} else {
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes);
if(n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToEmptyKey), n);
CFRelease(n);
}
CFDictionarySetValue(mDict, CFSTR(kIOPSTimeToFullChargeKey), n0);
}
}
}
CFRelease(n0);
_setBatteryHealthConfidence(mDict, b);
if(b->name) {
CFDictionarySetValue(mDict, CFSTR(kIOPSNameKey), b->name);
} else {
CFDictionarySetValue(mDict, CFSTR(kIOPSNameKey), CFSTR("Unnamed"));
}
if (ps->psType != kPSTypeUPS) {
psID = MAKE_UNIQ_SOURCE_ID(ps->pid, ps->psid);
n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &psID);
if (n) {
CFDictionarySetValue(mDict, CFSTR(kIOPSPowerSourceIDKey), n);
CFRelease(n);
}
}
return mDict;
}
static void _readAndPublishACAdapter(bool adapterExists, CFDictionaryRef batteryACDict)
{
static CFDictionaryRef oldACDict = NULL;
CFStringRef key = NULL;
Boolean success = FALSE;
CFDictionaryRef acDict = NULL;
if (control.readACAdapterAgain) {
control.readACAdapterAgain = false;
if (oldACDict) {
CFRelease(oldACDict);
oldACDict = NULL;
}
}
if (adapterExists) {
if (!batteryACDict) {
acDict = _copyACAdapterInfo(oldACDict);
if (acDict == NULL) {
goto exit;
}
batteryACDict = acDict;
}
if (isA_CFDictionary(oldACDict) && CFEqual(batteryACDict, oldACDict)) {
goto exit;
}
}
key = SCDynamicStoreKeyCreate(
kCFAllocatorDefault,
CFSTR("%@%@"),
kSCDynamicStoreDomainState,
CFSTR(kIOPSDynamicStorePowerAdapterKey));
if (oldACDict) {
CFRelease(oldACDict);
oldACDict = NULL;
}
if (!key) {
goto exit;
}
if (!adapterExists) {
success = PMStoreRemoveValue(key);
} else {
success = PMStoreSetValue(key, batteryACDict);
oldACDict = batteryACDict;
CFRetain(oldACDict);
}
if (success) {
notify_post("com.apple.system.powermanagement.poweradapter");
}
exit:
if (acDict)
CFRelease(acDict);
if (key)
CFRelease(key);
return ;
}
static PSStruct *iops_newps(int pid, int psid)
{
for (int i=0; i<kPSMaxCount; i++)
{
if (0 == gPSList[i].psid)
{
bzero(&gPSList[i], sizeof(PSStruct));
gPSList[i].pid = pid;
gPSList[i].psid = psid;
return &gPSList[i];
}
}
return NULL;
}
static PSStruct *iopsFromPSID(int _pid, int _psid)
{
for (int i=0; i<kPSMaxCount; i++)
{
if (gPSList[i].psid == _psid
&& gPSList[i].pid == _pid)
{
return &gPSList[i];
}
}
return NULL;
}
__private_extern__ CFDictionaryRef getActiveBatteryDictionary(void)
{
for (int i=0; i<kPSMaxCount; i++)
{
if (!gPSList[i].description) {
continue;
}
CFStringRef transport_type = NULL;
transport_type = CFDictionaryGetValue(gPSList[i].description,
CFSTR(kIOPSTransportTypeKey));
if (isA_CFString(transport_type)
&& ( CFEqual(transport_type, CFSTR(kIOPSInternalType))))
{
return gPSList[i].description;
}
}
return NULL;
}
static CFDictionaryRef getPSByType(CFStringRef type)
{
for (int i=0; i<kPSMaxCount; i++)
{
if (!isA_CFDictionary(gPSList[i].description)) {
continue;
}
CFStringRef ps_type = CFDictionaryGetValue(gPSList[i].description, CFSTR(kIOPSTypeKey));
if (isA_CFString(ps_type) && CFEqual(ps_type, type)) {
return gPSList[i].description;
}
}
return NULL;
}
__private_extern__ CFDictionaryRef getActiveUPSDictionary(void)
{
return getPSByType(CFSTR(kIOPSUPSType));
}
__private_extern__ int getActivePSType(void)
{
CFDictionaryRef activeBattery = getActiveBatteryDictionary();
CFDictionaryRef activeUPS = getActiveUPSDictionary();
CFStringRef ps_state = NULL;
if(!activeBattery)
{
if(!activeUPS) {
return kIOPSProvidedByAC;
} else {
ps_state = CFDictionaryGetValue(activeUPS,
CFSTR(kIOPSPowerSourceStateKey));
if(ps_state && CFEqual(ps_state, CFSTR(kIOPSACPowerValue)))
{
return kIOPSProvidedByAC;
} else if(ps_state && CFEqual(ps_state, CFSTR(kIOPSBatteryPowerValue)))
{
return kIOPSProvidedByExternalBattery;
}
}
return kIOPSProvidedByAC;
} else {
ps_state = CFDictionaryGetValue(activeBattery,
CFSTR(kIOPSPowerSourceStateKey));
if(ps_state && CFEqual(ps_state,
CFSTR(kIOPSBatteryPowerValue)))
{
return kIOPSProvidedByBattery;
}
else
{
if (!activeUPS)
{
return kIOPSProvidedByAC;
} else {
ps_state = CFDictionaryGetValue(activeUPS,
CFSTR(kIOPSPowerSourceStateKey));
if(ps_state && CFEqual(ps_state, CFSTR(kIOPSBatteryPowerValue)))
{
return kIOPSProvidedByExternalBattery;
} else if(ps_state && CFEqual(ps_state, CFSTR(kIOPSACPowerValue)))
{
return kIOPSProvidedByAC;
}
}
}
}
return kIOPSProvidedByAC;
}
kern_return_t _io_ps_new_pspowersource(
mach_port_t server __unused,
audit_token_t token,
int *psid, int *result)
{
static unsigned int gPSID = 5000;
int callerPID;
PSStruct *ps;
audit_token_to_au32(token, NULL, NULL, NULL, NULL, NULL,
&callerPID, NULL, NULL);
*result = kIOReturnError;
ps = iops_newps(callerPID, gPSID);
if (!ps)
{
*result = kIOReturnNoSpace;
goto exit;
}
ps->procdeathsrc= dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
callerPID,
DISPATCH_PROC_EXIT,
dispatch_get_main_queue());
dispatch_source_set_cancel_handler(ps->procdeathsrc, ^{
if (ps->psType == kPSTypeAccessory) {
notify_post(kIOPSAccNotifyTimeRemaining);
notify_post(kIOPSAccNotifyAttach);
}
else {
notify_post(kIOPSNotifyTimeRemaining);
notify_post(kIOPSNotifyAttach);
}
INFO_LOG("Posted notifications for loss of power source id %ld\n", ps->psid);
if (ps->procdeathsrc) {
dispatch_release(ps->procdeathsrc);
}
if (ps->description) {
CFRelease(ps->description);
}
if (ps->log) {
CFRelease(ps->log);
}
bzero(ps, sizeof(PSStruct));
dispatch_async(dispatch_get_main_queue(), ^()
{ HandlePublishAllPowerSources(); });
});
dispatch_source_set_event_handler(ps->procdeathsrc, ^{
dispatch_source_cancel(ps->procdeathsrc);
});
dispatch_resume(ps->procdeathsrc);
*psid = gPSID++;
if (*psid == 0)
*psid = gPSID; *result = kIOReturnSuccess;
exit:
return KERN_SUCCESS;
}
kern_return_t _io_ps_update_pspowersource(
mach_port_t server __unused,
audit_token_t token,
int psid,
vm_offset_t details_ptr,
mach_msg_type_number_t details_len,
int *return_code)
{
CFMutableDictionaryRef details = NULL;
int callerPID;
CFStringRef psTypeStr = NULL;
CFNumberRef psIDKey = NULL;
int psID = 0;
audit_token_to_au32(token, NULL, NULL, NULL, NULL, NULL,
&callerPID, NULL, NULL);
*return_code = kIOReturnError;
details = (CFMutableDictionaryRef)IOCFUnserialize((const char *)details_ptr, NULL, 0, NULL);
if (!isA_CFDictionary(details))
{
*return_code = kIOReturnBadArgument;
} else {
PSStruct *next = iopsFromPSID(callerPID, psid);
if (!next) {
ERROR_LOG("Failed to find the power source for psid 0x%x from pid %d\n", psid, callerPID);
*return_code = kIOReturnNotFound;
} else {
psIDKey = CFDictionaryGetValue(details, CFSTR(kIOPSPowerSourceIDKey));
if (!isA_CFNumber(psIDKey)) {
psID = MAKE_UNIQ_SOURCE_ID(next->pid, next->psid);
psIDKey = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &psID);
if (psIDKey) {
CFDictionarySetValue(details, CFSTR(kIOPSPowerSourceIDKey), psIDKey);
CFRelease(psIDKey);
}
}
if (next->psType == kPSTypeUnknown) {
psTypeStr = CFDictionaryGetValue(details, CFSTR(kIOPSTypeKey));
if (isA_CFString(psTypeStr)) {
if (CFStringCompare(psTypeStr, CFSTR(kIOPSAccessoryType), 0) == kCFCompareEqualTo)
next->psType = kPSTypeAccessory;
else if ((CFStringCompare(psTypeStr, CFSTR(kIOPSUPSType), 0) == kCFCompareEqualTo)
#if TARGET_OS_EMBEDDED
|| (CFStringCompare(psTypeStr, CFSTR(kIOPSPrivateBatteryCaseType), 0) == kCFCompareEqualTo)
#endif
)
next->psType = kPSTypeUPS;
else if (CFStringCompare(psTypeStr, CFSTR(kIOPSInternalBatteryType), 0) == kCFCompareEqualTo)
next->psType = kPSTypeIntBattery;
}
}
INFO_LOG("Received power source(psid:%d) update from pid %d: %@\n", psid, callerPID, details);
if ((next->psType == kPSTypeIntBattery) || (next->psType == kPSTypeUPS)) {
if (next->description) {
CFRelease(next->description);
}
else {
notify_post(kIOPSNotifyAttach);
INFO_LOG("Posted \"%s\" for new power source id %d\n", kIOPSNotifyAttach, psid);
}
next->description = details;
updateLogBuffer(next, false);
dispatch_async(dispatch_get_main_queue(), ^()
{ HandlePublishAllPowerSources(); });
*return_code = kIOReturnSuccess;
}
else if (next->psType == kPSTypeAccessory) {
*return_code = HandleAccessoryPowerSources(next, details);
}
}
}
if (kIOReturnSuccess != *return_code) {
CFRelease(details);
}
vm_deallocate(mach_task_self(), details_ptr, details_len);
return 0;
}
kern_return_t _io_ps_release_pspowersource(
mach_port_t server __unused,
audit_token_t token,
int psid)
{
int callerPID;
audit_token_to_au32(token, NULL, NULL, NULL, NULL, NULL,
&callerPID, NULL, NULL);
INFO_LOG("Releasing power source id = %d\n", psid);
PSStruct *toRelease = iopsFromPSID(callerPID, psid);
if (toRelease) {
dispatch_source_cancel(toRelease->procdeathsrc);
}
return 0;
}
kern_return_t _io_ps_copy_powersources_info(
mach_port_t server __unused,
int type,
vm_offset_t *ps_ptr,
mach_msg_type_number_t *ps_len,
int *return_code)
{
CFMutableArrayRef return_value = NULL;
for (int i=0; i<kPSMaxCount; i++) {
if (gPSList[i].description == NULL) {
continue;
}
switch(type) {
case kIOPSSourceInternal:
if (gPSList[i].psType != kPSTypeIntBattery)
continue;
break;
case kIOPSSourceUPS:
if (gPSList[i].psType != kPSTypeUPS)
continue;
break;
case kIOPSSourceInternalAndUPS:
if ((gPSList[i].psType != kPSTypeIntBattery) && (gPSList[i].psType != kPSTypeUPS))
continue;
break;
case kIOPSSourceForAccessories:
if (gPSList[i].psType != kPSTypeAccessory)
continue;
break;
case kIOPSSourceAll:
break;
default:
continue;
}
if (!return_value) {
return_value = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks);
}
CFArrayAppendValue(return_value,
(const void *)gPSList[i].description);
}
if (!return_value) {
*ps_ptr = 0;
*ps_len = 0;
} else {
CFDataRef d = CFPropertyListCreateData(0, return_value,
kCFPropertyListBinaryFormat_v1_0,
0, NULL);
CFRelease(return_value);
if (d) {
*ps_len = (mach_msg_type_number_t)CFDataGetLength(d);
vm_allocate(mach_task_self(), (vm_address_t *)ps_ptr, *ps_len, TRUE);
memcpy((void *)*ps_ptr, CFDataGetBytePtr(d), *ps_len);
CFRelease(d);
}
}
*return_code = kIOReturnSuccess;
return 0;
}
static IOReturn HandleAccessoryPowerSources(PSStruct *ps, CFDictionaryRef update)
{
CFNumberRef n = NULL;
int old_cap = 0, new_cap = 0;
CFStringRef old_src = NULL, new_src = NULL;
#if TARGET_OS_IPHONE
CFStringRef old_name, new_name;
CFStringRef old_pname, new_pname;
bool old_exists, new_exists;
#endif
new_src = CFDictionaryGetValue(update, CFSTR(kIOPSPowerSourceStateKey));
n = CFDictionaryGetValue(update, CFSTR(kIOPSCurrentCapacityKey));
if (n) {
CFNumberGetValue(n, kCFNumberIntType, &new_cap);
}
if (!new_src || !n) {
ERROR_LOG("PS update is missing SourceState or Capacity\n");
return kIOReturnBadArgument;
}
if (ps->description != NULL) {
old_src = CFDictionaryGetValue(ps->description, CFSTR(kIOPSPowerSourceStateKey));
if (old_src && CFStringCompare(new_src, old_src, 0) != kCFCompareEqualTo) {
notify_post(kIOPSAccNotifyPowerSource);
INFO_LOG("Posted \"%s\" for power source id %ld\n", kIOPSAccNotifyPowerSource, ps->psid);
}
n = CFDictionaryGetValue(ps->description, CFSTR(kIOPSCurrentCapacityKey));
if (n) {
CFNumberGetValue(n, kCFNumberIntType, &old_cap);
}
if (new_cap != old_cap) {
notify_post(kIOPSAccNotifyTimeRemaining);
INFO_LOG("Posted \"%s\" for power source id %ld\n", kIOPSAccNotifyTimeRemaining, ps->psid);
}
#if TARGET_OS_IPHONE
old_name = new_name = NULL;
old_exists = CFDictionaryGetValueIfPresent(ps->description, CFSTR(kIOPSNameKey), (const void **)&old_name);
new_exists = CFDictionaryGetValueIfPresent(update, CFSTR(kIOPSNameKey), (const void **)&new_name);
if ((old_exists != new_exists) ||
(isA_CFString(old_name) && isA_CFString(new_name) &&
!CFEqual(old_name, new_name))) {
notify_post(kIOPSAccNotifyPowerSource); INFO_LOG("Posted \"%s\" for name change of power source id %ld\n", kIOPSAccNotifyPowerSource, ps->psid);
}
old_pname = new_pname = NULL;
old_exists = CFDictionaryGetValueIfPresent(ps->description, CFSTR(kIOPSPartNameKey), (const void **)&old_pname);
new_exists = CFDictionaryGetValueIfPresent(update, CFSTR(kIOPSPartNameKey), (const void **)&new_pname);
if ((old_exists != new_exists) ||
(isA_CFString(old_pname) && isA_CFString(new_pname) &&
!CFEqual(old_pname, new_pname))) {
notify_post(kIOPSAccNotifyPowerSource); INFO_LOG("Posted \"%s\" for partname change of power source id %ld\n", kIOPSAccNotifyPowerSource, ps->psid);
}
#endif
CFRelease(ps->description);
}
else {
notify_post(kIOPSAccNotifyTimeRemaining);
notify_post(kIOPSAccNotifyAttach);
INFO_LOG("Posted notifications for new power source id %ld\n", ps->psid);
}
ps->description = update;
return kIOReturnSuccess;
}
CFArrayRef copyPowerSourceLog(PSStruct *ps, CFAbsoluteTime ts)
{
CFIndex i, arrCnt;
CFDateRef entry_ts = NULL;
CFDateRef input_ts = NULL;
CFDictionaryRef entry = NULL;
CFMutableArrayRef updates = NULL;
arrCnt = CFArrayGetCount(ps->log);
if (arrCnt == 0)
goto exit;
updates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (updates == NULL) {
goto exit;
}
input_ts = CFDateCreate(NULL, ts);
if (input_ts == NULL) {
goto exit;
}
if (arrCnt < kBattLogMaxEntries)
ps->logIdx = 0;
i = ps->logIdx;
do {
entry = NULL;
if (i < arrCnt)
entry = CFArrayGetValueAtIndex(ps->log, i);
if (!isA_CFDictionary(entry)) {
i = (i+1) % kBattLogMaxEntries;
if (i >= arrCnt) i = 0;
continue;
}
if (!entry_ts) {
entry_ts = CFDictionaryGetValue(entry, CFSTR(kIOPSBattLogEntryTime));
if ((entry_ts == NULL) || (CFDateCompare(entry_ts, input_ts, NULL) == kCFCompareLessThan)) {
i = (i+1) % kBattLogMaxEntries;
if (i >= arrCnt) i = 0;
entry_ts = NULL;
continue;
}
}
CFArrayAppendValue(updates, entry);
i = (i+1) % kBattLogMaxEntries;
if (i >= arrCnt) i = 0;
} while (i != ps->logIdx);
CFArrayRemoveAllValues(ps->log);
ps->logIdx = 0;
exit:
if (input_ts)
CFRelease(input_ts);
return updates;
}
kern_return_t _io_ps_copy_chargelog(
mach_port_t server __unused,
audit_token_t token,
double ts,
vm_offset_t *updates,
mach_msg_type_number_t *updates_len,
int *rc)
{
CFDataRef serializedLog = NULL;
CFArrayRef psLog = NULL;
CFStringRef name = NULL;
CFMutableDictionaryRef logDict = NULL;
CFErrorRef err = NULL;
*updates = NULL; *updates_len = 0;
*rc = kIOReturnNotFound;
if (!auditTokenHasEntitlement(token, CFSTR("com.apple.private.iokit.powerlogging")))
{
*rc = kIOReturnNotPrivileged;
goto exit;
}
logDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!logDict)
goto exit;
for (int i=0; i<kPSMaxCount; i++)
{
if (!gPSList[i].log) {
continue;
}
name = CFDictionaryGetValue(gPSList[i].description, CFSTR(kIOPSNameKey));
if (!isA_CFString(name)) {
continue;
}
psLog = copyPowerSourceLog(&gPSList[i], ts);
if (!psLog)
continue;
CFDictionarySetValue(logDict, name, psLog);
CFRelease(psLog);
}
serializedLog = CFPropertyListCreateData(0, logDict,
kCFPropertyListBinaryFormat_v1_0, 0, &err);
if (!serializedLog)
goto exit;
*updates_len = (mach_msg_type_number_t)CFDataGetLength(serializedLog);
vm_allocate(mach_task_self(), (vm_address_t *)updates, *updates_len, TRUE);
if (*updates == 0)
goto exit;
memcpy((void *)*updates, CFDataGetBytePtr(serializedLog), *updates_len);
*rc = kIOReturnSuccess;
exit:
if (logDict) CFRelease(logDict);
if (serializedLog) CFRelease(serializedLog);
return KERN_SUCCESS;
}