summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 12:22:00 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 12:22:00 -0500
commit17dcf049aca7b5a65d68389a110dc87d915a5393 (patch)
treeceb8c798af26b9d5e6d7d3d9655f0f475465fe98 /tools
parenta3d801e394a1c57833c816edc3d3cee22dc4a78a (diff)
downloadopen-iov-17dcf049aca7b5a65d68389a110dc87d915a5393.tar.gz
open-iov-17dcf049aca7b5a65d68389a110dc87d915a5393.zip
vgsp-gr: re-target the captured render surface per launch so a partial VA-colliding allocation cannot receive the draw
The SURF-PRELUDE target pair was patched once per capture generation and then reused; the re-patch test was dead because gsp_sema_gr_surf_recs() only fills in the VA when it returns true and the code negated the call, so 'want' could never be computed. A later draw whose ~10-page SYS allocation sits at the same 0x1204xxxxx VA as the X server's 1280x720 screen inherited the X server's 900-page target; the engine's RT pair then named the client's command/operand buffer, which is why the 'surface' held method words and copy-out saw no engine pixels. Add gsp_phys_gr_surf_launch_target(): only a complete contiguous rec run for the captured surface may use its data-rec bounce lane; a partial run falls back to the boot-time contiguous alias built from the captured bind's VAS pages (the real screen HPAs). Re-patch whenever the cached pair differs from that effective target (SURF-PRELUDE retarget); RT0 copies the same words, so the RT0==RT1 design is unchanged. r1490 (VP_MODE=6 RT0=1, r1476-args): 15 launches, poison=0, retarget have=0xa000000 want=0x15e0000 for the partial lane then 900-page targets, output retirement up to 3/900 with 2 copy-out pages changed by engine, and a non-empty 3.69 MB xwd. New wall: the draw still produces no raster fragments - the only changed pages are the forced MME constant-buffer pages inside the screen (offset pages 768/772/773, 0xff000000 fills). Also harden tools/guest-diag/qga_fetch.py (retry open, long timeout, atomic .part write, clear error exit) for evidence capture.
Diffstat (limited to 'tools')
-rwxr-xr-xtools/guest-diag/qga_fetch.py94
1 files changed, 75 insertions, 19 deletions
diff --git a/tools/guest-diag/qga_fetch.py b/tools/guest-diag/qga_fetch.py
index 5a940fc..2fd53fc 100755
--- a/tools/guest-diag/qga_fetch.py
+++ b/tools/guest-diag/qga_fetch.py
@@ -2,15 +2,29 @@
# Fetch a file from the guest via the QEMU guest agent protocol
# (/tmp/qemu-guest-agent.sock, the virtio-serial QGA chardev - the same
# socket vgpu_vm.py exec uses). Usage: qga_fetch.py <guest_path> <host_path> [chunk]
+#
+# Robustness (r1490): the r1489 evidence capture fetched nothing because the
+# guest-side frame was still being written / the agent answered late. Retry
+# the open a few times, tolerate error replies with a clear diagnostic, use a
+# long socket timeout, and write to a temporary file so a partial transfer is
+# never mistaken for a complete capture.
import base64
import json
+import os
import socket
import sys
+import time
+
+if len(sys.argv) < 3:
+ print("usage: qga_fetch.py <guest_path> <host_path> [chunk]", file=sys.stderr)
+ sys.exit(2)
GUEST = sys.argv[1]
HOST = sys.argv[2]
CHUNK = int(sys.argv[3]) if len(sys.argv) > 3 else 1 << 20
SOCK = "/tmp/qemu-guest-agent.sock"
+TRIES = 6
+TMP = HOST + ".part"
def recv_line(s):
@@ -29,25 +43,67 @@ def cmd(s, obj):
return json.loads(line)
-s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-s.settimeout(30.0)
-s.connect(SOCK)
-r = cmd(s, {"execute": "guest-file-open",
- "arguments": {"path": GUEST, "mode": "r"}})
-h = r["return"]
+def open_guest():
+ last = None
+ for i in range(TRIES):
+ try:
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.settimeout(120.0)
+ s.connect(SOCK)
+ r = cmd(s, {"execute": "guest-file-open",
+ "arguments": {"path": GUEST, "mode": "r"}})
+ if "return" in r:
+ return s, r["return"], None
+ last = f"guest-file-open error: {r.get('error')}"
+ except Exception as e: # noqa: BLE001 - diagnostic surface
+ last = f"guest-file-open exception: {e}"
+ try:
+ s.close()
+ except Exception:
+ pass
+ time.sleep(1.5)
+ if i + 1 < TRIES:
+ print(f"retry {i + 1}/{TRIES}: {last}", file=sys.stderr)
+ return None, None, last
+
+
+s, h, err = open_guest()
+if s is None:
+ print(f"FETCH-FAIL {GUEST} -> {HOST}: {err}", file=sys.stderr)
+ sys.exit(1)
+
print(f"handle={h}")
total = 0
-with open(HOST, "wb") as out:
- while True:
- r = cmd(s, {"execute": "guest-file-read",
- "arguments": {"handle": h, "count": CHUNK}})
- d = r["return"]
- buf = base64.b64decode(d.get("buf-b64", ""))
- if buf:
- out.write(buf)
- total += len(buf)
- print(f"\r{total}", end="", flush=True)
- if d.get("eof"):
- break
-cmd(s, {"execute": "guest-file-close", "arguments": {"handle": h}})
+try:
+ with open(TMP, "wb") as out:
+ while True:
+ r = cmd(s, {"execute": "guest-file-read",
+ "arguments": {"handle": h, "count": CHUNK}})
+ if "return" not in r:
+ raise RuntimeError(f"guest-file-read error: {r.get('error')}")
+ d = r["return"]
+ buf = base64.b64decode(d.get("buf-b64", ""))
+ if buf:
+ out.write(buf)
+ total += len(buf)
+ print(f"\r{total}", end="", flush=True)
+ if d.get("eof"):
+ break
+ cmd(s, {"execute": "guest-file-close", "arguments": {"handle": h}})
+except Exception as e: # noqa: BLE001
+ print(f"\nFETCH-FAIL {GUEST} -> {HOST}: {e}", file=sys.stderr)
+ try:
+ if os.path.exists(TMP):
+ os.unlink(TMP)
+ except OSError:
+ pass
+ sys.exit(1)
+finally:
+ try:
+ s.close()
+ except Exception:
+ pass
+
+os.replace(TMP, HOST)
print(f"\nFETCHED {total} bytes -> {HOST}")
+sys.exit(0)