danos/docs/device-driver-development/intel-igpu.md

41 KiB
Raw Blame History

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, coreboot's libgfxinit, the Linux i915 display driver, and Haiku's intel_extreme), not an implementation. It is the companion to nvidia-gpus.md and should be read against it — the two answer the same question for opposite silicon.

Read display.md and 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).

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) — 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.

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; 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).
  • 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. 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 for Gen4Gen9.5, kiwitree, 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 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 (GMBUS0GMBUS5): 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).

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). 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).
  • 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; 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).
  • 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, Phoronix Sept 2024).
  • SerenityOS — added basic native Intel graphics (PR #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) 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.adbEnable_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:

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):

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).
  • 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.