Define complete Rust back-end API and functionality

This commit is contained in:
NGnius (Graham) 2022-08-09 20:56:22 -04:00
parent 20ce2f1d5f
commit c225554f78
30 changed files with 1523 additions and 89 deletions

1
.gitignore vendored
View file

@ -42,3 +42,4 @@ yalc.lock
# rust
/powertools-rs/target
/bin

113
powertools-rs/Cargo.lock generated
View file

@ -2,6 +2,42 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "aes-gcm-siv"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"polyval",
"subtle",
"zeroize",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -66,6 +102,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]]
name = "cpufeatures"
version = "0.2.2"
@ -85,6 +130,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -251,6 +305,12 @@ dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "0.2.8"
@ -437,6 +497,12 @@ dependencies = [
"libc",
]
[[package]]
name = "obfstr"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2b2cbbfd8defa51ff24450a61d73b3ff3e158484ddd274a883e886e6fbaa78"
[[package]]
name = "once_cell"
version = "1.13.0"
@ -487,9 +553,21 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "powertools-rs"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"log",
"serde",
@ -691,6 +769,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.98"
@ -945,6 +1029,16 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "url"
version = "2.2.2"
@ -959,11 +1053,13 @@ dependencies = [
[[package]]
name = "usdpl-back"
version = "0.5.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d237439986405621b9b6da350aefcfca9e2b808c10695f55f8b80ccc324b2c0"
checksum = "cbbc0781e83ba990f8239142e33173a2d2548701775f3db66702d1af4fd0319a"
dependencies = [
"bytes",
"hex",
"obfstr",
"tokio",
"usdpl-core",
"warp",
@ -971,10 +1067,11 @@ dependencies = [
[[package]]
name = "usdpl-core"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd726b9f0121d4449082e3ce73586dea0a0448494031833b7b173e4476f0ea5"
checksum = "862153581fac266458521f49e5906a71c1eee1665cb4c7d71e9586bd34b45394"
dependencies = [
"aes-gcm-siv",
"base64",
]
@ -1109,3 +1206,9 @@ name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "zeroize"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"

View file

@ -1,15 +1,21 @@
[package]
name = "powertools-rs"
version = "0.1.0"
version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
usdpl-back = { version = "0.5.3", features = ["blocking"]}
usdpl-back = { version = "0.6.0", features = ["blocking"]}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# logging
log = "0.4"
simplelog = "0.12"
[features]
default = []
decky = ["usdpl-back/decky"]
crankshaft = ["usdpl-back/crankshaft"]
encrypt = ["usdpl-back/encrypt"]

View file

@ -1,6 +1,5 @@
#!/bin/bash
cargo build --release
mkdir ../bin
# TODO replace "backend" \/ with binary name
cp ./target/release/backend ../bin/backend
cargo build --release --target x86_64-unknown-linux-musl
mkdir ../bin &> /dev/null
cp ./target/release/powertools-rs ../bin/backend

View file

@ -0,0 +1,64 @@
use std::sync::{mpsc::Sender, Arc, Mutex};
use usdpl_back::core::serdes::Primitive;
use crate::settings::{Battery, OnSet};
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
/// Current current (ha!) web method
pub fn current_now(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(crate::settings::Battery::current_now())
}
/// Generate set battery charge rate web method
pub fn set_charge_rate(
settings: Arc<Mutex<Battery>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(new_val)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "battery");
settings_lock.charge_rate = Some(*new_val as _);
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
super::utility::map_empty_result(
settings_lock.on_set(),
settings_lock.charge_rate.unwrap(),
)
} else {
vec!["set_charge_rate missing parameter".into()]
}
}
}
/// Generate get battery charge rate web method
pub fn get_charge_rate(
settings: Arc<Mutex<Battery>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "battery");
vec![settings_lock
.charge_rate
.map(|x| x.into())
.unwrap_or(Primitive::Empty)]
}
}
/// Generate unset battery charge rate web method
pub fn unset_charge_rate(
settings: Arc<Mutex<Battery>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |_: super::ApiParameterType| {
let mut settings_lock = unwrap_lock(settings.lock(), "battery");
settings_lock.charge_rate = None;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
vec![]
}
}

View file

