p4tcc.c revision 126412
1141296Sdas/* $OpenBSD: p4tcc.c,v 1.1 2003/12/20 18:23:18 tedu Exp $ */ 2141296Sdas/* 32116Sjkh * Copyright (c) 2003 Ted Unangst 42116Sjkh * Copyright (c) 2004 Maxim Sobolev <sobomax@FreeBSD.org> 52116Sjkh * All rights reserved. 62116Sjkh * 7141296Sdas * Redistribution and use in source and binary forms, with or without 82116Sjkh * modification, are permitted provided that the following conditions 9141296Sdas * are met: 102116Sjkh * 1. Redistributions of source code must retain the above copyright 112116Sjkh * notice, this list of conditions and the following disclaimer. 12141296Sdas * 2. Redistributions in binary form must reproduce the above copyright 132116Sjkh * notice, this list of conditions and the following disclaimer in the 142116Sjkh * documentation and/or other materials provided with the distribution. 15176385Sbde * 16176385Sbde * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 172116Sjkh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 182116Sjkh * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19141296Sdas * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20141296Sdas * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 212116Sjkh * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 222116Sjkh * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 232116Sjkh * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24176465Sbde * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25176465Sbde * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 262116Sjkh * SUCH DAMAGE. 272116Sjkh */ 282116Sjkh/* 292116Sjkh * Restrict power consumption by using thermal control circuit. 302116Sjkh * This operates independently of speedstep. 312116Sjkh * Found on Pentium 4 and later models (feature TM). 322116Sjkh * 332116Sjkh * References: 342116Sjkh * Intel Developer's manual v.3 #245472-012 352116Sjkh * 362116Sjkh * On some models, the cpu can hang if it's running at a slow speed. 372116Sjkh * Workarounds included below. 382116Sjkh */ 398870Srgrimes 402116Sjkh#include <sys/cdefs.h> 412116Sjkh__FBSDID("$FreeBSD: head/sys/i386/cpufreq/p4tcc.c 126412 2004-02-29 18:30:35Z maxim $"); 422116Sjkh 432116Sjkh#include "opt_cpu.h" 442116Sjkh#include <sys/param.h> 452116Sjkh#include <sys/systm.h> 462116Sjkh#include <sys/kernel.h> 472116Sjkh#include <sys/conf.h> 482116Sjkh#include <sys/power.h> 492116Sjkh#include <sys/sysctl.h> 502116Sjkh#include <sys/types.h> 51176385Sbde 52176385Sbde#include <machine/md_var.h> 53176385Sbde#include <machine/specialreg.h> 54176385Sbde 55176385Sbdestatic u_int p4tcc_percentage; 562116Sjkhstatic u_int p4tcc_economy; 572116Sjkhstatic u_int p4tcc_performance; 582116Sjkhstatic struct sysctl_ctx_list p4tcc_sysctl_ctx; 592116Sjkhstatic struct sysctl_oid *p4tcc_sysctl_tree; 602116Sjkh 612116Sjkhstatic struct { 622116Sjkh u_short level; 632116Sjkh u_short rlevel; 64176409Sbde u_short reg; 652116Sjkh} tcc[] = { 662116Sjkh { 88, 100, 0 }, 67176409Sbde { 75, 88, 7 }, 68176409Sbde { 63, 75, 6 }, 69176409Sbde { 50, 63, 5 }, 70176409Sbde { 38, 50, 4 }, 71176409Sbde { 25, 38, 3 }, 72176409Sbde { 13, 25, 2 }, 73176409Sbde { 0, 13, 1 } 747659Sbde}; 757659Sbde 76176409Sbde#define TCC_LEVELS sizeof(tcc) / sizeof(tcc[0]) 77176409Sbde 78176409Sbdestatic u_short 797659Sbdep4tcc_getperf(void) 807659Sbde{ 81176409Sbde u_int64_t msreg; 827659Sbde int i; 83176409Sbde 84176409Sbde msreg = rdmsr(MSR_THERM_CONTROL); 85176409Sbde msreg = (msreg >> 1) & 0x07; 86176409Sbde for (i = 0; i < TCC_LEVELS; i++) { 87176409Sbde if (msreg == tcc[i].reg) 88176409Sbde break; 89176409Sbde } 90176409Sbde 91176409Sbde return (tcc[i].rlevel); 92176409Sbde} 93176409Sbde 94176409Sbdestatic void 957659Sbdep4tcc_setperf(u_int percentage) 967659Sbde{ 97176409Sbde int i; 98176409Sbde u_int64_t msreg; 99176409Sbde 100176409Sbde if (percentage > tcc[0].rlevel) 101176409Sbde percentage = tcc[0].rlevel; 102176409Sbde for (i = 0; i < TCC_LEVELS - 1; i++) { 103176409Sbde if (percentage > tcc[i].level) 104176409Sbde break; 105176409Sbde } 106176409Sbde 107176409Sbde msreg = rdmsr(MSR_THERM_CONTROL); 108176409Sbde msreg &= ~0x1e; /* bit 0 reserved */ 109176409Sbde if (tcc[i].reg != 0) 110176409Sbde msreg |= tcc[i].reg << 1 | 1 << 4; 111176409Sbde wrmsr(MSR_THERM_CONTROL, msreg); 112176409Sbde} 113176409Sbde 114176409Sbdestatic int 115176409Sbdep4tcc_perf_sysctl(SYSCTL_HANDLER_ARGS) 116176409Sbde{ 117176409Sbde u_int percentage; 118176409Sbde int error; 119176409Sbde 120176409Sbde p4tcc_percentage = p4tcc_getperf(); 121176409Sbde percentage = p4tcc_percentage; 122176409Sbde error = sysctl_handle_int(oidp, &percentage, 0, req); 123176409Sbde if (error || !req->newptr) { 124176409Sbde return (error); 125176409Sbde } 126176409Sbde if (p4tcc_percentage != percentage) { 127176409Sbde p4tcc_setperf(percentage); 1282116Sjkh } 129176409Sbde 1302116Sjkh return (error); 131176465Sbde} 132176465Sbde 133176465Sbdestatic void 134176465Sbdep4tcc_power_profile(void *arg) 135176465Sbde{ 136176465Sbde int state; 1372116Sjkh u_int new; 1382116Sjkh 139176465Sbde state = power_profile_get_state(); 1402116Sjkh if (state != POWER_PROFILE_PERFORMANCE && 1412116Sjkh state != POWER_PROFILE_ECONOMY) { 142176466Sbde return; 1432116Sjkh } 1442116Sjkh 145141296Sdas switch (state) { 1462116Sjkh case POWER_PROFILE_PERFORMANCE: 1472116Sjkh new = p4tcc_performance; 1482116Sjkh break; 1492116Sjkh case POWER_PROFILE_ECONOMY: 150141296Sdas new = p4tcc_economy; 1512116Sjkh break; 152141296Sdas default: 1532116Sjkh new = p4tcc_getperf(); 1542116Sjkh break; 1552116Sjkh } 1562116Sjkh 1572116Sjkh if (p4tcc_getperf() != new) { 158141296Sdas p4tcc_setperf(new); 1592116Sjkh } 160141296Sdas} 1612116Sjkh 1622116Sjkhstatic int 1632116Sjkhp4tcc_profile_sysctl(SYSCTL_HANDLER_ARGS) 1642116Sjkh{ 1652116Sjkh u_int32_t *argp; 1662116Sjkh u_int32_t arg; 1672116Sjkh int error; 1682116Sjkh 169141296Sdas argp = (u_int32_t *)oidp->oid_arg1; 1702116Sjkh arg = *argp; 1712116Sjkh error = sysctl_handle_int(oidp, &arg, 0, req); 1722116Sjkh 1732116Sjkh /* error or no new value */ 1742116Sjkh if ((error != 0) || (req->newptr == NULL)) 1752116Sjkh return (error); 1762116Sjkh 1772116Sjkh /* range check */ 1782116Sjkh if (arg > tcc[0].rlevel) 1792116Sjkh arg = tcc[0].rlevel; 1802116Sjkh 1812116Sjkh /* set new value and possibly switch */ 1822116Sjkh *argp = arg; 1832116Sjkh 1842116Sjkh p4tcc_power_profile(NULL); 1852116Sjkh 1862116Sjkh *argp = p4tcc_getperf(); 187176356Sdas 1882116Sjkh return (0); 1892116Sjkh} 1902116Sjkh 191static void 192setup_p4tcc(void *dummy __unused) 193{ 194 195 if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) != 196 (CPUID_ACPI | CPUID_TM)) 197 return; 198 199 switch (cpu_id & 0xf) { 200 case 0x22: /* errata O50 P44 and Z21 */ 201 case 0x24: 202 case 0x25: 203 case 0x27: 204 case 0x29: 205 /* hang with 12.5 */ 206 tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2]; 207 break; 208 case 0x07: /* errata N44 and P18 */ 209 case 0x0a: 210 case 0x12: 211 case 0x13: 212 /* hang at 12.5 and 25 */ 213 tcc[TCC_LEVELS - 1] = tcc[TCC_LEVELS - 2] = tcc[TCC_LEVELS - 3]; 214 break; 215 default: 216 break; 217 } 218 219 p4tcc_economy = tcc[TCC_LEVELS - 1].rlevel; 220 p4tcc_performance = tcc[0].rlevel; 221 222 p4tcc_percentage = p4tcc_getperf(); 223 printf("Pentium 4 TCC support enabled, current performance %u%%\n", 224 p4tcc_percentage); 225 226 sysctl_ctx_init(&p4tcc_sysctl_ctx); 227 p4tcc_sysctl_tree = SYSCTL_ADD_NODE(&p4tcc_sysctl_ctx, 228 SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "p4tcc", CTLFLAG_RD, 0, 229 "Pentium 4 Thermal Control Circuitry support"); 230 SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx, 231 SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO, 232 "cpuperf", CTLTYPE_INT | CTLFLAG_RW, 233 &p4tcc_percentage, 0, p4tcc_perf_sysctl, "I", 234 "CPU performance in % of maximum"); 235 SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx, 236 SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO, 237 "cpuperf_performance", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW, 238 &p4tcc_performance, 0, p4tcc_profile_sysctl, "I", 239 "CPU performance in % of maximum in Performance mode"); 240 SYSCTL_ADD_PROC(&p4tcc_sysctl_ctx, 241 SYSCTL_CHILDREN(p4tcc_sysctl_tree), OID_AUTO, 242 "cpuperf_economy", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_RW, 243 &p4tcc_economy, 0, p4tcc_profile_sysctl, "I", 244 "CPU performance in % of maximum in Economy mode"); 245 246 /* register performance profile change handler */ 247 EVENTHANDLER_REGISTER(power_profile_change, p4tcc_power_profile, NULL, 0); 248} 249SYSINIT(setup_p4tcc, SI_SUB_CPU, SI_ORDER_ANY, setup_p4tcc, NULL); 250