intel: Phase 5 — runtime power management

- RPS interactive governor: fast ramp-up on activity, slow ramp-down on idle
- Runtime PM with wakeref counting and RC6 transitions
- Forcewake automatically taken on first wakeref, released on last
- Frequency tracking with min/max/target per-GT state
This commit is contained in:
2026-06-01 21:29:43 +03:00
parent e01a4b2dcf
commit 48397c6419
@@ -75,6 +75,12 @@ pub struct IntelGtManager {
forcewake_active: bool,
rc6_enabled: bool,
gt_freq_initialized: bool,
wakeref_count: u32,
rps_idle_count: u32,
last_busy_check: Option<Instant>,
target_freq: u32,
min_freq: u32,
max_freq: u32,
}
impl IntelGtManager {
@@ -85,6 +91,12 @@ impl IntelGtManager {
forcewake_active: false,
rc6_enabled: false,
gt_freq_initialized: false,
wakeref_count: 0,
rps_idle_count: 0,
last_busy_check: None,
target_freq: 0,
min_freq: 0,
max_freq: 0,
}
}
@@ -240,11 +252,13 @@ impl IntelGtManager {
let (min_freq, max_freq) = self.read_gt_freq_limits()?;
if min_freq > 0 && max_freq > 0 {
let target = max_freq;
self.set_gt_frequency(target)?;
self.min_freq = min_freq;
self.max_freq = max_freq;
self.target_freq = max_freq;
self.set_gt_frequency(self.target_freq)?;
info!(
"redox-drm-intel: GT frequency set to {} MHz (min {}, max {})",
target * 50, min_freq * 50, max_freq * 50
self.target_freq * 50, min_freq * 50, max_freq * 50
);
} else {
warn!("redox-drm-intel: GT frequency limits invalid ({}/{})", min_freq, max_freq);
@@ -269,6 +283,81 @@ impl IntelGtManager {
Ok(())
}
pub fn rps_update(&mut self) -> Result<()> {
if !self.gt_freq_initialized || self.max_freq == 0 {
return Ok(());
}
let now = Instant::now();
let idle_duration = self.last_busy_check.map(|t| now.duration_since(t));
let busy = self.wakeref_count > 0 || self.forcewake_active;
if busy {
self.rps_idle_count = 0;
let step = (self.max_freq - self.min_freq).max(1) / 8;
let up = self.target_freq.saturating_add(step).min(self.max_freq);
if up != self.target_freq {
self.target_freq = up;
self.set_gt_frequency(self.target_freq)?;
debug!("redox-drm-intel: RPS ↑ {} MHz (wakeref={})",
self.target_freq * 50, self.wakeref_count);
}
} else if let Some(dur) = idle_duration {
if dur.as_millis() >= 100 {
self.rps_idle_count += 1;
if self.rps_idle_count >= 3 {
let step = (self.max_freq - self.min_freq).max(1) / 4;
let down = self.target_freq.saturating_sub(step).max(self.min_freq);
if down != self.target_freq {
self.target_freq = down;
self.set_gt_frequency(self.target_freq)?;
debug!("redox-drm-intel: RPS ↓ {} MHz (idle {} ticks)",
self.target_freq * 50, self.rps_idle_count);
}
}
}
}
self.last_busy_check = Some(now);
Ok(())
}
pub fn runtime_get(&mut self) -> Result<()> {
if self.wakeref_count == 0 {
self.take_forcewake()?;
if self.rc6_enabled {
self.disable_rc6()?;
}
}
self.wakeref_count = self.wakeref_count.saturating_add(1);
Ok(())
}
pub fn runtime_put(&mut self) -> Result<()> {
if self.wakeref_count == 0 {
return Ok(());
}
self.wakeref_count = self.wakeref_count.saturating_sub(1);
if self.wakeref_count == 0 {
self.rps_update()?;
if self.forcewake_active {
self.release_forcewake()?;
}
if !self.rc6_enabled {
self.enable_rc6()?;
}
}
Ok(())
}
pub fn wakeref_count(&self) -> u32 {
self.wakeref_count
}
pub fn rps_target_freq_mhz(&self) -> u32 {
self.target_freq * 50
}
fn init_cache_config(&mut self) -> Result<()> {
debug!("redox-drm-intel: configuring L3 cache");