summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-20 23:11:59 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-20 23:11:59 -0400
commit89a6497e78027be58069af84a7a77a5beaf3a971 (patch)
tree3587a84485a8d22a037d706ccd3ebd59d1be8257
parent03275eae56dc37e2b6bb4d50052648256af80f60 (diff)
downloadopen-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).
-rw-r--r--kernel/gsp_sema_service.c156
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)