mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
power_supply&cpu: use objects
This commit is contained in:
parent
6377480312
commit
6ef4da9113
3 changed files with 473 additions and 525 deletions
557
src/cpu.rs
557
src/cpu.rs
|
@ -1,19 +1,6 @@
|
||||||
use anyhow::{Context, bail};
|
use anyhow::{Context, bail};
|
||||||
use derive_more::Display;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::{fs, io, path::Path, string::ToString};
|
use std::{fs, path::Path, string::ToString};
|
||||||
|
|
||||||
// // Valid EPP (Energy Performance Preference) string values.
|
|
||||||
// const EPP_FALLBACK_VALUES: &[&str] = &[
|
|
||||||
// "default",
|
|
||||||
// "performance",
|
|
||||||
// "balance-performance",
|
|
||||||
// "balance_performance", // Alternative form with underscore.
|
|
||||||
// "balance-power",
|
|
||||||
// "balance_power", // Alternative form with underscore.
|
|
||||||
// "power",
|
|
||||||
// ];
|
|
||||||
|
|
||||||
fn exists(path: impl AsRef<Path>) -> bool {
|
fn exists(path: impl AsRef<Path>) -> bool {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
@ -41,8 +28,24 @@ fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get real, tunable CPUs.
|
pub struct Cpu {
|
||||||
pub fn get_real_cpus() -> anyhow::Result<Vec<u32>> {
|
pub number: u32,
|
||||||
|
pub has_cpufreq: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cpu {
|
||||||
|
pub fn new(number: u32) -> anyhow::Result<Self> {
|
||||||
|
let mut cpu = Self {
|
||||||
|
number,
|
||||||
|
has_cpufreq: false,
|
||||||
|
};
|
||||||
|
cpu.rescan()?;
|
||||||
|
|
||||||
|
Ok(cpu)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all CPUs.
|
||||||
|
pub fn all() -> anyhow::Result<Vec<Cpu>> {
|
||||||
const PATH: &str = "/sys/devices/system/cpu";
|
const PATH: &str = "/sys/devices/system/cpu";
|
||||||
|
|
||||||
let mut cpus = vec![];
|
let mut cpus = vec![];
|
||||||
|
@ -62,55 +65,40 @@ pub fn get_real_cpus() -> anyhow::Result<Vec<u32>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Has to match "cpu{N}".
|
// Has to match "cpu{N}".
|
||||||
let Ok(cpu) = cpu_prefix_removed.parse::<u32>() else {
|
let Ok(number) = cpu_prefix_removed.parse::<u32>() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Has to match "cpu{N}/cpufreq".
|
cpus.push(Self::new(number)?);
|
||||||
if !entry.path().join("cpufreq").exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cpus.push(cpu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back if sysfs iteration above fails to find any cpufreq CPUs.
|
// Fall back if sysfs iteration above fails to find any cpufreq CPUs.
|
||||||
if cpus.is_empty() {
|
if cpus.is_empty() {
|
||||||
cpus = (0..num_cpus::get() as u32).collect();
|
for number in 0..num_cpus::get() as u32 {
|
||||||
|
cpus.push(Self::new(number)?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cpus)
|
Ok(cpus)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the governor for a CPU.
|
/// Rescan CPU, tuning local copy of settings.
|
||||||
pub fn set_governor(governor: &str, cpu: u32) -> anyhow::Result<()> {
|
pub fn rescan(&mut self) -> anyhow::Result<()> {
|
||||||
let governors = get_available_governors_for(cpu);
|
let has_cpufreq = exists(format!(
|
||||||
|
"/sys/devices/system/cpu/cpu{number}/cpufreq",
|
||||||
|
number = self.number,
|
||||||
|
));
|
||||||
|
|
||||||
if !governors
|
self.has_cpufreq = has_cpufreq;
|
||||||
.iter()
|
|
||||||
.any(|avail_governor| avail_governor == governor)
|
Ok(())
|
||||||
{
|
|
||||||
bail!(
|
|
||||||
"governor '{governor}' is not available for CPU {cpu}. valid governors: {governors}",
|
|
||||||
governors = governors.join(", "),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write(
|
pub fn get_available_governors(&self) -> Vec<String> {
|
||||||
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_governor"),
|
let Self { number, .. } = self;
|
||||||
governor,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"this probably means that CPU {cpu} doesn't exist or doesn't support changing governors"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get available CPU governors for a CPU.
|
|
||||||
fn get_available_governors_for(cpu: u32) -> Vec<String> {
|
|
||||||
let Ok(content) = fs::read_to_string(format!(
|
let Ok(content) = fs::read_to_string(format!(
|
||||||
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_available_governors"
|
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors"
|
||||||
)) else {
|
)) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
@ -121,21 +109,208 @@ fn get_available_governors_for(cpu: u32) -> Vec<String> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, clap::ValueEnum)]
|
pub fn set_governor(&self, governor: &str) -> anyhow::Result<()> {
|
||||||
pub enum Turbo {
|
let Self { number, .. } = self;
|
||||||
Always,
|
|
||||||
Never,
|
let governors = self.get_available_governors();
|
||||||
|
|
||||||
|
if !governors
|
||||||
|
.iter()
|
||||||
|
.any(|avail_governor| avail_governor == governor)
|
||||||
|
{
|
||||||
|
bail!(
|
||||||
|
"governor '{governor}' is not available for CPU {number}. available governors: {governors}",
|
||||||
|
governors = governors.join(", "),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
|
write(
|
||||||
let value_boost = match setting {
|
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor"),
|
||||||
Turbo::Always => "1", // boost = 1 means turbo is enabled.
|
governor,
|
||||||
Turbo::Never => "0", // boost = 0 means turbo is disabled.
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"this probably means that CPU {number} doesn't exist or doesn't support changing governors"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_available_epps(&self) -> Vec<String> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
let Ok(content) = fs::read_to_string(format!(
|
||||||
|
"/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences"
|
||||||
|
)) else {
|
||||||
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_boost_negated = match setting {
|
content
|
||||||
Turbo::Always => "0", // no_turbo = 0 means turbo is enabled.
|
.split_whitespace()
|
||||||
Turbo::Never => "1", // no_turbo = 1 means turbo is disabled.
|
.map(ToString::to_string)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_epp(&self, epp: &str) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
let epps = self.get_available_epps();
|
||||||
|
|
||||||
|
if !epps.iter().any(|avail_epp| avail_epp == epp) {
|
||||||
|
bail!(
|
||||||
|
"EPP value '{epp}' is not availabile for CPU {number}. available EPP values: {epps}",
|
||||||
|
epps = epps.join(", "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(
|
||||||
|
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference"),
|
||||||
|
epp,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"this probably means that CPU {number} doesn't exist or doesn't support changing EPP"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_available_epbs(&self) -> &'static [&'static str] {
|
||||||
|
if !self.has_cpufreq {
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
|
||||||
|
&[
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"10",
|
||||||
|
"11",
|
||||||
|
"12",
|
||||||
|
"13",
|
||||||
|
"14",
|
||||||
|
"15",
|
||||||
|
"performance",
|
||||||
|
"balance-performance",
|
||||||
|
"balance_performance", // Alternative form with underscore.
|
||||||
|
"balance-power",
|
||||||
|
"balance_power", // Alternative form with underscore.
|
||||||
|
"power",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
let epbs = self.get_available_epbs();
|
||||||
|
|
||||||
|
if !epbs.contains(&epb) {
|
||||||
|
bail!(
|
||||||
|
"EPB value '{epb}' is not available for CPU {number}. available EPB values: {valid}",
|
||||||
|
valid = epbs.join(", "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(
|
||||||
|
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias"),
|
||||||
|
epb,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"this probably means that CPU {number} doesn't exist or doesn't support changing EPB"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_frequency_minimum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
self.validate_frequency_minimum(frequency_mhz)?;
|
||||||
|
|
||||||
|
// We use u64 for the intermediate calculation to prevent overflow
|
||||||
|
let frequency_khz = u64::from(frequency_mhz) * 1000;
|
||||||
|
let frequency_khz = frequency_khz.to_string();
|
||||||
|
|
||||||
|
write(
|
||||||
|
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"),
|
||||||
|
&frequency_khz,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("this probably means that CPU {number} doesn't exist or doesn't support changing minimum frequency")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
let Ok(minimum_frequency_khz) = read_u64(format!(
|
||||||
|
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
|
||||||
|
)) else {
|
||||||
|
// Just let it pass if we can't find anything.
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if new_frequency_mhz as u64 * 1000 < minimum_frequency_khz {
|
||||||
|
bail!(
|
||||||
|
"new minimum frequency ({new_frequency_mhz} MHz) cannot be lower than the minimum frequency ({} MHz) for CPU {number}",
|
||||||
|
minimum_frequency_khz / 1000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_frequency_maximum(&self, frequency_mhz: u64) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
self.validate_frequency_maximum(frequency_mhz)?;
|
||||||
|
|
||||||
|
// We use u64 for the intermediate calculation to prevent overflow
|
||||||
|
let frequency_khz = u64::from(frequency_mhz) * 1000;
|
||||||
|
let frequency_khz = frequency_khz.to_string();
|
||||||
|
|
||||||
|
write(
|
||||||
|
format!("/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq"),
|
||||||
|
&frequency_khz,
|
||||||
|
)
|
||||||
|
.with_context(|| {
|
||||||
|
format!("this probably means that CPU {number} doesn't exist or doesn't support changing maximum frequency")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
|
||||||
|
let Self { number, .. } = self;
|
||||||
|
|
||||||
|
let Ok(maximum_frequency_khz) = read_u64(format!(
|
||||||
|
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
|
||||||
|
)) else {
|
||||||
|
// Just let it pass if we can't find anything.
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if new_frequency_mhz * 1000 > maximum_frequency_khz {
|
||||||
|
bail!(
|
||||||
|
"new maximum frequency ({new_frequency_mhz} MHz) cannot be higher than the maximum frequency ({} MHz) for CPU {number}",
|
||||||
|
maximum_frequency_khz / 1000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_turbo(on: bool) -> anyhow::Result<()> {
|
||||||
|
let value_boost = match on {
|
||||||
|
true => "1", // boost = 1 means turbo is enabled.
|
||||||
|
false => "0", // boost = 0 means turbo is disabled.
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_boost_negated = match on {
|
||||||
|
true => "0", // no_turbo = 0 means turbo is enabled.
|
||||||
|
false => "1", // no_turbo = 1 means turbo is disabled.
|
||||||
};
|
};
|
||||||
|
|
||||||
// AMD specific paths
|
// AMD specific paths
|
||||||
|
@ -161,9 +336,11 @@ pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also try per-core cpufreq boost for some AMD systems.
|
// Also try per-core cpufreq boost for some AMD systems.
|
||||||
if get_real_cpus()?.iter().any(|cpu| {
|
if Self::all()?.iter().any(|cpu| {
|
||||||
|
let Cpu { number, .. } = cpu;
|
||||||
|
|
||||||
write(
|
write(
|
||||||
&format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/boost"),
|
&format!("/sys/devices/system/cpu/cpu{number}/cpufreq/boost"),
|
||||||
value_boost,
|
value_boost,
|
||||||
)
|
)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|
@ -173,262 +350,4 @@ pub fn set_turbo(setting: Turbo) -> anyhow::Result<()> {
|
||||||
|
|
||||||
bail!("no supported CPU boost control mechanism found");
|
bail!("no supported CPU boost control mechanism found");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_epp(epp: &str, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
// Validate the EPP value against available options
|
|
||||||
let epps = get_available_epps(cpu);
|
|
||||||
|
|
||||||
if !epps.iter().any(|avail_epp| avail_epp == epp) {
|
|
||||||
bail!(
|
|
||||||
"epp value '{epp}' is not availabile for CPU {cpu}. valid epp values: {epps}",
|
|
||||||
epps = epps.join(", "),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
write(
|
|
||||||
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_preference"),
|
|
||||||
epp,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing EPP")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get available EPP values for a CPU.
|
|
||||||
fn get_available_epps(cpu: u32) -> Vec<String> {
|
|
||||||
let Ok(content) = fs::read_to_string(format!(
|
|
||||||
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_available_preferences"
|
|
||||||
)) else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
content
|
|
||||||
.split_whitespace()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_epb(epb: &str, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
// Validate EPB value - should be a number 0-15 or a recognized string value.
|
|
||||||
validate_epb_value(epb)?;
|
|
||||||
|
|
||||||
write(
|
|
||||||
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/energy_performance_bias"),
|
|
||||||
epb,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing EPB")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_epb_value(epb: &str) -> anyhow::Result<()> {
|
|
||||||
// EPB can be a number from 0-15 or a recognized string.
|
|
||||||
|
|
||||||
const VALID_EPB_STRINGS: &[&str] = &[
|
|
||||||
"performance",
|
|
||||||
"balance-performance",
|
|
||||||
"balance_performance", // Alternative form with underscore.
|
|
||||||
"balance-power",
|
|
||||||
"balance_power", // Alternative form with underscore.
|
|
||||||
"power",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Try parsing as a number first.
|
|
||||||
if let Ok(value) = epb.parse::<u8>() {
|
|
||||||
if value <= 15 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("EPB numeric value must be between 0 and 15, got {value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a number, check if it's a recognized string value.
|
|
||||||
if VALID_EPB_STRINGS.contains(&epb) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!(
|
|
||||||
"invalid EPB value: '{epb}'. must be a number between 0-15 inclusive or one of: {valid}",
|
|
||||||
valid = VALID_EPB_STRINGS.join(", "),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_frequency_minimum(frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
validate_frequency_minimum(frequency_mhz, cpu)?;
|
|
||||||
|
|
||||||
// We use u64 for the intermediate calculation to prevent overflow
|
|
||||||
let frequency_khz = u64::from(frequency_mhz) * 1000;
|
|
||||||
let frequency_khz = frequency_khz.to_string();
|
|
||||||
|
|
||||||
write(
|
|
||||||
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"),
|
|
||||||
&frequency_khz,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing minimum frequency")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_frequency_maximum(frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
validate_max_frequency(frequency_mhz, cpu)?;
|
|
||||||
|
|
||||||
// We use u64 for the intermediate calculation to prevent overflow
|
|
||||||
let frequency_khz = u64::from(frequency_mhz) * 1000;
|
|
||||||
let frequency_khz = frequency_khz.to_string();
|
|
||||||
|
|
||||||
write(
|
|
||||||
format!("/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_max_freq"),
|
|
||||||
&frequency_khz,
|
|
||||||
)
|
|
||||||
.with_context(|| {
|
|
||||||
format!("this probably means that CPU {cpu} doesn't exist or doesn't support changing maximum frequency")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_frequency_minimum(new_frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
let Ok(minimum_frequency_khz) = read_u64(format!(
|
|
||||||
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"
|
|
||||||
)) else {
|
|
||||||
// Just let it pass if we can't find anything.
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if new_frequency_mhz as u64 * 1000 < minimum_frequency_khz {
|
|
||||||
bail!(
|
|
||||||
"new minimum frequency ({new_frequency_mhz} MHz) cannot be lower than the minimum frequency ({} MHz) for CPU {cpu}",
|
|
||||||
minimum_frequency_khz / 1000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_max_frequency(new_frequency_mhz: u64, cpu: u32) -> anyhow::Result<()> {
|
|
||||||
let Ok(maximum_frequency_khz) = read_u64(format!(
|
|
||||||
"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_min_freq"
|
|
||||||
)) else {
|
|
||||||
// Just let it pass if we can't find anything.
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if new_frequency_mhz * 1000 > maximum_frequency_khz {
|
|
||||||
bail!(
|
|
||||||
"new maximum frequency ({new_frequency_mhz} MHz) cannot be higher than the maximum frequency ({} MHz) for CPU {cpu}",
|
|
||||||
maximum_frequency_khz / 1000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the platform profile.
|
|
||||||
/// This changes the system performance, temperature, fan, and other hardware replated characteristics.
|
|
||||||
///
|
|
||||||
/// Also see [`The Kernel docs`] for this.
|
|
||||||
///
|
|
||||||
/// [`The Kernel docs`]: <https://docs.kernel.org/userspace-api/sysfs-platform_profile.html>
|
|
||||||
pub fn set_platform_profile(profile: &str) -> anyhow::Result<()> {
|
|
||||||
let profiles = get_platform_profiles();
|
|
||||||
|
|
||||||
if !profiles
|
|
||||||
.iter()
|
|
||||||
.any(|avail_profile| avail_profile == profile)
|
|
||||||
{
|
|
||||||
bail!(
|
|
||||||
"profile '{profile}' is not available for system. valid profiles: {profiles}",
|
|
||||||
profiles = profiles.join(", "),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
write("/sys/firmware/acpi/platform_profile", profile)
|
|
||||||
.context("this probably means that your system does not support changing ACPI profiles")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the list of available platform profiles.
|
|
||||||
pub fn get_platform_profiles() -> Vec<String> {
|
|
||||||
let path = "/sys/firmware/acpi/platform_profile_choices";
|
|
||||||
|
|
||||||
let Ok(content) = fs::read_to_string(path) else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
content
|
|
||||||
.split_whitespace()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Path for storing the governor override state.
|
|
||||||
const GOVERNOR_OVERRIDE_PATH: &str = "/etc/xdg/superfreq/governor_override";
|
|
||||||
|
|
||||||
#[derive(Display, Debug, Clone, Copy, clap::ValueEnum)]
|
|
||||||
pub enum GovernorOverride {
|
|
||||||
#[display("performance")]
|
|
||||||
Performance,
|
|
||||||
#[display("powersave")]
|
|
||||||
Powersave,
|
|
||||||
#[display("reset")]
|
|
||||||
Reset,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_governor_override(mode: GovernorOverride) -> anyhow::Result<()> {
|
|
||||||
let parent = Path::new(GOVERNOR_OVERRIDE_PATH).parent().unwrap();
|
|
||||||
if !parent.exists() {
|
|
||||||
fs::create_dir_all(parent).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to create directory '{path}'",
|
|
||||||
path = parent.display(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
match mode {
|
|
||||||
GovernorOverride::Reset => {
|
|
||||||
// Remove the override file if it exists
|
|
||||||
let result = fs::remove_file(GOVERNOR_OVERRIDE_PATH);
|
|
||||||
|
|
||||||
if let Err(error) = result {
|
|
||||||
if error.kind() != io::ErrorKind::NotFound {
|
|
||||||
return Err(error).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to delete governor override file '{GOVERNOR_OVERRIDE_PATH}'"
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
"governor override has been deleted. normal profile-based settings will be used"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
GovernorOverride::Performance | GovernorOverride::Powersave => {
|
|
||||||
let governor = mode.to_string();
|
|
||||||
|
|
||||||
write(GOVERNOR_OVERRIDE_PATH, &governor)
|
|
||||||
.context("failed to write governor override")?;
|
|
||||||
|
|
||||||
// TODO: Apply the setting too.
|
|
||||||
|
|
||||||
log::info!(
|
|
||||||
"governor override set to '{governor}'. this setting will persist across reboots"
|
|
||||||
);
|
|
||||||
log::info!("to reset, run: superfreq set --governor-persist reset");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current governor override if set.
|
|
||||||
pub fn get_governor_override() -> anyhow::Result<Option<String>> {
|
|
||||||
match fs::read_to_string(GOVERNOR_OVERRIDE_PATH) {
|
|
||||||
Ok(governor_override) => Ok(Some(governor_override)),
|
|
||||||
|
|
||||||
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
|
|
||||||
|
|
||||||
Err(error) => Err(error).with_context(|| {
|
|
||||||
format!("failed to read governor override at '{GOVERNOR_OVERRIDE_PATH}'")
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
36
src/main.rs
36
src/main.rs
|
@ -61,7 +61,7 @@ enum Command {
|
||||||
|
|
||||||
/// Set turbo boost behaviour. Has to be for all CPUs.
|
/// Set turbo boost behaviour. Has to be for all CPUs.
|
||||||
#[arg(short = 't', long, conflicts_with = "for_")]
|
#[arg(short = 't', long, conflicts_with = "for_")]
|
||||||
turbo: Option<cpu::Turbo>,
|
turbo: Option<bool>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Modify power supply attributes.
|
/// Modify power supply attributes.
|
||||||
|
@ -113,34 +113,42 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
turbo,
|
turbo,
|
||||||
} => {
|
} => {
|
||||||
let cpus = match for_ {
|
let cpus = match for_ {
|
||||||
Some(cpus) => cpus,
|
Some(numbers) => {
|
||||||
None => cpu::get_real_cpus()?,
|
let mut cpus = Vec::with_capacity(numbers.len());
|
||||||
|
|
||||||
|
for number in numbers {
|
||||||
|
cpus.push(cpu::Cpu::new(number)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
cpus
|
||||||
|
}
|
||||||
|
None => cpu::Cpu::all()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
for cpu in cpus {
|
for cpu in cpus {
|
||||||
if let Some(governor) = governor.as_ref() {
|
if let Some(governor) = governor.as_ref() {
|
||||||
cpu::set_governor(governor, cpu)?;
|
cpu.set_governor(governor)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(epp) = energy_performance_preference.as_ref() {
|
if let Some(epp) = energy_performance_preference.as_ref() {
|
||||||
cpu::set_epp(epp, cpu)?;
|
cpu.set_epp(epp)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(epb) = energy_performance_bias.as_ref() {
|
if let Some(epb) = energy_performance_bias.as_ref() {
|
||||||
cpu::set_epb(epb, cpu)?;
|
cpu.set_epb(epb)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mhz_minimum) = frequency_mhz_minimum {
|
if let Some(mhz_minimum) = frequency_mhz_minimum {
|
||||||
cpu::set_frequency_minimum(mhz_minimum, cpu)?;
|
cpu.set_frequency_minimum(mhz_minimum)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mhz_maximum) = frequency_mhz_maximum {
|
if let Some(mhz_maximum) = frequency_mhz_maximum {
|
||||||
cpu::set_frequency_maximum(mhz_maximum, cpu)?;
|
cpu.set_frequency_maximum(mhz_maximum)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(turbo) = turbo {
|
if let Some(turbo) = turbo {
|
||||||
cpu::set_turbo(turbo)?;
|
cpu::Cpu::set_turbo(turbo)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -157,13 +165,13 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
let power_supplies = Vec::with_capacity(names.len());
|
let power_supplies = Vec::with_capacity(names.len());
|
||||||
|
|
||||||
for name in names {
|
for name in names {
|
||||||
power_supplies.push(power_supply::get_power_supply(&name)?);
|
power_supplies.push(power_supply::PowerSupply::from_name(name)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
power_supplies
|
power_supplies
|
||||||
}
|
}
|
||||||
|
|
||||||
None => power_supply::get_power_supplies()?
|
None => power_supply::PowerSupply::all()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|power_supply| power_supply.threshold_config.is_some())
|
.filter(|power_supply| power_supply.threshold_config.is_some())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -171,16 +179,16 @@ fn real_main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
for power_supply in power_supplies {
|
for power_supply in power_supplies {
|
||||||
if let Some(threshold_start) = charge_threshold_start {
|
if let Some(threshold_start) = charge_threshold_start {
|
||||||
power_supply::set_charge_threshold_start(&power_supply, threshold_start)?;
|
power_supply.set_charge_threshold_start(threshold_start)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(threshold_end) = charge_threshold_end {
|
if let Some(threshold_end) = charge_threshold_end {
|
||||||
power_supply::set_charge_threshold_end(&power_supply, threshold_end)?;
|
power_supply.set_charge_threshold_end(threshold_end)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(platform_profile) = platform_profile.as_ref() {
|
if let Some(platform_profile) = platform_profile.as_ref() {
|
||||||
cpu::set_platform_profile(platform_profile)?;
|
power_supply::PowerSupply::set_platform_profile(platform_profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
use anyhow::Context;
|
use anyhow::{Context, bail};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
os::macos::fs::MetadataExt,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Migrate to central utils file. Same exists in cpu.rs.
|
||||||
|
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
fs::write(path, value).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to write '{value}' to '{path}'",
|
||||||
|
path = path.display(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a pattern of path suffixes used to control charge thresholds
|
/// Represents a pattern of path suffixes used to control charge thresholds
|
||||||
/// for different device vendors.
|
/// for different device vendors.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -50,7 +61,12 @@ pub struct PowerSupply {
|
||||||
|
|
||||||
impl fmt::Display for PowerSupply {
|
impl fmt::Display for PowerSupply {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "power supply '{name}'", name = &self.name)?;
|
write!(
|
||||||
|
f,
|
||||||
|
"power supply '{name}' at '{path}'",
|
||||||
|
name = &self.name,
|
||||||
|
path = self.path.display(),
|
||||||
|
)?;
|
||||||
|
|
||||||
if let Some(config) = self.threshold_config.as_ref() {
|
if let Some(config) = self.threshold_config.as_ref() {
|
||||||
write!(
|
write!(
|
||||||
|
@ -64,74 +80,42 @@ impl fmt::Display for PowerSupply {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PowerSupply {
|
|
||||||
pub fn charge_threshold_path_start(&self) -> Option<PathBuf> {
|
|
||||||
self.threshold_config
|
|
||||||
.map(|config| self.path.join(config.path_start))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn charge_threshold_path_end(&self) -> Option<PathBuf> {
|
|
||||||
self.threshold_config
|
|
||||||
.map(|config| self.path.join(config.path_end))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Migrate to central utils file. Same exists in cpu.rs.
|
|
||||||
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
|
|
||||||
fs::write(path, value).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to write '{value}' to '{path}'",
|
|
||||||
path = path.display(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_battery(path: &Path) -> anyhow::Result<bool> {
|
|
||||||
let type_path = path.join("type");
|
|
||||||
|
|
||||||
let type_ = fs::read_to_string(&type_path)
|
|
||||||
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
|
|
||||||
|
|
||||||
Ok(type_ == "Battery")
|
|
||||||
}
|
|
||||||
|
|
||||||
const POWER_SUPPLY_PATH: &str = "/sys/class/power_supply";
|
const POWER_SUPPLY_PATH: &str = "/sys/class/power_supply";
|
||||||
|
|
||||||
/// Get power supply.
|
impl PowerSupply {
|
||||||
pub fn get_power_supply(name: &str) -> anyhow::Result<PowerSupply> {
|
pub fn from_name(name: String) -> anyhow::Result<Self> {
|
||||||
let entry_path = Path::new(POWER_SUPPLY_PATH).join(name);
|
let mut power_supply = Self {
|
||||||
|
path: Path::new(POWER_SUPPLY_PATH).join(&name),
|
||||||
|
name,
|
||||||
|
threshold_config: None,
|
||||||
|
};
|
||||||
|
|
||||||
let threshold_config = is_battery(&entry_path)
|
power_supply.rescan()?;
|
||||||
|
|
||||||
|
Ok(power_supply)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_path(path: PathBuf) -> anyhow::Result<Self> {
|
||||||
|
let mut power_supply = PowerSupply {
|
||||||
|
name: path
|
||||||
|
.file_name()
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!("failed to get file name of '{path}'", path = path.display(),)
|
||||||
"failed to determine what type of power supply '{path}' is",
|
|
||||||
path = entry_path.display(),
|
|
||||||
)
|
|
||||||
})?
|
})?
|
||||||
.then(|| {
|
.to_string_lossy()
|
||||||
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
.to_string(),
|
||||||
if entry_path.join(config.path_start).exists()
|
|
||||||
&& entry_path.join(config.path_end).exists()
|
path,
|
||||||
{
|
|
||||||
return Some(*config);
|
threshold_config: None,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
power_supply.rescan()?;
|
||||||
|
|
||||||
|
Ok(power_supply)
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
pub fn all() -> anyhow::Result<Vec<PowerSupply>> {
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
Ok(PowerSupply {
|
|
||||||
name: name.to_owned(),
|
|
||||||
path: entry_path,
|
|
||||||
threshold_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all power supplies.
|
|
||||||
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
|
||||||
let mut power_supplies = Vec::new();
|
let mut power_supplies = Vec::new();
|
||||||
|
|
||||||
for entry in fs::read_dir(POWER_SUPPLY_PATH)
|
for entry in fs::read_dir(POWER_SUPPLY_PATH)
|
||||||
|
@ -146,83 +130,120 @@ pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let entry_path = entry.path();
|
power_supplies.push(PowerSupply::from_path(entry.path())?);
|
||||||
|
|
||||||
let mut power_supply_config = None;
|
|
||||||
|
|
||||||
if is_battery(&entry_path).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to determine what type of power supply '{path}' is",
|
|
||||||
path = entry_path.display(),
|
|
||||||
)
|
|
||||||
})? {
|
|
||||||
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
|
||||||
if entry_path.join(config.path_start).exists()
|
|
||||||
&& entry_path.join(config.path_end).exists()
|
|
||||||
{
|
|
||||||
power_supply_config = Some(*config);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
power_supplies.push(PowerSupply {
|
|
||||||
name: entry_path
|
|
||||||
.file_name()
|
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"failed to get file name of '{path}'",
|
|
||||||
path = entry_path.display(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
|
|
||||||
path: entry_path,
|
|
||||||
|
|
||||||
threshold_config: power_supply_config,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(power_supplies)
|
Ok(power_supplies)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_charge_threshold_start(
|
fn is_battery(&self) -> anyhow::Result<bool> {
|
||||||
power_supply: &PowerSupply,
|
let type_path = self.path.join("type");
|
||||||
charge_threshold_start: u8,
|
|
||||||
) -> anyhow::Result<()> {
|
let type_ = fs::read_to_string(&type_path)
|
||||||
|
.with_context(|| format!("failed to read '{path}'", path = type_path.display()))?;
|
||||||
|
|
||||||
|
Ok(type_ == "Battery")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rescan(&mut self) -> anyhow::Result<()> {
|
||||||
|
let threshold_config = self
|
||||||
|
.is_battery()
|
||||||
|
.with_context(|| format!("failed to determine what type of power supply '{self}' is"))?
|
||||||
|
.then(|| {
|
||||||
|
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
|
||||||
|
if self.path.join(config.path_start).exists()
|
||||||
|
&& self.path.join(config.path_end).exists()
|
||||||
|
{
|
||||||
|
return Some(*config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
self.threshold_config = threshold_config;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn charge_threshold_path_start(&self) -> Option<PathBuf> {
|
||||||
|
self.threshold_config
|
||||||
|
.map(|config| self.path.join(config.path_start))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn charge_threshold_path_end(&self) -> Option<PathBuf> {
|
||||||
|
self.threshold_config
|
||||||
|
.map(|config| self.path.join(config.path_end))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_charge_threshold_start(&self, charge_threshold_start: u8) -> anyhow::Result<()> {
|
||||||
write(
|
write(
|
||||||
&power_supply.charge_threshold_path_start().ok_or_else(|| {
|
&self.charge_threshold_path_start().ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"power supply '{name}' does not support changing charge threshold levels",
|
"power supply '{name}' does not support changing charge threshold levels",
|
||||||
name = power_supply.name,
|
name = self.name,
|
||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
&charge_threshold_start.to_string(),
|
&charge_threshold_start.to_string(),
|
||||||
)
|
)
|
||||||
.with_context(|| format!("failed to set charge threshold start for {power_supply}"))?;
|
.with_context(|| format!("failed to set charge threshold start for {self}"))?;
|
||||||
|
|
||||||
log::info!("set battery threshold start for {power_supply} to {charge_threshold_start}%");
|
log::info!("set battery threshold start for {self} to {charge_threshold_start}%");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_charge_threshold_end(
|
pub fn set_charge_threshold_end(&self, charge_threshold_end: u8) -> anyhow::Result<()> {
|
||||||
power_supply: &PowerSupply,
|
|
||||||
charge_threshold_end: u8,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
write(
|
write(
|
||||||
&power_supply.charge_threshold_path_end().ok_or_else(|| {
|
&self.charge_threshold_path_end().ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"power supply '{name}' does not support changing charge threshold levels",
|
"power supply '{name}' does not support changing charge threshold levels",
|
||||||
name = power_supply.name,
|
name = self.name,
|
||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
&charge_threshold_end.to_string(),
|
&charge_threshold_end.to_string(),
|
||||||
)
|
)
|
||||||
.with_context(|| format!("failed to set charge threshold end for {power_supply}"))?;
|
.with_context(|| format!("failed to set charge threshold end for {self}"))?;
|
||||||
|
|
||||||
log::info!("set battery threshold end for {power_supply} to {charge_threshold_end}%");
|
log::info!("set battery threshold end for {self} to {charge_threshold_end}%");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_available_platform_profiles() -> Vec<String> {
|
||||||
|
let path = "/sys/firmware/acpi/platform_profile_choices";
|
||||||
|
|
||||||
|
let Ok(content) = fs::read_to_string(path) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
content
|
||||||
|
.split_whitespace()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the platform profile.
|
||||||
|
/// This changes the system performance, temperature, fan, and other hardware replated characteristics.
|
||||||
|
///
|
||||||
|
/// Also see [`The Kernel docs`] for this.
|
||||||
|
///
|
||||||
|
/// [`The Kernel docs`]: <https://docs.kernel.org/userspace-api/sysfs-platform_profile.html>
|
||||||
|
pub fn set_platform_profile(profile: &str) -> anyhow::Result<()> {
|
||||||
|
let profiles = Self::get_available_platform_profiles();
|
||||||
|
|
||||||
|
if !profiles
|
||||||
|
.iter()
|
||||||
|
.any(|avail_profile| avail_profile == profile)
|
||||||
|
{
|
||||||
|
bail!(
|
||||||
|
"profile '{profile}' is not available for system. valid profiles: {profiles}",
|
||||||
|
profiles = profiles.join(", "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
write("/sys/firmware/acpi/platform_profile", profile)
|
||||||
|
.context("this probably means that your system does not support changing ACPI profiles")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue