diff --git a/Hardware/CPU/AMD17CPU.cs b/Hardware/CPU/AMD17CPU.cs new file mode 100644 index 0000000..fda2c47 --- /dev/null +++ b/Hardware/CPU/AMD17CPU.cs @@ -0,0 +1,342 @@ +/* + + 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) 2020 Michael Möller + +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace OpenHardwareMonitor.Hardware.CPU { + + internal sealed class AMD17CPU : AMDCPU { + + private readonly Core[] cores; + + private readonly Sensor coreTemperature; + private readonly Sensor tctlTemperature; + private readonly Sensor ccdMaxTemperature; + private readonly Sensor ccdAvgTemperature; + private readonly Sensor[] ccdTemperatures; + private readonly Sensor packagePowerSensor; + private readonly Sensor coresPowerSensor; + private readonly Sensor busClock; + + private const uint FAMILY_17H_M01H_THM_TCON_TEMP = 0x00059800; + private const uint FAMILY_17H_M01H_THM_TCON_TEMP_RANGE_SEL = 0x80000; + private uint FAMILY_17H_M70H_CCD_TEMP(uint i) { return 0x00059954 + i * 4; } + private const uint FAMILY_17H_M70H_CCD_TEMP_VALID = 0x800; + private const uint MAX_CCD_COUNT = 8; + + private const uint MSR_RAPL_PWR_UNIT = 0xC0010299; + private const uint MSR_CORE_ENERGY_STAT = 0xC001029A; + private const uint MSR_PKG_ENERGY_STAT = 0xC001029B; + private const uint MSR_P_STATE_0 = 0xC0010064; + private const uint MSR_FAMILY_17H_P_STATE = 0xc0010293; + + private float energyUnitMultiplier = 0; + private uint lastEnergyConsumed; + private DateTime lastEnergyTime; + + private readonly double timeStampCounterMultiplier; + + private struct TctlOffsetItem { + public string Name { get; set; } + public float Offset { get; set; } + } + private IEnumerable tctlOffsetItems = new[] { + new TctlOffsetItem { Name = "AMD Ryzen 5 1600X", Offset = 20.0f }, + new TctlOffsetItem { Name = "AMD Ryzen 7 1700X", Offset = 20.0f }, + new TctlOffsetItem { Name = "AMD Ryzen 7 1800X", Offset = 20.0f }, + new TctlOffsetItem { Name = "AMD Ryzen 7 2700X", Offset = 10.0f }, + new TctlOffsetItem { Name = "AMD Ryzen Threadripper 19", Offset = 27.0f }, + new TctlOffsetItem { Name = "AMD Ryzen Threadripper 29", Offset = 27.0f } + }; + private readonly float tctlOffset = 0.0f; + + public AMD17CPU(int processorIndex, CPUID[][] cpuid, ISettings settings) + : base(processorIndex, cpuid, settings) + { + string cpuName = cpuid[0][0].BrandString; + if (!string.IsNullOrEmpty(cpuName)) { + foreach (var item in tctlOffsetItems) { + if (cpuName.StartsWith(item.Name)) { + tctlOffset = item.Offset; + break; + } + } + } + + coreTemperature = new Sensor( + "CPU Package", 0, SensorType.Temperature, this, new[] { + new ParameterDescription("Offset [°C]", "Temperature offset.", 0) + }, this.settings); + + if (tctlOffset != 0.0f) + tctlTemperature = new Sensor( + "CPU Tctl", 1, true, SensorType.Temperature, this, new[] { + new ParameterDescription("Offset [°C]", "Temperature offset.", 0) + }, this.settings); + + ccdMaxTemperature = new Sensor( + "CPU CCD Max", 2, SensorType.Temperature, this, this.settings); + + ccdAvgTemperature = new Sensor( + "CPU CCD Average", 3, SensorType.Temperature, this, this.settings); + + ccdTemperatures = new Sensor[MAX_CCD_COUNT]; + for (int i = 0; i < MAX_CCD_COUNT; i++) { + ccdTemperatures[i] = new Sensor( + "CPU CCD #" + (i + 1), i + 4, SensorType.Temperature, this, + new[] { + new ParameterDescription("Offset [°C]", "Temperature offset.", 0) + }, this.settings); + } + + if (Ring0.Rdmsr(MSR_RAPL_PWR_UNIT, out uint eax, out _)) { + energyUnitMultiplier = 1.0f / (1 << (int)((eax >> 8) & 0x1F)); + } + + if (energyUnitMultiplier != 0) { + if (Ring0.Rdmsr(MSR_PKG_ENERGY_STAT, out uint energyConsumed, out _)) { + lastEnergyTime = DateTime.UtcNow; + lastEnergyConsumed = energyConsumed; + packagePowerSensor = new Sensor( + "CPU Package", 0, SensorType.Power, this, settings); + ActivateSensor(packagePowerSensor); + } + + coresPowerSensor = new Sensor("CPU Cores", 1, SensorType.Power, this, + settings); + } + + busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings); + timeStampCounterMultiplier = GetTimeStampCounterMultiplier(); + if (timeStampCounterMultiplier > 0) { + busClock.Value = (float)(TimeStampCounterFrequency / + timeStampCounterMultiplier); + ActivateSensor(busClock); + } + + this.cores = new Core[coreCount]; + for (int i = 0; i < this.cores.Length; i++) { + this.cores[i] = new Core(i, cpuid[i], this, settings); + } + } + + protected override uint[] GetMSRs() { + return new uint[] { MSR_P_STATE_0, MSR_FAMILY_17H_P_STATE, + MSR_RAPL_PWR_UNIT, MSR_CORE_ENERGY_STAT, MSR_PKG_ENERGY_STAT }; + } + + private IList GetSmnRegisters() { + var registers = new List(); + registers.Add(FAMILY_17H_M01H_THM_TCON_TEMP); + for (uint i = 0; i < MAX_CCD_COUNT; i++) { + registers.Add(FAMILY_17H_M70H_CCD_TEMP(i)); + } + return registers; + } + + public override string GetReport() { + StringBuilder r = new StringBuilder(); + r.Append(base.GetReport()); + + r.Append("Time Stamp Counter Multiplier: "); + r.AppendLine(timeStampCounterMultiplier.ToString( + CultureInfo.InvariantCulture)); + r.AppendLine(); + + r.AppendLine("SMN Registers"); + r.AppendLine(); + r.AppendLine(" Register Value"); + var registers = GetSmnRegisters(); + for (int i = 0; i < registers.Count; i++) + if (ReadSmnRegister(registers[i], out uint value)) { + r.Append(" "); + r.Append(registers[i].ToString("X8", CultureInfo.InvariantCulture)); + r.Append(" "); + r.Append(value.ToString("X8", CultureInfo.InvariantCulture)); + r.AppendLine(); + } + r.AppendLine(); + + return r.ToString(); + } + + private double GetTimeStampCounterMultiplier() { + Ring0.Rdmsr(MSR_P_STATE_0, out uint eax, out _); + uint cpuDfsId = (eax >> 8) & 0x3f; + uint cpuFid = eax & 0xff; + return 2.0 * cpuFid / cpuDfsId; + } + + private bool ReadSmnRegister(uint address, out uint value) { + if (!Ring0.WritePciConfig(0, 0x60, address)) { + value = 0; + return false; + } + return Ring0.ReadPciConfig(0, 0x64, out value); + } + + public override void Update() { + base.Update(); + + uint value; + if (ReadSmnRegister(FAMILY_17H_M01H_THM_TCON_TEMP, out value)) { + float temperature = ((value >> 21) & 0x7FF) / 8.0f; + if ((value & FAMILY_17H_M01H_THM_TCON_TEMP_RANGE_SEL) != 0) + temperature -= 49; + + if (tctlTemperature != null) { + tctlTemperature.Value = temperature + + tctlTemperature.Parameters[0].Value; + ActivateSensor(tctlTemperature); + } + + temperature -= tctlOffset; + + coreTemperature.Value = temperature + + coreTemperature.Parameters[0].Value; + ActivateSensor(coreTemperature); + } + + float maxTemperature = float.MinValue; + int ccdCount = 0; + float ccdTemperatureSum = 0; + for (uint i = 0; i < MAX_CCD_COUNT; i++) { + if (ReadSmnRegister(FAMILY_17H_M70H_CCD_TEMP(i), out value)) { + if ((value & FAMILY_17H_M70H_CCD_TEMP_VALID) == 0) + continue; + + float temperature = (value & 0x7FF) / 8.0f - 49; + temperature += ccdTemperatures[i].Parameters[0].Value; + + if (temperature > maxTemperature) + maxTemperature = temperature; + ccdCount++; + ccdTemperatureSum += temperature; + + ccdTemperatures[i].Value = temperature; + ActivateSensor(ccdTemperatures[i]); + } + } + + if (ccdCount > 1) { + ccdMaxTemperature.Value = maxTemperature; + ActivateSensor(ccdMaxTemperature); + + ccdAvgTemperature.Value = ccdTemperatureSum / ccdCount; + ActivateSensor(ccdAvgTemperature); + } + + if (Ring0.Rdmsr(MSR_PKG_ENERGY_STAT, out uint energyConsumed, out _)) { + + DateTime time = DateTime.UtcNow; + float deltaTime = (float)(time - lastEnergyTime).TotalSeconds; + if (deltaTime > 0.01) { + + packagePowerSensor.Value = energyUnitMultiplier * unchecked( + energyConsumed - lastEnergyConsumed) / deltaTime; + lastEnergyTime = time; + lastEnergyConsumed = energyConsumed; + } + } + + float? coresPower = 0f; + for (int i = 0; i < cores.Length; i++) { + cores[i].Update(); + coresPower += cores[i].Power; + } + coresPowerSensor.Value = coresPower; + + if (coresPower.HasValue) { + ActivateSensor(coresPowerSensor); + } + } + + private class Core { + + private readonly AMD17CPU cpu; + private readonly ulong threadAffinityMask; + + private readonly Sensor powerSensor; + private readonly Sensor clockSensor; + + private DateTime lastEnergyTime; + private uint lastEnergyConsumed; + private float? power = null; + + public Core(int index, CPUID[] threads, AMD17CPU cpu, ISettings settings) + { + this.cpu = cpu; + this.threadAffinityMask = 1UL << threads[0].Thread; + + string coreString = cpu.CoreString(index); + this.powerSensor = + new Sensor(coreString, index + 2, SensorType.Power, cpu, settings); + this.clockSensor = + new Sensor(coreString, index + 1, SensorType.Clock, cpu, settings); + + if (cpu.energyUnitMultiplier != 0) { + if (Ring0.RdmsrTx(MSR_CORE_ENERGY_STAT, out uint energyConsumed, + out _, threadAffinityMask)) + { + lastEnergyTime = DateTime.UtcNow; + lastEnergyConsumed = energyConsumed; + cpu.ActivateSensor(powerSensor); + } + } + } + + private double? GetMultiplier() { + if (Ring0.Rdmsr(MSR_FAMILY_17H_P_STATE, out uint eax, out _)) { + uint cpuDfsId = (eax >> 8) & 0x3f; + uint cpuFid = eax & 0xff; + return 2.0 * cpuFid / cpuDfsId; + } else { + return null; + } + } + + public float? Power { get { return power; } } + + public void Update() { + DateTime energyTime = DateTime.MinValue; + double? multiplier = null; + + ulong mask = ThreadAffinity.Set(threadAffinityMask); + if (Ring0.Rdmsr(MSR_CORE_ENERGY_STAT, out uint energyConsumed, out _)) { + energyTime = DateTime.UtcNow; + } + + multiplier = GetMultiplier(); + ThreadAffinity.Set(mask); + + float deltaTime = (float)(energyTime - lastEnergyTime).TotalSeconds; + if (deltaTime > 0.01) { + power = cpu.energyUnitMultiplier * + unchecked(energyConsumed - lastEnergyConsumed) / deltaTime; + powerSensor.Value = power; + lastEnergyTime = energyTime; + lastEnergyConsumed = energyConsumed; + } + + if (multiplier.HasValue) { + float? clock = (float?)(multiplier * cpu.busClock.Value); + clockSensor.Value = clock; + if (clock.HasValue) + cpu.ActivateSensor(clockSensor); + } + } + + } + } + +} diff --git a/Hardware/CPU/CPUGroup.cs b/Hardware/CPU/CPUGroup.cs index 7429550..2e2aed9 100644 --- a/Hardware/CPU/CPUGroup.cs +++ b/Hardware/CPU/CPUGroup.cs @@ -4,7 +4,7 @@ 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-2014 Michael Möller + Copyright (C) 2009-2020 Michael Möller */ @@ -104,6 +104,9 @@ namespace OpenHardwareMonitor.Hardware.CPU { case 0x16: hardware.Add(new AMD10CPU(index, coreThreads, settings)); break; + case 0x17: + hardware.Add(new AMD17CPU(index, coreThreads, settings)); + break; default: hardware.Add(new GenericCPU(index, coreThreads, settings)); break; diff --git a/Hardware/CPU/CPUID.cs b/Hardware/CPU/CPUID.cs index e418dc4..67be4ff 100644 --- a/Hardware/CPU/CPUID.cs +++ b/Hardware/CPU/CPUID.cs @@ -4,7 +4,7 @@ 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-2014 Michael Möller + Copyright (C) 2009-2020 Michael Möller */ @@ -178,14 +178,20 @@ namespace OpenHardwareMonitor.Hardware.CPU { NextLog2(maxCoreAndThreadIdPerPackage / maxCoreIdPerPackage); coreMaskWith = NextLog2(maxCoreIdPerPackage); break; - case Vendor.AMD: - uint corePerPackage; - if (maxCpuidExt >= 8) - corePerPackage = (cpuidExtData[8, 2] & 0xFF) + 1; - else - corePerPackage = 1; - threadMaskWith = 0; - coreMaskWith = NextLog2(corePerPackage); + case Vendor.AMD: + if (this.family == 0x17) { + coreMaskWith = (cpuidExtData[8, 2] >> 12) & 0xF; + threadMaskWith = + NextLog2(((cpuidExtData[0x1E, 1] >> 8) & 0xFF) + 1); + } else { + uint corePerPackage; + if (maxCpuidExt >= 8) + corePerPackage = (cpuidExtData[8, 2] & 0xFF) + 1; + else + corePerPackage = 1; + coreMaskWith = NextLog2(corePerPackage); + threadMaskWith = 0; + } break; default: threadMaskWith = 0; diff --git a/OpenHardwareMonitorLib.csproj b/OpenHardwareMonitorLib.csproj index 9eb1e8d..fd6bf2f 100644 --- a/OpenHardwareMonitorLib.csproj +++ b/OpenHardwareMonitorLib.csproj @@ -62,6 +62,7 @@ +