DynamicPowerStep.c   [plain text]


/*
 * Copyright (c) 2002-2005 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*

cc -DMAIN -o DynamicPowerStep -framework CoreFoundation -framework IOKit -framework SystemConfiguration DynamicPowerStep.c

 */

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

#include <unistd.h>

#include <mach/mach.h>
#include <mach/mach_host.h>
#include <mach/host_info.h>

#include <CoreFoundation/CoreFoundation.h>

#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCDPlugin.h>
#include <SystemConfiguration/SCPrivate.h>
#include <SystemConfiguration/SCValidation.h>

#include <IOKit/IOKitLib.h>
#include <IOKit/pwr_mgt/IOPM.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/pwr_mgt/IOPMLibPrivate.h>

#define kTimeInterval		(0.1)
#define kNumSamples		(8)
#define kPinThreshold		(0.70)
#define kSwitchThreshold	(0.50)
#define kDynamicSwitch		(0x80000000UL)

static mach_port_t			gHostPort;
static mach_port_t			gMasterPort;
static io_connect_t			gPowerManager;
static SCDynamicStoreRef		gSCDynamicStore;
static CFRunLoopTimerRef		gTimerRef;
static CFStringRef			gIOPMDynamicStoreSettingsKey;
static Boolean				gDPSEnabled;
static long				gCurrentSpeed;
static long				gPinsInRow;
static double				gLoadAverages[kNumSamples];
static host_cpu_load_info_data_t	gSavedTicks;
static IONotificationPortRef        gNotifyPort = NULL;
static io_object_t                  gNotifyHandle = MACH_PORT_NULL;
static CFRunLoopSourceRef           gNotifyRLS = NULL;

void load(CFBundleRef bundle, Boolean bundleVerbose);
static void UpdateConfiguration(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info);
static void EnableDynamicPowerStep(Boolean enable);
static void UpdateDynamicLoad(CFRunLoopTimerRef timerRef, void *info);
static void InitDynamicPowerStep(void);
static void FeatureChanged(void *refcon, io_service_t service,
                    uint32_t messageType, void *messageArgument);

