Add and (mostly) test driver support

This commit is contained in:
NGnius (Graham) 2022-11-19 15:21:09 -05:00
parent 165a78e231
commit 31c72de11e
34 changed files with 2379 additions and 358 deletions

View file

@ -0,0 +1,54 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct RangeLimit<T> {
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<RangeLimit<u64>>,
pub charge_step: u64,
}
#[derive(Serialize, Deserialize)]
pub struct CpusLimits {
pub cpus: Vec<CpuLimits>,
pub count: usize,
pub smt_capable: bool,
}
#[derive(Serialize, Deserialize)]
pub struct CpuLimits {
pub clock_min_limits: Option<RangeLimit<u64>>,
pub clock_max_limits: Option<RangeLimit<u64>>,
pub clock_step: u64,
pub governors: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct GeneralLimits {
}
#[derive(Serialize, Deserialize)]
pub struct GpuLimits {
pub fast_ppt_limits: Option<RangeLimit<u64>>,
pub slow_ppt_limits: Option<RangeLimit<u64>>,
pub ppt_step: u64,
pub tdp_limits: Option<RangeLimit<u64>>,
pub tdp_boost_limits: Option<RangeLimit<u64>>,
pub tdp_step: u64,
pub clock_min_limits: Option<RangeLimit<u64>>,
pub clock_max_limits: Option<RangeLimit<u64>>,
pub clock_step: u64,
pub memory_control_capable: bool,
}

View file

@ -6,22 +6,22 @@ use super::handler::{ApiMessage, BatteryMessage};
/// Current current (ha!) web method /// Current current (ha!) web method
pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType { 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 /// Charge now web method
pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType { 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 /// Charge full web method
pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType { 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 /// Charge design web method
pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType { 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 /// Generate set battery charge rate web method

View file

@ -3,14 +3,14 @@ use std::sync::{Arc, Mutex};
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
use usdpl_back::AsyncCallable; 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 crate::utility::{unwrap_lock, unwrap_maybe_fatal};
use super::handler::{ApiMessage, CpuMessage}; use super::handler::{ApiMessage, CpuMessage};
/// Available CPUs web method /// Available CPUs web method
pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType { pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result( super::utility::map_result(
Cpus::cpu_count() crate::settings::steam_deck::Cpus::cpu_count()
.map(|x| x as u64) .map(|x| x as u64)
.ok_or_else( .ok_or_else(
|| SettingError { || SettingError {

View file

@ -161,3 +161,19 @@ pub fn lock_unlock_all(
} }
} }
} }
/// Generate get limits web method
pub fn get_limits(
sender: Sender<ApiMessage>,
) -> 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())]
}
}

View file

@ -1,6 +1,6 @@
use std::sync::mpsc::{self, Receiver, Sender}; 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::persist::SettingsJson;
use crate::utility::unwrap_maybe_fatal; use crate::utility::unwrap_maybe_fatal;
@ -16,6 +16,7 @@ pub enum ApiMessage {
LoadSettings(String, String), // (path, name) LoadSettings(String, String), // (path, name)
LoadMainSettings, LoadMainSettings,
LoadSystemSettings, LoadSystemSettings,
GetLimits(Callback<super::SettingsLimits>),
} }
pub enum BatteryMessage { pub enum BatteryMessage {
@ -24,10 +25,10 @@ pub enum BatteryMessage {
} }
impl BatteryMessage { impl BatteryMessage {
fn process(self, settings: &mut Battery) { fn process(self, settings: &mut dyn TBattery) {
match self { match self {
Self::SetChargeRate(rate) => settings.charge_rate = rate, Self::SetChargeRate(rate) => settings.charge_rate(rate),
Self::GetChargeRate(cb) => cb(settings.charge_rate), Self::GetChargeRate(cb) => cb(settings.get_charge_rate()),
} }
} }
} }
@ -45,41 +46,41 @@ pub enum CpuMessage {
} }
impl CpuMessage { impl CpuMessage {
fn process(self, settings: &mut Cpus) { fn process(self, settings: &mut dyn TCpus) {
match self { 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) => { Self::SetCpusOnline(cpus) => {
for i in 0..cpus.len() { 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) => { Self::SetSmt(status, cb) => {
let mut result = Vec::with_capacity(settings.cpus.len()); let mut result = Vec::with_capacity(settings.len());
for i in 0..settings.cpus.len() { for i in 0..settings.len() {
settings.cpus[i].online = settings.cpus[i].online && (status || i % 2 == 0); *settings.cpus()[i].online() = *settings.cpus()[i].online() && (status || i % 2 == 0);
result.push(settings.cpus[i].online); result.push(*settings.cpus()[i].online());
} }
cb(result); cb(result);
} }
Self::GetCpusOnline(cb) => { Self::GetCpusOnline(cb) => {
let mut result = Vec::with_capacity(settings.cpus.len()); let mut result = Vec::with_capacity(settings.len());
for cpu in &settings.cpus { for cpu in settings.cpus() {
result.push(cpu.online); result.push(*cpu.online());
} }
cb(result); cb(result);
}, },
Self::SetClockLimits(index, clocks) => {settings.cpus.get_mut(index).map(|c| c.clock_limits = clocks);}, 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::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::SetCpuGovernor(index, gov) => {settings.cpus().get_mut(index).map(|c| c.governor(gov));},
Self::SetCpusGovernor(govs) => { Self::SetCpusGovernor(govs) => {
for i in 0..govs.len() { 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) => { Self::GetCpusGovernor(cb) => {
let mut result = Vec::with_capacity(settings.cpus.len()); let mut result = Vec::with_capacity(settings.len());
for cpu in &settings.cpus { for cpu in settings.cpus() {
result.push(cpu.governor.clone()); result.push(cpu.get_governor().to_owned());
} }
cb(result); cb(result);
} }
@ -97,17 +98,14 @@ pub enum GpuMessage {
} }
impl GpuMessage { impl GpuMessage {
fn process(self, settings: &mut Gpu) { fn process(self, settings: &mut dyn TGpu) {
match self { match self {
Self::SetPpt(fast, slow) => { Self::SetPpt(fast, slow) => settings.ppt(fast, slow),
settings.fast_ppt = fast; Self::GetPpt(cb) => cb(settings.get_ppt()),
settings.slow_ppt = slow; Self::SetClockLimits(clocks) => settings.clock_limits(clocks),
}, Self::GetClockLimits(cb) => cb(settings.get_clock_limits().map(|x| x.to_owned())),
Self::GetPpt(cb) => cb((settings.fast_ppt, settings.slow_ppt)), Self::SetSlowMemory(val) => *settings.slow_memory() = val,
Self::SetClockLimits(clocks) => settings.clock_limits = clocks, Self::GetSlowMemory(cb) => cb(*settings.slow_memory()),
Self::GetClockLimits(cb) => cb(settings.clock_limits.clone()),
Self::SetSlowMemory(val) => settings.slow_memory = val,
Self::GetSlowMemory(cb) => cb(settings.slow_memory),
} }
} }
} }
@ -119,11 +117,11 @@ pub enum GeneralMessage {
} }
impl GeneralMessage { impl GeneralMessage {
fn process(self, settings: &mut General) { fn process(self, settings: &mut dyn TGeneral) {
match self { match self {
Self::SetPersistent(val) => settings.persistent = val, Self::SetPersistent(val) => *settings.persistent() = val,
Self::GetPersistent(cb) => cb(settings.persistent), Self::GetPersistent(cb) => cb(*settings.persistent()),
Self::GetCurrentProfileName(cb) => cb(settings.name.clone()), Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()),
} }
} }
} }
@ -150,11 +148,11 @@ impl ApiMessageHandler {
} }
// save // save
log::debug!("api_worker is saving..."); log::debug!("api_worker is saving...");
let is_persistent = settings.general.persistent; let is_persistent = *settings.general.persistent();
if is_persistent { if is_persistent {
let save_path = crate::utility::settings_dir() let save_path = crate::utility::settings_dir()
.join(settings.general.path.clone()); .join(settings.general.get_path().clone());
let settings_clone = settings.clone(); let settings_clone = settings.json();
let save_json: SettingsJson = settings_clone.into(); let save_json: SettingsJson = settings_clone.into();
unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings"); unwrap_maybe_fatal(save_json.save(&save_path), "Failed to save settings");
log::debug!("Saved settings to {}", save_path.display()); log::debug!("Saved settings to {}", save_path.display());
@ -166,10 +164,10 @@ impl ApiMessageHandler {
pub fn process(&mut self, settings: &mut Settings, message: ApiMessage) { pub fn process(&mut self, settings: &mut Settings, message: ApiMessage) {
match message { match message {
ApiMessage::Battery(x) => x.process(&mut settings.battery), ApiMessage::Battery(x) => x.process(settings.battery.as_mut()),
ApiMessage::Cpu(x) => x.process(&mut settings.cpus), ApiMessage::Cpu(x) => x.process(settings.cpus.as_mut()),
ApiMessage::Gpu(x) => x.process(&mut settings.gpu), ApiMessage::Gpu(x) => x.process(settings.gpu.as_mut()),
ApiMessage::General(x) => x.process(&mut settings.general), ApiMessage::General(x) => x.process(settings.general.as_mut()),
ApiMessage::OnResume => { ApiMessage::OnResume => {
if let Err(e) = settings.on_resume() { if let Err(e) = settings.on_resume() {
log::error!("Settings on_resume() err: {}", e); log::error!("Settings on_resume() err: {}", e);
@ -194,6 +192,14 @@ impl ApiMessageHandler {
} }
ApiMessage::LoadSystemSettings => { ApiMessage::LoadSystemSettings => {
settings.load_system_default(); 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(),
});
} }
} }
} }

View file

@ -1,3 +1,4 @@
mod api_types;
pub mod battery; pub mod battery;
pub mod cpu; pub mod cpu;
pub mod general; pub mod general;
@ -7,3 +8,5 @@ mod async_utils;
mod utility; mod utility;
pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>; pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>;
pub use api_types::*;

View file

@ -14,6 +14,20 @@ pub fn map_result<T: Into<Primitive>>(result: Result<T, SettingError>) -> super:
} }
} }
#[inline]
pub fn map_optional_result<T: Into<Primitive>>(result: Result<Option<T>, 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] /*#[inline]
pub fn map_empty_result<T: Into<Primitive>>( pub fn map_empty_result<T: Into<Primitive>>(
result: Result<(), SettingError>, result: Result<(), SettingError>,

View file

@ -44,6 +44,8 @@ fn main() -> Result<(), ()> {
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
println!("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)) 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())) .map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into()))
.unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into())); .unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into()));
@ -183,6 +185,10 @@ fn main() -> Result<(), ()> {
.register_async( .register_async(
"GENERAL_wait_for_unlocks", "GENERAL_wait_for_unlocks",
api::general::lock_unlock_all(api_sender.clone()) 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); api_worker::spawn(loaded_settings, api_handler);

View file

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

View file

@ -3,7 +3,7 @@ use std::default::Default;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::JsonError; use super::JsonError;
use super::{BatteryJson, CpuJson, GpuJson}; use super::{BatteryJson, CpuJson, GpuJson, DriverJson};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SettingsJson { pub struct SettingsJson {
@ -13,6 +13,7 @@ pub struct SettingsJson {
pub cpus: Vec<CpuJson>, pub cpus: Vec<CpuJson>,
pub gpu: GpuJson, pub gpu: GpuJson,
pub battery: BatteryJson, pub battery: BatteryJson,
pub provider: Option<DriverJson>,
} }
impl Default for SettingsJson { impl Default for SettingsJson {
@ -24,6 +25,7 @@ impl Default for SettingsJson {
cpus: Vec::with_capacity(8), cpus: Vec::with_capacity(8),
gpu: GpuJson::default(), gpu: GpuJson::default(),
battery: BatteryJson::default(), battery: BatteryJson::default(),
provider: None,
} }
} }
} }

View file

@ -1,11 +1,13 @@
mod battery; mod battery;
mod cpu; mod cpu;
mod driver;
mod error; mod error;
mod general; mod general;
mod gpu; mod gpu;
pub use battery::BatteryJson; pub use battery::BatteryJson;
pub use cpu::CpuJson; pub use cpu::CpuJson;
pub use driver::DriverJson;
pub use general::{MinMaxJson, SettingsJson}; pub use general::{MinMaxJson, SettingsJson};
pub use gpu::GpuJson; pub use gpu::GpuJson;

View file

@ -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<dyn TGeneral>,
pub cpus: Box<dyn TCpus>,
pub gpu: Box<dyn TGpu>,
pub battery: Box<dyn TBattery>,
}
impl Driver {
pub fn init(settings: SettingsJson, json_path: std::path::PathBuf) -> Result<Self, SettingError> {
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<Self, SettingError> {
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<Option<u64>, 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<Option<f64>, 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<Option<f64>, 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<Option<f64>, 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),
}
}

View file

@ -1,9 +1,9 @@
use std::convert::Into;
use std::path::PathBuf; use std::path::PathBuf;
//use std::sync::{Arc, Mutex}; //use std::sync::{Arc, Mutex};
use super::{Battery, Cpus, Gpu}; //use super::{Battery, Cpus, Gpu};
use super::{OnResume, OnSet, SettingError}; use super::{OnResume, OnSet, SettingError};
use super::{TGeneral, TGpu, TCpus, TBattery};
use crate::persist::SettingsJson; use crate::persist::SettingsJson;
//use crate::utility::unwrap_lock; //use crate::utility::unwrap_lock;
@ -33,6 +33,7 @@ pub struct General {
pub persistent: bool, pub persistent: bool,
pub path: PathBuf, pub path: PathBuf,
pub name: String, pub name: String,
pub driver: crate::persist::DriverJson,
} }
impl OnSet for General { 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 struct Settings {
pub general: General, pub general: Box<dyn TGeneral>,
pub cpus: Cpus, pub cpus: Box<dyn TCpus>,
pub gpu: Gpu, pub gpu: Box<dyn TGpu>,
pub battery: Battery, pub battery: Box<dyn TBattery>,
} }
impl OnSet for Settings { impl OnSet for Settings {
@ -62,47 +103,38 @@ impl OnSet for Settings {
impl Settings { impl Settings {
#[inline] #[inline]
pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self { pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self {
match other.version { match super::Driver::init(other, json_path.clone()) {
0 => Self { Ok(x) => {
general: General { log::info!("Loaded settings for driver {:?}", x.general.provider());
persistent: other.persistent, Self {
path: json_path, general: x.general,
name: other.name, cpus: x.cpus,
}, gpu: x.gpu,
cpus: Cpus::from_json(other.cpus, other.version), battery: x.battery,
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),
}, },
Err(e) => {
log::error!("Driver init error: {}", e);
Self::system_default(json_path)
}
} }
} }
pub fn system_default(json_path: PathBuf) -> Self { pub fn system_default(json_path: PathBuf) -> Self {
let driver = super::Driver::system_default(json_path);
Self { Self {
general: General { general: driver.general,
persistent: false, cpus: driver.cpus,
path: json_path, gpu: driver.gpu,
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(), battery: driver.battery,
},
cpus: Cpus::system_default(),
gpu: Gpu::system_default(),
battery: Battery::system_default(),
} }
} }
pub fn load_system_default(&mut self) { pub fn load_system_default(&mut self) {
self.cpus = Cpus::system_default(); let driver = super::Driver::system_default(self.general.get_path().to_owned());
self.gpu = Gpu::system_default(); self.cpus = driver.cpus;
self.battery = Battery::system_default(); self.gpu = driver.gpu;
self.battery = driver.battery;
} }
pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result<bool, SettingError> { pub fn load_file(&mut self, filename: PathBuf, name: String, system_defaults: bool) -> Result<bool, SettingError> {
@ -115,24 +147,36 @@ impl Settings {
})?; })?;
if !settings_json.persistent { if !settings_json.persistent {
log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display()); log::warn!("Loaded persistent config `{}` ({}) with persistent=false", &settings_json.name, json_path.display());
self.general.persistent = false; *self.general.persistent() = false;
self.general.name = name; self.general.name(name);
} else { } else {
self.cpus = Cpus::from_json(settings_json.cpus, settings_json.version); self.cpus = Box::new(super::steam_deck::Cpus::from_json(settings_json.cpus, settings_json.version));
self.gpu = Gpu::from_json(settings_json.gpu, settings_json.version); self.gpu = Box::new(super::steam_deck::Gpu::from_json(settings_json.gpu, settings_json.version));
self.battery = Battery::from_json(settings_json.battery, 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.persistent() = true;
self.general.name = settings_json.name; self.general.name(settings_json.name);
} }
} else { } else {
if system_defaults { if system_defaults {
self.load_system_default(); self.load_system_default();
} }
self.general.persistent = false; *self.general.persistent() = false;
self.general.name = name; 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<SettingsJson> for Settings { /*impl Into<SettingsJson> for Settings {
#[inline] #[inline]
fn into(self) -> SettingsJson { fn into(self) -> SettingsJson {
log::debug!("Converting into json"); log::debug!("Converting into json");
SettingsJson { SettingsJson {
version: LATEST_VERSION, version: LATEST_VERSION,
name: self.general.name.clone(), name: self.general.get_name().to_owned(),
persistent: self.general.persistent, persistent: self.general.get_persistent(),
cpus: self.cpus.cpus cpus: self.cpus.json(),
.clone() gpu: self.gpu.json(),
.drain(..) battery: self.battery.json(),
.map(|cpu| cpu.into()) provider: Some(self.general.provider()),
.collect(),
gpu: self.gpu.clone().into(),
battery: self.battery.clone().into(),
}
} }
} }
}*/

View file

@ -1,19 +1,19 @@
mod battery; pub mod driver;
mod cpu;
mod error; mod error;
mod general; mod general;
mod gpu;
mod min_max; mod min_max;
mod traits; mod traits;
pub use battery::Battery; pub mod steam_deck;
pub use cpu::{Cpu, Cpus}; pub mod steam_deck_adv;
pub mod unknown;
pub use driver::Driver;
pub use general::{SettingVariant, Settings, General}; pub use general::{SettingVariant, Settings, General};
pub use gpu::Gpu;
pub use min_max::MinMax; pub use min_max::MinMax;
pub use error::SettingError; pub use error::SettingError;
pub use traits::{OnResume, OnSet, SettingsRange}; pub use traits::{OnResume, OnSet, SettingsRange, TGeneral, TGpu, TCpus, TBattery, TCpu};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -1,12 +1,14 @@
use std::convert::Into; 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; use crate::persist::BatteryJson;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Battery { pub struct Battery {
pub charge_rate: Option<u64>, pub charge_rate: Option<u64>,
state: crate::state::Battery, state: crate::state::steam_deck::Battery,
} }
const BATTERY_VOLTAGE: f64 = 7.7; const BATTERY_VOLTAGE: f64 = 7.7;
@ -23,11 +25,11 @@ impl Battery {
match version { match version {
0 => Self { 0 => Self {
charge_rate: other.charge_rate, charge_rate: other.charge_rate,
state: crate::state::Battery::default(), state: crate::state::steam_deck::Battery::default(),
}, },
_ => Self { _ => Self {
charge_rate: other.charge_rate, 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( usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err(
|e| SettingError { |e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}, },
) )
} else if self.state.charge_rate_set { } 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( usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, Self::max().charge_rate.unwrap()).map_err(
|e| SettingError { |e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e), msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}, },
) )
} else { } else {
@ -66,11 +68,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) {
Err((Some(e), None)) => Err(SettingError { Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), 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 { Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e), msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
Err(_) => panic!( Err(_) => panic!(
"Invalid error while reading from `{}`", "Invalid error while reading from `{}`",
@ -86,11 +88,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) {
Err((Some(e), None)) => Err(SettingError { Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), 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 { Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e), msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
Err(_) => panic!( Err(_) => panic!(
"Invalid error while reading from `{}`", "Invalid error while reading from `{}`",
@ -105,11 +107,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) {
Err((Some(e), None)) => Err(SettingError { Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), 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 { Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e), msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
Err(_) => panic!( Err(_) => panic!(
"Invalid error while reading from `{}`", "Invalid error while reading from `{}`",
@ -124,11 +126,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) { match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) {
Err((Some(e), None)) => Err(SettingError { Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), 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 { Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e), msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
setting: super::SettingVariant::Battery, setting: crate::settings::SettingVariant::Battery,
}), }),
Err(_) => panic!( Err(_) => panic!(
"Invalid error while reading from `{}`", "Invalid error while reading from `{}`",
@ -142,7 +144,7 @@ impl Battery {
pub fn system_default() -> Self { pub fn system_default() -> Self {
Self { Self {
charge_rate: None, 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 { fn max() -> Self {
Self { Self {
charge_rate: Some(2500), 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 { fn min() -> Self {
Self { Self {
charge_rate: Some(250), 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<u64>) {
self.charge_rate = rate;
}
fn get_charge_rate(&self) -> Option<u64> {
self.charge_rate
}
}

View file

@ -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<Cpu>,
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<usize> {
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::<usize>() {
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<CpuJson>, 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<crate::persist::CpuJson> {
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<MinMax<u64>>,
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<String> {
// 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<CpuJson> 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<MinMax<u64>>) {
self.clock_limits = limits;
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
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
)
}

View file

@ -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<u64>,
pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMax<u64>>,
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<GpuJson> 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<u64>, slow: Option<u64>) {
self.fast_ppt = fast.map(|x| x * PPT_DIVISOR);
self.slow_ppt = slow.map(|x| x * PPT_DIVISOR);
}
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
(self.fast_ppt.map(|x| x / PPT_DIVISOR), self.slow_ppt.map(|x| x / PPT_DIVISOR))
}
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
self.clock_limits = limits;
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
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)
}

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu;

View file

@ -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<u64>,
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<u64, SettingError> {
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<f64, SettingError> {
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<f64, SettingError> {
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<f64, SettingError> {
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<BatteryJson> 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<u64>) {
self.charge_rate = rate;
}
fn get_charge_rate(&self) -> Option<u64> {
self.charge_rate
}
}

View file

@ -1,7 +1,9 @@
use std::convert::Into; use std::convert::Into;
use super::MinMax; use crate::api::RangeLimit;
use super::{OnResume, OnSet, SettingError, SettingsRange}; use crate::settings::MinMax;
use crate::settings::{OnResume, OnSet, SettingError, SettingsRange};
use crate::settings::{TCpus, TCpu};
use crate::persist::CpuJson; use crate::persist::CpuJson;
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present"; const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
@ -25,7 +27,7 @@ impl OnSet for Cpus {
"Failed to write `on` to `{}`: {}", "Failed to write `on` to `{}`: {}",
CPU_SMT_PATH, e CPU_SMT_PATH, e
), ),
setting: super::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
} }
})?; })?;
} else { } else {
@ -35,7 +37,7 @@ impl OnSet for Cpus {
"Failed to write `off` to `{}`: {}", "Failed to write `off` to `{}`: {}",
CPU_SMT_PATH, e 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<crate::persist::CpuJson> {
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)] #[derive(Debug, Clone)]
pub struct Cpu { pub struct Cpu {
pub online: bool, pub online: bool,
pub clock_limits: Option<MinMax<u64>>, pub clock_limits: Option<MinMax<u64>>,
pub governor: String, pub governor: String,
index: usize, 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"; 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)), clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor, governor: other.governor,
index: i, index: i,
state: crate::state::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
}, },
_ => Self { _ => Self {
online: other.online, online: other.online,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor, governor: other.governor,
index: i, 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| { usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write to `{}`: {}", &online_path, e), 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 `{}`: {}", "Failed to write `manual` to `{}`: {}",
CPU_FORCE_LIMITS_PATH, e CPU_FORCE_LIMITS_PATH, e
), ),
setting: super::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
} }
})?; })?;
} }
@ -199,7 +223,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_max, CPU_CLOCK_LIMITS_PATH, e &payload_max, CPU_CLOCK_LIMITS_PATH, e
), ),
setting: super::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
}, },
)?; )?;
// min clock // min clock
@ -210,7 +234,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_min, CPU_CLOCK_LIMITS_PATH, e &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 { } else if self.state.clock_limits_set || self.state.is_resuming {
@ -225,7 +249,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_max, CPU_CLOCK_LIMITS_PATH, e &payload_max, CPU_CLOCK_LIMITS_PATH, e
), ),
setting: super::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
}, },
)?; )?;
// min clock // min clock
@ -236,7 +260,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_min, CPU_CLOCK_LIMITS_PATH, e &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| { usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e), msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Cpu, setting: crate::settings::SettingVariant::Cpu,
} }
})?; })?;
@ -257,7 +281,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&self.governor, &governor_path, e &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 { 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, 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()), .unwrap_or("schedutil".to_owned()),
index: index, index: cpu_index,
state: crate::state::Cpu::default(), 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<MinMax<u64>>) {
self.clock_limits = limits;
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
self.clock_limits.as_ref()
}
}
impl SettingsRange for Cpu { impl SettingsRange for Cpu {
#[inline] #[inline]
fn max() -> Self { fn max() -> Self {
@ -324,7 +390,7 @@ impl SettingsRange for Cpu {
}), }),
governor: "schedutil".to_owned(), governor: "schedutil".to_owned(),
index: usize::MAX, 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 }), clock_limits: Some(MinMax { max: 500, min: 1400 }),
governor: "schedutil".to_owned(), governor: "schedutil".to_owned(),
index: usize::MIN, index: usize::MIN,
state: crate::state::Cpu::default(), state: crate::state::steam_deck::Cpu::default(),
} }
} }
} }

View file

@ -1,7 +1,9 @@
use std::convert::Into; use std::convert::Into;
use super::MinMax; use crate::api::RangeLimit;
use super::{OnResume, OnSet, SettingError, SettingsRange}; use crate::settings::MinMax;
use crate::settings::{OnResume, OnSet, SettingError, SettingsRange};
use crate::settings::TGpu;
use crate::persist::GpuJson; use crate::persist::GpuJson;
const SLOW_PPT: u8 = 1; const SLOW_PPT: u8 = 1;
@ -13,7 +15,7 @@ pub struct Gpu {
pub slow_ppt: Option<u64>, pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMax<u64>>, pub clock_limits: Option<MinMax<u64>>,
pub slow_memory: bool, pub slow_memory: bool,
state: crate::state::Gpu, state: crate::state::steam_deck::Gpu,
} }
// same as CPU // same as CPU
@ -30,14 +32,14 @@ impl Gpu {
slow_ppt: other.slow_ppt, slow_ppt: other.slow_ppt,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
slow_memory: other.slow_memory, slow_memory: other.slow_memory,
state: crate::state::Gpu::default(), state: crate::state::steam_deck::Gpu::default(),
}, },
_ => Self { _ => Self {
fast_ppt: other.fast_ppt, fast_ppt: other.fast_ppt,
slow_ppt: other.slow_ppt, slow_ppt: other.slow_ppt,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)), clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
slow_memory: other.slow_memory, 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 `{}`: {}", "Failed to write `{}` to `{}`: {}",
fast_ppt, &fast_ppt_path, e 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 `{}`: {}", "Failed to write `{}` to `{}`: {}",
slow_ppt, &slow_ppt_path, e 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 `{}`: {}", "Failed to write `manual` to `{}`: {}",
GPU_FORCE_LIMITS_PATH, e 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) usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8)
.map_err(|e| SettingError { .map_err(|e| SettingError {
msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e), 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 { if let Some(clock_limits) = &self.clock_limits {
// set clock limits // set clock limits
@ -100,7 +102,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_max, GPU_CLOCK_LIMITS_PATH, e &payload_max, GPU_CLOCK_LIMITS_PATH, e
), ),
setting: super::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}, },
)?; )?;
// min clock // min clock
@ -111,7 +113,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_min, GPU_CLOCK_LIMITS_PATH, e &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 { } else if self.state.clock_limits_set || self.state.is_resuming {
@ -125,7 +127,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_max, GPU_CLOCK_LIMITS_PATH, e &payload_max, GPU_CLOCK_LIMITS_PATH, e
), ),
setting: super::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
}, },
)?; )?;
// min clock // min clock
@ -136,7 +138,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}", "Failed to write `{}` to `{}`: {}",
&payload_min, GPU_CLOCK_LIMITS_PATH, e &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| { usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
SettingError { SettingError {
msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e), msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Gpu, setting: crate::settings::SettingVariant::Gpu,
} }
})?; })?;
@ -180,7 +182,7 @@ impl Gpu {
slow_ppt: None, slow_ppt: None,
clock_limits: None, clock_limits: None,
slow_memory: false, slow_memory: false,
state: crate::state::Gpu::default(), state: crate::state::steam_deck::Gpu::default(),
} }
} }
} }
@ -216,14 +218,14 @@ impl SettingsRange for Gpu {
#[inline] #[inline]
fn max() -> Self { fn max() -> Self {
Self { Self {
fast_ppt: Some(30000000), fast_ppt: Some(30_000_000),
slow_ppt: Some(29000000), slow_ppt: Some(29_000_000),
clock_limits: Some(MinMax { clock_limits: Some(MinMax {
min: 1600, min: 1600,
max: 1600, max: 1600,
}), }),
slow_memory: false, 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), slow_ppt: Some(1000000),
clock_limits: Some(MinMax { min: 200, max: 200 }), clock_limits: Some(MinMax { min: 200, max: 200 }),
slow_memory: true, 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<u64>, slow: Option<u64>) {
self.fast_ppt = fast;
self.slow_ppt = slow;
}
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
(self.fast_ppt, self.slow_ppt)
}
fn clock_limits(&mut self, limits: Option<MinMax<u64>>) {
self.clock_limits = limits;
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
self.clock_limits.as_ref()
}
fn slow_memory(&mut self) -> &mut bool {
&mut self.slow_memory
}
}
#[inline] #[inline]
fn gpu_power_path(power_number: u8) -> String { fn gpu_power_path(power_number: u8) -> String {
format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number) format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number)

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu;

View file

@ -1,4 +1,6 @@
use std::fmt::Debug;
use super::SettingError; use super::SettingError;
use super::MinMax;
pub trait OnSet { pub trait OnSet {
fn on_set(&mut self) -> Result<(), SettingError>; fn on_set(&mut self) -> Result<(), SettingError>;
@ -12,3 +14,69 @@ pub trait SettingsRange {
fn max() -> Self; fn max() -> Self;
fn min() -> 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<u64>, slow: Option<u64>);
fn get_ppt(&self) -> (Option<u64>, Option<u64>);
fn clock_limits(&mut self, limits: Option<MinMax<u64>>);
fn get_clock_limits(&self) -> Option<&MinMax<u64>>;
fn slow_memory(&mut self) -> &mut bool;
}
pub trait TCpus: OnResume + OnSet + Debug + Send {
fn limits(&self) -> crate::api::CpusLimits;
fn json(&self) -> Vec<crate::persist::CpuJson>;
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<MinMax<u64>>);
fn get_clock_limits(&self) -> Option<&MinMax<u64>>;
}
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<u64>);
fn get_charge_rate(&self) -> Option<u64>;
}

View file

@ -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<BatteryJson> 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<u64>) {
}
fn get_charge_rate(&self) -> Option<u64> {
None
}
}

View file

@ -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<Cpu>,
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<usize> {
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::<usize>() {
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<CpuJson>, 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<crate::persist::CpuJson> {
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<CpuJson> 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<MinMax<u64>>) {
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
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
)
}

View file

@ -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<GpuJson> 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<u64>, _slow: Option<u64>) {
}
fn get_ppt(&self) -> (Option<u64>, Option<u64>) {
(None, None)
}
fn clock_limits(&mut self, _limits: Option<MinMax<u64>>) {
}
fn get_clock_limits(&self) -> Option<&MinMax<u64>> {
None
}
fn slow_memory(&mut self) -> &mut bool {
&mut self.slow_memory
}
}

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::{Cpu, Cpus};
pub use gpu::Gpu;

View file

@ -1,11 +1,7 @@
mod battery;
mod cpu;
mod error; mod error;
mod gpu;
mod traits; mod traits;
pub use battery::Battery; pub mod steam_deck;
pub use cpu::Cpu;
pub use error::StateError; pub use error::StateError;
pub use gpu::Gpu;
pub use traits::OnPoll; pub use traits::OnPoll;

View file

@ -0,0 +1,7 @@
mod battery;
mod cpu;
mod gpu;
pub use battery::Battery;
pub use cpu::Cpu;
pub use gpu::Gpu;

View file

@ -24,6 +24,50 @@ export async function initBackend() {
//setReady(true); //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 // API
export async function getInfo(): Promise<string> { export async function getInfo(): Promise<string> {
@ -66,9 +110,9 @@ export async function setCpuSmt(status: boolean): Promise<boolean> {
return (await call_backend("CPU_set_smt", [status]))[0]; return (await call_backend("CPU_set_smt", [status]))[0];
} }
export async function getCpuCount(): Promise<number> { /*export async function getCpuCount(): Promise<number> {
return (await call_backend("CPU_count", []))[0]; return (await call_backend("CPU_count", []))[0];
} }*/
export async function setCpuOnline(index: number, online: boolean): Promise<boolean> { export async function setCpuOnline(index: number, online: boolean): Promise<boolean> {
return (await call_backend("CPU_set_online", [index, online]))[0]; return (await call_backend("CPU_set_online", [index, online]))[0];
@ -165,3 +209,7 @@ export async function getGeneralSettingsName(): Promise<string> {
export async function waitForComplete(): Promise<boolean> { export async function waitForComplete(): Promise<boolean> {
return (await call_backend("GENERAL_wait_for_unlocks", []))[0]; return (await call_backend("GENERAL_wait_for_unlocks", []))[0];
} }
export async function getLimits(): Promise<SettingsLimits> {
return (await call_backend("GENERAL_get_limits", []))[0];
}

View file

@ -6,7 +6,7 @@ import {
//MenuItem, //MenuItem,
PanelSection, PanelSection,
PanelSectionRow, PanelSectionRow,
//Router, Router,
ServerAPI, ServerAPI,
//showContextMenu, //showContextMenu,
staticClasses, staticClasses,
@ -17,8 +17,8 @@ import {
//DropdownOption, //DropdownOption,
SingleDropdownOption, SingleDropdownOption,
//NotchLabel //NotchLabel
gamepadDialogClasses, //gamepadDialogClasses,
joinClassNames, //joinClassNames,
} from "decky-frontend-lib"; } from "decky-frontend-lib";
import { VFC, useState } from "react"; import { VFC, useState } from "react";
import { GiDrill } from "react-icons/gi"; import { GiDrill } from "react-icons/gi";
@ -32,7 +32,9 @@ var lifetimeHook: any = null;
var startHook: any = null; var startHook: any = null;
var usdplReady = false; var usdplReady = false;
var smtAllowed = true; var eggCount = 0;
//var smtAllowed = true;
var advancedMode = false; var advancedMode = false;
var advancedCpu = 1; var advancedCpu = 1;
@ -41,44 +43,19 @@ type MinMax = {
max: number | null; max: number | null;
} }
const governorOptions: SingleDropdownOption[] = [
{
data: "conservative",
label: <span>conservative</span>,
},
{
data: "ondemand",
label: <span>ondemand</span>,
},
{
data: "userspace",
label: <span>userspace</span>,
},
{
data: "powersave",
label: <span>powersave</span>,
},
{
data: "performance",
label: <span>performance</span>,
},
{
data: "schedutil",
label: <span>schedutil</span>,
},
];
// usdpl persistent store keys // usdpl persistent store keys
const BACKEND_INFO = "VINFO"; const BACKEND_INFO = "VINFO";
const LIMITS_INFO = "LIMITS_all";
const CURRENT_BATT = "BATTERY_current_now"; const CURRENT_BATT = "BATTERY_current_now";
const CHARGE_RATE_BATT = "BATTERY_charge_rate"; const CHARGE_RATE_BATT = "BATTERY_charge_rate";
const CHARGE_NOW_BATT = "BATTERY_charge_now"; const CHARGE_NOW_BATT = "BATTERY_charge_now";
const CHARGE_FULL_BATT = "BATTERY_charge_full"; const CHARGE_FULL_BATT = "BATTERY_charge_full";
const CHARGE_DESIGN_BATT = "BATTERY_charge_design" const CHARGE_DESIGN_BATT = "BATTERY_charge_design"
const TOTAL_CPUS = "CPUs_total"; //const TOTAL_CPUS = "CPUs_total";
const ONLINE_CPUS = "CPUs_online"; const ONLINE_CPUS = "CPUs_online";
const ONLINE_STATUS_CPUS = "CPUs_status_online"; const ONLINE_STATUS_CPUS = "CPUs_status_online";
const SMT_CPU = "CPUs_SMT"; const SMT_CPU = "CPUs_SMT";
@ -107,7 +84,7 @@ function countCpus(statii: boolean[]): number {
} }
function syncPlebClockToAdvanced() { 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 minClock = get_value(CLOCK_MIN_CPU);
const maxClock = get_value(CLOCK_MAX_CPU); const maxClock = get_value(CLOCK_MAX_CPU);
let clockArr = []; let clockArr = [];
@ -123,18 +100,23 @@ function syncPlebClockToAdvanced() {
const reload = function() { const reload = function() {
if (!usdplReady) {return;} 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.getBatteryCurrent(), (rate: number) => { set_value(CURRENT_BATT, rate) });
backend.resolve(backend.getBatteryChargeRate(), (rate: number) => { set_value(CHARGE_RATE_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.getBatteryChargeNow(), (rate: number) => { set_value(CHARGE_NOW_BATT, rate) });
backend.resolve(backend.getBatteryChargeFull(), (rate: number) => { set_value(CHARGE_FULL_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.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[]) => { backend.resolve(backend.getCpusOnline(), (statii: boolean[]) => {
set_value(ONLINE_STATUS_CPUS, statii); set_value(ONLINE_STATUS_CPUS, statii);
const count = countCpus(statii); const count = countCpus(statii);
set_value(ONLINE_CPUS, count); 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[]) => { backend.resolve(backend.getCpuClockLimits(0), (limits: number[]) => {
set_value(CLOCK_MIN_CPU, limits[0]); set_value(CLOCK_MIN_CPU, limits[0]);
@ -144,7 +126,6 @@ const reload = function() {
backend.resolve(backend.getCpusGovernor(), (governors: string[]) => { backend.resolve(backend.getCpusGovernor(), (governors: string[]) => {
set_value(GOVERNOR_CPU, governors); set_value(GOVERNOR_CPU, governors);
console.log("POWERTOOLS: Governors from backend", governors); console.log("POWERTOOLS: Governors from backend", governors);
console.log("POWERTOOLS: Governors in dropdown", governorOptions);
}); });
backend.resolve(backend.getGpuPpt(), (ppts: number[]) => { backend.resolve(backend.getGpuPpt(), (ppts: number[]) => {
@ -226,10 +207,16 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
reloadGUI("periodic" + (new Date()).getTime().toString()); reloadGUI("periodic" + (new Date()).getTime().toString());
}, 1000); }, 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 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: <span>{elem}</span>,
};});
return ( return (
<PanelSection> <PanelSection>
@ -280,7 +267,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
label="Threads" label="Threads"
value={get_value(ONLINE_CPUS)} value={get_value(ONLINE_CPUS)}
step={1} 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} min={1}
showValue={true} showValue={true}
onChange={(cpus: number) => { onChange={(cpus: number) => {
@ -307,13 +294,17 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
</PanelSectionRow>} </PanelSectionRow>}
{!advancedMode && <PanelSectionRow> {!advancedMode && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(CLOCK_MIN_CPU) != null && get_value(CLOCK_MAX_CPU) != null} checked={get_value(CLOCK_MIN_CPU) != null || get_value(CLOCK_MAX_CPU) != null}
label="Frequency Limits" label="Frequency Limits"
description="Set bounds on clock speed" description="Set bounds on clock speed"
onChange={(value: boolean) => { onChange={(value: boolean) => {
if (value) { if (value) {
set_value(CLOCK_MIN_CPU, 1400); if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null) {
set_value(CLOCK_MAX_CPU, 3500); 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(); syncPlebClockToAdvanced();
reloadGUI("CPUFreqToggle"); reloadGUI("CPUFreqToggle");
} else { } else {
@ -330,13 +321,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/> />
</PanelSectionRow>} </PanelSectionRow>}
{!advancedMode && <PanelSectionRow> {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits != null && <PanelSectionRow>
{get_value(CLOCK_MIN_CPU) != null && <SliderField {get_value(CLOCK_MIN_CPU) != null && <SliderField
label="Minimum (MHz)" label="Minimum (MHz)"
value={get_value(CLOCK_MIN_CPU)} value={get_value(CLOCK_MIN_CPU)}
max={3500} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.max}
min={1400} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_min_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MIN_CPU) == null} disabled={get_value(CLOCK_MIN_CPU) == null}
onChange={(freq: number) => { onChange={(freq: number) => {
@ -360,13 +351,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/>} />}
</PanelSectionRow>} </PanelSectionRow>}
{!advancedMode && <PanelSectionRow> {!advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits != null && <PanelSectionRow>
{get_value(CLOCK_MAX_CPU) != null && <SliderField {get_value(CLOCK_MAX_CPU) != null && <SliderField
label="Maximum (MHz)" label="Maximum (MHz)"
value={get_value(CLOCK_MAX_CPU)} value={get_value(CLOCK_MAX_CPU)}
max={3500} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.max}
min={500} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_max_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[0].clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MAX_CPU) == null} disabled={get_value(CLOCK_MAX_CPU) == null}
onChange={(freq: number) => { onChange={(freq: number) => {
@ -393,10 +384,10 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
{/* CPU advanced mode */} {/* CPU advanced mode */}
{advancedMode && <PanelSectionRow> {advancedMode && <PanelSectionRow>
<SliderField <SliderField
label="CPU to modify" label="Selected CPU"
value={advancedCpu} value={advancedCpu}
step={1} step={1}
max={8} max={total_cpus}
min={1} min={1}
showValue={true} showValue={true}
onChange={(cpuNum: number) => { onChange={(cpuNum: number) => {
@ -406,9 +397,9 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
</PanelSectionRow>} </PanelSectionRow>}
{advancedMode && <PanelSectionRow> {advancedMode && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(ONLINE_CPUS)[advancedCpuIndex]} checked={get_value(ONLINE_STATUS_CPUS)[advancedCpuIndex]}
label="Online" label="Online"
description="Allow the CPU thread to do processing" description="Allow the CPU thread to do work"
onChange={(status: boolean) => { onChange={(status: boolean) => {
console.debug("CPU " + advancedCpu.toString() + " is now " + status.toString()); console.debug("CPU " + advancedCpu.toString() + " is now " + status.toString());
if (get_value(SMT_CPU)) { if (get_value(SMT_CPU)) {
@ -426,14 +417,19 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
</PanelSectionRow>} </PanelSectionRow>}
{advancedMode && <PanelSectionRow> {advancedMode && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max} checked={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null || get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null}
label="Frequency Limits" label="Frequency Limits"
description="Set bounds on clock speed" description="Set bounds on clock speed"
onChange={(value: boolean) => { onChange={(value: boolean) => {
if (value) { if (value) {
const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[]; const clocks = get_value(CLOCK_MIN_MAX_CPU) as MinMax[];
clocks[advancedCpuIndex].min = 1400; if ((get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null) {
clocks[advancedCpuIndex].max = 3500; 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); set_value(CLOCK_MIN_MAX_CPU, clocks);
reloadGUI("CPUFreqToggle"); reloadGUI("CPUFreqToggle");
} else { } else {
@ -448,13 +444,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/> />
</PanelSectionRow>} </PanelSectionRow>}
{advancedMode && <PanelSectionRow> {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits != null && <PanelSectionRow>
{get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && <SliderField {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min != null && <SliderField
label="Minimum (MHz)" label="Minimum (MHz)"
value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min} value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min}
max={3500} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.max}
min={1400} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_min_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min == null} disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].min == null}
onChange={(freq: number) => { onChange={(freq: number) => {
@ -473,13 +469,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/>} />}
</PanelSectionRow>} </PanelSectionRow>}
{advancedMode && <PanelSectionRow> {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits != null && <PanelSectionRow>
{get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && <SliderField {get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max != null && <SliderField
label="Maximum (MHz)" label="Maximum (MHz)"
value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max} value={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max}
max={3500} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.max}
min={500} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_max_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max == null} disabled={get_value(CLOCK_MIN_MAX_CPU)[advancedCpuIndex].max == null}
onChange={(freq: number) => { onChange={(freq: number) => {
@ -498,7 +494,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/>} />}
</PanelSectionRow>} </PanelSectionRow>}
{advancedMode && <PanelSectionRow> {advancedMode && (get_value(LIMITS_INFO) as backend.SettingsLimits).cpu.cpus[advancedCpuIndex].governors.length != 0 && <PanelSectionRow>
<Field <Field
label="Governor" label="Governor"
> >
@ -527,15 +523,20 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
<div className={staticClasses.PanelSectionTitle}> <div className={staticClasses.PanelSectionTitle}>
GPU GPU
</div> </div>
<PanelSectionRow> { ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits != null ||(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(SLOW_PPT_GPU) != null && get_value(FAST_PPT_GPU) != null} checked={get_value(SLOW_PPT_GPU) != null || get_value(FAST_PPT_GPU) != null}
label="PowerPlay Limits" label="PowerPlay Limits"
description="Override APU TDP settings" description="Override APU TDP settings"
onChange={(value: boolean) => { onChange={(value: boolean) => {
if (value) { if (value) {
set_value(SLOW_PPT_GPU, 15000000); if ((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits != null) {
set_value(FAST_PPT_GPU, 15000000); 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"); reloadGUI("GPUPPTToggle");
} else { } else {
set_value(SLOW_PPT_GPU, null); set_value(SLOW_PPT_GPU, null);
@ -546,21 +547,22 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
} }
}} }}
/> />
</PanelSectionRow> </PanelSectionRow>}
<PanelSectionRow> <PanelSectionRow>
{ get_value(SLOW_PPT_GPU) != null && <SliderField { get_value(SLOW_PPT_GPU) != null && <SliderField
label="SlowPPT (uW)" label="SlowPPT (W)"
value={get_value(SLOW_PPT_GPU)} value={get_value(SLOW_PPT_GPU)}
max={29000000} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.max}
min={1000000} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.slow_ppt_limits!.min}
step={1000000} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.ppt_step}
showValue={true} showValue={true}
disabled={get_value(SLOW_PPT_GPU) == null} disabled={get_value(SLOW_PPT_GPU) == null}
onChange={(ppt: number) => { onChange={(ppt: number) => {
console.debug("SlowPPT is now " + ppt.toString()); console.debug("SlowPPT is now " + ppt.toString());
const pptNow = get_value(SLOW_PPT_GPU); const pptNow = get_value(SLOW_PPT_GPU);
if (ppt != pptNow) { const realPpt = ppt;
backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), ppt), if (realPpt != pptNow) {
backend.resolve(backend.setGpuPpt(get_value(FAST_PPT_GPU), realPpt),
(limits: number[]) => { (limits: number[]) => {
set_value(FAST_PPT_GPU, limits[0]); set_value(FAST_PPT_GPU, limits[0]);
set_value(SLOW_PPT_GPU, limits[1]); set_value(SLOW_PPT_GPU, limits[1]);
@ -572,18 +574,19 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
{get_value(FAST_PPT_GPU) != null && <SliderField {get_value(FAST_PPT_GPU) != null && <SliderField
label="FastPPT (uW)" label="FastPPT (W)"
value={get_value(FAST_PPT_GPU)} value={get_value(FAST_PPT_GPU)}
max={29000000} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.max}
min={1000000} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.fast_ppt_limits!.min}
step={1000000} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.ppt_step}
showValue={true} showValue={true}
disabled={get_value(FAST_PPT_GPU) == null} disabled={get_value(FAST_PPT_GPU) == null}
onChange={(ppt: number) => { onChange={(ppt: number) => {
console.debug("FastPPT is now " + ppt.toString()); console.debug("FastPPT is now " + ppt.toString());
const pptNow = get_value(FAST_PPT_GPU); const pptNow = get_value(FAST_PPT_GPU);
if (ppt != pptNow) { const realPpt = ppt;
backend.resolve(backend.setGpuPpt(get_value(SLOW_PPT_GPU), ppt), if (realPpt != pptNow) {
backend.resolve(backend.setGpuPpt(realPpt, get_value(SLOW_PPT_GPU)),
(limits: number[]) => { (limits: number[]) => {
set_value(FAST_PPT_GPU, limits[0]); set_value(FAST_PPT_GPU, limits[0]);
set_value(SLOW_PPT_GPU, limits[1]); set_value(SLOW_PPT_GPU, limits[1]);
@ -593,15 +596,21 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/>} />}
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> {((get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits != null || (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits != null) && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(CLOCK_MIN_GPU) != null && get_value(CLOCK_MAX_GPU) != null} checked={get_value(CLOCK_MIN_GPU) != null || get_value(CLOCK_MAX_GPU) != null}
label="Frequency Limits" label="Frequency Limits"
description="Override bounds on gpu clock" description="Override bounds on gpu clock"
onChange={(value: boolean) => { onChange={(value: boolean) => {
if (value) { if (value) {
set_value(CLOCK_MIN_GPU, 200); let clock_min_limits = (get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits;
set_value(CLOCK_MAX_GPU, 1600); 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"); reloadGUI("GPUFreqToggle");
} else { } else {
set_value(CLOCK_MIN_GPU, null); set_value(CLOCK_MIN_GPU, null);
@ -612,14 +621,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
} }
}} }}
/> />
</PanelSectionRow> </PanelSectionRow>}
<PanelSectionRow> <PanelSectionRow>
{ get_value(CLOCK_MIN_GPU) != null && <SliderField { get_value(CLOCK_MIN_GPU) != null && <SliderField
label="Minimum (MHz)" label="Minimum (MHz)"
value={get_value(CLOCK_MIN_GPU)} value={get_value(CLOCK_MIN_GPU)}
max={1600} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits!.max}
min={200} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_min_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MIN_GPU) == null} disabled={get_value(CLOCK_MIN_GPU) == null}
onChange={(val: number) => { onChange={(val: number) => {
@ -640,9 +649,9 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
{get_value(CLOCK_MAX_GPU) != null && <SliderField {get_value(CLOCK_MAX_GPU) != null && <SliderField
label="Maximum (MHz)" label="Maximum (MHz)"
value={get_value(CLOCK_MAX_GPU)} value={get_value(CLOCK_MAX_GPU)}
max={1600} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits!.max}
min={200} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_max_limits!.min}
step={100} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.clock_step}
showValue={true} showValue={true}
disabled={get_value(CLOCK_MAX_GPU) == null} disabled={get_value(CLOCK_MAX_GPU) == null}
onChange={(val: number) => { onChange={(val: number) => {
@ -659,7 +668,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}} }}
/>} />}
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> {(get_value(LIMITS_INFO) as backend.SettingsLimits).gpu.memory_control_capable && <PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(SLOW_MEMORY_GPU)} checked={get_value(SLOW_MEMORY_GPU)}
label="Downclock Memory" label="Downclock Memory"
@ -671,36 +680,28 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
}) })
}} }}
/> />
</PanelSectionRow> </PanelSectionRow>}
{/* Battery */} {/* Battery */}
<div className={staticClasses.PanelSectionTitle}> <div className={staticClasses.PanelSectionTitle}>
Battery Battery
</div> </div>
<PanelSectionRow> {get_value(CHARGE_NOW_BATT) != null && get_value(CHARGE_FULL_BATT) != null && <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Now (Charge)"
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> eggCount++}
Now (Charge) focusable={false}>
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%) {get_value(CHARGE_NOW_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_NOW_BATT) / get_value(CHARGE_FULL_BATT)).toFixed(1)}%)
</div> </Field>
</div> </PanelSectionRow>}
</div> {get_value(CHARGE_FULL_BATT) != null && get_value(CHARGE_DESIGN_BATT) != null && <PanelSectionRow>
</PanelSectionRow> <Field
<PanelSectionRow> label="Max (Design)"
<div className={FieldWithSeparator}> onClick={()=> eggCount++}
<div className={gamepadDialogClasses.FieldLabelRow}> focusable={false}>
<div className={gamepadDialogClasses.FieldLabel}>
Max (Design)
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%) {get_value(CHARGE_FULL_BATT).toFixed(1)} Wh ({(100 * get_value(CHARGE_FULL_BATT) / get_value(CHARGE_DESIGN_BATT)).toFixed(1)}%)
</div> </Field>
</div> </PanelSectionRow>}
</div> {(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_rate != null && <PanelSectionRow>
</PanelSectionRow>
<PanelSectionRow>
<ToggleField <ToggleField
checked={get_value(CHARGE_RATE_BATT) != null} checked={get_value(CHARGE_RATE_BATT) != null}
label="Charge Current Limits" label="Charge Current Limits"
@ -720,9 +721,9 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
{ get_value(CHARGE_RATE_BATT) != null && <SliderField { get_value(CHARGE_RATE_BATT) != null && <SliderField
label="Maximum (mA)" label="Maximum (mA)"
value={get_value(CHARGE_RATE_BATT)} value={get_value(CHARGE_RATE_BATT)}
max={2500} max={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_rate!.max}
min={250} min={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_rate!.min}
step={50} step={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_step}
showValue={true} showValue={true}
disabled={get_value(CHARGE_RATE_BATT) == null} disabled={get_value(CHARGE_RATE_BATT) == null}
onChange={(val: number) => { onChange={(val: number) => {
@ -737,18 +738,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
} }
}} }}
/>} />}
</PanelSectionRow> </PanelSectionRow>}
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Current"
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> eggCount++}
Current focusable={false}>
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{get_value(CURRENT_BATT)} mA {get_value(CURRENT_BATT)} mA
</div> </Field>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
{/* Persistence */} {/* Persistence */}
<div className={staticClasses.PanelSectionTitle}> <div className={staticClasses.PanelSectionTitle}>
@ -769,56 +766,51 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
/> />
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Profile"
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> eggCount++}
Profile focusable={false}>
</div>
<div className={gamepadDialogClasses.FieldChildren}>
{get_value(NAME_GEN)} {get_value(NAME_GEN)}
</div> </Field>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
{/* Version Info */} {/* Version Info */}
<div className={staticClasses.PanelSectionTitle}> <div className={staticClasses.PanelSectionTitle}>
Debug {eggCount % 10 == 9 ? "Ha! Nerd" : "Debug"}
</div> </div>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label={eggCount % 10 == 9 ? "PowerTools" : "Native"}
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> {
Native if (eggCount % 10 == 9) {
</div> // you know you're bored and/or conceited when you spend time adding an easter egg
<div className={gamepadDialogClasses.FieldChildren}> // that just sends people to your own project's repo
{get_value(BACKEND_INFO)} Router.NavigateToExternalWeb("https://github.com/NGnius/PowerTools");
</div> }
</div> eggCount++;
</div> }}>
{eggCount % 10 == 9 ? "by NGnius" : get_value(BACKEND_INFO)}
</Field>
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="Framework"
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> eggCount++}>
Framework {eggCount % 10 == 9 ? "<3 <3 <3" : target_usdpl()}
</div> </Field>
<div className={gamepadDialogClasses.FieldChildren}>
{target_usdpl()}
</div>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<div className={FieldWithSeparator}> <Field
<div className={gamepadDialogClasses.FieldLabelRow}> label="USDPL"
<div className={gamepadDialogClasses.FieldLabel}> onClick={()=> {
USDPL if (eggCount % 10 == 9) {
</div> // you know you're bored and/or conceited when you spend time adding an easter egg
<div className={gamepadDialogClasses.FieldChildren}> // that just sends people to your own project's repo
Router.NavigateToExternalWeb("https://github.com/NGnius/usdpl-rs");
}
eggCount++;
}}>
v{version_usdpl()} v{version_usdpl()}
</div> </Field>
</div>
</div>
</PanelSectionRow> </PanelSectionRow>
<PanelSectionRow> <PanelSectionRow>
<ButtonItem <ButtonItem