@ -0,0 +1,194 @@
use std::sync::{mpsc::Sender, Arc, Mutex};
use usdpl_back::core::serdes::Primitive;
use crate::settings::{Cpu, OnSet, SettingError, SettingVariant, MinMax};
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
/// Available CPUs web method
pub fn max_cpus(_: super::ApiParameterType) -> super::ApiParameterType {
super::utility::map_result(
Cpu::cpu_count()
.map(|x| x as u64)
.ok_or_else(
|| SettingError {
msg: "Failed to parse CPU count".to_owned(),
setting: SettingVariant::Cpu,
}
)
)
}
/// Generate set CPU online web method
pub fn set_cpu_online(
settings: Arc<Mutex<Vec<Cpu>>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(index)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
if let Some(Primitive::Bool(online)) = params_in.get(1) {
cpu.online = *online;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
super::utility::map_empty_result(
cpu.on_set(),
cpu.online,
)
} else {
vec!["set_cpu_online missing parameter 1".into()]
}
} else {
vec!["set_cpu_online cpu index out of bounds".into()]
}
} else {
vec!["set_cpu_online missing parameter 0".into()]
}
}
}
pub fn get_cpus_online(
settings: Arc<Mutex<Vec<Cpu>>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "cpu");
let mut output = Vec::with_capacity(settings_lock.len());
for cpu in settings_lock.as_slice() {
output.push(cpu.online.into());
}
output
}
}
pub fn set_clock_limits(
settings: Arc<Mutex<Vec<Cpu>>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(index)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
if let Some(Primitive::F64(min)) = params_in.get(1) {
if let Some(Primitive::F64(max)) = params_in.get(2) {
cpu.clock_limits = Some(MinMax {
min: *min as _,
max: *max as _,
});
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
match cpu.on_set() {
Ok(_) => vec![
cpu.clock_limits.as_ref().unwrap().min.into(),
cpu.clock_limits.as_ref().unwrap().max.into(),
],
Err(e) => vec![e.msg.into()]
}
} else {
vec!["set_clock_limits missing parameter 2".into()]
}
} else {
vec!["set_clock_limits missing parameter 1".into()]
}
} else {
vec!["set_clock_limits cpu index out of bounds".into()]
}
} else {
vec!["set_clock_limits missing parameter 0".into()]
}
}
}
pub fn get_clock_limits(
settings: Arc<Mutex<Vec<Cpu>>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(index)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
if let Some(min_max) = &cpu.clock_limits {
vec![min_max.max.into(), min_max.min.into()]
} else {
vec![Primitive::Empty, Primitive::Empty]
}
} else {
vec!["get_clock_limits cpu index out of bounds".into()]
}
} else {
vec!["get_clock_limits missing parameter 0".into()]
}
}
}
pub fn unset_clock_limits(
settings: Arc<Mutex<Vec<Cpu>>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(index)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
cpu.clock_limits = None;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
vec![]
} else {
vec!["get_clock_limits cpu index out of bounds".into()]
}
} else {
vec!["get_clock_limits missing parameter 0".into()]
}
}
}
pub fn set_cpu_governor(
settings: Arc<Mutex<Vec<Cpu>>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(index)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "cpu");
if let Some(cpu) = settings_lock.get_mut(*index as usize) {
if let Some(Primitive::String(governor)) = params_in.get(1) {
cpu.governor = governor.to_owned();
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
super::utility::map_empty_result(
cpu.on_set(),
&cpu.governor as &str,
)
} else {
vec!["set_cpu_governor missing parameter 1".into()]
}
} else {
vec!["set_cpu_governor cpu index out of bounds".into()]
}
} else {
vec!["set_cpu_governor missing parameter 0".into()]
}
}
}
pub fn get_cpu_governors(
settings: Arc<Mutex<Vec<Cpu>>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "cpu");
let mut output = Vec::with_capacity(settings_lock.len());
for cpu in settings_lock.as_slice() {
output.push(cpu.governor.clone().into());
}
output
}
}

View file

