Files
Snapintosh/tools/cycle_lib/installed_fresh.py
T

2237 lines
162 KiB
Python

# SPDX-License-Identifier: MIT
"""cycle.py installed-fresh: one installed-SSD boot admitted from current sources.
docs/INSTALLED-FRESH-ADMISSION-20261016.md. The historical admission chain
(baseline launch, predecessor retirement and preservation, image, SSD and TXM
qualifications) was deleted in the 2026-09-30 cleanup and is not recreated.
Every input here is regenerated and checked against an independent pin (the
profile's firmware and root-hash pins, txmnative.IMAGE_SHA, the retained trust
cache), or is a retained asset bound by its recorded SHA-256. The device, GPU
and SSD state are re-observed on hardware. Nothing here writes the SSD; the
guest writes only inside its APFS container in the window (--ans-writable).
"""
import ast
import contextlib
import hashlib
import io
import json
import os
from pathlib import Path
import plistlib
import re
import struct
import subprocess
import sys
import time
import zipfile
from .common import need, image_base, PINS, serror_clear, PARKED_SERVICE_WORD, battery_minimum
from .bootstrap_smoke import IDENTITY, digest, load, modules, sha, source_paths, write
from . import speed_test as st
PROFILE = 'j700ap-26A428-xars-uniform-auxkc-installed-adreno-metal-display-sks-quiet'
FIRMWARE = st.FIRMWARE
IPSW = 'downloads/UniversalMac_27.0_26A428_Restore.ipsw'
# The display8 build that last reached AdrenoMTLDevice (docs/WORKSPACE-CLEANUP-20260930.md).
# The display8 collection with one instruction changed: the colour element's colorimetry 16 (Default
# RGB) became 10 (sRGB), as AdrenoDisplay.cpp now says (tools/patch-auxkc-colorimetry.py; source
# collection 4c8770c7, booted through 2026-10-17). docs/ADRENO-WINDOWSERVER-TARGETS-20261017.md.
# Rollback: AUXKC_SRGB, booted through v32.
AUXKC_SRGB = 'build/current-assets/auxkc-srgb-20261017/adreno.kc'
AUXKC_SRGB_SHA = 'c25f2f2cc8ad054e063021970a19edb66e7948e7962f93ee6d47433c640da302'
# From v33: relinked from current kext source (tools/build-auxkc-user.py; build/build-auxkc.py pin). The
# display8 code plus the sRGB element (now in source), the en0 naming thread, and read-only backing
# (AdrenoMemoryMap::pinBacking: a descriptor wired for device reads is prepared Out and mapped no-write;
# v32 dropped 68 submissions on kIOReturnNotWritable). docs/ADRENO-DEPTH-STENCIL-20261017.md, v32.
# kmutil marks AuxKC kexts OSBundleRequireExplicitLoad=true, which hides their personalities, and this
# route has no kernelmanagerd to load them: v33 booted the raw relink (c48b0ed0) and AdrenoX2 never
# started. tools/auxkc-autoload.py flips that one token (docs/ADRENO-AUXKC-AUTOLOAD-20260926.md).
# Rollback: AUXKC_READONLY (kext 7ec040b4), booted v33-v39.
AUXKC_READONLY = 'build/current-assets/auxkc-readonly-autoload-20261017/adreno.kc'
AUXKC_READONLY_SHA = '2cffa096f5b70838120c32381b01ea98d03f53d390afda2ac18503eb4ef2b439'
# Then GPU mappings resident across IOGPU's unwire within 512 MiB, and contiguous-run inserts
# (docs/ADRENO-GPU-MAP-RESIDENCY-20261017.md; v39: 1.09M one-page DART maps, 99% unmapped again).
# Kext 604216d0, relinked, autoloaded and pinned as above.
AUXKC = 'build/current-assets/auxkc-resident-20261017/adreno.kc'
AUXKC_SHA = '284f052f4af61114e4f3d13bc33146d336e5c149f17237bf2b65995e23f984b3'
KEXT_BUNDLE = 'org.snapintosh.driver.AdrenoX2'
# --input (docs/A16-INPUT-INSTALLED-20261017.md): the quiet profile plus the built-in keyboard's
# SnapI2CHID node and GENI page, a monitor with -DSNAP_I2C_INPUT=1, and this collection: the default
# AuxKC's AdrenoX2.kext input (7ec040b4) relinked with SnapI2CHID (tools/build-auxkc-user.py), both
# explicit-load flags cleared by tools/auxkc-autoload.py, pinned by build/build-auxkc.py.
INPUT_PROFILE = PROFILE + '-input'
INPUT_TOUCHPAD_PROFILE = INPUT_PROFILE + '-touchpad' # --input --input-touchpad; needs TOUCHPAD_QUALIFIED
# From v40: SnapI2CHID recovers its own leftover controller after a panic and a chainload (v39 refused it
# busy); docs/A16-INPUT-INSTALLED-20261017.md section 10. Rollback: auxkc-input-20261017 (09f79749), v38-v39.
# Then the same SnapI2CHID (6d79e8c1) with the resident-mapping AdrenoX2 (604216d0); rollback: the
# recover collection auxkc-input-recover-20261017 (94f19597).
# From v41: the GPU-residency AdrenoX2 (604216d0) with SnapI2CHID's keyboard event-driver personality (26A428's
# generic ones are IOMatchDefer; v40's reports made no key events) and HID-stack diagnostics;
# docs/A16-INPUT-INSTALLED-20261017.md section 11. Rollback: auxkc-input-resident-20261017 (ddeb0658).
# v42 isolation: the same SnapI2CHID (keyboard event-driver personality) with the pre-residency AdrenoX2 (7ec040b4):
# v41 (residency 604216d0 + refmod-skip) lost launchd to SIGILL. Residency build: auxkc-input-hidfix-20261017 (98a240e9).
# From v43: SnapI2CHID's own keyboard event service (SnapI2CHIDKeyboard, an IOHIDEventService the bus starts and
# feeds; v41's cross-bundle IOHIDEventDriver personality never attached) with the pre-residency AdrenoX2 (7ec040b4);
# docs/A16-INPUT-INSTALLED-20261017.md section 12. v46 typed at the login window.
# From v48: the same plus HID country code 33 (US) and SnapI2CHIDPointer, the touchpad's mouse-mode pointer service
# (used only by --input-touchpad, refused until TOUCHPAD_QUALIFIED); section 13. Rollback: auxkc-input-kbd-20261017
# (7ff4e128).
# From v48: the real touchpad (2808:0219, generation 33) declares a 64-byte vendor input report 6 beside
# wMaxInputLength 52, which the old rule (every declared input report must fit) failed in report-descriptor;
# SnapI2CHID now needs only one input report to fit (section 14). Rollback: auxkc-input-tpd-20261017 (9c85e798).
# From 2026-10-18: the same SnapI2CHID with the cursor AdrenoX2 (851b9900): 7ec040b4's objects relinked with
# AdrenoDisplay.o rebuilt to composite the hardware-cursor layer and advertise CursorPlane=1
# (docs/ADRENO-DISPLAY-CURSOR-20261018.md). Rollback: auxkc-input-tpd2-20261018 (6b8ce322), below.
# 2026-10-19: the cursor collection's 7ec040b4 objects relinked with the current AdrenoDisplay.o (composed-row cursor
# publication, docs/GRAPHICS-CORRECTNESS-20261018.md; same undefined symbols as the cursor build). SnapI2CHID unchanged.
# The cursor collection remains for rollback as INPUT_AUXKC_CURSOR.
INPUT_AUXKC_PRESENT = 'build/current-assets/auxkc-input-present-20261019/adreno-input.kc'
INPUT_AUXKC_PRESENT_SHA = 'ee7bb6d5b9f8f5b5383e6585285a2176968c62ef7f27e0acf4097935a8c2e3e6'
# v81: the present collection's objects with adreno-ib-command.o rebuilt (CS storage tables of 1-8 slots in
# RGBA32F/RGBA16F/RGBA8/BGRA8, CS T# loads of 1-8 units; docs/USERSPACE-GRAPHICS-20261019.md). Rollback: PRESENT.
# Speed batch (docs/USERSPACE-GRAPHICS-20261019.md, "Speed batch"): the ci collection's objects with AdrenoDisplay.o
# rebuilt (frame tick armed before the layer-0 copy; copy cost published as CopyTimed/CopyMicros/CopyMaxMicros).
# Rollback: INPUT_AUXKC_CI.
# Then (same section): AdrenoDisplay.o rebuilt again to time the source-row cache maintenance separately
# (CopyMaintMicros). Rollback: INPUT_AUXKC_PACING.
INPUT_AUXKC = 'build/current-assets/auxkc-input-maint-20261019/adreno-input.kc'
INPUT_AUXKC_SHA = 'bd479cb1b02d342c4ff6df7b12dddf934d0ea3c6bfb7a421b985a48e83eeabba'
INPUT_AUXKC_PACING = 'build/current-assets/auxkc-input-pacing-20261019/adreno-input.kc'
INPUT_AUXKC_PACING_SHA = '0fe87092130668aabfb9538885742a87a4806bddf31fc82b6adce2bdfc7856a4'
INPUT_AUXKC_CI = 'build/current-assets/auxkc-input-ci-20261019/adreno-input.kc'
INPUT_AUXKC_CI_SHA = '8889bf2e216d2aa621a0ab5a67ff073a152a1cae2a7f439206476e309b30fad7'
# --present-damage (opt-in, 2026-10-20): INPUT_AUXKC's objects with AdrenoDisplay.o rebuilt. The layer-0 present
# maintains every source row in one pass with one DSB SY, reads each row once into a cacheable stage and writes only
# rows whose 128-bit signature changed, plus every cursor row and a rolling refresh of ceil(H/64) rows per present
# (WindowServer 26A428 sends no IOMFB dirty region). AdrenoDisplayStats adds CopyBarrier/Read/Hash/WriteMicros,
# CopyRows{Compared,Written,Skipped,Refreshed}, LastCacheMode, SourceAttr and SwapDirtyFlagged. SnapI2CHID and the
# other 11 objects are the maint collection's. Built offline; not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_DAMAGE = 'build/current-assets/auxkc-input-damage-20261020/adreno-input.kc'
INPUT_AUXKC_DAMAGE_SHA = '18f6d15bcee238b434efd81e2c09b5a72774860efda4f3b7fbda50bbb69254cd'
# --present-cached-read (opt-in, 2026-10-20): the damage collection with AdrenoDisplay.o built with
# -DADRENO_PRESENT_CACHED_READ=1. The layer-0 map asks kIOMapCopybackCache; 26A428's memoryReferenceMap applies the mode
# to the surface's VM object (vm_object_change_wimg_mode, SPTM UPDATE_DISJOINT_MULTIPAGE), so the write-combined IOMFB
# framebuffer becomes write-back for every mapping. The kext reads through it only after both probes walk to write-back
# (no mismatched alias), keeps the per-line CIVAC and barrier, and counts CachedRead{Conversions,Fallbacks,Mismatch,
# Reverted}. Built offline; not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_CACHED_READ = 'build/current-assets/auxkc-input-cachedread-20261020/adreno-input.kc'
INPUT_AUXKC_CACHED_READ_SHA = '8c735000ac55ca7b549c1c9a6f65b05a7880955c144199ca78d402195643d37b'
# --present-gpumap (opt-in, kext batch 8, 2026-10-20; FAILURES-AND-DESIGN.md in that batch): the cached-read collection
# with AdrenoX2.o, AdrenoMemoryMap.o, adreno-resource.o and AdrenoDisplay.o rebuilt. Frame pacing on the perf output (now
# five lines of at most 250 bytes: why/split/src/pace/map), the native CB0 range released before the DART reservation
# (no more "native VA bind failed" races), a collision retried at a fresh address, and a pool of empty DART reservations
# so a final free no longer recommits every page for a strict unmap (the 1 MiB UBO arenas: 128 -> 64 page maps each).
# Residency budget 0 (every unwire clears, as since v42). Built offline; not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_GPUMAP = 'build/current-assets/auxkc-input-gpumap-20261020/adreno-input.kc'
INPUT_AUXKC_GPUMAP_SHA = '0ce6467c67ed77abc240a267729fa2352a9ee8d0437ede524097898903545caf'
# --present-vsync60 (opt-in, 2026-10-20; FAILURES-AND-DESIGN-VSYNC.md in that work): the gpumap collection with only
# AdrenoDisplay.o rebuilt with -DADRENO_VSYNC60=1. The frame tick runs on an absolute 16,666,667 ns grid
# (IOTimerEventSource::wakeAtTime at the next grid point, armed early by the 26A428 timer's coalescing slop) instead of
# setTimeoutMS(16) relative to each tick (v9: 16 ms + ~1.8 ms, median 55.3 Hz). A stall skips missed grid points, an idle
# of more than four periods re-syncs to now + one period, and a sixth perf line reports the grid error. Built offline;
# not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_VSYNC60 = 'build/current-assets/auxkc-input-vsync60-20261020/adreno-input.kc'
INPUT_AUXKC_VSYNC60_SHA = '8cb1b11b67b910915fdd8abf86d839325a124bd21f93cbb1f0edaea96e60fc34'
# --present-dartpool (opt-in, kext batch 9, 2026-10-20; batch9/dart FAILURES-AND-DESIGN.md K1-K12): the vsync60
# collection with AdrenoMemoryMap.o and adreno-resource.o rebuilt with -DADRENO_POOL_ARENA=1. The pool of empty DART
# reservations gets a class for the plug-in's 1 MiB UBO staging arenas (256 entries, outside the 128 MiB the other spans
# share): v9's never-reused spans filled that budget for good, so 13,257 arenas were refused and each recommitted 64
# pages for a strict unmap. Built offline; not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_DARTPOOL = 'build/current-assets/auxkc-input-dartpool-20261020/adreno-input.kc'
INPUT_AUXKC_DARTPOOL_SHA = '3f1fe10b3684def919293dca99d6937f03d571ddedf131d6a6c20344ed52fe3c'
# --input-ptp (opt-in, 2026-10-20; docs/A16-INPUT-PTP-20261020.md): the vsync60 collection's AdrenoX2 (9822b918, the
# same bundle byte for byte) with a SnapI2CHID that switches the touchpad to Precision Touchpad mode at publish (Input
# Mode 3, Surface/Button Switch on, readback), parses the multi-contact report from the device's own descriptor and
# dispatches one-finger pointer, clicks, tap-to-click and phased two-finger scroll with momentum, under the trackpad
# scroll identity. A refused or unparseable switch leaves today's mouse mode. Needs --input --input-touchpad; implies
# --present-vsync60's AdrenoX2 (not --present-dartpool's). Built offline; not booted. Default unchanged (INPUT_AUXKC).
INPUT_AUXKC_PTP = 'build/current-assets/auxkc-input-ptp-20261020/adreno-input.kc'
INPUT_AUXKC_PTP_SHA = 'dc2673f3f5bf3abd4b37203d7c6744784a521380ee739c6ad47bdd0d2f5f2824'
# --input-ptp --present-dartpool together: the dartpool collection's AdrenoX2 (d8fe1363, byte for byte) with the
# --input-ptp SnapI2CHID (0850513d, byte for byte), linked, packaged and pinned the same way. Built offline; not booted.
INPUT_AUXKC_PTP_DARTPOOL = 'build/current-assets/auxkc-input-ptp-dartpool-20261020/adreno-input.kc'
INPUT_AUXKC_PTP_DARTPOOL_SHA = '19cd50515998699d27fc5d1f3ffb326d32fee9be8fadbb3564bdb7ace4ada199'
# --present-resident (opt-in, pacing work 2026-10-21, failure modes R1-R13/P1-P7 in its report): the ptp-dartpool
# collection with AdrenoX2.o, AdrenoMemoryMap.o and AdrenoDisplay.o rebuilt with -DADRENO_RESIDENT_BUDGET_MIB=512
# -DADRENO_PERF_GPU=1: GPU mappings stay resident across IOGPU's unwire within 512 MiB (no per-command-buffer DART
# re-insert or clear for reused memory: the plug-in's recycled staging arenas, WindowServer's surfaces), and a seventh
# perf line splits each window's GPU execution, submission, preparation, cache maintenance and residency. Linked
# 2026-10-21 (operator-20261021-wifi/build-resident-mt.sh) with AdrenoX2 fd7efe94 and the gestures stage-2 SnapI2CHID
# (b3d7fd99: the yield fix plus the Apple multitouch device, agents/gestures). Built offline; not booted.
# Batch 12 (2026-10-21): the same collection with AdrenoX2 5bbd4901 = resident+perfgpu plus asynchronous IB completion
# (-DADRENO_IB_ASYNC=1, agents/async-ib; pairs with --ib-async, falls back to the synchronous IB without it) and sparse
# cache maintenance (-DADRENO_CACHE_SPARSE=1, agents/cache-maint); SnapI2CHID b3d7fd99 unchanged. Built offline by
# operator-20261021-wifi/build-b12-sparse.sh; not booted. Rollback (b11v1, booted clean): auxkc-input-ptp-dartpool-
# resident-20261021, f226d667232445d59113bb6ea349bfdde44759f4b8de272f6042ce453542f61a.
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT = 'build/current-assets/auxkc-input-ptp-dartpool-resident-async-sparse-20261021/adreno-input.kc'
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_SHA = 'cdb153fa35e16e8d64e148c083f34222774c11cb347d84bb81a69bbcc5e5a0e7'
# --present-beam (opt-in, agents/tear-free 2026-10-21, FAILURE-MODES.md A-F): tear-free presentation on the panel's real
# scanout. The collection is the resident one with agents/tear-free/build/kext-final's AdrenoX2 (async-ib + cache-sparse
# configuration; AdrenoDisplay.o with -DADRENO_BEAM=1 -DADRENO_PRESENT_CURSOR_STAGE=1 -DADRENO_PRESENT_SKIP_MAINT=1) and
# the same SnapI2CHID; the monitor gets -DSNAP_DISPLAY_TIMING=1 and the launch SNAP_DISPLAY_TIMING=1 (both or neither):
# the eDP interface's INTF_5 page (0x0ae3a000) read-only Device at stage 2, its params block and two GPU-node properties
# (probes/displaytiming.py, compat/display-timing.h). Built offline; not booted. None until linked and pinned: refused.
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM = ('build/current-assets/auxkc-input-ptp-dartpool-resident-beam2-20261021/'
'adreno-input.kc')
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA = '255fd55db9742fd09375d88680809ff4324ef1d460bfd04df88ae0cb51aa33fd' # AdrenoX2 a8ed75f9 (tear-free fix2: Device attr 0b0000dd0x accepted, skip-maint only while LOCKED); b12v4 ran 63b15c6d (beam refused, attr 0x1)
DISPLAY_TIMING_DEFINE = '-DSNAP_DISPLAY_TIMING=1'
# --present-va-reclaim (opt-in, agents/gpu-va 2026-10-21, FAILURE-MODES.md G0-G17): the --present-beam collection with
# agents/gpu-va/build/kext-va's AdrenoX2 (tear-free kext-final's configuration plus -DADRENO_VA_RECLAIM=1 in
# AdrenoMemoryMap.o, AdrenoDisplay.o and adreno-resource.o): a refused DART reservation drains the pool of empty
# reservations and retries, later frees go back to the DART for a while, a released reservation is strict-freed over a
# pinned read-only filler instead of re-pinning its backing (b11v1's 47 frozen releases), and a perf va line reports the
# window's occupancy. Same SnapI2CHID, same monitor (no define); pairs with --present-beam's SNAP_DISPLAY_TIMING. Built
# offline; not booted. None until linked and pinned: refused.
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA = 'build/current-assets/auxkc-input-ptp-dartpool-resident-beam-va-20261021/adreno-input.kc'
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_SHA = 'beaaf19f45fa4f49b0791fd13191a0e7c12bbece035dd334bf2aef511cf4e4ff' # AdrenoX2 2fa57c97 (beam fix2 + ADRENO_VA_RECLAIM), operator-20261021-wifi/build-b12-va.sh
# --gpu-va-wide (opt-in, agents/gpu-va-wide 2026-10-21, FAILURE-MODES.md V1-V18): the GPU VA window [1 GiB, 16 GiB)
# instead of [1 GiB, 4 GiB), every party together: q1n1 `cp-fence --va-wide` and `adopt --va-wide` (CB0 T0SZ 30), the
# monitor define ADRENO_VA_WIDE (16 CB0 root entries, virtual DART slots 32..511, tables preallocated in a 30.06 MiB
# monitor-private reservation above the ANS2 one), the loader's SNAP_ADRENO_VA_WIDE (dart-adreno vm-size 0x3_c000_0000,
# GPU node snapintosh,gpu-va-bits = 34, params pool word) and the --present-va-reclaim collection relinked with
# agents/gpu-va-wide's AdrenoX2 (that kext's defines plus -DADRENO_VA_WIDE=1 in every unit). Built offline; not booted.
# None until linked and pinned: refused.
GPU_VA_WIDE_DEFINE = '-DADRENO_VA_WIDE=1'
# b12v9 A/B (2026-10-21): the same configuration without ADRENO_CACHE_SPARSE (AdrenoX2 9b61d748), for b12v8's
# stale bands / flicker. Rollback (b12v8, with cache-sparse): auxkc-input-ptp-dartpool-resident-beam-va-wide-20261021,
# eaebd3d8191a9a62e51bb3994c63dc5ee864a945a0ed685e9614d83827a8576b.
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE = 'build/current-assets/auxkc-input-ptp-dartpool-resident-beam-va-wide-nosparse-20261021/adreno-input.kc'
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_SHA = 'a2674b9e4ff8107dbe352a4acf307b07c493461c78df4497398d4624d8d344d4' # AdrenoX2 9b61d748 (no cache-sparse)
# --present-latch (opt-in, agents/input-latency 2026-10-21, FAILURE-MODES.md L1-L9 H1-H11 T1-T9): touch-to-photon latency.
# The --gpu-va-wide collection relinked with:
# - agents/input-latency's AdrenoX2: gpu-va-wide's defines plus -DADRENO_LATCH=1 in AdrenoDisplay.o. The beam tick fires
# late in the panel's 2040-line blank, 3 ms before row 0, instead of before the vblank. The swap on screen is
# completed just before the next latch, and the pending swap is copied at the latch, so WindowServer renders just in
# time instead of a frame ahead. Built twice: on b13148e0 (kext-latch) and on the nosparse 9b61d748 (kext-latch-nosparse).
# Since b12v11 (frames copied before their IB finished, ibaft 17-87 a window): the hold is off by default, and a layer-0
# swap first seen less than 4 ms ago waits one latch while a GPU submission is in the kext (AdrenoX2.o counts them).
# b12v11's collection (b68c9539, below) is the first build; latch2 (agents/input-latency build-auxkc-latch.sh) is the fix.
# - its SnapI2CHID: stage B plus -DSNAP_I2C_HID_LATENCY=1. Polls are timed from the previous poll's start, every 4 ms
# while a report came in the last 0.5 s, and wire time is waited to the microsecond.
# Kext only: no monitor define, no loader change. Tunable with guest boot-args (--present-latch-us, --present-hold,
# --present-hold-margin-us, --input-active-us). Built offline; not booted. None until linked and pinned: refused.
# b12v12 (2026-10-21): input-latency's latch2 (hold off, copy gate) plus present-sync's -DADRENO_PRESENT_SYNC=1, nosparse
# (AdrenoX2 66992b5b, SnapI2CHID d90e0199). b12v11 ran b68c9539 (latch with hold: ibaft=36, 31.7 pps).
# b12v14 (2026-10-21): gpu-pipeline's kext (AdrenoX2 7dc009e2 = present-sync nosparse + ADRENO_PIPELINE; latch2, hold off).
# Rollback: latch2-sync-nosparse (f930bc2154ebea37b7c19be785ee4dafe7c1a360f20cd78541e67d4a54c51968), b12v12/b12v13.
# b12v16 fallback (2026-10-21): the pipeline kext (272bcccb, AdrenoX2 7dc009e2) panicked in b12v15 (IOGPUFamily data abort
# called from AdrenoX2), so back to present-sync's latch2-sync-nosparse (AdrenoX2 66992b5b, ran clean in b12v12/b12v13).
# Pipeline candidate: auxkc-...-latch2-pipe-nosparse-20261021, 272bcccbb4334ea6d090d9e24aa2b08c189e18eedfb389687b491a78f30a5f83.
# b12v17 (2026-10-21): fixed pipeline (AdrenoX2 71c94a4a, IOGPU initEvent static) + GPU recovery level 1
# (-DADRENO_GPU_RECOVER=1): AdrenoX2 73b4b8da, agents/gpu-recovery/build/build-auxkc-recover.sh VARIANT=pipe.
# Boot with --present-latch --present-pipeline 1 --ib-async --gpu-recover soft. Rollback: latch2-sync-nosparse
# f930bc2154ebea37b7c19be785ee4dafe7c1a360f20cd78541e67d4a54c51968 (b12v12/b12v13/b12v16, no pipeline).
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH = 'build/current-assets/org-desktop-nocoherent-20261023/adreno-input.kc'
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA = 'ccf0c8f4da4f63fbe25c2b74d728030452fea1ab23fcab162d108dc0e36d1592'
# b12v23 (2026-10-23): + IO-coherent maintenance skipping (AdrenoX2 c66edb6b = SnapAdreno PROFILE=desktop at its profiles
# commit; SnapI2CHID d90e0199 = PROFILE=desktop); boot with --gpu-coherent. Previous: cursor 43bed810 (AdrenoX2
# 981e0b88 = PROFILE=desktop-nocoherent), b12v19c-b12v20. tools/build-auxkc-desktop.sh NAME rebuilds a collection from
# this checkout and prints the two lines to put here (its bytes differ from these: see the script).
# b12v19c: + aligned live-scanout copies (agents/cursor-fix; b12v19r panicked on the shake-to-locate cursor). Previous: repin 2ec76b70...
# b12v19r: + resident re-pin kext b2686dfb (agents/resident-evict). Previous: relatch-sparse 1cd18fa5...
# b12v19: relatch + cache-sparse kext 2cd7ef66 (agents/b12v19-kext). Rollback b12v18: relatch-nosparse 65243492...
# b12v18: relatch kext 128667ac (agents/present-pacing). Rollback: latch2-pipe2-recover-nosparse, 2cb7b6cc...
# --edp-backlight (opt-in, agents/brightness): the --present-latch collection with SnapAdreno PROFILE=desktop-backlight's
# AdrenoX2 (the desktop profile plus -DADRENO_BACKLIGHT=1 in AdrenoDisplay.o: the AdrenoBacklight service CoreBrightness
# drives) and SnapI2CHID's desktop profile. None until built (KEXT_PROFILE=desktop-backlight
# tools/build-auxkc-desktop.sh NAME prints the two lines) and pinned: --edp-backlight refuses meanwhile.
INPUT_AUXKC_DESKTOP_BACKLIGHT = None
INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA = None
# --guest-net: INPUT_AUXKC's objects with SnapintoshVirtualEthernet's datapath (docs/GUEST-NETWORK-20261019.md).
# None until that collection is built and pinned; --guest-net refuses meanwhile.
INPUT_AUXKC_NET = None
INPUT_AUXKC_NET_SHA = None
INPUT_AUXKC_CURSOR = 'build/current-assets/auxkc-input-cursor-20261018/adreno-input.kc'
INPUT_AUXKC_CURSOR_SHA = '61c2b3c7d54989d83e0407ed188ab5f4e35f12cd1eac19b65bc47d685ece93de'
INPUT_AUXKC_TPD2 = 'build/current-assets/auxkc-input-tpd2-20261018/adreno-input.kc'
INPUT_AUXKC_TPD2_SHA = '6b8ce3226dedfea08745eef0ff41b7c006740f930e18b74c6718f84a7dfab109'
# The v43 SnapI2CHID (keyboard only, no pointer) with the GPU-residency AdrenoX2 (604216d0, suspected in v41's
# launchd SIGILL): swap these two
# into INPUT_AUXKC/INPUT_AUXKC_SHA to boot it.
INPUT_AUXKC_RESIDENT = 'build/current-assets/auxkc-input-kbd-resident-20261017/adreno-input.kc'
INPUT_AUXKC_RESIDENT_SHA = '92f878759ec07ed62cd4fdae532e4cff3fa2738e6ca2baec85eb75c7772a7b35'
INPUT_BUNDLES = sorted([KEXT_BUNDLE, 'org.snapintosh.driver.SnapI2CHID'])
INPUT_DEFINE = '-DSNAP_I2C_INPUT=1' # compat/i2c-input.h
# --qcc2072-wifi (opt-in, 2026-10-21; probes/qcc2072wifi.py, compat/qcc2072-wifi.h): the QCC2072's BAR0, BAR2 and
# read-only ECAM page, its 64 MiB DMA arena at 0xdc000000 and a read-only control page at stage 2, the
# /arm-io/snap-qcc2072 node, a monitor with -DSNAP_QCC2072_WIFI=1 and SNAP_QCC2072_WIFI=1 in the launch (both or
# neither). The collection is the route's input collection relinked with SnapintoshQCC2072 by
# tools/build-qcc2072-native.py --baseline <its directory>, named by --qcc2072-auxkc DIR and --qcc2072-auxkc-sha256.
QCC2072_WIFI_DEFINE = '-DSNAP_QCC2072_WIFI=1'
QCC2072_BUNDLE = 'org.snapintosh.driver.SnapintoshQCC2072'
QCC2072_AUXKC_STATUS = 'NATIVE_WIFI_AUXKC_LINKED_RUNTIME_UNQUALIFIED'
QCC2072_INPUTS = 'qcc2072-inputs' # under the attempt's directory: the three staged, hash-checked inputs
# The retained metal-stage rows plus the target-format plug-in's CDHash (docs/ADRENO-WINDOWSERVER-TARGETS-20261017.md),
# so the installed plug-in (b2600ed7) and its replacement (99cb0965) both load. The payload is the loader's own
# trust_cache_spec of these rows; it reproduces the retained metal-stage payload from the retained rows.
# 2026-10-17 depth/stencil (docs/ADRENO-DEPTH-STENCIL-20261017.md): the rows above plus the depth/stencil plug-in's
# CDHash (60da7066), so 9f6d28df/b2600ed7, dc0816af/99cb0965 and the new binary all load (rollback keeps booting).
# 2026-10-17 lossless '&BGA' surfaces, sampler clamps, A8/16-bit unorm/32-bit float sampling
# (docs/ADRENO-DEPTH-STENCIL-20261017.md): the rows above plus 9c0a6006, so every earlier plug-in still loads.
# 2026-10-17 framebuffer fetch (docs/ADRENO-DEPTH-STENCIL-20261017.md, "Plug-in: framebuffer fetch"): the rows above
# plus fef9f3d3, so every earlier plug-in still loads.
# 2026-10-17 BGR10A2/RGB10A2 targets and 'l10r' surfaces (ControlCenter's status items, v35): the rows above plus
# 417894e7, so every earlier plug-in still loads.
# 2026-10-17 unbound or unread textures, samplers and buffers (v38: com.apple.dock.extra's icon draws, loginwindow): the
# rows above plus c64b78c4, so every earlier plug-in still loads.
# 2026-10-19 userspace graphics plug-in (docs/USERSPACE-GRAPHICS-20261019.md): the rows above plus cefae7f6, so every
# earlier plug-in still loads. The previous set (dockicons, c64b78c4) remains for rollback as ADDITIONS_DOCKICONS.
# 2026-10-19 compute runtime set (docs/USERSPACE-GRAPHICS-20261019.md): the graphics rows plus executable 3a35a474 and
# the rebuilt compiler runtime d3431892; the graphics set (cefae7f6) remains for rollback as ADDITIONS_GRAPHICS.
# v81 (CoreImage compute shapes, lines): the compute rows plus executable a77ac8d8; the compute set (3a35a474,
# d3431892) remains for rollback as ADDITIONS_COMPUTE.
# v84 (aligned IOSurface shadows, CoreImage fill): the ci rows plus executable 18382aa6; the ci set (a77ac8d8) remains
# for rollback as ADDITIONS_CI.
# v86 (RenderBox icons): the shadow rows plus executable 5cf27b37 and runtime v3 910d3b8d; the shadow set (18382aa6)
# remains for rollback as ADDITIONS_SHADOW.
# v88 (RenderBox custom effects): the icons rows plus executable 8b9f0f7c and runtime v4 a282c8ef; the icons set
# (5cf27b37, 910d3b8d) remains for rollback as ADDITIONS_ICONS.
# 2026-10-19 Liquid Glass / corners (docs/USERSPACE-GRAPHICS-20261019.md, "CoreAnimation's generic fallback"): the
# effects rows plus executable 9e8d6f01 (CA_DISABLE_GENERIC_SHADERS without framebuffer fetch); the effects set
# (8b9f0f7c) remains for rollback as ADDITIONS_EFFECTS.
# 16-bit derivatives set (docs/USERSPACE-GRAPHICS-20261019.md, "Fix: derivatives of 16-bit values at 32 bits"): the
# glass rows plus runtime v5 (e29f47d0) and the executable resealed for it (a0178daf). The glass set (plug-in 9e8d6f01,
# runtime v4) remains for rollback as ADDITIONS_GLASS.
# Mip levels set (docs/USERSPACE-GRAPHICS-20261019.md, "Fix: mip levels the GPU can sample"): the deriv rows plus the
# executable 03c706d4 (multi-level T#s, the GPU's level pitches); the deriv set (a0178daf, runtime v5) remains for
# rollback as ADDITIONS_DERIV.
# Framebuffer fetch set (docs/USERSPACE-GRAPHICS-20261019.md, "Window corner wedges"): the mip levels rows plus the
# executable 537a9702 (isFramebufferReadSupported YES); the mip levels set (03c706d4) remains for rollback as
# ADDITIONS_MIPLEVELS.
# Compile speed set (docs/USERSPACE-GRAPHICS-20261019.md, "Speed: shader compile time"): the fbfetch rows plus runtime
# v6 (5e63dca7) and the executable resealed for it (213f1073); the fbfetch set remains for rollback as ADDITIONS_FBFETCH.
# Speed batch set (docs/USERSPACE-GRAPHICS-20261019.md, "Speed batch"): the compile speed rows plus the executable
# f4200c28 (display copy cost in the stats line); the compile speed set remains for rollback as ADDITIONS_COMPILESPEED.
# UBO staging set (docs/USERSPACE-GRAPHICS-20261019.md, "Speed: CoreAnimation uniforms staged by the kilobyte"): the
# pacing rows plus the executable 90bf8b14; the pacing set remains for rollback as ADDITIONS_PACING.
# Drag speed set (docs/PLUGIN-DRAG-SPEED-20261020.md): the UBO rows plus the executable a2ae66df (per-program draw
# plans, bulk dword writes, recycled staging arenas); the UBO set remains for rollback as ADDITIONS_UBO.
# WindowServer abort set (docs/WINDOWSERVER-APL-ABORT-20261020.md): the drag speed rows plus runtime v7 (ab4ccbd4: AIR 2.2
# texture reads, bounds-checked call operands) and the executable resealed for it (f605a348: CoreAnimation's APL filter
# off); the drag speed set remains for rollback as ADDITIONS_DRAGSPEED.
# Arena trim set (docs/PLUGIN-ARENA-TRIM-20261021.md): the WindowServer abort rows plus the executable 3566f23f (UBO
# staging arenas that give back GPU address space); the WindowServer abort set remains for rollback as ADDITIONS_WSABORT.
# Combined set (agents/present-sync, agents/gfx-correctness, agents/turnip-audit): the arena trim rows plus the executable
# 24c391f4 (commitAndWaitUntilSubmitted waits for the kernel submission, GPU mipmap generation, integer render targets and
# textures, BR-thread blit/compute passes, render-pass split); the arena trim set remains for rollback as
# ADDITIONS_ARENATRIM.
# gpu-perf set (agents/gpu-perf): the combined rows plus the executable 07149eed (per-command-buffer UBO mirror, GPU work
# counters); the combined set remains for rollback as ADDITIONS_COMBINED.
# Pipeline set (agents/gpu-pipeline): gpu-perf's rows plus the executable 1848e9a6 (gpu-perf's source built with
# -DADRENO_PIPELINE=1: IOSurface shadows wait for the command buffers using them on the GPU, needed once the kext returns
# WindowServer's submissions while their IB is in flight); gpu-perf's set remains for rollback as ADDITIONS_GPUPERF.
# Pipeline2 set (agents/gpu-pipeline): the pipeline rows plus the executable c291173b (pipeline + gpu-perf's blit chunks:
# no CP_MEMCPY over 1 MiB, after b12v13's 4 MiB copy latched the GPU); the pipeline set remains as ADDITIONS_PIPELINE.
# Mirror-slab set (agents/gpu-perf FAILURE-MODES.md R): the pipeline2 rows plus the executable 933060b2 (UBO mirror regions
# from slabs allocated once and never released); the pipeline2 set remains for rollback as ADDITIONS_PIPELINE2.
ADDITIONS = 'build/current-assets/metal-stage-mirrorslab-20261021/trust-additions.json'
ADDITIONS_SHA = '4802847aef13e6e46ebc50cd03b1e35ebafe929d5ae46f44f4de6a8d4fb64ae7'
TRUST_PAYLOAD = 'build/current-assets/metal-stage-mirrorslab-20261021/trustcache.payload'
TRUST_SHA = 'fb429aa20cea252f321d3b4ff18d7bc9425c10babb391d370315c9de5b4ff50f'
ADDITIONS_PIPELINE2 = 'build/current-assets/metal-stage-pipeline2-20261021/trust-additions.json'
ADDITIONS_PIPELINE2_SHA = 'eb035ef6ff2a380ece5ace3eddd9d9bc1365d9a8a478ba25ede795d8ccf5e119'
TRUST_PAYLOAD_PIPELINE2 = 'build/current-assets/metal-stage-pipeline2-20261021/trustcache.payload'
TRUST_SHA_PIPELINE2 = '2a6667e6607f00f5c77a43ccd91dbee382da9ec5160af5327813932aee2096e4'
ADDITIONS_PIPELINE = 'build/current-assets/metal-stage-pipeline-20261021/trust-additions.json'
ADDITIONS_PIPELINE_SHA = 'c764a692e94bc81185782032731025942221f278aae89a32fbb12f5f823632c5'
TRUST_PAYLOAD_PIPELINE = 'build/current-assets/metal-stage-pipeline-20261021/trustcache.payload'
TRUST_SHA_PIPELINE = 'd299050b828075d14a5f6c308a982e2c82678f0559c199fc54a160a5ccbe327d'
ADDITIONS_GPUPERF = 'build/current-assets/metal-stage-gpuperf-20261021/trust-additions.json'
ADDITIONS_GPUPERF_SHA = 'bd9946715ea5fd3e104649baaf5c6fb23c2d44e5e9649b836ec6d3ac259cd5d6'
TRUST_PAYLOAD_GPUPERF = 'build/current-assets/metal-stage-gpuperf-20261021/trustcache.payload'
TRUST_SHA_GPUPERF = '2abfb483845b156e1f67917a887ceac53b0ca49dc5ef4b413a143a207a1ca72d'
ADDITIONS_COMBINED = 'build/current-assets/metal-stage-combined-20261021/trust-additions.json'
ADDITIONS_COMBINED_SHA = '50cfd8625b9612fe60a7c1b76f05fda70acfe39627ff35c8c285cedd65d8e3ae'
TRUST_PAYLOAD_COMBINED = 'build/current-assets/metal-stage-combined-20261021/trustcache.payload'
TRUST_SHA_COMBINED = 'f03d27cf7964c875716901c0e0c271e5d21940dd6c9965dd00f592832403c5bc'
ADDITIONS_ARENATRIM = 'build/current-assets/metal-stage-arenatrim-20261021/trust-additions.json'
ADDITIONS_ARENATRIM_SHA = '4deb533398368a760ad6508705f21db97e65ecc60210abf7e427b9c5598632be'
TRUST_PAYLOAD_ARENATRIM = 'build/current-assets/metal-stage-arenatrim-20261021/trustcache.payload'
TRUST_SHA_ARENATRIM = 'd0d673120e04f3215e60cd2570e2294e39c1600406ac45add9e9122ccdd829ac'
ADDITIONS_WSABORT = 'build/current-assets/metal-stage-wsabort-20261020/trust-additions.json'
ADDITIONS_WSABORT_SHA = '3f8eedc22c69cd8340a2d6d3cbefc4cd8f2b42d22845220c3135a2ac02963163'
TRUST_PAYLOAD_WSABORT = 'build/current-assets/metal-stage-wsabort-20261020/trustcache.payload'
TRUST_SHA_WSABORT = '439d438c80546a5a31ce9a110bb8c022fe872ec806b23804f4aeb79630c1f973'
ADDITIONS_DRAGSPEED = 'build/current-assets/metal-stage-dragspeed-20261020/trust-additions.json'
ADDITIONS_DRAGSPEED_SHA = 'd70ca1af87a2f39b83574f8fc711ea00e0132ae938c9bbc88c4e3ee576f11ee9'
TRUST_PAYLOAD_DRAGSPEED = 'build/current-assets/metal-stage-dragspeed-20261020/trustcache.payload'
TRUST_SHA_DRAGSPEED = '50d0ddc2247f49496084e95b8b0c6058f09b6e2741a15f9d7db5d221de8ca4b4'
ADDITIONS_UBO = 'build/current-assets/metal-stage-ubo-20261019/trust-additions.json'
ADDITIONS_UBO_SHA = 'ea32f781d6317137d942b5888e8c4a4796b85dbe3a9ead3ffe90cdd8374c4aca'
TRUST_PAYLOAD_UBO = 'build/current-assets/metal-stage-ubo-20261019/trustcache.payload'
TRUST_SHA_UBO = '71b51c49975f26a97a4c02d3c7bb8ead3b7b0d333fc89614efd25a9b4a5070a1'
ADDITIONS_PACING = 'build/current-assets/metal-stage-pacing-20261019/trust-additions.json'
ADDITIONS_PACING_SHA = '36307882ee8a602a6708890c8c2a1e7955d40e3a3008b0f18bb0f954b720c91f'
TRUST_PAYLOAD_PACING = 'build/current-assets/metal-stage-pacing-20261019/trustcache.payload'
TRUST_SHA_PACING = '7edc4548cadc32758de124e7e4d1ff632aea2c2886ba044ac2b9f452a5da4d19'
ADDITIONS_COMPILESPEED = 'build/current-assets/metal-stage-compilespeed-20261019/trust-additions.json'
ADDITIONS_COMPILESPEED_SHA = 'ca579f1895cc9a5193072ca9f6b04ede896845541774b74037c91c3290301175'
TRUST_PAYLOAD_COMPILESPEED = 'build/current-assets/metal-stage-compilespeed-20261019/trustcache.payload'
TRUST_SHA_COMPILESPEED = 'f059c641fa2a08fd7b4a9431f3e82627c01f6b6f2f7affb0b1f103f75ed48491'
ADDITIONS_FBFETCH = 'build/current-assets/metal-stage-fbfetch-20261019/trust-additions.json'
ADDITIONS_FBFETCH_SHA = '10aef973d5320f4c6bc08a3662c13e505840262c3b691ec2d61c1690b532dcb9'
TRUST_PAYLOAD_FBFETCH = 'build/current-assets/metal-stage-fbfetch-20261019/trustcache.payload'
TRUST_SHA_FBFETCH = '1b09653c2e4bf5517b82d976d4121de8ec5d1458b4177824b762daeb92b3b6df'
ADDITIONS_MIPLEVELS = 'build/current-assets/metal-stage-miplevels-20261019/trust-additions.json'
ADDITIONS_MIPLEVELS_SHA = 'e32d83a3fdf6d5f5f2f0ec2756804c2982d24fb6344242f84324f2b8fa024dc6'
TRUST_PAYLOAD_MIPLEVELS = 'build/current-assets/metal-stage-miplevels-20261019/trustcache.payload'
TRUST_SHA_MIPLEVELS = 'fe6896ebb974a97a0758a60e7883c660c2b05ea1f75cddbfa7debf0ee192da97'
ADDITIONS_DERIV = 'build/current-assets/metal-stage-deriv-20261019/trust-additions.json'
ADDITIONS_DERIV_SHA = '14a505a987fddf7ca10f985886cdd809cfb289fc9001de83b87fa4228913caa5'
TRUST_PAYLOAD_DERIV = 'build/current-assets/metal-stage-deriv-20261019/trustcache.payload'
TRUST_SHA_DERIV = '735d2fe083eee2a2f4ef2940cbc132ab0b3192ed509a7c83ec440ae468d099f7'
ADDITIONS_GLASS = 'build/current-assets/metal-stage-glass-20261019/trust-additions.json'
ADDITIONS_GLASS_SHA = 'ac0bccbacb6d854169dba07cdaff08d62f47016b3b1a7b8530eb79f250435d72'
TRUST_PAYLOAD_GLASS = 'build/current-assets/metal-stage-glass-20261019/trustcache.payload'
TRUST_SHA_GLASS = 'da671e4c98117da6a9bdb36a4d7147951eece30ced281de40e6c17f3a2cfa8db'
ADDITIONS_EFFECTS = 'build/current-assets/metal-stage-effects-20261019/trust-additions.json'
ADDITIONS_EFFECTS_SHA = 'a87c0465b7e912b16ebfacd905584064439a244a787257292e5535fe359c40e3'
TRUST_PAYLOAD_EFFECTS = 'build/current-assets/metal-stage-effects-20261019/trustcache.payload'
TRUST_SHA_EFFECTS = 'e0fa2043c92e6f539cd27632dad70eb1a55cf72ddfd1ad7478929c49bd012bcd'
ADDITIONS_ICONS = 'build/current-assets/metal-stage-icons-20261019/trust-additions.json'
ADDITIONS_ICONS_SHA = 'd1a90f804ec5a8c3de24aed28d7c6fc7032dbcf957b6b9cf24a948d20238227c'
ADDITIONS_SHADOW = 'build/current-assets/metal-stage-shadow-20261019/trust-additions.json'
ADDITIONS_SHADOW_SHA = '63eb4e3315567d530c838bd2d4a35b9d77f65e8d8dec6adac51235c9e1c16059'
ADDITIONS_CI = 'build/current-assets/metal-stage-ci-20261019/trust-additions.json'
ADDITIONS_CI_SHA = 'c811ba71dda4896fae6634311800bce5d49116ac001e8ff445cf0883e21c5d6d'
ADDITIONS_COMPUTE = 'build/current-assets/metal-stage-compute-20261019/trust-additions.json'
ADDITIONS_COMPUTE_SHA = 'c58926605b314eb37405f118ad98ecac1069f8c5276de9388c26b4ef9afc5077'
ADDITIONS_GRAPHICS = 'build/current-assets/metal-stage-graphics-20261019/trust-additions.json'
ADDITIONS_GRAPHICS_SHA = '6785043c1a0c09e78076b27760c9a74a5fd312bf608d837ce3af7f695c7523cf'
ADDITIONS_DOCKICONS = 'build/current-assets/metal-stage-dockicons-20261017/trust-additions.json'
ADDITIONS_DOCKICONS_SHA = 'b454cd4bf54bb04c667f20d1594684e79f27464250220cfc4b420723ec5b0edc'
BRIDGE = ('tools/txm-image4-bridge.S', 'tools/txm-image4-bridge.ld')
LLVM = '/opt/homebrew/opt/llvm/bin'
# Only this prefix of the window header's SHA-256 survives (git 140bcfe, the
# gen72 point-size apply's header-after); the boot records the full value.
WINDOW_HEADER_PREFIX = '7016b991'
TIMESYNC_FLAG = '-timesync_bringup'
# 26A428's metacompressor runs Apple's WKdm compression instruction (0x00200820 at vm_compressor_algorithms.c:557)
# unless this boot-arg sets its software flag; the A16 has no such instruction, so the first compression under
# memory pressure panicked ("Undefined kernel instruction", v38). Not a kernel patch: the kernel's own switch.
COMPRESSOR_SW_FLAG = '-vm_compressor_wk_sw'
BASE_CMDLINE = '-v debug=0x8 serial=3 cluster_power=0 wdt=-1'
WATCHDOG = 1800
DISPLAY_SLOT = '/device-tree/arm-io/adreno-display'
# The installed launches' environment lived only in deleted launch records: the
# speed test's six-CPU settings without its RAM-disk preload requirement, plus
# the installed route's per-run keys (set at boot).
ENV = {'SNAP_SMP': '1', 'SNAP_GUEST_MIB': str(st.GUEST_MIB), 'SNAP_PHYS_BASE': hex(st.PHYS_BASE),
'SNAP_NATIVE_CALLS': '1', 'SNAP_WATCHDOG': str(WATCHDOG), 'SNAP_EARLY_STOP': 'guest,host',
'SNAP_EARLY_STOP_DELAY': '600', 'SNAP_EARLY_STOP_MAX_DELAY': '600'}
SCHEMA = 'snapintosh-installed-fresh-v1'
# The two-way console (docs/GUEST-SHELL-DESIGN-20261016.md), opt-in per attempt:
# the console rides on the host poll, the console-shell job runs only with
# snapintosh_shell=1, and -s asks launchd for a single-user shell first.
CONSOLE_FLAG = 'snapintosh_shell=1'
SINGLE_USER_FLAG = '-s'
# Opt-in monitor defines on top of the variant's recipe, given to every mode of the attempt.
WFI_RESUME_DEFINE = '-DSNAP_WFI_RESUME=1' # compat/wfi-resume.h: host-only EL2 ticks return an idle vCPU to its WFI
# compat/fast-timer-irq.h SNAP_FAST_TIMER_IRQ: vCPUs 1-5 take their EL2 timer tick without the service token (v89:
# 2.07M secondary ticks waited 87.1 s for it); needs the fast set's fast-trap admission, --root-cache-wide (its
# counters) and --service-subclass (it reports after the exit profile)
FAST_TIMER_IRQ_DEFINE = '-DSNAP_FAST_TIMER_IRQ=1'
# docs/DART-MEMO-20261019.md: compat/sptm-dart-memo.h SNAP_DART_MEMO, DART table verdicts so MAP/UNMAP (every GPU
# memory mapping) skip the 2,048-descriptor scan per level (v90: 1.6M maps at 21.6 us under the token); needs
# --root-cache-wide (the memo and its counters)
DART_MEMO_DEFINE = '-DSNAP_DART_MEMO=1'
# docs/AES-BITSLICE-20261019.md: compat/aes-bitslice.h SNAP_AES_BITSLICE, the PAC key derivation's software AES as a
# bitsliced cipher (v90: 790,926 derive-cache misses at about 325 µs each, inside 282 s of user returns); any variant
AES_BITSLICE_DEFINE = '-DSNAP_AES_BITSLICE=1'
# docs/FAST-IPI-20261019.md: compat/fast-ipi.h SNAP_FAST_IPI, the Apple IPI registers without the service token (v90:
# 3.96M of the 5.88M locked sysreg traps, which waited 183.6 s for it); needs the fast set's fast-trap admission and
# --root-cache-wide (its counters)
FAST_IPI_DEFINE = '-DSNAP_FAST_IPI=1'
# compat/fast-vector.h fast_el0_sysreg: EL0 counter-alias and SPRR UPERM reads answered off the service lock (fast only)
FAST_EL0_SYSREG_DEFINE = '-DSNAP_FAST_EL0_SYSREG=1'
# compat/service-profile.h service_sub_*: service-lock hold and exit timing per sub-class (instrumentation)
SERVICE_SUBCLASS_DEFINE = '-DSNAP_SERVICE_SUBCLASS=1'
# compat/dart-wiredcnt.h: ADRENO_GUEST_UNMAP subtracts the leaves it clears from XNU's IOMMU-table count (v29-v32 leak)
DART_WIREDCNT_DEFINE = '-DSNAP_DART_WIREDCNT=1'
# probes/xnuboot.c monitor_call_impl: after XNU's PANIC_ENTRY, serve ordinary calls so the panic string gets printed
PANIC_SERVE_DEFINE = '-DSNAP_PANIC_SERVE=1'
# compat/root-miss.h: a root-cache miss reuses the memo's shared-table verdicts, and a full cache evicts the
# least recently installed inactive root (v34: 49,783 rebuilds at about 8 ms, 73% of lock hold)
ROOT_MISS_FAST_DEFINE = '-DSNAP_ROOT_MISS_FAST=1'
# compat/update-fast.h: no TLBI for an access-flag-clear old leaf; the six-CPU kernel-copy check in one pass
UPDATE_DISJOINT_FAST_DEFINE = '-DSNAP_UPDATE_DISJOINT_FAST=1'
# shadow/sprr-class-fast.h: incremental kernel class proof on a PPERM/TPRO cache miss (v34: 153 s of lock hold)
KERNEL_PPERM_FAST_DEFINE = '-DSNAP_KERNEL_PPERM_FAST=1'
# compat/adreno-guest.h: a 1 s IB completion budget instead of 100 ms (v38 latched UNCERTAIN on one IB, no fault)
IB_BUDGET_1S_DEFINE = '-DADRENO_IB_POLL_DIVISOR=1u'
# compat/root-wide.h: 192 cached user roots instead of 64, state in monitor arena pages (v34: 64 roots
# reused within 0.4 s, so the full cache rebuilt one every 8 ms)
ROOT_CACHE_WIDE_DEFINE = '-DSNAP_ROOT_CACHE_WIDE=1'
# probes/xnuboot.c user_update_kernel_skip: the six-CPU kernel-copy check every 1024 skips instead of 64 (weaker audit)
UPDATE_CHECK_SPARSE_DEFINE = '-DSNAP_UPDATE_CHECK_SPARSE=1'
# compat/sptm-user.h SNAP_UNMAP_MIXED: an UNMAP_DISJOINT list naming user and kernel roots for one frame runs as
# one transaction (v39 stopped with BAD_CONFIG on a user-first one)
UNMAP_MIXED_DEFINE = '-DSNAP_UNMAP_MIXED=1'
# compat/boot-relocate.h SNAP_BOOT_RELOCATE: vCPU 0 runs on a physical CPU other than q1n1's (--prime-secondaries puts
# all six vCPUs on Prime cluster 1; a cross-cluster layout starved vCPU 0 at the service lock in v26)
BOOT_RELOCATE_DEFINE = '-DSNAP_BOOT_RELOCATE=1'
# compat/txm.h SNAP_TXM_ASPACE_WIDE: 4096 TXM address spaces instead of 512 (v47: 512 live 15 minutes after login),
# with the loader's matching 64-page arena (SNAP_TXM_ASPACE_WIDE=1 in the launch environment, below)
TXM_ASPACE_WIDE_DEFINE = '-DSNAP_TXM_ASPACE_WIDE=1'
# compat/fine-lock.h SNAP_FINE_LOCKS (docs/FINE-LOCKS-20261017.md): user MAP_PAGE, UNMAP_REGION and
# UPDATE_DISJOINT run under per-root stripes instead of the service token; SNAP_FINE_SWITCH adds SWITCH_ROOT
# cache hits. The monitor refuses to build outside the fast installed flag set (root-cache-wide,
# update-disjoint-fast, service-subclass and the native-table defaults).
FINE_LOCKS_DEFINE = '-DSNAP_FINE_LOCKS=1'
FINE_SWITCH_DEFINE = '-DSNAP_FINE_SWITCH=1'
# compat/service-lock.h SNAP_FAIR_TOKEN: the service token granted in arrival order (a ticket queue), so a CPU
# re-acquiring from its own cache cannot pass a waiter in another cluster (v26's one-second SERVICE_TIMEOUT).
FAIR_TOKEN_DEFINE = '-DSNAP_FAIR_TOKEN=1'
# compat/user-hw-walk.h SNAP_USER_HW_WALK: 4K (format 1) user roots run on XNU's own tables, UMPRR18 roots on
# monitor copies of their TPRO paths; page-table frames read-only at stage 2. Pair with --kernel-user-4k.
USER_HW_WALK_DEFINE = '-DSNAP_USER_HW_WALK=1'
# probes/xnuboot.c SNAP_IDREG_MMFR3: every vCPU records ID_AA64MMFR3/MMFR1 (S1PIE, S1POE) at setup
IDREG_MMFR3_DEFINE = '-DSNAP_IDREG_MMFR3=1'
# compat/s2-fault-retry.h SNAP_S2_FAULT_RETRY: a guest stage-2 translation fault on guest RAM that the CPU's own
# stage-2 tree maps is snapshotted (HPFAR, VTTBR/VTCR/HCR, both walks) and retried after TLBI VMALLS12E1 instead of
# stopping the run; the same access three times in a row or over 1000 a second per vCPU still stops (v62/v63/v65)
S2_FAULT_RETRY_DEFINE = '-DSNAP_S2_FAULT_RETRY=1'
# compat/s2-fault-retry.h SNAP_S2_FAULT_RETRY_STAGE1 (needs the above): also retry a fault whose hardware IPA (HPFAR)
# is not mapped but whose stage-1 re-walk's is. Off, that case stops: a stale stage-1 translation that did not fault
# may already have written the wrong page, and the guest writes the real install's disk.
S2_FAULT_RETRY_STAGE1_DEFINE = '-DSNAP_S2_FAULT_RETRY_STAGE1=1'
# docs/TOKEN-COSTS-20261018.md: the token's next costs. compat/nest-fast.h SNAP_NEST_FAST (NEST/UNNEST answer
# shared tables from the SWITCH_ROOT memo) and SNAP_DETACH_FAST (UNMAP_TABLE of an empty table, table by table);
# compat/root-index.h SNAP_ROOT_INDEX (TXM35/36/37 and root constructors without frame-map scans);
# compat/token-costs.h SNAP_OTHER_PROFILE (call_other split by call) and SNAP_GATE_WAIT (a converted call
# waits out a long token hold instead of declining). All need the fast set with --root-cache-wide and
# --service-subclass; NEST_FAST also the memo (installed defaults), GATE_WAIT --fine-locks.
NEST_FAST_DEFINE = '-DSNAP_NEST_FAST=1'
DETACH_FAST_DEFINE = '-DSNAP_DETACH_FAST=1'
ROOT_INDEX_DEFINE = '-DSNAP_ROOT_INDEX=1'
OTHER_PROFILE_DEFINE = '-DSNAP_OTHER_PROFILE=1'
GATE_WAIT_DEFINE = '-DSNAP_GATE_WAIT=1'
# docs/FINE-HW-WALK-20261019.md: compat/fine-lock.h SNAP_FINE_HW, converted MAP_PAGE and UNMAP_REGION on
# hardware-walk roots; needs --fine-locks and --user-hw-walk.
FINE_HW_DEFINE = '-DSNAP_FINE_HW=1'
# compat/fine-lock.h SNAP_FAST_PAUTH_CALLS: SIGN/AUTH_USER_POINTER and BATCH_SIGN_USER_POINTER as converted calls
# (this CPU's signing state only, the gate shared, no stripe) instead of under the service token (v89: 1.66 M
# SIGN calls waited 14.7 s for the token); needs --fine-locks. Appended last: earlier flag orders are unchanged.
FAST_PAUTH_CALLS_DEFINE = '-DSNAP_FAST_PAUTH_CALLS=1'
# docs/GUEST-NETWORK-20261019.md: compat/guest-net-link.h SNAP_GUEST_NET, the guest's paravirtual NIC on the host
# poll (ADRENO_GUEST_NET, q1n1 POLL2); with the launch word net (SNAP_EARLY_STOP) and the net AuxKC below.
GUEST_NET_DEFINE = '-DSNAP_GUEST_NET=1'
# docs/SPEED-BATCH4-20261020.md: SNAP_NATIVE_PACGA, the kernel's PACGA without a trap (the guest GA key in APGAKey
# and HCR_EL2.API for EL1; kernel address keys stay off), and SNAP_FAST_TIMER_IRQ_CPU0, vCPU 0's tick without the
# token while none of its own duties is due (needs --fast-timer-irq). Appended after every earlier flag.
NATIVE_PACGA_DEFINE = '-DSNAP_NATIVE_PACGA=1'
FAST_TIMER_IRQ_CPU0_DEFINE = '-DSNAP_FAST_TIMER_IRQ_CPU0=1'
# Batch 5 (compat/token-trim.h): SNAP_TOKEN_TRIM, the kernel refresh's reuse proof from the table memo's write
# witness and the Adreno DART's MAP/UNMAP as converted calls; needs --variant fast with the installed defaults'
# SNAP_KERNEL_VIEW_REUSE and SNAP_USER_TREE_MEMO, --fine-locks, --fast-pauth-calls, --root-cache-wide and
# --service-subclass. Appended after every earlier flag.
TOKEN_TRIM_DEFINE = '-DSNAP_TOKEN_TRIM=1'
# probes/xnuboot.c SNAP_PAUTH_EL0_LIVE: the EL0 reads --fast-el0-sysreg answers lock-free (SPRR_UPERM, the ordered
# counter alias; installed-fresh-20261020-v3: 112.0M at 938 ns) return to EL0 with the process's PAC bank still
# installed, without the bank leave and enter (compat/pauth-switch.S pauth_switch_live). Needs --variant fast and
# --fast-el0-sysreg. Appended after every earlier flag.
PAUTH_EL0_LIVE_DEFINE = '-DSNAP_PAUTH_EL0_LIVE=1'
# compat/lean-trap.h SNAP_LEAN_TRAPS: fewer and cheaper lock-free sysreg exits. 1: run-ahead over adjacent MRS the
# lock-free path answers (one trap per FAR/ESR or PMC0/PMC1 pair), a division-free counter conversion; 2: also
# FEAT_FGT in place of HCR_EL2.TVM/TRVM, so ESR_EL1/FAR_EL1 reads and CONTEXTIDR_EL1 writes stop trapping (needs
# SCR_EL3.FGTEn, unrecorded). Needs the fast set and --root-cache-wide. Appended after every earlier flag.
LEAN_TRAPS_DEFINE = '-DSNAP_LEAN_TRAPS={}'
# shadow/sprr-view.h SNAP_CLASS_PROOF_WIDE: the kernel class proof's walk limit is the arena's page count instead of
# 8,192 tables (installed-fresh-20261020-v3 stopped when the kernel shadow reached 8,192). Any variant.
CLASS_PROOF_WIDE_DEFINE = '-DSNAP_CLASS_PROOF_WIDE=1'
# Batch 6 (compat/el0-memo.h) SNAP_EL0_MEMO: the lock-free EL0 read answers its instruction fetch from a per-vCPU memo
# of the PC page's translation, kept from an agreeing double read and re-read on every hit (every descriptor of both
# walks, the code frame's type, the word), without the root-cache scan or the repeat read (installed-fresh-20261020-v3:
# 112.0M reads at 938 ns); the user return derives its PAC bank through a 128-entry second level behind the 8-entry
# memo (v3: 1.22M derivations at ~15 us). Needs --pauth-el0-live and --root-cache-wide. Appended after every earlier
# flag.
EL0_MEMO_DEFINE = '-DSNAP_EL0_MEMO=1'
# compat/nest-fast.h 3 SNAP_UNNEST_4K_FAST (docs/UNNEST-4K-FAST-20261020.md): UNNEST_REGION on 4K user roots (the
# kernel-user-4k route) answers nested shared tables and their leaf counts from the SWITCH_ROOT memo, which batch 4's
# SNAP_NEST_FAST does for 16K roots only (v5: 923,675 UNNESTs, 122.9 s of token hold, every table walked). Needs
# --nest-fast. Appended after every earlier flag.
UNNEST_4K_FAST_DEFINE = '-DSNAP_UNNEST_4K_FAST=1'
# Batch 6 (compat/service-lock.h): SNAP_PHASE_FAIR_GATE, a phase-fair L0 gate: a converted call that finds a token
# holder waits for that holder only (installed-fresh-20261020-v5: converted MAP_PAGE waited 524 s at the gate for 63 s
# of hold), and the holder drains only the converted calls that arrived before it. Needs --fine-locks, --fair-token
# (writers keep the token's FIFO) and --gate-wait (the bounded wait that withdraws). Appended after every earlier flag.
PHASE_FAIR_GATE_DEFINE = '-DSNAP_PHASE_FAIR_GATE=1'
# Batch 8 (compat/fine-lock.h): SNAP_FINE_SWITCH_HW, a SWITCH_ROOT to a hardware-walk root whose record already exists
# as a converted call under the old root's and the target's stripes (installed-fresh-20261020-v8: 15.6M such switches
# held the token 126.8 s and waited 243.6 s; SNAP_FINE_SWITCH converts cache hits only, and these roots have none).
# Needs --fine-switch and --user-hw-walk. Appended after every earlier flag.
FINE_SWITCH_HW_DEFINE = '-DSNAP_FINE_SWITCH_HW=1'
# Batch 9 (compat/dart-converted.h): SNAP_DART_CONVERTED, the Adreno DART's MAP/UNMAP as converted calls (gate shared,
# a device lock) instead of the service token: batch 5's --token-trim part B on its own, without its kernel-view
# witness (installed-fresh-20261020-v9: 3.85M DART maps held the token 49.3 s and waited 87.7 s). Needs --variant fast,
# --fine-locks and --fast-pauth-calls; refused with --token-trim (which converts the same calls). Appended after every
# earlier flag.
DART_CONVERTED_DEFINE = '-DSNAP_DART_CONVERTED=1'
# agents/sptm-locks (compat/fine-lock.h): SNAP_SPTM_FINE converts user UNMAP_DISJOINT and UPDATE_REGION and the
# hardware-walk update events; SNAP_SPTM_FINE_RETYPE adds private frame locks and the page-table frame lifecycle
# RETYPEs; SNAP_SPTM_FINE_KERNEL the kernel root's MAP_PAGE and UNMAP_REGION outside the kernel views' copy sources.
# Each needs the one before it; SNAP_SPTM_FINE needs --fine-locks, --fine-hw and --user-hw-walk. Appended after every
# earlier flag.
SPTM_FINE_DEFINE = '-DSNAP_SPTM_FINE=1'
SPTM_FINE_RETYPE_DEFINE = '-DSNAP_SPTM_FINE_RETYPE=1'
SPTM_FINE_KERNEL_DEFINE = '-DSNAP_SPTM_FINE_KERNEL=1'
# compat/audit-sparse.h SNAP_AUDIT_SPARSE: four sampled self-audits under the service token (the TPRO class-proof hit
# verification, ~7 ms each; the kernel refresh's reuse-hit verification; the exact kernel-variant swap and PPERM-reuse
# skip verifications) run 1 in 1024 events instead of 1 in 64 (installed-fresh-neon-20261021-v2: about 58 s of the
# 264.6 s token hold, and the multi-ms holds behind its 15,035 gate timeouts). A weaker audit accepted for speed.
# Needs --variant fast and --kernel-pperm-fast (with the installed defaults' view reuse, PPERM reuse and variants).
# Appended after every earlier flag.
AUDIT_SPARSE_DEFINE = '-DSNAP_AUDIT_SPARSE=1'
# shadow/sprr-reuse.h SNAP_REUSE_MATCH_MAPPED: the kernel refresh's exact reuse proof reads each copied source table
# through class_table_read and compares it in one pass instead of one kernel_table_read callback per word (neon-v2:
# 797,113 reuse hits, 41.1 s of nested token hold with the 1-in-64 verification; host 71 -> 13 us per proof). Same
# verdicts. Needs SNAP_KERNEL_VIEW_REUSE and SNAP_CLASS_PROOF_MAPPED (the installed defaults); not with --token-trim.
# Appended after every earlier flag.
REUSE_MATCH_MAPPED_DEFINE = '-DSNAP_REUSE_MATCH_MAPPED=1'
# probes/xnuboot.c SNAP_EL0_WFE: SCTLR_EL1.nTWE in the installed SCTLR_EL1, as 26A428's SPTM installs it. Without it
# every EL0 WFE traps to EL1 and XNU's handle_wf_trap raises SIGILL: launchd died there (libdispatch
# _dispatch_wait_for_enqueuer+12) in installed-fresh-20261017-v41, -20261019-v88, -20261020-v16 and
# -desktop-20261021-v1. Any variant. Appended after every earlier flag.
EL0_WFE_DEFINE = '-DSNAP_EL0_WFE=1'
# probes/xnuboot.c SNAP_EL0_CACHE_MAINT: SCTLR_EL1.UCI, the other EL0 permission SPTM grants: EL0 DC CVAU/CVAC/CIVAC
# and IC IVAU (sys_icache_invalidate) no longer trap to EL1, where handle_msr_trap raises SIGILL. Any variant.
# Appended after every earlier flag.
EL0_CACHE_MAINT_DEFINE = '-DSNAP_EL0_CACHE_MAINT=1'
# compat/user-crash.h SNAP_USER_CRASH_WIDE: the crash ring also records the EL0 classes XNU turns into SIGILL, SIGFPE
# or a panic that it missed (0x01 WFI/WFE among them: launchd's SIGILL left no row), each exception row with its
# instruction word. Needs SNAP_USER_CRASH_RING (the base recipe's). Appended after every earlier flag.
USER_CRASH_WIDE_DEFINE = '-DSNAP_USER_CRASH_WIDE=1'
# compat/pmu-rtc.h SNAP_SPMI_SCRATCH: the PMU scratch-pad registers the guest's AppleDialogSPMIPMU polls are served from
# 384 bytes of RAM instead of refused (b11v1: 22-27% of console bytes were its retries and parity errors). Any variant.
# Appended after every earlier flag.
SPMI_SCRATCH_DEFINE = '-DSNAP_SPMI_SCRATCH=1'
# compat/adreno-ib.h SNAP_ADRENO_IB_ASYNC: ADRENO_GUEST_IB_START (0x00010008) and ADRENO_GUEST_IB_POLL (0x00010009). The
# synchronous ADRENO_GUEST_IB busy-polls the GPU at EL2 inside the global service token, so every other vCPU's locked
# traps wait for the GPU (v14: GPU calls held the token 613 s of 1,800 s; 68-190 ms single holds). START rings the IB and
# spins at most 20 us; POLL is one completion pass; the guest waits between them. The flight lives in monitor arena
# pages (root_ext, so --root-cache-wide) and is reported behind IBASYNC1 (tools/perf_counters.py decode_ib_async).
# Without an ADRENO_IB_ASYNC kext nothing calls them. Failure modes: agents/async-ib/FAILURE-MODES.md. Any variant.
# Appended after every earlier flag.
IB_ASYNC_DEFINE = '-DSNAP_ADRENO_IB_ASYNC=1'
# agents/gpu-x290 (FAILURE-MODES.md F1-F10; turnip-audit FINDINGS item 3). The ring is in CP_THREAD_CONTROL(BOTH) after
# every IB, so FILL32 and the fences (compat/adreno-fill.c, compat/adreno.c fence_at) run their CP_MEM_WRITEs on both CP
# threads, and FILL32 counts as done when its marker lands, without RBBM idle. SNAP_ADRENO_FILL_BR (--adreno-fill-br):
# CP_THREAD_CONTROL(BR | SYNC_THREADS), the fill, CP_WAIT_MEM_WRITES, the marker, CP_THREAD_CONTROL(BOTH); completion
# also needs RBBM idle. SNAP_ADRENO_FENCE_BR (--adreno-fence-br): the same for the fences, with CP_WAIT_FOR_IDLE before
# the write. Any variant. Appended after every earlier flag; off, the monitor is byte-identical.
ADRENO_FILL_BR_DEFINE = '-DSNAP_ADRENO_FILL_BR=1'
ADRENO_FENCE_BR_DEFINE = '-DSNAP_ADRENO_FENCE_BR=1'
# agents/nvme-async (FAILURE-MODES.md). b12v5: the virtual ANS2's doorbell submitted to the SSD and busy-polled it under
# the service token (207k commands, 36.8 s of hold, mean 177.5 us), and every other vCPU's trap waited behind it.
# SNAP_ANS_ASYNC (--nvme-async): the doorbell queues the command and the SSD command is reaped without waiting, at later
# ANS accesses and at the owning vCPU's EL2 deadline (compat/ans.h); needs --root-cache-wide. SNAP_ANS_CONVERTED
# (--nvme-converted): the I/O doorbells and table-6 MAP/UNMAP_PAGES as converted calls under an ANS device lock
# (fine profile bit 4); needs --nvme-async, --variant fast and --fine-locks. SNAP_ATTR_CLEAN_DEFER (--attr-clean-defer):
# the second cache clean of an attribute change after the token is released (UPDATE_DISJOINT_MULTIPAGE held the token up
# to 82.5 ms); needs --root-cache-wide. Appended after every earlier flag.
ANS_ASYNC_DEFINE = '-DSNAP_ANS_ASYNC=1'
ANS_CONVERTED_DEFINE = '-DSNAP_ANS_CONVERTED=1'
ATTR_CLEAN_DEFER_DEFINE = '-DSNAP_ATTR_CLEAN_DEFER=1'
# agents/sptm-locks/multipage (FAILURE-MODES.md P1-P18): SNAP_MULTIPAGE_FAST (--multipage-fast). b12v16:
# UPDATE_DISJOINT_MULTIPAGE held the token 7.79 ms per call (14,880 calls, 116 s; max 118.5 ms), 82 % of it full
# kernel-view re-syncs (a changed aperture leaf in a copied table) and 12 % reuse proofs, one per frame. The aperture
# steps' first cleans run before the token is taken, a same-class AttrIndx change skips both cleans, a changed word of
# a copied table is patched into the copies and the reuse snapshot, and a run of headers is published, refreshed and
# invalidated once. Needs --attr-clean-defer (the second clean off the token) and --root-cache-wide; refused with
# --token-trim. Appended after every earlier flag.
MULTIPAGE_FAST_DEFINE = '-DSNAP_MULTIPAGE_FAST=1'
# SNAP_ADRENO_COHERENT (--gpu-coherent). b12v19/b12v20 spend about
# 16.7 ms of CPU cache maintenance per IB (214 MiB with cache-sparse). At prepare the monitor proves, with CP_MEMCPY
# jobs of at most 1 MiB in both directions (and non-coherent controls first), that GPU accesses through AttrIndx 1 /
# SH IS leaves snoop the CPU's caches; only then are CB0's mirror leaves IO-coherent, and ADRENO_GUEST_COHERENT tells
# the kext so. Its state is in root_ext (--root-cache-wide); its scratch is the q1n1 arena's Stage 4 pool region,
# free only with --gpu-va-wide. The guest boot-arg adreno_coherent=1 opts the kext in. Appended after every earlier
# flag.
GPU_COHERENT_DEFINE = '-DSNAP_ADRENO_COHERENT=1'
# --gpu-coherent-force: debug only, an A/B rendering check of skipped maintenance before the self-test can prove
# anything. The monitor reports ON with ADRENO_COHERENT_DETAIL_FORCED and runs no job; the kext accepts that only with
# its own boot-arg adreno_coherent_force=1. Appended after GPU_COHERENT_DEFINE.
GPU_COHERENT_FORCE_DEFINE = '-DSNAP_ADRENO_COHERENT_FORCE=1'
# --edp-backlight (agents/brightness): compat/edp-backlight.h SNAP_EDP_BACKLIGHT, ADRENO_GUEST_BACKLIGHT QUERY/SET of the
# panel's DPCD backlight level over the eDP controller's AUX channel. Only the AUX transport registers and DPCD
# 0x721-0x723 are written, the level is clamped to [10% of full scale, the panel's maximum], and any failure leaves the
# brightness as it was. The kext half is INPUT_AUXKC_DESKTOP_BACKLIGHT and the guest boot-arg adreno_backlight=1 the
# flag adds. Monitor define only (no loader change). Appended after every earlier flag; off, the monitor is
# byte-identical.
EDP_BACKLIGHT_DEFINE = '-DSNAP_EDP_BACKLIGHT=1'
# agents/dvfs (FAILURE-MODES.md): opt-in governors on vCPU 0's locked tick, state at the end of root_ext (so
# --root-cache-wide), report DVFSREP1 after the thermal record, restored at the stop; both need --thermal-guard (they
# never write without the guard's readings). compat/cpu-dvfs.h SNAP_CPU_DVFS: SCMI PERF LEVEL_SET fastchannels of the
# PDP domains that host vCPUs, from the EL2 tick's WFI classification (needs --wfi-resume), floor = firmware's boot
# level, ceiling = the sustained (highest non-boost) level or --cpu-dvfs-ceiling. compat/gpu-dvfs.h SNAP_GPU_DVFS: HFI
# GX_BW_PERF_VOTE between OPP 1 (310 MHz) and OPP 6 (915 MHz, the approved mid OPP) from the monitor's own IB
# times (race to idle, votes also while an async IB is in flight unless --gpu-dvfs-defer); the hfi step lists six
# OPPs and still starts at OPP 1. Appended after every earlier flag.
# agents/gpu-recovery (FAILURE-MODES.md R1-R30). b12v13: one IB past the 1 s budget latched the GPU off for the boot
# (127k failed submissions). compat/adreno-ib.h SNAP_ADRENO_RECOVER (--gpu-recover soft|reset, needs --ib-async): 1 the
# budget becomes soft up to ADRENO_IB_HARD_SECONDS (--ib-hard-seconds, default 3) and ADRENO_GUEST_RECOVER QUERY/SETTLE
# un-latch an ADRENO_GPU_RECOVER kext once the GPU is proven idle; 2 also the CP soft reset at EL2 (q1n1's hw_init
# replayed from compat/adreno-recover-table.h, which prepare checks against the cp-fence steps and q1n1's sources).
# Appended after every earlier flag; off, the monitor is byte-identical.
GPU_RECOVER_DEFINE = '-DSNAP_ADRENO_RECOVER={}'
IB_HARD_SECONDS_DEFINE = '-DADRENO_IB_HARD_SECONDS={}u'
GPU_RECOVER_LEVELS = {'soft': 1, 'reset': 2}
RECOVER_TABLE = 'compat/adreno-recover-table.h'
CPU_DVFS_DEFINE = '-DSNAP_CPU_DVFS=1'
CPU_DVFS_CEILING_DEFINE = '-DSNAP_CPU_DVFS_CEILING={}'
GPU_DVFS_DEFINE = '-DSNAP_GPU_DVFS=1'
GPU_DVFS_DEFER_DEFINE = '-DSNAP_GPU_DVFS_MIDFLIGHT=0'
GPU_DVFS_HOLD_DEFINE = '-DSNAP_GPU_DVFS_HOLD_MS={}'
def selected(args):
"""(profile, AuxKC, its pinned SHA-256, its bundles): the default route, or --input's; with --qcc2072-wifi
the same route's input collection relinked with SnapintoshQCC2072 (tools/build-qcc2072-native.py)."""
profile, aux, aux_sha, bundles = route_selected(args)
if not getattr(args, 'qcc2072_wifi', False):
need(not getattr(args, 'qcc2072_auxkc', None) and not getattr(args, 'qcc2072_auxkc_sha256', None),
'--qcc2072-auxkc/--qcc2072-auxkc-sha256 need --qcc2072-wifi')
return profile, aux, aux_sha, bundles
need(getattr(args, 'input', False), '--qcc2072-wifi needs --input (its collection is the input route\'s, relinked)')
folder, want = getattr(args, 'qcc2072_auxkc', None), getattr(args, 'qcc2072_auxkc_sha256', None)
need(folder and want, '--qcc2072-wifi needs --qcc2072-auxkc DIR (tools/build-qcc2072-native.py --output DIR '
f'--baseline {Path(aux).parent} --expected-baseline-sha256 {aux_sha}) and '
'--qcc2072-auxkc-sha256 (its native-wifi.kc)')
folder = Path(folder).resolve()
receipt = json.loads((folder / 'build.json').read_text())
want_bundles = sorted([*bundles, QCC2072_BUNDLE])
need(receipt.get('status') == QCC2072_AUXKC_STATUS, f'{folder}: not a completed native Wi-Fi link')
need(receipt.get('baseline_collection_sha256') == aux_sha,
f'{folder}: linked over {receipt.get("baseline_collection_sha256")}, not this route\'s {aux_sha} ({aux})')
need(receipt.get('bundle_identifiers') == want_bundles, f'{folder}: bundles differ from {want_bundles}')
kc = folder / 'native-wifi.kc'
need((receipt.get('outputs') or {}).get('native-wifi.kc') == want and st.file_sha(kc) == want,
f'{kc} is not the --qcc2072-auxkc-sha256 collection its receipt records')
return profile, str(kc), want, want_bundles
def route_selected(args):
"""(profile, AuxKC, its pinned SHA-256, its bundles): the default route, or --input's."""
touchpad = getattr(args, 'input_touchpad', False)
need(not touchpad or getattr(args, 'input', False), '--input-touchpad needs --input')
damage = getattr(args, 'present_damage', False)
cached = getattr(args, 'present_cached_read', False)
gpumap = getattr(args, 'present_gpumap', False)
vsync60 = getattr(args, 'present_vsync60', False)
dartpool = getattr(args, 'present_dartpool', False)
ptp = getattr(args, 'input_ptp', False)
need(not ptp or (getattr(args, 'input', False) and touchpad),
'--input-ptp needs --input --input-touchpad (it drives the touchpad node)')
need(not (ptp and (damage or cached or gpumap or vsync60)),
'--input-ptp already includes --present-vsync60\'s AdrenoX2 (and so --present-gpumap\'s), or with '
'--present-dartpool that one\'s: give one of them')
need(not damage or getattr(args, 'input', False), '--present-damage needs --input (its AdrenoX2 is in the input AuxKC)')
need(not cached or getattr(args, 'input', False),
'--present-cached-read needs --input (its AdrenoX2 is in the input AuxKC)')
need(not gpumap or getattr(args, 'input', False), '--present-gpumap needs --input (its AdrenoX2 is in the input AuxKC)')
need(not vsync60 or getattr(args, 'input', False), '--present-vsync60 needs --input (its AdrenoX2 is in the input AuxKC)')
need(not dartpool or getattr(args, 'input', False),
'--present-dartpool needs --input (its AdrenoX2 is in the input AuxKC)')
need(not (dartpool and (damage or cached or gpumap or vsync60)),
'--present-dartpool already includes --present-vsync60\'s (and so --present-gpumap\'s) kext: give one of them')
resident = getattr(args, 'present_resident', False)
need(not resident or (dartpool and ptp),
'--present-resident needs --present-dartpool --input-ptp (it is that collection with residency and the perf '
'gpu line)')
beam = getattr(args, 'present_beam', False)
need(not beam or resident,
'--present-beam needs --present-resident --present-dartpool --input-ptp (its AdrenoX2 extends that collection)')
va_reclaim = getattr(args, 'present_va_reclaim', False)
need(not va_reclaim or beam,
'--present-va-reclaim needs --present-beam --present-resident --present-dartpool --input-ptp (its AdrenoX2 '
'extends that collection)')
va_wide = getattr(args, 'gpu_va_wide', False)
need(not va_wide or va_reclaim,
'--gpu-va-wide needs --present-va-reclaim --present-beam --present-resident --present-dartpool --input-ptp (its '
'AdrenoX2 is that collection\'s with ADRENO_VA_WIDE)')
latch = getattr(args, 'present_latch', False)
need(not latch or va_wide,
'--present-latch needs --gpu-va-wide and its chain (its AdrenoX2 is that collection\'s with ADRENO_LATCH, its '
'SnapI2CHID stage B with SNAP_I2C_HID_LATENCY)')
edp_backlight = getattr(args, 'edp_backlight', False)
need(not edp_backlight or latch,
'--edp-backlight needs --present-latch and its chain (its AdrenoX2 is that collection\'s with ADRENO_BACKLIGHT)')
need(not (vsync60 and (damage or cached or gpumap)),
'--present-vsync60 already includes --present-gpumap\'s (and so --present-cached-read\'s) kext: give one of them')
need(not (damage and cached), '--present-cached-read already includes --present-damage\'s present: give one of them')
need(not (gpumap and (damage or cached)),
'--present-gpumap already includes --present-cached-read\'s (and --present-damage\'s) present: give one of them')
if getattr(args, 'input', False):
if getattr(args, 'guest_net', False):
need(not damage and not cached and not gpumap and not vsync60 and not dartpool and not ptp,
'--present-damage/--present-cached-read/--present-gpumap/--present-vsync60/--present-dartpool/'
'--input-ptp and --guest-net each select their own input AuxKC')
need(INPUT_AUXKC_NET is not None, '--guest-net: no net AuxKC is pinned yet (INPUT_AUXKC_NET)')
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_NET, INPUT_AUXKC_NET_SHA,
INPUT_BUNDLES)
if damage:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_DAMAGE,
INPUT_AUXKC_DAMAGE_SHA, INPUT_BUNDLES)
if cached:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_CACHED_READ,
INPUT_AUXKC_CACHED_READ_SHA, INPUT_BUNDLES)
if gpumap:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_GPUMAP,
INPUT_AUXKC_GPUMAP_SHA, INPUT_BUNDLES)
if vsync60:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_VSYNC60,
INPUT_AUXKC_VSYNC60_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch and edp_backlight:
need(INPUT_AUXKC_DESKTOP_BACKLIGHT is not None and INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA is not None,
'--edp-backlight: its AuxKC is not pinned yet (INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA; '
'KEXT_PROFILE=desktop-backlight tools/build-auxkc-desktop.sh NAME links it)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_DESKTOP_BACKLIGHT, INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA,
INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH is not None and
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA is not None,
'--present-latch: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA; '
'agents/input-latency/LATENCY-BUDGET.md section 7 links it)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH,
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim and va_wide:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE is not None and
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_SHA is not None,
'--gpu-va-wide: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_SHA; '
'agents/gpu-va-wide/HARDWARE-STEPS.md links it)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE,
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA is not None and
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_SHA is not None,
'--present-va-reclaim: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_SHA)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA,
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident and beam:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA is not None,
'--present-beam: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM,
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_SHA is not None,
'--present-resident: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_SHA)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT, INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_SHA,
INPUT_BUNDLES)
if dartpool and ptp:
return INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP_DARTPOOL, INPUT_AUXKC_PTP_DARTPOOL_SHA, INPUT_BUNDLES
if dartpool:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_DARTPOOL,
INPUT_AUXKC_DARTPOOL_SHA, INPUT_BUNDLES)
if ptp:
need(INPUT_AUXKC_PTP_SHA is not None, '--input-ptp: its AuxKC is not pinned yet (INPUT_AUXKC_PTP_SHA)')
return INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_PTP, INPUT_AUXKC_PTP_SHA, INPUT_BUNDLES
return (INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC, INPUT_AUXKC_SHA, INPUT_BUNDLES
return PROFILE, AUXKC, AUXKC_SHA, [KEXT_BUNDLE]
def variant_flags(sets, args, variant=None):
variant = variant or args.variant
flags = list(sets[variant])
if getattr(args, 'wfi_resume', False):
flags.append(WFI_RESUME_DEFINE)
if getattr(args, 'fast_timer_irq', False):
need(variant == 'fast' and '-DSNAP_FAST_VCPU_TRAPS=1' in flags and '-DSNAP_FAST_TIMER_WRITE=1' in flags and
getattr(args, 'root_cache_wide', False) and getattr(args, 'service_subclass', False),
'--fast-timer-irq needs --variant fast (SNAP_FAST_VCPU_TRAPS, SNAP_FAST_TIMER_WRITE), --root-cache-wide '
'and --service-subclass')
flags.append(FAST_TIMER_IRQ_DEFINE)
if getattr(args, 'dart_memo', False):
need(getattr(args, 'root_cache_wide', False), '--dart-memo needs --root-cache-wide')
flags.append(DART_MEMO_DEFINE)
if getattr(args, 'aes_bitslice', False):
flags.append(AES_BITSLICE_DEFINE)
if getattr(args, 'fast_ipi', False):
need(variant == 'fast' and '-DSNAP_FAST_VCPU_TRAPS=1' in flags and getattr(args, 'root_cache_wide', False),
'--fast-ipi needs --variant fast (SNAP_FAST_VCPU_TRAPS) and --root-cache-wide')
flags.append(FAST_IPI_DEFINE)
if getattr(args, 'fast_el0_sysreg', False):
need(variant == 'fast', '--fast-el0-sysreg needs --variant fast')
flags.append(FAST_EL0_SYSREG_DEFINE)
if getattr(args, 'service_subclass', False):
flags.append(SERVICE_SUBCLASS_DEFINE)
if getattr(args, 'dart_wiredcnt', False):
flags.append(DART_WIREDCNT_DEFINE)
if getattr(args, 'panic_serve', False):
flags.append(PANIC_SERVE_DEFINE)
if getattr(args, 'root_miss_fast', False):
flags.append(ROOT_MISS_FAST_DEFINE)
if getattr(args, 'update_disjoint_fast', False):
flags.append(UPDATE_DISJOINT_FAST_DEFINE)
if getattr(args, 'root_cache_wide', False):
flags.append(ROOT_CACHE_WIDE_DEFINE)
if getattr(args, 'update_check_sparse', False):
flags.append(UPDATE_CHECK_SPARSE_DEFINE)
if getattr(args, 'input', False):
flags.append(INPUT_DEFINE)
if getattr(args, 'kernel_pperm_fast', False):
flags.append(KERNEL_PPERM_FAST_DEFINE)
if getattr(args, 'ib_budget_1s', False):
flags.append(IB_BUDGET_1S_DEFINE)
if getattr(args, 'unmap_mixed', False):
flags.append(UNMAP_MIXED_DEFINE)
if getattr(args, 'prime_secondaries', False):
flags.append(BOOT_RELOCATE_DEFINE)
if getattr(args, 'txm_aspace_wide', False):
flags.append(TXM_ASPACE_WIDE_DEFINE)
if getattr(args, 'fine_locks', False) or getattr(args, 'fine_switch', False):
need(variant == 'fast' and getattr(args, 'root_cache_wide', False) and
getattr(args, 'update_disjoint_fast', False) and getattr(args, 'service_subclass', False),
'--fine-locks/--fine-switch need --variant fast, --root-cache-wide, --update-disjoint-fast and '
'--service-subclass')
flags.append(FINE_LOCKS_DEFINE)
if getattr(args, 'fine_switch', False):
flags.append(FINE_SWITCH_DEFINE)
if getattr(args, 'fair_token', False):
flags.append(FAIR_TOKEN_DEFINE)
if getattr(args, 'user_hw_walk', False):
flags.append(USER_HW_WALK_DEFINE)
if getattr(args, 'fine_hw', False):
need(getattr(args, 'fine_locks', False) and getattr(args, 'user_hw_walk', False),
'--fine-hw needs --fine-locks and --user-hw-walk')
flags.append(FINE_HW_DEFINE)
if getattr(args, 'idreg_mmfr3', False):
flags.append(IDREG_MMFR3_DEFINE)
if getattr(args, 's2_fault_retry', False):
flags.append(S2_FAULT_RETRY_DEFINE)
if getattr(args, 's2_fault_retry_stage1', False):
need(getattr(args, 's2_fault_retry', False), '--s2-fault-retry-stage1 needs --s2-fault-retry')
flags.append(S2_FAULT_RETRY_STAGE1_DEFINE)
costs = [(name, define) for name, define in (('nest_fast', NEST_FAST_DEFINE), ('detach_fast', DETACH_FAST_DEFINE),
('root_index', ROOT_INDEX_DEFINE),
('other_profile', OTHER_PROFILE_DEFINE),
('gate_wait', GATE_WAIT_DEFINE))
if getattr(args, name, False)]
if costs:
need(variant == 'fast' and getattr(args, 'root_cache_wide', False) and getattr(args, 'service_subclass', False),
'--nest-fast/--detach-fast/--root-index/--other-profile/--gate-wait need --variant fast, '
'--root-cache-wide and --service-subclass')
need(not getattr(args, 'gate_wait', False) or getattr(args, 'fine_locks', False),
'--gate-wait needs --fine-locks')
flags.extend(define for _, define in costs)
if getattr(args, 'fast_pauth_calls', False):
need(getattr(args, 'fine_locks', False), '--fast-pauth-calls needs --fine-locks')
flags.append(FAST_PAUTH_CALLS_DEFINE)
if getattr(args, 'guest_net', False):
# Appended last, so every earlier flag order is unchanged.
need(getattr(args, 'input', False), '--guest-net needs --input (its NIC is in the input AuxKC)')
flags.append(GUEST_NET_DEFINE)
if getattr(args, 'fast_timer_irq_cpu0', False):
need(getattr(args, 'fast_timer_irq', False), '--fast-timer-irq-cpu0 needs --fast-timer-irq')
flags.append(FAST_TIMER_IRQ_CPU0_DEFINE)
if getattr(args, 'native_pacga', False):
flags.append(NATIVE_PACGA_DEFINE)
if getattr(args, 'token_trim', False):
need(variant == 'fast' and '-DSNAP_KERNEL_VIEW_REUSE=1' in flags and '-DSNAP_USER_TREE_MEMO=1' in flags and
getattr(args, 'fine_locks', False) and getattr(args, 'fast_pauth_calls', False) and
getattr(args, 'root_cache_wide', False) and getattr(args, 'service_subclass', False),
'--token-trim needs --variant fast (SNAP_KERNEL_VIEW_REUSE, SNAP_USER_TREE_MEMO), --fine-locks, '
'--fast-pauth-calls, --root-cache-wide and --service-subclass')
flags.append(TOKEN_TRIM_DEFINE)
if getattr(args, 'pauth_el0_live', False):
need(variant == 'fast' and '-DSNAP_FAST_CROSSINGS=1' in flags and FAST_EL0_SYSREG_DEFINE in flags,
'--pauth-el0-live needs --variant fast (SNAP_FAST_CROSSINGS) and --fast-el0-sysreg')
flags.append(PAUTH_EL0_LIVE_DEFINE)
lean = getattr(args, 'lean_traps', None)
if lean:
need(lean in (1, 2), '--lean-traps is 1 (run-ahead, cheaper exits) or 2 (also FEAT_FGT)')
need(variant == 'fast' and '-DSNAP_FAST_VCPU_TRAPS=1' in flags and getattr(args, 'root_cache_wide', False),
'--lean-traps needs --variant fast (SNAP_FAST_VCPU_TRAPS) and --root-cache-wide')
flags.append(LEAN_TRAPS_DEFINE.format(lean))
if getattr(args, 'class_proof_wide', False):
flags.append(CLASS_PROOF_WIDE_DEFINE)
if getattr(args, 'el0_memo', False):
need(PAUTH_EL0_LIVE_DEFINE in flags and getattr(args, 'root_cache_wide', False),
'--el0-memo needs --pauth-el0-live and --root-cache-wide')
flags.append(EL0_MEMO_DEFINE)
if getattr(args, 'unnest_4k_fast', False):
need(NEST_FAST_DEFINE in flags and '-DSNAP_ROOT_VALIDATE_MEMO=1' in flags,
'--unnest-4k-fast needs --nest-fast (and its SWITCH_ROOT memo, SNAP_ROOT_VALIDATE_MEMO)')
flags.append(UNNEST_4K_FAST_DEFINE)
if getattr(args, 'phase_fair_gate', False):
need(getattr(args, 'fine_locks', False) and getattr(args, 'fair_token', False) and
getattr(args, 'gate_wait', False),
'--phase-fair-gate needs --fine-locks, --fair-token and --gate-wait')
flags.append(PHASE_FAIR_GATE_DEFINE)
if getattr(args, 'fine_switch_hw', False):
need(getattr(args, 'fine_switch', False) and getattr(args, 'user_hw_walk', False),
'--fine-switch-hw needs --fine-switch and --user-hw-walk')
flags.append(FINE_SWITCH_HW_DEFINE)
if getattr(args, 'dart_converted', False):
need(variant == 'fast' and getattr(args, 'fine_locks', False) and getattr(args, 'fast_pauth_calls', False),
'--dart-converted needs --variant fast, --fine-locks and --fast-pauth-calls')
need(not getattr(args, 'token_trim', False),
'--dart-converted and --token-trim convert the same Adreno DART calls (--token-trim\'s part B): give one')
flags.append(DART_CONVERTED_DEFINE)
if getattr(args, 'audit_sparse', False):
need(variant == 'fast' and KERNEL_PPERM_FAST_DEFINE in flags and '-DSNAP_KERNEL_VIEW_REUSE=1' in flags and
'-DSNAP_KERNEL_PPERM_REUSE=1' in flags and '-DSNAP_KERNEL_PPERM_VARIANTS=1' in flags,
'--audit-sparse needs --variant fast and --kernel-pperm-fast (SNAP_KERNEL_VIEW_REUSE, '
'SNAP_KERNEL_PPERM_REUSE, SNAP_KERNEL_PPERM_VARIANTS)')
flags.append(AUDIT_SPARSE_DEFINE)
if getattr(args, 'reuse_match_mapped', False):
need('-DSNAP_KERNEL_VIEW_REUSE=1' in flags and '-DSNAP_CLASS_PROOF_MAPPED=1' in flags,
'--reuse-match-mapped needs SNAP_KERNEL_VIEW_REUSE and SNAP_CLASS_PROOF_MAPPED')
need(not getattr(args, 'token_trim', False),
'--reuse-match-mapped and --token-trim answer the same kernel reuse proof: give one')
flags.append(REUSE_MATCH_MAPPED_DEFINE)
if getattr(args, 'qcc2072_wifi', False):
# Appended after every earlier flag; the loader half is SNAP_QCC2072_WIFI (environment()).
need(getattr(args, 'input', False), '--qcc2072-wifi needs --input')
flags.append(QCC2072_WIFI_DEFINE)
if getattr(args, 'el0_wfe', False):
flags.append(EL0_WFE_DEFINE)
if getattr(args, 'el0_cache_maint', False):
flags.append(EL0_CACHE_MAINT_DEFINE)
if getattr(args, 'user_crash_wide', False):
need('-DSNAP_USER_CRASH_RING=1' in flags, '--user-crash-wide needs the crash ring (SNAP_USER_CRASH_RING)')
flags.append(USER_CRASH_WIDE_DEFINE)
if getattr(args, 'spmi_scratch', False):
flags.append(SPMI_SCRATCH_DEFINE)
if getattr(args, 'ib_async', False):
need(getattr(args, 'root_cache_wide', False),
'--ib-async needs --root-cache-wide (the flight lives in root_ext\'s monitor arena pages)')
flags.append(IB_ASYNC_DEFINE)
recover, hard = getattr(args, 'gpu_recover', None), getattr(args, 'ib_hard_seconds', None)
need(hard is None or recover, '--ib-hard-seconds needs --gpu-recover')
if recover:
need(getattr(args, 'ib_async', False), '--gpu-recover needs --ib-async (it ends asynchronous IB flights)')
flags.append(GPU_RECOVER_DEFINE.format(GPU_RECOVER_LEVELS[recover]))
if hard is not None:
need(1 <= hard <= 4, '--ib-hard-seconds is 1..4 (IOGPU waits 5 s for the GPU)')
flags.append(IB_HARD_SECONDS_DEFINE.format(hard))
ceiling = getattr(args, 'cpu_dvfs_ceiling', None)
need(ceiling is None or getattr(args, 'cpu_dvfs', False), '--cpu-dvfs-ceiling needs --cpu-dvfs')
if getattr(args, 'cpu_dvfs', False) or getattr(args, 'gpu_dvfs', False):
need(getattr(args, 'thermal_guard', False),
'--cpu-dvfs/--gpu-dvfs need --thermal-guard (the governors clamp on its readings and stay off without it)')
need(getattr(args, 'root_cache_wide', False), '--cpu-dvfs/--gpu-dvfs need --root-cache-wide (state in root_ext)')
if getattr(args, 'cpu_dvfs', False):
need(WFI_RESUME_DEFINE in flags, '--cpu-dvfs needs --wfi-resume (the EL2 tick\'s busy/idle classification)')
flags.append(CPU_DVFS_DEFINE)
if ceiling is not None:
need(1 <= ceiling <= 19, '--cpu-dvfs-ceiling is a level index 1..19 (each domain also stops at its own '
'sustained level: PDP0 20, PDP1/2 19; boost levels are never requested)')
flags.append(CPU_DVFS_CEILING_DEFINE.format(ceiling))
need(not getattr(args, 'gpu_dvfs_defer', False) or getattr(args, 'gpu_dvfs', False),
'--gpu-dvfs-defer needs --gpu-dvfs')
hold = getattr(args, 'gpu_dvfs_hold', None)
need(hold is None or getattr(args, 'gpu_dvfs', False), '--gpu-dvfs-hold needs --gpu-dvfs')
if getattr(args, 'gpu_dvfs', False):
need(not getattr(args, 'gpu_opp_mhz', None), '--gpu-dvfs and --gpu-opp-mhz both choose the GPU clock: give one')
flags.append(GPU_DVFS_DEFINE)
if getattr(args, 'gpu_dvfs_defer', False):
flags.append(GPU_DVFS_DEFER_DEFINE)
if hold is not None:
need(300 <= hold <= 5000, '--gpu-dvfs-hold is 300..5000 ms')
flags.append(GPU_DVFS_HOLD_DEFINE.format(hold))
if getattr(args, 'present_beam', False):
# Appended after every earlier flag; the loader half is SNAP_DISPLAY_TIMING (environment()).
flags.append(DISPLAY_TIMING_DEFINE)
need(not (getattr(args, 'sptm_fine_retype', False) or getattr(args, 'sptm_fine_kernel', False)) or
getattr(args, 'sptm_fine', False), '--sptm-fine-retype and --sptm-fine-kernel need --sptm-fine')
if getattr(args, 'sptm_fine', False):
need(variant == 'fast' and getattr(args, 'fine_locks', False) and getattr(args, 'fine_hw', False) and
getattr(args, 'user_hw_walk', False) and '-DSNAP_UPDATE_DISJOINT_FAST=1' in flags,
'--sptm-fine needs --variant fast, --fine-locks, --fine-hw, --user-hw-walk and --update-disjoint-fast')
flags.append(SPTM_FINE_DEFINE)
if getattr(args, 'sptm_fine_retype', False):
flags.append(SPTM_FINE_RETYPE_DEFINE)
if getattr(args, 'sptm_fine_kernel', False):
flags.append(SPTM_FINE_KERNEL_DEFINE)
if getattr(args, 'gpu_va_wide', False):
# Last; the loader half is SNAP_ADRENO_VA_WIDE (environment()), the q1n1 half gpu_steps' and boot()'s --va-wide.
need('-DSNAP_ROOT_CACHE_WIDE=1' in flags or '-DSNAP_DART_WIREDCNT=1' not in flags,
'--gpu-va-wide with SNAP_DART_WIREDCNT keeps its wide pin bitmap in root_ext: needs --root-cache-wide')
flags.append(GPU_VA_WIDE_DEFINE)
need(not getattr(args, 'nvme_converted', False) or getattr(args, 'nvme_async', False),
'--nvme-converted needs --nvme-async (a converted doorbell must never wait for the SSD)')
if getattr(args, 'nvme_async', False):
need(getattr(args, 'root_cache_wide', False), '--nvme-async needs --root-cache-wide (its schedule is in root_ext)')
flags.append(ANS_ASYNC_DEFINE)
if getattr(args, 'nvme_converted', False):
need(variant == 'fast' and getattr(args, 'fine_locks', False),
'--nvme-converted needs --variant fast and --fine-locks')
flags.append(ANS_CONVERTED_DEFINE)
if getattr(args, 'attr_clean_defer', False):
need(getattr(args, 'root_cache_wide', False),
'--attr-clean-defer needs --root-cache-wide (each CPU\'s deferred list is in root_ext)')
flags.append(ATTR_CLEAN_DEFER_DEFINE)
if getattr(args, 'adreno_fill_br', False):
flags.append(ADRENO_FILL_BR_DEFINE)
if getattr(args, 'adreno_fence_br', False):
flags.append(ADRENO_FENCE_BR_DEFINE)
if getattr(args, 'multipage_fast', False):
need(getattr(args, 'attr_clean_defer', False) and getattr(args, 'root_cache_wide', False) and
'-DSNAP_KERNEL_VIEW_REUSE=1' in flags and '-DSNAP_TOKEN_TRIM=1' not in flags,
'--multipage-fast needs --attr-clean-defer and --root-cache-wide (and the kernel view reuse snapshot), '
'not --token-trim')
flags.append(MULTIPAGE_FAST_DEFINE)
if getattr(args, 'gpu_coherent', False):
need(GPU_VA_WIDE_DEFINE in flags and '-DSNAP_ROOT_CACHE_WIDE=1' in flags,
'--gpu-coherent needs --gpu-va-wide (its self-test scratch is the arena\'s Stage 4 pool region) and '
'--root-cache-wide (its state is in root_ext)')
flags.append(GPU_COHERENT_DEFINE)
if getattr(args, 'gpu_coherent_force', False):
need(getattr(args, 'gpu_coherent', False), '--gpu-coherent-force needs --gpu-coherent: it forces that '
'self-test\'s outcome')
flags.append(GPU_COHERENT_FORCE_DEFINE)
if getattr(args, 'edp_backlight', False):
flags.append(EDP_BACKLIGHT_DEFINE)
return flags
def environment(args):
"""ENV, or with --console the console on the same poll; --kernel-sysreg-zero, --kernel-refmod-skip and
--kernel-user-4k add the opt-in kernel patches."""
env = dict(ENV, SNAP_EARLY_STOP='guest,console') if getattr(args, 'console', False) else dict(ENV)
if getattr(args, 'hide_amx', False):
env['SNAP_HIDE_AMX'] = '1'
if getattr(args, 'guest_net', False):
# The guest network rides on the host poll (tools/early_stop.py net: F_HOST | F_NET, 5 ms polls).
env['SNAP_EARLY_STOP'] = env['SNAP_EARLY_STOP'] + ',net'
if getattr(args, 'thermal_guard', False):
# docs/A16-THERMAL-GUARD-20261018.md: TSENS sampled every 250 ms on CPU0's timer path, a 60 C ceiling, per-sensor
# validity grace and a missed-poll bound stop the run (thermal implies the host poll). A launch parameter.
env['SNAP_EARLY_STOP'] = env['SNAP_EARLY_STOP'] + ',thermal'
if getattr(args, 'kernel_sysreg_zero', False):
env['SNAP_KERNEL_SYSREG_ZERO'] = '1'
if getattr(args, 'kernel_refmod_skip', False):
env['SNAP_KERNEL_REFMOD_SKIP'] = '1'
if getattr(args, 'kernel_user_4k', False):
env['SNAP_KERNEL_USER_4K'] = '1'
if getattr(args, 'prime_secondaries', False):
env['SNAP_SMP_PRIME'] = '1'
if getattr(args, 'txm_aspace_wide', False):
# probes/xnuboot.py configure_txm_arena: the loader half of TXM_ASPACE_WIDE_DEFINE (both or neither).
env['SNAP_TXM_ASPACE_WIDE'] = '1'
if getattr(args, 'qcc2072_wifi', False):
# probes/run.py: the loader half of QCC2072_WIFI_DEFINE (live admission, arena, node, appended params);
# boot() adds SNAP_QCC2072_INPUTS, the attempt's staged inputs.
env['SNAP_QCC2072_WIFI'] = '1'
if getattr(args, 'present_beam', False):
# probes/run.py: the loader half of DISPLAY_TIMING_DEFINE (params block, stage-2 row, GPU-node properties).
env['SNAP_DISPLAY_TIMING'] = '1'
if getattr(args, 'gpu_va_wide', False):
# probes/xnuboot.py configure_adreno_va_wide: the loader half of GPU_VA_WIDE_DEFINE (both or neither).
env['SNAP_ADRENO_VA_WIDE'] = '1'
if getattr(args, 'let_panic', False):
# probes/run.py SNAP_LET_PANIC: EL1 exceptions (XNU's debugger trap on panic) resume into XNU's own
# handler instead of stopping, so the panic string is printed (v29 and v35 stopped silently at
# DebuggerTrapWithState). A launch parameter, not a monitor define.
env['SNAP_LET_PANIC'] = '1'
delay = getattr(args, 'early_stop_delay', None)
if delay is not None:
# The kext's first-present / plug-in-failure stop fires this long after its request (default 600 s);
# it must leave the watchdog the last word.
need(60 <= delay < WATCHDOG, f'--early-stop-delay must be 60..{WATCHDOG - 1} s')
env['SNAP_EARLY_STOP_DELAY'] = env['SNAP_EARLY_STOP_MAX_DELAY'] = str(delay)
return env
# XNU zone corruption logging (osfmk/kern/zalloc.c: zlog1=<zone name> records every allocation and free of that zone
# with backtraces, zrecs=<n> records; a use-after-free panic then prints the culprit's stacks). Zone names are XNU's
# own (heap prefix, '.' for spaces), so only that alphabet is admitted.
ZONE_LOG_NAME = re.compile(r'[A-Za-z0-9_.]{1,64}')
def boot_addition(args):
"""What the boot appends to BASE_CMDLINE: TimeSync, software WKdm compression, the console's flags and any
zone-corruption logging."""
console, single = getattr(args, 'console', False), getattr(args, 'single_user', False)
need(console or not single, '--single-user needs --console: its shell is on the console')
zone, records = getattr(args, 'zone_log', None), getattr(args, 'zone_log_records', None)
need(records is None or zone is not None, '--zone-log-records needs --zone-log')
extra = []
if getattr(args, 'present_maint', False):
extra.append('adreno_present_maint=1')
if getattr(args, 'gpu_coherent', False):
extra.append('adreno_coherent=1') # the kext still skips only after the monitor's self-test said ON
if getattr(args, 'gpu_coherent_force', False):
extra.append('adreno_coherent_force=1') # and, debug only, after a forced ON (no self-test)
need(not getattr(args, 'edp_backlight_mode', False) or getattr(args, 'edp_backlight', False),
'--edp-backlight-mode needs --edp-backlight')
if getattr(args, 'edp_backlight', False):
extra.append('adreno_backlight=1') # the kext publishes its service only after the monitor's QUERY
if getattr(args, 'edp_backlight_mode', False):
extra.append('adreno_backlight_mode=1') # and may switch a panel left in PWM mode to DPCD control
resident_mib = getattr(args, 'resident_mib', None)
if resident_mib is not None:
need(0 <= resident_mib <= 4096, '--resident-mib must be 0..4096')
extra.append(f'adreno_resident_mib={resident_mib}')
# --present-latch's guest boot-args (AdrenoDisplay start_hardware, SnapI2CHIDBus::start); none without the flag.
latch_us, hold = getattr(args, 'present_latch_us', None), getattr(args, 'present_hold', None)
margin_us, active_us = getattr(args, 'present_hold_margin_us', None), getattr(args, 'input_active_us', None)
gate_us = getattr(args, 'present_gate_us', None)
pipeline, poll_us = getattr(args, 'present_pipeline', None), getattr(args, 'present_pipeline_poll_us', None)
need(getattr(args, 'present_latch', False) or (latch_us, hold, margin_us, active_us, gate_us) == (None,) * 5,
'--present-latch-us/--present-hold/--present-hold-margin-us/--present-gate-us/--input-active-us need '
'--present-latch')
need(getattr(args, 'present_latch', False) or (pipeline, poll_us) == (None, None),
'--present-pipeline/--present-pipeline-poll-us need --present-latch (its AuxKC carries the pipeline AdrenoX2)')
if pipeline is not None:
extra.append(f'adreno_pipeline={pipeline}')
if poll_us is not None:
need(50 <= poll_us <= 5000, '--present-pipeline-poll-us must be 50..5000')
extra.append(f'adreno_pipeline_poll_us={poll_us}')
if latch_us is not None:
need(0 <= latch_us <= 9000, '--present-latch-us must be 0..9000 (0: the beam kext\'s aim)')
extra.append(f'adreno_latch_us={latch_us}')
if hold is not None:
extra.append(f'adreno_hold={1 if hold == "on" else 0}')
if margin_us is not None:
need(0 <= margin_us <= 16000, '--present-hold-margin-us must be 0..16000')
extra.append(f'adreno_hold_margin_us={margin_us}')
if gate_us is not None:
need(0 <= gate_us <= 16000, '--present-gate-us must be 0..16000')
extra.append(f'adreno_gate_us={gate_us}')
if active_us is not None:
need(active_us == 0 or 2000 <= active_us <= 100000, '--input-active-us must be 0 or 2000..100000')
extra.append(f'snap_i2c_active_us={active_us}')
# --input-fnkeys: the keyboard's Fn-key layer (drivers/snap-i2c-hid/src/hid-keyboard.h HID_FNKEYS_*), read by a
# SnapI2CHID built with SNAP_I2C_HID_FNKEYS; the same sets the kext refuses are refused here.
fnkeys = getattr(args, 'input_fnkeys', None)
if fnkeys is not None:
need(getattr(args, 'input', False), '--input-fnkeys needs --input (the SnapI2CHID AuxKC)')
need(0 < fnkeys <= 0x1f and not fnkeys & ~0x1f, '--input-fnkeys must be 0x1..0x1f (hid-keyboard.h HID_FNKEYS_*)')
need((fnkeys >> 3) & 3 != 3, '--input-fnkeys: brightness encoding 3 (0x18) does not exist')
need(not fnkeys & 0x4 or fnkeys & 0x1, '--input-fnkeys: the host Fn-lock (0x4) needs translation (0x1)')
need(fnkeys & 0x3, '--input-fnkeys: give translation (0x1) or the raw log (0x2)')
extra.append(f'snap_i2c_fnkeys={fnkeys}')
if zone is not None:
need(ZONE_LOG_NAME.fullmatch(zone), f'--zone-log {zone!r}: XNU zone names are [A-Za-z0-9_.], at most 64')
extra.append(f'zlog1={zone}')
if records is not None:
need(1 <= records <= 65536, '--zone-log-records must be 1..65536')
extra.append(f'zrecs={records}')
return ' '.join([TIMESYNC_FLAG, COMPRESSOR_SW_FLAG] + ([CONSOLE_FLAG] if console else []) +
([SINGLE_USER_FLAG] if single else []) + extra)
# After a cold boot every RPMh TCS reads empty, so gmu-boot's first MX/CX vote on the
# fresh arena is the operator's decision (it stops at B1 otherwise, as
# installed-fresh-20261016-v3 did). The Stage 3, 4, P6/P7 and 6 runbooks take it as NOM:
# "MX/CX voted NOM (index 5) with --rail-vote-unseen --rail-level nom, link unaffected"
# (q1n1/docs/A16-ADRENO-STAGE3.md). The pinned launcher ran only on warm-chainloaded
# generations whose arena already carried that vote, so it never passed these.
COLD_BOOT_VOTE = ['--rail-vote-unseen', '--rail-level', 'nom']
def launcher_steps(root):
"""(name, arguments) of the established preparation, read from the pinned base launcher."""
base = root.parent / PINS['base_launcher']['path']
need(sha(base) == PINS['base_launcher']['sha256'], f'{base} changed')
for node in ast.parse(base.read_text()).body:
if isinstance(node, ast.Assign) and getattr(node.targets[0], 'id', None) == 'STEPS':
return [tuple(step) for step in ast.literal_eval(node.value)]
need(False, 'base launcher has no STEPS')
# --gpu-opp-mhz (docs/USERSPACE-GRAPHICS-20261019.md, "Speed batch"): the hfi step's perf table lists the speed bin's
# OPPs up to that clock and its GX_BW_PERF_VOTE asks for it (q1n1 a16gpu_stage3.py --max-opps/--perf-opp). 915 MHz is
# the 6th OPP (RPMh 256, NOM, 16.5 GB/s). Without the option the steps are the launcher's, byte for byte.
GPU_OPP_ARGS = {915: ['--max-opps', '6', '--perf-opp', '6']}
# --gpu-dvfs: the perf and bandwidth tables list OPPs 1..6 (310..915 MHz) and the first vote stays OPP 1; the monitor's
# governor (compat/gpu-dvfs.h) then votes within them.
GPU_DVFS_HFI_ARGS = ['--max-opps', '6', '--perf-opp', '1'] # b12-era table (915 MHz top); kept for reference
# agents/dvfs section 9 (user approval 2026-10-21: every OPP up to the chip maximum, thermal guard on): the hfi step's
# table runs up to --gpu-dvfs-ceiling. q1n1 adreno_x2.py GPU_OPPS, speed bin 1 (fuse 388): all 12 rows; the A16's
# cmd-db mx.lvl tops out at 452, so 1850 MHz (456) has no MX corner and q1n1 refuses it (rail_refusal): 1700 is the
# default and the maximum. The monitor reads the table size from the host record (hfi_opps): no rebuild per ceiling.
GPU_DVFS_CEILINGS_MHZ = (310, 410, 572, 760, 820, 915, 1070, 1185, 1350, 1550, 1700)
GPU_DVFS_CEILING_DEFAULT = 1700
def gpu_dvfs_hfi_args(ceiling_mhz=None):
ceiling = GPU_DVFS_CEILING_DEFAULT if ceiling_mhz is None else ceiling_mhz
need(ceiling != 1850, '--gpu-dvfs-ceiling 1850: the A16 cmd-db mx.lvl tops out at RPMh 452, so 1850 MHz (456) has '
'no MX corner; refused rather than under-voted (q1n1 a16gpu_stage3.rail_refusal)')
need(ceiling in GPU_DVFS_CEILINGS_MHZ, f'--gpu-dvfs-ceiling is one of {GPU_DVFS_CEILINGS_MHZ} (MHz)')
return ['--max-opps', str(GPU_DVFS_CEILINGS_MHZ.index(ceiling) + 1), '--perf-opp', '1']
# --ddr-vote (agents/dvfs lever 1, q1n1 tools/a16gpu_ddr.py): before the GPU steps, apps drv-2 votes SH0/MC0 at the
# BCM clamp with SH1/ACV on (what Linux holds until icc_sync_state); quiesce sends the explicit restore vote (the GPU
# OPP-1 row). --bw-probe: tools/measure-a16-memory-bandwidth.py before and after that vote, in this launcher's session.
DDR_VOTE_ARGS = ['ddr-vote', '--write', '--level', 'max']
DDR_RESTORE_ARGS = ['ddr-restore', '--write']
BW_PROBE = dict(bytes=64 << 20, passes=6, sweep=[8 << 20, 16 << 20, 32 << 20])
# --llcc (agents/dvfs FAILURE-MODES.md section 6, q1n1 tools/a16gpu_llcc.py): after the DDR vote and before the GPU steps,
# configure and activate the LLCC slices CPUSS (1), GPU (9) and GPUHTW (11) as llcc-qcom.c does (Linux glymur table, or
# --llcc-table windows: the A16 Windows driver's), with the probe before and after; after cp-fence, llcc-gpu points the
# GPU's GBIF traffic at the GPU slice (a8xx_llc_activate); quiesce deactivates the slices this boot activated.
LLCC_SLICES = 'cpuss,gpu,gpuhtw'
LLCC_GPU_STEP = ('llcc_gpu', ['llcc-gpu', '--write'])
# agents/gpu-x290 (FAILURE-MODES.md X1-X11). --gpu-x290: q1n1 `cp-fence --x290` on every cp-fence of the GMU boot (C0
# proves the 4-slice X2-90 from the fuse before anything is written; C3 adds a8xx_set_ubwc_config for glymur; a resumed
# call checks the block is there). --gpu-x290-survey: the read-only `x290-survey` after the cp-fence steps.
X290_SURVEY_STEP = ('x290_survey', ['x290-survey'])
def llcc_plan(args):
"""The qualification's 'llcc' entry: None without --llcc (the record then has no such key)."""
table = getattr(args, 'llcc_table', None)
if not getattr(args, 'llcc', False):
need(table is None, '--llcc-table needs --llcc')
return None
return dict(activate=['llcc-activate', '--write', '--slices', LLCC_SLICES, '--table', table or 'linux'],
deactivate=['llcc-deactivate', '--write'],
probe=dict(BW_PROBE) if getattr(args, 'bw_probe', False) else None)
def ddr_plan(args):
"""The qualification's 'ddr' entry: None without --ddr-vote (the record then has no such key)."""
if not getattr(args, 'ddr_vote', False):
need(not getattr(args, 'bw_probe', False) or getattr(args, 'llcc', False),
'--bw-probe measures around the DDR vote or the LLCC activation: it needs --ddr-vote or --llcc')
return None
return dict(vote=DDR_VOTE_ARGS, restore=DDR_RESTORE_ARGS,
probe=dict(BW_PROBE) if getattr(args, 'bw_probe', False) else None)
def bandwidth_tool(root):
"""tools/measure-a16-memory-bandwidth.py as a module (its stub build, measure and summary)."""
import runpy
return runpy.run_path(str(root / 'tools/measure-a16-memory-bandwidth.py'), run_name='bw_probe')
def bandwidth_probe(root, runner, device, dest, label, generation, plan):
"""--bw-probe: one single-core EL2 measurement (FAILURE-MODES.md P1-P10) in this launcher's own session, before
any guest is loaded; the record is build/PREFIX/bw-LABEL.json. Returns what launch.json keeps."""
import types
tool = bandwidth_tool(root)
blob = tool['build_stub']()
need(tool['sha'](blob) == plan['stub_sha256'], 'the bandwidth stub differs from the preparation')
memory_blob = tool['build_memory_probe']()
out = dest / f'bw-{label}.json'
need(not out.exists(), f'{out} exists; captures are never overwritten')
record = dict(schema='snapintosh-memory-bandwidth-v1', label=label, started=time.time(),
bytes_each=plan['bytes'], passes=plan['passes'], stub_sha256=plan['stub_sha256'],
qualification='single core (q1n1 boot CPU), EL2, MMU and caches on; not a multi-core figure')
proxy = runner.connect(device, tries=1)
try:
info = proxy.bootinfo()
need(info['current_el'] == 2 and info['stage_generation'] == generation, 'not the admitted EL2 generation')
record['serror_before'] = proxy.serror_state()
need(serror_clear(record['serror_before']), 'SError before the bandwidth probe')
tool['measure'](proxy, info, blob, memory_blob,
types.SimpleNamespace(bytes=plan['bytes'], passes=plan['passes'], buffer_pa=None, qemu=False,
sweep=plan.get('sweep') or []),
record, lambda: write(out, record), timer_hz=info['timer_hz'])
record['serror_after'] = proxy.serror_state()
need(serror_clear(record['serror_after']), 'SError during the bandwidth probe')
record['status'] = 'MEASURED'
except BaseException as error:
record['status'] = 'FAILED'
record['error'] = repr(error)
raise
finally:
write(out, record)
proxy.link.close()
return dict(path=str(out), sha256=st.file_sha(out), best=tool['summary'](record))
def recover_table_check(root, steps, args):
"""--gpu-recover reset replays compat/adreno-recover-table.h after a CP soft reset: it must be the generator's output
for the q1n1 sources this run's GPU steps use (their sha256 in ADRENO_RECOVER_TABLE_INPUTS) and the same cp-fence
options (--x290 changes hw_init); tools/gen-adreno-recover-table.py regenerates it."""
if getattr(args, 'gpu_recover', None) != 'reset':
return None
text = (root / RECOVER_TABLE).read_text()
inputs = re.search(r'#define ADRENO_RECOVER_TABLE_INPUTS "([^"]*)"', text)
options = re.search(r'#define ADRENO_RECOVER_TABLE_CP_FENCE_OPTIONS "([^"]*)"', text)
need(inputs and options, f'{RECOVER_TABLE}: no generator record (regenerate it)')
tools = root.parent / 'q1n1' / 'tools'
for item in inputs.group(1).split():
name, digest = item.split(':')
need(st.file_sha(tools / name) == digest, f'{RECOVER_TABLE} was generated from another q1n1 {name}: '
'run tools/gen-adreno-recover-table.py')
x290 = any(arguments[0] == 'cp-fence' and '--x290' in arguments for _, arguments in steps)
need((options.group(1) == '--x290') == x290, f'{RECOVER_TABLE} was generated for cp-fence '
f'"{options.group(1)}", the run\'s cp-fence has{"" if x290 else " no"} --x290')
return {'table_sha256': st.file_sha(root / RECOVER_TABLE), 'inputs': inputs.group(1), 'cp_fence': options.group(1)}
def gpu_steps(root, args=None):
"""The launcher's steps with the runbooks' first rail vote, for a cold-booted arena, and the opt-in GPU OPP."""
steps = launcher_steps(root)
need(dict(steps).get('gmu') == ['gmu-boot', '--write'], 'launcher gmu step changed')
need(dict(steps).get('hfi') == ['hfi', '--write'], 'launcher hfi step changed')
opp = GPU_OPP_ARGS[args.gpu_opp_mhz] if getattr(args, 'gpu_opp_mhz', None) else []
need(getattr(args, 'gpu_dvfs_ceiling', None) is None or getattr(args, 'gpu_dvfs', False),
'--gpu-dvfs-ceiling needs --gpu-dvfs')
if getattr(args, 'gpu_dvfs', False):
need(not opp, '--gpu-dvfs and --gpu-opp-mhz both choose the GPU clock: give one')
opp = gpu_dvfs_hfi_args(getattr(args, 'gpu_dvfs_ceiling', None))
extra = {'gmu': COLD_BOOT_VOTE, 'hfi': opp}
result = [(name, [*arguments, *extra.get(name, [])]) for name, arguments in steps]
if getattr(args, 'gpu_va_wide', False):
# q1n1 cp-fence --va-wide: CB0 T0SZ 30, on every cp-fence of the GMU boot (C1 programs it, the others refuse a
# CB0 without it). boot() gives adopt the same flag from the preparation's SNAP_ADRENO_VA_WIDE.
need(any(arguments[0] == 'cp-fence' for _, arguments in result), 'launcher has no cp-fence step')
result = [(name, [*arguments, '--va-wide'] if arguments[0] == 'cp-fence' else arguments)
for name, arguments in result]
if getattr(args, 'gpu_x290', False):
need(any(arguments[0] == 'cp-fence' for _, arguments in result), 'launcher has no cp-fence step')
result = [(name, [*arguments, '--x290'] if arguments[0] == 'cp-fence' else arguments)
for name, arguments in result]
if getattr(args, 'llcc', False):
result.append(LLCC_GPU_STEP) # after cp-fence (GX on), before adopt
if getattr(args, 'gpu_x290_survey', False):
result.append(X290_SURVEY_STEP) # read-only, after every GPU write step, before adopt
return result
def root_hash_payload(root):
"""The SystemVolume root hash, extracted from the IPSW and checked against the
profile's component and payload pins and the manifest's SHA-384."""
from guestprofile import get_profile
spec = get_profile(PROFILE).installed_root
with zipfile.ZipFile(root / IPSW) as archive:
manifest = plistlib.loads(archive.read('BuildManifest.plist'))
need(manifest['ProductBuildVersion'] == '26A428', 'IPSW build differs')
paths = {i['Manifest'][spec.component]['Info']['Path']: i['Manifest'][spec.component]['Digest']
for i in manifest['BuildIdentities'] if spec.component in i['Manifest']}
found = [(p, d, archive.read(p)) for p, d in paths.items()]
found = [(p, d, data) for p, d, data in found if hashlib.sha256(data).hexdigest() == spec.component_sha256]
need(len(found) == 1, 'IPSW has no single SystemVolume component with the profile hash')
path, manifest_digest, data = found[0]
need(hashlib.sha384(data).digest() == manifest_digest, 'root-hash component differs from its manifest')
payload = load('extract_firmware', root / 'tools/extract-firmware.py').im4p_payload(data)
need(len(payload) == spec.payload_bytes and digest(payload) == spec.payload_sha256,
'extracted root hash differs from the profile pin')
return path, payload
def txm_helper(root, dest):
"""The native TXM helper, relocated from the firmware TXM with the bridge built from source."""
import txmnative
out = dest / 'txm-bridge'
out.mkdir()
subprocess.run([f'{LLVM}/clang', '--target=aarch64-unknown-none-elf', '-c', str(root / BRIDGE[0]),
'-o', str(out / 'bridge.o')], check=True)
subprocess.run(['ld.lld', '-T', str(root / BRIDGE[1]), '-o', str(out / 'bridge.elf'), str(out / 'bridge.o')],
check=True)
subprocess.run([f'{LLVM}/llvm-objcopy', '-O', 'binary', str(out / 'bridge.elf'), str(out / 'bridge.bin')],
check=True)
bridge = (out / 'bridge.bin').read_bytes()
need(digest(bridge) == txmnative.BRIDGE_SHA, 'bridge differs from the qualified bridge')
flat, fixes, patches = txmnative.relocate((root / FIRMWARE / 'txm.macosx.release').read_bytes(),
txmnative.HELPER_BASE, bridge)
need(len(flat) == txmnative.IMAGE_BYTES and digest(flat) == txmnative.IMAGE_SHA,
'relocated helper differs from the image qualified on the A16')
(dest / 'image4.bin').write_bytes(flat)
return dict(bridge_sha256=digest(bridge), image_sha256=digest(flat), fixups=len(fixes), patches=len(patches))
def image_checks(root, dest, layout, root_hash, sysreg_zero=False, args=None, refmod_skip=False, user_4k=False,
context_layout=None):
"""The complete guest image, built offline with a synthetic GPU adoption."""
import adreno
import monitor_work
import txmnative
from appledt import DeviceTree
profile_name, aux, _, _ = selected(args)
xb = load('installed_image', root / 'probes/xnuboot.py')
xb.configure_guest(profile_name, root / FIRMWARE)
xb.configure_txm_arena(bool(getattr(args, 'txm_aspace_wide', False)))
need(not xb.check_profile(kernelcache=xb.DEFAULT_KERNELCACHE), 'firmware profile rejected')
need(xb.BOOT_CMDLINE == BASE_CMDLINE, 'boot command line differs from the TimeSync base')
profile = xb.GUEST_PROFILE
need(profile.installed_root and profile.adreno and profile.ans_boot and profile.aux_collection_linked,
'profile is not the installed Adreno route')
wide = bool(getattr(args, 'gpu_va_wide', False))
xb.configure_adreno_va_wide(wide)
span = st.GUEST_MIB << 20
xb.place(st.PHYS_BASE, span + xb.bootstrap_reserve(span) + monitor_work.WORK_SIZE, guest_span=span)
xb.ADRENO_RECORD = adreno.fixture_record(wide=wide)
beam = bool(getattr(args, 'present_beam', False))
import displaytiming
xb.DISPLAY_TIMING = displaytiming.SPEC if beam else None
qcc = bool(getattr(args, 'qcc2072_wifi', False))
if qcc:
# The offline admission (generation 1, a fixed cookie) over the attempt's staged inputs; the boot's
# record is made live by probes/run.py.
import qcc2072wifi
xb.QCC2072_WIFI = qcc2072wifi.grant(qcc2072wifi.fixture_spec(st.PHYS_BASE),
qcc2072wifi.load_inputs(dest / QCC2072_INPUTS))
log = io.StringIO()
with contextlib.redirect_stdout(log):
built = xb.build(str(xb.DEFAULT_KERNELCACHE), root_hash=str(root_hash), auxkc=str(root / aux),
trust_additions=str(root / ADDITIONS), sysreg_zero=bool(sysreg_zero),
refmod_skip=bool(refmod_skip), user_4k=bool(user_4k),
hide_amx=bool(getattr(args, 'hide_amx', False)))
need(bool(built.get('kernel_sysreg_zero')) == bool(sysreg_zero), 'kernel sysreg-zero patch state differs from the request')
need(bool(built.get('kernel_refmod_skip')) == bool(refmod_skip), 'kernel refmod-skip patch state differs from the request')
need(bool(built.get('kernel_user_4k')) == bool(user_4k), 'kernel user-4k patch state differs from the request')
need(bool(built.get('hide_amx')) == bool(getattr(args, 'hide_amx', False)),
'application AMX capability patch state differs from the request')
(dest / 'image-build.log').write_text(log.getvalue())
chunks = built['chunks']
tree = chunks[xb.DEVICETREE_PA]
dt = DeviceTree(tree)
need(dt.serialize() == tree, 'device tree does not round-trip')
need(dt.nodes['/device-tree/chosen/asmb']['lp-sip0'].value == (2).to_bytes(8, 'little'), 'lp-sip0 is not 2')
need(adreno.GPU_NODE in dt.nodes and adreno.DART_NODE in dt.nodes, 'no Adreno GPU or DART node')
for capability in ('guest-fence-abi', 'guest-unmap-abi', 'guest-cache-abi', 'guest-fill32-abi', 'guest-ib-abi'):
need(dt.nodes[adreno.GPU_NODE]['snapintosh,' + capability].value == (1).to_bytes(4, 'little'),
f'GPU node lacks {capability}')
slots = [child for child in dt.children['/device-tree/arm-io']
if bytes(dt.get(child, 'device_type') or b'').rstrip(b'\0') in (b'display-subsystem', b'ext-display-subsystem')]
need(slots == [DISPLAY_SLOT], f'display slots {slots}, expected {DISPLAY_SLOT}')
for path in ('/device-tree/arm-io/dispext0', '/device-tree/arm-io/dcpext', '/device-tree/arm-io/dcpext-expert'):
need(not any(n == path or n.startswith(path + '/') for n in dt.nodes), f'external display node {path}')
trust = xb.trust_cache_spec(root / ADDITIONS).load(root / FIRMWARE)
need(digest(trust) == TRUST_SHA and trust == (root / TRUST_PAYLOAD).read_bytes(), 'trust cache differs')
need(sum(trust in raw for raw in chunks.values()) == 1, 'trust cache is not uploaded exactly once')
aux_va, (aux_pa, aux_span) = built['auxkc_va_range'], built['auxkc_range']
words = struct.unpack_from('<4Q', chunks[built['params_pa']], layout.offset('params', 'auxkc'))
need(words == (aux_va[0], aux_va[1], aux_pa, aux_pa + aux_span), 'params do not carry the AuxKC range')
ans = xb.ans_reservation()
adreno.check_disjoint(xb.ADRENO_RECORD, [(xb.PHYS_BASE, ans[0] + ans[1] - xb.PHYS_BASE)])
window = struct.unpack('<Q', bytes(dt.get(adreno.DART_NODE, 'vm-size')))[0]
need(window == adreno.window_end(wide) - adreno.WINDOW_BASE, f'dart-adreno vm-size {window:#x} for gpu_va_wide={wide}')
need((adreno.GPU_VA_BITS_PROPERTY in dt.nodes[adreno.GPU_NODE]) == wide,
f'{adreno.GPU_VA_BITS_PROPERTY} on the GPU node only with --gpu-va-wide')
gpu_words = struct.unpack_from(f'<{adreno.PARAMS_WORDS}Q', chunks[built['params_pa']], layout.offset('params', 'gpu'))
pool = xb.gpu_pool_reservation()
if wide:
# agents/gpu-va-wide V5: the params pool word is the monitor-private reservation, above the ANS2 one, clear of
# the guest, the work arena, the ANS2 reservation and the q1n1 arena.
need(pool is not None and pool[0] >= ans[0] + ans[1] and gpu_words[8] == pool[0],
'params pool word is not the GPU table reservation')
need(gpu_words[6] & 0x3F == adreno.CB0_T0SZ_WIDE, 'params CB0 TCR is not T0SZ 30')
adreno.check_disjoint(xb.ADRENO_RECORD, [(xb.PHYS_BASE, pool[0] + pool[1] - xb.PHYS_BASE)])
adreno.check_pool_reservation(xb.ADRENO_RECORD, pool[0], [(xb.PHYS_BASE, ans[0] + ans[1] - xb.PHYS_BASE)])
for lo, hi, _, what in xb.stage2_ranges():
need(not (lo < pool[0] + pool[1] and pool[0] < hi), f'the GPU table reservation is in stage 2 ({what})')
else:
need(pool is None and gpu_words[8] == xb.ADRENO_RECORD['pool']['pa'], 'params pool word is not the arena pool')
trailing = trailing_triples = 0
if qcc:
import qcc2072wifi
trailing, trailing_triples = 8 * qcc2072wifi.PARAMS_WORDS, len(qcc2072wifi.WINDOWS)
if beam:
trailing += 8 * displaytiming.PARAMS_WORDS # no arm-io ranges triple: the kext maps by address
extra = (input_checks(xb, dt, chunks[built['params_pa']], layout, trailing, trailing_triples)
if getattr(args, 'input', False) else {})
need(bool(extra) or not any(n.startswith('/device-tree/arm-io/i2c-hid') for n in dt.nodes),
'an input node outside --input')
if qcc:
extra.update(qcc2072_checks(xb, dt, chunks[built['params_pa']], layout,
8 * displaytiming.PARAMS_WORDS if beam else 0))
if beam:
extra.update(display_timing_checks(xb, dt, chunks[built['params_pa']], layout, qcc))
need(beam or not any(k.startswith('snapintosh,dpu-intf') for k in dt.nodes[adreno.GPU_NODE]),
'DPU INTF properties on the GPU node outside --present-beam')
need(qcc or not any(n == '/device-tree/arm-io/snap-qcc2072' for n in dt.nodes), 'a QCC2072 node outside --qcc2072-wifi')
# The table arena lies between the context and the TXM helper's 3 MiB at the top: the build's context
# (st.build_layout), not the define-free one.
arena = monitor_work.WORK_SIZE - txmnative.WORK_BYTES - (((context_layout or layout).size('context') + 4095) & ~4095)
need(arena >= 128 << 20, f'table arena {arena >> 20} MiB is too small')
(dest / 'device-tree.bin').write_bytes(tree)
return dict(profile=profile_name, kernel_sha256=sha(xb.DEFAULT_KERNELCACHE),
firmware_sha256=profile.hashes(root / FIRMWARE), entry=hex(built['entry']),
chunks=[dict(address=hex(a), bytes=len(b), sha256=digest(b)) for a, b in sorted(chunks.items())],
upload_bytes=sum(len(b) for b in chunks.values()), display_slots=slots,
trust_cache_sha256=digest(trust), device_tree_sha256=digest(tree),
auxkc_range=[hex(aux_pa), hex(aux_span)], table_arena_bytes=arena,
**({'gpu_va_wide': dict(window=[hex(adreno.WINDOW_BASE), hex(adreno.window_end(True))],
pool=[hex(pool[0]), hex(pool[1])], pool_bytes=hex(adreno.pool_bytes(True)))}
if wide else {}),
kernel_sysreg_zero=built.get('kernel_sysreg_zero'),
**({'kernel_refmod_skip': built['kernel_refmod_skip']} if refmod_skip else {}),
**({'kernel_user_4k': built['kernel_user_4k']} if user_4k else {}),
**({'hide_amx': built['hide_amx']} if getattr(args, 'hide_amx', False) else {}), **extra,
qualification='offline image at the declared base with a synthetic GPU adoption; '
'hardware placement and the real adoption are captured at boot')
def input_checks(xb, dt, params, layout, trailing=0, trailing_triples=0):
"""--input's nodes, ranges triples, appended params words and stage-2 rows, against probes/i2cinput.py:
the keyboard, and with --input-touchpad the touchpad too. `trailing` bytes of a later params block and
`trailing_triples` later arm-io ranges triples (both --qcc2072-wifi's) may follow the input's."""
import struct
import i2cinput
spec = xb.GUEST_PROFILE.i2c_input
devices = spec.devices() if spec is not None else ()
need(devices in ((i2cinput.KEYBOARD,), (i2cinput.KEYBOARD, i2cinput.TOUCHPAD)),
'input profile is not the keyboard, or the keyboard and the touchpad')
triples = list(struct.iter_unpack('<3Q', bytes(dt.nodes['/device-tree/arm-io']['ranges'].value)))
triples = triples[:len(triples) - trailing_triples]
for d, triple in zip(devices, triples[-len(devices):]):
node = dt.nodes.get(d.node)
need(node is not None, f'no {d.node}')
value = lambda k: bytes(node[k].value)
need(value('compatible') == i2cinput.MATCH.encode() + b'\0' and 'interrupts' not in node and
value('reg') == struct.pack('<QQ', i2cinput.CHILD_OFFSET + d.pa, i2cinput.PAGE) and
value('snapintosh,hid-address') == struct.pack('<I', d.address) and
value('snapintosh,hid-descriptor-reg') == struct.pack('<I', d.descriptor_register) and
value('snapintosh,hid-vendor-product') == struct.pack('<2I', d.vendor, d.product) and
value('snapintosh,irq-policy') == struct.pack('<I', 1), f'{d.node} differs from probes/i2cinput.py')
need(triple == (i2cinput.CHILD_OFFSET + d.pa, d.pa, i2cinput.QUP_SPAN),
f'{d.node}: its ranges triple is not among the appended last ones')
# MonitorLayout reads xnuboot.c without build defines: the appended block starts at its params size.
words = list(struct.unpack_from(f'<{i2cinput.PARAMS_WORDS}Q', params, layout.size('params')))
rows = [(d.pa, d.pa + i2cinput.PAGE) for d in devices]
need(len(params) == layout.size('params') + 8 * i2cinput.PARAMS_WORDS + trailing and i2cinput.rows(words) == rows,
'params do not end with the input block')
need([(lo, hi) for lo, hi, _, _ in xb.stage2_ranges() if (lo, hi) in rows] == rows, 'stage 2 lacks an input page')
return dict(input=dict(nodes=[d.node for d in devices], params_words=[hex(w) for w in words],
stage2=[[hex(lo), hex(hi)] for lo, hi in rows], define=INPUT_DEFINE))
def qcc2072_checks(xb, dt, params, layout, trailing=0):
"""--qcc2072-wifi's node, its five ranges triples (appended last), the provider record in the node, the params
block appended after struct params and the input block, and the five stage-2 rows, against
probes/qcc2072wifi.py and the attempt's staged inputs."""
import qcc2072wifi as q
grant = xb.QCC2072_WIFI
need(grant is not None, 'no QCC2072 admission in the image')
node = dt.nodes.get(q.NODE)
need(node is not None, f'no {q.NODE}')
value = lambda k: bytes(node[k].value)
reg = b''.join(struct.pack('<QQ', q.CHILD_OFFSET + pa, size) for _, pa, size, *_ in q.WINDOWS)
need(sorted(node) == sorted(['name', 'compatible', 'reg', q.ADMISSION]) and 'interrupts' not in node and
value('name') == b'snap-qcc2072\0' and value('compatible') == q.MATCH.encode() + b'\0' and
value('reg') == reg and value(q.ADMISSION) == grant.record, f'{q.NODE} differs from probes/qcc2072wifi.py')
words = struct.unpack('<32Q', grant.record)
need(words[:4] == (q.RECORD_MAGIC, 1, 256, 1) and words[6:19] == (
q.PCI_ID, q.PCI_SUBSYSTEM, q.SID, q.PROOFS, q.BAR0_PA, q.BAR0_BYTES, q.BAR2_PA, q.BAR2_BYTES, q.ECAM_PA,
q.DMA_PA, q.DMA_PA, q.DMA_BYTES, q.CONTROL_PA) and words[28:] == (64, 0, 0, 0),
'the provider record differs from the allow-list')
triples = list(struct.iter_unpack('<3Q', bytes(dt.nodes['/device-tree/arm-io']['ranges'].value)))
need(triples[-len(q.WINDOWS):] == q.triples(), 'the QCC2072 ranges triples are not the appended last ones')
need(dt.children['/device-tree/arm-io'].count(q.NODE) == 1, 'the QCC2072 node is not one arm-io child')
block = list(struct.unpack_from(f'<{q.PARAMS_WORDS}Q', params, len(params) - trailing - 8 * q.PARAMS_WORDS))
rows = q.rows(block)
import i2cinput
extra_input = 8 * i2cinput.PARAMS_WORDS if xb.GUEST_PROFILE.i2c_input is not None else 0
need(len(params) == layout.size('params') + extra_input + 8 * q.PARAMS_WORDS + trailing and rows is not None,
'params do not end with the QCC2072 block')
need([(lo, hi) for lo, hi, _, _ in xb.stage2_ranges() if (lo, hi) in rows] == rows, 'stage 2 lacks a QCC2072 window')
return dict(qcc2072_wifi=dict(node=q.NODE, record_sha256=q.SHA(grant.record), params_words=[hex(w) for w in block],
stage2=[[hex(lo), hex(hi), access] for lo, hi, access, _ in q.stage2_ranges(grant)],
ranges=[[hex(x) for x in t] for t in q.triples()], define=QCC2072_WIFI_DEFINE,
qualification='offline fixture admission (generation 1, fixed cookie); the boot '
'admits live and draws a fresh cookie'))
def display_timing_checks(xb, dt, params, layout, qcc=False):
"""--present-beam's params block (appended last, after any input and QCC2072 blocks), its read-only stage-2 row and
the two GPU-node properties, against probes/displaytiming.py."""
import adreno
import displaytiming as d
need(xb.DISPLAY_TIMING is not None, 'no display timing in the image')
gpu = dt.nodes[adreno.GPU_NODE]
need(bytes(gpu[d.ABI_PROPERTY].value) == struct.pack('<I', d.ABI) and
bytes(gpu[d.PA_PROPERTY].value) == struct.pack('<Q', d.INTF_PA), 'the GPU node\'s DPU INTF properties differ')
block = list(struct.unpack_from(f'<{d.PARAMS_WORDS}Q', params, len(params) - 8 * d.PARAMS_WORDS))
import i2cinput
import qcc2072wifi
before = (8 * i2cinput.PARAMS_WORDS if xb.GUEST_PROFILE.i2c_input is not None else 0) + \
(8 * qcc2072wifi.PARAMS_WORDS if qcc else 0)
need(len(params) == layout.size('params') + before + 8 * d.PARAMS_WORDS and d.rows(block) is not None,
'params do not end with the display timing block')
rows = [(lo, hi, access) for lo, hi, access, _ in xb.stage2_ranges() if (lo, hi) == (d.INTF_PA, d.INTF_PA + d.PAGE)]
need(rows == [(d.INTF_PA, d.INTF_PA + d.PAGE, 'ro')], 'stage 2 lacks the read-only DPU INTF page')
return dict(display_timing=dict(node=adreno.GPU_NODE, properties=[d.ABI_PROPERTY, d.PA_PROPERTY],
params_words=[hex(w) for w in block], stage2=[hex(d.INTF_PA), hex(d.INTF_PA + d.PAGE),
'ro'], define=DISPLAY_TIMING_DEFINE))
def qcc2072_stop(proxy):
"""After preservation: the QCC2072's bus mastering off (memory decode stays on), read back. Never raises, so a
preserved stop stays preserved; a failure is recorded instead (the next Wi-Fi attempt needs a cold boot anyway:
the card's firmware is left running)."""
try:
import qcc2072wifi
return dict(qcc2072wifi.stop(proxy), at=time.time())
except Exception as error: # the record says what happened; --quiesce tries again
return dict(error=repr(error), bus_master_off=None, at=time.time())
def inputs(root, dest, args=None):
_, aux, _, _ = selected(args)
paths = [root / aux, root / (aux + '.json'), root / ADDITIONS, root / TRUST_PAYLOAD,
dest / 'root-hash.payload', dest / 'image4.bin', *(root / b for b in BRIDGE)]
if getattr(args, 'qcc2072_wifi', False):
import qcc2072wifi
paths += [Path(aux).parent / 'build.json', *(dest / QCC2072_INPUTS / n for n in qcc2072wifi.INPUTS)]
return {str(p): st.file_sha(p) for p in paths}
def prepare(ws, args, dest):
need(args.variant in st.VARIANTS, 'prepare needs --variant control|fast')
need(not dest.exists(), f'preserve previous attempt: {dest}')
root = ws.root
runner, layout, _ = modules(root)
sys.path.insert(0, str(root / 'probes'))
import auxkc
sets, fast = st.flag_sets(root)
flags = variant_flags(sets, args)
sources = {str(p): sha(p) for p in source_paths(root)}
profile_name, aux, aux_sha, bundles = selected(args)
from guestprofile import get_profile as profile_of
spec = profile_of(profile_name).i2c_input
if spec is not None:
try:
spec.require_qualified() # --input-touchpad: refused before anything is built
except ValueError as error:
need(False, str(error))
need(st.file_sha(root / aux) == aux_sha, 'retained AuxKC changed')
need(not re.search(rb'OSBundleRequireExplicitLoad</key>\s*<true/>', (root / aux).read_bytes()),
'AuxKC kext requires an explicit load: run tools/auxkc-autoload.py on it')
need(sha(root / ADDITIONS) == ADDITIONS_SHA, 'retained trust additions changed')
_, pin = auxkc.load(root / aux, sha(root / FIRMWARE / 'kernelcache.release.mac17p'))
need(pin['sha256'] == aux_sha and pin['bundles'] == bundles, 'AuxKC pin differs')
component, payload = root_hash_payload(root)
steps = gpu_steps(root, args)
recover_table = recover_table_check(root, steps, args)
ddr, llcc = ddr_plan(args), llcc_plan(args)
for plan in (ddr, llcc):
if plan and plan['probe']:
plan['probe']['stub_sha256'] = bandwidth_tool(root)['sha'](bandwidth_tool(root)['build_stub']())
dest.mkdir()
(dest / 'root-hash.payload').write_bytes(payload)
qcc_inputs = None
if getattr(args, 'qcc2072_wifi', False):
# The three inputs the provider verifies, copied here hash-checked: the boot reads only these.
import qcc2072wifi
try:
qcc_inputs = qcc2072wifi.stage_inputs(dest / QCC2072_INPUTS)
except (OSError, ValueError) as error:
need(False, f'QCC2072 inputs refused: {error}')
helper = txm_helper(root, dest)
runner.FLAGS[:] = flags
with st.environment(args.prefix), (dest / 'prepare.log').open('x') as stream, contextlib.redirect_stdout(stream):
blob = runner.build('xnuboot', dest)
context = st.build_layout(root, flags)
image = image_checks(root, dest, layout, dest / 'root-hash.payload',
sysreg_zero=getattr(args, 'kernel_sysreg_zero', False), args=args,
refmod_skip=getattr(args, 'kernel_refmod_skip', False),
user_4k=getattr(args, 'kernel_user_4k', False), context_layout=context)
need(all(sha(p) == h for p, h in sources.items()), 'sources changed during preparation')
record = dict(schema=SCHEMA, status='OFFLINE_PREPARED_NOT_BOOTED', prefix=args.prefix, variant=args.variant,
device=args.device or ws.device, profile=profile_name, watchdog_seconds=WATCHDOG, environment=environment(args),
flags=flags, fast_defines=fast if args.variant == 'fast' else [], gpu_steps=steps,
root_hash_component=component, txm_helper=helper, auxkc_pin=pin,
window_header_prefix=WINDOW_HEADER_PREFIX, boot_argument_addition=boot_addition(args),
inputs_sha256=inputs(root, dest, args), source_sha256=sources,
monitor_sha256=digest(blob), monitor_bytes=len(blob), elf_sha256=sha(dest / 'xnuboot.elf'),
context_bytes=context.size('context'), context_layout='build-defines', image=image,
**({'qcc2072_inputs': qcc_inputs} if qcc_inputs else {}),
**({'gpu_recover_table': recover_table} if recover_table else {}),
**({'ddr': ddr} if ddr else {}),
**({'llcc': llcc} if llcc else {}),
qualification='installed-SSD boot with our Adreno kext and Metal plug-in, admitted from '
'current sources and retained assets; not a cycle.py launch baseline')
write(dest / 'qualification.json', record)
return record
def check(ws, args, dest):
path = dest / 'qualification.json'
need(path.is_file(), 'run --prepare first')
record = json.loads(path.read_text())
need(record['schema'] == SCHEMA and record['prefix'] == args.prefix, 'wrong qualification')
need(record['device'] == (args.device or ws.device), 'device differs from preparation')
need(record['environment'] == environment(args) and record['profile'] == selected(args)[0] and
record['boot_argument_addition'] == boot_addition(args), 'installed-fresh scope changed')
# The profile is the same for every input collection, so the pinned AuxKC itself is compared: a boot without
# the preparation's --present-damage (or --guest-net) would otherwise load another collection.
need(record['auxkc_pin']['sha256'] == selected(args)[2],
'the AuxKC differs from the preparation (give --present-damage/--present-cached-read/--present-gpumap/'
'--present-vsync60/--present-dartpool/--present-resident/--present-beam/--present-va-reclaim/--gpu-va-wide/'
'--present-latch/--edp-backlight/'
'--input-ptp/--guest-net to every mode)')
need(record['flags'] == variant_flags(st.flag_sets(ws.root)[0], args, record['variant']), 'variant flags differ from the recipe')
need(record['gpu_steps'] == [list(s) for s in gpu_steps(ws.root, args)], 'GPU steps differ from the pinned launcher')
plan = ddr_plan(args)
recorded = record.get('ddr')
need((plan is None) == (recorded is None) and (plan is None or (
recorded['vote'] == plan['vote'] and recorded['restore'] == plan['restore'] and
bool(recorded['probe']) == bool(plan['probe']))),
'the DDR vote/probe differ from the preparation (give --ddr-vote/--bw-probe to every mode)')
plan, recorded = llcc_plan(args), record.get('llcc')
need((plan is None) == (recorded is None) and (plan is None or (
recorded['activate'] == plan['activate'] and recorded['deactivate'] == plan['deactivate'] and
bool(recorded['probe']) == bool(plan['probe']))),
'the LLCC activation/probe differ from the preparation (give --llcc/--llcc-table/--bw-probe to every mode)')
for name, expected in record['source_sha256'].items():
need(sha(name) == expected, f'source changed: {name}')
for name, expected in record['inputs_sha256'].items():
need(st.file_sha(name) == expected, f'input changed: {name}')
need(sha(dest / 'xnuboot.bin') == record['monitor_sha256'] and sha(dest / 'xnuboot.elf') == record['elf_sha256'],
'prepared monitor changed')
need(not (dest / 'launch.json').exists(), 'this attempt already has a launch record; never rerun it')
from guestprofile import get_profile
need(get_profile(selected(args)[0]).hashes(ws.root / FIRMWARE) == record['image']['firmware_sha256'],
'firmware inputs changed')
return record
HOST_IDENTITY = ('magic', 'version', 'arena_base')
# Every host-record field that names something live (an SMMU table, a booted GMU, an HFI or OOB session, a
# started CP, the ring, a hardware init, an SQE probe, P6/P7 state, a mapped framebuffer). A graceful quiesce
# releases all of them; the history counters (hfi_seq, fence_seq/value, rsc_cx/mx votes, tz_ok, pdc_sleep)
# stay, and a16gpu's steps account for them on a second cycle (votes already sent are not resent).
LIVE_TOKENS = ('gmu_ttbr', 'gpu_ttbr', 'boot_token', 'hfi_token', 'oob_token', 'cp_token', 'rb_wptr',
'hwinit_token', 'sqe_probe', 'p67_token', 'fb_mapped')
def gpu_unprepared(status):
"""A read-only a16gpu status of a GPU nothing has prepared in this generation.
q1n1 publishes a host record for its EFI-reserved arena at every boot; until a
preparation step runs, every token in it is zero (seen on the A16 after a cold
boot, installed-fresh-20261016-v2). So: no live GMU, and either no arena record
or one whose tokens are all zero."""
if (status.get('gmu') or {}).get('live') or 'order' not in status or status.get('stopped'):
return False
host = status.get('host_record')
if host is None:
return 'missing' in status.get('arena', {})
tokens = {k: v for k, v in host.items() if k not in HOST_IDENTITY}
return bool(tokens) and all(int(v, 16) == 0 for v in tokens.values())
def gpu_quiesced_after_use(status):
"""A read-only a16gpu status of a GPU that a previous attempt in this boot prepared and then quiesced
gracefully: no live GMU, not stopped, the arena host record present with every live token released.
q1n1's Stage 3 run 2 brought the GPU up again after such a quiesce on this A16 (A16-ADRENO-STAGE3.md)."""
if (status.get('gmu') or {}).get('live') or 'order' not in status or status.get('stopped'):
return False
host = status.get('host_record')
if not host or any(k not in host for k in LIVE_TOKENS):
return False
return all(int(host[k], 16) == 0 for k in LIVE_TOKENS)
def previous_quiesce(ws, root, prefix):
"""The attempt that last used the GPU in this boot (--after-quiesce): its captures verified, its GPU
quiesced gracefully with no SError. Returns what the launch record keeps."""
dest = root / 'build' / prefix
launch = json.loads((dest / 'launch.json').read_text())
need(launch['status'] in ('STOPPED_PRESERVED', 'FAILED', 'NO_GUEST_CALL'), f'{prefix}: stop state {launch["status"]}')
if launch['status'] == 'STOPPED_PRESERVED':
record = (dest / 'preservation.json').read_bytes()
need(hashlib.sha256(record).hexdigest() == launch['preservation_sha256'], f'{prefix}: preservation record changed')
for name, artifact in json.loads(record)['artifacts'].items():
need(st.file_sha(dest / name) == artifact['sha256'], f'{prefix}: preserved {name} changed')
quiet = json.loads((ws.q / 'build/a16-gpu' / f'{prefix}-quiesce.json').read_text())
need(quiet.get('quiesce') == 'graceful' and not quiet.get('stopped') and serror_clear(quiet['serror_guard']['after']),
f'{prefix}: the GPU was not quiesced gracefully')
return dict(prefix=prefix, status=launch['status'], preservation_sha256=launch.get('preservation_sha256'),
quiesce_sha256=st.file_sha(ws.q / 'build/a16-gpu' / f'{prefix}-quiesce.json'))
def a16gpu(ws, device, out, arguments, environment):
"""One a16gpu.py client; its record must not exist and must end unstopped."""
log = out.with_suffix('.log')
need(not out.exists() and not log.exists(), f'{out.name} already exists')
# q1n1's build/ is untracked, so a fresh checkout has no build/a16-gpu/ yet.
log.parent.mkdir(parents=True, exist_ok=True)
with log.open('x') as stream:
result = subprocess.run([sys.executable, 'tools/a16gpu.py', '--device', device, '--out', str(out), *arguments],
cwd=ws.q, env=environment, stdout=stream, stderr=subprocess.STDOUT)
record = json.loads(out.read_text()) if out.is_file() else {}
return result.returncode, record
@contextlib.contextmanager
def boot_hooks(root, dest, record):
"""The two changes the base launcher made to the loader, made here explicitly:
-timesync_bringup on the command line of the one xnuboot module run.py loads,
and the SSD window header check plus APFS checkpoints when the window is armed."""
import importlib.util
sys.path.insert(0, str(root.parent / 'q1n1/tools'))
import nvme_m3
original_spec, original_arm = importlib.util.spec_from_file_location, nvme_m3.arm_window
loader = (root / 'probes/xnuboot.py').resolve()
injected = []
def bound_spec(name, location, *a, **k):
spec = original_spec(name, location, *a, **k)
if name == 'xnuboot' and Path(location).resolve() == loader:
need(not injected, 'the guest loader must be loaded exactly once')
exec_module = spec.loader.exec_module
def load_module(module):
exec_module(module)
need(module.BOOT_CMDLINE == BASE_CMDLINE, 'boot command line differs from the TimeSync base')
module.BOOT_CMDLINE += ' ' + record['boot_argument_addition']
injected.append(module.BOOT_CMDLINE)
record['boot_cmdline'] = module.BOOT_CMDLINE
spec.loader.exec_module = load_module
return spec
def checked_arm(nvme, guid, report):
first, last = original_arm(nvme, guid, report)
raw = nvme.read(first + nvme_m3.HEADER_OFF // nvme.lba_bytes, 4096 // nvme.lba_bytes)
header = hashlib.sha256(raw).hexdigest()
record['window_header_sha256'] = header
need(header.startswith(WINDOW_HEADER_PREFIX), f'SSD window header {header[:16]} is not the last applied one')
checkpoints = load('apfs_checkpoints', root / 'tools/apfs-checkpoints.py').capture(
nvme, first, report['window_armed']['header'], dest / 'apfs-before.json')
record['apfs_before'] = {n: x['latest_xid'] for n, x in checkpoints['containers'].items()}
return first, last
importlib.util.spec_from_file_location, nvme_m3.arm_window = bound_spec, checked_arm
try:
yield injected
finally:
importlib.util.spec_from_file_location, nvme_m3.arm_window = original_spec, original_arm
def refused_by_thermal_before_gpu(dest, launch):
"""True when the preserved run proves its monitor refused at a thermal checkpoint of xnuboot_prepare
before GPU preparation (docs/INSTALLED-FRESH-ADMISSION-20261016.md, 2026-10-19): no guest, no
secondary or relocated CPU (each launch follows the last checkpoint), and the monitor never adopted
the GPU, so the work area holds no live CPU although the service word never parked. Evidence: exit 3,
the report's prepare status -28 with nothing entered, a thermal report, and a GPU flow status of 0.
The preservation record and what it binds are checked before they are read."""
import a16_thermal
if launch.get('status') != 'STOPPED_PRESERVED' or launch.get('exit_code') != 3 or \
(launch.get('summary') or {}).get('cpus_active') != 0:
return False
record = (dest / 'preservation.json').read_bytes()
if hashlib.sha256(record).hexdigest() != launch.get('preservation_sha256'):
return False
artifacts = json.loads(record)['artifacts']
for name in ('report.bin', 'context.bin', 'work-locator.bin'):
if name not in artifacts or st.file_sha(dest / name) != artifacts[name]['sha256']:
return False
report = (dest / 'report.bin').read_bytes()
status, entered = struct.unpack_from('<2Q', report, 0)
thermal = a16_thermal.decode(report[a16_thermal.REPORT_OFFSET:a16_thermal.REPORT_OFFSET + a16_thermal.SIZE])
code = Path(__file__).resolve().parents[2] # the decoders, wherever the run's records are
runner = modules(code)[0]
gpu = load('adreno_counters', code / 'tools/adreno_counters.py')
flow = gpu.decode(report[runner.GPU_FLOW_OFFSET:runner.GPU_FLOW_OFFSET + gpu.SIZE])
return status == (1 << 64) - 28 and entered == 0 and bool(thermal) and flow['status'] == 0
def predecessor_stop(root, prefix, layout):
"""What a parked predecessor left (--after-quiesce): its preserved work locator and the service words of
its preserved context, which must still be the parked stop (cycle.py retire's native-stop criterion),
or, for a predecessor refused by a thermal check before GPU preparation, the never-parked words it preserved.
A predecessor whose loader refused before the guest call parked nothing: None, so admit() requires an
empty locator."""
dest = root / 'build' / prefix
launch = json.loads((dest / 'launch.json').read_text())
if launch['status'] == 'NO_GUEST_CALL' and not (dest / 'context.bin').exists():
return None
services = layout.offset('context', 'services')
words = struct.unpack_from('<8Q', (dest / 'context.bin').read_bytes(), services)
need(words[2] == PARKED_SERVICE_WORD or refused_by_thermal_before_gpu(dest, launch),
f'{prefix}: preserved service word is not a parked stop')
boot = launch['bootinfo']
return dict(prefix=prefix, locator=(dest / 'work-locator.bin').read_bytes(), services_offset=services, words=words,
boot={k: boot[k] for k in ('heap_base', 'image_base', 'stage_generation')})
def admit(proxy, record, generation, runner, predecessor=None):
"""The live preflight: EL2 generation N at its slot, SError guard, no guest, no host exception. A resident
locator is admitted only for --after-quiesce, and only when it is the predecessor's preserved locator and the
work area it names still holds the predecessor's parked service words: every secondary of that run went to
PSCI CPU_OFF before its primary returned (xnuboot.c secondary_collect), so its work area holds no live CPU."""
import monitor_work
info = proxy.bootinfo()
need(info['current_el'] == 2 and info['stage_generation'] == generation,
f'requires EL2 generation {generation}; found {info["stage_generation"]} at EL{info["current_el"]}')
need(info['image_base'] == image_base(generation), 'stage is not in the slot its generation selects')
need(proxy.stage_features() & 1, 'stage has no SError guard')
locator = proxy.readmem(monitor_work.locator_address(info), monitor_work.LOCATOR_SIZE)
if locator not in (bytes(len(locator)), bytes([0xaf]) * len(locator)):
need(predecessor is not None, 'a guest locator already occupies this stage')
need(locator == predecessor['locator'], f'the resident locator is not {predecessor["prefix"]}\'s preserved one')
# The locator binds the q1n1 heap and the stage generation that wrote it: the same q1n1 boot (heap
# unchanged, so no reset since) at the predecessor's generation.
need(predecessor['boot']['heap_base'] == info['heap_base'], f'{predecessor["prefix"]} ran in another q1n1 boot')
work, size = monitor_work.decode_locator(locator, dict(info, **predecessor['boot']),
monitor_work.firmware_free(proxy, info))
at = work + predecessor['services_offset']
raw = proxy.readmem(at, 64)
need(struct.unpack('<8Q', raw) == tuple(predecessor['words']) and proxy.readmem(at, 64) == raw,
f'{predecessor["prefix"]}\'s live service words are not its preserved parked stop')
record.setdefault('predecessor_stop', dict(prefix=predecessor['prefix'], work=hex(work), bytes=size,
locator_sha256=hashlib.sha256(locator).hexdigest(),
service_hex=raw.hex()))
guard = proxy.serror_state()
need(serror_clear(guard) and not proxy.get_exc_count(), 'host exceptions before boot')
return info
def boot(ws, args, dest, qualification):
need(args.qualification_sha256 == sha(dest / 'qualification.json'), 'boot needs the exact qualification SHA256')
need(args.generation and args.generation >= 1, 'boot needs --generation N >= 1 (a chainloaded stage)')
root, device = ws.root, args.device or ws.device
runner, layout, admission = modules(root)
# The build's layout for the private context (st.build_layout): run.py sizes the guest-readable table window
# with runner.FLAGS and the capture retains the whole context. layout stays define-free for the params blocks
# and the predecessor's service words, which sit before every define-dependent field.
runner.FLAGS[:] = qualification['flags']
context = st.build_layout(root, qualification['flags'])
need(qualification.get('context_layout') != 'build-defines' or
qualification['context_bytes'] == context.size('context'), 'the build\'s context size changed since prepare')
gpu_dir = ws.q / 'build/a16-gpu'
out = lambda name: gpu_dir / f'{args.prefix}-{name}.json'
record = dict(schema='snapintosh-installed-fresh-launch-v1', status='PREFLIGHT', started=time.time(),
qualification_sha256=args.qualification_sha256, variant=qualification['variant'],
boot_argument_addition=qualification['boot_argument_addition'],
flags=qualification['flags'], monitor_sha256=qualification['monitor_sha256'],
monitor_bytes=qualification['monitor_bytes'], source_sha256=qualification['source_sha256'],
uploads=[], calls=[], gpu={}, pid=os.getpid())
path = dest / 'launch.json'
write(path, record)
proxy = witness = info = None
gpu_env = dict(os.environ, A16_GPU_FW_DIR=str(root / 'gpu/adreno/gpu-bringup-q1n1/fw'))
try:
admission.other_clients(device)
proxy = runner.connect(device, tries=1)
after = getattr(args, 'after_quiesce', None)
predecessor = predecessor_stop(root, after, layout) if after else None
record['bootinfo'] = first = admit(proxy, record, args.generation, runner, predecessor)
proxy.link.close()
proxy = None
plan = qualification.get('ddr')
if plan:
# agents/dvfs lever 1, before anything of the GPU's: the probe, the DDR vote, the probe again.
record['ddr'] = dict(restore=plan['restore'])
if plan['probe']:
record['ddr']['probe_before'] = bandwidth_probe(root, runner, device, dest, 'before', args.generation,
plan['probe'])
write(path, record)
record['ddr']['attempted'] = True # quiesce sends the restore even after an uncertain vote
write(path, record)
code, step = a16gpu(ws, device, out('ddr'), plan['vote'], gpu_env)
record['ddr']['vote'] = dict(exit=code, stopped=step.get('stopped'),
sha256=st.file_sha(out('ddr')) if out('ddr').is_file() else None,
sent=(step.get('ddr') or {}).get('sent'))
write(path, record)
need(code == 0 and step.get('stopped') is None, f'DDR vote stopped: {step.get("stopped")}')
if plan['probe']:
record['ddr']['probe_after'] = bandwidth_probe(root, runner, device, dest, 'after', args.generation,
plan['probe'])
write(path, record)
llcc = qualification.get('llcc')
if llcc:
# agents/dvfs section 6, still before anything of the GPU's: the LLCC slices, bracketed by the probe.
record['llcc'] = dict(deactivate=llcc['deactivate'])
if llcc['probe'] and not (plan and plan['probe']):
record['llcc']['probe_before'] = bandwidth_probe(root, runner, device, dest, 'before', args.generation,
llcc['probe'])
record['llcc']['attempted'] = True # quiesce deactivates what the record shows activated
write(path, record)
code, step = a16gpu(ws, device, out('llcc'), llcc['activate'], gpu_env)
slices = ((step.get('llcc') or {}).get('slices') or {})
record['llcc']['activate'] = dict(exit=code, stopped=step.get('stopped'),
sha256=st.file_sha(out('llcc')) if out('llcc').is_file() else None,
version=(step.get('llcc') or {}).get('version'),
banks=(step.get('llcc') or {}).get('banks'),
activated=[n for n, e in slices.items() if e.get('activated')],
skipped=[n for n, e in slices.items() if e.get('skipped')])
write(path, record)
need(code == 0 and step.get('stopped') is None, f'LLCC activation stopped: {step.get("stopped")}')
if llcc['probe']:
record['llcc']['probe_after'] = bandwidth_probe(root, runner, device, dest, 'after-llcc',
args.generation, llcc['probe'])
write(path, record)
# The GPU, read-only, then the established preparation: one client at a time.
code, status = a16gpu(ws, device, out('status'), ['status'], gpu_env)
record['gpu']['status'] = dict(exit=code, record=str(out('status')))
if after:
record['gpu']['after_quiesce'] = previous_quiesce(ws, root, after)
need(code == 0 and (gpu_unprepared(status) or gpu_quiesced_after_use(status)),
'the GPU holds live state; quiesce it gracefully first')
record['gpu']['gate'] = 'cold' if gpu_unprepared(status) else 'after-quiesce'
else:
need(code == 0 and gpu_unprepared(status), 'the GPU is already prepared in this generation; quiesce, cold boot')
record['gpu']['gate'] = 'cold'
write(path, record)
for name, arguments in qualification['gpu_steps']:
code, step = a16gpu(ws, device, out(name), arguments, gpu_env)
record['gpu'][name] = dict(exit=code, stopped=step.get('stopped'), sha256=st.file_sha(out(name)))
write(path, record)
need(code == 0 and step.get('stopped') is None, f'GPU step {name} stopped: {step.get("stopped")}')
if name == 'guard':
need(step['serror_guard']['present'], 'GPU guard step found no SError guard')
adoption = out('adopt').with_name(f'{args.prefix}-adopt-record.json')
va_wide = qualification['environment'].get('SNAP_ADRENO_VA_WIDE') == '1' # --gpu-va-wide's q1n1 half
# --gpu-coherent's q1n1 half: adopt also proves CB0 MAIR0/CBAR read-only (the record is the same).
coherent = ['--coherent'] if GPU_COHERENT_DEFINE in qualification['flags'] else []
code, _ = a16gpu(ws, device, out('adopt'), ['adopt', '--record', str(adoption), *(['--va-wide'] if va_wide else []),
*coherent], gpu_env)
need(code == 0 and adoption.is_file(), 'GPU adoption refused')
record['gpu']['adoption'] = dict(path=str(adoption), sha256=st.file_sha(adoption))
proxy = runner.connect(device, tries=1)
info = admit(proxy, record, args.generation, runner, predecessor)
need({k: info[k] for k in IDENTITY} == {k: first[k] for k in IDENTITY},
'stage identity changed during GPU preparation')
import adreno
adreno.load_adoption(json.loads(adoption.read_text()), info, va_wide)
witness = st.SpeedWitness(proxy, path, record, info['heap_base'] + runner.CODE_AT)
need(info['battery_call'] and info['battery_result'], 'firmware has no live battery helper')
status = witness.call(info['battery_call'], 0, 0, 0, 0, 0, timeout=12)
battery = proxy.live_battery_record(info['battery_result'])
record['battery'] = battery
# The project's battery policy (cycle_lib.common.battery_minimum: pins.json default, SNAP_BATTERY_MINIMUM).
minimum = record['battery_minimum_without_ac'] = battery_minimum(None)
need(status == 0 and battery['plausible'] and (battery['ac'] or battery['percent'] >= minimum),
f'battery preflight refused: below {minimum}% without AC')
record['status'] = 'BOOTING'
write(path, record)
settings = dict(qualification['environment'], SNAP_SMP_REPORT_PREFIX=args.prefix, SNAP_ADRENO_ADOPT=str(adoption),
SNAP_TXM_NATIVE=str(dest / 'image4.bin'), SNAP_STAGE_BUILDS=str(ws.q / 'build/uefi'))
if settings.get('SNAP_QCC2072_WIFI'):
# probes/run.py reads the prepared, hash-pinned inputs (check() re-verified them above).
settings['SNAP_QCC2072_INPUTS'] = str(dest / QCC2072_INPUTS)
record['environment'] = settings
saved = dict(os.environ)
try:
for key in [k for k in os.environ if k.startswith('SNAP_')]:
del os.environ[key]
os.environ.update(settings)
with boot_hooks(root, dest, record), (dest / 'boot.log').open('x') as stream, \
contextlib.redirect_stdout(stream), contextlib.redirect_stderr(stream):
record['exit_code'] = runner.run(witness, (dest / 'xnuboot.bin').read_bytes(), 'xnuboot', device,
dict(guest_profile=qualification['profile'], firmware_dir=str(root / FIRMWARE), console_mode='serial',
root_hash=str(dest / 'root-hash.payload'), auxkc=str(root / selected(args)[1]),
trust_additions=str(root / ADDITIONS), ans_writable=True))
finally:
os.environ.clear()
os.environ.update(saved)
record['exit_name'] = runner.EXIT_NAMES.get(record['exit_code'])
except BaseException as error:
record['error'] = repr(error)
record['status'] = 'FAILED'
raise
finally:
try:
if witness and witness.quarantined:
record['status'] = 'NATIVE_QUARANTINE_NO_FURTHER_TARGET_IO'
elif witness and witness.unknown:
record['status'] = 'UNKNOWN_CALL_OUTCOME_NO_FURTHER_TARGET_IO'
elif witness and witness.control_guard.frozen:
record['status'] = 'CONTROL_FAILURE_NO_FURTHER_TARGET_IO'
elif witness and witness.main_returned:
st.capture(witness, info, runner, context, dest, record)
record['status'] = 'STOPPED_PRESERVED'
if (record.get('environment') or {}).get('SNAP_QCC2072_WIFI'):
record['qcc2072_stop'] = qcc2072_stop(witness)
elif record['status'] != 'FAILED':
record['status'] = 'NO_GUEST_CALL'
except BaseException as error:
record['capture_error'] = repr(error)
record['status'] = 'STOPPED_CAPTURE_FAILED'
raise
finally:
record['finished'] = time.time()
write(path, record)
if proxy:
proxy.link.close()
if record['status'] == 'STOPPED_PRESERVED':
try:
record['summary'] = st.decode(ws, dest, record)
except Exception as error: # an observer gap, never a guest result; the raw bytes are retained
record['decode_error'] = repr(error)
write(path, record)
return record
CHECKSUM_FREEZE = 'link.readmem failed: ProxyError: read data checksum error at '
def host_read_freeze(launch):
"""A control failure whose only unknown is one host-side read (docs/INSTALLED-FRESH-ADMISSION-20261016.md,
v75): the guard froze on a link read checksum error with nothing native in flight, every call returned,
none was the guest call, and the monitor never returned an exit code. Any other freeze stays unknown."""
guard = launch.get('control_guard') or {}
calls = launch.get('calls') or []
return (launch.get('status') == 'CONTROL_FAILURE_NO_FURTHER_TARGET_IO' and guard.get('frozen') is True and
guard.get('native_inflight') is None and str(guard.get('reason', '')).startswith(CHECKSUM_FREEZE) and
all(c.get('returned') is True and c.get('main_guest') is False for c in calls) and
'exit_code' not in launch)
def quiesce(ws, args, dest):
"""After preservation: the graceful GPU quiesce the preparation requires."""
launch = json.loads((dest / 'launch.json').read_text())
# Any GPU write step (everything but the read-only status) leaves the GPU to quiesce,
# including a preparation that stopped partway.
need(any(name != 'status' for name in launch.get('gpu', {})), 'nothing prepared the GPU in this attempt')
need(launch['status'] in ('STOPPED_PRESERVED', 'FAILED', 'NO_GUEST_CALL') or host_read_freeze(launch),
'stop state unknown; recover it first')
root, device = ws.root, args.device or ws.device
runner, _, admission = modules(root)
admission.other_clients(device)
out = ws.q / 'build/a16-gpu' / f'{args.prefix}-quiesce.json'
code, quiet = a16gpu(ws, device, out, ['quiesce', '--write'],
dict(os.environ, A16_GPU_FW_DIR=str(root / 'gpu/adreno/gpu-bringup-q1n1/fw')))
need(code == 0 and quiet.get('quiesce') == 'graceful' and not quiet.get('stopped'), 'quiescence was not graceful')
need(serror_clear(quiet['serror_guard']['after']), 'SError after quiescence')
write(dest / 'quiesce.json', dict(path=str(out), sha256=st.file_sha(out), quiesce='graceful'))
if (launch.get('environment') or {}).get('SNAP_QCC2072_WIFI'):
# --qcc2072-wifi: bus mastering off again (boot() did it after preservation; this confirms it).
proxy = runner.connect(device, tries=1)
try:
stopped = qcc2072_stop(proxy)
finally:
proxy.link.close()
write(dest / 'qcc2072-quiesce.json', stopped)
need(stopped.get('bus_master_off') is True, f'QCC2072 bus mastering not confirmed off: {stopped}')
ddr = launch.get('ddr') or {}
if ddr.get('attempted'):
# --ddr-vote: the explicit restore vote (a16gpu_ddr.py: the GPU OPP-1 row); a cold boot resets RPMh anyway.
restore = ws.q / 'build/a16-gpu' / f'{args.prefix}-ddr-restore.json'
code, restored = a16gpu(ws, device, restore, ddr['restore'], dict(os.environ))
write(dest / 'ddr-restore.json', dict(path=str(restore), exit=code,
sha256=st.file_sha(restore) if restore.is_file() else None,
stopped=restored.get('stopped'),
sent=(restored.get('ddr') or {}).get('sent')))
need(code == 0 and restored.get('stopped') is None, f'DDR restore vote stopped: {restored.get("stopped")}')
llcc = launch.get('llcc') or {}
ours = (llcc.get('activate') or {}).get('activated') or []
if llcc.get('attempted') and not ours and (llcc.get('activate') or {}).get('exit') != 0:
# An activation that stopped partway: which slices it left active is in its q1n1 record, not here.
ours = LLCC_SLICES.split(',')
if llcc.get('attempted') and ours:
# --llcc: the slices this boot activated, back to deactivated (after the GPU's quiesce: no GPU traffic).
record = ws.q / 'build/a16-gpu' / f'{args.prefix}-llcc-deactivate.json'
code, done = a16gpu(ws, device, record, [*llcc['deactivate'], '--slices', ','.join(ours)], dict(os.environ))
write(dest / 'llcc-deactivate.json', dict(path=str(record), exit=code, slices=ours,
sha256=st.file_sha(record) if record.is_file() else None,
stopped=done.get('stopped')))
need(code == 0 and done.get('stopped') is None, f'LLCC deactivation stopped: {done.get("stopped")}')
return quiet
def main(ws, args):
need(args.prefix and re.fullmatch('[a-z0-9-]+', args.prefix), 'invalid or missing --prefix')
dest = ws.root / 'build' / args.prefix
sys.path.insert(0, str(ws.root / 'probes'))
if args.prepare:
prepare(ws, args, dest)
if args.prepare or args.check or args.boot:
record = check(ws, args, dest)
if args.boot or args.quiesce:
import fcntl
with Path('/tmp/snapintosh-hardware.lock').open('a') as lock:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
if args.boot:
result = boot(ws, args, dest, record)
print(json.dumps({k: result.get(k) for k in ('status', 'exit_code', 'exit_name', 'error',
'capture_error', 'decode_error', 'preservation_sha256')},
indent=2))
return 0 if result.get('status') == 'STOPPED_PRESERVED' else 1
quiet = quiesce(ws, args, dest)
print(json.dumps(dict(status='GPU_QUIESCED', quiesce=quiet.get('quiesce')), indent=2))
return 0
print(json.dumps(dict(status='OFFLINE_READY_NOT_BOOTED', variant=record['variant'],
qualification=str(dest / 'qualification.json'),
sha256=sha(dest / 'qualification.json'), hardware_access=False), indent=2))
return 0