Add VangoghGPU SMU controller with CPU and GPU clock frequency control

This commit is contained in:
Kamil Trzciński 2022-11-15 15:42:05 +01:00
parent 9eba0239a1
commit a3a25bfb4e
11 changed files with 816 additions and 39 deletions

View file

@ -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);

114
CommonHelpers/WinRing0.cs Normal file
View file

@ -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
}
}

View file

@ -26,6 +26,12 @@
<None Update="RTSSSharedMemoryNET.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="WinRing0x64.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="WinRing0x64.sys">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Binary file not shown.

Binary file not shown.

View file

@ -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>(T msg, uint param)
{
return SendMsg((ushort)(object)msg, param, out _);
}
public bool SendMsg<T>(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));
}
}
}

View file

@ -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<string, uint> All
{
get
{
var dict = new Dictionary<string, uint>();
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));
}
}
}

View file

@ -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()
{

View file

@ -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();

View file

@ -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
<img src="images/power_control_swicd_3.png" height="500"/>
## 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`

View file

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