diff --git a/data/platforms/jupiter.toml b/data/platforms/jupiter.toml new file mode 100644 index 0000000..ae106df --- /dev/null +++ b/data/platforms/jupiter.toml @@ -0,0 +1,18 @@ +[factory_reset] +script = "/usr/bin/steamos-factory-reset-config" + +[update_bios] +script = "/usr/bin/jupiter-biosupdate" +script_args = ["--auto"] + +[update_dock] +script = "/usr/lib/jupiter-dock-updater/jupiter-dock-updater.sh" + +[storage.trim_devices] +script = "/usr/lib/hwsupport/trim-devices.sh" + +[storage.format_device] +script = "/usr/lib/hwsupport/format-device.sh" +label_flag = "--label" +device_flag = "--device" +no_validate_flag = "--skip-validation" diff --git a/src/lib.rs b/src/lib.rs index 3c0f6b7..f55abad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod ds_inhibit; mod error; mod job; mod manager; +mod platform; mod process; mod sls; mod systemd; diff --git a/src/manager/root.rs b/src/manager/root.rs index 82fad74..58c89b6 100644 --- a/src/manager/root.rs +++ b/src/manager/root.rs @@ -21,6 +21,7 @@ use crate::daemon::DaemonCommand; use crate::error::{to_zbus_error, to_zbus_fdo_error}; use crate::hardware::{variant, FanControl, FanControlState, HardwareVariant}; use crate::job::JobManager; +use crate::platform::platform_config; use crate::power::{ set_cpu_scaling_governor, set_gpu_clocks, set_gpu_performance_level, set_gpu_power_profile, set_tdp_limit, CPUScalingGovernor, GPUPerformanceLevel, GPUPowerProfile, @@ -32,6 +33,25 @@ use crate::wifi::{ }; use crate::API_VERSION; +macro_rules! with_platform_config { + ($config:ident = $field:ident ($name:literal) => $eval:expr) => { + if let Some(config) = platform_config() + .await + .map_err(to_zbus_fdo_error)? + .as_ref() + .and_then(|config| config.$field.as_ref()) + { + let $config = config; + $eval + } else { + Err(fdo::Error::NotSupported(format!( + "{} is not supported on this platform", + $name + ))) + } + }; +} + #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] enum PrepareFactoryReset { @@ -67,12 +87,16 @@ const ALS_INTEGRATION_PATH: &str = "/sys/devices/platform/AMDI0010:00/i2c-0/i2c- #[interface(name = "com.steampowered.SteamOSManager1.RootManager")] impl SteamOSManager { - async fn prepare_factory_reset(&self) -> u32 { + async fn prepare_factory_reset(&self) -> fdo::Result { // Run steamos factory reset script and return true on success - let res = run_script("/usr/bin/steamos-factory-reset-config", &[] as &[&OsStr]).await; - match res { - Ok(_) => PrepareFactoryReset::RebootRequired as u32, - Err(_) => PrepareFactoryReset::Unknown as u32, + with_platform_config! { + config = factory_reset ("PrepareFactoryReset") => { + let res = run_script(&config.script, &config.script_args).await; + Ok(match res { + Ok(_) => PrepareFactoryReset::RebootRequired as u32, + Err(_) => PrepareFactoryReset::Unknown as u32, + }) + } } } @@ -139,31 +163,35 @@ impl SteamOSManager { async fn update_bios(&mut self) -> fdo::Result { // Update the bios as needed - self.job_manager - .run_process("/usr/bin/jupiter-biosupdate", &["--auto"], "updating BIOS") - .await + with_platform_config! { + config = update_bios ("UpdateBios") => { + self.job_manager + .run_process(&config.script, &config.script_args, "updating BIOS") + .await + } + } } async fn update_dock(&mut self) -> fdo::Result { // Update the dock firmware as needed - self.job_manager - .run_process( - "/usr/lib/jupiter-dock-updater/jupiter-dock-updater.sh", - &[] as &[String; 0], - "updating dock", - ) - .await + with_platform_config! { + config = update_dock ("UpdateDock") => { + self.job_manager + .run_process(&config.script, &config.script_args, "updating dock") + .await + } + } } async fn trim_devices(&mut self) -> fdo::Result { // Run steamos-trim-devices script - self.job_manager - .run_process( - "/usr/lib/hwsupport/trim-devices.sh", - &[] as &[String; 0], - "trimming devices", - ) - .await + with_platform_config! { + config = storage ("TrimDevices") => { + self.job_manager + .run_process(&config.trim_devices.script, config.trim_devices.script_args.as_ref(), "trimming devices") + .await + } + } } async fn format_device( @@ -172,17 +200,33 @@ impl SteamOSManager { label: &str, validate: bool, ) -> fdo::Result { - let mut args = vec!["--label", label, "--device", device]; - if !validate { - args.push("--skip-validation"); + with_platform_config! { + config = storage ("FormatDevice") => { + let config = &config.format_device; + let mut args: Vec<&OsStr> = config.script_args.iter().map(AsRef::as_ref).collect(); + + args.extend_from_slice(&[OsStr::new(config.label_flag.as_str()), OsStr::new(label)]); + + match (validate, &config.validate_flag, &config.no_validate_flag) { + (true, Some(validate_flag), _) => args.push(OsStr::new(validate_flag)), + (false, _, Some(no_validate_flag)) => args.push(OsStr::new(no_validate_flag)), + _ => (), + } + + if let Some(device_flag) = &config.device_flag { + args.push(OsStr::new(device_flag)); + } + args.push(OsStr::new(device)); + + self.job_manager + .run_process( + &config.script, + &args, + format!("formatting {device}").as_str(), + ) + .await + } } - self.job_manager - .run_process( - "/usr/lib/hwsupport/format-device.sh", - args.as_ref(), - format!("formatting {device}").as_str(), - ) - .await } async fn set_gpu_power_profile(&self, value: &str) -> fdo::Result<()> { @@ -326,6 +370,7 @@ mod test { use super::*; use crate::daemon::channel; use crate::daemon::root::RootContext; + use crate::platform::{PlatformConfig, ScriptConfig}; use crate::power::test::{format_clocks, read_clocks}; use crate::power::{self, get_gpu_performance_level}; use crate::process::test::{code, exit, ok}; @@ -379,6 +424,11 @@ mod test { #[tokio::test] async fn prepare_factory_reset() { let test = start().await.expect("start"); + + let mut config = PlatformConfig::default(); + config.factory_reset = Some(ScriptConfig::default()); + test.h.test.platform_config.replace(Some(config)); + let name = test.connection.unique_name().unwrap(); let proxy = PrepareFactoryResetProxy::new(&test.connection, name.clone()) .await diff --git a/src/manager/user.rs b/src/manager/user.rs index de4d56e..c5320b0 100644 --- a/src/manager/user.rs +++ b/src/manager/user.rs @@ -21,6 +21,7 @@ use crate::daemon::DaemonCommand; use crate::error::{to_zbus_error, to_zbus_fdo_error, zbus_to_zbus_fdo}; use crate::hardware::{check_support, is_deck, HardwareCurrentlySupported}; use crate::job::JobManagerCommand; +use crate::platform::platform_config; use crate::power::{ get_available_cpu_scaling_governors, get_available_gpu_performance_levels, get_available_gpu_power_profiles, get_cpu_scaling_governor, get_gpu_clocks, @@ -577,6 +578,7 @@ pub(crate) async fn create_interfaces( proxy: proxy.clone(), }; + let config = platform_config().await?; let object_server = session.object_server(); object_server.at(MANAGER_PATH, manager).await?; @@ -585,7 +587,14 @@ pub(crate) async fn create_interfaces( } object_server.at(MANAGER_PATH, cpu_scaling).await?; - object_server.at(MANAGER_PATH, factory_reset).await?; + + if config + .as_ref() + .is_some_and(|config| config.factory_reset.is_some()) + { + object_server.at(MANAGER_PATH, factory_reset).await?; + } + object_server.at(MANAGER_PATH, fan_control).await?; if !get_available_gpu_performance_levels() @@ -612,9 +621,28 @@ pub(crate) async fn create_interfaces( object_server.at(MANAGER_PATH, hdmi_cec).await?; object_server.at(MANAGER_PATH, manager2).await?; - object_server.at(MANAGER_PATH, storage).await?; - object_server.at(MANAGER_PATH, update_bios).await?; - object_server.at(MANAGER_PATH, update_dock).await?; + + if config + .as_ref() + .is_some_and(|config| config.storage.is_some()) + { + object_server.at(MANAGER_PATH, storage).await?; + } + + if config + .as_ref() + .is_some_and(|config| config.update_bios.is_some()) + { + object_server.at(MANAGER_PATH, update_bios).await?; + } + + if config + .as_ref() + .is_some_and(|config| config.update_dock.is_some()) + { + object_server.at(MANAGER_PATH, update_dock).await?; + } + object_server.at(MANAGER_PATH, wifi_debug).await?; object_server .at(MANAGER_PATH, wifi_power_management) @@ -630,6 +658,7 @@ mod test { use crate::daemon::user::UserContext; use crate::hardware::test::fake_model; use crate::hardware::HardwareVariant; + use crate::platform::{PlatformConfig, ScriptConfig, StorageConfig}; use crate::{power, testing}; use std::time::Duration; @@ -642,10 +671,21 @@ mod test { connection: Connection, } - async fn start() -> Result { + fn all_config() -> Option { + Some(PlatformConfig { + factory_reset: Some(ScriptConfig::default()), + update_bios: Some(ScriptConfig::default()), + update_dock: Some(ScriptConfig::default()), + storage: Some(StorageConfig::default()), + }) + } + + async fn start(platform_config: Option) -> Result { let mut handle = testing::start(); let (tx_ctx, _rx_ctx) = channel::(); let (tx_job, _rx_job) = unbounded_channel::(); + + handle.test.platform_config.replace(platform_config); let connection = handle.new_dbus().await?; fake_model(HardwareVariant::Jupiter).await?; power::test::create_nodes().await?; @@ -661,7 +701,7 @@ mod test { #[tokio::test] async fn interface_matches() { - let test = start().await.expect("start"); + let test = start(None).await.expect("start"); let remote = testing::InterfaceIntrospection::from_remote::( &test.connection, @@ -689,9 +729,15 @@ mod test { Ok(remote.compare(&local)) } + async fn test_interface_missing(connection: &Connection) -> bool { + let remote = + testing::InterfaceIntrospection::from_remote::(connection, MANAGER_PATH).await; + return remote.is_err(); + } + #[tokio::test] async fn interface_matches_ambient_light_sensor1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!( test_interface_matches::(&test.connection) @@ -702,7 +748,7 @@ mod test { #[tokio::test] async fn interface_matches_cpu_scaling1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -711,16 +757,23 @@ mod test { #[tokio::test] async fn interface_matches_factory_reset1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await .unwrap()); } + #[tokio::test] + async fn interface_missing_factory_reset1() { + let test = start(None).await.expect("start"); + + assert!(test_interface_missing::(&test.connection).await); + } + #[tokio::test] async fn interface_matches_fan_control1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -729,7 +782,7 @@ mod test { #[tokio::test] async fn interface_matches_gpu_performance_level1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!( test_interface_matches::(&test.connection) @@ -740,7 +793,7 @@ mod test { #[tokio::test] async fn interface_matches_gpu_power_profile1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -749,7 +802,7 @@ mod test { #[tokio::test] async fn interface_matches_gpu_tdp_limit1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -758,7 +811,7 @@ mod test { #[tokio::test] async fn interface_matches_hdmi_cec1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -767,7 +820,7 @@ mod test { #[tokio::test] async fn interface_matches_manager2() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await @@ -776,34 +829,55 @@ mod test { #[tokio::test] async fn interface_matches_storage1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await .unwrap()); } + #[tokio::test] + async fn interface_missing_storage1() { + let test = start(None).await.expect("start"); + + assert!(test_interface_missing::(&test.connection).await); + } + #[tokio::test] async fn interface_matches_update_bios1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await .unwrap()); } + #[tokio::test] + async fn interface_missing_update_bios1() { + let test = start(None).await.expect("start"); + + assert!(test_interface_missing::(&test.connection).await); + } + #[tokio::test] async fn interface_matches_update_dock1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await .unwrap()); } + #[tokio::test] + async fn interface_missing_update_dock1() { + let test = start(None).await.expect("start"); + + assert!(test_interface_missing::(&test.connection).await); + } + #[tokio::test] async fn interface_matches_wifi_power_management1() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!( test_interface_matches::(&test.connection) @@ -814,7 +888,7 @@ mod test { #[tokio::test] async fn interface_matches_wifi_debug() { - let test = start().await.expect("start"); + let test = start(all_config()).await.expect("start"); assert!(test_interface_matches::(&test.connection) .await diff --git a/src/platform.rs b/src/platform.rs new file mode 100644 index 0000000..8e74072 --- /dev/null +++ b/src/platform.rs @@ -0,0 +1,94 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use serde::Deserialize; +use std::path::PathBuf; +use tokio::fs::read_to_string; +#[cfg(not(test))] +use tokio::sync::OnceCell; + +#[cfg(not(test))] +use crate::hardware::is_deck; + +#[cfg(not(test))] +static CONFIG: OnceCell> = OnceCell::const_new(); + +#[derive(Clone, Default, Deserialize, Debug)] +#[serde(default)] +pub(crate) struct PlatformConfig { + pub factory_reset: Option, + pub update_bios: Option, + pub update_dock: Option, + pub storage: Option, +} + +#[derive(Clone, Default, Deserialize, Debug)] +pub(crate) struct ScriptConfig { + pub script: PathBuf, + #[serde(default)] + pub script_args: Vec, +} + +#[derive(Clone, Default, Deserialize, Debug)] +pub(crate) struct StorageConfig { + pub trim_devices: ScriptConfig, + pub format_device: FormatDeviceConfig, +} + +#[derive(Clone, Default, Deserialize, Debug)] +pub(crate) struct FormatDeviceConfig { + pub script: PathBuf, + #[serde(default)] + pub script_args: Vec, + pub label_flag: String, + #[serde(default)] + pub device_flag: Option, + #[serde(default)] + pub validate_flag: Option, + #[serde(default)] + pub no_validate_flag: Option, +} + +impl PlatformConfig { + #[cfg(not(test))] + async fn load() -> Result> { + if !is_deck().await? { + // Non-Steam Deck platforms are not yet supported + return Ok(None); + } + + let config = read_to_string("/usr/share/steamos-manager/platforms/jupiter.toml").await?; + Ok(Some(toml::from_str(config.as_ref())?)) + } +} + +#[cfg(not(test))] +pub(crate) async fn platform_config() -> Result<&'static Option> { + CONFIG.get_or_try_init(PlatformConfig::load).await +} + +#[cfg(test)] +pub(crate) async fn platform_config() -> Result> { + let test = crate::testing::current(); + let config = test.platform_config.borrow().clone(); + Ok(config) +} + +#[cfg(test)] +mod test { + use super::*; + + #[tokio::test] + async fn jupiter_valid() { + let config = read_to_string("data/platforms/jupiter.toml") + .await + .expect("read_to_string"); + let res = toml::from_str::(config.as_ref()); + assert!(res.is_ok(), "{res:?}"); + } +} diff --git a/src/testing.rs b/src/testing.rs index 30c3bb4..6a17c90 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -22,6 +22,8 @@ use zbus::zvariant::ObjectPath; use zbus::{Address, Connection, ConnectionBuilder, Interface}; use zbus_xml::{Method, Node, Property}; +use crate::platform::PlatformConfig; + thread_local! { static TEST: RefCell>> = RefCell::new(None); } @@ -66,6 +68,7 @@ pub fn start() -> TestHandle { process_cb: Cell::new(|_, _| Err(anyhow!("No current process_cb"))), mock_dbus: Cell::new(None), dbus_address: Mutex::new(None), + platform_config: RefCell::new(None), }); *lock.borrow_mut() = Some(test.clone()); TestHandle { test } @@ -98,6 +101,7 @@ pub struct Test { pub process_cb: Cell Result<(i32, String)>>, pub mock_dbus: Cell>, pub dbus_address: Mutex>, + pub platform_config: RefCell>, } pub struct TestHandle {