Skip to main content

scx_utils/
pm.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2
3// This software may be used and distributed according to the terms of the
4// GNU General Public License version 2.
5
6use anyhow::{anyhow, Result};
7use std::fs::{File, OpenOptions};
8use std::io::Write;
9use std::path::Path;
10
11/// Updates the global idle resume latency. When the returned file is closed the request is
12/// dropped. See the following kernel docs for more details:
13/// https://www.kernel.org/doc/html/latest/admin-guide/pm/cpuidle.html#power-management-quality-of-service-for-cpus
14pub fn update_global_idle_resume_latency(value_us: i32) -> Result<File> {
15    if value_us < 0 {
16        return Err(anyhow!("Latency value must be non-negative"));
17    }
18
19    let mut file = OpenOptions::new()
20        .write(true)
21        .open("/dev/cpu_dma_latency")?;
22    let bytes = value_us.to_le_bytes(); // Convert to little-endian bytes
23    file.write_all(&bytes)?;
24    Ok(file) // return file descriptor so it can be closed later
25}
26
27/// Updates per cpu idle resume latency.
28pub fn update_cpu_idle_resume_latency(cpu_num: usize, value_us: i32) -> Result<()> {
29    if value_us < 0 {
30        return Err(anyhow!("Latency value must be non-negative"));
31    }
32
33    let path = format!("/sys/devices/system/cpu/cpu{cpu_num}/power/pm_qos_resume_latency_us");
34
35    let mut file = File::create(Path::new(&path))?;
36    write!(file, "{value_us}")?;
37    Ok(())
38}
39
40/// Returns if idle resume latency is supported.
41pub fn cpu_idle_resume_latency_supported() -> bool {
42    std::fs::exists("/sys/devices/system/cpu/cpu0/power/pm_qos_resume_latency_us").unwrap_or(false)
43}
44
45const INTEL_UNCORE_FREQ_PATH: &str = "/sys/devices/system/cpu/intel_uncore_frequency";
46
47/// Returns if Intel uncore frequency control is supported.
48pub fn uncore_freq_supported() -> bool {
49    std::fs::exists(INTEL_UNCORE_FREQ_PATH).unwrap_or(false)
50}
51
52/// Gets the initial max uncore frequency for a package/die in kHz.
53pub fn get_uncore_max_freq_khz(package: u32, die: u32) -> Result<u32> {
54    let path = format!(
55        "{}/package_{:02}_die_{:02}/initial_max_freq_khz",
56        INTEL_UNCORE_FREQ_PATH, package, die
57    );
58    let content = std::fs::read_to_string(&path)?;
59    content
60        .trim()
61        .parse()
62        .map_err(|e| anyhow!("Failed to parse uncore freq: {}", e))
63}
64
65/// Gets the initial min uncore frequency for a package/die in kHz.
66pub fn get_uncore_min_freq_khz(package: u32, die: u32) -> Result<u32> {
67    let path = format!(
68        "{}/package_{:02}_die_{:02}/initial_min_freq_khz",
69        INTEL_UNCORE_FREQ_PATH, package, die
70    );
71    let content = std::fs::read_to_string(&path)?;
72    content
73        .trim()
74        .parse()
75        .map_err(|e| anyhow!("Failed to parse uncore freq: {}", e))
76}
77
78/// Sets the max uncore frequency for a package/die in kHz.
79pub fn set_uncore_max_freq_khz(package: u32, die: u32, freq_khz: u32) -> Result<()> {
80    let path = format!(
81        "{}/package_{:02}_die_{:02}/max_freq_khz",
82        INTEL_UNCORE_FREQ_PATH, package, die
83    );
84    let mut file = File::create(Path::new(&path))?;
85    write!(file, "{freq_khz}")?;
86    Ok(())
87}
88
89/// Iterates over all package/die combinations and applies a function.
90pub fn for_each_uncore_domain<F>(mut f: F) -> Result<()>
91where
92    F: FnMut(u32, u32) -> Result<()>,
93{
94    let entries = std::fs::read_dir(INTEL_UNCORE_FREQ_PATH)?;
95    for entry in entries {
96        let entry = entry?;
97        let name = entry.file_name();
98        let name = name.to_string_lossy();
99        if let Some(rest) = name.strip_prefix("package_") {
100            let parts: Vec<&str> = rest.split("_die_").collect();
101            if parts.len() == 2 {
102                if let (Ok(pkg), Ok(die)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) {
103                    f(pkg, die)?;
104                }
105            }
106        }
107    }
108    Ok(())
109}
110
111const INTEL_PSTATE_PATH: &str = "/sys/devices/system/cpu/intel_pstate";
112
113/// Returns if Intel pstate turbo control is supported.
114pub fn turbo_supported() -> bool {
115    std::fs::exists(format!("{}/no_turbo", INTEL_PSTATE_PATH)).unwrap_or(false)
116}
117
118/// Gets current turbo state (true = turbo enabled).
119pub fn get_turbo_enabled() -> Result<bool> {
120    let content = std::fs::read_to_string(format!("{}/no_turbo", INTEL_PSTATE_PATH))?;
121    Ok(content.trim() == "0")
122}
123
124/// Sets turbo state (true = enable turbo).
125pub fn set_turbo_enabled(enabled: bool) -> Result<()> {
126    let value = if enabled { "0" } else { "1" };
127    std::fs::write(format!("{}/no_turbo", INTEL_PSTATE_PATH), value)?;
128    Ok(())
129}
130
131/// Returns if EPP (Energy Performance Preference) is supported.
132pub fn epp_supported() -> bool {
133    std::fs::exists("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference")
134        .unwrap_or(false)
135}
136
137/// Gets EPP for a CPU.
138pub fn get_epp(cpu: usize) -> Result<String> {
139    let path = format!(
140        "/sys/devices/system/cpu/cpu{}/cpufreq/energy_performance_preference",
141        cpu
142    );
143    Ok(std::fs::read_to_string(&path)?.trim().to_string())
144}
145
146/// Sets EPP for a CPU. Valid values: default, performance, balance_performance, balance_power, power
147pub fn set_epp(cpu: usize, epp: &str) -> Result<()> {
148    let path = format!(
149        "/sys/devices/system/cpu/cpu{}/cpufreq/energy_performance_preference",
150        cpu
151    );
152    std::fs::write(&path, epp)?;
153    Ok(())
154}