@ -0,0 +1,161 @@
use std::sync::{mpsc::Sender, Arc, Mutex};
use usdpl_back::core::serdes::Primitive;
use crate::settings::{Gpu, OnSet, MinMax};
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
pub fn set_ppt(
settings: Arc<Mutex<Gpu>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(fast_ppt)) = params_in.get(0) {
if let Some(Primitive::F64(slow_ppt)) = params_in.get(1) {
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
settings_lock.fast_ppt = Some(*fast_ppt as u64);
settings_lock.slow_ppt = Some(*slow_ppt as u64);
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
match settings_lock.on_set() {
Ok(_) => vec![
settings_lock.fast_ppt.unwrap().into(),
settings_lock.slow_ppt.unwrap().into()
],
Err(e) => vec![e.msg.into()],
}
} else {
vec!["set_ppt missing parameter 1".into()]
}
} else {
vec!["set_ppt missing parameter 0".into()]
}
}
}
pub fn get_ppt(
settings: Arc<Mutex<Gpu>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "gpu");
let fast_ppt = settings_lock.fast_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty);
let slow_ppt = settings_lock.slow_ppt.map(|x| x.into()).unwrap_or(Primitive::Empty);
vec![fast_ppt, slow_ppt]
}
}
pub fn unset_ppt(
settings: Arc<Mutex<Gpu>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |_: super::ApiParameterType| {
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
settings_lock.fast_ppt = None;
settings_lock.slow_ppt = None;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
super::utility::map_empty_result(
settings_lock.on_set(),
Primitive::Empty,
)
}
}
pub fn set_clock_limits(
settings: Arc<Mutex<Gpu>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::F64(min)) = params_in.get(0) {
if let Some(Primitive::F64(max)) = params_in.get(1) {
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
settings_lock.clock_limits = Some(MinMax {
min: *min as _,
max: *max as _,
});
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
match settings_lock.on_set() {
Ok(_) => vec![
settings_lock.clock_limits.as_ref().unwrap().min.into(),
settings_lock.clock_limits.as_ref().unwrap().max.into(),
],
Err(e) => vec![e.msg.into()]
}
} else {
vec!["set_clock_limits missing parameter 1".into()]
}
} else {
vec!["set_clock_limits missing parameter 0".into()]
}
}
}
pub fn get_clock_limits(
settings: Arc<Mutex<Gpu>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "gpu");
if let Some(min_max) = &settings_lock.clock_limits {
vec![min_max.max.into(), min_max.min.into()]
} else {
vec![Primitive::Empty, Primitive::Empty]
}
}
}
pub fn unset_clock_limits(
settings: Arc<Mutex<Gpu>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |_: super::ApiParameterType| {
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
settings_lock.clock_limits = None;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
vec![]
}
}
pub fn set_slow_memory(
settings: Arc<Mutex<Gpu>>,
saver: Sender<()>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
let saver = Mutex::new(saver); // Sender is not Sync; this is required for safety
move |params_in: super::ApiParameterType| {
if let Some(Primitive::Bool(memory_is_slow)) = params_in.get(0) {
let mut settings_lock = unwrap_lock(settings.lock(), "gpu");
settings_lock.slow_memory = *memory_is_slow;
unwrap_maybe_fatal(
unwrap_lock(saver.lock(), "save channel").send(()),
"Failed to send on save channel",
);
super::utility::map_empty_result(
settings_lock.on_set(),
settings_lock.slow_memory,
)
} else {
vec!["set_slow_memory missing parameter 0".into()]
}
}
}
pub fn get_slow_memory(
settings: Arc<Mutex<Gpu>>,
) -> impl Fn(super::ApiParameterType) -> super::ApiParameterType {
move |_: super::ApiParameterType| {
let settings_lock = unwrap_lock(settings.lock(), "cpu");
vec![settings_lock.slow_memory.into()]
}
}

View file

@ -0,0 +1,6 @@
pub mod battery;
pub mod cpu;
pub mod gpu;
mod utility;
pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>;

View file

@ -0,0 +1,21 @@
use std::convert::Into;
use usdpl_back::core::serdes::Primitive;
use crate::settings::SettingError;
pub fn map_result<T: Into<Primitive>>(result: Result<T, SettingError>) -> super::ApiParameterType {
match result {
Ok(val) => vec![val.into()],
Err(e) => vec![e.msg.into()],
}
}
pub fn map_empty_result<T: Into<Primitive>>(
result: Result<(), SettingError>,
success: T,
) -> super::ApiParameterType {
match result {
Ok(_) => vec![success.into()],
Err(e) => vec![e.msg.into()],
}
}

View file

