Fluke/dbus refactor

This commit is contained in:
Luke Jones
2023-12-03 20:44:01 +00:00
parent f6e4cc0626
commit 0a69c23288
143 changed files with 5421 additions and 10343 deletions

View File

@@ -6,8 +6,6 @@ edition = "2021"
[dependencies]
log.workspace = true
rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
serde.workspace = true
serde_derive.workspace = true
zbus.workspace = true
@@ -19,4 +17,5 @@ typeshare.workspace = true
rusb.workspace = true
[dev-dependencies]
cargo-husky.workspace = true
cargo-husky.workspace = true
rog_aura = { path = "../rog-aura" }

258
rog-platform/src/cpu.rs Normal file
View File

@@ -0,0 +1,258 @@
use std::path::PathBuf;
use log::{info, warn};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use zbus::zvariant::{OwnedValue, Type, Value};
use crate::error::{PlatformError, Result};
use crate::platform::PlatformPolicy;
use crate::{read_attr_string, to_device};
const ATTR_AVAILABLE_GOVERNORS: &str = "cpufreq/scaling_available_governors";
const ATTR_GOVERNOR: &str = "cpufreq/scaling_governor";
const ATTR_AVAILABLE_EPP: &str = "cpufreq/energy_performance_available_preferences";
const ATTR_EPP: &str = "cpufreq/energy_performance_preference";
/// Both modern AMD and Intel have cpufreq control if using `powersave`
/// governor. What interests us the most here is `energy_performance_preference`
/// which can drastically alter CPU performance.
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
pub struct CPUControl {
paths: Vec<PathBuf>,
}
impl CPUControl {
pub fn new() -> Result<Self> {
let mut enumerator = udev::Enumerator::new().map_err(|err| {
warn!("{}", err);
PlatformError::Udev("enumerator failed".into(), err)
})?;
enumerator.match_subsystem("cpu").map_err(|err| {
warn!("{}", err);
PlatformError::Udev("match_subsystem failed".into(), err)
})?;
let mut supported = false;
let mut cpu = CPUControl { paths: Vec::new() };
for device in enumerator.scan_devices().map_err(|err| {
warn!("{}", err);
PlatformError::Udev("CPU: scan_devices failed".into(), err)
})? {
if !supported {
info!(
"Found CPU support at {:?}, checking supported items",
device.sysname()
);
match device.attribute_value(ATTR_AVAILABLE_GOVERNORS) {
Some(g) => info!("{ATTR_AVAILABLE_GOVERNORS}: {g:?}"),
None => {
return Err(PlatformError::CPU(format!(
"{ATTR_AVAILABLE_GOVERNORS} not found"
)))
}
}
match device.attribute_value(ATTR_GOVERNOR) {
Some(g) => info!("{ATTR_GOVERNOR}: {g:?}"),
None => return Err(PlatformError::CPU(format!("{ATTR_GOVERNOR} not found"))),
}
match device.attribute_value(ATTR_AVAILABLE_EPP) {
Some(g) => info!("{ATTR_AVAILABLE_EPP}: {g:?}"),
None => {
return Err(PlatformError::CPU(format!(
"{ATTR_AVAILABLE_EPP} not found"
)))
}
}
match device.attribute_value(ATTR_EPP) {
Some(g) => info!("{ATTR_EPP}: {g:?}"),
None => return Err(PlatformError::CPU(format!("{ATTR_EPP} not found"))),
}
supported = true;
}
if supported {
info!("Adding: {:?}", device.syspath());
cpu.paths.push(device.syspath().to_owned());
}
}
if cpu.paths.is_empty() {
return Err(PlatformError::MissingFunction(
"asus-nb-wmi not found".into(),
));
}
Ok(cpu)
}
pub fn get_governor(&self) -> Result<CPUGovernor> {
if let Some(path) = self.paths.first() {
let s = read_attr_string(&to_device(path)?, ATTR_GOVERNOR)?;
Ok(s.as_str().into())
// TODO: check cpu are sync
} else {
Err(PlatformError::CPU("No CPU's?".to_string()))
}
}
pub fn get_available_governors(&self) -> Result<Vec<CPUGovernor>> {
if let Some(path) = self.paths.first() {
read_attr_string(&to_device(path)?, ATTR_AVAILABLE_GOVERNORS)
.map(|s| s.split_whitespace().map(|s| s.into()).collect())
// TODO: check cpu are sync
} else {
Err(PlatformError::CPU("No CPU's?".to_string()))
}
}
pub fn set_governor(&self, gov: CPUGovernor) -> Result<()> {
if !self.get_available_governors()?.contains(&gov) {
return Err(PlatformError::CPU(format!("{gov:?} is not available")));
}
for path in &self.paths {
let mut dev = to_device(path)?;
dev.set_attribute_value(ATTR_AVAILABLE_GOVERNORS, String::from(gov))?;
}
Ok(())
}
pub fn get_epp(&self) -> Result<CPUEPP> {
if let Some(path) = self.paths.first() {
let s = read_attr_string(&to_device(path)?, ATTR_EPP)?;
Ok(s.as_str().into())
// TODO: check cpu are sync
} else {
Err(PlatformError::CPU("No CPU's?".to_string()))
}
}
pub fn get_available_epp(&self) -> Result<Vec<CPUEPP>> {
if let Some(path) = self.paths.first() {
read_attr_string(&to_device(path)?, ATTR_AVAILABLE_EPP)
.map(|s| s.split_whitespace().map(|s| s.into()).collect())
// TODO: check cpu are sync
} else {
Err(PlatformError::CPU("No CPU's?".to_string()))
}
}
pub fn set_epp(&self, epp: CPUEPP) -> Result<()> {
if !self.get_available_epp()?.contains(&epp) {
return Err(PlatformError::CPU(format!("{epp:?} is not available")));
}
for path in &self.paths {
let mut dev = to_device(path)?;
dev.set_attribute_value(ATTR_EPP, String::from(epp))?;
}
Ok(())
}
}
#[typeshare]
#[repr(u8)]
#[derive(
Deserialize, Serialize, Type, Value, OwnedValue, Debug, PartialEq, PartialOrd, Clone, Copy,
)]
#[zvariant(signature = "s")]
pub enum CPUGovernor {
Performance = 0,
Powersave = 1,
BadValue = 2,
}
impl From<&str> for CPUGovernor {
fn from(s: &str) -> Self {
match s {
"performance" => Self::Performance,
"powersave" => Self::Powersave,
_ => Self::BadValue,
}
}
}
impl From<CPUGovernor> for String {
fn from(g: CPUGovernor) -> Self {
match g {
CPUGovernor::Performance => "performance".to_string(),
CPUGovernor::Powersave => "powersave".to_string(),
CPUGovernor::BadValue => "bad_value".to_string(),
}
}
}
#[typeshare]
#[repr(u8)]
#[derive(
Deserialize, Serialize, Type, Value, OwnedValue, Debug, PartialEq, PartialOrd, Clone, Copy,
)]
#[zvariant(signature = "s")]
pub enum CPUEPP {
Default = 0,
Performance = 1,
BalancePerformance = 2,
BalancePower = 3,
Power = 4,
}
impl From<PlatformPolicy> for CPUEPP {
fn from(value: PlatformPolicy) -> Self {
match value {
PlatformPolicy::Balanced => CPUEPP::BalancePerformance,
PlatformPolicy::Performance => CPUEPP::Performance,
PlatformPolicy::Quiet => CPUEPP::Power,
}
}
}
impl From<&str> for CPUEPP {
fn from(s: &str) -> Self {
match s {
"default" => Self::Default,
"performance" => Self::Performance,
"balance_performance" => Self::BalancePerformance,
"balance_power" => Self::BalancePower,
"power" => Self::Power,
_ => Self::Default,
}
}
}
impl From<CPUEPP> for String {
fn from(g: CPUEPP) -> Self {
match g {
CPUEPP::Default => "default".to_string(),
CPUEPP::Performance => "performance".to_string(),
CPUEPP::BalancePerformance => "balance_performance".to_string(),
CPUEPP::BalancePower => "balance_power".to_string(),
CPUEPP::Power => "power".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::CPUControl;
use crate::cpu::{CPUGovernor, CPUEPP};
#[test]
#[ignore = "Can't run this in a docker image"]
fn check_cpu() {
let cpu = CPUControl::new().unwrap();
assert_eq!(cpu.get_governor().unwrap(), CPUGovernor::Powersave);
assert_eq!(
cpu.get_available_governors().unwrap(),
vec![CPUGovernor::Performance, CPUGovernor::Powersave]
);
assert_eq!(cpu.get_epp().unwrap(), CPUEPP::BalancePower);
assert_eq!(
cpu.get_available_epp().unwrap(),
vec![
CPUEPP::Default,
CPUEPP::Performance,
CPUEPP::BalancePerformance,
CPUEPP::BalancePower,
CPUEPP::Power,
]
);
}
}

View File

@@ -1,5 +1,7 @@
use std::fmt;
use zbus::fdo::Error as FdoErr;
pub type Result<T> = std::result::Result<T, PlatformError>;
#[derive(Debug)]
@@ -19,6 +21,7 @@ pub enum PlatformError {
Io(std::io::Error),
NoAuraKeyboard,
NoAuraNode,
CPU(String),
}
impl fmt::Display for PlatformError {
@@ -45,6 +48,7 @@ impl fmt::Display for PlatformError {
PlatformError::IoPath(path, detail) => write!(f, "{} {}", path, detail),
PlatformError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
PlatformError::NoAuraNode => write!(f, "No Aura keyboard node found"),
PlatformError::CPU(s) => write!(f, "CPU control: {s}"),
}
}
}
@@ -62,3 +66,13 @@ impl From<std::io::Error> for PlatformError {
PlatformError::Io(err)
}
}
impl From<PlatformError> for FdoErr {
fn from(error: PlatformError) -> Self {
log::error!("PlatformError: got: {error}");
match error {
PlatformError::NotSupported => FdoErr::NotSupported("".to_owned()),
_ => FdoErr::Failed(format!("Failed with {error}")),
}
}
}

