repo: merge the head rule and IOMFB trace (display-modes)

This commit is contained in:
Acelogic committed 2026-10-24 03:45:45 -04:00
commit 39a39dba90
7 files changed
+666 -14

No files matched your search

+118 -1
View File
@@ -20,7 +20,10 @@
* holds the whole page, and native DMA maps whole pages too;
* cache nothing past the line that holds the descriptor's last byte; that line, when partial, is cleaned and
* invalidated (TO_DEVICE, dc civac) instead of invalidated (FROM_DEVICE, dc ivac), which would discard
* CPU data the client keeps beside the buffer in the same line. */
* CPU data the client keeps beside the buffer in the same line.
*
* The head rule (-DADRENO_RESIDENT_HEAD_OFFSET=1 with ADRENO_RESIDENT_REPIN, below): the same arithmetic for a
* descriptor that starts `head` bytes into its first page, which the tail rule leaves refused. */
#pragma once
#include <stdint.h>
#include "adreno-resource.h"
@@ -91,3 +94,117 @@ static inline int adreno_tail_cache(const struct adreno_resource *r, uint64_t by
}
return 0;
}
/* ---- the head rule: a descriptor that starts inside its first page (-DADRENO_RESIDENT_HEAD_OFFSET=1) ----
*
* Why: withClientBuffer sizes the memory as round_page(page offset + length) (above), so a client buffer at a
* page-unaligned address gets a map that reaches one page past round_page(length). The tail rule refuses it: the
* shortfall is more than a page, and the first segment does not start a page. OBSERVED twice on the A16 (b12v23 and
* org2, 26A428), at the onset of the glitch the owner ties to opening System Settings > Displays: a WindowServer
* buffer with a 16 KiB descriptor under a 32 KiB map is refused -83 (kIOReturnBadArgument), and every later
* WindowServer submission that lists it fails its preparation (-115, about one per frame to the end of both runs),
* so stale framebuffers alternate on the panel (89% of scanout rows rewritten per present instead of 2-5%).
*
* Map offset m holds descriptor byte m - head. The map covers the whole pages the descriptor touches: the descriptor
* is [head, head + bytes) of the map; the bytes before head and after head + bytes are the same task's bytes in the
* same wired pages, reachable by the GPU exactly as native DMA reaches them (a DART maps whole pages too).
* admission head + bytes fits the map, and the map is exactly the pages that range touches;
* segments the first run is moved back to its page's start; a run that reaches the descriptor's end goes on to
* that page's end; a segment that does not sit where the head says is refused (0);
* cache only the lines the descriptor touches; a partial first or last line is cleaned and invalidated;
* ranges a client's buffer offset (IB relocation, FILL32) is inside the descriptor's bytes and lands at
* head + offset of the map.
* head 0 is the tail rule: the functions below give exactly the tail rule's answers (tools/test-adreno-residency.c). */
static inline int adreno_head_admit(uint64_t head, uint64_t bytes, uint64_t logical)
{
if (head >= ADRENO_TAIL_PAGE || !bytes || bytes > UINT64_MAX - 2 * ADRENO_TAIL_PAGE) return 0;
if (!head) return adreno_tail_admit(bytes, logical);
return !(logical & (ADRENO_TAIL_PAGE - 1)) && head + bytes <= logical &&
logical <= adreno_tail_round_page(head + bytes);
}
/* Before the wire, when the head is not known yet: 1 when some head could make `bytes` fit `logical`. */
static inline int adreno_head_fit(uint64_t bytes, uint64_t logical)
{
return bytes && bytes < logical && !(logical & (ADRENO_TAIL_PAGE - 1)) &&
bytes <= UINT64_MAX - 2 * ADRENO_TAIL_PAGE && logical <= adreno_tail_round_page(bytes) + ADRENO_TAIL_PAGE;
}
/* The descriptor offset the physical callback asks for at page-aligned map offset `offset`. */
static inline uint64_t adreno_head_descriptor_offset(uint64_t offset, uint64_t head)
{
return offset ? offset - head : 0;
}
/* The run the core's physical callback reports at page-aligned map offset `offset`, from the descriptor's own segment
* (pa, span) at adreno_head_descriptor_offset(offset, head): its physical start and, in *length, its bytes. 0 refuses. */
static inline uint64_t adreno_head_run(uint64_t offset, uint64_t head, uint64_t bytes, uint64_t pa, uint64_t span,
uint64_t *length)
{
const uint64_t at = adreno_head_descriptor_offset(offset, head), lead = offset ? 0 : head;
*length = 0;
if (!pa || !span || head >= ADRENO_TAIL_PAGE || (offset & (ADRENO_TAIL_PAGE - 1)) || (offset && offset < head) ||
at >= bytes || span > bytes - at || pa < lead || ((pa - lead) & (ADRENO_TAIL_PAGE - 1)))
return 0;
*length = span == bytes - at ? adreno_tail_round_page(head + bytes) - offset : span + lead;
return pa - lead;
}
/* Cache maintenance of [offset, offset + len) of the map (64-byte aligned) for a descriptor of `bytes` at `head`: at
* most three ranges, in order. 0 when nothing of the descriptor is inside. */
static inline unsigned adreno_head_cache_plan(uint64_t offset, uint64_t len, uint64_t head, uint64_t bytes,
unsigned direction, struct adreno_tail_range out[3])
{
const uint64_t first = head & ~(ADRENO_TAIL_LINE - 1); /* the line of the first byte */
const uint64_t lead = (head + ADRENO_TAIL_LINE - 1) & ~(ADRENO_TAIL_LINE - 1); /* the first whole line */
const uint64_t whole = (head + bytes) & ~(ADRENO_TAIL_LINE - 1); /* the end of the whole lines */
const uint64_t touched = (head + bytes + ADRENO_TAIL_LINE - 1) & ~(ADRENO_TAIL_LINE - 1);
const uint64_t start = offset > first ? offset : first;
const uint64_t stop = len > touched || offset > touched - len ? touched : offset + len;
unsigned n = 0;
if (start >= stop) return 0;
if (direction != ADRENO_CACHE_FROM_DEVICE || (lead == first && whole == touched)) {
out[n++] = (struct adreno_tail_range){start, stop - start, direction};
return n;
}
if (lead >= whole) { /* no line is wholly the descriptor's */
out[n++] = (struct adreno_tail_range){start, stop - start, ADRENO_CACHE_TO_DEVICE};
return n;
}
if (start < lead) out[n++] = (struct adreno_tail_range){start, (stop < lead ? stop : lead) - start, ADRENO_CACHE_TO_DEVICE};
const uint64_t a = start > lead ? start : lead, b = stop < whole ? stop : whole;
if (a < b) out[n++] = (struct adreno_tail_range){a, b - a, direction};
const uint64_t c = start > whole ? start : whole;
if (c < stop) out[n++] = (struct adreno_tail_range){c, stop - c, ADRENO_CACHE_TO_DEVICE};
return n;
}
/* adreno_tail_cache with the head: validation first, then the plan in chunks of at most ADRENO_CACHE_MAX_BYTES. */
static inline int adreno_head_cache(const struct adreno_resource *r, uint64_t head, uint64_t bytes, unsigned direction,
uint64_t offset, uint64_t len,
int (*maintain)(void *, uint64_t, uint64_t, unsigned), void *context)
{
int e = adreno_resource_cache(r, 0, 0, 0);
if (e) return e;
if (direction > ADRENO_CACHE_FROM_DEVICE || (direction && !maintain)) return ADRENO_RESOURCE_E_STATE;
if (!len || offset >= r->logical_length || len > r->logical_length - offset) return ADRENO_RESOURCE_E_RANGE;
if (head >= ADRENO_TAIL_PAGE || !bytes || bytes > r->logical_length || head > r->logical_length - bytes)
return ADRENO_RESOURCE_E_RANGE;
if (!direction) return 0;
const uint64_t start = offset & ~(ADRENO_TAIL_LINE - 1);
const uint64_t end = (offset + len + ADRENO_TAIL_LINE - 1) & ~(ADRENO_TAIL_LINE - 1); /* <= logical_length */
struct adreno_tail_range plan[3];
const unsigned n = adreno_head_cache_plan(start, end - start, head, bytes, direction, plan);
for (unsigned i = 0; i < n; i++)
for (uint64_t at = plan[i].offset; at < plan[i].offset + plan[i].bytes;) {
uint64_t chunk = plan[i].offset + plan[i].bytes - at;
if (chunk > ADRENO_CACHE_MAX_BYTES) chunk = ADRENO_CACHE_MAX_BYTES;
if (maintain(context, r->address + at, chunk, plan[i].direction)) return ADRENO_RESOURCE_E_CACHE;
at += chunk;
}
return 0;
}
/* A client's range [offset, offset + len) of a head-offset buffer: 1 and its map offset when it lies inside the
* descriptor's bytes; 0 otherwise (the caller refuses the IB or FILL32 that names it). */
static inline int adreno_head_range(uint64_t head, uint64_t bytes, uint64_t offset, uint64_t len, uint64_t *map_offset)
{
*map_offset = 0;
if (!len || head >= ADRENO_TAIL_PAGE || offset > bytes || len > bytes - offset) return 0;
*map_offset = head + offset;
return 1;
}
+8 -3
View File
@@ -21,7 +21,7 @@
(kernel, IOMobileGraphicsFamily, IOAVFamily, IOSurface, IOGPUFamily).
Writes <out>/iomfb-vtable-check.json (the full per-slot table) and exits 1 on any failure.
Usage: tools/check-iomfb-vtable.py [--out DIR] [--kc PATH] [--kdk PATH] [--no-display]
Usage: tools/check-iomfb-vtable.py [--out DIR] [--kc PATH] [--kdk PATH] [--no-display] [--display-define NAME=V ...]
"""
import argparse
import hashlib
@@ -83,7 +83,7 @@ def sh(cmd, **kw):
return r.stdout
def compile_bundle(out, sources):
def compile_bundle(out, sources, extra=None):
sdk = sh(['xcrun', '--show-sdk-path']).strip()
kh = f'{sdk}/System/Library/Frameworks/Kernel.framework/Headers'
common = ['-target', 'arm64e-apple-macos27.0', '-isysroot', sdk, '-mkernel', '-nostdinc',
@@ -95,6 +95,7 @@ def compile_bundle(out, sources):
for src in sources:
obj = out / (Path(src).stem + '.o')
lang = cxx if str(src).endswith('.cpp') else common
lang = lang + list((extra or {}).get(str(src), ()))
sh(['xcrun', 'clang++' if str(src).endswith('.cpp') else 'clang'] + lang + ['-c', str(src), '-o', str(obj)])
objs.append(str(obj))
binary = out / 'IOMFBCheck'
@@ -198,6 +199,9 @@ def main():
ap.add_argument('--kdk', type=Path, default=gen.DEFAULT_KDK)
ap.add_argument('--kc', type=Path, default=gen.DEFAULT_KC)
ap.add_argument('--no-display', action='store_true', help='check only the stub subclass')
ap.add_argument('--display-define', action='append', default=[], metavar='NAME[=VALUE]',
help='a -D for AdrenoDisplay.cpp only, repeatable (e.g. a profile\'s AdrenoDisplay set, or '
'ADRENO_DISPLAY_TRACE_MODES=1, whose overrides add slots to check)')
args = ap.parse_args()
out = args.out
out.mkdir(parents=True, exist_ok=True)
@@ -217,7 +221,8 @@ def main():
if not args.no_display:
sources.insert(1, KEXT / 'src/AdrenoDisplay.cpp')
classes.append('AdrenoDisplay')
binary_path = compile_bundle(out, sources)
binary_path = compile_bundle(out, sources, {str(KEXT / 'src/AdrenoDisplay.cpp'):
['-D' + d for d in args.display_define]})
binary = gen.MachOKext(binary_path)
results = {}
+41 -1
View File
@@ -36,7 +36,14 @@
* min(255, s + round(u * (255 - a) / 255)) per byte). The PRECHANGE build models the
* 7ec040b4 presentGated, which drops layers 1..3; its cursor run must fail.
*
* `geometry` runs adreno_display_swap_geometry (AdrenoDisplayCopy.h): what a layer-0 swap asks that the 1:1, top-left
* copy does not do, as the kext's -DADRENO_DISPLAY_TRACE_MODES logs it (agents/display-modeset). The cases are the
* panel's own mode, macOS's scaled resolutions of a 2880x1800 panel (1280x800 to 1920x1200 points at 2x: the
* framebuffer is the points' size twice, presented to the full panel), a placed destination, a cropped source,
* negative rectangles and an unknown surface.
*
* test-adreno-display-present selftest
* test-adreno-display-present geometry (prints a JSON line)
* test-adreno-display-present cursor (prints a JSON line)
* test-adreno-display-present surface DUMP.adsf PANEL.bin (prints a JSON line)
*/
@@ -861,14 +868,47 @@ static int surface_mode(const char *dump_path, const char *panel_path)
return e ? 1 : 0;
}
static int geometry_mode(void)
{
enum { S = ADRENO_DISPLAY_GEOMETRY_SCALED, P = ADRENO_DISPLAY_GEOMETRY_PLACED, C = ADRENO_DISPLAY_GEOMETRY_CROPPED,
Z = ADRENO_DISPLAY_GEOMETRY_SIZED };
static const struct { int32_t sx, sy, sw, sh, dx, dy, dw, dh; uint64_t w, h; unsigned want; const char *what; } k[] = {
{0, 0, 2880, 1800, 0, 0, 2880, 1800, 2880, 1800, 0, "native 1440x900 at 2x"},
{0, 0, 2880, 1800, 0, 0, 2880, 1800, 0, 0, 0, "native, surface unknown"},
{0, 0, 2560, 1600, 0, 0, 2880, 1800, 2560, 1600, S | Z, "1280x800 at 2x (larger text)"},
{0, 0, 3360, 2100, 0, 0, 2880, 1800, 3360, 2100, S | Z, "1680x1050 at 2x (more space)"},
{0, 0, 3840, 2400, 0, 0, 2880, 1800, 3840, 2400, S | Z, "1920x1200 at 2x"},
{0, 0, 2048, 1280, 0, 0, 2880, 1800, 2048, 1280, S | Z, "1024x640 at 2x"},
{0, 0, 2880, 1800, 0, 0, 2880, 1800, 3360, 2100, C | Z, "a panel-sized source inside a larger surface"},
{0, 0, 1440, 900, 0, 0, 2880, 1800, 1440, 900, S | Z, "1440x900 at 1x, scaled up by the display"},
{0, 0, 1440, 900, 720, 450, 1440, 900, 1440, 900, P | Z, "unscaled, centred"},
{16, 8, 2880, 1800, 0, 0, 2880, 1800, 2896, 1808, C | Z, "source offset"},
{16, 8, 2880, 1800, 0, 0, 2880, 1800, 2880, 1800, C, "source origin past the surface's top-left"},
{0, 0, -1, 1800, 0, 0, -1, 1800, 2880, 1800, P | C, "negative width"},
{0, 0, 2880, 1800, 0, 0, 2880, 1800, 2880, 1792, C | Z, "surface one row short of its source"},
};
unsigned bad = 0;
for (unsigned i = 0; i < sizeof k / sizeof k[0]; i++) {
const unsigned got = adreno_display_swap_geometry(k[i].sx, k[i].sy, k[i].sw, k[i].sh, k[i].dx, k[i].dy, k[i].dw,
k[i].dh, k[i].w, k[i].h, 2880, 1800);
CHECK(got == k[i].want);
if (got != k[i].want) { bad++; fprintf(stderr, "geometry %s: 0x%x, want 0x%x\n", k[i].what, got, k[i].want); }
}
printf("{\"variant\":\"%s\",\"geometry_cases\":%u,\"failures\":%u}\n", VARIANT,
(unsigned)(sizeof k / sizeof k[0]), bad);
return bad ? 1 : 0;
}
int main(int argc, char **argv)
{
if (argc == 2 && !strcmp(argv[1], "geometry"))
return geometry_mode();
if (argc >= 3 && !strcmp(argv[1], "surface"))
return surface_mode(argv[2], argc > 3 ? argv[3] : NULL);
if (argc == 2 && !strcmp(argv[1], "cursor"))
return cursor_mode();
if (argc != 2 || strcmp(argv[1], "selftest")) {
fprintf(stderr, "usage: %s selftest | cursor | surface DUMP [PANEL]\n", argv[0]);
fprintf(stderr, "usage: %s selftest | geometry | cursor | surface DUMP [PANEL]\n", argv[0]);
return 64;
}
case_full_panel_stale_cache();
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""The IOMFB call trace (-DADRENO_DISPLAY_TRACE_MODES=1, gpu/adreno-kext/kext/src/AdrenoDisplay/trace.cpp) and the
swap-geometry rule it logs (AdrenoDisplayCopy.h adreno_display_swap_geometry). Offline: no hardware.
python3 tools/test-adreno-display-trace.py [-v]
- geometry: tools/test-adreno-display-present.c `geometry` runs the rule over the panel's mode and macOS's scaled
resolutions of a 2880x1800 panel; weakened rules must fail.
- the trace is logging only: every traced IOMFB method forwards its arguments unchanged to IOMobileFramebufferLegacy and
returns that result; every identifier it adds sits behind its define (the define-off kext is section-identical,
checked with tools/macho-sections.py by the change's handoff, not here).
- ABI: tools/check-iomfb-vtable.py with the desktop profile's AdrenoDisplay defines plus the trace: every one of the 483
slots keeps Apple's PAC diversity and binds to the right target, and every IOMFB symbol the overrides call is
exported by the kernelcache. SKIP without the KDK or the J700 kernelcache (SNAP_KERNELCACHE, default
downloads/extracted/j700ap/kernelcache.release.mac17p).
"""
from pathlib import Path
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parent))
from source_text import read_source # noqa: E402
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / 'gpu/adreno-kext/kext/src'
HARNESS = ROOT / 'tools/test-adreno-display-present.c'
COPY = SRC / 'AdrenoDisplayCopy.h'
TRACE = SRC / 'AdrenoDisplay/trace.cpp'
HEADER = SRC / 'AdrenoDisplay.h'
PROFILE = ROOT / 'gpu/adreno-kext/kext/profiles/desktop-nocoherent.sh'
KDK = Path('/Library/Developer/KDKs/KDK_27.0_26A428.kdk')
KC = Path(os.environ.get('SNAP_KERNELCACHE', ROOT / 'downloads/extracted/j700ap/kernelcache.release.mac17p'))
FLAGS = ['-O2', '-Wall', '-Wextra', '-Werror', '-Wno-deprecated-declarations']
DEFINE = 'ADRENO_DISPLAY_TRACE_MODES'
# Each weakens one clause of adreno_display_swap_geometry; the geometry cases must notice.
GEOMETRY_MUTANTS = {
'scaling not seen': (' if (sw != dw || sh != dh)\n', ' if (0)\n'),
'placement judged by origin only': (
' if (dx || dy || dw < 0 || dh < 0 || (uint64_t)dw != panel_w || (uint64_t)dh != panel_h)\n',
' if (dx || dy)\n'),
'source offset ignored': (' if (sx || sy || sw < 0 || sh < 0 ||\n', ' if (sw < 0 || sh < 0 ||\n'),
'surface size ignored': (
' if ((surface_w || surface_h) && (surface_w != panel_w || surface_h != panel_h))\n', ' if (0)\n'),
}
def geometry(header_text=None):
"""Build the present harness (against a replacement AdrenoDisplayCopy.h when given) and run `geometry`."""
with tempfile.TemporaryDirectory() as d:
d = Path(d)
harness = HARNESS
if header_text is not None:
# The harness includes ../gpu/adreno-kext/kext/src/<header>: mirror that layout around a copy.
(d / 'tools').mkdir()
(d / 'gpu/adreno-kext/kext/src').mkdir(parents=True)
harness = d / 'tools' / HARNESS.name
shutil.copy(HARNESS, harness)
for h in SRC.glob('AdrenoDisplay*.h'):
shutil.copy(h, d / 'gpu/adreno-kext/kext/src' / h.name)
(d / 'gpu/adreno-kext/kext/src' / COPY.name).write_text(header_text)
exe = d / 'present'
# A mutant may leave a parameter unused; that must not be why it fails.
extra = ['-Wno-unused-parameter'] if header_text is not None else []
subprocess.run(['xcrun', 'clang', *FLAGS, *extra, str(harness), '-o', str(exe)], check=True,
capture_output=True, timeout=300)
return subprocess.run([str(exe), 'geometry'], capture_output=True, text=True, timeout=120)
def body(text, signature):
start = text.index(signature)
return text[start:text.index('\n}\n', start)]
class Geometry(unittest.TestCase):
def test_cases_pass(self):
r = geometry()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
row = json.loads(r.stdout.strip().splitlines()[-1])
self.assertEqual(row['failures'], 0)
self.assertGreaterEqual(row['geometry_cases'], 12)
def test_weakened_rules_fail(self):
text = COPY.read_text()
for name, (old, new) in GEOMETRY_MUTANTS.items():
with self.subTest(name):
self.assertEqual(text.count(old), 1, name)
r = geometry(text.replace(old, new))
self.assertNotEqual(r.returncode, 0, name)
class TraceIsLoggingOnly(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.trace = TRACE.read_text()
cls.header = HEADER.read_text()
def overrides(self):
block = self.header[self.header.index('#if ' + DEFINE + '\n // The IOMFB call trace'):]
block = block[:block.index('#endif')]
return re.findall(r'virtual IOReturn (\w+)\(', block)
def test_every_traced_method_forwards_and_returns_the_family_result(self):
names = self.overrides()
self.assertGreaterEqual(len(names), 20)
for name in names:
with self.subTest(name):
fn = body(self.trace, f'IOReturn AdrenoDisplay::{name}(')
signature = fn[:fn.index('\n{')]
args = re.findall(r'\b(a[0-9]|properties|owningTask|securityID|type|handler)\b', signature)
call = f'const IOReturn r = IOMobileFramebufferLegacy::{name}({", ".join(args)});'
self.assertIn(call, fn)
self.assertTrue(fn.rstrip().endswith('return r;'), name)
self.assertEqual(fn.count('return'), 2 if name == 'setProperties' else 1, name)
mode = body(self.trace, 'IOReturn AdrenoDisplay::set_digital_out_mode(')
self.assertIn('const IOReturn r = digitalOutMode(colorID, timingID);', mode)
def test_mode_set_body_is_the_untraced_one(self):
modeset = (SRC / 'AdrenoDisplay/modeset.cpp').read_text()
self.assertIn('#if ' + DEFINE + '\nIOReturn AdrenoDisplay::digitalOutMode(unsigned int colorID, unsigned int '
'timingID)', modeset)
self.assertIn('#else\nIOReturn AdrenoDisplay::set_digital_out_mode(unsigned int colorID, unsigned int timingID)'
'\n#endif\n{', modeset)
def test_every_trace_identifier_is_behind_the_define(self):
words = ('traceSwapGeometryGated', 'traceWindowGated', 'digitalOutMode', 'TRACE_CALL', 'gTraceCalls',
'gTraceMark', 'traceCount', 'traceDue', 'adreno_display_swap_geometry')
unit = read_source(SRC / 'AdrenoDisplay.cpp')
for name, text in (('AdrenoDisplay unit', unit), ('AdrenoDisplay.h', self.header)):
inside = []
for line in text.splitlines():
stripped = line.strip()
if stripped.startswith('#if'):
inside.append(DEFINE in stripped)
elif stripped.startswith('#el') and inside:
inside[-1] = DEFINE in stripped and stripped.startswith('#elif')
elif stripped.startswith('#endif'):
inside.pop()
elif any(w in line for w in words) and not stripped.startswith('//'):
self.assertTrue(any(inside), f'{name}: {line}')
def test_the_desktop_profiles_build_the_trace(self):
profile = PROFILE.read_text()
display = re.search(r'^U_AdrenoDisplay="([^"]*)"', profile, re.M).group(1)
self.assertIn('-D' + DEFINE + '=1', display.split())
class VTable(unittest.TestCase):
def test_trace_keeps_apple_slots(self):
if not KDK.is_dir() or not KC.is_file():
self.skipTest(f'needs {KDK} and the J700 kernelcache ({KC}; set SNAP_KERNELCACHE)')
env = dict(os.environ, D=str(PROFILE.parent.parent))
defines = subprocess.run(['sh', '-c', f'. "{PROFILE}"; echo "$U_AdrenoDisplay"'], capture_output=True, text=True,
env=env, check=True).stdout.split()
args = []
for d in [x[2:] for x in defines if x.startswith('-D')]:
args += ['--display-define', d]
self.assertIn(DEFINE + '=1', args)
with tempfile.TemporaryDirectory() as out:
subprocess.run([sys.executable, str(ROOT / 'tools/check-iomfb-vtable.py'), '--kc', str(KC), '--out', out,
*args], capture_output=True, text=True, timeout=1800)
report = json.loads((Path(out) / 'iomfb-vtable-check.json').read_text())
display = report['classes']['AdrenoDisplay']
self.assertEqual((display['slots'], display['errors'], len(display['extra_slots'])), (483, 0, 0))
# The bundle links AdrenoDisplay.o alone, so the profile's references into AdrenoX2.o stay undefined there;
# none of them may be an IOMFB or IOKit symbol the overrides call.
missing = report['undefined_not_exported']
self.assertFalse([m for m in missing if 'IOMobileFramebuffer' in m or 'IOService' in m or 'OSCollection' in m],
missing)
if __name__ == '__main__':
unittest.main()
+209 -8
View File
@@ -69,6 +69,9 @@
#if ADRENO_RESIDENT_REPIN
#include "adreno-resource-tail.h"
#endif
#if ADRENO_RESIDENT_HEAD_OFFSET && !ADRENO_RESIDENT_REPIN
#error "ADRENO_RESIDENT_HEAD_OFFSET needs ADRENO_RESIDENT_REPIN, as in the kext"
#endif
#if !defined(RESOURCE_BASE) && !ADRENO_RESOURCE_POOL
#error "build with -DADRENO_RESOURCE_POOL=1 (both translation units), or -DRESOURCE_BASE against the base core"
#endif
@@ -99,6 +102,7 @@ struct desc {
int live;
unsigned refs, npages, dir, ro_other, wires_ro, wires_rw, our_pins, native;
uint64_t logical;
uint64_t head; /* display-modeset: the first byte's offset in pages[0] (a page-unaligned client) */
uint32_t *pages;
};
struct mem { int live, desc, wired, fb; unsigned prep, idle, nmaps; int maps[3];
@@ -110,6 +114,7 @@ struct map {
int native; /* map+0x10 bit 0: CB0 range held */
int refuse_pin, refusal; /* resident-evict: this map's pins fail; the kext's fRefusal */
uint64_t tail; /* resident-evict: the kext's fBackingBytes */
uint64_t head; /* display-modeset: the kext's fBackingHead */
uint64_t native_addr, native_len;
};
struct counts {
@@ -144,6 +149,8 @@ static struct {
/* faults: insert number (1-based) and how many of its pages install first; one-shot others */
unsigned fault_insert_at, fault_insert_partial, fault_clear, fault_unpin, fault_free, fault_pin;
unsigned window_pages; /* the IODART allocator's pages from the window base (0: all of it) */
int head_off; /* display-modeset: boot-arg adreno_head_offset=0 */
uint64_t head_admitted, head_refused;
#if ADRENO_VA_RECLAIM
struct adreno_va_filler filler;
int use_filler, pool_locked;
@@ -193,19 +200,37 @@ static void free_pages(struct desc *d)
{
for (unsigned i = 0; i < d->npages; i++) { CHECK(M.page_owner[d->pages[i]] == d - M.descs); M.page_owner[d->pages[i]] = -1; }
}
static int new_desc(uint64_t logical, unsigned dir)
/* A descriptor of `logical` bytes whose first byte is `head` bytes into its first page (0: page-aligned). */
static int new_desc_at(uint64_t logical, unsigned dir, uint64_t head)
{
CHECK(M.ndescs < MAX_DESCS);
CHECK(M.ndescs < MAX_DESCS && head < PAGE);
int id = M.ndescs++;
struct desc *d = &M.descs[id];
memset(d, 0, sizeof(*d));
d->live = 1; d->logical = logical; d->dir = dir;
d->npages = (unsigned)((logical + PAGE - 1) / PAGE);
d->live = 1; d->logical = logical; d->dir = dir; d->head = head;
d->npages = (unsigned)((head + logical + PAGE - 1) / PAGE);
d->pages = calloc(d->npages, sizeof(*d->pages));
CHECK(d->pages);
alloc_pages(d, id);
return id;
}
static int new_desc(uint64_t logical, unsigned dir) { return new_desc_at(logical, dir, 0); }
#if ADRENO_RESIDENT_HEAD_OFFSET
/* IOMemoryDescriptor::getPhysicalSegment(at): the physical address of descriptor byte `at` and the bytes physically
* contiguous from it, up to the descriptor's end. */
static uint64_t segment(const struct desc *d, uint64_t at, uint64_t *length)
{
CHECK(at < d->logical);
const uint64_t p = d->head + at;
unsigned i = (unsigned)(p / PAGE), run = 1;
CHECK(i < d->npages);
while (i + run < d->npages && d->pages[i + run] == d->pages[i] + run) run++;
uint64_t bytes = (uint64_t)(i + run) * PAGE - p;
if (bytes > d->logical - at) bytes = d->logical - at;
*length = bytes;
return pa_of(d->pages[i]) + p % PAGE;
}
#endif
static int wired(const struct desc *d) { return d->our_pins || d->native || d->wires_ro || d->wires_rw || d->ro_other; }
static void put_desc(int id)
{
@@ -370,7 +395,12 @@ static int cb_pin(void *ctx)
if (id < 0) return -1;
struct desc *d = &M.descs[id];
if (m->refuse_pin) return -1;
#if ADRENO_RESIDENT_REPIN
#if ADRENO_RESIDENT_HEAD_OFFSET
/* the kext's pinBacking: the head is read from the wired descriptor's first segment */
const int tail = d->logical < m->r.logical_length && adreno_tail_admit(d->logical, m->r.logical_length);
const int headed = d->logical < m->r.logical_length && !M.head_off && adreno_head_fit(d->logical, m->r.logical_length);
if (d->logical < m->r.logical_length && !tail && !headed) return -1;
#elif ADRENO_RESIDENT_REPIN
const int tail = d->logical < m->r.logical_length && adreno_tail_admit(d->logical, m->r.logical_length);
if (d->logical < m->r.logical_length && !tail) return -1;
#else
@@ -382,9 +412,21 @@ static int cb_pin(void *ctx)
dir = DIR_OUT;
if (prepare(d, dir)) return -1;
}
#if ADRENO_RESIDENT_HEAD_OFFSET
uint64_t head = 0;
if (headed) {
uint64_t span = 0;
head = segment(d, 0, &span) & (PAGE - 1);
if (!adreno_head_admit(head, d->logical, m->r.logical_length)) { complete(d, dir); M.head_refused++; return -1; }
if (head) M.head_admitted++;
}
#endif
d->refs++; d->our_pins++;
m->backing = id; m->backing_dir = dir;
#if ADRENO_RESIDENT_REPIN
#if ADRENO_RESIDENT_HEAD_OFFSET
m->tail = tail || headed ? d->logical : 0;
m->head = head;
#elif ADRENO_RESIDENT_REPIN
m->tail = tail ? d->logical : 0;
#endif
M.c.pins++;
@@ -402,7 +444,7 @@ static int cb_unpin(void *ctx)
complete(d, m->backing_dir);
CHECK(d->our_pins); d->our_pins--;
int id = m->backing;
m->backing = -1; m->tail = 0;
m->backing = -1; m->tail = 0; m->head = 0;
put_desc(id);
M.c.unpins++;
return 0;
@@ -413,6 +455,13 @@ static uint64_t cb_physical(void *ctx, uint64_t offset, uint64_t *length)
CHECK(m->backing >= 0); /* only while prepared */
struct desc *d = &M.descs[m->backing];
CHECK(!(offset % PAGE) && offset < (uint64_t)d->npages * PAGE);
#if ADRENO_RESIDENT_HEAD_OFFSET
if (m->head) { /* the kext's physicalPage, head rule */
uint64_t span = 0;
const uint64_t pa = segment(d, adreno_head_descriptor_offset(offset, m->head), &span);
return adreno_head_run(offset, m->head, m->tail, pa, span, length);
}
#endif
unsigned i = (unsigned)(offset / PAGE), run = 1;
while (i + run < d->npages && d->pages[i + run] == d->pages[i] + run) run++;
uint64_t bytes = (uint64_t)run * PAGE;
@@ -865,7 +914,7 @@ static void replace_backing(struct mem *me)
{
if (!me->live || me->wired || me->prep) return;
const struct desc *old = &M.descs[me->desc];
int fresh = new_desc(old->logical, old->dir);
int fresh = new_desc_at(old->logical, old->dir, old->head); /* the client's address keeps its page offset */
M.descs[fresh].refs = 1;
int previous = me->desc;
me->desc = fresh;
@@ -1841,6 +1890,20 @@ static int tail_mem(uint64_t bytes)
new_map(id);
return id;
}
/* display-modeset: a client buffer of `bytes` at a page-unaligned address: its first byte `head` bytes into its first
* page, IOGPU's memory (and map) length round_page(page offset + length). */
static int head_mem(uint64_t bytes, uint64_t head)
{
CHECK(M.nmems < MAX_MEMS);
int id = M.nmems++;
struct mem *me = &M.mems[id];
memset(me, 0, sizeof(*me));
me->live = 1; me->maplen = (head + bytes + PAGE - 1) & ~(PAGE - 1);
me->desc = new_desc_at(bytes, DIR_INOUT, head);
M.descs[me->desc].refs = 1;
new_map(id);
return id;
}
#if ADRENO_RESIDENT_REPIN
struct cache_call { uint64_t va, bytes; unsigned direction; };
static struct cache_call calls[8];
@@ -1946,6 +2009,144 @@ static void repin_scenarios(void)
#endif
check_all(1);
free_mem(s); check_all(1); }
/* 33. org2 and b12v23: WindowServer's 16 KiB buffer at a page-unaligned address under IOGPU's 32 KiB map. Without the
* head rule every wire reports it committed (clean refusal, E_PIN) and every submission that lists it is refused,
* round after round, as the -115 lines of both runs; with it the pin is admitted, the map's two pages are the
* descriptor's two pages, every client byte the GPU reaches through the head-moved offset is the CPU's byte, the
* cache passes touch only the descriptor's lines, and it survives unwire, eviction and a replaced descriptor. */
{ static const uint64_t heads[] = {0x10, 0x40, 0x200, 0x1000, 0x2a0c, 0x3fc0, 0x3ff0};
const uint64_t bytes = 0x4000;
for (unsigned h = 0; h < sizeof heads / sizeof heads[0]; h++) {
const uint64_t head = heads[h];
reset(2, RUNS, 512ULL << 20, 33 + h);
int t = head_mem(bytes, head);
struct mem *me = &M.mems[t];
CHECK(map_of(t)->r.logical_length == 2 * PAGE && M.descs[me->desc].npages == 2);
int list[1] = {t}, writes[1] = {1};
for (int round = 0; round < 3; round++) {
#if ADRENO_RESIDENT_HEAD_OFFSET
CHECK(submit(list, writes, 1));
const struct map *m = map_of(t);
CHECK(admitted(m) && m->head == head && m->tail == bytes && m->r.mapped_length == 2 * PAGE);
me->prep++; check_gpu_view(m, 1);
for (uint64_t k = 0; k < bytes; k += (k < 256 || k > bytes - 256) ? 1 : 61) {
uint64_t at = 0;
CHECK(adreno_head_range(m->head, m->tail, k, 1, &at) && at == head + k); /* resolveRange */
const uint64_t va = m->r.address + at, leaf = M.leaves[leaf_index(va & ~(PAGE - 1))];
const struct desc *d = &M.descs[me->desc];
CHECK((leaf & 1) && (leaf & ~(NO_WRITE | 1ULL)) + va % PAGE ==
pa_of(d->pages[(head + k) / PAGE]) + (head + k) % PAGE); /* GPU byte == CPU byte */
}
uint64_t at = 0;
CHECK(adreno_head_range(head, bytes, bytes - 4, 4, &at) && at == head + bytes - 4);
CHECK(!adreno_head_range(head, bytes, bytes, 4, &at) && !adreno_head_range(head, bytes, bytes - 4, 8, &at) &&
!adreno_head_range(head, bytes, 0, bytes + 1, &at) && !adreno_head_range(head, bytes, 0, 0, &at));
for (unsigned dir = ADRENO_CACHE_TO_DEVICE; dir <= ADRENO_CACHE_FROM_DEVICE; dir++) {
ncalls = 0;
CHECK(!adreno_head_cache(&m->r, m->head, m->tail, dir, 0, m->r.logical_length, record_cache, NULL));
const uint64_t first = m->r.address + (head & ~63ULL), end = m->r.address + ((head + bytes + 63) & ~63ULL);
CHECK(ncalls && calls[0].va == first && calls[ncalls - 1].va + calls[ncalls - 1].bytes == end);
for (unsigned i = 1; i < ncalls; i++) CHECK(calls[i].va == calls[i - 1].va + calls[i - 1].bytes);
for (unsigned i = 0; i < ncalls; i++) {
const uint64_t a = calls[i].va - m->r.address, b = a + calls[i].bytes;
/* invalidate only lines wholly the descriptor's; partial lines are cleaned and invalidated */
if (calls[i].direction == ADRENO_CACHE_FROM_DEVICE) CHECK(a >= head && b <= head + bytes);
else CHECK(calls[i].direction == ADRENO_CACHE_TO_DEVICE);
}
if (dir == ADRENO_CACHE_FROM_DEVICE)
CHECK(ncalls == 1u + !!(head & 63) + !!((head + bytes) & 63) &&
calls[!!(head & 63)].direction == ADRENO_CACHE_FROM_DEVICE);
else CHECK(ncalls == 1 && calls[0].direction == ADRENO_CACHE_TO_DEVICE);
}
me->prep--;
check_all(1);
if (round == 0) native_unwire(me); /* resident */
if (round == 1) { native_unwire(me); replace_backing(me); } /* stale: evicted, re-pinned */
if (round == 2) { M.res.budget = 0; native_unwire(me); } /* released: re-pinned at free */
#elif ADRENO_RESIDENT_REPIN
const uint64_t pins = M.c.pins;
CHECK(!submit(list, writes, 1) && me->wired); /* the memory wires; the map stays unadmitted */
CHECK(!admitted(map_of(t)) && map_of(t)->refusal == ADRENO_RESOURCE_E_PIN && M.c.pins == pins);
check_all(1);
native_unwire(me);
#else
CHECK(!submit(list, writes, 1) && !me->wired && !admitted(map_of(t)));
check_all(1);
#endif
}
#if ADRENO_RESIDENT_HEAD_OFFSET
if (!(M.res.evicted == 1 && M.head_admitted == 2 && !M.head_refused))
fatal(__LINE__, "head %llx: evicted %llu admitted %llu refused %llu", (unsigned long long)head,
(unsigned long long)M.res.evicted, (unsigned long long)M.head_admitted, (unsigned long long)M.head_refused);
#endif
free_mem(t); check_all(1);
}
}
#if ADRENO_RESIDENT_HEAD_OFFSET
/* 34. What the head rule still refuses, and its head-0 equivalence to the tail rule. A map a page longer than the
* pages the descriptor touches, a map too short for head + bytes, and boot-arg adreno_head_offset=0 are refused
* cleanly (nothing pinned, wired once and completed); every submission of them stays refused. */
reset(2, RUNS, 512ULL << 20, 40);
{ const uint64_t bytes = 0x4000, head = 0x1000;
int longer = head_mem(bytes, head), shorter = head_mem(bytes + 0x3000, 0x1100);
M.mems[longer].maplen = 0; /* the model's map length is fixed at creation: */
CHECK(map_of(longer)->r.logical_length == 2 * PAGE);
struct map *lm = map_of(longer);
/* stretch the refused map by one page the way a wrong IOGPU length would: refuse, never map past the pages */
lm->r.logical_length = 3 * PAGE;
int a[1] = {longer}, w[1] = {0};
CHECK(!submit(a, w, 1) && !admitted(lm) && lm->refusal == ADRENO_RESOURCE_E_PIN && lm->backing < 0);
lm->r.logical_length = 2 * PAGE; /* restored: admitted */
CHECK(submit(a, w, 1) && admitted(lm) && lm->head == head);
struct map *sm = map_of(shorter);
sm->r.logical_length = 2 * PAGE; /* 0x1100 + 0x7000 needs 3 pages */
int b[1] = {shorter};
CHECK(!submit(b, w, 1) && !admitted(sm) && sm->backing < 0);
sm->r.logical_length = 3 * PAGE;
CHECK(submit(b, w, 1) && admitted(sm));
check_all(1);
native_unwire(&M.mems[longer]); native_unwire(&M.mems[shorter]);
free_mem(longer); free_mem(shorter); check_all(1);
M.head_off = 1; /* boot-arg adreno_head_offset=0 */
int off = head_mem(bytes, head), c[1] = {off};
CHECK(!submit(c, w, 1) && !admitted(map_of(off)) && map_of(off)->refusal == ADRENO_RESOURCE_E_PIN);
native_unwire(&M.mems[off]); free_mem(off); check_all(1);
M.head_off = 0;
CHECK(M.head_refused == 2); }
/* head 0: admission, runs and cache plans are the tail rule's, over every byte count of a few pages. */
for (uint64_t bytes = 1; bytes <= 3 * PAGE; bytes += bytes < 300 ? 1 : 251)
for (uint64_t logical = PAGE; logical <= 5 * PAGE; logical += PAGE) {
CHECK(adreno_head_admit(0, bytes, logical) == adreno_tail_admit(bytes, logical));
if (!adreno_tail_admit(bytes, logical)) continue;
for (uint64_t off = 0; off < logical; off += PAGE) {
if (off >= bytes) continue;
const uint64_t span = bytes - off < 2 * PAGE ? bytes - off : 2 * PAGE;
uint64_t len = 0;
CHECK(adreno_head_run(off, 0, bytes, PA_BASE + off, span, &len) == PA_BASE + off &&
len == adreno_tail_span(off, span, bytes));
}
for (unsigned dir = 0; dir <= ADRENO_CACHE_FROM_DEVICE; dir++)
for (uint64_t o = 0; o < logical; o += 64 * 37)
for (uint64_t l = 64; o + l <= logical; l += 64 * 53) {
struct adreno_tail_range t[2], h[3];
const unsigned nt = adreno_tail_cache_plan(o, l, bytes, dir, t);
const unsigned nh = adreno_head_cache_plan(o, l, 0, bytes, dir, h);
CHECK(nt == nh);
for (unsigned i = 0; i < nt; i++)
CHECK(t[i].offset == h[i].offset && t[i].bytes == h[i].bytes && t[i].direction == h[i].direction);
}
}
/* A segment that does not sit where the head says is refused page by page (the core then unwinds). */
{ uint64_t len = 1;
CHECK(!adreno_head_run(0, 0x200, 0x4000, PA_BASE + 0x300, 0x3d00, &len) && !len); /* not the head */
CHECK(!adreno_head_run(PAGE, 0x200, 0x4000, PA_BASE + 0x10, 0x210, &len) && !len); /* unaligned page */
CHECK(adreno_head_run(0, 0x200, 0x4000, PA_BASE + 0x200, 0x3e00, &len) == PA_BASE && len == PAGE);
CHECK(adreno_head_run(PAGE, 0x200, 0x4000, PA_BASE + 7 * PAGE, 0x200, &len) == PA_BASE + 7 * PAGE &&
len == PAGE); /* the last page, whole */
CHECK(!adreno_head_admit(0x200, 0x4000, 3 * PAGE) && !adreno_head_admit(0x200, 0x4000, PAGE) &&
adreno_head_admit(0x200, 0x4000, 2 * PAGE) && !adreno_head_admit(PAGE, 0x4000, 2 * PAGE)); }
printf("PASS head\n");
#endif
printf("PASS repin\n");
}
#endif
+109
View File
@@ -657,5 +657,114 @@ class Replays(unittest.TestCase):
self.assertTrue(result.stdout.strip().splitlines()[-1].startswith('PASS'), result.stdout[-500:])
# agents/display-modeset: the head rule (compat/adreno-resource-tail.h, -DADRENO_RESIDENT_HEAD_OFFSET=1 with
# ADRENO_RESIDENT_REPIN). org2 and b12v23: WindowServer's 16 KiB buffer at a page-unaligned address under IOGPU's 32 KiB
# map, refused -83 at the onset of the Displays-pane glitch, then every composite listing it failed (-115).
TAIL = ROOT / 'compat/adreno-resource-tail.h'
REPIN = ['-DADRENO_RESIDENT_REPIN=1']
HEAD = REPIN + ['-DADRENO_RESIDENT_HEAD_OFFSET=1']
# Each weakens one head rule; scenario 33 (org2's buffer) or 34 (refusals, head-0 equivalence) must notice.
HEAD_MUTANTS = {
'admission without the page bound': (
' logical <= adreno_tail_round_page(head + bytes);\n}', ' 1;\n}'),
'first page not moved back to its start': (
'lead = offset ? 0 : head;', 'lead = 0;'),
'last page not mapped whole': (
'*length = span == bytes - at ? adreno_tail_round_page(head + bytes) - offset : span + lead;',
'*length = span + lead;'),
'a misplaced segment accepted': (
'pa < lead || ((pa - lead) & (ADRENO_TAIL_PAGE - 1)))', 'pa < lead)'),
'descriptor offset ignores the head': (
' return offset ? offset - head : 0;', ' return offset;'),
'partial first line only invalidated': (
'(stop < lead ? stop : lead) - start, ADRENO_CACHE_TO_DEVICE};',
'(stop < lead ? stop : lead) - start, direction};'),
'maintenance from the map start, not the first line': (
' const uint64_t start = offset > first ? offset : first;', ' const uint64_t start = offset;'),
'client offset not moved by the head': (' *map_offset = head + offset;', ' *map_offset = offset;'),
'client range bounded by the map, not the descriptor': (
'offset > bytes || len > bytes - offset) return 0;',
'offset > bytes + head || len > bytes + head - offset) return 0;'),
}
def run_repin(directory, extra, header=None):
"""Build the model with `extra` (and a replacement adreno-resource-tail.h when given) and run its re-pin part."""
include = []
if header is not None:
(Path(directory) / 'adreno-resource-tail.h').write_text(header)
include = ['-I' + str(directory)]
exe = Path(directory) / 'test-adreno-residency'
subprocess.run(['cc', *FLAGS, *MUTANT_FLAGS, *extra, *include, '-Icompat', str(HARNESS), str(CORE), '-o', str(exe)],
cwd=ROOT, check=True, timeout=300, capture_output=True)
return subprocess.run([str(exe), 'repin'], capture_output=True, text=True, timeout=600)
class HeadOffset(unittest.TestCase):
def test_org2_buffer_refused_without_the_rule(self):
# The re-pin build alone: scenario 33 asserts the clean refusal (E_PIN) and a refused submission every round.
with tempfile.TemporaryDirectory() as d:
result = run_repin(d, REPIN)
self.assertEqual(result.returncode, 0, result.stdout[-2000:] + result.stderr[-2000:])
self.assertIn('PASS: repin', result.stdout)
self.assertNotIn('PASS head', result.stdout)
def test_org2_buffer_admitted_and_mapped_with_the_rule(self):
with tempfile.TemporaryDirectory() as d:
result = run_repin(d, HEAD)
self.assertEqual(result.returncode, 0, result.stdout[-2000:] + result.stderr[-2000:])
self.assertIn('PASS head', result.stdout)
self.assertIn('PASS: repin', result.stdout)
def test_weakened_head_rules_fail(self):
header = TAIL.read_text()
for name, (old, new) in HEAD_MUTANTS.items():
with self.subTest(name), tempfile.TemporaryDirectory() as d:
self.assertEqual(header.count(old), 1, name)
result = run_repin(d, HEAD, header.replace(old, new))
self.assertNotEqual(result.returncode, 0, name)
self.assertNotIn('PASS: repin', result.stdout, name)
def test_kext_glue_uses_the_rule_behind_its_define(self):
source = read_source(MAP)
pin = between(source, 'int AdrenoMemoryMap::pinBacking(void *ctx)')
self.assertIn('adreno_head_fit(bytes, map->fResource.logical_length)', pin)
self.assertIn('map->admitHead(backing, direction, bytes, &head)', pin)
self.assertLess(pin.index('backing->prepare(direction)'), pin.index('map->admitHead(')) # head known once wired
physical = between(source, 'uint64_t AdrenoMemoryMap::physicalPage(')
self.assertIn('adreno_head_run(offset, map->fBackingHead, map->fBackingBytes, pa, span, length)', physical)
self.assertIn('adreno_head_descriptor_offset(offset, map->fBackingHead)', physical)
admit = between(source, 'bool AdrenoMemoryMap::admitHead(')
self.assertIn('adreno_head_admit(*head, bytes, fResource.logical_length)', admit)
self.assertLess(admit.index('backing->complete(direction)'), admit.index('backing->release()'))
for method in ('int AdrenoMemoryMap::resolveFill(', 'int AdrenoMemoryMap::resolveRange(',
'int AdrenoMemoryMap::cacheRange('):
self.assertIn('headRange(offset,bytes,&at)', between(source, method), method)
self.assertIn('adreno_head_cache(&fResource,fBackingHead,fBackingBytes,direction,0,fResource.logical_length',
between(source, 'int AdrenoMemoryMap::cacheSubmission('))
self.assertIn('map->fBackingHead = 0;', between(source, 'int AdrenoMemoryMap::unpinBacking('))
# Every head-rule identifier sits inside #if ADRENO_RESIDENT_HEAD_OFFSET (the define-off build is unchanged).
words = ('fBackingHead', 'admitHead', 'headRange', 'headOffsetWanted', 'adreno_head_', 'kRepinHead', 'gHead')
for name, text in (('AdrenoMemoryMap', source), ('AdrenoX2.h', (KEXT / 'src/AdrenoX2.h').read_text())):
inside = []
for line in text.splitlines():
stripped = line.strip()
if stripped.startswith('#if'):
inside.append('ADRENO_RESIDENT_HEAD_OFFSET' in stripped)
elif stripped.startswith(('#elif', '#else')) and inside:
inside[-1] = 'ADRENO_RESIDENT_HEAD_OFFSET' in stripped and stripped.startswith('#elif')
elif stripped.startswith('#endif'):
inside.pop()
elif any(word in line for word in words) and not stripped.startswith('//'):
self.assertTrue(any(inside), name + ': ' + line)
def test_desktop_profiles_build_the_rule_in_every_unit(self):
profile = (KEXT / 'profiles/desktop-nocoherent.sh').read_text()
common = re.search(r'^PROFILE_ALL="([^"]*)"', profile, re.M).group(1)
self.assertIn('-DADRENO_RESIDENT_HEAD_OFFSET=1', common.split())
self.assertIn('-DADRENO_RESIDENT_REPIN=1', common.split())
self.assertIn('. "$D/profiles/desktop-nocoherent.sh"', (KEXT / 'profiles/desktop.sh').read_text())
if __name__ == '__main__':
unittest.main(verbosity=2)