diff --git a/backend/src/api/api_types.rs b/backend/src/api/api_types.rs new file mode 100644 index 0000000..1210ece --- /dev/null +++ b/backend/src/api/api_types.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct RangeLimit { + pub min: T, + pub max: T, +} + +#[derive(Serialize, Deserialize)] +pub struct SettingsLimits { + pub battery: BatteryLimits, + pub cpu: CpusLimits, + pub gpu: GpuLimits, + pub general: GeneralLimits, +} + +#[derive(Serialize, Deserialize)] +pub struct BatteryLimits { + pub charge_rate: Option>, + pub charge_step: u64, +} + +#[derive(Serialize, Deserialize)] +pub struct CpusLimits { + pub cpus: Vec, + pub count: usize, + pub smt_capable: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct CpuLimits { + pub clock_min_limits: Option>, + pub clock_max_limits: Option>, + pub clock_step: u64, + pub governors: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct GeneralLimits { +} + +#[derive(Serialize, Deserialize)] +pub struct GpuLimits { + pub fast_ppt_limits: Option>, + pub slow_ppt_limits: Option>, + pub ppt_step: u64, + pub tdp_limits: Option>, + pub tdp_boost_limits: Option>, + pub tdp_step: u64, + pub clock_min_limits: Option>, + pub clock_max_limits: Option>, + pub clock_step: u64, + pub memory_control_capable: bool, +} diff --git a/backend/src/api/battery.rs b/backend/src/api/battery.rs index 83af197..caa35d4 100644 --- a/backend/src/api/battery.rs +++ b/backend/src/api/battery.rs @@ -6,22 +6,22 @@ use super::handler::{ApiMessage, BatteryMessage}; /// Current current (ha!) web method pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_current_now()) + super::utility::map_optional_result(crate::settings::driver::read_current_now()) } /// Charge now web method pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_now()) + super::utility::map_optional_result(crate::settings::driver::read_charge_now()) } /// Charge full web method pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_full()) + super::utility::map_optional_result(crate::settings::driver::read_charge_full()) } /// Charge design web method pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType { - super::utility::map_result(crate::settings::Battery::read_charge_design()) + super::utility::map_optional_result(crate::settings::driver::read_charge_design()) } /// Generate set battery charge rate web method diff --git a/backend/src/api/cpu.rs b/backend/src/api/cpu.rs index c4c1922..02e2f1e 100644 --- a/backend/src/api/cpu.rs +++ b/backend/src/api/cpu.rs @@ -3,14 +3,14 @@ use std::sync::{Arc, Mutex}; use usdpl_back::core::serdes::Primitive; use usdpl_back::AsyncCallable; -use crate::settings::{Cpus, SettingError, SettingVariant, MinMax}; +use crate::settings::{SettingError, SettingVariant, MinMax}; //use crate::utility::{unwrap_lock, unwrap_maybe_fatal}; use super::handler::{ApiMessage, CpuMessage}; /// Available CPUs web method pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType { super::utility::map_result( - Cpus::cpu_count() + crate::settings::steam_deck::Cpus::cpu_count() .map(|x| x as u64) .ok_or_else( || SettingError { diff --git a/backend/src/api/general.rs b/backend/src/api/general.rs index a0216eb..9dc765f 100644 --- a/backend/src/api/general.rs +++ b/backend/src/api/general.rs @@ -161,3 +161,19 @@ pub fn lock_unlock_all( } } } + +/// Generate get limits web method +pub fn get_limits( + sender: Sender, +) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType { + let sender = Mutex::new(sender); // Sender is not Sync; this is required for safety + let getter = move || { + let (tx, rx) = mpsc::channel(); + let callback = move |value: super::SettingsLimits| tx.send(value).expect("get_limits callback send failed"); + sender.lock().unwrap().send(ApiMessage::GetLimits(Box::new(callback))).expect("get_limits send failed"); + rx.recv().expect("get_limits callback recv failed") + }; + move |_: super::ApiParameterType| { + vec![Primitive::Json(serde_json::to_string(&getter()).unwrap())] + } +} diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs index 3ceef2e..aa3e683 100644 --- a/backend/src/api/handler.rs +++ b/backend/src/api/handler.rs @@ -1,6 +1,6 @@ use std::sync::mpsc::{self, Receiver, Sender}; -use crate::settings::{Settings, Cpus, Gpu, Battery, General, OnSet, OnResume, MinMax}; +use crate::settings::{Settings, TCpus, TGpu, TBattery, TGeneral, OnSet, OnResume, MinMax}; use crate::persist::SettingsJson; use crate::utility::unwrap_maybe_fatal; @@ -16,6 +16,7 @@ pub enum ApiMessage { LoadSettings(String, String), // (path, name) LoadMainSettings, LoadSystemSettings, + GetLimits(Callback), } pub enum BatteryMessage { @@ -24,10 +25,10 @@ pub enum BatteryMessage { } impl BatteryMessage { - fn process(self, settings: &mut Battery) { + fn process(self, settings: &mut dyn TBattery) { match self { - Self::SetChargeRate(rate) => settings.charge_rate = rate, - Self::GetChargeRate(cb) => cb(settings.charge_rate), + Self::SetChargeRate(rate) => settings.charge_rate(rate), + Self::GetChargeRate(cb) => cb(settings.get_charge_rate()), } } } @@ -45,41 +46,41 @@ pub enum CpuMessage { } impl CpuMessage { - fn process(self, settings: &mut Cpus) { + fn process(self, settings: &mut dyn TCpus) { match self { - Self::SetCpuOnline(index, status) => {settings.cpus.get_mut(index).map(|c| c.online = status);}, + Self::SetCpuOnline(index, status) => {settings.cpus().get_mut(index).map(|c| *c.online() = status);}, Self::SetCpusOnline(cpus) => { for i in 0..cpus.len() { - settings.cpus.get_mut(i).map(|c| c.online = cpus[i]); + settings.cpus().get_mut(i).map(|c| *c.online() = cpus[i]); } }, Self::SetSmt(status, cb) => { - let mut result = Vec::with_capacity(settings.cpus.len()); - for i in 0..settings.cpus.len() { - settings.cpus[i].online = settings.cpus[i].online && (status || i % 2 == 0); - result.push(settings.cpus[i].online); + let mut result = Vec::with_capacity(settings.len()); + for i in 0..settings.len() { + *settings.cpus()[i].online() = *settings.cpus()[i].online() && (status || i % 2 == 0); + result.push(*settings.cpus()[i].online()); } cb(result); } Self::GetCpusOnline(cb) => { - let mut result = Vec::with_capacity(settings.cpus.len()); - for cpu in &settings.cpus { - result.push(cpu.online); + let mut result = Vec::with_capacity(settings.len()); + for cpu in settings.cpus() { + result.push(*cpu.online()); } cb(result); }, - Self::SetClockLimits(index, clocks) => {settings.cpus.get_mut(index).map(|c| c.clock_limits = clocks);}, - Self::GetClockLimits(index, cb) => {settings.cpus.get(index).map(|c| cb(c.clock_limits.clone()));}, - Self::SetCpuGovernor(index, gov) => {settings.cpus.get_mut(index).map(|c| c.governor = gov);}, + Self::SetClockLimits(index, clocks) => {settings.cpus().get_mut(index).map(|c| c.clock_limits(clocks));}, + Self::GetClockLimits(index, cb) => {settings.cpus().get(index).map(|c| cb(c.get_clock_limits().map(|x| x.to_owned())));}, + Self::SetCpuGovernor(index, gov) => {settings.cpus().get_mut(index).map(|c| c.governor(gov));}, Self::SetCpusGovernor(govs) => { for i in 0..govs.len() { - settings.cpus.get_mut(i).map(|c| c.governor = govs[i].clone()); + settings.cpus().get_mut(i).map(|c| c.governor(govs[i].clone())); } }, Self::GetCpusGovernor(cb) => { - let mut result = Vec::with_capacity(settings.cpus.len()); - for cpu in &settings.cpus { - result.push(cpu.governor.clone()); + let mut result = Vec::with_capacity(settings.len()); + for cpu in settings.cpus() { + result.push(cpu.get_governor().to_owned()); } cb(result); } @@ -97,17 +98,14 @@ pub enum GpuMessage { } impl GpuMessage { - fn process(self, settings: &mut Gpu) { + fn process(self, settings: &mut dyn TGpu) { match self { - Self::SetPpt(fast, slow) => { - settings.fast_ppt = fast; - settings.slow_ppt = slow; - }, - Self::GetPpt(cb) => cb((settings.fast_ppt, settings.slow_ppt)), - Self::SetClockLimits(clocks) => settings.clock_limits = clocks, - Self::GetClockLimits(cb) => cb(settings.clock_limits.clone()), - Self::SetSlowMemory(val) => settings.slow_memory = val, - Self::GetSlowMemory(cb) => cb(settings.slow_memory), + Self::SetPpt(fast, slow) => settings.ppt(fast, slow), + Self::GetPpt(cb) => cb(settings.get_ppt()), + Self::SetClockLimits(clocks) => settings.clock_limits(clocks), + Self::GetClockLimits(cb) => cb(settings.get_clock_limits().map(|x| x.to_owned())), + Self::SetSlowMemory(val) => *settings.slow_memory() = val, + Self::GetSlowMemory(cb) => cb(*settings.slow_memory()), } } } @@ -119,11 +117,11 @@ pub enum GeneralMessage { } impl GeneralMessage { - fn process(self, settings: &mut General) { + fn process(self, settings: &mut dyn TGeneral) { match self { - Self::SetPersistent(val) => settings.persistent = val, - Self::GetPersistent(cb) => cb(settings.persistent), - Self::GetCurrentProfileName(cb) => cb(settings.name.clone()), + Self::SetPersistent(val) => *settings.persistent() = val, + Self::GetPersistent(cb) => cb(*settings.persistent()), + Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()), } } } @@ -150,11 +148,11 @@ impl ApiMessageHandler { } // save log::debug!("api_worker is saving..."); - let is_persistent = settings.general.persistent; + let is_persistent = *settings.general.persistent(); if is_persistent { let save_path = crate::utility::settings_dir() - .join(settings.general.path.clone()); - let settings_clone = settings.clone(); + .join(settings.general.get_path().clone()); + let settings_clone = settings.json(); let save_json: SettingsJson = settings_clone.into(); unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); log::debug!("Saved settings to {}", save_path.display()); @@ -166,10 +164,10 @@ impl ApiMessageHandler { pub fn process(&mut self, settings: &mut Settings, message: ApiMessage) { match message { - ApiMessage::Battery(x) => x.process(&mut settings.battery), - ApiMessage::Cpu(x) => x.process(&mut settings.cpus), - ApiMessage::Gpu(x) => x.process(&mut settings.gpu), - ApiMessage::General(x) => x.process(&mut settings.general), + ApiMessage::Battery(x) => x.process(settings.battery.as_mut()), + ApiMessage::Cpu(x) => x.process(settings.cpus.as_mut()), + ApiMessage::Gpu(x) => x.process(settings.gpu.as_mut()), + ApiMessage::General(x) => x.process(settings.general.as_mut()), ApiMessage::OnResume => { if let Err(e) = settings.on_resume() { log::error!("Settings on_resume() err: {}", e); @@ -194,6 +192,14 @@ impl ApiMessageHandler { } ApiMessage::LoadSystemSettings => { settings.load_system_default(); + }, + ApiMessage::GetLimits(cb) => { + cb(super::SettingsLimits { + battery: settings.battery.limits(), + cpu: settings.cpus.limits(), + gpu: settings.gpu.limits(), + general: settings.general.limits(), + }); } } } diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 03412a8..174d1bb 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,3 +1,4 @@ +mod api_types; pub mod battery; pub mod cpu; pub mod general; @@ -7,3 +8,5 @@ mod async_utils; mod utility; pub(super) type ApiParameterType = Vec; + +pub use api_types::*; diff --git a/backend/src/api/utility.rs b/backend/src/api/utility.rs index c797f60..76957ca 100644 --- a/backend/src/api/utility.rs +++ b/backend/src/api/utility.rs @@ -14,6 +14,20 @@ pub fn map_result>(result: Result) -> super: } } +#[inline] +pub fn map_optional_result>(result: Result, SettingError>) -> super::ApiParameterType { + match result { + Ok(val) => match val { + Some(val) => vec![val.into()], + None => vec![Primitive::Empty], + }, + Err(e) => { + log::debug!("Mapping error to primitive: {}", e); + vec![e.msg.into()] + }, + } +} + /*#[inline] pub fn map_empty_result>( result: Result<(), SettingError>, diff --git a/backend/src/main.rs b/backend/src/main.rs index aa53e00..884d519 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -44,6 +44,8 @@ fn main() -> Result<(), ()> { log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); + crate::settings::driver::auto_detect_loud(); + let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) .map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into())) .unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into())); @@ -183,6 +185,10 @@ fn main() -> Result<(), ()> { .register_async( "GENERAL_wait_for_unlocks", api::general::lock_unlock_all(api_sender.clone()) + ) + .register( + "GENERAL_get_limits", + api::general::get_limits(api_sender.clone()) ); api_worker::spawn(loaded_settings, api_handler); diff --git a/backend/src/persist/driver.rs b/backend/src/persist/driver.rs new file mode 100644 index 0000000..bf8b891 --- /dev/null +++ b/backend/src/persist/driver.rs @@ -0,0 +1,14 @@ +//use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub enum DriverJson { + #[default] + #[serde(rename = "steam-deck", alias = "gabe-boy")] + SteamDeck, + #[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")] + SteamDeckAdvance, + #[serde(rename = "unknown")] + Unknown, +} diff --git a/backend/src/persist/general.rs b/backend/src/persist/general.rs index 4ae3c89..b85df70 100644 --- a/backend/src/persist/general.rs +++ b/backend/src/persist/general.rs @@ -3,7 +3,7 @@ use std::default::Default; use serde::{Deserialize, Serialize}; use super::JsonError; -use super::{BatteryJson, CpuJson, GpuJson}; +use super::{BatteryJson, CpuJson, GpuJson, DriverJson}; #[derive(Serialize, Deserialize)] pub struct SettingsJson { @@ -13,6 +13,7 @@ pub struct SettingsJson { pub cpus: Vec, pub gpu: GpuJson, pub battery: BatteryJson, + pub provider: Option, } impl Default for SettingsJson { @@ -24,6 +25,7 @@ impl Default for SettingsJson { cpus: Vec::with_capacity(8), gpu: GpuJson::default(), battery: BatteryJson::default(), + provider: None, } } } diff --git a/backend/src/persist/mod.rs b/backend/src/persist/mod.rs index 5b70ff5..ef9a677 100644 --- a/backend/src/persist/mod.rs +++ b/backend/src/persist/mod.rs @@ -1,11 +1,13 @@ mod battery; mod cpu; +mod driver; mod error; mod general; mod gpu; pub use battery::BatteryJson; pub use cpu::CpuJson; +pub use driver::DriverJson; pub use general::{MinMaxJson, SettingsJson}; pub use gpu::GpuJson; diff --git a/backend/src/settings/driver.rs b/backend/src/settings/driver.rs new file mode 100644 index 0000000..921c7a7 --- /dev/null +++ b/backend/src/settings/driver.rs @@ -0,0 +1,179 @@ +use crate::persist::{DriverJson, SettingsJson}; +use super::{TGeneral, TCpus, TGpu, TBattery, SettingError, General}; + +/// Device detection logic +fn auto_detect() -> DriverJson { + let lscpu: String = match usdpl_back::api::files::read_single("/proc/cpuinfo") { + Ok(s) => s, + Err(_) => return DriverJson::Unknown, + }; + log::debug!("Read from /proc/cpuinfo:\n{}", lscpu); + let os_info: String = match usdpl_back::api::files::read_single("/etc/os-release") { + Ok(s) => s, + Err(_) => return DriverJson::Unknown, + }; + log::debug!("Read from /etc/os-release:\n{}", os_info); + if let Some(_) = lscpu.find("model name\t: AMD Custom APU 0405\n") { + // definitely a Steam Deck, check if it's overclocked + let max_freq: u64 = match usdpl_back::api::files::read_single("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") { + Ok(u) => u, + Err(_) => return DriverJson::SteamDeck, + }; + if max_freq == 2800000 { // default clock speed + DriverJson::SteamDeck + } else { + DriverJson::SteamDeckAdvance + } + } else { + DriverJson::Unknown + } +} + +#[inline] +pub fn auto_detect_loud() -> DriverJson { + let provider = auto_detect(); + log::info!("Detected device automatically, compatible driver: {:?}", provider); + provider +} + +pub struct Driver { + pub general: Box, + pub cpus: Box, + pub gpu: Box, + pub battery: Box, +} + +impl Driver { + pub fn init(settings: SettingsJson, json_path: std::path::PathBuf) -> Result { + Ok(match settings.version { + 0 => Self::version0(settings, json_path)?, + _ => Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::SteamDeck, + }), + cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), + }, + }) + } + + fn version0(settings: SettingsJson, json_path: std::path::PathBuf) -> Result { + let provider = settings.provider.unwrap_or_else(auto_detect); + match provider { + DriverJson::SteamDeck => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::SteamDeck, + }), + cpus: Box::new(super::steam_deck::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck::Battery::from_json(settings.battery, settings.version)), + }), + DriverJson::SteamDeckAdvance => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::SteamDeckAdvance, + }), + cpus: Box::new(super::steam_deck_adv::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::steam_deck_adv::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::steam_deck_adv::Battery::from_json(settings.battery, settings.version)), + }), + DriverJson::Unknown => Ok(Self { + general: Box::new(General { + persistent: settings.persistent, + path: json_path, + name: settings.name, + driver: DriverJson::Unknown, + }), + cpus: Box::new(super::unknown::Cpus::from_json(settings.cpus, settings.version)), + gpu: Box::new(super::unknown::Gpu::from_json(settings.gpu, settings.version)), + battery: Box::new(super::unknown::Battery), + }), + } + } + + pub fn system_default(json_path: std::path::PathBuf) -> Self { + let provider = auto_detect(); + match provider { + DriverJson::SteamDeck => Self { + general: Box::new(General { + persistent: false, + path: json_path, + name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + driver: DriverJson::SteamDeck, + }), + cpus: Box::new(super::steam_deck::Cpus::system_default()), + gpu: Box::new(super::steam_deck::Gpu::system_default()), + battery: Box::new(super::steam_deck::Battery::system_default()), + }, + DriverJson::SteamDeckAdvance => Self { + general: Box::new(General { + persistent: false, + path: json_path, + name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), + driver: DriverJson::SteamDeck, + }), + cpus: Box::new(super::steam_deck_adv::Cpus::system_default()), + gpu: Box::new(super::steam_deck_adv::Gpu::system_default()), + battery: Box::new(super::steam_deck_adv::Battery::system_default()), + }, + DriverJson::Unknown => 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::unknown::Cpus::system_default()), + gpu: Box::new(super::unknown::Gpu::system_default()), + battery: Box::new(super::unknown::Battery), + } + } + } +} + +// static battery calls + +#[inline] +pub fn read_current_now() -> Result, SettingError> { + match auto_detect() { + DriverJson::SteamDeck => super::steam_deck::Battery::read_current_now().map(|x| Some(x)), + DriverJson::SteamDeckAdvance => super::steam_deck_adv::Battery::read_current_now().map(|x| Some(x)), + DriverJson::Unknown => Ok(None), + } +} + +#[inline] +pub fn read_charge_now() -> Result, SettingError> { + match auto_detect() { + DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_now().map(|x| Some(x)), + DriverJson::SteamDeckAdvance => super::steam_deck_adv::Battery::read_charge_now().map(|x| Some(x)), + DriverJson::Unknown => Ok(None), + } +} + +#[inline] +pub fn read_charge_full() -> Result, SettingError> { + match auto_detect() { + DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_full().map(|x| Some(x)), + DriverJson::SteamDeckAdvance => super::steam_deck_adv::Battery::read_charge_full().map(|x| Some(x)), + DriverJson::Unknown => Ok(None), + } +} + +#[inline] +pub fn read_charge_design() -> Result, SettingError> { + match auto_detect() { + DriverJson::SteamDeck => super::steam_deck::Battery::read_charge_design().map(|x| Some(x)), + DriverJson::SteamDeckAdvance => super::steam_deck_adv::Battery::read_charge_design().map(|x| Some(x)), + DriverJson::Unknown => Ok(None), + } +} diff --git a/backend/src/settings/general.rs b/backend/src/settings/general.rs index fb95f52..9c6afd7 100644 --- a/backend/src/settings/general.rs +++ b/backend/src/settings/general.rs @@ -1,9 +1,9 @@ -use std::convert::Into; use std::path::PathBuf; //use std::sync::{Arc, Mutex}; -use super::{Battery, Cpus, Gpu}; +//use super::{Battery, Cpus, Gpu}; use super::{OnResume, OnSet, SettingError}; +use super::{TGeneral, TGpu, TCpus, TBattery}; use crate::persist::SettingsJson; //use crate::utility::unwrap_lock; @@ -33,6 +33,7 @@ pub struct General { pub persistent: bool, pub path: PathBuf, pub name: String, + pub driver: crate::persist::DriverJson, } impl OnSet for General { @@ -41,12 +42,52 @@ impl OnSet for General { } } -#[derive(Debug, Clone)] +impl OnResume for General { + fn on_resume(&self) -> Result<(), SettingError> { + Ok(()) + } +} + +impl TGeneral for General { + fn limits(&self) -> crate::api::GeneralLimits { + crate::api::GeneralLimits { } + } + + fn get_persistent(&self) -> bool { + self.persistent + } + + fn persistent(&mut self) -> &'_ mut bool { + &mut self.persistent + } + + fn get_path(&self) -> &'_ std::path::Path { + &self.path + } + + fn path(&mut self, path: std::path::PathBuf) { + self.path = path; + } + + fn get_name(&self) -> &'_ str { + &self.name + } + + fn name(&mut self, name: String) { + self.name = name; + } + + fn provider(&self) -> crate::persist::DriverJson { + self.driver.clone() + } +} + +#[derive(Debug)] pub struct Settings { - pub general: General, - pub cpus: Cpus, - pub gpu: Gpu, - pub battery: Battery, + pub general: Box, + pub cpus: Box, + pub gpu: Box, + pub battery: Box, } impl OnSet for Settings { @@ -62,47 +103,38 @@ impl OnSet for Settings { impl Settings { #[inline] pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self { - match other.version { - 0 => Self { - general: General { - persistent: other.persistent, - path: json_path, - name: other.name, - }, - cpus: Cpus::from_json(other.cpus, other.version), - gpu: Gpu::from_json(other.gpu, other.version), - battery: Battery::from_json(other.battery, other.version), - }, - _ => Self { - general: General { - persistent: other.persistent, - path: json_path, - name: other.name, - }, - cpus: Cpus::from_json(other.cpus, other.version), - gpu: Gpu::from_json(other.gpu, other.version), - battery: Battery::from_json(other.battery, other.version), + match super::Driver::init(other, json_path.clone()) { + Ok(x) => { + log::info!("Loaded settings for driver {:?}", x.general.provider()); + Self { + general: x.general, + cpus: x.cpus, + gpu: x.gpu, + battery: x.battery, + } }, + Err(e) => { + log::error!("Driver init error: {}", e); + Self::system_default(json_path) + } } } pub fn system_default(json_path: PathBuf) -> Self { + let driver = super::Driver::system_default(json_path); Self { - general: General { - persistent: false, - path: json_path, - name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), - }, - cpus: Cpus::system_default(), - gpu: Gpu::system_default(), - battery: Battery::system_default(), + general: driver.general, + cpus: driver.cpus, + gpu: driver.gpu, + battery: driver.battery, } } pub fn load_system_default(&mut self) { - self.cpus = Cpus::system_default(); - self.gpu = Gpu::system_default(); - self.battery = Battery::system_default(); + let driver = super::Driver::system_default(self.general.get_path().to_owned()); + self.cpus = driver.cpus; + self.gpu = driver.gpu; + self.battery = driver.battery; } pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result { @@ -115,24 +147,36 @@ impl Settings { })?; if !settings_json.persistent { log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display()); - self.general.persistent = false; - self.general.name = name; + *self.general.persistent() = false; + self.general.name(name); } else { - self.cpus = Cpus::from_json(settings_json.cpus, settings_json.version); - self.gpu = Gpu::from_json(settings_json.gpu, settings_json.version); - self.battery = Battery::from_json(settings_json.battery, settings_json.version); - self.general.persistent = true; - self.general.name = settings_json.name; + self.cpus = Box::new(super::steam_deck::Cpus::from_json(settings_json.cpus, settings_json.version)); + self.gpu = Box::new(super::steam_deck::Gpu::from_json(settings_json.gpu, settings_json.version)); + self.battery = Box::new(super::steam_deck::Battery::from_json(settings_json.battery, settings_json.version)); + *self.general.persistent() = true; + self.general.name(settings_json.name); } } else { if system_defaults { self.load_system_default(); } - self.general.persistent = false; - self.general.name = name; + *self.general.persistent() = false; + self.general.name(name); + } + self.general.path(json_path); + Ok(*self.general.persistent()) + } + + pub fn json(&self) -> SettingsJson { + SettingsJson { + version: LATEST_VERSION, + name: self.general.get_name().to_owned(), + persistent: self.general.get_persistent(), + cpus: self.cpus.json(), + gpu: self.gpu.json(), + battery: self.battery.json(), + provider: Some(self.general.provider()), } - self.general.path = json_path; - Ok(self.general.persistent) } } @@ -149,21 +193,18 @@ impl OnResume for Settings { } } -impl Into for Settings { +/*impl Into for Settings { #[inline] fn into(self) -> SettingsJson { log::debug!("Converting into json"); SettingsJson { version: LATEST_VERSION, - name: self.general.name.clone(), - persistent: self.general.persistent, - cpus: self.cpus.cpus - .clone() - .drain(..) - .map(|cpu| cpu.into()) - .collect(), - gpu: self.gpu.clone().into(), - battery: self.battery.clone().into(), + name: self.general.get_name().to_owned(), + persistent: self.general.get_persistent(), + cpus: self.cpus.json(), + gpu: self.gpu.json(), + battery: self.battery.json(), + provider: Some(self.general.provider()), } } -} +}*/ diff --git a/backend/src/settings/mod.rs b/backend/src/settings/mod.rs index 2cef68c..5dd569c 100644 --- a/backend/src/settings/mod.rs +++ b/backend/src/settings/mod.rs @@ -1,19 +1,19 @@ -mod battery; -mod cpu; +pub mod driver; mod error; mod general; -mod gpu; mod min_max; mod traits; -pub use battery::Battery; -pub use cpu::{Cpu, Cpus}; +pub mod steam_deck; +pub mod steam_deck_adv; +pub mod unknown; + +pub use driver::Driver; pub use general::{SettingVariant, Settings, General}; -pub use gpu::Gpu; pub use min_max::MinMax; pub use error::SettingError; -pub use traits::{OnResume, OnSet, SettingsRange}; +pub use traits::{OnResume, OnSet, SettingsRange, TGeneral, TGpu, TCpus, TBattery, TCpu}; #[cfg(test)] mod tests { diff --git a/backend/src/settings/battery.rs b/backend/src/settings/steam_deck/battery.rs similarity index 76% rename from backend/src/settings/battery.rs rename to backend/src/settings/steam_deck/battery.rs index ae0dd63..a002bca 100644 --- a/backend/src/settings/battery.rs +++ b/backend/src/settings/steam_deck/battery.rs @@ -1,12 +1,14 @@ use std::convert::Into; -use super::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::api::RangeLimit; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::settings::TBattery; use crate::persist::BatteryJson; #[derive(Debug, Clone)] pub struct Battery { pub charge_rate: Option, - state: crate::state::Battery, + state: crate::state::steam_deck::Battery, } const BATTERY_VOLTAGE: f64 = 7.7; @@ -23,11 +25,11 @@ impl Battery { match version { 0 => Self { charge_rate: other.charge_rate, - state: crate::state::Battery::default(), + state: crate::state::steam_deck::Battery::default(), }, _ => Self { charge_rate: other.charge_rate, - state: crate::state::Battery::default(), + state: crate::state::steam_deck::Battery::default(), }, } } @@ -38,7 +40,7 @@ impl Battery { 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), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }, ) } else if self.state.charge_rate_set { @@ -46,7 +48,7 @@ impl Battery { usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, Self::max().charge_rate.unwrap()).map_err( |e| SettingError { msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }, ) } else { @@ -66,11 +68,11 @@ impl Battery { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { Err((Some(e), None)) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err((None, Some(e))) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err(_) => panic!( "Invalid error while reading from `{}`", @@ -86,11 +88,11 @@ impl Battery { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { Err((Some(e), None)) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err((None, Some(e))) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err(_) => panic!( "Invalid error while reading from `{}`", @@ -105,11 +107,11 @@ impl Battery { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { Err((Some(e), None)) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err((None, Some(e))) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err(_) => panic!( "Invalid error while reading from `{}`", @@ -124,11 +126,11 @@ impl Battery { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { Err((Some(e), None)) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err((None, Some(e))) => Err(SettingError { msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), - setting: super::SettingVariant::Battery, + setting: crate::settings::SettingVariant::Battery, }), Err(_) => panic!( "Invalid error while reading from `{}`", @@ -142,7 +144,7 @@ impl Battery { pub fn system_default() -> Self { Self { charge_rate: None, - state: crate::state::Battery::default(), + state: crate::state::steam_deck::Battery::default(), } } } @@ -174,7 +176,7 @@ impl SettingsRange for Battery { fn max() -> Self { Self { charge_rate: Some(2500), - state: crate::state::Battery::default(), + state: crate::state::steam_deck::Battery::default(), } } @@ -182,7 +184,33 @@ impl SettingsRange for Battery { fn min() -> Self { Self { charge_rate: Some(250), - state: crate::state::Battery::default(), + state: crate::state::steam_deck::Battery::default(), } } } + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + let max = Self::max(); + let min = Self::min(); + crate::api::BatteryLimits { + charge_rate: Some(RangeLimit{ + min: min.charge_rate.unwrap(), + max: max.charge_rate.unwrap(), + }), + charge_step: 50, + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, rate: Option) { + self.charge_rate = rate; + } + + fn get_charge_rate(&self) -> Option { + self.charge_rate + } +} diff --git a/backend/src/settings/steam_deck/cpu.rs b/backend/src/settings/steam_deck/cpu.rs new file mode 100644 index 0000000..845e3fe --- /dev/null +++ b/backend/src/settings/steam_deck/cpu.rs @@ -0,0 +1,446 @@ +use std::convert::Into; + +use crate::api::RangeLimit; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +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 clock_limits: Option>, + pub governor: String, + index: usize, + state: crate::state::steam_deck::Cpu, +} + +const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; +const CPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; + +impl Cpu { + #[inline] + pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self { + match version { + 0 => Self { + online: other.online, + clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + governor: other.governor, + index: i, + state: crate::state::steam_deck::Cpu::default(), + }, + _ => Self { + online: other.online, + clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + 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 clock limits + log::debug!("Setting {} to manual", CPU_FORCE_LIMITS_PATH); + let mode: String = usdpl_back::api::files::read_single(CPU_FORCE_LIMITS_PATH.to_owned()).unwrap(); + if mode != "manual" { + // set manual control + usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "manual").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `manual` to `{}`: {}", + CPU_FORCE_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + } + })?; + } + if let Some(clock_limits) = &self.clock_limits { + log::debug!("Setting CPU {} (min, max) clockspeed to ({}, {})", self.index, clock_limits.min, clock_limits.max); + self.state.clock_limits_set = true; + // max clock + let payload_max = format!("p {} 1 {}\n", self.index / 2, clock_limits.max); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + // min clock + let payload_min = format!("p {} 0 {}\n", self.index / 2, clock_limits.min); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + } else if self.state.clock_limits_set || self.state.is_resuming { + self.state.clock_limits_set = false; + // disable manual clock limits + log::debug!("Setting CPU {} to default clockspeed", self.index); + // max clock + let payload_max = format!("p {} 1 {}\n", self.index / 2, Self::max().clock_limits.unwrap().max); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + // min clock + let payload_min = format!("p {} 0 {}\n", self.index / 2, Self::min().clock_limits.unwrap().min); + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, CPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Cpu, + }, + )?; + } + // commit changes + usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { + SettingError { + msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_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 clamp_all(&mut self) { + let min = Self::min(); + let max = Self::max(); + if let Some(clock_limits) = &mut self.clock_limits { + let max_boost = max.clock_limits.as_ref().unwrap(); + let min_boost = min.clock_limits.as_ref().unwrap(); + clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min); + clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max); + } + } + + fn from_sys(cpu_index: usize) -> Self { + Self { + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, + clock_limits: None, + 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 { + let max = Self::max(); + let max_clocks = max.clock_limits.unwrap(); + + let min = Self::min(); + let min_clocks = min.clock_limits.unwrap(); + crate::api::CpuLimits { + clock_min_limits: Some(RangeLimit { + min: min_clocks.min, + max: max_clocks.min + }), + clock_max_limits: Some(RangeLimit { + min: min_clocks.max, + max: max_clocks.max + }), + clock_step: 100, + governors: self.governors(), + } + } + + fn governors(&self) -> Vec { + // NOTE: this eats errors + let gov_str: String = match usdpl_back::api::files::read_single(cpu_available_governors_path(self.index)) { + Ok(s) => s, + Err((Some(e), None)) => { + log::warn!("Error getting available CPU governors: {}", e); + return vec![]; + }, + Err((None, Some(e))) => { + log::warn!("Error getting available CPU governors: {}", e); + return vec![]; + }, + Err(_) => return vec![], + }; + gov_str.split(' ').map(|s| s.to_owned()).collect() + } +} + +impl Into for Cpu { + #[inline] + fn into(self) -> CpuJson { + CpuJson { + online: self.online, + clock_limits: self.clock_limits.map(|x| x.into()), + 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>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } +} + +impl SettingsRange for Cpu { + #[inline] + fn max() -> Self { + Self { + online: true, + clock_limits: Some(MinMax { + max: 3500, + min: 3500, + }), + governor: "schedutil".to_owned(), + index: usize::MAX, + state: crate::state::steam_deck::Cpu::default(), + } + } + + #[inline] + fn min() -> Self { + Self { + online: false, + clock_limits: Some(MinMax { max: 500, min: 1400 }), + governor: "schedutil".to_owned(), + index: usize::MIN, + state: crate::state::steam_deck::Cpu::default(), + } + } +} + +#[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 + ) +} + + +#[inline] +fn cpu_available_governors_path(index: usize) -> String { + format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_governors", + index + ) +} diff --git a/backend/src/settings/steam_deck/gpu.rs b/backend/src/settings/steam_deck/gpu.rs new file mode 100644 index 0000000..665a7df --- /dev/null +++ b/backend/src/settings/steam_deck/gpu.rs @@ -0,0 +1,308 @@ +use std::convert::Into; + +use crate::api::RangeLimit; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::settings::TGpu; +use crate::persist::GpuJson; + +const SLOW_PPT: u8 = 1; +const FAST_PPT: u8 = 2; + +#[derive(Debug, Clone)] +pub struct Gpu { + pub fast_ppt: Option, + pub slow_ppt: Option, + pub clock_limits: Option>, + pub slow_memory: bool, + state: crate::state::steam_deck::Gpu, +} + +// same as CPU +const GPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; +const GPU_FORCE_LIMITS_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; +const GPU_MEMORY_DOWNCLOCK_PATH: &str = "/sys/class/drm/card0/device/pp_dpm_fclk"; + +impl Gpu { + #[inline] + pub fn from_json(other: GpuJson, version: u64) -> Self { + match version { + 0 => Self { + fast_ppt: other.fast_ppt, + slow_ppt: other.slow_ppt, + clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + slow_memory: other.slow_memory, + state: crate::state::steam_deck::Gpu::default(), + }, + _ => Self { + fast_ppt: other.fast_ppt, + slow_ppt: other.slow_ppt, + clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), + slow_memory: other.slow_memory, + state: crate::state::steam_deck::Gpu::default(), + }, + } + } + + fn set_all(&mut self) -> Result<(), SettingError> { + // set fast PPT + if let Some(fast_ppt) = &self.fast_ppt { + let fast_ppt_path = gpu_power_path(FAST_PPT); + usdpl_back::api::files::write_single(&fast_ppt_path, fast_ppt).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + fast_ppt, &fast_ppt_path, e + ), + setting: crate::settings::SettingVariant::Gpu, + } + })?; + } + // set slow PPT + if let Some(slow_ppt) = &self.slow_ppt { + let slow_ppt_path = gpu_power_path(SLOW_PPT); + usdpl_back::api::files::write_single(&slow_ppt_path, slow_ppt).map_err(|e| { + SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + slow_ppt, &slow_ppt_path, e + ), + setting: crate::settings::SettingVariant::Gpu, + } + })?; + } + // settings using force_performance_level + let mode: String = usdpl_back::api::files::read_single(GPU_FORCE_LIMITS_PATH.to_owned()).unwrap(); + if mode != "manual" { + // set manual control + usdpl_back::api::files::write_single(GPU_FORCE_LIMITS_PATH, "manual").map_err(|e| { + SettingError { + msg: format!( + "Failed to write `manual` to `{}`: {}", + GPU_FORCE_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Gpu, + } + })?; + } + // enable/disable downclock of GPU memory (to 400Mhz?) + usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8) + .map_err(|e| SettingError { + msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e), + setting: crate::settings::SettingVariant::Gpu, + })?; + if let Some(clock_limits) = &self.clock_limits { + // set clock limits + self.state.clock_limits_set = true; + // max clock + let payload_max = format!("s 1 {}\n", clock_limits.max); + usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, GPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Gpu, + }, + )?; + // min clock + let payload_min = format!("s 0 {}\n", clock_limits.min); + usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, GPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Gpu, + }, + )?; + } else if self.state.clock_limits_set || self.state.is_resuming { + self.state.clock_limits_set = false; + // disable manual clock limits + // max clock + let payload_max = format!("s 1 {}\n", Self::max().clock_limits.unwrap().max); + usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_max).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_max, GPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Gpu, + }, + )?; + // min clock + let payload_min = format!("s 0 {}\n", Self::min().clock_limits.unwrap().min); + usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, &payload_min).map_err( + |e| SettingError { + msg: format!( + "Failed to write `{}` to `{}`: {}", + &payload_min, GPU_CLOCK_LIMITS_PATH, e + ), + setting: crate::settings::SettingVariant::Gpu, + }, + )?; + } + // commit changes + usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { + SettingError { + msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e), + setting: crate::settings::SettingVariant::Gpu, + } + })?; + + Ok(()) + } + + fn clamp_all(&mut self) { + let min = Self::min(); + let max = Self::max(); + if let Some(fast_ppt) = &mut self.fast_ppt { + *fast_ppt = (*fast_ppt).clamp( + *min.fast_ppt.as_ref().unwrap(), + *max.fast_ppt.as_ref().unwrap(), + ); + } + if let Some(slow_ppt) = &mut self.slow_ppt { + *slow_ppt = (*slow_ppt).clamp( + *min.slow_ppt.as_ref().unwrap(), + *max.slow_ppt.as_ref().unwrap(), + ); + } + if let Some(clock_limits) = &mut self.clock_limits { + let max_boost = max.clock_limits.as_ref().unwrap(); + let min_boost = min.clock_limits.as_ref().unwrap(); + clock_limits.min = clock_limits.min.clamp(min_boost.min, max_boost.min); + clock_limits.max = clock_limits.max.clamp(min_boost.max, max_boost.max); + } + } + + pub fn system_default() -> Self { + Self { + fast_ppt: None, + slow_ppt: None, + clock_limits: None, + slow_memory: false, + state: crate::state::steam_deck::Gpu::default(), + } + } +} + +impl Into for Gpu { + #[inline] + fn into(self) -> GpuJson { + GpuJson { + fast_ppt: self.fast_ppt, + slow_ppt: self.slow_ppt, + clock_limits: self.clock_limits.map(|x| x.into()), + slow_memory: self.slow_memory, + } + } +} + +impl OnSet for Gpu { + fn on_set(&mut self) -> Result<(), SettingError> { + self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Gpu { + fn on_resume(&self) -> Result<(), SettingError> { + let mut copy = self.clone(); + copy.state.is_resuming = true; + copy.set_all() + } +} + +impl SettingsRange for Gpu { + #[inline] + fn max() -> Self { + Self { + fast_ppt: Some(30_000_000), + slow_ppt: Some(29_000_000), + clock_limits: Some(MinMax { + min: 1600, + max: 1600, + }), + slow_memory: false, + state: crate::state::steam_deck::Gpu::default(), + } + } + + #[inline] + fn min() -> Self { + Self { + fast_ppt: Some(0), + slow_ppt: Some(1000000), + clock_limits: Some(MinMax { min: 200, max: 200 }), + slow_memory: true, + state: crate::state::steam_deck::Gpu::default(), + } + } +} + +const PPT_DIVISOR: u64 = 1_000_000; + +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + let max = Self::max(); + let max_clock_limits = max.clock_limits.unwrap(); + + let min = Self::min(); + let min_clock_limits = min.clock_limits.unwrap(); + crate::api::GpuLimits { + fast_ppt_limits: Some(RangeLimit { + min: min.fast_ppt.unwrap() / PPT_DIVISOR, + max: max.fast_ppt.unwrap() / PPT_DIVISOR, + }), + slow_ppt_limits: Some(RangeLimit { + min: min.slow_ppt.unwrap() / PPT_DIVISOR, + max: max.slow_ppt.unwrap() / PPT_DIVISOR, + }), + ppt_step: 1, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 42, + clock_min_limits: Some(RangeLimit { + min: min_clock_limits.min, + max: max_clock_limits.max, + }), + clock_max_limits: Some(RangeLimit { + min: min_clock_limits.min, + max: max_clock_limits.max, + }), + clock_step: 100, + memory_control_capable: true, + } + } + + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + self.fast_ppt = fast.map(|x| x * PPT_DIVISOR); + self.slow_ppt = slow.map(|x| x * PPT_DIVISOR); + } + + fn get_ppt(&self) -> (Option, Option) { + (self.fast_ppt.map(|x| x / PPT_DIVISOR), self.slow_ppt.map(|x| x / PPT_DIVISOR)) + } + + fn clock_limits(&mut self, limits: Option>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } +} + +#[inline] +fn gpu_power_path(power_number: u8) -> String { + format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number) +} diff --git a/backend/src/settings/steam_deck/mod.rs b/backend/src/settings/steam_deck/mod.rs new file mode 100644 index 0000000..2039cae --- /dev/null +++ b/backend/src/settings/steam_deck/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/steam_deck_adv/battery.rs b/backend/src/settings/steam_deck_adv/battery.rs new file mode 100644 index 0000000..a002bca --- /dev/null +++ b/backend/src/settings/steam_deck_adv/battery.rs @@ -0,0 +1,216 @@ +use std::convert::Into; + +use crate::api::RangeLimit; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::settings::TBattery; +use crate::persist::BatteryJson; + +#[derive(Debug, Clone)] +pub struct Battery { + pub charge_rate: Option, + state: crate::state::steam_deck::Battery, +} + +const BATTERY_VOLTAGE: f64 = 7.7; + +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/hwmon/hwmon2/device/charge_now"; // read-only +const BATTERY_CHARGE_FULL_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full"; // read-only +const BATTERY_CHARGE_DESIGN_PATH: &str = "/sys/class/hwmon/hwmon2/device/charge_full_design"; // read-only + +impl Battery { + #[inline] + pub fn from_json(other: BatteryJson, version: u64) -> Self { + match version { + 0 => Self { + charge_rate: other.charge_rate, + state: crate::state::steam_deck::Battery::default(), + }, + _ => Self { + charge_rate: other.charge_rate, + state: crate::state::steam_deck::Battery::default(), + }, + } + } + + fn set_all(&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( + |e| SettingError { + msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, 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::max().charge_rate.unwrap()).map_err( + |e| SettingError { + msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }, + ) + } else { + Ok(()) + } + } + + fn clamp_all(&mut self) { + let min = Self::min(); + let max = Self::max(); + if let Some(charge_rate) = &mut self.charge_rate { + *charge_rate = (*charge_rate).clamp(min.charge_rate.unwrap(), max.charge_rate.unwrap()); + } + } + + pub fn read_current_now() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { + Err((Some(e), None)) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err((None, Some(e))) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err(_) => panic!( + "Invalid error while reading from `{}`", + BATTERY_CURRENT_NOW_PATH + ), + // this value is in uA, while it's set in mA + // so convert this to mA for consistency + Ok(val) => Ok(val / 1000), + } + } + + pub fn read_charge_now() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { + Err((Some(e), None)) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err((None, Some(e))) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err(_) => panic!( + "Invalid error while reading from `{}`", + BATTERY_CHARGE_NOW_PATH + ), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn read_charge_full() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { + Err((Some(e), None)) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err((None, Some(e))) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err(_) => panic!( + "Invalid error while reading from `{}`", + BATTERY_CHARGE_NOW_PATH + ), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn read_charge_design() -> Result { + match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { + Err((Some(e), None)) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err((None, Some(e))) => Err(SettingError { + msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), + setting: crate::settings::SettingVariant::Battery, + }), + Err(_) => panic!( + "Invalid error while reading from `{}`", + BATTERY_CHARGE_NOW_PATH + ), + // convert to Wh + Ok(val) => Ok((val as f64) / 1000000.0 * BATTERY_VOLTAGE), + } + } + + pub fn system_default() -> Self { + Self { + charge_rate: None, + state: crate::state::steam_deck::Battery::default(), + } + } +} + +impl Into for Battery { + #[inline] + fn into(self) -> BatteryJson { + BatteryJson { + charge_rate: self.charge_rate, + } + } +} + +impl OnSet for Battery { + fn on_set(&mut self) -> Result<(), SettingError> { + self.clamp_all(); + self.set_all() + } +} + +impl OnResume for Battery { + fn on_resume(&self) -> Result<(), SettingError> { + self.clone().set_all() + } +} + +impl SettingsRange for Battery { + #[inline] + fn max() -> Self { + Self { + charge_rate: Some(2500), + state: crate::state::steam_deck::Battery::default(), + } + } + + #[inline] + fn min() -> Self { + Self { + charge_rate: Some(250), + state: crate::state::steam_deck::Battery::default(), + } + } +} + +impl TBattery for Battery { + fn limits(&self) -> crate::api::BatteryLimits { + let max = Self::max(); + let min = Self::min(); + crate::api::BatteryLimits { + charge_rate: Some(RangeLimit{ + min: min.charge_rate.unwrap(), + max: max.charge_rate.unwrap(), + }), + charge_step: 50, + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, rate: Option) { + self.charge_rate = rate; + } + + fn get_charge_rate(&self) -> Option { + self.charge_rate + } +} diff --git a/backend/src/settings/cpu.rs b/backend/src/settings/steam_deck_adv/cpu.rs similarity index 79% rename from backend/src/settings/cpu.rs rename to backend/src/settings/steam_deck_adv/cpu.rs index b2d1efe..1d04a27 100644 --- a/backend/src/settings/cpu.rs +++ b/backend/src/settings/steam_deck_adv/cpu.rs @@ -1,7 +1,9 @@ use std::convert::Into; -use super::MinMax; -use super::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::api::RangeLimit; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::settings::{TCpus, TCpu}; use crate::persist::CpuJson; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; @@ -25,7 +27,7 @@ impl OnSet for Cpus { "Failed to write `on` to `{}`: {}", CPU_SMT_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; } else { @@ -35,7 +37,7 @@ impl OnSet for Cpus { "Failed to write `off` to `{}`: {}", CPU_SMT_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; } @@ -129,13 +131,35 @@ impl Cpus { } } +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 clock_limits: Option>, pub governor: String, index: usize, - state: crate::state::Cpu, + state: crate::state::steam_deck::Cpu, } const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; @@ -150,14 +174,14 @@ impl Cpu { clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), governor: other.governor, index: i, - state: crate::state::Cpu::default(), + state: crate::state::steam_deck::Cpu::default(), }, _ => Self { online: other.online, clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), governor: other.governor, index: i, - state: crate::state::Cpu::default(), + state: crate::state::steam_deck::Cpu::default(), }, } } @@ -169,7 +193,7 @@ impl Cpu { 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: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; } @@ -184,7 +208,7 @@ impl Cpu { "Failed to write `manual` to `{}`: {}", CPU_FORCE_LIMITS_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; } @@ -199,7 +223,7 @@ impl Cpu { "Failed to write `{}` to `{}`: {}", &payload_max, CPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, }, )?; // min clock @@ -210,7 +234,7 @@ impl Cpu { "Failed to write `{}` to `{}`: {}", &payload_min, CPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, }, )?; } else if self.state.clock_limits_set || self.state.is_resuming { @@ -225,7 +249,7 @@ impl Cpu { "Failed to write `{}` to `{}`: {}", &payload_max, CPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, }, )?; // min clock @@ -236,7 +260,7 @@ impl Cpu { "Failed to write `{}` to `{}`: {}", &payload_min, CPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, }, )?; } @@ -244,7 +268,7 @@ impl Cpu { usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { SettingError { msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; @@ -257,7 +281,7 @@ impl Cpu { "Failed to write `{}` to `{}`: {}", &self.governor, &governor_path, e ), - setting: super::SettingVariant::Cpu, + setting: crate::settings::SettingVariant::Cpu, } })?; } @@ -275,14 +299,34 @@ impl Cpu { } } - fn from_sys(index: usize) -> Self { + fn from_sys(cpu_index: usize) -> Self { Self { - online: usdpl_back::api::files::read_single(cpu_online_path(index)).unwrap_or(1u8) != 0, + online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0, clock_limits: None, - governor: usdpl_back::api::files::read_single(cpu_governor_path(index)) + governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index)) .unwrap_or("schedutil".to_owned()), - index: index, - state: crate::state::Cpu::default(), + index: cpu_index, + state: crate::state::steam_deck::Cpu::default(), + } + } + + fn limits(&self) -> crate::api::CpuLimits { + let max = Self::max(); + let max_clocks = max.clock_limits.unwrap(); + + let min = Self::min(); + let min_clocks = min.clock_limits.unwrap(); + crate::api::CpuLimits { + clock_min_limits: Some(RangeLimit { + min: min_clocks.min, + max: max_clocks.min + }), + clock_max_limits: Some(RangeLimit { + min: min_clocks.max, + max: max_clocks.max + }), + clock_step: 100, + governors: vec![], // TODO } } } @@ -313,6 +357,28 @@ impl OnResume for Cpu { } } +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>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } +} + impl SettingsRange for Cpu { #[inline] fn max() -> Self { @@ -324,7 +390,7 @@ impl SettingsRange for Cpu { }), governor: "schedutil".to_owned(), index: usize::MAX, - state: crate::state::Cpu::default(), + state: crate::state::steam_deck::Cpu::default(), } } @@ -335,7 +401,7 @@ impl SettingsRange for Cpu { clock_limits: Some(MinMax { max: 500, min: 1400 }), governor: "schedutil".to_owned(), index: usize::MIN, - state: crate::state::Cpu::default(), + state: crate::state::steam_deck::Cpu::default(), } } } diff --git a/backend/src/settings/gpu.rs b/backend/src/settings/steam_deck_adv/gpu.rs similarity index 72% rename from backend/src/settings/gpu.rs rename to backend/src/settings/steam_deck_adv/gpu.rs index 3a3b15a..fccd598 100644 --- a/backend/src/settings/gpu.rs +++ b/backend/src/settings/steam_deck_adv/gpu.rs @@ -1,7 +1,9 @@ use std::convert::Into; -use super::MinMax; -use super::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::api::RangeLimit; +use crate::settings::MinMax; +use crate::settings::{OnResume, OnSet, SettingError, SettingsRange}; +use crate::settings::TGpu; use crate::persist::GpuJson; const SLOW_PPT: u8 = 1; @@ -13,7 +15,7 @@ pub struct Gpu { pub slow_ppt: Option, pub clock_limits: Option>, pub slow_memory: bool, - state: crate::state::Gpu, + state: crate::state::steam_deck::Gpu, } // same as CPU @@ -30,14 +32,14 @@ impl Gpu { slow_ppt: other.slow_ppt, clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), slow_memory: other.slow_memory, - state: crate::state::Gpu::default(), + state: crate::state::steam_deck::Gpu::default(), }, _ => Self { fast_ppt: other.fast_ppt, slow_ppt: other.slow_ppt, clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), slow_memory: other.slow_memory, - state: crate::state::Gpu::default(), + state: crate::state::steam_deck::Gpu::default(), }, } } @@ -52,7 +54,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", fast_ppt, &fast_ppt_path, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -65,7 +67,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", slow_ppt, &slow_ppt_path, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -79,7 +81,7 @@ impl Gpu { "Failed to write `manual` to `{}`: {}", GPU_FORCE_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; } @@ -87,7 +89,7 @@ impl Gpu { usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8) .map_err(|e| SettingError { msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, })?; if let Some(clock_limits) = &self.clock_limits { // set clock limits @@ -100,7 +102,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_max, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; // min clock @@ -111,7 +113,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_min, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; } else if self.state.clock_limits_set || self.state.is_resuming { @@ -125,7 +127,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_max, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; // min clock @@ -136,7 +138,7 @@ impl Gpu { "Failed to write `{}` to `{}`: {}", &payload_min, GPU_CLOCK_LIMITS_PATH, e ), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, }, )?; } @@ -144,7 +146,7 @@ impl Gpu { usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| { SettingError { msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e), - setting: super::SettingVariant::Gpu, + setting: crate::settings::SettingVariant::Gpu, } })?; @@ -180,7 +182,7 @@ impl Gpu { slow_ppt: None, clock_limits: None, slow_memory: false, - state: crate::state::Gpu::default(), + state: crate::state::steam_deck::Gpu::default(), } } } @@ -216,14 +218,14 @@ impl SettingsRange for Gpu { #[inline] fn max() -> Self { Self { - fast_ppt: Some(30000000), - slow_ppt: Some(29000000), + fast_ppt: Some(30_000_000), + slow_ppt: Some(29_000_000), clock_limits: Some(MinMax { min: 1600, max: 1600, }), slow_memory: false, - state: crate::state::Gpu::default(), + state: crate::state::steam_deck::Gpu::default(), } } @@ -234,11 +236,70 @@ impl SettingsRange for Gpu { slow_ppt: Some(1000000), clock_limits: Some(MinMax { min: 200, max: 200 }), slow_memory: true, - state: crate::state::Gpu::default(), + state: crate::state::steam_deck::Gpu::default(), } } } +impl TGpu for Gpu { + fn limits(&self) -> crate::api::GpuLimits { + let max = Self::max(); + let max_clock_limits = max.clock_limits.unwrap(); + + let min = Self::min(); + let min_clock_limits = min.clock_limits.unwrap(); + crate::api::GpuLimits { + fast_ppt_limits: Some(RangeLimit { + min: min.fast_ppt.unwrap(), + max: max.fast_ppt.unwrap(), + }), + slow_ppt_limits: Some(RangeLimit { + min: min.slow_ppt.unwrap(), + max: max.slow_ppt.unwrap(), + }), + ppt_step: 1_000_000, + tdp_limits: None, + tdp_boost_limits: None, + tdp_step: 42, + clock_min_limits: Some(RangeLimit { + min: min_clock_limits.min, + max: max_clock_limits.max, + }), + clock_max_limits: Some(RangeLimit { + min: min_clock_limits.min, + max: max_clock_limits.max, + }), + clock_step: 100, + memory_control_capable: true, + } + } + + fn json(&self) -> crate::persist::GpuJson { + self.clone().into() + } + + fn ppt(&mut self, fast: Option, slow: Option) { + self.fast_ppt = fast; + self.slow_ppt = slow; + } + + fn get_ppt(&self) -> (Option, Option) { + (self.fast_ppt, self.slow_ppt) + } + + fn clock_limits(&mut self, limits: Option>) { + self.clock_limits = limits; + } + + fn get_clock_limits(&self) -> Option<&MinMax> { + self.clock_limits.as_ref() + } + + fn slow_memory(&mut self) -> &mut bool { + &mut self.slow_memory + } +} + #[inline] fn gpu_power_path(power_number: u8) -> String { format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number) diff --git a/backend/src/settings/steam_deck_adv/mod.rs b/backend/src/settings/steam_deck_adv/mod.rs new file mode 100644 index 0000000..2039cae --- /dev/null +++ b/backend/src/settings/steam_deck_adv/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/traits.rs b/backend/src/settings/traits.rs index 56450df..8c49385 100644 --- a/backend/src/settings/traits.rs +++ b/backend/src/settings/traits.rs @@ -1,4 +1,6 @@ +use std::fmt::Debug; use super::SettingError; +use super::MinMax; pub trait OnSet { fn on_set(&mut self) -> Result<(), SettingError>; @@ -12,3 +14,69 @@ pub trait SettingsRange { fn max() -> Self; fn min() -> Self; } + +pub trait TGpu: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::GpuLimits; + + fn json(&self) -> crate::persist::GpuJson; + + fn ppt(&mut self, fast: Option, slow: Option); + + fn get_ppt(&self) -> (Option, Option); + + fn clock_limits(&mut self, limits: Option>); + + fn get_clock_limits(&self) -> Option<&MinMax>; + + fn slow_memory(&mut self) -> &mut bool; +} + +pub trait TCpus: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::CpusLimits; + + fn json(&self) -> Vec; + + fn cpus(&mut self) -> Vec<&mut dyn TCpu>; + + fn len(&self) -> usize; +} + +pub trait TCpu: Debug + Send { + fn online(&mut self) -> &mut bool; + + fn governor(&mut self, governor: String); + + fn get_governor(&self) -> &'_ str; + + fn clock_limits(&mut self, limits: Option>); + + fn get_clock_limits(&self) -> Option<&MinMax>; +} + +pub trait TGeneral: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::GeneralLimits; + + fn get_persistent(&self) -> bool; + + fn persistent(&mut self) -> &'_ mut bool; + + fn get_path(&self) -> &'_ std::path::Path; + + fn path(&mut self, path: std::path::PathBuf); + + fn get_name(&self) -> &'_ str; + + fn name(&mut self, name: String); + + fn provider(&self) -> crate::persist::DriverJson; +} + +pub trait TBattery: OnResume + OnSet + Debug + Send { + fn limits(&self) -> crate::api::BatteryLimits; + + fn json(&self) -> crate::persist::BatteryJson; + + fn charge_rate(&mut self, rate: Option); + + fn get_charge_rate(&self) -> Option; +} diff --git a/backend/src/settings/unknown/battery.rs b/backend/src/settings/unknown/battery.rs new file mode 100644 index 0000000..e2cca2f --- /dev/null +++ b/backend/src/settings/unknown/battery.rs @@ -0,0 +1,49 @@ +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, + } + } +} + +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_rate: None, + charge_step: 50, + } + } + + fn json(&self) -> crate::persist::BatteryJson { + self.clone().into() + } + + fn charge_rate(&mut self, _rate: Option) { + } + + fn get_charge_rate(&self) -> Option { + None + } +} diff --git a/backend/src/settings/unknown/cpu.rs b/backend/src/settings/unknown/cpu.rs new file mode 100644 index 0000000..48d4b99 --- /dev/null +++ b/backend/src/settings/unknown/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/unknown/gpu.rs b/backend/src/settings/unknown/gpu.rs new file mode 100644 index 0000000..9733cc8 --- /dev/null +++ b/backend/src/settings/unknown/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/unknown/mod.rs b/backend/src/settings/unknown/mod.rs new file mode 100644 index 0000000..2039cae --- /dev/null +++ b/backend/src/settings/unknown/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/state/mod.rs b/backend/src/state/mod.rs index 1eb1e58..72ca58f 100644 --- a/backend/src/state/mod.rs +++ b/backend/src/state/mod.rs @@ -1,11 +1,7 @@ -mod battery; -mod cpu; mod error; -mod gpu; mod traits; -pub use battery::Battery; -pub use cpu::Cpu; +pub mod steam_deck; + pub use error::StateError; -pub use gpu::Gpu; pub use traits::OnPoll; diff --git a/backend/src/state/battery.rs b/backend/src/state/steam_deck/battery.rs similarity index 100% rename from backend/src/state/battery.rs rename to backend/src/state/steam_deck/battery.rs diff --git a/backend/src/state/cpu.rs b/backend/src/state/steam_deck/cpu.rs similarity index 100% rename from backend/src/state/cpu.rs rename to backend/src/state/steam_deck/cpu.rs diff --git a/backend/src/state/gpu.rs b/backend/src/state/steam_deck/gpu.rs similarity index 100% rename from backend/src/state/gpu.rs rename to backend/src/state/steam_deck/gpu.rs diff --git a/backend/src/state/steam_deck/mod.rs b/backend/src/state/steam_deck/mod.rs new file mode 100644 index 0000000..c7dca59 --- /dev/null +++ b/backend/src/state/steam_deck/mod.rs @@ -0,0 +1,7 @@ +mod battery; +mod cpu; +mod gpu; + +pub use battery::Battery; +pub use cpu::Cpu; +pub use gpu::Gpu; diff --git a/src/backend.ts b/src/backend.ts index 2e50f60..d985380 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -24,6 +24,50 @@ export async function initBackend() { //setReady(true); } +// API limit types + +export type RangeLimit = { + min: number; + max: number; +}; + +export type SettingsLimits = { + battery: BatteryLimits; + cpu: CpusLimits; + gpu: GpuLimits; + general: GeneralLimits; +}; + +export type BatteryLimits = { + charge_rate: RangeLimit | null; + charge_step: number; +}; + +export type CpuLimits = { + clock_min_limits: RangeLimit | null; + clock_max_limits: RangeLimit | null; + clock_step: number; + governors: string[]; +}; + +export type CpusLimits = { + cpus: CpuLimits[]; + count: number; + smt_capable: boolean; +}; + +export type GeneralLimits = {}; + +export type GpuLimits = { + fast_ppt_limits: RangeLimit | null; + slow_ppt_limits: RangeLimit | null; + ppt_step: number; + clock_min_limits: RangeLimit | null; + clock_max_limits: RangeLimit | null; + clock_step: number; + memory_control_capable: boolean; +}; + // API export async function getInfo(): Promise { @@ -66,9 +110,9 @@ export async function setCpuSmt(status: boolean): Promise { return (await call_backend("CPU_set_smt", [status]))[0]; } -export async function getCpuCount(): Promise { +/*export async function getCpuCount(): Promise { return (await call_backend("CPU_count", []))[0]; -} +}*/ export async function setCpuOnline(index: number, online: boolean): Promise { return (await call_backend("CPU_set_online", [index, online]))[0]; @@ -165,3 +209,7 @@ export async function getGeneralSettingsName(): Promise { export async function waitForComplete(): Promise { return (await call_backend("GENERAL_wait_for_unlocks", []))[0]; } + +export async function getLimits(): Promise { + return (await call_backend("GENERAL_get_limits", []))[0]; +} diff --git a/src/index.tsx b/src/index.tsx index 8a33f52..98352c4 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,7 @@ import { //MenuItem, PanelSection, PanelSectionRow, - //Router, + Router, ServerAPI, //showContextMenu, staticClasses, @@ -17,8 +17,8 @@ import { //DropdownOption, SingleDropdownOption, //NotchLabel - gamepadDialogClasses, - joinClassNames, + //gamepadDialogClasses, + //joinClassNames, } from "decky-frontend-lib"; import { VFC, useState } from "react"; import { GiDrill } from "react-icons/gi"; @@ -32,7 +32,9 @@ var lifetimeHook: any = null; var startHook: any = null; var usdplReady = false; -var smtAllowed = true; +var eggCount = 0; + +//var smtAllowed = true; var advancedMode = false; var advancedCpu = 1; @@ -41,44 +43,19 @@ type MinMax = { max: number | null; } -const governorOptions: SingleDropdownOption[] = [ - { - data: "conservative", - label: conservative, - }, - { - data: "ondemand", - label: ondemand, - }, - { - data: "userspace", - label: userspace, - }, - { - data: "powersave", - label: powersave, - }, - { - data: "performance", - label: performance, - }, - { - data: "schedutil", - label: schedutil, - }, -]; - // usdpl persistent store keys const BACKEND_INFO = "VINFO"; +const LIMITS_INFO = "LIMITS_all"; + const CURRENT_BATT = "BATTERY_current_now"; const CHARGE_RATE_BATT = "BATTERY_charge_rate"; const CHARGE_NOW_BATT = "BATTERY_charge_now"; const CHARGE_FULL_BATT = "BATTERY_charge_full"; const CHARGE_DESIGN_BATT = "BATTERY_charge_design" -const TOTAL_CPUS = "CPUs_total"; +//const TOTAL_CPUS = "CPUs_total"; const ONLINE_CPUS = "CPUs_online"; const ONLINE_STATUS_CPUS = "CPUs_status_online"; const SMT_CPU = "CPUs_SMT"; @@ -107,7 +84,7 @@ function countCpus(statii: boolean[]): number { } function syncPlebClockToAdvanced() { - const cpuCount = get_value(TOTAL_CPUS); + const cpuCount = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.count; const minClock = get_value(CLOCK_MIN_CPU); const maxClock = get_value(CLOCK_MAX_CPU); let clockArr = []; @@ -123,18 +100,23 @@ function syncPlebClockToAdvanced() { const reload = function() { if (!usdplReady) {return;} + backend.resolve(backend.getLimits(), (limits) => { + set_value(LIMITS_INFO, limits); + console.debug("POWERTOOLS: got limits", limits); + }); + backend.resolve(backend.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) }); backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_BATT, rate) }); backend.resolve(backend.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) }); backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_BATT, rate) }); backend.resolve(backend.getBatteryChargeDesign(), (rate: number) => { set_value(CHARGE_DESIGN_BATT, rate) }); - backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)}); + //backend.resolve(backend.getCpuCount(), (count: number) => { set_value(TOTAL_CPUS, count)}); backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => { set_value(ONLINE_STATUS_CPUS, statii); const count = countCpus(statii); set_value(ONLINE_CPUS, count); - set_value(SMT_CPU, statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3] && smtAllowed); + set_value(SMT_CPU, statii.length > 3 && statii[0] == statii[1] && statii[2] == statii[3]); }); backend.resolve(backend.getCpuClockLimits(0), (limits: number[]) => { set_value(CLOCK_MIN_CPU, limits[0]); @@ -144,7 +126,6 @@ const reload = function() { backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { set_value(GOVERNOR_CPU, governors); console.log("POWERTOOLS: Governors from backend", governors); - console.log("POWERTOOLS: Governors in dropdown", governorOptions); }); backend.resolve(backend.getGpuPpt(), (ppts: number[]) => { @@ -226,10 +207,16 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { reloadGUI("periodic" + (new Date()).getTime().toString()); }, 1000); - const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard); + //const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard); - const total_cpus = get_value(TOTAL_CPUS); + const total_cpus = (get_value(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.count ?? 8; const advancedCpuIndex = advancedCpu - 1; + const smtAllowed = (get_value(LIMITS_INFO) as backend.SettingsLimits | null)?.cpu.smt_capable ?? true; + + const governorOptions: SingleDropdownOption[] = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.map((elem) => {return { + data: elem, + label: {elem}, + };}); return ( @@ -280,7 +267,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { label="Threads" value={get_value(ONLINE_CPUS)} step={1} - max={get_value(SMT_CPU)? total_cpus : total_cpus/2} + max={get_value(SMT_CPU) || !smtAllowed ? total_cpus : total_cpus/2} min={1} showValue={true} onChange={(cpus: number) => { @@ -307,13 +294,17 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } {!advancedMode && { if (value) { - set_value(CLOCK_MIN_CPU, 1400); - set_value(CLOCK_MAX_CPU, 3500); + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null) { + set_value(CLOCK_MIN_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.min); + } + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null) { + set_value(CLOCK_MAX_CPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.max); + } syncPlebClockToAdvanced(); reloadGUI("CPUFreqToggle"); } else { @@ -330,13 +321,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} /> } - {!advancedMode && + {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null && {get_value(CLOCK_MIN_CPU) != null && { @@ -360,13 +351,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} />} } - {!advancedMode && + {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null && {get_value(CLOCK_MAX_CPU) != null && { @@ -393,10 +384,10 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { {/* CPU advanced mode */} {advancedMode && { @@ -406,9 +397,9 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } {advancedMode && { console.debug("CPU " + advancedCpu.toString() + " is now " + status.toString()); if (get_value(SMT_CPU)) { @@ -426,14 +417,19 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } {advancedMode && { if (value) { const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; - clocks[advancedCpuIndex].min = 1400; - clocks[advancedCpuIndex].max = 3500; + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null) { + clocks[advancedCpuIndex].min = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.min; + } + + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null) { + clocks[advancedCpuIndex].max = (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.max; + } set_value(CLOCK_MIN_MAX_CPU, clocks); reloadGUI("CPUFreqToggle"); } else { @@ -448,13 +444,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} /> } - {advancedMode && + {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && { @@ -473,13 +469,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} />} } - {advancedMode && + {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null && {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && { @@ -498,7 +494,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} />} } - {advancedMode && + {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.length != 0 && @@ -527,15 +523,20 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
GPU
- + { ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null ||(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) && { if (value) { - set_value(SLOW_PPT_GPU, 15000000); - set_value(FAST_PPT_GPU, 15000000); + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) { + set_value(SLOW_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max); + } + + if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null) { + set_value(FAST_PPT_GPU, (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max); + } reloadGUI("GPUPPTToggle"); } else { set_value(SLOW_PPT_GPU, null); @@ -546,21 +547,22 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } }} /> - + } { get_value(SLOW_PPT_GPU) != null && { console.debug("SlowPPT is now " + ppt.toString()); const pptNow = get_value(SLOW_PPT_GPU); - if (ppt != pptNow) { - backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), ppt), + const realPpt = ppt; + if (realPpt != pptNow) { + backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), realPpt), (limits: number[]) => { set_value(FAST_PPT_GPU, limits[0]); set_value(SLOW_PPT_GPU, limits[1]); @@ -572,18 +574,19 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { {get_value(FAST_PPT_GPU) != null && { console.debug("FastPPT is now " + ppt.toString()); const pptNow = get_value(FAST_PPT_GPU); - if (ppt != pptNow) { - backend.resolve(backend.setGpuPpt(get_value(SLOW_PPT_GPU), ppt), + const realPpt = ppt; + if (realPpt != pptNow) { + backend.resolve(backend.setGpuPpt(realPpt, get_value(SLOW_PPT_GPU)), (limits: number[]) => { set_value(FAST_PPT_GPU, limits[0]); set_value(SLOW_PPT_GPU, limits[1]); @@ -593,15 +596,21 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} />} - + {((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits != null || (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits != null) && { if (value) { - set_value(CLOCK_MIN_GPU, 200); - set_value(CLOCK_MAX_GPU, 1600); + let clock_min_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits; + let clock_max_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits; + if (clock_min_limits != null) { + set_value(CLOCK_MIN_GPU, clock_min_limits.min); + } + if (clock_max_limits != null) { + set_value(CLOCK_MAX_GPU, clock_max_limits.max); + } reloadGUI("GPUFreqToggle"); } else { set_value(CLOCK_MIN_GPU, null); @@ -612,14 +621,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } }} /> - + } { get_value(CLOCK_MIN_GPU) != null && { @@ -640,9 +649,9 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { {get_value(CLOCK_MAX_GPU) != null && { @@ -659,7 +668,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }} />} - + {(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control_capable && = ({}) => { }) }} /> - + } {/* Battery */}
Battery
- -
-
-
- Now (Charge) -
-
- {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) -
-
-
-
- -
-
-
- Max (Design) -
-
- {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) -
-
-
-
- + {get_value(CHARGE_NOW_BATT) != null && get_value(CHARGE_FULL_BATT) != null && + eggCount++} + focusable={false}> + {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) + + } + {get_value(CHARGE_FULL_BATT) != null && get_value(CHARGE_DESIGN_BATT) != null && + eggCount++} + focusable={false}> + {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) + + } + {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_rate != null && = ({}) => { { get_value(CHARGE_RATE_BATT) != null && { @@ -737,18 +738,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { } }} />} - + } -
-
-
- Current -
-
- {get_value(CURRENT_BATT)} mA -
-
-
+ eggCount++} + focusable={false}> + {get_value(CURRENT_BATT)} mA +
{/* Persistence */}
@@ -769,56 +766,51 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { /> -
-
-
- Profile -
-
- {get_value(NAME_GEN)} -
-
-
+ eggCount++} + focusable={false}> + {get_value(NAME_GEN)} +
{/* Version Info */}
- Debug + {eggCount % 10 == 9 ? "Ha! Nerd" : "Debug"}
-
-
-
- Native -
-
- {get_value(BACKEND_INFO)} -
-
-
+ { + if (eggCount % 10 == 9) { + // you know you're bored and/or conceited when you spend time adding an easter egg + // that just sends people to your own project's repo + Router.NavigateToExternalWeb("https://github.com/NGnius/PowerTools"); + } + eggCount++; + }}> + {eggCount % 10 == 9 ? "by NGnius" : get_value(BACKEND_INFO)} +
-
-
-
- Framework -
-
- {target_usdpl()} -
-
-
+ eggCount++}> + {eggCount % 10 == 9 ? "<3 <3 <3" : target_usdpl()} +
-
-
-
- USDPL -
-
- v{version_usdpl()} -
-
-
+ { + if (eggCount % 10 == 9) { + // you know you're bored and/or conceited when you spend time adding an easter egg + // that just sends people to your own project's repo + Router.NavigateToExternalWeb("https://github.com/NGnius/usdpl-rs"); + } + eggCount++; + }}> + v{version_usdpl()} +