From fe9857c2ed1a8f56bb0c6a22c34bb352bd31f8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Bederi=C3=A1n?= <4043375+zzzoom@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:11:02 -0300 Subject: [PATCH] Intel discrete GPU support (#1822) * Intel discrete GPU support through Intel GCL library * Formatting --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> --- .../Hardware/Gpu/IntelDiscreteGpu.cs | 428 ++++++++++++++ .../Hardware/Gpu/IntelGpuGroup.cs | 165 ++++-- LibreHardwareMonitorLib/Interop/IntelGcl.cs | 525 ++++++++++++++++++ 3 files changed, 1078 insertions(+), 40 deletions(-) create mode 100644 LibreHardwareMonitorLib/Hardware/Gpu/IntelDiscreteGpu.cs create mode 100644 LibreHardwareMonitorLib/Interop/IntelGcl.cs diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/IntelDiscreteGpu.cs b/LibreHardwareMonitorLib/Hardware/Gpu/IntelDiscreteGpu.cs new file mode 100644 index 0000000..a6184b6 --- /dev/null +++ b/LibreHardwareMonitorLib/Hardware/Gpu/IntelDiscreteGpu.cs @@ -0,0 +1,428 @@ +// 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.Runtime.InteropServices; +using LibreHardwareMonitor.Interop; + +namespace LibreHardwareMonitor.Hardware.Gpu; + +internal sealed class IntelDiscreteGpu : GenericGpu +{ + // Constants + private const double MemoryFrequencyDivisor = 8.0; // Intel GCL returns memory frequency multiplied by 8 + + // Clock sensors + private readonly Sensor _clockCore; + private readonly Sensor _clockMemory; + + // Fan sensors + private readonly Sensor[] _fans; + + // Utilization sensors + private readonly Sensor _loadGlobalActivity; + private readonly Sensor _loadMedia; + private readonly Sensor _loadRenderCompute; + + // Power sensors + private readonly Sensor _powerGpu; + private readonly Sensor _powerTotal; + + // Temperature sensors + private readonly Sensor _temperatureGpuCore; + private readonly Sensor _temperatureMemory; + + // Voltage sensors + private readonly Sensor _voltageCore; + private readonly Sensor _voltageMemory; + + // Timestamps + private double _currentTimestamp = double.NaN; + private string _deviceId; + + // Intel GCL properties and data + private readonly IntelGcl.ctl_device_adapter_handle_t _handle; + + // Power calculation support + private double _lastEnergyReading = double.NaN; + + // Activity counter calculation support + private double _lastGlobalActivityCounter = double.NaN; + private double _lastMediaActivityCounter = double.NaN; + private double _lastRenderComputeActivityCounter = double.NaN; + private double _lastTimestamp = double.NaN; + private double _lastTotalCardEnergyReading = double.NaN; + private IntelGcl.ctl_device_adapter_properties_t _properties; + + // Telemetry data + private IntelGcl.ctl_power_telemetry_t _telemetry; + + public IntelDiscreteGpu(IntelGcl.ctl_device_adapter_handle_t handle, ISettings settings) + : base(GetDeviceName(handle), new Identifier("gpu-intel", GetDeviceId(handle)), settings) + { + _handle = handle; + IsValid = false; + + // Initialize device properties + if (!InitializeDevice()) + return; + + // Initialize temperature sensors + _temperatureGpuCore = new Sensor("GPU Core", 0, SensorType.Temperature, this, settings); + _temperatureMemory = new Sensor("GPU Memory", 1, SensorType.Temperature, this, settings); + + // Initialize clock sensors + _clockCore = new Sensor("GPU Core", 0, SensorType.Clock, this, settings); + _clockMemory = new Sensor("GPU Memory", 1, SensorType.Clock, this, settings); + + // Initialize voltage sensors + _voltageCore = new Sensor("GPU Core", 0, SensorType.Voltage, this, settings); + _voltageMemory = new Sensor("GPU Memory", 1, SensorType.Voltage, this, settings); + + // Initialize power sensors + _powerGpu = new Sensor("GPU Package", 0, SensorType.Power, this, settings); + _powerTotal = new Sensor("GPU Total", 1, SensorType.Power, this, settings); + + // Initialize utilization sensors + _loadGlobalActivity = new Sensor("GPU Core", 0, SensorType.Load, this, settings); + _loadRenderCompute = new Sensor("GPU Render/Compute", 1, SensorType.Load, this, settings); + _loadMedia = new Sensor("GPU Media", 2, SensorType.Load, this, settings); + + // Initialize fan sensors based on available fans + int fanCount = (int)GetFanCount(); + _fans = new Sensor[fanCount]; + + for (int i = 0; i < fanCount; i++) + { + string fanName = fanCount == 1 ? "GPU Fan" : $"GPU Fan {i + 1}"; + _fans[i] = new Sensor(fanName, i, SensorType.Fan, this, settings); + } + + Update(); + } + + public override string DeviceId => _deviceId ?? GetDeviceId(_handle); + + public uint DriverVersion { get; private set; } + + public override HardwareType HardwareType => HardwareType.GpuIntel; + + public bool IsValid { get; private set; } + + public uint RevisionId { get; private set; } + + public uint VendorId { get; private set; } + + private static bool TryGetDeviceProperties(IntelGcl.ctl_device_adapter_handle_t handle, out IntelGcl.ctl_device_adapter_properties_t properties) + { + properties = new IntelGcl.ctl_device_adapter_properties_t + { + Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_device_adapter_properties_t)), + Version = 2 + }; + + int result = IntelGcl.ctlGetDeviceProperties(handle, ref properties); + return result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && + properties.device_type == IntelGcl.ctl_device_type_t.CTL_DEVICE_TYPE_GRAPHICS; + } + + private static string GetDeviceName(IntelGcl.ctl_device_adapter_handle_t handle) + { + if (TryGetDeviceProperties(handle, out IntelGcl.ctl_device_adapter_properties_t properties)) + { + return properties.name; + } + + return "Intel GPU"; + } + + private static string GetDeviceId(IntelGcl.ctl_device_adapter_handle_t handle) + { + if (TryGetDeviceProperties(handle, out IntelGcl.ctl_device_adapter_properties_t properties)) + { + return $"0x{properties.pci_device_id:X4}"; + } + + return "0x0000"; + } + + // Device initialization + private bool InitializeDevice() + { + if (TryGetDeviceProperties(_handle, out _properties)) + { + _deviceId = $"0x{_properties.pci_device_id:X4}"; + VendorId = _properties.pci_vendor_id; + RevisionId = _properties.rev_id; + DriverVersion = (uint)_properties.driver_version; + IsValid = true; + return true; + } + + return false; + } + + public override void Update() + { + if (!IsValid) + return; + + try + { + // Update telemetry data from Intel GCL + if (!UpdateTelemetry()) + return; + + // Update power sensors + UpdatePowerFromEnergyCounter(_telemetry.gpuEnergyCounter, ref _lastEnergyReading, _powerGpu); + UpdatePowerFromEnergyCounter(_telemetry.totalCardEnergyCounter, ref _lastTotalCardEnergyReading, _powerTotal); + + // Update temperature sensors + UpdateSensorFromTelemetry(_telemetry.gpuCurrentTemperature, _temperatureGpuCore); + UpdateSensorFromTelemetry(_telemetry.vramCurrentTemperature, _temperatureMemory); + + // Update clock sensors + UpdateSensorFromTelemetry(_telemetry.gpuCurrentClockFrequency, _clockCore); + UpdateMemoryFrequency(_clockMemory); + + // Update voltage sensors + UpdateSensorFromTelemetry(_telemetry.gpuVoltage, _voltageCore); + UpdateSensorFromTelemetry(_telemetry.vramVoltage, _voltageMemory); + + // Update utilization sensors + UpdateUtilizationFromActivityCounter(_telemetry.globalActivityCounter, ref _lastGlobalActivityCounter, _loadGlobalActivity); + UpdateUtilizationFromActivityCounter(_telemetry.renderComputeActivityCounter, ref _lastRenderComputeActivityCounter, _loadRenderCompute); + UpdateUtilizationFromActivityCounter(_telemetry.mediaActivityCounter, ref _lastMediaActivityCounter, _loadMedia); + + // Update fan sensors + UpdateFanSpeeds(_fans); + } + catch (Exception ex) + { + // Log error but don't crash the update + System.Diagnostics.Debug.WriteLine($"Error updating Intel GPU sensors: {ex.Message}"); + } + } + + private bool UpdateTelemetry() + { + if (!IsValid) + return false; + + var telemetry = new IntelGcl.ctl_power_telemetry_t + { + Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_power_telemetry_t)), + Version = 1, + psu = new IntelGcl.ctl_psu_info_t[IntelGcl.CTL_PSU_COUNT], + fanSpeed = new IntelGcl.ctl_oc_telemetry_item_t[IntelGcl.CTL_FAN_COUNT] + }; + + if (IntelGcl.ctlPowerTelemetryGet(_handle, ref telemetry) == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS) + { + _telemetry = telemetry; + _lastTimestamp = _currentTimestamp; + _currentTimestamp = _telemetry.timeStamp.bSupported ? GetTelemetryValue(_telemetry.timeStamp) : DateTimeOffset.UtcNow.Ticks; + return true; + } + + return false; + } + + private void UpdateMemoryFrequency(Sensor sensor) + { + double frequency = double.NaN; + + uint freqCount = 0; + int result = IntelGcl.ctlEnumFrequencyDomains(_handle, ref freqCount, null); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && freqCount > 0) + { + var freqHandles = new IntelGcl.ctl_freq_handle_t[freqCount]; + result = IntelGcl.ctlEnumFrequencyDomains(_handle, ref freqCount, freqHandles); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS) + { + for (int i = 0; i < freqCount; i++) + { + var properties = new IntelGcl.ctl_freq_properties_t + { + Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_freq_properties_t)), + Version = 0 + }; + + result = IntelGcl.ctlFrequencyGetProperties(freqHandles[i], ref properties); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && + properties.type == IntelGcl.ctl_freq_domain_t.CTL_FREQ_DOMAIN_MEMORY) + { + var state = new IntelGcl.ctl_freq_state_t + { + Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_freq_state_t)), + Version = 0 + }; + + result = IntelGcl.ctlFrequencyGetState(freqHandles[i], ref state); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && state.actual >= 0) + { + frequency = state.actual / MemoryFrequencyDivisor; + break; + } + } + } + } + } + + if (double.IsNaN(frequency) && _telemetry.vramCurrentClockFrequency.bSupported) + { + frequency = GetTelemetryValue(_telemetry.vramCurrentClockFrequency); + } + + if (!double.IsNaN(frequency)) + { + sensor.Value = (float)frequency; + ActivateSensor(sensor); + } + else + { + sensor.Value = null; + } + } + + private uint GetFanCount() + { + uint fanCount = 0; + int result = IntelGcl.ctlEnumFans(_handle, ref fanCount, null); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS) + { + return fanCount; + } + + return 0; + } + + private void UpdateFanSpeeds(Sensor[] fanSensors) + { + uint fanCount = (uint)Math.Min(Math.Max(0, GetFanCount()), fanSensors.Length); + if (fanCount == 0) + return; + + var fanHandles = new IntelGcl.ctl_fan_handle_t[fanCount]; + int result = IntelGcl.ctlEnumFans(_handle, ref fanCount, fanHandles); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS) + { + for (int i = 0; i < fanCount; i++) + { + int fanSpeed = -1; + result = IntelGcl.ctlFanGetState(fanHandles[i], IntelGcl.ctl_fan_speed_units_t.CTL_FAN_SPEED_UNITS_RPM, ref fanSpeed); + + if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && fanSpeed >= 0) + { + fanSensors[i].Value = fanSpeed; + ActivateSensor(fanSensors[i]); + } + else + { + fanSensors[i].Value = null; + } + } + + for (int i = (int)fanCount; i < fanSensors.Length; i++) + { + fanSensors[i].Value = null; + } + } + } + + private void UpdateSensorFromTelemetry(IntelGcl.ctl_oc_telemetry_item_t telemetryItem, Sensor sensor) + { + if (telemetryItem.bSupported) + { + sensor.Value = (float)GetTelemetryValue(telemetryItem); + ActivateSensor(sensor); + } + else + { + sensor.Value = null; + } + } + + private double GetTelemetryValue(IntelGcl.ctl_oc_telemetry_item_t item) + { + return item.type switch + { + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_FLOAT => item.value.datafloat, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_DOUBLE => item.value.datadouble, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT32 => item.value.datau32, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT32 => item.value.data32, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT64 => item.value.datau64, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT64 => item.value.data64, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT16 => item.value.datau16, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT16 => item.value.data16, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT8 => item.value.datau8, + IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT8 => item.value.data8, + _ => double.NaN + }; + } + + private void UpdatePowerFromEnergyCounter(IntelGcl.ctl_oc_telemetry_item_t energyCounter, ref double lastEnergyReading, Sensor powerSensor) + { + if (!IsValid || powerSensor == null) + return; + + double currentEnergy = energyCounter.bSupported ? GetTelemetryValue(energyCounter) : double.NaN; + double deltaTime = _currentTimestamp - _lastTimestamp; + + if (deltaTime > 0.0 && !double.IsNaN(currentEnergy) && !double.IsNaN(lastEnergyReading)) + { + double deltaEnergy = currentEnergy - lastEnergyReading; + double power = deltaEnergy / deltaTime; + power = power < 0 ? 0 : power; + + powerSensor.Value = (float)power; + ActivateSensor(powerSensor); + } + else + { + powerSensor.Value = null; + } + + lastEnergyReading = currentEnergy; + } + + private void UpdateUtilizationFromActivityCounter(IntelGcl.ctl_oc_telemetry_item_t activityCounter, ref double lastActivityReading, Sensor activitySensor) + { + if (!IsValid || activitySensor == null) + return; + + double currentActivity = activityCounter.bSupported ? GetTelemetryValue(activityCounter) : double.NaN; + double deltaTime = _currentTimestamp - _lastTimestamp; + + if (deltaTime > 0 && !double.IsNaN(currentActivity) && !double.IsNaN(lastActivityReading)) + { + double activeDiff = currentActivity - lastActivityReading; + if (activeDiff >= 0) + { + double activity = (activeDiff / deltaTime) * 100.0; + activity = Math.Min(Math.Max(activity, 0.0), 100.0); + + activitySensor.Value = (float)activity; + ActivateSensor(activitySensor); + } + else + { + activitySensor.Value = null; + } + } + else + { + activitySensor.Value = null; + } + + lastActivityReading = currentActivity; + } +} diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/IntelGpuGroup.cs b/LibreHardwareMonitorLib/Hardware/Gpu/IntelGpuGroup.cs index 12ac761..d7db5c8 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/IntelGpuGroup.cs +++ b/LibreHardwareMonitorLib/Hardware/Gpu/IntelGpuGroup.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; using LibreHardwareMonitor.Hardware.Cpu; +using LibreHardwareMonitor.Interop; namespace LibreHardwareMonitor.Hardware.Gpu; @@ -18,55 +19,126 @@ internal class IntelGpuGroup : IGroup public IntelGpuGroup(List intelCpus, ISettings settings) { - if (!Software.OperatingSystem.IsUnix && intelCpus?.Count > 0) + if (!Software.OperatingSystem.IsUnix) { - _report.AppendLine("Intel GPU (D3D)"); - _report.AppendLine(); + // Initialize Intel GCL for discrete GPUs + bool gclInitialized = false; - 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++) + try { - 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)) + if (IntelGcl.IsAvailable) { - _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)); + gclInitialized = IntelGcl.Initialize(); + } + } + catch (Exception ex) + { + _report.Append("Intel GCL initialization failed: "); + _report.AppendLine(ex.Message); + } - if (deviceInfo.Integrated) + _report.AppendLine("Intel GPU Detection"); + _report.AppendLine(); + _report.Append("Intel GCL Initialized: "); + _report.AppendLine(gclInitialized.ToString(CultureInfo.InvariantCulture)); + _report.AppendLine(); + + // Enumerate discrete GPUs using Intel GCL + if (gclInitialized) + { + try + { + var handles = IntelGcl.GetDeviceHandles(); + _report.Append("Device handles found: "); + _report.AppendLine(handles.Length.ToString(CultureInfo.InvariantCulture)); + + foreach (var handle in handles) { - // 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)); + try + { + var gpu = new IntelDiscreteGpu(handle, settings); + if (gpu.IsValid) + { + _report.Append("Discrete GPU: "); + _report.AppendLine(gpu.Name); + _report.Append("Device ID: "); + _report.AppendLine(gpu.DeviceId); + _report.AppendLine(); + + _hardware.Add(gpu); + _report.AppendLine("Successfully added discrete GPU to hardware list"); + } + else + { + _report.AppendLine("Skipped invalid GPU device"); + } + } + catch (Exception ex) + { + _report.Append("Failed to create IntelDiscreteGpu: "); + _report.AppendLine(ex.Message); + _report.AppendLine(ex.StackTrace); + } } } + catch (Exception ex) + { + _report.Append("Failed to enumerate Intel GPU devices: "); + _report.AppendLine(ex.Message); + } + } + // Enumerate integrated GPUs using D3D (existing logic) + if (intelCpus?.Count > 0) + { + _report.AppendLine("Intel GPU (D3D - Integrated)"); _report.AppendLine(); + + string[] ids = D3DDisplayDevice.GetDeviceIdentifiers(); + + _report.Append("Number of D3D 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(); + } } } } @@ -82,5 +154,18 @@ internal class IntelGpuGroup : IGroup { foreach (Hardware gpu in _hardware) gpu.Close(); + + // Shutdown Intel GCL + try + { + if (IntelGcl.IsInitialized) + { + IntelGcl.Cleanup(); + } + } + catch + { + // Ignore shutdown errors + } } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Interop/IntelGcl.cs b/LibreHardwareMonitorLib/Interop/IntelGcl.cs new file mode 100644 index 0000000..ca42d05 --- /dev/null +++ b/LibreHardwareMonitorLib/Interop/IntelGcl.cs @@ -0,0 +1,525 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibreHardwareMonitor.Interop; + +internal static class IntelGcl +{ + public const int CTL_FAN_COUNT = 5; + public const uint CTL_IMPL_MAJOR_VERSION = 1; + public const uint CTL_IMPL_MINOR_VERSION = 1; + public const uint CTL_IMPL_VERSION = (CTL_IMPL_MAJOR_VERSION << 16) | CTL_IMPL_MINOR_VERSION; + public const int CTL_MAX_DEVICE_NAME_LEN = 100; + public const int CTL_MAX_RESERVED_SIZE = 112; + public const int CTL_PSU_COUNT = 5; + public const int MAX_DEVICES = 64; + public const int MAX_STRING_LENGTH = 256; + + private const string DllName = "ControlLib.dll"; + + static IntelGcl() + { + IsAvailable = GclMethodExists(nameof(ctlInit)) && GclMethodExists(nameof(ctlEnumerateDevices)); + } + + public enum ctl_data_type_t + { + CTL_DATA_TYPE_INT8 = 0, + CTL_DATA_TYPE_UINT8 = 1, + CTL_DATA_TYPE_INT16 = 2, + CTL_DATA_TYPE_UINT16 = 3, + CTL_DATA_TYPE_INT32 = 4, + CTL_DATA_TYPE_UINT32 = 5, + CTL_DATA_TYPE_INT64 = 6, + CTL_DATA_TYPE_UINT64 = 7, + CTL_DATA_TYPE_FLOAT = 8, + CTL_DATA_TYPE_DOUBLE = 9, + CTL_DATA_TYPE_STRING_ASCII = 10, + CTL_DATA_TYPE_STRING_UTF16 = 11, + CTL_DATA_TYPE_STRING_UTF132 = 12, + CTL_DATA_TYPE_UNKNOWN = 0x4800FFFF + } + + public enum ctl_device_type_t + { + CTL_DEVICE_TYPE_GRAPHICS = 1, + CTL_DEVICE_TYPE_SYSTEM = 2, + CTL_DEVICE_TYPE_MAX + } + + public enum ctl_fan_speed_mode_t + { + CTL_FAN_SPEED_MODE_DEFAULT = 0, + CTL_FAN_SPEED_MODE_FIXED = 1, + CTL_FAN_SPEED_MODE_TABLE = 2, + CTL_FAN_SPEED_MODE_MAX + } + + public enum ctl_fan_speed_units_t + { + CTL_FAN_SPEED_UNITS_RPM = 0, + CTL_FAN_SPEED_UNITS_PERCENT = 1, + CTL_FAN_SPEED_UNITS_MAX + } + + public enum ctl_freq_domain_t + { + CTL_FREQ_DOMAIN_GPU = 0, + CTL_FREQ_DOMAIN_MEMORY = 1, + CTL_FREQ_DOMAIN_MEDIA = 2, + CTL_FREQ_DOMAIN_MAX + } + + // Initialization flags + public enum ctl_init_flag_t : uint + { + CTL_INIT_FLAG_USE_LEVEL_ZERO = 1 << 0, // CTL_BIT(0) - Required for telemetry + CTL_INIT_FLAG_MAX = 0x80000000 + } + + public enum ctl_psu_type_t + { + CTL_PSU_TYPE_PSU_NONE = 0, + CTL_PSU_TYPE_PSU_PCIE = 1, + CTL_PSU_TYPE_PSU_6PIN = 2, + CTL_PSU_TYPE_PSU_8PIN = 3 + } + + // Enums + public enum ctl_result_t + { + CTL_RESULT_SUCCESS = 0x00000000, + CTL_RESULT_SUCCESS_STILL_OPEN_BY_ANOTHER_CALLER = 0x00000001, + CTL_RESULT_ERROR_SUCCESS_END = 0x0000FFFF, + CTL_RESULT_ERROR_GENERIC_START = 0x40000000, + CTL_RESULT_ERROR_NOT_INITIALIZED = 0x40000001, + CTL_RESULT_ERROR_ALREADY_INITIALIZED = 0x40000002, + CTL_RESULT_ERROR_DEVICE_LOST = 0x40000003, + CTL_RESULT_ERROR_OUT_OF_HOST_MEMORY = 0x40000004, + CTL_RESULT_ERROR_OUT_OF_DEVICE_MEMORY = 0x40000005, + CTL_RESULT_ERROR_INSUFFICIENT_PERMISSIONS = 0x40000006, + CTL_RESULT_ERROR_NOT_AVAILABLE = 0x40000007, + CTL_RESULT_ERROR_UNINITIALIZED = 0x40000008, + CTL_RESULT_ERROR_UNSUPPORTED_VERSION = 0x40000009, + CTL_RESULT_ERROR_UNSUPPORTED_FEATURE = 0x4000000a, + CTL_RESULT_ERROR_INVALID_ARGUMENT = 0x4000000b, + CTL_RESULT_ERROR_INVALID_API_HANDLE = 0x4000000c, + CTL_RESULT_ERROR_INVALID_NULL_HANDLE = 0x4000000d, + CTL_RESULT_ERROR_INVALID_NULL_POINTER = 0x4000000e, + CTL_RESULT_ERROR_INVALID_SIZE = 0x4000000f, + CTL_RESULT_ERROR_UNSUPPORTED_SIZE = 0x40000010, + CTL_RESULT_ERROR_UNSUPPORTED_ALIGNMENT = 0x40000011, + CTL_RESULT_ERROR_INVALID_SYNCHRONIZATION_OBJECT = 0x40000012, + CTL_RESULT_ERROR_INVALID_ENUMERATION = 0x40000013, + CTL_RESULT_ERROR_UNSUPPORTED_ENUMERATION = 0x40000014, + CTL_RESULT_ERROR_UNSUPPORTED_IMAGE_FORMAT = 0x40000015, + CTL_RESULT_ERROR_INVALID_NATIVE_BINARY = 0x40000016, + CTL_RESULT_ERROR_INVALID_GLOBAL_NAME = 0x40000017, + CTL_RESULT_ERROR_INVALID_KERNEL_NAME = 0x40000018, + CTL_RESULT_ERROR_INVALID_FUNCTION_NAME = 0x40000019, + CTL_RESULT_ERROR_INVALID_GROUP_SIZE_DIMENSION = 0x4000001a, + CTL_RESULT_ERROR_INVALID_GLOBAL_WIDTH_DIMENSION = 0x4000001b, + CTL_RESULT_ERROR_INVALID_KERNEL_ARGUMENT_INDEX = 0x4000001c, + CTL_RESULT_ERROR_INVALID_KERNEL_ARGUMENT_SIZE = 0x4000001d, + CTL_RESULT_ERROR_INVALID_KERNEL_ATTRIBUTE_VALUE = 0x4000001e, + CTL_RESULT_ERROR_INVALID_MODULE_UNLINKED = 0x4000001f, + CTL_RESULT_ERROR_INVALID_COMMAND_LIST_TYPE = 0x40000020, + CTL_RESULT_ERROR_OVERLAPPING_REGIONS = 0x40000021, + CTL_RESULT_ERROR_UNKNOWN = 0x4000FFFF + } + + public enum ctl_units_t + { + CTL_UNITS_FREQUENCY_MHZ = 0, + CTL_UNITS_OPERATIONS_GTS = 1, + CTL_UNITS_OPERATIONS_MTS = 2, + CTL_UNITS_VOLTAGE_VOLTS = 3, + CTL_UNITS_POWER_WATTS = 4, + CTL_UNITS_TEMPERATURE_CELSIUS = 5, + CTL_UNITS_ENERGY_JOULES = 6, + CTL_UNITS_TIME_SECONDS = 7, + CTL_UNITS_MEMORY_BYTES = 8, + CTL_UNITS_ANGULAR_SPEED_RPM = 9, + CTL_UNITS_POWER_MILLIWATTS = 10, + CTL_UNITS_PERCENT = 11, + CTL_UNITS_MEM_SPEED_GBPS = 12, + CTL_UNITS_VOLTAGE_MILLIVOLTS = 13, + CTL_UNITS_BANDWIDTH_MBPS = 14, + CTL_UNITS_UNKNOWN = 0x4800FFFF + } + + public static ctl_api_handle_t ApiHandle { get; private set; } + + // Public interface + public static bool IsAvailable { get; private set; } + + public static bool IsInitialized { get; private set; } + + // P/Invoke declarations + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int ctlInit(ref ctl_init_args_t pInitDesc, ref ctl_api_handle_t phAPIHandle); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int ctlEnumerateDevices(ctl_api_handle_t hAPIHandle, ref uint pCount, [Out] ctl_device_adapter_handle_t[] phDevices); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlGetDeviceProperties(ctl_device_adapter_handle_t hDAhandle, ref ctl_device_adapter_properties_t pProperties); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlPowerTelemetryGet(ctl_device_adapter_handle_t hDeviceHandle, ref ctl_power_telemetry_t pTelemetryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlEnumFans(ctl_device_adapter_handle_t hDAhandle, ref uint pCount, [Out] ctl_fan_handle_t[] phFan); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlFanGetState(ctl_fan_handle_t hFan, ctl_fan_speed_units_t units, ref int pSpeed); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlFanGetProperties(ctl_fan_handle_t hFan, ref ctl_fan_properties_t pProperties); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlEnumFrequencyDomains(ctl_device_adapter_handle_t hDAhandle, ref uint pCount, [Out] ctl_freq_handle_t[] phFrequency); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlFrequencyGetProperties(ctl_freq_handle_t hFrequency, ref ctl_freq_properties_t pProperties); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + public static extern int ctlFrequencyGetState(ctl_freq_handle_t hFrequency, ref ctl_freq_state_t pState); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern int ctlClose(ctl_api_handle_t hAPIHandle); + + private static bool GclMethodExists(string gclMethod) + { + IntPtr module = Kernel32.LoadLibrary(DllName); + if (module != IntPtr.Zero) + { + bool result = Kernel32.GetProcAddress(module, gclMethod) != IntPtr.Zero; + Kernel32.FreeLibrary(module); + return result; + } + + return false; + } + + public static bool Initialize() + { + if (!IsAvailable) + return false; + + if (IsInitialized) + return true; + + var initArgs = new ctl_init_args_t(); + initArgs.Size = (uint)Marshal.SizeOf(typeof(ctl_init_args_t)); + initArgs.Version = 0; + initArgs.AppVersion = CTL_IMPL_VERSION; + initArgs.flags = (uint)ctl_init_flag_t.CTL_INIT_FLAG_USE_LEVEL_ZERO; + + var apiHandle = new ctl_api_handle_t(); + int result = ctlInit(ref initArgs, ref apiHandle); + + if (result != (int)ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + ApiHandle = apiHandle; + IsInitialized = true; + return true; + } + + public static ctl_device_adapter_handle_t[] GetDeviceHandles() + { + if (!IsInitialized && (!Initialize())) + return Array.Empty(); + + // First call to get the device count + uint count = 0; + int result = ctlEnumerateDevices(ApiHandle, ref count, null); + count = Math.Min(count, MAX_DEVICES); + + if (result != (int)ctl_result_t.CTL_RESULT_SUCCESS || count == 0) + return Array.Empty(); + + // Second call to get the actual device handles + var handles = new ctl_device_adapter_handle_t[count]; + result = ctlEnumerateDevices(ApiHandle, ref count, handles); + + if (result != (int)ctl_result_t.CTL_RESULT_SUCCESS) + return Array.Empty(); + + return handles; + } + + public static void Cleanup() + { + if (IsInitialized) + { + try + { + ctlClose(ApiHandle); + } + catch + { + // Ignore cleanup errors + } + finally + { + IsInitialized = false; + ApiHandle = new ctl_api_handle_t(); + } + } + } + + // Unions + [StructLayout(LayoutKind.Explicit)] + public struct ctl_data_value_t + { + [FieldOffset(0)] + public sbyte data8; + + [FieldOffset(0)] + public byte datau8; + + [FieldOffset(0)] + public short data16; + + [FieldOffset(0)] + public ushort datau16; + + [FieldOffset(0)] + public int data32; + + [FieldOffset(0)] + public uint datau32; + + [FieldOffset(0)] + public long data64; + + [FieldOffset(0)] + public ulong datau64; + + [FieldOffset(0)] + public float datafloat; + + [FieldOffset(0)] + public double datadouble; + } + + // Structures + [StructLayout(LayoutKind.Sequential)] + public struct ctl_api_handle_t + { + private IntPtr pNext; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_application_id_t + { + public uint Data1; + public ushort Data2; + public ushort Data3; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Data4; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_init_args_t + { + public uint Size; + public byte Version; + public uint AppVersion; + public uint flags; + public uint SupportedVersion; + public ctl_application_id_t ApplicationUID; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_device_adapter_handle_t + { + private IntPtr pNext; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_fan_handle_t + { + private IntPtr pNext; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_fan_speed_t + { + public uint Size; + public byte Version; + public int speed; + public ctl_fan_speed_units_t units; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_fan_properties_t + { + public uint Size; + public byte Version; + public bool canControl; + public uint supportedModes; + public uint supportedUnits; + public int maxRPM; + public int maxPoints; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_freq_handle_t + { + private IntPtr pNext; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_freq_properties_t + { + public uint Size; + public byte Version; + public ctl_freq_domain_t type; + public bool canControl; + public double min; + public double max; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_freq_state_t + { + public uint Size; + public byte Version; + public double currentVoltage; + public double request; + public double tdp; + public double efficient; + public double actual; + public uint throttleReasons; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_device_adapter_properties_t + { + public uint Size; + public byte Version; + public IntPtr pDeviceID; + public uint device_id_size; + public ctl_device_type_t device_type; + public uint supported_subfunction_flags; + public ulong driver_version; + public ctl_firmware_version_t firmware_version; + public uint pci_vendor_id; + public uint pci_device_id; + public uint rev_id; + public uint num_eus_per_sub_slice; + public uint num_sub_slices_per_slice; + public uint num_slices; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CTL_MAX_DEVICE_NAME_LEN)] + public string name; + + public uint graphics_adapter_properties; + public uint Frequency; + public ushort pci_subsys_id; + public ushort pci_subsys_vendor_id; + public ctl_adapter_bdf_t adapter_bdf; + public uint num_xe_cores; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = CTL_MAX_RESERVED_SIZE)] + public byte[] reserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_adapter_bdf_t + { + public byte bus; + public byte device; + public byte function; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_firmware_version_t + { + public ulong major_version; + public ulong minor_version; + public ulong build_number; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_oc_telemetry_item_t + { + public bool bSupported; + public ctl_units_t units; + public ctl_data_type_t type; + public ctl_data_value_t value; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_psu_info_t + { + public bool bSupported; + public ctl_psu_type_t psuType; + public ctl_oc_telemetry_item_t energyCounter; + public ctl_oc_telemetry_item_t voltage; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_power_telemetry_t + { + public uint Size; + public byte Version; + public ctl_oc_telemetry_item_t timeStamp; + public ctl_oc_telemetry_item_t gpuEnergyCounter; + public ctl_oc_telemetry_item_t gpuVoltage; + public ctl_oc_telemetry_item_t gpuCurrentClockFrequency; + public ctl_oc_telemetry_item_t gpuCurrentTemperature; + public ctl_oc_telemetry_item_t globalActivityCounter; + public ctl_oc_telemetry_item_t renderComputeActivityCounter; + public ctl_oc_telemetry_item_t mediaActivityCounter; + public bool gpuPowerLimited; + public bool gpuTemperatureLimited; + public bool gpuCurrentLimited; + public bool gpuVoltageLimited; + public bool gpuUtilizationLimited; + public ctl_oc_telemetry_item_t vramEnergyCounter; + public ctl_oc_telemetry_item_t vramVoltage; + public ctl_oc_telemetry_item_t vramCurrentClockFrequency; + public ctl_oc_telemetry_item_t vramCurrentEffectiveFrequency; + public ctl_oc_telemetry_item_t vramReadBandwidthCounter; + public ctl_oc_telemetry_item_t vramWriteBandwidthCounter; + public ctl_oc_telemetry_item_t vramCurrentTemperature; + public bool vramPowerLimited; + public bool vramTemperatureLimited; + public bool vramCurrentLimited; + public bool vramVoltageLimited; + public bool vramUtilizationLimited; + public ctl_oc_telemetry_item_t totalCardEnergyCounter; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = CTL_PSU_COUNT)] + public ctl_psu_info_t[] psu; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = CTL_FAN_COUNT)] + public ctl_oc_telemetry_item_t[] fanSpeed; + + public ctl_oc_telemetry_item_t gpuVrTemp; + public ctl_oc_telemetry_item_t vramVrTemp; + public ctl_oc_telemetry_item_t saVrTemp; + public ctl_oc_telemetry_item_t gpuEffectiveClock; + public ctl_oc_telemetry_item_t gpuOverVoltagePercent; + public ctl_oc_telemetry_item_t gpuPowerPercent; + public ctl_oc_telemetry_item_t gpuTemperaturePercent; + public ctl_oc_telemetry_item_t vramReadBandwidth; + public ctl_oc_telemetry_item_t vramWriteBandwidth; + } +}