Add hardware limits provider and auto detection with online updater (cached locally)

This commit is contained in:
NGnius (Graham) 2022-12-02 21:12:13 -05:00
parent d1d5265224
commit 3766386726
34 changed files with 1864 additions and 139 deletions

1
.gitignore vendored
View file

@ -44,3 +44,4 @@ yalc.lock
/backend/target /backend/target
/bin /bin
/backend/out /backend/out
/**/target

129
backend/Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aead" name = "aead"
version = "0.4.3" version = "0.4.3"
@ -38,6 +44,30 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]] [[package]]
name = "async-recursion" name = "async-recursion"
version = "1.0.0" version = "1.0.0"
@ -96,6 +126,16 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]] [[package]]
name = "buf_redux" name = "buf_redux"
version = "0.8.4" version = "0.8.4"
@ -124,6 +164,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chunked_transfer"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.3.0" version = "0.3.0"
@ -142,6 +188,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -180,6 +235,15 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "encoding_rs"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.8.0" version = "1.8.0"
@ -189,6 +253,16 @@ dependencies = [
"instant", "instant",
] ]
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -433,6 +507,14 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "limits_core"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.17" version = "0.4.17"
@ -470,6 +552,15 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.4" version = "0.8.4"
@ -592,11 +683,14 @@ name = "powertools-rs"
version = "1.1.0" version = "1.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"limits_core",
"log", "log",
"regex",
"serde", "serde",
"serde_json", "serde_json",
"simplelog", "simplelog",
"tokio", "tokio",
"ureq",
"usdpl-back", "usdpl-back",
] ]
@ -669,6 +763,23 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
version = "0.5.3" version = "0.5.3"
@ -1063,6 +1174,24 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "ureq"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"
dependencies = [
"base64",
"brotli-decompressor",
"chunked_transfer",
"encoding_rs",
"flate2",
"log",
"once_cell",
"serde",
"serde_json",
"url",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.2" version = "2.2.2"

View file

@ -18,11 +18,18 @@ async-trait = { version = "0.1" }
log = "0.4" log = "0.4"
simplelog = "0.12" simplelog = "0.12"
# limits & driver functionality
limits_core = { version = "0.1.0", path = "./limits_core" }
regex = "1"
# ureq's tls feature does not like musl targets
ureq = { version = "2.5", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true }
[features] [features]
default = [] default = ["online"]
decky = ["usdpl-back/decky"] decky = ["usdpl-back/decky"]
crankshaft = ["usdpl-back/crankshaft"] crankshaft = ["usdpl-back/crankshaft"]
encrypt = ["usdpl-back/encrypt"] encrypt = ["usdpl-back/encrypt"]
online = ["ureq"]
[profile.release] [profile.release]
debug = false debug = false

View file

@ -0,0 +1,10 @@
[package]
name = "limits_core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View file

@ -0,0 +1,49 @@
use std::default::Default;
use serde::{Deserialize, Serialize};
/// Base JSON limits information
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Base {
/// System-specific configurations
pub configs: Vec<super::Config>,
/// URL from which to grab the next update
pub refresh: Option<String>,
}
impl Default for Base {
fn default() -> Self {
Base {
configs: vec![
super::Config {
name: "Steam Deck".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
os: None,
command: None,
},
limits: vec![
super::Limits::Cpu(super::CpuLimit::SteamDeck),
super::Limits::Gpu(super::GpuLimit::SteamDeck),
super::Limits::Battery(super::BatteryLimit::SteamDeck),
]
},
super::Config {
name: "Fallback".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: None,
os: None,
command: None,
},
limits: vec![
super::Limits::Cpu(super::CpuLimit::Unknown),
super::Limits::Gpu(super::GpuLimit::Unknown),
super::Limits::Battery(super::BatteryLimit::Unknown),
]
}
],
refresh: Some("http://limits.ngni.us:45000/powertools/v1".to_owned())
}
}
}

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "target")]
pub enum BatteryLimit {
SteamDeck,
SteamDeckAdvance,
Generic(GenericBatteryLimit),
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GenericBatteryLimit {
/* TODO */
}

View file

@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};
/// Conditions under which a config applies
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Conditions {
/// Regex pattern for dmidecode output
pub dmi: Option<String>,
/// Regex pattern for /proc/cpuinfo reading
pub cpuinfo: Option<String>,
/// Regex pattern for /etc/os-release reading
pub os: Option<String>,
/// Custom command to run, where an exit code of 0 means a successful match
pub command: Option<String>,
}
impl Conditions {
pub fn is_empty(&self) -> bool {
self.dmi.is_none() && self.cpuinfo.is_none() && self.os.is_none() && self.command.is_none()
}
}

