nvapi_hi/
gpu.rs

1use std::collections::BTreeMap;
2use {allowable_result, allowable_result_fallback};
3
4use nvapi::{self,
5    ClockTable, VfpCurve, VfpEntry, Sensor, Cooler, ThermalInfo, PowerInfoEntry,
6    ClockFrequencyType, ClockEntry,
7    BaseVoltage, PStates, ClockRange, ThermalLimit,
8};
9pub use nvapi::{
10    PhysicalGpu,
11    Vendor, SystemType, RamType, RamMaker, Foundry,
12    ClockFrequencies, ClockDomain, VoltageDomain, UtilizationDomain, Utilizations, ClockLockMode, ClockLockEntry,
13    CoolerType, CoolerController, CoolerControl, CoolerPolicy, CoolerTarget, CoolerLevel,
14    VoltageStatus, VoltageTable,
15    PerfInfo, PerfStatus,
16    ThermalController, ThermalTarget,
17    MemoryInfo, PciIdentifiers, DriverModel,
18    Percentage, Celsius,
19    Range,
20    Kibibytes, Microvolts, MicrovoltsDelta, Kilohertz, KilohertzDelta,
21    PState,
22};
23
24pub struct Gpu {
25    gpu: PhysicalGpu,
26}
27
28#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
29#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
30pub struct GpuInfo {
31    pub name: String,
32    pub codename: String,
33    pub bios_version: String,
34    pub driver_model: DriverModel,
35    pub vendor: Vendor,
36    pub pci: PciIdentifiers,
37    pub memory: MemoryInfo,
38    pub system_type: SystemType,
39    pub ram_type: RamType,
40    pub ram_maker: RamMaker,
41    pub ram_bus_width: u32,
42    pub ram_bank_count: u32,
43    pub ram_partition_count: u32,
44    pub foundry: Foundry,
45    pub core_count: u32,
46    pub shader_pipe_count: u32,
47    pub shader_sub_pipe_count: u32,
48    pub base_clocks: ClockFrequencies,
49    pub boost_clocks: ClockFrequencies,
50    pub sensors: Vec<SensorDesc>,
51    pub coolers: Vec<CoolerDesc>,
52    pub perf: PerfInfo,
53    pub sensor_limits: Vec<SensorLimit>,
54    pub power_limits: Vec<PowerLimit>,
55    pub pstate_limits: BTreeMap<PState, BTreeMap<ClockDomain, PStateLimit>>,
56    // TODO: pstate base_voltages
57    pub overvolt_limits: Vec<OvervoltLimit>,
58    pub vfp_limits: BTreeMap<ClockDomain, VfpRange>,
59    pub vfp_locks: Vec<usize>,
60}
61
62#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
63#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
64pub struct VfpRange {
65    pub range: Range<KilohertzDelta>,
66    pub temperature: Celsius,
67}
68
69impl From<ClockRange> for VfpRange {
70    fn from(c: ClockRange) -> Self {
71        VfpRange {
72            range: Range::range_from(c.range),
73            temperature: c.temp_max.into(),
74        }
75    }
76}
77
78#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
79#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
80pub struct GpuStatus {
81    pub pstate: PState,
82    pub clocks: ClockFrequencies,
83    pub memory: MemoryInfo,
84    pub voltage: Option<Microvolts>,
85    pub voltage_domains: Option<VoltageStatus>,
86    pub voltage_step: Option<VoltageStatus>,
87    pub voltage_table: Option<VoltageTable>,
88    pub tachometer: Option<u32>,
89    pub utilization: Utilizations,
90    pub power: Vec<Percentage>,
91    pub sensors: Vec<(SensorDesc, Celsius)>,
92    pub coolers: Vec<(CoolerDesc, CoolerStatus)>,
93    pub perf: PerfStatus,
94    pub vfp: Option<VfpTable>,
95    pub vfp_locks: BTreeMap<usize, Microvolts>,
96}
97
98#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
99#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
100pub struct GpuSettings {
101    pub voltage_boost: Option<Percentage>,
102    pub sensor_limits: Vec<Celsius>,
103    pub power_limits: Vec<Percentage>,
104    pub coolers: Vec<(CoolerDesc, CoolerStatus)>,
105    pub vfp: Option<VfpDeltas>,
106    pub pstate_deltas: BTreeMap<PState, BTreeMap<ClockDomain, KilohertzDelta>>,
107    pub overvolt: Vec<MicrovoltsDelta>,
108    pub vfp_locks: BTreeMap<usize, ClockLockEntry>,
109}
110
111impl Gpu {
112    pub fn new(gpu: PhysicalGpu) -> Self {
113        Gpu {
114            gpu: gpu,
115        }
116    }
117
118    pub fn into_inner(self) -> PhysicalGpu {
119        self.gpu
120    }
121
122    pub fn inner(&self) -> &PhysicalGpu {
123        &self.gpu
124    }
125
126    pub fn enumerate() -> nvapi::Result<Vec<Self>> {
127        PhysicalGpu::enumerate().map(|v| v.into_iter().map(Gpu::new).collect())
128    }
129
130    pub fn info(&self) -> nvapi::Result<GpuInfo> {
131        let pstates = allowable_result(self.gpu.pstates())?;
132        let (pstates, ov) = match pstates {
133            Ok(PStates { editable: _editable, pstates, overvolt }) => (pstates, overvolt),
134            Err(..) => (Default::default(), Default::default()),
135        };
136        let pci = self.gpu.pci_identifiers()?;
137
138        Ok(GpuInfo {
139            name: self.gpu.full_name()?,
140            codename: self.gpu.short_name()?,
141            bios_version: self.gpu.vbios_version_string()?,
142            driver_model: self.gpu.driver_model()?,
143            vendor: allowable_result_fallback(pci.vendor().map_err(From::from), Vendor::Unknown)?,
144            pci: pci,
145            memory: self.gpu.memory_info()?,
146            system_type: allowable_result_fallback(self.gpu.system_type(), SystemType::Unknown)?,
147            ram_type: allowable_result_fallback(self.gpu.ram_type(), RamType::Unknown)?,
148            ram_maker: allowable_result_fallback(self.gpu.ram_maker(), RamMaker::Unknown)?,
149            ram_bus_width: allowable_result_fallback(self.gpu.ram_bus_width(), 0)?,
150            ram_bank_count: allowable_result_fallback(self.gpu.ram_bank_count(), 0)?,
151            ram_partition_count: allowable_result_fallback(self.gpu.ram_partition_count(), 0)?,
152            foundry: allowable_result_fallback(self.gpu.foundry(), Foundry::Unknown)?,
153            core_count: self.gpu.core_count()?,
154            shader_pipe_count: self.gpu.shader_pipe_count()?,
155            shader_sub_pipe_count: self.gpu.shader_sub_pipe_count()?,
156            base_clocks: self.gpu.clock_frequencies(ClockFrequencyType::Base)?,
157            boost_clocks: self.gpu.clock_frequencies(ClockFrequencyType::Boost)?,
158            sensors: match allowable_result(self.gpu.thermal_settings(None))? {
159                Ok(s) => s.into_iter().map(From::from).collect(),
160                Err(..) => Default::default(),
161            },
162            coolers: match allowable_result(self.gpu.cooler_settings(None))? {
163                Ok(c) => c.into_iter().map(From::from).collect(),
164                Err(..) => Default::default(),
165            },
166            perf: self.gpu.perf_info()?,
167            sensor_limits: match allowable_result(self.gpu.thermal_limit_info())? {
168                Ok((_, l)) => l.into_iter().map(From::from).collect(),
169                Err(..) => Default::default(),
170            },
171            power_limits: match allowable_result(self.gpu.power_limit_info())? {
172                Ok(p) => p.entries.into_iter().map(From::from).collect(),
173                Err(..) => Default::default(),
174            },
175            pstate_limits: pstates.into_iter().map(|p| (p.id, p.clocks.into_iter().map(|p| (p.domain(), p.into())).collect())).collect(),
176            overvolt_limits: ov.into_iter().map(From::from).collect(),
177            vfp_limits: match allowable_result(self.gpu.vfp_ranges())? {
178                Ok(l) => l.into_iter().map(|v| (v.domain, v.into())).collect(),
179                Err(..) => Default::default(),
180            },
181            vfp_locks: match allowable_result(self.gpu.vfp_locks())? {
182                Ok(v) => v.into_iter().map(|(id, _)| id).collect(),
183                Err(..) => Default::default(),
184            },
185        })
186    }
187
188    pub fn status(&self) -> nvapi::Result<GpuStatus> {
189        let mask = allowable_result(self.gpu.vfp_mask())?;
190
191        Ok(GpuStatus {
192            pstate: self.gpu.current_pstate()?,
193            clocks: self.gpu.clock_frequencies(ClockFrequencyType::Current)?,
194            memory: self.gpu.memory_info()?,
195            voltage: allowable_result(self.gpu.core_voltage())?.ok(),
196            voltage_domains: allowable_result(self.gpu.voltage_domains_status())?.ok(),
197            voltage_step: allowable_result(self.gpu.voltage_step())?.ok(),
198            voltage_table: allowable_result(self.gpu.voltage_table())?.ok(),
199            tachometer: allowable_result(self.gpu.tachometer())?.ok(),
200            utilization: self.gpu.dynamic_pstates_info()?,
201            power: self.gpu.power_usage()?.into_iter().map(From::from).collect(),
202            sensors: match allowable_result(self.gpu.thermal_settings(None))? {
203                Ok(s) => s.into_iter().map(|s| (From::from(s), s.current_temperature)).collect(),
204                Err(..) => Default::default(),
205            },
206            coolers: match allowable_result(self.gpu.cooler_settings(None))? {
207                Ok(c) => c.into_iter().map(|c| (From::from(c), From::from(c))).collect(),
208                Err(..) => Default::default(),
209            },
210            perf: self.gpu.perf_status()?,
211            vfp: match mask {
212                Ok(mask) => allowable_result(self.gpu.vfp_curve(mask.mask))?.map(From::from).ok(),
213                Err(..) => None,
214            },
215            vfp_locks: match allowable_result(self.gpu.vfp_locks())? {
216                Ok(l) => l.into_iter().filter_map(|(id, e)| if e.mode == ClockLockMode::Manual {
217                    Some((id, e.voltage))
218                } else {
219                    None
220                }).collect(),
221                Err(..) => Default::default(),
222            },
223        })
224    }
225
226    pub fn settings(&self) -> nvapi::Result<GpuSettings> {
227        let mask = allowable_result(self.gpu.vfp_mask())?;
228        let pstates = allowable_result(self.gpu.pstates())?;
229        let (pstates, ov) = match pstates {
230            Ok(PStates { editable: _editable, pstates, overvolt }) => (pstates, overvolt),
231            Err(..) => (Default::default(), Default::default()),
232        };
233
234        Ok(GpuSettings {
235            voltage_boost: allowable_result(self.gpu.core_voltage_boost())?.ok(),
236            sensor_limits: match allowable_result(self.gpu.thermal_limit())? {
237                Ok(l) => l.into_iter().map(|l| l.value.into()).collect(),
238                Err(..) => Default::default(),
239            },
240            power_limits: match allowable_result(self.gpu.power_limit())? {
241                Ok(l) => l.into_iter().map(|l| l.into()).collect(),
242                Err(..) => Default::default(),
243            },
244            coolers: match allowable_result(self.gpu.cooler_settings(None))? {
245                Ok(c) => c.into_iter().map(|c| (From::from(c), From::from(c))).collect(),
246                Err(..) => Default::default(),
247            },
248            vfp: match mask {
249                Ok(mask) => allowable_result(self.gpu.vfp_table(mask.mask))?.map(From::from).ok(),
250                Err(..) => None,
251            },
252            vfp_locks: match allowable_result(self.gpu.vfp_locks())? {
253                Ok(l) => l,
254                Err(..) => Default::default(),
255            },
256            pstate_deltas: pstates.into_iter().filter(|p| p.editable)
257                .map(|p| (p.id, p.clocks.into_iter().filter(|p| p.editable())
258                    .map(|p| (p.domain(), p.frequency_delta().value)).collect())
259                ).collect(),
260            overvolt: ov.into_iter().filter(|v| v.editable).map(|v| v.voltage_delta.value).collect(),
261        })
262    }
263
264    pub fn set_voltage_boost(&self, boost: Percentage) -> nvapi::Result<()> {
265        self.gpu.set_core_voltage_boost(boost)
266    }
267
268    pub fn set_power_limits<I: Iterator<Item=Percentage>>(&self, limits: I) -> nvapi::Result<()> {
269        // TODO: match against power_limit_info, use range.min/max from there if it matches (can get fraction of a percent!)
270        self.gpu.set_power_limit(limits.map(From::from))
271    }
272
273    pub fn set_sensor_limits<I: Iterator<Item=Celsius>>(&self, limits: I) -> nvapi::Result<()> {
274        self.gpu.thermal_limit_info().and_then(|(_, info)| self.gpu.set_thermal_limit(
275            limits.zip(info.into_iter()).map(|(limit, info)| ThermalLimit {
276                controller: info.controller,
277                flags: info.default_flags,
278                value: limit.into(),
279            })
280        ))
281    }
282
283    pub fn set_cooler_levels<I: Iterator<Item=CoolerLevel>>(&self, levels: I) -> nvapi::Result<()> {
284        self.gpu.set_cooler_levels(None, levels)
285    }
286
287    pub fn reset_cooler_levels(&self) -> nvapi::Result<()> {
288        self.gpu.restore_cooler_settings(&[])
289    }
290
291    pub fn set_vfp<I: Iterator<Item=(usize, KilohertzDelta)>, M: Iterator<Item=(usize, KilohertzDelta)>>(&self, clock_deltas: I, mem_deltas: M) -> nvapi::Result<()> {
292        self.gpu.set_vfp_table([0, 0, 0, 0], clock_deltas.map(|(i, d)| (i, d.into())), mem_deltas.map(|(i, d)| (i, d.into())))
293    }
294
295    pub fn set_vfp_lock(&self, voltage: Microvolts) -> nvapi::Result<()> {
296        self.gpu.set_vfp_locks(self.gpu.vfp_locks()?
297            .into_iter().max_by_key(|&(id, _)| id).into_iter()
298            .map(|(id, entry)| (id, Some(voltage)))
299        )
300    }
301
302    pub fn reset_vfp_lock(&self) -> nvapi::Result<()> {
303        self.gpu.set_vfp_locks(self.gpu.vfp_locks()?.into_iter().map(|(id, _)| (id, None)))
304    }
305
306    pub fn reset_vfp(&self) -> nvapi::Result<()> {
307        use std::iter;
308
309        let mask = self.gpu.vfp_mask()?;
310        self.gpu.set_vfp_table(mask.mask, iter::empty(), iter::empty())
311    }
312}
313
314#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
315#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
316pub struct OvervoltLimit {
317    pub domain: VoltageDomain,
318    pub voltage: Microvolts,
319    pub range: Option<Range<MicrovoltsDelta>>,
320}
321
322impl From<BaseVoltage> for OvervoltLimit {
323    fn from(v: BaseVoltage) -> Self {
324        OvervoltLimit {
325            domain: v.voltage_domain,
326            voltage: v.voltage,
327            range: if v.editable {
328                Some(v.voltage_delta.range)
329            } else {
330                None
331            },
332        }
333    }
334}
335
336#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
337#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
338pub struct PStateLimit {
339    pub frequency_delta: Option<Range<KilohertzDelta>>,
340    pub frequency: Range<Kilohertz>,
341    pub voltage: Range<Microvolts>,
342    pub voltage_domain: VoltageDomain,
343}
344
345impl From<ClockEntry> for PStateLimit {
346    fn from(s: ClockEntry) -> Self {
347        match s {
348            ClockEntry::Range { domain: _, editable, frequency_delta, frequency_range, voltage_domain, voltage_range } => PStateLimit {
349                frequency_delta: if editable { Some(frequency_delta.range) } else { None },
350                frequency: frequency_range,
351                voltage: voltage_range,
352                voltage_domain: voltage_domain,
353            },
354            ClockEntry::Single { domain: _, editable, frequency_delta, frequency } => PStateLimit {
355                frequency_delta: if editable { Some(frequency_delta.range) } else { None },
356                frequency: Range::from_scalar(frequency),
357                voltage: Default::default(),
358                voltage_domain: VoltageDomain::Undefined,
359            },
360        }
361    }
362}
363
364#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
365#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
366pub struct PowerLimit {
367    pub range: Range<Percentage>,
368    pub default: Percentage,
369}
370
371impl From<PowerInfoEntry> for PowerLimit {
372    fn from(info: PowerInfoEntry) -> Self {
373        PowerLimit {
374            range: Range::range_from(info.range),
375            default: info.default_limit.into(),
376        }
377    }
378}
379
380#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
381#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
382pub struct SensorLimit {
383    pub range: Range<Celsius>,
384    pub default: Celsius,
385    pub flags: u32,
386}
387
388impl From<ThermalInfo> for SensorLimit {
389    fn from(info: ThermalInfo) -> Self {
390        SensorLimit {
391            range: Range::range_from(info.temperature_range),
392            default: info.default_temperature.into(),
393            flags: info.default_flags,
394        }
395    }
396}
397
398#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
399#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
400pub struct SensorDesc {
401    pub controller: ThermalController,
402    pub target: ThermalTarget,
403    pub range: Range<Celsius>,
404}
405
406impl From<Sensor> for SensorDesc {
407    fn from(sensor: Sensor) -> Self {
408        SensorDesc {
409            controller: sensor.controller,
410            target: sensor.target,
411            range: sensor.default_temperature_range,
412        }
413    }
414}
415
416#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
417#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
418pub struct CoolerDesc {
419    pub kind: CoolerType,
420    pub controller: CoolerController,
421    pub range: Range<Percentage>,
422    pub default_policy: CoolerPolicy,
423    pub target: CoolerTarget,
424    pub control: CoolerControl,
425}
426
427impl From<Cooler> for CoolerDesc {
428    fn from(cooler: Cooler) -> Self {
429        CoolerDesc {
430            kind: cooler.kind,
431            controller: cooler.controller,
432            range: cooler.default_level_range,
433            default_policy: cooler.default_policy,
434            target: cooler.target,
435            control: cooler.control,
436        }
437    }
438}
439
440#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
441#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
442pub struct CoolerStatus {
443    pub range: Range<Percentage>,
444    pub level: Percentage,
445    pub policy: CoolerPolicy,
446    pub active: bool,
447}
448
449impl From<Cooler> for CoolerStatus {
450    fn from(cooler: Cooler) -> Self {
451        CoolerStatus {
452            range: cooler.current_level_range,
453            level: cooler.current_level,
454            policy: cooler.current_policy,
455            active: cooler.active,
456        }
457    }
458}
459
460#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
461#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
462pub struct VfpPoint {
463    pub frequency: Kilohertz,
464    pub voltage: Microvolts,
465}
466
467impl<T> From<VfpEntry<T>> for VfpPoint where Kilohertz: From<T> {
468    fn from(v: VfpEntry<T>) -> Self {
469        VfpPoint {
470            frequency: v.frequency.into(),
471            voltage: v.voltage,
472        }
473    }
474}
475
476#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
477#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
478pub struct VfpTable {
479    pub graphics: BTreeMap<usize, VfpPoint>,
480    pub memory: BTreeMap<usize, VfpPoint>,
481}
482
483impl From<VfpCurve> for VfpTable {
484    fn from(v: VfpCurve) -> Self {
485        VfpTable {
486            graphics: v.graphics.into_iter().map(|(i, e)| (i, e.into())).collect(),
487            memory: v.memory.into_iter().map(|(i, e)| (i, e.into())).collect(),
488        }
489    }
490}
491
492#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
493#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
494pub struct VfpDeltas {
495    pub graphics: BTreeMap<usize, KilohertzDelta>,
496    pub memory: BTreeMap<usize, KilohertzDelta>,
497}
498
499impl From<ClockTable> for VfpDeltas {
500    fn from(c: ClockTable) -> Self {
501        VfpDeltas {
502            graphics: c.gpu_delta.into_iter().map(|(i, d)| (i, d.into())).collect(),
503            memory: c.mem_delta.into_iter().map(|(i, d)| (i, d.into())).collect(),
504        }
505    }
506}
507
508#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
509#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
510pub struct VfPoint {
511    pub voltage: Microvolts,
512    pub frequency: Kilohertz,
513    pub delta: KilohertzDelta,
514}
515
516impl VfPoint {
517    pub fn new(point: VfpPoint, delta: KilohertzDelta) -> Self {
518        VfPoint {
519            voltage: point.voltage,
520            frequency: point.frequency,
521            delta: delta,
522        }
523    }
524}