#ifdef MAIN
int main(int argc, char **argv)
{
  load(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
  CFRunLoopRun();
  return 0;
}
#endif

void load(CFBundleRef bundle, Boolean bundleVerbose)
{
    io_service_t                rootDomainRef;
    kern_return_t               kr = KERN_FAILURE;
    
    // Return immediately if not on PowerPC.
#ifndef __ppc__
    return;
#endif    
    
    // Install a notification when PM features are published
    // We are interested in kIOPMDynamicPowerStepKey & kIOPMReduceSpeedKey    
    rootDomainRef = IORegistryEntryFromPath( kIOMasterPortDefault,
                        kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
    gNotifyPort = IONotificationPortCreate( kIOMasterPortDefault );
    gNotifyRLS = IONotificationPortGetRunLoopSource(gNotifyPort);
    if (gNotifyRLS) {   
        CFRunLoopAddSource( CFRunLoopGetCurrent(), 
                            gNotifyRLS, kCFRunLoopDefaultMode);
    }
    
    if (gNotifyRLS && gNotifyPort && (MACH_PORT_NULL != rootDomainRef))
    {
        kr = IOServiceAddInterestNotification( gNotifyPort, 
                                          rootDomainRef,
                                          kIOGeneralInterest,
                                          (IOServiceInterestCallback)FeatureChanged,
                                          NULL,
                                          &gNotifyHandle);
    }
    
    if (MACH_PORT_NULL != rootDomainRef) {
        IOObjectRelease(rootDomainRef);
    }
    
    if (KERN_SUCCESS != kr) {
        // Unable to install notification.
        // Cleanup here; then continue to see 
        // if features are already published.
    
        if (gNotifyHandle != MACH_PORT_NULL) {
            IOObjectRelease(gNotifyHandle);
            gNotifyHandle = MACH_PORT_NULL;
        }
        if (gNotifyRLS) {    
            CFRunLoopRemoveSource( CFRunLoopGetCurrent(), 
                                    gNotifyRLS, kCFRunLoopDefaultMode);    
            gNotifyRLS = NULL;
        }
        if (gNotifyPort) {
            IONotificationPortDestroy(gNotifyPort);
            gNotifyPort = NULL;
        }
    }

    // Check for feature availability, and initDPS if supported.
    // Return immediately if neither DPS or Reduced is supported.
    if( IOPMFeatureIsAvailable(CFSTR(kIOPMDynamicPowerStepKey), NULL)
     || IOPMFeatureIsAvailable(CFSTR(kIOPMReduceSpeedKey), NULL) )
    {
        InitDynamicPowerStep();
    }

    // non-failure return
    return;
}

static void FeatureChanged(
    void *refcon __unused, 
    io_service_t service __unused,                    
    uint32_t messageType, 
    void *messageArgument __unused)
{
    if (messageType == kIOPMMessageFeatureChange)
    {
        if( IOPMFeatureIsAvailable(CFSTR(kIOPMDynamicPowerStepKey), NULL)
         || IOPMFeatureIsAvailable(CFSTR(kIOPMReduceSpeedKey), NULL) )
        {
            InitDynamicPowerStep();
        }
    }
}

static void InitDynamicPowerStep(void)
{
    kern_return_t               result;
    CFRunLoopSourceRef          runLoopSourceRef;
    CFMutableArrayRef           arrayRef;
    Boolean	                    ok;

    gIOPMDynamicStoreSettingsKey = CFSTR(kIOPMDynamicStoreSettingsKey);
    
    // Set up some defaults: DPS off and assume full speed.
    gDPSEnabled = FALSE;
    gCurrentSpeed = 0;
    
    // Get the host port
    gHostPort = mach_host_self();
    
    result = IOMasterPort(bootstrap_port, &gMasterPort);
    if (result != KERN_SUCCESS) return;
    
    // Open a connection to the Power Manager.
    gPowerManager = IOPMFindPowerManagement(gMasterPort);
    if (gPowerManager == 0) return;
    
    // Open a session to the SCDynamicStore.
    gSCDynamicStore = SCDynamicStoreCreate(NULL, CFSTR("Dynamic Power Step"),
                                           UpdateConfiguration, NULL);
    if (gSCDynamicStore == 0) return;
    
    // Set the dynamic store notifications for the IOPM settings.
    arrayRef = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    CFArrayAppendValue(arrayRef, gIOPMDynamicStoreSettingsKey);
    ok = SCDynamicStoreSetNotificationKeys(gSCDynamicStore, arrayRef, NULL);
    CFRelease(arrayRef);
    if (!ok) return;
    
    // Create a run loop source for the dynamic store.
    runLoopSourceRef = SCDynamicStoreCreateRunLoopSource(NULL, gSCDynamicStore, 0);
    if (runLoopSourceRef == 0) return;
    
    // Add the dynamic store source to the run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSourceRef, kCFRunLoopDefaultMode);
    CFRelease(runLoopSourceRef);
    
    // Remove ourselves from the feature publish notifications since we 
    // are already loaded and no longer need them.
    if (gNotifyHandle != MACH_PORT_NULL) {
        IOObjectRelease(gNotifyHandle);
        gNotifyHandle = MACH_PORT_NULL;
    }
    if (gNotifyRLS) {    
        CFRunLoopRemoveSource(CFRunLoopGetCurrent(), gNotifyRLS, kCFRunLoopDefaultMode);    
        gNotifyRLS = NULL;
    }
    if (gNotifyPort) {
        IONotificationPortDestroy(gNotifyPort);
        gNotifyPort = NULL;
    }
    
    UpdateConfiguration(gSCDynamicStore, 0, 0);
    
    return;
}


static void UpdateConfiguration(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
{
    CFDictionaryRef	dictionaryRef;
    CFNumberRef		rpsRef, dpsRef;
    long		rps, dps;
    
    dictionaryRef = SCDynamicStoreCopyValue(store, gIOPMDynamicStoreSettingsKey);
    if (isA_CFDictionary(dictionaryRef) == 0) {
        if (dictionaryRef != 0) CFRelease(dictionaryRef);
        return;
    }
    
    rpsRef = isA_CFNumber(CFDictionaryGetValue(dictionaryRef, CFSTR(kIOPMReduceSpeedKey)));
    dpsRef = isA_CFNumber(CFDictionaryGetValue(dictionaryRef, CFSTR(kIOPMDynamicPowerStepKey)));
    
    // Set Reduce Processor Speed as needed.
    if (rpsRef != 0) {
        CFNumberGetValue((CFNumberRef)rpsRef, kCFNumberSInt32Type, &rps);
        
        gCurrentSpeed = 0;
        if (rps == 0) {
            // Enable or disable Dynamic Power Step as needed.
            if (dpsRef != 0) {
                CFNumberGetValue((CFNumberRef)dpsRef, kCFNumberSInt32Type, &dps);
                
                if (dps != gDPSEnabled) {
                    EnableDynamicPowerStep(!gDPSEnabled);
                }
            }
        } else {
            if (gDPSEnabled) EnableDynamicPowerStep(FALSE);
            
            gCurrentSpeed = 1;
        }
    }
    
    CFRelease(dictionaryRef);
    
    // Change Processor Speed to refect new settings.
    IOPMSetAggressiveness(gPowerManager, kPMSetProcessorSpeed,
			  (gDPSEnabled * kDynamicSwitch) | gCurrentSpeed);
}

static void EnableDynamicPowerStep(Boolean enable)
{
    long cnt;
    
    if (enable) {
        // Create a new CFRunLoopTimer and add it to the run loop
        gTimerRef = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(),
                                        kTimeInterval, 0, 0,
                                        UpdateDynamicLoad, NULL);
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), gTimerRef, kCFRunLoopDefaultMode);
        
        // Clear Dynamic Power Step data.
        gPinsInRow = 0;
        for (cnt = 0; cnt < kNumSamples; cnt++) gLoadAverages[cnt] = 0.0;
        bzero(&gSavedTicks, sizeof(host_cpu_load_info_data_t));
        
        // Set processor speed slow.
        gCurrentSpeed = 1;
        
        // Mark Dynamic Power Step as enabled.
        gDPSEnabled = true;
    } else {
        // Mark Dynamic Power Step as disabled.
        gDPSEnabled = false;
        
        // Set processor speed fast.
        gCurrentSpeed = 0;
        
        // Invalidate the CFRunLoopTimer.
        CFRunLoopTimerInvalidate(gTimerRef);
        CFRelease(gTimerRef);
    }
}