View file

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Config {
pub name: String,
pub conditions: super::Conditions,
pub limits: Vec<super::Limits>,
}

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "target")]
pub enum CpuLimit {
SteamDeck,
SteamDeckAdvance,
Generic(GenericCpuLimit),
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GenericCpuLimit {
/* TODO */
}

View file

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "target")]
pub enum GpuLimit {
SteamDeck,
SteamDeckAdvance,
Generic(GenericGpuLimit),
Unknown,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GenericGpuLimit {
/* TODO */
}

View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "limits")]
pub enum Limits {
Cpu(super::CpuLimit),
Gpu(super::GpuLimit),
Battery(super::BatteryLimit),
}

View file

@ -0,0 +1,17 @@
mod base;
mod battery_limit;
mod conditions;
mod config;
mod cpu_limit;
mod gpu_limit;
mod limits;
mod target;
pub use base::Base;
pub use battery_limit::{BatteryLimit, GenericBatteryLimit};
pub use conditions::Conditions;
pub use cpu_limit::{CpuLimit, GenericCpuLimit};
pub use gpu_limit::{GpuLimit, GenericGpuLimit};
pub use config::Config;
pub use limits::Limits;
pub use target::Target;

View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Target {
SteamDeck,
SteamDeckAdvance,
Generic,
Unknown,
}

View file

@ -0,0 +1 @@
pub mod json;

1026
backend/limits_srv/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
[package]
name = "limits_srv"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
limits_core = { version = "0.1.0", path = "../limits_core" }
serde_json = "1.0"
warp = { version = "0.3" }
tokio = { version = "1.22", features = ["macros", "rt", "rt-multi-thread"] }

View file

@ -0,0 +1,43 @@
{
"configs": [
{
"name": "Steam Deck",
"conditions": {
"cpuinfo": "model name\t: AMD Custom APU 0405\n"
},
"limits": [
{
"limits": "Cpu",
"target": "SteamDeck"
},
{
"limits": "Gpu",
"target": "SteamDeck"
},
{
"limits": "Battery",
"target": "SteamDeck"
}
]
},
{
"name": "Fallback",
"conditions": {},
"limits": [
{
"limits": "Cpu",
"target": "Unknown"
},
{
"limits": "Gpu",
"target": "Unknown"
},
{
"limits": "Battery",
"target": "Unknown"
}
]
}
],
"refresh": "http://limits.ngni.us:45000/powertools/v1"
}

View file

@ -0,0 +1,53 @@
use std::sync::atomic::{Ordering, AtomicU64};
use std::sync::{RwLock, Arc};
use warp::Filter;
use limits_core::json::Base;
static VISIT_COUNT: AtomicU64 = AtomicU64::new(0);
fn get_limits(base: Base) -> impl warp::Reply {
VISIT_COUNT.fetch_add(1, Ordering::AcqRel);
//println!("Count: {} + 1", old_count);
warp::reply::json(&base)
}
fn get_visits() -> impl warp::Reply {
let count = VISIT_COUNT.load(Ordering::Relaxed);
//println!("Count: {}", count);
warp::reply::json(&count)
}
fn routes(base: Arc<RwLock<Base>>) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get().and(
warp::path!("powertools" / "v1")
.map(move || {
let base = base.read().expect("Failed to acquire base limits read lock").clone();
get_limits(base)
})
.or(
warp::path!("powertools" / "count")
.map(get_visits)
)
).recover(recovery)
}
pub async fn recovery(reject: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection> {
if reject.is_not_found() {
Ok(warp::hyper::StatusCode::NOT_FOUND)
} else {
Err(reject)
}
}
#[tokio::main]
async fn main() {
let file = std::fs::File::open("./pt_limits.json").expect("Failed to read limits file");
let limits: Base = serde_json::from_reader(file).expect("Failed to parse limits file");
assert!(limits.refresh.is_some(), "`refresh` cannot be null, since it will brick future refreshes");
warp::serve(routes(Arc::new(RwLock::new(limits))))
.run(([0, 0, 0, 0], 8080))
.await;
}

View file

@ -5,3 +5,5 @@ pub const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION");
pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json"; pub const DEFAULT_SETTINGS_FILE: &str = "default_settings.json";
pub const DEFAULT_SETTINGS_NAME: &str = "Main"; pub const DEFAULT_SETTINGS_NAME: &str = "Main";
pub const LIMITS_FILE: &str = "limits_cache.json";

View file

@ -53,7 +53,8 @@ fn main() -> Result<(), ()> {
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION); println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
crate::settings::driver::auto_detect_loud(); let _limits_handle = crate::settings::limits_worker_spawn();
log::info!("Detected device automatically, starting with driver: {:?} (This can be overriden)", crate::settings::auto_detect_provider());
let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE)) let mut loaded_settings = persist::SettingsJson::open(utility::settings_dir().join(DEFAULT_SETTINGS_FILE))
.map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into())) .map(|settings| settings::Settings::from_json(settings, DEFAULT_SETTINGS_FILE.into()))

View file

