manager/user: Add BatteryChargeLimit1 interface (#12)

This commit is contained in:
Vicki Pfau 2025-01-13 22:42:22 -08:00
parent 02ed562bd0
commit 91890e5948
10 changed files with 292 additions and 36 deletions

View file

@ -29,6 +29,31 @@
</interface> </interface>
<!--
com.steampowered.SteamOSManager1.BatteryChargeLimit1
@short_description: Optional interface for battery charging limit
properties.
-->
<interface name="com.steampowered.SteamOSManager1.BatteryChargeLimit1">
<!--
MaxChargeLevel:
The maximum allowable percentage for battery charging. If setting to
-1, this will reset to the default.
-->
<property name="MaxChargeLevel" type="i" access="readwrite"/>
<!--
SuggestedMinimumLimit:
The suggested minimum value for a frontend to allow setting the max
charge level.
-->
<property name="SuggestedMinimumLimit" type="i" access="read"/>
</interface>
<!-- <!--
com.steampowered.SteamOSManager1.CpuScaling1 com.steampowered.SteamOSManager1.CpuScaling1
@short_description: Optional interface for adjusting CPU scaling. @short_description: Optional interface for adjusting CPU scaling.

View file

@ -5,7 +5,7 @@ script_args = ["factory-reset", "--reset-all"]
[factory_reset.os] [factory_reset.os]
script = "/usr/bin/steamos-reset-tool" script = "/usr/bin/steamos-reset-tool"
script_args = ["factory-reset", "--reset-os"] script_args = ["factory-reset", "--reset-os"]
[factory_reset.user] [factory_reset.user]
script = "/usr/bin/steamos-reset-tool" script = "/usr/bin/steamos-reset-tool"
script_args = ["factory-reset", "--reset-user-data"] script_args = ["factory-reset", "--reset-user-data"]
@ -36,3 +36,8 @@ max = 15
[gpu_clocks] [gpu_clocks]
min = 200 min = 200
max = 1600 max = 1600
[battery_charge_limit]
suggested_minimum_limit = 10
hwmon_name = "steamdeck_hwmon"
attribute = "max_battery_charge_level"

View file

@ -14,10 +14,10 @@ use steamos_manager::cec::HdmiCecState;
use steamos_manager::hardware::{FactoryResetKind, FanControlState}; use steamos_manager::hardware::{FactoryResetKind, FanControlState};
use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerProfile}; use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerProfile};
use steamos_manager::proxy::{ use steamos_manager::proxy::{
AmbientLightSensor1Proxy, CpuScaling1Proxy, FactoryReset1Proxy, FanControl1Proxy, AmbientLightSensor1Proxy, BatteryChargeLimit1Proxy, CpuScaling1Proxy, FactoryReset1Proxy,
GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy, Manager2Proxy, Storage1Proxy, FanControl1Proxy, GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy,
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy, Manager2Proxy, Storage1Proxy, TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy,
WifiPowerManagement1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy, WifiPowerManagement1Proxy,
}; };
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement}; use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
use zbus::fdo::{IntrospectableProxy, PropertiesProxy}; use zbus::fdo::{IntrospectableProxy, PropertiesProxy};
@ -172,6 +172,18 @@ enum Commands {
/// Valid kind(s) are `user`, `os`, `all` /// Valid kind(s) are `user`, `os`, `all`
kind: FactoryResetKind, kind: FactoryResetKind,
}, },
/// Get the maximum charge level set for the battery
GetMaxChargeLevel,
/// Set the maximum charge level set for the battery
SetMaxChargeLevel {
/// Valid levels are 1 - 100, or -1 to reset to default
level: i32,
},
/// Get the recommended minimum for a charge level limit
SuggestedMinimumChargeLimit,
} }
async fn get_all_properties(conn: &Connection) -> Result<()> { async fn get_all_properties(conn: &Connection) -> Result<()> {
@ -437,6 +449,20 @@ async fn main() -> Result<()> {
let proxy = Storage1Proxy::new(&conn).await?; let proxy = Storage1Proxy::new(&conn).await?;
let _ = proxy.trim_devices().await?; let _ = proxy.trim_devices().await?;
} }
Commands::GetMaxChargeLevel => {
let proxy = BatteryChargeLimit1Proxy::new(&conn).await?;
let level = proxy.max_charge_level().await?;
println!("Max charge level: {level}");
}
Commands::SetMaxChargeLevel { level } => {
let proxy = BatteryChargeLimit1Proxy::new(&conn).await?;
proxy.set_max_charge_level(*level).await?;
}
Commands::SuggestedMinimumChargeLimit => {
let proxy = BatteryChargeLimit1Proxy::new(&conn).await?;
let limit = proxy.suggested_minimum_limit().await?;
println!("Suggested minimum charge limit: {limit}");
}
} }
Ok(()) Ok(())

