watchthelight labs

Intel GPU: kernel driver attach (i915/xe) and Metal on Intel #18

openarea:gpu
watchthelight 2026-10-21

Later stage: claim the iGPU for rendering without losing the panel, then Mesa ANV through the Metal plugin. ANV compiles and links on Tahoe (link only). Parent: #12

watchthelight 2026-10-21

Code review of the Intel Metal path (reading only, nothing run), blockers that remain even once the GPU is claimed:

  1. ANV maps buffers with plain mmap(2) (anv_kmd_backend.c i915/xe gem_mmap); a macOS device node cannot be mmapped, so this needs the node-ioctl mapping on Darwin.
  2. libdrm finds render nodes by device minor; clone/first-free minors on macOS do not match renderD128, so enumeration returns no device.
  3. The i915 backend requires dma-buf sync-file support at probe (i915_gem_supports_dma_buf_sync_file); without PRIME export ANV returns VK_ERROR_INCOMPATIBLE_DRIVER. Darwin should skip it and hide the sync-fd extensions.
  4. The Metal plugin refuses WindowServer on non-NVIDIA vendors and pins one vendor per process; with two accelerators the first one enumerated decides.
  5. NVAccel only treats head 0 as its primary framebuffer; a lone head 4 (muxless laptops, NVIDIA without display engine) never gets a display pipe, VRAM or GPU addresses.
    None of these show up in drm-shim tests. Order of fixes: node minors and mmap, then the dma-buf probe, then the plugin vendor rule, then NVAccel's head-0 assumption.
watchthelight 2026-10-21