View File

@@ -1,3 +1,4 @@
use std::cell::UnsafeCell;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
@@ -6,8 +7,11 @@ use log::{info, warn};
use crate::error::{PlatformError, Result};
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
pub struct HidRaw(PathBuf);
#[derive(Debug)]
pub struct HidRaw {
path: UnsafeCell<PathBuf>,
prod_id: String,
}
impl HidRaw {
pub fn new(id_product: &str) -> Result<Self> {
@@ -35,7 +39,10 @@ impl HidRaw {
if parent == id_product {
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?} for hidraw control", dev_node);
return Ok(Self(dev_node.to_owned()));
return Ok(Self {
path: UnsafeCell::new(dev_node.to_owned()),
prod_id: id_product.to_string(),
});
}
}
}
@@ -48,7 +55,10 @@ impl HidRaw {
"Using device at: {:?} for <TODO: label control> control",
dev_node
);
return Ok(Self(dev_node.to_owned()));
return Ok(Self {
path: UnsafeCell::new(dev_node.to_owned()),
prod_id: id_product.to_string(),
});
}
}
}
@@ -60,12 +70,22 @@ impl HidRaw {
}
pub fn write_bytes(&self, message: &[u8]) -> Result<()> {
let mut file = OpenOptions::new()
.write(true)
.open(&self.0)
.map_err(|e| PlatformError::IoPath(self.0.to_string_lossy().to_string(), e))?;
// println!("write: {:02x?}", &message);
let mut path = unsafe { &*(self.path.get()) };
let mut file = match OpenOptions::new().write(true).open(path) {
Ok(f) => f,
Err(e) => {
warn!("write_bytes failed for {:?}, trying again: {e}", self.path);
unsafe {
*(self.path.get()) = (*(Self::new(&self.prod_id)?.path.get())).clone();
path = &mut *(self.path.get());
}
OpenOptions::new()
.write(true)
.open(path)
.map_err(|e| PlatformError::IoPath(path.to_string_lossy().to_string(), e))?
}
};
file.write_all(message)
.map_err(|e| PlatformError::IoPath(self.0.to_string_lossy().to_string(), e))
.map_err(|e| PlatformError::IoPath(path.to_string_lossy().to_string(), e))
}
}

