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).