[BACK]Return to cpu.c CVS log [TXT][DIR] Up to [local] / prex / dev / i386 / i386

File: [local] / prex / dev / i386 / i386 / cpu.c (download)

Revision 1.1, Tue Aug 19 12:46:47 2008 UTC (15 years, 9 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*	$OpenBSD: est.c,v 1.11 2005/03/07 06:59:14 mbalmer Exp $ */
/*
 * Copyright (c) 2003 Michael Eriksson.
 * 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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */
/*
 * This is a driver for Intel's Enhanced SpeedStep, as implemented in
 * Pentium M processors.
 *
 * Reference documentation:
 *
 * - IA-32 Intel Architecture Software Developer's Manual, Volume 3:
 *   System Programming Guide.
 *   Section 13.14, Enhanced Intel SpeedStep technology.
 *   Table B-2, MSRs in Pentium M Processors.
 *   http://www.intel.com/design/pentium4/manuals/245472.htm
 *
 * - Intel Pentium M Processor Datasheet.
 *   Table 5, Voltage and Current Specifications.
 *   http://www.intel.com/design/mobile/datashts/252612.htm
 *
 * - Intel Pentium M Processor on 90 nm Process with 2-MB L2 Cache Datasheet
 *   Table 3-4, Voltage and Current Specifications.
 *   http://www.intel.com/design/mobile/datashts/302189.htm
 *
 * - Linux cpufreq patches, speedstep-centrino.c.
 *   Encoding of MSR_PERF_CTL and MSR_PERF_STATUS.
 *   http://www.codemonkey.org.uk/projects/cpufreq/cpufreq-2.4.22-pre6-1.gz
 */

/*
 * cpu.c - Processor driver for Intel CPU
 */

#include <driver.h>
#include <sys/ioctl.h>
#include <cpufunc.h>

/* #define DEBUG_CPU 1 */

#ifdef DEBUG
#define DPRINTF(a) printf a
#else
#define DPRINTF(a)
#endif

/* Status/control registers (from the IA-32 System Programming Guide). */
#define MSR_PERF_STATUS		0x198
#define MSR_PERF_CTL		0x199

/* Register and bit for enabling SpeedStep. */
#define MSR_MISC_ENABLE		0x1a0
#define MSR_SS_ENABLE		(1<<16)

static int cpu_ioctl(device_t dev, u_long cmd, void *arg);
static int cpu_init(void);

/*
 * Driver structure
 */
struct driver cpu_drv = {
	/* name */	"Processor",
	/* order */	1,
	/* init */	cpu_init,
};

static struct devio cpu_io = {
	/* open */	NULL,
	/* close */	NULL,
	/* read */	NULL,
	/* write */	NULL,
	/* ioctl */	cpu_ioctl,
	/* event */	NULL,
};

static device_t cpu_dev;		/* Device object */

/*
 * Frequency tables
 */
struct fq_info {
	u_short mhz;
	u_short mv;
};

/* Ultra Low Voltage Intel Pentium M processor 900 MHz */
static const struct fq_info pentium_m_900[] = {
	{  900, 1004 },
	{  800,  988 },
	{  600,  844 },
};

/* Ultra Low Voltage Intel Pentium M processor 1.00 GHz */
static const struct fq_info pentium_m_1000[] = {
	{ 1000, 1004 },
	{  900,  988 },
	{  800,  972 },
	{  600,  844 },
};

/* Low Voltage Intel Pentium M processor 1.10 GHz */
static const struct fq_info pentium_m_1100[] = {
	{ 1100, 1180 },
	{ 1000, 1164 },
	{  900, 1100 },
	{  800, 1020 },
	{  600,  956 },
};

/* Low Voltage Intel Pentium M processor 1.20 GHz */
static const struct fq_info pentium_m_1200[] = {
	{ 1200, 1180 },
	{ 1100, 1164 },
	{ 1000, 1100 },
	{  900, 1020 },
	{  800, 1004 },
	{  600,  956 },
};

/* Intel Pentium M processor 1.30 GHz */
static const struct fq_info pentium_m_1300[] = {
	{ 1300, 1388 },
	{ 1200, 1356 },
	{ 1000, 1292 },
	{  800, 1260 },
	{  600,  956 },
};

/* Intel Pentium M processor 1.40 GHz */
static const struct fq_info pentium_m_1400[] = {
	{ 1400, 1484 },
	{ 1200, 1436 },
	{ 1000, 1308 },
	{  800, 1180 },
	{  600,  956 }
};

/* Intel Pentium M processor 1.50 GHz */
static const struct fq_info pentium_m_1500[] = {
	{ 1500, 1484 },
	{ 1400, 1452 },
	{ 1200, 1356 },
	{ 1000, 1228 },
	{  800, 1116 },
	{  600,  956 }
};

/* Intel Pentium M processor 1.60 GHz */
static const struct fq_info pentium_m_1600[] = {
	{ 1600, 1484 },
	{ 1400, 1420 },
	{ 1200, 1276 },
	{ 1000, 1164 },
	{  800, 1036 },
	{  600,  956 }
};

/* Intel Pentium M processor 1.70 GHz */
static const struct fq_info pentium_m_1700[] = {
	{ 1700, 1484 },
	{ 1400, 1308 },
	{ 1200, 1228 },
	{ 1000, 1116 },
	{  800, 1004 },
	{  600,  956 }
};


/* Intel Pentium M processor 723 1.0 GHz */
static const struct fq_info pentium_m_n723[] = {
	{ 1000,  940 },
	{  900,  908 },
	{  800,  876 },
	{  600,  812 }
};

/* Intel Pentium M processor 733 1.1 GHz */
static const struct fq_info pentium_m_n733[] = {
	{ 1100,  940 },
	{ 1000,  924 },
	{  900,  892 },
	{  800,  876 },
	{  600,  812 }
};

/* Intel Pentium M processor 753 1.2 GHz */
static const struct fq_info pentium_m_n753[] = {
	{ 1200,  940 },
	{ 1100,  924 },
	{ 1000,  908 },
	{  900,  876 },
	{  800,  860 },
	{  600,  812 }
};

/* Intel Pentium M processor 738 1.4 GHz */
static const struct fq_info pentium_m_n738[] = {
	{ 1400, 1116 },
	{ 1300, 1116 },
	{ 1200, 1100 },
	{ 1100, 1068 },
	{ 1000, 1052 },
	{  900, 1036 },
	{  800, 1020 },
	{  600,  988 }
};

#if 0
/* Intel Pentium M processor 758 1.5 GHz */
static const struct fq_info pentium_m_n758[] = {
	{ 1500, 1116 },
	{ 1400, 1116 },
	{ 1300, 1100 },
	{ 1200, 1084 },
	{ 1100, 1068 },
	{ 1000, 1052 },
	{  900, 1036 },
	{  800, 1020 },
	{  600,  988 }
};
#endif

/* Intel Pentium M processor 715 1.5 GHz */
static const struct fq_info pentium_m_n715[] = {
	{ 1500, 1340 },
	{ 1200, 1228 },
	{ 1000, 1148 },
	{  800, 1068 },
	{  600,  988 }
};

/* Intel Pentium M processor 725 1.6 GHz */
static const struct fq_info pentium_m_n725[] = {
	{ 1600, 1340 },
	{ 1400, 1276 },
	{ 1200, 1212 },
	{ 1000, 1132 },
	{  800, 1068 },
	{  600,  988 }
};

/* Intel Pentium M processor 735 1.7 GHz */
static const struct fq_info pentium_m_n735[] = {
	{ 1700, 1340 },
	{ 1400, 1244 },
	{ 1200, 1180 },
	{ 1000, 1116 },
	{  800, 1052 },
	{  600,  988 }
};

/* Intel Pentium M processor 745 1.8 GHz */
static const struct fq_info pentium_m_n745[] = {
	{ 1800, 1340 },
	{ 1600, 1292 },
	{ 1400, 1228 },
	{ 1200, 1164 },
	{ 1000, 1116 },
	{  800, 1052 },
	{  600,  988 }
};

/* Intel Pentium M processor 755 2.0 GHz */
static const struct fq_info pentium_m_n755[] = {
	{ 2000, 1340 },
	{ 1800, 1292 },
	{ 1600, 1244 },
	{ 1400, 1196 },
	{ 1200, 1148 },
	{ 1000, 1100 },
	{  800, 1052 },
	{  600,  988 }
};

/* Intel Pentium M processor 765 2.1 GHz */
static const struct fq_info pentium_m_n765[] = {
	{ 2100, 1340 },
	{ 1800, 1276 },
	{ 1600, 1228 },
	{ 1400, 1180 },
	{ 1200, 1132 },
	{ 1000, 1084 },
	{  800, 1036 },
	{  600,  988 }
};

struct fqlist {
	const char *brand_tag;
	const struct fq_info *table;
	int n;
};

static const struct fqlist pentium_m[] = {
#define ENTRY(s, v)	{ s, v, sizeof(v) / sizeof((v)[0]) }
	ENTRY(" 900", pentium_m_900),
	ENTRY("1000", pentium_m_1000),
	ENTRY("1100", pentium_m_1100),
	ENTRY("1200", pentium_m_1200),
	ENTRY("1300", pentium_m_1300),
	ENTRY("1400", pentium_m_1400),
	ENTRY("1500", pentium_m_1500),
	ENTRY("1600", pentium_m_1600),
	ENTRY("1700", pentium_m_1700),
#undef ENTRY
};

static const struct fqlist pentium_m_dothan[] = {
#define ENTRY(s, v)	{ s, v, sizeof(v) / sizeof((v)[0]) }
	ENTRY("1.00", pentium_m_n723),
	ENTRY("1.10", pentium_m_n733),
	ENTRY("1.20", pentium_m_n753),
	ENTRY("1.40", pentium_m_n738),
#if 0
	ENTRY("1.50", pentium_m_n758),
#endif
	ENTRY("1.50", pentium_m_n715),
	ENTRY("1.60", pentium_m_n725),
	ENTRY("1.70", pentium_m_n735),
	ENTRY("1.80", pentium_m_n745),
	ENTRY("2.00", pentium_m_n755),
	ENTRY("2.10", pentium_m_n765),
#undef ENTRY
};

struct est_cpu {
	const char *brand_prefix;
	const char *brand_suffix;
	const struct fqlist *list;
	int n;
};

static const struct est_cpu est_cpus[] = {
	{
		"Intel(R) Pentium(R) M processor ", "MHz",
		pentium_m,
		(sizeof(pentium_m) / sizeof(pentium_m[0]))
	},
	{
		"Intel(R) Pentium(R) M processor ", "GHz",
		pentium_m_dothan,
		(sizeof(pentium_m_dothan) / sizeof(pentium_m_dothan[0]))
	},
};

#define NESTCPUS	  (int)(sizeof(est_cpus) / sizeof(est_cpus[0]))


#define MSRVALUE(mhz, mv)	((((mhz) / 100) << 8) | (((mv) - 700) / 16))
#define MSR2MHZ(msr)		((((u_int) (msr) >> 8) & 0xff) * 100)
#define MSR2MV(msr)		(((int) (msr) & 0xff) * 16 + 700)

static const struct fqlist *est_fqlist;

static struct cpu_info cpu_info;
static struct cpu_stat cpu_stat;
#ifdef CONFIG_DVS_EMULATION
static int bochs;
#endif

/*
 * Set CPU performance
 *
 * @level: percent of cpu speed
 */
int
cpu_setperf(int level)
{
	int i, fq;
	u_int msr_lo, msr_hi;
	int max_mhz;

	ASSERT(cpu_info.clock_ctrl);

	max_mhz = est_fqlist->table[0].mhz;
	fq = max_mhz * level / 100;

	for (i = est_fqlist->n - 1; i > 0; i--)
		if (est_fqlist->table[i].mhz >= fq)
			break;

	if (est_fqlist->table[i].mhz == cpu_stat.speed)
		return 0;

	cpu_stat.speed = est_fqlist->table[i].mhz;
	cpu_stat.power = est_fqlist->table[i].mv;
#ifdef DEBUG_CPU
	DPRINTF(("setperf: %dMHz %dmV\n", cpu_stat.speed, cpu_stat.power));
#endif
#ifdef CONFIG_DVS_EMULATION
	if (bochs)
		return 0;
#endif
	rdmsr(MSR_PERF_CTL, &msr_lo, &msr_hi);
	msr_lo = (msr_lo & ~0xffff) |
		MSRVALUE(est_fqlist->table[i].mhz, est_fqlist->table[i].mv);
	wrmsr(MSR_PERF_CTL, msr_lo, msr_hi);
	return 0;
}

/*
 * Get CPU performance
 */
int
cpu_getperf(void)
{
	int max_mhz;
	int level;

	ASSERT(cpu_info.clock_ctrl);

	max_mhz = est_fqlist->table[0].mhz;
	ASSERT(max_mhz);
	level = cpu_stat.speed * 100 / max_mhz;
	return level;
}

/*
 * Initialize CPU performance
 * Return false on error.
 */
int
cpu_initperf(void)
{
	int i, j, n, mhz, mv;
	const struct est_cpu *cpu;
	u_int msr_lo, msr_hi;
	char *tag, *brand_str;
	const struct fqlist *fql;

	if (!cpu_info.clock_ctrl)
		return -1;

#ifdef CONFIG_DVS_EMULATION
	if (bochs) {
		msr_lo = 0x1031;
		cpu = &est_cpus[0];
		est_fqlist = &cpu->list[7];
	} else
		rdmsr(MSR_PERF_STATUS, &msr_lo, &msr_hi);
#else
	rdmsr(MSR_PERF_STATUS, &msr_lo, &msr_hi);
#endif

	mhz = MSR2MHZ(msr_lo);
	mv = MSR2MV(msr_lo);
	DPRINTF(("Enhanced SpeedStep %d MHz (%d mV)\n", mhz, mv));

#ifdef CONFIG_DVS_EMULATION
	if (!bochs) {
#endif
	/*
	 * Look for a CPU matching brand_str.
	 */
	brand_str = cpu_info.name;
	for (i = 0; est_fqlist == NULL && i < NESTCPUS; i++) {
		cpu = &est_cpus[i];
		n = strnlen(cpu->brand_prefix, 48);
		if (strncmp(cpu->brand_prefix, brand_str, n) != 0)
			continue;
		tag = brand_str + n;
		for (j = 0; j < cpu->n; j++) {
			fql = &cpu->list[j];
			n = strnlen(fql->brand_tag, 48);
			if (!strncmp(fql->brand_tag, tag, n) &&
			    !strncmp(cpu->brand_suffix, tag + n, 48)) {
				est_fqlist = fql;
				break;
			}
		}
	}
	if (est_fqlist == NULL) {
		DPRINTF(("Unknown EST cpu, no changes possible\n"));
		cpu_info.clock_ctrl = 0;
		return -1;
	}

	/*
	 * Check that the current operating point is in our list.
	 */
	for (i = est_fqlist->n - 1; i >= 0; i--)
		if (est_fqlist->table[i].mhz == mhz)
			break;
	if (i < 0) {
		DPRINTF((" (not in table)\n"));
		cpu_info.clock_ctrl = 0;
		return -1;
	}
#ifdef CONFIG_DVS_EMULATION
	}
#endif
	/*
	 * Store current state
	 */
	cpu_info.speed = est_fqlist->table[0].mhz;
	cpu_info.power = est_fqlist->table[0].mv;
	cpu_stat.speed = mhz;
	cpu_stat.power = mv;

	/*
	 * OK, tell the user the available frequencies.
	 */
#ifdef DEBUG
	printf("Speeds: ");
	for (i = 0; i < est_fqlist->n; i++)
		printf("%d%s", est_fqlist->table[i].mhz,
		       i < est_fqlist->n - 1 ? ", " : " MHz\n");
#endif
	return 0;
}

static int
cpu_ioctl(device_t dev, u_long cmd, void *arg)
{

	switch (cmd) {
	case CPUIOC_GET_INFO:
		if (umem_copyout(&cpu_info, arg, sizeof(cpu_info)))
			return EFAULT;
		break;
	case CPUIOC_GET_STAT:
		if (umem_copyout(&cpu_stat, arg, sizeof(cpu_stat)))
			return EFAULT;
		break;
	default:
		return EINVAL;
	}
	return 0;
}

/*
 * Initialize CPU addon feature
 *
 * FIXME: i486 does not support cpuid instruction
 */
static int
cpu_init(void)
{
	u_int regs[4];
	char brand_str[49];
	char *p, *q;

	/* Create device object */
	cpu_dev = device_create(&cpu_io, "cpu", DF_CHR);
	ASSERT(cpu_dev);

#ifdef CONFIG_DVS_EMULATION
	bochs = 0;
	if (inb(0xe9) == 0xe9) {
		/*
		 * Detect Bochs. Fake the cpuid value.
		 */
		bochs = 1;
		cpu_info.id = 0x6d6;
		cpu_info.clock_ctrl = 1;
		strncpy(cpu_info.name,
			"Intel(R) Pentium(R) M processor 1600MHz", 50);

		DPRINTF(("CPU ID: %08x\n", cpu_info.id));
		DPRINTF(("CPU brand: %s\n", cpu_info.name));
		return 0;
	}
#endif
	/*
	 * Check enhanced speed step capability
	 */
	cpuid(1, regs);
	cpu_info.id = regs[0];
	DPRINTF(("CPU ID: %08x\n", regs[0]));

	if ((regs[2] & 0x80) == 0) {
		DPRINTF(("cpu: Clock control not supported\n"));
		cpu_info.clock_ctrl = 0;
		return 0;
	}
	cpu_info.clock_ctrl = 1;

	/*
	 * Get CPU brand string
	 */
	cpuid(0x80000002, regs);
	memcpy(brand_str, regs, sizeof(regs));
	cpuid(0x80000003, regs);
	memcpy(brand_str + 16, regs, sizeof(regs));
	cpuid(0x80000004, regs);
	memcpy(brand_str + 32, regs, sizeof(regs));

	/* Store string with lef-align */
	q = &cpu_info.name[0];
	p = brand_str;
	while (*p == ' ')
		p++;
	while (*p)
		*q++ = *p++;
	*q = '\0';

	DPRINTF(("CPU brand: %s\n", cpu_info.name));
	return 0;
}