View File

@@ -1,13 +1,13 @@
//! This crate functions as a wrapper of all the relevant ASUS functionality
//! on ROG, Strix, and TUF laptops.
pub mod cpu;
pub mod error;
pub mod hid_raw;
pub mod keyboard_led;
pub(crate) mod macros;
pub mod platform;
pub mod power;
pub mod supported;
pub mod usb_raw;
use std::path::Path;

View File

@@ -5,10 +5,9 @@ use std::str::FromStr;
use log::{info, warn};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use zbus::zvariant::Type;
use zbus::zvariant::{OwnedValue, Type, Value};
use crate::error::{PlatformError, Result};
use crate::supported::PlatformSupportedFunctions;
use crate::{attr_bool, attr_string, attr_u8, to_device};
/// The "platform" device provides access to things like:
@@ -20,12 +19,12 @@ use crate::{attr_bool, attr_string, attr_u8, to_device};
/// - `keyboard_mode`, set keyboard RGB mode and speed
/// - `keyboard_state`, set keyboard power states
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
pub struct AsusPlatform {
pub struct RogPlatform {
path: PathBuf,
pp_path: PathBuf,
}
impl AsusPlatform {
impl RogPlatform {
attr_bool!("dgpu_disable", path);
attr_bool!("egpu_enable", path);
@@ -138,7 +137,7 @@ impl AsusPlatform {
}
}
impl Default for AsusPlatform {
impl Default for RogPlatform {
fn default() -> Self {
unsafe {
Self {
@@ -149,39 +148,21 @@ impl Default for AsusPlatform {
}
}
impl From<AsusPlatform> for PlatformSupportedFunctions {
fn from(a: AsusPlatform) -> Self {
PlatformSupportedFunctions {
post_animation_sound: a.has_post_animation_sound(),
gpu_mux: a.has_gpu_mux_mode(),
panel_overdrive: a.has_panel_od(),
dgpu_disable: a.has_dgpu_disable(),
egpu_enable: a.has_egpu_enable(),
mini_led_mode: a.has_mini_led_mode(),
ppt_pl1_spl: a.has_ppt_pl1_spl(),
ppt_pl2_sppt: a.has_ppt_pl2_sppt(),
ppt_fppt: a.has_ppt_fppt(),
ppt_apu_sppt: a.has_ppt_apu_sppt(),
ppt_platform_sppt: a.has_ppt_platform_sppt(),
nv_dynamic_boost: a.has_nv_dynamic_boost(),
nv_temp_target: a.has_nv_temp_target(),
}
}
}
#[typeshare]
#[repr(u8)]
#[derive(Serialize, Deserialize, Default, Type, Debug, PartialEq, Eq, Clone, Copy)]
#[derive(
Serialize, Deserialize, Default, Type, Value, OwnedValue, Debug, PartialEq, Eq, Clone, Copy,
)]
pub enum GpuMode {
Discrete,
Optimus,
Integrated,
Egpu,
Vfio,
Ultimate,
Discrete = 0,
Optimus = 1,
Integrated = 2,
Egpu = 3,
Vfio = 4,
Ultimate = 5,
#[default]
Error,
NotSupported,
Error = 6,
NotSupported = 7,
}
impl From<u8> for GpuMode {
@@ -266,3 +247,130 @@ impl Display for GpuMode {
}
}
}
#[typeshare]
#[repr(u8)]
#[derive(
Deserialize,
Serialize,
Default,
Type,
Value,
OwnedValue,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Clone,
Copy,
)]
#[zvariant(signature = "s")]
/// `throttle_thermal_policy` in asus_wmi
pub enum PlatformPolicy {
#[default]
Balanced = 0,
Performance = 1,
Quiet = 2,
}
impl PlatformPolicy {
pub const fn next(&self) -> Self {
match self {
Self::Balanced => Self::Balanced,
Self::Performance => Self::Quiet,
Self::Quiet => Self::Balanced,
}
}
pub const fn list() -> [Self; 3] {
[Self::Balanced, Self::Performance, Self::Quiet]
}
}
impl From<u8> for PlatformPolicy {
fn from(num: u8) -> Self {
match num {
0 => Self::Balanced,
1 => Self::Performance,
2 => Self::Quiet,
_ => {
warn!("Unknown number for PlatformProfile: {}", num);
Self::Balanced
}
}
}
}
impl From<PlatformPolicy> for u8 {
fn from(p: PlatformPolicy) -> Self {
match p {
PlatformPolicy::Balanced => 0,
PlatformPolicy::Performance => 1,
PlatformPolicy::Quiet => 2,
}
}
}
impl From<PlatformPolicy> for &str {
fn from(profile: PlatformPolicy) -> &'static str {
match profile {
PlatformPolicy::Balanced => "balanced",
PlatformPolicy::Performance => "performance",
PlatformPolicy::Quiet => "quiet",
}
}
}
impl std::str::FromStr for PlatformPolicy {
type Err = PlatformError;
fn from_str(profile: &str) -> Result<Self> {
match profile.to_ascii_lowercase().trim() {
"balanced" => Ok(PlatformPolicy::Balanced),
"performance" => Ok(PlatformPolicy::Performance),
"quiet" => Ok(PlatformPolicy::Quiet),
_ => Err(PlatformError::NotSupported),
}
}
}
impl Display for PlatformPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl PlatformPolicy {
pub fn get_next_profile(current: PlatformPolicy) -> PlatformPolicy {
match current {
PlatformPolicy::Balanced => PlatformPolicy::Performance,
PlatformPolicy::Performance => PlatformPolicy::Quiet,
PlatformPolicy::Quiet => PlatformPolicy::Balanced,
}
}
}
/// CamelCase names of the properties. Intended for use with DBUS
#[typeshare]
#[repr(u8)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Type, PartialEq, PartialOrd)]
#[zvariant(signature = "s")]
pub enum Properties {
ChargeControlEndThreshold,
DgpuDisable,
GpuMuxMode,
PostAnimationSound,
PanelOd,
MiniLedMode,
EgpuEnable,
PlatformPolicy,
PptPl1Spl,
PptPl2Sppt,
PptFppt,
PptApuSppt,
PptPlatformSppt,
NvDynamicBoost,
NvTempTarget,
}