@ -1,35 +1,132 @@
mod api;
mod persist;
mod settings;
mod state;
use simplelog::{WriteLogger, LevelFilter};
mod resume_worker;
mod save_worker;
mod utility;
use simplelog::{LevelFilter, WriteLogger};
use usdpl_back::Instance;
use usdpl_back::core::serdes::Primitive;
use usdpl_back::Instance;
const PORT: u16 = 44443;
const PACKAGE_NAME: &'static str = env!("CARGO_PKG_NAME");
const PACKAGE_VERSION: &'static str = env!("CARGO_PKG_VERSION");
const DEFAULT_SETTINGS_FILE: &str = "default_settings.json";
fn main() -> Result<(), ()> {
let log_filepath = format!("/tmp/{}.log", PACKAGE_NAME);
WriteLogger::init(
#[cfg(debug_assertions)]{LevelFilter::Debug},
#[cfg(not(debug_assertions))]{LevelFilter::Info},
#[cfg(debug_assertions)]
{
LevelFilter::Debug
},
#[cfg(not(debug_assertions))]
{
LevelFilter::Info
},
Default::default(),
std::fs::File::create(&log_filepath).unwrap()
).unwrap();
std::fs::File::create(&log_filepath).unwrap(),
)
.unwrap();
log::info!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
println!("Starting back-end ({} v{})", PACKAGE_NAME, PACKAGE_VERSION);
let default_settings: settings::Settings = persist::SettingsJson::open(
settings_dir().join("default_settings.json")
).unwrap_or_default().into();
let default_settings = persist::SettingsJson::open(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()));
log::debug!("Settings: {:?}", default_settings);
let (_save_handle, save_sender) = save_worker::spawn(default_settings.clone());
let _resume_handle = resume_worker::spawn(default_settings.clone());
Instance::new(PORT)
.register("hello", |_: Vec<Primitive>| vec![format!("Hello {}", PACKAGE_NAME).into()])
.register("hello", |_: Vec<Primitive>| {
vec![format!("Hello {}", PACKAGE_NAME).into()]
})
// battery API functions
.register("BATTERY_current_now", api::battery::current_now)
.register(
"BATTERY_set_charge_rate",
api::battery::set_charge_rate(default_settings.battery.clone(), save_sender.clone()),
)
.register(
"BATTERY_get_charge_rate",
api::battery::get_charge_rate(default_settings.battery.clone()),
)
.register(
"BATTERY_unset_charge_rate",
api::battery::unset_charge_rate(default_settings.battery.clone(), save_sender.clone()),
)
// cpu API functions
.register("CPU_count", api::cpu::max_cpus)
.register(
"CPU_set_online",
api::cpu::set_cpu_online(default_settings.cpus.clone(), save_sender.clone())
)
.register(
"CPU_get_online",
api::cpu::get_cpus_online(default_settings.cpus.clone())
)
.register(
"CPU_set_clock_limits",
api::cpu::set_clock_limits(default_settings.cpus.clone(), save_sender.clone())
)
.register(
"CPU_get_clock_limits",
api::cpu::get_clock_limits(default_settings.cpus.clone())
)
.register(
"CPU_unset_clock_limits",
api::cpu::unset_clock_limits(default_settings.cpus.clone(), save_sender.clone())
)
.register(
"CPU_set_governor",
api::cpu::set_cpu_governor(default_settings.cpus.clone(), save_sender.clone())
)
.register(
"CPU_get_governors",
api::cpu::get_cpu_governors(default_settings.cpus.clone())
)
// gpu API functions
.register(
"GPU_set_ppt",
api::gpu::set_ppt(default_settings.gpu.clone(), save_sender.clone())
)
.register(
"GPU_get_ppt",
api::gpu::get_ppt(default_settings.gpu.clone())
)
.register(
"GPU_unset_ppt",
api::gpu::unset_ppt(default_settings.gpu.clone(), save_sender.clone())
)
.register(
"GPU_set_clock_limits",
api::gpu::set_clock_limits(default_settings.gpu.clone(), save_sender.clone())
)
.register(
"GPU_get_clock_limits",
api::gpu::get_clock_limits(default_settings.gpu.clone())
)
.register(
"GPU_unset_clock_limits",
api::gpu::unset_clock_limits(default_settings.gpu.clone(), save_sender.clone())
)
.register(
"GPU_set_slow_memory",
api::gpu::set_slow_memory(default_settings.gpu.clone(), save_sender.clone())
)
.register(
"GPU_get_slow_memory",
api::gpu::get_slow_memory(default_settings.gpu.clone())
)
.run_blocking()
}

View file

@ -0,0 +1,15 @@
use std::default::Default;
//use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct BatteryJson {
pub charge_rate: Option<u64>,
}
impl Default for BatteryJson {
fn default() -> Self {
Self { charge_rate: None }
}
}

View file

@ -1,27 +1,25 @@
use std::default::Default;
//use std::fmt::Display;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000];
use super::MinMaxJson;
//const SCALING_FREQUENCIES: &[u64] = &[1700000, 2400000, 2800000];
#[derive(Serialize, Deserialize)]
pub struct CpuJson {
pub online: bool,
pub max_boost: u64,
pub min_boost: u64,
pub clock_limits: Option<MinMaxJson<u64>>,
pub governor: String,
pub boost: bool,
}
impl Default for CpuJson {
fn default() -> Self {
Self {
online: true,
max_boost: SCALING_FREQUENCIES[SCALING_FREQUENCIES.len() - 1],
min_boost: SCALING_FREQUENCIES[0],
clock_limits: None,
governor: "schedutil".to_owned(),
boost: true,
}
}
}

View file

@ -0,0 +1,14 @@
#[derive(Debug)]
pub enum JsonError {
Serde(serde_json::Error),
Io(std::io::Error),
}
impl std::fmt::Display for JsonError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Serde(e) => (e as &dyn std::fmt::Display).fmt(f),
Self::Io(e) => (e as &dyn std::fmt::Display).fmt(f),
}
}
}

View file

