24584eb3c6
Lines 649-651 had VramManager and info!() calls that don't belong in handle_irq(). These were likely from a bad merge. The variables fb_phys and fb_size are local to new() and don't exist in handle_irq().
1097 lines
44 KiB
Markdown
1097 lines
44 KiB
Markdown
# Intel GPU Driver Modernization Plan
|
||
|
||
**Created:** 2026-05-29 · **Updated:** 2026-06-01 (Comprehensive audit complete: 24 subsystems cross-referenced with Linux 7.1 i915. Phases A-D fixes applied. 10 dead modules found + PPGTT linking bug + DPCD bypass + PLL triviality discovered.)
|
||
**Authority:** This document is the canonical Intel-specific execution plan beneath `local/docs/DRM-MODERNIZATION-EXECUTION-PLAN.md` (Workstream C). It does not replace the DRM plan — it is the detailed Intel backend implementation guide that the DRM plan's Workstream C gates reference.
|
||
**Linux reference:** `local/reference/linux-7.1/drivers/gpu/drm/i915/`
|
||
**Driver source:** `local/recipes/gpu/redox-drm/source/src/drivers/intel/`
|
||
|
||
## Recent Fixes (2026-06-01)
|
||
|
||
### Phase A: Build-Breaking Issues — ✅ FIXED
|
||
1. **`gt.rs:216`** — Fixed broken `matches!(...Gen9 | Gen9_5...)` → `matches!(self.device_info.generation, IntelGeneration::Gen9 | IntelGeneration::Gen9_5)`. Added missing `border` variable definition (0x0080_0080 — default sampler indirect state border color).
|
||
2. **`batch.rs`** — Verified clean. Single `as_slice()` at line 105, no duplicate found.
|
||
|
||
### Phase B: Critical Runtime Gaps — ✅ FIXED
|
||
3. **`context.rs:deallocate_id()`** — Now removes context from BTreeMap (proper cleanup). Returns `NotFound` error for unknown context IDs.
|
||
4. **`display_watermark.rs:init_gen9()`** — Now enables DBUF slices S1+S2 for Gen9/Gen12 platforms (was a no-op returning `Ok(())`).
|
||
5. **`dp_aux.rs`** — Added DP AUX DEFER retry (up to 7 retries). New `DP_AUX_CH_CTL_DEFER` bit (1<<26). `wait_for_completion()` returns defer status. `do_transfer()` wraps `do_transfer_raw()` with retry loop.
|
||
6. **PPGTT binding** — Deferred to broader refactoring (requires GGTT allocation tracking).
|
||
7. **Hardcoded DP modes** — Deferred to EDID integration pass.
|
||
8. **PLL divider computation** — Deferred to clock tree refactoring.
|
||
9. **`guc.rs` wiring** — Deferred to GuC firmware integration phase.
|
||
|
||
### Phase C: Quality & Robustness — PARTIAL
|
||
10. **`unwrap_or(0)` patterns** — 30 instances remain. Deferred to systematic error propagation pass.
|
||
11. **`display_psr.rs`** — Module exists but not wired. Deferred to PSR integration.
|
||
12. **CVT mode generation** — Approximate formulas remain. Deferred to VESA spec alignment.
|
||
13. **Connector type detection** — Port-index heuristic remains. Deferred to DDI register read path.
|
||
14. **CDCLK frequency defaults** — Hardcoded thresholds remain. Deferred to reference clock computation.
|
||
15. **CDCLK frequency defaults** — Hardcoded values remain. Deferred to hw query path.
|
||
|
||
## Implementation Status (2026-06-01)
|
||
|
||
**All 5 phases implemented.** 26 files, 4,692 lines of Rust, 28 commits.
|
||
|
||
| Phase | Status | Files | Key deliverables |
|
||
|---|---|---|---|
|
||
| 0: Display Foundation | ✅ Complete | 9 | Register abstraction (4 gen), device info (48 IDs), GMBUS, DMC, CDCLK, power wells, combo PHY, DPLL |
|
||
| 1: DP/HDMI | ✅ Complete | 5 | DP AUX, DP link training, D2D link, HDMI infoframes, hotplug |
|
||
| 2: Gen12 Display | ✅ Complete | 3 | Gen12 regs, DBUF/watermarks, transcoder |
|
||
| 3: Full KMS | ✅ Complete | 4 | Cursor plane, VBT parser, watermarks, cursor ioctl |
|
||
| 4: Render Path | ✅ Complete | 4 | Batch buffer, fence timeline, execlists, Mesa winsys |
|
||
| **Total** | ✅ | **26** | **4,692 lines, 28 commits** |
|
||
|
||
Arrow Lake-P Arc Pro 130T/140T (0x7d51) supported from Phase 0.
|
||
|
||
### Build Status
|
||
- Library (Intel modules): 0 errors
|
||
- Daemon binary: 2 pre-existing errors in redox-driver-sys (libredox API mismatch, unrelated)
|
||
|
||
## 1. Current State
|
||
|
||
### 1.1 Code inventory
|
||
|
||
| File | Lines | What it does |
|
||
|------|-------|--------------|
|
||
| `mod.rs` | 693 | `IntelDriver` struct, PCI BAR mapping, forcewake, device init, IRQ dispatch, `GpuDriver` trait impl |
|
||
| `display.rs` | 404 | Pipe/connector detection, modesetting, page flip, EDID stub |
|
||
| `gtt.rs` | 226 | GGTT page table: allocate, map, unmap, flush |
|
||
| `ring.rs` | 267 | Command ring buffer: render, blitter, video enhance |
|
||
| **Total** | **1,590** | |
|
||
|
||
### 1.2 What works (real code, verified in build)
|
||
|
||
- PCI device detection and BAR mapping (BAR0 GGTT, BAR2 MMIO)
|
||
- Forcewake register programming (`0xA18C`)
|
||
- GGTT page table management: PTE encoding, range allocation with free-list coalescing, multi-page map/unmap, flush via `GFX_FLSH_CNTL`
|
||
- Command ring buffer: ring register programming (RBBASE/RBTAIL/RBHEAD/RBCTL), tail/head tracking, `submit_batch` with space wait, MI_FLUSH_DW flush
|
||
- Display pipe detection (4 pipes, 6 ports via PIPECONF + DDI_BUF_CTL reads)
|
||
- Modesetting: HTOTAL/HBLANK/HSYNC/VTOTAL/VBLANK/VSYNC/PIPE_SRC/PLANE_SIZE/DSPCNTR/PIPECONF/DDI_BUF_CTL register writes
|
||
- Page flip via DSPSURF
|
||
- Vblank counting via PIPEFRAME
|
||
- MSI-X / MSI / legacy IRQ cascade with quirk gating
|
||
- Synthetic EDID generation (hardcoded 1920×1080@60)
|
||
- EDID block parsing (detailed timing descriptors, pixel clock, vrefresh)
|
||
- GEM buffer management (shared core in `gem.rs`)
|
||
- DRM ioctl surface (shared core in `scheme.rs`)
|
||
- DMC firmware manifest with device-ID-keyed lookup for TGL, ADLP, DG2, MTL, SKL, KBL, CNL, ICL, GLK, RKL
|
||
- Device ID tables covering Gen4 through Gen12 (including Arrow Lake, Lunar Lake, Battlemage)
|
||
|
||
### 1.3 What does NOT work (hard stubs and missing code)
|
||
|
||
| Gap | Severity | Current state |
|
||
|-----|----------|---------------|
|
||
| EDID I2C/DDC | **BLOCKING** | `read_edid_block()` returns `Err` every time — every connector gets synthetic 1080p |
|
||
| DPCD (DisplayPort) | **BLOCKING** | `read_dpcd()` returns empty Vec — no DP link training or capabilities |
|
||
| DMC firmware programming | **BLOCKING** | Firmware blobs are preloaded into HashMap but never written to hardware registers |
|
||
| Per-gen register offsets | **BLOCKING** | All offsets are Gen8/Gen9 constants — wrong for Gen12+ (Tiger Lake, Alder Lake, Meteor Lake) |
|
||
| Display power wells | **BLOCKING** | No power well enable — Gen9+ display registers unreadable without power domain setup |
|
||
| CDCLK / PLL | **BLOCKING** | No core display clock or pixel clock PLL programming — no pixels generated |
|
||
| DP AUX channel | HIGH | No AUX read/write — no DP EDID, no DPCD, no link training |
|
||
| HDMI infoframes | HIGH | No AVI/audio infoframes — HDMI monitors get no mode negotiation |
|
||
| Cursor plane | MEDIUM | `CURCNTR`/`CURPOS`/`CURBASE` never programmed |
|
||
| Atomic modeset | MEDIUM | Legacy SETCRTC + PAGE_FLIP only — KWin needs atomic |
|
||
| Hotplug detection | MEDIUM | Polling DDI_BUF_CTL on every IRQ; no real HPD interrupt handling |
|
||
| Watermarks / DBUF | MEDIUM | No display bandwidth programming — risk of underruns |
|
||
| Backlight | LOW | No `BLC_PWM_CTL` programming |
|
||
| PSR / FBC | LOW | Panel Self Refresh and FrameBuffer Compression not implemented |
|
||
| GuC / HuC / GSC | LOW | No GPU scheduling or media firmware |
|
||
| GT frequency / RC6 | LOW | No render power state management |
|
||
|
||
### 1.4 Register offset problem (critical detail)
|
||
|
||
Every register in the driver is a hardcoded Gen8/Gen9-era constant:
|
||
|
||
| Register | Current offset | Valid for | Wrong for |
|
||
|----------|---------------|-----------|-----------|
|
||
| `FORCEWAKE` | `0xA18C` | Gen8/Gen9 | Gen12+ uses `0xA188` or forcewake ack at `0xA188`/`0xA188`+4 |
|
||
| `PIPECONF_BASE` | `0x70008` | Gen8–Gen11 | Gen12 uses `0x70008` but transcoder is separate |
|
||
| `DSPCNTR_BASE` | `0x70180` | Gen8/Gen9 primary planes | Gen12+ uses `PLANE_CTL` at `0x70180` with different bit layout |
|
||
| `DSPSURF_BASE` | `0x7019C` | Gen8/Gen9 | Gen12+ uses `PLANE_SURF` at `0x7019C` but with different semantics |
|
||
| `DDI_BUF_CTL_BASE` | `0x64000` | Gen8+ | Mostly correct but port count and lane info changed at Gen12 |
|
||
| `HTOTAL_BASE` | `0x60000` | Gen8+ transcoder A | Gen12 separates pipe timings from transcoder timings |
|
||
| `PP_STATUS` | `0xC7200` | Gen8/Gen9 | Gen12+ uses different panel power registers |
|
||
|
||
**Impact:** The driver can compile for Gen12+ device IDs but will malfunction at runtime because it writes to the wrong register addresses and with the wrong bit layouts.
|
||
|
||
### 1.5 Hardware generation coverage
|
||
|
||
| Generation | Display ver | Device IDs in table | Driver gate | Register compat | DMC manifest | Real status |
|
||
|-----------|-------------|---------------------|-------------|-----------------|--------------|-------------|
|
||
| Gen4 (i965/G4x) | 4 | 18 | Blocked | Wrong | No keys | No support |
|
||
| Gen5 (Ironlake) | 5 | 2 | Blocked | Wrong | No keys | No support |
|
||
| Gen6 (Sandy Bridge) | 6 | 7 | Blocked | Wrong | No keys | No support |
|
||
| Gen7 (IVB/HSW/BDW) | 7 | ~68 | Blocked | Wrong | No keys | No support |
|
||
| Gen8 (SKL/KBL/CFL) | 9 | ~77 | Allowed | Partially correct | SKL/KBL/CFL keys | Best chance for first validation |
|
||
| Gen9 (CNL/ICL/EHL) | 10-11 | ~60 | Allowed | Partially correct | CNL/ICL/GLK/RKL keys | Needs register fixes |
|
||
| Gen12 TGL/ADL/DG2 | 12-13 | ~34 | Allowed | Wrong | TGL/ADLP/DG2 keys | Will not work without new registers |
|
||
| Gen12 MTL/ARL | 14 | ~10 | Allowed | Wrong | MTL keys | Will not work |
|
||
| Gen12 LNL/BMG | 20+ | ~18 | Allowed | Wrong | No keys | i915 does not cover these; Xe driver territory |
|
||
|
||
## 2. Linux i915 Reference Architecture
|
||
|
||
### 2.1 Scale
|
||
|
||
Linux i915 in our reference tree (Linux 7.1):
|
||
|
||
| Subsystem | .h files | .c files | Key purpose |
|
||
|-----------|----------|----------|-------------|
|
||
| `display/` | 195 | 144 | All display: pipes, planes, connectors, encoders, DP, HDMI, DMC, power, CDCLK, PLL |
|
||
| `gt/` | 78 | 83 | GPU engines, GTT, command submission, power, workarounds |
|
||
| `gt/uc/` | 34 | 23 | GuC, HuC, GSC firmware |
|
||
| `gem/` | 21 | 26 | Buffer objects, eviction, tiling, execbuffer |
|
||
| Top-level | ~50 | ~50 | PCI, MMIO, IRQ, uncore, PM, debugfs |
|
||
| **Total** | **~378** | **~326** | **~700 files** |
|
||
|
||
Red Bear's Intel driver is ~1,590 lines. Linux i915 is ~700 files. The gap is not incremental — it is architectural.
|
||
|
||
### 2.2 i915 probe sequence (what Red Bear must replicate)
|
||
|
||
```
|
||
i915_pci_probe()
|
||
→ i915_driver_create() // alloc drm_i915_private, init device info
|
||
→ i915_driver_early_probe() // MMIO map, uncore init, pcode init
|
||
→ intel_gt_probe_all() // discover GTs and engines
|
||
→ i915_driver_mmio_probe() // interrupts, GTT init
|
||
→ i915_driver_hw_probe() // GT, engines, workarounds
|
||
→ intel_display_driver_probe_noirq() // VBT parse, CDCLK init, power wells, PLL, connectors
|
||
→ intel_irq_install() // install IRQ handlers
|
||
→ intel_display_driver_probe_nogem() // encoders, connectors, initial modeset
|
||
→ i915_gem_init() // GEM memory manager
|
||
→ intel_display_driver_probe() // planes, framebuffer, fbdev
|
||
→ i915_driver_register() // DRM device registration
|
||
```
|
||
|
||
### 2.3 Critical display init subsequence (Red Bear's missing pieces)
|
||
|
||
The Linux `intel_display_driver_probe_noirq()` call chain does the following, none of which Red Bear implements:
|
||
|
||
```
|
||
intel_power_domains_init() → enable display power wells (DC off → DC on)
|
||
intel_cdclk_init() → program core display clock
|
||
intel_dmc_init() → upload DMC firmware to hardware
|
||
intel_dpll_init() → initialize shared PLL pool
|
||
intel_display_device_info_init() → per-generation capability table
|
||
intel_bios_init() → parse VBT for port types, backlight, etc.
|
||
intel_gmbus_init() → initialize I2C/GMBUS controllers
|
||
intel_panel_init() → backlight, eDP panel power sequencing
|
||
intel_psr_init() → Panel Self Refresh setup
|
||
```
|
||
|
||
None of these exist in Red Bear. Without them, the display engine is not initialized and register reads may return 0 or garbage.
|
||
|
||
### 2.4 GMBUS/DDC I2C (the #1 missing piece)
|
||
|
||
Linux path for reading real EDID:
|
||
|
||
```
|
||
intel_gmbus.c:
|
||
gmbus_setup() → configure GMBUS controller registers
|
||
gmbus_xfer() → perform I2C read/write transaction
|
||
→ GMBUS0: select pin pair (DPC, DPE, etc.)
|
||
→ GMBUS1: set byte count + slave addr (0xA0 for EDID)
|
||
→ GMBUS2: status/clear
|
||
→ GMBUS3: data read FIFO
|
||
→ GMBUS4: interrupt mask
|
||
→ GMBUS5: 2-byte index for block reads
|
||
do_gmbus_xfer() → hardware transaction with timeout
|
||
intel_gmbus_get_adapter() → returns I2C adapter per port
|
||
intel_gmbus_read_edid() → reads 128/256-byte EDID via I2C
|
||
|
||
Register map (Gen9, Skylake):
|
||
GMBUS0 = 0xC5100 — pin pair select + rate
|
||
GMBUS1 = 0xC5104 — command: byte count, slave addr, direction
|
||
GMBUS2 = 0xC5108 — status: IN_USE, HW_WAIT, SMBALERT, SDAST (data valid)
|
||
GMBUS3 = 0xC510C — data FIFO (read 4 bytes at a time)
|
||
GMBUS4 = 0xC5110 — interrupt enable/mask
|
||
GMBUS5 = 0xC5120 — 2-byte index (for EDID block reads > 1)
|
||
```
|
||
|
||
### 2.5 DMC firmware programming
|
||
|
||
Linux path:
|
||
|
||
```
|
||
intel_dmc.c:
|
||
intel_dmc_init()
|
||
→ parse CSS header (first 32 bytes of firmware blob)
|
||
→ find matching stepping (ucode version)
|
||
→ write DMC payload to MMIO:
|
||
DMC_FW1_BASE(offset) = payload address low
|
||
DMC_FW1_BASE+4 = payload address high
|
||
DMC_MMIO_START(offset) = 0x10000 (SRAM base)
|
||
DMC_MMIO_END(offset) = 0x10000 + payload_size
|
||
→ set DMC_CTRL = ENABLE bit
|
||
→ wait for DMC_STATUS to indicate loaded
|
||
|
||
Register map (Gen9):
|
||
DMC_MMIO_START = 0x6F000
|
||
DMC_MMIO_END = 0x6F004
|
||
DMC_FW_BASE = 0x6F008 (or per-variant: 0x6F038, 0x6F0B0)
|
||
DMC_CTRL = 0x6F064
|
||
DMC_STATUS = 0x6F06C
|
||
DMC_SRAM_BASE = 0x10000 (SRAM offset inside MMIO space)
|
||
```
|
||
|
||
### 2.6 Display power wells
|
||
|
||
Linux path:
|
||
|
||
```
|
||
intel_display_power.c:
|
||
intel_power_domains_init()
|
||
→ register power well list per generation
|
||
→ each well: domains bitmask, ops (enable/disable/is_enabled)
|
||
|
||
SKL power wells (Gen9 example):
|
||
PW_1 (always on): domains = DISPLAY_CORE, PIPE_A
|
||
PW_2: domains = PIPE_B, PIPE_C, PIPE_D, AUX_*
|
||
PW_3: domains = DDI_*, TRANS_*
|
||
DDI_A_IO, DDI_B_IO, DDI_C_IO, DDI_D_IO, DDI_E_IO
|
||
AUX_A, AUX_B, AUX_C, AUX_D
|
||
|
||
Each power well enable:
|
||
→ write POWER_WELL_CTL register (0x45400 for SKL)
|
||
→ wait for power well to become active (read-back verify)
|
||
→ on Gen12: also enable DBUF slices via DBUF_CTL_S1/S2
|
||
```
|
||
|
||
Without power wells enabled, DDI_BUF_CTL reads return 0 — connector detection appears to find nothing connected.
|
||
|
||
## 3. Target Hardware Generations
|
||
|
||
### 3.1 Generational strategy
|
||
|
||
| Phase | Target gen | Display ver | Reason |
|
||
|-------|-----------|-------------|--------|
|
||
| Phase 0 | Gen9 (Skylake, Kaby Lake, Coffee Lake) | 9 | Simplest modern display engine. Widest installed base of laptops. Registers are mostly correct in current code. |
|
||
| Phase 1 | Gen9.5 (Ice Lake, Elkhart Lake) | 11 | Adds combo PHY, different DDI programming. Validates register abstraction. |
|
||
| Phase 2 | Gen12 (Tiger Lake, Alder Lake-P) | 12-13 | Major display engine rewrite. Separate transcoder from pipe. DBUF slices. |
|
||
| Phase 3 | Gen12.7 (Meteor Lake, Arrow Lake) | 14 | Newest i915-supported platform. Uses Xe-LP display. |
|
||
| Post-i915 | Battlemage, Lunar Lake, Panther Lake | 20-30 | NOT in i915. Uses Xe driver architecture (`drivers/gpu/drm/xe/`). Future work beyond this plan. |
|
||
|
||
### 3.2 Gen9 (Skylake family) — first validation target
|
||
|
||
**Why Gen9 first:**
|
||
- Most of our register offsets are already correct for Gen9
|
||
- Largest installed base of Intel GPUs in 2024-2026 laptops and desktops
|
||
- Well-documented in Linux i915 with clear per-register comments
|
||
- No complex combo PHY or separate transcoder (pipe = transcoder)
|
||
- DMC firmware is small and well-understood (SKL: 42KB, KBL: 42KB, CFL: 40KB)
|
||
- GMBUS registers at standard offsets (`0xC5100`-`0xC5120`)
|
||
|
||
**Device IDs for Gen9 validation:**
|
||
|
||
| Platform | Substring | Example device IDs |
|
||
|----------|-----------|-------------------|
|
||
| Skylake DT GT2 | `0x1912`, `0x191B`, `0x191D`, `0x191E`, `0x1921` | Desktop HD 530 |
|
||
| Skylake ULT GT2 | `0x1916`, `0x1921`, `0x1923`, `0x1926`, `0x1927` | Laptop HD 520/530 |
|
||
| Skylake ULX GT2 | `0x191E` | HD 515 |
|
||
| Kaby Lake DT GT2 | `0x5912`, `0x5916`, `0x591B`, `0x591D` | Desktop HD 630 |
|
||
| Kaby Lake ULT GT2 | `0x5916`, `0x5921`, `0x5923`, `0x5926`, `0x5927` | Laptop HD 620/630 |
|
||
| Coffee Lake DT GT2 | `0x3E90`, `0x3E91`, `0x3E92`, `0x3E93`, `0x3E96`, `0x3E98`, `0x3E9A`, `0x3E9B` | Desktop UHD 630 |
|
||
| Coffee Lake ULT GT3 | `0x3EA5`, `0x3EA6`, `0x3EA7`, `0x3EA8` | Laptop Iris Plus |
|
||
|
||
## 4. New File Structure
|
||
|
||
Current:
|
||
```
|
||
src/drivers/intel/
|
||
├── mod.rs (693 lines)
|
||
├── display.rs (404 lines)
|
||
├── gtt.rs (226 lines)
|
||
└── ring.rs (267 lines)
|
||
```
|
||
|
||
Target:
|
||
```
|
||
src/drivers/intel/
|
||
├── mod.rs Driver struct, init, GpuDriver impl
|
||
├── display.rs Display pipe, modesetting, page flip
|
||
├── display_power.rs Power well enable/disable per generation [NEW]
|
||
├── display_cdclk.rs Core display clock programming [NEW]
|
||
├── display_dpll.rs Display PLL (pixel clock generation) [NEW]
|
||
├── display_dmc.rs DMC firmware upload to hardware [NEW]
|
||
├── gmbus.rs GMBUS I2C controller (EDID/DDC) [NEW]
|
||
├── dp_aux.rs DisplayPort AUX channel read/write [NEW]
|
||
├── dp_link.rs DP link training [NEW]
|
||
├── hdmi.rs HDMI infoframe programming [NEW]
|
||
├── hotplug.rs HPD interrupt handling [NEW]
|
||
├── cursor.rs Hardware cursor plane [NEW]
|
||
├── gtt.rs GGTT page table (existing, mostly complete)
|
||
├── ring.rs Command ring buffer (existing, mostly complete)
|
||
├── regs.rs Per-generation register tables [NEW]
|
||
├── regs_gen9.rs Gen9 (SKL/KBL/CFL) register offsets [NEW]
|
||
├── regs_gen11.rs Gen11 (ICL/EHL) register offsets [NEW]
|
||
├── regs_gen12.rs Gen12 (TGL/ADL/DG2/MTL) register offsets [NEW]
|
||
├── info.rs Device info, capability flags, display version [NEW]
|
||
└── vbt.rs Video BIOS Table parser [NEW]
|
||
```
|
||
|
||
## 5. Implementation Phases
|
||
|
||
### Phase 0 — Display Foundation for Gen9 (4-6 weeks)
|
||
|
||
**Goal:** Real EDID reading, DMC firmware programming, and display power well initialization on Gen9 (Skylake / Kaby Lake / Coffee Lake). First real Intel display output on hardware.
|
||
|
||
**Exit criteria:**
|
||
- `read_edid_block()` returns real EDID from a physical monitor via GMBUS I2C
|
||
- DMC firmware is uploaded to hardware and status confirms loaded
|
||
- Display power wells are enabled before connector detection
|
||
- Connector detection reads real connection status (not synthetic)
|
||
- Mode list comes from monitor's EDID, not hardcoded 1080p
|
||
- CDCLK is programmed based on display bandwidth requirements
|
||
- Bounded modeset proof on real Gen9 hardware via `test-intel-gpu.sh`
|
||
|
||
#### 0A: Device info and register abstraction (regs.rs, info.rs)
|
||
|
||
**Create `info.rs`** — Device capability table:
|
||
|
||
```rust
|
||
pub struct IntelDeviceInfo {
|
||
pub display_version: u8,
|
||
pub gt_version: u8,
|
||
pub num_pipes: u8,
|
||
pub num_ports: u8,
|
||
pub has_ddi: bool,
|
||
pub has_dp_aux: bool,
|
||
pub has_gmbus: bool,
|
||
pub has_dmc: bool,
|
||
pub has_combo_phy: bool,
|
||
pub has_dbuf_slice: bool,
|
||
pub has_transcoder: bool, // Gen12+: pipe != transcoder
|
||
pub dmc_fw_key: Option<&'static str>,
|
||
}
|
||
```
|
||
|
||
Populate from device ID → generation lookup. Key function:
|
||
|
||
```rust
|
||
pub fn device_info_from_id(device_id: u16) -> IntelDeviceInfo
|
||
```
|
||
|
||
This replaces the ad-hoc `is_supported_intel_generation()` gate with a rich capability table.
|
||
|
||
**Create `regs.rs`** — Register access abstraction:
|
||
|
||
```rust
|
||
pub trait IntelRegs {
|
||
// Power wells
|
||
const POWER_WELL_CTL: usize;
|
||
|
||
// Display clock
|
||
const CDCLK_CTL: usize;
|
||
|
||
// DMC
|
||
const DMC_MMIO_START: usize;
|
||
const DMC_MMIO_END: usize;
|
||
const DMC_FW_BASE: usize;
|
||
const DMC_CTRL: usize;
|
||
const DMC_STATUS: usize;
|
||
|
||
// GMBUS
|
||
const GMBUS0: usize;
|
||
const GMBUS1: usize;
|
||
const GMBUS2: usize;
|
||
const GMBUS3: usize;
|
||
const GMBUS4: usize;
|
||
const GMBUS5: usize;
|
||
|
||
// Pipe / transcoder
|
||
const PIPECONF(n: u8) -> usize;
|
||
const HTOTAL(n: u8) -> usize;
|
||
// ... etc
|
||
|
||
// Planes
|
||
const DSPCNTR(n: u8) -> usize;
|
||
const DSPSURF(n: u8) -> usize;
|
||
|
||
// Cursor
|
||
const CURCNTR(n: u8) -> usize;
|
||
const CURPOS(n: u8) -> usize;
|
||
const CURBASE(n: u8) -> usize;
|
||
|
||
// DDI
|
||
const DDI_BUF_CTL(port: u8) -> usize;
|
||
|
||
// Forcewake
|
||
const FORCEWAKE_REQ: usize;
|
||
const FORCEWAKE_ACK: usize;
|
||
}
|
||
```
|
||
|
||
**Create `regs_gen9.rs`** — Gen9 register constants:
|
||
|
||
All offsets from `local/reference/linux-7.1/drivers/gpu/drm/i915/display/intel_display_regs.h` for DISPLAY_VER 9.
|
||
|
||
```
|
||
GMBUS0 = 0xC5100
|
||
GMBUS1 = 0xC5104
|
||
GMBUS2 = 0xC5108
|
||
GMBUS3 = 0xC510C
|
||
GMBUS4 = 0xC5110
|
||
GMBUS5 = 0xC5120
|
||
|
||
POWER_WELL_CTL = 0x45400
|
||
|
||
CDCLK_CTL = 0x46000
|
||
|
||
DMC_MMIO_START = 0x6F000
|
||
DMC_MMIO_END = 0x6F004
|
||
DMC_FW1_BASE = 0x6F038
|
||
DMC_FW2_BASE = 0x6F0B0
|
||
DMC_CTRL = 0x6F064
|
||
DMC_STATUS = 0x6F06C
|
||
DMC_SRAM_BASE = 0x10000
|
||
|
||
PIPECONF(A) = 0x70008 (pipe stride = 0x1000)
|
||
HTOTAL(A) = 0x60000 (transcoder stride = 0x1000)
|
||
DDI_BUF_CTL(A) = 0x64000 (port stride = 0x100)
|
||
|
||
CURCNTR(A) = 0x70080 (pipe stride = 0x1000)
|
||
CURPOS(A) = 0x70084
|
||
CURBASE(A) = 0x70088
|
||
|
||
FORCEWAKE_REQ = 0xA18C
|
||
FORCEWAKE_ACK = 0xA194
|
||
```
|
||
|
||
**Linux reference files:**
|
||
- `i915/i915_reg.h` — master register definitions
|
||
- `i915/display/intel_display_regs.h` — display-specific registers
|
||
- `i915/display/intel_dmc_regs.h` — DMC registers
|
||
- `i915/display/intel_gmbus_regs.h` — GMBUS registers
|
||
|
||
**Estimated effort:** ~500 lines Rust across 3 files.
|
||
|
||
#### 0B: GMBUS / DDC I2C for real EDID (gmbus.rs)
|
||
|
||
**Create `gmbus.rs`** — GMBUS I2C controller:
|
||
|
||
Key operations to port from `intel_gmbus.c`:
|
||
|
||
```rust
|
||
pub struct GmbusController {
|
||
mmio: Arc<MmioRegion>,
|
||
regs: &'static dyn IntelRegs,
|
||
}
|
||
|
||
impl GmbusController {
|
||
/// Initialize GMBUS hardware
|
||
pub fn init(&self) -> Result<()>;
|
||
|
||
/// Read bytes from I2C slave via GMBUS
|
||
pub fn read(&self, port: GmbusPort, slave_addr: u8, offset: u8, buf: &mut [u8]) -> Result<()>;
|
||
|
||
/// Read full EDID block from monitor (128 or 256 bytes)
|
||
pub fn read_edid(&self, port: GmbusPort) -> Result<Vec<u8>>;
|
||
}
|
||
|
||
pub enum GmbusPort {
|
||
DDC_B = 0x04, // Legacy VGA / DVI
|
||
DDC_C = 0x05, // HDMI port C
|
||
DDC_D = 0x06, // HDMI port D
|
||
// Gen9+ additional ports
|
||
DPC = 0x02, // DisplayPort C
|
||
DPE = 0x03, // DisplayPort E
|
||
}
|
||
```
|
||
|
||
Implementation sequence:
|
||
|
||
1. **`init()`**: Reset GMBUS controller — clear `GMBUS2` status, set `GMBUS0` pin pair select.
|
||
2. **`read()`**:
|
||
- Write `GMBUS1`: byte count | (slave_addr << 1) | direction (read=1)
|
||
- Wait for `GMBUS2.SDAST` bit (data available in `GMBUS3`)
|
||
- Read 4 bytes at a time from `GMBUS3` FIFO
|
||
- For multi-byte offset reads (EDID block 1+): write `GMBUS5` with 2-byte index
|
||
3. **`read_edid()`**: Call `read()` with slave address `0xA0`, read 128 bytes. Parse byte 126 for extension count; if nonzero, read block 1 via `GMBUS5` index mode.
|
||
4. **Error handling**: Timeout after 1ms per byte. Clear `GMBUS2` errors. Hardware reset via `GMBUS0` GPIO mode if stuck.
|
||
|
||
**Integration with display.rs:**
|
||
- Replace `read_edid_block()` stub with `GmbusController::read_edid()` call
|
||
- Port-to-GMBUS mapping: port 0→DPC, port 1→DPC, port 2→DPE, port 3→DPE (Gen9 SKL mapping from Linux)
|
||
- Fall back to synthetic EDID only if GMBUS read fails AND DDI_BUF_CTL reports connected
|
||
|
||
**Linux reference files:**
|
||
- `display/intel_gmbus.c` (~560 lines) — full GMBUS implementation
|
||
- `display/intel_gmbus_regs.h` — register definitions
|
||
|
||
**Estimated effort:** ~400-500 lines Rust.
|
||
|
||
#### 0C: Display power wells (display_power.rs)
|
||
|
||
**Create `display_power.rs`** — Power domain management:
|
||
|
||
```rust
|
||
pub struct DisplayPower {
|
||
mmio: Arc<MmioRegion>,
|
||
regs: &'static dyn IntelRegs,
|
||
}
|
||
|
||
impl DisplayPower {
|
||
/// Enable all power domains needed for display operation
|
||
pub fn init_domains(&self) -> Result<()>;
|
||
|
||
/// Enable a specific power well
|
||
pub fn enable_well(&self, well: PowerWell) -> Result<()>;
|
||
|
||
/// Check if a power well is active
|
||
pub fn is_well_enabled(&self, well: PowerWell) -> bool;
|
||
|
||
/// Disable unused power wells (power saving)
|
||
pub fn disable_unused(&self, active_pipes: u8) -> Result<()>;
|
||
}
|
||
|
||
pub enum PowerWell {
|
||
PW1, // Always-on domain: display core
|
||
PW2, // Pipe B/C/D, AUX channels
|
||
DDI_A, // DDI port A (eDP)
|
||
DDI_B, // DDI port B (HDMI)
|
||
DDI_C, // DDI port C (DP)
|
||
DDI_D, // DDI port D (DP)
|
||
DDI_E, // DDI port E (HDMI)
|
||
AUX_A, // DP AUX channel A
|
||
AUX_B, // DP AUX channel B
|
||
AUX_C, // DP AUX channel C
|
||
AUX_D, // DP AUX channel D
|
||
}
|
||
```
|
||
|
||
Implementation for Gen9 (SKL):
|
||
- `POWER_WELL_CTL` at `0x45400`: each power well is a bit in this register
|
||
- Enable: write `1 << well_bit` to `POWER_WELL_CTL`, then read back until the corresponding status bit is set
|
||
- Required wells before connector detection: PW1, PW2, all DDI wells, all AUX wells
|
||
|
||
**Integration with mod.rs:**
|
||
- Call `display_power.init_domains()` in `IntelDriver::new()` before any connector detection
|
||
- GMBUS reads require AUX power wells enabled
|
||
|
||
**Linux reference files:**
|
||
- `display/intel_display_power.c` (~1000 lines)
|
||
- `display/intel_display_power_well.c` (~500 lines)
|
||
- `display/intel_display_power_map.c` (~200 lines)
|
||
|
||
**Estimated effort:** ~400 lines Rust (Gen9 only initially).
|
||
|
||
#### 0D: DMC firmware programming (display_dmc.rs)
|
||
|
||
**Create `display_dmc.rs`** — DMC firmware upload:
|
||
|
||
```rust
|
||
pub struct DmcFirmware {
|
||
mmio: Arc<MmioRegion>,
|
||
regs: &'static dyn IntelRegs,
|
||
}
|
||
|
||
impl DmcFirmware {
|
||
/// Upload DMC firmware blob to hardware
|
||
pub fn upload(&self, firmware: &[u8]) -> Result<()>;
|
||
|
||
/// Check if DMC firmware is loaded and running
|
||
pub fn is_loaded(&self) -> bool;
|
||
|
||
/// Get the firmware version running on hardware
|
||
pub fn firmware_version(&self) -> u32;
|
||
}
|
||
```
|
||
|
||
Implementation:
|
||
1. Parse firmware blob: first 32 bytes are CSS header. Extract payload offset, payload size, and ucode version.
|
||
2. Find matching stepping: compare firmware ucode version against hardware stepping (from `MCHBAR` or PCI revision).
|
||
3. Upload payload:
|
||
- Write `DMC_MMIO_START` = `0x10000` (SRAM base)
|
||
- Write `DMC_MMIO_END` = `0x10000 + payload_size`
|
||
- Write `DMC_FW_BASE` through `DMC_FW_BASE + payload_size` in 4-byte chunks (payload data)
|
||
4. Enable: write `DMC_CTRL` with enable bit
|
||
5. Wait: poll `DMC_STATUS` for loaded indication, timeout 50ms
|
||
|
||
**Integration with mod.rs:**
|
||
- `IntelDriver::new()` already receives `firmware: HashMap<String, Vec<u8>>` — use the DMC key from the device info table
|
||
- Call `dmc.upload(&firmware[dmc_key])` after power wells but before connector detection
|
||
- DMC is optional for Gen8/Gen9 (display works without it but power management is broken), required for Gen12+
|
||
|
||
**Linux reference files:**
|
||
- `display/intel_dmc.c` (~600 lines)
|
||
- `display/intel_dmc_regs.h` (~60 lines)
|
||
|
||
**Estimated effort:** ~300 lines Rust.
|
||
|
||
#### 0E: Core display clock (display_cdclk.rs)
|
||
|
||
**Create `display_cdclk.rs`** — CDCLK programming:
|
||
|
||
```rust
|
||
pub struct CdclkState {
|
||
pub frequency: u32, // Hz
|
||
pub voltage_level: u32, // for SKL: 0=337.5MHz, 1=450MHz, 2=540MHz
|
||
}
|
||
|
||
pub struct DisplayClock {
|
||
mmio: Arc<MmioRegion>,
|
||
regs: &'static dyn IntelRegs,
|
||
}
|
||
|
||
impl DisplayClock {
|
||
/// Initialize CDCLK to minimum safe frequency
|
||
pub fn init(&self) -> Result<CdclkState>;
|
||
|
||
/// Set CDCLK frequency for required display bandwidth
|
||
pub fn set_frequency(&self, target: u32) -> Result<CdclkState>;
|
||
|
||
/// Calculate required CDCLK for a given set of display modes
|
||
pub fn required_cdclk(modes: &[ModeInfo]) -> u32;
|
||
}
|
||
```
|
||
|
||
For Gen9 (SKL):
|
||
- `CDCLK_CTL` at `0x46000`
|
||
- Supported frequencies: 337.5 MHz, 450 MHz, 540 MHz
|
||
- Single pipe at 1080p60 needs ~337.5 MHz (pixel rate 148.5 MHz < 337.5/2)
|
||
- Bump to 450 MHz for dual pipe or high-resolution modes
|
||
- `CDCLK_CTL` bit layout: bits 26:26 = CD_FREQ_SELECT (0=337.5, 1=450, 2=540)
|
||
|
||
**Linux reference files:**
|
||
- `display/intel_cdclk.c` (~1000 lines)
|
||
|
||
**Estimated effort:** ~300 lines Rust (Gen9 only).
|
||
|
||
#### 0F: Connect Phase 0 into existing driver
|
||
|
||
Modify existing files:
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `mod.rs` | `IntelDriver::new()`: call power well init → DMC upload → CDCLK init before connector detection. Replace hardcoded register offsets with `regs` trait calls. |
|
||
| `display.rs` | Replace `read_edid_block()` stub with `GmbusController::read_edid()` call. Use `regs` trait for pipe/plane/DDI register access. Add fallback path: try GMBUS → try DP AUX → fall back to synthetic EDID. |
|
||
| `mod.rs` | Add `GmbusController`, `DisplayPower`, `DmcFirmware`, `DisplayClock` as struct fields. |
|
||
|
||
**Estimated effort:** ~200 lines of modifications to existing files.
|
||
|
||
### Phase 0 Summary
|
||
|
||
| Sub-task | New file | Est. lines | Linux reference | Depends on |
|
||
|----------|----------|-----------|-----------------|------------|
|
||
| 0A | `info.rs`, `regs.rs`, `regs_gen9.rs` | ~500 | `intel_display_regs.h`, `intel_device_info.h` | None |
|
||
| 0B | `gmbus.rs` | ~500 | `intel_gmbus.c` | 0A (registers) |
|
||
| 0C | `display_power.rs` | ~400 | `intel_display_power.c` | 0A (registers) |
|
||
| 0D | `display_dmc.rs` | ~300 | `intel_dmc.c` | 0A (registers) |
|
||
| 0E | `display_cdclk.rs` | ~300 | `intel_cdclk.c` | 0A (registers), 0C (power wells) |
|
||
| 0F | Modify `mod.rs`, `display.rs` | ~200 | — | All above |
|
||
| **Total** | **10 files** | **~2,200** | | |
|
||
|
||
**Validation gate:**
|
||
```bash
|
||
# Must pass on real Gen9 hardware (Skylake/Kaby Lake/Coffee Lake):
|
||
./local/scripts/test-intel-gpu.sh
|
||
|
||
# In-guest checks:
|
||
# 1. connector enumeration returns real connectors (not empty or synthetic)
|
||
# 2. EDID is read from physical monitor (not synthetic 1080p)
|
||
# 3. mode list contains monitor's native resolution
|
||
# 4. bounded modeset produces visible output
|
||
# 5. DMC firmware status shows loaded
|
||
# 6. power wells show enabled
|
||
```
|
||
|
||
### Phase 1 — DisplayPort and HDMI (4-6 weeks)
|
||
|
||
**Goal:** Full DP and HDMI output with proper link training, DPCD reads, and infoframe programming. Multi-monitor support.
|
||
|
||
**Exit criteria:**
|
||
- DP link training succeeds and DP connector shows real modes
|
||
- HDMI AVI infoframes are programmed with correct VIC
|
||
- DPCD reads return real DP capabilities
|
||
- Hotplug detection works via HPD interrupts (not polling)
|
||
- Dual-monitor setup works with independent modes per pipe
|
||
|
||
#### 1A: DP AUX channel (dp_aux.rs)
|
||
|
||
**Create `dp_aux.rs`** — DisplayPort AUX channel I/O:
|
||
|
||
```rust
|
||
pub struct DpAux {
|
||
mmio: Arc<MmioRegion>,
|
||
regs: &'static dyn IntelRegs,
|
||
}
|
||
|
||
impl DpAux {
|
||
/// Read bytes from DP AUX channel
|
||
pub fn read(&self, port: u8, offset: u32, buf: &mut [u8]) -> Result<()>;
|
||
|
||
/// Write bytes to DP AUX channel
|
||
pub fn write(&self, port: u8, offset: u32, data: &[u8]) -> Result<()>;
|
||
|
||
/// Read full DPCD capability block (first 16 bytes)
|
||
pub fn read_dpcd_caps(&self, port: u8) -> Result<DpcdCaps>;
|
||
|
||
/// Read EDID via DP AUX (I2C-over-AUX)
|
||
pub fn read_edid(&self, port: u8) -> Result<Vec<u8>>;
|
||
}
|
||
```
|
||
|
||
AUX register map (Gen9):
|
||
```
|
||
DP_AUX_CTL(port) = 0x64010 + port * 0x100
|
||
DP_AUX_DATA(port) = 0x64014 + port * 0x100 (5 registers for 20-byte message)
|
||
```
|
||
|
||
**Linux reference:** `display/intel_dp_aux.c` (~500 lines)
|
||
**Estimated effort:** ~500 lines Rust.
|
||
|
||
#### 1B: DP link training (dp_link.rs)
|
||
|
||
**Create `dp_link.rs`** — DP link establishment:
|
||
|
||
```rust
|
||
pub struct DpLink {
|
||
aux: DpAux,
|
||
mmio: Arc<MmioRegion>,
|
||
}
|
||
|
||
impl DpLink {
|
||
/// Train the DP link: find max lane count, set link rate, train
|
||
pub fn train(&self, port: u8) -> Result<DpLinkConfig>;
|
||
|
||
/// Read current link status from DPCD
|
||
pub fn read_status(&self, port: u8) -> Result<DpLinkStatus>;
|
||
}
|
||
|
||
pub struct DpLinkConfig {
|
||
pub lane_count: u8, // 1, 2, or 4
|
||
pub link_rate: u32, // 162000, 270000, 540000, 810000 (kHz)
|
||
pub spread_spectrum: bool,
|
||
}
|
||
```
|
||
|
||
**Linux reference:** `display/intel_dp_link_training.c` (~600 lines)
|
||
**Estimated effort:** ~600 lines Rust.
|
||
|
||
#### 1C: HDMI infoframes (hdmi.rs)
|
||
|
||
**Create `hdmi.rs`** — HDMI AVI/audio infoframes:
|
||
|
||
```rust
|
||
pub struct HdmiInfoframes;
|
||
|
||
impl HdmiInfoframes {
|
||
/// Program AVI infoframe for a given mode
|
||
pub fn program_avi(mmio: &MmioRegion, pipe: u8, mode: &ModeInfo) -> Result<()>;
|
||
|
||
/// Program audio infoframe (basic stereo)
|
||
pub fn program_audio(mmio: &MmioRegion, pipe: u8) -> Result<()>;
|
||
|
||
/// Compute AVI infoframe checksum
|
||
pub fn checksum(data: &[u8; 17]) -> u8;
|
||
}
|
||
```
|
||
|
||
Register map (Gen9):
|
||
```
|
||
HSW_TVIDEO_DIP_CTL(pipe) = 0x61180 + pipe * 0x1000
|
||
HSW_TVIDEO_DIP_AVI_DATA(pipe) = 0x61184 + pipe * 0x1000 (5 regs, 4 bytes each)
|
||
```
|
||
|
||
**Linux reference:** `display/intel_hdmi.c` (infoframe helpers, ~400 lines relevant)
|
||
**Estimated effort:** ~300 lines Rust.
|
||
|
||
#### 1D: Hotplug interrupt handling (hotplug.rs)
|
||
|
||
**Create `hotplug.rs`** — HPD detection:
|
||
|
||
```rust
|
||
pub struct HotplugHandler {
|
||
mmio: Arc<MmioRegion>,
|
||
}
|
||
|
||
impl HotplugHandler {
|
||
/// Initialize HPD interrupt mask
|
||
pub fn init(&self) -> Result<()>;
|
||
|
||
/// Check which ports have pending HPD events
|
||
pub fn check_events(&self) -> Vec<HotplugEvent>;
|
||
|
||
/// Acknowledge HPD events
|
||
pub fn ack(&self, events: &[HotplugEvent]);
|
||
|
||
/// Distinguish short pulse (EDID change) from long pulse (connect/disconnect)
|
||
pub fn classify_pulse(&self, port: u8) -> HotplugPulse;
|
||
}
|
||
|
||
pub struct HotplugEvent {
|
||
pub port: u8,
|
||
pub pulse_type: HotplugPulse,
|
||
}
|
||
|
||
pub enum HotplugPulse {
|
||
Long, // Connect or disconnect
|
||
Short, // EDID change or link training request
|
||
}
|
||
```
|
||
|
||
Register map (Gen9):
|
||
```
|
||
SDEISR = 0xC4000 — south display interrupt status
|
||
SDEIMR = 0xC4004 — south display interrupt mask
|
||
SDEIIR = 0xC4008 — south display interrupt identity
|
||
SHOTPLUG_CTL = 0xC4030 — hotplug control
|
||
```
|
||
|
||
**Linux reference:** `display/intel_hotplug.c` + `display/intel_hotplug_irq.c` (~600 lines)
|
||
**Estimated effort:** ~400 lines Rust.
|
||
|
||
#### 1E: Display PLL (display_dpll.rs)
|
||
|
||
**Create `display_dpll.rs`** — Pixel clock generation:
|
||
|
||
```rust
|
||
pub struct DisplayPll {
|
||
mmio: Arc<MmioRegion>,
|
||
}
|
||
|
||
impl DisplayPll {
|
||
/// Initialize shared DPLL pool
|
||
pub fn init(&self) -> Result<()>;
|
||
|
||
/// Find or allocate a PLL for the given pixel clock
|
||
pub fn get_pll_for_clock(&self, pixel_clock_khz: u32) -> Result<DpllConfig>;
|
||
|
||
/// Enable a DPLL and wait for lock
|
||
pub fn enable(&self, config: &DpllConfig) -> Result<()>;
|
||
|
||
/// Wire a DPLL to a specific CRTC/port
|
||
pub fn wire_to_crtc(&self, dpll: u8, pipe: u8) -> Result<()>;
|
||
}
|
||
|
||
pub struct DpllConfig {
|
||
pub id: u8,
|
||
pub frequency_khz: u32,
|
||
pub pdiv: u32,
|
||
pub kdiv: u32,
|
||
pub qdiv_mode: u32,
|
||
}
|
||
```
|
||
|
||
Register map (Gen9 SKL):
|
||
```
|
||
LCPLL1_CTL = 0x46010 — LC PLL 1 control
|
||
LCPLL2_CTL = 0x46014 — LC PLL 2 control
|
||
DPLL0_CTL = 0x46200 — DPLL 0 control
|
||
DPLL1_CTL = 0x46204 — DPLL 1 control
|
||
DPLL2_CTL = 0x46208 — DPLL 2 control
|
||
SKL_DPLL_CTRL1 = 0x46204 — DPLL configuration
|
||
```
|
||
|
||
**Linux reference:** `display/intel_dpll_mgr.c` (~2000 lines, Gen9 portion ~500 lines)
|
||
**Estimated effort:** ~500 lines Rust (Gen9 SKL DPLLs only).
|
||
|
||
### Phase 1 Summary
|
||
|
||
| Sub-task | New file | Est. lines | Linux reference |
|
||
|----------|----------|-----------|-----------------|
|
||
| 1A | `dp_aux.rs` | ~500 | `intel_dp_aux.c` |
|
||
| 1B | `dp_link.rs` | ~600 | `intel_dp_link_training.c` |
|
||
| 1C | `hdmi.rs` | ~300 | `intel_hdmi.c` |
|
||
| 1D | `hotplug.rs` | ~400 | `intel_hotplug.c` |
|
||
| 1E | `display_dpll.rs` | ~500 | `intel_dpll_mgr.c` |
|
||
| **Total** | **5 files** | **~2,300** | |
|
||
|
||
### Phase 2 — Gen12 Display Engine (6-10 weeks)
|
||
|
||
**Goal:** Tiger Lake / Alder Lake-P support with Gen12 display engine registers.
|
||
|
||
**Exit criteria:**
|
||
- Gen12 device IDs produce correct register accesses
|
||
- Separate pipe/transcoder programming works
|
||
- DBUF slice allocation is correct
|
||
- Combo PHY initialization for Ice Lake / Tiger Lake
|
||
- Display on real Tiger Lake or Alder Lake hardware
|
||
|
||
#### 2A: Gen12 register tables (regs_gen12.rs)
|
||
|
||
Major differences from Gen9:
|
||
|
||
| Feature | Gen9 | Gen12 |
|
||
|---------|------|-------|
|
||
| Pipe / transcoder | Same thing | Separate: `PIPECONF` controls pipe, `TRANS_DDI_FUNC_CTL` controls transcoder |
|
||
| Plane programming | `DSPCNTR`/`DSPSURF` | `PLANE_CTL`/`PLANE_SURF`/`PLANE_STRIDE`/`PLANE_OFFSET` |
|
||
| DBUF | Single DBUF | DBUF slices (S1, S2) must be enabled per pipe |
|
||
| Combo PHY | N/A | ICL/TGL: `ICL_PORT_CL1K_DW0` through `ICL_PORT_CL1K_DW30` for each PHY |
|
||
| DMC | Single payload | Multiple payloads (DC3DC5, DC3DC6, etc.) |
|
||
| CDCLK | 3 fixed frequencies | Bounded range with `BXT_DE_PLL_CTL` |
|
||
| DDI | 6 ports max | Up to 8 DDI ports, some are combo (TC) |
|
||
| AUX | Per-DDI | Per-DDI but TC ports use `TC_AUX` registers |
|
||
|
||
**Create `regs_gen12.rs`** with all Gen12 display register offsets from `intel_display_regs.h` for DISPLAY_VER 12-13.
|
||
|
||
**Estimated effort:** ~800 lines (large register table).
|
||
|
||
#### 2B: Combo PHY initialization
|
||
|
||
For Tiger Lake and Ice Lake, the combo PHYs must be initialized before DDI programming. Port from `display/intel_combo_phy.c`.
|
||
|
||
**Estimated effort:** ~400 lines.
|
||
|
||
#### 2C: DBUF slice management
|
||
|
||
Gen12+ requires display buffer (DBUF) slices to be allocated per pipe. Port from `display/intel_dbuf.c`.
|
||
|
||
**Estimated effort:** ~300 lines.
|
||
|
||
#### 2D: Transcoder programming
|
||
|
||
Gen12 separates pipe from transcoder. New `TRANS_DDI_FUNC_CTL`, `TRANS_HTOTAL`, etc. Port from `display/intel_ddi.c` (transcoder path).
|
||
|
||
**Estimated effort:** ~500 lines of changes to `display.rs`.
|
||
|
||
### Phase 2 Summary
|
||
|
||
| Sub-task | Est. lines | Linux reference |
|
||
|----------|-----------|-----------------|
|
||
| 2A Gen12 regs | ~800 | `intel_display_regs.h` (DISPLAY_VER 12) |
|
||
| 2B Combo PHY | ~400 | `intel_combo_phy.c` |
|
||
| 2C DBUF slices | ~300 | `intel_dbuf.c` |
|
||
| 2D Transcoder | ~500 | `intel_ddi.c` |
|
||
| **Total** | **~2,000** | |
|
||
|
||
### Phase 3 — Full KMS Features (4-6 weeks)
|
||
|
||
**Goal:** Cursor planes, atomic modesetting, watermarks, backlight. What a compositor needs.
|
||
|
||
| Task | New file | Est. lines | Linux reference |
|
||
|------|----------|-----------|-----------------|
|
||
| Cursor plane | `cursor.rs` | ~200 | `intel_cursor.c` |
|
||
| Atomic modeset | Changes to `scheme.rs`, `display.rs` | ~600 | `intel_atomic.c` |
|
||
| Watermarks | `display_watermark.rs` | ~500 | `skl_watermark.c` |
|
||
| Backlight | `display_backlight.rs` | ~200 | `intel_backlight.c` |
|
||
| VBT parser | `vbt.rs` | ~500 | `intel_bios.c` |
|
||
| **Total** | **4 new files** | **~2,000** | |
|
||
|
||
### Phase 4 — Render Path (12-20 weeks, parallel with Phase 2-3)
|
||
|
||
**Goal:** GPU command submission for rendering. Mesa hardware renderer path.
|
||
|
||
This phase is architecturally different from display and can proceed in parallel.
|
||
|
||
| Task | File | Est. lines | Linux reference |
|
||
|------|------|-----------|-----------------|
|
||
| Per-gen ring context init | `ring.rs` expansion | ~400 | `intel_lrc.c`, `gen8_engine_cs.c` |
|
||
| Execlists submission | `execlists.rs` (new) | ~800 | `intel_execlists_submission.c` |
|
||
| Batch buffer protocol | `batch.rs` (new) | ~300 | `gem/i915_gem_execbuffer.c` |
|
||
| Fence objects | `fence.rs` (new) | ~400 | `i915_sw_fence.c`, `intel_timeline.c` |
|
||
| Mesa winsys | Changes to `scheme.rs` | ~600 | Mesa Redox winsys |
|
||
| **Total** | **3 new files** | **~2,500** | |
|
||
|
||
### Phase 5 — Power and Optimization (8-12 weeks)
|
||
|
||
| Task | Est. lines | Linux reference |
|
||
|------|-----------|-----------------|
|
||
| PSR (Panel Self Refresh) | ~400 | `intel_psr.c` |
|
||
| FBC (Frame Buffer Compression) | ~300 | `intel_fbc.c` |
|
||
| GT frequency / RPS / RC6 | ~400 | `intel_rps.c`, `intel_rc6.c` |
|
||
| GuC firmware loading | ~600 | `gt/uc/intel_guc.c`, `intel_guc_fw.c` |
|
||
| GuC submission | ~1000 | `gt/uc/intel_guc_submission.c` |
|
||
| **Total** | **~2,700** | |
|
||
|
||
## 6. Effort Summary
|
||
|
||
| Phase | Planned | Actual | Files |
|
||
|-------|---------|--------|-------|
|
||
| 0: Display Foundation | 4-6 weeks | Completed | 9 |
|
||
| 1: DP/HDMI | 4-6 weeks | Completed | 5 |
|
||
| 2: Gen12 Display | 6-10 weeks | Completed | 3 |
|
||
| 3: Full KMS | 4-6 weeks | Completed | 4 |
|
||
| 4: Render Path | 12-20 weeks | Completed | 4 |
|
||
| 5: Power/Optimize | 8-12 weeks | Deferred | — |
|
||
| **Total** | **38-60 weeks** | **Phases 0-4 done** | **26 files, 4,692 lines** |
|
||
|
||
### Actual vs Planned
|
||
|
||
| Metric | Planned | Actual |
|
||
|---|---|---|
|
||
| Total new lines | ~13,700 | 4,692 |
|
||
| New files | ~24 | 26 |
|
||
| Xe2 coverage | Out of scope | Full Arrow Lake support |
|
||
| Patch system | Required (P10-P15) | Not needed (source ownership) |
|
||
|
||
The critical path to first Intel display output on real hardware is **Phase 0** at 4-6 weeks.
|
||
|
||
## 7. Validation Strategy
|
||
|
||
### Per-phase validation
|
||
|
||
| Phase | Validation tool | Success criteria |
|
||
|-------|----------------|-----------------|
|
||
| 0 | `test-intel-gpu.sh` on Gen9 hardware | Real EDID read, real modes, visible output |
|
||
| 1 | DP/HDMI cable hotplug test | DP link training succeeds, HDMI infoframes valid |
|
||
| 2 | TGL/ADL-P hardware test | Gen12 modeset with separate transcoder |
|
||
| 3 | KWin compositor test | Cursor visible, atomic modeset works |
|
||
| 4 | Mesa `glxgears` test | Hardware-rendered output via Intel Mesa driver |
|
||
| 5 | Battery life comparison | PSR/FBC measurably reduces power consumption |
|
||
|
||
### QEMU validation (bounded, not sufficient for claims)
|
||
|
||
QEMU with `qemu64` CPU does not emulate Intel GPU. For Intel DRM testing in QEMU:
|
||
- Use `-device virtio-gpu` for shared DRM core validation only
|
||
- Real Intel validation requires bare metal or Intel GVT-g passthrough
|
||
|
||
### Hardware test matrix
|
||
|
||
| Hardware | Gen | Phase 0 | Phase 1 | Phase 2 | Phase 3 |
|
||
|----------|-----|---------|---------|---------|---------|
|
||
| Skylake HD 530 desktop | 9 | Target | Target | N/A | Target |
|
||
| Kaby Lake HD 620 laptop | 9 | Target | Target | N/A | Target |
|
||
| Coffee Lake UHD 630 | 9 | Target | Target | N/A | Target |
|
||
| Ice Lake Iris Plus | 11 | Secondary | Secondary | Target | Secondary |
|
||
| Tiger Lake Xe | 12 | N/A | N/A | Target | Target |
|
||
| Alder Lake-P Iris Xe | 12 | N/A | N/A | Target | Target |
|
||
|
||
## 8. Anti-Patterns and Constraints
|
||
|
||
### Code quality rules
|
||
|
||
1. **No stubs.** Every new function must have real register-level implementation. No `todo!()`, no hardcoded return values, no synthetic data when hardware can provide real data.
|
||
2. **No `as any` or type suppression.** All MMIO accesses are typed through the register abstraction layer.
|
||
3. **No `unwrap()` in new code.** All fallible operations return `Result<T, DriverError>`.
|
||
4. **Bounds-checked MMIO.** Every `read_volatile` / `write_volatile` must be preceded by a bounds check against the MMIO region size.
|
||
5. **Per-gen registers.** No hardcoded offsets in implementation code. All offsets come from `regs.rs` trait or `regs_gen*.rs` constants.
|
||
|
||
### Porting rules
|
||
|
||
1. **Use Linux as reference only.** Port the behavior, not the C code structure. Rust idioms (Result, enums, traits) replace C idioms (error codes, macros, function pointers).
|
||
2. **Comment Linux source references.** Every major function should have a comment like `// Ported from intel_gmbus.c:do_gmbus_xfer()`
|
||
3. **Test incrementally.** Each sub-task (0A, 0B, etc.) must compile and be testable independently before integration.
|
||
4. **Generation-gate everything.** If a register offset differs per generation, it goes in the register table, not as an if/else chain.
|
||
|
||
### Source management
|
||
|
||
All changes to `redox-drm` source go as git commits in the source fork at
|
||
`local/recipes/gpu/redox-drm/source/`. Red Bear uses direct source ownership —
|
||
no patches. Build with `repo cook redox-drm`.
|
||
|
||
## 9. Dependencies on Other Red Bear Subsystems
|
||
|
||
| Dependency | Status | Impact on Intel work |
|
||
|-----------|--------|---------------------|
|
||
| `redox-driver-sys` | Build-visible | PCI, memory, IRQ access — sufficient for Phase 0 |
|
||
| `linux-kpi` | Build-visible | Not used by Intel Rust driver (not needed — Intel backend is pure Rust) |
|
||
| `firmware-loader` | Working daemon | DMC firmware blobs served via `scheme:firmware` — ready |
|
||
| `libdrm` | Build-visible with Redox patches | Render node support needed for Phase 4 |
|
||
| `Mesa` | Software only (swrast) | Hardware Intel driver needed for Phase 4 — large separate effort |
|
||
| `pcid` / `pcid-spawner` | Working | Intel GPU device detection and driver launch — working |
|
||
| Kernel `scheme:memory` | Working | BAR mapping and DMA buffer allocation — sufficient |
|
||
| Kernel `scheme:irq` | Working | MSI/MSI-X delivery — sufficient |
|
||
| Kernel `scheme:proc` | Working | Process management — sufficient |
|
||
|
||
## 10. Out of Scope
|
||
|
||
The following are explicitly NOT part of this plan:
|
||
|
||
- **Xe driver (`drivers/gpu/drm/xe/`)** — Linux Xe driver for Battlemage/Lunar Lake/Panther Lake. Arrow Lake (integrated) uses i915 display engine with Xe2 GT — our IntelRegs trait supports this. Future discrete Xe GPUs may need Xe driver architecture.
|
||
- **GPU virtualization (GVT-g)** — Not needed for bare-metal desktop.
|
||
- **Gen2-Gen7 support** — Legacy hardware. The driver gate correctly blocks these.
|
||
- **HDMI/DP audio** — Deferred. Display-only first.
|
||
- **Content protection (HDCP, PXP)** — Deferred indefinitely.
|
||
- **DSC (Display Stream Compression)** — Deferred to Phase 5+.
|
||
- **VRR (Variable Refresh Rate)** — Deferred to Phase 5+.
|