1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

power_supply: don't ignore non-batteries

This commit is contained in:
RGBCube 2025-05-18 23:38:44 +03:00
parent baef8af981
commit 6377480312
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M
2 changed files with 153 additions and 70 deletions

View file

@ -33,22 +33,22 @@ enum Command {
/// Start the daemon. /// Start the daemon.
Start, Start,
/// Modify attributes. /// Modify CPU attributes.
Set { CpuSet {
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs. /// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
#[arg(short = 'c', long = "for")] #[arg(short = 'c', long = "for")]
for_: Option<Vec<u32>>, for_: Option<Vec<u32>>,
/// Set the CPU governor. /// Set the CPU governor.
#[arg(long)] #[arg(short = 'g', long)]
governor: Option<String>, // TODO: Validate with clap for available governors. governor: Option<String>, // TODO: Validate with clap for available governors.
/// Set CPU Energy Performance Preference (EPP). Short form: --epp. /// Set CPU Energy Performance Preference (EPP). Short form: --epp.
#[arg(long, alias = "epp")] #[arg(short = 'p', long, alias = "epp")]
energy_performance_preference: Option<String>, energy_performance_preference: Option<String>,
/// Set CPU Energy Performance Bias (EPB). Short form: --epb. /// Set CPU Energy Performance Bias (EPB). Short form: --epb.
#[arg(long, alias = "epb")] #[arg(short = 'b', long, alias = "epb")]
energy_performance_bias: Option<String>, energy_performance_bias: Option<String>,
/// Set minimum CPU frequency in MHz. Short form: --freq-min. /// Set minimum CPU frequency in MHz. Short form: --freq-min.
@ -60,20 +60,27 @@ enum Command {
frequency_mhz_maximum: Option<u64>, frequency_mhz_maximum: Option<u64>,
/// Set turbo boost behaviour. Has to be for all CPUs. /// Set turbo boost behaviour. Has to be for all CPUs.
#[arg(long, conflicts_with = "for_")] #[arg(short = 't', long, conflicts_with = "for_")]
turbo: Option<cpu::Turbo>, turbo: Option<cpu::Turbo>,
},
/// Set ACPI platform profile. Has to be for all CPUs. /// Modify power supply attributes.
#[arg(long, alias = "profile", conflicts_with = "for_")] PowerSet {
platform_profile: Option<String>, /// The power supplies to apply the changes to. When unspecified, will be applied to all power supplies.
#[arg(short = 'p', long = "for")]
for_: Option<Vec<String>>,
/// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start. /// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start.
#[arg(short = 'p', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100), conflicts_with = "for_")] #[arg(short = 'c', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100))]
charge_threshold_start: Option<u8>, charge_threshold_start: Option<u8>,
/// Set the percentage where charging will stop. Short form: --charge-end. /// Set the percentage where charging will stop. Short form: --charge-end.
#[arg(short = 'P', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100), conflicts_with = "for_")] #[arg(short = 'C', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100))]
charge_threshold_end: Option<u8>, charge_threshold_end: Option<u8>,
/// Set ACPI platform profile. Has to be for all power supplies.
#[arg(short = 'f', long, alias = "profile", conflicts_with = "for_")]
platform_profile: Option<String>,
}, },
} }
@ -96,7 +103,7 @@ fn real_main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
Command::Set { Command::CpuSet {
for_, for_,
governor, governor,
energy_performance_preference, energy_performance_preference,
@ -104,9 +111,6 @@ fn real_main() -> anyhow::Result<()> {
frequency_mhz_minimum, frequency_mhz_minimum,
frequency_mhz_maximum, frequency_mhz_maximum,
turbo, turbo,
platform_profile,
charge_threshold_start,
charge_threshold_end,
} => { } => {
let cpus = match for_ { let cpus = match for_ {
Some(cpus) => cpus, Some(cpus) => cpus,
@ -139,11 +143,33 @@ fn real_main() -> anyhow::Result<()> {
cpu::set_turbo(turbo)?; cpu::set_turbo(turbo)?;
} }
if let Some(platform_profile) = platform_profile.as_ref() { Ok(())
cpu::set_platform_profile(platform_profile)?; }
}
for power_supply in power_supply::get_power_supplies()? { Command::PowerSet {
for_,
charge_threshold_start,
charge_threshold_end,
platform_profile,
} => {
let power_supplies = match for_ {
Some(names) => {
let power_supplies = Vec::with_capacity(names.len());
for name in names {
power_supplies.push(power_supply::get_power_supply(&name)?);
}
power_supplies
}
None => power_supply::get_power_supplies()?
.into_iter()
.filter(|power_supply| power_supply.threshold_config.is_some())
.collect(),
};
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(&power_supply, threshold_start)?;
} }
@ -153,6 +179,10 @@ fn real_main() -> anyhow::Result<()> {
} }
} }
if let Some(platform_profile) = platform_profile.as_ref() {
cpu::set_platform_profile(platform_profile)?;
}
Ok(()) Ok(())
} }
} }

