summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_phys_booter.c113
-rwxr-xr-xtools/guest-diag/qga_fetch.py94
2 files changed, 162 insertions, 45 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 6f2a858..d6ce2c2 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -25213,6 +25213,64 @@ static struct gsp_phys_gr_surf_keep gsp_phys_gr_surf[4];
static u32 gsp_phys_gr_surf_n;
/*
+ * r1490: the effective engine target of the captured surface ON THIS LAUNCH.
+ * The captured-surface rec set (gsp_sema_gr_surf_recs) is global - it is the
+ * last C597 colour-target bind a trusted channel emitted - while a launch's
+ * own data recs only cover it when the fill resolved the WHOLE span through
+ * the captured bind's VAS. Several clients share the same 0x1200xxxxx VAs:
+ * the beef client holds a ~10-page SYS allocation at 0x120400000, the X
+ * server's 1280x720 screen of the same VA is a 900-page FB allocation. A
+ * partial run must never win the rec-over-alias preference: the engine would
+ * be pointed at the client's code/operand buffer, its stores would land in
+ * (and its copy-in would read) method/SASS words instead of the screen, and
+ * the surface would look like it received the method stream.
+ *
+ * Complete rec run (contiguous base..last reloc VA) -> its bounce base, the
+ * path that lets the retire wait and copy-out see the pixels. Otherwise the
+ * per-boot contiguous alias built from the captured bind's VAS pages is the
+ * target: the engine writes the guest screen HPAs directly, exactly as the
+ * pre-r1483 alias path did. 0 = neither is available (caller keeps the
+ * per-page path).
+ */
+static u64 gsp_phys_gr_surf_launch_target(const struct gsp_phys_gr_submit *req)
+{
+ u64 sva = 0, last_va, base = 0, last = 0;
+ u32 sn = 0, di;
+ bool have_base = false, have_last = false;
+
+ if (!req)
+ return 0;
+ if (gsp_sema_gr_surf_recs(&sva, &sn) && sva && sn >= 2u) {
+ for (di = 0; di < req->n_data; di++) {
+ if (req->data[di].va == sva &&
+ req->data[di].reloc_va) {
+ base = req->data[di].reloc_va;
+ have_base = true;
+ break;
+ }
+ }
+ last_va = sva + ((u64)(sn - 1u) << 12);
+ for (di = 0; di < req->n_data; di++) {
+ if (req->data[di].va == last_va &&
+ req->data[di].reloc_va) {
+ last = req->data[di].reloc_va;
+ have_last = true;
+ break;
+ }
+ }
+ if (have_base && have_last &&
+ last == base + ((u64)(sn - 1u) << 12))
+ return base;
+ }
+ if (gsp_sema_surf_va_get(&sva, &sn)) {
+ for (di = 0; di < gsp_phys_gr_surf_n; di++)
+ if (gsp_phys_gr_surf[di].va == sva)
+ return gsp_phys_gr_surf[di].alias;
+ }
+ return 0;
+}
+
+/*
* SESSION 153-CONTINUED-27: slack tail for the alias. r994 measured the
* replay's colour writes landing past the declared w*h*4 footprint
* (1280x720x4 = 0x384000) by up to +0xE000 (11.2 rows) - FAULT_PTE on
@@ -29098,33 +29156,26 @@ gr_page_unmapped:
u64 want = 0;
/*
- * r1483: the rec-over-alias preference below
- * rewrites the pair to THIS launch's data-rec
- * bounce VA, and the fill alternates its
- * relocation half per capture push. A buffer
- * patched once per capture generation would
- * keep the first launch's half forever, so
- * re-patch when the launch's complete surface
- * rec names a different target.
+ * r1483/r1490: the rec-over-alias preference
+ * below rewrites the pair to THIS launch's
+ * data-rec bounce VA, and the fill alternates
+ * its relocation half per capture push. A
+ * buffer patched once per capture generation
+ * would keep the first launch's target
+ * forever - including the X server's 900-page
+ * rec lane when the next launch is the beef
+ * client whose partial 10-page allocation sits
+ * at the same VA. Re-patch whenever this
+ * launch's EFFECTIVE surface target (complete
+ * rec run or the contiguous alias, see
+ * gsp_phys_gr_surf_launch_target) differs
+ * from the cached words, so a partial launch
+ * always falls back to the real screen alias
+ * and never to its own code/operand buffer.
*/
if (!repatch && gsp_phys_gr_surf_pre_n >= 3) {
- u64 sva = 0;
- u32 sn = 0, di;
-
- if (!gsp_sema_gr_surf_recs(&sva, &sn) &&
- sva) {
- for (di = 0; di < req->n_data;
- di++) {
- if (req->data[di].va ==
- sva &&
- req->data[di]
- .reloc_va) {
- want = req->data[di]
- .reloc_va;
- break;
- }
- }
- }
+ want = gsp_phys_gr_surf_launch_target(
+ req);
if (want) {
u64 have =
((u64)(gsp_phys_gr_surf_pre[1] &
@@ -29132,8 +29183,18 @@ gr_page_unmapped:
(u64)(gsp_phys_gr_surf_pre[2] &
0xfffffffcu);
- if (have != want)
+ if (have != want) {
+ static u32 retarget_log;
+
repatch = true;
+ if (retarget_log < 16u) {
+ retarget_log++;
+ pr_info("vgsp-gr: SURF-PRELUDE retarget have=0x%llx want=0x%llx h=0x%x push=0x%llx\n",
+ have, want,
+ req->h_object,
+ req->push_va);
+ }
+ }
}
}
if (repatch) {
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)