feat: initial support for RDNA3 fan configuration (PMFW) (#239)

* wip

* feat: basic PMFW options gui

* bump amdgpu-sysfs

* test

* test 2

* test 3

* revert test

* fix: set pmfw settings last

* fix: always set pmfw values

* feat: reset pmfw button

* fix: reset button placement

* chore: cleanup

* chore: bump version

* feat: clean up pmfw settings on exit

* fix
This commit is contained in:
Ilya Zlobintsev 2024-01-07 17:03:56 +02:00 committed by GitHub
parent ce1cd56444
commit 2faea931d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 368 additions and 42 deletions

17
Cargo.lock generated
View File

@ -41,9 +41,9 @@ dependencies = [
[[package]] [[package]]
name = "amdgpu-sysfs" name = "amdgpu-sysfs"
version = "0.12.8" version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165e7e777966011248a311f2c2e96ec1242935da4b829ac2c9a615ae936c0640" checksum = "b3a224a37eff351e9942de53949ad8822b0ef0e628a3fb41d3b78fd06f39ecfc"
dependencies = [ dependencies = [
"enum_dispatch", "enum_dispatch",
"serde", "serde",
@ -1270,7 +1270,7 @@ dependencies = [
[[package]] [[package]]
name = "lact" name = "lact"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"lact-cli", "lact-cli",
@ -1281,7 +1281,7 @@ dependencies = [
[[package]] [[package]]
name = "lact-cli" name = "lact-cli"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"lact-client", "lact-client",
@ -1290,7 +1290,7 @@ dependencies = [
[[package]] [[package]]
name = "lact-client" name = "lact-client"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"lact-schema", "lact-schema",
@ -1302,7 +1302,7 @@ dependencies = [
[[package]] [[package]]
name = "lact-daemon" name = "lact-daemon"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -1327,7 +1327,7 @@ dependencies = [
[[package]] [[package]]
name = "lact-gui" name = "lact-gui"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gtk4", "gtk4",
@ -1341,13 +1341,14 @@ dependencies = [
[[package]] [[package]]
name = "lact-schema" name = "lact-schema"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"amdgpu-sysfs", "amdgpu-sysfs",
"clap", "clap",
"indexmap", "indexmap",
"serde", "serde",
"serde_json", "serde_json",
"serde_with",
] ]
[[package]] [[package]]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lact-cli" name = "lact-cli"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lact-client" name = "lact-client"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -10,8 +10,8 @@ use schema::{
power_profile_mode::PowerProfileModesTable, PerformanceLevel, PowerLevelKind, power_profile_mode::PowerProfileModesTable, PerformanceLevel, PowerLevelKind,
}, },
request::{ConfirmCommand, SetClocksCommand}, request::{ConfirmCommand, SetClocksCommand},
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap, PowerStates, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap, PmfwOptions,
Request, Response, SystemInfo, PowerStates, Request, Response, SystemInfo,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
@ -111,6 +111,7 @@ impl DaemonClient {
mode: Option<FanControlMode>, mode: Option<FanControlMode>,
static_speed: Option<f64>, static_speed: Option<f64>,
curve: Option<FanCurveMap>, curve: Option<FanCurveMap>,
pmfw: PmfwOptions,
) -> anyhow::Result<u64> { ) -> anyhow::Result<u64> {
self.make_request(Request::SetFanControl { self.make_request(Request::SetFanControl {
id, id,
@ -118,6 +119,7 @@ impl DaemonClient {
mode, mode,
static_speed, static_speed,
curve, curve,
pmfw,
})? })?
.inner() .inner()
} }
@ -138,6 +140,7 @@ impl DaemonClient {
PowerProfileModesTable PowerProfileModesTable
); );
request_with_id!(get_power_states, GetPowerStates, PowerStates); request_with_id!(get_power_states, GetPowerStates, PowerStates);
request_with_id!(reset_pmfw, ResetPmfw, u64);
pub fn set_performance_level( pub fn set_performance_level(
&self, &self,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lact-daemon" name = "lact-daemon"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
[features] [features]

View File

@ -4,7 +4,7 @@ use lact_schema::{
amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind}, amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind},
default_fan_curve, default_fan_curve,
request::SetClocksCommand, request::SetClocksCommand,
FanControlMode, FanControlMode, PmfwOptions,
}; };
use nix::unistd::getuid; use nix::unistd::getuid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -56,6 +56,8 @@ impl Default for Daemon {
pub struct Gpu { pub struct Gpu {
pub fan_control_enabled: bool, pub fan_control_enabled: bool,
pub fan_control_settings: Option<FanControlSettings>, pub fan_control_settings: Option<FanControlSettings>,
#[serde(default)]
pub pmfw_options: PmfwOptions,
pub power_cap: Option<f64>, pub power_cap: Option<f64>,
pub performance_level: Option<PerformanceLevel>, pub performance_level: Option<PerformanceLevel>,
#[serde(default, flatten)] #[serde(default, flatten)]
@ -179,10 +181,9 @@ fn default_apply_settings_timer() -> u64 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use lact_schema::{FanControlMode, PmfwOptions};
use std::collections::HashMap; use std::collections::HashMap;
use lact_schema::FanControlMode;
use super::{ClocksConfiguration, Config, Daemon, FanControlSettings, Gpu}; use super::{ClocksConfiguration, Config, Daemon, FanControlSettings, Gpu};
use crate::server::gpu_controller::fan_control::FanCurve; use crate::server::gpu_controller::fan_control::FanCurve;
@ -217,6 +218,7 @@ mod tests {
let mut gpu = Gpu { let mut gpu = Gpu {
fan_control_enabled: false, fan_control_enabled: false,
fan_control_settings: None, fan_control_settings: None,
pmfw_options: PmfwOptions::default(),
power_cap: None, power_cap: None,
performance_level: None, performance_level: None,
clocks_configuration: ClocksConfiguration::default(), clocks_configuration: ClocksConfiguration::default(),

View File

@ -18,7 +18,7 @@ use lact_schema::{
sysfs::SysFS, sysfs::SysFS,
}, },
ClocksInfo, ClockspeedStats, DeviceInfo, DeviceStats, DrmInfo, FanStats, GpuPciInfo, LinkInfo, ClocksInfo, ClockspeedStats, DeviceInfo, DeviceStats, DrmInfo, FanStats, GpuPciInfo, LinkInfo,
PciInfo, PowerState, PowerStates, PowerStats, VoltageStats, VramStats, PciInfo, PmfwInfo, PowerState, PowerStates, PowerStats, VoltageStats, VramStats,
}; };
use pciid_parser::Database; use pciid_parser::Database;
use std::{ use std::{
@ -262,6 +262,12 @@ impl GpuController {
speed_current: self.hw_mon_and_then(HwMon::get_fan_current), speed_current: self.hw_mon_and_then(HwMon::get_fan_current),
speed_max: self.hw_mon_and_then(HwMon::get_fan_max), speed_max: self.hw_mon_and_then(HwMon::get_fan_max),
speed_min: self.hw_mon_and_then(HwMon::get_fan_min), speed_min: self.hw_mon_and_then(HwMon::get_fan_min),
pmfw_info: PmfwInfo {
acoustic_limit: self.handle.get_fan_acoustic_limit().ok(),
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(),
},
}, },
clockspeed: ClockspeedStats { clockspeed: ClockspeedStats {
gpu_clockspeed: self.hw_mon_and_then(HwMon::get_gpu_clockspeed), gpu_clockspeed: self.hw_mon_and_then(HwMon::get_gpu_clockspeed),
@ -468,6 +474,30 @@ impl GpuController {
.collect() .collect()
} }
pub fn reset_pmfw_settings(&self) {
let handle = &self.handle;
if self.handle.get_fan_target_temperature().is_ok() {
if let Err(err) = handle.reset_fan_target_temperature() {
warn!("Could not reset target temperature: {err:#}");
}
}
if self.handle.get_fan_acoustic_target().is_ok() {
if let Err(err) = handle.reset_fan_acoustic_target() {
warn!("Could not reset acoustic target: {err:#}");
}
}
if self.handle.get_fan_acoustic_limit().is_ok() {
if let Err(err) = handle.reset_fan_acoustic_limit() {
warn!("Could not reset acoustic limit: {err:#}");
}
}
if self.handle.get_fan_minimum_pwm().is_ok() {
if let Err(err) = handle.reset_fan_minimum_pwm() {
warn!("Could not reset minimum pwm: {err:#}");
}
}
}
pub async fn apply_config(&self, config: &config::Gpu) -> anyhow::Result<()> { pub async fn apply_config(&self, config: &config::Gpu) -> anyhow::Result<()> {
if config.fan_control_enabled { if config.fan_control_enabled {
if let Some(ref settings) = config.fan_control_settings { if let Some(ref settings) = config.fan_control_settings {
@ -598,6 +628,30 @@ impl GpuController {
.with_context(|| format!("Could not set {kind:?} power states"))?; .with_context(|| format!("Could not set {kind:?} power states"))?;
} }
if !config.fan_control_enabled {
let pmfw = &config.pmfw_options;
if let Some(acoustic_limit) = pmfw.acoustic_limit {
self.handle
.set_fan_acoustic_limit(acoustic_limit)
.context("Could not set acoustic limit")?;
}
if let Some(acoustic_target) = pmfw.acoustic_target {
self.handle
.set_fan_acoustic_target(acoustic_target)
.context("Could not set acoustic target")?;
}
if let Some(target_temperature) = pmfw.target_temperature {
self.handle
.set_fan_target_temperature(target_temperature)
.context("Could not set target temperature")?;
}
if let Some(minimum_pwm) = pmfw.minimum_pwm {
self.handle
.set_fan_minimum_pwm(minimum_pwm)
.context("Could not set minimum pwm")?;
}
}
Ok(()) Ok(())
} }
} }

View File

@ -13,7 +13,8 @@ use lact_schema::{
}, },
default_fan_curve, default_fan_curve,
request::{ConfirmCommand, SetClocksCommand}, request::{ConfirmCommand, SetClocksCommand},
ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap, PowerStates, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanControlMode, FanCurveMap, PmfwOptions,
PowerStates,
}; };
use std::{ use std::{
cell::RefCell, cell::RefCell,
@ -263,6 +264,7 @@ impl<'a> Handler {
mode: Option<FanControlMode>, mode: Option<FanControlMode>,
static_speed: Option<f64>, static_speed: Option<f64>,
curve: Option<FanCurveMap>, curve: Option<FanCurveMap>,
pmfw: PmfwOptions,
) -> anyhow::Result<u64> { ) -> anyhow::Result<u64> {
let settings = { let settings = {
let mut config_guard = self let mut config_guard = self
@ -323,6 +325,17 @@ impl<'a> Handler {
if let Some(settings) = settings { if let Some(settings) = settings {
config.fan_control_settings = Some(settings); config.fan_control_settings = Some(settings);
} }
config.pmfw_options = pmfw;
})
.await
}
pub async fn reset_pmfw(&self, id: &str) -> anyhow::Result<u64> {
info!("Resetting PMFW settings");
self.controller_by_id(id)?.reset_pmfw_settings();
self.edit_gpu_config(id.to_owned(), |config| {
config.pmfw_options = PmfwOptions::default();
}) })
.await .await
} }
@ -510,6 +523,8 @@ impl<'a> Handler {
} }
} }
controller.reset_pmfw_settings();
if let Err(err) = controller.apply_config(&config::Gpu::default()).await { if let Err(err) = controller.apply_config(&config::Gpu::default()).await {
error!("Could not reset settings for controller {id}: {err:#}"); error!("Could not reset settings for controller {id}: {err:#}");
} }

View File

@ -92,11 +92,13 @@ async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyho
mode, mode,
static_speed, static_speed,
curve, curve,
pmfw,
} => ok_response( } => ok_response(
handler handler
.set_fan_control(id, enabled, mode, static_speed, curve) .set_fan_control(id, enabled, mode, static_speed, curve, pmfw)
.await?, .await?,
), ),
Request::ResetPmfw { id } => ok_response(handler.reset_pmfw(id).await?),
Request::SetPowerCap { id, cap } => ok_response(handler.set_power_cap(id, cap).await?), Request::SetPowerCap { id, cap } => ok_response(handler.set_power_cap(id, cap).await?),
Request::SetPerformanceLevel { Request::SetPerformanceLevel {
id, id,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lact-gui" name = "lact-gui"
version = "0.5.1" version = "0.5.2"
authors = ["Ilya Zlobintsev <ilya.zl@protonmail.com>"] authors = ["Ilya Zlobintsev <ilya.zl@protonmail.com>"]
edition = "2021" edition = "2021"

View File

@ -125,6 +125,23 @@ impl App {
} }
})); }));
app.root_stack.thermals_page.connect_reset_pmfw(clone!(@strong app, @strong current_gpu_id => move || {
debug!("Resetting PMFW settings");
let gpu_id = current_gpu_id.borrow().clone();
match app.daemon_client.reset_pmfw(&gpu_id)
.and_then(|buffer| buffer.inner())
.and_then(|_| app.daemon_client.confirm_pending_config(ConfirmCommand::Confirm))
{
Ok(()) => {
app.set_initial(&gpu_id);
}
Err(err) => {
show_error(&app.window, err);
}
}
}));
app.apply_revealer.connect_apply_button_clicked( app.apply_revealer.connect_apply_button_clicked(
clone!(@strong app, @strong current_gpu_id => move || { clone!(@strong app, @strong current_gpu_id => move || {
glib::idle_add_local_once(clone!(@strong app, @strong current_gpu_id => move || { glib::idle_add_local_once(clone!(@strong app, @strong current_gpu_id => move || {
@ -389,6 +406,7 @@ impl App {
thermals_settings.mode, thermals_settings.mode,
thermals_settings.static_speed, thermals_settings.static_speed,
thermals_settings.curve, thermals_settings.curve,
thermals_settings.pmfw,
) )
.context("Could not set fan control")?; .context("Could not set fan control")?;
self.daemon_client self.daemon_client

View File

@ -1,4 +1,5 @@
mod info_page; mod info_page;
mod oc_adjustment;
mod oc_page; mod oc_page;
mod software_page; mod software_page;
mod thermals_page; mod thermals_page;

View File

@ -2,7 +2,7 @@ mod imp;
use glib::Object; use glib::Object;
use gtk::{ use gtk::{
glib::{self}, glib::{self, ObjectExt},
subclass::prelude::*, subclass::prelude::*,
traits::AdjustmentExt, traits::AdjustmentExt,
}; };
@ -57,9 +57,19 @@ impl OcAdjustment {
} }
} }
pub fn get_nonzero_value(&self) -> Option<f64> {
let value = self.value();
if value == 0.0 {
None
} else {
Some(value)
}
}
pub fn set_initial_value(&self, value: f64) { pub fn set_initial_value(&self, value: f64) {
let inner = self.imp(); let inner = self.imp();
inner.obj().set_value(value); inner.obj().set_value(value);
inner.obj().emit_by_name::<()>("value_changed", &[]);
inner.changed.store(false, Ordering::SeqCst); inner.changed.store(false, Ordering::SeqCst);
} }
} }

View File

@ -1,6 +1,5 @@
mod clocks_frame; mod clocks_frame;
mod gpu_stats_section; mod gpu_stats_section;
mod oc_adjustment;
mod performance_frame; mod performance_frame;
mod power_cap_section; mod power_cap_section;
// mod power_cap_frame; // mod power_cap_frame;

View File

@ -29,7 +29,7 @@ impl Default for PowerCapSection {
} }
mod imp { mod imp {
use crate::app::{page_section::PageSection, root_stack::oc_page::oc_adjustment::OcAdjustment}; use crate::app::{page_section::PageSection, root_stack::oc_adjustment::OcAdjustment};
use gtk::{ use gtk::{
glib::{self, clone, subclass::InitializingObject, Properties, StaticTypeExt}, glib::{self, clone, subclass::InitializingObject, Properties, StaticTypeExt},
prelude::{ButtonExt, ObjectExt}, prelude::{ButtonExt, ObjectExt},

View File

@ -1,15 +1,16 @@
mod fan_curve_frame; mod fan_curve_frame;
mod pmfw_frame;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::*; use gtk::*;
use lact_client::schema::{default_fan_curve, DeviceStats, FanControlMode, FanCurveMap}; use lact_client::schema::{
default_fan_curve, DeviceStats, FanControlMode, FanCurveMap, PmfwOptions,
use crate::app::page_section::PageSection; };
use self::fan_curve_frame::FanCurveFrame;
use self::{fan_curve_frame::FanCurveFrame, pmfw_frame::PmfwFrame};
use super::{label_row, values_grid}; use super::{label_row, values_grid};
use crate::app::page_section::PageSection;
#[derive(Debug)] #[derive(Debug)]
pub struct ThermalsSettings { pub struct ThermalsSettings {
@ -17,6 +18,7 @@ pub struct ThermalsSettings {
pub mode: Option<FanControlMode>, pub mode: Option<FanControlMode>,
pub static_speed: Option<f64>, pub static_speed: Option<f64>,
pub curve: Option<FanCurveMap>, pub curve: Option<FanCurveMap>,
pub pmfw: PmfwOptions,
} }
#[derive(Clone)] #[derive(Clone)]
@ -24,6 +26,7 @@ pub struct ThermalsPage {
pub container: Box, pub container: Box,
temperatures_label: Label, temperatures_label: Label,
fan_speed_label: Label, fan_speed_label: Label,
pmfw_frame: PmfwFrame,
fan_static_speed_adjustment: Adjustment, fan_static_speed_adjustment: Adjustment,
fan_curve_frame: FanCurveFrame, fan_curve_frame: FanCurveFrame,
fan_control_mode_stack: Stack, fan_control_mode_stack: Stack,
@ -58,6 +61,8 @@ impl ThermalsPage {
.build(); .build();
let fan_static_speed_adjustment = static_speed_adj(&fan_static_speed_frame); 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_section = PageSection::new("Fan control");
let fan_control_mode_stack = Stack::builder().build(); let fan_control_mode_stack = Stack::builder().build();
@ -67,14 +72,8 @@ impl ThermalsPage {
.sensitive(false) .sensitive(false)
.build(); .build();
fan_control_mode_stack.add_titled( fan_control_mode_stack.add_titled(&pmfw_frame.container, Some("automatic"), "Automatic");
&Box::new(Orientation::Vertical, 15),
Some("automatic"),
"Automatic",
);
fan_control_mode_stack.add_titled(&fan_curve_frame.container, Some("curve"), "Curve"); fan_control_mode_stack.add_titled(&fan_curve_frame.container, Some("curve"), "Curve");
fan_control_mode_stack.add_titled(&fan_static_speed_frame, Some("static"), "Static"); fan_control_mode_stack.add_titled(&fan_static_speed_frame, Some("static"), "Static");
fan_control_section.append(&fan_control_mode_stack_switcher); fan_control_section.append(&fan_control_mode_stack_switcher);
@ -96,6 +95,7 @@ impl ThermalsPage {
fan_curve_frame, fan_curve_frame,
fan_control_mode_stack, fan_control_mode_stack,
fan_control_mode_stack_switcher, fan_control_mode_stack_switcher,
pmfw_frame,
} }
} }
@ -155,6 +155,8 @@ impl ThermalsPage {
if !stats.fan.control_enabled && self.fan_curve_frame.get_curve().is_empty() { if !stats.fan.control_enabled && self.fan_curve_frame.get_curve().is_empty() {
self.fan_curve_frame.set_curve(&default_fan_curve()); self.fan_curve_frame.set_curve(&default_fan_curve());
} }
self.pmfw_frame.set_info(&stats.fan.pmfw_info);
} }
} }
@ -169,6 +171,8 @@ impl ThermalsPage {
f(); f();
})); }));
self.pmfw_frame.connect_settings_changed(f.clone());
self.fan_curve_frame.connect_adjusted(move || { self.fan_curve_frame.connect_adjusted(move || {
f(); f();
}); });
@ -191,16 +195,23 @@ impl ThermalsPage {
let curve = self.fan_curve_frame.get_curve(); let curve = self.fan_curve_frame.get_curve();
let curve = if curve.is_empty() { None } else { Some(curve) }; let curve = if curve.is_empty() { None } else { Some(curve) };
let pmfw = self.pmfw_frame.get_pmfw_options();
Some(ThermalsSettings { Some(ThermalsSettings {
manual_fan_control, manual_fan_control,
mode, mode,
static_speed, static_speed,
curve, curve,
pmfw,
}) })
} else { } else {
None None
} }
} }
pub fn connect_reset_pmfw<F: Fn() + 'static + Clone>(&self, f: F) {
self.pmfw_frame.connect_reset(f);
}
} }
fn static_speed_adj(parent_box: &Box) -> Adjustment { fn static_speed_adj(parent_box: &Box) -> Adjustment {

View File

@ -0,0 +1,179 @@
use crate::app::root_stack::oc_adjustment::OcAdjustment;
use gtk::{
glib::clone,
prelude::{AdjustmentExt, ButtonExt, GridExt, WidgetExt},
Align, Button, Grid, Label, MenuButton, Orientation, Popover, Scale, SpinButton,
};
use lact_client::schema::{amdgpu_sysfs::gpu_handle::fan_control::FanInfo, PmfwInfo, PmfwOptions};
#[derive(Clone)]
pub struct PmfwFrame {
pub container: Grid,
target_temperature: OcAdjustment,
acoustic_limit: OcAdjustment,
acoustic_target: OcAdjustment,
minimum_pwm: OcAdjustment,
reset_button: Button,
}
impl PmfwFrame {
pub fn new() -> Self {
let grid = Grid::builder()
.orientation(Orientation::Vertical)
.row_spacing(5)
.margin_top(10)
.margin_bottom(10)
.margin_start(10)
.margin_end(10)
.build();
let target_temperature = adjustment(&grid, "Target temperature (°C)", 0);
let acoustic_limit = adjustment(&grid, "Acoustic limit (RPM)", 1);
let acoustic_target = adjustment(&grid, "Acoustic target (RPM)", 2);
let minimum_pwm = adjustment(&grid, "Minimum fan speed (%)", 3);
let reset_button = Button::builder()
.label("Reset")
.halign(Align::Fill)
.margin_top(5)
.margin_bottom(5)
.tooltip_text("Warning: this resets the fan firmware settings!")
.css_classes(["destructive-action"])
.visible(false)
.build();
grid.attach(&reset_button, 5, 4, 1, 1);
Self {
container: grid,
target_temperature,
acoustic_limit,
acoustic_target,
minimum_pwm,
reset_button,
}
}
pub fn set_info(&self, info: &PmfwInfo) {
set_fan_info(&self.acoustic_limit, info.acoustic_limit);
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);
let settings_available = *info != PmfwInfo::default();
self.reset_button.set_visible(settings_available);
}
pub fn connect_settings_changed<F: Fn() + 'static + Clone>(&self, f: F) {
self.acoustic_limit
.connect_value_changed(clone!(@strong f => move |_| {
f();
}));
self.acoustic_target
.connect_value_changed(clone!(@strong f => move |_| {
f();
}));
self.minimum_pwm
.connect_value_changed(clone!(@strong f => move |_| {
f();
}));
self.target_temperature
.connect_value_changed(clone!(@strong f => move |_| {
f();
}));
}
pub fn connect_reset<F: Fn() + 'static + Clone>(&self, f: F) {
self.reset_button.connect_clicked(move |_| {
f();
});
}
pub fn get_pmfw_options(&self) -> PmfwOptions {
PmfwOptions {
acoustic_limit: self
.acoustic_limit
.get_nonzero_value()
.map(|value| value as u32),
acoustic_target: self
.acoustic_target
.get_nonzero_value()
.map(|value| value as u32),
minimum_pwm: self
.minimum_pwm
.get_nonzero_value()
.map(|value| value as u32),
target_temperature: self
.target_temperature
.get_nonzero_value()
.map(|value| value as u32),
}
}
}
fn set_fan_info(adjustment: &OcAdjustment, info: Option<FanInfo>) {
match info {
Some(info) => {
if let Some((min, max)) = info.allowed_range {
adjustment.set_lower(min as f64);
adjustment.set_upper(max as f64);
} else {
adjustment.set_lower(0.0);
adjustment.set_upper(info.current as f64);
}
adjustment.set_initial_value(info.current as f64);
}
None => {
adjustment.set_upper(0.0);
adjustment.set_initial_value(0.0);
}
}
}
fn adjustment(parent_grid: &Grid, label: &str, row: i32) -> OcAdjustment {
let label = Label::builder().label(label).halign(Align::Start).build();
let adjustment = OcAdjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 0.0);
let scale = Scale::builder()
.orientation(Orientation::Horizontal)
.adjustment(&adjustment)
.hexpand(true)
.margin_start(5)
.margin_end(5)
.build();
let value_selector = SpinButton::new(Some(&adjustment), 1.0, 0);
let value_label = Label::new(None);
let popover = Popover::builder().child(&value_selector).build();
let value_button = MenuButton::builder()
.popover(&popover)
.child(&value_label)
.build();
adjustment.connect_value_changed(
clone!(@strong value_label, @strong label, @strong scale, @strong value_button => move |adjustment| {
let value = adjustment.value();
value_label.set_text(&format!("{}", value as u32));
if adjustment.upper() == 0.0 {
label.hide();
value_label.hide();
scale.hide();
value_button.hide();
} else {
label.show();
value_label.show();
scale.show();
value_button.show();
}
}),
);
parent_grid.attach(&label, 0, row, 1, 1);
parent_grid.attach(&scale, 1, row, 4, 1);
parent_grid.attach(&value_button, 5, row, 1, 1);
adjustment
}