@ -3,7 +3,7 @@ use std::default::Default;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct BatteryJson { pub struct BatteryJson {
pub charge_rate: Option<u64>, pub charge_rate: Option<u64>,
pub charge_mode: Option<String>, pub charge_mode: Option<String>,

View file

@ -7,7 +7,7 @@ use super::MinMaxJson;
//const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000]; //const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000];
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct CpuJson { pub struct CpuJson {
pub online: bool, pub online: bool,
pub clock_limits: Option<MinMaxJson<u64>>, pub clock_limits: Option<MinMaxJson<u64>>,

View file

@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug, Clone)] #[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub enum DriverJson { pub enum DriverJson {
#[default]
#[serde(rename = "steam-deck", alias = "gabe-boy")] #[serde(rename = "steam-deck", alias = "gabe-boy")]
SteamDeck, SteamDeck,
#[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")] #[serde(rename = "steam-deck-oc", alias = "gabe-boy-advance")]
@ -13,4 +12,7 @@ pub enum DriverJson {
Generic, Generic,
#[serde(rename = "unknown")] #[serde(rename = "unknown")]
Unknown, Unknown,
#[default]
#[serde(rename = "auto")]
AutoDetect,
} }

View file

@ -56,7 +56,7 @@ impl SettingsJson {
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct MinMaxJson<T> { pub struct MinMaxJson<T> {
pub max: T, pub max: T,
pub min: T, pub min: T,

View file

@ -4,7 +4,7 @@ use std::default::Default;
use super::MinMaxJson; use super::MinMaxJson;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct GpuJson { pub struct GpuJson {
pub fast_ppt: Option<u64>, pub fast_ppt: Option<u64>,
pub slow_ppt: Option<u64>, pub slow_ppt: Option<u64>,

View file

@ -0,0 +1,191 @@
use std::fs::File;
use regex::RegexBuilder;
use limits_core::json::{Limits, BatteryLimit, CpuLimit, GpuLimit};
use crate::persist::{DriverJson, SettingsJson};
use crate::settings::{TGeneral, TCpus, TGpu, TBattery, Driver, General};
#[inline]
pub fn auto_detect_provider() -> DriverJson {
let provider = auto_detect0(None, crate::utility::settings_dir().join("autodetect.json"))
.general
.provider();
//log::info!("Detected device automatically, compatible driver: {:?}", provider);
provider
}
/// Device detection logic
pub fn auto_detect0(settings_opt: Option<SettingsJson>, json_path: std::path::PathBuf) -> Driver {
let mut builder = DriverBuilder::new(json_path);
let cpu_info: String = usdpl_back::api::files::read_single("/proc/cpuinfo").unwrap_or_default();
log::debug!("Read from /proc/cpuinfo:\n{}", cpu_info);
let os_info: String = usdpl_back::api::files::read_single("/etc/os-release").unwrap_or_default();
log::debug!("Read from /etc/os-release:\n{}", os_info);
let dmi_info: String = std::process::Command::new("dmidecode").output().map(|out| String::from_utf8_lossy(&out.stdout).into_owned()).unwrap_or_default();
log::debug!("Read dmidecode:\n{}", dmi_info);
let limits_path = super::utility::limits_path();
let limits = match File::open(&limits_path) {
Ok(f) => {
match serde_json::from_reader(f) {
Ok(lim) => lim,
Err(e) => {
log::warn!("Failed to parse limits file `{}`, cannot use for auto_detect: {}", limits_path.display(), e);
limits_core::json::Base::default()
}
}
},
Err(e) => {
log::warn!("Failed to open limits file `{}` (trying force refresh...): {}", limits_path.display(), e);
super::limits_worker::get_limits_blocking()
}
};
// build driver based on limits conditions
for conf in limits.configs {
let conditions = conf.conditions;
let mut matches = true;
if conditions.is_empty() {
matches = !builder.is_complete();
} else {
if let Some(dmi) = &conditions.dmi {
let pattern = RegexBuilder::new(dmi)
.multi_line(true)
.build()
.expect("Invalid DMI regex");
matches &=pattern.is_match(&dmi_info);
}
if let Some(cpuinfo) = &conditions.cpuinfo {
let pattern = RegexBuilder::new(cpuinfo)
.multi_line(true)
.build()
.expect("Invalid CPU regex");
matches &=pattern.is_match(&cpu_info);
}
if let Some(os) = &conditions.os {
let pattern = RegexBuilder::new(os)
.multi_line(true)
.build()
.expect("Invalid OS regex");
matches &=pattern.is_match(&os_info);
}
if let Some(cmd) = &conditions.command {
match std::process::Command::new("bash")
.args(["-c", cmd])
.status() {
Ok(status) => matches &= status.code().map(|c| c == 0).unwrap_or(false),
Err(e) => log::warn!("Ignoring bash limits error: {}", e),
}
}
}
if matches {
if let Some(settings) = &settings_opt {
for limit in conf.limits {
match limit {
Limits::Cpu(cpus) => {
let cpu_driver: Box<dyn TCpus> = match cpus {
CpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Cpus::from_json(settings.cpus.clone(), settings.version)),
CpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck_adv::Cpus::from_json(settings.cpus.clone(), settings.version)),
CpuLimit::Generic(x) => Box::new(crate::settings::generic::Cpus::from_json_and_limits(settings.cpus.clone(), settings.version, x)),
CpuLimit::Unknown => Box::new(crate::settings::unknown::Cpus::from_json(settings.cpus.clone(), settings.version)),
};
builder.cpus = Some(cpu_driver);
},
Limits::Gpu(gpu) => {
let driver: Box<dyn TGpu> = match gpu {
GpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Gpu::from_json(settings.gpu.clone(), settings.version)),
GpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck_adv::Gpu::from_json(settings.gpu.clone(), settings.version)),
GpuLimit::Generic(x) => Box::new(crate::settings::generic::Gpu::from_json_and_limits(settings.gpu.clone(), settings.version, x)),
GpuLimit::Unknown => Box::new(crate::settings::unknown::Gpu::from_json(settings.gpu.clone(), settings.version)),
};
builder.gpu = Some(driver);
},
Limits::Battery(batt) => {
let driver: Box<dyn TBattery> = match batt {
BatteryLimit::SteamDeck => Box::new(crate::settings::steam_deck::Battery::from_json(settings.battery.clone(), settings.version)),
BatteryLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Battery::from_json(settings.battery.clone(), settings.version)),
BatteryLimit::Generic(x) => Box::new(crate::settings::generic::Battery::from_json_and_limits(settings.battery.clone(), settings.version, x)),
BatteryLimit::Unknown => Box::new(crate::settings::unknown::Battery),
};
builder.battery = Some(driver);
}
}
}
} else {
for limit in conf.limits {
match limit {
Limits::Cpu(cpus) => {
let cpu_driver: Box<dyn TCpus> = match cpus {
CpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Cpus::system_default()),
CpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck_adv::Cpus::system_default()),
CpuLimit::Generic(x) => Box::new(crate::settings::generic::Cpus::from_limits(x)),
CpuLimit::Unknown => Box::new(crate::settings::unknown::Cpus::system_default()),
};
builder.cpus = Some(cpu_driver);
},
Limits::Gpu(gpu) => {
let driver: Box<dyn TGpu> = match gpu {
GpuLimit::SteamDeck => Box::new(crate::settings::steam_deck::Gpu::system_default()),
GpuLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck_adv::Gpu::system_default()),
GpuLimit::Generic(x) => Box::new(crate::settings::generic::Gpu::from_limits(x)),
GpuLimit::Unknown => Box::new(crate::settings::unknown::Gpu::system_default()),
};
builder.gpu = Some(driver);
},
Limits::Battery(batt) => {
let driver: Box<dyn TBattery> = match batt {
BatteryLimit::SteamDeck => Box::new(crate::settings::steam_deck::Battery::system_default()),
BatteryLimit::SteamDeckAdvance => Box::new(crate::settings::steam_deck::Battery::system_default()),
BatteryLimit::Generic(x) => Box::new(crate::settings::generic::Battery::from_limits(x)),
BatteryLimit::Unknown => Box::new(crate::settings::unknown::Battery),
};
builder.battery = Some(driver);
}
}
}
}
}
}
builder.build()
}
struct DriverBuilder {
general: Box<dyn TGeneral>,
cpus: Option<Box<dyn TCpus>>,
gpu: Option<Box<dyn TGpu>>,
battery: Option<Box<dyn TBattery>>,
}
impl DriverBuilder {
fn new(json_path: std::path::PathBuf) -> Self {
Self {
general: Box::new(General {
persistent: false,
path: json_path,
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
driver: DriverJson::AutoDetect,
}),
cpus: None,
gpu: None,
battery: None,
}
}
fn is_complete(&self) -> bool {
self.cpus.is_some() && self.gpu.is_some() && self.battery.is_some()
}
fn build(self) -> Driver {
Driver {
general: self.general,
cpus: self.cpus.unwrap_or_else(|| Box::new(crate::settings::unknown::Cpus::system_default())),
gpu: self.gpu.unwrap_or_else(|| Box::new(crate::settings::unknown::Gpu::system_default())),
battery: self.battery.unwrap_or_else(|| Box::new(crate::settings::unknown::Battery))
}
}
}

