From 3df898af2436c6e5d8c8ca115d78034dc2da9295 Mon Sep 17 00:00:00 2001 From: kolos Date: Fri, 11 Jun 2021 09:36:17 +0200 Subject: [PATCH] Add Ryzen SMU support (#428) Thanks to @kolos. --- .editorconfig | 4 +- LibreHardwareMonitor/Resources/current.png | Bin 0 -> 639 bytes .../Hardware/Cpu/Amd17Cpu.cs | 109 ++- LibreHardwareMonitorLib/Hardware/Cpu/CpuId.cs | 3 + .../Hardware/Cpu/GenericCpu.cs | 6 + LibreHardwareMonitorLib/Hardware/ISensor.cs | 2 +- .../Hardware/KernelDriver.cs | 20 + LibreHardwareMonitorLib/Hardware/Ring0.cs | 9 + LibreHardwareMonitorLib/Hardware/RyzenSMU.cs | 819 ++++++++++++++++++ LibreHardwareMonitorLib/Hardware/WinRing0.sys | Bin 14416 -> 15560 bytes .../Hardware/WinRing0x64.sys | Bin 14544 -> 14536 bytes 11 files changed, 930 insertions(+), 42 deletions(-) create mode 100644 LibreHardwareMonitor/Resources/current.png create mode 100644 LibreHardwareMonitorLib/Hardware/RyzenSMU.cs diff --git a/.editorconfig b/.editorconfig index 3eba5d1..73f2470 100644 --- a/.editorconfig +++ b/.editorconfig @@ -28,8 +28,8 @@ csharp_new_line_between_query_expression_clauses = true # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true +csharp_indent_case_contents = false +csharp_indent_case_contents_when_block = false csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current diff --git a/LibreHardwareMonitor/Resources/current.png b/LibreHardwareMonitor/Resources/current.png new file mode 100644 index 0000000000000000000000000000000000000000..3721f55c6610bd9c9e338c8afef78f983d22d25b GIT binary patch literal 639 zcmV-_0)YLAP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igG+ z6&nq2q#f1(000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0005nNkl?xD zA`A=+3@rFGnJY1KXe*0{8R$r*@iM=({{HRL|9xi|JI_4&U;UGT;U7)|EEJe^1AWXA zv{VEWwIp8&viy6`@ba_B$7NeTC9OI2e;u+~xB;H#qHO+-9f3pkh5Y_f(WfZqz`16C6 z;mZ$phSy&i|G)mkaO~@EhH0<=Ff7Ih0~kp3Pzmz)(3;Q3aF_4fYfgrnPxL<=KJ$3h z&Z93UZ@?Ls@Z{w?A7(Gw_GZ=Hw+x?gMKvQd zbqVnBu`Co4WV4eL{ _sensorTypeIndex; + private readonly RyzenSMU _smu; public Amd17Cpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings) { + _sensorTypeIndex = new Dictionary(); + foreach (SensorType type in Enum.GetValues(typeof(SensorType))) + { + _sensorTypeIndex.Add(type, 0); + } + + _sensorTypeIndex[SensorType.Load] = _active.Count(x => x.SensorType == SensorType.Load); + + _smu = new RyzenSMU(_family, _model, _packageType); + // Add all numa nodes. // Register ..1E_2, [10:8] + 1 _processor = new Processor(this); @@ -51,6 +56,7 @@ namespace LibreHardwareMonitor.Hardware.CPU { coreId++; } + lastCoreId = coreIdRead; _processor.AppendThread(thread, nodeId, coreId); @@ -66,9 +72,9 @@ namespace LibreHardwareMonitor.Hardware.CPU public override string GetReport() { - StringBuilder r = new StringBuilder(); + StringBuilder r = new(); r.Append(base.GetReport()); - r.Append("Ryzen"); + r.Append(_smu.GetReport()); return r.ToString(); } @@ -77,6 +83,7 @@ namespace LibreHardwareMonitor.Hardware.CPU base.Update(); _processor.UpdateSensors(); + foreach (NumaNode node in _processor.Nodes) { NumaNode.UpdateSensors(); @@ -90,37 +97,44 @@ namespace LibreHardwareMonitor.Hardware.CPU private class Processor { - private readonly Amd17Cpu _cpu; private readonly Sensor _busClock; private readonly Sensor[] _ccdTemperatures; private readonly Sensor _coreTemperatureTctl; private readonly Sensor _coreTemperatureTctlTdie; private readonly Sensor _coreTemperatureTdie; private readonly Sensor _coreVoltage; + private readonly Amd17Cpu _cpu; private readonly Sensor _packagePower; + private readonly Dictionary, Sensor> _smuSensors = new(); private readonly Sensor _socVoltage; + private Sensor _ccdsAverageTemperature; private Sensor _ccdsMaxTemperature; - private DateTime _lastPwrTime = new DateTime(0); + private DateTime _lastPwrTime = new(0); private uint _lastPwrValue; public Processor(Hardware hardware) { _cpu = (Amd17Cpu)hardware; - _packagePower = new Sensor("Package Power", _cpu._sensorPower++, SensorType.Power, _cpu, _cpu._settings); - _coreTemperatureTctl = new Sensor("Core (Tctl)", _cpu._sensorTemperatures++, SensorType.Temperature, _cpu, _cpu._settings); - _coreTemperatureTdie = new Sensor("Core (Tdie)", _cpu._sensorTemperatures++, SensorType.Temperature, _cpu, _cpu._settings); - _coreTemperatureTctlTdie = new Sensor("Core (Tctl/Tdie)", _cpu._sensorTemperatures++, SensorType.Temperature, _cpu, _cpu._settings); + _packagePower = new Sensor("Package", _cpu._sensorTypeIndex[SensorType.Power]++, SensorType.Power, _cpu, _cpu._settings); + _coreTemperatureTctl = new Sensor("Core (Tctl)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings); + _coreTemperatureTdie = new Sensor("Core (Tdie)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings); + _coreTemperatureTctlTdie = new Sensor("Core (Tctl/Tdie)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings); _ccdTemperatures = new Sensor[8]; // Hardcoded until there's a way to get max CCDs. - _coreVoltage = new Sensor("Core (SVI2 TFN)", _cpu._sensorVoltage++, SensorType.Voltage, _cpu, _cpu._settings); - _socVoltage = new Sensor("SoC (SVI2 TFN)", _cpu._sensorVoltage++, SensorType.Voltage, _cpu, _cpu._settings); - _busClock = new Sensor("Bus Speed", _cpu._sensorClock++, SensorType.Clock, _cpu, _cpu._settings); + _coreVoltage = new Sensor("Core (SVI2 TFN)", _cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, _cpu, _cpu._settings); + _socVoltage = new Sensor("SoC (SVI2 TFN)", _cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, _cpu, _cpu._settings); + _busClock = new Sensor("Bus Speed", _cpu._sensorTypeIndex[SensorType.Clock]++, SensorType.Clock, _cpu, _cpu._settings); _cpu.ActivateSensor(_packagePower); + + foreach (KeyValuePair sensor in _cpu._smu.GetPmTableStructure()) + { + _smuSensors.Add(sensor, new Sensor(sensor.Value.Name, _cpu._sensorTypeIndex[sensor.Value.Type]++, sensor.Value.Type, _cpu, _cpu._settings)); + } } - public List Nodes { get; } = new List(); + public List Nodes { get; } = new(); public void UpdateSensors() { @@ -279,16 +293,17 @@ namespace LibreHardwareMonitor.Hardware.CPU ccdRawTemp &= 0xFFF; float ccdTemp = ((ccdRawTemp * 125) - 305000) * 0.001f; - if (ccdRawTemp > 0 && ccdTemp < 125) // Zen 2 reports 95 degrees C max, but it might exceed that. + if (ccdRawTemp > 0 && ccdTemp < 125) // Zen 2 reports 95 degrees C max, but it might exceed that. { if (_ccdTemperatures[i] == null) { _cpu.ActivateSensor(_ccdTemperatures[i] = new Sensor($"CCD{i + 1} (Tdie)", - _cpu._sensorTemperatures++, - SensorType.Temperature, - _cpu, - _cpu._settings)); + _cpu._sensorTypeIndex[SensorType.Temperature]++, + SensorType.Temperature, + _cpu, + _cpu._settings)); } + _ccdTemperatures[i].Value = ccdTemp; } } @@ -301,25 +316,26 @@ namespace LibreHardwareMonitor.Hardware.CPU if (_ccdsMaxTemperature == null) { _cpu.ActivateSensor(_ccdsMaxTemperature = new Sensor("CCDs Max (Tdie)", - _cpu._sensorTemperatures++, - SensorType.Temperature, - _cpu, - _cpu._settings)); + _cpu._sensorTypeIndex[SensorType.Temperature]++, + SensorType.Temperature, + _cpu, + _cpu._settings)); } if (_ccdsAverageTemperature == null) { _cpu.ActivateSensor(_ccdsAverageTemperature = new Sensor("CCDs Average (Tdie)", - _cpu._sensorTemperatures++, - SensorType.Temperature, - _cpu, - _cpu._settings)); + _cpu._sensorTypeIndex[SensorType.Temperature]++, + SensorType.Temperature, + _cpu, + _cpu._settings)); } _ccdsMaxTemperature.Value = activeCcds.Max(x => x.Value); _ccdsAverageTemperature.Value = activeCcds.Average(x => x.Value); } } + Ring0.ReleasePciBusMutex(); } @@ -354,6 +370,21 @@ namespace LibreHardwareMonitor.Hardware.CPU _busClock.Value = (float)(_cpu.TimeStampCounterFrequency / timeStampCounterMultiplier); _cpu.ActivateSensor(_busClock); } + + if (_cpu._smu.IsPmTableLayoutDefined()) + { + float[] smuData = _cpu._smu.GetPmTable(); + + foreach (var sensor in _smuSensors) + { + if (smuData.Length > sensor.Key.Key) + { + sensor.Value.Value = smuData[sensor.Key.Key] * sensor.Key.Value.Scale; + if (sensor.Value.Value != 0) + _cpu.ActivateSensor(sensor.Value); + } + } + } } private double GetTimeStampCounterMultiplier() @@ -427,13 +458,13 @@ namespace LibreHardwareMonitor.Hardware.CPU private class Core { - private readonly Amd17Cpu _cpu; private readonly Sensor _clock; + private readonly Amd17Cpu _cpu; private readonly Sensor _multiplier; private readonly Sensor _power; private readonly Sensor _vcore; private ISensor _busSpeed; - private DateTime _lastPwrTime = new DateTime(0); + private DateTime _lastPwrTime = new(0); private uint _lastPwrValue; public Core(Amd17Cpu cpu, int id) @@ -441,10 +472,10 @@ namespace LibreHardwareMonitor.Hardware.CPU _cpu = cpu; Threads = new List(); CoreId = id; - _clock = new Sensor("Core #" + CoreId, cpu._sensorClock++, SensorType.Clock, cpu, cpu._settings); - _multiplier = new Sensor("Core #" + CoreId, cpu._sensorMulti++, SensorType.Factor, cpu, cpu._settings); - _power = new Sensor("Core #" + CoreId + " (SMU)", cpu._sensorPower++, SensorType.Power, cpu, cpu._settings); - _vcore = new Sensor("Core #" + CoreId + " VID", cpu._sensorVoltage++, SensorType.Voltage, cpu, cpu._settings); + _clock = new Sensor("Core #" + CoreId, _cpu._sensorTypeIndex[SensorType.Clock]++, SensorType.Clock, cpu, cpu._settings); + _multiplier = new Sensor("Core #" + CoreId, cpu._sensorTypeIndex[SensorType.Factor]++, SensorType.Factor, cpu, cpu._settings); + _power = new Sensor("Core #" + CoreId + " (SMU)", cpu._sensorTypeIndex[SensorType.Power]++, SensorType.Power, cpu, cpu._settings); + _vcore = new Sensor("Core #" + CoreId + " VID", cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, cpu, cpu._settings); cpu.ActivateSensor(_clock); cpu.ActivateSensor(_multiplier); @@ -506,7 +537,7 @@ namespace LibreHardwareMonitor.Hardware.CPU double clock = 200.0; _busSpeed ??= _cpu.Sensors.FirstOrDefault(x => x.Name == "Bus Speed"); if (_busSpeed?.Value.HasValue == true && _busSpeed.Value > 0) - clock = (double) (_busSpeed.Value * 2); + clock = (double)(_busSpeed.Value * 2); _clock.Value = (float)(curCpuFid / (double)curCpuDfsId * clock); diff --git a/LibreHardwareMonitorLib/Hardware/Cpu/CpuId.cs b/LibreHardwareMonitorLib/Hardware/Cpu/CpuId.cs index e4b5d34..e75917c 100644 --- a/LibreHardwareMonitorLib/Hardware/Cpu/CpuId.cs +++ b/LibreHardwareMonitorLib/Hardware/Cpu/CpuId.cs @@ -141,6 +141,7 @@ namespace LibreHardwareMonitor.Hardware.CPU Model = ((Data[1, 0] & 0x0F0000) >> 12) + ((Data[1, 0] & 0xF0) >> 4); Stepping = Data[1, 0] & 0x0F; ApicId = (Data[1, 1] >> 24) & 0xFF; + PkgType = (ExtData[1, 1] >> 28) & 0xFF; switch (Vendor) { @@ -240,6 +241,8 @@ namespace LibreHardwareMonitor.Hardware.CPU public Vendor Vendor { get; } = Vendor.Unknown; + public uint PkgType { get; } + /// /// Gets the specified . /// diff --git a/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs b/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs index 417ce8c..a691141 100644 --- a/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs +++ b/LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs @@ -20,6 +20,7 @@ namespace LibreHardwareMonitor.Hardware.CPU protected readonly uint _model; protected readonly int _processorIndex; protected readonly uint _stepping; + protected readonly uint _packageType; private readonly Sensor[] _coreLoads; private readonly CpuLoad _cpuLoad; @@ -39,6 +40,7 @@ namespace LibreHardwareMonitor.Hardware.CPU _family = cpuId[0][0].Family; _model = cpuId[0][0].Model; _stepping = cpuId[0][0].Stepping; + _packageType = cpuId[0][0].PkgType; _processorIndex = processorIndex; _coreCount = cpuId.Length; @@ -71,10 +73,14 @@ namespace LibreHardwareMonitor.Hardware.CPU if (_cpuLoad.IsAvailable) { foreach (Sensor sensor in _coreLoads) + { ActivateSensor(sensor); + } if (_totalLoad != null) + { ActivateSensor(_totalLoad); + } } if (HasTimeStampCounter) diff --git a/LibreHardwareMonitorLib/Hardware/ISensor.cs b/LibreHardwareMonitorLib/Hardware/ISensor.cs index 48b903a..750c685 100644 --- a/LibreHardwareMonitorLib/Hardware/ISensor.cs +++ b/LibreHardwareMonitorLib/Hardware/ISensor.cs @@ -16,6 +16,7 @@ namespace LibreHardwareMonitor.Hardware { Voltage, // V Current, // A + Power, // W Clock, // MHz Temperature, // °C Load, // % @@ -25,7 +26,6 @@ namespace LibreHardwareMonitor.Hardware Control, // % Level, // % Factor, // 1 - Power, // W Data, // GB = 2^30 Bytes SmallData, // MB = 2^20 Bytes Throughput // B/s diff --git a/LibreHardwareMonitorLib/Hardware/KernelDriver.cs b/LibreHardwareMonitorLib/Hardware/KernelDriver.cs index 62d954c..8070ecf 100644 --- a/LibreHardwareMonitorLib/Hardware/KernelDriver.cs +++ b/LibreHardwareMonitorLib/Hardware/KernelDriver.cs @@ -137,6 +137,26 @@ namespace LibreHardwareMonitor.Hardware return b; } + public bool DeviceIOControl(Kernel32.IOControlCode ioControlCode, object inBuffer, ref T[] outBuffer) + { + if (_device == null) + return false; + + + object boxedOutBuffer = outBuffer; + bool b = Kernel32.DeviceIoControl(_device, + ioControlCode, + inBuffer, + inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer), + boxedOutBuffer, + (uint)(Marshal.SizeOf(typeof(T)) * outBuffer.Length), + out uint _, + IntPtr.Zero); + + outBuffer = (T[])boxedOutBuffer; + return b; + } + public void Close() { if (_device != null) diff --git a/LibreHardwareMonitorLib/Hardware/Ring0.cs b/LibreHardwareMonitorLib/Hardware/Ring0.cs index 263d29f..334068b 100644 --- a/LibreHardwareMonitorLib/Hardware/Ring0.cs +++ b/LibreHardwareMonitorLib/Hardware/Ring0.cs @@ -466,6 +466,15 @@ namespace LibreHardwareMonitor.Hardware return _driver.DeviceIOControl(Interop.Ring0.IOCTL_OLS_READ_MEMORY, input, ref buffer); } + public static bool ReadMemory(ulong address, ref T[] buffer) + { + if (_driver == null) + return false; + + ReadMemoryInput input = new ReadMemoryInput { Address = address, UnitSize = (uint)Marshal.SizeOf(typeof(T)), Count = (uint)buffer.Length }; + return _driver.DeviceIOControl(Interop.Ring0.IOCTL_OLS_READ_MEMORY, input, ref buffer); + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct WriteMsrInput { diff --git a/LibreHardwareMonitorLib/Hardware/RyzenSMU.cs b/LibreHardwareMonitorLib/Hardware/RyzenSMU.cs new file mode 100644 index 0000000..b0746b9 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/RyzenSMU.cs @@ -0,0 +1,819 @@ +// ported from: https://gitlab.com/leogx9r/ryzen_smu +// and: https://github.com/irusanov/SMUDebugTool + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +// ReSharper disable InconsistentNaming + +namespace LibreHardwareMonitor.Hardware +{ + internal class RyzenSMU + { + private const byte SMU_PCI_ADDR_REG = 0xC4; + private const byte SMU_PCI_DATA_REG = 0xC8; + private const uint SMU_REQ_MAX_ARGS = 6; + private const uint SMU_RETRIES_MAX = 8096; + + private readonly CpuCodeName _cpuCodeName; + private readonly Mutex _mutex = new(); + private readonly bool _supportedCPU; + + private readonly Dictionary> _supportedPmTableVersions = new() + { + { + // Zen Raven Ridge APU. + 0x001E0004, new Dictionary + { + { 28, new SmuSensorType { Name = "TDC", Type = SensorType.Current, Scale = 1}}, + { 44, new SmuSensorType { Name = "EDC", Type = SensorType.Current, Scale = 1}}, + //{ 61, new SmuSensorType { Name = "Core", Type = SensorType.Voltage } }, + //{ 62, new SmuSensorType { Name = "Core", Type = SensorType.Current, Scale = 1} }, + //{ 63, new SmuSensorType { Name = "Core", Type = SensorType.Power, Scale = 1 } }, + //{ 65, new SmuSensorType { Name = "SoC", Type = SensorType.Voltage } }, + { 66, new SmuSensorType { Name = "SoC", Type = SensorType.Current, Scale = 1 } }, + { 67, new SmuSensorType { Name = "SoC", Type = SensorType.Power, Scale = 1 } }, + //{ 96, new SmuSensorType { Name = "Core #1", Type = SensorType.Power } }, + //{ 97, new SmuSensorType { Name = "Core #2", Type = SensorType.Power } }, + //{ 98, new SmuSensorType { Name = "Core #3", Type = SensorType.Power } }, + //{ 99, new SmuSensorType { Name = "Core #4", Type = SensorType.Power } }, + { 108, new SmuSensorType { Name = "Core #1", Type = SensorType.Temperature, Scale = 1 } }, + { 109, new SmuSensorType { Name = "Core #2", Type = SensorType.Temperature, Scale = 1 } }, + { 110, new SmuSensorType { Name = "Core #3", Type = SensorType.Temperature, Scale = 1 } }, + { 111, new SmuSensorType { Name = "Core #4", Type = SensorType.Temperature, Scale = 1 } }, + { 150, new SmuSensorType { Name = "GFX", Type = SensorType.Voltage, Scale = 1 } }, + { 151, new SmuSensorType { Name = "GFX", Type = SensorType.Temperature, Scale = 1 } }, + { 154, new SmuSensorType { Name = "GFX", Type = SensorType.Clock, Scale = 1 } }, + { 156, new SmuSensorType { Name = "GFX", Type = SensorType.Load, Scale = 1 } }, + { 166, new SmuSensorType { Name = "Fabric", Type = SensorType.Clock, Scale = 1 } }, + { 177, new SmuSensorType { Name = "Uncore", Type = SensorType.Clock, Scale = 1 } }, + { 178, new SmuSensorType { Name = "Memory", Type = SensorType.Clock, Scale = 1 } }, + { 342, new SmuSensorType { Name = "Displays", Type = SensorType.Factor, Scale = 1 } }, + } + }, + { + // Zen 2. + 0x00240903, new Dictionary + { + { 15, new SmuSensorType { Name = "TDC", Type = SensorType.Current, Scale = 1}}, + { 21, new SmuSensorType { Name = "EDC", Type = SensorType.Current, Scale = 1}}, + { 48, new SmuSensorType { Name = "Fabric", Type = SensorType.Clock, Scale = 1} }, + { 50, new SmuSensorType { Name = "Uncore", Type = SensorType.Clock, Scale = 1} }, + { 51, new SmuSensorType { Name = "Memory", Type = SensorType.Clock, Scale = 1} }, + { 115, new SmuSensorType { Name = "SoC", Type = SensorType.Temperature, Scale = 1} }, + //{ 66, new SmuSensorType { Name = "Bus Speed", Type = SensorType.Clock, Scale = 1 } }, + //{ 188, new SmuSensorType { Name = "Core #1", Type = SensorType.Clock, Scale = 1000 } }, + //{ 189, new SmuSensorType { Name = "Core #2", Type = SensorType.Clock, Scale = 1000 } }, + //{ 190, new SmuSensorType { Name = "Core #3", Type = SensorType.Clock, Scale = 1000 } }, + //{ 191, new SmuSensorType { Name = "Core #4", Type = SensorType.Clock, Scale = 1000 } }, + //{ 192, new SmuSensorType { Name = "Core #5", Type = SensorType.Clock, Scale = 1000 } }, + //{ 193, new SmuSensorType { Name = "Core #6", Type = SensorType.Clock, Scale = 1000 } }, + } + } + }; + + private uint _argsAddr; + private uint _cmdAddr; + private uint _dramBaseAddr; + private uint _pmTableSize; + private uint _pmTableSizeAlt; + private uint _pmTableVersion; + private uint _rspAddr; + + public RyzenSMU(uint family, uint model, uint packageType) + { + _cpuCodeName = GetCpuCodeName(family, model, packageType); + + _supportedCPU = SetAddresses(_cpuCodeName); + + SetupPmTableAddrAndSize(); + } + + private static CpuCodeName GetCpuCodeName(uint family, uint model, uint packageType) + { + if (family == 0x17) + { + switch (model) + { + case 0x01: + { + return packageType == 7 ? CpuCodeName.Threadripper : CpuCodeName.SummitRidge; + } + case 0x08: + { + return packageType == 7 ? CpuCodeName.Colfax : CpuCodeName.PinnacleRidge; + } + case 0x11: + { + return CpuCodeName.RavenRidge; + } + case 0x18: + { + return packageType == 2 ? CpuCodeName.RavenRidge2 : CpuCodeName.Picasso; + } + case 0x20: + { + return CpuCodeName.Dali; + } + case 0x31: + { + return CpuCodeName.CastlePeak; + } + case 0x60: + { + return CpuCodeName.Renoir; + } + case 0x71: + { + return CpuCodeName.Matisse; + } + case 0x90: + { + return CpuCodeName.Vangogh; + } + default: + { + return CpuCodeName.Undefined; + } + } + } + + if (family == 0x19) + { + switch (model) + { + case 0x00: + { + return CpuCodeName.Milan; + } + case 0x20: + case 0x21: + { + return CpuCodeName.Vermeer; + } + case 0x40: + { + return CpuCodeName.Rembrandt; + } + case 0x50: + { + return CpuCodeName.Cezanne; + } + default: + { + return CpuCodeName.Undefined; + } + } + } + + return CpuCodeName.Undefined; + } + + public string GetReport() + { + StringBuilder r = new(); + + r.AppendLine("Ryzen SMU"); + r.AppendLine(); + r.AppendLine($" PM table version: 0x{_pmTableVersion:X8}"); + r.AppendLine($" PM table supported: {_supportedCPU}"); + r.AppendLine($" PM table layout defined: {IsPmTableLayoutDefined()}"); + + if (_supportedCPU) + { + r.AppendLine($" PM table size: 0x{_pmTableSize:X3}"); + r.AppendLine($" PM table start address: 0x{_dramBaseAddr:X8}"); + r.AppendLine(); + r.AppendLine(" PM table dump:"); + r.AppendLine(" Idx Offset Value"); + r.AppendLine(" ------------------------"); + + float[] pm_values = GetPmTable(); + for (int i = 0; i < pm_values.Length; i++) + { + r.AppendLine($" {i,4} 0x{i * 4:X3} {pm_values[i]}"); + } + } + + return r.ToString(); + } + + private bool SetAddresses(CpuCodeName codeName) + { + switch (codeName) + { + case CpuCodeName.CastlePeak: + case CpuCodeName.Matisse: + case CpuCodeName.Vermeer: + { + _cmdAddr = 0x3B10524; + _rspAddr = 0x3B10570; + _argsAddr = 0x3B10A40; + + return true; + } + case CpuCodeName.Colfax: + case CpuCodeName.SummitRidge: + case CpuCodeName.Threadripper: + case CpuCodeName.PinnacleRidge: + { + _cmdAddr = 0x3B1051C; + _rspAddr = 0x3B10568; + _argsAddr = 0x3B10590; + + return true; + } + case CpuCodeName.Renoir: + case CpuCodeName.Picasso: + case CpuCodeName.RavenRidge: + case CpuCodeName.RavenRidge2: + case CpuCodeName.Dali: + { + _cmdAddr = 0x3B10A20; + _rspAddr = 0x3B10A80; + _argsAddr = 0x3B10A88; + + return true; + } + default: + { + return false; + } + } + } + + public uint GetSmuVersion() + { + uint[] args = { 1 }; + + if (SendCommand(0x02, ref args)) + { + return args[0]; + } + + return 0; + } + + public Dictionary GetPmTableStructure() + { + if (!IsPmTableLayoutDefined()) + return new Dictionary(); + + + return _supportedPmTableVersions[_pmTableVersion]; + } + + public bool IsPmTableLayoutDefined() + { + return _supportedPmTableVersions.ContainsKey(_pmTableVersion); + } + + public float[] GetPmTable() + { + if (!_supportedCPU) + return new float[] { 0 }; + + if (!SetupPmTableAddrAndSize()) + return new float[] { 0 }; + + if (!TransferTableToDram()) + return new float[] { 0 }; + + + float[] table = ReadDramToArray(); + if (table[0] == 0) /* Fix for Zen+ empty values at first call bug */ + { + Thread.Sleep(10); + TransferTableToDram(); + table = ReadDramToArray(); + } + + return table; + } + + private float[] ReadDramToArray() + { + float[] table = new float[_pmTableSize / 4]; + + Ring0.ReadMemory(_dramBaseAddr, ref table); + + return table; + } + + private bool SetupPmTableAddrAndSize() + { + if (_pmTableSize == 0) + { + SetupPmTableSize(); + } + + if (_dramBaseAddr == 0) + { + SetupDramBaseAddr(); + } + + return _dramBaseAddr != 0 && _pmTableSize != 0; + } + + private void SetupPmTableSize() + { + if (!GetPmTableVersion(ref _pmTableVersion)) + return; + + + switch (_cpuCodeName) + { + case CpuCodeName.Matisse: + { + switch (_pmTableVersion) + { + case 0x240902: + { + _pmTableSize = 0x514; + break; + } + case 0x240903: + { + _pmTableSize = 0x518; + break; + } + case 0x240802: + { + _pmTableSize = 0x7E0; + break; + } + case 0x240803: + { + _pmTableSize = 0x7E4; + break; + } + default: + { + return; + } + } + + break; + } + case CpuCodeName.Vermeer: + { + switch (_pmTableVersion) + { + case 0x2D0903: + { + _pmTableSize = 0x594; + break; + } + case 0x380904: + { + _pmTableSize = 0x5A4; + break; + } + case 0x380905: + { + _pmTableSize = 0x5D0; + break; + } + case 0x2D0803: + { + _pmTableSize = 0x894; + break; + } + case 0x380804: + { + _pmTableSize = 0x8A4; + break; + } + case 0x380805: + { + _pmTableSize = 0x8F0; + break; + } + default: + { + return; + } + } + + break; + } + case CpuCodeName.Renoir: + { + switch (_pmTableVersion) + { + case 0x370000: + { + _pmTableSize = 0x794; + break; + } + case 0x370001: + { + _pmTableSize = 0x884; + break; + } + case 0x370002: + case 0x370003: + { + _pmTableSize = 0x88C; + break; + } + case 0x370004: + { + _pmTableSize = 0x8AC; + break; + } + case 0x370005: + { + _pmTableSize = 0x8C8; + break; + } + default: + { + return; + } + } + + break; + } + case CpuCodeName.Cezanne: + { + switch (_pmTableVersion) + { + case 0x400005: + { + _pmTableSize = 0x944; + break; + } + default: + { + return; + } + } + + break; + } + case CpuCodeName.Picasso: + case CpuCodeName.RavenRidge: + case CpuCodeName.RavenRidge2: + { + _pmTableSizeAlt = 0xA4; + _pmTableSize = 0x608 + _pmTableSizeAlt; + break; + } + default: + { + return; + } + } + } + + private bool GetPmTableVersion(ref uint version) + { + uint[] args = { 0 }; + uint fn; + + switch (_cpuCodeName) + { + case CpuCodeName.RavenRidge: + case CpuCodeName.Picasso: + { + fn = 0x0c; + break; + } + case CpuCodeName.Matisse: + case CpuCodeName.Vermeer: + { + fn = 0x08; + break; + } + case CpuCodeName.Renoir: + { + fn = 0x06; + break; + } + default: + { + return false; + } + } + + bool ret = SendCommand(fn, ref args); + version = args[0]; + + return ret; + } + + private void SetupAddrClass1(uint[] fn) + { + uint[] args = { 1, 1 }; + + bool command = SendCommand(fn[0], ref args); + if (!command) + return; + + + _dramBaseAddr = args[0] | (args[1] << 32); + } + + private void SetupAddrClass2(uint[] fn) + { + uint[] args = { 0, 0, 0, 0, 0, 0 }; + + bool command = SendCommand(fn[0], ref args); + if (!command) + return; + + + args = new uint[] { 0 }; + command = SendCommand(fn[1], ref args); + if (!command) + return; + + + _dramBaseAddr = args[0]; + } + + private void SetupAddrClass3(uint[] fn) + { + uint[] parts = { 0, 0 }; + + // == Part 1 == + uint[] args = { 3 }; + bool command = SendCommand(fn[0], ref args); + if (!command) + return; + + + args = new uint[] { 3 }; + command = SendCommand(fn[2], ref args); + if (!command) + return; + + + // 1st Base. + parts[0] = args[0]; + // == Part 1 End == + + // == Part 2 == + args = new uint[] { 3 }; + command = SendCommand(fn[1], ref args); + if (!command) + return; + + + args = new uint[] { 5 }; + command = SendCommand(fn[0], ref args); + if (!command) + return; + + + args = new uint[] { 5 }; + command = SendCommand(fn[2], ref args); + if (!command) + return; + + + // 2nd base. + parts[1] = args[0]; + // == Part 2 End == + + _dramBaseAddr = parts[0] & 0xFFFFFFFF; + } + + private void SetupDramBaseAddr() + { + uint[] fn = { 0, 0, 0 }; + + switch (_cpuCodeName) + { + case CpuCodeName.Vermeer: + case CpuCodeName.Matisse: + case CpuCodeName.CastlePeak: + { + fn[0] = 0x06; + SetupAddrClass1(fn); + + return; + } + case CpuCodeName.Renoir: + { + fn[0] = 0x66; + SetupAddrClass1(fn); + + return; + } + case CpuCodeName.Colfax: + case CpuCodeName.PinnacleRidge: + { + fn[0] = 0x0b; + fn[1] = 0x0c; + SetupAddrClass2(fn); + + return; + } + case CpuCodeName.Dali: + case CpuCodeName.Picasso: + case CpuCodeName.RavenRidge: + case CpuCodeName.RavenRidge2: + { + fn[0] = 0x0a; + fn[1] = 0x3d; + fn[2] = 0x0b; + SetupAddrClass3(fn); + + return; + } + default: + { + return; + } + } + } + + public bool TransferTableToDram() + { + uint[] args = { 0 }; + uint fn; + + switch (_cpuCodeName) + { + case CpuCodeName.Matisse: + case CpuCodeName.Vermeer: + { + fn = 0x05; + break; + } + case CpuCodeName.Renoir: + { + args[0] = 3; + fn = 0x65; + break; + } + case CpuCodeName.Picasso: + case CpuCodeName.RavenRidge: + case CpuCodeName.RavenRidge2: + { + args[0] = 3; + fn = 0x3d; + break; + } + default: + { + return false; + } + } + + return SendCommand(fn, ref args); + } + + private bool SendCommand(uint msg, ref uint[] args) + { + uint[] cmdArgs = new uint[SMU_REQ_MAX_ARGS]; + int argsLength = Math.Min(args.Length, cmdArgs.Length); + + for (int i = 0; i < argsLength; ++i) + cmdArgs[i] = args[i]; + + uint tmp = 0; + if (_mutex.WaitOne(5000)) + { + // Step 1: Wait until the RSP register is non-zero. + + tmp = 0; + uint retries = SMU_RETRIES_MAX; + do + { + if (!ReadReg(_rspAddr, ref tmp)) + { + _mutex.ReleaseMutex(); + return false; + } + } + while (tmp == 0 && 0 != retries--); + + // Step 1.b: A command is still being processed meaning a new command cannot be issued. + + if (retries == 0 && tmp == 0) + { + _mutex.ReleaseMutex(); + return false; + } + + // Step 2: Write zero (0) to the RSP register + WriteReg(_rspAddr, 0); + + // Step 3: Write the argument(s) into the argument register(s) + for (int i = 0; i < cmdArgs.Length; ++i) + WriteReg(_argsAddr + (uint)(i * 4), cmdArgs[i]); + + // Step 4: Write the message Id into the Message ID register + WriteReg(_cmdAddr, msg); + + // Step 5: Wait until the Response register is non-zero. + tmp = 0; + retries = SMU_RETRIES_MAX; + do + { + if (!ReadReg(_rspAddr, ref tmp)) + { + _mutex.ReleaseMutex(); + return false; + } + } + while (tmp == 0 && 0 != retries--); + + if (retries == 0 && tmp != (uint)Status.OK) + { + _mutex.ReleaseMutex(); + return false; + } + + // Step 6: If the Response register contains OK, then SMU has finished processing the message. + + args = new uint[SMU_REQ_MAX_ARGS]; + for (byte i = 0; i < SMU_REQ_MAX_ARGS; i++) + { + if (!ReadReg(_argsAddr + (uint)(i * 4), ref args[i])) + { + _mutex.ReleaseMutex(); + return false; + } + } + + ReadReg(_rspAddr, ref tmp); + _mutex.ReleaseMutex(); + } + + return tmp == (uint)Status.OK; + } + + private void WriteReg(uint addr, uint data) + { + if (Ring0.WaitPciBusMutex(10)) + { + if (Ring0.WritePciConfig(0x00, SMU_PCI_ADDR_REG, addr)) + { + Ring0.WritePciConfig(0x00, SMU_PCI_DATA_REG, data); + } + + Ring0.ReleasePciBusMutex(); + } + } + + private bool ReadReg(uint addr, ref uint data) + { + bool read = false; + + if (Ring0.WaitPciBusMutex(10)) + { + if (Ring0.WritePciConfig(0x00, SMU_PCI_ADDR_REG, addr)) + { + read = Ring0.ReadPciConfig(0x00, SMU_PCI_DATA_REG, out data); + } + + Ring0.ReleasePciBusMutex(); + } + + return read; + } + + public struct SmuSensorType + { + public string Name; + public SensorType Type; + public float Scale; + } + + private enum Status : uint + { + OK = 0x01, + Failed = 0xFF, + UnknownCmd = 0xFE, + CmdRejectedPrereq = 0xFD, + CmdRejectedBusy = 0xFC + } + + private enum CpuCodeName + { + Undefined, + Colfax, + Renoir, + Picasso, + Matisse, + Threadripper, + CastlePeak, + RavenRidge, + RavenRidge2, + SummitRidge, + PinnacleRidge, + Rembrandt, + Vermeer, + Vangogh, + Cezanne, + Milan, + Dali + } + } +} diff --git a/LibreHardwareMonitorLib/Hardware/WinRing0.sys b/LibreHardwareMonitorLib/Hardware/WinRing0.sys index feaea414a8b4991a2de419dc0c81a9144256807b..4657dfb582b546dcd35778b15d7b7b536755002d 100644 GIT binary patch literal 15560 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 literal 14544 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