@ -1,25 +1,29 @@
use std::default::Default;
use std::fmt::Display;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use super::{CpuJson, GpuJson};
use super::JsonError;
use super::{BatteryJson, CpuJson, GpuJson};
#[derive(Serialize, Deserialize)]
pub struct SettingsJson {
pub version: u64,
pub name: String,
pub persistent: bool,
pub cpus: Vec<CpuJson>,
pub gpu: GpuJson,
pub battery: BatteryJson,
}
impl Default for SettingsJson {
fn default() -> Self {
Self {
version: 0,
name: "default".to_owned(),
persistent: false,
cpus: Vec::with_capacity(8),
gpu: GpuJson::default(),
battery: BatteryJson::default(),
}
}
}
@ -40,17 +44,8 @@ impl SettingsJson {
}
}
#[derive(Debug)]
pub enum JsonError {
Serde(serde_json::Error),
Io(std::io::Error),
}
impl Display for JsonError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Serde(e) => (e as &dyn Display).fmt(f),
Self::Io(e) => (e as &dyn Display).fmt(f),
}
}
#[derive(Serialize, Deserialize)]
pub struct MinMaxJson<T> {
pub max: T,
pub min: T,
}

View file

@ -1,12 +1,15 @@
use std::default::Default;
//use std::fmt::Display;
use serde::{Serialize, Deserialize};
use super::MinMaxJson;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct GpuJson {
pub fast_ppt: Option<u64>,
pub slow_ppt: Option<u64>,
pub clock_limits: Option<MinMaxJson<u64>>,
pub slow_memory: bool,
}
impl Default for GpuJson {
@ -14,6 +17,8 @@ impl Default for GpuJson {
Self {
fast_ppt: None,
slow_ppt: None,
clock_limits: None,
slow_memory: false,
}
}
}

View file

@ -1,7 +1,12 @@
mod battery;
mod cpu;
mod error;
mod general;
mod gpu;
pub use battery::BatteryJson;
pub use cpu::CpuJson;
pub use general::SettingsJson;
pub use general::{MinMaxJson, SettingsJson};
pub use gpu::GpuJson;
pub use error::JsonError;

View file

@ -0,0 +1,27 @@
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use crate::settings::{OnResume, Settings};
use crate::utility::unwrap_maybe_fatal;
const ALLOWED_ERROR: f64 = 0.001;
pub fn spawn(settings: Settings) -> JoinHandle<()> {
thread::spawn(move || {
let duration = Duration::from_millis(5000);
let mut start = Instant::now();
loop {
thread::sleep(duration);
let old_start = start.elapsed();
start = Instant::now();
if old_start.as_secs_f64() > duration.as_secs_f64() * (1.0 + ALLOWED_ERROR) {
// has just resumed from sleep
unwrap_maybe_fatal(settings.on_resume(), "On resume failure");
log::info!(
"OnResume completed after sleeping for {}s",
old_start.as_secs_f32()
);
}
}
})
}

View file

@ -0,0 +1,19 @@
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread::{self, JoinHandle};
use crate::persist::SettingsJson;
use crate::settings::Settings;
use crate::utility::{unwrap_lock, unwrap_maybe_fatal};
pub fn spawn(settings: Settings) -> (JoinHandle<()>, Sender<()>) {
let (sender, receiver): (Sender<()>, Receiver<()>) = mpsc::channel();
let worker = thread::spawn(move || {
for _ in receiver.iter() {
let save_path = unwrap_lock(settings.general.lock(), "general").path.clone();
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());
}
});
(worker, sender)
}

View file

@ -0,0 +1,109 @@
use std::convert::Into;
use super::{OnResume, OnSet, SettingError, SettingsRange};
use crate::persist::BatteryJson;
#[derive(Debug, Clone)]
pub struct Battery {
pub charge_rate: Option<u64>,
}
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
impl Battery {
#[inline]
pub fn from_json(other: BatteryJson, version: u64) -> Self {
match version {
0 => Self {
charge_rate: other.charge_rate,
},
_ => Self {
charge_rate: other.charge_rate,
},
}
}
fn set_all(&self) -> Result<(), SettingError> {
if let Some(charge_rate) = self.charge_rate {
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,
},
)
} 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 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: super::SettingVariant::Battery,
}),
Err((None, Some(e))) => Err(SettingError {
msg: format!("Failed to read from `{}`: {}", BATTERY_CURRENT_NOW_PATH, e),
setting: super::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 system_default() -> Self {
Self { charge_rate: None }
}
}
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.set_all()
}
}
impl SettingsRange for Battery {
#[inline]
fn max() -> Self {
Self {
charge_rate: Some(2500),
}
}
#[inline]
fn min() -> Self {
Self {
charge_rate: Some(250),
}
}
}

View file

