Files
UniXNU/scripts/gen-turnip-replay.py
2026-10-19 18:49:31 +08:00

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)))