--- /dev/null
+From 34a4dd45cf210c04fee773b0dbc350aec285f03c Mon Sep 17 00:00:00 2001
+From: Matthew Brost <matthew.brost@intel.com>
+Date: Wed, 1 Jul 2026 18:24:34 -0700
+Subject: drm/xe: Fix PTE index in xe_vm_populate_pgtable() for chunked binds
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Matthew Brost <matthew.brost@intel.com>
+
+commit 34a4dd45cf210c04fee773b0dbc350aec285f03c upstream.
+
+xe_vm_populate_pgtable() indexed the source PTE array (update->pt_entries)
+by the per-call loop counter, assuming each call starts at the first entry
+of the update. That holds for the CPU bind path
+(xe_migrate_update_pgtables_cpu), which populates a whole update in a single
+call, but not for the GPU bind path: write_pgtable() splits an update into
+MAX_PTE_PER_SDI (510) sized MI_STORE_DATA_IMM chunks, invoking the populate
+callback once per chunk with an advancing qword_ofs but a fresh command-
+buffer destination pointer.
+
+As a result, every chunk after the first re-read pt_entries from index 0
+instead of from its true offset, so PTEs beyond the first 510 entries of a
+single update were programmed with the wrong physical pages, shifting the
+mapping by exactly MAX_PTE_PER_SDI pages.
+
+This stayed latent because a single update only exceeds 510 qwords when a
+large (e.g. 2M) region is bound as individual 4K PTEs rather than a single
+huge-page entry, which happens when the backing store is sufficiently
+fragmented. It was surfaced by the BO defrag path, which deliberately
+rebinds such fragmented ranges via the GPU bind path, producing
+deterministic data corruption offset by 510 pages.
+
+Index pt_entries by the chunk's absolute offset relative to update->ofs so
+both the CPU and GPU paths pick the correct entries.
+
+Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs")
+Cc: stable@vger.kernel.org
+Assisted-by: GitHub_Copilot:claude-opus-4.8
+Signed-off-by: Matthew Brost <matthew.brost@intel.com>
+Reviewed-by: Matthew Auld <matthew.auld@intel.com>
+Link: https://patch.msgid.link/20260702012434.3861171-1-matthew.brost@intel.com
+(cherry picked from commit e6f2d0b757c4fb577a513c577140109d1d292a9a)
+Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/drm/xe/xe_pt.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+--- a/drivers/gpu/drm/xe/xe_pt.c
++++ b/drivers/gpu/drm/xe/xe_pt.c
+@@ -868,12 +868,22 @@ xe_vm_populate_pgtable(struct xe_migrate
+ u64 *ptr = data;
+ u32 i;
+
++ /*
++ * @qword_ofs is the absolute entry offset within the page table, while
++ * @ptes is indexed relative to @update->ofs (its first entry). The GPU
++ * path (write_pgtable) splits a single update into MAX_PTE_PER_SDI-sized
++ * chunks, calling this with an advancing @qword_ofs but a fresh @data
++ * pointer per chunk, so translate back into a @ptes index rather than
++ * assuming the chunk starts at ptes[0].
++ */
+ for (i = 0; i < num_qwords; i++) {
++ u32 idx = qword_ofs - update->ofs + i;
++
+ if (map)
+ xe_map_wr(tile_to_xe(tile), map, (qword_ofs + i) *
+- sizeof(u64), u64, ptes[i].pte);
++ sizeof(u64), u64, ptes[idx].pte);
+ else
+- ptr[i] = ptes[i].pte;
++ ptr[i] = ptes[idx].pte;
+ }
+ }
+