Improve sysfs calls from providers to intelligently find the correct entry to fix #114

This commit is contained in:
NGnius (Graham) 2023-08-06 14:49:19 -04:00
parent 52f01ad7f3
commit 64cb4193f9
21 changed files with 387 additions and 147 deletions

5
backend/Cargo.lock generated
View file

@ -1053,6 +1053,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"simplelog", "simplelog",
"sysfuss",
"tokio", "tokio",
"ureq", "ureq",
"usdpl-back", "usdpl-back",
@ -1296,6 +1297,10 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sysfuss"
version = "0.1.0"
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.3" version = "1.1.3"

View file

@ -15,6 +15,7 @@ readme = "../README.md"
usdpl-back = { version = "0.10.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"} usdpl-back = { version = "0.10.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"}
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
sysfuss = { version = "0.1", path = "../../sysfs-nav", features = ["derive"] }
# async # async
tokio = { version = "*", features = ["time"] } tokio = { version = "*", features = ["time"] }

View file

@ -73,10 +73,6 @@ fn main() -> Result<(), ()> {
} }
let _limits_handle = crate::settings::limits_worker_spawn(); let _limits_handle = crate::settings::limits_worker_spawn();
log::info!(
"Detected device automatically, starting with driver: {:?} (This can be overriden)",
crate::settings::auto_detect_provider()
);
let mut loaded_settings = let mut loaded_settings =
persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE))
@ -88,6 +84,11 @@ fn main() -> Result<(), ()> {
) )
}); });
log::info!(
"Detected device automatically {:?}, using driver: {:?} (This can be overriden)",
crate::settings::auto_detect_provider(), loaded_settings.cpus.provider()
);
log::debug!("Settings: {:?}", loaded_settings); log::debug!("Settings: {:?}", loaded_settings);
let (api_handler, api_sender) = crate::api::handler::ApiMessageHandler::new(); let (api_handler, api_sender) = crate::api::handler::ApiMessageHandler::new();

View file

@ -9,6 +9,8 @@ pub struct BatteryJson {
pub charge_mode: Option<String>, pub charge_mode: Option<String>,
#[serde(default)] #[serde(default)]
pub events: Vec<BatteryEventJson>, pub events: Vec<BatteryEventJson>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<String>,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
@ -24,6 +26,7 @@ impl Default for BatteryJson {
charge_rate: None, charge_rate: None,
charge_mode: None, charge_mode: None,
events: Vec::new(), events: Vec::new(),
root: None,
} }
} }
} }

View file

@ -12,6 +12,8 @@ pub struct CpuJson {
pub online: bool, pub online: bool,
pub clock_limits: Option<MinMaxJson<u64>>, pub clock_limits: Option<MinMaxJson<u64>>,
pub governor: String, pub governor: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<String>,
} }
impl Default for CpuJson { impl Default for CpuJson {
@ -20,6 +22,7 @@ impl Default for CpuJson {
online: true, online: true,
clock_limits: None, clock_limits: None,
governor: "schedutil".to_owned(), governor: "schedutil".to_owned(),
root: None,
} }
} }
} }

View file

@ -10,6 +10,8 @@ pub struct GpuJson {
pub slow_ppt: Option<u64>, pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMaxJson<u64>>, pub clock_limits: Option<MinMaxJson<u64>>,
pub slow_memory: bool, pub slow_memory: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<String>,
} }
impl Default for GpuJson { impl Default for GpuJson {
@ -19,6 +21,7 @@ impl Default for GpuJson {
slow_ppt: None, slow_ppt: None,
clock_limits: None, clock_limits: None,
slow_memory: false, slow_memory: false,
root: None,
} }
} }
} }

View file

@ -1,6 +1,7 @@
use std::convert::Into; use std::convert::Into;
use limits_core::json::GenericBatteryLimit; use limits_core::json::GenericBatteryLimit;
use sysfuss::SysEntity;
use crate::persist::BatteryJson; use crate::persist::BatteryJson;
use crate::settings::TBattery; use crate::settings::TBattery;
@ -10,6 +11,7 @@ use crate::settings::{OnResume, OnSet, SettingError};
pub struct Battery { pub struct Battery {
#[allow(dead_code)] #[allow(dead_code)]
limits: GenericBatteryLimit, limits: GenericBatteryLimit,
sysfs: sysfuss::PowerSupplyPath,
} }
impl Into<BatteryJson> for Battery { impl Into<BatteryJson> for Battery {
@ -19,6 +21,7 @@ impl Into<BatteryJson> for Battery {
charge_rate: None, charge_rate: None,
charge_mode: None, charge_mode: None,
events: Vec::default(), events: Vec::default(),
root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|s| s.to_owned())),
} }
} }
} }
@ -37,18 +40,41 @@ impl Battery {
} }
} }
fn find_psu_sysfs(root: Option<impl AsRef<std::path::Path>>) -> sysfuss::PowerSupplyPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.power_supply(crate::settings::util::always_satisfied) {
Ok(mut iter) => {
iter.next()
.unwrap_or_else(|| {
log::error!("Failed to find generic battery power_supply in sysfs (no results), using naive fallback");
root.power_supply_by_name("BAT0")
})
},
Err(e) => {
log::error!("Failed to find generic battery power_supply in sysfs ({}), using naive fallback", e);
root.power_supply_by_name("BAT0")
}
}
}
pub fn from_limits(limits: limits_core::json::GenericBatteryLimit) -> Self { pub fn from_limits(limits: limits_core::json::GenericBatteryLimit) -> Self {
// TODO // TODO
Self { limits } Self {
limits,
sysfs: Self::find_psu_sysfs(None::<&'static str>),
}
} }
pub fn from_json_and_limits( pub fn from_json_and_limits(
_other: BatteryJson, other: BatteryJson,
_version: u64, _version: u64,
limits: limits_core::json::GenericBatteryLimit, limits: limits_core::json::GenericBatteryLimit,
) -> Self { ) -> Self {
// TODO // TODO
Self { limits } Self {
limits,
sysfs: Self::find_psu_sysfs(other.root)
}
} }
} }

View file

