// 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 { { 7, new SmuSensorType { Name = "TDC", Type = SensorType.Current, Scale = 1 } }, { 11, 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 } }, } }, { // Zen 3. 0x00380805, new Dictionary { { 3, new SmuSensorType { Name = "TDC", Type = SensorType.Current, Scale = 1 } }, // TODO: requires some post-processing // see: https://gitlab.com/leogx9r/ryzen_smu/-/blob/master/userspace/monitor_cpu.c#L577 // { 9, 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 } }, { 127, new SmuSensorType { Name = "SoC", Type = SensorType.Temperature, Scale = 1 } }, { 268, new SmuSensorType { Name = "Core #1 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 269, new SmuSensorType { Name = "Core #2 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 270, new SmuSensorType { Name = "Core #3 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 271, new SmuSensorType { Name = "Core #4 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 272, new SmuSensorType { Name = "Core #5 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 273, new SmuSensorType { Name = "Core #6 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 274, new SmuSensorType { Name = "Core #7 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 275, new SmuSensorType { Name = "Core #8 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 276, new SmuSensorType { Name = "Core #9 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 277, new SmuSensorType { Name = "Core #10 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 278, new SmuSensorType { Name = "Core #11 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 279, new SmuSensorType { Name = "Core #12 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 280, new SmuSensorType { Name = "Core #13 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 281, new SmuSensorType { Name = "Core #14 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 282, new SmuSensorType { Name = "Core #15 (Effective)", Type = SensorType.Clock, Scale = 1000 } }, { 283, new SmuSensorType { Name = "Core #16 (Effective)", 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 = Environment.Is64BitOperatingSystem == Environment.Is64BitProcess && SetAddresses(_cpuCodeName); if (_supportedCPU) { InpOut.Open(); SetupPmTableAddrAndSize(); } } private static CpuCodeName GetCpuCodeName(uint family, uint model, uint packageType) { if (family == 0x17) { return model switch { 0x01 => packageType == 7 ? CpuCodeName.Threadripper : CpuCodeName.SummitRidge, 0x08 => packageType == 7 ? CpuCodeName.Colfax : CpuCodeName.PinnacleRidge, 0x11 => CpuCodeName.RavenRidge, 0x18 => packageType == 2 ? CpuCodeName.RavenRidge2 : CpuCodeName.Picasso, 0x20 => CpuCodeName.Dali, 0x31 => CpuCodeName.CastlePeak, 0x60 => CpuCodeName.Renoir, 0x71 => CpuCodeName.Matisse, 0x90 => CpuCodeName.Vangogh, _ => CpuCodeName.Undefined }; } if (family == 0x19) { return model switch { 0x00 => CpuCodeName.Milan, 0x20 or 0x21 => CpuCodeName.Vermeer, 0x40 => CpuCodeName.Rembrandt, 0x50 => CpuCodeName.Cezanne, _ => 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 || !TransferTableToDram()) return new float[] { 0 }; float[] table = ReadDramToArray(); // Fix for Zen+ empty values on first call. if (table.Length == 0 || table[0] == 0) { Thread.Sleep(10); TransferTableToDram(); table = ReadDramToArray(); } return table; } private float[] ReadDramToArray() { float[] table = new float[_pmTableSize / 4]; byte[] bytes = InpOut.ReadMemory(new IntPtr(_dramBaseAddr), _pmTableSize); if (bytes != null) Buffer.BlockCopy(bytes, 0, table, 0, bytes.Length); 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 && retries-- != 0); 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 static 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 static 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 } } }