#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # # client_test.py - external producer/consumer check for the CUBB v1 transport. # # Simulates what vgsp.ko will do: write a CUBB job record into the job file, # wait for the daemon to update the done file with the matching seq, then # verify the fields. Uses a fake guest-RAM file (never /dev/shm/vgpu-guest-ram). # # python3 client_test.py import array import os import struct import subprocess import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) BIN = os.path.join(HERE, "cuda_bridge") DIR = os.environ.get("CUBB_TEST_DIR", "/tmp/opencode/cuda-bridge-test") RAM = os.path.join(DIR, "transport-ram.bin") JOB = os.path.join(DIR, "transport-job.bin") DONE = os.path.join(DIR, "transport-done.bin") LOG = os.path.join(DIR, "transport-daemon.log") N = 1 << 20 MASK = 0xFFFFFFFF JOB_FMT = "<4sIQ10I9Q" DONE_FMT = "<4sIQIIQQII64sQII" assert struct.calcsize(JOB_FMT) == 128 assert struct.calcsize(DONE_FMT) == 128 def job_record(seq, opcode, n, mail_out_gpa=0, a_gpa=0, b_gpa=0, c_gpa=0): buffer_bytes = n * 4 mail_bytes = n if opcode == 1 else 0 return struct.pack( JOB_FMT, b"CUBB", 1, seq, opcode, (n + 255) // 256 if opcode != 1 else 1, 1, 1, 256, 1, 1, 0, n, 0, mail_out_gpa, mail_bytes, a_gpa, buffer_bytes, b_gpa, buffer_bytes, c_gpa, buffer_bytes, 0, ) def parse_done(data): ( magic, version, seq, status, bad_count, checksum_bits, kernel_ns, name_len, opcode, name, host_ns, err_code, _reserved, ) = struct.unpack(DONE_FMT, data) checksum = struct.unpack(" 0 ) if not ok: fails += 1 print( "TRANSPORT seq=%d op=%d status=%d bad=%d kernel_ns=%d " "checksum=%.6f device=\"%s\" file_bad=%d %s" % ( d["seq"], d["opcode"], d["status"], d["bad_count"], d["kernel_ns"], d["checksum"], d["device"], bad, "PASS" if ok else "FAIL", ) ) write_job(job_record(1001, 1, len(b"the little gpu man says you've got mail"), mail_out_gpa=0x40000000)) data = wait_done(1001) want = b"the little gpu man says you've got mail" if data is None: print("TRANSPORT seq=1001 mail: no done record") fails += 1 else: d = parse_done(data) with open(RAM, "rb") as fh: fh.seek(0x40000000) got = fh.read(len(want)) ok = d["status"] == 0 and d["bad_count"] == 0 and got == want if not ok: fails += 1 print( "TRANSPORT seq=%d op=%d status=%d bad=%d mail=\"%s\" %s" % ( d["seq"], d["opcode"], d["status"], d["bad_count"], got.decode(), "PASS" if ok else "FAIL", ) ) finally: daemon.terminate() try: daemon.wait(timeout=10) except subprocess.TimeoutExpired: daemon.kill() daemon.wait() print("TRANSPORT %s" % ("PASS" if fails == 0 else "FAIL")) return 0 if fails == 0 else 1 if __name__ == "__main__": sys.exit(main())