@ -202,6 +202,7 @@ pub struct Cpu {
limits: GenericCpuLimit, limits: GenericCpuLimit,
index: usize, index: usize,
state: crate::state::steam_deck::Cpu, state: crate::state::steam_deck::Cpu,
root: std::path::PathBuf,
} }
/*impl Cpu { /*impl Cpu {
@ -235,6 +236,7 @@ impl FromGenericCpuInfo for Cpu {
limits, limits,
index: cpu_index, index: cpu_index,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: "/".into(),
} }
} }
@ -258,6 +260,7 @@ impl FromGenericCpuInfo for Cpu {
limits, limits,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: other.root.unwrap_or_else(|| "/".to_string()).into(),
}, },
_ => Self { _ => Self {
online: other.online, online: other.online,
@ -266,6 +269,7 @@ impl FromGenericCpuInfo for Cpu {
limits, limits,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: other.root.unwrap_or_else(|| "/".to_string()).into(),
}, },
} }
} }
@ -354,6 +358,7 @@ impl Into<CpuJson> for Cpu {
online: self.online, online: self.online,
clock_limits: self.clock_limits.map(|x| x.into()), clock_limits: self.clock_limits.map(|x| x.into()),
governor: self.governor, governor: self.governor,
root: self.root.to_str().map(|s| s.to_owned()),
} }
} }
} }

View file

@ -1,6 +1,7 @@
use std::convert::Into; use std::convert::Into;
use limits_core::json::GenericGpuLimit; use limits_core::json::GenericGpuLimit;
use sysfuss::{BasicEntityPath, SysEntity};
use crate::api::RangeLimit; use crate::api::RangeLimit;
use crate::persist::GpuJson; use crate::persist::GpuJson;
@ -15,6 +16,7 @@ pub struct Gpu {
pub slow_ppt: Option<u64>, pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMax<u64>>, pub clock_limits: Option<MinMax<u64>>,
limits: GenericGpuLimit, limits: GenericGpuLimit,
sysfs: BasicEntityPath,
} }
impl Gpu { impl Gpu {
@ -31,6 +33,23 @@ impl Gpu {
} }
}*/ }*/
fn find_card_sysfs(root: Option<impl AsRef<std::path::Path>>) -> BasicEntityPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.class("drm", crate::settings::util::always_satisfied) {
Ok(mut iter) => {
iter.next()
.unwrap_or_else(|| {
log::error!("Failed to find generic gpu drm in sysfs (no results), using naive fallback");
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
})
},
Err(e) => {
log::error!("Failed to find generic gpu drm in sysfs ({}), using naive fallback", e);
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
}
}
}
pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self { pub fn from_limits(limits: limits_core::json::GenericGpuLimit) -> Self {
Self { Self {
slow_memory: false, slow_memory: false,
@ -38,6 +57,7 @@ impl Gpu {
slow_ppt: None, slow_ppt: None,
clock_limits: None, clock_limits: None,
limits, limits,
sysfs: Self::find_card_sysfs(None::<&'static str>),
} }
} }
@ -65,6 +85,7 @@ impl Gpu {
}, },
clock_limits: clock_lims, clock_limits: clock_lims,
limits, limits,
sysfs: Self::find_card_sysfs(other.root)
} }
} }
} }
@ -77,6 +98,7 @@ impl Into<GpuJson> for Gpu {
slow_ppt: self.slow_ppt, slow_ppt: self.slow_ppt,
clock_limits: self.clock_limits.map(|x| x.into()), clock_limits: self.clock_limits.map(|x| x.into()),
slow_memory: false, slow_memory: false,
root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|s| s.to_owned()))
} }
} }
} }

View file

