/**
* $Id: common.c 757 2012-07-01 06:02:26Z elkner $
*
* AMD K8+ sensor processing. Called k10sensor, since K10 is the first
* microarch, which provides reliable sensor data (with some exceptions - see
* erratum #319).
*
* Work is solely based on the information found in the
* AMD BIOS and Kernel Developers Guides (BKDG), Revision Guides, Power and
* Thermal Data Sheets (PTDS) and Functional Data Sheets (FDS). See:
* http://developer.amd.com/documentation/guides/Pages/default.aspx
*
*
* * CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* All Rights reserved.
* CDDL HEADER END
*/
/*
* Copyright (c) 2011,2012 by Jens Elkner,
* Otto-von-Guericke Universitaet Magdeburg. All rights reserved.
*/
#ifndef _KERNEL
#include <strings.h>
#endif
#include "k10sensor.h"
/* Function 03 stuff */
#define THERMTRIP_STATUS_REG 0xe4
#define NB_CAPABILITIES_REG 0xe8
#define HW_THERMAL_CTRL_REG 0x64
#define SW_THERMAL_CTRL_REG 0x68
#define LH_THERMAL_CTRL_REG 0x138
#define REPORTED_TEMPERATURE_REG 0xa4
#define AMD_BRANDING(ebx) ((ebx) & 0x0000ffff)
/** Set the category index (v->catIdx) wrt. cat_range_map.
* @param v cpu vars: requires familyModel
*/
void
fillCategoryIndex(cpu_vars_t *v) {
uint32_t i;
uint32_t max = sizeof(cat_range_map)/sizeof(struct cat_range);
uint32_t xfm = v->xfamilyModel;
for (i = 0; i < max; i++) {
if (xfm < cat_range_map[i].nextFamilyModel) {
v->catIdx = i;
xfm = NULL;
break;
}
}
if (xfm != NULL) {
v->catIdx = max - 1;
}
}
/*
* Set the silicon version string (v->revision).
*
* @param v cpu vars: requires xfamilyModel, modelStepping
*/
void
fillSiliconRevision(cpu_vars_t *v) {
uint32_t stepping = v->modelStepping & 0xf;
v->revision[0] = v->revision[5] = '\0'; /* just to be sure */
if (v->xfamilyModel < 4) {
/* Athlon64 + Opteron RevGuide - Last Update: 07/20/2009 3.79
* @@
* BH-E4: 02-B1 939
* CH-CG: 00-82,00-B2 754,939
* CH-D0: 01-80,01-B0 754,939
* DH-CG: 00-C0,00-E0,00-F0 754,754,939
* DH-D0: 01-C0,01-F0 754,939
* DH-E3: 02-C0,02-F0 754,939
* DH-E6: 02-C2,02-F2 754,939
* JH-E1: 02-10 940
* JH-E6: 02-12,02-32 940,939
* SH-B0: 00-40,00-50 754,940
* SH-B3: 00-51 940
* SH-C0: 00-48,00-58 754,940
* SH-CG: 00-4A,00-5A,00-7A 754,940,939
* SH-D0: 01-40,01-50,01-70 754,940,939
* SH-E4: 02-51,02-71 940,939
* SH-E5: 02-42 754
* ?H-E4: 03-72 ?
* ?H-E6: 03-F2 ?
* Sockets: 940pin, 939pin, 754pin
*/
if (v->xfamilyModel == 0x03) {
strcpy(v->revision, v->modelStepping < 0xF0 ? "?H-E4" : "?H-E6");
} else if (v->modelStepping < 0x40) {
strcpy(v->revision, "JH-E6"); /* JH-* */
if (stepping < 0x2) {
v->revision[4] = '1';
}
} else if (v->modelStepping < 0x80) {
strcpy(v->revision, "SH-B0"); /* SH-* */
if (v->xfamilyModel == 0x02) {
v->revision[3] = 'E';
v->revision[4] = stepping < 0x2 ? '4' : '5';
} else if (v->xfamilyModel == 0x01) {
v->revision[3] = 'D';
} else /* 00h */ if (stepping >= 0xA) {
v->revision[3] = 'C';
v->revision[4] = 'G';
} else if (stepping >= 0x8) {
v->revision[3] = 'C';
} else if (stepping >= 0x1) {
v->revision[4] = '3';
}
} else if (v->modelStepping == 0xB1) {
strcpy(v->revision, "BH-E4"); /* BH-* */
} else if (v->modelStepping < 0xC0) {
strcpy(v->revision, stepping == 0 ? "CH-D0" : "CH-CG"); /* CH-* */
} else /* DH-* */ if (v->xfamilyModel == 0) {
strcpy(v->revision, "DH-CG");
} else if (v->xfamilyModel == 1) {
strcpy(v->revision, "DH-D0");
} else {
strcpy(v->revision, stepping == 0 ? "DH-E3" : "DH-E6");
}
} else if (v->xfamilyModel < 0x10) {
/* 0Fh RevGuide - Last Update: 09/22/2009 3.46
* @@
* BH-F2: 04-82,04-B2 S1g1,AM2dc
* BH-G1: 06-81,06-B1 S1g1,AM2dc
* BH-G2: 06-82,06-B2 S1g1,AM2dc|ASB1
* JH-F2: 04-12,04-32 F,AM2dc
* JH-F3: 04-13,04-33,05-D3,0C-13 F,AM2dc,F,Fr3
* DH-F2: 04-C2,04-F2,05-F2 S1g1,AM2sc,AM2sc
* DH-F3: 05-F3 AM2sc
* DH-G1: 07-F1 AM2sc
* DH-G2: 06-C2,06-F2,07-C2,07-F2 S1g1,AM2sc|ASB1,S1g1,AM2sc|ASB1
* Sockets: F, Fr3, AM2 (dc=DualCore|sc=SingleCore), ASB1, S1g1 (638pin)
*/
strcpy(v->revision, "JH-F3");
v->revision[4] = '0' + stepping;
if (v->xfamilyModel != 0x0C) {
if (v->xfamilyModel > 0x05) {
v->revision[3] = 'G';
}
/* now the remaining first letter */
switch (v->modelStepping & 0xf0) {
case 0xB0:
case 0x80:
v->revision[0] = 'B'; break;
case 0xC0:
case 0xF0:
v->revision[0] = 'D'; break;
}
}
} else if (v->xfamilyModel == 0x10) {
/* 10h RevGuide - Last Update: 3/21/2012 3.92
* @@ Rev. cnames
* DR-B1: 10-21 Fr2|AM2r2 :B
* DR-B2: 10-22 Fr2|AM2r2
* DR-B3: 10-23 Fr2|AM2r2
* DR-BA: 10-2A Fr2
* RB-C2: 10-42 Fr2|Fr5|AM2r2|AM3 :C
* BL-C2: 10-52 AM3
* DA-C2: 10-62 AM3|S1g3 :DA-C
* RB-C3: 10-43 AM3 :C3
* BL-C3: 10-53 AM3|S1g4
* DA-C3: 10-63 AM3|S1g4|ASB2
* HY-D0: 10-80 Fr6|C32r1 :D
* HY-D1: 10-81,10-91 C32r1,G34r1 :D1
* PH-E0: 10-A0 AM3 :E
* Sockets: Fr2, Fr5, Fr6, AM2r2, AM3, G34r1, C32r1, ASB2, S1g3, S1g4
*/
switch(v->modelStepping & 0xf0) {
case 0x20: strcpy(v->revision, "DR-B"); break;
case 0x40: strcpy(v->revision, "RB-C"); break;
case 0x50: strcpy(v->revision, "BL-C"); break;
case 0x60: strcpy(v->revision, "DA-C"); break;
case 0x80:
case 0x90: strcpy(v->revision, "HY-D"); break;
case 0xA0: strcpy(v->revision, "PH-E"); break;
default: strcpy(v->revision, "??-?"); break;
}
v->revision[4] = stepping < 10 ? '0' + stepping : 'A' + stepping;
} else if (v->xfamilyModel == 0x20) {
/* 11h RevGuide - Last Update: 12/16/2011 3.10
* @@
* LG-B1: 20-31 S1g2
* Sockets: S1g2
*/
strcpy(v->revision, "LG-B1");
} else if (v->xfamilyModel == 0x30) {
/* 12h RevGuide - Last Update: 03/21/2012 3.10
* @@
* LN-B0: 30-10 FS1|FM1
* Sockets: FS1, FM1
*/
strcpy(v->revision, "LN-B0");
} else if (v->xfamilyModel == 0x50) {
/* 14h RevGuide - Last Update: 03/29/2012 3.12
* @@
* ON-B0: 50-10 FT1
* ON-C0: 50-20 FT1
* Sockets: FT1
*/
strcpy(v->revision, "ON-B0");
if (v->modelStepping == 0x20) {
v->revision[3] = 'C';
}
} else if (v->xfamilyModel == 0x60) {
/* 15h RevGuide - Last Update: 11/14/2011 3.04
* @@
* OR-B2: 60-12 AM3r2|C32r1|G34r1
* Sockets: AM3r2, C32r1, G34r1
*/
strcpy(v->revision, "OR-B2");
} else {
strcpy(v->revision, "??-??");
}
}
/**
* Check, whether an AMD erratum applies, which indicates, that the obtained
* temperature values are inaccurate and thus should not be used for overheat
* detection. If none applies, v->erratum gets set to 0, to the related erratum
* number otherwise.
*
* Erratum 141 applies to all revisions of 0x0Fh.
*
* Erratum 319 applies to some revisions of 0x10h.
* For now (Feb-2011) these are: DR-BA, DR-B2, DR-B3, RB-C2, HY-D0
* and these are not: BL-C2, DA-C2, RB-C3, BL-C3, DA-C3, HY-D1, PH-E0
*
* @param v cpu vars: requires xfamilyModel, modelStepping, ebx
*/
void
fillErratum(cpu_vars_t *v) {
uint32_t pkgType = BITS(v->ebx, 31, 28);
v->erratum = 0;
if (v->xfamilyModel >= 0x4 && v->xfamilyModel < 0x10) {
/* 0Fh */
v->erratum = 141;
}
if (v->xfamilyModel != 0x10) {
return;
}
/* AMD suggestion to find affected revisions:
* (((CPUID Fn8000_0001_EBX[PkgType, bits 31:28] == 1 (AM2r2 or AM3))
* && (F2x[1, 0]94[Ddr3Mode, bit 8] == 0))
* || (CPUID Fn8000_0001_EBX[31:28] == 0 (Fr{2|5|6}(1207))))
*/
if (pkgType == 0) { /* AMD_SOCKET_F */
v->erratum = 319;
} else if (pkgType == 1) { /* AMD_SOCKET_AM2R2_OR_AM3) */
/* remaining: DR-B2, DR-B3 (AM2r2); RB-C2 (AM2r2, AM3)
* AMD suggests to deduce: DDR2 usage == affected, however AM3 can use
* DDR2 as well, and even if DDR3 is in use, it would not catch RB-C2.
* Just in case we change our mind, we need to check:
* Func2, AMD_DRAM_CONFIG_HIGH_REG 0x94, AMD_DDR3_MODE_MASK (1 << 8)
*/
if ((v->modelStepping & 0xf0) == 0x20 || v->modelStepping == 0x42) {
v->erratum = 319;
}
} else if ((pkgType == 5) /* AMD_SOCKET_C32 */
&& ((v->modelStepping & 0xf) == 0)) /* HY-D0 (C32r1) */
{
v->erratum = 319;
}
}
/**
* Fill in thermtrip register relevant data. Depending on the architecture
* this includes v->{t_case_max, diode_offset, flags[HTC_THERMTRIP]}.
*
* @param pci_hdl handle to the PCI device to query.
* @param v cpu vars: requires xfamilyModel, modelStepping, revision, ebx
*/
void
fillThermtrip(PCI_HDL pci_hdl, cpu_vars_t *v) {
uint32_t data = 0;
int32_t offset = 0;
v->t_case_max = INT32_MAX;
v->diode_offset = 0;
v->flags &= ~HTC_THERMTRIP;
PCI_GET32(pci_hdl, data, THERMTRIP_STATUS_REG, return;)
/* RO: Thermtrip enabled ? */
if (BITS(data, 5, 5)) {
v->flags |= HTC_THERMTRIP;
}
/* T case max. The only place, where we really need it, is to determine
* whether we need to correct the diode offset for JH-E6 chips below */
if (v->xfamilyModel == 0x20) {
/* 11h: TCaseDie */
v->t_case_max = 100 * 1000;
} else if (v->xfamilyModel < 0x04) {
if (v->revision[3] < 'D') {
/* Athlon64/Opteron < rev. D: fix as stated in PTDS */
if (v->revision[3] < 'C') {
v->t_case_max = 69 * 1000;
} else if (v->revision[4] == 'G' && v->modelStepping == 0x7A
&& (BITS(v->ebx,15,6) == 0x24 && BITS(v->ebx,5,0) == (55-24)))
{
v->t_case_max = 63 * 1000; /* Athlon FX55 */
} else {
/* in most cases it is right, but not always - use with care */
v->t_case_max = 70 * 1000;
}
} else {
/* Athlon64/Opteron rev D, E */
offset = BITS(data, 28, 25) << 1;
v->t_case_max = (offset != 0) ? ((offset + 49) * 1000) : INT32_MAX;
}
}
/* RO: Diode offset */
if (v->xfamilyModel > 0x20) {
/* > 11h */
offset = 0;
} else if (v->xfamilyModel >= 0x10) {
/* 10h, 11h {-52..10C} */
offset = BITS(data, 14, 8);
if (offset == 0 || offset >= 0x40) {
offset = 0;
} else {
offset = (11 - offset) * 1000;
}
} else if (v->xfamilyModel >= 0x04) {
/* 0Fh: BDGK says, offset needs to be added - so we change the sign */
uint32_t brandIdx = BITS(v->ebx, 13, 9);
uint32_t pwrLmt = BITS(v->ebx, 3, 0);
offset = BITS(v->modelStepping, 5, 4);
if (offset == 0) {
/* S1g1 aka S1 == lidless {0..32C} */
offset = BITS(data, 28, 24) * -1000;
} else if (offset == 3 && ((brandIdx > 0xA)
|| (brandIdx == 0x09 && pwrLmt == 0x01)
|| (brandIdx == 0x07 && pwrLmt < 0x03)))
{ /* ABS1 == lidless {0..32C} */
offset = BITS(data, 28, 24) * -1000;
} else {
/* lidded {-52..10C} */
offset = BITS(data, 13, 8);
if (offset != 0) {
offset = (11 - offset) * -1000;
}
}
} else {
/* Athlon64/Opteron */
/* 940pin FDS Note6: for Rev < 'CG' _accuraccy_ (after applying
* possible ideality factor) is +- 10 degrees C. */
offset = - BITS(data, 13, 8);
if ((v->revision[3] >= 'D') && BITS(data, 24, 24)) {
/* obey sign bit */
offset *= -1000;
} else {
offset *= 1000;
}
/* DC Opteron 150,165,170,175,180: erratum 154 (JH-E6) workaround */
if ((BITS(v->ebx, 11, 0) == 0xC2) && v->t_case_max <= 2000) {
offset += 10000;
}
}
v->diode_offset = offset;
}
/**
* Check Northbridge register and populate
* v->flags[HTC_CAPABLE,LHTC_CAPABLE,DC_0Fh].
*
* @param pci_hdl handle to the PCI device to query.
* @param v cpu vars: requires xfamilyModel
*/
void
fillNBcapabilities(PCI_HDL pci_hdl, cpu_vars_t *v)
{
uint32_t data = 0;
v->flags &= ~HTC_CAPABLE;
v->flags &= ~LHTC_CAPABLE;
v->flags &= ~DC_0Fh;
PCI_GET32(pci_hdl, data, NB_CAPABILITIES_REG, return;)
/* 0Fh, RO: DualCore Capability */
if (v->xfamilyModel >= 0x04 && v->xfamilyModel < 0x10
&& BITS(data, 13, 12) > 0)
{
v->flags |= DC_0Fh;
}
/* 0Fh+, RO: HTC capable */
if (v->xfamilyModel >= 0x04 && BITS(data, 10, 10)) {
v->flags |= HTC_CAPABLE;
}
/* 12h, 14h, RO: LHTC capable */
if ((v->xfamilyModel == 0x30 || v->xfamilyModel == 0x50)
&& BITS(data, 28,28))
{
v->flags |= LHTC_CAPABLE;
}
}
/**
* Check the HTC register and populate v->limits[HTC].
*
* @param pci_hdl handle to the PCI device to query.
* @param v cpu vars: requires xfamilyModel, modelStepping, revision, ebx,
* flags[HTC_CAPABLE]
*/
void
fillHtc(PCI_HDL pci_hdl, cpu_vars_t *v) {
uint32_t data = 0;
tc_limit_t *limits = &v->limits[HTC];
limits->flags = 0;
limits->tcrit = limits->threshold = INT32_MAX;
if (v->xfamilyModel < 0x04) {
/* Athlon64/Opteron has no HTC register. However for some CG and C0
versions Tcrit is specified in the PTDS. */
uint32_t branding = AMD_BRANDING(v->ebx);
if (v->revision[3] == 'C') {
switch (v->revision[4]) {
case 'G':
/* AthlonFX53 = 70 and AthlonFX55 = 63 */
if (v->modelStepping == 0x7A && (branding >> 6) == 0x24
&& (branding & 0x3f) == (55-24))
{
limits->tcrit = 63 * 1000;
} else {
limits->tcrit = 70 * 1000;
}
break;
case '0':
/* Wrt. C0 it is specified for Athlon64/FX */
if ((branding >> 6) == 0x4A || ((branding >> 6) == 0x24)) {
limits->tcrit = 70 * 1000;
}
break;
}
} else if ((v->revision[3] > 'C') && ((branding >> 6) != 0x0B)) {
/* but not specified for Turion64 */
limits->tcrit = 70 * 1000;
}
return;
}
/* 0Fh+ */
if ((v->flags & HTC_CAPABLE) == 0) {
/* not allowed to access HW_THERMAL_CTRL_REG if uncapable */
return;
}
PCI_GET32(pci_hdl, data, HW_THERMAL_CTRL_REG, return; )
/* RW: HTC enabled */
if (BITS(data, 0, 0)) {
limits->flags |= TC_ENABLED;
}
if (v->xfamilyModel < 0x10) {
/* 0Fh has no further HTC info */
return;
}
/* 10h+, RW: slew-controlled temperature */
if (BITS(data, 23, 23)) {
limits->flags |= TC_SLEWED;
}
/* RW: HTC temperature limit */
limits->tcrit = 52000 + BITS(data, 22, 16) * 500; /* 52 + val/2 */
/* RW: HTC hysteresis */
limits->threshold = limits->tcrit - BITS(data, 27, 24) * 500;
}
/**
* Check the STC or LHTC register and populate v->limits[STC].
*
* @param pci_hdl handle to the PCI device to query.
* @param v cpu vars: requires xfamilyModel, flags[HTC_CAPABLE,LHTC_CAPABLE]
*/
void
fillStc(PCI_HDL pci_hdl, cpu_vars_t *v)
{
uint32_t data = 0, val;
tc_limit_t *limits = &v->limits[STC];
limits->flags = 0;
limits->tcrit = limits->threshold = INT32_MAX;
/* Athlon64/Opteron, 11h and 15h don't have a STC/LHTC,
* for 0Fh, 10h it must be HTC_CAPABLE,
* for 12h, 14h it must be LHTC_CAPABLE */
if (v->xfamilyModel < 0x04 || v->xfamilyModel == 0x20 || v->xfamilyModel == 0x60
|| (v->xfamilyModel <= 0x10 && (v->flags & HTC_CAPABLE) == 0)
|| (v->xfamilyModel >= 0x30 && (v->flags & LHTC_CAPABLE) == 0))
{
return;
}
PCI_GET32(pci_hdl, data,
v->xfamilyModel > 0x20 ? LH_THERMAL_CTRL_REG : SW_THERMAL_CTRL_REG,
return; )
if (v->xfamilyModel < 0x10) {
/* 0Fh, RW: STC Temperature Limit */
val = BITS(data, 20, 16);
if (val != 0) {
limits->tcrit = 52000 + val * 2000;
/* RW: STC Hysteresis Limit */
limits->threshold = limits->tcrit - (BITS(data, 27, 24) + 1) * 2000;
limits->flags |= TC_ENABLED;
}
} else if (v->xfamilyModel == 0x10) {
/* 10h, RW: STC temperature limit */
val = BITS(data, 22, 16);
if (val != 0) {
limits->tcrit = 52000 + val * 500;
/* RW: STC hysteresis */
limits->threshold = limits->tcrit
- BITS(data, 27, 24) * ((v->revision[3] < 'C') ? 500 : 1000);
limits->flags |= TC_ENABLED;
}
/* RW: STC slew-controlled temperature */
if (BITS(data, 23, 23)) {
limits->flags |= TC_SLEWED;
}
} else {
/* 12h, 14h, RW: local HTC temperature limit */
limits->tcrit = 52000 + BITS(data, 22, 16) * 500;
/* RW: local HTC hysteresis */
limits->threshold = limits->tcrit - BITS(data, 27, 24) * 500;
/* RW: local HTC enable */
if (BITS(data, 0, 0)) {
limits->flags |= TC_ENABLED;
}
/* RW: local HTC slew-controlled temperature */
if (BITS(data, 23, 23)) {
limits->flags |= TC_SLEWED;
}
}
}
/**
* Get the current raw control temperature (Tctl). I.e. to get the final
* control temperature, which can be used e.g. to compare against T control max
* etc., one needs to substract v->diode_offset from the returned value.
*
* @param pci_hdl handle to the PCI device to query.
* @param v cpu vars: requires flags[DC_0Fh], xfamilyModel, revision
* @param core1 if {@code B_TRUE} read sensor data from core1, otherwise from
* core0 (ignored if not a family 0Fh DualCore CPU).
* @see v->flags & DC_0Fh
* @return 0 if n/a or an error occured, the control temperature in 1/1000 C
* otherwise.
*/
int32_t
getControlTemp(PCI_HDL pci_hdl, cpu_vars_t *v, boolean_t core1)
{
uint32_t data = 0;
if (v->xfamilyModel >= 0x10) {
PCI_GET32(pci_hdl, data, REPORTED_TEMPERATURE_REG, return 0;)
return (BITS(data, 31, 21)) * 125;
}
if (v->xfamilyModel < 0x04) {
/* n/a for < 0Fh */
return data;
}
PCI_GET32(pci_hdl, data, THERMTRIP_STATUS_REG, return 0;)
/* F0h */
/* Bit: 2 = core selection (0 == core0, 1 == core1) */
/* Bit: 6 = sensor selection (0 == sensor0, 1 == sensor1) */
/* never saw a 2nd sensor, so we always take sensor0 */
if (v->flags & DC_0Fh) {
uint32_t mask = core1 ? (1 << 2) : 0;
if ((data & ((1<<6) || (1<<2))) != mask) {
/* select the intended core and refresh */
data = (data & ~((1<<6) | (1<<2))) | mask;
PCI_PUT32(pci_hdl, data, THERMTRIP_STATUS_REG, return 0;)
PCI_GET32(pci_hdl, data, THERMTRIP_STATUS_REG, return 0;)
}
}
if (v->revision[3] >= 'G') {
data = BITS(data, 23, 14) * 250;
} else {
data = BITS(data, 23, 16) * 1000;
}
if (data != 0) {
data -= 49000;
}
return data;
}
#define FORMAT "%22s:\t%s\n"
#define FORMAT_N "%22s:\t%d.%d\n"
#define PRINT_YORN(buf, what, val) \
sprintf((buf), FORMAT, (what), (val) ? "yes" : "no");
#define PRINT_ENABLED(buf, what, val) \
sprintf((buf), FORMAT, (what), (val) ? "enabled" : "disabled");
#define PRINT_ENABLED_NA(buf, what, val) \
sprintf((buf), FORMAT, (what), (val) ? "enabled" : "n/a");
#define PRINT_VAL(buf, what, val) \
if ((val) == INT32_MAX) { \
sprintf((buf), FORMAT, (what), "n/a"); \
} else { \
int32_t x = (val); \
x += x < 0 ? -50 : + 50; \
x /= 100; \
sprintf((buf), FORMAT_N, (what), x/10, (x < 0 ? -x : x)%10); \
}
/**
* Dump all values of the given parameter as a human readable string by
* appending them to the given buffer. The buffer is expected to have at least
* space for 800 chars.
* @param buf where to append the human readable string
* @param v cpu data to dump
* @return pointer to the end of the appended string (the NULL char)
*/
char *
dumpCpuInfo(char *buf, cpu_vars_t *v) {
int stc;
if (v == NULL || buf == NULL) {
return buf;
}
buf[0] = '\0';
strcat(buf, "########################################################\n");
buf += strlen(buf);
sprintf(buf, "CPU #%d", v->chipId);
if (v->erratum > 0) {
buf += strlen(buf);
sprintf(buf, " INACCURATE TEMPERATURE VALUES (erratum %d)!",
v->erratum);
}
buf += strlen(buf);
strcat(buf, "\n########################################################\n");
buf += strlen(buf);
sprintf(buf, FORMAT, "Silicon CPU Revision", v->revision);
buf += strlen(buf) - 1;
sprintf(buf, " (%02x-%02x)\n", v->xfamilyModel, v->modelStepping);
buf += strlen(buf);
sprintf(buf, FORMAT, "Family", cat_range_map[v->catIdx].name);
buf += strlen(buf) - 1;
sprintf(buf, " (ebx=0x%08x)\n", v->ebx);
buf += strlen(buf);
buf[0] = '\n'; buf++; buf[0] = '\0';
PRINT_ENABLED_NA(buf, "Thermtrip", v->flags & HTC_THERMTRIP)
buf += strlen(buf);
PRINT_YORN(buf, "HTC capable", v->flags & HTC_CAPABLE)
buf += strlen(buf);
PRINT_VAL(buf, "T Case/Die Max [C]", v->t_case_max)
buf += strlen(buf);
PRINT_VAL(buf, "T Diode Offset [C]", v->diode_offset)
buf += strlen(buf);
buf[0] = '\n'; buf++; buf[0] = '\0';
PRINT_ENABLED(buf, "HTC", v->limits[HTC].flags & TC_ENABLED)
buf += strlen(buf);
PRINT_YORN(buf, "HTC slew-controlled", v->limits[HTC].flags & TC_SLEWED)
buf += strlen(buf);
PRINT_VAL(buf, "HTC Limit [C]", v->limits[HTC].tcrit)
buf += strlen(buf);
PRINT_VAL(buf, "HTC Hysteresis [C]", v->limits[HTC].threshold)
buf += strlen(buf);
buf[0] = '\n'; buf++; buf[0] = '\0';
stc = v->xfamilyModel != 0x30 && v->xfamilyModel != 0x50;
PRINT_ENABLED(buf, stc ? "STC" : "LHTC", v->limits[STC].flags & TC_ENABLED)
buf += strlen(buf);
PRINT_YORN(buf, stc ? "STC slew-controlled" : "LHTC slew-controlled",
v->limits[STC].flags & TC_SLEWED)
buf += strlen(buf);
PRINT_VAL(buf, stc ? "STC Limit [C]" : "LHTC Limit [C]",
v->limits[STC].tcrit)
buf += strlen(buf);
PRINT_VAL(buf, stc ? "STC Hysteresis [C]" : "LHTC Hysteresis [C]",
v->limits[STC].threshold)
return (buf + strlen(buf));
}
/**
* Dump the current final T control values as a human readable string by
* appending them to the given buffer. The buffer is expected to have at least
* space for 80 chars.
* @param buf where to append the human readable string
* @param pci_hdl handle to the PCI device to query.
* @param v cpu data to use to obtain/calculate T control values
* @return pointer to the end of the appended string (the NULL char)
*/
char *
dumpTctrl(char *buf, PCI_HDL pci_hdl, cpu_vars_t *v) {
int32_t t;
if (v == NULL || buf == NULL || v == NULL) {
return buf;
}
buf[0] = '\0';
if (v->xfamilyModel < 0x04) {
t = INT32_MAX; /* n/a */
PRINT_VAL(buf, "T control [C]", t)
} else if (v->flags & DC_0Fh) {
/* 0Fh DualCore */
t = getControlTemp(pci_hdl, v, 0);
if (t == 0) { t == INT32_MAX; } else { t -= v->diode_offset; }
PRINT_VAL(buf, "T control Core 0 [C]", t)
buf += strlen(buf);
t = getControlTemp(pci_hdl, v, 1);
if (t == 0) { t == INT32_MAX; } else { t -= v->diode_offset; }
PRINT_VAL(buf, "T control Core 1 [C]", t)
} else {
/* 10h + or 0Fh single core */
t = getControlTemp(pci_hdl, v, 0);
if (t == 0) { t == INT32_MAX; } else { t -= v->diode_offset; }
PRINT_VAL(buf, "T control [C]", t)
}
return (buf + strlen(buf));
}
/* vim: tabstop=4:shiftwidth=4:noexpandtab
*/