watchthelight labs

Hybrid graphics on macOS: Optimus, GC6 and the display mux on the Legion 5 15IAX10

Started 2026-10-22 after Bash asked whether NVIDIA Optimus and its relatives could be rebuilt as a macOS kext. Local findings first (ACPI read by byte scanning, as in the project's GPU power notes: no audited iasl on macOS), web research in section 4. Confidence per claim: High / Medium / Low / Unverified.

1. What the firmware gives us

All in the Legion ACPI dump (research repo, N-13 sysreport) (BIOS S2CN26WW). Offsets are file offsets.

Interface Where What it is Confidence
NVOP _DSM A486D8F8-0BDA-471B-A72B-6042A6B5BEE0 SSDT-21 0x6c6, method NVOP 0x8a8 Optimus: capabilities, dGPU power state (DGPS/GPRF/OPCE refs), HDA control High (GUID, names)
JT _DSM CBECA351-067B-4924-9CBD-B46B00B86F34 SSDT-21 0x74c, method NVJT 0xf8a (1191 B) GC6 / runtime power control; calls GC6I and GC6O, reads GPWO, PWGD, LREN High
GPS _DSM A3132D01-... SSDT-21 0x6fb thermal / power supply (see legion-gpu-power-wmi.md) High
NBCI _DSM D4A50B75-... SSDT-21 0x79d NVIDIA backlight / MXM-ish info Medium
NVPCF _DSM 36B49710-... SSDT-21 0x1760 cTGP / Dynamic Boost (in use by NvGfx for 115 W) High
GC6I / GC6O SSDT-21 0xe10 / 0xe8a (also SSDT-18) GC6 entry / exit on RP01: DL23 / L23D (PCIe L2/L3 ready and detect), LREN / LTRE (LTR), RSTG / PPIN (reset, power GPIOs), NVID check High (names), Medium (sequence)
MXDS SSDT-21 0x2506, under EDP1 (the dGPU's eDP output) the display mux: one GPIO, pad 0x00160C97 High (decoded below)
MXDM SSDT-21 0x257e mux mode: 0 returns DSMF; 2 or 4 writes SMBB 0x26 / 0x25 and SMBA 0xCA High (bytes), Medium (meaning)
MXID SSDT-21 0x25d3 panel id from MMID, chosen by PANT bit 4 High
_DOD/_DCS/_DGS/_DSS/_BCL/_BCM SSDT-21 dGPU-side display outputs and backlight High

MXDS(Arg0), decoded from the AML bytes:

Arg0 & 0xF == 0: return GGOV(0x00160C97) == 0 ? 1 : 2      (which GPU has the panel)
Arg0 & 0xF == 1: SGOV(0x00160C97, (Arg0 & 0x10) ? 1 : 0)   (bit 4 selects the GPU), return 1
otherwise:       return 0

Measured 2026-10-22 14:30 EDT in BIOS Discrete (panel on the RTX 5070, OpenCore FB 0x6000000000), read-only through IGPUConsoleRange 0.4: MXDS(0) = 2 (GPIO high), MXID(0) = 0x10005. So 2 = dGPU and bit 4 set selects the dGPU on this firmware, the opposite of Linux mxm-wmi's adapter numbering (0x10 = iGPU). Still to read: MXDS(0) in Hybrid, expected 1. Confidence: High for the code and the Discrete reading, Medium for the mapping until the Hybrid read.

2. What the earlier attempts tell us (N-202 to N-207)

  • In Hybrid, Lenovo's EC detaches the dGPU (GZFD.HTPL, HPFF) and PXP._ON refuses while HPFF is set or DSMF == 2.
  • Calling Lenovo's attach (WEJF(4,0), PXP._ON, Notify) powered the GPU but the link never trained: RP01 Link Disable (LDIS) was set after boot by IOPCIFamily, and clearing it was not enough. Three tries ended in a power cut (I-093).
  • None of them used GC6O / the JT _DSM, which is the path NVIDIA's driver uses to bring a powered-down GPU back, including the L2/L3 exit and LTR restore. That is the untried route.

3. What macOS already has

  • NvGfx (from NullMoth) runs a headless NVIDIA GPU: with no NVIDIA head, NVAccel still publishes the Metal device and the plugin copies IOSurfaces out through system memory to the GPU that owns WindowServer (nvrm-accel.cpp, NullMoth issues #76, #106). That is render offload in macOS terms.
  • NvGfx also has a proxy-head idea (nvfbigpu=1): NVIDIA composites, the Intel GPU only scans out. kitsuneislife/ nmx-intel showed the copy (NVIDIA-composited frames to an Intel panel) at about 1.4 ms per frame.
  • Both need the RTX 5070 on PCI in Hybrid, which is exactly what I-093 blocks. Discrete mode avoids it but keeps the dGPU on all the time.

3b. Apple's own switching stack, still in Tahoe 26.7.1 (checked 2026-10-22)

  • AppleGraphicsControl.kext 8.1.12 ships AppleMuxControl / AppleMuxControl2, AppleGPUWrangler, ApplePolicyControl, AGDC, AppleGraphicsDevicePolicy and AGDCBacklightControl, because Tahoe still supports the dual-GPU MacBookPro16,1 and 16,4. High (local plists and SystemKernelExtensions.kc strings).
  • AppleMuxControl matches an IOACPIPlatformDevice named GMUX (AppleMuxControl2: GPUC), picks a config from ConfigMap by board-id, and names the GPU drivers it works with by IOClass (FBDriver0=AppleIntelFramebuffer, FBDriver1=NVDA, GLDriver1=NVKernel,nvAccelerator, HDA drivers), plus GPUMinimumOffTime, PowerUpDownSequence, SaveRootPort, HPDMask, Policy. On the Legion it does not load: no GMUX/GPUC device, and it knows neither NVRM nor XeGfx. AGDC, GPUWrangler, DevicePolicy, AGPM and NullMoth's com.nullmoth.NVRMAGDC are loaded. High.
  • IOFramebuffer side (Apple IOGraphics source): AGC sends 'gMUX' WillSwitch / DidSwitch / DidNotSwitch, the two GPUs' framebuffers for one panel are paired by AAPL,display-alias, the incoming one comes online in the background and the outgoing one goes offline; WindowServer drives the switch (WS_DO_SWITCH, "Glitchless Switch suppressed: not all FBs are online"). Only one GPU composites the desktop at a time: Apple has no Optimus-style per-app offload into the iGPU's desktop, it moves the whole desktop. High (source), Medium (switch details from strings).
  • Triggers: MTLCreateSystemDefaultDevice() asks for the high-power GPU, MTLCopyAllDevices() does not; OpenGL contexts switch unless NSSupportsAutomaticGraphicsSwitching; Battery > Options "Automatic graphics switching", pmset gpuswitch 0/1/2; NVRAM gpu-policy, gpu-active; boot GPU from EFI gpu-power-prefs. Power-down after GPUMinimumOffTime only if the vendor driver reports ready (3D engine and HDA idle) through AGC's power attribute.
  • Real gmux (Linux apple-gmux.c): ports SWITCH_DISPLAY 0x10, SWITCH_DDC 0x28, DISCRETE_POWER 0x50, BRIGHTNESS 0x74; separate backlight chip because either GPU may be off.

Consequence for the Legion: two roads. (a) Our own stack (LegionHybrid + NvGfx headless/proxy head), which macOS already tolerates. (b) Make Apple's AGC drive us: a fake GMUX/GPUC ACPI device whose methods call the Legion's MXDS and power methods, a ConfigMap match (MacBookPro16,1 board-id style), and framebuffer drivers that speak 'gMUX', AAPL,display-alias and AGC's power attribute. (b) would give Apple's real switching UI and WindowServer handoff, but depends on a Mac model config and on both drivers implementing Apple's protocol: Low confidence for now.

4. How Windows and Linux do it (web and source research, 2026-10-22)

Sources: NVIDIA open-gpu-kernel-modules 615.78.08 (local, src/common/inc/{nvop,jt,nbci,nvhybridacpi}.h, platform/guids.c, dynamic-power.c), coreboot google/brya ACPI (NVIDIA-written: nvjt.asl, power.asl, gpu_defines.h), Microsoft ADS spec, Lenovo Legion Toolkit Features/Hybrid, Linux nouveau_acpi.c, mxm-wmi.c, vga_switcheroo docs, NVIDIA RTD3 and PRIME READMEs. High unless noted.

_DSM functions. NVOP rev 0x100: 0x1A OPTIMUSCAPS (bits 25:24 = 3 asks for dGPU power-down; returns Optimus enabled, dGPU power state, dynamic power capability, HDA state), 0x1B OPTIMUSFLAG. JT rev 0x103: 0x01 CAPS, 0x03 POWERCONTROL (bits 2:0: 0 status, 1 GC6 enter, 2 GC6 enter with self-refresh, 3 GC6 exit, 4 exit with self-refresh; bits 15:14 defer to _PS3/_PS0; returns GC state 0 transition, 1 on, 2 off, 3 GC6, bit 3 power OK). PEX _DSM on the root port: 0xA aux power limit, 0xB PERST# assertion delay. No GUID switches the mux: NVIDIA uses MXM MXDS.

Power sequence (coreboot reference, names match the Legion's). GC6 entry GC6I: save LTR, DL23 (L2/L3 Ready), wait NVVDD_EN low, drop PWRGOOD, ramp FBVDD/PEXVDD/NVVDD down, assert PERST#, gate CLKREQ. Exit GC6O: ungate clock, deassert PERST#, wait NVVDD_EN, rails up, PWRGOOD, L23D (link to L0, retrain), poll config for vendor 10DE up to 50 x 100 us, restore LTR, clear error status. GC-OFF (full rail cut) needs at least 250 ms off time. The OS-visible order: JT POWERCONTROL (deferred), D3hot, _PS3 / _PR3._OFF; resume reverses. NVIDIA's Linux driver uses GC6 only when VBIOS and the PEX _DSM setup allow it, else GC-OFF with VRAM saved to system RAM (cap 200 MB default). Failure looks like "fallen off the bus" (config reads 0xFFFFFFFF), the N-207 picture.

Lenovo GPU modes (LLT source). Hybrid on/off = Gamezone WMI 0x29/0x2A (reboot). iGPU-only / Auto = Gamezone 63/64/65 or LENOVO_OTHER_METHOD capability IGPUMode 0x00010000 (0 default, 1 iGPU only, 2 auto), then the EC is told whether the dGPU is present: Gamezone 66 NotifyDGPUStatus or capability GPUStatus 0x02070000 (+ GPUDidVid). The EC then detaches or re-attaches the dGPU: this is the HPFF / HTPL path N-202 hit. Gamezone 53 SetDDSControlOwner exists, meaning Unverified.

Live mux switch. Microsoft ADS: freeze HPD, target GPU raises backlight, source GPU enters PSR1 so the panel holds the frame, OS calls the mux method, source drops panel power/backlight, target does full eDP link training, reports the panel, sets the mode, exits PSR. No HPD may fire; both GPUs need the same brightness table. Advanced Optimus does the same inside NVIDIA's driver via MXDS, 3-5 s with a black flash (Medium). Linux has no live DDS (vga_switcheroo cannot switch a driven panel; NVIDIA's 2022 RFC proposed doing it in DRM atomic).

Offload. PRIME render offload: the dGPU copies (copy engine) from tiled VRAM into a linear system-memory buffer the iGPU scans out or composites; one PCIe copy per frame, about one frame of latency, no VRR on the iGPU panel. Reverse PRIME (iGPU renders, dGPU scans out) is known to be slow.

5. A macOS hybrid-graphics kext: what to build, in order

Working name LegionHybrid (an ACPI client, growing out of IGPUConsoleRange's debug.wtlacpi). Each step is testable on its own and keeps today's Discrete setup as the fallback.

  1. Probe (read-only, now). _DSM support lists for NVOP, JT, PEX; JT CAPS; MXDS(0) in Hybrid; the EC flags (HPFF, IGPS, DSMF) and RP01 LDIS/PSTA. Needs _DSM with buffer/UUID arguments in the evaluator (IGPUConsoleRange 0.5).
  2. dGPU on in Hybrid, done the NVIDIA way (one guarded boot). EC first (IGPUMode 0 + NotifyDGPUStatus(1), as LLT), then JT POWERCONTROL exit (or _PR3._ON + L23D), poll config for 10DE, restore LTR, clear AER, then rescan. Never write Link Disable by hand. This is the step that failed three times as raw PXP._ON (I-093, power cuts), so it needs Bash's OK and the power-cut caveat.
  3. Render offload. With the RTX 5070 on PCI and the panel on the iGPU, NvGfx already runs headless: Metal apps can pick the NVIDIA device, frames are copied to the iGPU's WindowServer. Works with IONDRV on the iGPU today.
  4. Accelerated Hybrid desktop. NvGfx proxy head (nvfbigpu): NVIDIA composites the whole desktop and copies each frame into the iGPU's scanout buffer (nmx-intel measured about 1.4 ms per frame). No Intel acceleration needed.
  5. dGPU off when idle (RTD3). NvGfx learns GC6/GC-OFF entry and exit (idle check, VRAM save, GSP re-boot, about 13 s today, so only for long idle periods first) and LegionHybrid drives JT POWERCONTROL. Battery life.
  6. Live mux switch (Advanced Optimus). MXDS set plus PSR hold on the source, full eDP training on the target, no HPD, matched backlight. Needs an Intel display driver that can light the panel (XeGfx display or NMIntelFB) and PSR on both sides; last, and hardest.