/* ACPICPUThrottle.c: ACPI Processor Performance Control Module for MacOS X * * Copyright (C) 2006 Niall Douglas * AMD direct drive taken from FreeBSD acpi_ppc driver Copyright (C) 2004,2005 FUKUDA Nobuhiko * Intel direct drive taken from EnhancedSpeedStep driver Copyright (C) 2006 keithpk * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Reference: * - ACPI Specification Revision 2.0c, pp.232-236 * * Sysctl: * - kern.cputhrottle_curfreq: Current CPU frequency (in Mhz) * - kern.cputhrottle_verbose: Print lots of debug info to system.log * - kern.cputhrottle_freqs: List of space separated frequencies available (in Mhz) * Some version text and contact info * Then a series of Px prefixed available powerstates in the form: * Px, Mhz, mW, us, us (ctrl=0x, status=0x) * * This module compiles for powerpc but is pretty useless there! :) */ /**********************************************************************/ #include #include "IOKit/acpi/IOACPIPlatformDevice.h" #include #include #include #if defined(__i386__) || defined(__amd64__) #include #include //for rdmsr64 and friends #include // Names and numbers from IA-32 System Programming Guide #define MSR_PERF_STATUS 0x198 #define MSR_PERF_CTL 0x199 #endif template < typename type > class Ptr { protected: type * v; Ptr(type * _v = 0):v(_v) { } public: ~Ptr() { if (v) v->release(); v = 0; } Ptr & operator=(type * _v) { if (v) v->release(); v = _v; return *this; } type *operator->() { return v; } type *operator *() { return v; } const type *operator->() const { return v; } const type *operator *() const { return v; } bool operator!() const { return !v; } private: class Tester; public: operator Tester * ()const { if (v) return (Tester *) 1; return 0; } }; class OSObjPtr:public Ptr < OSObject > { public: OSObjPtr(OSObject * r = 0):Ptr < OSObject > (r) { } }; class IORegPtr:public Ptr < IORegistryEntry > { public: IORegPtr(IORegistryEntry * r = 0):Ptr < IORegistryEntry > (r) { } }; class IORegIt:public Ptr < IORegistryIterator > { public: IORegIt(IORegPtr & r, const IORegistryPlane * plane) { v = IORegistryIterator::iterateOver(*r, plane); } IORegIt(IORegistryEntry * r, const IORegistryPlane * plane) { v = IORegistryIterator::iterateOver(r, plane); } }; /**********************************************************************/ struct PowerState { bool active; unsigned int freq, power, tlat, bmlat, control, status; }; static struct LogicalCPU { bool active; IOACPIPlatformDevice *cpudevice; PowerState powerStates[16]; int maxPowerStates, currentState; } logicalCPUs[8]; #ifdef DEBUG static int verbose = 1; #else static int verbose = 1; #endif static char frequencies[1024] = ""; static uint32_t baseSystemFreq; #define dbg if(verbose) printf /**********************************************************************/ #define ERRH(r) { IOReturn __r=r; if(kIOReturnSuccess!=__r) { dbg("ACPICPUThrottle: Failed with code %d\n", __r); return false; } } #define dbgPEClock(v) dbg("ACPICPUThrottle: " #v "=%llu\n", gPEClockFrequencyInfo. v ) #define abs(v) ((((long)(v))<0) ? -(long)(v) : (long)(v)) // Kernel exports we need but aren't declared in the public header files __BEGIN_DECLS extern void rtc_clock_stepping(uint32_t new_frequency, uint32_t old_frequency); extern void rtc_clock_stepped(uint32_t new_frequency, uint32_t old_frequency); __END_DECLS static void printPEClockFrequencyInfo() { dbgPEClock(bus_clock_rate_hz); dbgPEClock(cpu_clock_rate_hz); dbgPEClock(dec_clock_rate_hz); dbgPEClock(bus_clock_rate_num); dbgPEClock(bus_clock_rate_den); dbgPEClock(bus_to_cpu_rate_num); dbgPEClock(bus_to_cpu_rate_den); dbgPEClock(bus_to_dec_rate_num); dbgPEClock(bus_to_dec_rate_den); dbgPEClock(timebase_frequency_hz); dbgPEClock(timebase_frequency_num); dbgPEClock(timebase_frequency_den); dbgPEClock(bus_frequency_hz); dbgPEClock(cpu_frequency_hz); } static void fixupPEClockFrequencyInfo(LogicalCPU & lc) { // Setup gPEClockFrequencyInfo which seems to have mostly random values gPEClockFrequencyInfo.timebase_frequency_hz = 1000000000; gPEClockFrequencyInfo.bus_frequency_hz = 100000000; gPEClockFrequencyInfo.bus_frequency_min_hz = gPEClockFrequencyInfo.bus_frequency_hz; gPEClockFrequencyInfo.bus_frequency_max_hz = gPEClockFrequencyInfo.bus_frequency_hz; gPEClockFrequencyInfo.cpu_frequency_min_hz = lc.powerStates[lc.maxPowerStates - 1].freq * 1000000ULL; gPEClockFrequencyInfo.cpu_frequency_max_hz = lc.powerStates[0].freq * 1000000ULL; gPEClockFrequencyInfo.cpu_frequency_hz = lc.powerStates[lc.currentState].freq * 1000000ULL; gPEClockFrequencyInfo.dec_clock_rate_hz = gPEClockFrequencyInfo.timebase_frequency_hz; gPEClockFrequencyInfo.bus_clock_rate_hz = gPEClockFrequencyInfo.bus_frequency_hz; gPEClockFrequencyInfo.cpu_clock_rate_hz = gPEClockFrequencyInfo.cpu_frequency_hz; gPEClockFrequencyInfo.bus_clock_rate_num = gPEClockFrequencyInfo.bus_clock_rate_hz; gPEClockFrequencyInfo.bus_clock_rate_den = 1; gPEClockFrequencyInfo.bus_to_cpu_rate_num = (2 * gPEClockFrequencyInfo.cpu_clock_rate_hz) / gPEClockFrequencyInfo.bus_clock_rate_hz; gPEClockFrequencyInfo.bus_to_cpu_rate_den = 2; gPEClockFrequencyInfo.bus_to_dec_rate_num = 1; gPEClockFrequencyInfo.bus_to_dec_rate_den = gPEClockFrequencyInfo.bus_clock_rate_hz / gPEClockFrequencyInfo.dec_clock_rate_hz; //dbg("ACPICPUThrottle: gPEClockFrequencyInfo after fixup:\n"); //printPEClockFrequencyInfo(); } static uint32_t determineRTCClockStepping(uint32_t newfreq, uint32_t basefreq) { uint64_t rtc_cycle_count = (basefreq * 1000000ULL) / 20; uint64_t newcount = rtc_cycle_count * newfreq / basefreq; dbg("ACPICPUThrottle: rtc_cycle_count=%llu, newcount=%llu, cutoff=%llu\n", rtc_cycle_count, newcount, (1000000000ULL / 20)); return newfreq; } static bool setCPU(LogicalCPU & lc, int powerstate) { dbg("ACPICPUThrottle: Setting %s to powerstate %d\n", lc.cpudevice->getName(), powerstate); if (powerstate < 0) powerstate = 0; if (powerstate >= lc.maxPowerStates) powerstate = lc.maxPowerStates - 1; //if(powerstate==lc.currentState) return true; fixupPEClockFrequencyInfo(lc); rtc_clock_stepping(determineRTCClockStepping (lc.powerStates[powerstate].freq, lc.powerStates[0].freq), baseSystemFreq); // Copied straight from the EnhancedSpeedStep driver uint64_t msr, v64 = lc.powerStates[powerstate].control; msr = rdmsr64(MSR_PERF_CTL); msr = (msr & ~(uint64_t) (0xffff)) | v64; wrmsr64(MSR_PERF_CTL, msr); unsigned int inc = lc.powerStates[powerstate].tlat / 10; for (unsigned int n = 0; n <= lc.powerStates[powerstate].tlat; n += inc) { IODelay(inc); msr = rdmsr64(MSR_PERF_STATUS) & 0xffff; if (msr == lc.powerStates[powerstate].status) break; } /* if (msr == lc.powerStates[powerstate].status) */ { dbg("ACPICPUThrottle: Throttle succeeded with cur_status=0x%x, want_status=0x%x!\n", msr, lc.powerStates[powerstate].status); rtc_clock_stepped(determineRTCClockStepping (lc.powerStates[powerstate].freq, lc.powerStates[0].freq), baseSystemFreq); lc.currentState = powerstate; fixupPEClockFrequencyInfo(lc); return true; } /* printf("ACPICPUThrottle: Throttle failed with cur_status=0x%x, want_status=0x%x!\n", msr, lc.powerStates[powerstate].status); */ return false; } static bool setCPUs(int powerstate) { bool ret = true; for (int n = 0; logicalCPUs[n].active; n++) ret = ret && setCPU(logicalCPUs[n], powerstate); return ret; } static void setPowerStates(LogicalCPU & lc) { lc.maxPowerStates = 4; for (int n = 0; n < lc.maxPowerStates; n++) { PowerState & powerState = lc.powerStates[n]; powerState.tlat = 110; powerState.bmlat = 10; switch (n) { case 0: powerState.freq = 2000; powerState.power = 31000; powerState.control = 0x83; powerState.status = 0; break; case 1: powerState.freq = 1667; powerState.power = 25000; powerState.control = 0x183; powerState.status = 1; break; case 2: powerState.freq = 1333; powerState.power = 19000; powerState.control = 0x283; powerState.status = 2; break; case 3: powerState.freq = 1000; powerState.power = 13000; powerState.control = 0x383; powerState.status = 3; break; } powerState.active = true; } } static bool addCPU(LogicalCPU & lc, IOACPIPlatformDevice * cpu) { OSObject *result; printf("ACPICPUThrottle: Adding %s\n", cpu->getName()); lc.cpudevice = cpu; setPowerStates(lc); // Generate sysctl list char *sysctlend = frequencies; int lastActive = lc.maxPowerStates; for (int n = lc.maxPowerStates - 1; n >= 0; n--) { sprintf(sysctlend, "%d ", lc.powerStates[n].freq); sysctlend = strchr(sysctlend, 0); } *sysctlend++ = 10; strcpy(sysctlend, "ACPICPUThrottle v1.02 (" __DATE__ ") (C) 2006 Niall Douglas\n" " http://www.nedprod.com/programs/MacOS%20X/ACPICPUThrottle/\n"); sysctlend = strchr(sysctlend, 0); for (int n = 0; n < lc.maxPowerStates; n++) { PowerState & powerState = lc.powerStates[n]; if (!powerState.active) sprintf(sysctlend, "P%u, %uMhz disabled\n", n, powerState.freq); else sprintf(sysctlend, "P%u, %uMhz, %umW, %uus, %uus (ctrl=0x%x, status=0x%x)\n", n, powerState.freq, powerState.power, powerState.tlat, powerState.bmlat, powerState.control, powerState.status); printf("---> %s", sysctlend); sysctlend = strchr(sysctlend, 0); } strcpy(sysctlend, "(CPU Powerstates under 1GHz are not available on MacOS X)\n"); lc.maxPowerStates = lastActive; if (1 == lc.maxPowerStates) { printf("ACPICPUThrottle: Only one available power state found (all states <=1Ghz are unavailable), exiting!\n"); return false; } // Determine what the system is currently running at lc.currentState = -1; uint32_t diff = 1 << 30; for (int n = 0; n < sizeof(lc.powerStates) / sizeof(lc.powerStates[0]); n++) { PowerState & powerState = lc.powerStates[n]; //printf("diff=%ld\n", abs(powerState.freq-gPEClockFrequencyInfo.cpu_frequency_hz/1000000ULL)); if (powerState.freq && abs(powerState.freq - gPEClockFrequencyInfo.cpu_frequency_hz / 1000000ULL) < diff) { lc.currentState = n; baseSystemFreq = powerState.freq; diff = abs(powerState.freq - gPEClockFrequencyInfo.cpu_frequency_hz / 1000000ULL); } } if (diff >= 50) lc.currentState = -1; if (-1 == lc.currentState) { printf("ACPICPUThrottle: Failed to determine current system speed (no match to %llu)!\n", gPEClockFrequencyInfo.cpu_frequency_hz / 1000000ULL); return false; } if (!setCPU(lc, lc.maxPowerStates - 1)) // Failed to set, so don't activate return true; //dbg("ACPICPUThrottle: first item is metaclass of %s\n", result->getMetaClass()->getClassName()); lc.active = true; return true; } /**********************************************************************/ static int cputhrottle_sysctl_mhz SYSCTL_HANDLER_ARGS { int err = 0; if (req->newptr) { int pstate, MHz_wanted, bestdiff = 1 << 30; err = SYSCTL_IN(req, &MHz_wanted, sizeof(int)); if (err) return err; if (MHz_wanted < 16) { // He wants a pstate pstate = MHz_wanted; } else { for (int n = 0; n < logicalCPUs[0].maxPowerStates; n++) { PowerState & ps = logicalCPUs[0].powerStates[n]; if (abs(MHz_wanted - (int)ps.freq) < bestdiff) { pstate = n; bestdiff = abs(MHz_wanted - (int)ps.freq); } } } setCPUs(pstate); } else { int MHz = logicalCPUs[0].powerStates[logicalCPUs[0].currentState].freq; err = SYSCTL_OUT(req, &MHz, sizeof(int)); } return err; } SYSCTL_INT(_kern, OID_AUTO, cputhrottle_verbose, CTLFLAG_RW, &verbose, 0, "Log CPU frequency changes"); SYSCTL_PROC(_kern, OID_AUTO, cputhrottle_curfreq, CTLTYPE_INT | CTLFLAG_RW, 0, 0, &cputhrottle_sysctl_mhz, "I", "Current CPU frequency"); SYSCTL_STRING(_kern, OID_AUTO, cputhrottle_freqs, CTLFLAG_RD, frequencies, 0, "CPU frequencies supported"); extern "C" kern_return_t ACPICPUThrottle_start(kmod_info_t * ki, void *d) { dbg("ACPI CPU Throttle loaded\n"); //printPEClockFrequencyInfo(); const IORegistryPlane *IODeviceTree = IORegistryEntry::getPlane("IODeviceTree"); if (!IODeviceTree) return KERN_FAILURE; // Firstly, let's see what CPUs we have IORegPtr cpus(IORegistryEntry::fromPath("/cpus", IODeviceTree)); if (!cpus) return KERN_FAILURE; IORegistryEntry *cpu; int n = 0, i = 0; for (IORegIt it(cpus, IODeviceTree); cpu = it->getCurrentEntry(); it->getNextObjectFlat(), n++) { // Sadly OSDynamicCast is not available to us if (n && i < sizeof(logicalCPUs) / sizeof(LogicalCPU)) { if (addCPU (logicalCPUs[i], static_cast < IOACPIPlatformDevice * >(cpu))) i++; } } if (!logicalCPUs[0].active) { printf("ACPICPUThrottle: No valid CPUs returned by ACPI! It's possible your BIOS " "does not think your system should be throttled and therefore did not " "return any available power states, or maybe your CPU only supports one " "other power state than maximum which is below 1Ghz!\n"); return KERN_FAILURE; } /* Register our SYSCTL */ sysctl_register_oid(&sysctl__kern_cputhrottle_curfreq); sysctl_register_oid(&sysctl__kern_cputhrottle_verbose); sysctl_register_oid(&sysctl__kern_cputhrottle_freqs); return KERN_SUCCESS; } extern "C" kern_return_t ACPICPUThrottle_stop(kmod_info_t * ki, void *d) { setCPUs(0); sysctl_unregister_oid(&sysctl__kern_cputhrottle_curfreq); sysctl_unregister_oid(&sysctl__kern_cputhrottle_verbose); sysctl_unregister_oid(&sysctl__kern_cputhrottle_freqs); dbg("ACPI CPU Throttle unloaded\n"); return KERN_SUCCESS; }