intel: audio/ELD, RPS/RC6, GPU reset — final subsystems

audio_eld.rs: EDID-Like Data + N/CTS computation
  EldData parser from CEA-861 EDID extension block
  HDMI OUI (0x000C03) Short Audio Descriptor extraction
  Speaker allocation string mapping (stereo → 7.1)
  compute_n_cts for 7 sample rates (32/44.1/48/88.2/96/176.4/192 kHz)

rps_rc6.rs: full Render Power State + RC6 management
  Gen9 freq table: 20 entries (100-1050 MHz)
  Gen12 freq table: 17 entries (100-1500 MHz)
  RPS up/down with evaluation interval + timeout gating
  RC6 enable/disable with HW-managed transitions
  Interrupt limits + up/down threshold programming

gpu_reset.rs: per-engine + global GPU reset recovery
  reset_engine with RESET_CTL request → ready → clear sequence
  reset_render convenience wrapper
  reset_gpu with GEN6_GDRST full domain register
  Per-domain force_reset for render/media/blitter/vecs/GUC
  Reset count + recovery success counter tracking
This commit is contained in:
2026-06-02 06:23:41 +03:00
parent a52ffc5ac6
commit 5fe927e506
3 changed files with 400 additions and 0 deletions
@@ -0,0 +1,114 @@
use std::collections::HashMap;
pub struct EldData {
pub eld: [u8; 128],
pub eld_size: usize,
pub speaker_allocation: u8,
pub sad_count: u8,
pub baseline_len: u8,
}
impl EldData {
pub fn new(edid: &[u8]) -> Self {
let mut eld = [0u8; 128];
let mut size = 0usize;
if edid.len() >= 256 {
let mut offset = 128u16;
while offset < (edid.len() as u16).min(256) && offset + 3 <= edid.len() as u16 {
let tag = edid[offset as usize] >> 5;
let len = (edid[offset as usize] & 0x1F) as u16;
if tag == 0x02 {
let data = &edid[offset as usize + 1..];
let mut dtd_offset = 0u16;
while dtd_offset < len {
let dtd_tag = data[dtd_offset as usize] >> 5;
let dtd_len = (data[dtd_offset as usize] & 0x1F) as u16;
if dtd_tag == 0x07 && dtd_len >= 3 {
let oui = ((data[dtd_offset as usize + 1] as u32) << 16)
| ((data[dtd_offset as usize + 2] as u32) << 8)
| (data[dtd_offset as usize + 3] as u32);
if oui == 0x000C03 {
let sad_start = dtd_offset + 4;
let sad_end = (sad_start + dtd_len - 3).min(128);
let copy_len = sad_end - sad_start;
eld[..copy_len].copy_from_slice(
&data[sad_start as usize..sad_start as usize + copy_len]);
size = copy_len;
}
}
dtd_offset += dtd_len + 1;
}
break;
}
offset += len + 1;
}
}
Self {
eld, eld_size: size,
speaker_allocation: 0x01,
sad_count: 0,
baseline_len: 8,
}
}
pub fn speaker_allocation_str(&self) -> &str {
match self.speaker_allocation {
0x01 => "FL/FR",
0x02 => "LFE",
0x04 => "FC",
0x09 => "FL/FR + LFE",
0x0B => "FL/FR + LFE + FC",
0x13 => "FL/FR + LFE + FC + SL/SR",
0x1F => "FL/FR + LFE + FC + SL/SR + RLC/RRC",
_ => "Stereo",
}
}
pub fn compute_n_cts(pixel_clock: u32, sample_rate: u32) -> (u32, u32) {
match sample_rate {
32000 => {
let n = 4096u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
44100 => {
let n = 6272u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
48000 => {
let n = 6144u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
88200 => {
let n = 12544u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
96000 => {
let n = 12288u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
176400 => {
let n = 25088u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
192000 => {
let n = 24576u32;
let cts = pixel_clock * n / (128 * sample_rate);
(n, cts)
}
_ => (6144, pixel_clock * 6144 / (128 * 48000)),
}
}
pub fn is_valid(&self) -> bool {
self.eld_size > 0 && self.eld[0] != 0
}
}
@@ -0,0 +1,123 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, error, info, warn};
use redox_driver_sys::memory::MmioRegion;
use crate::driver::{DriverError, Result};
const GEN6_GDRST: usize = 0x941C;
const GEN6_GRDOM_RENDER: u32 = 1 << 1;
const GEN6_GRDOM_MEDIA: u32 = 1 << 2;
const GEN6_GRDOM_BLT: u32 = 1 << 3;
const GEN6_GRDOM_VECS: u32 = 1 << 4;
const GEN6_GRDOM_FULL: u32 = 1 << 0;
const GEN6_GRDOM_GUC: u32 = 1 << 5;
const RING_RESET_CTL: usize = 0xD0;
const RESET_CTL_REQUEST_RESET: u32 = 1 << 0;
const RESET_CTL_READY_TO_RESET: u32 = 1 << 1;
const RESET_TIMEOUT_MS: u64 = 50;
const RECOVERY_TIMEOUT_MS: u64 = 200;
pub struct GpuResetManager {
mmio: Arc<MmioRegion>,
render_ring_base: usize,
reset_count: u32,
recovery_successful: u32,
}
impl GpuResetManager {
pub fn new(mmio: Arc<MmioRegion>, render_ring_base: usize) -> Self {
Self {
mmio, render_ring_base,
reset_count: 0,
recovery_successful: 0,
}
}
pub fn reset_engine(&mut self, ring_base: usize) -> Result<()> {
self.reset_count += 1;
info!("redox-drm-intel: engine reset requested for ring {:#06x} (attempt {})",
ring_base, self.reset_count);
let reset_ctl = ring_base + RING_RESET_CTL;
self.mmio.write32(reset_ctl, RESET_CTL_REQUEST_RESET);
let deadline = Instant::now() + Duration::from_millis(RESET_TIMEOUT_MS);
loop {
let status = self.mmio.read32(reset_ctl);
if status & RESET_CTL_READY_TO_RESET != 0 { break; }
if Instant::now() > deadline {
error!("redox-drm-intel: engine reset timeout for ring {:#06x}", ring_base);
return Err(DriverError::Initialization("engine reset timeout"));
}
std::hint::spin_loop();
}
self.mmio.write32(reset_ctl, 0);
let deadline = Instant::now() + Duration::from_millis(RECOVERY_TIMEOUT_MS);
loop {
let status = self.mmio.read32(reset_ctl);
if status & RESET_CTL_READY_TO_RESET == 0 { break; }
if Instant::now() > deadline {
warn!("redox-drm-intel: engine reset recovery timeout for ring {:#06x}", ring_base);
return Ok(());
}
std::hint::spin_loop();
}
self.recovery_successful += 1;
info!("redox-drm-intel: engine reset completed for ring {:#06x}", ring_base);
Ok(())
}
pub fn reset_render(&mut self) -> Result<()> {
self.reset_engine(self.render_ring_base)
}
pub fn reset_gpu(&mut self) -> Result<()> {
self.reset_count += 1;
error!("redox-drm-intel: GLOBAL GPU RESET (attempt {})", self.reset_count);
let val = GEN6_GRDOM_FULL | GEN6_GRDOM_RENDER
| GEN6_GRDOM_MEDIA | GEN6_GRDOM_BLT | GEN6_GRDOM_VECS
| GEN6_GRDOM_GUC;
self.mmio.write32(GEN6_GDRST, val);
let deadline = Instant::now() + Duration::from_millis(RECOVERY_TIMEOUT_MS);
loop {
let status = self.mmio.read32(GEN6_GDRST);
if status & val == 0 { break; }
if Instant::now() > deadline {
error!("redox-drm-intel: global GPU reset timeout — status {:#x}", status);
return Err(DriverError::Initialization("global GPU reset timeout"));
}
std::hint::spin_loop();
}
self.recovery_successful += 1;
info!("redox-drm-intel: global GPU reset completed");
Ok(())
}
pub fn force_reset_domain(&mut self, domain: u32) -> Result<()> {
self.mmio.write32(GEN6_GDRST, domain);
let deadline = Instant::now() + Duration::from_millis(RESET_TIMEOUT_MS);
loop {
if self.mmio.read32(GEN6_GDRST) & domain == 0 { break; }
if Instant::now() > deadline {
return Err(DriverError::Initialization("reset domain timeout"));
}
std::hint::spin_loop();
}
Ok(())
}
pub fn reset_count(&self) -> u32 { self.reset_count }
pub fn recovery_count(&self) -> u32 { self.recovery_successful }
}
@@ -0,0 +1,163 @@
use std::sync::Arc;
use std::time::Instant;
use log::{debug, info};
use redox_driver_sys::memory::MmioRegion;
use super::info::IntelDeviceInfo;
use crate::driver::Result;
const GEN6_RPNSWREQ: usize = 0xA008;
const GEN6_RP_CONTROL: usize = 0xA024;
const GEN6_RC_CONTROL: usize = 0xA090;
const GEN6_RC_STATE: usize = 0xA094;
const GEN6_RP_INTERRUPT_LIMITS: usize = 0xA014;
const GEN6_RP_DOWN_TIMEOUT: usize = 0xA018;
const GEN6_RP_UP_THRESHOLD: usize = 0xA02C;
const GEN6_RP_DOWN_THRESHOLD: usize = 0xA030;
const GEN6_RP_DOWN_EI: usize = 0xA050;
const GEN6_RP_UP_EI: usize = 0xA054;
const RPNSWREQ_FREQ_MASK: u32 = 0xFF;
const RC_CTL_RC6_ENABLE: u32 = 1 << 0;
const RC_CTL_HW_ENABLE: u32 = 1 << 31;
const RPS_UP_THRESHOLD_DEFAULT: u32 = 85;
const RPS_DOWN_THRESHOLD_DEFAULT: u32 = 65;
const RPS_DOWN_TIMEOUT_MS: u32 = 50;
const RPS_EI_INTERVAL_US: u32 = 1000;
const FREQ_TABLE_GEN9: &[(u32, u32)] = &[
(100, 1), (150, 2), (200, 3), (250, 4), (300, 5),
(350, 6), (400, 7), (450, 8), (500, 9), (550, 10),
(600, 11), (650, 12), (700, 13), (750, 14), (800, 15),
(850, 16), (900, 17), (950, 18), (1000, 19), (1050, 20),
];
const FREQ_TABLE_GEN12: &[(u32, u32)] = &[
(100, 2), (150, 3), (200, 4), (250, 5), (300, 6),
(400, 8), (500, 10), (600, 12), (700, 14), (800, 16),
(900, 18), (1000, 20), (1100, 22), (1200, 24), (1300, 26),
(1400, 28), (1500, 30),
];
pub struct RpsRc6Manager {
mmio: Arc<MmioRegion>,
device_info: IntelDeviceInfo,
rps_enabled: bool,
rc6_enabled: bool,
current_freq_idx: usize,
min_freq_idx: usize,
max_freq_idx: usize,
last_up_eval: Option<Instant>,
last_down_eval: Option<Instant>,
up_threshold: u32,
down_threshold: u32,
}
impl RpsRc6Manager {
pub fn new(mmio: Arc<MmioRegion>, device_info: &IntelDeviceInfo) -> Self {
Self {
mmio, device_info: device_info.clone(),
rps_enabled: false, rc6_enabled: false,
current_freq_idx: 0, min_freq_idx: 0, max_freq_idx: 0,
last_up_eval: None, last_down_eval: None,
up_threshold: RPS_UP_THRESHOLD_DEFAULT,
down_threshold: RPS_DOWN_THRESHOLD_DEFAULT,
}
}
pub fn init(&mut self) -> Result<()> {
let table = self.freq_table();
self.min_freq_idx = 0;
self.max_freq_idx = table.len() - 1;
self.current_freq_idx = self.max_freq_idx;
self.set_frequency(table[self.current_freq_idx].1)?;
self.rps_enabled = true;
self.mmio.write32(GEN6_RP_INTERRUPT_LIMITS,
(table[self.max_freq_idx].1 << 24) | (table[self.min_freq_idx].1 << 16));
self.mmio.write32(GEN6_RP_DOWN_TIMEOUT, RPS_DOWN_TIMEOUT_MS);
self.mmio.write32(GEN6_RP_UP_THRESHOLD, self.up_threshold);
self.mmio.write32(GEN6_RP_DOWN_THRESHOLD, self.down_threshold);
self.mmio.write32(GEN6_RP_DOWN_EI, RPS_EI_INTERVAL_US);
self.mmio.write32(GEN6_RP_UP_EI, RPS_EI_INTERVAL_US);
info!("redox-drm-intel: RPS initialized ({} MHz, range {}-{} MHz)",
table[self.current_freq_idx].0,
table[self.min_freq_idx].0,
table[self.max_freq_idx].0);
Ok(())
}
pub fn enable_rc6(&mut self) -> Result<()> {
let mut ctl = self.mmio.read32(GEN6_RC_CONTROL);
ctl |= RC_CTL_RC6_ENABLE | RC_CTL_HW_ENABLE;
self.mmio.write32(GEN6_RC_CONTROL, ctl);
self.rc6_enabled = true;
info!("redox-drm-intel: RC6 enabled (HW-managed)");
Ok(())
}
pub fn disable_rc6(&mut self) -> Result<()> {
let mut ctl = self.mmio.read32(GEN6_RC_CONTROL);
ctl &= !(RC_CTL_RC6_ENABLE | RC_CTL_HW_ENABLE);
self.mmio.write32(GEN6_RC_CONTROL, ctl);
self.rc6_enabled = false;
Ok(())
}
pub fn rps_up(&mut self) -> Result<()> {
if !self.rps_enabled { return Ok(()); }
if self.current_freq_idx >= self.max_freq_idx { return Ok(()); }
let now = Instant::now();
if let Some(last) = self.last_up_eval {
if now.duration_since(last).as_micros() < RPS_EI_INTERVAL_US as u128 { return Ok(()); }
}
self.current_freq_idx += 1;
let table = self.freq_table();
self.set_frequency(table[self.current_freq_idx].1)?;
self.last_up_eval = Some(now);
debug!("redox-drm-intel: RPS ↑ {} MHz", table[self.current_freq_idx].0);
Ok(())
}
pub fn rps_down(&mut self) -> Result<()> {
if !self.rps_enabled { return Ok(()); }
if self.current_freq_idx <= self.min_freq_idx { return Ok(()); }
let now = Instant::now();
if let Some(last) = self.last_down_eval {
if now.duration_since(last).as_millis() < RPS_DOWN_TIMEOUT_MS as u128 { return Ok(()); }
}
self.current_freq_idx -= 1;
let table = self.freq_table();
self.set_frequency(table[self.current_freq_idx].1)?;
self.last_down_eval = Some(now);
debug!("redox-drm-intel: RPS ↓ {} MHz", table[self.current_freq_idx].0);
Ok(())
}
fn set_frequency(&self, freq_val: u32) -> Result<()> {
let mut rpns = self.mmio.read32(GEN6_RPNSWREQ);
rpns &= !(RPNSWREQ_FREQ_MASK);
rpns |= freq_val & RPNSWREQ_FREQ_MASK;
self.mmio.write32(GEN6_RPNSWREQ, rpns);
Ok(())
}
pub fn current_frequency_mhz(&self) -> u32 {
self.freq_table().get(self.current_freq_idx)
.map(|&(mhz, _)| mhz).unwrap_or(0)
}
fn freq_table(&self) -> &[(u32, u32)] {
if self.device_info.is_gen12_or_later() {
FREQ_TABLE_GEN12
} else {
FREQ_TABLE_GEN9
}
}
pub fn rps_enabled(&self) -> bool { self.rps_enabled }
pub fn rc6_enabled(&self) -> bool { self.rc6_enabled }
}