View file

@ -0,0 +1,110 @@
use std::thread::{self, JoinHandle};
#[cfg(feature = "online")]
use std::time::Duration;
use limits_core::json::Base;
#[cfg(feature = "online")]
pub fn spawn() -> JoinHandle<()> {
thread::spawn(move || {
log::info!("limits_worker starting...");
let sleep_dur = Duration::from_secs(60*60*24); // 1 day
let limits_path = super::utility::limits_path();
loop {
thread::sleep(sleep_dur);
if (limits_path.exists() && limits_path.is_file()) || !limits_path.exists() {
// try to load limits from file, fallback to built-in default
let base = match std::fs::File::open(&limits_path) {
Ok(f) => {
match serde_json::from_reader(f) {
Ok(b) => b,
Err(e) => {
log::error!("Cannot parse {}: {}", limits_path.display(), e);
Base::default()
}
}
},
Err(e) => {
log::error!("Cannot open {}: {}", limits_path.display(), e);
Base::default()
}
};
if let Some(refresh) = &base.refresh {
// try to retrieve newer version
match ureq::get(refresh)
.call() {
Ok(response) => {
let json_res: std::io::Result<Base> = response.into_json();
match json_res {
Ok(new_base) => {
match std::fs::File::create(&limits_path) {
Ok(f) => {
match serde_json::to_writer_pretty(f, &new_base) {
Ok(_) => log::info!("Successfully updated limits from `{}`, cached at {}", refresh, limits_path.display()),
Err(e) => log::error!("Failed to save limits json to file `{}`: {}", limits_path.display(), e),
}
},
Err(e) => log::error!("Cannot create {}: {}", limits_path.display(), e)
}
},
Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e),
}
},
Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e),
}
} else {
log::info!("limits_worker refresh is empty, terminating...");
break;
}
} else if !limits_path.is_file() {
log::error!("Path for storing limits is not a file!");
}
}
log::warn!("limits_worker completed!");
})
}
#[cfg(not(feature = "online"))]
pub fn spawn() -> JoinHandle<()> {
thread::spawn(move || {
log::info!("limits_worker disabled...");
})
}
pub fn get_limits_blocking() -> Base {
let limits_path = super::utility::limits_path();
if limits_path.is_file() {
match std::fs::File::open(&limits_path) {
Ok(f) => {
match serde_json::from_reader(f) {
Ok(b) => b,
Err(e) => {
log::error!("Cannot parse {}: {}", limits_path.display(), e);
Base::default()
}
}
},
Err(e) => {
log::error!("Cannot open {}: {}", limits_path.display(), e);
Base::default()
}
}
} else {
#[cfg(feature = "online")]
{
let refresh = Base::default().refresh.unwrap();
match ureq::get(&refresh) // try to retrieve newer version
.call() {
Ok(response) => {
let json_res: std::io::Result<Base> = response.into_json();
match json_res {
Ok(new_base) => return new_base,
Err(e) => log::error!("Cannot parse response from `{}`: {}", refresh, e)
}
},
Err(e) => log::warn!("Cannot download limits from `{}`: {}", refresh, e),
}
}
Base::default()
}
}

