378 lines
14 KiB
C#

/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (C) 2009-2013 Michael Möller <mmoeller@openhardwaremonitor.org>
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace OpenHardwareMonitor.Hardware.CPU {
internal sealed class AMD10CPU : AMDCPU {
private readonly Sensor coreTemperature;
private readonly Sensor[] coreClocks;
private readonly Sensor busClock;
private const uint PERF_CTL_0 = 0xC0010000;
private const uint PERF_CTR_0 = 0xC0010004;
private const uint HWCR = 0xC0010015;
private const uint P_STATE_0 = 0xC0010064;
private const uint COFVID_STATUS = 0xC0010071;
private const byte MISCELLANEOUS_CONTROL_FUNCTION = 3;
private const ushort FAMILY_10H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1203;
private const ushort FAMILY_11H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1303;
private const ushort FAMILY_12H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1703;
private const ushort FAMILY_14H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1703;
private const ushort FAMILY_15H_MODEL_00_MISC_CONTROL_DEVICE_ID = 0x1603;
private const ushort FAMILY_15H_MODEL_10_MISC_CONTROL_DEVICE_ID = 0x1403;
private const ushort FAMILY_15H_MODEL_30_MISC_CONTROL_DEVICE_ID = 0x141D;
private const ushort FAMILY_16H_MODEL_00_MISC_CONTROL_DEVICE_ID = 0x1533;
private const ushort FAMILY_16H_MODEL_30_MISC_CONTROL_DEVICE_ID = 0x1583;
private const uint REPORTED_TEMPERATURE_CONTROL_REGISTER = 0xA4;
private const uint CLOCK_POWER_TIMING_CONTROL_0_REGISTER = 0xD4;
private readonly uint miscellaneousControlAddress;
private readonly ushort miscellaneousControlDeviceId;
private readonly FileStream temperatureStream;
private readonly double timeStampCounterMultiplier;
private readonly bool corePerformanceBoostSupport;
public AMD10CPU(int processorIndex, CPUID[][] cpuid, ISettings settings)
: base(processorIndex, cpuid, settings)
{
// AMD family 1Xh processors support only one temperature sensor
coreTemperature = new Sensor(
"Core" + (coreCount > 1 ? " #1 - #" + coreCount : ""), 0,
SensorType.Temperature, this, new [] {
new ParameterDescription("Offset [°C]", "Temperature offset.", 0)
}, settings);
switch (family) {
case 0x10: miscellaneousControlDeviceId =
FAMILY_10H_MISCELLANEOUS_CONTROL_DEVICE_ID; break;
case 0x11: miscellaneousControlDeviceId =
FAMILY_11H_MISCELLANEOUS_CONTROL_DEVICE_ID; break;
case 0x12: miscellaneousControlDeviceId =
FAMILY_12H_MISCELLANEOUS_CONTROL_DEVICE_ID; break;
case 0x14: miscellaneousControlDeviceId =
FAMILY_14H_MISCELLANEOUS_CONTROL_DEVICE_ID; break;
case 0x15:
switch (model & 0xF0) {
case 0x00: miscellaneousControlDeviceId =
FAMILY_15H_MODEL_00_MISC_CONTROL_DEVICE_ID; break;
case 0x10: miscellaneousControlDeviceId =
FAMILY_15H_MODEL_10_MISC_CONTROL_DEVICE_ID; break;
case 0x30: miscellaneousControlDeviceId =
FAMILY_15H_MODEL_30_MISC_CONTROL_DEVICE_ID; break;
default: miscellaneousControlDeviceId = 0; break;
} break;
case 0x16:
switch (model & 0xF0) {
case 0x00: miscellaneousControlDeviceId =
FAMILY_16H_MODEL_00_MISC_CONTROL_DEVICE_ID; break;
case 0x30: miscellaneousControlDeviceId =
FAMILY_16H_MODEL_30_MISC_CONTROL_DEVICE_ID; break;
default: miscellaneousControlDeviceId = 0; break;
} break;
default: miscellaneousControlDeviceId = 0; break;
}
// get the pci address for the Miscellaneous Control registers
miscellaneousControlAddress = GetPciAddress(
MISCELLANEOUS_CONTROL_FUNCTION, miscellaneousControlDeviceId);
busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings);
coreClocks = new Sensor[coreCount];
for (int i = 0; i < coreClocks.Length; i++) {
coreClocks[i] = new Sensor(CoreString(i), i + 1, SensorType.Clock,
this, settings);
if (HasTimeStampCounter)
ActivateSensor(coreClocks[i]);
}
corePerformanceBoostSupport = (cpuid[0][0].ExtData[7, 3] & (1 << 9)) > 0;
// set affinity to the first thread for all frequency estimations
var previousAffinity = ThreadAffinity.Set(cpuid[0][0].Affinity);
// disable core performance boost
uint hwcrEax, hwcrEdx;
Ring0.Rdmsr(HWCR, out hwcrEax, out hwcrEdx);
if (corePerformanceBoostSupport)
Ring0.Wrmsr(HWCR, hwcrEax | (1 << 25), hwcrEdx);
uint ctlEax, ctlEdx;
Ring0.Rdmsr(PERF_CTL_0, out ctlEax, out ctlEdx);
uint ctrEax, ctrEdx;
Ring0.Rdmsr(PERF_CTR_0, out ctrEax, out ctrEdx);
timeStampCounterMultiplier = estimateTimeStampCounterMultiplier();
// restore the performance counter registers
Ring0.Wrmsr(PERF_CTL_0, ctlEax, ctlEdx);
Ring0.Wrmsr(PERF_CTR_0, ctrEax, ctrEdx);
// restore core performance boost
if (corePerformanceBoostSupport)
Ring0.Wrmsr(HWCR, hwcrEax, hwcrEdx);
// restore the thread affinity.
ThreadAffinity.Set(previousAffinity);
// the file reader for lm-sensors support on Linux
temperatureStream = null;
if (OperatingSystem.IsUnix) {
string[] devicePaths = Directory.GetDirectories("/sys/class/hwmon/");
foreach (string path in devicePaths) {
string name = null;
try {
using (StreamReader reader = new StreamReader(path + "/device/name"))
name = reader.ReadLine();
} catch (IOException) { }
switch (name) {
case "k10temp":
temperatureStream = new FileStream(path + "/device/temp1_input",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
break;
}
}
}
Update();
}
private double estimateTimeStampCounterMultiplier() {
// preload the function
estimateTimeStampCounterMultiplier(0);
estimateTimeStampCounterMultiplier(0);
// estimate the multiplier
List<double> estimate = new List<double>(3);
for (int i = 0; i < 3; i++)
estimate.Add(estimateTimeStampCounterMultiplier(0.025));
estimate.Sort();
return estimate[1];
}
private double estimateTimeStampCounterMultiplier(double timeWindow) {
uint eax, edx;
// select event "076h CPU Clocks not Halted" and enable the counter
Ring0.Wrmsr(PERF_CTL_0,
(1 << 22) | // enable performance counter
(1 << 17) | // count events in user mode
(1 << 16) | // count events in operating-system mode
0x76, 0x00000000);
// set the counter to 0
Ring0.Wrmsr(PERF_CTR_0, 0, 0);
long ticks = (long)(timeWindow * Stopwatch.Frequency);
uint lsbBegin, msbBegin, lsbEnd, msbEnd;
long timeBegin = Stopwatch.GetTimestamp() +
(long)Math.Ceiling(0.001 * ticks);
long timeEnd = timeBegin + ticks;
while (Stopwatch.GetTimestamp() < timeBegin) { }
Ring0.Rdmsr(PERF_CTR_0, out lsbBegin, out msbBegin);
while (Stopwatch.GetTimestamp() < timeEnd) { }
Ring0.Rdmsr(PERF_CTR_0, out lsbEnd, out msbEnd);
Ring0.Rdmsr(COFVID_STATUS, out eax, out edx);
double coreMultiplier = GetCoreMultiplier(eax);
ulong countBegin = ((ulong)msbBegin << 32) | lsbBegin;
ulong countEnd = ((ulong)msbEnd << 32) | lsbEnd;
double coreFrequency = 1e-6 *
(((double)(countEnd - countBegin)) * Stopwatch.Frequency) /
(timeEnd - timeBegin);
double busFrequency = coreFrequency / coreMultiplier;
return 0.25 * Math.Round(4 * TimeStampCounterFrequency / busFrequency);
}
protected override uint[] GetMSRs() {
return new uint[] { PERF_CTL_0, PERF_CTR_0, HWCR, P_STATE_0,
COFVID_STATUS };
}
public override string GetReport() {
StringBuilder r = new StringBuilder();
r.Append(base.GetReport());
r.Append("Miscellaneous Control Address: 0x");
r.AppendLine((miscellaneousControlAddress).ToString("X",
CultureInfo.InvariantCulture));
r.Append("Time Stamp Counter Multiplier: ");
r.AppendLine(timeStampCounterMultiplier.ToString(
CultureInfo.InvariantCulture));
if (family == 0x14) {
uint value = 0;
Ring0.ReadPciConfig(miscellaneousControlAddress,
CLOCK_POWER_TIMING_CONTROL_0_REGISTER, out value);
r.Append("PCI Register D18F3xD4: ");
r.AppendLine(value.ToString("X8", CultureInfo.InvariantCulture));
}
r.AppendLine();
return r.ToString();
}
private double GetCoreMultiplier(uint cofvidEax) {
switch (family) {
case 0x10:
case 0x11:
case 0x15:
case 0x16: {
// 8:6 CpuDid: current core divisor ID
// 5:0 CpuFid: current core frequency ID
uint cpuDid = (cofvidEax >> 6) & 7;
uint cpuFid = cofvidEax & 0x1F;
return 0.5 * (cpuFid + 0x10) / (1 << (int)cpuDid);
}
case 0x12: {
// 8:4 CpuFid: current CPU core frequency ID
// 3:0 CpuDid: current CPU core divisor ID
uint cpuFid = (cofvidEax >> 4) & 0x1F;
uint cpuDid = cofvidEax & 0xF;
double divisor;
switch (cpuDid) {
case 0: divisor = 1; break;
case 1: divisor = 1.5; break;
case 2: divisor = 2; break;
case 3: divisor = 3; break;
case 4: divisor = 4; break;
case 5: divisor = 6; break;
case 6: divisor = 8; break;
case 7: divisor = 12; break;
case 8: divisor = 16; break;
default: divisor = 1; break;
}
return (cpuFid + 0x10) / divisor;
}
case 0x14: {
// 8:4: current CPU core divisor ID most significant digit
// 3:0: current CPU core divisor ID least significant digit
uint divisorIdMSD = (cofvidEax >> 4) & 0x1F;
uint divisorIdLSD = cofvidEax & 0xF;
uint value = 0;
Ring0.ReadPciConfig(miscellaneousControlAddress,
CLOCK_POWER_TIMING_CONTROL_0_REGISTER, out value);
uint frequencyId = value & 0x1F;
return (frequencyId + 0x10) /
(divisorIdMSD + (divisorIdLSD * 0.25) + 1);
}
default:
return 1;
}
}
private string ReadFirstLine(Stream stream) {
StringBuilder sb = new StringBuilder();
try {
stream.Seek(0, SeekOrigin.Begin);
int b = stream.ReadByte();
while (b != -1 && b != 10) {
sb.Append((char)b);
b = stream.ReadByte();
}
} catch { }
return sb.ToString();
}
public override void Update() {
base.Update();
if (temperatureStream == null) {
if (miscellaneousControlAddress != Ring0.InvalidPciAddress) {
uint value;
if (Ring0.ReadPciConfig(miscellaneousControlAddress,
REPORTED_TEMPERATURE_CONTROL_REGISTER, out value)) {
if (family == 0x15 && (value & 0x30000) == 0x30000) {
if ((model & 0xF0) == 0x00) {
coreTemperature.Value = ((value >> 21) & 0x7FC) / 8.0f +
coreTemperature.Parameters[0].Value - 49;
} else {
coreTemperature.Value = ((value >> 21) & 0x7FF) / 8.0f +
coreTemperature.Parameters[0].Value - 49;
}
} else if (family == 0x16 &&
((value & 0x30000) == 0x30000 || (value & 0x80000) == 0x80000)) {
coreTemperature.Value = ((value >> 21) & 0x7FF) / 8.0f +
coreTemperature.Parameters[0].Value - 49;
} else {
coreTemperature.Value = ((value >> 21) & 0x7FF) / 8.0f +
coreTemperature.Parameters[0].Value;
}
ActivateSensor(coreTemperature);
} else {
DeactivateSensor(coreTemperature);
}
}
} else {
string s = ReadFirstLine(temperatureStream);
try {
coreTemperature.Value = 0.001f *
long.Parse(s, CultureInfo.InvariantCulture);
ActivateSensor(coreTemperature);
} catch {
DeactivateSensor(coreTemperature);
}
}
if (HasTimeStampCounter) {
double newBusClock = 0;
for (int i = 0; i < coreClocks.Length; i++) {
Thread.Sleep(1);
uint curEax, curEdx;
if (Ring0.RdmsrTx(COFVID_STATUS, out curEax, out curEdx,
cpuid[i][0].Affinity))
{
double multiplier;
multiplier = GetCoreMultiplier(curEax);
coreClocks[i].Value =
(float)(multiplier * TimeStampCounterFrequency /
timeStampCounterMultiplier);
newBusClock =
(float)(TimeStampCounterFrequency / timeStampCounterMultiplier);
} else {
coreClocks[i].Value = (float)TimeStampCounterFrequency;
}
}
if (newBusClock > 0) {
this.busClock.Value = (float)newBusClock;
ActivateSensor(this.busClock);
}
}
}
public override void Close() {
if (temperatureStream != null) {
temperatureStream.Close();
}
base.Close();
}
}
}