#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/SCPreferencesPrivate.h>
#include <SystemConfiguration/SCDynamicStorePrivate.h>
#include <SystemConfiguration/SCDynamicStoreCopySpecificPrivate.h>
#include <SystemConfiguration/SCDPlugin.h>
#include <IOKit/pwr_mgt/IOPM.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/pwr_mgt/IOPMLibPrivate.h>
#include <IOKit/IOKitKeysPrivate.h>
#include <IOKit/IOMessage.h>
#include <IOKit/ps/IOPSKeys.h>
#include <IOKit/ps/IOPowerSources.h>
#include <syslog.h>
#include <unistd.h>
#include "PMSettings.h"
#include "PSLowPower.h"
#include "BatteryTimeRemaining.h"
#include "AutoWakeScheduler.h"
#include "RepeatingAutoWake.h"
#include "PrivateLib.h"
#define kIOPMAppName "Power Management configd plugin"
#define kIOPMPrefsPath "com.apple.PowerManagement.xml"
#define kApplePMUUCMagicCookie 0x0101BEEF
static CFStringRef EnergyPrefsKey = NULL;
static CFStringRef AutoWakePrefsKey = NULL;
static CFStringRef ConsoleUserKey = NULL;
static SCDynamicStoreRef energyDS = NULL;
static io_service_t gIOResourceService = 0;
static io_connect_t _pm_ack_port = 0;
static void tellSMU_GMTOffset(void);
static void
PMUInterestNotification(void *refcon, io_service_t service, natural_t messageType, void *arg)
{
if((kIOPMUMessageLegacyAutoWake == messageType) ||
(kIOPMUMessageLegacyAutoPower == messageType) )
AutoWakePMUInterestNotification(messageType, (UInt32)arg);
}
static void
RootDomainInterestNotification(void *refcon, io_service_t service, natural_t messageType, void *arg)
{
CFArrayRef battery_info;
if(kIOPMMessageBatteryStatusHasChanged == messageType)
{
battery_info = isA_CFArray(_copyBatteryInfo());
if(!battery_info) return;
PMSettingsBatteriesHaveChanged(battery_info);
BatteryTimeRemainingBatteriesHaveChanged(battery_info);
CFRelease(battery_info);
}
}
static void
SleepWakeCallback(void * port,io_service_t y,natural_t messageType,void * messageArgument)
{
BatteryTimeRemainingSleepWakeNotification(messageType);
PMSettingsSleepWakeNotification(messageType);
AutoWakeSleepWakeNotification(messageType);
RepeatingAutoWakeSleepWakeNotification(messageType);
switch ( messageType ) {
case kIOMessageSystemWillSleep:
tellSMU_GMTOffset(); case kIOMessageCanSystemSleep:
IOAllowPowerChange(_pm_ack_port, (long)messageArgument);
break;
case kIOMessageSystemHasPoweredOn:
break;
}
}
static void
ESPrefsHaveChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
{
CFRange key_range = CFRangeMake(0, CFArrayGetCount(changedKeys));
if(CFArrayContainsValue(changedKeys, key_range, EnergyPrefsKey))
{
PMSettingsPrefsHaveChanged();
PSLowPowerPrefsHaveChanged();
}
if(CFArrayContainsValue(changedKeys, key_range, AutoWakePrefsKey))
{
AutoWakePrefsHaveChanged();
RepeatingAutoWakePrefsHaveChanged();
}
if(CFArrayContainsValue(changedKeys, key_range, ConsoleUserKey))
{
CFArrayRef sessionList = SCDynamicStoreCopyConsoleInformation(energyDS);
if (!sessionList)
sessionList = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks);
if (sessionList)
{
IORegistryEntrySetCFProperty(gIOResourceService, CFSTR(kIOConsoleUsersKey), sessionList);
CFRelease(sessionList);
}
}
return;
}
extern void
PowerSourcesHaveChanged(void *info)
{
CFTypeRef ps_blob;
ps_blob = isA_CFDictionary(IOPSCopyPowerSourcesInfo());
if(!ps_blob) return;
PSLowPowerPSChange(ps_blob);
PMSettingsPSChange(ps_blob);
CFRelease(ps_blob);
}
static void
tellSMU_GMTOffset(void)
{
static io_registry_entry_t smuRegEntry = MACH_PORT_NULL;
static io_connect_t smuConnect = MACH_PORT_NULL;
CFTimeZoneRef tzr = NULL;
CFDataRef d = NULL;
int secondsOffset = 0;
if(!systemHasSMU()) return;
if(MACH_PORT_NULL == smuRegEntry)
{
smuRegEntry = (io_registry_entry_t)IOServiceGetMatchingService(0,
IOServiceNameMatching("AppleSMU"));
if(MACH_PORT_NULL == smuRegEntry) {
return;
}
IOServiceOpen(smuRegEntry, mach_task_self(), kApplePMUUCMagicCookie, &smuConnect);
}
CFTimeZoneResetSystem();
tzr = CFTimeZoneCopySystem();
if(!tzr) goto exit;
secondsOffset = (int)CFTimeZoneGetSecondsFromGMT(tzr, CFAbsoluteTimeGetCurrent());
d = CFDataCreate(0, &secondsOffset, sizeof(int));
if(!d) goto exit;
IOConnectSetCFProperty(smuConnect, CFSTR("TimeZoneOffsetSeconds"), d);
exit:
if(tzr) CFRelease(tzr);
if(d) CFRelease(d);
}
static void
displayPowerStateChange(void *ref, io_service_t service, natural_t messageType, void *arg)
{
static int level = 0;
switch (messageType)
{
case kIOMessageDeviceWillPowerOff:
level++;
if(2 == level) {
tellSMU_GMTOffset();
}
break;
case kIOMessageDeviceHasPoweredOn:
level = 0;
break;
}
}
static void
initializeESPrefsDynamicStore(void)
{
CFRunLoopSourceRef CFrls;
energyDS = SCDynamicStoreCreate(NULL, CFSTR(kIOPMAppName), &ESPrefsHaveChanged, NULL);
EnergyPrefsKey = SCDynamicStoreKeyCreatePreferences(NULL, CFSTR(kIOPMPrefsPath), kSCPreferencesKeyApply);
if(EnergyPrefsKey)
SCDynamicStoreAddWatchedKey(energyDS, EnergyPrefsKey, FALSE);
AutoWakePrefsKey = SCDynamicStoreKeyCreatePreferences(NULL, CFSTR(kIOPMAutoWakePrefsPath), kSCPreferencesKeyCommit);
if(AutoWakePrefsKey)
SCDynamicStoreAddWatchedKey(energyDS, AutoWakePrefsKey, FALSE);
gIOResourceService = IORegistryEntryFromPath(NULL, kIOServicePlane ":/" kIOResourcesClass);
ConsoleUserKey = SCDynamicStoreKeyCreateConsoleUser( NULL );
if(ConsoleUserKey && gIOResourceService)
SCDynamicStoreAddWatchedKey(energyDS, ConsoleUserKey, FALSE);
CFrls = SCDynamicStoreCreateRunLoopSource(NULL, energyDS, 0);
if(CFrls) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFrls, kCFRunLoopDefaultMode);
CFRelease(CFrls);
}
return;
}
static void
initializePowerSourceChangeNotification(void)
{
CFRunLoopSourceRef CFrls;
CFrls = IOPSNotificationCreateRunLoopSource(PowerSourcesHaveChanged, NULL);
if(CFrls) {
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFrls, kCFRunLoopDefaultMode);
CFRelease(CFrls);
}
}
static void
initializeInterestNotifications()
{
IONotificationPortRef notify_port = 0;
IONotificationPortRef r_notify_port = 0;
io_object_t notification_ref = 0;
io_service_t pmu_service_ref = 0;
io_service_t root_domain_ref = 0;
CFRunLoopSourceRef rlser = 0;
CFRunLoopSourceRef r_rlser = 0;
IOReturn ret;
pmu_service_ref = IOServiceGetMatchingService(0, IOServiceNameMatching("ApplePMU"));
if(!pmu_service_ref) goto root_domain;
notify_port = IONotificationPortCreate(0);
ret = IOServiceAddInterestNotification(notify_port, pmu_service_ref,
kIOGeneralInterest, PMUInterestNotification,
0, ¬ification_ref);
if(kIOReturnSuccess != ret) goto root_domain;
rlser = IONotificationPortGetRunLoopSource(notify_port);
if(!rlser) goto root_domain;
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlser, kCFRunLoopDefaultMode);
root_domain:
root_domain_ref = IOServiceGetMatchingService(0, IOServiceNameMatching("IOPMrootDomain"));
if(!root_domain_ref) goto finish;
r_notify_port = IONotificationPortCreate(0);
ret = IOServiceAddInterestNotification(r_notify_port, root_domain_ref,
kIOGeneralInterest, RootDomainInterestNotification,
0, ¬ification_ref);
if(kIOReturnSuccess != ret) goto finish;
r_rlser = IONotificationPortGetRunLoopSource(r_notify_port);
if(!r_rlser) goto finish;
CFRunLoopAddSource(CFRunLoopGetCurrent(), r_rlser, kCFRunLoopDefaultMode);
finish:
if(rlser) CFRelease(rlser);
if(r_rlser) CFRelease(r_rlser);
if(notify_port) IOObjectRelease((io_object_t)notify_port);
if(r_notify_port) IOObjectRelease((io_object_t)r_notify_port);
if(pmu_service_ref) IOObjectRelease(pmu_service_ref);
if(root_domain_ref) IOObjectRelease(root_domain_ref);
return;
}
static bool
systemHasSMU(void)
{
static io_registry_entry_t smuRegEntry = MACH_PORT_NULL;
static bool known = false;
if(known) return (smuRegEntry?true:false);
smuRegEntry = (io_registry_entry_t)IOServiceGetMatchingService(0,
IOServiceNameMatching("AppleSMU"));
if(MACH_PORT_NULL == smuRegEntry)
{
known = true;
return false;
}
IOObjectRelease(smuRegEntry);
known = true;
return true;
}
static void
intializeDisplaySleepNotifications(void)
{
IONotificationPortRef note_port = MACH_PORT_NULL;
CFRunLoopSourceRef dimSrc = NULL;
io_service_t display_wrangler = MACH_PORT_NULL;
io_object_t dimming_notification_object = MACH_PORT_NULL;
IOReturn ret;
if(!systemHasSMU()) return;
display_wrangler = IOServiceGetMatchingService(NULL, IOServiceNameMatching("IODisplayWrangler"));
if(!display_wrangler) return;
note_port = IONotificationPortCreate(NULL);
if(!note_port) return;
ret = IOServiceAddInterestNotification(note_port, display_wrangler,
kIOGeneralInterest, displayPowerStateChange,
NULL, &dimming_notification_object);
if(ret != kIOReturnSuccess) return;
dimSrc = IONotificationPortGetRunLoopSource(note_port);
if(dimSrc)
{
CFRunLoopAddSource(CFRunLoopGetCurrent(), dimSrc, kCFRunLoopDefaultMode);
CFRelease(dimSrc);
}
}
static void _ioupsd_exited(
pid_t pid,
int status,
struct rusage *rusage,
void *context)
{
syslog(LOG_INFO, "PowerManagement: /usr/libexec/ioupsd(%d) has exited with status %d\n", pid, status);
}
void
prime()
{
char *argv[2] = {"/usr/libexec/ioupsd", NULL};
pid_t _ioupsd_pid;
BatteryTimeRemaining_prime();
PMSettings_prime();
PSLowPower_prime();
AutoWake_prime();
RepeatingAutoWake_prime();
_ioupsd_pid = _SCDPluginExecCommand(&_ioupsd_exited, 0, 0, 0,
"/usr/libexec/ioupsd", argv);
return;
}
void
load(CFBundleRef bundle, Boolean bundleVerbose)
{
IONotificationPortRef notify;
io_object_t anIterator;
initializePowerSourceChangeNotification();
initializeESPrefsDynamicStore();
initializeInterestNotifications();
intializeDisplaySleepNotifications();
_pm_ack_port = IORegisterForSystemPower (0, ¬ify, SleepWakeCallback, &anIterator);
if ( _pm_ack_port != NULL ) {
if(notify) CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notify),
kCFRunLoopDefaultMode);
}
}
#ifdef STANDALONE
int
main(int argc, char **argv)
{
openlog("pmcfgd", LOG_PID | LOG_NDELAY, LOG_USER);
load(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
prime();
CFRunLoopRun();
exit(0);
return 0;
}
#endif