@ -1,4 +1,8 @@
use std::convert::Into; use std::convert::Into;
use std::sync::Arc;
use sysfuss::{PowerSupplyAttribute, PowerSupplyPath, HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, SysEntity, SysEntityAttributesExt, SysAttribute};
use sysfuss::capability::attributes;
use super::oc_limits::{BatteryLimits, OverclockLimits}; use super::oc_limits::{BatteryLimits, OverclockLimits};
use super::util::ChargeMode; use super::util::ChargeMode;
@ -15,6 +19,8 @@ pub struct Battery {
limits: BatteryLimits, limits: BatteryLimits,
state: crate::state::steam_deck::Battery, state: crate::state::steam_deck::Battery,
driver_mode: crate::persist::DriverJson, driver_mode: crate::persist::DriverJson,
sysfs_bat: PowerSupplyPath,
sysfs_hwmon: Arc<HwMonPath>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -32,6 +38,7 @@ struct EventInstruction {
charge_rate: Option<u64>, charge_rate: Option<u64>,
charge_mode: Option<ChargeMode>, charge_mode: Option<ChargeMode>,
is_triggered: bool, is_triggered: bool,
sysfs_hwmon: Arc<HwMonPath>,
} }
impl OnPowerEvent for EventInstruction { impl OnPowerEvent for EventInstruction {
@ -109,7 +116,7 @@ impl EventInstruction {
} }
} }
fn from_json(other: BatteryEventJson, _version: u64) -> Self { fn from_json(other: BatteryEventJson, _version: u64, hwmon: Arc<HwMonPath>) -> Self {
Self { Self {
trigger: Self::str_to_trigger(&other.trigger).unwrap_or(EventTrigger::Ignored), trigger: Self::str_to_trigger(&other.trigger).unwrap_or(EventTrigger::Ignored),
charge_rate: other.charge_rate, charge_rate: other.charge_rate,
@ -118,6 +125,7 @@ impl EventInstruction {
.map(|x| Battery::str_to_charge_mode(&x)) .map(|x| Battery::str_to_charge_mode(&x))
.flatten(), .flatten(),
is_triggered: false, is_triggered: false,
sysfs_hwmon: hwmon,
} }
} }
@ -136,12 +144,13 @@ impl EventInstruction {
fn set_charge_rate(&self) -> Result<(), SettingError> { fn set_charge_rate(&self) -> Result<(), SettingError> {
if let Some(charge_rate) = self.charge_rate { if let Some(charge_rate) = self.charge_rate {
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate) let attr = HwMonAttribute::custom("maximum_battery_charge_rate");
.map_err(|e| SettingError { self.sysfs_hwmon.set(attr, charge_rate).map_err(
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), |e| SettingError {
msg: format!("Failed to write to `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}) },
.map(|_| ()) )
} else { } else {
Ok(()) Ok(())
} }
@ -173,13 +182,32 @@ impl Into<BatteryEventJson> for EventInstruction {
const BATTERY_VOLTAGE: f64 = 7.7; const BATTERY_VOLTAGE: f64 = 7.7;
const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only /*const BATTERY_CHARGE_RATE_PATH: &str = "/sys/class/hwmon/hwmon5/maximum_battery_charge_rate"; // write-only
const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only const BATTERY_CURRENT_NOW_PATH: &str = "/sys/class/power_supply/BAT1/current_now"; // read-only
const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/power_supply/BAT1/charge_now"; // read-only const BATTERY_CHARGE_NOW_PATH: &str = "/sys/class/power_supply/BAT1/charge_now"; // read-only
const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/power_supply/BAT1/charge_full"; // read-only const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/power_supply/BAT1/charge_full"; // read-only
const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/power_supply/BAT1/charge_full_design"; // read-only const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/power_supply/BAT1/charge_full_design"; // read-only
const USB_PD_IN_MVOLTAGE_PATH: &str = "/sys/class/hwmon/hwmon5/in0_input"; // read-only const USB_PD_IN_MVOLTAGE_PATH: &str = "/sys/class/hwmon/hwmon5/in0_input"; // read-only
const USB_PD_IN_CURRENT_PATH: &str = "/sys/class/hwmon/hwmon5/curr1_input"; // read-only const USB_PD_IN_CURRENT_PATH: &str = "/sys/class/hwmon/hwmon5/curr1_input"; // read-only*/
const BATTERY_NEEDS: &[PowerSupplyAttribute] = &[
PowerSupplyAttribute::Type,
PowerSupplyAttribute::CurrentNow,
PowerSupplyAttribute::ChargeNow,
PowerSupplyAttribute::ChargeFull,
PowerSupplyAttribute::ChargeFullDesign,
PowerSupplyAttribute::CycleCount,
PowerSupplyAttribute::Capacity,
PowerSupplyAttribute::CapacityLevel,
];
const HWMON_NEEDS: &[HwMonAttribute] = &[
HwMonAttribute::name(),
HwMonAttribute::new(HwMonAttributeType::In, 0, HwMonAttributeItem::Input),
HwMonAttribute::new(HwMonAttributeType::Curr, 1, HwMonAttributeItem::Input),
//HwMonAttribute::custom("maximum_battery_charge_rate"), // NOTE: Cannot filter by custom capabilities
];
impl Battery { impl Battery {
#[inline] #[inline]
@ -191,6 +219,7 @@ impl Battery {
} else { } else {
crate::persist::DriverJson::SteamDeckAdvance crate::persist::DriverJson::SteamDeckAdvance
}; };
let hwmon_sys = Arc::new(Self::find_hwmon_sysfs(None::<&'static str>));
match version { match version {
0 => Self { 0 => Self {
charge_rate: other.charge_rate, charge_rate: other.charge_rate,
@ -201,11 +230,13 @@ impl Battery {
events: other events: other
.events .events
.into_iter() .into_iter()
.map(|x| EventInstruction::from_json(x, version)) .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone()))
.collect(), .collect(),
limits: oc_limits, limits: oc_limits,
state: crate::state::steam_deck::Battery::default(), state: crate::state::steam_deck::Battery::default(),
driver_mode: driver, driver_mode: driver,
sysfs_bat: Self::find_battery_sysfs(None::<&'static str>),
sysfs_hwmon: hwmon_sys,
}, },
_ => Self { _ => Self {
charge_rate: other.charge_rate, charge_rate: other.charge_rate,
@ -216,15 +247,52 @@ impl Battery {
events: other events: other
.events .events
.into_iter() .into_iter()
.map(|x| EventInstruction::from_json(x, version)) .map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone()))
.collect(), .collect(),
limits: oc_limits, limits: oc_limits,
state: crate::state::steam_deck::Battery::default(), state: crate::state::steam_deck::Battery::default(),
driver_mode: driver, driver_mode: driver,
sysfs_bat: Self::find_battery_sysfs(None::<&'static str>),
sysfs_hwmon: hwmon_sys,
}, },
} }
} }
fn find_battery_sysfs(root: Option<impl AsRef<std::path::Path>>) -> PowerSupplyPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.power_supply(attributes(BATTERY_NEEDS.into_iter().copied())) {
Ok(mut iter) => {
iter.next()
.unwrap_or_else(|| {
log::error!("Failed to find SteamDeck battery power_supply in sysfs (no results), using naive fallback");
root.power_supply_by_name("BAT1")
})
},
Err(e) => {
log::error!("Failed to find SteamDeck battery power_supply in sysfs ({}), using naive fallback", e);
root.power_supply_by_name("BAT1")
}
}
}
fn find_hwmon_sysfs(root: Option<impl AsRef<std::path::Path>>) -> HwMonPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.hwmon_by_name(super::util::JUPITER_HWMON_NAME) {
Ok(hwmon) => {
if hwmon.capable(attributes(HWMON_NEEDS.into_iter().copied())) {
hwmon
} else {
log::error!("Failed to find SteamDeck battery hwmon in sysfs (hwmon by name {} exists but missing attributes), using naive fallback", super::util::JUPITER_HWMON_NAME);
root.hwmon_by_index(5)
}
},
Err(e) => {
log::error!("Failed to find SteamDeck battery hwmon in sysfs ({}), using naive fallback", e);
root.hwmon_by_index(5)
}
}
}
#[inline] #[inline]
fn charge_mode_to_str(mode: ChargeMode) -> String { fn charge_mode_to_str(mode: ChargeMode) -> String {
match mode { match mode {
@ -248,22 +316,24 @@ impl Battery {
fn set_charge_rate(&mut self) -> Result<(), SettingError> { fn set_charge_rate(&mut self) -> Result<(), SettingError> {
if let Some(charge_rate) = self.charge_rate { if let Some(charge_rate) = self.charge_rate {
self.state.charge_rate_set = true; self.state.charge_rate_set = true;
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err( let attr = HwMonAttribute::custom("maximum_battery_charge_rate");
let path = attr.path(&*self.sysfs_hwmon);
self.sysfs_hwmon.set(attr, charge_rate).map_err(
|e| SettingError { |e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), msg: format!("Failed to write to `{}`: {}", path.display(), e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}, },
) )
} else if self.state.charge_rate_set { } else if self.state.charge_rate_set {
self.state.charge_rate_set = false; self.state.charge_rate_set = false;
usdpl_back::api::files::write_single( let attr = HwMonAttribute::custom("maximum_battery_charge_rate");
BATTERY_CHARGE_RATE_PATH, let path = attr.path(&*self.sysfs_hwmon);
self.limits.charge_rate.max, self.sysfs_hwmon.set(attr, self.limits.charge_rate.max,).map_err(
|e| SettingError {
msg: format!("Failed to write to `{}`: {}", path.display(), e),
setting: crate::settings::SettingVariant::Battery,
},
) )
.map_err(|e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
setting: crate::settings::SettingVariant::Battery,
})
} else { } else {
Ok(()) Ok(())
} }
@ -309,10 +379,11 @@ impl Battery {
} }
} }
pub fn read_current_now() -> Result<u64, SettingError> { pub fn read_current_now(&self) -> Result<u64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { let attr = PowerSupplyAttribute::CurrentNow;
match self.sysfs_bat.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), msg: format!("Failed to read from `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
// this value is in uA, while it's set in mA // this value is in uA, while it's set in mA
@ -321,16 +392,17 @@ impl Battery {
} }
} }
pub fn read_charge_power() -> Result<f64, SettingError> { pub fn read_charge_power(&self) -> Result<f64, SettingError> {
let current = Self::read_usb_current()?; let current = self.read_usb_current()?;
let voltage = Self::read_usb_voltage()?; let voltage = self.read_usb_voltage()?;
Ok(current * voltage) Ok(current * voltage)
} }
pub fn read_charge_now() -> Result<f64, SettingError> { pub fn read_charge_now(&self) -> Result<f64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { let attr = PowerSupplyAttribute::ChargeNow;
match self.sysfs_bat.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), msg: format!("Failed to read from `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
// convert to Wh // convert to Wh
@ -338,10 +410,11 @@ impl Battery {
} }
} }
pub fn read_charge_full() -> Result<f64, SettingError> { pub fn read_charge_full(&self) -> Result<f64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { let attr = PowerSupplyAttribute::ChargeFull;
match self.sysfs_bat.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), msg: format!("Failed to read from `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
// convert to Wh // convert to Wh
@ -349,13 +422,11 @@ impl Battery {
} }
} }
pub fn read_charge_design() -> Result<f64, SettingError> { pub fn read_charge_design(&self) -> Result<f64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { let attr = PowerSupplyAttribute::ChargeFullDesign;
match self.sysfs_bat.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!( msg: format!("Failed to read from `{:?}`: {}", attr, e),
"Failed to read from `{}`: {}",
BATTERY_CHARGE_DESIGN_PATH, e
),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
// convert to Wh // convert to Wh
@ -363,10 +434,11 @@ impl Battery {
} }
} }
pub fn read_usb_voltage() -> Result<f64, SettingError> { pub fn read_usb_voltage(&self) -> Result<f64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(USB_PD_IN_MVOLTAGE_PATH) { let attr = HwMonAttribute::new(HwMonAttributeType::In, 0, HwMonAttributeItem::Input);
match self.sysfs_hwmon.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", USB_PD_IN_MVOLTAGE_PATH, e), msg: format!("Failed to read from `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
// convert to V (from mV) // convert to V (from mV)
@ -374,10 +446,11 @@ impl Battery {
} }
} }
pub fn read_usb_current() -> Result<f64, SettingError> { pub fn read_usb_current(&self) -> Result<f64, SettingError> {
match usdpl_back::api::files::read_single::<_, u64, _>(USB_PD_IN_CURRENT_PATH) { let attr = HwMonAttribute::new(HwMonAttributeType::Curr, 1, HwMonAttributeItem::Input);
match self.sysfs_hwmon.attribute::<u64, _>(attr) {
Err(e) => Err(SettingError { Err(e) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", USB_PD_IN_CURRENT_PATH, e), msg: format!("Failed to read `{:?}`: {}", attr, e),
setting: crate::settings::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
Ok(val) => Ok((val as f64) / 1000.0), // mA -> A Ok(val) => Ok((val as f64) / 1000.0), // mA -> A
@ -399,6 +472,8 @@ impl Battery {
limits: oc_limits, limits: oc_limits,
state: crate::state::steam_deck::Battery::default(), state: crate::state::steam_deck::Battery::default(),
driver_mode: driver, driver_mode: driver,
sysfs_bat: Self::find_battery_sysfs(None::<&'static str>),
sysfs_hwmon: Arc::new(Self::find_hwmon_sysfs(None::<&'static str>)),
} }
} }
@ -438,6 +513,7 @@ impl Into<BatteryJson> for Battery {
charge_rate: self.charge_rate, charge_rate: self.charge_rate,
charge_mode: self.charge_mode.map(Self::charge_mode_to_str), charge_mode: self.charge_mode.map(Self::charge_mode_to_str),
events: self.events.into_iter().map(|x| x.into()).collect(), events: self.events.into_iter().map(|x| x.into()).collect(),
root: self.sysfs_bat.root().or(self.sysfs_hwmon.root()).and_then(|p| p.as_ref().to_str().map(|x| x.to_owned()))
} }
} }
} }
@ -530,7 +606,7 @@ impl TBattery for Battery {
} }
fn read_charge_full(&self) -> Option<f64> { fn read_charge_full(&self) -> Option<f64> {
match Self::read_charge_full() { match self.read_charge_full() {
Ok(x) => Some(x), Ok(x) => Some(x),
Err(e) => { Err(e) => {
log::warn!("read_charge_full err: {}", e.msg); log::warn!("read_charge_full err: {}", e.msg);
@ -540,7 +616,7 @@ impl TBattery for Battery {
} }
fn read_charge_now(&self) -> Option<f64> { fn read_charge_now(&self) -> Option<f64> {
match Self::read_charge_now() { match self.read_charge_now() {
Ok(x) => Some(x), Ok(x) => Some(x),
Err(e) => { Err(e) => {
log::warn!("read_charge_now err: {}", e.msg); log::warn!("read_charge_now err: {}", e.msg);
@ -550,7 +626,7 @@ impl TBattery for Battery {
} }
fn read_charge_design(&self) -> Option<f64> { fn read_charge_design(&self) -> Option<f64> {
match Self::read_charge_design() { match self.read_charge_design() {
Ok(x) => Some(x), Ok(x) => Some(x),
Err(e) => { Err(e) => {
log::warn!("read_charge_design err: {}", e.msg); log::warn!("read_charge_design err: {}", e.msg);
@ -561,7 +637,7 @@ impl TBattery for Battery {
fn read_current_now(&self) -> Option<f64> { fn read_current_now(&self) -> Option<f64> {
if self.limits.extra_readouts { if self.limits.extra_readouts {
match Self::read_current_now() { match self.read_current_now() {
Ok(x) => Some(x as f64), Ok(x) => Some(x as f64),
Err(e) => { Err(e) => {
log::warn!("read_current_now err: {}", e.msg); log::warn!("read_current_now err: {}", e.msg);
@ -575,7 +651,7 @@ impl TBattery for Battery {
fn read_charge_power(&self) -> Option<f64> { fn read_charge_power(&self) -> Option<f64> {
if self.limits.extra_readouts { if self.limits.extra_readouts {
match Self::read_charge_power() { match self.read_charge_power() {
Ok(x) => Some(x as f64), Ok(x) => Some(x as f64),
Err(e) => { Err(e) => {
log::warn!("read_current_now err: {}", e.msg); log::warn!("read_current_now err: {}", e.msg);
@ -601,6 +677,7 @@ impl TBattery for Battery {
charge_rate: None, charge_rate: None,
charge_mode: Some(ChargeMode::Idle), charge_mode: Some(ChargeMode::Idle),
is_triggered: false, is_triggered: false,
sysfs_hwmon: self.sysfs_hwmon.clone(),
}; };
} else { } else {
self.events.remove(index); self.events.remove(index);
@ -615,6 +692,7 @@ impl TBattery for Battery {
charge_rate: None, charge_rate: None,
charge_mode: Some(ChargeMode::Idle), charge_mode: Some(ChargeMode::Idle),
is_triggered: false, is_triggered: false,
sysfs_hwmon: self.sysfs_hwmon.clone(),
}); });
} }
// lower limit // lower limit
@ -631,6 +709,7 @@ impl TBattery for Battery {
charge_rate: None, charge_rate: None,
charge_mode: Some(ChargeMode::Normal), charge_mode: Some(ChargeMode::Normal),
is_triggered: false, is_triggered: false,
sysfs_hwmon: self.sysfs_hwmon.clone(),
}; };
} else { } else {
self.events.remove(index); self.events.remove(index);
@ -646,6 +725,7 @@ impl TBattery for Battery {
charge_rate: None, charge_rate: None,
charge_mode: Some(ChargeMode::Normal), charge_mode: Some(ChargeMode::Normal),
is_triggered: false, is_triggered: false,
sysfs_hwmon: self.sysfs_hwmon.clone(),
}); });
} }
} }
@ -668,7 +748,7 @@ impl TBattery for Battery {
log::debug!("Steam Deck power vibe check"); log::debug!("Steam Deck power vibe check");
let mut errors = Vec::new(); let mut errors = Vec::new();
let mut events = Vec::new(); let mut events = Vec::new();
match (Self::read_charge_full(), Self::read_charge_now()) { match (self.read_charge_full(), self.read_charge_now()) {
(Ok(full), Ok(now)) => events.push(PowerMode::BatteryCharge(now / full)), (Ok(full), Ok(now)) => events.push(PowerMode::BatteryCharge(now / full)),
(Err(e1), Err(e2)) => { (Err(e1), Err(e2)) => {
errors.push(e1); errors.push(e1);
@ -677,7 +757,7 @@ impl TBattery for Battery {
(Err(e), _) => errors.push(e), (Err(e), _) => errors.push(e),
(_, Err(e)) => errors.push(e), (_, Err(e)) => errors.push(e),
} }
match Self::read_usb_voltage() { match self.read_usb_voltage() {
Ok(voltage) => { Ok(voltage) => {
if voltage > 0.0 if voltage > 0.0
&& self.state.charger_state != crate::state::steam_deck::ChargeState::PluggedIn && self.state.charger_state != crate::state::steam_deck::ChargeState::PluggedIn

View file

@ -1,5 +1,7 @@
use std::convert::Into; use std::convert::Into;
use sysfuss::{BasicEntityPath, SysEntity, SysEntityAttributesExt};
use super::oc_limits::{CpuLimits, CpusLimits, OverclockLimits}; use super::oc_limits::{CpuLimits, CpusLimits, OverclockLimits};
use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT; use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT;
use crate::api::RangeLimit; use crate::api::RangeLimit;
@ -11,6 +13,10 @@ use crate::settings::{TCpu, TCpus};
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control";
const CARD_NEEDS: &[&'static str] = &[
super::DPM_FORCE_LIMITS_ATTRIBUTE
];
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Cpus { pub struct Cpus {
pub cpus: Vec<Cpu>, pub cpus: Vec<Cpu>,
@ -230,9 +236,11 @@ pub struct Cpu {
limits: CpuLimits, limits: CpuLimits,
index: usize, index: usize,
state: crate::state::steam_deck::Cpu, state: crate::state::steam_deck::Cpu,
sysfs: BasicEntityPath,
} }
const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; //const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage";
const CPU_CLOCK_LIMITS_ATTRIBUTE: &str = "device/pp_od_clk_voltage";
enum ClockType { enum ClockType {
Min = 0, Min = 0,
@ -250,6 +258,7 @@ impl Cpu {
limits: oc_limits, limits: oc_limits,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
sysfs: Self::find_card_sysfs(other.root),
}, },
_ => Self { _ => Self {
online: other.online, online: other.online,
@ -258,17 +267,35 @@ impl Cpu {
limits: oc_limits, limits: oc_limits,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
sysfs: Self::find_card_sysfs(other.root),
}, },
} }
} }
fn set_clock_limit(index: usize, speed: u64, mode: ClockType) -> Result<(), SettingError> { fn find_card_sysfs(root: Option<impl AsRef<std::path::Path>>) -> BasicEntityPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.class("drm", sysfuss::capability::attributes(CARD_NEEDS.into_iter().map(|s| s.to_string()))) {
Ok(mut iter) => {
iter.next()
.unwrap_or_else(|| {
log::error!("Failed to find SteamDeck drm in sysfs (no results), trying naive fallback");
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
})
},
Err(e) => {
log::error!("Failed to find SteamDeck drm in sysfs ({}), using naive fallback", e);
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
}
}
}
fn set_clock_limit(&self, index: usize, speed: u64, mode: ClockType) -> Result<(), SettingError> {
let payload = format!("p {} {} {}\n", index / 2, mode as u8, speed); let payload = format!("p {} {} {}\n", index / 2, mode as u8, speed);
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload).map_err(|e| { self.sysfs.set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload).map_err(|e| {
SettingError { SettingError {
msg: format!( msg: format!(
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload, CPU_CLOCK_LIMITS_PATH, e &payload, CPU_CLOCK_LIMITS_ATTRIBUTE, e
), ),
setting: crate::settings::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
} }
@ -279,7 +306,7 @@ impl Cpu {
let mut errors = Vec::new(); let mut errors = Vec::new();
if let Some(clock_limits) = &self.clock_limits { if let Some(clock_limits) = &self.clock_limits {
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index);
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level()?; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?;
log::debug!( log::debug!(
"Setting CPU {} (min, max) clockspeed to ({:?}, {:?})", "Setting CPU {} (min, max) clockspeed to ({:?}, {:?})",
self.index, self.index,
@ -289,7 +316,7 @@ impl Cpu {
self.state.clock_limits_set = true; self.state.clock_limits_set = true;
// max clock // max clock
if let Some(max) = clock_limits.max { if let Some(max) = clock_limits.max {
Self::set_clock_limit(self.index, max, ClockType::Max) self.set_clock_limit(self.index, max, ClockType::Max)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
} }
// min clock // min clock
@ -299,7 +326,7 @@ impl Cpu {
} else { } else {
min min
}; };
Self::set_clock_limit(self.index, valid_min, ClockType::Min) self.set_clock_limit(self.index, valid_min, ClockType::Min)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
} }
@ -316,17 +343,17 @@ impl Cpu {
self.state.clock_limits_set = false; self.state.clock_limits_set = false;
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index);
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
.enforce_level()?; .enforce_level(&self.sysfs)?;
if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() {
// always set clock speeds, since it doesn't reset correctly (kernel/hardware bug) // always set clock speeds, since it doesn't reset correctly (kernel/hardware bug)
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level()?; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?;
// disable manual clock limits // disable manual clock limits
log::debug!("Setting CPU {} to default clockspeed", self.index); log::debug!("Setting CPU {} to default clockspeed", self.index);
// max clock // max clock
Self::set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max) self.set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
// min clock // min clock
Self::set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min) self.set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
} }
// TODO remove this when it's no longer needed // TODO remove this when it's no longer needed
@ -346,17 +373,17 @@ impl Cpu {
let mut errors = Vec::new(); let mut errors = Vec::new();
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index);
// always set clock speeds, since it doesn't reset correctly (kernel/hardware bug) // always set clock speeds, since it doesn't reset correctly (kernel/hardware bug)
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level()?; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?;
// disable manual clock limits // disable manual clock limits
log::debug!("Setting CPU {} to default clockspeed", self.index); log::debug!("Setting CPU {} to default clockspeed", self.index);
// max clock // max clock
Self::set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max) self.set_clock_limit(self.index, self.limits.clock_max.max, ClockType::Max)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
// min clock // min clock
Self::set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min) self.set_clock_limit(self.index, self.limits.clock_min.min, ClockType::Min)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
Self::set_confirm().unwrap_or_else(|e| errors.push(e)); self.set_confirm().unwrap_or_else(|e| errors.push(e));
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index);
if errors.is_empty() { if errors.is_empty() {
Ok(()) Ok(())
@ -365,10 +392,10 @@ impl Cpu {
} }
} }
fn set_confirm() -> Result<(), SettingError> { fn set_confirm(&self) -> Result<(), SettingError> {
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { self.sysfs.set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n").map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e), msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_ATTRIBUTE, e),
setting: crate::settings::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
} }
}) })
@ -385,7 +412,7 @@ impl Cpu {
// commit changes (if no errors have already occured) // commit changes (if no errors have already occured)
if errors.is_empty() { if errors.is_empty() {
if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() {
Self::set_confirm().map_err(|e| vec![e]) self.set_confirm().map_err(|e| vec![e])
} else { } else {
Ok(()) Ok(())
} }
@ -470,6 +497,7 @@ impl Cpu {
limits: oc_limits, limits: oc_limits,
index: cpu_index, index: cpu_index,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
sysfs: Self::find_card_sysfs(None::<&'static str>)
} }
} }
@ -509,6 +537,7 @@ impl Into<CpuJson> for Cpu {
online: self.online, online: self.online,
clock_limits: self.clock_limits.map(|x| x.into()), clock_limits: self.clock_limits.map(|x| x.into()),
governor: self.governor, governor: self.governor,
root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|r| r.to_owned()))
} }
} }
} }

View file

@ -1,5 +1,7 @@
use std::convert::Into; use std::convert::Into;
use sysfuss::{BasicEntityPath, HwMonPath, SysEntity, capability::attributes, SysEntityAttributesExt, SysAttribute};
use super::oc_limits::{GpuLimits, OverclockLimits}; use super::oc_limits::{GpuLimits, OverclockLimits};
use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT; use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT;
use crate::api::RangeLimit; use crate::api::RangeLimit;
@ -8,8 +10,9 @@ use crate::settings::TGpu;
use crate::settings::{min_max_from_json, MinMax}; use crate::settings::{min_max_from_json, MinMax};
use crate::settings::{OnResume, OnSet, SettingError}; use crate::settings::{OnResume, OnSet, SettingError};
const SLOW_PPT: u8 = 1; // usually in /sys/class/hwmon/hwmon4/<attribute>
const FAST_PPT: u8 = 2; const SLOW_PPT_ATTRIBUTE: sysfuss::HwMonAttribute = sysfuss::HwMonAttribute::custom("power1_cap");
const FAST_PPT_ATTRIBUTE: sysfuss::HwMonAttribute = sysfuss::HwMonAttribute::custom("power2_cap");
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Gpu { pub struct Gpu {
@ -20,11 +23,22 @@ pub struct Gpu {
limits: GpuLimits, limits: GpuLimits,
state: crate::state::steam_deck::Gpu, state: crate::state::steam_deck::Gpu,
driver_mode: crate::persist::DriverJson, driver_mode: crate::persist::DriverJson,
sysfs_card: BasicEntityPath,
sysfs_hwmon: HwMonPath
} }
// same as CPU // same as CPU
const GPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; //const GPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage";
const GPU_MEMORY_DOWNCLOCK_PATH: &str = "/sys/class/drm/card0/device/pp_dpm_fclk"; //const GPU_MEMORY_DOWNCLOCK_PATH: &str = "/sys/class/drm/card0/device/pp_dpm_fclk";
const GPU_CLOCK_LIMITS_ATTRIBUTE: &str = "device/pp_od_clk_voltage";
const GPU_MEMORY_DOWNCLOCK_ATTRIBUTE: &str = "device/pp_dpm_fclk";
const CARD_NEEDS: &[&'static str] = &[
GPU_CLOCK_LIMITS_ATTRIBUTE,
GPU_MEMORY_DOWNCLOCK_ATTRIBUTE,
super::DPM_FORCE_LIMITS_ATTRIBUTE,
];
enum ClockType { enum ClockType {
Min = 0, Min = 0,
@ -49,6 +63,8 @@ impl Gpu {
limits: oc_limits.gpu, limits: oc_limits.gpu,
state: crate::state::steam_deck::Gpu::default(), state: crate::state::steam_deck::Gpu::default(),
driver_mode: driver, driver_mode: driver,
sysfs_card: Self::find_card_sysfs(other.root.clone()),
sysfs_hwmon: Self::find_hwmon_sysfs(other.root),
}, },
_ => Self { _ => Self {
fast_ppt: other.fast_ppt, fast_ppt: other.fast_ppt,
@ -58,27 +74,53 @@ impl Gpu {
limits: oc_limits.gpu, limits: oc_limits.gpu,
state: crate::state::steam_deck::Gpu::default(), state: crate::state::steam_deck::Gpu::default(),
driver_mode: driver, driver_mode: driver,
sysfs_card: Self::find_card_sysfs(other.root.clone()),
sysfs_hwmon: Self::find_hwmon_sysfs(other.root),
}, },
} }
} }
fn set_clock_limit(speed: u64, mode: ClockType) -> Result<(), SettingError> { fn find_card_sysfs(root: Option<impl AsRef<std::path::Path>>) -> BasicEntityPath {
let root = crate::settings::util::root_or_default_sysfs(root);
match root.class("drm", attributes(CARD_NEEDS.into_iter().map(|s| s.to_string()))) {
Ok(mut iter) => {
iter.next()
.unwrap_or_else(|| {
log::error!("Failed to find SteamDeck gpu drm in sysfs (no results), trying naive fallback");
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
})
},
Err(e) => {
log::error!("Failed to find SteamDeck gpu drm in sysfs ({}), using naive fallback", e);
BasicEntityPath::new(root.as_ref().join("sys/class/drm/card0"))
}
}
}
fn find_hwmon_sysfs(root: Option<impl AsRef<std::path::Path>>) -> HwMonPath {
let root = crate::settings::util::root_or_default_sysfs(root);
root.hwmon_by_name(super::util::GPU_HWMON_NAME).unwrap_or_else(|e| {
log::error!("Failed to find SteamDeck gpu hwmon in sysfs ({}), using naive fallback", e);
root.hwmon_by_index(4)
})
}
fn set_clock_limit(&self, speed: u64, mode: ClockType) -> Result<(), SettingError> {
let payload = format!("s {} {}\n", mode as u8, speed); let payload = format!("s {} {}\n", mode as u8, speed);
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload).map_err(|e| { let path = GPU_CLOCK_LIMITS_ATTRIBUTE.path(&self.sysfs_card);
self.sysfs_card.set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), &payload).map_err(|e| {
SettingError { SettingError {
msg: format!( msg: format!("Failed to write `{}` to `{}`: {}", &payload, path.display(), e),
"Failed to write `{}` to `{}`: {}",
&payload, GPU_CLOCK_LIMITS_PATH, e
),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
} }
}) })
} }
fn set_confirm() -> Result<(), SettingError> { fn set_confirm(&self) -> Result<(), SettingError> {
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { let path = GPU_CLOCK_LIMITS_ATTRIBUTE.path(&self.sysfs_card);
self.sysfs_card.set(GPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n").map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e), msg: format!("Failed to write `c` to `{}`: {}", path.display(), e),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
} }
}) })
@ -88,19 +130,19 @@ impl Gpu {
let mut errors = Vec::new(); let mut errors = Vec::new();
if let Some(clock_limits) = &self.clock_limits { if let Some(clock_limits) = &self.clock_limits {
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true);
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level()?; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs_card)?;
// set clock limits // set clock limits
self.state.clock_limits_set = true; self.state.clock_limits_set = true;
// max clock // max clock
if let Some(max) = clock_limits.max { if let Some(max) = clock_limits.max {
Self::set_clock_limit(max, ClockType::Max).unwrap_or_else(|e| errors.push(e)); self.set_clock_limit(max, ClockType::Max).unwrap_or_else(|e| errors.push(e));
} }
// min clock // min clock
if let Some(min) = clock_limits.min { if let Some(min) = clock_limits.min {
Self::set_clock_limit(min, ClockType::Min).unwrap_or_else(|e| errors.push(e)); self.set_clock_limit(min, ClockType::Min).unwrap_or_else(|e| errors.push(e));
} }
Self::set_confirm().unwrap_or_else(|e| errors.push(e)); self.set_confirm().unwrap_or_else(|e| errors.push(e));
} else if self.state.clock_limits_set } else if self.state.clock_limits_set
|| (self.state.is_resuming && !self.limits.skip_resume_reclock) || (self.state.is_resuming && !self.limits.skip_resume_reclock)
|| POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() || POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual()
@ -108,19 +150,19 @@ impl Gpu {
self.state.clock_limits_set = false; self.state.clock_limits_set = false;
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.slow_memory); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.slow_memory);
if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() {
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level()?; POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs_card)?;
// disable manual clock limits // disable manual clock limits
// max clock // max clock
Self::set_clock_limit(self.limits.clock_max.max, ClockType::Max) self.set_clock_limit(self.limits.clock_max.max, ClockType::Max)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
// min clock // min clock
Self::set_clock_limit(self.limits.clock_min.min, ClockType::Min) self.set_clock_limit(self.limits.clock_min.min, ClockType::Min)
.unwrap_or_else(|e| errors.push(e)); .unwrap_or_else(|e| errors.push(e));
Self::set_confirm().unwrap_or_else(|e| errors.push(e)); self.set_confirm().unwrap_or_else(|e| errors.push(e));
} else { } else {
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
.enforce_level() .enforce_level(&self.sysfs_card)
.unwrap_or_else(|mut e| errors.append(&mut e)); .unwrap_or_else(|mut e| errors.append(&mut e));
} }
} }
@ -131,10 +173,11 @@ impl Gpu {
} }
} }
fn set_slow_memory(slow: bool) -> Result<(), SettingError> { fn set_slow_memory(&self, slow: bool) -> Result<(), SettingError> {
usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, slow as u8).map_err(|e| { let path = GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.path(&self.sysfs_card);
self.sysfs_card.set(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned(), slow as u8).map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e), msg: format!("Failed to write to `{}`: {}", path.display(), e),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
} }
}) })
@ -146,14 +189,14 @@ impl Gpu {
if self.slow_memory { if self.slow_memory {
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true);
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
.enforce_level() .enforce_level(&self.sysfs_card)
.unwrap_or_else(|mut e| errors.append(&mut e)); .unwrap_or_else(|mut e| errors.append(&mut e));
Self::set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e)); self.set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e));
} else if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() { } else if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() {
Self::set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e)); self.set_slow_memory(self.slow_memory).unwrap_or_else(|e| errors.push(e));
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.clock_limits.is_some()); POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.clock_limits.is_some());
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
.enforce_level() .enforce_level(&self.sysfs_card)
.unwrap_or_else(|mut e| errors.append(&mut e)); .unwrap_or_else(|mut e| errors.append(&mut e));
} }
self.set_clocks() self.set_clocks()
@ -161,7 +204,7 @@ impl Gpu {
// commit changes (if no errors have already occured) // commit changes (if no errors have already occured)
if errors.is_empty() { if errors.is_empty() {
if self.slow_memory || self.clock_limits.is_some() { if self.slow_memory || self.clock_limits.is_some() {
Self::set_confirm().map_err(|e| { self.set_confirm().map_err(|e| {
errors.push(e); errors.push(e);
errors errors
}) })
@ -178,12 +221,11 @@ impl Gpu {
// set fast PPT // set fast PPT
if let Some(fast_ppt) = &self.fast_ppt { if let Some(fast_ppt) = &self.fast_ppt {
self.state.fast_ppt_set = true; self.state.fast_ppt_set = true;
let fast_ppt_path = gpu_power_path(FAST_PPT); self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt)
usdpl_back::api::files::write_single(&fast_ppt_path, fast_ppt)
.map_err(|e| SettingError { .map_err(|e| SettingError {
msg: format!( msg: format!(
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{:?}`: {}",
fast_ppt, &fast_ppt_path, e fast_ppt, FAST_PPT_ATTRIBUTE, e
), ),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}) })
@ -193,12 +235,11 @@ impl Gpu {
} else if self.state.fast_ppt_set { } else if self.state.fast_ppt_set {
self.state.fast_ppt_set = false; self.state.fast_ppt_set = false;
let fast_ppt = self.limits.fast_ppt_default; let fast_ppt = self.limits.fast_ppt_default;
let fast_ppt_path = gpu_power_path(FAST_PPT); self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt)
usdpl_back::api::files::write_single(&fast_ppt_path, fast_ppt)
.map_err(|e| SettingError { .map_err(|e| SettingError {
msg: format!( msg: format!(
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{:?}`: {}",
fast_ppt, &fast_ppt_path, e fast_ppt, FAST_PPT_ATTRIBUTE, e
), ),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}) })
@ -209,12 +250,11 @@ impl Gpu {
// set slow PPT // set slow PPT
if let Some(slow_ppt) = &self.slow_ppt { if let Some(slow_ppt) = &self.slow_ppt {
self.state.slow_ppt_set = true; self.state.slow_ppt_set = true;
let slow_ppt_path = gpu_power_path(SLOW_PPT); self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt)
usdpl_back::api::files::write_single(&slow_ppt_path, slow_ppt)
.map_err(|e| SettingError { .map_err(|e| SettingError {
msg: format!( msg: format!(
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{:?}`: {}",
slow_ppt, &slow_ppt_path, e slow_ppt, SLOW_PPT_ATTRIBUTE, e
), ),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}) })
@ -224,12 +264,11 @@ impl Gpu {
} else if self.state.slow_ppt_set { } else if self.state.slow_ppt_set {
self.state.slow_ppt_set = false; self.state.slow_ppt_set = false;
let slow_ppt = self.limits.slow_ppt_default; let slow_ppt = self.limits.slow_ppt_default;
let slow_ppt_path = gpu_power_path(SLOW_PPT); self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt)
usdpl_back::api::files::write_single(&slow_ppt_path, slow_ppt)
.map_err(|e| SettingError { .map_err(|e| SettingError {
msg: format!( msg: format!(
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{:?}`: {}",
slow_ppt, &slow_ppt_path, e slow_ppt, SLOW_PPT_ATTRIBUTE, e
), ),
setting: crate::settings::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}) })
@ -279,6 +318,8 @@ impl Gpu {
} else { } else {
crate::persist::DriverJson::SteamDeckAdvance crate::persist::DriverJson::SteamDeckAdvance
}, },
sysfs_card: Self::find_card_sysfs(None::<&'static str>),
sysfs_hwmon: Self::find_hwmon_sysfs(None::<&'static str>),
} }
} }
} }
@ -291,6 +332,7 @@ impl Into<GpuJson> for Gpu {
slow_ppt: self.slow_ppt, slow_ppt: self.slow_ppt,
clock_limits: self.clock_limits.map(|x| x.into()), clock_limits: self.clock_limits.map(|x| x.into()),
slow_memory: self.slow_memory, slow_memory: self.slow_memory,
root: self.sysfs_card.root().or(self.sysfs_hwmon.root()).and_then(|p| p.as_ref().to_str().map(|r| r.to_owned()))
} }
} }
} }
@ -372,8 +414,3 @@ impl TGpu for Gpu {
self.driver_mode.clone() self.driver_mode.clone()
} }
} }
#[inline]
fn gpu_power_path(power_number: u8) -> String {
format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number)
}

View file

@ -8,6 +8,6 @@ mod util;
pub use battery::Battery; pub use battery::Battery;
pub use cpu::{Cpu, Cpus}; pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu; pub use gpu::Gpu;
pub(self) use power_dpm_force::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT; pub(self) use power_dpm_force::{POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT, DPM_FORCE_LIMITS_ATTRIBUTE};
pub use util::flash_led; pub use util::flash_led;

View file

@ -5,6 +5,8 @@
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use sysfuss::{BasicEntityPath, SysEntityAttributesExt, SysAttribute};
use crate::settings::SettingError; use crate::settings::SettingError;
const DEFAULT_BITS: u64 = 0; const DEFAULT_BITS: u64 = 0;
@ -19,7 +21,8 @@ pub struct PDFPLManager(AtomicU64);
const GPU_BIT: usize = 1; const GPU_BIT: usize = 1;
const CPU_BITS_START: usize = 2; const CPU_BITS_START: usize = 2;
const DPM_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; //const DPM_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level";
pub const DPM_FORCE_LIMITS_ATTRIBUTE: &str = "device/power_dpm_force_performance_level";
impl PDFPLManager { impl PDFPLManager {
#[inline] #[inline]
@ -56,49 +59,44 @@ impl PDFPLManager {
self.set(DEFAULT_BITS); self.set(DEFAULT_BITS);
} }
pub fn enforce_level(&self) -> Result<(), Vec<SettingError>> { pub fn enforce_level(&self, entity: &BasicEntityPath) -> Result<(), Vec<SettingError>> {
let needs = self.needs_manual(); let needs = self.needs_manual();
let mut errors = Vec::new(); let mut errors = Vec::new();
let mode: String = usdpl_back::api::files::read_single(DPM_FORCE_LIMITS_PATH.to_owned()) let path = DPM_FORCE_LIMITS_ATTRIBUTE.path(entity);
let mode: String = entity.attribute(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned())
.map_err(|e| { .map_err(|e| {
vec![SettingError { vec![SettingError {
msg: format!("Failed to read `{}`: {}", DPM_FORCE_LIMITS_PATH, e), msg: format!("Failed to read `{}`: {}", path.display(), e),
setting: crate::settings::SettingVariant::General, setting: crate::settings::SettingVariant::General,
}] }]
})?; })?;
if mode != "manual" && needs { if mode != "manual" && needs {
log::info!("Setting `{}` to manual", DPM_FORCE_LIMITS_PATH); log::info!("Setting `{}` to manual", path.display());
// set manual control // set manual control
usdpl_back::api::files::write_single(DPM_FORCE_LIMITS_PATH, "manual") entity.set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "manual")
.map_err(|e| { .map_err(|e| {
errors.push(SettingError { errors.push(SettingError {
msg: format!( msg: format!("Failed to write `manual` to `{}`: {}", path.display(), e),
"Failed to write `manual` to `{}`: {}",
DPM_FORCE_LIMITS_PATH, e
),
setting: crate::settings::SettingVariant::General, setting: crate::settings::SettingVariant::General,
}) })
}) })
.unwrap_or(()); .unwrap_or(());
} else if mode != "auto" && !needs { } else if mode != "auto" && !needs {
log::info!("Setting `{}` to auto", DPM_FORCE_LIMITS_PATH); log::info!("Setting `{}` to auto", path.display());
// unset manual control // unset manual control
usdpl_back::api::files::write_single(DPM_FORCE_LIMITS_PATH, "auto") entity.set(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned(), "auto")
.map_err(|e| { .map_err(|e| {
errors.push(SettingError { errors.push(SettingError {
msg: format!( msg: format!("Failed to write `auto` to `{}`: {}", path.display(), e),
"Failed to write `auto` to `{}`: {}",
DPM_FORCE_LIMITS_PATH, e
),
setting: crate::settings::SettingVariant::General, setting: crate::settings::SettingVariant::General,
}) })
}) })
.unwrap_or(()); .unwrap_or(());
} }
if let Ok(mode_now) = if let Ok(mode_now) =
usdpl_back::api::files::read_single::<_, String, _>(DPM_FORCE_LIMITS_PATH.to_owned()) entity.attribute::<String, _>(DPM_FORCE_LIMITS_ATTRIBUTE.to_owned())
{ {
log::debug!("Mode for `{}` is now `{}`", DPM_FORCE_LIMITS_PATH, mode_now); log::debug!("Mode for `{}` is now `{}`", path.display(), mode_now);
} else { } else {
log::debug!("Error getting new mode for debugging purposes"); log::debug!("Error getting new mode for debugging purposes");
} }

View file

@ -8,6 +8,9 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::{Error, Read, Seek, SeekFrom, Write}; use std::io::{Error, Read, Seek, SeekFrom, Write};
pub const JUPITER_HWMON_NAME: &'static str = "jupiter";
pub const GPU_HWMON_NAME: &'static str = "amdgpu";
#[inline] #[inline]
fn write2(p0: u8, p1: u8) -> Result<usize, Error> { fn write2(p0: u8, p1: u8) -> Result<usize, Error> {
write_to(0x6c, 0x81)?; write_to(0x6c, 0x81)?;

View file

@ -14,6 +14,7 @@ impl Into<BatteryJson> for Battery {
charge_rate: None, charge_rate: None,
charge_mode: None, charge_mode: None,
events: Vec::default(), events: Vec::default(),
root: None,
} }
} }
} }

View file

@ -180,6 +180,7 @@ pub struct Cpu {
pub governor: String, pub governor: String,
index: usize, index: usize,
state: crate::state::steam_deck::Cpu, state: crate::state::steam_deck::Cpu,
root: std::path::PathBuf,
} }
impl Cpu { impl Cpu {
@ -191,12 +192,14 @@ impl Cpu {
governor: other.governor, governor: other.governor,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: other.root.unwrap_or_else(|| "/".to_owned()).into(),
}, },
_ => Self { _ => Self {
online: other.online, online: other.online,
governor: other.governor, governor: other.governor,
index: i, index: i,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: other.root.unwrap_or_else(|| "/".to_owned()).into(),
}, },
} }
} }
@ -243,6 +246,7 @@ impl Cpu {
.unwrap_or("schedutil".to_owned()), .unwrap_or("schedutil".to_owned()),
index: cpu_index, index: cpu_index,
state: crate::state::steam_deck::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
root: "/".into()
} }
} }
@ -263,6 +267,7 @@ impl Into<CpuJson> for Cpu {
online: self.online, online: self.online,
clock_limits: None, clock_limits: None,
governor: self.governor, governor: self.governor,
root: self.root.to_str().map(|s| s.to_owned()),
} }
} }
} }

View file

@ -29,6 +29,7 @@ impl Into<GpuJson> for Gpu {
slow_ppt: None, slow_ppt: None,
clock_limits: None, clock_limits: None,
slow_memory: false, slow_memory: false,
root: None,
} }
} }
} }

View file

@ -6,6 +6,18 @@ pub fn guess_smt(cpus: &Vec<crate::persist::CpuJson>) -> bool {
guess guess
} }
pub fn root_or_default_sysfs(root: Option<impl AsRef<std::path::Path>>) -> sysfuss::SysPath {
if let Some(root) = root {
sysfuss::SysPath::path(root)
} else {
sysfuss::SysPath::default()
}
}
pub fn always_satisfied<'a, X>(_: &'a X) -> bool {
true
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -76,6 +76,9 @@ fn version_filepath() -> std::path::PathBuf {
pub fn save_version_file() -> std::io::Result<usize> { pub fn save_version_file() -> std::io::Result<usize> {
let path = version_filepath(); let path = version_filepath();
if let Some(parent_dir) = path.parent() {
std::fs::create_dir_all(parent_dir)?;
}
std::fs::File::create(path)?.write(crate::consts::PACKAGE_VERSION.as_bytes()) std::fs::File::create(path)?.write(crate::consts::PACKAGE_VERSION.as_bytes())
} }

View file

@ -6,6 +6,7 @@ import {
staticClasses, staticClasses,
PanelSectionRow, PanelSectionRow,
ButtonItem, ButtonItem,
Navigation,
} from "decky-frontend-lib"; } from "decky-frontend-lib";
import { MESSAGE_LIST } from "../consts"; import { MESSAGE_LIST } from "../consts";
@ -30,7 +31,8 @@ export class DevMessages extends Component<backend.IdcProps> {
{message.title} {message.title}
</div> </div>
<PanelSectionRow> <PanelSectionRow>
<Field> <Field
onClick={()=> { if (message.url) { Navigation.NavigateToExternalWeb(message.url); } }}>
{message.body} {message.body}
</Field> </Field>
<ButtonItem <ButtonItem