diff --git a/nvml-wrapper-sys/nvml.h b/nvml-wrapper-sys/nvml.h index 9f96cb1..a3070d7 100644 --- a/nvml-wrapper-sys/nvml.h +++ b/nvml-wrapper-sys/nvml.h @@ -2358,27 +2358,33 @@ typedef enum nvmlDeviceGpuRecoveryAction_s { #define NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_13 248 //!< Count of symbol errors that are corrected - bin 13 #define NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_14 249 //!< Count of symbol errors that are corrected - bin 14 #define NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_15 250 //!< Count of symbol errors that are corrected - bin 15 +/* Clock Event Reason and Sync Power Balancing */ +#define NVML_FI_DEV_CLOCKS_EVENT_REASON_SW_THERM_SLOWDOWN 251 //!< Throttling to ensure ((GPU temp < GPU Max Operating Temp) && (Memory Temp < Memory Max Operating Temp)) in ns +#define NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_THERM_SLOWDOWN 252 //!< Throttling by HW thermal slowdown in ns +#define NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_POWER_BRAKE_SLOWDOWN 253 //!< Throttling by HW power brake slowdown in ns +#define NVML_FI_DEV_POWER_SYNC_BALANCING_FREQ 254 //!< Power Sync balancing frequency +#define NVML_FI_DEV_POWER_SYNC_BALANCING_AF 255 //!< Power Sync balancing AF /* Power Smoothing */ -#define NVML_FI_PWR_SMOOTHING_ENABLED 251 //!< Enablement (0/DISABLED or 1/ENABLED) -#define NVML_FI_PWR_SMOOTHING_PRIV_LVL 252 //!< Current privilege level -#define NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED 253 //!< Immediate ramp down enablement (0/DISABLED or 1/ENABLED) -#define NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL 254 //!< Applied TMP ceiling value in Watts -#define NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR 255 //!< Applied TMP floor value in Watts -#define NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING 256 //!< Max % TMP Floor value -#define NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING 257 //!< Min % TMP Floor value -#define NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING 258 //!< HW Circuitry % lifetime remaining -#define NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES 259 //!< Max number of preset profiles -#define NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR 260 //!< % TMP floor for a given profile -#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE 261 //!< Ramp up rate in mW/s for a given profile -#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE 262 //!< Ramp down rate in mW/s for a given profile -#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL 263 //!< Ramp down hysteresis value in ms for a given profile -#define NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE 264 //!< Active preset profile number -#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR 265 //!< % TMP floor for a given profile -#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE 266 //!< Ramp up rate in mW/s for a given profile -#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE 267 //!< Ramp down rate in mW/s for a given profile -#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL 268 //!< Ramp down hysteresis value in ms for a given profile - -#define NVML_FI_MAX 269 //!< One greater than the largest field ID defined above +#define NVML_FI_PWR_SMOOTHING_ENABLED 256 //!< Enablement (0/DISABLED or 1/ENABLED) +#define NVML_FI_PWR_SMOOTHING_PRIV_LVL 257 //!< Current privilege level +#define NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED 258 //!< Immediate ramp down enablement (0/DISABLED or 1/ENABLED) +#define NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL 259 //!< Applied TMP ceiling value in Watts +#define NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR 260 //!< Applied TMP floor value in Watts +#define NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING 261 //!< Max % TMP Floor value +#define NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING 262 //!< Min % TMP Floor value +#define NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING 263 //!< HW Circuitry % lifetime remaining +#define NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES 264 //!< Max number of preset profiles +#define NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR 265 //!< % TMP floor for a given profile +#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE 266 //!< Ramp up rate in mW/s for a given profile +#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE 267 //!< Ramp down rate in mW/s for a given profile +#define NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL 268 //!< Ramp down hysteresis value in ms for a given profile +#define NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE 269 //!< Active preset profile number +#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR 270 //!< % TMP floor for a given profile +#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE 271 //!< Ramp up rate in mW/s for a given profile +#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE 272 //!< Ramp down rate in mW/s for a given profile +#define NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL 273 //!< Ramp down hysteresis value in ms for a given profile + +#define NVML_FI_MAX 274 //!< One greater than the largest field ID defined above /** * NVML_FI_DEV_NVLINK_GET_POWER_THRESHOLD_UNITS diff --git a/nvml-wrapper-sys/src/bindings.rs b/nvml-wrapper-sys/src/bindings.rs index 7ce7ba8..3183568 100644 --- a/nvml-wrapper-sys/src/bindings.rs +++ b/nvml-wrapper-sys/src/bindings.rs @@ -343,25 +343,30 @@ pub mod field_id { pub const NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_13: u32 = 248; pub const NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_14: u32 = 249; pub const NVML_FI_DEV_NVLINK_COUNT_FEC_HISTORY_15: u32 = 250; - pub const NVML_FI_PWR_SMOOTHING_ENABLED: u32 = 251; - pub const NVML_FI_PWR_SMOOTHING_PRIV_LVL: u32 = 252; - pub const NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED: u32 = 253; - pub const NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL: u32 = 254; - pub const NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR: u32 = 255; - pub const NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING: u32 = 256; - pub const NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING: u32 = 257; - pub const NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING: u32 = 258; - pub const NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES: u32 = 259; - pub const NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR: u32 = 260; - pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE: u32 = 261; - pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE: u32 = 262; - pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL: u32 = 263; - pub const NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE: u32 = 264; - pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR: u32 = 265; - pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE: u32 = 266; - pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE: u32 = 267; - pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL: u32 = 268; - pub const NVML_FI_MAX: u32 = 269; + pub const NVML_FI_DEV_CLOCKS_EVENT_REASON_SW_THERM_SLOWDOWN: u32 = 251; + pub const NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_THERM_SLOWDOWN: u32 = 252; + pub const NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_POWER_BRAKE_SLOWDOWN: u32 = 253; + pub const NVML_FI_DEV_POWER_SYNC_BALANCING_FREQ: u32 = 254; + pub const NVML_FI_DEV_POWER_SYNC_BALANCING_AF: u32 = 255; + pub const NVML_FI_PWR_SMOOTHING_ENABLED: u32 = 256; + pub const NVML_FI_PWR_SMOOTHING_PRIV_LVL: u32 = 257; + pub const NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED: u32 = 258; + pub const NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL: u32 = 259; + pub const NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR: u32 = 260; + pub const NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING: u32 = 261; + pub const NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING: u32 = 262; + pub const NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING: u32 = 263; + pub const NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES: u32 = 264; + pub const NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR: u32 = 265; + pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE: u32 = 266; + pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE: u32 = 267; + pub const NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL: u32 = 268; + pub const NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE: u32 = 269; + pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR: u32 = 270; + pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE: u32 = 271; + pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE: u32 = 272; + pub const NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL: u32 = 273; + pub const NVML_FI_MAX: u32 = 274; } pub const NVML_NVLINK_LOW_POWER_THRESHOLD_UNIT_100US: u32 = 0; pub const NVML_NVLINK_LOW_POWER_THRESHOLD_UNIT_50US: u32 = 1; diff --git a/nvml-wrapper/src/device.rs b/nvml-wrapper/src/device.rs index d7447c1..8b6f882 100644 --- a/nvml-wrapper/src/device.rs +++ b/nvml-wrapper/src/device.rs @@ -3653,7 +3653,7 @@ impl<'nvml> Device<'nvml> { for id in id_slice.iter() { let mut raw: nvmlFieldValue_t = mem::zeroed(); - raw.fieldId = id.0; + raw.fieldId = crate::translate_field_id(self.nvml.field_id_scheme, id.0); field_values.push(raw); } @@ -7443,6 +7443,145 @@ mod test { }) } + /// Verify that the v12↔v13U1 field ID remapping works correctly at runtime. + /// + /// On a v13U1+ driver (>= 580.82), CLOCKS_EVENT_REASON fields must be + /// remapped from their canonical v12 IDs (251-253) to the driver's v13U1 + /// IDs (269-271). If the remapping is broken, the driver would interpret + /// these as PWR_SMOOTHING fields instead, returning either NotSupported + /// or silently wrong data. + /// + /// The CLOCKS_EVENT_REASON fields return throttle-reason nanosecond + /// counters and should work on most GPUs (including consumer cards like + /// the RTX 4090). PWR_SMOOTHING fields are Blackwell-only and should + /// return NotSupported on older architectures — so if we get a successful + /// result, we know the remapping sent the right ID to the driver. + #[test] + fn field_values_for_v12_v13u1_remapping() { + let nvml = nvml(); + + let driver = nvml + .sys_driver_version() + .unwrap_or_else(|_| "unknown".into()); + let scheme = nvml.field_id_scheme(); + println!("Driver: {driver}, scheme: {scheme:?}"); + + // (canonical v12 name, v12 ID, expected to work on most GPUs?) + let fields: &[(&str, u32)] = &[ + ( + "CLOCKS_EVENT_REASON_SW_THERM_SLOWDOWN", + NVML_FI_DEV_CLOCKS_EVENT_REASON_SW_THERM_SLOWDOWN, + ), + ( + "CLOCKS_EVENT_REASON_HW_THERM_SLOWDOWN", + NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_THERM_SLOWDOWN, + ), + ( + "CLOCKS_EVENT_REASON_HW_POWER_BRAKE_SLOWDOWN", + NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_POWER_BRAKE_SLOWDOWN, + ), + ( + "POWER_SYNC_BALANCING_FREQ", + NVML_FI_DEV_POWER_SYNC_BALANCING_FREQ, + ), + ( + "POWER_SYNC_BALANCING_AF", + NVML_FI_DEV_POWER_SYNC_BALANCING_AF, + ), + ("PWR_SMOOTHING_ENABLED", NVML_FI_PWR_SMOOTHING_ENABLED), + ("PWR_SMOOTHING_PRIV_LVL", NVML_FI_PWR_SMOOTHING_PRIV_LVL), + ( + "PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED", + NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED, + ), + ( + "PWR_SMOOTHING_APPLIED_TMP_CEIL", + NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL, + ), + ( + "PWR_SMOOTHING_APPLIED_TMP_FLOOR", + NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR, + ), + ( + "PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING", + NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING, + ), + ( + "PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING", + NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING, + ), + ( + "PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING", + NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING, + ), + ( + "PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES", + NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES, + ), + ( + "PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR", + NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR, + ), + ( + "PWR_SMOOTHING_PROFILE_RAMP_UP_RATE", + NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE, + ), + ( + "PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE", + NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE, + ), + ( + "PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL", + NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL, + ), + ( + "PWR_SMOOTHING_ACTIVE_PRESET_PROFILE", + NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE, + ), + ( + "PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR", + NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR, + ), + ( + "PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE", + NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE, + ), + ( + "PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE", + NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE, + ), + ( + "PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL", + NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL, + ), + ]; + + let field_ids: Vec = fields.iter().map(|(_, id)| FieldId(*id)).collect(); + + let device = device(&nvml); + let results = device + .field_values_for(&field_ids) + .expect("field_values_for call succeeded"); + + println!( + "{:<52} {:>6} {:>10} {}", + "NAME", "V12_ID", "DRIVER_ID", "RESULT" + ); + println!("{}", "-".repeat(90)); + + for ((name, v12_id), sample) in fields.iter().zip(results.iter()) { + let driver_id = crate::translate_field_id(scheme, *v12_id); + let result_str = match sample { + Ok(s) => match &s.value { + Ok(v) => format!("Ok({v:?})"), + Err(e) => format!("{e:?}"), + }, + Err(e) => format!("ERR: {e:?}"), + }; + println!("{name:<52} {v12_id:>6} {driver_id:>10} {result_str}"); + } + } + // Passing an empty slice should return an `InvalidArg` error #[should_panic(expected = "InvalidArg")] #[test] diff --git a/nvml-wrapper/src/lib.rs b/nvml-wrapper/src/lib.rs index cc56ff8..0774983 100644 --- a/nvml-wrapper/src/lib.rs +++ b/nvml-wrapper/src/lib.rs @@ -196,8 +196,24 @@ and may not accurately reflect the version of NVML that this library is written should ideally read the doc comments on an up-to-date NVML API header. Such a header can be downloaded as part of the [CUDA toolkit](https://developer.nvidia.com/cuda-downloads). */ +/// Describes which field ID numbering scheme the loaded NVML driver uses for +/// IDs 251-273. NVIDIA broke ABI compatibility for these IDs between the +/// original CUDA 13.0 release and CUDA 13.0 Update 1 (driver >= 580.82). +/// +/// See +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FieldIdScheme { + /// Used by drivers before 580.82 (CUDA 12.x and original CUDA 13.0). + /// IDs 251-255 are CLOCKS_EVENT_REASON/POWER_SYNC, 256-273 are PWR_SMOOTHING. + V12, + /// Used by drivers >= 580.82 (CUDA 13.0 Update 1+). + /// IDs 251-268 are PWR_SMOOTHING, 269-273 are CLOCKS_EVENT_REASON/POWER_SYNC. + V13Update1, +} + pub struct Nvml { lib: ManuallyDrop, + field_id_scheme: FieldIdScheme, } assert_impl_all!(Nvml: Send, Sync); @@ -208,6 +224,36 @@ impl std::fmt::Debug for Nvml { } } +/// Parse a driver version string (e.g. "580.82.07") and determine the field ID scheme. +/// Returns `V13Update1` for driver >= 580.82, `V12` otherwise. +fn detect_field_id_scheme(driver_version: &str) -> FieldIdScheme { + let mut parts = driver_version.split('.'); + let major: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0); + let minor: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0); + + if major > 580 || (major == 580 && minor >= 82) { + FieldIdScheme::V13Update1 + } else { + FieldIdScheme::V12 + } +} + +/// Translate a field ID from the canonical v12 numbering to the v13U1 numbering. +/// Only affects IDs in the 251-273 range. IDs outside this range pass through unchanged. +pub(crate) fn translate_field_id(scheme: FieldIdScheme, id: u32) -> u32 { + if scheme == FieldIdScheme::V12 { + return id; + } + // V13Update1 remapping: + // v12 251-255 (CLOCKS_EVENT_REASON/POWER_SYNC) → v13U1 269-273 + // v12 256-273 (PWR_SMOOTHING) → v13U1 251-268 + match id { + 251..=255 => id + 18, + 256..=273 => id - 5, + other => other, + } +} + impl Nvml { /** Handles NVML initialization and must be called before doing anything else. @@ -246,7 +292,16 @@ impl Nvml { ManuallyDrop::new(lib) }; - Ok(Self { lib }) + let mut nvml = Self { + lib, + field_id_scheme: FieldIdScheme::V12, + }; + nvml.field_id_scheme = nvml + .sys_driver_version() + .map(|v| detect_field_id_scheme(&v)) + .unwrap_or(FieldIdScheme::V12); + + Ok(nvml) } /** @@ -293,7 +348,16 @@ impl Nvml { ManuallyDrop::new(lib) }; - Ok(Self { lib }) + let mut nvml = Self { + lib, + field_id_scheme: FieldIdScheme::V12, + }; + nvml.field_id_scheme = nvml + .sys_driver_version() + .map(|v| detect_field_id_scheme(&v)) + .unwrap_or(FieldIdScheme::V12); + + Ok(nvml) } /// Create an `NvmlBuilder` for further flexibility in how NVML is initialized. @@ -306,6 +370,11 @@ impl Nvml { &self.lib } + /// Returns the detected field ID numbering scheme for the loaded driver. + pub fn field_id_scheme(&self) -> FieldIdScheme { + self.field_id_scheme + } + /** Use this to shutdown NVML and release allocated resources if you care about handling potential errors (*the `Drop` implementation ignores errors!*). @@ -1326,4 +1395,114 @@ mod test { let nvml = nvml(); test(3, || nvml.set_vgpu_version(VgpuVersion { min: 0, max: 0 })) } + + #[test] + fn detect_field_id_scheme_v12_drivers() { + assert_eq!(detect_field_id_scheme("575.51.03"), FieldIdScheme::V12); + assert_eq!(detect_field_id_scheme("570.86.16"), FieldIdScheme::V12); + assert_eq!(detect_field_id_scheme("580.65.06"), FieldIdScheme::V12); + assert_eq!(detect_field_id_scheme("580.0.0"), FieldIdScheme::V12); + } + + #[test] + fn detect_field_id_scheme_v13u1_drivers() { + assert_eq!( + detect_field_id_scheme("580.82.07"), + FieldIdScheme::V13Update1 + ); + assert_eq!( + detect_field_id_scheme("580.95.05"), + FieldIdScheme::V13Update1 + ); + assert_eq!( + detect_field_id_scheme("580.126.09"), + FieldIdScheme::V13Update1 + ); + assert_eq!(detect_field_id_scheme("581.0.0"), FieldIdScheme::V13Update1); + assert_eq!(detect_field_id_scheme("600.0.0"), FieldIdScheme::V13Update1); + } + + #[test] + fn detect_field_id_scheme_malformed() { + assert_eq!(detect_field_id_scheme(""), FieldIdScheme::V12); + assert_eq!(detect_field_id_scheme("garbage"), FieldIdScheme::V12); + } + + #[test] + fn translate_field_id_v12_is_noop() { + for id in 0..300 { + assert_eq!(translate_field_id(FieldIdScheme::V12, id), id); + } + } + + #[test] + fn translate_field_id_v13u1_remaps_affected_range() { + use crate::ffi::bindings::field_id::*; + + // Exhaustive check: CLOCKS_EVENT_REASON/POWER_SYNC (v12: 251-255) → v13U1: 269-273 + let v12_clocks_event = [ + (NVML_FI_DEV_CLOCKS_EVENT_REASON_SW_THERM_SLOWDOWN, 269), + (NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_THERM_SLOWDOWN, 270), + (NVML_FI_DEV_CLOCKS_EVENT_REASON_HW_POWER_BRAKE_SLOWDOWN, 271), + (NVML_FI_DEV_POWER_SYNC_BALANCING_FREQ, 272), + (NVML_FI_DEV_POWER_SYNC_BALANCING_AF, 273), + ]; + for (v12_id, expected_v13u1) in v12_clocks_event { + assert_eq!( + translate_field_id(FieldIdScheme::V13Update1, v12_id), + expected_v13u1, + "v12 ID {v12_id} should map to v13U1 ID {expected_v13u1}" + ); + } + + // Exhaustive check: PWR_SMOOTHING (v12: 256-273) → v13U1: 251-268 + let v12_pwr_smoothing = [ + (NVML_FI_PWR_SMOOTHING_ENABLED, 251), + (NVML_FI_PWR_SMOOTHING_PRIV_LVL, 252), + (NVML_FI_PWR_SMOOTHING_IMM_RAMP_DOWN_ENABLED, 253), + (NVML_FI_PWR_SMOOTHING_APPLIED_TMP_CEIL, 254), + (NVML_FI_PWR_SMOOTHING_APPLIED_TMP_FLOOR, 255), + (NVML_FI_PWR_SMOOTHING_MAX_PERCENT_TMP_FLOOR_SETTING, 256), + (NVML_FI_PWR_SMOOTHING_MIN_PERCENT_TMP_FLOOR_SETTING, 257), + ( + NVML_FI_PWR_SMOOTHING_HW_CIRCUITRY_PERCENT_LIFETIME_REMAINING, + 258, + ), + (NVML_FI_PWR_SMOOTHING_MAX_NUM_PRESET_PROFILES, 259), + (NVML_FI_PWR_SMOOTHING_PROFILE_PERCENT_TMP_FLOOR, 260), + (NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_UP_RATE, 261), + (NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_RATE, 262), + (NVML_FI_PWR_SMOOTHING_PROFILE_RAMP_DOWN_HYST_VAL, 263), + (NVML_FI_PWR_SMOOTHING_ACTIVE_PRESET_PROFILE, 264), + (NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_PERCENT_TMP_FLOOR, 265), + (NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_UP_RATE, 266), + (NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_RATE, 267), + (NVML_FI_PWR_SMOOTHING_ADMIN_OVERRIDE_RAMP_DOWN_HYST_VAL, 268), + ]; + for (v12_id, expected_v13u1) in v12_pwr_smoothing { + assert_eq!( + translate_field_id(FieldIdScheme::V13Update1, v12_id), + expected_v13u1, + "v12 ID {v12_id} should map to v13U1 ID {expected_v13u1}" + ); + } + + // Verify the mapping is bijective (no collisions) over the full 251-273 range + let mut mapped: Vec = (251..=273) + .map(|id| translate_field_id(FieldIdScheme::V13Update1, id)) + .collect(); + mapped.sort(); + let expected: Vec = (251..=273).collect(); + assert_eq!( + mapped, expected, + "remapping must be a bijection over 251-273" + ); + } + + #[test] + fn translate_field_id_v13u1_passthrough_outside_range() { + assert_eq!(translate_field_id(FieldIdScheme::V13Update1, 0), 0); + assert_eq!(translate_field_id(FieldIdScheme::V13Update1, 250), 250); + assert_eq!(translate_field_id(FieldIdScheme::V13Update1, 274), 274); + } }