Files
Snapintosh/tools/test-display-timing.py
Acelogic f47befb905 probes: split xnuboot.c into ordered parts
Phase 1 of decomposing the 15,770-line monitor unit: move contiguous
top-level ranges verbatim into probes/xnuboot/*.c, and have
probes/xnuboot.c #include them in their original order. The compiler
still sees one translation unit. Every #include of a header stays in
probes/xnuboot.c, because quoted includes resolve beside the including
file and the monitor build has no -Icompat.

Cuts fall only between top-level declarations; no function, struct,
initializer or conditional is split, and every part has balanced
#if/#endif. Two exceptions are recorded for phase 2:
- monitor_call_impl is 1,318 lines on its own, so
  probes/xnuboot/sptm_dispatch.c is 1,370 lines.
- The 1,894-line #if SNAP_FINE_LOCKS region is split into three
  consecutive identical #if SNAP_FINE_LOCKS blocks (fine_locks_*.c).
  SNAP_FINE_LOCKS is not defined or undefined inside the region, and
  nothing inside it includes a header.
The parts that carry the m1n1-derived SPTM endpoint numbers and return
codes (report_layout.c, sptm_dispatch.c) keep the file's notice.

Tools that read xnuboot.c as text now read the whole unit through
tools/source_text.py read_source(); tools that hash or pin monitor
sources add every part through source_files() (generic over each .c
in their lists, so later splits are covered the same way).

Verification (base 84dd208 vs this commit, clang 23.1.3):
- clang -c, qualification flags (b12v20 qualification.json, no -flto)
  plus -I shadow -I q1n1/platform/uefi: probes/xnuboot.c objects
  byte-identical (cmp). PASS
- clang -c, probes/run.py FLAGS plus the same -I: byte-identical. PASS
- Qualification flags with -flto: llvm-dis output equal; the full IR
  differs only in 468 inline-asm !srcloc source-location cookies. PASS
- probes/run.py build('xnuboot'), every unit linked: xnuboot.bin and
  xnuboot.elf byte-identical with the qualification flags (LTO,
  bin sha256 0d8b9ec3...) and with run.py FLAGS (2b5d5f6b...). PASS
- Reconstruction: expanding the includes and dropping the part headers
  and the duplicated #if/#endif lines reproduces xnuboot.c exactly.
- Host battery (63 commands: the two gcc SPTM suites, test-idregs,
  test-audit-sptm-refs and every tools/test-*.py that reads these
  sources, plus the edited tools), 300 s limit, base vs split:
  no new failure. 76 of 78 outcomes equal; test-audit-sparse and
  test-monitor-perf timed out (NOT RUN) on split under host load and
  on rerun match base (PASS; FAIL with the same record). Per-class
  reruns of the long suites (21 commands) equal on both trees.
  Pre-existing failures (missing private inputs or stale checks) are
  unchanged.
- wc -l: xnuboot.c 918, every part under 1,000 except sptm_dispatch.c
  (1,370).
2026-10-23 04:04:44 -04:00

260 lines
14 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""cycle.py installed-fresh --present-beam, offline: monitor allow-list, loader, selection, image, build.
python3 tools/test-display-timing.py # header, source, loader, selection (seconds)
python3 tools/test-display-timing.py --image # + both guest images (about a minute)
python3 tools/test-display-timing.py --build --qualification QUAL.json # + monitors, define absent/0/1
No hardware. agents/tear-free/FAILURE-MODES.md A1-A5, D3, E1, E3.
Monitor: tools/test-display-timing.c runs compat/display-timing.h and must PASS; weakened copies of the header must
FAIL it. Constants: probes/displaytiming.py, compat/dpu-intf.h and compat/display-timing.h agree, and the page is the
glymur intf_5 derivation. probes/xnuboot.c defaults the define to 0 and touches struct-params-free code only under it.
--image: the --present-resident image and the --present-beam image differ only in the params chunk (exactly the four
appended words), the device tree (exactly the two GPU-node properties) and the boot-arguments page that records the
tree's size; image_checks(--present-beam) passes.
--build: xnuboot with each --qualification's flags, define absent, =0 and =1: absent == =0 == the qualification's
monitor_sha256; =1 differs and fits the code budget.
"""
from pathlib import Path
import argparse
import hashlib
import json
import os
import re
import struct
import subprocess
import sys
import tempfile
import types
import unittest
from source_text import read_source
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'probes'))
sys.path.insert(0, str(ROOT / 'tools'))
import displaytiming as d # noqa: E402
_ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
_ap.add_argument('--image', action='store_true')
_ap.add_argument('--build', action='store_true')
_ap.add_argument('--out')
_ap.add_argument('--qualification', action='append', default=[])
ARGS, REST = _ap.parse_known_args() if __name__ == '__main__' else (_ap.parse_args([]), [])
HEADER = ROOT / 'compat/display-timing.h'
RESIDENT = dict(input=True, input_touchpad=True, input_ptp=True, present_dartpool=True, present_resident=True)
MUTATIONS = (
('no magic check', 'p->magic != DISPLAY_TIMING_MAGIC || ', ''),
('no flags check', ' || p->flags != DISPLAY_TIMING_F_INTF', ''),
('page widened', 'p->intf_end != DPU_INTF_PA + DPU_INTF_PAGE', 'p->intf_end < DPU_INTF_PA + DPU_INTF_PAGE'),
('read-write', 'S2_AP_RO | S2_ATTRS_DEVICE', 'S2_AP_RW | S2_ATTRS_DEVICE'),
('executable', 'S2_ATTRS_DEVICE, S2_XN}', 'S2_ATTRS_DEVICE, 0}'),
)
def compile_c(work, header_dir=None):
out = work / 'test-display-timing'
include = [f'-I{header_dir}'] if header_dir else []
result = subprocess.run(['cc', '-std=c11', '-O1', '-g', '-fsanitize=address,undefined', '-Wall', '-Wextra', '-Werror',
*include, '-Ishadow', '-Icompat', 'tools/test-display-timing.c', '-o', str(out)], cwd=ROOT,
capture_output=True, text=True)
if result.returncode:
raise AssertionError(result.stderr)
return subprocess.run([str(out)], capture_output=True, text=True)
class Monitor(unittest.TestCase):
def test_host_model(self):
with tempfile.TemporaryDirectory() as work:
run = compile_c(Path(work))
self.assertEqual(run.returncode, 0, run.stderr)
self.assertIn('"display_timing_failures":0', run.stdout)
def test_weakened_header_fails(self):
text = HEADER.read_text()
for name, old, new in MUTATIONS:
with self.subTest(name), tempfile.TemporaryDirectory() as work:
self.assertIn(old, text, name)
(Path(work) / 'display-timing.h').write_text(text.replace(old, new, 1))
(Path(work) / 'dpu-intf.h').write_text((ROOT / 'compat/dpu-intf.h').read_text())
self.assertNotEqual(compile_c(Path(work), work).returncode, 0, name)
def test_constants_match_the_loader(self):
c = d.header_constants(ROOT)
self.assertEqual(c['mdp'] + c['first'] + c['stride'] * c['index'], d.INTF_PA)
self.assertEqual((c['abi_property'], c['pa_property']), (d.ABI_PROPERTY, d.PA_PROPERTY))
text = HEADER.read_text()
self.assertIn(f'#define DISPLAY_TIMING_PARAMS_WORDS {d.PARAMS_WORDS}', text)
self.assertEqual(int(re.search(r'#define DISPLAY_TIMING_MAGIC\s+0x([0-9a-f]+)ULL', text).group(1), 16), d.MAGIC)
self.assertEqual(d.INTF_PA, 0x0AE3A000)
self.assertEqual(d.S2_DEVICE_RO, 0x40 | 0x4 | 0x300 | 0x400)
def test_monitor_source(self):
src = read_source(ROOT / 'probes/xnuboot.c')
self.assertIn('#ifndef SNAP_DISPLAY_TIMING\n#define SNAP_DISPLAY_TIMING 0\n#endif', src)
self.assertEqual(src.count('#include "../compat/display-timing.h"'), 1)
self.assertIn('if (rows < 0) return -43;', src)
self.assertIn('#define PARAMS_WORDS 1154', src) # struct params unchanged
for use in ('display_timing_rows(', 'display_timing_window(', 'display_timing_block('):
for at in [m.start() for m in re.finditer(re.escape(use), src)]:
# the closest preprocessor block opened before the use is SNAP_DISPLAY_TIMING's, still open
self.assertLess(src.rfind('#endif', 0, at), src.rfind('#if SNAP_DISPLAY_TIMING', 0, at), use)
class Loader(unittest.TestCase):
def test_rows_and_audit(self):
words = d.params_words(d.SPEC)
self.assertEqual(words, [d.MAGIC, 1, 0x0AE3A000, 0x0AE3B000])
self.assertEqual(d.rows(words), [(0x0AE3A000, 0x0AE3B000)])
self.assertEqual(d.stage2_audit_ranges(words), [(0x0AE3A000, 0x0AE3B000, 0x744, 1 << 54)])
for bad in ([0] * 4, [d.MAGIC, 3, 0x0AE3A000, 0x0AE3B000], [d.MAGIC, 1, 0x0AE3B000, 0x0AE3C000],
[d.MAGIC, 1, 0x0AE3A000, 0x0AE3C000], words[:3]):
self.assertIsNone(d.rows(bad))
self.assertEqual(d.stage2_audit_ranges(bad), [])
self.assertEqual(d.stage2_ranges(None), [])
self.assertEqual(d.stage2_ranges(d.SPEC), [(0x0AE3A000, 0x0AE3B000, 'ro',
'DPU INTF_5 timing page (Device-nGnRE, read-only)')])
def test_advertise(self):
import adreno
class Tree:
def __init__(self):
self.nodes = {adreno.GPU_NODE: {}}
def add(self, path, key, value):
self.nodes[path][key] = value
t = Tree()
d.advertise(t, d.SPEC, [(0x80000000, 1 << 30)])
self.assertEqual(t.nodes[adreno.GPU_NODE], {d.ABI_PROPERTY: struct.pack('<I', 1),
d.PA_PROPERTY: struct.pack('<Q', 0x0AE3A000)})
with self.assertRaises(ValueError):
d.advertise(t, d.SPEC) # twice
with self.assertRaises(ValueError):
d.advertise(Tree(), d.SPEC, [(0x0AE00000, 0x100000)]) # overlapping owned memory
t2 = Tree(); t2.nodes = {}
with self.assertRaises(ValueError):
d.advertise(t2, d.SPEC) # no GPU node
for name in (d.ABI_PROPERTY, d.PA_PROPERTY):
self.assertLess(len(name), 32) # Apple's property-name field
def test_xnuboot_hooks(self):
src = (ROOT / 'probes/xnuboot.py').read_text()
self.assertIn('DISPLAY_TIMING = None', src)
self.assertIn('ranges += displaytiming.stage2_ranges(DISPLAY_TIMING)', src)
self.assertIn('words += displaytiming.params_words(DISPLAY_TIMING)', src)
self.assertIn('(PARAMS_WORDS + 6 + 12 + 4) * 8', src)
self.assertIn("xb.DISPLAY_TIMING = displaytiming.SPEC", (ROOT / 'probes/run.py').read_text())
def test_installed_fresh_selection(self):
from cycle_lib import installed_fresh as f
sets = dict(fast=['-DX=1'], control=['-DX=1'])
beam = types.SimpleNamespace(variant='fast', present_beam=True, **RESIDENT)
plain = types.SimpleNamespace(variant='fast', **RESIDENT)
self.assertEqual(f.variant_flags(sets, beam)[-1], d.DEFINE)
self.assertNotIn(d.DEFINE, f.variant_flags(sets, plain))
self.assertEqual(f.DISPLAY_TIMING_DEFINE, d.DEFINE)
self.assertEqual(f.environment(beam)[d.ENV], '1')
self.assertNotIn(d.ENV, f.environment(plain))
with self.assertRaisesRegex(BaseException, 'not pinned'): # refused until its AuxKC is linked and pinned
f.selected(beam)
with self.assertRaisesRegex(BaseException, 'needs --present-resident'):
f.selected(types.SimpleNamespace(variant='fast', present_beam=True, input=True, input_ptp=True,
present_dartpool=True, input_touchpad=True))
saved = f.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA
try:
f.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA = '0' * 64
self.assertEqual(f.selected(beam)[1:3], (f.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM, '0' * 64))
self.assertNotEqual(f.selected(plain)[1], f.selected(beam)[1])
finally:
f.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_SHA = saved
@unittest.skipUnless(ARGS.image, 'pass --image to build the guest images')
class Image(unittest.TestCase):
@classmethod
def setUpClass(cls):
os.environ['SNAP_SEED'] = 'test-display-timing'
from cycle_lib import installed_fresh as f
from cycle_lib.bootstrap_smoke import load
cls.f = f
cls.layout = load('dt_layout', ROOT / 'tools/monitor_layout.py').MonitorLayout()
cls.work = Path(ARGS.out) / 'images' if ARGS.out else Path(tempfile.mkdtemp(prefix='display-timing-image-'))
cls.work.mkdir(parents=True, exist_ok=True)
_, payload = f.root_hash_payload(ROOT)
(cls.work / 'root-hash.payload').write_bytes(payload)
def image(self, name, **kwargs):
dest = self.work / name
dest.mkdir()
args = types.SimpleNamespace(**kwargs)
saved = self.f.selected
resident = self.f.route_selected(types.SimpleNamespace(**RESIDENT))
self.f.selected = lambda a: resident # the resident collection for both images
try:
record = self.f.image_checks(ROOT, dest, self.layout, self.work / 'root-hash.payload', args=args)
finally:
self.f.selected = saved
(dest / 'image.json').write_text(json.dumps(record, indent=1, default=str) + '\n')
return record, (dest / 'device-tree.bin').read_bytes()
def test_images(self):
from appledt import DeviceTree
import adreno
base, tree0 = self.image('resident', **RESIDENT)
self.assertNotIn('display_timing', base)
beam, tree1 = self.image('resident-beam', present_beam=True, **RESIDENT)
self.assertEqual(beam['display_timing']['stage2'], ['0xae3a000', '0xae3b000', 'ro'])
chunks0 = {c['address']: c for c in base['chunks']}
chunks1 = {c['address']: c for c in beam['chunks']}
self.assertEqual(set(chunks0), set(chunks1))
differing = sorted(a for a in chunks0 if chunks0[a] != chunks1[a])
params = [a for a in differing if chunks1[a]['bytes'] == chunks0[a]['bytes'] + 8 * d.PARAMS_WORDS]
self.assertEqual(len(params), 1, differing)
self.assertLessEqual(len(differing), 3, differing) # params, device tree, boot-arguments page
dt0, dt1 = DeviceTree(tree0), DeviceTree(tree1)
self.assertEqual(set(dt0.nodes), set(dt1.nodes))
for path, props in dt0.nodes.items():
extra = set(dt1.nodes[path]) - set(props)
self.assertEqual(extra, {d.ABI_PROPERTY, d.PA_PROPERTY} if path == adreno.GPU_NODE else set(), path)
for key, prop in props.items():
self.assertEqual(bytes(dt1.nodes[path][key].value), bytes(prop.value), f'{path} {key}')
(self.work / 'result.json').write_text(json.dumps(dict(differing_chunks=differing, params_chunk=params,
display_timing=beam['display_timing']), indent=1) + '\n')
@unittest.skipUnless(ARGS.build, 'pass --build to compile the monitor (minutes)')
class Build(unittest.TestCase):
def test_monitor_builds(self):
sys.path.insert(0, str(ROOT.parent / 'q1n1' / 'tools'))
import run
out = Path(ARGS.out) / 'builds' if ARGS.out else Path(tempfile.mkdtemp(prefix='display-timing-build-'))
budget = run.OUT_AT - run.CODE_AT
saved = list(run.FLAGS)
results = {}
try:
for path in ARGS.qualification:
record = json.loads(Path(path).read_text())
name, blobs = Path(path).parent.name, {}
for label, extra in (('default', []), ('off', ['-DSNAP_DISPLAY_TIMING=0']), ('on', [d.DEFINE])):
run.FLAGS[:] = record['flags'] + extra
blob = blobs[label] = run.build('xnuboot', out / f'{name}-{label}')
results[f'{name}-{label}'] = dict(bytes=len(blob), headroom=budget - len(blob),
sha256=hashlib.sha256(blob).hexdigest())
self.assertGreater(budget - len(blob), 0)
self.assertEqual(blobs['default'], blobs['off'], name)
self.assertNotEqual(blobs['default'], blobs['on'], name)
self.assertEqual(results[f'{name}-default']['sha256'], record['monitor_sha256'], name)
finally:
run.FLAGS[:] = saved
(out / 'result.json').write_text(json.dumps(results, indent=1) + '\n')
print(json.dumps(results, indent=1), file=sys.stderr)
if __name__ == '__main__':
os.chdir(ROOT)
unittest.main(argv=[sys.argv[0], *REST], verbosity=2)