All pastes #359932 Raw Edit

Something

public text v1 · immutable
·published 2007-02-17 04:09 UTC
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;
}