/* * Copyright (c) 2003 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) 2003 Apple Computer, Inc. All rights reserved. * * HISTORY * * 30-Jan-03 ebold created * */ #include #include #include #include "PrivateLib.h" #include "AutoWakeScheduler.h" #include "RepeatingAutoWake.h" enum { kIOWakeTimer = 0, kIOPowerOnTimer = 1, kIOSleepTimer = 2, kIOShutdownTimer = 3 }; #if TARGET_OS_EMBEDDED #define MIN_SCHEDULE_TIME (5.0) #else #define MIN_SCHEDULE_TIME (10.0) #endif typedef void (*powerEventCallout)(CFDictionaryRef); /* * We use one PowerEventBehavior struct per-type of schedule power event * sleep/wake/power/shutdown/wakeORpower/restart. * The struct contains special behavior per-type. */ struct PowerEventBehavior { // These values change to reflect the state of current // and upcoming power events CFMutableArrayRef array; CFDictionaryRef currentEvent; CFRunLoopTimerRef timer; CFStringRef title; // wake and poweron sharedEvents pointer points to wakeorpoweron struct struct PowerEventBehavior *sharedEvents; // Callouts will be defined at startup time and not modified after that powerEventCallout timerExpirationCallout; powerEventCallout scheduleNextCallout; powerEventCallout noScheduledEventCallout; }; typedef struct PowerEventBehavior PowerEventBehavior; /* * Global structs tracking behaviors & current state */ PowerEventBehavior sleepBehavior; PowerEventBehavior shutdownBehavior; PowerEventBehavior restartBehavior; PowerEventBehavior wakeBehavior; PowerEventBehavior poweronBehavior; PowerEventBehavior wakeorpoweronBehavior; enum { kBehaviorsCount = 6 }; /* * Stick pointers to them in an array for safekeeping */ PowerEventBehavior *behaviors[] = { &sleepBehavior, &shutdownBehavior, &restartBehavior, &wakeBehavior, &poweronBehavior, &wakeorpoweronBehavior }; /* * forwards */ static bool isEntryValidAndFuturistic(CFDictionaryRef, CFDateRef); static void schedulePowerEvent(PowerEventBehavior *); static bool purgePastEvents(PowerEventBehavior *); static void copyScheduledPowerChangeArrays(void); static CFDictionaryRef getEarliestUpcoming(PowerEventBehavior *); static kern_return_t openHIDService(io_connect_t *); static void wakeDozingMachine(void); static bool isRepeating(CFDictionaryRef); static CFDateRef _getScheduledEventDate(CFDictionaryRef); static CFArrayRef copyMergedEventArray(PowerEventBehavior *, PowerEventBehavior *); static CFComparisonResult compareEvDates(CFDictionaryRef, CFDictionaryRef, void *); void poweronScheduleCallout(CFDictionaryRef); void wakeScheduleCallout(CFDictionaryRef); void wakeNoScheduledEventCallout(CFDictionaryRef); void wakeTimerExpiredCallout(CFDictionaryRef); void poweronTimerExpiredCallout(CFDictionaryRef); void sleepTimerExpiredCallout(CFDictionaryRef); void shutdownTimerExpiredCallout(CFDictionaryRef); void restartTimerExpiredCallout(CFDictionaryRef); /* AutoWakeScheduler overview * * PURPOSE * Set wake and power on timers to automatically power on the machine at a * user/app requested time. * Requests come via IOKit/pwr_mgt/IOPMLib.h:IOPMSchedulePowerEvent() * * On pre-2004 machines this is the PMU's responsibility. * The SMU has taken over the AutoWake/AutoPower role on some post-2004 machines. * The IOPMrootDomain kernel entity routes all requests to the appropriate * controller. * * POWER ON * Every time we set a power on time in the PMU, start a CFTimer to fire at the same time. * (in scheduleShutdownTime()) * If the CFTimer fires, then the machine was not powered off and we should find the * next power on date and schedule that. (in handleTimerPowerOnReset()) * If the machine is powered off, the CFTimer won't fire and the PMU will power the machine. * * WAKE * Wake is simpler than power on, since we get a notification on the way to sleep. * At going-to-sleep time we scan the wake_arr CFArray for the next upcoming * wakeup time (in AutoWakeSleepWakeNotification()) * * LOADING NEW AUTOWAKEUP TIMES * Via SCPreferences notifications * * PURGING OLD TIMES * Done only at boot and wakeup times. We have to write the file back to disk (if it changed). * So to minimize disk access we only purge when we think the disk is "up" anyway. */ __private_extern__ void AutoWake_prime(void) { PowerEventBehavior *this_behavior; int i; // clear out behavior structs for good measure for(i=0; itimer = 0; if( behave->timerExpirationCallout ) { (*behave->timerExpirationCallout)(behave->currentEvent); } if(isRepeating(behave->currentEvent)) { RepeatingAutoWakeRepeatingEventOcurred(behave->currentEvent); } // free and NULL currentEvent???? return; } /* * Required behaviors at event scheduling time: * * on wake and/or poweron: * - transmit expected wake/on time to underlying hardware that will wake * the system when appropriate, be it PMU, SMU, SMC, or beyond. * */ static void schedulePowerEvent(PowerEventBehavior *behave) { static CFRunLoopTimerContext tmr_context = {0,0,0,0,0}; CFAbsoluteTime fire_time = 0.0; CFDictionaryRef upcoming = NULL; CFDateRef temp_date = NULL; if(behave->timer) { CFRunLoopTimerInvalidate(behave->timer); behave->timer = 0; } // find upcoming time upcoming = getEarliestUpcoming(behave); if(!upcoming) { // No scheduled events if (behave->noScheduledEventCallout) { (*behave->noScheduledEventCallout)(NULL); } return; } /* * Perform any necessary actions at schedulePowerEvent time */ if ( behave->scheduleNextCallout ) { (*behave->scheduleNextCallout)(upcoming); } behave->currentEvent = (CFDictionaryRef)upcoming; tmr_context.info = (void *)behave; temp_date = _getScheduledEventDate(upcoming); if(!temp_date) goto exit; fire_time = CFDateGetAbsoluteTime(temp_date); behave->timer = CFRunLoopTimerCreate(0, fire_time, 0.0, 0, 0, handleTimerExpiration, &tmr_context); if(behave->timer) { CFRunLoopAddTimer( CFRunLoopGetCurrent(), behave->timer, kCFRunLoopDefaultMode); CFRelease(behave->timer); } exit: return; } /****************************************************************************** ****************************************************************************** * Event type-specific callouts ****************************************************************************** ******************************************************************************/ /* * poweron */ void poweronScheduleCallout(CFDictionaryRef event) { IOPMSchedulePowerEvent( _getScheduledEventDate(event), NULL, CFSTR(kIOPMAutoPowerScheduleImmediate) ); } void poweronTimerExpiredCallout(CFDictionaryRef event __unused) { schedulePowerEvent(&poweronBehavior); } /* * wake */ void wakeScheduleCallout(CFDictionaryRef event) { CFDateRef chosenWakeDate = _getScheduledEventDate(event); _pm_scheduledevent_choose_best_wake_event(kChooseFullWake, CFDateGetAbsoluteTime(chosenWakeDate)); } void wakeTimerExpiredCallout(CFDictionaryRef event __unused) { wakeDozingMachine(); } void wakeNoScheduledEventCallout(CFDictionaryRef event __unused) { _pm_scheduledevent_choose_best_wake_event(kChooseFullWake, 0); } /* * sleep */ void sleepTimerExpiredCallout(CFDictionaryRef event __unused) { _askNicelyThenSleepSystem(); } /* * shutdown */ void shutdownTimerExpiredCallout(CFDictionaryRef event __unused) { _askNicelyThenShutdownSystem(); } /* * restart */ void restartTimerExpiredCallout(CFDictionaryRef event __unused) { _askNicelyThenRestartSystem(); } /****************************************************************************** ****************************************************************************** * Utility functions from here on out ****************************************************************************** ******************************************************************************/ /* * * isEntryValidAndFuturistic * Returns true if the CFDictionary is validly formed * AND if the date is in the future * Returns false if anything about the dictionary is invalid * OR if the CFDate is prior to the current time * */ static bool isEntryValidAndFuturistic(CFDictionaryRef wakeup_dict, CFDateRef date_now) { CFDateRef wakeup_date; bool ret = true; wakeup_dict = isA_CFDictionary(wakeup_dict); if(!wakeup_dict) { // bogus entry! ret = false; } else { // valid entry wakeup_date = isA_CFDate(CFDictionaryGetValue(wakeup_dict, CFSTR(kIOPMPowerEventTimeKey))); if( !wakeup_date || (kCFCompareLessThan == CFDateCompare(wakeup_date, date_now, 0))) { // date is too early ret = false; } // otherwise date is after now, and ret = true } return ret; } /* * * Purge past wakeup times * Does not care whether its operating on wakeup or poweron array. * Just purges all entries with a time < now * returns true on success, false on any failure * */ static bool purgePastEvents(PowerEventBehavior *behave) { bool array_has_changed = false; CFDateRef date_now; bool ret; SCPreferencesRef prefs = 0; if( !behave || !behave->title || !behave->array || (0 == CFArrayGetCount(behave->array))) { return true; } date_now = CFDateCreate(0, CFAbsoluteTimeGetCurrent()); // Loop over the array and remove any values that are in the past. // Since array is sorted by date already, we stop once we reach an event // scheduled in the future. // Do not try to optimize the CFArrayGetCount out of the while loop; this value may // change during loop execution. while( (0 < CFArrayGetCount(behave->array)) && !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(behave->array, 0), date_now)) { // Remove entry from the array - its time has past // The rest of the array will shift down to fill index 0 CFArrayRemoveValueAtIndex(behave->array, 0); array_has_changed = true; } CFRelease(date_now); if(array_has_changed) { // write new, purged array to disk // We should soon-after get a prefs notification to re-read the values prefs = SCPreferencesCreate(0, CFSTR("IOKit-AutoWake"), CFSTR(kIOPMAutoWakePrefsPath)); if(!prefs) return 0; if(!SCPreferencesLock(prefs, true)) { ret = false; goto exit; } SCPreferencesSetValue(prefs, behave->title, behave->array); if(!SCPreferencesCommitChanges(prefs)) { ret = false; goto exit; } SCPreferencesUnlock(prefs); } ret = true; exit: if(prefs) CFRelease(prefs); return ret; } /* * * copySchedulePowerChangeArrays * */ static void copyScheduledPowerChangeArrays(void) { CFArrayRef tmp; SCPreferencesRef prefs; PowerEventBehavior *this_behavior; int i; prefs = SCPreferencesCreate(0, CFSTR("PM-configd-AutoWake"), CFSTR(kIOPMAutoWakePrefsPath)); if(!prefs) return; // Loop through all sleep, wake, shutdown powerbehaviors for(i=0; iarray) { CFRelease(this_behavior->array); this_behavior->array = NULL; } tmp = isA_CFArray(SCPreferencesGetValue(prefs, this_behavior->title)); if(tmp && (0 < CFArrayGetCount(tmp))) { this_behavior->array = CFArrayCreateMutableCopy(0, 0, tmp); } else { this_behavior->array = NULL; } } CFRelease(prefs); } /* * * Find earliest upcoming wakeup time * */ static CFDictionaryRef getEarliestUpcoming(PowerEventBehavior *b) { CFArrayRef arr = NULL; CFDateRef now = NULL; CFDictionaryRef the_result = NULL; int i, count; if(!b) return NULL; // wake and poweron types get merged with wakeorpoweron array if(b->sharedEvents) { // musst release arr later arr = copyMergedEventArray(b, b->sharedEvents); } else { arr = b->array; } // If the array is NULL, we have no work to do. if(!arr || (0 == CFArrayGetCount(arr))) goto exit; count = CFArrayGetCount(arr); if(0 == count) goto exit; now = CFDateCreate(0, CFAbsoluteTimeGetCurrent() + MIN_SCHEDULE_TIME); // iterate through all past entries, stopping at one occurring // >MIN_SCHEDULE_TIME seconds in the future, or at the end of the array i = 0; while( (i < count) && !isEntryValidAndFuturistic(CFArrayGetValueAtIndex(arr, i), now) ) { i++; } CFRelease(now); if(i < count) { the_result = CFArrayGetValueAtIndex(arr, i); } exit: if(arr && b->sharedEvents) CFRelease(arr); return the_result; } /* * * comapareEvDates() - internal sorting helper for copyMergedEventArray() * */ static CFComparisonResult compareEvDates( CFDictionaryRef a1, CFDictionaryRef a2, void *c __unused) { CFDateRef d1, d2; a1 = isA_CFDictionary(a1); a2 = isA_CFDictionary(a2); if(!a1) return kCFCompareGreaterThan; else if(!a2) return kCFCompareLessThan; d1 = isA_CFDate(CFDictionaryGetValue(a1, CFSTR(kIOPMPowerEventTimeKey))); d2 = isA_CFDate(CFDictionaryGetValue(a2, CFSTR(kIOPMPowerEventTimeKey))); if(!d1) return kCFCompareGreaterThan; else if(!d2) return kCFCompareLessThan; return CFDateCompare(d1, d2, 0); } /* * * copyMergedEventArray * * Takes two PowerEventBehavior*, merges their CFArray array members into one * mutable array, sorted by date. */ static CFArrayRef copyMergedEventArray( PowerEventBehavior *a, PowerEventBehavior *b) { CFMutableArrayRef merged; int bcount; CFRange rng; if(!a || !b) return NULL; if(!a->array && !b->array) return NULL; if(!a->array) return CFRetain(b->array); if(!b->array) return CFRetain(a->array); // merge! merged = CFArrayCreateMutableCopy(0, 0, a->array); bcount = CFArrayGetCount(b->array); rng = CFRangeMake(0, bcount); CFArrayAppendArray(merged, b->array, rng); // sort! // We sort using the same compare_dates function used in IOKitUser // pwr_mgt/IOPMAutoWake.c. Arrays must be sorted identically to how // they would be there. bcount = CFArrayGetCount(merged); rng = CFRangeMake(0, bcount); CFArraySortValues(merged, rng, (CFComparatorFunction)compareEvDates, 0); // caller must release return merged; } /* * * Find HID service. Only used by wakeDozingMachine * */ #if HAVE_HID_SYSTEM static kern_return_t openHIDService(io_connect_t *connection) { kern_return_t kr; io_service_t service; io_connect_t hid_connect = MACH_PORT_NULL; service = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching(kIOHIDSystemClass)); if (MACH_PORT_NULL == service) { return kIOReturnNotFound; } kr = IOServiceOpen( service, mach_task_self(), kIOHIDParamConnectType, &hid_connect); IOObjectRelease(service); if (kr != KERN_SUCCESS) { return kr; } *connection = hid_connect; return kr; } #endif /* HAVE_HID_SYSTEM */ /* * * Wakes a dozing machine by posting a NULL HID event * Will thus also wake displays on a running machine running * */ static void wakeDozingMachine(void) { #if HAVE_HID_SYSTEM IOGPoint loc; kern_return_t kr; NXEvent nullEvent = {NX_NULLEVENT, {0, 0}, 0, -1, 0}; static io_connect_t io_connection = MACH_PORT_NULL; // If the HID service has never been opened, do it now if (io_connection == MACH_PORT_NULL) { kr = openHIDService(&io_connection); if (kr != KERN_SUCCESS) { io_connection = MACH_PORT_NULL; return; } } // Finally, post a NULL event IOHIDPostEvent( io_connection, NX_NULLEVENT, loc, &nullEvent.data, FALSE, 0, FALSE ); #endif /* HAVE_HID_SYSTEM */ return; } static bool isRepeating(CFDictionaryRef event) { CFStringRef whose = CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventAppNameKey)); if( whose && CFEqual(whose, CFSTR("Repeating")) ) return true; else return false; } static CFDateRef _getScheduledEventDate(CFDictionaryRef event) { return isA_CFDate(CFDictionaryGetValue(event, CFSTR(kIOPMPowerEventTimeKey))); }