hardware: Split out device type detection, add initial identification of Legion Go S

This commit is contained in:
Vicki Pfau 2025-02-20 03:29:00 -08:00
parent cd9558fd67
commit 7d8bd602a7
5 changed files with 147 additions and 64 deletions

View file

@ -19,15 +19,24 @@ use crate::systemd::SystemdUnit;
const SYS_VENDOR_PATH: &str = "/sys/class/dmi/id/sys_vendor";
const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name";
const PRODUCT_NAME_PATH: &str = "/sys/class/dmi/id/product_name";
#[derive(PartialEq, Debug, Default, Copy, Clone)]
pub(crate) enum HardwareVariant {
pub(crate) enum SteamDeckVariant {
#[default]
Unknown,
Jupiter,
Galileo,
}
#[derive(PartialEq, Debug, Default, Copy, Clone)]
pub(crate) enum DeviceType {
#[default]
Unknown,
SteamDeck,
LegionGoS,
}
#[derive(Display, EnumString, PartialEq, Debug, Copy, Clone, TryFromPrimitive)]
#[strum(ascii_case_insensitive)]
#[repr(u32)]
@ -38,13 +47,13 @@ pub enum FanControlState {
Os = 1,
}
impl FromStr for HardwareVariant {
impl FromStr for SteamDeckVariant {
type Err = Error;
fn from_str(input: &str) -> Result<HardwareVariant, Self::Err> {
fn from_str(input: &str) -> Result<SteamDeckVariant, Self::Err> {
Ok(match input {
"Jupiter" => HardwareVariant::Jupiter,
"Galileo" => HardwareVariant::Galileo,
_ => HardwareVariant::Unknown,
"Jupiter" => SteamDeckVariant::Jupiter,
"Galileo" => SteamDeckVariant::Galileo,
_ => SteamDeckVariant::Unknown,
})
}
}
@ -58,21 +67,27 @@ pub enum FactoryResetKind {
All = 3,
}
pub(crate) async fn variant() -> Result<HardwareVariant> {
pub(crate) async fn steam_deck_variant() -> Result<SteamDeckVariant> {
let sys_vendor = fs::read_to_string(path(SYS_VENDOR_PATH)).await?;
if sys_vendor.trim_end() != "Valve" {
return Ok(HardwareVariant::Unknown);
return Ok(SteamDeckVariant::Unknown);
}
let board_name = fs::read_to_string(path(BOARD_NAME_PATH)).await?;
HardwareVariant::from_str(board_name.trim_end())
SteamDeckVariant::from_str(board_name.trim_end())
}
pub(crate) async fn is_deck() -> Result<bool> {
match variant().await {
Ok(variant) => Ok(variant != HardwareVariant::Unknown),
Err(e) => Err(e),
pub(crate) async fn device_type() -> Result<DeviceType> {
if variant().await? != SteamDeckVariant::Unknown {
return Ok(DeviceType::SteamDeck);
}
let board_vendor = fs::read_to_string(path(SYS_VENDOR_PATH)).await?;
if board_vendor.trim_end() == "LENOVO" {
let product_name = fs::read_to_string(path(PRODUCT_NAME_PATH)).await?;
if ["83L3", "83N6", "83Q2", "83Q3"].contains(&product_name.trim_end()) {
return Ok(DeviceType::LegionGoS);
}
}
Ok(DeviceType::Unknown)
}
pub(crate) struct FanControl {
@ -153,15 +168,15 @@ pub mod test {
use zbus::fdo;
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
pub(crate) async fn fake_model(model: HardwareVariant) -> Result<()> {
pub(crate) async fn fake_model(model: SteamDeckVariant) -> Result<()> {
create_dir_all(crate::path("/sys/class/dmi/id")).await?;
match model {
HardwareVariant::Unknown => write(crate::path(SYS_VENDOR_PATH), "LENOVO\n").await?,
HardwareVariant::Jupiter => {
SteamDeckVariant::Unknown => write(crate::path(SYS_VENDOR_PATH), "LENOVO\n").await?,
SteamDeckVariant::Jupiter => {
write(crate::path(SYS_VENDOR_PATH), "Valve\n").await?;
write(crate::path(BOARD_NAME_PATH), "Jupiter\n").await?;
}
HardwareVariant::Galileo => {
SteamDeckVariant::Galileo => {
write(crate::path(SYS_VENDOR_PATH), "Valve\n").await?;
write(crate::path(BOARD_NAME_PATH), "Galileo\n").await?;
}
@ -176,12 +191,58 @@ pub mod test {
create_dir_all(crate::path("/sys/class/dmi/id"))
.await
.expect("create_dir_all");
assert!(variant().await.is_err());
assert!(steam_deck_variant().await.is_err());
write(crate::path(SYS_VENDOR_PATH), "LENOVO\n")
.await
.expect("write");
assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown);
write(crate::path(BOARD_NAME_PATH), "INVALID\n")
.await
.expect("write");
write(crate::path(PRODUCT_NAME_PATH), "INVALID\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::Unknown);
write(crate::path(PRODUCT_NAME_PATH), "83L3\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::LegionGoS);
write(crate::path(PRODUCT_NAME_PATH), "83N6\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::LegionGoS);
write(crate::path(PRODUCT_NAME_PATH), "83Q2\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::LegionGoS);
write(crate::path(PRODUCT_NAME_PATH), "83Q3\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::LegionGoS);
write(crate::path(SYS_VENDOR_PATH), "Valve\n")
.await
@ -189,17 +250,35 @@ pub mod test {
write(crate::path(BOARD_NAME_PATH), "Jupiter\n")
.await
.expect("write");
assert_eq!(variant().await.unwrap(), HardwareVariant::Jupiter);
write(crate::path(PRODUCT_NAME_PATH), "Jupiter\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Jupiter
);
assert_eq!(device_type().await.unwrap(), DeviceType::SteamDeck);
write(crate::path(BOARD_NAME_PATH), "Galileo\n")
.await
.expect("write");
assert_eq!(variant().await.unwrap(), HardwareVariant::Galileo);
write(crate::path(PRODUCT_NAME_PATH), "Galileo\n")
.await
.expect("write");
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Galileo
);
assert_eq!(device_type().await.unwrap(), DeviceType::SteamDeck);
write(crate::path(BOARD_NAME_PATH), "Neptune\n")
.await
.expect("write");
assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown);
assert_eq!(
steam_deck_variant().await.unwrap(),
SteamDeckVariant::Unknown
);
assert_eq!(device_type().await.unwrap(), DeviceType::Unknown);
}
#[test]

View file

@ -20,7 +20,9 @@ use zbus::{fdo, interface, Connection};
use crate::daemon::root::{Command, RootCommand};
use crate::daemon::DaemonCommand;
use crate::error::{to_zbus_error, to_zbus_fdo_error};
use crate::hardware::{variant, FactoryResetKind, FanControl, FanControlState, HardwareVariant};
use crate::hardware::{
steam_deck_variant, FactoryResetKind, FanControl, FanControlState, SteamDeckVariant,
};
use crate::job::JobManager;
use crate::platform::platform_config;
use crate::power::{
@ -78,7 +80,7 @@ impl SteamOSManager {
Ok(SteamOSManager {
fan_control: FanControl::new(connection.clone()),
wifi_debug_mode: WifiDebugMode::Off,
should_trace: variant().await? == HardwareVariant::Galileo,
should_trace: steam_deck_variant().await? == SteamDeckVariant::Galileo,
job_manager: JobManager::new(connection.clone()).await?,
connection,
channel,
@ -151,9 +153,9 @@ impl SteamOSManager {
async fn als_calibration_gain(&self) -> Vec<f64> {
// Run script to get calibration value
let mut gains = Vec::new();
let indices: &[&str] = match variant().await {
Ok(HardwareVariant::Jupiter) => &["2"],
Ok(HardwareVariant::Galileo) => &["2", "4"],
let indices: &[&str] = match steam_deck_variant().await {
Ok(SteamDeckVariant::Jupiter) => &["2"],
Ok(SteamDeckVariant::Galileo) => &["2", "4"],
_ => return Vec::new(),
};
for index in indices {
@ -176,10 +178,10 @@ impl SteamOSManager {
async fn get_als_integration_time_file_descriptor(&self, index: u32) -> fdo::Result<Fd> {
// Get the file descriptor for the als integration time sysfs path
let i0 = match variant().await.map_err(to_zbus_fdo_error)? {
HardwareVariant::Jupiter => 1,
HardwareVariant::Galileo => index,
HardwareVariant::Unknown => {
let i0 = match steam_deck_variant().await.map_err(to_zbus_fdo_error)? {
SteamDeckVariant::Jupiter => 1,
SteamDeckVariant::Galileo => index,
SteamDeckVariant::Unknown => {
return Err(fdo::Error::Failed(String::from("Unknown model")))
}
};
@ -457,7 +459,7 @@ mod test {
async fn start() -> Result<TestHandle> {
let mut handle = testing::start();
fake_model(HardwareVariant::Jupiter).await?;
fake_model(SteamDeckVariant::Jupiter).await?;
create_dir_all(crate::path("/etc/NetworkManager/conf.d")).await?;
write(
crate::path("/etc/NetworkManager/conf.d/99-valve-wifi-backend.conf"),
@ -608,22 +610,22 @@ mod test {
.process_cb
.set(|_, _| Ok((0, String::from("0.0\n"))));
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
assert_eq!(proxy.als_calibration_gain().await.unwrap(), &[0.0]);
fake_model(HardwareVariant::Galileo)
fake_model(SteamDeckVariant::Galileo)
.await
.expect("fake_model");
assert_eq!(proxy.als_calibration_gain().await.unwrap(), &[0.0, 0.0]);
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
assert_eq!(proxy.als_calibration_gain().await.unwrap(), &[]);
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");

View file

@ -19,7 +19,7 @@ use crate::cec::{HdmiCecControl, HdmiCecState};
use crate::daemon::user::Command;
use crate::daemon::DaemonCommand;
use crate::error::{to_zbus_error, to_zbus_fdo_error, zbus_to_zbus_fdo};
use crate::hardware::{is_deck, variant, HardwareVariant};
use crate::hardware::{device_type, steam_deck_variant, DeviceType, SteamDeckVariant};
use crate::job::JobManagerCommand;
use crate::platform::platform_config;
use crate::power::{
@ -680,10 +680,10 @@ pub(crate) async fn create_interfaces(
create_config_interfaces(&proxy, object_server, &job_manager).await?;
if is_deck().await? {
if device_type().await.unwrap_or_default() == DeviceType::SteamDeck {
object_server.at(MANAGER_PATH, als).await?;
}
if variant().await? == HardwareVariant::Galileo {
if steam_deck_variant().await.unwrap_or_default() == SteamDeckVariant::Galileo {
object_server.at(MANAGER_PATH, wifi_debug_dump).await?;
}
@ -721,7 +721,7 @@ pub(crate) async fn create_interfaces(
object_server.at(MANAGER_PATH, tdp_limit).await?;
}
if variant().await.unwrap_or_default() == HardwareVariant::Galileo {
if steam_deck_variant().await.unwrap_or_default() == SteamDeckVariant::Galileo {
object_server.at(MANAGER_PATH, wifi_debug).await?;
}
@ -740,7 +740,7 @@ mod test {
use crate::daemon::channel;
use crate::daemon::user::UserContext;
use crate::hardware::test::fake_model;
use crate::hardware::HardwareVariant;
use crate::hardware::SteamDeckVariant;
use crate::platform::{
BatteryChargeLimitConfig, PlatformConfig, RangeConfig, ResetConfig, ScriptConfig,
ServiceConfig, StorageConfig,
@ -813,7 +813,7 @@ mod test {
write(&exe_path, "").await?;
set_permissions(&exe_path, PermissionsExt::from_mode(0o700)).await?;
fake_model(HardwareVariant::Galileo).await?;
fake_model(SteamDeckVariant::Galileo).await?;
handle
.test
.process_cb

View file

@ -18,7 +18,7 @@ use tokio::sync::OnceCell;
use tokio::task::spawn_blocking;
#[cfg(not(test))]
use crate::hardware::is_deck;
use crate::hardware::{device_type, DeviceType};
#[cfg(not(test))]
static CONFIG: OnceCell<Option<PlatformConfig>> = OnceCell::const_new();
@ -137,12 +137,14 @@ impl<T: Clone> RangeConfig<T> {
impl PlatformConfig {
#[cfg(not(test))]
async fn load() -> Result<Option<PlatformConfig>> {
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?;
let platform = match device_type().await? {
DeviceType::SteamDeck => "jupiter",
_ => return Ok(None),
};
let config = read_to_string(format!(
"/usr/share/steamos-manager/platforms/{platform}.toml"
))
.await?;
Ok(Some(toml::from_str(config.as_ref())?))
}

View file

@ -16,7 +16,7 @@ use tokio::fs::{self, try_exists, File};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tracing::{error, warn};
use crate::hardware::is_deck;
use crate::hardware::{device_type, DeviceType};
use crate::platform::platform_config;
use crate::{path, write_synced};
@ -139,7 +139,7 @@ pub(crate) async fn get_gpu_power_profile() -> Result<GPUPowerProfile> {
// check which profile is current and return if possible
let contents = read_gpu_sysfs_contents(GPU_POWER_PROFILE_SUFFIX).await?;
// NOTE: We don't filter based on is_deck here because the sysfs
// NOTE: We don't filter based on deck here because the sysfs
// firmware support setting the value to no-op values.
let lines = contents.lines();
for line in lines {
@ -162,7 +162,7 @@ pub(crate) async fn get_gpu_power_profile() -> Result<GPUPowerProfile> {
pub(crate) async fn get_available_gpu_power_profiles() -> Result<Vec<(u32, String)>> {
let contents = read_gpu_sysfs_contents(GPU_POWER_PROFILE_SUFFIX).await?;
let deck = is_deck().await?;
let deck = device_type().await.unwrap_or_default() == DeviceType::SteamDeck;
let mut map = Vec::new();
let lines = contents.lines();
@ -437,7 +437,7 @@ pub(crate) async fn set_max_charge_level(limit: i32) -> Result<()> {
pub(crate) mod test {
use super::*;
use crate::hardware::test::fake_model;
use crate::hardware::HardwareVariant;
use crate::hardware::SteamDeckVariant;
use crate::platform::{BatteryChargeLimitConfig, PlatformConfig};
use crate::{enum_roundtrip, testing};
use anyhow::anyhow;
@ -815,7 +815,7 @@ CCLK_RANGE in Core0:
write(filename.as_path(), contents).await.expect("write");
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
@ -836,7 +836,7 @@ CCLK_RANGE in Core0:
]
);
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
@ -872,7 +872,7 @@ CCLK_RANGE in Core0:
write(filename.as_path(), contents).await.expect("write");
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
@ -894,7 +894,7 @@ CCLK_RANGE in Core0:
]
);
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
@ -929,7 +929,7 @@ CCLK_RANGE in Core0:
write(filename.as_path(), contents).await.expect("write");
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
assert_eq!(
@ -937,7 +937,7 @@ CCLK_RANGE in Core0:
GPUPowerProfile::Video
);
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
assert_eq!(
@ -967,12 +967,12 @@ CCLK_RANGE in Core0:
write(filename.as_path(), contents).await.expect("write");
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
assert!(get_gpu_power_profile().await.is_err());
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
assert!(get_gpu_power_profile().await.is_err());
@ -1000,12 +1000,12 @@ CCLK_RANGE in Core0:
write(filename.as_path(), contents).await.expect("write");
fake_model(HardwareVariant::Unknown)
fake_model(SteamDeckVariant::Unknown)
.await
.expect("fake_model");
assert!(get_gpu_power_profile().await.is_err());
fake_model(HardwareVariant::Jupiter)
fake_model(SteamDeckVariant::Jupiter)
.await
.expect("fake_model");
assert!(get_gpu_power_profile().await.is_err());