From 05fc1764dee8b6ecb14a604b6497c2e28e31c42b Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:39:23 +0100 Subject: [PATCH] docs: native GPU driver feasibility studies (NVIDIA + Intel) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two companion research snapshots on what a minimal display-only native driver (EDID + mode-set + framebuffer scanout, no acceleration) would take as a danos .scanout backend, and how the two vendors compare. - nvidia-gpus.md — RTX 3060 (Ampere GA106). Display is not GSP-gated: nouveau's ga102.c has a direct register path, and danos's GOP boot lets a driver attach to a live, already-devinit'd display. Tier-4 effort; the register-level display code lives in GPL nouveau while the permissive OGKM reference is the hard GSP path. GA10x is the last NVIDIA family that keeps a register-level display path. - intel-igpu.md — the companion, and a materially easier, lower-tier target. Intel publishes register-level Display Engine PRMs, so a clean-room permissive driver is viable; the DMC microcontroller is optional (power states only), scanout comes from system RAM via the GGTT (no VRAM manager), and coreboot's libgfxinit is a compact native reference. The catch is hardware: an iGPU may not drive the monitor on a discrete-GPU desktop. Sourced, cited snapshots — not implementations; each ends with a "first light" milestone ladder framed as a danos .scanout service. Not yet linked from docs/README.md. --- docs/intel-igpu.md | 556 ++++++++++++++++++++++++++++++++++++++++++++ docs/nvidia-gpus.md | 246 ++++++++++++++++++++ 2 files changed, 802 insertions(+) create mode 100644 docs/intel-igpu.md create mode 100644 docs/nvidia-gpus.md diff --git a/docs/intel-igpu.md b/docs/intel-igpu.md new file mode 100644 index 0000000..ecf66ac --- /dev/null +++ b/docs/intel-igpu.md @@ -0,0 +1,556 @@ +# 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<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.* diff --git a/docs/nvidia-gpus.md b/docs/nvidia-gpus.md new file mode 100644 index 0000000..2c80862 --- /dev/null +++ b/docs/nvidia-gpus.md @@ -0,0 +1,246 @@ +# Native NVIDIA GPU support — feasibility and roadmap + +**Status: research snapshot, not implemented.** This records what a *native* display driver for a +real discrete NVIDIA GPU — specifically an **RTX 3060 (Ampere GA106)** — would take, and how it +would slot into danos's pluggable scanout architecture. It is a survey of primary sources +(NVIDIA's [open-gpu-kernel-modules](https://github.com/NVIDIA/open-gpu-kernel-modules), the Linux +[nouveau/nvkm](https://github.com/torvalds/linux/tree/master/drivers/gpu/drm/nouveau) driver, +NVIDIA's [open-gpu-doc](https://nvidia.github.io/open-gpu-doc/), and +[linux-firmware](https://github.com/NVIDIA/linux-firmware)), not an implementation. The NVIDIA +driver landscape moves quickly (GSP defaults, firmware ABIs); treat specifics as a mid-decade +snapshot and re-verify against current source before building. + +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 + +- A **minimal display-only driver** (EDID + mode-set + framebuffer scanout, **no** 3D/compute) + for the RTX 3060 **can and should avoid the GSP entirely**. nouveau has a register-level, + CPU-driven display path for Ampere (`nvkm/engine/disp/ga102.c`) that lights up GA106 with no + external firmware; the signed-firmware wall gates the **compute/graphics** engines (PGRAPH), + **not** the display controller. "GSP is mandatory on Ampere" is true only for NVIDIA's own + RM-object route. +- **danos's UEFI GOP boot is the single biggest thing in its favour.** The VBIOS/GOP has already + run devinit and brought up the display PLLs, so a driver attaches to a **live, initialized** + GA106 — no firmware load, no cold-boot POST, no devinit interpreter. You reprogram a running + display rather than bring one up from cold. +- It is still a **hard, multi-week-to-months expert effort** (effort tier ≈ 4/5) dominated by + NVDisplay channel-DMA programming, SOR/head routing, DisplayPort AUX + link training, and the + display supervisor handshake. The GSP/RM route is tier 5 (near-infeasible solo). +- The **licensing tension is counterintuitive**: the permissively-licensed reference (NVIDIA + open-gpu-kernel-modules, MIT/GPLv2) is the **hard GSP path**; the register-level display code + you actually want lives in **GPL nouveau**. See [Licensing](#licensing). +- The **window is closing**: GA10x (Ampere) is the *last* NVIDIA family with a register-level + display path — Ada (RTX 40) deleted its non-GSP display HAL. Targeting Ampere specifically + matters. +- **Recommendation:** for *this card*, GOP already gives native-resolution scanout with zero GPU + code and zero maintenance. A native driver buys only runtime mode changes, hardware + vsync/vblank, and multihead. It is justified if that runtime control is a danos goal, or to + *learn the craft* — for which an Intel iGPU or a pre-Turing NVIDIA card reaches "first pixel" + far faster. + +## The GSP wall, and why display sits on the near side of it + +On Turing and later, NVIDIA split its driver's Resource Manager into a host **CPU-RM** and a +**GSP-RM** running on an on-die RISC-V core ("Peregrine"), talking over RPC +([LWN 953144](https://lwn.net/Articles/953144/)). The GSP is a *full resource manager*, not a +display coprocessor — there is no "display-only" GSP image and no small display RPC subset. Its +boot chain is entirely signed and mandatory: a VBIOS-resident **FWSEC-FRTS** app carves a +write-protected region (WPR2), a signed **Booter** on the SEC2 falcon loads the GSP bootloader, +and that loads **GSP-RM** inside WPR. The firmware ships pre-computed signatures and the driver +picks one by an on-chip fuse-version register — **you cannot self-sign**, and there is **no stable +firmware ABI** (it is revised every driver release; nouveau and the Rust nova-core driver each pin +exactly one version). A GSP driver is a permanent maintenance liability, not a one-time build +([LWN 1037379](https://lwn.net/Articles/1037379/), +[nova-core cover letter](https://lore.freedesktop.org/nouveau/20250826-nova_firmware-v2-7-93566252fe3a@nvidia.com/T/)). + +**But display doesn't need any of that on Ampere.** `nvkm/engine/disp/ga102.c` dual-dispatches: + +``` +if (nvkm_gsp_rm(device->gsp)) return r535_disp_new(&ga102_disp, ...); // GSP RPC path +return nvkm_disp_new_(&ga102_disp, ...); // direct register path +``` + +Both branches use the same `ga102_disp` HAL and the same `GA102_DISP_*` class IDs; GSP merely +swaps register programming for RPC. GA106 (chipset `0x176`) is wired to `ga102_disp_new` in the +device table, identical to GA102/103/104/107. Ampere lit up displays via the **direct** path in +Linux 5.11/5.17 — two years before GSP-RM landed (6.7, 2023) +([ga102.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c), +[Phoronix GA106](https://www.phoronix.com/news/Nouveau-NVIDIA-GA106)). + +**Caveat — this is now the legacy path.** As of Linux 6.18, nouveau defaults to GSP on +Turing/Ampere; the direct path is a retained, forceable fallback (`nouveau.config=NvGspRm=0`, and +automatic when GSP firmware is absent). It is stable and proven, but NVIDIA and nova-core are +moving to GSP-only, and **Ada already deleted its non-GSP display HAL**. GA10x is the last family +that keeps a register-level display path. + +## What "direct" actually entails + +"Direct" is not "plain register pokes." Only SOR / PLL / DP-link / clock setup is bare MMIO. The +**mode-set and scanout themselves flow through the NVDisplay channels — a DMA pushbuffer**: + +- Display classes for Ampere (the C670 family): core `GA102_DISP_CORE_CHANNEL_DMA` (`0xc67d`), + window `0xc67e`, window-immediate `0xc67b`, cursor `0xc67a` (headers `clc67d.h` / `clc67e.h` / + `clc67a.h` in [open-gpu-doc `classes/display/`](https://github.com/NVIDIA/open-gpu-doc/tree/master/classes/display)). +- The core channel needs **instance memory, a RAMHT, DMA objects, and a channel user-MMIO + region** ([disp/chan.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/nouveau/nvkm/engine/disp/chan.c)). + The register-level "plumbing" to allocate/kick a channel is in NVIDIA's GA102 display register + manual: `NV_PDISP_FE_CHNCTL_CORE/WIN/CURS`, `NV_PDISP_FE_PBBASE/PBBASEHI` + ([dev_display_withoffset.ref.txt](https://raw.githubusercontent.com/NVIDIA/open-gpu-doc/master/manuals/ampere/ga102/dev_display_withoffset.ref.txt)). +- **Mode-set is a method stream** on the core channel: `HEAD_SET_RASTER_*`, + `HEAD_SET_PIXEL_CLOCK_FREQUENCY`, `HEAD_SET_CONTROL_OUTPUT_RESOURCE`, `SOR_SET_CONTROL` + (protocol select), viewport/scaler, then `UPDATE`. The window channel points at the scanout + surface (`SET_CONTEXT_DMA_ISO`, `SET_STORAGE`, `SET_OFFSET`). +- After `UPDATE` you must complete the display **supervisor** interrupt handshake (SV1/SV2/SV3). + +**EDID and DisplayPort are a separate subdev you must port.** open-gpu-doc documents *none* of +EDID/DDC/AUX. On the direct path you read EDID in-driver via nouveau's `nvkm/subdev/i2c`: bit-bang +**DDC/I²C at address `0x50`** (E-DDC `0x30`) for TMDS/HDMI, or native **DP AUX** in `i2c/aux.c` +for DisplayPort. DisplayPort **link training** (the `dp.c` `train_cr` / `train_eq` state machine +over AUX — clock recovery, lane/rate, voltage-swing/pre-emphasis) is the single hardest and most +fragile piece; a DVI/HDMI (TMDS) panel avoids it entirely. + +## The memory floor (smaller than you'd fear) + +Neither route hands you a framebuffer allocator — even GSP-RM does not manage the scanout +framebuffer; the driver owns VRAM and merely tells GSP where its page directory is. But +display-only is a small fraction of a full GEM/TTM stack: + +- **Pitch-linear (untiled) scanout is allowed** on nv50→Ampere — the window's storage method has a + `PITCH` layout mode, so you skip block-linear tiling math + ([wndwc37e.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/nouveau/dispnv50/wndwc37e.c)). +- The window references its surface through a simple **display context-DMA** + (`SET_CONTEXT_DMA_ISO` + a 256-byte-granular `SET_OFFSET = addr>>8`) — a base/limit descriptor, + **not** the GPU's 5-level compute page tables. **No full GPU VMM is needed** for scanout. +- The surface must live in **VRAM** in practice (nouveau always pins scanout to VRAM). *Open + question:* whether GA10x can scan out from a system-memory (GART) surface via a sysmem-target + ctxdma — which would let danos skip a VRAM allocator. No source forbids it; nouveau never does + it (confidence: medium). +- **CPU access** to the framebuffer for compositing goes through **BAR1** (a VRAM aperture); BAR0 + is the 16 MB register window. BAR1 can be smaller than 12 GB of VRAM unless Resizable BAR maps + it all. + +**Net:** you need (1) a contiguous aligned VRAM allocator (256-byte base, pitch a multiple of +64 bytes — confirm against the Ampere display refs), (2) a little instmem for the channel +pushbuffers + iso ctxdma, (3) a BAR1 CPU mapping. You do **not** need the 5-level VMM, GEM/TTM +eviction, or tiling. + +## Licensing + +The tension is the opposite of convenient: + +- **NVIDIA open-gpu-kernel-modules is dual MIT/GPLv2** — usable under MIT, no copyleft on your + other code — **but its display logic is the GSP/RM-object route.** Its class headers + (`cl0073.h`, `cl2080.h`, `ctrl0073*.h`) are useful, permissive references. +- **nouveau is GPLv2**, and the **register-level display sequences you actually want live in + nouveau**, not in the MIT code. So the *easy technical path is the GPL-licensed one.* Reading + GPL nouveau and reimplementing it in Zig is a derivative-work risk proportional to how closely + your code tracks its structure/constants. + +Options: **(a)** accept that the danos NVIDIA display driver is a **GPL component**. danos's +userspace-driver-over-IPC model (a driver is a separate process behind a defined protocol, not +linked into the kernel) is about the cleanest possible GPL boundary, so the GPL would be contained +to that one binary and the rest of danos could keep its own license — but this is a +licensing-boundary judgement that wants real diligence, not a settled fact. **(b)** clean-room +from *specification* rather than *code*: [envytools](https://envytools.readthedocs.io) + NVIDIA's +open-gpu-doc register manuals + the MIT OGKM class headers, treating nouveau as +documentation-of-last-resort. + +**Firmware licensing is moot for the direct path** (no firmware is loaded). For completeness: the +GSP blobs are marked redistributable under `LICENCE.nvidia`, which permits use by **any +OSI-approved open-source OS** (not just Linux), on NVIDIA GPUs, **unmodified**, with **no +reverse-engineering of the firmware binary**. The one gate — is danos released under an OSI +license? — is only reached on the GSP route, which this doc recommends against for this card. + +## Prior art + +**No one has built a from-scratch native NVIDIA driver outside Linux.** FreeBSD ships +`nvidia-drm-kmod`, a *port of NVIDIA's own closed `nvidia-drm.ko`* loading the GSP blob (its old +nouveau port was removed). Haiku's NVIDIA support is likewise a *port of OGKM* (GSP, Turing+, very +alpha). OpenBSD / DragonFly have neither. Every non-Linux OS that supports modern NVIDIA chose to +**wrap NVIDIA's GSP stack** rather than write a native driver. A danos direct-register driver +would have exactly one reference implementation — GPL nouveau — and no non-Linux precedent. + +## Alternatives + +| 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** | +| **Pre-Turing NVIDIA** (Kepler / early Maxwell) | Direct EVO/disp-core + CRTC/PLL modeset, **no signed firmware, no coprocessor**; mature nouveau reference | Older display class; not this card; only reclocking is firmware-gated | +| **Intel iGPU** | **Publicly documented** register interfaces (Intel PRMs); no coprocessor mediating modeset | i915 is huge + generation-specific; write one generation from the PRM | +| **Native GA106 direct** (this doc) | Runtime modeset, vsync, multihead on the actual card | Tier-4 effort; GPL reference; DP link training; legacy/de-emphasized path | +| **GA106 via GSP/OGKM** | Also unlocks 3D / reclocking later | Tier-5; ~14k-line ante; unstable version-pinned ABI; unprecedented outside Linux | + +## "First light" milestones (direct path, inheriting GOP state) + +Framed as a danos `.scanout` service (like the virtio-gpu driver), taking the direct register path +and inheriting the GOP-initialized display — no signed firmware, no devinit, no GSP: + +1. **PCI/BAR bring-up** — enumerate GA106 (`0x176`), map **BAR0** (registers) and **BAR1** (VRAM + aperture) via danos MMIO grants; confirm the display engine is GOP-live. +2. **VRAM + instmem allocator** — contiguous aligned VRAM for the scanout surface (256-byte base) + + small instmem for pushbuffers / RAMHT / iso ctxdma. No VMM, no TTM. +3. **EDID** — port `nvkm/subdev/i2c` DDC (`0x50`) + DP-AUX (`aux.c`); read + parse the panel EDID. +4. **Core channel up** — allocate the `0xc67d` core channel as a DMA pushbuffer; stand up the + SV1/SV2/SV3 supervisor-interrupt handshake. +5. **First pixel = reprogram, don't re-POST** — bind a window (`0xc67e`) at the existing WC + framebuffer via `SET_CONTEXT_DMA_ISO` + `SET_OFFSET`, pitch-linear, `UPDATE`; prove you can + drive the *current* GOP mode from your own channel before changing anything. +6. **Modeset** — push raster timings on a head, route head→SOR→connector, program the pixel-clock + PLL, switch to an EDID mode (needs the `clc67d/e` method opcodes from the OGKM headers + the + supervisor timing from nouveau `head.c`). +7. **DisplayPort link training** — only if the panel is DP and GOP's link can't be reused; the + `dp.c` `train_cr`/`train_eq` state machine. TMDS/HDMI is far simpler. +8. **Wire into the compositor `.scanout` backend** (`attach_scanout`), add vsync via the display + interrupt, then multihead. + +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 + +**Direct path — nouveau (GPLv2):** +- `nvkm/engine/disp/ga102.c` — the GA10x display HAL + the GSP/non-GSP dispatch. +- `nvkm/engine/disp/{head.c, ior.c, dp.c, hdmi.c, chan.c}` — head/SOR routing, DP AUX + link + training, channel-DMA plumbing. +- `dispnv50/{corec37d.c, corec57d.c, wndwc37e.c, wndwc57e.c, wndwc67e.c, headc37d.c, cursc37a.c}`. +- `nvkm/subdev/i2c` (DDC + `aux.c`) for EDID; `nvkm/subdev/bios/init.c` + `devinit/` **only** if + you ever have to re-POST (danos's GOP handoff means you shouldn't). + +**Object model / GSP path — NVIDIA OGKM (MIT/GPLv2):** class headers `cl0073.h`, `cl2080.h`, +`ctrl0073system.h`, `ctrl0073specific.h`; `src/nvidia/` for RM control sequences. +`nvidia-modeset.ko` (NVKMS) is a *policy* layer over RM and can be bypassed entirely. +[nova-core](https://lore.freedesktop.org/nouveau/) (Rust) is the forward-looking reference for GSP +boot mechanics (falcon signing, queue rings, RPC). + +**Register / method specs — NVIDIA open-gpu-doc:** +- [`classes/display/README.txt`](https://raw.githubusercontent.com/NVIDIA/open-gpu-doc/master/classes/display/README.txt) + — the channel model + class-to-GPU map (read first). +- [`classes/display/clc67d.h`](https://raw.githubusercontent.com/NVIDIA/open-gpu-doc/master/classes/display/clc67d.h) + + `clc67e.h` / `clc67a.h` — the Ampere core/window/cursor mode-set method vocabulary. +- [`manuals/ampere/ga102/dev_display_withoffset.ref.txt`](https://raw.githubusercontent.com/NVIDIA/open-gpu-doc/master/manuals/ampere/ga102/dev_display_withoffset.ref.txt) + — `NV_PDISP_FE_*` channel/pushbuffer registers + SOR. +- [`DCB`](https://github.com/NVIDIA/open-gpu-doc/tree/master/DCB) — connector→output-resource + routing; [`Devinit`](https://github.com/NVIDIA/open-gpu-doc/tree/master/Devinit) + + [`BIOS-Information-Table`](https://github.com/NVIDIA/open-gpu-doc/tree/master/BIOS-Information-Table) + — VBIOS parsing (bring-up reference; not needed if inheriting GOP). +- The 632 KB Volta [`dev_display.ref`](https://download.nvidia.com/open-gpu-doc/Display-Ref-Manuals/1/gv100/dev_display.ref) + is the best shot at SOR-DP/AUX register detail the smaller Ampere file omits. + +## Open questions (unresolved by the survey) + +Each needs a direct read of the named nouveau file or experimentation on the actual card: + +- Exact GA106 register/method offsets and PADLINK→SOR→connector wiring (can vary by board vendor). +- Whether *any* PLL/devinit re-run is unavoidable vs. fully inherited from GOP. +- Whether DisplayPort needs full retraining on takeover, or the GOP-established link can be reused. +- The precise SV1/SV2/SV3 supervisor sequence. +- Whether a system-memory-target scanout ctxdma could eliminate the VRAM allocator. +- The exact `clc67d.h`/`clc67e.h` method opcode numbers (not captured verbatim in the survey). + +--- + +*Research snapshot; verify against current nouveau / open-gpu-kernel-modules source before +building — NVIDIA's GSP defaults and firmware ABIs change per release.*