@ -1,34 +1,162 @@
use std::convert::Into;
use super::MinMax;
use super::{OnResume, OnSet, SettingError, SettingsRange};
use crate::persist::CpuJson;
#[derive(Debug, Clone)]
pub struct Cpu {
pub online: bool,
pub max_boost: u64,
pub min_boost: u64,
pub clock_limits: Option<MinMax<u64>>,
pub governor: String,
pub boost: bool,
index: usize,
}
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";
const CPU_PRESENT_PATH: &str = "/sys/devices/system/cpu/present";
impl Cpu {
#[inline]
pub fn from_json(other: CpuJson, version: u64) -> Self {
pub fn from_json(other: CpuJson, version: u64, i: usize) -> Self {
match version {
0 => Self {
online: other.online,
max_boost: other.max_boost,
min_boost: other.min_boost,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor,
boost: other.boost,
index: i,
},
_ => Self {
online: other.online,
max_boost: other.max_boost,
min_boost: other.min_boost,
clock_limits: other.clock_limits.map(|x| MinMax::from_json(x, version)),
governor: other.governor,
boost: other.boost,
index: i,
},
}
}
fn set_all(&self) -> Result<(), SettingError> {
// set cpu online/offline
if self.index != 0 { // 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: super::SettingVariant::Cpu,
}
})?;
}
// set clock limits
if let Some(clock_limits) = &self.clock_limits {
// 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: super::SettingVariant::Cpu,
}
})?;
// max clock
let payload_max = format!("p {} 1 {}", 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: super::SettingVariant::Cpu,
},
)?;
// min clock
let payload_min = format!("p {} 0 {}", 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: super::SettingVariant::Cpu,
},
)?;
// commit changes
usdpl_back::api::files::write_single(CPU_CLOCK_LIMITS_PATH, "c").map_err(|e| {
SettingError {
msg: format!("Failed to write `c` to `{}`: {}", CPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Cpu,
}
})?;
} else {
// disable manual clock limits
usdpl_back::api::files::write_single(CPU_FORCE_LIMITS_PATH, "auto").map_err(|e| {
SettingError {
msg: format!(
"Failed to write `auto` to `{}`: {}",
CPU_FORCE_LIMITS_PATH, e
),
setting: super::SettingVariant::Cpu,
}
})?;
}
// set governor
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: super::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(index: usize) -> Self {
Self {
online: usdpl_back::api::files::read_single(cpu_online_path(index)).unwrap_or(1u8) != 0,
clock_limits: None,
governor: usdpl_back::api::files::read_single(cpu_governor_path(index))
.unwrap_or("schedutil".to_owned()),
index: index,
}
}
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);
}
}
log::warn!("Failed to parse CPU info from kernel, is Tux evil?");
None
}
pub fn system_default() -> Vec<Self> {
if let Some(max_cpu) = Self::cpu_count() {
let mut cpus = Vec::with_capacity(max_cpu + 1);
for i in 0..=max_cpu {
cpus.push(Self::from_sys(i));
}
cpus
} else {
Vec::with_capacity(0)
}
}
}
@ -38,10 +166,59 @@ impl Into<CpuJson> for Cpu {
fn into(self) -> CpuJson {
CpuJson {
online: self.online,
max_boost: self.max_boost,
min_boost: self.min_boost,
clock_limits: self.clock_limits.map(|x| x.into()),
governor: self.governor,
boost: self.boost,
}
}
}
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> {
self.set_all()
}
}
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,
}
}
#[inline]
fn min() -> Self {
Self {
online: false,
clock_limits: Some(MinMax { max: 500, min: 1400 }),
governor: "schedutil".to_owned(),
index: 0,
}
}
}
#[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,13 @@
use super::SettingVariant;
#[derive(Debug, Clone)]
pub struct SettingError {
pub msg: String,
pub setting: SettingVariant,
}
impl std::fmt::Display for SettingError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} setting error: {}", self.setting, self.msg)
}
}

View file

