release/1.1.0 #1

Merged
nvrl merged 6 commits from release/1.1.0 into main 2026-02-26 14:25:29 +01:00
5 changed files with 187 additions and 7 deletions
Showing only changes of commit 48c3b46a0c - Show all commits

View File

@@ -0,0 +1,41 @@
pub struct I8kmonConfig {
pub t_ambient: f32,
pub t_max_fan: f32,
}
pub struct I8kmonTranslator;
impl I8kmonTranslator {
pub fn generate_conf(config: &I8kmonConfig) -> String {
let t_off = config.t_ambient + 5.0;
let t_low_on = config.t_ambient + 12.0;
let t_low_off = config.t_ambient + 10.0;
let t_high_on = config.t_max_fan;
let t_high_off = config.t_max_fan - 5.0;
let t_low_trigger = (config.t_max_fan - 15.0).max(t_low_on + 2.0);
format!(
r#"# Generated by ember-tune Optimizer
# Grounded in physical thermal resistance
set config(gen_shadow) 1
set config(i8k_ignore_dmi) 1
# Fan states: {{state_low state_high temp_on temp_off}}
set config(0) {{0 0 {t_low_on:.0} {t_off:.0}}}
set config(1) {{1 1 {t_low_trigger:.0} {t_low_off:.0}}}
set config(2) {{2 2 {t_high_on:.0} {t_high_off:.0}}}
# Speed thresholds (approximate for XPS 9380)
set config(speed_low) 2500
set config(speed_high) 4500
"#,
t_low_on = t_low_on,
t_off = t_off,
t_low_trigger = t_low_trigger,
t_low_off = t_low_off,
t_high_on = t_high_on,
t_high_off = t_high_off
)
}
}

View File

@@ -1 +1,2 @@
pub mod throttled; pub mod throttled;
pub mod i8kmon;

View File

@@ -1,3 +1,4 @@
use std::collections::HashSet;
pub struct ThrottledConfig { pub struct ThrottledConfig {
pub pl1_limit: f32, pub pl1_limit: f32,
@@ -11,7 +12,7 @@ impl ThrottledTranslator {
pub fn generate_conf(config: &ThrottledConfig) -> String { pub fn generate_conf(config: &ThrottledConfig) -> String {
format!( format!(
r#"[GENERAL] r#"[GENERAL]
# Generated by FerroTherm Optimizer # Generated by ember-tune Optimizer
# Physical Sweet Spot found at {pl1:.1}W # Physical Sweet Spot found at {pl1:.1}W
[BATTERY] [BATTERY]
@@ -35,4 +36,98 @@ Trip_Temp_C: {trip:.0}
trip = config.trip_temp trip = config.trip_temp
) )
} }
/// Merges benchmarked values into an existing throttled.conf content.
/// Preserves all other sections (like [UnderVOLT]), comments, and formatting.
pub fn merge_conf(existing_content: &str, config: &ThrottledConfig) -> String {
let mut sections = Vec::new();
let mut current_section_name = String::new();
let mut current_section_lines = Vec::new();
// 1. Parse into sections to ensure we only update keys in [BATTERY] and [AC]
for line in existing_content.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
if !current_section_lines.is_empty() || !current_section_name.is_empty() {
sections.push((current_section_name.clone(), current_section_lines.clone()));
}
current_section_name = trimmed[1..trimmed.len() - 1].to_string();
current_section_lines = vec![line.to_string()];
} else {
current_section_lines.push(line.to_string());
}
}
sections.push((current_section_name, current_section_lines));
let target_keys = [
("PL1_Tdp_W", format!("{:.0}", config.pl1_limit)),
("PL2_Tdp_W", format!("{:.0}", config.pl2_limit)),
("Trip_Temp_C", format!("{:.0}", config.trip_temp)),
];
let mut result_lines = Vec::new();
let mut handled_sections = HashSet::new();
// 2. Process sections
for (name, mut lines) in sections {
if name == "BATTERY" || name == "AC" {
handled_sections.insert(name.clone());
let mut updated_keys = HashSet::new();
let mut new_lines = Vec::new();
for line in lines {
let mut updated = false;
let trimmed = line.trim();
if !trimmed.starts_with('#') && !trimmed.is_empty() {
if let Some((key, _)) = trimmed.split_once(':') {
let key = key.trim();
for (target_key, new_value) in &target_keys {
if key == *target_key {
if let Some(colon_idx) = line.find(':') {
let prefix = &line[..colon_idx + 1];
let rest = &line[colon_idx + 1..];
let comment = if let Some(hash_idx) = rest.find('#') {
&rest[hash_idx..]
} else {
""
};
new_lines.push(format!("{} {}{}", prefix, new_value, comment));
updated_keys.insert(*target_key);
updated = true;
break;
}
}
}
}
}
if !updated {
new_lines.push(line);
}
}
for (target_key, new_value) in &target_keys {
if !updated_keys.contains(*target_key) {
new_lines.push(format!("{}: {}", target_key, new_value));
}
}
lines = new_lines;
}
result_lines.extend(lines);
}
// 3. Add missing sections if they didn't exist at all
for section_name in &["BATTERY", "AC"] {
if !handled_sections.contains(*section_name) {
result_lines.push(String::new());
result_lines.push(format!("[{}]", section_name));
for (target_key, new_value) in &target_keys {
result_lines.push(format!("{}: {}", target_key, new_value));
}
}
}
result_lines.join("\n")
}
} }

View File

@@ -182,7 +182,7 @@ impl BenchmarkOrchestrator {
// Phase 5: Finalizing // Phase 5: Finalizing
self.phase = BenchmarkPhase::Finalizing; self.phase = BenchmarkPhase::Finalizing;
self.log("Benchmark sequence complete. Generating configuration...")?; self.log("Benchmark sequence complete. Generating configurations...")?;
let config = crate::engine::formatters::throttled::ThrottledConfig { let config = crate::engine::formatters::throttled::ThrottledConfig {
pl1_limit: res.silicon_knee_watts, pl1_limit: res.silicon_knee_watts,
@@ -190,9 +190,25 @@ impl BenchmarkOrchestrator {
trip_temp: res.max_temp_c.max(95.0), trip_temp: res.max_temp_c.max(95.0),
}; };
let conf_content = crate::engine::formatters::throttled::ThrottledTranslator::generate_conf(&config); // 1. Throttled (Merged if exists)
std::fs::write("throttled.conf", conf_content)?; let throttled_path = "throttled.conf";
self.log("✓ Saved 'throttled.conf'.")?; let existing_throttled = std::fs::read_to_string(throttled_path).unwrap_or_default();
let throttled_content = if existing_throttled.is_empty() {
crate::engine::formatters::throttled::ThrottledTranslator::generate_conf(&config)
} else {
crate::engine::formatters::throttled::ThrottledTranslator::merge_conf(&existing_throttled, &config)
};
std::fs::write(throttled_path, throttled_content)?;
self.log("✓ Saved 'throttled.conf' (merged).")?;
// 2. i8kmon
let i8k_config = crate::engine::formatters::i8kmon::I8kmonConfig {
t_ambient: self.profile.ambient_temp,
t_max_fan: res.max_temp_c - 5.0, // Aim to hit max fan before max temp
};
let i8k_content = crate::engine::formatters::i8kmon::I8kmonTranslator::generate_conf(&i8k_config);
std::fs::write("i8kmon.conf", i8k_content)?;
self.log("✓ Saved 'i8kmon.conf'.")?;
self.guard.restore()?; self.guard.restore()?;
self.log("✓ Environment restored.")?; self.log("✓ Environment restored.")?;

View File

@@ -96,7 +96,19 @@ impl PreflightAuditor for DellXps9380Sal {
outcome: if unsafe { libc::getuid() } == 0 { Ok(()) } else { Err(AuditError::RootRequired) } outcome: if unsafe { libc::getuid() } == 0 { Ok(()) } else { Err(AuditError::RootRequired) }
}); });
// 2. Kernel parameters check // 2. Kernel modules check (simplified check via sysfs/proc)
let modules = ["dell_smm_hwmon", "msr", "intel_rapl_msr"];
for mod_name in modules {
let path = format!("/sys/module/{}", mod_name);
steps.push(AuditStep {
description: format!("Kernel Module: {}", mod_name),
outcome: if PathBuf::from(path).exists() { Ok(()) } else {
Err(AuditError::ToolMissing(format!("Module '{}' not loaded. Run 'sudo modprobe {}'", mod_name, mod_name)))
}
});
}
// 3. Kernel parameters check
let cmdline = fs::read_to_string("/proc/cmdline").unwrap_or_default(); let cmdline = fs::read_to_string("/proc/cmdline").unwrap_or_default();
steps.push(AuditStep { steps.push(AuditStep {
description: "Kernel Param: dell_smm_hwmon.ignore_dmi=1".to_string(), description: "Kernel Param: dell_smm_hwmon.ignore_dmi=1".to_string(),
@@ -104,6 +116,12 @@ impl PreflightAuditor for DellXps9380Sal {
Err(AuditError::MissingKernelParam("dell_smm_hwmon.ignore_dmi=1".to_string())) Err(AuditError::MissingKernelParam("dell_smm_hwmon.ignore_dmi=1".to_string()))
} }
}); });
steps.push(AuditStep {
description: "Kernel Param: dell_smm_hwmon.restricted=0".to_string(),
outcome: if cmdline.contains("dell_smm_hwmon.restricted=0") { Ok(()) } else {
Err(AuditError::MissingKernelParam("dell_smm_hwmon.restricted=0".to_string()))
}
});
steps.push(AuditStep { steps.push(AuditStep {
description: "Kernel Param: msr.allow_writes=on".to_string(), description: "Kernel Param: msr.allow_writes=on".to_string(),
outcome: if cmdline.contains("msr.allow_writes=on") { Ok(()) } else { outcome: if cmdline.contains("msr.allow_writes=on") { Ok(()) } else {
@@ -111,7 +129,16 @@ impl PreflightAuditor for DellXps9380Sal {
} }
}); });
// 3. Check AC power // 4. Lockdown check
let lockdown = fs::read_to_string("/sys/kernel/security/lockdown").unwrap_or_default();
steps.push(AuditStep {
description: "Kernel Lockdown Status".to_string(),
outcome: if lockdown.contains("[none]") || lockdown.is_empty() { Ok(()) } else {
Err(AuditError::KernelIncompatible("Kernel is in lockdown mode. Set to 'none' to allow MSR/SMM writes.".to_string()))
}
});
// 5. Check AC power
let ac_status = fs::read_to_string("/sys/class/power_supply/AC/online").unwrap_or_else(|_| "0".to_string()); let ac_status = fs::read_to_string("/sys/class/power_supply/AC/online").unwrap_or_else(|_| "0".to_string());
steps.push(AuditStep { steps.push(AuditStep {
description: "AC Power Connection".to_string(), description: "AC Power Connection".to_string(),