99 lines
5.1 KiB
Python
99 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Record Turnip's first submission (the plugin's fill) on the host simulation and embed it for a prefix replay on the tablet.
|
|
|
|
The host plugin smoke (scripts/check-turnip-darwin-host.py builds it) runs the same Mesa source as the tablet's Turnip against the host
|
|
copy of the kernel stack, with FD_RD_DUMP writing every command-stream buffer. The first submission's buffers (GPU address, size, contents)
|
|
and its single IB are written to build/turnip-smoke/TurnipReplay.h together with the dword offset after every TYPE7 packet, so the plugin
|
|
can submit growing prefixes of the IB and report the first one the CP does not finish. Generated, never committed."""
|
|
import glob
|
|
import gzip
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
HOST = ROOT / 'build/turnip-darwin-host'
|
|
OUT = ROOT / 'build/turnip-replay'
|
|
HEADER = ROOT / 'build/turnip-smoke/TurnipReplay.h'
|
|
RD_GPUADDR, RD_BUFFER_CONTENTS, RD_CMDSTREAM_ADDR = 3, 12, 6
|
|
|
|
if not (HOST / 'plugin_smoke').exists():
|
|
subprocess.run([sys.executable, ROOT / 'scripts/check-turnip-darwin-host.py'], check=True)
|
|
shutil.rmtree(OUT, ignore_errors=True)
|
|
OUT.mkdir(parents=True)
|
|
env = dict(os.environ, DYLD_INSERT_LIBRARIES=str(HOST / 'libhostiokit.dylib'), MESA_LOG='', TU_DEBUG='nocb', FD_RD_DUMP='enable', HOSTSIM_PAGE_SIZE='4096',
|
|
FD_RD_DUMP_PATH=str(OUT))
|
|
env.pop('TU_DARWIN_SERVICE', None)
|
|
subprocess.run([str(HOST / 'plugin_smoke'), str(ROOT / 'build/mesa-tu/src/freedreno/vulkan/libvulkan_freedreno.dylib')], env=env,
|
|
check=True, capture_output=True, timeout=300)
|
|
first = sorted(glob.glob(str(OUT / '*submit00000.rd')))
|
|
if not first:
|
|
raise SystemExit('no rd dump of the first submission')
|
|
data = gzip.decompress(Path(first[0]).read_bytes())
|
|
buffers, streams, at = [], [], 0
|
|
while at + 8 <= len(data):
|
|
kind, size = struct.unpack_from('<II', data, at)
|
|
body = data[at + 8:at + 8 + size]
|
|
at += 8 + size
|
|
if kind == RD_GPUADDR:
|
|
lo, length, hi = struct.unpack_from('<III', body)
|
|
buffers.append([(hi << 32) | lo, length, b''])
|
|
elif kind == RD_BUFFER_CONTENTS and buffers:
|
|
buffers[-1][2] = body
|
|
elif kind == RD_CMDSTREAM_ADDR:
|
|
lo, dwords, hi = struct.unpack_from('<III', body)
|
|
streams.append(((hi << 32) | lo, dwords))
|
|
if len(streams) != 1:
|
|
raise SystemExit('expected one IB in the first submission, found %d' % len(streams))
|
|
ib, ib_dwords = streams[0]
|
|
owner = next((b for b in buffers if b[0] <= ib < b[0] + b[1] and b[2]), None)
|
|
if not owner:
|
|
raise SystemExit('the IB buffer has no recorded contents')
|
|
words = struct.unpack_from('<%dI' % ib_dwords, owner[2], ib - owner[0])
|
|
# CP_COND_EXEC (0x44) and CP_COND_REG_EXEC (0x47) own the DWORDS that follow them (the low 24 bits of their last payload word,
|
|
# adreno_pm4.xml): a cut inside that block leaves the thread that skips it waiting for dwords past the end of the IB (int-061 stopped
|
|
# that way on a BR-only CP_COND_REG_EXEC, with BR waiting for the BV timestamp), so no cut is placed before the block ends.
|
|
COND_EXEC, COND_REG_EXEC = 0x44, 0x47
|
|
cuts, ops, k, covered = [], [], 0, 0
|
|
while k < len(words):
|
|
header = words[k]
|
|
if header >> 28 == 7:
|
|
count = header & 0x3fff
|
|
opcode = (header >> 16) & 0x7f
|
|
k += 1 + count
|
|
if opcode in (COND_EXEC, COND_REG_EXEC) and count:
|
|
covered = max(covered, k + (words[k - 1] & 0xffffff))
|
|
if k >= covered:
|
|
cuts.append(k)
|
|
ops.append(opcode)
|
|
elif header >> 28 == 4:
|
|
k += 1 + (header & 0x7f)
|
|
else:
|
|
raise SystemExit('unknown packet header %08x at dword %d' % (header, k))
|
|
if cuts[-1] != len(words):
|
|
cuts.append(len(words))
|
|
ops.append(0)
|
|
|
|
lines = ['// Generated by scripts/gen-turnip-replay.py from a host rd dump of Turnip\'s first submission. Not committed.', '#pragma once',
|
|
'#include <cstdint>', 'namespace turnipreplay {',
|
|
'struct Buffer {uint64_t iova; uint64_t bytes; uint32_t words; const uint32_t *data;};']
|
|
for index, (iova, length, contents) in enumerate(buffers):
|
|
used = len(contents.rstrip(b'\0'))
|
|
used = (used + 3) // 4
|
|
values = struct.unpack_from('<%dI' % used, contents) if used else ()
|
|
lines.append('static const uint32_t kData%d[] = {%s};' % (index, ','.join('0x%x' % v for v in values) or '0'))
|
|
lines.append('static const Buffer kBuffers[] = {%s};' % ','.join(
|
|
'{0x%xull, 0x%xull, %d, kData%d}' % (iova, (length + 4095) & ~4095, (len(contents.rstrip(b'\0')) + 3) // 4, i)
|
|
for i, (iova, length, contents) in enumerate(buffers)))
|
|
lines.append('static const uint64_t kIbIova = 0x%xull;' % ib)
|
|
lines.append('static const uint32_t kCuts[] = {%s};' % ','.join(map(str, cuts)))
|
|
lines.append('static const uint8_t kCutOps[] = {%s};' % ','.join('0x%x' % o for o in ops))
|
|
lines.append('} // namespace turnipreplay')
|
|
HEADER.parent.mkdir(parents=True, exist_ok=True)
|
|
HEADER.write_text('\n'.join(lines) + '\n')
|
|
print('PASS: Turnip first submission: %d buffers, IB 0x%x (%d dwords), %d prefixes -> %s' % (len(buffers), ib, len(words), len(cuts),
|
|
HEADER.relative_to(ROOT)))
|