From 0d43b94908dba1d7e5d5f02443f39354369274fc Mon Sep 17 00:00:00 2001 From: f4mrfaux Date: Mon, 22 Jun 2026 07:51:07 -0400 Subject: [PATCH] Fix: bidirectional Quiet<->LowPower fallback in platform_profile setters On hardware where the kernel ACPI platform_profile sysfs exposes only one of Quiet / LowPower for the same semantic profile (different ASUS DMI quirks expose different names), setting the absent variant fails with "platform_profile: (low-power) not supported" even when the equivalent IS in choices. The previous fallback in set_platform_profile_on_battery / set_platform_profile_on_ac only handled Quiet -> LowPower, and the bare set_platform_profile property had no fallback at all. This patch: - Adds bidirectional Quiet <-> LowPower substitution in set_platform_profile so user-facing CLI/D-Bus calls succeed regardless of which name the kernel exposes. - Extends the existing one-direction fallback in set_platform_profile_on_battery and set_platform_profile_on_ac to be bidirectional, so the canonical name is what gets persisted to /etc/asusd/asusd.ron. - Extends select_power_profile_for_source to normalize both directions at apply-time, fixing stale-config rehydration after reboot. EPP behavior is preserved: get_config_epp_for_throttle and impl From for CPUEPP already map both Quiet and LowPower to the same source (profile_quiet_epp / CPUEPP::Power), so substitution does not change EPP outcomes. Tested live on GA503QR (linux-g14 7.0.5, platform_profile_choices = quiet balanced performance) against a swap-in-place patched daemon: - asusctl profile set LowPower -> kernel writes 'quiet', active=Quiet (previously errored NotSupported) - asusctl profile set Quiet / Balanced / Performance unchanged - asusctl profile set LowPower -b / -a -> config persists 'Quiet' (the canonical name for this hardware), not LowPower - CLI parser still rejects bogus profile names at argument-parse time Closes gitlab #648 follow-up (the CLI-parsing portion was fixed by gitlab MR !226 / commit 9366b0e but the platform-aliasing portion at the D-Bus property layer was not addressed). Co-Authored-By: Claude Opus 4.7 (1M context) --- asusd/src/ctrl_platform.rs | 94 ++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/asusd/src/ctrl_platform.rs b/asusd/src/ctrl_platform.rs index de737f5e..e507ab58 100644 --- a/asusd/src/ctrl_platform.rs +++ b/asusd/src/ctrl_platform.rs @@ -264,25 +264,32 @@ impl CtrlPlatform { self.config.lock().await.platform_profile_on_battery }; - // Older configs may still contain Quiet on devices that only support LowPower. + // Configs may contain a profile name (Quiet or LowPower) that this + // platform does not expose via the kernel ACPI sysfs — different ASUS + // platforms expose one or the other for the same semantic profile. // Normalize at apply-time so AC/BAT transitions still work correctly. - if configured == PlatformProfile::Quiet { + let alias = match configured { + PlatformProfile::Quiet => Some(PlatformProfile::LowPower), + PlatformProfile::LowPower => Some(PlatformProfile::Quiet), + _ => None, + }; + if let Some(target) = alias { if let Ok(choices) = self.platform.get_platform_profile_choices() { - if !choices.contains(&PlatformProfile::Quiet) - && choices.contains(&PlatformProfile::LowPower) - { + if !choices.contains(&configured) && choices.contains(&target) { let mut cfg = self.config.lock().await; if power_plugged { - cfg.platform_profile_on_ac = PlatformProfile::LowPower; + cfg.platform_profile_on_ac = target; } else { - cfg.platform_profile_on_battery = PlatformProfile::LowPower; + cfg.platform_profile_on_battery = target; } cfg.write(); warn!( - "Configured profile Quiet is unavailable, falling back to LowPower for {}", + "Configured profile {:?} is unavailable, falling back to {:?} for {}", + configured, + target, if power_plugged { "AC" } else { "battery" } ); - return PlatformProfile::LowPower; + return target; } } } @@ -480,12 +487,36 @@ impl CtrlPlatform { self.config.lock().await.write(); let choices = self.platform.get_platform_profile_choices()?; - if !choices.contains(&policy) { - return Err(FdoErr::NotSupported(format!( - "RogPlatform: platform_profile: {} not supported", - policy - ))); - } + // Different ASUS platforms expose only one of Quiet / LowPower + // via the kernel ACPI platform_profile sysfs for the same + // semantic profile. Map the requested variant to the available + // one so user-facing setters succeed regardless of which name + // the kernel reports on this machine. Same bidirectional + // fallback is applied in set_platform_profile_on_battery / + // set_platform_profile_on_ac and at apply-time in + // select_power_profile_for_source. + let policy = if !choices.contains(&policy) { + match policy { + PlatformProfile::Quiet + if choices.contains(&PlatformProfile::LowPower) => + { + PlatformProfile::LowPower + } + PlatformProfile::LowPower + if choices.contains(&PlatformProfile::Quiet) => + { + PlatformProfile::Quiet + } + _ => { + return Err(FdoErr::NotSupported(format!( + "RogPlatform: platform_profile: {} not supported", + policy + ))); + } + } + } else { + policy + }; self.platform .set_platform_profile(policy.into()) @@ -525,13 +556,22 @@ impl CtrlPlatform { #[zbus(signal_context)] ctxt: SignalEmitter<'_>, policy: PlatformProfile, ) -> Result<(), FdoErr> { - // If the requested profile isn't available on this platform, and it's - // `Quiet`, fall back to `LowPower` so we don't write an unavailable - // profile into the config file. + // If the requested profile isn't available on this platform, fall + // back to the semantic equivalent (Quiet <-> LowPower) so we don't + // write an unavailable profile into the config file. Different ASUS + // platforms expose one or the other for the same semantic profile. let mut chosen = policy; if let Ok(choices) = self.platform.get_platform_profile_choices() { - if chosen == PlatformProfile::Quiet && !choices.contains(&PlatformProfile::Quiet) { - chosen = PlatformProfile::LowPower; + if !choices.contains(&chosen) { + if chosen == PlatformProfile::Quiet + && choices.contains(&PlatformProfile::LowPower) + { + chosen = PlatformProfile::LowPower; + } else if chosen == PlatformProfile::LowPower + && choices.contains(&PlatformProfile::Quiet) + { + chosen = PlatformProfile::Quiet; + } } } @@ -564,11 +604,19 @@ impl CtrlPlatform { #[zbus(signal_context)] ctxt: SignalEmitter<'_>, policy: PlatformProfile, ) -> Result<(), FdoErr> { - // Mirror the same fallback behavior for AC profile changes. + // Mirror the same bidirectional Quiet <-> LowPower fallback for AC. let mut chosen = policy; if let Ok(choices) = self.platform.get_platform_profile_choices() { - if chosen == PlatformProfile::Quiet && !choices.contains(&PlatformProfile::Quiet) { - chosen = PlatformProfile::LowPower; + if !choices.contains(&chosen) { + if chosen == PlatformProfile::Quiet + && choices.contains(&PlatformProfile::LowPower) + { + chosen = PlatformProfile::LowPower; + } else if chosen == PlatformProfile::LowPower + && choices.contains(&PlatformProfile::Quiet) + { + chosen = PlatformProfile::Quiet; + } } }