From 27d64bd971c70c5244bcdf8e8b91729fa418b40f Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Fri, 31 Jul 2026 20:27:31 +0200 Subject: [PATCH 1/2] feat(asusd): add power profiles daemon conflict warning and disable EPP when active --- asusd/src/ctrl_platform.rs | 61 +++++++++++++++++++++++++----- asusd/src/daemon.rs | 77 +++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 10 deletions(-) diff --git a/asusd/src/ctrl_platform.rs b/asusd/src/ctrl_platform.rs index 3a9f2e24a..f7e709190 100644 --- a/asusd/src/ctrl_platform.rs +++ b/asusd/src/ctrl_platform.rs @@ -233,7 +233,46 @@ impl CtrlPlatform { } } - fn check_and_set_epp(&self, enegy_pref: CPUEPP, change_epp: bool) { + /// Check whether a power profiles daemon (net.hadess.PowerProfiles) owns its D-Bus name. + /// Returns `true` if ownership is confirmed or if an error occurs during the check (fail closed), + /// and `false` ONLY when ownership is confirmed absent. + async fn is_power_profiles_daemon_active(&self) -> bool { + let dbus_proxy = match zbus::fdo::DBusProxy::new(&self.connection).await { + Ok(proxy) => proxy, + Err(e) => { + error!("Failed to create D-Bus proxy for PowerProfiles ownership check: {e:?}"); + return true; + } + }; + + let ppd_name = match zbus::names::BusName::try_from("net.hadess.PowerProfiles") { + Ok(name) => name, + Err(e) => { + error!("Failed to parse PowerProfiles BusName: {e:?}"); + return true; + } + }; + + match dbus_proxy.name_has_owner(ppd_name).await { + Ok(true) => { + info!( + "Power profiles daemon is active on D-Bus; skipping asusd EPP / platform \ + profile setting to prevent conflict." + ); + true + } + Ok(false) => false, + Err(e) => { + error!("Failed to query D-Bus name_has_owner for net.hadess.PowerProfiles: {e:?}"); + true + } + } + } + + async fn check_and_set_epp(&self, enegy_pref: CPUEPP, change_epp: bool) { + if self.is_power_profiles_daemon_active().await { + return; + } if !change_epp { info!("ThrottlePolicy unlinked from EPP"); return; @@ -374,6 +413,10 @@ impl CtrlPlatform { return; } + if self.is_power_profiles_daemon_active().await { + return; + } + let throttle = self.select_power_profile_for_source(power_plugged).await; debug!("Setting {throttle:?} before EPP"); let epp = self.get_config_epp_for_throttle(throttle).await; @@ -381,7 +424,7 @@ impl CtrlPlatform { warn!("Failed to set platform profile {throttle:?} on AC/BAT change: {err}"); return; } - self.check_and_set_epp(epp, change_epp); + self.check_and_set_epp(epp, change_epp).await; } } @@ -506,7 +549,7 @@ impl CtrlPlatform { if self.platform.has_platform_profile() { let change_epp = self.config.lock().await.platform_profile_linked_epp; let epp = self.get_config_epp_for_throttle(policy).await; - self.check_and_set_epp(epp, change_epp); + self.check_and_set_epp(epp, change_epp).await; self.platform .set_platform_profile(policy.into()) .map_err(|err| { @@ -543,7 +586,7 @@ impl CtrlPlatform { if self.platform.has_platform_profile() { let change_epp = self.config.lock().await.platform_profile_linked_epp; let epp = self.get_config_epp_for_throttle(policy).await; - self.check_and_set_epp(epp, change_epp); + self.check_and_set_epp(epp, change_epp).await; self.config.lock().await.write(); @@ -669,7 +712,7 @@ impl CtrlPlatform { async fn set_profile_quiet_epp(&mut self, epp: CPUEPP) -> Result<(), FdoErr> { let change_pp = self.config.lock().await.platform_profile_linked_epp; self.config.lock().await.profile_quiet_epp = epp; - self.check_and_set_epp(epp, change_pp); + self.check_and_set_epp(epp, change_pp).await; self.config.lock().await.write(); Ok(()) } @@ -685,7 +728,7 @@ impl CtrlPlatform { async fn set_profile_balanced_epp(&mut self, epp: CPUEPP) -> Result<(), FdoErr> { let change_pp = self.config.lock().await.platform_profile_linked_epp; self.config.lock().await.profile_balanced_epp = epp; - self.check_and_set_epp(epp, change_pp); + self.check_and_set_epp(epp, change_pp).await; self.config.lock().await.write(); Ok(()) } @@ -701,7 +744,7 @@ impl CtrlPlatform { async fn set_profile_performance_epp(&mut self, epp: CPUEPP) -> Result<(), FdoErr> { let change_pp = self.config.lock().await.platform_profile_linked_epp; self.config.lock().await.profile_performance_epp = epp; - self.check_and_set_epp(epp, change_pp); + self.check_and_set_epp(epp, change_pp).await; self.config.lock().await.write(); Ok(()) @@ -864,7 +907,7 @@ impl ReloadAndNotify for CtrlPlatform { PlatformProfile::Custom => data.profile_custom_epp, }; warn!("setting epp to {epp:?}"); - self.check_and_set_epp(epp, true); + self.check_and_set_epp(epp, true).await; } // reload_and_notify!(platform_profile, "platform_profile"); @@ -1089,7 +1132,7 @@ impl CtrlTask for CtrlPlatform { { let change_epp = ctrl.config.lock().await.platform_profile_linked_epp; let epp = ctrl.get_config_epp_for_throttle(profile).await; - ctrl.check_and_set_epp(epp, change_epp); + ctrl.check_and_set_epp(epp, change_epp).await; ctrl.platform_profile_changed(&signal_ctxt_copy).await.ok(); ctrl.enable_ppt_group_changed(&signal_ctxt_copy).await.ok(); let power_plugged = ctrl diff --git a/asusd/src/daemon.rs b/asusd/src/daemon.rs index 02f4025a0..6af5e1532 100644 --- a/asusd/src/daemon.rs +++ b/asusd/src/daemon.rs @@ -127,7 +127,7 @@ async fn start_daemon() -> Result<(), Box> { } match CtrlPlatform::new( - platform, + platform.clone(), power, attributes, config.clone(), @@ -162,6 +162,81 @@ async fn start_daemon() -> Result<(), Box> { Err(e) => error!("XG Mobile LED: {e}"), } + // Check if a power profiles daemon is running while asusd profile controls are enabled + let profile_controls_enabled = { + let cfg = config.lock().await; + (cfg.platform_profile_linked_epp + || cfg.change_platform_profile_on_ac + || cfg.change_platform_profile_on_battery) + && platform.has_platform_profile() + }; + + if profile_controls_enabled { + if let Ok(dbus_proxy) = zbus::fdo::DBusProxy::new(&server).await { + let ppd_name = zbus::names::BusName::try_from("net.hadess.PowerProfiles"); + let ppd_owned = match ppd_name { + Ok(ref name) => dbus_proxy + .name_has_owner(name.clone()) + .await + .unwrap_or(false), + Err(_) => false, + }; + + if ppd_owned { + let mut provider_name = None; + if let Ok(ref name) = ppd_name { + if let Ok(unique_name) = dbus_proxy.get_name_owner(name.clone()).await { + if let Ok(pid) = dbus_proxy + .get_connection_unix_process_id(unique_name.into()) + .await + { + if let Ok(exe_path) = std::fs::read_link(format!("/proc/{pid}/exe")) { + if let Some(exe_name) = + exe_path.file_name().and_then(|n| n.to_str()) + { + provider_name = Some(exe_name.trim().to_string()); + } + } + } + } + } + + warn!( + "********************************************************************************" + ); + if let Some(ref provider) = provider_name { + warn!( + "WARN: Power profiles daemon provider '{provider}' is active on system D-Bus!" + ); + } else { + warn!("WARN: A power profiles daemon is active on system D-Bus!"); + } + warn!( + "Running a power profiles daemon concurrently with asusd causes EPP and \ + platform_profile contention." + ); + warn!("To allow asusd to manage profiles, EPP and fan curves, disable it."); + + if let Some(ref provider) = provider_name { + let service_name = if provider.ends_with(".service") { + provider.clone() + } else { + format!("{provider}.service") + }; + warn!(" sudo systemctl disable --now {service_name}"); + } else { + warn!( + " sudo systemctl disable --now power-profiles-daemon.service (or \ + equivalent service)" + ); + } + warn!( + "********************************************************************************" + ); + } + } + } + // Request dbus name after finishing initalizing all functions server.request_name(DBUS_NAME).await?; From 655999c390a9bbde790d6e8b8761502815b6cfcf Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Fri, 31 Jul 2026 20:27:36 +0200 Subject: [PATCH 2/2] docs(manual): update platform_profile documentation for power profiles daemon warning --- MANUAL.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/MANUAL.md b/MANUAL.md index 00a78669a..35be9f19f 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -71,7 +71,27 @@ These options are not written to the config file as they are stored in efivars. ### Profiles -asusctl can support setting a power profile via platform_profile drivers. This requires [power-profiles-daemon](https://gitlab.freedesktop.org/hadess/power-profiles-daemon) v0.10.0 minimum. It also requires the kernel patch for platform_profile support to be applied form [here](https://lkml.org/lkml/2021/8/18/1022) - this patch is merged to 5.15 kernel upstream. +asusctl supports setting power profiles via ACPI `platform_profile` drivers. + +> [!IMPORTANT] +> **Compatibility with Power Profiles Daemons**: +> Running an external power profiles daemon (such as `power-profiles-daemon` or `tuned`) concurrently with `asusd`'s profile management can cause race conditions and contention over `/sys/firmware/acpi/platform_profile` and CPU EPP preferences. +> +> You have two options for managing power profiles: +> +> 1. **Allow `asusd` to manage profiles**: Disable the conflicting service (e.g. `power-profiles-daemon`): +> +> ```bash +> sudo systemctl disable --now power-profiles-daemon.service +> ``` +> +> 2. **Allow an external daemon (PPD/Tuned) to manage profiles**: Keep `power-profiles-daemon` active, and disable `asusd`'s profile management settings in `/etc/asusd/asusd.ron`: +> +> ```ron +> change_platform_profile_on_ac: false, +> change_platform_profile_on_battery: false, +> platform_profile_linked_epp: false, +> ``` A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n` to cycle through the 3 profiles: