557 lines
41 KiB
Markdown
557 lines
41 KiB
Markdown
# 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 DPLL0–3, 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 A–E,
|
||
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 Gen4–Gen9.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, DP1–3, HDMI1–3, 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 "~13–14k 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 3–5 byte header + payload, set SEND_BUSY, poll it clear, read
|
||
DONE/TIMEOUT/RECEIVE_ERROR. Message size 1–20 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.98–99): (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 2400–4800 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 2–3 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) (Gen4–Gen9.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_*`, T1–T12 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.*
|