diff --git a/backend/src/persist/driver.rs b/backend/src/persist/driver.rs index bf8b891..91244a2 100644 --- a/backend/src/persist/driver.rs +++ b/backend/src/persist/driver.rs @@ -9,6 +9,8 @@ pub enum DriverJson { SteamDeck, #[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")] SteamDeckAdvance, + #[serde(rename = "generic")] + Generic, #[serde(rename = "unknown")] Unknown, } diff --git a/backend/src/settings/driver.rs b/backend/src/settings/driver.rs index 7f8b160..a9a5d09 100644 --- a/backend/src/settings/driver.rs +++ b/backend/src/settings/driver.rs @@ -26,6 +26,8 @@ fn auto_detect() -> DriverJson { } else { DriverJson::SteamDeckAdvance } + } else if let Some(_) = lscpu.find("model name\t: AMD Ryzen") { + DriverJson::Generic } else { DriverJson::Unknown } @@ -88,6 +90,17 @@ impl Driver { gpu: Box::new(super::steam_deck_adv::Gpu::from_json(settings.gpu, settings.version)), battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), }), + DriverJson::Generic => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::Unknown, + }), + cpus: Box::new(super::generic::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::generic::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::generic::Battery), + }), DriverJson::Unknown => Ok(Self { general: Box::new(General { persistent: settings.persistent, @@ -127,6 +140,17 @@ impl Driver { gpu: Box::new(super::steam_deck_adv::Gpu::system_default()), battery: Box::new(super::steam_deck::Battery::system_default()), }, + DriverJson::Generic => Self { + general: Box::new(General { + persistent: false, + path: json_path, + name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + driver: DriverJson::Unknown, + }), + cpus: Box::new(super::generic::Cpus::system_default()), + gpu: Box::new(super::generic::Gpu::system_default()), + battery: Box::new(super::generic::Battery), + }, DriverJson::Unknown => Self { general: Box::new(General { persistent: false, @@ -159,6 +183,7 @@ pub fn maybe_do_button() { std::thread::sleep(period); } }, + DriverJson::Generic => log::warn!("You need to come up with something fun on generic"), DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"), } } diff --git a/backend/src/settings/generic/battery.rs b/backend/src/settings/generic/battery.rs new file mode 100644 index 0000000..6b6ad6b --- /dev/null +++ b/backend/src/settings/generic/battery.rs @@ -0,0 +1,115 @@ +use std::convert::Into; + +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TBattery; +use crate::persist::BatteryJson; + +#[derive(Debug, Clone)] +pub struct Battery; + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + BatteryJson { + charge_rate: None, + charge_mode: None, + } + } +} + +impl Battery { + fn read_f64>(path: P) -> Result { + let path = path.as_ref(); + match usdpl_back::api::files::read_single::<_, f64, _>(path) { + Err((Some(e), None)) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", path.display(), e), + setting: crate::settings::SettingVariant::Battery, + }), + Err((None, Some(e))) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", path.display(), e), + setting: crate::settings::SettingVariant::Battery, + }), + Err(_) => panic!( + "Invalid error while reading from `{}`", + path.display() + ), + // this value is in uA, while it's set in mA + // so convert this to mA for consistency + Ok(val) => Ok(val / 1000.0), + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + crate::api::BatteryLimits { + charge_current: None, + charge_current_step: 50, + charge_modes: vec![], + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, _rate: Option) { + } + + fn get_charge_rate(&self) -> Option { + None + } + + fn charge_mode(&mut self, _rate: Option) { + } + + fn get_charge_mode(&self) -> Option { + None + } + + fn read_charge_full(&self) -> Option { + match Self::read_f64("/sys/class/power_supply/BAT0/energy_full") { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_full err: {}", e.msg); + None + } + } + } + + fn read_charge_now(&self) -> Option { + match Self::read_f64("/sys/class/power_supply/BAT0/energy_now") { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_now err: {}", e.msg); + None + } + } + } + + fn read_charge_design(&self) -> Option { + match Self::read_f64("/sys/class/power_supply/BAT0/energy_design") { + Ok(x) => Some(x), + Err(e) => { + log::warn!("read_charge_design err: {}", e.msg); + None + } + } + } + + fn read_current_now(&self) -> Option { + None + } +} diff --git a/backend/src/settings/generic/cpu.rs b/backend/src/settings/generic/cpu.rs new file mode 100644 index 0000000..48d4b99 --- /dev/null +++ b/backend/src/settings/generic/cpu.rs @@ -0,0 +1,289 @@ +use std::convert::Into; + +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::{TCpus, TCpu}; +use crate::persist::CpuJson; + +const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; +const CPU_SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; + +#[derive(Debug, Clone)] +pub struct Cpus { + pub cpus: Vec, + pub smt: bool, + pub smt_capable: bool, +} + +impl OnSet for Cpus { + fn on_set(&mut self) -> Result<(), SettingError> { + if self.smt_capable { + // toggle SMT + if self.smt { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "on").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `on` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } else { + usdpl_back::api::files::write_single(CPU_SMT_PATH, "off").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `off` to `{}`: {}", + CPU_SMT_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + } + for (i, cpu) in self.cpus.as_mut_slice().iter_mut().enumerate() { + cpu.state.do_set_online = self.smt || i % 2 == 0; + cpu.on_set()?; + } + Ok(()) + } +} + +impl OnResume for Cpus { + fn on_resume(&self) -> Result<(), SettingError> { + for cpu in &self.cpus { + cpu.on_resume()?; + } + Ok(()) + } +} + +impl Cpus { + pub fn cpu_count() -> Option { + let mut data: String = usdpl_back::api::files::read_single(CPU_PRESENT_PATH) + .unwrap_or_else(|_| "0-7".to_string() /* Steam Deck's default */); + if let Some(dash_index) = data.find('-') { + let data = data.split_off(dash_index + 1); + if let Ok(max_cpu) = data.parse::() { + return Some(max_cpu + 1); + } + } + log::warn!("Failed to parse CPU info from kernel, is Tux evil?"); + None + } + + fn system_smt_capabilities() -> (bool, bool) { + match usdpl_back::api::files::read_single::<_, String, _>(CPU_SMT_PATH) { + Ok(val) => (val.trim().to_lowercase() == "on", true), + Err(_) => (false, false) + } + } + + pub fn system_default() -> Self { + if let Some(max_cpu) = Self::cpu_count() { + let mut sys_cpus = Vec::with_capacity(max_cpu); + for i in 0..max_cpu { + sys_cpus.push(Cpu::from_sys(i)); + } + let (smt_status, can_smt) = Self::system_smt_capabilities(); + Self { + cpus: sys_cpus, + smt: smt_status, + smt_capable: can_smt, + } + } else { + Self { + cpus: vec![], + smt: false, + smt_capable: false, + } + } + } + + #[inline] + pub fn from_json(mut other: Vec, version: u64) -> Self { + let (_, can_smt) = Self::system_smt_capabilities(); + let mut result = Vec::with_capacity(other.len()); + let max_cpus = Self::cpu_count(); + for (i, cpu) in other.drain(..).enumerate() { + // prevent having more CPUs than available + if let Some(max_cpus) = max_cpus { + if i == max_cpus { + break; + } + } + result.push(Cpu::from_json(cpu, version, i)); + } + if let Some(max_cpus) = max_cpus { + if result.len() != max_cpus { + let mut sys_cpus = Cpus::system_default(); + for i in result.len()..sys_cpus.cpus.len() { + result.push(sys_cpus.cpus.remove(i)); + } + } + } + Self { + cpus: result, + smt: true, + smt_capable: can_smt, + } + } +} + +impl TCpus for Cpus { + fn limits(&self) -> crate::api::CpusLimits { + crate::api::CpusLimits { + cpus: self.cpus.iter().map(|x| x.limits()).collect(), + count: self.cpus.len(), + smt_capable: self.smt_capable, + } + } + + fn json(&self) -> Vec { + self.cpus.iter().map(|x| x.to_owned().into()).collect() + } + + fn cpus(&mut self) -> Vec<&mut dyn TCpu> { + self.cpus.iter_mut().map(|x| x as &mut dyn TCpu).collect() + } + + fn len(&self) -> usize { + self.cpus.len() + } +} + +#[derive(Debug, Clone)] +pub struct Cpu { + pub online: bool, + pub governor: String, + index: usize, + state: crate::state::steam_deck::Cpu, +} + + +impl Cpu { + #[inline] + pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self { + match version { + 0 => Self { + online: other.online, + governor: other.governor, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + _ => Self { + online: other.online, + governor: other.governor, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + // set cpu online/offline + if self.index != 0 && self.state.do_set_online { // cpu0 cannot be disabled + let online_path = cpu_online_path(self.index); + usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| { + SettingError { + msg: format!("Failed to write to `{}`: {}", &online_path, e), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + + // set governor + if self.index == 0 || self.online { + let governor_path = cpu_governor_path(self.index); + usdpl_back::api::files::write_single(&governor_path, &self.governor).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &self.governor, &governor_path, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + Ok(()) + } + + fn from_sys(cpu_index: usize) -> Self { + Self { + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, + governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index)) + .unwrap_or("schedutil".to_owned()), + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + } + + fn limits(&self) -> crate::api::CpuLimits { + crate::api::CpuLimits { + clock_min_limits: None, + clock_max_limits: None, + clock_step: 100, + governors: vec![], // TODO + } + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + CpuJson { + online: self.online, + clock_limits: None, + governor: self.governor, + } + } +} + +impl OnSet for Cpu { + fn on_set(&mut self) -> Result<(), SettingError> { + //self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Cpu { + fn on_resume(&self) -> Result<(), SettingError> { + let mut copy = self.clone(); + copy.state.is_resuming = true; + copy.set_all() + } +} + +impl TCpu for Cpu { + fn online(&mut self) -> &mut bool { + &mut self.online + } + + fn governor(&mut self, governor: String) { + self.governor = governor; + } + + fn get_governor(&self) -> &'_ str { + &self.governor + } + + fn clock_limits(&mut self, _limits: Option>) { + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + None + } +} + +#[inline] +fn cpu_online_path(index: usize) -> String { + format!("/sys/devices/system/cpu/cpu{}/online", index) +} + +#[inline] +fn cpu_governor_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor", + index + ) +} diff --git a/backend/src/settings/generic/gpu.rs b/backend/src/settings/generic/gpu.rs new file mode 100644 index 0000000..9733cc8 --- /dev/null +++ b/backend/src/settings/generic/gpu.rs @@ -0,0 +1,89 @@ +use std::convert::Into; + +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError}; +use crate::settings::TGpu; +use crate::persist::GpuJson; + +#[derive(Debug, Clone)] +pub struct Gpu { + slow_memory: bool, // ignored +} + +impl Gpu { + #[inline] + pub fn from_json(_other: GpuJson, _version: u64) -> Self { + Self { + slow_memory: false, + } + } + + pub fn system_default() -> Self { + Self { + slow_memory: false, + } + } +} + +impl Into for Gpu { + #[inline] + fn into(self) -> GpuJson { + GpuJson { + fast_ppt: None, + slow_ppt: None, + clock_limits: None, + slow_memory: false, + } + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + crate::api::GpuLimits { + fast_ppt_limits: None, + slow_ppt_limits: None, + ppt_step: 1_000_000, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 42, + clock_min_limits: None, + clock_max_limits: None, + clock_step: 100, + memory_control_capable: false, + } + } + + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, _fast: Option, _slow: Option) { + } + + fn get_ppt(&self) -> (Option, Option) { + (None, None) + } + + fn clock_limits(&mut self, _limits: Option>) { + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + None + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } +} diff --git a/backend/src/settings/generic/mod.rs b/backend/src/settings/generic/mod.rs new file mode 100644 index 0000000..2039cae --- /dev/null +++ b/backend/src/settings/generic/mod.rs @@ -0,0 +1,7 @@ +mod battery; +mod cpu; +mod gpu; + +pub use battery::Battery; +pub use cpu::{Cpu, Cpus}; +pub use gpu::Gpu; diff --git a/backend/src/settings/mod.rs b/backend/src/settings/mod.rs index 5dd569c..c12cf12 100644 --- a/backend/src/settings/mod.rs +++ b/backend/src/settings/mod.rs @@ -4,6 +4,7 @@ mod general; mod min_max; mod traits; +pub mod generic; pub mod steam_deck; pub mod steam_deck_adv; pub mod unknown;