rendered paste body/* ACPICPUThrottle.c: ACPI Processor Performance Control Module for MacOS X
*
* Copyright (C) 2006 Niall Douglas <http://www.nedprod.com/>
* AMD direct drive taken from FreeBSD acpi_ppc driver Copyright (C) 2004,2005 FUKUDA Nobuhiko <nfukuda@spa.is.uec.ac.jp>
* 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, <clock>Mhz, <power>mW, <cpu latency>us, <bus latency>us (ctrl=0x<reg to write>, status=0x<reg to read>)
*
* This module compiles for powerpc but is pretty useless there! :)
*/
/**********************************************************************/
#include <mach/mach_types.h>
#include "IOKit/acpi/IOACPIPlatformDevice.h"
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#if defined(__i386__) || defined(__amd64__)
#include <i386/cpuid.h>
#include <i386/proc_reg.h> //for rdmsr64 and friends
#include <i386/apic.h>
// 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;
}