diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-05 15:22:03 -0500 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-05 15:22:03 -0500 |
| commit | 84ae0002eeb65b266b2f08978c662fce5df7b230 (patch) | |
| tree | c156ffae6d7c4e573ecdef76c05a559e0423bb4e /kernel/gsp_sema_service.c | |
| parent | c126441814ded6498b2e06ebb2a33ee05faac994 (diff) | |
| download | open-iov-84ae0002eeb65b266b2f08978c662fce5df7b230.tar.gz open-iov-84ae0002eeb65b266b2f08978c662fce5df7b230.zip | |
vgsp-gr: deliver empty FB fetch pages from real FBMEM, discover the CB per boot, clear the DS latch after each window
The active indexed draw's vertex/index buffers resolve through the client's
page tables to vidmem pages whose emulated BAR1 shadow is empty (no emulated
upload path ever populated it), so the engine fetched zeros and the draw
stored nothing. Three delivery paths now cover the page: a real-FB VID leaf
for launch fetch pages (QMD pointers, never the data-fill surface) and for
vertex streams when every shadow byte is zero; a sysmem-backing copy when the
same VA also has a nonzero SYS mapping; otherwise the existing shadow page is
used untouched. Render-target pages keep their shadow identity so the copy-out
can still ship stores back to the guest.
The fixed constant buffer is discovered per boot: the captured
SET_MME_SHADOW_SCRATCH(0x18) value is authoritative, the historically observed
base is a gated fallback, and a resolved real page is remembered for later
misses. The DS HWW ESR latch is now also cleared (W1C) after each replayed
window, so a failed window cannot poison the next window's entry state.
Validated with three supervised cycles (r1497-r1499): the active streams read
the client's real vertex floats, the real MVP resolves, poison stays at 0-1
per run, and the engine clears the render target to opaque black
(dump_vidmem 0x200000 = ff000000) with the guest window still all black -
the remaining wall is geometry coverage, not data delivery.
Diffstat (limited to 'kernel/gsp_sema_service.c')
| -rw-r--r-- | kernel/gsp_sema_service.c | 241 |
1 files changed, 238 insertions, 3 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index dd02aa0..8668b6f 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -1399,6 +1399,8 @@ struct sema_svc { u64 fwd_copy_skipped; u64 fwd_copy_soft; u64 fwd_copy_fail; + /* r1497: empty FB fetch pages populated from their sysmem backing. */ + u64 fwd_fetch_backing; u64 fwd_rel_deferred; u64 fwd_rel_applied; u64 fwd_rel_fallback; @@ -15453,13 +15455,33 @@ static void svc_gl_cb_copy(struct nv_vgpu_device *vgpu, u32 h_object) if (sema_gl_cb_src_va) { src_va = sema_gl_cb_src_va; val = (u32)(src_va >> 8); - } else { - if (!svc_scratch18_seen) - return; + } else if (svc_scratch18_seen) { + /* + * r1497: per-boot discovery. The client's own + * SET_MME_SHADOW_SCRATCH(0x18) write (method 0x3460) is the + * authority for the fixed-CB base; svc_scratch18_watch() + * captures it from whichever decoded push carries it. Use + * it instead of a hardcoded VA so a boot whose mapping moved + * still reads the real matrix. + */ val = READ_ONCE(svc_scratch18_val); if (!val) return; src_va = (u64)val << 8; + } else { + /* + * The 0x3460 write did not reach a decoded push on this + * boot (r1496: 0 captures). Fall back to the fixed-CB VA + * observed on the boots where the capture did fire + * (0x0cd70000) and let the matrix gate decide whether the + * page is the real CB. This keeps a real matrix instead of + * the synthetic identity whenever the page is present under + * the historical VA; a failed gate still falls through to + * cached-real / synthetic-id below. + */ + src_va = 0x0cd70000ULL; + val = (u32)(src_va >> 8); + src = "captured-miss"; } /* * r1491: the fixed constant buffer is ENGINE-written (the driver @@ -31678,6 +31700,9 @@ static void svc_fwd_relay_select(struct svc_fwd_req *req) */ static const char *svc_gr_src_probe(struct nv_vgpu_device *vgpu, u64 va, u64 *pa_out, u32 *ap_out); +/* r1497: deliver the sysmem backing of an empty FB fetch page. */ +bool gsp_sema_gr_fetch_sys_into(u64 va, u64 fb_pa, u64 *gpa_out); +bool gsp_sema_gr_fetch_backing(u64 va); static void svc_gr_page_note(struct nv_vgpu_device *vgpu, u64 va, u32 qmd_word) { u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); @@ -31845,6 +31870,29 @@ static void svc_gr_page_note(struct nv_vgpu_device *vgpu, u64 va, u32 qmd_word) src = "walk"; note_done: /* + * r1497: an FB launch page whose emulated BAR1 shadow is empty is a + * page the engine will fetch as zeros (r1496 wall). If the same VA + * also has a sysmem mapping with real bytes, deliver them into the + * shadow page now, before the launch maps/copies it. The fb_pa/ap + * stay unchanged, so every downstream identity (QMD relocation, + * copy-out, ledger) is untouched. + */ + if (svc_fwd_req.gr_pages[i].ap == SVC_APERTURE_FB && + qmd_word != SVC_GR_QMD_WORD_NONE) { + u64 gpa = 0; + + if (gsp_sema_gr_fetch_sys_into(page, + svc_fwd_req.gr_pages[i].pa, + &gpa)) { + svc.fwd_fetch_backing++; + if (svc.fwd_fetch_backing <= 32) + pr_info("sema-gr: FETCH-BACKING note va=0x%llx fb_pa=0x%llx gpa=0x%llx qmd=%u n=%llu\n", + page, svc_fwd_req.gr_pages[i].pa, + gpa, qmd_word, + svc.fwd_fetch_backing); + } + } + /* * r372 diagnostic: the exact (pa, ap) and branch the launch page * resolved through - the value the engine's hpa and the copy-out use. * A D2H readback that lands elsewhere (the sum=0 class) names its @@ -32065,6 +32113,193 @@ static const char *svc_gr_src_probe(struct nv_vgpu_device *vgpu, u64 va, } /* + * r1497: fetch-page sysmem backing. + * + * THE WALL (r1496): the active indexed draw's vertex/index buffer resolves + * through the client's own VAS to a vidmem page (FB aperture, low offset) + * but the emulated BAR1 shadow at that offset is entirely zero - the upload + * never passed through an emulated path (no inline write, no CE copy the + * host decoded), so only the real FBMEM has the bytes. The client's object + * is nevertheless also mapped in the channel's page tables with a SYS + * answer (the CPU/staging copy): walking every registered VAS for the same + * VA finds a guest-RAM GPA whose page holds the real vertex/index data + * (r1497 live proof: VA 0x44ff000/0x44fea00 -> GPA 0x124c89000/0x12c77e00, + * first8 of the latter 00000000 3f800000 32f0b842 c0166666 ...). + * + * When the FB shadow page is all-zero, copy that SYS page into the shadow + * page the engine already reads. The launch map, the MME stream line and + * the copy-out keep their existing identities; only the empty page's + * content changes. A shadow page with any byte set is never touched, so + * every working stream and the CB seed keep their current behaviour. + * + * The helper runs in process context (service thread or the GR work item); + * the walks may sleep in vfio_dma_rw exactly like svc_walk_vaspaces. + */ +static bool svc_page_nonzero(const u8 *p, size_t n) +{ + size_t i; + + for (i = 0; i + 8u <= n; i += 8u) { + u64 v; + + memcpy(&v, p + i, 8); + if (v) + return true; + } + for (; i < n; i++) + if (p[i]) + return true; + return false; +} + +static int svc_gr_sys_backing_walk(struct nv_vgpu_device *vgpu, u64 va, + u64 *gpa_out) +{ + struct svc_vaspace local[SVC_MAX_VASPACES]; + unsigned long flags; + unsigned long stamps[SVC_MAX_VASPACES]; + u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); + u8 tmp[32]; + int i, n = 0; + + /* + * The channel currently being serviced is the most specific answer; + * it is the VAS that produced the FB mapping in the first place, so + * ask it first for the alternate (SYS) encoding of the same VA. + */ + { + u64 pa = 0; + int ap = SVC_APERTURE_SYS; + + if (!svc_chanvas_lookup(vgpu, page, &pa, &ap) && + ap == SVC_APERTURE_SYS && + !svc_read_ap(vgpu, pa, SVC_APERTURE_SYS, tmp, + sizeof(tmp)) && + svc_page_nonzero(tmp, sizeof(tmp))) { + *gpa_out = pa; + return 0; + } + } + + spin_lock_irqsave(&svc.vas_lock, flags); + for (i = 0; i < SVC_MAX_VASPACES; i++) { + if (!svc.vas[i].valid) + continue; + local[n] = svc.vas[i]; + stamps[n] = local[n].stamp; + n++; + } + spin_unlock_irqrestore(&svc.vas_lock, flags); + + /* Newest registration first, same authority order as svc_walk_vaspaces. */ + for (i = 1; i < n; i++) { + struct svc_vaspace k = local[i]; + unsigned long ks = stamps[i]; + int j = i - 1; + + while (j >= 0 && time_before(stamps[j], ks)) { + local[j + 1] = local[j]; + stamps[j + 1] = stamps[j]; + j--; + } + local[j + 1] = k; + stamps[j + 1] = ks; + } + + for (i = 0; i < n; i++) { + int alt = local[i].ap == SVC_APERTURE_FB ? SVC_APERTURE_SYS + : SVC_APERTURE_FB; + int taps[2]; + int t; + + taps[0] = local[i].ap; + taps[1] = alt; + for (t = 0; t < 2; t++) { + u64 pa = 0; + int ap = SVC_APERTURE_SYS; + + if (svc_gmmu_walk(vgpu, local[i].pdb, + local[i].entries, page, taps[t], + &pa, &ap, false)) + continue; + if (ap != SVC_APERTURE_SYS) + continue; + if (svc_read_ap(vgpu, pa, SVC_APERTURE_SYS, tmp, + sizeof(tmp))) + continue; + if (!svc_page_nonzero(tmp, sizeof(tmp))) + continue; + *gpa_out = pa; + return 0; + } + } + return -ENOENT; +} + +bool gsp_sema_gr_fetch_sys_into(u64 va, u64 fb_pa, u64 *gpa_out) +{ + struct nv_vgpu_device *vgpu = svc_fwd_req.vgpu; + static u32 back_log; + u64 gpa = 0; + u8 *base, *dst; + u32 wfb[8] = { 0 }, wsys[8] = { 0 }; + u32 k; + + if (!vgpu || !gpa_out) + return false; + base = vgpu->bars[1].vaddr; + if (!base || fb_pa + SEMA_PAGE_SIZE > svc_fb_shadow_size(vgpu)) + return false; + dst = base + fb_pa; + if (svc_page_nonzero(dst, SEMA_PAGE_SIZE)) + return false; + if (svc_gr_sys_backing_walk(vgpu, va, &gpa)) + return false; + /* + * Take the first 8 words of the SYS candidate before the copy so the + * receipt shows both sides of the delivery even when the copy itself + * is what the engine later reads. + */ + if (!svc_read_ap(vgpu, gpa, SVC_APERTURE_SYS, wsys, sizeof(wsys))) + ; + for (k = 0; k < 8u; k++) + memcpy(&wfb[k], dst + (size_t)k * 4u, 4); + if (svc_read_ap(vgpu, gpa, SVC_APERTURE_SYS, dst, SEMA_PAGE_SIZE)) + return false; + *gpa_out = gpa; + if (back_log < 64u) { + back_log++; + pr_info("vgsp-sema: FETCH-SYSBACK va=0x%llx fb_pa=0x%llx gpa=0x%llx pages=1 first8_fb=%08x %08x %08x %08x %08x %08x %08x %08x first8_sys=%08x %08x %08x %08x %08x %08x %08x %08x\n", + va, fb_pa, gpa, + wfb[0], wfb[1], wfb[2], wfb[3], wfb[4], wfb[5], + wfb[6], wfb[7], wsys[0], wsys[1], wsys[2], wsys[3], + wsys[4], wsys[5], wsys[6], wsys[7]); + } + return true; +} + +/* + * Resolver wrapper for callers that only have the VA (the booter's MME + * stream-line builder). Resolve the VA the same way the relocation chain + * does; only an FB answer whose shadow page is empty is a candidate. + */ +bool gsp_sema_gr_fetch_backing(u64 va) +{ + struct nv_vgpu_device *vgpu = svc_fwd_req.vgpu; + u64 pa = 0, gpa = 0; + u32 ap = 0; + + if (!vgpu) + return false; + if (svc_gr_src_probe(vgpu, va & ~(u64)(SEMA_PAGE_SIZE - 1), &pa, &ap)) + return false; + if (ap != SVC_APERTURE_FB) + return false; + return gsp_sema_gr_fetch_sys_into(va, pa & ~(u64)(SEMA_PAGE_SIZE - 1), + &gpa); +} + +/* * relfix PART 3: append one attempt to the resolver's diagnostic chain * ("name:rc,"). Bounded; the tail is simply dropped once full. */ |
