summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/04-implementation/04-real-render-walls.md46
-rw-r--r--kernel/gsp_phys_booter.c188
-rw-r--r--kernel/gsp_sema_service.c56
3 files changed, 273 insertions, 17 deletions
diff --git a/docs/04-implementation/04-real-render-walls.md b/docs/04-implementation/04-real-render-walls.md
index b080732..0099306 100644
--- a/docs/04-implementation/04-real-render-walls.md
+++ b/docs/04-implementation/04-real-render-walls.md
@@ -1051,3 +1051,49 @@ shadow is re-copied from the guest page. The next work is to deliver the
captured vertex image into the guest page for every window and to calibrate
the viewport against the client's own CALL(33) arguments or a synthetic
full-screen control draw.
+
+## Session 154d (r1494-r1496): fixed-CB stability and contiguous VTG streams
+
+Two delivery defects on the path from the client's constants and vertex buffers
+to the engine were isolated and fixed; neither alone produces fragments yet.
+
+1. **The fixed constant buffer must be re-delivered every draw.** The MME
+ constant-buffer base is forced to a page inside the client's screen
+ allocation and the engine reads it through the launch's bounce alias. That
+ page is ordinary guest memory: the client (and the guest console) can
+ overwrite it between launches. The previous fallback wrote an identity
+ matrix whenever the source VA failed to resolve, which wiped an already
+ validated real CB (`cb-copy ... src=synthetic-id`). The copy now remembers
+ the last page that resolved through a SYS (engine-visible) source and passed
+ the matrix gate, and reuses it for the rest of the boot when a later
+ resolution misses. The synthetic identity is used only when no real CB was
+ ever resolved. The copy is not skipped on an unchanged hash, because the
+ seed page can have been rewritten by the guest since the previous launch
+ (receipts: `src=vaswalk` -> `src=cached-real` with the client's MVP in
+ `m[0x80..0xbc]`, and a guest-witness readback of the real matrix words).
+
+2. **A VTG stream must be one contiguous engine-visible range.** The vertex
+ stream macro payload is `[stream, end_hi, end_lo, base_hi, base_lo]`, where
+ `end - base` is the stream length. Relocating `end` and `base` as two
+ independent single-page operands put a page-crossing stream's ends in
+ unrelated shadow windows: the computed length went negative and the fetch
+ past the base page read an invalid PTE (the `miss=1` poison seen once the
+ real vertex shader started executing). The payload pair is now mapped into
+ one fresh contiguous fetch line (up to 16 x 4 KB) and both words are
+ rewritten from that line; unresolvable pages map the module zero page so a
+ read is a degenerate vertex, never a fault. The receipt
+ (`MME-VSTREAM-LINE ... pages=N dummy=... line=... first8=...`) reads the
+ client's real vertex floats back through the mapped page.
+
+**Remaining wall (measured, r1496).** The active indexed draw's vertex and
+index buffers are vidmem (BAR1-aperture) allocations: the launch page set
+resolves them to FB page `0xb9f000`, but the emulated vidmem shadow for that
+page is all zero (`dump_vidmem` proof), while the same vertex/index data is
+present in guest system memory as the client's staging/backing copy. The
+engine therefore fetches zeros for the active draw even though the stream
+mapping is correct, and the copy-out still reports `0/900` changed pages. The
+fixed-CB source VA also moves between boots (resolved in r1492/r1493/r1495,
+unresolved in r1496), so a single hard-coded source VA is not sufficient. The
+DS continues to latch SPH stage errors on some windows. The next work is to
+source the active vertex/index page from the sysmem backing (or to make the
+vidmem upload deterministic) and to make the CB source discoverable per boot.
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 494cb25..e7879cc 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -25633,6 +25633,42 @@ static u64 gsp_phys_rmcall_gr_prog_next_hpa_locked(
return 0;
}
+/*
+ * r1494: resolve one guest page through the same chain the relocation pass
+ * uses: the launch's own page set (copy-in authored pages), the tracked
+ * data recs, the GR ledger/pin/walk resolver, then the late-resolved gap
+ * pages. 0 = not resolvable. Unlike prog_next_hpa this takes any page, not
+ * just page0+4K.
+ */
+static u64 gsp_phys_rmcall_gr_stream_page_hpa_locked(
+ const struct gsp_phys_gr_submit *req, u64 page)
+{
+ u32 i;
+
+ for (i = 0; i < req->n_pages; i++)
+ if ((req->pages[i].guest_va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page &&
+ req->pages[i].hpa)
+ return req->pages[i].hpa;
+ for (i = 0; i < req->n_data; i++)
+ if ((req->data[i].va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page &&
+ req->data[i].hpa)
+ return req->data[i].hpa;
+ {
+ struct gsp_sema_gr_reloc r;
+
+ if (!gsp_sema_gr_reloc_resolve(page, &r) && r.hpa)
+ return r.hpa;
+ }
+ for (i = 0; i < gsp_phys_gr_gap_n; i++)
+ if ((gsp_phys_gr_gap[i].va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page &&
+ gsp_phys_gr_gap[i].hpa)
+ return gsp_phys_gr_gap[i].hpa;
+ return 0;
+}
+
static void gsp_phys_rmcall_gr_alias_cache_set_locked(u64 hpa, u64 va)
{
u32 i;
@@ -25652,6 +25688,109 @@ static void gsp_phys_rmcall_gr_alias_cache_set_locked(u64 hpa, u64 va)
}
/*
+ * r1494: a vertex stream in the MME payload is
+ * [stream, end_hi, end_lo, base_hi, base_lo]; END and BASE are both client
+ * VAs and the MME derives the stream length from end - base. The generic
+ * per-word payload pass relocated each end independently to its own
+ * single-page shadow fetch line, so any stream that crosses a 4 KB
+ * boundary (the client's vertex buffers here are 0x600..0x2d2c bytes and
+ * several cross) came out with its two ends in unrelated shadow windows:
+ * end - base went negative, and the VTG's fetch past the base page hit an
+ * invalid PTE (the 0xbadf1201 miss=1 priv-error seen once the real VS
+ * started executing). Map the whole [base, end) range into ONE fresh
+ * fetch line (up to 16 x 4 KB) and rewrite BOTH words from that line, so
+ * the size the MME computes is the client's real size and every page of
+ * the stream is engine-visible at base+offset. A page that cannot be
+ * resolved maps the module zero page: a zero attribute is a degenerate
+ * vertex, never a fault. Returns the line base VA; caller consumes one
+ * shadow slot.
+ */
+static u64 gsp_phys_rmcall_gr_stream_line_locked(
+ const struct gsp_phys_gr_submit *req, u64 base_va, u64 end_va,
+ u32 slot)
+{
+ struct gsp_phys_boot_chunk *sh = &boot_stage.rmcall_gr_shadow;
+ u64 base_page = base_va & ~(u64)(GSP_PHYS_PAGE_SIZE - 1);
+ u64 end_page = (end_va - 1u) & ~(u64)(GSP_PHYS_PAGE_SIZE - 1);
+ u64 hpa[GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES];
+ u64 va0, dummy = 0;
+ u32 npages, i, line, page, line_in_page, dummy_n = 0;
+ u8 *pt;
+ int ret;
+
+ if (end_va <= base_va)
+ return 0;
+ npages = (u32)((end_page - base_page) >> 12) + 1u;
+ if (npages > GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES)
+ return 0;
+ for (i = 0; i < npages; i++) {
+ hpa[i] = gsp_phys_rmcall_gr_stream_page_hpa_locked(req,
+ base_page + ((u64)i << 12));
+ if (!hpa[i]) {
+ if (!dummy)
+ dummy = gsp_phys_gr_surf_dummy_dma_locked();
+ if (!dummy)
+ return 0;
+ hpa[i] = dummy;
+ dummy_n++;
+ }
+ }
+ if (!sh->pages) {
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, sh,
+ GSP_PHYS_RMCALL_GR_SHADOW_PAGES);
+ if (ret)
+ return 0;
+ gsp_phys_chunk_zero(sh);
+ }
+ if (slot >= sh->pages || !boot_stage.rmcall_va_guest_pt)
+ return 0;
+ line = boot_stage.rmcall_guest_pt_next_line;
+ if (line >= GSP_PHYS_RMCALL_GUEST_PT_TOTAL_LINES)
+ return 0;
+ page = line / GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE;
+ line_in_page = line % GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE;
+ ret = gsp_phys_rmcall_gr_scratch_win_install_locked(page);
+ if (ret)
+ return 0;
+ pt = gsp_phys_rmcall_guest_pt_page_va(page);
+ if (!pt)
+ return 0;
+ for (i = 0; i < npages; i++)
+ put_unaligned_le64(gsp_phys_rmcall_gr_pte_leaf(hpa[i]),
+ pt + (size_t)(
+ GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES *
+ line_in_page + i) * 8);
+ dma_wmb();
+ boot_stage.rmcall_guest_pt_next_line = line + 1u;
+ va0 = gsp_phys_rmcall_guest_pt_win_va(page) +
+ ((u64)line_in_page << 16);
+ for (i = 0; i < npages; i++)
+ gsp_phys_rmcall_gr_alias_cache_set_locked(hpa[i],
+ va0 + ((u64)i << 12));
+ {
+ static u32 stream_line_log;
+ u32 w[8] = { 0 }, k;
+ u64 hpa0 = hpa[0];
+
+ if (pfn_valid((unsigned long)(hpa0 >> PAGE_SHIFT))) {
+ const u8 *p = (const u8 *)__va(
+ hpa0 + (base_va & (GSP_PHYS_PAGE_SIZE - 1)));
+
+ for (k = 0; k < 8u; k++)
+ w[k] = get_unaligned_le32(p + (size_t)k * 4u);
+ }
+ if (stream_line_log < 32u) {
+ stream_line_log++;
+ pr_info("vgsp-gr: MME-VSTREAM-LINE h=0x%x base=0x%llx end=0x%llx pages=%u dummy=%u line=0x%llx hpa0=0x%llx first8=%08x %08x %08x %08x %08x %08x %08x %08x\n",
+ req ? req->h_object : 0u, base_va, end_va,
+ npages, dummy_n, va0, hpa0, w[0], w[1], w[2],
+ w[3], w[4], w[5], w[6], w[7]);
+ }
+ }
+ return va0;
+}
+
+/*
* Allocate (or reuse) the pair line. Returns the line base VA on success,
* 0 when the second half cannot be resolved or the pool is exhausted (the
* caller then falls through to the single-page path). The fresh line is
@@ -26770,12 +26909,57 @@ static u32 gsp_phys_rmcall_gr_3d_patch_window(const u32 *in, u32 n, u32 *out,
if (READ_ONCE(gsp_phys_rmcall_gr_mme_reloc) &&
addr >= 0x3800u && addr < 0x3a00u) {
u32 wi;
+ bool stream_pair = false;
+ /*
+ * r1494: VERTEX_STREAM macro(2) carries
+ * [stream, end_hi, end_lo, base_hi, base_lo]
+ * where END and BASE are client VAs (the MME
+ * computes the stream length as end - base).
+ * Relocating the two words independently put
+ * a page-crossing stream's ends in unrelated
+ * shadow windows: end - base went negative
+ * and the VTG fetch past the base page read
+ * an invalid PTE -> 0xbadf1201 miss=1 once
+ * the real VS ran. Map the whole range into
+ * one fresh contiguous line and rewrite both
+ * words from it; fall back to the historical
+ * per-word pass only when the range cannot be
+ * mapped.
+ */
+ if (addr == 0x3810u && cnt >= 5u &&
+ !d[1] && !d[3]) {
+ u64 bva = (u64)d[4];
+ u64 eva = (u64)d[2];
+ u64 lva;
+
+ if (bva >= 0x10000u && eva > bva &&
+ (eva - bva) <= 0x10000u &&
+ !(bva & 3u) && !(eva & 3u) &&
+ *n_shadow <
+ GSP_PHYS_RMCALL_GR_SHADOW_PAGES &&
+ (lva = gsp_phys_rmcall_gr_stream_line_locked(
+ req, bva, eva,
+ *n_shadow)) != 0) {
+ u64 bpage = bva &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1);
+
+ d[2] = (u32)(lva + (eva - bpage));
+ d[4] = (u32)(lva + (bva &
+ (GSP_PHYS_PAGE_SIZE - 1)));
+ (*n_shadow)++;
+ stream_pair = true;
+ reloc += 2;
+ }
+ }
for (wi = 0; wi < cnt; wi++) {
u32 wv = d[wi];
u32 up = 0, lo = wv;
static u32 mme_log;
+ if (stream_pair &&
+ (wi == 2u || wi == 4u))
+ continue;
if (wv < 0x10000u || (wv & 3u))
continue;
if (!gsp_phys_rmcall_gr_3d_reloc(
@@ -26897,9 +27081,9 @@ static u32 gsp_phys_rmcall_gr_3d_patch_window(const u32 *in, u32 n, u32 *out,
if (addr == 0x3810u && cnt >= 5u) {
static u32 vstream_log;
- if (vstream_log < 8u) {
+ if (vstream_log < 24u) {
vstream_log++;
- pr_info("vgsp-gr: MME-VSTREAM h=0x%x stream=%u limit=%08x:%08x addr=%08x:%08x (post-reloc)\n",
+ pr_info("vgsp-gr: MME-VSTREAM h=0x%x stream=%u end=%08x:%08x base=%08x:%08x (post-reloc)\n",
req ? req->h_object : 0u,
d[0], d[1], d[2],
d[3], d[4]);
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 5bc387e..dd02aa0 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -15344,6 +15344,16 @@ static u32 svc_gl_cb_copy_h;
static u32 svc_gl_cb_copy_val;
static u32 svc_gl_cb_copy_hash;
static u32 svc_gl_cb_copy_log;
+/*
+ * r1494: the last fixed-CB page that resolved through a SYS (engine-page)
+ * source and passed the matrix gate. The client VA can move/reuse after
+ * the first frame (r1492 read back a method page with the same VA and
+ * r1493 fell back to identity), so a later resolution miss must not zero
+ * the seed page: reuse this page for the rest of the boot. Only a page
+ * that was never credibly resolved falls back to the synthetic identity.
+ */
+static u8 svc_gl_cb_good[SEMA_PAGE_SIZE];
+static bool svc_gl_cb_good_valid;
static u32 svc_gl_cb_hash(const u8 *p, u32 n)
{
@@ -15558,33 +15568,49 @@ have:
* r1491: a source whose 0x80 block is not a credible matrix would
* still collapse every vertex. Only a SYS (engine-page) read is
* trusted; an FB-shadow read is by construction not the engine's
- * page, so author the identity positive control there too.
- */
- if ((ssrc == 4 || ssrc == 5 || ssrc == 6 || ssrc == 7) ||
- !svc_gl_cb_matrix_ok(buf)) {
+ * page, so it must not become the seed either.
+ *
+ * r1494: split "is this the real CB?" from "what do we write?".
+ * A validated SYS page becomes the boot's remembered real CB. When
+ * a later resolution misses (or answers with a method page), write
+ * that remembered page again instead of the identity fallback -
+ * the fallback may only run when no real CB was ever resolved this
+ * boot. Do not skip the write on an unchanged hash: the seed page
+ * lives in the client's screen allocation and the guest can have
+ * rewritten it between launches, so "same content as last time" is
+ * not the same as "page still holds it".
+ */
+ if (ssrc != 4 && ssrc != 5 && ssrc != 6 && ssrc != 7 &&
+ svc_gl_cb_matrix_ok(buf)) {
+ memcpy(svc_gl_cb_good, buf, sizeof(svc_gl_cb_good));
+ svc_gl_cb_good_valid = true;
+ } else if (svc_gl_cb_good_valid) {
+ memcpy(buf, svc_gl_cb_good, sizeof(svc_gl_cb_good));
+ src = "cached-real";
+ ssrc = 9;
+ } else {
memset(buf, 0, sizeof(buf));
put_unaligned_le32(0x3f800000u, buf + 0x80); /* m00 */
put_unaligned_le32(0x3f800000u, buf + 0x94); /* m11 */
put_unaligned_le32(0x3f800000u, buf + 0xa8); /* m22 */
put_unaligned_le32(0x3f800000u, buf + 0xbc); /* m33 */
src = "synthetic-id";
+ ssrc = 7;
}
hash = svc_gl_cb_hash(buf, sizeof(buf));
- if (svc_gl_cb_copy_h == h_object && svc_gl_cb_copy_val == val &&
- svc_gl_cb_copy_hash == hash)
- return;
+ (void)hash;
rc = svc_grce_inline_write(vgpu, sema_gl_cb_copy_to, buf, sizeof(buf),
h_object);
- if (svc_gl_cb_copy_log < 24u) {
- u32 w0, w1;
+ if (svc_gl_cb_copy_log < 96u) {
+ u32 w[8];
- memcpy(&w0, buf, 4);
- memcpy(&w1, buf + 4, 4);
+ memcpy(w, buf, sizeof(w));
svc_gl_cb_copy_log++;
- pr_info("vgsp-sema: sema-gl: cb-copy src=0x%llx dst=0x%llx pa=0x%llx ap=%u hpa=0x%llx ssrc=%d first=%08x %08x src=%s write=%d (fixed-CB page -> forced seed page)\n",
- src_va, sema_gl_cb_copy_to, pa, ap, hpa, ssrc, w0, w1,
- src, rc);
- if (svc_gl_cb_copy_log <= 4u)
+ pr_info("vgsp-sema: sema-gl: cb-copy src=0x%llx dst=0x%llx pa=0x%llx ap=%u hpa=0x%llx ssrc=%d first8=%08x %08x %08x %08x %08x %08x %08x %08x src=%s write=%d cached=%d (fixed-CB page -> forced seed page)\n",
+ src_va, sema_gl_cb_copy_to, pa, ap, hpa, ssrc,
+ w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7],
+ src, rc, svc_gl_cb_good_valid ? 1 : 0);
+ if (svc_gl_cb_copy_log <= 12u)
svc_gl_cb_dump_matrix(buf, src, pa, ap, ssrc);
}
if (!rc) {