View File

@ -1,16 +1,19 @@
[package] [package]
name = "lact-schema" name = "lact-schema"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
[features] [features]
args = ["clap"] args = ["clap"]
[dependencies] [dependencies]
amdgpu-sysfs = { version = "0.12.7", features = ["serde"] } amdgpu-sysfs = { version = "0.12.9", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
indexmap = { version = "*", features = ["serde"] } indexmap = { version = "*", features = ["serde"] }
clap = { version = "4.4.11", features = ["derive"], optional = true } clap = { version = "4.4.11", features = ["derive"], optional = true }
serde_with = { version = "3.4.0", default-features = false, features = [
"macros",
] }
[dev-dependencies] [dev-dependencies]
serde_json = "1.0" serde_json = "1.0"

View File

@ -12,6 +12,7 @@ pub use response::Response;
use amdgpu_sysfs::{ use amdgpu_sysfs::{
gpu_handle::{ gpu_handle::{
fan_control::FanInfo,
overdrive::{ClocksTable, ClocksTableGen}, overdrive::{ClocksTable, ClocksTableGen},
PerformanceLevel, PerformanceLevel,
}, },
@ -19,6 +20,7 @@ use amdgpu_sysfs::{
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
@ -195,6 +197,18 @@ pub struct FanStats {
pub speed_current: Option<u32>, pub speed_current: Option<u32>,
pub speed_max: Option<u32>, pub speed_max: Option<u32>,
pub speed_min: Option<u32>, pub speed_min: Option<u32>,
// RDNA3+ params
#[serde(default)]
pub pmfw_info: PmfwInfo,
}
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PmfwInfo {
pub acoustic_limit: Option<FanInfo>,
pub acoustic_target: Option<FanInfo>,
pub target_temp: Option<FanInfo>,
pub minimum_pwm: Option<FanInfo>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, Clone, Copy)]
@ -248,3 +262,12 @@ pub enum InitramfsType {
Debian, Debian,
Mkinitcpio, Mkinitcpio,
} }
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PmfwOptions {
pub acoustic_limit: Option<u32>,
pub acoustic_target: Option<u32>,
pub minimum_pwm: Option<u32>,
pub target_temperature: Option<u32>,
}

View File

@ -1,4 +1,4 @@
use crate::{FanControlMode, FanCurveMap}; use crate::{FanControlMode, FanCurveMap, PmfwOptions};
use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind}; use amdgpu_sysfs::gpu_handle::{PerformanceLevel, PowerLevelKind};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -26,6 +26,11 @@ pub enum Request<'a> {
mode: Option<FanControlMode>, mode: Option<FanControlMode>,
static_speed: Option<f64>, static_speed: Option<f64>,
curve: Option<FanCurveMap>, curve: Option<FanCurveMap>,
#[serde(default)]
pmfw: PmfwOptions,
},
ResetPmfw {
id: &'a str,
}, },
SetPowerCap { SetPowerCap {
id: &'a str, id: &'a str,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lact" name = "lact"
version = "0.5.1" version = "0.5.2"
edition = "2021" edition = "2021"
[features] [features]

View File

@ -3,7 +3,7 @@ metadata:
description: AMDGPU control utility description: AMDGPU control utility
arch: x86_64 arch: x86_64
license: MIT license: MIT
version: 0.5.1 version: 0.5.2
maintainer: ilya-zlobintsev maintainer: ilya-zlobintsev
url: https://github.com/ilya-zlobintsev/lact url: https://github.com/ilya-zlobintsev/lact
source: source:

View File

@ -3,7 +3,7 @@ metadata:
description: AMDGPU control utility description: AMDGPU control utility
arch: x86_64 arch: x86_64
license: MIT license: MIT
version: 0.5.1 version: 0.5.2
maintainer: ilya-zlobintsev maintainer: ilya-zlobintsev
url: https://github.com/ilya-zlobintsev/lact url: https://github.com/ilya-zlobintsev/lact
source: source:

View File

@ -3,7 +3,7 @@ metadata:
description: AMDGPU control utility description: AMDGPU control utility
arch: x86_64 arch: x86_64
license: MIT license: MIT
version: 0.5.1 version: 0.5.2
maintainer: ilya-zlobintsev maintainer: ilya-zlobintsev
url: https://github.com/ilya-zlobintsev/lact url: https://github.com/ilya-zlobintsev/lact
source: source: