intel: LSPCON bridge + PPGTT context documentation

lspcon.rs (110 lines): HDMI 1.4→2.0 protocol converter
  Parade Technologies + MegaChips vendor OUI detection
  LS/PCON/FRL mode selection via DP AUX
  Mode change with polling timeout
  Appears as DP-to-HDMI bridge on DDI port

context.rs: module header documentation
  PPGTT 4-level page table architecture (PDP→PD→PT→PTE)
  512 entries per level × 4KB pages
  Context manager BTreeMap + LRC descriptor lifecycle

Ported from Linux 7.1:
  intel_lspcon.c → Lspcon

Intel driver: 95 files, 0 errors — 24 spec-commented files
This commit is contained in:
2026-06-02 11:32:56 +03:00
parent 913a23a7f8
commit 8080e983de
2 changed files with 117 additions and 0 deletions
@@ -5,6 +5,21 @@ use log::{debug, info};
use super::gtt::IntelGtt;
use crate::driver::{ContextHandle, DriverError, Result};
// ── Intel PPGTT + GPU Context Manager ───────────────────────────────────
// PPGTT (Per-Process Graphics Translation Tables) provide virtual address
// spaces for GPU contexts. Gen8+ uses 4-level page tables (PDP→PD→PT→PTE)
// with 512 entries per level. Each entry maps a 4KB page.
//
// Context manager maintains a BTreeMap of IntelContext objects keyed by
// ContextHandle. Each context owns an IntelPpgtt with its own page tables
// allocated from the GGTT aperture. Context creation allocates an LRC
// (Logical Ring Context) descriptor for GPU state save/restore.
//
// Page table levels:
// PDP (Page Directory Pointer): top level, 512 entries → 1GB each
// PD (Page Directory): 512 entries → 2MB each
// PT (Page Table): 512 entries → 4KB each
const PAGE_SIZE: u64 = 4096;
const PPGTT_PTE_PRESENT: u64 = 1 << 0;
const PPGTT_PTE_RW: u64 = 1 << 1;
@@ -0,0 +1,102 @@
use std::sync::Arc;
use std::time::{Duration, Instant};
use log::{debug, info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::dp_aux::DpAux;
use crate::driver::{DriverError, Result};
// ── LSPCON — Level Shifter/Protocol Converter ─────────────────────────────
// LSPCON converts HDMI 1.4 to HDMI 2.0 by adding TMDS scrambling and
// increasing the TMDS clock from 3.4 Gbps to 6.0 Gbps. It appears as a
// DP-to-HDMI bridge on the DDI port and communicates via DP AUX.
//
// LSPCON modes:
// LS mode (Level Shifter): passes through HDMI 1.4 TMDS unchanged
// PCON mode (Protocol Converter): adds HDMI 2.0 TMDS scrambling
// FRL mode: HDMI 2.1 FRL passthrough (LSPCON 2.0+)
//
// Detection: read LSPCON vendor OUI via DP AUX at I2C address 0x80.
// Parade Technologies OUI: 0x0011CF
// MegaChips OUI: 0x008067
const LSPCON_I2C_ADDR: u8 = 0x80;
const LSPCON_VENDOR_OUI_OFFSET: u32 = 0x0050;
const LSPCON_MODE_OFFSET: u32 = 0x0052;
const LSPCON_MODE_LS: u8 = 0;
const LSPCON_MODE_PCON: u8 = 1;
const LSPCON_MODE_FRL: u8 = 2;
const PARADE_OUI: [u8; 3] = [0xCF, 0x11, 0x00];
const MEGACHIPS_OUI: [u8; 3] = [0x67, 0x80, 0x00];
const LSPCON_TIMEOUT_MS: u64 = 500;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LspconMode { LevelShifter, ProtocolConverter, Frl }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LspconVendor { Parade, MegaChips, Unknown }
pub struct Lspcon {
detected: bool,
vendor: LspconVendor,
mode: LspconMode,
port: u8,
}
impl Lspcon {
pub fn new(port: u8) -> Self {
Self { detected: false, vendor: LspconVendor::Unknown, mode: LspconMode::LevelShifter, port }
}
pub fn detect(&mut self, aux: &DpAux) -> Result<bool> {
match aux.read_dpcd(LSPCON_VENDOR_OUI_OFFSET, 4) {
Ok(data) if data.len() >= 3 => {
if &data[0..3] == &PARADE_OUI {
self.vendor = LspconVendor::Parade;
self.detected = true;
info!("redox-drm-intel: LSPCON Parade Technologies detected on port {}", self.port);
} else if &data[0..3] == &MEGACHIPS_OUI {
self.vendor = LspconVendor::MegaChips;
self.detected = true;
info!("redox-drm-intel: LSPCON MegaChips detected on port {}", self.port);
}
}
_ => {}
}
Ok(self.detected)
}
pub fn set_mode(&mut self, aux: &DpAux, mode: LspconMode) -> Result<()> {
if !self.detected { return Ok(()); }
let mode_val = match mode {
LspconMode::LevelShifter => LSPCON_MODE_LS,
LspconMode::ProtocolConverter => LSPCON_MODE_PCON,
LspconMode::Frl => LSPCON_MODE_FRL,
};
aux.write_dpcd(LSPCON_MODE_OFFSET, &[mode_val])?;
let deadline = Instant::now() + Duration::from_millis(LSPCON_TIMEOUT_MS);
loop {
if let Ok(data) = aux.read_dpcd(LSPCON_MODE_OFFSET, 1) {
if !data.is_empty() && data[0] == mode_val {
self.mode = mode;
info!("redox-drm-intel: LSPCON mode → {:?}", mode);
return Ok(());
}
}
if Instant::now() > deadline {
warn!("redox-drm-intel: LSPCON mode change timeout");
return Err(DriverError::Initialization("LSPCON mode change timeout"));
}
std::hint::spin_loop();
}
}
pub fn is_detected(&self) -> bool { self.detected }
pub fn vendor(&self) -> LspconVendor { self.vendor }
pub fn mode(&self) -> LspconMode { self.mode }
}