danos/docs/intel-igpu.md

557 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Native Intel iGPU display support — feasibility and roadmap
**Status: research snapshot, not implemented.** This records what a *minimal, display-only*
native driver for an **Intel integrated GPU** — EDID read + mode-set + framebuffer scanout, with
**no** 3D/media/compute — would take, and how it slots into danos's pluggable scanout
architecture. It is a survey of primary sources (Intel's open-source
[Programmer's Reference Manuals](https://www.intel.com/content/www/us/en/docs/graphics-for-linux/developer-reference/1-0/overview.html),
coreboot's [libgfxinit](https://doc.coreboot.org/gfx/libgfxinit.html), the Linux
[i915 display](https://github.com/torvalds/linux/tree/master/drivers/gpu/drm/i915/display) driver,
and Haiku's [intel_extreme](https://github.com/haiku/haiku/tree/master/src/add-ons/kernel/drivers/graphics/intel_extreme/)),
not an implementation. It is the companion to [nvidia-gpus.md](nvidia-gpus.md) and should be read
against it — the two answer the same question for opposite silicon.
Read [display.md](display.md) and [display-v2.md](display-v2.md) first — this doc assumes the v2
model where scanout is a **pluggable backend** and a native driver is just another `.scanout`
service (like the virtio-gpu one), announcing to the compositor over `attach_scanout`.
## TL;DR
- **Intel is a materially easier, lower-tier target than the NVIDIA RTX 3060 — and the reason is
documentation, not silicon.** Intel publishes official, register-level, per-platform **Display
Engine** PRMs with named registers, bitfields, and numbered enable sequences; NVIDIA publishes
no display PRM and forces reverse-engineering against GPL nouveau. A minimal Intel display-only
driver is roughly **tier 2 to low-tier 3** for well-covered generations (Skylake / Kaby Lake /
Coffee Lake), versus NVIDIA's **tier 4** for GA106. This is the load-bearing conclusion.
- **The display block is a genuinely separable register domain.** Mode-set + scanout touch only
display registers (pipes, planes, transcoders, DDI buffers, PLLs, power wells, GMBUS/AUX) — **no
render engine, no command streamer, no GEM/3D, no signed microcode.** Two small carve-outs, both
trivial pokes that do *not* pull in the render engine: a real CDCLK frequency change writes the
shared GT PCODE mailbox, and the plane's surface register is a GGTT (memory-interface) address.
- **There is no firmware wall on the display path.** The only display microcontroller (DMC / "CSR",
Skylake+) is **optional** — its sole job is saving/restoring display state across DC5/DC6
low-power idle. Without it, i915 prints "Disabling runtime power management" and mode-sets and
scans out normally. GuC/HuC are render/media coprocessors, never touched by a display driver.
Pre-Skylake parts have no display microcontroller at all yet mode-set fine. There is **nothing
analogous to NVIDIA's GSP**.
- **The scanout memory model is dramatically simpler than a discrete GPU.** Intel iGPUs have **no
VRAM**: the display scans out of ordinary system RAM addressed through the Global GTT (GGTT), a
flat single-level page table. Linear (untiled) framebuffers are first-class. You need **no
GEM/TTM, no VMM, no VRAM allocator, no BAR1 aperture juggling** — the exact machinery the NVIDIA
path forces on you.
- **coreboot libgfxinit is a compact, complete, display-only reference** doing precisely this scope
(EDID + PLL/mode-set + scanout, zero 3D) in ~22k lines of formally-analysed SPARK/Ada — versus
i915's ~400k lines. It is a *read-and-reimplement* reference, not drop-in code (GPL-2.0-or-later,
and Ada, not Zig).
- **The clean-room, permissively-licensed path is real** — you can implement from the PRM without
reading GPL code, and Haiku's MIT `intel_extreme` is a permissive precedent. This is the decisive
contrast with NVIDIA, where no vendor register spec exists.
- **The practical catch is hardware, not software.** On a desktop with an RTX 3060, the monitor is
almost certainly cabled to the *card*, so an iGPU driver would light a dark motherboard port; the
CPU may be an **F-SKU with the iGPU fused off entirely**; and every clean-room reference targets
*older* Intel. Intel is the right target to **learn** display bring-up — "run it on my machine"
is a separate, machine-dependent question that may not resolve in the reader's favour.
- **Recommendation:** as with the NVIDIA doc, GOP already gives native-resolution scanout with zero
GPU code. A native Intel driver buys runtime mode changes, hardware vsync, and multihead — and it
reaches "first pixel" far faster than the NVIDIA path *if* the target machine actually has a
usable, cable-attached iGPU of a documented generation.
## Display engine architecture, and why it's separable
For the common single-display path (SST DisplayPort / HDMI / eDP), the Intel display data flow is a
small, fully documented, essentially fixed sequence:
```
memory surface → PLANE(s) → PIPE → TRANSCODER → DDI (drives IO/PHY) → connector
```
The Tiger Lake PRM Vol 12 states it verbatim: *"The front end of the display contains the pipes.
The pipes connect to the transcoders. The transcoders, except for wireless, connect to the DDIs to
drive the IO/PHY."* A **pipe** blends planes (primary/sprite/cursor) into one raster stream; the
**transcoder** wraps it in port-protocol timing (DP/HDMI/eDP/DSI); the **DDI** is the physical port
and PHY. Pipe, Planes, Transcoder, and Digital Display Interface are each first-class PRM chapters
with per-object files in libgfxinit
([TGL PRM Vol 12](https://cdrdv2-public.intel.com/705833/intel-gfx-prm-osrc-tgl-vol-12-display-engine.pdf)).
**Two honest qualifications** the raw research overstated (per verification):
- The pipeline is *not* strictly linear in all cases — the same PRM pages document optional branches
a minimal driver simply ignores (wireless writeback to memory, MIPI DSI, DisplayPort multistream
many-to-one, DSC/tiled pipe-joining). Ignoring them does not weaken feasibility.
- The four-object model *as named* is **Haswell-onward** (DDI introduced ~2013), not "every gen."
Pre-Haswell used FDI + PCH transcoders + port-specific encoders. Within the modern iGPU range
danos would realistically target (Skylake → Meteor/Lunar Lake) the model is stable.
**The DPLL/clock block is a separate, per-port programmable clock source** and is one of the harder,
most gen-specific pieces: pick/enable a PLL, route its output to the DDI, then bring up the port.
The register layout and divider math change substantially per generation — pre-SKL SPLL/WRPLL/LCPLL,
Skylake+ shared DPLL03, Gen11+ combo-PHY plus Type-C MG/DKL PLLs. Pixel-clock computation is a
classic per-gen rewrite.
### Separable from render — the single most important enabler
The display is a distinct register domain from render/media, and this is confirmed at the primary
level: the TGL PRM ships display as its own volume (Vol 12), separate from Render Engine (Vol 9) and
Media (Vol 11); Linux's KMS "is provided by Intel Display Driver, and **shared with drm/xe**"
([kernel.org i915](https://docs.kernel.org/gpu/i915.html)) — i.e. the display module is
reused across two different GPU drivers. A full mode-set lights a display end-to-end using only power
wells, PLL/port-clock, DDI-buffer/PHY, transcoder and pipe registers — **zero render commands, zero
GEM objects, zero command-streamer.** libgfxinit is decisive proof: complete EDID + modeset +
framebuffer with no render/3D code at all.
Two carve-outs the "touches ONLY display registers" phrasing needs (per verification), **neither of
which drags in the render engine**:
1. A mode-set that changes the **Core Display Clock (CDCLK)** frequency/voltage pokes the shared **GT
Driver Mailbox** (PCODE/PCU power-controller interface), per Vol 12's own "Display Voltage
Frequency Switching" step. A trivial register handshake, documented alongside the display sequence.
2. The primary plane's surface register (`PLANE_SURF`) holds a **GGTT graphics address** (a
memory-interface concept, not covered in Vol 12). Using pre-mapped stolen memory — as libgfxinit
does — sidesteps any active GGTT programming. See [Memory and scanout](#memory-and-scanout).
### Per-gen churn: what's stable, what you rewrite
The **object model** (pipes/planes/transcoders/DDIs, GMBUS-for-EDID, double-buffered plane registers
armed atomically) is conceptually stable from Ironlake/Haswell through Tiger Lake. What you rewrite
per generation is:
1. the **CPU-vs-PCH split and interconnect**,
2. the **port/PHY + DPLL** programming,
3. **register offsets + power-well / CDCLK topology**, and
4. the **mode-set enable sequence itself** (power-well ordering, PLL lock, DDI-buffer enable,
transcoder clock-select) — an effective fourth axis the raw research folded into (1)/(2).
Interconnect eras, with the timeline **corrected** (the cited Haiku doc was chronologically loose):
- **Gen5 Ironlake (2010) → Ivy Bridge:** FDI (Flexible Display Interface) links the CPU display
engine to PCH-resident ports. The FDI/PCH-split era begins at **Ironlake**, not Gen7.
- **Haswell (Gen7.5):** the main digital outputs come **back onto the CPU die as DDIs** (DDI A = eDP)
— the *opposite* of "moving output to the PCH," and it collapses the FDI/PCH dance **for the
digital ports only**. FDI is **retained** for the legacy VGA/CRT path (DDI E → PCH CRT DAC), so a
driver gets the single DDI code path only by omitting analog VGA (which a minimal driver does).
- **Skylake (Gen9):** reworks clock/PLL, CDCLK, and the power-well model; introduces the optional DMC.
- **Gen11 Ice Lake / Gen12 Tiger Lake:** add combo-PHY + USB-Type-C/Thunderbolt MG/DKL PHYs — the
single biggest cost increase, and the reason "newest silicon" is *not* the easiest target. (DSC is
documented per-**pipe**; MSO is an eDP feature — not "per-transcoder" as the raw research said.)
### The tractable sweet spot
The documented, tractable sweet spot for a from-scratch display-only driver is the
**Haswell (Gen7.5) / Broadwell (Gen8) DDI family, with Skylake (Gen9) as the modern-hardware pick**
since it shares the same DDI object model. Rationale:
- Broadwell has a complete, freely downloadable
[PRM Vol 11 Display](https://cdrdv2-public.intel.com/690828/intel-gfx-prm-osrc-bdw-vol-11-display.pdf);
its engine (3 pipes A/B/C, 4 transcoders incl. transcoder-EDP that floats onto any pipe, DDI AE,
WRPLL/SPLL/LCPLL) is the classic "DDI + transcoder + WRPLL" model.
- It predates the combo-PHY / Type-C / MG-DKL complexity of Ice Lake / Tiger Lake.
- libgfxinit's DDI **connector/EDID/DP layer is uniform from Haswell through Coffee Lake**, so the
hardest-to-get-right port logic generalises widely.
Two supporting claims from the raw research are **wrong and corrected here (verification):**
- **The BDW and SKL PRMs are NOT 0BSD-licensed.** Both carry a Creative Commons
**Attribution-NoDerivatives** notice. Only the *newer* OSRC PRMs (Tiger Lake 2021 onward) put their
embedded code samples under **Zero-Clause BSD**. So for the recommended Haswell/Broadwell/Skylake
generations there are no "copy-pasteable 0BSD code samples" — the legal basis is *reimplementation
from a CC-BY-ND spec* (register facts are not copyrightable), not copying.
- **FDI+PCH is not fully eliminated on Haswell/Broadwell.** The BDW PRM keeps FDI for the DDI E → PCH
CRT DAC. The "one DDI code path" holds only for the digital outputs a minimal driver targets.
Sandy/Ivy Bridge (Gen6/7) is where the hobby-doc walkthroughs concentrate (the OSDev GMBUS/EDID
material) but carries the FDI+PCH split cost. *(Low confidence on the OSDev specifics — the wiki
returns 403 to automated fetches and its "guaranteed to work" phrasing is a hobby assertion, not a
silicon guarantee.)*
## Documentation — and the clean-room question
This is the crux of the whole comparison. **Intel hands you the register spec that NVIDIA withholds.**
- The Tiger Lake **"Vol 12: Display Engine"** PRM is a real, first-party, open-source document —
**433 pages, verified by direct download** — with named registers + addresses + bitfield tables
(`TRANS_DDI_FUNC_CTL`, `DDI_BUF_CTL`, `DP_TP_CTL`, `PLANE_STRIDE`, `DPLL_CFGCR0/1`, `CDCLK_CTL`,
`PWR_WELL_CTL_DDI`, …) and **numbered, step-by-step enable sequences** with explicit writes, wait
conditions, and microsecond timeouts. It even includes the "magic value" tables older PRMs deferred
to the driver (DisplayPort PLL DCO/divider values; voltage-swing/de-emphasis in mV). *"A spec you
could write a driver from directly"* is well-supported, not hyperbole
([TGL Vol 12](https://cdrdv2-public.intel.com/705833/intel-gfx-prm-osrc-tgl-vol-12-display-engine.pdf)).
- **Clean-room, permissively-licensed implementation is legally and practically feasible from the
PRM alone.** CC-BY-ND governs redistribution of the *document*; register addresses and bit
definitions are functional facts, and original code implementing a described hardware interface is
not a derivative of the PDF. *(This is standard copyright reasoning, not adjudicated case law —
treat it as well-grounded, not settled.)* Two independent implementations already exist built
essentially from these docs (libgfxinit, Haiku), so the spec is demonstrably sufficient.
**The documentation ceiling — corrected.** The raw research said public PRMs stop "roughly at Ice
Lake / Tiger Lake." Verification refuted this: full public **"Vol 12 Display Engine"** PRMs exist for
Ice Lake, Lakefield, Tiger Lake, Rocket Lake, DG1, **and DG2/Arc "Alchemist" (Gen12.5, 2022)**
[the ACM display PRM is public](https://www.x.org/docs/intel/ACM/intel-gfx-prm-osrc-acm-vol12-displayengine.pdf).
The genuine cliff is **Meteor Lake (2023) and newer**: those have only a high-level architecture
overview, no register-level display PRM, and i915 references their display registers by opaque
internal **Bspec numeric IDs**. Alder Lake and Raptor Lake iGPUs are Gen12 Xe-LP display — the same
IP as Tiger Lake — so despite lacking a dedicated PRM they are effectively covered by the TGL PRM.
Net: a from-docs driver can confidently target **Skylake through DG2/Arc**, which is essentially the
entire current laptop/NUC installed base; only Meteor Lake and later slide back toward the NVIDIA
situation (reverse-engineering or reading GPL i915). The PRMs also survived 01.org's shutdown and are
mirrored in several stable places (Intel's cdrdv2 host, the
[Igalia CC-BY-ND archive](https://github.com/Igalia/intel-osrc-gfx-prm) for Gen4Gen9.5,
[kiwitree](https://kiwitree.net/~lina/intel-gfx-docs/prm/), x.org) — not a single point of failure.
*(Note: the Igalia archive stops at Kaby Lake and contains no Display Engine volume; the TGL/DG2
display PRMs are separate Intel/x.org downloads.)*
## coreboot libgfxinit — the native reference
[libgfxinit](https://doc.coreboot.org/gfx/libgfxinit.html) is the closest thing to a template danos
could ask for: a self-contained **native modeset library** (no VBIOS/int10, no firmware blobs) that
probes displays via EDID over DDC/I²C and DP AUX, and drives LVDS, eDP, DP13, HDMI13, analog VGA,
plus USB-C DP/HDMI alt-mode on Tiger Lake. It sets up pipes (Primary/Secondary/Tertiary), planes,
transcoders, PLLs, panel power/backlight, the GTT, and framebuffer scanout — **display-only, zero
3D/media/compute**, which is exactly danos's scope. Its public entry is essentially
`Initialize()` then `Update_Outputs(Pipe_Configs)`, where each `Pipe_Config` carries
`{Port, Framebuffer, Cursor, Mode}` — a near-perfect fit for a pluggable scanout backend.
Why it beats i915 as a reference (**verified by measurement**): **131 Ada source files, ~818 KB,
~22k code lines** across *all* generations, factored precisely along the axes you care about (`edid`,
`dp_aux`, `dp_training`, `pipe_setup`, `transcoder`, `plls`, `connectors`, `port_detect`), with
**none** of the DRM/KMS/GEM/TTM, GT/3D, RC6/RPS, or GuC/HuC machinery that makes
`drivers/gpu/drm/i915` **~419k lines / 900 files / 12 MB**. (A grep confirms *zero* gem/ttm/guc/huc/
execbuf identifiers in the tree.) It depends only on a small HW-access shim, `libhwbase`
(`HW.PCI`, `HW.Port_IO`, `HW.MMIO`, `HW.Time`), which maps naturally onto danos's MMIO-grant + IPC
primitives — you provide Zig equivalents and the modeset logic sits on top. *(Correction to the raw
research: the widely-quoted "~1314k LOC" is only the generic `common/` layer; the eight
per-generation subdirs roughly double it.)*
**It is a read-and-reimplement reference, not drop-in code.** Two hard constraints:
- **License is GPL-2.0-or-later** (the COPYING file is GPLv2; per-file headers add "or any later
version"). The CC-BY-4.0 on the docs *site* is a footer, not the source license. Copyleft applies
to ported code.
- **It is SPARK/Ada, and designed to run as coreboot boot-firmware**, not a runtime OS driver. A
danos port means either an Ada/GNAT toolchain in the build or hand-transliteration into Zig; the
SPARK "absence of runtime errors" proof does **not** carry over to your reimplementation (and note
it proves absence of runtime errors, **not** functional modeset correctness).
Two more caveats worth knowing: its **error handling is limited** — "only the case that no display
could be found counts as failure"; a later DP link-training failure is *not* propagated. And its
**verified-in-coreboot** hardware list stops at **Coffee Lake + Apollo Lake**, even though the tree
contains a `tigerlake/` directory (Ice Lake has no directory at all, and Alder Lake support is only
"begun"). So treat Haswell..Coffee Lake as the trustworthy transliteration window and TGL as
present-but-less-proven.
The orchestration reads as a clean state machine (`hw-gfx-gma.adb` `Enable_Output`):
`Fill_Port_Config → Preferred_Link_Setting → PLLs.Alloc → [retry] Connectors.Pre_On →
Display_Controller.On → Connectors.Post_On`, with a literal *"try each DP-lane configuration twice"*
inner retry and an outer link-setting step-down. `hw-gfx-dp_training.adb` (398 lines) is a complete,
generic DP link-training implementation (TP1/TP2/TP3, CR + EQ loops, swing/pre-emphasis adjust from
sink status). Per-generation buffer translations plug in underneath via
`Program_Buffer_Translations`, gated on `Config.Has_DDI_Buffer_Trans`. All of this was confirmed
against the source line-by-line.
## The EDID + mode-set path (Haswell/Broadwell target)
The whole path is memory-mapped register programming with polled status bits — no command ring, no
microcode, no DMA channel.
**EDID over DDC (GMBUS).** Pure MMIO poking of the GMBUS I²C controller (`GMBUS0``GMBUS5`): `GMBUS0`
selects pin-pair/port + clock; `GMBUS1` carries slave address (`0x50` for EDID), byte count,
direction, SW-ready; `GMBUS2` exposes HW-ready/NAK/ACTIVE to poll; `GMBUS3` is a 4-byte data FIFO;
`GMBUS5` gives the 2-byte segment index for E-DDC. A read is: write `GMBUS0`, write `GMBUS1`
(`CYCLE_WAIT | count | SLAVE_READ | SW_RDY | slave<<addr`), loop {poll `HW_RDY`, read 4 bytes}, then
STOP ([i915 intel_gmbus.c](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/i915/display/intel_gmbus.c)).
**EDID + DPCD over DP AUX.** For DisplayPort/eDP, EDID (as I²C-over-AUX to `0x50`) and all DPCD
capability/link-status registers are read over the AUX channel: per-DDI `DDI_AUX_CTL` + 5×
`DDI_AUX_DATA`. Build a 35 byte header + payload, set SEND_BUSY, poll it clear, read
DONE/TIMEOUT/RECEIVE_ERROR. Message size 120 bytes; spec requires 3 retries. On Haswell/BDW the AUX
clock divider is programmed explicitly; SKL+ derive it automatically
([i915 intel_dp_aux.c](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/i915/display/intel_dp_aux.c)).
Both GMBUS and DP-AUX live in libgfxinit's shared `common/` cheap and nearly gen-invariant.
**The mode-set is a fixed, documented register sequence.** The Broadwell DisplayPort enable order
(verbatim from BDW PRM Vol 11, pp.9899): (1) DDI lane capability; (2) panel power sequencing if
needed; (3) enable the CPU display PLL (WRPLL/SPLL) and wait ~20 µs; (4) Port Clock Select DDI,
enable `DP_TP_CTL` with training pattern 1, configure `DDI_BUF_TRANS`, enable `DDI_BUF_CTL`, wait
>518 µs, run link training, set `DP_TP_CTL` to Normal (Idle first for eDP); (5) Transcoder Clock
Select, enable the plane, panel fitter if needed, program transcoder timings + M/N/TU, enable
`TRANS_DDI_FUNC_CTL`, enable `TRANS_CONF`, then backlight. Disable is the exact reverse — a bounded
checklist.
**DisplayPort/eDP link training is driver-driven in software over AUX** — the CPU runs the
clock-recovery and channel-equalization state machines by hand; it is **not** offloaded to a hardware
sequencer or firmware. The source side exposes only primitives: `DP_TP_CTL` selects the training
pattern the port emits; `DDI_BUF_CTL`/`DDI_BUF_TRANS` set voltage-swing/pre-emphasis. The driver
loops: emit pattern + set source levels → write `TRAINING_PATTERN_SET` (DPCD 0x102) + `TRAINING_LANEx_SET`
(0x103) over AUX → delay (100 µs CR / 400 µs EQ) → read `LANE_STATUS` → on failure adjust to the
sink's `ADJUST_REQUEST` values and retry. A few hundred lines of ordinary CPU/AUX code (libgfxinit
`Train_DP`: CR loop 1..32, EQ loop 1..6). **This is the single fiddliest, most fragile piece** — a
TMDS/HDMI panel avoids it entirely, and targeting an already-lit eDP panel avoids most of it.
**The clock (WRPLL) is documented divider math, not a magic table.** On Haswell/BDW the WRPLL derives
the symbol clock from a 2700 MHz LCPLL reference through R2/N2/P dividers with VCO 24004800 MHz —
small integer arithmetic. DP is *easier* than HDMI because it runs at a few fixed link rates (1.62 /
2.7 / 5.4 GHz), so a DP/eDP-only minimal driver can often use fixed rates and skip most of the search.
**Plane/scanout programming is trivial for a compositor.** The primary plane is `PRI_CTL`
(enable + pixel format), `PRI_STRIDE`, `PRI_SURF` (surface base — writing it triggers the atomic
update), `PRI_OFFSET`; formats include 32-bit BGRX 8:8:8 and 16-bit BGRX 5:6:5 — a direct match for a
linear XRGB compositor buffer. Plane registers are double-buffered and latch at vblank via an
**arming** write — so a page-flip is "write base + stride + size, then the arming write." This is
*exactly* the primitive danos's damage-driven compositor already expresses over GOP/virtio-gpu; the
incremental work is "program these display-domain registers," not a new scanout model. The panel
fitter (`PF_WIN_POS`/`PF_WIN_SZ`/`PF_CTRL`) can be left disabled for native-resolution scanout;
Skylake+ replaces it with a shared pipe-scaler (`PS_CTRL`).
**Smallest useful target:** eDP (DDI A / transcoder-EDP) or a single DP output at native resolution,
panel fitter off, plane in 32bpp XRGB. That is: GMBUS + I²C-over-AUX EDID/DPCD, one fixed-rate or
WRPLL config, the ~20-step enable sequence, the software CR/EQ loop, and `PRI_*` plane setup with
`PRI_SURF`-write flips. Out of scope: 3D, media, tiling, RC6/power-gating, PSR, audio.
## Memory and scanout
This is where Intel's *architecture* — not just its docs — makes the job smaller, and it is the
biggest single simplification versus a discrete GPU.
- **No VRAM.** Intel iGPUs have a unified memory architecture; the display scans out of ordinary
**system RAM** addressed through the **Global GTT (GGTT)**. The only way to give the GPU memory is
to bind system pages into the GGTT
([i915/GEM crashcourse](https://blog.ffwll.ch/2012/10/i915gem-crashcourse.html)).
- **The plane surface register is a GGTT offset**, not a raw physical address — the display walks the
GGTT to fetch pixels, so a scanout buffer must be GGTT-mapped (global, not per-process). libgfxinit
writes the framebuffer offset straight into `DSPSURF`/`PLANE_SURF` masked to 4 KB.
- **Linear (untiled) scanout is a first-class supported mode** — the plane's tiling field value 0 is
Linear. No X/Y/Yf tiling engine is needed for a display-only driver. (UEFI GOP itself hands off a
linear framebuffer the plane is already scanning.)
- **No memory manager.** You need only (1) some contiguous-ish system pages and (2) GGTT PTEs
pointing at them (`physical_addr | valid_bit` — the GGTT is a flat single-level array of PTEs in
the `GTTMMADR` MMIO BAR), then program the plane. **No GEM/TTM/PPGTT/GuC.** coreboot's native-init
literally does `for(i…) WRITE32(base + i*inc | 1, (i*4) | 1)`.
- **"Stolen memory"** (GSM/DSM) is firmware-reserved system RAM where the firmware places the GGTT
itself and the boot framebuffer. A driver is not obligated to keep scanout there — it can rebind
GGTT entries to its own pages. Stolen memory matters mainly for *inheriting* the GOP framebuffer at
handoff.
**The contrast with NVIDIA is stark.** On a discrete GPU the scanout surface must live in **VRAM**
(nouveau always pins scanout to VRAM), CPU access goes through the **BAR1** aperture (which on
consumer cards can be far smaller than total VRAM unless Resizable BAR is on), and you need a
contiguous aligned VRAM allocator plus a BAR1 mapping. The Intel iGPU path **eliminates all of that**
— scanout is plain system RAM, and a userspace compositor can write the framebuffer pages directly
(as danos already does with the GOP WC framebuffer).
Because danos boots via GOP, an Intel driver attaches to a display whose **GGTT is already populated
and whose plane is already scanning a linear framebuffer at native resolution.** A minimal driver can
reuse that live mapping and reprogram the running plane rather than come up from cold — the same
"attach to a live display" advantage the NVIDIA doc identifies, but with a far smaller register
surface and no firmware wall. *(Low-confidence, per-target details to pin from the specific gen's
PRM: GGTT PTE size — 4-byte pre-gen8 vs 8-byte gen8+ — the `GTTMMADR`/aperture BAR layout, surface
alignment — 4 KB floor but some gens/tilings want 256 KB — and whether the display's GGTT-mediated
DMA sits before or after danos's M16 IOMMU on the target platform.)*
## Firmware
A minimal display-only Intel driver is **effectively firmware-free — more so than NVIDIA.**
- **DMC (Display Microcontroller, "CSR", Skylake+) is NOT required for mode-set or scanout.** Its
sole job is saving/restoring display-engine registers across DC5/DC6 low-power idle. Absent, i915
prints *"Failed to load DMC firmware … Disabling runtime power management"* and the display
mode-sets and scans out normally — you lose only the deep display idle states, not output
([intel_dmc.c](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/i915/display/intel_dmc.c);
corroborated by multiple distro bug threads). *(A source-level `HAS_DMC` early-return citation would
strengthen this beyond distro testimony, but the conclusion is well-supported.)*
- **Pre-Skylake parts have no display microcontroller at all** yet perform full mode-set (and even
Panel Self Refresh). This confirms the display engine is fundamentally CPU/MMIO-driven; the
microcontroller is an add-on for autonomous idling, not a prerequisite for lighting a panel.
Targeting a pre-Skylake or DMC-optional generation sidesteps the question entirely.
- **GuC and HuC are render/media microcontrollers on the GT side** — GuC schedules the render engines,
HuC assists HEVC/H.265 codec (plus later HDCP/PXP/GSC). Neither is in the scanout path; a
display-only driver never loads them
([kernel.org microcontrollers](https://docs.kernel.org/gpu/i915.html)).
- **PSR firmware lives on the panel**, not in the OS — a minimal driver simply doesn't enable PSR.
- **Type-C/TCSS (Ice Lake+) firmware** (PMC/IOM/PHY) is part of platform BIOS/coreboot init and the
hardware, *not* a signed blob the display driver loads at runtime. A driver attaching to an
already-lit GOP connector, or targeting classic DDI ports, avoids it. *(Cold DP-alt-mode changes
from a userspace driver on modern TCSS platforms were not traced to primary source — flagged.)*
There is **no signed-firmware wall over the Intel GPU at all** on the display path. This is the
architectural opposite of NVIDIA's mandatory, unsignable, ABI-unstable GSP — which even on the
near-side "direct" display path is a permanent maintenance liability for anything beyond scanout.
## Licensing
The situation is *better* than NVIDIA's but still nuanced.
- **The two best code references are both GPL** — Linux i915 (GPL-2.0) and coreboot libgfxinit
(GPL-2.0-or-later). You cannot copy either into a permissively-licensed danos. libgfxinit's WRPLL
divider math is itself copied from i915, so it carries the same encumbrance.
- **But you don't need to copy code.** The Intel PRM is a *specification*, and a clean-room Zig
implementation written from the PRM (using libgfxinit/i915 only to understand behaviour, never to
copy) is legitimate — register numbers and bit definitions are functional facts, not copyrightable
expression. This is the exact inverse of the NVIDIA case, where no such spec exists and the only
guide is the GPL/RE'd code itself.
- **A permissive precedent exists: Haiku's `intel_extreme` is MIT-licensed** and was built from
Intel's public docs. So if danos wants a permissive license, the model is: implement from the PRM,
optionally read MIT Haiku for structure, treat GPL libgfxinit/i915 as documentation-of-last-resort.
- **A licensing nuance on the recommended generations:** the "copy the 0BSD PRM code samples" shortcut
only applies to Tiger-Lake-era (2021+) PRMs. The Haswell/Broadwell/Skylake PRMs are CC-BY-ND, so
their register *facts* are free to implement but there are no code samples to lift.
As with the NVIDIA doc: danos's userspace-driver-over-IPC model (a driver is a separate process behind
a defined protocol) is the cleanest possible license boundary if the project ever chooses to ship a
GPL display-driver binary and keep the rest of danos permissive — but that is a boundary judgement
wanting real diligence, not a settled fact. The clean-room-from-PRM route avoids the question.
## Prior art outside Linux
This is a **real contrast with NVIDIA**, where no one has built a from-scratch native driver outside
Linux. For Intel there are **multiple independent, non-Linux, clean-room native modeset
implementations** to learn from:
- **coreboot libgfxinit** — SPARK/Ada, G45/GM45 and Arrandale → Coffee Lake + Apollo Lake (TGL
in-tree), the strongest structural reference.
- **Haiku `intel_extreme`** — modeset-only (no 2D/3D accel), **MIT-licensed**, i845 through Sandy
Bridge solid, newer Gemini/Ice/Tiger Lake in progress but "hit or miss, as the driver lags behind
the specs" ([Haiku generations](https://www.haiku-os.org/docs/develop/drivers/intel_extreme/generations.html),
[Phoronix Sept 2024](https://www.phoronix.com/news/Haiku-OS-September-2024)).
- **SerenityOS** — added basic native Intel graphics ([PR #6277](https://github.com/SerenityOS/serenity/pull/6277)),
though only for very old ICH7-class hardware.
- **managarm** — native Intel G45 support.
The catch: **every clean-room non-Linux implementation targets old hardware.** A modern Gen12 "Xe"
desktop iGPU is beyond all of them; for the very newest parts only GPL i915 covers the registers. So
the wealth of prior art is real but concentrated below Tiger Lake.
## The practical desktop caveat
Before any effort estimate is trusted, three hardware realities — the honest reason "Intel is easier"
does **not** automatically mean "it'll light up the reader's monitor":
1. **Muxing / cabling.** On a desktop with a discrete RTX 3060, the monitor is almost certainly
plugged into the *card's* outputs, not the motherboard's. An iGPU driver would light a
**different, currently-dark** output. To see danos on Intel the reader would have to physically
move the cable to a motherboard video port **and** likely enable the iGPU / "IGD Multi-Monitor" in
BIOS. Intel-first probably does **not** light the current display without re-cabling.
2. **No iGPU at all.** Intel **F-SKU** desktop chips (i5-9400F, i5-12400F, i5-13400F, i7-13700KF, …)
ship the graphics **fused off** and cannot be re-enabled. These are extremely common in
budget/mid gaming builds paired with an RTX 3060. On an F-SKU (or an X-series HEDT part) the
Intel-iGPU path is a **non-starter** regardless of cabling.
3. **Generation coverage.** If the CPU *is* a recent non-F part, its iGPU may be Gen12 Xe (Alder/
Raptor Lake), beyond libgfxinit's verified set and beyond most non-Linux prior art — leaving GPL
i915 (or the TGL-class PRM, which covers Alder/Raptor display IP) as the only reference.
A cleaner path for *learning* without the hardware lottery: an older bare-metal Intel box (Haswell/
Skylake NUC or laptop) whose panel is natively on the iGPU. Note QEMU does **not** emulate an Intel
iGPU display engine, so a VM cannot exercise a real Intel modeset path — virtio-gpu (already working)
is the VM answer.
## Alternatives, and the honest Intel-vs-NVIDIA verdict
| Option | What you get | The tradeoff |
|---|---|---|
| **Stay on GOP** (working today) | Native-res scanout, zero GPU code/firmware/maintenance | Resolution frozen at ExitBootServices; no runtime mode change, no hardware vsync, no multihead |
| **Intel iGPU, reuse-GOP** | EDID read + plane page-flips on the GOP-set mode | Still bounded to GOP's resolution; but real driver-owned scanout |
| **Intel iGPU, full modeset** (this doc) | Runtime modeset, vsync, multihead, from public docs | Tier 23 effort; DP link training; per-gen churn; **needs a cable-attached, documented iGPU** |
| **Native NVIDIA GA106 direct** ([nvidia-gpus.md](nvidia-gpus.md)) | Same, on the RTX 3060 the monitor is actually plugged into | **Tier 4**; GPL-only reference; DMA channel modeset; de-emphasised legacy path |
| **GA106 via GSP/OGKM** | Also unlocks 3D later | Tier 5; unstable version-pinned firmware ABI |
**The verdict for *this reader* (RTX 3060 box):** For pure "see danos on my screen," **NVIDIA-direct
is paradoxically the more relevant path**, because the monitor is already cabled to the 3060 and GOP
already drives it — a native NVIDIA driver reprograms *that* live display. An Intel driver, however
much easier to *write*, likely lights a dark motherboard port the reader isn't looking at, or hits an
F-SKU with no iGPU.
**The verdict for *learning display bring-up*:** **Intel wins decisively.** Public register PRMs, four
independent open reference drivers, an MIT precedent (Haiku), a compact formally-analysed blueprint
(libgfxinit), no signed-firmware wall, no VRAM/BAR memory manager, and a legitimate permissive
clean-room path. It reaches "first pixel" far faster than the NVIDIA native path — *on hardware that
actually has a cable-attached, documented Intel iGPU.* Those two goals — "run on my machine" and
"learn the craft" — point at different silicon, and that is the honest bottom line.
## "First light" milestones — a danos `.scanout` service
Framed as a danos `.scanout` service (like the virtio-gpu and proposed NVIDIA ones), inheriting the
GOP-initialized display — no firmware, no cold POST:
1. **PCI/BAR bring-up** — enumerate the iGPU, map its MMIO BAR (`GTTMMADR` + register block) and the
aperture BAR via danos MMIO grants; confirm the display engine is GOP-live.
2. **EDID** — implement GMBUS DDC (`0x50`) and DP AUX; read + parse the panel EDID and DPCD caps.
*(Smallest self-contained, gen-invariant milestone — a good first commit.)*
3. **First pixel = reprogram, don't re-modeset** — with GOP's mode and GGTT mapping inherited,
reprogram the running plane (`PRI_CTL`/`PRI_STRIDE`/`PRI_SURF`, linear, 32bpp XRGB) to point at a
danos-owned system-RAM buffer; prove a page-flip via the `PRI_SURF` arming write on the *current*
mode before changing timings. This defers the entire DPLL/DDI/transcoder/link-training surface —
the hardest, most gen-specific ~70% of the work.
4. **GGTT ownership** — write your own GGTT PTEs (via an MMIO grant to `GTTMMADR`) pointing at
compositor-owned pages, for double-buffered damage-driven present.
5. **Wire into the compositor `.scanout` backend** (`attach_scanout`); add vsync via the display
vblank interrupt (IRQ-as-IPC).
6. **Full mode-set** (the hard, gen-specific step) — for one chosen generation (Haswell/Broadwell or
Skylake): WRPLL/DPLL programming, the ~20-step DDI/transcoder/pipe enable sequence, panel power
sequencing for eDP (`PP_CONTROL`/`PP_ON_DELAYS`/`PP_OFF_DELAYS` — a common black-screen pitfall).
7. **DisplayPort link training** — only if the panel is DP and GOP's link can't be reused; the
software CR/EQ state machine over AUX. TMDS/HDMI avoids it; a live eDP panel avoids most of it.
8. **Multihead**, then optionally a second generation once one is solid.
Keep the GOP backend as the fallback the whole way — a stall at any step still leaves danos with a
working display, exactly the resilience v2 already provides via re-attach.
## Reading list
**Native reference — coreboot libgfxinit (GPL-2.0-or-later, SPARK/Ada):**
- `common/hw-gfx-gma.adb``Enable_Output`, the end-to-end modeset state machine.
- `common/hw-gfx-dp_training.adb` — the complete generic DP link-training CR/EQ loops.
- `common/hw-gfx-gma-pipe_setup.adb` — plane/pipe/scaler + `DSPSURF`/`DSPSTRIDE`/`DSPCNTR` scanout.
- `common/hw-gfx-gma-transcoder.adb` — timing generator; `common/hw-gfx-edid.adb`,
`hw-gfx-gma-i2c.adb`, `hw-gfx-dp_aux_ch.adb` — EDID/DDC/AUX; `hw-gfx-gma-registers.ads` — offsets.
- `common/haswell*/`, `skylake/`, `tigerlake/` — the per-gen PLL/PHY/buffer-translation backends.
**Vendor register specs — Intel OSRC PRMs:**
- [Broadwell Vol 11: Display](https://cdrdv2-public.intel.com/690828/intel-gfx-prm-osrc-bdw-vol-11-display.pdf)
(CC-BY-ND) — the recommended Haswell/Broadwell-class enable sequences, plane, panel fitter.
- [Tiger Lake Vol 12: Display Engine](https://cdrdv2-public.intel.com/705833/intel-gfx-prm-osrc-tgl-vol-12-display-engine.pdf)
(code samples 0BSD) — the most complete modern reference incl. PLL/voltage-swing value tables.
- [DG2/Arc Vol 12: Display Engine](https://www.x.org/docs/intel/ACM/intel-gfx-prm-osrc-acm-vol12-displayengine.pdf)
— the newest public display PRM (Gen12.5, 2022).
- [Igalia CC-BY-ND archive](https://github.com/Igalia/intel-osrc-gfx-prm) (Gen4Gen9.5) and the
[kiwitree mirror](https://kiwitree.net/~lina/intel-gfx-docs/prm/) — stable mirrors.
**GPL reference-of-last-resort — Linux i915 display:**
- `intel_gmbus.c`, `intel_dp_aux.c` — the concrete EDID/DDC and DP-AUX register sequences.
- `intel_ddi.c` / `intel_ddi_buf_trans.c`, `intel_cdclk.c`, `intel_dpll_mgr.c` — DDI/CDCLK/PLL;
`i9xx_plane.c`, `intel_crtc.c` — plane/pipe; `intel_dp.c` — link training. Huge and modular; a
reference to confirm undocumented quirks, not a template.
**Permissive prior art — Haiku `intel_extreme` (MIT):**
- [`src/add-ons/kernel/drivers/graphics/intel_extreme/`](https://github.com/haiku/haiku/tree/master/src/add-ons/kernel/drivers/graphics/intel_extreme/)
— a second independent modeset-only driver; MIT, so structurally readable for a permissive danos.
- [generations.html](https://www.haiku-os.org/docs/develop/drivers/intel_extreme/generations.html)
— the best plain-English per-generation fault-line map.
## Open questions (unresolved by the survey)
- **Does the target machine have a usable, cable-attached iGPU at all?** F-SKU check, CPU generation,
and monitor cabling must be resolved before any effort estimate is trusted (see
[practical caveat](#the-practical-desktop-caveat)).
- **Does danos even need native mode-*setting*, or only plane/scanout control on the GOP-set mode?**
If runtime mode changes aren't required, the driver collapses to EDID + plane page-flips, dropping
the DPLL/DDI/link-training ~70% of the work.
- **GGTT vs raw physical:** confirm from the exact target-gen PRM that `PLANE_SURF` is interpreted as
a GGTT graphics address (well-established, but per-gen confirmation advisable), and the PTE size /
`GTTMMADR` / aperture layout for writing GGTT entries.
- **Reuse the firmware/GOP GGTT + framebuffer, or install your own GGTT entries?** The latter (needed
for double-buffering) means writing GGTT PTEs from the userspace driver via an MMIO grant.
- **eDP panel power sequencing** (`PP_*`, T1T12 delays) — not covered in this pass and a common
black-screen source.
- **IOMMU interaction** — whether the display's GGTT-mediated DMA needs IOMMU passthrough for the
framebuffer pages under danos's M16 IOMMU, or sits before the IOMMU on the target platform.
- **DP link-training / AUX robustness and per-generation register drift** are the dominant *risks*
not documentation scarcity.
- **Exact Haswell/BDW MMIO offsets** (commonly cited: GMBUS ~`0xC5100`, `DDI_AUX_CTL_A` ~`0x64010`,
`DDI_BUF_CTL_A` ~`0x64000`, `DP_TP_CTL_A` ~`0x64040`) were not extracted verbatim from the PRM —
confirm against `i915_reg.h` before coding.
---
*Research snapshot; verify against current libgfxinit / i915 source and the specific target
generation's PRM before building. Intel's public-PRM coverage and the muxing/F-SKU realities of a
given machine both change what is actually achievable.*