first commit

This commit is contained in:
2025-04-07 07:44:27 -07:00
commit d6cde0c05e
512 changed files with 142392 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Linq;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Battery;
internal sealed class Battery : Hardware
{
private readonly SafeFileHandle _batteryHandle;
private readonly Kernel32.BATTERY_INFORMATION _batteryInformation;
private readonly uint _batteryTag;
private readonly Sensor _chargeDischargeCurrent;
private readonly Sensor _chargeDischargeRate;
private readonly Sensor _chargeLevel;
private readonly Sensor _degradationLevel;
private readonly Sensor _designedCapacity;
private readonly Sensor _fullChargedCapacity;
private readonly Sensor _remainingCapacity;
private readonly Sensor _remainingTime;
private readonly Sensor _temperature;
private readonly Sensor _voltage;
public Battery
(
string name,
string manufacturer,
SafeFileHandle batteryHandle,
Kernel32.BATTERY_INFORMATION batteryInfo,
uint batteryTag,
ISettings settings) :
base(name, new Identifier("battery", $"{name.Replace(' ', '-')}_{batteryTag}"), settings)
{
Name = name;
Manufacturer = manufacturer;
_batteryTag = batteryTag;
_batteryHandle = batteryHandle;
_batteryInformation = batteryInfo;
if (batteryInfo.Chemistry.SequenceEqual(new[] { 'P', 'b', 'A', 'c' }))
{
Chemistry = BatteryChemistry.LeadAcid;
}
else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'L', 'I', 'O', 'N' }) || batteryInfo.Chemistry.SequenceEqual(new[] { 'L', 'i', '-', 'I' }))
{
Chemistry = BatteryChemistry.LithiumIon;
}
else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'C', 'd' }))
{
Chemistry = BatteryChemistry.NickelCadmium;
}
else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'M', 'H' }))
{
Chemistry = BatteryChemistry.NickelMetalHydride;
}
else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'N', 'i', 'Z', 'n' }))
{
Chemistry = BatteryChemistry.NickelZinc;
}
else if (batteryInfo.Chemistry.SequenceEqual(new[] { 'R', 'A', 'M', '\x00' }))
{
Chemistry = BatteryChemistry.AlkalineManganese;
}
else
{
Chemistry = BatteryChemistry.Unknown;
}
_designedCapacity = new Sensor("Designed Capacity", 0, SensorType.Energy, this, settings);
_fullChargedCapacity = new Sensor("Fully-Charged Capacity", 1, SensorType.Energy, this, settings);
_degradationLevel = new Sensor("Degradation Level", 1, SensorType.Level, this, settings);
_chargeLevel = new Sensor("Charge Level", 0, SensorType.Level, this, settings);
_voltage = new Sensor("Voltage", 0, SensorType.Voltage, this, settings);
_remainingCapacity = new Sensor("Remaining Capacity", 2, SensorType.Energy, this, settings);
_chargeDischargeCurrent = new Sensor("Charge/Discharge Current", 0, SensorType.Current, this, settings);
_chargeDischargeRate = new Sensor("Charge/Discharge Rate", 0, SensorType.Power, this, settings);
_remainingTime = new Sensor("Remaining Time (Estimated)", 0, SensorType.TimeSpan, this, settings);
_temperature = new Sensor("Battery Temperature", 0, SensorType.Temperature, this, settings);
if (batteryInfo.FullChargedCapacity is not Kernel32.BATTERY_UNKNOWN_CAPACITY &&
batteryInfo.DesignedCapacity is not Kernel32.BATTERY_UNKNOWN_CAPACITY)
{
_designedCapacity.Value = batteryInfo.DesignedCapacity;
_fullChargedCapacity.Value = batteryInfo.FullChargedCapacity;
_degradationLevel.Value = 100f - (batteryInfo.FullChargedCapacity * 100f / batteryInfo.DesignedCapacity);
DesignedCapacity = batteryInfo.DesignedCapacity;
FullChargedCapacity = batteryInfo.FullChargedCapacity;
ActivateSensor(_designedCapacity);
ActivateSensor(_fullChargedCapacity);
ActivateSensor(_degradationLevel);
}
}
public float? ChargeDischargeCurrent { get; private set; }
public float? ChargeDischargeRate { get; private set; }
public float? ChargeLevel => _chargeLevel.Value;
public BatteryChemistry Chemistry { get; }
public float? DegradationLevel => _degradationLevel.Value;
public float? DesignedCapacity { get; }
public float? FullChargedCapacity { get; }
public override HardwareType HardwareType => HardwareType.Battery;
public string Manufacturer { get; }
public float? RemainingCapacity => _remainingCapacity.Value;
public float? RemainingTime => _remainingTime.Value;
public float? Temperature => _temperature.Value;
public float? Voltage => _voltage.Value;
private void ActivateSensorIfValueNotNull(ISensor sensor)
{
if (sensor.Value != null)
ActivateSensor(sensor);
else
DeactivateSensor(sensor);
}
public override void Update()
{
Kernel32.BATTERY_WAIT_STATUS bws = default;
bws.BatteryTag = _batteryTag;
Kernel32.BATTERY_STATUS batteryStatus = default;
if (Kernel32.DeviceIoControl(_batteryHandle,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_STATUS,
ref bws,
Marshal.SizeOf(bws),
ref batteryStatus,
Marshal.SizeOf(batteryStatus),
out _,
IntPtr.Zero))
{
if (batteryStatus.Capacity != Kernel32.BATTERY_UNKNOWN_CAPACITY)
_remainingCapacity.Value = batteryStatus.Capacity;
else
_remainingCapacity.Value = null;
_chargeLevel.Value = _remainingCapacity.Value * 100f / _fullChargedCapacity.Value;
if (batteryStatus.Voltage is not Kernel32.BATTERY_UNKNOWN_VOLTAGE)
_voltage.Value = batteryStatus.Voltage / 1000f;
else
_voltage.Value = null;
if (batteryStatus.Rate is Kernel32.BATTERY_UNKNOWN_RATE)
{
ChargeDischargeCurrent = null;
_chargeDischargeCurrent.Value = null;
ChargeDischargeRate = null;
_chargeDischargeRate.Value = null;
}
else
{
float rateWatts = batteryStatus.Rate / 1000f;
ChargeDischargeRate = rateWatts;
_chargeDischargeRate.Value = Math.Abs(rateWatts);
float? current = rateWatts / _voltage.Value;
ChargeDischargeCurrent = current;
if (current is not null)
_chargeDischargeCurrent.Value = Math.Abs(current.Value);
else
_chargeDischargeCurrent.Value = null;
if (rateWatts > 0)
{
_chargeDischargeRate.Name = "Charge Rate";
_chargeDischargeCurrent.Name = "Charge Current";
}
else if (rateWatts < 0)
{
_chargeDischargeRate.Name = "Discharge Rate";
_chargeDischargeCurrent.Name = "Discharge Current";
}
else
{
_chargeDischargeRate.Name = "Charge/Discharge Rate";
_chargeDischargeCurrent.Name = "Charge/Discharge Current";
}
}
}
uint estimatedRunTime = 0;
Kernel32.BATTERY_QUERY_INFORMATION bqi = default;
bqi.BatteryTag = _batteryTag;
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryEstimatedTime;
if (Kernel32.DeviceIoControl(_batteryHandle,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ref estimatedRunTime,
Marshal.SizeOf<uint>(),
out _,
IntPtr.Zero))
{
if (estimatedRunTime != Kernel32.BATTERY_UNKNOWN_TIME)
_remainingTime.Value = estimatedRunTime;
else
_remainingTime.Value = null;
}
else
{
_remainingTime.Value = null;
}
uint temperature = 0;
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryTemperature;
if (Kernel32.DeviceIoControl(_batteryHandle,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ref temperature,
Marshal.SizeOf<uint>(),
out _,
IntPtr.Zero))
{
_temperature.Value = (temperature / 10f) - 273.15f;
}
else
{
_temperature.Value = null;
}
ActivateSensorIfValueNotNull(_remainingCapacity);
ActivateSensorIfValueNotNull(_chargeLevel);
ActivateSensorIfValueNotNull(_voltage);
ActivateSensorIfValueNotNull(_chargeDischargeCurrent);
ActivateSensorIfValueNotNull(_chargeDischargeRate);
ActivateSensorIfValueNotNull(_remainingTime);
ActivateSensorIfValueNotNull(_temperature);
}
public override void Close()
{
base.Close();
_batteryHandle.Close();
}
}

View File

@@ -0,0 +1,17 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Battery;
internal enum BatteryChemistry
{
Unknown,
LeadAcid,
NickelCadmium,
NickelMetalHydride,
LithiumIon,
NickelZinc,
AlkalineManganese
}

View File

@@ -0,0 +1,235 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Battery;
internal class BatteryGroup : IGroup
{
private readonly List<Battery> _hardware = new();
static bool QueryStringFromBatteryInfo(SafeFileHandle battery, Kernel32.BATTERY_QUERY_INFORMATION bqi, out string value)
{
const int maxLoadString = 100;
value = null;
bool result = false;
IntPtr ptrString = Marshal.AllocHGlobal(maxLoadString);
if (Kernel32.DeviceIoControl(battery,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ptrString,
maxLoadString,
out uint stringSizeBytes,
IntPtr.Zero))
{
// Use the value stored in stringSizeBytes to avoid relying on a
// terminator char.
// See https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/1158#issuecomment-1979559929
int stringSizeChars = (int)stringSizeBytes / 2;
value = Marshal.PtrToStringUni(ptrString, stringSizeChars);
result = true;
}
Marshal.FreeHGlobal(ptrString);
return result;
}
public unsafe BatteryGroup(ISettings settings)
{
// No implementation for battery information on Unix systems
if (Software.OperatingSystem.IsUnix)
return;
IntPtr hdev = SetupApi.SetupDiGetClassDevs(ref SetupApi.GUID_DEVICE_BATTERY, IntPtr.Zero, IntPtr.Zero, SetupApi.DIGCF_PRESENT | SetupApi.DIGCF_DEVICEINTERFACE);
if (hdev != SetupApi.INVALID_HANDLE_VALUE)
{
for (uint i = 0; ; i++)
{
SetupApi.SP_DEVICE_INTERFACE_DATA did = default;
did.cbSize = (uint)Marshal.SizeOf(typeof(SetupApi.SP_DEVICE_INTERFACE_DATA));
if (!SetupApi.SetupDiEnumDeviceInterfaces(hdev,
IntPtr.Zero,
ref SetupApi.GUID_DEVICE_BATTERY,
i,
ref did))
{
if (Marshal.GetLastWin32Error() == SetupApi.ERROR_NO_MORE_ITEMS)
break;
}
else
{
SetupApi.SetupDiGetDeviceInterfaceDetail(hdev,
did,
IntPtr.Zero,
0,
out uint cbRequired,
IntPtr.Zero);
if (Marshal.GetLastWin32Error() == SetupApi.ERROR_INSUFFICIENT_BUFFER)
{
IntPtr pdidd = Kernel32.LocalAlloc(Kernel32.LPTR, cbRequired);
Marshal.WriteInt32(pdidd, Environment.Is64BitProcess ? 8 : 4 + Marshal.SystemDefaultCharSize); // cbSize.
if (SetupApi.SetupDiGetDeviceInterfaceDetail(hdev,
did,
pdidd,
cbRequired,
out _,
IntPtr.Zero))
{
string devicePath = new((char*)(pdidd + 4));
SafeFileHandle battery = Kernel32.CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
if (!battery.IsInvalid)
{
Kernel32.BATTERY_QUERY_INFORMATION bqi = default;
uint dwWait = 0;
if (Kernel32.DeviceIoControl(battery,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_TAG,
ref dwWait,
Marshal.SizeOf(dwWait),
ref bqi.BatteryTag,
Marshal.SizeOf(bqi.BatteryTag),
out _,
IntPtr.Zero))
{
Kernel32.BATTERY_INFORMATION bi = default;
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryInformation;
if (Kernel32.DeviceIoControl(battery,
Kernel32.IOCTL.IOCTL_BATTERY_QUERY_INFORMATION,
ref bqi,
Marshal.SizeOf(bqi),
ref bi,
Marshal.SizeOf(bi),
out _,
IntPtr.Zero))
{
// Only batteries count.
if (bi.Capabilities.HasFlag(Kernel32.BatteryCapabilities.BATTERY_SYSTEM_BATTERY))
{
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryDeviceName;
QueryStringFromBatteryInfo(battery, bqi, out string batteryName);
bqi.InformationLevel = Kernel32.BATTERY_QUERY_INFORMATION_LEVEL.BatteryManufactureName;
QueryStringFromBatteryInfo(battery, bqi, out string manufacturer);
_hardware.Add(new Battery(batteryName, manufacturer, battery, bi, bqi.BatteryTag, settings));
}
}
}
}
}
Kernel32.LocalFree(pdidd);
}
}
}
SetupApi.SetupDiDestroyDeviceInfoList(hdev);
}
}
/// <inheritdoc />
public IReadOnlyList<IHardware> Hardware => _hardware;
/// <inheritdoc />
public void Close()
{
foreach (Battery battery in _hardware)
battery.Close();
}
/// <inheritdoc />
public string GetReport()
{
StringBuilder reportBuilder = new();
uint count = 1;
foreach (Battery bat in _hardware)
{
string chemistry = bat.Chemistry switch
{
BatteryChemistry.LeadAcid => "Lead Acid",
BatteryChemistry.NickelCadmium => "Nickel-Cadmium",
BatteryChemistry.NickelMetalHydride => "Nickel-Metal Hydride",
BatteryChemistry.LithiumIon => "Lithium Ion",
BatteryChemistry.NickelZinc => "Nickel-Zinc",
BatteryChemistry.AlkalineManganese => "Rechargeable Alkaline-Manganese",
_ => "Unknown"
};
reportBuilder.Append("Battery #").Append(count).AppendLine(":")
.Append(" Name: ").AppendLine(bat.Name)
.Append(" Manufacturer: ").AppendLine(bat.Manufacturer)
.Append(" Chemistry: ").AppendLine(chemistry);
if (bat.DegradationLevel.HasValue)
reportBuilder.Append(" Degradation Level: ").AppendFormat("{0:F2}", bat.DegradationLevel).AppendLine(" %");
if (bat.DesignedCapacity.HasValue)
reportBuilder.Append(" Designed Capacity: ").Append(bat.DesignedCapacity).AppendLine(" mWh");
if (bat.FullChargedCapacity.HasValue)
reportBuilder.Append(" Fully-Charged Capacity: ").Append(bat.FullChargedCapacity).AppendLine(" mWh");
if (bat.RemainingCapacity.HasValue)
reportBuilder.Append(" Remaining Capacity: ").Append(bat.RemainingCapacity).AppendLine(" mWh");
if (bat.ChargeLevel.HasValue)
reportBuilder.Append(" Charge Level: ").AppendFormat("{0:F2}", bat.ChargeLevel).AppendLine(" %");
if (bat.Voltage.HasValue)
reportBuilder.Append(" Voltage: ").AppendFormat("{0:F3}", bat.Voltage).AppendLine(" V");
if (bat.Temperature.HasValue)
reportBuilder.Append(" Temperature: ").AppendFormat("{0:F3}", bat.Temperature).AppendLine(" ºC");
if (bat.RemainingTime.HasValue)
reportBuilder.Append(" Remaining Time (Estimated): ").AppendFormat("{0:g}", TimeSpan.FromSeconds(bat.RemainingTime.Value)).AppendLine();
string cdRateSensorName;
string cdCurrentSensorName;
if (bat.ChargeDischargeRate > 0)
{
cdRateSensorName = " Charge Rate: ";
cdCurrentSensorName = " Charge Current: ";
}
else if (bat.ChargeDischargeRate < 0)
{
cdRateSensorName = " Discharge Rate: ";
cdCurrentSensorName = " Discharge Current: ";
}
else
{
cdRateSensorName = " Charge/Discharge Rate: ";
cdCurrentSensorName = " Charge/Discharge Current: ";
}
if (bat.ChargeDischargeRate.HasValue)
reportBuilder.Append(cdRateSensorName).AppendFormat("{0:F1}", Math.Abs(bat.ChargeDischargeRate.Value)).AppendLine(" W");
if (bat.ChargeDischargeCurrent.HasValue)
reportBuilder.Append(cdCurrentSensorName).AppendFormat("{0:F3}", Math.Abs(bat.ChargeDischargeCurrent.Value)).AppendLine(" A");
reportBuilder.AppendLine();
count++;
}
return reportBuilder.ToString();
}
}

View File

@@ -0,0 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Linq;
namespace LibreHardwareMonitor.Hardware;
internal class CompositeSensor : Sensor
{
private readonly ISensor[] _components;
private readonly Func<float, ISensor, float> _reducer;
private readonly float _seedValue;
public CompositeSensor
(
string name,
int index,
SensorType sensorType,
Hardware hardware,
ISettings settings,
ISensor[] components,
Func<float, ISensor, float> reducer,
float seedValue = .0f)
: base(name, index, sensorType, hardware, settings)
{
_components = components;
_reducer = reducer;
_seedValue = seedValue;
}
public override float? Value
{
get { return _components.Aggregate(_seedValue, _reducer); }
set => throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,697 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using LibreHardwareMonitor.Hardware.Battery;
using LibreHardwareMonitor.Hardware.Controller.AeroCool;
using LibreHardwareMonitor.Hardware.Controller.AquaComputer;
using LibreHardwareMonitor.Hardware.Controller.Heatmaster;
using LibreHardwareMonitor.Hardware.Controller.Nzxt;
using LibreHardwareMonitor.Hardware.Controller.Razer;
using LibreHardwareMonitor.Hardware.Controller.TBalancer;
using LibreHardwareMonitor.Hardware.Cpu;
using LibreHardwareMonitor.Hardware.Gpu;
using LibreHardwareMonitor.Hardware.Memory;
using LibreHardwareMonitor.Hardware.Motherboard;
using LibreHardwareMonitor.Hardware.Network;
using LibreHardwareMonitor.Hardware.Psu.Corsair;
using LibreHardwareMonitor.Hardware.Storage;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Stores all hardware groups and decides which devices should be enabled and updated.
/// </summary>
public class Computer : IComputer
{
private readonly List<IGroup> _groups = new();
private readonly object _lock = new();
private readonly ISettings _settings;
private bool _batteryEnabled;
private bool _controllerEnabled;
private bool _cpuEnabled;
private bool _gpuEnabled;
private bool _memoryEnabled;
private bool _motherboardEnabled;
private bool _networkEnabled;
private bool _open;
private bool _psuEnabled;
private SMBios _smbios;
private bool _storageEnabled;
/// <summary>
/// Creates a new <see cref="IComputer" /> instance with basic initial <see cref="Settings" />.
/// </summary>
public Computer()
{
_settings = new Settings();
}
/// <summary>
/// Creates a new <see cref="IComputer" /> instance with additional <see cref="ISettings" />.
/// </summary>
/// <param name="settings">Computer settings that will be transferred to each <see cref="IHardware" />.</param>
public Computer(ISettings settings)
{
_settings = settings ?? new Settings();
}
/// <inheritdoc />
public event HardwareEventHandler HardwareAdded;
/// <inheritdoc />
public event HardwareEventHandler HardwareRemoved;
/// <inheritdoc />
public IList<IHardware> Hardware
{
get
{
lock (_lock)
{
List<IHardware> list = new();
foreach (IGroup group in _groups)
list.AddRange(group.Hardware);
return list;
}
}
}
/// <inheritdoc />
public bool IsBatteryEnabled
{
get { return _batteryEnabled; }
set
{
if (_open && value != _batteryEnabled)
{
if (value)
{
Add(new BatteryGroup(_settings));
}
else
{
RemoveType<BatteryGroup>();
}
}
_batteryEnabled = value;
}
}
/// <inheritdoc />
public bool IsControllerEnabled
{
get { return _controllerEnabled; }
set
{
if (_open && value != _controllerEnabled)
{
if (value)
{
Add(new TBalancerGroup(_settings));
Add(new HeatmasterGroup(_settings));
Add(new AquaComputerGroup(_settings));
Add(new AeroCoolGroup(_settings));
Add(new NzxtGroup(_settings));
Add(new RazerGroup(_settings));
}
else
{
RemoveType<TBalancerGroup>();
RemoveType<HeatmasterGroup>();
RemoveType<AquaComputerGroup>();
RemoveType<AeroCoolGroup>();
RemoveType<NzxtGroup>();
RemoveType<RazerGroup>();
}
}
_controllerEnabled = value;
}
}
/// <inheritdoc />
public bool IsCpuEnabled
{
get { return _cpuEnabled; }
set
{
if (_open && value != _cpuEnabled)
{
if (value)
Add(new CpuGroup(_settings));
else
RemoveType<CpuGroup>();
}
_cpuEnabled = value;
}
}
/// <inheritdoc />
public bool IsGpuEnabled
{
get { return _gpuEnabled; }
set
{
if (_open && value != _gpuEnabled)
{
if (value)
{
Add(new AmdGpuGroup(_settings));
Add(new NvidiaGroup(_settings));
Add(new IntelGpuGroup(GetIntelCpus(), _settings));
}
else
{
RemoveType<AmdGpuGroup>();
RemoveType<NvidiaGroup>();
RemoveType<IntelGpuGroup>();
}
}
_gpuEnabled = value;
}
}
/// <inheritdoc />
public bool IsMemoryEnabled
{
get { return _memoryEnabled; }
set
{
if (_open && value != _memoryEnabled)
{
if (value)
Add(new MemoryGroup(_settings));
else
RemoveType<MemoryGroup>();
}
_memoryEnabled = value;
}
}
/// <inheritdoc />
public bool IsMotherboardEnabled
{
get { return _motherboardEnabled; }
set
{
if (_open && value != _motherboardEnabled)
{
if (value)
Add(new MotherboardGroup(_smbios, _settings));
else
RemoveType<MotherboardGroup>();
}
_motherboardEnabled = value;
}
}
/// <inheritdoc />
public bool IsNetworkEnabled
{
get { return _networkEnabled; }
set
{
if (_open && value != _networkEnabled)
{
if (value)
Add(new NetworkGroup(_settings));
else
RemoveType<NetworkGroup>();
}
_networkEnabled = value;
}
}
/// <inheritdoc />
public bool IsPsuEnabled
{
get { return _psuEnabled; }
set
{
if (_open && value != _psuEnabled)
{
if (value)
{
Add(new CorsairPsuGroup(_settings));
}
else
{
RemoveType<CorsairPsuGroup>();
}
}
_psuEnabled = value;
}
}
/// <inheritdoc />
public bool IsStorageEnabled
{
get { return _storageEnabled; }
set
{
if (_open && value != _storageEnabled)
{
if (value)
Add(new StorageGroup(_settings));
else
RemoveType<StorageGroup>();
}
_storageEnabled = value;
}
}
/// <summary>
/// Contains computer information table read in accordance with <see href="https://www.dmtf.org/standards/smbios">System Management BIOS (SMBIOS) Reference Specification</see>.
/// </summary>
public SMBios SMBios
{
get
{
if (!_open)
throw new InvalidOperationException("SMBIOS cannot be accessed before opening.");
return _smbios;
}
}
//// <inheritdoc />
public string GetReport()
{
lock (_lock)
{
using StringWriter w = new(CultureInfo.InvariantCulture);
w.WriteLine();
w.WriteLine(nameof(LibreHardwareMonitor) + " Report");
w.WriteLine();
Version version = typeof(Computer).Assembly.GetName().Version;
NewSection(w);
w.Write("Version: ");
w.WriteLine(version.ToString());
w.WriteLine();
NewSection(w);
w.Write("Common Language Runtime: ");
w.WriteLine(Environment.Version.ToString());
w.Write("Operating System: ");
w.WriteLine(Environment.OSVersion.ToString());
w.Write("Process Type: ");
w.WriteLine(IntPtr.Size == 4 ? "32-Bit" : "64-Bit");
w.WriteLine();
string r = Ring0.GetReport();
if (r != null)
{
NewSection(w);
w.Write(r);
w.WriteLine();
}
NewSection(w);
w.WriteLine("Sensors");
w.WriteLine();
foreach (IGroup group in _groups)
{
foreach (IHardware hardware in group.Hardware)
ReportHardwareSensorTree(hardware, w, string.Empty);
}
w.WriteLine();
NewSection(w);
w.WriteLine("Parameters");
w.WriteLine();
foreach (IGroup group in _groups)
{
foreach (IHardware hardware in group.Hardware)
ReportHardwareParameterTree(hardware, w, string.Empty);
}
w.WriteLine();
foreach (IGroup group in _groups)
{
string report = group.GetReport();
if (!string.IsNullOrEmpty(report))
{
NewSection(w);
w.Write(report);
}
foreach (IHardware hardware in group.Hardware)
ReportHardware(hardware, w);
}
return w.ToString();
}
}
/// <summary>
/// Triggers the <see cref="IVisitor.VisitComputer" /> method for the given observer.
/// </summary>
/// <param name="visitor">Observer who call to devices.</param>
public void Accept(IVisitor visitor)
{
if (visitor == null)
throw new ArgumentNullException(nameof(visitor));
visitor.VisitComputer(this);
}
/// <summary>
/// Triggers the <see cref="IElement.Accept" /> method with the given visitor for each device in each group.
/// </summary>
/// <param name="visitor">Observer who call to devices.</param>
public void Traverse(IVisitor visitor)
{
lock (_lock)
{
// Use a for-loop instead of foreach to avoid a collection modified exception after sleep, even though everything is under a lock.
for (int i = 0; i < _groups.Count; i++)
{
IGroup group = _groups[i];
for (int j = 0; j < group.Hardware.Count; j++)
group.Hardware[j].Accept(visitor);
}
}
}
private void HardwareAddedEvent(IHardware hardware)
{
HardwareAdded?.Invoke(hardware);
}
private void HardwareRemovedEvent(IHardware hardware)
{
HardwareRemoved?.Invoke(hardware);
}
private void Add(IGroup group)
{
if (group == null)
return;
lock (_lock)
{
if (_groups.Contains(group))
return;
_groups.Add(group);
if (group is IHardwareChanged hardwareChanged)
{
hardwareChanged.HardwareAdded += HardwareAddedEvent;
hardwareChanged.HardwareRemoved += HardwareRemovedEvent;
}
}
if (HardwareAdded != null)
{
foreach (IHardware hardware in group.Hardware)
HardwareAdded(hardware);
}
}
private void Remove(IGroup group)
{
lock (_lock)
{
if (!_groups.Contains(group))
return;
_groups.Remove(group);
if (group is IHardwareChanged hardwareChanged)
{
hardwareChanged.HardwareAdded -= HardwareAddedEvent;
hardwareChanged.HardwareRemoved -= HardwareRemovedEvent;
}
}
if (HardwareRemoved != null)
{
foreach (IHardware hardware in group.Hardware)
HardwareRemoved(hardware);
}
group.Close();
}
private void RemoveType<T>() where T : IGroup
{
List<T> list = new();
lock (_lock)
{
foreach (IGroup group in _groups)
{
if (group is T t)
list.Add(t);
}
}
foreach (T group in list)
Remove(group);
}
/// <summary>
/// If hasn't been opened before, opens <see cref="SMBios" />, <see cref="Ring0" />, <see cref="OpCode" /> and triggers the private <see cref="AddGroups" /> method depending on which categories are
/// enabled.
/// </summary>
public void Open()
{
if (_open)
return;
_smbios = new SMBios();
Ring0.Open();
Mutexes.Open();
OpCode.Open();
AddGroups();
_open = true;
}
private void AddGroups()
{
if (_motherboardEnabled)
Add(new MotherboardGroup(_smbios, _settings));
if (_cpuEnabled)
Add(new CpuGroup(_settings));
if (_memoryEnabled)
Add(new MemoryGroup(_settings));
if (_gpuEnabled)
{
Add(new AmdGpuGroup(_settings));
Add(new NvidiaGroup(_settings));
Add(new IntelGpuGroup(GetIntelCpus(), _settings));
}
if (_controllerEnabled)
{
Add(new TBalancerGroup(_settings));
Add(new HeatmasterGroup(_settings));
Add(new AquaComputerGroup(_settings));
Add(new AeroCoolGroup(_settings));
Add(new NzxtGroup(_settings));
Add(new RazerGroup(_settings));
}
if (_storageEnabled)
Add(new StorageGroup(_settings));
if (_networkEnabled)
Add(new NetworkGroup(_settings));
if (_psuEnabled)
Add(new CorsairPsuGroup(_settings));
if (_batteryEnabled)
Add(new BatteryGroup(_settings));
}
private static void NewSection(TextWriter writer)
{
for (int i = 0; i < 8; i++)
writer.Write("----------");
writer.WriteLine();
writer.WriteLine();
}
private static int CompareSensor(ISensor a, ISensor b)
{
int c = a.SensorType.CompareTo(b.SensorType);
if (c == 0)
return a.Index.CompareTo(b.Index);
return c;
}
private static void ReportHardwareSensorTree(IHardware hardware, TextWriter w, string space)
{
w.WriteLine("{0}|", space);
w.WriteLine("{0}+- {1} ({2})", space, hardware.Name, hardware.Identifier);
ISensor[] sensors = hardware.Sensors;
Array.Sort(sensors, CompareSensor);
foreach (ISensor sensor in sensors)
w.WriteLine("{0}| +- {1,-14} : {2,8:G6} {3,8:G6} {4,8:G6} ({5})", space, sensor.Name, sensor.Value, sensor.Min, sensor.Max, sensor.Identifier);
foreach (IHardware subHardware in hardware.SubHardware)
ReportHardwareSensorTree(subHardware, w, "| ");
}
private static void ReportHardwareParameterTree(IHardware hardware, TextWriter w, string space)
{
w.WriteLine("{0}|", space);
w.WriteLine("{0}+- {1} ({2})", space, hardware.Name, hardware.Identifier);
ISensor[] sensors = hardware.Sensors;
Array.Sort(sensors, CompareSensor);
foreach (ISensor sensor in sensors)
{
string innerSpace = space + "| ";
if (sensor.Parameters.Count > 0)
{
w.WriteLine("{0}|", innerSpace);
w.WriteLine("{0}+- {1} ({2})", innerSpace, sensor.Name, sensor.Identifier);
foreach (IParameter parameter in sensor.Parameters)
{
string innerInnerSpace = innerSpace + "| ";
w.WriteLine("{0}+- {1} : {2}", innerInnerSpace, parameter.Name, string.Format(CultureInfo.InvariantCulture, "{0} : {1}", parameter.DefaultValue, parameter.Value));
}
}
}
foreach (IHardware subHardware in hardware.SubHardware)
ReportHardwareParameterTree(subHardware, w, "| ");
}
private static void ReportHardware(IHardware hardware, TextWriter w)
{
string hardwareReport = hardware.GetReport();
if (!string.IsNullOrEmpty(hardwareReport))
{
NewSection(w);
w.Write(hardwareReport);
}
foreach (IHardware subHardware in hardware.SubHardware)
ReportHardware(subHardware, w);
}
/// <summary>
/// If opened before, removes all <see cref="IGroup" /> and triggers <see cref="OpCode.Close" />, <see cref="InpOut.Close" /> and <see cref="Ring0.Close" />.
/// </summary>
public void Close()
{
if (!_open)
return;
lock (_lock)
{
while (_groups.Count > 0)
{
IGroup group = _groups[_groups.Count - 1];
Remove(group);
}
}
OpCode.Close();
InpOut.Close();
Ring0.Close();
Mutexes.Close();
_smbios = null;
_open = false;
}
/// <summary>
/// If opened before, removes all <see cref="IGroup" /> and recreates it.
/// </summary>
public void Reset()
{
if (!_open)
return;
RemoveGroups();
AddGroups();
}
private void RemoveGroups()
{
lock (_lock)
{
while (_groups.Count > 0)
{
IGroup group = _groups[_groups.Count - 1];
Remove(group);
}
}
}
private List<IntelCpu> GetIntelCpus()
{
// Create a temporary cpu group if one has not been added.
lock (_lock)
{
IGroup cpuGroup = _groups.Find(x => x is CpuGroup) ?? new CpuGroup(_settings);
return cpuGroup.Hardware.Select(x => x as IntelCpu).ToList();
}
}
/// <summary>
/// <see cref="Computer" /> specific additional settings passed to its <see cref="IHardware" />.
/// </summary>
private class Settings : ISettings
{
public bool Contains(string name)
{
return false;
}
public void SetValue(string name, string value)
{ }
public string GetValue(string name, string value)
{
return value;
}
public void Remove(string name)
{ }
}
}

View File

@@ -0,0 +1,97 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Globalization;
namespace LibreHardwareMonitor.Hardware;
internal delegate void ControlEventHandler(Control control);
internal class Control : IControl
{
private readonly ISettings _settings;
private ControlMode _mode;
private float _softwareValue;
public Control
(
ISensor sensor,
ISettings settings,
float minSoftwareValue,
float maxSoftwareValue)
{
_settings = settings;
Identifier = new Identifier(sensor.Identifier, "control");
Sensor = sensor;
MinSoftwareValue = minSoftwareValue;
MaxSoftwareValue = maxSoftwareValue;
if (!float.TryParse(settings.GetValue(new Identifier(Identifier, "value").ToString(), "0"), NumberStyles.Float, CultureInfo.InvariantCulture, out _softwareValue))
_softwareValue = 0;
if (!int.TryParse(settings.GetValue(new Identifier(Identifier, "mode").ToString(), ((int)ControlMode.Undefined).ToString(CultureInfo.InvariantCulture)),
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out int mode))
{
_mode = ControlMode.Undefined;
}
else
_mode = (ControlMode)mode;
}
internal event ControlEventHandler ControlModeChanged;
internal event ControlEventHandler SoftwareControlValueChanged;
public ControlMode ControlMode
{
get { return _mode; }
private set
{
if (_mode != value)
{
_mode = value;
ControlModeChanged?.Invoke(this);
_settings.SetValue(new Identifier(Identifier, "mode").ToString(), ((int)_mode).ToString(CultureInfo.InvariantCulture));
}
}
}
public Identifier Identifier { get; }
public float MaxSoftwareValue { get; }
public float MinSoftwareValue { get; }
public ISensor Sensor { get; }
public float SoftwareValue
{
get { return _softwareValue; }
private set
{
if (_softwareValue != value)
{
_softwareValue = value;
SoftwareControlValueChanged?.Invoke(this);
_settings.SetValue(new Identifier(Identifier, "value").ToString(), value.ToString(CultureInfo.InvariantCulture));
}
}
}
public void SetDefault()
{
ControlMode = ControlMode.Default;
}
public void SetSoftware(float value)
{
ControlMode = ControlMode.Software;
SoftwareValue = value;
}
}

View File

@@ -0,0 +1,57 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AeroCool;
public class AeroCoolGroup : IGroup
{
private readonly List<IHardware> _hardware = new();
private readonly StringBuilder _report = new();
public AeroCoolGroup(ISettings settings)
{
_report.AppendLine("AeroCool Hardware");
_report.AppendLine();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x2E97))
{
int hubno = dev.ProductID - 0x1000;
if (dev.DevicePath.Contains("mi_02") && hubno is >= 1 and <= 8)
{
var device = new P7H1(dev, settings);
_report.AppendLine($"Device name: {device.Name}");
_report.AppendLine($"HUB number: {device.HubNumber}");
_report.AppendLine();
_hardware.Add(device);
}
}
if (_hardware.Count == 0)
{
_report.AppendLine("No AeroCool Hardware found.");
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}

View File

@@ -0,0 +1,96 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Threading.Tasks;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AeroCool;
internal sealed class P7H1 : Hardware
{
private const byte REPORT_ID = 0x0;
private readonly HidDevice _device;
private readonly Sensor[] _rpm = new Sensor[5];
private readonly float[] _speeds = new float[5];
private readonly HidStream _stream;
private bool _running;
public P7H1(HidDevice dev, ISettings settings) : base("AeroCool P7-H1", new Identifier(dev), settings)
{
_device = dev;
HubNumber = _device.ProductID - 0x1000;
Name = $"AeroCool P7-H1 #{HubNumber}";
if (_device.TryOpen(out _stream))
{
_running = true;
Task.Run(ReadStream);
for (int i = 0; i < 5; i++)
{
_rpm[i] = new Sensor($"Fan #{i + 1}", i, SensorType.Fan, this, settings);
ActivateSensor(_rpm[i]);
}
}
}
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public int HubNumber { get; }
private void ReadStream()
{
byte[] inputReportBuffer = new byte[_device.GetMaxInputReportLength()];
while (_running)
{
IAsyncResult ar = null;
while (_running)
{
ar ??= _stream.BeginRead(inputReportBuffer, 0, inputReportBuffer.Length, null, null);
if (ar.IsCompleted)
{
int byteCount = _stream.EndRead(ar);
ar = null;
if (byteCount == 16 && inputReportBuffer[0] == REPORT_ID)
{
for (int i = 0; i < 5; i++)
{
_speeds[i] = (inputReportBuffer[(i * 3) + 2] * 256) + inputReportBuffer[(i * 3) + 3];
}
}
}
else
{
ar.AsyncWaitHandle.WaitOne(1000);
}
}
}
}
public override void Close()
{
_running = false;
_stream.Close();
base.Close();
}
public override void Update()
{
for (int i = 0; i < 5; i++)
{
_rpm[i].Value = _speeds[i];
}
}
}

View File

@@ -0,0 +1,126 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
public class AquaComputerGroup : IGroup
{
private readonly List<IHardware> _hardware = new();
private readonly StringBuilder _report = new();
public AquaComputerGroup(ISettings settings)
{
_report.AppendLine("AquaComputer Hardware");
_report.AppendLine();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x0c70))
{
string productName = dev.GetProductName();
productName = productName.Substring(0, 1).ToUpper() + productName.Substring(1);
switch (dev.ProductID)
{
case 0xF00E:
var d5Next = new D5Next(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {d5Next.FirmwareVersion}");
_report.AppendLine();
_hardware.Add(d5Next);
break;
case 0xf0b6:
var aquastreamXt = new AquastreamXT(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Device variant: {aquastreamXt.Variant}");
_report.AppendLine($"Firmware version: {aquastreamXt.FirmwareVersion}");
_report.AppendLine($"{aquastreamXt.Status}");
_report.AppendLine();
_hardware.Add(aquastreamXt);
break;
case 0xf003:
var mps = new MPS(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {mps.FirmwareVersion}");
_report.AppendLine($"{mps.Status}");
_report.AppendLine();
_hardware.Add(mps);
break;
case 0xF00D:
var quadro = new Quadro(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {quadro.FirmwareVersion}");
_report.AppendLine();
_hardware.Add(quadro);
break;
case 0xF00B:
var aquastreamUltimate = new AquastreamUltimate(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {aquastreamUltimate.FirmwareVersion}");
_report.AppendLine();
_hardware.Add(aquastreamUltimate);
break;
case 0xF011:
var octo = new Octo(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {octo.FirmwareVersion}");
_report.AppendLine();
_hardware.Add(octo);
break;
case 0xF00A:
var farbwerk = new Farbwerk(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {farbwerk.FirmwareVersion}");
_report.AppendLine($"{farbwerk.Status}");
_report.AppendLine();
_hardware.Add(farbwerk);
break;
case 0xF012:
var highflownext = new HighFlowNext(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {highflownext.FirmwareVersion}");
_report.AppendLine();
_hardware.Add(highflownext);
break;
default:
_report.AppendLine($"Unknown Hardware PID: {dev.ProductID} Name: {productName}");
_report.AppendLine();
break;
}
}
if (_hardware.Count == 0)
{
_report.AppendLine("No AquaComputer Hardware found.");
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}

View File

@@ -0,0 +1,117 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class AquastreamUltimate : Hardware
{
private readonly byte[] _rawData = new byte[104];
private readonly HidStream _stream;
private readonly Sensor[] _rpmSensors = new Sensor[2];
private readonly Sensor[] _temperatures = new Sensor[2];
private readonly Sensor[] _voltages = new Sensor[2];
private readonly Sensor[] _currents = new Sensor[2];
private readonly Sensor[] _powers = new Sensor[2];
private readonly Sensor[] _flows = new Sensor[2];
public AquastreamUltimate(HidDevice dev, ISettings settings) : base("AquastreamUltimate", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
// Reading output report instead of feature report, as the measurements are in the output report.
_stream.Read(_rawData);
FirmwareVersion = GetConvertedValue(0xD).GetValueOrDefault(0);
Name = "Aquastream ULTIMATE";
_temperatures[0] = new Sensor("Coolant", 0, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[0]);
_temperatures[1] = new Sensor("External Sensor", 1, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[1]);
_rpmSensors[0] = new Sensor("Pump", 0, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[0]);
_voltages[0] = new Sensor("Pump", 0, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[0]);
_currents[0] = new Sensor("Pump", 0, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_currents[0]);
_powers[0] = new Sensor("Pump", 0, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_powers[0]);
// Initialize the flow sensor
_flows[0] = new Sensor("Pump", 0, SensorType.Flow, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_flows[0]);
_flows[1] = new Sensor("Pressure (mBar)", 1, SensorType.Factor, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_flows[1]);
_rpmSensors[1] = new Sensor("Fan", 1, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[1]);
_voltages[1] = new Sensor("Fan", 1, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[1]);
_currents[1] = new Sensor("Fan", 1, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_currents[1]);
_powers[1] = new Sensor("Fan", 1, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_powers[1]);
}
}
public ushort FirmwareVersion { get; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
// Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
_rpmSensors[0].Value = GetConvertedValue(0x51); // Pump speed.
_rpmSensors[1].Value = GetConvertedValue(0x41 + 0x06); // Fan speed.
_temperatures[0].Value = GetConvertedValue(0x2D) / 100f; // Water temp.
_temperatures[1].Value = GetConvertedValue(0x2F) / 100f; // Ext sensor temp.
_voltages[0].Value = GetConvertedValue(0x3D) / 100f; // Pump input voltage.
_voltages[1].Value = GetConvertedValue(0x41 + 0x02) / 100f; // Fan output voltage.
_currents[0].Value = GetConvertedValue(0x53) / 1000f; // Pump current.
_currents[1].Value = GetConvertedValue(0x41 + 0x00) / 1000f; // Fan current.
_powers[0].Value = GetConvertedValue(0x55) / 100f; // Pump power.
_powers[1].Value = GetConvertedValue(0x41 + 0x04) / 100f; // Fan power.
_flows[0].Value = GetConvertedValue(0x37); // Flow.
_flows[1].Value = GetConvertedValue(0x57) / 1000f; // Pressure.
}
private ushort? GetConvertedValue(int index)
{
if (_rawData[index] == sbyte.MaxValue)
return null;
return Convert.ToUInt16(_rawData[index + 1] | (_rawData[index] << 8));
}
}

View File

@@ -0,0 +1,168 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
//TODO:
//Check tested and fix unknown variables in Update()
//Check if property "Variant" is valid interpreted
//Implement Fan Control in SetControl()
internal sealed class AquastreamXT : Hardware
{
private readonly Sensor _fanControl;
private readonly Sensor[] _frequencies = new Sensor[2];
private readonly Sensor _pumpFlow;
private readonly Sensor _pumpPower;
private readonly byte[] _rawData = new byte[64];
private readonly Sensor[] _rpmSensors = new Sensor[2];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[3];
private readonly Sensor[] _voltages = new Sensor[2];
public AquastreamXT(HidDevice dev, ISettings settings) : base("Aquastream XT", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
do
{
_rawData[0] = 0x4;
_stream.GetFeature(_rawData);
}
while (_rawData[0] != 0x4);
Name = $"Aquastream XT {Variant}";
FirmwareVersion = BitConverter.ToUInt16(_rawData, 50);
_temperatures[0] = new Sensor("External Fan VRM", 0, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[0]);
_temperatures[1] = new Sensor("External", 1, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[1]);
_temperatures[2] = new Sensor("Internal Water", 2, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[2]);
_voltages[0] = new Sensor("External Fan", 1, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[0]);
_voltages[1] = new Sensor("Pump", 2, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[1]);
_pumpPower = new Sensor("Pump", 0, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_pumpPower);
_pumpFlow = new Sensor("Pump", 0, SensorType.Flow, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_pumpFlow);
_rpmSensors[0] = new Sensor("External Fan", 0, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[0]);
_rpmSensors[1] = new Sensor("Pump", 1, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[1]);
_fanControl = new Sensor("External Fan", 0, SensorType.Control, this, Array.Empty<ParameterDescription>(), settings);
_fanControl.Control = new Control(_fanControl, settings, 0, 100);
ActivateSensor(_fanControl);
_frequencies[0] = new Sensor("Pump Frequency", 0, SensorType.Frequency, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_frequencies[0]);
_frequencies[1] = new Sensor("Pump MaxFrequency", 1, SensorType.Frequency, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_frequencies[1]);
}
}
public ushort FirmwareVersion { get; private set; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public string Status
{
get
{
FirmwareVersion = BitConverter.ToUInt16(_rawData, 50);
return FirmwareVersion < 1008 ? $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 1018" : "Status: OK";
}
}
//TODO: Check if valid
public string Variant
{
get
{
MODE mode = (MODE)_rawData[33];
if (mode.HasFlag(MODE.MODE_PUMP_ADV))
return "Ultra + Internal Flow Sensor";
if (mode.HasFlag(MODE.MODE_FAN_CONTROLLER))
return "Ultra";
if (mode.HasFlag(MODE.MODE_FAN_AMP))
return "Advanced";
return "Standard";
}
}
public override void Close()
{
_stream.Close();
base.Close();
}
//TODO: Check tested and fix unknown variables
public override void Update()
{
try
{
_rawData[0] = 0x4;
_stream.GetFeature(_rawData);
}
catch (IOException)
{
return;
}
if (_rawData[0] != 0x4)
return;
//var rawSensorsFan = BitConverter.ToUInt16(rawData, 1); //unknown - redundant?
//var rawSensorsExt = BitConverter.ToUInt16(rawData, 3); //unknown - redundant?
//var rawSensorsWater = BitConverter.ToUInt16(rawData, 5); //unknown - redundant?
_voltages[0].Value = BitConverter.ToUInt16(_rawData, 7) / 61f; //External Fan Voltage: tested - OK
_voltages[1].Value = BitConverter.ToUInt16(_rawData, 9) / 61f; //Pump Voltage: tested - OK
_pumpPower.Value = _voltages[1].Value * BitConverter.ToInt16(_rawData, 11) / 625f; //Pump Voltage * Pump Current: tested - OK
_temperatures[0].Value = BitConverter.ToUInt16(_rawData, 13) / 100f; //External Fan VRM Temperature: untested
_temperatures[1].Value = BitConverter.ToUInt16(_rawData, 15) / 100f; //External Temperature Sensor: untested
_temperatures[2].Value = BitConverter.ToUInt16(_rawData, 17) / 100f; //Internal Water Temperature Sensor: tested - OK
_frequencies[0].Value = (1f / BitConverter.ToInt16(_rawData, 19)) * 750000; //Pump Frequency: tested - OK
_rpmSensors[1].Value = _frequencies[0].Value * 60f; //Pump RPM: tested - OK
_frequencies[1].Value = (1f / BitConverter.ToUInt16(_rawData, 21)) * 750000; //Pump Max Frequency: tested - OK
_pumpFlow.Value = BitConverter.ToUInt32(_rawData, 23); //Internal Pump Flow Sensor: unknown
_rpmSensors[0].Value = BitConverter.ToUInt32(_rawData, 27); //External Fan RPM: untested
_fanControl.Value = 100f / byte.MaxValue * _rawData[31]; //External Fan Control: tested, External Fan Voltage scales by this value - OK
}
[Flags]
[SuppressMessage("ReSharper", "InconsistentNaming")]
private enum MODE : byte
{
MODE_PUMP_ADV = 1,
MODE_FAN_AMP = 2,
MODE_FAN_CONTROLLER = 4
}
}

View File

@@ -0,0 +1,65 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class D5Next : Hardware
{
//Available Reports, found them by looking at the below methods
//var test = dev.GetRawReportDescriptor();
//var test2 = dev.GetReportDescriptor();
// ID 1; Length 158; INPUT
// ID 2; Length 11; OUTPUT
// ID 3; Length 1025; <-- works FEATURE
// ID 8; Length 1025; <-- works FEATURE
// ID 12; Length 1025; <-- 0xC FEATURE
private readonly byte[] _rawData = new byte[1025];
private readonly Sensor[] _rpmSensors = new Sensor[1];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[1];
public D5Next(HidDevice dev, ISettings settings) : base("D5Next", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
Name = "D5Next";
FirmwareVersion = Convert.ToUInt16(_rawData[14] | (_rawData[13] << 8));
_temperatures[0] = new Sensor("Water Temperature", 0, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[0]);
_rpmSensors[0] = new Sensor("Pump", 0, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[0]);
}
}
public ushort FirmwareVersion { get; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
_temperatures[0].Value = (_rawData[88] | (_rawData[87] << 8)) / 100f; //Water Temp
_rpmSensors[0].Value = _rawData[117] | (_rawData[116] << 8); //Pump RPM
}
}

View File

@@ -0,0 +1,115 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
//TODO:
//Implement set RGB Controls
internal sealed class Farbwerk : Hardware
{
private const int FEATURE_ID = 3;
private const int HEADER_SIZE = 27;
private const int SENSOR_OFFSET = 20;
private const int COLORS_OFFSET = 40;
private const int TEMPERATURE_COUNT = 4;
private const int COLOR_COUNT = 4;
private const int COLOR_VALUE_COUNT = COLOR_COUNT * 3;
private readonly byte[] _rawData = new byte[140];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[TEMPERATURE_COUNT];
private readonly Sensor[] _colors = new Sensor[COLOR_VALUE_COUNT];
public Farbwerk(HidDevice dev, ISettings settings) : base("Farbwerk", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
for (int i = 0; i < _temperatures.Length; i++)
{
_temperatures[i] = new Sensor($"Sensor {i + 1}", i, SensorType.Temperature, this, settings);
ActivateSensor(_temperatures[i]);
}
for (int i = 0; i < _colors.Length; i++)
{
int control = (i / 3) + 1;
string color = (i % 3) switch
{
0 => "Red",
1 => "Green",
2 => "Blue",
_ => "Invalid"
};
_colors[i] = new Sensor($"Controller {control} {color}", COLOR_COUNT + i, SensorType.Level, this, settings);
ActivateSensor(_colors[i]);
}
Update();
}
}
public ushort FirmwareVersion { get; private set; }
public override HardwareType HardwareType
{
get { return HardwareType.EmbeddedController; }
}
public string Status
{
get
{
if (_rawData[0] != 0x1)
{
return $"Status: Invalid header {_rawData[0]}";
}
if (FirmwareVersion < 1009)
{
return $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 1009";
}
return "Status: OK";
}
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
int length = _stream.Read(_rawData);
if (length != _rawData.Length || _rawData[0] != 0x1)
{
return;
}
FirmwareVersion = Convert.ToUInt16(_rawData[21] << 8 | _rawData[22]);
int offset = HEADER_SIZE + SENSOR_OFFSET;
for (int i = 0; i < _temperatures.Length; i++)
{
_temperatures[i].Value = (_rawData[offset] << 8 | _rawData[offset + 1]) / 100.0f;
offset += 2;
}
offset = HEADER_SIZE + COLORS_OFFSET;
for (int i = 0; i < _colors.Length; i++)
{
_colors[i].Value = (_rawData[offset] << 8 | _rawData[offset + 1]) / 81.90f;
offset += 2;
}
}
}

View File

@@ -0,0 +1,139 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class HighFlowNext : Hardware
{
private readonly byte[] _rawData = new byte[1025];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[2];
private readonly Sensor[] _flows = new Sensor[1];
private readonly Sensor[] _levels = new Sensor[1];
private readonly Sensor[] _powers = new Sensor[1];
private readonly Sensor[] _conductivities = new Sensor[1];
private readonly Sensor[] _voltages = new Sensor[2];
private readonly Sensor[] _alarms = new Sensor[4];
public HighFlowNext(HidDevice dev, ISettings settings) : base("high flow NEXT", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
// Reading output report instead of feature report, as the measurements are in the output report.
_stream.Read(_rawData);
FirmwareVersion = ReadUInt16BE(_rawData, 13);
_temperatures[0] = new Sensor("Water Temperature", 0, SensorType.Temperature, this, settings);
ActivateSensor(_temperatures[0]);
_temperatures[1] = new Sensor("External Temperature", 1, SensorType.Temperature, this, settings);
ActivateSensor(_temperatures[1]);
_flows[0] = new Sensor("Flow", 0, SensorType.Flow, this, settings);
ActivateSensor(_flows[0]);
_levels[0] = new Sensor("Water Quality", 0, SensorType.Level, this, settings);
ActivateSensor(_levels[0]);
_powers[0] = new Sensor("Dissipated Power", 0, SensorType.Power, this, settings);
ActivateSensor(_powers[0]);
_conductivities[0] = new Sensor("Conductivity", 0, SensorType.Conductivity, this, settings);
ActivateSensor(_conductivities[0]);
_voltages[0] = new Sensor("VCC", 0, SensorType.Voltage, this, settings);
ActivateSensor(_voltages[0]);
_voltages[1] = new Sensor("VCC USB", 1, SensorType.Voltage, this, settings);
ActivateSensor(_voltages[1]);
_alarms[0] = new Sensor("Flow Alarm", 0, true, SensorType.Factor, this, null, settings);
ActivateSensor(_alarms[0]);
_alarms[1] = new Sensor("Water Temperature Alarm", 1, true, SensorType.Factor, this, null, settings);
ActivateSensor(_alarms[0]);
_alarms[2] = new Sensor("External Temperature Alarm", 2, true, SensorType.Factor, this, null, settings);
ActivateSensor(_alarms[0]);
_alarms[3] = new Sensor("Water Quality Alarm", 3, true, SensorType.Factor, this, null, settings);
ActivateSensor(_alarms[0]);
}
}
public ushort FirmwareVersion { get; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
// Reading output report instead of feature report, as the measurements are in the output report.
_stream.Read(_rawData);
_temperatures[0].Value = ReadUInt16BE(_rawData, 85) / 100f; // Water Temperature
// External Temperature.
ushort rawExtTempValue = ReadUInt16BE(_rawData, 87);
bool externalTempSensorConnected = rawExtTempValue != short.MaxValue;
if (externalTempSensorConnected)
{
_temperatures[1].Value = rawExtTempValue / 100f;
}
else
{
// No external temp sensor connected.
_temperatures[1].Value = null;
}
_flows[0].Value = ReadUInt16BE(_rawData, 81) / 10f; // Flow
_levels[0].Value = ReadUInt16BE(_rawData, 89) / 100f; // Water Quality
// Dissipated Power.
if (externalTempSensorConnected)
{
_powers[0].Value = ReadUInt16BE(_rawData, 91);
}
else
{
// Power calculation requires the external temp sensor to be connected.
_powers[0].Value = null;
}
_conductivities[0].Value = ReadUInt16BE(_rawData, 95) / 10f; // Conductivity
_voltages[0].Value = ReadUInt16BE(_rawData, 97) / 100f; // VCC
_voltages[1].Value = ReadUInt16BE(_rawData, 99) / 100f; // VCC USB
_alarms[0].Value = (_rawData[116] & 0x02) >> 1; // Flow alarm
_alarms[1].Value = (_rawData[116] & 0x04) >> 2; // Water temperature alarm
_alarms[2].Value = (_rawData[116] & 0x08) >> 3; // External temperature alarm
_alarms[3].Value = (_rawData[116] & 0x10) >> 4; // Water quality alarm
// Unused:
// _rawData[101..104] -> Total pumped volume liters
// _rawData[105..109] -> Internal impulse counter from flow meter
}
private ushort ReadUInt16BE(byte[] value, int startIndex)
{
return (ushort)(value[startIndex + 1] | (value[startIndex] << 8));
}
}

View File

@@ -0,0 +1,114 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.IO;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class MPS : Hardware
{
public const int ExternalTemperature = 43;
public const int InternalWaterTemperature = 45;
public const int PumpFlow = 35;
private const byte MPS_REPORT_ID = 0x2;
private readonly Sensor _pumpFlow;
private readonly byte[] _rawData = new byte[64];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[2];
private ushort _externalTemperature;
public MPS(HidDevice dev, ISettings settings) : base("MPS", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
do
{
_rawData[0] = MPS_REPORT_ID;
_stream.GetFeature(_rawData);
}
while (_rawData[0] != MPS_REPORT_ID);
Name = "MPS";
FirmwareVersion = ExtractFirmwareVersion();
_temperatures[0] = new Sensor("External", 0, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[0]);
_temperatures[1] = new Sensor("Internal Water", 1, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[1]);
_pumpFlow = new Sensor("Pump", 0, SensorType.Flow, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_pumpFlow);
}
}
public ushort FirmwareVersion { get; private set; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public string Status
{
get
{
FirmwareVersion = ExtractFirmwareVersion();
if (FirmwareVersion < 1012)
{
return $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 1012";
}
return "Status: OK";
}
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
try
{
_rawData[0] = MPS_REPORT_ID;
_stream.GetFeature(_rawData);
}
catch (IOException)
{
return;
}
if (_rawData[0] != MPS_REPORT_ID)
return;
_pumpFlow.Value = BitConverter.ToUInt16(_rawData, PumpFlow) / 10f;
_externalTemperature = BitConverter.ToUInt16(_rawData, ExternalTemperature);
//sensor reading returns Int16.MaxValue (32767), when not connected
if (_externalTemperature != short.MaxValue)
{
_temperatures[0].Value = _externalTemperature / 100f;
}
else
{
_temperatures[0].Value = null;
}
_temperatures[1].Value = BitConverter.ToUInt16(_rawData, InternalWaterTemperature) / 100f;
}
private ushort ExtractFirmwareVersion()
{
return BitConverter.ToUInt16(_rawData, 3);
}
}

View File

@@ -0,0 +1,187 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class Octo : Hardware
{
private readonly byte[] _rawData = new byte[1025];
private readonly Sensor[] _rpmSensors = new Sensor[8];
private readonly HidStream _stream;
private readonly Sensor[] _temperatures = new Sensor[4];
private readonly Sensor[] _voltages = new Sensor[9];
private readonly Sensor[] _currents = new Sensor[8];
private readonly Sensor[] _powers = new Sensor[8];
public Octo(HidDevice dev, ISettings settings) : base("Octo", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
Name = "OCTO";
FirmwareVersion = GetConvertedValue(OctoDataIndexes.FIRMWARE_VERSION).GetValueOrDefault(0);
// Initialize the 4 temperature sensors
for (int i = 0; i < 4; i++)
{
_temperatures[i] = new Sensor($"Temperature {i + 1}", i, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[i]);
}
// Initialize the 8 fan speed sensors
for (int i = 0; i < 8; i++)
{
_rpmSensors[i] = new Sensor($"Fan {i + 1}", i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[i]);
}
// Initialize the input voltage sensor
_voltages[0] = new Sensor("Input", 0, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[0]);
// Initialize the 8 fan voltage sensors
for (int i = 1; i < 9; i++)
{
_voltages[i] = new Sensor($"Fan {i}", i, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[i]);
}
// Initialize the 8 fan current sensors
for (int i = 0; i < 8; i++)
{
_currents[i] = new Sensor($"Fan {i + 1}", i, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_currents[i]);
}
// Initialize the 8 fan power sensors
for (int i = 0; i < 8; i++)
{
_powers[i] = new Sensor($"Fan {i + 1}", i, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_powers[i]);
}
}
}
public ushort FirmwareVersion { get; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
_temperatures[0].Value = GetConvertedValue(OctoDataIndexes.TEMP_1) / 100f; // Temp 1
_temperatures[1].Value = GetConvertedValue(OctoDataIndexes.TEMP_2) / 100f; // Temp 2
_temperatures[2].Value = GetConvertedValue(OctoDataIndexes.TEMP_3) / 100f; // Temp 3
_temperatures[3].Value = GetConvertedValue(OctoDataIndexes.TEMP_4) / 100f; // Temp 4
_rpmSensors[0].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_1); // Fan 1 speed
_rpmSensors[1].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_2); // Fan 2 speed
_rpmSensors[2].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_3); // Fan 3 speed
_rpmSensors[3].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_4); // Fan 4 speed
_rpmSensors[4].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_5); // Fan 5 speed
_rpmSensors[5].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_6); // Fan 6 speed
_rpmSensors[6].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_7); // Fan 7 speed
_rpmSensors[7].Value = GetConvertedValue(OctoDataIndexes.FAN_SPEED_8); // Fan 8 speed
_voltages[0].Value = GetConvertedValue(OctoDataIndexes.VOLTAGE) / 100f; // Input voltage
_voltages[1].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_1) / 100f; // Fan 1 voltage
_voltages[2].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_2) / 100f; // Fan 2 voltage
_voltages[3].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_3) / 100f; // Fan 3 voltage
_voltages[4].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_4) / 100f; // Fan 4 voltage
_voltages[5].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_5) / 100f; // Fan 5 voltage
_voltages[6].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_6) / 100f; // Fan 6 voltage
_voltages[7].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_7) / 100f; // Fan 7 voltage
_voltages[8].Value = GetConvertedValue(OctoDataIndexes.FAN_VOLTAGE_8) / 100f; // Fan 8 voltage
_currents[0].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_1) / 1000f; // Fan 1 current
_currents[1].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_2) / 1000f; // Fan 2 current
_currents[2].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_3) / 1000f; // Fan 3 current
_currents[3].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_4) / 1000f; // Fan 4 current
_currents[4].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_5) / 1000f; // Fan 5 current
_currents[5].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_6) / 1000f; // Fan 6 current
_currents[6].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_7) / 1000f; // Fan 7 current
_currents[7].Value = GetConvertedValue(OctoDataIndexes.FAN_CURRENT_8) / 1000f; // Fan 8 current
_powers[0].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_1) / 100f; // Fan 1 power
_powers[1].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_2) / 100f; // Fan 2 power
_powers[2].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_3) / 100f; // Fan 3 power
_powers[3].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_4) / 100f; // Fan 4 power
_powers[4].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_5) / 100f; // Fan 5 power
_powers[5].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_6) / 100f; // Fan 6 power
_powers[6].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_7) / 100f; // Fan 7 power
_powers[7].Value = GetConvertedValue(OctoDataIndexes.FAN_POWER_8) / 100f; // Fan 8 power
}
private sealed class OctoDataIndexes
{
public const int FIRMWARE_VERSION = 13;
public const int TEMP_1 = 61;
public const int TEMP_2 = 63;
public const int TEMP_3 = 65;
public const int TEMP_4 = 67;
public const int FAN_SPEED_1 = 133;
public const int FAN_SPEED_2 = 146;
public const int FAN_SPEED_3 = 159;
public const int FAN_SPEED_4 = 172;
public const int FAN_SPEED_5 = 185;
public const int FAN_SPEED_6 = 198;
public const int FAN_SPEED_7 = 211;
public const int FAN_SPEED_8 = 224;
public const int FAN_POWER_1 = 131;
public const int FAN_POWER_2 = 144;
public const int FAN_POWER_3 = 157;
public const int FAN_POWER_4 = 170;
public const int FAN_POWER_5 = 183;
public const int FAN_POWER_6 = 196;
public const int FAN_POWER_7 = 209;
public const int FAN_POWER_8 = 222;
public const int VOLTAGE = 117;
public const int FAN_VOLTAGE_1 = 127;
public const int FAN_VOLTAGE_2 = 140;
public const int FAN_VOLTAGE_3 = 153;
public const int FAN_VOLTAGE_4 = 166;
public const int FAN_VOLTAGE_5 = 179;
public const int FAN_VOLTAGE_6 = 192;
public const int FAN_VOLTAGE_7 = 205;
public const int FAN_VOLTAGE_8 = 218;
public const int FAN_CURRENT_1 = 129;
public const int FAN_CURRENT_2 = 142;
public const int FAN_CURRENT_3 = 155;
public const int FAN_CURRENT_4 = 168;
public const int FAN_CURRENT_5 = 181;
public const int FAN_CURRENT_6 = 194;
public const int FAN_CURRENT_7 = 207;
public const int FAN_CURRENT_8 = 220;
}
private ushort? GetConvertedValue(int index)
{
if (_rawData[index] == sbyte.MaxValue)
return null;
return Convert.ToUInt16(_rawData[index + 1] | (_rawData[index] << 8));
}
}

View File

@@ -0,0 +1,170 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.AquaComputer;
internal sealed class Quadro : Hardware
{
private readonly byte[] _rawData = new byte[210];
private readonly HidStream _stream;
private readonly Sensor[] _rpmSensors = new Sensor[4];
private readonly Sensor[] _temperatures = new Sensor[4];
private readonly Sensor[] _voltages = new Sensor[5];
private readonly Sensor[] _currents = new Sensor[4];
private readonly Sensor[] _powers = new Sensor[4];
private readonly Sensor[] _flows = new Sensor[1];
public Quadro(HidDevice dev, ISettings settings) : base("Quadro", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
Name = "QUADRO";
FirmwareVersion = GetConvertedValue(QuadroDataIndexes.FIRMWARE_VERSION).GetValueOrDefault(0);
// Initialize the 4 temperature sensors
for (int i = 0; i < 4; i++)
{
_temperatures[i] = new Sensor($"Temperature {i + 1}", i, SensorType.Temperature, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_temperatures[i]);
}
// Initialize the input voltage sensor
_voltages[0] = new Sensor("Input", 0, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[0]);
// Initialize the flow sensor
_flows[0] = new Sensor("Flow", 0, SensorType.Flow, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_flows[0]);
// Initialize the 4 fan voltage sensors
for (int i = 1; i < 5; i++)
{
_voltages[i] = new Sensor($"Fan {i}", i, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_voltages[i]);
}
// Initialize the 4 fan current sensors
for (int i = 0; i < 4; i++)
{
_currents[i] = new Sensor($"Fan {i + 1}", i, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_currents[i]);
}
// Initialize the 4 fan power sensors
for (int i = 0; i < 4; i++)
{
_powers[i] = new Sensor($"Fan {i + 1}", i, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_powers[i]);
}
// Initialize the 4 fan speed sensors
for (int i = 0; i < 4; i++)
{
_rpmSensors[i] = new Sensor($"Fan {i + 1}", i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[i]);
}
}
}
public ushort FirmwareVersion { get; }
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public override void Close()
{
_stream.Close();
base.Close();
}
public override void Update()
{
//Reading output report instead of feature report, as the measurements are in the output report
_stream.Read(_rawData);
_temperatures[0].Value = GetConvertedValue(QuadroDataIndexes.TEMP_1) / 100f; // Temp 1
_temperatures[1].Value = GetConvertedValue(QuadroDataIndexes.TEMP_2) / 100f; // Temp 2
_temperatures[2].Value = GetConvertedValue(QuadroDataIndexes.TEMP_3) / 100f; // Temp 3
_temperatures[3].Value = GetConvertedValue(QuadroDataIndexes.TEMP_4) / 100f; // Temp 4
_voltages[0].Value = GetConvertedValue(QuadroDataIndexes.VOLTAGE) / 100f; // Input voltage
_flows[0].Value = GetConvertedValue(QuadroDataIndexes.FLOW) / 10f; // Flow
_voltages[1].Value = GetConvertedValue(QuadroDataIndexes.FAN_VOLTAGE_1) / 100f; // Fan 1 voltage
_voltages[2].Value = GetConvertedValue(QuadroDataIndexes.FAN_VOLTAGE_2) / 100f; // Fan 2 voltage
_voltages[3].Value = GetConvertedValue(QuadroDataIndexes.FAN_VOLTAGE_3) / 100f; // Fan 3 voltage
_voltages[4].Value = GetConvertedValue(QuadroDataIndexes.FAN_VOLTAGE_4) / 100f; // Fan 4 voltage
_currents[0].Value = GetConvertedValue(QuadroDataIndexes.FAN_CURRENT_1) / 1000f; // Fan 1 current
_currents[1].Value = GetConvertedValue(QuadroDataIndexes.FAN_CURRENT_2) / 1000f; // Fan 2 current
_currents[2].Value = GetConvertedValue(QuadroDataIndexes.FAN_CURRENT_3) / 1000f; // Fan 3 current
_currents[3].Value = GetConvertedValue(QuadroDataIndexes.FAN_CURRENT_4) / 1000f; // Fan 4 current
_powers[0].Value = GetConvertedValue(QuadroDataIndexes.FAN_POWER_1) / 100f; // Fan 1 power
_powers[1].Value = GetConvertedValue(QuadroDataIndexes.FAN_POWER_2) / 100f; // Fan 2 power
_powers[2].Value = GetConvertedValue(QuadroDataIndexes.FAN_POWER_3) / 100f; // Fan 3 power
_powers[3].Value = GetConvertedValue(QuadroDataIndexes.FAN_POWER_4) / 100f; // Fan 4 power
_rpmSensors[0].Value = GetConvertedValue(QuadroDataIndexes.FAN_SPEED_1); // Fan 1 speed
_rpmSensors[1].Value = GetConvertedValue(QuadroDataIndexes.FAN_SPEED_2); // Fan 2 speed
_rpmSensors[2].Value = GetConvertedValue(QuadroDataIndexes.FAN_SPEED_3); // Fan 3 speed
_rpmSensors[3].Value = GetConvertedValue(QuadroDataIndexes.FAN_SPEED_4); // Fan 4 speed
}
private sealed class QuadroDataIndexes
{
public const int FIRMWARE_VERSION = 13;
public const int TEMP_1 = 52;
public const int TEMP_2 = 54;
public const int TEMP_3 = 56;
public const int TEMP_4 = 58;
public const int VOLTAGE = 108;
public const int FLOW = 110;
public const int FAN_VOLTAGE_1 = 114;
public const int FAN_VOLTAGE_2 = 127;
public const int FAN_VOLTAGE_3 = 140;
public const int FAN_VOLTAGE_4 = 153;
public const int FAN_CURRENT_1 = 116;
public const int FAN_CURRENT_2 = 129;
public const int FAN_CURRENT_3 = 142;
public const int FAN_CURRENT_4 = 155;
public const int FAN_POWER_1 = 118;
public const int FAN_POWER_2 = 131;
public const int FAN_POWER_3 = 144;
public const int FAN_POWER_4 = 157;
public const int FAN_SPEED_1 = 120;
public const int FAN_SPEED_2 = 133;
public const int FAN_SPEED_3 = 146;
public const int FAN_SPEED_4 = 159;
}
private ushort? GetConvertedValue(int index)
{
if (_rawData[index] == sbyte.MaxValue)
return null;
return Convert.ToUInt16(_rawData[index + 1] | (_rawData[index] << 8));
}
}

View File

@@ -0,0 +1,290 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Controller.Heatmaster;
internal sealed class Heatmaster : Hardware, IDisposable
{
private readonly bool _available;
private readonly StringBuilder _buffer = new();
private readonly Sensor[] _controls;
private readonly Sensor[] _fans;
private readonly int _firmwareCrc;
private readonly int _firmwareRevision;
private readonly Sensor[] _flows;
private readonly int _hardwareRevision;
private readonly string _portName;
private readonly Sensor[] _relays;
private readonly Sensor[] _temperatures;
private SerialPort _serialPort;
public Heatmaster(string portName, ISettings settings) : base("Heatmaster", new Identifier("heatmaster", portName.TrimStart('/').ToLowerInvariant()), settings)
{
_portName = portName;
try
{
_serialPort = new SerialPort(portName, 38400, Parity.None, 8, StopBits.One);
_serialPort.Open();
_serialPort.NewLine = ((char)0x0D).ToString();
_hardwareRevision = ReadInteger(0, 'H');
_firmwareRevision = ReadInteger(0, 'V');
_firmwareCrc = ReadInteger(0, 'C');
int fanCount = Math.Min(ReadInteger(32, '?'), 4);
int temperatureCount = Math.Min(ReadInteger(48, '?'), 6);
int flowCount = Math.Min(ReadInteger(64, '?'), 1);
int relayCount = Math.Min(ReadInteger(80, '?'), 1);
_fans = new Sensor[fanCount];
_controls = new Sensor[fanCount];
for (int i = 0; i < fanCount; i++)
{
int device = 33 + i;
string name = ReadString(device, 'C');
_fans[i] = new Sensor(name, device, SensorType.Fan, this, settings) { Value = ReadInteger(device, 'R') };
ActivateSensor(_fans[i]);
_controls[i] = new Sensor(name, device, SensorType.Control, this, settings) { Value = (100 / 255.0f) * ReadInteger(device, 'P') };
ActivateSensor(_controls[i]);
}
_temperatures = new Sensor[temperatureCount];
for (int i = 0; i < temperatureCount; i++)
{
int device = 49 + i;
string name = ReadString(device, 'C');
_temperatures[i] = new Sensor(name, device, SensorType.Temperature, this, settings);
int value = ReadInteger(device, 'T');
_temperatures[i].Value = 0.1f * value;
if (value != -32768)
ActivateSensor(_temperatures[i]);
}
_flows = new Sensor[flowCount];
for (int i = 0; i < flowCount; i++)
{
int device = 65 + i;
string name = ReadString(device, 'C');
_flows[i] = new Sensor(name, device, SensorType.Flow, this, settings) { Value = 0.1f * ReadInteger(device, 'L') };
ActivateSensor(_flows[i]);
}
_relays = new Sensor[relayCount];
for (int i = 0; i < relayCount; i++)
{
int device = 81 + i;
string name = ReadString(device, 'C');
_relays[i] = new Sensor(name, device, SensorType.Control, this, settings)
{
Value = 100 * ReadInteger(device, 'S')
};
ActivateSensor(_relays[i]);
}
// set the update rate to 2 Hz
WriteInteger(0, 'L', 2);
_available = true;
}
catch (IOException)
{ }
catch (TimeoutException)
{ }
}
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
public void Dispose()
{
if (_serialPort != null)
{
_serialPort.Dispose();
_serialPort = null;
}
}
private string ReadLine(int timeout)
{
int i = 0;
StringBuilder builder = new();
while (i <= timeout)
{
while (_serialPort.BytesToRead > 0)
{
byte b = (byte)_serialPort.ReadByte();
switch (b)
{
case 0xAA: return ((char)b).ToString();
case 0x0D: return builder.ToString();
default:
builder.Append((char)b);
break;
}
}
i++;
Thread.Sleep(1);
}
throw new TimeoutException();
}
private string ReadField(int device, char field)
{
_serialPort.WriteLine("[0:" + device + "]R" + field);
for (int i = 0; i < 5; i++)
{
string s = ReadLine(200);
Match match = Regex.Match(s, @"-\[0:" + device.ToString(CultureInfo.InvariantCulture) + @"\]R" + Regex.Escape(field.ToString(CultureInfo.InvariantCulture)) + ":(.*)");
if (match.Success)
return match.Groups[1].Value;
}
return null;
}
private string ReadString(int device, char field)
{
string s = ReadField(device, field);
if (s?[0] == '"' && s[s.Length - 1] == '"')
return s.Substring(1, s.Length - 2);
return null;
}
private int ReadInteger(int device, char field)
{
string s = ReadField(device, field);
if (int.TryParse(s, out int i))
return i;
return 0;
}
private void WriteField(int device, char field, string value)
{
_serialPort.WriteLine("[0:" + device + "]W" + field + ":" + value);
for (int i = 0; i < 5; i++)
{
string s = ReadLine(200);
Match match = Regex.Match(s, @"-\[0:" + device.ToString(CultureInfo.InvariantCulture) + @"\]W" + Regex.Escape(field.ToString(CultureInfo.InvariantCulture)) + ":" + value);
if (match.Success)
return;
}
}
private void WriteInteger(int device, char field, int value)
{
WriteField(device, field, value.ToString(CultureInfo.InvariantCulture));
}
private void ProcessUpdateLine(string line)
{
Match match = Regex.Match(line, @">\[0:(\d+)\]([0-9:\|-]+)");
if (match.Success && int.TryParse(match.Groups[1].Value, out int device))
{
foreach (string s in match.Groups[2].Value.Split('|'))
{
string[] strings = s.Split(':');
int[] ints = new int[strings.Length];
bool valid = true;
for (int i = 0; i < ints.Length; i++)
{
if (!int.TryParse(strings[i], out ints[i]))
{
valid = false;
break;
}
}
if (!valid)
continue;
switch (device)
{
case 32:
if (ints.Length == 3 && ints[0] <= _fans.Length)
{
_fans[ints[0] - 1].Value = ints[1];
_controls[ints[0] - 1].Value = (100 / 255.0f) * ints[2];
}
break;
case 48:
if (ints.Length == 2 && ints[0] <= _temperatures.Length)
_temperatures[ints[0] - 1].Value = 0.1f * ints[1];
break;
case 64:
if (ints.Length == 3 && ints[0] <= _flows.Length)
_flows[ints[0] - 1].Value = 0.1f * ints[1];
break;
case 80:
if (ints.Length == 2 && ints[0] <= _relays.Length)
_relays[ints[0] - 1].Value = 100 * ints[1];
break;
}
}
}
}
public override void Update()
{
if (!_available)
return;
while (_serialPort.IsOpen && _serialPort.BytesToRead > 0)
{
byte b = (byte)_serialPort.ReadByte();
if (b == 0x0D)
{
ProcessUpdateLine(_buffer.ToString());
_buffer.Length = 0;
}
else
{
_buffer.Append((char)b);
}
}
}
public override string GetReport()
{
StringBuilder r = new();
r.AppendLine("Heatmaster");
r.AppendLine();
r.Append("Port: ");
r.AppendLine(_portName);
r.Append("Hardware Revision: ");
r.AppendLine(_hardwareRevision.ToString(CultureInfo.InvariantCulture));
r.Append("Firmware Revision: ");
r.AppendLine(_firmwareRevision.ToString(CultureInfo.InvariantCulture));
r.Append("Firmware CRC: ");
r.AppendLine(_firmwareCrc.ToString(CultureInfo.InvariantCulture));
r.AppendLine();
return r.ToString();
}
public override void Close()
{
_serialPort.Close();
_serialPort.Dispose();
_serialPort = null;
base.Close();
}
}

View File

@@ -0,0 +1,205 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Ports;
using System.Security;
using System.Text;
using System.Threading;
using Microsoft.Win32;
namespace LibreHardwareMonitor.Hardware.Controller.Heatmaster;
internal class HeatmasterGroup : IGroup
{
private readonly List<Heatmaster> _hardware = new();
private readonly StringBuilder _report = new();
public HeatmasterGroup(ISettings settings)
{
// No implementation for Heatmaster on Unix systems
if (Software.OperatingSystem.IsUnix)
return;
string[] portNames = GetRegistryPortNames();
for (int i = 0; i < portNames.Length; i++)
{
bool isValid = false;
try
{
using SerialPort serialPort = new(portNames[i], 38400, Parity.None, 8, StopBits.One);
serialPort.NewLine = ((char)0x0D).ToString();
_report.Append("Port Name: ");
_report.AppendLine(portNames[i]);
try
{
serialPort.Open();
}
catch (UnauthorizedAccessException)
{
_report.AppendLine("Exception: Access Denied");
}
if (serialPort.IsOpen)
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
serialPort.Write(new byte[] { 0xAA }, 0, 1);
int j = 0;
while (serialPort.BytesToRead == 0 && j < 10)
{
Thread.Sleep(20);
j++;
}
if (serialPort.BytesToRead > 0)
{
bool flag = false;
while (serialPort.BytesToRead > 0 && !flag)
{
flag |= serialPort.ReadByte() == 0xAA;
}
if (flag)
{
serialPort.WriteLine("[0:0]RH");
try
{
int k = 0;
int revision = 0;
while (k < 5)
{
string line = ReadLine(serialPort, 100);
if (line.StartsWith("-[0:0]RH:", StringComparison.Ordinal))
{
revision = int.Parse(line.Substring(9), CultureInfo.InvariantCulture);
break;
}
k++;
}
isValid = revision == 770;
if (!isValid)
{
_report.Append("Status: Wrong Hardware Revision " + revision.ToString(CultureInfo.InvariantCulture));
}
}
catch (TimeoutException)
{
_report.AppendLine("Status: Timeout Reading Revision");
}
}
else
{
_report.AppendLine("Status: Wrong Startflag");
}
}
else
{
_report.AppendLine("Status: No Response");
}
serialPort.DiscardInBuffer();
}
else
{
_report.AppendLine("Status: Port not Open");
}
}
catch (Exception e)
{
_report.AppendLine(e.ToString());
}
if (isValid)
{
_report.AppendLine("Status: OK");
_hardware.Add(new Heatmaster(portNames[i], settings));
}
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
if (_report.Length > 0)
{
StringBuilder r = new();
r.AppendLine("Serial Port Heatmaster");
r.AppendLine();
r.Append(_report);
r.AppendLine();
return r.ToString();
}
return null;
}
public void Close()
{
foreach (Heatmaster heatmaster in _hardware)
heatmaster.Close();
}
private static string ReadLine(SerialPort port, int timeout)
{
int i = 0;
StringBuilder builder = new();
while (i < timeout)
{
while (port.BytesToRead > 0)
{
byte b = (byte)port.ReadByte();
switch (b)
{
case 0xAA: return ((char)b).ToString();
case 0x0D: return builder.ToString();
default:
builder.Append((char)b);
break;
}
}
i++;
Thread.Sleep(1);
}
throw new TimeoutException();
}
private static string[] GetRegistryPortNames()
{
List<string> result = new();
string[] paths = { string.Empty, "&MI_00" };
try
{
foreach (string path in paths)
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB\VID_10C4&PID_EA60" + path);
if (key != null)
{
foreach (string subKeyName in key.GetSubKeyNames())
{
RegistryKey subKey = key.OpenSubKey(subKeyName + "\\" + "Device Parameters");
if (subKey?.GetValue("PortName") is string name && !result.Contains(name))
result.Add(name);
}
}
}
}
catch (SecurityException)
{ }
return result.ToArray();
}
}

View File

@@ -0,0 +1,186 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Threading;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the NZXT GRID+ V3 devices.
/// </summary>
internal sealed class GridV3 : Hardware
{
private const int FANS_COUNT = 6;
// Some initialization messages to send to the controller. No visible effects but NZXT CAM send them.
private static readonly byte[] _initialize1 = { 0x01, 0x5c };
private static readonly byte[] _initialize2 = { 0x01, 0x5d };
private static readonly byte[] _initialize3 = { 0x01, 0x59 };
private readonly Sensor[] _currents = new Sensor[FANS_COUNT];
private readonly Control[] _fanControls = new Control[FANS_COUNT];
private readonly Sensor _noise;
private readonly Sensor[] _powers = new Sensor[FANS_COUNT];
private readonly Sensor[] _pwmControls = new Sensor[FANS_COUNT];
private readonly Dictionary<int, byte[]> _rawData = new();
private readonly Sensor[] _rpmSensors = new Sensor[FANS_COUNT];
private readonly byte[] _setFanSpeedMsg;
private readonly HidStream _stream;
private readonly Sensor[] _voltages = new Sensor[FANS_COUNT];
public GridV3(HidDevice dev, ISettings settings) : base("NZXT GRID+ V3", new Identifier(dev), settings)
{
if (dev.TryOpen(out _stream))
{
for (int fanId = 0; fanId < FANS_COUNT; fanId++)
_rawData[fanId] = new byte[21];
_setFanSpeedMsg = new byte[65];
_setFanSpeedMsg[0] = 0x02;
_setFanSpeedMsg[1] = 0x4d;
_setFanSpeedMsg[3] = 0x00;
_stream.Write(_initialize1);
_stream.Write(_initialize2);
_stream.Write(_initialize3);
do
{
_stream.Read(_rawData[0]);
if (_rawData[0][0] == 0x04)
{
FirmwareVersion = $"{_rawData[0][11]}.{_rawData[0][14]}";
}
}
while (FirmwareVersion == null);
Name = "NZXT GRID+ V3";
// Initialize all sensors and controls for all fans
for (int i = 0; i < FANS_COUNT; i++)
{
_rpmSensors[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
_voltages[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Voltage, this, Array.Empty<ParameterDescription>(), settings);
_currents[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Current, this, Array.Empty<ParameterDescription>(), settings);
_powers[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Power, this, Array.Empty<ParameterDescription>(), settings);
_pwmControls[i] = new Sensor($"GRID Fan #{i + 1}", i, SensorType.Control, this, Array.Empty<ParameterDescription>(), settings);
_fanControls[i] = new Control(_pwmControls[i], settings, 0, 100);
_pwmControls[i].Control = _fanControls[i];
_fanControls[i].ControlModeChanged += SoftwareControlValueChanged;
_fanControls[i].SoftwareControlValueChanged += SoftwareControlValueChanged;
SoftwareControlValueChanged(_fanControls[i]);
ActivateSensor(_rpmSensors[i]);
ActivateSensor(_voltages[i]);
ActivateSensor(_currents[i]);
ActivateSensor(_powers[i]);
ActivateSensor(_pwmControls[i]);
// NZXT GRID does not report current PWM value. So we need to initialize it with some value to keep GUI and device values in sync.
_fanControls[i].SetDefault();
}
_noise = new Sensor("GRID Noise", 0, SensorType.Noise, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_noise);
Thread readGridReports = new(ContinuousRead) { IsBackground = true };
readGridReports.Start(_rawData);
IsValid = true;
}
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
private void SoftwareControlValueChanged(Control control)
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
byte fanSpeed = (byte)(value > 100 ? 100 : value < 0 ? 0 : value); // Clamp the value, anything out of range will fail
//_controlling = true;
_setFanSpeedMsg[2] = (byte)control.Sensor.Index;
_setFanSpeedMsg[4] = fanSpeed;
_stream.Write(_setFanSpeedMsg);
_pwmControls[control.Sensor.Index].Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode, but let's say a safe setting is 40%
_setFanSpeedMsg[2] = (byte)control.Sensor.Index;
_setFanSpeedMsg[4] = 40;
_stream.Write(_setFanSpeedMsg);
_pwmControls[control.Sensor.Index].Value = 40;
break;
}
}
public override void Close()
{
_stream?.Close();
base.Close();
}
private void ContinuousRead(object state)
{
byte[] buffer = new byte[_rawData[0].Length];
while (_stream.CanRead)
{
try
{
_stream.Read(buffer); // This is a blocking call, will wait for bytes to become available
if (buffer[0] == 0x04)
{
lock (_rawData)
{
int fanId = (buffer[15] >> 4) & 0x0f;
Array.Copy(buffer, _rawData[fanId], buffer.Length);
}
}
}
catch (TimeoutException)
{
// Don't care, just make sure the stream is still open
Thread.Sleep(500);
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
return;
}
}
}
public override void Update()
{
// The NZXT GRID+ V3 series sends updates periodically. We have to read it in a separate thread, this call just reads that data.
lock (_rawData)
{
for (int fanId = 0; fanId < FANS_COUNT; fanId++)
{
_rpmSensors[fanId].Value = (_rawData[fanId][3] << 8) | _rawData[fanId][4];
_voltages[fanId].Value = _rawData[fanId][7] + _rawData[fanId][8] / 100.0f;
_currents[fanId].Value = _rawData[fanId][9] + _rawData[fanId][10] / 100.0f;
_powers[fanId].Value = _currents[fanId].Value * _voltages[fanId].Value;
}
_noise.Value = _rawData[2][1];
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the Kraken X (X42, X52, X62 or X72) devices.
/// </summary>
internal sealed class KrakenV2 : Hardware
{
private static readonly byte[] _getFirmwareInfo = [0x10, 0x01];
private readonly HidDevice _device;
private readonly Sensor _fan;
private readonly byte _fanChannel;
private readonly bool _fanControl;
private readonly Sensor _fanRpm;
private readonly TimeSpan _interval = TimeSpan.FromMilliseconds(5000);
private readonly Sensor _liquidTemperature;
private readonly Sensor _pump;
private readonly byte _pumpChannel;
private readonly Sensor _pumpRpm;
private readonly byte[] _rawData = new byte[64];
private readonly string _supportedFirmware;
private DateTime _lastUpdate = DateTime.MinValue;
public KrakenV2(HidDevice dev, ISettings settings) : base("Nzxt Kraken X", new Identifier(dev), settings)
{
_device = dev;
switch (dev.ProductID)
{
case 0x170e:
default:
Name = "NZXT Kraken X";
_fanControl = true;
_fanChannel = 0x00;
_pumpChannel = 0x40;
_supportedFirmware = "6.2.0";
break;
}
try
{
using HidStream stream = dev.Open();
stream.Write(_getFirmwareInfo);
int tries = 0;
while (FirmwareVersion == null && tries++ < 10)
{
stream.Read(_rawData);
if (_rawData[0] == 0x11 && _rawData[1] == 0x01)
FirmwareVersion = $"{_rawData[0x11]}.{_rawData[0x12]}.{_rawData[0x13]}";
}
if (FirmwareVersion == null)
return;
// Liquid temperature
_liquidTemperature = new Sensor("Liquid", 0, SensorType.Temperature, this, [], settings);
ActivateSensor(_liquidTemperature);
// Pump Control
_pump = new Sensor("Pump Control", 0, SensorType.Control, this, [], settings);
Control pumpControl = new(_pump, settings, 60, 100);
_pump.Control = pumpControl;
pumpControl.ControlModeChanged += c => ControlValueChanged(_pump, c);
pumpControl.SoftwareControlValueChanged += c => ControlValueChanged(_pump, c);
ControlValueChanged(_pump, pumpControl);
ActivateSensor(_pump);
// Pump RPM
_pumpRpm = new Sensor("Pump", 0, SensorType.Fan, this, [], settings);
ActivateSensor(_pumpRpm);
if (_fanControl)
{
// Fan Control
_fan = new Sensor("Fans Control", 1, SensorType.Control, this, [], settings);
Control fanControl = new(_fan, settings, 0, 100);
_fan.Control = fanControl;
fanControl.ControlModeChanged += c => ControlValueChanged(_fan, c);
fanControl.SoftwareControlValueChanged += c => ControlValueChanged(_fan, c);
ControlValueChanged(_fan, fanControl);
ActivateSensor(_fan);
// Fan RPM
_fanRpm = new Sensor("Fans", 1, SensorType.Fan, this, [], settings);
ActivateSensor(_fanRpm);
}
IsValid = true;
}
catch
{ }
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
public string Status => FirmwareVersion != _supportedFirmware ? $"Status: Untested firmware version {FirmwareVersion}! Please consider updating to version {_supportedFirmware}" : "Status: OK";
private void ControlValueChanged(Sensor sensor, IControl control)
{
try
{
if (control.ControlMode == ControlMode.Software)
{
//value will be updated at next Update()
sensor.Value = control.SoftwareValue;
_lastUpdate = DateTime.MinValue;
Update();
}
else
{
//will let the device handle the value
sensor.Value = null;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
public override void Update()
{
try
{
using HidStream stream = _device.Open();
stream.Read(_rawData);
// if not 0x04, it is not temperature data
if (_rawData[0] != 0x04)
return;
// some packet may have 0 as temperature, don't know why just ignore it
if (_rawData[1] == 0x00)
return;
_liquidTemperature.Value = _rawData[1] + (_rawData[2] / 10.0f);
_fanRpm.Value = (_rawData[3] << 8) | _rawData[4];
_pumpRpm.Value = (_rawData[5] << 8) | _rawData[6];
// if we don't have control over the fan or pump, we don't need to update
if (!_pump.Value.HasValue && (!_fanControl || !_fan.Value.HasValue))
return;
//control value need to be updated every 5 seconds or it falls back to default
if (DateTime.Now - _lastUpdate < _interval)
return;
if (_fanControl && _fan.Value.HasValue)
SetDuty(stream, _fanChannel, (byte)_fan.Value);
if (_fanControl && _pump.Value.HasValue)
SetDuty(stream, _pumpChannel, (byte)_pump.Value);
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
private void SetDuty(HidStream stream, byte channel, byte duty)
{
SetDuty(stream, channel, 0, duty);
}
private void SetDuty(HidStream stream, byte channel, byte temperature, byte duty)
{
stream.Write([0x2, 0x4d, channel, temperature, duty]);
_lastUpdate = DateTime.Now;
}
}

View File

@@ -0,0 +1,250 @@
using System;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
/// <summary>
/// Support for the KrakenZ devices.
/// </summary>
internal sealed class KrakenV3 : Hardware
{
private static readonly byte[] _getFirmwareInfo = [0x10, 0x01];
private static readonly byte[] _setFanTarget = new byte[64];
private static readonly byte[] _setPumpTarget = new byte[64];
private static readonly byte[] _statusRequest = [0x74, 0x01];
private readonly Sensor _fan;
private readonly bool _fanControl;
private readonly Sensor _fanRpm;
private readonly Sensor _pump;
private readonly Sensor _pumpRpm;
private readonly byte[] _rawData = new byte[64];
private readonly HidStream _stream;
private readonly string _supportedFirmware;
private readonly Sensor _temperature;
private volatile bool _controllingFans;
private volatile bool _controllingPump;
public KrakenV3(HidDevice dev, ISettings settings) : base("Nzxt Kraken Z", new Identifier(dev), settings)
{
switch (dev.ProductID)
{
case 0x3008:
Name = "NZXT Kraken Z3";
_fanControl = true;
_supportedFirmware = "5.7.0";
Array.Copy(new byte[] { 0x72, 0x01, 0x00, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x00, 0x00 }, 0, _setFanTarget, 0, 4);
break;
case 0x300C:
Name = "NZXT Kraken Elite";
_fanControl = true;
_supportedFirmware = "1.2.4";
Array.Copy(new byte[] { 0x72, 0x01, 0x01, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x01, 0x01 }, 0, _setFanTarget, 0, 4);
break;
case 0x300E:
Name = "NZXT Kraken";
_fanControl = true;
_supportedFirmware = "1.2.4"; // Firmware version to be confirmed
Array.Copy(new byte[] { 0x72, 0x01, 0x01, 0x00 }, 0, _setPumpTarget, 0, 4);
Array.Copy(new byte[] { 0x72, 0x02, 0x01, 0x01 }, 0, _setFanTarget, 0, 4);
break;
default:
Name = "NZXT Kraken X3";
_fanControl = false;
_supportedFirmware = "2.1.0";
Array.Copy(new byte[] { 0x72, 0x01, 0x00, 0x00 }, 0, _setPumpTarget, 0, 4);
break;
}
FillTargetArray(_setPumpTarget, 60);
FillTargetArray(_setFanTarget, 40);
if (dev.TryOpen(out _stream))
{
_stream.ReadTimeout = 5000;
_stream.Write(_getFirmwareInfo);
int tries = 0;
while (FirmwareVersion == null && tries++ < 10)
{
_stream.Read(_rawData);
if (_rawData[0] == 0x11 && _rawData[1] == 0x01)
FirmwareVersion = $"{_rawData[0x11]}.{_rawData[0x12]}.{_rawData[0x13]}";
}
if (FirmwareVersion == null)
return;
// Liquid temperature
_temperature = new Sensor("Liquid", 0, SensorType.Temperature, this, [], settings);
ActivateSensor(_temperature);
// Pump Control
_pump = new Sensor("Pump Control", 0, SensorType.Control, this, [], settings);
Control pumpControl = new(_pump, settings, 20, 100);
_pump.Control = pumpControl;
pumpControl.ControlModeChanged += PumpSoftwareControlValueChanged;
pumpControl.SoftwareControlValueChanged += PumpSoftwareControlValueChanged;
PumpSoftwareControlValueChanged(pumpControl);
ActivateSensor(_pump);
// Pump RPM
_pumpRpm = new Sensor("Pump", 0, SensorType.Fan, this, [], settings);
ActivateSensor(_pumpRpm);
if (_fanControl)
{
// Fan Control
_fan = new Sensor("Fans Control", 1, SensorType.Control, this, [], settings);
Control fanControl = new(_fan, settings, 20, 100);
_fan.Control = fanControl;
fanControl.ControlModeChanged += FanSoftwareControlValueChanged;
fanControl.SoftwareControlValueChanged += FanSoftwareControlValueChanged;
FanSoftwareControlValueChanged(fanControl);
ActivateSensor(_fan);
// Fan RPM
_fanRpm = new Sensor("Fans", 1, SensorType.Fan, this, [], settings);
ActivateSensor(_fanRpm);
}
IsValid = true;
}
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public bool IsValid { get; }
public string Status => FirmwareVersion != _supportedFirmware ? $"Status: Untested firmware version {FirmwareVersion}! Please consider updating to version {_supportedFirmware}" : "Status: OK";
private static void FillTargetArray(byte[] targetArray, byte value)
{
for (byte i = 4; i < targetArray.Length; i++)
targetArray[i] = value;
}
private void PumpSoftwareControlValueChanged(Control control)
{
try
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
FillTargetArray(_setPumpTarget, (byte)(value > 100 ? 100 : value < 0 ? 0 : value));
_controllingPump = true;
_stream.Write(_setPumpTarget);
_pump.Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode with this pump, but a safe setting is 60%
FillTargetArray(_setPumpTarget, 60);
_stream.Write(_setPumpTarget);
break;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
private void FanSoftwareControlValueChanged(Control control)
{
try
{
switch (control.ControlMode)
{
case ControlMode.Software:
float value = control.SoftwareValue;
FillTargetArray(_setFanTarget, (byte)(value > 100 ? 100 : value < 0 ? 0 : value));
_controllingFans = true;
_stream.Write(_setFanTarget);
_fan.Value = value;
break;
case ControlMode.Default:
// There isn't a "default" mode with this fan, but a safe setting is 40%
FillTargetArray(_setFanTarget, 40);
_stream.Write(_setFanTarget);
break;
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
public override void Close()
{
base.Close();
_stream?.Close();
}
public override void Update()
{
try
{
_stream.Write(_statusRequest);
do
{
_stream.Read(_rawData);
}
while (_rawData[0] != 0x75 || _rawData[1] != 0x1);
_temperature.Value = _rawData[15] + (_rawData[16] / 10.0f);
_pumpRpm.Value = (_rawData[18] << 8) | _rawData[17];
// The following logic makes sure the pump is set to the controlling value. This pump sometimes sets itself to 0% when instructed to a value.
if (!_controllingPump)
{
_pump.Value = _rawData[19];
}
else if (_pump.Value != _rawData[19])
{
float value = _pump.Value.GetValueOrDefault();
FillTargetArray(_setPumpTarget, (byte)value);
_stream.Write(_setPumpTarget);
}
else
{
_controllingPump = false;
}
if (_fanControl)
{
_fanRpm.Value = (_rawData[24] << 8) | _rawData[23];
if (!_controllingFans)
{
_fan.Value = _rawData[25];
}
else if (_fan.Value != _rawData[25])
{
float value = _fan.Value.GetValueOrDefault();
FillTargetArray(_setFanTarget, (byte)value);
_stream.Write(_setFanTarget);
}
else
{
_controllingFans = false;
}
}
}
catch (ObjectDisposedException)
{
// Could be unplugged, or the app is stopping...
}
}
}

View File

@@ -0,0 +1,94 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Nzxt;
internal class NzxtGroup : IGroup
{
private readonly List<IHardware> _hardware = new();
private readonly StringBuilder _report = new();
public NzxtGroup(ISettings settings)
{
_report.AppendLine("Nzxt Hardware");
_report.AppendLine();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x1e71))
{
string productName = dev.GetProductName();
switch (dev.ProductID)
{
case 0x170E: // NZXT Kraken X (X42, X52, X62 or X72)
var krakenV2 = new KrakenV2(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {krakenV2.FirmwareVersion}");
_report.AppendLine($"{krakenV2.Status}");
_report.AppendLine();
if (krakenV2.IsValid)
_hardware.Add(krakenV2);
break;
case 0x2007: // Kraken X3 original pid
case 0x2014: // Kraken X3 new pid
case 0x3008: // Kraken Z3
case 0x300C: // Kraken 2023 elite
case 0x300E: // Kraken 2023 standard
// NZXT KrakenV3 Devices
var krakenV3 = new KrakenV3(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {krakenV3.FirmwareVersion}");
_report.AppendLine($"{krakenV3.Status}");
_report.AppendLine();
if (krakenV3.IsValid)
_hardware.Add(krakenV3);
break;
case 0x1711:
var gridv3 = new GridV3(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {gridv3.FirmwareVersion}");
_report.AppendLine();
if (gridv3.IsValid)
_hardware.Add(gridv3);
break;
default:
_report.AppendLine($"Unknown Hardware PID: {dev.ProductID} Name: {productName}");
_report.AppendLine();
break;
}
}
if (_hardware.Count == 0)
{
_report.AppendLine("No Nzxt Hardware found.");
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}

View File

@@ -0,0 +1,408 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Razer;
internal sealed class RazerFanController : Hardware
{
private const int DEFAULT_SPEED_CHANNEL_POWER = 50;
private const byte PERCENT_MIN = 0;
private const byte PERCENT_MAX = 100;
private const int DEVICE_READ_DELAY_MS = 5;
private const int DEVICE_READ_TIMEOUT_MS = 500;
private const int CHANNEL_COUNT = 8;
//private const int FORCE_WRITE_SPEEDS_INTERVAL_MS = 2500; // TODO: Add timer
private HidStream _stream;
private readonly HidDevice _device;
private readonly SequenceCounter _sequenceCounter = new();
private readonly float?[] _pwm = new float?[CHANNEL_COUNT];
private readonly Sensor[] _pwmControls = new Sensor[CHANNEL_COUNT];
private readonly Sensor[] _rpmSensors = new Sensor[CHANNEL_COUNT];
public RazerFanController(HidDevice dev, ISettings settings) : base("Razer PWM PC Fan Controller", new Identifier(dev), settings)
{
_device = dev;
if (_device.TryOpen(out _stream))
{
_stream.ReadTimeout = 5000;
Packet packet = new Packet
{
SequenceNumber = _sequenceCounter.Next(),
DataLength = 0,
CommandClass = CommandClass.Info,
Command = 0x87,
};
if (Mutexes.WaitRazer(250))
{
while (FirmwareVersion == null)
{
Thread.Sleep(DEVICE_READ_DELAY_MS);
try
{
Packet response = TryWriteAndRead(packet);
FirmwareVersion = $"{response.Data[0]:D}.{response.Data[1]:D2}.{response.Data[2]:D2}";
}
catch { }
}
Mutexes.ReleaseRazer();
}
Name = "Razer PWM PC Fan Controller";
for (int i = 0; i < CHANNEL_COUNT; i++)
{
// Fan Control
_pwmControls[i] = new Sensor("Fan Control #" + (i + 1), i, SensorType.Control, this, Array.Empty<ParameterDescription>(), settings);
Control fanControl = new(_pwmControls[i], settings, PERCENT_MIN, PERCENT_MAX);
_pwmControls[i].Control = fanControl;
fanControl.ControlModeChanged += FanSoftwareControlValueChanged;
fanControl.SoftwareControlValueChanged += FanSoftwareControlValueChanged;
//fanControl.SetDefault();
FanSoftwareControlValueChanged(fanControl);
ActivateSensor(_pwmControls[i]);
// Fan RPM
_rpmSensors[i] = new Sensor("Fan #" + (i + 1), i, SensorType.Fan, this, Array.Empty<ParameterDescription>(), settings);
ActivateSensor(_rpmSensors[i]);
}
}
}
public string FirmwareVersion { get; }
public override HardwareType HardwareType => HardwareType.Cooler;
public string Status => FirmwareVersion != "1.01.00" ? $"Status: Untested Firmware Version {FirmwareVersion}! Please consider Updating to Version 1.01.00" : "Status: OK";
private void FanSoftwareControlValueChanged(Control control) // TODO: Add timer here
{
if (control.ControlMode == ControlMode.Undefined || !Mutexes.WaitRazer(250))
return;
if (control.ControlMode == ControlMode.Software)
{
SetChannelModeToManual(control.Sensor.Index);
float value = control.SoftwareValue;
byte fanSpeed = (byte)(value > 100 ? 100 : value < 0 ? 0 : value);
var packet = new Packet
{
SequenceNumber = _sequenceCounter.Next(),
DataLength = 3,
CommandClass = CommandClass.Pwm,
Command = PwmCommand.SetChannelPercent,
};
packet.Data[0] = 0x01;
packet.Data[1] = (byte)(0x05 + control.Sensor.Index);
packet.Data[2] = fanSpeed;
TryWriteAndRead(packet);
_pwm[control.Sensor.Index] = value;
}
else if (control.ControlMode == ControlMode.Default)
{
SetChannelModeToManual(control.Sensor.Index); // TODO: switch to auto mode here if it enabled before
var packet = new Packet
{
SequenceNumber = _sequenceCounter.Next(),
DataLength = 3,
CommandClass = CommandClass.Pwm,
Command = PwmCommand.SetChannelPercent,
};
packet.Data[0] = 0x01;
packet.Data[1] = (byte)(0x05 + control.Sensor.Index);
packet.Data[2] = DEFAULT_SPEED_CHANNEL_POWER;
TryWriteAndRead(packet);
_pwm[control.Sensor.Index] = DEFAULT_SPEED_CHANNEL_POWER;
}
Mutexes.ReleaseRazer();
}
private int GetChannelSpeed(int channel)
{
Packet packet = new Packet
{
SequenceNumber = _sequenceCounter.Next(),
DataLength = 6,
CommandClass = CommandClass.Pwm,
Command = PwmCommand.GetChannelSpeed,
};
packet.Data[0] = 0x01;
packet.Data[1] = (byte)(0x05 + channel);
Packet response = TryWriteAndRead(packet);
return (response.Data[4] << 8) | response.Data[5];
}
private void SetChannelModeToManual(int channel)
{
Packet packet = new Packet
{
SequenceNumber = _sequenceCounter.Next(),
DataLength = 3,
CommandClass = CommandClass.Pwm,
Command = PwmCommand.SetChannelMode,
};
packet.Data[0] = 0x01;
packet.Data[1] = (byte)(0x05 + channel);
packet.Data[2] = 0x04;
TryWriteAndRead(packet);
}
private void ThrowIfNotReady()
{
bool @throw;
try
{
@throw = _stream is null;
}
catch (ObjectDisposedException)
{
@throw = true;
}
if (@throw)
{
throw new InvalidOperationException("The device is not ready.");
}
}
private Packet TryWriteAndRead(Packet packet)
{
Packet readPacket = null;
int devTimeout = 400;
int devReconnectTimeout = 3000;
do
{
try
{
byte[] response = Packet.CreateBuffer();
byte[] buffer = packet.ToBuffer();
ThrowIfNotReady();
_stream?.SetFeature(buffer, 0, buffer.Length);
Thread.Sleep(DEVICE_READ_DELAY_MS);
ThrowIfNotReady();
_stream?.GetFeature(response, 0, response.Length);
readPacket = Packet.FromBuffer(response);
if (readPacket.Status == DeviceStatus.Busy)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < DEVICE_READ_TIMEOUT_MS && readPacket.Status == DeviceStatus.Busy)
{
Thread.Sleep(DEVICE_READ_DELAY_MS);
ThrowIfNotReady();
_stream?.GetFeature(response, 0, response.Length);
readPacket = Packet.FromBuffer(response);
}
}
}
catch (IOException) // Unexpected device disconnect or fan plug/unplug
{
if (devTimeout <= 0)
{
while (devReconnectTimeout > 0)
{
_stream?.Close();
if (_device.TryOpen(out _stream))
break;
Thread.Sleep(1000);
devReconnectTimeout -= 500;
}
if (devReconnectTimeout <= 0) // Device disconnected
{
for (int i = 0; i < CHANNEL_COUNT; i++)
{
_pwmControls[i].Control = null;
_pwmControls[i].Value = null;
_rpmSensors[i].Value = null;
_pwm[i] = null;
DeactivateSensor(_pwmControls[i]);
DeactivateSensor(_rpmSensors[i]);
}
Close();
Packet ret = new Packet();
for (int i = 0; i < 80; i++)
ret.Data[i] = 0;
return ret;
}
devTimeout = 400;
}
Thread.Sleep(DEVICE_READ_DELAY_MS);
devTimeout -= DEVICE_READ_DELAY_MS;
}
} while (readPacket == null);
return readPacket;
}
public override void Close()
{
base.Close();
_stream?.Close();
}
public override void Update()
{
if (!Mutexes.WaitRazer(250))
return;
for (int i = 0; i < CHANNEL_COUNT; i++)
{
_rpmSensors[i].Value = GetChannelSpeed(i);
_pwmControls[i].Value = _pwm[i];
}
Mutexes.ReleaseRazer();
}
private enum DeviceStatus : byte
{
Default = 0x00,
Busy = 0x01,
Success = 0x02,
Error = 0x03,
Timeout = 0x04,
Invalid = 0x05,
}
private enum ProtocolType : byte
{
Default = 0x00,
}
private static class CommandClass
{
public static readonly byte Info = 0x00;
public static readonly byte Pwm = 0x0d;
}
private static class PwmCommand
{
public static readonly byte SetChannelPercent = 0x0d;
public static readonly byte SetChannelMode = 0x02;
public static readonly byte GetChannelSpeed = 0x81;
}
private sealed class Packet
{
public byte ReportId { get; set; }
public DeviceStatus Status { get; set; }
public byte SequenceNumber { get; set; }
public short RemainingCount { get; set; }
public ProtocolType ProtocolType { get; set; }
public byte DataLength { get; set; }
public byte CommandClass { get; set; }
public byte Command { get; set; }
public byte[] Data { get; } = new byte[80];
public byte CRC { get; set; }
public byte Reserved { get; set; }
public byte[] ToBuffer()
{
byte[] buffer = CreateBuffer();
buffer[0] = ReportId;
buffer[1] = (byte)Status;
buffer[2] = SequenceNumber;
buffer[3] = (byte)((RemainingCount >> 8) & 0xff);
buffer[4] = (byte)(RemainingCount & 0xff);
buffer[5] = (byte)ProtocolType;
buffer[6] = DataLength;
buffer[7] = CommandClass;
buffer[8] = Command;
for (int i = 0; i < Data.Length; i++)
buffer[9 + i] = Data[i];
buffer[89] = GenerateChecksum(buffer);
buffer[90] = Reserved;
return buffer;
}
public static Packet FromBuffer(byte[] buffer)
{
var packet = new Packet
{
ReportId = buffer[0],
Status = (DeviceStatus)buffer[1],
SequenceNumber = buffer[2],
RemainingCount = (short)((buffer[3] << 8) | buffer[4]),
ProtocolType = (ProtocolType)buffer[5],
DataLength = buffer[6],
CommandClass = buffer[7],
Command = buffer[8],
CRC = buffer[89],
Reserved = buffer[90]
};
for (int i = 0; i < packet.Data.Length; i++)
packet.Data[i] = buffer[9 + i];
return packet;
}
public static byte[] CreateBuffer() => new byte[91];
internal static byte GenerateChecksum(byte[] buffer)
{
byte result = 0;
for (int i = 3; i < 89; i++)
{
result = (byte)(result ^ buffer[i]);
}
return result;
}
}
private sealed class SequenceCounter
{
private byte _sequenceId = 0x00;
public byte Next()
{
while (_sequenceId == 0x00)
{
_sequenceId += 0x08;
}
return _sequenceId;
}
}
}

View File

@@ -0,0 +1,69 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware.Controller.Razer;
internal class RazerGroup : IGroup
{
private readonly List<IHardware> _hardware = new();
private readonly StringBuilder _report = new();
public RazerGroup(ISettings settings)
{
_report.AppendLine("Razer Hardware");
_report.AppendLine();
foreach (HidDevice dev in DeviceList.Local.GetHidDevices(0x1532))
{
string productName = dev.GetProductName();
switch (dev.ProductID)
{
case 0x0F3C: // Razer PWM PC fan controller
if (dev.GetMaxFeatureReportLength() <= 0)
break;
var device = new RazerFanController(dev, settings);
_report.AppendLine($"Device name: {productName}");
_report.AppendLine($"Firmware version: {device.FirmwareVersion}");
_report.AppendLine($"{device.Status}");
_report.AppendLine();
_hardware.Add(device);
break;
default:
_report.AppendLine($"Unknown Hardware PID: {dev.ProductID} Name: {productName}");
_report.AppendLine();
break;
}
}
if (_hardware.Count == 0)
{
_report.AppendLine("No Razer Hardware found.");
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (IHardware iHardware in _hardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
public string GetReport()
{
return _report.ToString();
}
}

View File

@@ -0,0 +1,373 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.Interop;
// ReSharper disable InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Controller.TBalancer;
internal class TBalancer : Hardware
{
internal const byte EndFlag = 254;
internal const byte StartFlag = 100;
private readonly MethodDelegate _alternativeRequest;
private readonly Sensor[] _analogTemperatures = new Sensor[4];
private readonly Sensor[] _controls = new Sensor[4];
private readonly List<ISensor> _deactivating = new();
private readonly Sensor[] _digitalTemperatures = new Sensor[8];
private readonly Sensor[] _fans = new Sensor[4];
private readonly Sensor[] _miniNgControls = new Sensor[4];
private readonly Sensor[] _miniNgFans = new Sensor[4];
private readonly Sensor[] _miniNgTemperatures = new Sensor[4];
private readonly int _portIndex;
private readonly byte _protocolVersion;
private readonly Sensor[] _sensorHubFlows = new Sensor[2];
private readonly Sensor[] _sensorHubTemperatures = new Sensor[6];
private byte[] _alternativeData = Array.Empty<byte>();
private readonly byte[] _data = new byte[285];
private Ftd2xx.FT_HANDLE _handle;
private byte[] _primaryData = Array.Empty<byte>();
public TBalancer(int portIndex, byte protocolVersion, ISettings settings) :
base("T-Balancer bigNG", new Identifier("bigng", portIndex.ToString(CultureInfo.InvariantCulture)), settings)
{
_portIndex = portIndex;
_protocolVersion = protocolVersion;
ParameterDescription[] parameter = { new("Offset [°C]", "Temperature offset.", 0) };
int offset = 0;
for (int i = 0; i < _digitalTemperatures.Length; i++)
_digitalTemperatures[i] = new Sensor("Digital Sensor " + i, offset + i, SensorType.Temperature, this, parameter, settings);
offset += _digitalTemperatures.Length;
for (int i = 0; i < _analogTemperatures.Length; i++)
_analogTemperatures[i] = new Sensor("Analog Sensor " + (i + 1), offset + i, SensorType.Temperature, this, parameter, settings);
offset += _analogTemperatures.Length;
for (int i = 0; i < _sensorHubTemperatures.Length; i++)
_sensorHubTemperatures[i] = new Sensor("Sensorhub Sensor " + i, offset + i, SensorType.Temperature, this, parameter, settings);
offset += _sensorHubTemperatures.Length;
for (int i = 0; i < _miniNgTemperatures.Length; i++)
_miniNgTemperatures[i] = new Sensor("miniNG #" + ((i / 2) + 1) + " Sensor " + ((i % 2) + 1), offset + i, SensorType.Temperature, this, parameter, settings);
for (int i = 0; i < _sensorHubFlows.Length; i++)
{
_sensorHubFlows[i] = new Sensor("Flowmeter " + (i + 1),
i,
SensorType.Flow,
this,
new[] { new ParameterDescription("Impulse Rate", "The impulse rate of the flowmeter in pulses/L", 509) },
settings);
}
for (int i = 0; i < _controls.Length; i++)
{
_controls[i] = new Sensor("Fan Channel " + i, i, SensorType.Control, this, settings);
}
for (int i = 0; i < _miniNgControls.Length; i++)
{
_miniNgControls[i] = new Sensor("miniNG #" + ((i / 2) + 1) + " Fan Channel " + ((i % 2) + 1), 4 + i, SensorType.Control, this, settings);
}
_alternativeRequest = DelayedAlternativeRequest;
Open();
Update();
}
public override HardwareType HardwareType
{
get { return HardwareType.Cooler; }
}
protected override void ActivateSensor(ISensor sensor)
{
_deactivating.Remove(sensor);
base.ActivateSensor(sensor);
}
protected override void DeactivateSensor(ISensor sensor)
{
if (_deactivating.Contains(sensor))
{
_deactivating.Remove(sensor);
base.DeactivateSensor(sensor);
}
else if (_active.Contains(sensor))
{
_deactivating.Add(sensor);
}
}
private void ReadMiniNg(int number)
{
int offset = 1 + (number * 65);
if (_data[offset + 61] != EndFlag)
return;
for (int i = 0; i < 2; i++)
{
Sensor sensor = _miniNgTemperatures[(number * 2) + i];
if (_data[offset + 7 + i] > 0)
{
sensor.Value = (0.5f * _data[offset + 7 + i]) +
sensor.Parameters[0].Value;
ActivateSensor(sensor);
}
else
{
DeactivateSensor(sensor);
}
}
for (int i = 0; i < 2; i++)
{
_miniNgFans[(number * 2) + i] ??= new Sensor("miniNG #" + (number + 1) + " Fan Channel " + (i + 1), 4 + (number * 2) + i, SensorType.Fan, this, _settings);
Sensor sensor = _miniNgFans[(number * 2) + i];
sensor.Value = 20.0f * _data[offset + 43 + (2 * i)];
ActivateSensor(sensor);
}
for (int i = 0; i < 2; i++)
{
Sensor sensor = _miniNgControls[(number * 2) + i];
sensor.Value = _data[offset + 15 + i];
ActivateSensor(sensor);
}
}
private void ReadData()
{
Ftd2xx.Read(_handle, _data);
if (_data[0] != StartFlag)
{
Ftd2xx.FT_Purge(_handle, Ftd2xx.FT_PURGE.FT_PURGE_RX);
return;
}
if (_data[1] is 255 or 88)
{
// bigNG
if (_data[274] != _protocolVersion)
return;
if (_primaryData.Length == 0)
_primaryData = new byte[_data.Length];
_data.CopyTo(_primaryData, 0);
for (int i = 0; i < _digitalTemperatures.Length; i++)
{
if (_data[238 + i] > 0)
{
_digitalTemperatures[i].Value = (0.5f * _data[238 + i]) + _digitalTemperatures[i].Parameters[0].Value;
ActivateSensor(_digitalTemperatures[i]);
}
else
{
DeactivateSensor(_digitalTemperatures[i]);
}
}
for (int i = 0; i < _analogTemperatures.Length; i++)
{
if (_data[260 + i] > 0)
{
_analogTemperatures[i].Value = (0.5f * _data[260 + i]) + _analogTemperatures[i].Parameters[0].Value;
ActivateSensor(_analogTemperatures[i]);
}
else
{
DeactivateSensor(_analogTemperatures[i]);
}
}
for (int i = 0; i < _sensorHubTemperatures.Length; i++)
{
if (_data[246 + i] > 0)
{
_sensorHubTemperatures[i].Value = (0.5f * _data[246 + i]) + _sensorHubTemperatures[i].Parameters[0].Value;
ActivateSensor(_sensorHubTemperatures[i]);
}
else
{
DeactivateSensor(_sensorHubTemperatures[i]);
}
}
for (int i = 0; i < _sensorHubFlows.Length; i++)
{
if (_data[231 + i] > 0 && _data[234] > 0)
{
float pulsesPerSecond = (_data[231 + i] * 4.0f) / _data[234];
float pulsesPerLiter = _sensorHubFlows[i].Parameters[0].Value;
_sensorHubFlows[i].Value = pulsesPerSecond * 3600 / pulsesPerLiter;
ActivateSensor(_sensorHubFlows[i]);
}
else
{
DeactivateSensor(_sensorHubFlows[i]);
}
}
for (int i = 0; i < _fans.Length; i++)
{
float maxRpm = 11.5f * ((_data[149 + (2 * i)] << 8) | _data[148 + (2 * i)]);
_fans[i] ??= new Sensor("Fan Channel " + i,
i,
SensorType.Fan,
this,
new[]
{
new ParameterDescription("MaxRPM",
"Maximum revolutions per minute (RPM) of the fan.",
maxRpm)
},
_settings);
float value;
if ((_data[136] & (1 << i)) == 0) // pwm mode
value = 0.02f * _data[137 + i];
else // analog mode
value = 0.01f * _data[141 + i];
_fans[i].Value = _fans[i].Parameters[0].Value * value;
ActivateSensor(_fans[i]);
_controls[i].Value = 100 * value;
ActivateSensor(_controls[i]);
}
}
else if (_data[1] == 253)
{
// miniNG #1
if (_alternativeData.Length == 0)
_alternativeData = new byte[_data.Length];
_data.CopyTo(_alternativeData, 0);
ReadMiniNg(0);
if (_data[66] == 253) // miniNG #2
ReadMiniNg(1);
}
}
public override string GetReport()
{
StringBuilder r = new();
r.AppendLine("T-Balancer bigNG");
r.AppendLine();
r.Append("Port Index: ");
r.AppendLine(_portIndex.ToString(CultureInfo.InvariantCulture));
r.AppendLine();
r.AppendLine("Primary System Information Answer");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
for (int i = 0; i <= 0x11; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X3", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
int index = (i << 4) | j;
if (index < _primaryData.Length)
{
r.Append(" ");
r.Append(_primaryData[index].ToString("X2", CultureInfo.InvariantCulture));
}
}
r.AppendLine();
}
r.AppendLine();
if (_alternativeData.Length > 0)
{
r.AppendLine("Alternative System Information Answer");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
for (int i = 0; i <= 0x11; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X3", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
int index = (i << 4) | j;
if (index < _alternativeData.Length)
{
r.Append(" ");
r.Append(_alternativeData[index].ToString("X2", CultureInfo.InvariantCulture));
}
}
r.AppendLine();
}
r.AppendLine();
}
return r.ToString();
}
private void DelayedAlternativeRequest()
{
System.Threading.Thread.Sleep(500);
Ftd2xx.Write(_handle, new byte[] { 0x37 });
}
public void Open()
{
Ftd2xx.FT_Open(_portIndex, out _handle);
Ftd2xx.FT_SetBaudRate(_handle, 19200);
Ftd2xx.FT_SetDataCharacteristics(_handle, 8, 1, 0);
Ftd2xx.FT_SetFlowControl(_handle, Ftd2xx.FT_FLOW_CONTROL.FT_FLOW_RTS_CTS, 0x11, 0x13);
Ftd2xx.FT_SetTimeouts(_handle, 1000, 1000);
Ftd2xx.FT_Purge(_handle, Ftd2xx.FT_PURGE.FT_PURGE_ALL);
}
public sealed override void Update()
{
while (Ftd2xx.BytesToRead(_handle) >= 285)
ReadData();
if (Ftd2xx.BytesToRead(_handle) == 1)
Ftd2xx.ReadByte(_handle);
Ftd2xx.Write(_handle, new byte[] { 0x38 });
_alternativeRequest.BeginInvoke(null, null);
}
public override void Close()
{
Ftd2xx.FT_Close(_handle);
base.Close();
}
private delegate void MethodDelegate();
}

View File

@@ -0,0 +1,180 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using LibreHardwareMonitor.Interop;
// ReSharper disable InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Controller.TBalancer;
internal class TBalancerGroup : IGroup
{
private readonly List<TBalancer> _hardware = new();
private readonly StringBuilder _report = new();
public TBalancerGroup(ISettings settings)
{
uint numDevices;
try
{
if (!Ftd2xx.DllExists())
{
_report.AppendLine("Status: missing DLL");
return;
}
if (Ftd2xx.FT_CreateDeviceInfoList(out numDevices) != Ftd2xx.FT_STATUS.FT_OK)
{
_report.AppendLine("Status: FT_CreateDeviceInfoList failed");
return;
}
}
catch (Exception e) when (e is DllNotFoundException or ArgumentNullException or EntryPointNotFoundException or BadImageFormatException)
{
return;
}
Ftd2xx.FT_DEVICE_INFO_NODE[] info = new Ftd2xx.FT_DEVICE_INFO_NODE[numDevices];
if (Ftd2xx.FT_GetDeviceInfoList(info, ref numDevices) != Ftd2xx.FT_STATUS.FT_OK)
{
_report.AppendLine("Status: FT_GetDeviceInfoList failed");
return;
}
// make sure numDevices is not larger than the info array
if (numDevices > info.Length)
numDevices = (uint)info.Length;
for (int i = 0; i < numDevices; i++)
{
_report.Append("Device Index: ");
_report.AppendLine(i.ToString(CultureInfo.InvariantCulture));
_report.Append("Device Type: ");
_report.AppendLine(info[i].Type.ToString());
// the T-Balancer always uses an FT232BM
if (info[i].Type != Ftd2xx.FT_DEVICE.FT_DEVICE_232BM)
{
_report.AppendLine("Status: Wrong device type");
continue;
}
Ftd2xx.FT_STATUS status = Ftd2xx.FT_Open(i, out Ftd2xx.FT_HANDLE handle);
if (status != Ftd2xx.FT_STATUS.FT_OK)
{
_report.AppendLine("Open Status: " + status);
continue;
}
Ftd2xx.FT_SetBaudRate(handle, 19200);
Ftd2xx.FT_SetDataCharacteristics(handle, 8, 1, 0);
Ftd2xx.FT_SetFlowControl(handle, Ftd2xx.FT_FLOW_CONTROL.FT_FLOW_RTS_CTS, 0x11, 0x13);
Ftd2xx.FT_SetTimeouts(handle, 1000, 1000);
Ftd2xx.FT_Purge(handle, Ftd2xx.FT_PURGE.FT_PURGE_ALL);
status = Ftd2xx.Write(handle, new byte[] { 0x38 });
if (status != Ftd2xx.FT_STATUS.FT_OK)
{
_report.AppendLine("Write Status: " + status);
Ftd2xx.FT_Close(handle);
continue;
}
bool isValid = false;
byte protocolVersion = 0;
int j = 0;
while (Ftd2xx.BytesToRead(handle) == 0 && j < 2)
{
Thread.Sleep(100);
j++;
}
if (Ftd2xx.BytesToRead(handle) > 0)
{
if (Ftd2xx.ReadByte(handle) == TBalancer.StartFlag)
{
while (Ftd2xx.BytesToRead(handle) < 284 && j < 5)
{
Thread.Sleep(100);
j++;
}
int length = Ftd2xx.BytesToRead(handle);
if (length >= 284)
{
byte[] data = new byte[285];
data[0] = TBalancer.StartFlag;
for (int k = 1; k < data.Length; k++)
data[k] = Ftd2xx.ReadByte(handle);
// check protocol version 2X (protocols seen: 2C, 2A, 28)
isValid = (data[274] & 0xF0) == 0x20;
protocolVersion = data[274];
if (!isValid)
{
_report.Append("Status: Wrong Protocol Version: 0x");
_report.AppendLine(protocolVersion.ToString("X", CultureInfo.InvariantCulture));
}
}
else
{
_report.AppendLine("Status: Wrong Message Length: " + length);
}
}
else
{
_report.AppendLine("Status: Wrong Startflag");
}
}
else
{
_report.AppendLine("Status: No Response");
}
Ftd2xx.FT_Purge(handle, Ftd2xx.FT_PURGE.FT_PURGE_ALL);
Ftd2xx.FT_Close(handle);
if (isValid)
{
_report.AppendLine("Status: OK");
_hardware.Add(new TBalancer(i, protocolVersion, settings));
}
if (i < numDevices - 1)
_report.AppendLine();
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
if (_report.Length > 0)
{
StringBuilder r = new();
r.AppendLine("FTD2XX");
r.AppendLine();
r.Append(_report);
r.AppendLine();
return r.ToString();
}
return null;
}
public void Close()
{
foreach (TBalancer balancer in _hardware)
balancer.Close();
}
}

View File

@@ -0,0 +1,161 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.Text;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal sealed class Amd0FCpu : AmdCpu
{
private readonly Sensor _busClock;
private readonly Sensor[] _coreClocks;
private readonly Sensor[] _coreTemperatures;
private readonly uint _miscellaneousControlAddress;
private readonly byte _thermSenseCoreSelCPU0;
private readonly byte _thermSenseCoreSelCPU1;
public Amd0FCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
{
float offset = -49.0f;
// AM2+ 65nm +21 offset
uint model = cpuId[0][0].Model;
if (model is >= 0x69 and not 0xc1 and not 0x6c and not 0x7c)
offset += 21;
if (model < 40)
{
// AMD Athlon 64 Processors
_thermSenseCoreSelCPU0 = 0x0;
_thermSenseCoreSelCPU1 = 0x4;
}
else
{
// AMD NPT Family 0Fh Revision F, G have the core selection swapped
_thermSenseCoreSelCPU0 = 0x4;
_thermSenseCoreSelCPU1 = 0x0;
}
// check if processor supports a digital thermal sensor
if (cpuId[0][0].ExtData.GetLength(0) > 7 && (cpuId[0][0].ExtData[7, 3] & 1) != 0)
{
_coreTemperatures = new Sensor[_coreCount];
for (int i = 0; i < _coreCount; i++)
{
_coreTemperatures[i] = new Sensor("Core #" + (i + 1),
i,
SensorType.Temperature,
this,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\nTemperature = Value + Offset.", offset) },
settings);
}
}
else
{
_coreTemperatures = Array.Empty<Sensor>();
}
_miscellaneousControlAddress = GetPciAddress(MISCELLANEOUS_CONTROL_FUNCTION, MISCELLANEOUS_CONTROL_DEVICE_ID);
_busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings);
_coreClocks = new Sensor[_coreCount];
for (int i = 0; i < _coreClocks.Length; i++)
{
_coreClocks[i] = new Sensor(CoreString(i), i + 1, SensorType.Clock, this, settings);
if (HasTimeStampCounter)
ActivateSensor(_coreClocks[i]);
}
Update();
}
protected override uint[] GetMsrs()
{
return new[] { FIDVID_STATUS };
}
public override string GetReport()
{
StringBuilder r = new();
r.Append(base.GetReport());
r.Append("Miscellaneous Control Address: 0x");
r.AppendLine(_miscellaneousControlAddress.ToString("X", CultureInfo.InvariantCulture));
r.AppendLine();
return r.ToString();
}
public override void Update()
{
base.Update();
if (Mutexes.WaitPciBus(10))
{
if (_miscellaneousControlAddress != Interop.Ring0.INVALID_PCI_ADDRESS)
{
for (uint i = 0; i < _coreTemperatures.Length; i++)
{
if (Ring0.WritePciConfig(_miscellaneousControlAddress,
THERMTRIP_STATUS_REGISTER,
i > 0 ? _thermSenseCoreSelCPU1 : _thermSenseCoreSelCPU0))
{
if (Ring0.ReadPciConfig(_miscellaneousControlAddress, THERMTRIP_STATUS_REGISTER, out uint value))
{
_coreTemperatures[i].Value = ((value >> 16) & 0xFF) + _coreTemperatures[i].Parameters[0].Value;
ActivateSensor(_coreTemperatures[i]);
}
else
{
DeactivateSensor(_coreTemperatures[i]);
}
}
}
}
Mutexes.ReleasePciBus();
}
if (HasTimeStampCounter)
{
double newBusClock = 0;
for (int i = 0; i < _coreClocks.Length; i++)
{
Thread.Sleep(1);
if (Ring0.ReadMsr(FIDVID_STATUS, out uint eax, out uint _, _cpuId[i][0].Affinity))
{
// CurrFID can be found in eax bits 0-5, MaxFID in 16-21
// 8-13 hold StartFID, we don't use that here.
double curMp = 0.5 * ((eax & 0x3F) + 8);
double maxMp = 0.5 * ((eax >> 16 & 0x3F) + 8);
_coreClocks[i].Value = (float)(curMp * TimeStampCounterFrequency / maxMp);
newBusClock = (float)(TimeStampCounterFrequency / maxMp);
}
else
{
// Fail-safe value - if the code above fails, we'll use this instead
_coreClocks[i].Value = (float)TimeStampCounterFrequency;
}
}
if (newBusClock > 0)
{
_busClock.Value = (float)newBusClock;
ActivateSensor(_busClock);
}
}
}
// ReSharper disable InconsistentNaming
private const uint FIDVID_STATUS = 0xC0010042;
private const ushort MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1103;
private const byte MISCELLANEOUS_CONTROL_FUNCTION = 3;
private const uint THERMTRIP_STATUS_REGISTER = 0xE4;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,504 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal sealed class Amd10Cpu : AmdCpu
{
private readonly Sensor _busClock;
private readonly Sensor[] _coreClocks;
private readonly Sensor _coreTemperature;
private readonly Sensor _coreVoltage;
private readonly byte _cStatesIoOffset;
private readonly Sensor[] _cStatesResidency;
private readonly bool _hasSmuTemperatureRegister;
private readonly bool _isSvi2;
private readonly uint _miscellaneousControlAddress;
private readonly Sensor _northbridgeVoltage;
private readonly FileStream _temperatureStream;
private readonly double _timeStampCounterMultiplier;
public Amd10Cpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
{
// AMD family 1Xh processors support only one temperature sensor
ushort miscellaneousControlDeviceId;
_coreTemperature = new Sensor("CPU Cores", 0, SensorType.Temperature, this, new[] { new ParameterDescription("Offset [°C]", "Temperature offset.", 0) }, settings);
_coreVoltage = new Sensor("CPU Cores", 0, SensorType.Voltage, this, settings);
ActivateSensor(_coreVoltage);
_northbridgeVoltage = new Sensor("Northbridge", 0, SensorType.Voltage, this, settings);
ActivateSensor(_northbridgeVoltage);
_isSvi2 = (_family == 0x15 && _model >= 0x10) || _family == 0x16;
switch (_family)
{
case 0x10:
miscellaneousControlDeviceId = FAMILY_10H_MISCELLANEOUS_CONTROL_DEVICE_ID;
break;
case 0x11:
miscellaneousControlDeviceId = FAMILY_11H_MISCELLANEOUS_CONTROL_DEVICE_ID;
break;
case 0x12:
miscellaneousControlDeviceId = FAMILY_12H_MISCELLANEOUS_CONTROL_DEVICE_ID;
break;
case 0x14:
miscellaneousControlDeviceId = FAMILY_14H_MISCELLANEOUS_CONTROL_DEVICE_ID;
break;
case 0x15:
switch (_model & 0xF0)
{
case 0x00:
miscellaneousControlDeviceId = FAMILY_15H_MODEL_00_MISC_CONTROL_DEVICE_ID;
break;
case 0x10:
miscellaneousControlDeviceId = FAMILY_15H_MODEL_10_MISC_CONTROL_DEVICE_ID;
break;
case 0x30:
miscellaneousControlDeviceId = FAMILY_15H_MODEL_30_MISC_CONTROL_DEVICE_ID;
break;
case 0x70:
miscellaneousControlDeviceId = FAMILY_15H_MODEL_70_MISC_CONTROL_DEVICE_ID;
_hasSmuTemperatureRegister = true;
break;
case 0x60:
miscellaneousControlDeviceId = FAMILY_15H_MODEL_60_MISC_CONTROL_DEVICE_ID;
_hasSmuTemperatureRegister = true;
break;
default:
miscellaneousControlDeviceId = 0;
break;
}
break;
case 0x16:
miscellaneousControlDeviceId = (_model & 0xF0) switch
{
0x00 => FAMILY_16H_MODEL_00_MISC_CONTROL_DEVICE_ID,
0x30 => FAMILY_16H_MODEL_30_MISC_CONTROL_DEVICE_ID,
_ => 0
};
break;
default:
miscellaneousControlDeviceId = 0;
break;
}
// get the pci address for the Miscellaneous Control registers
_miscellaneousControlAddress = GetPciAddress(MISCELLANEOUS_CONTROL_FUNCTION, miscellaneousControlDeviceId);
_busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings);
_coreClocks = new Sensor[_coreCount];
for (int i = 0; i < _coreClocks.Length; i++)
{
_coreClocks[i] = new Sensor(CoreString(i), i + 1, SensorType.Clock, this, settings);
if (HasTimeStampCounter)
ActivateSensor(_coreClocks[i]);
}
bool corePerformanceBoostSupport = (cpuId[0][0].ExtData[7, 3] & (1 << 9)) > 0;
// set affinity to the first thread for all frequency estimations
GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity);
// disable core performance boost
Ring0.ReadMsr(HWCR, out uint hwcrEax, out uint hwcrEdx);
if (corePerformanceBoostSupport)
Ring0.WriteMsr(HWCR, hwcrEax | (1 << 25), hwcrEdx);
Ring0.ReadMsr(PERF_CTL_0, out uint ctlEax, out uint ctlEdx);
Ring0.ReadMsr(PERF_CTR_0, out uint ctrEax, out uint ctrEdx);
_timeStampCounterMultiplier = EstimateTimeStampCounterMultiplier();
// restore the performance counter registers
Ring0.WriteMsr(PERF_CTL_0, ctlEax, ctlEdx);
Ring0.WriteMsr(PERF_CTR_0, ctrEax, ctrEdx);
// restore core performance boost
if (corePerformanceBoostSupport)
Ring0.WriteMsr(HWCR, hwcrEax, hwcrEdx);
// restore the thread affinity.
ThreadAffinity.Set(previousAffinity);
// the file reader for lm-sensors support on Linux
_temperatureStream = null;
if (Software.OperatingSystem.IsUnix)
{
foreach (string path in Directory.GetDirectories("/sys/class/hwmon/"))
{
string name = null;
try
{
using StreamReader reader = new(path + "/device/name");
name = reader.ReadLine();
}
catch (IOException)
{ }
_temperatureStream = name switch
{
"k10temp" => new FileStream(path + "/device/temp1_input", FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
_ => _temperatureStream
};
}
}
uint addr = Ring0.GetPciAddress(0, 20, 0);
if (Ring0.ReadPciConfig(addr, 0, out uint dev))
{
Ring0.ReadPciConfig(addr, 8, out uint rev);
_cStatesIoOffset = dev switch
{
0x43851002 => (byte)((rev & 0xFF) < 0x40 ? 0xB3 : 0x9C),
0x780B1022 or 0x790B1022 => 0x9C,
_ => _cStatesIoOffset
};
}
if (_cStatesIoOffset != 0)
{
_cStatesResidency = new[] { new Sensor("CPU Package C2", 0, SensorType.Level, this, settings), new Sensor("CPU Package C3", 1, SensorType.Level, this, settings) };
ActivateSensor(_cStatesResidency[0]);
ActivateSensor(_cStatesResidency[1]);
}
Update();
}
private double EstimateTimeStampCounterMultiplier()
{
// preload the function
EstimateTimeStampCounterMultiplier(0);
EstimateTimeStampCounterMultiplier(0);
// estimate the multiplier
List<double> estimate = new(3);
for (int i = 0; i < 3; i++)
estimate.Add(EstimateTimeStampCounterMultiplier(0.025));
estimate.Sort();
return estimate[1];
}
private double EstimateTimeStampCounterMultiplier(double timeWindow)
{
// select event "076h CPU Clocks not Halted" and enable the counter
Ring0.WriteMsr(PERF_CTL_0,
(1 << 22) | // enable performance counter
(1 << 17) | // count events in user mode
(1 << 16) | // count events in operating-system mode
0x76,
0x00000000);
// set the counter to 0
Ring0.WriteMsr(PERF_CTR_0, 0, 0);
long ticks = (long)(timeWindow * Stopwatch.Frequency);
long timeBegin = Stopwatch.GetTimestamp() +
(long)Math.Ceiling(0.001 * ticks);
long timeEnd = timeBegin + ticks;
while (Stopwatch.GetTimestamp() < timeBegin)
{ }
Ring0.ReadMsr(PERF_CTR_0, out uint lsbBegin, out uint msbBegin);
while (Stopwatch.GetTimestamp() < timeEnd)
{ }
Ring0.ReadMsr(PERF_CTR_0, out uint lsbEnd, out uint msbEnd);
Ring0.ReadMsr(COFVID_STATUS, out uint eax, out uint _);
double coreMultiplier = GetCoreMultiplier(eax);
ulong countBegin = ((ulong)msbBegin << 32) | lsbBegin;
ulong countEnd = ((ulong)msbEnd << 32) | lsbEnd;
double coreFrequency = 1e-6 * ((double)(countEnd - countBegin) * Stopwatch.Frequency) / (timeEnd - timeBegin);
double busFrequency = coreFrequency / coreMultiplier;
return 0.25 * Math.Round(4 * TimeStampCounterFrequency / busFrequency);
}
protected override uint[] GetMsrs()
{
return new[] { PERF_CTL_0, PERF_CTR_0, HWCR, P_STATE_0, COFVID_STATUS };
}
public override string GetReport()
{
StringBuilder r = new();
r.Append(base.GetReport());
r.Append("Miscellaneous Control Address: 0x");
r.AppendLine(_miscellaneousControlAddress.ToString("X", CultureInfo.InvariantCulture));
r.Append("Time Stamp Counter Multiplier: ");
r.AppendLine(_timeStampCounterMultiplier.ToString(CultureInfo.InvariantCulture));
if (_family == 0x14)
{
Ring0.ReadPciConfig(_miscellaneousControlAddress, CLOCK_POWER_TIMING_CONTROL_0_REGISTER, out uint value);
r.Append("PCI Register D18F3xD4: ");
r.AppendLine(value.ToString("X8", CultureInfo.InvariantCulture));
}
r.AppendLine();
return r.ToString();
}
private double GetCoreMultiplier(uint cofVidEax)
{
uint cpuDid;
uint cpuFid;
switch (_family)
{
case 0x10:
case 0x11:
case 0x15:
case 0x16:
// 8:6 CpuDid: current core divisor ID
// 5:0 CpuFid: current core frequency ID
cpuDid = (cofVidEax >> 6) & 7;
cpuFid = cofVidEax & 0x1F;
return 0.5 * (cpuFid + 0x10) / (1 << (int)cpuDid);
case 0x12:
// 8:4 CpuFid: current CPU core frequency ID
// 3:0 CpuDid: current CPU core divisor ID
cpuFid = (cofVidEax >> 4) & 0x1F;
cpuDid = cofVidEax & 0xF;
double divisor = cpuDid switch
{
0 => 1,
1 => 1.5,
2 => 2,
3 => 3,
4 => 4,
5 => 6,
6 => 8,
7 => 12,
8 => 16,
_ => 1
};
return (cpuFid + 0x10) / divisor;
case 0x14:
// 8:4: current CPU core divisor ID most significant digit
// 3:0: current CPU core divisor ID least significant digit
uint divisorIdMsd = (cofVidEax >> 4) & 0x1F;
uint divisorIdLsd = cofVidEax & 0xF;
Ring0.ReadPciConfig(_miscellaneousControlAddress, CLOCK_POWER_TIMING_CONTROL_0_REGISTER, out uint value);
uint frequencyId = value & 0x1F;
return (frequencyId + 0x10) / (divisorIdMsd + (divisorIdLsd * 0.25) + 1);
default:
return 1;
}
}
private static string ReadFirstLine(Stream stream)
{
StringBuilder stringBuilder = new();
try
{
stream.Seek(0, SeekOrigin.Begin);
int b = stream.ReadByte();
while (b is not -1 and not 10)
{
stringBuilder.Append((char)b);
b = stream.ReadByte();
}
}
catch
{ }
return stringBuilder.ToString();
}
public override void Update()
{
base.Update();
if (_temperatureStream == null)
{
if (_miscellaneousControlAddress != Interop.Ring0.INVALID_PCI_ADDRESS)
{
bool isValueValid = _hasSmuTemperatureRegister
? ReadSmuRegister(SMU_REPORTED_TEMP_CTRL_OFFSET, out uint value)
: Ring0.ReadPciConfig(_miscellaneousControlAddress, REPORTED_TEMPERATURE_CONTROL_REGISTER, out value);
if (isValueValid)
{
if ((_family == 0x15 || _family == 0x16) && (value & 0x30000) == 0x3000)
{
if (_family == 0x15 && (_model & 0xF0) == 0x00)
{
_coreTemperature.Value = (((value >> 21) & 0x7FC) / 8.0f) + _coreTemperature.Parameters[0].Value - 49;
}
else
{
_coreTemperature.Value = (((value >> 21) & 0x7FF) / 8.0f) + _coreTemperature.Parameters[0].Value - 49;
}
}
else
{
_coreTemperature.Value = (((value >> 21) & 0x7FF) / 8.0f) + _coreTemperature.Parameters[0].Value;
}
ActivateSensor(_coreTemperature);
}
else
{
DeactivateSensor(_coreTemperature);
}
}
else
{
DeactivateSensor(_coreTemperature);
}
}
else
{
string s = ReadFirstLine(_temperatureStream);
try
{
_coreTemperature.Value = 0.001f * long.Parse(s, CultureInfo.InvariantCulture);
ActivateSensor(_coreTemperature);
}
catch
{
DeactivateSensor(_coreTemperature);
}
}
if (HasTimeStampCounter)
{
double newBusClock = 0;
float maxCoreVoltage = 0, maxNbVoltage = 0;
for (int i = 0; i < _coreClocks.Length; i++)
{
Thread.Sleep(1);
if (Ring0.ReadMsr(COFVID_STATUS, out uint curEax, out uint _, _cpuId[i][0].Affinity))
{
double multiplier = GetCoreMultiplier(curEax);
_coreClocks[i].Value = (float)(multiplier * TimeStampCounterFrequency / _timeStampCounterMultiplier);
newBusClock = (float)(TimeStampCounterFrequency / _timeStampCounterMultiplier);
}
else
{
_coreClocks[i].Value = (float)TimeStampCounterFrequency;
}
float SVI2Volt(uint vid) => vid < 0b1111_1000 ? 1.5500f - (0.00625f * vid) : 0;
float SVI1Volt(uint vid) => vid < 0x7C ? 1.550f - (0.0125f * vid) : 0;
float newCoreVoltage, newNbVoltage;
uint coreVid60 = (curEax >> 9) & 0x7F;
if (_isSvi2)
{
newCoreVoltage = SVI2Volt((curEax >> 13 & 0x80) | coreVid60);
newNbVoltage = SVI2Volt(curEax >> 24);
}
else
{
newCoreVoltage = SVI1Volt(coreVid60);
newNbVoltage = SVI1Volt(curEax >> 25);
}
if (newCoreVoltage > maxCoreVoltage)
maxCoreVoltage = newCoreVoltage;
if (newNbVoltage > maxNbVoltage)
maxNbVoltage = newNbVoltage;
}
_coreVoltage.Value = maxCoreVoltage;
_northbridgeVoltage.Value = maxNbVoltage;
if (newBusClock > 0)
{
_busClock.Value = (float)newBusClock;
ActivateSensor(_busClock);
}
}
if (_cStatesResidency != null)
{
for (int i = 0; i < _cStatesResidency.Length; i++)
{
Ring0.WriteIoPort(CSTATES_IO_PORT, (byte)(_cStatesIoOffset + i));
_cStatesResidency[i].Value = Ring0.ReadIoPort(CSTATES_IO_PORT + 1) / 256f * 100;
}
}
}
private static bool ReadSmuRegister(uint address, out uint value)
{
if (Mutexes.WaitPciBus(10))
{
if (!Ring0.WritePciConfig(0, 0xB8, address))
{
value = 0;
Mutexes.ReleasePciBus();
return false;
}
bool result = Ring0.ReadPciConfig(0, 0xBC, out value);
Mutexes.ReleasePciBus();
return result;
}
value = 0;
return false;
}
public override void Close()
{
_temperatureStream?.Close();
base.Close();
}
// ReSharper disable InconsistentNaming
private const uint CLOCK_POWER_TIMING_CONTROL_0_REGISTER = 0xD4;
private const uint COFVID_STATUS = 0xC0010071;
private const uint CSTATES_IO_PORT = 0xCD6;
private const uint SMU_REPORTED_TEMP_CTRL_OFFSET = 0xD8200CA4;
private const uint HWCR = 0xC0010015;
private const byte MISCELLANEOUS_CONTROL_FUNCTION = 3;
private const uint P_STATE_0 = 0xC0010064;
private const uint PERF_CTL_0 = 0xC0010000;
private const uint PERF_CTR_0 = 0xC0010004;
private const uint REPORTED_TEMPERATURE_CONTROL_REGISTER = 0xA4;
private const ushort FAMILY_10H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1203;
private const ushort FAMILY_11H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1303;
private const ushort FAMILY_12H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1703;
private const ushort FAMILY_14H_MISCELLANEOUS_CONTROL_DEVICE_ID = 0x1703;
private const ushort FAMILY_15H_MODEL_00_MISC_CONTROL_DEVICE_ID = 0x1603;
private const ushort FAMILY_15H_MODEL_10_MISC_CONTROL_DEVICE_ID = 0x1403;
private const ushort FAMILY_15H_MODEL_30_MISC_CONTROL_DEVICE_ID = 0x141D;
private const ushort FAMILY_15H_MODEL_60_MISC_CONTROL_DEVICE_ID = 0x1573;
private const ushort FAMILY_15H_MODEL_70_MISC_CONTROL_DEVICE_ID = 0x15B3;
private const ushort FAMILY_16H_MODEL_00_MISC_CONTROL_DEVICE_ID = 0x1533;
private const ushort FAMILY_16H_MODEL_30_MISC_CONTROL_DEVICE_ID = 0x1583;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,604 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal sealed class Amd17Cpu : AmdCpu
{
private readonly Processor _processor;
private readonly Dictionary<SensorType, int> _sensorTypeIndex;
private readonly RyzenSMU _smu;
public Amd17Cpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
{
_sensorTypeIndex = new Dictionary<SensorType, int>();
foreach (SensorType type in Enum.GetValues(typeof(SensorType)))
{
_sensorTypeIndex.Add(type, 0);
}
_sensorTypeIndex[SensorType.Load] = _active.Count(x => x.SensorType == SensorType.Load);
_smu = new RyzenSMU(_family, _model, _packageType);
// Add all numa nodes.
// Register ..1E_2, [10:8] + 1
_processor = new Processor(this);
// Add all numa nodes.
int coreId = 0;
int lastCoreId = -1; // Invalid id.
// Ryzen 3000's skip some core ids.
// So start at 1 and count upwards when the read core changes.
foreach (CpuId[] cpu in cpuId.OrderBy(x => x[0].ExtData[0x1e, 1] & 0xFF))
{
CpuId thread = cpu[0];
// CPUID_Fn8000001E_EBX, Register ..1E_1, [7:0]
// threads per core = CPUID_Fn8000001E_EBX[15:8] + 1
// CoreId: core ID = CPUID_Fn8000001E_EBX[7:0]
int coreIdRead = (int)(thread.ExtData[0x1e, 1] & 0xff);
// CPUID_Fn8000001E_ECX, Node Identifiers, Register ..1E_2
// NodesPerProcessor = CPUID_Fn8000001E_ECX[10:8]
// nodeID = CPUID_Fn8000001E_ECX[7:0]
int nodeId = (int)(thread.ExtData[0x1e, 2] & 0xff);
if (coreIdRead != lastCoreId)
{
coreId++;
}
lastCoreId = coreIdRead;
_processor.AppendThread(thread, nodeId, coreId);
}
Update();
}
protected override uint[] GetMsrs()
{
return new[] { PERF_CTL_0, PERF_CTR_0, HWCR, MSR_PSTATE_0, COFVID_STATUS };
}
public override string GetReport()
{
StringBuilder r = new();
r.Append(base.GetReport());
r.Append(_smu.GetReport());
return r.ToString();
}
public override void Update()
{
base.Update();
_processor.UpdateSensors();
foreach (NumaNode node in _processor.Nodes)
{
NumaNode.UpdateSensors();
foreach (Core c in node.Cores)
{
c.UpdateSensors();
}
}
}
private class Processor
{
private readonly Sensor _busClock;
private readonly Sensor[] _ccdTemperatures;
private readonly Sensor _coreTemperatureTctl;
private readonly Sensor _coreTemperatureTctlTdie;
private readonly Sensor _coreTemperatureTdie;
private readonly Sensor _coreVoltage;
private readonly Amd17Cpu _cpu;
private readonly Sensor _packagePower;
private readonly Dictionary<KeyValuePair<uint, RyzenSMU.SmuSensorType>, Sensor> _smuSensors = new();
private readonly Sensor _socVoltage;
private Sensor _ccdsAverageTemperature;
private Sensor _ccdsMaxTemperature;
private DateTime _lastPwrTime = new(0);
private uint _lastPwrValue;
public Processor(Hardware hardware)
{
_cpu = (Amd17Cpu)hardware;
_packagePower = new Sensor("Package", _cpu._sensorTypeIndex[SensorType.Power]++, SensorType.Power, _cpu, _cpu._settings);
_coreTemperatureTctl = new Sensor("Core (Tctl)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings);
_coreTemperatureTdie = new Sensor("Core (Tdie)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings);
_coreTemperatureTctlTdie = new Sensor("Core (Tctl/Tdie)", _cpu._sensorTypeIndex[SensorType.Temperature]++, SensorType.Temperature, _cpu, _cpu._settings);
_ccdTemperatures = new Sensor[8]; // Hardcoded until there's a way to get max CCDs.
_coreVoltage = new Sensor("Core (SVI2 TFN)", _cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, _cpu, _cpu._settings);
_socVoltage = new Sensor("SoC (SVI2 TFN)", _cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, _cpu, _cpu._settings);
_busClock = new Sensor("Bus Speed", _cpu._sensorTypeIndex[SensorType.Clock]++, SensorType.Clock, _cpu, _cpu._settings);
_cpu.ActivateSensor(_packagePower);
foreach (KeyValuePair<uint, RyzenSMU.SmuSensorType> sensor in _cpu._smu.GetPmTableStructure())
{
_smuSensors.Add(sensor, new Sensor(sensor.Value.Name, _cpu._sensorTypeIndex[sensor.Value.Type]++, sensor.Value.Type, _cpu, _cpu._settings));
}
}
public List<NumaNode> Nodes { get; } = new();
public void UpdateSensors()
{
NumaNode node = Nodes[0];
Core core = node?.Cores[0];
CpuId cpuId = core?.Threads[0];
if (cpuId == null)
return;
GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId.Affinity);
// MSRC001_0299
// TU [19:16]
// ESU [12:8] -> Unit 15.3 micro Joule per increment
// PU [3:0]
Ring0.ReadMsr(MSR_PWR_UNIT, out uint _, out uint _);
// MSRC001_029B
// total_energy [31:0]
DateTime sampleTime = DateTime.Now;
Ring0.ReadMsr(MSR_PKG_ENERGY_STAT, out uint eax, out _);
uint totalEnergy = eax;
uint smuSvi0Tfn = 0;
uint smuSvi0TelPlane0 = 0;
uint smuSvi0TelPlane1 = 0;
if (Mutexes.WaitPciBus(10))
{
// THM_TCON_CUR_TMP
// CUR_TEMP [31:21]
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, F17H_M01H_THM_TCON_CUR_TMP);
Ring0.ReadPciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER + 4, out uint temperature);
// SVI0_TFN_PLANE0 [0]
// SVI0_TFN_PLANE1 [1]
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, F17H_M01H_SVI + 0x8);
Ring0.ReadPciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER + 4, out smuSvi0Tfn);
bool supportsPerCcdTemperatures = false;
// TODO: find a better way because these will probably keep changing in the future.
uint sviPlane0Offset;
uint sviPlane1Offset;
switch (cpuId.Model)
{
case 0x31: // Threadripper 3000.
sviPlane0Offset = F17H_M01H_SVI + 0x14;
sviPlane1Offset = F17H_M01H_SVI + 0x10;
supportsPerCcdTemperatures = true;
break;
case 0x71: // Zen 2.
case 0x21: // Zen 3.
sviPlane0Offset = F17H_M01H_SVI + 0x10;
sviPlane1Offset = F17H_M01H_SVI + 0xC;
supportsPerCcdTemperatures = true;
break;
case 0x61: //Zen 4
case 0x44: //Zen 5
sviPlane0Offset = F17H_M01H_SVI + 0x10;
sviPlane1Offset = F17H_M01H_SVI + 0xC;
supportsPerCcdTemperatures = true;
break;
default: // Zen and Zen+.
sviPlane0Offset = F17H_M01H_SVI + 0xC;
sviPlane1Offset = F17H_M01H_SVI + 0x10;
break;
}
// SVI0_PLANE0_VDDCOR [24:16]
// SVI0_PLANE0_IDDCOR [7:0]
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, sviPlane0Offset);
Ring0.ReadPciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER + 4, out smuSvi0TelPlane0);
// SVI0_PLANE1_VDDCOR [24:16]
// SVI0_PLANE1_IDDCOR [7:0]
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, sviPlane1Offset);
Ring0.ReadPciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER + 4, out smuSvi0TelPlane1);
ThreadAffinity.Set(previousAffinity);
// power consumption
// power.Value = (float) ((double)pu * 0.125);
// esu = 15.3 micro Joule per increment
if (_lastPwrTime.Ticks == 0)
{
_lastPwrTime = sampleTime;
_lastPwrValue = totalEnergy;
}
// ticks diff
TimeSpan time = sampleTime - _lastPwrTime;
long pwr;
if (_lastPwrValue <= totalEnergy)
pwr = totalEnergy - _lastPwrValue;
else
pwr = (0xffffffff - _lastPwrValue) + totalEnergy;
// update for next sample
_lastPwrTime = sampleTime;
_lastPwrValue = totalEnergy;
double energy = 15.3e-6 * pwr;
energy /= time.TotalSeconds;
if (!double.IsNaN(energy))
_packagePower.Value = (float)energy;
// current temp Bit [31:21]
// If bit 19 of the Temperature Control register is set, there is an additional offset of 49 degrees C.
bool tempOffsetFlag = (temperature & F17H_TEMP_OFFSET_FLAG) != 0;
temperature = (temperature >> 21) * 125;
float offset = 0.0f;
// Offset table: https://github.com/torvalds/linux/blob/master/drivers/hwmon/k10temp.c#L78
if (string.IsNullOrWhiteSpace(cpuId.Name))
offset = 0;
else if (cpuId.Name.Contains("1600X") || cpuId.Name.Contains("1700X") || cpuId.Name.Contains("1800X"))
offset = -20.0f;
else if (cpuId.Name.Contains("Threadripper 19") || cpuId.Name.Contains("Threadripper 29"))
offset = -27.0f;
else if (cpuId.Name.Contains("2700X"))
offset = -10.0f;
float t = temperature * 0.001f;
if (tempOffsetFlag)
t += -49.0f;
if (offset < 0)
{
_coreTemperatureTctl.Value = t;
_coreTemperatureTdie.Value = t + offset;
_cpu.ActivateSensor(_coreTemperatureTctl);
_cpu.ActivateSensor(_coreTemperatureTdie);
}
else
{
// Zen 2 doesn't have an offset so Tdie and Tctl are the same.
_coreTemperatureTctlTdie.Value = t;
_cpu.ActivateSensor(_coreTemperatureTctlTdie);
}
// Tested only on R5 3600 & Threadripper 3960X, 5900X, 7900X
if (supportsPerCcdTemperatures)
{
for (uint i = 0; i < _ccdTemperatures.Length; i++)
{
if (cpuId.Model is 0x61 or 0x44) // Raphael or GraniteRidge
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, F17H_M61H_CCD1_TEMP + (i * 0x4));
else
Ring0.WritePciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER, F17H_M70H_CCD1_TEMP + (i * 0x4));
Ring0.ReadPciConfig(0x00, FAMILY_17H_PCI_CONTROL_REGISTER + 4, out uint ccdRawTemp);
ccdRawTemp &= 0xFFF;
float ccdTemp = ((ccdRawTemp * 125) - 305000) * 0.001f;
if (ccdRawTemp > 0 && ccdTemp < 125) // Zen 2 reports 95 degrees C max, but it might exceed that.
{
if (_ccdTemperatures[i] == null)
{
_cpu.ActivateSensor(_ccdTemperatures[i] = new Sensor($"CCD{i + 1} (Tdie)",
_cpu._sensorTypeIndex[SensorType.Temperature]++,
SensorType.Temperature,
_cpu,
_cpu._settings));
}
_ccdTemperatures[i].Value = ccdTemp;
}
}
Sensor[] activeCcds = _ccdTemperatures.Where(x => x != null).ToArray();
if (activeCcds.Length > 1)
{
// No need to get the max / average ccds temp if there is only one CCD.
if (_ccdsMaxTemperature == null)
{
_cpu.ActivateSensor(_ccdsMaxTemperature = new Sensor("CCDs Max (Tdie)",
_cpu._sensorTypeIndex[SensorType.Temperature]++,
SensorType.Temperature,
_cpu,
_cpu._settings));
}
if (_ccdsAverageTemperature == null)
{
_cpu.ActivateSensor(_ccdsAverageTemperature = new Sensor("CCDs Average (Tdie)",
_cpu._sensorTypeIndex[SensorType.Temperature]++,
SensorType.Temperature,
_cpu,
_cpu._settings));
}
_ccdsMaxTemperature.Value = activeCcds.Max(x => x.Value);
_ccdsAverageTemperature.Value = activeCcds.Average(x => x.Value);
}
}
Mutexes.ReleasePciBus();
}
// voltage
const double vidStep = 0.00625;
double vcc;
uint svi0PlaneXVddCor;
if (cpuId.Model is 0x61 or 0x44) // Readout not working for Ryzen 7000/9000.
smuSvi0Tfn |= 0x01 | 0x02;
// Core (0x01).
if ((smuSvi0Tfn & 0x01) == 0)
{
svi0PlaneXVddCor = (smuSvi0TelPlane0 >> 16) & 0xff;
vcc = 1.550 - (vidStep * svi0PlaneXVddCor);
_coreVoltage.Value = (float)vcc;
_cpu.ActivateSensor(_coreVoltage);
}
// SoC (0x02), not every Zen cpu has this voltage.
if (cpuId.Model is 0x11 or 0x21 or 0x71 or 0x31 || (smuSvi0Tfn & 0x02) == 0)
{
svi0PlaneXVddCor = (smuSvi0TelPlane1 >> 16) & 0xff;
vcc = 1.550 - (vidStep * svi0PlaneXVddCor);
_socVoltage.Value = (float)vcc;
_cpu.ActivateSensor(_socVoltage);
}
double timeStampCounterMultiplier = GetTimeStampCounterMultiplier();
if (timeStampCounterMultiplier > 0)
{
_busClock.Value = (float)(_cpu.TimeStampCounterFrequency / timeStampCounterMultiplier);
_cpu.ActivateSensor(_busClock);
}
if (_cpu._smu.IsPmTableLayoutDefined())
{
float[] smuData = _cpu._smu.GetPmTable();
foreach (KeyValuePair<KeyValuePair<uint, RyzenSMU.SmuSensorType>, Sensor> sensor in _smuSensors)
{
if (smuData.Length > sensor.Key.Key)
{
sensor.Value.Value = smuData[sensor.Key.Key] * sensor.Key.Value.Scale;
if (sensor.Value.Value != 0)
_cpu.ActivateSensor(sensor.Value);
}
}
}
}
private double GetTimeStampCounterMultiplier()
{
Ring0.ReadMsr(MSR_PSTATE_0, out uint eax, out _);
uint cpuDfsId = (eax >> 8) & 0x3f;
uint cpuFid = eax & 0xff;
return 2.0 * cpuFid / cpuDfsId;
}
public void AppendThread(CpuId thread, int numaId, int coreId)
{
NumaNode node = null;
foreach (NumaNode n in Nodes)
{
if (n.NodeId == numaId)
{
node = n;
break;
}
}
if (node == null)
{
node = new NumaNode(_cpu, numaId);
Nodes.Add(node);
}
if (thread != null)
node.AppendThread(thread, coreId);
}
}
private class NumaNode
{
private readonly Amd17Cpu _cpu;
public NumaNode(Amd17Cpu cpu, int id)
{
Cores = new List<Core>();
NodeId = id;
_cpu = cpu;
}
public List<Core> Cores { get; }
public int NodeId { get; }
public void AppendThread(CpuId thread, int coreId)
{
Core core = null;
foreach (Core c in Cores)
{
if (c.CoreId == coreId)
core = c;
}
if (core == null)
{
core = new Core(_cpu, coreId);
Cores.Add(core);
}
if (thread != null)
core.Threads.Add(thread);
}
public static void UpdateSensors()
{ }
}
private class Core
{
private readonly Sensor _clock;
private readonly Amd17Cpu _cpu;
private readonly Sensor _multiplier;
private readonly Sensor _power;
private readonly Sensor _vcore;
private ISensor _busSpeed;
private DateTime _lastPwrTime = new(0);
private uint _lastPwrValue;
public Core(Amd17Cpu cpu, int id)
{
_cpu = cpu;
Threads = new List<CpuId>();
CoreId = id;
_clock = new Sensor("Core #" + CoreId, _cpu._sensorTypeIndex[SensorType.Clock]++, SensorType.Clock, cpu, cpu._settings);
_multiplier = new Sensor("Core #" + CoreId, cpu._sensorTypeIndex[SensorType.Factor]++, SensorType.Factor, cpu, cpu._settings);
_power = new Sensor("Core #" + CoreId + " (SMU)", cpu._sensorTypeIndex[SensorType.Power]++, SensorType.Power, cpu, cpu._settings);
_vcore = new Sensor("Core #" + CoreId + " VID", cpu._sensorTypeIndex[SensorType.Voltage]++, SensorType.Voltage, cpu, cpu._settings);
cpu.ActivateSensor(_clock);
cpu.ActivateSensor(_multiplier);
cpu.ActivateSensor(_power);
cpu.ActivateSensor(_vcore);
}
public int CoreId { get; }
public List<CpuId> Threads { get; }
public void UpdateSensors()
{
// CPUID cpu = threads.FirstOrDefault();
CpuId cpu = Threads[0];
if (cpu == null)
return;
GroupAffinity previousAffinity = ThreadAffinity.Set(cpu.Affinity);
// MSRC001_0299
// TU [19:16]
// ESU [12:8] -> Unit 15.3 micro Joule per increment
// PU [3:0]
Ring0.ReadMsr(MSR_PWR_UNIT, out _, out _);
// MSRC001_029A
// total_energy [31:0]
DateTime sampleTime = DateTime.Now;
Ring0.ReadMsr(MSR_CORE_ENERGY_STAT, out uint eax, out _);
uint totalEnergy = eax;
// MSRC001_0293
// CurHwPstate [24:22]
// CurCpuVid [21:14]
// CurCpuDfsId [13:8]
// CurCpuFid [7:0]
Ring0.ReadMsr(MSR_HARDWARE_PSTATE_STATUS, out eax, out _);
int curCpuVid = (int)((eax >> 14) & 0xff);
int curCpuDfsId = (int)((eax >> 8) & 0x3f);
int curCpuFid = (int)(eax & 0xff);
// MSRC001_0064 + x
// IddDiv [31:30]
// IddValue [29:22]
// CpuVid [21:14]
// CpuDfsId [13:8]
// CpuFid [7:0]
// Ring0.ReadMsr(MSR_PSTATE_0 + (uint)CurHwPstate, out eax, out edx);
// int IddDiv = (int)((eax >> 30) & 0x03);
// int IddValue = (int)((eax >> 22) & 0xff);
// int CpuVid = (int)((eax >> 14) & 0xff);
ThreadAffinity.Set(previousAffinity);
// clock
// CoreCOF is (Core::X86::Msr::PStateDef[CpuFid[7:0]] / Core::X86::Msr::PStateDef[CpuDfsId]) * 200
double clock = 200.0;
_busSpeed ??= _cpu.Sensors.FirstOrDefault(x => x.Name == "Bus Speed");
if (_busSpeed?.Value.HasValue == true && _busSpeed.Value > 0)
clock = (double)(_busSpeed.Value * 2);
_clock.Value = (float)(curCpuFid / (double)curCpuDfsId * clock);
// multiplier
_multiplier.Value = (float)(curCpuFid / (double)curCpuDfsId * 2.0);
// Voltage
const double vidStep = 0.00625;
double vcc = 1.550 - (vidStep * curCpuVid);
_vcore.Value = (float)vcc;
// power consumption
// power.Value = (float) ((double)pu * 0.125);
// esu = 15.3 micro Joule per increment
if (_lastPwrTime.Ticks == 0)
{
_lastPwrTime = sampleTime;
_lastPwrValue = totalEnergy;
}
// ticks diff
TimeSpan time = sampleTime - _lastPwrTime;
long pwr;
if (_lastPwrValue <= totalEnergy)
pwr = totalEnergy - _lastPwrValue;
else
pwr = (0xffffffff - _lastPwrValue) + totalEnergy;
// update for next sample
_lastPwrTime = sampleTime;
_lastPwrValue = totalEnergy;
double energy = 15.3e-6 * pwr;
energy /= time.TotalSeconds;
if (!double.IsNaN(energy))
_power.Value = (float)energy;
}
}
// ReSharper disable InconsistentNaming
private const uint COFVID_STATUS = 0xC0010071;
private const uint F17H_M01H_SVI = 0x0005A000;
private const uint F17H_M01H_THM_TCON_CUR_TMP = 0x00059800;
private const uint F17H_M70H_CCD1_TEMP = 0x00059954;
private const uint F17H_M61H_CCD1_TEMP = 0x00059b08;
private const uint F17H_TEMP_OFFSET_FLAG = 0x80000;
private const uint FAMILY_17H_PCI_CONTROL_REGISTER = 0x60;
private const uint HWCR = 0xC0010015;
private const uint MSR_CORE_ENERGY_STAT = 0xC001029A;
private const uint MSR_HARDWARE_PSTATE_STATUS = 0xC0010293;
private const uint MSR_PKG_ENERGY_STAT = 0xC001029B;
private const uint MSR_PSTATE_0 = 0xC0010064;
private const uint MSR_PWR_UNIT = 0xC0010299;
private const uint PERF_CTL_0 = 0xC0010000;
private const uint PERF_CTR_0 = 0xC0010004;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,36 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Cpu;
internal abstract class AmdCpu : GenericCpu
{
protected AmdCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
{ }
protected uint GetPciAddress(byte function, ushort deviceId)
{
// assemble the pci address
uint address = Ring0.GetPciAddress(PCI_BUS, (byte)(PCI_BASE_DEVICE + Index), function);
// verify that we have the correct bus, device and function
if (!Ring0.ReadPciConfig(address, DEVICE_VENDOR_ID_REGISTER, out uint deviceVendor))
return Interop.Ring0.INVALID_PCI_ADDRESS;
if (deviceVendor != (deviceId << 16 | AMD_VENDOR_ID))
return Interop.Ring0.INVALID_PCI_ADDRESS;
return address;
}
// ReSharper disable InconsistentNaming
private const ushort AMD_VENDOR_ID = 0x1022;
private const byte DEVICE_VENDOR_ID_REGISTER = 0;
private const byte PCI_BASE_DEVICE = 0x18;
private const byte PCI_BUS = 0;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,211 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal class CpuGroup : IGroup
{
private readonly List<GenericCpu> _hardware = new();
private readonly CpuId[][][] _threads;
public CpuGroup(ISettings settings)
{
CpuId[][] processorThreads = GetProcessorThreads();
_threads = new CpuId[processorThreads.Length][][];
int index = 0;
foreach (CpuId[] threads in processorThreads)
{
if (threads.Length == 0)
continue;
CpuId[][] coreThreads = GroupThreadsByCore(threads);
_threads[index] = coreThreads;
switch (threads[0].Vendor)
{
case Vendor.Intel:
_hardware.Add(new IntelCpu(index, coreThreads, settings));
break;
case Vendor.AMD:
switch (threads[0].Family)
{
case 0x0F:
_hardware.Add(new Amd0FCpu(index, coreThreads, settings));
break;
case 0x10:
case 0x11:
case 0x12:
case 0x14:
case 0x15:
case 0x16:
_hardware.Add(new Amd10Cpu(index, coreThreads, settings));
break;
case 0x17:
case 0x19:
case 0x1A:
_hardware.Add(new Amd17Cpu(index, coreThreads, settings));
break;
default:
_hardware.Add(new GenericCpu(index, coreThreads, settings));
break;
}
break;
default:
_hardware.Add(new GenericCpu(index, coreThreads, settings));
break;
}
index++;
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
if (_threads == null)
return null;
StringBuilder r = new();
r.AppendLine("CPUID");
r.AppendLine();
for (int i = 0; i < _threads.Length; i++)
{
r.AppendLine("Processor " + i);
r.AppendLine();
r.AppendFormat("Processor Vendor: {0}{1}", _threads[i][0][0].Vendor, Environment.NewLine);
r.AppendFormat("Processor Brand: {0}{1}", _threads[i][0][0].BrandString, Environment.NewLine);
r.AppendFormat("Family: 0x{0}{1}", _threads[i][0][0].Family.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
r.AppendFormat("Model: 0x{0}{1}", _threads[i][0][0].Model.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
r.AppendFormat("Stepping: 0x{0}{1}", _threads[i][0][0].Stepping.ToString("X", CultureInfo.InvariantCulture), Environment.NewLine);
r.AppendLine();
r.AppendLine("CPUID Return Values");
r.AppendLine();
for (int j = 0; j < _threads[i].Length; j++)
{
for (int k = 0; k < _threads[i][j].Length; k++)
{
r.AppendLine(" CPU Group: " + _threads[i][j][k].Group);
r.AppendLine(" CPU Thread: " + _threads[i][j][k].Thread);
r.AppendLine(" APIC ID: " + _threads[i][j][k].ApicId);
r.AppendLine(" Processor ID: " + _threads[i][j][k].ProcessorId);
r.AppendLine(" Core ID: " + _threads[i][j][k].CoreId);
r.AppendLine(" Thread ID: " + _threads[i][j][k].ThreadId);
r.AppendLine();
r.AppendLine(" Function EAX EBX ECX EDX");
AppendCpuidData(r, _threads[i][j][k].Data, CpuId.CPUID_0);
AppendCpuidData(r, _threads[i][j][k].ExtData, CpuId.CPUID_EXT);
r.AppendLine();
}
}
}
return r.ToString();
}
public void Close()
{
foreach (GenericCpu cpu in _hardware)
{
cpu.Close();
}
}
private static CpuId[][] GetProcessorThreads()
{
List<CpuId> threads = new();
for (int i = 0; i < ThreadAffinity.ProcessorGroupCount; i++)
{
for (int j = 0; j < 192; j++)
{
try
{
var cpuid = CpuId.Get(i, j);
if (cpuid != null)
threads.Add(cpuid);
}
catch (ArgumentOutOfRangeException)
{
// Continue...
}
}
}
SortedDictionary<uint, List<CpuId>> processors = new();
foreach (CpuId thread in threads)
{
processors.TryGetValue(thread.ProcessorId, out List<CpuId> list);
if (list == null)
{
list = new List<CpuId>();
processors.Add(thread.ProcessorId, list);
}
list.Add(thread);
}
CpuId[][] processorThreads = new CpuId[processors.Count][];
int index = 0;
foreach (List<CpuId> list in processors.Values)
{
processorThreads[index] = list.ToArray();
index++;
}
return processorThreads;
}
private static CpuId[][] GroupThreadsByCore(IEnumerable<CpuId> threads)
{
SortedDictionary<uint, List<CpuId>> cores = new();
foreach (CpuId thread in threads)
{
cores.TryGetValue(thread.CoreId, out List<CpuId> coreList);
if (coreList == null)
{
coreList = new List<CpuId>();
cores.Add(thread.CoreId, coreList);
}
coreList.Add(thread);
}
CpuId[][] coreThreads = new CpuId[cores.Count][];
int index = 0;
foreach (List<CpuId> list in cores.Values)
{
coreThreads[index] = list.ToArray();
index++;
}
return coreThreads;
}
private static void AppendCpuidData(StringBuilder r, uint[,] data, uint offset)
{
for (int i = 0; i < data.GetLength(0); i++)
{
r.Append(" ");
r.Append((i + offset).ToString("X8", CultureInfo.InvariantCulture));
for (int j = 0; j < 4; j++)
{
r.Append(" ");
r.Append(data[i, j].ToString("X8", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
}
}

View File

@@ -0,0 +1,274 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Cpu;
public enum Vendor
{
Unknown,
Intel,
AMD
}
public class CpuId
{
/// <summary>
/// Initializes a new instance of the <see cref="CpuId" /> class.
/// </summary>
/// <param name="group">The group.</param>
/// <param name="thread">The thread.</param>
/// <param name="affinity">The affinity.</param>
private CpuId(int group, int thread, GroupAffinity affinity)
{
Thread = thread;
Group = group;
Affinity = affinity;
uint threadMaskWith;
uint coreMaskWith;
uint maxCpuidExt;
if (thread >= 64)
throw new ArgumentOutOfRangeException(nameof(thread));
uint maxCpuid;
if (OpCode.CpuId(CPUID_0, 0, out uint eax, out uint ebx, out uint ecx, out uint edx))
{
if (eax > 0)
maxCpuid = eax;
else
return;
StringBuilder vendorBuilder = new();
AppendRegister(vendorBuilder, ebx);
AppendRegister(vendorBuilder, edx);
AppendRegister(vendorBuilder, ecx);
Vendor = vendorBuilder.ToString() switch
{
"GenuineIntel" => Vendor.Intel,
"AuthenticAMD" => Vendor.AMD,
_ => Vendor.Unknown
};
if (OpCode.CpuId(CPUID_EXT, 0, out eax, out _, out _, out _))
{
if (eax > CPUID_EXT)
maxCpuidExt = eax - CPUID_EXT;
else
return;
}
else
{
throw new ArgumentOutOfRangeException(nameof(thread));
}
}
else
{
throw new ArgumentOutOfRangeException(nameof(thread));
}
maxCpuid = Math.Min(maxCpuid, 1024);
maxCpuidExt = Math.Min(maxCpuidExt, 1024);
Data = new uint[maxCpuid + 1, 4];
for (uint i = 0; i < maxCpuid + 1; i++)
{
OpCode.CpuId(CPUID_0 + i, 0, out Data[i, 0], out Data[i, 1], out Data[i, 2], out Data[i, 3]);
}
ExtData = new uint[maxCpuidExt + 1, 4];
for (uint i = 0; i < maxCpuidExt + 1; i++)
{
OpCode.CpuId(CPUID_EXT + i, 0, out ExtData[i, 0], out ExtData[i, 1], out ExtData[i, 2], out ExtData[i, 3]);
}
StringBuilder nameBuilder = new();
for (uint i = 2; i <= 4; i++)
{
if (OpCode.CpuId(CPUID_EXT + i, 0, out eax, out ebx, out ecx, out edx))
{
AppendRegister(nameBuilder, eax);
AppendRegister(nameBuilder, ebx);
AppendRegister(nameBuilder, ecx);
AppendRegister(nameBuilder, edx);
}
}
nameBuilder.Replace('\0', ' ');
BrandString = nameBuilder.ToString().Trim();
nameBuilder.Replace("(R)", string.Empty);
nameBuilder.Replace("(TM)", string.Empty);
nameBuilder.Replace("(tm)", string.Empty);
nameBuilder.Replace("CPU", string.Empty);
nameBuilder.Replace("Dual-Core Processor", string.Empty);
nameBuilder.Replace("Triple-Core Processor", string.Empty);
nameBuilder.Replace("Quad-Core Processor", string.Empty);
nameBuilder.Replace("Six-Core Processor", string.Empty);
nameBuilder.Replace("Eight-Core Processor", string.Empty);
nameBuilder.Replace("64-Core Processor", string.Empty);
nameBuilder.Replace("32-Core Processor", string.Empty);
nameBuilder.Replace("24-Core Processor", string.Empty);
nameBuilder.Replace("16-Core Processor", string.Empty);
nameBuilder.Replace("12-Core Processor", string.Empty);
nameBuilder.Replace("8-Core Processor", string.Empty);
nameBuilder.Replace("6-Core Processor", string.Empty);
for (int i = 0; i < 10; i++)
nameBuilder.Replace(" ", " ");
Name = nameBuilder.ToString();
if (Name.Contains("@"))
Name = Name.Remove(Name.LastIndexOf('@'));
Name = Name.Trim();
Family = ((Data[1, 0] & 0x0FF00000) >> 20) + ((Data[1, 0] & 0x0F00) >> 8);
Model = ((Data[1, 0] & 0x0F0000) >> 12) + ((Data[1, 0] & 0xF0) >> 4);
Stepping = Data[1, 0] & 0x0F;
ApicId = (Data[1, 1] >> 24) & 0xFF;
PkgType = (ExtData[1, 1] >> 28) & 0xFF;
switch (Vendor)
{
case Vendor.Intel:
uint maxCoreAndThreadIdPerPackage = (Data[1, 1] >> 16) & 0xFF;
uint maxCoreIdPerPackage;
if (maxCpuid >= 4)
maxCoreIdPerPackage = ((Data[4, 0] >> 26) & 0x3F) + 1;
else
maxCoreIdPerPackage = 1;
threadMaskWith = NextLog2(maxCoreAndThreadIdPerPackage / maxCoreIdPerPackage);
coreMaskWith = NextLog2(maxCoreIdPerPackage);
break;
case Vendor.AMD:
uint corePerPackage;
if (maxCpuidExt >= 8)
corePerPackage = (ExtData[8, 2] & 0xFF) + 1;
else
corePerPackage = 1;
threadMaskWith = 0;
coreMaskWith = NextLog2(corePerPackage);
if (Family is 0x17 or 0x19)
{
// ApicIdCoreIdSize: APIC ID size.
// cores per DIE
// we need this for Ryzen 5 (4 cores, 8 threads) ans Ryzen 6 (6 cores, 12 threads)
// Ryzen 5: [core0][core1][dummy][dummy][core2][core3] (Core0 EBX = 00080800, Core2 EBX = 08080800)
coreMaskWith = ((ExtData[8, 2] >> 12) & 0xF) switch
{
0x04 => NextLog2(16), // Ryzen
0x05 => NextLog2(32), // Threadripper
0x06 => NextLog2(64), // Epic
_ => coreMaskWith
};
}
break;
default:
threadMaskWith = 0;
coreMaskWith = 0;
break;
}
ProcessorId = ApicId >> (int)(coreMaskWith + threadMaskWith);
CoreId = (ApicId >> (int)threadMaskWith) - (ProcessorId << (int)coreMaskWith);
ThreadId = ApicId - (ProcessorId << (int)(coreMaskWith + threadMaskWith)) - (CoreId << (int)threadMaskWith);
}
public GroupAffinity Affinity { get; }
public uint ApicId { get; }
public string BrandString { get; } = string.Empty;
public uint CoreId { get; }
public uint[,] Data { get; } = new uint[0, 0];
public uint[,] ExtData { get; } = new uint[0, 0];
public uint Family { get; }
public int Group { get; }
public uint Model { get; }
public string Name { get; } = string.Empty;
public uint PkgType { get; }
public uint ProcessorId { get; }
public uint Stepping { get; }
public int Thread { get; }
public uint ThreadId { get; }
public Vendor Vendor { get; } = Vendor.Unknown;
/// <summary>
/// Gets the specified <see cref="CpuId" />.
/// </summary>
/// <param name="group">The group.</param>
/// <param name="thread">The thread.</param>
/// <returns><see cref="CpuId" />.</returns>
public static CpuId Get(int group, int thread)
{
if (thread >= 64)
return null;
var affinity = GroupAffinity.Single((ushort)group, thread);
GroupAffinity previousAffinity = ThreadAffinity.Set(affinity);
if (previousAffinity == GroupAffinity.Undefined)
return null;
try
{
return new CpuId(group, thread, affinity);
}
finally
{
ThreadAffinity.Set(previousAffinity);
}
}
private static void AppendRegister(StringBuilder b, uint value)
{
b.Append((char)(value & 0xff));
b.Append((char)((value >> 8) & 0xff));
b.Append((char)((value >> 16) & 0xff));
b.Append((char)((value >> 24) & 0xff));
}
private static uint NextLog2(long x)
{
if (x <= 0)
return 0;
x--;
uint count = 0;
while (x > 0)
{
x >>= 1;
count++;
}
return count;
}
// ReSharper disable InconsistentNaming
public const uint CPUID_0 = 0;
public const uint CPUID_EXT = 0x80000000;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,184 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal class CpuLoad
{
private readonly float[] _threadLoads;
private long[] _idleTimes;
private float _totalLoad;
private long[] _totalTimes;
public CpuLoad(CpuId[][] cpuid)
{
_threadLoads = new float[cpuid.Sum(x => x.Length)];
_totalLoad = 0;
try
{
GetTimes(out _idleTimes, out _totalTimes);
}
catch (Exception)
{
_idleTimes = null;
_totalTimes = null;
}
if (_idleTimes != null)
IsAvailable = true;
}
public bool IsAvailable { get; }
private static bool GetTimes(out long[] idle, out long[] total)
{
return !Software.OperatingSystem.IsUnix ? GetWindowsTimes(out idle, out total) : GetUnixTimes(out idle, out total);
}
private static bool GetWindowsTimes(out long[] idle, out long[] total)
{
idle = null;
total = null;
//Query processor idle information
Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[] idleInformation = new Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[64];
int idleSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION));
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorIdleInformation, idleInformation, idleInformation.Length * idleSize, out int idleReturn) != 0)
return false;
//Query processor performance information
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation,
perfInformation,
perfInformation.Length * perfSize,
out int perfReturn) != 0)
{
return false;
}
idle = new long[idleReturn / idleSize];
for (int i = 0; i < idle.Length; i++)
idle[i] = idleInformation[i].IdleTime;
total = new long[perfReturn / perfSize];
for (int i = 0; i < total.Length; i++)
total[i] = perfInformation[i].KernelTime + perfInformation[i].UserTime;
return true;
}
private static bool GetUnixTimes(out long[] idle, out long[] total)
{
idle = null;
total = null;
List<long> idleList = new();
List<long> totalList = new();
if (!File.Exists("/proc/stat"))
return false;
string[] cpuInfos = File.ReadAllLines("/proc/stat");
// currently parse the OverAll CPU info
// cpu 1583083 737 452845 36226266 723316 63685 31896 0 0 0
// cpu0 397468 189 109728 9040007 191429 16939 14954 0 0 0
// 0=cpu 1=user 2=nice 3=system 4=idle 5=iowait 6=irq 7=softirq 8=steal 9=guest 10=guest_nice
foreach (string cpuInfo in cpuInfos.Where(s => s.StartsWith("cpu") && s.Length > 3 && s[3] != ' '))
{
string[] overall = cpuInfo.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
try
{
// Parse idle information.
idleList.Add(long.Parse(overall[4]));
}
catch
{
// Ignored.
}
// Parse total information = user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice.
long currentTotal = 0;
foreach (string item in overall.Skip(1))
{
try
{
currentTotal += long.Parse(item);
}
catch
{
// Ignored.
}
}
totalList.Add(currentTotal);
}
idle = idleList.ToArray();
total = totalList.ToArray();
return true;
}
public float GetTotalLoad()
{
return _totalLoad;
}
public float GetThreadLoad(int thread)
{
return _threadLoads[thread];
}
public void Update()
{
if (_idleTimes == null || !GetTimes(out long[] newIdleTimes, out long[] newTotalTimes))
return;
int minDiff = Software.OperatingSystem.IsUnix ? 100 : 100000;
for (int i = 0; i < Math.Min(newTotalTimes.Length, _totalTimes.Length); i++)
{
if (newTotalTimes[i] - _totalTimes[i] < minDiff)
return;
}
if (newIdleTimes == null)
return;
float total = 0;
int count = 0;
for (int i = 0; i < _threadLoads.Length && i < _idleTimes.Length && i < newIdleTimes.Length; i++)
{
float idle = (newIdleTimes[i] - _idleTimes[i]) / (float)(newTotalTimes[i] - _totalTimes[i]);
_threadLoads[i] = 100f * (1.0f - Math.Min(idle, 1.0f));
total += idle;
count++;
}
if (count > 0)
{
total = 1.0f - (total / count);
total = total < 0 ? 0 : total;
}
else
{
total = 0;
}
_totalLoad = total * 100;
_totalTimes = newTotalTimes;
_idleTimes = newIdleTimes;
}
}

View File

@@ -0,0 +1,321 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Cpu;
public class GenericCpu : Hardware
{
protected readonly int _coreCount;
protected readonly CpuId[][] _cpuId;
protected readonly uint _family;
protected readonly uint _model;
protected readonly uint _packageType;
protected readonly uint _stepping;
protected readonly int _threadCount;
private readonly CpuLoad _cpuLoad;
private readonly double _estimatedTimeStampCounterFrequency;
private readonly double _estimatedTimeStampCounterFrequencyError;
private readonly bool _isInvariantTimeStampCounter;
private readonly Sensor[] _threadLoads;
private readonly Sensor _totalLoad;
private readonly Sensor _maxLoad;
private readonly Vendor _vendor;
private long _lastTime;
private ulong _lastTimeStampCount;
public GenericCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(cpuId[0][0].Name, CreateIdentifier(cpuId[0][0].Vendor, processorIndex), settings)
{
_cpuId = cpuId;
_vendor = cpuId[0][0].Vendor;
_family = cpuId[0][0].Family;
_model = cpuId[0][0].Model;
_stepping = cpuId[0][0].Stepping;
_packageType = cpuId[0][0].PkgType;
Index = processorIndex;
_coreCount = cpuId.Length;
_threadCount = cpuId.Sum(x => x.Length);
// Check if processor has MSRs.
HasModelSpecificRegisters = cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x20) != 0;
// Check if processor has a TSC.
HasTimeStampCounter = cpuId[0][0].Data.GetLength(0) > 1 && (cpuId[0][0].Data[1, 3] & 0x10) != 0;
// Check if processor supports an invariant TSC.
_isInvariantTimeStampCounter = cpuId[0][0].ExtData.GetLength(0) > 7 && (cpuId[0][0].ExtData[7, 3] & 0x100) != 0;
_totalLoad = _coreCount > 1 ? new Sensor("CPU Total", 0, SensorType.Load, this, settings) : null;
_maxLoad = _coreCount > 1 ? new Sensor("CPU Core Max", 1, SensorType.Load, this, settings) : null;
_cpuLoad = new CpuLoad(cpuId);
if (_cpuLoad.IsAvailable)
{
_threadLoads = new Sensor[_threadCount];
for (int coreIdx = 0; coreIdx < cpuId.Length; coreIdx++)
{
for (int threadIdx = 0; threadIdx < cpuId[coreIdx].Length; threadIdx++)
{
int thread = cpuId[coreIdx][threadIdx].Thread;
if (thread < _threadLoads.Length)
{
// Some cores may have 2 threads while others have only one (e.g. P-cores vs E-cores on Intel 12th gen).
string sensorName = CoreString(coreIdx) + (cpuId[coreIdx].Length > 1 ? $" Thread #{threadIdx + 1}" : string.Empty);
_threadLoads[thread] = new Sensor(sensorName, thread + 2, SensorType.Load, this, settings);
ActivateSensor(_threadLoads[thread]);
}
}
}
if (_totalLoad != null)
{
ActivateSensor(_totalLoad);
}
if (_maxLoad != null)
{
ActivateSensor(_maxLoad);
}
}
if (HasTimeStampCounter)
{
GroupAffinity previousAffinity = ThreadAffinity.Set(cpuId[0][0].Affinity);
EstimateTimeStampCounterFrequency(out _estimatedTimeStampCounterFrequency, out _estimatedTimeStampCounterFrequencyError);
ThreadAffinity.Set(previousAffinity);
}
else
{
_estimatedTimeStampCounterFrequency = 0;
}
TimeStampCounterFrequency = _estimatedTimeStampCounterFrequency;
}
/// <summary>
/// Gets the CPUID.
/// </summary>
public CpuId[][] CpuId => _cpuId;
public override HardwareType HardwareType => HardwareType.Cpu;
public bool HasModelSpecificRegisters { get; }
public bool HasTimeStampCounter { get; }
/// <summary>
/// Gets the CPU index.
/// </summary>
public int Index { get; }
public double TimeStampCounterFrequency { get; private set; }
protected string CoreString(int i)
{
if (_coreCount == 1)
return "CPU Core";
return "CPU Core #" + (i + 1);
}
private static Identifier CreateIdentifier(Vendor vendor, int processorIndex)
{
string s = vendor switch
{
Vendor.AMD => "amdcpu",
Vendor.Intel => "intelcpu",
_ => "genericcpu"
};
return new Identifier(s, processorIndex.ToString(CultureInfo.InvariantCulture));
}
private static void EstimateTimeStampCounterFrequency(out double frequency, out double error)
{
// preload the function
EstimateTimeStampCounterFrequency(0, out double f, out double e);
EstimateTimeStampCounterFrequency(0, out f, out e);
// estimate the frequency
error = double.MaxValue;
frequency = 0;
for (int i = 0; i < 5; i++)
{
EstimateTimeStampCounterFrequency(0.025, out f, out e);
if (e < error)
{
error = e;
frequency = f;
}
if (error < 1e-4)
break;
}
}
private static void EstimateTimeStampCounterFrequency(double timeWindow, out double frequency, out double error)
{
long ticks = (long)(timeWindow * Stopwatch.Frequency);
long timeBegin = Stopwatch.GetTimestamp() + (long)Math.Ceiling(0.001 * ticks);
long timeEnd = timeBegin + ticks;
while (Stopwatch.GetTimestamp() < timeBegin)
{ }
ulong countBegin = OpCode.Rdtsc();
long afterBegin = Stopwatch.GetTimestamp();
while (Stopwatch.GetTimestamp() < timeEnd)
{ }
ulong countEnd = OpCode.Rdtsc();
long afterEnd = Stopwatch.GetTimestamp();
double delta = timeEnd - timeBegin;
frequency = 1e-6 * ((double)(countEnd - countBegin) * Stopwatch.Frequency) / delta;
double beginError = (afterBegin - timeBegin) / delta;
double endError = (afterEnd - timeEnd) / delta;
error = beginError + endError;
}
private static void AppendMsrData(StringBuilder r, uint msr, GroupAffinity affinity)
{
if (Ring0.ReadMsr(msr, out uint eax, out uint edx, affinity))
{
r.Append(" ");
r.Append(msr.ToString("X8", CultureInfo.InvariantCulture));
r.Append(" ");
r.Append(edx.ToString("X8", CultureInfo.InvariantCulture));
r.Append(" ");
r.Append(eax.ToString("X8", CultureInfo.InvariantCulture));
r.AppendLine();
}
}
protected virtual uint[] GetMsrs()
{
return null;
}
public override string GetReport()
{
StringBuilder r = new();
switch (_vendor)
{
case Vendor.AMD:
r.AppendLine("AMD CPU");
break;
case Vendor.Intel:
r.AppendLine("Intel CPU");
break;
default:
r.AppendLine("Generic CPU");
break;
}
r.AppendLine();
r.AppendFormat("Name: {0}{1}", _name, Environment.NewLine);
r.AppendFormat("Number of Cores: {0}{1}", _coreCount, Environment.NewLine);
r.AppendFormat("Threads per Core: {0}{1}", _cpuId[0].Length, Environment.NewLine);
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Timer Frequency: {0} MHz", Stopwatch.Frequency * 1e-6));
r.AppendLine("Time Stamp Counter: " + (HasTimeStampCounter ? _isInvariantTimeStampCounter ? "Invariant" : "Not Invariant" : "None"));
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Estimated Time Stamp Counter Frequency: {0} MHz", Math.Round(_estimatedTimeStampCounterFrequency * 100) * 0.01));
r.AppendLine(string.Format(CultureInfo.InvariantCulture,
"Estimated Time Stamp Counter Frequency Error: {0} Mhz",
Math.Round(_estimatedTimeStampCounterFrequency * _estimatedTimeStampCounterFrequencyError * 1e5) * 1e-5));
r.AppendLine(string.Format(CultureInfo.InvariantCulture, "Time Stamp Counter Frequency: {0} MHz", Math.Round(TimeStampCounterFrequency * 100) * 0.01));
r.AppendLine();
uint[] msrArray = GetMsrs();
if (msrArray is { Length: > 0 })
{
for (int i = 0; i < _cpuId.Length; i++)
{
r.AppendLine("MSR Core #" + (i + 1));
r.AppendLine();
r.AppendLine(" MSR EDX EAX");
foreach (uint msr in msrArray)
AppendMsrData(r, msr, _cpuId[i][0].Affinity);
r.AppendLine();
}
}
return r.ToString();
}
public override void Update()
{
if (HasTimeStampCounter && _isInvariantTimeStampCounter)
{
// make sure always the same thread is used
GroupAffinity previousAffinity = ThreadAffinity.Set(_cpuId[0][0].Affinity);
// read time before and after getting the TSC to estimate the error
long firstTime = Stopwatch.GetTimestamp();
ulong timeStampCount = OpCode.Rdtsc();
long time = Stopwatch.GetTimestamp();
// restore the thread affinity mask
ThreadAffinity.Set(previousAffinity);
double delta = (double)(time - _lastTime) / Stopwatch.Frequency;
double error = (double)(time - firstTime) / Stopwatch.Frequency;
// only use data if they are measured accurate enough (max 0.1ms delay)
if (error < 0.0001)
{
// ignore the first reading because there are no initial values
// ignore readings with too large or too small time window
if (_lastTime != 0 && delta is > 0.5 and < 2)
{
// update the TSC frequency with the new value
TimeStampCounterFrequency = (timeStampCount - _lastTimeStampCount) / (1e6 * delta);
}
_lastTimeStampCount = timeStampCount;
_lastTime = time;
}
}
if (_cpuLoad.IsAvailable)
{
_cpuLoad.Update();
float maxLoad = 0;
if (_threadLoads != null)
{
for (int i = 0; i < _threadLoads.Length; i++)
{
if (_threadLoads[i] != null)
{
_threadLoads[i].Value = _cpuLoad.GetThreadLoad(i);
maxLoad = Math.Max(maxLoad, _threadLoads[i].Value ?? 0);
}
}
}
if (_totalLoad != null)
_totalLoad.Value = _cpuLoad.GetTotalLoad();
if (_maxLoad != null)
_maxLoad.Value = maxLoad;
}
}
}

View File

@@ -0,0 +1,731 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Cpu;
internal sealed class IntelCpu : GenericCpu
{
private readonly Sensor _busClock;
private readonly Sensor _coreAvg;
private readonly Sensor[] _coreClocks;
private readonly Sensor _coreMax;
private readonly Sensor[] _coreTemperatures;
private readonly Sensor[] _coreVIDs;
private readonly Sensor _coreVoltage;
private readonly Sensor[] _distToTjMaxTemperatures;
private readonly uint[] _energyStatusMsrs = { MSR_PKG_ENERGY_STATUS, MSR_PP0_ENERGY_STATUS, MSR_PP1_ENERGY_STATUS, MSR_DRAM_ENERGY_STATUS, MSR_PLATFORM_ENERGY_STATUS };
private readonly uint[] _lastEnergyConsumed;
private readonly DateTime[] _lastEnergyTime;
private readonly MicroArchitecture _microArchitecture;
private readonly Sensor _packageTemperature;
private readonly Sensor[] _powerSensors;
private readonly double _timeStampCounterMultiplier;
public IntelCpu(int processorIndex, CpuId[][] cpuId, ISettings settings) : base(processorIndex, cpuId, settings)
{
uint eax;
// set tjMax
float[] tjMax;
switch (_family)
{
case 0x06:
{
switch (_model)
{
case 0x0F: // Intel Core 2 (65nm)
_microArchitecture = MicroArchitecture.Core;
tjMax = _stepping switch
{
// B2
0x06 => _coreCount switch
{
2 => Floats(80 + 10),
4 => Floats(90 + 10),
_ => Floats(85 + 10)
},
// G0
0x0B => Floats(90 + 10),
// M0
0x0D => Floats(85 + 10),
_ => Floats(85 + 10)
};
break;
case 0x17: // Intel Core 2 (45nm)
_microArchitecture = MicroArchitecture.Core;
tjMax = Floats(100);
break;
case 0x1C: // Intel Atom (45nm)
_microArchitecture = MicroArchitecture.Atom;
tjMax = _stepping switch
{
// C0
0x02 => Floats(90),
// A0, B0
0x0A => Floats(100),
_ => Floats(90)
};
break;
case 0x1A: // Intel Core i7 LGA1366 (45nm)
case 0x1E: // Intel Core i5, i7 LGA1156 (45nm)
case 0x1F: // Intel Core i5, i7
case 0x25: // Intel Core i3, i5, i7 LGA1156 (32nm)
case 0x2C: // Intel Core i7 LGA1366 (32nm) 6 Core
case 0x2E: // Intel Xeon Processor 7500 series (45nm)
case 0x2F: // Intel Xeon Processor (32nm)
_microArchitecture = MicroArchitecture.Nehalem;
tjMax = GetTjMaxFromMsr();
break;
case 0x2A: // Intel Core i5, i7 2xxx LGA1155 (32nm)
case 0x2D: // Next Generation Intel Xeon, i7 3xxx LGA2011 (32nm)
_microArchitecture = MicroArchitecture.SandyBridge;
tjMax = GetTjMaxFromMsr();
break;
case 0x3A: // Intel Core i5, i7 3xxx LGA1155 (22nm)
case 0x3E: // Intel Core i7 4xxx LGA2011 (22nm)
_microArchitecture = MicroArchitecture.IvyBridge;
tjMax = GetTjMaxFromMsr();
break;
case 0x3C: // Intel Core i5, i7 4xxx LGA1150 (22nm)
case 0x3F: // Intel Xeon E5-2600/1600 v3, Core i7-59xx
// LGA2011-v3, Haswell-E (22nm)
case 0x45: // Intel Core i5, i7 4xxxU (22nm)
case 0x46:
_microArchitecture = MicroArchitecture.Haswell;
tjMax = GetTjMaxFromMsr();
break;
case 0x3D: // Intel Core M-5xxx (14nm)
case 0x47: // Intel i5, i7 5xxx, Xeon E3-1200 v4 (14nm)
case 0x4F: // Intel Xeon E5-26xx v4
case 0x56: // Intel Xeon D-15xx
_microArchitecture = MicroArchitecture.Broadwell;
tjMax = GetTjMaxFromMsr();
break;
case 0x36: // Intel Atom S1xxx, D2xxx, N2xxx (32nm)
_microArchitecture = MicroArchitecture.Atom;
tjMax = GetTjMaxFromMsr();
break;
case 0x37: // Intel Atom E3xxx, Z3xxx (22nm)
case 0x4A:
case 0x4D: // Intel Atom C2xxx (22nm)
case 0x5A:
case 0x5D:
_microArchitecture = MicroArchitecture.Silvermont;
tjMax = GetTjMaxFromMsr();
break;
case 0x4E:
case 0x5E: // Intel Core i5, i7 6xxxx LGA1151 (14nm)
case 0x55: // Intel Core X i7, i9 7xxx LGA2066 (14nm)
_microArchitecture = MicroArchitecture.Skylake;
tjMax = GetTjMaxFromMsr();
break;
case 0x4C: // Intel Airmont (Cherry Trail, Braswell)
_microArchitecture = MicroArchitecture.Airmont;
tjMax = GetTjMaxFromMsr();
break;
case 0x8E: // Intel Core i5, i7 7xxxx (14nm) (Kaby Lake) and 8xxxx (14nm++) (Coffee Lake)
case 0x9E:
_microArchitecture = MicroArchitecture.KabyLake;
tjMax = GetTjMaxFromMsr();
break;
case 0x5C: // Goldmont (Apollo Lake)
case 0x5F: // (Denverton)
_microArchitecture = MicroArchitecture.Goldmont;
tjMax = GetTjMaxFromMsr();
break;
case 0x7A: // Goldmont plus (Gemini Lake)
_microArchitecture = MicroArchitecture.GoldmontPlus;
tjMax = GetTjMaxFromMsr();
break;
case 0x66: // Intel Core i3 8xxx (10nm) (Cannon Lake)
_microArchitecture = MicroArchitecture.CannonLake;
tjMax = GetTjMaxFromMsr();
break;
case 0x7D: // Intel Core i3, i5, i7 10xxx (10nm) (Ice Lake)
case 0x7E:
case 0x6A: // Ice Lake server
case 0x6C:
_microArchitecture = MicroArchitecture.IceLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xA5:
case 0xA6: // Intel Core i3, i5, i7 10xxxU (14nm)
_microArchitecture = MicroArchitecture.CometLake;
tjMax = GetTjMaxFromMsr();
break;
case 0x86: // Tremont (10nm) (Elkhart Lake, Skyhawk Lake)
_microArchitecture = MicroArchitecture.Tremont;
tjMax = GetTjMaxFromMsr();
break;
case 0x8C: // Tiger Lake (Intel 10 nm SuperFin, Gen. 11)
case 0x8D:
_microArchitecture = MicroArchitecture.TigerLake;
tjMax = GetTjMaxFromMsr();
break;
case 0x97: // Alder Lake (Intel 7 (10ESF), Gen. 12)
case 0x9A: // Alder Lake-L (Intel 7 (10ESF), Gen. 12)
case 0xBE: // Alder Lake-N (Intel 7 (10ESF), Gen. 12)
_microArchitecture = MicroArchitecture.AlderLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xB7: // Raptor Lake (Intel 7 (10ESF), Gen. 13)
case 0xBA: // Raptor Lake-P (Intel 7 (10ESF), Gen. 13)
case 0xBF: // Raptor Lake-N (Intel 7 (10ESF), Gen. 13)
_microArchitecture = MicroArchitecture.RaptorLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xAC: // Meteor Lake (Intel 4, TSMC N5/N6, Gen. 14)
case 0xAA: // Meteor Lake-L (Intel 4, TSMC N5/N6, Gen. 14)
_microArchitecture = MicroArchitecture.MeteorLake;
tjMax = GetTjMaxFromMsr();
break;
case 0x9C: // Jasper Lake (10nm)
_microArchitecture = MicroArchitecture.JasperLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xA7: // Intel Core i5, i6, i7 11xxx (14nm) (Rocket Lake)
_microArchitecture = MicroArchitecture.RocketLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xC6: // Intel Core Ultra 7 200 Series ArrowLake
_microArchitecture = MicroArchitecture.ArrowLake;
tjMax = GetTjMaxFromMsr();
break;
case 0xBD: // Intel Core Ultra 5/7 200 Series LunarLake
_microArchitecture = MicroArchitecture.LunarLake;
tjMax = GetTjMaxFromMsr();
break;
default:
_microArchitecture = MicroArchitecture.Unknown;
tjMax = Floats(100);
break;
}
}
break;
case 0x0F:
switch (_model)
{
case 0x00: // Pentium 4 (180nm)
case 0x01: // Pentium 4 (130nm)
case 0x02: // Pentium 4 (130nm)
case 0x03: // Pentium 4, Celeron D (90nm)
case 0x04: // Pentium 4, Pentium D, Celeron D (90nm)
case 0x06: // Pentium 4, Pentium D, Celeron D (65nm)
_microArchitecture = MicroArchitecture.NetBurst;
tjMax = Floats(100);
break;
default:
_microArchitecture = MicroArchitecture.Unknown;
tjMax = Floats(100);
break;
}
break;
default:
_microArchitecture = MicroArchitecture.Unknown;
tjMax = Floats(100);
break;
}
// set timeStampCounterMultiplier
switch (_microArchitecture)
{
case MicroArchitecture.Atom:
case MicroArchitecture.Core:
case MicroArchitecture.NetBurst:
if (Ring0.ReadMsr(IA32_PERF_STATUS, out uint _, out uint edx))
_timeStampCounterMultiplier = ((edx >> 8) & 0x1f) + (0.5 * ((edx >> 14) & 1));
break;
case MicroArchitecture.Airmont:
case MicroArchitecture.AlderLake:
case MicroArchitecture.ArrowLake:
case MicroArchitecture.Broadwell:
case MicroArchitecture.CannonLake:
case MicroArchitecture.CometLake:
case MicroArchitecture.Goldmont:
case MicroArchitecture.GoldmontPlus:
case MicroArchitecture.Haswell:
case MicroArchitecture.IceLake:
case MicroArchitecture.IvyBridge:
case MicroArchitecture.JasperLake:
case MicroArchitecture.KabyLake:
case MicroArchitecture.LunarLake:
case MicroArchitecture.Nehalem:
case MicroArchitecture.MeteorLake:
case MicroArchitecture.RaptorLake:
case MicroArchitecture.RocketLake:
case MicroArchitecture.SandyBridge:
case MicroArchitecture.Silvermont:
case MicroArchitecture.Skylake:
case MicroArchitecture.TigerLake:
case MicroArchitecture.Tremont:
if (Ring0.ReadMsr(MSR_PLATFORM_INFO, out eax, out uint _))
_timeStampCounterMultiplier = (eax >> 8) & 0xff;
break;
default:
_timeStampCounterMultiplier = 0;
break;
}
int coreSensorId = 0;
//core temp avg and max value
//is only available when the cpu has more than 1 core
if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 0x40) != 0 && _microArchitecture != MicroArchitecture.Unknown && _coreCount > 1)
{
_coreMax = new Sensor("Core Max", coreSensorId, SensorType.Temperature, this, settings);
ActivateSensor(_coreMax);
coreSensorId++;
_coreAvg = new Sensor("Core Average", coreSensorId, SensorType.Temperature, this, settings);
ActivateSensor(_coreAvg);
coreSensorId++;
}
else
{
_coreMax = null;
_coreAvg = null;
}
// check if processor supports a digital thermal sensor at core level
if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 1) != 0 && _microArchitecture != MicroArchitecture.Unknown)
{
_coreTemperatures = new Sensor[_coreCount];
for (int i = 0; i < _coreTemperatures.Length; i++)
{
_coreTemperatures[i] = new Sensor(CoreString(i),
coreSensorId,
SensorType.Temperature,
this,
new[]
{
new ParameterDescription("TjMax [°C]", "TjMax temperature of the core sensor.\n" + "Temperature = TjMax - TSlope * Value.", tjMax[i]),
new ParameterDescription("TSlope [°C]", "Temperature slope of the digital thermal sensor.\n" + "Temperature = TjMax - TSlope * Value.", 1)
},
settings);
ActivateSensor(_coreTemperatures[i]);
coreSensorId++;
}
}
else
_coreTemperatures = Array.Empty<Sensor>();
// check if processor supports a digital thermal sensor at package level
if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 0x40) != 0 && _microArchitecture != MicroArchitecture.Unknown)
{
_packageTemperature = new Sensor("CPU Package",
coreSensorId,
SensorType.Temperature,
this,
new[]
{
new ParameterDescription("TjMax [°C]", "TjMax temperature of the package sensor.\n" + "Temperature = TjMax - TSlope * Value.", tjMax[0]),
new ParameterDescription("TSlope [°C]", "Temperature slope of the digital thermal sensor.\n" + "Temperature = TjMax - TSlope * Value.", 1)
},
settings);
ActivateSensor(_packageTemperature);
coreSensorId++;
}
// dist to tjmax sensor
if (cpuId[0][0].Data.GetLength(0) > 6 && (cpuId[0][0].Data[6, 0] & 1) != 0 && _microArchitecture != MicroArchitecture.Unknown)
{
_distToTjMaxTemperatures = new Sensor[_coreCount];
for (int i = 0; i < _distToTjMaxTemperatures.Length; i++)
{
_distToTjMaxTemperatures[i] = new Sensor(CoreString(i) + " Distance to TjMax", coreSensorId, SensorType.Temperature, this, settings);
ActivateSensor(_distToTjMaxTemperatures[i]);
coreSensorId++;
}
}
else
_distToTjMaxTemperatures = Array.Empty<Sensor>();
_busClock = new Sensor("Bus Speed", 0, SensorType.Clock, this, settings);
_coreClocks = new Sensor[_coreCount];
for (int i = 0; i < _coreClocks.Length; i++)
{
_coreClocks[i] = new Sensor(CoreString(i), i + 1, SensorType.Clock, this, settings);
if (HasTimeStampCounter && _microArchitecture != MicroArchitecture.Unknown)
ActivateSensor(_coreClocks[i]);
}
if (_microArchitecture is MicroArchitecture.Airmont or
MicroArchitecture.AlderLake or
MicroArchitecture.ArrowLake or
MicroArchitecture.Broadwell or
MicroArchitecture.CannonLake or
MicroArchitecture.CometLake or
MicroArchitecture.Goldmont or
MicroArchitecture.GoldmontPlus or
MicroArchitecture.Haswell or
MicroArchitecture.IceLake or
MicroArchitecture.IvyBridge or
MicroArchitecture.JasperLake or
MicroArchitecture.KabyLake or
MicroArchitecture.LunarLake or
MicroArchitecture.MeteorLake or
MicroArchitecture.RaptorLake or
MicroArchitecture.RocketLake or
MicroArchitecture.SandyBridge or
MicroArchitecture.Silvermont or
MicroArchitecture.Skylake or
MicroArchitecture.TigerLake or
MicroArchitecture.Tremont)
{
_powerSensors = new Sensor[_energyStatusMsrs.Length];
_lastEnergyTime = new DateTime[_energyStatusMsrs.Length];
_lastEnergyConsumed = new uint[_energyStatusMsrs.Length];
if (Ring0.ReadMsr(MSR_RAPL_POWER_UNIT, out eax, out uint _))
{
EnergyUnitsMultiplier = _microArchitecture switch
{
MicroArchitecture.Silvermont or MicroArchitecture.Airmont => 1.0e-6f * (1 << (int)((eax >> 8) & 0x1F)),
_ => 1.0f / (1 << (int)((eax >> 8) & 0x1F))
};
}
if (EnergyUnitsMultiplier != 0)
{
string[] powerSensorLabels = { "CPU Package", "CPU Cores", "CPU Graphics", "CPU Memory", "CPU Platform" };
for (int i = 0; i < _energyStatusMsrs.Length; i++)
{
if (!Ring0.ReadMsr(_energyStatusMsrs[i], out eax, out uint _))
continue;
// Don't show the "GPU Graphics" sensor on windows, it will show up under the GPU instead.
if (i == 2 && !Software.OperatingSystem.IsUnix)
continue;
_lastEnergyTime[i] = DateTime.UtcNow;
_lastEnergyConsumed[i] = eax;
_powerSensors[i] = new Sensor(powerSensorLabels[i],
i,
SensorType.Power,
this,
settings);
ActivateSensor(_powerSensors[i]);
}
}
}
if (Ring0.ReadMsr(IA32_PERF_STATUS, out eax, out uint _) && ((eax >> 32) & 0xFFFF) > 0)
{
_coreVoltage = new Sensor("CPU Core", 0, SensorType.Voltage, this, settings);
ActivateSensor(_coreVoltage);
}
_coreVIDs = new Sensor[_coreCount];
for (int i = 0; i < _coreVIDs.Length; i++)
{
_coreVIDs[i] = new Sensor(CoreString(i), i + 1, SensorType.Voltage, this, settings);
ActivateSensor(_coreVIDs[i]);
}
Update();
}
public float EnergyUnitsMultiplier { get; }
private float[] Floats(float f)
{
float[] result = new float[_coreCount];
for (int i = 0; i < _coreCount; i++)
result[i] = f;
return result;
}
private float[] GetTjMaxFromMsr()
{
float[] result = new float[_coreCount];
for (int i = 0; i < _coreCount; i++)
{
if (Ring0.ReadMsr(IA32_TEMPERATURE_TARGET, out uint eax, out uint _, _cpuId[i][0].Affinity))
result[i] = (eax >> 16) & 0xFF;
else
result[i] = 100;
}
return result;
}
protected override uint[] GetMsrs()
{
return new[]
{
MSR_PLATFORM_INFO,
IA32_PERF_STATUS,
IA32_THERM_STATUS_MSR,
IA32_TEMPERATURE_TARGET,
IA32_PACKAGE_THERM_STATUS,
MSR_RAPL_POWER_UNIT,
MSR_PKG_ENERGY_STATUS,
MSR_DRAM_ENERGY_STATUS,
MSR_PP0_ENERGY_STATUS,
MSR_PP1_ENERGY_STATUS,
MSR_PLATFORM_ENERGY_STATUS,
};
}
public override string GetReport()
{
StringBuilder r = new();
r.Append(base.GetReport());
r.Append("MicroArchitecture: ");
r.AppendLine(_microArchitecture.ToString());
r.Append("Time Stamp Counter Multiplier: ");
r.AppendLine(_timeStampCounterMultiplier.ToString(CultureInfo.InvariantCulture));
r.AppendLine();
return r.ToString();
}
public override void Update()
{
base.Update();
float coreMax = float.MinValue;
float coreAvg = 0;
uint eax;
for (int i = 0; i < _coreTemperatures.Length; i++)
{
// if reading is valid
if (Ring0.ReadMsr(IA32_THERM_STATUS_MSR, out eax, out _, _cpuId[i][0].Affinity) && (eax & 0x80000000) != 0)
{
// get the dist from tjMax from bits 22:16
float deltaT = (eax & 0x007F0000) >> 16;
float tjMax = _coreTemperatures[i].Parameters[0].Value;
float tSlope = _coreTemperatures[i].Parameters[1].Value;
_coreTemperatures[i].Value = tjMax - (tSlope * deltaT);
coreAvg += (float)_coreTemperatures[i].Value;
if (coreMax < _coreTemperatures[i].Value)
coreMax = (float)_coreTemperatures[i].Value;
_distToTjMaxTemperatures[i].Value = deltaT;
}
else
{
_coreTemperatures[i].Value = null;
_distToTjMaxTemperatures[i].Value = null;
}
}
//calculate average cpu temperature over all cores
if (_coreMax != null && coreMax != float.MinValue)
{
_coreMax.Value = coreMax;
coreAvg /= _coreTemperatures.Length;
_coreAvg.Value = coreAvg;
}
if (_packageTemperature != null)
{
// if reading is valid
if (Ring0.ReadMsr(IA32_PACKAGE_THERM_STATUS, out eax, out _, _cpuId[0][0].Affinity) && (eax & 0x80000000) != 0)
{
// get the dist from tjMax from bits 22:16
float deltaT = (eax & 0x007F0000) >> 16;
float tjMax = _packageTemperature.Parameters[0].Value;
float tSlope = _packageTemperature.Parameters[1].Value;
_packageTemperature.Value = tjMax - (tSlope * deltaT);
}
else
{
_packageTemperature.Value = null;
}
}
if (HasTimeStampCounter && _timeStampCounterMultiplier > 0)
{
double newBusClock = 0;
for (int i = 0; i < _coreClocks.Length; i++)
{
System.Threading.Thread.Sleep(1);
if (Ring0.ReadMsr(IA32_PERF_STATUS, out eax, out _, _cpuId[i][0].Affinity))
{
newBusClock = TimeStampCounterFrequency / _timeStampCounterMultiplier;
switch (_microArchitecture)
{
case MicroArchitecture.Nehalem:
_coreClocks[i].Value = (float)((eax & 0xff) * newBusClock);
break;
case MicroArchitecture.Airmont:
case MicroArchitecture.AlderLake:
case MicroArchitecture.ArrowLake:
case MicroArchitecture.Broadwell:
case MicroArchitecture.CannonLake:
case MicroArchitecture.CometLake:
case MicroArchitecture.Goldmont:
case MicroArchitecture.GoldmontPlus:
case MicroArchitecture.Haswell:
case MicroArchitecture.IceLake:
case MicroArchitecture.IvyBridge:
case MicroArchitecture.JasperLake:
case MicroArchitecture.KabyLake:
case MicroArchitecture.LunarLake:
case MicroArchitecture.MeteorLake:
case MicroArchitecture.RaptorLake:
case MicroArchitecture.RocketLake:
case MicroArchitecture.SandyBridge:
case MicroArchitecture.Silvermont:
case MicroArchitecture.Skylake:
case MicroArchitecture.TigerLake:
case MicroArchitecture.Tremont:
_coreClocks[i].Value = (float)(((eax >> 8) & 0xff) * newBusClock);
break;
default:
_coreClocks[i].Value = (float)((((eax >> 8) & 0x1f) + (0.5 * ((eax >> 14) & 1))) * newBusClock);
break;
}
}
else
{
// if IA32_PERF_STATUS is not available, assume TSC frequency
_coreClocks[i].Value = (float)TimeStampCounterFrequency;
}
}
if (newBusClock > 0)
{
_busClock.Value = (float)newBusClock;
ActivateSensor(_busClock);
}
}
if (_powerSensors != null)
{
foreach (Sensor sensor in _powerSensors)
{
if (sensor == null)
continue;
if (!Ring0.ReadMsr(_energyStatusMsrs[sensor.Index], out eax, out _))
continue;
DateTime time = DateTime.UtcNow;
uint energyConsumed = eax;
float deltaTime = (float)(time - _lastEnergyTime[sensor.Index]).TotalSeconds;
if (deltaTime < 0.01)
continue;
sensor.Value = EnergyUnitsMultiplier * unchecked(energyConsumed - _lastEnergyConsumed[sensor.Index]) / deltaTime;
_lastEnergyTime[sensor.Index] = time;
_lastEnergyConsumed[sensor.Index] = energyConsumed;
}
}
if (_coreVoltage != null && Ring0.ReadMsr(IA32_PERF_STATUS, out _, out uint edx))
{
_coreVoltage.Value = ((edx >> 32) & 0xFFFF) / (float)(1 << 13);
}
for (int i = 0; i < _coreVIDs.Length; i++)
{
if (Ring0.ReadMsr(IA32_PERF_STATUS, out _, out edx, _cpuId[i][0].Affinity) && ((edx >> 32) & 0xFFFF) > 0)
{
_coreVIDs[i].Value = ((edx >> 32) & 0xFFFF) / (float)(1 << 13);
ActivateSensor(_coreVIDs[i]);
}
else
{
DeactivateSensor(_coreVIDs[i]);
}
}
}
[SuppressMessage("ReSharper", "IdentifierTypo")]
private enum MicroArchitecture
{
Airmont,
AlderLake,
Atom,
ArrowLake, // Gen. 15 (0xC6, -H = 0xC5)
Broadwell,
CannonLake,
CometLake,
Core,
Goldmont,
GoldmontPlus,
Haswell,
IceLake,
IvyBridge,
JasperLake,
KabyLake,
LunarLake,
Nehalem,
NetBurst,
MeteorLake,
RocketLake,
SandyBridge,
Silvermont,
Skylake,
TigerLake,
Tremont,
RaptorLake,
Unknown
}
// ReSharper disable InconsistentNaming
private const uint IA32_PACKAGE_THERM_STATUS = 0x1B1;
private const uint IA32_PERF_STATUS = 0x0198;
private const uint IA32_TEMPERATURE_TARGET = 0x01A2;
private const uint IA32_THERM_STATUS_MSR = 0x019C;
private const uint MSR_DRAM_ENERGY_STATUS = 0x619;
private const uint MSR_PKG_ENERGY_STATUS = 0x611;
private const uint MSR_PLATFORM_INFO = 0xCE;
private const uint MSR_PP0_ENERGY_STATUS = 0x639;
private const uint MSR_PP1_ENERGY_STATUS = 0x641;
private const uint MSR_PLATFORM_ENERGY_STATUS = 0x64D;
private const uint MSR_RAPL_POWER_UNIT = 0x606;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,288 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
// Ported from: https://github.com/processhacker/processhacker/blob/master/plugins/ExtendedTools/gpumon.c
using System;
using System.Linq;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware;
internal static class D3DDisplayDevice
{
public static string[] GetDeviceIdentifiers()
{
if (CfgMgr32.CM_Get_Device_Interface_List_Size(out uint size, ref CfgMgr32.GUID_DISPLAY_DEVICE_ARRIVAL, null, CfgMgr32.CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CfgMgr32.CR_SUCCESS)
return null;
char[] data = new char[size];
if (CfgMgr32.CM_Get_Device_Interface_List(ref CfgMgr32.GUID_DISPLAY_DEVICE_ARRIVAL, null, data, (uint)data.Length, CfgMgr32.CM_GET_DEVICE_INTERFACE_LIST_PRESENT) == CfgMgr32.CR_SUCCESS)
return new string(data).Split('\0').Where(m => !string.IsNullOrEmpty(m)).ToArray();
return null;
}
public static string GetActualDeviceIdentifier(string deviceIdentifier)
{
if (string.IsNullOrEmpty(deviceIdentifier))
return deviceIdentifier;
// For example:
// \\?\ROOT#BasicRender#0000#{1ca05180-a699-450a-9a0c-de4fbe3ddd89} --> ROOT\BasicRender\0000
// \\?\PCI#VEN_1002&DEV_731F&SUBSYS_57051682&REV_C4#6&e539058&0&00000019#{1ca05180-a699-450a-9a0c-de4fbe3ddd89} --> PCI\VEN_1002&DEV_731F&SUBSYS_57051682&REV_C4\6&e539058&0&00000019
if (deviceIdentifier.StartsWith(@"\\?\"))
deviceIdentifier = deviceIdentifier.Substring(4);
if (deviceIdentifier.Length > 0 && deviceIdentifier[deviceIdentifier.Length - 1] == '}')
{
int lastIndex = deviceIdentifier.LastIndexOf('{');
if (lastIndex > 0)
deviceIdentifier = deviceIdentifier.Substring(0, lastIndex - 1);
}
return deviceIdentifier.Replace('#', '\\');
}
public static bool GetDeviceInfoByIdentifier(string deviceIdentifier, out D3DDeviceInfo deviceInfo)
{
deviceInfo = new D3DDeviceInfo();
OpenAdapterFromDeviceName(out uint status, deviceIdentifier, out D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter);
if (status != WinNt.STATUS_SUCCESS)
return false;
GetAdapterType(out status, adapter, out D3dkmth.D3DKMT_ADAPTERTYPE adapterType);
if (status != WinNt.STATUS_SUCCESS)
return false;
if (!adapterType.Value.HasFlag(D3dkmth.D3DKMT_ADAPTERTYPE_FLAGS.SoftwareDevice))
return false;
deviceInfo.Integrated = !adapterType.Value.HasFlag(D3dkmth.D3DKMT_ADAPTERTYPE_FLAGS.HybridIntegrated);
GetQueryStatisticsAdapterInformation(out status, adapter, out D3dkmth.D3DKMT_QUERYSTATISTICS_ADAPTER_INFORMATION adapterInformation);
if (status != WinNt.STATUS_SUCCESS)
return false;
uint segmentCount = adapterInformation.NbSegments;
uint nodeCount = adapterInformation.NodeCount;
deviceInfo.Nodes = new D3DDeviceNodeInfo[nodeCount];
DateTime queryTime = DateTime.Now;
for (uint nodeId = 0; nodeId < nodeCount; nodeId++)
{
GetNodeMetaData(out status, adapter, nodeId, out D3dkmth.D3DKMT_NODEMETADATA nodeMetaData);
if (status != WinNt.STATUS_SUCCESS)
return false;
GetQueryStatisticsNode(out status, adapter, nodeId, out D3dkmth.D3DKMT_QUERYSTATISTICS_NODE_INFORMATION nodeInformation);
if (status != WinNt.STATUS_SUCCESS)
return false;
deviceInfo.Nodes[nodeId] = new D3DDeviceNodeInfo
{
Id = nodeId,
Name = GetNodeEngineTypeString(nodeMetaData),
RunningTime = nodeInformation.GlobalInformation.RunningTime.QuadPart,
QueryTime = queryTime
};
}
GetSegmentSize(out status, adapter, out D3dkmth.D3DKMT_SEGMENTSIZEINFO segmentSizeInfo);
if (status != WinNt.STATUS_SUCCESS)
return false;
deviceInfo.GpuSharedLimit = segmentSizeInfo.SharedSystemMemorySize;
deviceInfo.GpuVideoMemoryLimit = segmentSizeInfo.DedicatedVideoMemorySize;
deviceInfo.GpuDedicatedLimit = segmentSizeInfo.DedicatedSystemMemorySize;
for (uint segmentId = 0; segmentId < segmentCount; segmentId++)
{
GetQueryStatisticsSegment(out status, adapter, segmentId, out D3dkmth.D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION segmentInformation);
if (status != WinNt.STATUS_SUCCESS)
return false;
ulong bytesResident = segmentInformation.BytesResident;
ulong bytesCommitted = segmentInformation.BytesCommitted;
uint aperture = segmentInformation.Aperture;
if (aperture == 1)
{
deviceInfo.GpuSharedUsed += bytesResident;
deviceInfo.GpuSharedMax += bytesCommitted;
}
else
{
deviceInfo.GpuDedicatedUsed += bytesResident;
deviceInfo.GpuDedicatedMax += bytesCommitted;
}
}
CloseAdapter(out status, adapter);
return status == WinNt.STATUS_SUCCESS;
}
private static string GetNodeEngineTypeString(D3dkmth.D3DKMT_NODEMETADATA nodeMetaData)
{
return nodeMetaData.NodeData.EngineType switch
{
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_OTHER => "D3D " + (!string.IsNullOrWhiteSpace(nodeMetaData.NodeData.FriendlyName) ? nodeMetaData.NodeData.FriendlyName : "Other"),
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_3D => "D3D 3D",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_VIDEO_DECODE => "D3D Video Decode",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_VIDEO_ENCODE => "D3D Video Encode",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_VIDEO_PROCESSING => "D3D Video Processing",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_SCENE_ASSEMBLY => "D3D Scene Assembly",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_COPY => "D3D Copy",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_OVERLAY => "D3D Overlay",
D3dkmdt.DXGK_ENGINE_TYPE.DXGK_ENGINE_TYPE_CRYPTO => "D3D Crypto",
_ => "D3D Unknown"
};
}
private static void GetSegmentSize
(
out uint status,
D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter,
out D3dkmth.D3DKMT_SEGMENTSIZEINFO sizeInformation)
{
IntPtr segmentSizePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(D3dkmth.D3DKMT_SEGMENTSIZEINFO)));
sizeInformation = new D3dkmth.D3DKMT_SEGMENTSIZEINFO();
Marshal.StructureToPtr(sizeInformation, segmentSizePtr, true);
var queryAdapterInfo = new D3dkmth.D3DKMT_QUERYADAPTERINFO
{
hAdapter = adapter.hAdapter,
Type = D3dkmth.KMTQUERYADAPTERINFOTYPE.KMTQAITYPE_GETSEGMENTSIZE,
pPrivateDriverData = segmentSizePtr,
PrivateDriverDataSize = Marshal.SizeOf(typeof(D3dkmth.D3DKMT_SEGMENTSIZEINFO))
};
status = Gdi32.D3DKMTQueryAdapterInfo(ref queryAdapterInfo);
sizeInformation = Marshal.PtrToStructure<D3dkmth.D3DKMT_SEGMENTSIZEINFO>(segmentSizePtr);
Marshal.FreeHGlobal(segmentSizePtr);
}
private static void GetNodeMetaData(out uint status, D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter, uint nodeId, out D3dkmth.D3DKMT_NODEMETADATA nodeMetaDataResult)
{
IntPtr nodeMetaDataPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(D3dkmth.D3DKMT_NODEMETADATA)));
nodeMetaDataResult = new D3dkmth.D3DKMT_NODEMETADATA { NodeOrdinalAndAdapterIndex = nodeId };
Marshal.StructureToPtr(nodeMetaDataResult, nodeMetaDataPtr, true);
var queryAdapterInfo = new D3dkmth.D3DKMT_QUERYADAPTERINFO
{
hAdapter = adapter.hAdapter,
Type = D3dkmth.KMTQUERYADAPTERINFOTYPE.KMTQAITYPE_NODEMETADATA,
pPrivateDriverData = nodeMetaDataPtr,
PrivateDriverDataSize = Marshal.SizeOf(typeof(D3dkmth.D3DKMT_NODEMETADATA))
};
status = Gdi32.D3DKMTQueryAdapterInfo(ref queryAdapterInfo);
nodeMetaDataResult = Marshal.PtrToStructure<D3dkmth.D3DKMT_NODEMETADATA>(nodeMetaDataPtr);
Marshal.FreeHGlobal(nodeMetaDataPtr);
}
private static void GetQueryStatisticsNode(out uint status, D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter, uint nodeId, out D3dkmth.D3DKMT_QUERYSTATISTICS_NODE_INFORMATION nodeInformation)
{
var queryElement = new D3dkmth.D3DKMT_QUERYSTATISTICS_QUERY_ELEMENT { QueryNode = { NodeId = nodeId } };
var queryStatistics = new D3dkmth.D3DKMT_QUERYSTATISTICS
{
AdapterLuid = adapter.AdapterLuid, Type = D3dkmth.D3DKMT_QUERYSTATISTICS_TYPE.D3DKMT_QUERYSTATISTICS_NODE, QueryElement = queryElement
};
status = Gdi32.D3DKMTQueryStatistics(ref queryStatistics);
nodeInformation = queryStatistics.QueryResult.NodeInformation;
}
private static void GetQueryStatisticsSegment
(
out uint status,
D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter,
uint segmentId,
out D3dkmth.D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION segmentInformation)
{
var queryElement = new D3dkmth.D3DKMT_QUERYSTATISTICS_QUERY_ELEMENT { QuerySegment = { SegmentId = segmentId } };
var queryStatistics = new D3dkmth.D3DKMT_QUERYSTATISTICS
{
AdapterLuid = adapter.AdapterLuid, Type = D3dkmth.D3DKMT_QUERYSTATISTICS_TYPE.D3DKMT_QUERYSTATISTICS_SEGMENT, QueryElement = queryElement
};
status = Gdi32.D3DKMTQueryStatistics(ref queryStatistics);
segmentInformation = queryStatistics.QueryResult.SegmentInformation;
}
private static void GetQueryStatisticsAdapterInformation
(
out uint status,
D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter,
out D3dkmth.D3DKMT_QUERYSTATISTICS_ADAPTER_INFORMATION adapterInformation)
{
var queryStatistics = new D3dkmth.D3DKMT_QUERYSTATISTICS { AdapterLuid = adapter.AdapterLuid, Type = D3dkmth.D3DKMT_QUERYSTATISTICS_TYPE.D3DKMT_QUERYSTATISTICS_ADAPTER };
status = Gdi32.D3DKMTQueryStatistics(ref queryStatistics);
adapterInformation = queryStatistics.QueryResult.AdapterInformation;
}
private static void GetAdapterType(out uint status, D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter, out D3dkmth.D3DKMT_ADAPTERTYPE adapterTypeResult)
{
IntPtr adapterTypePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(D3dkmth.D3DKMT_ADAPTERTYPE)));
var queryAdapterInfo = new D3dkmth.D3DKMT_QUERYADAPTERINFO
{
hAdapter = adapter.hAdapter,
Type = D3dkmth.KMTQUERYADAPTERINFOTYPE.KMTQAITYPE_ADAPTERTYPE,
pPrivateDriverData = adapterTypePtr,
PrivateDriverDataSize = Marshal.SizeOf(typeof(D3dkmth.D3DKMT_ADAPTERTYPE))
};
status = Gdi32.D3DKMTQueryAdapterInfo(ref queryAdapterInfo);
adapterTypeResult = Marshal.PtrToStructure<D3dkmth.D3DKMT_ADAPTERTYPE>(adapterTypePtr);
Marshal.FreeHGlobal(adapterTypePtr);
}
private static void OpenAdapterFromDeviceName(out uint status, string displayDeviceName, out D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter)
{
adapter = new D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME { pDeviceName = displayDeviceName };
status = Gdi32.D3DKMTOpenAdapterFromDeviceName(ref adapter);
}
private static void CloseAdapter(out uint status, D3dkmth.D3DKMT_OPENADAPTERFROMDEVICENAME adapter)
{
var closeAdapter = new D3dkmth.D3DKMT_CLOSEADAPTER { hAdapter = adapter.hAdapter };
status = Gdi32.D3DKMTCloseAdapter(ref closeAdapter);
}
public struct D3DDeviceNodeInfo
{
public ulong Id;
public string Name;
public long RunningTime;
public DateTime QueryTime;
}
public struct D3DDeviceInfo
{
public ulong GpuSharedLimit;
public ulong GpuDedicatedLimit;
public ulong GpuVideoMemoryLimit;
public ulong GpuSharedUsed;
public ulong GpuDedicatedUsed;
public ulong GpuSharedMax;
public ulong GpuDedicatedMax;
public D3DDeviceNodeInfo[] Nodes;
public bool Integrated;
}
}

View File

@@ -0,0 +1,95 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace LibreHardwareMonitor.Hardware;
internal static class FirmwareTable
{
public static byte[] GetTable(Interop.Kernel32.Provider provider, string table)
{
int id = table[3] << 24 | table[2] << 16 | table[1] << 8 | table[0];
return GetTable(provider, id);
}
public static byte[] GetTable(Interop.Kernel32.Provider provider, int table)
{
int size;
try
{
size = Interop.Kernel32.GetSystemFirmwareTable(provider, table, IntPtr.Zero, 0);
}
catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException)
{
return null;
}
if (size <= 0)
return null;
IntPtr allocatedBuffer = IntPtr.Zero;
try
{
allocatedBuffer = Marshal.AllocHGlobal(size);
Interop.Kernel32.GetSystemFirmwareTable(provider, table, allocatedBuffer, size);
if (Marshal.GetLastWin32Error() != 0)
return null;
byte[] buffer = new byte[size];
Marshal.Copy(allocatedBuffer, buffer, 0, size);
return buffer;
}
finally
{
if (allocatedBuffer != IntPtr.Zero)
Marshal.FreeHGlobal(allocatedBuffer);
}
}
public static string[] EnumerateTables(Interop.Kernel32.Provider provider)
{
int size;
try
{
size = Interop.Kernel32.EnumSystemFirmwareTables(provider, IntPtr.Zero, 0);
}
catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException)
{
return null;
}
IntPtr allocatedBuffer = IntPtr.Zero;
try
{
allocatedBuffer = Marshal.AllocHGlobal(size);
Interop.Kernel32.EnumSystemFirmwareTables(provider, allocatedBuffer, size);
byte[] buffer = new byte[size];
Marshal.Copy(allocatedBuffer, buffer, 0, size);
string[] result = new string[size / 4];
for (int i = 0; i < result.Length; i++)
result[i] = Encoding.ASCII.GetString(buffer, 4 * i, 4);
return result;
}
finally
{
if (allocatedBuffer != IntPtr.Zero)
Marshal.FreeHGlobal(allocatedBuffer);
}
}
}

View File

@@ -0,0 +1,981 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Gpu;
internal sealed class AmdGpu : GenericGpu
{
private readonly AtiAdlxx.ADLAdapterInfo _adapterInfo;
private readonly AtiAdlxx.ADLPMLogStartOutput _adlPMLogStartOutput;
private readonly AtiAdlxx.ADLPMLogSupportInfo _adlPMLogSupportInfo;
private readonly AtiAdlxx.ADLGcnInfo _adlGcnInfo;
private readonly IntPtr _context = IntPtr.Zero;
private readonly Sensor _controlSensor;
private readonly Sensor _coreClock;
private readonly Sensor _coreLoad;
private readonly Sensor _coreVoltage;
private readonly int _currentOverdriveApiLevel;
private readonly string _d3dDeviceId;
private readonly uint _device;
private readonly Sensor _fan;
private readonly Control _fanControl;
private readonly bool _frameMetricsStarted;
private readonly Sensor _fullscreenFps;
private readonly Sensor _gpuDedicatedMemoryUsage;
private readonly Sensor _gpuDedicatedMemoryFree;
private readonly Sensor _gpuDedicatedMemoryTotal;
private readonly Sensor[] _gpuNodeUsage;
private readonly DateTime[] _gpuNodeUsagePrevTick;
private readonly long[] _gpuNodeUsagePrevValue;
private readonly Sensor _gpuSharedMemoryUsage;
private readonly Sensor _gpuSharedMemoryFree;
private readonly Sensor _gpuSharedMemoryTotal;
private readonly Sensor _memoryClock;
private readonly Sensor _memoryLoad;
private readonly Sensor _memoryVoltage;
private readonly Sensor _memoryTotal;
private readonly Sensor _memoryUsed;
private readonly Sensor _memoryFree;
private readonly bool _overdriveApiSupported;
private readonly bool _pmLogStarted;
private readonly Sensor _powerCore;
private readonly Sensor _powerPpt;
private readonly Sensor _powerSoC;
private readonly Sensor _powerTotal;
private readonly Sensor _socClock;
private readonly Sensor _socVoltage;
private readonly Sensor _temperatureCore;
private readonly Sensor _temperatureHotSpot;
private readonly Sensor _temperatureLiquid;
private readonly Sensor _temperatureMemory;
private readonly Sensor _temperatureMvdd;
private readonly Sensor _temperaturePlx;
private readonly Sensor _temperatureSoC;
private readonly Sensor _temperatureVddc;
private readonly ushort _pmLogSampleRate = 1000;
private bool _overdrive8LogExists;
public AmdGpu(IntPtr amdContext, AtiAdlxx.ADLAdapterInfo adapterInfo, AtiAdlxx.ADLGcnInfo gcnInfo, ISettings settings)
: base(adapterInfo.AdapterName.Trim(), new Identifier("gpu-amd", adapterInfo.AdapterIndex.ToString(CultureInfo.InvariantCulture)), settings)
{
_context = amdContext;
_adlGcnInfo = gcnInfo;
_adapterInfo = adapterInfo;
BusNumber = adapterInfo.BusNumber;
DeviceNumber = adapterInfo.DeviceNumber;
_temperatureCore = new Sensor("GPU Core", 0, SensorType.Temperature, this, settings);
_temperatureMemory = new Sensor("GPU Memory", 1, SensorType.Temperature, this, settings);
_temperatureVddc = new Sensor("GPU VR VDDC", 2, SensorType.Temperature, this, settings);
_temperatureMvdd = new Sensor("GPU VR MVDD", 3, SensorType.Temperature, this, settings);
_temperatureSoC = new Sensor("GPU VR SoC", 4, SensorType.Temperature, this, settings);
_temperatureLiquid = new Sensor("GPU Liquid", 5, SensorType.Temperature, this, settings);
_temperaturePlx = new Sensor("GPU PLX", 6, SensorType.Temperature, this, settings);
_temperatureHotSpot = new Sensor("GPU Hot Spot", 7, SensorType.Temperature, this, settings);
_coreClock = new Sensor("GPU Core", 0, SensorType.Clock, this, settings);
_socClock = new Sensor("GPU SoC", 1, SensorType.Clock, this, settings);
_memoryClock = new Sensor("GPU Memory", 2, SensorType.Clock, this, settings);
_fan = new Sensor("GPU Fan", 0, SensorType.Fan, this, settings);
_coreVoltage = new Sensor("GPU Core", 0, SensorType.Voltage, this, settings);
_memoryVoltage = new Sensor("GPU Memory", 1, SensorType.Voltage, this, settings);
_socVoltage = new Sensor("GPU SoC", 2, SensorType.Voltage, this, settings);
_coreLoad = new Sensor("GPU Core", 0, SensorType.Load, this, settings);
_memoryLoad = new Sensor("GPU Memory", 1, SensorType.Load, this, settings);
_controlSensor = new Sensor("GPU Fan", 0, SensorType.Control, this, settings);
_powerCore = new Sensor("GPU Core", 0, SensorType.Power, this, settings);
_powerPpt = new Sensor("GPU PPT", 1, SensorType.Power, this, settings);
_powerSoC = new Sensor("GPU SoC", 2, SensorType.Power, this, settings);
_powerTotal = new Sensor("GPU Package", 3, SensorType.Power, this, settings);
_fullscreenFps = new Sensor("Fullscreen FPS", 0, SensorType.Factor, this, settings);
_memoryUsed = new Sensor("GPU Memory Used", 0, SensorType.SmallData, this, settings);
_memoryFree = new Sensor("GPU Memory Free", 1, SensorType.SmallData, this, settings);
_memoryTotal = new Sensor("GPU Memory Total", 2, SensorType.SmallData, this, settings);
if (!Software.OperatingSystem.IsUnix)
{
string[] deviceIds = D3DDisplayDevice.GetDeviceIdentifiers();
if (deviceIds != null)
{
foreach (string deviceId in deviceIds)
{
string actualDeviceId = D3DDisplayDevice.GetActualDeviceIdentifier(deviceId);
if ((actualDeviceId.IndexOf(adapterInfo.PNPString, StringComparison.OrdinalIgnoreCase) != -1 ||
adapterInfo.PNPString.IndexOf(actualDeviceId, StringComparison.OrdinalIgnoreCase) != -1) &&
D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
_d3dDeviceId = deviceId;
int nodeSensorIndex = 2;
int memorySensorIndex = 3;
_gpuDedicatedMemoryUsage = new Sensor("D3D Dedicated Memory Used", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuDedicatedMemoryFree = new Sensor("D3D Dedicated Memory Free", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuDedicatedMemoryTotal = new Sensor("D3D Dedicated Memory Total", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuSharedMemoryUsage = new Sensor("D3D Shared Memory Used", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuSharedMemoryFree = new Sensor("D3D Shared Memory Free", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuSharedMemoryTotal = new Sensor("D3D Shared Memory Total", memorySensorIndex++, SensorType.SmallData, this, settings);
_gpuNodeUsage = new Sensor[deviceInfo.Nodes.Length];
_gpuNodeUsagePrevValue = new long[deviceInfo.Nodes.Length];
_gpuNodeUsagePrevTick = new DateTime[deviceInfo.Nodes.Length];
foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes.OrderBy(x => x.Name))
{
_gpuNodeUsage[node.Id] = new Sensor(node.Name, nodeSensorIndex++, SensorType.Load, this, settings);
_gpuNodeUsagePrevValue[node.Id] = node.RunningTime;
_gpuNodeUsagePrevTick[node.Id] = node.QueryTime;
}
break;
}
}
}
}
AtiAdlxx.ADLMemoryInfoX4 memoryInfo = new();
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_MemoryInfoX4_Get)) &&
AtiAdlxx.ADL2_Adapter_MemoryInfoX4_Get(_context, _adapterInfo.AdapterIndex, out memoryInfo) == AtiAdlxx.ADLStatus.ADL_OK)
{
_memoryTotal.Value = memoryInfo.iMemorySize / 1024 / 1024;
ActivateSensor(_memoryTotal);
}
int supported = 0;
int enabled = 0;
int version = 0;
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_FrameMetrics_Caps)) &&
AtiAdlxx.ADL2_Adapter_FrameMetrics_Caps(_context, _adapterInfo.AdapterIndex, ref supported) == AtiAdlxx.ADLStatus.ADL_OK &&
supported == AtiAdlxx.ADL_TRUE &&
AtiAdlxx.ADL2_Adapter_FrameMetrics_Start(_context, _adapterInfo.AdapterIndex, 0) == AtiAdlxx.ADLStatus.ADL_OK)
{
_frameMetricsStarted = true;
_fullscreenFps.Value = -1;
ActivateSensor(_fullscreenFps);
}
if (AtiAdlxx.UsePmLogForFamily(_adlGcnInfo.ASICFamilyId) &&
AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_PMLog_Support_Get)) &&
AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Device_PMLog_Device_Create)) &&
AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_PMLog_Start)))
{
AtiAdlxx.ADLPMLogStartInput _adlPMLogStartInput = new();
_adlPMLogSupportInfo = new();
_adlPMLogStartOutput = new AtiAdlxx.ADLPMLogStartOutput();
_adlPMLogStartInput.usSensors = new ushort[AtiAdlxx.ADL_PMLOG_MAX_SENSORS];
if (_device == 0 &&
AtiAdlxx.ADLStatus.ADL_OK == AtiAdlxx.ADL2_Device_PMLog_Device_Create(_context, _adapterInfo.AdapterIndex, ref _device) &&
AtiAdlxx.ADLStatus.ADL_OK == AtiAdlxx.ADL2_Adapter_PMLog_Support_Get(_context, _adapterInfo.AdapterIndex, ref _adlPMLogSupportInfo))
{
int i = 0;
while (_adlPMLogSupportInfo.usSensors[i] != (ushort)AtiAdlxx.ADLPMLogSensors.ADL_SENSOR_MAXTYPES)
{
_adlPMLogStartInput.usSensors[i] = _adlPMLogSupportInfo.usSensors[i];
i++;
}
_adlPMLogStartInput.usSensors[i] = (ushort)AtiAdlxx.ADLPMLogSensors.ADL_SENSOR_MAXTYPES;
_adlPMLogStartInput.ulSampleRate = _pmLogSampleRate;
if (AtiAdlxx.ADL2_Adapter_PMLog_Start(_context,
adapterInfo.AdapterIndex,
ref _adlPMLogStartInput,
ref _adlPMLogStartOutput,
_device) == AtiAdlxx.ADLStatus.ADL_OK)
{
_pmLogStarted = true;
}
}
}
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Overdrive_Caps)) &&
AtiAdlxx.ADL2_Overdrive_Caps(_context, _adapterInfo.AdapterIndex, ref supported, ref enabled, ref version) == AtiAdlxx.ADLStatus.ADL_OK)
{
_overdriveApiSupported = supported == AtiAdlxx.ADL_TRUE;
_currentOverdriveApiLevel = version;
}
else
{
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Overdrive6_Capabilities_Get)))
{
AtiAdlxx.ADLOD6Capabilities capabilities = new();
if (AtiAdlxx.ADL2_Overdrive6_Capabilities_Get(_context, _adapterInfo.AdapterIndex, ref capabilities) == AtiAdlxx.ADLStatus.ADL_OK && capabilities.iCapabilities > 0)
{
_overdriveApiSupported = true;
_currentOverdriveApiLevel = 6;
}
}
if (!_overdriveApiSupported)
{
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Overdrive5_ODParameters_Get)) &&
AtiAdlxx.ADL2_Overdrive5_ODParameters_Get(_context, _adapterInfo.AdapterIndex, out AtiAdlxx.ADLODParameters p) == AtiAdlxx.ADLStatus.ADL_OK &&
p.iActivityReportingSupported > 0)
{
_overdriveApiSupported = true;
_currentOverdriveApiLevel = 5;
}
else
{
_currentOverdriveApiLevel = -1;
}
}
}
AtiAdlxx.ADLFanSpeedInfo fanSpeedInfo = new();
if (AtiAdlxx.ADL2_Overdrive5_FanSpeedInfo_Get(_context, _adapterInfo.AdapterIndex, 0, ref fanSpeedInfo) != AtiAdlxx.ADLStatus.ADL_OK)
{
fanSpeedInfo.iMaxPercent = 100;
fanSpeedInfo.iMinPercent = 0;
}
_fanControl = new Control(_controlSensor, settings, fanSpeedInfo.iMinPercent, fanSpeedInfo.iMaxPercent);
_fanControl.ControlModeChanged += ControlModeChanged;
_fanControl.SoftwareControlValueChanged += SoftwareControlValueChanged;
ControlModeChanged(_fanControl);
_controlSensor.Control = _fanControl;
Update();
}
public int BusNumber { get; }
/// <inheritdoc />
public override string DeviceId => _adapterInfo.PNPString;
public int DeviceNumber { get; }
public override HardwareType HardwareType
{
get { return HardwareType.GpuAmd; }
}
private void SoftwareControlValueChanged(IControl control)
{
if (control.ControlMode == ControlMode.Software)
{
AtiAdlxx.ADLFanSpeedValue fanSpeedValue = new()
{
iSpeedType = AtiAdlxx.ADL_DL_FANCTRL_SPEED_TYPE_PERCENT,
iFlags = AtiAdlxx.ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED,
iFanSpeed = (int)control.SoftwareValue
};
AtiAdlxx.ADL2_Overdrive5_FanSpeed_Set(_context, _adapterInfo.AdapterIndex, 0, ref fanSpeedValue);
}
}
private void ControlModeChanged(IControl control)
{
switch (control.ControlMode)
{
case ControlMode.Undefined:
return;
case ControlMode.Default:
SetDefaultFanSpeed();
break;
case ControlMode.Software:
SoftwareControlValueChanged(control);
break;
default:
return;
}
}
/// <summary>
/// Sets the default fan speed.
/// </summary>
private void SetDefaultFanSpeed()
{
AtiAdlxx.ADL2_Overdrive5_FanSpeedToDefault_Set(_context, _adapterInfo.AdapterIndex, 0);
}
public override void Update()
{
if (_d3dDeviceId != null && D3DDisplayDevice.GetDeviceInfoByIdentifier(_d3dDeviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
_gpuDedicatedMemoryTotal.Value = 1f * deviceInfo.GpuVideoMemoryLimit / 1024 / 1024;
_gpuDedicatedMemoryUsage.Value = 1f * deviceInfo.GpuDedicatedUsed / 1024 / 1024;
_gpuDedicatedMemoryFree.Value = _gpuDedicatedMemoryTotal.Value - _gpuDedicatedMemoryUsage.Value;
_gpuSharedMemoryUsage.Value = 1f * deviceInfo.GpuSharedUsed / 1024 / 1024;
_gpuSharedMemoryTotal.Value = 1f * deviceInfo.GpuSharedLimit / 1024 / 1024;
_gpuSharedMemoryFree.Value = _gpuSharedMemoryTotal.Value - _gpuSharedMemoryUsage.Value;
ActivateSensor(_gpuDedicatedMemoryTotal);
ActivateSensor(_gpuDedicatedMemoryFree);
ActivateSensor(_gpuDedicatedMemoryUsage);
ActivateSensor(_gpuSharedMemoryUsage);
ActivateSensor(_gpuSharedMemoryFree);
ActivateSensor(_gpuSharedMemoryTotal);
foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes)
{
long runningTimeDiff = node.RunningTime - _gpuNodeUsagePrevValue[node.Id];
long timeDiff = node.QueryTime.Ticks - _gpuNodeUsagePrevTick[node.Id].Ticks;
_gpuNodeUsage[node.Id].Value = 100f * runningTimeDiff / timeDiff;
_gpuNodeUsagePrevValue[node.Id] = node.RunningTime;
_gpuNodeUsagePrevTick[node.Id] = node.QueryTime;
ActivateSensor(_gpuNodeUsage[node.Id]);
}
}
int vramUsed = 0;
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_DedicatedVRAMUsage_Get)) &&
AtiAdlxx.ADL2_Adapter_DedicatedVRAMUsage_Get(_context, _adapterInfo.AdapterIndex, out vramUsed) == AtiAdlxx.ADLStatus.ADL_OK)
{
_memoryUsed.Value = vramUsed;
ActivateSensor(_memoryUsed);
}
if (_memoryTotal.Value > 0)
{
_memoryFree.Value = _memoryTotal.Value - _memoryUsed.Value;
ActivateSensor(_memoryFree);
}
if (_frameMetricsStarted)
{
float framesPerSecond = 0;
if (AtiAdlxx.ADL2_Adapter_FrameMetrics_Get(_context, _adapterInfo.AdapterIndex, 0, ref framesPerSecond) == AtiAdlxx.ADLStatus.ADL_OK)
{
_fullscreenFps.Value = framesPerSecond;
}
}
if (_overdriveApiSupported)
{
GetOD5Temperature(_temperatureCore);
GetOD5FanSpeed(AtiAdlxx.ADL_DL_FANCTRL_SPEED_TYPE_RPM, _fan);
GetOD5FanSpeed(AtiAdlxx.ADL_DL_FANCTRL_SPEED_TYPE_PERCENT, _controlSensor);
GetOD5CurrentActivity();
if (_currentOverdriveApiLevel >= 6)
{
GetOD6Power(AtiAdlxx.ADLODNCurrentPowerType.ODN_GPU_TOTAL_POWER, _powerTotal);
GetOD6Power(AtiAdlxx.ADLODNCurrentPowerType.ODN_GPU_PPT_POWER, _powerPpt);
GetOD6Power(AtiAdlxx.ADLODNCurrentPowerType.ODN_GPU_SOCKET_POWER, _powerSoC);
GetOD6Power(AtiAdlxx.ADLODNCurrentPowerType.ODN_GPU_CHIP_POWER, _powerCore);
}
if (_currentOverdriveApiLevel >= 7)
{
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.EDGE, _temperatureCore, -256, 0.001, false);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.MEM, _temperatureMemory);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.VRVDDC, _temperatureVddc);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.VRMVDD, _temperatureMvdd);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.LIQUID, _temperatureLiquid);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.PLX, _temperaturePlx);
GetODNTemperature(AtiAdlxx.ADLODNTemperatureType.HOTSPOT, _temperatureHotSpot);
}
}
if (_currentOverdriveApiLevel >= 8 || !_overdriveApiSupported)
{
_overdrive8LogExists = false;
AtiAdlxx.ADLPMLogDataOutput logDataOutput = new();
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_New_QueryPMLogData_Get)) &&
AtiAdlxx.ADL2_New_QueryPMLogData_Get(_context, _adapterInfo.AdapterIndex, ref logDataOutput) == AtiAdlxx.ADLStatus.ADL_OK)
{
_overdrive8LogExists = true;
}
AtiAdlxx.ADLPMLogData adlPMLogData = new();
if (_pmLogStarted)
{
adlPMLogData = (AtiAdlxx.ADLPMLogData)Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress, typeof(AtiAdlxx.ADLPMLogData));
}
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_CLK_GFXCLK, _coreClock, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_CLK_SOCCLK, _socClock);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_CLK_MEMCLK, _memoryClock, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_EDGE, _temperatureCore, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_MEM, _temperatureMemory, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_VRVDDC, _temperatureVddc, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_VRMVDD, _temperatureMvdd, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_LIQUID, _temperatureLiquid, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_PLX, _temperaturePlx, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_HOTSPOT, _temperatureHotSpot, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_TEMPERATURE_SOC, _temperatureSoC);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_FAN_RPM, _fan, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_FAN_PERCENTAGE, _controlSensor, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_GFX_VOLTAGE, _coreVoltage, 0.001f, false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_SOC_VOLTAGE, _socVoltage, 0.001f);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_MEM_VOLTAGE, _memoryVoltage, 0.001f);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_INFO_ACTIVITY_GFX, _coreLoad, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_INFO_ACTIVITY_MEM, _memoryLoad);
if (_adlGcnInfo.ASICFamilyId >= (int)AtiAdlxx.GCNFamilies.FAMILY_NV3 || !GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_ASIC_POWER, _powerTotal, reset: false))
{
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_BOARD_POWER, _powerTotal, reset: false);
}
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_GFX_POWER, _powerCore, reset: false);
GetAdlSensor(adlPMLogData, logDataOutput, AtiAdlxx.ADLPMLogSensors.ADL_PMLOG_SOC_POWER, _powerSoC, reset: false);
}
}
private bool IsSensorSupportedByPMLog(AtiAdlxx.ADLPMLogSensors sensorType)
{
if (!_pmLogStarted || (int)sensorType == 0)
return false;
for (int i = 0; i < AtiAdlxx.ADL_PMLOG_MAX_SENSORS; i++)
{
if (_adlPMLogSupportInfo.usSensors[i] == (int)sensorType)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets a sensor value.
/// </summary>
///
/// <param name="adlPMLogData">Current pmlog struct, used with pmlog-support/start.</param>
/// <param name="od8Log">Legacy pmlogdataoutput struct, used with ADL2_New_QueryPMLogData_Get.</param>
/// <param name="sensorType">Type of the sensor.</param>
/// <param name="sensor">The sensor.</param>
/// <param name="factor">The factor.</param>
/// <param name="reset">If set to <c>true</c>, resets the sensor value to <c>null</c>.</param>
/// <returns>true if sensor is supported, false otherwise</returns>
private bool GetAdlSensor(AtiAdlxx.ADLPMLogData adlPMLogData, AtiAdlxx.ADLPMLogDataOutput od8Log,
AtiAdlxx.ADLPMLogSensors sensorType, Sensor sensor, float factor = 1.0f, bool reset = true)
{
int i = (int)sensorType;
bool supportedByPMLog = IsSensorSupportedByPMLog(sensorType);
bool supportedByOD8 = _overdrive8LogExists && i < od8Log.sensors.Length && od8Log.sensors[i].supported != 0;
if (!supportedByPMLog && !supportedByOD8)
{
if (reset)
sensor.Value = null;
return false;
}
if (_pmLogStarted)
{
//check if ulLastUpdated is a valid number, avoid timezone issues with unspecified kind and 48h offset
DateTime now = new(DateTime.Now.Ticks, DateTimeKind.Unspecified);
if (adlPMLogData.ulLastUpdated == 0 ||
adlPMLogData.ulActiveSampleRate < 0 ||
adlPMLogData.ulActiveSampleRate > 86400000 ||
now.AddHours(48).ToFileTime() < (long)adlPMLogData.ulLastUpdated ||
now.AddHours(-48).ToFileTime() > (long)adlPMLogData.ulLastUpdated)
{
supportedByPMLog = false;
}
}
if (supportedByPMLog)
{
bool found = false;
if (adlPMLogData.ulValues != null)
{
for (int k = 0; k < adlPMLogData.ulValues.Length - 1; k += 2)
{
if (adlPMLogData.ulValues[k] == (ushort)AtiAdlxx.ADLPMLogSensors.ADL_SENSOR_MAXTYPES)
break;
if (adlPMLogData.ulValues[k] == i)
{
sensor.Value = adlPMLogData.ulValues[k + 1] * factor;
ActivateSensor(sensor);
found = true;
}
}
}
if (!found && reset)
{
sensor.Value = null;
}
}
else if (_overdrive8LogExists)
{
if (supportedByOD8)
{
sensor.Value = od8Log.sensors[i].value * factor;
ActivateSensor(sensor);
}
else if (reset)
{
sensor.Value = null;
}
}
return true;
}
private void GetOD5CurrentActivity()
{
AtiAdlxx.ADLPMActivity adlpmActivity = new();
if (AtiAdlxx.ADL2_Overdrive5_CurrentActivity_Get(_context, _adapterInfo.AdapterIndex, ref adlpmActivity) == AtiAdlxx.ADLStatus.ADL_OK)
{
if (adlpmActivity.iEngineClock > 0)
{
_coreClock.Value = 0.01f * adlpmActivity.iEngineClock;
ActivateSensor(_coreClock);
}
else
{
_coreClock.Value = null;
}
if (adlpmActivity.iMemoryClock > 0)
{
_memoryClock.Value = 0.01f * adlpmActivity.iMemoryClock;
ActivateSensor(_memoryClock);
}
else
{
_memoryClock.Value = null;
}
if (adlpmActivity.iVddc > 0)
{
_coreVoltage.Value = 0.001f * adlpmActivity.iVddc;
ActivateSensor(_coreVoltage);
}
else
{
_coreVoltage.Value = null;
}
_coreLoad.Value = Math.Min(adlpmActivity.iActivityPercent, 100);
ActivateSensor(_coreLoad);
}
else
{
_coreClock.Value = null;
_memoryClock.Value = null;
_coreVoltage.Value = null;
_coreLoad.Value = null;
}
}
private void GetOD5FanSpeed(int speedType, Sensor sensor)
{
AtiAdlxx.ADLFanSpeedValue fanSpeedValue = new() { iSpeedType = speedType };
if (AtiAdlxx.ADL2_Overdrive5_FanSpeed_Get(_context, _adapterInfo.AdapterIndex, 0, ref fanSpeedValue) == AtiAdlxx.ADLStatus.ADL_OK)
{
sensor.Value = fanSpeedValue.iFanSpeed;
ActivateSensor(sensor);
}
else
{
sensor.Value = null;
}
}
private void GetOD5Temperature(Sensor temperatureCore)
{
AtiAdlxx.ADLTemperature temperature = new();
if (AtiAdlxx.ADL2_Overdrive5_Temperature_Get(_context, _adapterInfo.AdapterIndex, 0, ref temperature) == AtiAdlxx.ADLStatus.ADL_OK)
{
temperatureCore.Value = 0.001f * temperature.iTemperature;
ActivateSensor(temperatureCore);
}
else
{
temperatureCore.Value = null;
}
}
/// <summary>
/// Gets the OverdriveN temperature.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="sensor">The sensor.</param>
/// <param name="minTemperature">The minimum temperature.</param>
/// <param name="scale">The scale.</param>
/// <param name="reset">If set to <c>true</c>, resets the sensor value to <c>null</c>.</param>
private void GetODNTemperature(AtiAdlxx.ADLODNTemperatureType type, Sensor sensor, double minTemperature = -256, double scale = 1, bool reset = true)
{
// If a sensor isn't available, some cards report 54000 degrees C.
// 110C is expected for Navi, so 256C should be enough to use as a maximum.
int maxTemperature = (int)(256 / scale);
minTemperature = (int)(minTemperature / scale);
int temperature = 0;
if (AtiAdlxx.ADL2_OverdriveN_Temperature_Get(_context, _adapterInfo.AdapterIndex, type, ref temperature) == AtiAdlxx.ADLStatus.ADL_OK &&
temperature >= minTemperature &&
temperature <= maxTemperature)
{
sensor.Value = (float)(scale * temperature);
ActivateSensor(sensor);
}
else if (reset)
{
sensor.Value = null;
}
}
/// <summary>
/// Gets the Overdrive6 power.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="sensor">The sensor.</param>
private void GetOD6Power(AtiAdlxx.ADLODNCurrentPowerType type, Sensor sensor)
{
int powerOf8 = 0;
if (AtiAdlxx.ADL2_Overdrive6_CurrentPower_Get(_context, _adapterInfo.AdapterIndex, type, ref powerOf8) == AtiAdlxx.ADLStatus.ADL_OK)
{
sensor.Value = powerOf8 >> 8;
ActivateSensor(sensor);
}
else
{
sensor.Value = null;
}
}
public override void Close()
{
_fanControl.ControlModeChanged -= ControlModeChanged;
_fanControl.SoftwareControlValueChanged -= SoftwareControlValueChanged;
if (_fanControl.ControlMode != ControlMode.Undefined)
SetDefaultFanSpeed();
if (_frameMetricsStarted)
AtiAdlxx.ADL2_Adapter_FrameMetrics_Stop(_context, _adapterInfo.AdapterIndex, 0);
if (_pmLogStarted && _device != 0)
{
AtiAdlxx.ADL2_Adapter_PMLog_Stop(_context, _adapterInfo.AdapterIndex, _device);
}
if (_device != 0)
{
AtiAdlxx.ADL2_Device_PMLog_Device_Destroy(_context, _device);
}
base.Close();
}
public override string GetReport()
{
var r = new StringBuilder();
r.AppendLine("AMD GPU");
r.AppendLine();
r.Append("AdapterIndex: ");
r.AppendLine(_adapterInfo.AdapterIndex.ToString(CultureInfo.InvariantCulture));
r.AppendLine();
r.AppendLine("Overdrive Caps");
r.AppendLine();
try
{
int supported = 0;
int enabled = 0;
int version = 0;
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive_Caps(_context, _adapterInfo.AdapterIndex, ref supported, ref enabled, ref version);
r.Append(" Status: ");
r.AppendLine(status.ToString());
r.Append(" Supported: ");
r.AppendLine(supported.ToString(CultureInfo.InvariantCulture));
r.Append(" Enabled: ");
r.AppendLine(enabled.ToString(CultureInfo.InvariantCulture));
r.Append(" Version: ");
r.AppendLine(version.ToString(CultureInfo.InvariantCulture));
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
r.AppendLine("Overdrive5 Parameters");
r.AppendLine();
try
{
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive5_ODParameters_Get(_context, _adapterInfo.AdapterIndex, out AtiAdlxx.ADLODParameters p);
r.Append(" Status: ");
r.AppendLine(status.ToString());
r.AppendFormat(" NumberOfPerformanceLevels: {0}{1}", p.iNumberOfPerformanceLevels, Environment.NewLine);
r.AppendFormat(" ActivityReportingSupported: {0}{1}", p.iActivityReportingSupported, Environment.NewLine);
r.AppendFormat(" DiscretePerformanceLevels: {0}{1}", p.iDiscretePerformanceLevels, Environment.NewLine);
r.AppendFormat(" EngineClock.Min: {0}{1}", p.sEngineClock.iMin, Environment.NewLine);
r.AppendFormat(" EngineClock.Max: {0}{1}", p.sEngineClock.iMax, Environment.NewLine);
r.AppendFormat(" EngineClock.Step: {0}{1}", p.sEngineClock.iStep, Environment.NewLine);
r.AppendFormat(" MemoryClock.Min: {0}{1}", p.sMemoryClock.iMin, Environment.NewLine);
r.AppendFormat(" MemoryClock.Max: {0}{1}", p.sMemoryClock.iMax, Environment.NewLine);
r.AppendFormat(" MemoryClock.Step: {0}{1}", p.sMemoryClock.iStep, Environment.NewLine);
r.AppendFormat(" Vddc.Min: {0}{1}", p.sVddc.iMin, Environment.NewLine);
r.AppendFormat(" Vddc.Max: {0}{1}", p.sVddc.iMax, Environment.NewLine);
r.AppendFormat(" Vddc.Step: {0}{1}", p.sVddc.iStep, Environment.NewLine);
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
r.AppendLine("Overdrive5 Temperature");
r.AppendLine();
try
{
var adlt = new AtiAdlxx.ADLTemperature();
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive5_Temperature_Get(_context, _adapterInfo.AdapterIndex, 0, ref adlt);
r.Append(" Status: ");
r.AppendLine(status.ToString());
r.AppendFormat(" Value: {0}{1}", 0.001f * adlt.iTemperature, Environment.NewLine);
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
r.AppendLine("Overdrive5 FanSpeed");
r.AppendLine();
try
{
var adlf = new AtiAdlxx.ADLFanSpeedValue { iSpeedType = AtiAdlxx.ADL_DL_FANCTRL_SPEED_TYPE_RPM };
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive5_FanSpeed_Get(_context, _adapterInfo.AdapterIndex, 0, ref adlf);
r.Append(" Status RPM: ");
r.AppendLine(status.ToString());
r.AppendFormat(" Value RPM: {0}{1}", adlf.iFanSpeed, Environment.NewLine);
adlf.iSpeedType = AtiAdlxx.ADL_DL_FANCTRL_SPEED_TYPE_PERCENT;
status = AtiAdlxx.ADL2_Overdrive5_FanSpeed_Get(_context, _adapterInfo.AdapterIndex, 0, ref adlf);
r.Append(" Status Percent: ");
r.AppendLine(status.ToString());
r.AppendFormat(" Value Percent: {0}{1}", adlf.iFanSpeed, Environment.NewLine);
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
r.AppendLine("Overdrive5 CurrentActivity");
r.AppendLine();
try
{
var adlp = new AtiAdlxx.ADLPMActivity();
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive5_CurrentActivity_Get(_context, _adapterInfo.AdapterIndex, ref adlp);
r.Append(" Status: ");
r.AppendLine(status.ToString());
r.AppendFormat(" EngineClock: {0}{1}", 0.01f * adlp.iEngineClock, Environment.NewLine);
r.AppendFormat(" MemoryClock: {0}{1}", 0.01f * adlp.iMemoryClock, Environment.NewLine);
r.AppendFormat(" Vddc: {0}{1}", 0.001f * adlp.iVddc, Environment.NewLine);
r.AppendFormat(" ActivityPercent: {0}{1}", adlp.iActivityPercent, Environment.NewLine);
r.AppendFormat(" CurrentPerformanceLevel: {0}{1}", adlp.iCurrentPerformanceLevel, Environment.NewLine);
r.AppendFormat(" CurrentBusSpeed: {0}{1}", adlp.iCurrentBusSpeed, Environment.NewLine);
r.AppendFormat(" CurrentBusLanes: {0}{1}", adlp.iCurrentBusLanes, Environment.NewLine);
r.AppendFormat(" MaximumBusLanes: {0}{1}", adlp.iMaximumBusLanes, Environment.NewLine);
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
if (_context != IntPtr.Zero)
{
r.AppendLine("Overdrive6 CurrentPower");
r.AppendLine();
try
{
int power = 0;
for (int i = 0; i < 4; i++)
{
string pt = ((AtiAdlxx.ADLODNCurrentPowerType)i).ToString();
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_Overdrive6_CurrentPower_Get(_context, _adapterInfo.AdapterIndex, (AtiAdlxx.ADLODNCurrentPowerType)i, ref power);
r.AppendFormat(" Power[{0}].Status: {1}{2}", pt, status.ToString(), Environment.NewLine);
r.AppendFormat(" Power[{0}].Value: {1}{2}", pt, power * (1.0f / 0xFF), Environment.NewLine);
}
}
catch (EntryPointNotFoundException)
{
r.AppendLine(" Status: Entry point not found");
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
}
if (_context != IntPtr.Zero)
{
r.AppendLine("OverdriveN Temperature");
r.AppendLine();
try
{
for (int i = 1; i < 8; i++)
{
int temperature = 0;
string tt = ((AtiAdlxx.ADLODNTemperatureType)i).ToString();
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_OverdriveN_Temperature_Get(_context, _adapterInfo.AdapterIndex, (AtiAdlxx.ADLODNTemperatureType)i, ref temperature);
r.AppendFormat(" Temperature[{0}].Status: {1}{2}", tt, status.ToString(), Environment.NewLine);
r.AppendFormat(" Temperature[{0}].Value: {1}{2}", tt, 0.001f * temperature, Environment.NewLine);
}
}
catch (EntryPointNotFoundException)
{
r.AppendLine(" Status: Entry point not found");
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
}
if (_context != IntPtr.Zero)
{
r.AppendLine("OverdriveN Performance Status");
r.AppendLine();
try
{
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_OverdriveN_PerformanceStatus_Get(_context, _adapterInfo.AdapterIndex, out AtiAdlxx.ADLODNPerformanceStatus ps);
r.Append(" Status: ");
r.AppendLine(status.ToString());
r.AppendFormat(" CoreClock: {0}{1}", ps.iCoreClock, Environment.NewLine);
r.AppendFormat(" MemoryClock: {0}{1}", ps.iMemoryClock, Environment.NewLine);
r.AppendFormat(" DCEFClock: {0}{1}", ps.iDCEFClock, Environment.NewLine);
r.AppendFormat(" GFXClock: {0}{1}", ps.iGFXClock, Environment.NewLine);
r.AppendFormat(" UVDClock: {0}{1}", ps.iUVDClock, Environment.NewLine);
r.AppendFormat(" VCEClock: {0}{1}", ps.iVCEClock, Environment.NewLine);
r.AppendFormat(" GPUActivityPercent: {0}{1}", ps.iGPUActivityPercent, Environment.NewLine);
r.AppendFormat(" CurrentCorePerformanceLevel: {0}{1}", ps.iCurrentCorePerformanceLevel, Environment.NewLine);
r.AppendFormat(" CurrentMemoryPerformanceLevel: {0}{1}", ps.iCurrentMemoryPerformanceLevel, Environment.NewLine);
r.AppendFormat(" CurrentDCEFPerformanceLevel: {0}{1}", ps.iCurrentDCEFPerformanceLevel, Environment.NewLine);
r.AppendFormat(" CurrentGFXPerformanceLevel: {0}{1}", ps.iCurrentGFXPerformanceLevel, Environment.NewLine);
r.AppendFormat(" UVDPerformanceLevel: {0}{1}", ps.iUVDPerformanceLevel, Environment.NewLine);
r.AppendFormat(" VCEPerformanceLevel: {0}{1}", ps.iVCEPerformanceLevel, Environment.NewLine);
r.AppendFormat(" CurrentBusSpeed: {0}{1}", ps.iCurrentBusSpeed, Environment.NewLine);
r.AppendFormat(" CurrentBusLanes: {0}{1}", ps.iCurrentBusLanes, Environment.NewLine);
r.AppendFormat(" MaximumBusLanes: {0}{1}", ps.iMaximumBusLanes, Environment.NewLine);
r.AppendFormat(" VDDC: {0}{1}", ps.iVDDC, Environment.NewLine);
r.AppendFormat(" VDDCI: {0}{1}", ps.iVDDCI, Environment.NewLine);
}
catch (EntryPointNotFoundException)
{
r.AppendLine(" Status: Entry point not found");
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
}
if (_context != IntPtr.Zero)
{
r.AppendLine("Performance Metrics");
r.AppendLine();
try
{
var data = new AtiAdlxx.ADLPMLogDataOutput();
AtiAdlxx.ADLStatus status = AtiAdlxx.ADL2_New_QueryPMLogData_Get(_context, _adapterInfo.AdapterIndex, ref data);
AtiAdlxx.ADLPMLogData adlPMLogData = new();
if (_pmLogStarted)
{
adlPMLogData = (AtiAdlxx.ADLPMLogData)Marshal.PtrToStructure(_adlPMLogStartOutput.pLoggingAddress, typeof(AtiAdlxx.ADLPMLogData));
}
foreach (AtiAdlxx.ADLPMLogSensors sensorType in Enum.GetValues(typeof(AtiAdlxx.ADLPMLogSensors)))
{
int i = (int)sensorType;
if (i == 0)
continue;
bool supported = false;
string st = ((AtiAdlxx.ADLPMLogSensors)i).ToString();
if (IsSensorSupportedByPMLog(sensorType))
{
for (int k = 0; k < adlPMLogData.ulValues.Length - 1; k += 2)
{
if (adlPMLogData.ulValues[k] == (ushort)AtiAdlxx.ADLPMLogSensors.ADL_SENSOR_MAXTYPES)
break;
if (adlPMLogData.ulValues[k] == i)
{
r.AppendFormat(" Sensor[{0}].Value: {1}{2}", st, adlPMLogData.ulValues[k + 1], Environment.NewLine);
supported = true;
}
}
}
else if (_overdrive8LogExists && i < data.sensors.Length && data.sensors[i].supported != 0)
{
r.AppendFormat(" Sensor[{0}].Value: {1}{2}", st, data.sensors[i].value, Environment.NewLine);
supported = true;
}
r.AppendFormat(" Sensor[{0}].Supported: {1}{2}", st, supported, Environment.NewLine);
}
}
catch (EntryPointNotFoundException)
{
r.AppendLine(" Status: Entry point not found");
}
catch (Exception e)
{
r.AppendLine(" Status: " + e.Message);
}
r.AppendLine();
}
if (_d3dDeviceId != null)
{
r.AppendLine("D3D");
r.AppendLine();
r.AppendLine(" Id: " + _d3dDeviceId);
}
return r.ToString();
}
}

View File

@@ -0,0 +1,182 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Gpu;
internal class AmdGpuGroup : IGroup
{
private readonly IntPtr _context = IntPtr.Zero;
private readonly List<AmdGpu> _hardware = new();
private readonly StringBuilder _report = new();
private readonly AtiAdlxx.ADLStatus _status;
public AmdGpuGroup(ISettings settings)
{
try
{
_status = AtiAdlxx.ADL2_Main_Control_Create(AtiAdlxx.Main_Memory_Alloc, 1, ref _context);
_report.AppendLine("AMD Display Library");
_report.AppendLine();
_report.Append("Status: ");
_report.AppendLine(_status == AtiAdlxx.ADLStatus.ADL_OK ? "OK" : _status.ToString());
_report.AppendLine();
if (_status == AtiAdlxx.ADLStatus.ADL_OK)
{
int numberOfAdapters = 0;
AtiAdlxx.ADL2_Adapter_NumberOfAdapters_Get(_context, ref numberOfAdapters);
_report.Append("Number of adapters: ");
_report.AppendLine(numberOfAdapters.ToString(CultureInfo.InvariantCulture));
_report.AppendLine();
if (numberOfAdapters > 0)
{
List<AmdGpu> potentialHardware = new();
AtiAdlxx.ADLAdapterInfo[] adapterInfo = new AtiAdlxx.ADLAdapterInfo[numberOfAdapters];
if (AtiAdlxx.ADL2_Adapter_AdapterInfo_Get(ref _context, adapterInfo) == AtiAdlxx.ADLStatus.ADL_OK)
{
for (int i = 0; i < numberOfAdapters; i++)
{
uint device = 0;
AtiAdlxx.ADLGcnInfo gcnInfo = new();
AtiAdlxx.ADLPMLogSupportInfo pmLogSupportInfo = new();
AtiAdlxx.ADL2_Adapter_Active_Get(_context, adapterInfo[i].AdapterIndex, out int isActive);
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_GcnAsicInfo_Get)))
{
AtiAdlxx.ADL2_GcnAsicInfo_Get(_context, adapterInfo[i].AdapterIndex, ref gcnInfo);
}
int adapterId = -1;
if (AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_ID_Get)))
AtiAdlxx.ADL2_Adapter_ID_Get(_context, adapterInfo[i].AdapterIndex, out adapterId);
_report.Append("AdapterIndex: ");
_report.AppendLine(i.ToString(CultureInfo.InvariantCulture));
_report.Append("isActive: ");
_report.AppendLine(isActive.ToString(CultureInfo.InvariantCulture));
_report.Append("AdapterName: ");
_report.AppendLine(adapterInfo[i].AdapterName);
_report.Append("UDID: ");
_report.AppendLine(adapterInfo[i].UDID);
_report.Append("PNPString: ");
_report.AppendLine(adapterInfo[i].PNPString);
_report.Append("Present: ");
_report.AppendLine(adapterInfo[i].Present.ToString(CultureInfo.InvariantCulture));
_report.Append("VendorID: 0x");
_report.AppendLine(adapterInfo[i].VendorID.ToString("X", CultureInfo.InvariantCulture));
_report.Append("BusNumber: ");
_report.AppendLine(adapterInfo[i].BusNumber.ToString(CultureInfo.InvariantCulture));
_report.Append("DeviceNumber: ");
_report.AppendLine(adapterInfo[i].DeviceNumber.ToString(CultureInfo.InvariantCulture));
_report.Append("FunctionNumber: ");
_report.AppendLine(adapterInfo[i].FunctionNumber.ToString(CultureInfo.InvariantCulture));
_report.Append("AdapterID: 0x");
_report.AppendLine(adapterId.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine("Family: " + gcnInfo.ASICFamilyId);
int sensorsSupported = 0;
if (AtiAdlxx.UsePmLogForFamily(gcnInfo.ASICFamilyId) &&
AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Adapter_PMLog_Support_Get)) &&
AtiAdlxx.ADL_Method_Exists(nameof(AtiAdlxx.ADL2_Device_PMLog_Device_Create)))
{
if (AtiAdlxx.ADLStatus.ADL_OK == AtiAdlxx.ADL2_Device_PMLog_Device_Create(_context, adapterInfo[i].AdapterIndex, ref device) &&
AtiAdlxx.ADLStatus.ADL_OK == AtiAdlxx.ADL2_Adapter_PMLog_Support_Get(_context, adapterInfo[i].AdapterIndex, ref pmLogSupportInfo))
{
int k = 0;
while (pmLogSupportInfo.usSensors[k] != (ushort)AtiAdlxx.ADLPMLogSensors.ADL_SENSOR_MAXTYPES)
{
k++;
}
sensorsSupported = k;
}
_report.AppendLine("Sensors Supported: " + sensorsSupported);
if (device != 0)
{
AtiAdlxx.ADL2_Device_PMLog_Device_Destroy(_context, device);
}
}
if (!string.IsNullOrEmpty(adapterInfo[i].UDID) && adapterInfo[i].VendorID == AtiAdlxx.ATI_VENDOR_ID &&
!IsAlreadyAdded(adapterInfo[i].BusNumber, adapterInfo[i].DeviceNumber))
{
if (sensorsSupported > 0)
{
_hardware.Add(new AmdGpu(_context, adapterInfo[i], gcnInfo, settings));
}
else
{
potentialHardware.Add(new AmdGpu(_context, adapterInfo[i], gcnInfo, settings));
}
}
_report.AppendLine();
}
}
foreach (IGrouping<string, AmdGpu> amdGpus in potentialHardware.GroupBy(x => $"{x.BusNumber}-{x.DeviceNumber}"))
{
AmdGpu amdGpu = amdGpus.OrderByDescending(x => x.Sensors.Length).FirstOrDefault();
if (amdGpu != null && !IsAlreadyAdded(amdGpu.BusNumber, amdGpu.DeviceNumber))
_hardware.Add(amdGpu);
}
}
}
}
catch (DllNotFoundException)
{ }
catch (EntryPointNotFoundException e)
{
_report.AppendLine();
_report.AppendLine(e.ToString());
_report.AppendLine();
}
}
private bool IsAlreadyAdded(int busNumber, int deviceNumber)
{
foreach (AmdGpu g in _hardware)
{
if (g.BusNumber == busNumber && g.DeviceNumber == deviceNumber)
{
return true;
}
}
return false;
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
return _report.ToString();
}
public void Close()
{
try
{
foreach (AmdGpu gpu in _hardware)
gpu.Close();
if (_status == AtiAdlxx.ADLStatus.ADL_OK && _context != IntPtr.Zero)
AtiAdlxx.ADL2_Main_Control_Destroy(_context);
}
catch (Exception)
{ }
}
}

View File

@@ -0,0 +1,23 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Gpu;
public abstract class GenericGpu : Hardware
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericGpu" /> class.
/// </summary>
/// <param name="name">Component name.</param>
/// <param name="identifier">Identifier that will be assigned to the device. Based on <see cref="Identifier" /></param>
/// <param name="settings">Additional settings passed by the <see cref="IComputer" />.</param>
protected GenericGpu(string name, Identifier identifier, ISettings settings) : base(name, identifier, settings)
{ }
/// <summary>
/// Gets the device identifier.
/// </summary>
public abstract string DeviceId { get; }
}

View File

@@ -0,0 +1,86 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.Hardware.Cpu;
namespace LibreHardwareMonitor.Hardware.Gpu;
internal class IntelGpuGroup : IGroup
{
private readonly List<Hardware> _hardware = new();
private readonly StringBuilder _report = new();
public IntelGpuGroup(List<IntelCpu> intelCpus, ISettings settings)
{
if (!Software.OperatingSystem.IsUnix && intelCpus?.Count > 0)
{
_report.AppendLine("Intel GPU (D3D)");
_report.AppendLine();
string[] ids = D3DDisplayDevice.GetDeviceIdentifiers();
_report.Append("Number of adapters: ");
_report.AppendLine(ids.Length.ToString(CultureInfo.InvariantCulture));
_report.AppendLine();
for (int i = 0; i < ids.Length; i++)
{
string deviceId = ids[i];
bool isIntel = deviceId.IndexOf("VEN_8086", StringComparison.Ordinal) != -1;
_report.Append("AdapterIndex: ");
_report.AppendLine(i.ToString(CultureInfo.InvariantCulture));
_report.Append("DeviceId: ");
_report.AppendLine(deviceId);
_report.Append("IsIntel: ");
_report.AppendLine(isIntel.ToString(CultureInfo.InvariantCulture));
if (isIntel && D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
_report.Append("GpuSharedLimit: ");
_report.AppendLine(deviceInfo.GpuSharedLimit.ToString(CultureInfo.InvariantCulture));
_report.Append("GpuSharedUsed: ");
_report.AppendLine(deviceInfo.GpuSharedUsed.ToString(CultureInfo.InvariantCulture));
_report.Append("GpuSharedMax: ");
_report.AppendLine(deviceInfo.GpuSharedMax.ToString(CultureInfo.InvariantCulture));
_report.Append("GpuDedicatedLimit: ");
_report.AppendLine(deviceInfo.GpuDedicatedLimit.ToString(CultureInfo.InvariantCulture));
_report.Append("GpuDedicatedUsed: ");
_report.AppendLine(deviceInfo.GpuDedicatedUsed.ToString(CultureInfo.InvariantCulture));
_report.Append("GpuDedicatedMax: ");
_report.AppendLine(deviceInfo.GpuDedicatedMax.ToString(CultureInfo.InvariantCulture));
_report.Append("Integrated: ");
_report.AppendLine(deviceInfo.Integrated.ToString(CultureInfo.InvariantCulture));
if (deviceInfo.Integrated)
{
// It may seem strange to only use the first cpu here, but in-case we have a multi cpu system with integrated graphics (does that exist?),
// we would pick up the multiple device identifiers above and would add one instance for each CPU.
_hardware.Add(new IntelIntegratedGpu(intelCpus[0], deviceId, deviceInfo, settings));
}
}
_report.AppendLine();
}
}
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
return _report.ToString();
}
public void Close()
{
foreach (Hardware gpu in _hardware)
gpu.Close();
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Globalization;
using System.Linq;
using Microsoft.Win32;
namespace LibreHardwareMonitor.Hardware.Gpu;
internal class IntelIntegratedGpu : GenericGpu
{
private const uint MSR_PP1_ENERGY_STATUS = 0x641;
private readonly Sensor _dedicatedMemoryUsage;
private readonly Sensor _sharedMemoryLimit;
private readonly Sensor _sharedMemoryFree;
private readonly string _deviceId;
private readonly float _energyUnitMultiplier;
private readonly Sensor[] _nodeUsage;
private readonly DateTime[] _nodeUsagePrevTick;
private readonly long[] _nodeUsagePrevValue;
private readonly Sensor _powerSensor;
private readonly Sensor _sharedMemoryUsage;
private uint _lastEnergyConsumed;
private DateTime _lastEnergyTime;
public IntelIntegratedGpu(Cpu.IntelCpu intelCpu, string deviceId, D3DDisplayDevice.D3DDeviceInfo deviceInfo, ISettings settings)
: base(GetName(deviceId),
new Identifier("gpu-intel-integrated", deviceId.ToString(CultureInfo.InvariantCulture)),
settings)
{
_deviceId = deviceId;
int memorySensorIndex = 0;
if (deviceInfo.GpuDedicatedLimit > 0)
{
_dedicatedMemoryUsage = new Sensor("D3D Dedicated Memory Used", memorySensorIndex++, SensorType.SmallData, this, settings);
}
_sharedMemoryUsage = new Sensor("D3D Shared Memory Used", memorySensorIndex++, SensorType.SmallData, this, settings);
if (deviceInfo.GpuSharedLimit > 0)
{
_sharedMemoryFree = new Sensor("D3D Shared Memory Free", memorySensorIndex++, SensorType.SmallData, this, settings);
_sharedMemoryLimit = new Sensor("D3D Shared Memory Total", memorySensorIndex++, SensorType.SmallData, this, settings);
}
if (Ring0.ReadMsr(MSR_PP1_ENERGY_STATUS, out uint eax, out uint _))
{
_energyUnitMultiplier = intelCpu.EnergyUnitsMultiplier;
if (_energyUnitMultiplier != 0)
{
_lastEnergyTime = DateTime.UtcNow;
_lastEnergyConsumed = eax;
_powerSensor = new Sensor("GPU Power", 0, SensorType.Power, this, settings);
ActivateSensor(_powerSensor);
}
}
_nodeUsage = new Sensor[deviceInfo.Nodes.Length];
_nodeUsagePrevValue = new long[deviceInfo.Nodes.Length];
_nodeUsagePrevTick = new DateTime[deviceInfo.Nodes.Length];
int nodeSensorIndex = 0;
foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes.OrderBy(x => x.Name))
{
_nodeUsage[node.Id] = new Sensor(node.Name, nodeSensorIndex++, SensorType.Load, this, settings);
_nodeUsagePrevValue[node.Id] = node.RunningTime;
_nodeUsagePrevTick[node.Id] = node.QueryTime;
}
}
/// <inheritdoc />
public override string DeviceId => D3DDisplayDevice.GetActualDeviceIdentifier(_deviceId);
public override HardwareType HardwareType => HardwareType.GpuIntel;
public override void Update()
{
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(_deviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
if (_dedicatedMemoryUsage != null)
{
_dedicatedMemoryUsage.Value = 1f * deviceInfo.GpuDedicatedUsed / 1024 / 1024;
ActivateSensor(_dedicatedMemoryUsage);
}
if (_sharedMemoryLimit != null)
{
_sharedMemoryLimit.Value = 1f * deviceInfo.GpuSharedLimit / 1024 / 1024;
ActivateSensor(_sharedMemoryLimit);
if (_sharedMemoryUsage != null)
{
_sharedMemoryFree.Value = _sharedMemoryLimit.Value - _sharedMemoryUsage.Value;
ActivateSensor(_sharedMemoryFree);
}
}
_sharedMemoryUsage.Value = 1f * deviceInfo.GpuSharedUsed / 1024 / 1024;
ActivateSensor(_sharedMemoryUsage);
if (_powerSensor != null && Ring0.ReadMsr(MSR_PP1_ENERGY_STATUS, out uint eax, out uint _))
{
DateTime time = DateTime.UtcNow;
float deltaTime = (float)(time - _lastEnergyTime).TotalSeconds;
if (deltaTime >= 0.01)
{
_powerSensor.Value = _energyUnitMultiplier * unchecked(eax - _lastEnergyConsumed) / deltaTime;
_lastEnergyTime = time;
_lastEnergyConsumed = eax;
}
}
if (_nodeUsage.Length == deviceInfo.Nodes.Length)
{
foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes)
{
long runningTimeDiff = node.RunningTime - _nodeUsagePrevValue[node.Id];
long timeDiff = node.QueryTime.Ticks - _nodeUsagePrevTick[node.Id].Ticks;
_nodeUsage[node.Id].Value = 100f * runningTimeDiff / timeDiff;
_nodeUsagePrevValue[node.Id] = node.RunningTime;
_nodeUsagePrevTick[node.Id] = node.QueryTime;
ActivateSensor(_nodeUsage[node.Id]);
}
}
}
}
private static string GetName(string deviceId)
{
string path = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\" + D3DDisplayDevice.GetActualDeviceIdentifier(deviceId);
if (Registry.GetValue(path, "DeviceDesc", null) is string deviceDesc)
{
return deviceDesc.Split(';').Last();
}
return "Intel Integrated Graphics";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Gpu;
internal class NvidiaGroup : IGroup
{
private readonly List<Hardware> _hardware = new();
private readonly StringBuilder _report = new();
public NvidiaGroup(ISettings settings)
{
if (!NvApi.IsAvailable)
return;
_report.AppendLine("NvApi");
_report.AppendLine();
if (NvApi.NvAPI_GetInterfaceVersionString(out string version) == NvApi.NvStatus.OK)
{
_report.Append("Version: ");
_report.AppendLine(version);
}
NvApi.NvPhysicalGpuHandle[] handles = new NvApi.NvPhysicalGpuHandle[NvApi.MAX_PHYSICAL_GPUS];
if (NvApi.NvAPI_EnumPhysicalGPUs == null)
{
_report.AppendLine("Error: NvAPI_EnumPhysicalGPUs not available");
_report.AppendLine();
return;
}
NvApi.NvStatus status = NvApi.NvAPI_EnumPhysicalGPUs(handles, out int count);
if (status != NvApi.NvStatus.OK)
{
_report.AppendLine("Status: " + status);
_report.AppendLine();
return;
}
IDictionary<NvApi.NvPhysicalGpuHandle, NvApi.NvDisplayHandle> displayHandles = new Dictionary<NvApi.NvPhysicalGpuHandle, NvApi.NvDisplayHandle>();
if (NvApi.NvAPI_EnumNvidiaDisplayHandle != null && NvApi.NvAPI_GetPhysicalGPUsFromDisplay != null)
{
status = NvApi.NvStatus.OK;
int i = 0;
while (status == NvApi.NvStatus.OK)
{
NvApi.NvDisplayHandle displayHandle = new();
status = NvApi.NvAPI_EnumNvidiaDisplayHandle(i, ref displayHandle);
i++;
if (status == NvApi.NvStatus.OK)
{
NvApi.NvPhysicalGpuHandle[] handlesFromDisplay = new NvApi.NvPhysicalGpuHandle[NvApi.MAX_PHYSICAL_GPUS];
if (NvApi.NvAPI_GetPhysicalGPUsFromDisplay(displayHandle, handlesFromDisplay, out uint countFromDisplay) == NvApi.NvStatus.OK)
{
for (int j = 0; j < countFromDisplay; j++)
{
if (!displayHandles.ContainsKey(handlesFromDisplay[j]))
displayHandles.Add(handlesFromDisplay[j], displayHandle);
}
}
}
}
}
_report.Append("Number of GPUs: ");
_report.AppendLine(count.ToString(CultureInfo.InvariantCulture));
for (int i = 0; i < count; i++)
{
displayHandles.TryGetValue(handles[i], out NvApi.NvDisplayHandle displayHandle);
_hardware.Add(new NvidiaGpu(i, handles[i], displayHandle, settings));
}
_report.AppendLine();
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
return _report.ToString();
}
public void Close()
{
foreach (Hardware gpu in _hardware)
gpu.Close();
NvidiaML.Close();
}
}

View File

@@ -0,0 +1,92 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// This structure describes a group-specific affinity.
/// </summary>
public readonly struct GroupAffinity
{
public static GroupAffinity Undefined = new(ushort.MaxValue, 0);
/// <summary>
/// Initializes a new instance of the <see cref="GroupAffinity" /> struct.
/// </summary>
/// <param name="group">The group.</param>
/// <param name="mask">The mask.</param>
public GroupAffinity(ushort group, ulong mask)
{
Group = group;
Mask = mask;
}
/// <summary>
/// Gets a single group affinity.
/// </summary>
/// <param name="group">The group.</param>
/// <param name="index">The index.</param>
/// <returns><see cref="GroupAffinity" />.</returns>
public static GroupAffinity Single(ushort group, int index)
{
return new GroupAffinity(group, 1UL << index);
}
/// <summary>
/// Gets the group.
/// </summary>
public ushort Group { get; }
/// <summary>
/// Gets the mask.
/// </summary>
public ulong Mask { get; }
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
/// </summary>
/// <param name="o">The <see cref="System.Object" /> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object o)
{
if (o == null || GetType() != o.GetType())
return false;
GroupAffinity a = (GroupAffinity)o;
return (Group == a.Group) && (Mask == a.Mask);
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode()
{
return Group.GetHashCode() ^ Mask.GetHashCode();
}
/// <summary>
/// Implements the == operator.
/// </summary>
/// <param name="a1">The a1.</param>
/// <param name="a2">The a2.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(GroupAffinity a1, GroupAffinity a2)
{
return (a1.Group == a2.Group) && (a1.Mask == a2.Mask);
}
/// <summary>
/// Implements the != operator.
/// </summary>
/// <param name="a1">The a1.</param>
/// <param name="a2">The a2.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(GroupAffinity a1, GroupAffinity a2)
{
return (a1.Group != a2.Group) || (a1.Mask != a2.Mask);
}
}

View File

@@ -0,0 +1,134 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Object representing a component of the computer.
/// <para>
/// Individual information can be read from the <see cref="Sensors"/>.
/// </para>
/// </summary>
public abstract class Hardware : IHardware
{
protected readonly HashSet<ISensor> _active = new();
protected readonly string _name;
protected readonly ISettings _settings;
private string _customName;
/// <summary>
/// Creates a new <see cref="Hardware"/> instance based on the data provided.
/// </summary>
/// <param name="name">Component name.</param>
/// <param name="identifier">Identifier that will be assigned to the device. Based on <see cref="Identifier"/></param>
/// <param name="settings">Additional settings passed by the <see cref="IComputer"/>.</param>
protected Hardware(string name, Identifier identifier, ISettings settings)
{
_settings = settings;
_name = name;
Identifier = identifier;
_customName = settings.GetValue(new Identifier(Identifier, "name").ToString(), name);
}
/// <summary>
/// Event triggered when <see cref="Hardware"/> is closing.
/// </summary>
public event HardwareEventHandler Closing;
/// <inheritdoc />
public abstract HardwareType HardwareType { get; }
/// <inheritdoc />
public Identifier Identifier { get; }
/// <inheritdoc />
public string Name
{
get { return _customName; }
set
{
_customName = !string.IsNullOrEmpty(value) ? value : _name;
_settings.SetValue(new Identifier(Identifier, "name").ToString(), _customName);
}
}
/// <inheritdoc />
public virtual IHardware Parent
{
get { return null; }
}
/// <inheritdoc />
public virtual IDictionary<string, string> Properties => new SortedDictionary<string, string>();
/// <inheritdoc />
public virtual ISensor[] Sensors
{
get { return _active.ToArray(); }
}
/// <inheritdoc />
public IHardware[] SubHardware
{
get { return Array.Empty<IHardware>(); }
}
/// <inheritdoc />
public virtual string GetReport()
{
return null;
}
/// <inheritdoc />
public abstract void Update();
/// <inheritdoc />
public void Accept(IVisitor visitor)
{
if (visitor == null)
throw new ArgumentNullException(nameof(visitor));
visitor.VisitHardware(this);
}
/// <inheritdoc />
public virtual void Traverse(IVisitor visitor)
{
foreach (ISensor sensor in _active)
sensor.Accept(visitor);
}
/// <inheritdoc />
protected virtual void ActivateSensor(ISensor sensor)
{
if (_active.Add(sensor))
SensorAdded?.Invoke(sensor);
}
/// <inheritdoc />
protected virtual void DeactivateSensor(ISensor sensor)
{
if (_active.Remove(sensor))
SensorRemoved?.Invoke(sensor);
}
/// <inheritdoc />
public virtual void Close()
{
Closing?.Invoke(this);
}
#pragma warning disable 67
public event SensorEventHandler SensorAdded;
public event SensorEventHandler SensorRemoved;
#pragma warning restore 67
}

View File

@@ -0,0 +1,27 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Collection of identifiers representing the purpose of the hardware.
/// </summary>
public enum HardwareType
{
Motherboard,
SuperIO,
Cpu,
Memory,
GpuNvidia,
GpuAmd,
GpuIntel,
Storage,
Network,
Cooler,
EmbeddedController,
Psu,
Battery
}

View File

@@ -0,0 +1,116 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Handler that will trigger the actions assigned to it when the event occurs.
/// </summary>
/// <param name="hardware">Component returned to the assigned action(s).</param>
public delegate void HardwareEventHandler(IHardware hardware);
/// <summary>
/// Basic abstract with methods for the class which can store all hardware and decides which devices are to be checked and updated.
/// </summary>
public interface IComputer : IElement
{
/// <summary>
/// Triggered when a new <see cref="IHardware" /> is registered.
/// </summary>
event HardwareEventHandler HardwareAdded;
/// <summary>
/// Triggered when a <see cref="IHardware" /> is removed.
/// </summary>
event HardwareEventHandler HardwareRemoved;
/// <summary>
/// Gets a list of all known <see cref="IHardware" />.
/// <para>Can be updated by <see cref="IVisitor" />.</para>
/// </summary>
/// <returns>List of all enabled devices.</returns>
IList<IHardware> Hardware { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Battery" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsBatteryEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about:
/// <list>
/// <item>
/// <see cref="Controller.TBalancer.TBalancerGroup" />
/// </item>
/// <item>
/// <see cref="Controller.Heatmaster.HeatmasterGroup" />
/// </item>
/// <item>
/// <see cref="Controller.AquaComputer.AquaComputerGroup" />
/// </item>
/// <item>
/// <see cref="Controller.AeroCool.AeroCoolGroup" />
/// </item>
/// <item>
/// <see cref="Controller.Nzxt.NzxtGroup" />
/// </item>
/// </list>
/// devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsControllerEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Cpu" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsCpuEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.GpuAmd" /> or <see cref="HardwareType.GpuNvidia" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsGpuEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Memory" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsMemoryEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Motherboard" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsMotherboardEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Network" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsNetworkEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Psu" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsPsuEnabled { get; }
/// <summary>
/// Gets or sets a value indicating whether collecting information about <see cref="HardwareType.Storage" /> devices should be enabled and updated.
/// </summary>
/// <returns><see langword="true" /> if a given category of devices is already enabled.</returns>
bool IsStorageEnabled { get; }
/// <summary>
/// Generates full LibreHardwareMonitor report for devices that have been enabled.
/// </summary>
/// <returns>A formatted text string with library, OS and hardware information.</returns>
string GetReport();
}

View File

@@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
public enum ControlMode
{
Undefined,
Software,
Default
}
public interface IControl
{
ControlMode ControlMode { get; }
Identifier Identifier { get; }
float MaxSoftwareValue { get; }
float MinSoftwareValue { get; }
ISensor Sensor { get; }
float SoftwareValue { get; }
void SetDefault();
void SetSoftware(float value);
}

View File

@@ -0,0 +1,25 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Abstract parent with logic for the abstract class that stores data.
/// </summary>
public interface IElement
{
/// <summary>
/// Accepts the observer for this instance.
/// </summary>
/// <param name="visitor">Computer observer making the calls.</param>
void Accept(IVisitor visitor);
/// <summary>
/// Call the <see cref="Accept"/> method for all child instances <c>(called only from visitors).</c>
/// </summary>
/// <param name="visitor">Computer observer making the calls.</param>
void Traverse(IVisitor visitor);
}

View File

@@ -0,0 +1,31 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// A group of devices from one category in one list.
/// </summary>
internal interface IGroup
{
/// <summary>
/// Gets a list that stores information about <see cref="IHardware"/> in a given group.
/// </summary>
IReadOnlyList<IHardware> Hardware { get; }
/// <summary>
/// Report containing most of the known information about all <see cref="IHardware"/> in this <see cref="IGroup"/>.
/// </summary>
/// <returns>A formatted text string with hardware information.</returns>
string GetReport();
/// <summary>
/// Stop updating this group in the future.
/// </summary>
void Close();
}

View File

@@ -0,0 +1,81 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Handler that will trigger the actions assigned to it when the event occurs.
/// </summary>
/// <param name="sensor">Component returned to the assigned action(s).</param>
public delegate void SensorEventHandler(ISensor sensor);
/// <summary>
/// Abstract object that stores information about a device. All sensors are available as an array of <see cref="Sensors"/>.
/// <para>
/// Can contain <see cref="SubHardware"/>.
/// Type specified in <see cref="HardwareType"/>.
/// </para>
/// </summary>
public interface IHardware : IElement
{
/// <summary>
/// <inheritdoc cref="LibreHardwareMonitor.Hardware.HardwareType"/>
/// </summary>
HardwareType HardwareType { get; }
/// <summary>
/// Gets a unique hardware ID that represents its location.
/// </summary>
Identifier Identifier { get; }
/// <summary>
/// Gets or sets device name.
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets the device that is the parent of the current hardware. For example, the motherboard is the parent of SuperIO.
/// </summary>
IHardware Parent { get; }
/// <summary>
/// Gets an array of all sensors such as <see cref="SensorType.Temperature"/>, <see cref="SensorType.Clock"/>, <see cref="SensorType.Load"/> etc.
/// </summary>
ISensor[] Sensors { get; }
/// <summary>
/// Gets child devices, e.g. <see cref="LibreHardwareMonitor.Hardware.Motherboard.Lpc.LpcIO"/> of the <see cref="LibreHardwareMonitor.Hardware.Motherboard.Motherboard"/>.
/// </summary>
IHardware[] SubHardware { get; }
/// <summary>
/// Report containing most of the known information about the current device.
/// </summary>
/// <returns>A formatted text string with hardware information.</returns>
string GetReport();
/// <summary>
/// Refreshes the information stored in <see cref="Sensors"/> array.
/// </summary>
void Update();
/// <summary>
/// An <see langword="event"/> that will be triggered when a new sensor appears.
/// </summary>
event SensorEventHandler SensorAdded;
/// <summary>
/// An <see langword="event"/> that will be triggered when one of the sensors is removed.
/// </summary>
event SensorEventHandler SensorRemoved;
/// <summary>
/// Gets rarely changed hardware properties that can't be represented as sensors.
/// </summary>
IDictionary<string, string> Properties { get; }
}

View File

@@ -0,0 +1,12 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
internal interface IHardwareChanged
{
event HardwareEventHandler HardwareAdded;
event HardwareEventHandler HardwareRemoved;
}

View File

@@ -0,0 +1,48 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Abstract object that represents additional parameters included in <see cref="ISensor"/>.
/// </summary>
public interface IParameter : IElement
{
/// <summary>
/// Gets a parameter default value defined by library.
/// </summary>
float DefaultValue { get; }
/// <summary>
/// Gets a parameter description defined by library.
/// </summary>
string Description { get; }
/// <summary>
/// Gets a unique parameter ID that represents its location.
/// </summary>
Identifier Identifier { get; }
/// <summary>
/// Gets or sets information whether the given <see cref="IParameter"/> is the default for <see cref="ISensor"/>.
/// </summary>
bool IsDefault { get; set; }
/// <summary>
/// Gets a parameter name defined by library.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the sensor that is the data container for the given parameter.
/// </summary>
ISensor Sensor { get; }
/// <summary>
/// Gets or sets the current value.
/// </summary>
float Value { get; set; }
}

View File

@@ -0,0 +1,133 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Category of what type the selected sensor is.
/// </summary>
public enum SensorType
{
Voltage, // V
Current, // A
Power, // W
Clock, // MHz
Temperature, // °C
Load, // %
Frequency, // Hz
Fan, // RPM
Flow, // L/h
Control, // %
Level, // %
Factor, // 1
Data, // GB = 2^30 Bytes
SmallData, // MB = 2^20 Bytes
Throughput, // B/s
TimeSpan, // Seconds
Energy, // milliwatt-hour (mWh)
Noise, // dBA
Conductivity, // µS/cm
Humidity // %
}
/// <summary>
/// Stores the readed value and the time in which it was recorded.
/// </summary>
public struct SensorValue
{
/// <param name="value"><see cref="Value"/> of the sensor.</param>
/// <param name="time">The time code during which the <see cref="Value"/> was recorded.</param>
public SensorValue(float value, DateTime time)
{
Value = value;
Time = time;
}
/// <summary>
/// Gets the value of the sensor
/// </summary>
public float Value { get; }
/// <summary>
/// Gets the time code during which the <see cref="Value"/> was recorded.
/// </summary>
public DateTime Time { get; }
}
/// <summary>
/// Stores information about the readed values and the time in which they were collected.
/// </summary>
public interface ISensor : IElement
{
IControl Control { get; }
/// <summary>
/// <inheritdoc cref="IHardware"/>
/// </summary>
IHardware Hardware { get; }
Identifier Identifier { get; }
/// <summary>
/// Gets the unique identifier of this sensor for a given <see cref="IHardware"/>.
/// </summary>
int Index { get; }
bool IsDefaultHidden { get; }
/// <summary>
/// Gets a maximum value recorded for the given sensor.
/// </summary>
float? Max { get; }
/// <summary>
/// Gets a minimum value recorded for the given sensor.
/// </summary>
float? Min { get; }
/// <summary>
/// Gets or sets a sensor name.
/// <para>By default determined by the library.</para>
/// </summary>
string Name { get; set; }
IReadOnlyList<IParameter> Parameters { get; }
/// <summary>
/// <inheritdoc cref="LibreHardwareMonitor.Hardware.SensorType"/>
/// </summary>
SensorType SensorType { get; }
/// <summary>
/// Gets the last recorded value for the given sensor.
/// </summary>
float? Value { get; }
/// <summary>
/// Gets a list of recorded values for the given sensor.
/// </summary>
IEnumerable<SensorValue> Values { get; }
TimeSpan ValuesTimeWindow { get; set; }
/// <summary>
/// Resets a value stored in <see cref="Min"/>.
/// </summary>
void ResetMin();
/// <summary>
/// Resets a value stored in <see cref="Max"/>.
/// </summary>
void ResetMax();
/// <summary>
/// Clears the values stored in <see cref="Values"/>.
/// </summary>
void ClearValues();
}

View File

@@ -0,0 +1,38 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Abstract object that stores information about the limits of <see cref="ISensor"/>.
/// </summary>
public interface ISensorLimits
{
/// <summary>
/// Upper limit of <see cref="ISensor"/> value.
/// </summary>
float? HighLimit { get; }
/// <summary>
/// Lower limit of <see cref="ISensor"/> value.
/// </summary>
float? LowLimit { get; }
}
/// <summary>
/// Abstract object that stores information about the critical limits of <see cref="ISensor"/>.
/// </summary>
public interface ICriticalSensorLimits
{
/// <summary>
/// Critical upper limit of <see cref="ISensor"/> value.
/// </summary>
float? CriticalHighLimit { get; }
/// <summary>
/// Critical lower limit of <see cref="ISensor"/> value.
/// </summary>
float? CriticalLowLimit { get; }
}

View File

@@ -0,0 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Abstract object that stores settings passed to <see cref="IComputer"/>, <see cref="IHardware"/> and <see cref="ISensor"/>.
/// </summary>
public interface ISettings
{
/// <summary>
/// Returns information whether the given collection of settings contains a value assigned to the given key.
/// </summary>
/// <param name="name">Key to which the setting value is assigned.</param>
bool Contains(string name);
/// <summary>
/// Assigns a setting option to a given key.
/// </summary>
/// <param name="name">Key to which the setting value is assigned.</param>
/// <param name="value">Text setting value.</param>
void SetValue(string name, string value);
/// <summary>
/// Gets a setting option assigned to the given key.
/// </summary>
/// <param name="name">Key to which the setting value is assigned.</param>
/// <param name="value">Default value.</param>
string GetValue(string name, string value);
/// <summary>
/// Removes a setting with the specified key from the settings collection.
/// </summary>
/// <param name="name">Key to which the setting value is assigned.</param>
void Remove(string name);
}

View File

@@ -0,0 +1,37 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Base interface for creating observers who call to devices.
/// </summary>
public interface IVisitor
{
/// <summary>
/// Refreshes the values of all <see cref="ISensor"/> in all <see cref="IHardware"/> on selected <see cref="IComputer"/>.
/// </summary>
/// <param name="computer">Instance of the computer to be revisited.</param>
void VisitComputer(IComputer computer);
/// <summary>
/// Refreshes the values of all <see cref="ISensor"/> on selected <see cref="IHardware"/>.
/// </summary>
/// <param name="hardware">Instance of the hardware to be revisited.</param>
void VisitHardware(IHardware hardware);
/// <summary>
/// Refreshes the values on selected <see cref="ISensor"/>.
/// </summary>
/// <param name="sensor">Instance of the sensor to be revisited.</param>
void VisitSensor(ISensor sensor);
/// <summary>
/// Refreshes the values on selected <see cref="IParameter"/>.
/// </summary>
/// <param name="parameter">Instance of the parameter to be revisited.</param>
void VisitParameter(IParameter parameter);
}

View File

@@ -0,0 +1,155 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Text;
using HidSharp;
namespace LibreHardwareMonitor.Hardware;
/// <summary>
/// Represents a unique <see cref="ISensor" />/<see cref="IHardware" /> identifier in text format with a / separator.
/// </summary>
public class Identifier : IComparable<Identifier>
{
private const char Separator = '/';
private readonly string _identifier;
public Identifier(params string[] identifiers)
{
CoerceIdentifiers(identifiers);
StringBuilder s = new();
for (int i = 0; i < identifiers.Length; i++)
{
s.Append(Separator);
s.Append(identifiers[i]);
}
_identifier = s.ToString();
}
/// <summary>
/// Creates a new identifier instance based on the base <see cref="Identifier" /> and additional elements.
/// </summary>
/// <param name="identifier">Base identifier being the beginning of the new one.</param>
/// <param name="extensions">Additional parts by which the base <see cref="Identifier" /> will be extended.</param>
public Identifier(Identifier identifier, params string[] extensions)
{
CoerceIdentifiers(extensions);
StringBuilder s = new();
s.Append(identifier);
for (int i = 0; i < extensions.Length; i++)
{
s.Append(Separator);
s.Append(extensions[i]);
}
_identifier = s.ToString();
}
/// <summary>
/// Creates a new identifier instance based on the supplied <see cref="HidDevice" />.
/// If available the identifier will consist of the vendor-id, product-id and serial number of the HidDevice.
/// Alternatively a platform dependent identifier based on the usb device-path is generated.
/// </summary>
/// <param name="dev">The <see cref="HidDevice" /> this identifier will be created for.</param>
public Identifier(HidDevice dev)
{
string[] identifiers;
try
{
identifiers = ["usbhid", dev.VendorID.ToString("X4"), dev.ProductID.ToString("X4"), dev.GetSerialNumber()];
}
catch
{
identifiers = ["usbhid", dev.DevicePath];
}
CoerceIdentifiers(identifiers);
StringBuilder s = new();
for (int i = 0; i < identifiers.Length; i++)
{
s.Append(Separator);
s.Append(identifiers[i]);
}
_identifier = s.ToString();
}
/// <inheritdoc />
public int CompareTo(Identifier other)
{
if (other == null)
return 1;
return string.Compare(_identifier,
other._identifier,
StringComparison.Ordinal);
}
private static void CoerceIdentifiers(string[] identifiers)
{
for (int i = 0; i < identifiers.Length; i++)
{
string s = identifiers[i];
identifiers[i] = Uri.EscapeDataString(identifiers[i]);
}
}
/// <inheritdoc />
public override string ToString()
{
return _identifier;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj == null)
return false;
Identifier id = obj as Identifier;
if (id == null)
return false;
return _identifier == id._identifier;
}
/// <inheritdoc />
public override int GetHashCode()
{
return _identifier.GetHashCode();
}
public static bool operator ==(Identifier id1, Identifier id2)
{
if (id1 is null && id2 is null)
return true;
return id1 is not null && id1.Equals(id2);
}
public static bool operator !=(Identifier id1, Identifier id2)
{
return !(id1 == id2);
}
public static bool operator <(Identifier id1, Identifier id2)
{
if (id1 == null)
return id2 != null;
return id1.CompareTo(id2) < 0;
}
public static bool operator >(Identifier id1, Identifier id2)
{
if (id1 == null)
return false;
return id1.CompareTo(id2) > 0;
}
}

View File

@@ -0,0 +1,241 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware;
internal static class InpOut
{
private static string _filePath;
private static IntPtr _libraryHandle;
private static Interop.InpOut.MapPhysToLinDelegate _mapPhysToLin;
private static Interop.InpOut.UnmapPhysicalMemoryDelegate _unmapPhysicalMemory;
public static bool IsOpen { get; private set; }
public static bool Open()
{
if (Software.OperatingSystem.IsUnix)
return false;
if (IsOpen)
return true;
_filePath = GetFilePath();
if (_filePath != null && (File.Exists(_filePath) || Extract(_filePath)))
{
_libraryHandle = Kernel32.LoadLibrary(_filePath);
if (_libraryHandle != IntPtr.Zero)
{
IntPtr mapPhysToLinAddress = Kernel32.GetProcAddress(_libraryHandle, "MapPhysToLin");
IntPtr unmapPhysicalMemoryAddress = Kernel32.GetProcAddress(_libraryHandle, "UnmapPhysicalMemory");
if (mapPhysToLinAddress != IntPtr.Zero)
_mapPhysToLin = Marshal.GetDelegateForFunctionPointer<Interop.InpOut.MapPhysToLinDelegate>(mapPhysToLinAddress);
if (unmapPhysicalMemoryAddress != IntPtr.Zero)
_unmapPhysicalMemory = Marshal.GetDelegateForFunctionPointer<Interop.InpOut.UnmapPhysicalMemoryDelegate>(unmapPhysicalMemoryAddress);
IsOpen = true;
}
}
if (!IsOpen)
Delete();
return IsOpen;
}
public static void Close()
{
if (_libraryHandle != IntPtr.Zero)
{
Kernel32.FreeLibrary(_libraryHandle);
Delete();
_libraryHandle = IntPtr.Zero;
}
IsOpen = false;
}
public static byte[] ReadMemory(IntPtr baseAddress, uint size)
{
if (_mapPhysToLin != null && _unmapPhysicalMemory != null)
{
IntPtr pdwLinAddr = _mapPhysToLin(baseAddress, size, out IntPtr pPhysicalMemoryHandle);
if (pdwLinAddr != IntPtr.Zero)
{
byte[] bytes = new byte[size];
Marshal.Copy(pdwLinAddr, bytes, 0, bytes.Length);
_unmapPhysicalMemory(pPhysicalMemoryHandle, pdwLinAddr);
return bytes;
}
}
return null;
}
public static bool WriteMemory(IntPtr baseAddress, byte value)
{
if (_mapPhysToLin == null || _unmapPhysicalMemory == null)
return false;
IntPtr pdwLinAddr = _mapPhysToLin(baseAddress, 1, out IntPtr pPhysicalMemoryHandle);
if (pdwLinAddr == IntPtr.Zero)
return false;
Marshal.WriteByte(pdwLinAddr, value);
_unmapPhysicalMemory(pPhysicalMemoryHandle, pdwLinAddr);
return true;
}
public static IntPtr MapMemory(IntPtr baseAddress, uint size, out IntPtr handle)
{
if (_mapPhysToLin == null)
{
handle = IntPtr.Zero;
return IntPtr.Zero;
}
return _mapPhysToLin(baseAddress, size, out handle);
}
public static bool UnmapMemory(IntPtr handle, IntPtr address)
{
if (_unmapPhysicalMemory == null)
return false;
return _unmapPhysicalMemory(handle, address);
}
private static void Delete()
{
try
{
// try to delete the DLL
if (_filePath != null && File.Exists(_filePath))
File.Delete(_filePath);
_filePath = null;
}
catch
{ }
}
private static string GetFilePath()
{
string filePath;
try
{
filePath = Path.GetTempFileName();
if (!string.IsNullOrEmpty(filePath))
return Path.ChangeExtension(filePath, ".dll");
}
catch (IOException)
{ }
const string fileName = "inpout.dll";
try
{
ProcessModule processModule = Process.GetCurrentProcess().MainModule;
if (!string.IsNullOrEmpty(processModule?.FileName))
return Path.Combine(Path.GetDirectoryName(processModule.FileName) ?? string.Empty, fileName);
}
catch
{
// Continue with the other options.
}
filePath = GetPathFromAssembly(Assembly.GetExecutingAssembly());
if (!string.IsNullOrEmpty(filePath))
return Path.Combine(Path.GetDirectoryName(filePath) ?? string.Empty, fileName);
filePath = GetPathFromAssembly(typeof(InpOut).Assembly);
if (!string.IsNullOrEmpty(filePath))
return Path.Combine(Path.GetDirectoryName(filePath) ?? string.Empty, fileName);
return null;
static string GetPathFromAssembly(Assembly assembly)
{
try
{
string location = assembly?.Location;
return !string.IsNullOrEmpty(location) ? location : null;
}
catch
{
return null;
}
}
}
private static bool Extract(string filePath)
{
string resourceName = $"{nameof(LibreHardwareMonitor)}.Resources.{(Software.OperatingSystem.Is64Bit ? "inpoutx64.gz" : "inpout32.gz")}";
Assembly assembly = typeof(InpOut).Assembly;
long requiredLength = 0;
try
{
using Stream stream = assembly.GetManifestResourceStream(resourceName);
if (stream != null)
{
using FileStream target = new(filePath, FileMode.Create);
stream.Position = 1; // Skip first byte.
using var gzipStream = new GZipStream(stream, CompressionMode.Decompress);
gzipStream.CopyTo(target);
requiredLength = target.Length;
}
}
catch
{
return false;
}
if (HasValidFile())
return true;
// Ensure the file is actually written to the file system.
var stopwatch = new Stopwatch();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 2000)
{
if (HasValidFile())
return true;
Thread.Yield();
}
return false;
bool HasValidFile()
{
try
{
return File.Exists(filePath) && new FileInfo(filePath).Length == requiredLength;
}
catch
{
return false;
}
}
}
}

View File

@@ -0,0 +1,184 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware;
internal class KernelDriver
{
private readonly string _driverId;
private readonly string _serviceName;
private SafeFileHandle _device;
public KernelDriver(string serviceName, string driverId)
{
_serviceName = serviceName;
_driverId = driverId;
}
public bool IsOpen => _device != null;
public bool Install(string path, out string errorMessage)
{
IntPtr manager = AdvApi32.OpenSCManager(null, null, AdvApi32.SC_MANAGER_ACCESS_MASK.SC_MANAGER_CREATE_SERVICE);
if (manager == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
errorMessage = $"OpenSCManager returned the error code: {errorCode:X8}.";
return false;
}
IntPtr service = AdvApi32.CreateService(manager,
_serviceName,
_serviceName,
AdvApi32.SERVICE_ACCESS_MASK.SERVICE_ALL_ACCESS,
AdvApi32.SERVICE_TYPE.SERVICE_KERNEL_DRIVER,
AdvApi32.SERVICE_START.SERVICE_DEMAND_START,
AdvApi32.SERVICE_ERROR.SERVICE_ERROR_NORMAL,
path,
null,
null,
null,
null,
null);
if (service == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
if (errorCode == Kernel32.ERROR_SERVICE_EXISTS)
{
errorMessage = "Service already exists";
return false;
}
errorMessage = $"CreateService returned the error code: {errorCode:X8}.";
AdvApi32.CloseServiceHandle(manager);
return false;
}
if (!AdvApi32.StartService(service, 0, null))
{
int errorCode = Marshal.GetLastWin32Error();
if (errorCode != Kernel32.ERROR_SERVICE_ALREADY_RUNNING)
{
errorMessage = $"StartService returned the error code: {errorCode:X8}.";
AdvApi32.CloseServiceHandle(service);
AdvApi32.CloseServiceHandle(manager);
return false;
}
}
AdvApi32.CloseServiceHandle(service);
AdvApi32.CloseServiceHandle(manager);
try
{
// restrict the driver access to system (SY) and builtin admins (BA)
// TODO: replace with a call to IoCreateDeviceSecure in the driver
FileInfo fileInfo = new(@"\\.\" + _driverId);
FileSecurity fileSecurity = fileInfo.GetAccessControl();
fileSecurity.SetSecurityDescriptorSddlForm("O:BAG:SYD:(A;;FA;;;SY)(A;;FA;;;BA)");
fileInfo.SetAccessControl(fileSecurity);
}
catch
{ }
errorMessage = null;
return true;
}
public bool Open()
{
IntPtr fileHandle = Kernel32.CreateFile(@"\\.\" + _driverId, 0xC0000000, FileShare.None, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
_device = new SafeFileHandle(fileHandle, true);
if (_device.IsInvalid)
Close();
return _device != null;
}
public bool DeviceIOControl(Kernel32.IOControlCode ioControlCode, object inBuffer)
{
return _device != null && Kernel32.DeviceIoControl(_device, ioControlCode, inBuffer, inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer), null, 0, out uint _, IntPtr.Zero);
}
public bool DeviceIOControl<T>(Kernel32.IOControlCode ioControlCode, object inBuffer, ref T outBuffer)
{
if (_device == null)
return false;
object boxedOutBuffer = outBuffer;
bool b = Kernel32.DeviceIoControl(_device,
ioControlCode,
inBuffer,
inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer),
boxedOutBuffer,
(uint)Marshal.SizeOf(boxedOutBuffer),
out uint _,
IntPtr.Zero);
outBuffer = (T)boxedOutBuffer;
return b;
}
public bool DeviceIOControl<T>(Kernel32.IOControlCode ioControlCode, object inBuffer, ref T[] outBuffer)
{
if (_device == null)
return false;
object boxedOutBuffer = outBuffer;
bool b = Kernel32.DeviceIoControl(_device,
ioControlCode,
inBuffer,
inBuffer == null ? 0 : (uint)Marshal.SizeOf(inBuffer),
boxedOutBuffer,
(uint)(Marshal.SizeOf(typeof(T)) * outBuffer.Length),
out uint _,
IntPtr.Zero);
outBuffer = (T[])boxedOutBuffer;
return b;
}
public void Close()
{
if (_device != null)
{
_device.Close();
_device.Dispose();
_device = null;
}
}
public bool Delete()
{
IntPtr manager = AdvApi32.OpenSCManager(null, null, AdvApi32.SC_MANAGER_ACCESS_MASK.SC_MANAGER_CONNECT);
if (manager == IntPtr.Zero)
return false;
IntPtr service = AdvApi32.OpenService(manager, _serviceName, AdvApi32.SERVICE_ACCESS_MASK.SERVICE_ALL_ACCESS);
if (service == IntPtr.Zero)
{
AdvApi32.CloseServiceHandle(manager);
return true;
}
AdvApi32.SERVICE_STATUS status = new();
AdvApi32.ControlService(service, AdvApi32.SERVICE_CONTROL.SERVICE_CONTROL_STOP, ref status);
AdvApi32.DeleteService(service);
AdvApi32.CloseServiceHandle(service);
AdvApi32.CloseServiceHandle(manager);
return true;
}
}

View File

@@ -0,0 +1,92 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.IO;
using System.Linq;
namespace LibreHardwareMonitor.Hardware.Memory;
internal sealed class GenericLinuxMemory : Hardware
{
private readonly Sensor _physicalMemoryAvailable;
private readonly Sensor _physicalMemoryLoad;
private readonly Sensor _physicalMemoryUsed;
private readonly Sensor _virtualMemoryAvailable;
private readonly Sensor _virtualMemoryLoad;
private readonly Sensor _virtualMemoryUsed;
public override HardwareType HardwareType => HardwareType.Memory;
public GenericLinuxMemory(string name, ISettings settings) : base(name, new Identifier("ram"), settings)
{
_physicalMemoryUsed = new Sensor("Memory Used", 0, SensorType.Data, this, settings);
ActivateSensor(_physicalMemoryUsed);
_physicalMemoryAvailable = new Sensor("Memory Available", 1, SensorType.Data, this, settings);
ActivateSensor(_physicalMemoryAvailable);
_physicalMemoryLoad = new Sensor("Memory", 0, SensorType.Load, this, settings);
ActivateSensor(_physicalMemoryLoad);
_virtualMemoryUsed = new Sensor("Virtual Memory Used", 2, SensorType.Data, this, settings);
ActivateSensor(_virtualMemoryUsed);
_virtualMemoryAvailable = new Sensor("Virtual Memory Available", 3, SensorType.Data, this, settings);
ActivateSensor(_virtualMemoryAvailable);
_virtualMemoryLoad = new Sensor("Virtual Memory", 1, SensorType.Load, this, settings);
ActivateSensor(_virtualMemoryLoad);
}
public override void Update()
{
try
{
string[] memoryInfo = File.ReadAllLines("/proc/meminfo");
{
float totalMemory_GB = GetMemInfoValue(memoryInfo.First(entry => entry.StartsWith("MemTotal:"))) / 1024.0f / 1024.0f;
float freeMemory_GB = GetMemInfoValue(memoryInfo.First(entry => entry.StartsWith("MemFree:"))) / 1024.0f / 1024.0f;
float cachedMemory_GB = GetMemInfoValue(memoryInfo.First(entry => entry.StartsWith("Cached:"))) / 1024.0f / 1024.0f;
float usedMemory_GB = totalMemory_GB - freeMemory_GB - cachedMemory_GB;
_physicalMemoryUsed.Value = usedMemory_GB;
_physicalMemoryAvailable.Value = totalMemory_GB;
_physicalMemoryLoad.Value = 100.0f * (usedMemory_GB / totalMemory_GB);
}
{
float totalSwapMemory_GB = GetMemInfoValue(memoryInfo.First(entry => entry.StartsWith("SwapTotal"))) / 1024.0f / 1024.0f;
float freeSwapMemory_GB = GetMemInfoValue(memoryInfo.First(entry => entry.StartsWith("SwapFree"))) / 1024.0f / 1024.0f;
float usedSwapMemory_GB = totalSwapMemory_GB - freeSwapMemory_GB;
_virtualMemoryUsed.Value = usedSwapMemory_GB;
_virtualMemoryAvailable.Value = totalSwapMemory_GB;
_virtualMemoryLoad.Value = 100.0f * (usedSwapMemory_GB / totalSwapMemory_GB);
}
}
catch
{
_physicalMemoryUsed.Value = null;
_physicalMemoryAvailable.Value = null;
_physicalMemoryLoad.Value = null;
_virtualMemoryUsed.Value = null;
_virtualMemoryAvailable.Value = null;
_virtualMemoryLoad.Value = null;
}
}
private static long GetMemInfoValue(string line)
{
// Example: "MemTotal: 32849676 kB"
string valueWithUnit = line.Split(':').Skip(1).First().Trim();
string valueAsString = valueWithUnit.Split(' ').First();
return long.Parse(valueAsString);
}
}

View File

@@ -0,0 +1,62 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Memory;
internal sealed class GenericWindowsMemory : Hardware
{
private readonly Sensor _physicalMemoryAvailable;
private readonly Sensor _physicalMemoryLoad;
private readonly Sensor _physicalMemoryUsed;
private readonly Sensor _virtualMemoryAvailable;
private readonly Sensor _virtualMemoryLoad;
private readonly Sensor _virtualMemoryUsed;
public GenericWindowsMemory(string name, ISettings settings) : base(name, new Identifier("ram"), settings)
{
_physicalMemoryUsed = new Sensor("Memory Used", 0, SensorType.Data, this, settings);
ActivateSensor(_physicalMemoryUsed);
_physicalMemoryAvailable = new Sensor("Memory Available", 1, SensorType.Data, this, settings);
ActivateSensor(_physicalMemoryAvailable);
_physicalMemoryLoad = new Sensor("Memory", 0, SensorType.Load, this, settings);
ActivateSensor(_physicalMemoryLoad);
_virtualMemoryUsed = new Sensor("Virtual Memory Used", 2, SensorType.Data, this, settings);
ActivateSensor(_virtualMemoryUsed);
_virtualMemoryAvailable = new Sensor("Virtual Memory Available", 3, SensorType.Data, this, settings);
ActivateSensor(_virtualMemoryAvailable);
_virtualMemoryLoad = new Sensor("Virtual Memory", 1, SensorType.Load, this, settings);
ActivateSensor(_virtualMemoryLoad);
}
public override HardwareType HardwareType
{
get { return HardwareType.Memory; }
}
public override void Update()
{
Kernel32.MEMORYSTATUSEX status = new() { dwLength = (uint)Marshal.SizeOf<Kernel32.MEMORYSTATUSEX>() };
if (!Kernel32.GlobalMemoryStatusEx(ref status))
return;
_physicalMemoryUsed.Value = (float)(status.ullTotalPhys - status.ullAvailPhys) / (1024 * 1024 * 1024);
_physicalMemoryAvailable.Value = (float)status.ullAvailPhys / (1024 * 1024 * 1024);
_physicalMemoryLoad.Value = 100.0f - ((100.0f * status.ullAvailPhys) / status.ullTotalPhys);
_virtualMemoryUsed.Value = (float)(status.ullTotalPageFile - status.ullAvailPageFile) / (1024 * 1024 * 1024);
_virtualMemoryAvailable.Value = (float)status.ullAvailPageFile / (1024 * 1024 * 1024);
_virtualMemoryLoad.Value = 100.0f - ((100.0f * status.ullAvailPageFile) / status.ullTotalPageFile);
}
}

View File

@@ -0,0 +1,32 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Memory;
internal class MemoryGroup : IGroup
{
private readonly Hardware[] _hardware;
public MemoryGroup(ISettings settings)
{
_hardware = new Hardware[] { Software.OperatingSystem.IsUnix ? new GenericLinuxMemory("Generic Memory", settings) : new GenericWindowsMemory("Generic Memory", settings) };
}
public string GetReport()
{
return null;
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public void Close()
{
foreach (Hardware ram in _hardware)
ram.Close();
}
}

View File

@@ -0,0 +1,13 @@
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class Control
{
public readonly int Index;
public readonly string Name;
public Control(string name, int index)
{
Name = name;
Index = index;
}
}

View File

@@ -0,0 +1,13 @@
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class Fan
{
public readonly int Index;
public readonly string Name;
public Fan(string name, int index)
{
Name = name;
Index = index;
}
}

View File

@@ -0,0 +1,617 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class Identification
{
public static Manufacturer GetManufacturer(string name)
{
switch (name)
{
case var _ when name.IndexOf("abit.com.tw", StringComparison.OrdinalIgnoreCase) > -1:
return Manufacturer.Acer;
case var _ when name.StartsWith("Acer", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Acer;
case var _ when name.StartsWith("AMD", StringComparison.OrdinalIgnoreCase):
return Manufacturer.AMD;
case var _ when name.Equals("Alienware", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Alienware;
case var _ when name.StartsWith("AOpen", StringComparison.OrdinalIgnoreCase):
return Manufacturer.AOpen;
case var _ when name.StartsWith("Apple", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Apple;
case var _ when name.Equals("ASRock", StringComparison.OrdinalIgnoreCase):
return Manufacturer.ASRock;
case var _ when name.StartsWith("ASUSTeK", StringComparison.OrdinalIgnoreCase):
case var _ when name.StartsWith("ASUS ", StringComparison.OrdinalIgnoreCase):
return Manufacturer.ASUS;
case var _ when name.StartsWith("Biostar", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Biostar;
case var _ when name.StartsWith("Clevo", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Clevo;
case var _ when name.StartsWith("Dell", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Dell;
case var _ when name.Equals("DFI", StringComparison.OrdinalIgnoreCase):
case var _ when name.StartsWith("DFI Inc", StringComparison.OrdinalIgnoreCase):
return Manufacturer.DFI;
case var _ when name.Equals("ECS", StringComparison.OrdinalIgnoreCase):
case var _ when name.StartsWith("ELITEGROUP", StringComparison.OrdinalIgnoreCase):
return Manufacturer.ECS;
case var _ when name.Equals("EPoX COMPUTER CO., LTD", StringComparison.OrdinalIgnoreCase):
return Manufacturer.EPoX;
case var _ when name.StartsWith("EVGA", StringComparison.OrdinalIgnoreCase):
return Manufacturer.EVGA;
case var _ when name.Equals("FIC", StringComparison.OrdinalIgnoreCase):
case var _ when name.StartsWith("First International Computer", StringComparison.OrdinalIgnoreCase):
return Manufacturer.FIC;
case var _ when name.Equals("Foxconn", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Foxconn;
case var _ when name.StartsWith("Fujitsu", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Fujitsu;
case var _ when name.StartsWith("Gigabyte", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Gigabyte;
case var _ when name.StartsWith("Hewlett-Packard", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("HP", StringComparison.OrdinalIgnoreCase):
return Manufacturer.HP;
case var _ when name.Equals("IBM", StringComparison.OrdinalIgnoreCase):
return Manufacturer.IBM;
case var _ when name.Equals("Intel", StringComparison.OrdinalIgnoreCase):
case var _ when name.StartsWith("Intel Corp", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Intel;
case var _ when name.StartsWith("Jetway", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Jetway;
case var _ when name.StartsWith("Lenovo", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Lenovo;
case var _ when name.Equals("LattePanda", StringComparison.OrdinalIgnoreCase):
return Manufacturer.LattePanda;
case var _ when name.StartsWith("Medion", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Medion;
case var _ when name.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Microsoft;
case var _ when name.StartsWith("Micro-Star International", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("MSI", StringComparison.OrdinalIgnoreCase):
return Manufacturer.MSI;
case var _ when name.StartsWith("NEC ", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("NEC", StringComparison.OrdinalIgnoreCase):
return Manufacturer.NEC;
case var _ when name.StartsWith("Pegatron", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Pegatron;
case var _ when name.StartsWith("Samsung", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Samsung;
case var _ when name.StartsWith("Sapphire", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Sapphire;
case var _ when name.StartsWith("Shuttle", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Shuttle;
case var _ when name.StartsWith("Sony", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Sony;
case var _ when name.StartsWith("Supermicro", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Supermicro;
case var _ when name.StartsWith("Toshiba", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Toshiba;
case var _ when name.Equals("XFX", StringComparison.OrdinalIgnoreCase):
return Manufacturer.XFX;
case var _ when name.StartsWith("Zotac", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Zotac;
case var _ when name.Equals("To be filled by O.E.M.", StringComparison.OrdinalIgnoreCase):
return Manufacturer.Unknown;
default:
return Manufacturer.Unknown;
}
}
public static Model GetModel(string name)
{
switch (name)
{
case var _ when name.Equals("880GMH/USB3", StringComparison.OrdinalIgnoreCase):
return Model._880GMH_USB3;
case var _ when name.Equals("B85M-DGS", StringComparison.OrdinalIgnoreCase):
return Model.B85M_DGS;
case var _ when name.Equals("ASRock AOD790GX/128M", StringComparison.OrdinalIgnoreCase):
return Model.AOD790GX_128M;
case var _ when name.Equals("AB350 Pro4", StringComparison.OrdinalIgnoreCase):
return Model.AB350_Pro4;
case var _ when name.Equals("AB350M Pro4", StringComparison.OrdinalIgnoreCase):
return Model.AB350M_Pro4;
case var _ when name.Equals("AB350M", StringComparison.OrdinalIgnoreCase):
return Model.AB350M;
case var _ when name.Equals("B450 Steel Legend", StringComparison.OrdinalIgnoreCase):
return Model.B450_Steel_Legend;
case var _ when name.Equals("B450M Steel Legend", StringComparison.OrdinalIgnoreCase):
return Model.B450M_Steel_Legend;
case var _ when name.Equals("B450 Pro4", StringComparison.OrdinalIgnoreCase):
return Model.B450_Pro4;
case var _ when name.Equals("B450M Pro4", StringComparison.OrdinalIgnoreCase):
return Model.B450M_Pro4;
case var _ when name.Equals("Fatal1ty AB350 Gaming K4", StringComparison.OrdinalIgnoreCase):
return Model.Fatal1ty_AB350_Gaming_K4;
case var _ when name.Equals("AB350M-HDV", StringComparison.OrdinalIgnoreCase):
return Model.AB350M_HDV;
case var _ when name.Equals("X399 Phantom Gaming 6", StringComparison.OrdinalIgnoreCase):
return Model.X399_Phantom_Gaming_6;
case var _ when name.Equals("A320M-HDV", StringComparison.OrdinalIgnoreCase):
return Model.A320M_HDV;
case var _ when name.Equals("P55 Deluxe", StringComparison.OrdinalIgnoreCase):
return Model.P55_Deluxe;
case var _ when name.Equals("Crosshair III Formula", StringComparison.OrdinalIgnoreCase):
return Model.CROSSHAIR_III_FORMULA;
case var _ when name.Equals("ROG CROSSHAIR VIII HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_VIII_HERO;
case var _ when name.Equals("ROG CROSSHAIR VIII HERO (WI-FI)", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_VIII_HERO_WIFI;
case var _ when name.Equals("ROG CROSSHAIR VIII DARK HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_VIII_DARK_HERO;
case var _ when name.Equals("ROG CROSSHAIR VIII FORMULA", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_VIII_FORMULA;
case var _ when name.Equals("ROG CROSSHAIR VIII IMPACT", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_VIII_IMPACT;
case var _ when name.Equals("PRIME B650-PLUS", StringComparison.OrdinalIgnoreCase):
return Model.PRIME_B650_PLUS;
case var _ when name.Equals("ROG CROSSHAIR X670E EXTREME", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_X670E_EXTREME;
case var _ when name.Equals("ROG CROSSHAIR X670E HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_X670E_HERO;
case var _ when name.Equals("ROG CROSSHAIR X670E GENE", StringComparison.OrdinalIgnoreCase):
return Model.ROG_CROSSHAIR_X670E_GENE;
case var _ when name.Equals("PROART X670E-CREATOR WIFI", StringComparison.OrdinalIgnoreCase):
return Model.PROART_X670E_CREATOR_WIFI;
case var _ when name.Equals("M2N-SLI DELUXE", StringComparison.OrdinalIgnoreCase):
return Model.M2N_SLI_Deluxe;
case var _ when name.Equals("M4A79XTD EVO", StringComparison.OrdinalIgnoreCase):
return Model.M4A79XTD_EVO;
case var _ when name.Equals("P5W DH Deluxe", StringComparison.OrdinalIgnoreCase):
return Model.P5W_DH_Deluxe;
case var _ when name.Equals("P6T", StringComparison.OrdinalIgnoreCase):
return Model.P6T;
case var _ when name.Equals("P6X58D-E", StringComparison.OrdinalIgnoreCase):
return Model.P6X58D_E;
case var _ when name.Equals("P8P67", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("P8P67 REV 3.1", StringComparison.OrdinalIgnoreCase):
return Model.P8P67;
case var _ when name.Equals("P8P67 EVO", StringComparison.OrdinalIgnoreCase):
return Model.P8P67_EVO;
case var _ when name.Equals("P8P67 PRO", StringComparison.OrdinalIgnoreCase):
return Model.P8P67_PRO;
case var _ when name.Equals("P8P67-M PRO", StringComparison.OrdinalIgnoreCase):
return Model.P8P67_M_PRO;
case var _ when name.Equals("P8Z77-V", StringComparison.OrdinalIgnoreCase):
return Model.P8Z77_V;
case var _ when name.Equals("P9X79", StringComparison.OrdinalIgnoreCase):
return Model.P9X79;
case var _ when name.Equals("Rampage Extreme", StringComparison.OrdinalIgnoreCase):
return Model.RAMPAGE_EXTREME;
case var _ when name.Equals("Rampage II GENE", StringComparison.OrdinalIgnoreCase):
return Model.RAMPAGE_II_GENE;
case var _ when name.Equals("LP BI P45-T2RS Elite", StringComparison.OrdinalIgnoreCase):
return Model.LP_BI_P45_T2RS_Elite;
case var _ when name.Equals("ROG STRIX B550-F GAMING (WI-FI)", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_B550_F_GAMING_WIFI;
case var _ when name.Equals("ROG STRIX X470-I GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X470_I;
case var _ when name.Equals("ROG STRIX B550-E GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_B550_E_GAMING;
case var _ when name.Equals("ROG STRIX B550-I GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_B550_I_GAMING;
case var _ when name.Equals("ROG STRIX X570-E GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X570_E_GAMING;
case var _ when name.Equals("ROG STRIX X570-E GAMING WIFI II", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X570_E_GAMING_WIFI_II;
case var _ when name.Equals("ROG STRIX X570-I GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X570_I_GAMING;
case var _ when name.Equals("ROG STRIX X570-F GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X570_F_GAMING;
case var _ when name.Equals("LP DK P55-T3eH9", StringComparison.OrdinalIgnoreCase):
return Model.LP_DK_P55_T3EH9;
case var _ when name.Equals("A890GXM-A", StringComparison.OrdinalIgnoreCase):
return Model.A890GXM_A;
case var _ when name.Equals("X58 SLI Classified", StringComparison.OrdinalIgnoreCase):
return Model.X58_SLI_Classified;
case var _ when name.Equals("132-BL-E758", StringComparison.OrdinalIgnoreCase):
return Model.X58_3X_SLI;
case var _ when name.Equals("965P-S3", StringComparison.OrdinalIgnoreCase):
return Model._965P_S3;
case var _ when name.Equals("EP45-DS3R", StringComparison.OrdinalIgnoreCase):
return Model.EP45_DS3R;
case var _ when name.Equals("EP45-UD3R", StringComparison.OrdinalIgnoreCase):
return Model.EP45_UD3R;
case var _ when name.Equals("EX58-EXTREME", StringComparison.OrdinalIgnoreCase):
return Model.EX58_EXTREME;
case var _ when name.Equals("EX58-UD3R", StringComparison.OrdinalIgnoreCase):
return Model.EX58_UD3R;
case var _ when name.Equals("G41M-Combo", StringComparison.OrdinalIgnoreCase):
return Model.G41M_COMBO;
case var _ when name.Equals("G41MT-S2", StringComparison.OrdinalIgnoreCase):
return Model.G41MT_S2;
case var _ when name.Equals("G41MT-S2P", StringComparison.OrdinalIgnoreCase):
return Model.G41MT_S2P;
case var _ when name.Equals("970A-DS3P", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("970A-DS3P FX", StringComparison.OrdinalIgnoreCase):
return Model._970A_DS3P;
case var _ when name.Equals("GA-970A-UD3", StringComparison.OrdinalIgnoreCase):
return Model._970A_UD3;
case var _ when name.Equals("GA-MA770T-UD3", StringComparison.OrdinalIgnoreCase):
return Model.MA770T_UD3;
case var _ when name.Equals("GA-MA770T-UD3P", StringComparison.OrdinalIgnoreCase):
return Model.MA770T_UD3P;
case var _ when name.Equals("GA-MA785GM-US2H", StringComparison.OrdinalIgnoreCase):
return Model.MA785GM_US2H;
case var _ when name.Equals("GA-MA785GMT-UD2H", StringComparison.OrdinalIgnoreCase):
return Model.MA785GMT_UD2H;
case var _ when name.Equals("GA-MA78LM-S2H", StringComparison.OrdinalIgnoreCase):
return Model.MA78LM_S2H;
case var _ when name.Equals("GA-MA790X-UD3P", StringComparison.OrdinalIgnoreCase):
return Model.MA790X_UD3P;
case var _ when name.Equals("H55-USB3", StringComparison.OrdinalIgnoreCase):
return Model.H55_USB3;
case var _ when name.Equals("H55N-USB3", StringComparison.OrdinalIgnoreCase):
return Model.H55N_USB3;
case var _ when name.Equals("H61M-DGS", StringComparison.OrdinalIgnoreCase):
return Model.H61M_DGS;
case var _ when name.Equals("H61M-DS2 REV 1.2", StringComparison.OrdinalIgnoreCase):
return Model.H61M_DS2_REV_1_2;
case var _ when name.Equals("H61M-USB3-B3 REV 2.0", StringComparison.OrdinalIgnoreCase):
return Model.H61M_USB3_B3_REV_2_0;
case var _ when name.Equals("H67A-UD3H-B3", StringComparison.OrdinalIgnoreCase):
return Model.H67A_UD3H_B3;
case var _ when name.Equals("H67A-USB3-B3", StringComparison.OrdinalIgnoreCase):
return Model.H67A_USB3_B3;
case var _ when name.Equals("H97-D3H-CF", StringComparison.OrdinalIgnoreCase):
return Model.H97_D3H;
case var _ when name.Equals("H81M-HD3", StringComparison.OrdinalIgnoreCase):
return Model.H81M_HD3;
case var _ when name.Equals("B75M-D3H", StringComparison.OrdinalIgnoreCase):
return Model.B75M_D3H;
case var _ when name.Equals("P35-DS3", StringComparison.OrdinalIgnoreCase):
return Model.P35_DS3;
case var _ when name.Equals("P35-DS3L", StringComparison.OrdinalIgnoreCase):
return Model.P35_DS3L;
case var _ when name.Equals("P55-UD4", StringComparison.OrdinalIgnoreCase):
return Model.P55_UD4;
case var _ when name.Equals("P55A-UD3", StringComparison.OrdinalIgnoreCase):
return Model.P55A_UD3;
case var _ when name.Equals("P55M-UD4", StringComparison.OrdinalIgnoreCase):
return Model.P55M_UD4;
case var _ when name.Equals("P67A-UD3-B3", StringComparison.OrdinalIgnoreCase):
return Model.P67A_UD3_B3;
case var _ when name.Equals("P67A-UD3R-B3", StringComparison.OrdinalIgnoreCase):
return Model.P67A_UD3R_B3;
case var _ when name.Equals("P67A-UD4-B3", StringComparison.OrdinalIgnoreCase):
return Model.P67A_UD4_B3;
case var _ when name.Equals("P8Z68-V PRO", StringComparison.OrdinalIgnoreCase):
return Model.P8Z68_V_PRO;
case var _ when name.Equals("X38-DS5", StringComparison.OrdinalIgnoreCase):
return Model.X38_DS5;
case var _ when name.Equals("X58A-UD3R", StringComparison.OrdinalIgnoreCase):
return Model.X58A_UD3R;
case var _ when name.Equals("Z270 PC MATE", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z270 PC MATE (MS-7A72)", StringComparison.OrdinalIgnoreCase):
return Model.Z270_PC_MATE;
case var _ when name.Equals("Z77 MPower", StringComparison.OrdinalIgnoreCase): // MS-7751 Rev 4.x
case var _ when name.Equals("Z77 MPower (MS-7751)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z77A-GD65", StringComparison.OrdinalIgnoreCase): // MS-7751 Rev >1.x
case var _ when name.Equals("Z77A-GD65 (MS-7751)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z77A-GD65 GAMING", StringComparison.OrdinalIgnoreCase): // MS-7751 Rev 2.x
case var _ when name.Equals("Z77A-GD65 GAMING (MS-7751)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z77A-GD55", StringComparison.OrdinalIgnoreCase): // MS-7751 Rev 1.x
case var _ when name.Equals("Z77A-GD55 (MS-7751)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z77A-GD80", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z77A-GD80 (MS-7757)", StringComparison.OrdinalIgnoreCase):
return Model.Z77_MS7751;
case var _ when name.Equals("Z68A-GD80", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z68A-GD80 (MS-7672)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("P67A-GD80", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("P67A-GD80 (MS-7672)", StringComparison.OrdinalIgnoreCase):
return Model.Z68_MS7672;
case var _ when name.Equals("X79-UD3", StringComparison.OrdinalIgnoreCase):
return Model.X79_UD3;
case var _ when name.Equals("Z68A-D3H-B3", StringComparison.OrdinalIgnoreCase):
return Model.Z68A_D3H_B3;
case var _ when name.Equals("Z68AP-D3", StringComparison.OrdinalIgnoreCase):
return Model.Z68AP_D3;
case var _ when name.Equals("Z68X-UD3H-B3", StringComparison.OrdinalIgnoreCase):
return Model.Z68X_UD3H_B3;
case var _ when name.Equals("Z68X-UD7-B3", StringComparison.OrdinalIgnoreCase):
return Model.Z68X_UD7_B3;
case var _ when name.Equals("Z68XP-UD3R", StringComparison.OrdinalIgnoreCase):
return Model.Z68XP_UD3R;
case var _ when name.Equals("Z170N-WIFI-CF", StringComparison.OrdinalIgnoreCase):
return Model.Z170N_WIFI;
case var _ when name.Equals("Z390 M GAMING-CF", StringComparison.OrdinalIgnoreCase):
return Model.Z390_M_GAMING;
case var _ when name.Equals("Z390 AORUS ULTRA", StringComparison.OrdinalIgnoreCase):
return Model.Z390_AORUS_ULTRA;
case var _ when name.Equals("Z390 AORUS PRO-CF", StringComparison.OrdinalIgnoreCase):
return Model.Z390_AORUS_PRO;
case var _ when name.Equals("Z390 UD", StringComparison.OrdinalIgnoreCase):
return Model.Z390_UD;
case var _ when name.Equals("Z690 AORUS PRO", StringComparison.OrdinalIgnoreCase):
return Model.Z690_AORUS_PRO;
case var _ when name.Equals("Z690 AORUS ULTRA", StringComparison.OrdinalIgnoreCase):
return Model.Z690_AORUS_ULTRA;
case var _ when name.Equals("Z690 AORUS MASTER", StringComparison.OrdinalIgnoreCase):
return Model.Z690_AORUS_MASTER;
case var _ when name.Equals("Z690 GAMING X DDR4", StringComparison.OrdinalIgnoreCase):
return Model.Z690_GAMING_X_DDR4;
case var _ when name.Equals("Z790 AORUS PRO X", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z790 AORUS PRO X WIFI7", StringComparison.OrdinalIgnoreCase):
return Model.Z790_AORUS_PRO_X;
case var _ when name.Equals("Z790 UD", StringComparison.OrdinalIgnoreCase):
return Model.Z790_UD;
case var _ when name.Equals("Z790 UD AC", StringComparison.OrdinalIgnoreCase):
return Model.Z790_UD_AC;
case var _ when name.Equals("Z790 GAMING X", StringComparison.OrdinalIgnoreCase):
return Model.Z790_GAMING_X;
case var _ when name.Equals("Z790 GAMING X AX", StringComparison.OrdinalIgnoreCase):
return Model.Z790_GAMING_X_AX;
case var _ when name.Equals("FH67", StringComparison.OrdinalIgnoreCase):
return Model.FH67;
case var _ when name.Equals("AX370-Gaming K7", StringComparison.OrdinalIgnoreCase):
return Model.AX370_Gaming_K7;
case var _ when name.Equals("PRIME X370-PRO", StringComparison.OrdinalIgnoreCase):
return Model.PRIME_X370_PRO;
case var _ when name.Equals("PRIME X470-PRO", StringComparison.OrdinalIgnoreCase):
return Model.PRIME_X470_PRO;
case var _ when name.Equals("PRIME X570-PRO", StringComparison.OrdinalIgnoreCase):
return Model.PRIME_X570_PRO;
case var _ when name.Equals("ProArt X570-CREATOR WIFI", StringComparison.OrdinalIgnoreCase):
return Model.PROART_X570_CREATOR_WIFI;
case var _ when name.Equals("Pro WS X570-ACE", StringComparison.OrdinalIgnoreCase):
return Model.PRO_WS_X570_ACE;
case var _ when name.Equals("ROG MAXIMUS X APEX", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_X_APEX;
case var _ when name.Equals("AB350-Gaming 3-CF", StringComparison.OrdinalIgnoreCase):
return Model.AB350_Gaming_3;
case var _ when name.Equals("X399 AORUS Gaming 7", StringComparison.OrdinalIgnoreCase):
return Model.X399_AORUS_Gaming_7;
case var _ when name.Equals("ROG ZENITH EXTREME", StringComparison.OrdinalIgnoreCase):
return Model.ROG_ZENITH_EXTREME;
case var _ when name.Equals("ROG ZENITH II EXTREME", StringComparison.OrdinalIgnoreCase):
return Model.ROG_ZENITH_II_EXTREME;
case var _ when name.Equals("Z170-A", StringComparison.OrdinalIgnoreCase):
return Model.Z170_A;
case var _ when name.Equals("B150M-C", StringComparison.OrdinalIgnoreCase):
return Model.B150M_C;
case var _ when name.Equals("B150M-C D3", StringComparison.OrdinalIgnoreCase):
return Model.B150M_C_D3;
case var _ when name.Equals("Z77 Pro4-M", StringComparison.OrdinalIgnoreCase):
return Model.Z77Pro4M;
case var _ when name.Equals("X570 Pro4", StringComparison.OrdinalIgnoreCase):
return Model.X570_Pro4;
case var _ when name.Equals("X570 Taichi", StringComparison.OrdinalIgnoreCase):
return Model.X570_Taichi;
case var _ when name.Equals("X570 Phantom Gaming-ITX/TB3", StringComparison.OrdinalIgnoreCase):
return Model.X570_Phantom_Gaming_ITX;
case var _ when name.Equals("X570 Phantom Gaming 4", StringComparison.OrdinalIgnoreCase):
return Model.X570_Phantom_Gaming_4;
case var _ when name.Equals("AX370-Gaming 5", StringComparison.OrdinalIgnoreCase):
return Model.AX370_Gaming_5;
case var _ when name.Equals("TUF X470-PLUS GAMING", StringComparison.OrdinalIgnoreCase):
return Model.TUF_X470_PLUS_GAMING;
case var _ when name.Equals("B360M PRO-VDH (MS-7B24)", StringComparison.OrdinalIgnoreCase):
return Model.B360M_PRO_VDH;
case var _ when name.Equals("B550-A PRO (MS-7C56)", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("PRO B550-VC (MS-7C56)", StringComparison.OrdinalIgnoreCase):
return Model.B550A_PRO;
case var _ when name.Equals("B450-A PRO (MS-7B86)", StringComparison.OrdinalIgnoreCase):
return Model.B450A_PRO;
case var _ when name.Equals("B350 GAMING PLUS (MS-7A34)", StringComparison.OrdinalIgnoreCase):
return Model.B350_Gaming_Plus;
case var _ when name.Equals("B450 AORUS PRO", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450 AORUS PRO WIFI", StringComparison.OrdinalIgnoreCase):
return Model.B450_AORUS_PRO;
case var _ when name.Equals("B450 GAMING X", StringComparison.OrdinalIgnoreCase):
return Model.B450_GAMING_X;
case var _ when name.Equals("B450 AORUS ELITE", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450 AORUS ELITE V2", StringComparison.OrdinalIgnoreCase):
return Model.B450_AORUS_ELITE;
case var _ when name.Equals("B450M AORUS ELITE", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M AORUS ELITE-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_AORUS_ELITE;
case var _ when name.Equals("B450M GAMING", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M GAMING-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_GAMING;
case var _ when name.Equals("B450M AORUS M", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M AORUS M-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450_AORUS_M;
case var _ when name.Equals("B450M DS3H", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M DS3H WIFI", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M DS3H-CF", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M DS3H WIFI-CF", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M DS3H V2", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M DS3H V2-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_DS3H;
case var _ when name.Equals("B450M S2H", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M S2H V2", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M S2H-CF", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M S2H V2-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_S2H;
case var _ when name.Equals("B450M H", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M H-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_H;
case var _ when name.Equals("B450M K", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M K-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450M_K;
case var _ when name.Equals("B450M I AORUS PRO WIFI", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B450M I AORUS PRO WIFI-CF", StringComparison.OrdinalIgnoreCase):
return Model.B450_I_AORUS_PRO_WIFI;
case var _ when name.Equals("X470 AORUS GAMING 7 WIFI-CF", StringComparison.OrdinalIgnoreCase):
return Model.X470_AORUS_GAMING_7_WIFI;
case var _ when name.Equals("X570 AORUS MASTER", StringComparison.OrdinalIgnoreCase):
return Model.X570_AORUS_MASTER;
case var _ when name.Equals("X570 AORUS PRO", StringComparison.OrdinalIgnoreCase):
return Model.X570_AORUS_PRO;
case var _ when name.Equals("X570 AORUS ULTRA", StringComparison.OrdinalIgnoreCase):
return Model.X570_AORUS_ULTRA;
case var _ when name.Equals("X570 GAMING X", StringComparison.OrdinalIgnoreCase):
return Model.X570_GAMING_X;
case var _ when name.Equals("TUF GAMING X570-PLUS (WI-FI)", StringComparison.OrdinalIgnoreCase):
return Model.TUF_GAMING_X570_PLUS_WIFI;
case var _ when name.Equals("TUF GAMING B550M-PLUS (WI-FI)", StringComparison.OrdinalIgnoreCase):
return Model.TUF_GAMING_B550M_PLUS_WIFI;
case var _ when name.Equals("B360 AORUS GAMING 3 WIFI-CF", StringComparison.OrdinalIgnoreCase):
return Model.B360_AORUS_GAMING_3_WIFI_CF;
case var _ when name.Equals("B550I AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B550I_AORUS_PRO_AX;
case var _ when name.Equals("B550M AORUS PRO", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550M AORUS PRO-P", StringComparison.OrdinalIgnoreCase):
return Model.B550M_AORUS_PRO;
case var _ when name.Equals("B550M AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B550M_AORUS_PRO_AX;
case var _ when name.Equals("B550M AORUS ELITE", StringComparison.OrdinalIgnoreCase):
return Model.B550M_AORUS_ELITE;
case var _ when name.Equals("B550M GAMING", StringComparison.OrdinalIgnoreCase):
return Model.B550M_GAMING;
case var _ when name.Equals("B550M DS3H", StringComparison.OrdinalIgnoreCase):
return Model.B550M_DS3H;
case var _ when name.Equals("B550M DS3H AC", StringComparison.OrdinalIgnoreCase):
return Model.B550M_DS3H_AC;
case var _ when name.Equals("B550M S2H", StringComparison.OrdinalIgnoreCase):
return Model.B550M_S2H;
case var _ when name.Equals("B550M H", StringComparison.OrdinalIgnoreCase):
return Model.B550M_H;
case var _ when name.Equals("B550 AORUS MASTER", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_MASTER;
case var _ when name.Equals("B550 AORUS PRO", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 AORUS PRO V2", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_PRO;
case var _ when name.Equals("B550 AORUS PRO AC", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_PRO_AC;
case var _ when name.Equals("B550 AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_PRO_AX;
case var _ when name.Equals("B550 VISION D", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 VISION D-P", StringComparison.OrdinalIgnoreCase):
return Model.B550_VISION_D;
case var _ when name.Equals("B550 AORUS ELITE", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 AORUS ELITE V2", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_ELITE;
case var _ when name.Equals("B550 AORUS ELITE AX", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 AORUS ELITE AX V2", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 AORUS ELITE AX V3", StringComparison.OrdinalIgnoreCase):
return Model.B550_AORUS_ELITE_AX;
case var _ when name.Equals("B550 GAMING X", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 GAMING X V2", StringComparison.OrdinalIgnoreCase):
return Model.B550_GAMING_X;
case var _ when name.Equals("B550 UD AC", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B550 UD AC-Y1", StringComparison.OrdinalIgnoreCase):
return Model.B550_UD_AC;
case var _ when name.Equals("B560M AORUS ELITE", StringComparison.OrdinalIgnoreCase):
return Model.B560M_AORUS_ELITE;
case var _ when name.Equals("B560M AORUS PRO", StringComparison.OrdinalIgnoreCase):
return Model.B560M_AORUS_PRO;
case var _ when name.Equals("B560M AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B560M_AORUS_PRO_AX;
case var _ when name.Equals("B560I AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B560I_AORUS_PRO_AX;
case var _ when name.Equals("B650 AORUS ELITE", StringComparison.OrdinalIgnoreCase):
return Model.B650_AORUS_ELITE;
case var _ when name.Equals("B650 AORUS ELITE AX", StringComparison.OrdinalIgnoreCase):
return Model.B650_AORUS_ELITE_AX;
case var _ when name.Equals("B650 AORUS ELITE V2", StringComparison.OrdinalIgnoreCase):
return Model.B650_AORUS_ELITE_V2;
case var _ when name.Equals("B650 AORUS ELITE AX V2", StringComparison.OrdinalIgnoreCase):
return Model.B650_AORUS_ELITE_AX_V2;
case var _ when name.Equals("B650 AORUS ELITE AX ICE", StringComparison.OrdinalIgnoreCase):
return Model.B650_AORUS_ELITE_AX_ICE;
case var _ when name.Equals("B650E AORUS ELITE AX ICE", StringComparison.OrdinalIgnoreCase):
return Model.B650E_AORUS_ELITE_AX_ICE;
case var _ when name.Equals("B650M AORUS PRO", StringComparison.OrdinalIgnoreCase):
return Model.B650M_AORUS_PRO;
case var _ when name.Equals("B650M AORUS PRO AX", StringComparison.OrdinalIgnoreCase):
return Model.B650M_AORUS_PRO_AX;
case var _ when name.Equals("B650M AORUS ELITE", StringComparison.OrdinalIgnoreCase):
return Model.B650M_AORUS_ELITE;
case var _ when name.Equals("B650M AORUS ELITE AX", StringComparison.OrdinalIgnoreCase):
return Model.B650M_AORUS_ELITE_AX;
case var _ when name.Equals("ROG STRIX Z390-E GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z390_E_GAMING;
case var _ when name.Equals("ROG STRIX Z390-F GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z390_F_GAMING;
case var _ when name.Equals("ROG STRIX Z390-I GAMING", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z390_I_GAMING;
case var _ when name.Equals("ROG STRIX Z690-A GAMING WIFI D4", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z690_A_GAMING_WIFI_D4;
case var _ when name.Equals("ROG MAXIMUS XI FORMULA", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_XI_FORMULA;
case var _ when name.Equals("ROG MAXIMUS XII FORMULA", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_XII_Z490_FORMULA;
case var _ when name.Equals("ROG MAXIMUS X HERO (WI-FI AC)", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_X_HERO_WIFI_AC;
case var _ when name.Equals("ROG MAXIMUS Z690 FORMULA", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z690_FORMULA;
case var _ when name.Equals("ROG MAXIMUS Z690 HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z690_HERO;
case var _ when name.Equals("ROG MAXIMUS Z690 EXTREME GLACIAL", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z690_EXTREME_GLACIAL;
case var _ when name.Equals("ROG STRIX X670E-A GAMING WIFI", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X670E_A_GAMING_WIFI;
case var _ when name.Equals("ROG STRIX X670E-E GAMING WIFI", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X670E_E_GAMING_WIFI;
case var _ when name.Equals("ROG STRIX X670E-F GAMING WIFI", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_X670E_F_GAMING_WIFI;
case var _ when name.Equals("B660GTN", StringComparison.OrdinalIgnoreCase):
return Model.B660GTN;
case var _ when name.Equals("X670E VALKYRIE", StringComparison.OrdinalIgnoreCase):
return Model.X670E_Valkyrie;
case var _ when name.Equals("ROG MAXIMUS Z790 HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z790_HERO;
case var _ when name.Equals("ROG MAXIMUS Z790 DARK HERO", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z790_DARK_HERO;
case var _ when name.Equals("PRIME Z690-A", StringComparison.OrdinalIgnoreCase):
return Model.PRIME_Z690_A;
case var _ when name.Equals("Z690 Steel Legend WiFi 6E", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z690 Steel Legend", StringComparison.OrdinalIgnoreCase):
return Model.Z690_Steel_Legend;
case var _ when name.Equals("Z690 Extreme WiFi 6E", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z690 Extreme", StringComparison.OrdinalIgnoreCase):
return Model.Z690_Extreme;
case var _ when name.Equals("Z790 Pro RS", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z790 Pro RS WiFi", StringComparison.OrdinalIgnoreCase):
return Model.Z790_Pro_RS;
case var _ when name.Equals("Z790 Taichi", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("Z790 Taichi Carrara", StringComparison.OrdinalIgnoreCase):
return Model.Z790_Taichi;
case var _ when name.Equals("B650M-C", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B650M-CW", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B650M-CX", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B650M-CWX", StringComparison.OrdinalIgnoreCase):
return Model.B650M_C;
case var _ when name.Equals("B650M GAMING PLUS WIFI (MS-7E24)", StringComparison.OrdinalIgnoreCase):
return Model.B650M_Gaming_Plus_Wifi;
case var _ when name.Equals("B660 DS3H DDR4-Y1",StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B660 DS3H DDR4",StringComparison.OrdinalIgnoreCase):
return Model.B660_DS3H_DDR4;
case var _ when name.Equals("B660 DS3H AC DDR4-Y1",StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("B660 DS3H AC DDR4",StringComparison.OrdinalIgnoreCase):
return Model.B660_DS3H_AC_DDR4;
case var _ when name.Equals("B660M DS3H AX DDR4",StringComparison.OrdinalIgnoreCase):
return Model.B660M_DS3H_AX_DDR4;
case var _ when name.Equals("ROG STRIX Z790-I GAMING WIFI", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z790_I_GAMING_WIFI;
case var _ when name.Equals("ROG STRIX Z790-E GAMING WIFI", StringComparison.OrdinalIgnoreCase):
return Model.ROG_STRIX_Z790_E_GAMING_WIFI;
case var _ when name.Equals("MPG X570 GAMING PLUS (MS-7C37)",StringComparison.OrdinalIgnoreCase):
return Model.X570_Gaming_Plus;
case var _ when name.Equals("ROG MAXIMUS Z790 FORMULA", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_Z790_FORMULA;
case var _ when name.Equals("Z790 Nova WiFi", StringComparison.OrdinalIgnoreCase):
return Model.Z790_Nova_WiFi;
case var _ when name.Equals("ROG MAXIMUS XII HERO (WI-FI)", StringComparison.OrdinalIgnoreCase):
return Model.ROG_MAXIMUS_XII_HERO_WIFI;
case var _ when name.Equals("X870E AORUS PRO", StringComparison.OrdinalIgnoreCase):
return Model.X870E_AORUS_PRO;
case var _ when name.Equals("X870E AORUS PRO ICE", StringComparison.OrdinalIgnoreCase):
return Model.X870E_AORUS_PRO_ICE;
case var _ when name.Equals("Base Board Product Name", StringComparison.OrdinalIgnoreCase):
case var _ when name.Equals("To be filled by O.E.M.", StringComparison.OrdinalIgnoreCase):
return Model.Unknown;
default:
return Model.Unknown;
}
}
}

View File

@@ -0,0 +1,160 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Diagnostics.CodeAnalysis;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
[SuppressMessage("ReSharper", "InconsistentNaming")]
internal enum Chip : ushort
{
Unknown = 0,
ATK0110 = 0x0110,
F71808E = 0x0901,
F71811 = 0x1007,
F71858 = 0x0507,
F71862 = 0x0601,
F71869 = 0x0814,
F71869A = 0x1007,
F71878AD = 0x1106,
F71882 = 0x0541,
F71889AD = 0x1005,
F71889ED = 0x0909,
F71889F = 0x0723,
IT8613E = 0x8613,
IT8620E = 0x8620,
IT8625E = 0x8625,
IT8628E = 0x8628,
IT8631E = 0x8631,
IT8655E = 0x8655,
IT8665E = 0x8665,
IT8686E = 0x8686,
IT8688E = 0x8688,
IT8689E = 0x8689,
IT8696E = 0x8696,
IT8705F = 0x8705,
IT8712F = 0x8712,
IT8716F = 0x8716,
IT8718F = 0x8718,
IT8720F = 0x8720,
IT8721F = 0x8721,
IT8726F = 0x8726,
IT8728F = 0x8728,
IT8771E = 0x8771,
IT8772E = 0x8772,
IT8790E = 0x8790,
IT8792E = 0x8733, // Could also be IT8791E, IT8795E
IT87952E = 0x8695,
NCT610XD = 0xC452,
NCT6771F = 0xB470,
NCT6776F = 0xC330,
NCT6779D = 0xC560,
NCT6791D = 0xC803,
NCT6792D = 0xC911,
NCT6792DA = 0xC913,
NCT6793D = 0xD121,
NCT6795D = 0xD352,
NCT6796D = 0xD423,
NCT6796DR = 0xD42A,
NCT6797D = 0xD451,
NCT6798D = 0xD42B,
NCT6686D = 0xD440,
NCT6687D = 0xD592,
NCT6683D = 0xC732,
NCT6799D = 0xD802,
W83627DHG = 0xA020,
W83627DHGP = 0xB070,
W83627EHF = 0x8800,
W83627HF = 0x5200,
W83627THF = 0x8280,
W83667HG = 0xA510,
W83667HGB = 0xB350,
W83687THF = 0x8541,
IPMI = 0x4764,
}
internal class ChipName
{
public static string GetName(Chip chip)
{
switch (chip)
{
case Chip.ATK0110: return "Asus ATK0110";
case Chip.F71858: return "Fintek F71858";
case Chip.F71862: return "Fintek F71862";
case Chip.F71869: return "Fintek F71869";
case Chip.F71878AD: return "Fintek F71878AD";
case Chip.F71869A: return "Fintek F71869A/F71811";
case Chip.F71882: return "Fintek F71882";
case Chip.F71889AD: return "Fintek F71889AD";
case Chip.F71889ED: return "Fintek F71889ED";
case Chip.F71889F: return "Fintek F71889F";
case Chip.F71808E: return "Fintek F71808E";
case Chip.IT8613E: return "ITE IT8613E";
case Chip.IT8620E: return "ITE IT8620E";
case Chip.IT8625E: return "ITE IT8625E";
case Chip.IT8628E: return "ITE IT8628E";
case Chip.IT8631E: return "ITE IT8631E";
case Chip.IT8655E: return "ITE IT8655E";
case Chip.IT8665E: return "ITE IT8665E";
case Chip.IT8686E: return "ITE IT8686E";
case Chip.IT8688E: return "ITE IT8688E";
case Chip.IT8689E: return "ITE IT8689E";
case Chip.IT8696E: return "ITE IT8696E";
case Chip.IT8705F: return "ITE IT8705F";
case Chip.IT8712F: return "ITE IT8712F";
case Chip.IT8716F: return "ITE IT8716F";
case Chip.IT8718F: return "ITE IT8718F";
case Chip.IT8720F: return "ITE IT8720F";
case Chip.IT8721F: return "ITE IT8721F";
case Chip.IT8726F: return "ITE IT8726F";
case Chip.IT8728F: return "ITE IT8728F";
case Chip.IT8771E: return "ITE IT8771E";
case Chip.IT8772E: return "ITE IT8772E";
case Chip.IT8790E: return "ITE IT8790E";
case Chip.IT8792E: return "ITE IT8791E/IT8792E/IT8795E";
case Chip.IT87952E: return "ITE IT87952E";
case Chip.NCT610XD: return "Nuvoton NCT6102D/NCT6104D/NCT6106D";
case Chip.NCT6771F: return "Nuvoton NCT6771F";
case Chip.NCT6776F: return "Nuvoton NCT6776F";
case Chip.NCT6779D: return "Nuvoton NCT6779D";
case Chip.NCT6791D: return "Nuvoton NCT6791D";
case Chip.NCT6792D: return "Nuvoton NCT6792D";
case Chip.NCT6792DA: return "Nuvoton NCT6792D-A";
case Chip.NCT6793D: return "Nuvoton NCT6793D";
case Chip.NCT6795D: return "Nuvoton NCT6795D";
case Chip.NCT6796D: return "Nuvoton NCT6796D";
case Chip.NCT6796DR: return "Nuvoton NCT6796D-R";
case Chip.NCT6797D: return "Nuvoton NCT6797D";
case Chip.NCT6798D: return "Nuvoton NCT6798D";
case Chip.NCT6799D: return "Nuvoton NCT6799D";
case Chip.NCT6686D: return "Nuvoton NCT6686D";
case Chip.NCT6687D: return "Nuvoton NCT6687D";
case Chip.NCT6683D: return "Nuvoton NCT6683D";
case Chip.W83627DHG: return "Winbond W83627DHG";
case Chip.W83627DHGP: return "Winbond W83627DHG-P";
case Chip.W83627EHF: return "Winbond W83627EHF";
case Chip.W83627HF: return "Winbond W83627HF";
case Chip.W83627THF: return "Winbond W83627THF";
case Chip.W83667HG: return "Winbond W83667HG";
case Chip.W83667HGB: return "Winbond W83667HG-B";
case Chip.W83687THF: return "Winbond W83687THF";
case Chip.IPMI: return "IPMI";
default: return "Unknown";
}
}
}

View File

@@ -0,0 +1,632 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
public abstract class EmbeddedController : Hardware
{
// If you are updating board information, please consider sharing your changes with the corresponding Linux driver.
// You can do that at https://github.com/zeule/asus-ec-sensors or contribute directly to Linux HWMON.
// If you are adding a new board, please share DSDT table for the board at https://github.com/zeule/asus-ec-sensors.
// https://dortania.github.io/Getting-Started-With-ACPI/Manual/dump.html
private static readonly BoardInfo[] _boards =
{
new(Model.PRIME_X470_PRO,
BoardFamily.Amd400,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempVrm,
ECSensor.TempVrm,
ECSensor.FanCPUOpt,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.PRIME_X570_PRO,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempVrm,
ECSensor.TempTSensor,
ECSensor.FanChipset),
new(Model.PROART_X570_CREATOR_WIFI,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempVrm,
ECSensor.TempTSensor,
ECSensor.FanCPUOpt,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.PRO_WS_X570_ACE,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempVrm,
ECSensor.FanChipset,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(new[] { Model.ROG_CROSSHAIR_VIII_HERO, Model.ROG_CROSSHAIR_VIII_HERO_WIFI, Model.ROG_CROSSHAIR_VIII_FORMULA },
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt,
ECSensor.FanChipset,
ECSensor.FanWaterFlow,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.ROG_CROSSHAIR_X670E_EXTREME,
BoardFamily.Amd600,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt),
new(Model.ROG_CROSSHAIR_X670E_HERO,
BoardFamily.Amd600,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt),
new(Model.ROG_CROSSHAIR_X670E_GENE,
BoardFamily.Amd600,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt),
new(Model.ROG_STRIX_X670E_E_GAMING_WIFI,
BoardFamily.Amd600,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt),
new(Model.ROG_STRIX_X670E_F_GAMING_WIFI,
BoardFamily.Amd600,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt),
new(Model.ROG_CROSSHAIR_VIII_DARK_HERO,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanCPUOpt,
ECSensor.FanWaterFlow,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.ROG_CROSSHAIR_VIII_IMPACT,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.FanChipset,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.ROG_STRIX_B550_E_GAMING,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.FanCPUOpt),
new(Model.ROG_STRIX_B550_I_GAMING,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.FanVrmHS,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.ROG_STRIX_X570_E_GAMING,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.FanChipset,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.ROG_STRIX_X570_E_GAMING_WIFI_II,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.TempVrm),
new(Model.ROG_STRIX_X570_F_GAMING,
BoardFamily.Amd500,
ECSensor.TempChipset,
ECSensor.TempCPU,
ECSensor.TempMB,
ECSensor.TempTSensor,
ECSensor.FanChipset),
new(Model.ROG_STRIX_X570_I_GAMING,
BoardFamily.Amd500,
ECSensor.TempTSensor,
ECSensor.FanVrmHS,
ECSensor.FanChipset,
ECSensor.CurrCPU,
ECSensor.VoltageCPU,
ECSensor.TempChipset,
ECSensor.TempVrm),
new(Model.ROG_STRIX_Z390_E_GAMING,
BoardFamily.Intel300,
ECSensor.TempVrm,
ECSensor.TempChipset,
ECSensor.TempTSensor,
ECSensor.FanCPUOpt),
new(Model.ROG_STRIX_Z390_F_GAMING,
BoardFamily.Intel300,
ECSensor.TempVrm,
ECSensor.TempChipset,
ECSensor.TempTSensor,
ECSensor.FanCPUOpt),
new(Model.ROG_STRIX_Z390_I_GAMING,
BoardFamily.Intel300,
ECSensor.TempVrm,
ECSensor.TempChipset,
ECSensor.TempTSensor),
new(Model.ROG_MAXIMUS_XI_FORMULA,
BoardFamily.Intel300,
ECSensor.TempVrm,
ECSensor.TempChipset,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.TempTSensor,
ECSensor.FanCPUOpt),
new(Model.ROG_MAXIMUS_XII_Z490_FORMULA,
BoardFamily.Intel400,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanWaterFlow),
new(Model.ROG_STRIX_Z690_A_GAMING_WIFI_D4,
BoardFamily.Intel600,
ECSensor.TempTSensor,
ECSensor.TempVrm),
new(Model.ROG_MAXIMUS_Z690_HERO,
BoardFamily.Intel600,
ECSensor.TempTSensor,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanWaterFlow),
new(Model.ROG_MAXIMUS_Z690_FORMULA,
BoardFamily.Intel600,
ECSensor.TempTSensor,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.TempWaterBlockIn,
ECSensor.FanWaterFlow),
new(Model.ROG_MAXIMUS_Z690_EXTREME_GLACIAL,
BoardFamily.Intel600,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.TempWaterBlockIn,
ECSensor.FanWaterFlow),
new(Model.ROG_MAXIMUS_Z790_HERO,
BoardFamily.Intel700,
ECSensor.TempVrm,
ECSensor.TempTSensor,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanWaterFlow,
ECSensor.FanCPUOpt),
new(Model.ROG_MAXIMUS_Z790_DARK_HERO,
BoardFamily.Intel700,
ECSensor.TempVrm,
ECSensor.FanCPUOpt,
ECSensor.TempTSensor,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.FanWaterFlow),
new(Model.Z170_A,
BoardFamily.Intel100,
ECSensor.TempTSensor,
ECSensor.TempChipset,
ECSensor.FanWaterPump,
ECSensor.CurrCPU,
ECSensor.VoltageCPU),
new(Model.PRIME_Z690_A,
BoardFamily.Intel600,
ECSensor.TempTSensor,
ECSensor.TempVrm),
new(Model.ROG_STRIX_Z790_I_GAMING_WIFI,
BoardFamily.Intel700,
ECSensor.TempTSensor,
ECSensor.TempTSensor2),
new(Model.ROG_STRIX_Z790_E_GAMING_WIFI,
BoardFamily.Intel700,
ECSensor.TempWaterIn),
new(Model.ROG_MAXIMUS_Z790_FORMULA,
BoardFamily.Intel700,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut),
new(Model.ROG_MAXIMUS_XII_HERO_WIFI,
BoardFamily.Intel400,
ECSensor.TempTSensor,
ECSensor.TempChipset,
ECSensor.TempVrm,
ECSensor.TempWaterIn,
ECSensor.TempWaterOut,
ECSensor.CurrCPU,
ECSensor.FanCPUOpt,
ECSensor.FanWaterFlow),
};
private static readonly Dictionary<BoardFamily, Dictionary<ECSensor, EmbeddedControllerSource>> _knownSensors = new()
{
{
BoardFamily.Amd400, new Dictionary<ECSensor, EmbeddedControllerSource>() // no chipset fans in this generation
{
{ ECSensor.TempChipset, new EmbeddedControllerSource("Chipset", SensorType.Temperature, 0x003a) },
{ ECSensor.TempCPU, new EmbeddedControllerSource("CPU", SensorType.Temperature, 0x003b) },
{ ECSensor.TempMB, new EmbeddedControllerSource("Motherboard", SensorType.Temperature, 0x003c) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x003e) },
{ ECSensor.VoltageCPU, new EmbeddedControllerSource("CPU Core", SensorType.Voltage, 0x00a2, 2, factor: 1e-3f) },
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00bc, 2) },
{ ECSensor.FanVrmHS, new EmbeddedControllerSource("VRM Heat Sink Fan", SensorType.Fan, 0x00b2, 2) },
{ ECSensor.FanWaterFlow, new EmbeddedControllerSource("Water flow", SensorType.Flow, 0x00b4, 2, factor: 1.0f / 42f * 60f) },
{ ECSensor.CurrCPU, new EmbeddedControllerSource("CPU", SensorType.Current, 0x00f4) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x010d, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x010b, blank: -40) }
}
},
{
BoardFamily.Amd500, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempChipset, new EmbeddedControllerSource("Chipset", SensorType.Temperature, 0x003a) },
{ ECSensor.TempCPU, new EmbeddedControllerSource("CPU", SensorType.Temperature, 0x003b) },
{ ECSensor.TempMB, new EmbeddedControllerSource("Motherboard", SensorType.Temperature, 0x003c) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x003e) },
{ ECSensor.VoltageCPU, new EmbeddedControllerSource("CPU Core", SensorType.Voltage, 0x00a2, 2, factor: 1e-3f) },
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00b0, 2) },
{ ECSensor.FanVrmHS, new EmbeddedControllerSource("VRM Heat Sink Fan", SensorType.Fan, 0x00b2, 2) },
{ ECSensor.FanChipset, new EmbeddedControllerSource("Chipset Fan", SensorType.Fan, 0x00b4, 2) },
// TODO: "why 42?" is a silly question, I know, but still, why? On the serious side, it might be 41.6(6)
{ ECSensor.FanWaterFlow, new EmbeddedControllerSource("Water flow", SensorType.Flow, 0x00bc, 2, factor: 1.0f / 42f * 60f) },
{ ECSensor.CurrCPU, new EmbeddedControllerSource("CPU", SensorType.Current, 0x00f4) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) }
}
},
{
BoardFamily.Amd600, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00b0, 2) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) }
}
},
{
BoardFamily.Intel100, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempChipset, new EmbeddedControllerSource("Chipset", SensorType.Temperature, 0x003a) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.FanWaterPump, new EmbeddedControllerSource("Water Pump", SensorType.Fan, 0x00bc, 2) },
{ ECSensor.CurrCPU, new EmbeddedControllerSource("CPU", SensorType.Current, 0x00f4) },
{ ECSensor.VoltageCPU, new EmbeddedControllerSource("CPU Core", SensorType.Voltage, 0x00a2, 2, factor: 1e-3f) }
}
},
{
BoardFamily.Intel300, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x003e) },
{ ECSensor.TempChipset, new EmbeddedControllerSource("Chipset", SensorType.Temperature, 0x003a) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) },
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00b0, 2) }
}
},
{
BoardFamily.Intel400, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempChipset, new EmbeddedControllerSource("Chipset", SensorType.Temperature, 0x003a) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x003e) },
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00b0, 2) },
{ ECSensor.FanWaterFlow, new EmbeddedControllerSource("Water Flow", SensorType.Flow, 0x00be, 2, factor: 1.0f / 42f * 60f) }, // todo: need validation for this calculation
{ ECSensor.CurrCPU, new EmbeddedControllerSource("CPU", SensorType.Current, 0x00f4) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) },
}
},
{
BoardFamily.Intel600, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x003d, blank: -40) },
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x003e) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) },
{ ECSensor.TempWaterBlockIn, new EmbeddedControllerSource("Water Block In", SensorType.Temperature, 0x0102, blank: -40) },
{ ECSensor.FanWaterFlow, new EmbeddedControllerSource("Water Flow", SensorType.Flow, 0x00be, 2, factor: 1.0f / 42f * 60f) } // todo: need validation for this calculation
}
},
{
BoardFamily.Intel700, new Dictionary<ECSensor, EmbeddedControllerSource>
{
{ ECSensor.TempVrm, new EmbeddedControllerSource("VRM", SensorType.Temperature, 0x0033) },
{ ECSensor.FanCPUOpt, new EmbeddedControllerSource("CPU Optional Fan", SensorType.Fan, 0x00b0, 2) },
{ ECSensor.TempTSensor, new EmbeddedControllerSource("T Sensor", SensorType.Temperature, 0x0109, blank: -40) },
{ ECSensor.TempTSensor2, new EmbeddedControllerSource("T Sensor 2", SensorType.Temperature, 0x105, blank: -40) },
{ ECSensor.TempWaterIn, new EmbeddedControllerSource("Water In", SensorType.Temperature, 0x0100, blank: -40) },
{ ECSensor.TempWaterOut, new EmbeddedControllerSource("Water Out", SensorType.Temperature, 0x0101, blank: -40) },
{ ECSensor.FanWaterFlow, new EmbeddedControllerSource("Water Flow", SensorType.Flow, 0x00be, 2, factor: 1.0f / 42f * 60f) } // todo: need validation for this calculation
}
}
};
private readonly byte[] _data;
private readonly ushort[] _registers;
private readonly List<Sensor> _sensors;
private readonly IReadOnlyList<EmbeddedControllerSource> _sources;
protected EmbeddedController(IEnumerable<EmbeddedControllerSource> sources, ISettings settings) : base("Embedded Controller", new Identifier("lpc", "ec"), settings)
{
// sorting by address, which implies sorting by bank, for optimized EC access
var sourcesList = sources.ToList();
sourcesList.Sort((left, right) => left.Register.CompareTo(right.Register));
_sources = sourcesList;
var indices = new Dictionary<SensorType, int>();
foreach (SensorType t in Enum.GetValues(typeof(SensorType)))
{
indices.Add(t, 0);
}
_sensors = new List<Sensor>();
List<ushort> registers = new();
foreach (EmbeddedControllerSource s in _sources)
{
int index = indices[s.Type];
indices[s.Type] = index + 1;
_sensors.Add(new Sensor(s.Name, index, s.Type, this, settings));
for (int i = 0; i < s.Size; ++i)
{
registers.Add((ushort)(s.Register + i));
}
ActivateSensor(_sensors[_sensors.Count - 1]);
}
_registers = registers.ToArray();
_data = new byte[_registers.Length];
}
public override HardwareType HardwareType => HardwareType.EmbeddedController;
internal static EmbeddedController Create(Model model, ISettings settings)
{
var boards = _boards.Where(b => b.Models.Contains(model)).ToList();
switch (boards.Count)
{
case 0:
return null;
case > 1:
throw new MultipleBoardRecordsFoundException(model.ToString());
}
BoardInfo board = boards[0];
IEnumerable<EmbeddedControllerSource> sources = board.Sensors.Select(ecs => _knownSensors[board.Family][ecs]);
return Environment.OSVersion.Platform switch
{
PlatformID.Win32NT => new WindowsEmbeddedController(sources, settings),
_ => null
};
}
public override void Update()
{
if (!TryUpdateData())
{
// just skip this update cycle?
return;
}
int readRegister = 0;
for (int si = 0; si < _sensors.Count; ++si)
{
int val = _sources[si].Size switch
{
1 => _sources[si].Type switch { SensorType.Temperature => unchecked((sbyte)_data[readRegister]), _ => _data[readRegister] },
2 => unchecked((short)((_data[readRegister] << 8) + _data[readRegister + 1])),
_ => 0
};
readRegister += _sources[si].Size;
_sensors[si].Value = val != _sources[si].Blank ? val * _sources[si].Factor : null;
}
}
public override string GetReport()
{
StringBuilder r = new();
r.AppendLine("EC " + GetType().Name);
r.AppendLine("Embedded Controller Registers");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
try
{
using IEmbeddedControllerIO embeddedControllerIO = AcquireIOInterface();
ushort[] src = new ushort[0x100];
byte[] data = new byte[0x100];
for (ushort i = 0; i < src.Length; ++i)
{
src[i] = i;
}
embeddedControllerIO.Read(src, data);
for (int i = 0; i <= 0xF; ++i)
{
r.Append(" ");
r.Append((i << 4).ToString("X2", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; ++j)
{
byte address = (byte)(i << 4 | j);
r.Append(" ");
r.Append(data[address].ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
}
catch (IOException e)
{
r.AppendLine(e.Message);
}
return r.ToString();
}
protected abstract IEmbeddedControllerIO AcquireIOInterface();
private bool TryUpdateData()
{
try
{
using IEmbeddedControllerIO embeddedControllerIO = AcquireIOInterface();
embeddedControllerIO.Read(_registers, _data);
return true;
}
catch (IOException)
{
return false;
}
}
private enum ECSensor
{
/// <summary>Chipset temperature [℃]</summary>
TempChipset,
/// <summary>CPU temperature [℃]</summary>
TempCPU,
/// <summary>motherboard temperature [℃]</summary>
TempMB,
/// <summary>"T_Sensor" temperature sensor reading [℃]</summary>
TempTSensor,
/// <summary>"T_Sensor 2" temperature sensor reading [℃]</summary>
TempTSensor2,
/// <summary>VRM temperature [℃]</summary>
TempVrm,
/// <summary>CPU Core voltage [mV]</summary>
VoltageCPU,
/// <summary>CPU_Opt fan [RPM]</summary>
FanCPUOpt,
/// <summary>VRM heat sink fan [RPM]</summary>
FanVrmHS,
/// <summary>Chipset fan [RPM]</summary>
FanChipset,
/// <summary>Water Pump [RPM]</summary>
FanWaterPump,
/// <summary>Water flow sensor reading [RPM]</summary>
FanWaterFlow,
/// <summary>CPU current [A]</summary>
CurrCPU,
/// <summary>"Water_In" temperature sensor reading [℃]</summary>
TempWaterIn,
/// <summary>"Water_Out" temperature sensor reading [℃]</summary>
TempWaterOut,
/// <summary>Water block temperature sensor reading [℃]</summary>
TempWaterBlockIn,
Max
}
private enum BoardFamily
{
Amd400,
Amd500,
Amd600,
Intel100,
Intel300,
Intel400,
Intel600,
Intel700
}
private struct BoardInfo
{
public BoardInfo(Model[] models, BoardFamily family, params ECSensor[] sensors)
{
Models = models;
Family = family;
Sensors = sensors;
}
public BoardInfo(Model model, BoardFamily family, params ECSensor[] sensors)
{
Models = new[] { model };
Family = family;
Sensors = sensors;
}
public Model[] Models { get; }
public BoardFamily Family { get; }
public ECSensor[] Sensors { get; }
}
public class IOException : System.IO.IOException
{
public IOException(string message) : base($"ACPI embedded controller I/O error: {message}")
{ }
}
public class BadConfigurationException : Exception
{
public BadConfigurationException(string message) : base(message)
{ }
}
public class MultipleBoardRecordsFoundException : BadConfigurationException
{
public MultipleBoardRecordsFoundException(string model) : base($"Multiple board records refer to the same model '{model}'")
{ }
}
}

View File

@@ -0,0 +1,8 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
public delegate float EmbeddedControllerReader(IEmbeddedControllerIO ecIO, ushort register);

View File

@@ -0,0 +1,31 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
public class EmbeddedControllerSource
{
public EmbeddedControllerSource(string name, SensorType type, ushort register, byte size = 1, float factor = 1.0f, int blank = int.MaxValue)
{
Name = name;
Register = register;
Size = size;
Type = type;
Factor = factor;
Blank = blank;
}
public string Name { get; }
public ushort Register { get; }
public byte Size { get; }
public float Factor { get; }
public int Blank { get; }
public EmbeddedControllerReader Reader { get; }
public SensorType Type { get; }
}

View File

@@ -0,0 +1,13 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
public interface IEmbeddedControllerIO : IDisposable
{
void Read(ushort[] registers, byte[] data);
}

View File

@@ -0,0 +1,19 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
public class WindowsEmbeddedController : EmbeddedController
{
public WindowsEmbeddedController(IEnumerable<EmbeddedControllerSource> sources, ISettings settings) : base(sources, settings)
{ }
protected override IEmbeddedControllerIO AcquireIOInterface()
{
return new WindowsEmbeddedControllerIO();
}
}

View File

@@ -0,0 +1,248 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
/// <summary>
/// An unsafe but universal implementation for the ACPI Embedded Controller IO interface for Windows
/// </summary>
/// <remarks>
/// It is unsafe because of possible race condition between this application and the PC firmware when
/// writing to the EC registers. For a safe approach ACPI/WMI methods have to be used, but those are
/// different for each motherboard model.
/// </remarks>
public class WindowsEmbeddedControllerIO : IEmbeddedControllerIO
{
private const int FailuresBeforeSkip = 20;
private const int MaxRetries = 5;
// implementation
private const int WaitSpins = 50;
private bool _disposed;
private int _waitReadFailures;
public WindowsEmbeddedControllerIO()
{
if (!Mutexes.WaitEc(10))
{
throw new BusMutexLockingFailedException();
}
}
public void Read(ushort[] registers, byte[] data)
{
Trace.Assert(registers.Length <= data.Length,
"data buffer length has to be greater or equal to the registers array length");
byte bank = 0;
byte prevBank = SwitchBank(bank);
// oops... somebody else is working with the EC too
Trace.WriteLineIf(prevBank != 0, "Concurrent access to the ACPI EC detected.\nRace condition possible.");
// read registers minimizing bank switches.
for (int i = 0; i < registers.Length; i++)
{
byte regBank = (byte)(registers[i] >> 8);
byte regIndex = (byte)(registers[i] & 0xFF);
// registers are sorted by bank
if (regBank > bank)
{
bank = SwitchBank(regBank);
}
data[i] = ReadByte(regIndex);
}
SwitchBank(prevBank);
}
private byte ReadByte(byte register)
{
return ReadLoop<byte>(register, ReadByteOp);
}
private void WriteByte(byte register, byte value)
{
WriteLoop(register, value, WriteByteOp);
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
Mutexes.ReleaseEc();
}
}
private byte SwitchBank(byte bank)
{
byte previous = ReadByte(0xFF);
WriteByte(0xFF, bank);
return previous;
}
private TResult ReadLoop<TResult>(byte register, ReadOp<TResult> op) where TResult : new()
{
TResult result = new();
for (int i = 0; i < MaxRetries; i++)
{
if (op(register, out result))
{
return result;
}
}
return result;
}
private void WriteLoop<TValue>(byte register, TValue value, WriteOp<TValue> op)
{
for (int i = 0; i < MaxRetries; i++)
{
if (op(register, value))
{
return;
}
}
}
private bool WaitForStatus(Status status, bool isSet)
{
for (int i = 0; i < WaitSpins; i++)
{
byte value = ReadIOPort(Port.Command);
if (((byte)status & (!isSet ? value : (byte)~value)) == 0)
{
return true;
}
Thread.Sleep(1);
}
return false;
}
private bool WaitRead()
{
if (_waitReadFailures > FailuresBeforeSkip)
{
return true;
}
if (WaitForStatus(Status.OutputBufferFull, true))
{
_waitReadFailures = 0;
return true;
}
_waitReadFailures++;
return false;
}
private bool WaitWrite()
{
return WaitForStatus(Status.InputBufferFull, false);
}
private byte ReadIOPort(Port port)
{
return Ring0.ReadIoPort((uint)port);
}
private void WriteIOPort(Port port, byte datum)
{
Ring0.WriteIoPort((uint)port, datum);
}
public class BusMutexLockingFailedException : EmbeddedController.IOException
{
public BusMutexLockingFailedException()
: base("could not lock ISA bus mutex")
{ }
}
private delegate bool ReadOp<TParam>(byte register, out TParam p);
private delegate bool WriteOp<in TParam>(byte register, TParam p);
// see the ACPI specification chapter 12
private enum Port : byte
{
Command = 0x66,
Data = 0x62
}
private enum Command : byte
{
Read = 0x80, // RD_EC
Write = 0x81, // WR_EC
BurstEnable = 0x82, // BE_EC
BurstDisable = 0x83, // BD_EC
Query = 0x84 // QR_EC
}
private enum Status : byte
{
OutputBufferFull = 0x01, // EC_OBF
InputBufferFull = 0x02, // EC_IBF
Command = 0x08, // CMD
BurstMode = 0x10, // BURST
SciEventPending = 0x20, // SCI_EVT
SmiEventPending = 0x40 // SMI_EVT
}
#region Read/Write ops
protected bool ReadByteOp(byte register, out byte value)
{
if (WaitWrite())
{
WriteIOPort(Port.Command, (byte)Command.Read);
if (WaitWrite())
{
WriteIOPort(Port.Data, register);
if (WaitWrite() && WaitRead())
{
value = ReadIOPort(Port.Data);
return true;
}
}
}
value = 0;
return false;
}
protected bool WriteByteOp(byte register, byte value)
{
if (WaitWrite())
{
WriteIOPort(Port.Command, (byte)Command.Write);
if (WaitWrite())
{
WriteIOPort(Port.Data, register);
if (WaitWrite())
{
WriteIOPort(Port.Data, value);
return true;
}
}
}
return false;
}
#endregion
}

View File

@@ -0,0 +1,77 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Threading;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class EcioPortGigabyteController : IGigabyteController
{
private const ushort ControllerVersionOffset = 0x00;
private const ushort ControllerEnableRegister = 0x47;
private const ushort ControllerFanControlArea = 0x900;
private const ushort EcioRegisterPort = 0x3F4;
private const ushort EcioValuePort = 0x3F0;
private readonly IT879xEcioPort _port;
private bool? _initialState;
private EcioPortGigabyteController(IT879xEcioPort port)
{
_port = port;
}
public static EcioPortGigabyteController TryCreate()
{
IT879xEcioPort port = new(EcioRegisterPort, EcioValuePort);
// Check compatibility by querying its version.
if (!port.Read(ControllerFanControlArea + ControllerVersionOffset, out byte majorVersion) || majorVersion != 1)
{
return null;
}
return new EcioPortGigabyteController(port);
}
public bool Enable(bool enabled)
{
ushort offset = ControllerFanControlArea + ControllerEnableRegister;
if (!_port.Read(offset, out byte bCurrent))
{
return false;
}
bool current = Convert.ToBoolean(bCurrent);
_initialState ??= current;
if (current != enabled)
{
if (!_port.Write(offset, Convert.ToByte(enabled)))
{
return false;
}
// Allow the system to catch up.
Thread.Sleep(500);
}
return true;
}
public void Restore()
{
if (_initialState.HasValue)
{
Enable(_initialState.Value);
}
}
}

View File

@@ -0,0 +1,252 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.Text;
// ReSharper disable once InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class F718XX : ISuperIO
{
private readonly ushort _address;
private readonly byte[] _initialFanPwmControl = new byte[4];
private readonly bool[] _restoreDefaultFanPwmControlRequired = new bool[4];
public F718XX(Chip chip, ushort address)
{
_address = address;
Chip = chip;
Voltages = new float?[chip == Chip.F71858 ? 3 : 9];
Temperatures = new float?[chip == Chip.F71808E ? 2 : 3];
Fans = new float?[chip is Chip.F71882 or Chip.F71858 ? 4 : 3];
Controls = new float?[chip == Chip.F71878AD || chip == Chip.F71889AD ? 3 : (chip == Chip.F71882 ? 4 : 0)];
}
public Chip Chip { get; }
public float?[] Controls { get; }
public float?[] Fans { get; }
public float?[] Temperatures { get; }
public float?[] Voltages { get; }
public byte? ReadGpio(int index)
{
return null;
}
public void WriteGpio(int index, byte value)
{ }
public void SetControl(int index, byte? value)
{
if (index < 0 || index >= Controls.Length)
throw new ArgumentOutOfRangeException(nameof(index));
if (!Mutexes.WaitIsaBus(10))
return;
if (value.HasValue)
{
SaveDefaultFanPwmControl(index);
WriteByte(FAN_PWM_REG[index], value.Value);
}
else
{
RestoreDefaultFanPwmControl(index);
}
Mutexes.ReleaseIsaBus();
}
public string GetReport()
{
StringBuilder r = new();
r.AppendLine("LPC " + GetType().Name);
r.AppendLine();
r.Append("Base Address: 0x");
r.AppendLine(_address.ToString("X4", CultureInfo.InvariantCulture));
r.AppendLine();
if (!Mutexes.WaitIsaBus(100))
return r.ToString();
r.AppendLine("Hardware Monitor Registers");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
for (int i = 0; i <= 0xF; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X2", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
r.Append(" ");
r.Append(ReadByte((byte)((i << 4) | j)).ToString("X2",
CultureInfo.InvariantCulture));
}
r.AppendLine();
}
r.AppendLine();
Mutexes.ReleaseIsaBus();
return r.ToString();
}
public void Update()
{
if (!Mutexes.WaitIsaBus(10))
return;
for (int i = 0; i < Voltages.Length; i++)
{
if (Chip == Chip.F71808E && i == 6)
{
// 0x26 is reserved on F71808E
Voltages[i] = 0;
}
else
{
int value = ReadByte((byte)(VOLTAGE_BASE_REG + i));
Voltages[i] = 0.008f * value;
}
}
for (int i = 0; i < Temperatures.Length; i++)
{
switch (Chip)
{
case Chip.F71858:
{
int tableMode = 0x3 & ReadByte(TEMPERATURE_CONFIG_REG);
int high = ReadByte((byte)(TEMPERATURE_BASE_REG + (2 * i)));
int low = ReadByte((byte)(TEMPERATURE_BASE_REG + (2 * i) + 1));
if (high is not 0xbb and not 0xcc)
{
int bits = 0;
switch (tableMode)
{
case 0:
break;
case 1:
bits = 0;
break;
case 2:
bits = (high & 0x80) << 8;
break;
case 3:
bits = (low & 0x01) << 15;
break;
}
bits |= high << 7;
bits |= (low & 0xe0) >> 1;
short value = (short)(bits & 0xfff0);
Temperatures[i] = value / 128.0f;
}
else
{
Temperatures[i] = null;
}
}
break;
default:
{
sbyte value = (sbyte)ReadByte((byte)(TEMPERATURE_BASE_REG + (2 * (i + 1))));
if (value is < sbyte.MaxValue and > 0)
Temperatures[i] = value;
else
Temperatures[i] = null;
}
break;
}
}
for (int i = 0; i < Fans.Length; i++)
{
int value = ReadByte(FAN_TACHOMETER_REG[i]) << 8;
value |= ReadByte((byte)(FAN_TACHOMETER_REG[i] + 1));
if (value > 0)
Fans[i] = value < 0x0fff ? 1.5e6f / value : 0;
else
Fans[i] = null;
}
for (int i = 0; i < Controls.Length; i++)
{
if (Chip == Chip.F71882 || Chip == Chip.F71889AD)
{
Controls[i] = ReadByte((byte)(FAN_PWM_REG[i])) * 100.0f / 0xFF;
}
else
{
Controls[i] = ReadByte((byte)(PWM_VALUES_OFFSET + i)) * 100.0f / 0xFF;
}
}
Mutexes.ReleaseIsaBus();
}
private void SaveDefaultFanPwmControl(int index)
{
if (!_restoreDefaultFanPwmControlRequired[index])
{
_initialFanPwmControl[index] = ReadByte(FAN_PWM_REG[index]);
_restoreDefaultFanPwmControlRequired[index] = true;
}
}
private void RestoreDefaultFanPwmControl(int index)
{
if (_restoreDefaultFanPwmControlRequired[index])
{
WriteByte(FAN_PWM_REG[index], _initialFanPwmControl[index]);
_restoreDefaultFanPwmControlRequired[index] = false;
}
}
private byte ReadByte(byte register)
{
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), register);
return Ring0.ReadIoPort((ushort)(_address + DATA_REGISTER_OFFSET));
}
private void WriteByte(byte register, byte value)
{
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), register);
Ring0.WriteIoPort((ushort)(_address + DATA_REGISTER_OFFSET), value);
}
// ReSharper disable InconsistentNaming
#pragma warning disable IDE1006 // Naming Styles
private const byte ADDRESS_REGISTER_OFFSET = 0x05;
private const byte DATA_REGISTER_OFFSET = 0x06;
private const byte PWM_VALUES_OFFSET = 0x2D;
private const byte TEMPERATURE_BASE_REG = 0x70;
private const byte TEMPERATURE_CONFIG_REG = 0x69;
private const byte VOLTAGE_BASE_REG = 0x20;
private readonly byte[] FAN_PWM_REG = { 0xA3, 0xB3, 0xC3, 0xD3 };
private readonly byte[] FAN_TACHOMETER_REG = { 0xA0, 0xB0, 0xC0, 0xD0 };
// ReSharper restore InconsistentNaming
#pragma warning restore IDE1006 // Naming Styles
}

View File

@@ -0,0 +1,14 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal interface IGigabyteController
{
bool Enable(bool enabled);
void Restore();
}

View File

@@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal interface ISuperIO
{
Chip Chip { get; }
float?[] Controls { get; }
float?[] Fans { get; }
float?[] Temperatures { get; }
// get voltage, temperature, fan and control channel values
float?[] Voltages { get; }
// set control value, null = auto
void SetControl(int index, byte? value);
// read and write GPIO
byte? ReadGpio(int index);
void WriteGpio(int index, byte value);
string GetReport();
void Update();
}

View File

@@ -0,0 +1,139 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Diagnostics;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class IT879xEcioPort
{
public IT879xEcioPort(ushort registerPort, ushort valuePort)
{
RegisterPort = registerPort;
ValuePort = valuePort;
}
public ushort RegisterPort { get; }
public ushort ValuePort { get; }
public bool Read(ushort offset, out byte value)
{
if (!Init(0xB0, offset))
{
value = 0;
return false;
}
return ReadFromValue(out value);
}
public bool Write(ushort offset, byte value)
{
if (!Init(0xB1, offset))
{
return false;
}
return WriteToValue(value);
}
private bool Init(byte command, ushort offset)
{
if (!WriteToRegister(command))
{
return false;
}
if (!WriteToValue((byte)((offset >> 8) & 0xFF)))
{
return false;
}
if (!WriteToValue((byte)(offset & 0xFF)))
{
return false;
}
return true;
}
private bool WriteToRegister(byte value)
{
if (!WaitIBE())
{
return false;
}
Ring0.WriteIoPort(RegisterPort, value);
return true;
}
private bool WriteToValue(byte value)
{
if (!WaitIBE())
{
return false;
}
Ring0.WriteIoPort(ValuePort, value);
return true;
}
private bool ReadFromValue(out byte value)
{
if (!WaitOBF())
{
value = 0;
return false;
}
value = Ring0.ReadIoPort(ValuePort);
return true;
}
private bool WaitIBE()
{
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
while ((Ring0.ReadIoPort(RegisterPort) & 2) != 0)
{
if (stopwatch.ElapsedMilliseconds > WAIT_TIMEOUT)
{
return false;
}
}
return true;
}
finally
{
stopwatch.Stop();
}
}
private bool WaitOBF()
{
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
while ((Ring0.ReadIoPort(RegisterPort) & 1) == 0)
{
if (stopwatch.ElapsedMilliseconds > WAIT_TIMEOUT)
{
return false;
}
}
return true;
}
finally
{
stopwatch.Stop();
}
}
private const long WAIT_TIMEOUT = 1000L;
}

View File

@@ -0,0 +1,619 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
// ReSharper disable once InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class IT87XX : ISuperIO
{
private const int MaxFanHeaders = 6;
private readonly ushort _address;
private readonly ushort _addressReg;
private readonly int _bankCount;
private readonly ushort _dataReg;
private readonly bool[] _fansDisabled = Array.Empty<bool>();
private readonly ushort _gpioAddress;
private readonly int _gpioCount;
private readonly bool _has16BitFanCounter;
private readonly bool _hasExtReg;
private readonly bool[] _initialFanOutputModeEnabled = new bool[3]; // Initial Fan Controller Main Control Register value.
private readonly byte[] _initialFanPwmControl = new byte[MaxFanHeaders]; // This will also store the 2nd control register value.
private readonly byte[] _initialFanPwmControlExt = new byte[MaxFanHeaders];
private readonly bool[] _restoreDefaultFanPwmControlRequired = new bool[MaxFanHeaders];
private readonly byte _version;
private readonly float _voltageGain;
private IGigabyteController _gigabyteController;
private readonly bool _requiresBankSelect; // Fix #780 Set to true for those chips that need a SelectBank(0) to fix dodgy temps and fan speeds
private bool SupportsMultipleBanks => _bankCount > 1;
public IT87XX(Chip chip, ushort address, ushort gpioAddress, byte version, Motherboard motherboard, IGigabyteController gigabyteController)
{
_address = address;
_version = version;
_addressReg = (ushort)(address + ADDRESS_REGISTER_OFFSET);
_dataReg = (ushort)(address + DATA_REGISTER_OFFSET);
_gpioAddress = gpioAddress;
_gigabyteController = gigabyteController;
_requiresBankSelect = false;
Chip = chip;
// Check vendor id
byte vendorId = ReadByte(VENDOR_ID_REGISTER, out bool valid);
if (!valid)
return;
bool hasMatchingVendorId = false;
foreach (byte iteVendorId in ITE_VENDOR_IDS)
{
if (iteVendorId == vendorId)
{
hasMatchingVendorId = true;
break;
}
}
if (!hasMatchingVendorId)
return;
// Bit 0x10 of the configuration register should always be 1
byte configuration = ReadByte(CONFIGURATION_REGISTER, out valid);
if (!valid || ((configuration & 0x10) == 0 && chip != Chip.IT8655E && chip != Chip.IT8665E))
return;
FAN_PWM_CTRL_REG = chip switch
{
Chip.IT8665E or Chip.IT8625E => new byte[] { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 },
Chip.IT8792E => new byte[] { 0x15, 0x16, 0x17 },
_ => new byte[] { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf }
};
_bankCount = chip switch
{
Chip.IT8689E => 4,
_ => 1
};
_hasExtReg = chip is Chip.IT8721F or
Chip.IT8728F or
Chip.IT8665E or
Chip.IT8686E or
Chip.IT8688E or
Chip.IT8689E or
Chip.IT87952E or
Chip.IT8628E or
Chip.IT8625E or
Chip.IT8620E or
Chip.IT8613E or
Chip.IT8792E or
Chip.IT8655E or
Chip.IT8631E or
Chip.IT8696E;
switch (chip)
{
case Chip.IT8613E:
Voltages = new float?[10];
Temperatures = new float?[4];
Fans = new float?[5];
Controls = new float?[4];
break;
case Chip.IT8625E:
Voltages = new float?[7];
Temperatures = new float?[3];
Fans = new float?[6];
Controls = new float?[6];
break;
case Chip.IT8628E:
Voltages = new float?[10];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[6];
break;
case Chip.IT8631E:
Voltages = new float?[9];
Temperatures = new float?[2];
Fans = new float?[2];
Controls = new float?[2];
break;
case Chip.IT8665E:
Voltages = new float?[9];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[6];
_requiresBankSelect = true;
break;
case Chip.IT8686E:
Voltages = new float?[10];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[5];
break;
case Chip.IT8688E:
Voltages = new float?[11];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[5];
break;
case Chip.IT8689E:
Voltages = new float?[10];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[6];
break;
case Chip.IT8696E:
Voltages = new float?[10];
Temperatures = new float?[6];
Fans = new float?[6];
Controls = new float?[6];
break;
case Chip.IT87952E:
Voltages = new float?[10];
Temperatures = new float?[3];
Fans = new float?[3];
Controls = new float?[3];
break;
case Chip.IT8655E:
Voltages = new float?[9];
Temperatures = new float?[6];
Fans = new float?[3];
Controls = new float?[3];
_requiresBankSelect = true;
break;
case Chip.IT8792E:
Voltages = new float?[9];
Temperatures = new float?[3];
Fans = new float?[3];
Controls = new float?[3];
break;
case Chip.IT8705F:
Voltages = new float?[9];
Temperatures = new float?[3];
Fans = new float?[3];
Controls = new float?[3];
break;
case Chip.IT8620E:
Voltages = new float?[9];
Temperatures = new float?[3];
Fans = new float?[5];
Controls = new float?[5];
break;
default:
Voltages = new float?[9];
Temperatures = new float?[3];
Fans = new float?[5];
Controls = new float?[3];
break;
}
_fansDisabled = new bool[Fans.Length];
// Voltage gain varies by model.
// Conflicting reports on IT8792E: either 0.0109 in linux drivers or 0.011 comparing with Gigabyte board & SIV SW.
_voltageGain = chip switch
{
Chip.IT8613E or Chip.IT8620E or Chip.IT8628E or Chip.IT8631E or Chip.IT8721F or Chip.IT8728F or Chip.IT8771E or Chip.IT8772E or Chip.IT8686E or Chip.IT8688E or Chip.IT8689E or Chip.IT8696E => 0.012f,
Chip.IT8625E or Chip.IT8792E or Chip.IT87952E => 0.011f,
Chip.IT8655E or Chip.IT8665E => 0.0109f,
_ => 0.016f
};
// Older IT8705F and IT8721F revisions do not have 16-bit fan counters.
_has16BitFanCounter = (chip != Chip.IT8705F || version >= 3) && (chip != Chip.IT8712F || version >= 8);
// Disable any fans that aren't set with 16-bit fan counters
if (_has16BitFanCounter)
{
int modes = ReadByte(FAN_TACHOMETER_16BIT_REGISTER, out valid);
if (!valid)
return;
if (Fans.Length >= 5)
{
_fansDisabled[3] = (modes & (1 << 4)) == 0;
_fansDisabled[4] = (modes & (1 << 5)) == 0;
}
if (Fans.Length >= 6)
_fansDisabled[5] = (modes & (1 << 2)) == 0;
}
// Set the number of GPIO sets
_gpioCount = chip switch
{
Chip.IT8712F or Chip.IT8716F or Chip.IT8718F or Chip.IT8726F => 5,
Chip.IT8720F or Chip.IT8721F => 8,
_ => 0
};
}
public Chip Chip { get; }
public float?[] Controls { get; } = Array.Empty<float?>();
public float?[] Fans { get; } = Array.Empty<float?>();
public float?[] Temperatures { get; } = Array.Empty<float?>();
public float?[] Voltages { get; } = Array.Empty<float?>();
public byte? ReadGpio(int index)
{
if (index >= _gpioCount)
return null;
return Ring0.ReadIoPort((ushort)(_gpioAddress + index));
}
public void WriteGpio(int index, byte value)
{
if (index >= _gpioCount)
return;
Ring0.WriteIoPort((ushort)(_gpioAddress + index), value);
}
public void SetControl(int index, byte? value)
{
if (index < 0 || index >= Controls.Length)
throw new ArgumentOutOfRangeException(nameof(index));
if (!Mutexes.WaitIsaBus(10))
return;
if (value.HasValue)
{
SaveDefaultFanPwmControl(index);
// Disable the controller when setting values to prevent it from overriding them
if (_gigabyteController != null)
_gigabyteController.Enable(false);
if (index < 3 && !_initialFanOutputModeEnabled[index])
WriteByte(FAN_MAIN_CTRL_REG, (byte)(ReadByte(FAN_MAIN_CTRL_REG, out _) | (1 << index)));
if (_hasExtReg)
{
if (Chip == Chip.IT8689E)
{
WriteByte(FAN_PWM_CTRL_REG[index], 0x7F);
}
else
{
WriteByte(FAN_PWM_CTRL_REG[index], (byte)(_initialFanPwmControl[index] & 0x7F));
}
WriteByte(FAN_PWM_CTRL_EXT_REG[index], value.Value);
}
else
{
WriteByte(FAN_PWM_CTRL_REG[index], (byte)(value.Value >> 1));
}
}
else
{
RestoreDefaultFanPwmControl(index);
}
Mutexes.ReleaseIsaBus();
}
public string GetReport()
{
StringBuilder r = new();
r.AppendLine("LPC " + GetType().Name);
r.AppendLine();
r.Append("Chip ID: 0x");
r.AppendLine(Chip.ToString("X"));
r.Append("Chip Version: 0x");
r.AppendLine(_version.ToString("X", CultureInfo.InvariantCulture));
r.Append("Base Address: 0x");
r.AppendLine(_address.ToString("X4", CultureInfo.InvariantCulture));
r.Append("GPIO Address: 0x");
r.AppendLine(_gpioAddress.ToString("X4", CultureInfo.InvariantCulture));
r.AppendLine();
if (!Mutexes.WaitIsaBus(100))
return r.ToString();
if (_requiresBankSelect)
SelectBank(0);
// dump memory of all banks if supported by chip
for (byte b = 0; b < _bankCount; b++)
{
if (SupportsMultipleBanks && b > 0)
{
SelectBank(b);
}
r.AppendLine($"Environment Controller Registers Bank {b}");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
for (int i = 0; i <= 0xA; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X2", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
r.Append(" ");
byte value = ReadByte((byte)((i << 4) | j), out bool valid);
r.Append(valid ? value.ToString("X2", CultureInfo.InvariantCulture) : "??");
}
r.AppendLine();
}
r.AppendLine();
}
if (SupportsMultipleBanks)
{
SelectBank(0);
}
r.AppendLine();
r.AppendLine("GPIO Registers");
r.AppendLine();
for (int i = 0; i < _gpioCount; i++)
{
r.Append(" ");
r.Append(ReadGpio(i)?.ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
r.AppendLine();
Mutexes.ReleaseIsaBus();
return r.ToString();
}
/// <summary>
/// Selects another bank. Memory from 0x10-0xAF swaps to data from new bank.
/// Beware to select the default bank 0 after changing.
/// Bank selection is reset after power cycle.
/// </summary>
/// <param name="bankIndex">New bank index. Can be a value of 0-3.</param>
private void SelectBank(byte bankIndex)
{
if (bankIndex >= _bankCount)
return; // current chip does not support that many banks
// hard cap SelectBank to 2 bit values. If we ever have chips with more bank bits rewrite this method.
bankIndex &= 0x3;
byte value = ReadByte(BANK_REGISTER, out bool valid);
if (valid)
{
value &= 0x9F;
value |= (byte)(bankIndex << 5);
WriteByte(BANK_REGISTER, value);
}
}
public void Update()
{
if (!Mutexes.WaitIsaBus(10))
return;
// Is this needed on every update? Yes, until a way to detect resume from sleep/hibernation is added, as that invalidates the bank select.
if (_requiresBankSelect)
SelectBank(0);
for (int i = 0; i < Voltages.Length; i++)
{
float value = _voltageGain * ReadByte((byte)(VOLTAGE_BASE_REG + i), out bool valid);
if (!valid)
continue;
if (value > 0)
Voltages[i] = value;
else
Voltages[i] = null;
}
for (int i = 0; i < Temperatures.Length; i++)
{
sbyte value = (sbyte)ReadByte((byte)(TEMPERATURE_BASE_REG + i), out bool valid);
if (!valid)
continue;
if (value is < sbyte.MaxValue and > 0)
Temperatures[i] = value;
else
Temperatures[i] = null;
}
if (_has16BitFanCounter)
{
for (int i = 0; i < Fans.Length; i++)
{
if (_fansDisabled[i])
continue;
int value = ReadByte(FAN_TACHOMETER_REG[i], out bool valid);
if (!valid)
continue;
value |= ReadByte(FAN_TACHOMETER_EXT_REG[i], out valid) << 8;
if (!valid)
continue;
if (value > 0x3f)
Fans[i] = value < 0xffff ? 1.35e6f / (value * 2) : 0;
else
Fans[i] = null;
}
}
else
{
for (int i = 0; i < Fans.Length; i++)
{
int value = ReadByte(FAN_TACHOMETER_REG[i], out bool valid);
if (!valid)
continue;
int divisor = 2;
if (i < 2)
{
int divisors = ReadByte(FAN_TACHOMETER_DIVISOR_REGISTER, out valid);
if (!valid)
continue;
divisor = 1 << ((divisors >> (3 * i)) & 0x7);
}
if (value > 0)
Fans[i] = value < 0xff ? 1.35e6f / (value * divisor) : 0;
else
Fans[i] = null;
}
}
for (int i = 0; i < Controls.Length; i++)
{
byte value = ReadByte(FAN_PWM_CTRL_REG[i], out bool valid);
if (!valid)
continue;
if ((value & 0x80) > 0)
{
// Automatic operation (value can't be read).
Controls[i] = null;
}
else
{
// Software operation.
if (_hasExtReg)
{
value = ReadByte(FAN_PWM_CTRL_EXT_REG[i], out valid);
if (valid)
Controls[i] = (float)Math.Round(value * 100.0f / 0xFF);
}
else
{
Controls[i] = (float)Math.Round((value & 0x7F) * 100.0f / 0x7F);
}
}
}
Mutexes.ReleaseIsaBus();
}
private byte ReadByte(byte register, out bool valid)
{
Ring0.WriteIoPort(_addressReg, register);
byte value = Ring0.ReadIoPort(_dataReg);
valid = register == Ring0.ReadIoPort(_addressReg) || Chip == Chip.IT8688E;
// IT8688E doesn't return the value we wrote to
// addressReg when we read it back.
return value;
}
private void WriteByte(byte register, byte value)
{
Ring0.WriteIoPort(_addressReg, register);
Ring0.WriteIoPort(_dataReg, value);
Ring0.ReadIoPort(_addressReg);
}
private void SaveDefaultFanPwmControl(int index)
{
if (!_restoreDefaultFanPwmControlRequired[index])
{
_initialFanPwmControl[index] = ReadByte(FAN_PWM_CTRL_REG[index], out bool _);
if (index < 3)
_initialFanOutputModeEnabled[index] = ReadByte(FAN_MAIN_CTRL_REG, out bool _) != 0; // Save default control reg value.
if (_hasExtReg)
_initialFanPwmControlExt[index] = ReadByte(FAN_PWM_CTRL_EXT_REG[index], out _);
}
_restoreDefaultFanPwmControlRequired[index] = true;
}
private void RestoreDefaultFanPwmControl(int index)
{
if (_restoreDefaultFanPwmControlRequired[index])
{
WriteByte(FAN_PWM_CTRL_REG[index], _initialFanPwmControl[index]);
if (index < 3)
{
byte value = ReadByte(FAN_MAIN_CTRL_REG, out _);
bool isEnabled = (value & (1 << index)) != 0;
if (isEnabled != _initialFanOutputModeEnabled[index])
WriteByte(FAN_MAIN_CTRL_REG, (byte)(value ^ (1 << index)));
}
if (_hasExtReg)
WriteByte(FAN_PWM_CTRL_EXT_REG[index], _initialFanPwmControlExt[index]);
_restoreDefaultFanPwmControlRequired[index] = false;
// restore the GB controller when all fans become restored
if (_gigabyteController != null && _restoreDefaultFanPwmControlRequired.All(e => e == false))
_gigabyteController.Restore();
}
}
// ReSharper disable InconsistentNaming
#pragma warning disable IDE1006 // Naming Styles
private const byte ADDRESS_REGISTER_OFFSET = 0x05;
private const byte CONFIGURATION_REGISTER = 0x00;
private const byte DATA_REGISTER_OFFSET = 0x06;
private const byte BANK_REGISTER = 0x06; // bit 5-6 define selected bank
private const byte FAN_TACHOMETER_16BIT_REGISTER = 0x0C;
private const byte FAN_TACHOMETER_DIVISOR_REGISTER = 0x0B;
private readonly byte[] ITE_VENDOR_IDS = { 0x90, 0x7F };
private const byte TEMPERATURE_BASE_REG = 0x29;
private const byte VENDOR_ID_REGISTER = 0x58;
private const byte VOLTAGE_BASE_REG = 0x20;
private readonly byte[] FAN_PWM_CTRL_REG;
private readonly byte[] FAN_PWM_CTRL_EXT_REG = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
private readonly byte[] FAN_TACHOMETER_EXT_REG = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d };
private readonly byte[] FAN_TACHOMETER_REG = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c };
// Address of the Fan Controller Main Control Register.
// No need for the 2nd control register (bit 7 of 0x15 0x16 0x17),
// as PWM value will set it to manual mode when new value is set.
private const byte FAN_MAIN_CTRL_REG = 0x13;
#pragma warning restore IDE1006 // Naming Styles
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,335 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class Ipmi : ISuperIO
{
// ReSharper disable InconsistentNaming
private const byte COMMAND_FAN_LEVEL = 0x70;
private const byte COMMAND_FAN_MODE = 0x45;
private const byte COMMAND_GET_SDR = 0x23;
private const byte COMMAND_GET_SDR_REPOSITORY_INFO = 0x20;
private const byte COMMAND_GET_SENSOR_READING = 0x2d;
private const byte FAN_MODE_FULL = 0x01;
private const byte FAN_MODE_OPTIMAL = 0x02;
private const byte NETWORK_FUNCTION_SENSOR_EVENT = 0x04;
private const byte NETWORK_FUNCTION_STORAGE = 0x0a;
private const byte NETWORK_FUNCTION_SUPERMICRO = 0x30;
// ReSharper restore InconsistentNaming
private readonly List<string> _controlNames = new();
private readonly List<float> _controls = new();
private readonly List<string> _fanNames = new();
private readonly List<float> _fans = new();
private readonly ManagementObject _ipmi;
private readonly Manufacturer _manufacturer;
private readonly List<Interop.Ipmi.Sdr> _sdrs = new();
private readonly List<string> _temperatureNames = new();
private readonly List<float> _temperatures = new();
private readonly List<string> _voltageNames = new();
private readonly List<float> _voltages = new();
private bool _touchedFans;
public Ipmi(Manufacturer manufacturer)
{
Chip = Chip.IPMI;
_manufacturer = manufacturer;
using ManagementClass ipmiClass = new("root\\WMI", "Microsoft_IPMI", null);
foreach (ManagementBaseObject ipmi in ipmiClass.GetInstances())
{
if (ipmi is ManagementObject managementObject)
_ipmi = managementObject;
}
// Fan control is exposed for Supermicro only as it differs between IPMI implementations
if (_manufacturer == Manufacturer.Supermicro)
{
_controlNames.Add("CPU Fan");
_controlNames.Add("System Fan");
}
// Perform an early update to count the number of sensors and get their names
Update();
Controls = new float?[_controls.Count];
Fans = new float?[_fans.Count];
Temperatures = new float?[_temperatures.Count];
Voltages = new float?[_voltages.Count];
}
public Chip Chip { get; }
public float?[] Controls { get; }
public float?[] Fans { get; }
public float?[] Temperatures { get; }
public float?[] Voltages { get; }
public string GetReport()
{
StringBuilder sb = new();
Update(sb);
return sb.ToString();
}
public void SetControl(int index, byte? value)
{
if (_manufacturer == Manufacturer.Supermicro)
{
if (value != null || _touchedFans)
{
_touchedFans = true;
if (value == null)
{
RunIPMICommand(COMMAND_FAN_MODE, NETWORK_FUNCTION_SUPERMICRO, new byte[] { 0x01 /* Set */, FAN_MODE_OPTIMAL });
}
else
{
byte[] fanMode = RunIPMICommand(COMMAND_FAN_MODE, NETWORK_FUNCTION_SUPERMICRO, new byte[] { 0x00 });
if (fanMode == null || fanMode.Length < 2 || fanMode[0] != 0 || fanMode[1] != FAN_MODE_FULL)
RunIPMICommand(COMMAND_FAN_MODE, NETWORK_FUNCTION_SUPERMICRO, new byte[] { 0x01 /* Set */, FAN_MODE_FULL });
float speed = (float)value / 255.0f * 100.0f;
RunIPMICommand(COMMAND_FAN_LEVEL, NETWORK_FUNCTION_SUPERMICRO, new byte[] { 0x66, 0x01 /* Set */, (byte)index, (byte)speed });
}
}
}
else
{
throw new NotImplementedException();
}
}
public void Update()
{
Update(null);
}
private unsafe void Update(StringBuilder stringBuilder)
{
_fans.Clear();
_temperatures.Clear();
_voltages.Clear();
_controls.Clear();
if (_sdrs.Count == 0 || stringBuilder != null)
{
byte[] sdrInfo = RunIPMICommand(COMMAND_GET_SDR_REPOSITORY_INFO, NETWORK_FUNCTION_STORAGE, new byte[] { });
if (sdrInfo?[0] == 0)
{
int recordCount = (sdrInfo[3] * 256) + sdrInfo[2];
byte recordLower = 0;
byte recordUpper = 0;
for (int i = 0; i < recordCount; ++i)
{
byte[] sdrRaw = RunIPMICommand(COMMAND_GET_SDR, NETWORK_FUNCTION_STORAGE, new byte[] { 0, 0, recordLower, recordUpper, 0, 0xff });
if (sdrRaw?.Length >= 3 && sdrRaw[0] == 0)
{
recordLower = sdrRaw[1];
recordUpper = sdrRaw[2];
fixed (byte* pSdr = sdrRaw)
{
Interop.Ipmi.Sdr sdr = (Interop.Ipmi.Sdr)Marshal.PtrToStructure((IntPtr)pSdr + 3, typeof(Interop.Ipmi.Sdr));
_sdrs.Add(sdr);
stringBuilder?.AppendLine("IPMI sensor " + i + " num: " + sdr.sens_num + " info: " + BitConverter.ToString(sdrRaw).Replace("-", ""));
}
}
else
{
break;
}
}
}
}
foreach (Interop.Ipmi.Sdr sdr in _sdrs)
{
if (sdr.rectype == 1)
{
byte[] reading = RunIPMICommand(COMMAND_GET_SENSOR_READING, NETWORK_FUNCTION_SENSOR_EVENT, new[] { sdr.sens_num });
if (reading?.Length > 1 && reading[0] == 0)
{
switch (sdr.sens_type)
{
case 1:
_temperatures.Add(RawToFloat(reading[1], sdr));
if (Temperatures == null || Temperatures.Length == 0)
_temperatureNames.Add(sdr.id_string.Replace(" Temp", ""));
break;
case 2:
_voltages.Add(RawToFloat(reading[1], sdr));
if (Voltages == null || Voltages.Length == 0)
_voltageNames.Add(sdr.id_string);
break;
case 4:
_fans.Add(RawToFloat(reading[1], sdr));
if (Fans == null || Fans.Length == 0)
_fanNames.Add(sdr.id_string);
break;
}
stringBuilder?.AppendLine("IPMI sensor num: " + sdr.sens_num + " reading: " + BitConverter.ToString(reading).Replace("-", ""));
}
}
}
if (_manufacturer == Manufacturer.Supermicro)
{
for (int i = 0; i < _controlNames.Count; ++i)
{
byte[] fanLevel = RunIPMICommand(COMMAND_FAN_LEVEL, NETWORK_FUNCTION_SUPERMICRO, new byte[] { 0x66, 0x00 /* Get */, (byte)i });
if (fanLevel?.Length >= 2 && fanLevel[0] == 0)
{
_controls.Add(fanLevel[1]);
stringBuilder?.AppendLine("IPMI fan " + i + ": " + BitConverter.ToString(fanLevel).Replace("-", ""));
}
}
}
if (Temperatures != null)
{
for (int i = 0; i < Math.Min(_temperatures.Count, Temperatures.Length); ++i)
Temperatures[i] = _temperatures[i];
}
if (Voltages != null)
{
for (int i = 0; i < Math.Min(_voltages.Count, Voltages.Length); ++i)
Voltages[i] = _voltages[i];
}
if (Fans != null)
{
for (int i = 0; i < Math.Min(_fans.Count, Fans.Length); ++i)
Fans[i] = _fans[i];
}
if (Controls != null)
{
for (int i = 0; i < Math.Min(_controls.Count, Controls.Length); ++i)
Controls[i] = _controls[i];
}
}
public IEnumerable<Temperature> GetTemperatures()
{
for (int i = 0; i < _temperatureNames.Count; i++)
yield return new Temperature(_temperatureNames[i], i);
}
public IEnumerable<Fan> GetFans()
{
for (int i = 0; i < _fanNames.Count; i++)
yield return new Fan(_fanNames[i], i);
}
public IEnumerable<Voltage> GetVoltages()
{
for (int i = 0; i < _voltageNames.Count; i++)
yield return new Voltage(_voltageNames[i], i);
}
public IEnumerable<Control> GetControls()
{
for (int i = 0; i < _controlNames.Count; i++)
yield return new Control(_controlNames[i], i);
}
public static bool IsBmcPresent()
{
try
{
using ManagementObjectSearcher searcher = new("root\\WMI", "SELECT * FROM Microsoft_IPMI WHERE Active='True'");
return searcher.Get().Count > 0;
}
catch
{
return false;
}
}
public byte? ReadGpio(int index)
{
return null;
}
public void WriteGpio(int index, byte value)
{ }
private byte[] RunIPMICommand(byte command, byte networkFunction, byte[] requestData)
{
using ManagementBaseObject inParams = _ipmi.GetMethodParameters("RequestResponse");
inParams["NetworkFunction"] = networkFunction;
inParams["Lun"] = 0;
inParams["ResponderAddress"] = 0x20;
inParams["Command"] = command;
inParams["RequestDataSize"] = requestData.Length;
inParams["RequestData"] = requestData;
using ManagementBaseObject outParams = _ipmi.InvokeMethod("RequestResponse", inParams, null);
return (byte[])outParams["ResponseData"];
}
// Ported from ipmiutil
// Bare minimum to read Supermicro X13 IPMI sensors, may need expanding for other boards
private static float RawToFloat(byte sensorReading, Interop.Ipmi.Sdr sdr)
{
double reading = sensorReading;
int m = sdr.m + ((sdr.m_t & 0xc0) << 2);
if (Convert.ToBoolean(m & 0x0200))
m -= 0x0400;
int b = sdr.b + ((sdr.b_a & 0xc0) << 2);
if (Convert.ToBoolean(b & 0x0200))
b -= 0x0400;
int rx = (sdr.rx_bx & 0xf0) >> 4;
if (Convert.ToBoolean(rx & 0x08))
rx -= 0x10;
int bExp = sdr.rx_bx & 0x0f;
if (Convert.ToBoolean(bExp & 0x08))
bExp -= 0x10;
if ((sdr.sens_units & 0xc0) != 0)
reading = Convert.ToBoolean(sensorReading & 0x80) ? sensorReading - 0x100 : sensorReading;
reading *= m;
reading += b * Math.Pow(10, bExp);
reading *= Math.Pow(10, rx);
if (sdr.linear != 0)
throw new NotImplementedException();
return (float)reading;
}
}

View File

@@ -0,0 +1,199 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using LibreHardwareMonitor.Hardware.Cpu;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
/// <summary>
/// This is a controller present on some Gigabyte motherboards for both Intel and AMD, that is in custom firmware
/// loaded onto the 2nd ITE EC.
/// It can be accessed by using memory mapped IO, mapping its internal RAM onto main RAM via the ISA Bridge.
/// This class can disable it so that the regular IT87XX code can drive the fans.
/// </summary>
internal class IsaBridgeGigabyteController : IGigabyteController
{
private const uint ControllerAddressRange = 0xFF;
private const int ControllerEnableRegister = 0x47;
private const uint ControllerFanControlArea = 0x900;
/// <summary>
/// Base address in PCI RAM that maps to the EC's RAM
/// </summary>
private readonly uint _controllerBaseAddress;
private readonly Vendor _vendor;
private bool? _initialState;
public IsaBridgeGigabyteController(uint address, Vendor vendor)
{
_controllerBaseAddress = address;
_vendor = vendor;
}
/// <summary>
/// Enable/Disable Fan Control
/// </summary>
/// <param name="enabled"></param>
/// <returns>true on success</returns>
public bool Enable(bool enabled)
{
return _vendor switch
{
Vendor.Intel => IntelEnable(enabled),
Vendor.AMD => AmdEnable(enabled),
_ => false
};
}
private bool IntelEnable(bool enabled)
{
if (!Mutexes.WaitPciBus(10))
return false;
bool result = false;
uint intelIsaBridgeAddress = Ring0.GetPciAddress(0x0, 0x1F, 0x0);
const uint ioOrMemoryPortDecodeEnableRegister = 0xD8;
const uint romAddressRange2Register = 0x98;
uint controllerFanControlAddress = _controllerBaseAddress + ControllerFanControlArea;
Ring0.ReadPciConfig(intelIsaBridgeAddress, ioOrMemoryPortDecodeEnableRegister, out uint originalDecodeEnableRegister);
Ring0.ReadPciConfig(intelIsaBridgeAddress, romAddressRange2Register, out uint originalRomAddressRegister);
bool originalMmIoEnabled = false;
if (!enabled)
{
originalMmIoEnabled = ((int)originalDecodeEnableRegister & 1) == 0 || ((int)originalRomAddressRegister & 1) == 1;
}
else
{
originalMmIoEnabled = ((int)originalDecodeEnableRegister & 1) == 0 && ((int)originalRomAddressRegister & 1) == 1;
}
if (enabled == originalMmIoEnabled)
{
result = Enable(enabled, new IntPtr(controllerFanControlAddress));
Mutexes.ReleasePciBus();
return result;
}
uint lpcBiosDecodeEnable;
uint lpcMemoryRange;
if (enabled)
{
lpcBiosDecodeEnable = ioOrMemoryPortDecodeEnableRegister & ~(uint)(1 << 0);
lpcMemoryRange = romAddressRange2Register | (uint)(1 << 0);
}
else
{
lpcBiosDecodeEnable = Convert.ToUInt32(ioOrMemoryPortDecodeEnableRegister | (uint)(1 << 0));
lpcMemoryRange = Convert.ToUInt32(romAddressRange2Register & ~(uint)(1 << 0));
}
Ring0.WritePciConfig(intelIsaBridgeAddress, ioOrMemoryPortDecodeEnableRegister, lpcBiosDecodeEnable);
Ring0.WritePciConfig(intelIsaBridgeAddress, romAddressRange2Register, lpcMemoryRange);
result = Enable(enabled, new IntPtr(controllerFanControlAddress));
Mutexes.ReleasePciBus();
return result;
}
private bool AmdEnable(bool enabled)
{
if (!Mutexes.WaitPciBus(10))
return false;
// see D14F3x https://www.amd.com/system/files/TechDocs/55072_AMD_Family_15h_Models_70h-7Fh_BKDG.pdf
uint amdIsaBridgeAddress = Ring0.GetPciAddress(0x0, 0x14, 0x3);
const uint ioOrMemoryPortDecodeEnableRegister = 0x48;
const uint memoryRangePortEnableMask = 0x1 << 5;
const uint pciMemoryAddressForLpcTargetCyclesRegister = 0x60;
const uint romAddressRange2Register = 0x6C;
uint controllerFanControlAddress = _controllerBaseAddress + ControllerFanControlArea;
uint pciAddressStart = _controllerBaseAddress >> 0x10;
uint pciAddressEnd = pciAddressStart + 1;
uint enabledPciMemoryAddressRegister = pciAddressEnd << 0x10 | pciAddressStart;
uint enabledRomAddressRegister = 0xFFFFU << 0x10 | pciAddressEnd;
Ring0.ReadPciConfig(amdIsaBridgeAddress, ioOrMemoryPortDecodeEnableRegister, out uint originalDecodeEnableRegister);
Ring0.ReadPciConfig(amdIsaBridgeAddress, pciMemoryAddressForLpcTargetCyclesRegister, out uint originalPciMemoryAddressRegister);
Ring0.ReadPciConfig(amdIsaBridgeAddress, romAddressRange2Register, out uint originalRomAddressRegister);
bool originalMmIoEnabled = (originalDecodeEnableRegister & memoryRangePortEnableMask) != 0 &&
originalPciMemoryAddressRegister == enabledPciMemoryAddressRegister &&
originalRomAddressRegister == enabledRomAddressRegister;
if (!originalMmIoEnabled)
{
Ring0.WritePciConfig(amdIsaBridgeAddress, ioOrMemoryPortDecodeEnableRegister, originalDecodeEnableRegister | memoryRangePortEnableMask);
Ring0.WritePciConfig(amdIsaBridgeAddress, pciMemoryAddressForLpcTargetCyclesRegister, enabledPciMemoryAddressRegister);
Ring0.WritePciConfig(amdIsaBridgeAddress, romAddressRange2Register, enabledRomAddressRegister);
}
bool result = Enable(enabled, new IntPtr(controllerFanControlAddress));
// Restore previous values
if (!originalMmIoEnabled)
{
Ring0.WritePciConfig(amdIsaBridgeAddress, ioOrMemoryPortDecodeEnableRegister, originalDecodeEnableRegister);
Ring0.WritePciConfig(amdIsaBridgeAddress, pciMemoryAddressForLpcTargetCyclesRegister, originalPciMemoryAddressRegister);
Ring0.WritePciConfig(amdIsaBridgeAddress, romAddressRange2Register, originalRomAddressRegister);
}
Mutexes.ReleasePciBus();
return result;
}
private bool Enable(bool enabled, IntPtr pciMmIoBaseAddress)
{
// Map PCI memory to this process memory
if (!InpOut.Open())
return false;
IntPtr mapped = InpOut.MapMemory(pciMmIoBaseAddress, ControllerAddressRange, out IntPtr handle);
if (mapped == IntPtr.Zero)
return false;
bool current = Convert.ToBoolean(Marshal.ReadByte(mapped, ControllerEnableRegister));
_initialState ??= current;
// Update Controller State
if (current != enabled)
{
Marshal.WriteByte(mapped, ControllerEnableRegister, Convert.ToByte(enabled));
// Give it some time to see the change
Thread.Sleep(500);
}
InpOut.UnmapMemory(handle, mapped);
return true;
}
/// <summary>
/// Restore settings back to initial values
/// </summary>
public void Restore()
{
if (_initialState.HasValue)
Enable(_initialState.Value);
}
}

View File

@@ -0,0 +1,301 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
// ReSharper disable once InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class LMSensors
{
private const string HwMonPath = "/sys/class/hwmon/";
private readonly List<ISuperIO> _superIOs = [];
public LMSensors()
{
if (!Directory.Exists(HwMonPath))
return;
foreach (string basePath in Directory.GetDirectories(HwMonPath))
{
foreach (string devicePath in new[] { "/device", string.Empty })
{
string path = basePath + devicePath;
string name = null;
try
{
using StreamReader reader = new(path + "/name");
name = reader.ReadLine();
}
catch (IOException)
{ }
switch (name)
{
case "atk0110":
_superIOs.Add(new LMChip(Chip.ATK0110, path));
break;
case "f71858fg":
_superIOs.Add(new LMChip(Chip.F71858, path));
break;
case "f71862fg":
_superIOs.Add(new LMChip(Chip.F71862, path));
break;
case "f71869":
_superIOs.Add(new LMChip(Chip.F71869, path));
break;
case "f71869a":
_superIOs.Add(new LMChip(Chip.F71869A, path));
break;
case "f71882fg":
_superIOs.Add(new LMChip(Chip.F71882, path));
break;
case "f71889a":
_superIOs.Add(new LMChip(Chip.F71889AD, path));
break;
case "f71878ad":
_superIOs.Add(new LMChip(Chip.F71878AD, path));
break;
case "f71889ed":
_superIOs.Add(new LMChip(Chip.F71889ED, path));
break;
case "f71889fg":
_superIOs.Add(new LMChip(Chip.F71889F, path));
break;
case "f71808e":
_superIOs.Add(new LMChip(Chip.F71808E, path));
break;
case "it8705":
_superIOs.Add(new LMChip(Chip.IT8705F, path));
break;
case "it8712":
_superIOs.Add(new LMChip(Chip.IT8712F, path));
break;
case "it8716":
_superIOs.Add(new LMChip(Chip.IT8716F, path));
break;
case "it8718":
_superIOs.Add(new LMChip(Chip.IT8718F, path));
break;
case "it8720":
_superIOs.Add(new LMChip(Chip.IT8720F, path));
break;
case "nct6775":
_superIOs.Add(new LMChip(Chip.NCT6771F, path));
break;
case "nct6776":
_superIOs.Add(new LMChip(Chip.NCT6776F, path));
break;
case "nct6779":
_superIOs.Add(new LMChip(Chip.NCT6779D, path));
break;
case "nct6791":
_superIOs.Add(new LMChip(Chip.NCT6791D, path));
break;
case "nct6792":
_superIOs.Add(new LMChip(Chip.NCT6792D, path));
break;
case "nct6793":
_superIOs.Add(new LMChip(Chip.NCT6793D, path));
break;
case "nct6795":
_superIOs.Add(new LMChip(Chip.NCT6795D, path));
break;
case "nct6796":
_superIOs.Add(new LMChip(Chip.NCT6796D, path));
break;
case "nct6797":
_superIOs.Add(new LMChip(Chip.NCT6797D, path));
break;
case "nct6798":
_superIOs.Add(new LMChip(Chip.NCT6798D, path));
break;
case "nct6799":
_superIOs.Add(new LMChip(Chip.NCT6799D, path));
break;
case "w83627ehf":
_superIOs.Add(new LMChip(Chip.W83627EHF, path));
break;
case "w83627dhg":
_superIOs.Add(new LMChip(Chip.W83627DHG, path));
break;
case "w83667hg":
_superIOs.Add(new LMChip(Chip.W83667HG, path));
break;
case "w83627hf":
_superIOs.Add(new LMChip(Chip.W83627HF, path));
break;
case "w83627thf":
_superIOs.Add(new LMChip(Chip.W83627THF, path));
break;
case "w83687thf":
_superIOs.Add(new LMChip(Chip.W83687THF, path));
break;
}
}
}
}
public IReadOnlyList<ISuperIO> SuperIO
{
get { return _superIOs; }
}
public void Close()
{
foreach (ISuperIO superIO in _superIOs)
{
if (superIO is LMChip lmChip)
lmChip.Close();
}
}
private class LMChip : ISuperIO
{
private readonly FileStream[] _fanStreams;
private readonly FileStream[] _temperatureStreams;
private readonly FileStream[] _voltageStreams;
private string _path;
public LMChip(Chip chip, string path)
{
_path = path;
Chip = chip;
string[] voltagePaths = Directory.GetFiles(path, "in*_input");
Voltages = new float?[voltagePaths.Length];
_voltageStreams = new FileStream[voltagePaths.Length];
for (int i = 0; i < voltagePaths.Length; i++)
_voltageStreams[i] = new FileStream(voltagePaths[i], FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
string[] temperaturePaths = Directory.GetFiles(path, "temp*_input");
Temperatures = new float?[temperaturePaths.Length];
_temperatureStreams = new FileStream[temperaturePaths.Length];
for (int i = 0; i < temperaturePaths.Length; i++)
_temperatureStreams[i] = new FileStream(temperaturePaths[i], FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
string[] fanPaths = Directory.GetFiles(path, "fan*_input");
Fans = new float?[fanPaths.Length];
_fanStreams = new FileStream[fanPaths.Length];
for (int i = 0; i < fanPaths.Length; i++)
_fanStreams[i] = new FileStream(fanPaths[i], FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
Controls = Array.Empty<float?>();
}
public Chip Chip { get; }
public float?[] Controls { get; }
public float?[] Fans { get; }
public float?[] Temperatures { get; }
public float?[] Voltages { get; }
public byte? ReadGpio(int index)
{
return null;
}
public void WriteGpio(int index, byte value)
{ }
public string GetReport()
{
return null;
}
public void SetControl(int index, byte? value)
{ }
public void Update()
{
for (int i = 0; i < Voltages.Length; i++)
{
string s = ReadFirstLine(_voltageStreams[i]);
try
{
Voltages[i] = 0.001f *
long.Parse(s, CultureInfo.InvariantCulture);
}
catch
{
Voltages[i] = null;
}
}
for (int i = 0; i < Temperatures.Length; i++)
{
string s = ReadFirstLine(_temperatureStreams[i]);
try
{
Temperatures[i] = 0.001f *
long.Parse(s, CultureInfo.InvariantCulture);
}
catch
{
Temperatures[i] = null;
}
}
for (int i = 0; i < Fans.Length; i++)
{
string s = ReadFirstLine(_fanStreams[i]);
try
{
Fans[i] = long.Parse(s, CultureInfo.InvariantCulture);
}
catch
{
Fans[i] = null;
}
}
}
private static string ReadFirstLine(Stream stream)
{
StringBuilder sb = new();
try
{
stream.Seek(0, SeekOrigin.Begin);
int b = stream.ReadByte();
while (b is not -1 and not 10)
{
sb.Append((char)b);
b = stream.ReadByte();
}
}
catch
{ }
return sb.ToString();
}
public void Close()
{
foreach (FileStream stream in _voltageStreams)
stream.Close();
foreach (FileStream stream in _temperatureStreams)
stream.Close();
foreach (FileStream stream in _fanStreams)
stream.Close();
}
}
}

View File

@@ -0,0 +1,751 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using LibreHardwareMonitor.Hardware.Cpu;
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class LpcIO
{
private readonly StringBuilder _report = new();
private readonly List<ISuperIO> _superIOs = new();
public LpcIO(Motherboard motherboard)
{
if (!Ring0.IsOpen || !Mutexes.WaitIsaBus(100))
return;
Detect(motherboard);
Mutexes.ReleaseIsaBus();
if (Ipmi.IsBmcPresent())
_superIOs.Add(new Ipmi(motherboard.Manufacturer));
}
public ISuperIO[] SuperIO => _superIOs.ToArray();
private void ReportUnknownChip(LpcPort port, string type, int chip)
{
_report.Append("Chip ID: Unknown ");
_report.Append(type);
_report.Append(" with ID 0x");
_report.Append(chip.ToString("X", CultureInfo.InvariantCulture));
_report.Append(" at 0x");
_report.Append(port.RegisterPort.ToString("X", CultureInfo.InvariantCulture));
_report.Append("/0x");
_report.AppendLine(port.ValuePort.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine();
}
private bool DetectSmsc(LpcPort port)
{
port.SmscEnter();
ushort chipId = port.ReadWord(CHIP_ID_REGISTER);
if (chipId is not 0 and not 0xffff)
{
port.SmscExit();
ReportUnknownChip(port, "SMSC", chipId);
}
return false;
}
private void Detect(Motherboard motherboard)
{
for (int i = 0; i < REGISTER_PORTS.Length; i++)
{
var port = new LpcPort(REGISTER_PORTS[i], VALUE_PORTS[i]);
if (DetectWinbondFintek(port)) continue;
if (DetectIT87(port, motherboard)) continue;
if (DetectSmsc(port)) continue;
}
}
public string GetReport()
{
if (_report.Length > 0)
{
return "LpcIO" + Environment.NewLine + Environment.NewLine + _report;
}
return null;
}
private bool DetectWinbondFintek(LpcPort port)
{
port.WinbondNuvotonFintekEnter();
byte logicalDeviceNumber = 0;
byte id = port.ReadByte(CHIP_ID_REGISTER);
byte revision = port.ReadByte(CHIP_REVISION_REGISTER);
Chip chip = Chip.Unknown;
switch (id)
{
case 0x05:
switch (revision)
{
case 0x07:
chip = Chip.F71858;
logicalDeviceNumber = F71858_HARDWARE_MONITOR_LDN;
break;
case 0x41:
chip = Chip.F71882;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x06:
switch (revision)
{
case 0x01:
chip = Chip.F71862;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x07:
switch (revision)
{
case 0x23:
chip = Chip.F71889F;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x08:
switch (revision)
{
case 0x14:
chip = Chip.F71869;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x09:
switch (revision)
{
case 0x01:
chip = Chip.F71808E;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
case 0x09:
chip = Chip.F71889ED;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x10:
switch (revision)
{
case 0x05:
chip = Chip.F71889AD;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
case 0x07:
chip = Chip.F71869A;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x11:
switch (revision)
{
case 0x06:
chip = Chip.F71878AD;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
case 0x18:
chip = Chip.F71811;
logicalDeviceNumber = FINTEK_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x52:
switch (revision)
{
case 0x17:
case 0x3A:
case 0x41:
chip = Chip.W83627HF;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x82:
switch (revision & 0xF0)
{
case 0x80:
chip = Chip.W83627THF;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x85:
switch (revision)
{
case 0x41:
chip = Chip.W83687THF;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0x88:
switch (revision & 0xF0)
{
case 0x50:
case 0x60:
chip = Chip.W83627EHF;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xA0:
switch (revision & 0xF0)
{
case 0x20:
chip = Chip.W83627DHG;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xA5:
switch (revision & 0xF0)
{
case 0x10:
chip = Chip.W83667HG;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xB0:
switch (revision & 0xF0)
{
case 0x70:
chip = Chip.W83627DHGP;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xB3:
switch (revision & 0xF0)
{
case 0x50:
chip = Chip.W83667HGB;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xB4:
switch (revision & 0xF0)
{
case 0x70:
chip = Chip.NCT6771F;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC3:
switch (revision & 0xF0)
{
case 0x30:
chip = Chip.NCT6776F;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC4:
switch (revision & 0xF0)
{
case 0x50:
chip = Chip.NCT610XD;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC5:
switch (revision & 0xF0)
{
case 0x60:
chip = Chip.NCT6779D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC7:
switch (revision)
{
case 0x32:
chip = Chip.NCT6683D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC8:
switch (revision)
{
case 0x03:
chip = Chip.NCT6791D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xC9:
switch (revision)
{
case 0x11:
chip = Chip.NCT6792D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
case 0x13:
chip = Chip.NCT6792DA;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xD1:
switch (revision)
{
case 0x21:
chip = Chip.NCT6793D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xD3:
switch (revision)
{
case 0x52:
chip = Chip.NCT6795D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xD4:
switch (revision)
{
case 0x23:
chip = Chip.NCT6796D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
case 0x2A:
chip = Chip.NCT6796DR;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
case 0x51:
chip = Chip.NCT6797D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
case 0x2B:
chip = Chip.NCT6798D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
case 0x40:
case 0x41:
chip = Chip.NCT6686D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xD5:
switch (revision)
{
case 0x92:
chip = Chip.NCT6687D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
case 0xD8:
switch (revision)
{
case 0x02:
chip = Chip.NCT6799D;
logicalDeviceNumber = WINBOND_NUVOTON_HARDWARE_MONITOR_LDN;
break;
}
break;
}
if (chip == Chip.Unknown)
{
if (id is not 0 and not 0xff)
{
port.WinbondNuvotonFintekExit();
ReportUnknownChip(port, "Winbond / Nuvoton / Fintek", (id << 8) | revision);
}
}
else
{
port.Select(logicalDeviceNumber);
ushort address = port.ReadWord(BASE_ADDRESS_REGISTER);
Thread.Sleep(1);
ushort verify = port.ReadWord(BASE_ADDRESS_REGISTER);
ushort vendorId = port.ReadWord(FINTEK_VENDOR_ID_REGISTER);
// disable the hardware monitor i/o space lock on NCT679XD chips
if (address == verify &&
chip is Chip.NCT6791D or Chip.NCT6792D or Chip.NCT6792DA or Chip.NCT6793D or Chip.NCT6795D or Chip.NCT6796D or Chip.NCT6796DR or Chip.NCT6798D or Chip.NCT6797D or Chip.NCT6799D)
{
port.NuvotonDisableIOSpaceLock();
}
port.WinbondNuvotonFintekExit();
if (address != verify)
{
_report.Append("Chip ID: 0x");
_report.AppendLine(chip.ToString("X"));
_report.Append("Chip revision: 0x");
_report.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine("Error: Address verification failed");
_report.AppendLine();
return false;
}
// some Fintek chips have address register offset 0x05 added already
if ((address & 0x07) == 0x05)
address &= 0xFFF8;
if (address < 0x100 || (address & 0xF007) != 0)
{
_report.Append("Chip ID: 0x");
_report.AppendLine(chip.ToString("X"));
_report.Append("Chip revision: 0x");
_report.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
_report.Append("Error: Invalid address 0x");
_report.AppendLine(address.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine();
return false;
}
switch (chip)
{
case Chip.W83627DHG:
case Chip.W83627DHGP:
case Chip.W83627EHF:
case Chip.W83627HF:
case Chip.W83627THF:
case Chip.W83667HG:
case Chip.W83667HGB:
case Chip.W83687THF:
_superIOs.Add(new W836XX(chip, revision, address));
break;
case Chip.NCT610XD:
case Chip.NCT6771F:
case Chip.NCT6776F:
case Chip.NCT6779D:
case Chip.NCT6791D:
case Chip.NCT6792D:
case Chip.NCT6792DA:
case Chip.NCT6793D:
case Chip.NCT6795D:
case Chip.NCT6796D:
case Chip.NCT6796DR:
case Chip.NCT6797D:
case Chip.NCT6798D:
case Chip.NCT6799D:
case Chip.NCT6686D:
case Chip.NCT6687D:
case Chip.NCT6683D:
_superIOs.Add(new Nct677X(chip, revision, address, port));
break;
case Chip.F71858:
case Chip.F71862:
case Chip.F71869:
case Chip.F71878AD:
case Chip.F71869A:
case Chip.F71882:
case Chip.F71889AD:
case Chip.F71889ED:
case Chip.F71889F:
case Chip.F71808E:
if (vendorId != FINTEK_VENDOR_ID)
{
_report.Append("Chip ID: 0x");
_report.AppendLine(chip.ToString("X"));
_report.Append("Chip revision: 0x");
_report.AppendLine(revision.ToString("X", CultureInfo.InvariantCulture));
_report.Append("Error: Invalid vendor ID 0x");
_report.AppendLine(vendorId.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine();
return false;
}
_superIOs.Add(new F718XX(chip, address));
break;
}
return true;
}
return false;
}
private bool DetectIT87(LpcPort port, Motherboard motherboard)
{
// IT87XX can enter only on port 0x2E
// IT8792 using 0x4E
if (port.RegisterPort is not 0x2E and not 0x4E)
return false;
// Read the chip ID before entering.
// If already entered (not 0xFFFF) and the register port is 0x4E, it is most likely bugged and should be left alone.
// Entering IT8792 in this state will result in IT8792 reporting with chip ID of 0x8883.
if (port.RegisterPort != 0x4E || !port.TryReadWord(CHIP_ID_REGISTER, out ushort chipId))
{
port.IT87Enter();
chipId = port.ReadWord(CHIP_ID_REGISTER);
}
Chip chip = chipId switch
{
0x8613 => Chip.IT8613E,
0x8620 => Chip.IT8620E,
0x8625 => Chip.IT8625E,
0x8628 => Chip.IT8628E,
0x8631 => Chip.IT8631E,
0x8665 => Chip.IT8665E,
0x8655 => Chip.IT8655E,
0x8686 => Chip.IT8686E,
0x8688 => Chip.IT8688E,
0x8689 => Chip.IT8689E,
0x8696 => Chip.IT8696E,
0x8705 => Chip.IT8705F,
0x8712 => Chip.IT8712F,
0x8716 => Chip.IT8716F,
0x8718 => Chip.IT8718F,
0x8720 => Chip.IT8720F,
0x8721 => Chip.IT8721F,
0x8726 => Chip.IT8726F,
0x8728 => Chip.IT8728F,
0x8771 => Chip.IT8771E,
0x8772 => Chip.IT8772E,
0x8790 => Chip.IT8790E,
0x8733 => Chip.IT8792E,
0x8695 => Chip.IT87952E,
_ => Chip.Unknown
};
if (chip == Chip.Unknown)
{
if (chipId is not 0 and not 0xffff)
{
port.IT87Exit();
ReportUnknownChip(port, "ITE", chipId);
}
}
else
{
port.Select(IT87_ENVIRONMENT_CONTROLLER_LDN);
ushort address = port.ReadWord(BASE_ADDRESS_REGISTER);
Thread.Sleep(1);
ushort verify = port.ReadWord(BASE_ADDRESS_REGISTER);
byte version = (byte)(port.ReadByte(IT87_CHIP_VERSION_REGISTER) & 0x0F);
ushort gpioAddress;
ushort gpioVerify;
if (chip == Chip.IT8705F)
{
port.Select(IT8705_GPIO_LDN);
gpioAddress = port.ReadWord(BASE_ADDRESS_REGISTER);
Thread.Sleep(1);
gpioVerify = port.ReadWord(BASE_ADDRESS_REGISTER);
}
else
{
port.Select(IT87XX_GPIO_LDN);
gpioAddress = port.ReadWord(BASE_ADDRESS_REGISTER + 2);
Thread.Sleep(1);
gpioVerify = port.ReadWord(BASE_ADDRESS_REGISTER + 2);
}
IGigabyteController gigabyteController = FindGigabyteEC(port, chip, motherboard);
port.IT87Exit();
if (address != verify || address < 0x100 || (address & 0xF007) != 0)
{
_report.Append("Chip ID: 0x");
_report.AppendLine(chip.ToString("X"));
_report.Append("Error: Invalid address 0x");
_report.AppendLine(address.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine();
return false;
}
if (gpioAddress != gpioVerify || gpioAddress < 0x100 || (gpioAddress & 0xF007) != 0)
{
_report.Append("Chip ID: 0x");
_report.AppendLine(chip.ToString("X"));
_report.Append("Error: Invalid GPIO address 0x");
_report.AppendLine(gpioAddress.ToString("X", CultureInfo.InvariantCulture));
_report.AppendLine();
return false;
}
_superIOs.Add(new IT87XX(chip, address, gpioAddress, version, motherboard, gigabyteController));
return true;
}
return false;
}
private IGigabyteController FindGigabyteEC(LpcPort port, Chip chip, Motherboard motherboard)
{
// The controller only affects the 2nd ITE chip if present, and only a few
// models are known to use this controller.
// IT8795E likely to need this too, but may use different registers.
if (motherboard.Manufacturer != Manufacturer.Gigabyte || port.RegisterPort != 0x4E || chip is not (Chip.IT8790E or Chip.IT8792E or Chip.IT87952E))
return null;
Vendor vendor = DetectVendor();
IGigabyteController gigabyteController = FindGigabyteECUsingSmfi(port, chip, vendor);
if (gigabyteController != null)
return gigabyteController;
// ECIO is only available on AMD motherboards with IT8791E/IT8792E/IT8795E.
if (chip == Chip.IT8792E && vendor == Vendor.AMD)
{
gigabyteController = EcioPortGigabyteController.TryCreate();
if (gigabyteController != null)
return gigabyteController;
}
return null;
Vendor DetectVendor()
{
string manufacturer = motherboard.SMBios.Processors[0].ManufacturerName;
if (manufacturer.IndexOf("Intel", StringComparison.OrdinalIgnoreCase) != -1)
return Vendor.Intel;
if (manufacturer.IndexOf("Advanced Micro Devices", StringComparison.OrdinalIgnoreCase) != -1 || manufacturer.StartsWith("AMD", StringComparison.OrdinalIgnoreCase))
return Vendor.AMD;
return Vendor.Unknown;
}
}
private IGigabyteController FindGigabyteECUsingSmfi(LpcPort port, Chip chip, Vendor vendor)
{
port.Select(IT87XX_SMFI_LDN);
// Check if the SMFI logical device is enabled
byte enabled = port.ReadByte(IT87_LD_ACTIVE_REGISTER);
Thread.Sleep(1);
byte enabledVerify = port.ReadByte(IT87_LD_ACTIVE_REGISTER);
// The EC has no SMFI or it's RAM access is not enabled, assume the controller is not present
if (enabled != enabledVerify || enabled == 0)
return null;
// Read the host RAM address that maps to the Embedded Controller's RAM (two registers).
uint addressHi = 0;
uint addressHiVerify = 0;
uint address = port.ReadWord(IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER);
if (chip == Chip.IT87952E)
addressHi = port.ReadByte(IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER_HIGH);
Thread.Sleep(1);
uint addressVerify = port.ReadWord(IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER);
if (chip == Chip.IT87952E)
addressHiVerify = port.ReadByte(IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER_HIGH);
if ((address != addressVerify) || (addressHi != addressHiVerify))
return null;
// Address is xryy, Host Address is FFyyx000
// For IT87952E, Address is rzxryy, Host Address is (0xFC000000 | 0x0zyyx000)
uint hostAddress;
if (chip == Chip.IT87952E)
hostAddress = 0xFC000000;
else
hostAddress = 0xFF000000;
hostAddress |= (address & 0xF000) | ((address & 0xFF) << 16) | ((addressHi & 0xF) << 24);
return new IsaBridgeGigabyteController(hostAddress, vendor);
}
// ReSharper disable InconsistentNaming
private const byte BASE_ADDRESS_REGISTER = 0x60;
private const byte CHIP_ID_REGISTER = 0x20;
private const byte CHIP_REVISION_REGISTER = 0x21;
private const byte F71858_HARDWARE_MONITOR_LDN = 0x02;
private const byte FINTEK_HARDWARE_MONITOR_LDN = 0x04;
private const byte IT87_ENVIRONMENT_CONTROLLER_LDN = 0x04;
private const byte IT8705_GPIO_LDN = 0x05;
private const byte IT87XX_GPIO_LDN = 0x07;
// Shared Memory/Flash Interface
private const byte IT87XX_SMFI_LDN = 0x0F;
private const byte WINBOND_NUVOTON_HARDWARE_MONITOR_LDN = 0x0B;
private const ushort FINTEK_VENDOR_ID = 0x1934;
private const byte FINTEK_VENDOR_ID_REGISTER = 0x23;
private const byte IT87_CHIP_VERSION_REGISTER = 0x22;
private const byte IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER = 0xF5;
private const byte IT87_SMFI_HLPC_RAM_BASE_ADDRESS_REGISTER_HIGH = 0xFC;
private const byte IT87_LD_ACTIVE_REGISTER = 0x30;
private readonly ushort[] REGISTER_PORTS = { 0x2E, 0x4E };
private readonly ushort[] VALUE_PORTS = { 0x2F, 0x4F };
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,105 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class LpcPort
{
public LpcPort(ushort registerPort, ushort valuePort)
{
RegisterPort = registerPort;
ValuePort = valuePort;
}
public ushort RegisterPort { get; }
public ushort ValuePort { get; }
public byte ReadByte(byte register)
{
Ring0.WriteIoPort(RegisterPort, register);
return Ring0.ReadIoPort(ValuePort);
}
public void WriteByte(byte register, byte value)
{
Ring0.WriteIoPort(RegisterPort, register);
Ring0.WriteIoPort(ValuePort, value);
}
public ushort ReadWord(byte register)
{
return (ushort)((ReadByte(register) << 8) | ReadByte((byte)(register + 1)));
}
public bool TryReadWord(byte register, out ushort value)
{
value = ReadWord(register);
return value != 0xFFFF;
}
public void Select(byte logicalDeviceNumber)
{
Ring0.WriteIoPort(RegisterPort, DEVICE_SELECT_REGISTER);
Ring0.WriteIoPort(ValuePort, logicalDeviceNumber);
}
public void WinbondNuvotonFintekEnter()
{
Ring0.WriteIoPort(RegisterPort, 0x87);
Ring0.WriteIoPort(RegisterPort, 0x87);
}
public void WinbondNuvotonFintekExit()
{
Ring0.WriteIoPort(RegisterPort, 0xAA);
}
public void NuvotonDisableIOSpaceLock()
{
byte options = ReadByte(NUVOTON_HARDWARE_MONITOR_IO_SPACE_LOCK);
// if the i/o space lock is enabled
if ((options & 0x10) > 0)
{
// disable the i/o space lock
WriteByte(NUVOTON_HARDWARE_MONITOR_IO_SPACE_LOCK, (byte)(options & ~0x10));
}
}
public void IT87Enter()
{
Ring0.WriteIoPort(RegisterPort, 0x87);
Ring0.WriteIoPort(RegisterPort, 0x01);
Ring0.WriteIoPort(RegisterPort, 0x55);
Ring0.WriteIoPort(RegisterPort, RegisterPort == 0x4E ? (byte)0xAA : (byte)0x55);
}
public void IT87Exit()
{
// Do not exit config mode for secondary super IO.
if (RegisterPort != 0x4E)
{
Ring0.WriteIoPort(RegisterPort, CONFIGURATION_CONTROL_REGISTER);
Ring0.WriteIoPort(ValuePort, 0x02);
}
}
public void SmscEnter()
{
Ring0.WriteIoPort(RegisterPort, 0x55);
}
public void SmscExit()
{
Ring0.WriteIoPort(RegisterPort, 0xAA);
}
// ReSharper disable InconsistentNaming
private const byte CONFIGURATION_CONTROL_REGISTER = 0x02;
private const byte DEVICE_SELECT_REGISTER = 0x07;
private const byte NUVOTON_HARDWARE_MONITOR_IO_SPACE_LOCK = 0x28;
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,516 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
using System.Text;
// ReSharper disable once InconsistentNaming
namespace LibreHardwareMonitor.Hardware.Motherboard.Lpc;
internal class W836XX : ISuperIO
{
private readonly ushort _address;
private readonly byte _revision;
private readonly bool[] _peciTemperature = Array.Empty<bool>();
private readonly byte[] _voltageBank = Array.Empty<byte>();
private readonly float _voltageGain = 0.008f;
private readonly byte[] _voltageRegister = Array.Empty<byte>();
// Added to control fans.
private readonly byte[] _fanPwmRegister = Array.Empty<byte>();
private readonly byte[] _fanPrimaryControlModeRegister = Array.Empty<byte>();
private readonly byte[] _fanPrimaryControlValue = Array.Empty<byte>();
private readonly byte[] _fanSecondaryControlModeRegister = Array.Empty<byte>();
private readonly byte[] _fanSecondaryControlValue = Array.Empty<byte>();
private readonly byte[] _fanTertiaryControlModeRegister = Array.Empty<byte>();
private readonly byte[] _fanTertiaryControlValue = Array.Empty<byte>();
private readonly byte[] _initialFanControlValue = Array.Empty<byte>();
private readonly byte[] _initialFanSecondaryControlValue = Array.Empty<byte>();
private readonly byte[] _initialFanTertiaryControlValue = Array.Empty<byte>();
private readonly bool[] _restoreDefaultFanPwmControlRequired = Array.Empty<bool>();
public W836XX(Chip chip, byte revision, ushort address)
{
_address = address;
_revision = revision;
Chip = chip;
if (!IsWinbondVendor())
return;
Temperatures = new float?[3];
_peciTemperature = new bool[3];
switch (chip)
{
case Chip.W83667HG:
case Chip.W83667HGB:
// note temperature sensor registers that read PECI
byte flag = ReadByte(0, TEMPERATURE_SOURCE_SELECT_REG);
_peciTemperature[0] = (flag & 0x04) != 0;
_peciTemperature[1] = (flag & 0x40) != 0;
_peciTemperature[2] = false;
break;
case Chip.W83627DHG:
case Chip.W83627DHGP:
// note temperature sensor registers that read PECI
byte sel = ReadByte(0, TEMPERATURE_SOURCE_SELECT_REG);
_peciTemperature[0] = (sel & 0x07) != 0;
_peciTemperature[1] = (sel & 0x70) != 0;
_peciTemperature[2] = false;
break;
default:
// no PECI support
_peciTemperature[0] = false;
_peciTemperature[1] = false;
_peciTemperature[2] = false;
break;
}
switch (chip)
{
case Chip.W83627EHF:
Voltages = new float?[10];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x50, 0x51, 0x52 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 0, 0, 5, 5, 5 };
_voltageGain = 0.008f;
Fans = new float?[5];
_fanPwmRegister = new byte[] { 0x01, 0x03, 0x11 }; // Fan PWM values.
_fanPrimaryControlModeRegister = new byte[] { 0x04, 0x04, 0x12 }; // Primary control register.
_fanPrimaryControlValue = new byte[] { 0b11110011, 0b11001111, 0b11111001 }; // Values to gain control of fans.
_initialFanControlValue = new byte[3]; // To store primary default value.
_initialFanSecondaryControlValue = new byte[3]; // To store secondary default value.
Controls = new float?[3];
break;
case Chip.W83627DHG:
case Chip.W83627DHGP:
Voltages = new float?[9];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x50, 0x51 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 0, 0, 5, 5 };
_voltageGain = 0.008f;
Fans = new float?[5];
_fanPwmRegister = new byte[] { 0x01, 0x03, 0x11 }; // Fan PWM values
_fanPrimaryControlModeRegister = new byte[] { 0x04, 0x04, 0x12 }; // added. Primary control register
_fanPrimaryControlValue = new byte[] { 0b11110011, 0b11001111, 0b11111001 }; // Values to gain control of fans
_initialFanControlValue = new byte[3]; // To store primary default value
_initialFanSecondaryControlValue = new byte[3]; // To store secondary default value.
Controls = new float?[3];
break;
case Chip.W83667HG:
case Chip.W83667HGB:
Voltages = new float?[9];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x50, 0x51 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 0, 0, 5, 5 };
_voltageGain = 0.008f;
Fans = new float?[5];
_fanPwmRegister = new byte[] { 0x01, 0x03, 0x11 }; // Fan PWM values.
_fanPrimaryControlModeRegister = new byte[] { 0x04, 0x04, 0x12 }; // Primary control register.
_fanPrimaryControlValue = new byte[] { 0b11110011, 0b11001111, 0b11111001 }; // Values to gain control of fans.
_fanSecondaryControlModeRegister = new byte[] { 0x7c, 0x7c, 0x7c }; // Secondary control register for SmartFan4.
_fanSecondaryControlValue = new byte[] { 0b11101111, 0b11011111, 0b10111111 }; // Values for secondary register to gain control of fans.
_fanTertiaryControlModeRegister = new byte[] { 0x62, 0x7c, 0x62 }; // Tertiary control register. 2nd fan doesn't have Tertiary control, same as secondary to avoid change.
_fanTertiaryControlValue = new byte[] { 0b11101111, 0b11011111, 0b11011111 }; // Values for tertiary register to gain control of fans. 2nd fan doesn't have Tertiary control, same as secondary to avoid change.
_initialFanControlValue = new byte[3]; // To store primary default value.
_initialFanSecondaryControlValue = new byte[3]; // To store secondary default value.
_initialFanTertiaryControlValue = new byte[3]; // To store tertiary default value.
Controls = new float?[3];
break;
case Chip.W83627HF:
Voltages = new float?[7];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x50, 0x51 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 5, 5 };
_voltageGain = 0.016f;
Fans = new float?[3];
_fanPwmRegister = new byte[] { 0x5A, 0x5B }; // Fan PWM values.
Controls = new float?[2];
break;
case Chip.W83627THF:
Voltages = new float?[7];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x50, 0x51 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 5, 5 };
_voltageGain = 0.016f;
Fans = new float?[3];
_fanPwmRegister = new byte[] { 0x01, 0x03, 0x11 }; // Fan PWM values.
_fanPrimaryControlModeRegister = new byte[] { 0x04, 0x04, 0x12 }; // Primary control register.
_fanPrimaryControlValue = new byte[] { 0b11110011, 0b11001111, 0b11111001 }; // Values to gain control of fans.
_initialFanControlValue = new byte[3]; // To store primary default value.
Controls = new float?[3];
break;
case Chip.W83687THF:
Voltages = new float?[7];
_voltageRegister = new byte[] { 0x20, 0x21, 0x22, 0x23, 0x24, 0x50, 0x51 };
_voltageBank = new byte[] { 0, 0, 0, 0, 0, 5, 5 };
_voltageGain = 0.016f;
Fans = new float?[3];
break;
}
}
public Chip Chip { get; }
public float?[] Controls { get; } = Array.Empty<float?>();
public float?[] Fans { get; } = Array.Empty<float?>();
public float?[] Temperatures { get; } = Array.Empty<float?>();
public float?[] Voltages { get; } = Array.Empty<float?>();
public byte? ReadGpio(int index)
{
return null;
}
public void WriteGpio(int index, byte value)
{ }
public void SetControl(int index, byte? value)
{
if (index < 0 || index >= Controls.Length)
throw new ArgumentOutOfRangeException(nameof(index));
if (!Mutexes.WaitIsaBus(10))
return;
if (value.HasValue)
{
SaveDefaultFanPwmControl(index);
if (_fanPrimaryControlModeRegister.Length > 0)
{
WriteByte(0, _fanPrimaryControlModeRegister[index], (byte)(_fanPrimaryControlValue[index] & ReadByte(0, _fanPrimaryControlModeRegister[index])));
if (_fanSecondaryControlModeRegister.Length > 0)
{
if (_fanSecondaryControlModeRegister[index] != _fanPrimaryControlModeRegister[index])
{
WriteByte(0, _fanSecondaryControlModeRegister[index], (byte)(_fanSecondaryControlValue[index] & ReadByte(0, _fanSecondaryControlModeRegister[index])));
}
if (_fanTertiaryControlModeRegister.Length > 0 && _fanTertiaryControlModeRegister[index] != _fanSecondaryControlModeRegister[index])
{
WriteByte(0, _fanTertiaryControlModeRegister[index], (byte)(_fanTertiaryControlValue[index] & ReadByte(0, _fanTertiaryControlModeRegister[index])));
}
}
}
// set output value
WriteByte(0, _fanPwmRegister[index], value.Value);
}
else
{
RestoreDefaultFanPwmControl(index);
}
Mutexes.ReleaseIsaBus();
}
private void SaveDefaultFanPwmControl(int index) //added to save initial control values
{
if (_fanPrimaryControlModeRegister.Length > 0 &&
_initialFanControlValue.Length > 0 &&
_fanPrimaryControlValue.Length > 0 &&
_restoreDefaultFanPwmControlRequired.Length > 0 &&
!_restoreDefaultFanPwmControlRequired[index])
{
_initialFanControlValue[index] = ReadByte(0, _fanPrimaryControlModeRegister[index]);
if (_fanSecondaryControlModeRegister.Length > 0 && _initialFanSecondaryControlValue.Length > 0 && _fanSecondaryControlValue.Length > 0)
{
if (_fanSecondaryControlModeRegister[index] != _fanPrimaryControlModeRegister[index])
{
_initialFanSecondaryControlValue[index] = ReadByte(0, _fanSecondaryControlModeRegister[index]);
}
if (_fanTertiaryControlModeRegister.Length > 0 &&
_initialFanTertiaryControlValue.Length > 0 &&
_fanTertiaryControlValue.Length > 0 &&
_fanTertiaryControlModeRegister[index] != _fanSecondaryControlModeRegister[index])
{
_initialFanTertiaryControlValue[index] = ReadByte(0, _fanTertiaryControlModeRegister[index]);
}
}
_restoreDefaultFanPwmControlRequired[index] = true;
}
}
private void RestoreDefaultFanPwmControl(int index) //added to restore initial control values
{
if (_fanPrimaryControlModeRegister.Length > 0 &&
_initialFanControlValue.Length > 0 &&
_fanPrimaryControlValue.Length > 0 &&
_restoreDefaultFanPwmControlRequired.Length > 0 &&
_restoreDefaultFanPwmControlRequired[index])
{
WriteByte(0,
_fanPrimaryControlModeRegister[index],
(byte)((_initialFanControlValue[index] & ~_fanPrimaryControlValue[index]) |
ReadByte(0, _fanPrimaryControlModeRegister[index]))); //bitwise operands to change only desired bits
if (_fanSecondaryControlModeRegister.Length > 0 && _initialFanSecondaryControlValue.Length > 0 && _fanSecondaryControlValue.Length > 0)
{
if (_fanSecondaryControlModeRegister[index] != _fanPrimaryControlModeRegister[index])
{
WriteByte(0,
_fanSecondaryControlModeRegister[index],
(byte)((_initialFanSecondaryControlValue[index] & ~_fanSecondaryControlValue[index]) |
ReadByte(0, _fanSecondaryControlModeRegister[index]))); //bitwise operands to change only desired bits
}
if (_fanTertiaryControlModeRegister.Length > 0 &&
_initialFanTertiaryControlValue.Length > 0 &&
_fanTertiaryControlValue.Length > 0 &&
_fanTertiaryControlModeRegister[index] != _fanSecondaryControlModeRegister[index])
{
WriteByte(0,
_fanTertiaryControlModeRegister[index],
(byte)((_initialFanTertiaryControlValue[index] & ~_fanTertiaryControlValue[index]) | ReadByte(0, _fanTertiaryControlModeRegister[index]))); //bitwise operands to change only desired bits
}
}
_restoreDefaultFanPwmControlRequired[index] = false;
}
}
public void Update()
{
if (!Mutexes.WaitIsaBus(10))
return;
for (int i = 0; i < Voltages.Length; i++)
{
if (_voltageRegister[i] != VOLTAGE_VBAT_REG)
{
// two special VCore measurement modes for W83627THF
float fValue;
if ((Chip == Chip.W83627HF || Chip == Chip.W83627THF || Chip == Chip.W83687THF) && i == 0)
{
byte vrmConfiguration = ReadByte(0, 0x18);
int value = ReadByte(_voltageBank[i], _voltageRegister[i]);
if ((vrmConfiguration & 0x01) == 0)
fValue = 0.016f * value; // VRM8 formula
else
fValue = (0.00488f * value) + 0.69f; // VRM9 formula
}
else
{
int value = ReadByte(_voltageBank[i], _voltageRegister[i]);
fValue = _voltageGain * value;
}
if (fValue > 0)
Voltages[i] = fValue;
else
Voltages[i] = null;
}
else
{
// Battery voltage
bool valid = (ReadByte(0, 0x5D) & 0x01) > 0;
if (valid)
{
Voltages[i] = _voltageGain * ReadByte(5, VOLTAGE_VBAT_REG);
}
else
{
Voltages[i] = null;
}
}
}
for (int i = 0; i < Temperatures.Length; i++)
{
int value = (sbyte)ReadByte(TEMPERATURE_BANK[i], TEMPERATURE_REG[i]) << 1;
if (TEMPERATURE_BANK[i] > 0)
value |= ReadByte(TEMPERATURE_BANK[i], (byte)(TEMPERATURE_REG[i] + 1)) >> 7;
float temperature = value / 2.0f;
if (temperature is <= 125 and >= -55 && !_peciTemperature[i])
{
Temperatures[i] = temperature;
}
else
{
Temperatures[i] = null;
}
}
ulong bits = 0;
foreach (byte t in FAN_BIT_REG)
bits = (bits << 8) | ReadByte(0, t);
ulong newBits = bits;
for (int i = 0; i < Fans.Length; i++)
{
int count = ReadByte(FAN_TACHO_BANK[i], FAN_TACHO_REG[i]);
// assemble fan divisor
int divisorBits = (int)(
(((bits >> FAN_DIV_BIT2[i]) & 1) << 2) |
(((bits >> FAN_DIV_BIT1[i]) & 1) << 1) |
((bits >> FAN_DIV_BIT0[i]) & 1));
int divisor = 1 << divisorBits;
Fans[i] = count < 0xff ? 1.35e6f / (count * divisor) : 0;
switch (count)
{
// update fan divisor
case > 192 when divisorBits < 7:
divisorBits++;
break;
case < 96 when divisorBits > 0:
divisorBits--;
break;
}
newBits = SetBit(newBits, FAN_DIV_BIT2[i], (divisorBits >> 2) & 1);
newBits = SetBit(newBits, FAN_DIV_BIT1[i], (divisorBits >> 1) & 1);
newBits = SetBit(newBits, FAN_DIV_BIT0[i], divisorBits & 1);
}
for (int i = 0; i < Controls.Length; i++)
{
byte value = ReadByte(0, _fanPwmRegister[i]);
Controls[i] = (float)Math.Round(value * 100.0f / 0xFF);
}
Mutexes.ReleaseIsaBus();
}
public string GetReport()
{
StringBuilder r = new();
r.AppendLine("LPC " + GetType().Name);
r.AppendLine();
r.Append("Chip Id: 0x");
r.AppendLine(Chip.ToString("X"));
r.Append("Chip Revision: 0x");
r.AppendLine(_revision.ToString("X", CultureInfo.InvariantCulture));
r.Append("Base Address: 0x");
r.AppendLine(_address.ToString("X4", CultureInfo.InvariantCulture));
r.AppendLine();
if (!Mutexes.WaitIsaBus(100))
return r.ToString();
r.AppendLine("Hardware Monitor Registers");
r.AppendLine();
r.AppendLine(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
r.AppendLine();
for (int i = 0; i <= 0x7; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X2", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
r.Append(" ");
r.Append(ReadByte(0, (byte)((i << 4) | j)).ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
for (int k = 1; k <= 15; k++)
{
r.AppendLine("Bank " + k);
for (int i = 0x5; i < 0x6; i++)
{
r.Append(" ");
r.Append((i << 4).ToString("X2", CultureInfo.InvariantCulture));
r.Append(" ");
for (int j = 0; j <= 0xF; j++)
{
r.Append(" ");
r.Append(ReadByte((byte)k, (byte)((i << 4) | j)).ToString("X2", CultureInfo.InvariantCulture));
}
r.AppendLine();
}
}
r.AppendLine();
Mutexes.ReleaseIsaBus();
return r.ToString();
}
private byte ReadByte(byte bank, byte register)
{
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), BANK_SELECT_REGISTER);
Ring0.WriteIoPort((ushort)(_address + DATA_REGISTER_OFFSET), bank);
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), register);
return Ring0.ReadIoPort((ushort)(_address + DATA_REGISTER_OFFSET));
}
private void WriteByte(byte bank, byte register, byte value)
{
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), BANK_SELECT_REGISTER);
Ring0.WriteIoPort((ushort)(_address + DATA_REGISTER_OFFSET), bank);
Ring0.WriteIoPort((ushort)(_address + ADDRESS_REGISTER_OFFSET), register);
Ring0.WriteIoPort((ushort)(_address + DATA_REGISTER_OFFSET), value);
}
private bool IsWinbondVendor()
{
ushort vendorId = (ushort)((ReadByte(HIGH_BYTE, VENDOR_ID_REGISTER) << 8) | ReadByte(0, VENDOR_ID_REGISTER));
return vendorId == WINBOND_VENDOR_ID;
}
private static ulong SetBit(ulong target, int bit, int value)
{
if ((value & 1) != value)
throw new ArgumentException("Value must be one bit only.");
if (bit is < 0 or > 63)
throw new ArgumentException("Bit out of range.");
ulong mask = (ulong)1 << bit;
return value > 0 ? target | mask : target & ~mask;
}
// ReSharper disable InconsistentNaming
private const byte ADDRESS_REGISTER_OFFSET = 0x05;
private const byte BANK_SELECT_REGISTER = 0x4E;
private const byte DATA_REGISTER_OFFSET = 0x06;
private const byte HIGH_BYTE = 0x80;
private const byte TEMPERATURE_SOURCE_SELECT_REG = 0x49;
private const byte VENDOR_ID_REGISTER = 0x4F;
private const byte VOLTAGE_VBAT_REG = 0x51;
private const ushort WINBOND_VENDOR_ID = 0x5CA3;
private readonly byte[] FAN_BIT_REG = { 0x47, 0x4B, 0x4C, 0x59, 0x5D };
private readonly byte[] FAN_DIV_BIT0 = { 36, 38, 30, 8, 10 };
private readonly byte[] FAN_DIV_BIT1 = { 37, 39, 31, 9, 11 };
private readonly byte[] FAN_DIV_BIT2 = { 5, 6, 7, 23, 15 };
private readonly byte[] FAN_TACHO_BANK = { 0, 0, 0, 0, 5 };
private readonly byte[] FAN_TACHO_REG = { 0x28, 0x29, 0x2A, 0x3F, 0x53 };
private readonly byte[] TEMPERATURE_BANK = { 1, 2, 0 };
private readonly byte[] TEMPERATURE_REG = { 0x50, 0x50, 0x27 };
// ReSharper restore InconsistentNaming
}

View File

@@ -0,0 +1,56 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Diagnostics.CodeAnalysis;
namespace LibreHardwareMonitor.Hardware.Motherboard;
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "CommentTypo")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum Manufacturer
{
Abit,
Acer,
Alienware,
AMD,
AOpen,
Apple,
ASRock,
ASUS,
Biostar,
Clevo,
Dell,
DFI,
ECS,
EPoX,
EVGA,
FIC,
Foxconn,
Fujitsu,
Gateway,
Gigabyte,
HP,
IBM,
Intel,
Jetway,
LattePanda,
Lenovo,
Medion,
Microsoft,
MSI,
NEC,
Pegatron,
Samsung,
Sapphire,
Shuttle,
Sony,
Supermicro,
Toshiba,
XFX,
Zotac,
Unknown
}

View File

@@ -0,0 +1,261 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
// ReSharper disable InconsistentNaming
using System.Diagnostics.CodeAnalysis;
namespace LibreHardwareMonitor.Hardware.Motherboard;
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "CommentTypo")]
public enum Model
{
// ASRock
_880GMH_USB3,
A320M_HDV,
AB350_Pro4,
AB350M,
AB350M_HDV,
AB350M_Pro4,
AOD790GX_128M,
B450_Pro4,
B450_Steel_Legend,
B450M_Pro4,
B450M_Steel_Legend,
B85M_DGS,
Fatal1ty_AB350_Gaming_K4,
P55_Deluxe,
X399_Phantom_Gaming_6,
Z77Pro4M,
X570_Pro4,
X570_Taichi,
X570_Phantom_Gaming_ITX,
Z690_Extreme,
Z690_Steel_Legend,
Z790_Pro_RS,
X570_Phantom_Gaming_4,
Z790_Taichi,
Z790_Nova_WiFi,
B650M_C,
H61M_DGS,
// ASUS
CROSSHAIR_III_FORMULA,
ROG_CROSSHAIR_VIII_HERO,
ROG_CROSSHAIR_VIII_HERO_WIFI,
ROG_CROSSHAIR_VIII_DARK_HERO,
ROG_CROSSHAIR_VIII_FORMULA,
ROG_CROSSHAIR_VIII_IMPACT,
ROG_STRIX_X470_I,
ROG_CROSSHAIR_X670E_EXTREME,
ROG_CROSSHAIR_X670E_HERO,
ROG_CROSSHAIR_X670E_GENE,
ROG_STRIX_X670E_A_GAMING_WIFI,
ROG_STRIX_X670E_E_GAMING_WIFI,
ROG_STRIX_X670E_F_GAMING_WIFI,
PROART_X670E_CREATOR_WIFI,
ROG_STRIX_X570_E_GAMING,
ROG_STRIX_X570_E_GAMING_WIFI_II,
ROG_STRIX_X570_F_GAMING,
ROG_STRIX_X570_I_GAMING,
ROG_STRIX_B550_E_GAMING,
ROG_STRIX_B550_F_GAMING_WIFI,
ROG_STRIX_B550_I_GAMING,
ROG_STRIX_Z390_E_GAMING,
ROG_STRIX_Z390_F_GAMING,
ROG_STRIX_Z390_I_GAMING,
ROG_STRIX_Z690_A_GAMING_WIFI_D4,
ROG_MAXIMUS_XI_FORMULA,
ROG_MAXIMUS_XII_Z490_FORMULA,
ROG_MAXIMUS_X_HERO_WIFI_AC,
ROG_MAXIMUS_Z690_FORMULA,
ROG_MAXIMUS_Z690_HERO,
ROG_MAXIMUS_Z690_EXTREME_GLACIAL,
ROG_STRIX_Z790_I_GAMING_WIFI,
ROG_STRIX_Z790_E_GAMING_WIFI,
M2N_SLI_Deluxe,
M4A79XTD_EVO,
P5W_DH_Deluxe,
P6T,
P6X58D_E,
P8P67,
P8P67_EVO,
P8P67_M_PRO,
P8P67_PRO,
P8Z77_V,
P9X79,
PRIME_B650_PLUS,
PRIME_X370_PRO,
PRIME_X470_PRO,
PRIME_X570_PRO,
PROART_X570_CREATOR_WIFI,
PRO_WS_X570_ACE,
RAMPAGE_EXTREME,
RAMPAGE_II_GENE,
ROG_MAXIMUS_X_APEX,
ROG_ZENITH_EXTREME,
ROG_ZENITH_II_EXTREME,
TUF_X470_PLUS_GAMING,
Z170_A,
B150M_C,
B150M_C_D3,
TUF_GAMING_X570_PLUS_WIFI,
TUF_GAMING_B550M_PLUS_WIFI,
ROG_MAXIMUS_Z790_HERO,
ROG_MAXIMUS_Z790_DARK_HERO,
PRIME_Z690_A,
ROG_MAXIMUS_Z790_FORMULA,
ROG_MAXIMUS_XII_HERO_WIFI,
//BIOSTAR
B660GTN,
X670E_Valkyrie,
// DFI
LP_BI_P45_T2RS_Elite,
LP_DK_P55_T3EH9,
// ECS
A890GXM_A,
// MSI
B350_Gaming_Plus,
B360M_PRO_VDH,
B450A_PRO,
B550A_PRO,
B650M_Gaming_Plus_Wifi,
Z270_PC_MATE,
Z77_MS7751,
Z68_MS7672,
X570_Gaming_Plus,
// EVGA
X58_SLI_Classified,
X58_3X_SLI,
// Gigabyte
_965P_S3,
_970A_DS3P,
_970A_UD3,
AB350_Gaming_3,
AX370_Gaming_5,
AX370_Gaming_K7,
B360_AORUS_GAMING_3_WIFI_CF,
B550_AORUS_MASTER,
B550_AORUS_PRO,
B550_AORUS_PRO_AC,
B550_AORUS_PRO_AX,
B550_VISION_D,
B550_AORUS_ELITE,
B550_AORUS_ELITE_AX,
B550_GAMING_X,
B550_UD_AC,
B550M_AORUS_PRO,
B550M_AORUS_PRO_AX,
B550M_AORUS_ELITE,
B550M_GAMING,
B550M_DS3H,
B550M_DS3H_AC,
B550M_S2H,
B550M_H,
B550I_AORUS_PRO_AX,
B560M_AORUS_ELITE,
B560M_AORUS_PRO,
B560M_AORUS_PRO_AX,
B560I_AORUS_PRO_AX,
B660_DS3H_DDR4,
B660_DS3H_AC_DDR4,
B660M_DS3H_AX_DDR4,
EP45_DS3R,
EP45_UD3R,
EX58_EXTREME,
EX58_UD3R,
G41M_COMBO,
G41MT_S2,
G41MT_S2P,
H55_USB3,
H55N_USB3,
H61M_DS2_REV_1_2,
H61M_USB3_B3_REV_2_0,
H67A_UD3H_B3,
H67A_USB3_B3,
H97_D3H,
H81M_HD3,
B75M_D3H,
MA770T_UD3,
MA770T_UD3P,
MA785GM_US2H,
MA785GMT_UD2H,
MA78LM_S2H,
MA790X_UD3P,
P35_DS3,
P35_DS3L,
P55_UD4,
P55A_UD3,
P55M_UD4,
P67A_UD3_B3,
P67A_UD3R_B3,
P67A_UD4_B3,
P8Z68_V_PRO,
X38_DS5,
X399_AORUS_Gaming_7,
X58A_UD3R,
X79_UD3,
Z390_AORUS_ULTRA,
Z390_AORUS_PRO,
Z390_M_GAMING,
Z390_UD,
Z68A_D3H_B3,
Z68AP_D3,
Z68X_UD3H_B3,
Z68X_UD7_B3,
Z68XP_UD3R,
Z690_AORUS_PRO,
Z690_AORUS_ULTRA,
Z690_AORUS_MASTER,
Z690_GAMING_X_DDR4,
Z790_AORUS_PRO_X,
Z790_UD,
Z790_UD_AC,
Z790_GAMING_X,
Z790_GAMING_X_AX,
Z170N_WIFI,
B450_AORUS_M,
B450_AORUS_PRO,
B450_GAMING_X,
B450_AORUS_ELITE,
B450M_AORUS_ELITE,
B450M_GAMING,
B450_I_AORUS_PRO_WIFI,
B450M_DS3H,
B450M_S2H,
B450M_H,
B450M_K,
X470_AORUS_GAMING_7_WIFI,
X570_AORUS_MASTER,
X570_AORUS_PRO,
X570_GAMING_X,
X570_AORUS_ULTRA,
B650_AORUS_ELITE,
B650_AORUS_ELITE_AX,
B650_AORUS_ELITE_V2,
B650_AORUS_ELITE_AX_V2,
B650_AORUS_ELITE_AX_ICE,
B650E_AORUS_ELITE_AX_ICE,
B650M_AORUS_PRO,
B650M_AORUS_PRO_AX,
B650M_AORUS_ELITE,
B650M_AORUS_ELITE_AX,
X870E_AORUS_PRO,
X870E_AORUS_PRO_ICE,
// Shuttle
FH67,
// Unknown
Unknown
}

View File

@@ -0,0 +1,206 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LibreHardwareMonitor.Hardware.Motherboard.Lpc;
using LibreHardwareMonitor.Hardware.Motherboard.Lpc.EC;
using OperatingSystem = LibreHardwareMonitor.Software.OperatingSystem;
namespace LibreHardwareMonitor.Hardware.Motherboard;
/// <summary>
/// Represents the motherboard of a computer with its <see cref="LpcIO" /> and <see cref="EmbeddedController" /> as <see cref="SubHardware" />.
/// </summary>
public class Motherboard : IHardware
{
private readonly LMSensors _lmSensors;
private readonly LpcIO _lpcIO;
private readonly string _name;
private readonly ISettings _settings;
private string _customName;
/// <summary>
/// Creates motherboard instance by retrieving information from <see cref="LibreHardwareMonitor.Hardware.SMBios" /> and creates a new <see cref="SubHardware" /> based on data from <see cref="LpcIO" />
/// and <see cref="EmbeddedController" />.
/// </summary>
/// <param name="smBios"><see cref="LibreHardwareMonitor.Hardware.SMBios" /> table containing motherboard data.</param>
/// <param name="settings">Additional settings passed by <see cref="IComputer" />.</param>
public Motherboard(SMBios smBios, ISettings settings)
{
IReadOnlyList<ISuperIO> superIO;
_settings = settings;
SMBios = smBios;
Manufacturer = smBios.Board == null ? Manufacturer.Unknown : Identification.GetManufacturer(smBios.Board.ManufacturerName);
Model = smBios.Board == null ? Model.Unknown : Identification.GetModel(smBios.Board.ProductName);
if (smBios.Board != null)
{
if (!string.IsNullOrEmpty(smBios.Board.ProductName))
{
if (Manufacturer == Manufacturer.Unknown)
_name = smBios.Board.ProductName;
else
_name = Manufacturer + " " + smBios.Board.ProductName;
}
else
{
_name = Manufacturer.ToString();
}
}
else
{
_name = nameof(Manufacturer.Unknown);
}
_customName = settings.GetValue(new Identifier(Identifier, "name").ToString(), _name);
if (OperatingSystem.IsUnix)
{
_lmSensors = new LMSensors();
superIO = _lmSensors.SuperIO;
}
else
{
_lpcIO = new LpcIO(this);
superIO = _lpcIO.SuperIO;
}
EmbeddedController embeddedController = EmbeddedController.Create(Model, settings);
List<IHardware> subHardwareList = new List<IHardware>();
// there may be more than 1 of the same SuperIO chip
// group by chip
foreach (IGrouping<Chip, ISuperIO> group in superIO.GroupBy(x => x.Chip))
{
// index by group
foreach ((ISuperIO superIo, int i) in group.Select((x, i) => (x, i)))
{
subHardwareList.Add(new SuperIOHardware(this, superIo, Manufacturer, Model, settings, i));
}
}
if (embeddedController != null)
subHardwareList.Add(embeddedController);
SubHardware = subHardwareList.ToArray();
}
/// <inheritdoc />
public event SensorEventHandler SensorAdded;
/// <inheritdoc />
public event SensorEventHandler SensorRemoved;
/// <inheritdoc />
public HardwareType HardwareType => HardwareType.Motherboard;
/// <inheritdoc />
public Identifier Identifier => new("motherboard");
/// <summary>
/// Gets the <see cref="LibreHardwareMonitor.Hardware.Motherboard.Manufacturer" />.
/// </summary>
public Manufacturer Manufacturer { get; }
/// <summary>
/// Gets the <see cref="LibreHardwareMonitor.Hardware.Motherboard.Model" />.
/// </summary>
public Model Model { get; }
/// <summary>
/// Gets the name obtained from <see cref="LibreHardwareMonitor.Hardware.SMBios" />.
/// </summary>
public string Name
{
get { return _customName; }
set
{
_customName = !string.IsNullOrEmpty(value) ? value : _name;
_settings.SetValue(new Identifier(Identifier, "name").ToString(), _customName);
}
}
/// <inheritdoc />
/// <returns>Always <see langword="null" /></returns>
public virtual IHardware Parent
{
get { return null; }
}
/// <inheritdoc />
public virtual IDictionary<string, string> Properties => new SortedDictionary<string, string>();
/// <inheritdoc />
public ISensor[] Sensors
{
get { return Array.Empty<ISensor>(); }
}
/// <summary>
/// Gets the <see cref="LibreHardwareMonitor.Hardware.SMBios" /> information.
/// </summary>
public SMBios SMBios { get; }
/// <inheritdoc />
public IHardware[] SubHardware { get; }
/// <inheritdoc />
public string GetReport()
{
StringBuilder r = new();
r.AppendLine("Motherboard");
r.AppendLine();
r.Append(SMBios.GetReport());
if (_lpcIO != null)
r.Append(_lpcIO.GetReport());
return r.ToString();
}
/// <summary>
/// Motherboard itself cannot be updated. Update <see cref="SubHardware" /> instead.
/// </summary>
public void Update()
{ }
/// <inheritdoc />
public void Accept(IVisitor visitor)
{
if (visitor == null)
throw new ArgumentNullException(nameof(visitor));
visitor.VisitHardware(this);
}
/// <inheritdoc />
public void Traverse(IVisitor visitor)
{
foreach (IHardware hardware in SubHardware)
hardware.Accept(visitor);
}
/// <summary>
/// Closes <see cref="SubHardware" /> using <see cref="Hardware.Close" />.
/// </summary>
public void Close()
{
_lmSensors?.Close();
foreach (IHardware iHardware in SubHardware)
{
if (iHardware is Hardware hardware)
hardware.Close();
}
}
}

View File

@@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class MotherboardGroup : IGroup
{
private readonly Motherboard[] _motherboards;
public MotherboardGroup(SMBios smbios, ISettings settings)
{
_motherboards = new Motherboard[1];
_motherboards[0] = new Motherboard(smbios, settings);
}
public IReadOnlyList<IHardware> Hardware => _motherboards;
public void Close()
{
foreach (Motherboard mainboard in _motherboards)
mainboard.Close();
}
public string GetReport()
{
return null;
}
}

View File

@@ -0,0 +1,13 @@
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class Temperature
{
public readonly int Index;
public readonly string Name;
public Temperature(string name, int index)
{
Name = name;
Index = index;
}
}

View File

@@ -0,0 +1,24 @@
namespace LibreHardwareMonitor.Hardware.Motherboard;
internal class Voltage
{
public readonly bool Hidden;
public readonly int Index;
public readonly string Name;
public readonly float Rf;
public readonly float Ri;
public readonly float Vf;
public Voltage(string name, int index, bool hidden = false) : this(name, index, 0, 1, 0, hidden)
{ }
public Voltage(string name, int index, float ri, float rf, float vf = 0, bool hidden = false)
{
Name = name;
Index = index;
Ri = ri;
Rf = rf;
Vf = vf;
Hidden = hidden;
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Threading;
namespace LibreHardwareMonitor.Hardware;
internal static class Mutexes
{
private static Mutex _ecMutex;
private static Mutex _isaBusMutex;
private static Mutex _pciBusMutex;
private static Mutex _razerMutex;
/// <summary>
/// Opens the mutexes.
/// </summary>
public static void Open()
{
_isaBusMutex = CreateOrOpenExistingMutex("Global\\Access_ISABUS.HTP.Method");
_pciBusMutex = CreateOrOpenExistingMutex("Global\\Access_PCI");
_ecMutex = CreateOrOpenExistingMutex("Global\\Access_EC");
_razerMutex = CreateOrOpenExistingMutex("Global\\RazerReadWriteGuardMutex");
static Mutex CreateOrOpenExistingMutex(string name)
{
try
{
return new Mutex(false, name);
}
catch (UnauthorizedAccessException)
{
try
{
return Mutex.OpenExisting(name);
}
catch
{
// Ignored.
}
}
return null;
}
}
/// <summary>
/// Closes the mutexes.
/// </summary>
public static void Close()
{
_isaBusMutex?.Close();
_pciBusMutex?.Close();
_ecMutex?.Close();
_razerMutex?.Close();
}
public static bool WaitIsaBus(int millisecondsTimeout)
{
return WaitMutex(_isaBusMutex, millisecondsTimeout);
}
public static void ReleaseIsaBus()
{
_isaBusMutex?.ReleaseMutex();
}
public static bool WaitPciBus(int millisecondsTimeout)
{
return WaitMutex(_pciBusMutex, millisecondsTimeout);
}
public static void ReleasePciBus()
{
_pciBusMutex?.ReleaseMutex();
}
public static bool WaitEc(int millisecondsTimeout)
{
return WaitMutex(_ecMutex, millisecondsTimeout);
}
public static void ReleaseEc()
{
_ecMutex?.ReleaseMutex();
}
public static bool WaitRazer(int millisecondsTimeout)
{
return WaitMutex(_razerMutex, millisecondsTimeout);
}
public static void ReleaseRazer()
{
_razerMutex?.ReleaseMutex();
}
private static bool WaitMutex(Mutex mutex, int millisecondsTimeout)
{
if (mutex == null)
return true;
try
{
return mutex.WaitOne(millisecondsTimeout, false);
}
catch (AbandonedMutexException)
{
return true;
}
catch (InvalidOperationException)
{
return false;
}
}
}

View File

@@ -0,0 +1,112 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Diagnostics;
using System.Net.NetworkInformation;
namespace LibreHardwareMonitor.Hardware.Network;
internal sealed class Network : Hardware
{
private readonly Sensor _dataDownloaded;
private readonly Sensor _dataUploaded;
private readonly Sensor _downloadSpeed;
private readonly Sensor _networkUtilization;
private readonly Sensor _uploadSpeed;
private long _bytesDownloaded;
private long _bytesUploaded;
private long _lastTick;
public Network(NetworkInterface networkInterface, ISettings settings)
: base(networkInterface.Name, new Identifier("nic", networkInterface.Id), settings)
{
NetworkInterface = networkInterface;
_dataUploaded = new Sensor("Data Uploaded", 2, SensorType.Data, this, settings);
ActivateSensor(_dataUploaded);
_dataDownloaded = new Sensor("Data Downloaded", 3, SensorType.Data, this, settings);
ActivateSensor(_dataDownloaded);
_uploadSpeed = new Sensor("Upload Speed", 7, SensorType.Throughput, this, settings);
ActivateSensor(_uploadSpeed);
_downloadSpeed = new Sensor("Download Speed", 8, SensorType.Throughput, this, settings);
ActivateSensor(_downloadSpeed);
_networkUtilization = new Sensor("Network Utilization", 1, SensorType.Load, this, settings);
ActivateSensor(_networkUtilization);
_bytesUploaded = NetworkInterface.GetIPStatistics().BytesSent;
_bytesDownloaded = NetworkInterface.GetIPStatistics().BytesReceived;
_lastTick = Stopwatch.GetTimestamp();
}
public override HardwareType HardwareType
{
get { return HardwareType.Network; }
}
internal NetworkInterface NetworkInterface { get; private set; }
public override void Update()
{
try
{
if (NetworkInterface == null)
return;
long newTick = Stopwatch.GetTimestamp();
double dt = new TimeSpan(newTick - _lastTick).TotalSeconds;
IPv4InterfaceStatistics interfaceStats = NetworkInterface.GetIPv4Statistics();
// Report out the number of GB (2^30 Bytes) that this interface has up/downloaded. Note
// that these values can reset back at zero (eg: after waking from sleep).
_dataUploaded.Value = (float)(interfaceStats.BytesSent / (double)0x40000000);
_dataDownloaded.Value = (float)(interfaceStats.BytesReceived / (double)0x40000000);
// Detect a reset in interface stats if the new total is less than what was previously
// seen. While setting the previous values to zero doesn't encapsulate the value the
// instant before the reset, it is the best approximation we have.
if (interfaceStats.BytesSent < _bytesUploaded || interfaceStats.BytesReceived < _bytesDownloaded)
{
_bytesUploaded = 0;
_bytesDownloaded = 0;
}
long dBytesUploaded = interfaceStats.BytesSent - _bytesUploaded;
long dBytesDownloaded = interfaceStats.BytesReceived - _bytesDownloaded;
// Upload and download speeds are reported as the number of bytes transfered over the
// time difference since the last report. In this way, the values represent the average
// number of bytes up/downloaded in a second.
_uploadSpeed.Value = (float)(dBytesUploaded / dt);
_downloadSpeed.Value = (float)(dBytesDownloaded / dt);
// Network speed is in bits per second, so when calculating the load on the NIC we first
// grab the total number of bits up/downloaded
long dbits = (dBytesUploaded + dBytesDownloaded) * 8;
// Converts the ratio of total bits transferred over time over theoretical max bits
// transfer rate into a percentage load
double load = (dbits / dt / NetworkInterface.Speed) * 100;
// Finally clamp the value between 0% and 100% to avoid reporting nonsensical numbers
_networkUtilization.Value = (float)Math.Min(Math.Max(load, 0), 100);
// Store the recorded values and time, so they can be used in the next update
_bytesUploaded = interfaceStats.BytesSent;
_bytesDownloaded = interfaceStats.BytesReceived;
_lastTick = newTick;
}
catch (NetworkInformationException networkInformationException) when (unchecked(networkInformationException.HResult == (int)0x80004005))
{
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
if (networkInterface.Id.Equals(NetworkInterface?.Id))
{
NetworkInterface = networkInterface;
break;
}
}
}
}
}

View File

@@ -0,0 +1,149 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
namespace LibreHardwareMonitor.Hardware.Network;
internal class NetworkGroup : IGroup, IHardwareChanged
{
public event HardwareEventHandler HardwareAdded;
public event HardwareEventHandler HardwareRemoved;
private readonly Dictionary<string, Network> _networks = new();
private readonly object _scanLock = new();
private readonly ISettings _settings;
private readonly List<Network> _hardware = new();
public NetworkGroup(ISettings settings)
{
_settings = settings;
UpdateNetworkInterfaces(settings);
NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAddressChanged;
}
public IReadOnlyList<IHardware> Hardware => _hardware;
public string GetReport()
{
var report = new StringBuilder();
foreach (Network network in _hardware)
{
report.AppendLine(network.NetworkInterface.Description);
report.AppendLine(network.NetworkInterface.OperationalStatus.ToString());
report.AppendLine();
foreach (ISensor sensor in network.Sensors)
{
report.AppendLine(sensor.Name);
report.AppendLine(sensor.Value.ToString());
report.AppendLine();
}
}
return report.ToString();
}
public void Close()
{
NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
NetworkChange.NetworkAvailabilityChanged -= NetworkChange_NetworkAddressChanged;
foreach (Network network in _hardware)
network.Close();
}
private void UpdateNetworkInterfaces(ISettings settings)
{
// When multiple events fire concurrently, we don't want threads interfering
// with others as they manipulate non-thread safe state.
lock (_scanLock)
{
IOrderedEnumerable<NetworkInterface> networkInterfaces = GetNetworkInterfaces();
if (networkInterfaces == null)
return;
var foundNetworkInterfaces = networkInterfaces.ToDictionary(x => x.Id, x => x);
// Remove network interfaces that no longer exist.
List<string> removeKeys = new();
foreach (KeyValuePair<string, Network> networkInterfacePair in _networks)
{
if (foundNetworkInterfaces.ContainsKey(networkInterfacePair.Key))
continue;
removeKeys.Add(networkInterfacePair.Key);
}
foreach (string key in removeKeys)
{
Network network = _networks[key];
network.Close();
_networks.Remove(key);
_hardware.Remove(network);
HardwareRemoved?.Invoke(network);
}
// Add new network interfaces.
foreach (KeyValuePair<string, NetworkInterface> networkInterfacePair in foundNetworkInterfaces)
{
if (!_networks.ContainsKey(networkInterfacePair.Key))
{
_networks.Add(networkInterfacePair.Key, new Network(networkInterfacePair.Value, settings));
_hardware.Add(_networks[networkInterfacePair.Key]);
HardwareAdded?.Invoke(_networks[networkInterfacePair.Key]);
}
}
}
}
private static IOrderedEnumerable<NetworkInterface> GetNetworkInterfaces()
{
int retry = 0;
while (retry++ < 5)
{
try
{
return NetworkInterface.GetAllNetworkInterfaces()
.Where(DesiredNetworkType)
.OrderBy(x => x.Name);
}
catch (NetworkInformationException)
{
// Disabling IPv4 while running can cause a NetworkInformationException: The pipe is being closed.
// This can be retried.
}
}
return null;
}
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
UpdateNetworkInterfaces(_settings);
}
private static bool DesiredNetworkType(NetworkInterface nic)
{
switch (nic.NetworkInterfaceType)
{
case NetworkInterfaceType.Loopback:
case NetworkInterfaceType.Tunnel:
case NetworkInterfaceType.Unknown:
return false;
default:
return true;
}
}
}

View File

@@ -0,0 +1,301 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Reflection;
using System.Runtime.InteropServices;
namespace LibreHardwareMonitor.Hardware;
internal static class OpCode
{
public static CpuidDelegate CpuId;
public static RdtscDelegate Rdtsc;
private static IntPtr _codeBuffer;
private static ulong _size;
// void __stdcall cpuidex(unsigned int index, unsigned int ecxValue,
// unsigned int* eax, unsigned int* ebx, unsigned int* ecx,
// unsigned int* edx)
// {
// int info[4];
// __cpuidex(info, index, ecxValue);
// *eax = info[0];
// *ebx = info[1];
// *ecx = info[2];
// *edx = info[3];
// }
private static readonly byte[] CpuId32 =
{
0x55, // push ebp
0x8B,
0xEC, // mov ebp, esp
0x83,
0xEC,
0x10, // sub esp, 10h
0x8B,
0x45,
0x08, // mov eax, dword ptr [ebp+8]
0x8B,
0x4D,
0x0C, // mov ecx, dword ptr [ebp+0Ch]
0x53, // push ebx
0x0F,
0xA2, // cpuid
0x56, // push esi
0x8D,
0x75,
0xF0, // lea esi, [info]
0x89,
0x06, // mov dword ptr [esi],eax
0x8B,
0x45,
0x10, // mov eax, dword ptr [eax]
0x89,
0x5E,
0x04, // mov dword ptr [esi+4], ebx
0x89,
0x4E,
0x08, // mov dword ptr [esi+8], ecx
0x89,
0x56,
0x0C, // mov dword ptr [esi+0Ch], edx
0x8B,
0x4D,
0xF0, // mov ecx, dword ptr [info]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x14, // mov eax, dword ptr [ebx]
0x8B,
0x4D,
0xF4, // mov ecx, dword ptr [ebp-0Ch]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x18, // mov eax, dword ptr [ecx]
0x8B,
0x4D,
0xF8, // mov ecx, dword ptr [ebp-8]
0x89,
0x08, // mov dword ptr [eax], ecx
0x8B,
0x45,
0x1C, // mov eax, dword ptr [edx]
0x8B,
0x4D,
0xFC, // mov ecx, dword ptr [ebp-4]
0x5E, // pop esi
0x89,
0x08, // mov dword ptr [eax], ecx
0x5B, // pop ebx
0xC9, // leave
0xC2,
0x18,
0x00 // ret 18h
};
private static readonly byte[] CpuId64Linux =
{
0x49,
0x89,
0xD2, // mov r10, rdx
0x49,
0x89,
0xCB, // mov r11, rcx
0x53, // push rbx
0x89,
0xF8, // mov eax, edi
0x89,
0xF1, // mov ecx, esi
0x0F,
0xA2, // cpuid
0x41,
0x89,
0x02, // mov dword ptr [r10], eax
0x41,
0x89,
0x1B, // mov dword ptr [r11], ebx
0x41,
0x89,
0x08, // mov dword ptr [r8], ecx
0x41,
0x89,
0x11, // mov dword ptr [r9], edx
0x5B, // pop rbx
0xC3 // ret
};
private static readonly byte[] CpuId64Windows =
{
0x48,
0x89,
0x5C,
0x24,
0x08, // mov qword ptr [rsp+8], rbx
0x8B,
0xC1, // mov eax, ecx
0x8B,
0xCA, // mov ecx, edx
0x0F,
0xA2, // cpuid
0x41,
0x89,
0x00, // mov dword ptr [r8], eax
0x48,
0x8B,
0x44,
0x24,
0x28, // mov rax, qword ptr [rsp+28h]
0x41,
0x89,
0x19, // mov dword ptr [r9], ebx
0x48,
0x8B,
0x5C,
0x24,
0x08, // mov rbx, qword ptr [rsp+8]
0x89,
0x08, // mov dword ptr [rax], ecx
0x48,
0x8B,
0x44,
0x24,
0x30, // mov rax, qword ptr [rsp+30h]
0x89,
0x10, // mov dword ptr [rax], edx
0xC3 // ret
};
// unsigned __int64 __stdcall rdtsc() {
// return __rdtsc();
// }
private static readonly byte[] Rdtsc32 =
{
0x0F,
0x31, // rdtsc
0xC3 // ret
};
private static readonly byte[] Rdtsc64 =
{
0x0F,
0x31, // rdtsc
0x48,
0xC1,
0xE2,
0x20, // shl rdx, 20h
0x48,
0x0B,
0xC2, // or rax, rdx
0xC3 // ret
};
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool CpuidDelegate(uint index, uint ecxValue, out uint eax, out uint ebx, out uint ecx, out uint edx);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate ulong RdtscDelegate();
public static void Open()
{
byte[] rdTscCode;
byte[] cpuidCode;
if (IntPtr.Size == 4)
{
rdTscCode = Rdtsc32;
cpuidCode = CpuId32;
}
else
{
rdTscCode = Rdtsc64;
cpuidCode = Software.OperatingSystem.IsUnix ? CpuId64Linux : CpuId64Windows;
}
_size = (ulong)(rdTscCode.Length + cpuidCode.Length);
if (Software.OperatingSystem.IsUnix)
{
#if NETFRAMEWORK
Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
#else
Assembly assembly = Assembly.Load("Mono.Posix.NETStandard, Version=1.0.0.0, Culture=neutral");
#endif
Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
MethodInfo mmap = sysCall.GetMethod("mmap");
Type mmapProts = assembly.GetType("Mono.Unix.Native.MmapProts");
object mmapProtsParam = Enum.ToObject(mmapProts,
(int)mmapProts.GetField("PROT_READ").GetValue(null) |
(int)mmapProts.GetField("PROT_WRITE").GetValue(null) |
(int)mmapProts.GetField("PROT_EXEC").GetValue(null));
Type mmapFlags = assembly.GetType("Mono.Unix.Native.MmapFlags");
object mmapFlagsParam = Enum.ToObject(mmapFlags,
(int)mmapFlags.GetField("MAP_ANONYMOUS").GetValue(null) |
(int)mmapFlags.GetField("MAP_PRIVATE").GetValue(null));
if (mmap != null)
_codeBuffer = (IntPtr)mmap.Invoke(null, new[] { IntPtr.Zero, _size, mmapProtsParam, mmapFlagsParam, -1, 0 });
}
else
{
_codeBuffer = Interop.Kernel32.VirtualAlloc(IntPtr.Zero,
(UIntPtr)_size,
Interop.Kernel32.MEM.MEM_COMMIT | Interop.Kernel32.MEM.MEM_RESERVE,
Interop.Kernel32.PAGE.PAGE_EXECUTE_READWRITE);
}
Marshal.Copy(rdTscCode, 0, _codeBuffer, rdTscCode.Length);
Rdtsc = Marshal.GetDelegateForFunctionPointer(_codeBuffer, typeof(RdtscDelegate)) as RdtscDelegate;
IntPtr cpuidAddress = (IntPtr)((long)_codeBuffer + rdTscCode.Length);
Marshal.Copy(cpuidCode, 0, cpuidAddress, cpuidCode.Length);
CpuId = Marshal.GetDelegateForFunctionPointer(cpuidAddress, typeof(CpuidDelegate)) as CpuidDelegate;
}
public static void Close()
{
Rdtsc = null;
CpuId = null;
if (Software.OperatingSystem.IsUnix)
{
#if NETFRAMEWORK
Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
#else
Assembly assembly = Assembly.Load("Mono.Posix.NETStandard, Version=1.0.0.0, Culture=neutral");
#endif
Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
MethodInfo method = sysCall.GetMethod("munmap");
method?.Invoke(null, new object[] { _codeBuffer, _size });
}
else
{
Interop.Kernel32.VirtualFree(_codeBuffer, UIntPtr.Zero, Interop.Kernel32.MEM.MEM_RELEASE);
}
}
public static bool CpuIdTx(uint index, uint ecxValue, out uint eax, out uint ebx, out uint ecx, out uint edx, GroupAffinity affinity)
{
GroupAffinity previousAffinity = ThreadAffinity.Set(affinity);
if (previousAffinity == GroupAffinity.Undefined)
{
eax = ebx = ecx = edx = 0;
return false;
}
CpuId(index, ecxValue, out eax, out ebx, out ecx, out edx);
ThreadAffinity.Set(previousAffinity);
return true;
}
}

View File

@@ -0,0 +1,89 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Globalization;
namespace LibreHardwareMonitor.Hardware;
internal class Parameter : IParameter
{
private readonly ISettings _settings;
private readonly ParameterDescription _description;
private bool _isDefault;
private float _value;
public Parameter(ParameterDescription description, ISensor sensor, ISettings settings)
{
Sensor = sensor;
_description = description;
_settings = settings;
_isDefault = !settings.Contains(Identifier.ToString());
_value = description.DefaultValue;
if (!_isDefault && !float.TryParse(settings.GetValue(Identifier.ToString(), "0"), NumberStyles.Float, CultureInfo.InvariantCulture, out _value))
{
_value = description.DefaultValue;
}
}
public float DefaultValue
{
get { return _description.DefaultValue; }
}
public string Description
{
get { return _description.Description; }
}
public Identifier Identifier
{
get { return new Identifier(Sensor.Identifier, "parameter", Name.Replace(" ", string.Empty).ToLowerInvariant()); }
}
public bool IsDefault
{
get { return _isDefault; }
set
{
_isDefault = value;
if (value)
{
_value = _description.DefaultValue;
_settings.Remove(Identifier.ToString());
}
}
}
public string Name
{
get { return _description.Name; }
}
public ISensor Sensor { get; }
public float Value
{
get { return _value; }
set
{
_isDefault = false;
_value = value;
_settings.SetValue(Identifier.ToString(), value.ToString(CultureInfo.InvariantCulture));
}
}
public void Accept(IVisitor visitor)
{
if (visitor == null)
throw new ArgumentNullException(nameof(visitor));
visitor.VisitParameter(this);
}
public void Traverse(IVisitor visitor)
{ }
}

Some files were not shown because too many files have changed in this diff Show More