Skip to content

Commit f99c683

Browse files
feat: add target temperature (acoustic limit) support
1 parent e8c1b25 commit f99c683

4 files changed

Lines changed: 149 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ repository = "https://github.com/Dreaming-Codes/nvidia_oc"
1010
clap = { version = "4.5.54", features = ["derive"] }
1111
clap_complete = "4.5.65"
1212
nvml-wrapper = "0.11.0"
13+
nvml-wrapper-sys = "0.9.0"
1314
serde = { version = "1.0.228", features = ["derive"] }
1415
serde_json = "1.0.149"
1516
sudo2 = "0.2.1"

example_config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"memOffset": 160,
66
"powerLimit": 500,
77
"minClock": 0,
8-
"maxClock": 2000
8+
"maxClock": 2000,
9+
"targetTemp": 75
910
}
1011
}
1112
}

src/main.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
use clap::{Args, CommandFactory, Parser, Subcommand};
22
use clap_complete::{generate, Generator, Shell};
33
use nvml_wrapper::{error::NvmlError, Device, Nvml};
4+
use nvml_wrapper_sys::bindings::{
5+
nvmlDevice_t, nvmlReturn_enum_NVML_SUCCESS,
6+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_CURR,
7+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_MAX,
8+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_MIN, NvmlLib,
9+
};
410
use serde::Deserialize;
511
use std::{collections::HashMap, io};
612

@@ -64,6 +70,11 @@ struct Sets {
6470
/// GPU max memory clock
6571
#[arg(long, requires = "min_mem_clock")]
6672
max_mem_clock: Option<u32>,
73+
/// Target temperature in Celsius (acoustic limit). The GPU will automatically
74+
/// adjust fan curves to maintain this temperature. Similar to MSI Afterburner's
75+
/// "target temperature" feature.
76+
#[arg(short, long)]
77+
target_temp: Option<u32>,
6778
}
6879

6980
impl Sets {
@@ -121,6 +132,20 @@ impl Sets {
121132
.set_mem_locked_clocks(min_mem_clock, max_mem_clock)
122133
.expect("Failed to set GPU min and max memory clocks");
123134
}
135+
136+
if let Some(target_temp) = self.target_temp {
137+
if let Err(e) = set_acoustic_temperature(device, target_temp) {
138+
let (min, max) = get_acoustic_temperature_range(device);
139+
let mut error_msg = format!(
140+
"Failed to set target temperature: {}°C - {}",
141+
target_temp, e
142+
);
143+
if let (Some(min), Some(max)) = (min, max) {
144+
error_msg.push_str(&format!(" Valid range: {}°C - {}°C", min, max));
145+
}
146+
panic!("{}", error_msg);
147+
}
148+
}
124149
}
125150
}
126151