View file

@ -0,0 +1,5 @@
mod auto_detect;
pub mod limits_worker;
mod utility;
pub use auto_detect::{auto_detect_provider, auto_detect0};

View file

@ -0,0 +1,3 @@
pub fn limits_path() -> std::path::PathBuf {
crate::utility::settings_dir().join(crate::consts::LIMITS_FILE)
}

View file

@ -1,44 +1,5 @@
use crate::persist::{DriverJson, SettingsJson}; use crate::persist::{DriverJson, SettingsJson};
use super::{TGeneral, TCpus, TGpu, TBattery, SettingError, General}; use super::{TGeneral, TCpus, TGpu, TBattery, SettingError, General, auto_detect0};
/// 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
// TODO: this auto-detect doesn't work
// look for a file instead?
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 if let Some(_) = lscpu.find("model name\t: AMD Ryzen") {
DriverJson::Generic
} 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 struct Driver {
pub general: Box<dyn TGeneral>, pub general: Box<dyn TGeneral>,
@ -66,7 +27,7 @@ impl Driver {
} }
fn version0(settings: SettingsJson, json_path: std::path::PathBuf) -> Result<Self, SettingError> { fn version0(settings: SettingsJson, json_path: std::path::PathBuf) -> Result<Self, SettingError> {
let provider = settings.provider.unwrap_or_else(auto_detect); if let Some(provider) = &settings.provider {
match provider { match provider {
DriverJson::SteamDeck => Ok(Self { DriverJson::SteamDeck => Ok(Self {
general: Box::new(General { general: Box::new(General {
@ -101,75 +62,23 @@ impl Driver {
gpu: Box::new(super::generic::Gpu::from_json(settings.gpu, settings.version)), gpu: Box::new(super::generic::Gpu::from_json(settings.gpu, settings.version)),
battery: Box::new(super::generic::Battery), battery: Box::new(super::generic::Battery),
}), }),
DriverJson::Unknown => Ok(Self { DriverJson::Unknown => Ok(super::detect::auto_detect0(Some(settings), json_path)),
general: Box::new(General { DriverJson::AutoDetect => Ok(super::detect::auto_detect0(Some(settings), json_path)),
persistent: settings.persistent, }
path: json_path, } else {
name: settings.name, Ok(super::detect::auto_detect0(Some(settings), json_path))
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 { pub fn system_default(json_path: std::path::PathBuf) -> Self {
let provider = auto_detect(); auto_detect0(None, json_path)
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::Battery::system_default()),
},
DriverJson::Generic => Self {
general: Box::new(General {
persistent: false,
path: json_path,
name: crate::consts::DEFAULT_SETTINGS_NAME.to_owned(),
driver: DriverJson::Unknown,
}),
cpus: Box::new(super::generic::Cpus::system_default()),
gpu: Box::new(super::generic::Gpu::system_default()),
battery: Box::new(super::generic::Battery),
},
DriverJson::Unknown => Self {
general: Box::new(General {
persistent: false,
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),
}
}
} }
} }
// sshhhh, this function isn't here ;) // sshhhh, this function isn't here ;)
#[inline] #[inline]
pub fn maybe_do_button() { pub fn maybe_do_button() {
match auto_detect() { match super::auto_detect_provider() {
DriverJson::SteamDeck | DriverJson::SteamDeckAdvance => { DriverJson::SteamDeck | DriverJson::SteamDeckAdvance => {
let period = std::time::Duration::from_millis(500); let period = std::time::Duration::from_millis(500);
for _ in 0..10 { for _ in 0..10 {
@ -185,5 +94,6 @@ pub fn maybe_do_button() {
}, },
DriverJson::Generic => log::warn!("You need to come up with something fun on generic"), DriverJson::Generic => log::warn!("You need to come up with something fun on generic"),
DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"), DriverJson::Unknown => log::warn!("Can't do button activities on unknown platform"),
DriverJson::AutoDetect => log::warn!("WTF, why is auto_detect detecting AutoDetect???")
} }
} }

View file

@ -38,6 +38,16 @@ impl Battery {
Ok(val) => Ok(val / 1000.0), Ok(val) => Ok(val / 1000.0),
} }
} }
pub fn from_limits(_limits: limits_core::json::GenericBatteryLimit) -> Self {
// TODO
Self
}
pub fn from_json_and_limits(_other: BatteryJson, _version: u64, _limits: limits_core::json::GenericBatteryLimit) -> Self {
// TODO
Self
}
} }
impl OnSet for Battery { impl OnSet for Battery {

View file

@ -128,6 +128,43 @@ impl Cpus {
smt_capable: can_smt, smt_capable: can_smt,
} }
} }
pub fn from_limits(_limits: limits_core::json::GenericCpuLimit) -> Self {
// TODO
Self {
cpus: vec![],
smt: false,
smt_capable: false,
}
}
pub fn from_json_and_limits(mut other: Vec<CpuJson>, version: u64, _limits: limits_core::json::GenericCpuLimit) -> 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 { impl TCpus for Cpus {

View file

@ -18,7 +18,20 @@ impl Gpu {
} }
} }
pub fn system_default() -> Self { /*pub fn system_default() -> Self {
Self {
slow_memory: false,
}
}*/
pub fn from_limits(_limits: limits_core::json::GenericGpuLimit) -> Self {
// TODO
Self {
slow_memory: false,
}
}
pub fn from_json_and_limits(_other: GpuJson, _version: u64, _limits: limits_core::json::GenericGpuLimit) -> Self {
Self { Self {
slow_memory: false, slow_memory: false,
} }

View file

@ -1,3 +1,4 @@
mod detect;
pub mod driver; pub mod driver;
mod error; mod error;
mod general; mod general;
@ -9,6 +10,7 @@ pub mod steam_deck;
pub mod steam_deck_adv; pub mod steam_deck_adv;
pub mod unknown; pub mod unknown;
pub use detect::{auto_detect0, auto_detect_provider, limits_worker::spawn as limits_worker_spawn};
pub use driver::Driver; pub use driver::Driver;
pub use general::{SettingVariant, Settings, General}; pub use general::{SettingVariant, Settings, General};
pub use min_max::MinMax; pub use min_max::MinMax;