#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # # bridge_selftest.py - offline self-test of cuda_bridge --bridge (CUBB2/CBD2 v2) # using plain files in place of the vgsp.ko debugfs files. # # Simulates the module side: packs 256-byte CUBB2 job records at the documented # natural-alignment offsets, prepares the in blob, waits for the CBD2 done # record, and checks the out blob: # (a) mail n=39 -> done ok, out bytes exact # (b) vadd n=1<<20 -> out c correct, bad=0, checksum 392344090.500000 # (c) stale seq -> ignored (no re-execution) # (d) invalid magic -> ignored (idle) # (e) bad opcode -> done written with non-zero status # (f) oversized n -> done written with non-zero status (bound) # # python3 bridge_selftest.py [--dir D] [--bin PATH] # # Never touches /dev/shm/vgpu-guest-ram, the guest, QEMU, mdev or vgsp.ko. import argparse import array import os import struct import subprocess import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) MAIL = b"the little gpu man says you've got mail" VADD_CHECKSUM = 392344090.500000 CHECKSUM_TOL = 1e-4 JOB_MAGIC = 0x32424243 # 'CBB2' LE DONE_MAGIC = 0x32444243 # 'CBD2' LE VERSION = 2 JOB_SIZE = 256 DONE_SIZE = 256 ST_OK, ST_BADJOB, ST_CUDA, ST_RAM = 0, 1, 2, 3 def pack_job(seq, opcode, n, in_bytes=0, out_bytes=0, grid=(1, 1, 1), block=(64, 1, 1), shared=0, sem_va=0, sem_payload=0, sem_flags=0, prog_va=0, param_count=2, magic=JOB_MAGIC, version=VERSION): """Pack a CUBB2 job record field-by-field at the frozen v2 offsets.""" b = bytearray(JOB_SIZE) struct.pack_into(" 0 and d["device"]) print('BRIDGE-SELFTEST (a) mail n=%d gpu="%s" kernel_ns=%d ' 'status=%d bad=%d out_bytes=%d mail="%s"' % (n, d["device"], d["kernel_ns"], d["status"], d["bad"], d["out_bytes_written"], got.decode(errors="replace"))) self.check("(a) mail n=39", ok, "out_exact=%s" % ("yes" if got == MAIL else "no")) def case_vadd(self): n = 1 << 20 a = array.array("f", ((i % 1000) * 0.5 for i in range(n))) b = array.array("f", ((i % 997) * 0.25 for i in range(n))) self.write_in(a.tobytes() + b.tobytes()) self.write_job(pack_job(2, 2, n, in_bytes=8 * n, out_bytes=4 * n, grid=((n + 255) // 256, 1, 1), block=(256, 1, 1), param_count=5)) d = self.wait_done(2) if d is None: self.check("(b) vadd n=1<<20", False, "no done record") return with open(self.out, "rb") as fh: c = array.array("f") c.frombytes(fh.read(4 * n)) bad = sum(1 for i in range(n) if c[i] != a[i] + b[i]) checksum = sum(c) delta = abs(checksum - VADD_CHECKSUM) ok = (d["status"] == ST_OK and d["bad"] == 0 and bad == 0 and delta <= CHECKSUM_TOL and d["out_bytes_written"] == 4 * n and abs(d["checksum"] - VADD_CHECKSUM) <= CHECKSUM_TOL and d["kernel_ns"] > 0) print("BRIDGE-SELFTEST (b) vadd n=%d gpu=\"%s\" kernel_ns=%d " "status=%d bad=%d checksum=%.6f delta=%.3e out_bytes=%d" % (n, d["device"], d["kernel_ns"], d["status"], d["bad"], checksum, delta, d["out_bytes_written"])) self.check("(b) vadd n=1<<20", ok, "file_bad=%d" % bad) def case_stale(self): with open(self.out, "rb") as fh: before = fh.read() self.write_job(pack_job(2, 1, 39, in_bytes=0, out_bytes=39, grid=(1, 1, 1), block=(64, 1, 1), param_count=2)) time.sleep(0.6) with open(self.out, "rb") as fh: after = fh.read() lines = [ln for ln in self.log_text().splitlines() if ln.startswith("CUBB2 seq=2 ")] d = None try: with open(self.done, "rb") as fh: d = parse_done(fh.read(DONE_SIZE)) except (OSError, AssertionError): pass ok = (len(lines) == 1 and before == after and d is not None and d["seq"] == 2 and d["status"] == ST_OK) self.check("(c) stale seq=2 ignored", ok, "exec_lines=%d out_unchanged=%s" % (len(lines), "yes" if before == after else "no")) def case_badmagic(self): self.write_job(pack_job(100, 1, 39, in_bytes=0, out_bytes=39, magic=0xDEADBEEF)) time.sleep(0.4) hits = [ln for ln in self.log_text().splitlines() if "seq=100" in ln or "seq=100 " in ln] self.check("(d) invalid magic seq=100 ignored", len(hits) == 0, "exec_lines=%d" % len(hits)) def case_failure(self): self.write_job(pack_job(3, 9, 8, in_bytes=0, out_bytes=8)) d = self.wait_done(3) ok = (d is not None and d["status"] != ST_OK and d["err_code"] == 9) self.check("(e) bad opcode done non-zero", ok, "status=%s err_code=%s" % (d["status"], d["err_code"]) if d else "no done record") def case_bound(self): n = (1 << 26) + 1 self.write_job(pack_job(4, 2, n, in_bytes=8 * n, out_bytes=4 * n)) d = self.wait_done(4) ok = (d is not None and d["status"] != ST_OK) self.check("(f) oversized n bound done non-zero", ok, "status=%s err_code=%s" % (d["status"], d["err_code"]) if d else "no done record") def main(): ap = argparse.ArgumentParser() ap.add_argument("--dir", default="/tmp/opencode/cuda-bridge-bridge-test") ap.add_argument("--bin", default=os.path.join(HERE, "cuda_bridge")) args = ap.parse_args() if not os.path.exists(args.bin): print("bridge_selftest: binary not found: %s (run make first)" % args.bin) return 2 return Harness(args.bin, args.dir).run() if __name__ == "__main__": sys.exit(main())