View file

@ -336,6 +336,7 @@ pub mod test {
))), ))),
tdp_limit: None, tdp_limit: None,
gpu_clocks: None, gpu_clocks: None,
battery_charge_limit: None,
})); }));
let fan_control = FanControl::new(connection); let fan_control = FanControl::new(connection);

View file

@ -25,7 +25,7 @@ use crate::job::JobManager;
use crate::platform::platform_config; use crate::platform::platform_config;
use crate::power::{ use crate::power::{
set_cpu_scaling_governor, set_gpu_clocks, set_gpu_performance_level, set_gpu_power_profile, set_cpu_scaling_governor, set_gpu_clocks, set_gpu_performance_level, set_gpu_power_profile,
set_tdp_limit, CPUScalingGovernor, GPUPerformanceLevel, GPUPowerProfile, set_max_charge_level, set_tdp_limit, CPUScalingGovernor, GPUPerformanceLevel, GPUPowerProfile,
}; };
use crate::process::{run_script, script_output}; use crate::process::{run_script, script_output};
use crate::wifi::{ use crate::wifi::{
@ -421,6 +421,12 @@ impl SteamOSManager {
.map_err(to_zbus_fdo_error) .map_err(to_zbus_fdo_error)
} }
async fn set_max_charge_level(&self, level: i32) -> fdo::Result<()> {
set_max_charge_level(if level == -1 { 0 } else { level })
.await
.map_err(to_zbus_fdo_error)
}
/// A version property. /// A version property.
#[zbus(property(emits_changed_signal = "const"))] #[zbus(property(emits_changed_signal = "const"))]
async fn version(&self) -> u32 { async fn version(&self) -> u32 {

View file

@ -27,8 +27,8 @@ use crate::platform::platform_config;
use crate::power::{ use crate::power::{
get_available_cpu_scaling_governors, get_available_gpu_performance_levels, get_available_cpu_scaling_governors, get_available_gpu_performance_levels,
get_available_gpu_power_profiles, get_cpu_scaling_governor, get_gpu_clocks, get_available_gpu_power_profiles, get_cpu_scaling_governor, get_gpu_clocks,
get_gpu_clocks_range, get_gpu_performance_level, get_gpu_power_profile, get_tdp_limit, get_gpu_clocks_range, get_gpu_performance_level, get_gpu_power_profile, get_max_charge_level,
get_tdp_limit_range, get_tdp_limit, get_tdp_limit_range,
}; };
use crate::wifi::{get_wifi_backend, get_wifi_power_management_state, list_wifi_interfaces}; use crate::wifi::{get_wifi_backend, get_wifi_power_management_state, list_wifi_interfaces};
use crate::API_VERSION; use crate::API_VERSION;
@ -103,6 +103,10 @@ struct AmbientLightSensor1 {
proxy: Proxy<'static>, proxy: Proxy<'static>,
} }
struct BatteryChargeLimit1 {
proxy: Proxy<'static>,
}
struct CpuScaling1 { struct CpuScaling1 {
proxy: Proxy<'static>, proxy: Proxy<'static>,
} }
@ -226,6 +230,41 @@ impl AmbientLightSensor1 {
} }
} }
impl BatteryChargeLimit1 {
const DEFAULT_SUGGESTED_MINIMUM_LIMIT: i32 = 10;
}
#[interface(name = "com.steampowered.SteamOSManager1.BatteryChargeLimit1")]
impl BatteryChargeLimit1 {
#[zbus(property(emits_changed_signal = "false"))]
async fn max_charge_level(&self) -> fdo::Result<i32> {
let level = get_max_charge_level().await.map_err(to_zbus_fdo_error)?;
if level <= 0 {
Ok(-1)
} else {
Ok(level)
}
}
#[zbus(property)]
async fn set_max_charge_level(&self, limit: i32) -> zbus::Result<()> {
self.proxy.call("SetMaxChargeLevel", &(limit)).await
}
#[zbus(property(emits_changed_signal = "const"))]
async fn suggested_minimum_limit(&self) -> i32 {
let Ok(Some(ref config)) = platform_config().await else {
return BatteryChargeLimit1::DEFAULT_SUGGESTED_MINIMUM_LIMIT;
};
let Some(ref config) = config.battery_charge_limit else {
return BatteryChargeLimit1::DEFAULT_SUGGESTED_MINIMUM_LIMIT;
};
config
.suggested_minimum_limit
.unwrap_or(BatteryChargeLimit1::DEFAULT_SUGGESTED_MINIMUM_LIMIT)
}
}
#[interface(name = "com.steampowered.SteamOSManager1.CpuScaling1")] #[interface(name = "com.steampowered.SteamOSManager1.CpuScaling1")]
impl CpuScaling1 { impl CpuScaling1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "false"))]
@ -541,6 +580,9 @@ pub(crate) async fn create_interfaces(
let als = AmbientLightSensor1 { let als = AmbientLightSensor1 {
proxy: proxy.clone(), proxy: proxy.clone(),
}; };
let battery_charge_limit = BatteryChargeLimit1 {
proxy: proxy.clone(),
};
let cpu_scaling = CpuScaling1 { let cpu_scaling = CpuScaling1 {
proxy: proxy.clone(), proxy: proxy.clone(),
}; };
@ -597,6 +639,10 @@ pub(crate) async fn create_interfaces(
object_server.at(MANAGER_PATH, wifi_debug_dump).await?; object_server.at(MANAGER_PATH, wifi_debug_dump).await?;
} }
if get_max_charge_level().await.is_ok() {
object_server.at(MANAGER_PATH, battery_charge_limit).await?;
}
object_server.at(MANAGER_PATH, cpu_scaling).await?; object_server.at(MANAGER_PATH, cpu_scaling).await?;
if config if config
@ -683,7 +729,8 @@ mod test {
use crate::hardware::test::fake_model; use crate::hardware::test::fake_model;
use crate::hardware::HardwareVariant; use crate::hardware::HardwareVariant;
use crate::platform::{ use crate::platform::{
PlatformConfig, RangeConfig, ResetConfig, ScriptConfig, ServiceConfig, StorageConfig, BatteryChargeLimitConfig, PlatformConfig, RangeConfig, ResetConfig, ScriptConfig,
ServiceConfig, StorageConfig,
}; };
use crate::systemd::test::{MockManager, MockUnit}; use crate::systemd::test::{MockManager, MockUnit};
use crate::{power, testing}; use crate::{power, testing};
@ -710,6 +757,11 @@ mod test {
))), ))),
tdp_limit: Some(RangeConfig::new(3, 15)), tdp_limit: Some(RangeConfig::new(3, 15)),
gpu_clocks: Some(RangeConfig::new(200, 1600)), gpu_clocks: Some(RangeConfig::new(200, 1600)),
battery_charge_limit: Some(BatteryChargeLimitConfig {
suggested_minimum_limit: Some(10),
hwmon_name: String::from("steamdeck_hwmon"),
attribute: String::from("max_battery_charge_level"),
}),
}) })
} }
@ -801,6 +853,17 @@ mod test {
); );
} }
#[tokio::test]
async fn interface_matches_battery_charge_limit() {
let test = start(all_config()).await.expect("start");
assert!(
test_interface_matches::<BatteryChargeLimit1>(&test.connection)
.await
.unwrap()
);
}
#[tokio::test] #[tokio::test]
async fn interface_matches_cpu_scaling1() { async fn interface_matches_cpu_scaling1() {
let test = start(all_config()).await.expect("start"); let test = start(all_config()).await.expect("start");

View file

@ -28,6 +28,7 @@ pub(crate) struct PlatformConfig {
pub fan_control: Option<ServiceConfig>, pub fan_control: Option<ServiceConfig>,
pub tdp_limit: Option<RangeConfig<u32>>, pub tdp_limit: Option<RangeConfig<u32>>,
pub gpu_clocks: Option<RangeConfig<u32>>, pub gpu_clocks: Option<RangeConfig<u32>>,
pub battery_charge_limit: Option<BatteryChargeLimitConfig>,
} }
#[derive(Clone, Deserialize, Debug)] #[derive(Clone, Deserialize, Debug)]
@ -70,6 +71,13 @@ pub(crate) struct StorageConfig {
pub format_device: FormatDeviceConfig, pub format_device: FormatDeviceConfig,
} }
#[derive(Clone, Deserialize, Debug)]
pub(crate) struct BatteryChargeLimitConfig {
pub suggested_minimum_limit: Option<i32>,
pub hwmon_name: String,
pub attribute: String,
}
#[derive(Clone, Default, Deserialize, Debug)] #[derive(Clone, Default, Deserialize, Debug)]
pub(crate) struct FormatDeviceConfig { pub(crate) struct FormatDeviceConfig {
pub script: PathBuf, pub script: PathBuf,

View file

@ -20,8 +20,10 @@ use crate::hardware::is_deck;
use crate::platform::platform_config; use crate::platform::platform_config;
use crate::{path, write_synced}; use crate::{path, write_synced};
const GPU_HWMON_PREFIX: &str = "/sys/class/hwmon"; const HWMON_PREFIX: &str = "/sys/class/hwmon";
const GPU_HWMON_NAME: &str = "amdgpu"; const GPU_HWMON_NAME: &str = "amdgpu";
const CPU_PREFIX: &str = "/sys/devices/system/cpu/cpufreq"; const CPU_PREFIX: &str = "/sys/devices/system/cpu/cpufreq";
const CPU0_NAME: &str = "policy0"; const CPU0_NAME: &str = "policy0";
@ -85,14 +87,14 @@ pub enum CPUScalingGovernor {
async fn read_gpu_sysfs_contents<S: AsRef<Path>>(suffix: S) -> Result<String> { async fn read_gpu_sysfs_contents<S: AsRef<Path>>(suffix: S) -> Result<String> {
// Read a given suffix for the GPU // Read a given suffix for the GPU
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
fs::read_to_string(base.join(suffix.as_ref())) fs::read_to_string(base.join(suffix.as_ref()))
.await .await
.map_err(|message| anyhow!("Error opening sysfs file for reading {message}")) .map_err(|message| anyhow!("Error opening sysfs file for reading {message}"))
} }
async fn write_gpu_sysfs_contents<S: AsRef<Path>>(suffix: S, data: &[u8]) -> Result<()> { async fn write_gpu_sysfs_contents<S: AsRef<Path>>(suffix: S, data: &[u8]) -> Result<()> {
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
write_synced(base.join(suffix), data) write_synced(base.join(suffix), data)
.await .await
.inspect_err(|message| error!("Error writing to sysfs file: {message}")) .inspect_err(|message| error!("Error writing to sysfs file: {message}"))
@ -195,7 +197,7 @@ pub(crate) async fn set_gpu_power_profile(value: GPUPowerProfile) -> Result<()>
} }
pub(crate) async fn get_available_gpu_performance_levels() -> Result<Vec<GPUPerformanceLevel>> { pub(crate) async fn get_available_gpu_performance_levels() -> Result<Vec<GPUPerformanceLevel>> {
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
if try_exists(base.join(GPU_PERFORMANCE_LEVEL_SUFFIX)).await? { if try_exists(base.join(GPU_PERFORMANCE_LEVEL_SUFFIX)).await? {
Ok(vec![ Ok(vec![
GPUPerformanceLevel::Auto, GPUPerformanceLevel::Auto,
@ -288,7 +290,7 @@ pub(crate) async fn get_gpu_clocks_range() -> Result<(u32, u32)> {
pub(crate) async fn set_gpu_clocks(clocks: u32) -> Result<()> { pub(crate) async fn set_gpu_clocks(clocks: u32) -> Result<()> {
// Set GPU clocks to given value valid // Set GPU clocks to given value valid
// Only used when GPU Performance Level is manual, but write whenever called. // Only used when GPU Performance Level is manual, but write whenever called.
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
let mut myfile = File::create(base.join(GPU_CLOCKS_SUFFIX)) let mut myfile = File::create(base.join(GPU_CLOCKS_SUFFIX))
.await .await
.inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?;
@ -317,7 +319,7 @@ pub(crate) async fn set_gpu_clocks(clocks: u32) -> Result<()> {
} }
pub(crate) async fn get_gpu_clocks() -> Result<u32> { pub(crate) async fn get_gpu_clocks() -> Result<u32> {
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
let clocks_file = File::open(base.join(GPU_CLOCKS_SUFFIX)).await?; let clocks_file = File::open(base.join(GPU_CLOCKS_SUFFIX)).await?;
let mut reader = BufReader::new(clocks_file); let mut reader = BufReader::new(clocks_file);
loop { loop {
@ -343,8 +345,8 @@ pub(crate) async fn get_gpu_clocks() -> Result<u32> {
Ok(0) Ok(0)
} }
async fn find_hwmon() -> Result<PathBuf> { async fn find_hwmon(hwmon: &str) -> Result<PathBuf> {
let mut dir = fs::read_dir(path(GPU_HWMON_PREFIX)).await?; let mut dir = fs::read_dir(path(HWMON_PREFIX)).await?;
loop { loop {
let base = match dir.next_entry().await? { let base = match dir.next_entry().await? {
Some(entry) => entry.path(), Some(entry) => entry.path(),
@ -355,14 +357,14 @@ async fn find_hwmon() -> Result<PathBuf> {
.await? .await?
.trim() .trim()
.to_string(); .to_string();
if name == GPU_HWMON_NAME { if name == hwmon {
return Ok(base); return Ok(base);
} }
} }
} }
pub(crate) async fn get_tdp_limit() -> Result<u32> { pub(crate) async fn get_tdp_limit() -> Result<u32> {
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
let power1cap = fs::read_to_string(base.join(TDP_LIMIT1)).await?; let power1cap = fs::read_to_string(base.join(TDP_LIMIT1)).await?;
let power1cap: u32 = power1cap.trim_end().parse()?; let power1cap: u32 = power1cap.trim_end().parse()?;
Ok(power1cap / 1_000_000) Ok(power1cap / 1_000_000)
@ -374,7 +376,7 @@ pub(crate) async fn set_tdp_limit(limit: u32) -> Result<()> {
ensure!((3..=15).contains(&limit), "Invalid limit"); ensure!((3..=15).contains(&limit), "Invalid limit");
let data = format!("{limit}000000"); let data = format!("{limit}000000");
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
write_synced(base.join(TDP_LIMIT1), data.as_bytes()) write_synced(base.join(TDP_LIMIT1), data.as_bytes())
.await .await
.inspect_err(|message| { .inspect_err(|message| {
@ -400,19 +402,51 @@ pub(crate) async fn get_tdp_limit_range() -> Result<(u32, u32)> {
Ok((range.min, range.max)) Ok((range.min, range.max))
} }
pub(crate) async fn get_max_charge_level() -> Result<i32> {
let config = platform_config()
.await?
.as_ref()
.and_then(|config| config.battery_charge_limit.clone())
.ok_or(anyhow!("No battery charge limit configured"))?;
let base = find_hwmon(config.hwmon_name.as_str()).await?;
fs::read_to_string(base.join(config.attribute.as_str()))
.await
.map_err(|message| anyhow!("Error reading sysfs: {message}"))?
.trim()
.parse()
.map_err(|e| anyhow!("Error parsing value: {e}"))
}
pub(crate) async fn set_max_charge_level(limit: i32) -> Result<()> {
ensure!((0..=100).contains(&limit), "Invalid limit");
let data = limit.to_string();
let config = platform_config()
.await?
.as_ref()
.and_then(|config| config.battery_charge_limit.clone())
.ok_or(anyhow!("No battery charge limit configured"))?;
let base = find_hwmon(config.hwmon_name.as_str()).await?;
write_synced(base.join(config.attribute.as_str()), data.as_bytes())
.await
.inspect_err(|message| error!("Error writing to sysfs file: {message}"))
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod test { pub(crate) mod test {
use super::*; use super::*;
use crate::hardware::test::fake_model; use crate::hardware::test::fake_model;
use crate::hardware::HardwareVariant; use crate::hardware::HardwareVariant;
use crate::platform::{BatteryChargeLimitConfig, PlatformConfig};
use crate::{enum_roundtrip, testing}; use crate::{enum_roundtrip, testing};
use anyhow::anyhow; use anyhow::anyhow;
use tokio::fs::{create_dir_all, read_to_string, remove_dir, write}; use tokio::fs::{create_dir_all, read_to_string, remove_dir, write};
pub async fn setup() -> Result<()> { pub async fn setup() -> Result<()> {
// Use hwmon5 just as a test. We needed a subfolder of GPU_HWMON_PREFIX // Use hwmon5 just as a test. We needed a subfolder of HWMON_PREFIX
// and this is as good as any. // and this is as good as any.
let base = path(GPU_HWMON_PREFIX).join("hwmon5"); let base = path(HWMON_PREFIX).join("hwmon5");
let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX); let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX);
// Creates hwmon path, including device subpath // Creates hwmon path, including device subpath
create_dir_all(filename.parent().unwrap()).await?; create_dir_all(filename.parent().unwrap()).await?;
@ -423,7 +457,7 @@ pub(crate) mod test {
pub async fn create_nodes() -> Result<()> { pub async fn create_nodes() -> Result<()> {
setup().await?; setup().await?;
let base = find_hwmon().await?; let base = find_hwmon(GPU_HWMON_NAME).await?;
let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX); let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX);
write(filename.as_path(), "auto\n").await?; write(filename.as_path(), "auto\n").await?;
@ -441,11 +475,18 @@ pub(crate) mod test {
let filename = base.join(TDP_LIMIT1); let filename = base.join(TDP_LIMIT1);
write(filename.as_path(), "15000000\n").await?; write(filename.as_path(), "15000000\n").await?;
let base = path(HWMON_PREFIX).join("hwmon6");
create_dir_all(&base).await?;
write(base.join("name"), "steamdeck_hwmon\n").await?;
write(base.join("max_battery_charge_level"), "10\n").await?;
Ok(()) Ok(())
} }
pub async fn write_clocks(mhz: u32) { pub async fn write_clocks(mhz: u32) {
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_CLOCKS_SUFFIX); let filename = base.join(GPU_CLOCKS_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -467,7 +508,7 @@ CCLK_RANGE in Core0:
} }
pub async fn read_clocks() -> Result<String, std::io::Error> { pub async fn read_clocks() -> Result<String, std::io::Error> {
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
read_to_string(base.join(GPU_CLOCKS_SUFFIX)).await read_to_string(base.join(GPU_CLOCKS_SUFFIX)).await
} }
@ -480,7 +521,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX); let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX);
assert!(get_gpu_performance_level().await.is_err()); assert!(get_gpu_performance_level().await.is_err());
@ -525,7 +566,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX); let filename = base.join(GPU_PERFORMANCE_LEVEL_SUFFIX);
set_gpu_performance_level(GPUPerformanceLevel::Auto) set_gpu_performance_level(GPUPerformanceLevel::Auto)
@ -570,7 +611,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let hwmon = path(GPU_HWMON_PREFIX); let hwmon = path(HWMON_PREFIX);
assert!(get_tdp_limit().await.is_err()); assert!(get_tdp_limit().await.is_err());
@ -594,7 +635,7 @@ CCLK_RANGE in Core0:
); );
assert!(set_tdp_limit(10).await.is_err()); assert!(set_tdp_limit(10).await.is_err());
let hwmon = path(GPU_HWMON_PREFIX); let hwmon = path(HWMON_PREFIX);
assert_eq!( assert_eq!(
set_tdp_limit(10).await.unwrap_err().to_string(), set_tdp_limit(10).await.unwrap_err().to_string(),
anyhow!("No such file or directory (os error 2)").to_string() anyhow!("No such file or directory (os error 2)").to_string()
@ -645,7 +686,7 @@ CCLK_RANGE in Core0:
assert!(get_gpu_clocks().await.is_err()); assert!(get_gpu_clocks().await.is_err());
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_CLOCKS_SUFFIX); let filename = base.join(GPU_CLOCKS_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -678,7 +719,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_CLOCK_LEVELS_SUFFIX); let filename = base.join(GPU_CLOCK_LEVELS_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -758,7 +799,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_POWER_PROFILE_SUFFIX); let filename = base.join(GPU_POWER_PROFILE_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -814,7 +855,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_POWER_PROFILE_SUFFIX); let filename = base.join(GPU_POWER_PROFILE_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -872,7 +913,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_POWER_PROFILE_SUFFIX); let filename = base.join(GPU_POWER_PROFILE_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -910,7 +951,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_POWER_PROFILE_SUFFIX); let filename = base.join(GPU_POWER_PROFILE_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -942,7 +983,7 @@ CCLK_RANGE in Core0:
let _h = testing::start(); let _h = testing::start();
setup().await.expect("setup"); setup().await.expect("setup");
let base = find_hwmon().await.unwrap(); let base = find_hwmon(GPU_HWMON_NAME).await.unwrap();
let filename = base.join(GPU_POWER_PROFILE_SUFFIX); let filename = base.join(GPU_POWER_PROFILE_SUFFIX);
create_dir_all(filename.parent().unwrap()) create_dir_all(filename.parent().unwrap())
.await .await
@ -1053,4 +1094,52 @@ CCLK_RANGE in Core0:
assert!(get_cpu_scaling_governor().await.is_err()); assert!(get_cpu_scaling_governor().await.is_err());
} }
#[tokio::test]
async fn read_max_charge_level() {
let handle = testing::start();
let platform_config = Some(PlatformConfig {
factory_reset: None,
update_bios: None,
update_dock: None,
storage: None,
fan_control: None,
tdp_limit: None,
gpu_clocks: None,
battery_charge_limit: Some(BatteryChargeLimitConfig {
suggested_minimum_limit: Some(10),
hwmon_name: String::from("steamdeck_hwmon"),
attribute: String::from("max_battery_charge_level"),
}),
});
handle.test.platform_config.replace(platform_config);
let base = path(HWMON_PREFIX).join("hwmon6");
create_dir_all(&base).await.expect("create_dir_all");
write(base.join("name"), "steamdeck_hwmon\n")
.await
.expect("write");
assert_eq!(
find_hwmon("steamdeck_hwmon").await.unwrap(),
path(HWMON_PREFIX).join("hwmon6")
);
write(base.join("max_battery_charge_level"), "10\n")
.await
.expect("write");
assert_eq!(get_max_charge_level().await.unwrap(), 10);
set_max_charge_level(99).await.expect("set");
assert_eq!(get_max_charge_level().await.unwrap(), 99);
set_max_charge_level(0).await.expect("set");
assert_eq!(get_max_charge_level().await.unwrap(), 0);
assert!(set_max_charge_level(101).await.is_err());
assert!(set_max_charge_level(-1).await.is_err());
}
} }

View file

@ -0,0 +1,31 @@
//! # D-Bus interface proxy for: `com.steampowered.SteamOSManager1.BatteryChargeLimit1`
//!
//! This code was generated by `zbus-xmlgen` `5.0.1` from D-Bus introspection data.
//! Source: `com.steampowered.SteamOSManager1.xml`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
//! More information can be found in the [Writing a client proxy] section of the zbus
//! documentation.
//!
//!
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
use zbus::proxy;
#[proxy(
interface = "com.steampowered.SteamOSManager1.BatteryChargeLimit1",
default_service = "com.steampowered.SteamOSManager1",
default_path = "/com/steampowered/SteamOSManager1",
assume_defaults = true
)]
pub trait BatteryChargeLimit1 {
/// MaxChargeLevel property
#[zbus(property)]
fn max_charge_level(&self) -> zbus::Result<i32>;
#[zbus(property)]
fn set_max_charge_level(&self, value: i32) -> zbus::Result<()>;
/// SuggestedMinimumLimit property
#[zbus(property)]
fn suggested_minimum_limit(&self) -> zbus::Result<i32>;
}

View file

@ -15,6 +15,7 @@ pub use crate::proxy::manager::ManagerProxy;
// Optional interfaces // Optional interfaces
mod ambient_light_sensor1; mod ambient_light_sensor1;
mod battery_charge_limit1;
mod cpu_scaling1; mod cpu_scaling1;
mod factory_reset1; mod factory_reset1;
mod fan_control1; mod fan_control1;
@ -30,6 +31,7 @@ mod wifi_debug1;
mod wifi_debug_dump1; mod wifi_debug_dump1;
mod wifi_power_management1; mod wifi_power_management1;
pub use crate::proxy::ambient_light_sensor1::AmbientLightSensor1Proxy; pub use crate::proxy::ambient_light_sensor1::AmbientLightSensor1Proxy;
pub use crate::proxy::battery_charge_limit1::BatteryChargeLimit1Proxy;
pub use crate::proxy::cpu_scaling1::CpuScaling1Proxy; pub use crate::proxy::cpu_scaling1::CpuScaling1Proxy;
pub use crate::proxy::factory_reset1::FactoryReset1Proxy; pub use crate::proxy::factory_reset1::FactoryReset1Proxy;
pub use crate::proxy::fan_control1::FanControl1Proxy; pub use crate::proxy::fan_control1::FanControl1Proxy;