@ -1,43 +1,150 @@
use std::convert::{Into, From};
use std::convert::Into;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::persist::SettingsJson;
use super::{Cpu, Gpu};
use super::{Battery, Cpu, Gpu};
use super::{OnResume, OnSet, SettingError};
use crate::persist::{CpuJson, SettingsJson};
use crate::utility::unwrap_lock;
const LATEST_VERSION: u64 = 0;
#[derive(Debug, Clone)]
pub struct Settings {
pub persistent: bool,
pub cpus: Vec<Cpu>,
pub gpu: Gpu,
#[derive(Debug, Clone, Copy)]
pub enum SettingVariant {
Battery,
Cpu,
Gpu,
General,
}
impl From<SettingsJson> for Settings {
#[inline]
fn from(mut other: SettingsJson) -> Self {
match other.version {
0 => Self {
persistent: other.persistent,
cpus: other.cpus.drain(..).map(|cpu| Cpu::from_json(cpu, other.version)).collect(),
gpu: Gpu::from_json(other.gpu, other.version),
},
_ => Self {
persistent: other.persistent,
cpus: other.cpus.drain(..).map(|cpu| Cpu::from_json(cpu, other.version)).collect(),
gpu: Gpu::from_json(other.gpu, other.version),
impl std::fmt::Display for SettingVariant {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Battery => write!(f, "Battery"),
Self::Cpu => write!(f, "CPU"),
Self::Gpu => write!(f, "GPU"),
Self::General => write!(f, "General"),
}
}
}
#[derive(Debug, Clone)]
pub struct General {
pub persistent: bool,
pub path: PathBuf,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct Settings {
pub general: Arc<Mutex<General>>,
pub cpus: Arc<Mutex<Vec<Cpu>>>,
pub gpu: Arc<Mutex<Gpu>>,
pub battery: Arc<Mutex<Battery>>,
}
impl OnSet for Settings {
fn on_set(&mut self) -> Result<(), SettingError> {
unwrap_lock(self.battery.lock(), "battery").on_set()?;
{
// cpu lock scope
let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
for cpu in cpu_lock.iter_mut() {
cpu.on_set()?;
}
}
unwrap_lock(self.gpu.lock(), "gpu").on_set()?;
{
// general lock scope
let gen_lock = unwrap_lock(self.general.lock(), "general");
if !gen_lock.persistent && gen_lock.path.exists() {
std::fs::remove_file(&gen_lock.path).map_err(|e| SettingError {
msg: format!("Failed to delete `{}`: {}", gen_lock.path.display(), e),
setting: SettingVariant::General,
})?;
}
}
Ok(())
}
}
impl Settings {
#[inline]
pub fn from_json(other: SettingsJson, json_path: PathBuf) -> Self {
match other.version {
0 => Self {
general: Arc::new(Mutex::new(General {
persistent: other.persistent,
path: json_path,
name: other.name,
})),
cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))),
gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))),
battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))),
},
_ => Self {
general: Arc::new(Mutex::new(General {
persistent: other.persistent,
path: json_path,
name: other.name,
})),
cpus: Arc::new(Mutex::new(Self::convert_cpus(other.cpus, other.version))),
gpu: Arc::new(Mutex::new(Gpu::from_json(other.gpu, other.version))),
battery: Arc::new(Mutex::new(Battery::from_json(other.battery, other.version))),
},
}
}
fn convert_cpus(mut cpus: Vec<CpuJson>, version: u64) -> Vec<Cpu> {
let mut result = Vec::with_capacity(cpus.len());
for (i, cpu) in cpus.drain(..).enumerate() {
result.push(Cpu::from_json(cpu, version, i));
}
result
}
pub fn system_default(json_path: PathBuf) -> Self {
Self {
general: Arc::new(Mutex::new(General {
persistent: false,
path: json_path,
name: "".to_owned(),
})),
cpus: Arc::new(Mutex::new(Cpu::system_default())),
gpu: Arc::new(Mutex::new(Gpu::system_default())),
battery: Arc::new(Mutex::new(Battery::system_default())),
}
}
}
impl OnResume for Settings {
fn on_resume(&self) -> Result<(), SettingError> {
unwrap_lock(self.battery.lock(), "battery").on_resume()?;
{
let mut cpu_lock = unwrap_lock(self.cpus.lock(), "cpu");
for cpu in cpu_lock.iter_mut() {
cpu.on_resume()?;
}
}
unwrap_lock(self.gpu.lock(), "gpu").on_resume()?;
Ok(())
}
}
impl Into<SettingsJson> for Settings {
#[inline]
fn into(mut self) -> SettingsJson {
fn into(self) -> SettingsJson {
SettingsJson {
version: LATEST_VERSION,
persistent: self.persistent,
cpus: self.cpus.drain(..).map(|cpu| cpu.into()).collect(),
gpu: self.gpu.into()
name: unwrap_lock(self.general.lock(), "general").name.clone(),
persistent: unwrap_lock(self.general.lock(), "general").persistent,
cpus: unwrap_lock(self.cpus.lock(), "cpu")
.clone()
.drain(..)
.map(|cpu| cpu.into())
.collect(),
gpu: unwrap_lock(self.gpu.lock(), "gpu").clone().into(),
battery: unwrap_lock(self.battery.lock(), "battery").clone().into(),
}
}
}

View file

@ -1,13 +1,25 @@
use std::convert::Into;
use super::MinMax;
use super::{OnResume, OnSet, SettingError, SettingsRange};
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,
}
// 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 {
@ -15,11 +27,140 @@ impl Gpu {
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,
},
_ => 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,
},
}
}
fn set_all(&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: super::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: super::SettingVariant::Gpu,
}
})?;
}
// settings using force_performance_level
if self.clock_limits.is_some() || self.slow_memory {
// 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: super::SettingVariant::Gpu,
}
})?;
// set clock limits
if let Some(clock_limits) = &self.clock_limits {
// max clock
let payload_max = format!("s 1 {}", 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: super::SettingVariant::Gpu,
},
)?;
// min clock
let payload_min = format!("s 0 {}", 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: super::SettingVariant::Gpu,
},
)?;
}
// force 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: super::SettingVariant::Gpu,
})?;
// commit changes
usdpl_back::api::files::write_single(GPU_CLOCK_LIMITS_PATH, "c").map_err(|e| {
SettingError {
msg: format!("Failed to write `c` to `{}`: {}", GPU_CLOCK_LIMITS_PATH, e),
setting: super::SettingVariant::Gpu,
}
})?;
} else {
// disable manual clock limits
usdpl_back::api::files::write_single(GPU_FORCE_LIMITS_PATH, "auto").map_err(|e| {
SettingError {
msg: format!(
"Failed to write `auto` to `{}`: {}",
GPU_FORCE_LIMITS_PATH, e
),
setting: super::SettingVariant::Gpu,
}
})?;
}
Ok(()) // TODO
}
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,
}
}
}
@ -30,6 +171,51 @@ impl Into<GpuJson> for Gpu {
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> {
self.set_all()
}
}
impl SettingsRange for Gpu {
#[inline]
fn max() -> Self {
Self {
fast_ppt: Some(30000000),
slow_ppt: Some(29000000),
clock_limits: Some(MinMax {
min: 1600,
max: 1600,
}),
slow_memory: false,
}
}
#[inline]
fn min() -> Self {
Self {
fast_ppt: Some(0),
slow_ppt: Some(1000000),
clock_limits: Some(MinMax { min: 200, max: 200 }),
slow_memory: true,
}
}
}
#[inline]
fn gpu_power_path(power_number: u8) -> String {
format!("/sys/class/hwmon/hwmon4/power{}_cap", power_number)
}

