Lilu plugin for Intel iGPU acceleration on macOS — Haswell through Raptor Lake, via Tiger Lake driver spoofing.
Patches Apple's Tiger Lake (Gen12) graphics drivers to work with newer Intel iGPUs. Handles device-id spoofing, MMIO addressing, ForceWake, GPU topology, display controller init, combo PHY calibration, and GT workarounds.
Work in progress. Framebuffer controller starts, combo PHY calibration is patched, accelerator ring initialises, and host-based scheduler (type 5) runs on RPL with sustained RCS activity. Login is reachable on current test setups, but stability is still under active tuning for full-Metal paths. DYLD patches hook _cs_validate_page early (before DeviceInfo) so CoreDisplay is patched before WindowServer starts. Metal remains enabled by default. V50 still patches gpu_bundle_find_trusted() in libsystem_sandbox.dylib to redirect GPU bundle search from /Library/GPUBundles to /Library/Extensions/ where the TGL driver bundle is installed. ICL Metal driver device-ID bypass uses mask-based matching for build portability. V52 adds CPUID-based cross-platform detection (isRealTGL) so RPL-only patches are skipped on genuine Tiger Lake hardware.
- CRITICAL FIX (V75): Removed post-
awaitPublishingproperty override that was causing cursor corruption (TV static). Display now stable. - Early IGPU identity detection is confirmed working: Lilu reads
AAPL,ig-platform-id(9A490000) via OpenCore DeviceProperties duringDeviceInfoscan. - Platform-ID and device-ID injection now handled cleanly by config.plist only — NootedGreen no longer interferes mid-initialization.
- System boots to login screen reliably with no visual corruption or kernel panics.
- CoreDisplay DYLD path keeps assertion bypass always-on for Ventura+/Sonoma. Stage-3 safety stubs (RunFullDisplayPipe NULL guard, GetMTLTexture NULL stub, GetMTLCommandQueue NULL stub) are now applied only on non-real TGL unless full-MTL mode is forced.
- Blit3D init policy on spoofed paths is now conservative by default: Apple's original
IGHardwareBlit3DContext::initialize()is disabled unless explicitly opted in with-ngreenV69AllowOriginal. -ngreenV69AllowOriginalis diagnostic only. It can still panic atIGHardwareBlit3DContext::initialize + 0x4c(SecurityAgent/IOAccel submit path) on unsupported setups.DisplayPipeSupportednative path is now the default. Use-ngreendp0only for forced fallback testing.- V77 display-pipe client termination is disabled by default (delay defaults to full monitor window).
- V88 scanout fill remains opt-in (
-ngreenv88). Default boots do not paint diagnostic bars over normal UI layout. - GPU reset storm fully tamed (V157):
resetGraphicsEnginecircuit-breaker (V153+V154) confirmed working — V153 fires from call #1 (coercesret=1025→0when RCS ring head == tail), V154 opens at call #4 (skips original reset entirely after 3 consecutive quiescent coercions). Ring CTL is restored to0x7001(RING_ENABLE set) on every fake-success path via V155 saved-CTL restore. - Execlist cleanup in progress (V158): Root cause of remaining
userspace watchdog timeoutpanics is identified —EXEC=0x40018098(execlist FIFO stalled, bit 30 set) andCSB=0x1001(15 unread context-status entries) persist post-fake-success, causing Apple's driver to keep scheduling resets. V158 addresses this by writing four null descriptors toRING_ELSP(cancelling pending EL0/EL1 contexts) and advancing the CSB read pointer to match the write pointer on every V153/V154 return-0 path. - V80L root cause identified: The
userspace watchdog timeoutKP (T+120s, 2 induced WS crashes) was traced to the V80L plane-linearization block insidev71EmrEnforcer. It was writingPLANE_CTL/PLANE_STRIDE/PLANE_SURFevery 50ms while WindowServer was actively compositing those same registers — WS detected a render failure, crash-looped, and watchdogd fired. V80L is now limited to the first 3 enforcer ticks (≤150ms afterstart()), which preserves the visible brief display flash at early boot without fighting WS after it takes over the compositor. Use-ngreenv80lfor continuous mode (testing only).
Root cause: IGHardwareContext::restoreFromSafeImage returns true on RPL and skips the g_cInitGfxRingContextRCS memcpy that would normally write a valid Gen12 ring-context LRI (MI_LRI header + RING_HEAD/TAIL/START/CTL register pairs) into LRCA page1. The LRCA page1 at GGTT[0x19] (the ExecList submission slot hardware reads on context-restore) is left at GEM fill (0x00ffffff for arg=2 contexts, 0x00bfbfbf for arg=0 contexts). Hardware cannot parse the ring registers → context submits but immediately idles without going ACTIVE → fwWaitForHardwareRegisterValue times out (15 polls) → HANGCHECK → retry loop.
Fix (V508/V509):
IGHardwareContext::withOptionspost-hook (V508) saves the ctx pointer tocb->lastRCSCtxand copies LRCA page1 from the actual context buffer (GGTT[ctxPage1Idx] — whichrestoreFromSafeImagedoes write correctly) to the submission slot (GGTT[0x19]). No hardcoded ring values; the copy uses exactly what Apple wrote.IGHardwareContext::initWithOptionspost-hook (V509) logs for diagnostics — confirms the context buffer is valid (DW1=11001015/11081019) right after init.populateResetRegisterListpost-hook (V507) re-runs the copy on every engine reset cycle usingcb->lastRCSCtx. The display retry loop reuses an existing context without callingwithOptionsagain, so V508 never fires after initial context creation; V507 covers all subsequent submission attempts.
Validity check: (ap[1] & 0x1F801000) != 0x11001000 — catches both fill patterns and any other non-LRI value. Passes 0x11001005 and 0x11081019 (the two real headers seen from Apple's context images, which differ by LRI count field for different context types).
Status confirmed from logs:
- V508[1]: slot
00ffffff→ copied from ctx buffer →DW1=11001015✓ - V508[4] (arg=0): slot
00bfbfbf→ copied →DW1=11081019, persists in all subsequent V508 calls ✓ - Accelerator starts: V45
getState()=0x1e(reg=1 match=1 pub=1), Metal/GL/VA plugins loaded ✓ - V507 re-repair now fires on every
populateResetRegisterListreturn to cover the display retry loop
Remaining blocker: Preamble ring (16 DWs, two PIPE_CONTROLs) executes (HEAD=TAIL=0x40), but stamp write (value=2 to GPU address 0x40001200 = GGTT[0x40001]+0x200) may not land — GGTT[0x40001] PTE not yet verified. If unmapped, fwWaitForHardwareRegisterValue polls forever. Next step: add GGTT[0x40001] PTE + content dump to HANGCHECK.
- fRegCache/fWriteAccessor boot hang root cause identified and fixed:
AppleIntelPlane::fRegCacheis at +0x88 (verified via disasm) — it holds anAppleIntelPlaneRegCache *that Apple's code dereferences for vtable DSB accessor dispatch. WritingcconttofRegCachecauses an immediate hang becauseccontis the wrong object type (vtable mismatch on DSB method dispatch). The correct ccont slot isfWriteAccessorat +0x90. All three plane ccont-init hooks in V204 now usegetMember<void *>(that, 0x90) = ccontinstead ofthat->fRegCache = …, leaving Apple's realfRegCachepointer untouched.AppleIntelPlanestruct fully verified:fEnabled=+0x84 (byte),fRegCache=+0x88,fWriteAccessor=+0x90, inline shadowsPLANE_CTL=+0x100,PLANE_STRIDE=+0x104,PLANE_COLOR_CTL=+0x154 (all confirmed from disasm). - V221
waitForStamprestored: RoutesIOAccelEventMachine2::waitForStamp— if a stamp times out beforeGen11::gGfxAccelStartDoneis set (i.e. beforeIntelAccelerator::start()returns), fakes success (*outStamp = stamp, return 0) to unblock a CoreDisplay deadlock that occurs when CoreDisplay calls stamp-3 before the GPU accelerator finishes initialising.gGfxAccelStartDoneis settruein thestart()hook wrapper immediately after the real start returns; from that point stamps are handled natively. - V500
set_id_modediagnostic restored: RoutesIOAccelLegacySurface::set_id_modeand logs failures (capped at 32 per boot). On failure, extractsbadBits = mode & 0xff8073c0andgoodBits = mode & 0x007f8c3fto identify which reserved mode bits the caller is setting. Pure diagnostic — does not alter return value. - DBUF root cause identified (
-ngreentglwithgfxrequired on Gen11+): Per-planeDBUF_BUF_CFG_<PIPE>_<PLANE>allocation lives in the watermark/atomic-commit pipeline that ships withAppleIntelTGLGraphics.kext(the HW accelerator kext), not inAppleIntelTGLGraphicsFramebuffer.kext. With FB-only mode (-ngreentglfb),DBUF_BUF_CFG_A_PAstays at0x00000000even thoughPLANE_CTLenable bit is set — the display engine fetches from a zero-block DBUF range and produces duplicated/fragmented output (most visible on the boot Apple-logo + loading-bar phase and the login screen). Loading both kexts via-ngreentglwithgfxlets Apple's native code program DBUF correctly. V203 register dump (Pipe/Trans/DSC/M-N/DBUF) confirmedDBUF_BUF_CFG_A_PA = 0as the smoking gun in the FB-only case. - V204 ccont init hooks re-enabled:
AppleIntelScaler::initandAppleIntelPlane::initare now routed in the FBT branch soccontis set intothat[+0x28]/that[+0x90]at construction time (matches Visual Ehrmanntraut's working configuration). The per-method ccont patches (disableScaler/enablePlane/programPipeScaler/updateRegisterCache/disableDisplayEngine/enableDisplayEngine/hwSetPowerWellStateAux/DDI/raWriteRegister32b) remain enabled as belt-and-suspenders for any path that bypasses the constructor. - V204b backlight panel hook re-enabled:
AppleIntelPanel::setDisplayroute +F%uT%04x→F%uTxxxxpanel-data string patch inkextBacklightbranch. - dp0 path (CPU compositor /
-ngreendp0) active investigation:- V99S: Hook in
raWriteRegister32intercepts allPLANE_SURFwrites ≥0x10000000(non-aperture) and redirects them to0x0; simultaneously forcesPLANE_CTLlinear andPLANE_STRIDE=0xa0. Keeps the display scanning BAR2 aperture while the Intel driver tries to migrate to non-aperture GGTT. - V99G: One-shot GGTT remap — when V99S first intercepts a non-aperture
PLANE_SURFwrite, copies PTEs from the non-aperture surface pages (GGTT[surfPage..surfPage+3999]) toGGTT[0..3999]so thatSURF=0x0scans the same physical pages that WindowServer's CPU compositor writes to. Confirmed visible: Apple boot flash appears briefly on dp0. - V88 dp0 guard: Added
!isDisplayPipeForceDisabled()guard to the V88 scanout-fill block. Without this, V88 iteration 5 read the hardwarePLANE_SURFecho (0x412be000) before V99S redirected it, causing V88's plane toggle to arm with the wrong surface and paint non-aperture pages with color bands (confirmed red horizontal bar artefact — now fixed). - Remaining blocker:
kIOWindowServerActiveAttribute = 0x3 → 0x1transition fires ~215–233 ms after WindowServer goes active. After reaching state0x1, WS stops CPU compositing — screen freezes on the last written frame. Root cause under investigation: stamp-9 timeout inIOAccelDisplayPipetransactions (V55/Path D testing), possible inability to openIOAccelDisplayPipeUserClient2, or WS degrading due to a GPU restart signal propagation.
- V99S: Hook in
- V80L fix:
userspace watchdog timeoutKP root cause confirmed and fixed. V80L (plane-linearization writes inv71EMrEnforcer) limited to first 3 ticks only. Brief display flash preserved; WS crash-loop eliminated.-ngreenv80lenables continuous mode for testing. - V158: Cancel pending execlist contexts + drain CSB after every fake-success
resetGraphicsEnginereturn. Writes 4×0 toRING_ELSP(null EL0+EL1 context descriptors), then readsRING_CONTEXT_STATUS_PTRand advances read_ptr to write_ptr. Applied on both V153 and V154 return-0 paths. - V158: Cancel pending execlist contexts + drain CSB after every fake-success
resetGraphicsEnginereturn. Writes 4×0 toRING_ELSP(two null 64-bit context descriptors = cancel EL0 + EL1), then readsRING_CONTEXT_STATUS_PTRand writes back with read_ptr set to write_ptr. LogsEXECvalue after the null writes to confirm whether execlist FIFO cleared. Applied on both V153 and V154 return-0 paths. Build confirmed clean. Boot test pending. - V157: Removed
rcsCtl == 0guard from V153 quiescent check — hardware restores CTL to0x7001before V153 reads it, so the condition was always false. OnlyrcsHead == rcsTail && err == 0is needed. Boot log confirmed: V153 now fires from call #1. - V156: Fixed
RING_ENABLEbit (ORed| 1at all CTL write sites). DroppedbCtl == 0from V154 check. Stabilized ring-enabled state across all fake-success paths. - V155: Snapshot of RCS CTL (with
RING_ENABLEforced) taken before the originalresetGraphicsEnginecall; restored on V153 and V154 return-0 paths to prevent the driver disabling the ring. - V154: RPL-only circuit-breaker — after 3 consecutive quiescent
ret=1025coercions by V153, skip calling the originalresetGraphicsEngineentirely. Prevents the health monitor (V60M, 2 s tick × 60 = 120 s budget) from burning the full watchdog window on redundant resets. - V153: Coerce
resetGraphicsEngine ret=1025 → 0when RCS ring is quiescent (rcsHead == rcsTail,ERROR_GEN6 == 0) on non-real TGL. Increments consecutive-quiescent counter for V154 arming. - V152: BCS ring drain — force
RING_TAIL ← RING_HEADbefore BCS reset to stop repeated "ring not empty" resets on RPL where the BCS engine cannot execute under the TGL driver. - DYLD compact baseline: Assertion bypass remains baseline on Ventura+/Sonoma. Stage-3 safety stubs are conditional (non-real TGL default), with full-MTL override available via
-ngreenfullmtl. - DisplayPipe defaults updated: Native
DisplayPipeSupportedis default.-ngreendp0is explicit fallback. - V77 default policy updated: kill delay default is full monitor window (no automatic client termination unless explicitly requested).
- V96 connector state update:
getOnlineInfoforces online + changed for stronger hotplug/state propagation. - V110: V59 delayed checks + V74 EMR enforcer run unconditionally on non-real TGL; V60 monitor remains opt-in via
-ngreenexp. - V75: CRITICAL FIX — Removed post-
awaitPublishingproperty override race condition. Display output now stable and corruption-free. - V74: Permanent EMR enforcer — 50ms timer runs indefinitely, keeping ERROR_GEN6 masked.
- V111C: For non-real TGL, original Blit3D initializer is no longer auto-selected in full-MTL mode; it is opt-in only via
-ngreenV69AllowOriginal.
On RPL under the TGL driver spoof, Apple's resetGraphicsEngine returns 1025 (ETIMEDOUT) on every call because the TGL reset sequence issues commands the RPL hardware cannot execute. This triggers a userspace watchdog timeout panic as follows:
- The V60M health monitor fires every ~2 s for up to 120 s (60 ticks).
- Each tick calls
resetGraphicsEngine— before V153/V154 this always returned1025and reset the watchdog counter. - After 120 s of no successful reset, the kernel watchdog fires.
V153/V154 break the storm by detecting a quiescent RCS ring and returning 0 (success) without touching the hardware. V154 then short-circuits all subsequent calls for the remainder of the boot session. V155/V156/V157 ensure the RCS ring stays enabled (CTL=0x7001) across all fake-success returns.
The remaining issue post-V157 is that RING_EXECLIST_STATUS (EXEC) stays at 0x40018098 (bit 30 = execlist queue stalled) and RING_CONTEXT_STATUS_PTR shows write_ptr ahead of read_ptr across all V60M ticks — meaning Apple's driver still sees pending context work and continues scheduling resets even though V154 returns instantly. V158 targets this by nulling the execlist submission port and draining the CSB on every fake-success return.
Key registers (all offsets from RENDER_RING_BASE = 0x2000):
| Register | Offset | Role in fix |
|---|---|---|
RING_ELSP |
+0x230 |
Write 4×0 to cancel pending EL0+EL1 context descriptors |
RING_EXECLIST_STATUS |
+0x234 |
Bit 30 = execlist FIFO stalled; cleared by ELSP null writes |
RING_CONTEXT_STATUS_PTR |
+0x3a0 |
Bits[15:8]=write_ptr, [7:0]=read_ptr; advance read→write to drain CSB |
RING_CTL |
+0x3c |
Bit 0 = RING_ENABLE; V155 saves and restores this on every fake-success |
ERROR_GEN6 |
0x40A0 |
Must be 0 for V153 quiescent condition |
System boots to login on RPL macOS 14.7.1 (23H222). GPU reset storm tamed (V153/V154). userspace watchdog timeout KP fixed (V80L). CTL stable at 0x7001.
dp0 investigation (-ngreendp0): V99G GGTT remap + V99S PLANE_SURF redirect confirmed working — boot flash (Apple logo) is visible briefly with linear+stride 0xa0 force. Root cause of black screen with X-tiled CTL identified: in FB-only mode the GPU tile engine is not running, so physical pages are written linearly by the CPU compositor while PLANE_CTL programs X-tiled scanout → display engine reads wrong bytes → black. Linear+0xa0 produces a visible but fragmented ("8 copies") image because the EFI/stolen-memory pages at GGTT[0..3999] contain linearly-written data that the HW interprets at the wrong stride. Multi-buffer GGTT mapping now available via ngreen-buf=N — each unique non-aperture IOSurface is assigned a fixed aperture slot so double/triple-buffered WS flips are routed to independent GGTT[slot*4000..] ranges and PLANE_SURF is rewritten to the matching slot address. Active blocker: kIOWindowServerActiveAttribute 0x3→0x1 ~215–233 ms after WS goes active; WS stops compositing after that transition. V55/Path D testing in progress.
V88 visual fill remains opt-in only (-ngreenv88). Default boots preserve normal Apple UI layout.
- Lilu 1.7.2+
- macOS Sonoma 14.x (Gen11/Gen12 targets) or macOS 10.12–14.x (legacy NootedBlue targets)
- Supported Intel iGPU (see Compatibility below)
- Discrete GPU disabled via SSDT (recommended) or
disable-gpuDeviceProperty on its PCI path
For current RPL test configuration, OpenCore DeviceProperties IGPU injection is required:
PciRoot(0x0)/Pci(0x2,0x0)AAPL,ig-platform-id=AABJmg==(0x9A490000 in little-endian)device-id=SZoAAA==(0x9A490000 in little-endian)force-online=AQAAAA==(enable force-online WEG patch)complete-modeset=AQAAAA==(enable complete-modeset WEG patch)rps-control=AQAAAA==(enable rps-control WEG patch)built-in=AA==AAPL,slot-name=built-inhda-gfx=onboard-1
These properties are essential for correct platform identification and WEG coexistence mode on RPL with TGL driver spoof.
Boot args advised for testing (Hookcase in /Library/Extensions/ too):
FB-only:
-v keepsyms=1 debug=0x100 IGLogLevel=8 -ngreentglfb -NGreenDebug liludump=250 msgbuf=725288 liludbuf=725288 ngreen-dmc=adlp -ngreenv189
FB+GFX:
-v keepsyms=1 debug=0x100 IGLogLevel=8 -ngreentglwithgfx -NGreenDebug liludump=250 msgbuf=725288 liludbuf=725288 ngreen-dmc=adlp -ngreenv189 -allow3d -disablegfxfirmware -ngreenfullmtlcore
Where: // Boot-arg "ngreen-dmc": // not set or "skip" → safe fallback: passthrough original + AUX only (proven working) // "tgl" → load TGL DMC v2.12 blob + TGL display engine registers // + ICL/TGL combo PHY signal levels (PHY_A eDP, PHY_B DP) // "adlp" → load ADL-P DMC v2.16 blob + ADL-P display engine registers // + combo PHY signal levels (PHY_A eDP) // "icl" → passthrough original ICL DMC load + ICL combo PHY signal levels
Note:
-ngreentglwithgfxis required on Gen11/TGL hardware to load BOTH the TGL framebuffer AND the TGL HW (accelerator) kext. FB-only mode (-ngreentglfb) does not allocate per-plane DBUF on Gen11+ — the watermark/DBUF programming pipeline lives in the HW kext (AppleIntelTGLGraphics.kext), so without it the display engine fetches from a zero-block DBUF range and produces duplicated/fragmented output.-ngreendp0 -ngreenv88are diagnostic flags for the dp0/CPU-compositor investigation path. Remove them for normal operation.
- Note: GPU bundles goes to /Library/GPUBundles
| Arg | Purpose |
|---|---|
-NGreenDebug |
Enable NootedGreen debug logging |
-ngreentglfb |
Load only the TGL framebuffer kext (FB-only mode). On Gen11+, this is generally NOT enough for a coherent display because per-plane DBUF allocation is HW-kext side. Diagnostic / FB-driver-isolation use only. |
-ngreentglwithgfx |
Load both the TGL framebuffer AND the TGL HW accelerator kext. Recommended for normal operation on TGL/RPL hardware. Pairs the FB driver with AppleIntelTGLGraphics.kext so the watermark/DBUF programming pipeline runs at mode-set time. |
-ngreentglgfx |
Load only the TGL HW kext, no FB. Diagnostic — hardware will not display anything without an FB driver. |
-ngreenicl |
Load the legacy ICL framebuffer + HW kexts instead of TGL. For older Gen11 hardware where TGL spoof isn't suitable. |
-disablegfxfirmware |
Disable GuC/HuC firmware loading — required on RPL/ADL because scheduler selection (ngreenSched) happens after the HW-branch processKext, so the driver attempts firmware init before NootedGreen can override the scheduler type. Not needed on real TGL (GuC loads natively). |
-ngwegcoex / ngwegcoex=1 |
Enable WEG coexistence mode. |
ngreenSched=N |
Select GPU scheduler type: 3 = GuC firmware, 4 = IGScheduler4, 5 = host preemptive (default: 3 on real TGL, 5 on RPL/ADL) |
| `ngreen-dmc=skip | tgl |
-allow3d |
Force 3D acceleration |
-nbdyldoff |
Disable ALL DYLD patches (CoreDisplay, OpenGL, Metal, SkyLight) — debug only |
-ngreenexp / ngreenexp=1 |
Enable experimental runtime monitor/timer paths (V60 monitor and extra diagnostics) |
-ngreenv60 / ngreenv60=1 |
Force-enable V60 monitor. |
-ngreenv60off / ngreenv60off=1 |
Force-disable V60 monitor. |
-ngreenv60rw / ngreenv60rw=1 |
Enable aggressive V60 MMIO write path. |
-ngreendp0 / ngreendp0=1 |
Force fallback mode: set DisplayPipeSupported=0 in accelerator capabilities |
-ngreendp1 / ngreendp1=1 |
Explicitly keep native DisplayPipeSupported path (default behavior) |
ngreenV77DelayKill=N |
Delay V77 display-pipe client termination by N monitor iterations (0..60, default 60 = effectively disabled) |
-ngreenv88 / ngreenv88=1 |
Enable V88 scanout fill + plane toggle diagnostics (draws test bars/colors; off by default) |
-ngreenv93 / ngreenv93=1 |
Enable V93 plane guard diagnostics (disabled by default). |
-ngreenfullmtl / ngreenfullmtl=1 |
Force full CoreDisplay Metal path on Ventura+/Sonoma by skipping Stage-3 NULL safety stubs (GetMTLTexture/GetMTLCommandQueue/RunFullDisplayPipe guard). This does not auto-enable Apple's original Blit3D initializer. |
-ngreenfullmtldyld / ngreenfullmtldyld=1 |
DYLD-side full-MTL override only. |
-ngreenfullmtlcore / ngreenfullmtlcore=1 |
Kernel-side full-MTL override only — forces shouldForceFullMetalPath() true. Accepts unified -ngreenfullmtl as fallback. Use together with -ngreenfullmtldyld for full override. |
| `ngreenV142=0 | 1 |
-ngreenbcsirq |
Enable BCS bit in tier-1 interrupt want mask on spoof path (advanced diagnostic). |
| `ngreenV120=0 | 1 |
| `ngreenV130=0 | 1 |
ngreenV130warmup=N |
Hybrid warmup window for ngreenV130=3 (0..200, default 12 calls). |
-ngreenV130forceorig |
Allow unsafe original barrier path when mode requests it. |
-ngreenV69AllowOriginal |
Opt in to Apple's original Blit3D initialize on non-real TGL when safety preconditions are met. High risk / diagnostic only; can panic on unsupported setups. |
-ngreenV69SkipOriginal |
Hard-disable Apple's original Blit3D initialize on non-real TGL, even if -ngreenV69AllowOriginal is present. |
-ngreenV69ForceOriginalUnsafe |
Override safety rejection and force original Blit3D init on spoofed RPL/ADL (crash-debug only). |
-ngreenv80l / ngreenv80l=1 |
Run V80L plane-linearization writes continuously (every 50ms) instead of the default first-3-ticks-only behavior. Testing only — causes WS crash-loop + watchdog KP on normal boots. |
ngreen-buf=N |
GGTT multi-buffer slots for the dp0 SURF-redirect path: 1=single, 2=double (default), 3=triple. Each slot occupies 4000 GGTT pages (0xFA0000 bytes). Slot 0 → SURF=0x0, slot 1 → SURF=0xFA0000, slot 2 → SURF=0x1F40000. Apple's non-aperture IOSurface pages are remapped into their assigned slot on every flip; SURF is rewritten to the matching aperture address. Single-buffer collapses all flips to slot 0 (original behaviour). Double/triple allow the display engine to scan independent physical pages per IOSurface without cross-contamination. |
-ngreenforceprops / ngreenforceprops=1 |
Enable legacy forced IGPU property injection (AAPL,ig-platform-id, model, saved-config, etc.). Disabled by default in compatibility-first mode. |
-ngreenV188htfind |
Enable narrow DYLD hash-find guard variant (V188) for AccessComplete crash diagnostics. |
netdbg=<ip:port> |
Enable network debug logger destination for kernel-side NETDBG output. |
IGLogLevel=8 |
Maximum Intel GPU driver logging |
-liludbg |
Enable Lilu debug logging |
liludump=N |
Dump Lilu logs after N seconds (example: 125 or 200). |
Recommended debug order for ngreenV142 on spoofed RPL/ADL:
ngreenV142=1(stable bypass baseline)ngreenV142=2(alternate bypass semantics)ngreenV142=3only for controlled repro (Apple original path)
Sonoma 14.7.1 note: when WindowServer crashes with EXC_BAD_ACCESS at 0x80 in
CoreDisplay::DisplaySurface::AccessComplete() / std::__hash_table::find, NootedGreen
now applies a non-real-TGL-only DYLD guard (V188) that stubs the crashing
MTLRenderPipelineState hash-table find specialization so CoreDisplay can take its
fallback path. Real TGL behavior is unchanged.
-ngreenV188htfind enables the optional narrow V188 hash-find debug path. Keep it off for normal testing.
Hookcase (change AppleInteePortHal and AppleIntelPortHal implementation):
// Register selection by platform:
// ICL (AppleIntelFramebufferController path): 0xC4030 (ICL_SHOTPLUG_CTL_DDI)
// TGL: 0x44470
// ADL-P / RPL-P: 0x1638a0
uint32_t registerValue = callback->readReg32(0x1638a0); // ADL-P/RPL-Pneed your logs or kernel panic log.. without them I cannot do anything... so you must boot with - for example an empty nblue - inside the system and get previous lilulog and logs related THAT boot.
log show --style syslog --predicate 'processID == 0' --last 15m --info --debug > /tmp/x.log
grep "[IGFB]" /tmp/x.log > /tmp/fb.logExample Lilu log path:
/private/var/log/Lilu_1.7.2_23.6.txt
/Libtary/Logs/DiagnosticReports/..
Additional developer note:
[N.B. library is in continue developement]
Developer from all over the world, are you ready? Still needs some patches to my fully working RPL-P laptop but... it's open to developer now.
No more IOPCIPrimaryMatch, we work on IOResource so just put your kexts (+ bundle) in /Library/Extensions folder or use /System/Library/Extensions kext
https://github.com/sgiammori/NootedGreen
IOResources solving now is done like this for TGL kexts or IcL ketxts : first look at LE kexts and if any kexts is found than fallback to find in SLE kexts : * framebuffer for fb * + * graphics for gpu * + * bundle for metal *
=> they need permissions fix (also Hookcase) so before move to /L/E do in some random folder, check below
=> IOPCIPrimarymatch must be set in both *TGLGraphics* kexts in /Library/Extensiona
- sudo chmod -R 755 Apple*
- sudo chown -R root:wheel Apple*
- move the files to /L/E
- delete /Library/KernelCollections/AuxiliaryKernelExtensions.kc
- redo sudo chown -R root:wheel /Library/Extensions/Apple*
- sudo kextcache -i /
(if System asks you permissions, just allow and restart before to test)
(maybe necessary this below also)
sudo kmutil load -p /Library/Extensions/AppleIntelTGLGraphics.kext 2>&1
sudo kextcache -i /
Recent changes switch NootedGreen to safer defaults for cross-machine portability:
- Legacy hardcoded IGPU property seeding is now opt-in, not default.
- Experimental watchdog/monitor logic is still opt-in via
-ngreenexp. - Native display-pipe path is now default; forced fallback mode is opt-in via
-ngreendp0. - Coexistence paths avoid forcing DVMT/framebuffer processing when the DVMT module is not enabled.
This reduces machine-specific assumptions in default boots and keeps aggressive behavior available only when explicitly requested for debugging.
The Intel TGL graphics driver supports three scheduler types, selectable at boot:
| Type | Name | Description |
|---|---|---|
| 3 | GuC firmware | Default Apple scheduler — loads GuC binary firmware. Requires matching firmware blobs. |
| 4 | IGScheduler4 | Intermediate scheduler. |
| 5 | Host preemptive | Host-based scheduler — no firmware required. Ring command streamer managed by the driver. Recommended for unsupported hardware. |
Selection priority:
- Boot argument
ngreenSched=N(highest priority) SchedulerTypekey in Info.plist (NootedGreen personality)- Default:
3(GuC firmware) on real TGL,5(host preemptive) on RPL/ADL
Type 5 bypasses IGScheduler::initFirmware() entirely, avoiding GuC/HuC binary loading which fails on spoofed devices. The RCS ring is initialized directly by the host driver.
NootedGreen uses inline CPUID (EAX=1) at load time to read the real CPU model — no config.plist or device-ID spoofing can fake this. The result sets isRealTGL:
| CPU | Model | isRealTGL |
|---|---|---|
| Tiger Lake-U (i7-1165G7, etc.) | 0x8C |
true |
| Tiger Lake-H (i7-11800H, etc.) | 0x8D |
true |
| Raptor Lake-P (i7-13700H, etc.) | 0xBA |
false |
| Raptor Lake-S | 0xBF |
false |
| Raptor Lake-HX | 0xB7 |
false |
| Alder Lake-P | 0x9A |
false |
| Alder Lake-S | 0x97 |
false |
When isRealTGL = false (RPL/ADL), the following RPL-specific patches are applied:
- Topology overrides — L3 bank count, max EU count, subslice count hardcoded for 96EU RPL config
- BCS engine bypass — skip blitter engine init in
hwDevStart(RPL BCS is dead under TGL driver) - GuC binary stub —
loadGuCBinaryreturns 1 instead of loading firmware (wrong microarch) - MultiForceWakeSelect=1 — redirect ForceWake to hooked
SafeForceWakeMultithreaded(RPL ACK=0 on native path) - BCS engine reset — stop+clear dead BCS ring after
start()(V51)
When isRealTGL = true, all of the above are skipped — the driver uses Apple's native topology, GuC firmware, ForceWake, and BCS engine as-is.
For GPU acceleration, the following userspace driver bundles must be installed in /Library/Extensions/:
| Bundle | Purpose |
|---|---|
AppleIntelTGLGraphicsMTLDriver.bundle |
Metal driver |
AppleIntelTGLGraphicsGLDriver.bundle |
OpenGL driver |
AppleIntelTGLGraphicsVADriver.bundle |
Video Acceleration driver |
AppleIntelTGLGraphicsVAME.bundle |
VA Media Engine |
AppleIntelGraphicsShared.bundle |
Shared graphics library |
These bundles are loaded by name (via MetalPluginName, IOGLBundleName, etc.), not by CFBundleIdentifier. They are not shipped with NootedGreen and must be sourced separately.
NootedGreen keeps a strict load-path policy for Gen11/Gen12 bring-up:
- Primary (preferred): TGL from
/Library/ExtensionsAppleIntelTGLGraphicsFramebuffer.kextAppleIntelTGLGraphics.kext
- Fallback: ICL from
/System/Library/ExtensionsAppleIntelICLLPGraphicsFramebuffer.kextAppleIntelICLGraphics.kext
Runtime guards enforce this behavior:
- If TGL framebuffer loads, ICL framebuffer processing is skipped.
- If TGL accelerator loads, ICL accelerator processing is skipped.
For Metal bundle discovery, DYLD patching still prioritizes /Library/Extensions first (via gpu_bundle_find_trusted path rewrite), with /System/Library/Extensions retained as fallback.
NootedBlue legacy support (fully preserved):
- Haswell (10.12+)
- Broadwell / Braswell (10.14+)
- Gemini Lake (10.14+)
NootedGreen (Gen11/Gen12 — TGL driver spoofing):
| Platform | Status | Est. | Notes |
|---|---|---|---|
| Tiger Lake | ~90% | V52 | RPL-specific patches auto-skipped via CPUID. GuC, topology, ForceWake, BCS all use native Apple paths. Remaining risk: SKU bypass hook + DYLD patches still in the path. No real TGL hardware tested yet. |
| Raptor Lake-P | ~70% | V80L | Primary dev platform (i7-13700H). System boots to login on macOS 14.7.1 (23H222). GPU reset storm tamed: V153/V154 circuit-breaker confirmed working. userspace watchdog timeout KP root cause identified and fixed: V80L plane-linearization in v71EmrEnforcer was fighting WindowServer over plane registers every 50ms — now limited to first 3 ticks. Brief display flash at boot preserved. Active work: V158 execlist/CSB drain. |
| Alder Lake | ~35% | — | Same Gen12 arch as RPL, should behave similarly. Untested. |
| Rocket Lake | ~25% | — | Gen12 LP but different display engine. Untested. |
| Ice Lake | ~50% | V52 | Dedicated ICL path exists (ICL FB + ICL HW kextInfos, ICL-specific object offsets in getGPUInfoICL, SKU gate×3, platform remap, PAVP hook, DYLD ICL Metal device-ID bypass). Topology hardcoded to ICL GT2 LP (1×8×8=64EU). IRQ init disabled (V37 boot hang). ICL path only activates when TGL kexts are absent. Untested on real ICL hardware. |
Open NootedGreen.xcodeproj and select the NootedGreen scheme to build the Gen11/Gen12 plugin, or one of the original NootedBlue schemes for legacy hardware. Build with Xcode.
- Stefano Giammori (@sgiammori) — reverse engineering, driver development, hardware testing