xps 13 3980
This commit is contained in:
235
src/sal/dell_xps_9380.rs
Normal file
235
src/sal/dell_xps_9380.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use super::traits::{PreflightAuditor, EnvironmentGuard, SensorBus, ActuatorBus, HardwareWatchdog, AuditError, AuditStep};
|
||||
use anyhow::{Result, Context};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::Mutex;
|
||||
use tracing::debug;
|
||||
|
||||
pub struct DellXps9380Sal {
|
||||
temp_path: PathBuf,
|
||||
pwr_path: PathBuf,
|
||||
fan_path: PathBuf,
|
||||
pl1_path: PathBuf,
|
||||
pl2_path: PathBuf,
|
||||
last_poll: Mutex<Instant>,
|
||||
last_temp: Mutex<f32>,
|
||||
last_fan: Mutex<u32>,
|
||||
}
|
||||
|
||||
impl DellXps9380Sal {
|
||||
pub fn init() -> Result<Self> {
|
||||
let mut temp_path = None;
|
||||
let mut pwr_path = None;
|
||||
let mut fan_path = None;
|
||||
let mut rapl_base_path = None;
|
||||
|
||||
// Dynamic hwmon discovery
|
||||
if let Ok(entries) = fs::read_dir("/sys/class/hwmon") {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
let name = fs::read_to_string(p.join("name")).unwrap_or_default().trim().to_string();
|
||||
|
||||
if name == "dell_smm" {
|
||||
temp_path = Some(p.join("temp1_input"));
|
||||
fan_path = Some(p.join("fan1_input"));
|
||||
}
|
||||
|
||||
if name == "intel_rapl" || name == "rapl" {
|
||||
pwr_path = Some(p.join("power1_average"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discovery for RAPL via powercap
|
||||
if let Ok(entries) = fs::read_dir("/sys/class/powercap") {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if let Ok(name) = fs::read_to_string(p.join("name")) {
|
||||
if name.trim() == "package-0" {
|
||||
rapl_base_path = Some(p.clone());
|
||||
if pwr_path.is_none() {
|
||||
pwr_path = Some(p.join("energy_uj"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rapl_base = rapl_base_path.context("Could not find RAPL package-0 path in powercap")?;
|
||||
|
||||
Ok(Self {
|
||||
temp_path: temp_path.context("Could not find dell_smm temperature path")?,
|
||||
pwr_path: pwr_path.context("Could not find RAPL power path")?,
|
||||
fan_path: fan_path.context("Could not find dell_smm fan path")?,
|
||||
pl1_path: rapl_base.join("constraint_0_power_limit_uw"),
|
||||
pl2_path: rapl_base.join("constraint_1_power_limit_uw"),
|
||||
last_poll: Mutex::new(Instant::now() - Duration::from_secs(2)),
|
||||
last_temp: Mutex::new(0.0),
|
||||
last_fan: Mutex::new(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PreflightAuditor for DellXps9380Sal {
|
||||
fn audit(&self) -> Box<dyn Iterator<Item = AuditStep> + '_> {
|
||||
let mut steps = Vec::new();
|
||||
|
||||
// 1. Root check
|
||||
steps.push(AuditStep {
|
||||
description: "Root Privileges".to_string(),
|
||||
outcome: if unsafe { libc::getuid() } == 0 { Ok(()) } else { Err(AuditError::RootRequired) }
|
||||
});
|
||||
|
||||
// 2. Kernel parameters check
|
||||
let cmdline = fs::read_to_string("/proc/cmdline").unwrap_or_default();
|
||||
steps.push(AuditStep {
|
||||
description: "Kernel Param: dell_smm_hwmon.ignore_dmi=1".to_string(),
|
||||
outcome: if cmdline.contains("dell_smm_hwmon.ignore_dmi=1") { Ok(()) } else {
|
||||
Err(AuditError::MissingKernelParam("dell_smm_hwmon.ignore_dmi=1".to_string()))
|
||||
}
|
||||
});
|
||||
steps.push(AuditStep {
|
||||
description: "Kernel Param: msr.allow_writes=on".to_string(),
|
||||
outcome: if cmdline.contains("msr.allow_writes=on") { Ok(()) } else {
|
||||
Err(AuditError::MissingKernelParam("msr.allow_writes=on".to_string()))
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Check AC power
|
||||
let ac_status = fs::read_to_string("/sys/class/power_supply/AC/online").unwrap_or_else(|_| "0".to_string());
|
||||
steps.push(AuditStep {
|
||||
description: "AC Power Connection".to_string(),
|
||||
outcome: if ac_status.trim() == "1" { Ok(()) } else {
|
||||
Err(AuditError::AcPowerMissing("System must be on AC power for benchmarking".to_string()))
|
||||
}
|
||||
});
|
||||
|
||||
Box::new(steps.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DellXps9380Guard {
|
||||
stopped_services: Vec<String>,
|
||||
}
|
||||
|
||||
impl DellXps9380Guard {
|
||||
pub fn new() -> Self {
|
||||
Self { stopped_services: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl EnvironmentGuard for DellXps9380Guard {
|
||||
fn suppress(&mut self) -> Result<()> {
|
||||
let services = ["tlp", "thermald"];
|
||||
for s in services {
|
||||
if Command::new("systemctl").args(["is-active", "--quiet", s]).status()?.success() {
|
||||
Command::new("systemctl").args(["stop", s]).status()?;
|
||||
self.stopped_services.push(s.to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore(&mut self) -> Result<()> {
|
||||
for s in &self.stopped_services {
|
||||
let _ = Command::new("systemctl").args(["start", s]).status();
|
||||
}
|
||||
self.stopped_services.clear();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DellXps9380Guard {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.restore();
|
||||
}
|
||||
}
|
||||
|
||||
impl SensorBus for DellXps9380Sal {
|
||||
fn get_temp(&self) -> Result<f32> {
|
||||
// Enforce 1000ms rate limit for Dell SMM as per GEMINI.md
|
||||
let mut last_poll = self.last_poll.lock().unwrap();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(*last_poll) < Duration::from_millis(1000) {
|
||||
return Ok(*self.last_temp.lock().unwrap());
|
||||
}
|
||||
|
||||
let s = fs::read_to_string(&self.temp_path)?;
|
||||
let val = s.trim().parse::<f32>()? / 1000.0;
|
||||
|
||||
*self.last_temp.lock().unwrap() = val;
|
||||
*last_poll = now;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn get_power_w(&self) -> Result<f32> {
|
||||
if self.pwr_path.to_string_lossy().contains("energy_uj") {
|
||||
let e1 = fs::read_to_string(&self.pwr_path)?.trim().parse::<u64>()?;
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let e2 = fs::read_to_string(&self.pwr_path)?.trim().parse::<u64>()?;
|
||||
Ok((e2.saturating_sub(e1)) as f32 / 100000.0)
|
||||
} else {
|
||||
let s = fs::read_to_string(&self.pwr_path)?;
|
||||
Ok(s.trim().parse::<f32>()? / 1000000.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fan_rpm(&self) -> Result<u32> {
|
||||
let mut last_poll = self.last_poll.lock().unwrap();
|
||||
let now = Instant::now();
|
||||
|
||||
if now.duration_since(*last_poll) < Duration::from_millis(1000) {
|
||||
return Ok(*self.last_fan.lock().unwrap());
|
||||
}
|
||||
|
||||
let s = fs::read_to_string(&self.fan_path)?;
|
||||
let val = s.trim().parse::<u32>()?;
|
||||
|
||||
*self.last_fan.lock().unwrap() = val;
|
||||
*last_poll = now;
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActuatorBus for DellXps9380Sal {
|
||||
fn set_fan_mode(&self, mode: &str) -> Result<()> {
|
||||
match mode {
|
||||
"max" | "Manual" => {
|
||||
Command::new("dell-bios-fan-control").arg("0").status()?;
|
||||
}
|
||||
"auto" | "Auto" => {
|
||||
Command::new("dell-bios-fan-control").arg("1").status()?;
|
||||
}
|
||||
_ => {
|
||||
debug!("Unknown fan mode requested: {}", mode);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_sustained_power_limit(&self, watts: f32) -> Result<()> {
|
||||
let uw = (watts * 1_000_000.0) as u64;
|
||||
fs::write(&self.pl1_path, uw.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_burst_power_limit(&self, watts: f32) -> Result<()> {
|
||||
let uw = (watts * 1_000_000.0) as u64;
|
||||
fs::write(&self.pl2_path, uw.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareWatchdog for DellXps9380Sal {
|
||||
fn check_emergency(&self) -> Result<bool> {
|
||||
// Check for thermal throttling or BD PROCHOT
|
||||
// Simplified for now
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user