Kernel driver review (i915/xe on a LinuxKPI layer, checked against drm-kmod c16de0f9fa84, Linux 6.14 xe and XNU IOServicePM; details with exact locations go to nullmoth directly, his tree is private):

  1. Meteor/Arrow Lake attach ("none took it"): xe registers last, so the driver-ID search returned xe for the vgapci offer (xe lists MTL/ARL IDs behind force_probe), and the FreeBSD-style probe then silently returned ENXIO for i915. i915 itself accepts these parts (mtl_info has no force_probe). The newer source fixes the search; the 7D55 spoof changed nothing because 7D55 is in xe's table too.
  2. "stolen memory 0x0" is harmless: on display 14 config 0xC0/0x5C read 0 (confirmed on this Legion); i915 takes stolen memory from MMIO DSMBASE through the stolen-LMEM path, not that value.
  3. disable_display does not leave the panel alone: in this i915 base it still runs power-domain init, DMC, cdclk, VGA disable, output setup, PPS, hw-state sanitize and the initial plane takeover, then reports every DP/eDP connector disconnected. That matches "screen still dark".
  4. IOKit power glue runs i915's resume on a live device: the first power state is On without kIOPMInitialDeviceState, so IOKit calls setPowerState(1) right after attach, which forwards to i915 resume: engine reset, GGTT rebind, DMC resume, drm_mode_config_reset (drops the adopted BIOS framebuffer) and display re-init. Fix: mark the initial state with kIOPMInitialDeviceState and only forward a resume after a forwarded suspend.
  5. Ruled out: a GGTT scrub blacking the panel (Gen8+ clear_range is a no-op).
    Next failures forecast: on Arrow Lake the console-on-this-GPU check looks in the BARs and misses the stolen-memory framebuffer (same cause as #14); drm-kmod defaults enable_guc=0, so MTL/ARL would run execlists without SLPC/HuC, where Linux here runs GuC 70.53 submission + SLPC.
watchthelight 2026-10-22

XeGfx 0.0.11 is built and staged for the next test boot. It hasn't run on hardware yet. (XeGfx 9a024bf, NvGfx xe 3c201e5)

One boot now covers the whole path from i915 bring-up to a userspace batch:

  • Stage 4 fails when i915 attached but a GT is wedged or the GuC isn't running. i915's probe reports success in both cases.
  • Stage 5 runs one kernel request on every engine and counts the interrupts that arrive.
  • Stage 6 has every engine store a known value into its status page through the GGTT, and the CPU reads it back.
  • GEM mapping without mmap(2). XNU won't let a kext implement mmap on a device, so userspace maps GEM objects through two ioctls on the DRM node. They use the same layout and numbers as NullMoth's Darwin libdrm patch, so that patch works unchanged. The pages stay pinned while the mapping lives.
  • xgprobe is the first userspace client. It reads the DRM version and params, queries engines, creates a context, then runs a softpinned store-dword batch on each legacy ring through those mappings. On Arrow Lake this is the only way to get data into a buffer, because i915 refuses pread, pwrite and relocations after Tiger Lake.
  • Fixes from other ports: the upstream signal_irq_work() use-after-free fix (VedCloud hit it on Meteor Lake), FreeBSD LinuxKPI's lost wake-up (also found by VedCloud), and a stolen-memory reservation for the firmware's console image (VedCloud and nmx-intel).

Next on the userspace side: sync_file and dma-buf fd emulation, without which ANV won't start (XeGfx #6).

watchthelight 2026-10-22

XeGfx 0.0.12 (def47c7, NvGfx xe 38d66a2) adds what Mesa ANV needs before it will start. None of it has run on hardware yet.

  • Fences without file descriptors. A kext can't create fds, so libdrm on Darwin now hands out stand-in dma-buf and sync_file descriptors (patches/libdrm-xegfx.patch). The kernel half is two node ioctls that move a buffer's implicit fences to and from a syncobj, with the same rules as Linux's dma-buf sync_file export and import.
  • DRM_IOCTL_GET_PCIINFO. libdrm uses it on the BSDs and Darwin to list devices. drm-kmod declares it but never implemented it.
  • Mesa (patches/mesa-xegfx.patch): ANV's ioctls and buffer mappings now go through libdrm. The drm-shim chip matrix still passes all 28 simulated chips.

The next boot (N-222, Hybrid mode, panel on the iGPU) runs stages 1 to 6, then xgprobe, drmlist and an ANV device plus compute pipeline on the real node.

watchthelight 2026-10-22

Two test boots of XeGfx on the Legion today, N-222 in Hybrid mode and N-221 in Discrete. Both stopped early in stage 4, while i915 was probing. N-221 left a panic report:

xegfx: vm: pmap_qenter could not map 1 pages, from i915_ggtt_probe_hw -> setup_scratch_page -> i915_gem_object_pin_map -> vmap.

So the problem is in our shim, not in the GPU or the display. kva_alloc reserved kernel VA as a pageable buffer, and on Tahoe those live in IOKit's pageable submaps. pmap_qenter then tried to lay physical pages over that reservation with kIOMapOverwrite, which fails. N-219 had stopped before i915 got this far, so this was the first time the code ran.

XeGfx 0.0.14 (59d790f, NvGfx xe 893eb90) changes LinuxKPI's vmap family to map pages at an address IOKit picks, the same way every BAR mapping that already works is made. Stage 1 now tests that path before anything touches the GPU, and fails cleanly instead of panicking. It hasn't run on hardware yet.

watchthelight 2026-10-23

XeGfx 0.0.34 (N-272 pass): user page pins, i915 userptr, ANV host import. Metal textures backed by an IOSurface now live in the surface's own pages on the Intel GPU (blit 10.2 -> 0.8 ms at 2560x1600). Details in #12, build in XeGfx v0.0.34.

watchthelight 2026-10-24

XeGfx 0.0.38 (ETIME 101 for timed-out ioctls, ktime from boot, syncobj deadlines rebased from Darwin CLOCK_MONOTONIC) passes N-281: stages 1-7, vkhost and mtlbench checks with normal wait timings. Release: https://github.com/watchthelight-labs/XeGfx/releases/tag/v0.0.38. Summary of the WindowServer findings in #12.

watchthelight 2026-10-27

2026-10-27: XeGfx 0.0.45 (prerelease https://github.com/watchthelight-labs/XeGfx/releases/tag/v0.0.45) ran WindowServer on the Intel GPU for a full hour on N-296. The whole-machine freeze (I-116) went away with the NVRAM breadcrumb trace turned off; its 250 ms writes run in SMM on this firmware. N-297 repeats it for two hours under disk and GPU load. Details in #12.

Reply on GitHub