From 84ae0002eeb65b266b2f08978c662fce5df7b230 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 5 Nov 2026 15:22:03 -0500 Subject: 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. --- kernel/gsp_phys_booter.c | 224 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 12 deletions(-) (limited to 'kernel/gsp_phys_booter.c') diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c index e7879cc..b20e43e 100644 --- a/kernel/gsp_phys_booter.c +++ b/kernel/gsp_phys_booter.c @@ -5216,6 +5216,9 @@ static bool gsp_phys_rmcall_gr_mme_iram_probe; * launch thread's time and stops early once the poison is seen). */ static u32 gsp_phys_mme_fast_ms = 400u; +/* r1497: defined with the emission knob below; used by the post-launch + * DS ESR clear. */ +static bool gsp_phys_rmcall_gr_ds_mask_clear; module_param_named(sema_mme_fast_ms, gsp_phys_mme_fast_ms, uint, 0644); MODULE_PARM_DESC(sema_mme_fast_ms, "Spin-sample the MME fault registers right after each GR launch doorbell for this many ms, logging every transition before the GSP-RM clears them (0 = off; default 400)"); @@ -5617,6 +5620,28 @@ static bool gsp_phys_mme_fast_sample(void __iomem *bar0, const char *tag, } pr_info("vgsp-gr: MME-FAST end %s h=0x%x events=%u%s\n", tag, h_object, n, poisoned ? " (poison)" : ""); + /* + * r1497: clear the DS HWW ESR latch (0x405840, W1C) after the + * launch as well as before it. The pre-clear makes each window's + * validation set readable; a draw that latches a6040e24 otherwise + * leaves the pending report to poison the next window's entry state + * (r1496: ds a6040e24 read at the following begin while poison was + * otherwise low). The clear is W1C and host-only, like the mask + * write above, so the A/B knob covers both sites. + */ + if (READ_ONCE(gsp_phys_rmcall_gr_ds_mask_clear) && bar0) { + static u32 ds_post_log; + u32 esr = ioread32(bar0 + 0x00405840u); + + if (esr) { + iowrite32(esr, bar0 + 0x00405840u); + if (ds_post_log < 24u) { + ds_post_log++; + pr_info("vgsp-gr: DS-ESR post-clear=%08x h=0x%x tag=%s\n", + esr, h_object, tag); + } + } + } /* * SESSION 153-CONTINUED-107: post-spin IRAM sample - the state left * when the poison (if any) has just cleared, before the poll/retire @@ -10728,6 +10753,61 @@ static u64 gsp_phys_rmcall_gr_pte_leaf(u64 hpa) return pte; } +/* + * r1497: a VID-aperture leaf pointing at the page's real frame-buffer + * offset. The guest's own page tables encode exactly this + * (VALID=1, APERTURE=VID(0), KIND=6 GENERIC_MEMORY, PA>>12 in [56:8]), + * so the engine reads the real FBMEM page the client uploaded instead of + * an emulated BAR1 shadow that no upload ever populated. + */ +static u64 gsp_phys_rmcall_gr_pte_leaf_vid(u64 fb_off) +{ + return 1ULL | (((fb_off >> 12) & 0x1ffffffULL) << 8) | + ((u64)6u << 56); +} + +/* + * r1497: is the engine-visible shadow page at `hpa` entirely empty? + * Only then may the real-FB VID leaf replace it; pages the module seeded + * (fixed CB), the engine wrote, or the client wrote through the emulated + * aperture keep their shadow identity. + */ +static bool gsp_phys_gr_shadow_page_empty(u64 hpa) +{ + const u8 *p; + u32 i; + + if (!hpa || !pfn_valid((unsigned long)(hpa >> PAGE_SHIFT))) + return false; + p = (const u8 *)__va(hpa); + for (i = 0; i < GSP_PHYS_PAGE_SIZE; i++) + if (p[i]) + return false; + return true; +} + +/* + * r1497: resolve a fetch VA to its real frame-buffer offset when the page + * is FB-aperture AND the emulated shadow copy of it is all-zero. This is + * the active indexed draw's vertex/index class (r1496): the client's page + * tables map the page to vidmem, the upload never reached an emulated + * path, and the engine fetched zeros. Returns true with *fb_off set. + */ +static bool gsp_phys_gr_fetch_vid_off_locked(u64 va, u64 *fb_off) +{ + struct gsp_sema_gr_reloc r; + + if (gsp_sema_gr_reloc_resolve(va & ~(u64)(GSP_PHYS_PAGE_SIZE - 1), + &r)) + return false; + if (r.ap != 2 /* SVC_APERTURE_FB */ || !r.hpa) + return false; + if (!gsp_phys_gr_shadow_page_empty(r.hpa)) + return false; + *fb_off = r.pa; + return true; +} + /* * GP10X leaf PTE for a frame-buffer (VIDEO_MEMORY aperture 0) address: * VALID=1 (bit 0), APERTURE=0 (bits 2:1), VOL=1 (bit 3), frame-buffer byte @@ -17759,6 +17839,9 @@ static u64 gsp_phys_gr_timed_out; static u64 gsp_phys_gr_zerowin_skips; static u64 gsp_phys_gr_retries; /* fresh-entry launch retries published */ static u64 gsp_phys_gr_pool_exhausted; +/* r1497: launch fetch pages mapped through a real-FB VID leaf (empty shadow). */ +static u64 gsp_phys_gr_vid_map_pages; +static u32 gsp_phys_gr_vid_log; /* m156 (E5): zero-write retries where the warm sem push proved the engine * wedged (the persistent-lottery class); further retries are skipped. */ static u64 gsp_phys_gr_sem_wedged; @@ -22888,6 +22971,58 @@ static int gsp_phys_rmcall_gr_map_va_locked(void __iomem *bar0, u64 guest_va, return 0; } +/* + * r1497: install a VID-aperture PTE at the guest VA so the engine reads + * the real FBMEM page (fb_off) instead of an empty shadow page. Same + * never-rewrite discipline; the cache key is tagged so a VID entry can + * never be mistaken for a host-PA alias. + */ +static int gsp_phys_rmcall_gr_map_va_vid_locked(void __iomem *bar0, + u64 guest_va, u64 fb_off, + u32 epoch) +{ + u64 page = guest_va & ~(u64)(GSP_PHYS_PAGE_SIZE - 1); + u32 pde_index = (u32)(page >> 21); + u32 pte_idx = (u32)((page >> 12) & 0x1ffu); + u32 line = pte_idx / GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES; + u64 key = (fb_off & ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) | + (1ULL << 63); + struct gsp_phys_boot_chunk *pt; + u8 *pt_va; + u32 slot, i; + int ret; + + ret = gsp_phys_rmcall_gr_win_slot_locked(bar0, pde_index, &slot); + if (ret) + return ret; + for (i = 0; i < boot_stage.rmcall_gr_win[slot].pte_count; i++) { + if (boot_stage.rmcall_gr_win[slot].pte[i].idx == pte_idx) { + if (boot_stage.rmcall_gr_win[slot].pte[i].hpa == key) + return 0; + return -EALREADY; + } + if (boot_stage.rmcall_gr_win[slot].pte[i].epoch != epoch && + boot_stage.rmcall_gr_win[slot].pte[i].idx / + GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES == line) + return -EBUSY; + } + if (boot_stage.rmcall_gr_win[slot].pte_count >= + GSP_PHYS_RMCALL_GR_PT_ENTRIES) + return -ENOSPC; + pt = &boot_stage.rmcall_gr_pt[boot_stage.rmcall_gr_win[slot].pt_slot]; + pt_va = gsp_phys_chunk_page_va(pt, 0); + if (!pt_va) + return -EIO; + put_unaligned_le64(gsp_phys_rmcall_gr_pte_leaf_vid(fb_off), + pt_va + (size_t)pte_idx * 8); + dma_wmb(); + boot_stage.rmcall_gr_win[slot].pte[i].idx = pte_idx; + boot_stage.rmcall_gr_win[slot].pte[i].epoch = epoch; + boot_stage.rmcall_gr_win[slot].pte[i].hpa = key; + boot_stage.rmcall_gr_win[slot].pte_count++; + return 0; +} + static void __iomem *gsp_phys_gr_scratch_bar0; /* @@ -25645,6 +25780,14 @@ static u64 gsp_phys_rmcall_gr_stream_page_hpa_locked( { u32 i; + /* + * r1497: a vertex/index fetch page can resolve to an FB page whose + * emulated shadow is empty while the same VA has a sysmem mapping + * with the real bytes (r1496: the active indexed draw's 0x44fea00 + * stream read all-zero). Populate the empty shadow page before the + * stream line is built. Non-empty pages and SYS answers are no-ops. + */ + gsp_sema_gr_fetch_backing(page); for (i = 0; i < req->n_pages; i++) if ((req->pages[i].guest_va & ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page && @@ -25713,8 +25856,11 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( 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 pte[GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES]; + u64 fb_off[GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES]; + bool real[GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES]; u64 va0, dummy = 0; - u32 npages, i, line, page, line_in_page, dummy_n = 0; + u32 npages, i, line, page, line_in_page, dummy_n = 0, vid_n = 0; u8 *pt; int ret; @@ -25724,8 +25870,24 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( 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)); + u64 pa = base_page + ((u64)i << 12); + + real[i] = false; + fb_off[i] = 0; + /* + * r1497: the active draw's vertex/index page resolves to a + * vidmem page whose emulated shadow is empty. Point the + * fetch line at the real FBMEM page (the guest's own page + * table names the offset) instead of delivering zeros. + */ + if (gsp_phys_gr_fetch_vid_off_locked(pa, &fb_off[i])) { + pte[i] = gsp_phys_rmcall_gr_pte_leaf_vid(fb_off[i]); + hpa[i] = 0; + real[i] = true; + vid_n++; + continue; + } + hpa[i] = gsp_phys_rmcall_gr_stream_page_hpa_locked(req, pa); if (!hpa[i]) { if (!dummy) dummy = gsp_phys_gr_surf_dummy_dma_locked(); @@ -25734,6 +25896,7 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( hpa[i] = dummy; dummy_n++; } + pte[i] = gsp_phys_rmcall_gr_pte_leaf(hpa[i]); } if (!sh->pages) { ret = gsp_phys_chunk_alloc(boot_stage.pdev, sh, @@ -25756,8 +25919,7 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( if (!pt) return 0; for (i = 0; i < npages; i++) - put_unaligned_le64(gsp_phys_rmcall_gr_pte_leaf(hpa[i]), - pt + (size_t)( + put_unaligned_le64(pte[i], pt + (size_t)( GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES * line_in_page + i) * 8); dma_wmb(); @@ -25765,14 +25927,16 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( 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)); + if (!real[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))) { + if (!real[0] && + pfn_valid((unsigned long)(hpa0 >> PAGE_SHIFT))) { const u8 *p = (const u8 *)__va( hpa0 + (base_va & (GSP_PHYS_PAGE_SIZE - 1))); @@ -25781,10 +25945,11 @@ static u64 gsp_phys_rmcall_gr_stream_line_locked( } 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", + pr_info("vgsp-gr: MME-VSTREAM-LINE h=0x%x base=0x%llx end=0x%llx pages=%u dummy=%u vid=%u fb0=0x%llx 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]); + npages, dummy_n, vid_n, fb_off[0], va0, hpa0, + w[0], w[1], w[2], w[3], w[4], w[5], w[6], + w[7]); } } return va0; @@ -28174,6 +28339,41 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req, pg_va = pg->guest_va & ~(u64)(GSP_PHYS_PAGE_SIZE - 1); pr_info("vgsp-gr: page[%u] va=0x%llx hpa=0x%llx qmd_word=%u\n", i, pg->guest_va, pg->hpa, pg->qmd_word); + /* + * r1497/r1498: an FB *fetch* page (a QMD pointer pair, not + * the data-fill surface) whose shadow is empty is the active + * indexed draw's vertex/index class. Map the real FBMEM page + * through a VID leaf instead of the empty shadow. The + * render-target/surface pages (QMD word NONE) must keep + * their shadow identity: the engine's stores have to land in + * the emulated page so the copy-out can ship them back to + * the guest (r1498: mapping the surface to real FB made the + * frame invisible to the guest). + */ + if (pg->hpa && pg->qmd_word != 48u && + pg->qmd_word != GSP_PHYS_GR_QMD_WORD_NONE) { + u64 fb_off = 0; + + if (gsp_phys_gr_fetch_vid_off_locked(pg_va, + &fb_off)) { + ret = gsp_phys_rmcall_gr_map_va_vid_locked( + bar0, pg_va, fb_off, epoch); + if (!ret) { + mapped = true; + gsp_phys_gr_vid_map_pages++; + if (gsp_phys_gr_vid_log < 32u) { + gsp_phys_gr_vid_log++; + pr_info("vgsp-gr: FETCH-VID-MAP va=0x%llx fb=0x%llx qmd=%u h=0x%x push=0x%llx\n", + pg_va, fb_off, + pg->qmd_word, + req->h_object, + req->push_va); + } + } else if (ret == -ENOSPC) { + gsp_phys_gr_pool_exhausted++; + } + } + } /* * SESSION 149 R15 (M2-S1): with the window-0 shadow path * enabled, force the program page (QMD word 48) OFF the @@ -28184,7 +28384,7 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req, * the win0 shadow copy + w48/49 rewrite line, which is the * proven one. Every other page keeps its Tier-1 route. */ - if (pg->hpa && !(pg->qmd_word == 48u && + if (!mapped && pg->hpa && !(pg->qmd_word == 48u && gsp_phys_rmcall_gr_shadow_win0_value())) { ret = gsp_phys_rmcall_gr_map_va_locked(bar0, pg_va, pg->hpa, epoch); -- cgit v1.2.3