View File

@@ -1,150 +0,0 @@
use std::fmt;
use rog_aura::aura_detection::PowerZones;
use rog_aura::usb::AuraDevice;
use rog_aura::{AdvancedAuraType, AuraModeNum, AuraZone};
use rog_profiles::FanCurvePU;
use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare;
use zbus::zvariant::Type;
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct SupportedFunctions {
pub anime_ctrl: AnimeSupportedFunctions,
pub charge_ctrl: ChargeSupportedFunctions,
pub platform_profile: PlatformProfileFunctions,
pub keyboard_led: LedSupportedFunctions,
pub rog_bios_ctrl: PlatformSupportedFunctions,
}
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct AnimeSupportedFunctions(pub bool);
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct ChargeSupportedFunctions {
pub charge_level_set: bool,
}
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct PlatformProfileFunctions {
pub platform_profile: bool,
pub fans: Vec<FanCurvePU>,
}
#[typeshare]
#[derive(Serialize, Deserialize, Default, Type, Debug, Clone)]
#[zvariant(signature = "s")]
pub enum AdvancedAura {
#[default]
None,
Zoned,
PerKey,
}
impl From<AdvancedAuraType> for AdvancedAura {
fn from(a: AdvancedAuraType) -> Self {
match a {
AdvancedAuraType::None => Self::None,
AdvancedAuraType::Zoned(_) => Self::Zoned,
AdvancedAuraType::PerKey => Self::PerKey,
}
}
}
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct LedSupportedFunctions {
pub dev_id: AuraDevice,
pub brightness: bool,
pub basic_modes: Vec<AuraModeNum>,
pub basic_zones: Vec<AuraZone>,
pub advanced_type: AdvancedAura,
pub power_zones: Vec<PowerZones>,
}
#[typeshare]
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct PlatformSupportedFunctions {
pub post_animation_sound: bool,
pub gpu_mux: bool,
pub panel_overdrive: bool,
pub dgpu_disable: bool,
pub egpu_enable: bool,
pub mini_led_mode: bool,
pub ppt_pl1_spl: bool,
pub ppt_pl2_sppt: bool,
pub ppt_fppt: bool,
pub ppt_apu_sppt: bool,
pub ppt_platform_sppt: bool,
pub nv_dynamic_boost: bool,
pub nv_temp_target: bool,
}
impl fmt::Display for SupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{}", self.anime_ctrl)?;
writeln!(f, "{}", self.charge_ctrl)?;
writeln!(f, "{}", self.platform_profile)?;
writeln!(f, "{}", self.keyboard_led)?;
writeln!(f, "{}", self.rog_bios_ctrl)
}
}
impl fmt::Display for AnimeSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "AniMe Matrix:")?;
writeln!(f, "\tAnime Matrix control: {}", self.0)
}
}
impl fmt::Display for ChargeSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Charge:")?;
writeln!(
f,
"\tBattery charge limit control: {}",
self.charge_level_set
)
}
}
impl fmt::Display for PlatformProfileFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Platform profiles:")?;
writeln!(f, "\tplatform: {}", self.platform_profile)?;
writeln!(f, "\tfan curves: {:?}", self.fans)
}
}
impl fmt::Display for LedSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "LED:")?;
writeln!(f, "\tDevice ID: {:?}", self.dev_id)?;
writeln!(f, "\tBrightness control: {}", self.brightness)?;
writeln!(f, "\tBasic modes: {:?}", self.basic_modes)?;
writeln!(f, "\tBasic zones: {:?}", self.basic_zones)?;
writeln!(f, "\tAdvanced modes: {:?}", self.advanced_type)
}
}
impl fmt::Display for PlatformSupportedFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "ROG BIOS:")?;
writeln!(f, "\tPOST sound switch: {}", self.post_animation_sound)?;
writeln!(f, "\tPanel Overdrive: {}", self.panel_overdrive)?;
writeln!(f, "\tMiniLED backlight: {}", self.mini_led_mode)?;
writeln!(f, "\tdGPU disable switch: {}", self.dgpu_disable)?;
writeln!(f, "\teGPU enable switch: {}", self.egpu_enable)?;
writeln!(f, "\tGPU MUX control: {}", self.gpu_mux)?;
writeln!(f, "\tppt_pl1_spl: {}", self.ppt_pl1_spl)?;
writeln!(f, "\tppt_pl2_sppt: {}", self.ppt_pl2_sppt)?;
writeln!(f, "\tppt_fppt {}", self.ppt_fppt)?;
writeln!(f, "\tppt_apu_sppt: {}", self.ppt_apu_sppt)?;
writeln!(f, "\tppt_platform_sppt: {}", self.ppt_platform_sppt)?;
writeln!(f, "\tnv_dynamic_boost: {}", self.nv_dynamic_boost)?;
writeln!(f, "\tnv_temp_target: {}", self.nv_temp_target)?;
Ok(())
}
}