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_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"

View File

@ -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"] }

View File

@ -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();

View File

@ -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,
}
}
}

View File

@ -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,
}
}
}

View File

@ -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,
}
}
}

View File

@ -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)
}
}
}

View File

@ -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()),
}
}
}

View File

@ -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()))
}
}
}

View File

@ -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,
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,
},
)
.map_err(|e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, 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

View File

@ -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()))
}
}
}

View File

@ -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