1/*
2 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <TargetConditionals.h>
25#include <IOKit/pwr_mgt/IOPMPrivate.h>
26#include <IOKit/pwr_mgt/IOPMLibDefs.h>
27#include "IOSystemConfiguration.h"
28#include "IOPMKeys.h"
29#include "IOPMLib.h"
30#include "IOPMLibPrivate.h"
31
32#include "powermanagement_mig.h"
33#include "powermanagement.h"
34
35#include <servers/bootstrap.h>
36#include <notify.h>
37
38enum {
39    kIOPMMaxScheduledEntries = 1000
40};
41
42#ifndef kIOPMMaintenanceScheduleImmediate
43#define kIOPMMaintenanceScheduleImmediate   "MaintenanceImmediate"
44#endif
45
46#ifndef kPMSetMaintenanceWakeCalendar
47#define kPMSetMaintenanceWakeCalendar 8
48#endif
49
50// Forward decls
51
52static CFAbsoluteTime roundOffDate(
53    CFAbsoluteTime time);
54static CFDictionaryRef _IOPMCreatePowerOnDictionary(
55    CFAbsoluteTime the_time,
56    CFStringRef the_id,
57    CFStringRef type);
58static bool inputsValid(
59    CFDateRef time_to_wake,
60    CFStringRef my_id,
61    CFStringRef type);
62static IOReturn _setRootDomainProperty(
63    CFStringRef key,
64    CFTypeRef val);
65static void tellClockController(
66    CFStringRef command,
67    CFDateRef power_date);
68IOReturn IOPMSchedulePowerEvent(
69    CFDateRef time_to_wake,
70    CFStringRef my_id,
71    CFStringRef type);
72IOReturn IOPMCancelScheduledPowerEvent(
73    CFDateRef time_to_wake,
74    CFStringRef my_id,
75    CFStringRef wake_or_restart);
76CFArrayRef IOPMCopyScheduledPowerEvents( void );
77
78static IOReturn doAMaintenanceWake(CFDateRef earliestRequest, int type);
79
80__private_extern__ IOReturn _copyPMServerObject(int selector, int assertionID, CFTypeRef *objectOut);
81
82
83IOReturn _pm_connect(mach_port_t *newConnection);
84IOReturn _pm_disconnect(mach_port_t connection);
85
86#define ROUND_SCHEDULE_TIME	(5.0)
87#define MIN_SCHEDULE_TIME	(5.0)
88
89static CFAbsoluteTime roundOffDate(CFAbsoluteTime time)
90{
91    // round time down to the closest multiple of ROUND_SCHEDULE_TIME seconds
92    // CFAbsoluteTimes are encoded as doubles
93    return (CFAbsoluteTime) (trunc(time / ((double) ROUND_SCHEDULE_TIME)) * ((double) ROUND_SCHEDULE_TIME));
94}
95
96static CFDictionaryRef
97_IOPMCreatePowerOnDictionary(
98    CFAbsoluteTime the_time,
99    CFStringRef the_id,
100    CFStringRef type)
101{
102    CFMutableDictionaryRef          d;
103    CFDateRef                       the_date;
104
105    // make sure my_id is valid or NULL
106    the_id = isA_CFString(the_id);
107    // round wakeup time to last ROUND_SCHEDULE_TIME second increment
108    the_time = roundOffDate(the_time);
109    // package AbsoluteTime as a date for CFType purposes
110    the_date = CFDateCreate(0, the_time);
111    d = CFDictionaryCreateMutable(0, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
112    if(!d) return NULL;
113    CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventTimeKey), the_date);
114    if(!the_id) the_id = CFSTR("");
115    CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventAppNameKey), the_id);
116    CFDictionaryAddValue(d, CFSTR(kIOPMPowerEventTypeKey), type);
117    CFRelease(the_date);
118    return d;
119}
120
121static bool
122inputsValid(
123    CFDateRef time_to_wake,
124    CFStringRef my_id __unused,
125    CFStringRef type)
126{
127    // NULL is an acceptable input for my_id
128
129    // NULL is only an accetable input to IOPMSchedulePowerEvent
130    // if the type of event being scheduled is Immediate; in which
131    // case NULL means "zero out current settings" to the hardware.
132    if (!isA_CFDate(time_to_wake)
133        && !CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate))
134        && !CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)))
135    {
136        return false;
137    }
138
139    if(!isA_CFString(type)) return false;
140    if(!(CFEqual(type, CFSTR(kIOPMAutoWake)) ||
141        CFEqual(type, CFSTR(kIOPMAutoPowerOn)) ||
142        CFEqual(type, CFSTR(kIOPMAutoWakeOrPowerOn)) ||
143        CFEqual(type, CFSTR(kIOPMAutoSleep)) ||
144        CFEqual(type, CFSTR(kIOPMAutoShutdown)) ||
145        CFEqual(type, CFSTR(kIOPMAutoRestart)) ||
146        CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate)) ||
147        CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)) ||
148        CFEqual(type, CFSTR(kIOPMAutoWakeRelativeSeconds)) ||
149        CFEqual(type, CFSTR(kIOPMAutoPowerRelativeSeconds)) ||
150        CFEqual(type, CFSTR(kIOPMMaintenanceScheduleImmediate)) ||
151        CFEqual(type, CFSTR(kIOPMSleepServiceScheduleImmediate)) ))
152    {
153        return false;
154    }
155
156    return true;
157}
158
159
160static IOReturn
161_setRootDomainProperty(
162    CFStringRef                 key,
163    CFTypeRef                   val)
164{
165    io_registry_entry_t         root_domain;
166    IOReturn                    ret;
167
168    root_domain = IORegistryEntryFromPath( kIOMasterPortDefault,
169                kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
170    if(!root_domain) return kIOReturnError;
171
172    ret = IORegistryEntrySetCFProperty(root_domain, key, val);
173
174    IOObjectRelease(root_domain);
175    return ret;
176}
177
178static IOReturn doAMaintenanceWake(
179    CFDateRef                   earliestRequest,
180    int                         type)
181 {
182    CFGregorianDate         maintGregorian;
183    IOPMCalendarStruct      pmMaintenanceDate;
184    IOReturn                connectReturn = 0;
185    size_t                  connectReturnSize = sizeof(IOReturn);
186    io_connect_t            root_domain_connect = IO_OBJECT_NULL;
187    io_registry_entry_t     root_domain = IO_OBJECT_NULL;
188    IOReturn                ret = kIOReturnError;
189    kern_return_t           kr = -1;
190
191    // Package maintenance time as a PMCalendarType and pass it into IOPMrootDomain
192    CFTimeZoneRef  myTimeZone = NULL;
193
194    myTimeZone = CFTimeZoneCreateWithTimeIntervalFromGMT(0, 0.0);
195    if (!myTimeZone)
196        goto exit;
197
198    maintGregorian = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(earliestRequest), myTimeZone);
199
200    CFRelease(myTimeZone);
201
202    // Stuff into PM Calendar struct
203    bzero(&pmMaintenanceDate, sizeof(pmMaintenanceDate));
204    pmMaintenanceDate.year      = maintGregorian.year;
205    pmMaintenanceDate.month     = maintGregorian.month;
206    pmMaintenanceDate.day       = maintGregorian.day;
207    pmMaintenanceDate.hour      = maintGregorian.hour;
208    pmMaintenanceDate.minute    = maintGregorian.minute;
209    pmMaintenanceDate.second    = maintGregorian.second;
210    pmMaintenanceDate.selector  = type;
211
212    // Open up RootDomain
213
214    root_domain = IORegistryEntryFromPath( kIOMasterPortDefault,
215                kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
216
217    if (root_domain != IO_OBJECT_NULL)
218    {
219        kr = IOServiceOpen(root_domain, mach_task_self(), 0, &root_domain_connect);
220
221        if (KERN_SUCCESS == kr)
222        {
223            kr = IOConnectCallStructMethod(
224                root_domain_connect,
225                kPMSetMaintenanceWakeCalendar,
226                &pmMaintenanceDate, sizeof(pmMaintenanceDate),      // inputs struct
227                &connectReturn, &connectReturnSize);                // outputs struct
228
229            IOServiceClose(root_domain_connect);
230        }
231
232        IOObjectRelease(root_domain);
233    }
234
235    if ((KERN_SUCCESS != kr) || (kIOReturnSuccess != connectReturn))
236    {
237        ret = kIOReturnError;
238        goto exit;
239    }
240
241    ret = kIOReturnSuccess;
242exit:
243    return ret;
244}
245
246static void
247tellClockController(
248    CFStringRef command,
249    CFDateRef power_date)
250{
251    CFAbsoluteTime          now, wake_time;
252    CFGregorianDate         gmt_calendar;
253    CFTimeZoneRef           gmt_tz = NULL;
254    long int                diff_secs;
255    IOReturn                ret;
256    CFNumberRef             seconds_delta = NULL;
257    IOPMCalendarStruct      *cal_date = NULL;
258    CFMutableDataRef        date_data = NULL;
259
260    if(!command) goto exit;
261
262    // We broadcast the wakeup time both as calendar date struct and as seconds.
263    //  * AppleRTC hardware needs the date in a structured calendar format
264
265    // ******************** Calendar struct ************************************
266
267    date_data = CFDataCreateMutable(NULL, sizeof(IOPMCalendarStruct));
268    CFDataSetLength(date_data, sizeof(IOPMCalendarStruct));
269    cal_date = (IOPMCalendarStruct *)CFDataGetBytePtr(date_data);
270    bzero(cal_date, sizeof(IOPMCalendarStruct));
271
272    if(!power_date) {
273
274        // Zeroed out calendar means "clear wakeup timer"
275
276    } else {
277
278        // A calendar struct stuffed with meaningful date and time
279        // schedules a wake or power event for then.
280
281        wake_time = CFDateGetAbsoluteTime(power_date);
282
283        gmt_tz = CFTimeZoneCreateWithTimeIntervalFromGMT(0, 0.0);
284        gmt_calendar = CFAbsoluteTimeGetGregorianDate(wake_time, gmt_tz);
285        CFRelease(gmt_tz);
286
287        cal_date->second    = lround(gmt_calendar.second);
288        if (60 == cal_date->second)
289            cal_date->second = 59;
290        cal_date->minute    = gmt_calendar.minute;
291        cal_date->hour      = gmt_calendar.hour;
292        cal_date->day       = gmt_calendar.day;
293        cal_date->month     = gmt_calendar.month;
294        cal_date->year      = gmt_calendar.year;
295    }
296
297    if(CFEqual(command, CFSTR(kIOPMAutoWake))) {
298
299        // Set AutoWake calendar property
300        ret = _setRootDomainProperty(CFSTR(kIOPMSettingAutoWakeCalendarKey), date_data);
301    } else {
302
303        // Set AutoPower calendar property
304        ret = _setRootDomainProperty(CFSTR(kIOPMSettingAutoPowerCalendarKey), date_data);
305    }
306
307    if(kIOReturnSuccess != ret) {
308        goto exit;
309    }
310
311
312    // *************************** Seconds *************************************
313    // ApplePMU/AppleSMU seconds path
314    // Machine needs to be told alarm in seconds relative to current time.
315
316    if(!power_date) {
317        // NULL dictionary argument, clear wakeup timer
318        diff_secs = 0;
319    } else {
320        // Assume a well-formed entry since we've been doing thorough
321        // type-checking in the find & purge functions
322        now = CFAbsoluteTimeGetCurrent();
323        wake_time = CFDateGetAbsoluteTime(power_date);
324
325        diff_secs = lround(wake_time - now);
326        if(diff_secs < 0) goto exit;
327    }
328
329    // Package diff_secs as a CFNumber
330    seconds_delta = CFNumberCreate(0, kCFNumberLongType, &diff_secs);
331    if(!seconds_delta) goto exit;
332
333    if(CFEqual(command, CFSTR(kIOPMAutoWake))) {
334
335        // Set AutoWake seconds property
336        ret = _setRootDomainProperty(CFSTR(kIOPMSettingAutoWakeSecondsKey), seconds_delta);
337    } else {
338
339        // Set AutoPower seconds property
340        ret = _setRootDomainProperty(CFSTR(kIOPMSettingAutoPowerSecondsKey), seconds_delta);
341    }
342
343    if(kIOReturnSuccess != ret) {
344        goto exit;
345    }
346
347exit:
348    if(date_data) CFRelease(date_data);
349    if(seconds_delta) CFRelease(seconds_delta);
350    return;
351}
352
353
354IOReturn IOPMSchedulePowerEvent(
355    CFDateRef time_to_wake,
356    CFStringRef my_id,
357    CFStringRef type)
358{
359    CFDictionaryRef         package = 0;
360    IOReturn                ret = kIOReturnError;
361    CFAbsoluteTime          abs_time_to_wake;
362    CFDataRef               flatPackage = NULL;
363    kern_return_t           rc = KERN_SUCCESS;
364    mach_port_t       		pm_server = MACH_PORT_NULL;
365
366    //  verify inputs
367    if(!inputsValid(time_to_wake, my_id, type))
368    {
369        ret = kIOReturnBadArgument;
370        goto exit;
371    }
372
373    if( CFEqual(type, CFSTR(kIOPMMaintenanceScheduleImmediate)) )
374    {
375        ret = doAMaintenanceWake(time_to_wake, kPMCalendarTypeMaintenance);
376        goto exit;
377    } else if (CFEqual(type, CFSTR(kIOPMSleepServiceScheduleImmediate)) )
378    {
379        ret = doAMaintenanceWake(time_to_wake, kPMCalendarTypeSleepService);
380        goto exit;
381    } else if( CFEqual(type, CFSTR(kIOPMAutoWakeScheduleImmediate)) )
382    {
383
384        // Just send down the wake event immediately
385        tellClockController(CFSTR(kIOPMAutoWake), time_to_wake);
386        ret = kIOReturnSuccess;
387        goto exit;
388
389    } else if( CFEqual(type, CFSTR(kIOPMAutoPowerScheduleImmediate)) )
390    {
391
392        // Just send down the power on event immediately
393        tellClockController(CFSTR(kIOPMAutoPowerOn), time_to_wake);
394        ret = kIOReturnSuccess;
395        goto exit;
396
397    } else if( CFEqual( type, CFSTR( kIOPMAutoWakeRelativeSeconds) )
398            || CFEqual( type, CFSTR( kIOPMAutoPowerRelativeSeconds) ) )
399    {
400
401        // Immediately send down a relative seconds argument
402        // Seconds are relative to "right now" in CFAbsoluteTime
403        CFAbsoluteTime      now_secs;
404        CFAbsoluteTime      event_secs;
405        CFNumberRef         diff_secs = NULL;
406        int                 diff;
407
408        if(time_to_wake)
409        {
410            now_secs = CFAbsoluteTimeGetCurrent();
411            event_secs = CFDateGetAbsoluteTime(time_to_wake);
412            diff = (int)event_secs - (int)now_secs;
413            if(diff <= 0)
414            {
415                // Only positive diffs are meaningful
416                return kIOReturnIsoTooOld;
417            }
418        } else {
419            diff = 0;
420        }
421
422        diff_secs = CFNumberCreate(0, kCFNumberIntType, &diff);
423        if(!diff_secs) goto exit;
424
425        _setRootDomainProperty( type, (CFTypeRef)diff_secs );
426
427        CFRelease(diff_secs);
428
429        ret = kIOReturnSuccess;
430        goto exit;
431    }
432
433    abs_time_to_wake = CFDateGetAbsoluteTime(time_to_wake);
434    if(abs_time_to_wake < (CFAbsoluteTimeGetCurrent() + MIN_SCHEDULE_TIME))
435    {
436        ret = kIOReturnNotReady;
437        goto exit;
438    }
439
440    if(kIOReturnSuccess != _pm_connect(&pm_server)) {
441        ret = kIOReturnInternalError;
442        goto exit;
443    }
444
445    // Package the event in a CFDictionary
446    package = _IOPMCreatePowerOnDictionary(abs_time_to_wake, my_id, type);
447    flatPackage = CFPropertyListCreateData(0, package,
448                          kCFPropertyListBinaryFormat_v1_0, 0, NULL /* error */);
449    if ( !flatPackage ) {
450        ret = kIOReturnBadArgument;
451        goto exit;
452    }
453
454    rc = io_pm_schedule_power_event(pm_server, (vm_offset_t)CFDataGetBytePtr(flatPackage),
455                CFDataGetLength(flatPackage), 1, &ret);
456    if (rc != KERN_SUCCESS)
457        ret = kIOReturnInternalError;
458
459    notify_post(kIOPMSchedulePowerEventNotification);
460
461exit:
462
463    if (MACH_PORT_NULL != pm_server) {
464        _pm_disconnect(pm_server);
465    }
466    if(package) CFRelease(package);
467    if(flatPackage) CFRelease(flatPackage);
468    return ret;
469}
470
471
472IOReturn IOPMCancelScheduledPowerEvent(
473    CFDateRef time_to_wake,
474    CFStringRef my_id,
475    CFStringRef wake_or_restart)
476{
477    CFDictionaryRef         package = 0;
478    IOReturn                ret = kIOReturnSuccess;
479    mach_port_t             pm_server = MACH_PORT_NULL;
480    kern_return_t           err = KERN_SUCCESS;
481    CFAbsoluteTime          abs_time_to_wake;
482    CFDataRef               flatPackage = NULL;
483
484    if(!inputsValid(time_to_wake, my_id, wake_or_restart))
485    {
486        ret = kIOReturnBadArgument;
487        goto exit;
488    }
489
490    err = _pm_connect(&pm_server);
491    if(kIOReturnSuccess != err) {
492        ret = kIOReturnInternalError;
493        goto exit;
494    }
495
496    abs_time_to_wake = CFDateGetAbsoluteTime(time_to_wake);
497    package = _IOPMCreatePowerOnDictionary(abs_time_to_wake, my_id, wake_or_restart);
498    flatPackage = CFPropertyListCreateData(0, package,
499                          kCFPropertyListBinaryFormat_v1_0, 0, NULL /* error */);
500    if ( !flatPackage ) {
501        ret = kIOReturnBadArgument;
502        goto exit;
503    }
504
505    io_pm_schedule_power_event(pm_server,
506            (vm_offset_t)CFDataGetBytePtr(flatPackage), CFDataGetLength(flatPackage), 0, &ret);
507
508
509exit:
510
511    if (pm_server != MACH_PORT_NULL)
512        _pm_disconnect(pm_server);
513    if(package) CFRelease(package);
514    if(flatPackage) CFRelease(flatPackage);
515    return ret;
516}
517
518CFArrayRef IOPMCopyScheduledPowerEvents(void)
519{
520    CFMutableArrayRef           new_arr = NULL;
521
522    _copyPMServerObject(kIOPMPowerEventsMIGCopyScheduledEvents, 0, (CFTypeRef *)&new_arr);
523
524    return (CFArrayRef)new_arr;
525}
526