View file

@ -0,0 +1,29 @@
use std::convert::Into;
use crate::persist::MinMaxJson;
#[derive(Debug, Clone)]
pub struct MinMax<T> {
pub max: T,
pub min: T,
}
impl<T> MinMax<T> {
#[inline]
pub fn from_json<X: Into<T>>(other: MinMaxJson<X>, _version: u64) -> Self {
Self {
max: other.max.into(),
min: other.min.into(),
}
}
}
impl<X: Into<Y>, Y> Into<MinMaxJson<Y>> for MinMax<X> {
#[inline]
fn into(self) -> MinMaxJson<Y> {
MinMaxJson {
max: self.max.into(),
min: self.min.into(),
}
}
}

View file

@ -1,7 +1,25 @@
mod battery;
mod cpu;
mod error;
mod general;
mod gpu;
mod min_max;
mod traits;
pub use battery::Battery;
pub use cpu::Cpu;
pub use general::Settings;
pub use general::{SettingVariant, Settings};
pub use gpu::Gpu;
pub use min_max::MinMax;
pub use error::SettingError;
pub use traits::{OnResume, OnSet, SettingsRange};
#[cfg(test)]
mod tests {
#[test]
fn system_defaults_test() {
let settings = super::Settings::system_default("idc".into());
println!("Loaded system settings: {:?}", settings);
}
}

View file

@ -0,0 +1,14 @@
use super::SettingError;
pub trait OnSet {
fn on_set(&mut self) -> Result<(), SettingError>;
}
pub trait OnResume {
fn on_resume(&self) -> Result<(), SettingError>;
}
pub trait SettingsRange {
fn max() -> Self;
fn min() -> Self;
}

View file

@ -0,0 +1,16 @@
use crate::settings::SettingVariant;
pub struct StateError {
pub msg: String,
pub setting: Option<SettingVariant>,
}
impl std::fmt::Display for StateError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(setting) = self.setting {
write!(f, "{} setting state error: {}", setting, self.msg)
} else {
write!(f, "State error: {}", self.msg)
}
}
}

View file

@ -0,0 +1,5 @@
mod error;
mod traits;
pub use error::StateError;
pub use traits::OnPoll;

View file

@ -0,0 +1,5 @@
use super::StateError;
pub trait OnPoll {
fn on_poll(&self) -> Result<(), StateError>;
}

View file

@ -0,0 +1,25 @@
use std::fmt::Display;
use std::sync::{LockResult, MutexGuard};
pub fn unwrap_maybe_fatal<T: Sized, E: Display>(result: Result<T, E>, message: &str) -> T {
match result {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", message, e);
panic!("{}: {}", message, e);
}
}
}
pub fn unwrap_lock<'a, T: Sized>(
result: LockResult<MutexGuard<'a, T>>,
lock_name: &str,
) -> MutexGuard<'a, T> {
match result {
Ok(x) => x,
Err(e) => {
log::error!("Failed to acquire {} lock: {}", lock_name, e);
panic!("Failed to acquire {} lock: {}", lock_name, e);
}
}
}