watchthelight labs

Intel built-in panel (NMIntelFB) on Arrow Lake-S 7D67: tracking #12

openarea:gpuarea:display
watchthelight 2026-10-21

Tracking issue for the Intel built-in panel framebuffer (NMIntelFB) on an Arrow Lake-S laptop, branch intel-xe. Each iteration gets a comment: what changed, what the boot showed, what is next.

Machine: Lenovo Legion 5 15IAX10, Core Ultra 9 275HX, iGPU 8086:7D67 (GMD_ID graphics 12.70, display 14.00), RTX 5070 Laptop. BIOS mux: dGPU mode puts the panel on NVIDIA (works, accelerated), Hybrid mode puts it on the iGPU. macOS Tahoe 26.7.1.

Goal: a desktop on the built-in panel in Hybrid mode, first unaccelerated on the firmware's mode, then composited by the NVIDIA accelerator (head 4 copy path), then Intel acceleration.

Measured so far (Linux probe, firmware state in Hybrid): plane A linear XRGB8888 2560x1600 at 165 Hz, PLANE_SURF 0xFF000000 (GGTT offset beyond the 256 MiB BAR2), GOP framebuffer 0x70800000 = DSMBASE (stolen memory), GSMBASE 0x70000000, PCode stolen-access bit (0x138914) = 1, PSR1 panel, backlight not on the PCH PWM.

Boots so far:

  • Apple's IONDRVFramebuffer on the iGPU: WindowServer builds a virtual framebuffer only (no GUI).
  • NMIntelFB 0.1/0.2 matched at boot: passes the register reads, the boot-console check and IOFramebuffer::start, then the machine hangs in the write-combined kernel map of the 0x70800000 framebuffer (NVRAM step breadcrumbs, two boots).
  • NMIntelFB 0.3 (no map in start()): first boot inconclusive.

Next: adversarial review of the whole display path (other components acting on the iGPU), then the next build.

watchthelight 2026-10-21

Workstreams are now sub-issues: probe data (#13, done), IONDRV no desktop (#14), NMIntelFB start hang (#15), NVIDIA compositing onto the Intel panel (#16), backlight (#17), Intel kernel driver and Metal (#18). Current step: an adversarial review of everything on the machine that can act on the iGPU or the panel (kexts, Lilu plugins, injected properties, daemons, OpenCore), plus a code review of the framebuffer, the accelerator hookup and the userland. Results and the next build will be posted here.

watchthelight 2026-10-21

Update 2026-10-20 ~21:00. Five parallel reviews finished (framebuffer, kernel port attach, Metal path, everything else on the machine that touches the iGPU, and an assumption breaker).

  • #14 has a source-level root cause: Apple's IONDRV only accepts a boot console that lies inside a PCI BAR; on Meteor/Arrow Lake the GOP framebuffer is in stolen memory. New kext IGPUConsoleRange (branch intel-xe, 4e5bedb) adds that range so Apple's own driver can use it. Next Hybrid boot tests it alone.
  • #15: the stage-8 hang is narrowed to IOFramebuffer's open/mapping window; NMIntelFB parked until #14's result.
  • #18: five userland/glue blockers listed for the Intel Metal path.
    Waiting on: one Hybrid boot.
watchthelight 2026-10-21

Red-team review of everything else on the machine (kexts, Lilu plugins, injected properties, daemons, OpenCore) finished:

  • WhateverGreen is an accomplice. It hooks IOFramebuffer::initFB and, unless boot-arg gfxrst=4 is set, copies the saved boot console into each new framebuffer on first open. NullMoth's own NVRMFB notes that this needs gfxrst=4 with WhateverGreen. Neither this machine nor the Tiger Lake test laptop had it. With the console in stolen memory that hook maps exactly the range the NMIntelFB freezes were in. Added gfxrst=4 to N-201.
  • In Hybrid mode the auxiliary kext collection loads ~5 s after userland, not ~60 s; every failed Hybrid boot died 4-15 s after NMIntelFB loaded.
  • Ruled out with the N-192 control boot (Hybrid, NVIDIA off, no NMIntelFB, alive): every background daemon, display audio (no Intel HDMI codec probed), AppleGraphicsDevicePolicy/mux kexts (not loaded), the injected iGPU properties (WhateverGreen's invalid-platform pattern), OpenCore settings.
    N-201 = IGPUConsoleRange + gfxrst=4 + NVIDIA off, Hybrid.
watchthelight 2026-10-21

N-201: first desktop on the Intel iGPU. Apple's own IONDRVFramebuffer now drives the built-in panel in Hybrid mode after IGPUConsoleRange (OpenCore-injected) adds the stolen-memory console range to the iGPU and gfxrst=4 keeps WhateverGreen's initFB hook out of the way. Unaccelerated (software compositor). #14 closed. Next: acceleration through the NVIDIA card rendering and NVAccel copying frames onto this framebuffer (#16), which needs NVAccel to work with a foreign framebuffer and no NVIDIA head.

watchthelight 2026-10-21

Next boot N-202 aims at an accelerated desktop on the Intel panel: SSDT powers the RTX 5070 on in Hybrid mode (macOS never ran the dGPU's ACPI power-on, so the GPU was absent from PCI), and a proxy head in NVRMFB lets the NVIDIA accelerator composite with Metal and copy each frame into the iGPU's framebuffer. Details on #16.

watchthelight 2026-10-21

Update 2026-10-20 ~21:35. N-202 booted to the software desktop: the dGPU was still absent, held off by an EC flag. N-203 attached it at run time and then cut power on all four boots, 40 to 80 s in, with no panic recorded. Lenovo's ACPI shows the EC write we suspected first is normal power-on behaviour, so the next boot (N-204) separates the attach from the NVIDIA driver start: attach on, NvGfx off. Details and the follow-ups for each outcome are on #16.

watchthelight 2026-10-21

N-204: power lost again with NvGfx off. The trigger is an iGPU reset (FLR) when IGPUConsoleRange hands the panel back after waiting 24 s for a dGPU that never appeared; power goes 7-8 s later. Safe config for the next boot; the fix plan is on #16.

watchthelight 2026-10-21

N-203 to N-205 (2026-10-20 21:12 to 21:47 EDT), Hybrid mode

  • N-203 / N-204: the laptop lost power 40-80 s into each boot. Cause found (I-093): IGPUConsoleRange 0.3 claimed the iGPU's framebuffer slot and, when no NVIDIA GPU had appeared after 24 s, terminated itself so IONDRV could take over. IOPCIFamily marks a nub whose client was terminated as needing a reset (IOPCIDevice::detachFromChild), and the next match runs an FLR on it (getResources, "Performing FLR on nub IGPU"). Resetting the iGPU while it scans out the panel cut power 7-8 s later. N-204 had -nvoff, so the NVIDIA driver is not involved.
  • N-205: safe config (no slot claim, SSDT off). IONDRV desktop, stable, no FLR.
  • The DGON attach result was lost: this kext's IOLog lines reach neither the unified log nor dmesg here.

N-206 (staged, NvGfx intel-xe eec12da): IGPUConsoleRange 0.4

  • The slot is decided once, in probe, and never handed back: with nvfbigpu=1 it attaches the dGPU from probe and claims only if the RTX 5070 is there.
  • sysctl debug.wtlacpi evaluates ACPI methods live (EC REJF, DGON, RP01 rescan), so the dGPU attach in Hybrid can be iterated on one boot instead of one reboot per try. The trace lives in debug.wtlacpi_log.
  • N-206 boots with IONDRV on the panel as in N-205; the attach is then tried live.
watchthelight 2026-10-21

N-206 (2026-10-20 21:59 EDT): live dGPU attach in Hybrid, result: power cut

IGPUConsoleRange 0.4 booted cleanly (IONDRV desktop, no FLR), and debug.wtlacpi read the Lenovo state live:

  • EC: IGPS 0, HPFF 0, CUMA 0, GSTA 0. The RTX 5070 is not detached in Hybrid, only powered off (PXP status 0). macOS never runs _PR0.
  • No GC6 (TDGC 0). Root port RP01 present (VDID ae4d8086), PXSX NVID 0xffff, RP01 LDIS 1.

DGON (Lenovo's PXP._ON power branch with a bounded wait) timed out: power was on (PSTA 1) but the link never trained (NVID still 0xffff). The laptop lost power about 12 s later, with no FLR this time. So I-093 is revised: the cut follows powering the dGPU while RP01's link stays down. N-203/N-204 fit the same timing. The FLR fix in 0.4 stays, but the FLR was probably not what cut power.

Live power-on experiments are paused: each one is a hard power loss. Next is a read-only study of Lenovo's Hybrid path (PXP._OFF is \STMC(4,0), so the real on/off goes through STMC), and of how Linux/Windows bring the link up, before any new attempt.

watchthelight 2026-10-21

N-207 (2026-10-20 22:08 EDT): DGON v3, power cut again. Hybrid dGPU attach parked.

  • RP01's Link Disable is set before any attach (LDIS 1 at 45 s; IOPCIFamily logged it clear at 1.8 s, so it gets set after boot).
  • DGON v3 cleared it after power-on and waited 1 s: the link still did not train (NVID 0xffff, LASX 0). Lenovo's POFF then did not drop power (PSTA stayed 1), and the laptop lost power seconds later.
  • Every live power-on of the RTX 5070 in Hybrid has now ended in a hard power cut: N-206, N-207, and the probe-time attach in N-203/N-204. That is too costly for the laptop and its disks, so the attach is stopped and SSDT-DGPU-ON is off again.
  • Consequence: the "NVIDIA renders, Intel panel scans out" proxy path needs the dGPU on PCI in Hybrid, so it is parked with the attach.
  • State now: Hybrid gives a stable IONDRV desktop on the iGPU (unaccelerated, N-201/N-205); Discrete gives the accelerated NVIDIA desktop. Accelerated Intel graphics needs a real Intel GPU driver (i915/xe port), which is the M2+ part of this plan.
watchthelight 2026-10-21

New direction (2026-10-20 22:20 EDT): a generic Intel Xe driver for macOS ("XeGfx")

Goal: any laptop with Intel Xe graphics (Tiger Lake to Arrow Lake first, Lunar Lake and later via xe in phase 2) boots an accelerated macOS desktop, Lenovo or not. The Legion-specific Hybrid dGPU route is closed (three power cuts, see above) and never generalised anyway.

Design:

  • Kernel: FreeBSD's drm-kmod (Linux i915 at the 6.14 level, MIT) on a LinuxKPI-for-XNU layer, GT only at first (display code disabled).
  • Userspace: Mesa ANV for Vulkan, plus a Metal-on-Vulkan layer for WindowServer.
  • Display without mode setting: the firmware lights the panel, and a flip is one PLANE_SURF write to a GGTT address. That works across display versions 12 to 14 without i915's 177k-line display code.

First measurements (sources: linux 9e73b18, freebsd d2145e9, drm-kmod c16de0f):

  • Compile harness: all 376 drm, dma-buf, ttm and i915 files (with the XNU Kconfig: no debugfs, procfs, fbdev, PXP) get through the front end for the macOS kernel target. That needed Mach-O linker sets, module metadata without ELF sections, and FreeBSD's generated interface headers. LinuxKPI's own sources: 30 of 38.
  • The objects need 458 functions nothing in them defines: macOS already exports 60, and 398 are the FreeBSD kernel API to write on XNU. By area: locks/sleepqueues, malloc/uma, taskqueue/callout/kthread, vm_page/pmap (GEM buffers and their user mappings, the hardest part), PCI resources/MSI (to IOPCIDevice), char devices/fds (for the DRM node), sysctl, firmware, and stubs for debugfs/vt.

Milestones: X1 compiles, X2 links, X3 loads GT-only on 7D67 next to IONDRV, X4 GuC + first request, X5 ANV offscreen, X6 accelerated desktop, X7 a non-Lenovo laptop.

watchthelight 2026-10-21

XeGfx now has its own repo: https://github.com/watchthelight-labs/XeGfx (BSD-2-Clause, public)

State at 2d060c5:

  • All 415 objects (drm, dma-buf, ttm, i915, LinuxKPI, our shim) build for the macOS kernel target.
  • Done on XNU: curthread shadows, per-CPU data, critical sections, clock/ticks/jiffies, malloc/contigmalloc/UMA, mtx/sx/rw/cv/sleepqueues, taskqueues, callouts.
  • Kext skeleton: idle unless xegfx=1, and it never claims the iGPU yet. Link script with generated stubs that panic with the name of whatever is still missing.

Now running: six parallel agents, one per area, on the remaining 305 functions:

  • pci: newbus, PCI and MSI on IOPCIDevice
  • dma: DMA, GTT, stolen memory, ACPI, backlight
  • vm: vm_page/pmap/cdev pager for GEM
  • fd: cdevsw bridge for the DRM node
  • sched: kthreads, RCU, timers, CPU features
  • misc: sysctl, firmware, the rest

Each one pushes to main as soon as it finishes, so progress shows up in the commit log as it lands. Next after that: a clean link, then the first load on the Legion in Hybrid with the GT side only.

watchthelight 2026-10-21

XeGfx X2 done: the whole stack links, using only public KPIs (XeGfx a36e0b4)

  • 423 objects: drm, dma-buf, ttm, i915, LinuxKPI, FreeBSD's eventhandler, and the shim. 4.0 MB, 0 stubs.
  • Every symbol resolves from a library the kext declares; nothing from kpi.private and nothing the kernel exports only to itself. tools/kpi_check.py enforces this on every link.
  • Consequences for the design: no direct map of RAM (PHYS_TO_DMAP is a lookup over the shim's IOKit mappings), critical sections and sched_pin run on public lck_spin locks, and there is no CPU binding (documented where it matters).

Next boot (N-208): the kext is in the auxiliary collection, idle; it does nothing at load and never claims the iGPU. After login, sysctl debug.xegfx_start=1 runs the shim and every SYSINIT (drm, ttm, i915 module init) without handing the Linux PCI driver a device yet. If that panics, the Mac reboots into the idle kext, with no config change.

watchthelight 2026-10-21

N-208 (XeGfx 0.0.1 idle in the aux collection): both Hybrid boots stopped within seconds of userland, with no boot dump, no flushed unified log and no panic. Likely cause: XeGfx's IOPCIDevice personality matches the live iGPU when the aux collection loads, and late matching on IGPU is what triggered the iGPU FLR and power loss in N-203/N-204. The iGPU is on PCI in Discrete mode too, so N-209 is a safe boot (-x skips the aux collection) to uninstall it. For the driver: start from IOResources and find IGPU by path instead of a PCI personality.

watchthelight 2026-10-21

N-209: safe boot (-x) cannot boot this machine (boot.efi kernel memory allocation fails, STOP 0x16), so XeGfx is fenced off another way for N-210: OpenCore gives the iGPU vendor-id 0x1234, XeGfx's vendor-0x8086 personality no longer matches it, and IGPUConsoleRange matches 0x1234 too. The Mac session uninstalls XeGfx on that boot. Lesson for XeGfx: no IOPCIDevice personality on a live iGPU.

watchthelight 2026-10-21

N-208 to N-210 (2026-10-21): the first XeGfx install stopped the Legion, now fixed in the design

  • N-208: with XeGfx in the auxiliary collection, two boots stopped seconds into userland, with no panic and no log flush (I-094). The kernel shim never ran: it only starts on a sysctl. The likely trigger is IOKit itself. XeGfx's PCI personality matched the live iGPU late, as a second match on that nub, and IOPCIFamily's getResources() then resets the nub's software state (resetNubState(), "effectively a no-op on the first match" in the source).
  • N-209: a safe-boot rescue is impossible on this machine (boot.efi can't allocate kernel memory with -x).
  • N-210: an OCEFI vendor-ID spoof fenced XeGfx off without anyone having to type a command. On that boot XeGfx was uninstalled, and the spoof was removed for N-211, which is back to the N-207 state.

XeGfx b53b58b no longer has a PCI personality at all. It rests on IOResources and will find the iGPU in the registry when its shim is started. Separately, it now links only against declared public KPI libraries: no kpi.private and no symbols the kernel doesn't offer to kexts (tools/kpi_check.py, run by tools/link.sh).

watchthelight 2026-10-21

N-212 (XeGfx with an IOResources personality only): failed the same way as N-208, dead within ~40 s with nothing flushed. N-210 passed with XeGfx in the aux collection only because nothing matched it, so the fault is in XeGfx's load path (_start, static initialisers, the resident start), not in iGPU matching. N-213: a codeless OpenCore-injected XeGfxBlock.kext takes XeGfx's match category at a higher score so it never loads; the Mac session then uninstalls it and tests XeGfx loads by hand outside the aux collection.

watchthelight 2026-10-21

Progress, 2026-10-21. The Intel work now has its own home and is open for collaborators: NvGfx branch xe (docs/XE.md) and XeGfx with issues for every open task.

Kernel driver (XeGfx 0.0.2, Linux i915 through FreeBSD drm-kmod and LinuxKPI on XNU)

  • The two boots that died when XeGfx loaded (N-208, N-212) had one cause: the FreeBSD sysctl code linked into the kext defined sysctl_register_oid, so _start registered an XNU sysctl through the FreeBSD copy and dereferenced NULL. 40 such names are now renamed, and the link fails if a kext symbol shadows a kernel export (XeGfx#1).
  • Nothing loads XeGfx at boot any more. It is armed with kmutil load and runs in stages by sysctl: shim, a read-only GPU probe, bus/DMA, then i915 with its display code off and a check that the panel still scans out. A stalled stage panics on purpose so it leaves a report.
  • No GPL-only code in the build: an overlay replaces drm-kmod's GPL-only dma-buf, sync_file and fence-unwrap files (from NullMoth's old AMD/Intel port, commit bf2dee3), and the build refuses any GPL-only source or header. 418 of 418 sources compile and link against public kernel interfaces only.
  • First hardware run on the Arrow Lake 7D67 is queued (XeGfx#2).

Userspace

  • Mesa ANV (bffd919) and libdrm build natively for macOS with the Darwin patches, LLVM only at build time. On Mesa's simulated driver, every Intel generation from Gen9 to Xe3 (TGL, RKL, ADL, RPL, MTL, ARL incl. 7D67, DG2, LNL, BMG, PTL) creates a Vulkan device and compiles a compute pipeline. That proves the compiler, not the hardware path.
  • The futex code needs macOS 14.4+ for now (XeGfx#10). ANV on i915 also needs sync_file emulated in a small userspace shim (XeGfx#6).

Research and credits. The README of each repo links every project this builds on or learned from, among them FreeBSD drm-kmod and LinuxKPI, Linux i915, Mesa, NullMoth's driver, b00t0x's WhateverXe, NootedGreen, JulioNectar's XPS 13 Plus work, IntelIGPU-Tahoe, VedCloud2's CoreUltraGraphics and LunarBridge.

Testers with any Intel Xe machine: the read-only probe is described in CONTRIBUTING.md (XeGfx#3).

Reply on GitHub