@@ -179,6 +204,20 @@ fn main() {
179204
),
180205
Err(e) => eprintln!("Failed to get GPU power limit constraints: {:?}", e),
181206
}
207+
208+
// Target temperature (acoustic limit)
209+
match get_acoustic_temperature(&device) {
210+
Some(temp) => println!("Target temperature (acoustic): {}°C", temp),
211+
None => eprintln!("Failed to get target temperature (not supported or not set)"),
212+
}
213+
214+
let (min_temp, max_temp) = get_acoustic_temperature_range(&device);
215+
match (min_temp, max_temp) {
216+
(Some(min), Some(max)) => {
217+
println!("Target temperature range: {}°C - {}°C", min, max)
218+
}
219+
_ => eprintln!("Failed to get target temperature range (not supported)"),
220+
}
182221
}
183222
None => {
184223
let Ok(config_file) = std::fs::read_to_string(cli.file) else {
@@ -227,3 +266,109 @@ fn generate_completion_script<G: Generator>(gen: G) {
227266
let name = cmd.get_name().to_string();
228267
generate(gen, &mut cmd, name, &mut io::stdout());
229268
}
269+
270+
/// Gets the raw NVML device handle from a Device.
271+
/// This is needed to call low-level NVML functions not exposed by nvml-wrapper.
272+
fn get_raw_device_handle(device: &Device) -> nvmlDevice_t {
273+
// SAFETY: Device stores the raw handle as the first field in its struct.
274+
// We access it by transmuting the reference.
275+
unsafe { std::ptr::read(device as *const Device as *const nvmlDevice_t) }
276+
}
277+
278+
/// Sets the acoustic (target) temperature threshold.
279+
/// The GPU will automatically adjust fan curves to maintain this temperature.
280+
fn set_acoustic_temperature(device: &Device, temp_celsius: u32) -> Result<(), String> {
281+
let handle = get_raw_device_handle(device);
282+
let mut temp = temp_celsius as i32;
283+
284+
// Load the NVML library
285+
let nvml_lib = unsafe {
286+
NvmlLib::new("libnvidia-ml.so.1")
287+
.or_else(|_| NvmlLib::new("libnvidia-ml.so"))
288+
.map_err(|e| format!("Failed to load NVML library: {:?}", e))?
289+
};
290+
291+
let result = unsafe {
292+
nvml_lib.nvmlDeviceSetTemperatureThreshold(
293+
handle,
294+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_CURR,
295+
&mut temp,
296+
)
297+
};
298+
299+
if result == nvmlReturn_enum_NVML_SUCCESS {
300+
Ok(())
301+
} else {
302+
Err(format!("NVML error code: {}", result))
303+
}
304+
}
305+
306+
/// Gets the current acoustic (target) temperature threshold.
307+
fn get_acoustic_temperature(device: &Device) -> Option<u32> {
308+
let handle = get_raw_device_handle(device);
309+
let mut temp: u32 = 0;
310+
311+
let nvml_lib = unsafe {
312+
NvmlLib::new("libnvidia-ml.so.1")
313+
.or_else(|_| NvmlLib::new("libnvidia-ml.so"))
314+
.ok()?
315+
};
316+
317+
let result = unsafe {
318+
nvml_lib.nvmlDeviceGetTemperatureThreshold(
319+
handle,
320+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_CURR,
321+
&mut temp,
322+
)
323+
};
324+
325+
if result == nvmlReturn_enum_NVML_SUCCESS {
326+
Some(temp)
327+
} else {
328+
None
329+
}
330+
}
331+
332+
/// Gets the min and max acoustic temperature range.
333+
fn get_acoustic_temperature_range(device: &Device) -> (Option<u32>, Option<u32>) {
334+
let handle = get_raw_device_handle(device);
335+
let mut min_temp: u32 = 0;
336+
let mut max_temp: u32 = 0;
337+
338+
let nvml_lib = match unsafe {
339+
NvmlLib::new("libnvidia-ml.so.1").or_else(|_| NvmlLib::new("libnvidia-ml.so"))
340+
} {
341+
Ok(lib) => lib,
342+
Err(_) => return (None, None),
343+
};
344+
345+
let min_result = unsafe {
346+
nvml_lib.nvmlDeviceGetTemperatureThreshold(
347+
handle,
348+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_MIN,
349+
&mut min_temp,
350+
)
351+
};
352+
353+
let max_result = unsafe {
354+
nvml_lib.nvmlDeviceGetTemperatureThreshold(
355+
handle,
356+
nvmlTemperatureThresholds_enum_NVML_TEMPERATURE_THRESHOLD_ACOUSTIC_MAX,
357+
&mut max_temp,
358+
)
359+
};
360+
361+
let min = if min_result == nvmlReturn_enum_NVML_SUCCESS {
362+
Some(min_temp)
363+
} else {
364+
None
365+
};
366+
367+
let max = if max_result == nvmlReturn_enum_NVML_SUCCESS {
368+
Some(max_temp)
369+
} else {
370+
None
371+
};
372+
373+
(min, max)
374+
}

0 commit comments

Comments
 (0)