/* * Copyright (c) 2005-2006 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@ */ #include #include #include #include #include #include #include #include #include #include "PrivateLib.h" #include "PMSettings.h" #include "SetActive.h" #include "BatteryTimeRemaining.h" #include "powermanagementServer.h" #define kIOPMAppName "Power Management configd plugin" #define kIOPMPrefsPath "com.apple.PowerManagement.xml" #define kMaxTaskAssertions 64 #define kIOPMTaskPortKey CFSTR("task") #define kIOPMTaskPIDKey CFSTR("pid") #define kIOPMTaskAssertionsKey CFSTR("assertions") /* kIOPMAssertionTimerRefKey * For internal use only. * Key into Assertion dictionary. * Records the CFRunLoopTimerRef (if any) associated with a given assertion. */ #define kIOPMAssertionTimerRefKey CFSTR("AssertTimerRef") /* * ASL Log constants */ #define kPMASLFacility "com.apple.powermanagement" #define kPMASLPIDKey "Process" #define kPMASLAssertionNameKey "AssertionName" #define kPMASLNewCallerValueKey "NewValue" #define kPMASLPreviousSystemValueKey "PreviousSystemValue" #define kPMASLNewSystemValueKey "SystemValue" #define kPMASLActionKey "Action" #define kPMASLActionAssert "Assert" #define kPMASLActionRelease "Release" #define kPMASLActionClientDied "ClientDied" #define kPMASLActionTimedOut "TimedOut" enum { // These must be consecutive integers beginning at 0 kHighPerfIndex = 0, kPreventIdleIndex = 1, kDisableInflowIndex = 2, kInhibitChargeIndex = 3, kDisableWarningsIndex = 4, kPreventDisplaySleepIndex = 5, kEnableIdleIndex = 6, kExternalMediaIndex = 7, // Make sure this is the last enum element, as it tells us the total // number of elements in the enum definition kIOPMNumAssertionTypes }; // Selectors for AppleSmartBatteryManagerUserClient enum { kSBUCInflowDisable = 0, kSBUCChargeInhibit = 1 }; static const int kMaxCountTimedOut = 5; extern CFMachPortRef pmServerMachPort; #define DEBUG_LOG(x...) do { \ asl_log(NULL, NULL, ASL_LEVEL_ERR, x); \ } while(false); // forward static void evaluateAssertions(void); static void calculateAggregates(void); static void publishAssertionStatus(void); static void sendUserAssertionsToKernel(uint32_t user_assertions); static void sendSmartBatteryCommand(uint32_t which, uint32_t level); static int indexForAssertionName(CFStringRef assertionName); static void logAssertionEvent( const char *assertionActionStr, CFDictionaryRef taskDictionary, CFDictionaryRef assertionDictionary); static void timeoutExpirationCallBack(CFRunLoopTimerRef timer, void *info); static IOReturn copyAssertionForID( mach_port_t inPort, int inID, CFDictionaryRef *outTask, CFMutableArrayRef *outTaskAssertions, CFMutableDictionaryRef *outAssertion); static CFArrayRef copyPIDAssertionDictionaryFlattened(void); static CFDictionaryRef copyAggregateValuesDictionary(void); static CFArrayRef copyTimedOutAssertionsArray(void); // static void debugLogAssertion(CFDictionaryRef log_me); // globals static CFMutableDictionaryRef gAssertionsDict = NULL; static CFMutableArrayRef gTimedOutArray = NULL; static bool gNotifyTimeOuts = false; static CFRunLoopTimerRef gUpdateAssertionStatusTimer = NULL; static int aggregate_assertions[kIOPMNumAssertionTypes]; static int last_aggregate_assertions[kIOPMNumAssertionTypes]; static CFStringRef assertion_types_arr[kIOPMNumAssertionTypes]; static bool idle_enable_assumed = true; static void logAssertionEvent( const char *assertionActionCStr, CFDictionaryRef taskDictionary, CFDictionaryRef assertionDictionary) { const int kLongStringLen = 100; const int kShortStringLen = 10; aslmsg m; int aslLogLevel = ASL_LEVEL_INFO; CFNumberRef pidNum = NULL; int app_pid = -1; int index = 0; CFNumberRef levelNum = NULL; CFStringRef assertionTypeStr = NULL; uint32_t assertionLevel = -1; char assertionTypeCString[kLongStringLen]; char pid_buf[kShortStringLen]; char level_buf[kShortStringLen]; char prior_system_level_buf[kShortStringLen]; char new_system_level_buf[kShortStringLen]; m = asl_new(ASL_TYPE_MSG); /* Log Action */ asl_set(m, kPMASLActionKey, assertionActionCStr); /* Facility type */ asl_set(m, ASL_KEY_FACILITY, kPMASLFacility); /* Log PID */ pid_buf[0] = 0; if (taskDictionary) { // Try to read pid from Task's dictionary pidNum = CFDictionaryGetValue(taskDictionary, kIOPMTaskPIDKey); } if (!pidNum && assertionDictionary) { // Try to read pid from Assertion's dictionary pidNum = CFDictionaryGetValue(assertionDictionary, kIOPMAssertionPIDKey); } if (pidNum) { if (CFNumberGetValue(pidNum, kCFNumberIntType, &app_pid)) { if (0 < snprintf(pid_buf, kShortStringLen, "%d", app_pid)) { asl_set(m, kPMASLPIDKey, pid_buf); } } } /* Assertion details are specified for creation & release logs * Not for client death logs * If available, log them */ if (isA_CFDictionary(assertionDictionary)) { assertionTypeStr = CFDictionaryGetValue(assertionDictionary, kIOPMAssertionTypeKey); levelNum = CFDictionaryGetValue(assertionDictionary, kIOPMAssertionLevelKey); CFNumberGetValue(levelNum, kCFNumberIntType, &assertionLevel); } if (assertionTypeStr) { if (CFStringGetCString(assertionTypeStr, assertionTypeCString, kLongStringLen, kCFStringEncodingMacRoman)) { asl_set(m, kPMASLAssertionNameKey, assertionTypeCString); } index = indexForAssertionName(assertionTypeStr); level_buf[0] = 0; if (0 < snprintf(level_buf, kShortStringLen, "%d", assertionLevel)) { asl_set(m, kPMASLNewCallerValueKey, level_buf); } prior_system_level_buf[0] = 0; if (0 < snprintf(prior_system_level_buf, kShortStringLen, "%d", last_aggregate_assertions[index])) { asl_set(m, kPMASLPreviousSystemValueKey, prior_system_level_buf); } new_system_level_buf[0] = 0; if (0 < snprintf(new_system_level_buf, kShortStringLen, "%d", aggregate_assertions[index])) { asl_set(m, kPMASLNewSystemValueKey, new_system_level_buf); } } if (!strncmp(assertionActionCStr, kPMASLActionTimedOut, strlen(kPMASLActionTimedOut))) { // Set a high log level for timeouts aslLogLevel = ASL_LEVEL_ERR; } /* And log the message. * By default, INFO level messages won't get sent to the server, * and won't get written to the disk. */ asl_log(NULL, m, aslLogLevel, "PMAssertion(%s) %s %s", pid_buf, assertionActionCStr, assertionTypeStr ? assertionTypeCString:""); asl_free(m); } static int indexForAssertionName(CFStringRef assertionName) { if (CFEqual(assertionName, kIOPMAssertionTypeNeedsCPU)) return kHighPerfIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeNoIdleSleep)) return kPreventIdleIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeEnableIdleSleep)) return kEnableIdleIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeDisableInflow)) return kDisableInflowIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeInhibitCharging)) return kInhibitChargeIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeDisableLowBatteryWarnings)) return kDisableWarningsIndex; else if (CFEqual(assertionName, kIOPMAssertionTypeNoDisplaySleep)) return kPreventDisplaySleepIndex; else return 0; } static void calculateAggregates(void) { CFDictionaryRef *process_assertions = NULL; int process_count = 0; int i, j; // Clear out the aggregate assertion values. We are about to re-calculate // these values in the big nasty loop below. bzero( aggregate_assertions, sizeof(aggregate_assertions) ); // Initialize kEnableIdleIndex to idle_enable_assumed aggregate_assertions[kEnableIdleIndex] = idle_enable_assumed; if (!gAssertionsDict) { goto exit; } process_count = CFDictionaryGetCount(gAssertionsDict); if (0 == process_count) { goto exit; } process_assertions = malloc(sizeof(CFDictionaryRef) * process_count); CFDictionaryGetKeysAndValues(gAssertionsDict, NULL, (const void **)process_assertions); for (i=0; i= kMaxTaskAssertions) { // ERROR! out of space in array! result = kIOReturnNoMemory; goto exit; } /* * Populate our new assertion dictionary */ CFMutableDictionaryRef new_assertion_dict = NULL; CFNumberRef cf_assertion_val = NULL; CFDateRef start_date = NULL; *assertion_id = ID_FROM_INDEX(arrayIndex); new_assertion_dict = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); /* Type */ assertionString = CFStringCreateWithCString(0, assertionCStr, kCFStringEncodingMacRoman); if (assertionString) { CFDictionarySetValue(new_assertion_dict, kIOPMAssertionTypeKey, assertionString); CFRelease(assertionString); } /* Level */ cf_assertion_val = CFNumberCreate(0, kCFNumberIntType, &level); if (cf_assertion_val) { CFDictionarySetValue(new_assertion_dict, kIOPMAssertionLevelKey, cf_assertion_val); CFRelease(cf_assertion_val); } /* Name */ nameString = CFStringCreateWithCString(0, nameCStr, kCFStringEncodingMacRoman); if (nameString) { CFDictionarySetValue(new_assertion_dict, kIOPMAssertionNameKey, nameString); CFRelease(nameString); } /* Create Time */ start_date = CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent()); if (start_date) { CFDictionarySetValue(new_assertion_dict, kIOPMAssertionCreateDateKey, start_date); CFRelease(start_date); } /* Owner's PID */ if (task_pid_num) { CFDictionarySetValue(new_assertion_dict, kIOPMAssertionPIDKey, task_pid_num); CFRelease(task_pid_num); } CFArraySetValueAtIndex(assertions, arrayIndex, new_assertion_dict); evaluateAssertions(); // ASL Log assertion logAssertionEvent(kPMASLActionAssert, this_task, new_assertion_dict); CFRelease(new_assertion_dict); result = kIOReturnSuccess; exit: return result; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Minimum timeout: 1 seconds */ static const int kIOMinimumTimeoutInterval = 1; kern_return_t _io_pm_assertion_settimeout ( mach_port_t server __unused, mach_port_t task, int assertion_id, int interval, int *return_code ) { CFMutableDictionaryRef timeoutAssertion = NULL; CFNumberRef intervalNum = NULL; CFRunLoopTimerRef timeOutTimer = NULL; const mach_port_t mach_task_me = mach_task_self(); if (interval < kIOMinimumTimeoutInterval) { return kIOReturnBadArgument; } if (mach_task_me != task) { __MACH_PORT_DEBUG(true, "set_timeout: expect send right", task); // Expect to have 2 send right on tasks if this assertion exists. // If this was called (invalidly) by a process without any assertions, // we expect 1 send right on task. } *return_code = copyAssertionForID(task, assertion_id, NULL, /* task's dictionary */ NULL, /* task's array */ &timeoutAssertion); if (kIOReturnSuccess != *return_code) { goto exit; } if (!timeoutAssertion) { *return_code = kIOReturnNotFound; goto exit; } // Set time interval property kIOPMAssertionTimeOutIntervalKey intervalNum = CFNumberCreate(0, kCFNumberIntType, &interval); if (intervalNum) { CFDictionarySetValue(timeoutAssertion, kIOPMAssertionTimeOutIntervalKey, intervalNum); CFRelease(intervalNum); } // And create CFRunLoopTimerRef property CFDateRef assertionCreatedDate = CFDictionaryGetValue(timeoutAssertion, kIOPMAssertionCreateDateKey); if (assertionCreatedDate) { CFRunLoopTimerContext timerContext = { 0, (void *)timeoutAssertion, NULL, NULL, NULL }; CFAbsoluteTime fireDate = CFDateGetAbsoluteTime(assertionCreatedDate); fireDate += (CFTimeInterval)interval; timeOutTimer = CFRunLoopTimerCreate(0, fireDate, 0.0, 0, 0, timeoutExpirationCallBack, &timerContext); if (timeOutTimer) { CFRunLoopAddTimer(CFRunLoopGetCurrent(), timeOutTimer, kCFRunLoopDefaultMode); CFDictionarySetValue(timeoutAssertion, kIOPMAssertionTimerRefKey, timeOutTimer); CFRelease(timeOutTimer); } } // Do not call evaluateAssertions() - nothing to evaluate here. // If our timer instantiated correctly, we are now waiting for a timer to fire *return_code = kIOReturnSuccess; exit: if (mach_task_me != task) { // Remove the send right on task that we received with this invocation. __MACH_PORT_DEBUG(true, "set_timeout: deallocate extra send right", task); mach_port_deallocate(mach_task_self(), task); } if (timeoutAssertion) { CFRelease(timeoutAssertion); } return KERN_SUCCESS; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ kern_return_t _io_pm_assertion_release ( mach_port_t server __unused, mach_port_t task, int assertion_id, int *return_code ) { CFDictionaryRef callerTask = NULL; CFMutableArrayRef taskAssertions = NULL; CFMutableDictionaryRef releaseAssertion = NULL; CFRunLoopTimerRef timeOutTimer = NULL; int i; int n; bool releaseTask; const mach_port_t mach_task_me = mach_task_self(); // Expect to have 2 send right on task if this assertion exists. // If this was called (invalidly) by a process without any assertions, // we expect 1 send right on task. if (mach_task_me != task) { __MACH_PORT_DEBUG(true, "assertion_release", task); } *return_code = copyAssertionForID(task, assertion_id, &callerTask, &taskAssertions, &releaseAssertion); if (kIOReturnSuccess != *return_code) { goto exit; } // Cancel timeout at kIOPMAssertionTimerRefKey timeOutTimer = (CFRunLoopTimerRef)CFDictionaryGetValue( releaseAssertion, kIOPMAssertionTimerRefKey); if (timeOutTimer) { CFRunLoopTimerInvalidate(timeOutTimer); } // Add a "Released" timestamp if this assertion has already timed out // If this assertion has timed-out, debugging humans will want to know if // and when it was released. if (CFDictionaryGetValue(releaseAssertion, kIOPMAssertionTimedOutDateKey)) { CFDateRef dateNow = CFDateCreate(0, CFAbsoluteTimeGetCurrent()); CFDictionarySetValue(releaseAssertion, kIOPMAssertionReleaseDateKey, dateNow); CFRelease(dateNow); } // Figure out the index into the returned taskAssertions array CFIndex arrayIndex = INDEX_FROM_ID(assertion_id); // Release it from its task array // * Note that if this assertion has timed-out, it will continue to exist for // record-keeping purposes on the gTimedOutAssertions arrray. CFArraySetValueAtIndex(taskAssertions, arrayIndex, kCFBooleanFalse); // Check whether this is the last assertion in the task's array. // If no more assertions tied to the task, clean up the task's bookkeeping. releaseTask = TRUE; n = CFArrayGetCount(taskAssertions); for (i =0; i < n; i++) { CFTypeRef assertion; assertion = CFArrayGetValueAtIndex(taskAssertions, i); if (!CFEqual(assertion, kCFBooleanFalse)) { releaseTask = FALSE; break; } } if (releaseTask) { CFDictionaryRemoveValue(gAssertionsDict, MY_CAST_INT_POINTER(task)); if (mach_task_me != task) { // If we're dropping the task, then we need to drop our send right // that we received in _IOPMAssertionCreateRequiresRoot __MACH_PORT_DEBUG(true, "assertion_release: deallocating task's #1 send right", task); mach_port_deallocate(mach_task_self(), task); } } // Re-evaluate evaluateAssertions(); // ASL Log assertion release logAssertionEvent(kPMASLActionRelease, callerTask, releaseAssertion); if (callerTask) { CFRelease(callerTask); } if (taskAssertions) { CFRelease(taskAssertions); } if (releaseAssertion) { CFRelease(releaseAssertion); } *return_code = kIOReturnSuccess; exit: if (mach_task_me != task) { __MACH_PORT_DEBUG(true, "assertion_release: EXIT deallocating send right", task); mach_port_deallocate(mach_task_self(), task); } return KERN_SUCCESS; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Creates a dictionary mapping process id's to the assertions that they own. * * The corresponding IOKit API IOPMCopyAssertionsByProcess returns a dictionary * whose keys are process ID's. * This is perfectly acceptable in CoreFoundation, EXCEPT that you cannot * serialize a dictionary with CFNumbers for keys using CF or IOKit * serialization. * * To serialize this dictionary and pass it from configd to the caller's process, * we re-formatted it as a "flattened" array of dictionaries in configd, * and we will re-constitute with pid's for keys here. * * Next time around, I will simply not use CFNumberRefs for keys in API. */ static CFArrayRef copyPIDAssertionDictionaryFlattened(void) { CFDictionaryRef *taskDataArray = NULL; CFMutableArrayRef returnArray = 0; int dict_count; int task_index; if(!gAssertionsDict) goto exit; dict_count = CFDictionaryGetCount(gAssertionsDict); if(0 == dict_count) goto exit; returnArray = CFArrayCreateMutable(0, 0, &kCFTypeArrayCallBacks); taskDataArray = (CFDictionaryRef *)malloc(sizeof(CFDictionaryRef)*dict_count); if (!taskDataArray) goto exit; CFDictionaryGetKeysAndValues(gAssertionsDict, (const void **)NULL, (const void **)taskDataArray); // Iterate our master list of processes and map them to pids & their assertions for (task_index=0; task_index= CFArrayGetCount(localTaskAssertions))) { ret = kIOReturnNotFound; goto exit; } localAssertion = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(localTaskAssertions, arrayIndex); if (!localAssertion || !isA_CFDictionary(localAssertion)) { ret = kIOReturnNotFound; goto exit; } if (outTask) { CFRetain(localTask); *outTask = localTask; } if (outTaskAssertions) { CFRetain(localTaskAssertions); *outTaskAssertions = localTaskAssertions; } if (outAssertion) { CFRetain(localAssertion); *outAssertion = localAssertion; } ret = kIOReturnSuccess; exit: return ret; } /* static void debugLogAssertion(CFDictionaryRef log_me) { //#if 1 CFStringRef *keys; CFTypeRef *values; int num_properties = 0; int i; if (!log_me) { asl_log(NULL,NULL,ASL_LEVEL_ERR, "No assertion to log!\n"); } num_properties = CFDictionaryGetCount(log_me); keys = (CFStringRef *)malloc(num_properties * sizeof(void *)); values = (CFTypeRef *)malloc(num_properties * sizeof(void *)); if(!keys || !values) return; CFDictionaryGetKeysAndValues(log_me, (const void **)keys, (const void **)values); asl_log(NULL, NULL, ASL_LEVEL_ERR, " * Logging assertion %p\n", (void *)log_me); for (i=0; i