mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-05 14:10:34 -04:00
manager/user: Add BatteryChargeLimit1 interface (#12)
This commit is contained in:
parent
02ed562bd0
commit
91890e5948
10 changed files with 292 additions and 36 deletions
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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,
|
||||||
|
|
143
src/power.rs
143
src/power.rs
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/proxy/battery_charge_limit1.rs
Normal file
31
src/proxy/battery_charge_limit1.rs
Normal 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>;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue