diff --git a/CommonHelpers/InpOut.cs b/CommonHelpers/InpOut.cs
index 39cf4c0..31771fc 100644
--- a/CommonHelpers/InpOut.cs
+++ b/CommonHelpers/InpOut.cs
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
namespace CommonHelpers
{
- internal class InpOut
+ public class InpOut
{
[DllImport("inpoutx64.dll", EntryPoint = "MapPhysToLin", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr MapPhysToLin(IntPtr pbPhysAddr, uint dwPhysSize, out IntPtr pPhysicalMemoryHandle);
@@ -23,6 +23,12 @@ namespace CommonHelpers
[DllImport("inpoutx64.dll", EntryPoint = "DlPortWritePortUchar", CallingConvention = CallingConvention.StdCall)]
public static extern byte DlPortWritePortUchar(ushort port, byte vlaue);
+ [DllImport("inpoutx64.dll", CallingConvention = CallingConvention.StdCall)]
+ public static extern bool GetPhysLong(IntPtr pbPhysAddr, out uint physValue);
+
+ [DllImport("inpoutx64.dll", CallingConvention = CallingConvention.StdCall)]
+ public static extern bool SetPhysLong(IntPtr pbPhysAddr, uint physValue);
+
public static byte[] ReadMemory(IntPtr baseAddress, uint size)
{
IntPtr pdwLinAddr = MapPhysToLin(baseAddress, size, out IntPtr pPhysicalMemoryHandle);
diff --git a/CommonHelpers/WinRing0.cs b/CommonHelpers/WinRing0.cs
new file mode 100644
index 0000000..bc32de6
--- /dev/null
+++ b/CommonHelpers/WinRing0.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CommonHelpers
+{
+ public class WinRing0
+ {
+ public const Int32 OLS_DLL_NO_ERROR = 0;
+ public const Int32 OLS_DLL_UNSUPPORTED_PLATFORM = 1;
+ public const Int32 OLS_DLL_DRIVER_NOT_LOADED = 2;
+ public const Int32 OLS_DLL_DRIVER_NOT_FOUND = 3;
+ public const Int32 OLS_DLL_DRIVER_UNLOADED = 4;
+ public const Int32 OLS_DLL_DRIVER_NOT_LOADED_ON_NETWORK = 5;
+ public const Int32 OLS_DLL_UNKNOWN_ERROR = 9;
+
+ public const Int32 OLS_DRIVER_TYPE_UNKNOWN = 0;
+ public const Int32 OLS_DRIVER_TYPE_WIN_9X = 1;
+ public const Int32 OLS_DRIVER_TYPE_WIN_NT = 2;
+ public const Int32 OLS_DRIVER_TYPE_WIN_NT4 = 3;
+ public const Int32 OLS_DRIVER_TYPE_WIN_NT_X64 = 4;
+ public const Int32 OLS_DRIVER_TYPE_WIN_NT_IA64 = 5;// Reseved
+
+ public const UInt32 OLS_ERROR_PCI_BUS_NOT_EXIST = (0xE0000001);
+ public const UInt32 OLS_ERROR_PCI_NO_DEVICE = (0xE0000002);
+ public const UInt32 OLS_ERROR_PCI_WRITE_CONFIG = (0xE0000003);
+ public const UInt32 OLS_ERROR_PCI_READ_CONFIG = (0xE0000004);
+
+ public const UInt32 NO_DEVICE = 0xFFFFFFFF;
+
+ public static UInt32 PciBusDevFunc(UInt32 Bus, UInt32 Dev, UInt32 Func)
+ {
+ return ((Bus & 0xFF) << 8) | ((Dev & 0x1F) << 3) | (Func & 7);
+ }
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 GetDllStatus();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 GetDllVersion(out byte major, out byte minor, out byte revision, out byte release);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 GetDriverVersion(out byte major, out byte minor, out byte revision, out byte release);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 GetDriverType();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool InitializeOls();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern void DeinitializeOls();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool IsCpuid();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool IsMsr();
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool IsTsc();
+
+ #region PCI Bus
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern void SetPciMaxBusIndex(byte max);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern byte ReadPciConfigByte(UInt32 pci, byte address);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt16 ReadPciConfigWord(UInt32 pci, byte address);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 ReadPciConfigDword(UInt32 pci, byte address);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool ReadPciConfigByteEx(UInt32 pci, byte address, out byte value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool ReadPciConfigWordEx(UInt32 pci, byte address, out UInt16 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool ReadPciConfigDwordEx(UInt32 pci, byte address, out UInt32 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern void WritePciConfigByte(UInt32 pci, byte address, byte value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern void WritePciConfigWord(UInt32 pci, byte address, UInt16 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern void WritePciConfigDword(UInt32 pci, byte address, UInt32 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool WritePciConfigByteEx(UInt32 pci, byte address, byte value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool WritePciConfigWordEx(UInt32 pci, byte address, UInt16 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern bool WritePciConfigDwordEx(UInt32 pci, byte address, UInt32 value);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 FindPciDeviceById(UInt16 vendor, UInt16 device, byte index);
+
+ [DllImport("WinRing0x64.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern UInt32 FindPciDeviceByClass(byte baseClass, byte subClass, byte programIf, byte index);
+ #endregion
+ }
+}
diff --git a/ExternalHelpers/ExternalHelpers.csproj b/ExternalHelpers/ExternalHelpers.csproj
index 247d3ee..7c9468b 100644
--- a/ExternalHelpers/ExternalHelpers.csproj
+++ b/ExternalHelpers/ExternalHelpers.csproj
@@ -26,6 +26,12 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
diff --git a/ExternalHelpers/WinRing0x64.dll b/ExternalHelpers/WinRing0x64.dll
new file mode 100644
index 0000000..4a48c7a
Binary files /dev/null and b/ExternalHelpers/WinRing0x64.dll differ
diff --git a/ExternalHelpers/WinRing0x64.sys b/ExternalHelpers/WinRing0x64.sys
new file mode 100644
index 0000000..197c255
Binary files /dev/null and b/ExternalHelpers/WinRing0x64.sys differ
diff --git a/PowerControl/Helpers/AMD/RyzenSMU.cs b/PowerControl/Helpers/AMD/RyzenSMU.cs
new file mode 100644
index 0000000..eaa39a6
--- /dev/null
+++ b/PowerControl/Helpers/AMD/RyzenSMU.cs
@@ -0,0 +1,214 @@
+using CommonHelpers;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PowerControl.Helpers.GPU
+{
+ public class RyzenSMU : IDisposable
+ {
+ public IntPtr MMIO_ADDR;
+ public uint MMIO_SIZE;
+ public uint RES_ADDR, MSG_ADDR, PARAM_ADDR;
+ public uint INDEX_ADDR, DATA_ADDR;
+
+ IntPtr mappedAddress;
+ IntPtr physicalHandle;
+
+ public RyzenSMU()
+ {
+ }
+
+ ~RyzenSMU()
+ {
+ Dispose();
+ }
+
+ public bool Opened
+ {
+ get { return physicalHandle != IntPtr.Zero && mappedAddress != IntPtr.Zero; }
+ }
+
+ public bool Open()
+ {
+ if (physicalHandle != IntPtr.Zero)
+ return true;
+ if (MMIO_ADDR == IntPtr.Zero || MMIO_SIZE == 0)
+ return false;
+
+ try
+ {
+ mappedAddress = InpOut.MapPhysToLin(MMIO_ADDR, MMIO_SIZE, out physicalHandle);
+ }
+ catch (Exception e)
+ {
+ TraceLine("RyzenSMU: {0}", e);
+ return false;
+ }
+
+ return Opened;
+ }
+
+ public void Dispose()
+ {
+ if (physicalHandle == IntPtr.Zero)
+ return;
+
+ GC.SuppressFinalize(this);
+ InpOut.UnmapPhysicalMemory(physicalHandle, mappedAddress);
+ mappedAddress = IntPtr.Zero;
+ physicalHandle = IntPtr.Zero;
+ }
+
+ private uint RregRaw(uint reg)
+ {
+ return (uint)Marshal.ReadInt32(mappedAddress, (int)reg);
+ }
+
+ private void WregRaw(uint reg, uint value)
+ {
+ Marshal.WriteInt32(mappedAddress, (int)reg, (int)value);
+ }
+
+ private bool WregCheckedRaw(uint reg, uint value)
+ {
+ WregRaw(reg, value);
+ return RregRaw(reg) == value;
+ }
+
+ private bool Wreg(uint reg, uint value)
+ {
+ if (!Opened)
+ return false;
+
+ bool success = false;
+
+ try
+ {
+ if (reg < MMIO_SIZE)
+ {
+ return success = WregCheckedRaw(reg, value);
+ }
+ else
+ {
+ if (!WregCheckedRaw(INDEX_ADDR, reg))
+ return false;
+ if (!WregCheckedRaw(DATA_ADDR, value))
+ return false;
+ }
+
+ return success = true;
+ }
+ finally
+ {
+ TraceLine("Wreg: reg={0:X}, value={1:X} => success={2}",
+ reg, value, success);
+ }
+ }
+
+ private bool Rreg(uint reg, out uint value)
+ {
+ value = default;
+
+ if (!Opened)
+ return false;
+
+ bool success = false;
+
+ try
+ {
+ if (reg < MMIO_SIZE)
+ {
+ value = RregRaw(reg);
+ }
+ else
+ {
+ if (!WregCheckedRaw(INDEX_ADDR, reg))
+ return false;
+ value = RregRaw(DATA_ADDR);
+ }
+
+ return success = true;
+ }
+ finally
+ {
+ TraceLine("Rreg: reg={0:X} => read={1}/{1:X}, success={2}",
+ reg, value, success);
+ }
+ }
+
+ private uint WaitForResponse()
+ {
+ const int timeout = 20;
+ for (int i = 0; i < timeout; i++)
+ {
+ uint value;
+ if (!Rreg(RES_ADDR, out value))
+ return 0;
+ if (value != 0)
+ return value;
+ Thread.SpinWait(100);
+ }
+ return 0;
+ }
+
+ public bool SendMsg(ushort msg, uint param)
+ {
+ return SendMsg(msg, param, out _);
+ }
+
+ public bool SendMsg(ushort msg, uint param, out uint arg)
+ {
+ bool success = false;
+
+ arg = 0;
+
+ try
+ {
+ var res = WaitForResponse();
+ if (res != 0x1)
+ {
+ // Reset SMU state
+ if (res != 0)
+ Wreg(RES_ADDR, 1);
+ return false;
+ }
+
+ Wreg(RES_ADDR, 0);
+ Wreg(PARAM_ADDR, param);
+ Wreg(MSG_ADDR, msg);
+
+ res = WaitForResponse();
+ if (res != 0x1)
+ return false;
+
+ success = Rreg(PARAM_ADDR, out arg);
+ return success;
+ }
+ finally
+ {
+ TraceLine(">> SendMsg: msg={0:X}, param={1:X} => arg={2}/{2:X}, success={3}",
+ msg, param, arg, success);
+ }
+ }
+
+ public bool SendMsg(T msg, uint param)
+ {
+ return SendMsg((ushort)(object)msg, param, out _);
+ }
+
+ public bool SendMsg(T msg, uint param, out uint arg) where T : unmanaged
+ {
+ return SendMsg((ushort)(object)msg, param, out arg);
+ }
+
+ private static void TraceLine(string format, params object?[]? arg)
+ {
+ Trace.WriteLine(string.Format(format, arg));
+ }
+ }
+}
diff --git a/PowerControl/Helpers/AMD/VangoghGPU.cs b/PowerControl/Helpers/AMD/VangoghGPU.cs
new file mode 100644
index 0000000..f54bfbf
--- /dev/null
+++ b/PowerControl/Helpers/AMD/VangoghGPU.cs
@@ -0,0 +1,380 @@
+using CommonHelpers;
+using System.Diagnostics;
+
+namespace PowerControl.Helpers.GPU
+{
+ internal class VangoghGPU : IDisposable
+ {
+ public struct SupportedDevice
+ {
+ public ushort VendorID, DeviceID;
+ public IntPtr MMIOAddress;
+ public uint MMIOSize;
+ public uint SMUVersion;
+
+ public SupportedDevice(ushort VendorID, ushort DeviceID, uint MMIOAddress, uint MMIOSize, uint SMUVersion)
+ {
+ this.VendorID = VendorID;
+ this.DeviceID = DeviceID;
+ this.MMIOAddress = new IntPtr(MMIOAddress);
+ this.MMIOSize = MMIOSize;
+ this.SMUVersion = SMUVersion;
+ }
+
+ private void Log(string format, params object?[]? arg)
+ {
+ Trace.WriteLine(string.Format("GPU: [{0:X4}:{1:X4}] ", VendorID, DeviceID) + string.Format(format, arg));
+ }
+
+ public bool Found()
+ {
+ // Strong validate device that it has our "memory layout"
+ var pciAddress = WinRing0.FindPciDeviceById(VendorID, DeviceID, 0);
+ if (pciAddress == WinRing0.NO_DEVICE)
+ {
+ Log("PCI: not found");
+ return false;
+ }
+
+ Log("PCI: [{0:X8}]", pciAddress);
+
+ var barAddr = WinRing0.ReadPciConfigDword(pciAddress, 0x24); // BAR6
+ if (MMIOAddress != new IntPtr(barAddr))
+ {
+ Log("PCI: [{0:X8}] => BAR: {1:X8} vs {2:X8} => mismatch",
+ pciAddress, MMIOAddress, barAddr);
+ return false;
+ }
+
+ Log("PCI: [{0:X8}] => BAR: {1:X8} => OK",
+ pciAddress, barAddr);
+ return true;
+ }
+
+ public VangoghGPU? Open()
+ {
+ var gpu = VangoghGPU.OpenMMIO(MMIOAddress, MMIOSize);
+ if (gpu == null)
+ return null;
+
+ // Check supported SMU version
+ var smuVersion = gpu.SMUVersion;
+ if (smuVersion != SMUVersion)
+ {
+ Log("SMU: {0:X8} => not supported", smuVersion);
+ return null;
+ }
+
+ Log("SMU: {0:X8} => detected", smuVersion);
+ return gpu;
+ }
+ };
+
+ public static readonly SupportedDevice[] SupportedDevices = new SupportedDevice[]
+ {
+ // SteamDeck
+ new SupportedDevice(0x1002, 0x163F, 0x80300000, 0x80380000 - 0x80300000, 0x43F3900)
+ };
+
+ private static SupportedDevice? DetectedDevice;
+
+ public static bool IsSupported
+ {
+ get { return DetectedDevice != null; }
+ }
+
+ public static VangoghGPU? Open()
+ {
+ return DetectedDevice?.Open();
+ }
+
+ public static bool Detect()
+ {
+ foreach ( var device in SupportedDevices)
+ {
+ if (!device.Found())
+ continue;
+
+ using (var gpu = device.Open())
+ {
+ if (gpu is not null)
+ {
+ DetectedDevice = device;
+ return true;
+ }
+ }
+ }
+
+ DetectedDevice = null;
+ return false;
+ }
+
+ // Addresses:
+ // drivers/gpu/drm/amd/include/vangogh_ip_offset.h => MP1_BASE => 0x00016000
+ // drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c => mmMP1_SMN_C2PMSG_* => 0x0282/0x0292/0x029a
+ // drivers/gpu/drm/amd/include/asic_reg/nbio/nbio_7_4_offset.h => mmPCIE_INDEX2/mmPCIE_DATA2 => 0x000e/0x000f
+ //
+ // Messages:
+ // drivers/gpu/drm/amd/pm/inc/smu_v11_5_ppsmc.h
+
+ private RyzenSMU smu;
+
+ ~VangoghGPU()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ if (smu != null)
+ smu.Dispose();
+ }
+
+ private static VangoghGPU? OpenMMIO(IntPtr mmioAddress, uint mmioSize)
+ {
+ var gpu = new VangoghGPU
+ {
+ smu = new RyzenSMU()
+ {
+ MMIO_ADDR = mmioAddress,
+ MMIO_SIZE = mmioSize,
+ RES_ADDR = 0x0001629A * 4,
+ MSG_ADDR = 0x00016282 * 4,
+ PARAM_ADDR = 0x00016292 * 4,
+ INDEX_ADDR = 0xE * 4,
+ DATA_ADDR = 0xF * 4
+ }
+ };
+
+ if (!gpu.smu.Open())
+ return null;
+
+ return gpu;
+ }
+
+ public UInt32 SMUVersion
+ {
+ get { return getValue(Message.PPSMC_MSG_GetSmuVersion); }
+ }
+
+ public UInt32 IfVersion
+ {
+ get { return getValue(Message.PPSMC_MSG_GetDriverIfVersion); }
+ }
+
+ const uint MIN_TDP = 3000;
+ const uint MAX_TDP = 15000;
+
+ public uint SlowTDP
+ {
+ get { return getValue(Message.PPSMC_MSG_GetSlowPPTLimit); }
+ set { setValue(Message.PPSMC_MSG_SetSlowPPTLimit, value, MIN_TDP, MAX_TDP); }
+ }
+
+ public uint FastTDP
+ {
+ get { return getValue(Message.PPSMC_MSG_GetFastPPTLimit); }
+ set { setValue(Message.PPSMC_MSG_SetFastPPTLimit, value, MIN_TDP, MAX_TDP); }
+ }
+
+ public uint GfxClock
+ {
+ get { return getValue(Message.PPSMC_MSG_GetGfxclkFrequency); }
+ }
+
+ public uint FClock
+ {
+ get { return getValue(Message.PPSMC_MSG_GetFclkFrequency); }
+ }
+
+ const uint MIN_CPU_CLOCK = 800;
+ const uint MAX_CPU_CLOCK = 3500;
+
+ public uint MinCPUClock
+ {
+ set { setValue(Message.PPSMC_MSG_SetSoftMinCclk, value, MIN_CPU_CLOCK, MAX_CPU_CLOCK); }
+ }
+
+ public uint MaxCPUClock
+ {
+ set { setValue(Message.PPSMC_MSG_SetSoftMaxCclk, value, MIN_CPU_CLOCK, MAX_CPU_CLOCK); }
+ }
+
+ const uint MIN_GFX_CLOCK = 200;
+ const uint MAX_GFX_CLOCK = 1600;
+
+ public uint HardMinGfxClock
+ {
+ set { setValue(Message.PPSMC_MSG_SetHardMinGfxClk, value, MIN_GFX_CLOCK, MAX_GFX_CLOCK); }
+ }
+
+ public uint SoftMinGfxClock
+ {
+ set { setValue(Message.PPSMC_MSG_SetSoftMinGfxclk, value, MIN_GFX_CLOCK, MAX_GFX_CLOCK); }
+ }
+
+ public uint SoftMaxGfxClock
+ {
+ set { setValue(Message.PPSMC_MSG_SetSoftMaxGfxClk, value, MIN_GFX_CLOCK, MAX_GFX_CLOCK); }
+ }
+
+ public Dictionary All
+ {
+ get
+ {
+ var dict = new Dictionary();
+
+ foreach(var key in ValuesGetters)
+ {
+ if (!this.smu.SendMsg(key, 0, out var value))
+ continue;
+
+ var keyString = key.ToString().Replace("PPSMC_MSG_Get", "");
+ dict[keyString] = value;
+ }
+
+ return dict;
+ }
+ }
+
+ private uint getValue(Message msg)
+ {
+ this.smu.SendMsg(msg, 0, out var value);
+ return value;
+ }
+
+ private void setValue(Message msg, uint value, uint min = UInt32.MinValue, uint max = UInt32.MaxValue)
+ {
+ this.smu.SendMsg(msg, Math.Clamp(value, min, max));
+ }
+
+ private readonly Message[] ValuesGetters = new Message[]
+ {
+ Message.PPSMC_MSG_GetGfxclkFrequency,
+ Message.PPSMC_MSG_GetFclkFrequency,
+ Message.PPSMC_MSG_GetPptLimit,
+ Message.PPSMC_MSG_GetThermalLimit,
+ Message.PPSMC_MSG_GetFastPPTLimit,
+ Message.PPSMC_MSG_GetSlowPPTLimit,
+
+ // Those values return PPSMC_Result_CmdRejectedPrereq
+ Message.PPSMC_MSG_GetCurrentTemperature,
+ Message.PPSMC_MSG_GetCurrentPower,
+ Message.PPSMC_MSG_GetCurrentCurrent,
+ Message.PPSMC_MSG_GetCurrentFreq,
+ Message.PPSMC_MSG_GetCurrentVoltage,
+ Message.PPSMC_MSG_GetAverageCpuActivity,
+ Message.PPSMC_MSG_GetAverageGfxActivity,
+ Message.PPSMC_MSG_GetAveragePower,
+ Message.PPSMC_MSG_GetAverageTemperature
+ };
+
+ enum Result : byte
+ {
+ PPSMC_Result_OK = 0x1,
+ PPSMC_Result_Failed = 0xFF,
+ PPSMC_Result_UnknownCmd = 0xFE,
+ PPSMC_Result_CmdRejectedPrereq = 0xFD,
+ PPSMC_Result_CmdRejectedBusy = 0xFC
+ }
+
+ enum Tables : byte
+ {
+ TABLE_BIOS_IF = 0, // Called by BIOS
+ TABLE_WATERMARKS = 1, // Called by DAL through VBIOS
+ TABLE_CUSTOM_DPM = 2, // Called by Driver
+ TABLE_SPARE1 = 3,
+ TABLE_DPMCLOCKS = 4, // Called by Driver
+ TABLE_SPARE2 = 5, // Called by Tools
+ TABLE_MODERN_STDBY = 6, // Called by Tools for Modern Standby Log
+ TABLE_SMU_METRICS = 7, // Called by Driver
+ TABLE_COUNT = 8
+ }
+
+ enum Message : ushort
+ {
+ PPSMC_MSG_TestMessage = 0x1,
+ PPSMC_MSG_GetSmuVersion = 0x2,
+ PPSMC_MSG_GetDriverIfVersion = 0x3,
+ PPSMC_MSG_EnableGfxOff = 0x4,
+ PPSMC_MSG_DisableGfxOff = 0x5,
+ PPSMC_MSG_PowerDownIspByTile = 0x6, // ISP is power gated by default
+ PPSMC_MSG_PowerUpIspByTile = 0x7,
+ PPSMC_MSG_PowerDownVcn = 0x8, // VCN is power gated by default
+ PPSMC_MSG_PowerUpVcn = 0x9,
+ PPSMC_MSG_RlcPowerNotify = 0xA,
+ PPSMC_MSG_SetHardMinVcn = 0xB, // For wireless display
+ PPSMC_MSG_SetSoftMinGfxclk = 0xC, //Sets SoftMin for GFXCLK. Arg is in MHz
+ PPSMC_MSG_ActiveProcessNotify = 0xD,
+ PPSMC_MSG_SetHardMinIspiclkByFreq = 0xE,
+ PPSMC_MSG_SetHardMinIspxclkByFreq = 0xF,
+ PPSMC_MSG_SetDriverDramAddrHigh = 0x10,
+ PPSMC_MSG_SetDriverDramAddrLow = 0x11,
+ PPSMC_MSG_TransferTableSmu2Dram = 0x12,
+ PPSMC_MSG_TransferTableDram2Smu = 0x13,
+ PPSMC_MSG_GfxDeviceDriverReset = 0x14, //mode 2 reset during TDR
+ PPSMC_MSG_GetEnabledSmuFeatures = 0x15,
+ PPSMC_MSG_spare1 = 0x16,
+ PPSMC_MSG_SetHardMinSocclkByFreq = 0x17,
+ PPSMC_MSG_SetSoftMinFclk = 0x18, //Used to be PPSMC_MSG_SetMinVideoFclkFreq
+ PPSMC_MSG_SetSoftMinVcn = 0x19,
+ PPSMC_MSG_EnablePostCode = 0x1A,
+ PPSMC_MSG_GetGfxclkFrequency = 0x1B,
+ PPSMC_MSG_GetFclkFrequency = 0x1C,
+ PPSMC_MSG_AllowGfxOff = 0x1D,
+ PPSMC_MSG_DisallowGfxOff = 0x1E,
+ PPSMC_MSG_SetSoftMaxGfxClk = 0x1F,
+ PPSMC_MSG_SetHardMinGfxClk = 0x20,
+ PPSMC_MSG_SetSoftMaxSocclkByFreq = 0x21,
+ PPSMC_MSG_SetSoftMaxFclkByFreq = 0x22,
+ PPSMC_MSG_SetSoftMaxVcn = 0x23,
+ PPSMC_MSG_spare2 = 0x24,
+ PPSMC_MSG_SetPowerLimitPercentage = 0x25,
+ PPSMC_MSG_PowerDownJpeg = 0x26,
+ PPSMC_MSG_PowerUpJpeg = 0x27,
+ PPSMC_MSG_SetHardMinFclkByFreq = 0x28,
+ PPSMC_MSG_SetSoftMinSocclkByFreq = 0x29,
+ PPSMC_MSG_PowerUpCvip = 0x2A,
+ PPSMC_MSG_PowerDownCvip = 0x2B,
+ PPSMC_MSG_GetPptLimit = 0x2C,
+ PPSMC_MSG_GetThermalLimit = 0x2D,
+ PPSMC_MSG_GetCurrentTemperature = 0x2E,
+ PPSMC_MSG_GetCurrentPower = 0x2F,
+ PPSMC_MSG_GetCurrentVoltage = 0x30,
+ PPSMC_MSG_GetCurrentCurrent = 0x31,
+ PPSMC_MSG_GetAverageCpuActivity = 0x32,
+ PPSMC_MSG_GetAverageGfxActivity = 0x33,
+ PPSMC_MSG_GetAveragePower = 0x34,
+ PPSMC_MSG_GetAverageTemperature = 0x35,
+ PPSMC_MSG_SetAveragePowerTimeConstant = 0x36,
+ PPSMC_MSG_SetAverageActivityTimeConstant = 0x37,
+ PPSMC_MSG_SetAverageTemperatureTimeConstant = 0x38,
+ PPSMC_MSG_SetMitigationEndHysteresis = 0x39,
+ PPSMC_MSG_GetCurrentFreq = 0x3A,
+ PPSMC_MSG_SetReducedPptLimit = 0x3B,
+ PPSMC_MSG_SetReducedThermalLimit = 0x3C,
+ PPSMC_MSG_DramLogSetDramAddr = 0x3D,
+ PPSMC_MSG_StartDramLogging = 0x3E,
+ PPSMC_MSG_StopDramLogging = 0x3F,
+ PPSMC_MSG_SetSoftMinCclk = 0x40,
+ PPSMC_MSG_SetSoftMaxCclk = 0x41,
+ PPSMC_MSG_SetDfPstateActiveLevel = 0x42,
+ PPSMC_MSG_SetDfPstateSoftMinLevel = 0x43,
+ PPSMC_MSG_SetCclkPolicy = 0x44,
+ PPSMC_MSG_DramLogSetDramAddrHigh = 0x45,
+ PPSMC_MSG_DramLogSetDramBufferSize = 0x46,
+ PPSMC_MSG_RequestActiveWgp = 0x47,
+ PPSMC_MSG_QueryActiveWgp = 0x48,
+ PPSMC_MSG_SetFastPPTLimit = 0x49,
+ PPSMC_MSG_SetSlowPPTLimit = 0x4A,
+ PPSMC_MSG_GetFastPPTLimit = 0x4B,
+ PPSMC_MSG_GetSlowPPTLimit = 0x4C,
+ PPSMC_Message_Count = 0x4D,
+ }
+ private static void TraceLine(string format, params object?[]? arg)
+ {
+ Trace.WriteLine(string.Format(format, arg));
+ }
+ }
+}
diff --git a/PowerControl/MenuStack.cs b/PowerControl/MenuStack.cs
index 53dd124..7c104f1 100644
--- a/PowerControl/MenuStack.cs
+++ b/PowerControl/MenuStack.cs
@@ -1,14 +1,7 @@
-using CommonHelpers;
+using CommonHelpers;
using PowerControl.Helpers;
using PowerControl.Helpers.GPU;
-using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Navigation;
-using static PowerControl.Helpers.PhysicalMonitorBrightnessController;
namespace PowerControl
{
@@ -157,42 +150,85 @@ namespace PowerControl
new Menu.MenuItemWithOptions()
{
Name = "TDP",
- Options = { "Auto", "3W", "4W", "5W", "6W", "7W", "8W", "10W", "12W", "15W" },
+ Options = { "3W", "4W", "5W", "6W", "7W", "8W", "10W", "12W", "15W" },
ApplyDelay = 1000,
- ResetValue = () => { return "Auto"; },
+ ResetValue = () => { return "15W"; },
+ ActiveOption = "?",
ApplyValue = delegate(object selected)
{
- int mW = 15000;
+ uint mW = uint.Parse(selected.ToString().Replace("W", "")) * 1000;
- if (selected.ToString() != "Auto")
+ if (VangoghGPU.IsSupported)
{
- mW = int.Parse(selected.ToString().Replace("W", "")) * 1000;
+ using (var sd = VangoghGPU.Open())
+ {
+ if (sd is null)
+ return null;
+
+ sd.SlowTDP = mW;
+ sd.FastTDP = mW;
+ }
}
-
- int stampLimit = mW/10;
-
- Process.Start(new ProcessStartInfo()
+ else
{
- FileName = "Resources/RyzenAdj/ryzenadj.exe",
- ArgumentList = {
- "--stapm-limit=" + stampLimit.ToString(),
- "--slow-limit=" + mW.ToString(),
- "--fast-limit=" + mW.ToString(),
- },
- WindowStyle = ProcessWindowStyle.Hidden,
- UseShellExecute = false,
- CreateNoWindow = true
- });
+ uint stampLimit = mW/10;
+
+ Process.Start(new ProcessStartInfo()
+ {
+ FileName = "Resources/RyzenAdj/ryzenadj.exe",
+ ArgumentList = {
+ "--stapm-limit=" + stampLimit.ToString(),
+ "--slow-limit=" + mW.ToString(),
+ "--fast-limit=" + mW.ToString(),
+ },
+ WindowStyle = ProcessWindowStyle.Hidden,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ });
+ }
return selected;
}
},
new Menu.MenuItemWithOptions()
{
- Name = "GPU Clock",
- Options = { "Auto", 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600 },
+ Name = "GPU Min",
+ Options = { "200MHz", "400MHz", "800MHz", "1200MHz", "1600MHz" },
ApplyDelay = 1000,
- Visible = false
+ Visible = VangoghGPU.IsSupported,
+ ActiveOption = "?",
+ ResetValue = () => { return "200MHz"; },
+ ApplyValue = delegate(object selected)
+ {
+ using (var sd = VangoghGPU.Open())
+ {
+ if (sd is null)
+ return null;
+
+ sd.HardMinGfxClock = uint.Parse(selected.ToString().Replace("MHz", ""));
+ return sd.GfxClock.ToString() + "MHz";
+ }
+ }
+ },
+ new Menu.MenuItemWithOptions()
+ {
+ Name = "CPU Max",
+ Options = { "800MHz", "2000MHz", "3000MHz", "3500MHz" },
+ ApplyDelay = 1000,
+ ActiveOption = "?",
+ Visible = VangoghGPU.IsSupported,
+ ResetValue = () => { return "3500MHz"; },
+ ApplyValue = delegate(object selected)
+ {
+ using (var sd = VangoghGPU.Open())
+ {
+ if (sd is null)
+ return null;
+
+ sd.MaxCPUClock = uint.Parse(selected.ToString().Replace("MHz", ""));
+ return selected;
+ }
+ }
},
new Menu.MenuItemWithOptions()
{
diff --git a/PowerControl/Program.cs b/PowerControl/Program.cs
index ad2a0fd..ed80963 100644
--- a/PowerControl/Program.cs
+++ b/PowerControl/Program.cs
@@ -1,4 +1,6 @@
-using PowerControl;
+using CommonHelpers;
+using PowerControl.Helpers.GPU;
+using System.Diagnostics;
namespace PowerControl
{
@@ -10,6 +12,17 @@ namespace PowerControl
[STAThread]
static void Main()
{
+#if DEBUG
+ Settings.Default.EnableExperimentalFeatures = true;
+#endif
+
+ if (Settings.Default.EnableExperimentalFeatures)
+ {
+ Trace.WriteLine("WinRing0 initialized=" + WinRing0.InitializeOls().ToString());
+
+ VangoghGPU.Detect();
+ }
+
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
diff --git a/README.md b/README.md
index 7075d70..2e527f2 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
This repository contains my own personal set of tools to help running Windows on Steam Deck.
**This software is provided on best-effort basis and can break your SteamDeck.**
-To learn more go to [Risks](#4-risk).
+To learn more go to [Risks](#4-risks).
## 1. Steam Deck Fan Control
@@ -25,11 +25,7 @@ It provides 3 modes of operation:
### 1.2. How it works?
-This uses highly unstable (and firmware specific) direct manipulation of kernel memory
-to control usage of EC (Embedded Controller) and setting desired fan RPM via VLV0100.
-
-It was build based on knowledge gained in Steam Deck kernel patch and DSDT presented by bios.
-The memory addresses used are hardcoded and can be changed any moment by the Bios update.
+See [Risks](#4-risks).
### 1.3. Limitations
@@ -128,7 +124,7 @@ Since the SWICD will mess-up with double inputs you need to configure the follow
-## 4. Risk
+## 4. Risks
**This software is provided on best-effort basis and can break your SteamDeck.** It does a direct manipulation
of kernel memory to control usage the EC (Embedded Controller) and setting desired fan RPM via VLV0100
@@ -142,6 +138,17 @@ The memory addresses used are hardcoded and can be changed any moment by the Bio
Fortunately quite amount of people are using it with a success and no problems observed.
However, you should be aware of the consequences.
+### 4.1. Risk of CPU and GPU frequency change
+
+The APU manipulation of CPU and GPU frequency uses a ported implementation from Linux kernel.
+It is more unstable than Fan Control (since the Fan Control is controller managing EC on Windows).
+The change of the CPU and GPU frequency switch might sometimes crash system.
+The AMD Display Driver periodically requests GPU metrics and in some rare circumstances it might
+the change might lock the GPU driver. If something like this happens it is advised to
+power shutdown the device. Disconnect from charging, and hold power button for 10s.
+Power device normally afterwards. This is rather unlikely to break the device if done right
+after when such event occurs.
+
## 5. Anti-Cheat and Antivirus software
Since this project uses direct manipulation of kernel memory via `inpoutx64.dll` and `WinRing0x64.dll`
diff --git a/RELEASE.md b/RELEASE.md
index e3e427d..c254776 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -6,5 +6,6 @@
- Press `3 dots + L4 + R4 + L5 + R5` to reset (TDP, Refresh Rate, FPS limit) to default
- Allow to disable SMT (second threads of each physical cores)
- Show Full OSD if in PowerControl mode
+- Highly risky: Allow to change CPU and GPU frequency (enable `EnableExperimentalFeatures` in `PowerControl.dll.config`)
If you found it useful buy me [Ko-fi](https://ko-fi.com/ayufan).