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