/* * Copyright (c) 2008 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_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. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * 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_OSREFERENCE_LICENSE_HEADER_END@ */ #include "powermanagementServer.h" // mig generated #include #include #include #include #include #include #include #include "PrivateLib.h" #include "PMConnection.h" #include "AutoWakeScheduler.h" #include "RepeatingAutoWake.h" /************************************************************************************/ /* Bookkeeping data types */ enum { kSleepWakeInterestBit = 1, kMaintenanceInterestBit = 2 }; enum { _kSleepStateBits = 0x0000, _kOnStateBits = 0xFFFF }; static int const kMaxConnectionIDCount = 1000*1000*1000; static int const kConnectionOffset = 1000; static double const kPMConnectionNotifyTimeoutDefault = 25.0; extern CFMachPortRef pmServerMachPort; /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /* Bookkeeping structs */ /* PMResponseWrangler * While we have an outstanding notification, we have one of these guys sitting around * waiting to handle the incoming responses. * We assert that only one PMResponseWrangler shall exist at a time - e.g. no more * than one system state transition shall occur simultaneously. */ typedef struct { CFMutableArrayRef awaitingResponses; CFRunLoopTimerRef awaitingResponsesTimeout; CFAbsoluteTime allRepliedTime; long kernelAcknowledgementID; int notificationType; int awaitingResponsesCount; int awaitResponsesTimeoutSeconds; int completedStatus; // status after timed out or, all acked bool needsMaintenanceWake; bool completed; bool nextIsValid; long nextKernelAcknowledgementID; int nextInterestBits; } PMResponseWrangler; /* PMConnection - one tracker corresponds to one PMConnection * in an application. * * responseHandler - Should be NULL unless this connection has outstanding * notifications to reply to. */ typedef struct { mach_port_t notifyPort; PMResponseWrangler *responseHandler; CFStringRef callerName; uint32_t uniqueID; int callerPID; IOPMSystemPowerStateCapabilities interestsBits; } PMConnection; /* PMResponse * represents one outstanding notification acknowledgement */ typedef struct { PMConnection *connection; PMResponseWrangler *myResponseWrangler; IOPMConnectionMessageToken token; CFAbsoluteTime repliedWhen; CFAbsoluteTime notifiedWhen; CFAbsoluteTime maintenanceRequested; int notificationType; bool replied; bool timedout; } PMResponse; /*****************************************************************************/ // Private constants shared between us and xnu IOPMrootDomain #define kIOPMPrivilegedPowerInterest "IOPMPrivilegedPowerInterest" #define kIOPMRootDomainRunStateKey "Run State" /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /* Internal methods */ static IOReturn createConnectionWithID( PMConnection **); static PMConnection *connectionForID( uint32_t findMe); static CFArrayRef createArrayOfConnectionsWithInterest( int interestBitsNotify); static PMResponseWrangler *connectionFireNotification( int notificationType, long kernelAcknowledgementID); static void _sendMachMessage( mach_port_t port, mach_msg_id_t msg_id, uint32_t payload_bits, uint32_t payload_messagetoken); static void checkResponses(PMResponseWrangler *wrangler); static void responsesTimedOut(CFRunLoopTimerRef timer, void * info); static void cleanupConnection(PMConnection *reap); static void cleanupResponseWrangler(PMResponseWrangler *reap); static void setSystemSleepStateTracking(IOPMSystemPowerStateCapabilities); static void PMConnectionPowerCallBack( void *port, io_service_t rootdomainservice, natural_t messageType, void *acknowledgementToken); __private_extern__ void ClockSleepWakeNotification( natural_t messageType); /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /* CFArrayRef support structures */ static Boolean _CFArrayConnectionEquality(const void *value1, const void *value2) { const PMConnection *v1 = (const PMConnection *)value1; const PMConnection *v2 = (const PMConnection *)value2; return (v1->uniqueID == v2->uniqueID); } static CFArrayCallBacks _CFArrayConnectionCallBacks = { 0, NULL, NULL, NULL, _CFArrayConnectionEquality }; static CFArrayCallBacks _CFArrayVanillaCallBacks = { 0, NULL, NULL, NULL, NULL }; /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /* Globals */ static CFMutableArrayRef gConnections = NULL; static uint32_t globalConnectionIDTally = 0; static io_connect_t gRootDomainConnect = IO_OBJECT_NULL; static PMResponseWrangler * gLastResponseWrangler = NULL; /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /* * PMConnection_prime */ __private_extern__ void PMConnection_prime() { io_service_t rootDomainService = IO_OBJECT_NULL; io_object_t sleepWakeCallbackHandle = IO_OBJECT_NULL; IONotificationPortRef notify = NULL; kern_return_t kr = 0; char *errorString = NULL; gConnections = CFArrayCreateMutable(kCFAllocatorDefault, 100, &_CFArrayConnectionCallBacks); // Find it rootDomainService = getRootDomain(); if (IO_OBJECT_NULL == rootDomainService) { errorString = "Could not find IOPMrootDomain"; goto error; } // Open it kr = IOServiceOpen(rootDomainService, mach_task_self(), 0, &gRootDomainConnect); if (KERN_SUCCESS != kr) { errorString = "Could not open IOPMrootDomain"; goto error; } notify = IONotificationPortCreate(MACH_PORT_NULL); if (!notify) { errorString = "Could not create IONotificationPort"; goto error; } // Register for sleep wake notifications kr = IOServiceAddInterestNotification( notify, rootDomainService, kIOPMPrivilegedPowerInterest, (IOServiceInterestCallback) PMConnectionPowerCallBack, NULL, &sleepWakeCallbackHandle); if (KERN_SUCCESS != kr) { errorString = "Could not add interest notification kIOPMPrivilegedPowerInterest"; goto error; } CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopDefaultMode); return; error: // ASL_LOG: KEEP asl_log(NULL, NULL, ASL_LEVEL_ERR, "PowerManagement: unable to register with kernel power management. %s %s", errorString ? "Reason = : ":"", errorString ? errorString:""); return; } /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /*****************************************************************************/ /* M I G */ /* M I G */ /* M I G */ /* M I G */ /* M I G */ /* * Create a PM Connection * - assign a globally unique ID * - find the remote mach port * - get an invalidation notification on the remote callback * */ kern_return_t _io_pm_connection_create ( mach_port_t server, mach_port_t task_in, string_t name, int interests, uint32_t *connection_id, int *return_code ) { PMConnection *newConnection = NULL; int task_pid; // Allocate: create a new PMConnection type createConnectionWithID(&newConnection); if (!newConnection) { *return_code = kIOReturnError; goto exit; } if (KERN_SUCCESS == pid_for_task(task_in, &task_pid)) { newConnection->callerPID = task_pid; } // Save caller name for logging if (name && strlen(name)) { newConnection->callerName = CFStringCreateWithCString(0, name, kCFStringEncodingUTF8); } // TODO: for now, all clients are listening for all notifications newConnection->interestsBits = 0xFF; *connection_id = newConnection->uniqueID; *return_code = kIOReturnSuccess; exit: if (MACH_PORT_NULL != task_in) { __MACH_PORT_DEBUG(true, "_io_pm_connection_create dropping send right", task_in); mach_port_deallocate(mach_task_self(), task_in); } return KERN_SUCCESS; } /*****************************************************************************/ /*****************************************************************************/ kern_return_t _io_pm_connection_schedule_notification( mach_port_t server, uint32_t connection_id, mach_port_t notify_port_in, int disable, int *return_code) { PMConnection *connection = NULL; mach_port_t oldNotify; if (MACH_PORT_NULL == notify_port_in || NULL == return_code) { if (return_code) *return_code = kIOReturnBadArgument; goto exit; } __MACH_PORT_DEBUG(true, "_io_pm_connection_schedule_notification notify_port", notify_port_in); *return_code = kIOReturnError; connection = connectionForID(connection_id); if (!connection) { return kIOReturnNotFound; } connection->notifyPort = notify_port_in; mach_port_request_notification( mach_task_self(), // task notify_port_in, // port that will die MACH_NOTIFY_DEAD_NAME, // msgid 1, // make-send count CFMachPortGetPort(pmServerMachPort), // notify port MACH_MSG_TYPE_MAKE_SEND_ONCE, // notifyPoly &oldNotify); // previous __MACH_PORT_DEBUG(true, "Registered dead name notification on notifyPort", notify_port_in); *return_code = kIOReturnSuccess; exit: return KERN_SUCCESS; } /*****************************************************************************/ /*****************************************************************************/ kern_return_t _io_pm_connection_release ( mach_port_t server, uint32_t connection_id, int *return_code ) { PMConnection *cleanMeUp = NULL; cleanMeUp = connectionForID(connection_id); if (cleanMeUp) { cleanupConnection(cleanMeUp); if (return_code) *return_code = kIOReturnSuccess; } else { if (return_code) *return_code = kIOReturnNotFound; } return KERN_SUCCESS; } /*****************************************************************************/ /*****************************************************************************/ kern_return_t _io_pm_connection_acknowledge_event ( mach_port_t server, uint32_t connection_id, int messageToken, vm_offset_t options_ptr, mach_msg_type_number_t options_len, int *return_code ) { #if TARGET_OS_EMBEDDED return KERN_FAILURE; #else PMConnection *connection = connectionForID(connection_id); PMResponse *checkResponse = NULL; PMResponse *foundResponse = NULL; CFMutableArrayRef responsesTrackingList = NULL; int responsesCount = 0; int i; int timeIntervalMS; // Check options dictionary CFDictionaryRef ackOptionsDict = NULL; CFDateRef maintenanceDate = NULL; CFNumberRef maintenanceRequirementBits = NULL; int maintRequireBitfield = 0; CFDataRef optionsAsData = NULL; CFPropertyListRef optionsUnzipped = NULL; if (!connection) { *return_code = kIOReturnNotFound; goto exit; } if (!connection->responseHandler) { *return_code = kIOReturnNotFound; goto exit; } responsesTrackingList = connection->responseHandler->awaitingResponses; if (!responsesTrackingList) { *return_code = kIOReturnNotFound; goto exit; } responsesCount = CFArrayGetCount(responsesTrackingList); for (i=0; itoken)) { foundResponse = checkResponse; break; } } if (!foundResponse) { *return_code = kIOReturnNotFound; goto exit; } // Record some timing & tracking data into our found response struct; // then pass control over to the event-specific code. foundResponse->repliedWhen = CFAbsoluteTimeGetCurrent(); foundResponse->replied = true; // Log if response time exceeds kAppResponseLogThresholdMS. timeIntervalMS = (int)((foundResponse->repliedWhen - foundResponse->notifiedWhen) * 1000); if (timeIntervalMS > kAppResponseLogThresholdMS) { CFNumberRef timeIntervalNumber = CFNumberCreate(NULL, kCFNumberIntType, &timeIntervalMS); logASLMessageApplicationResponse( kAppResponseLogSourcePMConnection, connection->callerName, CFSTR(kIOPMStatsResponseSlow), timeIntervalNumber); if (timeIntervalNumber) CFRelease(timeIntervalNumber); } // Unpack the passed-in options data structure if ( options_ptr != 0 && options_len != 0) { optionsAsData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t *)options_ptr, options_len, kCFAllocatorNull); if (optionsAsData) { /* CFPropertyListCreateWithData not available yet on iPhone */ optionsUnzipped = CFPropertyListCreateWithData(0, optionsAsData, kCFPropertyListImmutable, NULL, NULL); ackOptionsDict = (CFDictionaryRef) isA_CFDictionary(optionsUnzipped); } if (ackOptionsDict) { maintenanceDate = isA_CFDate( CFDictionaryGetValue(ackOptionsDict, kIOPMAcknowledgmentOptionWakeDate)); maintenanceRequirementBits = isA_CFNumber( CFDictionaryGetValue(ackOptionsDict, kIOPMAcknowledgmentOptionSystemCapabilityRequirements)); if (maintenanceDate && maintenanceRequirementBits) { connection->responseHandler->needsMaintenanceWake = true; foundResponse->maintenanceRequested = CFDateGetAbsoluteTime(maintenanceDate); // TODO: record maintenanceCapabilityRequested here too // At the moment we are only impleenting scheduled maintenance wakes via this // interface. CFNumberGetValue(maintenanceRequirementBits, kCFNumberSInt32Type, &maintRequireBitfield); /* // ASL_LOG: KEEP asl_log(NULL, NULL, ASL_LEVEL_ERR, "Acknowledgement client requested maintenance wake in %d seconds with bits 0x%02x\n", (int)(CFDateGetAbsoluteTime(maintenanceDate) - CFAbsoluteTimeGetCurrent()), maintRequireBitfield); */ } } } checkResponses(connection->responseHandler); *return_code = kIOReturnSuccess; exit: if (optionsAsData) CFRelease(optionsAsData); if (optionsUnzipped) CFRelease(optionsUnzipped); vm_deallocate(mach_task_self(), options_ptr, options_len); return KERN_SUCCESS; #endif } /*****************************************************************************/ /*****************************************************************************/ kern_return_t _io_pm_connection_copy_status ( mach_port_t server, int status_index, vm_offset_t *status_data, mach_msg_type_number_t *status_dataCnt, int *return_val ) { return KERN_SUCCESS; } /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ static void cleanupConnection(PMConnection *reap) { PMResponseWrangler *responseWrangler = NULL; PMResponse *openResponse = NULL; int allResponsesCount = 0; int i; int index; CFRange connectionsRange = CFRangeMake(0, CFArrayGetCount(gConnections)); if (MACH_PORT_NULL != reap->notifyPort) { __MACH_PORT_DEBUG(true, "IOPMConnection cleanupConnection drop notifyPort", reap->notifyPort); mach_port_deallocate(mach_task_self(), reap->notifyPort); reap->notifyPort = MACH_PORT_NULL; } if (reap->callerName) { CFRelease(reap->callerName); reap->callerName = NULL; } responseWrangler = reap->responseHandler; if (responseWrangler && responseWrangler->awaitingResponses) { allResponsesCount = CFArrayGetCount(responseWrangler->awaitingResponses); for (i=0; iawaitingResponses, i); if (openResponse) { openResponse->connection = NULL; openResponse->replied = true; openResponse->timedout = true; } } // Let the response wrangler finish handling the outstanding event // now that we've zeroed out any of our pending responses for this dead client. checkResponses(responseWrangler); } // Remove our struct from gConnections index = CFArrayGetFirstIndexOfValue(gConnections, connectionsRange, (void *)reap); if (kCFNotFound != index) { CFArrayRemoveValueAtIndex(gConnections, index); } /* // ASL_LOG: KEEP asl_log(NULL, NULL, ASL_LEVEL_ERR, "PowerManagement: cleaned up connection for pid=%d.\n", reap ? reap->callerPID : 666); */ free(reap); return; } /*****************************************************************************/ /*****************************************************************************/ static void cleanupResponseWrangler(PMResponseWrangler *reap) { PMConnection *one_connection; long nextAcknowledgementID; int responseCount; int connectionsCount; int i; int nextInterestBits; bool nextIsValid; if (!reap) return; if (!gConnections) return; // Cache the next response fields. nextInterestBits = reap->nextInterestBits; nextAcknowledgementID = reap->nextKernelAcknowledgementID; nextIsValid = reap->nextIsValid; // Loop through all connections. If any connections are referring to // responseWrangler in their tracking structs, zero that out. connectionsCount = CFArrayGetCount(gConnections); for (i=0; iresponseHandler) { // Zero out this reference before it points to a free'd pointer one_connection->responseHandler = NULL; } } // Loop responses, destroy responses if (reap->awaitingResponses) { responseCount = CFArrayGetCount(reap->awaitingResponses); if (responseCount > 0) { for (i=0; iawaitingResponses, i)); } } CFRelease(reap->awaitingResponses); reap->awaitingResponses = NULL; } // Invalidate the pointer to the in-flight response wrangler. if (gLastResponseWrangler == reap) { gLastResponseWrangler = NULL; } free(reap); // Create a new response wrangler if the reaped wrangler has a queued // notification stored. If new wrangler is NULL, make sure to ack any // sleep message. if (nextIsValid) { PMResponseWrangler * resp; resp = connectionFireNotification(nextInterestBits, nextAcknowledgementID); if (!resp && (nextInterestBits == _kSleepStateBits)) { _pm_scheduledevent_choose_best_wake_event( kChooseMaintenance, 0); IOAllowPowerChange(gRootDomainConnect, nextAcknowledgementID); } } } /*****************************************************************************/ /*****************************************************************************/ __private_extern__ bool PMConnectionHandleDeadName(mach_port_t deadPort) { PMConnection *one_connection = NULL;; PMConnection *the_connection = NULL; int connectionsCount = 0; int i; if (!gConnections) return false; connectionsCount = CFArrayGetCount(gConnections); // Find the PMConnection that owns this mach port for (i=0; inotifyPort)) { the_connection = one_connection; break; } } if (the_connection) { cleanupConnection(the_connection); return true; } else { return false; } } static int getRunState(void) { io_registry_entry_t rootdomainservice = IO_OBJECT_NULL; CFNumberRef runState = NULL; CFTypeRef runState0 = NULL; int rstate = 0; rootdomainservice = getRootDomain(); runState0 = IORegistryEntryCreateCFProperty( rootdomainservice, CFSTR(kIOPMRootDomainRunStateKey), kCFAllocatorDefault, kNilOptions); runState = (CFNumberRef)isA_CFNumber(runState0); if (runState) { CFNumberGetValue(runState, kCFNumberSInt32Type, &rstate); CFRelease(runState); } else if (runState0) { // on the remote chance that runState0 is a valid CF type, but not // a number, we release it here to avoid a leak. CFRelease(runState0); } return rstate; } /*****************************************************************************/ /*****************************************************************************/ static void setSystemSleepStateTracking(IOPMSystemPowerStateCapabilities capables) { SCDynamicStoreRef store = _getSharedPMDynamicStore(); CFStringRef key = NULL; CFNumberRef capablesNum = NULL; if (!store) return; key = SCDynamicStoreKeyCreate(0, CFSTR("%@%@"), kSCDynamicStoreDomainState, CFSTR(kIOPMSystemPowerCapabilitiesKeySuffix)); if (!key) return; capablesNum = CFNumberCreate(0, kCFNumberIntType, &capables); if (capablesNum) { SCDynamicStoreSetValue(store, key, capablesNum); CFRelease(capablesNum); } CFRelease(key); } __private_extern__ void PMConnectionPowerCallBack( void *port, io_service_t rootdomainservice, natural_t messageType, void *acknowledgementToken) { PMResponseWrangler *responseController = NULL; IOPMSystemPowerStateCapabilities deliverCapabilityBits = 0; int rstate = getRunState(); // We run all of our scheduled event code on every maintenance wake // and on every maintenance sleep, in case there's a scheduled // event we have to honor coming up. if (kIOMessageSystemWillSleep == messageType || kIOMessageSystemHasPoweredOn == messageType) { // Reset the arbiter between full wake events and // maintenance wake events. _pm_scheduledevent_choose_best_wake_event(kChooseReset, 0); } AutoWakeSleepWakeNotification(messageType, rstate); RepeatingAutoWakeSleepWakeNotification(messageType, rstate); ClockSleepWakeNotification(messageType); switch (messageType) { case kIOMessageSystemWillSleep: // Send an async notify out - clients include SCNetworkReachability API's; no ack expected setSystemSleepStateTracking(0); notify_post(kIOPMSystemPowerStateNotify); responseController = connectionFireNotification(_kSleepStateBits, (long)acknowledgementToken); if (!responseController) { // We have zero clients. Acknowledge immediately. _pm_scheduledevent_choose_best_wake_event( kChooseMaintenance, 0); IOAllowPowerChange(gRootDomainConnect, (long)acknowledgementToken); } /* We must acknowledge this sleep event within 30 second timeout, * once clients ack via IOPMConnectionAcknowledgeEvent(). * Our processing will pick-up again in our handler * _io_pm_connection_acknowledge_event */ break; case kIOMessageCanSystemSleep: /* IOPMConnection clients don't get any notification of CanSystemSleep * They should all be using IOPMAssertions to prevent idle sleep! */ IOAllowPowerChange(gRootDomainConnect, (long)acknowledgementToken); break; case kIOMessageSystemWillPowerOn: switch (rstate) { case kRStateNormal: deliverCapabilityBits = kIOPMSystemPowerStateCapabilityCPU | kIOPMSystemPowerStateCapabilityDisk | kIOPMSystemPowerStateCapabilityNetwork | kIOPMSystemPowerStateCapabilityAudio | kIOPMSystemPowerStateCapabilityVideo; break; case kRStateDark: case kRStateMaintenance: deliverCapabilityBits = kIOPMSystemPowerStateCapabilityCPU | kIOPMSystemPowerStateCapabilityDisk | kIOPMSystemPowerStateCapabilityNetwork; break; default: deliverCapabilityBits = 0; break; } // Send an async notify out - clients include SCNetworkReachability API's; no ack expected setSystemSleepStateTracking(deliverCapabilityBits); notify_post(kIOPMSystemPowerStateNotify); responseController = connectionFireNotification(deliverCapabilityBits, 0); /* We don't need to acknowledge this wake event. But we make our clients * acknowledge us. * Our processing will pick-up again in our handler * _io_pm_connection_acknowledge_event */ break; case kIOMessageSystemHasPoweredOn: switch (rstate) { case kRStateNormal: logASLMessageSleep(kMsgTracerSigSuccess, NULL, CFAbsoluteTimeGetCurrent(), NULL); logASLMessageWake(kMsgTracerSigSuccess, NULL, CFAbsoluteTimeGetCurrent(), NULL); logASLMessageHibernateStatistics(); logASLMessageKernelApplicationResponses(); break; case kRStateDark: case kRStateMaintenance: logASLMessageMaintenanceWake(); break; } break; } } /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ /************************************************************************************/ static PMResponseWrangler *connectionFireNotification( int interestBitsNotify, long kernelAcknowledgementID) { static int lastInterestBits = 0xFFFFFFFF; int affectedBits = 0; CFArrayRef interested = NULL; PMConnection *connection = NULL; int interestedCount = 0; uint32_t messageToken = 0; uint16_t calloutCount = 0; PMResponseWrangler *responseWrangler = NULL; PMResponse *awaitThis = NULL; /* * If a response wrangler is active, store the new notification on the * active wrangler. Then wait for its completion before firing the new * notification. Only a single notification can be stored. */ if (gLastResponseWrangler && !gLastResponseWrangler->nextIsValid) { gLastResponseWrangler->nextIsValid = true; gLastResponseWrangler->nextInterestBits = interestBitsNotify; gLastResponseWrangler->nextKernelAcknowledgementID = kernelAcknowledgementID; return gLastResponseWrangler; } // We only send state change notifications out to entities interested in the changing // bits, or interested in a subset of the changing bits. // Any client who is interested in a superset of the changing bits shall not receive // a notification. // affectedBits & InterestedBits != 0 // and affectedBits & InterestedBits == InterestedBits affectedBits = interestBitsNotify ^ lastInterestBits; lastInterestBits = interestBitsNotify; interested = createArrayOfConnectionsWithInterest(affectedBits); if (!interested) { goto exit; } interestedCount = CFArrayGetCount(interested); if (0 == interestedCount) { goto exit; } /* Allocate the response wrangler * * This object will be allocated & valid for the duration of * sending out notifications & awaiting responses. */ responseWrangler = calloc(1, sizeof(PMResponseWrangler)); if (!responseWrangler) { goto exit; } responseWrangler->notificationType = interestBitsNotify; responseWrangler->awaitResponsesTimeoutSeconds = (int)kPMConnectionNotifyTimeoutDefault; responseWrangler->kernelAcknowledgementID = kernelAcknowledgementID; /* * We will track each notification we're sending out with an individual response. * Record that response in the "active response array" so we can group them * all later when they acknowledge, or fail to acknowledge. */ responseWrangler->awaitingResponses = CFArrayCreateMutable(kCFAllocatorDefault, interestedCount, &_CFArrayVanillaCallBacks); responseWrangler->awaitingResponsesCount = interestedCount; for (calloutCount=0; calloutCountnotifyPort) { continue; } /* We generate a messagetoken here, which the notifiee must pass * back into us when the client acknowledges. * We note the token in the PMResponse struct. */ messageToken = (interestBitsNotify << 16) | calloutCount; // Mark this connection with the responseWrangler that's awaiting its responses connection->responseHandler = responseWrangler; _sendMachMessage(connection->notifyPort, 0, interestBitsNotify, messageToken); /* * Track the response! */ awaitThis = calloc(1, sizeof(PMResponse)); if (!awaitThis) { goto exit; } awaitThis->token = messageToken; awaitThis->connection = connection; awaitThis->notificationType = interestBitsNotify; awaitThis->myResponseWrangler = responseWrangler; awaitThis->notifiedWhen = CFAbsoluteTimeGetCurrent(); CFArrayAppendValue(responseWrangler->awaitingResponses, awaitThis); } // TODO: Set off a timer to fire in xx30xx seconds CFRunLoopTimerContext responseTimerContext = { 0, (void *)responseWrangler, NULL, NULL, NULL }; responseWrangler->awaitingResponsesTimeout = CFRunLoopTimerCreate(0, CFAbsoluteTimeGetCurrent() + responseWrangler->awaitResponsesTimeoutSeconds, 0.0, 0, 0, responsesTimedOut, &responseTimerContext); if (responseWrangler->awaitingResponsesTimeout) { CFRunLoopAddTimer(CFRunLoopGetCurrent(), responseWrangler->awaitingResponsesTimeout, kCFRunLoopDefaultMode); CFRelease(responseWrangler->awaitingResponsesTimeout); } exit: if (interested) CFRelease(interested); // Record the active wrangler in a global, then clear when reaped. if (responseWrangler) gLastResponseWrangler = responseWrangler; return responseWrangler; } /*****************************************************************************/ /*****************************************************************************/ static void responsesTimedOut(CFRunLoopTimerRef timer, void * info) { PMResponseWrangler *responseWrangler = (PMResponseWrangler *)info; PMResponse *one_response = NULL; int responsesCount = 0; int i; int tardyCount = 0; if (!responseWrangler) return; // Mark the timer as NULL since it's just fired and autoreleased. responseWrangler->awaitingResponsesTimeout = NULL; // Iterate list of awaiting responses, and tattle on anyone who hasn't // acknowledged yet. // Artificially mark them as "replied", with their reason being "timed out" responsesCount = CFArrayGetCount(responseWrangler->awaitingResponses); for (i=0; iawaitingResponses, i); if (!one_response) continue; if (one_response->replied) continue; // Caught a tardy reply tardyCount++; one_response->replied = true; one_response->timedout = true; one_response->repliedWhen = CFAbsoluteTimeGetCurrent(); int timeIntervalMS = (int)((one_response->repliedWhen - one_response->notifiedWhen) * 1000); CFNumberRef timeIntervalNumber = CFNumberCreate(NULL, kCFNumberIntType, &timeIntervalMS); logASLMessageApplicationResponse( kAppResponseLogSourcePMConnection, one_response->connection->callerName, CFSTR(kIOPMStatsResponseTimedOut), timeIntervalNumber); if (timeIntervalNumber) CFRelease(timeIntervalNumber); } checkResponses(responseWrangler); } /*****************************************************************************/ /*****************************************************************************/ #define kMsgPayloadCount 2 typedef struct { mach_msg_header_t header; mach_msg_body_t body; uint32_t payload[kMsgPayloadCount]; } IOPMMessageStructure; static void _sendMachMessage( mach_port_t port, mach_msg_id_t msg_id, uint32_t payload_bits, uint32_t payload_messagetoken) { kern_return_t status; IOPMMessageStructure msg; bzero(&msg, sizeof(msg)); msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); msg.header.msgh_size = sizeof(msg); msg.header.msgh_remote_port = port; msg.header.msgh_local_port = MACH_PORT_NULL; msg.header.msgh_id = msg_id; msg.body.msgh_descriptor_count = 0; msg.payload[0] = payload_bits; msg.payload[1] = payload_messagetoken; status = mach_msg(&msg.header, /* msg */ MACH_SEND_MSG | MACH_SEND_TIMEOUT, /* options */ msg.header.msgh_size, /* send_size */ 0, /* rcv_size */ MACH_PORT_NULL, /* rcv_name */ MACH_MSG_TIMEOUT_NONE, /* timeout */ MACH_PORT_NULL); /* notify */ if (status == MACH_SEND_TIMED_OUT) { mach_msg_destroy(&msg.header); } if (status != MACH_MSG_SUCCESS) { // Pray for the lost message. } return; } static void checkResponses(PMResponseWrangler *wrangler) { int i = 0; int responsesCount = 0; int notRepliedCount = 0; bool complete = true; PMResponse *oneResponse = NULL; PMResponse *maintrequester = NULL; CFAbsoluteTime earliestRequest = 0.0; responsesCount = CFArrayGetCount(wrangler->awaitingResponses); for (i=0; iawaitingResponses, i); if (!oneResponse->replied) { complete = false; notRepliedCount++; } if ( ((oneResponse->maintenanceRequested < earliestRequest) || (0.0 == earliestRequest)) && oneResponse->maintenanceRequested != 0.0) { earliestRequest = oneResponse->maintenanceRequested; maintrequester = oneResponse; } } if (!complete) { // We await more responses, or more deaths, or a timeout. return; } // Completion: all clients have acknowledged. if (wrangler->awaitingResponsesTimeout) { CFRunLoopTimerInvalidate(wrangler->awaitingResponsesTimeout); wrangler->awaitingResponsesTimeout = NULL; } if (wrangler->needsMaintenanceWake) { _pm_scheduledevent_choose_best_wake_event(kChooseMaintenance, earliestRequest); } else { // No wake events _pm_scheduledevent_choose_best_wake_event(kChooseMaintenance, 0); } // Handle PowerManagement acknowledgements if (_kSleepStateBits == wrangler->notificationType) { IOAllowPowerChange(gRootDomainConnect, wrangler->kernelAcknowledgementID); } cleanupResponseWrangler(wrangler); return; } /*****************************************************************************/ /*****************************************************************************/ static CFArrayRef createArrayOfConnectionsWithInterest( int interestBits) { CFMutableArrayRef arrayFoundInterests = NULL; PMConnection *lookee; int gConnectionsCount; int i; if (0 == interestBits) return NULL; // ********* arrayFoundInterests = CFArrayCreateMutable(kCFAllocatorDefault, 0, &_CFArrayConnectionCallBacks); gConnectionsCount = CFArrayGetCount(gConnections); for (i=0; iinterestsBits) { CFArrayAppendValue(arrayFoundInterests, lookee); } } return arrayFoundInterests; } /*****************************************************************************/ /*****************************************************************************/ static IOReturn createConnectionWithID(PMConnection **out) { static bool hasLoggedTooManyConnections = false; if ((globalConnectionIDTally > kMaxConnectionIDCount) && !hasLoggedTooManyConnections) { // ASL_LOG: KEEP FOR NOW // asl_log(NULL, NULL, ASL_LEVEL_ERR, "PM configd connections: connection count exceeded %d", // kMaxConnectionIDCount); return kIOReturnNoSpace; } *out = (PMConnection *)calloc(1, sizeof(PMConnection)); if (!*out) return kIOReturnNoMemory; ((PMConnection *)*out)->uniqueID = kConnectionOffset + globalConnectionIDTally++; // Add new connection to the global tracking array CFArrayAppendValue(gConnections, *out); return kIOReturnSuccess; } /*****************************************************************************/ /*****************************************************************************/ static PMConnection *connectionForID(uint32_t findMe) { CFIndex where = 0; CFRange theRange = CFRangeMake(0, CFArrayGetCount(gConnections)); PMConnection dummy; // Our gConnections CFArray equality callback only checks the "uniqueID" field // of the passed in PMConnection type. Searching for the value "dummy" // will return the first instance with connection ID "findMe". dummy.uniqueID = findMe; where = CFArrayGetFirstIndexOfValue(gConnections, theRange, &dummy); if (kCFNotFound == where) { return NULL; } else { return (PMConnection *)CFArrayGetValueAtIndex(gConnections, where); } }