summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 07:10:28 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 07:10:28 -0400
commit1cbe3d79d8f8d0bc81c81eecf3ca364ed3084f24 (patch)
treea70ce7b7779193a4d1bf436ad0009fea97509752 /kernel
parentefb096bc8a888a3e9931ae8011976ef7b4a1f577 (diff)
downloadopen-iov-1cbe3d79d8f8d0bc81c81eecf3ca364ed3084f24.tar.gz
open-iov-1cbe3d79d8f8d0bc81c81eecf3ca364ed3084f24.zip
gsp-phys: P6a debug triggers resolve the guest page HPA at trigger time
Triggers 6 (W2G) and 7 (G2H) previously used page-table slots pre-built once during the ladder's stage 3. A guest page's host physical address can change between translations (observed 0x13a591000 -> 0x3b8318000 for the same GPA), and a runtime PTE rewrite into an already-fetched 128-byte line is invisible (no invalidate path), so the later write-direction trigger validated against the wrong page and failed its data check. Both triggers now translate at trigger time and run through the same fresh-scratch-line path as forwarding, via a shared internal copy body; the exported forwarding API is unchanged and still requires gsp_phys_work_forward. A changed HPA is now an informational once-per-boot note rather than a failure; the probe-pattern guard still refuses stale pages. Trigger 8 and the stage-3 pre-built slots are unchanged. Live (run p6w3): GUEST G2H COPY OK gpa=0x12423d000 hpa=0x56ab3a000; GUEST W2G COPY OK with verify_ok=1; PTE selftest OK; guest SEEN_W2G; ladder REAL COPY OK; clean latched release (5 power-domain locks, rebound, WPR2 down, no rail cycles, pstore empty, same boot). The full smoke run hit the known guest cuInit/WPR2-already-up wedge at cdt; the trigger path itself is green.
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_booter.c246
-rw-r--r--kernel/gsp_phys_booter.h17
2 files changed, 166 insertions, 97 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 69a749a..94b0cae 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -311,20 +311,19 @@ bool gsp_phys_boot_requested(void)
/*
* P6a guest-memory windows (gsp_phys_guest_mem=1). Two more slots in the same
* 2 MB PD0 slot / small-PT page as the five ladder pages, so VA_GUEST_W takes
- * PT slot 0x105 and VA_GUEST_R slot 0x106. When a guest GPA was already
- * pinned before stage 3 (gsp-phys-guest-gpa, gsp_phys_rmcall_pdb_build runs
- * before any engine access) both slots are pre-built with the translated
- * guest HPA there: the engine's GMMU caches the PTE fetch line covering slots
- * 0x100..0x107 during the stage-4/P5 walks and no invalidate exists, so a
- * later runtime write to one of these slots could be invisible and the copy
- * would time out. Triggers 6/7 keep programming their slot as the fallback
- * path (a GPA pinned after stage 3 has only that route). Without a pin the
- * slots stay zero: a zero small PTE is invalid, so an engine fetch through
- * them faults (it can never fall through to a stale mapping from a previous
- * GPA). Trigger 7 programs the read slot and copies guest -> ladder DST;
- * trigger 6 programs the write slot and copies ladder SRC -> guest. The
- * walk-up indices (root/PD2/PD1/PD0) are unchanged because both VAs still
- * live inside the same 2 MB PD0 slot.
+ * PT slot 0x105 and VA_GUEST_R slot 0x106. These VAs are legacy: triggers 6/7
+ * no longer translate through them and no longer program them at runtime.
+ * Both directions now resolve the GPA->HPA at trigger time and run the copy
+ * through the P6b fresh-scratch-line pool (internal trigger variant of
+ * gsp_phys_backend_guest_ce_copy), one never-reused 128 B fetch line per
+ * pair, so a host PA that moved since the pin cannot be shadowed by an
+ * engine-cached PTE fetch line. The stage-3 pre-build below
+ * (gsp_phys_rmcall_pdb_build) is kept harmless: it may still fill the slots
+ * when a GPA was pinned before stage 3, but the triggers no longer depend on
+ * it. Without a pin the slots stay zero: a zero small PTE is invalid, so an
+ * engine fetch through them faults rather than falling through to a stale
+ * mapping from a previous GPA. The walk-up indices (root/PD2/PD1/PD0) are
+ * unchanged because both VAs still live inside the same 2 MB PD0 slot.
*/
#define GSP_PHYS_RMCALL_VA_GUEST_W (GSP_PHYS_RMCALL_VA_BASE + 0x5000)
#define GSP_PHYS_RMCALL_VA_GUEST_R (GSP_PHYS_RMCALL_VA_BASE + 0x6000)
@@ -1030,10 +1029,11 @@ static struct gsp_phys_boot_stage {
/*
* P6a guest-memory pin (debugfs gsp-phys-guest-gpa) and proof flags
* (ladder triggers 6/7; gsp_phys_guest_mem=1). One GPA per boot: the
- * PT slot is programmed with the translated HPA and the engine's GMMU
- * may cache that translation, so switching GPAs under a live channel
- * would risk a stale-TLB hit; a second different GPA is refused.
- * rmcall_guest_hpa keeps the last translation for the state dump,
+ * guest page's host PA may move under a live channel, so switching
+ * GPAs would risk a stale engine translation; a second different GPA
+ * is refused. Triggers 6/7 re-translate the GPA at trigger time and
+ * copy through fresh 128 B scratch lines, so the held rmcall_guest_hpa
+ * is only for the state dump and the once-per-boot change note.
* rmcall_guest_r_ok records that the G2H direction proved the
* translation, rmcall_guest_w2g_ok records the W2G proof, and
* rmcall_guest_selftest_ok records that trigger 8 saw a runtime
@@ -1042,6 +1042,7 @@ static struct gsp_phys_boot_stage {
u64 rmcall_guest_gpa; /* pinned guest GPA (page-aligned) */
bool rmcall_guest_gpa_valid;
u64 rmcall_guest_hpa; /* last GPA->HPA translation */
+ bool rmcall_guest_hpa_note_logged; /* "guest HPA changed" noted */
bool rmcall_guest_r_ok; /* trigger 7: guest page read proved */
bool rmcall_guest_w2g_ok; /* trigger 6: guest page write proved */
bool rmcall_guest_selftest_ok; /* trigger 8: fresh-line PTE write proved */
@@ -1604,6 +1605,7 @@ static void gsp_phys_rmcall_reset(void)
boot_stage.rmcall_guest_gpa = 0;
boot_stage.rmcall_guest_gpa_valid = false;
boot_stage.rmcall_guest_hpa = 0;
+ boot_stage.rmcall_guest_hpa_note_logged = false;
boot_stage.rmcall_guest_r_ok = false;
boot_stage.rmcall_guest_w2g_ok = false;
boot_stage.rmcall_guest_selftest_ok = false;
@@ -5904,15 +5906,14 @@ static void gsp_phys_rmcall_pdb_build(void)
* P6a guest windows: start from explicit zero (invalid PTE) for both
* slots, then, when the guest GPA is already pinned (it must be pinned
* before stage 3 for this), pre-build both slots with the translated
- * guest HPA. The engine's GMMU caches the PTE fetch line covering
- * slots 0x100..0x107 during the stage-4/P5 walks and this driver has
- * no invalidate, so programming 0x105/0x106 only at trigger time
- * could be invisible (the first live attempt timed out that way).
- * Building them here, before any engine access, makes the first fetch
- * of the line load the final translation. Triggers 6/7 still rewrite
- * their slot as the fallback path for a GPA pinned after stage 3. On
- * translation failure the slots stay zero so an engine fetch through
- * them still faults instead of ever reusing a stale mapping.
+ * guest HPA. Triggers 6/7 no longer depend on these slots or on this
+ * pre-build: they re-translate the GPA at trigger time and run both
+ * directions through the P6b fresh 128 B scratch-line pool, so a host
+ * PA that changed since this stage cannot be masked by the cached PTE
+ * fetch line covering slots 0x100..0x107. The pre-build is kept
+ * harmless. On translation failure the slots stay zero so an engine
+ * fetch through them still faults instead of ever reusing a stale
+ * mapping.
*/
put_unaligned_le64(0, (u8 *)gsp_phys_chunk_page_va(pt, 0) +
(size_t)((GSP_PHYS_RMCALL_VA_GUEST_W >> 12) & 0x1ffu) * 8);
@@ -6409,10 +6410,16 @@ static u64 gsp_phys_work_released;
static u64 gsp_phys_work_timed_out;
static u64 gsp_phys_work_refused;
-bool gsp_phys_backend_work_ready(void)
+/*
+ * Ladder/backend readiness shared by the P5 shadow API and the P6a trigger
+ * guest copies: READY backend session, live claim, no unload committed, a
+ * built client-memory channel, a work-submit token and all ladder VAs. The
+ * P5/P6b forwarding APIs additionally require gsp_phys_work_forward (see
+ * gsp_phys_backend_work_ready below); triggers 6/7 must run with forwarding
+ * off, so their guest-copy variant uses this gate directly.
+ */
+static bool gsp_phys_backend_ladder_ready(void)
{
- if (!gsp_phys_work_forward_enabled())
- return false;
if (gsp_phys_backend_state() != GSP_PHYS_BACKEND_READY)
return false;
if (!gsp_phys_gpu_claimed() || !boot_stage.rm_init_done ||
@@ -6427,6 +6434,13 @@ bool gsp_phys_backend_work_ready(void)
return false;
return true;
}
+
+bool gsp_phys_backend_work_ready(void)
+{
+ if (!gsp_phys_work_forward_enabled())
+ return false;
+ return gsp_phys_backend_ladder_ready();
+}
EXPORT_SYMBOL_GPL(gsp_phys_backend_work_ready);
bool gsp_phys_backend_work_operands(u64 *src_va, u64 *dst_va, u64 *sem_va,
@@ -6664,8 +6678,20 @@ static int gsp_phys_rmcall_guest_map_locked(u64 src_hpa, u64 dst_hpa,
return 0;
}
-int gsp_phys_backend_guest_ce_copy(u64 src_hpa, u32 src_off, u64 dst_hpa,
- u32 dst_off, u32 bytes, u32 poll_ms)
+/*
+ * Shared body of the P6b forwarding API and the P6a trigger variant. When
+ * need_work_forward is true this is exactly the exported forwarding gate
+ * (gsp_phys_work_forward required); when false it keeps every other gate
+ * (READY backend/ladder, gsp_phys_guest_mem, identity DMA domain, active
+ * guest-RAM file, operand bounds, fresh never-reused 128 B scratch line,
+ * module semaphore release, direct-map verify) and only drops the
+ * gsp_phys_work_forward requirement, so the P6a triggers can share the same
+ * fresh-line copy path with forwarding. *sem_out receives the last
+ * semaphore word seen on success or timeout.
+ */
+static int gsp_phys_guest_ce_copy_common(u64 src_hpa, u32 src_off, u64 dst_hpa,
+ u32 dst_off, u32 bytes, u32 poll_ms,
+ bool need_work_forward, u32 *sem_out)
{
struct gsp_phys_boot_chunk *sem =
&boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_SEM];
@@ -6679,9 +6705,16 @@ int gsp_phys_backend_guest_ce_copy(u64 src_hpa, u32 src_off, u64 dst_hpa,
* Every gate is fail-closed: the sema service's ladder fallback keeps
* the software release authoritative, so a refusal never loses work.
*/
- if (!gsp_phys_backend_work_ready()) {
- gsp_phys_guest_refused++;
- return -EAGAIN;
+ if (need_work_forward) {
+ if (!gsp_phys_backend_work_ready()) {
+ gsp_phys_guest_refused++;
+ return -EAGAIN;
+ }
+ } else {
+ if (!gsp_phys_backend_ladder_ready()) {
+ gsp_phys_guest_refused++;
+ return -EAGAIN;
+ }
}
if (!gsp_phys_guest_mem_enabled() ||
gsp_phys_dma_domain_identity() != 1 ||
@@ -6772,6 +6805,8 @@ int gsp_phys_backend_guest_ce_copy(u64 src_hpa, u32 src_off, u64 dst_hpa,
break;
if (time_after_eq(jiffies, deadline)) {
gsp_phys_guest_refused++;
+ if (sem_out)
+ *sem_out = poll;
pr_err("gsp-phys-work: GUEST CE COPY FAILED: timeout after %u ms (sem=0x%08x want=0x%08x) src_hpa=0x%llx dst_hpa=0x%llx bytes=%u\n",
poll_ms, poll, GSP_PHYS_RMCALL_SEM_PAYLOAD,
src_hpa, dst_hpa, bytes);
@@ -6795,15 +6830,41 @@ int gsp_phys_backend_guest_ce_copy(u64 src_hpa, u32 src_off, u64 dst_hpa,
gsp_phys_guest_verify_ok++;
gsp_phys_guest_fwd_ok++;
+ if (sem_out)
+ *sem_out = poll;
if (gsp_phys_guest_fwd_ok == 1 || (gsp_phys_guest_fwd_ok & 63) == 0)
pr_info("gsp-phys-work: GUEST CE COPY OK src_hpa=0x%llx dst_hpa=0x%llx bytes=%u sem=0x%08x verify_ok=1 copied=%llu\n",
src_hpa, dst_hpa, bytes, poll,
gsp_phys_guest_fwd_ok);
return 0;
}
+
+int gsp_phys_backend_guest_ce_copy(u64 src_hpa, u32 src_off, u64 dst_hpa,
+ u32 dst_off, u32 bytes, u32 poll_ms)
+{
+ return gsp_phys_guest_ce_copy_common(src_hpa, src_off, dst_hpa, dst_off,
+ bytes, poll_ms, true, NULL);
+}
EXPORT_SYMBOL_GPL(gsp_phys_backend_guest_ce_copy);
/*
+ * P6a trigger variant: identical to the exported forwarding call except that
+ * the gsp_phys_work_forward parameter gate is dropped (every other guard and
+ * the whole fresh-scratch-line copy/verify body are shared). Triggers 6/7 use
+ * this so they re-translate the pinned GPA at trigger time and never depend
+ * on the stage-3 pre-built 0x105/0x106 slots. *sem_out carries the observed
+ * module-semaphore word for the trigger's OK/FAILED logs.
+ */
+static int gsp_phys_backend_guest_ce_copy_trigger(u64 src_hpa, u32 src_off,
+ u64 dst_hpa, u32 dst_off,
+ u32 bytes, u32 poll_ms,
+ u32 *sem_out)
+{
+ return gsp_phys_guest_ce_copy_common(src_hpa, src_off, dst_hpa, dst_off,
+ bytes, poll_ms, false, sem_out);
+}
+
+/*
* Stage 4: the corrected token sequence - BIND (COPY0) -> GPFIFO_SCHEDULE ->
* TSG_GPFIFO_SCHEDULE -> SET_WORK_SUBMIT_TOKEN_NOTIF_INDEX ->
* GET_WORK_SUBMIT_TOKEN - then the first real work item: write the CE method
@@ -7090,37 +7151,52 @@ static int gsp_phys_rmcall_stage5(void __iomem *bar0)
/*
* P6a triggers 6/7 (gsp_phys_guest_mem=1): prove the real CE engine can read
- * and write one pinned guest RAM page. The direction order is deliberate:
- * trigger 7 (G2H, guest read) is the translation proof - it resolves the GPA
- * through the mdev's VFIO type-1 domain, requires the physical device's own
- * domain to be IOMMU_DOMAIN_IDENTITY (the module programs host PAs into the
- * engine page tables), programs the read window PT slot, poisons the ladder
- * DST page and copies 4 KB guest -> DST. Only after G2H proved the
- * translation may trigger 6 (W2G, guest write) program the write slot, fill
- * the ladder SRC page with the 0x5a^(i&0xff) pattern and copy 4 KB SRC ->
- * guest. Both run the shared bounded submit/poll helper; refusals are logged
- * explicitly and never retried.
+ * and write one pinned guest RAM page. Both directions resolve the GPA->HPA
+ * through the mdev's VFIO type-1 domain at trigger time and run the copy
+ * through the internal trigger variant of the P6b fresh-scratch-line guest
+ * copy (gsp_phys_backend_guest_ce_copy_trigger): one whole never-reused
+ * 128 B GMMU PTE fetch line per pair, so a host PA that changed since the pin
+ * (observed live: the same guest GPA translated to a different HPA between
+ * ladder stage 3 and trigger time) lands in a line the engine has never
+ * fetched and cannot be shadowed by an engine-cached translation. The
+ * stage-3 pre-built 0x105/0x106 slots are no longer used by these triggers.
+ *
+ * The direction order is deliberate: trigger 7 (G2H, guest read) is the
+ * translation proof - it requires the physical device's own domain to be
+ * IOMMU_DOMAIN_IDENTITY (the module programs host PAs into the engine page
+ * tables), poisons the ladder DST page and copies 4 KB guest -> DST. Only
+ * after G2H proved the translation may trigger 6 (W2G, guest write) fill the
+ * ladder SRC page with the 0x5a^(i&0xff) pattern and copy 4 KB SRC -> guest.
+ * Both use the shared bounded submit/poll/verify body; refusals are logged
+ * explicitly and never retried. An HPA that differs from the last successful
+ * translation is informational (one "guest HPA changed" note per boot), not
+ * a failure: the fresh-line re-translation is the point of this path, while
+ * the guest probe pattern guard below still decides whether the page may be
+ * written.
*
- * The direct-map comparisons below (memcmp against __va(hpa)) are safe: the
+ * The direct-map comparisons (memcmp against __va(hpa)) are safe: the
* physical device is in an identity domain (checked above) and
* gsp_phys_gpa_to_hpa() validated the PFN (pfn_valid) of the pinned host PA,
- * so __va(hpa) is a valid kernel mapping. The PT slot program + dma_wmb()
- * precede the submit under the human-driven debugfs interface. When the GPA
- * was pinned before stage 3, gsp_phys_rmcall_pdb_build() already pre-built
- * both slots before any engine walk (the cached-fetch-line fix); the trigger
- * rewrite below remains the fallback path for a GPA pinned after stage 3 and
- * re-programs exactly one slot per run.
+ * so __va(hpa) is a valid kernel mapping.
*/
+static void gsp_phys_rmcall_guest_hpa_note(u64 gpa, u64 hpa)
+{
+ if (!boot_stage.rmcall_guest_hpa ||
+ boot_stage.rmcall_guest_hpa == hpa ||
+ boot_stage.rmcall_guest_hpa_note_logged)
+ return;
+ boot_stage.rmcall_guest_hpa_note_logged = true;
+ pr_info("gsp-phys-boot: rm-call: guest HPA changed: gpa=0x%llx was 0x%llx now 0x%llx (re-translated at trigger time; informational, not a failure)\n",
+ gpa, boot_stage.rmcall_guest_hpa, hpa);
+}
+
static int gsp_phys_rmcall_guest_g2h(void __iomem *bar0)
{
- struct gsp_phys_boot_chunk *pt =
- &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_PT];
struct gsp_phys_boot_chunk *dst =
&boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_DST];
u64 gpa = boot_stage.rmcall_guest_gpa;
u64 hpa = 0;
u64 pte = 0;
- u32 pt_idx = (u32)((GSP_PHYS_RMCALL_VA_GUEST_R >> 12) & 0x1ffu);
u32 sem = 0, i;
u8 *dst_page;
int dom, ret;
@@ -7139,29 +7215,24 @@ static int gsp_phys_rmcall_guest_g2h(void __iomem *bar0)
return -EIO;
}
+ /* pte is log-only now: the copy runs through a fresh scratch line. */
pte = gsp_phys_rmcall_pte(hpa);
- boot_stage.rmcall_guest_hpa = hpa;
+ gsp_phys_rmcall_guest_hpa_note(gpa, hpa);
/* Poison the destination so a no-op copy cannot pass by accident. */
- memset(gsp_phys_chunk_page_va(dst, 0), 0xc3,
- GSP_PHYS_RMCALL_COPY_SIZE);
-
- put_unaligned_le64(pte, (u8 *)gsp_phys_chunk_page_va(pt, 0) +
- (size_t)pt_idx * 8);
- dma_wmb();
+ dst_page = gsp_phys_chunk_page_va(dst, 0);
+ memset(dst_page, 0xc3, GSP_PHYS_RMCALL_COPY_SIZE);
- ret = gsp_phys_rmcall_ce_copy_va(bar0, GSP_PHYS_RMCALL_VA_GUEST_R,
- GSP_PHYS_RMCALL_VA_DST,
- GSP_PHYS_RMCALL_VA_SEM,
- GSP_PHYS_RMCALL_COPY_SIZE, &sem);
+ ret = gsp_phys_backend_guest_ce_copy_trigger(
+ hpa, 0, (u64)gsp_phys_chunk_page_dma(dst, 0), 0,
+ GSP_PHYS_RMCALL_COPY_SIZE, GSP_PHYS_RMCALL_GUEST_POLL_MS, &sem);
if (ret) {
pr_err("gsp-phys-boot: rm-call: GUEST G2H COPY FAILED: hpa=0x%llx pte=0x%016llx: CE submit/poll failed (%d, sem=0x%08x want=0x%08x)\n",
hpa, pte, ret, sem, GSP_PHYS_RMCALL_SEM_PAYLOAD);
return -EIO;
}
- if (memcmp(gsp_phys_chunk_page_va(dst, 0), __va(hpa),
- GSP_PHYS_RMCALL_COPY_SIZE) != 0) {
+ if (memcmp(dst_page, __va(hpa), GSP_PHYS_RMCALL_COPY_SIZE) != 0) {
pr_err("gsp-phys-boot: rm-call: GUEST G2H COPY FAILED: hpa=0x%llx pte=0x%016llx: semaphore released (sem=0x%08x) but DST does not match the guest page at hpa=0x%llx (gpa=0x%llx)\n",
hpa, pte, sem, hpa, gpa);
return -EIO;
@@ -7175,7 +7246,6 @@ static int gsp_phys_rmcall_guest_g2h(void __iomem *bar0)
* the translated page is the guard that makes a wrong translation
* detectable before any engine write to guest memory is allowed.
*/
- dst_page = gsp_phys_chunk_page_va(dst, 0);
for (i = 0; i < GSP_PHYS_RMCALL_COPY_SIZE; i++) {
if (dst_page[i] != (u8)(0xa5 ^ (i & 0xff))) {
/* Dump both heads to tell a bad translation from a bad copy. */
@@ -7185,6 +7255,7 @@ static int gsp_phys_rmcall_guest_g2h(void __iomem *bar0)
}
}
+ boot_stage.rmcall_guest_hpa = hpa;
boot_stage.rmcall_guest_r_ok = true;
pr_info("gsp-phys-boot: rm-call: GUEST G2H COPY OK gpa=0x%llx hpa=0x%llx bytes=%u sem=0x%08x\n",
gpa, hpa, GSP_PHYS_RMCALL_COPY_SIZE, sem);
@@ -7193,14 +7264,11 @@ static int gsp_phys_rmcall_guest_g2h(void __iomem *bar0)
static int gsp_phys_rmcall_guest_w2g(void __iomem *bar0)
{
- struct gsp_phys_boot_chunk *pt =
- &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_PT];
struct gsp_phys_boot_chunk *src =
&boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_SRC];
u64 gpa = boot_stage.rmcall_guest_gpa;
u64 hpa = 0;
u64 pte = 0;
- u32 pt_idx = (u32)((GSP_PHYS_RMCALL_VA_GUEST_W >> 12) & 0x1ffu);
u32 sem = 0, i;
u8 *src_page;
int dom, ret;
@@ -7230,14 +7298,16 @@ static int gsp_phys_rmcall_guest_w2g(void __iomem *bar0)
return -EIO;
}
+ /* pte is log-only now: the copy runs through a fresh scratch line. */
pte = gsp_phys_rmcall_pte(hpa);
- boot_stage.rmcall_guest_hpa = hpa;
+ gsp_phys_rmcall_guest_hpa_note(gpa, hpa);
/*
* The page at hpa must still carry the guest probe pattern that G2H
* proved (P6a debugfs contract with tools/test/gpage_test.c); if it
* changed, the re-translate produced a stale/incorrect hpa and the
- * engine must not write to guest memory.
+ * engine must not write to guest memory. A changed HPA itself is not a
+ * failure (the note above records it): the pattern is the guard.
*/
for (i = 0; i < GSP_PHYS_RMCALL_COPY_SIZE; i++) {
if (((const u8 *)__va(hpa))[i] != (u8)(0xa5 ^ (i & 0xff))) {
@@ -7252,14 +7322,9 @@ static int gsp_phys_rmcall_guest_w2g(void __iomem *bar0)
for (i = 0; i < GSP_PHYS_RMCALL_COPY_SIZE; i++)
src_page[i] = (u8)(0x5a ^ (i & 0xff));
- put_unaligned_le64(pte, (u8 *)gsp_phys_chunk_page_va(pt, 0) +
- (size_t)pt_idx * 8);
- dma_wmb();
-
- ret = gsp_phys_rmcall_ce_copy_va(bar0, GSP_PHYS_RMCALL_VA_SRC,
- GSP_PHYS_RMCALL_VA_GUEST_W,
- GSP_PHYS_RMCALL_VA_SEM,
- GSP_PHYS_RMCALL_COPY_SIZE, &sem);
+ ret = gsp_phys_backend_guest_ce_copy_trigger(
+ (u64)gsp_phys_chunk_page_dma(src, 0), 0, hpa, 0,
+ GSP_PHYS_RMCALL_COPY_SIZE, GSP_PHYS_RMCALL_GUEST_POLL_MS, &sem);
if (ret) {
pr_err("gsp-phys-boot: rm-call: GUEST W2G COPY FAILED: hpa=0x%llx pte=0x%016llx: CE submit/poll failed (%d, sem=0x%08x want=0x%08x)\n",
hpa, pte, ret, sem, GSP_PHYS_RMCALL_SEM_PAYLOAD);
@@ -7272,6 +7337,7 @@ static int gsp_phys_rmcall_guest_w2g(void __iomem *bar0)
return -EIO;
}
+ boot_stage.rmcall_guest_hpa = hpa;
boot_stage.rmcall_guest_w2g_ok = true;
pr_info("gsp-phys-boot: rm-call: GUEST W2G COPY OK gpa=0x%llx hpa=0x%llx bytes=%u sem=0x%08x\n",
gpa, hpa, GSP_PHYS_RMCALL_COPY_SIZE, sem);
@@ -7281,14 +7347,14 @@ static int gsp_phys_rmcall_guest_w2g(void __iomem *bar0)
/*
* P6a trigger 8 (gsp_phys_guest_mem=1): characterize a runtime small-PTE
* write on a fresh GMMU fetch line. Slots 0x100..0x106 all share the one
- * 64 B line that the stage-4/P5 walks already cached, so the trigger 6/7
- * runtime writes to 0x105/0x106 may be invisible to the engine; slot 0x183
- * sits on a different, never-walked line. The self-test builds the SRC page
- * PT entry for that slot at runtime, runs the same 4 KB CE copy path as the
- * guest triggers (VA_GUEST_S -> VA_DST) and verifies DST against the 0x3c
- * pattern SRC. No guest GPA is needed: this measures write visibility, not
- * the guest translation. It uses the shared bounded submit/poll helper and
- * never retries.
+ * 64 B line that the stage-4/P5 walks already cached (triggers 6/7 no longer
+ * use 0x105/0x106: they run through the fresh-line scratch pool above), so
+ * the self-test picks slot 0x183 on a different, never-walked line. The
+ * self-test builds the SRC page PT entry for that slot at runtime, runs the
+ * same 4 KB CE copy path as the guest triggers (VA_GUEST_S -> VA_DST) and
+ * verifies DST against the 0x3c pattern SRC. No guest GPA is needed: this
+ * measures write visibility, not the guest translation. It uses the shared
+ * bounded submit/poll helper and never retries.
*/
static int gsp_phys_rmcall_guest_selftest(void __iomem *bar0)
{
diff --git a/kernel/gsp_phys_booter.h b/kernel/gsp_phys_booter.h
index cc3b935..ada3280 100644
--- a/kernel/gsp_phys_booter.h
+++ b/kernel/gsp_phys_booter.h
@@ -317,13 +317,16 @@ int gsp_phys_boot_rm_unload(void __iomem *bar0);
* gsp_phys_guest_mem=1 and a GPA pinned through gsp-phys-guest-gpa,
* writing 7 (G2H, guest read) and then 6 (W2G, guest write) additionally
* run one 4 KB CE copy each against that guest RAM page: the GPA is
- * translated through the active mdev's VFIO type-1 domain, the physical
- * device's domain must be identity, the corresponding channel PDB slot
- * (0x106 read / 0x105 write) is programmed with the translated HPA, and
- * the result is verified with a direct-map memcmp. Both directions are
- * gated on a live READY claim, ladder stage >= 3 and the pinned GPA;
- * every refusal is a logged no-op and no guest byte is touched unless
- * G2H already proved the translation.
+ * re-translated through the active mdev's VFIO type-1 domain at trigger
+ * time, the physical device's domain must be identity, and the copy runs
+ * through the P6b fresh 128 B scratch-line path (one never-reused line
+ * per pair; the internal trigger variant of the guest-copy backend, so
+ * gsp_phys_work_forward is not required), so a host PA that changed since
+ * the pin cannot be shadowed by an engine-cached PTE fetch. The result is
+ * verified with a direct-map memcmp. Both directions are gated on a live
+ * READY claim, ladder stage >= 3 and the pinned GPA; every refusal is a
+ * logged no-op and no guest byte is touched unless G2H already proved
+ * the translation.
* - "gsp-phys-guest-gpa" (write-only, mode 0200): pin the one guest RAM
* GPA ("0x<hex>", 4 KB page-aligned) that the rm-call guest directions
* may touch. One GPA per boot (a second different GPA is refused: