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
pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(crate::settings::Battery::read_current_now())
super::utility::map_optional_result(crate::settings::driver::read_current_now())
}
/// Charge now web method
pub fn charge_now(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(crate::settings::Battery::read_charge_now())
super::utility::map_optional_result(crate::settings::driver::read_charge_now())
}
/// Charge full web method
pub fn charge_full(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(crate::settings::Battery::read_charge_full())
super::utility::map_optional_result(crate::settings::driver::read_charge_full())
}
/// Charge design web method
pub fn charge_design(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(crate::settings::Battery::read_charge_design())
super::utility::map_optional_result(crate::settings::driver::read_charge_design())
}
/// Generate set battery charge rate web method

View file

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

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

View file

@ -1,3 +1,4 @@
mod api_types;
pub mod battery;
pub mod cpu;
pub mod general;
@ -7,3 +8,5 @@ mod async_utils;
mod utility;
pub(super) type ApiParameterType = Vec<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]
pub fn map_empty_result<T: Into<Primitive>>(
result: Result<(), SettingError>,

View file

@ -44,6 +44,8 @@ fn main() -> Result<(), ()> {
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
crate::settings::driver::auto_detect_loud();
let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE))
.map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into()))
.unwrap_or_else(|_| settings::Settings::system_default(DEFAULT_SETTINGS_FILE.into()));
@ -183,6 +185,10 @@ fn main() -> Result<(), ()> {
.register_async(
"GENERAL_wait_for_unlocks",
api::general::lock_unlock_all(api_sender.clone())
)
.register(
"GENERAL_get_limits",
api::general::get_limits(api_sender.clone())
);
api_worker::spawn(loaded_settings, api_handler);

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

View file

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

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

View file

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

View file

@ -1,12 +1,14 @@
use std::convert::Into;
use super::{OnResume, OnSet, SettingError, SettingsRange};
use crate::api::RangeLimit;
use crate::settings::{OnResume, OnSet, SettingError, SettingsRange};
use crate::settings::TBattery;
use crate::persist::BatteryJson;
#[derive(Debug, Clone)]
pub struct Battery {
pub charge_rate: Option<u64>,
state: crate::state::Battery,
state: crate::state::steam_deck::Battery,
}
const BATTERY_VOLTAGE: f64 = 7.7;
@ -23,11 +25,11 @@ impl Battery {
match version {
0 => Self {
charge_rate: other.charge_rate,
state: crate::state::Battery::default(),
state: crate::state::steam_deck::Battery::default(),
},
_ => Self {
charge_rate: other.charge_rate,
state: crate::state::Battery::default(),
state: crate::state::steam_deck::Battery::default(),
},
}
}
@ -38,7 +40,7 @@ impl Battery {
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, charge_rate).map_err(
|e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
},
)
} else if self.state.charge_rate_set {
@ -46,7 +48,7 @@ impl Battery {
usdpl_back::api::files::write_single(BATTERY_CHARGE_RATE_PATH, Self::max().charge_rate.unwrap()).map_err(
|e| SettingError {
msg: format!("Failed to write to `{}`: {}", BATTERY_CHARGE_RATE_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
},
)
} else {
@ -66,11 +68,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CURRENT_NOW_PATH) {
Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err(_) => panic!(
"Invalid error while reading from `{}`",
@ -86,11 +88,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_NOW_PATH) {
Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_NOW_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err(_) => panic!(
"Invalid error while reading from `{}`",
@ -105,11 +107,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_FULL_PATH) {
Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_FULL_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err(_) => panic!(
"Invalid error while reading from `{}`",
@ -124,11 +126,11 @@ impl Battery {
match usdpl_back::api::files::read_single::<_, u64, _>(BATTERY_CHARGE_DESIGN_PATH) {
Err((Some(e), None)) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CHARGE_DESIGN_PATH, e),
setting: super::SettingVariant::Battery,
setting: crate::settings::SettingVariant::Battery,
}),
Err(_) => panic!(
"Invalid error while reading from `{}`",
@ -142,7 +144,7 @@ impl Battery {
pub fn system_default() -> Self {
Self {
charge_rate: None,
state: crate::state::Battery::default(),
state: crate::state::steam_deck::Battery::default(),
}
}
}
@ -174,7 +176,7 @@ impl SettingsRange for Battery {
fn max() -> Self {
Self {
charge_rate: Some(2500),
state: crate::state::Battery::default(),
state: crate::state::steam_deck::Battery::default(),
}
}
@ -182,7 +184,33 @@ impl SettingsRange for Battery {
fn min() -> Self {
Self {
charge_rate: Some(250),
state: crate::state::Battery::default(),
state: crate::state::steam_deck::Battery::default(),
}
}
}
impl TBattery for Battery {
fn limits(&self) -> crate::api::BatteryLimits {
let max = Self::max();
let min = Self::min();
crate::api::BatteryLimits {
charge_rate: Some(RangeLimit{
min: min.charge_rate.unwrap(),
max: max.charge_rate.unwrap(),
}),
charge_step: 50,
}
}
fn json(&self) -> crate::persist::BatteryJson {
self.clone().into()
}
fn charge_rate(&mut self, rate: Option<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 super::MinMax;
use super::{OnResume, OnSet, SettingError, SettingsRange};
use crate::api::RangeLimit;
use crate::settings::MinMax;
use crate::settings::{OnResume, OnSet, SettingError, SettingsRange};
use crate::settings::{TCpus, TCpu};
use crate::persist::CpuJson;
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
@ -25,7 +27,7 @@ impl OnSet for Cpus {
"Failed to write `on` to `{}`: {}",
CPU_SMT_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
} else {
@ -35,7 +37,7 @@ impl OnSet for Cpus {
"Failed to write `off` to `{}`: {}",
CPU_SMT_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
}
@ -129,13 +131,35 @@ impl Cpus {
}
}
impl TCpus for Cpus {
fn limits(&self) -> crate::api::CpusLimits {
crate::api::CpusLimits {
cpus: self.cpus.iter().map(|x| x.limits()).collect(),
count: self.cpus.len(),
smt_capable: self.smt_capable,
}
}
fn json(&self) -> Vec<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::Cpu,
state: crate::state::steam_deck::Cpu,
}
const CPU_CLOCK_LIMITS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage";
@ -150,14 +174,14 @@ impl Cpu {
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor,
index: i,
state: crate::state::Cpu::default(),
state: crate::state::steam_deck::Cpu::default(),
},
_ => Self {
online: other.online,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor,
index: i,
state: crate::state::Cpu::default(),
state: crate::state::steam_deck::Cpu::default(),
},
}
}
@ -169,7 +193,7 @@ impl Cpu {
usdpl_back::api::files::write_single(&online_path, self.online as u8).map_err(|e| {
SettingError {
msg: format!("Failed to write to `{}`: {}", &online_path, e),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
}
@ -184,7 +208,7 @@ impl Cpu {
"Failed to write `manual` to `{}`: {}",
CPU_FORCE_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
}
@ -199,7 +223,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}",
&payload_max, CPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
},
)?;
// min clock
@ -210,7 +234,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}",
&payload_min, CPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
},
)?;
} else if self.state.clock_limits_set || self.state.is_resuming {
@ -225,7 +249,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}",
&payload_max, CPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
},
)?;
// min clock
@ -236,7 +260,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}",
&payload_min, CPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
},
)?;
}
@ -244,7 +268,7 @@ impl Cpu {
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
SettingError {
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
@ -257,7 +281,7 @@ impl Cpu {
"Failed to write `{}` to `{}`: {}",
&self.governor, &governor_path, e
),
setting: super::SettingVariant::Cpu,
setting: crate::settings::SettingVariant::Cpu,
}
})?;
}
@ -275,14 +299,34 @@ impl Cpu {
}
}
fn from_sys(index: usize) -> Self {
fn from_sys(cpu_index: usize) -> Self {
Self {
online: usdpl_back::api::files::read_single(cpu_online_path(index)).unwrap_or(1u8) != 0,
online: usdpl_back::api::files::read_single(cpu_online_path(cpu_index)).unwrap_or(1u8) != 0,
clock_limits: None,
governor: usdpl_back::api::files::read_single(cpu_governor_path(index))
governor: usdpl_back::api::files::read_single(cpu_governor_path(cpu_index))
.unwrap_or("schedutil".to_owned()),
index: index,
state: crate::state::Cpu::default(),
index: cpu_index,
state: crate::state::steam_deck::Cpu::default(),
}
}
fn limits(&self) -> crate::api::CpuLimits {
let max = Self::max();
let max_clocks = max.clock_limits.unwrap();
let min = Self::min();
let min_clocks = min.clock_limits.unwrap();
crate::api::CpuLimits {
clock_min_limits: Some(RangeLimit {
min: min_clocks.min,
max: max_clocks.min
}),
clock_max_limits: Some(RangeLimit {
min: min_clocks.max,
max: max_clocks.max
}),
clock_step: 100,
governors: vec![], // TODO
}
}
}
@ -313,6 +357,28 @@ impl OnResume for Cpu {
}
}
impl TCpu for Cpu {
fn online(&mut self) -> &mut bool {
&mut self.online
}
fn governor(&mut self, governor: String) {
self.governor = governor;
}
fn get_governor(&self) -> &'_ str {
&self.governor
}
fn clock_limits(&mut self, limits: Option<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 {
@ -324,7 +390,7 @@ impl SettingsRange for Cpu {
}),
governor: "schedutil".to_owned(),
index: usize::MAX,
state: crate::state::Cpu::default(),
state: crate::state::steam_deck::Cpu::default(),
}
}
@ -335,7 +401,7 @@ impl SettingsRange for Cpu {
clock_limits: Some(MinMax { max: 500, min: 1400 }),
governor: "schedutil".to_owned(),
index: usize::MIN,
state: crate::state::Cpu::default(),
state: crate::state::steam_deck::Cpu::default(),
}
}
}

View file

@ -1,7 +1,9 @@
use std::convert::Into;
use super::MinMax;
use super::{OnResume, OnSet, SettingError, SettingsRange};
use crate::api::RangeLimit;
use crate::settings::MinMax;
use crate::settings::{OnResume, OnSet, SettingError, SettingsRange};
use crate::settings::TGpu;
use crate::persist::GpuJson;
const SLOW_PPT: u8 = 1;
@ -13,7 +15,7 @@ pub struct Gpu {
pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMax<u64>>,
pub slow_memory: bool,
state: crate::state::Gpu,
state: crate::state::steam_deck::Gpu,
}
// same as CPU
@ -30,14 +32,14 @@ impl Gpu {
slow_ppt: other.slow_ppt,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
slow_memory: other.slow_memory,
state: crate::state::Gpu::default(),
state: crate::state::steam_deck::Gpu::default(),
},
_ => Self {
fast_ppt: other.fast_ppt,
slow_ppt: other.slow_ppt,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
slow_memory: other.slow_memory,
state: crate::state::Gpu::default(),
state: crate::state::steam_deck::Gpu::default(),
},
}
}
@ -52,7 +54,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
fast_ppt, &fast_ppt_path, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
}
})?;
}
@ -65,7 +67,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
slow_ppt, &slow_ppt_path, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
}
})?;
}
@ -79,7 +81,7 @@ impl Gpu {
"Failed to write `manual` to `{}`: {}",
GPU_FORCE_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
}
})?;
}
@ -87,7 +89,7 @@ impl Gpu {
usdpl_back::api::files::write_single(GPU_MEMORY_DOWNCLOCK_PATH, self.slow_memory as u8)
.map_err(|e| SettingError {
msg: format!("Failed to write to `{}`: {}", GPU_MEMORY_DOWNCLOCK_PATH, e),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
})?;
if let Some(clock_limits) = &self.clock_limits {
// set clock limits
@ -100,7 +102,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
&payload_max, GPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
},
)?;
// min clock
@ -111,7 +113,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
&payload_min, GPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
},
)?;
} else if self.state.clock_limits_set || self.state.is_resuming {
@ -125,7 +127,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
&payload_max, GPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
},
)?;
// min clock
@ -136,7 +138,7 @@ impl Gpu {
"Failed to write `{}` to `{}`: {}",
&payload_min, GPU_CLOCK_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
},
)?;
}
@ -144,7 +146,7 @@ impl Gpu {
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c\n").map_err(|e| {
SettingError {
msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Gpu,
setting: crate::settings::SettingVariant::Gpu,
}
})?;
@ -180,7 +182,7 @@ impl Gpu {
slow_ppt: None,
clock_limits: None,
slow_memory: false,
state: crate::state::Gpu::default(),
state: crate::state::steam_deck::Gpu::default(),
}
}
}
@ -216,14 +218,14 @@ impl SettingsRange for Gpu {
#[inline]
fn max() -> Self {
Self {
fast_ppt: Some(30000000),
slow_ppt: Some(29000000),
fast_ppt: Some(30_000_000),
slow_ppt: Some(29_000_000),
clock_limits: Some(MinMax {
min: 1600,
max: 1600,
}),
slow_memory: false,
state: crate::state::Gpu::default(),
state: crate::state::steam_deck::Gpu::default(),
}
}
@ -234,11 +236,70 @@ impl SettingsRange for Gpu {
slow_ppt: Some(1000000),
clock_limits: Some(MinMax { min: 200, max: 200 }),
slow_memory: true,
state: crate::state::Gpu::default(),
state: crate::state::steam_deck::Gpu::default(),
}
}
}
impl TGpu for Gpu {
fn limits(&self) -> crate::api::GpuLimits {
let max = Self::max();
let max_clock_limits = max.clock_limits.unwrap();
let min = Self::min();
let min_clock_limits = min.clock_limits.unwrap();
crate::api::GpuLimits {
fast_ppt_limits: Some(RangeLimit {
min: min.fast_ppt.unwrap(),
max: max.fast_ppt.unwrap(),
}),
slow_ppt_limits: Some(RangeLimit {
min: min.slow_ppt.unwrap(),
max: max.slow_ppt.unwrap(),
}),
ppt_step: 1_000_000,
tdp_limits: None,
tdp_boost_limits: None,
tdp_step: 42,
clock_min_limits: Some(RangeLimit {
min: min_clock_limits.min,
max: max_clock_limits.max,
}),
clock_max_limits: Some(RangeLimit {
min: min_clock_limits.min,
max: max_clock_limits.max,
}),
clock_step: 100,
memory_control_capable: true,
}
}
fn json(&self) -> crate::persist::GpuJson {
self.clone().into()
}
fn ppt(&mut self, fast: Option<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]
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

@ -1,4 +1,6 @@
use std::fmt::Debug;
use super::SettingError;
use super::MinMax;
pub trait OnSet {
fn on_set(&mut self) -> Result<(), SettingError>;
@ -12,3 +14,69 @@ pub trait SettingsRange {
fn max() -> Self;
fn min() -> Self;
}
pub trait TGpu: OnResume + OnSet + Debug + Send {
fn limits(&self) -> crate::api::GpuLimits;
fn json(&self) -> crate::persist::GpuJson;
fn ppt(&mut self, fast: Option<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 gpu;
mod traits;
pub use battery::Battery;
pub use cpu::Cpu;
pub mod steam_deck;
pub use error::StateError;
pub use gpu::Gpu;
pub use traits::OnPoll;

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