mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
feat: add RDNA3 zero RPM setting (#393)
* feat: add zero rpm setting * feat: update zero rpm interface * chore: use upstream amdgpu-sysfs * feat: include zero rpm files in debug snapshots * doc: update readme * doc: improve
This commit is contained in:
parent
27d3402d08
commit
3e53e0336e
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -53,9 +53,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "amdgpu-sysfs"
|
||||
version = "0.17.2"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01631a1d520df9737660f26b0c64c5ea9d74667bf4cbb1c1559a6ddb8478c4ef"
|
||||
checksum = "e265e2ed3991ab499a8bfde9f663f7cd485a0414197b19f6ae9de919f5db089e"
|
||||
dependencies = [
|
||||
"enum_dispatch",
|
||||
"serde",
|
||||
|
@ -10,7 +10,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
amdgpu-sysfs = { version = "0.17.0", features = ["serde"] }
|
||||
amdgpu-sysfs = { version = "0.17.3", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_with = { version = "3.5.0", default-features = false, features = [
|
||||
"macros",
|
||||
|
@ -75,7 +75,7 @@ However the following table shows what functionality can be expected for a given
|
||||
| Vega | Supported | Supported | Supported | Supported | |
|
||||
| RDNA1 (RX 5000) | Supported | Supported | Supported | Supported | |
|
||||
| RDNA2 (RX 6000) | Supported | Supported | Supported | Supported | |
|
||||
| RDNA3 (RX 7000) | Supported | Limited | Supported | Limited | There is an unconfigurable temperature threshold below which the fan does not get turned on, even with a custom curve. The power cap is also sometimes lower than it should be. Requires kernel 6.7+. See [#255](https://github.com/ilya-zlobintsev/LACT/issues/255) for more info. |
|
||||
| RDNA3 (RX 7000) | Supported | Limited | Supported | Limited | Fan zero RPM mode is enabled by default even with a custom fan curve, and requires kernel 6.13 (`linux-next` when writing this) to be disabled. The power cap is sometimes reported lower than it should be. See [#255](https://github.com/ilya-zlobintsev/LACT/issues/255) for more info. |
|
||||
|
||||
GPUs not listed here will still work, but might not have full functionality available.
|
||||
Monitoring/system info will be available everywhere. Integrated GPUs might also only have basic configuration available.
|
||||
|
@ -610,6 +610,8 @@ impl GpuController for AmdGpuController {
|
||||
acoustic_target: self.handle.get_fan_acoustic_target().ok(),
|
||||
target_temp: self.handle.get_fan_target_temperature().ok(),
|
||||
minimum_pwm: self.handle.get_fan_minimum_pwm().ok(),
|
||||
zero_rpm_enable: self.handle.get_fan_zero_rpm_enable().ok(),
|
||||
zero_rpm_temperature: self.handle.get_fan_zero_rpm_stop_temperature().ok(),
|
||||
},
|
||||
},
|
||||
clockspeed: ClockspeedStats {
|
||||
@ -964,6 +966,35 @@ impl GpuController for AmdGpuController {
|
||||
.context("Failed to stop fan control")?;
|
||||
}
|
||||
|
||||
// Unlike the other PMFW options, zero rpm should be functional with a custom curve
|
||||
if let Some(zero_rpm) = config.pmfw_options.zero_rpm {
|
||||
let current_zero_rpm = self
|
||||
.handle
|
||||
.get_fan_zero_rpm_enable()
|
||||
.context("Could not get zero RPM mode")?;
|
||||
if current_zero_rpm != zero_rpm {
|
||||
let commit_handle = self
|
||||
.handle
|
||||
.set_fan_zero_rpm_enable(zero_rpm)
|
||||
.context("Could not set zero RPM mode")?;
|
||||
commit_handles.push(commit_handle);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(zero_rpm_threshold) = config.pmfw_options.zero_rpm_threshold {
|
||||
let current_threshold = self
|
||||
.handle
|
||||
.get_fan_zero_rpm_stop_temperature()
|
||||
.context("Could not get zero RPM temperature")?;
|
||||
if current_threshold.current != zero_rpm_threshold {
|
||||
let commit_handle = self
|
||||
.handle
|
||||
.set_fan_zero_rpm_stop_temperature(zero_rpm_threshold)
|
||||
.context("Could not set zero RPM temperature")?;
|
||||
commit_handles.push(commit_handle);
|
||||
}
|
||||
}
|
||||
|
||||
for handle in commit_handles {
|
||||
handle.commit()?;
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ const SNAPSHOT_FAN_CTRL_FILES: &[&str] = &[
|
||||
"acoustic_target_rpm_threshold",
|
||||
"fan_minimum_pwm",
|
||||
"fan_target_temperature",
|
||||
"fan_zero_rpm_enable",
|
||||
"fan_zero_rpm_stop_temperature",
|
||||
];
|
||||
const SNAPSHOT_HWMON_FILE_PREFIXES: &[&str] =
|
||||
&["fan", "pwm", "power", "temp", "freq", "in", "name"];
|
||||
|
@ -2,12 +2,13 @@ mod point_adjustment;
|
||||
|
||||
use self::point_adjustment::PointAdjustment;
|
||||
use crate::app::pages::oc_adjustment::OcAdjustment;
|
||||
use glib::clone;
|
||||
use glib::{clone, Propagation};
|
||||
use gtk::graphene::Point;
|
||||
use gtk::gsk::Transform;
|
||||
use gtk::prelude::*;
|
||||
use gtk::*;
|
||||
use lact_client::schema::{default_fan_curve, FanCurveMap};
|
||||
use lact_schema::PmfwInfo;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
@ -19,6 +20,8 @@ const DEFAULT_SPINDOWN_DELAY_MS: u64 = 5000;
|
||||
pub struct FanCurveFrame {
|
||||
pub container: Box,
|
||||
curve_container: Frame,
|
||||
zero_rpm_switch: Switch,
|
||||
zero_rpm_row: Box,
|
||||
points: Rc<RefCell<Vec<PointAdjustment>>>,
|
||||
spindown_delay_adj: OcAdjustment,
|
||||
change_threshold_adj: OcAdjustment,
|
||||
@ -110,10 +113,27 @@ impl FanCurveFrame {
|
||||
|
||||
root_box.append(&hysteresis_grid);
|
||||
|
||||
let zero_rpm_label = Label::builder()
|
||||
.label("Zero RPM mode")
|
||||
.halign(Align::Start)
|
||||
.build();
|
||||
let zero_rpm_switch = Switch::builder().halign(Align::End).hexpand(true).build();
|
||||
let zero_rpm_row = gtk::Box::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(5)
|
||||
.hexpand(true)
|
||||
.build();
|
||||
zero_rpm_row.append(&zero_rpm_label);
|
||||
zero_rpm_row.append(&zero_rpm_switch);
|
||||
|
||||
root_box.append(&zero_rpm_row);
|
||||
|
||||
let curve_frame = Self {
|
||||
container: root_box,
|
||||
curve_container,
|
||||
points,
|
||||
zero_rpm_row,
|
||||
zero_rpm_switch,
|
||||
spindown_delay_adj: spindown_delay_adj.clone(),
|
||||
change_threshold_adj: change_threshold_adj.clone(),
|
||||
hysteresis_grid,
|
||||
@ -230,6 +250,15 @@ impl FanCurveFrame {
|
||||
}
|
||||
);
|
||||
|
||||
self.zero_rpm_switch.connect_state_set(clone!(
|
||||
#[strong]
|
||||
f,
|
||||
move |_, _| {
|
||||
f();
|
||||
Propagation::Proceed
|
||||
}
|
||||
));
|
||||
|
||||
for point in &*self.points.borrow() {
|
||||
point.ratio.connect_value_changed(closure.clone());
|
||||
point.temperature.connect_value_changed(closure.clone());
|
||||
@ -257,6 +286,23 @@ impl FanCurveFrame {
|
||||
pub fn set_hysteresis_settings_visibile(&self, visible: bool) {
|
||||
self.hysteresis_grid.set_visible(visible);
|
||||
}
|
||||
|
||||
pub fn set_pmfw(&self, pmfw_info: &PmfwInfo) {
|
||||
self.zero_rpm_row
|
||||
.set_visible(pmfw_info.zero_rpm_enable.is_some());
|
||||
|
||||
if let Some(value) = pmfw_info.zero_rpm_enable {
|
||||
self.zero_rpm_switch.set_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_zero_rpm(&self) -> Option<bool> {
|
||||
if self.zero_rpm_row.is_visible() {
|
||||
Some(self.zero_rpm_switch.state())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OcAdjustmentOptions {
|
||||
|
@ -75,6 +75,8 @@ impl ThermalsPage {
|
||||
|
||||
container.append(&stats_section);
|
||||
|
||||
let pmfw_frame = PmfwFrame::new();
|
||||
|
||||
let fan_curve_frame = FanCurveFrame::new();
|
||||
|
||||
let fan_static_speed_frame = Box::builder()
|
||||
@ -84,8 +86,6 @@ impl ThermalsPage {
|
||||
.build();
|
||||
let fan_static_speed_adjustment = static_speed_adj(&fan_static_speed_frame);
|
||||
|
||||
let pmfw_frame = PmfwFrame::new();
|
||||
|
||||
let fan_control_section = PageSection::new("Fan control");
|
||||
|
||||
let fan_control_mode_stack = Stack::builder().build();
|
||||
@ -231,6 +231,7 @@ impl ThermalsPage {
|
||||
self.fan_curve_frame.set_curve(&default_fan_curve());
|
||||
}
|
||||
|
||||
self.fan_curve_frame.set_pmfw(&stats.fan.pmfw_info);
|
||||
self.pmfw_frame.set_info(&stats.fan.pmfw_info);
|
||||
}
|
||||
}
|
||||
@ -263,6 +264,8 @@ impl ThermalsPage {
|
||||
|
||||
pub fn get_thermals_settings(&self) -> Option<ThermalsSettings> {
|
||||
if self.fan_control_mode_stack_switcher.is_sensitive() {
|
||||
let mut pmfw = self.pmfw_frame.get_pmfw_options();
|
||||
|
||||
let name = self.fan_control_mode_stack.visible_child_name();
|
||||
let name = name
|
||||
.as_ref()
|
||||
@ -270,7 +273,10 @@ impl ThermalsPage {
|
||||
.expect("No name on the visible child");
|
||||
let (manual_fan_control, mode) = match name {
|
||||
"automatic" => (false, None),
|
||||
"curve" => (true, Some(FanControlMode::Curve)),
|
||||
"curve" => {
|
||||
pmfw.zero_rpm = self.fan_curve_frame.get_zero_rpm();
|
||||
(true, Some(FanControlMode::Curve))
|
||||
}
|
||||
"static" => (true, Some(FanControlMode::Static)),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@ -278,8 +284,6 @@ impl ThermalsPage {
|
||||
let curve = self.fan_curve_frame.get_curve();
|
||||
let curve = if curve.is_empty() { None } else { Some(curve) };
|
||||
|
||||
let pmfw = self.pmfw_frame.get_pmfw_options();
|
||||
|
||||
Some(ThermalsSettings {
|
||||
manual_fan_control,
|
||||
mode,
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::app::pages::oc_adjustment::OcAdjustment;
|
||||
use amdgpu_sysfs::gpu_handle::fan_control::FanInfo;
|
||||
use gtk::{
|
||||
glib::clone,
|
||||
glib::{clone, Propagation},
|
||||
prelude::{AdjustmentExt, ButtonExt, GridExt, WidgetExt},
|
||||
Align, Button, Grid, Label, MenuButton, Orientation, Popover, Scale, SpinButton,
|
||||
Align, Button, Grid, Label, MenuButton, Orientation, Popover, Scale, SpinButton, Switch,
|
||||
};
|
||||
use lact_client::schema::{PmfwInfo, PmfwOptions};
|
||||
|
||||
@ -14,6 +14,9 @@ pub struct PmfwFrame {
|
||||
acoustic_limit: OcAdjustment,
|
||||
acoustic_target: OcAdjustment,
|
||||
minimum_pwm: OcAdjustment,
|
||||
zero_rpm_label: Label,
|
||||
zero_rpm_switch: Switch,
|
||||
zero_rpm_temperature: OcAdjustment,
|
||||
reset_button: Button,
|
||||
}
|
||||
|
||||
@ -33,6 +36,17 @@ impl PmfwFrame {
|
||||
let acoustic_target = adjustment(&grid, "Acoustic target (RPM)", 2);
|
||||
let minimum_pwm = adjustment(&grid, "Minimum fan speed (%)", 3);
|
||||
|
||||
let zero_rpm_label = Label::builder()
|
||||
.label("Zero RPM mode")
|
||||
.halign(Align::Start)
|
||||
.build();
|
||||
let zero_rpm_switch = Switch::builder().halign(Align::End).build();
|
||||
|
||||
grid.attach(&zero_rpm_label, 0, 4, 1, 1);
|
||||
grid.attach(&zero_rpm_switch, 5, 4, 1, 1);
|
||||
|
||||
let zero_rpm_temperature = adjustment(&grid, "Zero RPM stop temperature (°C)", 5);
|
||||
|
||||
let reset_button = Button::builder()
|
||||
.label("Reset")
|
||||
.halign(Align::Fill)
|
||||
@ -42,7 +56,7 @@ impl PmfwFrame {
|
||||
.css_classes(["destructive-action"])
|
||||
.visible(false)
|
||||
.build();
|
||||
grid.attach(&reset_button, 5, 4, 1, 1);
|
||||
grid.attach(&reset_button, 5, 6, 1, 1);
|
||||
|
||||
Self {
|
||||
container: grid,
|
||||
@ -50,6 +64,9 @@ impl PmfwFrame {
|
||||
acoustic_limit,
|
||||
acoustic_target,
|
||||
minimum_pwm,
|
||||
zero_rpm_temperature,
|
||||
zero_rpm_switch,
|
||||
zero_rpm_label,
|
||||
reset_button,
|
||||
}
|
||||
}
|
||||
@ -59,8 +76,20 @@ impl PmfwFrame {
|
||||
set_fan_info(&self.acoustic_target, info.acoustic_target);
|
||||
set_fan_info(&self.minimum_pwm, info.minimum_pwm);
|
||||
set_fan_info(&self.target_temperature, info.target_temp);
|
||||
set_fan_info(&self.zero_rpm_temperature, info.zero_rpm_temperature);
|
||||
|
||||
if let Some(zero_rpm) = info.zero_rpm_enable {
|
||||
self.zero_rpm_switch.set_active(zero_rpm);
|
||||
|
||||
self.zero_rpm_switch.set_visible(true);
|
||||
self.zero_rpm_label.set_visible(true);
|
||||
} else {
|
||||
self.zero_rpm_switch.set_visible(false);
|
||||
self.zero_rpm_label.set_visible(false);
|
||||
}
|
||||
|
||||
let settings_available = *info != PmfwInfo::default();
|
||||
|
||||
self.reset_button.set_visible(settings_available);
|
||||
}
|
||||
|
||||
@ -93,6 +122,22 @@ impl PmfwFrame {
|
||||
f();
|
||||
}
|
||||
));
|
||||
self.zero_rpm_temperature.connect_value_changed(clone!(
|
||||
#[strong]
|
||||
f,
|
||||
move |_| {
|
||||
f();
|
||||
}
|
||||
));
|
||||
|
||||
self.zero_rpm_switch.connect_state_set(clone!(
|
||||
#[strong]
|
||||
f,
|
||||
move |_, _| {
|
||||
f();
|
||||
Propagation::Proceed
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
pub fn connect_reset<F: Fn() + 'static + Clone>(&self, f: F) {
|
||||
@ -119,6 +164,17 @@ impl PmfwFrame {
|
||||
.target_temperature
|
||||
.get_nonzero_value()
|
||||
.map(|value| value as u32),
|
||||
zero_rpm_threshold: self
|
||||
.zero_rpm_temperature
|
||||
.get_nonzero_value()
|
||||
.map(|value| value as u32),
|
||||
zero_rpm: {
|
||||
if self.zero_rpm_switch.is_visible() {
|
||||
Some(self.zero_rpm_switch.state())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,6 +243,8 @@ pub struct PmfwInfo {
|
||||
pub acoustic_target: Option<FanInfo>,
|
||||
pub target_temp: Option<FanInfo>,
|
||||
pub minimum_pwm: Option<FanInfo>,
|
||||
pub zero_rpm_enable: Option<bool>,
|
||||
pub zero_rpm_temperature: Option<FanInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
|
||||
@ -308,6 +310,8 @@ pub struct PmfwOptions {
|
||||
pub acoustic_target: Option<u32>,
|
||||
pub minimum_pwm: Option<u32>,
|
||||
pub target_temperature: Option<u32>,
|
||||
pub zero_rpm: Option<bool>,
|
||||
pub zero_rpm_threshold: Option<u32>,
|
||||
}
|
||||
|
||||
impl PmfwOptions {
|
||||
|
Loading…
Reference in New Issue
Block a user