Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 00/25] CPU binds and ULLS on migration queue
@ 2026-09-03 23:58 Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 01/25] drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns Matthew Brost
                   ` (27 more replies)
  0 siblings, 28 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

We now have data demonstrating the need for CPU binds and ULLS on the
migration queue, based on results generated from [1].

On BMG, measurements show that when the GPU is continuously processing
faults, copy jobs run approximately 30–40µs faster (depending on the
test case) with ULLS compared to traditional GuC submission with SLPC
enabled on the migration queue. Startup from a cold GPU shows an even
larger speedup. Given the critical nature of fault performance, ULLS
appears to be a worthwhile feature.

In addition to driver telemetry, UMD compute benchmarks consistently
show multiple GB/s improvement in pagefault benchmarks with ULLS enabled.

ULLS will consume more power (not yet measured) due to a continuously
running batch on the paging engine. However, compute UMDs already do
this on engines exposed to users, so this seems like a worthwhile
tradeoff. To mitigate power concerns, ULLS will exit after a period of
time in which no faults have been processed.

CPU binds are required for ULLS to function, as the migration queue
needs exclusive access to the paging hardware engine. Thus, CPU binds
are included here.

Beyond being a requirement for ULLS, CPU binds should also reduce
VM-bind latency, provide clearer multi-tile and TLB-invalidation
layering, reduce pressure on GuC during fault storms as it is bypassed,
and decouple kernel binds from unrelated copy/clear jobs—especially
beneficial when faults are serviced in parallel. In a parallel-faulting
test case, average bind time was reduced by approximately 15µs. In the
worst case, 2MB copy time (~60–140µs) × (number of pagefault threads −
1) of latency would otherwise be added to a single fault. Reducing this
latency increases overall throughput of the fault handler.

This series can be merged in phases:

Phase 1: CPU binds (patches 1–13)
Phase 2: CPU-bind components and multi-tile relayers (patches 14–17)
Phase 3: ULLS on the migration execution queue (patches 18–25)

v2:
 - Use delayed worker to exit ULLS mode in an effort to save on power
 - Various other cleanups
v3:
 - CPU bind component, multi-tile relayer
 - Split CPU bind patches in many small patches
v4:
 - Rebase, address feedback, add ULLS doc patch

Matt

[1] https://patchwork.freedesktop.org/series/149811/

Matthew Brost (25):
  drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear
    vfuns
  drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper
  drm/xe: Decouple exec queue idle check from LRC
  drm/xe: Add job count to GuC exec queue snapshot
  drm/xe: Update xe_bo_put_deferred arguments to include writeback flag
  drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
  drm/xe: Update scheduler job layer to support PT jobs
  drm/xe: Add helpers to access PT ops
  drm/xe: Add struct xe_pt_job_ops
  drm/xe: Update GuC submission backend to run PT jobs
  drm/xe: Store level in struct xe_vm_pgtable_update
  drm/xe: Don't use migrate exec queue for page fault binds
  drm/xe: Enable CPU binds for jobs
  drm/xe: Remove unused arguments from xe_migrate_pt_update_ops
  drm/xe: Make bind queues operate cross-tile
  drm/xe: Add CPU bind layer
  drm/xe: Add device flag to enable PT mirroring across tiles
  drm/xe: Add xe_hw_engine_write_ring_tail
  drm/xe: Add ULLS support to LRC
  drm/xe: Add ULLS migration job support to migration layer
  drm/xe: Add ULLS migration job support to ring ops
  drm/xe: Add ULLS migration job support to GuC submission
  drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
  drm/xe: Add modparam to enable / disable ULLS on migrate queue
  drm/xe: Document ULLS for migration jobs

 Documentation/gpu/xe/xe_migrate.rst      |   3 +
 drivers/gpu/drm/xe/Makefile              |   1 +
 drivers/gpu/drm/xe/xe_bo.c               |   8 +-
 drivers/gpu/drm/xe/xe_bo.h               |  11 +-
 drivers/gpu/drm/xe/xe_bo_types.h         |   2 -
 drivers/gpu/drm/xe/xe_cpu_bind.c         | 295 +++++++++
 drivers/gpu/drm/xe/xe_cpu_bind.h         | 118 ++++
 drivers/gpu/drm/xe/xe_debugfs.c          |   1 +
 drivers/gpu/drm/xe/xe_defaults.h         |   1 +
 drivers/gpu/drm/xe/xe_device.c           |   6 +
 drivers/gpu/drm/xe/xe_device_types.h     |  11 +
 drivers/gpu/drm/xe/xe_drm_client.c       |   2 +-
 drivers/gpu/drm/xe/xe_exec_queue.c       | 164 ++---
 drivers/gpu/drm/xe/xe_exec_queue.h       |  16 +-
 drivers/gpu/drm/xe/xe_exec_queue_types.h |  20 +-
 drivers/gpu/drm/xe/xe_guc_submit.c       |  78 ++-
 drivers/gpu/drm/xe/xe_guc_submit_types.h |   2 +
 drivers/gpu/drm/xe/xe_hw_engine.c        |  20 +
 drivers/gpu/drm/xe/xe_hw_engine.h        |   1 +
 drivers/gpu/drm/xe/xe_lrc.c              |  51 ++
 drivers/gpu/drm/xe/xe_lrc.h              |   3 +
 drivers/gpu/drm/xe/xe_lrc_types.h        |   4 +
 drivers/gpu/drm/xe/xe_migrate.c          | 694 +++++++++-----------
 drivers/gpu/drm/xe/xe_migrate.h          |  94 +--
 drivers/gpu/drm/xe/xe_module.c           |   4 +
 drivers/gpu/drm/xe/xe_module.h           |   1 +
 drivers/gpu/drm/xe/xe_pagefault.c        |   3 +
 drivers/gpu/drm/xe/xe_pci.c              |   2 +
 drivers/gpu/drm/xe/xe_pci_types.h        |   3 +-
 drivers/gpu/drm/xe/xe_pt.c               | 782 ++++++++++++++---------
 drivers/gpu/drm/xe/xe_pt.h               |  12 +-
 drivers/gpu/drm/xe/xe_pt_types.h         |  49 +-
 drivers/gpu/drm/xe/xe_ring_ops.c         |  32 +
 drivers/gpu/drm/xe/xe_sched_job.c        | 101 ++-
 drivers/gpu/drm/xe/xe_sched_job_types.h  |  36 +-
 drivers/gpu/drm/xe/xe_sync.c             |  20 +-
 drivers/gpu/drm/xe/xe_tlb_inval_job.c    |  28 +-
 drivers/gpu/drm/xe/xe_tlb_inval_job.h    |   4 +-
 drivers/gpu/drm/xe/xe_vm.c               | 240 +++----
 drivers/gpu/drm/xe/xe_vm.h               |   3 +
 drivers/gpu/drm/xe/xe_vm_types.h         |  12 +-
 41 files changed, 1751 insertions(+), 1187 deletions(-)
 create mode 100644 drivers/gpu/drm/xe/xe_cpu_bind.c
 create mode 100644 drivers/gpu/drm/xe/xe_cpu_bind.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v4 01/25] drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper Matthew Brost
                   ` (26 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Francois Dugast

Remove the xe_migrate_pt_update argument from the populate and clear
vfuns. This structure will not be available in run_job, where CPU binds
will be implemented. The populate path no longer needs it, and the clear
path already uses the VM field instead.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Francois Dugast <francois.dugast@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-2-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c |  9 +++++----
 drivers/gpu/drm/xe/xe_migrate.h | 12 +++++-------
 drivers/gpu/drm/xe/xe_pt.c      | 12 +++++-------
 3 files changed, 15 insertions(+), 18 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index ff45c24d8889..149c5fa654e6 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -1760,6 +1760,7 @@ static void write_pgtable(struct xe_tile *tile, struct xe_bb *bb, u64 ppgtt_ofs,
 			  struct xe_migrate_pt_update *pt_update)
 {
 	const struct xe_migrate_pt_update_ops *ops = pt_update->ops;
+	struct xe_vm *vm = pt_update->vops->vm;
 	u32 chunk;
 	u32 ofs = update->ofs, size = update->qwords;
 
@@ -1791,10 +1792,10 @@ static void write_pgtable(struct xe_tile *tile, struct xe_bb *bb, u64 ppgtt_ofs,
 		bb->cs[bb->len++] = lower_32_bits(addr);
 		bb->cs[bb->len++] = upper_32_bits(addr);
 		if (pt_op->bind)
-			ops->populate(pt_update, tile, NULL, bb->cs + bb->len,
+			ops->populate(tile, NULL, bb->cs + bb->len,
 				      ofs, chunk, update);
 		else
-			ops->clear(pt_update, tile, NULL, bb->cs + bb->len,
+			ops->clear(vm, tile, NULL, bb->cs + bb->len,
 				   ofs, chunk, update);
 
 		bb->len += chunk * 2;
@@ -1851,12 +1852,12 @@ xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 				&pt_op->entries[j];
 
 			if (pt_op->bind)
-				ops->populate(pt_update, m->tile,
+				ops->populate(m->tile,
 					      &update->pt_bo->vmap, NULL,
 					      update->ofs, update->qwords,
 					      update);
 			else
-				ops->clear(pt_update, m->tile,
+				ops->clear(vm, m->tile,
 					   &update->pt_bo->vmap, NULL,
 					   update->ofs, update->qwords, update);
 		}
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index a9acc62f78f0..2ec9de896dfe 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -40,7 +40,6 @@ enum xe_migrate_copy_dir {
 struct xe_migrate_pt_update_ops {
 	/**
 	 * @populate: Populate a command buffer or page-table with ptes.
-	 * @pt_update: Embeddable callback argument.
 	 * @tile: The tile for the current operation.
 	 * @map: struct iosys_map into the memory to be populated.
 	 * @pos: If @map is NULL, map into the memory to be populated.
@@ -52,13 +51,12 @@ struct xe_migrate_pt_update_ops {
 	 * page-table system to populate command buffers or shared
 	 * page-tables with PTEs.
 	 */
-	void (*populate)(struct xe_migrate_pt_update *pt_update,
-			 struct xe_tile *tile, struct iosys_map *map,
+	void (*populate)(struct xe_tile *tile, struct iosys_map *map,
 			 void *pos, u32 ofs, u32 num_qwords,
 			 const struct xe_vm_pgtable_update *update);
 	/**
 	 * @clear: Clear a command buffer or page-table with ptes.
-	 * @pt_update: Embeddable callback argument.
+	 * @vm: VM being updated
 	 * @tile: The tile for the current operation.
 	 * @map: struct iosys_map into the memory to be populated.
 	 * @pos: If @map is NULL, map into the memory to be populated.
@@ -70,9 +68,9 @@ struct xe_migrate_pt_update_ops {
 	 * page-table system to populate command buffers or shared
 	 * page-tables with PTEs.
 	 */
-	void (*clear)(struct xe_migrate_pt_update *pt_update,
-		      struct xe_tile *tile, struct iosys_map *map,
-		      void *pos, u32 ofs, u32 num_qwords,
+	void (*clear)(struct xe_vm *vm, struct xe_tile *tile,
+		      struct iosys_map *map, void *pos, u32 ofs,
+		      u32 num_qwords,
 		      const struct xe_vm_pgtable_update *update);
 
 	/**
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 5d990c1c3740..854c0a59af71 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -1092,9 +1092,8 @@ bool xe_pt_zap_ptes_range(struct xe_tile *tile, struct xe_vm *vm,
 }
 
 static void
-xe_vm_populate_pgtable(struct xe_migrate_pt_update *pt_update, struct xe_tile *tile,
-		       struct iosys_map *map, void *data,
-		       u32 qword_ofs, u32 num_qwords,
+xe_vm_populate_pgtable(struct xe_tile *tile, struct iosys_map *map,
+		       void *data, u32 qword_ofs, u32 num_qwords,
 		       const struct xe_vm_pgtable_update *update)
 {
 	struct xe_pt_entry *ptes = update->pt_entries;
@@ -2008,12 +2007,11 @@ static unsigned int xe_pt_stage_unbind(struct xe_tile *tile,
 }
 
 static void
-xe_migrate_clear_pgtable_callback(struct xe_migrate_pt_update *pt_update,
-				  struct xe_tile *tile, struct iosys_map *map,
-				  void *ptr, u32 qword_ofs, u32 num_qwords,
+xe_migrate_clear_pgtable_callback(struct xe_vm *vm, struct xe_tile *tile,
+				  struct iosys_map *map, void *ptr,
+				  u32 qword_ofs, u32 num_qwords,
 				  const struct xe_vm_pgtable_update *update)
 {
-	struct xe_vm *vm = pt_update->vops->vm;
 	u64 empty = __xe_pt_empty_pte(tile, vm, update->pt->level);
 	int i;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 01/25] drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:15   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 03/25] drm/xe: Decouple exec queue idle check from LRC Matthew Brost
                   ` (25 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Francois Dugast

Add the xe_migrate_update_pgtables_cpu_execute helper, which performs
the CPU-side page-table update. This will support implementing CPU
binds, as the submission backend can call this helper once a bind job’s
dependencies are resolved. While here, add assertions to provide basic
sanity checks on tht function arguments.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Francois Dugast <francois.dugast@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-3-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c | 58 ++++++++++++++++++++-------------
 1 file changed, 35 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 149c5fa654e6..084bfee0d047 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -1819,6 +1819,38 @@ struct migrate_test_params {
 	container_of(_priv, struct migrate_test_params, base)
 #endif
 
+static void
+xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
+				       const struct xe_migrate_pt_update_ops *ops,
+				       struct xe_vm_pgtable_update_op *pt_op,
+				       int num_ops)
+{
+	u32 j, i;
+
+	for (j = 0; j < num_ops; ++j, ++pt_op) {
+		for (i = 0; i < pt_op->num_entries; i++) {
+			const struct xe_vm_pgtable_update *update =
+				&pt_op->entries[i];
+
+			xe_tile_assert(tile, update);
+			xe_tile_assert(tile, update->pt_bo);
+			xe_tile_assert(tile, !iosys_map_is_null(&update->pt_bo->vmap));
+
+			if (pt_op->bind)
+				ops->populate(tile, &update->pt_bo->vmap,
+					      NULL, update->ofs, update->qwords,
+					      update);
+			else
+				ops->clear(vm, tile, &update->pt_bo->vmap,
+					   NULL, update->ofs, update->qwords,
+					   update);
+		}
+	}
+
+	trace_xe_vm_cpu_bind(vm);
+	xe_device_wmb(vm->xe);
+}
+
 static struct dma_fence *
 xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 			       struct xe_migrate_pt_update *pt_update)
@@ -1831,7 +1863,6 @@ xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 	struct xe_vm_pgtable_update_ops *pt_update_ops =
 		&pt_update->vops->pt_update_ops[pt_update->tile_id];
 	int err;
-	u32 i, j;
 
 	if (XE_TEST_ONLY(test && test->force_gpu))
 		return ERR_PTR(-ETIME);
@@ -1843,28 +1874,9 @@ xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 			return ERR_PTR(err);
 	}
 
-	for (i = 0; i < pt_update_ops->num_ops; ++i) {
-		const struct xe_vm_pgtable_update_op *pt_op =
-			&pt_update_ops->ops[i];
-
-		for (j = 0; j < pt_op->num_entries; j++) {
-			const struct xe_vm_pgtable_update *update =
-				&pt_op->entries[j];
-
-			if (pt_op->bind)
-				ops->populate(m->tile,
-					      &update->pt_bo->vmap, NULL,
-					      update->ofs, update->qwords,
-					      update);
-			else
-				ops->clear(vm, m->tile,
-					   &update->pt_bo->vmap, NULL,
-					   update->ofs, update->qwords, update);
-		}
-	}
-
-	trace_xe_vm_cpu_bind(vm);
-	xe_device_wmb(vm->xe);
+	xe_migrate_update_pgtables_cpu_execute(vm, m->tile, ops,
+					       pt_update_ops->ops,
+					       pt_update_ops->num_ops);
 
 	return dma_fence_get_stub();
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 03/25] drm/xe: Decouple exec queue idle check from LRC
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 01/25] drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 04/25] drm/xe: Add job count to GuC exec queue snapshot Matthew Brost
                   ` (24 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Stuart Summers

We already maintain a job count for each exec queue, so simplify the idle
check to rely on the job count rather than the LRC state. This decouples
exec queues from LRC-based backends and avoids unnecessarily coupling idle
detection to backend-specific implementation details.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Stuart Summers <stuart.summers@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-4-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_exec_queue.c | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c
index c4213bb9c137..a894551c3ea6 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.c
+++ b/drivers/gpu/drm/xe/xe_exec_queue.c
@@ -1516,20 +1516,7 @@ bool xe_exec_queue_is_lr(struct xe_exec_queue *q)
  */
 bool xe_exec_queue_is_idle(struct xe_exec_queue *q)
 {
-	if (xe_exec_queue_is_parallel(q)) {
-		int i;
-
-		for (i = 0; i < q->width; ++i) {
-			if (xe_lrc_seqno(q->lrc[i]) !=
-			    q->lrc[i]->fence_ctx.next_seqno - 1)
-				return false;
-		}
-
-		return true;
-	}
-
-	return xe_lrc_seqno(q->lrc[0]) ==
-		q->lrc[0]->fence_ctx.next_seqno - 1;
+	return !atomic_read(&q->job_cnt);
 }
 
 /**
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 04/25] drm/xe: Add job count to GuC exec queue snapshot
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (2 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 03/25] drm/xe: Decouple exec queue idle check from LRC Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 05/25] drm/xe: Update xe_bo_put_deferred arguments to include writeback flag Matthew Brost
                   ` (23 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Stuart Summers

Add the job count to the GuC exec queue snapshot, as this is useful
debug information.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Stuart Summers <stuart.summers@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-5-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_guc_submit.c       | 2 ++
 drivers/gpu/drm/xe/xe_guc_submit_types.h | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index 99d8c807ff05..cf306568ef30 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -3658,6 +3658,7 @@ xe_guc_exec_queue_snapshot_capture(struct xe_exec_queue *q)
 	snapshot->logical_mask = q->logical_mask;
 	snapshot->width = q->width;
 	snapshot->refcount = kref_read(&q->refcount);
+	snapshot->jobcount = atomic_read(&q->job_cnt);
 	snapshot->sched_timeout = sched->base.timeout;
 	snapshot->sched_props.timeslice_us = q->sched_props.timeslice_us;
 	snapshot->sched_props.preempt_timeout_us =
@@ -3730,6 +3731,7 @@ xe_guc_exec_queue_snapshot_print(struct xe_guc_submit_exec_queue_snapshot *snaps
 	drm_printf(p, "\tLogical mask: 0x%x\n", snapshot->logical_mask);
 	drm_printf(p, "\tWidth: %d\n", snapshot->width);
 	drm_printf(p, "\tRef: %d\n", snapshot->refcount);
+	drm_printf(p, "\tJob count: %d\n", snapshot->jobcount);
 	drm_printf(p, "\tTimeout: %ld (ms)\n", snapshot->sched_timeout);
 	drm_printf(p, "\tTimeslice: %u (us)\n",
 		   snapshot->sched_props.timeslice_us);
diff --git a/drivers/gpu/drm/xe/xe_guc_submit_types.h b/drivers/gpu/drm/xe/xe_guc_submit_types.h
index 7824f61b1290..8271702e692e 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_submit_types.h
@@ -77,6 +77,8 @@ struct xe_guc_submit_exec_queue_snapshot {
 	u16 width;
 	/** @refcount: ref count of this exec queue */
 	u32 refcount;
+	/** @jobcount: job count of this exec queue */
+	u32 jobcount;
 	/**
 	 * @sched_timeout: the time after which a job is removed from the
 	 * scheduler.
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 05/25] drm/xe: Update xe_bo_put_deferred arguments to include writeback flag
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (3 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 04/25] drm/xe: Add job count to GuC exec queue snapshot Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC Matthew Brost
                   ` (22 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Francois Dugast

Update the xe_bo_put_deferred arguments to include a writeback flag,
which indicates whether the BO was added to the deferred list. This is
useful when the caller needs to take additional actions after the BO has
been queued for deferred release.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Francois Dugast <francois.dugast@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-6-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_bo.h         | 10 ++++++++--
 drivers/gpu/drm/xe/xe_drm_client.c |  2 +-
 drivers/gpu/drm/xe/xe_pt.c         |  2 +-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h
index e8081af5bfc1..6092f305ea8e 100644
--- a/drivers/gpu/drm/xe/xe_bo.h
+++ b/drivers/gpu/drm/xe/xe_bo.h
@@ -495,6 +495,8 @@ void __xe_bo_release_dummy(struct kref *kref);
  * @bo: The bo to put.
  * @deferred: List to which to add the buffer object if we cannot put, or
  * NULL if the function is to put unconditionally.
+ * @added: BO was added to deferred list, written back to caller, can be NULL if
+ * writeback is not needed. Only set to true when added, never set to false.
  *
  * Since the final freeing of an object includes both sleeping and (!)
  * memory allocation in the dma_resv individualization, it's not ok
@@ -514,7 +516,8 @@ void __xe_bo_release_dummy(struct kref *kref);
  * false otherwise.
  */
 static inline bool
-xe_bo_put_deferred(struct xe_bo *bo, struct llist_head *deferred)
+xe_bo_put_deferred(struct xe_bo *bo, struct llist_head *deferred,
+		   bool *added)
 {
 	if (!deferred) {
 		xe_bo_put(bo);
@@ -524,6 +527,9 @@ xe_bo_put_deferred(struct xe_bo *bo, struct llist_head *deferred)
 	if (!kref_put(&bo->ttm.base.refcount, __xe_bo_release_dummy))
 		return false;
 
+	if (added)
+		*added = true;
+
 	return llist_add(&bo->freed, deferred);
 }
 
@@ -540,7 +546,7 @@ xe_bo_put_async(struct xe_bo *bo)
 {
 	struct xe_bo_dev *bo_device = &xe_bo_device(bo)->bo_device;
 
-	if (xe_bo_put_deferred(bo, &bo_device->async_list))
+	if (xe_bo_put_deferred(bo, &bo_device->async_list, NULL))
 		schedule_work(&bo_device->async_free);
 }
 
diff --git a/drivers/gpu/drm/xe/xe_drm_client.c b/drivers/gpu/drm/xe/xe_drm_client.c
index e116fb562c4c..4c424d1c6721 100644
--- a/drivers/gpu/drm/xe/xe_drm_client.c
+++ b/drivers/gpu/drm/xe/xe_drm_client.c
@@ -256,7 +256,7 @@ static void show_meminfo(struct drm_printer *p, struct drm_file *file)
 			xe_assert(xef->xe, !list_empty(&bo->client_link));
 		}
 
-		xe_bo_put_deferred(bo, &deferred);
+		xe_bo_put_deferred(bo, &deferred, NULL);
 	}
 	spin_unlock(&client->bos_lock);
 
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 854c0a59af71..3170df1f3fbf 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -213,7 +213,7 @@ void xe_pt_destroy(struct xe_pt *pt, u32 flags, struct llist_head *deferred)
 
 	XE_WARN_ON(!list_empty(&pt->bo->ttm.base.gpuva.list));
 	xe_bo_unpin(pt->bo);
-	xe_bo_put_deferred(pt->bo, deferred);
+	xe_bo_put_deferred(pt->bo, deferred, NULL);
 
 	if (pt->level > 0 && pt->num_live) {
 		struct xe_pt_dir *pt_dir = as_xe_pt_dir(pt);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (4 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 05/25] drm/xe: Update xe_bo_put_deferred arguments to include writeback flag Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:18   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs Matthew Brost
                   ` (21 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add XE_BO_FLAG_PUT_VM_ASYNC, which indicates that an async BO put must
also drop an additional reference to the BO’s VM. This is useful when a
kernel BO, one that does not normally hold a VM reference, needs to be
put asynchronously, ensuring the shared dma-resv object does not
disappear before the BO.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-7-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_bo.c | 8 +++++++-
 drivers/gpu/drm/xe/xe_bo.h | 1 +
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
index dde309821237..5cde5dff2d48 100644
--- a/drivers/gpu/drm/xe/xe_bo.c
+++ b/drivers/gpu/drm/xe/xe_bo.c
@@ -3989,8 +3989,14 @@ void xe_bo_put_commit(struct llist_head *deferred)
 	if (!freed)
 		return;
 
-	llist_for_each_entry_safe(bo, next, freed, freed)
+	llist_for_each_entry_safe(bo, next, freed, freed) {
+		struct xe_vm *vm = bo->vm;
+		bool async = bo->flags & XE_BO_FLAG_PUT_VM_ASYNC;
+
 		drm_gem_object_free(&bo->ttm.base.refcount);
+		if (async)
+			xe_vm_put(vm);
+	}
 }
 
 static void xe_bo_dev_work_func(struct work_struct *work)
diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h
index 6092f305ea8e..b810da83e9ef 100644
--- a/drivers/gpu/drm/xe/xe_bo.h
+++ b/drivers/gpu/drm/xe/xe_bo.h
@@ -54,6 +54,7 @@
 #define XE_BO_FLAG_FORCE_USER_VRAM	BIT(25)
 #define XE_BO_FLAG_NO_COMPRESSION	BIT(26)
 #define XE_BO_FLAG_NEEDS_1G		BIT(27)
+#define XE_BO_FLAG_PUT_VM_ASYNC		BIT(28)
 
 /* this one is trigger internally only */
 #define XE_BO_FLAG_INTERNAL_TEST	BIT(30)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (5 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:25   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 08/25] drm/xe: Add helpers to access PT ops Matthew Brost
                   ` (20 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Update the scheduler job layer to support PT jobs. PT jobs are executed
entirely on the CPU and do not require LRC fences or a batch address.
Repurpose the LRC fence storage to hold PT‑job arguments and update the
scheduler job layer to distinguish between PT jobs and jobs that require
an LRC.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-8-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_sched_job.c       | 93 ++++++++++++++++---------
 drivers/gpu/drm/xe/xe_sched_job_types.h | 31 ++++++++-
 2 files changed, 90 insertions(+), 34 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_sched_job.c b/drivers/gpu/drm/xe/xe_sched_job.c
index a4fa00632a30..cfd3c50bb682 100644
--- a/drivers/gpu/drm/xe/xe_sched_job.c
+++ b/drivers/gpu/drm/xe/xe_sched_job.c
@@ -26,19 +26,22 @@ static struct kmem_cache *xe_sched_job_parallel_slab;
 
 int __init xe_sched_job_module_init(void)
 {
+	struct xe_sched_job *job;
+	size_t size;
+
+	size = struct_size(job, ptrs, 1);
 	xe_sched_job_slab =
-		kmem_cache_create("xe_sched_job",
-				  sizeof(struct xe_sched_job) +
-				  sizeof(struct xe_job_ptrs), 0,
+		kmem_cache_create("xe_sched_job", size, 0,
 				  SLAB_HWCACHE_ALIGN, NULL);
 	if (!xe_sched_job_slab)
 		return -ENOMEM;
 
+	size = max_t(size_t,
+		     struct_size(job, ptrs,
+				 XE_HW_ENGINE_MAX_INSTANCE),
+		     struct_size(job, pt_update, 1));
 	xe_sched_job_parallel_slab =
-		kmem_cache_create("xe_sched_job_parallel",
-				  sizeof(struct xe_sched_job) +
-				  sizeof(struct xe_job_ptrs) *
-				  XE_HW_ENGINE_MAX_INSTANCE, 0,
+		kmem_cache_create("xe_sched_job_parallel", size, 0,
 				  SLAB_HWCACHE_ALIGN, NULL);
 	if (!xe_sched_job_parallel_slab) {
 		kmem_cache_destroy(xe_sched_job_slab);
@@ -84,6 +87,9 @@ static void xe_sched_job_free_fences(struct xe_sched_job *job)
 {
 	int i;
 
+	if (job->is_pt_job)
+		return;
+
 	for (i = 0; i < job->q->width; ++i) {
 		struct xe_job_ptrs *ptrs = &job->ptrs[i];
 
@@ -93,10 +99,23 @@ static void xe_sched_job_free_fences(struct xe_sched_job *job)
 	}
 }
 
+/**
+ * xe_sched_job_create() - Create a scheduler job
+ * @q: exec queue to create the scheduler job for
+ * @batch_addr: array of batch addresses for the job; must match the width of
+ * @q, or NULL to indicate a PT job that does not require a batch address
+ *
+ * Create a scheduler job for submission.
+ *
+ * Context: Reclaim
+ *
+ * Return: a &xe_sched_job object on success, or an ERR_PTR on failure.
+ */
 struct xe_sched_job *xe_sched_job_create(struct xe_exec_queue *q,
 					 u64 *batch_addr)
 {
 	bool is_migration = xe_sched_job_is_migration(q);
+	struct xe_device *xe = gt_to_xe(q->gt);
 	struct xe_sched_job *job;
 	int err;
 	int i;
@@ -105,6 +124,9 @@ struct xe_sched_job *xe_sched_job_create(struct xe_exec_queue *q,
 	/* only a kernel context can submit a vm-less job */
 	XE_WARN_ON(!q->vm && !(q->flags & EXEC_QUEUE_FLAG_KERNEL));
 
+	xe_assert(xe, batch_addr ||
+		  q->flags & (EXEC_QUEUE_FLAG_VM | EXEC_QUEUE_FLAG_MIGRATE));
+
 	job = job_alloc(xe_exec_queue_is_parallel(q) || is_migration);
 	if (!job)
 		return ERR_PTR(-ENOMEM);
@@ -119,34 +141,39 @@ struct xe_sched_job *xe_sched_job_create(struct xe_exec_queue *q,
 	if (err)
 		goto err_free;
 
-	for (i = 0; i < q->width; ++i) {
-		struct dma_fence *fence = xe_lrc_alloc_seqno_fence();
-		struct dma_fence_chain *chain;
-
-		if (IS_ERR(fence)) {
-			err = PTR_ERR(fence);
-			goto err_sched_job;
+	if (!batch_addr) {
+		job->fence = dma_fence_get_stub();
+		job->is_pt_job = true;
+	} else {
+		for (i = 0; i < q->width; ++i) {
+			struct dma_fence *fence = xe_lrc_alloc_seqno_fence();
+			struct dma_fence_chain *chain;
+
+			if (IS_ERR(fence)) {
+				err = PTR_ERR(fence);
+				goto err_sched_job;
+			}
+			job->ptrs[i].lrc_fence = fence;
+
+			if (i + 1 == q->width)
+				continue;
+
+			chain = dma_fence_chain_alloc();
+			if (!chain) {
+				err = -ENOMEM;
+				goto err_sched_job;
+			}
+			job->ptrs[i].chain_fence = chain;
 		}
-		job->ptrs[i].lrc_fence = fence;
 
-		if (i + 1 == q->width)
-			continue;
+		width = q->width;
+		if (is_migration)
+			width = 2;
 
-		chain = dma_fence_chain_alloc();
-		if (!chain) {
-			err = -ENOMEM;
-			goto err_sched_job;
-		}
-		job->ptrs[i].chain_fence = chain;
+		for (i = 0; i < width; ++i)
+			job->ptrs[i].batch_addr = batch_addr[i];
 	}
 
-	width = q->width;
-	if (is_migration)
-		width = 2;
-
-	for (i = 0; i < width; ++i)
-		job->ptrs[i].batch_addr = batch_addr[i];
-
 	atomic_inc(&q->job_cnt);
 	xe_pm_runtime_get_noresume(job_to_xe(job));
 	trace_xe_sched_job_create(job);
@@ -246,7 +273,7 @@ bool xe_sched_job_completed(struct xe_sched_job *job)
 void xe_sched_job_arm(struct xe_sched_job *job)
 {
 	struct xe_exec_queue *q = job->q;
-	struct dma_fence *fence, *prev;
+	struct dma_fence *fence = job->fence, *prev;
 	struct xe_vm *vm = q->vm;
 	u64 seqno = 0;
 	int i;
@@ -266,6 +293,9 @@ void xe_sched_job_arm(struct xe_sched_job *job)
 		job->ring_ops_flush_tlb = true;
 	}
 
+	if (job->is_pt_job)
+		goto arm;
+
 	/* Arm the pre-allocated fences */
 	for (i = 0; i < q->width; prev = fence, ++i) {
 		struct dma_fence_chain *chain;
@@ -286,6 +316,7 @@ void xe_sched_job_arm(struct xe_sched_job *job)
 		fence = &chain->base;
 	}
 
+arm:
 	job->fence = dma_fence_get(fence);	/* Pairs with put in scheduler */
 	drm_sched_job_arm(&job->drm);
 }
diff --git a/drivers/gpu/drm/xe/xe_sched_job_types.h b/drivers/gpu/drm/xe/xe_sched_job_types.h
index 0490b1247a6e..5e1824c36c74 100644
--- a/drivers/gpu/drm/xe/xe_sched_job_types.h
+++ b/drivers/gpu/drm/xe/xe_sched_job_types.h
@@ -10,10 +10,29 @@
 
 #include <drm/gpu_scheduler.h>
 
-struct xe_exec_queue;
 struct dma_fence;
 struct dma_fence_chain;
 
+struct xe_exec_queue;
+struct xe_migrate_pt_update_ops;
+struct xe_pt_job_ops;
+struct xe_tile;
+struct xe_vm;
+
+/**
+ * struct xe_pt_update_args - PT update arguments
+ */
+struct xe_pt_update_args {
+	/** @vm: VM which is being bound */
+	struct xe_vm *vm;
+	/** @tile: Tile which page tables belong to */
+	struct xe_tile *tile;
+	/** @ops: Migrate PT update ops */
+	const struct xe_migrate_pt_update_ops *ops;
+	/** @pt_job_ops: PT job ops state */
+	struct xe_pt_job_ops *pt_job_ops;
+};
+
 /**
  * struct xe_job_ptrs - Per hw engine instance data
  */
@@ -71,8 +90,14 @@ struct xe_sched_job {
 	bool restore_replay;
 	/** @last_replay: last job being replayed */
 	bool last_replay;
-	/** @ptrs: per instance pointers. */
-	struct xe_job_ptrs ptrs[];
+	/** @is_pt_job: is a PT job */
+	bool is_pt_job;
+	union {
+		/** @ptrs: per instance pointers. */
+		DECLARE_FLEX_ARRAY(struct xe_job_ptrs, ptrs);
+		/** @pt_update: PT update arguments */
+		DECLARE_FLEX_ARRAY(struct xe_pt_update_args, pt_update);
+	};
 };
 
 struct xe_sched_job_snapshot {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 08/25] drm/xe: Add helpers to access PT ops
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (6 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 09/25] drm/xe: Add struct xe_pt_job_ops Matthew Brost
                   ` (19 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Francois Dugast

Add helpers to access PT ops, making it easier to shuffle the location of
the ops structures without requiring widespread code changes.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Francois Dugast <francois.dugast@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-9-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_pt.c | 65 ++++++++++++++++++++++++++------------
 1 file changed, 45 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 3170df1f3fbf..e560f167fdf1 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -2078,13 +2078,37 @@ xe_pt_commit_prepare_unbind(struct xe_vma *vma,
 	}
 }
 
+static struct xe_vm_pgtable_update_op *
+to_pt_op(struct xe_vm_pgtable_update_ops *pt_update_ops, u32 op_idx)
+{
+	return &pt_update_ops->ops[op_idx];
+}
+
+static u32
+get_current_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
+{
+	return pt_update_ops->current_op;
+}
+
+static struct xe_vm_pgtable_update_op *
+to_current_pt_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
+{
+	return to_pt_op(pt_update_ops, get_current_op(pt_update_ops));
+}
+
+static void
+incr_current_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
+{
+	++pt_update_ops->current_op;
+}
+
 static void
 xe_pt_update_ops_rfence_interval(struct xe_vm_pgtable_update_ops *pt_update_ops,
 				 u64 start, u64 end)
 {
 	u64 last;
-	u32 current_op = pt_update_ops->current_op;
-	struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[current_op];
+	struct xe_vm_pgtable_update_op *pt_op =
+		to_current_pt_op(pt_update_ops);
 	int i, level = 0;
 
 	for (i = 0; i < pt_op->num_entries; i++) {
@@ -2119,8 +2143,8 @@ static int bind_op_prepare(struct xe_vm *vm, struct xe_tile *tile,
 			   struct xe_vm_pgtable_update_ops *pt_update_ops,
 			   struct xe_vma *vma, bool invalidate_on_bind)
 {
-	u32 current_op = pt_update_ops->current_op;
-	struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[current_op];
+	struct xe_vm_pgtable_update_op *pt_op =
+		to_current_pt_op(pt_update_ops);
 	int err;
 
 	xe_tile_assert(tile, !xe_vma_is_cpu_addr_mirror(vma));
@@ -2149,7 +2173,7 @@ static int bind_op_prepare(struct xe_vm *vm, struct xe_tile *tile,
 		xe_pt_update_ops_rfence_interval(pt_update_ops,
 						 xe_vma_start(vma),
 						 xe_vma_end(vma));
-		++pt_update_ops->current_op;
+		incr_current_op(pt_update_ops);
 		pt_update_ops->needs_svm_lock |= xe_vma_is_userptr(vma);
 
 		/*
@@ -2192,8 +2216,8 @@ static int bind_range_prepare(struct xe_vm *vm, struct xe_tile *tile,
 			      struct xe_vm_pgtable_update_ops *pt_update_ops,
 			      struct xe_vma *vma, struct xe_svm_range *range)
 {
-	u32 current_op = pt_update_ops->current_op;
-	struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[current_op];
+	struct xe_vm_pgtable_update_op *pt_op =
+		to_current_pt_op(pt_update_ops);
 	int err;
 
 	xe_tile_assert(tile, xe_vma_is_cpu_addr_mirror(vma));
@@ -2217,7 +2241,7 @@ static int bind_range_prepare(struct xe_vm *vm, struct xe_tile *tile,
 		xe_pt_update_ops_rfence_interval(pt_update_ops,
 						 xe_svm_range_start(range),
 						 xe_svm_range_end(range));
-		++pt_update_ops->current_op;
+		incr_current_op(pt_update_ops);
 		pt_update_ops->needs_svm_lock = true;
 
 		pt_op->vma = vma;
@@ -2235,8 +2259,8 @@ static int unbind_op_prepare(struct xe_tile *tile,
 			     struct xe_vma *vma)
 {
 	struct xe_device *xe = tile_to_xe(tile);
-	u32 current_op = pt_update_ops->current_op;
-	struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[current_op];
+	struct xe_vm_pgtable_update_op *pt_op =
+		to_current_pt_op(pt_update_ops);
 	int err;
 
 	if (!((vma->tile_present | vma->tile_staged) & BIT(tile->id)))
@@ -2275,7 +2299,7 @@ static int unbind_op_prepare(struct xe_tile *tile,
 				pt_op->num_entries, false);
 	xe_pt_update_ops_rfence_interval(pt_update_ops, xe_vma_start(vma),
 					 xe_vma_end(vma));
-	++pt_update_ops->current_op;
+	incr_current_op(pt_update_ops);
 	pt_update_ops->needs_svm_lock |= xe_vma_is_userptr(vma);
 	pt_update_ops->needs_invalidation = true;
 
@@ -2315,8 +2339,8 @@ static int unbind_range_prepare(struct xe_vm *vm,
 				struct xe_vm_pgtable_update_ops *pt_update_ops,
 				struct xe_svm_range *range)
 {
-	u32 current_op = pt_update_ops->current_op;
-	struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[current_op];
+	struct xe_vm_pgtable_update_op *pt_op =
+		to_current_pt_op(pt_update_ops);
 
 	if (!(range->tile_present & BIT(tile->id)))
 		return 0;
@@ -2337,7 +2361,7 @@ static int unbind_range_prepare(struct xe_vm *vm,
 				pt_op->num_entries, false);
 	xe_pt_update_ops_rfence_interval(pt_update_ops, xe_svm_range_start(range),
 					 xe_svm_range_end(range));
-	++pt_update_ops->current_op;
+	incr_current_op(pt_update_ops);
 	pt_update_ops->needs_svm_lock = true;
 	pt_update_ops->needs_invalidation |= xe_vm_has_scratch(vm) ||
 		xe_vm_has_valid_gpu_mapping(tile, range->tile_present,
@@ -2494,7 +2518,7 @@ int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops)
 			return err;
 	}
 
-	xe_tile_assert(tile, pt_update_ops->current_op <=
+	xe_tile_assert(tile, get_current_op(pt_update_ops) <=
 		       pt_update_ops->num_ops);
 
 #ifdef TEST_VM_OPS_ERROR
@@ -2727,7 +2751,7 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 	lockdep_assert_held(&vm->lock);
 	xe_vm_assert_held(vm);
 
-	if (!pt_update_ops->current_op) {
+	if (!get_current_op(pt_update_ops)) {
 		xe_tile_assert(tile, xe_vm_in_fault_mode(vm));
 
 		return dma_fence_get_stub();
@@ -2795,8 +2819,9 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 	}
 
 	/* Point of no return - VM killed if failure after this */
-	for (i = 0; i < pt_update_ops->current_op; ++i) {
-		struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[i];
+	for (i = 0; i < get_current_op(pt_update_ops); ++i) {
+		struct xe_vm_pgtable_update_op *pt_op =
+			to_pt_op(pt_update_ops, i);
 
 		xe_pt_commit(pt_op->vma, pt_op->entries,
 			     pt_op->num_entries, &pt_update_ops->deferred);
@@ -2920,9 +2945,9 @@ void xe_pt_update_ops_abort(struct xe_tile *tile, struct xe_vma_ops *vops)
 
 	for (i = pt_update_ops->num_ops - 1; i >= 0; --i) {
 		struct xe_vm_pgtable_update_op *pt_op =
-			&pt_update_ops->ops[i];
+			to_pt_op(pt_update_ops, i);
 
-		if (!pt_op->vma || i >= pt_update_ops->current_op)
+		if (!pt_op->vma || i >= get_current_op(pt_update_ops))
 			continue;
 
 		if (pt_op->bind)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 09/25] drm/xe: Add struct xe_pt_job_ops
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (7 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 08/25] drm/xe: Add helpers to access PT ops Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs Matthew Brost
                   ` (18 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add struct xe_pt_job_ops, a dynamically refcounted object that contains
the information required to issue a CPU bind via a job after the initial
bind IOCTL returns.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-10-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c  |  10 +--
 drivers/gpu/drm/xe/xe_pt.c       | 133 ++++++++++++++++++++++++++-----
 drivers/gpu/drm/xe/xe_pt.h       |   4 +
 drivers/gpu/drm/xe/xe_pt_types.h |  27 +++++--
 drivers/gpu/drm/xe/xe_vm.c       |  10 +--
 5 files changed, 149 insertions(+), 35 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 084bfee0d047..92e378a45e53 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -1875,7 +1875,7 @@ xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 	}
 
 	xe_migrate_update_pgtables_cpu_execute(vm, m->tile, ops,
-					       pt_update_ops->ops,
+					       pt_update_ops->pt_job_ops->ops,
 					       pt_update_ops->num_ops);
 
 	return dma_fence_get_stub();
@@ -1902,7 +1902,7 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 	bool usm = is_migrate && xe->info.has_usm;
 
 	for (i = 0; i < pt_update_ops->num_ops; ++i) {
-		struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[i];
+		struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->pt_job_ops->ops[i];
 		struct xe_vm_pgtable_update *updates = pt_op->entries;
 
 		num_updates += pt_op->num_entries;
@@ -1971,7 +1971,7 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 
 			for (; i < pt_update_ops->num_ops; ++i) {
 				struct xe_vm_pgtable_update_op *pt_op =
-					&pt_update_ops->ops[i];
+					&pt_update_ops->pt_job_ops->ops[i];
 				struct xe_vm_pgtable_update *updates = pt_op->entries;
 
 				for (; j < pt_op->num_entries; ++j, ++current_update, ++idx) {
@@ -2008,7 +2008,7 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 			(page_ofs / sizeof(u64)) * XE_PAGE_SIZE;
 		for (i = 0; i < pt_update_ops->num_ops; ++i) {
 			struct xe_vm_pgtable_update_op *pt_op =
-				&pt_update_ops->ops[i];
+				&pt_update_ops->pt_job_ops->ops[i];
 			struct xe_vm_pgtable_update *updates = pt_op->entries;
 
 			for (j = 0; j < pt_op->num_entries; ++j) {
@@ -2026,7 +2026,7 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 
 		for (i = 0; i < pt_update_ops->num_ops; ++i) {
 			struct xe_vm_pgtable_update_op *pt_op =
-				&pt_update_ops->ops[i];
+				&pt_update_ops->pt_job_ops->ops[i];
 			struct xe_vm_pgtable_update *updates = pt_op->entries;
 
 			for (j = 0; j < pt_op->num_entries; ++j)
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index e560f167fdf1..4c277153398e 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -206,6 +206,7 @@ unsigned int xe_pt_shift(unsigned int level)
  */
 void xe_pt_destroy(struct xe_pt *pt, u32 flags, struct llist_head *deferred)
 {
+	bool added = false;
 	int i;
 
 	if (!pt)
@@ -213,7 +214,20 @@ void xe_pt_destroy(struct xe_pt *pt, u32 flags, struct llist_head *deferred)
 
 	XE_WARN_ON(!list_empty(&pt->bo->ttm.base.gpuva.list));
 	xe_bo_unpin(pt->bo);
-	xe_bo_put_deferred(pt->bo, deferred, NULL);
+	xe_bo_put_deferred(pt->bo, deferred, &added);
+	if (added) {
+		xe_assert(pt->bo->vm->xe, !kref_read(&pt->bo->ttm.base.refcount));
+
+		/*
+		 * We need the VM present until the BO is destroyed as it shares
+		 * a dma-resv and BO destroy is async. Reinit BO refcount so
+		 * xe_bo_put_async can be used when the PT job ops refcount goes
+		 * to zero.
+		 */
+		xe_vm_get(pt->bo->vm);
+		pt->bo->flags |= XE_BO_FLAG_PUT_VM_ASYNC;
+		kref_init(&pt->bo->ttm.base.refcount);
+	}
 
 	if (pt->level > 0 && pt->num_live) {
 		struct xe_pt_dir *pt_dir = as_xe_pt_dir(pt);
@@ -2081,13 +2095,13 @@ xe_pt_commit_prepare_unbind(struct xe_vma *vma,
 static struct xe_vm_pgtable_update_op *
 to_pt_op(struct xe_vm_pgtable_update_ops *pt_update_ops, u32 op_idx)
 {
-	return &pt_update_ops->ops[op_idx];
+	return &pt_update_ops->pt_job_ops->ops[op_idx];
 }
 
 static u32
 get_current_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
 {
-	return pt_update_ops->current_op;
+	return pt_update_ops->pt_job_ops->current_op;
 }
 
 static struct xe_vm_pgtable_update_op *
@@ -2099,7 +2113,7 @@ to_current_pt_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
 static void
 incr_current_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
 {
-	++pt_update_ops->current_op;
+	++pt_update_ops->pt_job_ops->current_op;
 }
 
 static void
@@ -2473,8 +2487,7 @@ static int op_prepare(struct xe_vm *vm,
 static void
 xe_pt_update_ops_init(struct xe_vm_pgtable_update_ops *pt_update_ops)
 {
-	init_llist_head(&pt_update_ops->deferred);
-	pt_update_ops->current_op = 0;
+	pt_update_ops->pt_job_ops->current_op = 0;
 	pt_update_ops->start = ~0x0ull;
 	pt_update_ops->last = 0x0ull;
 	pt_update_ops->needs_svm_lock = false;
@@ -2824,7 +2837,8 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 			to_pt_op(pt_update_ops, i);
 
 		xe_pt_commit(pt_op->vma, pt_op->entries,
-			     pt_op->num_entries, &pt_update_ops->deferred);
+			     pt_op->num_entries,
+			     &pt_update_ops->pt_job_ops->deferred);
 		pt_op->vma = NULL;	/* skip in xe_pt_update_ops_abort */
 	}
 
@@ -2912,19 +2926,8 @@ void xe_pt_update_ops_fini(struct xe_tile *tile, struct xe_vma_ops *vops)
 {
 	struct xe_vm_pgtable_update_ops *pt_update_ops =
 		&vops->pt_update_ops[tile->id];
-	int i;
 
 	xe_page_reclaim_entries_put(pt_update_ops->prl.entries);
-
-	lockdep_assert_held(&vops->vm->lock);
-	xe_vm_assert_held(vops->vm);
-
-	for (i = 0; i < pt_update_ops->current_op; ++i) {
-		struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->ops[i];
-
-		xe_pt_free_bind(pt_op->entries, pt_op->num_entries);
-	}
-	xe_bo_put_commit(&vops->pt_update_ops[tile->id].deferred);
 }
 
 /**
@@ -2961,3 +2964,97 @@ void xe_pt_update_ops_abort(struct xe_tile *tile, struct xe_vma_ops *vops)
 
 	xe_pt_update_ops_fini(tile, vops);
 }
+
+/**
+ * xe_pt_job_ops_alloc() - Allocate PT job ops
+ * @num_ops: Number of VM PT update ops
+ *
+ * Allocate PT job ops and internal array of VM PT update ops.
+ *
+ * Return: Pointer to PT job ops or NULL
+ */
+struct xe_pt_job_ops *xe_pt_job_ops_alloc(u32 num_ops)
+{
+	struct xe_pt_job_ops *pt_job_ops;
+
+	pt_job_ops = kmalloc(sizeof(*pt_job_ops), GFP_KERNEL);
+	if (!pt_job_ops)
+		return NULL;
+
+	pt_job_ops->ops = kvmalloc_array(num_ops, sizeof(*pt_job_ops->ops),
+					 GFP_KERNEL);
+	if (!pt_job_ops->ops) {
+		kfree(pt_job_ops);
+		return NULL;
+	}
+
+	pt_job_ops->current_op = 0;
+	kref_init(&pt_job_ops->refcount);
+	init_llist_head(&pt_job_ops->deferred);
+
+	return pt_job_ops;
+}
+
+/**
+ * xe_pt_job_ops_get() - Get PT job ops
+ * @pt_job_ops: PT job ops to get
+ *
+ * Take a reference to PT job ops
+ *
+ * Return: Pointer to PT job ops or NULL
+ */
+struct xe_pt_job_ops *xe_pt_job_ops_get(struct xe_pt_job_ops *pt_job_ops)
+{
+	if (pt_job_ops)
+		kref_get(&pt_job_ops->refcount);
+
+	return pt_job_ops;
+}
+
+static void xe_pt_update_ops_free(struct xe_vm_pgtable_update_op *pt_op,
+				  u32 num_ops)
+{
+	u32 i;
+
+	for (i = 0; i < num_ops; ++i, ++pt_op)
+		xe_pt_free_bind(pt_op->entries, pt_op->num_entries);
+}
+
+static void xe_pt_job_ops_destroy(struct kref *ref)
+{
+	struct xe_pt_job_ops *pt_job_ops =
+		container_of(ref, struct xe_pt_job_ops, refcount);
+	struct llist_node *freed;
+	struct xe_bo *bo, *next;
+
+	xe_pt_update_ops_free(pt_job_ops->ops,
+			      pt_job_ops->current_op);
+
+	freed = llist_del_all(&pt_job_ops->deferred);
+	if (freed) {
+		llist_for_each_entry_safe(bo, next, freed, freed)
+			/*
+			 * If called from run_job, we are in the dma-fencing
+			 * path and cannot take dma-resv locks so use an async
+			 * put.
+			 */
+			xe_bo_put_async(bo);
+	}
+
+	kvfree(pt_job_ops->ops);
+	kfree(pt_job_ops);
+}
+
+/**
+ * xe_pt_job_ops_put() - Put PT job ops
+ * @pt_job_ops: PT job ops to put
+ *
+ * Drop a reference to PT job ops
+ */
+void xe_pt_job_ops_put(struct xe_pt_job_ops *pt_job_ops)
+{
+	if (!pt_job_ops)
+		return;
+
+	kref_put(&pt_job_ops->refcount, xe_pt_job_ops_destroy);
+}
diff --git a/drivers/gpu/drm/xe/xe_pt.h b/drivers/gpu/drm/xe/xe_pt.h
index 4daeebaab5a1..5faddb8e700c 100644
--- a/drivers/gpu/drm/xe/xe_pt.h
+++ b/drivers/gpu/drm/xe/xe_pt.h
@@ -49,4 +49,8 @@ bool xe_pt_zap_ptes(struct xe_tile *tile, struct xe_vma *vma);
 bool xe_pt_zap_ptes_range(struct xe_tile *tile, struct xe_vm *vm,
 			  struct xe_svm_range *range);
 
+struct xe_pt_job_ops *xe_pt_job_ops_alloc(u32 num_ops);
+struct xe_pt_job_ops *xe_pt_job_ops_get(struct xe_pt_job_ops *pt_job_ops);
+void xe_pt_job_ops_put(struct xe_pt_job_ops *pt_job_ops);
+
 #endif
diff --git a/drivers/gpu/drm/xe/xe_pt_types.h b/drivers/gpu/drm/xe/xe_pt_types.h
index a7d1bb708b69..39c5b89ce9b7 100644
--- a/drivers/gpu/drm/xe/xe_pt_types.h
+++ b/drivers/gpu/drm/xe/xe_pt_types.h
@@ -91,12 +91,29 @@ struct xe_vm_pgtable_update_op {
 	bool rebind;
 };
 
+/**
+ * struct xe_pt_job_ops - Page-table update operations (dynamically allocated)
+ *
+ * This is the portion of &struct xe_vma_ops and
+ * &struct xe_vm_pgtable_update_ops that is dynamically allocated, as it
+ * must remain valid until the associated bind job completes. A reference
+ * count controls its lifetime.
+ */
+struct xe_pt_job_ops {
+	/** @current_op: current page-table update operation */
+	u32 current_op;
+	/** @refcount: reference count */
+	struct kref refcount;
+	/** @deferred: list of deferred PT entries to destroy */
+	struct llist_head deferred;
+	/** @ops: page-table update operations */
+	struct xe_vm_pgtable_update_op *ops;
+};
+
 /** struct xe_vm_pgtable_update_ops: page table update operations */
 struct xe_vm_pgtable_update_ops {
-	/** @ops: operations */
-	struct xe_vm_pgtable_update_op *ops;
-	/** @deferred: deferred list to destroy PT entries */
-	struct llist_head deferred;
+	/** @pt_job_ops: PT update operations dynamic allocation*/
+	struct xe_pt_job_ops *pt_job_ops;
 	/** @q: exec queue for PT operations */
 	struct xe_exec_queue *q;
 	/** @prl: embedded page reclaim list */
@@ -107,8 +124,6 @@ struct xe_vm_pgtable_update_ops {
 	u64 last;
 	/** @num_ops: number of operations */
 	u32 num_ops;
-	/** @current_op: current operations */
-	u32 current_op;
 	/** @needs_svm_lock: Needs SVM lock */
 	bool needs_svm_lock;
 	/** @needs_invalidation: Needs invalidation */
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 753a5fc55baa..2737bd25f39a 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -681,11 +681,9 @@ static int xe_vma_ops_alloc(struct xe_vma_ops *vops, bool array_of_binds)
 		if (!vops->pt_update_ops[i].num_ops)
 			continue;
 
-		vops->pt_update_ops[i].ops =
-			kmalloc_objs(*vops->pt_update_ops[i].ops,
-				     vops->pt_update_ops[i].num_ops,
-				     GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
-		if (!vops->pt_update_ops[i].ops)
+		vops->pt_update_ops[i].pt_job_ops =
+			xe_pt_job_ops_alloc(vops->pt_update_ops[i].num_ops);
+		if (!vops->pt_update_ops[i].pt_job_ops)
 			return array_of_binds ? -ENOBUFS : -ENOMEM;
 	}
 
@@ -732,7 +730,7 @@ static void xe_vma_ops_fini(struct xe_vma_ops *vops)
 	xe_vma_svm_prefetch_ops_fini(vops);
 
 	for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
-		kfree(vops->pt_update_ops[i].ops);
+		xe_pt_job_ops_put(vops->pt_update_ops[i].pt_job_ops);
 }
 
 static void xe_vma_ops_incr_pt_update_ops(struct xe_vma_ops *vops, u8 tile_mask, int inc_val)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (8 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 09/25] drm/xe: Add struct xe_pt_job_ops Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:36   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update Matthew Brost
                   ` (17 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

PT jobs bypass GPU execution for the final step of a bind job, using the
CPU to program the required page tables. Teach the GuC submission backend
how to execute these jobs.

PT job submission is implemented in the GuC backend for simplicity. A
follow-up patch could introduce a dedicated backend for PT jobs.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-11-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_guc_submit.c | 37 ++++++++++++++++++++++++++----
 drivers/gpu/drm/xe/xe_migrate.c    | 13 ++++++++++-
 drivers/gpu/drm/xe/xe_migrate.h    |  8 +++++++
 3 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index cf306568ef30..528869928c1d 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -37,9 +37,11 @@
 #include "xe_lrc.h"
 #include "xe_macros.h"
 #include "xe_map.h"
+#include "xe_migrate.h"
 #include "xe_mocs.h"
 #include "xe_module.h"
 #include "xe_pm.h"
+#include "xe_pt.h"
 #include "xe_ring_ops_types.h"
 #include "xe_sched_job.h"
 #include "xe_sleep.h"
@@ -1236,6 +1238,20 @@ static void submit_exec_queue(struct xe_exec_queue *q, struct xe_sched_job *job)
 	}
 }
 
+static bool is_pt_job(struct xe_sched_job *job)
+{
+	return job->is_pt_job;
+}
+
+static void run_pt_job(struct xe_sched_job *job)
+{
+	xe_migrate_update_pgtables_cpu_execute(job->pt_update[0].vm,
+					       job->pt_update[0].tile,
+					       job->pt_update[0].ops,
+					       job->pt_update[0].pt_job_ops->ops,
+					       job->pt_update[0].pt_job_ops->current_op);
+}
+
 static struct dma_fence *
 guc_exec_queue_run_job(struct drm_sched_job *drm_job)
 {
@@ -1261,14 +1277,25 @@ guc_exec_queue_run_job(struct drm_sched_job *drm_job)
 				register_exec_queue(primary, GUC_CONTEXT_NORMAL);
 		}
 
-		if (!exec_queue_registered(q))
-			register_exec_queue(q, GUC_CONTEXT_NORMAL);
-		if (!job->restore_replay)
-			q->ring_ops->emit_job(job);
-		submit_exec_queue(q, job);
+		if (is_pt_job(job)) {
+			xe_gt_assert(guc_to_gt(guc), !exec_queue_registered(q));
+			run_pt_job(job);
+		} else {
+			if (!exec_queue_registered(q))
+				register_exec_queue(q, GUC_CONTEXT_NORMAL);
+			if (!job->restore_replay)
+				q->ring_ops->emit_job(job);
+			submit_exec_queue(q, job);
+		}
 		job->restore_replay = false;
 	}
 
+	if (is_pt_job(job)) {
+		xe_pt_job_ops_put(job->pt_update[0].pt_job_ops);
+		dma_fence_put(job->fence);	/* Drop ref from xe_sched_job_arm */
+		return NULL;
+	}
+
 run_job_out:
 
 	return job->fence;
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 92e378a45e53..160045f08cb8 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -1819,7 +1819,18 @@ struct migrate_test_params {
 	container_of(_priv, struct migrate_test_params, base)
 #endif
 
-static void
+/**
+ * xe_migrate_update_pgtables_cpu_execute() - Update a VM's PTEs via the CPU
+ * @vm: The VM being updated
+ * @tile: The tile being updated
+ * @ops: The migrate PT update ops
+ * @pt_ops: The VM PT update ops
+ * @num_ops: The number of The VM PT update ops
+ *
+ * Execute the VM PT update ops array which results in a VM's PTEs being updated
+ * via the CPU.
+ */
+void
 xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
 				       const struct xe_migrate_pt_update_ops *ops,
 				       struct xe_vm_pgtable_update_op *pt_op,
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index 2ec9de896dfe..84403c242feb 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -24,6 +24,7 @@ struct xe_pt;
 struct xe_tile;
 struct xe_vm;
 struct xe_vm_pgtable_update;
+struct xe_vm_pgtable_update_op;
 struct xe_vma;
 
 enum xe_sriov_vf_ccs_rw_ctxs;
@@ -163,6 +164,13 @@ struct dma_fence *xe_migrate_clear(struct xe_migrate *m,
 
 struct xe_vm *xe_migrate_get_vm(struct xe_migrate *m);
 
+
+void
+xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
+				       const struct xe_migrate_pt_update_ops *ops,
+				       struct xe_vm_pgtable_update_op *pt_op,
+				       int num_ops);
+
 struct dma_fence *
 xe_migrate_update_pgtables(struct xe_migrate *m,
 			   struct xe_migrate_pt_update *pt_update);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (9 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:19   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 12/25] drm/xe: Don't use migrate exec queue for page fault binds Matthew Brost
                   ` (16 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe; +Cc: Stuart Summers

The level was previously extracted from struct xe_pt inside
xe_vm_pgtable_update during CPU binds, which always occurred during the
bind IOCTL. With CPU binds now supported in bind jobs, struct xe_pt may
no longer be valid in memory at that point. To address this, store the
level directly in struct xe_vm_pgtable_update.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Reviewed-by: Stuart Summers <stuart.summers@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-12-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_pt.c       | 3 ++-
 drivers/gpu/drm/xe/xe_pt_types.h | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 4c277153398e..486de8a46a6a 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -389,6 +389,7 @@ xe_pt_new_shared(struct xe_walk_update *wupd, struct xe_pt *parent,
 	entry->flags = 0;
 	entry->qwords = 0;
 	entry->pt_bo->update_index = -1;
+	entry->level = parent->level;
 
 	if (alloc_entries) {
 		entry->pt_entries = kmalloc_objs(*entry->pt_entries, XE_PDES);
@@ -2026,7 +2027,7 @@ xe_migrate_clear_pgtable_callback(struct xe_vm *vm, struct xe_tile *tile,
 				  u32 qword_ofs, u32 num_qwords,
 				  const struct xe_vm_pgtable_update *update)
 {
-	u64 empty = __xe_pt_empty_pte(tile, vm, update->pt->level);
+	u64 empty = __xe_pt_empty_pte(tile, vm, update->level);
 	int i;
 
 	if (map && map->is_iomem)
diff --git a/drivers/gpu/drm/xe/xe_pt_types.h b/drivers/gpu/drm/xe/xe_pt_types.h
index 39c5b89ce9b7..ccab6613385f 100644
--- a/drivers/gpu/drm/xe/xe_pt_types.h
+++ b/drivers/gpu/drm/xe/xe_pt_types.h
@@ -65,12 +65,18 @@ struct xe_vm_pgtable_update {
 	/** @qwords: number of PTE's to write */
 	u32 qwords;
 
-	/** @pt: opaque pointer useful for the caller of xe_migrate_update_pgtables */
+	/**
+	 * @pt: opaque pointer useful for PT building in the bind IOCTL. Only
+	 * safe to touch during the bind IOCTL (i.e., not in bind jobs).
+	 */
 	struct xe_pt *pt;
 
 	/** @pt_entries: Newly added pagetable entries */
 	struct xe_pt_entry *pt_entries;
 
+	/** @level: level of update */
+	unsigned int level;
+
 	/** @flags: Target flags */
 	u32 flags;
 };
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 12/25] drm/xe: Don't use migrate exec queue for page fault binds
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (10 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs Matthew Brost
                   ` (15 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Now that the CPU is always used for binds even in jobs, CPU bind jobs
can pass GPU jobs in the same exec queue resulting dma-fences signaling
out-of-order. Use a dedicated exec queue for binds issued from page
faults to avoid ordering issues and avoid blocking kernel binds on
unrelated copies / clears.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-13-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c | 47 ++++++++++++++++++++++++++++++---
 drivers/gpu/drm/xe/xe_migrate.h |  1 +
 drivers/gpu/drm/xe/xe_vm.c      | 17 +++++++-----
 3 files changed, 55 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 160045f08cb8..100e84b7b81d 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -51,6 +51,8 @@
 struct xe_migrate {
 	/** @q: Default exec queue used for migration */
 	struct xe_exec_queue *q;
+	/** @bind_q: Default exec queue used for binds */
+	struct xe_exec_queue *bind_q;
 	/** @tile: Backpointer to the tile this struct xe_migrate belongs to. */
 	struct xe_tile *tile;
 	/** @job_mutex: Timeline mutex for @eng. */
@@ -115,6 +117,7 @@ static void xe_migrate_fini(void *arg)
 	mutex_destroy(&m->job_mutex);
 	xe_vm_close_and_put(m->q->vm);
 	xe_exec_queue_put(m->q);
+	xe_exec_queue_put(m->bind_q);
 }
 
 static inline u16 xe_migrate_pat_index(struct xe_device *xe,
@@ -504,6 +507,15 @@ int xe_migrate_init(struct xe_migrate *m)
 			goto err_out;
 		}
 
+		m->bind_q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe0,
+						 EXEC_QUEUE_FLAG_KERNEL |
+						 EXEC_QUEUE_FLAG_HIGH_PRIORITY |
+						 EXEC_QUEUE_FLAG_MIGRATE, 0);
+		if (IS_ERR(m->bind_q)) {
+			err = PTR_ERR(m->bind_q);
+			goto err_out;
+		}
+
 		/*
 		 * XXX: Currently only reserving 1 (likely slow) BCS instance on
 		 * PVC, may want to revisit if performance is needed.
@@ -514,6 +526,15 @@ int xe_migrate_init(struct xe_migrate *m)
 					    EXEC_QUEUE_FLAG_MIGRATE |
 					    EXEC_QUEUE_FLAG_LOW_LATENCY, 0);
 	} else {
+		m->bind_q = xe_exec_queue_create_class(xe, primary_gt, vm,
+						       XE_ENGINE_CLASS_COPY,
+						       EXEC_QUEUE_FLAG_KERNEL |
+						       EXEC_QUEUE_FLAG_MIGRATE, 0);
+		if (IS_ERR(m->bind_q)) {
+			err = PTR_ERR(m->bind_q);
+			goto err_out;
+		}
+
 		m->q = xe_exec_queue_create_class(xe, primary_gt, vm,
 						  XE_ENGINE_CLASS_COPY,
 						  EXEC_QUEUE_FLAG_KERNEL |
@@ -549,6 +570,8 @@ int xe_migrate_init(struct xe_migrate *m)
 	return err;
 
 err_out:
+	if (!IS_ERR_OR_NULL(m->bind_q))
+		xe_exec_queue_put(m->bind_q);
 	xe_vm_close_and_put(vm);
 	return err;
 
@@ -1505,6 +1528,17 @@ static u32 blt_mem_set_cmd_len(struct xe_device *xe)
 	return 7;
 }
 
+/**
+ * xe_get_migrate_bind_queue() - Get the bind queue from migrate context.
+ * @migrate: Migrate context.
+ *
+ * Return: Pointer to bind queue on success, error on failure
+ */
+struct xe_exec_queue *xe_migrate_bind_queue(struct xe_migrate *migrate)
+{
+	return migrate->bind_q;
+}
+
 static void emit_clear_link_copy(struct xe_gt *gt, struct xe_bb *bb, u64 src_ofs,
 				 u32 size, u32 pitch)
 {
@@ -1892,6 +1926,11 @@ xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
 	return dma_fence_get_stub();
 }
 
+static bool is_migrate_queue(struct xe_migrate *m, struct xe_exec_queue *q)
+{
+	return m->bind_q == q;
+}
+
 static struct dma_fence *
 __xe_migrate_update_pgtables(struct xe_migrate *m,
 			     struct xe_migrate_pt_update *pt_update,
@@ -1909,7 +1948,7 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 	u32 num_updates = 0, current_update = 0;
 	u64 addr;
 	int err = 0;
-	bool is_migrate = pt_update_ops->q == m->q;
+	bool is_migrate = is_migrate_queue(m, pt_update_ops->q);
 	bool usm = is_migrate && xe->info.has_usm;
 
 	for (i = 0; i < pt_update_ops->num_ops; ++i) {
@@ -2631,7 +2670,7 @@ int xe_migrate_access_memory(struct xe_migrate *m, struct xe_bo *bo,
  */
 void xe_migrate_job_lock(struct xe_migrate *m, struct xe_exec_queue *q)
 {
-	bool is_migrate = q == m->q;
+	bool is_migrate = is_migrate_queue(m, q);
 
 	if (is_migrate)
 		mutex_lock(&m->job_mutex);
@@ -2649,7 +2688,7 @@ void xe_migrate_job_lock(struct xe_migrate *m, struct xe_exec_queue *q)
  */
 void xe_migrate_job_unlock(struct xe_migrate *m, struct xe_exec_queue *q)
 {
-	bool is_migrate = q == m->q;
+	bool is_migrate = is_migrate_queue(m, q);
 
 	if (is_migrate)
 		mutex_unlock(&m->job_mutex);
@@ -2666,7 +2705,7 @@ void xe_migrate_job_lock_assert(struct xe_exec_queue *q)
 {
 	struct xe_migrate *m = gt_to_tile(q->gt)->migrate;
 
-	xe_gt_assert(q->gt, q == m->q);
+	xe_gt_assert(q->gt, q == m->bind_q);
 	lockdep_assert_held(&m->job_mutex);
 }
 #endif
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index 84403c242feb..ee4c4a829963 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -146,6 +146,7 @@ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo,
 
 struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate);
 struct xe_exec_queue *xe_migrate_exec_queue(struct xe_migrate *migrate);
+struct xe_exec_queue *xe_migrate_bind_queue(struct xe_migrate *migrate);
 struct dma_fence *xe_migrate_vram_copy_chunk(struct xe_bo *vram_bo, u64 vram_offset,
 					     struct xe_bo *sysmem_bo, u64 sysmem_offset,
 					     u64 size, enum xe_migrate_copy_dir dir);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 2737bd25f39a..13e984ac4e4f 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -795,7 +795,9 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
 	struct xe_vma *vma, *next;
 	struct xe_vma_ops vops;
 	struct xe_vma_op *op, *next_op;
-	int err, i;
+	struct xe_tile *tile;
+	u8 id;
+	int err;
 
 	lockdep_assert_held(&vm->lock);
 	if ((xe_vm_in_lr_mode(vm) && !rebind_worker) ||
@@ -803,8 +805,11 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
 		return 0;
 
 	xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
-	for (i = 0; i < XE_MAX_TILES_PER_DEVICE; ++i)
-		vops.pt_update_ops[i].wait_vm_bookkeep = true;
+	for_each_tile(tile, vm->xe, id) {
+		vops.pt_update_ops[id].wait_vm_bookkeep = true;
+		vops.pt_update_ops[id].q =
+			xe_migrate_bind_queue(tile->migrate);
+	}
 
 	xe_vm_assert_held(vm);
 	list_for_each_entry(vma, &vm->rebind_list, combined_links.rebind) {
@@ -862,7 +867,7 @@ struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_ma
 	for_each_tile(tile, vm->xe, id) {
 		vops.pt_update_ops[id].wait_vm_bookkeep = true;
 		vops.pt_update_ops[tile->id].q =
-			xe_migrate_exec_queue(tile->migrate);
+			xe_migrate_bind_queue(tile->migrate);
 	}
 
 	err = xe_vm_ops_add_rebind(&vops, vma, tile_mask);
@@ -954,7 +959,7 @@ struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
 	for_each_tile(tile, vm->xe, id) {
 		vops.pt_update_ops[id].wait_vm_bookkeep = true;
 		vops.pt_update_ops[tile->id].q =
-			xe_migrate_exec_queue(tile->migrate);
+			xe_migrate_bind_queue(tile->migrate);
 	}
 
 	err = xe_vm_ops_add_range_rebind(&vops, vma, range, tile_mask);
@@ -1038,7 +1043,7 @@ struct dma_fence *xe_vm_range_unbind(struct xe_vm *vm,
 	for_each_tile(tile, vm->xe, id) {
 		vops.pt_update_ops[id].wait_vm_bookkeep = true;
 		vops.pt_update_ops[tile->id].q =
-			xe_migrate_exec_queue(tile->migrate);
+			xe_migrate_bind_queue(tile->migrate);
 	}
 
 	err = xe_vm_ops_add_range_unbind(&vops, range);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (11 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 12/25] drm/xe: Don't use migrate exec queue for page fault binds Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:31   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 14/25] drm/xe: Remove unused arguments from xe_migrate_pt_update_ops Matthew Brost
                   ` (14 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

No reason to use the GPU for binds.

Benefits of CPU-based binds:
- Lower latency once dependencies are resolved, as there is no
  interaction with the GuC or a hardware context switch both of which
  are relatively slow.
- Large arrays of binds do not risk running out of migration PTEs,
  avoiding -ENOBUFS being returned to userspace.
- Kernel binds are decoupled from the migration exec queue (which issues
  copies and clears), so they cannot get stuck behind unrelated
  jobs—this can be a problem with parallel GPU faults.
- Paves the for path decouping binds from tiles and individual engines
- Enables ULLS on the migration exec queue, as this queue has exclusive
  access to the paging copy engine.

Update migration layer to formulate a PT job which will issue CPU bind
in the submission backend.

All code related to GPU-based binding has been removed.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-14-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_bo_types.h |   2 -
 drivers/gpu/drm/xe/xe_migrate.c  | 249 +++----------------------------
 drivers/gpu/drm/xe/xe_pt.c       |   1 -
 3 files changed, 17 insertions(+), 235 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h
index e45f24301050..7a67d422dc30 100644
--- a/drivers/gpu/drm/xe/xe_bo_types.h
+++ b/drivers/gpu/drm/xe/xe_bo_types.h
@@ -82,8 +82,6 @@ struct xe_bo {
 
 	/** @freed: List node for delayed put. */
 	struct llist_node freed;
-	/** @update_index: Update index if PT BO */
-	int update_index;
 	/** @created: Whether the bo has passed initial creation */
 	bool created;
 
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 100e84b7b81d..62ae0e2e85be 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -77,18 +77,12 @@ struct xe_migrate {
 	 * Protected by @job_mutex.
 	 */
 	struct dma_fence *fence;
-	/**
-	 * @vm_update_sa: For integrated, used to suballocate page-tables
-	 * out of the pt_bo.
-	 */
-	struct drm_suballoc_manager vm_update_sa;
 	/** @min_chunk_size: For dgfx, Minimum chunk size */
 	u64 min_chunk_size;
 };
 
 #define MAX_PREEMPTDISABLE_TRANSFER SZ_8M /* Around 1ms. */
 #define MAX_CCS_LIMITED_TRANSFER SZ_4M /* XE_PAGE_SIZE * (FIELD_MAX(XE2_CCS_SIZE_MASK) + 1) */
-#define NUM_KERNEL_PDE 15
 #define NUM_PT_SLOTS 48
 #define LEVEL0_PAGE_TABLE_ENCODE_SIZE SZ_2M
 #define MAX_NUM_PTE 512
@@ -113,7 +107,6 @@ static void xe_migrate_fini(void *arg)
 
 	dma_fence_put(m->fence);
 	xe_bo_put(m->pt_bo);
-	drm_suballoc_manager_fini(&m->vm_update_sa);
 	mutex_destroy(&m->job_mutex);
 	xe_vm_close_and_put(m->q->vm);
 	xe_exec_queue_put(m->q);
@@ -234,8 +227,6 @@ static int xe_migrate_pt_bo_alloc(struct xe_tile *tile, struct xe_migrate *m,
 	BUILD_BUG_ON(NUM_PT_SLOTS > SZ_2M/XE_PAGE_SIZE);
 	/* Must be a multiple of 64K to support all platforms */
 	BUILD_BUG_ON(NUM_PT_SLOTS * XE_PAGE_SIZE % SZ_64K);
-	/* And one slot reserved for the 4KiB page table updates */
-	BUILD_BUG_ON(!(NUM_KERNEL_PDE & 1));
 
 	/* Need to be sure everything fits in the first PT, or create more */
 	xe_tile_assert(tile, m->batch_base_ofs + xe_bo_size(batch) < SZ_2M);
@@ -391,17 +382,9 @@ static void xe_migrate_prepare_vm(struct xe_tile *tile, struct xe_migrate *m,
 		}
 	}
 
-	if (ofs)
-		*ofs = map_ofs;
-}
-
-static void xe_migrate_suballoc_manager_init(struct xe_migrate *m, u32 map_ofs)
-{
 	/*
 	 * Example layout created above, with root level = 3:
 	 * [PT0...PT7]: kernel PT's for copy/clear; 64 or 4KiB PTE's
-	 * [PT8]: Kernel PT for VM_BIND, 4 KiB PTE's
-	 * [PT9...PT40]: Userspace PT's for VM_BIND, 4 KiB PTE's
 	 * [PT41 = PDE 0] [PT44...PT47 = 4K and 2M vram identity maps]
 	 *
 	 * This makes the lowest part of the VM point to the pagetables.
@@ -409,19 +392,13 @@ static void xe_migrate_suballoc_manager_init(struct xe_migrate *m, u32 map_ofs)
 	 * and flushes, other parts of the VM can be used either for copying and
 	 * clearing.
 	 *
-	 * For performance, the kernel reserves PDE's, so about 20 are left
-	 * for async VM updates.
-	 *
 	 * To make it easier to work, each scratch PT is put in slot (1 + PT #)
 	 * everywhere, this allows lockless updates to scratch pages by using
 	 * the different addresses in VM.
 	 */
-#define NUM_VMUSA_UNIT_PER_PAGE	32
-#define VM_SA_UPDATE_UNIT_SIZE		(XE_PAGE_SIZE / NUM_VMUSA_UNIT_PER_PAGE)
-#define NUM_VMUSA_WRITES_PER_UNIT	(VM_SA_UPDATE_UNIT_SIZE / sizeof(u64))
-	drm_suballoc_manager_init(&m->vm_update_sa,
-				  (size_t)(map_ofs / XE_PAGE_SIZE - NUM_KERNEL_PDE) *
-				  NUM_VMUSA_UNIT_PER_PAGE, 0);
+
+	if (ofs)
+		*ofs = map_ofs;
 }
 
 static bool xe_migrate_needs_ccs_emit(struct xe_device *xe)
@@ -466,7 +443,6 @@ static int xe_migrate_lock_prepare_vm(struct xe_tile *tile, struct xe_migrate *m
 			return err;
 
 		xe_migrate_prepare_vm(tile, m, vm, &map_ofs);
-		xe_migrate_suballoc_manager_init(m, map_ofs);
 		drm_exec_retry_on_contention(&exec);
 		xe_validation_retry_on_oom(&ctx, &err);
 	}
@@ -1169,6 +1145,9 @@ struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate)
 	return migrate->q->lrc[0];
 }
 
+/* XXX: With CPU binds this can be removed in a follow up */
+#define NUM_KERNEL_PDE 15
+
 static u64 migrate_vm_ppgtt_addr_tlb_inval(void)
 {
 	/*
@@ -1788,56 +1767,6 @@ struct dma_fence *xe_migrate_clear(struct xe_migrate *m,
 	return fence;
 }
 
-static void write_pgtable(struct xe_tile *tile, struct xe_bb *bb, u64 ppgtt_ofs,
-			  const struct xe_vm_pgtable_update_op *pt_op,
-			  const struct xe_vm_pgtable_update *update,
-			  struct xe_migrate_pt_update *pt_update)
-{
-	const struct xe_migrate_pt_update_ops *ops = pt_update->ops;
-	struct xe_vm *vm = pt_update->vops->vm;
-	u32 chunk;
-	u32 ofs = update->ofs, size = update->qwords;
-
-	/*
-	 * If we have 512 entries (max), we would populate it ourselves,
-	 * and update the PDE above it to the new pointer.
-	 * The only time this can only happen if we have to update the top
-	 * PDE. This requires a BO that is almost vm->size big.
-	 *
-	 * This shouldn't be possible in practice.. might change when 16K
-	 * pages are used. Hence the assert.
-	 */
-	xe_tile_assert(tile, update->qwords < MAX_NUM_PTE);
-	if (!ppgtt_ofs)
-		ppgtt_ofs = xe_migrate_vram_ofs(tile_to_xe(tile),
-						xe_bo_addr(update->pt_bo, 0,
-							   XE_PAGE_SIZE), false);
-
-	do {
-		u64 addr = ppgtt_ofs + ofs * 8;
-
-		chunk = min(size, MAX_PTE_PER_SDI);
-
-		/* Ensure populatefn can do memset64 by aligning bb->cs */
-		if (!(bb->len & 1))
-			bb->cs[bb->len++] = MI_NOOP;
-
-		bb->cs[bb->len++] = MI_STORE_DATA_IMM | MI_SDI_NUM_QW(chunk);
-		bb->cs[bb->len++] = lower_32_bits(addr);
-		bb->cs[bb->len++] = upper_32_bits(addr);
-		if (pt_op->bind)
-			ops->populate(tile, NULL, bb->cs + bb->len,
-				      ofs, chunk, update);
-		else
-			ops->clear(vm, tile, NULL, bb->cs + bb->len,
-				   ofs, chunk, update);
-
-		bb->len += chunk * 2;
-		ofs += chunk;
-		size -= chunk;
-	} while (size);
-}
-
 struct xe_vm *xe_migrate_get_vm(struct xe_migrate *m)
 {
 	return xe_vm_get(m->q->vm);
@@ -1938,162 +1867,18 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 {
 	const struct xe_migrate_pt_update_ops *ops = pt_update->ops;
 	struct xe_tile *tile = m->tile;
-	struct xe_gt *gt = tile->primary_gt;
-	struct xe_device *xe = tile_to_xe(tile);
 	struct xe_sched_job *job;
 	struct dma_fence *fence;
-	struct drm_suballoc *sa_bo = NULL;
-	struct xe_bb *bb;
-	u32 i, j, batch_size = 0, ppgtt_ofs, update_idx, page_ofs = 0;
-	u32 num_updates = 0, current_update = 0;
-	u64 addr;
-	int err = 0;
 	bool is_migrate = is_migrate_queue(m, pt_update_ops->q);
-	bool usm = is_migrate && xe->info.has_usm;
-
-	for (i = 0; i < pt_update_ops->num_ops; ++i) {
-		struct xe_vm_pgtable_update_op *pt_op = &pt_update_ops->pt_job_ops->ops[i];
-		struct xe_vm_pgtable_update *updates = pt_op->entries;
-
-		num_updates += pt_op->num_entries;
-		for (j = 0; j < pt_op->num_entries; ++j) {
-			u32 num_cmds = DIV_ROUND_UP(updates[j].qwords,
-						    MAX_PTE_PER_SDI);
-
-			/* align noop + MI_STORE_DATA_IMM cmd prefix */
-			batch_size += 4 * num_cmds + updates[j].qwords * 2;
-		}
-	}
-
-	/* fixed + PTE entries */
-	if (IS_DGFX(xe))
-		batch_size += 2;
-	else
-		batch_size += 6 * (num_updates / MAX_PTE_PER_SDI + 1) +
-			num_updates * 2;
-
-	bb = xe_bb_new(gt, batch_size, usm);
-	if (IS_ERR(bb))
-		return ERR_CAST(bb);
-
-	/* For sysmem PTE's, need to map them in our hole.. */
-	if (!IS_DGFX(xe)) {
-		u16 pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
-		u32 ptes, ofs;
-
-		ppgtt_ofs = NUM_KERNEL_PDE - 1;
-		if (!is_migrate) {
-			u32 num_units = DIV_ROUND_UP(num_updates,
-						     NUM_VMUSA_WRITES_PER_UNIT);
-
-			if (num_units > m->vm_update_sa.size) {
-				err = -ENOBUFS;
-				goto err_bb;
-			}
-			sa_bo = drm_suballoc_new(&m->vm_update_sa, num_units,
-						 GFP_KERNEL, true, 0);
-			if (IS_ERR(sa_bo)) {
-				err = PTR_ERR(sa_bo);
-				goto err_bb;
-			}
-
-			ppgtt_ofs = NUM_KERNEL_PDE +
-				(drm_suballoc_soffset(sa_bo) /
-				 NUM_VMUSA_UNIT_PER_PAGE);
-			page_ofs = (drm_suballoc_soffset(sa_bo) %
-				    NUM_VMUSA_UNIT_PER_PAGE) *
-				VM_SA_UPDATE_UNIT_SIZE;
-		}
-
-		/* Map our PT's to gtt */
-		i = 0;
-		j = 0;
-		ptes = num_updates;
-		ofs = ppgtt_ofs * XE_PAGE_SIZE + page_ofs;
-		while (ptes) {
-			u32 chunk = min(MAX_PTE_PER_SDI, ptes);
-			u32 idx = 0;
-
-			bb->cs[bb->len++] = MI_STORE_DATA_IMM |
-				MI_SDI_NUM_QW(chunk);
-			bb->cs[bb->len++] = ofs;
-			bb->cs[bb->len++] = 0; /* upper_32_bits */
-
-			for (; i < pt_update_ops->num_ops; ++i) {
-				struct xe_vm_pgtable_update_op *pt_op =
-					&pt_update_ops->pt_job_ops->ops[i];
-				struct xe_vm_pgtable_update *updates = pt_op->entries;
-
-				for (; j < pt_op->num_entries; ++j, ++current_update, ++idx) {
-					struct xe_vm *vm = pt_update->vops->vm;
-					struct xe_bo *pt_bo = updates[j].pt_bo;
-
-					if (idx == chunk)
-						goto next_cmd;
-
-					xe_tile_assert(tile, xe_bo_size(pt_bo) == SZ_4K);
-
-					/* Map a PT at most once */
-					if (pt_bo->update_index < 0)
-						pt_bo->update_index = current_update;
-
-					addr = vm->pt_ops->pte_encode_bo(pt_bo, 0,
-									 pat_index, 0);
-					bb->cs[bb->len++] = lower_32_bits(addr);
-					bb->cs[bb->len++] = upper_32_bits(addr);
-				}
-
-				j = 0;
-			}
-
-next_cmd:
-			ptes -= chunk;
-			ofs += chunk * sizeof(u64);
-		}
-
-		bb->cs[bb->len++] = MI_BATCH_BUFFER_END;
-		update_idx = bb->len;
-
-		addr = xe_migrate_vm_addr(ppgtt_ofs, 0) +
-			(page_ofs / sizeof(u64)) * XE_PAGE_SIZE;
-		for (i = 0; i < pt_update_ops->num_ops; ++i) {
-			struct xe_vm_pgtable_update_op *pt_op =
-				&pt_update_ops->pt_job_ops->ops[i];
-			struct xe_vm_pgtable_update *updates = pt_op->entries;
-
-			for (j = 0; j < pt_op->num_entries; ++j) {
-				struct xe_bo *pt_bo = updates[j].pt_bo;
-
-				write_pgtable(tile, bb, addr +
-					      pt_bo->update_index * XE_PAGE_SIZE,
-					      pt_op, &updates[j], pt_update);
-			}
-		}
-	} else {
-		/* phys pages, no preamble required */
-		bb->cs[bb->len++] = MI_BATCH_BUFFER_END;
-		update_idx = bb->len;
-
-		for (i = 0; i < pt_update_ops->num_ops; ++i) {
-			struct xe_vm_pgtable_update_op *pt_op =
-				&pt_update_ops->pt_job_ops->ops[i];
-			struct xe_vm_pgtable_update *updates = pt_op->entries;
-
-			for (j = 0; j < pt_op->num_entries; ++j)
-				write_pgtable(tile, bb, 0, pt_op, &updates[j],
-					      pt_update);
-		}
-	}
+	int err;
 
-	job = xe_bb_create_migration_job(pt_update_ops->q, bb,
-					 xe_migrate_batch_base(m, usm),
-					 update_idx);
+	job = xe_sched_job_create(pt_update_ops->q, NULL);
 	if (IS_ERR(job)) {
 		err = PTR_ERR(job);
-		goto err_sa;
+		goto err_out;
 	}
 
-	xe_sched_job_add_migrate_flush(job, MI_INVALIDATE_TLB);
+	xe_tile_assert(tile, job->is_pt_job);
 
 	if (ops->pre_commit) {
 		pt_update->job = job;
@@ -2104,6 +1889,12 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 	if (is_migrate)
 		mutex_lock(&m->job_mutex);
 
+	job->pt_update[0].vm = pt_update->vops->vm;
+	job->pt_update[0].tile = tile;
+	job->pt_update[0].ops = ops;
+	job->pt_update[0].pt_job_ops =
+		xe_pt_job_ops_get(pt_update_ops->pt_job_ops);
+
 	xe_sched_job_arm(job);
 	fence = dma_fence_get(&job->drm.s_fence->finished);
 	xe_sched_job_push(job);
@@ -2111,17 +1902,11 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
 	if (is_migrate)
 		mutex_unlock(&m->job_mutex);
 
-	xe_bb_free(bb, fence);
-	drm_suballoc_free(sa_bo, fence);
-
 	return fence;
 
 err_job:
 	xe_sched_job_put(job);
-err_sa:
-	drm_suballoc_free(sa_bo, NULL);
-err_bb:
-	xe_bb_free(bb, NULL);
+err_out:
 	return ERR_PTR(err);
 }
 
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 486de8a46a6a..baa6581f8d33 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -388,7 +388,6 @@ xe_pt_new_shared(struct xe_walk_update *wupd, struct xe_pt *parent,
 	entry->pt = parent;
 	entry->flags = 0;
 	entry->qwords = 0;
-	entry->pt_bo->update_index = -1;
 	entry->level = parent->level;
 
 	if (alloc_entries) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 14/25] drm/xe: Remove unused arguments from xe_migrate_pt_update_ops
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (12 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 15/25] drm/xe: Make bind queues operate cross-tile Matthew Brost
                   ` (13 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Both populate and clear have unused void* ptr arguments, remove these.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-15-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c |  4 ++--
 drivers/gpu/drm/xe/xe_migrate.h |  7 ++-----
 drivers/gpu/drm/xe/xe_pt.c      | 37 ++++++++++++---------------------
 3 files changed, 17 insertions(+), 31 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 62ae0e2e85be..66e77de4e192 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -1812,11 +1812,11 @@ xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
 
 			if (pt_op->bind)
 				ops->populate(tile, &update->pt_bo->vmap,
-					      NULL, update->ofs, update->qwords,
+					      update->ofs, update->qwords,
 					      update);
 			else
 				ops->clear(vm, tile, &update->pt_bo->vmap,
-					   NULL, update->ofs, update->qwords,
+					   update->ofs, update->qwords,
 					   update);
 		}
 	}
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index ee4c4a829963..48896fae1fbd 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -43,7 +43,6 @@ struct xe_migrate_pt_update_ops {
 	 * @populate: Populate a command buffer or page-table with ptes.
 	 * @tile: The tile for the current operation.
 	 * @map: struct iosys_map into the memory to be populated.
-	 * @pos: If @map is NULL, map into the memory to be populated.
 	 * @ofs: qword offset into @map, unused if @map is NULL.
 	 * @num_qwords: Number of qwords to write.
 	 * @update: Information about the PTEs to be inserted.
@@ -53,14 +52,13 @@ struct xe_migrate_pt_update_ops {
 	 * page-tables with PTEs.
 	 */
 	void (*populate)(struct xe_tile *tile, struct iosys_map *map,
-			 void *pos, u32 ofs, u32 num_qwords,
+			 u32 ofs, u32 num_qwords,
 			 const struct xe_vm_pgtable_update *update);
 	/**
 	 * @clear: Clear a command buffer or page-table with ptes.
 	 * @vm: VM being updated
 	 * @tile: The tile for the current operation.
 	 * @map: struct iosys_map into the memory to be populated.
-	 * @pos: If @map is NULL, map into the memory to be populated.
 	 * @ofs: qword offset into @map, unused if @map is NULL.
 	 * @num_qwords: Number of qwords to write.
 	 * @update: Information about the PTEs to be inserted.
@@ -70,8 +68,7 @@ struct xe_migrate_pt_update_ops {
 	 * page-tables with PTEs.
 	 */
 	void (*clear)(struct xe_vm *vm, struct xe_tile *tile,
-		      struct iosys_map *map, void *pos, u32 ofs,
-		      u32 num_qwords,
+		      struct iosys_map *map, u32 ofs, u32 num_qwords,
 		      const struct xe_vm_pgtable_update *update);
 
 	/**
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index baa6581f8d33..0f749f717861 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -1107,30 +1107,18 @@ bool xe_pt_zap_ptes_range(struct xe_tile *tile, struct xe_vm *vm,
 
 static void
 xe_vm_populate_pgtable(struct xe_tile *tile, struct iosys_map *map,
-		       void *data, u32 qword_ofs, u32 num_qwords,
+		       u32 qword_ofs, u32 num_qwords,
 		       const struct xe_vm_pgtable_update *update)
 {
 	struct xe_pt_entry *ptes = update->pt_entries;
-	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;
+	xe_assert(tile_to_xe(tile), map);
+	xe_assert(tile_to_xe(tile), !iosys_map_is_null(map));
 
-		if (map)
-			xe_map_wr(tile_to_xe(tile), map, (qword_ofs + i) *
-				  sizeof(u64), u64, ptes[idx].pte);
-		else
-			ptr[i] = ptes[idx].pte;
-	}
+	for (i = 0; i < num_qwords; i++)
+		xe_map_wr(tile_to_xe(tile), map, (qword_ofs + i) *
+			  sizeof(u64), u64, ptes[i].pte);
 }
 
 static void xe_pt_cancel_bind(struct xe_vma *vma,
@@ -2022,22 +2010,23 @@ static unsigned int xe_pt_stage_unbind(struct xe_tile *tile,
 
 static void
 xe_migrate_clear_pgtable_callback(struct xe_vm *vm, struct xe_tile *tile,
-				  struct iosys_map *map, void *ptr,
-				  u32 qword_ofs, u32 num_qwords,
+				  struct iosys_map *map, u32 qword_ofs,
+				  u32 num_qwords,
 				  const struct xe_vm_pgtable_update *update)
 {
 	u64 empty = __xe_pt_empty_pte(tile, vm, update->level);
 	int i;
 
-	if (map && map->is_iomem)
+	xe_assert(vm->xe, map);
+	xe_assert(vm->xe, !iosys_map_is_null(map));
+
+	if (map->is_iomem)
 		for (i = 0; i < num_qwords; ++i)
 			xe_map_wr(tile_to_xe(tile), map, (qword_ofs + i) *
 				  sizeof(u64), u64, empty);
-	else if (map)
+	else
 		memset64(map->vaddr + qword_ofs * sizeof(u64), empty,
 			 num_qwords);
-	else
-		memset64(ptr, empty, num_qwords);
 }
 
 static void xe_pt_abort_unbind(struct xe_vma *vma,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 15/25] drm/xe: Make bind queues operate cross-tile
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (13 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 14/25] drm/xe: Remove unused arguments from xe_migrate_pt_update_ops Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 16/25] drm/xe: Add CPU bind layer Matthew Brost
                   ` (12 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Since bind jobs execute on the CPU rather than the GPU, maintaining a
per-tile bind queue no longer provides value. Convert the driver to use
a single bind queue shared across tiles. The primary change is routing
all GT TLB invalidations through this unified bind queue.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-16-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_exec_queue.c       | 143 +++++++++--------------
 drivers/gpu/drm/xe/xe_exec_queue.h       |  14 +--
 drivers/gpu/drm/xe/xe_exec_queue_types.h |  20 +---
 drivers/gpu/drm/xe/xe_pt.c               |  22 ++--
 drivers/gpu/drm/xe/xe_sync.c             |  20 +---
 drivers/gpu/drm/xe/xe_tlb_inval_job.c    |  15 ++-
 drivers/gpu/drm/xe/xe_tlb_inval_job.h    |   2 +-
 drivers/gpu/drm/xe/xe_vm.c               |  65 +++++------
 drivers/gpu/drm/xe/xe_vm_types.h         |   2 +-
 9 files changed, 125 insertions(+), 178 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c
index a894551c3ea6..afc5e37faa1b 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.c
+++ b/drivers/gpu/drm/xe/xe_exec_queue.c
@@ -142,9 +142,8 @@ static void __xe_exec_queue_free(struct xe_exec_queue *q)
 {
 	int i;
 
-	for (i = 0; i < XE_EXEC_QUEUE_TLB_INVAL_COUNT; ++i)
-		if (q->tlb_inval[i].dep_scheduler)
-			xe_dep_scheduler_fini(q->tlb_inval[i].dep_scheduler);
+	for_each_tlb_inval(q, i)
+		xe_dep_scheduler_fini(q->tlb_inval[i].dep_scheduler);
 
 	if (xe_exec_queue_uses_pxp(q))
 		xe_pxp_exec_queue_remove(gt_to_xe(q->gt)->pxp, q);
@@ -166,31 +165,34 @@ static void __xe_exec_queue_free(struct xe_exec_queue *q)
 
 static int alloc_dep_schedulers(struct xe_device *xe, struct xe_exec_queue *q)
 {
-	struct xe_tile *tile = gt_to_tile(q->gt);
-	int i;
+	struct xe_tile *tile;
+	int i = 0, j;
+	u8 id;
 
-	for (i = 0; i < XE_EXEC_QUEUE_TLB_INVAL_COUNT; ++i) {
-		struct xe_dep_scheduler *dep_scheduler;
-		struct xe_gt *gt;
-		struct workqueue_struct *wq;
+	for_each_tile(tile, xe, id) {
+		for (j = 0; j < (XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT + 1); ++j, ++i) {
+			struct xe_dep_scheduler *dep_scheduler;
+			struct xe_gt *gt;
+			struct workqueue_struct *wq;
 
-		if (i == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT)
-			gt = tile->primary_gt;
-		else
-			gt = tile->media_gt;
+			if (j == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT)
+				gt = tile->primary_gt;
+			else
+				gt = tile->media_gt;
 
-		if (!gt)
-			continue;
+			if (!gt)
+				continue;
 
-		wq = gt->tlb_inval.job_wq;
+			wq = gt->tlb_inval.job_wq;
 
 #define MAX_TLB_INVAL_JOBS	16	/* Picking a reasonable value */
-		dep_scheduler = xe_dep_scheduler_create(xe, wq, q->name,
-							MAX_TLB_INVAL_JOBS);
-		if (IS_ERR(dep_scheduler))
-			return PTR_ERR(dep_scheduler);
+			dep_scheduler = xe_dep_scheduler_create(xe, wq, q->name,
+								MAX_TLB_INVAL_JOBS);
+			if (IS_ERR(dep_scheduler))
+				return PTR_ERR(dep_scheduler);
 
-		q->tlb_inval[i].dep_scheduler = dep_scheduler;
+			q->tlb_inval[i].dep_scheduler = dep_scheduler;
+		}
 	}
 #undef MAX_TLB_INVAL_JOBS
 
@@ -224,7 +226,6 @@ static struct xe_exec_queue *__xe_exec_queue_alloc(struct xe_device *xe,
 	q->ops = gt->exec_queue_ops;
 	INIT_LIST_HEAD(&q->lr.link);
 	INIT_LIST_HEAD(&q->vm_exec_queue_link);
-	INIT_LIST_HEAD(&q->multi_gt_link);
 	INIT_LIST_HEAD(&q->hw_engine_group_link);
 	INIT_LIST_HEAD(&q->pxp.link);
 	spin_lock_init(&q->multi_queue.lock);
@@ -572,7 +573,6 @@ ALLOW_ERROR_INJECTION(xe_exec_queue_create_bind, ERRNO);
 void xe_exec_queue_destroy(struct kref *ref)
 {
 	struct xe_exec_queue *q = container_of(ref, struct xe_exec_queue, refcount);
-	struct xe_exec_queue *eq, *next;
 	int i;
 
 	xe_assert(gt_to_xe(q->gt), atomic_read(&q->job_cnt) == 0);
@@ -584,15 +584,9 @@ void xe_exec_queue_destroy(struct kref *ref)
 		xe_pxp_exec_queue_remove(gt_to_xe(q->gt)->pxp, q);
 
 	xe_exec_queue_last_fence_put_unlocked(q);
-	for_each_tlb_inval(i)
+	for_each_tlb_inval(q, i)
 		xe_exec_queue_tlb_inval_last_fence_put_unlocked(q, i);
 
-	if (!(q->flags & EXEC_QUEUE_FLAG_BIND_ENGINE_CHILD)) {
-		list_for_each_entry_safe(eq, next, &q->multi_gt_list,
-					 multi_gt_link)
-			xe_exec_queue_put(eq);
-	}
-
 	if (q->user_vm) {
 		xe_vm_put(q->user_vm);
 		q->user_vm = NULL;
@@ -1279,7 +1273,6 @@ int xe_exec_queue_create_ioctl(struct drm_device *dev, void *data,
 		u64_to_user_ptr(args->instances);
 	struct xe_hw_engine *hwe;
 	struct xe_vm *vm;
-	struct xe_tile *tile;
 	struct xe_exec_queue *q = NULL;
 	u32 logical_mask;
 	u32 flags = 0;
@@ -1328,31 +1321,16 @@ int xe_exec_queue_create_ioctl(struct drm_device *dev, void *data,
 			return -ENOENT;
 		}
 
-		for_each_tile(tile, xe, id) {
-			struct xe_exec_queue *new;
-
-			flags |= EXEC_QUEUE_FLAG_VM;
-			if (id)
-				flags |= EXEC_QUEUE_FLAG_BIND_ENGINE_CHILD;
-
-			new = xe_exec_queue_create_bind(xe, tile, vm, flags,
-							args->extensions);
-			if (IS_ERR(new)) {
-				up_read(&vm->lock);
-				xe_vm_put(vm);
-				err = PTR_ERR(new);
-				if (q)
-					goto put_exec_queue;
-				return err;
-			}
-			if (id == 0)
-				q = new;
-			else
-				list_add_tail(&new->multi_gt_list,
-					      &q->multi_gt_link);
-		}
+		flags |= EXEC_QUEUE_FLAG_VM;
+
+		q = xe_exec_queue_create_bind(xe, xe_device_get_root_tile(xe),
+					      vm, flags, args->extensions);
 		up_read(&vm->lock);
 		xe_vm_put(vm);
+		if (IS_ERR(q)) {
+			err = PTR_ERR(q);
+			return err;
+		}
 	} else {
 		logical_mask = calc_validate_logical_mask(xe, eci,
 							  args->width,
@@ -1570,14 +1548,6 @@ void xe_exec_queue_update_run_ticks(struct xe_exec_queue *q)
  */
 void xe_exec_queue_kill(struct xe_exec_queue *q)
 {
-	struct xe_exec_queue *eq = q, *next;
-
-	list_for_each_entry_safe(eq, next, &eq->multi_gt_list,
-				 multi_gt_link) {
-		q->ops->kill(eq);
-		xe_vm_remove_compute_exec_queue(q->vm, eq);
-	}
-
 	q->ops->kill(q);
 	xe_vm_remove_compute_exec_queue(q->vm, q);
 }
@@ -1738,42 +1708,39 @@ void xe_exec_queue_last_fence_set(struct xe_exec_queue *q, struct xe_vm *vm,
  * xe_exec_queue_tlb_inval_last_fence_put() - Drop ref to last TLB invalidation fence
  * @q: The exec queue
  * @vm: The VM the engine does a bind for
- * @type: Either primary or media GT
+ * @idx: Index of tlb invalidation
  */
 void xe_exec_queue_tlb_inval_last_fence_put(struct xe_exec_queue *q,
 					    struct xe_vm *vm,
-					    unsigned int type)
+					    unsigned int idx)
 {
 	xe_exec_queue_last_fence_lockdep_assert(q, vm);
-	xe_assert(vm->xe, type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
-		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+	xe_assert(vm->xe, idx < XE_EXEC_QUEUE_TLB_INVAL_COUNT);
 
-	xe_exec_queue_tlb_inval_last_fence_put_unlocked(q, type);
+	xe_exec_queue_tlb_inval_last_fence_put_unlocked(q, idx);
 }
 
 /**
  * xe_exec_queue_tlb_inval_last_fence_put_unlocked() - Drop ref to last TLB
  * invalidation fence unlocked
  * @q: The exec queue
- * @type: Either primary or media GT
+ * @idx: Index of tlb invalidation
  *
  * Only safe to be called from xe_exec_queue_destroy().
  */
 void xe_exec_queue_tlb_inval_last_fence_put_unlocked(struct xe_exec_queue *q,
-						     unsigned int type)
+						     unsigned int idx)
 {
-	xe_assert(gt_to_xe(q->gt), type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
-		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+	xe_assert(q->vm->xe, idx < XE_EXEC_QUEUE_TLB_INVAL_COUNT);
 
-	dma_fence_put(q->tlb_inval[type].last_fence);
-	q->tlb_inval[type].last_fence = NULL;
-}
+	dma_fence_put(q->tlb_inval[idx].last_fence);
+	q->tlb_inval[idx].last_fence = NULL;}
 
 /**
  * xe_exec_queue_tlb_inval_last_fence_get() - Get last fence for TLB invalidation
  * @q: The exec queue
  * @vm: The VM the engine does a bind for
- * @type: Either primary or media GT
+ * @idx: Index of tlb invalidation
  *
  * Get last fence, takes a ref
  *
@@ -1781,22 +1748,21 @@ void xe_exec_queue_tlb_inval_last_fence_put_unlocked(struct xe_exec_queue *q,
  */
 struct dma_fence *xe_exec_queue_tlb_inval_last_fence_get(struct xe_exec_queue *q,
 							 struct xe_vm *vm,
-							 unsigned int type)
+							 unsigned int idx)
 {
 	struct dma_fence *fence;
 
 	xe_exec_queue_last_fence_lockdep_assert(q, vm);
-	xe_assert(vm->xe, type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
-		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+	xe_assert(vm->xe, idx < XE_EXEC_QUEUE_TLB_INVAL_COUNT);
 	xe_assert(vm->xe, q->flags & (EXEC_QUEUE_FLAG_VM |
 				      EXEC_QUEUE_FLAG_MIGRATE));
 
-	if (q->tlb_inval[type].last_fence &&
+	if (q->tlb_inval[idx].last_fence &&
 	    test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
-		     &q->tlb_inval[type].last_fence->flags))
-		xe_exec_queue_tlb_inval_last_fence_put(q, vm, type);
+		     &q->tlb_inval[idx].last_fence->flags))
+		xe_exec_queue_tlb_inval_last_fence_put(q, vm, idx);
 
-	fence = q->tlb_inval[type].last_fence ?: dma_fence_get_stub();
+	fence = q->tlb_inval[idx].last_fence ?: dma_fence_get_stub();
 	dma_fence_get(fence);
 	return fence;
 }
@@ -1806,26 +1772,25 @@ struct dma_fence *xe_exec_queue_tlb_inval_last_fence_get(struct xe_exec_queue *q
  * @q: The exec queue
  * @vm: The VM the engine does a bind for
  * @fence: The fence
- * @type: Either primary or media GT
+ * @idx: Index of tlb invalidation
  *
- * Set the last fence for the tlb invalidation type on the queue. Increases
+ * Set the last fence for the tlb invalidation client on the queue. Increases
  * reference count for fence, when closing queue
  * xe_exec_queue_tlb_inval_last_fence_put should be called.
  */
 void xe_exec_queue_tlb_inval_last_fence_set(struct xe_exec_queue *q,
 					    struct xe_vm *vm,
 					    struct dma_fence *fence,
-					    unsigned int type)
+					    unsigned int idx)
 {
 	xe_exec_queue_last_fence_lockdep_assert(q, vm);
-	xe_assert(vm->xe, type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
-		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+	xe_assert(vm->xe, idx < XE_EXEC_QUEUE_TLB_INVAL_COUNT);
 	xe_assert(vm->xe, q->flags & (EXEC_QUEUE_FLAG_VM |
 				      EXEC_QUEUE_FLAG_MIGRATE));
 	xe_assert(vm->xe, !dma_fence_is_container(fence));
 
-	xe_exec_queue_tlb_inval_last_fence_put(q, vm, type);
-	q->tlb_inval[type].last_fence = dma_fence_get(fence);
+	xe_exec_queue_tlb_inval_last_fence_put(q, vm, idx);
+	q->tlb_inval[idx].last_fence = dma_fence_get(fence);
 }
 
 /**
diff --git a/drivers/gpu/drm/xe/xe_exec_queue.h b/drivers/gpu/drm/xe/xe_exec_queue.h
index 0225426c57b0..b02a390ba989 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.h
+++ b/drivers/gpu/drm/xe/xe_exec_queue.h
@@ -14,9 +14,9 @@ struct drm_file;
 struct xe_device;
 struct xe_file;
 
-#define for_each_tlb_inval(__i)	\
-	for (__i = XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT; \
-	     __i <= XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT; ++__i)
+#define for_each_tlb_inval(__q, __i)	\
+	for (__i = 0; __i < XE_EXEC_QUEUE_TLB_INVAL_COUNT; ++__i)	\
+		for_each_if((__q)->tlb_inval[__i].dep_scheduler)
 
 struct xe_exec_queue *xe_exec_queue_create(struct xe_device *xe, struct xe_vm *vm,
 					   u32 logical_mask, u16 width,
@@ -141,19 +141,19 @@ void xe_exec_queue_last_fence_set(struct xe_exec_queue *e, struct xe_vm *vm,
 
 void xe_exec_queue_tlb_inval_last_fence_put(struct xe_exec_queue *q,
 					    struct xe_vm *vm,
-					    unsigned int type);
+					    unsigned int idx);
 
 void xe_exec_queue_tlb_inval_last_fence_put_unlocked(struct xe_exec_queue *q,
-						     unsigned int type);
+						     unsigned int idx);
 
 struct dma_fence *xe_exec_queue_tlb_inval_last_fence_get(struct xe_exec_queue *q,
 							 struct xe_vm *vm,
-							 unsigned int type);
+							 unsigned int idx);
 
 void xe_exec_queue_tlb_inval_last_fence_set(struct xe_exec_queue *q,
 					    struct xe_vm *vm,
 					    struct dma_fence *fence,
-					    unsigned int type);
+					    unsigned int idx);
 
 void xe_exec_queue_update_run_ticks(struct xe_exec_queue *q);
 
diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h
index 95f75d61a647..5e460d61932a 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue_types.h
+++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h
@@ -137,16 +137,14 @@ struct xe_exec_queue {
 #define EXEC_QUEUE_FLAG_KERNEL			BIT(0)
 /* for VM jobs. Caller needs to hold rpm ref when creating queue with this flag */
 #define EXEC_QUEUE_FLAG_VM			BIT(1)
-/* child of VM queue for multi-tile VM jobs */
-#define EXEC_QUEUE_FLAG_BIND_ENGINE_CHILD	BIT(2)
 /* kernel exec_queue only, set priority to highest level */
-#define EXEC_QUEUE_FLAG_HIGH_PRIORITY		BIT(3)
+#define EXEC_QUEUE_FLAG_HIGH_PRIORITY		BIT(2)
 /* flag to indicate low latency hint to guc */
-#define EXEC_QUEUE_FLAG_LOW_LATENCY		BIT(4)
+#define EXEC_QUEUE_FLAG_LOW_LATENCY		BIT(3)
 /* for migration (kernel copy, clear, bind) jobs */
-#define EXEC_QUEUE_FLAG_MIGRATE			BIT(5)
+#define EXEC_QUEUE_FLAG_MIGRATE			BIT(4)
 /* for programming COMMON_SLICE_CHICKEN3 on first submission */
-#define EXEC_QUEUE_FLAG_DISABLE_STATE_CACHE_PERF_FIX	BIT(6)
+#define EXEC_QUEUE_FLAG_DISABLE_STATE_CACHE_PERF_FIX	BIT(5)
 
 	/**
 	 * @flags: flags for this exec queue, should statically setup aside from ban
@@ -154,13 +152,6 @@ struct xe_exec_queue {
 	 */
 	unsigned long flags;
 
-	union {
-		/** @multi_gt_list: list head for VM bind engines if multi-GT */
-		struct list_head multi_gt_list;
-		/** @multi_gt_link: link for VM bind engines if multi-GT */
-		struct list_head multi_gt_link;
-	};
-
 	union {
 		/** @execlist: execlist backend specific state for exec queue */
 		struct xe_execlist_exec_queue *execlist;
@@ -227,7 +218,8 @@ struct xe_exec_queue {
 
 #define XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT	0
 #define XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT	1
-#define XE_EXEC_QUEUE_TLB_INVAL_COUNT		(XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT  + 1)
+#define XE_EXEC_QUEUE_TLB_INVAL_COUNT	\
+	((XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT + 1) * 2)
 
 	/** @tlb_inval: TLB invalidations exec queue state */
 	struct {
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 0f749f717861..7c7cd7a0365b 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -2711,12 +2711,18 @@ static const struct xe_migrate_pt_update_ops svm_userptr_migrate_ops;
 #endif
 
 static struct xe_dep_scheduler *to_dep_scheduler(struct xe_exec_queue *q,
-						 struct xe_gt *gt)
+						 struct xe_tile *tile,
+						 struct xe_gt *gt,
+						 unsigned int *type)
 {
+	int tile_ofs = tile->id * (XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT + 1);
+
 	if (xe_gt_is_media_type(gt))
-		return q->tlb_inval[XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT].dep_scheduler;
+		*type = tile_ofs + XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT;
+	else
+		*type = tile_ofs + XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT;
 
-	return q->tlb_inval[XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT].dep_scheduler;
+	return q->tlb_inval[*type].dep_scheduler;
 }
 
 /**
@@ -2741,6 +2747,7 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 	struct xe_tlb_inval_job *ijob = NULL, *mjob = NULL;
 	struct xe_range_fence *rfence;
 	struct xe_vma_op *op;
+	unsigned int type;
 	int err = 0, i;
 	struct xe_migrate_pt_update update = {
 		.ops = pt_update_ops->needs_svm_lock ?
@@ -2767,13 +2774,13 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 
 	if (pt_update_ops->needs_invalidation) {
 		struct xe_dep_scheduler *dep_scheduler =
-			to_dep_scheduler(q, tile->primary_gt);
+			to_dep_scheduler(q, tile, tile->primary_gt, &type);
 
 		ijob = xe_tlb_inval_job_create(q, &tile->primary_gt->tlb_inval,
 					       dep_scheduler, vm,
 					       pt_update_ops->start,
 					       pt_update_ops->last,
-					       XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+					       type);
 		if (IS_ERR(ijob)) {
 			err = PTR_ERR(ijob);
 			goto kill_vm_tile1;
@@ -2792,14 +2799,15 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 		}
 
 		if (tile->media_gt) {
-			dep_scheduler = to_dep_scheduler(q, tile->media_gt);
+			dep_scheduler = to_dep_scheduler(q, tile,
+							 tile->media_gt, &type);
 
 			mjob = xe_tlb_inval_job_create(q,
 						       &tile->media_gt->tlb_inval,
 						       dep_scheduler, vm,
 						       pt_update_ops->start,
 						       pt_update_ops->last,
-						       XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT);
+						       type);
 			if (IS_ERR(mjob)) {
 				err = PTR_ERR(mjob);
 				goto free_ijob;
diff --git a/drivers/gpu/drm/xe/xe_sync.c b/drivers/gpu/drm/xe/xe_sync.c
index 37866768d64c..06b1c913588a 100644
--- a/drivers/gpu/drm/xe/xe_sync.c
+++ b/drivers/gpu/drm/xe/xe_sync.c
@@ -345,15 +345,9 @@ xe_sync_in_fence_get(struct xe_sync_entry *sync, int num_sync,
 			return ERR_PTR(-EOPNOTSUPP);
 
 	if (q->flags & EXEC_QUEUE_FLAG_VM) {
-		struct xe_exec_queue *__q;
-		struct xe_tile *tile;
-		u8 id;
-
-		for_each_tile(tile, vm->xe, id) {
+		num_fence++;
+		for_each_tlb_inval(q, i)
 			num_fence++;
-			for_each_tlb_inval(i)
-				num_fence++;
-		}
 
 		fences = kmalloc_objs(*fences, num_fence);
 		if (!fences)
@@ -361,17 +355,9 @@ xe_sync_in_fence_get(struct xe_sync_entry *sync, int num_sync,
 
 		fences[current_fence++] =
 			xe_exec_queue_last_fence_get(q, vm);
-		for_each_tlb_inval(i)
+		for_each_tlb_inval(q, i)
 			fences[current_fence++] =
 				xe_exec_queue_tlb_inval_last_fence_get(q, vm, i);
-		list_for_each_entry(__q, &q->multi_gt_list,
-				    multi_gt_link) {
-			fences[current_fence++] =
-				xe_exec_queue_last_fence_get(__q, vm);
-			for_each_tlb_inval(i)
-				fences[current_fence++] =
-					xe_exec_queue_tlb_inval_last_fence_get(__q, vm, i);
-		}
 
 		xe_assert(vm->xe, current_fence == num_fence);
 		cf = dma_fence_array_create(num_fence, fences,
diff --git a/drivers/gpu/drm/xe/xe_tlb_inval_job.c b/drivers/gpu/drm/xe/xe_tlb_inval_job.c
index 04d21015cd5d..81f560068d3c 100644
--- a/drivers/gpu/drm/xe/xe_tlb_inval_job.c
+++ b/drivers/gpu/drm/xe/xe_tlb_inval_job.c
@@ -39,8 +39,8 @@ struct xe_tlb_inval_job {
 	u64 start;
 	/** @end: End address to invalidate */
 	u64 end;
-	/** @type: GT type */
-	int type;
+	/** @idx: Index of tlb invalidation */
+	int idx;
 	/** @fence_armed: Fence has been armed */
 	bool fence_armed;
 };
@@ -87,7 +87,7 @@ static const struct xe_dep_job_ops dep_job_ops = {
  * @vm: VM which TLB invalidation is being issued for
  * @start: Start address to invalidate
  * @end: End address to invalidate
- * @type: GT type
+ * @idx: Index of tlb invalidation
  *
  * Create a TLB invalidation job and initialize internal fields. The caller is
  * responsible for releasing the creation reference.
@@ -97,7 +97,7 @@ static const struct xe_dep_job_ops dep_job_ops = {
 struct xe_tlb_inval_job *
 xe_tlb_inval_job_create(struct xe_exec_queue *q, struct xe_tlb_inval *tlb_inval,
 			struct xe_dep_scheduler *dep_scheduler,
-			struct xe_vm *vm, u64 start, u64 end, int type)
+			struct xe_vm *vm, u64 start, u64 end, int idx)
 {
 	struct xe_tlb_inval_job *job;
 	struct drm_sched_entity *entity =
@@ -105,8 +105,7 @@ xe_tlb_inval_job_create(struct xe_exec_queue *q, struct xe_tlb_inval *tlb_inval,
 	struct xe_tlb_inval_fence *ifence;
 	int err;
 
-	xe_assert(vm->xe, type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
-		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
+	xe_assert(vm->xe, idx < XE_EXEC_QUEUE_TLB_INVAL_COUNT);
 
 	job = kmalloc_obj(*job);
 	if (!job)
@@ -120,7 +119,7 @@ xe_tlb_inval_job_create(struct xe_exec_queue *q, struct xe_tlb_inval *tlb_inval,
 	job->fence_armed = false;
 	xe_page_reclaim_list_init(&job->prl);
 	job->dep.ops = &dep_job_ops;
-	job->type = type;
+	job->idx = idx;
 	kref_init(&job->refcount);
 	xe_exec_queue_get(q);	/* Pairs with put in xe_tlb_inval_job_destroy */
 	xe_vm_get(vm);		/* Pairs with put in xe_tlb_inval_job_destroy */
@@ -280,7 +279,7 @@ struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
 	/* Let the upper layers fish this out */
 	xe_exec_queue_tlb_inval_last_fence_set(job->q, job->vm,
 					       &job->dep.drm.s_fence->finished,
-					       job->type);
+					       job->idx);
 
 	xe_migrate_job_unlock(m, job->q);
 
diff --git a/drivers/gpu/drm/xe/xe_tlb_inval_job.h b/drivers/gpu/drm/xe/xe_tlb_inval_job.h
index 03d6e21cd611..2a4478f529e6 100644
--- a/drivers/gpu/drm/xe/xe_tlb_inval_job.h
+++ b/drivers/gpu/drm/xe/xe_tlb_inval_job.h
@@ -20,7 +20,7 @@ struct xe_vm;
 struct xe_tlb_inval_job *
 xe_tlb_inval_job_create(struct xe_exec_queue *q, struct xe_tlb_inval *tlb_inval,
 			struct xe_dep_scheduler *dep_scheduler,
-			struct xe_vm *vm, u64 start, u64 end, int type);
+			struct xe_vm *vm, u64 start, u64 end, int idx);
 
 void xe_tlb_inval_job_add_page_reclaim(struct xe_tlb_inval_job *job,
 				       struct xe_page_reclaim_list *prl);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 13e984ac4e4f..433a0a681556 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -1815,7 +1815,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
 			struct xe_exec_queue *q;
 			u32 create_flags = EXEC_QUEUE_FLAG_VM;
 
-			if (!vm->pt_root[id])
+			if (!vm->pt_root[id] || vm->q)
 				continue;
 
 			if (!xef) /* Not from userspace */
@@ -1826,7 +1826,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
 				err = PTR_ERR(q);
 				goto err_close;
 			}
-			vm->q[id] = q;
+			vm->q = q;
 		}
 	}
 
@@ -1933,24 +1933,18 @@ void xe_vm_close_and_put(struct xe_vm *vm)
 	if (xe_vm_in_fault_mode(vm))
 		xe_svm_close(vm);
 
-	down_write(&vm->lock);
-	for_each_tile(tile, xe, id) {
-		if (vm->q[id]) {
-			int i;
+	if (vm->q) {
+		int i;
 
-			xe_exec_queue_last_fence_put(vm->q[id], vm);
-			for_each_tlb_inval(i)
-				xe_exec_queue_tlb_inval_last_fence_put(vm->q[id], vm, i);
-		}
-	}
-	up_write(&vm->lock);
+		down_write(&vm->lock);
+		xe_exec_queue_last_fence_put(vm->q, vm);
+		for_each_tlb_inval(vm->q, i)
+			xe_exec_queue_tlb_inval_last_fence_put(vm->q, vm, i);
+		up_write(&vm->lock);
 
-	for_each_tile(tile, xe, id) {
-		if (vm->q[id]) {
-			xe_exec_queue_kill(vm->q[id]);
-			xe_exec_queue_put(vm->q[id]);
-			vm->q[id] = NULL;
-		}
+		xe_exec_queue_kill(vm->q);
+		xe_exec_queue_put(vm->q);
+		vm->q = NULL;
 	}
 
 	down_write(&vm->lock);
@@ -2084,7 +2078,7 @@ u64 xe_vm_pdp4_descriptor(struct xe_vm *vm, struct xe_tile *tile)
 static struct xe_exec_queue *
 to_wait_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
 {
-	return q ? q : vm->q[0];
+	return q ? q : vm->q;
 }
 
 static struct xe_user_fence *
@@ -3527,13 +3521,10 @@ static int vm_ops_setup_tile_args(struct xe_vm *vm, struct xe_vma_ops *vops)
 		if (vops->pt_update_ops[id].q)
 			continue;
 
-		if (q) {
+		if (q)
 			vops->pt_update_ops[id].q = q;
-			if (vm->pt_root[id] && !list_empty(&q->multi_gt_list))
-				q = list_next_entry(q, multi_gt_list);
-		} else {
-			vops->pt_update_ops[id].q = vm->q[id];
-		}
+		else
+			vops->pt_update_ops[id].q = vm->q;
 	}
 
 	return number_tiles;
@@ -3553,15 +3544,15 @@ static struct dma_fence *ops_execute(struct xe_vm *vm,
 	if (number_tiles == 0)
 		return ERR_PTR(-ENODATA);
 
-	for_each_tile(tile, vm->xe, id) {
+	for_each_tile(tile, vm->xe, id)
 		++n_fence;
 
-		if (!(vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT))
-			for_each_tlb_inval(i)
-				++n_fence;
+	if (!(vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT)) {
+		for_each_tlb_inval(vops->pt_update_ops[0].q, i)
+			++n_fence;
 	}
 
-	fences = kmalloc_objs(*fences, n_fence);
+	fences = kcalloc(n_fence, sizeof(*fences), GFP_KERNEL);
 	if (!fences) {
 		fence = ERR_PTR(-ENOMEM);
 		goto err_trace;
@@ -3603,9 +3594,15 @@ static struct dma_fence *ops_execute(struct xe_vm *vm,
 			continue;
 
 		xe_migrate_job_lock(tile->migrate, q);
-		for_each_tlb_inval(i)
-			fences[current_fence++] =
-				xe_exec_queue_tlb_inval_last_fence_get(q, vm, i);
+		for_each_tlb_inval(q, i) {
+			if (i >= (tile->id + 1) * XE_MAX_GT_PER_TILE ||
+			    i < tile->id * XE_MAX_GT_PER_TILE)
+				continue;
+
+			fences[current_fence++] = fence ?
+				xe_exec_queue_tlb_inval_last_fence_get(q, vm, i) :
+				dma_fence_get_stub();
+		}
 		xe_migrate_job_unlock(tile->migrate, q);
 	}
 
@@ -4135,7 +4132,7 @@ int xe_vm_bind_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
 
 	syncs_user = u64_to_user_ptr(args->syncs);
 	for (num_syncs = 0; num_syncs < args->num_syncs; num_syncs++) {
-		struct xe_exec_queue *__q = q ?: vm->q[0];
+		struct xe_exec_queue *__q = q ?: vm->q;
 
 		err = xe_sync_entry_parse(xe, xef, &syncs[num_syncs],
 					  &syncs_user[num_syncs],
diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h
index 648031e64145..c91cb13fc4f1 100644
--- a/drivers/gpu/drm/xe/xe_vm_types.h
+++ b/drivers/gpu/drm/xe/xe_vm_types.h
@@ -262,7 +262,7 @@ struct xe_vm {
 	struct xe_device *xe;
 
 	/* exec queue used for (un)binding vma's */
-	struct xe_exec_queue *q[XE_MAX_TILES_PER_DEVICE];
+	struct xe_exec_queue *q;
 
 	/** @lru_bulk_move: Bulk LRU move list for this VM's BOs */
 	struct ttm_lru_bulk_move lru_bulk_move;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 16/25] drm/xe: Add CPU bind layer
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (14 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 15/25] drm/xe: Make bind queues operate cross-tile Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:31   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles Matthew Brost
                   ` (11 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

With CPU binds, it no longer makes sense to implement CPU bind handling
in the migrate layer, as these operations are entirely decoupled from
hardware. Introduce a dedicated CPU bind layer stored at the device
level.

Since CPU binds are tile-independent, update the PT layer to generate a
single bind job even when pages are mirrored across tiles.

This patch is large because the refactor touches multiple file / layers
and ensures functional equivalence before and after the change.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-17-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/Makefile             |   1 +
 drivers/gpu/drm/xe/xe_cpu_bind.c        | 295 +++++++++++++
 drivers/gpu/drm/xe/xe_cpu_bind.h        | 118 +++++
 drivers/gpu/drm/xe/xe_device.c          |   5 +
 drivers/gpu/drm/xe/xe_device_types.h    |   4 +
 drivers/gpu/drm/xe/xe_exec_queue.c      |   3 +-
 drivers/gpu/drm/xe/xe_guc_submit.c      |  41 +-
 drivers/gpu/drm/xe/xe_migrate.c         | 246 -----------
 drivers/gpu/drm/xe/xe_migrate.h         |  94 ----
 drivers/gpu/drm/xe/xe_pt.c              | 549 ++++++++++++------------
 drivers/gpu/drm/xe/xe_pt.h              |   8 +-
 drivers/gpu/drm/xe/xe_pt_types.h        |  14 -
 drivers/gpu/drm/xe/xe_sched_job.c       |  10 +-
 drivers/gpu/drm/xe/xe_sched_job_types.h |  11 +-
 drivers/gpu/drm/xe/xe_tlb_inval_job.c   |  13 +-
 drivers/gpu/drm/xe/xe_tlb_inval_job.h   |   2 -
 drivers/gpu/drm/xe/xe_vm.c              | 155 ++-----
 drivers/gpu/drm/xe/xe_vm_types.h        |  10 +-
 18 files changed, 810 insertions(+), 769 deletions(-)
 create mode 100644 drivers/gpu/drm/xe/xe_cpu_bind.c
 create mode 100644 drivers/gpu/drm/xe/xe_cpu_bind.h

diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile
index 67b8b5477639..0670a3f08ec8 100644
--- a/drivers/gpu/drm/xe/Makefile
+++ b/drivers/gpu/drm/xe/Makefile
@@ -35,6 +35,7 @@ $(obj)/generated/%_device_wa_oob.c $(obj)/generated/%_device_wa_oob.h: $(obj)/xe
 xe-y += xe_bb.o \
 	xe_bo.o \
 	xe_bo_evict.o \
+	xe_cpu_bind.o \
 	xe_dep_scheduler.o \
 	xe_devcoredump.o \
 	xe_device.o \
diff --git a/drivers/gpu/drm/xe/xe_cpu_bind.c b/drivers/gpu/drm/xe/xe_cpu_bind.c
new file mode 100644
index 000000000000..ed79e229151b
--- /dev/null
+++ b/drivers/gpu/drm/xe/xe_cpu_bind.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#include <drm/drm_managed.h>
+#include <linux/mutex.h>
+
+#include "xe_cpu_bind.h"
+#include "xe_device_types.h"
+#include "xe_exec_queue.h"
+#include "xe_pt.h"
+#include "xe_sched_job.h"
+#include "xe_trace_bo.h"
+#include "xe_vm.h"
+
+/**
+ * struct xe_cpu_bind - cpu_bind context.
+ */
+struct xe_cpu_bind {
+	/** @xe: Xe device */
+	struct xe_device *xe;
+	/** @q: Default exec queue used for kernel binds */
+	struct xe_exec_queue *q;
+	/** @job_mutex: Timeline mutex for @q. */
+	struct mutex job_mutex;
+};
+
+static bool is_cpu_bind_queue(struct xe_cpu_bind *cpu_bind,
+			      struct xe_exec_queue *q)
+{
+	return cpu_bind->q == q;
+}
+
+static void xe_cpu_bind_fini(void *arg)
+{
+	struct xe_cpu_bind *cpu_bind = arg;
+
+	mutex_destroy(&cpu_bind->job_mutex);
+	xe_exec_queue_put(cpu_bind->q);
+}
+
+/**
+ * xe_cpu_bind_init() - Initialize a cpu_bind context
+ * @xe: &xe_device
+ *
+ * Return: 0 if successful, negative error code on failure
+ */
+int xe_cpu_bind_init(struct xe_device *xe)
+{
+	struct xe_cpu_bind *cpu_bind =
+		drmm_kzalloc(&xe->drm, sizeof(*cpu_bind), GFP_KERNEL);
+	struct xe_exec_queue *q;
+
+	q = xe_exec_queue_create_bind(xe, xe_device_get_root_tile(xe), NULL,
+				      EXEC_QUEUE_FLAG_KERNEL |
+				      EXEC_QUEUE_FLAG_MIGRATE, 0);
+	if (IS_ERR(q))
+		return PTR_ERR(q);
+
+	cpu_bind->xe = xe;
+	cpu_bind->q = q;
+	xe->cpu_bind = cpu_bind;
+
+	mutex_init(&cpu_bind->job_mutex);
+
+	fs_reclaim_acquire(GFP_KERNEL);
+	might_lock(&cpu_bind->job_mutex);
+	fs_reclaim_release(GFP_KERNEL);
+
+	return devm_add_action_or_reset(cpu_bind->xe->drm.dev, xe_cpu_bind_fini,
+					cpu_bind);
+}
+
+/**
+ * xe_cpu_bind_queue() - Get the bind queue from cpu_bind context.
+ * @cpu_bind: The cpu bind context.
+ *
+ * Return: Pointer to bind queue on success, error on failure
+ */
+struct xe_exec_queue *xe_cpu_bind_queue(struct xe_cpu_bind *cpu_bind)
+{
+	return cpu_bind->q;
+}
+
+/**
+ * xe_cpu_bind_update_pgtables_execute() - Update a VM's PTEs via the CPU
+ * @vm: The VM being updated
+ * @tile: The tile being updated
+ * @ops: The migrate PT update ops
+ * @pt_op: The VM PT update op
+ * @num_ops: The number of The VM PT update ops
+ *
+ * Execute the VM PT update ops array which results in a VM's PTEs being updated
+ * via the CPU.
+ */
+void
+xe_cpu_bind_update_pgtables_execute(struct xe_vm *vm, struct xe_tile *tile,
+				    const struct xe_cpu_bind_pt_update_ops *ops,
+				    struct xe_vm_pgtable_update_op *pt_op,
+				    int num_ops)
+{
+	u32 j, i;
+
+	for (j = 0; j < num_ops; ++j, ++pt_op) {
+		for (i = 0; i < pt_op->num_entries; i++) {
+			const struct xe_vm_pgtable_update *update =
+				&pt_op->entries[i];
+
+			xe_assert(vm->xe, update);
+			xe_assert(vm->xe, update->pt_bo);
+			xe_assert(vm->xe, !iosys_map_is_null(&update->pt_bo->vmap));
+
+			if (pt_op->bind)
+				ops->populate(tile, &update->pt_bo->vmap,
+					      update->ofs, update->qwords,
+					      update);
+			else
+				ops->clear(vm, tile, &update->pt_bo->vmap,
+					   update->ofs, update->qwords,
+					   update);
+		}
+	}
+
+	trace_xe_vm_cpu_bind(vm);
+	xe_device_wmb(vm->xe);
+}
+
+static struct dma_fence *
+xe_cpu_bind_update_pgtables_no_job(struct xe_cpu_bind *cpu_bind,
+				   struct xe_cpu_bind_pt_update *pt_update)
+{
+	const struct xe_cpu_bind_pt_update_ops *ops = pt_update->ops;
+	struct xe_vm *vm = pt_update->vops->vm;
+	struct xe_tile *tile;
+	int err, id;
+
+	if (ops->pre_commit) {
+		pt_update->job = NULL;
+		err = ops->pre_commit(pt_update);
+		if (err)
+			return ERR_PTR(err);
+	}
+
+	for_each_tile(tile, vm->xe, id) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&pt_update->vops->pt_update_ops[tile->id];
+
+		if (!pt_update_ops->pt_job_ops)
+			continue;
+
+		xe_cpu_bind_update_pgtables_execute(vm, tile, ops,
+						    pt_update_ops->pt_job_ops->ops,
+						    pt_update_ops->pt_job_ops->current_op);
+	}
+
+	return dma_fence_get_stub();
+}
+
+static struct dma_fence *
+xe_cpu_bind_update_pgtables_job(struct xe_cpu_bind *cpu_bind,
+				struct xe_cpu_bind_pt_update *pt_update)
+{
+	const struct xe_cpu_bind_pt_update_ops *ops = pt_update->ops;
+	struct xe_exec_queue *q = pt_update->vops->q;
+	struct xe_device *xe = cpu_bind->xe;
+	struct xe_sched_job *job;
+	struct dma_fence *fence;
+	struct xe_tile *tile;
+	int err, id;
+	bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
+
+	job = xe_sched_job_create(q, NULL);
+	if (IS_ERR(job))
+		return ERR_CAST(job);
+
+	xe_assert(xe, job->is_pt_job);
+
+	if (ops->pre_commit) {
+		pt_update->job = job;
+		err = ops->pre_commit(pt_update);
+		if (err)
+			goto err_job;
+	}
+
+	if (is_cpu_bind)
+		mutex_lock(&cpu_bind->job_mutex);
+
+	job->pt_update[0].vm = pt_update->vops->vm;
+	job->pt_update[0].ops = ops;
+	for_each_tile(tile, xe, id) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&pt_update->vops->pt_update_ops[tile->id];
+
+		job->pt_update[0].pt_job_ops[tile->id] =
+			xe_pt_job_ops_get(pt_update_ops->pt_job_ops);
+	}
+
+	xe_sched_job_arm(job);
+	fence = dma_fence_get(&job->drm.s_fence->finished);
+	xe_sched_job_push(job);
+
+	if (is_cpu_bind)
+		mutex_unlock(&cpu_bind->job_mutex);
+
+	return fence;
+
+err_job:
+	xe_sched_job_put(job);
+	return ERR_PTR(err);
+}
+
+/**
+ * xe_cpu_bind_update_pgtables() - Pipelined page-table update
+ * @cpu_bind: The cpu bind context.
+ * @pt_update: PT update arguments
+ *
+ * Perform a pipelined page-table update. The update descriptors are typically
+ * built under the same lock critical section as a call to this function. If
+ * using the default engine for the updates, they will be performed in the
+ * order they grab the job_mutex. If different engines are used, external
+ * synchronization is needed for overlapping updates to maintain page-table
+ * consistency. Note that the meaning of "overlapping" is that the updates
+ * touch the same page-table, which might be a higher-level page-directory.
+ * If no pipelining is needed, then updates may be performed by the cpu.
+ *
+ * Return: A dma_fence that, when signaled, indicates the update completion.
+ */
+struct dma_fence *
+xe_cpu_bind_update_pgtables(struct xe_cpu_bind *cpu_bind,
+			    struct xe_cpu_bind_pt_update *pt_update)
+{
+	struct dma_fence *fence;
+
+	fence = xe_cpu_bind_update_pgtables_no_job(cpu_bind, pt_update);
+
+	/* -ETIME indicates a job is needed, anything else is legit error */
+	if (!IS_ERR(fence) || PTR_ERR(fence) != -ETIME)
+		return fence;
+
+	return xe_cpu_bind_update_pgtables_job(cpu_bind, pt_update);
+}
+
+/**
+ * xe_cpu_bind_job_lock() - Lock cpu_bind job lock
+ * @cpu_bind: The cpu bind context.
+ * @q: Queue associated with the operation which requires a lock
+ *
+ * Lock the cpu_bind job lock if the queue is a cpu bind queue, otherwise
+ * assert the VM's dma-resv is held (user queue's have own locking).
+ */
+void xe_cpu_bind_job_lock(struct xe_cpu_bind *cpu_bind,
+			  struct xe_exec_queue *q)
+{
+	bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
+
+	if (is_cpu_bind)
+		mutex_lock(&cpu_bind->job_mutex);
+	else
+		xe_vm_assert_held(q->user_vm);	/* User queues VM's should be locked */
+}
+
+/**
+ * xe_cpu_bind_job_unlock() - Unlock cpu_bind job lock
+ * @cpu_bind: The cpu bind context.
+ * @q: Queue associated with the operation which requires a lock
+ *
+ * Unlock the cpu_bind job lock if the queue is a cpu bind queue, otherwise
+ * assert the VM's dma-resv is held (user queue's have own locking).
+ */
+void xe_cpu_bind_job_unlock(struct xe_cpu_bind *cpu_bind,
+			    struct xe_exec_queue *q)
+{
+	bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
+
+	if (is_cpu_bind)
+		mutex_unlock(&cpu_bind->job_mutex);
+	else
+		xe_vm_assert_held(q->user_vm);	/* User queues VM's should be locked */
+}
+
+#if IS_ENABLED(CONFIG_PROVE_LOCKING)
+/**
+ * xe_cpu_bind_job_lock_assert() - Assert cpu_bind job lock held of queue
+ * @q: cpu bind queue
+ */
+void xe_cpu_bind_job_lock_assert(struct xe_exec_queue *q)
+{
+	struct xe_device *xe = gt_to_xe(q->gt);
+	struct xe_cpu_bind *cpu_bind = xe->cpu_bind;
+
+	xe_assert(xe, q == cpu_bind->q);
+	lockdep_assert_held(&cpu_bind->job_mutex);
+}
+#endif
diff --git a/drivers/gpu/drm/xe/xe_cpu_bind.h b/drivers/gpu/drm/xe/xe_cpu_bind.h
new file mode 100644
index 000000000000..95996a6a5c20
--- /dev/null
+++ b/drivers/gpu/drm/xe/xe_cpu_bind.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#ifndef _XE_CPU_BIND_H_
+#define _XE_CPU_BIND_H_
+
+#include <linux/types.h>
+
+struct dma_fence;
+struct iosys_map;
+struct xe_cpu_bind;
+struct xe_cpu_bind_pt_update;
+struct xe_device;
+struct xe_tlb_inval_job;
+struct xe_tile;
+struct xe_vm;
+struct xe_vm_pgtable_update;
+struct xe_vm_pgtable_update_op;
+struct xe_vma_ops;
+
+/**
+ * struct xe_cpu_bind_pt_update_ops - Callbacks for the
+ * xe_cpu_bind_update_pgtables() function.
+ */
+struct xe_cpu_bind_pt_update_ops {
+	/**
+	 * @populate: Populate a command buffer or page-table with ptes.
+	 * @tile: The tile for the current operation.
+	 * @map: struct iosys_map into the memory to be populated.
+	 * @ofs: qword offset into @map, unused if @map is NULL.
+	 * @num_qwords: Number of qwords to write.
+	 * @update: Information about the PTEs to be inserted.
+	 *
+	 * This interface is intended to be used as a callback into the
+	 * page-table system to populate command buffers or shared
+	 * page-tables with PTEs.
+	 */
+	void (*populate)(struct xe_tile *tile, struct iosys_map *map,
+			 u32 ofs, u32 num_qwords,
+			 const struct xe_vm_pgtable_update *update);
+	/**
+	 * @clear: Clear a command buffer or page-table with ptes.
+	 * @vm: VM being updated
+	 * @tile: The tile for the current operation.
+	 * @map: struct iosys_map into the memory to be populated.
+	 * @ofs: qword offset into @map, unused if @map is NULL.
+	 * @num_qwords: Number of qwords to write.
+	 * @update: Information about the PTEs to be inserted.
+	 *
+	 * This interface is intended to be used as a callback into the
+	 * page-table system to populate command buffers or shared
+	 * page-tables with PTEs.
+	 */
+	void (*clear)(struct xe_vm *vm, struct xe_tile *tile,
+		      struct iosys_map *map, u32 ofs, u32 num_qwords,
+		      const struct xe_vm_pgtable_update *update);
+
+	/**
+	 * @pre_commit: Callback to be called just before arming the
+	 * sched_job.
+	 * @pt_update: Pointer to embeddable callback argument.
+	 *
+	 * Return: 0 on success, negative error code on error.
+	 */
+	int (*pre_commit)(struct xe_cpu_bind_pt_update *pt_update);
+};
+
+/**
+ * struct xe_cpu_bind_pt_update - Argument to the struct
+ * xe_cpu_bind_pt_update_ops callbacks.
+ *
+ * Intended to be subclassed to support additional arguments if necessary.
+ */
+struct xe_cpu_bind_pt_update {
+	/** @ops: Pointer to the struct xe_cpu_bind_pt_update_ops callbacks */
+	const struct xe_cpu_bind_pt_update_ops *ops;
+	/** @vops: VMA operations */
+	struct xe_vma_ops *vops;
+	/** @job: The job if a GPU page-table update. NULL otherwise */
+	struct xe_sched_job *job;
+	/**
+	 * @ijobs: The TLB invalidation jobs, individual instances can be NULL
+	 */
+#define XE_CPU_BIND_INVAL_JOB_COUNT	4
+	struct xe_tlb_inval_job *ijobs[XE_CPU_BIND_INVAL_JOB_COUNT];
+};
+
+int xe_cpu_bind_init(struct xe_device *xe);
+
+struct xe_exec_queue *xe_cpu_bind_queue(struct xe_cpu_bind *cpu_bind);
+
+void
+xe_cpu_bind_update_pgtables_execute(struct xe_vm *vm, struct xe_tile *tile,
+				    const struct xe_cpu_bind_pt_update_ops *ops,
+				    struct xe_vm_pgtable_update_op *pt_op,
+				    int num_ops);
+
+struct dma_fence *
+xe_cpu_bind_update_pgtables(struct xe_cpu_bind *cpu_bind,
+			    struct xe_cpu_bind_pt_update *pt_update);
+
+void xe_cpu_bind_job_lock(struct xe_cpu_bind *cpu_bind,
+			  struct xe_exec_queue *q);
+
+void xe_cpu_bind_job_unlock(struct xe_cpu_bind *cpu_bind,
+			    struct xe_exec_queue *q);
+
+#if IS_ENABLED(CONFIG_PROVE_LOCKING)
+void xe_cpu_bind_job_lock_assert(struct xe_exec_queue *q);
+#else
+static inline void xe_cpu_bind_job_lock_assert(struct xe_exec_queue *q)
+{
+}
+#endif
+
+#endif
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index 8583b2e9ecf4..ade971de1652 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -27,6 +27,7 @@
 #include "xe_bo_evict.h"
 #include "xe_configfs.h"
 #include "xe_debugfs.h"
+#include "xe_cpu_bind.h"
 #include "xe_defaults.h"
 #include "xe_devcoredump.h"
 #include "xe_device_sysfs.h"
@@ -1075,6 +1076,10 @@ int xe_device_probe(struct xe_device *xe)
 	if (err)
 		return err;
 
+	err = xe_cpu_bind_init(xe);
+	if (err)
+		return err;
+
 	err = xe_pagefault_init(xe);
 	if (err)
 		return err;
diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
index 180d450a6deb..bc60833c39a7 100644
--- a/drivers/gpu/drm/xe/xe_device_types.h
+++ b/drivers/gpu/drm/xe/xe_device_types.h
@@ -38,6 +38,7 @@
 struct drm_pagemap_shrinker;
 struct intel_display;
 struct intel_dg_nvm_dev;
+struct xe_cpu_bind;
 struct xe_ggtt;
 struct xe_i2c;
 struct xe_pat_ops;
@@ -559,6 +560,9 @@ struct xe_device {
 	/** @sc: System Controller */
 	struct xe_sysctrl sc;
 
+	/** @cpu_bind: CPU bind object */
+	struct xe_cpu_bind *cpu_bind;
+
 	/** @atomic_svm_timeslice_ms: Atomic SVM fault timeslice MS */
 	u32 atomic_svm_timeslice_ms;
 
diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c
index afc5e37faa1b..e89802ff4f0e 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.c
+++ b/drivers/gpu/drm/xe/xe_exec_queue.c
@@ -14,6 +14,7 @@
 #include <uapi/drm/xe_drm.h>
 
 #include "xe_bo.h"
+#include "xe_cpu_bind.h"
 #include "xe_dep_scheduler.h"
 #include "xe_device.h"
 #include "xe_gt.h"
@@ -1598,7 +1599,7 @@ static void xe_exec_queue_last_fence_lockdep_assert(struct xe_exec_queue *q,
 						    struct xe_vm *vm)
 {
 	if (q->flags & EXEC_QUEUE_FLAG_MIGRATE) {
-		xe_migrate_job_lock_assert(q);
+		xe_cpu_bind_job_lock_assert(q);
 	} else if (q->flags & EXEC_QUEUE_FLAG_VM) {
 		lockdep_assert_held(&vm->lock);
 	} else {
diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index 528869928c1d..dcb4b8a4f3b7 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -18,6 +18,7 @@
 #include "abi/guc_klvs_abi.h"
 #include "xe_assert.h"
 #include "xe_bo.h"
+#include "xe_cpu_bind.h"
 #include "xe_devcoredump.h"
 #include "xe_device.h"
 #include "xe_exec_queue.h"
@@ -37,7 +38,6 @@
 #include "xe_lrc.h"
 #include "xe_macros.h"
 #include "xe_map.h"
-#include "xe_migrate.h"
 #include "xe_mocs.h"
 #include "xe_module.h"
 #include "xe_pm.h"
@@ -1243,13 +1243,36 @@ static bool is_pt_job(struct xe_sched_job *job)
 	return job->is_pt_job;
 }
 
-static void run_pt_job(struct xe_sched_job *job)
+static void run_pt_job(struct xe_device *xe, struct xe_sched_job *job)
 {
-	xe_migrate_update_pgtables_cpu_execute(job->pt_update[0].vm,
-					       job->pt_update[0].tile,
-					       job->pt_update[0].ops,
-					       job->pt_update[0].pt_job_ops->ops,
-					       job->pt_update[0].pt_job_ops->current_op);
+	struct xe_tile *tile;
+	int id;
+
+	for_each_tile(tile, xe, id) {
+		struct xe_pt_job_ops *pt_job_ops =
+			job->pt_update[0].pt_job_ops[id];
+
+		if (!pt_job_ops || !pt_job_ops->current_op)
+			continue;
+
+		xe_cpu_bind_update_pgtables_execute(job->pt_update[0].vm, tile,
+						    job->pt_update[0].ops,
+						    pt_job_ops->ops,
+						    pt_job_ops->current_op);
+	}
+}
+
+static void put_pt_job(struct xe_device *xe, struct xe_sched_job *job)
+{
+	struct xe_tile *tile;
+	int id;
+
+	for_each_tile(tile, xe, id) {
+		struct xe_pt_job_ops *pt_job_ops =
+			job->pt_update[0].pt_job_ops[id];
+
+		xe_pt_job_ops_put(pt_job_ops);
+	}
 }
 
 static struct dma_fence *
@@ -1279,7 +1302,7 @@ guc_exec_queue_run_job(struct drm_sched_job *drm_job)
 
 		if (is_pt_job(job)) {
 			xe_gt_assert(guc_to_gt(guc), !exec_queue_registered(q));
-			run_pt_job(job);
+			run_pt_job(guc_to_xe(guc), job);
 		} else {
 			if (!exec_queue_registered(q))
 				register_exec_queue(q, GUC_CONTEXT_NORMAL);
@@ -1291,7 +1314,7 @@ guc_exec_queue_run_job(struct drm_sched_job *drm_job)
 	}
 
 	if (is_pt_job(job)) {
-		xe_pt_job_ops_put(job->pt_update[0].pt_job_ops);
+		put_pt_job(guc_to_xe(guc), job);
 		dma_fence_put(job->fence);	/* Drop ref from xe_sched_job_arm */
 		return NULL;
 	}
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 66e77de4e192..f7e1a81434b2 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -51,8 +51,6 @@
 struct xe_migrate {
 	/** @q: Default exec queue used for migration */
 	struct xe_exec_queue *q;
-	/** @bind_q: Default exec queue used for binds */
-	struct xe_exec_queue *bind_q;
 	/** @tile: Backpointer to the tile this struct xe_migrate belongs to. */
 	struct xe_tile *tile;
 	/** @job_mutex: Timeline mutex for @eng. */
@@ -110,7 +108,6 @@ static void xe_migrate_fini(void *arg)
 	mutex_destroy(&m->job_mutex);
 	xe_vm_close_and_put(m->q->vm);
 	xe_exec_queue_put(m->q);
-	xe_exec_queue_put(m->bind_q);
 }
 
 static inline u16 xe_migrate_pat_index(struct xe_device *xe,
@@ -483,15 +480,6 @@ int xe_migrate_init(struct xe_migrate *m)
 			goto err_out;
 		}
 
-		m->bind_q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe0,
-						 EXEC_QUEUE_FLAG_KERNEL |
-						 EXEC_QUEUE_FLAG_HIGH_PRIORITY |
-						 EXEC_QUEUE_FLAG_MIGRATE, 0);
-		if (IS_ERR(m->bind_q)) {
-			err = PTR_ERR(m->bind_q);
-			goto err_out;
-		}
-
 		/*
 		 * XXX: Currently only reserving 1 (likely slow) BCS instance on
 		 * PVC, may want to revisit if performance is needed.
@@ -502,15 +490,6 @@ int xe_migrate_init(struct xe_migrate *m)
 					    EXEC_QUEUE_FLAG_MIGRATE |
 					    EXEC_QUEUE_FLAG_LOW_LATENCY, 0);
 	} else {
-		m->bind_q = xe_exec_queue_create_class(xe, primary_gt, vm,
-						       XE_ENGINE_CLASS_COPY,
-						       EXEC_QUEUE_FLAG_KERNEL |
-						       EXEC_QUEUE_FLAG_MIGRATE, 0);
-		if (IS_ERR(m->bind_q)) {
-			err = PTR_ERR(m->bind_q);
-			goto err_out;
-		}
-
 		m->q = xe_exec_queue_create_class(xe, primary_gt, vm,
 						  XE_ENGINE_CLASS_COPY,
 						  EXEC_QUEUE_FLAG_KERNEL |
@@ -546,8 +525,6 @@ int xe_migrate_init(struct xe_migrate *m)
 	return err;
 
 err_out:
-	if (!IS_ERR_OR_NULL(m->bind_q))
-		xe_exec_queue_put(m->bind_q);
 	xe_vm_close_and_put(vm);
 	return err;
 
@@ -1507,17 +1484,6 @@ static u32 blt_mem_set_cmd_len(struct xe_device *xe)
 	return 7;
 }
 
-/**
- * xe_get_migrate_bind_queue() - Get the bind queue from migrate context.
- * @migrate: Migrate context.
- *
- * Return: Pointer to bind queue on success, error on failure
- */
-struct xe_exec_queue *xe_migrate_bind_queue(struct xe_migrate *migrate)
-{
-	return migrate->bind_q;
-}
-
 static void emit_clear_link_copy(struct xe_gt *gt, struct xe_bb *bb, u64 src_ofs,
 				 u32 size, u32 pitch)
 {
@@ -1782,168 +1748,6 @@ struct migrate_test_params {
 	container_of(_priv, struct migrate_test_params, base)
 #endif
 
-/**
- * xe_migrate_update_pgtables_cpu_execute() - Update a VM's PTEs via the CPU
- * @vm: The VM being updated
- * @tile: The tile being updated
- * @ops: The migrate PT update ops
- * @pt_ops: The VM PT update ops
- * @num_ops: The number of The VM PT update ops
- *
- * Execute the VM PT update ops array which results in a VM's PTEs being updated
- * via the CPU.
- */
-void
-xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
-				       const struct xe_migrate_pt_update_ops *ops,
-				       struct xe_vm_pgtable_update_op *pt_op,
-				       int num_ops)
-{
-	u32 j, i;
-
-	for (j = 0; j < num_ops; ++j, ++pt_op) {
-		for (i = 0; i < pt_op->num_entries; i++) {
-			const struct xe_vm_pgtable_update *update =
-				&pt_op->entries[i];
-
-			xe_tile_assert(tile, update);
-			xe_tile_assert(tile, update->pt_bo);
-			xe_tile_assert(tile, !iosys_map_is_null(&update->pt_bo->vmap));
-
-			if (pt_op->bind)
-				ops->populate(tile, &update->pt_bo->vmap,
-					      update->ofs, update->qwords,
-					      update);
-			else
-				ops->clear(vm, tile, &update->pt_bo->vmap,
-					   update->ofs, update->qwords,
-					   update);
-		}
-	}
-
-	trace_xe_vm_cpu_bind(vm);
-	xe_device_wmb(vm->xe);
-}
-
-static struct dma_fence *
-xe_migrate_update_pgtables_cpu(struct xe_migrate *m,
-			       struct xe_migrate_pt_update *pt_update)
-{
-	XE_TEST_DECLARE(struct migrate_test_params *test =
-			to_migrate_test_params
-			(xe_cur_kunit_priv(XE_TEST_LIVE_MIGRATE));)
-	const struct xe_migrate_pt_update_ops *ops = pt_update->ops;
-	struct xe_vm *vm = pt_update->vops->vm;
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&pt_update->vops->pt_update_ops[pt_update->tile_id];
-	int err;
-
-	if (XE_TEST_ONLY(test && test->force_gpu))
-		return ERR_PTR(-ETIME);
-
-	if (ops->pre_commit) {
-		pt_update->job = NULL;
-		err = ops->pre_commit(pt_update);
-		if (err)
-			return ERR_PTR(err);
-	}
-
-	xe_migrate_update_pgtables_cpu_execute(vm, m->tile, ops,
-					       pt_update_ops->pt_job_ops->ops,
-					       pt_update_ops->num_ops);
-
-	return dma_fence_get_stub();
-}
-
-static bool is_migrate_queue(struct xe_migrate *m, struct xe_exec_queue *q)
-{
-	return m->bind_q == q;
-}
-
-static struct dma_fence *
-__xe_migrate_update_pgtables(struct xe_migrate *m,
-			     struct xe_migrate_pt_update *pt_update,
-			     struct xe_vm_pgtable_update_ops *pt_update_ops)
-{
-	const struct xe_migrate_pt_update_ops *ops = pt_update->ops;
-	struct xe_tile *tile = m->tile;
-	struct xe_sched_job *job;
-	struct dma_fence *fence;
-	bool is_migrate = is_migrate_queue(m, pt_update_ops->q);
-	int err;
-
-	job = xe_sched_job_create(pt_update_ops->q, NULL);
-	if (IS_ERR(job)) {
-		err = PTR_ERR(job);
-		goto err_out;
-	}
-
-	xe_tile_assert(tile, job->is_pt_job);
-
-	if (ops->pre_commit) {
-		pt_update->job = job;
-		err = ops->pre_commit(pt_update);
-		if (err)
-			goto err_job;
-	}
-	if (is_migrate)
-		mutex_lock(&m->job_mutex);
-
-	job->pt_update[0].vm = pt_update->vops->vm;
-	job->pt_update[0].tile = tile;
-	job->pt_update[0].ops = ops;
-	job->pt_update[0].pt_job_ops =
-		xe_pt_job_ops_get(pt_update_ops->pt_job_ops);
-
-	xe_sched_job_arm(job);
-	fence = dma_fence_get(&job->drm.s_fence->finished);
-	xe_sched_job_push(job);
-
-	if (is_migrate)
-		mutex_unlock(&m->job_mutex);
-
-	return fence;
-
-err_job:
-	xe_sched_job_put(job);
-err_out:
-	return ERR_PTR(err);
-}
-
-/**
- * xe_migrate_update_pgtables() - Pipelined page-table update
- * @m: The migrate context.
- * @pt_update: PT update arguments
- *
- * Perform a pipelined page-table update. The update descriptors are typically
- * built under the same lock critical section as a call to this function. If
- * using the default engine for the updates, they will be performed in the
- * order they grab the job_mutex. If different engines are used, external
- * synchronization is needed for overlapping updates to maintain page-table
- * consistency. Note that the meaning of "overlapping" is that the updates
- * touch the same page-table, which might be a higher-level page-directory.
- * If no pipelining is needed, then updates may be performed by the cpu.
- *
- * Return: A dma_fence that, when signaled, indicates the update completion.
- */
-struct dma_fence *
-xe_migrate_update_pgtables(struct xe_migrate *m,
-			   struct xe_migrate_pt_update *pt_update)
-
-{
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&pt_update->vops->pt_update_ops[pt_update->tile_id];
-	struct dma_fence *fence;
-
-	fence =  xe_migrate_update_pgtables_cpu(m, pt_update);
-
-	/* -ETIME indicates a job is needed, anything else is legit error */
-	if (!IS_ERR(fence) || PTR_ERR(fence) != -ETIME)
-		return fence;
-
-	return __xe_migrate_update_pgtables(m, pt_update, pt_update_ops);
-}
-
 /**
  * xe_migrate_wait() - Complete all operations using the xe_migrate context
  * @m: Migrate context to wait for.
@@ -2445,56 +2249,6 @@ int xe_migrate_access_memory(struct xe_migrate *m, struct xe_bo *bo,
 	return IS_ERR(fence) ? PTR_ERR(fence) : 0;
 }
 
-/**
- * xe_migrate_job_lock() - Lock migrate job lock
- * @m: The migration context.
- * @q: Queue associated with the operation which requires a lock
- *
- * Lock the migrate job lock if the queue is a migration queue, otherwise
- * assert the VM's dma-resv is held (user queue's have own locking).
- */
-void xe_migrate_job_lock(struct xe_migrate *m, struct xe_exec_queue *q)
-{
-	bool is_migrate = is_migrate_queue(m, q);
-
-	if (is_migrate)
-		mutex_lock(&m->job_mutex);
-	else
-		xe_vm_assert_held(q->user_vm);	/* User queues VM's should be locked */
-}
-
-/**
- * xe_migrate_job_unlock() - Unlock migrate job lock
- * @m: The migration context.
- * @q: Queue associated with the operation which requires a lock
- *
- * Unlock the migrate job lock if the queue is a migration queue, otherwise
- * assert the VM's dma-resv is held (user queue's have own locking).
- */
-void xe_migrate_job_unlock(struct xe_migrate *m, struct xe_exec_queue *q)
-{
-	bool is_migrate = is_migrate_queue(m, q);
-
-	if (is_migrate)
-		mutex_unlock(&m->job_mutex);
-	else
-		xe_vm_assert_held(q->user_vm);	/* User queues VM's should be locked */
-}
-
-#if IS_ENABLED(CONFIG_PROVE_LOCKING)
-/**
- * xe_migrate_job_lock_assert() - Assert migrate job lock held of queue
- * @q: Migrate queue
- */
-void xe_migrate_job_lock_assert(struct xe_exec_queue *q)
-{
-	struct xe_migrate *m = gt_to_tile(q->gt)->migrate;
-
-	xe_gt_assert(q->gt, q == m->bind_q);
-	lockdep_assert_held(&m->job_mutex);
-}
-#endif
-
 #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
 #include "tests/xe_migrate.c"
 #endif
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index 48896fae1fbd..67e5ba1f8284 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -34,78 +34,6 @@ enum xe_migrate_copy_dir {
 	XE_MIGRATE_COPY_TO_SRAM,
 };
 
-/**
- * struct xe_migrate_pt_update_ops - Callbacks for the
- * xe_migrate_update_pgtables() function.
- */
-struct xe_migrate_pt_update_ops {
-	/**
-	 * @populate: Populate a command buffer or page-table with ptes.
-	 * @tile: The tile for the current operation.
-	 * @map: struct iosys_map into the memory to be populated.
-	 * @ofs: qword offset into @map, unused if @map is NULL.
-	 * @num_qwords: Number of qwords to write.
-	 * @update: Information about the PTEs to be inserted.
-	 *
-	 * This interface is intended to be used as a callback into the
-	 * page-table system to populate command buffers or shared
-	 * page-tables with PTEs.
-	 */
-	void (*populate)(struct xe_tile *tile, struct iosys_map *map,
-			 u32 ofs, u32 num_qwords,
-			 const struct xe_vm_pgtable_update *update);
-	/**
-	 * @clear: Clear a command buffer or page-table with ptes.
-	 * @vm: VM being updated
-	 * @tile: The tile for the current operation.
-	 * @map: struct iosys_map into the memory to be populated.
-	 * @ofs: qword offset into @map, unused if @map is NULL.
-	 * @num_qwords: Number of qwords to write.
-	 * @update: Information about the PTEs to be inserted.
-	 *
-	 * This interface is intended to be used as a callback into the
-	 * page-table system to populate command buffers or shared
-	 * page-tables with PTEs.
-	 */
-	void (*clear)(struct xe_vm *vm, struct xe_tile *tile,
-		      struct iosys_map *map, u32 ofs, u32 num_qwords,
-		      const struct xe_vm_pgtable_update *update);
-
-	/**
-	 * @pre_commit: Callback to be called just before arming the
-	 * sched_job.
-	 * @pt_update: Pointer to embeddable callback argument.
-	 *
-	 * Return: 0 on success, negative error code on error.
-	 */
-	int (*pre_commit)(struct xe_migrate_pt_update *pt_update);
-};
-
-/**
- * struct xe_migrate_pt_update - Argument to the
- * struct xe_migrate_pt_update_ops callbacks.
- *
- * Intended to be subclassed to support additional arguments if necessary.
- */
-struct xe_migrate_pt_update {
-	/** @ops: Pointer to the struct xe_migrate_pt_update_ops callbacks */
-	const struct xe_migrate_pt_update_ops *ops;
-	/** @vops: VMA operations */
-	struct xe_vma_ops *vops;
-	/** @job: The job if a GPU page-table update. NULL otherwise */
-	struct xe_sched_job *job;
-	/**
-	 * @ijob: The TLB invalidation job for primary GT. NULL otherwise
-	 */
-	struct xe_tlb_inval_job *ijob;
-	/**
-	 * @mjob: The TLB invalidation job for media GT. NULL otherwise
-	 */
-	struct xe_tlb_inval_job *mjob;
-	/** @tile_id: Tile ID of the update */
-	u8 tile_id;
-};
-
 struct xe_migrate *xe_migrate_alloc(struct xe_tile *tile);
 int xe_migrate_init(struct xe_migrate *m);
 
@@ -143,7 +71,6 @@ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo,
 
 struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate);
 struct xe_exec_queue *xe_migrate_exec_queue(struct xe_migrate *migrate);
-struct xe_exec_queue *xe_migrate_bind_queue(struct xe_migrate *migrate);
 struct dma_fence *xe_migrate_vram_copy_chunk(struct xe_bo *vram_bo, u64 vram_offset,
 					     struct xe_bo *sysmem_bo, u64 sysmem_offset,
 					     u64 size, enum xe_migrate_copy_dir dir);
@@ -163,29 +90,8 @@ struct dma_fence *xe_migrate_clear(struct xe_migrate *m,
 struct xe_vm *xe_migrate_get_vm(struct xe_migrate *m);
 
 
-void
-xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
-				       const struct xe_migrate_pt_update_ops *ops,
-				       struct xe_vm_pgtable_update_op *pt_op,
-				       int num_ops);
-
-struct dma_fence *
-xe_migrate_update_pgtables(struct xe_migrate *m,
-			   struct xe_migrate_pt_update *pt_update);
-
 void xe_migrate_wait(struct xe_migrate *m);
 
-#if IS_ENABLED(CONFIG_PROVE_LOCKING)
-void xe_migrate_job_lock_assert(struct xe_exec_queue *q);
-#else
-static inline void xe_migrate_job_lock_assert(struct xe_exec_queue *q)
-{
-}
-#endif
-
-void xe_migrate_job_lock(struct xe_migrate *m, struct xe_exec_queue *q);
-void xe_migrate_job_unlock(struct xe_migrate *m, struct xe_exec_queue *q);
-
 #if IS_ENABLED(CONFIG_DRM_XE_DEBUG_MEM)
 int xe_migrate_debug_ccs_overlap(struct xe_migrate *m,
 				 struct xe_bo *scratch_bo,
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 7c7cd7a0365b..821edeaecaed 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -7,12 +7,12 @@
 
 #include "regs/xe_gtt_defs.h"
 #include "xe_bo.h"
+#include "xe_cpu_bind.h"
 #include "xe_device.h"
 #include "xe_drm_client.h"
 #include "xe_exec_queue.h"
 #include "xe_gt.h"
 #include "xe_gt_stats.h"
-#include "xe_migrate.h"
 #include "xe_page_reclaim.h"
 #include "xe_pat.h"
 #include "xe_pt_types.h"
@@ -1402,11 +1402,9 @@ static int op_add_deps(struct xe_vm *vm, struct xe_vma_op *op,
 }
 
 static int xe_pt_vm_dependencies(struct xe_sched_job *job,
-				 struct xe_tlb_inval_job *ijob,
-				 struct xe_tlb_inval_job *mjob,
+				 struct xe_tlb_inval_job **ijobs,
 				 struct xe_vm *vm,
 				 struct xe_vma_ops *vops,
-				 struct xe_vm_pgtable_update_ops *pt_update_ops,
 				 struct xe_range_fence_tree *rftree)
 {
 	struct xe_range_fence *rtfence;
@@ -1419,20 +1417,22 @@ static int xe_pt_vm_dependencies(struct xe_sched_job *job,
 	if (!job && !no_in_syncs(vops->syncs, vops->num_syncs))
 		return -ETIME;
 
-	if (!job && !xe_exec_queue_is_idle(pt_update_ops->q))
+	if (!job && !xe_exec_queue_is_idle(vops->q))
 		return -ETIME;
 
-	if (pt_update_ops->wait_vm_bookkeep || pt_update_ops->wait_vm_kernel) {
-		err = job_test_add_deps(job, xe_vm_resv(vm),
-					pt_update_ops->wait_vm_bookkeep ?
-					DMA_RESV_USAGE_BOOKKEEP :
-					DMA_RESV_USAGE_KERNEL);
+	if (vops->flags & (XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP |
+			   XE_VMA_OPS_FLAG_WAIT_VM_KERNEL)) {
+		enum dma_resv_usage usage = DMA_RESV_USAGE_KERNEL;
+
+		if (vops->flags & XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP)
+			usage = DMA_RESV_USAGE_BOOKKEEP;
+
+		err = job_test_add_deps(job, xe_vm_resv(vm), usage);
 		if (err)
 			return err;
 	}
 
-	rtfence = xe_range_fence_tree_first(rftree, pt_update_ops->start,
-					    pt_update_ops->last);
+	rtfence = xe_range_fence_tree_first(rftree, vops->start, vops->last);
 	while (rtfence) {
 		fence = rtfence->fence;
 
@@ -1450,9 +1450,8 @@ static int xe_pt_vm_dependencies(struct xe_sched_job *job,
 				return err;
 		}
 
-		rtfence = xe_range_fence_tree_next(rtfence,
-						   pt_update_ops->start,
-						   pt_update_ops->last);
+		rtfence = xe_range_fence_tree_next(rtfence, vops->start,
+						   vops->last);
 	}
 
 	list_for_each_entry(op, &vops->list, link) {
@@ -1465,14 +1464,11 @@ static int xe_pt_vm_dependencies(struct xe_sched_job *job,
 		err = xe_sync_entry_add_deps(&vops->syncs[i], job);
 
 	if (job) {
-		if (ijob) {
-			err = xe_tlb_inval_job_alloc_dep(ijob);
-			if (err)
-				return err;
-		}
+		for (i = 0; i < XE_CPU_BIND_INVAL_JOB_COUNT; ++i) {
+			if (!ijobs[i])
+				continue;
 
-		if (mjob) {
-			err = xe_tlb_inval_job_alloc_dep(mjob);
+			err = xe_tlb_inval_job_alloc_dep(ijobs[i]);
 			if (err)
 				return err;
 		}
@@ -1481,17 +1477,14 @@ static int xe_pt_vm_dependencies(struct xe_sched_job *job,
 	return err;
 }
 
-static int xe_pt_pre_commit(struct xe_migrate_pt_update *pt_update)
+static int xe_pt_pre_commit(struct xe_cpu_bind_pt_update *pt_update)
 {
 	struct xe_vma_ops *vops = pt_update->vops;
 	struct xe_vm *vm = vops->vm;
-	struct xe_range_fence_tree *rftree = &vm->rftree[pt_update->tile_id];
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&vops->pt_update_ops[pt_update->tile_id];
+	struct xe_range_fence_tree *rftree = &vm->rftree;
 
-	return xe_pt_vm_dependencies(pt_update->job, pt_update->ijob,
-				     pt_update->mjob, vm, pt_update->vops,
-				     pt_update_ops, rftree);
+	return xe_pt_vm_dependencies(pt_update->job, pt_update->ijobs,
+				     vm, vops, rftree);
 }
 
 #if IS_ENABLED(CONFIG_DRM_GPUSVM)
@@ -1551,8 +1544,7 @@ static bool xe_pt_userptr_inject_eagain(struct xe_userptr_vma *uvma)
 
 #endif
 
-static int vma_check_userptr(struct xe_vm *vm, struct xe_vma *vma,
-			     struct xe_vm_pgtable_update_ops *pt_update)
+static int vma_check_userptr(struct xe_vm *vm, struct xe_vma *vma)
 {
 	struct xe_userptr_vma *uvma;
 	unsigned long notifier_seq;
@@ -1582,8 +1574,7 @@ static int vma_check_userptr(struct xe_vm *vm, struct xe_vma *vma,
 	return 0;
 }
 
-static int op_check_svm_userptr(struct xe_vm *vm, struct xe_vma_op *op,
-				struct xe_vm_pgtable_update_ops *pt_update)
+static int op_check_svm_userptr(struct xe_vm *vm, struct xe_vma_op *op)
 {
 	int err = 0;
 
@@ -1594,13 +1585,13 @@ static int op_check_svm_userptr(struct xe_vm *vm, struct xe_vma_op *op,
 		if (!op->map.immediate && xe_vm_in_fault_mode(vm))
 			break;
 
-		err = vma_check_userptr(vm, op->map.vma, pt_update);
+		err = vma_check_userptr(vm, op->map.vma);
 		break;
 	case DRM_GPUVA_OP_REMAP:
 		if (op->remap.prev && !op->remap.skip_prev)
-			err = vma_check_userptr(vm, op->remap.prev, pt_update);
+			err = vma_check_userptr(vm, op->remap.prev);
 		if (!err && op->remap.next && !op->remap.skip_next)
-			err = vma_check_userptr(vm, op->remap.next, pt_update);
+			err = vma_check_userptr(vm, op->remap.next);
 		break;
 	case DRM_GPUVA_OP_UNMAP:
 		break;
@@ -1620,7 +1611,7 @@ static int op_check_svm_userptr(struct xe_vm *vm, struct xe_vma_op *op,
 				}
 			}
 		} else {
-			err = vma_check_userptr(vm, gpuva_to_vma(op->base.prefetch.va), pt_update);
+			err = vma_check_userptr(vm, gpuva_to_vma(op->base.prefetch.va));
 		}
 		break;
 #if IS_ENABLED(CONFIG_DRM_XE_GPUSVM)
@@ -1646,12 +1637,10 @@ static int op_check_svm_userptr(struct xe_vm *vm, struct xe_vma_op *op,
 	return err;
 }
 
-static int xe_pt_svm_userptr_pre_commit(struct xe_migrate_pt_update *pt_update)
+static int xe_pt_svm_userptr_pre_commit(struct xe_cpu_bind_pt_update *pt_update)
 {
 	struct xe_vm *vm = pt_update->vops->vm;
 	struct xe_vma_ops *vops = pt_update->vops;
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&vops->pt_update_ops[pt_update->tile_id];
 	struct xe_vma_op *op;
 	int err;
 
@@ -1662,7 +1651,7 @@ static int xe_pt_svm_userptr_pre_commit(struct xe_migrate_pt_update *pt_update)
 	xe_pt_svm_userptr_notifier_lock(vm);
 
 	list_for_each_entry(op, &vops->list, link) {
-		err = op_check_svm_userptr(vm, op, pt_update_ops);
+		err = op_check_svm_userptr(vm, op);
 		if (err) {
 			xe_pt_svm_userptr_notifier_unlock(vm);
 			break;
@@ -2009,10 +1998,10 @@ static unsigned int xe_pt_stage_unbind(struct xe_tile *tile,
 }
 
 static void
-xe_migrate_clear_pgtable_callback(struct xe_vm *vm, struct xe_tile *tile,
-				  struct iosys_map *map, u32 qword_ofs,
-				  u32 num_qwords,
-				  const struct xe_vm_pgtable_update *update)
+xe_pt_clear_pgtable_callback(struct xe_vm *vm, struct xe_tile *tile,
+			     struct iosys_map *map, u32 qword_ofs,
+			     u32 num_qwords,
+			     const struct xe_vm_pgtable_update *update)
 {
 	u64 empty = __xe_pt_empty_pte(tile, vm, update->level);
 	int i;
@@ -2090,6 +2079,9 @@ to_pt_op(struct xe_vm_pgtable_update_ops *pt_update_ops, u32 op_idx)
 static u32
 get_current_op(struct xe_vm_pgtable_update_ops *pt_update_ops)
 {
+	if (!pt_update_ops->pt_job_ops)
+		return 0;
+
 	return pt_update_ops->pt_job_ops->current_op;
 }
 
@@ -2379,6 +2371,7 @@ static int unbind_range_prepare(struct xe_vm *vm,
 
 static int op_prepare(struct xe_vm *vm,
 		      struct xe_tile *tile,
+		      struct xe_vma_ops *vops,
 		      struct xe_vm_pgtable_update_ops *pt_update_ops,
 		      struct xe_vma_op *op)
 {
@@ -2395,7 +2388,7 @@ static int op_prepare(struct xe_vm *vm,
 
 		err = bind_op_prepare(vm, tile, pt_update_ops, op->map.vma,
 				      op->map.invalidate_on_bind);
-		pt_update_ops->wait_vm_kernel = true;
+		vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
 		break;
 	case DRM_GPUVA_OP_REMAP:
 	{
@@ -2409,12 +2402,12 @@ static int op_prepare(struct xe_vm *vm,
 		if (!err && op->remap.prev && !op->remap.skip_prev) {
 			err = bind_op_prepare(vm, tile, pt_update_ops,
 					      op->remap.prev, false);
-			pt_update_ops->wait_vm_bookkeep = true;
+			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
 		}
 		if (!err && op->remap.next && !op->remap.skip_next) {
 			err = bind_op_prepare(vm, tile, pt_update_ops,
 					      op->remap.next, false);
-			pt_update_ops->wait_vm_bookkeep = true;
+			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
 		}
 		break;
 	}
@@ -2450,7 +2443,7 @@ static int op_prepare(struct xe_vm *vm,
 			}
 		} else {
 			err = bind_op_prepare(vm, tile, pt_update_ops, vma, false);
-			pt_update_ops->wait_vm_kernel = true;
+			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
 		}
 		break;
 	}
@@ -2484,18 +2477,8 @@ xe_pt_update_ops_init(struct xe_vm_pgtable_update_ops *pt_update_ops)
 	xe_page_reclaim_list_init(&pt_update_ops->prl);
 }
 
-/**
- * xe_pt_update_ops_prepare() - Prepare PT update operations
- * @tile: Tile of PT update operations
- * @vops: VMA operationa
- *
- * Prepare PT update operations which includes updating internal PT state,
- * allocate memory for page tables, populate page table being pruned in, and
- * create PT update operations for leaf insertion / removal.
- *
- * Return: 0 on success, negative error code on error.
- */
-int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops)
+static int __xe_pt_update_ops_prepare(struct xe_tile *tile,
+				      struct xe_vma_ops *vops)
 {
 	struct xe_vm_pgtable_update_ops *pt_update_ops =
 		&vops->pt_update_ops[tile->id];
@@ -2514,7 +2497,7 @@ int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops)
 		return err;
 
 	list_for_each_entry(op, &vops->list, link) {
-		err = op_prepare(vops->vm, tile, pt_update_ops, op);
+		err = op_prepare(vops->vm, tile, vops, pt_update_ops, op);
 
 		if (err)
 			return err;
@@ -2523,6 +2506,16 @@ int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops)
 	xe_tile_assert(tile, get_current_op(pt_update_ops) <=
 		       pt_update_ops->num_ops);
 
+	/* Propagate individual tile state up to VMA operation */
+	if (pt_update_ops->start < vops->start)
+		vops->start = pt_update_ops->start;
+	if (pt_update_ops->last > vops->last)
+		vops->last = pt_update_ops->last;
+	if (pt_update_ops->needs_invalidation)
+		vops->flags |= XE_VMA_OPS_FLAG_NEEDS_INVALIDATION;
+	if (pt_update_ops->needs_svm_lock)
+		vops->flags |= XE_VMA_OPS_FLAG_NEEDS_SVM_LOCK;
+
 #ifdef TEST_VM_OPS_ERROR
 	if (vops->inject_error &&
 	    vops->vm->xe->vm_inject_error_position == FORCE_OP_ERROR_PREPARE)
@@ -2531,35 +2524,68 @@ int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops)
 
 	return 0;
 }
-ALLOW_ERROR_INJECTION(xe_pt_update_ops_prepare, ERRNO);
 
-static void bind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
-			   struct xe_vm_pgtable_update_ops *pt_update_ops,
-			   struct xe_vma *vma, struct dma_fence *fence,
-			   struct dma_fence *fence2, bool invalidate_on_bind)
+/**
+ * xe_pt_update_ops_prepare() - Prepare PT update operations
+ * @xe: xe device.
+ * @vops: VMA operationa
+ *
+ * Prepare PT update operations which includes updating internal PT state,
+ * allocate memory for page tables, populate page table being pruned in, and
+ * create PT update operations for leaf insertion / removal.
+ *
+ * Return: 0 on success, negative error code on error.
+ */
+int xe_pt_update_ops_prepare(struct xe_device *xe, struct xe_vma_ops *vops)
 {
-	xe_tile_assert(tile, !xe_vma_is_cpu_addr_mirror(vma));
+	struct xe_tile *tile;
+	int id, err;
+
+	for_each_tile(tile, xe, id) {
+		if (!vops->pt_update_ops[id].num_ops)
+			continue;
 
-	if (!xe_vma_has_no_bo(vma) && !xe_vma_bo(vma)->vm) {
-		dma_resv_add_fence(xe_vma_bo(vma)->ttm.base.resv, fence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
-		if (fence2)
-			dma_resv_add_fence(xe_vma_bo(vma)->ttm.base.resv, fence2,
-					   pt_update_ops->wait_vm_bookkeep ?
-					   DMA_RESV_USAGE_KERNEL :
-					   DMA_RESV_USAGE_BOOKKEEP);
+		err = __xe_pt_update_ops_prepare(tile, vops);
+		if (err)
+			return err;
 	}
+
+	return 0;
+}
+ALLOW_ERROR_INJECTION(xe_pt_update_ops_prepare, ERRNO);
+
+static void vma_add_fences(struct xe_vma *vma, struct dma_fence **fences,
+			   int fence_count, enum dma_resv_usage usage)
+{
+	int i;
+
+	if (xe_vma_has_no_bo(vma) || xe_vma_bo(vma)->vm)
+		return;
+
+	for (i = 0; i < fence_count; ++i)
+		if (fences[i])
+			dma_resv_add_fence(xe_vma_bo(vma)->ttm.base.resv,
+					   fences[i], usage);
+}
+
+static void bind_op_commit(struct xe_vm *vm, struct xe_vma *vma,
+			   struct dma_fence **fences, int fence_count,
+			   enum dma_resv_usage usage, u8 tile_mask,
+			   bool invalidate_on_bind)
+{
+	xe_assert(vm->xe, !xe_vma_is_cpu_addr_mirror(vma));
+
+	vma_add_fences(vma, fences, fence_count, usage);
+
 	/* All WRITE_ONCE pair with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
-	WRITE_ONCE(vma->tile_present, vma->tile_present | BIT(tile->id));
+	WRITE_ONCE(vma->tile_present, vma->tile_present | tile_mask);
 	if (invalidate_on_bind)
 		WRITE_ONCE(vma->tile_invalidated,
-			   vma->tile_invalidated | BIT(tile->id));
+			   vma->tile_invalidated | tile_mask);
 	else
 		WRITE_ONCE(vma->tile_invalidated,
-			   vma->tile_invalidated & ~BIT(tile->id));
-	vma->tile_staged &= ~BIT(tile->id);
+			   vma->tile_invalidated & ~tile_mask);
+	vma->tile_staged &= ~tile_mask;
 	if (xe_vma_is_userptr(vma)) {
 		xe_svm_assert_held_read_or_inject_write(vm);
 		to_userptr_vma(vma)->userptr.initial_bind = true;
@@ -2569,31 +2595,21 @@ static void bind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
 	 * Kick rebind worker if this bind triggers preempt fences and not in
 	 * the rebind worker
 	 */
-	if (pt_update_ops->wait_vm_bookkeep &&
+	if (usage == DMA_RESV_USAGE_KERNEL &&
 	    xe_vm_in_preempt_fence_mode(vm) &&
 	    !current->mm)
 		xe_vm_queue_rebind_worker(vm);
 }
 
-static void unbind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
-			     struct xe_vm_pgtable_update_ops *pt_update_ops,
-			     struct xe_vma *vma, struct dma_fence *fence,
-			     struct dma_fence *fence2)
+static void unbind_op_commit(struct xe_vm *vm, struct xe_vma *vma,
+			     struct dma_fence **fences, int fence_count,
+			     enum dma_resv_usage usage, u8 tile_mask)
 {
-	xe_tile_assert(tile, !xe_vma_is_cpu_addr_mirror(vma));
+	xe_assert(vm->xe, !xe_vma_is_cpu_addr_mirror(vma));
 
-	if (!xe_vma_has_no_bo(vma) && !xe_vma_bo(vma)->vm) {
-		dma_resv_add_fence(xe_vma_bo(vma)->ttm.base.resv, fence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
-		if (fence2)
-			dma_resv_add_fence(xe_vma_bo(vma)->ttm.base.resv, fence2,
-					   pt_update_ops->wait_vm_bookkeep ?
-					   DMA_RESV_USAGE_KERNEL :
-					   DMA_RESV_USAGE_BOOKKEEP);
-	}
-	vma->tile_present &= ~BIT(tile->id);
+	vma_add_fences(vma, fences, fence_count, usage);
+
+	vma->tile_present &= ~tile_mask;
 	if (!vma->tile_present) {
 		list_del_init(&vma->combined_links.rebind);
 		if (xe_vma_is_userptr(vma)) {
@@ -2608,21 +2624,19 @@ static void unbind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
 
 static void range_present_and_invalidated_tile(struct xe_vm *vm,
 					       struct xe_svm_range *range,
-					       u8 tile_id)
+					       u8 tile_mask)
 {
 	/* All WRITE_ONCE pair with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
 
 	lockdep_assert_held(&vm->svm.gpusvm.notifier_lock);
 
-	WRITE_ONCE(range->tile_present, range->tile_present | BIT(tile_id));
-	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~BIT(tile_id));
+	WRITE_ONCE(range->tile_present, range->tile_present | tile_mask);
+	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~tile_mask);
 }
 
-static void op_commit(struct xe_vm *vm,
-		      struct xe_tile *tile,
-		      struct xe_vm_pgtable_update_ops *pt_update_ops,
-		      struct xe_vma_op *op, struct dma_fence *fence,
-		      struct dma_fence *fence2)
+static void op_commit(struct xe_vm *vm, struct xe_vma_op *op,
+		      struct dma_fence **fences, int fence_count,
+		      enum dma_resv_usage usage, u8 tile_mask)
 {
 	xe_vm_assert_held(vm);
 
@@ -2632,8 +2646,8 @@ static void op_commit(struct xe_vm *vm,
 		    (op->map.vma_flags & XE_VMA_SYSTEM_ALLOCATOR))
 			break;
 
-		bind_op_commit(vm, tile, pt_update_ops, op->map.vma, fence,
-			       fence2, op->map.invalidate_on_bind);
+		bind_op_commit(vm, op->map.vma, fences, fence_count, usage,
+			       tile_mask, op->map.invalidate_on_bind);
 		break;
 	case DRM_GPUVA_OP_REMAP:
 	{
@@ -2642,14 +2656,15 @@ static void op_commit(struct xe_vm *vm,
 		if (xe_vma_is_cpu_addr_mirror(old))
 			break;
 
-		unbind_op_commit(vm, tile, pt_update_ops, old, fence, fence2);
+		unbind_op_commit(vm, old, fences, fence_count, usage,
+				 tile_mask);
 
 		if (op->remap.prev && !op->remap.skip_prev)
-			bind_op_commit(vm, tile, pt_update_ops, op->remap.prev,
-				       fence, fence2, false);
+			bind_op_commit(vm, op->remap.prev, fences, fence_count,
+				       usage, tile_mask, false);
 		if (op->remap.next && !op->remap.skip_next)
-			bind_op_commit(vm, tile, pt_update_ops, op->remap.next,
-				       fence, fence2, false);
+			bind_op_commit(vm, op->remap.next, fences, fence_count,
+				       usage, tile_mask, false);
 		break;
 	}
 	case DRM_GPUVA_OP_UNMAP:
@@ -2657,8 +2672,8 @@ static void op_commit(struct xe_vm *vm,
 		struct xe_vma *vma = gpuva_to_vma(op->base.unmap.va);
 
 		if (!xe_vma_is_cpu_addr_mirror(vma))
-			unbind_op_commit(vm, tile, pt_update_ops, vma, fence,
-					 fence2);
+			unbind_op_commit(vm, vma, fences, fence_count,
+					 tile_mask, usage);
 		break;
 	}
 	case DRM_GPUVA_OP_PREFETCH:
@@ -2670,10 +2685,11 @@ static void op_commit(struct xe_vm *vm,
 			unsigned long i;
 
 			xa_for_each(&op->prefetch_range.range, i, range)
-				range_present_and_invalidated_tile(vm, range, tile->id);
+				range_present_and_invalidated_tile(vm, range,
+								   tile_mask);
 		} else {
-			bind_op_commit(vm, tile, pt_update_ops, vma, fence,
-				       fence2, false);
+			bind_op_commit(vm, vma, fences, fence_count, usage,
+				       tile_mask, false);
 		}
 		break;
 	}
@@ -2681,11 +2697,12 @@ static void op_commit(struct xe_vm *vm,
 	{
 		/* WRITE_ONCE pairs with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
 		if (op->subop == XE_VMA_SUBOP_MAP_RANGE)
-			range_present_and_invalidated_tile(vm, op->map_range.range, tile->id);
+			range_present_and_invalidated_tile(vm, op->map_range.range,
+							   tile_mask);
 		else if (op->subop == XE_VMA_SUBOP_UNMAP_RANGE)
 			WRITE_ONCE(op->unmap_range.range->tile_present,
 				   op->unmap_range.range->tile_present &
-				   ~BIT(tile->id));
+				   ~tile_mask);
 
 		break;
 	}
@@ -2694,40 +2711,25 @@ static void op_commit(struct xe_vm *vm,
 	}
 }
 
-static const struct xe_migrate_pt_update_ops migrate_ops = {
+static const struct xe_cpu_bind_pt_update_ops cpu_bind_ops = {
 	.populate = xe_vm_populate_pgtable,
-	.clear = xe_migrate_clear_pgtable_callback,
+	.clear = xe_pt_clear_pgtable_callback,
 	.pre_commit = xe_pt_pre_commit,
 };
 
 #if IS_ENABLED(CONFIG_DRM_GPUSVM)
-static const struct xe_migrate_pt_update_ops svm_userptr_migrate_ops = {
+static const struct xe_cpu_bind_pt_update_ops svm_userptr_cpu_bind_ops = {
 	.populate = xe_vm_populate_pgtable,
-	.clear = xe_migrate_clear_pgtable_callback,
+	.clear = xe_pt_clear_pgtable_callback,
 	.pre_commit = xe_pt_svm_userptr_pre_commit,
 };
 #else
-static const struct xe_migrate_pt_update_ops svm_userptr_migrate_ops;
+static const struct xe_cpu_bind_pt_update_ops svm_userptr_cpu_bind_ops;
 #endif
 
-static struct xe_dep_scheduler *to_dep_scheduler(struct xe_exec_queue *q,
-						 struct xe_tile *tile,
-						 struct xe_gt *gt,
-						 unsigned int *type)
-{
-	int tile_ofs = tile->id * (XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT + 1);
-
-	if (xe_gt_is_media_type(gt))
-		*type = tile_ofs + XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT;
-	else
-		*type = tile_ofs + XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT;
-
-	return q->tlb_inval[*type].dep_scheduler;
-}
-
 /**
  * xe_pt_update_ops_run() - Run PT update operations
- * @tile: Tile of PT update operations
+ * @xe: xe device.
  * @vops: VMA operationa
  *
  * Run PT update operations which includes committing internal PT state changes,
@@ -2737,82 +2739,83 @@ static struct xe_dep_scheduler *to_dep_scheduler(struct xe_exec_queue *q,
  * Return: fence on success, negative ERR_PTR on error.
  */
 struct dma_fence *
-xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
+xe_pt_update_ops_run(struct xe_device *xe, struct xe_vma_ops *vops)
 {
 	struct xe_vm *vm = vops->vm;
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&vops->pt_update_ops[tile->id];
-	struct xe_exec_queue *q = pt_update_ops->q;
-	struct dma_fence *fence, *ifence = NULL, *mfence = NULL;
-	struct xe_tlb_inval_job *ijob = NULL, *mjob = NULL;
+	struct xe_exec_queue *q = vops->q;
+	struct dma_fence *fence;
+	struct dma_fence *ifences[XE_CPU_BIND_INVAL_JOB_COUNT] = {};
 	struct xe_range_fence *rfence;
+	enum dma_resv_usage usage = DMA_RESV_USAGE_BOOKKEEP;
 	struct xe_vma_op *op;
-	unsigned int type;
-	int err = 0, i;
-	struct xe_migrate_pt_update update = {
-		.ops = pt_update_ops->needs_svm_lock ?
-			&svm_userptr_migrate_ops :
-			&migrate_ops,
+	struct xe_tile *tile;
+	int err = 0, total_ops = 0, i, j;
+	u8 tile_mask = 0;
+	bool needs_invalidation = vops->flags &
+		XE_VMA_OPS_FLAG_NEEDS_INVALIDATION;
+	bool needs_svm_lock = vops->flags &
+		XE_VMA_OPS_FLAG_NEEDS_SVM_LOCK;
+	struct xe_cpu_bind_pt_update update = {
+		.ops = needs_svm_lock ? &svm_userptr_cpu_bind_ops :
+			&cpu_bind_ops,
 		.vops = vops,
-		.tile_id = tile->id,
 	};
 
 	lockdep_assert_held(&vm->lock);
 	xe_vm_assert_held(vm);
 
-	if (!get_current_op(pt_update_ops)) {
-		xe_tile_assert(tile, xe_vm_in_fault_mode(vm));
+	for_each_tile(tile, xe, j) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&vops->pt_update_ops[j];
 
+		total_ops += get_current_op(pt_update_ops);
+	}
+	if (!total_ops) {
+		xe_assert(xe, xe_vm_in_fault_mode(vm));
 		return dma_fence_get_stub();
 	}
 
 #ifdef TEST_VM_OPS_ERROR
 	if (vops->inject_error &&
-	    vm->xe->vm_inject_error_position == FORCE_OP_ERROR_RUN)
+	    xe->vm_inject_error_position == FORCE_OP_ERROR_RUN)
 		return ERR_PTR(-ENOSPC);
 #endif
 
-	if (pt_update_ops->needs_invalidation) {
-		struct xe_dep_scheduler *dep_scheduler =
-			to_dep_scheduler(q, tile, tile->primary_gt, &type);
-
-		ijob = xe_tlb_inval_job_create(q, &tile->primary_gt->tlb_inval,
-					       dep_scheduler, vm,
-					       pt_update_ops->start,
-					       pt_update_ops->last,
-					       type);
-		if (IS_ERR(ijob)) {
-			err = PTR_ERR(ijob);
-			goto kill_vm_tile1;
-		}
-		update.ijob = ijob;
-		/*
-		 * Only add page reclaim for the primary GT. Media GT does not have
-		 * any PPC to flush, so enabling the PPC flush bit for media is
-		 * effectively a NOP and provides no performance benefit nor
-		 * interfere with primary GT.
-		 */
-		if (xe_page_reclaim_list_valid(&pt_update_ops->prl)) {
-			xe_tlb_inval_job_add_page_reclaim(ijob, &pt_update_ops->prl);
-			/* Release ref from alloc, job will now handle it */
-			xe_page_reclaim_list_invalidate(&pt_update_ops->prl);
-		}
-
-		if (tile->media_gt) {
-			dep_scheduler = to_dep_scheduler(q, tile,
-							 tile->media_gt, &type);
-
-			mjob = xe_tlb_inval_job_create(q,
-						       &tile->media_gt->tlb_inval,
-						       dep_scheduler, vm,
-						       pt_update_ops->start,
-						       pt_update_ops->last,
-						       type);
-			if (IS_ERR(mjob)) {
-				err = PTR_ERR(mjob);
+	if (needs_invalidation) {
+		for_each_tlb_inval(q, i) {
+			struct xe_dep_scheduler *dep_scheduler =
+				q->tlb_inval[i].dep_scheduler;
+			struct xe_tile *tile =
+				&xe->tiles[i / XE_MAX_GT_PER_TILE];
+			struct xe_vm_pgtable_update_ops *pt_update_ops =
+				&vops->pt_update_ops[tile->id];
+			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
+			struct xe_tlb_inval_job *ijob;
+			struct xe_gt *gt = i % XE_MAX_GT_PER_TILE ?
+				tile->media_gt : tile->primary_gt;
+
+			ijob = xe_tlb_inval_job_create(q, &gt->tlb_inval,
+						       dep_scheduler,
+						       vm, pt_update_ops->start,
+						       pt_update_ops->last, i);
+			if (IS_ERR(ijob)) {
+				err = PTR_ERR(ijob);
 				goto free_ijob;
 			}
-			update.mjob = mjob;
+
+			update.ijobs[i] = ijob;
+
+			/*
+			 * Only add page reclaim for the primary GT. Media GT
+			 * does not have any PPC to flush, so enabling the PPC
+			 * flush bit for media is effectively a NOP and provides
+			 * no performance benefit nor interfere with primary GT.
+			 */
+			if (xe_page_reclaim_list_valid(prl)) {
+				xe_tlb_inval_job_add_page_reclaim(ijob, prl);
+				/* Release ref from alloc, job will now handle it */
+				xe_page_reclaim_list_invalidate(prl);
+			}
 		}
 	}
 
@@ -2822,67 +2825,61 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 		goto free_ijob;
 	}
 
-	fence = xe_migrate_update_pgtables(tile->migrate, &update);
+	fence = xe_cpu_bind_update_pgtables(xe->cpu_bind, &update);
 	if (IS_ERR(fence)) {
 		err = PTR_ERR(fence);
 		goto free_rfence;
 	}
 
 	/* Point of no return - VM killed if failure after this */
-	for (i = 0; i < get_current_op(pt_update_ops); ++i) {
-		struct xe_vm_pgtable_update_op *pt_op =
-			to_pt_op(pt_update_ops, i);
-
-		xe_pt_commit(pt_op->vma, pt_op->entries,
-			     pt_op->num_entries,
-			     &pt_update_ops->pt_job_ops->deferred);
-		pt_op->vma = NULL;	/* skip in xe_pt_update_ops_abort */
+	for_each_tile(tile, xe, j) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&vops->pt_update_ops[j];
+
+		for (i = 0; i < get_current_op(pt_update_ops); ++i) {
+			struct xe_vm_pgtable_update_op *pt_op =
+				to_pt_op(pt_update_ops, i);
+
+			xe_pt_commit(pt_op->vma, pt_op->entries,
+				     pt_op->num_entries,
+				     &pt_update_ops->pt_job_ops->deferred);
+			pt_op->vma = NULL;	/* skip in xe_pt_update_ops_abort */
+			tile_mask |= BIT(tile->id);
+		}
 	}
 
-	if (xe_range_fence_insert(&vm->rftree[tile->id], rfence,
+	if (xe_range_fence_insert(&vm->rftree, rfence,
 				  &xe_range_fence_kfree_ops,
-				  pt_update_ops->start,
-				  pt_update_ops->last, fence))
+				  vops->start, vops->last, fence))
 		dma_fence_wait(fence, false);
 
-	if (ijob)
-		ifence = xe_tlb_inval_job_push(ijob, tile->migrate, fence);
-	if (mjob)
-		mfence = xe_tlb_inval_job_push(mjob, tile->migrate, fence);
+	if (vops->flags & XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP)
+		usage = DMA_RESV_USAGE_KERNEL;
 
-	if (!mjob && !ijob) {
-		dma_resv_add_fence(xe_vm_resv(vm), fence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
-
-		list_for_each_entry(op, &vops->list, link)
-			op_commit(vops->vm, tile, pt_update_ops, op, fence, NULL);
-	} else if (ijob && !mjob) {
-		dma_resv_add_fence(xe_vm_resv(vm), ifence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
+	if (!needs_invalidation) {
+		dma_resv_add_fence(xe_vm_resv(vm), fence, usage);
 
 		list_for_each_entry(op, &vops->list, link)
-			op_commit(vops->vm, tile, pt_update_ops, op, ifence, NULL);
+			op_commit(vops->vm, op, &fence, 1, usage, tile_mask);
 	} else {
-		dma_resv_add_fence(xe_vm_resv(vm), ifence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
+		for (i = 0; i < XE_CPU_BIND_INVAL_JOB_COUNT; ++i) {
+			if (!update.ijobs[i])
+				continue;
+
+			ifences[i] = xe_tlb_inval_job_push(update.ijobs[i],
+							   fence);
+			xe_assert(xe, !IS_ERR_OR_NULL(ifences[i]));
 
-		dma_resv_add_fence(xe_vm_resv(vm), mfence,
-				   pt_update_ops->wait_vm_bookkeep ?
-				   DMA_RESV_USAGE_KERNEL :
-				   DMA_RESV_USAGE_BOOKKEEP);
+			dma_resv_add_fence(xe_vm_resv(vm), ifences[i], usage);
+		}
 
 		list_for_each_entry(op, &vops->list, link)
-			op_commit(vops->vm, tile, pt_update_ops, op, ifence,
-				  mfence);
+			op_commit(vops->vm, op, ifences,
+				  XE_CPU_BIND_INVAL_JOB_COUNT, usage,
+				  tile_mask);
 	}
 
-	if (pt_update_ops->needs_svm_lock)
+	if (needs_svm_lock)
 		xe_pt_svm_userptr_notifier_unlock(vm);
 
 	/*
@@ -2892,21 +2889,18 @@ xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
 	if (!(q->flags & EXEC_QUEUE_FLAG_MIGRATE))
 		xe_exec_queue_last_fence_set(q, vm, fence);
 
-	xe_tlb_inval_job_put(mjob);
-	xe_tlb_inval_job_put(ijob);
-	dma_fence_put(ifence);
-	dma_fence_put(mfence);
+	for (i = 0; i < XE_CPU_BIND_INVAL_JOB_COUNT; ++i) {
+		xe_tlb_inval_job_put(update.ijobs[i]);
+		dma_fence_put(ifences[i]);
+	}
 
 	return fence;
 
 free_rfence:
 	kfree(rfence);
 free_ijob:
-	xe_tlb_inval_job_put(mjob);
-	xe_tlb_inval_job_put(ijob);
-kill_vm_tile1:
-	if (err != -EAGAIN && err != -ENODATA && tile->id)
-		xe_vm_kill(vops->vm, false);
+	for (i = 0; i < XE_CPU_BIND_INVAL_JOB_COUNT; ++i)
+		xe_tlb_inval_job_put(update.ijobs[i]);
 
 	return ERR_PTR(err);
 }
@@ -2914,52 +2908,65 @@ ALLOW_ERROR_INJECTION(xe_pt_update_ops_run, ERRNO);
 
 /**
  * xe_pt_update_ops_fini() - Finish PT update operations
- * @tile: Tile of PT update operations
+ * @xe: xe device.
  * @vops: VMA operations
  *
  * Finish PT update operations by committing to destroy page table memory
  */
-void xe_pt_update_ops_fini(struct xe_tile *tile, struct xe_vma_ops *vops)
+void xe_pt_update_ops_fini(struct xe_device *xe, struct xe_vma_ops *vops)
 {
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&vops->pt_update_ops[tile->id];
+	struct xe_tile *tile;
+	int id;
+
+	for_each_tile(tile, xe, id) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&vops->pt_update_ops[id];
 
-	xe_page_reclaim_entries_put(pt_update_ops->prl.entries);
+		if (!pt_update_ops->num_ops)
+			continue;
+
+		xe_page_reclaim_entries_put(pt_update_ops->prl.entries);
+	}
 }
 
 /**
  * xe_pt_update_ops_abort() - Abort PT update operations
- * @tile: Tile of PT update operations
+ * @xe: xe device.
  * @vops: VMA operationa
  *
  *  Abort PT update operations by unwinding internal PT state
  */
-void xe_pt_update_ops_abort(struct xe_tile *tile, struct xe_vma_ops *vops)
+void xe_pt_update_ops_abort(struct xe_device *xe, struct xe_vma_ops *vops)
 {
-	struct xe_vm_pgtable_update_ops *pt_update_ops =
-		&vops->pt_update_ops[tile->id];
-	int i;
+	struct xe_tile *tile;
+	int id;
 
 	lockdep_assert_held(&vops->vm->lock);
 	xe_vm_assert_held(vops->vm);
 
-	for (i = pt_update_ops->num_ops - 1; i >= 0; --i) {
-		struct xe_vm_pgtable_update_op *pt_op =
-			to_pt_op(pt_update_ops, i);
+	for_each_tile(tile, xe, id) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&vops->pt_update_ops[id];
+		int i;
 
-		if (!pt_op->vma || i >= get_current_op(pt_update_ops))
-			continue;
+		for (i = pt_update_ops->num_ops - 1; i >= 0; --i) {
+			struct xe_vm_pgtable_update_op *pt_op =
+				to_pt_op(pt_update_ops, i);
 
-		if (pt_op->bind)
-			xe_pt_abort_bind(pt_op->vma, pt_op->entries,
-					 pt_op->num_entries,
-					 pt_op->rebind);
-		else
-			xe_pt_abort_unbind(pt_op->vma, pt_op->entries,
-					   pt_op->num_entries);
+			if (!pt_op->vma || i >= get_current_op(pt_update_ops))
+				continue;
+
+			if (pt_op->bind)
+				xe_pt_abort_bind(pt_op->vma, pt_op->entries,
+						 pt_op->num_entries,
+						 pt_op->rebind);
+			else
+				xe_pt_abort_unbind(pt_op->vma, pt_op->entries,
+						   pt_op->num_entries);
+		}
 	}
 
-	xe_pt_update_ops_fini(tile, vops);
+	xe_pt_update_ops_fini(xe, vops);
 }
 
 /**
diff --git a/drivers/gpu/drm/xe/xe_pt.h b/drivers/gpu/drm/xe/xe_pt.h
index 5faddb8e700c..cd78141fb81c 100644
--- a/drivers/gpu/drm/xe/xe_pt.h
+++ b/drivers/gpu/drm/xe/xe_pt.h
@@ -39,11 +39,11 @@ void xe_pt_destroy(struct xe_pt *pt, u32 flags, struct llist_head *deferred);
 
 void xe_pt_clear(struct xe_device *xe, struct xe_pt *pt);
 
-int xe_pt_update_ops_prepare(struct xe_tile *tile, struct xe_vma_ops *vops);
-struct dma_fence *xe_pt_update_ops_run(struct xe_tile *tile,
+int xe_pt_update_ops_prepare(struct xe_device *xe, struct xe_vma_ops *vops);
+struct dma_fence *xe_pt_update_ops_run(struct xe_device *xe,
 				       struct xe_vma_ops *vops);
-void xe_pt_update_ops_fini(struct xe_tile *tile, struct xe_vma_ops *vops);
-void xe_pt_update_ops_abort(struct xe_tile *tile, struct xe_vma_ops *vops);
+void xe_pt_update_ops_fini(struct xe_device *xe, struct xe_vma_ops *vops);
+void xe_pt_update_ops_abort(struct xe_device *xe, struct xe_vma_ops *vops);
 
 bool xe_pt_zap_ptes(struct xe_tile *tile, struct xe_vma *vma);
 bool xe_pt_zap_ptes_range(struct xe_tile *tile, struct xe_vm *vm,
diff --git a/drivers/gpu/drm/xe/xe_pt_types.h b/drivers/gpu/drm/xe/xe_pt_types.h
index ccab6613385f..0d4bac22ee6c 100644
--- a/drivers/gpu/drm/xe/xe_pt_types.h
+++ b/drivers/gpu/drm/xe/xe_pt_types.h
@@ -120,8 +120,6 @@ struct xe_pt_job_ops {
 struct xe_vm_pgtable_update_ops {
 	/** @pt_job_ops: PT update operations dynamic allocation*/
 	struct xe_pt_job_ops *pt_job_ops;
-	/** @q: exec queue for PT operations */
-	struct xe_exec_queue *q;
 	/** @prl: embedded page reclaim list */
 	struct xe_page_reclaim_list prl;
 	/** @start: start address of ops */
@@ -134,18 +132,6 @@ struct xe_vm_pgtable_update_ops {
 	bool needs_svm_lock;
 	/** @needs_invalidation: Needs invalidation */
 	bool needs_invalidation;
-	/**
-	 * @wait_vm_bookkeep: PT operations need to wait until VM is idle
-	 * (bookkeep dma-resv slots are idle) and stage all future VM activity
-	 * behind these operations (install PT operations into VM kernel
-	 * dma-resv slot).
-	 */
-	bool wait_vm_bookkeep;
-	/**
-	 * @wait_vm_kernel: PT operations need to wait until VM kernel dma-resv
-	 * slots are idle.
-	 */
-	bool wait_vm_kernel;
 };
 
 #endif
diff --git a/drivers/gpu/drm/xe/xe_sched_job.c b/drivers/gpu/drm/xe/xe_sched_job.c
index cfd3c50bb682..1ebef9af4bd2 100644
--- a/drivers/gpu/drm/xe/xe_sched_job.c
+++ b/drivers/gpu/drm/xe/xe_sched_job.c
@@ -73,8 +73,9 @@ static void job_free(struct xe_sched_job *job)
 	struct xe_exec_queue *q = job->q;
 	bool is_migration = xe_sched_job_is_migration(q);
 
-	kmem_cache_free(xe_exec_queue_is_parallel(job->q) || is_migration ?
-			xe_sched_job_parallel_slab : xe_sched_job_slab, job);
+	kmem_cache_free(job->is_pt_job || xe_exec_queue_is_parallel(job->q) ||
+			is_migration ? xe_sched_job_parallel_slab :
+			xe_sched_job_slab, job);
 }
 
 static struct xe_device *job_to_xe(struct xe_sched_job *job)
@@ -127,10 +128,12 @@ struct xe_sched_job *xe_sched_job_create(struct xe_exec_queue *q,
 	xe_assert(xe, batch_addr ||
 		  q->flags & (EXEC_QUEUE_FLAG_VM | EXEC_QUEUE_FLAG_MIGRATE));
 
-	job = job_alloc(xe_exec_queue_is_parallel(q) || is_migration);
+	job = job_alloc(!batch_addr || xe_exec_queue_is_parallel(q) ||
+			is_migration);
 	if (!job)
 		return ERR_PTR(-ENOMEM);
 
+	job->is_pt_job = !batch_addr;
 	job->q = q;
 	job->sample_timestamp = U64_MAX;
 	kref_init(&job->refcount);
@@ -143,7 +146,6 @@ struct xe_sched_job *xe_sched_job_create(struct xe_exec_queue *q,
 
 	if (!batch_addr) {
 		job->fence = dma_fence_get_stub();
-		job->is_pt_job = true;
 	} else {
 		for (i = 0; i < q->width; ++i) {
 			struct dma_fence *fence = xe_lrc_alloc_seqno_fence();
diff --git a/drivers/gpu/drm/xe/xe_sched_job_types.h b/drivers/gpu/drm/xe/xe_sched_job_types.h
index 5e1824c36c74..9f527ac6df3e 100644
--- a/drivers/gpu/drm/xe/xe_sched_job_types.h
+++ b/drivers/gpu/drm/xe/xe_sched_job_types.h
@@ -14,7 +14,7 @@ struct dma_fence;
 struct dma_fence_chain;
 
 struct xe_exec_queue;
-struct xe_migrate_pt_update_ops;
+struct xe_cpu_bind_pt_update_ops;
 struct xe_pt_job_ops;
 struct xe_tile;
 struct xe_vm;
@@ -25,12 +25,11 @@ struct xe_vm;
 struct xe_pt_update_args {
 	/** @vm: VM which is being bound */
 	struct xe_vm *vm;
-	/** @tile: Tile which page tables belong to */
-	struct xe_tile *tile;
-	/** @ops: Migrate PT update ops */
-	const struct xe_migrate_pt_update_ops *ops;
+	/** @ops: CPU bind PT update ops */
+	const struct xe_cpu_bind_pt_update_ops *ops;
+#define XE_PT_UPDATE_JOB_OPS_COUNT	2
 	/** @pt_job_ops: PT job ops state */
-	struct xe_pt_job_ops *pt_job_ops;
+	struct xe_pt_job_ops *pt_job_ops[XE_PT_UPDATE_JOB_OPS_COUNT];
 };
 
 /**
diff --git a/drivers/gpu/drm/xe/xe_tlb_inval_job.c b/drivers/gpu/drm/xe/xe_tlb_inval_job.c
index 81f560068d3c..7378cfe6e855 100644
--- a/drivers/gpu/drm/xe/xe_tlb_inval_job.c
+++ b/drivers/gpu/drm/xe/xe_tlb_inval_job.c
@@ -4,6 +4,7 @@
  */
 
 #include "xe_assert.h"
+#include "xe_cpu_bind.h"
 #include "xe_dep_job_types.h"
 #include "xe_dep_scheduler.h"
 #include "xe_exec_queue.h"
@@ -12,7 +13,6 @@
 #include "xe_page_reclaim.h"
 #include "xe_tlb_inval.h"
 #include "xe_tlb_inval_job.h"
-#include "xe_migrate.h"
 #include "xe_pm.h"
 #include "xe_vm.h"
 
@@ -218,7 +218,6 @@ int xe_tlb_inval_job_alloc_dep(struct xe_tlb_inval_job *job)
 /**
  * xe_tlb_inval_job_push() - TLB invalidation job push
  * @job: TLB invalidation job to push
- * @m: The migration object being used
  * @fence: Dependency for TLB invalidation job
  *
  * Pushes a TLB invalidation job for execution, using @fence as a dependency.
@@ -230,11 +229,11 @@ int xe_tlb_inval_job_alloc_dep(struct xe_tlb_inval_job *job)
  * Return: Job's finished fence on success, cannot fail
  */
 struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
-					struct xe_migrate *m,
 					struct dma_fence *fence)
 {
 	struct xe_tlb_inval_fence *ifence =
 		container_of(job->fence, typeof(*ifence), base);
+	struct xe_cpu_bind *cpu_bind = gt_to_xe(job->q->gt)->cpu_bind;
 
 	if (!dma_fence_is_signaled(fence)) {
 		void *ptr;
@@ -258,11 +257,11 @@ struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
 	job->fence_armed = true;
 
 	/*
-	 * We need the migration lock to protect the job's seqno and the spsc
-	 * queue, only taken on migration queue, user queues protected dma-resv
+	 * We need the cpu_bind lock to protect the job's seqno and the spsc
+	 * queue, only taken on cpu_bind queue, user queues protected dma-resv
 	 * VM lock.
 	 */
-	xe_migrate_job_lock(m, job->q);
+	xe_cpu_bind_job_lock(cpu_bind, job->q);
 
 	/* Creation ref pairs with put in xe_tlb_inval_job_destroy */
 	xe_tlb_inval_fence_init(job->tlb_inval, ifence, false);
@@ -281,7 +280,7 @@ struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
 					       &job->dep.drm.s_fence->finished,
 					       job->idx);
 
-	xe_migrate_job_unlock(m, job->q);
+	xe_cpu_bind_job_unlock(cpu_bind, job->q);
 
 	/*
 	 * Not using job->fence, as it has its own dma-fence context, which does
diff --git a/drivers/gpu/drm/xe/xe_tlb_inval_job.h b/drivers/gpu/drm/xe/xe_tlb_inval_job.h
index 2a4478f529e6..97e032ea21c3 100644
--- a/drivers/gpu/drm/xe/xe_tlb_inval_job.h
+++ b/drivers/gpu/drm/xe/xe_tlb_inval_job.h
@@ -11,7 +11,6 @@
 struct dma_fence;
 struct xe_dep_scheduler;
 struct xe_exec_queue;
-struct xe_migrate;
 struct xe_page_reclaim_list;
 struct xe_tlb_inval;
 struct xe_tlb_inval_job;
@@ -28,7 +27,6 @@ void xe_tlb_inval_job_add_page_reclaim(struct xe_tlb_inval_job *job,
 int xe_tlb_inval_job_alloc_dep(struct xe_tlb_inval_job *job);
 
 struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
-					struct xe_migrate *m,
 					struct dma_fence *fence);
 
 void xe_tlb_inval_job_get(struct xe_tlb_inval_job *job);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 433a0a681556..fbfc42afef8b 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -24,6 +24,7 @@
 #include "regs/xe_gtt_defs.h"
 #include "xe_assert.h"
 #include "xe_bo.h"
+#include "xe_cpu_bind.h"
 #include "xe_device.h"
 #include "xe_drm_client.h"
 #include "xe_exec_queue.h"
@@ -795,8 +796,6 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
 	struct xe_vma *vma, *next;
 	struct xe_vma_ops vops;
 	struct xe_vma_op *op, *next_op;
-	struct xe_tile *tile;
-	u8 id;
 	int err;
 
 	lockdep_assert_held(&vm->lock);
@@ -804,12 +803,9 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
 	    list_empty(&vm->rebind_list))
 		return 0;
 
-	xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
-	for_each_tile(tile, vm->xe, id) {
-		vops.pt_update_ops[id].wait_vm_bookkeep = true;
-		vops.pt_update_ops[id].q =
-			xe_migrate_bind_queue(tile->migrate);
-	}
+	xe_vma_ops_init(&vops, vm, xe_cpu_bind_queue(vm->xe->cpu_bind),
+			NULL, 0);
+	vops.flags |= XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
 	xe_vm_assert_held(vm);
 	list_for_each_entry(vma, &vm->rebind_list, combined_links.rebind) {
@@ -854,21 +850,16 @@ struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_ma
 	struct dma_fence *fence = NULL;
 	struct xe_vma_ops vops;
 	struct xe_vma_op *op, *next_op;
-	struct xe_tile *tile;
-	u8 id;
 	int err;
 
 	lockdep_assert_held(&vm->lock);
 	xe_vm_assert_held(vm);
 	xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
 
-	xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
-	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT;
-	for_each_tile(tile, vm->xe, id) {
-		vops.pt_update_ops[id].wait_vm_bookkeep = true;
-		vops.pt_update_ops[tile->id].q =
-			xe_migrate_bind_queue(tile->migrate);
-	}
+	xe_vma_ops_init(&vops, vm, xe_cpu_bind_queue(vm->xe->cpu_bind),
+			NULL, 0);
+	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT |
+		XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
 	err = xe_vm_ops_add_rebind(&vops, vma, tile_mask);
 	if (err)
@@ -944,8 +935,6 @@ struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
 	struct dma_fence *fence = NULL;
 	struct xe_vma_ops vops;
 	struct xe_vma_op *op, *next_op;
-	struct xe_tile *tile;
-	u8 id;
 	int err;
 
 	lockdep_assert_held(&range->lock);
@@ -954,13 +943,10 @@ struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
 	xe_assert(vm->xe, xe_vm_in_fault_mode(vm));
 	xe_assert(vm->xe, xe_vma_is_cpu_addr_mirror(vma));
 
-	xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
-	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT;
-	for_each_tile(tile, vm->xe, id) {
-		vops.pt_update_ops[id].wait_vm_bookkeep = true;
-		vops.pt_update_ops[tile->id].q =
-			xe_migrate_bind_queue(tile->migrate);
-	}
+	xe_vma_ops_init(&vops, vm, xe_cpu_bind_queue(vm->xe->cpu_bind),
+			NULL, 0);
+	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT |
+		XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
 	err = xe_vm_ops_add_range_rebind(&vops, vma, range, tile_mask);
 	if (err)
@@ -1027,8 +1013,6 @@ struct dma_fence *xe_vm_range_unbind(struct xe_vm *vm,
 	struct dma_fence *fence = NULL;
 	struct xe_vma_ops vops;
 	struct xe_vma_op *op, *next_op;
-	struct xe_tile *tile;
-	u8 id;
 	int err;
 
 	lockdep_assert_held(&range->lock);
@@ -1039,12 +1023,9 @@ struct dma_fence *xe_vm_range_unbind(struct xe_vm *vm,
 	if (!range->tile_present)
 		return dma_fence_get_stub();
 
-	xe_vma_ops_init(&vops, vm, NULL, NULL, 0);
-	for_each_tile(tile, vm->xe, id) {
-		vops.pt_update_ops[id].wait_vm_bookkeep = true;
-		vops.pt_update_ops[tile->id].q =
-			xe_migrate_bind_queue(tile->migrate);
-	}
+	xe_vma_ops_init(&vops, vm, xe_cpu_bind_queue(vm->xe->cpu_bind),
+			NULL, 0);
+	vops.flags |= XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
 	err = xe_vm_ops_add_range_unbind(&vops, range);
 	if (err)
@@ -1715,9 +1696,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
 
 	init_rwsem(&vm->exec_queues.lock);
 	xe_vm_init_prove_locking(xe, vm);
-
-	for_each_tile(tile, xe, id)
-		xe_range_fence_tree_init(&vm->rftree[id]);
+	xe_range_fence_tree_init(&vm->rftree);
 
 	vm->pt_ops = &xelp_pt_ops;
 
@@ -1859,8 +1838,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
 	xe_svm_fini(vm);
 err_no_resv:
 	mutex_destroy(&vm->snap_mutex);
-	for_each_tile(tile, xe, id)
-		xe_range_fence_tree_fini(&vm->rftree[id]);
+	xe_range_fence_tree_fini(&vm->rftree);
 	ttm_lru_bulk_move_fini(&xe->ttm, &vm->lru_bulk_move);
 	if (vm->xef)
 		xe_file_put(vm->xef);
@@ -1916,10 +1894,8 @@ void xe_vm_close_and_put(struct xe_vm *vm)
 {
 	LIST_HEAD(contested);
 	struct xe_device *xe = vm->xe;
-	struct xe_tile *tile;
 	struct xe_vma *vma, *next_vma;
 	struct drm_gpuva *gpuva, *next;
-	u8 id;
 
 	xe_assert(xe, !vm->preempt.num_exec_queues);
 
@@ -2011,8 +1987,7 @@ void xe_vm_close_and_put(struct xe_vm *vm)
 
 	xe_vm_clear_fault_entries(vm);
 
-	for_each_tile(tile, xe, id)
-		xe_range_fence_tree_fini(&vm->rftree[id]);
+	xe_range_fence_tree_fini(&vm->rftree);
 
 	xe_vm_put(vm);
 }
@@ -3509,23 +3484,16 @@ static void trace_xe_vm_ops_execute(struct xe_vma_ops *vops)
 
 static int vm_ops_setup_tile_args(struct xe_vm *vm, struct xe_vma_ops *vops)
 {
-	struct xe_exec_queue *q = vops->q;
 	struct xe_tile *tile;
 	int number_tiles = 0;
 	u8 id;
 
-	for_each_tile(tile, vm->xe, id) {
+	for_each_tile(tile, vm->xe, id)
 		if (vops->pt_update_ops[id].num_ops)
 			++number_tiles;
 
-		if (vops->pt_update_ops[id].q)
-			continue;
-
-		if (q)
-			vops->pt_update_ops[id].q = q;
-		else
-			vops->pt_update_ops[id].q = vm->q;
-	}
+	if (!vops->q)
+		vops->q = vm->q;
 
 	return number_tiles;
 }
@@ -3533,22 +3501,17 @@ static int vm_ops_setup_tile_args(struct xe_vm *vm, struct xe_vma_ops *vops)
 static struct dma_fence *ops_execute(struct xe_vm *vm,
 				     struct xe_vma_ops *vops)
 {
-	struct xe_tile *tile;
+	struct xe_device *xe = vm->xe;
 	struct dma_fence *fence = NULL;
 	struct dma_fence **fences = NULL;
 	struct dma_fence_array *cf = NULL;
-	int number_tiles = 0, current_fence = 0, n_fence = 0, err, i;
-	u8 id;
+	int current_fence = 0, n_fence = 1, err, i;
 
-	number_tiles = vm_ops_setup_tile_args(vm, vops);
-	if (number_tiles == 0)
+	if (!vm_ops_setup_tile_args(vm, vops))
 		return ERR_PTR(-ENODATA);
 
-	for_each_tile(tile, vm->xe, id)
-		++n_fence;
-
 	if (!(vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT)) {
-		for_each_tlb_inval(vops->pt_update_ops[0].q, i)
+		for_each_tlb_inval(vops->q, i)
 			++n_fence;
 	}
 
@@ -3564,69 +3527,39 @@ static struct dma_fence *ops_execute(struct xe_vm *vm,
 		goto err_out;
 	}
 
-	for_each_tile(tile, vm->xe, id) {
-		if (!vops->pt_update_ops[id].num_ops)
-			continue;
-
-		err = xe_pt_update_ops_prepare(tile, vops);
-		if (err) {
-			fence = ERR_PTR(err);
-			goto err_out;
-		}
+	err = xe_pt_update_ops_prepare(xe, vops);
+	if (err) {
+		fence = ERR_PTR(err);
+		goto err_out;
 	}
 
 	trace_xe_vm_ops_execute(vops);
 
-	for_each_tile(tile, vm->xe, id) {
-		struct xe_exec_queue *q = vops->pt_update_ops[tile->id].q;
-
-		fence = NULL;
-		if (!vops->pt_update_ops[id].num_ops)
-			goto collect_fences;
-
-		fence = xe_pt_update_ops_run(tile, vops);
-		if (IS_ERR(fence))
-			goto err_out;
-
-collect_fences:
-		fences[current_fence++] = fence ?: dma_fence_get_stub();
-		if (vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT)
-			continue;
-
-		xe_migrate_job_lock(tile->migrate, q);
-		for_each_tlb_inval(q, i) {
-			if (i >= (tile->id + 1) * XE_MAX_GT_PER_TILE ||
-			    i < tile->id * XE_MAX_GT_PER_TILE)
-				continue;
+	fence = xe_pt_update_ops_run(xe, vops);
+	if (IS_ERR(fence))
+		goto err_out;
+	fences[current_fence++] = fence;
 
-			fences[current_fence++] = fence ?
-				xe_exec_queue_tlb_inval_last_fence_get(q, vm, i) :
-				dma_fence_get_stub();
-		}
-		xe_migrate_job_unlock(tile->migrate, q);
+	if (!(vops->flags & XE_VMA_OPS_FLAG_SKIP_TLB_WAIT)) {
+		xe_cpu_bind_job_lock(xe->cpu_bind, vops->q);
+		for_each_tlb_inval(vops->q, i)
+			fences[current_fence++] =
+				xe_exec_queue_tlb_inval_last_fence_get(vops->q,
+								       vm, i);
+		xe_cpu_bind_job_unlock(xe->cpu_bind, vops->q);
 	}
 
-	xe_assert(vm->xe, current_fence == n_fence);
+	xe_assert(xe, current_fence == n_fence);
 	dma_fence_array_init(cf, n_fence, fences, dma_fence_context_alloc(1),
 			     1);
 	fence = &cf->base;
 
-	for_each_tile(tile, vm->xe, id) {
-		if (!vops->pt_update_ops[id].num_ops)
-			continue;
-
-		xe_pt_update_ops_fini(tile, vops);
-	}
+	xe_pt_update_ops_fini(xe, vops);
 
 	return fence;
 
 err_out:
-	for_each_tile(tile, vm->xe, id) {
-		if (!vops->pt_update_ops[id].num_ops)
-			continue;
-
-		xe_pt_update_ops_abort(tile, vops);
-	}
+	xe_pt_update_ops_abort(xe, vops);
 	while (current_fence)
 		dma_fence_put(fences[--current_fence]);
 	kfree(fences);
@@ -3938,6 +3871,8 @@ static void xe_vma_ops_init(struct xe_vma_ops *vops, struct xe_vm *vm,
 	vops->syncs = syncs;
 	vops->num_syncs = num_syncs;
 	vops->flags = 0;
+	vops->start = ~0x0ull;
+	vops->last = 0x0ull;
 }
 
 static int xe_vm_bind_ioctl_validate_bo(struct xe_device *xe, struct xe_bo *bo,
diff --git a/drivers/gpu/drm/xe/xe_vm_types.h b/drivers/gpu/drm/xe/xe_vm_types.h
index c91cb13fc4f1..fb9305124679 100644
--- a/drivers/gpu/drm/xe/xe_vm_types.h
+++ b/drivers/gpu/drm/xe/xe_vm_types.h
@@ -317,7 +317,7 @@ struct xe_vm {
 	 * @rftree: range fence tree to track updates to page table structure.
 	 * Used to implement conflict tracking between independent bind engines.
 	 */
-	struct xe_range_fence_tree rftree[XE_MAX_TILES_PER_DEVICE];
+	struct xe_range_fence_tree rftree;
 
 	const struct xe_pt_ops *pt_ops;
 
@@ -557,6 +557,10 @@ struct xe_vma_ops {
 	u32 num_syncs;
 	/** @pt_update_ops: page table update operations */
 	struct xe_vm_pgtable_update_ops pt_update_ops[XE_MAX_TILES_PER_DEVICE];
+	/** @start: start address of ops */
+	u64 start;
+	/** @last: last address of ops */
+	u64 last;
 	/** @flag: signify the properties within xe_vma_ops*/
 #define XE_VMA_OPS_FLAG_HAS_SVM_PREFETCH	BIT(0)
 #define XE_VMA_OPS_FLAG_MADVISE			BIT(1)
@@ -566,6 +570,10 @@ struct xe_vma_ops {
 #define XE_VMA_OPS_FLAG_MODIFIES_GPUVA		BIT(5)
 #define XE_VMA_OPS_FLAG_DOWNGRADE_LOCK		BIT(6)
 #define XE_VMA_OPS_FLAG_HAS_SVM_VALID_RANGE	BIT(7)
+#define XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP	BIT(8)
+#define XE_VMA_OPS_FLAG_WAIT_VM_KERNEL		BIT(9)
+#define XE_VMA_OPS_FLAG_NEEDS_INVALIDATION	BIT(10)
+#define XE_VMA_OPS_FLAG_NEEDS_SVM_LOCK		BIT(11)
 	u32 flags;
 #ifdef TEST_VM_OPS_ERROR
 	/** @inject_error: inject error to test error handling */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (15 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 16/25] drm/xe: Add CPU bind layer Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:29   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 18/25] drm/xe: Add xe_hw_engine_write_ring_tail Matthew Brost
                   ` (10 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Some multi-tile devices may want to mirror page tables across tiles for
memory-bandwidth reasons, while others may not. Add a device flag that
allows enabling or disabling page-table mirroring across tiles.

Setting the flag to true (the existing behavior) on PVC, but both modes
have been tested and are working on PVC.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-18-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_device_types.h |  2 ++
 drivers/gpu/drm/xe/xe_migrate.c      |  5 ++--
 drivers/gpu/drm/xe/xe_pci.c          |  2 ++
 drivers/gpu/drm/xe/xe_pci_types.h    |  3 ++-
 drivers/gpu/drm/xe/xe_pt.c           | 38 ++++++++++++++++++++++++++--
 drivers/gpu/drm/xe/xe_vm.c           | 37 ++++++++++++++++++++++++---
 drivers/gpu/drm/xe/xe_vm.h           |  3 +++
 7 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
index bc60833c39a7..997ac82fd571 100644
--- a/drivers/gpu/drm/xe/xe_device_types.h
+++ b/drivers/gpu/drm/xe/xe_device_types.h
@@ -224,6 +224,8 @@ struct xe_device {
 		u8 has_usm:1;
 		/** @info.has_64bit_timestamp: Device supports 64-bit timestamps */
 		u8 has_64bit_timestamp:1;
+		/** @info.has_pt_mirror: Device has PT mirroring across tiles */
+		u8 has_pt_mirror:1;
 		/** @info.is_dgfx: is discrete device */
 		u8 is_dgfx:1;
 		/** @info.needs_scratch: needs scratch page for oob prefetch to work */
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index f7e1a81434b2..471ae5741836 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -246,7 +246,8 @@ static void xe_migrate_prepare_vm(struct xe_tile *tile, struct xe_migrate *m,
 	struct xe_device *xe = tile_to_xe(tile);
 	u16 pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
 	u8 id = tile->id;
-	u32 num_entries = NUM_PT_SLOTS, num_level = vm->pt_root[id]->level;
+	u32 num_entries = NUM_PT_SLOTS, num_level =
+		xe_vm_pt_root(vm, id)->level;
 #define VRAM_IDENTITY_MAP_PT_COUNT	4
 	u32 num_setup = num_level + VRAM_IDENTITY_MAP_PT_COUNT;
 #undef VRAM_IDENTITY_MAP_PT_COUNT
@@ -258,7 +259,7 @@ static void xe_migrate_prepare_vm(struct xe_tile *tile, struct xe_migrate *m,
 	u64 l1_pt_ofs = xe_bo_size(bo) - 5 * XE_PAGE_SIZE;
 
 	entry = vm->pt_ops->pde_encode_bo(bo, l1_pt_ofs);
-	xe_pt_write(xe, &vm->pt_root[id]->bo->vmap, 0, entry);
+	xe_pt_write(xe, &xe_vm_pt_root(vm, id)->bo->vmap, 0, entry);
 
 	map_ofs = (num_entries - num_setup) * XE_PAGE_SIZE;
 
diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c
index ab4da1d9a9f1..d9030cb4f1ad 100644
--- a/drivers/gpu/drm/xe/xe_pci.c
+++ b/drivers/gpu/drm/xe/xe_pci.c
@@ -371,6 +371,7 @@ static const __maybe_unused struct xe_device_desc pvc_desc = {
 	.has_display = false,
 	.has_drm_ras = true,
 	.has_gsc_nvm = 1,
+	.has_pt_mirror = 1,
 	.has_heci_gscfi = 1,
 	.max_gt_per_tile = 1,
 	.max_remote_tiles = 1,
@@ -808,6 +809,7 @@ static int xe_info_init_early(struct xe_device *xe,
 	xe->info.has_mert = desc->has_mert;
 	xe->info.has_page_reclaim_hw_assist = desc->has_page_reclaim_hw_assist;
 	xe->info.has_pre_prod_wa = desc->has_pre_prod_wa;
+	xe->info.has_pt_mirror = desc->has_pt_mirror;
 	xe->info.has_pxp = desc->has_pxp;
 	xe->info.has_soc_remapper_sysctrl = desc->has_soc_remapper_sysctrl;
 	xe->info.has_soc_remapper_telem = desc->has_soc_remapper_telem;
diff --git a/drivers/gpu/drm/xe/xe_pci_types.h b/drivers/gpu/drm/xe/xe_pci_types.h
index fed509ff601e..71068cdb3558 100644
--- a/drivers/gpu/drm/xe/xe_pci_types.h
+++ b/drivers/gpu/drm/xe/xe_pci_types.h
@@ -52,8 +52,9 @@ struct xe_device_desc {
 	u8 has_mbx_power_limits:1;
 	u8 has_mbx_thermal_info:1;
 	u8 has_mert:1;
-	u8 has_pre_prod_wa:1;
 	u8 has_page_reclaim_hw_assist:1;
+	u8 has_pre_prod_wa:1;
+	u8 has_pt_mirror:1;
 	u8 has_pxp:1;
 	u8 has_soc_remapper_sysctrl:1;
 	u8 has_soc_remapper_telem:1;
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index 821edeaecaed..deb33e85e6eb 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -813,7 +813,7 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma,
 		.wupd.entries = entries,
 		.clear_pt = clear_pt,
 	};
-	struct xe_pt *pt = vm->pt_root[tile->id];
+	struct xe_pt *pt = xe_vm_pt_root(vm, tile->id);
 	int ret;
 	bool is_purged = false;
 
@@ -1006,6 +1006,11 @@ static int xe_pt_zap_ptes_entry(struct xe_ptw *parent, pgoff_t offset,
 	return 0;
 }
 
+static bool pt_mirroring_disabled_for_tile(struct xe_vm *vm, u8 tile_id)
+{
+	return xe_vm_pt_root(vm, tile_id) != vm->pt_root[tile_id];
+}
+
 static const struct xe_pt_walk_ops xe_pt_zap_ptes_ops = {
 	.pt_entry = xe_pt_zap_ptes_entry,
 };
@@ -1047,6 +1052,9 @@ bool xe_pt_zap_ptes(struct xe_tile *tile, struct xe_vma *vma)
 	if (!(pt_mask & BIT(tile->id)))
 		return false;
 
+	if (pt_mirroring_disabled_for_tile(xe_vma_vm(vma), tile->id))
+		return true;
+
 	(void)xe_pt_walk_shared(&pt->base, pt->level, xe_vma_start(vma),
 				xe_vma_end(vma), &xe_walk.base);
 
@@ -1099,6 +1107,9 @@ bool xe_pt_zap_ptes_range(struct xe_tile *tile, struct xe_vm *vm,
 	if (!(pt_mask & BIT(tile->id)))
 		return false;
 
+	if (pt_mirroring_disabled_for_tile(vm, tile->id))
+		return true;
+
 	(void)xe_pt_walk_shared(&pt->base, pt->level, xe_svm_range_start(range),
 				xe_svm_range_end(range), &xe_walk.base);
 
@@ -1989,7 +2000,7 @@ static unsigned int xe_pt_stage_unbind(struct xe_tile *tile,
 		.wupd.entries = entries,
 		.prl = pt_update_op->prl,
 	};
-	struct xe_pt *pt = vm->pt_root[tile->id];
+	struct xe_pt *pt = xe_vm_pt_root(vm, tile->id);
 
 	(void)xe_pt_walk_shared(&pt->base, pt->level, start, end,
 				&xe_walk.base);
@@ -2542,9 +2553,20 @@ int xe_pt_update_ops_prepare(struct xe_device *xe, struct xe_vma_ops *vops)
 	int id, err;
 
 	for_each_tile(tile, xe, id) {
+		struct xe_vm_pgtable_update_ops *pt_update_ops =
+			&vops->pt_update_ops[id];
+
 		if (!vops->pt_update_ops[id].num_ops)
 			continue;
 
+		if (pt_mirroring_disabled_for_tile(vops->vm, id)) {
+			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
+
+			/* Transfer root PT update ops PRL to current */
+			*prl = vops->pt_update_ops[0].prl;
+			continue;
+		}
+
 		err = __xe_pt_update_ops_prepare(tile, vops);
 		if (err)
 			return err;
@@ -2836,6 +2858,12 @@ xe_pt_update_ops_run(struct xe_device *xe, struct xe_vma_ops *vops)
 		struct xe_vm_pgtable_update_ops *pt_update_ops =
 			&vops->pt_update_ops[j];
 
+		if (pt_mirroring_disabled_for_tile(vm, j)) {
+			xe_tile_assert(tile, !get_current_op(pt_update_ops));
+			tile_mask |= BIT(tile->id);
+			continue;
+		}
+
 		for (i = 0; i < get_current_op(pt_update_ops); ++i) {
 			struct xe_vm_pgtable_update_op *pt_op =
 				to_pt_op(pt_update_ops, i);
@@ -2925,6 +2953,9 @@ void xe_pt_update_ops_fini(struct xe_device *xe, struct xe_vma_ops *vops)
 		if (!pt_update_ops->num_ops)
 			continue;
 
+		if (pt_mirroring_disabled_for_tile(vops->vm, id))
+			continue;
+
 		xe_page_reclaim_entries_put(pt_update_ops->prl.entries);
 	}
 }
@@ -2949,6 +2980,9 @@ void xe_pt_update_ops_abort(struct xe_device *xe, struct xe_vma_ops *vops)
 			&vops->pt_update_ops[id];
 		int i;
 
+		if (pt_mirroring_disabled_for_tile(vops->vm, id))
+			continue;
+
 		for (i = pt_update_ops->num_ops - 1; i >= 0; --i) {
 			struct xe_vm_pgtable_update_op *pt_op =
 				to_pt_op(pt_update_ops, i);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index fbfc42afef8b..a6dc010e5e0d 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -845,6 +845,14 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
 	return err;
 }
 
+static u8 adjust_rebind_tile_mask(struct xe_vm *vm, u8 tile_mask)
+{
+	if (vm->xe->info.has_pt_mirror)
+		return tile_mask;
+
+	return (0x1 << vm->xe->info.max_gt_per_tile) - 1;
+}
+
 struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_mask)
 {
 	struct dma_fence *fence = NULL;
@@ -861,7 +869,8 @@ struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_ma
 	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT |
 		XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
-	err = xe_vm_ops_add_rebind(&vops, vma, tile_mask);
+	err = xe_vm_ops_add_rebind(&vops, vma,
+				   adjust_rebind_tile_mask(vm, tile_mask));
 	if (err)
 		return ERR_PTR(err);
 
@@ -948,7 +957,8 @@ struct dma_fence *xe_vm_range_rebind(struct xe_vm *vm,
 	vops.flags |= XE_VMA_OPS_FLAG_SKIP_TLB_WAIT |
 		XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP;
 
-	err = xe_vm_ops_add_range_rebind(&vops, vma, range, tile_mask);
+	err = xe_vm_ops_add_range_rebind(&vops, vma, range,
+					 adjust_rebind_tile_mask(vm, tile_mask));
 	if (err)
 		return ERR_PTR(err);
 
@@ -1738,7 +1748,8 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
 
 		for_each_tile(tile, xe, id) {
 			if (flags & XE_VM_FLAG_MIGRATION &&
-			    tile->id != XE_VM_FLAG_TILE_ID(flags))
+			    tile->id != XE_VM_FLAG_TILE_ID(flags) &&
+			    (vm->xe->info.has_pt_mirror || id))
 				continue;
 
 			vm->pt_root[id] = xe_pt_create(vm, tile, xe->info.vm_max_level,
@@ -2047,7 +2058,7 @@ struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id)
 
 u64 xe_vm_pdp4_descriptor(struct xe_vm *vm, struct xe_tile *tile)
 {
-	return vm->pt_ops->pde_encode_bo(vm->pt_root[tile->id]->bo, 0);
+	return vm->pt_ops->pde_encode_bo(xe_vm_pt_root(vm, tile->id)->bo, 0);
 }
 
 static struct xe_exec_queue *
@@ -5072,3 +5083,21 @@ void xe_vm_remove_exec_queue(struct xe_vm *vm, struct xe_exec_queue *q)
 	}
 	up_write(&vm->exec_queues.lock);
 }
+
+/**
+ * xe_vm_pt_root() - Retrieve VM page-table root
+ * @vm: The VM.
+ * @tile_id: Tile ID
+ *
+ * Retrieve VM page-table root for a tile ID, used to abstract if PT mirroring is
+ * enabled across tiles.
+ *
+ * Return: VM page-table root for a tile ID
+ */
+struct xe_pt *xe_vm_pt_root(struct xe_vm *vm, u8 tile_id)
+{
+	if (vm->xe->info.has_pt_mirror)
+		return vm->pt_root[tile_id];
+
+	return vm->pt_root[0];
+}
diff --git a/drivers/gpu/drm/xe/xe_vm.h b/drivers/gpu/drm/xe/xe_vm.h
index c5b900f38ded..89c4f95984a2 100644
--- a/drivers/gpu/drm/xe/xe_vm.h
+++ b/drivers/gpu/drm/xe/xe_vm.h
@@ -436,4 +436,7 @@ static inline struct drm_exec *xe_vm_validation_exec(struct xe_vm *vm)
 	((READ_ONCE(tile_present) & ~READ_ONCE(tile_invalidated)) & BIT((tile)->id))
 
 void xe_vma_mem_attr_copy(struct xe_vma_mem_attr *to, struct xe_vma_mem_attr *from);
+
+struct xe_pt *xe_vm_pt_root(struct xe_vm *vm, u8 tile_id);
+
 #endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 18/25] drm/xe: Add xe_hw_engine_write_ring_tail
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (16 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 19/25] drm/xe: Add ULLS support to LRC Matthew Brost
                   ` (9 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

ULLS for migration jobs need to directly set hw engine ring tail, add
function to support this.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-19-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_hw_engine.c | 20 ++++++++++++++++++++
 drivers/gpu/drm/xe/xe_hw_engine.h |  1 +
 2 files changed, 21 insertions(+)

diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c
index 010499766fce..635744f79be6 100644
--- a/drivers/gpu/drm/xe/xe_hw_engine.c
+++ b/drivers/gpu/drm/xe/xe_hw_engine.c
@@ -282,6 +282,26 @@ static void hw_engine_fini(void *arg)
 	hwe->gt = NULL;
 }
 
+static void xe_hw_engine_mmio_write32(struct xe_hw_engine *hwe, struct xe_reg reg, u32 val)
+{
+	xe_gt_assert(hwe->gt, !(reg.addr & hwe->mmio_base));
+	xe_force_wake_assert_held(gt_to_fw(hwe->gt), hwe->domain);
+
+	reg.addr += hwe->mmio_base;
+
+	xe_mmio_write32(&hwe->gt->mmio, reg, val);
+}
+
+/**
+ * xe_hw_engine_write_ring_tail() - Write ring tail
+ * @hwe: engine
+ * @val: desired 32-bit value to write
+ */
+void xe_hw_engine_write_ring_tail(struct xe_hw_engine *hwe, u32 val)
+{
+	xe_hw_engine_mmio_write32(hwe, RING_TAIL(0), val);
+}
+
 /**
  * xe_hw_engine_mmio_read32() - Read engine register
  * @hwe: engine
diff --git a/drivers/gpu/drm/xe/xe_hw_engine.h b/drivers/gpu/drm/xe/xe_hw_engine.h
index c3ee37f8cfc0..e13610a56ad7 100644
--- a/drivers/gpu/drm/xe/xe_hw_engine.h
+++ b/drivers/gpu/drm/xe/xe_hw_engine.h
@@ -77,5 +77,6 @@ u64 xe_hw_engine_read_timestamp(struct xe_hw_engine *hwe);
 enum xe_force_wake_domains xe_hw_engine_to_fw_domain(struct xe_hw_engine *hwe);
 
 u32 xe_hw_engine_mmio_read32(struct xe_hw_engine *hwe, struct xe_reg reg);
+void xe_hw_engine_write_ring_tail(struct xe_hw_engine *hwe, u32 val);
 
 #endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 19/25] drm/xe: Add ULLS support to LRC
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (17 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 18/25] drm/xe: Add xe_hw_engine_write_ring_tail Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer Matthew Brost
                   ` (8 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Define memory layout for ULLS semaphores stored in LRC memory. Add
support functions to return GGTT address and set semaphore based on a
job's seqno.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-20-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_lrc.c       | 51 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/xe/xe_lrc.h       |  3 ++
 drivers/gpu/drm/xe/xe_lrc_types.h |  4 +++
 3 files changed, 58 insertions(+)

diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c
index 25fe9dbc9141..a1a256a3932f 100644
--- a/drivers/gpu/drm/xe/xe_lrc.c
+++ b/drivers/gpu/drm/xe/xe_lrc.c
@@ -706,6 +706,7 @@ u32 xe_lrc_pphwsp_offset(struct xe_lrc *lrc)
 #define LRC_CTX_JOB_TIMESTAMP_OFFSET 512
 #define LRC_ENGINE_ID_PPHWSP_OFFSET 1024
 #define LRC_PARALLEL_PPHWSP_OFFSET 2048
+#define LRC_ULLS_PPHWSP_OFFSET 2048	/* Mutually exclusive with parallel */
 
 #define LRC_SEQNO_OFFSET 0
 #define LRC_START_SEQNO_OFFSET (LRC_SEQNO_OFFSET + 8)
@@ -768,6 +769,12 @@ static inline u32 __xe_lrc_engine_id_offset(struct xe_lrc *lrc)
 	return xe_lrc_pphwsp_offset(lrc) + LRC_ENGINE_ID_PPHWSP_OFFSET;
 }
 
+static u32 __xe_lrc_ulls_offset(struct xe_lrc *lrc)
+{
+	/* The ulls is stored in the driver-defined portion of PPHWSP */
+	return xe_lrc_pphwsp_offset(lrc) + LRC_ULLS_PPHWSP_OFFSET;
+}
+
 static u32 __xe_lrc_ctx_timestamp_offset(struct xe_lrc *lrc)
 {
 	return __xe_lrc_regs_offset(lrc) + CTX_TIMESTAMP * sizeof(u32);
@@ -835,6 +842,7 @@ DECL_MAP_ADDR_HELPERS(ctx_job_timestamp, lrc->bo)
 DECL_MAP_ADDR_HELPERS(ctx_timestamp, lrc->bo)
 DECL_MAP_ADDR_HELPERS(ctx_timestamp_udw, lrc->bo)
 DECL_MAP_ADDR_HELPERS(parallel, lrc->bo)
+DECL_MAP_ADDR_HELPERS(ulls, lrc->bo)
 DECL_MAP_ADDR_HELPERS(indirect_ring, lrc->bo)
 DECL_MAP_ADDR_HELPERS(engine_id, lrc->bo)
 DECL_MAP_ADDR_HELPERS(queue_timestamp, lrc->bo)
@@ -1984,6 +1992,49 @@ static u32 xe_lrc_engine_id(struct xe_lrc *lrc)
 	return xe_map_read32(xe, &map);
 }
 
+#define semaphore_offset(seqno) \
+	(sizeof(u32) * ((seqno) % LRC_MIGRATION_ULLS_SEMAPHORE_COUNT))
+
+/**
+ * xe_lrc_ulls_semaphore_ggtt_addr() - ULLS semaphore GGTT address
+ * @lrc: Pointer to the lrc.
+ * @seqno: seqno of current job.
+ *
+ * Calculate ULLS semaphore GGTT address based on input seqno
+ *
+ * Returns: ULLS semaphore GGTT address
+ */
+u32 xe_lrc_ulls_semaphore_ggtt_addr(struct xe_lrc *lrc, u32 seqno)
+{
+	xe_assert(lrc_to_xe(lrc), semaphore_offset(seqno) <
+		  LRC_PPHWSP_SIZE - LRC_ULLS_PPHWSP_OFFSET);
+
+	return __xe_lrc_ulls_ggtt_addr(lrc) + semaphore_offset(seqno);
+}
+
+/**
+ * xe_lrc_set_ulls_semaphore() - Set ULLS semaphore
+ * @lrc: Pointer to the lrc.
+ * @seqno: seqno of current job.
+ *
+ * Set ULLS semaphore based on input seqno
+ */
+void xe_lrc_set_ulls_semaphore(struct xe_lrc *lrc, u32 seqno)
+{
+	struct xe_device *xe = lrc_to_xe(lrc);
+	struct iosys_map map = __xe_lrc_ulls_map(lrc);
+
+	xe_assert(xe, semaphore_offset(seqno) <
+		  LRC_PPHWSP_SIZE - LRC_ULLS_PPHWSP_OFFSET);
+
+	xe_device_wmb(xe);	/* Ensure everything before in code is ordered */
+
+	iosys_map_incr(&map, semaphore_offset(seqno));
+	xe_map_write32(xe, &map, LRC_MIGRATION_ULLS_SEMAPHORE_SIGNAL);
+
+	xe_device_wmb(xe);	/* Flush write to hardware */
+}
+
 static int instr_dw(u32 cmd_header)
 {
 	/* GFXPIPE "SINGLE_DW" opcodes are a single dword */
diff --git a/drivers/gpu/drm/xe/xe_lrc.h b/drivers/gpu/drm/xe/xe_lrc.h
index 7be5e3da8bc8..c13e03807c2d 100644
--- a/drivers/gpu/drm/xe/xe_lrc.h
+++ b/drivers/gpu/drm/xe/xe_lrc.h
@@ -127,6 +127,9 @@ void xe_default_lrc_update_memirq_regs_with_address(struct xe_hw_engine *hwe);
 void xe_lrc_update_memirq_regs_with_address(struct xe_lrc *lrc, struct xe_hw_engine *hwe,
 					    u32 *regs);
 
+u32 xe_lrc_ulls_semaphore_ggtt_addr(struct xe_lrc *lrc, u32 seqno);
+void xe_lrc_set_ulls_semaphore(struct xe_lrc *lrc, u32 seqno);
+
 u32 xe_lrc_read_ctx_reg(struct xe_lrc *lrc, int reg_nr);
 void xe_lrc_write_ctx_reg(struct xe_lrc *lrc, int reg_nr, u32 val);
 
diff --git a/drivers/gpu/drm/xe/xe_lrc_types.h b/drivers/gpu/drm/xe/xe_lrc_types.h
index 53ef48feebfc..b18daf1092e6 100644
--- a/drivers/gpu/drm/xe/xe_lrc_types.h
+++ b/drivers/gpu/drm/xe/xe_lrc_types.h
@@ -12,6 +12,10 @@
 
 struct xe_bo;
 
+#define LRC_MIGRATION_ULLS_SEMAPHORE_COUNT	64	/* Must be pow2 */
+#define LRC_MIGRATION_ULLS_SEMAPHORE_CLEAR	0
+#define LRC_MIGRATION_ULLS_SEMAPHORE_SIGNAL	1
+
 /**
  * struct xe_lrc - Logical ring context (LRC) and submission ring object
  */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (18 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 19/25] drm/xe: Add ULLS support to LRC Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:27   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 21/25] drm/xe: Add ULLS migration job support to ring ops Matthew Brost
                   ` (7 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add function to enter ULLS mode for migration job and delayed worker to
exit (power saving). ULLS mode expected to entered upon page fault or
SVM prefetch. ULLS mode exit delay is currently set to 5us.

ULLS mode only support on DGFX and USM platforms where a hardware engine
is reserved for migrations jobs. When in ULLS mode, set several flags on
migration jobs so submission backend / ring ops can properly submit in
ULLS mode.

Upon ULLS mode enter, send a job trigger waiting a semphore pipling
initial GuC / HW conetxt switch.

Upon ULLS mode exit, send a job to trigger that current ULLS
semaphore so the ring can be taken off the hardware.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-21-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_exec_queue.c      |   5 +-
 drivers/gpu/drm/xe/xe_exec_queue.h      |   2 +-
 drivers/gpu/drm/xe/xe_migrate.c         | 180 ++++++++++++++++++++++++
 drivers/gpu/drm/xe/xe_migrate.h         |   2 +
 drivers/gpu/drm/xe/xe_pt.c              |   2 +-
 drivers/gpu/drm/xe/xe_sched_job_types.h |   6 +
 drivers/gpu/drm/xe/xe_vm.c              |   2 +-
 7 files changed, 194 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c
index e89802ff4f0e..98b9b1b88e95 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.c
+++ b/drivers/gpu/drm/xe/xe_exec_queue.c
@@ -1482,6 +1482,7 @@ bool xe_exec_queue_is_lr(struct xe_exec_queue *q)
 /**
  * xe_exec_queue_is_idle() - Whether an exec_queue is idle.
  * @q: The exec_queue
+ * @extra_jobs: Extra jobs on the queue
  *
  * FIXME: Need to determine what to use as the short-lived
  * timeline lock for the exec_queues, so that the return value
@@ -1493,9 +1494,9 @@ bool xe_exec_queue_is_lr(struct xe_exec_queue *q)
  *
  * Return: True if the exec_queue is idle, false otherwise.
  */
-bool xe_exec_queue_is_idle(struct xe_exec_queue *q)
+bool xe_exec_queue_is_idle(struct xe_exec_queue *q, int extra_jobs)
 {
-	return !atomic_read(&q->job_cnt);
+	return !(atomic_read(&q->job_cnt) - extra_jobs);
 }
 
 /**
diff --git a/drivers/gpu/drm/xe/xe_exec_queue.h b/drivers/gpu/drm/xe/xe_exec_queue.h
index b02a390ba989..e8963f85cabd 100644
--- a/drivers/gpu/drm/xe/xe_exec_queue.h
+++ b/drivers/gpu/drm/xe/xe_exec_queue.h
@@ -116,7 +116,7 @@ static inline struct xe_exec_queue *xe_exec_queue_multi_queue_primary(struct xe_
 
 bool xe_exec_queue_is_lr(struct xe_exec_queue *q);
 
-bool xe_exec_queue_is_idle(struct xe_exec_queue *q);
+bool xe_exec_queue_is_idle(struct xe_exec_queue *q, int extra_jobs);
 
 void xe_exec_queue_kill(struct xe_exec_queue *q);
 
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 471ae5741836..1fa236eb1a26 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -8,6 +8,7 @@
 #include <linux/bitfield.h>
 #include <linux/sizes.h>
 
+#include <drm/drm_drv.h>
 #include <drm/drm_managed.h>
 #include <drm/drm_pagemap.h>
 #include <drm/ttm/ttm_tt.h>
@@ -23,6 +24,7 @@
 #include "xe_bb.h"
 #include "xe_bo.h"
 #include "xe_exec_queue.h"
+#include "xe_force_wake.h"
 #include "xe_ggtt.h"
 #include "xe_gt.h"
 #include "xe_gt_printk.h"
@@ -32,6 +34,7 @@
 #include "xe_mem_pool.h"
 #include "xe_mocs.h"
 #include "xe_pat.h"
+#include "xe_pm.h"
 #include "xe_printk.h"
 #include "xe_pt.h"
 #include "xe_res_cursor.h"
@@ -77,6 +80,14 @@ struct xe_migrate {
 	struct dma_fence *fence;
 	/** @min_chunk_size: For dgfx, Minimum chunk size */
 	u64 min_chunk_size;
+	/** @ulls: ULLS support */
+	struct {
+		/** @ulls.enabled: ULLS is enabled */
+		bool enabled;
+#define ULLS_EXIT_JIFFIES	(HZ / 50)
+		/** @ulls.exit_work: ULLS exit worker */
+		struct delayed_work exit_work;
+	} ulls;
 };
 
 #define MAX_PREEMPTDISABLE_TRANSFER SZ_8M /* Around 1ms. */
@@ -98,6 +109,16 @@ struct xe_migrate {
 static void xe_migrate_fini(void *arg)
 {
 	struct xe_migrate *m = arg;
+	struct xe_device *xe = tile_to_xe(m->tile);
+
+	disable_delayed_work_sync(&m->ulls.exit_work);
+	mutex_lock(&m->job_mutex);
+	if (m->ulls.enabled) {
+		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
+		xe_pm_runtime_put(xe);
+		m->ulls.enabled = false;
+	}
+	mutex_unlock(&m->job_mutex);
 
 	xe_vm_lock(m->q->vm, false);
 	xe_bo_unpin(m->pt_bo);
@@ -448,6 +469,140 @@ static int xe_migrate_lock_prepare_vm(struct xe_tile *tile, struct xe_migrate *m
 	return err;
 }
 
+/**
+ * xe_migrate_ulls_enter() - Enter ULLS mode
+ * @m: The migration context.
+ *
+ * If DGFX and not a VF, enter ULLS mode bypassing GuC / HW context
+ * switches by utilizing semaphore and continuously running batches.
+ */
+void xe_migrate_ulls_enter(struct xe_migrate *m)
+{
+	struct xe_device *xe = tile_to_xe(m->tile);
+	struct xe_sched_job *job = NULL;
+	u64 batch_addr[2] = { 0, 0 };
+	bool alloc = false;
+
+	xe_assert(xe, xe->info.has_usm);
+
+	if (!IS_DGFX(xe) || IS_SRIOV_VF(xe))
+		return;
+
+job_alloc:
+	if (alloc) {
+		/*
+		 * Must be done outside job_mutex as that lock is tainted with
+		 * reclaim.
+		 */
+		job = xe_sched_job_create(m->q, batch_addr);
+		if (WARN_ON_ONCE(IS_ERR(job)))
+			return;		/* Not fatal */
+	}
+
+	mutex_lock(&m->job_mutex);
+	if (!m->ulls.enabled) {
+		unsigned int fw_ref;
+
+		if (!job) {
+			alloc = true;
+			mutex_unlock(&m->job_mutex);
+			goto job_alloc;
+		}
+
+		/* Pairs with FW put on ULLS exit */
+		fw_ref = xe_force_wake_get(gt_to_fw(m->q->hwe->gt),
+					   m->q->hwe->domain);
+		if (fw_ref) {
+			struct xe_device *xe = tile_to_xe(m->tile);
+			struct dma_fence *fence;
+
+			/* Pairs with PM put on ULLS exit */
+			xe_pm_runtime_get_noresume(xe);
+
+			xe_sched_job_get(job);
+			xe_sched_job_arm(job);
+			job->is_ulls = true;
+			job->is_ulls_first = true;
+			fence = dma_fence_get(&job->drm.s_fence->finished);
+			xe_sched_job_push(job);
+
+			dma_fence_put(fence);
+
+			xe_dbg(xe, "Migrate ULLS mode enter");
+			m->ulls.enabled = true;
+		}
+	}
+	if (job)
+		xe_sched_job_put(job);
+	if (m->ulls.enabled)
+		mod_delayed_work(system_percpu_wq, &m->ulls.exit_work,
+				 ULLS_EXIT_JIFFIES);
+	mutex_unlock(&m->job_mutex);
+}
+
+static void xe_migrate_ulls_exit(struct work_struct *work)
+{
+	struct xe_migrate *m = container_of(work, struct xe_migrate,
+					    ulls.exit_work.work);
+	struct xe_device *xe = tile_to_xe(m->tile);
+	struct xe_sched_job *job = NULL;
+	struct dma_fence *fence;
+	u64 batch_addr[2] = { 0, 0 };
+	int idx;
+
+	xe_assert(xe, m->ulls.enabled);
+
+	if (!drm_dev_enter(&xe->drm, &idx))
+		return;
+
+	/*
+	 * Must be done outside job_mutex as that lock is tainted with
+	 * reclaim and must be done holding a pm ref.
+	 */
+	job = xe_sched_job_create(m->q, batch_addr);
+	if (WARN_ON_ONCE(IS_ERR(job))) {
+		drm_dev_exit(idx);
+		mod_delayed_work(system_percpu_wq, &m->ulls.exit_work,
+				 ULLS_EXIT_JIFFIES);
+		return;		/* Not fatal */
+	}
+
+	mutex_lock(&m->job_mutex);
+
+	if (!xe_exec_queue_is_idle(m->q, 1))
+		goto unlock_exit;
+
+	xe_sched_job_get(job);
+	xe_sched_job_arm(job);
+	job->is_ulls = true;
+	job->is_ulls_last = true;
+	fence = dma_fence_get(&job->drm.s_fence->finished);
+	xe_sched_job_push(job);
+
+	/* Serialize force wake put */
+	dma_fence_wait(fence, false);
+	dma_fence_put(fence);
+
+	m->ulls.enabled = false;
+unlock_exit:
+	if (job)
+		xe_sched_job_put(job);
+	if (!m->ulls.enabled) {
+		/* Pairs with PM gets on enter */
+		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
+		xe_pm_runtime_put(xe);
+
+		cancel_delayed_work(&m->ulls.exit_work);
+		xe_dbg(xe, "Migrate ULLS mode exit");
+	} else {
+		mod_delayed_work(system_percpu_wq, &m->ulls.exit_work,
+				 ULLS_EXIT_JIFFIES);
+	}
+
+	mutex_unlock(&m->job_mutex);
+	drm_dev_exit(idx);
+}
+
 /**
  * xe_migrate_init() - Initialize a migrate context
  * @m: The migration context
@@ -506,6 +661,8 @@ int xe_migrate_init(struct xe_migrate *m)
 	might_lock(&m->job_mutex);
 	fs_reclaim_release(GFP_KERNEL);
 
+	INIT_DELAYED_WORK(&m->ulls.exit_work, xe_migrate_ulls_exit);
+
 	err = devm_add_action_or_reset(xe->drm.dev, xe_migrate_fini, m);
 	if (err)
 		return err;
@@ -871,6 +1028,26 @@ static u32 xe_migrate_ccs_copy(struct xe_migrate *m,
 	return flush_flags;
 }
 
+static bool xe_migrate_is_ulls(struct xe_migrate *m)
+{
+	lockdep_assert_held(&m->job_mutex);
+
+	return m->ulls.enabled;
+}
+
+static void xe_migrate_job_set_ulls_flags(struct xe_migrate *m,
+					  struct xe_sched_job *job)
+{
+	lockdep_assert_held(&m->job_mutex);
+	xe_tile_assert(m->tile, m->q == job->q);
+
+	if (xe_migrate_is_ulls(m)) {
+		job->is_ulls = true;
+		mod_delayed_work(system_percpu_wq, &m->ulls.exit_work,
+				 ULLS_EXIT_JIFFIES);
+	}
+}
+
 static struct dma_fence *__xe_migrate_copy(struct xe_migrate *m,
 					   struct xe_bo *src_bo,
 					   struct xe_bo *dst_bo,
@@ -1033,6 +1210,7 @@ static struct dma_fence *__xe_migrate_copy(struct xe_migrate *m,
 		}
 
 		mutex_lock(&m->job_mutex);
+		xe_migrate_job_set_ulls_flags(m, job);
 		xe_sched_job_arm(job);
 		dma_fence_put(fence);
 		fence = dma_fence_get(&job->drm.s_fence->finished);
@@ -1701,6 +1879,7 @@ struct dma_fence *xe_migrate_clear(struct xe_migrate *m,
 		}
 
 		mutex_lock(&m->job_mutex);
+		xe_migrate_job_set_ulls_flags(m, job);
 		xe_sched_job_arm(job);
 		dma_fence_put(fence);
 		fence = dma_fence_get(&job->drm.s_fence->finished);
@@ -1980,6 +2159,7 @@ static struct dma_fence *xe_migrate_vram(struct xe_migrate *m,
 	}
 
 	mutex_lock(&m->job_mutex);
+	xe_migrate_job_set_ulls_flags(m, job);
 	xe_sched_job_arm(job);
 	fence = dma_fence_get(&job->drm.s_fence->finished);
 	xe_sched_job_push(job);
diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h
index 67e5ba1f8284..71f11b2f66cd 100644
--- a/drivers/gpu/drm/xe/xe_migrate.h
+++ b/drivers/gpu/drm/xe/xe_migrate.h
@@ -98,4 +98,6 @@ int xe_migrate_debug_ccs_overlap(struct xe_migrate *m,
 				 bool write_to_ccs);
 #endif
 
+void xe_migrate_ulls_enter(struct xe_migrate *m);
+
 #endif
diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
index deb33e85e6eb..a1081349a6d5 100644
--- a/drivers/gpu/drm/xe/xe_pt.c
+++ b/drivers/gpu/drm/xe/xe_pt.c
@@ -1428,7 +1428,7 @@ static int xe_pt_vm_dependencies(struct xe_sched_job *job,
 	if (!job && !no_in_syncs(vops->syncs, vops->num_syncs))
 		return -ETIME;
 
-	if (!job && !xe_exec_queue_is_idle(vops->q))
+	if (!job && !xe_exec_queue_is_idle(vops->q, 0))
 		return -ETIME;
 
 	if (vops->flags & (XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP |
diff --git a/drivers/gpu/drm/xe/xe_sched_job_types.h b/drivers/gpu/drm/xe/xe_sched_job_types.h
index 9f527ac6df3e..db41f5388dd0 100644
--- a/drivers/gpu/drm/xe/xe_sched_job_types.h
+++ b/drivers/gpu/drm/xe/xe_sched_job_types.h
@@ -91,6 +91,12 @@ struct xe_sched_job {
 	bool last_replay;
 	/** @is_pt_job: is a PT job */
 	bool is_pt_job;
+	/** @is_ulls: is ULLS job */
+	bool is_ulls;
+	/** @is_ulls_first: is first ULLS job */
+	bool is_ulls_first;
+	/** @is_ulls_last: is last ULLS job */
+	bool is_ulls_last;
 	union {
 		/** @ptrs: per instance pointers. */
 		DECLARE_FLEX_ARRAY(struct xe_job_ptrs, ptrs);
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index a6dc010e5e0d..0e6ec05de551 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -148,7 +148,7 @@ static bool xe_vm_is_idle(struct xe_vm *vm)
 
 	xe_vm_assert_held(vm);
 	list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
-		if (!xe_exec_queue_is_idle(q))
+		if (!xe_exec_queue_is_idle(q, 0))
 			return false;
 	}
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 21/25] drm/xe: Add ULLS migration job support to ring ops
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (19 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission Matthew Brost
                   ` (6 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add preamble and postamble for ULLS migrations jobs. Preamble clears
current semaphore for reuse. Postamble waits on next semaphore which is
set upon next job submission. The last ULLS migration job skips BB
submission and postamble (clear current semaphore, write seqno, exit
ULLS).

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-23-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_ring_ops.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/drivers/gpu/drm/xe/xe_ring_ops.c b/drivers/gpu/drm/xe/xe_ring_ops.c
index 39a670e91ba7..f613c694d096 100644
--- a/drivers/gpu/drm/xe/xe_ring_ops.c
+++ b/drivers/gpu/drm/xe/xe_ring_ops.c
@@ -494,6 +494,28 @@ static void __emit_job_gen12_render_compute(struct xe_sched_job *job,
 	xe_lrc_write_ring(lrc, dw, i * sizeof(*dw));
 }
 
+static int emit_ulls_preamble(struct xe_lrc *lrc, u32 *dw, int i, u32 seqno)
+{
+	u32 addr = xe_lrc_ulls_semaphore_ggtt_addr(lrc, seqno);
+
+	return emit_store_imm_ggtt(addr, LRC_MIGRATION_ULLS_SEMAPHORE_CLEAR,
+				   dw, i);
+}
+
+static int emit_ulls_postamble(struct xe_lrc *lrc, u32 *dw, int i, u32 seqno)
+{
+	dw[i++] = MI_SEMAPHORE_WAIT |
+		MI_SEMW_GGTT |
+		MI_SEMW_POLL |
+		MI_SEMW_COMPARE(SAD_EQ_SDD);
+	dw[i++] = LRC_MIGRATION_ULLS_SEMAPHORE_SIGNAL;
+	dw[i++] = xe_lrc_ulls_semaphore_ggtt_addr(lrc, seqno + 1);
+	dw[i++] = 0;
+	dw[i++] = 0;
+
+	return i;
+}
+
 static void emit_migration_job_gen12(struct xe_sched_job *job,
 				     struct xe_lrc *lrc, u32 *head,
 				     u32 seqno)
@@ -507,10 +529,16 @@ static void emit_migration_job_gen12(struct xe_sched_job *job,
 
 	xe_gt_assert(gt, !job->ring_ops_force_reset);
 
+	if (job->is_ulls)
+		i = emit_ulls_preamble(lrc, dw, i, seqno);
+
 	i = emit_copy_timestamp(xe, lrc, dw, i);
 
 	i = emit_store_imm_ggtt(saddr, seqno, dw, i);
 
+	if (job->is_ulls_last || job->is_ulls_first)
+		goto seqno_write;
+
 	dw[i++] = MI_ARB_ON_OFF | MI_ARB_DISABLE; /* Enabled again below */
 
 	i = emit_bb_start(job->ptrs[0].batch_addr, BIT(8), dw, i);
@@ -521,12 +549,16 @@ static void emit_migration_job_gen12(struct xe_sched_job *job,
 
 	i = emit_bb_start(job->ptrs[1].batch_addr, BIT(8), dw, i);
 
+seqno_write:
 	i = emit_flush_imm_ggtt(xe_lrc_seqno_ggtt_addr(lrc), seqno,
 				job->migrate_flush_flags,
 				dw, i);
 
 	i = emit_user_interrupt(dw, i);
 
+	if (job->is_ulls && !job->is_ulls_last)
+		i = emit_ulls_postamble(lrc, dw, i, seqno);
+
 	xe_gt_assert(job->q->gt, i <= MAX_JOB_SIZE_DW);
 
 	xe_lrc_write_ring(lrc, dw, i * sizeof(*dw));
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (20 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 21/25] drm/xe: Add ULLS migration job support to ring ops Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:38   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch Matthew Brost
                   ` (5 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add ULLS migration job support to GuC submission backend.

Changes required:
- On migration queue, reduce max jobs to the number of ULLS semaphores
  minus one
- Directly set the hardware engine tail via a MMIO write for ULLS jobs
  except for first ULLS job
- Set ULLS sempahore for current job releasing last job except for first
  ULLS job
- Suppress submit H2G for ULLS except for first ULLS job

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-24-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_guc_submit.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index dcb4b8a4f3b7..1578d80b01b8 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -1207,6 +1207,11 @@ static void submit_exec_queue(struct xe_exec_queue *q, struct xe_sched_job *job)
 	if (exec_queue_suspended(q))
 		return;
 
+	if (job->is_ulls && !job->is_ulls_first) {
+		xe_hw_engine_write_ring_tail(q->hwe, lrc->ring.tail);
+		xe_lrc_set_ulls_semaphore(lrc, xe_sched_job_lrc_seqno(job));
+	}
+
 	if (!exec_queue_enabled(q)) {
 		action[len++] = XE_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
 		action[len++] = q->guc->id;
@@ -1220,13 +1225,14 @@ static void submit_exec_queue(struct xe_exec_queue *q, struct xe_sched_job *job)
 		set_exec_queue_pending_enable(q);
 		set_exec_queue_enabled(q);
 		trace_xe_exec_queue_scheduling_enable(q);
-	} else {
+	} else if (!job->is_ulls || job->is_ulls_first) {
 		action[len++] = XE_GUC_ACTION_SCHED_CONTEXT;
 		action[len++] = q->guc->id;
 		trace_xe_exec_queue_submit(q);
 	}
 
-	xe_guc_ct_send(&guc->ct, action, len, g2h_len, num_g2h);
+	if (!job->is_ulls || job->is_ulls_first || num_g2h)
+		xe_guc_ct_send(&guc->ct, action, len, g2h_len, num_g2h);
 
 	if (extra_submit) {
 		len = 0;
@@ -2088,6 +2094,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
 	struct xe_guc_exec_queue *ge;
 	long timeout;
 	int err, i;
+	int max_jobs = (xe_lrc_ring_size() / MAX_JOB_SIZE_BYTES);
 
 	xe_gt_assert(guc_to_gt(guc), xe_device_uc_enabled(guc_to_xe(guc)));
 
@@ -2127,8 +2134,11 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
 		submit_wq = primary->guc->sched.base.submit_wq;
 	}
 
+	if (q->vm && q->vm->flags & XE_VM_FLAG_MIGRATION)
+		max_jobs = min(max_jobs, LRC_MIGRATION_ULLS_SEMAPHORE_COUNT - 1);
+
 	err = xe_sched_init(&ge->sched, &drm_sched_ops, &xe_sched_ops,
-			    submit_wq, xe_lrc_ring_size() / MAX_JOB_SIZE_BYTES, 64,
+			    submit_wq, max_jobs, 64,
 			    timeout, guc_to_gt(guc)->ordered_wq, NULL,
 			    ge->name, gt_to_xe(q->gt)->drm.dev);
 	if (err)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (21 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:28   ` sashiko-bot
  2026-09-03 23:58 ` [PATCH v4 24/25] drm/xe: Add modparam to enable / disable ULLS on migrate queue Matthew Brost
                   ` (4 subsequent siblings)
  27 siblings, 1 reply; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Call xe_migration_ulls_enter upon page fault or SVM prefetch in an
effort speed up these critical paths.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-25-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_migrate.c   | 4 ++--
 drivers/gpu/drm/xe/xe_pagefault.c | 3 +++
 drivers/gpu/drm/xe/xe_vm.c        | 4 +++-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 1fa236eb1a26..80efa67fc2e1 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -114,8 +114,8 @@ static void xe_migrate_fini(void *arg)
 	disable_delayed_work_sync(&m->ulls.exit_work);
 	mutex_lock(&m->job_mutex);
 	if (m->ulls.enabled) {
-		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
 		xe_pm_runtime_put(xe);
+		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
 		m->ulls.enabled = false;
 	}
 	mutex_unlock(&m->job_mutex);
@@ -589,8 +589,8 @@ static void xe_migrate_ulls_exit(struct work_struct *work)
 		xe_sched_job_put(job);
 	if (!m->ulls.enabled) {
 		/* Pairs with PM gets on enter */
-		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
 		xe_pm_runtime_put(xe);
+		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
 
 		cancel_delayed_work(&m->ulls.exit_work);
 		xe_dbg(xe, "Migrate ULLS mode exit");
diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c
index e81ca24df37f..ecf2530f0284 100644
--- a/drivers/gpu/drm/xe/xe_pagefault.c
+++ b/drivers/gpu/drm/xe/xe_pagefault.c
@@ -15,6 +15,7 @@
 #include "xe_gt_stats.h"
 #include "xe_hw_engine.h"
 #include "xe_log.h"
+#include "xe_migrate.h"
 #include "xe_pagefault.h"
 #include "xe_pagefault_types.h"
 #include "xe_svm.h"
@@ -262,6 +263,8 @@ static int xe_pagefault_service(struct xe_pagefault *pf)
 	if (IS_ERR(vm))
 		return PTR_ERR(vm);
 
+	xe_migrate_ulls_enter(gt_to_tile(gt)->migrate);
+
 	down_read(&vm->lock);
 
 	if (xe_vm_is_closed(vm)) {
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 0e6ec05de551..25425b57ceaa 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -2526,8 +2526,10 @@ vm_bind_ioctl_ops_create(struct xe_vm *vm, struct xe_vma_ops *vops,
 			ctx.devmem_possible = IS_DGFX(vm->xe) &&
 					      IS_ENABLED(CONFIG_DRM_XE_PAGEMAP);
 
-			for_each_tile(tile, vm->xe, id)
+			for_each_tile(tile, vm->xe, id) {
+				xe_migrate_ulls_enter(tile->migrate);
 				tile_mask |= 0x1 << id;
+			}
 
 			if (prefetch_region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC) {
 				dpagemap = xe_vma_resolve_pagemap(vma,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 24/25] drm/xe: Add modparam to enable / disable ULLS on migrate queue
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (22 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-03 23:58 ` [PATCH v4 25/25] drm/xe: Document ULLS for migration jobs Matthew Brost
                   ` (3 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Having modparam to enable / disable ULLS on migrate queue will help with
quick experiments.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260228013501.106680-26-matthew.brost@intel.com
Signed-off-by: Maarten Lankhorst <dev@lankhorst.se>
---
 drivers/gpu/drm/xe/xe_debugfs.c      | 1 +
 drivers/gpu/drm/xe/xe_defaults.h     | 1 +
 drivers/gpu/drm/xe/xe_device.c       | 1 +
 drivers/gpu/drm/xe/xe_device_types.h | 5 +++++
 drivers/gpu/drm/xe/xe_migrate.c      | 2 +-
 drivers/gpu/drm/xe/xe_module.c       | 4 ++++
 drivers/gpu/drm/xe/xe_module.h       | 1 +
 7 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c
index 28135f84e286..a2d92729ed49 100644
--- a/drivers/gpu/drm/xe/xe_debugfs.c
+++ b/drivers/gpu/drm/xe/xe_debugfs.c
@@ -173,6 +173,7 @@ static int info(struct seq_file *m, void *data)
 	drm_printf(&p, "revid %d\n", xe->info.revid);
 	drm_printf(&p, "tile_count %d\n", xe->info.tile_count);
 	drm_printf(&p, "vm_max_level %d\n", xe->info.vm_max_level);
+	drm_printf(&p, "ulls_enable %s\n", str_yes_no(xe->info.ulls_enable));
 	drm_printf(&p, "has_flat_ccs %s\n", str_yes_no(xe->info.has_flat_ccs));
 	drm_printf(&p, "has_usm %s\n", str_yes_no(xe->info.has_usm));
 	drm_printf(&p, "skip_guc_pc %s\n", str_yes_no(xe->info.skip_guc_pc));
diff --git a/drivers/gpu/drm/xe/xe_defaults.h b/drivers/gpu/drm/xe/xe_defaults.h
index 0884224ef7c7..9c7e89a169c5 100644
--- a/drivers/gpu/drm/xe/xe_defaults.h
+++ b/drivers/gpu/drm/xe/xe_defaults.h
@@ -14,6 +14,7 @@
 #endif
 
 #define XE_DEFAULT_PROBE_DISPLAY		IS_ENABLED(CONFIG_DRM_XE_DISPLAY)
+#define XE_DEFAULT_ULLS_ENABLE			true
 #define XE_DEFAULT_VRAM_BAR_SIZE		0
 #define XE_DEFAULT_FORCE_PROBE			CONFIG_DRM_XE_FORCE_PROBE
 #define XE_DEFAULT_MAX_VFS			~0
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index ade971de1652..9b81759af28a 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -520,6 +520,7 @@ static void xe_device_parse_modparam(struct xe_device *xe)
 	xe->atomic_svm_timeslice_ms = 5;
 	xe->min_run_period_lr_ms = 5;
 	xe->info.num_pf_work = xe_modparam.num_pf_work;
+	xe->info.ulls_enable = xe_modparam.ulls_enable;
 	if (xe->info.num_pf_work < 1)
 		xe->info.num_pf_work = 1;
 	else if (xe->info.num_pf_work > XE_PAGEFAULT_WORK_MAX)
diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
index 997ac82fd571..fd4560378f5b 100644
--- a/drivers/gpu/drm/xe/xe_device_types.h
+++ b/drivers/gpu/drm/xe/xe_device_types.h
@@ -245,6 +245,11 @@ struct xe_device {
 		u8 skip_pcode:1;
 		/** @info.needs_shared_vf_gt_wq: needs shared GT WQ on VF */
 		u8 needs_shared_vf_gt_wq:1;
+		/**
+		 * @info.ulls_enable: Enable ULLS on migration queue in LR VM
+		 * open
+		 */
+		u8 ulls_enable:1;
 	} info;
 
 	/** @wa_active: keep track of active workarounds */
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 80efa67fc2e1..87a718b95eb0 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -485,7 +485,7 @@ void xe_migrate_ulls_enter(struct xe_migrate *m)
 
 	xe_assert(xe, xe->info.has_usm);
 
-	if (!IS_DGFX(xe) || IS_SRIOV_VF(xe))
+	if (!IS_DGFX(xe) || IS_SRIOV_VF(xe) || !xe->info.ulls_enable)
 		return;
 
 job_alloc:
diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index 4bc28dfc1992..a8b546550ead 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -23,6 +23,7 @@
 
 struct xe_modparam xe_modparam = {
 	.probe_display =	XE_DEFAULT_PROBE_DISPLAY,
+	.ulls_enable =		XE_DEFAULT_ULLS_ENABLE,
 	.guc_log_level =	XE_DEFAULT_GUC_LOG_LEVEL,
 	.force_probe =		XE_DEFAULT_FORCE_PROBE,
 #ifdef CONFIG_PCI_IOV
@@ -44,6 +45,9 @@ MODULE_PARM_DESC(probe_display, "Probe display HW, otherwise it's left untouched
 		 "[default=" __stringify(XE_DEFAULT_PROBE_DISPLAY) "])");
 #endif
 
+module_param_named(ulls_enable, xe_modparam.ulls_enable, bool, 0444);
+MODULE_PARM_DESC(ulls_enable, "Enable ULLS on migration queue if LR VM open (default: true)");
+
 module_param_named(vram_bar_size, xe_modparam.force_vram_bar_size, int, 0600);
 MODULE_PARM_DESC(vram_bar_size, "Set the vram bar size in MiB (<0=disable-resize, 0=max-needed-size, >0=force-size "
 		 "[default=" __stringify(XE_DEFAULT_VRAM_BAR_SIZE) "])");
diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h
index 6272d9e41207..5d88448c298a 100644
--- a/drivers/gpu/drm/xe/xe_module.h
+++ b/drivers/gpu/drm/xe/xe_module.h
@@ -13,6 +13,7 @@ struct work_struct;
 /* Module modprobe variables */
 struct xe_modparam {
 	bool probe_display;
+	bool ulls_enable;
 	int force_vram_bar_size;
 	int guc_log_level;
 	char *guc_firmware_path;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v4 25/25] drm/xe: Document ULLS for migration jobs
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (23 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 24/25] drm/xe: Add modparam to enable / disable ULLS on migrate queue Matthew Brost
@ 2026-09-03 23:58 ` Matthew Brost
  2026-09-04  0:47 ` ✗ CI.checkpatch: warning for CPU binds and ULLS on migration queue (rev6) Patchwork
                   ` (2 subsequent siblings)
  27 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-03 23:58 UTC (permalink / raw)
  To: intel-xe

Add a kernel-doc DOC section at the top of xe_migrate.c describing the
Ultra Low Latency Submission (ULLS) scheme used for migration jobs.

Cover the motivation (removing the H2G / GuC / context switch latency
from the page fault and SVM prefetch critical paths), the platform
requirements, the LRC PPHWSP semaphore layout and its relationship to
the migration queue job count, the ring preamble / postamble emitted by
the ring ops, the MMIO tail write submission fast path in the GuC
backend, and the enter / delayed exit flow along with the ULLS job
flags.

Hook the new section into Documentation/gpu/xe/xe_migrate.rst.

Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Assisted-by: Github-Copilot:Claude-opus-5
---
 Documentation/gpu/xe/xe_migrate.rst |  3 +
 drivers/gpu/drm/xe/xe_migrate.c     | 99 +++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+)

diff --git a/Documentation/gpu/xe/xe_migrate.rst b/Documentation/gpu/xe/xe_migrate.rst
index f92faec0ac94..d297ee53a582 100644
--- a/Documentation/gpu/xe/xe_migrate.rst
+++ b/Documentation/gpu/xe/xe_migrate.rst
@@ -6,3 +6,6 @@ Migrate Layer
 
 .. kernel-doc:: drivers/gpu/drm/xe/xe_migrate_doc.h
    :doc: Migrate Layer
+
+.. kernel-doc:: drivers/gpu/drm/xe/xe_migrate.c
+   :doc: ULLS (Ultra Low Latency Submission) for migration jobs
diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
index 87a718b95eb0..66a47a012ba1 100644
--- a/drivers/gpu/drm/xe/xe_migrate.c
+++ b/drivers/gpu/drm/xe/xe_migrate.c
@@ -48,6 +48,105 @@
 #include "xe_vm.h"
 #include "xe_vram.h"
 
+/**
+ * DOC: ULLS (Ultra Low Latency Submission) for migration jobs
+ *
+ * Migration jobs issued on behalf of GPU page faults and SVM prefetches sit
+ * directly in the critical path of a stalled GPU workload. The dominant cost
+ * of such a job is not the copy or clear itself but the submission latency:
+ * the H2G round trip to GuC, the GuC scheduling decision, and the hardware
+ * context switch required to place the migration LRC on an engine.
+ *
+ * ULLS removes that cost by keeping the migration context resident and
+ * *running* on the hardware engine across jobs. Instead of the ring going
+ * empty and the context being switched out between jobs, the tail of every
+ * ULLS job parks the engine on a semaphore wait for the *next* job's
+ * semaphore. Submitting the next job then only requires the CPU to write the
+ * ring tail via MMIO and signal that semaphore - no H2G, no GuC round trip,
+ * no context switch.
+ *
+ * Requirements
+ * ------------
+ *
+ * ULLS is only used on DGFX with USM support (where a hardware engine is
+ * reserved exclusively for migration jobs) and is not used on SRIOV VFs.
+ * Because the engine is spinning on a semaphore while ULLS is active, it can
+ * not be shared with user submissions. It can also be disabled at load time
+ * with the ``xe.ulls_enable`` module parameter.
+ *
+ * Semaphores
+ * ----------
+ *
+ * The semaphores live in the driver-defined portion of the migration LRC's
+ * PPHWSP (see LRC_ULLS_PPHWSP_OFFSET, mutually exclusive with the parallel
+ * submission area). There are LRC_MIGRATION_ULLS_SEMAPHORE_COUNT of them and
+ * a job's semaphore is selected by ``seqno % COUNT``, so the semaphore ring
+ * wraps with the job seqnos. To guarantee a job can never overwrite the
+ * semaphore of a job still in flight, the GuC backend caps the migration
+ * queue's scheduler job count at LRC_MIGRATION_ULLS_SEMAPHORE_COUNT - 1.
+ *
+ * Ring layout of a ULLS job
+ * -------------------------
+ *
+ * Emitted by emit_migration_job_gen12() in xe_ring_ops.c::
+ *
+ *	preamble:	clear semaphore[seqno]	(reuse for a later wrap)
+ *	<copy timestamp, start seqno store>
+ *	<batch buffer start(s)>			(skipped on first/last job)
+ *	<seqno write + user interrupt>
+ *	postamble:	wait on semaphore[seqno + 1]
+ *						(skipped on the last job)
+ *
+ * The preamble clears the current job's semaphore so it can be reused once
+ * the seqno space wraps. The postamble is what keeps the engine busy: it
+ * blocks on the *next* job's semaphore, which is only signaled when that job
+ * is actually submitted.
+ *
+ * Submission fast path
+ * --------------------
+ *
+ * In submit_exec_queue() (xe_guc_submit.c), for a ULLS job that is not the
+ * first one::
+ *
+ *	xe_hw_engine_write_ring_tail(hwe, tail);	MMIO ring tail write
+ *	xe_lrc_set_ulls_semaphore(lrc, seqno);		release previous job
+ *
+ * and the XE_GUC_ACTION_SCHED_CONTEXT H2G is suppressed entirely. The
+ * previously running job's semaphore wait is satisfied and the engine walks
+ * straight into the newly appended job.
+ *
+ * Enter / exit
+ * ------------
+ *
+ * xe_migrate_ulls_enter() is called from the page fault handler and from the
+ * SVM prefetch path, i.e. exactly where low latency migration matters. It
+ * takes a force wake reference and a PM runtime reference (the engine must
+ * stay awake while it spins), then submits a "first" ULLS job. That first job
+ * carries no batch buffer; it exists only to get the context onto the
+ * hardware through the normal GuC path and to leave the engine waiting on the
+ * next semaphore, pipelining the GuC/HW context switch out of the critical
+ * path.
+ *
+ * Keeping an engine spinning costs power, so ULLS is not left enabled
+ * indefinitely. Every enter and every ULLS job submission re-arms
+ * @xe_migrate.ulls.exit_work with a ULLS_EXIT_JIFFIES delay. When it fires
+ * with the queue idle, it submits a "last" ULLS job - again with no batch
+ * buffer and, crucially, with no postamble semaphore wait - which lets the
+ * ring drain so the context can be switched off the hardware. The force wake
+ * and PM references are then dropped. If the queue was not idle, the worker
+ * simply re-arms itself.
+ *
+ * Job flags
+ * ---------
+ *
+ * The state above is communicated to the ring ops and GuC backend via three
+ * flags on struct xe_sched_job, set under @xe_migrate.job_mutex:
+ *
+ * - @xe_sched_job.is_ulls: job is submitted while in ULLS mode
+ * - @xe_sched_job.is_ulls_first: job that entered ULLS mode
+ * - @xe_sched_job.is_ulls_last: job that exits ULLS mode
+ */
+
 /**
  * struct xe_migrate - migrate context.
  */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper
  2026-09-03 23:58 ` [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper Matthew Brost
@ 2026-09-04  0:15   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:15 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Low] Flawed assertion sequence causes kernel panic instead of warning when `update->pt_bo` is NULL.
- [Low] Redundant and meaningless assertion on `update`.
- [Low] Type mismatch for `num_ops` argument.
--

commit f45575c8533ab8798dcc0ef542bd7c22dad7e2b3
Author: Matthew Brost <matthew.brost@intel.com>
drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper

This commit adds the xe_migrate_update_pgtables_cpu_execute helper for
CPU-side page-table updates, allowing submission backends to call this
after resolving bind job dependencies. Asserts are also added to provide
basic sanity checks on function arguments.

Link: https://patch.msgid.link/20260228013501.106680-3-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> index 149c5fa654e67..084bfee0d0474 100644
> --- a/drivers/gpu/drm/xe/xe_migrate.c
> +++ b/drivers/gpu/drm/xe/xe_migrate.c
> @@ -1819,6 +1819,38 @@ struct migrate_test_params {
>  	container_of(_priv, struct migrate_test_params, base)
>  #endif
>  
> +static void
> +xe_migrate_update_pgtables_cpu_execute(struct xe_vm *vm, struct xe_tile *tile,
> +				       const struct xe_migrate_pt_update_ops *ops,
> +				       struct xe_vm_pgtable_update_op *pt_op,
> +				       int num_ops)

[Severity: Low]
Is this type mismatch intentional? The source pt_update_ops->num_ops and
the loop counter j below are both defined as u32, but the helper function
takes int num_ops. This causes a narrowing conversion and a mixed-type
comparison in the loop.

> +{
> +	u32 j, i;
> +
> +	for (j = 0; j < num_ops; ++j, ++pt_op) {
> +		for (i = 0; i < pt_op->num_entries; i++) {
> +			const struct xe_vm_pgtable_update *update =
> +				&pt_op->entries[i];
> +
> +			xe_tile_assert(tile, update);

[Severity: Low]
Does this assertion provide any validation? The update pointer is derived by
taking the address of an array element (&pt_op->entries[i]). This is
mathematically guaranteed to be non-NULL, making the assertion a tautology.

> +			xe_tile_assert(tile, update->pt_bo);
> +			xe_tile_assert(tile, !iosys_map_is_null(&update->pt_bo->vmap));

[Severity: Low]
Can this sequence cause an unintended kernel panic?

If update->pt_bo is NULL, the first assertion will trigger a warning and
execution will continue. The next assertion evaluates &update->pt_bo->vmap
(calculating an offset from NULL, producing an invalid pointer) and passes it
to iosys_map_is_null(). This function dereferences the invalid pointer to
check map->is_iomem, which would cause a page fault panic, defeating the
non-fatal nature of xe_tile_assert.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=2

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
  2026-09-03 23:58 ` [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC Matthew Brost
@ 2026-09-04  0:18   ` sashiko-bot
  2026-09-04  0:41     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:18 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The XE_BO_FLAG_PUT_VM_ASYNC mechanism causes VM reference leaks if xe_bo_put_async() is called when the BO's refcount is greater than 1.
- [High] Dropping the VM reference synchronously in xe_bo_put_commit causes a Use-After-Free of the shared dma_resv object if TTM defers destruction.
--

commit 73ce65401a8053bf565b7b064086594d1dafb49a
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC

This commit introduces XE_BO_FLAG_PUT_VM_ASYNC, which indicates that an
async BO put must also drop an additional reference to the BO's VM. This
ensures the shared dma-resv object does not disappear before the BO when
a kernel BO is put asynchronously.

Link: https://patch.msgid.link/20260228013501.106680-7-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
> index dde309821237b..5cde5dff2d481 100644
> --- a/drivers/gpu/drm/xe/xe_bo.c
> +++ b/drivers/gpu/drm/xe/xe_bo.c
> @@ -3989,8 +3989,14 @@ void xe_bo_put_commit(struct llist_head *deferred)
>  	if (!freed)
>  		return;
>  
> -	llist_for_each_entry_safe(bo, next, freed, freed)
> +	llist_for_each_entry_safe(bo, next, freed, freed) {
> +		struct xe_vm *vm = bo->vm;
> +		bool async = bo->flags & XE_BO_FLAG_PUT_VM_ASYNC;
> +
>  		drm_gem_object_free(&bo->ttm.base.refcount);
> +		if (async)
> +			xe_vm_put(vm);

[Severity: High]
Does this code leak the VM reference if xe_bo_put_async() is called when the
BO's refcount is greater than 1?

If xe_bo_put_async() is called while concurrent paths (like show_meminfo or
xe_bo_evict_all) hold a temporary reference, the BO won't be added to the
async_list.

Later, when the temporary reference is dropped via a synchronous xe_bo_put(),
the BO is destroyed by xe_ttm_bo_destroy(), which only drops bo->vm if
xe_bo_is_user(bo) is true (which is false for kernel PT BOs). Thus, the VM
reference could be permanently leaked.

[Severity: High]
Can dropping the VM reference synchronously here cause a use-after-free of
the shared dma_resv object if TTM defers destruction?

In xe_bo_put_commit(), drm_gem_object_free() drops the GEM reference. If TTM
concurrently holds an extra reference to the BO (e.g., via
ttm_bo_get_unless_zero() during an LRU walk), the BO will not be immediately
destroyed.

However, xe_bo_put_commit() continues and calls xe_vm_put(vm). If this drops
the last reference to the VM, the VM and its embedded resv are freed.
Because the BO is still alive and bo->ttm.base.resv points to the freed
vm->resv, TTM might later attempt to use the freed reservation object.

> +	}
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=6

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update
  2026-09-03 23:58 ` [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update Matthew Brost
@ 2026-09-04  0:19   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:19 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Silent failure during page-table unbind due to unhandled -ENOMEM can lead to GPU Use-After-Free.
--

commit e87f781059d52f2b1352d4dadd77d587b14fe864
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Store level in struct xe_vm_pgtable_update

This commit stores the level directly in struct xe_vm_pgtable_update to
avoid accessing struct xe_pt during bind jobs. This is necessary because
struct xe_pt may no longer be valid in memory when CPU binds are supported
in bind jobs.

Link: https://patch.msgid.link/20260228013501.106680-12-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
> index 4c277153398e6..486de8a46a6a5 100644
> --- a/drivers/gpu/drm/xe/xe_pt.c
> +++ b/drivers/gpu/drm/xe/xe_pt.c
> @@ -389,6 +389,7 @@ xe_pt_new_shared(struct xe_walk_update *wupd, struct xe_pt *parent,
>  	entry->flags = 0;
>  	entry->qwords = 0;
>  	entry->pt_bo->update_index = -1;
> +	entry->level = parent->level;
>  
>  	if (alloc_entries) {
>  		entry->pt_entries = kmalloc_objs(*entry->pt_entries, XE_PDES);

[Severity: Critical]
This is a pre-existing issue, but can this silent failure during page-table
unbind lead to a GPU use-after-free?

When unbind_op_prepare() calls xe_pt_stage_unbind(), the unbind uses
xe_pt_walk_shared() which eventually calls xe_pt_new_shared() with
alloc_entries=true. If kmalloc_objs() fails here, -ENOMEM is returned up to
xe_pt_walk_shared(), terminating the walk early.

However, xe_pt_stage_unbind() ignores this failure by casting the return
value to (void):

drivers/gpu/drm/xe/xe_pt.c:xe_pt_stage_unbind() {
	...
	(void)xe_pt_walk_shared(&pt->base, pt->level, start, end,
				&xe_walk.base);
	...
}

Because the error is swallowed and a truncated update array is returned, the
unbind only partially unmaps the target VMA. The GPU page table retains valid
mappings to memory that is subsequently freed by the CPU.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=11

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs
  2026-09-03 23:58 ` [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs Matthew Brost
@ 2026-09-04  0:25   ` sashiko-bot
  0 siblings, 0 replies; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:25 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Type Confusion in xe_sched_job union leads to kernel pointer leak via trace events and devcoredump
--

commit 6a6fd5ea520b6b9671aa39ce12a2edcd42ab0033
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Update scheduler job layer to support PT jobs

This commit updates the scheduler job layer to support page table (PT) jobs,
which run entirely on the CPU. It reuses the LRC fence storage by introducing
a union in the xe_sched_job struct to hold PT update arguments when the job
does not require an LRC fence or batch address.

Link: https://patch.msgid.link/20260228013501.106680-8-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_sched_job_types.h b/drivers/gpu/drm/xe/xe_sched_job_types.h
> index 0490b1247a6e9..5e1824c36c740 100644
> --- a/drivers/gpu/drm/xe/xe_sched_job_types.h
> +++ b/drivers/gpu/drm/xe/xe_sched_job_types.h

[ ... ]

> @@ -71,8 +90,14 @@ struct xe_sched_job {
>  	bool restore_replay;
>  	/** @last_replay: last job being replayed */
>  	bool last_replay;
> -	/** @ptrs: per instance pointers. */
> -	struct xe_job_ptrs ptrs[];
> +	/** @is_pt_job: is a PT job */
> +	bool is_pt_job;
> +	union {
> +		/** @ptrs: per instance pointers. */
> +		DECLARE_FLEX_ARRAY(struct xe_job_ptrs, ptrs);
> +		/** @pt_update: PT update arguments */
> +		DECLARE_FLEX_ARRAY(struct xe_pt_update_args, pt_update);
> +	};
>  };
>  

[Severity: Medium]
Does this regression cause a kernel pointer leak when the job is a PT job?

In struct xe_job_ptrs, batch_addr is a 64-bit value that aliases with the
const struct xe_migrate_pt_update_ops *ops pointer in struct
xe_pt_update_args.

When xe_sched_job_snapshot_capture() in drivers/gpu/drm/xe/xe_sched_job.c
captures a snapshot, it appears to unconditionally read batch_addr without
checking if the job is a PT job:

for (i = 0; i < q->width; i++)
	snapshot->batch_addr[i] =
		xe_device_uncanonicalize_addr(xe, job->ptrs[i].batch_addr);

Similarly, the xe_sched_job trace event in drivers/gpu/drm/xe/xe_trace.h
unconditionally reads batch_addr:

__entry->batch_addr = (u64)job->ptrs[0].batch_addr;

Can this type confusion expose kernel function pointers to userspace via
trace events or sysfs devcoredump during a GPU hang?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=7

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer
  2026-09-03 23:58 ` [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer Matthew Brost
@ 2026-09-04  0:27   ` sashiko-bot
  2026-09-04  1:35     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:27 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Waiting on a dma_fence while holding a reclaim-tainted mutex causes deadlocks and lockdep splats.
- [Medium] ULLS mode exit delay is implemented as 20ms instead of the intended 5us.
--

commit 68ec8c46273b3023414fee593faab1cdb6c24eca
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Add ULLS migration job support to migration layer

This commit adds a function to enter ULLS mode for migration jobs and a
delayed worker to exit it for power savings. The ULLS mode exit delay is
intended to be 5us. It also updates migration job flags so the submission
backend properly submits in ULLS mode.

Link: https://patch.msgid.link/20260228013501.106680-21-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> --- a/drivers/gpu/drm/xe/xe_migrate.c
> +++ b/drivers/gpu/drm/xe/xe_migrate.c
> @@ -77,6 +80,14 @@ struct xe_migrate {
>  	struct dma_fence *fence;
>  	/** @min_chunk_size: For dgfx, Minimum chunk size */
>  	u64 min_chunk_size;
> +	/** @ulls: ULLS support */
> +	struct {
> +		/** @ulls.enabled: ULLS is enabled */
> +		bool enabled;
> +#define ULLS_EXIT_JIFFIES	(HZ / 50)

[Severity: Medium]
The commit message states "ULLS mode exit delay is currently set to 5us", but
does this macro actually result in 5us? 

Using (HZ / 50) yields 20 milliseconds on a 1000Hz kernel, and 20 milliseconds
on a 100Hz kernel (since 100/50 is 2 jiffies). Both evaluate to 20,000us
instead of 5us.

Additionally, delayed_work uses jiffy-resolution timers which cannot achieve
microsecond precision. Will this cause the engine to remain awake and consume
power for 4000x longer than intended?

> +		/** @ulls.exit_work: ULLS exit worker */
> +		struct delayed_work exit_work;
> +	} ulls;
>  };

[ ... ]

> +static void xe_migrate_ulls_exit(struct work_struct *work)
> +{

[ ... ]

> +	mutex_lock(&m->job_mutex);
> +
> +	if (!xe_exec_queue_is_idle(m->q, 1))
> +		goto unlock_exit;
> +
> +	xe_sched_job_get(job);
> +	xe_sched_job_arm(job);
> +	job->is_ulls = true;
> +	job->is_ulls_last = true;
> +	fence = dma_fence_get(&job->drm.s_fence->finished);
> +	xe_sched_job_push(job);
> +
> +	/* Serialize force wake put */
> +	dma_fence_wait(fence, false);

[Severity: High]
Can calling dma_fence_wait() here while holding m->job_mutex cause a deadlock?

The job_mutex is initialized with fs_reclaim_acquire(), which marks it as
participating in memory reclaim paths. Waiting on a dma_fence while holding
a reclaim-tainted lock is generally unsafe because fence signaling paths
cannot safely allocate memory without risking a forward-progress dependency
inversion.

If a page fault blocks on job_mutex (e.g. via xe_migrate_ulls_enter) while
a GPU reset that requires memory reclaim is pending, could this deadlock the
system and trigger a lockdep splat?

> +	dma_fence_put(fence);
> +
> +	m->ulls.enabled = false;
> +unlock_exit:
> +	if (job)
> +		xe_sched_job_put(job);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=20

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
  2026-09-03 23:58 ` [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch Matthew Brost
@ 2026-09-04  0:28   ` sashiko-bot
  2026-09-04  1:32     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:28 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Incorrect ordering of xe_pm_runtime_put() and xe_force_wake_put() leading to hardware MMIO access on a potentially suspended device.
--

commit 43d7d496e886ff3c7ea66fac7a62b351eae1e2b2
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch

This commit calls xe_migrate_ulls_enter upon page fault or SVM prefetch in an
effort to speed up these critical paths. It also adjusts the teardown and exit
sequences for ULLS migration jobs.

Link: https://patch.msgid.link/20260228013501.106680-25-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> index 1fa236eb1a260..80efa67fc2e15 100644
> --- a/drivers/gpu/drm/xe/xe_migrate.c
> +++ b/drivers/gpu/drm/xe/xe_migrate.c
> @@ -114,8 +114,8 @@ static void xe_migrate_fini(void *arg)
>  	disable_delayed_work_sync(&m->ulls.exit_work);
>  	mutex_lock(&m->job_mutex);
>  	if (m->ulls.enabled) {
> -		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
>  		xe_pm_runtime_put(xe);
> +		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);

[Severity: High]
Does swapping the order here risk accessing hardware MMIO while the device
is suspended?

Because xe_pm_runtime_put() drops the runtime PM usage counter, it could
trigger a synchronous or asynchronous runtime suspend. If the device suspends
before xe_force_wake_put() completes its MMIO writes to release the forcewake
domain, it could result in PCI Unsupported Request (UR) errors or device hangs.

>  		m->ulls.enabled = false;
>  	}
>  	mutex_unlock(&m->job_mutex);
> @@ -589,8 +589,8 @@ static void xe_migrate_ulls_exit(struct work_struct *work)
>  		xe_sched_job_put(job);
>  	if (!m->ulls.enabled) {
>  		/* Pairs with PM gets on enter */
> -		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
>  		xe_pm_runtime_put(xe);
> +		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);

[Severity: High]
Is this order change safe from a runtime PM perspective? 

Similar to the sequence in xe_migrate_fini(), dropping the PM reference in
xe_migrate_ulls_exit() before releasing the forcewake domain might allow the
device to suspend while xe_force_wake_put() is still accessing hardware MMIO
registers.

>  
>  		cancel_delayed_work(&m->ulls.exit_work);
>  		xe_dbg(xe, "Migrate ULLS mode exit");

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=23

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles
  2026-09-03 23:58 ` [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles Matthew Brost
@ 2026-09-04  0:29   ` sashiko-bot
  2026-09-04  1:33     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:29 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Incorrect tile mask generation causes missing TLB invalidations on secondary tiles
- [High] Shallow copy of Page Reclaim List (PRL) leads to Double Free during TLB invalidation
- [Medium] Unnecessary allocation of unused page table roots and scratch tables
--

commit 084f96016724c31d7bcd97e7c49979198c29fe71
Author: Matthew Brost <matthew.brost@intel.com>
Subject: drm/xe: Add device flag to enable PT mirroring across tiles

Some multi-tile devices may want to mirror page tables across tiles for
memory-bandwidth reasons, while others may not. Add a device flag that
allows enabling or disabling page-table mirroring across tiles.

Link: https://patch.msgid.link/20260228013501.106680-18-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
> index 821edeaecaed9..deb33e85e6eb7 100644
> --- a/drivers/gpu/drm/xe/xe_pt.c
> +++ b/drivers/gpu/drm/xe/xe_pt.c
[ ... ]
> @@ -2542,9 +2553,20 @@ int xe_pt_update_ops_prepare(struct xe_device *xe, struct xe_vma_ops *vops)
>  	int id, err;
>  
>  	for_each_tile(tile, xe, id) {
> +		struct xe_vm_pgtable_update_ops *pt_update_ops =
> +			&vops->pt_update_ops[id];
> +
>  		if (!vops->pt_update_ops[id].num_ops)
>  			continue;
>  
> +		if (pt_mirroring_disabled_for_tile(vops->vm, id)) {
> +			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
> +
> +			/* Transfer root PT update ops PRL to current */
> +			*prl = vops->pt_update_ops[0].prl;

[Severity: High]
Does this shallow copy of the Page Reclaim List (PRL) cause a double free?

By copying the structure directly without incrementing a reference count,
multiple tiles now hold a pointer to the same page. Later, when
xe_tlb_inval_job_create() adds this to jobs in xe_pt_update_ops_run(), both
jobs take ownership. When the jobs finish, they will both call
xe_page_reclaim_entries_put(), freeing the same page twice.

> +			continue;
> +		}
> +
>  		err = __xe_pt_update_ops_prepare(tile, vops);
>  		if (err)
>  			return err;
[ ... ]
> diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
> index 33ef4ccc3550b..d9a9c0844c5e3 100644
> --- a/drivers/gpu/drm/xe/xe_vm.c
> +++ b/drivers/gpu/drm/xe/xe_vm.c
> @@ -844,6 +844,14 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
>  	return err;
>  }
>  
> +static u8 adjust_rebind_tile_mask(struct xe_vm *vm, u8 tile_mask)
> +{
> +	if (vm->xe->info.has_pt_mirror)
> +		return tile_mask;
> +
> +	return (0x1 << vm->xe->info.max_gt_per_tile) - 1;

[Severity: High]
Is max_gt_per_tile being used here incorrectly instead of the total tile count?

If a device (like PVC) has 1 GT per tile, this evaluates to 1, causing the
generated mask to omit secondary tiles. During xe_vma_rebind(), the num_ops
will only be incremented for tile 0.

Consequently, xe_pt_update_ops_prepare() skips preparation for the secondary
tiles, leaving their start and last variables uninitialized (zeroed). When
TLB invalidation jobs are created later, they use a range of [0, 0] for
these tiles, leaving stale TLB entries that could cause memory corruption.

> +}
> +
>  struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_mask)
>  {
>  	struct dma_fence *fence = NULL;
[ ... ]
> @@ -1737,7 +1747,8 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
>  
>  		for_each_tile(tile, xe, id) {
>  			if (flags & XE_VM_FLAG_MIGRATION &&
> -			    tile->id != XE_VM_FLAG_TILE_ID(flags))
> +			    tile->id != XE_VM_FLAG_TILE_ID(flags) &&
> +			    (vm->xe->info.has_pt_mirror || id))
>  				continue;
>  
>  			vm->pt_root[id] = xe_pt_create(vm, tile, xe->info.vm_max_level,
>  						       &exec);

[Severity: Medium]
For standard user VMs, does this unconditionally allocate page table roots
and scratch tables for secondary tiles even when PT mirroring is disabled?

When has_pt_mirror is false, xe_vm_pt_root() is hardcoded to return
vm->pt_root[0]. As a result, the allocations made here for id > 0 (and their
associated scratch tables) are never used, wasting VRAM.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=17

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs
  2026-09-03 23:58 ` [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs Matthew Brost
@ 2026-09-04  0:31   ` sashiko-bot
  2026-09-04  1:04     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:31 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] A Use-After-Free of `xe_vm` occurs because CPU-based PT updates execute asynchronously on the device-global bind queue without holding a reference to the VM.
- [High] execlist backend ignores is_pt_job and submits PT jobs to hardware, skipping CPU updates.
- [High] The `xe_pt_job_ops` reference is leaked when a PT job is aborted or destroyed without running.
- [Medium] xe_sched_job_snapshot_capture reads from the wrong union member for PT jobs.
--

commit 1bc0a17fc99eb78f42df916d66ac3038c0338a83
Author: Matthew Brost <matthew.brost@intel.com>
Subject: drm/xe: Enable CPU binds for jobs

This commit updates the migration layer to formulate a PT job which will issue
CPU binds in the submission backend. All code related to GPU-based binding has
been removed.

Link: https://patch.msgid.link/20260228013501.106680-14-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> index 100e84b7b81df..62ae0e2e85be5 100644
> --- a/drivers/gpu/drm/xe/xe_migrate.c
> +++ b/drivers/gpu/drm/xe/xe_migrate.c
> @@ -1938,162 +1867,18 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
>  {

[ ... ]

>  	int err;
>  
> -	job = xe_bb_create_migration_job(pt_update_ops->q, bb,
> -					 xe_migrate_batch_base(m, usm),
> -					 update_idx);
> +	job = xe_sched_job_create(pt_update_ops->q, NULL);
>  	if (IS_ERR(job)) {
>  		err = PTR_ERR(job);
> -		goto err_sa;
> +		goto err_out;
>  	}
>  
> -	xe_sched_job_add_migrate_flush(job, MI_INVALIDATE_TLB);
> +	xe_tile_assert(tile, job->is_pt_job);

[Severity: High]
If the platform uses the execlist backend (e.g., GuC disabled), does
execlist_run_job() properly handle CPU-only PT jobs?

Looking at the execlist backend, execlist_run_job() appears to unconditionally
call q->ring_ops->emit_job(job) without checking job->is_pt_job. Because
job->ptrs and job->pt_update share a union, the ring ops might read a garbage
value (like the ops kernel pointer) as batch_addr and emit a hardware migration
job instead.

Could this regression cause an immediate GPU fault or hang from executing an
invalid address, while completely skipping the actual page table update on the
CPU?

>  
>  	if (ops->pre_commit) {
>  		pt_update->job = job;
> @@ -2104,6 +1889,12 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
>  	if (is_migrate)
>  		mutex_lock(&m->job_mutex);
>  
> +	job->pt_update[0].vm = pt_update->vops->vm;

[Severity: Critical]
Is it possible for the xe_vm pointer assigned here to be used after it is freed?

PT jobs are assigned a raw VM pointer without taking an xe_vm_get() reference.
CPU binds are queued and executed on a device-global queue (xe->cpu_bind->q).
If the user closes the VM, xe_vm_close_and_put() kills the VM's specific
queues and frees the struct xe_vm, but it does not appear to wait for jobs
pending on the global cpu_bind->q.

When the DRM scheduler later executes the pending PT job on the cpu_bind->q,
could it access the now-freed vm pointer during CPU execution, resulting in a
use-after-free regression?

[Severity: Medium]
When xe_sched_job_snapshot_capture() iterates over q->width, will it safely
handle PT jobs?

It appears that xe_sched_job_snapshot_capture() accesses job->ptrs[i].batch_addr
unconditionally. For PT jobs, job->ptrs aliases with job->pt_update via a union.

This causes batch_addr to overlap with the ops kernel pointer (or similar
members) from xe_pt_update_args. Could this mangled pointer then be
uncanonicalized and included in GPU devcoredumps, leading to corrupted state
data and a potential kernel pointer information leak?

> +	job->pt_update[0].tile = tile;
> +	job->pt_update[0].ops = ops;
> +	job->pt_update[0].pt_job_ops =
> +		xe_pt_job_ops_get(pt_update_ops->pt_job_ops);

[Severity: High]
What happens to the pt_job_ops reference if the PT job is aborted or destroyed
without running?

The job destruction path in xe_sched_job_destroy() does not seem to check for
is_pt_job, and thus skips calling xe_pt_job_ops_put(). If the job is canceled,
aborted, or its execution queue is destroyed before it runs, the DRM scheduler
bypasses the normal execution path and directly calls the free_job callback.

Would this permanently leak the pt_job_ops structure on those failure paths?

> +
>  	xe_sched_job_arm(job);
>  	fence = dma_fence_get(&job->drm.s_fence->finished);
>  	xe_sched_job_push(job);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=13

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 16/25] drm/xe: Add CPU bind layer
  2026-09-03 23:58 ` [PATCH v4 16/25] drm/xe: Add CPU bind layer Matthew Brost
@ 2026-09-04  0:31   ` sashiko-bot
  2026-09-04  1:18     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:31 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] TLB invalidation on mirrored (shared) tiles uses uninitialized `start` and `last` addresses, failing to invalidate the correctly updated address range.
- [Critical] Shallow copying of the page reclaim list (PRL) for mirrored tiles leads to a Use-After-Free and double-free of the PRL entries during TLB invalidation.
- [Critical] The arguments `tile_mask` and `usage` are swapped when calling `unbind_op_commit()` in the `DRM_GPUVA_OP_UNMAP` case, leading to un-cleared VMAs and corrupted dma-resv state.
- [High] Missing NULL check for the result of `drmm_kzalloc()` in `xe_cpu_bind_init()`.
- [High] `op_prepare` mistakenly uses `XE_VMA_OPS_FLAG_WAIT_VM_KERNEL` instead of `XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP` for REMAP operations, breaking dma-resv wait semantics.
--

commit 87e03e5ddd64ecb931de7c6186e72459d6401535
Author: Matthew Brost <matthew.brost@intel.com>
Subject: drm/xe: Add CPU bind layer

This commit introduces a dedicated CPU bind layer for the xe driver, moving
CPU bind handling out of the migrate layer since it's decoupled from
hardware. It also updates the page table layer to generate a single bind job
when pages are mirrored across tiles.

Link: https://patch.msgid.link/20260228013501.106680-17-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_cpu_bind.c b/drivers/gpu/drm/xe/xe_cpu_bind.c
> new file mode 100644
> index 0000000000000..ed79e229151b4
> --- /dev/null
> +++ b/drivers/gpu/drm/xe/xe_cpu_bind.c
> @@ -0,0 +1,295 @@
[ ... ]
> +int xe_cpu_bind_init(struct xe_device *xe)
> +{
> +	struct xe_cpu_bind *cpu_bind =
> +		drmm_kzalloc(&xe->drm, sizeof(*cpu_bind), GFP_KERNEL);
> +	struct xe_exec_queue *q;
> +
> +	q = xe_exec_queue_create_bind(xe, xe_device_get_root_tile(xe), NULL,
> +				      EXEC_QUEUE_FLAG_KERNEL |
> +				      EXEC_QUEUE_FLAG_MIGRATE, 0);
> +	if (IS_ERR(q))
> +		return PTR_ERR(q);
> +
> +	cpu_bind->xe = xe;

[Severity: High]
Does this code dereference cpu_bind without checking if drmm_kzalloc()
succeeded? If the allocation fails under memory pressure and returns NULL,
this assignment will cause a NULL pointer dereference kernel panic during
device probe.

> diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
> index 7c7cd7a0365b7..821edeaecaed9 100644
> --- a/drivers/gpu/drm/xe/xe_pt.c
> +++ b/drivers/gpu/drm/xe/xe_pt.c
[ ... ]
> @@ -2395,7 +2388,7 @@ static int op_prepare(struct xe_vm *vm,
>  
>  		err = bind_op_prepare(vm, tile, pt_update_ops, op->map.vma,
>  				      op->map.invalidate_on_bind);
> -		pt_update_ops->wait_vm_kernel = true;
> +		vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
>  		break;
>  	case DRM_GPUVA_OP_REMAP:
>  	{
> @@ -2409,12 +2402,12 @@ static int op_prepare(struct xe_vm *vm,
>  		if (!err && op->remap.prev && !op->remap.skip_prev) {
>  			err = bind_op_prepare(vm, tile, pt_update_ops,
>  					      op->remap.prev, false);
> -			pt_update_ops->wait_vm_bookkeep = true;
> +			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
>  		}
>  		if (!err && op->remap.next && !op->remap.skip_next) {
>  			err = bind_op_prepare(vm, tile, pt_update_ops,
>  					      op->remap.next, false);
> -			pt_update_ops->wait_vm_bookkeep = true;
> +			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
>  		}
>  		break;
>  	}

[Severity: High]
Are the flag updates in the DRM_GPUVA_OP_REMAP branch intentionally setting
XE_VMA_OPS_FLAG_WAIT_VM_KERNEL instead of XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP?

The original code correctly set wait_vm_bookkeep to true for remap
operations, but the updated version uses the KERNEL flag. This flips the
expected synchronization dependency semantics, which could cause REMAP
operations to incorrectly wait on KERNEL fences instead of BOOKKEEP fences,
violating the synchronization contract.

[ ... ]
> @@ -2604,21 +2624,19 @@ static void unbind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
>  
>  static void range_present_and_invalidated_tile(struct xe_vm *vm,
>  					       struct xe_svm_range *range,
> -					       u8 tile_id)
> +					       u8 tile_mask)
>  {
>  	/* All WRITE_ONCE pair with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
>  
>  	lockdep_assert_held(&vm->svm.gpusvm.notifier_lock);
>  
> -	WRITE_ONCE(range->tile_present, range->tile_present | BIT(tile_id));
> -	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~BIT(tile_id));
> +	WRITE_ONCE(range->tile_present, range->tile_present | tile_mask);
> +	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~tile_mask);
>  }
>  
> -static void op_commit(struct xe_vm *vm,
> -		      struct xe_tile *tile,
> -		      struct xe_vm_pgtable_update_ops *pt_update_ops,
> -		      struct xe_vma_op *op, struct dma_fence *fence,
> -		      struct dma_fence *fence2)
> +static void op_commit(struct xe_vm *vm, struct xe_vma_op *op,
> +		      struct dma_fence **fences, int fence_count,
> +		      enum dma_resv_usage usage, u8 tile_mask)
>  {
>  	xe_vm_assert_held(vm);
>  
[ ... ]
> @@ -2657,8 +2672,8 @@ static void op_commit(struct xe_vm *vm,
>  		struct xe_vma *vma = gpuva_to_vma(op->base.unmap.va);
>  
>  		if (!xe_vma_is_cpu_addr_mirror(vma))
> -			unbind_op_commit(vm, tile, pt_update_ops, vma, fence,
> -					 fence2);
> +			unbind_op_commit(vm, vma, fences, fence_count,
> +					 tile_mask, usage);
>  		break;
>  	}

[Severity: Critical]
Is it possible the tile_mask and usage arguments are swapped here?

Looking at the signature for unbind_op_commit(), it expects usage before
tile_mask:

unbind_op_commit(struct xe_vm *vm, struct xe_vma *vma,
                 struct dma_fence **fences, int fence_count,
                 enum dma_resv_usage usage, u8 tile_mask)

Because the arguments are passed in reverse order, the dma_resv usage enum
is treated as the tile mask. This would mean vma->tile_present &= ~tile_mask
evaluates with the enum value (e.g. DMA_RESV_USAGE_KERNEL = 0), leaving
the VMA marked fully present, and un-cleared memory could be incorrectly
freed. Simultaneously, the tile mask is passed into the dma_resv tracking
slots as a bogus usage value.

[ ... ]
> @@ -2737,82 +2739,83 @@ static struct xe_dep_scheduler *to_dep_scheduler(struct xe_exec_queue *q,
>  struct dma_fence *
> -xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
> +xe_pt_update_ops_run(struct xe_device *xe, struct xe_vma_ops *vops)
>  {
[ ... ]
> -	if (pt_update_ops->needs_invalidation) {
> -		struct xe_dep_scheduler *dep_scheduler =
> -			to_dep_scheduler(q, tile, tile->primary_gt, &type);
> -
> -		ijob = xe_tlb_inval_job_create(q, &tile->primary_gt->tlb_inval,
> -					       dep_scheduler, vm,
> -					       pt_update_ops->start,
> -					       pt_update_ops->last,
> -					       type);
> -		if (IS_ERR(ijob)) {
> -			err = PTR_ERR(ijob);
> -			goto kill_vm_tile1;
> -		}
> -		update.ijob = ijob;
> -		/*
> -		 * Only add page reclaim for the primary GT. Media GT does not have
> -		 * any PPC to flush, so enabling the PPC flush bit for media is
> -		 * effectively a NOP and provides no performance benefit nor
> -		 * interfere with primary GT.
> -		 */
> -		if (xe_page_reclaim_list_valid(&pt_update_ops->prl)) {
> -			xe_tlb_inval_job_add_page_reclaim(ijob, &pt_update_ops->prl);
> -			/* Release ref from alloc, job will now handle it */
> -			xe_page_reclaim_list_invalidate(&pt_update_ops->prl);
> -		}
> -
> -		if (tile->media_gt) {
> -			dep_scheduler = to_dep_scheduler(q, tile,
> -							 tile->media_gt, &type);
> -
> -			mjob = xe_tlb_inval_job_create(q,
> -						       &tile->media_gt->tlb_inval,
> -						       dep_scheduler, vm,
> -						       pt_update_ops->start,
> -						       pt_update_ops->last,
> -						       type);
> -			if (IS_ERR(mjob)) {
> -				err = PTR_ERR(mjob);
> +	if (needs_invalidation) {
> +		for_each_tlb_inval(q, i) {
> +			struct xe_dep_scheduler *dep_scheduler =
> +				q->tlb_inval[i].dep_scheduler;
> +			struct xe_tile *tile =
> +				&xe->tiles[i / XE_MAX_GT_PER_TILE];
> +			struct xe_vm_pgtable_update_ops *pt_update_ops =
> +				&vops->pt_update_ops[tile->id];
> +			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
> +			struct xe_tlb_inval_job *ijob;
> +			struct xe_gt *gt = i % XE_MAX_GT_PER_TILE ?
> +				tile->media_gt : tile->primary_gt;
> +
> +			ijob = xe_tlb_inval_job_create(q, &gt->tlb_inval,
> +						       dep_scheduler,
> +						       vm, pt_update_ops->start,
> +						       pt_update_ops->last, i);
> +			if (IS_ERR(ijob)) {
> +				err = PTR_ERR(ijob);
>  				goto free_ijob;
>  			}
> -			update.mjob = mjob;
> +
> +			update.ijobs[i] = ijob;
> +
> +			/*
> +			 * Only add page reclaim for the primary GT. Media GT
> +			 * does not have any PPC to flush, so enabling the PPC
> +			 * flush bit for media is effectively a NOP and provides
> +			 * no performance benefit nor interfere with primary GT.
> +			 */
> +			if (xe_page_reclaim_list_valid(prl)) {
> +				xe_tlb_inval_job_add_page_reclaim(ijob, prl);
> +				/* Release ref from alloc, job will now handle it */
> +				xe_page_reclaim_list_invalidate(prl);
> +			}
>  		}
>  	}

[Severity: Critical]
Does this code inadvertently create TLB invalidation jobs with uninitialized
bounds for secondary mirrored tiles?

When a tile shares a page table (mirroring disabled), the initialization of
pt_update_ops->start and pt_update_ops->last is skipped during the prepare
phase. Consequently, they remain 0. Here, xe_tlb_inval_job_create() relies
on these bounds, meaning the secondary tile's GTs could execute an invalid
TLB flush for the [0, 0] range, leaving stale TLB entries for the actual
modified range.

[Severity: Critical]
Can this loop trigger a use-after-free and double-free on the page reclaim
list (PRL)?

For secondary mirrored tiles, the PRL is set up as a shallow copy of the
primary tile's PRL without taking an additional page reference.
When the loop iterates over the primary tile, it adds the PRL and calls
xe_page_reclaim_list_invalidate(), which drops the page reference.

When the loop then reaches the secondary tile, its shallow-copied
prl.entries pointer is still non-NULL but points to freed memory. The code
will pass this dangling pointer to the secondary job and call
xe_page_reclaim_list_invalidate() a second time, triggering a double-free.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=16

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs
  2026-09-03 23:58 ` [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs Matthew Brost
@ 2026-09-04  0:36   ` sashiko-bot
  2026-09-04  0:57     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:36 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] PT jobs on wedged, killed, or banned queues silently report success to the DRM scheduler, masking failures.
- [High] Memory leak of xe_pt_job_ops and dma_fence references if a PT job is aborted before execution.
--

commit 86f7996687fb641831b3ca6cb60d0fed641a6978
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Update GuC submission backend to run PT jobs

This commit teaches the GuC submission backend to execute PT (Page Table) jobs.
PT jobs bypass GPU execution for the final step of a bind job, instead using
the CPU to program the required page tables.

Link: https://patch.msgid.link/20260228013501.106680-11-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
> --- a/drivers/gpu/drm/xe/xe_guc_submit.c
> +++ b/drivers/gpu/drm/xe/xe_guc_submit.c
> @@ -1261,14 +1277,25 @@ guc_exec_queue_run_job(struct drm_sched_job *drm_job)

[ ... ]

>  	if (!killed_or_banned_or_wedged && !xe_sched_job_is_error(job)) {

[ ... ]

> +		if (is_pt_job(job)) {
> +			xe_gt_assert(guc_to_gt(guc), !exec_queue_registered(q));
> +			run_pt_job(job);
> +		} else {
> +			if (!exec_queue_registered(q))
> +				register_exec_queue(q, GUC_CONTEXT_NORMAL);
> +			if (!job->restore_replay)
> +				q->ring_ops->emit_job(job);
> +			submit_exec_queue(q, job);
> +		}
>  		job->restore_replay = false;
>  	}
>  
> +	if (is_pt_job(job)) {
> +		xe_pt_job_ops_put(job->pt_update[0].pt_job_ops);
> +		dma_fence_put(job->fence);	/* Drop ref from xe_sched_job_arm */
> +		return NULL;
> +	}

[Severity: High]
If the queue is wedged, killed, or banned, the execution block above is
skipped, but this block unconditionally returns NULL for PT jobs. Can this
cause PT jobs on failed queues to silently report success to the DRM
scheduler and mask failures?

[Severity: High]
This handles dropping the extra dma_fence and xe_pt_job_ops references during
normal execution, but what happens if the DRM scheduler aborts the job before
it executes (for example, if the queue is destroyed)?

Looking at xe_sched_job_destroy() in drivers/gpu/drm/xe/xe_sched_job.c:

> void xe_sched_job_destroy(struct kref *ref)
> {
>         struct xe_sched_job *job =
>                 container_of(ref, struct xe_sched_job, refcount);
> ...
>         xe_sched_job_free_fences(job);
>         dma_fence_put(job->fence);
>         drm_sched_job_cleanup(&job->drm);
>         job_free(job);
> ...
> }

It seems the destructor fails to call xe_pt_job_ops_put() for PT jobs and
only drops the primary fence reference. Does this code leak the xe_pt_job_ops
structure and the extra dma_fence reference taken during job arming if
execution is aborted?

>  run_job_out:
>  
>  	return job->fence;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=10

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission
  2026-09-03 23:58 ` [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission Matthew Brost
@ 2026-09-04  0:38   ` sashiko-bot
  2026-09-04  1:41     ` Matthew Brost
  0 siblings, 1 reply; 48+ messages in thread
From: sashiko-bot @ 2026-09-04  0:38 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Direct MMIO write to the physical engine's RING_TAIL for GuC-scheduled ULLS jobs causes hardware state corruption if the context is preempted or evicted.
--

commit f60d4c34d363517fe528679e6ff3d059b7861099
Author: Matthew Brost <matthew.brost@intel.com>

drm/xe: Add ULLS migration job support to GuC submission

This commit adds ULLS migration job support to the GuC submission backend.
It modifies the migration queue job limits and introduces direct hardware
engine tail writes for subsequent ULLS jobs.

Link: https://patch.msgid.link/20260228013501.106680-24-matthew.brost@intel.com

> diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
> index dcb4b8a4f3b78..1578d80b01b82 100644
> --- a/drivers/gpu/drm/xe/xe_guc_submit.c
> +++ b/drivers/gpu/drm/xe/xe_guc_submit.c
> @@ -1207,6 +1207,11 @@ static void submit_exec_queue(struct xe_exec_queue *q, struct xe_sched_job *job)
>  	if (exec_queue_suspended(q))
>  		return;
>  
> +	if (job->is_ulls && !job->is_ulls_first) {
> +		xe_hw_engine_write_ring_tail(q->hwe, lrc->ring.tail);

[Severity: Critical]
Can this direct MMIO write to the engine's RING_TAIL cause hardware state
corruption if the ULLS context is preempted or evicted?

The driver bypasses the GuC submission interface (H2G) for ULLS jobs and
directly writes to the engine's MMIO RING_TAIL register. If the ULLS
migration context is not actively resident on the hardware engine at the exact
moment of the write, the write will advance the ring tail of whichever context
is currently resident on that engine (e.g., VM bind contexts sharing the same
USM engine).

Since the GuC scheduler fundamentally owns engine context switching, it can
preempt or evict the ULLS context for internal tasks or schedule other queues
that explicitly share the USM engine. If the CPU submits a ULLS job while the
ULLS context is swapped out, would the direct MMIO tail write hit the
currently running context instead?

> +		xe_lrc_set_ulls_semaphore(lrc, xe_sched_job_lrc_seqno(job));
> +	}
> +
>  	if (!exec_queue_enabled(q)) {
>  		action[len++] = XE_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
>  		action[len++] = q->guc->id;

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=22

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
  2026-09-04  0:18   ` sashiko-bot
@ 2026-09-04  0:41     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  0:41 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:18:26AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] The XE_BO_FLAG_PUT_VM_ASYNC mechanism causes VM reference leaks if xe_bo_put_async() is called when the BO's refcount is greater than 1.
> - [High] Dropping the VM reference synchronously in xe_bo_put_commit causes a Use-After-Free of the shared dma_resv object if TTM defers destruction.
> --
> 
> commit 73ce65401a8053bf565b7b064086594d1dafb49a
> Author: Matthew Brost <matthew.brost@intel.com>
> 
> drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
> 
> This commit introduces XE_BO_FLAG_PUT_VM_ASYNC, which indicates that an
> async BO put must also drop an additional reference to the BO's VM. This
> ensures the shared dma-resv object does not disappear before the BO when
> a kernel BO is put asynchronously.
> 
> Link: https://patch.msgid.link/20260228013501.106680-7-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
> > index dde309821237b..5cde5dff2d481 100644
> > --- a/drivers/gpu/drm/xe/xe_bo.c
> > +++ b/drivers/gpu/drm/xe/xe_bo.c
> > @@ -3989,8 +3989,14 @@ void xe_bo_put_commit(struct llist_head *deferred)
> >  	if (!freed)
> >  		return;
> >  
> > -	llist_for_each_entry_safe(bo, next, freed, freed)
> > +	llist_for_each_entry_safe(bo, next, freed, freed) {
> > +		struct xe_vm *vm = bo->vm;
> > +		bool async = bo->flags & XE_BO_FLAG_PUT_VM_ASYNC;
> > +
> >  		drm_gem_object_free(&bo->ttm.base.refcount);
> > +		if (async)
> > +			xe_vm_put(vm);
> 
> [Severity: High]
> Does this code leak the VM reference if xe_bo_put_async() is called when the
> BO's refcount is greater than 1?
> 
> If xe_bo_put_async() is called while concurrent paths (like show_meminfo or
> xe_bo_evict_all) hold a temporary reference, the BO won't be added to the
> async_list.
> 
> Later, when the temporary reference is dropped via a synchronous xe_bo_put(),
> the BO is destroyed by xe_ttm_bo_destroy(), which only drops bo->vm if
> xe_bo_is_user(bo) is true (which is false for kernel PT BOs). Thus, the VM
> reference could be permanently leaked.
> 
> [Severity: High]
> Can dropping the VM reference synchronously here cause a use-after-free of
> the shared dma_resv object if TTM defers destruction?
> 
> In xe_bo_put_commit(), drm_gem_object_free() drops the GEM reference. If TTM
> concurrently holds an extra reference to the BO (e.g., via
> ttm_bo_get_unless_zero() during an LRU walk), the BO will not be immediately
> destroyed.
> 
> However, xe_bo_put_commit() continues and calls xe_vm_put(vm). If this drops
> the last reference to the VM, the VM and its embedded resv are freed.
> Because the BO is still alive and bo->ttm.base.resv points to the freed
> vm->resv, TTM might later attempt to use the freed reservation object.
> 

I don't think this reasoning is 100% correct, but the reinit of the ref
count is indeed dangerous. I'll rework this part.

Matt

> > +	}
> >  }
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=6

^ permalink raw reply	[flat|nested] 48+ messages in thread

* ✗ CI.checkpatch: warning for CPU binds and ULLS on migration queue (rev6)
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (24 preceding siblings ...)
  2026-09-03 23:58 ` [PATCH v4 25/25] drm/xe: Document ULLS for migration jobs Matthew Brost
@ 2026-09-04  0:47 ` Patchwork
  2026-09-04  0:49 ` ✓ CI.KUnit: success " Patchwork
  2026-09-04  1:33 ` ✓ Xe.CI.BAT: " Patchwork
  27 siblings, 0 replies; 48+ messages in thread
From: Patchwork @ 2026-09-04  0:47 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

== Series Details ==

Series: CPU binds and ULLS on migration queue (rev6)
URL   : https://patchwork.freedesktop.org/series/149888/
State : warning

== Summary ==

+ KERNEL=/kernel
+ git clone https://gitlab.freedesktop.org/drm/maintainer-tools mt
Cloning into 'mt'...
warning: redirecting to https://gitlab.freedesktop.org/drm/maintainer-tools.git/
+ git -C mt rev-list -n1 origin/master
d875049d2b299159a272bd5151994970cdcd1e31
+ cd /kernel
+ git config --global --add safe.directory /kernel
+ git log -n1
commit 68b14d4b181af4041d0b713be2e643a1b354ce00
Author: Matthew Brost <matthew.brost@intel.com>
Date:   Thu Sep 3 16:58:42 2026 -0700

    drm/xe: Document ULLS for migration jobs
    
    Add a kernel-doc DOC section at the top of xe_migrate.c describing the
    Ultra Low Latency Submission (ULLS) scheme used for migration jobs.
    
    Cover the motivation (removing the H2G / GuC / context switch latency
    from the page fault and SVM prefetch critical paths), the platform
    requirements, the LRC PPHWSP semaphore layout and its relationship to
    the migration queue job count, the ring preamble / postamble emitted by
    the ring ops, the MMIO tail write submission fast path in the GuC
    backend, and the enter / delayed exit flow along with the ULLS job
    flags.
    
    Hook the new section into Documentation/gpu/xe/xe_migrate.rst.
    
    Signed-off-by: Matthew Brost <matthew.brost@intel.com>
    Assisted-by: Github-Copilot:Claude-opus-5
+ /mt/dim checkpatch 71bc3b7cc55631a9b3807da98e2b2880838b9623 drm-intel
e99c82be4164 drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns
68aec156a212 drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper
a5af43ce36a5 drm/xe: Decouple exec queue idle check from LRC
35d716c3dd93 drm/xe: Add job count to GuC exec queue snapshot
f785360d5554 drm/xe: Update xe_bo_put_deferred arguments to include writeback flag
35b631062bcf drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC
64c2c47db7e1 drm/xe: Update scheduler job layer to support PT jobs
f88fc942b036 drm/xe: Add helpers to access PT ops
23d3af849e14 drm/xe: Add struct xe_pt_job_ops
-:179: WARNING:ALLOC_WITH_SIZEOF: Prefer kmalloc_obj over kmalloc with sizeof
#179: FILE: drivers/gpu/drm/xe/xe_pt.c:2980:
+	pt_job_ops = kmalloc(sizeof(*pt_job_ops), GFP_KERNEL);

total: 0 errors, 1 warnings, 0 checks, 296 lines checked
a7f9dded5697 drm/xe: Update GuC submission backend to run PT jobs
-:125: CHECK:LINE_SPACING: Please don't use multiple blank lines
#125: FILE: drivers/gpu/drm/xe/xe_migrate.h:167:
 
+

total: 0 errors, 0 warnings, 1 checks, 100 lines checked
4768e31ab635 drm/xe: Store level in struct xe_vm_pgtable_update
d23af916d835 drm/xe: Don't use migrate exec queue for page fault binds
f8002c748c36 drm/xe: Enable CPU binds for jobs
524c3c0e0390 drm/xe: Remove unused arguments from xe_migrate_pt_update_ops
f0becdf6a0cf drm/xe: Make bind queues operate cross-tile
-:222: ERROR:SPACING: space required after that ';' (ctx:VxV)
#222: FILE: drivers/gpu/drm/xe/xe_exec_queue.c:1737:
+	q->tlb_inval[idx].last_fence = NULL;}
 	                                   ^

-:306: CHECK:MACRO_ARG_REUSE: Macro argument reuse '__i' - possible side-effects?
#306: FILE: drivers/gpu/drm/xe/xe_exec_queue.h:17:
+#define for_each_tlb_inval(__q, __i)	\
+	for (__i = 0; __i < XE_EXEC_QUEUE_TLB_INVAL_COUNT; ++__i)	\
+		for_each_if((__q)->tlb_inval[__i].dep_scheduler)

total: 1 errors, 0 warnings, 1 checks, 624 lines checked
5ba4a71dee84 drm/xe: Add CPU bind layer
-:34: WARNING:FILE_PATH_CHANGES: added, moved or deleted file(s), does MAINTAINERS need updating?
#34: 
new file mode 100644

total: 0 errors, 1 warnings, 0 checks, 2324 lines checked
71733aacb3f3 drm/xe: Add device flag to enable PT mirroring across tiles
361e0da643ed drm/xe: Add xe_hw_engine_write_ring_tail
653cc5b6c569 drm/xe: Add ULLS support to LRC
54fa13a279b6 drm/xe: Add ULLS migration job support to migration layer
cb3dc4613fc2 drm/xe: Add ULLS migration job support to ring ops
990cfae4513a drm/xe: Add ULLS migration job support to GuC submission
3152a92a32db drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
d2215068cd3e drm/xe: Add modparam to enable / disable ULLS on migrate queue
68b14d4b181a drm/xe: Document ULLS for migration jobs



^ permalink raw reply	[flat|nested] 48+ messages in thread

* ✓ CI.KUnit: success for CPU binds and ULLS on migration queue (rev6)
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (25 preceding siblings ...)
  2026-09-04  0:47 ` ✗ CI.checkpatch: warning for CPU binds and ULLS on migration queue (rev6) Patchwork
@ 2026-09-04  0:49 ` Patchwork
  2026-09-04  1:33 ` ✓ Xe.CI.BAT: " Patchwork
  27 siblings, 0 replies; 48+ messages in thread
From: Patchwork @ 2026-09-04  0:49 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

== Series Details ==

Series: CPU binds and ULLS on migration queue (rev6)
URL   : https://patchwork.freedesktop.org/series/149888/
State : success

== Summary ==

+ trap cleanup EXIT
+ kunitconfigs=('/kernel/drivers/gpu/tests/.kunitconfig' '/kernel/drivers/gpu/drm/xe/.kunitconfig' '/kernel/drivers/gpu/drm/tests/.kunitconfig' '/kernel/drivers/gpu/drm/ttm/tests/.kunitconfig' '/kernel/drivers/dma-buf/.kunitconfig')
+ for kcfg in "${kunitconfigs[@]}"
+ [[ ! -f /kernel/drivers/gpu/tests/.kunitconfig ]]
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/gpu/tests/.kunitconfig
[00:47:38] Configuring KUnit Kernel ...
Generating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[00:47:42] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
[00:48:03] Starting KUnit Kernel (1/1)...
[00:48:03] ============================================================
Running tests with:
$ .kunit/linux kunit.enable=1 mem=1G console=tty kunit_shutdown=halt
[00:48:03] ============= refcount_interrupt (4 subtests) ==============
[00:48:03] [PASSED] test_single_irq_change
[00:48:03] [PASSED] test_nested_irq_change
[00:48:03] [PASSED] test_multiple_irq_change
[00:48:03] [PASSED] test_irq_save
[00:48:03] =============== [PASSED] refcount_interrupt ================
[00:48:03] ================= gpu_buddy (14 subtests) ==================
[00:48:03] [PASSED] gpu_test_buddy_alloc_limit
[00:48:03] [PASSED] gpu_test_buddy_alloc_optimistic
[00:48:03] [PASSED] gpu_test_buddy_alloc_pessimistic
[00:48:03] [PASSED] gpu_test_buddy_alloc_pathological
[00:48:03] [PASSED] gpu_test_buddy_alloc_contiguous
[00:48:03] [PASSED] gpu_test_buddy_alloc_clear
[00:48:03] [PASSED] gpu_test_buddy_alloc_range
[00:48:04] [PASSED] gpu_test_buddy_alloc_range_bias
[00:48:04] [PASSED] gpu_test_buddy_fragmentation_performance
[00:48:05] [PASSED] gpu_test_buddy_dirty_tracker_performance
[00:48:05] [PASSED] gpu_test_buddy_alloc_exceeds_max_order
[00:48:05] [PASSED] gpu_test_buddy_offset_aligned_allocation
[00:48:05] [PASSED] gpu_test_buddy_subtree_offset_alignment_stress
[00:48:05] [PASSED] gpu_test_buddy_addr_to_block
[00:48:05] ==================== [PASSED] gpu_buddy ====================
[00:48:05] ============================================================
[00:48:05] Testing complete. Ran 18 tests: passed: 18
[00:48:05] Elapsed time: 27.366s total, 4.451s configuring, 20.997s building, 1.837s running

+ for kcfg in "${kunitconfigs[@]}"
+ [[ ! -f /kernel/drivers/gpu/drm/xe/.kunitconfig ]]
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/gpu/drm/xe/.kunitconfig
[00:48:05] Configuring KUnit Kernel ...
Regenerating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[00:48:07] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
[00:48:41] Starting KUnit Kernel (1/1)...
[00:48:41] ============================================================
Running tests with:
$ .kunit/linux kunit.enable=1 mem=1G console=tty kunit_shutdown=halt
[00:48:42] ============= refcount_interrupt (4 subtests) ==============
[00:48:42] [PASSED] test_single_irq_change
[00:48:42] [PASSED] test_nested_irq_change
[00:48:42] [PASSED] test_multiple_irq_change
[00:48:42] [PASSED] test_irq_save
[00:48:42] =============== [PASSED] refcount_interrupt ================
[00:48:42] ================== guc_buf (11 subtests) ===================
[00:48:42] [PASSED] test_smallest
[00:48:42] [PASSED] test_largest
[00:48:42] [PASSED] test_granular
[00:48:42] [PASSED] test_unique
[00:48:42] [PASSED] test_overlap
[00:48:42] [PASSED] test_reusable
[00:48:42] [PASSED] test_too_big
[00:48:42] [PASSED] test_flush
[00:48:42] [PASSED] test_lookup
[00:48:42] [PASSED] test_data
[00:48:42] [PASSED] test_class
[00:48:42] ===================== [PASSED] guc_buf =====================
[00:48:42] =================== guc_dbm (7 subtests) ===================
[00:48:42] [PASSED] test_empty
[00:48:42] [PASSED] test_default
[00:48:42] ======================== test_size  ========================
[00:48:42] [PASSED] 4
[00:48:42] [PASSED] 8
[00:48:42] [PASSED] 32
[00:48:42] [PASSED] 256
[00:48:42] ==================== [PASSED] test_size ====================
[00:48:42] ======================= test_reuse  ========================
[00:48:42] [PASSED] 4
[00:48:42] [PASSED] 8
[00:48:42] [PASSED] 32
[00:48:42] [PASSED] 256
[00:48:42] =================== [PASSED] test_reuse ====================
[00:48:42] =================== test_range_overlap  ====================
[00:48:42] [PASSED] 4
[00:48:42] [PASSED] 8
[00:48:42] [PASSED] 32
[00:48:42] [PASSED] 256
[00:48:42] =============== [PASSED] test_range_overlap ================
[00:48:42] =================== test_range_compact  ====================
[00:48:42] [PASSED] 4
[00:48:42] [PASSED] 8
[00:48:42] [PASSED] 32
[00:48:42] [PASSED] 256
[00:48:42] =============== [PASSED] test_range_compact ================
[00:48:42] ==================== test_range_spare  =====================
[00:48:42] [PASSED] 4
[00:48:42] [PASSED] 8
[00:48:42] [PASSED] 32
[00:48:42] [PASSED] 256
[00:48:42] ================ [PASSED] test_range_spare =================
[00:48:42] ===================== [PASSED] guc_dbm =====================
[00:48:42] =================== guc_idm (6 subtests) ===================
[00:48:42] [PASSED] bad_init
[00:48:42] [PASSED] no_init
[00:48:42] [PASSED] init_fini
[00:48:42] [PASSED] check_used
[00:48:42] [PASSED] check_quota
[00:48:42] [PASSED] check_all
[00:48:42] ===================== [PASSED] guc_idm =====================
[00:48:42] =============== guc_klv_helpers (9 subtests) ===============
[00:48:42] [PASSED] test_count
[00:48:42] [PASSED] test_encode_u32
[00:48:42] [PASSED] test_encode_u64
[00:48:42] [PASSED] test_encode_string
[00:48:42] [PASSED] test_encode_object_raw
[00:48:42] [PASSED] test_encode_object_klv
[00:48:42] [PASSED] test_encode_object_nested
[00:48:42] [PASSED] test_encode_object_basic
[00:48:42] [PASSED] test_print
[00:48:42] ================= [PASSED] guc_klv_helpers =================
[00:48:42] =================== xe_log (4 subtests) ====================
[00:48:42] [PASSED] demo_cper
[00:48:42] [PASSED] demo_dmesg
[00:48:42] ======================= test_dmesg  ========================
[00:48:42] [PASSED] test_fatal
[00:48:42] [PASSED] test_fatal_tile
[00:48:42] [PASSED] test_fatal_gt
[00:48:42] [PASSED] test_fatal_comp
[00:48:42] [PASSED] test_fatal_comp_tile
[00:48:42] [PASSED] test_fatal_comp_gt
[00:48:42] [PASSED] test_fatal_all
[00:48:42] [PASSED] test_recoverable
[00:48:42] [PASSED] test_recoverable_tile
[00:48:42] [PASSED] test_recoverable_gt
[00:48:42] [PASSED] test_recoverable_comp
[00:48:42] [PASSED] test_recoverable_comp_tile
[00:48:42] [PASSED] test_recoverable_comp_gt
[00:48:42] [PASSED] test_recoverable_all
[00:48:42] [PASSED] test_info
[00:48:42] [PASSED] test_info_tile
[00:48:42] [PASSED] test_info_gt
[00:48:42] [PASSED] test_info_err
[00:48:42] [PASSED] test_info_comp
[00:48:42] [PASSED] test_info_comp_tile
[00:48:42] [PASSED] test_info_comp_gt
[00:48:42] [PASSED] test_info_all
[00:48:42] [PASSED] test_hw_fatal
[00:48:42] [PASSED] test_hw_recoverable
[00:48:42] [PASSED] test_hw_corrected
[00:48:42] [PASSED] test_hw_informational
[00:48:42] =================== [PASSED] test_dmesg ====================
[00:48:42] ====================== test_invalid  =======================
[00:48:42] [SKIPPED] no-component no-location no-warn (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] reserved location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] unknown location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] nonzero-device-id location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] invalid-tile-id location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] invalid-gt-id location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] unknown component class (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] unknown system component (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] unknown hardware component (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] [SKIPPED] unknown component and location (requires CONFIG_DRM_XE_DEBUG)
[00:48:42] ================== [SKIPPED] test_invalid ==================
[00:48:42] ===================== [PASSED] xe_log ======================
[00:48:42] ================== no_relay (3 subtests) ===================
[00:48:42] [PASSED] xe_drops_guc2pf_if_not_ready
[00:48:42] [PASSED] xe_drops_guc2vf_if_not_ready
[00:48:42] [PASSED] xe_rejects_send_if_not_ready
[00:48:42] ==================== [PASSED] no_relay =====================
[00:48:42] ================== pf_relay (14 subtests) ==================
[00:48:42] [PASSED] pf_rejects_guc2pf_too_short
[00:48:42] [PASSED] pf_rejects_guc2pf_too_long
[00:48:42] [PASSED] pf_rejects_guc2pf_no_payload
[00:48:42] [PASSED] pf_fails_no_payload
[00:48:42] [PASSED] pf_fails_bad_origin
[00:48:42] [PASSED] pf_fails_bad_type
[00:48:42] [PASSED] pf_txn_reports_error
[00:48:42] [PASSED] pf_txn_sends_pf2guc
[00:48:42] [PASSED] pf_sends_pf2guc
[00:48:42] [SKIPPED] pf_loopback_nop (requires CONFIG_DRM_XE_DEBUG_SRIOV)
[00:48:42] [SKIPPED] pf_loopback_echo (requires CONFIG_DRM_XE_DEBUG_SRIOV)
[00:48:42] [SKIPPED] pf_loopback_fail (requires CONFIG_DRM_XE_DEBUG_SRIOV)
[00:48:42] [SKIPPED] pf_loopback_busy (requires CONFIG_DRM_XE_DEBUG_SRIOV)
[00:48:42] [SKIPPED] pf_loopback_retry (requires CONFIG_DRM_XE_DEBUG_SRIOV)
[00:48:42] ==================== [PASSED] pf_relay =====================
[00:48:42] ================== vf_relay (3 subtests) ===================
[00:48:42] [PASSED] vf_rejects_guc2vf_too_short
[00:48:42] [PASSED] vf_rejects_guc2vf_too_long
[00:48:42] [PASSED] vf_rejects_guc2vf_no_payload
[00:48:42] ==================== [PASSED] vf_relay =====================
[00:48:42] ================ pf_gt_config (9 subtests) =================
[00:48:42] [PASSED] fair_contexts_1vf
[00:48:42] [PASSED] fair_doorbells_1vf
[00:48:42] [PASSED] fair_ggtt_1vf
[00:48:42] ====================== fair_vram_1vf  ======================
[00:48:42] [PASSED] 3.50 GiB
[00:48:42] [PASSED] 11.5 GiB
[00:48:42] [PASSED] 15.5 GiB
[00:48:42] [PASSED] 31.5 GiB
[00:48:42] [PASSED] 63.5 GiB
[00:48:42] [PASSED] 1.91 GiB
[00:48:42] ================== [PASSED] fair_vram_1vf ==================
[00:48:42] ================ fair_vram_1vf_admin_only  =================
[00:48:42] [PASSED] 3.50 GiB
[00:48:42] [PASSED] 11.5 GiB
[00:48:42] [PASSED] 15.5 GiB
[00:48:42] [PASSED] 31.5 GiB
[00:48:42] [PASSED] 63.5 GiB
[00:48:42] [PASSED] 1.91 GiB
[00:48:42] ============ [PASSED] fair_vram_1vf_admin_only =============
[00:48:42] ====================== fair_contexts  ======================
[00:48:42] [PASSED] 1 VF
[00:48:42] [PASSED] 2 VFs
[00:48:42] [PASSED] 3 VFs
[00:48:42] [PASSED] 4 VFs
[00:48:42] [PASSED] 5 VFs
[00:48:42] [PASSED] 6 VFs
[00:48:42] [PASSED] 7 VFs
[00:48:42] [PASSED] 8 VFs
[00:48:42] [PASSED] 9 VFs
[00:48:42] [PASSED] 10 VFs
[00:48:42] [PASSED] 11 VFs
[00:48:42] [PASSED] 12 VFs
[00:48:42] [PASSED] 13 VFs
[00:48:42] [PASSED] 14 VFs
[00:48:42] [PASSED] 15 VFs
[00:48:42] [PASSED] 16 VFs
[00:48:42] [PASSED] 17 VFs
[00:48:42] [PASSED] 18 VFs
[00:48:42] [PASSED] 19 VFs
[00:48:42] [PASSED] 20 VFs
[00:48:42] [PASSED] 21 VFs
[00:48:42] [PASSED] 22 VFs
[00:48:42] [PASSED] 23 VFs
[00:48:42] [PASSED] 24 VFs
[00:48:42] [PASSED] 25 VFs
[00:48:42] [PASSED] 26 VFs
[00:48:42] [PASSED] 27 VFs
[00:48:42] [PASSED] 28 VFs
[00:48:42] [PASSED] 29 VFs
[00:48:42] [PASSED] 30 VFs
[00:48:42] [PASSED] 31 VFs
[00:48:42] [PASSED] 32 VFs
[00:48:42] [PASSED] 33 VFs
[00:48:42] [PASSED] 34 VFs
[00:48:42] [PASSED] 35 VFs
[00:48:42] [PASSED] 36 VFs
[00:48:42] [PASSED] 37 VFs
[00:48:42] [PASSED] 38 VFs
[00:48:42] [PASSED] 39 VFs
[00:48:42] [PASSED] 40 VFs
[00:48:42] [PASSED] 41 VFs
[00:48:42] [PASSED] 42 VFs
[00:48:42] [PASSED] 43 VFs
[00:48:42] [PASSED] 44 VFs
[00:48:42] [PASSED] 45 VFs
[00:48:42] [PASSED] 46 VFs
[00:48:42] [PASSED] 47 VFs
[00:48:42] [PASSED] 48 VFs
[00:48:42] [PASSED] 49 VFs
[00:48:42] [PASSED] 50 VFs
[00:48:42] [PASSED] 51 VFs
[00:48:42] [PASSED] 52 VFs
[00:48:42] [PASSED] 53 VFs
[00:48:42] [PASSED] 54 VFs
[00:48:42] [PASSED] 55 VFs
[00:48:42] [PASSED] 56 VFs
[00:48:42] [PASSED] 57 VFs
[00:48:42] [PASSED] 58 VFs
[00:48:42] [PASSED] 59 VFs
[00:48:42] [PASSED] 60 VFs
[00:48:42] [PASSED] 61 VFs
[00:48:42] [PASSED] 62 VFs
[00:48:42] [PASSED] 63 VFs
[00:48:42] ================== [PASSED] fair_contexts ==================
[00:48:42] ===================== fair_doorbells  ======================
[00:48:42] [PASSED] 1 VF
[00:48:42] [PASSED] 2 VFs
[00:48:42] [PASSED] 3 VFs
[00:48:42] [PASSED] 4 VFs
[00:48:42] [PASSED] 5 VFs
[00:48:42] [PASSED] 6 VFs
[00:48:42] [PASSED] 7 VFs
[00:48:42] [PASSED] 8 VFs
[00:48:42] [PASSED] 9 VFs
[00:48:42] [PASSED] 10 VFs
[00:48:42] [PASSED] 11 VFs
[00:48:42] [PASSED] 12 VFs
[00:48:42] [PASSED] 13 VFs
[00:48:42] [PASSED] 14 VFs
[00:48:42] [PASSED] 15 VFs
[00:48:42] [PASSED] 16 VFs
[00:48:42] [PASSED] 17 VFs
[00:48:42] [PASSED] 18 VFs
[00:48:42] [PASSED] 19 VFs
[00:48:42] [PASSED] 20 VFs
[00:48:42] [PASSED] 21 VFs
[00:48:42] [PASSED] 22 VFs
[00:48:42] [PASSED] 23 VFs
[00:48:42] [PASSED] 24 VFs
[00:48:42] [PASSED] 25 VFs
[00:48:42] [PASSED] 26 VFs
[00:48:42] [PASSED] 27 VFs
[00:48:42] [PASSED] 28 VFs
[00:48:42] [PASSED] 29 VFs
[00:48:42] [PASSED] 30 VFs
[00:48:42] [PASSED] 31 VFs
[00:48:42] [PASSED] 32 VFs
[00:48:42] [PASSED] 33 VFs
[00:48:42] [PASSED] 34 VFs
[00:48:42] [PASSED] 35 VFs
[00:48:42] [PASSED] 36 VFs
[00:48:42] [PASSED] 37 VFs
[00:48:42] [PASSED] 38 VFs
[00:48:42] [PASSED] 39 VFs
[00:48:42] [PASSED] 40 VFs
[00:48:42] [PASSED] 41 VFs
[00:48:42] [PASSED] 42 VFs
[00:48:42] [PASSED] 43 VFs
[00:48:42] [PASSED] 44 VFs
[00:48:42] [PASSED] 45 VFs
[00:48:42] [PASSED] 46 VFs
[00:48:42] [PASSED] 47 VFs
[00:48:42] [PASSED] 48 VFs
[00:48:42] [PASSED] 49 VFs
[00:48:42] [PASSED] 50 VFs
[00:48:42] [PASSED] 51 VFs
[00:48:42] [PASSED] 52 VFs
[00:48:42] [PASSED] 53 VFs
[00:48:42] [PASSED] 54 VFs
[00:48:42] [PASSED] 55 VFs
[00:48:42] [PASSED] 56 VFs
[00:48:42] [PASSED] 57 VFs
[00:48:42] [PASSED] 58 VFs
[00:48:42] [PASSED] 59 VFs
[00:48:42] [PASSED] 60 VFs
[00:48:42] [PASSED] 61 VFs
[00:48:42] [PASSED] 62 VFs
[00:48:42] [PASSED] 63 VFs
[00:48:42] ================= [PASSED] fair_doorbells ==================
[00:48:42] ======================== fair_ggtt  ========================
[00:48:42] [PASSED] 1 VF
[00:48:42] [PASSED] 2 VFs
[00:48:42] [PASSED] 3 VFs
[00:48:42] [PASSED] 4 VFs
[00:48:42] [PASSED] 5 VFs
[00:48:42] [PASSED] 6 VFs
[00:48:42] [PASSED] 7 VFs
[00:48:42] [PASSED] 8 VFs
[00:48:42] [PASSED] 9 VFs
[00:48:42] [PASSED] 10 VFs
[00:48:42] [PASSED] 11 VFs
[00:48:42] [PASSED] 12 VFs
[00:48:42] [PASSED] 13 VFs
[00:48:42] [PASSED] 14 VFs
[00:48:42] [PASSED] 15 VFs
[00:48:42] [PASSED] 16 VFs
[00:48:42] [PASSED] 17 VFs
[00:48:42] [PASSED] 18 VFs
[00:48:42] [PASSED] 19 VFs
[00:48:42] [PASSED] 20 VFs
[00:48:42] [PASSED] 21 VFs
[00:48:42] [PASSED] 22 VFs
[00:48:42] [PASSED] 23 VFs
[00:48:42] [PASSED] 24 VFs
[00:48:42] [PASSED] 25 VFs
[00:48:42] [PASSED] 26 VFs
[00:48:42] [PASSED] 27 VFs
[00:48:42] [PASSED] 28 VFs
[00:48:42] [PASSED] 29 VFs
[00:48:42] [PASSED] 30 VFs
[00:48:42] [PASSED] 31 VFs
[00:48:42] [PASSED] 32 VFs
[00:48:42] [PASSED] 33 VFs
[00:48:42] [PASSED] 34 VFs
[00:48:42] [PASSED] 35 VFs
[00:48:42] [PASSED] 36 VFs
[00:48:42] [PASSED] 37 VFs
[00:48:42] [PASSED] 38 VFs
[00:48:42] [PASSED] 39 VFs
[00:48:42] [PASSED] 40 VFs
[00:48:42] [PASSED] 41 VFs
[00:48:42] [PASSED] 42 VFs
[00:48:42] [PASSED] 43 VFs
[00:48:42] [PASSED] 44 VFs
[00:48:42] [PASSED] 45 VFs
[00:48:42] [PASSED] 46 VFs
[00:48:42] [PASSED] 47 VFs
[00:48:42] [PASSED] 48 VFs
[00:48:42] [PASSED] 49 VFs
[00:48:42] [PASSED] 50 VFs
[00:48:42] [PASSED] 51 VFs
[00:48:42] [PASSED] 52 VFs
[00:48:42] [PASSED] 53 VFs
[00:48:42] [PASSED] 54 VFs
[00:48:42] [PASSED] 55 VFs
[00:48:42] [PASSED] 56 VFs
[00:48:42] [PASSED] 57 VFs
[00:48:42] [PASSED] 58 VFs
[00:48:42] [PASSED] 59 VFs
[00:48:42] [PASSED] 60 VFs
[00:48:42] [PASSED] 61 VFs
[00:48:42] [PASSED] 62 VFs
[00:48:42] [PASSED] 63 VFs
[00:48:42] ==================== [PASSED] fair_ggtt ====================
[00:48:42] ======================== fair_vram  ========================
[00:48:42] [PASSED] 1 VF
[00:48:42] [PASSED] 2 VFs
[00:48:42] [PASSED] 3 VFs
[00:48:42] [PASSED] 4 VFs
[00:48:42] [PASSED] 5 VFs
[00:48:42] [PASSED] 6 VFs
[00:48:42] [PASSED] 7 VFs
[00:48:42] [PASSED] 8 VFs
[00:48:42] [PASSED] 9 VFs
[00:48:42] [PASSED] 10 VFs
[00:48:42] [PASSED] 11 VFs
[00:48:42] [PASSED] 12 VFs
[00:48:42] [PASSED] 13 VFs
[00:48:42] [PASSED] 14 VFs
[00:48:42] [PASSED] 15 VFs
[00:48:42] [PASSED] 16 VFs
[00:48:42] [PASSED] 17 VFs
[00:48:42] [PASSED] 18 VFs
[00:48:42] [PASSED] 19 VFs
[00:48:42] [PASSED] 20 VFs
[00:48:42] [PASSED] 21 VFs
[00:48:42] [PASSED] 22 VFs
[00:48:42] [PASSED] 23 VFs
[00:48:42] [PASSED] 24 VFs
[00:48:42] [PASSED] 25 VFs
[00:48:42] [PASSED] 26 VFs
[00:48:42] [PASSED] 27 VFs
[00:48:42] [PASSED] 28 VFs
[00:48:42] [PASSED] 29 VFs
[00:48:42] [PASSED] 30 VFs
[00:48:42] [PASSED] 31 VFs
[00:48:42] [PASSED] 32 VFs
[00:48:42] [PASSED] 33 VFs
[00:48:42] [PASSED] 34 VFs
[00:48:42] [PASSED] 35 VFs
[00:48:42] [PASSED] 36 VFs
[00:48:42] [PASSED] 37 VFs
[00:48:42] [PASSED] 38 VFs
[00:48:42] [PASSED] 39 VFs
[00:48:42] [PASSED] 40 VFs
[00:48:42] [PASSED] 41 VFs
[00:48:42] [PASSED] 42 VFs
[00:48:42] [PASSED] 43 VFs
[00:48:42] [PASSED] 44 VFs
[00:48:42] [PASSED] 45 VFs
[00:48:42] [PASSED] 46 VFs
[00:48:42] [PASSED] 47 VFs
[00:48:42] [PASSED] 48 VFs
[00:48:42] [PASSED] 49 VFs
[00:48:42] [PASSED] 50 VFs
[00:48:42] [PASSED] 51 VFs
[00:48:42] [PASSED] 52 VFs
[00:48:42] [PASSED] 53 VFs
[00:48:42] [PASSED] 54 VFs
[00:48:42] [PASSED] 55 VFs
[00:48:42] [PASSED] 56 VFs
[00:48:42] [PASSED] 57 VFs
[00:48:42] [PASSED] 58 VFs
[00:48:42] [PASSED] 59 VFs
[00:48:42] [PASSED] 60 VFs
[00:48:42] [PASSED] 61 VFs
[00:48:42] [PASSED] 62 VFs
[00:48:42] [PASSED] 63 VFs
[00:48:42] ==================== [PASSED] fair_vram ====================
[00:48:42] ================== [PASSED] pf_gt_config ===================
[00:48:42] ===================== lmtt (1 subtest) =====================
[00:48:42] ======================== test_ops  =========================
[00:48:42] [PASSED] 2-level
[00:48:42] [PASSED] multi-level
[00:48:42] ==================== [PASSED] test_ops =====================
[00:48:42] ====================== [PASSED] lmtt =======================
[00:48:42] ================= sriov_packet (1 subtest) =================
[00:48:42] [PASSED] test_descriptor_init
[00:48:42] ================== [PASSED] sriov_packet ===================
[00:48:42] ================= pf_service (11 subtests) =================
[00:48:42] [PASSED] pf_negotiate_any
[00:48:42] [PASSED] pf_negotiate_base_match
[00:48:42] [PASSED] pf_negotiate_base_newer
[00:48:42] [PASSED] pf_negotiate_base_next
[00:48:42] [SKIPPED] pf_negotiate_base_older (no older minor)
[00:48:42] [PASSED] pf_negotiate_base_prev
[00:48:42] [PASSED] pf_negotiate_latest_match
[00:48:42] [PASSED] pf_negotiate_latest_newer
[00:48:42] [PASSED] pf_negotiate_latest_next
[00:48:42] [SKIPPED] pf_negotiate_latest_older (no older minor)
[00:48:42] [SKIPPED] pf_negotiate_latest_prev (no prev major)
[00:48:42] =================== [PASSED] pf_service ====================
[00:48:42] ================= xe_guc_g2g (2 subtests) ==================
[00:48:42] ============== xe_live_guc_g2g_kunit_default  ==============
[00:48:42] ========= [SKIPPED] xe_live_guc_g2g_kunit_default ==========
[00:48:42] ============== xe_live_guc_g2g_kunit_allmem  ===============
[00:48:42] ========== [SKIPPED] xe_live_guc_g2g_kunit_allmem ==========
[00:48:42] =================== [SKIPPED] xe_guc_g2g ===================
[00:48:42] =================== xe_mocs (2 subtests) ===================
[00:48:42] ================ xe_live_mocs_kernel_kunit  ================
[00:48:42] =========== [SKIPPED] xe_live_mocs_kernel_kunit ============
[00:48:42] ================ xe_live_mocs_reset_kunit  =================
[00:48:42] ============ [SKIPPED] xe_live_mocs_reset_kunit ============
[00:48:42] ==================== [SKIPPED] xe_mocs =====================
[00:48:42] ================= xe_migrate (2 subtests) ==================
[00:48:42] ================= xe_migrate_sanity_kunit  =================
[00:48:42] ============ [SKIPPED] xe_migrate_sanity_kunit =============
[00:48:42] ================== xe_validate_ccs_kunit  ==================
[00:48:42] ============= [SKIPPED] xe_validate_ccs_kunit ==============
[00:48:42] =================== [SKIPPED] xe_migrate ===================
[00:48:42] ================== xe_dma_buf (1 subtest) ==================
[00:48:42] ==================== xe_dma_buf_kunit  =====================
[00:48:42] ================ [SKIPPED] xe_dma_buf_kunit ================
[00:48:42] =================== [SKIPPED] xe_dma_buf ===================
[00:48:42] ================= xe_bo_shrink (1 subtest) =================
[00:48:42] =================== xe_bo_shrink_kunit  ====================
[00:48:42] =============== [SKIPPED] xe_bo_shrink_kunit ===============
[00:48:42] ================== [SKIPPED] xe_bo_shrink ==================
[00:48:42] ==================== xe_bo (2 subtests) ====================
[00:48:42] ================== xe_ccs_migrate_kunit  ===================
[00:48:42] ============== [SKIPPED] xe_ccs_migrate_kunit ==============
[00:48:42] ==================== xe_bo_evict_kunit  ====================
[00:48:42] =============== [SKIPPED] xe_bo_evict_kunit ================
[00:48:42] ===================== [SKIPPED] xe_bo ======================
[00:48:42] =================== xe_any (9 subtests) ====================
[00:48:42] [PASSED] test_to_xe
[00:48:42] [PASSED] test_to_dev
[00:48:42] [PASSED] test_to_pdev
[00:48:42] [PASSED] test_to_drm
[00:48:42] [PASSED] test_if_pdev
[00:48:42] [PASSED] test_if_xe
[00:48:42] [PASSED] test_if_tile
[00:48:42] [PASSED] test_if_gt
[00:48:42] [PASSED] test_to_id
[00:48:42] ===================== [PASSED] xe_any ======================
[00:48:42] ==================== args (13 subtests) ====================
[00:48:42] [PASSED] count_args_test
[00:48:42] [PASSED] call_args_example
[00:48:42] [PASSED] call_args_test
[00:48:42] [PASSED] drop_first_arg_example
[00:48:42] [PASSED] drop_first_arg_test
[00:48:42] [PASSED] first_arg_example
[00:48:42] [PASSED] first_arg_test
[00:48:42] [PASSED] last_arg_example
[00:48:42] [PASSED] last_arg_test
[00:48:42] [PASSED] pick_arg_example
[00:48:42] [PASSED] if_args_example
[00:48:42] [PASSED] if_args_test
[00:48:42] [PASSED] sep_comma_example
[00:48:42] ====================== [PASSED] args =======================
[00:48:42] =================== xe_pci (3 subtests) ====================
[00:48:42] ==================== check_graphics_ip  ====================
[00:48:42] [PASSED] 12.00 Xe_LP
[00:48:42] [PASSED] 12.10 Xe_LP+
[00:48:42] [PASSED] 12.55 Xe_HPG
[00:48:42] [PASSED] 12.60 Xe_HPC
[00:48:42] [PASSED] 12.70 Xe_LPG
[00:48:42] [PASSED] 12.71 Xe_LPG
[00:48:42] [PASSED] 12.74 Xe_LPG+
[00:48:42] [PASSED] 20.01 Xe2_HPG
[00:48:42] [PASSED] 20.02 Xe2_HPG
[00:48:42] [PASSED] 20.04 Xe2_LPG
[00:48:42] [PASSED] 30.00 Xe3_LPG
[00:48:42] [PASSED] 30.01 Xe3_LPG
[00:48:42] [PASSED] 30.03 Xe3_LPG
[00:48:42] [PASSED] 30.04 Xe3_LPG
[00:48:42] [PASSED] 30.05 Xe3_LPG
[00:48:42] [PASSED] 35.10 Xe3p_LPG
[00:48:42] [PASSED] 35.11 Xe3p_XPC
[00:48:42] ================ [PASSED] check_graphics_ip ================
[00:48:42] ===================== check_media_ip  ======================
[00:48:42] [PASSED] 12.00 Xe_M
[00:48:42] [PASSED] 12.55 Xe_HPM
[00:48:42] [PASSED] 13.00 Xe_LPM+
[00:48:42] [PASSED] 13.01 Xe2_HPM
[00:48:42] [PASSED] 20.00 Xe2_LPM
[00:48:42] [PASSED] 30.00 Xe3_LPM
[00:48:42] [PASSED] 30.02 Xe3_LPM
[00:48:42] [PASSED] 35.00 Xe3p_LPM
[00:48:42] [PASSED] 35.03 Xe3p_HPM
[00:48:42] ================= [PASSED] check_media_ip ==================
[00:48:42] =================== check_platform_desc  ===================
[00:48:42] [PASSED] 0x9A60 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A68 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A70 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A40 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A49 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A59 (TIGERLAKE)
[00:48:42] [PASSED] 0x9A78 (TIGERLAKE)
[00:48:42] [PASSED] 0x9AC0 (TIGERLAKE)
[00:48:42] [PASSED] 0x9AC9 (TIGERLAKE)
[00:48:42] [PASSED] 0x9AD9 (TIGERLAKE)
[00:48:42] [PASSED] 0x9AF8 (TIGERLAKE)
[00:48:42] [PASSED] 0x4C80 (ROCKETLAKE)
[00:48:42] [PASSED] 0x4C8A (ROCKETLAKE)
[00:48:42] [PASSED] 0x4C8B (ROCKETLAKE)
[00:48:42] [PASSED] 0x4C8C (ROCKETLAKE)
[00:48:42] [PASSED] 0x4C90 (ROCKETLAKE)
[00:48:42] [PASSED] 0x4C9A (ROCKETLAKE)
[00:48:42] [PASSED] 0x4680 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4682 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4688 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x468A (ALDERLAKE_S)
[00:48:42] [PASSED] 0x468B (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4690 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4692 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4693 (ALDERLAKE_S)
[00:48:42] [PASSED] 0x46A0 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46A1 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46A2 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46A3 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46A6 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46A8 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46AA (ALDERLAKE_P)
[00:48:42] [PASSED] 0x462A (ALDERLAKE_P)
[00:48:42] [PASSED] 0x4626 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x4628 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46B0 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46B1 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46B2 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46B3 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46C0 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46C1 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46C2 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46C3 (ALDERLAKE_P)
[00:48:42] [PASSED] 0x46D0 (ALDERLAKE_N)
[00:48:42] [PASSED] 0x46D1 (ALDERLAKE_N)
[00:48:42] [PASSED] 0x46D2 (ALDERLAKE_N)
[00:48:42] [PASSED] 0x46D3 (ALDERLAKE_N)
[00:48:42] [PASSED] 0x46D4 (ALDERLAKE_N)
[00:48:42] [PASSED] 0xA721 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7A1 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7A9 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7AC (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7AD (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA720 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7A0 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7A8 (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7AA (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA7AB (ALDERLAKE_P)
[00:48:42] [PASSED] 0xA780 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA781 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA782 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA783 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA788 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA789 (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA78A (ALDERLAKE_S)
[00:48:42] [PASSED] 0xA78B (ALDERLAKE_S)
[00:48:42] [PASSED] 0x4905 (DG1)
[00:48:42] [PASSED] 0x4906 (DG1)
[00:48:42] [PASSED] 0x4907 (DG1)
[00:48:42] [PASSED] 0x4908 (DG1)
[00:48:42] [PASSED] 0x4909 (DG1)
[00:48:42] [PASSED] 0x56C0 (DG2)
[00:48:42] [PASSED] 0x56C2 (DG2)
[00:48:42] [PASSED] 0x56C1 (DG2)
[00:48:42] [PASSED] 0x7D51 (METEORLAKE)
[00:48:42] [PASSED] 0x7DD1 (METEORLAKE)
[00:48:42] [PASSED] 0x7D41 (METEORLAKE)
[00:48:42] [PASSED] 0x7D67 (METEORLAKE)
[00:48:42] [PASSED] 0xB640 (METEORLAKE)
[00:48:42] [PASSED] 0x56A0 (DG2)
[00:48:42] [PASSED] 0x56A1 (DG2)
[00:48:42] [PASSED] 0x56A2 (DG2)
[00:48:42] [PASSED] 0x56BE (DG2)
[00:48:42] [PASSED] 0x56BF (DG2)
[00:48:42] [PASSED] 0x5690 (DG2)
[00:48:42] [PASSED] 0x5691 (DG2)
[00:48:42] [PASSED] 0x5692 (DG2)
[00:48:42] [PASSED] 0x56A5 (DG2)
[00:48:42] [PASSED] 0x56A6 (DG2)
[00:48:42] [PASSED] 0x56B0 (DG2)
[00:48:42] [PASSED] 0x56B1 (DG2)
[00:48:42] [PASSED] 0x56BA (DG2)
[00:48:42] [PASSED] 0x56BB (DG2)
[00:48:42] [PASSED] 0x56BC (DG2)
[00:48:42] [PASSED] 0x56BD (DG2)
[00:48:42] [PASSED] 0x5693 (DG2)
[00:48:42] [PASSED] 0x5694 (DG2)
[00:48:42] [PASSED] 0x5695 (DG2)
[00:48:42] [PASSED] 0x56A3 (DG2)
[00:48:42] [PASSED] 0x56A4 (DG2)
[00:48:42] [PASSED] 0x56B2 (DG2)
[00:48:42] [PASSED] 0x56B3 (DG2)
[00:48:42] [PASSED] 0x5696 (DG2)
[00:48:42] [PASSED] 0x5697 (DG2)
[00:48:42] [PASSED] 0xB69 (PVC)
[00:48:42] [PASSED] 0xB6E (PVC)
[00:48:42] [PASSED] 0xBD4 (PVC)
[00:48:42] [PASSED] 0xBD5 (PVC)
[00:48:42] [PASSED] 0xBD6 (PVC)
[00:48:42] [PASSED] 0xBD7 (PVC)
[00:48:42] [PASSED] 0xBD8 (PVC)
[00:48:42] [PASSED] 0xBD9 (PVC)
[00:48:42] [PASSED] 0xBDA (PVC)
[00:48:42] [PASSED] 0xBDB (PVC)
[00:48:42] [PASSED] 0xBE0 (PVC)
[00:48:42] [PASSED] 0xBE1 (PVC)
[00:48:42] [PASSED] 0xBE5 (PVC)
[00:48:42] [PASSED] 0x7D40 (METEORLAKE)
[00:48:42] [PASSED] 0x7D45 (METEORLAKE)
[00:48:42] [PASSED] 0x7D55 (METEORLAKE)
[00:48:42] [PASSED] 0x7D60 (METEORLAKE)
[00:48:42] [PASSED] 0x7DD5 (METEORLAKE)
[00:48:42] [PASSED] 0x6420 (LUNARLAKE)
[00:48:42] [PASSED] 0x64A0 (LUNARLAKE)
[00:48:42] [PASSED] 0x64B0 (LUNARLAKE)
[00:48:42] [PASSED] 0xE202 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE209 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE20B (BATTLEMAGE)
[00:48:42] [PASSED] 0xE20C (BATTLEMAGE)
[00:48:42] [PASSED] 0xE20D (BATTLEMAGE)
[00:48:42] [PASSED] 0xE210 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE211 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE212 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE216 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE220 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE221 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE222 (BATTLEMAGE)
[00:48:42] [PASSED] 0xE223 (BATTLEMAGE)
[00:48:42] [PASSED] 0xB080 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB081 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB082 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB083 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB084 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB085 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB086 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB087 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB08F (PANTHERLAKE)
[00:48:42] [PASSED] 0xB090 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB0A0 (PANTHERLAKE)
[00:48:42] [PASSED] 0xB0B0 (PANTHERLAKE)
[00:48:42] [PASSED] 0xFD80 (PANTHERLAKE)
[00:48:42] [PASSED] 0xFD81 (PANTHERLAKE)
[00:48:42] [PASSED] 0xD740 (NOVALAKE_S)
[00:48:42] [PASSED] 0xD741 (NOVALAKE_S)
[00:48:42] [PASSED] 0xD742 (NOVALAKE_S)
[00:48:42] [PASSED] 0xD743 (NOVALAKE_S)
[00:48:42] [PASSED] 0xD745 (NOVALAKE_S)
[00:48:42] [PASSED] 0xD74A (NOVALAKE_S)
[00:48:42] [PASSED] 0xD74B (NOVALAKE_S)
[00:48:42] [PASSED] 0x674C (CRESCENTISLAND)
[00:48:42] [PASSED] 0x674D (CRESCENTISLAND)
[00:48:42] [PASSED] 0x674E (CRESCENTISLAND)
[00:48:42] [PASSED] 0x674F (CRESCENTISLAND)
[00:48:42] [PASSED] 0x6750 (CRESCENTISLAND)
[00:48:42] [PASSED] 0xD750 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD751 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD752 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD753 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD754 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD755 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD756 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD757 (NOVALAKE_P)
[00:48:42] [PASSED] 0xD75F (NOVALAKE_P)
[00:48:42] =============== [PASSED] check_platform_desc ===============
[00:48:42] ===================== [PASSED] xe_pci ======================
[00:48:42] ============= xe_rtp_tables_test (5 subtests) ==============
[00:48:42] ================== xe_rtp_table_gt_test  ===================
[00:48:42] [PASSED] gt_was/14011060649
[00:48:42] [PASSED] gt_was/14011059788
[00:48:42] [PASSED] gt_was/14015795083
[00:48:42] [PASSED] gt_was/16021867713
[00:48:42] [PASSED] gt_was/14019449301
[00:48:42] [PASSED] gt_was/16028005424
[00:48:42] [PASSED] gt_was/14026578760
[00:48:42] [PASSED] gt_was/1409420604
[00:48:42] [PASSED] gt_was/1408615072
[00:48:42] [PASSED] gt_was/22010523718
[00:48:42] [PASSED] gt_was/14011006942
[00:48:42] [PASSED] gt_was/14014830051
[00:48:42] [PASSED] gt_was/18018781329
[00:48:42] [PASSED] gt_was/1509235366
[00:48:42] [PASSED] gt_was/18018781329
[00:48:42] [PASSED] gt_was/16016694945
[00:48:42] [PASSED] gt_was/14018575942
[00:48:42] [PASSED] gt_was/22016670082
[00:48:42] [PASSED] gt_was/22016670082
[00:48:42] [PASSED] gt_was/14017421178
[00:48:42] [PASSED] gt_was/16025250150
[00:48:42] [PASSED] gt_was/14021871409
[00:48:42] [PASSED] gt_was/16021865536
[00:48:42] [PASSED] gt_was/14021486841
[00:48:42] [PASSED] gt_was/14025160223
[00:48:42] [PASSED] gt_was/14026144927, 16029437861, 14026127056
[00:48:42] [PASSED] gt_was/14025635424
[00:48:42] [PASSED] gt_was/16028005424
[00:48:42] ============== [PASSED] xe_rtp_table_gt_test ===============
[00:48:42] ================== xe_rtp_table_gt_test  ===================
[00:48:42] [PASSED] gt_tunings/Tuning: Blend Fill Caching Optimization Disable
[00:48:42] [PASSED] gt_tunings/Tuning: 32B Access Enable
[00:48:42] [PASSED] gt_tunings/Tuning: L3 cache
[00:48:42] [PASSED] gt_tunings/Tuning: L3 cache - media
[00:48:42] [PASSED] gt_tunings/Tuning: Compression Overfetch
[00:48:42] [PASSED] gt_tunings/Tuning: Compression Overfetch - media
[00:48:42] [PASSED] gt_tunings/Tuning: Enable compressible partial write overfetch in L3
[00:48:42] [PASSED] gt_tunings/Tuning: Enable compressible partial write overfetch in L3 - media
[00:48:42] [PASSED] gt_tunings/Tuning: L2 Overfetch Compressible Only
[00:48:42] [PASSED] gt_tunings/Tuning: L2 Overfetch Compressible Only - media
[00:48:42] [PASSED] gt_tunings/Tuning: Stateless compression control
[00:48:42] [PASSED] gt_tunings/Tuning: Stateless compression control - media
[00:48:42] [PASSED] gt_tunings/Tuning: L3 RW flush all Cache
[00:48:42] [PASSED] gt_tunings/Tuning: L3 RW flush all cache - media
[00:48:42] [PASSED] gt_tunings/Tuning: Set STLB Bank Hash Mode to 4KB
[00:48:42] ============== [PASSED] xe_rtp_table_gt_test ===============
[00:48:42] ================== xe_rtp_table_oob_test  ==================
[00:48:42] [PASSED] oob_was/1607983814
[00:48:42] [PASSED] oob_was/16010904313
[00:48:42] [PASSED] oob_was/18022495364
[00:48:42] [PASSED] oob_was/22012773006
[00:48:42] [PASSED] oob_was/14014475959
[00:48:42] [PASSED] oob_was/22011391025
[00:48:42] [PASSED] oob_was/22012727170
[00:48:42] [PASSED] oob_was/22012727685
[00:48:42] [PASSED] oob_was/22016596838
[00:48:42] [PASSED] oob_was/18020744125
[00:48:42] [PASSED] oob_was/1409600907
[00:48:42] [PASSED] oob_was/22014953428
[00:48:42] [PASSED] oob_was/16017236439
[00:48:42] [PASSED] oob_was/14019821291
[00:48:42] [PASSED] oob_was/14015076503
[00:48:42] [PASSED] oob_was/14018913170
[00:48:42] [PASSED] oob_was/14018094691
[00:48:42] [PASSED] oob_was/18024947630
[00:48:42] [PASSED] oob_was/16022287689
[00:48:42] [PASSED] oob_was/13011645652
[00:48:42] [PASSED] oob_was/14022293748
[00:48:42] [PASSED] oob_was/22019794406
[00:48:42] [PASSED] oob_was/22019338487
[00:48:42] [PASSED] oob_was/16023588340
[00:48:42] [PASSED] oob_was/14019789679
[00:48:42] [PASSED] oob_was/14022866841
[00:48:42] [PASSED] oob_was/16021333562
[00:48:42] [PASSED] oob_was/14016712196
[00:48:42] [PASSED] oob_was/14015568240
[00:48:42] [PASSED] oob_was/18013179988
[00:48:42] [PASSED] oob_was/1508761755
[00:48:42] [PASSED] oob_was/16023105232
[00:48:42] [PASSED] oob_was/16026508708
[00:48:42] [PASSED] oob_was/14020001231
[00:48:42] [PASSED] oob_was/16023683509
[00:48:42] [PASSED] oob_was/14025515070
[00:48:42] [PASSED] oob_was/15015404425_disable
[00:48:42] [PASSED] oob_was/16026007364
[00:48:42] [PASSED] oob_was/14020316580
[00:48:42] [PASSED] oob_was/14025883347
[00:48:42] [PASSED] oob_was/16029380221
[00:48:42] [PASSED] oob_was/22022079272
[00:48:42] [PASSED] oob_was/16029897822
[00:48:42] [PASSED] oob_was/14027054324
[00:48:42] ============== [PASSED] xe_rtp_table_oob_test ==============
[00:48:42] ================ xe_rtp_table_dev_oob_test  ================
[00:48:42] [PASSED] device_oob_was/22010954014
[00:48:42] [PASSED] device_oob_was/15015404425
[00:48:42] [PASSED] device_oob_was/22019338487_display
[00:48:42] [PASSED] device_oob_was/14022085890
[00:48:42] [PASSED] device_oob_was/14026539277
[00:48:42] [PASSED] device_oob_was/14026633728
[00:48:42] [PASSED] device_oob_was/14026746987
[00:48:42] [PASSED] device_oob_was/14026779378
[00:48:42] ============ [PASSED] xe_rtp_table_dev_oob_test ============
[00:48:42] ========== xe_rtp_table_missing_upper_bound_test  ==========
[00:48:42] [PASSED] register_whitelist/WaAllowPMDepthAndInvocationCountAccessFromUMD, 1408556865
[00:48:42] [PASSED] register_whitelist/1508744258, 14012131227, 1808121037
[00:48:42] [PASSED] register_whitelist/1806527549
[00:48:42] [PASSED] register_whitelist/allow_read_ctx_timestamp
[00:48:42] [PASSED] register_whitelist/allow_read_queue_timestamp
[00:48:42] [PASSED] register_whitelist/16014440446
[00:48:42] [PASSED] register_whitelist/16017236439
[00:48:42] [PASSED] register_whitelist/16020183090
[00:48:42] [PASSED] register_whitelist/14024997852
[00:48:42] [PASSED] register_whitelist/14024997852
[00:48:42] ====== [PASSED] xe_rtp_table_missing_upper_bound_test ======
[00:48:42] =============== [PASSED] xe_rtp_tables_test ================
[00:48:42] =================== xe_rtp (3 subtests) ====================
[00:48:42] =================== xe_rtp_rules_tests  ====================
[00:48:42] [PASSED] no
[00:48:42] [PASSED] yes
[00:48:42] [PASSED] no-and-no
[00:48:42] [PASSED] no-and-yes
[00:48:42] [PASSED] yes-and-no
[00:48:42] [PASSED] yes-and-yes
[00:48:42] [PASSED] no-or-no
[00:48:42] [PASSED] no-or-yes
[00:48:42] [PASSED] yes-or-no
[00:48:42] [PASSED] yes-or-yes
[00:48:42] [PASSED] no-yes-or-yes-no
[00:48:42] [PASSED] no-yes-or-yes-yes
[00:48:42] [PASSED] yes-yes-or-no-yes
[00:48:42] [PASSED] yes-yes-or-yes-yes
[00:48:42] [PASSED] no-no-or-yes-or-no
[00:48:42] [PASSED] or
[00:48:42] [PASSED] or-yes
[00:48:42] [PASSED] or-no
[00:48:42] [PASSED] yes-or
[00:48:42] [PASSED] no-or
[00:48:42] [PASSED] no-or-or-yes
[00:48:42] [PASSED] yes-or-or-no
[00:48:42] [PASSED] no-or-or-no
[00:48:42] [PASSED] missing-context-engine-class
[00:48:42] [PASSED] missing-context-engine-class-or-yes
[00:48:42] [PASSED] missing-context-engine-class-or-or-yes
[00:48:42] =============== [PASSED] xe_rtp_rules_tests ================
[00:48:42] =============== xe_rtp_process_to_sr_tests  ================
[00:48:42] [PASSED] coalesce-same-reg
[00:48:42] [PASSED] coalesce-same-reg-literal-and-func
[00:48:42] [PASSED] no-match-no-add
[00:48:42] [PASSED] two-regs-two-entries
[00:48:42] [PASSED] clr-one-set-other
[00:48:42] [PASSED] set-field
[00:48:42] [PASSED] conflict-duplicate
[00:48:42] [PASSED] conflict-not-disjoint
[00:48:42] [PASSED] conflict-not-disjoint-literal-and-func
[00:48:42] [PASSED] conflict-reg-type
[00:48:42] [PASSED] bad-mcr-reg-forced-to-regular
[00:48:42] [PASSED] bad-regular-reg-forced-to-mcr
[00:48:42] =========== [PASSED] xe_rtp_process_to_sr_tests ============
[00:48:42] ================== xe_rtp_process_tests  ===================
[00:48:42] [PASSED] active1
[00:48:42] [PASSED] active2
[00:48:42] [PASSED] active-inactive
[00:48:42] [PASSED] inactive-active
[00:48:42] [PASSED] inactive-active-inactive
[00:48:42] [PASSED] inactive-inactive-inactive
[00:48:42] ============== [PASSED] xe_rtp_process_tests ===============
[00:48:42] ===================== [PASSED] xe_rtp ======================
[00:48:42] ==================== xe_wa (1 subtest) =====================
[00:48:42] ======================== xe_wa_gt  =========================
[00:48:42] [PASSED] TIGERLAKE B0
[00:48:42] [PASSED] DG1 A0
[00:48:42] [PASSED] DG1 B0
[00:48:42] [PASSED] ALDERLAKE_S A0
[00:48:42] [PASSED] ALDERLAKE_S B0
[00:48:42] [PASSED] ALDERLAKE_S C0
[00:48:42] [PASSED] ALDERLAKE_S D0
[00:48:42] [PASSED] ALDERLAKE_P A0
[00:48:42] [PASSED] ALDERLAKE_P B0
[00:48:42] [PASSED] ALDERLAKE_P C0
[00:48:42] [PASSED] ALDERLAKE_S RPLS D0
[00:48:42] [PASSED] ALDERLAKE_P RPLU E0
[00:48:42] [PASSED] DG2 G10 C0
[00:48:42] [PASSED] DG2 G11 B1
[00:48:42] [PASSED] DG2 G12 A1
[00:48:42] [PASSED] METEORLAKE 12.70(Xe_LPG) A0 13.00(Xe_LPM+) A0
[00:48:42] [PASSED] METEORLAKE 12.71(Xe_LPG) A0 13.00(Xe_LPM+) A0
[00:48:42] [PASSED] METEORLAKE 12.74(Xe_LPG+) A0 13.00(Xe_LPM+) A0
[00:48:42] [PASSED] LUNARLAKE 20.04(Xe2_LPG) A0 20.00(Xe2_LPM) A0
[00:48:42] [PASSED] LUNARLAKE 20.04(Xe2_LPG) B0 20.00(Xe2_LPM) A0
[00:48:42] [PASSED] BATTLEMAGE 20.01(Xe2_HPG) A0 13.01(Xe2_HPM) A1
[00:48:42] [PASSED] PANTHERLAKE 30.00(Xe3_LPG) A0 30.00(Xe3_LPM) A0
[00:48:42] ==================== [PASSED] xe_wa_gt =====================
[00:48:42] ====================== [PASSED] xe_wa ======================
[00:48:42] ============================================================
[00:48:42] Testing complete. Ran 793 tests: passed: 765, skipped: 28
[00:48:42] Elapsed time: 36.936s total, 1.845s configuring, 34.375s building, 0.688s running

+ for kcfg in "${kunitconfigs[@]}"
+ [[ ! -f /kernel/drivers/gpu/drm/tests/.kunitconfig ]]
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/gpu/drm/tests/.kunitconfig
[00:48:42] Configuring KUnit Kernel ...
Regenerating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[00:48:44] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
[00:49:09] Starting KUnit Kernel (1/1)...
[00:49:09] ============================================================
Running tests with:
$ .kunit/linux kunit.enable=1 mem=1G console=tty kunit_shutdown=halt
[00:49:10] ============= refcount_interrupt (4 subtests) ==============
[00:49:10] [PASSED] test_single_irq_change
[00:49:10] [PASSED] test_nested_irq_change
[00:49:10] [PASSED] test_multiple_irq_change
[00:49:10] [PASSED] test_irq_save
[00:49:10] =============== [PASSED] refcount_interrupt ================
[00:49:10] ============ drm_test_pick_cmdline (2 subtests) ============
[00:49:10] [PASSED] drm_test_pick_cmdline_res_1920_1080_60
[00:49:10] =============== drm_test_pick_cmdline_named  ===============
[00:49:10] [PASSED] NTSC
[00:49:10] [PASSED] NTSC-J
[00:49:10] [PASSED] PAL
[00:49:10] [PASSED] PAL-M
[00:49:10] =========== [PASSED] drm_test_pick_cmdline_named ===========
[00:49:10] ============== [PASSED] drm_test_pick_cmdline ==============
[00:49:10] == drm_test_atomic_get_connector_for_encoder (1 subtest) ===
[00:49:10] [PASSED] drm_test_drm_atomic_get_connector_for_encoder
[00:49:10] ==== [PASSED] drm_test_atomic_get_connector_for_encoder ====
[00:49:10] =========== drm_validate_clone_mode (2 subtests) ===========
[00:49:10] ============== drm_test_check_in_clone_mode  ===============
[00:49:10] [PASSED] in_clone_mode
[00:49:10] [PASSED] not_in_clone_mode
[00:49:10] ========== [PASSED] drm_test_check_in_clone_mode ===========
[00:49:10] =============== drm_test_check_valid_clones  ===============
[00:49:10] [PASSED] not_in_clone_mode
[00:49:10] [PASSED] valid_clone
[00:49:10] [PASSED] invalid_clone
[00:49:10] =========== [PASSED] drm_test_check_valid_clones ===========
[00:49:10] ============= [PASSED] drm_validate_clone_mode =============
[00:49:10] ============= drm_validate_modeset (1 subtest) =============
[00:49:10] [PASSED] drm_test_check_connector_changed_modeset
[00:49:10] ============== [PASSED] drm_validate_modeset ===============
[00:49:10] ====== drm_test_bridge_get_current_state (1 subtest) =======
[00:49:10] [PASSED] drm_test_drm_bridge_get_current_state_atomic
[00:49:10] ======== [PASSED] drm_test_bridge_get_current_state ========
[00:49:10] ====== drm_test_bridge_helper_reset_crtc (3 subtests) ======
[00:49:10] [PASSED] drm_test_drm_bridge_helper_reset_crtc_atomic
[00:49:10] [PASSED] drm_test_drm_bridge_helper_reset_crtc_atomic_disabled
[00:49:10] [PASSED] drm_test_drm_bridge_helper_hdmi_output_bus_fmts
[00:49:10] ======== [PASSED] drm_test_bridge_helper_reset_crtc ========
[00:49:10] ============== drm_bridge_alloc (2 subtests) ===============
[00:49:10] [PASSED] drm_test_drm_bridge_alloc_basic
[00:49:10] [PASSED] drm_test_drm_bridge_alloc_get_put
[00:49:10] ================ [PASSED] drm_bridge_alloc =================
[00:49:10] ============= drm_bridge_bus_fmt (5 subtests) ==============
[00:49:10] [PASSED] drm_test_bridge_rgb_yuv_rgb
[00:49:10] [PASSED] drm_test_bridge_must_convert_to_yuv444
[00:49:10] [PASSED] drm_test_bridge_hdmi_auto_rgb
[00:49:10] [PASSED] drm_test_bridge_auto_first
[00:49:10] [PASSED] drm_test_bridge_rgb_yuv_no_path
[00:49:10] =============== [PASSED] drm_bridge_bus_fmt ================
[00:49:10] ============= drm_cmdline_parser (40 subtests) =============
[00:49:10] [PASSED] drm_test_cmdline_force_d_only
[00:49:10] [PASSED] drm_test_cmdline_force_D_only_dvi
[00:49:10] [PASSED] drm_test_cmdline_force_D_only_hdmi
[00:49:10] [PASSED] drm_test_cmdline_force_D_only_not_digital
[00:49:10] [PASSED] drm_test_cmdline_force_e_only
[00:49:10] [PASSED] drm_test_cmdline_res
[00:49:10] [PASSED] drm_test_cmdline_res_vesa
[00:49:10] [PASSED] drm_test_cmdline_res_vesa_rblank
[00:49:10] [PASSED] drm_test_cmdline_res_rblank
[00:49:10] [PASSED] drm_test_cmdline_res_bpp
[00:49:10] [PASSED] drm_test_cmdline_res_refresh
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_interlaced
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_margins
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_force_off
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_force_on
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_force_on_analog
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_force_on_digital
[00:49:10] [PASSED] drm_test_cmdline_res_bpp_refresh_interlaced_margins_force_on
[00:49:10] [PASSED] drm_test_cmdline_res_margins_force_on
[00:49:10] [PASSED] drm_test_cmdline_res_vesa_margins
[00:49:10] [PASSED] drm_test_cmdline_name
[00:49:10] [PASSED] drm_test_cmdline_name_bpp
[00:49:10] [PASSED] drm_test_cmdline_name_option
[00:49:10] [PASSED] drm_test_cmdline_name_bpp_option
[00:49:10] [PASSED] drm_test_cmdline_rotate_0
[00:49:10] [PASSED] drm_test_cmdline_rotate_90
[00:49:10] [PASSED] drm_test_cmdline_rotate_180
[00:49:10] [PASSED] drm_test_cmdline_rotate_270
[00:49:10] [PASSED] drm_test_cmdline_hmirror
[00:49:10] [PASSED] drm_test_cmdline_vmirror
[00:49:10] [PASSED] drm_test_cmdline_margin_options
[00:49:10] [PASSED] drm_test_cmdline_multiple_options
[00:49:10] [PASSED] drm_test_cmdline_bpp_extra_and_option
[00:49:10] [PASSED] drm_test_cmdline_extra_and_option
[00:49:10] [PASSED] drm_test_cmdline_freestanding_options
[00:49:10] [PASSED] drm_test_cmdline_freestanding_force_e_and_options
[00:49:10] [PASSED] drm_test_cmdline_panel_orientation
[00:49:10] ================ drm_test_cmdline_invalid  =================
[00:49:10] [PASSED] margin_only
[00:49:10] [PASSED] interlace_only
[00:49:10] [PASSED] res_missing_x
[00:49:10] [PASSED] res_missing_y
[00:49:10] [PASSED] res_bad_y
[00:49:10] [PASSED] res_missing_y_bpp
[00:49:10] [PASSED] res_bad_bpp
[00:49:10] [PASSED] res_bad_refresh
[00:49:10] [PASSED] res_bpp_refresh_force_on_off
[00:49:10] [PASSED] res_invalid_mode
[00:49:10] [PASSED] res_bpp_wrong_place_mode
[00:49:10] [PASSED] name_bpp_refresh
[00:49:10] [PASSED] name_refresh
[00:49:10] [PASSED] name_refresh_wrong_mode
[00:49:10] [PASSED] name_refresh_invalid_mode
[00:49:10] [PASSED] rotate_multiple
[00:49:10] [PASSED] rotate_invalid_val
[00:49:10] [PASSED] rotate_truncated
[00:49:10] [PASSED] invalid_option
[00:49:10] [PASSED] invalid_tv_option
[00:49:10] [PASSED] truncated_tv_option
[00:49:10] ============ [PASSED] drm_test_cmdline_invalid =============
[00:49:10] =============== drm_test_cmdline_tv_options  ===============
[00:49:10] [PASSED] NTSC
[00:49:10] [PASSED] NTSC_443
[00:49:10] [PASSED] NTSC_J
[00:49:10] [PASSED] PAL
[00:49:10] [PASSED] PAL_M
[00:49:10] [PASSED] PAL_N
[00:49:10] [PASSED] SECAM
[00:49:10] [PASSED] MONO_525
[00:49:10] [PASSED] MONO_625
[00:49:10] =========== [PASSED] drm_test_cmdline_tv_options ===========
[00:49:10] =============== [PASSED] drm_cmdline_parser ================
[00:49:10] ========== drmm_connector_hdmi_init (20 subtests) ==========
[00:49:10] [PASSED] drm_test_connector_hdmi_init_valid
[00:49:10] [PASSED] drm_test_connector_hdmi_init_bpc_8
[00:49:10] [PASSED] drm_test_connector_hdmi_init_bpc_10
[00:49:10] [PASSED] drm_test_connector_hdmi_init_bpc_12
[00:49:10] [PASSED] drm_test_connector_hdmi_init_bpc_invalid
[00:49:10] [PASSED] drm_test_connector_hdmi_init_bpc_null
[00:49:10] [PASSED] drm_test_connector_hdmi_init_formats_empty
[00:49:10] [PASSED] drm_test_connector_hdmi_init_formats_no_rgb
[00:49:10] === drm_test_connector_hdmi_init_formats_yuv420_allowed  ===
[00:49:10] [PASSED] supported_formats=0x9 yuv420_allowed=1
[00:49:10] [PASSED] supported_formats=0x9 yuv420_allowed=0
[00:49:10] [PASSED] supported_formats=0x5 yuv420_allowed=1
[00:49:10] [PASSED] supported_formats=0x5 yuv420_allowed=0
[00:49:10] === [PASSED] drm_test_connector_hdmi_init_formats_yuv420_allowed ===
[00:49:10] [PASSED] drm_test_connector_hdmi_init_null_ddc
[00:49:10] [PASSED] drm_test_connector_hdmi_init_null_product
[00:49:10] [PASSED] drm_test_connector_hdmi_init_null_vendor
[00:49:10] [PASSED] drm_test_connector_hdmi_init_product_length_exact
[00:49:10] [PASSED] drm_test_connector_hdmi_init_product_length_too_long
[00:49:10] [PASSED] drm_test_connector_hdmi_init_product_valid
[00:49:10] [PASSED] drm_test_connector_hdmi_init_vendor_length_exact
[00:49:10] [PASSED] drm_test_connector_hdmi_init_vendor_length_too_long
[00:49:10] [PASSED] drm_test_connector_hdmi_init_vendor_valid
[00:49:10] ========= drm_test_connector_hdmi_init_type_valid  =========
[00:49:10] [PASSED] HDMI-A
[00:49:10] [PASSED] HDMI-B
[00:49:10] ===== [PASSED] drm_test_connector_hdmi_init_type_valid =====
[00:49:10] ======== drm_test_connector_hdmi_init_type_invalid  ========
[00:49:10] [PASSED] Unknown
[00:49:10] [PASSED] VGA
[00:49:10] [PASSED] DVI-I
[00:49:10] [PASSED] DVI-D
[00:49:10] [PASSED] DVI-A
[00:49:10] [PASSED] Composite
[00:49:10] [PASSED] SVIDEO
[00:49:10] [PASSED] LVDS
[00:49:10] [PASSED] Component
[00:49:10] [PASSED] DIN
[00:49:10] [PASSED] DP
[00:49:10] [PASSED] TV
[00:49:10] [PASSED] eDP
[00:49:10] [PASSED] Virtual
[00:49:10] [PASSED] DSI
[00:49:10] [PASSED] DPI
[00:49:10] [PASSED] Writeback
[00:49:10] [PASSED] SPI
[00:49:10] [PASSED] USB
[00:49:10] ==== [PASSED] drm_test_connector_hdmi_init_type_invalid ====
[00:49:10] ============ [PASSED] drmm_connector_hdmi_init =============
[00:49:10] ============= drmm_connector_init (3 subtests) =============
[00:49:10] [PASSED] drm_test_drmm_connector_init
[00:49:10] [PASSED] drm_test_drmm_connector_init_null_ddc
[00:49:10] ========= drm_test_drmm_connector_init_type_valid  =========
[00:49:10] [PASSED] Unknown
[00:49:10] [PASSED] VGA
[00:49:10] [PASSED] DVI-I
[00:49:10] [PASSED] DVI-D
[00:49:10] [PASSED] DVI-A
[00:49:10] [PASSED] Composite
[00:49:10] [PASSED] SVIDEO
[00:49:10] [PASSED] LVDS
[00:49:10] [PASSED] Component
[00:49:10] [PASSED] DIN
[00:49:10] [PASSED] DP
[00:49:10] [PASSED] HDMI-A
[00:49:10] [PASSED] HDMI-B
[00:49:10] [PASSED] TV
[00:49:10] [PASSED] eDP
[00:49:10] [PASSED] Virtual
[00:49:10] [PASSED] DSI
[00:49:10] [PASSED] DPI
[00:49:10] [PASSED] Writeback
[00:49:10] [PASSED] SPI
[00:49:10] [PASSED] USB
[00:49:10] ===== [PASSED] drm_test_drmm_connector_init_type_valid =====
[00:49:10] =============== [PASSED] drmm_connector_init ===============
[00:49:10] ========= drm_connector_dynamic_init (6 subtests) ==========
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_init
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_init_null_ddc
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_init_not_added
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_init_properties
[00:49:10] ===== drm_test_drm_connector_dynamic_init_type_valid  ======
[00:49:10] [PASSED] Unknown
[00:49:10] [PASSED] VGA
[00:49:10] [PASSED] DVI-I
[00:49:10] [PASSED] DVI-D
[00:49:10] [PASSED] DVI-A
[00:49:10] [PASSED] Composite
[00:49:10] [PASSED] SVIDEO
[00:49:10] [PASSED] LVDS
[00:49:10] [PASSED] Component
[00:49:10] [PASSED] DIN
[00:49:10] [PASSED] DP
[00:49:10] [PASSED] HDMI-A
[00:49:10] [PASSED] HDMI-B
[00:49:10] [PASSED] TV
[00:49:10] [PASSED] eDP
[00:49:10] [PASSED] Virtual
[00:49:10] [PASSED] DSI
[00:49:10] [PASSED] DPI
[00:49:10] [PASSED] Writeback
[00:49:10] [PASSED] SPI
[00:49:10] [PASSED] USB
[00:49:10] = [PASSED] drm_test_drm_connector_dynamic_init_type_valid ==
[00:49:10] ======== drm_test_drm_connector_dynamic_init_name  =========
[00:49:10] [PASSED] Unknown
[00:49:10] [PASSED] VGA
[00:49:10] [PASSED] DVI-I
[00:49:10] [PASSED] DVI-D
[00:49:10] [PASSED] DVI-A
[00:49:10] [PASSED] Composite
[00:49:10] [PASSED] SVIDEO
[00:49:10] [PASSED] LVDS
[00:49:10] [PASSED] Component
[00:49:10] [PASSED] DIN
[00:49:10] [PASSED] DP
[00:49:10] [PASSED] HDMI-A
[00:49:10] [PASSED] HDMI-B
[00:49:10] [PASSED] TV
[00:49:10] [PASSED] eDP
[00:49:10] [PASSED] Virtual
[00:49:10] [PASSED] DSI
[00:49:10] [PASSED] DPI
[00:49:10] [PASSED] Writeback
[00:49:10] [PASSED] SPI
[00:49:10] [PASSED] USB
[00:49:10] ==== [PASSED] drm_test_drm_connector_dynamic_init_name =====
[00:49:10] =========== [PASSED] drm_connector_dynamic_init ============
[00:49:10] ==== drm_connector_dynamic_register_early (4 subtests) =====
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_early_on_list
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_early_defer
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_early_no_init
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_early_no_mode_object
[00:49:10] ====== [PASSED] drm_connector_dynamic_register_early =======
[00:49:10] ======= drm_connector_dynamic_register (7 subtests) ========
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_on_list
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_no_defer
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_no_init
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_mode_object
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_sysfs
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_sysfs_name
[00:49:10] [PASSED] drm_test_drm_connector_dynamic_register_debugfs
[00:49:10] ========= [PASSED] drm_connector_dynamic_register ==========
[00:49:10] = drm_connector_attach_broadcast_rgb_property (2 subtests) =
[00:49:10] [PASSED] drm_test_drm_connector_attach_broadcast_rgb_property
[00:49:10] [PASSED] drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector
[00:49:10] === [PASSED] drm_connector_attach_broadcast_rgb_property ===
[00:49:10] ========== drm_get_tv_mode_from_name (2 subtests) ==========
[00:49:10] ========== drm_test_get_tv_mode_from_name_valid  ===========
[00:49:10] [PASSED] NTSC
[00:49:10] [PASSED] NTSC-443
[00:49:10] [PASSED] NTSC-J
[00:49:10] [PASSED] PAL
[00:49:10] [PASSED] PAL-M
[00:49:10] [PASSED] PAL-N
[00:49:10] [PASSED] SECAM
[00:49:10] [PASSED] Mono
[00:49:10] ====== [PASSED] drm_test_get_tv_mode_from_name_valid =======
[00:49:10] [PASSED] drm_test_get_tv_mode_from_name_truncated
[00:49:10] ============ [PASSED] drm_get_tv_mode_from_name ============
[00:49:10] = drm_test_connector_hdmi_compute_mode_clock (12 subtests) =
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb_10bpc_vic_1
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb_12bpc_vic_1
[00:49:10] [PASSED] drm_test_drm_hdmi_compute_mode_clock_rgb_double
[00:49:10] = drm_test_connector_hdmi_compute_mode_clock_yuv420_valid  =
[00:49:10] [PASSED] VIC 96
[00:49:10] [PASSED] VIC 97
[00:49:10] [PASSED] VIC 101
[00:49:10] [PASSED] VIC 102
[00:49:10] [PASSED] VIC 106
[00:49:10] [PASSED] VIC 107
[00:49:10] === [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv420_valid ===
[00:49:10] [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv420_10_bpc
[00:49:10] [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv420_12_bpc
[00:49:10] [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv422_8_bpc
[00:49:10] [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv422_10_bpc
[00:49:10] [PASSED] drm_test_connector_hdmi_compute_mode_clock_yuv422_12_bpc
[00:49:10] === [PASSED] drm_test_connector_hdmi_compute_mode_clock ====
[00:49:10] == drm_hdmi_connector_get_broadcast_rgb_name (2 subtests) ==
[00:49:10] === drm_test_drm_hdmi_connector_get_broadcast_rgb_name  ====
[00:49:10] [PASSED] Automatic
[00:49:10] [PASSED] Full
[00:49:10] [PASSED] Limited 16:235
[00:49:10] === [PASSED] drm_test_drm_hdmi_connector_get_broadcast_rgb_name ===
[00:49:10] [PASSED] drm_test_drm_hdmi_connector_get_broadcast_rgb_name_invalid
[00:49:10] ==== [PASSED] drm_hdmi_connector_get_broadcast_rgb_name ====
[00:49:10] == drm_hdmi_connector_get_output_format_name (2 subtests) ==
[00:49:10] === drm_test_drm_hdmi_connector_get_output_format_name  ====
[00:49:10] [PASSED] RGB
[00:49:10] [PASSED] YUV 4:2:0
[00:49:10] [PASSED] YUV 4:2:2
[00:49:10] [PASSED] YUV 4:4:4
[00:49:10] === [PASSED] drm_test_drm_hdmi_connector_get_output_format_name ===
[00:49:10] [PASSED] drm_test_drm_hdmi_connector_get_output_format_name_invalid
[00:49:10] ==== [PASSED] drm_hdmi_connector_get_output_format_name ====
[00:49:10] ============= drm_damage_helper (21 subtests) ==============
[00:49:10] [PASSED] drm_test_damage_iter_no_damage
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_fractional_src
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_src_moved
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_fractional_src_moved
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_not_visible
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_no_crtc
[00:49:10] [PASSED] drm_test_damage_iter_no_damage_no_fb
[00:49:10] [PASSED] drm_test_damage_iter_simple_damage
[00:49:10] [PASSED] drm_test_damage_iter_single_damage
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_intersect_src
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_outside_src
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_fractional_src
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_intersect_fractional_src
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_outside_fractional_src
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_src_moved
[00:49:10] [PASSED] drm_test_damage_iter_single_damage_fractional_src_moved
[00:49:10] [PASSED] drm_test_damage_iter_damage
[00:49:10] [PASSED] drm_test_damage_iter_damage_one_intersect
[00:49:10] [PASSED] drm_test_damage_iter_damage_one_outside
[00:49:10] [PASSED] drm_test_damage_iter_damage_src_moved
[00:49:10] [PASSED] drm_test_damage_iter_damage_not_visible
[00:49:10] ================ [PASSED] drm_damage_helper ================
[00:49:10] ============== drm_dp_mst_helper (3 subtests) ==============
[00:49:10] ============== drm_test_dp_mst_calc_pbn_mode  ==============
[00:49:10] [PASSED] Clock 154000 BPP 30 DSC disabled
[00:49:10] [PASSED] Clock 234000 BPP 30 DSC disabled
[00:49:10] [PASSED] Clock 297000 BPP 24 DSC disabled
[00:49:10] [PASSED] Clock 332880 BPP 24 DSC enabled
[00:49:10] [PASSED] Clock 324540 BPP 24 DSC enabled
[00:49:10] ========== [PASSED] drm_test_dp_mst_calc_pbn_mode ==========
[00:49:10] ============== drm_test_dp_mst_calc_pbn_div  ===============
[00:49:10] [PASSED] Link rate 2000000 lane count 4
[00:49:10] [PASSED] Link rate 2000000 lane count 2
[00:49:10] [PASSED] Link rate 2000000 lane count 1
[00:49:10] [PASSED] Link rate 1350000 lane count 4
[00:49:10] [PASSED] Link rate 1350000 lane count 2
[00:49:10] [PASSED] Link rate 1350000 lane count 1
[00:49:10] [PASSED] Link rate 1000000 lane count 4
[00:49:10] [PASSED] Link rate 1000000 lane count 2
[00:49:10] [PASSED] Link rate 1000000 lane count 1
[00:49:10] [PASSED] Link rate 810000 lane count 4
[00:49:10] [PASSED] Link rate 810000 lane count 2
[00:49:10] [PASSED] Link rate 810000 lane count 1
[00:49:10] [PASSED] Link rate 540000 lane count 4
[00:49:10] [PASSED] Link rate 540000 lane count 2
[00:49:10] [PASSED] Link rate 540000 lane count 1
[00:49:10] [PASSED] Link rate 270000 lane count 4
[00:49:10] [PASSED] Link rate 270000 lane count 2
[00:49:10] [PASSED] Link rate 270000 lane count 1
[00:49:10] [PASSED] Link rate 162000 lane count 4
[00:49:10] [PASSED] Link rate 162000 lane count 2
[00:49:10] [PASSED] Link rate 162000 lane count 1
[00:49:10] ========== [PASSED] drm_test_dp_mst_calc_pbn_div ===========
[00:49:10] ========= drm_test_dp_mst_sideband_msg_req_decode  =========
[00:49:10] [PASSED] DP_ENUM_PATH_RESOURCES with port number
[00:49:10] [PASSED] DP_POWER_UP_PHY with port number
[00:49:10] [PASSED] DP_POWER_DOWN_PHY with port number
[00:49:10] [PASSED] DP_ALLOCATE_PAYLOAD with SDP stream sinks
[00:49:10] [PASSED] DP_ALLOCATE_PAYLOAD with port number
[00:49:10] [PASSED] DP_ALLOCATE_PAYLOAD with VCPI
[00:49:10] [PASSED] DP_ALLOCATE_PAYLOAD with PBN
[00:49:10] [PASSED] DP_QUERY_PAYLOAD with port number
[00:49:10] [PASSED] DP_QUERY_PAYLOAD with VCPI
[00:49:10] [PASSED] DP_REMOTE_DPCD_READ with port number
[00:49:10] [PASSED] DP_REMOTE_DPCD_READ with DPCD address
[00:49:10] [PASSED] DP_REMOTE_DPCD_READ with max number of bytes
[00:49:10] [PASSED] DP_REMOTE_DPCD_WRITE with port number
[00:49:10] [PASSED] DP_REMOTE_DPCD_WRITE with DPCD address
[00:49:10] [PASSED] DP_REMOTE_DPCD_WRITE with data array
[00:49:10] [PASSED] DP_REMOTE_I2C_READ with port number
[00:49:10] [PASSED] DP_REMOTE_I2C_READ with I2C device ID
[00:49:10] [PASSED] DP_REMOTE_I2C_READ with transactions array
[00:49:10] [PASSED] DP_REMOTE_I2C_WRITE with port number
[00:49:10] [PASSED] DP_REMOTE_I2C_WRITE with I2C device ID
[00:49:10] [PASSED] DP_REMOTE_I2C_WRITE with data array
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with stream ID
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with client ID
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with stream event
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with valid stream event
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with stream behavior
[00:49:10] [PASSED] DP_QUERY_STREAM_ENC_STATUS with a valid stream behavior
[00:49:10] ===== [PASSED] drm_test_dp_mst_sideband_msg_req_decode =====
[00:49:10] ================ [PASSED] drm_dp_mst_helper ================
[00:49:10] ================== drm_exec (7 subtests) ===================
[00:49:10] [PASSED] sanitycheck
[00:49:10] [PASSED] test_lock
[00:49:10] [PASSED] test_lock_unlock
[00:49:10] [PASSED] test_duplicates
[00:49:10] [PASSED] test_prepare
[00:49:10] [PASSED] test_prepare_array
[00:49:10] [PASSED] test_multiple_loops
[00:49:10] ==================== [PASSED] drm_exec =====================
[00:49:10] =========== drm_format_helper_test (17 subtests) ===========
[00:49:10] ============== drm_test_fb_xrgb8888_to_gray8  ==============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========== [PASSED] drm_test_fb_xrgb8888_to_gray8 ==========
[00:49:10] ============= drm_test_fb_xrgb8888_to_rgb332  ==============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========= [PASSED] drm_test_fb_xrgb8888_to_rgb332 ==========
[00:49:10] ============= drm_test_fb_xrgb8888_to_rgb565  ==============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========= [PASSED] drm_test_fb_xrgb8888_to_rgb565 ==========
[00:49:10] ============ drm_test_fb_xrgb8888_to_xrgb1555  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_xrgb1555 =========
[00:49:10] ============ drm_test_fb_xrgb8888_to_argb1555  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_argb1555 =========
[00:49:10] ============ drm_test_fb_xrgb8888_to_rgba5551  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_rgba5551 =========
[00:49:10] ============= drm_test_fb_xrgb8888_to_rgb888  ==============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========= [PASSED] drm_test_fb_xrgb8888_to_rgb888 ==========
[00:49:10] ============= drm_test_fb_xrgb8888_to_bgr888  ==============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========= [PASSED] drm_test_fb_xrgb8888_to_bgr888 ==========
[00:49:10] ============ drm_test_fb_xrgb8888_to_argb8888  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_argb8888 =========
[00:49:10] =========== drm_test_fb_xrgb8888_to_xrgb2101010  ===========
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======= [PASSED] drm_test_fb_xrgb8888_to_xrgb2101010 =======
[00:49:10] =========== drm_test_fb_xrgb8888_to_argb2101010  ===========
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======= [PASSED] drm_test_fb_xrgb8888_to_argb2101010 =======
[00:49:10] ============== drm_test_fb_xrgb8888_to_mono  ===============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ========== [PASSED] drm_test_fb_xrgb8888_to_mono ===========
[00:49:10] ==================== drm_test_fb_swab  =====================
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ================ [PASSED] drm_test_fb_swab =================
[00:49:10] ============ drm_test_fb_xrgb8888_to_xbgr8888  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_xbgr8888 =========
[00:49:10] ============ drm_test_fb_xrgb8888_to_abgr8888  =============
[00:49:10] [PASSED] single_pixel_source_buffer
[00:49:10] [PASSED] single_pixel_clip_rectangle
[00:49:10] [PASSED] well_known_colors
[00:49:10] [PASSED] destination_pitch
[00:49:10] ======== [PASSED] drm_test_fb_xrgb8888_to_abgr8888 =========
[00:49:10] ================= drm_test_fb_clip_offset  =================
[00:49:10] [PASSED] pass through
[00:49:10] [PASSED] horizontal offset
[00:49:10] [PASSED] vertical offset
[00:49:10] [PASSED] horizontal and vertical offset
[00:49:10] [PASSED] horizontal offset (custom pitch)
[00:49:10] [PASSED] vertical offset (custom pitch)
[00:49:10] [PASSED] horizontal and vertical offset (custom pitch)
[00:49:10] ============= [PASSED] drm_test_fb_clip_offset =============
[00:49:10] =================== drm_test_fb_memcpy  ====================
[00:49:10] [PASSED] single_pixel_source_buffer: XR24 little-endian (0x34325258)
[00:49:10] [PASSED] single_pixel_source_buffer: XRA8 little-endian (0x38415258)
[00:49:10] [PASSED] single_pixel_source_buffer: YU24 little-endian (0x34325559)
[00:49:10] [PASSED] single_pixel_clip_rectangle: XB24 little-endian (0x34324258)
[00:49:10] [PASSED] single_pixel_clip_rectangle: XRA8 little-endian (0x38415258)
[00:49:10] [PASSED] single_pixel_clip_rectangle: YU24 little-endian (0x34325559)
[00:49:10] [PASSED] well_known_colors: XB24 little-endian (0x34324258)
[00:49:10] [PASSED] well_known_colors: XRA8 little-endian (0x38415258)
[00:49:10] [PASSED] well_known_colors: YU24 little-endian (0x34325559)
[00:49:10] [PASSED] destination_pitch: XB24 little-endian (0x34324258)
[00:49:10] [PASSED] destination_pitch: XRA8 little-endian (0x38415258)
[00:49:10] [PASSED] destination_pitch: YU24 little-endian (0x34325559)
[00:49:10] =============== [PASSED] drm_test_fb_memcpy ================
[00:49:10] ============= [PASSED] drm_format_helper_test ==============
[00:49:10] ================= drm_format (18 subtests) =================
[00:49:10] [PASSED] drm_test_format_block_width_invalid
[00:49:10] [PASSED] drm_test_format_block_width_one_plane
[00:49:10] [PASSED] drm_test_format_block_width_two_plane
[00:49:10] [PASSED] drm_test_format_block_width_three_plane
[00:49:10] [PASSED] drm_test_format_block_width_tiled
[00:49:10] [PASSED] drm_test_format_block_height_invalid
[00:49:10] [PASSED] drm_test_format_block_height_one_plane
[00:49:10] [PASSED] drm_test_format_block_height_two_plane
[00:49:10] [PASSED] drm_test_format_block_height_three_plane
[00:49:10] [PASSED] drm_test_format_block_height_tiled
[00:49:10] [PASSED] drm_test_format_min_pitch_invalid
[00:49:10] [PASSED] drm_test_format_min_pitch_one_plane_8bpp
[00:49:10] [PASSED] drm_test_format_min_pitch_one_plane_16bpp
[00:49:10] [PASSED] drm_test_format_min_pitch_one_plane_24bpp
[00:49:10] [PASSED] drm_test_format_min_pitch_one_plane_32bpp
[00:49:10] [PASSED] drm_test_format_min_pitch_two_plane
[00:49:10] [PASSED] drm_test_format_min_pitch_three_plane_8bpp
[00:49:10] [PASSED] drm_test_format_min_pitch_tiled
[00:49:10] =================== [PASSED] drm_format ====================
[00:49:10] ============== drm_framebuffer (10 subtests) ===============
[00:49:10] ========== drm_test_framebuffer_check_src_coords  ==========
[00:49:10] [PASSED] Success: source fits into fb
[00:49:10] [PASSED] Fail: overflowing fb with x-axis coordinate
[00:49:10] [PASSED] Fail: overflowing fb with y-axis coordinate
[00:49:10] [PASSED] Fail: overflowing fb with source width
[00:49:10] [PASSED] Fail: overflowing fb with source height
[00:49:10] ====== [PASSED] drm_test_framebuffer_check_src_coords ======
[00:49:10] [PASSED] drm_test_framebuffer_cleanup
[00:49:10] =============== drm_test_framebuffer_create  ===============
[00:49:10] [PASSED] ABGR8888 normal sizes
[00:49:10] [PASSED] ABGR8888 max sizes
[00:49:10] [PASSED] ABGR8888 pitch greater than min required
[00:49:10] [PASSED] ABGR8888 pitch less than min required
[00:49:10] [PASSED] ABGR8888 Invalid width
[00:49:10] [PASSED] ABGR8888 Invalid buffer handle
[00:49:10] [PASSED] No pixel format
[00:49:10] [PASSED] ABGR8888 Width 0
[00:49:10] [PASSED] ABGR8888 Height 0
[00:49:10] [PASSED] ABGR8888 Out of bound height * pitch combination
[00:49:10] [PASSED] ABGR8888 Large buffer offset
[00:49:10] [PASSED] ABGR8888 Buffer offset for inexistent plane
[00:49:10] [PASSED] ABGR8888 Invalid flag
[00:49:10] [PASSED] ABGR8888 Set DRM_MODE_FB_MODIFIERS without modifiers
[00:49:10] [PASSED] ABGR8888 Valid buffer modifier
[00:49:10] [PASSED] ABGR8888 Invalid buffer modifier(DRM_FORMAT_MOD_SAMSUNG_64_32_TILE)
[00:49:10] [PASSED] ABGR8888 Extra pitches without DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] ABGR8888 Extra pitches with DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] NV12 Normal sizes
[00:49:10] [PASSED] NV12 Max sizes
[00:49:10] [PASSED] NV12 Invalid pitch
[00:49:10] [PASSED] NV12 Invalid modifier/missing DRM_MODE_FB_MODIFIERS flag
[00:49:10] [PASSED] NV12 different  modifier per-plane
[00:49:10] [PASSED] NV12 with DRM_FORMAT_MOD_SAMSUNG_64_32_TILE
[00:49:10] [PASSED] NV12 Valid modifiers without DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] NV12 Modifier for inexistent plane
[00:49:10] [PASSED] NV12 Handle for inexistent plane
[00:49:10] [PASSED] NV12 Handle for inexistent plane without DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] YVU420 DRM_MODE_FB_MODIFIERS set without modifier
[00:49:10] [PASSED] YVU420 Normal sizes
[00:49:10] [PASSED] YVU420 Max sizes
[00:49:10] [PASSED] YVU420 Invalid pitch
[00:49:10] [PASSED] YVU420 Different pitches
[00:49:10] [PASSED] YVU420 Different buffer offsets/pitches
[00:49:10] [PASSED] YVU420 Modifier set just for plane 0, without DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] YVU420 Modifier set just for planes 0, 1, without DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] YVU420 Modifier set just for plane 0, 1, with DRM_MODE_FB_MODIFIERS
[00:49:10] [PASSED] YVU420 Valid modifier
[00:49:10] [PASSED] YVU420 Different modifiers per plane
[00:49:10] [PASSED] YVU420 Modifier for inexistent plane
[00:49:10] [PASSED] YUV420_10BIT Invalid modifier(DRM_FORMAT_MOD_LINEAR)
[00:49:10] [PASSED] X0L2 Normal sizes
[00:49:10] [PASSED] X0L2 Max sizes
[00:49:10] [PASSED] X0L2 Invalid pitch
[00:49:10] [PASSED] X0L2 Pitch greater than minimum required
[00:49:10] [PASSED] X0L2 Handle for inexistent plane
[00:49:10] [PASSED] X0L2 Offset for inexistent plane, without DRM_MODE_FB_MODIFIERS set
[00:49:10] [PASSED] X0L2 Modifier without DRM_MODE_FB_MODIFIERS set
[00:49:10] [PASSED] X0L2 Valid modifier
[00:49:10] [PASSED] X0L2 Modifier for inexistent plane
[00:49:10] =========== [PASSED] drm_test_framebuffer_create ===========
[00:49:10] [PASSED] drm_test_framebuffer_free
[00:49:10] [PASSED] drm_test_framebuffer_init
[00:49:10] [PASSED] drm_test_framebuffer_init_bad_format
[00:49:10] [PASSED] drm_test_framebuffer_init_dev_mismatch
[00:49:10] [PASSED] drm_test_framebuffer_lookup
[00:49:10] [PASSED] drm_test_framebuffer_lookup_inexistent
[00:49:10] [PASSED] drm_test_framebuffer_modifiers_not_supported
[00:49:10] ================= [PASSED] drm_framebuffer =================
[00:49:10] ================ drm_gem_shmem (8 subtests) ================
[00:49:10] [PASSED] drm_gem_shmem_test_obj_create
[00:49:10] [PASSED] drm_gem_shmem_test_obj_create_private
[00:49:10] [PASSED] drm_gem_shmem_test_pin_pages
[00:49:10] [PASSED] drm_gem_shmem_test_vmap
[00:49:10] [PASSED] drm_gem_shmem_test_get_sg_table
[00:49:10] [PASSED] drm_gem_shmem_test_get_pages_sgt
[00:49:10] [PASSED] drm_gem_shmem_test_madvise
[00:49:10] [PASSED] drm_gem_shmem_test_purge
[00:49:10] ================== [PASSED] drm_gem_shmem ==================
[00:49:10] === drm_atomic_helper_connector_hdmi_check (29 subtests) ===
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_auto_cea_mode
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_auto_cea_mode_vic_1
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_full_cea_mode
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_full_cea_mode_vic_1
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_limited_cea_mode
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_limited_cea_mode_vic_1
[00:49:10] ====== drm_test_check_broadcast_rgb_cea_mode_yuv420  =======
[00:49:10] [PASSED] Automatic
[00:49:10] [PASSED] Full
[00:49:10] [PASSED] Limited 16:235
[00:49:10] == [PASSED] drm_test_check_broadcast_rgb_cea_mode_yuv420 ===
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_crtc_mode_changed
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_crtc_mode_not_changed
[00:49:10] [PASSED] drm_test_check_disable_connector
[00:49:10] [PASSED] drm_test_check_hdmi_funcs_reject_rate
[00:49:10] [PASSED] drm_test_check_max_tmds_rate_bpc_fallback_rgb
[00:49:10] [PASSED] drm_test_check_max_tmds_rate_bpc_fallback_yuv420
[00:49:10] [PASSED] drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv422
[00:49:10] [PASSED] drm_test_check_max_tmds_rate_bpc_fallback_ignore_yuv420
[00:49:10] [PASSED] drm_test_check_driver_unsupported_fallback_yuv420
[00:49:10] [PASSED] drm_test_check_output_bpc_crtc_mode_changed
[00:49:10] [PASSED] drm_test_check_output_bpc_crtc_mode_not_changed
[00:49:10] [PASSED] drm_test_check_output_bpc_dvi
[00:49:10] [PASSED] drm_test_check_output_bpc_format_vic_1
[00:49:10] [PASSED] drm_test_check_output_bpc_format_display_8bpc_only
[00:49:10] [PASSED] drm_test_check_output_bpc_format_display_rgb_only
[00:49:10] [PASSED] drm_test_check_output_bpc_format_driver_8bpc_only
[00:49:10] [PASSED] drm_test_check_output_bpc_format_driver_rgb_only
[00:49:10] [PASSED] drm_test_check_tmds_char_rate_rgb_8bpc
[00:49:10] [PASSED] drm_test_check_tmds_char_rate_rgb_10bpc
[00:49:10] [PASSED] drm_test_check_tmds_char_rate_rgb_12bpc
[00:49:10] ============ drm_test_check_hdmi_color_format  =============
[00:49:10] [PASSED] AUTO -> RGB
[00:49:10] [PASSED] YCBCR422 -> YUV422
[00:49:10] [PASSED] YCBCR420 -> YUV420
[00:49:10] [PASSED] YCBCR444 -> YUV444
[00:49:10] [PASSED] RGB -> RGB
[00:49:10] ======== [PASSED] drm_test_check_hdmi_color_format =========
[00:49:10] ======== drm_test_check_hdmi_color_format_420_only  ========
[00:49:10] [PASSED] RGB should fail
[00:49:10] [PASSED] YUV444 should fail
[00:49:10] [PASSED] YUV422 should fail
[00:49:10] [PASSED] YUV420 should work
[00:49:10] ==== [PASSED] drm_test_check_hdmi_color_format_420_only ====
[00:49:10] ===== [PASSED] drm_atomic_helper_connector_hdmi_check ======
[00:49:10] === drm_atomic_helper_connector_hdmi_reset (6 subtests) ====
[00:49:10] [PASSED] drm_test_check_broadcast_rgb_value
[00:49:10] [PASSED] drm_test_check_bpc_8_value
[00:49:10] [PASSED] drm_test_check_bpc_10_value
[00:49:10] [PASSED] drm_test_check_bpc_12_value
[00:49:10] [PASSED] drm_test_check_format_value
[00:49:10] [PASSED] drm_test_check_tmds_char_value
[00:49:10] ===== [PASSED] drm_atomic_helper_connector_hdmi_reset ======
[00:49:10] = drm_atomic_helper_connector_hdmi_mode_valid (7 subtests) =
[00:49:10] [PASSED] drm_test_check_mode_valid
[00:49:10] [PASSED] drm_test_check_mode_valid_reject
[00:49:10] [PASSED] drm_test_check_mode_valid_reject_rate
[00:49:10] [PASSED] drm_test_check_mode_valid_reject_max_clock
[00:49:10] [PASSED] drm_test_check_mode_valid_yuv420_only_max_clock
[00:49:10] [PASSED] drm_test_check_mode_valid_reject_yuv420_only_connector
[00:49:10] [PASSED] drm_test_check_mode_valid_accept_yuv420_also_connector_rgb
[00:49:10] === [PASSED] drm_atomic_helper_connector_hdmi_mode_valid ===
[00:49:10] = drm_atomic_helper_connector_hdmi_infoframes (5 subtests) =
[00:49:10] [PASSED] drm_test_check_infoframes
[00:49:10] [PASSED] drm_test_check_reject_avi_infoframe
[00:49:10] [PASSED] drm_test_check_reject_hdr_infoframe_bpc_8
[00:49:10] [PASSED] drm_test_check_reject_hdr_infoframe_bpc_10
[00:49:10] [PASSED] drm_test_check_reject_audio_infoframe
[00:49:10] === [PASSED] drm_atomic_helper_connector_hdmi_infoframes ===
[00:49:10] ================= drm_managed (2 subtests) =================
[00:49:10] [PASSED] drm_test_managed_release_action
[00:49:10] [PASSED] drm_test_managed_run_action
[00:49:10] =================== [PASSED] drm_managed ===================
[00:49:10] =================== drm_mm (6 subtests) ====================
[00:49:10] [PASSED] drm_test_mm_init
[00:49:10] [PASSED] drm_test_mm_debug
[00:49:10] [PASSED] drm_test_mm_align32
[00:49:10] [PASSED] drm_test_mm_align64
[00:49:10] [PASSED] drm_test_mm_lowest
[00:49:10] [PASSED] drm_test_mm_highest
[00:49:10] ===================== [PASSED] drm_mm ======================
[00:49:10] ============= drm_modes_analog_tv (5 subtests) =============
[00:49:10] [PASSED] drm_test_modes_analog_tv_mono_576i
[00:49:10] [PASSED] drm_test_modes_analog_tv_ntsc_480i
[00:49:10] [PASSED] drm_test_modes_analog_tv_ntsc_480i_inlined
[00:49:10] [PASSED] drm_test_modes_analog_tv_pal_576i
[00:49:10] [PASSED] drm_test_modes_analog_tv_pal_576i_inlined
[00:49:10] =============== [PASSED] drm_modes_analog_tv ===============
[00:49:10] ============== drm_plane_helper (2 subtests) ===============
[00:49:10] =============== drm_test_check_plane_state  ================
[00:49:10] [PASSED] clipping_simple
[00:49:10] [PASSED] clipping_rotate_reflect
[00:49:10] [PASSED] positioning_simple
[00:49:10] [PASSED] upscaling
[00:49:10] [PASSED] downscaling
[00:49:10] [PASSED] rounding1
[00:49:10] [PASSED] rounding2
[00:49:10] [PASSED] rounding3
[00:49:10] [PASSED] rounding4
[00:49:10] =========== [PASSED] drm_test_check_plane_state ============
[00:49:10] =========== drm_test_check_invalid_plane_state  ============
[00:49:10] [PASSED] positioning_invalid
[00:49:10] [PASSED] upscaling_invalid
[00:49:10] [PASSED] downscaling_invalid
[00:49:10] ======= [PASSED] drm_test_check_invalid_plane_state ========
[00:49:10] ================ [PASSED] drm_plane_helper =================
[00:49:10] ====== drm_connector_helper_tv_get_modes (1 subtest) =======
[00:49:10] ====== drm_test_connector_helper_tv_get_modes_check  =======
[00:49:10] [PASSED] None
[00:49:10] [PASSED] PAL
[00:49:10] [PASSED] NTSC
[00:49:10] [PASSED] Both, NTSC Default
[00:49:10] [PASSED] Both, PAL Default
[00:49:10] [PASSED] Both, NTSC Default, with PAL on command-line
[00:49:10] [PASSED] Both, PAL Default, with NTSC on command-line
[00:49:10] == [PASSED] drm_test_connector_helper_tv_get_modes_check ===
[00:49:10] ======== [PASSED] drm_connector_helper_tv_get_modes ========
[00:49:10] ================== drm_rect (9 subtests) ===================
[00:49:10] [PASSED] drm_test_rect_clip_scaled_div_by_zero
[00:49:10] [PASSED] drm_test_rect_clip_scaled_not_clipped
[00:49:10] [PASSED] drm_test_rect_clip_scaled_clipped
[00:49:10] [PASSED] drm_test_rect_clip_scaled_signed_vs_unsigned
[00:49:10] ================= drm_test_rect_intersect  =================
[00:49:10] [PASSED] top-left x bottom-right: 2x2+1+1 x 2x2+0+0
[00:49:10] [PASSED] top-right x bottom-left: 2x2+0+0 x 2x2+1-1
[00:49:10] [PASSED] bottom-left x top-right: 2x2+1-1 x 2x2+0+0
[00:49:10] [PASSED] bottom-right x top-left: 2x2+0+0 x 2x2+1+1
[00:49:10] [PASSED] right x left: 2x1+0+0 x 3x1+1+0
[00:49:10] [PASSED] left x right: 3x1+1+0 x 2x1+0+0
[00:49:10] [PASSED] up x bottom: 1x2+0+0 x 1x3+0-1
[00:49:10] [PASSED] bottom x up: 1x3+0-1 x 1x2+0+0
[00:49:10] [PASSED] touching corner: 1x1+0+0 x 2x2+1+1
[00:49:10] [PASSED] touching side: 1x1+0+0 x 1x1+1+0
[00:49:10] [PASSED] equal rects: 2x2+0+0 x 2x2+0+0
[00:49:10] [PASSED] inside another: 2x2+0+0 x 1x1+1+1
[00:49:10] [PASSED] far away: 1x1+0+0 x 1x1+3+6
[00:49:10] [PASSED] points intersecting: 0x0+5+10 x 0x0+5+10
[00:49:10] [PASSED] points not intersecting: 0x0+0+0 x 0x0+5+10
[00:49:10] ============= [PASSED] drm_test_rect_intersect =============
[00:49:10] ================ drm_test_rect_calc_hscale  ================
[00:49:10] [PASSED] normal use
[00:49:10] [PASSED] out of max range
[00:49:10] [PASSED] out of min range
[00:49:10] [PASSED] zero dst
[00:49:10] [PASSED] negative src
[00:49:10] [PASSED] negative dst
[00:49:10] ============ [PASSED] drm_test_rect_calc_hscale ============
[00:49:10] ================ drm_test_rect_calc_vscale  ================
[00:49:10] [PASSED] normal use
[00:49:10] [PASSED] out of max range
[00:49:10] [PASSED] out of min range
[00:49:10] [PASSED] zero dst
[00:49:10] [PASSED] negative src
[00:49:10] [PASSED] negative dst
[00:49:10] ============ [PASSED] drm_test_rect_calc_vscale ============
[00:49:10] ================== drm_test_rect_rotate  ===================
[00:49:10] [PASSED] reflect-x
[00:49:10] [PASSED] reflect-y
[00:49:10] [PASSED] rotate-0
[00:49:10] [PASSED] rotate-90
[00:49:10] [PASSED] rotate-180
[00:49:10] [PASSED] rotate-270
[00:49:10] ============== [PASSED] drm_test_rect_rotate ===============
[00:49:10] ================ drm_test_rect_rotate_inv  =================
[00:49:10] [PASSED] reflect-x
[00:49:10] [PASSED] reflect-y
[00:49:10] [PASSED] rotate-0
[00:49:10] [PASSED] rotate-90
[00:49:10] [PASSED] rotate-180
[00:49:10] [PASSED] rotate-270
[00:49:10] ============ [PASSED] drm_test_rect_rotate_inv =============
[00:49:10] ==================== [PASSED] drm_rect =====================
[00:49:10] ============ drm_sysfb_modeset_test (1 subtest) ============
[00:49:10] ============ drm_test_sysfb_build_fourcc_list  =============
[00:49:10] [PASSED] no native formats
[00:49:10] [PASSED] XRGB8888 as native format
[00:49:10] [PASSED] remove duplicates
[00:49:10] [PASSED] convert alpha formats
[00:49:10] [PASSED] random formats
[00:49:10] ======== [PASSED] drm_test_sysfb_build_fourcc_list =========
[00:49:10] ============= [PASSED] drm_sysfb_modeset_test ==============
[00:49:10] ================== drm_fixp (2 subtests) ===================
[00:49:10] [PASSED] drm_test_int2fixp
[00:49:10] [PASSED] drm_test_sm2fixp
[00:49:10] ==================== [PASSED] drm_fixp =====================
[00:49:10] ============================================================
[00:49:10] Testing complete. Ran 641 tests: passed: 641
[00:49:10] Elapsed time: 27.322s total, 1.825s configuring, 25.329s building, 0.143s running

+ for kcfg in "${kunitconfigs[@]}"
+ [[ ! -f /kernel/drivers/gpu/drm/ttm/tests/.kunitconfig ]]
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/gpu/drm/ttm/tests/.kunitconfig
[00:49:10] Configuring KUnit Kernel ...
Regenerating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[00:49:12] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
[00:49:22] Starting KUnit Kernel (1/1)...
[00:49:22] ============================================================
Running tests with:
$ .kunit/linux kunit.enable=1 mem=1G console=tty kunit_shutdown=halt
[00:49:22] ============= refcount_interrupt (4 subtests) ==============
[00:49:22] [PASSED] test_single_irq_change
[00:49:22] [PASSED] test_nested_irq_change
[00:49:22] [PASSED] test_multiple_irq_change
[00:49:22] [PASSED] test_irq_save
[00:49:22] =============== [PASSED] refcount_interrupt ================
[00:49:22] ================= ttm_device (5 subtests) ==================
[00:49:22] [PASSED] ttm_device_init_basic
[00:49:22] [PASSED] ttm_device_init_multiple
[00:49:22] [PASSED] ttm_device_fini_basic
[00:49:22] [PASSED] ttm_device_init_no_vma_man
[00:49:22] ================== ttm_device_init_pools  ==================
[00:49:22] [PASSED] No DMA allocations, no DMA32 required
[00:49:22] [PASSED] DMA allocations, DMA32 required
[00:49:22] [PASSED] No DMA allocations, DMA32 required
[00:49:22] [PASSED] DMA allocations, no DMA32 required
[00:49:22] ============== [PASSED] ttm_device_init_pools ==============
[00:49:22] =================== [PASSED] ttm_device ====================
[00:49:22] ================== ttm_pool (8 subtests) ===================
[00:49:22] ================== ttm_pool_alloc_basic  ===================
[00:49:22] [PASSED] One page
[00:49:22] [PASSED] More than one page
[00:49:22] [PASSED] Above the allocation limit
[00:49:22] [PASSED] One page, with coherent DMA mappings enabled
[00:49:22] [PASSED] Above the allocation limit, with coherent DMA mappings enabled
[00:49:22] ============== [PASSED] ttm_pool_alloc_basic ===============
[00:49:22] ============== ttm_pool_alloc_basic_dma_addr  ==============
[00:49:22] [PASSED] One page
[00:49:22] [PASSED] More than one page
[00:49:22] [PASSED] Above the allocation limit
[00:49:22] [PASSED] One page, with coherent DMA mappings enabled
[00:49:22] [PASSED] Above the allocation limit, with coherent DMA mappings enabled
[00:49:22] ========== [PASSED] ttm_pool_alloc_basic_dma_addr ==========
[00:49:22] [PASSED] ttm_pool_alloc_order_caching_match
[00:49:22] [PASSED] ttm_pool_alloc_caching_mismatch
[00:49:22] [PASSED] ttm_pool_alloc_order_mismatch
[00:49:22] [PASSED] ttm_pool_free_dma_alloc
[00:49:22] [PASSED] ttm_pool_free_no_dma_alloc
[00:49:22] [PASSED] ttm_pool_fini_basic
[00:49:22] ==================== [PASSED] ttm_pool =====================
[00:49:22] ================ ttm_resource (8 subtests) =================
[00:49:22] ================= ttm_resource_init_basic  =================
[00:49:22] [PASSED] Init resource in TTM_PL_SYSTEM
[00:49:22] [PASSED] Init resource in TTM_PL_VRAM
[00:49:22] [PASSED] Init resource in a private placement
[00:49:22] [PASSED] Init resource in TTM_PL_SYSTEM, set placement flags
[00:49:22] ============= [PASSED] ttm_resource_init_basic =============
[00:49:22] [PASSED] ttm_resource_init_pinned
[00:49:22] [PASSED] ttm_resource_fini_basic
[00:49:22] [PASSED] ttm_resource_manager_init_basic
[00:49:22] [PASSED] ttm_resource_manager_usage_basic
[00:49:22] [PASSED] ttm_resource_manager_set_used_basic
[00:49:22] [PASSED] ttm_sys_man_alloc_basic
[00:49:22] [PASSED] ttm_sys_man_free_basic
[00:49:22] ================== [PASSED] ttm_resource ===================
[00:49:22] =================== ttm_tt (15 subtests) ===================
[00:49:22] ==================== ttm_tt_init_basic  ====================
[00:49:22] [PASSED] Page-aligned size
[00:49:22] [PASSED] Extra pages requested
[00:49:22] ================ [PASSED] ttm_tt_init_basic ================
[00:49:22] [PASSED] ttm_tt_init_misaligned
[00:49:22] [PASSED] ttm_tt_fini_basic
[00:49:22] [PASSED] ttm_tt_fini_sg
[00:49:22] [PASSED] ttm_tt_fini_shmem
[00:49:22] [PASSED] ttm_tt_create_basic
[00:49:22] [PASSED] ttm_tt_create_invalid_bo_type
[00:49:22] [PASSED] ttm_tt_create_ttm_exists
[00:49:22] [PASSED] ttm_tt_create_failed
[00:49:22] [PASSED] ttm_tt_destroy_basic
[00:49:22] [PASSED] ttm_tt_populate_null_ttm
[00:49:22] [PASSED] ttm_tt_populate_populated_ttm
[00:49:22] [PASSED] ttm_tt_unpopulate_basic
[00:49:22] [PASSED] ttm_tt_unpopulate_empty_ttm
[00:49:22] [PASSED] ttm_tt_swapin_basic
[00:49:22] ===================== [PASSED] ttm_tt ======================
[00:49:22] =================== ttm_bo (14 subtests) ===================
[00:49:22] =========== ttm_bo_reserve_optimistic_no_ticket  ===========
[00:49:22] [PASSED] Cannot be interrupted and sleeps
[00:49:22] [PASSED] Cannot be interrupted, locks straight away
[00:49:22] [PASSED] Can be interrupted, sleeps
[00:49:22] ======= [PASSED] ttm_bo_reserve_optimistic_no_ticket =======
[00:49:22] [PASSED] ttm_bo_reserve_locked_no_sleep
[00:49:22] [PASSED] ttm_bo_reserve_no_wait_ticket
[00:49:22] [PASSED] ttm_bo_reserve_double_resv
[00:49:22] [PASSED] ttm_bo_reserve_interrupted
[00:49:22] [PASSED] ttm_bo_reserve_deadlock
[00:49:22] [PASSED] ttm_bo_unreserve_basic
[00:49:22] [PASSED] ttm_bo_unreserve_pinned
[00:49:22] [PASSED] ttm_bo_unreserve_bulk
[00:49:22] [PASSED] ttm_bo_fini_basic
[00:49:22] [PASSED] ttm_bo_fini_shared_resv
[00:49:22] [PASSED] ttm_bo_pin_basic
[00:49:22] [PASSED] ttm_bo_pin_unpin_resource
[00:49:22] [PASSED] ttm_bo_multiple_pin_one_unpin
[00:49:22] ===================== [PASSED] ttm_bo ======================
[00:49:22] ============== ttm_bo_validate (22 subtests) ===============
[00:49:22] ============== ttm_bo_init_reserved_sys_man  ===============
[00:49:22] [PASSED] Buffer object for userspace
[00:49:22] [PASSED] Kernel buffer object
[00:49:22] [PASSED] Shared buffer object
[00:49:22] ========== [PASSED] ttm_bo_init_reserved_sys_man ===========
[00:49:22] ============== ttm_bo_init_reserved_mock_man  ==============
[00:49:22] [PASSED] Buffer object for userspace
[00:49:22] [PASSED] Kernel buffer object
[00:49:22] [PASSED] Shared buffer object
[00:49:22] ========== [PASSED] ttm_bo_init_reserved_mock_man ==========
[00:49:22] [PASSED] ttm_bo_init_reserved_resv
[00:49:22] ================== ttm_bo_validate_basic  ==================
[00:49:22] [PASSED] Buffer object for userspace
[00:49:22] [PASSED] Kernel buffer object
[00:49:22] [PASSED] Shared buffer object
[00:49:22] ============== [PASSED] ttm_bo_validate_basic ==============
[00:49:22] [PASSED] ttm_bo_validate_invalid_placement
[00:49:22] ============= ttm_bo_validate_same_placement  ==============
[00:49:22] [PASSED] System manager
[00:49:22] [PASSED] VRAM manager
[00:49:22] ========= [PASSED] ttm_bo_validate_same_placement ==========
[00:49:22] [PASSED] ttm_bo_validate_failed_alloc
[00:49:22] [PASSED] ttm_bo_validate_pinned
[00:49:22] [PASSED] ttm_bo_validate_busy_placement
[00:49:22] ================ ttm_bo_validate_multihop  =================
[00:49:22] [PASSED] Buffer object for userspace
[00:49:22] [PASSED] Kernel buffer object
[00:49:22] [PASSED] Shared buffer object
[00:49:22] ============ [PASSED] ttm_bo_validate_multihop =============
[00:49:22] ========== ttm_bo_validate_no_placement_signaled  ==========
[00:49:22] [PASSED] Buffer object in system domain, no page vector
[00:49:22] [PASSED] Buffer object in system domain with an existing page vector
[00:49:22] ====== [PASSED] ttm_bo_validate_no_placement_signaled ======
[00:49:22] ======== ttm_bo_validate_no_placement_not_signaled  ========
[00:49:22] [PASSED] Buffer object for userspace
[00:49:22] [PASSED] Kernel buffer object
[00:49:22] [PASSED] Shared buffer object
[00:49:22] ==== [PASSED] ttm_bo_validate_no_placement_not_signaled ====
[00:49:22] [PASSED] ttm_bo_validate_move_fence_signaled
[00:49:22] ========= ttm_bo_validate_move_fence_not_signaled  =========
[00:49:22] [PASSED] Waits for GPU
[00:49:22] [PASSED] Tries to lock straight away
[00:49:22] ===== [PASSED] ttm_bo_validate_move_fence_not_signaled =====
[00:49:22] [PASSED] ttm_bo_validate_swapout
[00:49:22] [PASSED] ttm_bo_validate_happy_evict
[00:49:22] [PASSED] ttm_bo_validate_all_pinned_evict
[00:49:22] [PASSED] ttm_bo_validate_allowed_only_evict
[00:49:22] [PASSED] ttm_bo_validate_deleted_evict
[00:49:22] [PASSED] ttm_bo_validate_busy_domain_evict
[00:49:22] [PASSED] ttm_bo_validate_evict_gutting
[00:49:22] [PASSED] ttm_bo_validate_recrusive_evict
[00:49:22] ================= [PASSED] ttm_bo_validate =================
[00:49:22] ============================================================
[00:49:22] Testing complete. Ran 106 tests: passed: 106
[00:49:22] Elapsed time: 12.117s total, 1.813s configuring, 10.037s building, 0.225s running

+ for kcfg in "${kunitconfigs[@]}"
+ [[ ! -f /kernel/drivers/dma-buf/.kunitconfig ]]
+ /kernel/tools/testing/kunit/kunit.py run --kunitconfig /kernel/drivers/dma-buf/.kunitconfig
[00:49:22] Configuring KUnit Kernel ...
Regenerating .config ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
[00:49:24] Building KUnit Kernel ...
Populating config with:
$ make ARCH=um O=.kunit olddefconfig
Building with:
$ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=48
[00:49:32] Starting KUnit Kernel (1/1)...
[00:49:32] ============================================================
Running tests with:
$ .kunit/linux kunit.enable=1 mem=1G console=tty kunit_shutdown=halt
[00:49:33] ============= refcount_interrupt (4 subtests) ==============
[00:49:33] [PASSED] test_single_irq_change
[00:49:33] [PASSED] test_nested_irq_change
[00:49:33] [PASSED] test_multiple_irq_change
[00:49:33] [PASSED] test_irq_save
[00:49:33] =============== [PASSED] refcount_interrupt ================
[00:49:33] =============== dma-buf-fence (12 subtests) ================
[00:49:33] [PASSED] test_sanitycheck
[00:49:33] [PASSED] test_signaling
[00:49:33] [PASSED] test_add_callback
[00:49:33] [PASSED] test_late_add_callback
[00:49:33] [PASSED] test_rm_callback
[00:49:33] [PASSED] test_late_rm_callback
[00:49:33] [PASSED] test_status
[00:49:33] [PASSED] test_error
[00:49:33] [PASSED] test_wait
[00:49:33] [PASSED] test_wait_timeout
[00:49:33] [PASSED] test_stub
[00:49:33] [SKIPPED] test_race_signal_callback (requires at least 2 CPUs)
[00:49:33] ================== [PASSED] dma-buf-fence ==================
[00:49:33] ============ dma-buf-fence-chain (11 subtests) =============
[00:49:33] [PASSED] test_sanitycheck
[00:49:33] [PASSED] test_find_seqno
[00:49:33] [PASSED] test_find_signaled
[00:49:33] [PASSED] test_find_out_of_order
[00:49:38] [PASSED] test_find_gap
[00:49:38] [PASSED] test_find_race
[00:49:38] [PASSED] test_signal_forward
[00:49:38] [PASSED] test_signal_backward
[00:49:38] [PASSED] test_wait_forward
[00:49:38] [PASSED] test_wait_backward
[00:49:38] [PASSED] test_wait_random
[00:49:38] =============== [PASSED] dma-buf-fence-chain ===============
[00:49:38] ============ dma-buf-fence-unwrap (10 subtests) ============
[00:49:38] [PASSED] test_sanitycheck
[00:49:38] [PASSED] test_unwrap_array
[00:49:38] [PASSED] test_unwrap_chain
[00:49:38] [PASSED] test_unwrap_chain_array
[00:49:38] [PASSED] test_unwrap_merge
[00:49:38] [PASSED] test_unwrap_merge_duplicate
[00:49:38] [PASSED] test_unwrap_merge_seqno
[00:49:38] [PASSED] test_unwrap_merge_order
[00:49:38] [PASSED] test_unwrap_merge_complex
[00:49:38] [PASSED] test_unwrap_merge_complex_seqno
[00:49:38] ============== [PASSED] dma-buf-fence-unwrap ===============
[00:49:38] ================ dma-buf-resv (5 subtests) =================
[00:49:38] [PASSED] test_sanitycheck
[00:49:38] ===================== test_signaling  ======================
[00:49:38] [PASSED] kernel
[00:49:38] [PASSED] write
[00:49:38] [PASSED] read
[00:49:38] [PASSED] bookkeep
[00:49:38] ================= [PASSED] test_signaling ==================
[00:49:38] ====================== test_for_each  ======================
[00:49:38] [PASSED] kernel
[00:49:38] [PASSED] write
[00:49:38] [PASSED] read
[00:49:38] [PASSED] bookkeep
[00:49:38] ================== [PASSED] test_for_each ==================
[00:49:38] ================= test_for_each_unlocked  ==================
[00:49:38] [PASSED] kernel
[00:49:38] [PASSED] write
[00:49:38] [PASSED] read
[00:49:38] [PASSED] bookkeep
[00:49:38] ============= [PASSED] test_for_each_unlocked ==============
[00:49:38] ===================== test_get_fences  =====================
[00:49:38] [PASSED] kernel
[00:49:38] [PASSED] write
[00:49:38] [PASSED] read
[00:49:38] [PASSED] bookkeep
[00:49:38] ================= [PASSED] test_get_fences =================
[00:49:38] ================== [PASSED] dma-buf-resv ===================
[00:49:38] ============================================================
[00:49:38] Testing complete. Ran 54 tests: passed: 53, skipped: 1
[00:49:38] Elapsed time: 15.831s total, 1.761s configuring, 8.697s building, 5.345s running

+ cleanup
++ stat -c %u:%g /kernel
+ chown -R 1003:1003 /kernel



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs
  2026-09-04  0:36   ` sashiko-bot
@ 2026-09-04  0:57     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  0:57 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:36:49AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] PT jobs on wedged, killed, or banned queues silently report success to the DRM scheduler, masking failures.
> - [High] Memory leak of xe_pt_job_ops and dma_fence references if a PT job is aborted before execution.
> --
> 
> commit 86f7996687fb641831b3ca6cb60d0fed641a6978
> Author: Matthew Brost <matthew.brost@intel.com>
> 
> drm/xe: Update GuC submission backend to run PT jobs
> 
> This commit teaches the GuC submission backend to execute PT (Page Table) jobs.
> PT jobs bypass GPU execution for the final step of a bind job, instead using
> the CPU to program the required page tables.
> 
> Link: https://patch.msgid.link/20260228013501.106680-11-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
> > --- a/drivers/gpu/drm/xe/xe_guc_submit.c
> > +++ b/drivers/gpu/drm/xe/xe_guc_submit.c
> > @@ -1261,14 +1277,25 @@ guc_exec_queue_run_job(struct drm_sched_job *drm_job)
> 
> [ ... ]
> 
> >  	if (!killed_or_banned_or_wedged && !xe_sched_job_is_error(job)) {
> 
> [ ... ]
> 
> > +		if (is_pt_job(job)) {
> > +			xe_gt_assert(guc_to_gt(guc), !exec_queue_registered(q));
> > +			run_pt_job(job);
> > +		} else {
> > +			if (!exec_queue_registered(q))
> > +				register_exec_queue(q, GUC_CONTEXT_NORMAL);
> > +			if (!job->restore_replay)
> > +				q->ring_ops->emit_job(job);
> > +			submit_exec_queue(q, job);
> > +		}
> >  		job->restore_replay = false;
> >  	}
> >  
> > +	if (is_pt_job(job)) {
> > +		xe_pt_job_ops_put(job->pt_update[0].pt_job_ops);
> > +		dma_fence_put(job->fence);	/* Drop ref from xe_sched_job_arm */
> > +		return NULL;
> > +	}
> 
> [Severity: High]
> If the queue is wedged, killed, or banned, the execution block above is
> skipped, but this block unconditionally returns NULL for PT jobs. Can this
> cause PT jobs on failed queues to silently report success to the DRM
> scheduler and mask failures?
> 

The intent to is drop the job on sliently in this case, so code is correct.

> [Severity: High]
> This handles dropping the extra dma_fence and xe_pt_job_ops references during
> normal execution, but what happens if the DRM scheduler aborts the job before
> it executes (for example, if the queue is destroyed)?
> 
> Looking at xe_sched_job_destroy() in drivers/gpu/drm/xe/xe_sched_job.c:
> 
> > void xe_sched_job_destroy(struct kref *ref)
> > {
> >         struct xe_sched_job *job =
> >                 container_of(ref, struct xe_sched_job, refcount);
> > ...
> >         xe_sched_job_free_fences(job);
> >         dma_fence_put(job->fence);
> >         drm_sched_job_cleanup(&job->drm);
> >         job_free(job);
> > ...
> > }
> 
> It seems the destructor fails to call xe_pt_job_ops_put() for PT jobs and
> only drops the primary fence reference. Does this code leak the xe_pt_job_ops
> structure and the extra dma_fence reference taken during job arming if
> execution is aborted?

Every job put into the scheduler reaches run_job, so a non-issue.

Matt

> 
> >  run_job_out:
> >  
> >  	return job->fence;
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=10

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs
  2026-09-04  0:31   ` sashiko-bot
@ 2026-09-04  1:04     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:04 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:31:44AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [Critical] A Use-After-Free of `xe_vm` occurs because CPU-based PT updates execute asynchronously on the device-global bind queue without holding a reference to the VM.
> - [High] execlist backend ignores is_pt_job and submits PT jobs to hardware, skipping CPU updates.
> - [High] The `xe_pt_job_ops` reference is leaked when a PT job is aborted or destroyed without running.
> - [Medium] xe_sched_job_snapshot_capture reads from the wrong union member for PT jobs.
> --
> 
> commit 1bc0a17fc99eb78f42df916d66ac3038c0338a83
> Author: Matthew Brost <matthew.brost@intel.com>
> Subject: drm/xe: Enable CPU binds for jobs
> 
> This commit updates the migration layer to formulate a PT job which will issue
> CPU binds in the submission backend. All code related to GPU-based binding has
> been removed.
> 
> Link: https://patch.msgid.link/20260228013501.106680-14-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> > index 100e84b7b81df..62ae0e2e85be5 100644
> > --- a/drivers/gpu/drm/xe/xe_migrate.c
> > +++ b/drivers/gpu/drm/xe/xe_migrate.c
> > @@ -1938,162 +1867,18 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
> >  {
> 
> [ ... ]
> 
> >  	int err;
> >  
> > -	job = xe_bb_create_migration_job(pt_update_ops->q, bb,
> > -					 xe_migrate_batch_base(m, usm),
> > -					 update_idx);
> > +	job = xe_sched_job_create(pt_update_ops->q, NULL);
> >  	if (IS_ERR(job)) {
> >  		err = PTR_ERR(job);
> > -		goto err_sa;
> > +		goto err_out;
> >  	}
> >  
> > -	xe_sched_job_add_migrate_flush(job, MI_INVALIDATE_TLB);
> > +	xe_tile_assert(tile, job->is_pt_job);
> 
> [Severity: High]
> If the platform uses the execlist backend (e.g., GuC disabled), does
> execlist_run_job() properly handle CPU-only PT jobs?
> 
> Looking at the execlist backend, execlist_run_job() appears to unconditionally
> call q->ring_ops->emit_job(job) without checking job->is_pt_job. Because
> job->ptrs and job->pt_update share a union, the ring ops might read a garbage
> value (like the ops kernel pointer) as batch_addr and emit a hardware migration
> job instead.
> 
> Could this regression cause an immediate GPU fault or hang from executing an
> invalid address, while completely skipping the actual page table update on the
> CPU?
> 

Execlist backend is completely broken and not even able to be enabled
via a modparam (i.e., it is deadcode).

> >  
> >  	if (ops->pre_commit) {
> >  		pt_update->job = job;
> > @@ -2104,6 +1889,12 @@ __xe_migrate_update_pgtables(struct xe_migrate *m,
> >  	if (is_migrate)
> >  		mutex_lock(&m->job_mutex);
> >  
> > +	job->pt_update[0].vm = pt_update->vops->vm;
> 
> [Severity: Critical]
> Is it possible for the xe_vm pointer assigned here to be used after it is freed?
> 
> PT jobs are assigned a raw VM pointer without taking an xe_vm_get() reference.
> CPU binds are queued and executed on a device-global queue (xe->cpu_bind->q).
> If the user closes the VM, xe_vm_close_and_put() kills the VM's specific
> queues and frees the struct xe_vm, but it does not appear to wait for jobs
> pending on the global cpu_bind->q.
> 
> When the DRM scheduler later executes the pending PT job on the cpu_bind->q,
> could it access the now-freed vm pointer during CPU execution, resulting in a
> use-after-free regression?
> 

The PT BOs hold a reference to the VM and PT BOs are only finally
destroyed after the jobs runs.

> [Severity: Medium]
> When xe_sched_job_snapshot_capture() iterates over q->width, will it safely
> handle PT jobs?
> 
> It appears that xe_sched_job_snapshot_capture() accesses job->ptrs[i].batch_addr
> unconditionally. For PT jobs, job->ptrs aliases with job->pt_update via a union.
> 
> This causes batch_addr to overlap with the ops kernel pointer (or similar
> members) from xe_pt_update_args. Could this mangled pointer then be
> uncanonicalized and included in GPU devcoredumps, leading to corrupted state
> data and a potential kernel pointer information leak?
> 

Flagged eariler, will fix.

> > +	job->pt_update[0].tile = tile;
> > +	job->pt_update[0].ops = ops;
> > +	job->pt_update[0].pt_job_ops =
> > +		xe_pt_job_ops_get(pt_update_ops->pt_job_ops);
> 
> [Severity: High]
> What happens to the pt_job_ops reference if the PT job is aborted or destroyed
> without running?
> 
> The job destruction path in xe_sched_job_destroy() does not seem to check for
> is_pt_job, and thus skips calling xe_pt_job_ops_put(). If the job is canceled,
> aborted, or its execution queue is destroyed before it runs, the DRM scheduler
> bypasses the normal execution path and directly calls the free_job callback.
> 
> Would this permanently leak the pt_job_ops structure on those failure paths?
> 

Non-issue. Once xe_sched_job_push is called run_job must be called. We
do not a failure path between xe_pt_job_ops_get and run_job eventually
executing which drops the ref pt_job_ops.

Matt

> > +
> >  	xe_sched_job_arm(job);
> >  	fence = dma_fence_get(&job->drm.s_fence->finished);
> >  	xe_sched_job_push(job);
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=13

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 16/25] drm/xe: Add CPU bind layer
  2026-09-04  0:31   ` sashiko-bot
@ 2026-09-04  1:18     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:18 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:31:49AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [Critical] TLB invalidation on mirrored (shared) tiles uses uninitialized `start` and `last` addresses, failing to invalidate the correctly updated address range.
> - [Critical] Shallow copying of the page reclaim list (PRL) for mirrored tiles leads to a Use-After-Free and double-free of the PRL entries during TLB invalidation.
> - [Critical] The arguments `tile_mask` and `usage` are swapped when calling `unbind_op_commit()` in the `DRM_GPUVA_OP_UNMAP` case, leading to un-cleared VMAs and corrupted dma-resv state.
> - [High] Missing NULL check for the result of `drmm_kzalloc()` in `xe_cpu_bind_init()`.
> - [High] `op_prepare` mistakenly uses `XE_VMA_OPS_FLAG_WAIT_VM_KERNEL` instead of `XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP` for REMAP operations, breaking dma-resv wait semantics.
> --

All of these are correct. Will fix.

Matt

> 
> commit 87e03e5ddd64ecb931de7c6186e72459d6401535
> Author: Matthew Brost <matthew.brost@intel.com>
> Subject: drm/xe: Add CPU bind layer
> 
> This commit introduces a dedicated CPU bind layer for the xe driver, moving
> CPU bind handling out of the migrate layer since it's decoupled from
> hardware. It also updates the page table layer to generate a single bind job
> when pages are mirrored across tiles.
> 
> Link: https://patch.msgid.link/20260228013501.106680-17-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_cpu_bind.c b/drivers/gpu/drm/xe/xe_cpu_bind.c
> > new file mode 100644
> > index 0000000000000..ed79e229151b4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/xe/xe_cpu_bind.c
> > @@ -0,0 +1,295 @@
> [ ... ]
> > +int xe_cpu_bind_init(struct xe_device *xe)
> > +{
> > +	struct xe_cpu_bind *cpu_bind =
> > +		drmm_kzalloc(&xe->drm, sizeof(*cpu_bind), GFP_KERNEL);
> > +	struct xe_exec_queue *q;
> > +
> > +	q = xe_exec_queue_create_bind(xe, xe_device_get_root_tile(xe), NULL,
> > +				      EXEC_QUEUE_FLAG_KERNEL |
> > +				      EXEC_QUEUE_FLAG_MIGRATE, 0);
> > +	if (IS_ERR(q))
> > +		return PTR_ERR(q);
> > +
> > +	cpu_bind->xe = xe;
> 
> [Severity: High]
> Does this code dereference cpu_bind without checking if drmm_kzalloc()
> succeeded? If the allocation fails under memory pressure and returns NULL,
> this assignment will cause a NULL pointer dereference kernel panic during
> device probe.
> 
> > diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
> > index 7c7cd7a0365b7..821edeaecaed9 100644
> > --- a/drivers/gpu/drm/xe/xe_pt.c
> > +++ b/drivers/gpu/drm/xe/xe_pt.c
> [ ... ]
> > @@ -2395,7 +2388,7 @@ static int op_prepare(struct xe_vm *vm,
> >  
> >  		err = bind_op_prepare(vm, tile, pt_update_ops, op->map.vma,
> >  				      op->map.invalidate_on_bind);
> > -		pt_update_ops->wait_vm_kernel = true;
> > +		vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
> >  		break;
> >  	case DRM_GPUVA_OP_REMAP:
> >  	{
> > @@ -2409,12 +2402,12 @@ static int op_prepare(struct xe_vm *vm,
> >  		if (!err && op->remap.prev && !op->remap.skip_prev) {
> >  			err = bind_op_prepare(vm, tile, pt_update_ops,
> >  					      op->remap.prev, false);
> > -			pt_update_ops->wait_vm_bookkeep = true;
> > +			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
> >  		}
> >  		if (!err && op->remap.next && !op->remap.skip_next) {
> >  			err = bind_op_prepare(vm, tile, pt_update_ops,
> >  					      op->remap.next, false);
> > -			pt_update_ops->wait_vm_bookkeep = true;
> > +			vops->flags |= XE_VMA_OPS_FLAG_WAIT_VM_KERNEL;
> >  		}
> >  		break;
> >  	}
> 
> [Severity: High]
> Are the flag updates in the DRM_GPUVA_OP_REMAP branch intentionally setting
> XE_VMA_OPS_FLAG_WAIT_VM_KERNEL instead of XE_VMA_OPS_FLAG_WAIT_VM_BOOKKEEP?
> 
> The original code correctly set wait_vm_bookkeep to true for remap
> operations, but the updated version uses the KERNEL flag. This flips the
> expected synchronization dependency semantics, which could cause REMAP
> operations to incorrectly wait on KERNEL fences instead of BOOKKEEP fences,
> violating the synchronization contract.
> 
> [ ... ]
> > @@ -2604,21 +2624,19 @@ static void unbind_op_commit(struct xe_vm *vm, struct xe_tile *tile,
> >  
> >  static void range_present_and_invalidated_tile(struct xe_vm *vm,
> >  					       struct xe_svm_range *range,
> > -					       u8 tile_id)
> > +					       u8 tile_mask)
> >  {
> >  	/* All WRITE_ONCE pair with READ_ONCE in xe_vm_has_valid_gpu_mapping() */
> >  
> >  	lockdep_assert_held(&vm->svm.gpusvm.notifier_lock);
> >  
> > -	WRITE_ONCE(range->tile_present, range->tile_present | BIT(tile_id));
> > -	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~BIT(tile_id));
> > +	WRITE_ONCE(range->tile_present, range->tile_present | tile_mask);
> > +	WRITE_ONCE(range->tile_invalidated, range->tile_invalidated & ~tile_mask);
> >  }
> >  
> > -static void op_commit(struct xe_vm *vm,
> > -		      struct xe_tile *tile,
> > -		      struct xe_vm_pgtable_update_ops *pt_update_ops,
> > -		      struct xe_vma_op *op, struct dma_fence *fence,
> > -		      struct dma_fence *fence2)
> > +static void op_commit(struct xe_vm *vm, struct xe_vma_op *op,
> > +		      struct dma_fence **fences, int fence_count,
> > +		      enum dma_resv_usage usage, u8 tile_mask)
> >  {
> >  	xe_vm_assert_held(vm);
> >  
> [ ... ]
> > @@ -2657,8 +2672,8 @@ static void op_commit(struct xe_vm *vm,
> >  		struct xe_vma *vma = gpuva_to_vma(op->base.unmap.va);
> >  
> >  		if (!xe_vma_is_cpu_addr_mirror(vma))
> > -			unbind_op_commit(vm, tile, pt_update_ops, vma, fence,
> > -					 fence2);
> > +			unbind_op_commit(vm, vma, fences, fence_count,
> > +					 tile_mask, usage);
> >  		break;
> >  	}
> 
> [Severity: Critical]
> Is it possible the tile_mask and usage arguments are swapped here?
> 
> Looking at the signature for unbind_op_commit(), it expects usage before
> tile_mask:
> 
> unbind_op_commit(struct xe_vm *vm, struct xe_vma *vma,
>                  struct dma_fence **fences, int fence_count,
>                  enum dma_resv_usage usage, u8 tile_mask)
> 
> Because the arguments are passed in reverse order, the dma_resv usage enum
> is treated as the tile mask. This would mean vma->tile_present &= ~tile_mask
> evaluates with the enum value (e.g. DMA_RESV_USAGE_KERNEL = 0), leaving
> the VMA marked fully present, and un-cleared memory could be incorrectly
> freed. Simultaneously, the tile mask is passed into the dma_resv tracking
> slots as a bogus usage value.
> 
> [ ... ]
> > @@ -2737,82 +2739,83 @@ static struct xe_dep_scheduler *to_dep_scheduler(struct xe_exec_queue *q,
> >  struct dma_fence *
> > -xe_pt_update_ops_run(struct xe_tile *tile, struct xe_vma_ops *vops)
> > +xe_pt_update_ops_run(struct xe_device *xe, struct xe_vma_ops *vops)
> >  {
> [ ... ]
> > -	if (pt_update_ops->needs_invalidation) {
> > -		struct xe_dep_scheduler *dep_scheduler =
> > -			to_dep_scheduler(q, tile, tile->primary_gt, &type);
> > -
> > -		ijob = xe_tlb_inval_job_create(q, &tile->primary_gt->tlb_inval,
> > -					       dep_scheduler, vm,
> > -					       pt_update_ops->start,
> > -					       pt_update_ops->last,
> > -					       type);
> > -		if (IS_ERR(ijob)) {
> > -			err = PTR_ERR(ijob);
> > -			goto kill_vm_tile1;
> > -		}
> > -		update.ijob = ijob;
> > -		/*
> > -		 * Only add page reclaim for the primary GT. Media GT does not have
> > -		 * any PPC to flush, so enabling the PPC flush bit for media is
> > -		 * effectively a NOP and provides no performance benefit nor
> > -		 * interfere with primary GT.
> > -		 */
> > -		if (xe_page_reclaim_list_valid(&pt_update_ops->prl)) {
> > -			xe_tlb_inval_job_add_page_reclaim(ijob, &pt_update_ops->prl);
> > -			/* Release ref from alloc, job will now handle it */
> > -			xe_page_reclaim_list_invalidate(&pt_update_ops->prl);
> > -		}
> > -
> > -		if (tile->media_gt) {
> > -			dep_scheduler = to_dep_scheduler(q, tile,
> > -							 tile->media_gt, &type);
> > -
> > -			mjob = xe_tlb_inval_job_create(q,
> > -						       &tile->media_gt->tlb_inval,
> > -						       dep_scheduler, vm,
> > -						       pt_update_ops->start,
> > -						       pt_update_ops->last,
> > -						       type);
> > -			if (IS_ERR(mjob)) {
> > -				err = PTR_ERR(mjob);
> > +	if (needs_invalidation) {
> > +		for_each_tlb_inval(q, i) {
> > +			struct xe_dep_scheduler *dep_scheduler =
> > +				q->tlb_inval[i].dep_scheduler;
> > +			struct xe_tile *tile =
> > +				&xe->tiles[i / XE_MAX_GT_PER_TILE];
> > +			struct xe_vm_pgtable_update_ops *pt_update_ops =
> > +				&vops->pt_update_ops[tile->id];
> > +			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
> > +			struct xe_tlb_inval_job *ijob;
> > +			struct xe_gt *gt = i % XE_MAX_GT_PER_TILE ?
> > +				tile->media_gt : tile->primary_gt;
> > +
> > +			ijob = xe_tlb_inval_job_create(q, &gt->tlb_inval,
> > +						       dep_scheduler,
> > +						       vm, pt_update_ops->start,
> > +						       pt_update_ops->last, i);
> > +			if (IS_ERR(ijob)) {
> > +				err = PTR_ERR(ijob);
> >  				goto free_ijob;
> >  			}
> > -			update.mjob = mjob;
> > +
> > +			update.ijobs[i] = ijob;
> > +
> > +			/*
> > +			 * Only add page reclaim for the primary GT. Media GT
> > +			 * does not have any PPC to flush, so enabling the PPC
> > +			 * flush bit for media is effectively a NOP and provides
> > +			 * no performance benefit nor interfere with primary GT.
> > +			 */
> > +			if (xe_page_reclaim_list_valid(prl)) {
> > +				xe_tlb_inval_job_add_page_reclaim(ijob, prl);
> > +				/* Release ref from alloc, job will now handle it */
> > +				xe_page_reclaim_list_invalidate(prl);
> > +			}
> >  		}
> >  	}
> 
> [Severity: Critical]
> Does this code inadvertently create TLB invalidation jobs with uninitialized
> bounds for secondary mirrored tiles?
> 
> When a tile shares a page table (mirroring disabled), the initialization of
> pt_update_ops->start and pt_update_ops->last is skipped during the prepare
> phase. Consequently, they remain 0. Here, xe_tlb_inval_job_create() relies
> on these bounds, meaning the secondary tile's GTs could execute an invalid
> TLB flush for the [0, 0] range, leaving stale TLB entries for the actual
> modified range.
> 
> [Severity: Critical]
> Can this loop trigger a use-after-free and double-free on the page reclaim
> list (PRL)?
> 
> For secondary mirrored tiles, the PRL is set up as a shallow copy of the
> primary tile's PRL without taking an additional page reference.
> When the loop iterates over the primary tile, it adds the PRL and calls
> xe_page_reclaim_list_invalidate(), which drops the page reference.
> 
> When the loop then reaches the secondary tile, its shallow-copied
> prl.entries pointer is still non-NULL but points to freed memory. The code
> will pass this dangling pointer to the secondary job and call
> xe_page_reclaim_list_invalidate() a second time, triggering a double-free.
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=16

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
  2026-09-04  0:28   ` sashiko-bot
@ 2026-09-04  1:32     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:32 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:28:52AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Incorrect ordering of xe_pm_runtime_put() and xe_force_wake_put() leading to hardware MMIO access on a potentially suspended device.
> --
> 
> commit 43d7d496e886ff3c7ea66fac7a62b351eae1e2b2
> Author: Matthew Brost <matthew.brost@intel.com>
> 
> drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch
> 
> This commit calls xe_migrate_ulls_enter upon page fault or SVM prefetch in an
> effort to speed up these critical paths. It also adjusts the teardown and exit
> sequences for ULLS migration jobs.
> 
> Link: https://patch.msgid.link/20260228013501.106680-25-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> > index 1fa236eb1a260..80efa67fc2e15 100644
> > --- a/drivers/gpu/drm/xe/xe_migrate.c
> > +++ b/drivers/gpu/drm/xe/xe_migrate.c
> > @@ -114,8 +114,8 @@ static void xe_migrate_fini(void *arg)
> >  	disable_delayed_work_sync(&m->ulls.exit_work);
> >  	mutex_lock(&m->job_mutex);
> >  	if (m->ulls.enabled) {
> > -		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
> >  		xe_pm_runtime_put(xe);
> > +		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
> 
> [Severity: High]
> Does swapping the order here risk accessing hardware MMIO while the device
> is suspended?
> 
> Because xe_pm_runtime_put() drops the runtime PM usage counter, it could
> trigger a synchronous or asynchronous runtime suspend. If the device suspends
> before xe_force_wake_put() completes its MMIO writes to release the forcewake
> domain, it could result in PCI Unsupported Request (UR) errors or device hangs.
> 
> >  		m->ulls.enabled = false;
> >  	}
> >  	mutex_unlock(&m->job_mutex);
> > @@ -589,8 +589,8 @@ static void xe_migrate_ulls_exit(struct work_struct *work)
> >  		xe_sched_job_put(job);
> >  	if (!m->ulls.enabled) {
> >  		/* Pairs with PM gets on enter */
> > -		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
> >  		xe_pm_runtime_put(xe);
> > +		xe_force_wake_put(gt_to_fw(m->q->hwe->gt), m->q->hwe->domain);
> 
> [Severity: High]
> Is this order change safe from a runtime PM perspective? 
> 
> Similar to the sequence in xe_migrate_fini(), dropping the PM reference in
> xe_migrate_ulls_exit() before releasing the forcewake domain might allow the
> device to suspend while xe_force_wake_put() is still accessing hardware MMIO
> registers.
> 

These are correct, will fix.

Matt

> >  
> >  		cancel_delayed_work(&m->ulls.exit_work);
> >  		xe_dbg(xe, "Migrate ULLS mode exit");
> 
> [ ... ]
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=23

^ permalink raw reply	[flat|nested] 48+ messages in thread

* ✓ Xe.CI.BAT: success for CPU binds and ULLS on migration queue (rev6)
  2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
                   ` (26 preceding siblings ...)
  2026-09-04  0:49 ` ✓ CI.KUnit: success " Patchwork
@ 2026-09-04  1:33 ` Patchwork
  27 siblings, 0 replies; 48+ messages in thread
From: Patchwork @ 2026-09-04  1:33 UTC (permalink / raw)
  To: Matthew Brost; +Cc: intel-xe

[-- Attachment #1: Type: text/plain, Size: 4837 bytes --]

== Series Details ==

Series: CPU binds and ULLS on migration queue (rev6)
URL   : https://patchwork.freedesktop.org/series/149888/
State : success

== Summary ==

CI Bug Log - changes from xe-5690-71bc3b7cc55631a9b3807da98e2b2880838b9623_BAT -> xe-pw-149888v6_BAT
====================================================

Summary
-------

  **SUCCESS**

  No regressions found.

  

Participating hosts (12 -> 13)
------------------------------

  Additional (1): bat-bmg-2 

Known issues
------------

  Here are the changes found in xe-pw-149888v6_BAT that come from known issues:

### IGT changes ###

#### Issues hit ####

  * igt@fbdev@write:
    - bat-bmg-2:          NOTRUN -> [SKIP][1] ([Intel XE#2134]) +4 other tests skip
   [1]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@fbdev@write.html

  * igt@kms_addfb_basic@addfb25-y-tiled-small-legacy:
    - bat-bmg-2:          NOTRUN -> [SKIP][2] ([Intel XE#2233])
   [2]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@kms_addfb_basic@addfb25-y-tiled-small-legacy.html

  * igt@kms_cursor_legacy@basic-flip-after-cursor-legacy:
    - bat-bmg-2:          NOTRUN -> [SKIP][3] ([Intel XE#2489] / [Intel XE#3419]) +13 other tests skip
   [3]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@kms_cursor_legacy@basic-flip-after-cursor-legacy.html

  * igt@kms_flip@basic-flip-vs-modeset:
    - bat-bmg-2:          NOTRUN -> [SKIP][4] ([Intel XE#2482]) +3 other tests skip
   [4]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@kms_flip@basic-flip-vs-modeset.html

  * igt@kms_frontbuffer_tracking@basic:
    - bat-bmg-2:          NOTRUN -> [SKIP][5] ([Intel XE#2434] / [Intel XE#2548] / [Intel XE#6314])
   [5]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@kms_frontbuffer_tracking@basic.html

  * igt@kms_psr@psr-sprite-plane-onoff:
    - bat-bmg-2:          NOTRUN -> [SKIP][6] ([Intel XE#2234] / [Intel XE#2850]) +2 other tests skip
   [6]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@kms_psr@psr-sprite-plane-onoff.html

  * igt@xe_exec_multi_queue@priority:
    - bat-bmg-2:          NOTRUN -> [SKIP][7] ([Intel XE#8364]) +13 other tests skip
   [7]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@xe_exec_multi_queue@priority.html

  * igt@xe_live_ktest@xe_bo@xe_ccs_migrate_kunit:
    - bat-bmg-2:          NOTRUN -> [SKIP][8] ([Intel XE#2229])
   [8]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@xe_live_ktest@xe_bo@xe_ccs_migrate_kunit.html

  * igt@xe_pat@pat-index-xehpc:
    - bat-bmg-2:          NOTRUN -> [SKIP][9] ([Intel XE#1420] / [Intel XE#7590])
   [9]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@xe_pat@pat-index-xehpc.html

  * igt@xe_pat@pat-index-xelp:
    - bat-bmg-2:          NOTRUN -> [SKIP][10] ([Intel XE#2245] / [Intel XE#7590])
   [10]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@xe_pat@pat-index-xelp.html

  * igt@xe_pat@pat-index-xelpg:
    - bat-bmg-2:          NOTRUN -> [SKIP][11] ([Intel XE#2236] / [Intel XE#7590])
   [11]: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/bat-bmg-2/igt@xe_pat@pat-index-xelpg.html

  
  [Intel XE#1420]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/1420
  [Intel XE#2134]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2134
  [Intel XE#2229]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2229
  [Intel XE#2233]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2233
  [Intel XE#2234]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2234
  [Intel XE#2236]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2236
  [Intel XE#2245]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2245
  [Intel XE#2434]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2434
  [Intel XE#2482]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2482
  [Intel XE#2489]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2489
  [Intel XE#2548]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2548
  [Intel XE#2850]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/2850
  [Intel XE#3419]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/3419
  [Intel XE#6314]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/6314
  [Intel XE#7590]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/7590
  [Intel XE#8364]: https://gitlab.freedesktop.org/drm/xe/kernel/issues/8364


Build changes
-------------

  * Linux: xe-5690-71bc3b7cc55631a9b3807da98e2b2880838b9623 -> xe-pw-149888v6

  IGT_9084: 9084
  xe-5690-71bc3b7cc55631a9b3807da98e2b2880838b9623: 71bc3b7cc55631a9b3807da98e2b2880838b9623
  xe-pw-149888v6: 149888v6

== Logs ==

For more details see: https://intel-gfx-ci.01.org/tree/intel-xe/xe-pw-149888v6/index.html

[-- Attachment #2: Type: text/html, Size: 5749 bytes --]

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles
  2026-09-04  0:29   ` sashiko-bot
@ 2026-09-04  1:33     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:33 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:29:53AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Incorrect tile mask generation causes missing TLB invalidations on secondary tiles
> - [High] Shallow copy of Page Reclaim List (PRL) leads to Double Free during TLB invalidation
> - [Medium] Unnecessary allocation of unused page table roots and scratch tables
> --
> 
> commit 084f96016724c31d7bcd97e7c49979198c29fe71
> Author: Matthew Brost <matthew.brost@intel.com>
> Subject: drm/xe: Add device flag to enable PT mirroring across tiles
> 
> Some multi-tile devices may want to mirror page tables across tiles for
> memory-bandwidth reasons, while others may not. Add a device flag that
> allows enabling or disabling page-table mirroring across tiles.
> 
> Link: https://patch.msgid.link/20260228013501.106680-18-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c
> > index 821edeaecaed9..deb33e85e6eb7 100644
> > --- a/drivers/gpu/drm/xe/xe_pt.c
> > +++ b/drivers/gpu/drm/xe/xe_pt.c
> [ ... ]
> > @@ -2542,9 +2553,20 @@ int xe_pt_update_ops_prepare(struct xe_device *xe, struct xe_vma_ops *vops)
> >  	int id, err;
> >  
> >  	for_each_tile(tile, xe, id) {
> > +		struct xe_vm_pgtable_update_ops *pt_update_ops =
> > +			&vops->pt_update_ops[id];
> > +
> >  		if (!vops->pt_update_ops[id].num_ops)
> >  			continue;
> >  
> > +		if (pt_mirroring_disabled_for_tile(vops->vm, id)) {
> > +			struct xe_page_reclaim_list *prl = &pt_update_ops->prl;
> > +
> > +			/* Transfer root PT update ops PRL to current */
> > +			*prl = vops->pt_update_ops[0].prl;
> 
> [Severity: High]
> Does this shallow copy of the Page Reclaim List (PRL) cause a double free?
> 
> By copying the structure directly without incrementing a reference count,
> multiple tiles now hold a pointer to the same page. Later, when
> xe_tlb_inval_job_create() adds this to jobs in xe_pt_update_ops_run(), both
> jobs take ownership. When the jobs finish, they will both call
> xe_page_reclaim_entries_put(), freeing the same page twice.
> 

This is correct. Will fix.

> > +			continue;
> > +		}
> > +
> >  		err = __xe_pt_update_ops_prepare(tile, vops);
> >  		if (err)
> >  			return err;
> [ ... ]
> > diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
> > index 33ef4ccc3550b..d9a9c0844c5e3 100644
> > --- a/drivers/gpu/drm/xe/xe_vm.c
> > +++ b/drivers/gpu/drm/xe/xe_vm.c
> > @@ -844,6 +844,14 @@ int xe_vm_rebind(struct xe_vm *vm, bool rebind_worker)
> >  	return err;
> >  }
> >  
> > +static u8 adjust_rebind_tile_mask(struct xe_vm *vm, u8 tile_mask)
> > +{
> > +	if (vm->xe->info.has_pt_mirror)
> > +		return tile_mask;
> > +
> > +	return (0x1 << vm->xe->info.max_gt_per_tile) - 1;
> 
> [Severity: High]
> Is max_gt_per_tile being used here incorrectly instead of the total tile count?
> 
> If a device (like PVC) has 1 GT per tile, this evaluates to 1, causing the
> generated mask to omit secondary tiles. During xe_vma_rebind(), the num_ops
> will only be incremented for tile 0.
> 
> Consequently, xe_pt_update_ops_prepare() skips preparation for the secondary
> tiles, leaving their start and last variables uninitialized (zeroed). When
> TLB invalidation jobs are created later, they use a range of [0, 0] for
> these tiles, leaving stale TLB entries that could cause memory corruption.
> 

This is correct. Will fix.

> > +}
> > +
> >  struct dma_fence *xe_vma_rebind(struct xe_vm *vm, struct xe_vma *vma, u8 tile_mask)
> >  {
> >  	struct dma_fence *fence = NULL;
> [ ... ]
> > @@ -1737,7 +1747,8 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
> >  
> >  		for_each_tile(tile, xe, id) {
> >  			if (flags & XE_VM_FLAG_MIGRATION &&
> > -			    tile->id != XE_VM_FLAG_TILE_ID(flags))
> > +			    tile->id != XE_VM_FLAG_TILE_ID(flags) &&
> > +			    (vm->xe->info.has_pt_mirror || id))
> >  				continue;
> >  
> >  			vm->pt_root[id] = xe_pt_create(vm, tile, xe->info.vm_max_level,
> >  						       &exec);
> 
> [Severity: Medium]
> For standard user VMs, does this unconditionally allocate page table roots
> and scratch tables for secondary tiles even when PT mirroring is disabled?
> 
> When has_pt_mirror is false, xe_vm_pt_root() is hardcoded to return
> vm->pt_root[0]. As a result, the allocations made here for id > 0 (and their
> associated scratch tables) are never used, wasting VRAM.
> 

Techincally, but I'm not going to worry about 4k vs functionality
required for the migrate layer.

Matt 

> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=17

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer
  2026-09-04  0:27   ` sashiko-bot
@ 2026-09-04  1:35     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:35 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:27:20AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Waiting on a dma_fence while holding a reclaim-tainted mutex causes deadlocks and lockdep splats.
> - [Medium] ULLS mode exit delay is implemented as 20ms instead of the intended 5us.
> --
> 
> commit 68ec8c46273b3023414fee593faab1cdb6c24eca
> Author: Matthew Brost <matthew.brost@intel.com>
> 
> drm/xe: Add ULLS migration job support to migration layer
> 
> This commit adds a function to enter ULLS mode for migration jobs and a
> delayed worker to exit it for power savings. The ULLS mode exit delay is
> intended to be 5us. It also updates migration job flags so the submission
> backend properly submits in ULLS mode.
> 
> Link: https://patch.msgid.link/20260228013501.106680-21-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c
> > --- a/drivers/gpu/drm/xe/xe_migrate.c
> > +++ b/drivers/gpu/drm/xe/xe_migrate.c
> > @@ -77,6 +80,14 @@ struct xe_migrate {
> >  	struct dma_fence *fence;
> >  	/** @min_chunk_size: For dgfx, Minimum chunk size */
> >  	u64 min_chunk_size;
> > +	/** @ulls: ULLS support */
> > +	struct {
> > +		/** @ulls.enabled: ULLS is enabled */
> > +		bool enabled;
> > +#define ULLS_EXIT_JIFFIES	(HZ / 50)
> 
> [Severity: Medium]
> The commit message states "ULLS mode exit delay is currently set to 5us", but
> does this macro actually result in 5us? 
> 
> Using (HZ / 50) yields 20 milliseconds on a 1000Hz kernel, and 20 milliseconds
> on a 100Hz kernel (since 100/50 is 2 jiffies). Both evaluate to 20,000us
> instead of 5us.
> 
> Additionally, delayed_work uses jiffy-resolution timers which cannot achieve
> microsecond precision. Will this cause the engine to remain awake and consume
> power for 4000x longer than intended?
> 

Code and commit message are wrong, intent is 5ms. Will fix both.

> > +		/** @ulls.exit_work: ULLS exit worker */
> > +		struct delayed_work exit_work;
> > +	} ulls;
> >  };
> 
> [ ... ]
> 
> > +static void xe_migrate_ulls_exit(struct work_struct *work)
> > +{
> 
> [ ... ]
> 
> > +	mutex_lock(&m->job_mutex);
> > +
> > +	if (!xe_exec_queue_is_idle(m->q, 1))
> > +		goto unlock_exit;
> > +
> > +	xe_sched_job_get(job);
> > +	xe_sched_job_arm(job);
> > +	job->is_ulls = true;
> > +	job->is_ulls_last = true;
> > +	fence = dma_fence_get(&job->drm.s_fence->finished);
> > +	xe_sched_job_push(job);
> > +
> > +	/* Serialize force wake put */
> > +	dma_fence_wait(fence, false);
> 
> [Severity: High]
> Can calling dma_fence_wait() here while holding m->job_mutex cause a deadlock?
> 
> The job_mutex is initialized with fs_reclaim_acquire(), which marks it as
> participating in memory reclaim paths. Waiting on a dma_fence while holding
> a reclaim-tainted lock is generally unsafe because fence signaling paths
> cannot safely allocate memory without risking a forward-progress dependency
> inversion.
> 
> If a page fault blocks on job_mutex (e.g. via xe_migrate_ulls_enter) while
> a GPU reset that requires memory reclaim is pending, could this deadlock the
> system and trigger a lockdep splat?
> 

No correct. A fence always signals without allocating more memory so no
deadlock risk.

Matt

> > +	dma_fence_put(fence);
> > +
> > +	m->ulls.enabled = false;
> > +unlock_exit:
> > +	if (job)
> > +		xe_sched_job_put(job);
> 
> [ ... ]
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=20

^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission
  2026-09-04  0:38   ` sashiko-bot
@ 2026-09-04  1:41     ` Matthew Brost
  0 siblings, 0 replies; 48+ messages in thread
From: Matthew Brost @ 2026-09-04  1:41 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: intel-xe

On Fri, Sep 04, 2026 at 12:38:37AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Critical] Direct MMIO write to the physical engine's RING_TAIL for GuC-scheduled ULLS jobs causes hardware state corruption if the context is preempted or evicted.
> --
> 
> commit f60d4c34d363517fe528679e6ff3d059b7861099
> Author: Matthew Brost <matthew.brost@intel.com>
> 
> drm/xe: Add ULLS migration job support to GuC submission
> 
> This commit adds ULLS migration job support to the GuC submission backend.
> It modifies the migration queue job limits and introduces direct hardware
> engine tail writes for subsequent ULLS jobs.
> 
> Link: https://patch.msgid.link/20260228013501.106680-24-matthew.brost@intel.com
> 
> > diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
> > index dcb4b8a4f3b78..1578d80b01b82 100644
> > --- a/drivers/gpu/drm/xe/xe_guc_submit.c
> > +++ b/drivers/gpu/drm/xe/xe_guc_submit.c
> > @@ -1207,6 +1207,11 @@ static void submit_exec_queue(struct xe_exec_queue *q, struct xe_sched_job *job)
> >  	if (exec_queue_suspended(q))
> >  		return;
> >  
> > +	if (job->is_ulls && !job->is_ulls_first) {
> > +		xe_hw_engine_write_ring_tail(q->hwe, lrc->ring.tail);
> 
> [Severity: Critical]
> Can this direct MMIO write to the engine's RING_TAIL cause hardware state
> corruption if the ULLS context is preempted or evicted?
> 

The GuC context can't ever be evicted or preempted as the migrate queue
completely owns the engine state which is way this is safe.

It is questionable if we should wait on the first ULLS job signaling so
we know the queue is on the hardware but I've yet to see any failures in
my testing. This is something to watch out for though - if we get a
random kernel queue hang, we should adjust to wait until first ULLS job
signals.

It we ever use multiple queues on the paging engine, we will also have
to use fences + ordering to ensure either all ULLS jobs are complete or
all non-ULLS jobs are complete before before jobs reach run_job().

Matt

> The driver bypasses the GuC submission interface (H2G) for ULLS jobs and
> directly writes to the engine's MMIO RING_TAIL register. If the ULLS
> migration context is not actively resident on the hardware engine at the exact
> moment of the write, the write will advance the ring tail of whichever context
> is currently resident on that engine (e.g., VM bind contexts sharing the same
> USM engine).
> 
> Since the GuC scheduler fundamentally owns engine context switching, it can
> preempt or evict the ULLS context for internal tasks or schedule other queues
> that explicitly share the USM engine. If the CPU submits a ULLS job while the
> ULLS context is swapped out, would the direct MMIO tail write hit the
> currently running context instead?
> 
> > +		xe_lrc_set_ulls_semaphore(lrc, xe_sched_job_lrc_seqno(job));
> > +	}
> > +
> >  	if (!exec_queue_enabled(q)) {
> >  		action[len++] = XE_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
> >  		action[len++] = q->guc->id;
> 
> [ ... ]
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903235842.3401722-1-matthew.brost@intel.com?part=22

^ permalink raw reply	[flat|nested] 48+ messages in thread

end of thread, other threads:[~2026-09-04  1:41 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 23:58 [PATCH v4 00/25] CPU binds and ULLS on migration queue Matthew Brost
2026-09-03 23:58 ` [PATCH v4 01/25] drm/xe: Drop struct xe_migrate_pt_update argument from populate/clear vfuns Matthew Brost
2026-09-03 23:58 ` [PATCH v4 02/25] drm/xe: Add xe_migrate_update_pgtables_cpu_execute helper Matthew Brost
2026-09-04  0:15   ` sashiko-bot
2026-09-03 23:58 ` [PATCH v4 03/25] drm/xe: Decouple exec queue idle check from LRC Matthew Brost
2026-09-03 23:58 ` [PATCH v4 04/25] drm/xe: Add job count to GuC exec queue snapshot Matthew Brost
2026-09-03 23:58 ` [PATCH v4 05/25] drm/xe: Update xe_bo_put_deferred arguments to include writeback flag Matthew Brost
2026-09-03 23:58 ` [PATCH v4 06/25] drm/xe: Add XE_BO_FLAG_PUT_VM_ASYNC Matthew Brost
2026-09-04  0:18   ` sashiko-bot
2026-09-04  0:41     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 07/25] drm/xe: Update scheduler job layer to support PT jobs Matthew Brost
2026-09-04  0:25   ` sashiko-bot
2026-09-03 23:58 ` [PATCH v4 08/25] drm/xe: Add helpers to access PT ops Matthew Brost
2026-09-03 23:58 ` [PATCH v4 09/25] drm/xe: Add struct xe_pt_job_ops Matthew Brost
2026-09-03 23:58 ` [PATCH v4 10/25] drm/xe: Update GuC submission backend to run PT jobs Matthew Brost
2026-09-04  0:36   ` sashiko-bot
2026-09-04  0:57     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 11/25] drm/xe: Store level in struct xe_vm_pgtable_update Matthew Brost
2026-09-04  0:19   ` sashiko-bot
2026-09-03 23:58 ` [PATCH v4 12/25] drm/xe: Don't use migrate exec queue for page fault binds Matthew Brost
2026-09-03 23:58 ` [PATCH v4 13/25] drm/xe: Enable CPU binds for jobs Matthew Brost
2026-09-04  0:31   ` sashiko-bot
2026-09-04  1:04     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 14/25] drm/xe: Remove unused arguments from xe_migrate_pt_update_ops Matthew Brost
2026-09-03 23:58 ` [PATCH v4 15/25] drm/xe: Make bind queues operate cross-tile Matthew Brost
2026-09-03 23:58 ` [PATCH v4 16/25] drm/xe: Add CPU bind layer Matthew Brost
2026-09-04  0:31   ` sashiko-bot
2026-09-04  1:18     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 17/25] drm/xe: Add device flag to enable PT mirroring across tiles Matthew Brost
2026-09-04  0:29   ` sashiko-bot
2026-09-04  1:33     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 18/25] drm/xe: Add xe_hw_engine_write_ring_tail Matthew Brost
2026-09-03 23:58 ` [PATCH v4 19/25] drm/xe: Add ULLS support to LRC Matthew Brost
2026-09-03 23:58 ` [PATCH v4 20/25] drm/xe: Add ULLS migration job support to migration layer Matthew Brost
2026-09-04  0:27   ` sashiko-bot
2026-09-04  1:35     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 21/25] drm/xe: Add ULLS migration job support to ring ops Matthew Brost
2026-09-03 23:58 ` [PATCH v4 22/25] drm/xe: Add ULLS migration job support to GuC submission Matthew Brost
2026-09-04  0:38   ` sashiko-bot
2026-09-04  1:41     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 23/25] drm/xe: Enter ULLS for migration jobs upon page fault or SVM prefetch Matthew Brost
2026-09-04  0:28   ` sashiko-bot
2026-09-04  1:32     ` Matthew Brost
2026-09-03 23:58 ` [PATCH v4 24/25] drm/xe: Add modparam to enable / disable ULLS on migrate queue Matthew Brost
2026-09-03 23:58 ` [PATCH v4 25/25] drm/xe: Document ULLS for migration jobs Matthew Brost
2026-09-04  0:47 ` ✗ CI.checkpatch: warning for CPU binds and ULLS on migration queue (rev6) Patchwork
2026-09-04  0:49 ` ✓ CI.KUnit: success " Patchwork
2026-09-04  1:33 ` ✓ Xe.CI.BAT: " Patchwork

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox