diff --git a/lact-daemon/src/server/gpu_controller/nvidia.rs b/lact-daemon/src/server/gpu_controller/nvidia.rs index 965aa8f2..09c2a45d 100644 --- a/lact-daemon/src/server/gpu_controller/nvidia.rs +++ b/lact-daemon/src/server/gpu_controller/nvidia.rs @@ -6,7 +6,9 @@ use crate::{ bindings::nvidia::NvPhysicalGpuHandle, server::gpu_controller::{ common::{fan_control::FanCurveExt, resolve_process_name}, - nvidia::nvapi::{CLOCK_CLIENT_CLK_VF_POINT_TYPE_PROG, ClockClientClkVfPointInfoV1}, + nvidia::nvapi::{ + CLOCK_CLIENT_CLK_VF_POINT_TYPE_PROG, ClockClientClkVfPointInfoV1, NvGpuClockDomainId, + }, }, }; use amdgpu_sysfs::{ @@ -882,6 +884,14 @@ impl GpuController for NvidiaGpuController { ); } + let mut extra_clocks = [ + ("SM", device.clock_info(Clock::SM)), + ("Video", device.clock_info(Clock::Video)), + ] + .into_iter() + .filter_map(|(name, value)| Some((name.to_owned(), u64::from(value.ok()?)))) + .collect::>(); + let mut voltage = None; if let Some((nvapi, handle)) = self.nvapi.as_ref() { @@ -952,6 +962,15 @@ impl GpuController for NvidiaGpuController { if let Ok(value) = nvapi.get_voltage(*handle) { voltage = Some(u64::from(value) / 1000); } + + if let Ok(clocks) = nvapi.get_all_clocks(*handle) { + for domain in NvGpuClockDomainId::EXTRA { + if let Some(info) = clocks.get_domain(domain) { + extra_clocks + .insert(domain.to_string(), u64::from(info.frequency) / 1000); + } + } + } } } @@ -1048,13 +1067,7 @@ impl GpuController for NvidiaGpuController { gpu_clockspeed: device.clock_info(Clock::Graphics).map(Into::into).ok(), vram_clockspeed: device.clock_info(Clock::Memory).map(Into::into).ok(), target_gpu_clockspeed: None, - sensors: [ - ("SM", device.clock_info(Clock::SM)), - ("Video", device.clock_info(Clock::Video)), - ] - .into_iter() - .filter_map(|(label, result)| Some((label.to_owned(), result.ok()?.into()))) - .collect(), + sensors: extra_clocks, }, throttle_info: device.current_throttle_reasons().ok().map(|reasons| { reasons diff --git a/lact-daemon/src/server/gpu_controller/nvidia/nvapi.rs b/lact-daemon/src/server/gpu_controller/nvidia/nvapi.rs index 658d1835..9faa470a 100644 --- a/lact-daemon/src/server/gpu_controller/nvidia/nvapi.rs +++ b/lact-daemon/src/server/gpu_controller/nvidia/nvapi.rs @@ -36,6 +36,7 @@ const QUERY_NVAPI_GPU_CLOCK_CLIENT_CLK_VF_POINTS_GET_INFO: u32 = 0x507b4b59; const QUERY_NVAPI_GPU_CLOCK_CLIENT_CLK_VF_POINTS_SET_CONTROL: u32 = 0x733e009; const QUERY_NVAPI_GPU_CLOCK_CLIENT_CLK_VF_POINTS_GET_CONTROL: u32 = 0x23f1b133; const QUERY_NVAPI_GPU_REGISTER_OP: u32 = 0x2eb3c140; +const QUERY_NVAPI_GPU_GET_ALL_CLOCKS: u32 = 0x1BD69F49; const REG_OFFSET_BLACKWELL_HOTSPOT_AGGREGATED: u32 = 0xad0aa0; @@ -114,7 +115,6 @@ impl NvApi { pub unsafe fn get_voltage(&self, handle: NvPhysicalGpuHandle) -> anyhow::Result { let mut data = NvApiVoltage::default(); - self.physical_gpu_query(handle, &mut data, QUERY_NVAPI_VOLTAGE)?; Ok(data.rails[0].current_voltage_uv) @@ -122,12 +122,21 @@ impl NvApi { pub unsafe fn get_voltage_boost(&self, handle: NvPhysicalGpuHandle) -> anyhow::Result { let mut data = NvApiVoltageBoost::default(); - self.physical_gpu_query(handle, &mut data, QUERY_NVAPI_VOLTAGE_BOOST_GET)?; Ok(data.percent) } + pub unsafe fn get_all_clocks( + &self, + handle: NvPhysicalGpuHandle, + ) -> anyhow::Result { + let mut data = NvGpuClockInfoV2::default(); + self.physical_gpu_query(handle, &mut data, QUERY_NVAPI_GPU_GET_ALL_CLOCKS)?; + + Ok(data) + } + pub unsafe fn set_voltage_boost( &self, handle: NvPhysicalGpuHandle, @@ -137,7 +146,6 @@ impl NvApi { percent, ..Default::default() }; - self.physical_gpu_query(handle, &mut data, QUERY_NVAPI_VOLTAGE_BOOST_SET)?; Ok(()) @@ -711,6 +719,133 @@ impl Default for NvGpuRegisterOpDataV1 { } } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NvGpuClockInfoV2 { + version: NvU32, + domain: [NvGpuClockInfoDomain; 32usize], + extended_domain: [NvGpuClockInfoV2ExtendedDomain; 32usize], +} + +impl NvGpuClockInfoV2 { + pub fn get_domain(&self, domain: NvGpuClockDomainId) -> Option { + self.domain + .get(domain as usize) + .filter(|domain| domain.bitfield & 1 != 0 && domain.frequency != 0) + .copied() + } +} + +impl Default for NvGpuClockInfoV2 { + fn default() -> Self { + Self { + version: make_version::(2), + domain: Default::default(), + extended_domain: Default::default(), + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +pub struct NvGpuClockInfoDomain { + pub frequency: NvU32, + bitfield: NvU32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +pub struct NvGpuClockInfoV2ExtendedDomain { + effective_frequency: NvU32, + ratio_domain: NvGpuClockDomainId, + ratio: NvU32, + reserved: [NvU32; 4usize], +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum NvGpuClockDomainId { + // Gpc = 0, + Xbar = 1, + Sys = 2, + Hub = 3, + // M = 4, + Host = 5, + Disp = 6, + Pclk0 = 8, + Pclk1 = 9, + Xclk = 11, + Leg = 19, + Pwr = 20, + Msd = 21, + Utils = 22, + _2D = 26, + _3D = 27, + Host1x = 28, + Disp0 = 29, + Disp1 = 30, + // Pciegen = 31, + #[default] + Undefined = 32, +} + +impl NvGpuClockDomainId { + /// Excludes the domains already reported through NVML + pub const EXTRA: [Self; 17] = [ + // Self::Gpc, + Self::Xbar, + Self::Sys, + Self::Hub, + // Self::M, + Self::Host, + Self::Disp, + Self::Pclk0, + Self::Pclk1, + Self::Xclk, + Self::Leg, + Self::Pwr, + Self::Msd, + Self::Utils, + Self::_2D, + Self::_3D, + Self::Host1x, + Self::Disp0, + Self::Disp1, + ]; + + pub fn into_str(self) -> &'static str { + match self { + // Self::Gpc => "GPC", + Self::Xbar => "XBAR", + Self::Sys => "SYS", + Self::Hub => "HUB", + // Self::M => "Memory", + Self::Host => "Host", + Self::Disp => "Display", + Self::Pclk0 => "Pixel Clock 0", + Self::Pclk1 => "Pixel Clock 1", + Self::Xclk => "External Clock", + Self::Leg => "Legacy", + Self::Pwr => "Power", + Self::Msd => "MSD", + Self::Utils => "Utils", + Self::_2D => "2D", + Self::_3D => "3D", + Self::Host1x => "Host1x", + Self::Disp0 => "Display 0", + Self::Disp1 => "Display 1", + // Self::Pciegen => "PCIe", + Self::Undefined => "Undefined", + } + } +} + +impl std::fmt::Display for NvGpuClockDomainId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.into_str().fmt(f) + } +} + #[allow(clippy::cast_possible_truncation)] const fn make_version(version: usize) -> u32 { (mem::size_of::() | (version << 16)) as u32