From 5a7e55047559bfc25134dea3fbb82de568d1c0a1 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 1 Jul 2026 08:50:42 +0200 Subject: [PATCH 1/2] fix: sync Intel WMI CPU power limits to RAPL powercap interfaces --- rog-platform/src/asus_armoury.rs | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/rog-platform/src/asus_armoury.rs b/rog-platform/src/asus_armoury.rs index 1265b518..b3e49ef0 100644 --- a/rog-platform/src/asus_armoury.rs +++ b/rog-platform/src/asus_armoury.rs @@ -53,6 +53,72 @@ pub struct Attribute { base_path: PathBuf, } +fn is_intel_cpu() -> bool { + std::fs::read_to_string("/proc/cpuinfo") + .map(|content| content.contains("GenuineIntel")) + .unwrap_or(false) +} + +fn write_intel_rapl_limit(constraint_idx: u32, watts: i32) { + let microwatts = (watts as u64) * 1_000_000; + let microwatts_str = microwatts.to_string(); + + fn walk_dir(dir: &Path, constraint_idx: u32, val_str: &str) { + if let Ok(entries) = read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + walk_dir(&path, constraint_idx, val_str); + } else if path.is_file() { + if let Some(file_name) = path.file_name() { + let expected_name = format!("constraint_{}_power_limit_uw", constraint_idx); + if file_name == expected_name.as_str() { + let path_str = path.to_string_lossy(); + if path_str.contains("intel-rapl") { + debug!("Writing {} to {}", val_str, path_str); + if let Ok(mut file) = OpenOptions::new().write(true).open(&path) { + let _ = file.write_all(val_str.as_bytes()); + } + } + } + } + } + } + } + } + + walk_dir( + Path::new("/sys/class/powercap"), + constraint_idx, + µwatts_str, + ); +} + +fn sync_all_intel_rapl_limits() { + if !is_intel_cpu() { + return; + } + + let base_dir = Path::new("/sys/class/firmware-attributes/asus-armoury/attributes"); + + let limits = [ + ("ppt_pl1_spl", 0), + ("ppt_pl2_sppt", 1), + ("ppt_pl3_fppt", 2), + ]; + + for (attr_name, constraint_idx) in &limits { + let current_value_path = base_dir.join(attr_name).join("current_value"); + if current_value_path.exists() { + if let Ok(val_str) = std::fs::read_to_string(¤t_value_path) { + if let Ok(watts) = val_str.trim().parse::() { + write_intel_rapl_limit(*constraint_idx, watts); + } + } + } + } +} + impl Attribute { pub fn name(&self) -> &str { &self.name @@ -97,6 +163,12 @@ impl Attribute { let mut file = OpenOptions::new().write(true).open(&path)?; file.write_all(value_str.as_bytes())?; + + if self.name == "ppt_pl1_spl" || self.name == "ppt_pl2_sppt" || self.name == "ppt_pl3_fppt" + { + sync_all_intel_rapl_limits(); + } + Ok(()) } From 28dffa0b9fb0f77673716ed96fca461c0a9d9565 Mon Sep 17 00:00:00 2001 From: Deniz Date: Mon, 6 Jul 2026 14:45:58 -0400 Subject: [PATCH 2/2] fix(intel-rapl): write to top level zones directly instead of walk The recursive walk caused the asusd service to indefinitely loop by following symlinks. Instead just write to the top level package zones directly. --- rog-platform/src/asus_armoury.rs | 49 +++++++++++++------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/rog-platform/src/asus_armoury.rs b/rog-platform/src/asus_armoury.rs index b3e49ef0..678d2307 100644 --- a/rog-platform/src/asus_armoury.rs +++ b/rog-platform/src/asus_armoury.rs @@ -60,38 +60,29 @@ fn is_intel_cpu() -> bool { } fn write_intel_rapl_limit(constraint_idx: u32, watts: i32) { - let microwatts = (watts as u64) * 1_000_000; - let microwatts_str = microwatts.to_string(); - - fn walk_dir(dir: &Path, constraint_idx: u32, val_str: &str) { - if let Ok(entries) = read_dir(dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - walk_dir(&path, constraint_idx, val_str); - } else if path.is_file() { - if let Some(file_name) = path.file_name() { - let expected_name = format!("constraint_{}_power_limit_uw", constraint_idx); - if file_name == expected_name.as_str() { - let path_str = path.to_string_lossy(); - if path_str.contains("intel-rapl") { - debug!("Writing {} to {}", val_str, path_str); - if let Ok(mut file) = OpenOptions::new().write(true).open(&path) { - let _ = file.write_all(val_str.as_bytes()); - } - } - } - } - } + let microwatts_str = ((watts as u64) * 1_000_000).to_string(); + let file_name = format!("constraint_{}_power_limit_uw", constraint_idx); + let Ok(entries) = read_dir("/sys/class/powercap") else { + return; + }; + for entry in entries.flatten() { + let zone = entry.file_name(); + let zone = zone.to_string_lossy(); + // A package zone name has exactly one ':' ("intel-rapl:0"). Sub-zones have two ("intel-rapl:0:0"). + let is_package_zone = (zone.starts_with("intel-rapl:") + || zone.starts_with("intel-rapl-mmio:")) + && zone.matches(':').count() == 1; + if !is_package_zone { + continue; + } + let path = entry.path().join(&file_name); + if path.exists() { + debug!("Writing {microwatts_str} to {}", path.display()); + if let Ok(mut file) = OpenOptions::new().write(true).open(&path) { + let _ = file.write_all(microwatts_str.as_bytes()); } } } - - walk_dir( - Path::new("/sys/class/powercap"), - constraint_idx, - µwatts_str, - ); } fn sync_all_intel_rapl_limits() {