repo: merge the head rule and IOMFB trace (display-modes)
This commit is contained in:
commit
39a39dba90
7 files changed
+666
-14
No files matched your search
@@ -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;
|
||||
}
|
||||
+1
-1
Submodule gpu/adreno-kext/kext updated: 79de60eaf1...c1b4ad57a9.
@@ -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 = {}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user