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:
Ilya Zlobintsev 2024-11-14 00:11:24 +02:00 committed by GitHub
parent 27d3402d08
commit 3e53e0336e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 156 additions and 13 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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",

View File

@ -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.

View File

@ -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()?;
}

View File

@ -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"];

View File

@ -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 {

View File

@ -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,

View File

@ -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
}
},
}
}
}

View File

@ -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 {