diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-20 23:11:59 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-20 23:11:59 -0400 |
| commit | 89a6497e78027be58069af84a7a77a5beaf3a971 (patch) | |
| tree | 3587a84485a8d22a037d706ccd3ebd59d1be8257 /kernel/gsp_sema_service.c | |
| parent | 03275eae56dc37e2b6bb4d50052648256af80f60 (diff) | |
| download | open-iov-89a6497e78027be58069af84a7a77a5beaf3a971.tar.gz open-iov-89a6497e78027be58069af84a7a77a5beaf3a971.zip | |
sema: r391 per-page destination resolution for virtual fills - the r390 guest panic root cause
The 383620-byte context-init zero fill (FILL-NOREMAP class) resolved only
its FIRST page through the pin (va=0x7c36c7400000 -> pa=0x131b63000, the
pin stream's first run = 29 pages) and then wrote the whole 375 KiB
contiguously past it. The pin stream shows the VA's pages are independent
guest allocations with split runs (0x131b63000 / 0x131a80000 / 0x128340000),
so the contiguous write covered unrelated memory - on r390 exactly the
guest kernel's page-table pages 0x131ba1000/0x131ba2000/0x131bab000. The
direct map grew three 4 KiB holes and the guest died with 'Fatal exception
in interrupt' (unmap_page_range / cpuacct_account_field / psi_task_change
faults on physmap addresses).
svc_copy has always translated virtual operands per 4 KB page ('a linear PA
walk past the first page reads the wrong memory'); svc_fill never did.
New svc_fill_dst_page_pa() resolves every page of a virtual fill through
the same authority order (fresh pin wins and repairs the landing table,
then the landing table, then the resolver chain); the SYS write loop clamps
each chunk to the page boundary and writes at that page's own landing; a
page with no resolution fails the fill loudly (FILL-TRUNC) instead of
writing through a contiguous guess. The >= 64 KiB landing-table pre-store
now records only the contiguous VID (shadow) case; SYS landings are stored
per page as they are written.
Also extends the r384 page-table-shape refusal to every page of a virtual
zero fill (the r390 fill's first page was the client's buffer, so the
first-page-only check could not see the PTE pages it later covered).
Evidence: /tmp/opencode/r390-live.log (FILL-EXEC + sema-opadd lines),
/tmp/opencode/r390-guestram.raw (three zeroed PTE pages, CR3 walk),
pin log 12-pin_gr_stream-r390.log line 789 (the split runs).
Diffstat (limited to 'kernel/gsp_sema_service.c')
| -rw-r--r-- | kernel/gsp_sema_service.c | 156 |
1 files changed, 148 insertions, 8 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 419a54a..2d41bb0 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -4717,6 +4717,65 @@ static bool svc_fill_target_is_pt(struct nv_vgpu_device *vgpu, u64 addr) return hits >= SVC_PT_MIN; } +/* + * r391 per-page destination resolution for a VIRTUAL fill (the r390 + * guest-panic class). A device VA's pages are independent guest allocations + * - the pin stream shows split runs (va=0x7c36c7400000 -> 29 pages at + * 0x131b63000, the next run at 0x131a80000) - so resolving only the fill's + * first page and writing `total` bytes contiguously past it writes over + * whatever memory follows the first run. On r390 a 383620-byte zero fill + * did exactly that: the contiguous write covered the guest kernel's + * page-table pages (0x131ba1000/0x131ba2000/0x131bab000), the direct map + * grew holes, and the guest died with "Fatal exception in interrupt" + * (unmap_page_range / cpuacct / psi faults). svc_copy has always translated + * virtual operands per 4 KB page; the fill must do the same. + * + * Authority order mirrors the top-level resolution (and svc_copy's per-page + * dst): a fresh pin (guest pagemap truth) wins and repairs the landing + * table, then the landing table (the page a previous write of this VA + * landed on), then the resolver chain. A page with no resolution returns + * -ENOENT: the caller fails the fill loudly instead of writing through a + * contiguous guess. + */ +static int svc_fill_dst_page_pa(struct nv_vgpu_device *vgpu, u64 va, + u64 *pa_out, u32 *ap_out, u32 *prov_out) +{ + u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); + u64 pa = 0, lpa = 0; + u32 aper = 0, lap = 0; + + if (!svc_pin_lookup_any(page, &pa, &aper)) { + *prov_out = 1; + if (!svc_ledger_lookup(page, &lpa, &lap) && + (((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) != page) || + (u32)lap != (u32)aper)) { + svc_ledger_pin_repairs++; + if (svc_ledger_pin_repairs <= 32) + pr_info("sema-ledger: PIN-WINS fill-page va=0x%llx ledger=0x%llx/%u pin=0x%llx/%u\n", + page, + lpa & ~(u64)(SEMA_PAGE_SIZE - 1), + lap, pa, aper); + svc_ledger_store(page, pa, aper); + } + *pa_out = pa; + *ap_out = aper; + return 0; + } + if (!svc_ledger_lookup(page, &pa, &aper)) { + *prov_out = 2; + *pa_out = pa; + *ap_out = aper; + return 0; + } + if (svc_op_translate(vgpu, page, &pa, &aper)) { + *prov_out = 3; + *pa_out = pa; + *ap_out = aper; + return 0; + } + return -ENOENT; +} + static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem, u32 count, u32 phys, u32 aperture, u64 *done_out) { @@ -4909,20 +4968,29 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem, /* * C2 (data class) + m180 unification: a large virtual fill is a * write/read pair with the memset's readers like any H2D copy - - * record its landing pages (SYS and VID alike) so the engine fill - * and the D2H read of this VA use exactly the page the fill wrote. + * record its landing pages so the engine fill and the D2H read of + * this VA use exactly the page the fill wrote. Only the VID + * (shadow) case is contiguous; a SYS landing is recorded per page + * in the loop below (r391: the contiguous `addr + po` store also + * poisoned the table with pages the fill never wrote). */ if (!phys && !shadow && va0 >= SVC_VAS_FLOOR && - total >= (64u << 10)) { - u32 cap = (aperture == CE_APERTURE_VID) ? - SVC_APERTURE_FB : SVC_APERTURE_SYS; + total >= (64u << 10) && aperture == CE_APERTURE_VID) { u64 po; for (po = 0; po < total; po += SEMA_PAGE_SIZE) - svc_ledger_store(va0 + po, addr + po, cap); + svc_ledger_store(va0 + po, addr + po, + SVC_APERTURE_FB); } - if (aperture == CE_APERTURE_VID) { + /* + * r391: a virtual device destination is translated per 4 KB page + * (see svc_fill_dst_page_pa) - never written through the first + * page's PA contiguously. The whole-range shadow write below only + * applies to a resolution that is contiguous by construction. + */ + if (aperture == CE_APERTURE_VID && + !(va0 >= SVC_VAS_FLOOR && !phys && !shadow)) { u8 *base = vgpu->bars[1].vaddr; size_t size = svc_fb_shadow_size(vgpu); @@ -4942,14 +5010,77 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem, for (o = 0; o < total; ) { size_t chunk = total - o; size_t i; + u64 wpa = addr; + u32 wap = aperture; + bool per_page = !phys && !shadow && va0 >= SVC_VAS_FLOOR; if (chunk > sizeof(bounce)) chunk = sizeof(bounce); + /* + * r391: resolve THIS page's own landing (see + * svc_fill_dst_page_pa) and clamp the chunk to the page + * boundary - a virtual VA's pages are independent guest + * allocations and must never be written through the first + * page's PA contiguously. + */ + if (per_page) { + u32 off = (u32)((va0 + o) & (SEMA_PAGE_SIZE - 1)); + u64 ppa = 0; + u32 pap = 0, prov = 0; + int prc; + + chunk = min_t(size_t, chunk, SEMA_PAGE_SIZE - off); + prc = svc_fill_dst_page_pa(vgpu, va0 + o, &ppa, + &pap, &prov); + if (prc) { + if (done_out) + *done_out = (u64)o; + pr_err_ratelimited("vgsp: FILL-TRUNC va=0x%llx want=0x%llx done=0x%llx path=sema-fill (page 0x%llx unresolved)\n", + va0, (u64)total, (u64)o, + (va0 + o) & + ~(u64)(SEMA_PAGE_SIZE - 1)); + return -ENOENT; + } + wap = (pap == SVC_APERTURE_FB) ? + CE_APERTURE_VID : CE_APERTURE_SYS; + wpa = (ppa & ~(u64)(SEMA_PAGE_SIZE - 1)) + off; + /* + * r391 net: the r384 page-table-shape refusal + * applied to EVERY page of a virtual zero fill. + * The r390 fill's first page was the client's + * buffer, the later pages were the guest's PTE + * pages - the first-page-only check could not see + * them. A page-table-shaped guest-RAM page is never + * a legitimate zero-fill target: skip it loudly + * instead of zeroing it. Pin-backed resolutions + * are exempt (the guest's pagemap is truth there). + */ + if (wap == CE_APERTURE_SYS && value == 0 && prov != 1 && + svc_fill_target_is_pt(vgpu, + wpa & ~(u64)(SEMA_PAGE_SIZE - 1))) { + svc_fill_pt_refused++; + if (svc_fill_pt_refused <= 32) + pr_err("vgsp-sema: FILL-PT-REFUSED va=0x%llx pa=0x%llx prov=%u total=%zu (zero fill page is a page table; page skipped)\n", + va0 + o, wpa, prov, total); + o += chunk; + continue; + } + } for (i = 0; i + 8 <= chunk; i += 8) memcpy(bounce + i, pat, 8); if (i < chunk) memcpy(bounce + i, pat, chunk - i); - if (svc_write(vgpu, addr + o, bounce, chunk)) { + if (wap == CE_APERTURE_VID) { + u8 *base = vgpu->bars[1].vaddr; + size_t size = svc_fb_shadow_size(vgpu); + + if (!base || wpa + chunk > size) { + if (done_out) + *done_out = (u64)o; + return -EINVAL; + } + memcpy(base + wpa, bounce, chunk); + } else if (svc_write(vgpu, wpa, bounce, chunk)) { if (done_out) *done_out = (u64)o; if (total >= SVC_COPY_AUDIT_MIN) @@ -4957,6 +5088,15 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem, va0, (u64)total, (u64)o, -EIO); return -EIO; } + /* + * r391: record this page's landing (the readers follow the + * table) - SYS and VID alike, exactly like the copy path. + */ + if (per_page) + svc_ledger_store(va0 + o, + wpa & ~(u64)(SEMA_PAGE_SIZE - 1), + (wap == CE_APERTURE_VID) ? + SVC_APERTURE_FB : SVC_APERTURE_SYS); o += chunk; } if (done_out) |
