Improve sysfs calls from providers to intelligently find the correct entry to fix #114
This commit is contained in:
parent
52f01ad7f3
commit
64cb4193f9
5
backend/Cargo.lock
generated
5
backend/Cargo.lock
generated
|
@ -1053,6 +1053,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"simplelog",
|
||||
"sysfuss",
|
||||
"tokio",
|
||||
"ureq",
|
||||
"usdpl-back",
|
||||
|
@ -1296,6 +1297,10 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysfuss"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
|
|
|
@ -15,6 +15,7 @@ readme = "../README.md"
|
|||
usdpl-back = { version = "0.10.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sysfuss = { version = "0.1", path = "../../sysfs-nav", features = ["derive"] }
|
||||
|
||||
# async
|
||||
tokio = { version = "*", features = ["time"] }
|
||||
|
|
|
@ -73,10 +73,6 @@ fn main() -> Result<(), ()> {
|
|||
}
|
||||
|
||||
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 =
|
||||
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);
|
||||
|
||||
let (api_handler, api_sender) = crate::api::handler::ApiMessageHandler::new();
|
||||
|
|
|
@ -9,6 +9,8 @@ pub struct BatteryJson {
|
|||
pub charge_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
pub events: Vec<BatteryEventJson>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub root: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
|
@ -24,6 +26,7 @@ impl Default for BatteryJson {
|
|||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
events: Vec::new(),
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ pub struct CpuJson {
|
|||
pub online: bool,
|
||||
pub clock_limits: Option<MinMaxJson<u64>>,
|
||||
pub governor: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub root: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for CpuJson {
|
||||
|
@ -20,6 +22,7 @@ impl Default for CpuJson {
|
|||
online: true,
|
||||
clock_limits: None,
|
||||
governor: "schedutil".to_owned(),
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ pub struct GpuJson {
|
|||
pub slow_ppt: Option<u64>,
|
||||
pub clock_limits: Option<MinMaxJson<u64>>,
|
||||
pub slow_memory: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub root: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for GpuJson {
|
||||
|
@ -19,6 +21,7 @@ impl Default for GpuJson {
|
|||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
slow_memory: false,
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use limits_core::json::GenericBatteryLimit;
|
||||
use sysfuss::SysEntity;
|
||||
|
||||
use crate::persist::BatteryJson;
|
||||
use crate::settings::TBattery;
|
||||
|
@ -10,6 +11,7 @@ use crate::settings::{OnResume, OnSet, SettingError};
|
|||
pub struct Battery {
|
||||
#[allow(dead_code)]
|
||||
limits: GenericBatteryLimit,
|
||||
sysfs: sysfuss::PowerSupplyPath,
|
||||
}
|
||||
|
||||
impl Into<BatteryJson> for Battery {
|
||||
|
@ -19,6 +21,7 @@ impl Into<BatteryJson> for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
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 {
|
||||
// TODO
|
||||
Self { limits }
|
||||
Self {
|
||||
limits,
|
||||
sysfs: Self::find_psu_sysfs(None::<&'static str>),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_and_limits(
|
||||
_other: BatteryJson,
|
||||
other: BatteryJson,
|
||||
_version: u64,
|
||||
limits: limits_core::json::GenericBatteryLimit,
|
||||
) -> Self {
|
||||
// TODO
|
||||
Self { limits }
|
||||
Self {
|
||||
limits,
|
||||
sysfs: Self::find_psu_sysfs(other.root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -202,6 +202,7 @@ pub struct Cpu {
|
|||
limits: GenericCpuLimit,
|
||||
index: usize,
|
||||
state: crate::state::steam_deck::Cpu,
|
||||
root: std::path::PathBuf,
|
||||
}
|
||||
|
||||
/*impl Cpu {
|
||||
|
@ -235,6 +236,7 @@ impl FromGenericCpuInfo for Cpu {
|
|||
limits,
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
root: "/".into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,6 +260,7 @@ impl FromGenericCpuInfo for Cpu {
|
|||
limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
root: other.root.unwrap_or_else(|| "/".to_string()).into(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
|
@ -266,6 +269,7 @@ impl FromGenericCpuInfo for Cpu {
|
|||
limits,
|
||||
index: i,
|
||||
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,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
governor: self.governor,
|
||||
root: self.root.to_str().map(|s| s.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use limits_core::json::GenericGpuLimit;
|
||||
use sysfuss::{BasicEntityPath, SysEntity};
|
||||
|
||||
use crate::api::RangeLimit;
|
||||
use crate::persist::GpuJson;
|
||||
|
@ -15,6 +16,7 @@ pub struct Gpu {
|
|||
pub slow_ppt: Option<u64>,
|
||||
pub clock_limits: Option<MinMax<u64>>,
|
||||
limits: GenericGpuLimit,
|
||||
sysfs: BasicEntityPath,
|
||||
}
|
||||
|
||||
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 {
|
||||
Self {
|
||||
slow_memory: false,
|
||||
|
@ -38,6 +57,7 @@ impl Gpu {
|
|||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
limits,
|
||||
sysfs: Self::find_card_sysfs(None::<&'static str>),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +85,7 @@ impl Gpu {
|
|||
},
|
||||
clock_limits: clock_lims,
|
||||
limits,
|
||||
sysfs: Self::find_card_sysfs(other.root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +98,7 @@ impl Into<GpuJson> for Gpu {
|
|||
slow_ppt: self.slow_ppt,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
slow_memory: false,
|
||||
root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|s| s.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
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::util::ChargeMode;
|
||||
|
@ -15,6 +19,8 @@ pub struct Battery {
|
|||
limits: BatteryLimits,
|
||||
state: crate::state::steam_deck::Battery,
|
||||
driver_mode: crate::persist::DriverJson,
|
||||
sysfs_bat: PowerSupplyPath,
|
||||
sysfs_hwmon: Arc<HwMonPath>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -32,6 +38,7 @@ struct EventInstruction {
|
|||
charge_rate: Option<u64>,
|
||||
charge_mode: Option<ChargeMode>,
|
||||
is_triggered: bool,
|
||||
sysfs_hwmon: Arc<HwMonPath>,
|
||||
}
|
||||
|
||||
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 {
|
||||
trigger: Self::str_to_trigger(&other.trigger).unwrap_or(EventTrigger::Ignored),
|
||||
charge_rate: other.charge_rate,
|
||||
|
@ -118,6 +125,7 @@ impl EventInstruction {
|
|||
.map(|x| Battery::str_to_charge_mode(&x))
|
||||
.flatten(),
|
||||
is_triggered: false,
|
||||
sysfs_hwmon: hwmon,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,12 +144,13 @@ impl EventInstruction {
|
|||
|
||||
fn set_charge_rate(&self) -> Result<(), SettingError> {
|
||||
if let Some(charge_rate) = self.charge_rate {
|
||||
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
let attr = HwMonAttribute::custom("maximum_battery_charge_rate");
|
||||
self.sysfs_hwmon.set(attr, charge_rate).map_err(
|
||||
|e| SettingError {
|
||||
msg: format!("Failed to write to `{:?}`: {}", attr, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
})
|
||||
.map(|_| ())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -173,13 +182,32 @@ impl Into<BatteryEventJson> for EventInstruction {
|
|||
|
||||
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_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_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_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 {
|
||||
#[inline]
|
||||
|
@ -191,6 +219,7 @@ impl Battery {
|
|||
} else {
|
||||
crate::persist::DriverJson::SteamDeckAdvance
|
||||
};
|
||||
let hwmon_sys = Arc::new(Self::find_hwmon_sysfs(None::<&'static str>));
|
||||
match version {
|
||||
0 => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
|
@ -201,11 +230,13 @@ impl Battery {
|
|||
events: other
|
||||
.events
|
||||
.into_iter()
|
||||
.map(|x| EventInstruction::from_json(x, version))
|
||||
.map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone()))
|
||||
.collect(),
|
||||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
driver_mode: driver,
|
||||
sysfs_bat: Self::find_battery_sysfs(None::<&'static str>),
|
||||
sysfs_hwmon: hwmon_sys,
|
||||
},
|
||||
_ => Self {
|
||||
charge_rate: other.charge_rate,
|
||||
|
@ -216,15 +247,52 @@ impl Battery {
|
|||
events: other
|
||||
.events
|
||||
.into_iter()
|
||||
.map(|x| EventInstruction::from_json(x, version))
|
||||
.map(|x| EventInstruction::from_json(x, version, hwmon_sys.clone()))
|
||||
.collect(),
|
||||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
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]
|
||||
fn charge_mode_to_str(mode: ChargeMode) -> String {
|
||||
match mode {
|
||||
|
@ -248,22 +316,24 @@ impl Battery {
|
|||
fn set_charge_rate(&mut self) -> Result<(), SettingError> {
|
||||
if let Some(charge_rate) = self.charge_rate {
|
||||
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 {
|
||||
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,
|
||||
},
|
||||
)
|
||||
} else if self.state.charge_rate_set {
|
||||
self.state.charge_rate_set = false;
|
||||
usdpl_back::api::files::write_single(
|
||||
BATTERY_CHARGE_RATE_PATH,
|
||||
self.limits.charge_rate.max,
|
||||
)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
|
||||
let attr = HwMonAttribute::custom("maximum_battery_charge_rate");
|
||||
let path = attr.path(&*self.sysfs_hwmon);
|
||||
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,
|
||||
})
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -309,10 +379,11 @@ impl Battery {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_current_now() -> Result<u64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) {
|
||||
pub fn read_current_now(&self) -> Result<u64, SettingError> {
|
||||
let attr = PowerSupplyAttribute::CurrentNow;
|
||||
match self.sysfs_bat.attribute::<u64, _>(attr) {
|
||||
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,
|
||||
}),
|
||||
// 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> {
|
||||
let current = Self::read_usb_current()?;
|
||||
let voltage = Self::read_usb_voltage()?;
|
||||
pub fn read_charge_power(&self) -> Result<f64, SettingError> {
|
||||
let current = self.read_usb_current()?;
|
||||
let voltage = self.read_usb_voltage()?;
|
||||
Ok(current * voltage)
|
||||
}
|
||||
|
||||
pub fn read_charge_now() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) {
|
||||
pub fn read_charge_now(&self) -> Result<f64, SettingError> {
|
||||
let attr = PowerSupplyAttribute::ChargeNow;
|
||||
match self.sysfs_bat.attribute::<u64, _>(attr) {
|
||||
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,
|
||||
}),
|
||||
// convert to Wh
|
||||
|
@ -338,10 +410,11 @@ impl Battery {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_full() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) {
|
||||
pub fn read_charge_full(&self) -> Result<f64, SettingError> {
|
||||
let attr = PowerSupplyAttribute::ChargeFull;
|
||||
match self.sysfs_bat.attribute::<u64, _>(attr) {
|
||||
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,
|
||||
}),
|
||||
// convert to Wh
|
||||
|
@ -349,13 +422,11 @@ impl Battery {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_charge_design() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) {
|
||||
pub fn read_charge_design(&self) -> Result<f64, SettingError> {
|
||||
let attr = PowerSupplyAttribute::ChargeFullDesign;
|
||||
match self.sysfs_bat.attribute::<u64, _>(attr) {
|
||||
Err(e) => Err(SettingError {
|
||||
msg: format!(
|
||||
"Failed to read from `{}`: {}",
|
||||
BATTERY_CHARGE_DESIGN_PATH, e
|
||||
),
|
||||
msg: format!("Failed to read from `{:?}`: {}", attr, e),
|
||||
setting: crate::settings::SettingVariant::Battery,
|
||||
}),
|
||||
// convert to Wh
|
||||
|
@ -363,10 +434,11 @@ impl Battery {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_usb_voltage() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(USB_PD_IN_MVOLTAGE_PATH) {
|
||||
pub fn read_usb_voltage(&self) -> Result<f64, SettingError> {
|
||||
let attr = HwMonAttribute::new(HwMonAttributeType::In, 0, HwMonAttributeItem::Input);
|
||||
match self.sysfs_hwmon.attribute::<u64, _>(attr) {
|
||||
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,
|
||||
}),
|
||||
// convert to V (from mV)
|
||||
|
@ -374,10 +446,11 @@ impl Battery {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_usb_current() -> Result<f64, SettingError> {
|
||||
match usdpl_back::api::files::read_single::<_, u64, _>(USB_PD_IN_CURRENT_PATH) {
|
||||
pub fn read_usb_current(&self) -> Result<f64, SettingError> {
|
||||
let attr = HwMonAttribute::new(HwMonAttributeType::Curr, 1, HwMonAttributeItem::Input);
|
||||
match self.sysfs_hwmon.attribute::<u64, _>(attr) {
|
||||
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,
|
||||
}),
|
||||
Ok(val) => Ok((val as f64) / 1000.0), // mA -> A
|
||||
|
@ -399,6 +472,8 @@ impl Battery {
|
|||
limits: oc_limits,
|
||||
state: crate::state::steam_deck::Battery::default(),
|
||||
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_mode: self.charge_mode.map(Self::charge_mode_to_str),
|
||||
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> {
|
||||
match Self::read_charge_full() {
|
||||
match self.read_charge_full() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_full err: {}", e.msg);
|
||||
|
@ -540,7 +616,7 @@ impl TBattery for Battery {
|
|||
}
|
||||
|
||||
fn read_charge_now(&self) -> Option<f64> {
|
||||
match Self::read_charge_now() {
|
||||
match self.read_charge_now() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_now err: {}", e.msg);
|
||||
|
@ -550,7 +626,7 @@ impl TBattery for Battery {
|
|||
}
|
||||
|
||||
fn read_charge_design(&self) -> Option<f64> {
|
||||
match Self::read_charge_design() {
|
||||
match self.read_charge_design() {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::warn!("read_charge_design err: {}", e.msg);
|
||||
|
@ -561,7 +637,7 @@ impl TBattery for Battery {
|
|||
|
||||
fn read_current_now(&self) -> Option<f64> {
|
||||
if self.limits.extra_readouts {
|
||||
match Self::read_current_now() {
|
||||
match self.read_current_now() {
|
||||
Ok(x) => Some(x as f64),
|
||||
Err(e) => {
|
||||
log::warn!("read_current_now err: {}", e.msg);
|
||||
|
@ -575,7 +651,7 @@ impl TBattery for Battery {
|
|||
|
||||
fn read_charge_power(&self) -> Option<f64> {
|
||||
if self.limits.extra_readouts {
|
||||
match Self::read_charge_power() {
|
||||
match self.read_charge_power() {
|
||||
Ok(x) => Some(x as f64),
|
||||
Err(e) => {
|
||||
log::warn!("read_current_now err: {}", e.msg);
|
||||
|
@ -601,6 +677,7 @@ impl TBattery for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: Some(ChargeMode::Idle),
|
||||
is_triggered: false,
|
||||
sysfs_hwmon: self.sysfs_hwmon.clone(),
|
||||
};
|
||||
} else {
|
||||
self.events.remove(index);
|
||||
|
@ -615,6 +692,7 @@ impl TBattery for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: Some(ChargeMode::Idle),
|
||||
is_triggered: false,
|
||||
sysfs_hwmon: self.sysfs_hwmon.clone(),
|
||||
});
|
||||
}
|
||||
// lower limit
|
||||
|
@ -631,6 +709,7 @@ impl TBattery for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: Some(ChargeMode::Normal),
|
||||
is_triggered: false,
|
||||
sysfs_hwmon: self.sysfs_hwmon.clone(),
|
||||
};
|
||||
} else {
|
||||
self.events.remove(index);
|
||||
|
@ -646,6 +725,7 @@ impl TBattery for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: Some(ChargeMode::Normal),
|
||||
is_triggered: false,
|
||||
sysfs_hwmon: self.sysfs_hwmon.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -668,7 +748,7 @@ impl TBattery for Battery {
|
|||
log::debug!("Steam Deck power vibe check");
|
||||
let mut errors = 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)),
|
||||
(Err(e1), Err(e2)) => {
|
||||
errors.push(e1);
|
||||
|
@ -677,7 +757,7 @@ impl TBattery for Battery {
|
|||
(Err(e), _) => errors.push(e),
|
||||
(_, Err(e)) => errors.push(e),
|
||||
}
|
||||
match Self::read_usb_voltage() {
|
||||
match self.read_usb_voltage() {
|
||||
Ok(voltage) => {
|
||||
if voltage > 0.0
|
||||
&& self.state.charger_state != crate::state::steam_deck::ChargeState::PluggedIn
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use sysfuss::{BasicEntityPath, SysEntity, SysEntityAttributesExt};
|
||||
|
||||
use super::oc_limits::{CpuLimits, CpusLimits, OverclockLimits};
|
||||
use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT;
|
||||
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_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control";
|
||||
|
||||
const CARD_NEEDS: &[&'static str] = &[
|
||||
super::DPM_FORCE_LIMITS_ATTRIBUTE
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cpus {
|
||||
pub cpus: Vec<Cpu>,
|
||||
|
@ -230,9 +236,11 @@ pub struct Cpu {
|
|||
limits: CpuLimits,
|
||||
index: usize,
|
||||
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 {
|
||||
Min = 0,
|
||||
|
@ -250,6 +258,7 @@ impl Cpu {
|
|||
limits: oc_limits,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
sysfs: Self::find_card_sysfs(other.root),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
|
@ -258,17 +267,35 @@ impl Cpu {
|
|||
limits: oc_limits,
|
||||
index: i,
|
||||
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);
|
||||
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 {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload, CPU_CLOCK_LIMITS_PATH, e
|
||||
&payload, CPU_CLOCK_LIMITS_ATTRIBUTE, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Cpu,
|
||||
}
|
||||
|
@ -279,7 +306,7 @@ impl Cpu {
|
|||
let mut errors = Vec::new();
|
||||
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.enforce_level()?;
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.enforce_level(&self.sysfs)?;
|
||||
log::debug!(
|
||||
"Setting CPU {} (min, max) clockspeed to ({:?}, {:?})",
|
||||
self.index,
|
||||
|
@ -289,7 +316,7 @@ impl Cpu {
|
|||
self.state.clock_limits_set = true;
|
||||
// max clock
|
||||
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));
|
||||
}
|
||||
// min clock
|
||||
|
@ -299,7 +326,7 @@ impl Cpu {
|
|||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -316,17 +343,17 @@ impl Cpu {
|
|||
self.state.clock_limits_set = false;
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(false, self.index);
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
|
||||
.enforce_level()?;
|
||||
.enforce_level(&self.sysfs)?;
|
||||
if POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual() {
|
||||
// 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
|
||||
log::debug!("Setting CPU {} to default clockspeed", self.index);
|
||||
// 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));
|
||||
// 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));
|
||||
}
|
||||
// TODO remove this when it's no longer needed
|
||||
|
@ -346,17 +373,17 @@ impl Cpu {
|
|||
let mut errors = Vec::new();
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_cpu(true, self.index);
|
||||
// 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
|
||||
log::debug!("Setting CPU {} to default clockspeed", self.index);
|
||||
// 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));
|
||||
// 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));
|
||||
|
||||
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);
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
|
@ -365,10 +392,10 @@ impl Cpu {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_confirm() -> Result<(), SettingError> {
|
||||
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
|
||||
fn set_confirm(&self) -> Result<(), SettingError> {
|
||||
self.sysfs.set(CPU_CLOCK_LIMITS_ATTRIBUTE.to_owned(), "c\n").map_err(|e| {
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
@ -385,7 +412,7 @@ impl Cpu {
|
|||
// commit changes (if no errors have already occured)
|
||||
if errors.is_empty() {
|
||||
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 {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -470,6 +497,7 @@ impl Cpu {
|
|||
limits: oc_limits,
|
||||
index: cpu_index,
|
||||
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,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
governor: self.governor,
|
||||
root: self.sysfs.root().and_then(|p| p.as_ref().to_str().map(|r| r.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::convert::Into;
|
||||
|
||||
use sysfuss::{BasicEntityPath, HwMonPath, SysEntity, capability::attributes, SysEntityAttributesExt, SysAttribute};
|
||||
|
||||
use super::oc_limits::{GpuLimits, OverclockLimits};
|
||||
use super::POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT;
|
||||
use crate::api::RangeLimit;
|
||||
|
@ -8,8 +10,9 @@ use crate::settings::TGpu;
|
|||
use crate::settings::{min_max_from_json, MinMax};
|
||||
use crate::settings::{OnResume, OnSet, SettingError};
|
||||
|
||||
const SLOW_PPT: u8 = 1;
|
||||
const FAST_PPT: u8 = 2;
|
||||
// usually in /sys/class/hwmon/hwmon4/<attribute>
|
||||
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)]
|
||||
pub struct Gpu {
|
||||
|
@ -20,11 +23,22 @@ pub struct Gpu {
|
|||
limits: GpuLimits,
|
||||
state: crate::state::steam_deck::Gpu,
|
||||
driver_mode: crate::persist::DriverJson,
|
||||
sysfs_card: BasicEntityPath,
|
||||
sysfs_hwmon: HwMonPath
|
||||
}
|
||||
|
||||
// same as CPU
|
||||
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_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_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 {
|
||||
Min = 0,
|
||||
|
@ -49,6 +63,8 @@ impl Gpu {
|
|||
limits: oc_limits.gpu,
|
||||
state: crate::state::steam_deck::Gpu::default(),
|
||||
driver_mode: driver,
|
||||
sysfs_card: Self::find_card_sysfs(other.root.clone()),
|
||||
sysfs_hwmon: Self::find_hwmon_sysfs(other.root),
|
||||
},
|
||||
_ => Self {
|
||||
fast_ppt: other.fast_ppt,
|
||||
|
@ -58,27 +74,53 @@ impl Gpu {
|
|||
limits: oc_limits.gpu,
|
||||
state: crate::state::steam_deck::Gpu::default(),
|
||||
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);
|
||||
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 {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
&payload, GPU_CLOCK_LIMITS_PATH, e
|
||||
),
|
||||
msg: format!("Failed to write `{}` to `{}`: {}", &payload, path.display(), e),
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn set_confirm() -> Result<(), SettingError> {
|
||||
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
|
||||
fn set_confirm(&self) -> Result<(), SettingError> {
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
@ -88,19 +130,19 @@ impl Gpu {
|
|||
let mut errors = Vec::new();
|
||||
if let Some(clock_limits) = &self.clock_limits {
|
||||
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
|
||||
self.state.clock_limits_set = true;
|
||||
// max clock
|
||||
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
|
||||
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
|
||||
|| (self.state.is_resuming && !self.limits.skip_resume_reclock)
|
||||
|| POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.needs_manual()
|
||||
|
@ -108,19 +150,19 @@ impl Gpu {
|
|||
self.state.clock_limits_set = false;
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(self.slow_memory);
|
||||
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
|
||||
// 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));
|
||||
// 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));
|
||||
|
||||
Self::set_confirm().unwrap_or_else(|e| errors.push(e));
|
||||
self.set_confirm().unwrap_or_else(|e| errors.push(e));
|
||||
} else {
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
|
||||
.enforce_level()
|
||||
.enforce_level(&self.sysfs_card)
|
||||
.unwrap_or_else(|mut e| errors.append(&mut e));
|
||||
}
|
||||
}
|
||||
|
@ -131,10 +173,11 @@ impl Gpu {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_slow_memory(slow: bool) -> Result<(), SettingError> {
|
||||
usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, slow as u8).map_err(|e| {
|
||||
fn set_slow_memory(&self, slow: bool) -> Result<(), SettingError> {
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
@ -146,14 +189,14 @@ impl Gpu {
|
|||
if self.slow_memory {
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT.set_gpu(true);
|
||||
POWER_DPM_FORCE_PERFORMANCE_LEVEL_MGMT
|
||||
.enforce_level()
|
||||
.enforce_level(&self.sysfs_card)
|
||||
.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() {
|
||||
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
|
||||
.enforce_level()
|
||||
.enforce_level(&self.sysfs_card)
|
||||
.unwrap_or_else(|mut e| errors.append(&mut e));
|
||||
}
|
||||
self.set_clocks()
|
||||
|
@ -161,7 +204,7 @@ impl Gpu {
|
|||
// commit changes (if no errors have already occured)
|
||||
if errors.is_empty() {
|
||||
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
|
||||
})
|
||||
|
@ -178,12 +221,11 @@ impl Gpu {
|
|||
// set fast PPT
|
||||
if let Some(fast_ppt) = &self.fast_ppt {
|
||||
self.state.fast_ppt_set = true;
|
||||
let fast_ppt_path = gpu_power_path(FAST_PPT);
|
||||
usdpl_back::api::files::write_single(&fast_ppt_path, fast_ppt)
|
||||
self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
fast_ppt, &fast_ppt_path, e
|
||||
"Failed to write `{}` to `{:?}`: {}",
|
||||
fast_ppt, FAST_PPT_ATTRIBUTE, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
})
|
||||
|
@ -193,12 +235,11 @@ impl Gpu {
|
|||
} else if self.state.fast_ppt_set {
|
||||
self.state.fast_ppt_set = false;
|
||||
let fast_ppt = self.limits.fast_ppt_default;
|
||||
let fast_ppt_path = gpu_power_path(FAST_PPT);
|
||||
usdpl_back::api::files::write_single(&fast_ppt_path, fast_ppt)
|
||||
self.sysfs_hwmon.set(FAST_PPT_ATTRIBUTE, fast_ppt)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
fast_ppt, &fast_ppt_path, e
|
||||
"Failed to write `{}` to `{:?}`: {}",
|
||||
fast_ppt, FAST_PPT_ATTRIBUTE, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
})
|
||||
|
@ -209,12 +250,11 @@ impl Gpu {
|
|||
// set slow PPT
|
||||
if let Some(slow_ppt) = &self.slow_ppt {
|
||||
self.state.slow_ppt_set = true;
|
||||
let slow_ppt_path = gpu_power_path(SLOW_PPT);
|
||||
usdpl_back::api::files::write_single(&slow_ppt_path, slow_ppt)
|
||||
self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
slow_ppt, &slow_ppt_path, e
|
||||
"Failed to write `{}` to `{:?}`: {}",
|
||||
slow_ppt, SLOW_PPT_ATTRIBUTE, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
})
|
||||
|
@ -224,12 +264,11 @@ impl Gpu {
|
|||
} else if self.state.slow_ppt_set {
|
||||
self.state.slow_ppt_set = false;
|
||||
let slow_ppt = self.limits.slow_ppt_default;
|
||||
let slow_ppt_path = gpu_power_path(SLOW_PPT);
|
||||
usdpl_back::api::files::write_single(&slow_ppt_path, slow_ppt)
|
||||
self.sysfs_hwmon.set(SLOW_PPT_ATTRIBUTE, slow_ppt)
|
||||
.map_err(|e| SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `{}` to `{}`: {}",
|
||||
slow_ppt, &slow_ppt_path, e
|
||||
"Failed to write `{}` to `{:?}`: {}",
|
||||
slow_ppt, SLOW_PPT_ATTRIBUTE, e
|
||||
),
|
||||
setting: crate::settings::SettingVariant::Gpu,
|
||||
})
|
||||
|
@ -279,6 +318,8 @@ impl Gpu {
|
|||
} else {
|
||||
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,
|
||||
clock_limits: self.clock_limits.map(|x| x.into()),
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gpu_power_path(power_number: u8) -> String {
|
||||
format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ mod util;
|
|||
pub use battery::Battery;
|
||||
pub use cpu::{Cpu, Cpus};
|
||||
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;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use sysfuss::{BasicEntityPath, SysEntityAttributesExt, SysAttribute};
|
||||
|
||||
use crate::settings::SettingError;
|
||||
|
||||
const DEFAULT_BITS: u64 = 0;
|
||||
|
@ -19,7 +21,8 @@ pub struct PDFPLManager(AtomicU64);
|
|||
const GPU_BIT: usize = 1;
|
||||
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 {
|
||||
#[inline]
|
||||
|
@ -56,49 +59,44 @@ impl PDFPLManager {
|
|||
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 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| {
|
||||
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,
|
||||
}]
|
||||
})?;
|
||||
if mode != "manual" && needs {
|
||||
log::info!("Setting `{}` to manual", DPM_FORCE_LIMITS_PATH);
|
||||
log::info!("Setting `{}` to manual", path.display());
|
||||
// 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| {
|
||||
errors.push(SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `manual` to `{}`: {}",
|
||||
DPM_FORCE_LIMITS_PATH, e
|
||||
),
|
||||
msg: format!("Failed to write `manual` to `{}`: {}", path.display(), e),
|
||||
setting: crate::settings::SettingVariant::General,
|
||||
})
|
||||
})
|
||||
.unwrap_or(());
|
||||
} else if mode != "auto" && !needs {
|
||||
log::info!("Setting `{}` to auto", DPM_FORCE_LIMITS_PATH);
|
||||
log::info!("Setting `{}` to auto", path.display());
|
||||
// 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| {
|
||||
errors.push(SettingError {
|
||||
msg: format!(
|
||||
"Failed to write `auto` to `{}`: {}",
|
||||
DPM_FORCE_LIMITS_PATH, e
|
||||
),
|
||||
msg: format!("Failed to write `auto` to `{}`: {}", path.display(), e),
|
||||
setting: crate::settings::SettingVariant::General,
|
||||
})
|
||||
})
|
||||
.unwrap_or(());
|
||||
}
|
||||
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 {
|
||||
log::debug!("Error getting new mode for debugging purposes");
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
use std::fs::OpenOptions;
|
||||
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]
|
||||
fn write2(p0: u8, p1: u8) -> Result<usize, Error> {
|
||||
write_to(0x6c, 0x81)?;
|
||||
|
|
|
@ -14,6 +14,7 @@ impl Into<BatteryJson> for Battery {
|
|||
charge_rate: None,
|
||||
charge_mode: None,
|
||||
events: Vec::default(),
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,6 +180,7 @@ pub struct Cpu {
|
|||
pub governor: String,
|
||||
index: usize,
|
||||
state: crate::state::steam_deck::Cpu,
|
||||
root: std::path::PathBuf,
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
|
@ -191,12 +192,14 @@ impl Cpu {
|
|||
governor: other.governor,
|
||||
index: i,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
root: other.root.unwrap_or_else(|| "/".to_owned()).into(),
|
||||
},
|
||||
_ => Self {
|
||||
online: other.online,
|
||||
governor: other.governor,
|
||||
index: i,
|
||||
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()),
|
||||
index: cpu_index,
|
||||
state: crate::state::steam_deck::Cpu::default(),
|
||||
root: "/".into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +267,7 @@ impl Into<CpuJson> for Cpu {
|
|||
online: self.online,
|
||||
clock_limits: None,
|
||||
governor: self.governor,
|
||||
root: self.root.to_str().map(|s| s.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ impl Into<GpuJson> for Gpu {
|
|||
slow_ppt: None,
|
||||
clock_limits: None,
|
||||
slow_memory: false,
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,18 @@ pub fn guess_smt(cpus: &Vec<crate::persist::CpuJson>) -> bool {
|
|||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -76,6 +76,9 @@ fn version_filepath() -> std::path::PathBuf {
|
|||
|
||||
pub fn save_version_file() -> std::io::Result<usize> {
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
staticClasses,
|
||||
PanelSectionRow,
|
||||
ButtonItem,
|
||||
Navigation,
|
||||
} from "decky-frontend-lib";
|
||||
|
||||
import { MESSAGE_LIST } from "../consts";
|
||||
|
@ -30,7 +31,8 @@ export class DevMessages extends Component<backend.IdcProps> {
|
|||
{message.title}
|
||||
</div>
|
||||
<PanelSectionRow>
|
||||
<Field>
|
||||
<Field
|
||||
onClick={()=> { if (message.url) { Navigation.NavigateToExternalWeb(message.url); } }}>
|
||||
{message.body}
|
||||
</Field>
|
||||
<ButtonItem
|
||||
|
|
Loading…
Reference in a new issue