first commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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...
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}'")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user