mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
Cleanup unsafe sysfs interfaces. Bugfixes for UI
This commit is contained in:
439
rog-platform/src/asus_armoury.rs
Normal file
439
rog-platform/src/asus_armoury.rs
Normal file
@@ -0,0 +1,439 @@
|
||||
use std::fs::{read_dir, File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zbus::zvariant::{OwnedValue, Type, Value};
|
||||
|
||||
use crate::error::PlatformError;
|
||||
|
||||
/// The root sysfs path. This path should never change in kernel so
|
||||
/// using udev to find it *should* not be required.
|
||||
const BASE_DIR: &str = "/sys/class/firmware-attributes/asus-armoury/attributes/";
|
||||
|
||||
fn read_i32(path: &Path) -> Result<i32, PlatformError> {
|
||||
if let Ok(mut f) = File::open(path) {
|
||||
let mut buf = String::new();
|
||||
f.read_to_string(&mut buf)?;
|
||||
buf.trim()
|
||||
.parse::<i32>()
|
||||
.map_err(|_| PlatformError::ParseNum)
|
||||
} else {
|
||||
Err(PlatformError::ParseNum)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_string(path: &Path) -> Result<String, PlatformError> {
|
||||
let mut f = File::open(path)?;
|
||||
let mut buf = String::new();
|
||||
f.read_to_string(&mut buf)?;
|
||||
Ok(buf.trim().to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
|
||||
pub enum AttrValue {
|
||||
Integer(i32),
|
||||
String(String),
|
||||
EnumInt(Vec<i32>),
|
||||
EnumStr(Vec<String>),
|
||||
#[default]
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Attribute {
|
||||
name: String,
|
||||
help: String,
|
||||
default_value: AttrValue,
|
||||
possible_values: AttrValue,
|
||||
min_value: AttrValue,
|
||||
max_value: AttrValue,
|
||||
scalar_increment: AttrValue,
|
||||
base_path: PathBuf
|
||||
}
|
||||
|
||||
impl Attribute {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn help(&self) -> &str {
|
||||
&self.help
|
||||
}
|
||||
|
||||
/// Read the `current_value` directly from the attribute path
|
||||
pub fn current_value(&self) -> Result<AttrValue, PlatformError> {
|
||||
match read_string(&self.base_path.join("current_value")) {
|
||||
Ok(val) => {
|
||||
if let Ok(int) = val.parse::<i32>() {
|
||||
Ok(AttrValue::Integer(int))
|
||||
} else {
|
||||
Ok(AttrValue::String(val))
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the `current_value` directly to the attribute path
|
||||
pub fn set_current_value(&self, new_value: AttrValue) -> Result<(), PlatformError> {
|
||||
let path = self.base_path.join("current_value");
|
||||
|
||||
let value_str = match new_value {
|
||||
AttrValue::Integer(val) => val.to_string(),
|
||||
AttrValue::String(val) => val,
|
||||
_ => return Err(PlatformError::InvalidValue)
|
||||
};
|
||||
|
||||
let mut file = OpenOptions::new().write(true).open(&path)?;
|
||||
file.write_all(value_str.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn default_value(&self) -> &AttrValue {
|
||||
&self.default_value
|
||||
}
|
||||
|
||||
pub fn possible_values(&self) -> &AttrValue {
|
||||
&self.possible_values
|
||||
}
|
||||
|
||||
pub fn min_value(&self) -> &AttrValue {
|
||||
&self.min_value
|
||||
}
|
||||
|
||||
pub fn max_value(&self) -> &AttrValue {
|
||||
&self.max_value
|
||||
}
|
||||
|
||||
pub fn scalar_increment(&self) -> &AttrValue {
|
||||
&self.scalar_increment
|
||||
}
|
||||
|
||||
/// Read all the immutable values to struct data. These should *never*
|
||||
/// change, if they do then it is possibly a driver issue - although this is
|
||||
/// subject to `firmware_attributes` class changes in kernel.
|
||||
fn read_base_values(
|
||||
base_path: &Path
|
||||
) -> (AttrValue, AttrValue, AttrValue, AttrValue, AttrValue) {
|
||||
let default_value = match read_string(&base_path.join("default_value")) {
|
||||
Ok(val) => {
|
||||
if let Ok(int) = val.parse::<i32>() {
|
||||
AttrValue::Integer(int)
|
||||
} else {
|
||||
AttrValue::String(val)
|
||||
}
|
||||
}
|
||||
Err(_) => AttrValue::None
|
||||
};
|
||||
|
||||
let possible_values = match read_string(&base_path.join("possible_values")) {
|
||||
Ok(val) => {
|
||||
if let Ok(int) = val.parse::<i32>() {
|
||||
AttrValue::Integer(int)
|
||||
} else if val.contains(';') {
|
||||
AttrValue::EnumInt(val.split(';').filter_map(|s| s.parse().ok()).collect())
|
||||
} else {
|
||||
AttrValue::EnumStr(val.split(';').map(|s| s.to_string()).collect())
|
||||
}
|
||||
}
|
||||
Err(_) => AttrValue::None
|
||||
};
|
||||
|
||||
let min_value = read_i32(&base_path.join("min_value"))
|
||||
.ok()
|
||||
.map(AttrValue::Integer)
|
||||
.unwrap_or_default();
|
||||
let max_value = read_i32(&base_path.join("max_value"))
|
||||
.ok()
|
||||
.map(AttrValue::Integer)
|
||||
.unwrap_or_default();
|
||||
let scalar_increment = read_i32(&base_path.join("scalar_increment"))
|
||||
.ok()
|
||||
.map(AttrValue::Integer)
|
||||
.unwrap_or_default();
|
||||
|
||||
(
|
||||
default_value, possible_values, min_value, max_value, scalar_increment
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_watcher(&self) -> Result<inotify::Inotify, PlatformError> {
|
||||
let path = self.base_path.join("current_value");
|
||||
if let Some(path) = path.to_str() {
|
||||
let inotify = inotify::Inotify::init()?;
|
||||
inotify
|
||||
.watches()
|
||||
.add(path, inotify::WatchMask::MODIFY)
|
||||
.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
PlatformError::AttrNotFound(self.name().to_string())
|
||||
} else {
|
||||
PlatformError::IoPath(path.to_string(), e)
|
||||
}
|
||||
})?;
|
||||
return Ok(inotify);
|
||||
}
|
||||
Err(PlatformError::AttrNotFound(self.name().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FirmwareAttributes {
|
||||
attrs: Vec<Attribute>
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl FirmwareAttributes {
|
||||
pub fn new() -> Self {
|
||||
let mut attrs = Vec::new();
|
||||
if let Ok(dir) = read_dir(BASE_DIR) {
|
||||
for entry in dir.flatten() {
|
||||
let base_path = entry.path();
|
||||
let name = base_path.file_name().unwrap().to_string_lossy().to_string();
|
||||
if name == "pending_reboot" {
|
||||
continue;
|
||||
}
|
||||
let help = read_string(&base_path.join("display_name")).unwrap_or_default();
|
||||
|
||||
let (default_value, possible_values, min_value, max_value, scalar_increment) =
|
||||
Attribute::read_base_values(&base_path);
|
||||
|
||||
attrs.push(Attribute {
|
||||
name,
|
||||
help,
|
||||
default_value,
|
||||
possible_values,
|
||||
min_value,
|
||||
max_value,
|
||||
scalar_increment,
|
||||
base_path
|
||||
});
|
||||
}
|
||||
}
|
||||
Self { attrs }
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> &Vec<Attribute> {
|
||||
&self.attrs
|
||||
}
|
||||
|
||||
pub fn attributes_mut(&mut self) -> &mut Vec<Attribute> {
|
||||
&mut self.attrs
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_attribute_getters {
|
||||
($($attr:ident),*) => {
|
||||
impl FirmwareAttributes {
|
||||
$(
|
||||
pub fn $attr(&self) -> Option<&Attribute> {
|
||||
self.attrs.iter().find(|a| a.name() == stringify!($attr))
|
||||
}
|
||||
|
||||
concat_idents::concat_idents!(attr_mut = $attr, _mut {
|
||||
pub fn attr_mut(&mut self) -> Option<&mut Attribute> {
|
||||
self.attrs.iter_mut().find(|a| a.name() == stringify!($attr))
|
||||
}
|
||||
});
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_attribute_getters!(
|
||||
apu_mem, cores_performance, cores_efficiency, ppt_pl1_spl, ppt_pl2_sppt, ppt_apu_sppt,
|
||||
ppt_platform_sppt, ppt_fppt, nv_dynamic_boost, nv_temp_target, dgpu_base_tgp, dgpu_tgp,
|
||||
charge_mode, boot_sound, mcu_powersave, panel_od, panel_hd_mode, egpu_connected, egpu_enable,
|
||||
dgpu_disable, gpu_mux_mode, mini_led_mode
|
||||
);
|
||||
|
||||
/// CamelCase names of the properties. Intended for use with DBUS
|
||||
#[repr(u8)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Type,
|
||||
Value,
|
||||
OwnedValue,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
)]
|
||||
#[zvariant(signature = "s")]
|
||||
pub enum FirmwareAttribute {
|
||||
ApuMem = 0,
|
||||
CoresPerformance = 1,
|
||||
CoresEfficiency = 2,
|
||||
PptPl1Spl = 3,
|
||||
PptPl2Sppt = 4,
|
||||
PptPl3Fppt = 5,
|
||||
PptFppt = 6,
|
||||
PptApuSppt = 7,
|
||||
PptPlatformSppt = 8,
|
||||
NvDynamicBoost = 9,
|
||||
NvTempTarget = 10,
|
||||
DgpuBaseTgp = 11,
|
||||
DgpuTgp = 12,
|
||||
ChargeMode = 13,
|
||||
BootSound = 14,
|
||||
McuPowersave = 15,
|
||||
PanelOverdrive = 16,
|
||||
PanelHdMode = 17,
|
||||
EgpuConnected = 18,
|
||||
EgpuEnable = 19,
|
||||
DgpuDisable = 20,
|
||||
GpuMuxMode = 21,
|
||||
MiniLedMode = 22,
|
||||
PendingReboot = 23,
|
||||
None = 24
|
||||
}
|
||||
|
||||
impl From<&str> for FirmwareAttribute {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"apu_mem" => Self::ApuMem,
|
||||
"cores_performance" => Self::CoresPerformance,
|
||||
"cores_efficiency" => Self::CoresEfficiency,
|
||||
"ppt_pl1_spl" => Self::PptPl1Spl,
|
||||
"ppt_pl2_sppt" => Self::PptPl2Sppt,
|
||||
"ppt_pl3_fppt" => Self::PptPl3Fppt,
|
||||
"ppt_fppt" => Self::PptFppt,
|
||||
"ppt_apu_sppt" => Self::PptApuSppt,
|
||||
"ppt_platform_sppt" => Self::PptPlatformSppt,
|
||||
"nv_dynamic_boost" => Self::NvDynamicBoost,
|
||||
"nv_temp_target" => Self::NvTempTarget,
|
||||
"dgpu_base_tgp" => Self::DgpuBaseTgp,
|
||||
"dgpu_tgp" => Self::DgpuTgp,
|
||||
"charge_mode" => Self::ChargeMode,
|
||||
"boot_sound" => Self::BootSound,
|
||||
"mcu_powersave" => Self::McuPowersave,
|
||||
"panel_overdrive" => Self::PanelOverdrive,
|
||||
"panel_hd_mode" => Self::PanelHdMode,
|
||||
"egpu_connected" => Self::EgpuConnected,
|
||||
"egpu_enable" => Self::EgpuEnable,
|
||||
"dgpu_disable" => Self::DgpuDisable,
|
||||
"gpu_mux_mode" => Self::GpuMuxMode,
|
||||
"mini_led_mode" => Self::MiniLedMode,
|
||||
"pending_reboot" => Self::PendingReboot,
|
||||
_ => panic!("Invalid firmware attribute: {}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FirmwareAttribute> for &str {
|
||||
fn from(attr: FirmwareAttribute) -> Self {
|
||||
match attr {
|
||||
FirmwareAttribute::ApuMem => "apu_mem",
|
||||
FirmwareAttribute::CoresPerformance => "cores_performance",
|
||||
FirmwareAttribute::CoresEfficiency => "cores_efficiency",
|
||||
FirmwareAttribute::PptPl1Spl => "ppt_pl1_spl",
|
||||
FirmwareAttribute::PptPl2Sppt => "ppt_pl2_sppt",
|
||||
FirmwareAttribute::PptPl3Fppt => "ppt_pl3_fppt",
|
||||
FirmwareAttribute::PptFppt => "ppt_fppt",
|
||||
FirmwareAttribute::PptApuSppt => "ppt_apu_sppt",
|
||||
FirmwareAttribute::PptPlatformSppt => "ppt_platform_sppt",
|
||||
FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost",
|
||||
FirmwareAttribute::NvTempTarget => "nv_temp_target",
|
||||
FirmwareAttribute::DgpuBaseTgp => "dgpu_base_tgp",
|
||||
FirmwareAttribute::DgpuTgp => "dgpu_tgp",
|
||||
FirmwareAttribute::ChargeMode => "charge_mode",
|
||||
FirmwareAttribute::BootSound => "boot_sound",
|
||||
FirmwareAttribute::McuPowersave => "mcu_powersave",
|
||||
FirmwareAttribute::PanelOverdrive => "panel_overdrive",
|
||||
FirmwareAttribute::PanelHdMode => "panel_hd_mode",
|
||||
FirmwareAttribute::EgpuConnected => "egpu_connected",
|
||||
FirmwareAttribute::EgpuEnable => "egpu_enable",
|
||||
FirmwareAttribute::DgpuDisable => "dgpu_disable",
|
||||
FirmwareAttribute::GpuMuxMode => "gpu_mux_mode",
|
||||
FirmwareAttribute::MiniLedMode => "mini_led_mode",
|
||||
FirmwareAttribute::PendingReboot => "pending_reboot",
|
||||
FirmwareAttribute::None => "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore = "Can't check in docker env"]
|
||||
fn find_attributes() {
|
||||
let attrs = FirmwareAttributes::new();
|
||||
for attr in attrs.attributes() {
|
||||
dbg!(attr.name());
|
||||
match attr.name().into() {
|
||||
FirmwareAttribute::DgpuDisable => {
|
||||
assert!(!attr.help().is_empty());
|
||||
assert!(matches!(
|
||||
attr.current_value().unwrap(),
|
||||
AttrValue::Integer(_)
|
||||
));
|
||||
if let AttrValue::Integer(val) = attr.current_value().unwrap() {
|
||||
assert_eq!(val, 0);
|
||||
}
|
||||
if let AttrValue::Integer(val) = attr.default_value {
|
||||
assert_eq!(val, 25);
|
||||
}
|
||||
assert_eq!(attr.min_value, AttrValue::None);
|
||||
assert_eq!(attr.max_value, AttrValue::None);
|
||||
}
|
||||
FirmwareAttribute::BootSound => {
|
||||
assert!(!attr.help().is_empty());
|
||||
assert!(matches!(
|
||||
attr.current_value().unwrap(),
|
||||
AttrValue::Integer(0)
|
||||
));
|
||||
// dbg!(attr.current_value());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Can't check in docker env"]
|
||||
fn test_boot_sound() {
|
||||
let attrs = FirmwareAttributes::new();
|
||||
let attr = attrs
|
||||
.attributes()
|
||||
.iter()
|
||||
.find(|a| a.name() == <&str>::from(FirmwareAttribute::BootSound))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(attr.name(), <&str>::from(FirmwareAttribute::BootSound));
|
||||
assert_eq!(
|
||||
attr.base_path.to_str().unwrap(),
|
||||
"/sys/class/firmware-attributes/asus-armoury/attributes/boot_sound"
|
||||
);
|
||||
assert!(!attr.help().is_empty());
|
||||
assert!(matches!(
|
||||
attr.current_value().unwrap(),
|
||||
AttrValue::Integer(_)
|
||||
));
|
||||
if let AttrValue::Integer(val) = attr.current_value().unwrap() {
|
||||
assert_eq!(val, 0); // assuming value is 0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Can't check in docker env"]
|
||||
fn test_set_boot_sound() {
|
||||
let attrs = FirmwareAttributes::new();
|
||||
let attr = attrs
|
||||
.attributes()
|
||||
.iter()
|
||||
.find(|a| a.name() == <&str>::from(FirmwareAttribute::BootSound))
|
||||
.unwrap();
|
||||
|
||||
let mut val = attr.current_value().unwrap();
|
||||
if let AttrValue::Integer(val) = &mut val {
|
||||
*val = 0;
|
||||
}
|
||||
attr.set_current_value(val).unwrap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user