From 3f87c6d642c3988c80c1537ab7da3b2bf714e386 Mon Sep 17 00:00:00 2001 From: Ilya Zlobintsev Date: Fri, 6 Jun 2025 21:31:33 +0300 Subject: [PATCH] feat: automatically disable conflicting power-profiles-daemon actions (#615) * feat: automatically disable conflicting power-profiles-daemon actions * chore: update README ppd information * fix: random intel xe tile ordering --- README.md | 7 +- lact-daemon/src/lib.rs | 4 +- lact-daemon/src/server.rs | 5 +- .../src/server/gpu_controller/intel.rs | 1 + lact-daemon/src/{server => }/system.rs | 4 +- .../src/system/power_profiles_daemon.rs | 117 ++++++++++++++++++ 6 files changed, 131 insertions(+), 7 deletions(-) rename lact-daemon/src/{server => }/system.rs (99%) create mode 100644 lact-daemon/src/system/power_profiles_daemon.rs diff --git a/README.md b/README.md index 4fff325e..4b4f5578 100644 --- a/README.md +++ b/README.md @@ -127,10 +127,13 @@ more information. ## Power profiles daemon note! If you are using `power-profiles-daemon` (which is installed by default on many -distributions), by default it will override the amdgpu performance level setting +distributions), by default it may override the amdgpu performance level setting according to its own profile. -To avoid this, create a file at +When using LACT 0.7.5+ and power-profiles-daemon 0.30+, LACT will try to connect to power-profiles-daemon +and automatically disable the conflicting amdgpu action in ppd to avoid this conflict. + +If running older versions, you can resolve this manually by creating a file at `/etc/systemd/system/power-profiles-daemon.service.d/override.conf` with the following contents: diff --git a/lact-daemon/src/lib.rs b/lact-daemon/src/lib.rs index ad51f98e..0bb839cc 100644 --- a/lact-daemon/src/lib.rs +++ b/lact-daemon/src/lib.rs @@ -6,13 +6,13 @@ mod config; mod server; mod socket; mod suspend; +mod system; #[cfg(test)] mod tests; use anyhow::Context; use config::Config; use futures::future::select_all; -use server::system; use server::{handle_stream, handler::Handler, Server}; use std::sync::Arc; use std::{os::unix::net::UnixStream as StdUnixStream, time::Duration}; @@ -31,7 +31,7 @@ use tracing_subscriber::EnvFilter; /// RDNA3, minimum family that supports the new pmfw interface pub const AMDGPU_FAMILY_GC_11_0_0: u32 = 145; -pub use server::system::MODULE_CONF_PATH; +pub use system::MODULE_CONF_PATH; const MIN_SYSTEM_UPTIME_SECS: f32 = 15.0; const DRM_EVENT_TIMEOUT_PERIOD_MS: u64 = 100; diff --git a/lact-daemon/src/server.rs b/lact-daemon/src/server.rs index 91181cd2..3c3785d2 100644 --- a/lact-daemon/src/server.rs +++ b/lact-daemon/src/server.rs @@ -2,11 +2,10 @@ pub mod gpu_controller; pub mod handler; mod opencl; mod profiles; -pub(crate) mod system; mod vulkan; use self::handler::Handler; -use crate::{config::Config, socket}; +use crate::{config::Config, socket, system}; use anyhow::Context; use futures::future::join_all; use lact_schema::{Pong, Request, Response}; @@ -39,6 +38,8 @@ impl Server { None }; + system::power_profiles_daemon::setup().await; + let handler = Handler::new(config).await?; socket::set_permissions(&socket_path, &handler.config.read().await.daemon)?; diff --git a/lact-daemon/src/server/gpu_controller/intel.rs b/lact-daemon/src/server/gpu_controller/intel.rs index 0c52fe13..c0bec0f9 100644 --- a/lact-daemon/src/server/gpu_controller/intel.rs +++ b/lact-daemon/src/server/gpu_controller/intel.rs @@ -89,6 +89,7 @@ impl IntelGpuController { tile_gts.len(), common.sysfs_path.display() ); + tile_gts.sort(); } let drm_file = if cfg!(not(test)) { let drm_path = format!("/dev/dri/by-path/pci-{}-render", common.pci_slot_name); diff --git a/lact-daemon/src/server/system.rs b/lact-daemon/src/system.rs similarity index 99% rename from lact-daemon/src/server/system.rs rename to lact-daemon/src/system.rs index 2b04b92a..56aed5b3 100644 --- a/lact-daemon/src/server/system.rs +++ b/lact-daemon/src/system.rs @@ -1,3 +1,5 @@ +pub mod power_profiles_daemon; + use anyhow::{anyhow, ensure, Context}; use lact_schema::{InitramfsType, SystemInfo, GIT_COMMIT}; use nix::sys::socket::{ @@ -249,7 +251,7 @@ pub(crate) fn listen_netlink_kernel_event(notify: &Notify) -> anyhow::Result<()> #[cfg(test)] mod tests { - use crate::server::system::detect_initramfs_type; + use super::detect_initramfs_type; use lact_schema::InitramfsType; use os_release::OsRelease; diff --git a/lact-daemon/src/system/power_profiles_daemon.rs b/lact-daemon/src/system/power_profiles_daemon.rs new file mode 100644 index 00000000..e1e422ed --- /dev/null +++ b/lact-daemon/src/system/power_profiles_daemon.rs @@ -0,0 +1,117 @@ +use anyhow::{anyhow, Context}; +use tracing::{debug, error, info, warn}; +use zbus::{proxy, Connection}; + +const CONFLICTING_ACTIONS: [&str; 1] = ["amdgpu_dpm"]; +const MIN_PPD_MINOR_VERSION: u32 = 30; + +pub async fn setup() { + let conn = match Connection::system().await { + Ok(conn) => conn, + Err(err) => { + warn!("could not establish DBus connection: {err}"); + return; + } + }; + + match PowerProfilesDaemonProxy::new(&conn).await { + Ok(ppd_client) => { + if let Err(err) = disable_conflicting_actions(&ppd_client).await { + error!("power-profiles-daemon detected, but conflicting actions could not be disabled: {err:#}"); + } + } + Err(err) => { + debug!("could not connect to power-profiles-daemon: {err}"); + } + } +} + +async fn disable_conflicting_actions(client: &PowerProfilesDaemonProxy<'_>) -> anyhow::Result<()> { + let version = client.version().await?; + debug!("connected to power-profiles-daemon {version}"); + + let (_major, minor) = version + .split_once('.') + .with_context(|| format!("Could not parse version string '{version}'"))?; + let minor = minor + .parse::() + .context("Could not parse minor version")?; + + if minor < MIN_PPD_MINOR_VERSION { + return Err(anyhow!( + "daemon version {version} is older than minimum required for actions configuration" + )); + } + + let current_actions = client + .actions_info() + .await + .context("Could not list actions")?; + + for action_map in current_actions { + if let Some(name) = action_map + .get("Name") + .and_then(|value| value.downcast_ref::().ok()) + { + if CONFLICTING_ACTIONS.contains(&name.as_str()) { + match action_map + .get("Enabled") + .and_then(|enabled| enabled.downcast_ref::().ok()) + { + Some(enabled) => { + if enabled { + match client.set_action_enabled(&name, false).await { + Ok(()) => { + info!( + "disabled conflicting power-profiles-daemon action {name}" + ); + } + Err(err) => { + error!("could not disable conflicting power-profiles-daemon action {name}: {err}"); + } + } + } else { + info!("conflicting power-profiles-daemon action {name} is already disabled"); + } + } + None => { + error!("could not check status for power-profiles-daemon action {name}: {action_map:?}"); + } + } + } + } + } + + Ok(()) +} + +#[proxy( + interface = "org.freedesktop.UPower.PowerProfiles", + default_service = "org.freedesktop.UPower.PowerProfiles", + default_path = "/org/freedesktop/UPower/PowerProfiles" +)] +trait PowerProfilesDaemon { + /// SetActionEnabled method + fn set_action_enabled(&self, action: &str, enabled: bool) -> zbus::Result<()>; + + /// Actions property + #[zbus(property)] + fn actions(&self) -> zbus::Result>; + + /// ActionsInfo property + #[zbus(property)] + fn actions_info( + &self, + ) -> zbus::Result>>; + + /// ActiveProfile property + #[zbus(property)] + fn active_profile(&self) -> zbus::Result; + + #[zbus(property)] + fn set_active_profile(&self, value: &str) -> zbus::Result<()>; + + /// Version property + #[zbus(property)] + fn version(&self) -> zbus::Result; +}