View file

@ -2,38 +2,39 @@ use anyhow::Context;
use std::{ use std::{
fmt, fs, fmt, fs,
os::macos::fs::MetadataExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
/// 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)]
pub struct PowerSupplyConfig { pub struct PowerSupplyThresholdConfig {
pub manufacturer: &'static str, pub manufacturer: &'static str,
pub path_start: &'static str, pub path_start: &'static str,
pub path_end: &'static str, pub path_end: &'static str,
} }
/// Charge threshold configs. /// Power supply threshold configs.
const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[ const POWER_SUPPLY_THRESHOLD_CONFIGS: &[PowerSupplyThresholdConfig] = &[
PowerSupplyConfig { PowerSupplyThresholdConfig {
manufacturer: "Standard", manufacturer: "Standard",
path_start: "charge_control_start_threshold", path_start: "charge_control_start_threshold",
path_end: "charge_control_end_threshold", path_end: "charge_control_end_threshold",
}, },
PowerSupplyConfig { PowerSupplyThresholdConfig {
manufacturer: "ASUS", manufacturer: "ASUS",
path_start: "charge_control_start_percentage", path_start: "charge_control_start_percentage",
path_end: "charge_control_end_percentage", path_end: "charge_control_end_percentage",
}, },
// Combine Huawei and ThinkPad since they use identical paths. // Combine Huawei and ThinkPad since they use identical paths.
PowerSupplyConfig { PowerSupplyThresholdConfig {
manufacturer: "ThinkPad/Huawei", manufacturer: "ThinkPad/Huawei",
path_start: "charge_start_threshold", path_start: "charge_start_threshold",
path_end: "charge_stop_threshold", path_end: "charge_stop_threshold",
}, },
// Framework laptop support. // Framework laptop support.
PowerSupplyConfig { PowerSupplyThresholdConfig {
manufacturer: "Framework", manufacturer: "Framework",
path_start: "charge_behaviour_start_threshold", path_start: "charge_behaviour_start_threshold",
path_end: "charge_behaviour_end_threshold", path_end: "charge_behaviour_end_threshold",
@ -44,27 +45,34 @@ const POWER_SUPPLY_CONFIGS: &[PowerSupplyConfig] = &[
pub struct PowerSupply { pub struct PowerSupply {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,
pub config: PowerSupplyConfig, pub threshold_config: Option<PowerSupplyThresholdConfig>,
} }
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!( write!(f, "power supply '{name}'", name = &self.name)?;
f,
"power suppply '{name}' from manufacturer '{manufacturer}'", if let Some(config) = self.threshold_config.as_ref() {
name = &self.name, write!(
manufacturer = &self.config.manufacturer, f,
) " from manufacturer '{manufacturer}'",
manufacturer = config.manufacturer,
)?;
}
Ok(())
} }
} }
impl PowerSupply { impl PowerSupply {
pub fn charge_threshold_path_start(&self) -> PathBuf { pub fn charge_threshold_path_start(&self) -> Option<PathBuf> {
self.path.join(self.config.path_start) self.threshold_config
.map(|config| self.path.join(config.path_start))
} }
pub fn charge_threshold_path_end(&self) -> PathBuf { pub fn charge_threshold_path_end(&self) -> Option<PathBuf> {
self.path.join(self.config.path_end) self.threshold_config
.map(|config| self.path.join(config.path_end))
} }
} }
@ -80,7 +88,7 @@ fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
}) })
} }
fn is_power_supply(path: &Path) -> anyhow::Result<bool> { fn is_battery(path: &Path) -> anyhow::Result<bool> {
let type_path = path.join("type"); let type_path = path.join("type");
let type_ = fs::read_to_string(&type_path) let type_ = fs::read_to_string(&type_path)
@ -89,13 +97,46 @@ fn is_power_supply(path: &Path) -> anyhow::Result<bool> {
Ok(type_ == "Battery") Ok(type_ == "Battery")
} }
/// Get all batteries in the system that support threshold control. const POWER_SUPPLY_PATH: &str = "/sys/class/power_supply";
pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
const PATH: &str = "/sys/class/power_supply";
/// Get power supply.
pub fn get_power_supply(name: &str) -> anyhow::Result<PowerSupply> {
let entry_path = Path::new(POWER_SUPPLY_PATH).join(name);
let threshold_config = is_battery(&entry_path)
.with_context(|| {
format!(
"failed to determine what type of power supply '{path}' is",
path = entry_path.display(),
)
})?
.then(|| {
for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
if entry_path.join(config.path_start).exists()
&& entry_path.join(config.path_end).exists()
{
return Some(*config);
}
}
None
})
.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();
'entries: for entry in fs::read_dir(PATH).with_context(|| format!("failed to read '{PATH}'"))? { for entry in fs::read_dir(POWER_SUPPLY_PATH)
.with_context(|| format!("failed to read '{POWER_SUPPLY_PATH}'"))?
{
let entry = match entry { let entry = match entry {
Ok(entry) => entry, Ok(entry) => entry,
@ -107,38 +148,40 @@ pub fn get_power_supplies() -> anyhow::Result<Vec<PowerSupply>> {
let entry_path = entry.path(); let entry_path = entry.path();
if !is_power_supply(&entry_path).with_context(|| { let mut power_supply_config = None;
if is_battery(&entry_path).with_context(|| {
format!( format!(
"failed to determine whether if '{path}' is a power supply", "failed to determine what type of power supply '{path}' is",
path = entry_path.display(), path = entry_path.display(),
) )
})? { })? {
continue; for config in POWER_SUPPLY_THRESHOLD_CONFIGS {
} if entry_path.join(config.path_start).exists()
&& entry_path.join(config.path_end).exists()
for config in POWER_SUPPLY_CONFIGS { {
if entry_path.join(config.path_start).exists() power_supply_config = Some(*config);
&& entry_path.join(config.path_end).exists() 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,
config: *config,
});
continue 'entries;
} }
} }
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)
@ -149,7 +192,12 @@ pub fn set_charge_threshold_start(
charge_threshold_start: u8, charge_threshold_start: u8,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
write( write(
&power_supply.charge_threshold_path_start(), &power_supply.charge_threshold_path_start().ok_or_else(|| {
anyhow::anyhow!(
"power supply '{name}' does not support changing charge threshold levels",
name = power_supply.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 {power_supply}"))?;
@ -164,7 +212,12 @@ pub fn set_charge_threshold_end(
charge_threshold_end: u8, charge_threshold_end: u8,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
write( write(
&power_supply.charge_threshold_path_end(), &power_supply.charge_threshold_path_end().ok_or_else(|| {
anyhow::anyhow!(
"power supply '{name}' does not support changing charge threshold levels",
name = power_supply.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 {power_supply}"))?;