static void UpdateDynamicLoad(CFRunLoopTimerRef timerRef, void *info)
{
    host_cpu_load_info_data_t		deltaTicks;
    host_cpu_load_info_data_t		newTicks;
    host_basic_info_data_t		hostInfo;
    mach_msg_type_number_t		infoCount;
    int					numProcessors;
    double				totalLoad, userLoad, systemLoad, niceLoad;
    double 				average;
    double				pinThreshold, switchThreshold;
    unsigned long			cnt, sum, newMode;
    
    infoCount = HOST_BASIC_INFO_COUNT;
    host_info(gHostPort, HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount);
    numProcessors = hostInfo.avail_cpus;

    pinThreshold = kPinThreshold / numProcessors;
    switchThreshold = kSwitchThreshold / numProcessors;
    
    infoCount = HOST_CPU_LOAD_INFO_COUNT;
    host_statistics(gHostPort,HOST_CPU_LOAD_INFO, 
                    (host_info_t)&newTicks, &infoCount);

    sum = 0;
    for (cnt = 0; cnt < CPU_STATE_MAX; cnt++) {
      deltaTicks.cpu_ticks[cnt] =
        newTicks.cpu_ticks[cnt] - gSavedTicks.cpu_ticks[cnt];
      
      sum += deltaTicks.cpu_ticks[cnt];
    }
    gSavedTicks = newTicks;
    
    if (sum > 0) {
        totalLoad  = 1.0 - ((double)(deltaTicks.cpu_ticks[CPU_STATE_IDLE])   / (double)sum);
        userLoad   = ((double)(deltaTicks.cpu_ticks[CPU_STATE_USER])   / (double)sum);
        systemLoad = ((double)(deltaTicks.cpu_ticks[CPU_STATE_SYSTEM]) / (double)sum);
        niceLoad   = ((double)(deltaTicks.cpu_ticks[CPU_STATE_NICE])   / (double)sum);
    } else {
        totalLoad  = 0.0;
        userLoad   = 0.0;
        systemLoad = 0.0;
        niceLoad   = 0.0;
    }
    
    // Age the load samples.
    for (cnt = kNumSamples - 1; cnt != 0 ; cnt--) {
        gLoadAverages[cnt] = gLoadAverages[cnt - 1];
    }
    
    gLoadAverages[0] = userLoad + systemLoad;
    
    if (gLoadAverages[0] >= pinThreshold) gPinsInRow++;
    else gPinsInRow /= 2;
    
    average = 0;
    for (cnt = 0; cnt < kNumSamples; cnt++) {
        average += gLoadAverages[cnt];
    }
    average += gPinsInRow;
    average /= 1.0 * (kNumSamples + gPinsInRow);
    
    newMode = average >= switchThreshold ? 0 : 1;
    
    if (newMode != gCurrentSpeed) {
        IOPMSetAggressiveness(gPowerManager, kPMSetProcessorSpeed,
			      kDynamicSwitch | newMode);
        gCurrentSpeed = newMode;
    }
}