From a6b842acf3ddd1efc53a56de9260cfa718fb35e7 Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Thu, 2 Jul 2026 14:58:05 -0700 Subject: [PATCH 01/81] drm/xe: Wait on external BO kernel fences in exec IOCTL Before arming a user job, xe_exec_ioctl() only added the VM's dma-resv KERNEL slot as a dependency. That slot covers rebinds and the kernel operations of the VM's private BOs, but not external BOs (bo->vm == NULL), which carry their kernel operations (evictions, moves, ...) in their own dma-resv KERNEL slot. The DMA_RESV_USAGE_KERNEL slot is the cross-driver contract for memory management operations that must complete before the BO or its backing store may be used: any accessor is required to wait on the KERNEL fences before touching the resv. By skipping the external BOs' KERNEL slots, the exec path violated that contract and could schedule a user job while a kernel operation on an external BO mapped by the VM was still in flight, racing against it and potentially reading or writing memory that was being moved. Replace the VM-only dependency with an iteration over every object locked by the exec, adding each object's KERNEL slot as a job dependency. This covers the VM resv (rebinds and private BOs) as well as every external BO, mirroring the drm_gpuvm_resv_add_fence() call that later publishes the job fence to the same set of objects. Long-running mode continues to skip this, as before. Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: stable@vger.kernel.org Assisted-by: GitHub_Copilot:claude-opus-4.8 Signed-off-by: Matthew Brost Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260702215805.4011228-1-matthew.brost@intel.com --- drivers/gpu/drm/xe/xe_exec.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_exec.c b/drivers/gpu/drm/xe/xe_exec.c index e05dabfcd43c..d5293bc33a67 100644 --- a/drivers/gpu/drm/xe/xe_exec.c +++ b/drivers/gpu/drm/xe/xe_exec.c @@ -292,13 +292,23 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file) goto err_exec; } - /* Wait behind rebinds */ + /* + * Wait behind rebinds and any kernel operations (evictions, defrag + * moves, ...) on the VM and all external BOs. The VM's private BOs + * carry their kernel ops in the VM dma-resv KERNEL slot, while each + * external BO carries them in its own dma-resv KERNEL slot; both are + * covered by iterating every object locked by the exec, mirroring the + * drm_gpuvm_resv_add_fence() below. + */ if (!xe_vm_in_lr_mode(vm)) { - err = xe_sched_job_add_deps(job, - xe_vm_resv(vm), - DMA_RESV_USAGE_KERNEL); - if (err) - goto err_put_job; + struct drm_gem_object *obj; + + drm_exec_for_each_locked_object(exec, obj) { + err = xe_sched_job_add_deps(job, obj->resv, + DMA_RESV_USAGE_KERNEL); + if (err) + goto err_put_job; + } } for (i = 0; i < num_syncs && !err; i++) From 84ed5b0a925721aaf069d36e18a99db966ff4e80 Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Thu, 2 Jul 2026 16:58:20 +0530 Subject: [PATCH 02/81] drm/xe: remove duplicate include xe_pci.c includes twice, separated only by the include. Drop the redundant second include; this is a non-functional cleanup flagged by scripts/checkincludes.pl. Fixes: 6cad22853cb8 ("drm/xe/kunit: Add stub to read_gmdid") Signed-off-by: Anas Khan Link: https://patch.msgid.link/20260702112820.34675-1-anxkhn28@gmail.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/tests/xe_pci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/xe/tests/xe_pci.c b/drivers/gpu/drm/xe/tests/xe_pci.c index 8df9029afcd3..bb0393475524 100644 --- a/drivers/gpu/drm/xe/tests/xe_pci.c +++ b/drivers/gpu/drm/xe/tests/xe_pci.c @@ -9,7 +9,6 @@ #include #include -#include #include #define PLATFORM_CASE(platform__, graphics_step__) \ From 3a8bfa1f2af71ca21818253753fccc53337b7b9b Mon Sep 17 00:00:00 2001 From: Rafael Passos Date: Tue, 30 Jun 2026 22:20:58 -0300 Subject: [PATCH 03/81] drm/xe: Documentation: fix chars used for subsection Fixes "ERROR: A level 2 section cannot be used here". Equal signs are reserved for document titles. This file docs gets imported by driver-uapi.rst, and the page title is defined there. Signed-off-by: Rafael Passos Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://patch.msgid.link/20260701012141.167868-1-rafael@rcpassos.me Signed-off-by: Rodrigo Vivi [Rodrigo modified the subject while pushing it] --- include/uapi/drm/xe_drm.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/drm/xe_drm.h b/include/uapi/drm/xe_drm.h index 50c80af4ad4e..509202a7b13e 100644 --- a/include/uapi/drm/xe_drm.h +++ b/include/uapi/drm/xe_drm.h @@ -2537,21 +2537,21 @@ struct drm_xe_exec_queue_set_property { * Refer to Documentation/netlink/specs/drm_ras.yaml for complete interface specification. * * Node Registration - * ================= + * ----------------- * * The driver registers DRM RAS nodes for each error severity level. * enum drm_xe_ras_error_severity defines the node-id, while DRM_XE_RAS_ERROR_SEVERITY_NAMES maps * node-id to node-name. * * Error Classification - * ==================== + * -------------------- * * Each node contains a list of error counters. Each error is identified by a error-id and * an error-name. enum drm_xe_ras_error_component defines the error-id, while * DRM_XE_RAS_ERROR_COMPONENT_NAMES maps error-id to error-name. * * User Interface - * ============== + * -------------- * * To retrieve error values of a error counter, userspace applications should * follow the below steps: From f7c05238ab72ae72b5f6bd5516b02e7a24fdfd0d Mon Sep 17 00:00:00 2001 From: Karthik Poosa Date: Thu, 2 Jul 2026 14:55:39 +0530 Subject: [PATCH 04/81] drm/xe/pcode: Add support to get pcode version from PMT Add api get_pcode_version() to read pcode version from PMT telemetry. Add PUNIT_VERSION telemetry offset in xe_pmt.h. Signed-off-by: Karthik Poosa Reviewed-by: Michael J. Ruhl Link: https://patch.msgid.link/20260702092540.1005095-2-karthik.poosa@intel.com Signed-off-by: Matt Roper --- drivers/gpu/drm/xe/regs/xe_pmt.h | 2 ++ drivers/gpu/drm/xe/xe_pcode.c | 32 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_pcode.h | 7 +++++++ 3 files changed, 41 insertions(+) diff --git a/drivers/gpu/drm/xe/regs/xe_pmt.h b/drivers/gpu/drm/xe/regs/xe_pmt.h index 240d57993ea6..a62ab05c6b4c 100644 --- a/drivers/gpu/drm/xe/regs/xe_pmt.h +++ b/drivers/gpu/drm/xe/regs/xe_pmt.h @@ -15,6 +15,8 @@ #define ENERGY_PKG REG_GENMASK64(31, 0) #define ENERGY_CARD REG_GENMASK64(63, 32) +#define PUNIT_VERSION_OFFSET 0xA0 + #define BMG_TELEMETRY_BASE_OFFSET 0xE0000 #define BMG_TELEMETRY_OFFSET (SOC_BASE + BMG_TELEMETRY_BASE_OFFSET) diff --git a/drivers/gpu/drm/xe/xe_pcode.c b/drivers/gpu/drm/xe/xe_pcode.c index 866986694d9c..ccc3bdeed6bb 100644 --- a/drivers/gpu/drm/xe/xe_pcode.c +++ b/drivers/gpu/drm/xe/xe_pcode.c @@ -11,10 +11,14 @@ #include +#include "regs/xe_pmt.h" #include "xe_assert.h" #include "xe_device.h" #include "xe_mmio.h" #include "xe_pcode_api.h" +#include "xe_pm.h" +#include "xe_printk.h" +#include "xe_vsec.h" /** * DOC: PCODE @@ -350,3 +354,31 @@ int xe_pcode_probe_early(struct xe_device *xe) return xe_pcode_ready(xe, false); } ALLOW_ERROR_INJECTION(xe_pcode_probe_early, ERRNO); /* See xe_pci_probe */ + +/** + * xe_get_pcode_version - Read pcode version via PMT telemetry + * @xe: xe instance + * @version: pointer to struct xe_pcode_version to store version info + * + * Reads the pcode version from PMT telemetry and fills the + * provided @version structure. + * + * Return: 0 on success, negative error code on failure. + */ +int xe_get_pcode_version(struct xe_device *xe, struct xe_pcode_version *version) +{ + int ret = 0; + + guard(xe_pm_runtime)(xe); + + ret = xe_pmt_telem_read(xe->drm.dev, + xe_mmio_read32(xe_root_tile_mmio(xe), PUNIT_TELEMETRY_GUID), + (u64 *)version, PUNIT_VERSION_OFFSET, sizeof(*version)); + if (ret != sizeof(*version)) { + xe_warn(xe, "pcode version read from PMT failed, ret %pe\n", ERR_PTR(ret)); + return ret; + } + xe_dbg(xe, "pcode version major %u minor %u engg %u\n", version->major, + version->minor, version->engg); + return 0; +} diff --git a/drivers/gpu/drm/xe/xe_pcode.h b/drivers/gpu/drm/xe/xe_pcode.h index 18260c29e620..8fb3e4ba13a6 100644 --- a/drivers/gpu/drm/xe/xe_pcode.h +++ b/drivers/gpu/drm/xe/xe_pcode.h @@ -12,6 +12,12 @@ struct drm_device; struct xe_device; struct xe_tile; +struct xe_pcode_version { + u16 minor; + u16 major; + u32 engg; +}; + int xe_pcode_init_early(struct xe_tile *tile); int xe_pcode_probe_early(struct xe_device *xe); int xe_pcode_ready(struct xe_device *xe, bool locked); @@ -22,6 +28,7 @@ int xe_pcode_write_timeout(struct xe_tile *tile, u32 mbox, u32 val, int timeout_ms); int xe_pcode_write64_timeout(struct xe_tile *tile, u32 mbox, u32 data0, u32 data1, int timeout); +int xe_get_pcode_version(struct xe_device *xe, struct xe_pcode_version *version); #define xe_pcode_write(tile, mbox, val) \ xe_pcode_write_timeout(tile, mbox, val, 1) From 0f800465ecabac95c434a98681124382bcec6207 Mon Sep 17 00:00:00 2001 From: Karthik Poosa Date: Thu, 2 Jul 2026 14:55:40 +0530 Subject: [PATCH 05/81] drm/xe/debugfs: Add debugfs for pcode information Introduce a pcode_info debugfs entry to report pcode details. This initial implementation exposes the pcode version. This can aid debugging when the pcode version is needed. Signed-off-by: Karthik Poosa Reviewed-by: Michael J. Ruhl Link: https://patch.msgid.link/20260702092540.1005095-3-karthik.poosa@intel.com Signed-off-by: Matt Roper --- drivers/gpu/drm/xe/xe_debugfs.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c index 3c018dbccc07..8c391c7b017a 100644 --- a/drivers/gpu/drm/xe/xe_debugfs.c +++ b/drivers/gpu/drm/xe/xe_debugfs.c @@ -21,6 +21,7 @@ #include "xe_guc_ads.h" #include "xe_hw_engine.h" #include "xe_mmio.h" +#include "xe_pcode.h" #include "xe_pm.h" #include "xe_psmi.h" #include "xe_pxp_debugfs.h" @@ -160,6 +161,22 @@ static int workaround_info(struct seq_file *m, void *data) return 0; } +static int pcode_info(struct seq_file *m, void *data) +{ + struct xe_device *xe = node_to_xe(m->private); + struct drm_printer p = drm_seq_file_printer(m); + struct xe_pcode_version version; + int ret = 0; + + ret = xe_get_pcode_version(xe, &version); + if (ret) + return ret; + + drm_printf(&p, "pcode version: %u.%u.%u\n", version.major, + version.minor, version.engg); + return 0; +} + static int dgfx_pkg_residencies_show(struct seq_file *m, void *data) { struct xe_device *xe; @@ -220,6 +237,10 @@ static const struct drm_info_list debugfs_list[] = { { .name = "workarounds", .show = workaround_info, }, }; +static const struct drm_info_list pcode_info_debugfs[] = { + { .name = "pcode_info", .show = pcode_info, }, +}; + static const struct drm_info_list debugfs_residencies[] = { { .name = "dgfx_pkg_residencies", .show = dgfx_pkg_residencies_show, }, { .name = "dgfx_pcie_link_residencies", .show = dgfx_pcie_link_residencies_show, }, @@ -566,6 +587,18 @@ void xe_debugfs_register(struct xe_device *xe) &inject_csc_hw_error); } + /* + * Pcode version read from PMT is currently only supported on CRI and BMG platforms in PF + * mode, as both platforms support the necessary telemetry read mechanism and have a fixed + * PUNIT_VERSION_OFFSET. + * Attempting this access on other platforms must be verified before enabling support. + */ + if (!IS_SRIOV_VF(xe) && + (xe->info.platform == XE_CRESCENTISLAND || xe->info.platform == XE_BATTLEMAGE)) + drm_debugfs_create_files(pcode_info_debugfs, + ARRAY_SIZE(pcode_info_debugfs), + root, minor); + debugfs_create_file("forcewake_all", 0400, root, xe, &forcewake_all_fops); From c3a1c3579b1250060da73507a4acef712974c78a Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 15:34:22 +0800 Subject: [PATCH 06/81] drm/xe: free madvise VMA array on L2 flush failure xe_vm_madvise_ioctl() allocates madvise_range.vmas in get_vmas(). After get_vmas() succeeds with at least one VMA, error paths must go through free_vmas so the array is released before the madvise details are destroyed. The L2 flush validation path added for PAT madvise rejects some SVM/userptr ranges after get_vmas() has succeeded, but jumps directly to madv_fini. This skips kfree(madvise_range.vmas), leaking the VMA array on each failed ioctl. Jump to free_vmas instead, matching the other validation failure paths after get_vmas() has succeeded. Fixes: 4f39a194d41e ("drm/xe/xe3p_lpg: Restrict UAPI to enable L2 flush optimization") Signed-off-by: Guangshuo Li Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260708073422.725186-1-lgs201920130244@gmail.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_vm_madvise.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_vm_madvise.c b/drivers/gpu/drm/xe/xe_vm_madvise.c index 9e343f9aa44d..0474768a38aa 100644 --- a/drivers/gpu/drm/xe/xe_vm_madvise.c +++ b/drivers/gpu/drm/xe/xe_vm_madvise.c @@ -657,7 +657,7 @@ int xe_vm_madvise_ioctl(struct drm_device *dev, void *data, struct drm_file *fil xe_device_is_l2_flush_optimized(xe) && (pat_index != 19 && coh_mode != XE_COH_2WAY))) { err = -EINVAL; - goto madv_fini; + goto free_vmas; } } From 3359422bf0a1140e96d783a19a397686e580a3ca Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 30 Jun 2026 19:22:21 +0000 Subject: [PATCH 07/81] drm/xe/userptr: Stub notifier_lock helpers when DRM_GPUSVM=n When CONFIG_DRM_GPUSVM=n (e.g. um-allyesconfig), the only caller of xe_pt_svm_userptr_notifier_lock() is compiled out, triggering: drivers/gpu/drm/xe/xe_pt.c:1418:13: warning: 'xe_pt_svm_userptr_notifier_lock' defined but not used [-Wunused-function] The helpers cannot simply be removed in this case: the matching xe_pt_svm_userptr_notifier_unlock() is also referenced from xe_pt_update_ops_run(), which lives outside any DRM_GPUSVM ifdef and is gated only at runtime by pt_update_ops->needs_svm_lock. The symbol must exist in all builds. Provide empty static inline stubs for !DRM_GPUSVM, matching the pattern used by xe_svm_notifier_lock()/_unlock() in xe_svm.h. Fixes: 80ccbd97ffee ("drm/xe/userptr: Hold notifier_lock for write on inject test path") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606302210.QqcLbOEN-lkp@intel.com/ Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260630192221.2998168-1-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/xe_pt.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index 5fdad444009f..e466f714bf86 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -1418,6 +1418,7 @@ static int xe_pt_pre_commit(struct xe_migrate_pt_update *pt_update) pt_update_ops, rftree); } +#if IS_ENABLED(CONFIG_DRM_GPUSVM) /* * Acquire/release the svm notifier_lock around xe_pt_svm_userptr_pre_commit() * and the matching late release in xe_pt_update_ops_run(). Read mode by @@ -1444,6 +1445,10 @@ static void xe_pt_svm_userptr_notifier_unlock(struct xe_vm *vm) xe_svm_notifier_unlock(vm); #endif } +#else +static inline void xe_pt_svm_userptr_notifier_lock(struct xe_vm *vm) { } +static inline void xe_pt_svm_userptr_notifier_unlock(struct xe_vm *vm) { } +#endif #if IS_ENABLED(CONFIG_DRM_GPUSVM) #ifdef CONFIG_DRM_XE_USERPTR_INVAL_INJECT From 50fa9acac26f2f6d12117c6bd27c3d28ec6c0924 Mon Sep 17 00:00:00 2001 From: Sk Anirban Date: Thu, 25 Jun 2026 01:16:20 +0530 Subject: [PATCH 08/81] drm/xe/guc: distinguish wedged from recoverable cancellation The CT layer returns -ECANCELED regardless of whether cancellation is due to a GT reset or a wedged device. Return -ENOTRECOVERABLE on wedge so callers don't need xe_device_wedged() checks to suppress spurious error logs. Also document the return codes of xe_guc_ct_send() in kernel-doc form. v2: Fix -ECANCELED description (Matt) Signed-off-by: Sk Anirban Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260624194618.2793571-5-sk.anirban@intel.com --- drivers/gpu/drm/xe/xe_guc_ct.c | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_ct.c b/drivers/gpu/drm/xe/xe_guc_ct.c index 21e0dad9a481..8ca7d37c79b6 100644 --- a/drivers/gpu/drm/xe/xe_guc_ct.c +++ b/drivers/gpu/drm/xe/xe_guc_ct.c @@ -1065,6 +1065,11 @@ static int __guc_ct_send_locked(struct xe_guc_ct *ct, const u32 *action, xe_gt_assert(gt, g2h_len || !num_g2h); lockdep_assert_held(&ct->lock); + if (xe_device_wedged(ct_to_xe(ct))) { + ret = -ENOTRECOVERABLE; + goto out; + } + if (unlikely(ct->ctbs.h2g.info.broken)) { ret = -EPIPE; goto out; @@ -1236,6 +1241,36 @@ static int guc_ct_send(struct xe_guc_ct *ct, const u32 *action, u32 len, return ret; } +/** + * xe_guc_ct_send - Send an HXG message to the GuC over CT + * @ct: the &xe_guc_ct + * @action: dword array with the HXG message (can't be NULL) + * @len: length of the HXG message in dwords (can't be 0) + * @g2h_len: G2H response space to reserve in dwords, or 0 + * @num_g2h: number of G2H messages expected, or 0 + * + * Return codes from the non-blocking send helpers are: + * + * * -ENOTRECOVERABLE: the xe device is wedged. Stop submitting new GuC work; the + * request cannot make progress until the device is recovered. + * * -EPIPE: the H2G CTB is marked broken. The channel stays unusable until the + * CT is restarted, which clears the broken flag. + * * -ENODEV: the CT channel is disabled, messages not expected in this state. + * Don't retry until it is enabled again. + * * -ECANCELED: the CT channel is stopped or a GT recovery is pending; the + * message was dropped. Often benign. Cancel-tolerant callers (e.g. TLB + * invalidations, GuC submission) rely on the stop/start flow to recover; + * others should retry once the CT is re-enabled or the reset/recovery + * completes. + * * -EDEADLK: no CTB room and the wait for space timed out. The send helpers + * have already requested an async GT reset before returning this error. + * + * -ENOMEM may also be returned if an internal allocation fails; the blocking + * xe_guc_ct_send_recv() path retries that allocation. -EBUSY and + * -EAGAIN are internal flow-control results handled by the send helpers. + * + * Return: 0 on success, or a negative error code on failure. + */ int xe_guc_ct_send(struct xe_guc_ct *ct, const u32 *action, u32 len, u32 g2h_len, u32 num_g2h) { @@ -1388,7 +1423,7 @@ static int guc_ct_send_recv(struct xe_guc_ct *ct, const u32 *action, u32 len, if (g2h_fence.fail) { if (g2h_fence.cancel) { xe_gt_dbg(gt, "H2G request %#x canceled!\n", action[0]); - ret = -ECANCELED; + ret = xe_device_wedged(ct_to_xe(ct)) ? -ENOTRECOVERABLE : -ECANCELED; goto unlock; } xe_gt_err(gt, "H2G request %#x failed: error %#x hint %#x\n", @@ -1724,6 +1759,9 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path) xe_gt_assert(gt, xe_guc_ct_initialized(ct)); lockdep_assert_held(&ct->fast_lock); + if (xe_device_wedged(xe)) + return -ENOTRECOVERABLE; + if (ct->state == XE_GUC_CT_STATE_DISABLED) return -ENODEV; From d72d2706b23503617a97a066316c00bf25702f2f Mon Sep 17 00:00:00 2001 From: Sk Anirban Date: Thu, 25 Jun 2026 01:16:21 +0530 Subject: [PATCH 09/81] drm/xe/guc: fix activity stats error message format Use ERR_PTR() to print the error code symbolically. This makes the failure easier to spot from IGT, e.g. when the device is wedged. Signed-off-by: Sk Anirban Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260624194618.2793571-6-sk.anirban@intel.com --- drivers/gpu/drm/xe/xe_guc_engine_activity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_engine_activity.c b/drivers/gpu/drm/xe/xe_guc_engine_activity.c index 2b99c1ebdd58..f43ca1c76f75 100644 --- a/drivers/gpu/drm/xe/xe_guc_engine_activity.c +++ b/drivers/gpu/drm/xe/xe_guc_engine_activity.c @@ -473,7 +473,7 @@ void xe_guc_engine_activity_enable_stats(struct xe_guc *guc) ret = enable_engine_activity_stats(guc); if (ret) - xe_gt_err(guc_to_gt(guc), "failed to enable activity stats%d\n", ret); + xe_gt_err(guc_to_gt(guc), "failed to enable activity stats: %pe\n", ERR_PTR(ret)); else engine_activity_set_cpu_ts(guc, 0); } From 4e57574fa3a3a152bcca40f7595a28e88af28b27 Mon Sep 17 00:00:00 2001 From: Zhanjun Dong Date: Mon, 6 Jul 2026 19:43:53 -0400 Subject: [PATCH 10/81] drm/xe/guc: Handle GuC local uncorrectable error notifications Add support for the GuC uncorrectable local error G2H notification and opt in to the feature when the submission ABI exposes it. When the notification targets a known exec queue, treat it like an engine reset request and route it through the existing timeout cleanup path. This keeps the queue teardown, pending job cancellation and error capture in one place instead of open-coding a parallel recovery flow. Signed-off-by: Zhanjun Dong Reviewed-by: Daniele Ceraolo Spurio Signed-off-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260706234353.3874355-1-zhanjun.dong@intel.com --- drivers/gpu/drm/xe/abi/guc_actions_abi.h | 1 + drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 8 ++++++ drivers/gpu/drm/xe/xe_gt_types.h | 5 ++++ drivers/gpu/drm/xe/xe_guc.c | 10 ++++++++ drivers/gpu/drm/xe/xe_guc_ct.c | 3 +++ drivers/gpu/drm/xe/xe_guc_submit.c | 32 ++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_submit.h | 1 + drivers/gpu/drm/xe/xe_pci.c | 15 ++++++++++- drivers/gpu/drm/xe/xe_pci_types.h | 2 ++ drivers/gpu/drm/xe/xe_trace.h | 5 ++++ 10 files changed, 81 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/abi/guc_actions_abi.h b/drivers/gpu/drm/xe/abi/guc_actions_abi.h index 83a6e7794982..f5c9b37038d4 100644 --- a/drivers/gpu/drm/xe/abi/guc_actions_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_actions_abi.h @@ -152,6 +152,7 @@ enum xe_guc_action { XE_GUC_ACTION_REPORT_PAGE_FAULT_REQ_DESC = 0x6002, XE_GUC_ACTION_PAGE_FAULT_RES_DESC = 0x6003, XE_GUC_ACTION_ACCESS_COUNTER_NOTIFY = 0x6004, + XE_GUC_ACTION_NOTIFY_UNCORRECTABLE_LOCAL_ERROR = 0x6005, XE_GUC_ACTION_TLB_INVALIDATION = 0x7000, XE_GUC_ACTION_TLB_INVALIDATION_DONE = 0x7001, XE_GUC_ACTION_TLB_INVALIDATION_ALL = 0x7002, diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index 644f5a4226d7..5c428f02a642 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -154,6 +154,11 @@ enum { * (instead of waiting the full timeslice duration). The bit is instead set * to one if a single context is queued on the engine, to avoid it being * switched out if there isn't another context that can run in its place. + * + * _`GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION` : 0x4004 + * This flag will enable notification from GuC to KMD via G2H message + * GUC_ACTION_GUC2HOST_NOTIFY_UNCORRECTABLE_LOCAL_ERROR upon receiving the + * same interrupt from the CS. */ #define GUC_KLV_OPT_IN_FEATURE_EXT_CAT_ERR_TYPE_KEY 0x4001 @@ -162,6 +167,9 @@ enum { #define GUC_KLV_OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH_KEY 0x4003 #define GUC_KLV_OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH_LEN 0u +#define GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION_KEY 0x4004 +#define GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION_LEN 0u + /** * DOC: GuC Scheduling Policies KLVs * diff --git a/drivers/gpu/drm/xe/xe_gt_types.h b/drivers/gpu/drm/xe/xe_gt_types.h index e5588c88800a..0d234160ee3a 100644 --- a/drivers/gpu/drm/xe/xe_gt_types.h +++ b/drivers/gpu/drm/xe/xe_gt_types.h @@ -144,6 +144,11 @@ struct xe_gt { u8 id; /** @info.has_indirect_ring_state: GT has indirect ring state support */ u8 has_indirect_ring_state:1; + /** + * @info.has_uncorrectable_error_reporting: GT has uncorrectable + * error reporting support + */ + u8 has_uncorrectable_error_reporting:1; /** * @info.has_xe2_blt_instructions: GT supports Xe2-style MEM_SET * and MEM_COPY blitter functionality. Note that despite the diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index 4023700ff2a9..543f3992bccd 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -12,6 +12,7 @@ #include "abi/guc_actions_abi.h" #include "abi/guc_errors_abi.h" +#include "abi/guc_klvs_abi.h" #include "regs/xe_gt_regs.h" #include "regs/xe_gtt_defs.h" #include "regs/xe_guc_regs.h" @@ -641,6 +642,15 @@ int xe_guc_opt_in_features_enable(struct xe_guc *guc) if (GUC_SUBMIT_VER(guc) >= MAKE_GUC_VER(1, 7, 0)) klvs[count++] = PREP_GUC_KLV_TAG(OPT_IN_FEATURE_EXT_CAT_ERR_TYPE); + /* + * The uncorrectable local error notification opt-in was added in + * GuC v70.38.0, which maps to compatibility version v1.18.0. + */ + if (GUC_SUBMIT_VER(guc) >= MAKE_GUC_VER(1, 18, 0) && + guc_to_gt(guc)->info.has_uncorrectable_error_reporting) + klvs[count++] = + PREP_GUC_KLV_TAG(OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION); + if (supports_dynamic_ics(guc)) klvs[count++] = PREP_GUC_KLV_TAG(OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH); diff --git a/drivers/gpu/drm/xe/xe_guc_ct.c b/drivers/gpu/drm/xe/xe_guc_ct.c index 8ca7d37c79b6..fe70c0fd85c5 100644 --- a/drivers/gpu/drm/xe/xe_guc_ct.c +++ b/drivers/gpu/drm/xe/xe_guc_ct.c @@ -1696,6 +1696,9 @@ static int process_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len) ret = xe_guc_exec_queue_memory_cat_error_handler(guc, payload, adj_len); break; + case XE_GUC_ACTION_NOTIFY_UNCORRECTABLE_LOCAL_ERROR: + ret = xe_guc_uncorrectable_error_handler(guc, payload, adj_len); + break; case XE_GUC_ACTION_REPORT_PAGE_FAULT_REQ_DESC: ret = xe_guc_pagefault_handler(guc, payload, adj_len); break; diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 12416bfa3255..e5fcb1f21115 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -3022,6 +3022,38 @@ int xe_guc_exec_queue_memory_cat_error_handler(struct xe_guc *guc, u32 *msg, return 0; } +int xe_guc_uncorrectable_error_handler(struct xe_guc *guc, u32 *msg, u32 len) +{ + struct xe_gt *gt = guc_to_gt(guc); + struct xe_exec_queue *q; + u32 guc_id; + + if (unlikely(!len || len > 1)) + return -EPROTO; + + guc_id = msg[0]; + + if (guc_id == GUC_ID_UNKNOWN) { + xe_gt_err(gt, "GuC: Uncorrectable local error with unknown GuC id\n"); + return 0; + } + + q = g2h_exec_queue_lookup(guc, guc_id); + if (unlikely(!q)) + return -EPROTO; + + xe_gt_err(gt, + "GuC: Uncorrectable local error! guc_id=%d class=%s, logical_mask=0x%x", + guc_id, xe_hw_engine_class_to_str(q->class), q->logical_mask); + + trace_xe_guc_uncorrectable_error(q); + + /* Treat the same as engine reset */ + xe_guc_exec_queue_reset_trigger_cleanup(q); + + return 0; +} + int xe_guc_exec_queue_reset_failure_handler(struct xe_guc *guc, u32 *msg, u32 len) { struct xe_gt *gt = guc_to_gt(guc); diff --git a/drivers/gpu/drm/xe/xe_guc_submit.h b/drivers/gpu/drm/xe/xe_guc_submit.h index b3839a90c142..ccade320dc69 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.h +++ b/drivers/gpu/drm/xe/xe_guc_submit.h @@ -34,6 +34,7 @@ int xe_guc_deregister_done_handler(struct xe_guc *guc, u32 *msg, u32 len); int xe_guc_exec_queue_reset_handler(struct xe_guc *guc, u32 *msg, u32 len); int xe_guc_exec_queue_memory_cat_error_handler(struct xe_guc *guc, u32 *msg, u32 len); +int xe_guc_uncorrectable_error_handler(struct xe_guc *guc, u32 *msg, u32 len); int xe_guc_exec_queue_reset_failure_handler(struct xe_guc *guc, u32 *msg, u32 len); int xe_guc_error_capture_handler(struct xe_guc *guc, u32 *msg, u32 len); int xe_guc_exec_queue_cgp_sync_done_handler(struct xe_guc *guc, u32 *msg, u32 len); diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 91af603e9431..11a69dffdce7 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -120,6 +120,7 @@ static const struct xe_graphics_desc graphics_xe2 = { static const struct xe_graphics_desc graphics_xe3p_lpg = { XE2_GFX_FEATURES, .has_indirect_ring_state = 1, + .has_uncorrectable_error_reporting = 1, .multi_queue_engine_class_mask = BIT(XE_ENGINE_CLASS_COPY) | BIT(XE_ENGINE_CLASS_COMPUTE), .num_geometry_xecore_fuse_regs = 3, .num_compute_xecore_fuse_regs = 3, @@ -129,6 +130,7 @@ static const struct xe_graphics_desc graphics_xe3p_xpc = { XE2_GFX_FEATURES, .has_access_counter = 0, .has_indirect_ring_state = 1, + .has_uncorrectable_error_reporting = 1, .hw_engine_mask = GENMASK(XE_HW_ENGINE_BCS8, XE_HW_ENGINE_BCS1) | GENMASK(XE_HW_ENGINE_CCS3, XE_HW_ENGINE_CCS0), @@ -151,6 +153,14 @@ static const struct xe_media_desc media_xelpmp = { BIT(XE_HW_ENGINE_GSCCS0) }; +static const struct xe_media_desc media_xe3p_hpm = { + .has_uncorrectable_error_reporting = 1, + .hw_engine_mask = + GENMASK(XE_HW_ENGINE_VCS7, XE_HW_ENGINE_VCS0) | + GENMASK(XE_HW_ENGINE_VECS3, XE_HW_ENGINE_VECS0) | + BIT(XE_HW_ENGINE_GSCCS0) +}; + /* Pre-GMDID Graphics IPs */ static const struct xe_ip graphics_ip_xelp = { 1200, "Xe_LP", &graphics_xelp }; static const struct xe_ip graphics_ip_xelpp = { 1210, "Xe_LP+", &graphics_xelp }; @@ -186,7 +196,7 @@ static const struct xe_ip media_ips[] = { { 3000, "Xe3_LPM", &media_xelpmp }, { 3002, "Xe3_LPM", &media_xelpmp }, { 3500, "Xe3p_LPM", &media_xelpmp }, - { 3503, "Xe3p_HPM", &media_xelpmp }, + { 3503, "Xe3p_HPM", &media_xe3p_hpm }, }; #define MULTI_LRC_MASK \ @@ -875,6 +885,8 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile, gt->info.type = XE_GT_TYPE_MAIN; gt->info.id = tile->id * xe->info.max_gt_per_tile; gt->info.has_indirect_ring_state = graphics_desc->has_indirect_ring_state; + gt->info.has_uncorrectable_error_reporting = + graphics_desc->has_uncorrectable_error_reporting; gt->info.multi_queue_engine_class_mask = graphics_desc->multi_queue_engine_class_mask; gt->info.engine_mask = graphics_desc->hw_engine_mask; gt->info.num_geometry_xecore_fuse_regs = graphics_desc->num_geometry_xecore_fuse_regs; @@ -920,6 +932,7 @@ static struct xe_gt *alloc_media_gt(struct xe_tile *tile, gt->info.type = XE_GT_TYPE_MEDIA; gt->info.id = tile->id * xe->info.max_gt_per_tile + 1; gt->info.has_indirect_ring_state = media_desc->has_indirect_ring_state; + gt->info.has_uncorrectable_error_reporting = media_desc->has_uncorrectable_error_reporting; gt->info.engine_mask = media_desc->hw_engine_mask; return gt; diff --git a/drivers/gpu/drm/xe/xe_pci_types.h b/drivers/gpu/drm/xe/xe_pci_types.h index 24d4a3d00517..fed509ff601e 100644 --- a/drivers/gpu/drm/xe/xe_pci_types.h +++ b/drivers/gpu/drm/xe/xe_pci_types.h @@ -79,12 +79,14 @@ struct xe_graphics_desc { u8 has_ctx_tlb_inval:1; u8 has_usm:1; u8 has_64bit_timestamp:1; + u8 has_uncorrectable_error_reporting:1; }; struct xe_media_desc { u64 hw_engine_mask; /* hardware engines provided by media IP */ u8 has_indirect_ring_state:1; + u8 has_uncorrectable_error_reporting:1; }; struct xe_ip { diff --git a/drivers/gpu/drm/xe/xe_trace.h b/drivers/gpu/drm/xe/xe_trace.h index 750fa32c13b2..2fe8f89a1e34 100644 --- a/drivers/gpu/drm/xe/xe_trace.h +++ b/drivers/gpu/drm/xe/xe_trace.h @@ -213,6 +213,11 @@ DEFINE_EVENT(xe_exec_queue, xe_exec_queue_memory_cat_error, TP_ARGS(q) ); +DEFINE_EVENT(xe_exec_queue, xe_guc_uncorrectable_error, + TP_PROTO(struct xe_exec_queue *q), + TP_ARGS(q) +); + DEFINE_EVENT(xe_exec_queue, xe_exec_queue_cgp_context_error, TP_PROTO(struct xe_exec_queue *q), TP_ARGS(q) From 0af278683cd61b6c31fbec7f548f89956f70c604 Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Mon, 6 Jul 2026 15:43:45 -0700 Subject: [PATCH 11/81] drm/xe: Add support for WA 22022079272 The WA is implemented by the GuC, so we just need to enable it via the dedicated KLV. This WA is supported starting from GuC 70.62. Signed-off-by: Daniele Ceraolo Spurio Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260706224344.2723462-3-daniele.ceraolospurio@intel.com --- drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 1 + drivers/gpu/drm/xe/xe_guc_ads.c | 3 +++ drivers/gpu/drm/xe/xe_wa_oob.rules | 3 +++ 3 files changed, 7 insertions(+) diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index 5c428f02a642..d156ef987020 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -512,6 +512,7 @@ enum xe_guc_klv_ids { GUC_WA_KLV_RESET_BB_STACK_PTR_ON_VF_SWITCH = 0x900b, GUC_WA_KLV_RESTORE_UNSAVED_MEDIA_CONTROL_REG = 0x900c, GUC_WA_KLV_CLR_CS_INDIRECT_RING_STATE_IF_IDLE_AT_CTX_REG = 0x900e, + GUC_WA_KLV_REMAP_RANGED_TLB_INV = 0x900f, }; #endif diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index c98454545a85..becf9a004867 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -364,6 +364,9 @@ static void guc_waklv_init(struct xe_guc_ads *ads) guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_CLR_CS_INDIRECT_RING_STATE_IF_IDLE_AT_CTX_REG); + if (XE_GT_WA(gt, 22022079272) && GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 62)) + guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_REMAP_RANGED_TLB_INV); + size = guc_ads_waklv_size(ads) - remain; if (!size) return; diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index 9027365f0043..a2ccd036abce 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -66,3 +66,6 @@ 14025883347 MEDIA_VERSION_RANGE(1301, 3503) GRAPHICS_VERSION_RANGE(2004, 3005) 16029380221 MEDIA_VERSION(3500) +22022079272 MEDIA_VERSION(3503) + GRAPHICS_VERSION(3510) + GRAPHICS_VERSION(3511) From f543084cdbd142b9cfc1baf6f9d81e7b95fe9505 Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Mon, 6 Jul 2026 15:43:46 -0700 Subject: [PATCH 12/81] drm/xe: Add support for WA 16029897822 The WA is implemented by the GuC, so we just need to enable it via the dedicated KLV. This WA is supported starting from GuC 70.69. Note that the GuC does not enable the relevant feature on NVL-S, so this WA can't (and shouldn't) be enabled on that platform. Signed-off-by: Daniele Ceraolo Spurio Reviewed-by: Julia Filipchuk Link: https://patch.msgid.link/20260706224344.2723462-4-daniele.ceraolospurio@intel.com --- drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 1 + drivers/gpu/drm/xe/xe_guc_ads.c | 6 ++++++ drivers/gpu/drm/xe/xe_wa_oob.rules | 2 ++ 3 files changed, 9 insertions(+) diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index d156ef987020..ec9c22dc21be 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -513,6 +513,7 @@ enum xe_guc_klv_ids { GUC_WA_KLV_RESTORE_UNSAVED_MEDIA_CONTROL_REG = 0x900c, GUC_WA_KLV_CLR_CS_INDIRECT_RING_STATE_IF_IDLE_AT_CTX_REG = 0x900e, GUC_WA_KLV_REMAP_RANGED_TLB_INV = 0x900f, + GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64 = 0x9010, }; #endif diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index becf9a004867..79d6fd8d919b 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -367,6 +367,12 @@ static void guc_waklv_init(struct xe_guc_ads *ads) if (XE_GT_WA(gt, 22022079272) && GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 62)) guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_REMAP_RANGED_TLB_INV); + /* The GuC does not enable the sem_tok_64 feature on NVL-S */ + if (XE_GT_WA(gt, 16029897822) && gt_to_xe(gt)->info.platform != XE_NOVALAKE_S && + GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 69)) + guc_waklv_enable(ads, NULL, 0, &offset, &remain, + GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64); + size = guc_ads_waklv_size(ads) - remain; if (!size) return; diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index a2ccd036abce..5d6574ec9dee 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -69,3 +69,5 @@ 22022079272 MEDIA_VERSION(3503) GRAPHICS_VERSION(3510) GRAPHICS_VERSION(3511) +16029897822 MEDIA_VERSION(3500) + GRAPHICS_VERSION(3510) From 9cdcdadab543149c1417d22f5574cecbe5ba0702 Mon Sep 17 00:00:00 2001 From: Julia Filipchuk Date: Tue, 7 Jul 2026 12:24:40 -0700 Subject: [PATCH 13/81] drm/xe/guc: Define GuC firmware for NVL-S GuC firmware 70.71.0 (UAPI 1.37.2) is the first official GuC firmware for Novalake S. Recommend this version for NVL-S platform. Signed-off-by: Julia Filipchuk Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260707192547.50535-12-julia.filipchuk@intel.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_uc_fw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/xe/xe_uc_fw.c b/drivers/gpu/drm/xe/xe_uc_fw.c index 3f08a3b54062..a8e6f18cc9b4 100644 --- a/drivers/gpu/drm/xe/xe_uc_fw.c +++ b/drivers/gpu/drm/xe/xe_uc_fw.c @@ -115,6 +115,7 @@ struct fw_blobs_by_type { #define XE_GT_TYPE_ANY XE_GT_TYPE_UNINITIALIZED #define XE_GUC_FIRMWARE_DEFS(fw_def, mmp_ver, major_ver) \ + fw_def(NOVALAKE_S, GT_TYPE_ANY, major_ver(xe, guc, nvl, 70, 71, 0)) \ fw_def(PANTHERLAKE, GT_TYPE_ANY, major_ver(xe, guc, ptl, 70, 54, 0)) \ fw_def(BATTLEMAGE, GT_TYPE_ANY, major_ver(xe, guc, bmg, 70, 54, 0)) \ fw_def(LUNARLAKE, GT_TYPE_ANY, major_ver(xe, guc, lnl, 70, 53, 0)) \ From 1ac2dbb6f7b4af285d6ccbade313e50b75a2f363 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:22 +0100 Subject: [PATCH 14/81] drm/xe/guc: refactor ads to use guc_class Currently in the lrc init flow on the ads side, we loop through each generic engine class and convert that to the respective guc engine class. However, with some upcoming changes, it will be better to go the opposite way and loop through every guc engine class, and convert that to the generic engine class. This will be needed in an upcoming patch where we have a new guc engine class that just matches up to the existing blitter/copy class, but needs to be treated as a separate entity from the normal copy lrc, when setting up the ADS. This also reworks engine_enable_mask to operate on the guc_class, that way we can easily filter out the PAGING vs normal BSC, when applicable. As a bonus, this also gets rid of two xe_engine_class_to_guc_class() users which will be helpful for the next patch. No functional changes. v2 (Daniele): - Simplify fill_engine_enable_masks() to just loop over all guc classes. Suggested-by: Daniele Ceraolo Spurio Signed-off-by: Matthew Auld Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-13-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_guc_ads.c | 69 ++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 79d6fd8d919b..6910179aff48 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -251,14 +251,35 @@ static size_t calculate_regset_size(struct xe_gt *gt) return count * sizeof(struct guc_mmio_reg); } -static u32 engine_enable_mask(struct xe_gt *gt, enum xe_engine_class class) +static inline enum xe_engine_class guc_class_to_engine_class(u16 guc_class) +{ + switch (guc_class) { + case GUC_RENDER_CLASS: + return XE_ENGINE_CLASS_RENDER; + case GUC_VIDEO_CLASS: + return XE_ENGINE_CLASS_VIDEO_DECODE; + case GUC_VIDEOENHANCE_CLASS: + return XE_ENGINE_CLASS_VIDEO_ENHANCE; + case GUC_BLITTER_CLASS: + return XE_ENGINE_CLASS_COPY; + case GUC_COMPUTE_CLASS: + return XE_ENGINE_CLASS_COMPUTE; + case GUC_GSC_OTHER_CLASS: + return XE_ENGINE_CLASS_OTHER; + default: + XE_WARN_ON(guc_class); + return -1; + } +} + +static u32 engine_enable_mask(struct xe_gt *gt, u16 guc_class) { struct xe_hw_engine *hwe; enum xe_hw_engine_id id; u32 mask = 0; for_each_hw_engine(hwe, gt, id) - if (hwe->class == class) + if (xe_engine_class_to_guc_class(hwe->class) == guc_class) mask |= BIT(hwe->instance); return mask; @@ -268,10 +289,13 @@ static size_t calculate_golden_lrc_size(struct xe_guc_ads *ads) { struct xe_gt *gt = ads_to_gt(ads); size_t total_size = 0, alloc_size, real_size; - int class; + u16 guc_class; - for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) { - if (!engine_enable_mask(gt, class)) + for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) { + enum xe_engine_class class = + guc_class_to_engine_class(guc_class); + + if (!engine_enable_mask(gt, guc_class)) continue; real_size = xe_gt_lrc_size(gt, class); @@ -472,20 +496,11 @@ static void fill_engine_enable_masks(struct xe_gt *gt, struct iosys_map *info_map) { struct xe_device *xe = gt_to_xe(gt); + u16 guc_class; - info_map_write(xe, info_map, engine_enabled_masks[GUC_RENDER_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_RENDER)); - info_map_write(xe, info_map, engine_enabled_masks[GUC_BLITTER_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_COPY)); - info_map_write(xe, info_map, engine_enabled_masks[GUC_VIDEO_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_VIDEO_DECODE)); - info_map_write(xe, info_map, - engine_enabled_masks[GUC_VIDEOENHANCE_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_VIDEO_ENHANCE)); - info_map_write(xe, info_map, engine_enabled_masks[GUC_COMPUTE_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_COMPUTE)); - info_map_write(xe, info_map, engine_enabled_masks[GUC_GSC_OTHER_CLASS], - engine_enable_mask(gt, XE_ENGINE_CLASS_OTHER)); + for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) + info_map_write(xe, info_map, engine_enabled_masks[guc_class], + engine_enable_mask(gt, guc_class)); } /* @@ -500,15 +515,14 @@ static void guc_golden_lrc_init(struct xe_guc_ads *ads) offsetof(struct __guc_ads_blob, system_info)); size_t alloc_size, real_size; u32 addr_ggtt, offset; - int class; + u16 guc_class; offset = guc_ads_golden_lrc_offset(ads); addr_ggtt = xe_bo_ggtt_addr(ads->bo) + offset; - for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) { - u8 guc_class; - - guc_class = xe_engine_class_to_guc_class(class); + for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) { + enum xe_engine_class class = + guc_class_to_engine_class(guc_class); if (!info_map_read(xe, &info_map, engine_enabled_masks[guc_class])) @@ -957,14 +971,13 @@ static void guc_golden_lrc_populate(struct xe_guc_ads *ads) offsetof(struct __guc_ads_blob, system_info)); size_t total_size = 0, alloc_size, real_size; u32 offset; - int class; + u16 guc_class; offset = guc_ads_golden_lrc_offset(ads); - for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) { - u8 guc_class; - - guc_class = xe_engine_class_to_guc_class(class); + for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) { + enum xe_engine_class class = + guc_class_to_engine_class(guc_class); if (!info_map_read(xe, &info_map, engine_enabled_masks[guc_class])) From 72670b90a0a767b0a097139790e56d5fc167ae50 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:23 +0100 Subject: [PATCH 15/81] drm/xe/guc: refactor to_guc_class() to accept hwe Rather than inferring the GuC engine class from the generic hw engine class, pass in the hwe itself, which gives the complete view, like instance etc. On future GuC versions, there is dedicated PAGING class to identify the KMD reserved BCS engine, so we need more info here in order to return the correct GuC specific engine class. With this everything should now be using the new hwe based interface. No functional changes. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-14-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c | 2 +- drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c | 2 +- drivers/gpu/drm/xe/xe_guc.h | 21 +--------------- drivers/gpu/drm/xe/xe_guc_ads.c | 27 ++++++++++++++++++--- drivers/gpu/drm/xe/xe_guc_capture.c | 12 ++++----- drivers/gpu/drm/xe/xe_guc_capture.h | 4 +-- drivers/gpu/drm/xe/xe_guc_engine_activity.c | 4 +-- drivers/gpu/drm/xe/xe_guc_submit.c | 2 +- 8 files changed, 38 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c index ffa27f66bba7..f28c7ae0e8c2 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c @@ -381,7 +381,7 @@ static ssize_t sched_group_engines_read(struct file *file, char __user *buf, if (group < num_groups) { for_each_hw_engine(hwe, gt, id) { - u8 guc_class = xe_engine_class_to_guc_class(hwe->class); + u8 guc_class = xe_hwe_to_guc_class(hwe); u32 mask = groups[group].engines[guc_class]; if (mask & BIT(hwe->logical_instance)) { diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c index e8458d63742d..cf117bf52d41 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c @@ -471,7 +471,7 @@ static void pf_sched_group_media_slices(struct xe_gt *gt, struct guc_sched_group return; for_each_hw_engine(hwe, gt, id) { - u8 guc_class = xe_engine_class_to_guc_class(hwe->class); + u8 guc_class = xe_hwe_to_guc_class(hwe); switch (hwe->class) { case XE_ENGINE_CLASS_VIDEO_DECODE: diff --git a/drivers/gpu/drm/xe/xe_guc.h b/drivers/gpu/drm/xe/xe_guc.h index 02514914f404..12faf0ba7229 100644 --- a/drivers/gpu/drm/xe/xe_guc.h +++ b/drivers/gpu/drm/xe/xe_guc.h @@ -67,26 +67,7 @@ bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc); int xe_guc_g2g_test_notification(struct xe_guc *guc, u32 *payload, u32 len); #endif -static inline u16 xe_engine_class_to_guc_class(enum xe_engine_class class) -{ - switch (class) { - case XE_ENGINE_CLASS_RENDER: - return GUC_RENDER_CLASS; - case XE_ENGINE_CLASS_VIDEO_DECODE: - return GUC_VIDEO_CLASS; - case XE_ENGINE_CLASS_VIDEO_ENHANCE: - return GUC_VIDEOENHANCE_CLASS; - case XE_ENGINE_CLASS_COPY: - return GUC_BLITTER_CLASS; - case XE_ENGINE_CLASS_COMPUTE: - return GUC_COMPUTE_CLASS; - case XE_ENGINE_CLASS_OTHER: - return GUC_GSC_OTHER_CLASS; - default: - XE_WARN_ON(class); - return -1; - } -} +u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe); static inline struct xe_gt *guc_to_gt(struct xe_guc *guc) { diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 6910179aff48..86b3261704bd 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -279,7 +279,7 @@ static u32 engine_enable_mask(struct xe_gt *gt, u16 guc_class) u32 mask = 0; for_each_hw_engine(hwe, gt, id) - if (xe_engine_class_to_guc_class(hwe->class) == guc_class) + if (xe_hwe_to_guc_class(hwe) == guc_class) mask |= BIT(hwe->instance); return mask; @@ -503,6 +503,27 @@ static void fill_engine_enable_masks(struct xe_gt *gt, engine_enable_mask(gt, guc_class)); } +u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe) +{ + switch (hwe->class) { + case XE_ENGINE_CLASS_RENDER: + return GUC_RENDER_CLASS; + case XE_ENGINE_CLASS_VIDEO_DECODE: + return GUC_VIDEO_CLASS; + case XE_ENGINE_CLASS_VIDEO_ENHANCE: + return GUC_VIDEOENHANCE_CLASS; + case XE_ENGINE_CLASS_COPY: + return GUC_BLITTER_CLASS; + case XE_ENGINE_CLASS_COMPUTE: + return GUC_COMPUTE_CLASS; + case XE_ENGINE_CLASS_OTHER: + return GUC_GSC_OTHER_CLASS; + default: + XE_WARN_ON(hwe->class); + return -1; + } +} + /* * Write the offsets corresponding to the golden LRCs. The actual data is * populated later by guc_golden_lrc_populate() @@ -573,7 +594,7 @@ static void guc_mapping_table_init(struct xe_gt *gt, for_each_hw_engine(hwe, gt, id) { u8 guc_class; - guc_class = xe_engine_class_to_guc_class(hwe->class); + guc_class = xe_hwe_to_guc_class(hwe); info_map_write(xe, info_map, mapping_table[guc_class][hwe->logical_instance], hwe->instance); @@ -828,7 +849,7 @@ static void guc_mmio_reg_state_init(struct xe_guc_ads *ads) * 2. Record in the header (ads.reg_state_list) the address * location and number of entries */ - gc = xe_engine_class_to_guc_class(hwe->class); + gc = xe_hwe_to_guc_class(hwe); ads_blob_write(ads, ads.reg_state_list[gc][hwe->instance].address, addr); ads_blob_write(ads, ads.reg_state_list[gc][hwe->instance].count, count); diff --git a/drivers/gpu/drm/xe/xe_guc_capture.c b/drivers/gpu/drm/xe/xe_guc_capture.c index 1a019137ddf4..3f287dd5e34e 100644 --- a/drivers/gpu/drm/xe/xe_guc_capture.c +++ b/drivers/gpu/drm/xe/xe_guc_capture.c @@ -440,7 +440,7 @@ static void guc_capture_alloc_steered_lists(struct xe_guc *guc) * to be extended */ for_each_hw_engine(hwe, gt, id) { - if (xe_engine_class_to_guc_capture_class(hwe->class) == + if (xe_hwe_to_guc_capture_class(hwe) == GUC_CAPTURE_LIST_CLASS_RENDER_COMPUTE) { has_rcs_ccs = true; break; @@ -818,7 +818,7 @@ static int guc_capture_output_size_est(struct xe_guc *guc) for_each_hw_engine(hwe, gt, id) { enum guc_capture_list_class_type capture_class; - capture_class = xe_engine_class_to_guc_capture_class(hwe->class); + capture_class = xe_hwe_to_guc_capture_class(hwe); capture_size += sizeof(struct guc_state_capture_group_header_t) + (3 * sizeof(struct guc_state_capture_header_t)); @@ -1626,7 +1626,7 @@ xe_engine_manual_capture(struct xe_hw_engine *hwe, struct xe_hw_engine_snapshot if (!new) return; - capture_class = xe_engine_class_to_guc_capture_class(hwe->class); + capture_class = xe_hwe_to_guc_capture_class(hwe); for (type = GUC_STATE_CAPTURE_TYPE_GLOBAL; type < GUC_STATE_CAPTURE_TYPE_MAX; type++) { struct gcap_reg_list_info *reginfo = &new->reginfo[type]; /* @@ -1668,7 +1668,7 @@ xe_engine_manual_capture(struct xe_hw_engine *hwe, struct xe_hw_engine_snapshot } } - new->eng_class = xe_engine_class_to_guc_class(hwe->class); + new->eng_class = xe_hwe_to_guc_class(hwe); new->eng_inst = hwe->instance; new->guc_id = guc_id; new->lrca = lrca; @@ -1832,7 +1832,7 @@ void xe_engine_snapshot_print(struct xe_hw_engine_snapshot *snapshot, struct drm xe_gt_assert(gt, snapshot->hwe); - capture_class = xe_engine_class_to_guc_capture_class(snapshot->hwe->class); + capture_class = xe_hwe_to_guc_capture_class(snapshot->hwe); drm_printf(p, "%s (physical), logical instance=%d\n", snapshot->name ? snapshot->name : "", @@ -1904,7 +1904,7 @@ xe_guc_capture_get_matching_and_lock(struct xe_exec_queue *q) for_each_hw_engine(hwe, q->gt, id) { if (hwe != q->hwe) continue; - guc_class = xe_engine_class_to_guc_class(hwe->class); + guc_class = xe_hwe_to_guc_class(hwe); break; } diff --git a/drivers/gpu/drm/xe/xe_guc_capture.h b/drivers/gpu/drm/xe/xe_guc_capture.h index dca97d52b192..eb954f4d1ffd 100644 --- a/drivers/gpu/drm/xe/xe_guc_capture.h +++ b/drivers/gpu/drm/xe/xe_guc_capture.h @@ -35,9 +35,9 @@ static inline enum guc_capture_list_class_type xe_guc_class_to_capture_class(u16 } static inline enum guc_capture_list_class_type -xe_engine_class_to_guc_capture_class(enum xe_engine_class class) +xe_hwe_to_guc_capture_class(struct xe_hw_engine *hwe) { - return xe_guc_class_to_capture_class(xe_engine_class_to_guc_class(class)); + return xe_guc_class_to_capture_class(xe_hwe_to_guc_class(hwe)); } void xe_guc_capture_process(struct xe_guc *guc); diff --git a/drivers/gpu/drm/xe/xe_guc_engine_activity.c b/drivers/gpu/drm/xe/xe_guc_engine_activity.c index f43ca1c76f75..c3a5fa80388b 100644 --- a/drivers/gpu/drm/xe/xe_guc_engine_activity.c +++ b/drivers/gpu/drm/xe/xe_guc_engine_activity.c @@ -27,7 +27,7 @@ static struct iosys_map engine_activity_map(struct xe_guc *guc, struct xe_hw_eng { struct xe_guc_engine_activity *engine_activity = &guc->engine_activity; struct engine_activity_buffer *buffer; - u16 guc_class = xe_engine_class_to_guc_class(hwe->class); + u16 guc_class = xe_hwe_to_guc_class(hwe); size_t offset; if (engine_activity->num_functions) { @@ -150,7 +150,7 @@ static struct engine_activity *hw_engine_to_engine_activity(struct xe_hw_engine { struct xe_guc *guc = &hwe->gt->uc.guc; struct engine_activity_group *eag = &guc->engine_activity.eag[index]; - u16 guc_class = xe_engine_class_to_guc_class(hwe->class); + u16 guc_class = xe_hwe_to_guc_class(hwe); return &eag->engine[guc_class][hwe->logical_instance]; } diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index e5fcb1f21115..68e484b16383 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -984,7 +984,7 @@ static void register_exec_queue(struct xe_exec_queue *q, int ctx_type) memset(&info, 0, sizeof(info)); info.context_idx = q->guc->id; - info.engine_class = xe_engine_class_to_guc_class(q->class); + info.engine_class = xe_hwe_to_guc_class(q->hwe); info.engine_submit_mask = q->logical_mask; info.hwlrca_lo = lower_32_bits(xe_lrc_descriptor(lrc)); info.hwlrca_hi = upper_32_bits(xe_lrc_descriptor(lrc)); From 0c9a7926bd8edbeb6e416afdf07cfd8a96d56e20 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:24 +0100 Subject: [PATCH 16/81] drm/xe/guc: add the plumbing for GUC_PAGING_CLASS On newer platforms, the GuC has a new engine class which we need to use to refer to the dedicated/reserved KMD BCS engine. With that, add the plumbing in the GuC backend to support GUC_PAGING_CLASS and GUC_CAPTURE_LIST_CLASS_PAGING. Currently this is still turned off. v2 (Daniele) - Also add adjust the capture list for hpg, so we account for nvl-s. - Move single paging engine assert to a more natural place. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-15-matthew.auld@intel.com --- drivers/gpu/drm/xe/abi/guc_capture_abi.h | 3 ++- drivers/gpu/drm/xe/abi/guc_scheduler_abi.h | 3 ++- drivers/gpu/drm/xe/xe_guc.c | 15 +++++++++++++++ drivers/gpu/drm/xe/xe_guc.h | 1 + drivers/gpu/drm/xe/xe_guc_ads.c | 12 ++++++++++++ drivers/gpu/drm/xe/xe_guc_capture.c | 9 +++++++++ drivers/gpu/drm/xe/xe_guc_capture.h | 2 ++ 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/abi/guc_capture_abi.h b/drivers/gpu/drm/xe/abi/guc_capture_abi.h index dd4117553739..ff9c0ae34a28 100644 --- a/drivers/gpu/drm/xe/abi/guc_capture_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_capture_abi.h @@ -32,9 +32,10 @@ enum guc_capture_list_class_type { GUC_CAPTURE_LIST_CLASS_VIDEOENHANCE = 2, GUC_CAPTURE_LIST_CLASS_BLITTER = 3, GUC_CAPTURE_LIST_CLASS_GSC_OTHER = 4, + GUC_CAPTURE_LIST_CLASS_PAGING = 5, }; -#define GUC_CAPTURE_LIST_CLASS_MAX (GUC_CAPTURE_LIST_CLASS_GSC_OTHER + 1) +#define GUC_CAPTURE_LIST_CLASS_MAX (GUC_CAPTURE_LIST_CLASS_PAGING + 1) /** * struct guc_mmio_reg - GuC MMIO reg state struct diff --git a/drivers/gpu/drm/xe/abi/guc_scheduler_abi.h b/drivers/gpu/drm/xe/abi/guc_scheduler_abi.h index 19ec89bf39c5..85bba34277ed 100644 --- a/drivers/gpu/drm/xe/abi/guc_scheduler_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_scheduler_abi.h @@ -21,7 +21,8 @@ #define GUC_BLITTER_CLASS 3 #define GUC_COMPUTE_CLASS 4 #define GUC_GSC_OTHER_CLASS 5 -#define GUC_LAST_ENGINE_CLASS GUC_GSC_OTHER_CLASS +#define GUC_PAGING_CLASS 6 +#define GUC_LAST_ENGINE_CLASS GUC_PAGING_CLASS #define GUC_MAX_ENGINE_CLASSES 16 #define GUC_MAX_INSTANCES_PER_CLASS 32 diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index 543f3992bccd..840722c007e0 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -1856,6 +1856,21 @@ bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc) return GT_VER(gt) >= 35; } +bool xe_guc_has_paging_engine(struct xe_guc *guc) +{ + /* + * On newer platforms the GuC now has a dedicated engine class for the + * special PAGING engine, which is the driver reserved BCS engine used + * for KMD paging/binding operations. GuC requires KMD to refer to this + * using the special PAGING engine class. Note that there is no new hw + * engine here, this is purely a sw view in the GuC itself, which we + * need to respect. + */ + + /* TODO: Have some way to query this from the GuC? */ + return false; +} + #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) #include "tests/xe_guc_g2g_test.c" #endif diff --git a/drivers/gpu/drm/xe/xe_guc.h b/drivers/gpu/drm/xe/xe_guc.h index 12faf0ba7229..0934927e8254 100644 --- a/drivers/gpu/drm/xe/xe_guc.h +++ b/drivers/gpu/drm/xe/xe_guc.h @@ -62,6 +62,7 @@ void xe_guc_stop(struct xe_guc *guc); int xe_guc_start(struct xe_guc *guc); void xe_guc_declare_wedged(struct xe_guc *guc); bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc); +bool xe_guc_has_paging_engine(struct xe_guc *guc); #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) int xe_guc_g2g_test_notification(struct xe_guc *guc, u32 *payload, u32 len); diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 86b3261704bd..1c88dbe25729 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -261,6 +261,7 @@ static inline enum xe_engine_class guc_class_to_engine_class(u16 guc_class) case GUC_VIDEOENHANCE_CLASS: return XE_ENGINE_CLASS_VIDEO_ENHANCE; case GUC_BLITTER_CLASS: + case GUC_PAGING_CLASS: return XE_ENGINE_CLASS_COPY; case GUC_COMPUTE_CLASS: return XE_ENGINE_CLASS_COMPUTE; @@ -282,6 +283,10 @@ static u32 engine_enable_mask(struct xe_gt *gt, u16 guc_class) if (xe_hwe_to_guc_class(hwe) == guc_class) mask |= BIT(hwe->instance); + /* We expect at most one paging engine per GuC instance, for now */ + if (guc_class == GUC_PAGING_CLASS) + xe_gt_assert(gt, !mask || is_power_of_2(mask)); + return mask; } @@ -505,6 +510,10 @@ static void fill_engine_enable_masks(struct xe_gt *gt, u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe) { + if (xe_guc_has_paging_engine(&hwe->gt->uc.guc) && + xe_gt_is_usm_hwe(hwe->gt, hwe)) + return GUC_PAGING_CLASS; + switch (hwe->class) { case XE_ENGINE_CLASS_RENDER: return GUC_RENDER_CLASS; @@ -624,6 +633,9 @@ static u32 guc_get_capture_engine_mask(struct xe_gt *gt, struct iosys_map *info_ case GUC_CAPTURE_LIST_CLASS_GSC_OTHER: mask = info_map_read(xe, info_map, engine_enabled_masks[GUC_GSC_OTHER_CLASS]); break; + case GUC_CAPTURE_LIST_CLASS_PAGING: + mask = info_map_read(xe, info_map, engine_enabled_masks[GUC_PAGING_CLASS]); + break; default: mask = 0; } diff --git a/drivers/gpu/drm/xe/xe_guc_capture.c b/drivers/gpu/drm/xe/xe_guc_capture.c index 3f287dd5e34e..82df19b304e1 100644 --- a/drivers/gpu/drm/xe/xe_guc_capture.c +++ b/drivers/gpu/drm/xe/xe_guc_capture.c @@ -249,6 +249,8 @@ static const struct __guc_mmio_reg_descr_group xe_hpg_lists[] = { MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_BLITTER), MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_GSC_OTHER), MAKE_REGLIST(xe_lp_gsc_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_GSC_OTHER), + MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_PAGING), + MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_PAGING), {} }; @@ -265,6 +267,8 @@ static const struct __guc_mmio_reg_descr_group xe3p_lists[] = { MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_BLITTER), MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_GSC_OTHER), MAKE_REGLIST(xe_lp_gsc_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_GSC_OTHER), + MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_PAGING), + MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_PAGING), {} }; static const char * const capture_list_type_names[] = { @@ -279,6 +283,7 @@ static const char * const capture_engine_class_names[] = { "VideoEnhance", "Blitter", "GSC-Other", + "Paging", }; struct __guc_capture_ads_cache { @@ -772,6 +777,10 @@ size_t xe_guc_capture_ads_input_worst_size(struct xe_guc *guc) total_size = PAGE_SIZE; /* Pad a page in front for empty lists */ for (i = 0; i < GUC_CAPTURE_LIST_INDEX_MAX; i++) { for (j = 0; j < GUC_CAPTURE_LIST_CLASS_MAX; j++) { + if (!xe_guc_has_paging_engine(guc) && + j == GUC_CAPTURE_LIST_CLASS_PAGING) + continue; + if (xe_guc_capture_getlistsize(guc, i, GUC_STATE_CAPTURE_TYPE_ENGINE_CLASS, j, &class_size) < 0) diff --git a/drivers/gpu/drm/xe/xe_guc_capture.h b/drivers/gpu/drm/xe/xe_guc_capture.h index eb954f4d1ffd..fcd4f1298536 100644 --- a/drivers/gpu/drm/xe/xe_guc_capture.h +++ b/drivers/gpu/drm/xe/xe_guc_capture.h @@ -28,6 +28,8 @@ static inline enum guc_capture_list_class_type xe_guc_class_to_capture_class(u16 case GUC_VIDEOENHANCE_CLASS: case GUC_BLITTER_CLASS: return class; + case GUC_PAGING_CLASS: + return GUC_CAPTURE_LIST_CLASS_PAGING; default: XE_WARN_ON(class); return GUC_CAPTURE_LIST_CLASS_MAX; From 08af1a0efd04301d094f9acc09ce0c1ba8ea26c5 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:25 +0100 Subject: [PATCH 17/81] drm/xe/hw_engine: don't open code is_usm_hwe() Prefer is_usm_hwe() here. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-16-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_hw_engine.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index 87d60c4117bd..dd2d37e3d80c 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -1043,8 +1043,7 @@ bool xe_hw_engine_is_reserved(struct xe_hw_engine *hwe) hwe->logical_instance >= gt->ccs_mode) return true; - return xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY && - hwe->instance == gt->usm.reserved_bcs_instance; + return xe_gt_is_usm_hwe(gt, hwe); } const char *xe_hw_engine_class_to_str(enum xe_engine_class class) From 16c8849297a8ca755d1ef24a407dc7815c361928 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:26 +0100 Subject: [PATCH 18/81] drm/xe: refactor the paging engine setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On newer platforms, the paging configuration is now configured by the PF via the ADS object, where VF side should ensure that everything configured as GUC_PAGING_CLASS is correctly mirrored on VF side. For example PF could in theory reserve two BCS instances, and we expect VF to mirror that. With that move towards having a logical mask of all the paging engines, and also generalise selecting those engines, based on the number of paging engines. Also cache the first designated paging engine, which will makes things a little cleaner here, and in later patches. No functional changes for existing platforms. v2 (Sashiko): - Rework the loop slightly so that we don't needlessly check for the paging engine, before we have correctly set the logical instance. - Add a proper error return, if we encounter a bogus paging config. Thinking ahead to VF where the config is defined by the PF, we should just gracefully exit the probe sequence. v3: - Move paging_engines > copy_engines engines check to VF patch. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Cc: Thomas Hellström Cc: Matthew Brost Reviewed-by: Francois Dugast Link: https://patch.msgid.link/20260626111520.487997-17-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_exec_queue.c | 5 +--- drivers/gpu/drm/xe/xe_gt.h | 6 ++--- drivers/gpu/drm/xe/xe_gt_types.h | 12 ++++++--- drivers/gpu/drm/xe/xe_hw_engine.c | 40 ++++++++++++++++++++++-------- drivers/gpu/drm/xe/xe_migrate.c | 32 +++--------------------- 5 files changed, 46 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index 1b5ca3ce578a..cfd2a4e6d4c7 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -530,10 +530,7 @@ struct xe_exec_queue *xe_exec_queue_create_bind(struct xe_device *xe, migrate_vm = xe_migrate_get_vm(tile->migrate); if (xe->info.has_usm) { - struct xe_hw_engine *hwe = xe_gt_hw_engine(gt, - XE_ENGINE_CLASS_COPY, - gt->usm.reserved_bcs_instance, - false); + struct xe_hw_engine *hwe = gt->usm.paging_hwe0; if (!hwe) { xe_vm_put(migrate_vm); diff --git a/drivers/gpu/drm/xe/xe_gt.h b/drivers/gpu/drm/xe/xe_gt.h index 4150aa594f05..a6cfaa1af23f 100644 --- a/drivers/gpu/drm/xe/xe_gt.h +++ b/drivers/gpu/drm/xe/xe_gt.h @@ -137,10 +137,8 @@ static inline bool xe_gt_is_media_type(struct xe_gt *gt) static inline bool xe_gt_is_usm_hwe(struct xe_gt *gt, struct xe_hw_engine *hwe) { - struct xe_device *xe = gt_to_xe(gt); - - return xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY && - hwe->instance == gt->usm.reserved_bcs_instance; + return hwe->class == XE_ENGINE_CLASS_COPY && + (gt->usm.paging_logical_mask & BIT(hwe->logical_instance)); } /** diff --git a/drivers/gpu/drm/xe/xe_gt_types.h b/drivers/gpu/drm/xe/xe_gt_types.h index 0d234160ee3a..a8bbfbdf3849 100644 --- a/drivers/gpu/drm/xe/xe_gt_types.h +++ b/drivers/gpu/drm/xe/xe_gt_types.h @@ -235,10 +235,16 @@ struct xe_gt { */ struct xe_sa_manager *bb_pool; /** - * @usm.reserved_bcs_instance: reserved BCS instance used for USM - * operations (e.g. migrations, fixing page tables) + * @usm.paging_hwe0: The first designated paging engine. + * This is some reserved BCS instance used for USM operations + * (e.g. migrations, fixing page tables) */ - u16 reserved_bcs_instance; + struct xe_hw_engine *paging_hwe0; + /** + * @usm.paging_logical_mask: logical mask of paging engines. + * Should be densely populated. + */ + u32 paging_logical_mask; } usm; /** @ordered_wq: used to serialize GT resets and TDRs */ diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index dd2d37e3d80c..d741d93601b2 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -647,10 +647,6 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe, xe_hw_engine_enable_ring(hwe); } - /* We reserve the highest BCS instance for USM */ - if (xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY) - gt->usm.reserved_bcs_instance = hwe->instance; - /* Ensure IDLEDLY is lower than MAXCNT */ adjust_idledly(hwe); @@ -662,19 +658,43 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe, return err; } -static void hw_engine_setup_logical_mapping(struct xe_gt *gt) +static void hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) { + struct xe_device *xe = gt_to_xe(gt); + unsigned int num_copy_engines = 0, num_paging_engines = 0; + unsigned int reserved_logical_bcs_start; + struct xe_hw_engine *hwe; + enum xe_hw_engine_id id; int class; + for_each_hw_engine(hwe, gt, id) + if (hwe->class == XE_ENGINE_CLASS_COPY) + num_copy_engines++; + + /* We just reserve the highest BCS instance for USM */ + if (num_copy_engines && xe->info.has_usm) + num_paging_engines = 1; + + reserved_logical_bcs_start = num_copy_engines - num_paging_engines; + /* FIXME: Doing a simple logical mapping that works for most hardware */ for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) { - struct xe_hw_engine *hwe; - enum xe_hw_engine_id id; int logical_instance = 0; - for_each_hw_engine(hwe, gt, id) - if (hwe->class == class) + for_each_hw_engine(hwe, gt, id) { + if (hwe->class == class) { hwe->logical_instance = logical_instance++; + + if (class == XE_ENGINE_CLASS_COPY && + hwe->logical_instance >= + reserved_logical_bcs_start) { + if (!gt->usm.paging_hwe0) + gt->usm.paging_hwe0 = hwe; + gt->usm.paging_logical_mask |= + BIT(hwe->logical_instance); + } + } + } } } @@ -894,7 +914,7 @@ int xe_hw_engines_init(struct xe_gt *gt) return err; } - hw_engine_setup_logical_mapping(gt); + hw_engine_setup_logical_and_paging_mapping(gt); err = xe_hw_engine_setup_groups(gt); if (err) return err; diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 9428dd5e7760..92d5e81ceac2 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -383,27 +383,6 @@ static void xe_migrate_suballoc_manager_init(struct xe_migrate *m, u32 map_ofs) NUM_VMUSA_UNIT_PER_PAGE, 0); } -/* - * Including the reserved copy engine is required to avoid deadlocks due to - * migrate jobs servicing the faults gets stuck behind the job that faulted. - */ -static u32 xe_migrate_usm_logical_mask(struct xe_gt *gt) -{ - u32 logical_mask = 0; - struct xe_hw_engine *hwe; - enum xe_hw_engine_id id; - - for_each_hw_engine(hwe, gt, id) { - if (hwe->class != XE_ENGINE_CLASS_COPY) - continue; - - if (xe_gt_is_usm_hwe(gt, hwe)) - logical_mask |= BIT(hwe->logical_instance); - } - - return logical_mask; -} - static bool xe_migrate_needs_ccs_emit(struct xe_device *xe) { return xe_device_has_flat_ccs(xe) && !(GRAPHICS_VER(xe) >= 20 && IS_DGFX(xe)); @@ -479,13 +458,10 @@ int xe_migrate_init(struct xe_migrate *m) goto err_out; if (xe->info.has_usm) { - struct xe_hw_engine *hwe = xe_gt_hw_engine(primary_gt, - XE_ENGINE_CLASS_COPY, - primary_gt->usm.reserved_bcs_instance, - false); - u32 logical_mask = xe_migrate_usm_logical_mask(primary_gt); + struct xe_hw_engine *hwe0 = primary_gt->usm.paging_hwe0; + u32 logical_mask = primary_gt->usm.paging_logical_mask; - if (!hwe || !logical_mask) { + if (!hwe0 || !logical_mask) { err = -EINVAL; goto err_out; } @@ -494,7 +470,7 @@ int xe_migrate_init(struct xe_migrate *m) * XXX: Currently only reserving 1 (likely slow) BCS instance on * PVC, may want to revisit if performance is needed. */ - m->q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe, + m->q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe0, EXEC_QUEUE_FLAG_KERNEL | EXEC_QUEUE_FLAG_PERMANENT | EXEC_QUEUE_FLAG_HIGH_PRIORITY | From 07bb6ba65524e934b87e792c8231ff84195e2719 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:27 +0100 Subject: [PATCH 19/81] drm/xe/guc: handle guc logical instance for paging engine In the GuC backend, we need a different logical instance when referring to the reserved paging engine. Under the hood, this is still just the same physical BSC engine, however from the GuC POV this is actually re-mapped to a separate GUC_PAGING_CLASS, with the logical index starting from zero. The idea is to not leak this into the upper layers, since this is GuC version specific, so the changes here are purely on the GuC side. No functional change. v2: - Add some kernel-doc to explain the usage. - Move the implementation to guc.c Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-18-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c | 3 +- drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c | 3 +- drivers/gpu/drm/xe/xe_guc.c | 31 +++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc.h | 1 + drivers/gpu/drm/xe/xe_guc_ads.c | 5 +++- drivers/gpu/drm/xe/xe_guc_engine_activity.c | 6 ++-- drivers/gpu/drm/xe/xe_hw_engine_types.h | 7 ++++- 7 files changed, 50 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c index f28c7ae0e8c2..0f242db775e1 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_debugfs.c @@ -382,9 +382,10 @@ static ssize_t sched_group_engines_read(struct file *file, char __user *buf, if (group < num_groups) { for_each_hw_engine(hwe, gt, id) { u8 guc_class = xe_hwe_to_guc_class(hwe); + u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe); u32 mask = groups[group].engines[guc_class]; - if (mask & BIT(hwe->logical_instance)) { + if (mask & BIT(guc_logical_instance)) { strlcat(engines, hwe->name, sizeof(engines)); strlcat(engines, " ", sizeof(engines)); } diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c index cf117bf52d41..cdfe194926d3 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_policy.c @@ -472,6 +472,7 @@ static void pf_sched_group_media_slices(struct xe_gt *gt, struct guc_sched_group for_each_hw_engine(hwe, gt, id) { u8 guc_class = xe_hwe_to_guc_class(hwe); + u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe); switch (hwe->class) { case XE_ENGINE_CLASS_VIDEO_DECODE: @@ -490,7 +491,7 @@ static void pf_sched_group_media_slices(struct xe_gt *gt, struct guc_sched_group slice = 0; } - values[slice_to_group[slice]].engines[guc_class] |= BIT(hwe->logical_instance); + values[slice_to_group[slice]].engines[guc_class] |= BIT(guc_logical_instance); } *groups = values; diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index 840722c007e0..bbf6dfb74532 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -1871,6 +1871,37 @@ bool xe_guc_has_paging_engine(struct xe_guc *guc) return false; } +/** + * xe_hwe_guc_logical_instance - Get the GuC-aligned logical instance of a + * hardware engine. + * @hwe: Hardware engine. + * + * For GuC backend usage, we should no longer use the raw logical instance + * directly. This helper must be used to retrieve the logical instance of the + * hardware engine, taking care of any necessary adjustments (such as the GuC + * PAGING engine mapping). This is assumed to be used in conjunction with the + * GuC engine class. + * + * Return: Logical instance, taking into account for stuff like GuC PAGING + * engine mapping. + */ +u16 xe_hwe_guc_logical_instance(struct xe_hw_engine *hwe) +{ + struct xe_gt *gt = hwe->gt; + + if (xe_guc_has_paging_engine(&hwe->gt->uc.guc) && + xe_gt_is_usm_hwe(gt, hwe)) { + int shift = gt->usm.paging_hwe0->logical_instance; + + xe_gt_assert(gt, shift <= hwe->logical_instance); + + /* GUC_PAGING_CLASS:guc_logical_instance */ + return hwe->logical_instance - shift; + } + + return hwe->logical_instance; +} + #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) #include "tests/xe_guc_g2g_test.c" #endif diff --git a/drivers/gpu/drm/xe/xe_guc.h b/drivers/gpu/drm/xe/xe_guc.h index 0934927e8254..61e3ee19a59b 100644 --- a/drivers/gpu/drm/xe/xe_guc.h +++ b/drivers/gpu/drm/xe/xe_guc.h @@ -69,6 +69,7 @@ int xe_guc_g2g_test_notification(struct xe_guc *guc, u32 *payload, u32 len); #endif u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe); +u16 xe_hwe_guc_logical_instance(struct xe_hw_engine *hwe); static inline struct xe_gt *guc_to_gt(struct xe_guc *guc) { diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 1c88dbe25729..5870194b06f6 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -601,11 +601,14 @@ static void guc_mapping_table_init(struct xe_gt *gt, guc_mapping_table_init_invalid(gt, info_map); for_each_hw_engine(hwe, gt, id) { + u16 guc_logical_instance; u8 guc_class; guc_class = xe_hwe_to_guc_class(hwe); + guc_logical_instance = xe_hwe_guc_logical_instance(hwe); + info_map_write(xe, info_map, - mapping_table[guc_class][hwe->logical_instance], + mapping_table[guc_class][guc_logical_instance], hwe->instance); } } diff --git a/drivers/gpu/drm/xe/xe_guc_engine_activity.c b/drivers/gpu/drm/xe/xe_guc_engine_activity.c index c3a5fa80388b..a782be57caad 100644 --- a/drivers/gpu/drm/xe/xe_guc_engine_activity.c +++ b/drivers/gpu/drm/xe/xe_guc_engine_activity.c @@ -28,6 +28,7 @@ static struct iosys_map engine_activity_map(struct xe_guc *guc, struct xe_hw_eng struct xe_guc_engine_activity *engine_activity = &guc->engine_activity; struct engine_activity_buffer *buffer; u16 guc_class = xe_hwe_to_guc_class(hwe); + u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe); size_t offset; if (engine_activity->num_functions) { @@ -39,7 +40,7 @@ static struct iosys_map engine_activity_map(struct xe_guc *guc, struct xe_hw_eng } offset += offsetof(struct guc_engine_activity_data, - engine_activity[guc_class][hwe->logical_instance]); + engine_activity[guc_class][guc_logical_instance]); return IOSYS_MAP_INIT_OFFSET(&buffer->activity_bo->vmap, offset); } @@ -151,8 +152,9 @@ static struct engine_activity *hw_engine_to_engine_activity(struct xe_hw_engine struct xe_guc *guc = &hwe->gt->uc.guc; struct engine_activity_group *eag = &guc->engine_activity.eag[index]; u16 guc_class = xe_hwe_to_guc_class(hwe); + u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe); - return &eag->engine[guc_class][hwe->logical_instance]; + return &eag->engine[guc_class][guc_logical_instance]; } static u64 cpu_ns_to_guc_tsc_tick(ktime_t ns, u32 freq) diff --git a/drivers/gpu/drm/xe/xe_hw_engine_types.h b/drivers/gpu/drm/xe/xe_hw_engine_types.h index 84c097da9b6f..ff115ab429fb 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine_types.h +++ b/drivers/gpu/drm/xe/xe_hw_engine_types.h @@ -114,7 +114,12 @@ struct xe_hw_engine { enum xe_engine_class class; /** @instance: physical instance of this hw engine */ u16 instance; - /** @logical_instance: logical instance of this hw engine */ + /** + * @logical_instance: logical instance of this hw engine. + * + * Note: For GuC usage, always use xe_hwe_guc_logical_instance(). + * For GuC usage, we should no longer use the raw logical instance. + */ u16 logical_instance; /** @irq_offset: IRQ offset of this hw engine */ u16 irq_offset; From d4438cf37005f7c1e12e5a79844be5c5ca35ea7c Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:28 +0100 Subject: [PATCH 20/81] drm/xe/guc: handle submit mask with paging engine We need to re-map the submit mask so that we correctly account for the logical mask of paging engines, if the GUC_PAGING_CLASS is in play. We could also have multiple instances (possible on VF), so we need to handle that also. v2 (Daniele): - Move the implementation to guc_submit.c Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-19-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_guc_submit.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 68e484b16383..cec3bbf3a10e 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -972,6 +972,27 @@ static void __register_exec_queue(struct xe_guc *guc, xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), 0, 0); } +static u32 xe_hwe_guc_logical_to_submit_mask(struct xe_hw_engine *hwe, u32 logical_mask) +{ + struct xe_gt *gt = hwe->gt; + + if (xe_gt_is_usm_hwe(gt, hwe)) { + int shift = gt->usm.paging_hwe0->logical_instance; + u32 paging_logical_mask = gt->usm.paging_logical_mask; + + xe_gt_assert(gt, (logical_mask & paging_logical_mask) == logical_mask); + + /* + * Remap to GUC_PAGING_CLASS logical instance mask, if + * applicable. + */ + if (xe_guc_has_paging_engine(&hwe->gt->uc.guc)) + return logical_mask >> shift; + } + + return logical_mask; +} + static void register_exec_queue(struct xe_exec_queue *q, int ctx_type) { struct xe_guc *guc = exec_queue_to_guc(q); @@ -985,7 +1006,8 @@ static void register_exec_queue(struct xe_exec_queue *q, int ctx_type) memset(&info, 0, sizeof(info)); info.context_idx = q->guc->id; info.engine_class = xe_hwe_to_guc_class(q->hwe); - info.engine_submit_mask = q->logical_mask; + info.engine_submit_mask = + xe_hwe_guc_logical_to_submit_mask(q->hwe, q->logical_mask); info.hwlrca_lo = lower_32_bits(xe_lrc_descriptor(lrc)); info.hwlrca_hi = upper_32_bits(xe_lrc_descriptor(lrc)); info.flags = CONTEXT_REGISTRATION_FLAG_KMD | From fe0d94f65bf2fcc6fea930360d834b199a77e1c2 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:29 +0100 Subject: [PATCH 21/81] drm/xe/vf: wire up NUM_PAGING_ENGINE_INSTANCES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When host PF writes the logical configuration for the GUC PAGING engine, the VF is meant to query it, and mirror it. Size of N means we have paging logical index range [0, N-1], with N fewer normal copy engines. Agreement is that PF will only spawn PAGING engines on NVL-S+, so this should be zero on older platforms, where we should simply fall back to the old behaviour. v2 (Sashiko): - We can't call use the guc_has_paging_engine() this early in the VF code. With that just unconditionally do the query, if the GuC is new enough and take the value as-is. With that drop the -1 special case and just let the upper layers figure out the rest. v3: - Also update xe_guc_klv_key_to_string. (Michal) - Add kernel-doc for xe_gt_sriov_vf_paging_engines(), plus other tweaks. (Michal) - Update with final GuC version. v4: - Just fallback to manual reserve when vf reported paging engines is zero. Will revisit in the future. v5 (Michal): - Convert the assert to a full abort if we ever see non-zero GuC paging engine count, on pre-nvl. - Move the VF hunk in guc_has_paging_engine() here. - Some small tweaks. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Cc: Piotr Piórkowski Cc: Michal Wajdeczko Reviewed-by: Michal Wajdeczko Link: https://patch.msgid.link/20260626111520.487997-20-matthew.auld@intel.com --- drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 9 +++++ drivers/gpu/drm/xe/xe_gt_sriov_vf.c | 47 +++++++++++++++++++++++ drivers/gpu/drm/xe/xe_gt_sriov_vf.h | 1 + drivers/gpu/drm/xe/xe_gt_sriov_vf_types.h | 4 ++ drivers/gpu/drm/xe/xe_guc.c | 6 +++ drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 2 + drivers/gpu/drm/xe/xe_hw_engine.c | 32 ++++++++++++++- 7 files changed, 99 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index ec9c22dc21be..e50c586f6146 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -52,6 +52,12 @@ * _`GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE` : 0x3001 * Tells the driver whether scheduler groups are enabled or not. * Requires GuC ABI 1.26+ + * + * _`GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES` : 0x3003 + * Tells the driver the paging engine configuration. + * Paging engine logical instances are guaranteed to be dense starting at + * index 0. + * Requires GuC ABI 1.36+ */ #define GUC_KLV_GLOBAL_CFG_GMD_ID_KEY 0x3000u @@ -60,6 +66,9 @@ #define GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_KEY 0x3001u #define GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_LEN 1u +#define GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY 0x3003u +#define GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_LEN 1u + /** * DOC: GuC Self Config KLVs * diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_vf.c b/drivers/gpu/drm/xe/xe_gt_sriov_vf.c index 0cd9d77f3351..37899fcf5b22 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_vf.c +++ b/drivers/gpu/drm/xe/xe_gt_sriov_vf.c @@ -658,6 +658,33 @@ static int vf_cache_sched_groups_status(struct xe_gt *gt) return 0; } +static int vf_cache_num_paging_engines(struct xe_gt *gt) +{ + struct xe_guc *guc = >->uc.guc; + struct xe_uc_fw_version guc_version; + u32 value = 0; + int err; + + xe_gt_sriov_vf_guc_versions(gt, NULL, &guc_version); + + if (MAKE_GUC_VER_STRUCT(guc_version) < MAKE_GUC_VER(1, 36, 0)) + return 0; + + err = guc_action_query_single_klv32(guc, GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY, + &value); + if (unlikely(err)) { + xe_gt_sriov_err(gt, + "Failed to obtain the number of paging instances (%pe)\n", + ERR_PTR(err)); + return err; + } + + gt->sriov.vf.runtime.num_paging_engine_instances = value; + + xe_gt_sriov_dbg(gt, "num_paging_engines %u\n", value); + return 0; +} + /** * xe_gt_sriov_vf_query_config - Query SR-IOV config data over MMIO. * @gt: the &xe_gt @@ -694,6 +721,10 @@ int xe_gt_sriov_vf_query_config(struct xe_gt *gt) if (has_gmdid(xe)) vf_cache_gmdid(gt); + err = vf_cache_num_paging_engines(gt); + if (unlikely(err)) + return err; + return 0; } @@ -731,6 +762,22 @@ u16 xe_gt_sriov_vf_guc_ids(struct xe_gt *gt) return gt->sriov.vf.self_config.num_ctxs; } +/** + * xe_gt_sriov_vf_paging_engines - Return the number of paging engine instances + * @gt: the &xe_gt + * + * This function is for VF use only. + * + * Return: number of GuC paging engine instances configured by the PF. + */ +u32 xe_gt_sriov_vf_paging_engines(struct xe_gt *gt) +{ + xe_gt_assert(gt, IS_SRIOV_VF(gt_to_xe(gt))); + xe_gt_assert(gt, gt->sriov.vf.guc_version.major); + + return gt->sriov.vf.runtime.num_paging_engine_instances; +} + static int relay_action_handshake(struct xe_gt *gt, u32 *major, u32 *minor) { u32 request[VF2PF_HANDSHAKE_REQUEST_MSG_LEN] = { diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_vf.h b/drivers/gpu/drm/xe/xe_gt_sriov_vf.h index 79878f21b1da..d171a8242a34 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_vf.h +++ b/drivers/gpu/drm/xe/xe_gt_sriov_vf.h @@ -31,6 +31,7 @@ u32 xe_gt_sriov_vf_gmdid(struct xe_gt *gt); u16 xe_gt_sriov_vf_guc_ids(struct xe_gt *gt); u64 xe_gt_sriov_vf_lmem(struct xe_gt *gt); bool xe_gt_sriov_vf_sched_groups_enabled(struct xe_gt *gt); +u32 xe_gt_sriov_vf_paging_engines(struct xe_gt *gt); u32 xe_gt_sriov_vf_read32(struct xe_gt *gt, struct xe_reg reg); void xe_gt_sriov_vf_write32(struct xe_gt *gt, struct xe_reg reg, u32 val); diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_vf_types.h b/drivers/gpu/drm/xe/xe_gt_sriov_vf_types.h index 80562ffadb16..466f0abd9c28 100644 --- a/drivers/gpu/drm/xe/xe_gt_sriov_vf_types.h +++ b/drivers/gpu/drm/xe/xe_gt_sriov_vf_types.h @@ -29,6 +29,10 @@ struct xe_gt_sriov_vf_runtime { u32 gmdid; /** @uses_sched_groups: whether PF enabled sched groups or not. */ bool uses_sched_groups; + /** + * @num_paging_engine_instances: number of configured paging engines. + */ + u32 num_paging_engine_instances; /** @regs_size: size of runtime register array. */ u32 regs_size; /** @num_regs: number of runtime registers in the array. */ diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index bbf6dfb74532..8ccaf4b16832 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -1858,6 +1858,9 @@ bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc) bool xe_guc_has_paging_engine(struct xe_guc *guc) { + struct xe_gt *gt = guc_to_gt(guc); + struct xe_device *xe = gt_to_xe(gt); + /* * On newer platforms the GuC now has a dedicated engine class for the * special PAGING engine, which is the driver reserved BCS engine used @@ -1867,6 +1870,9 @@ bool xe_guc_has_paging_engine(struct xe_guc *guc) * need to respect. */ + if (IS_SRIOV_VF(xe)) + return xe_gt_sriov_vf_paging_engines(gt); + /* TODO: Have some way to query this from the GuC? */ return false; } diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index 97600edda837..be992b8da9a1 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -24,6 +24,8 @@ const char *xe_guc_klv_key_to_string(u16 key) /* GuC Global Config KLVs */ case GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_KEY: return "group_scheduling_available"; + case GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY: + return "num_paging_engine_instances"; /* VGT POLICY keys */ case GUC_KLV_VGT_POLICY_SCHED_IF_IDLE_KEY: return "sched_if_idle"; diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index d741d93601b2..0e03328a564d 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -658,7 +658,7 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe, return err; } -static void hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) +static int hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) { struct xe_device *xe = gt_to_xe(gt); unsigned int num_copy_engines = 0, num_paging_engines = 0; @@ -675,6 +675,29 @@ static void hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) if (num_copy_engines && xe->info.has_usm) num_paging_engines = 1; + if (IS_SRIOV_VF(xe)) { + u32 vf_num_paging_engines; + + /* + * PF could in theory reserve multiple paging engines, which + * internally the submission/scheduling backend can load balance + * from. Not something we currently expect, but we are at the + * mercy of the PF, so we just need try our best to mirror the + * paging configuration. + */ + vf_num_paging_engines = xe_gt_sriov_vf_paging_engines(gt); + if (vf_num_paging_engines) { + /* This should only be non-zero on NVL-S+ */ + if (xe_gt_WARN_ON(gt, xe->info.platform < XE_NOVALAKE_S)) + return -EINVAL; + + num_paging_engines = vf_num_paging_engines; + } + } + + if (xe_gt_WARN_ON(gt, num_paging_engines > num_copy_engines)) + return -EINVAL; + reserved_logical_bcs_start = num_copy_engines - num_paging_engines; /* FIXME: Doing a simple logical mapping that works for most hardware */ @@ -696,6 +719,8 @@ static void hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) } } } + + return 0; } static void read_media_fuses(struct xe_gt *gt) @@ -914,7 +939,10 @@ int xe_hw_engines_init(struct xe_gt *gt) return err; } - hw_engine_setup_logical_and_paging_mapping(gt); + err = hw_engine_setup_logical_and_paging_mapping(gt); + if (err) + return err; + err = xe_hw_engine_setup_groups(gt); if (err) return err; From 85b42488f7689a5a7b5af28022ec11bb2984bd74 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:30 +0100 Subject: [PATCH 22/81] drm/xe/hw_engine: document top-down paging requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We were doing this anyway, but going forward for paging engines, agreement is to always reserve BCS instances in top down fashion. This hopefully future proofs things for VFs, where in some low-level places it might only have the physical BCS instance from the hw pov. If we stick to a consistent mapping scheme, it should make it possible to determine if this is a special paging engine, or not. v2 (Daniele) - Give a concrete example, like with page fault descriptor Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Cc: Thomas Hellström Cc: Matthew Brost Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-21-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_hw_engine.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index 0e03328a564d..010499766fce 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -671,7 +671,6 @@ static int hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) if (hwe->class == XE_ENGINE_CLASS_COPY) num_copy_engines++; - /* We just reserve the highest BCS instance for USM */ if (num_copy_engines && xe->info.has_usm) num_paging_engines = 1; @@ -698,6 +697,18 @@ static int hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt) if (xe_gt_WARN_ON(gt, num_paging_engines > num_copy_engines)) return -EINVAL; + /* + * On PF, we just reserve the highest BCS instance for USM. + * + * Note: This is now a requirement going forward. The PF must ALWAYS + * reserve BCS instances in top-down order, that way the VF has a chance + * of discovering the physical BCS instance mappings for paging engines, + * in conjunction with vf_num_paging_engines. In some places we might + * only have the physical instance, and from hw pov there is no such + * thing as a paging engine. For example, the page fault descriptor, + * which comes directly from the hw, will use the physical engine + * instance. + */ reserved_logical_bcs_start = num_copy_engines - num_paging_engines; /* FIXME: Doing a simple logical mapping that works for most hardware */ From ed5a093c2fdd980dda2b2e14b1577a285a87e506 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Fri, 26 Jun 2026 12:15:31 +0100 Subject: [PATCH 23/81] drm/xe/guc: toggle paging engine support for NVL-S+ NVL-S with latest GuC should be the first platform combo to support the special GUC_PAGING_CLASS feature. v2: - Update with the final GuC version v3: - Split VF vs PF versioning. Which is recommendation from GuC side. Signed-off-by: Matthew Auld Cc: Daniele Ceraolo Spurio Reviewed-by: Daniele Ceraolo Spurio Link: https://patch.msgid.link/20260626111520.487997-22-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_guc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index 8ccaf4b16832..c1115dbd875c 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -1873,8 +1873,8 @@ bool xe_guc_has_paging_engine(struct xe_guc *guc) if (IS_SRIOV_VF(xe)) return xe_gt_sriov_vf_paging_engines(gt); - /* TODO: Have some way to query this from the GuC? */ - return false; + return xe->info.platform >= XE_NOVALAKE_S && + GUC_FIRMWARE_VER_AT_LEAST(guc, 70, 69, 0); } /** From 73c5a253068e8cdf975941a14f032e351730a396 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Thu, 9 Jul 2026 20:08:22 +0000 Subject: [PATCH 24/81] drm/xe/configfs: Add enable_multi_queue attribute Add a new configfs boolean attribute 'enable_multi_queue' that lets an administrator force-disable multi-queue support on a device before it binds to the driver. The attribute defaults to true (use the platform hardware capability as-is); writing 0 force-disables multi-queue. This is intended for debugging and for validating non-multi-queue code paths on hardware that would otherwise expose multi-queue. The override disables multi-queue at two levels: - UAPI: In alloc_primary_gt(), clear gt->info.multi_queue_engine_class_mask on the primary GT so that xe_gt_supports_multi_queue() returns false and attempts to create a multi-queue group via DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_GROUP are rejected. - GuC: In guc_ctl_feature_flags(), set the GUC_CTL_DISABLE_MULTI_QUEUE (BIT(24)) init-params bit on GuC firmware older than 70.66. On GuC firmware 70.66 and above, guc_waklv_init() emits the new GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE Feature KLV (0x5001) via the ADS WA/Feature KLV buffer instead. Feature KLVs share the WA KLV buffer. The attribute is rejected after the device has been bound, so it only takes effect during probe: # echo 0 > /sys/kernel/config/xe/0000:03:00.0/enable_multi_queue # echo 0000:03:00.0 > /sys/bus/pci/drivers/xe/bind v2: Add log for multi-queue disabled. (Niranjana) v3: Rename attribute to enable_multi_queue with default true. (Stuart && Niranjana) v4: rebase. Assisted-by: Claude:claude-opus-4.7 Cc: Niranjana Vishwanathapura Reviewed-by: Stuart Summers Link: https://patch.msgid.link/20260709200822.3257825-1-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 3 +- drivers/gpu/drm/xe/xe_configfs.c | 65 +++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_configfs.h | 2 + drivers/gpu/drm/xe/xe_guc.c | 8 ++++ drivers/gpu/drm/xe/xe_guc_ads.c | 14 ++++++ drivers/gpu/drm/xe/xe_guc_fwif.h | 1 + drivers/gpu/drm/xe/xe_pci.c | 5 +++ 7 files changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index e50c586f6146..b83201a1b6da 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -508,9 +508,10 @@ enum { #define GUC_KLV_VF_CFG_ENGINE_GROUP_PREEMPT_TIMEOUT_MIN_LEN 1u #define GUC_KLV_VF_CFG_ENGINE_GROUP_PREEMPT_TIMEOUT_MAX_LEN GUC_MAX_SCHED_GROUPS /* - * Workaround keys: + * Feature and Workaround keys: */ enum xe_guc_klv_ids { + GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE = 0x5001, GUC_WORKAROUND_KLV_BLOCK_INTERRUPTS_WHEN_MGSR_BLOCKED = 0x9002, GUC_WORKAROUND_KLV_DISABLE_PSMI_INTERRUPTS_AT_C6_ENTRY_RESTORE_AT_EXIT = 0x9004, GUC_WORKAROUND_KLV_ID_GAM_PFQ_SHADOW_TAIL_POLLING = 0x9005, diff --git a/drivers/gpu/drm/xe/xe_configfs.c b/drivers/gpu/drm/xe/xe_configfs.c index 32102600a148..052cce962161 100644 --- a/drivers/gpu/drm/xe/xe_configfs.c +++ b/drivers/gpu/drm/xe/xe_configfs.c @@ -237,6 +237,18 @@ * * This setting only takes effect when probing the device. * + * Enable multi-queue + * ------------------ + * + * Multi-queue support on the device is enabled by default where the + * hardware supports it. Writing 0 force-disables multi-queue support: + * multi-queue exec-queue group creation via ioctl is refused, and the + * GuC feature is disabled:: + * + * # echo 0 > /sys/kernel/config/xe/0000:03:00.0/enable_multi_queue + * + * This attribute can only be set before binding to the device. + * * Remove devices * ============== * @@ -262,6 +274,7 @@ struct xe_config_group_device { struct wa_bb ctx_restore_mid_bb[XE_ENGINE_CLASS_MAX]; bool survivability_mode; bool enable_psmi; + bool enable_multi_queue; struct { unsigned int max_vfs; bool admin_only_pf; @@ -281,6 +294,7 @@ static const struct xe_config_device device_defaults = { .engines_allowed = U64_MAX, .survivability_mode = false, .enable_psmi = false, + .enable_multi_queue = true, .sriov = { .max_vfs = XE_DEFAULT_MAX_VFS, .admin_only_pf = XE_DEFAULT_ADMIN_ONLY_PF, @@ -575,6 +589,33 @@ static ssize_t enable_psmi_store(struct config_item *item, const char *page, siz return len; } +static ssize_t enable_multi_queue_show(struct config_item *item, char *page) +{ + struct xe_config_device *dev = to_xe_config_device(item); + + return sprintf(page, "%d\n", dev->enable_multi_queue); +} + +static ssize_t enable_multi_queue_store(struct config_item *item, const char *page, + size_t len) +{ + struct xe_config_group_device *dev = to_xe_config_group_device(item); + bool val; + int ret; + + ret = kstrtobool(page, &val); + if (ret) + return ret; + + guard(mutex)(&dev->lock); + if (is_bound(dev)) + return -EBUSY; + + dev->config.enable_multi_queue = val; + + return len; +} + static bool wa_bb_read_advance(bool dereference, char **p, const char *append, size_t len, size_t *max_size) @@ -812,6 +853,7 @@ static ssize_t ctx_restore_post_bb_store(struct config_item *item, CONFIGFS_ATTR(, ctx_restore_mid_bb); CONFIGFS_ATTR(, ctx_restore_post_bb); +CONFIGFS_ATTR(, enable_multi_queue); CONFIGFS_ATTR(, enable_psmi); CONFIGFS_ATTR(, engines_allowed); CONFIGFS_ATTR(, gt_types_allowed); @@ -820,6 +862,7 @@ CONFIGFS_ATTR(, survivability_mode); static struct configfs_attribute *xe_config_device_attrs[] = { &attr_ctx_restore_mid_bb, &attr_ctx_restore_post_bb, + &attr_enable_multi_queue, &attr_enable_psmi, &attr_engines_allowed, &attr_gt_types_allowed, @@ -1097,6 +1140,7 @@ static void dump_custom_dev_config(struct pci_dev *pdev, PRI_CUSTOM_ATTR("%llx", gt_types_allowed); PRI_CUSTOM_ATTR("%llx", engines_allowed); + PRI_CUSTOM_ATTR("%d", enable_multi_queue); PRI_CUSTOM_ATTR("%d", enable_psmi); PRI_CUSTOM_ATTR("%d", survivability_mode); PRI_CUSTOM_ATTR("%u", sriov.admin_only_pf); @@ -1225,6 +1269,27 @@ bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev) return ret; } +/** + * xe_configfs_get_enable_multi_queue - get configfs enable_multi_queue setting + * @pdev: pci device + * + * Return: true if multi-queue is enabled for this device (the default), + * false if it has been force-disabled via configfs. + */ +bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev) +{ + struct xe_config_group_device *dev = find_xe_config_group_device(pdev); + bool ret; + + if (!dev) + return true; + + ret = dev->config.enable_multi_queue; + config_group_put(&dev->group); + + return ret; +} + /** * xe_configfs_get_ctx_restore_mid_bb - get configfs ctx_restore_mid_bb setting * @pdev: pci device diff --git a/drivers/gpu/drm/xe/xe_configfs.h b/drivers/gpu/drm/xe/xe_configfs.h index 07d62bf0c152..4fbbeafba473 100644 --- a/drivers/gpu/drm/xe/xe_configfs.h +++ b/drivers/gpu/drm/xe/xe_configfs.h @@ -23,6 +23,7 @@ bool xe_configfs_primary_gt_allowed(struct pci_dev *pdev); bool xe_configfs_media_gt_allowed(struct pci_dev *pdev); u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev); bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev); +bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev); u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, enum xe_engine_class class, const u32 **cs); @@ -42,6 +43,7 @@ static inline bool xe_configfs_primary_gt_allowed(struct pci_dev *pdev) { return static inline bool xe_configfs_media_gt_allowed(struct pci_dev *pdev) { return true; } static inline u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev) { return U64_MAX; } static inline bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev) { return false; } +static inline bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev) { return true; } static inline u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev, enum xe_engine_class class, const u32 **cs) { return 0; } diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c index c1115dbd875c..4286bd05c686 100644 --- a/drivers/gpu/drm/xe/xe_guc.c +++ b/drivers/gpu/drm/xe/xe_guc.c @@ -102,6 +102,14 @@ static u32 guc_ctl_feature_flags(struct xe_guc *guc) if (xe_device_is_l2_flush_optimized(xe) && xe_gt_is_media_type(guc_to_gt(guc))) flags |= GUC_CTL_ENABLE_L2FLUSH_OPT; + /* + * On GuC firmware 70.66 and above, the GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE + * Feature KLV is used instead. + */ + if (!xe_configfs_get_enable_multi_queue(to_pci_dev(xe->drm.dev)) && + !GUC_FIRMWARE_VER_AT_LEAST(guc, 70, 66)) + flags |= GUC_CTL_DISABLE_MULTI_QUEUE; + return flags; } diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index 5870194b06f6..f0ac00586d3a 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -16,6 +16,7 @@ #include "regs/xe_gt_regs.h" #include "regs/xe_guc_regs.h" #include "xe_bo.h" +#include "xe_configfs.h" #include "xe_gt.h" #include "xe_gt_ccs_mode.h" #include "xe_gt_mcr.h" @@ -402,6 +403,19 @@ static void guc_waklv_init(struct xe_guc_ads *ads) guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64); + /* + * On GuC firmware 70.66 and above, use the Feature KLV (shared with the + * WA KLV buffer); older firmware uses GUC_CTL_DISABLE_MULTI_QUEUE in + * the init params instead. + */ + if (!xe_configfs_get_enable_multi_queue(to_pci_dev(gt_to_xe(gt)->drm.dev)) && + GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 66)) { + u32 data = 1; + + guc_waklv_enable(ads, &data, 1, &offset, &remain, + GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE); + } + size = guc_ads_waklv_size(ads) - remain; if (!size) return; diff --git a/drivers/gpu/drm/xe/xe_guc_fwif.h b/drivers/gpu/drm/xe/xe_guc_fwif.h index 3fbda4798cff..971b850f2136 100644 --- a/drivers/gpu/drm/xe/xe_guc_fwif.h +++ b/drivers/gpu/drm/xe/xe_guc_fwif.h @@ -68,6 +68,7 @@ struct guc_update_exec_queue_policy { #define GUC_CTL_MAIN_GAMCTRL_QUEUES BIT(9) #define GUC_CTL_DISABLE_SCHEDULER BIT(14) #define GUC_CTL_ENABLE_L2FLUSH_OPT BIT(15) +#define GUC_CTL_DISABLE_MULTI_QUEUE BIT(24) #define GUC_CTL_DEBUG 3 #define GUC_LOG_VERBOSITY REG_GENMASK(1, 0) diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 11a69dffdce7..a56c137008a1 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -22,6 +22,7 @@ #include "xe_device.h" #include "xe_drv.h" #include "xe_gt.h" +#include "xe_gt_printk.h" #include "xe_gt_sriov_vf.h" #include "xe_guc.h" #include "xe_mmio.h" @@ -888,6 +889,10 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile, gt->info.has_uncorrectable_error_reporting = graphics_desc->has_uncorrectable_error_reporting; gt->info.multi_queue_engine_class_mask = graphics_desc->multi_queue_engine_class_mask; + if (!xe_configfs_get_enable_multi_queue(to_pci_dev(xe->drm.dev))) { + xe_gt_info(gt, "Multi-queue disabled via configfs\n"); + gt->info.multi_queue_engine_class_mask = 0; + } gt->info.engine_mask = graphics_desc->hw_engine_mask; gt->info.num_geometry_xecore_fuse_regs = graphics_desc->num_geometry_xecore_fuse_regs; gt->info.num_compute_xecore_fuse_regs = graphics_desc->num_compute_xecore_fuse_regs; From f3fbb5e5f7cf16e1de116df00c03f44ad20b0c07 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Wed, 8 Jul 2026 22:12:33 +0000 Subject: [PATCH 25/81] drm/xe: Use xe_tile_info() in alloc_primary_gt() and alloc_media_gt() alloc_primary_gt() and alloc_media_gt() both operate in the context of a specific tile, and the configfs-disabled messages describe a per-tile primary/media GT. Switch from xe_info(xe, ...) to xe_tile_info(tile, ...) so the log lines are prefixed with "Tile%u:", which disambiguates the message on multi-tile devices. Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260708221233.3251663-1-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/xe_pci.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index a56c137008a1..f6e18e61a5ac 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -37,6 +37,7 @@ #include "xe_step.h" #include "xe_survivability_mode.h" #include "xe_tile.h" +#include "xe_tile_printk.h" enum toggle_d3cold { D3COLD_DISABLE, @@ -875,7 +876,7 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile, struct xe_gt *gt; if (!xe_configfs_primary_gt_allowed(to_pci_dev(xe->drm.dev))) { - xe_info(xe, "Primary GT disabled via configfs\n"); + xe_tile_info(tile, "Primary GT disabled via configfs\n"); return NULL; } @@ -923,7 +924,7 @@ static struct xe_gt *alloc_media_gt(struct xe_tile *tile, struct xe_gt *gt; if (!xe_configfs_media_gt_allowed(to_pci_dev(xe->drm.dev))) { - xe_info(xe, "Media GT disabled via configfs\n"); + xe_tile_info(tile, "Media GT disabled via configfs\n"); return NULL; } From 2245d3d1a674d3f6ecf0a5967a9ff475fe3df9a2 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:03 +0200 Subject: [PATCH 26/81] drm/xe/guc: Allow to print single KLV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can decode and print all KLVs from the buffer, but it might be helpful also to allow printing just single already decoded KLV. Extract existing code into new function and make it public. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-2-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 54 ++++++++++++++++--------- drivers/gpu/drm/xe/xe_guc_klv_helpers.h | 1 + 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index be992b8da9a1..073b35957790 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -78,6 +78,39 @@ const char *xe_guc_klv_key_to_string(u16 key) } } +/** + * xe_guc_klv_print_one() - Print single `GuC KLV`_. + * @key: KLV key + * @len: KLV length (in u32 dwords) of the KLV @value + * @value: KLV value (as array of @len u32 dwords) + * @p: the &drm_printer + * + * The buffer may contain more than one KLV. + */ +void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer *p) +{ + const char *name = xe_guc_klv_key_to_string(key); + + switch (len) { + case 0: + drm_printf(p, "{ key %#06x : no value } # %s\n", key, name); + break; + case 1: + drm_printf(p, "{ key %#06x : 32b value %u } # %s\n", + key, value[0], name); + break; + case 2: + drm_printf(p, "{ key %#06x : 64b value %#llx } # %s\n", + key, make_u64(value[1], value[0]), name); + break; + default: + drm_printf(p, "{ key %#06x : %zu bytes %*ph } # %s\n", + key, len * sizeof(u32), (int)(len * sizeof(u32)), + value, name); + break; + } +} + /** * xe_guc_klv_print - Print content of the buffer with `GuC KLV`_. * @klvs: the buffer with KLVs @@ -103,26 +136,7 @@ void xe_guc_klv_print(const u32 *klvs, u32 num_dwords, struct drm_printer *p) return; } - switch (len) { - case 0: - drm_printf(p, "{ key %#06x : no value } # %s\n", - key, xe_guc_klv_key_to_string(key)); - break; - case 1: - drm_printf(p, "{ key %#06x : 32b value %u } # %s\n", - key, klvs[0], xe_guc_klv_key_to_string(key)); - break; - case 2: - drm_printf(p, "{ key %#06x : 64b value %#llx } # %s\n", - key, make_u64(klvs[1], klvs[0]), - xe_guc_klv_key_to_string(key)); - break; - default: - drm_printf(p, "{ key %#06x : %zu bytes %*ph } # %s\n", - key, len * sizeof(u32), (int)(len * sizeof(u32)), - klvs, xe_guc_klv_key_to_string(key)); - break; - } + xe_guc_klv_print_one(key, len, klvs, p); klvs += len; num_dwords -= len; diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h index c676d21c173b..c7b7e61c1250 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h @@ -13,6 +13,7 @@ struct drm_printer; const char *xe_guc_klv_key_to_string(u16 key); +void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer *p); void xe_guc_klv_print(const u32 *klvs, u32 num_dwords, struct drm_printer *p); int xe_guc_klv_count(const u32 *klvs, u32 num_dwords); From 961826fbcd79133033bc6a90bcf7cb1bb907a250 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:04 +0200 Subject: [PATCH 27/81] drm/xe/guc: Prepare to print group KLVs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some future KLVs will be encoded as a group of nested KLVs. Prepare our KLV printer function to handle such KLVs. List of known group keys will be updated later, for now just prepare it for testing. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-3-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index 073b35957790..d0663c226e87 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -4,6 +4,7 @@ */ #include +#include #include #include "abi/guc_klvs_abi.h" @@ -12,6 +13,12 @@ #define make_u64(hi, lo) ((u64)((u64)(u32)(hi) << 32 | (u32)(lo))) +static bool is_group_key(u16 key) +{ + KUNIT_STATIC_STUB_REDIRECT(is_group_key, key); + return false; +} + /** * xe_guc_klv_key_to_string - Convert KLV key into friendly name. * @key: the `GuC KLV`_ key @@ -91,6 +98,17 @@ void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer { const char *name = xe_guc_klv_key_to_string(key); + if (is_group_key(key)) { + struct drm_printer gp = drm_line_printer(p, name, 0); + + drm_printf(p, "{ key %#06x : group %u dwords } # %s\n", + key, len, name); + + /* print group recursively */ + xe_guc_klv_print(value, len, &gp); + return; + } + switch (len) { case 0: drm_printf(p, "{ key %#06x : no value } # %s\n", key, name); From ac958330974399ad2b9b5e0924333ecef5e78bed Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:05 +0200 Subject: [PATCH 28/81] drm/xe/guc: Add basic KLV encoding helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We plan to encode more data as KLVs. Add helpers for that. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-4-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 56 +++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.h | 3 ++ 2 files changed, 59 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index d0663c226e87..667c4c119541 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -189,3 +189,59 @@ int xe_guc_klv_count(const u32 *klvs, u32 num_dwords) return num_dwords ? -ENODATA : num_klvs; } + +static u16 to_num_dwords(size_t size) +{ + return round_up(size, sizeof(u32)) / sizeof(u32); +} + +/** + * xe_guc_klv_encode_u32() - Encode 32-bit value as KLV. + * @klvs: the buffer where to place KLV + * @avail: number of dwords (u32) available in the buffer + * @key: key to be used + * @value: value to be encoded + * + * Return: pointer to the buffer location past the encoded KLV or + * an ERR_PTR if there was no space to encode the KLV. + */ +u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value) +{ + u16 len = to_num_dwords(sizeof(u32)); + + if (IS_ERR(klvs)) + return klvs; + + if (avail < GUC_KLV_LEN_MIN + len) + return ERR_PTR(-ENOSPC); + + *klvs++ = PREP_GUC_KLV(key, len); + *klvs++ = value; + return klvs; +} + +/** + * xe_guc_klv_encode_u64() - Encode 64-bit value as KLV. + * @klvs: the buffer where to place KLV + * @avail: number of dwords (u32) available in the buffer + * @key: key to be used + * @value: value to be encoded + * + * Return: pointer to the buffer location past the encoded KLV or + * an ERR_PTR if there was no space to encode the KLV. + */ +u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value) +{ + u16 len = to_num_dwords(sizeof(u64)); + + if (IS_ERR(klvs)) + return klvs; + + if (avail < GUC_KLV_LEN_MIN + len) + return ERR_PTR(-ENOSPC); + + *klvs++ = PREP_GUC_KLV(key, len); + *klvs++ = lower_32_bits(value); + *klvs++ = upper_32_bits(value); + return klvs; +} diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h index c7b7e61c1250..713ef7f7b9f2 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h @@ -17,6 +17,9 @@ void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer void xe_guc_klv_print(const u32 *klvs, u32 num_dwords, struct drm_printer *p); int xe_guc_klv_count(const u32 *klvs, u32 num_dwords); +u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value); +u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value); + /** * PREP_GUC_KLV - Prepare KLV header value based on provided key and len. * @key: KLV key From a408338d10fc220ca3f48a7cac55fc12d3df3749 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:06 +0200 Subject: [PATCH 29/81] drm/xe/guc: Add string KLV encoding helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We also plan to encode a text data as KLV. Add helper for that too. Signed-off-by: Michal Wajdeczko Cc: Michał Winiarski Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-5-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 35 +++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.h | 1 + 2 files changed, 36 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index 667c4c119541..fa5590b23ba0 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -190,6 +190,11 @@ int xe_guc_klv_count(const u32 *klvs, u32 num_dwords) return num_dwords ? -ENODATA : num_klvs; } +static size_t to_num_bytes(u16 dwords) +{ + return dwords * sizeof(u32); +} + static u16 to_num_dwords(size_t size) { return round_up(size, sizeof(u32)) / sizeof(u32); @@ -245,3 +250,33 @@ u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value) *klvs++ = upper_32_bits(value); return klvs; } + +/** + * xe_guc_klv_encode_string() - Encode string as KLV. + * @klvs: the buffer where to place KLV + * @avail: number of dwords (u32) available in the buffer + * @key: key to be used + * @s: string to be encoded + * + * Return: pointer to the buffer location past the encoded KLV or + * an ERR_PTR if there was no space to encode the KLV. + */ +u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s) +{ + size_t longest = to_num_bytes(FIELD_MAX(GUC_KLV_0_LEN)); + size_t size = strnlen(s, longest) + 1; /* \0 */ + u16 len = to_num_dwords(size); + + if (IS_ERR(klvs)) + return klvs; + + if (size > longest) + return ERR_PTR(-E2BIG); + + if (avail < GUC_KLV_LEN_MIN + len) + return ERR_PTR(-ENOSPC); + + *klvs++ = PREP_GUC_KLV(key, len); + strscpy_pad((void *)klvs, s, to_num_bytes(len)); + return klvs + len; +} diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h index 713ef7f7b9f2..1c576456efc5 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h @@ -19,6 +19,7 @@ int xe_guc_klv_count(const u32 *klvs, u32 num_dwords); u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value); u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value); +u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s); /** * PREP_GUC_KLV - Prepare KLV header value based on provided key and len. From b088da030362ae3e4437f8eb900e007c99750566 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:07 +0200 Subject: [PATCH 30/81] drm/xe/guc: Add object KLV encoding helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We plan to encode larger objects as single KLV or set of KLVs. Add helper for that. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-6-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 39 +++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.h | 2 ++ 2 files changed, 41 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index fa5590b23ba0..54d977252431 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -280,3 +280,42 @@ u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s) strscpy_pad((void *)klvs, s, to_num_bytes(len)); return klvs + len; } + +/** + * xe_guc_klv_encode_object() - Encode object using custom encoder as single KLV. + * @klvs: the buffer where to place KLV + * @avail: number of dwords (u32) available in the buffer + * @key: key to be used + * @obj: opaque object pointer + * @encoder: function pointer to the custom encoder + * + * Return: pointer to the buffer location past the encoded KLV or + * an ERR_PTR if there was no space to encode the KLV. + */ +u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj, + u32 *(*encoder)(u32 *klvs, u32 avail, const void *obj)) +{ + u32 *end; + + if (IS_ERR(klvs)) + return klvs; + + if (avail < GUC_KLV_LEN_MIN) + return ERR_PTR(-ENOSPC); + + if (avail > GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN)) + avail = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN); + + end = encoder(klvs + GUC_KLV_LEN_MIN, avail - GUC_KLV_LEN_MIN, obj); + if (IS_ERR(end)) + return end; + + if (WARN_ON(end < klvs + GUC_KLV_LEN_MIN)) + return ERR_PTR(-EPIPE); + + if (WARN_ON(end > klvs + avail)) + return ERR_PTR(-EFBIG); + + *klvs = PREP_GUC_KLV(key, end - (klvs + GUC_KLV_LEN_MIN)); + return end; +} diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h index 1c576456efc5..855805f4463d 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h @@ -20,6 +20,8 @@ int xe_guc_klv_count(const u32 *klvs, u32 num_dwords); u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value); u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value); u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s); +u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj, + u32 *(*encoder)(u32 *klvs, u32 avail, const void *obj)); /** * PREP_GUC_KLV - Prepare KLV header value based on provided key and len. From 5598b9356cb6edb05cece95231eb1db3426bf59e Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:08 +0200 Subject: [PATCH 31/81] drm/xe/guc: Add KLV parsing helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have already introduced a helper to encode larger objects. Now add helper to parse the KLVs buffer. We will use it shortly. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-7-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 38 +++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.h | 3 ++ 2 files changed, 41 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index 54d977252431..7ad0b6f88f4c 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -319,3 +319,41 @@ u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj, *klvs = PREP_GUC_KLV(key, end - (klvs + GUC_KLV_LEN_MIN)); return end; } + +/** + * xe_guc_klv_parser() - Parse and decode stream of KLVs. + * @klvs: the buffer with KLVs + * @num_dwords: number of dwords (u32) available in the buffer + * @obj: opaque pointer to be used by the @decoder function + * @decoder: pointer to the decoder function + * + * Return: The sum of all results returned by the decoder or + * an -errno on decoder or buffer failure. + */ +int xe_guc_klv_parser(const u32 *klvs, u32 num_dwords, void *obj, + int (*decoder)(void *obj, u16 key, u16 len, const u32 *value)) +{ + int total = 0; + int ret; + + while (num_dwords >= GUC_KLV_LEN_MIN) { + u16 key = FIELD_GET(GUC_KLV_0_KEY, klvs[0]); + u16 len = FIELD_GET(GUC_KLV_0_LEN, klvs[0]); + + klvs += GUC_KLV_LEN_MIN; + num_dwords -= GUC_KLV_LEN_MIN; + + if (num_dwords < len) + return -ENODATA; + + ret = decoder(obj, key, len, klvs); + if (ret < 0) + return ret; + total += ret; + + klvs += len; + num_dwords -= len; + } + + return total; +} diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h index 855805f4463d..cf3b29adc9d6 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.h +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.h @@ -23,6 +23,9 @@ u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s); u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj, u32 *(*encoder)(u32 *klvs, u32 avail, const void *obj)); +int xe_guc_klv_parser(const u32 *klvs, u32 num_dwords, void *obj, + int (*decoder)(void *obj, u16 key, u16 len, const u32 *value)); + /** * PREP_GUC_KLV - Prepare KLV header value based on provided key and len. * @key: KLV key From 2963124ff9eb0d9be96dd38f43817e40ee32c31a Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Fri, 10 Jul 2026 19:25:34 +0200 Subject: [PATCH 32/81] drm/xe/guc: Formalize Reserved KLVs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have already started using few KLV keys from the 0xF000 range that, as we have agreed with the GuC team, will not be used in any GuC ABI actions. Add definitions for that reserved range and move our migration KLVs to new ABI header. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260710172534.7201-1-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/abi/guc_klvs_abi.h | 17 +++++++++++++ drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h | 27 +++++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 14 +++++++++++ drivers/gpu/drm/xe/xe_sriov_packet.c | 7 ++---- 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h diff --git a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h index b83201a1b6da..685c4ef17b73 100644 --- a/drivers/gpu/drm/xe/abi/guc_klvs_abi.h +++ b/drivers/gpu/drm/xe/abi/guc_klvs_abi.h @@ -22,6 +22,7 @@ * | | | - `GuC Scheduling Policies KLVs`_ | * | | | - `GuC VGT Policy KLVs`_ | * | | | - `GuC VF Configuration KLVs`_ | + * | | | - `GuC Reserved KLVs`_ | * | | | | * | +-------+--------------------------------------------------------------+ * | | 15:0 | **LEN** - length of VALUE (in 32bit dwords) | @@ -526,4 +527,20 @@ enum xe_guc_klv_ids { GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64 = 0x9010, }; +/** + * DOC: GuC Reserved KLVs + * + * Range of `GuC KLV`_ keys reserved for internal use by the GuC that will + * never be part of the offcial GuC ABI and can be reused by the drivers. + * + * Currently this range includes 1024 keys starting from: + * + * _`GUC_KLV_RESERVED_RANGE_START` : 0xF000 + * + * See `Xe Driver KLVs`_ for the KLVs that the Xe driver is currently using. + */ + +#define GUC_KLV_RESERVED_RANGE_START 0xf000u +#define GUC_KLV_RESERVED_RANGE_LEN 1024u + #endif diff --git a/drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h b/drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h new file mode 100644 index 000000000000..3b557e56892a --- /dev/null +++ b/drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2026 Intel Corporation + */ + +#ifndef _ABI_XE_DRIVER_KLVS_ABI_H +#define _ABI_XE_DRIVER_KLVS_ABI_H + +#include "abi/guc_klvs_abi.h" + +/** + * DOC: Xe Driver KLVs + * + * The Xe driver uses the following keys from the `GuC Reserved KLVs`_ range: + * + * _`MIGRATION_KLV_DEVICE_DEVID_KEY` : + * PCI device ID of the migrated VF. + * _`MIGRATION_KLV_DEVICE_REVID_KEY` : + * PCI device revision ID of the migrated VF. + */ + +#define MIGRATION_KLV_DEVICE_DEVID_KEY 0xf001u +#define MIGRATION_KLV_DEVICE_DEVID_LEN 1u +#define MIGRATION_KLV_DEVICE_REVID_KEY 0xf002u +#define MIGRATION_KLV_DEVICE_REVID_LEN 1u + +#endif diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index 7ad0b6f88f4c..cf6cd862e2d2 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -8,6 +8,7 @@ #include #include "abi/guc_klvs_abi.h" +#include "abi/xe_driver_klvs_abi.h" #include "xe_guc_klv_helpers.h" #include "xe_guc_klv_thresholds_set.h" @@ -19,6 +20,11 @@ static bool is_group_key(u16 key) return false; } +static bool is_reserved_key(u16 key) +{ + return in_range(key, GUC_KLV_RESERVED_RANGE_START, GUC_KLV_RESERVED_RANGE_LEN); +} + /** * xe_guc_klv_key_to_string - Convert KLV key into friendly name. * @key: the `GuC KLV`_ key @@ -80,7 +86,15 @@ const char *xe_guc_klv_key_to_string(u16 key) MAKE_XE_GUC_KLV_THRESHOLDS_SET(define_threshold_key_to_string_case) #undef define_threshold_key_to_string_case + /* driver KLVs */ + case MIGRATION_KLV_DEVICE_DEVID_KEY: + return "migration_devid"; + case MIGRATION_KLV_DEVICE_REVID_KEY: + return "migration_revid"; + default: + if (is_reserved_key(key)) + return "(reserved)"; return "(unknown)"; } } diff --git a/drivers/gpu/drm/xe/xe_sriov_packet.c b/drivers/gpu/drm/xe/xe_sriov_packet.c index 2ae9eff2a7c0..e581e8e6c1d1 100644 --- a/drivers/gpu/drm/xe/xe_sriov_packet.c +++ b/drivers/gpu/drm/xe/xe_sriov_packet.c @@ -3,6 +3,8 @@ * Copyright © 2025 Intel Corporation */ +#include "abi/xe_driver_klvs_abi.h" + #include "xe_bo.h" #include "xe_device.h" #include "xe_guc_klv_helpers.h" @@ -352,11 +354,6 @@ ssize_t xe_sriov_packet_write_single(struct xe_device *xe, unsigned int vfid, return copied; } -#define MIGRATION_KLV_DEVICE_DEVID_KEY 0xf001u -#define MIGRATION_KLV_DEVICE_DEVID_LEN 1u -#define MIGRATION_KLV_DEVICE_REVID_KEY 0xf002u -#define MIGRATION_KLV_DEVICE_REVID_LEN 1u - #define MIGRATION_DESCRIPTOR_DWORDS (GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_DEVID_LEN + \ GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_REVID_LEN) static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid) From a2514d30282208f831a2e4576482fece8c455741 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:10 +0200 Subject: [PATCH 33/81] drm/xe/tests: Add GuC KLV helpers basic tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will be making more extensive use of GuC KLV helpers. Add simple tests to ensure the helpers are working as expected. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-9-michal.wajdeczko@intel.com --- .../drm/xe/tests/xe_guc_klv_helpers_kunit.c | 99 +++++++++++++++++++ drivers/gpu/drm/xe/xe_guc_klv_helpers.c | 4 + 2 files changed, 103 insertions(+) create mode 100644 drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c diff --git a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c new file mode 100644 index 000000000000..f87189ef13d5 --- /dev/null +++ b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 AND MIT +/* + * Copyright © 2026 Intel Corporation + */ + +#include +#include + +#define TEST_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3de) +#define TEST_PAD 0xdeadbeef + +static void test_count(struct kunit *test) +{ + u32 value = 0x12345678; + u16 key = TEST_KEY; + u32 klvs[] = { + PREP_GUC_KLV(key + 0, 0), + PREP_GUC_KLV(key + 1, 1), value, + PREP_GUC_KLV(key + 2, 2), value, value, + PREP_GUC_KLV(key + 3, 0), + 0, /* padding */ + }; + + KUNIT_EXPECT_EQ(test, 0, xe_guc_klv_count(klvs, 0)); + KUNIT_EXPECT_EQ(test, 1, xe_guc_klv_count(klvs, 1)); + KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 2)); + KUNIT_EXPECT_EQ(test, 2, xe_guc_klv_count(klvs, 3)); + KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 4)); + KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 5)); + KUNIT_EXPECT_EQ(test, 3, xe_guc_klv_count(klvs, 6)); + KUNIT_EXPECT_EQ(test, 4, xe_guc_klv_count(klvs, 7)); + + /* 0 is treated as reserved KLV { KEY=0, LEN=0 } */ + KUNIT_EXPECT_EQ(test, 5, xe_guc_klv_count(klvs, 8)); +} + +static void test_encode_u32(struct kunit *test) +{ + u32 *fail = ERR_PTR(-ENOMEM); + u32 value = 0x12345678; + u16 key = TEST_KEY; + u32 klvs[16]; + + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u32(klvs, 0, key, value)); + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u32(klvs, 1, key, value)); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe_guc_klv_encode_u32(klvs, 2, key, value)); + KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, 1)); + KUNIT_EXPECT_EQ(test, klvs[1], value); + KUNIT_EXPECT_EQ(test, klvs[2], TEST_PAD); + KUNIT_EXPECT_PTR_EQ(test, &klvs[2], xe_guc_klv_encode_u32(klvs, 2, key, value)); + KUNIT_EXPECT_PTR_EQ(test, + xe_guc_klv_encode_u32(klvs, 2, key, value), + xe_guc_klv_encode_u32(klvs, ARRAY_SIZE(klvs), key, value)); + + KUNIT_ASSERT_PTR_EQ(test, fail, xe_guc_klv_encode_u32(fail, ARRAY_SIZE(klvs), key, value)); +} + +static void test_encode_u64(struct kunit *test) +{ + u64 value = 0x123456789abcdef0; + u32 *fail = ERR_PTR(-ENOMEM); + u16 key = TEST_KEY; + u32 klvs[16]; + + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 0, key, value)); + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 1, key, value)); + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 2, key, value)); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe_guc_klv_encode_u64(klvs, 3, key, value)); + KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, 2)); + KUNIT_EXPECT_EQ(test, klvs[1], lower_32_bits(value)); + KUNIT_EXPECT_EQ(test, klvs[2], upper_32_bits(value)); + KUNIT_EXPECT_EQ(test, klvs[3], TEST_PAD); + KUNIT_EXPECT_PTR_EQ(test, &klvs[3], xe_guc_klv_encode_u64(klvs, 3, key, value)); + KUNIT_EXPECT_PTR_EQ(test, + xe_guc_klv_encode_u64(klvs, 3, key, value), + xe_guc_klv_encode_u64(klvs, ARRAY_SIZE(klvs), key, value)); + + KUNIT_ASSERT_PTR_EQ(test, fail, xe_guc_klv_encode_u64(fail, ARRAY_SIZE(klvs), key, value)); +} + +static struct kunit_case guc_klv_helpers_test_cases[] = { + KUNIT_CASE(test_count), + KUNIT_CASE(test_encode_u32), + KUNIT_CASE(test_encode_u64), + {} +}; + +static struct kunit_suite guc_klv_helpers_suite = { + .name = "guc_klv_helpers", + .test_cases = guc_klv_helpers_test_cases, +}; + +kunit_test_suite(guc_klv_helpers_suite); diff --git a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c index cf6cd862e2d2..dc8612d761e3 100644 --- a/drivers/gpu/drm/xe/xe_guc_klv_helpers.c +++ b/drivers/gpu/drm/xe/xe_guc_klv_helpers.c @@ -371,3 +371,7 @@ int xe_guc_klv_parser(const u32 *klvs, u32 num_dwords, void *obj, return total; } + +#if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST) +#include "tests/xe_guc_klv_helpers_kunit.c" +#endif From cca4cd467495da29e056b7bba7a6bb22ab3fd488 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 20:07:55 +0200 Subject: [PATCH 34/81] drm/xe/tests: Add string encoding helper test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before we start using string to KLV encoding helper, add a simple test to make sure it works as expected. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski #v1 Link: https://patch.msgid.link/20260708180755.2684-1-michal.wajdeczko@intel.com --- .../drm/xe/tests/xe_guc_klv_helpers_kunit.c | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c index f87189ef13d5..cb4b182d88d0 100644 --- a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c +++ b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c @@ -84,10 +84,94 @@ static void test_encode_u64(struct kunit *test) KUNIT_ASSERT_PTR_EQ(test, fail, xe_guc_klv_encode_u64(fail, ARRAY_SIZE(klvs), key, value)); } +static u32 str_klv_size(const char *string) +{ + return GUC_KLV_LEN_MIN + to_num_dwords(strlen(string) + 1); +} + +static void test_encode_string(struct kunit *test) +{ + size_t longest_str = to_num_bytes(FIELD_MAX(GUC_KLV_0_LEN)) - 1; + u32 avail = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN) + 1; + const char *string = "abcdefghijklmnopqrstvwxyz"; + u16 key = TEST_KEY; + u32 *klvs; + u32 *next; + char *buf; + u32 n; + + klvs = kunit_kcalloc(test, avail, sizeof(u32), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, klvs); + + buf = kunit_kzalloc(test, longest_str + 2, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + /* empty string, no space, must fail */ + for (n = 0; n < str_klv_size(""); n++) { + klvs[0] = TEST_PAD; + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_string(klvs, n, key, "")); + KUNIT_EXPECT_EQ(test, klvs[0], TEST_PAD); + } + + /* empty string, must pass */ + KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(""), + xe_guc_klv_encode_string(klvs, str_klv_size(""), key, "")); + KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(""), + xe_guc_klv_encode_string(klvs, avail, key, "")); + + /* demo string, no space, must fail */ + for (n = 0; n < str_klv_size(string); n++) { + klvs[0] = TEST_PAD; + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_string(klvs, n, key, string)); + KUNIT_EXPECT_EQ(test, klvs[0], TEST_PAD); + } + + /* different string len, must pass */ + for (n = 0; n <= strlen(string); n++) { + strscpy(buf, string, n + 1); + kunit_info(test, "%u: '%s'\n", n, buf); + KUNIT_ASSERT_EQ(test, n, strlen(buf)); + memset32(klvs, TEST_PAD, avail); + + next = xe_guc_klv_encode_string(klvs, str_klv_size(buf), key, buf); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, next); + KUNIT_EXPECT_PTR_EQ(test, next, klvs + str_klv_size(buf)); + KUNIT_EXPECT_STREQ_MSG(test, buf, (char *)(klvs + GUC_KLV_LEN_MIN), "n=%u", n); + kunit_info(test, "%u: %*ph\n", n, (int)to_num_bytes(next - klvs), klvs); + KUNIT_EXPECT_NE(test, *(next - 1), TEST_PAD); + KUNIT_ASSERT_EQ(test, *next, TEST_PAD); + + /* bigger buf doesn't matter */ + KUNIT_EXPECT_PTR_EQ(test, + xe_guc_klv_encode_string(klvs, str_klv_size(buf), key, buf), + xe_guc_klv_encode_string(klvs, avail, key, buf)); + } + + /* don't crash if already failed */ + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EROFS), + xe_guc_klv_encode_string(ERR_PTR(-EROFS), avail, key, "")); + + /* too long string, must fail */ + memset(buf, 'X', longest_str + 1); + buf[longest_str + 1] = '\0'; + KUNIT_EXPECT_LT(test, longest_str, strlen(buf)); + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-E2BIG), + xe_guc_klv_encode_string(klvs, avail, key, buf)); + + /* longest string, should pass */ + buf[longest_str] = '\0'; + KUNIT_EXPECT_EQ(test, longest_str, strlen(buf)); + KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(buf), + xe_guc_klv_encode_string(klvs, avail, key, buf)); +} + static struct kunit_case guc_klv_helpers_test_cases[] = { KUNIT_CASE(test_count), KUNIT_CASE(test_encode_u32), KUNIT_CASE(test_encode_u64), + KUNIT_CASE(test_encode_string), {} }; From e05f612ce70f7199de0366c533f66be6417faebd Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Fri, 10 Jul 2026 21:59:45 +0200 Subject: [PATCH 35/81] drm/xe/tests: Add object encoding helper test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will soon be encoding complex objects as KLVs using our helper function. Add few simple tests to make sure this helper function works as expected. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260710195945.7316-1-michal.wajdeczko@intel.com --- .../drm/xe/tests/xe_guc_klv_helpers_kunit.c | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c index cb4b182d88d0..33d7a52f6bf8 100644 --- a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c +++ b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c @@ -7,6 +7,7 @@ #include #define TEST_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3de) +#define TEST_GROUP_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3f0) #define TEST_PAD 0xdeadbeef static void test_count(struct kunit *test) @@ -167,11 +168,215 @@ static void test_encode_string(struct kunit *test) xe_guc_klv_encode_string(klvs, avail, key, buf)); } +struct some_object { + u32 value1; + u64 value2; +} __packed; + +static u32 *obj_raw_encoder(u32 *klvs, u32 avail, const void *arg) +{ + const struct some_object *obj = arg; + size_t sz = sizeof(*obj); + u32 dwords = to_num_dwords(sz); + + if (IS_ERR(klvs)) + return klvs; + if (dwords > avail) + return ERR_PTR(-ENOSPC); + memcpy(klvs, obj, sz); + return klvs + dwords; +} + +static u32 *obj_klv_encoder(u32 *klvs, u32 avail, const void *arg) +{ + const struct some_object *obj = arg; + u32 *end = klvs + avail; + + klvs = xe_guc_klv_encode_u32(klvs, end - klvs, TEST_KEY + 1, obj->value1); + klvs = xe_guc_klv_encode_u64(klvs, end - klvs, TEST_KEY + 2, obj->value2); + return klvs; +} + +static u32 *obj_nested_encoder(u32 *klvs, u32 avail, const void *arg) +{ + u32 *end = klvs + avail; + + klvs = xe_guc_klv_encode_object(klvs, end - klvs, TEST_GROUP_KEY + 1, + arg, obj_klv_encoder); + klvs = xe_guc_klv_encode_object(klvs, end - klvs, TEST_GROUP_KEY + 2, + arg, obj_klv_encoder); + return klvs; +} + +static void test_encode_object_raw(struct kunit *test) +{ + const struct some_object obj = { + .value1 = 0xdead1234, + .value2 = 0xdead87654321dead, + }; + u32 payload = to_num_dwords(sizeof(obj)); + u32 *err = ERR_PTR(-ENOSPC); + u16 key = TEST_KEY; + u32 klvs[16]; + u32 n; + + /* too small, must fail */ + for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) { + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_object(klvs, n, key, &obj, + obj_raw_encoder), + "buf size=%u dwords", n); + } + + /* must pass */ + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, + xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload, + key, &obj, obj_raw_encoder)); + KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload)); + KUNIT_EXPECT_MEMEQ(test, &klvs[1], &obj, sizeof(obj)); + + /* already failed, must fail */ + KUNIT_ASSERT_PTR_EQ(test, err, + xe_guc_klv_encode_object(err, ARRAY_SIZE(klvs), key, + &obj, obj_raw_encoder)); +} + +static void test_encode_object_klv(struct kunit *test) +{ + const struct some_object obj = { + .value1 = 0xdead1234, + .value2 = 0xdead87654321dead, + }; + u16 key = TEST_GROUP_KEY; + u32 payload = 0; + u32 klvs[16]; + u32 n; + + payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value1)); + payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value2)); + + /* too small, must fail */ + for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) { + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_object(klvs, n, key, &obj, + obj_klv_encoder), + "buf size=%u dwords", n); + } + + /* must pass */ + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, + xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload, + key, &obj, obj_klv_encoder)); + KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload)); + KUNIT_EXPECT_EQ(test, klvs[1], PREP_GUC_KLV(TEST_KEY + 1, 1)); + KUNIT_EXPECT_EQ(test, klvs[2], obj.value1); + KUNIT_EXPECT_EQ(test, klvs[3], PREP_GUC_KLV(TEST_KEY + 2, 2)); + KUNIT_EXPECT_EQ(test, klvs[4], lower_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[5], upper_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[6], TEST_PAD); +} + +static void test_encode_object_nested(struct kunit *test) +{ + const struct some_object obj = { + .value1 = 0xdead1234, + .value2 = 0xdead87654321dead, + }; + u16 key = TEST_GROUP_KEY; + u32 payload = 0; + u32 klvs[16]; + u32 n; + + payload += GUC_KLV_LEN_MIN; + payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value1)); + payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value2)); + payload *= 2; + + /* too small, must fail */ + for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) { + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_object(klvs, n, key, &obj, + obj_nested_encoder), + "buf size=%u dwords", n); + } + + /* must pass */ + memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, + xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload, + key, &obj, obj_nested_encoder)); + KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload)); + KUNIT_EXPECT_EQ(test, klvs[1], PREP_GUC_KLV(TEST_GROUP_KEY + 1, 5)); + KUNIT_EXPECT_EQ(test, klvs[2], PREP_GUC_KLV(TEST_KEY + 1, 1)); + KUNIT_EXPECT_EQ(test, klvs[3], obj.value1); + KUNIT_EXPECT_EQ(test, klvs[4], PREP_GUC_KLV(TEST_KEY + 2, 2)); + KUNIT_EXPECT_EQ(test, klvs[5], lower_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[6], upper_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[7], PREP_GUC_KLV(TEST_GROUP_KEY + 2, 5)); + KUNIT_EXPECT_EQ(test, klvs[8], PREP_GUC_KLV(TEST_KEY + 1, 1)); + KUNIT_EXPECT_EQ(test, klvs[9], obj.value1); + KUNIT_EXPECT_EQ(test, klvs[10], PREP_GUC_KLV(TEST_KEY + 2, 2)); + KUNIT_EXPECT_EQ(test, klvs[11], lower_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[12], upper_32_bits(obj.value2)); + KUNIT_EXPECT_EQ(test, klvs[13], TEST_PAD); +} + +static u32 *obj_echo_encoder(u32 *klvs, u32 avail, const void *arg) +{ + return ERR_CAST(arg); +} + +static void test_encode_object_basic(struct kunit *test) +{ + u32 longest = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN); + u32 avail = GUC_KLV_LEN_MIN + longest; + u16 key = TEST_GROUP_KEY; + u32 *klvs; + + klvs = kunit_kcalloc(test, avail, sizeof(u32), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, klvs); + + /* smallest */ + KUNIT_EXPECT_PTR_EQ(test, klvs + GUC_KLV_LEN_MIN, + xe_guc_klv_encode_object(klvs, avail, key, + klvs + GUC_KLV_LEN_MIN, + obj_echo_encoder)); + /* largest */ + KUNIT_EXPECT_PTR_EQ(test, klvs + longest, + xe_guc_klv_encode_object(klvs, avail, key, + klvs + longest, + obj_echo_encoder)); + /* already failed */ + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EROFS), + xe_guc_klv_encode_object(ERR_PTR(-EROFS), avail, key, + klvs + GUC_KLV_LEN_MIN, + obj_echo_encoder)); + /* encoding error */ + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EUCLEAN), + xe_guc_klv_encode_object(klvs, avail, key, + ERR_PTR(-EUCLEAN), + obj_echo_encoder)); + /* no space */ + KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), + xe_guc_klv_encode_object(klvs, 0, key, + klvs + GUC_KLV_LEN_MIN, + obj_echo_encoder)); +} + static struct kunit_case guc_klv_helpers_test_cases[] = { KUNIT_CASE(test_count), KUNIT_CASE(test_encode_u32), KUNIT_CASE(test_encode_u64), KUNIT_CASE(test_encode_string), + KUNIT_CASE(test_encode_object_raw), + KUNIT_CASE(test_encode_object_klv), + KUNIT_CASE(test_encode_object_nested), + KUNIT_CASE(test_encode_object_basic), {} }; From af98b8f067a93b76d47c70b1d22b7f89f004ee47 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Sat, 11 Jul 2026 09:36:08 +0200 Subject: [PATCH 36/81] drm/xe/tests: Add GuC KLV printer test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For completeness, add a simple test to exercise the KLV printer to make sure it doesn't crash at least. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260711073608.7829-1-michal.wajdeczko@intel.com --- .../drm/xe/tests/xe_guc_klv_helpers_kunit.c | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c index 33d7a52f6bf8..82869363ab7e 100644 --- a/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c +++ b/drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c @@ -10,6 +10,11 @@ #define TEST_GROUP_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3f0) #define TEST_PAD 0xdeadbeef +static bool fake_is_group_key(u16 key) +{ + return is_reserved_key(key) && key >= TEST_GROUP_KEY; +} + static void test_count(struct kunit *test) { u32 value = 0x12345678; @@ -368,6 +373,41 @@ static void test_encode_object_basic(struct kunit *test) obj_echo_encoder)); } +static void __drm_printfn_kunit(struct drm_printer *p, struct va_format *vaf) +{ + struct kunit *test = p->arg; + + kunit_info(test, "%pV", vaf); +} + +static struct drm_printer drm_kunit_printer(void) +{ + struct drm_printer p = { + .printfn = __drm_printfn_kunit, + .arg = kunit_get_current_test(), + }; + return p; +} + +static void test_print(struct kunit *test) +{ + struct drm_printer p = drm_kunit_printer(); + u32 zeros[] = { 0, 0, 0, /* padding */ }; + u32 klvs[] = { + PREP_GUC_KLV(GUC_KLV_OPT_IN_FEATURE_EXT_CAT_ERR_TYPE_KEY, 0), + PREP_GUC_KLV(GUC_KLV_VF_CFG_NUM_CONTEXTS_KEY, 1), 1234, + PREP_GUC_KLV(GUC_KLV_VF_CFG_GGTT_SIZE_KEY, 2), 0x4000, 0x0123, + PREP_GUC_KLV(TEST_KEY, 3), 1, 2, 3, + PREP_GUC_KLV(TEST_GROUP_KEY, 5), + PREP_GUC_KLV(TEST_KEY + 1, 1), 1, + PREP_GUC_KLV(TEST_KEY + 2, 2), 1, 2, + }; + + kunit_activate_static_stub(test, is_group_key, fake_is_group_key); + xe_guc_klv_print(zeros, ARRAY_SIZE(zeros), &p); + xe_guc_klv_print(klvs, ARRAY_SIZE(klvs), &p); +} + static struct kunit_case guc_klv_helpers_test_cases[] = { KUNIT_CASE(test_count), KUNIT_CASE(test_encode_u32), @@ -377,6 +417,7 @@ static struct kunit_case guc_klv_helpers_test_cases[] = { KUNIT_CASE(test_encode_object_klv), KUNIT_CASE(test_encode_object_nested), KUNIT_CASE(test_encode_object_basic), + KUNIT_CASE(test_print), {} }; From 6d599b33d86e569269aa784f19c8c8f64c0a8b54 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 20:09:21 +0200 Subject: [PATCH 37/81] drm/xe/tests: Add migration packet test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of our migration data packet (descriptor) is based on the KLV encoding. Add a simple descriptor initialization test, as we plan to use new KLV helper functions there. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260708180921.2715-1-michal.wajdeczko@intel.com --- .../gpu/drm/xe/tests/xe_sriov_packet_kunit.c | 89 +++++++++++++++++++ drivers/gpu/drm/xe/xe_sriov_packet.c | 4 + 2 files changed, 93 insertions(+) create mode 100644 drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c diff --git a/drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c b/drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c new file mode 100644 index 000000000000..c2720b461dee --- /dev/null +++ b/drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 AND MIT +/* + * Copyright © 2026 Intel Corporation + */ + +#include +#include + +#include "xe_device.h" +#include "xe_guc_klv_helpers.h" +#include "xe_kunit_helpers.h" +#include "xe_pci_test.h" + +#define TEST_VF VFID(1) + +static int sriov_packet_test_init(struct kunit *test) +{ + struct xe_pci_fake_data fake = { + .sriov_mode = XE_SRIOV_MODE_PF, + .platform = XE_PANTHERLAKE, /* we need MEMIRQ */ + .subplatform = XE_SUBPLATFORM_NONE, + .graphics_verx100 = 3000, + .media_verx100 = 3000, + }; + struct xe_device *xe; + + test->priv = &fake; + xe_kunit_helper_xe_device_test_init(test); + xe = test->priv; + + /* pretend we can support at least VF1 */ + xe->sriov.pf.device_total_vfs = 1; + xe->sriov.pf.driver_max_vfs = 1; + + KUNIT_ASSERT_EQ(test, 0, xe_sriov_init(xe)); + KUNIT_ASSERT_TRUE(test, xe_sriov_pf_migration_supported(xe)); + + return 0; +} + +static void test_descriptor_init(struct kunit *test) +{ + struct xe_device *xe = test->priv; + struct xe_sriov_packet **desc; + + /* note: with lock held we should avoid KUNIT_ASSERT() */ + guard(mutex)(pf_migration_mutex(xe, TEST_VF)); + + KUNIT_EXPECT_EQ(test, 0, pf_descriptor_init(xe, TEST_VF)); + desc = pf_pick_descriptor(xe, TEST_VF); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, *desc); + if (!*desc) + return; + KUNIT_EXPECT_NE(test, (*desc)->hdr.version, 0); + KUNIT_EXPECT_EQ(test, (*desc)->hdr.version, XE_SRIOV_PACKET_SUPPORTED_VERSION); + KUNIT_EXPECT_EQ(test, (*desc)->hdr.type, XE_SRIOV_PACKET_TYPE_DESCRIPTOR); + KUNIT_EXPECT_NE(test, (*desc)->hdr.size, 0); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, (*desc)->vaddr); + if (!(*desc)->vaddr) + return; + KUNIT_EXPECT_EQ(test, 0, xe_sriov_packet_process_descriptor(xe, TEST_VF, *desc)); + + switch ((*desc)->hdr.version) { + case 1: + /* v1 is KLV based */ + KUNIT_EXPECT_TRUE(test, IS_ALIGNED((*desc)->hdr.size, sizeof(u32))); + /* v1 has at least DEVID and REVID KLVs */ + KUNIT_EXPECT_LE(test, 2, + xe_guc_klv_count((*desc)->vaddr, + (*desc)->hdr.size / sizeof(u32))); + break; + default: + kunit_mark_skipped(test, "no test code for version %u\n", (*desc)->hdr.version); + return; + } +} + +static struct kunit_case sriov_packet_test_cases[] = { + KUNIT_CASE(test_descriptor_init), + {} +}; + +static struct kunit_suite sriov_packet_suite = { + .name = "sriov_packet", + .test_cases = sriov_packet_test_cases, + .init = sriov_packet_test_init, +}; + +kunit_test_suite(sriov_packet_suite); diff --git a/drivers/gpu/drm/xe/xe_sriov_packet.c b/drivers/gpu/drm/xe/xe_sriov_packet.c index e581e8e6c1d1..558a3697d639 100644 --- a/drivers/gpu/drm/xe/xe_sriov_packet.c +++ b/drivers/gpu/drm/xe/xe_sriov_packet.c @@ -516,3 +516,7 @@ int xe_sriov_packet_save_init(struct xe_device *xe, unsigned int vfid) return 0; } + +#if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST) +#include "tests/xe_sriov_packet_kunit.c" +#endif From 068388c0cd225654e7369907670d4e77b8dd3744 Mon Sep 17 00:00:00 2001 From: Michal Wajdeczko Date: Wed, 8 Jul 2026 00:08:15 +0200 Subject: [PATCH 38/81] drm/xe/pf: Handle migration descriptor using KLV helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we plan to add more KLVs to the migration descriptor packet, to simplify such extensions and avoid coding errors, start using our KLV helpers for packet preparing and parsing. Signed-off-by: Michal Wajdeczko Reviewed-by: Michał Winiarski Link: https://patch.msgid.link/20260707220816.677-14-michal.wajdeczko@intel.com --- drivers/gpu/drm/xe/xe_sriov_packet.c | 104 ++++++++++++++------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sriov_packet.c b/drivers/gpu/drm/xe/xe_sriov_packet.c index 558a3697d639..e9ae9c9744ea 100644 --- a/drivers/gpu/drm/xe/xe_sriov_packet.c +++ b/drivers/gpu/drm/xe/xe_sriov_packet.c @@ -360,8 +360,7 @@ static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid) { struct xe_sriov_packet **desc = pf_pick_descriptor(xe, vfid); struct xe_sriov_packet *data; - unsigned int len = 0; - u32 *klvs; + u32 *klvs, *end; int ret; data = xe_sriov_packet_alloc(xe); @@ -376,20 +375,55 @@ static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid) } klvs = data->vaddr; - klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_DEVID_KEY, - MIGRATION_KLV_DEVICE_DEVID_LEN); - klvs[len++] = xe->info.devid; - klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_REVID_KEY, - MIGRATION_KLV_DEVICE_REVID_LEN); - klvs[len++] = xe->info.revid; + end = klvs + MIGRATION_DESCRIPTOR_DWORDS; - xe_assert(xe, len == MIGRATION_DESCRIPTOR_DWORDS); + klvs = xe_guc_klv_encode_u32(klvs, end - klvs, + MIGRATION_KLV_DEVICE_DEVID_KEY, + xe->info.devid); + klvs = xe_guc_klv_encode_u32(klvs, end - klvs, + MIGRATION_KLV_DEVICE_REVID_KEY, + xe->info.revid); + xe_assert(xe, !IS_ERR(klvs)); + xe_assert(xe, klvs == end); *desc = data; return 0; } +static int descriptor_decoder(void *arg, u16 key, u16 len, const u32 *value) +{ + struct xe_device *xe = arg; + + xe_sriov_dbg_verbose(xe, "found KLV %#x %s\n", key, xe_guc_klv_key_to_string(key)); + + switch (key) { + case MIGRATION_KLV_DEVICE_DEVID_KEY: + if (*value != xe->info.devid) { + xe_sriov_warn(xe, "Aborting migration, devid mismatch %#06x!=%#06x\n", + *value, xe->info.devid); + return -ENODEV; + } + break; + case MIGRATION_KLV_DEVICE_REVID_KEY: + if (*value != xe->info.revid) { + xe_sriov_warn(xe, "Aborting migration, revid mismatch %#06x!=%#06x\n", + *value, xe->info.revid); + return -ENODEV; + } + break; + default: + if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) { + struct drm_printer p = xe_dbg_printer(xe); + + xe_sriov_dbg(xe, "unexpected KLV %#x in descriptor!\n", key); + xe_guc_klv_print_one(key, len, value, &p); + } + return 0; + } + return 1; +} + /** * xe_sriov_packet_process_descriptor() - Process migration data descriptor packet. * @xe: the &xe_device @@ -406,6 +440,7 @@ int xe_sriov_packet_process_descriptor(struct xe_device *xe, unsigned int vfid, { u32 num_dwords = data->hdr.size / sizeof(u32); u32 *klvs = data->vaddr; + int ret; xe_assert(xe, data->hdr.type == XE_SRIOV_PACKET_TYPE_DESCRIPTOR); @@ -415,47 +450,18 @@ int xe_sriov_packet_process_descriptor(struct xe_device *xe, unsigned int vfid, return -EINVAL; } - while (num_dwords >= GUC_KLV_LEN_MIN) { - u32 key = FIELD_GET(GUC_KLV_0_KEY, klvs[0]); - u32 len = FIELD_GET(GUC_KLV_0_LEN, klvs[0]); + ret = xe_guc_klv_count(klvs, num_dwords); + if (ret < 0) { + xe_sriov_warn(xe, "Aborting migration, corrupted descriptor KLVs (%pe)\n", + ERR_PTR(ret)); + return ret; + } - klvs += GUC_KLV_LEN_MIN; - num_dwords -= GUC_KLV_LEN_MIN; - - if (len > num_dwords) { - xe_sriov_warn(xe, "Aborting migration, truncated KLV %#x, len %u\n", - key, len); - return -EINVAL; - } - - switch (key) { - case MIGRATION_KLV_DEVICE_DEVID_KEY: - if (*klvs != xe->info.devid) { - xe_sriov_warn(xe, - "Aborting migration, devid mismatch %#06x!=%#06x\n", - *klvs, xe->info.devid); - return -ENODEV; - } - break; - case MIGRATION_KLV_DEVICE_REVID_KEY: - if (*klvs != xe->info.revid) { - xe_sriov_warn(xe, - "Aborting migration, revid mismatch %#06x!=%#06x\n", - *klvs, xe->info.revid); - return -ENODEV; - } - break; - default: - xe_sriov_dbg(xe, - "Skipping unknown migration KLV %#x, len=%u\n", - key, len); - print_hex_dump_bytes("desc: ", DUMP_PREFIX_OFFSET, klvs, - min(SZ_64, len * sizeof(u32))); - break; - } - - klvs += len; - num_dwords -= len; + ret = xe_guc_klv_parser(klvs, num_dwords, xe, descriptor_decoder); + if (ret < 0) { + xe_sriov_warn(xe, "Aborting migration, descriptor parsing failed (%pe)\n", + ERR_PTR(ret)); + return ret; } return 0; From 65f40fa5022bcdb90d3a39e4948adf2aa16732a3 Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 13 Jul 2026 13:23:16 -0700 Subject: [PATCH 39/81] drm/xe: only resume exec queues that were actually suspended A consumer-issued suspend() can fail (e.g. the queue is killed, banned or wedged), leaving the queue un-suspended. The consumer must then not issue the matching resume(): resuming a queue that was never suspended is incorrect. Add an lr.suspended flag to struct xe_exec_queue that records whether a consumer suspend() succeeded and a matching resume() is still owed. Set it on a successful suspend() in the preempt-fence path, clear it on resume(), and only resume queues that have it set. In resume_and_reinstall_preempt_fences() also skip queues that have since been reset/killed/banned/wedged: such a queue's suspend may not have completed (suspend_pending can still be set, e.g. a preempt fence signalled with -ENOENT without waiting), so resuming it would trip the !suspend_pending assert in the backend. Leave it marked suspended and let teardown resolve its state. A queue is only ever suspended by a single consumer at a time (preempt-fence mode and hw engine group fault mode are mutually exclusive), so a single flag is sufficient. Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-9-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_exec_queue_types.h | 12 ++++++++++++ drivers/gpu/drm/xe/xe_preempt_fence.c | 7 +++++++ drivers/gpu/drm/xe/xe_vm.c | 18 +++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h index d27ce24daae5..dbb2ee8eb5de 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h @@ -200,6 +200,18 @@ struct xe_exec_queue { u32 seqno; /** @lr.link: link into VM's list of exec queues */ struct list_head link; + /** + * @lr.suspended: Tracks whether the consumer-issued suspend() + * succeeded and a matching resume() is still owed. suspend() can + * fail (e.g. killed/banned/wedged), leaving the queue + * un-suspended, so consumers must only resume() queues that were + * actually suspended. Set by the suspend caller on success and + * cleared by the resume caller. A queue is only ever suspended by + * a single consumer at a time (preempt-fence mode and hw engine + * group fault mode are mutually exclusive), so a single flag is + * sufficient. + */ + bool suspended; } lr; #define XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT 0 diff --git a/drivers/gpu/drm/xe/xe_preempt_fence.c b/drivers/gpu/drm/xe/xe_preempt_fence.c index d6427b473ddd..4aa570fe745d 100644 --- a/drivers/gpu/drm/xe/xe_preempt_fence.c +++ b/drivers/gpu/drm/xe/xe_preempt_fence.c @@ -74,6 +74,13 @@ static bool preempt_fence_enable_signaling(struct dma_fence *fence) struct xe_exec_queue *q = pfence->q; pfence->error = q->ops->suspend(q); + /* + * Record a successful suspend so the rebind worker only resumes queues + * that were actually suspended; a failed suspend() leaves the queue + * un-suspended and must not be paired with a resume(). + */ + if (!pfence->error) + WRITE_ONCE(q->lr.suspended, true); queue_work(q->vm->xe->preempt_fence_wq, &pfence->preempt_work); return true; } diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 080c2fff0e95..23f4a9fb9a49 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -206,7 +206,23 @@ static void resume_and_reinstall_preempt_fences(struct xe_vm *vm, xe_vm_assert_held(vm); list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) { - q->ops->resume(q); + /* + * Only resume queues whose suspend() actually succeeded. A + * failed suspend() (e.g. killed/banned/wedged) leaves the queue + * un-suspended, so it must not be resumed. + * + * Also skip queues that have since been reset/killed/banned/ + * wedged: their suspend may not have completed (suspend_pending + * can still be set, e.g. a preempt fence signalled with -ENOENT + * without waiting), so resuming would trip the !suspend_pending + * assert in the backend. Such queues are being torn down anyway, + * so leave them marked suspended and let teardown resolve their + * state. + */ + if (READ_ONCE(q->lr.suspended) && !q->ops->reset_status(q)) { + WRITE_ONCE(q->lr.suspended, false); + q->ops->resume(q); + } drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, q->lr.pfence, DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP); From dad2af2da9ead8a390153c0f042feacd5056888b Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 13 Jul 2026 13:23:17 -0700 Subject: [PATCH 40/81] drm/xe/guc: ban exec queue on suspend timeout Harden guc_exec_queue_suspend_wait(): - In multi-queue mode the primary owns the group's GuC scheduling context, so wait on the primary's suspend to complete. - On timeout, ban the queue and trigger cleanup rather than leaving it suspended forever. Clearing suspend_pending via __suspend_fence_signal() lets a subsequent resume() proceed without tripping the !suspend_pending assert. A timeout on the primary wedges the whole group, so ban and tear down the entire group in the multi-queue case. The ban/cleanup is factored into guc_exec_queue_suspend_timeout_ban(). Add a note that on a signal (-ERESTARTSYS) the queue is not banned and the suspend is not confirmed complete, so callers must not resume() without re-confirming. Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-10-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_guc_submit.c | 53 +++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index cec3bbf3a10e..3ece51451f86 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -2202,12 +2202,53 @@ static int guc_exec_queue_suspend(struct xe_exec_queue *q) return 0; } +static void guc_exec_queue_suspend_timeout_ban(struct xe_exec_queue *q) +{ + struct xe_guc *guc = exec_queue_to_guc(q); + + xe_gt_warn(guc_to_gt(guc), + "Suspend fence, guc_id=%d, failed to respond, banning queue", + q->guc->id); + /* + * The GuC failed to respond to the suspend within the timeout. This is + * not recoverable for this context, so ban it and tear it down via + * cleanup rather than leave it suspended forever. __suspend_fence_signal + * clears suspend_pending and wakes any waiter. + * + * @q is the primary here; it owns the group's GuC context, so a failure + * to suspend it wedges the whole group. Ban and tear down the entire + * group in the multi-queue case. + */ + if (xe_exec_queue_is_multi_queue(q)) { + set_exec_queue_group_banned(q); + __suspend_fence_signal(q); + xe_guc_exec_queue_group_trigger_cleanup(q); + } else { + set_exec_queue_banned(q); + __suspend_fence_signal(q); + xe_guc_exec_queue_trigger_cleanup(q); + } +} + static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); int ret; + /* + * In multi-queue mode the primary owns the GuC scheduling context for + * the whole group, so wait on the primary's suspend to complete. All + * group members share the same GuC/device, so guc, xe and timeout above + * are computed from @q directly. + * + * A secondary's suspend is short-circuited (no GuC round-trip) and, as + * its SUSPEND message precedes the primary's on the shared FIFO + * submit_wq, completes before the primary's. So waiting on the primary + * is sufficient. + */ + q = xe_exec_queue_multi_queue_primary(q); + /* * Likely don't need to check exec_queue_killed() as we clear * suspend_pending upon kill but to be paranoid but races in which @@ -2230,10 +2271,7 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) return -EAGAIN; if (!ret) { - xe_gt_warn(guc_to_gt(guc), - "Suspend fence, guc_id=%d, failed to respond", - q->guc->id); - /* XXX: Trigger GT reset? */ + guc_exec_queue_suspend_timeout_ban(q); return -ETIME; } else if (IS_SRIOV_VF(xe) && !WAIT_COND) { /* Corner case on RESFIX DONE where vf_recovery() changes */ @@ -2242,6 +2280,13 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) #undef WAIT_COND + /* + * ret < 0 (-ERESTARTSYS): the interruptible wait was aborted by a + * signal. The queue is not banned - the failure is in the waiter, not + * the queue. The suspend is not confirmed complete, so suspend_pending + * may still be set; callers must not resume() on this error without + * re-confirming the suspend. + */ return ret < 0 ? ret : 0; } From 412c885692cade356a394835934d3f36ae8f6aca Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 13 Jul 2026 13:23:18 -0700 Subject: [PATCH 41/81] drm/xe/guc: add uninterruptible suspend wait for cross-process cleanup Add a suspend_wait_blocking() exec queue op: an uninterruptible variant of suspend_wait() for callers that must complete a suspend on behalf of a queue that may belong to a different process than the calling task (e.g. cleanup/undo paths). An interruptible suspend_wait() returns -ERESTARTSYS when the calling task is signalled, which would leave the other process's queue suspended forever - a cross-process DoS. The blocking variant waits uninterruptibly and, on a genuine GuC timeout, bans and tears down the queue like suspend_wait() (shared via guc_exec_queue_suspend_timeout_ban()). It deliberately does not handle VF recovery since a blocking caller cannot retry. Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-11-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_exec_queue_types.h | 9 ++++++ drivers/gpu/drm/xe/xe_execlist.c | 1 + drivers/gpu/drm/xe/xe_guc_submit.c | 39 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h index dbb2ee8eb5de..0aa0823cbdaa 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h @@ -322,6 +322,15 @@ struct xe_exec_queue_ops { * avoidance mechanism. */ int (*suspend_wait)(struct xe_exec_queue *q); + /** + * @suspend_wait_blocking: Like @suspend_wait, but waits uninterruptibly + * (does not abort on the calling task's signals). For cleanup/undo paths + * that must complete a suspend on behalf of a queue that may belong to a + * different process than the caller: a signal to the caller must not + * abandon the wait, which would leave the other process's queue + * suspended forever (cross-process DoS). A timeout bans like suspend_wait. + */ + int (*suspend_wait_blocking)(struct xe_exec_queue *q); /** * @resume: Resume exec queue execution, exec queue must be in a suspended * state and dma fence returned from most recent suspend call must be diff --git a/drivers/gpu/drm/xe/xe_execlist.c b/drivers/gpu/drm/xe/xe_execlist.c index 6b86b4f9cc1c..cc33ae80e8cf 100644 --- a/drivers/gpu/drm/xe/xe_execlist.c +++ b/drivers/gpu/drm/xe/xe_execlist.c @@ -468,6 +468,7 @@ static const struct xe_exec_queue_ops execlist_exec_queue_ops = { .set_preempt_timeout = execlist_exec_queue_set_preempt_timeout, .suspend = execlist_exec_queue_suspend, .suspend_wait = execlist_exec_queue_suspend_wait, + .suspend_wait_blocking = execlist_exec_queue_suspend_wait, .resume = execlist_exec_queue_resume, .reset_status = execlist_exec_queue_reset_status, }; diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 3ece51451f86..de9c131fc62d 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -2290,6 +2290,44 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) return ret < 0 ? ret : 0; } +static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q) +{ + struct xe_guc *guc = exec_queue_to_guc(q); + struct xe_device *xe = guc_to_xe(guc); + int ret; + + /* + * Uninterruptible variant of guc_exec_queue_suspend_wait() for callers + * that must complete the wait on behalf of a queue possibly owned by a + * different process (e.g. cleanup/undo paths). An interruptible wait + * could return -ERESTARTSYS if the calling task is signalled, leaving + * that queue suspended forever (cross-process DoS). + * + * A timeout is still a real per-queue fault, so it bans and cleans up + * like suspend_wait(). VF recovery is deliberately not handled (no + * -EAGAIN) since a blocking caller cannot retry. + */ + q = xe_exec_queue_multi_queue_primary(q); + +#define WAIT_COND \ + (!READ_ONCE(q->guc->suspend_pending) || exec_queue_killed(q) || \ + xe_guc_read_stopped(guc)) + + if (IS_SRIOV_VF(xe)) + ret = wait_event_timeout(guc->ct.wq, WAIT_COND, HZ * 5); + else + ret = wait_event_timeout(q->guc->suspend_wait, WAIT_COND, HZ * 5); + +#undef WAIT_COND + + if (!ret) { + guc_exec_queue_suspend_timeout_ban(q); + return -ETIME; + } + + return 0; +} + static void guc_exec_queue_resume(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched = &q->guc->sched; @@ -2329,6 +2367,7 @@ static const struct xe_exec_queue_ops guc_exec_queue_ops = { .set_multi_queue_priority = guc_exec_queue_set_multi_queue_priority, .suspend = guc_exec_queue_suspend, .suspend_wait = guc_exec_queue_suspend_wait, + .suspend_wait_blocking = guc_exec_queue_suspend_wait_blocking, .resume = guc_exec_queue_resume, .reset_status = guc_exec_queue_reset_status, }; From c373eaaada2fe8eb7f8b7b0cefa997309264965d Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 13 Jul 2026 13:23:19 -0700 Subject: [PATCH 42/81] drm/xe/hw_engine_group: propagate suspend failures during mode switch The hw engine group fault-mode switch suspends all faulting LR queues but ignored the suspend()/suspend_wait() return value. A suspend() can fail (e.g. the queue is killed/banned/wedged), leaving the queue un-suspended, so silently continuing could later resume a queue that was never suspended. Propagate the failure instead: in xe_hw_engine_group_add_exec_queue() bail out if suspend() fails, and in xe_hw_engine_group_suspend_faulting_lr_jobs() undo the partial suspend via a new err_resume path that resumes the sibling queues already suspended in this call. Record per-queue success with lr.suspended so only queues that were actually suspended are waited on and resumed, and skip the cleanup resume() when suspend_wait() failed or the queue was reset/killed/banned/wedged (its suspend may not have completed, so resuming would trip the !suspend_pending assert in the resume path; teardown resolves its state instead). Gate the group resume worker (hw_engine_group_resume_lr_jobs_func()) on lr.suspended for the same reason, so it only resumes queues that were actually suspended. v2: Don't let a dying queue block the switch (Matt Brost) Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-12-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_hw_engine_group.c | 85 ++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_hw_engine_group.c b/drivers/gpu/drm/xe/xe_hw_engine_group.c index 02cf32ae5aa9..0804b426b6f9 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine_group.c +++ b/drivers/gpu/drm/xe/xe_hw_engine_group.c @@ -34,6 +34,15 @@ hw_engine_group_resume_lr_jobs_func(struct work_struct *w) if (!xe_vm_in_fault_mode(q->vm)) continue; + /* + * Only resume queues that were actually suspended. A queue whose + * suspend() failed (e.g. killed/banned/wedged) was never + * suspended, so it must not be resumed. + */ + if (!READ_ONCE(q->lr.suspended)) + continue; + + WRITE_ONCE(q->lr.suspended, false); q->ops->resume(q); } @@ -140,7 +149,18 @@ int xe_hw_engine_group_add_exec_queue(struct xe_hw_engine_group *group, struct x return err; if (xe_vm_in_fault_mode(q->vm) && group->cur_mode == EXEC_MODE_DMA_FENCE) { - q->ops->suspend(q); + /* + * suspend() can fail (e.g. killed/banned/wedged), leaving the + * queue un-suspended. Propagate the failure so the queue is not + * added; on failure nothing was suspended, so there is nothing to + * undo. Only record the queue as suspended (and later resume it) + * once suspend() has succeeded. + */ + err = q->ops->suspend(q); + if (err) + goto err_suspend; + + WRITE_ONCE(q->lr.suspended, true); err = q->ops->suspend_wait(q); if (err) goto err_suspend; @@ -216,8 +236,22 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group return -EAGAIN; xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_COUNT, 1); + /* + * suspend() only fails when the queue is killed/banned/wedged. + * Such a queue is being torn down (its removal from HW is handled + * by the kill/ban teardown), so it is not a live fault-mode + * context the mode switch must preempt. Skip it rather than + * failing the switch, otherwise one dying sibling would block a + * dma-fence submission on the healthy queues in the group. Only + * queues recorded as suspended below are later waited on and + * resumed. + */ + err = q->ops->suspend(q); + if (err) + continue; + + WRITE_ONCE(q->lr.suspended, true); need_resume = true; - q->ops->suspend(q); gt = q->gt; } @@ -225,9 +259,13 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group if (!xe_vm_in_fault_mode(q->vm)) continue; + /* Only wait on queues that were actually suspended above. */ + if (!READ_ONCE(q->lr.suspended)) + continue; + err = q->ops->suspend_wait(q); if (err) - return err; + goto err_resume; } if (gt) { @@ -240,6 +278,47 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group xe_hw_engine_group_resume_faulting_lr_jobs(group); return 0; + +err_resume: + /* + * A suspend_wait() failed partway through the mode switch. Resume the + * sibling queues that were already suspended in this call so they are + * not left suspended forever. + * + * resume() requires the suspend to have completed (suspend_pending + * cleared) or it trips the !suspend_pending assert. So skip the resume + * when either: + * - suspend_wait_blocking() fails: on a GuC timeout it bans the queue + * and triggers cleanup, so the queue is being torn down; or + * - reset_status() is true: the queue was reset/killed/banned/wedged. + * suspend_wait() can return success in this case via its killed/ + * stopped wait condition while suspend_pending is still set, and the + * queue is being torn down anyway, so its state is resolved by + * teardown rather than by a resume here. + * In either case leave the queue marked suspended. + * + * Use the *blocking* (uninterruptible) wait here: the queues resumed on + * this path may belong to a different process than the one that + * triggered the mode switch. An interruptible suspend_wait() would + * return -ERESTARTSYS if the triggering task is signalled, skip the + * resume, and leave the other process's queue suspended forever + * (cross-process DoS). + */ + list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) { + if (!xe_vm_in_fault_mode(q->vm)) + continue; + + if (!READ_ONCE(q->lr.suspended)) + continue; + + if (q->ops->suspend_wait_blocking(q) || q->ops->reset_status(q)) + continue; + + WRITE_ONCE(q->lr.suspended, false); + q->ops->resume(q); + } + + return err; } /** From 3c316b09072c28e0702f980e6565929b532507c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Hellstr=C3=B6m?= Date: Mon, 13 Jul 2026 13:23:20 -0700 Subject: [PATCH 43/81] drm/xe/guc: Add suspend refcount to exec queue ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the lr.suspended flag a consumer already pairs its own suspend() and resume() correctly, and no current path issues overlapping suspends on the same queue. Add a reference count to the exec queue suspend operations, as a small self-contained building block for callers that can genuinely overlap. A queue stays suspended as long as any caller holds a suspend and only resumes once the last caller releases it, so each caller pairs its own suspend/resume without needing to know about the others. This is what the upcoming multi-queue support needs, where queues in a group share a primary and may be suspended concurrently. Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Co-authored-by: Niranjana Vishwanathapura Signed-off-by: Thomas Hellström Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-13-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_guc_exec_queue_types.h | 7 +++++ drivers/gpu/drm/xe/xe_guc_submit.c | 30 ++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h index e5e53b421f29..1207d51cf770 100644 --- a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h @@ -49,6 +49,13 @@ struct xe_guc_exec_queue { wait_queue_head_t suspend_wait; /** @suspend_pending: a suspend of the exec_queue is pending */ bool suspend_pending; + /** + * @suspend_count: Reference count of active suspend requests. The + * exec_queue remains suspended while this is non-zero, allowing + * multiple concurrent callers to independently hold a suspend without + * prematurely re-enabling the queue. Protected by @sched.msg_lock. + */ + int suspend_count; /** * @needs_cleanup: Needs a cleanup message during VF post migration * recovery. diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index de9c131fc62d..5a30f17f5c28 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -2188,15 +2188,21 @@ static int guc_exec_queue_set_multi_queue_priority(struct xe_exec_queue *q, static int guc_exec_queue_suspend(struct xe_exec_queue *q) { - struct xe_gpu_scheduler *sched = &q->guc->sched; - struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_SUSPEND; + struct xe_guc_exec_queue *ge = q->guc; + struct xe_gpu_scheduler *sched = &ge->sched; + struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_SUSPEND; if (exec_queue_killed_or_banned_or_wedged(q)) return -EINVAL; xe_sched_msg_lock(sched); - if (guc_exec_queue_try_add_msg(q, msg, SUSPEND)) - q->guc->suspend_pending = true; + if (++ge->suspend_count == 1) { + bool added = guc_exec_queue_try_add_msg(q, msg, SUSPEND); + + /* slot must be free at 0->1 */ + xe_gt_assert(guc_to_gt(exec_queue_to_guc(q)), added); + ge->suspend_pending = true; + } xe_sched_msg_unlock(sched); return 0; @@ -2330,14 +2336,20 @@ static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q) static void guc_exec_queue_resume(struct xe_exec_queue *q) { - struct xe_gpu_scheduler *sched = &q->guc->sched; - struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_RESUME; + struct xe_guc_exec_queue *ge = q->guc; + struct xe_gpu_scheduler *sched = &ge->sched; + struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME; struct xe_guc *guc = exec_queue_to_guc(q); - xe_gt_assert(guc_to_gt(guc), !q->guc->suspend_pending); - xe_sched_msg_lock(sched); - guc_exec_queue_try_add_msg(q, msg, RESUME); + xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending); + xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0); + if (--ge->suspend_count == 0) { + bool added = guc_exec_queue_try_add_msg(q, msg, RESUME); + + /* slot must be free at 1->0 */ + xe_gt_assert(guc_to_gt(guc), added); + } xe_sched_msg_unlock(sched); } From 5d5a729cc9780338d21450423d1f84fa705bae62 Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Mon, 13 Jul 2026 13:23:21 -0700 Subject: [PATCH 44/81] drm/xe/multi_queue: preempt primary on queue group suspend In a multi-queue group only the group's primary queue interfaces with GuC for scheduling; suspend/resume of secondary queues is handled internally and is not forwarded to GuC. As a result, suspending a secondary queue alone (e.g. on its preempt fence signalling) does not disable the primary's GuC context, so in-flight GPU work of the group is not actually preempted. Make a secondary queue suspend/resume like any other queue, driven by its own xe_guc_exec_queue.suspend_count, and additionally forward the suspend/resume to the primary so the GPU is actually preempted. The forward is gated on the secondary's own 0->1 / 1->0 suspend_count transition, so each group member contributes exactly one suspend reference to the primary: the primary keeps its GuC context disabled until every member that suspended it has resumed, including across the resume-all-queues-each-rebind-cycle behavior. group->suspend_lock makes the secondary transition and the primary forward atomic, and a member leaving while still suspended (queue teardown) drops its reference on the primary. v2: Add comment about suspend_wait() in drop_suspend() v3: Do not suspend a secondary if primary is killed, wait for primay suspend to complete before drop_suspend() Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260713202317.2187787-14-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_exec_queue.c | 1 + drivers/gpu/drm/xe/xe_exec_queue_types.h | 6 + drivers/gpu/drm/xe/xe_guc_submit.c | 180 ++++++++++++++++++++--- 3 files changed, 168 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index cfd2a4e6d4c7..f4b297a19218 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -839,6 +839,7 @@ static int xe_exec_queue_group_init(struct xe_device *xe, struct xe_exec_queue * group->primary = q; group->cgp_bo = bo; INIT_LIST_HEAD(&group->list); + spin_lock_init(&group->suspend_lock); xa_init_flags(&group->xa, XA_FLAGS_ALLOC1); mutex_init(&group->list_lock); q->multi_queue.group = group; diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h index 0aa0823cbdaa..53b6c0bf4849 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h @@ -62,6 +62,12 @@ struct xe_exec_queue_group { struct list_head list; /** @list_lock: Secondary queue list lock */ struct mutex list_lock; + /** + * @suspend_lock: Makes a secondary's suspend/resume and its forwarding + * to the primary atomic. Nested outside of the queue's message lock + * (@xe_guc_exec_queue.sched.msg_lock). + */ + spinlock_t suspend_lock; /** @sync_pending: CGP_SYNC_DONE g2h response pending */ bool sync_pending; /** @banned: Group banned */ diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 5a30f17f5c28..c70c77141a74 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -1704,11 +1704,36 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job) return DRM_GPU_SCHED_STAT_NO_HANG; } +static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q); +static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q); + static void guc_exec_queue_fini(struct xe_exec_queue *q) { struct xe_guc_exec_queue *ge = q->guc; struct xe_guc *guc = exec_queue_to_guc(q); + /* + * A secondary can leave the group while still preempt suspended (e.g. + * xe_vm_remove_compute_exec_queue() forces its preempt fence to signal, + * which suspends it). It holds one forwarded suspend reference on the + * primary, so drop it and resume the primary if it was the last member + * that had it suspended. Primaries forward to nobody, so they don't need + * this. + * + * First make sure the primary's forwarded suspend has completed. If the + * secondary was killed/reset before its preempt fence worker ran, that + * worker skips suspend_wait() (see preempt_fence_work_func()), leaving + * the primary's suspend possibly in flight. drop_suspend() runs under a + * spinlock and cannot wait, so drain it here with the uninterruptible + * blocking wait; otherwise resuming the primary in drop_suspend() could + * trip the !suspend_pending assert. + */ + if (xe_exec_queue_is_multi_queue_secondary(q)) { + if (READ_ONCE(q->guc->suspend_count)) + guc_exec_queue_suspend_wait_blocking(q); + guc_exec_queue_multi_queue_drop_suspend(q); + } + if (xe_exec_queue_is_multi_queue_secondary(q)) { struct xe_exec_queue_group *group = q->multi_queue.group; @@ -2186,17 +2211,22 @@ static int guc_exec_queue_set_multi_queue_priority(struct xe_exec_queue *q, return 0; } -static int guc_exec_queue_suspend(struct xe_exec_queue *q) +/* + * Core suspend: take a suspend reference on @q and, on the first reference, + * disable its GuC context so the GPU is actually preempted. Caller must have + * ensured @q is not killed/banned/wedged. Returns true if this was the first + * suspend reference (the 0->1 transition). + */ +static bool __guc_exec_queue_suspend(struct xe_exec_queue *q) { struct xe_guc_exec_queue *ge = q->guc; struct xe_gpu_scheduler *sched = &ge->sched; struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_SUSPEND; - - if (exec_queue_killed_or_banned_or_wedged(q)) - return -EINVAL; + bool first; xe_sched_msg_lock(sched); - if (++ge->suspend_count == 1) { + first = (++ge->suspend_count == 1); + if (first) { bool added = guc_exec_queue_try_add_msg(q, msg, SUSPEND); /* slot must be free at 0->1 */ @@ -2205,6 +2235,80 @@ static int guc_exec_queue_suspend(struct xe_exec_queue *q) } xe_sched_msg_unlock(sched); + return first; +} + +/* + * Core resume: drop a suspend reference on @q and, on the last reference, + * re-enable its GuC context. Returns true if this dropped the last suspend + * reference (the 1->0 transition). + */ +static bool __guc_exec_queue_resume(struct xe_exec_queue *q) +{ + struct xe_guc_exec_queue *ge = q->guc; + struct xe_gpu_scheduler *sched = &ge->sched; + struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME; + struct xe_guc *guc = exec_queue_to_guc(q); + bool last; + + xe_sched_msg_lock(sched); + xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending); + xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0); + last = (--ge->suspend_count == 0); + if (last) { + bool added = guc_exec_queue_try_add_msg(q, msg, RESUME); + + /* slot must be free at 1->0 */ + xe_gt_assert(guc_to_gt(guc), added); + } + xe_sched_msg_unlock(sched); + + return last; +} + +static int guc_exec_queue_suspend(struct xe_exec_queue *q) +{ + if (exec_queue_killed_or_banned_or_wedged(q)) + return -EINVAL; + + /* + * Non-multi-queue queues and multi-queue primaries suspend themselves + * directly: their own msg_lock makes the suspend_count 0->1 transition + * and the suspend_pending update atomic, so no group level serialization + * is needed. + */ + if (!xe_exec_queue_is_multi_queue_secondary(q)) { + __guc_exec_queue_suspend(q); + return 0; + } + + /* + * A secondary's suspend is meaningless once the primary - which owns the + * group's GuC context - is gone, so fail it too. This keeps the + * secondary's effective state consistent with guc_exec_queue_reset_status(), + * which already reports the primary's killed/banned/wedged state for + * secondaries. A primary killed *after* this check is still handled at + * message-processing time, where the SUSPEND is a no-op for a killed + * context; this only covers an already-dead primary. + */ + if (exec_queue_killed_or_banned_or_wedged(xe_exec_queue_multi_queue_primary(q))) + return -EINVAL; + + /* + * A secondary doesn't interface with GuC: suspend it like any other + * queue (its own suspend_count drives its internally handled scheduler + * state) and, only on its own 0->1 transition, forward the suspend to the + * primary so the GPU is actually preempted. Hold @suspend_lock so that + * observing the secondary's transition and forwarding it to the primary + * happen atomically; this keeps the primary's refcount paired with member + * transitions even if the same secondary is suspended and resumed + * concurrently across rebind cycles. + */ + scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) { + if (__guc_exec_queue_suspend(q)) + __guc_exec_queue_suspend(xe_exec_queue_multi_queue_primary(q)); + } + return 0; } @@ -2336,21 +2440,59 @@ static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q) static void guc_exec_queue_resume(struct xe_exec_queue *q) { - struct xe_guc_exec_queue *ge = q->guc; - struct xe_gpu_scheduler *sched = &ge->sched; - struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME; - struct xe_guc *guc = exec_queue_to_guc(q); - - xe_sched_msg_lock(sched); - xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending); - xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0); - if (--ge->suspend_count == 0) { - bool added = guc_exec_queue_try_add_msg(q, msg, RESUME); - - /* slot must be free at 1->0 */ - xe_gt_assert(guc_to_gt(guc), added); + /* + * Non-multi-queue queues and multi-queue primaries resume themselves + * directly; their own msg_lock is sufficient. + */ + if (!xe_exec_queue_is_multi_queue_secondary(q)) { + __guc_exec_queue_resume(q); + return; + } + + /* + * Mirror of guc_exec_queue_suspend(): resume the secondary like any + * other queue and, only on its own 1->0 transition, forward the resume + * to the primary so the primary's GuC context is re-enabled once the + * last member that suspended it resumes. @suspend_lock keeps the + * secondary transition and the primary forward atomic. + */ + scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) { + if (__guc_exec_queue_resume(q)) + __guc_exec_queue_resume(xe_exec_queue_multi_queue_primary(q)); + } +} + +/* + * Drop a leaving secondary's forwarded suspend reference on the primary and + * resume the primary if this was the last member that had it suspended. + * See guc_exec_queue_fini(). + */ +static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q) +{ + scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) { + struct xe_exec_queue *primary = xe_exec_queue_multi_queue_primary(q); + + /* + * A suspended secondary holds exactly one suspend reference on the + * primary (forwarded on its 0->1 transition). If it leaves while + * still suspended, release that reference so the primary is not + * kept disabled forever. + */ + if (!READ_ONCE(q->guc->suspend_count)) + break; + + if (exec_queue_killed_or_banned_or_wedged(primary)) + break; + + /* + * No suspend_wait() here (and we can't - suspend_lock is a + * spinlock). guc_exec_queue_fini() has already drained the + * primary's forwarded suspend with the blocking wait, so its + * suspend has completed (suspend_pending cleared) by the time we + * resume it here. __guc_exec_queue_resume() asserts this. + */ + __guc_exec_queue_resume(primary); } - xe_sched_msg_unlock(sched); } static bool guc_exec_queue_reset_status(struct xe_exec_queue *q) From d9a4906ac03be9f6ed3f3b45c56c866b867fd75b Mon Sep 17 00:00:00 2001 From: Himal Prasad Ghimiray Date: Wed, 24 Jun 2026 23:19:44 +0530 Subject: [PATCH 45/81] drm/xe/vm: Fix BO prefetch with CONSULT_MEM_ADVISE_PREF_LOC When prefetch region is DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC for a BO VMA, the code used it as an index into region_to_mem_type[], causing an out-of-bounds access since the value is -1. Resolve the preferred location for BO VMAs directly: local VRAM on dGFX (using the BO's tile placement) or system memory on iGPU. Discovered using AI-assisted static analysis confirmed by Intel Product Security. v2: -Fix null dereference Reported-by: Martin Hodo Fixes: c1bb69a2e8e2 ("drm/xe/svm: Consult madvise preferred location in prefetch") Cc: Matthew Brost Cc: stable@vger.kernel.org Reviewed-by: Matthew Brost Link: https://patchwork.freedesktop.org/patch/msgid/20260624174943.2808767-2-himal.prasad.ghimiray@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_vm.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 23f4a9fb9a49..726d8465414a 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -3271,11 +3271,26 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm, .request_decompress = false, .check_purged = true, }); - if (!err && !xe_vma_has_no_bo(vma)) - err = xe_bo_migrate(xe_vma_bo(vma), - region_to_mem_type[region], - NULL, - exec); + if (!err && !xe_vma_has_no_bo(vma)) { + struct xe_bo *bo = xe_vma_bo(vma); + u32 mem_type; + + if (region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC) { + unsigned int i; + + mem_type = XE_PL_TT; + for (i = 0; i < bo->placement.num_placement; i++) { + if (mem_type_is_vram(bo->placements[i].mem_type)) { + mem_type = bo->placements[i].mem_type; + break; + } + } + } else { + mem_type = region_to_mem_type[region]; + } + + err = xe_bo_migrate(bo, mem_type, NULL, exec); + } break; } default: From 3516f3fae6be35642f8f06f8a218da6425c0306a Mon Sep 17 00:00:00 2001 From: Nitin Gote Date: Sat, 11 Jul 2026 00:40:28 +0530 Subject: [PATCH 46/81] drm/xe: Hold a dma-buf reference for imported BOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An imported dma-buf BO is created as a ttm_bo_type_sg BO whose reservation object is the exporter's dma_buf->resv. The importer, however, only takes a dma-buf reference after a successful dma_buf_dynamic_attach(). Until then nothing keeps the exporter alive, so if the exporter is freed while the BO still references its resv, a later access to that resv is a use-after-free: Oops: general protection fault, probably for non-canonical address 0x6b6b6b6b6b6b6b9c Workqueue: ttm ttm_bo_delayed_delete [ttm] RIP: 0010:mutex_can_spin_on_owner+0x3f/0xc0 This can be reached on two paths: - dma_buf_dynamic_attach() fails, or - ttm_bo_init_reserved() fails during BO creation. In both cases the BO already has bo->base.resv pointing at the exporter resv, and sg BOs are always torn down via ttm_bo_delayed_delete(), which locks bo->base.resv asynchronously - potentially after the exporter has been freed. Take the dma-buf reference in xe_bo_init_locked(), before ttm_bo_init_reserved(), so it also covers a creation failure there, and release it in xe_ttm_bo_destroy(). The reference is held for the whole BO lifetime, keeping the shared resv alive on every path. v2: - Reworked the fix to avoid creating the imported sg BO before dma_buf_dynamic_attach() succeeds. - Attach with importer_priv == NULL and make invalidate_mappings ignore incomplete imports. v3: - Dropped the xe-side reordering approach since importer_priv must be valid when dma_buf_dynamic_attach() publishes the attachment. - Per Christian's suggestion on the v1 thread, keyed the check on import_attach rather than removing the sg guard entirely. - Fixes both xe and amdgpu in a single TTM patch. v4: - Moved import_attach check to after dma_resv_copy_fences() so fences are copied before returning for successful imports (Thomas). - Removed exporter-alive claim from commit message (Thomas). v5: - Add drm/xe patch to keep imported sg BOs off the LRU before attach succeeds; the TTM fix alone is not sufficient for xe if the BO is already LRU-visible. (Thomas) v4 patch: https://patchwork.freedesktop.org/patch/736663/?series=169129&rev=2 - Patch 1 (drm/ttm) carries Christian's Reviewed-by from v4. v6: - Reworked the fix based on Thomas' suggestion. Instead of the TTM resv individualization (v1-v5) plus the xe off-LRU/placement handling (v5), just hold a dma-buf reference for the imported BO lifetime so the shared resv can never be freed while the BO still references it. Single xe patch, no TTM change. (Thomas) - Take the reference in xe_bo_init_locked() before ttm_bo_init_reserved() so a TTM creation failure is covered too (Thomas). - Dropped the v5 series (drm/ttm + drm/xe off-LRU); the off-LRU approach also regressed in CI BAT via ttm_bo_pipeline_gutting() creating a ghost BO that outlived the exporter. Link to v5: https://patchwork.freedesktop.org/series/169984/ v7: - Move changelog above --- so it stays in the commit message. - Reorder changelog entries oldest-to-newest. (Thomas) Closes: https://gitlab.freedesktop.org/drm/xe/kernel/-/work_items/8023 Fixes: dd08ebf6c352 ("drm/xe: Introduce a new DRM driver for Intel GPUs") Cc: stable@vger.kernel.org Cc: Thomas Hellstrom Cc: Christian Konig Cc: Matthew Auld Suggested-by: Thomas Hellstrom Assisted-by: GitHub_Copilot:claude-sonnet-4.6 Reviewed-by: Thomas Hellström Signed-off-by: Nitin Gote Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260710191027.260160-2-nitin.r.gote@intel.com --- drivers/gpu/drm/xe/xe_bo.c | 24 ++++++++++++++++++++---- drivers/gpu/drm/xe/xe_bo.h | 3 ++- drivers/gpu/drm/xe/xe_bo_types.h | 2 ++ drivers/gpu/drm/xe/xe_dma_buf.c | 2 +- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index 4c80bac67622..ddbaf4242c79 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1349,7 +1349,7 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo) backup = xe_bo_init_locked(xe, NULL, NULL, bo->ttm.base.resv, NULL, xe_bo_size(bo), DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS | - XE_BO_FLAG_PINNED, &exec); + XE_BO_FLAG_PINNED, NULL, &exec); if (IS_ERR(backup)) { drm_exec_retry_on_contention(&exec); ret = PTR_ERR(backup); @@ -1490,7 +1490,7 @@ int xe_bo_evict_pinned(struct xe_bo *bo) xe_bo_size(bo), DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel, XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS | - XE_BO_FLAG_PINNED, &exec); + XE_BO_FLAG_PINNED, NULL, &exec); if (IS_ERR(backup)) { drm_exec_retry_on_contention(&exec); ret = PTR_ERR(backup); @@ -1826,6 +1826,8 @@ static void xe_ttm_bo_destroy(struct ttm_buffer_object *ttm_bo) if (bo->ttm.base.import_attach) drm_prime_gem_destroy(&bo->ttm.base, NULL); + if (bo->dma_buf) + dma_buf_put(bo->dma_buf); drm_gem_object_release(&bo->ttm.base); xe_assert(xe, list_empty(&ttm_bo->base.gpuva.list)); @@ -2283,6 +2285,8 @@ void xe_bo_free(struct xe_bo *bo) * @cpu_caching: The cpu caching used for system memory backing store. * @type: The TTM buffer object type. * @flags: XE_BO_FLAG_ flags. + * @dma_buf: The dma-buf to reference for the BO lifetime (imported BOs), + * or NULL. * @exec: The drm_exec transaction to use for exhaustive eviction. * * Initialize or create an xe buffer object. On failure, any allocated buffer @@ -2294,7 +2298,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, struct xe_tile *tile, struct dma_resv *resv, struct ttm_lru_bulk_move *bulk, size_t size, u16 cpu_caching, enum ttm_bo_type type, - u32 flags, struct drm_exec *exec) + u32 flags, struct dma_buf *dma_buf, + struct drm_exec *exec) { struct ttm_operation_ctx ctx = { .interruptible = true, @@ -2383,6 +2388,17 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, placement = (type == ttm_bo_type_sg || bo->flags & XE_BO_FLAG_DEFER_BACKING) ? &sys_placement : &bo->placement; + + /* + * For imported BOs, keep the exporter dma-buf alive for the BO + * lifetime. Taken before ttm_bo_init_reserved() to also cover a + * creation failure there. Released in xe_ttm_bo_destroy(). + */ + if (dma_buf) { + get_dma_buf(dma_buf); + bo->dma_buf = dma_buf; + } + err = ttm_bo_init_reserved(&xe->ttm, &bo->ttm, type, placement, alignment, &ctx, NULL, resv, xe_ttm_bo_destroy); @@ -2500,7 +2516,7 @@ __xe_bo_create_locked(struct xe_device *xe, vm && !xe_vm_in_fault_mode(vm) && flags & XE_BO_FLAG_USER ? &vm->lru_bulk_move : NULL, size, - cpu_caching, type, flags, exec); + cpu_caching, type, flags, NULL, exec); if (IS_ERR(bo)) return bo; diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 6340317f7d2e..7ae1d9ac0574 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -118,7 +118,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, struct xe_tile *tile, struct dma_resv *resv, struct ttm_lru_bulk_move *bulk, size_t size, u16 cpu_caching, enum ttm_bo_type type, - u32 flags, struct drm_exec *exec); + u32 flags, struct dma_buf *dma_buf, + struct drm_exec *exec); struct xe_bo *xe_bo_create_locked(struct xe_device *xe, struct xe_tile *tile, struct xe_vm *vm, size_t size, enum ttm_bo_type type, u32 flags, diff --git a/drivers/gpu/drm/xe/xe_bo_types.h b/drivers/gpu/drm/xe/xe_bo_types.h index fcc63ae3f455..e45f24301050 100644 --- a/drivers/gpu/drm/xe/xe_bo_types.h +++ b/drivers/gpu/drm/xe/xe_bo_types.h @@ -36,6 +36,8 @@ struct xe_bo { struct xe_bo *backup_obj; /** @parent_obj: Ref to parent bo if this a backup_obj */ struct xe_bo *parent_obj; + /** @dma_buf: Imported dma-buf ref to keep its resv alive. */ + struct dma_buf *dma_buf; /** @flags: flags for this buffer object */ u32 flags; /** @vm: VM this BO is attached to, for extobj this will be NULL */ diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c index 8a920e58245c..bf0728838ead 100644 --- a/drivers/gpu/drm/xe/xe_dma_buf.c +++ b/drivers/gpu/drm/xe/xe_dma_buf.c @@ -302,7 +302,7 @@ xe_dma_buf_create_obj(struct drm_device *dev, struct dma_buf *dma_buf) bo = xe_bo_init_locked(xe, NULL, NULL, resv, NULL, dma_buf->size, 0, /* Will require 1way or 2way for vm_bind */ - ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, &exec); + ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, dma_buf, &exec); drm_exec_retry_on_contention(&exec); if (IS_ERR(bo)) { ret = PTR_ERR(bo); From 7ef55ae582eba2b0a7a7441bd3b9aefd38a26bb9 Mon Sep 17 00:00:00 2001 From: Satyanarayana K V P Date: Tue, 14 Jul 2026 11:03:00 +0530 Subject: [PATCH 47/81] drm/xe/pf: Disable display in admin only PF mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin-only PF mode does not expose media or 3D execution capabilities to userspace, so display pipelines cannot receive rendered content. Fixes: d88c4bac8c2a ("drm/xe/pf: Restrict device query responses in admin-only PF mode") Signed-off-by: Satyanarayana K V P Cc: Michal Wajdeczko Cc: Piotr Piórkowski Cc: Michał Winiarski Cc: Rodrigo Vivi Reviewed-by: Piotr Piórkowski Link: https://patch.msgid.link/20260714053259.504308-2-satyanarayana.k.v.p@intel.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_device.c | 2 -- drivers/gpu/drm/xe/xe_pci.c | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index ad7f3e61d457..785222d54701 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -427,7 +427,6 @@ static const struct drm_ioctl_desc xe_ioctls_admin_only[] = { static const struct drm_driver admin_only_driver = { .driver_features = - XE_DISPLAY_DRIVER_FEATURES | DRIVER_GEM | DRIVER_RENDER, .open = xe_file_open, .postclose = xe_file_close, @@ -439,7 +438,6 @@ static const struct drm_driver admin_only_driver = { .major = DRIVER_MAJOR, .minor = DRIVER_MINOR, .patchlevel = DRIVER_PATCHLEVEL, - XE_DISPLAY_DRIVER_OPS, }; /** diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index f6e18e61a5ac..2af52a7ef970 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -821,7 +821,8 @@ static int xe_info_init_early(struct xe_device *xe, xe->info.probe_display = IS_ENABLED(CONFIG_DRM_XE_DISPLAY) && xe_modparam.probe_display && - desc->has_display; + desc->has_display && + !xe_device_is_admin_only(xe); xe_assert(xe, desc->max_gt_per_tile > 0); xe_assert(xe, desc->max_gt_per_tile <= XE_MAX_GT_PER_TILE); From 2007be18d2318a59748da5da1b8968042213d5f1 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 14 Jul 2026 08:54:17 +0300 Subject: [PATCH 48/81] drm/xe/nvm: fix writable override for CRI The witable override should be set when FDO_MODE bit is enabled. Fix the comparison to distingush this case from legacy systems where bit should be disabled to have override. Cc: stable@vger.kernel.org Fixes: 9dde74fd9e65 ("drm/xe/nvm: enable cri platform") Signed-off-by: Alexander Usyskin Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260714-cri_nvm_fdo_flip-v2-1-14580e71b58e@intel.com Signed-off-by: Rodrigo Vivi --- drivers/gpu/drm/xe/xe_nvm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_nvm.c b/drivers/gpu/drm/xe/xe_nvm.c index 33487e91f366..1ea67eaeae24 100644 --- a/drivers/gpu/drm/xe/xe_nvm.c +++ b/drivers/gpu/drm/xe/xe_nvm.c @@ -60,35 +60,40 @@ static bool xe_nvm_writable_override(struct xe_device *xe) struct xe_mmio *mmio = xe_root_tile_mmio(xe); bool writable_override; struct xe_reg reg; - u32 test_bit; + u32 test_bit, test_val; switch (xe->info.platform) { case XE_CRESCENTISLAND: reg = PCODE_SCRATCH(0); test_bit = FDO_MODE; + test_val = FDO_MODE; break; case XE_BATTLEMAGE: reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_PVC: reg = HECI_FWSTS2(PVC_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_DG2: reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; case XE_DG1: reg = HECI_FWSTS2(DG1_GSC_HECI2_BASE); test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE; + test_val = 0; break; default: drm_err(&xe->drm, "Unknown platform\n"); return true; } - writable_override = !(xe_mmio_read32(mmio, reg) & test_bit); + writable_override = (xe_mmio_read32(mmio, reg) & test_bit) == test_val; if (writable_override) drm_info(&xe->drm, "NVM access overridden by jumper\n"); return writable_override; From d45ad0aa7a1eb5d7288b5ed948b05695611dc39e Mon Sep 17 00:00:00 2001 From: Matthew Brost Date: Mon, 13 Jul 2026 23:24:40 -0700 Subject: [PATCH 49/81] drm/xe/vf: Fix VF CCS attach/detach race with in-flight BO moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_bo_move() attaches VF CCS read/write batch buffers (BBs) to a BO after it transitions NULL/SYSTEM -> TT, and detaches them after it transitions TT -> SYSTEM. Both operations were done synchronously on the CPU immediately after building the move's copy/clear fence, without waiting for that fence to signal. This creates two races with VF migration: - Attach happens too late relative to the copy job it is meant to protect. If the copy job is submitted before the CCS BBs are attached, a VF migration event that pauses execution mid-copy can observe partially copied CCS metadata without the attach state needed to correctly save/restore it. - Detach happens too early relative to the copy job that moves data out of TT. The CCS BBs are torn down right after the copy fence is obtained, while the actual blit may still be in flight. A VF migration event that pauses execution mid-copy can then race the save/restore path against the still-running blit, and the CCS BBs it would need to make sense of the paused state have already been removed. Fix both races: - Move the attach call to before the copy/clear job is submitted, so the CCS BBs are already registered by the time the copy runs. On attach failure, unwind and bail out of the move. xe_migrate_ccs_rw_copy() now takes the destination resource explicitly, since bo->ttm.resource is not updated to the new resource until after the move commits. - Detach only after explicitly waiting for the copy fence to signal, instead of tearing down the CCS BBs immediately after obtaining it. While here, also fix xe_sriov_vf_ccs_attach_bo() to properly unwind and propagate errors: the per-context loop previously never broke out on error, silently discarding earlier failures. Unwind by clearing each attached context directly via xe_migrate_ccs_rw_copy_clear() instead of reusing xe_sriov_vf_ccs_detach_bo(), which requires both contexts to be attached before it will clean up either one. Fixes: 864690cf4dd6 ("drm/xe/vf: Attach and detach CCS copy commands with BO") Cc: Michal Wajdeczko Cc: Matthew Auld Cc: Michał Winiarski Cc: Satyanarayana K V P Assisted-by: GitHub_Copilot:claude-sonnet-5 Signed-off-by: Matthew Brost Acked-by: Satyanarayana K V P Reviewed-by: Matthew Auld Link: https://patch.msgid.link/20260714062440.3421225-1-matthew.brost@intel.com --- drivers/gpu/drm/xe/xe_bo.c | 34 +++++++++++++++++++--------- drivers/gpu/drm/xe/xe_migrate.c | 5 +++- drivers/gpu/drm/xe/xe_migrate.h | 1 + drivers/gpu/drm/xe/xe_sriov_vf_ccs.c | 20 ++++++++++++++-- drivers/gpu/drm/xe/xe_sriov_vf_ccs.h | 3 ++- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index ae730bd6f4b2..c266fa6bade1 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1102,6 +1102,21 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, xe_pm_runtime_get_noresume(xe); } + /* + * Attach CCS BBs before submitting the copy job below so a VF + * migration racing the copy sees valid, up to date attach state. + */ + if (IS_VF_CCS_READY(xe) && + ((move_lacks_source && new_mem->mem_type == XE_PL_TT) || + (old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) && + handle_system_ccs) { + ret = xe_sriov_vf_ccs_attach_bo(bo, new_mem); + if (ret) { + xe_pm_runtime_put(xe); + goto out; + } + } + if (move_lacks_source) { u32 flags = 0; @@ -1139,22 +1154,19 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, ttm_bo_move_null(ttm_bo, new_mem); } - dma_fence_put(fence); - xe_pm_runtime_put(xe); - /* - * CCS meta data is migrated from TT -> SMEM. So, let us detach the - * BBs from BO as it is no longer needed. + * Detach must wait for the copy above to complete: a VF migration + * racing an in-flight copy must still see valid CCS BBs, so don't + * tear them down until the copy fence has signaled. */ if (IS_VF_CCS_READY(xe) && old_mem_type == XE_PL_TT && - new_mem->mem_type == XE_PL_SYSTEM) + new_mem->mem_type == XE_PL_SYSTEM) { + dma_fence_wait(fence, false); xe_sriov_vf_ccs_detach_bo(bo); + } - if (IS_VF_CCS_READY(xe) && - ((move_lacks_source && new_mem->mem_type == XE_PL_TT) || - (old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) && - handle_system_ccs) - ret = xe_sriov_vf_ccs_attach_bo(bo); + dma_fence_put(fence); + xe_pm_runtime_put(xe); out: if ((!ttm_bo->resource || ttm_bo->resource->mem_type == XE_PL_SYSTEM) && diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 92d5e81ceac2..c84e14e86a82 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -1142,6 +1142,8 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags) * @tile: Tile whose migration context to be used. * @q : Execution to be used along with migration context. * @src_bo: The buffer object @src is currently bound to. + * @new_mem: The (not yet committed) destination resource @src_bo is being + * moved into; src_bo->ttm.resource is still the old resource. * @read_write : Creates BB commands for CCS read/write. * * Creates batch buffer instructions to copy CCS metadata from CCS pool to @@ -1153,12 +1155,13 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags) */ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, struct xe_bo *src_bo, + struct ttm_resource *new_mem, enum xe_sriov_vf_ccs_rw_ctxs read_write) { bool src_is_pltt = read_write == XE_SRIOV_VF_CCS_READ_CTX; bool dst_is_pltt = read_write == XE_SRIOV_VF_CCS_WRITE_CTX; - struct ttm_resource *src = src_bo->ttm.resource; + struct ttm_resource *src = new_mem; struct xe_migrate *m = tile->migrate; struct xe_gt *gt = tile->primary_gt; u32 batch_size, batch_size_allocated; diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h index 965c45889c72..78e5b63f3ebe 100644 --- a/drivers/gpu/drm/xe/xe_migrate.h +++ b/drivers/gpu/drm/xe/xe_migrate.h @@ -138,6 +138,7 @@ struct dma_fence *xe_migrate_resolve(struct xe_migrate *m, int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, struct xe_bo *src_bo, + struct ttm_resource *new_mem, enum xe_sriov_vf_ccs_rw_ctxs read_write); void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c index 09b99fb2608b..6787564629c6 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c @@ -404,6 +404,8 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx) /** * xe_sriov_vf_ccs_attach_bo - Insert CCS read write commands in the BO. * @bo: the &buffer object to which batch buffer commands will be added. + * @new_mem: the (not yet committed) destination resource @bo is being moved + * into; bo->ttm.resource is still the old resource at this point. * * This function shall be called only by VF. It inserts the PTEs and copy * command instructions in the BO by calling xe_migrate_ccs_rw_copy() @@ -411,7 +413,7 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx) * * Returns: 0 if successful, negative error code on failure. */ -int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo) +int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem) { struct xe_device *xe = xe_bo_device(bo); enum xe_sriov_vf_ccs_rw_ctxs ctx_id; @@ -430,7 +432,21 @@ int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo) xe_assert(xe, !bb); ctx = &xe->sriov.vf.ccs.contexts[ctx_id]; - err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, ctx_id); + err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, new_mem, ctx_id); + if (err) + goto err_unwind; + } + return 0; + +err_unwind: + /* + * Clean up any contexts already attached. Can't reuse + * xe_sriov_vf_ccs_detach_bo() here as it requires both contexts + * attached before cleaning up either one. + */ + for_each_ccs_rw_ctx(ctx_id) { + if (bo->bb_ccs[ctx_id]) + xe_migrate_ccs_rw_copy_clear(bo, ctx_id); } return err; } diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h index 00e58b36c510..e1034d852104 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.h @@ -11,11 +11,12 @@ #include "xe_sriov_vf_ccs_types.h" struct drm_printer; +struct ttm_resource; struct xe_device; struct xe_bo; int xe_sriov_vf_ccs_init(struct xe_device *xe); -int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo); +int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem); int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo); int xe_sriov_vf_ccs_register_context(struct xe_device *xe); void xe_sriov_vf_ccs_rebase(struct xe_device *xe); From f6d8232d0c6978b299237c7c3dd4140c2e9db7ce Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Mon, 13 Jul 2026 13:17:57 +0530 Subject: [PATCH 50/81] drm/xe/xe_sysctrl: Make sysctrl flood limit reusable The sysctrl command flood limit was defined in an event specific header, restricting its usage to event handling. Move it to the shared header with a generic name so it can be re-used across all files using system controller commands. Reviewed-by: Raag Jadav Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260713074755.1278607-7-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_sysctrl_event.c | 2 +- drivers/gpu/drm/xe/xe_sysctrl_event_types.h | 3 --- drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h | 3 +++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_sysctrl_event.c b/drivers/gpu/drm/xe/xe_sysctrl_event.c index b4d17329af6c..da395148ee9d 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_event.c +++ b/drivers/gpu/drm/xe/xe_sysctrl_event.c @@ -16,7 +16,7 @@ static void get_pending_event(struct xe_sysctrl *sc, struct xe_sysctrl_mailbox_c { struct xe_sysctrl_event_response *response = command->data_out; struct xe_device *xe = sc_to_xe(sc); - u32 count = XE_SYSCTRL_EVENT_FLOOD; + u32 count = XE_SYSCTRL_FLOOD_LIMIT; size_t len; int ret; diff --git a/drivers/gpu/drm/xe/xe_sysctrl_event_types.h b/drivers/gpu/drm/xe/xe_sysctrl_event_types.h index c16c66b9fa7f..348768ca454a 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_event_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_event_types.h @@ -10,9 +10,6 @@ #define XE_SYSCTRL_EVENT_DATA_LEN 59 -/* Modify as needed */ -#define XE_SYSCTRL_EVENT_FLOOD 16 - /** * enum xe_sysctrl_event - Events reported by System Controller * diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h index 6e3753554510..b0c123096969 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h @@ -52,6 +52,9 @@ struct xe_sysctrl_mailbox_command { size_t data_out_len; }; +/* Modify as needed */ +#define XE_SYSCTRL_FLOOD_LIMIT 16 + #define XE_SYSCTRL_MB_FRAME_SIZE 16 #define XE_SYSCTRL_MB_MAX_FRAMES 64 #define XE_SYSCTRL_MB_MAX_MESSAGE_SIZE \ From 25913a062cd44bc23613b8de56f7261418d88d47 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Mon, 13 Jul 2026 13:17:58 +0530 Subject: [PATCH 51/81] drm/xe/xe_ras: Add support for uncorrectable core-compute errors Add structures and command for get soc error and process uncorrectable core-compute errors. Uncorrectable core-compute errors are classified into global and local errors. Global error is an error that affects the entire device requiring a reset. This type of error is not isolated. When an AER is reported and error_detected is invoked request an SBR (Secondary Bus Reset) from PCI core. Local error is confined to a specific component or context like a engine. These errors can be contained and recovered by resetting only the affected engine without disrupting the rest of the device. Upon detection of an uncorrectable local core-compute error, an AER is generated and GuC is notified of the error to trigger engine reset. Return recovered from PCI error callbacks for these errors as no action is needed. Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260713074755.1278607-8-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_ras.c | 116 +++++++++++++++++- drivers/gpu/drm/xe/xe_ras.h | 2 + drivers/gpu/drm/xe/xe_ras_types.h | 56 +++++++++ drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h | 2 + 4 files changed, 175 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c index 74d5016d9ffe..1204b9f05b24 100644 --- a/drivers/gpu/drm/xe/xe_ras.c +++ b/drivers/gpu/drm/xe/xe_ras.c @@ -8,12 +8,21 @@ #include "xe_pm.h" #include "xe_printk.h" #include "xe_ras.h" -#include "xe_ras_types.h" #include "xe_sysctrl.h" #include "xe_sysctrl_event_types.h" #include "xe_sysctrl_mailbox.h" #include "xe_sysctrl_mailbox_types.h" +#define CORE_COMPUTE_UNCORR_TYPE GENMASK(26, 25) +/* + * Uncorrectable error type for core compute errors. + * 0 - Correctable Error + * 1 - Local Uncorrectable Error + * 2 - Global Uncorrectable Error + * 3 - Informational Error + */ +#define GLOBAL_UNCORR_ERROR 2 + /* Severity of detected errors */ enum xe_ras_severity { XE_RAS_SEV_NOT_SUPPORTED = 0, @@ -193,6 +202,24 @@ static void ras_usp_aer_init(struct xe_device *xe) dev_dbg(&usp->dev, "Uncorrectable Internal Errors downgraded and unmasked\n"); } +static u8 handle_core_compute_errors(struct xe_ras_error_array *arr) +{ + struct xe_ras_compute_error *error_info = (void *)arr->details; + u8 uncorr_type; + + uncorr_type = FIELD_GET(CORE_COMPUTE_UNCORR_TYPE, error_info->log_header); + + /* Request a reset if error is global */ + if (uncorr_type == GLOBAL_UNCORR_ERROR) + return XE_RAS_RECOVERY_ACTION_RESET; + + /* + * No action needed for other errors. + * Local errors are recovered using an engine reset by GuC. + */ + return XE_RAS_RECOVERY_ACTION_RECOVERED; +} + void xe_ras_counter_threshold_crossed(struct xe_device *xe, struct xe_sysctrl_event_response *response) { @@ -254,6 +281,93 @@ static int get_counter(struct xe_device *xe, struct xe_ras_error_class *counter, return 0; } +/** + * xe_ras_process_errors() - Process and contain hardware errors + * @xe: xe device instance + * + * Get error details from system controller and return recovery + * method. + * + * Returns: recovery action to be taken + */ +enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe) +{ + struct xe_sysctrl_mailbox_command command = {0}; + enum xe_ras_recovery_action final_action; + u32 remaining = XE_SYSCTRL_FLOOD_LIMIT; + struct xe_ras_get_soc_error response; + size_t rlen; + int ret; + + if (!xe->info.has_sysctrl) + return XE_RAS_RECOVERY_ACTION_RESET; + + /* Default action */ + final_action = XE_RAS_RECOVERY_ACTION_RECOVERED; + + xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_GET_SOC_ERROR, + NULL, 0, &response, sizeof(response)); + + do { + memset(&response, 0, sizeof(response)); + + ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen); + if (ret) { + xe_err(xe, "sysctrl: failed to get soc error %d\n", ret); + goto err; + } + + if (rlen != sizeof(response)) { + xe_err(xe, "sysctrl: unexpected get soc error response length %zu (expected %zu)\n", + rlen, sizeof(response)); + goto err; + } + + /* Report if number of errors exceeds the maximum errors supported */ + if (response.num_errors > XE_RAS_NUM_ERROR_ARR) + xe_err(xe, "sysctrl: number of errors received %d out of bound (%d)\n", + response.num_errors, XE_RAS_NUM_ERROR_ARR); + + for (int i = 0; i < response.num_errors && i < XE_RAS_NUM_ERROR_ARR; i++) { + struct xe_ras_error_array *arr = &response.arr[i]; + enum xe_ras_recovery_action action; + u8 component, severity; + + component = arr->counter.common.component; + severity = arr->counter.common.severity; + + xe_info(xe, "[RAS]: %s %s detected\n", comp_to_str(component), + sev_to_str(severity)); + + switch (component) { + case XE_RAS_COMP_CORE_COMPUTE: + action = handle_core_compute_errors(arr); + break; + default: + /* For any other component, reset */ + action = XE_RAS_RECOVERY_ACTION_RESET; + break; + } + + /* Process and log all errors and then trigger highest recovery action */ + if (action > final_action) + final_action = action; + } + + /* Treat flooding as a system controller error */ + if (!--remaining) { + xe_err(xe, "[RAS]: sysctrl: get soc error response flooding\n"); + goto err; + } + + } while (response.additional_errors); + + return final_action; + +err: + return XE_RAS_RECOVERY_ACTION_RESET; +} + /** * xe_ras_get_counter() - Get error counter value * @xe: Xe device instance diff --git a/drivers/gpu/drm/xe/xe_ras.h b/drivers/gpu/drm/xe/xe_ras.h index ba0b0224df23..618364734043 100644 --- a/drivers/gpu/drm/xe/xe_ras.h +++ b/drivers/gpu/drm/xe/xe_ras.h @@ -7,6 +7,7 @@ #define _XE_RAS_H_ #include +#include "xe_ras_types.h" struct xe_device; struct xe_sysctrl_event_response; @@ -16,5 +17,6 @@ void xe_ras_counter_threshold_crossed(struct xe_device *xe, int xe_ras_get_counter(struct xe_device *xe, u8 severity, u8 component, u32 *value); int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component); void xe_ras_init(struct xe_device *xe); +enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe); #endif diff --git a/drivers/gpu/drm/xe/xe_ras_types.h b/drivers/gpu/drm/xe/xe_ras_types.h index 6688e11f57a8..8d344691b549 100644 --- a/drivers/gpu/drm/xe/xe_ras_types.h +++ b/drivers/gpu/drm/xe/xe_ras_types.h @@ -9,6 +9,25 @@ #include #define XE_RAS_NUM_COUNTERS 16 +#define XE_RAS_NUM_ERROR_ARR 3 + +/** + * enum xe_ras_recovery_action - RAS recovery actions + * + * @XE_RAS_RECOVERY_ACTION_RECOVERED: Error recovered + * @XE_RAS_RECOVERY_ACTION_RESET: Requires reset + * @XE_RAS_RECOVERY_ACTION_DISCONNECT: Requires disconnect + * @XE_RAS_RECOVERY_ACTION_MAX: Max action value + * + * This enum defines the possible recovery actions that can be taken in response + * to RAS errors. + */ +enum xe_ras_recovery_action { + XE_RAS_RECOVERY_ACTION_RECOVERED = 0, + XE_RAS_RECOVERY_ACTION_RESET, + XE_RAS_RECOVERY_ACTION_DISCONNECT, + XE_RAS_RECOVERY_ACTION_MAX +}; /** * struct xe_ras_error_common - Error fields that are common across all products @@ -121,4 +140,41 @@ struct xe_ras_clear_counter_response { /** @reserved1: Reserved for future use */ u32 reserved1[3]; } __packed; + +/** + * struct xe_ras_error_array - Details of the error types + */ +struct xe_ras_error_array { + /** @value: Counter value of the detailed error */ + u32 value; + /** @counter: Error counter */ + struct xe_ras_error_class counter; + /** @timestamp: Timestamp */ + u64 timestamp; + /** @details: Error details specific to the counter */ + u32 details[XE_RAS_NUM_COUNTERS]; +} __packed; + +/** + * struct xe_ras_get_soc_error - Response from get soc error command + */ +struct xe_ras_get_soc_error { + /** @num_errors: Number of errors reported in this response */ + u8 num_errors; + /** @additional_errors: Indicates if the errors are pending */ + u8 additional_errors; + /** @arr: Array of up to 3 errors */ + struct xe_ras_error_array arr[XE_RAS_NUM_ERROR_ARR]; +} __packed; + +/** + * struct xe_ras_compute_error - Error details of Core Compute error + */ +struct xe_ras_compute_error { + /** @log_header: Error Source and type */ + u32 log_header; + /** @reserved: Reserved */ + u32 reserved[15]; +} __packed; + #endif diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h index b0c123096969..f12bc99ee31b 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h @@ -22,11 +22,13 @@ enum xe_sysctrl_group { /** * enum xe_sysctrl_gfsp_cmd - Commands supported by GFSP group * + * @XE_SYSCTRL_CMD_GET_SOC_ERROR: Retrieve basic error information * @XE_SYSCTRL_CMD_GET_COUNTER: Get error counter value * @XE_SYSCTRL_CMD_CLEAR_COUNTER: Clear error counter value * @XE_SYSCTRL_CMD_GET_PENDING_EVENT: Retrieve pending event */ enum xe_sysctrl_gfsp_cmd { + XE_SYSCTRL_CMD_GET_SOC_ERROR = 0x01, XE_SYSCTRL_CMD_GET_COUNTER = 0x03, XE_SYSCTRL_CMD_CLEAR_COUNTER = 0x04, XE_SYSCTRL_CMD_GET_PENDING_EVENT = 0x07, From d9732e498f5fc1a7e0e3bb287d631c84815a17f7 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Mon, 13 Jul 2026 13:17:59 +0530 Subject: [PATCH 52/81] drm/xe/xe_ras: Query errors from system controller on probe On driver load, process and log any errors detected by firmware prior to load. Critical errors such as Punit, CSC are reported through Pcode init failure, causing the driver to enter survivability mode on probe. Cc: Umesh Nerlige Ramappa Reviewed-by: Umesh Nerlige Ramappa Link: https://patch.msgid.link/20260713074755.1278607-9-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_ras.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c index 1204b9f05b24..845b0e99754c 100644 --- a/drivers/gpu/drm/xe/xe_ras.c +++ b/drivers/gpu/drm/xe/xe_ras.c @@ -464,4 +464,11 @@ void xe_ras_init(struct xe_device *xe) if (IS_ENABLED(CONFIG_PCIEAER)) ras_usp_aer_init(xe); + + /* + * During probe, process and log any errors detected by firmware while the driver was not + * loaded. Critical errors such as Punit and CSC are reported through Pcode init failure, + * causing the driver to enter survivability mode. + */ + xe_ras_process_errors(xe); } From acc744fa62fb098358f371bfb38e6b32032459c7 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Mon, 13 Jul 2026 13:18:00 +0530 Subject: [PATCH 53/81] drm/xe/xe_pci_error: Process errors in mmio_enabled Query system controller when any non fatal error occurs to check the type of the error, contain and recover. The system controller is queried in the mmio_enabled callback. Reviewed-by: Raag Jadav Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260713074755.1278607-10-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_pci_error.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pci_error.c b/drivers/gpu/drm/xe/xe_pci_error.c index 9b78cc0d3293..e41af2ac7f23 100644 --- a/drivers/gpu/drm/xe/xe_pci_error.c +++ b/drivers/gpu/drm/xe/xe_pci_error.c @@ -10,6 +10,7 @@ #include "xe_pci.h" #include "xe_pm.h" #include "xe_printk.h" +#include "xe_ras.h" #include "xe_survivability_mode.h" static void prepare_device_for_reset(struct pci_dev *pdev) @@ -34,6 +35,21 @@ static void prepare_device_for_reset(struct pci_dev *pdev) pci_disable_device(pdev); } +static pci_ers_result_t ras_action_to_pci_result(struct pci_dev *pdev, u8 action) +{ + switch (action) { + case XE_RAS_RECOVERY_ACTION_RECOVERED: + return PCI_ERS_RESULT_RECOVERED; + case XE_RAS_RECOVERY_ACTION_RESET: + prepare_device_for_reset(pdev); + return PCI_ERS_RESULT_NEED_RESET; + case XE_RAS_RECOVERY_ACTION_DISCONNECT: + return PCI_ERS_RESULT_DISCONNECT; + default: + return PCI_ERS_RESULT_DISCONNECT; + } +} + static pci_ers_result_t xe_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state) { struct xe_device *xe = pdev_to_xe_device(pdev); @@ -62,11 +78,12 @@ static pci_ers_result_t xe_pci_error_detected(struct pci_dev *pdev, pci_channel_ static pci_ers_result_t xe_pci_error_mmio_enabled(struct pci_dev *pdev) { struct xe_device *xe = pdev_to_xe_device(pdev); + enum xe_ras_recovery_action action; xe_info(xe, "PCI error: MMIO enabled\n"); + action = xe_ras_process_errors(xe); - /* TODO: Query system controller for the type of error and take appropriate action */ - return PCI_ERS_RESULT_RECOVERED; + return ras_action_to_pci_result(pdev, action); } static pci_ers_result_t xe_pci_error_slot_reset(struct pci_dev *pdev) From 3033b0b24ed0e2f5e56bdd4d9c183417c365a45b Mon Sep 17 00:00:00 2001 From: Daniele Ceraolo Spurio Date: Mon, 13 Jul 2026 15:17:59 -0700 Subject: [PATCH 54/81] drm/xe/wopcm: fix WOPCM size for LNL+ Starting on LNL the WOPCM size is 8MB instead of 4, so we need to avoid using the [0, 8MB) range of the GGTT as that can be unaccessible from the microcontrollers. Note that the proper long-term fix here is to read the WOPCM size from the HW, but that is a more serious rework that would be difficult to backport, so we can do that as a follow-up. Fixes: 9c57bc08652a ("drm/xe/lnl: Drop force_probe requirement") Signed-off-by: Daniele Ceraolo Spurio Cc: Rodrigo Vivi Cc: Shuicheng Lin Cc: Matt Roper Reviewed-by: Shuicheng Lin Link: https://patch.msgid.link/20260713221758.3285744-2-daniele.ceraolospurio@intel.com --- drivers/gpu/drm/xe/xe_wopcm.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_wopcm.c b/drivers/gpu/drm/xe/xe_wopcm.c index 900daf1d1b1b..fe65ed246775 100644 --- a/drivers/gpu/drm/xe/xe_wopcm.c +++ b/drivers/gpu/drm/xe/xe_wopcm.c @@ -49,9 +49,9 @@ */ /* Default WOPCM size is 2MB from Gen11, 1MB on previous platforms */ -/* FIXME: Larger size require for 2 tile PVC, do a proper probe sooner or later */ +/* FIXME: Larger size require for some platforms, do a proper probe sooner or later */ #define DGFX_WOPCM_SIZE SZ_4M -/* FIXME: Larger size require for MTL, do a proper probe sooner or later */ +#define LNL_WOPCM_SIZE SZ_8M #define MTL_WOPCM_SIZE SZ_4M #define WOPCM_SIZE SZ_2M @@ -179,9 +179,14 @@ static int __wopcm_init_regs(struct xe_device *xe, struct xe_gt *gt, u32 xe_wopcm_size(struct xe_device *xe) { - return IS_DGFX(xe) ? DGFX_WOPCM_SIZE : - xe->info.platform == XE_METEORLAKE ? MTL_WOPCM_SIZE : - WOPCM_SIZE; + if (xe->info.platform >= XE_LUNARLAKE) + return LNL_WOPCM_SIZE; + else if (IS_DGFX(xe)) + return DGFX_WOPCM_SIZE; + else if (xe->info.platform == XE_METEORLAKE) + return MTL_WOPCM_SIZE; + else + return WOPCM_SIZE; } static u32 max_wopcm_size(struct xe_device *xe) From 046045543e530605c441063535e7dca0075369a6 Mon Sep 17 00:00:00 2001 From: Zongyao Bai Date: Tue, 14 Jul 2026 23:24:32 +0000 Subject: [PATCH 55/81] drm/xe/pt: Reset current_op in xe_pt_update_ops_init() xe_pt_update_ops_init() fails to reset current_op to 0. On the vm_bind path, ops_execute() calls xe_pt_update_ops_prepare() inside the xe_validation_guard() / drm_exec_until_all_locked() loop. When that loop retries due to lock contention or OOM eviction (drm_exec_retry_on_contention() / xe_validation_retry_on_oom()), xe_pt_update_ops_prepare() runs again on the same vops, and each call to bind_op_prepare() increments current_op without resetting it. After N retries current_op exceeds the array size allocated by xe_vma_ops_alloc(), causing an out-of-bounds write into SLUB-poisoned memory and a subsequent UAF crash in xe_migrate_update_pgtables_cpu() when reading the corrupted pt_op->bind. Also reset needs_svm_lock and needs_invalidation which are derived in the same prepare pass and would otherwise cause wrong migrate ops selection and redundant TLB invalidation on retry. Fix this by resetting current_op, needs_svm_lock and needs_invalidation in xe_pt_update_ops_init(). v2 (Matt): - Add details in commit message. - Add Fixes tag and Cc to stable@vger.kernel.org Fixes: e8babb280b5e ("drm/xe: Convert multiple bind ops into single job") Suggested-by: Matthew Auld Cc: stable@vger.kernel.org Assisted-by: GitHub-Copilot:claude-sonnet-4.6 Signed-off-by: Zongyao Bai Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260714232433.2737533-1-zongyao.bai@intel.com --- drivers/gpu/drm/xe/xe_pt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index e466f714bf86..598c6b2571e7 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -2371,8 +2371,11 @@ 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->start = ~0x0ull; pt_update_ops->last = 0x0ull; + pt_update_ops->needs_svm_lock = false; + pt_update_ops->needs_invalidation = false; xe_page_reclaim_list_init(&pt_update_ops->prl); } From 8cd24184c0f52f9f4e5a8eafa29ed0078ea33ddd Mon Sep 17 00:00:00 2001 From: Mallesh Koujalagi Date: Wed, 15 Jul 2026 14:21:59 +0530 Subject: [PATCH 56/81] drm/xe: Consolidate debugfs fault injection functions The fault injection code was scattered: the GT reset hook lived in xe_gt.h as an inline function with its own global variable, the CSC hook had a separate global in xe_hw_error.c with an extern declaration, and each was individually registered in xe_debugfs.c. Adding a new error type meant editing many files and copy-pasting the same boilerplate. Debugfs interface (under /sys/kernel/debug/dri/0/): - fail_gt_reset - GT reset failure - inject_csc_hw_error - CSC firmware error Signed-off-by: Mallesh Koujalagi Reviewed-by: Riana Tauro Link: https://patch.msgid.link/20260715085159.424040-2-mallesh.koujalagi@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_debugfs.c | 54 ++++++++++++++++++++++++++++++-- drivers/gpu/drm/xe/xe_debugfs.h | 6 ++++ drivers/gpu/drm/xe/xe_gt.c | 5 +-- drivers/gpu/drm/xe/xe_gt.h | 8 ----- drivers/gpu/drm/xe/xe_hw_error.c | 11 ++----- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c index 8c391c7b017a..5a3877fcb0f0 100644 --- a/drivers/gpu/drm/xe/xe_debugfs.c +++ b/drivers/gpu/drm/xe/xe_debugfs.c @@ -5,6 +5,7 @@ #include "xe_debugfs.h" +#include #include #include #include @@ -42,6 +43,55 @@ DECLARE_FAULT_ATTR(gt_reset_failure); DECLARE_FAULT_ATTR(inject_csc_hw_error); +static bool csc_hw_error_available(struct xe_device *xe) +{ + return !IS_SRIOV_VF(xe) && xe->info.platform == XE_BATTLEMAGE; +} + +/* + * Fault injection table. Each entry registers a debugfs attribute; add a + * matching FAULT_ACTION() below for every entry added here. + */ +static struct { + const char *name; + struct fault_attr *attr; + bool (*is_visible)(struct xe_device *xe); +} xe_fault_inject_entry[] = { + { .name = "fail_gt_reset", + .attr = >_reset_failure }, + { .name = "inject_csc_hw_error", + .attr = &inject_csc_hw_error, + .is_visible = csc_hw_error_available }, +}; + +/* + * FAULT_ACTION(name, fault_attr) - generate xe_fault_() accessor. + * Add one entry per row in xe_fault_inject_entry[]. + */ +#define FAULT_ACTION(name, fault_attr) \ +bool xe_fault_##name(void) \ +{ \ + return should_fail(&(fault_attr), 1); \ +} + +FAULT_ACTION(gt_reset, gt_reset_failure) +FAULT_ACTION(csc_hw_error, inject_csc_hw_error) + +static void xe_fault_inject_debugfs_register(struct xe_device *xe, + struct dentry *root) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(xe_fault_inject_entry); i++) { + if (xe_fault_inject_entry[i].is_visible && + !xe_fault_inject_entry[i].is_visible(xe)) + continue; + + fault_create_debugfs_attr(xe_fault_inject_entry[i].name, root, + xe_fault_inject_entry[i].attr); + } +} + static void read_residency_counter(struct xe_device *xe, struct xe_mmio *mmio, u32 offset, const char *name, struct drm_printer *p) { @@ -583,8 +633,6 @@ void xe_debugfs_register(struct xe_device *xe) drm_debugfs_create_files(debugfs_residencies, ARRAY_SIZE(debugfs_residencies), root, minor); - fault_create_debugfs_attr("inject_csc_hw_error", root, - &inject_csc_hw_error); } /* @@ -642,7 +690,7 @@ void xe_debugfs_register(struct xe_device *xe) xe_psmi_debugfs_register(xe); - fault_create_debugfs_attr("fail_gt_reset", root, >_reset_failure); + xe_fault_inject_debugfs_register(xe, root); if (IS_SRIOV_PF(xe)) xe_sriov_pf_debugfs_register(xe, root); diff --git a/drivers/gpu/drm/xe/xe_debugfs.h b/drivers/gpu/drm/xe/xe_debugfs.h index 17f4c2f1b5e4..cd56f7442b99 100644 --- a/drivers/gpu/drm/xe/xe_debugfs.h +++ b/drivers/gpu/drm/xe/xe_debugfs.h @@ -6,11 +6,17 @@ #ifndef _XE_DEBUGFS_H_ #define _XE_DEBUGFS_H_ +#include + struct xe_device; #ifdef CONFIG_DEBUG_FS +bool xe_fault_gt_reset(void); +bool xe_fault_csc_hw_error(void); void xe_debugfs_register(struct xe_device *xe); #else +static inline bool xe_fault_gt_reset(void) { return false; } +static inline bool xe_fault_csc_hw_error(void) { return false; } static inline void xe_debugfs_register(struct xe_device *xe) { } #endif diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c index d904527a8898..dfdacc0f6de9 100644 --- a/drivers/gpu/drm/xe/xe_gt.c +++ b/drivers/gpu/drm/xe/xe_gt.c @@ -21,6 +21,7 @@ #include "regs/xe_gt_regs.h" #include "xe_assert.h" #include "xe_bb.h" +#include "xe_debugfs.h" #include "xe_device.h" #include "xe_eu_stall.h" #include "xe_exec_queue.h" @@ -926,7 +927,7 @@ static void gt_reset_worker(struct work_struct *w) xe_gt_info(gt, "reset started\n"); - if (xe_fault_inject_gt_reset()) { + if (xe_fault_gt_reset()) { err = -ECANCELED; goto err_fail; } @@ -986,7 +987,7 @@ void xe_gt_reset_async(struct xe_gt *gt) return; /* Don't do a reset while one is already in flight */ - if (!xe_fault_inject_gt_reset() && xe_uc_reset_prepare(>->uc)) + if (!xe_fault_gt_reset() && xe_uc_reset_prepare(>->uc)) return; xe_gt_info(gt, "reset queued from %ps\n", __builtin_return_address(0)); diff --git a/drivers/gpu/drm/xe/xe_gt.h b/drivers/gpu/drm/xe/xe_gt.h index a6cfaa1af23f..65a4655b0994 100644 --- a/drivers/gpu/drm/xe/xe_gt.h +++ b/drivers/gpu/drm/xe/xe_gt.h @@ -6,8 +6,6 @@ #ifndef _XE_GT_H_ #define _XE_GT_H_ -#include - #include #include "xe_device.h" @@ -38,12 +36,6 @@ xe_gt_is_media_type(gt_) ? MEDIA_VER(xe) : GRAPHICS_VER(xe); \ }) -extern struct fault_attr gt_reset_failure; -static inline bool xe_fault_inject_gt_reset(void) -{ - return IS_ENABLED(CONFIG_DEBUG_FS) && should_fail(>_reset_failure, 1); -} - struct xe_gt *xe_gt_alloc(struct xe_tile *tile); int xe_gt_init_early(struct xe_gt *gt); int xe_gt_init(struct xe_gt *gt); diff --git a/drivers/gpu/drm/xe/xe_hw_error.c b/drivers/gpu/drm/xe/xe_hw_error.c index 4a4b363fc844..5f2abc9485ff 100644 --- a/drivers/gpu/drm/xe/xe_hw_error.c +++ b/drivers/gpu/drm/xe/xe_hw_error.c @@ -4,12 +4,12 @@ */ #include -#include #include "regs/xe_gsc_regs.h" #include "regs/xe_hw_error_regs.h" #include "regs/xe_irq_regs.h" +#include "xe_debugfs.h" #include "xe_device.h" #include "xe_drm_ras.h" #include "xe_hw_error.h" @@ -25,8 +25,6 @@ (PVC_COR_ERR_MASK & REG_BIT(err_bit)) : \ (PVC_FAT_ERR_MASK & REG_BIT(err_bit))) -extern struct fault_attr inject_csc_hw_error; - static const char * const error_severity[] = DRM_XE_RAS_ERROR_SEVERITY_NAMES; static const char * const hec_uncorrected_fw_errors[] = { @@ -167,11 +165,6 @@ static_assert(ARRAY_SIZE(pvc_master_local_nonfatal_err_reg) == XE_RAS_REG_SIZE); pvc_master_local_fatal_err_reg : \ pvc_master_local_nonfatal_err_reg) -static bool fault_inject_csc_hw_error(void) -{ - return IS_ENABLED(CONFIG_DEBUG_FS) && should_fail(&inject_csc_hw_error, 1); -} - static void csc_hw_error_work(struct work_struct *work) { struct xe_tile *tile = container_of(work, typeof(*tile), csc_hw_error_work); @@ -517,7 +510,7 @@ void xe_hw_error_irq_handler(struct xe_tile *tile, const u32 master_ctl) { enum hardware_error hw_err; - if (fault_inject_csc_hw_error()) + if (xe_fault_csc_hw_error()) schedule_work(&tile->csc_hw_error_work); for (hw_err = 0; hw_err < HARDWARE_ERROR_MAX; hw_err++) { From 4ceaf979fc68d7c25aa91a828ae745ceb86da570 Mon Sep 17 00:00:00 2001 From: Niranjana Vishwanathapura Date: Wed, 15 Jul 2026 21:58:16 -0700 Subject: [PATCH 57/81] drm/xe/multi_queue: wait for secondary's own suspend in suspend_wait For a multi-queue group secondary, guc_exec_queue_suspend_wait() (and its blocking variant) only waited on the primary's suspend, on the assumption that the secondary's suspend is synchronous. It is not: the secondary's suspend rides the sched-message worker (short-circuited, no GuC round-trip) and completes asynchronously. When the primary was already suspended the forward is a refcount-only transition that queues no new primary SUSPEND and leaves the primary's suspend_pending clear, so the wait returned immediately while the secondary's own suspend was still in flight. A subsequent resume() then tripped the secondary's !suspend_pending assert. Wait for the secondary's own suspend to complete before waiting on the primary. On a timeout, ban the queue (which tears down the group) rather than leave it with suspend_pending set - otherwise the preempt-fence and hw-engine-group resume paths would resume it and hit the assert. Factor the per-queue wait into guc_exec_queue_wait_suspend_done() and share the orchestration between suspend_wait() and suspend_wait_blocking() via guc_exec_queue_suspend_wait_common(). Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260716045815.2315470-2-niranjana.vishwanathapura@intel.com --- drivers/gpu/drm/xe/xe_guc_submit.c | 130 ++++++++++++++++------------- 1 file changed, 72 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index c70c77141a74..352f101b221f 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -2340,25 +2340,23 @@ static void guc_exec_queue_suspend_timeout_ban(struct xe_exec_queue *q) } } -static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) +/* + * Wait for @q's own suspend to complete: suspend_pending cleared, or the queue + * killed / GuC stopped. With @blocking, wait uninterruptibly and do not handle + * VF recovery (for callers that must complete on behalf of a possibly + * cross-process queue); otherwise wait interruptibly. + * + * Returns 0 on completion or -ETIME on timeout. Interruptible waits may also + * return -EAGAIN (VF recovery in progress, retry) or -ERESTARTSYS (aborted by a + * signal; suspend_pending may still be set, so callers must not resume() + * without re-confirming the suspend). + */ +static int guc_exec_queue_wait_suspend_done(struct xe_exec_queue *q, bool blocking) { struct xe_guc *guc = exec_queue_to_guc(q); struct xe_device *xe = guc_to_xe(guc); int ret; - /* - * In multi-queue mode the primary owns the GuC scheduling context for - * the whole group, so wait on the primary's suspend to complete. All - * group members share the same GuC/device, so guc, xe and timeout above - * are computed from @q directly. - * - * A secondary's suspend is short-circuited (no GuC round-trip) and, as - * its SUSPEND message precedes the primary's on the shared FIFO - * submit_wq, completes before the primary's. So waiting on the primary - * is sufficient. - */ - q = xe_exec_queue_multi_queue_primary(q); - /* * Likely don't need to check exec_queue_killed() as we clear * suspend_pending upon kill but to be paranoid but races in which @@ -2369,73 +2367,89 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) xe_guc_read_stopped(guc)) retry: - if (IS_SRIOV_VF(xe)) + if (blocking) { + if (IS_SRIOV_VF(xe)) + ret = wait_event_timeout(guc->ct.wq, WAIT_COND, HZ * 5); + else + ret = wait_event_timeout(q->guc->suspend_wait, WAIT_COND, + HZ * 5); + } else if (IS_SRIOV_VF(xe)) { ret = wait_event_interruptible_timeout(guc->ct.wq, WAIT_COND || - vf_recovery(guc), - HZ * 5); - else + vf_recovery(guc), HZ * 5); + } else { ret = wait_event_interruptible_timeout(q->guc->suspend_wait, WAIT_COND, HZ * 5); + } - if (vf_recovery(guc) && !xe_device_wedged((guc_to_xe(guc)))) + if (!blocking && vf_recovery(guc) && !xe_device_wedged(xe)) return -EAGAIN; - if (!ret) { - guc_exec_queue_suspend_timeout_ban(q); + if (!ret) return -ETIME; - } else if (IS_SRIOV_VF(xe) && !WAIT_COND) { + else if (!blocking && IS_SRIOV_VF(xe) && !WAIT_COND) /* Corner case on RESFIX DONE where vf_recovery() changes */ goto retry; - } #undef WAIT_COND - /* - * ret < 0 (-ERESTARTSYS): the interruptible wait was aborted by a - * signal. The queue is not banned - the failure is in the waiter, not - * the queue. The suspend is not confirmed complete, so suspend_pending - * may still be set; callers must not resume() on this error without - * re-confirming the suspend. - */ return ret < 0 ? ret : 0; } -static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q) +static int guc_exec_queue_suspend_wait_common(struct xe_exec_queue *q, bool blocking) { - struct xe_guc *guc = exec_queue_to_guc(q); - struct xe_device *xe = guc_to_xe(guc); int ret; /* - * Uninterruptible variant of guc_exec_queue_suspend_wait() for callers - * that must complete the wait on behalf of a queue possibly owned by a - * different process (e.g. cleanup/undo paths). An interruptible wait - * could return -ERESTARTSYS if the calling task is signalled, leaving - * that queue suspended forever (cross-process DoS). + * A secondary's suspend rides the sched-message worker (short-circuited, + * no GuC round-trip) and so is not synchronous with + * guc_exec_queue_suspend(): its own suspend_pending may still be set + * here. Waiting on the primary alone is not sufficient - if the primary + * was already suspended, the forward is a refcount-only transition that + * queues no new primary SUSPEND and leaves the primary's suspend_pending + * clear, so the primary wait would return immediately while the + * secondary's suspend is still in flight, and a later resume() would trip + * the secondary's !suspend_pending assert. So first wait for the + * secondary's own suspend to complete, then wait on the primary. * - * A timeout is still a real per-queue fault, so it bans and cleans up - * like suspend_wait(). VF recovery is deliberately not handled (no - * -EAGAIN) since a blocking caller cannot retry. + * A timeout on either bans the queue (being multi-queue, that tears down + * the whole group). A secondary suspend has no real GuC round-trip, so + * its timeout is a software scheduler stall rather than a GuC fault, but + * banning is still the safe recovery: otherwise the queue is left with + * suspend_pending set and a subsequent resume() trips the !suspend_pending + * assert. */ - q = xe_exec_queue_multi_queue_primary(q); - -#define WAIT_COND \ - (!READ_ONCE(q->guc->suspend_pending) || exec_queue_killed(q) || \ - xe_guc_read_stopped(guc)) - - if (IS_SRIOV_VF(xe)) - ret = wait_event_timeout(guc->ct.wq, WAIT_COND, HZ * 5); - else - ret = wait_event_timeout(q->guc->suspend_wait, WAIT_COND, HZ * 5); - -#undef WAIT_COND - - if (!ret) { - guc_exec_queue_suspend_timeout_ban(q); - return -ETIME; + if (xe_exec_queue_is_multi_queue_secondary(q)) { + ret = guc_exec_queue_wait_suspend_done(q, blocking); + if (ret == -ETIME) + guc_exec_queue_suspend_timeout_ban(q); + if (ret) + return ret; } - return 0; + q = xe_exec_queue_multi_queue_primary(q); + ret = guc_exec_queue_wait_suspend_done(q, blocking); + if (ret == -ETIME) + guc_exec_queue_suspend_timeout_ban(q); + + return ret; +} + +static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q) +{ + return guc_exec_queue_suspend_wait_common(q, false); +} + +/* + * Uninterruptible variant of guc_exec_queue_suspend_wait() for callers that + * must complete the wait on behalf of a queue possibly owned by a different + * process (e.g. cleanup/undo paths). An interruptible wait could return + * -ERESTARTSYS if the calling task is signalled, leaving that queue suspended + * forever (cross-process DoS). VF recovery is deliberately not handled (no + * -EAGAIN) since a blocking caller cannot retry. + */ +static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q) +{ + return guc_exec_queue_suspend_wait_common(q, true); } static void guc_exec_queue_resume(struct xe_exec_queue *q) From 41075f0eb5dcbd3b065d15f15ef7bbe9315188e8 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 14 Jul 2026 12:14:02 +0530 Subject: [PATCH 58/81] drm/xe/guc: Keep scheduler timeline name alive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheduler keeps a pointer to the timeline name, but q->name is freed with the exec queue while scheduler fences can still reference it. Store the name in struct xe_guc_exec_queue so it shares the scheduler's RCU-deferred lifetime. Fixes: 6bd90e700b42 ("drm/xe: Make dma-fences compliant with the safe access rules") Cc: Thomas Hellström Cc: Rodrigo Vivi Cc: Himal Prasad Ghimiray Cc: Matthew Brost Signed-off-by: Arvind Yadav Reviewed-by: Tvrtko Ursulin Acked-by: Matthew Brost Link: https://patch.msgid.link/20260714064402.2457257-1-arvind.yadav@intel.com Signed-off-by: Tejas Upadhyay --- drivers/gpu/drm/xe/xe_guc_exec_queue_types.h | 5 +++++ drivers/gpu/drm/xe/xe_guc_submit.c | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h index 1207d51cf770..acdc24d1a6bd 100644 --- a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h +++ b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h @@ -10,6 +10,7 @@ #include #include "xe_gpu_scheduler_types.h" +#include "xe_hw_fence_types.h" struct dma_fence; struct xe_exec_queue; @@ -24,6 +25,10 @@ struct xe_guc_exec_queue { struct rcu_head rcu; /** @sched: GPU scheduler for this xe_exec_queue */ struct xe_gpu_scheduler sched; + /** + * @name: Scheduler timeline name, kept with @sched until RCU free. + */ + char name[MAX_FENCE_NAME_LEN]; /** @entity: Scheduler entity for this xe_exec_queue */ struct xe_sched_entity entity; /** diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 352f101b221f..23f2aa950896 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -2003,6 +2003,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) xe_exec_queue_assign_name(q, q->guc->id); + strscpy(ge->name, q->name, sizeof(ge->name)); + /* * Use primary queue's submit_wq for all secondary queues of a * multi queue group. This serialization avoids any locking around @@ -2017,7 +2019,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) err = xe_sched_init(&ge->sched, &drm_sched_ops, &xe_sched_ops, submit_wq, xe_lrc_ring_size() / MAX_JOB_SIZE_BYTES, 64, timeout, guc_to_gt(guc)->ordered_wq, NULL, - q->name, gt_to_xe(q->gt)->drm.dev); + ge->name, gt_to_xe(q->gt)->drm.dev); if (err) goto err_release_id; From da1124abac689cc2b1d8995e5f0a816f8a122edb Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Thu, 16 Jul 2026 11:56:24 +0530 Subject: [PATCH 59/81] drm/xe/guc: Hold device ref until queue teardown completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GuC exec queue destruction can run asynchronously. If the final device put happens from a destroy worker, drmm cleanup can end up draining the same workqueue and deadlock. Hold a drm_device reference for the queue lifetime and drop it after queue teardown completes. This keeps drmm cleanup from running while async destroy work is still pending. Move GuC destroy work to a module-lifetime Xe workqueue and flush it on PCI remove so hot-unbind/rebind still waits for pending destroy work. With queue-held device refs, guc_submit_sw_fini() cannot run with live GuC IDs. Replace the fini wait with an assertion and remove the unused fini_wq. v2: - Rebase v3: - Switch to queue-lifetime drm_dev_get()/drm_dev_put() model. (Matt) - Queue async teardown on system_dfl_wq instead of xe->destroy_wq. (Matt) - Drop separate deferred drm_dev_put worker. - Remove stale drain_workqueue(xe->destroy_wq) from guc_submit_sw_fini(). v4: - Replace the guc_submit_sw_fini() wait with an assertion and remove the now-unused fini_wq. (sashiko) v5: - Move destroy work to a module-lifetime Xe workqueue instead of system_dfl_wq. (Matt) - Flush the module-lifetime destroy workqueue during PCI remove to preserve the old device-remove wait semantics. v6: - Keep SVM pagemap destroy work on the per-device destroy_wq to avoid letting it outlive the xe_device/drm_device. (Sashiko) - Use WQ_MEM_RECLAIM for xe->destroy_wq because SVM pagemap destroy work can be queued from the reclaim path. v7: - Drop the per-device xe->destroy_wq and use the module-level destroy WQ for SVM pagemap destroy as well. (Matt) - Rename xe_exec_queue_destroy_wq_*() helpers to xe_destroy_wq_*() helpers because the WQ is no longer exec-queue specific. (Matt) v8: - Rebase. v9: - Keep SVM pagemap destroy work on the per-device WQ_MEM_RECLAIM destroy_wq because it can be queued from reclaim and embeds the dev_pagemap used by devres teardown. (Sashiko) - Keep the module-level destroy WQ GuC-only and drop WQ_MEM_RECLAIM from it. - Update the module-WQ kdoc to document the GuC/SVM split. v10: - Keep xe->destroy_wq per-cpu while adding WQ_MEM_RECLAIM to fix the workqueue allocation warning. v11: - Drop the SVM pagemap destroy comment as it was revision-specific. (Thomas) v12: - Rebase. Fixes: 2d2be279f1ca ("drm/xe: fix UAF around queue destruction") Cc: Thomas Hellström Cc: Rodrigo Vivi Cc: Himal Prasad Ghimiray Cc: Tejas Upadhyay Reviewed-by: Matthew Brost Signed-off-by: Arvind Yadav Link: https://patch.msgid.link/20260716062624.211396-1-arvind.yadav@intel.com Signed-off-by: Tejas Upadhyay --- drivers/gpu/drm/xe/xe_device.c | 2 +- drivers/gpu/drm/xe/xe_device_types.h | 2 +- drivers/gpu/drm/xe/xe_guc_submit.c | 66 ++++++++++++++++------------ drivers/gpu/drm/xe/xe_guc_types.h | 2 - drivers/gpu/drm/xe/xe_module.c | 49 +++++++++++++++++++++ drivers/gpu/drm/xe/xe_module.h | 5 +++ drivers/gpu/drm/xe/xe_pci.c | 6 +++ 7 files changed, 100 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 785222d54701..4eed9a251e65 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -579,7 +579,7 @@ int xe_device_init_early(struct xe_device *xe) WQ_MEM_RECLAIM); xe->ordered_wq = alloc_ordered_workqueue("xe-ordered-wq", 0); xe->unordered_wq = alloc_workqueue("xe-unordered-wq", WQ_PERCPU, 0); - xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU, 0); + xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU | WQ_MEM_RECLAIM, 0); if (!xe->ordered_wq || !xe->unordered_wq || !xe->preempt_fence_wq || !xe->destroy_wq) { /* diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h index 022e08205897..56c17cca79c0 100644 --- a/drivers/gpu/drm/xe/xe_device_types.h +++ b/drivers/gpu/drm/xe/xe_device_types.h @@ -355,7 +355,7 @@ struct xe_device { /** @unordered_wq: used to serialize unordered work */ struct workqueue_struct *unordered_wq; - /** @destroy_wq: used to serialize user destroy work, like queue */ + /** @destroy_wq: used to serialize SVM pagemap destroy work */ struct workqueue_struct *destroy_wq; /** @tiles: device tiles */ diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c index 23f2aa950896..8aaed4fd13ea 100644 --- a/drivers/gpu/drm/xe/xe_guc_submit.c +++ b/drivers/gpu/drm/xe/xe_guc_submit.c @@ -10,6 +10,7 @@ #include #include +#include #include #include "abi/guc_actions_abi.h" @@ -37,6 +38,7 @@ #include "xe_macros.h" #include "xe_map.h" #include "xe_mocs.h" +#include "xe_module.h" #include "xe_pm.h" #include "xe_ring_ops_types.h" #include "xe_sched_job.h" @@ -232,17 +234,9 @@ static bool exec_queue_killed_or_banned_or_wedged(struct xe_exec_queue *q) static void guc_submit_sw_fini(struct drm_device *drm, void *arg) { struct xe_guc *guc = arg; - struct xe_device *xe = guc_to_xe(guc); struct xe_gt *gt = guc_to_gt(guc); - int ret; - ret = wait_event_timeout(guc->submission_state.fini_wq, - xa_empty(&guc->submission_state.exec_queue_lookup), - HZ * 5); - - drain_workqueue(xe->destroy_wq); - - xe_gt_assert(gt, ret); + xe_gt_assert(gt, xa_empty(&guc->submission_state.exec_queue_lookup)); xa_destroy(&guc->submission_state.exec_queue_lookup); } @@ -319,8 +313,6 @@ int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids) xa_init(&guc->submission_state.exec_queue_lookup); - init_waitqueue_head(&guc->submission_state.fini_wq); - primelockdep(guc); guc->submission_state.initialized = true; @@ -411,9 +403,6 @@ static void __release_guc_id(struct xe_guc *guc, struct xe_exec_queue *q, xe_guc_id_mgr_release_locked(&guc->submission_state.idm, q->guc->id, q->width); - if (xa_empty(&guc->submission_state.exec_queue_lookup)) - wake_up(&guc->submission_state.fini_wq); - mutex_unlock(&guc->submission_state.lock); } @@ -1711,6 +1700,7 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q) { struct xe_guc_exec_queue *ge = q->guc; struct xe_guc *guc = exec_queue_to_guc(q); + struct drm_device *drm = &guc_to_xe(guc)->drm; /* * A secondary can leave the group while still preempt suspended (e.g. @@ -1751,36 +1741,52 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q) * (timeline name). */ kfree_rcu(ge, rcu); + + drm_dev_put(drm); +} + +static void guc_exec_queue_do_destroy(struct xe_exec_queue *q) +{ + struct xe_guc_exec_queue *ge = q->guc; + struct xe_guc *guc = exec_queue_to_guc(q); + struct xe_device *xe = guc_to_xe(guc); + struct drm_device *drm = &xe->drm; + + /* + * guc_exec_queue_fini() drops the queue's drm_device ref. + * Keep the device alive until the PM-runtime guard unwinds. + */ + drm_dev_get(drm); + + scoped_guard(xe_pm_runtime, xe) { + trace_xe_exec_queue_destroy(q); + + /* Confirm no work left behind accessing device structures */ + cancel_delayed_work_sync(&ge->sched.base.work_tdr); + + xe_exec_queue_fini(q); + } + + drm_dev_put(drm); } static void __guc_exec_queue_destroy_async(struct work_struct *w) { struct xe_guc_exec_queue *ge = container_of(w, struct xe_guc_exec_queue, destroy_async); - struct xe_exec_queue *q = ge->q; - struct xe_guc *guc = exec_queue_to_guc(q); - guard(xe_pm_runtime)(guc_to_xe(guc)); - trace_xe_exec_queue_destroy(q); - - /* Confirm no work left behind accessing device structures */ - cancel_delayed_work_sync(&ge->sched.base.work_tdr); - - xe_exec_queue_fini(q); + guc_exec_queue_do_destroy(ge->q); } static void guc_exec_queue_destroy_async(struct xe_exec_queue *q) { - struct xe_guc *guc = exec_queue_to_guc(q); - struct xe_device *xe = guc_to_xe(guc); - INIT_WORK(&q->guc->destroy_async, __guc_exec_queue_destroy_async); /* We must block on kernel engines so slabs are empty on driver unload */ if (q->flags & EXEC_QUEUE_FLAG_PERMANENT || exec_queue_wedged(q)) - __guc_exec_queue_destroy_async(&q->guc->destroy_async); + guc_exec_queue_do_destroy(q); else - queue_work(xe->destroy_wq, &q->guc->destroy_async); + xe_destroy_wq_queue(&q->guc->destroy_async); } static void __guc_exec_queue_destroy(struct xe_guc *guc, struct xe_exec_queue *q) @@ -1975,6 +1981,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) { struct xe_gpu_scheduler *sched; struct xe_guc *guc = exec_queue_to_guc(q); + struct drm_device *drm = &guc_to_xe(guc)->drm; struct workqueue_struct *submit_wq = NULL; struct xe_guc_exec_queue *ge; long timeout; @@ -1986,6 +1993,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) if (!ge) return -ENOMEM; + drm_dev_get(drm); + q->guc = ge; ge->q = q; init_rcu_head(&ge->rcu); @@ -2064,6 +2073,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q) release_guc_id(guc, q); err_free: kfree(ge); + drm_dev_put(drm); return err; } diff --git a/drivers/gpu/drm/xe/xe_guc_types.h b/drivers/gpu/drm/xe/xe_guc_types.h index c7b9642b41ba..31a2acb63ac3 100644 --- a/drivers/gpu/drm/xe/xe_guc_types.h +++ b/drivers/gpu/drm/xe/xe_guc_types.h @@ -100,8 +100,6 @@ struct xe_guc { * even initialized - before that not even the lock is valid */ bool initialized; - /** @submission_state.fini_wq: submit fini wait queue */ - wait_queue_head_t fini_wq; } submission_state; /** @hwconfig: Hardware config state */ diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c index 39e4fc85f019..848d65265443 100644 --- a/drivers/gpu/drm/xe/xe_module.c +++ b/drivers/gpu/drm/xe/xe_module.c @@ -7,6 +7,7 @@ #include #include +#include #include @@ -88,6 +89,50 @@ static int xe_check_nomodeset(void) return 0; } +static struct workqueue_struct *xe_destroy_wq; + +static int __init xe_destroy_wq_module_init(void) +{ + xe_destroy_wq = alloc_workqueue("xe-guc-destroy-wq", WQ_UNBOUND, 0); + if (!xe_destroy_wq) + return -ENOMEM; + return 0; +} + +static void xe_destroy_wq_module_exit(void) +{ + if (xe_destroy_wq) + destroy_workqueue(xe_destroy_wq); + xe_destroy_wq = NULL; +} + +/** + * xe_destroy_wq_queue() - Queue work on the destroy workqueue + * @work: work item to queue + * + * The destroy workqueue has module lifetime and is used for GuC exec queue + * teardown that can outlive a single xe_device. SVM pagemap destroy uses the + * per-device xe->destroy_wq instead. + * + * Return: %true if @work was queued, %false if it was already pending. + */ +bool xe_destroy_wq_queue(struct work_struct *work) +{ + return queue_work(xe_destroy_wq, work); +} + +/** + * xe_destroy_wq_flush() - Flush the destroy workqueue + * + * Drains all pending destroy work. Called from PCI remove to ensure + * teardown ordering before the device is destroyed. + */ +void xe_destroy_wq_flush(void) +{ + if (xe_destroy_wq) + flush_workqueue(xe_destroy_wq); +} + struct init_funcs { int (*init)(void); void (*exit)(void); @@ -109,6 +154,10 @@ static const struct init_funcs init_funcs[] = { .init = xe_sched_job_module_init, .exit = xe_sched_job_module_exit, }, + { + .init = xe_destroy_wq_module_init, + .exit = xe_destroy_wq_module_exit, + }, { .init = xe_register_pci_driver, .exit = xe_unregister_pci_driver, diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h index c75153471248..a0eb7db07770 100644 --- a/drivers/gpu/drm/xe/xe_module.h +++ b/drivers/gpu/drm/xe/xe_module.h @@ -8,6 +8,8 @@ #include +struct work_struct; + /* Module modprobe variables */ struct xe_modparam { bool probe_display; @@ -26,5 +28,8 @@ struct xe_modparam { extern struct xe_modparam xe_modparam; +bool xe_destroy_wq_queue(struct work_struct *work); +void xe_destroy_wq_flush(void); + #endif diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 2af52a7ef970..691b68c69a2f 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -1118,6 +1118,12 @@ static void xe_pci_remove(struct pci_dev *pdev) return; xe_device_remove(xe); + + /* + * Preserve remove-time flush after moving destroy work to module + * lifetime. + */ + xe_destroy_wq_flush(); xe_pm_fini(xe); } From 015face96548063e16dfe4dd35ca78355a0f3479 Mon Sep 17 00:00:00 2001 From: Jagmeet Randhawa Date: Fri, 17 Jul 2026 05:47:45 +0800 Subject: [PATCH 60/81] drm/xe/multi_queue: Reject PXP usage on multi-queue exec queues HWDRM is currently the only supported PXP type, and it is display related, so it cannot be combined with multi-queue exec queue groups. Reject exec queue creation that requests both multi-queue and PXP, returning -EINVAL. The secondary queue path already rejects any PXP property, so this adds the missing check for the multi-queue primary, which would otherwise allow the combination. Validated with igt@xe_exec_multi_queue@sanity, which exercises both the PXP-unsupported (-ENODEV) and PXP-supported (-EINVAL) paths. v3: - Change commit title prefix to drm/xe/multi_queue:. - Add Niranjana's Reviewed-by. v2: - Move the multi-queue + PXP check to exec_queue_user_ext_check() to bail out early, keyed off the properties bitmask (Niranjana). Signed-off-by: Jagmeet Randhawa Reviewed-by: Niranjana Vishwanathapura Signed-off-by: Niranjana Vishwanathapura Link: https://patch.msgid.link/4d369249d52384bc93663055a3757a50614ebbfd.1784238312.git.jagmeet.randhawa@intel.com --- drivers/gpu/drm/xe/xe_exec_queue.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c index f4b297a19218..38972b6e6d37 100644 --- a/drivers/gpu/drm/xe/xe_exec_queue.c +++ b/drivers/gpu/drm/xe/xe_exec_queue.c @@ -1054,6 +1054,7 @@ int xe_exec_queue_set_property_ioctl(struct drm_device *dev, void *data, static int exec_queue_user_ext_check(struct xe_exec_queue *q, u64 properties) { + struct xe_device *xe = gt_to_xe(q->gt); u64 secondary_queue_valid_props = BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_GROUP) | BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_QUEUE_PRIORITY); @@ -1065,6 +1066,16 @@ static int exec_queue_user_ext_check(struct xe_exec_queue *q, u64 properties) properties & ~secondary_queue_valid_props) return -EINVAL; + /* + * HWDRM is the only supported PXP type today. It is display related and + * hence can't work with multi-queue. Reject the combination. The secondary + * queue path above already rejects any PXP property, so this also covers + * the multi-queue primary which would otherwise allow it. + */ + if (XE_IOCTL_DBG(xe, (properties & BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_GROUP)) && + (properties & BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_PXP_TYPE)))) + return -EINVAL; + return 0; } From 53a7115f9862f30ee748c1e1c5e6398ffa092672 Mon Sep 17 00:00:00 2001 From: Soham Purkait Date: Thu, 16 Jul 2026 13:06:02 +0530 Subject: [PATCH 61/81] drm/xe/xe_ras: Add RAS GPU health indicator Add a sysfs interface that reports the current GPU health state and lets admin users and management tools update it but is readable by all users. Requests are routed through the sysctrl mailbox. The interface is present only on platforms that support the GPU health indicator. The interface is a single read/write file at the device level: $ cat /sys/.../device/gpu_health ok $ echo critical > /sys/.../device/gpu_health $ cat /sys/.../device/gpu_health critical Signed-off-by: Soham Purkait Acked-by: Rodrigo Vivi Acked-by: Raag Jadav Reviewed-by: Andi Shyti Reviewed-by: Badal Nilawar Link: https://patch.msgid.link/20260716073600.674089-4-soham.purkait@intel.com Signed-off-by: Riana Tauro --- .../ABI/testing/sysfs-driver-intel-xe-ras | 30 ++++ Documentation/gpu/xe/xe_device.rst | 7 + drivers/gpu/drm/xe/xe_ras.c | 153 ++++++++++++++++++ drivers/gpu/drm/xe/xe_ras_types.h | 41 +++++ drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h | 4 + 5 files changed, 235 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-intel-xe-ras diff --git a/Documentation/ABI/testing/sysfs-driver-intel-xe-ras b/Documentation/ABI/testing/sysfs-driver-intel-xe-ras new file mode 100644 index 000000000000..3870e5a03a92 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intel-xe-ras @@ -0,0 +1,30 @@ +What: /sys/bus/pci/drivers/xe/.../gpu_health +Date: July 2026 +KernelVersion: 7.3 +Contact: intel-xe@lists.freedesktop.org +Description: + This file exposes the current gpu health state and allows the gpu + health state to be updated. + + This sysfs file is present only on Intel Xe platforms that support + the gpu health indicator interface for RAS. Reading the current + health state is available to all users, while updating the health + state is restricted to administrative users only. + + Read returns a single line containing one of the valid values for + the current gpu health state. Writing one of the valid values + updates the current gpu health state. + + The valid values for the gpu health state are: + + ok + The gpu is healthy and operating within normal + parameters. + + warning + The gpu is experiencing minor issues but remains + operational. + + critical + The gpu is in a critical state and may not be + operational. diff --git a/Documentation/gpu/xe/xe_device.rst b/Documentation/gpu/xe/xe_device.rst index 39a937b97cd3..d3a022362ade 100644 --- a/Documentation/gpu/xe/xe_device.rst +++ b/Documentation/gpu/xe/xe_device.rst @@ -8,3 +8,10 @@ Xe Device Wedging .. kernel-doc:: drivers/gpu/drm/xe/xe_device.c :doc: Xe Device Wedging + +==================== +GPU Health Indicator +==================== + +.. kernel-doc:: drivers/gpu/drm/xe/xe_ras.c + :doc: GPU Health Indicator diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c index 845b0e99754c..ed609912fda1 100644 --- a/drivers/gpu/drm/xe/xe_ras.c +++ b/drivers/gpu/drm/xe/xe_ras.c @@ -55,6 +55,14 @@ enum xe_ras_response_status { XE_RAS_STATUS_MAX }; +/* GPU health values */ +enum xe_ras_health { + XE_RAS_HEALTH_OK = 0, + XE_RAS_HEALTH_WARNING, + XE_RAS_HEALTH_CRITICAL, + XE_RAS_HEALTH_MAX +}; + static const char *const xe_ras_severities[] = { [XE_RAS_SEV_NOT_SUPPORTED] = "Not Supported", [XE_RAS_SEV_CORRECTABLE] = "Correctable Error", @@ -74,6 +82,13 @@ static const char *const xe_ras_components[] = { }; static_assert(ARRAY_SIZE(xe_ras_components) == XE_RAS_COMP_MAX); +static const char * const gpu_health_states[] = { + [XE_RAS_HEALTH_OK] = "ok", + [XE_RAS_HEALTH_WARNING] = "warning", + [XE_RAS_HEALTH_CRITICAL] = "critical", +}; +static_assert(ARRAY_SIZE(gpu_health_states) == XE_RAS_HEALTH_MAX); + static u8 drm_to_xe_ras_severity(u8 severity) { switch (severity) { @@ -446,6 +461,139 @@ int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component) return 0; } +static ssize_t gpu_health_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct xe_ras_get_health_response response = {0}; + struct xe_sysctrl_mailbox_command command = {0}; + struct xe_ras_get_health_request request = {0}; + struct xe_device *xe = kdev_to_xe_device(dev); + const char *health; + size_t rlen; + int ret; + + xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_GET_HEALTH, + &request, sizeof(request), &response, sizeof(response)); + guard(xe_pm_runtime)(xe); + ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen); + if (ret) { + xe_err(xe, "sysctrl: failed to get health %d\n", ret); + return ret; + } + + if (rlen != sizeof(response)) { + xe_err(xe, "sysctrl: unexpected get health response length %zu (expected %zu)\n", + rlen, sizeof(response)); + return -EIO; + } + if (response.health >= XE_RAS_HEALTH_MAX) { + xe_err(xe, "sysctrl: invalid health state %u\n", + response.health); + return -EIO; + } + + health = gpu_health_states[response.health]; + + xe_dbg(xe, "[RAS]: get health: %s\n", health); + + return sysfs_emit(buf, "%s\n", health); +} + +static ssize_t gpu_health_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct xe_ras_set_health_response response = {0}; + struct xe_sysctrl_mailbox_command command = {0}; + struct xe_ras_set_health_request request = {0}; + struct xe_device *xe = kdev_to_xe_device(dev); + const char *health; + size_t rlen; + int state; + int ret; + + state = sysfs_match_string(gpu_health_states, buf); + if (state < 0) + return -EINVAL; + + request.health = state; + + xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_SET_HEALTH, + &request, sizeof(request), &response, sizeof(response)); + guard(xe_pm_runtime)(xe); + ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen); + if (ret) { + xe_err(xe, "sysctrl: failed to set health %d\n", ret); + return ret; + } + + if (rlen != sizeof(response)) { + xe_err(xe, "sysctrl: unexpected set health response length %zu (expected %zu)\n", + rlen, sizeof(response)); + return -EIO; + } + + ret = ras_status_to_errno(response.status); + if (ret) { + xe_err(xe, "sysctrl: set health command failed with status %#x\n", + response.status); + return ret; + } + + if (response.health >= XE_RAS_HEALTH_MAX) { + xe_err(xe, "sysctrl: invalid health state %u\n", + response.health); + return -EIO; + } + + health = gpu_health_states[response.health]; + + xe_dbg(xe, "[RAS]: set health: %s\n", health); + + return count; +} +static DEVICE_ATTR_RW(gpu_health); + +static struct attribute *gpu_health_attrs[] = { + &dev_attr_gpu_health.attr, + NULL +}; + +/** + * DOC: GPU Health Indicator + * + * On Intel Xe platforms that support the gpu health indicator interface, + * the driver exposes this sysfs attribute for in-band access to the gpu + * health state:: + * + * /sys/bus/pci/devices//gpu_health + * + * Reading the attribute is available to all users and returns a single + * line containing the current gpu health state, whereas writing is + * restricted to administrative users and updates the state to one of the + * valid values. + * + * Management tools and administrators use this interface to query the + * current gpu health state (e.g. for telemetry/monitoring) and to + * update it - for example, to mark the gpu as ``warning`` or ``critical`` + * after diagnostics, or reset it back to ``ok`` once remediated. + * + * The valid values for the gpu health state are: + * + * - ``ok`` + * The gpu is healthy and operating within normal parameters. + * + * - ``warning`` + * The gpu is experiencing minor issues but remains operational. + * + * - ``critical`` + * The gpu is in a critical state and may not be operational. + * + * See Documentation/ABI/testing/sysfs-driver-intel-xe-ras for the ABI + * specification. + */ +static const struct attribute_group gpu_health_group = { + .attrs = gpu_health_attrs, +}; + /** * xe_ras_init - Initialize Xe RAS * @xe: xe device instance @@ -454,6 +602,8 @@ int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component) */ void xe_ras_init(struct xe_device *xe) { + int ret; + if (!xe->info.has_drm_ras) return; @@ -471,4 +621,7 @@ void xe_ras_init(struct xe_device *xe) * causing the driver to enter survivability mode. */ xe_ras_process_errors(xe); + ret = devm_device_add_group(xe->drm.dev, &gpu_health_group); + if (ret) + xe_err(xe, "Failed to create GPU health sysfs, err=%d\n", ret); } diff --git a/drivers/gpu/drm/xe/xe_ras_types.h b/drivers/gpu/drm/xe/xe_ras_types.h index 8d344691b549..766b4b41768e 100644 --- a/drivers/gpu/drm/xe/xe_ras_types.h +++ b/drivers/gpu/drm/xe/xe_ras_types.h @@ -177,4 +177,45 @@ struct xe_ras_compute_error { u32 reserved[15]; } __packed; +/** + * struct xe_ras_get_health_request - Request structure for obtaining gpu health + */ +struct xe_ras_get_health_request { + /** @reserved: Reserved for future use. */ + u32 reserved[2]; +} __packed; + +/** + * struct xe_ras_get_health_response - Response structure for obtaining gpu health + */ +struct xe_ras_get_health_response { + /** @health: gpu health value */ + u8 health; + /** @reserved: Reserved for future use */ + u8 reserved[3]; +} __packed; + +/** + * struct xe_ras_set_health_request - Request structure for setting gpu health + */ +struct xe_ras_set_health_request { + /** @health: gpu health value */ + u8 health; + /** @reserved: Reserved for future use */ + u8 reserved[3]; +} __packed; + +/** + * struct xe_ras_set_health_response - Response structure for setting gpu health + */ +struct xe_ras_set_health_response { + /** @status: Status of set health operation */ + u32 status; + /** @health: Resulting gpu health value */ + u8 health; + /** @reserved: Reserved for future use */ + u8 reserved[3]; + /** @reserved1: Reserved for future use */ + u32 reserved1[2]; +} __packed; #endif diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h index f12bc99ee31b..d0341538ad05 100644 --- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h +++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h @@ -26,12 +26,16 @@ enum xe_sysctrl_group { * @XE_SYSCTRL_CMD_GET_COUNTER: Get error counter value * @XE_SYSCTRL_CMD_CLEAR_COUNTER: Clear error counter value * @XE_SYSCTRL_CMD_GET_PENDING_EVENT: Retrieve pending event + * @XE_SYSCTRL_CMD_GET_HEALTH: Retrieve gpu health + * @XE_SYSCTRL_CMD_SET_HEALTH: Set gpu health */ enum xe_sysctrl_gfsp_cmd { XE_SYSCTRL_CMD_GET_SOC_ERROR = 0x01, XE_SYSCTRL_CMD_GET_COUNTER = 0x03, XE_SYSCTRL_CMD_CLEAR_COUNTER = 0x04, XE_SYSCTRL_CMD_GET_PENDING_EVENT = 0x07, + XE_SYSCTRL_CMD_GET_HEALTH = 0x0B, + XE_SYSCTRL_CMD_SET_HEALTH = 0x0C, }; /** From 21aa5bbe1bd7d0bb1b331274d59e2a121aed7ef1 Mon Sep 17 00:00:00 2001 From: Matt Roper Date: Thu, 16 Jul 2026 13:18:45 -0700 Subject: [PATCH 62/81] drm/xe/xe3p_lpg: Program TR_PTA_MODE Up until Xe3p_LPG, the PTA_MODE register controlled cacheability of accesses to the page tables for both PPGTT and TRTT. Starting with Xe3p_LPG, PTA_MODE is now only responsible for the PPGTT accesses, and a separate register, TR_PTA_MODE is used to control the TRTT accesses. The currently recommeded value for TR_PTA_MODE differs from PTA_MODE on Xe3p_LPG. Track and program this value separately in the driver. Note that even though the Xe3p_LP[G/M] IPs didn't add support for this new TR_PTA_MODE register until b-stepping, it's safe us to ignore that detail code-wise. Writes of the unrecognized registers on a-step hardware will be silently ignored, and the reads on a-step will come back as 0x0 which happens to be the value we'd be trying to program on these IP versions anyway (for both graphics and media). The new TRTT-specific register also does not exist on Xe3p_XPC platforms. v2: - Split gt_tr_pta_entry() out from gt_pta_entry(). (Gustavo) - Fix copy/paste mistake that caused us to write the PPGTT value to the TRTT register in the MCR path. (Sashiko) Bspec: 79814, 71582 Cc: Gustavo Sousa Reviewed-by: Gustavo Sousa Link: https://patch.msgid.link/20260716-tr_pta_mode-v2-1-e4cd50da1c94@intel.com Signed-off-by: Matt Roper --- drivers/gpu/drm/xe/xe_device_types.h | 4 +++ drivers/gpu/drm/xe/xe_pat.c | 44 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h index 56c17cca79c0..860ad322237f 100644 --- a/drivers/gpu/drm/xe/xe_device_types.h +++ b/drivers/gpu/drm/xe/xe_device_types.h @@ -404,6 +404,10 @@ struct xe_device { const struct xe_pat_table_entry *pat_primary_pta; /** @pat.pat_media_pta: media GT PAT entry for page table accesses */ const struct xe_pat_table_entry *pat_media_pta; + /** @pat.pat_primary_tr_pta: primary GT PAT entry for TRTT page table accesses */ + const struct xe_pat_table_entry *pat_primary_tr_pta; + /** @pat.pat_media_tr_pta: media GT PAT entry for TRTT page table accesses */ + const struct xe_pat_table_entry *pat_media_tr_pta; u16 idx[__XE_CACHE_LEVEL_COUNT]; } pat; diff --git a/drivers/gpu/drm/xe/xe_pat.c b/drivers/gpu/drm/xe/xe_pat.c index fad5b5a5ed4a..a5fe1beec652 100644 --- a/drivers/gpu/drm/xe/xe_pat.c +++ b/drivers/gpu/drm/xe/xe_pat.c @@ -25,6 +25,7 @@ 0x4800, 0x4804, \ 0x4848, 0x484c) #define _PAT_PTA 0x4820 +#define _PAT_TR_PTA 0x48cc #define XE2_NO_PROMOTE REG_BIT(10) #define XE2_COMP_EN REG_BIT(9) @@ -256,6 +257,7 @@ static const struct xe_pat_table_entry xe3p_xpc_pat_table[] = { static const struct xe_pat_table_entry xe3p_primary_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 3); static const struct xe_pat_table_entry xe3p_media_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 2); +static const struct xe_pat_table_entry xe3p_pat_tr_pta = XE2_PAT(0, 0, 0, 0, 0, 0); static const struct xe_pat_table_entry xe3p_lpg_pat_table[] = { [ 0] = XE2_PAT( 0, 0, 0, 0, 3, 0 ), @@ -325,11 +327,26 @@ static const struct xe_pat_table_entry *gt_pta_entry(struct xe_gt *gt) return NULL; } +static const struct xe_pat_table_entry *gt_tr_pta_entry(struct xe_gt *gt) +{ + struct xe_device *xe = gt_to_xe(gt); + + if (xe_gt_is_main_type(gt)) + return xe->pat.pat_primary_tr_pta; + + if (xe_gt_is_media_type(gt)) + return xe->pat.pat_media_tr_pta; + + xe_assert(xe, false); + return NULL; +} + static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[], int n_entries) { struct xe_device *xe = gt_to_xe(gt); const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt); + const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt); for (int i = 0; i < n_entries; i++) { struct xe_reg reg = XE_REG(_PAT_INDEX(i)); @@ -342,6 +359,9 @@ static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[ if (pta_entry) xe_mmio_write32(>->mmio, XE_REG(_PAT_PTA), pta_entry->value); + + if (tr_pta_entry) + xe_mmio_write32(>->mmio, XE_REG(_PAT_TR_PTA), tr_pta_entry->value); } static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry table[], @@ -349,6 +369,7 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta { struct xe_device *xe = gt_to_xe(gt); const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt); + const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt); for (int i = 0; i < n_entries; i++) { struct xe_reg_mcr reg_mcr = XE_REG_MCR(_PAT_INDEX(i)); @@ -361,6 +382,9 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta if (pta_entry) xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_PTA), pta_entry->value); + + if (tr_pta_entry) + xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_TR_PTA), tr_pta_entry->value); } static int xelp_dump(struct xe_gt *gt, struct drm_printer *p) @@ -531,6 +555,16 @@ static int xe2_dump(struct xe_gt *gt, struct drm_printer *p) drm_printf(p, "Page Table Access:\n"); xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false); + if (gt_tr_pta_entry(gt)) { + if (xe_gt_is_media_type(gt)) + pat = xe_mmio_read32(>->mmio, XE_REG(_PAT_TR_PTA)); + else + pat = xe_gt_mcr_unicast_read_any(gt, XE_REG_MCR(_PAT_TR_PTA)); + + drm_printf(p, "TRTT Page Table Access:\n"); + xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false); + } + if (xe_gt_is_media_type(gt)) pat = xe_mmio_read32(>->mmio, XE_REG(_PAT_ATS)); else @@ -577,6 +611,8 @@ void xe_pat_init_early(struct xe_device *xe) if (!IS_DGFX(xe)) { xe->pat.pat_primary_pta = &xe3p_primary_pat_pta; xe->pat.pat_media_pta = &xe3p_media_pat_pta; + xe->pat.pat_primary_tr_pta = &xe3p_pat_tr_pta; + xe->pat.pat_media_tr_pta = &xe3p_pat_tr_pta; } xe->pat.n_entries = ARRAY_SIZE(xe3p_lpg_pat_table); xe->pat.idx[XE_CACHE_NONE] = 3; @@ -701,6 +737,7 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p) { struct xe_device *xe = gt_to_xe(gt); const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt); + const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt); char label[PAT_LABEL_LEN]; if (!xe->pat.table || !xe->pat.n_entries) @@ -731,6 +768,13 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p) xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false); } + if (tr_pta_entry) { + u32 pat = tr_pta_entry->value; + + drm_printf(p, "TRTT Page Table Access:\n"); + xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false); + } + if (xe->pat.pat_ats) { u32 pat = xe->pat.pat_ats->value; From 116932705096281f402ccf779036a814ed89bf56 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Fri, 17 Jul 2026 19:46:52 +0530 Subject: [PATCH 63/81] drm/xe/xe_survivability: Decouple survivability info from boot survivability On CSC runtime firmware errors that requires firmware flash through SPI, PCODE sets the FDO mode bit in the Capability register. Currently the survivability_info group is created only for boot survivability. Create survivability_info group even for runtime survivability to allow userspace to check FDO mode sysfs. Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260717141650.2487761-6-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_survivability_mode.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_survivability_mode.c b/drivers/gpu/drm/xe/xe_survivability_mode.c index 427afd144f3a..4c506027fa94 100644 --- a/drivers/gpu/drm/xe/xe_survivability_mode.c +++ b/drivers/gpu/drm/xe/xe_survivability_mode.c @@ -54,7 +54,6 @@ * # cat /sys/bus/pci/devices//survivability_mode * Boot * - * * Any additional debug information if present will be visible under the directory * ``survivability_info``:: * @@ -98,6 +97,15 @@ * # cat /sys/bus/pci/devices//survivability_mode * Runtime * + * On some CSC firmware errors, PCODE sets FDO mode and the only recovery possible is through + * firmware flash using SPI driver. Userspace can check if FDO mode is set by checking the below + * sysfs entry. + * + * .. code-block:: shell + * + * # cat /sys/bus/pci/devices//survivability_info/fdo_mode + * enabled + * * When such errors occur, userspace is notified with the drm device wedged uevent and runtime * survivability mode. User can then initiate a firmware flash using userspace tools like fwupd * to restore device to normal operation. @@ -296,7 +304,8 @@ static int create_survivability_sysfs(struct pci_dev *pdev) if (ret) return ret; - if (check_boot_failure(xe)) { + /* Survivability info is not required if enabled via configfs */ + if (!xe_configfs_get_survivability_mode(pdev)) { ret = devm_device_add_group(dev, &survivability_info_group); if (ret) return ret; From 573f9e7ed1a97daf7db9c134fa49cad9b7f9c142 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Fri, 17 Jul 2026 19:46:53 +0530 Subject: [PATCH 64/81] drm/xe/xe_ras: Handle uncorrectable SoC Internal errors Some critical errors such as CSC firmware and Punit are reported under SoC internal errors and require special handling. CSC errors are classified into hardware errors and firmware errors. Hardware errors can be recovered using a SBR (Secondary Bus Reset) whereas firmware errors are critical and require a firmware flash. On such errors, device is wedged and runtime survivability mode will be enabled to notify userspace that a firmware flash is required. PUNIT uncorrectable errors can only be recovered through a cold reset. Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260717141650.2487761-7-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_ras.c | 44 ++++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_ras_types.h | 48 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c index ed609912fda1..73f9949286b4 100644 --- a/drivers/gpu/drm/xe/xe_ras.c +++ b/drivers/gpu/drm/xe/xe_ras.c @@ -8,6 +8,7 @@ #include "xe_pm.h" #include "xe_printk.h" #include "xe_ras.h" +#include "xe_survivability_mode.h" #include "xe_sysctrl.h" #include "xe_sysctrl_event_types.h" #include "xe_sysctrl_mailbox.h" @@ -235,6 +236,46 @@ static u8 handle_core_compute_errors(struct xe_ras_error_array *arr) return XE_RAS_RECOVERY_ACTION_RECOVERED; } +static u8 handle_soc_internal_errors(struct xe_device *xe, struct xe_ras_error_array *arr) +{ + struct xe_ras_soc_error *info = (void *)arr->details; + struct xe_ras_soc_error_source *source = &info->source; + struct xe_ras_error_class *counter = &arr->counter; + + if (source->csc) { + struct xe_ras_csc_error *csc_error = (void *)info->details; + + /* + * CSC uncorrectable errors are classified as hardware errors and firmware errors. + * CSC firmware errors are critical errors that can be recovered only by firmware + * update via SPI driver. On a CSC firmware error, PCODE enables FDO mode and sets + * the bit in the capability register. On receiving this error, the driver enables + * runtime survivability mode which notifies userspace that a firmware update + * is required. + */ + if (csc_error->hec_fw_error) { + xe_err(xe, "[RAS]: CSC %s detected: 0x%x\n", + sev_to_str(counter->common.severity), + csc_error->hec_fw_error); + xe_survivability_mode_runtime_enable(xe); + return XE_RAS_RECOVERY_ACTION_DISCONNECT; + } + } else if (source->ieh) { + struct xe_ras_ieh_error *ieh_error = (void *)info->details; + + if (ieh_error->global_error_status & XE_RAS_SOC_IEH_PUNIT) { + xe_err(xe, "[RAS]: PUNIT %s detected: 0x%x\n", + sev_to_str(counter->common.severity), + ieh_error->global_error_status); + /* TODO: Add PUNIT error handling */ + return XE_RAS_RECOVERY_ACTION_DISCONNECT; + } + } + + /* For other SoC internal errors, request a reset as recovery mechanism */ + return XE_RAS_RECOVERY_ACTION_RESET; +} + void xe_ras_counter_threshold_crossed(struct xe_device *xe, struct xe_sysctrl_event_response *response) { @@ -358,6 +399,9 @@ enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe) case XE_RAS_COMP_CORE_COMPUTE: action = handle_core_compute_errors(arr); break; + case XE_RAS_COMP_SOC_INTERNAL: + action = handle_soc_internal_errors(xe, arr); + break; default: /* For any other component, reset */ action = XE_RAS_RECOVERY_ACTION_RESET; diff --git a/drivers/gpu/drm/xe/xe_ras_types.h b/drivers/gpu/drm/xe/xe_ras_types.h index 766b4b41768e..066c1c39fc89 100644 --- a/drivers/gpu/drm/xe/xe_ras_types.h +++ b/drivers/gpu/drm/xe/xe_ras_types.h @@ -10,6 +10,8 @@ #define XE_RAS_NUM_COUNTERS 16 #define XE_RAS_NUM_ERROR_ARR 3 +/* Error bits in IEH global error status register */ +#define XE_RAS_SOC_IEH_PUNIT BIT(1) /** * enum xe_ras_recovery_action - RAS recovery actions @@ -177,6 +179,52 @@ struct xe_ras_compute_error { u32 reserved[15]; } __packed; +/** + * struct xe_ras_soc_error_source - Source of SoC error + */ +struct xe_ras_soc_error_source { + /** @csc: CSC */ + u32 csc:1; + /** @ieh: IEH (Integrated Error Handler) */ + u32 ieh:1; + /** @reserved: Reserved for future use */ + u32 reserved:30; +} __packed; + +/** + * struct xe_ras_soc_error - Error details of SoC internal error + */ +struct xe_ras_soc_error { + /** @source: Error source */ + struct xe_ras_soc_error_source source; + /** @details: Error details specific to the error source */ + u32 details[15]; +} __packed; + +/** + * struct xe_ras_csc_error - CSC error details + */ +struct xe_ras_csc_error { + /** @reserved: Reserved for future use */ + u32 reserved; + /** @hec_fw_error: CSC firmware error */ + u32 hec_fw_error; +} __packed; + +/** + * struct xe_ras_ieh_error - IEH (Integrated Error Handler) error details + */ +struct xe_ras_ieh_error { + /** @reserved: Reserved for future use */ + u32 reserved; + /** @global_error_status: Global error status */ + u32 global_error_status; + /** @reserved1: Reserved for future use */ + u32 reserved1[2]; + /** @info: Additional information */ + u32 info[10]; +} __packed; + /** * struct xe_ras_get_health_request - Request structure for obtaining gpu health */ From 942cbdcc8c07dc74a8d118914fc0a23e8fe76642 Mon Sep 17 00:00:00 2001 From: Riana Tauro Date: Fri, 17 Jul 2026 19:46:54 +0530 Subject: [PATCH 65/81] drm/xe/xe_ras: Add support to query device memory errors Add initial support to query uncorrectable device memory errors from system controller. The recovery action for memory errors depends on the error category. Firmware will set only one error category per response. Double bit ECC (Error Correcting Code) errors will be handled using Page offlining in a later patch. Poison and data parity errors are only logged. Rest of the errors require SBR (Secondary Bus Reset) to recover. Cc: Tejas Upadhyay Cc: Himal Prasad Ghimiray Reviewed-by: Mallesh Koujalagi Link: https://patch.msgid.link/20260717141650.2487761-8-riana.tauro@intel.com Signed-off-by: Riana Tauro --- drivers/gpu/drm/xe/xe_ras.c | 33 +++++++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_ras_types.h | 20 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c index 73f9949286b4..a31e06b8aa67 100644 --- a/drivers/gpu/drm/xe/xe_ras.c +++ b/drivers/gpu/drm/xe/xe_ras.c @@ -276,6 +276,36 @@ static u8 handle_soc_internal_errors(struct xe_device *xe, struct xe_ras_error_a return XE_RAS_RECOVERY_ACTION_RESET; } +static u8 handle_device_memory_errors(struct xe_device *xe, struct xe_ras_error_array *arr) +{ + struct xe_ras_memory_error *info = (void *)arr->details; + + /* + * For memory errors, the recovery action depends on the error category + * + * TODO: Double-bit ECC errors: Page offlining + * Poison and data parity errors: Log only + * For any other memory errors, request a reset as recovery mechanism + */ + switch (info->category) { + case XE_RAS_MEMORY_POISON: + xe_info(xe, "[RAS]: Poison error detected\n"); + break; + case XE_RAS_MEMORY_DATA_PARITY: + xe_info(xe, "[RAS]: Data parity error detected\n"); + break; + case XE_RAS_MEMORY_DB_ECC: + xe_info(xe, "[RAS]: Double-bit ECC error detected at sw address 0x%llx\n", + info->sw_address); + /* TODO: Add page offlining for Double-bit ECC error */ + fallthrough; + default: + return XE_RAS_RECOVERY_ACTION_RESET; + } + + return XE_RAS_RECOVERY_ACTION_RECOVERED; +} + void xe_ras_counter_threshold_crossed(struct xe_device *xe, struct xe_sysctrl_event_response *response) { @@ -402,6 +432,9 @@ enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe) case XE_RAS_COMP_SOC_INTERNAL: action = handle_soc_internal_errors(xe, arr); break; + case XE_RAS_COMP_DEVICE_MEMORY: + action = handle_device_memory_errors(xe, arr); + break; default: /* For any other component, reset */ action = XE_RAS_RECOVERY_ACTION_RESET; diff --git a/drivers/gpu/drm/xe/xe_ras_types.h b/drivers/gpu/drm/xe/xe_ras_types.h index 066c1c39fc89..99b2466e2062 100644 --- a/drivers/gpu/drm/xe/xe_ras_types.h +++ b/drivers/gpu/drm/xe/xe_ras_types.h @@ -12,6 +12,10 @@ #define XE_RAS_NUM_ERROR_ARR 3 /* Error bits in IEH global error status register */ #define XE_RAS_SOC_IEH_PUNIT BIT(1) +/* Device memory error categories */ +#define XE_RAS_MEMORY_DB_ECC BIT(1) +#define XE_RAS_MEMORY_POISON BIT(2) +#define XE_RAS_MEMORY_DATA_PARITY BIT(5) /** * enum xe_ras_recovery_action - RAS recovery actions @@ -225,6 +229,22 @@ struct xe_ras_ieh_error { u32 info[10]; } __packed; +/** + * struct xe_ras_memory_error - Device memory error details + */ +struct xe_ras_memory_error { + /** @category: Device memory error category */ + u8 category; + /** @reserved: Reserved for future use */ + u8 reserved[7]; + /** @reserved1: Reserved for future use */ + u64 reserved1; + /** @sw_address: Software address where error occurred */ + u64 sw_address; + /** @reserved2: Reserved for future use */ + u32 reserved2[10]; +} __packed; + /** * struct xe_ras_get_health_request - Request structure for obtaining gpu health */ From f92241226087ce789016c9e74e2af9d760ee1abc Mon Sep 17 00:00:00 2001 From: Sanjay Yadav Date: Fri, 10 Jul 2026 14:00:05 +0530 Subject: [PATCH 66/81] drm/xe/migrate: Revamp PAT index selection for migrate PTEs Improve PAT index selection logic in xe_migrate.c to avoid unnecessary coherency overhead when host-side memory is uncached. Previously, we defaulted to XE_CACHE_WB, which enforces 2-way coherency and may trigger cacheline pulls from CPU even when host-side memory is never dirty. This change introduces xe_migrate_pat_index() to choose the appropriate PAT index based on the actual TTM caching mode of the buffer object being mapped. For iGPUs with WC host mappings, we now prefer XE_CACHE_NONE to skip coherency snoops. For compressed PTEs on newer platforms, we select XE_CACHE_NONE_COMPRESSION. This avoids unnecessary cache traffic for uncached host mappings. v6: (sashiko) - Only apply the BO's host-side caching for system-memory PTEs. v5: (Matt A) - Simplify emit_pte() to derive caching from res->bo directly, removing the separate bo parameter - Leave changes in __xe_migrate_update_pgtables() and build_pt_update_batch_sram() - Fix comment about page-walker coherency in xe_migrate_pat_index() v4: - Keep xe_migrate_prepare_vm() on XE_CACHE_WB since page tables require page-walker coherency. - Pass BO into emit_pte() and select PAT attributes from the BO's TTM caching mode. Assisted-by: Github-Copilot:claude-opus-4.8 Signed-off-by: Sanjay Yadav Suggested-by: Matthew Auld Reviewed-by: Matthew Auld Signed-off-by: Matthew Auld Link: https://patch.msgid.link/20260710083004.1546599-2-sanjay.kumar.yadav@intel.com --- drivers/gpu/drm/xe/xe_migrate.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index c84e14e86a82..4366c41bd325 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -117,6 +117,27 @@ static void xe_migrate_fini(void *arg) xe_exec_queue_put(m->q); } +static inline u16 xe_migrate_pat_index(struct xe_device *xe, + enum ttm_caching caching, + bool is_comp_pte) +{ + enum xe_cache_level cache_level; + + /* + * Select the appropriate PAT index for buffer object PTEs programmed + * by emit_pte(). We choose not to mess with xe_migrate_prepare_vm() + * yet, for simplicity. + */ + if (is_comp_pte && GRAPHICS_VERx100(xe) >= 2000) + cache_level = XE_CACHE_NONE_COMPRESSION; + else if (caching == ttm_cached) + cache_level = XE_CACHE_WB; + else + cache_level = XE_CACHE_NONE; + + return xe_cache_pat_idx(xe, cache_level); +} + static u64 xe_migrate_vm_addr(u64 slot, u32 level) { XE_WARN_ON(slot >= NUM_PT_SLOTS); @@ -607,17 +628,17 @@ static void emit_pte(struct xe_migrate *m, { struct xe_device *xe = tile_to_xe(m->tile); struct xe_vm *vm = m->q->vm; + struct xe_bo *bo = ttm_to_xe_bo(res->bo); + enum ttm_caching caching = ttm_cached; u16 pat_index; u32 ptes; u64 ofs = (u64)at_pt * XE_PAGE_SIZE; u64 cur_ofs; - /* Indirect access needs compression enabled uncached PAT index */ - if (GRAPHICS_VERx100(xe) >= 2000) - pat_index = is_comp_pte ? xe_cache_pat_idx(xe, XE_CACHE_NONE_COMPRESSION) : - xe_cache_pat_idx(xe, XE_CACHE_WB); - else - pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB); + if (!is_vram && bo->ttm.ttm) + caching = bo->ttm.ttm->caching; + + pat_index = xe_migrate_pat_index(xe, caching, is_comp_pte); ptes = DIV_ROUND_UP(size, XE_PAGE_SIZE); From 1ae415a6eefe5004954a1d352b1718faca8844ef Mon Sep 17 00:00:00 2001 From: Satyanarayana K V P Date: Tue, 21 Jul 2026 10:52:14 +0530 Subject: [PATCH 67/81] drm/xe/vf: Add drm_dev guards when detaching CCS read/write buffers CCS read/write buffers are freed during BO destruction. In some cases, BOs may be destroyed after the device is unbound but while the DRM structure remains valid, leading to NULL pointer dereferences when accessing device resources. BUG: kernel NULL pointer dereference, address: 0000000000000000 PGD 0 P4D 0 Oops: Oops: 0000 [#1] SMP NOPTI CPU: 0 UID: 0 PID: 9376 Comm: xe_pat Not tainted 7.2.0-rc2+ #1 PREEMPT(lazy) RIP: 0010:xe_sriov_vf_ccs_rw_update_bb_addr+0x4d/0xa0 [xe] RSP: 0018:ffffcf304110b9c8 EFLAGS: 00010246 RAX: ffff8a85c38a0a00 RBX: 00000000810ef000 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff8a85c39c1888 RBP: ffffcf304110b9e8 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8a85c39c1888 R13: 0000000000000000 R14: ffff8a85c39b4f28 R15: ffff8a85c3885000 FS: 0000000000000000(0000) GS:ffff8a878b809000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000000 CR3: 000000010314a002 CR4: 0000000000772ef0 PKRU: 55555554 Call Trace: xe_migrate_ccs_rw_copy_clear+0x98/0x120 [xe] xe_sriov_vf_ccs_detach_bo+0x2c/0x60 [xe] xe_ttm_bo_delete_mem_notify+0xc8/0xe0 [xe] ttm_bo_cleanup_memtype_use+0x26/0x80 [ttm] ttm_bo_release+0x29e/0x2d0 [ttm] ttm_bo_fini+0x39/0x70 [ttm] xe_gem_object_free+0x1f/0x30 [xe] drm_gem_object_free+0x1d/0x40 ttm_bo_vm_close+0x5f/0x90 [ttm] remove_vma+0x2c/0x70 tear_down_vmas+0x63/0xf0 exit_mmap+0x20d/0x3f0 __mmput+0x45/0x170 mmput+0x31/0x40 do_exit+0x2ba/0xac0 do_group_exit+0x2d/0xb0 __x64_sys_exit_group+0x18/0x20 x64_sys_call+0x14a0/0x2390 do_syscall_64+0xdd/0x640 ? count_memcg_events+0xea/0x240 ? handle_mm_fault+0x1ec/0x2f0 Fixes: 864690cf4dd6 ("drm/xe/vf: Attach and detach CCS copy commands with BO") Signed-off-by: Satyanarayana K V P Cc: Matthew Brost Cc: Michal Wajdeczko Reviewed-by: Matthew Brost Signed-off-by: Matthew Brost Link: https://patch.msgid.link/20260721052215.2267228-2-satyanarayana.k.v.p@intel.com --- drivers/gpu/drm/xe/xe_migrate.c | 16 ++++++++++------ drivers/gpu/drm/xe/xe_migrate.h | 3 ++- drivers/gpu/drm/xe/xe_sriov_vf_ccs.c | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_migrate.c b/drivers/gpu/drm/xe/xe_migrate.c index 4366c41bd325..f79d0047bec6 100644 --- a/drivers/gpu/drm/xe/xe_migrate.c +++ b/drivers/gpu/drm/xe/xe_migrate.c @@ -1310,6 +1310,7 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, * content. * @src_bo: The buffer object @src is currently bound to. * @read_write : Creates BB commands for CCS read/write. + * @bound: Device is bound * * Directly clearing the BB lacks atomicity and can lead to undefined * behavior if the vCPU is halted mid-operation during the clearing @@ -1322,7 +1323,8 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, * Returns: None. */ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, - enum xe_sriov_vf_ccs_rw_ctxs read_write) + enum xe_sriov_vf_ccs_rw_ctxs read_write, + bool bound) { struct xe_mem_pool_node *bb = src_bo->bb_ccs[read_write]; struct xe_device *xe = xe_bo_device(src_bo); @@ -1336,13 +1338,15 @@ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, bb_pool = ctx->mem.ccs_bb_pool; scoped_guard(mutex, xe_mem_pool_bo_swap_guard(bb_pool)) { - xe_mem_pool_swap_shadow_locked(bb_pool); + if (bound) { + xe_mem_pool_swap_shadow_locked(bb_pool); - cs = xe_mem_pool_node_cpu_addr(bb); - memset(cs, MI_NOOP, bb->sa_node.size); - xe_sriov_vf_ccs_rw_update_bb_addr(ctx); + cs = xe_mem_pool_node_cpu_addr(bb); + memset(cs, MI_NOOP, bb->sa_node.size); + xe_sriov_vf_ccs_rw_update_bb_addr(ctx); - xe_mem_pool_sync_shadow_locked(bb); + xe_mem_pool_sync_shadow_locked(bb); + } xe_mem_pool_free_node(bb); src_bo->bb_ccs[read_write] = NULL; } diff --git a/drivers/gpu/drm/xe/xe_migrate.h b/drivers/gpu/drm/xe/xe_migrate.h index 78e5b63f3ebe..c3a268b01768 100644 --- a/drivers/gpu/drm/xe/xe_migrate.h +++ b/drivers/gpu/drm/xe/xe_migrate.h @@ -142,7 +142,8 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q, enum xe_sriov_vf_ccs_rw_ctxs read_write); void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo, - enum xe_sriov_vf_ccs_rw_ctxs read_write); + enum xe_sriov_vf_ccs_rw_ctxs read_write, + bool bound); struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate); struct xe_exec_queue *xe_migrate_exec_queue(struct xe_migrate *migrate); diff --git a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c index 6787564629c6..a8c831fbee3b 100644 --- a/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c +++ b/drivers/gpu/drm/xe/xe_sriov_vf_ccs.c @@ -3,6 +3,8 @@ * Copyright © 2025 Intel Corporation */ +#include + #include "instructions/xe_mi_commands.h" #include "instructions/xe_gpu_commands.h" #include "xe_bb.h" @@ -446,7 +448,7 @@ int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem) */ for_each_ccs_rw_ctx(ctx_id) { if (bo->bb_ccs[ctx_id]) - xe_migrate_ccs_rw_copy_clear(bo, ctx_id); + xe_migrate_ccs_rw_copy_clear(bo, ctx_id, true); } return err; } @@ -466,19 +468,27 @@ int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo) struct xe_device *xe = xe_bo_device(bo); enum xe_sriov_vf_ccs_rw_ctxs ctx_id; struct xe_mem_pool_node *bb; + bool bound; + int idx; xe_assert(xe, IS_VF_CCS_READY(xe)); if (!xe_bo_has_valid_ccs_bb(bo)) return 0; + bound = drm_dev_enter(&xe->drm, &idx); + for_each_ccs_rw_ctx(ctx_id) { bb = bo->bb_ccs[ctx_id]; if (!bb) continue; - xe_migrate_ccs_rw_copy_clear(bo, ctx_id); + xe_migrate_ccs_rw_copy_clear(bo, ctx_id, bound); } + + if (bound) + drm_dev_exit(idx); + return 0; } From a79f6abc8b516b5bd906e2eca8121e3549ee163f Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Tue, 21 Jul 2026 17:04:38 +0530 Subject: [PATCH 68/81] drm/xe/i2c: Allow per domain unique id PCI bus, device and function can be same for devices existing across different domains. Allow per domain unique identifier while registering platform device to prevent name conflict. Fixes: f0e53aadd702 ("drm/xe: Support for I2C attached MCUs") Signed-off-by: Raag Jadav Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260721113438.651100-1-raag.jadav@intel.com Signed-off-by: Matt Roper --- drivers/gpu/drm/xe/xe_i2c.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index bd956776b10b..a26c38bb17a1 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -95,18 +95,21 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) struct platform_device *pdev; struct fwnode_handle *fwnode; int ret; + u32 id; fwnode = fwnode_create_software_node(xe_i2c_adapter_properties, NULL); if (IS_ERR(fwnode)) return PTR_ERR(fwnode); + id = (pci_domain_nr(pci->bus) << 16) | pci_dev_id(pci); + /* * Not using platform_device_register_full() here because we don't have * a handle to the platform_device before it returns. xe_i2c_notifier() * uses that handle, but it may be called before * platform_device_register_full() is done. */ - pdev = platform_device_alloc(adapter_name, pci_dev_id(pci)); + pdev = platform_device_alloc(adapter_name, id); if (!pdev) { ret = -ENOMEM; goto err_fwnode_remove; From ca2a3587d577ba764e0fe628fb676244fc33ddd4 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 21 Jul 2026 20:55:14 +0000 Subject: [PATCH 69/81] drm/xe/vm: Fix SVM leak on resv obj alloc failure in xe_vm_create() Commit 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") made xe_svm_init() unconditional in xe_vm_create() and extended it to also initialize a "simple" gpusvm state for non-fault-mode VMs. The matching xe_svm_fini() call in xe_vm_close_and_put() was updated to run unconditionally, but the error unwind path in xe_vm_create() was not. On the drm_gpuvm_resv_object_alloc() failure path, xe_svm_init() has already succeeded but xe_svm_fini() is only called when XE_VM_FLAG_FAULT_MODE is set. For non-fault-mode VMs this leaves vm->svm.gpusvm partially initialized and leaks the resources allocated by drm_gpusvm_init(). For fault-mode VMs, xe_svm_init() additionally acquires the pagemap owner via drm_pagemap_acquire_owner() and the pagemaps via xe_svm_get_pagemaps(). Those resources are released by xe_svm_close(), not xe_svm_fini(). On the same error path, xe_svm_close() is not called either, so fault-mode VMs leak the pagemap owner and pagemaps. Fix both leaks: - Call xe_svm_fini() unconditionally on the err_svm_fini path, matching the unconditional xe_svm_init() call. Move the vm->size = 0 assignment out of the conditional so the xe_vm_is_closed() assert in xe_svm_fini() (and xe_svm_close()) holds for both modes. - Call xe_svm_close() for fault-mode VMs before xe_svm_fini(), matching the ordering used in xe_vm_close_and_put(). Fixes: 9e9787414882 ("drm/xe/userptr: replace xe_hmm with gpusvm") Cc: Matthew Auld Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260721205516.4058959-2-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/xe_vm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 57bcf0660eb8..5e636c6e5a51 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1825,10 +1825,10 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) return ERR_PTR(err); err_svm_fini: - if (flags & XE_VM_FLAG_FAULT_MODE) { - vm->size = 0; /* close the vm */ - xe_svm_fini(vm); - } + vm->size = 0; /* close the vm */ + if (flags & XE_VM_FLAG_FAULT_MODE) + xe_svm_close(vm); + xe_svm_fini(vm); err_no_resv: mutex_destroy(&vm->snap_mutex); for_each_tile(tile, xe, id) From cc98c031846e3f2f2f91fe4256d5754cd2f7ef71 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 21 Jul 2026 20:55:15 +0000 Subject: [PATCH 70/81] drm/xe/vm: Remove redundant INIT_WORK() for rebind_work in xe_vm_create() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xe_vm_create() initializes vm->preempt.rebind_work twice for LR-mode VMs: once in the LR-mode setup block before xe_svm_init(), and again inside the xe_validation_guard() block. The second call is a no-op on top of the first since the work is never queued between them, but re-initializing a work item is unnecessary and makes lifetime and ordering harder to reason about (e.g., any future change that queues the work earlier would be silently corrupted by the second INIT_WORK). Drop the duplicate INIT_WORK() and keep only the batch_invalidate_tlb flag handling in the later LR-mode block. The single INIT_WORK() call in the earlier LR-mode setup block remains the sole initialization. No functional change. Cc: Thomas Hellström Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260721205516.4058959-3-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/xe_vm.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 5e636c6e5a51..660e387f36fc 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1765,10 +1765,8 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) vm->batch_invalidate_tlb = true; } - if (vm->flags & XE_VM_FLAG_LR_MODE) { - INIT_WORK(&vm->preempt.rebind_work, preempt_rebind_work_func); + if (vm->flags & XE_VM_FLAG_LR_MODE) vm->batch_invalidate_tlb = false; - } /* Fill pt_root after allocating scratch tables */ for_each_tile(tile, xe, id) { From 329a7a6cee090e2d4a94f2d8af8aa04b8e23fec7 Mon Sep 17 00:00:00 2001 From: Shuicheng Lin Date: Tue, 21 Jul 2026 20:55:16 +0000 Subject: [PATCH 71/81] drm/xe/vm: Use regular comment for GSC VM lockdep note in xe_vm_create() The block comment describing the GSC VM lockdep annotation uses the kernel-doc opening marker (/**), but it is an in-function implementation note rather than API documentation. Per Documentation/doc-guide/kernel-doc.rst, /** is reserved for kernel-doc comments describing functions, structs, and other API elements, and using it for other comments can confuse kernel-doc tooling. Switch it to a regular block comment (/*). No functional change. Cc: Matthew Brost Assisted-by: Claude:claude-opus-4.7 Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260721205516.4058959-4-shuicheng.lin@intel.com Signed-off-by: Shuicheng Lin --- drivers/gpu/drm/xe/xe_vm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 660e387f36fc..9e0176861cb6 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1645,7 +1645,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef) if (xef) vm->xef = xe_file_get(xef); - /** + /* * GSC VMs are kernel-owned, only used for PXP ops and can sometimes be * manipulated under the PXP mutex. However, the PXP mutex can be taken * under a user-VM lock when the PXP session is started at exec_queue From 8b2d3bb766c69976da4767fb3b7299a909367f58 Mon Sep 17 00:00:00 2001 From: Gustavo Sousa Date: Wed, 22 Jul 2026 09:52:57 -0300 Subject: [PATCH 72/81] drm/xe/nvls: Drop force_probe requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NVL-S is stable enough for us to drop force_probe requirement. Let's do that. Cc: Jani Nikula Cc: Matthew Brost Cc: Thomas Hellström Reviewed-by: Rodrigo Vivi Link: https://patch.msgid.link/20260722-nvl_s-drop-force_probe-v1-1-db944d940cff@intel.com Signed-off-by: Gustavo Sousa --- drivers/gpu/drm/xe/xe_pci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c index 691b68c69a2f..5f2a0b19839d 100644 --- a/drivers/gpu/drm/xe/xe_pci.c +++ b/drivers/gpu/drm/xe/xe_pci.c @@ -461,7 +461,6 @@ static const struct xe_device_desc nvls_desc = { .has_sriov = true, .max_gt_per_tile = 2, MULTI_LRC_MASK, - .require_force_probe = true, .va_bits = 48, .vm_max_level = 4, }; From a1e4e62ebc799ce69236054672253e0086bf409b Mon Sep 17 00:00:00 2001 From: Harish Chegondi Date: Wed, 22 Jul 2026 11:46:13 -0700 Subject: [PATCH 73/81] drm/xe/eustall: Add WA 14027054324 support for graphics IP 35.11 WA 14027054324 is implemented in the firmware and is applied before EU stall sampling and reverted after EU stall sampling. The driver needs to notify the firmware whenever EU stall sampling is being enabled/disabled so that the firmware takes the necessary action. The driver uses a scratch pad register to communicate with the firmware. Before enabling EU stall sampling, write 0x20 to the SWF scratch pad register to request the firmware to apply the workaround. The firmware applies the workaround and sets the scratch pad register to 0x60 as an ACK. Before disabling EU stall sampling, write 0x40 to the SWF scratch pad register to request the firmware to revert the workaround. The firmware reverts the workaround and sets the scratch pad register to 0 as an ACK. The firmware is expected to take about 1 ms to apply/revert the workaround. 10 ms timeout is used in the driver while waiting for an ack from the firmware to have adequate grace period. Bspec update for the SWF scratch pad register is still pending, but has been confirmed offline with the firmware team. Bspec: 53188 Signed-off-by: Harish Chegondi Reviewed-by: Matt Roper Link: https://patch.msgid.link/16b6b972691943daddebad6e7b93b9d73add5249.1784745545.git.harish.chegondi@intel.com Signed-off-by: Matt Roper --- drivers/gpu/drm/xe/regs/xe_regs.h | 2 ++ drivers/gpu/drm/xe/xe_eu_stall.c | 37 +++++++++++++++++++++++++++--- drivers/gpu/drm/xe/xe_wa_oob.rules | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/xe/regs/xe_regs.h b/drivers/gpu/drm/xe/regs/xe_regs.h index ad93c57edd17..ef4746b7b5d3 100644 --- a/drivers/gpu/drm/xe/regs/xe_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_regs.h @@ -7,6 +7,8 @@ #include "regs/xe_reg_defs.h" +#define SWF_SCRATCHPAD(_idx) XE_REG(0x4f000 + (_idx) * 4) + #define SOC_BASE 0x280000 #define GU_CNTL_PROTECTED XE_REG(0x10100C) diff --git a/drivers/gpu/drm/xe/xe_eu_stall.c b/drivers/gpu/drm/xe/xe_eu_stall.c index d37770c58c5d..8a7c1b5d5ab9 100644 --- a/drivers/gpu/drm/xe/xe_eu_stall.c +++ b/drivers/gpu/drm/xe/xe_eu_stall.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include "xe_gt_printk.h" #include "xe_gt_topology.h" #include "xe_macros.h" +#include "xe_mmio.h" #include "xe_observation.h" #include "xe_pm.h" #include "xe_trace.h" @@ -27,8 +29,16 @@ #include "regs/xe_eu_stall_regs.h" #include "regs/xe_gt_regs.h" +#include "regs/xe_regs.h" #define POLL_PERIOD_MS 5 +#define FW_WA_WAIT_TIMEOUT_US 10000 + +#define SWF_EUSTALL_MASK REG_GENMASK(6, 5) +#define REQ_EUSTALL_ENABLE REG_BIT(5) +#define ACK_EUSTALL_ENABLE REG_GENMASK(6, 5) +#define REQ_EUSTALL_DISABLE REG_BIT(6) +#define ACK_EUSTALL_DISABLE 0 static size_t per_xecore_buf_size = SZ_512K; @@ -682,7 +692,7 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream) struct per_xecore_buf *xecore_buf; struct xe_gt *gt = stream->gt; u16 group, instance; - int xecore; + int xecore, ret = 0; /* Take runtime pm ref and forcewake to disable RC6 */ xe_pm_runtime_get(gt_to_xe(gt)); @@ -693,6 +703,18 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream) return -ETIMEDOUT; } + if (XE_GT_WA(gt, 14027054324)) { + /* Request the firmware to apply the workaround and wait for an ACK */ + xe_mmio_write32(>->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_ENABLE); + ret = xe_mmio_wait32(>->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK, + ACK_EUSTALL_ENABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false); + if (ret) { + xe_gt_err(gt, "Timeout polling for EU stall enable ACK from firmware\n"); + xe_force_wake_put(gt_to_fw(gt), stream->fw_ref); + xe_pm_runtime_put(gt_to_xe(gt)); + return ret; + } + } if (XE_GT_WA(gt, 22016596838)) xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2, REG_MASKED_FIELD_ENABLE(DISABLE_DOP_GATING)); @@ -730,7 +752,7 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream) reg_value |= XEHPC_EUSTALL_BASE_ENABLE_SAMPLING; xe_gt_mcr_multicast_write(gt, XEHPC_EUSTALL_BASE, reg_value); - return 0; + return ret; } static void eu_stall_data_buf_poll_work_fn(struct work_struct *work) @@ -840,6 +862,7 @@ static int xe_eu_stall_enable_locked(struct xe_eu_stall_data_stream *stream) static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream) { struct xe_gt *gt = stream->gt; + int ret = 0; if (!stream->enabled) return 0; @@ -853,11 +876,19 @@ static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream) if (XE_GT_WA(gt, 22016596838)) xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2, REG_MASKED_FIELD_DISABLE(DISABLE_DOP_GATING)); + if (XE_GT_WA(gt, 14027054324)) { + /* Request the firmware to revert the workaround and wait for an ACK */ + xe_mmio_write32(>->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_DISABLE); + ret = xe_mmio_wait32(>->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK, + ACK_EUSTALL_DISABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false); + if (ret) + xe_gt_err(gt, "Timeout polling for EU stall disable ACK from firmware\n"); + } xe_force_wake_put(gt_to_fw(gt), stream->fw_ref); xe_pm_runtime_put(gt_to_xe(gt)); - return 0; + return ret; } static long xe_eu_stall_stream_ioctl_locked(struct xe_eu_stall_data_stream *stream, diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index 5d6574ec9dee..f02ac9bf7424 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -71,3 +71,4 @@ GRAPHICS_VERSION(3511) 16029897822 MEDIA_VERSION(3500) GRAPHICS_VERSION(3510) +14027054324 GRAPHICS_VERSION(3511) From 05649458e4bc2e45eb8d231a7a9b00dd6c9e8206 Mon Sep 17 00:00:00 2001 From: Matthew Auld Date: Thu, 23 Jul 2026 17:15:43 +0100 Subject: [PATCH 74/81] drm/xe/bo: optimise TT population for DONTNEED BOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a VRAM buffer object is marked as DONTNEED, like in Mesa, the driver skips migrating its contents to system memory and instead purges the backing store during eviction (via xe_ttm_bo_purge). However, xe_evict_flags() still returns tt_placement (XE_PL_TT) for VRAM BOs even if they were marked DONTNEED. This causes ttm_bo_handle_move_mem() to always call ttm_bo_populate() to allocate destination system pages, only for those pages to be immediately freed right after when xe_bo_move() calls xe_ttm_bo_purge(). Fix this by changing xe_evict_flags() to return sys_placement (XE_PL_SYSTEM) for DONTNEED BOs. This causes TTM to skip the population step while still ensuring that TTM calls xe_bo_move(), so xe_ttm_bo_purge() still triggers. v2 (Thomas): - Add a comment to highlight why sys_placement over purge_placement. Assisted-by: Copilot:gemini-3.1-pro-preview Signed-off-by: Matthew Auld Cc: Thomas Hellström Cc: Matthew Brost Reviewed-by: Maarten Lankhorst Reviewed-by: Thomas Hellström Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260723161542.1220276-2-matthew.auld@intel.com --- drivers/gpu/drm/xe/xe_bo.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index c266fa6bade1..b1fdc802d27b 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -341,6 +341,18 @@ static void xe_evict_flags(struct ttm_buffer_object *tbo, return; } + if (xe_bo_madv_is_dontneed(bo)) { + /* + * We can't use purge_placement here, since we need to trigger + * our own purge procedure at the start of xe_bo_move(), which + * would otherwise be skipped. At the same time we don't want + * ttm to then populate the tt with dst pages, before the move + * callback, hence use sys_placement here. + */ + *placement = sys_placement; + return; + } + /* * For xe, sg bos that are evicted to system just triggers a * rebind of the sg list upon subsequent validation to XE_PL_TT. From 04eeeb45cb61b8a3e9d785003457e550c920ba49 Mon Sep 17 00:00:00 2001 From: Himal Prasad Ghimiray Date: Tue, 28 Jul 2026 11:29:17 +0530 Subject: [PATCH 75/81] drm/xe/pt: check no-DMA huge-pte cases before DMA segment test On a non-range clear, curs.size is never set, so the segment test (next - va_curs_start > curs->size) returns false for every level > 0 before the clear_pt short-circuit is reached. The clear then descends to level 0 instead of forming a huge zero-leaf, wasting page tables and risking -ENOMEM on unbind. Move the null-VMA, purged-BO and clear_pt short-circuits above the curs->size test. The bind path always sets curs.size, so it is unaffected. v2 - Also set curs.size on the clear path so the cursor stays meaningful during the walk. clear_pt is only reached with range == NULL, so assert that invariant. (Matthew Brost) Cc: Matthew Brost Fixes: 5b658b7e89c3 ("drm/xe: Clear scratch page on vm_bind") Reported-by: Sashiko Reviewed-by: Matthew Brost Link: https://patch.msgid.link/20260728055916.593707-2-himal.prasad.ghimiray@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_pt.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index 598c6b2571e7..0e9d669620b9 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -443,10 +443,6 @@ static bool xe_pt_hugepte_possible(u64 addr, u64 next, unsigned int level, if (!xe_pt_covers(addr, next, level, &xe_walk->base)) return false; - /* Does the DMA segment cover the whole pte? */ - if (next - xe_walk->va_curs_start > xe_walk->curs->size) - return false; - /* null VMA's and purged BO's do not have dma addresses */ if (xe_vma_is_null(xe_walk->vma) || (bo && xe_bo_is_purged(bo))) return true; @@ -455,6 +451,10 @@ static bool xe_pt_hugepte_possible(u64 addr, u64 next, unsigned int level, if (xe_walk->clear_pt) return true; + /* Does the DMA segment cover the whole pte? */ + if (next - xe_walk->va_curs_start > xe_walk->curs->size) + return false; + /* Is the DMA address huge PTE size aligned? */ size = next - addr; dma = addr - xe_walk->va_curs_start + xe_res_dma(xe_walk->curs); @@ -775,8 +775,11 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma, } xe_walk.needs_64K = (vm->flags & XE_VM_FLAG_64K); - if (clear_pt) + if (clear_pt) { + xe_assert(xe, !range); + curs.size = xe_vma_size(vma); goto walk_pt; + } if (vma->gpuva.flags & XE_VMA_ATOMIC_PTE_BIT) { xe_walk.default_vram_pte = xe_atomic_for_vram(vm, vma) ? XE_USM_PPGTT_PTE_AE : 0; From 6bea40fc4cf3f708c0eac23bdc7e47edc1cc7a8a Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:38 +0530 Subject: [PATCH 76/81] drm/xe: add page size allocation control state to xe_device Introduce xe_page_size_alloc_ctrl_mode and add page_size_alloc_ctrl state to struct xe_device along with mutex lock. The new control supports forcing user BO allocations to 2M pages, forcing them to 1G pages, or using a mixed round-robin mode across 4K, 64K, 2M, and 1G page sizes. Track the current mixed-mode index in xe_device so allocation policy can be applied consistently. v2 - make cur_index to atomic as update need in later patch to avoid race/concurency (sashiko) v3 - reworded comments - protect mode/index updates with a mutex for proper concurrency handling v4(sashiko) - move xe_debug_page_size_alloc_ctrl_init() before drm_dev_register(), so mutex and control states are initialized before any userspace visibility v5(Himal) - Guard all the debug page size policy code under CONFIG - Squash Kconfig patch to have Kconfig entry for DEBUG_PAGE_SIZE - Add inline to check debug page size support and exact mode configured if it is supported. v6 (fix CI build) v8 (Himal) - use drmm_mutex_init to avoid leak with mutex_init - call xe_debug_page_size_alloc_ctrl_init unconditionally - Add missed mixed mode check on xe_debug_page_size_mode_not_none check - Add xe_debug_page_size_mode_is_mixed() function v9 - make xe_debug_page_size_alloc_ctrl_init() return int - fail probe if drmm_mutex_init() for page_size_alloc_ctrl.lock fails Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-2-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/Kconfig.debug | 16 ++++++++++ drivers/gpu/drm/xe/xe_device.c | 25 +++++++++++++++ drivers/gpu/drm/xe/xe_device.h | 48 ++++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_device_types.h | 31 ++++++++++++++++++ 4 files changed, 120 insertions(+) diff --git a/drivers/gpu/drm/xe/Kconfig.debug b/drivers/gpu/drm/xe/Kconfig.debug index 01227c77f6d7..79118d9efd93 100644 --- a/drivers/gpu/drm/xe/Kconfig.debug +++ b/drivers/gpu/drm/xe/Kconfig.debug @@ -86,6 +86,22 @@ config DRM_XE_KUNIT_TEST If in doubt, say "N". +config DRM_XE_DEBUG_PAGE_SIZE + bool "Enable debug control for user BO page-size allocation" + depends on DRM_XE_DEBUG && DEBUG_FS + help + Expose a debugfs knob to override user BO page-size allocation + handling for validation and debug. Supported modes include forced + 2M, forced 1G, and a mixed mode that exercises 4K, 64K, 2M, and + 1G page-size paths on platforms that support them. + + This is an unstable debugfs interface intended for development and + validation only. Its layout, contents, and existence may change or + be removed at any time with no regression warranty. + + Recommended for driver developers only. + If in doubt, say "N". + config DRM_XE_DEBUG_GUC bool "Enable extra GuC related debug options" depends on DRM_XE_DEBUG diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 4eed9a251e65..7007b6113760 100644 --- a/drivers/gpu/drm/xe/xe_device.c +++ b/drivers/gpu/drm/xe/xe_device.c @@ -921,6 +921,27 @@ static void xe_device_wedged_fini(struct drm_device *drm, void *arg) xe_pm_runtime_put(xe); } +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +static int xe_debug_page_size_alloc_ctrl_init(struct xe_device *xe) +{ + int err; + + err = drmm_mutex_init(&xe->drm, &xe->page_size_alloc_ctrl.lock); + if (err) + return err; + + xe->page_size_alloc_ctrl.mode = XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE; + xe->page_size_alloc_ctrl.cur_index = 0; + + return 0; +} +#else +static int xe_debug_page_size_alloc_ctrl_init(struct xe_device *xe) +{ + return 0; +} +#endif + int xe_device_probe(struct xe_device *xe) { struct xe_tile *tile; @@ -1073,6 +1094,10 @@ int xe_device_probe(struct xe_device *xe) if (err) return err; + err = xe_debug_page_size_alloc_ctrl_init(xe); + if (err) + return err; + err = drm_dev_register(&xe->drm, 0); if (err) return err; diff --git a/drivers/gpu/drm/xe/xe_device.h b/drivers/gpu/drm/xe/xe_device.h index a03760d0ce38..6c4cfaebc44a 100644 --- a/drivers/gpu/drm/xe/xe_device.h +++ b/drivers/gpu/drm/xe/xe_device.h @@ -212,6 +212,54 @@ static inline bool xe_device_wedged(struct xe_device *xe) return atomic_read(&xe->wedged.flag); } +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +static inline bool xe_debug_page_size_supported(struct xe_device *xe) +{ + return IS_DGFX(xe); +} + +static inline bool xe_debug_page_size_mode_not_none(struct xe_device *xe) +{ + enum xe_page_size_alloc_ctrl_mode mode; + + if (!xe_debug_page_size_supported(xe)) + return false; + + mode = READ_ONCE(xe->page_size_alloc_ctrl.mode); + + return mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M || + mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G || + mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED; +} + +static inline bool xe_debug_page_size_mode_is_mixed(struct xe_device *xe) +{ + enum xe_page_size_alloc_ctrl_mode mode; + + if (!xe_debug_page_size_supported(xe)) + return false; + + mode = READ_ONCE(xe->page_size_alloc_ctrl.mode); + + return mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED; +} +#else +static inline bool xe_debug_page_size_supported(struct xe_device *xe) +{ + return false; +} + +static inline bool xe_debug_page_size_mode_not_none(struct xe_device *xe) +{ + return false; +} + +static inline bool xe_debug_page_size_mode_is_mixed(struct xe_device *xe) +{ + return false; +} +#endif + void xe_device_set_wedged_method(struct xe_device *xe, unsigned long method); void xe_device_declare_wedged(struct xe_device *xe); int xe_device_validate_wedged_mode(struct xe_device *xe, unsigned int mode); diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h index 860ad322237f..03a7bb08adf7 100644 --- a/drivers/gpu/drm/xe/xe_device_types.h +++ b/drivers/gpu/drm/xe/xe_device_types.h @@ -61,6 +61,23 @@ enum xe_wedged_mode { XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET = 2, }; +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +/** + * enum xe_page_size_alloc_ctrl_mode - User BO page-size allocation control modes + * @XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE: Use the normal allocation policy + * @XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M: Force user BO allocations to 2M pages + * @XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G: Force user BO allocations to 1G pages + * @XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED: Select page sizes in round-robin order + * (4K, 64K, 2M, 1G) + */ +enum xe_page_size_alloc_ctrl_mode { + XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE = 0, + XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M, + XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G, + XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED +}; +#endif + #define XE_BO_INVALID_OFFSET LONG_MAX #define GRAPHICS_VER(xe) ((xe)->info.graphics_verx100 / 100) @@ -478,6 +495,20 @@ struct xe_device { /** @late_bind: xe mei late bind interface */ struct xe_late_bind late_bind; +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE + /** + * @page_size_alloc_ctrl: User BO page-size allocation + * debug control state + */ + struct { + /** @page_size_alloc_ctrl.mode: xe page size allocation control mode */ + enum xe_page_size_alloc_ctrl_mode mode; + /** @page_size_alloc_ctrl.cur_index: Round-robin index used by mixed mode */ + u32 cur_index; + /** @page_size_alloc_ctrl.lock: Protects @mode and @cur_index */ + struct mutex lock; + } page_size_alloc_ctrl; +#endif /** @oa: oa observation subsystem */ struct xe_oa oa; From ceb4e4f0df78432128e11dca364207bc7856a339 Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:39 +0530 Subject: [PATCH 77/81] drm/xe/debugfs: add page-size allocation mode knob Expose a debugfs control to override the page-size allocation mode used for user BOs. The interface allows switching between the default allocation policy, forced 2M, forced 1G, and mixed allocation modes at runtime. This provides a simple way to validate behavior and debug page-size-dependent allocation flows. The debugfs entry is built only when CONFIG_DRM_XE_DEBUG_PAGE_SIZE is enabled. v2 - update changelog to match mutex-based cur_index handling - reset cur_index when switching to mixed mode (sashiko) v3 - add CONFIG guard for page-size allocation debugfs support (Himal) - create debugfs entry under CONFIG_DRM_XE_DEBUG_PAGE_SIZE v4 - reorderd this patch with kconfig patch to ensure patch builds - Gurding this debug knob for only discrete graphics v5(Himal) - Guard all page size calls with CONFIG_DRM_XE_DEBUG_PAGE_SIZE v8(Himal) - For read/show used READ_ONCE instead lock - to match Reader used WRITE_ONCE under lock protection - change modes to string format to read/writer for debugfs v9(Himal) - Add an OOB guard for mode in page_size_alloc_mode_show(). This check makes the function display "unknown" if mode has been maliciously altered by KMD, preventing out-of-bounds access. Under normal operation, values set through debugfs are validated, so OOB values should not occur. - simplify mode-to-string lookup using page_size_alloc_mode_names[] - use sysfs_match_string() to parse page_size_alloc_mode writes Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-3-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_debugfs.c | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c index 5a3877fcb0f0..8de78cd0aa03 100644 --- a/drivers/gpu/drm/xe/xe_debugfs.c +++ b/drivers/gpu/drm/xe/xe_debugfs.c @@ -614,6 +614,72 @@ static const struct file_operations disable_late_binding_fops = { .write = disable_late_binding_set, }; +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +static const char * const page_size_alloc_mode_names[] = { + [XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE] = "none", + [XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M] = "only_2m", + [XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G] = "only_1g", + [XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED] = "mixed", +}; + +static ssize_t page_size_alloc_mode_show(struct file *f, char __user *ubuf, + size_t size, loff_t *pos) +{ + struct xe_device *xe = file_inode(f)->i_private; + char buf[32]; + int len; + enum xe_page_size_alloc_ctrl_mode mode; + + mode = READ_ONCE(xe->page_size_alloc_ctrl.mode); + if (mode >= ARRAY_SIZE(page_size_alloc_mode_names) || + !page_size_alloc_mode_names[mode]) + len = scnprintf(buf, sizeof(buf), "unknown\n"); + else + len = scnprintf(buf, sizeof(buf), "%s\n", + page_size_alloc_mode_names[mode]); + return simple_read_from_buffer(ubuf, size, pos, buf, len); +} + +static ssize_t page_size_alloc_mode_set(struct file *f, const char __user *ubuf, + size_t size, loff_t *pos) +{ + struct xe_device *xe = file_inode(f)->i_private; + int ret; + char buf[32]; + int mode; + + if (*pos) + return -ESPIPE; + + if (size > sizeof(buf) - 1) + return -EINVAL; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, pos, ubuf, size); + if (ret < 0) + return ret; + buf[ret] = '\0'; + + mode = sysfs_match_string(page_size_alloc_mode_names, buf); + if (mode < 0) + return mode; + + mutex_lock(&xe->page_size_alloc_ctrl.lock); + if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED) + xe->page_size_alloc_ctrl.cur_index = 0; + WRITE_ONCE(xe->page_size_alloc_ctrl.mode, + (enum xe_page_size_alloc_ctrl_mode)mode); + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + + return size; +} + +static const struct file_operations page_size_alloc_mode_fops = { + .owner = THIS_MODULE, + .read = page_size_alloc_mode_show, + .write = page_size_alloc_mode_set, +}; +#endif + void xe_debugfs_register(struct xe_device *xe) { struct ttm_device *bdev = &xe->ttm; @@ -665,6 +731,18 @@ void xe_debugfs_register(struct xe_device *xe) debugfs_create_file("disable_late_binding", 0600, root, xe, &disable_late_binding_fops); +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE + /* + * Expose a debugfs knob to control user BO page-size allocation: + * "none" - default behavior + * "only_2m" - force 2M page allocations + * "only_1g" - force 1G page allocations + * "mixed" - select 4K, 64K, 2M, and 1G in round-robin order + */ + if (xe_debug_page_size_supported(xe)) + debugfs_create_file("page_size_alloc_mode", 0600, root, xe, + &page_size_alloc_mode_fops); +#endif /* * Don't expose page reclaim configuration file if not supported by the * hardware initially. From caf4fd4eb805f227b9bfe7811cc58a9d91a12430 Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:40 +0530 Subject: [PATCH 78/81] drm/xe: add XE_BO_FLAG_NEEDS_1G for minimum page-size sizing Add XE_BO_FLAG_NEEDS_1G to mark BOs that require 1G minimum page-size sizing. Update xe_bo_init_locked() to honor the new flag in the existing VRAM/stolen-memory minimum page-size sizing path. When XE_BO_FLAG_NEEDS_1G is set, the BO size is rounded up to 1G. Otherwise, the existing 2M and 64K sizing behavior is preserved. If multiple minimum page-size flags are set, the largest requirement takes precedence: 1G over 2M over 64K. v3 - commit message reworded Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-4-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_bo.c | 12 ++++++++++-- drivers/gpu/drm/xe/xe_bo.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index b1fdc802d27b..1c0b34c2c4ac 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -2352,8 +2352,16 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo, if (flags & (XE_BO_FLAG_VRAM_MASK | XE_BO_FLAG_STOLEN) && !(flags & XE_BO_FLAG_IGNORE_MIN_PAGE_SIZE) && ((xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K) || - (flags & (XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M)))) { - size_t align = flags & XE_BO_FLAG_NEEDS_2M ? SZ_2M : SZ_64K; + (flags & (XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M | + XE_BO_FLAG_NEEDS_1G)))) { + size_t align; + + if (flags & XE_BO_FLAG_NEEDS_1G) + align = SZ_1G; + else if (flags & XE_BO_FLAG_NEEDS_2M) + align = SZ_2M; + else + align = SZ_64K; aligned_size = ALIGN(size, align); if (type != ttm_bo_type_device) diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index 7ae1d9ac0574..c6d80e1bd6e7 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -52,6 +52,7 @@ #define XE_BO_FLAG_CPU_ADDR_MIRROR BIT(24) #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) /* this one is trigger internally only */ #define XE_BO_FLAG_INTERNAL_TEST BIT(30) From 2285c120e1009063d97d15e3d7b18c88356afaec Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:41 +0530 Subject: [PATCH 79/81] drm/xe: apply debug page-size allocation policy to user BOs Apply the debug page-size allocation policy during user BO creation. When page-size allocation control is enabled, override the user BO page-size selection flags based on the selected debug mode and round the requested size up to the corresponding granularity: - 2M mode selects 2M handling - 1G mode selects 1G handling - mixed mode selects the page size from the current mixed-mode index This is intended for internal debug and validation flows. When the control mode is left at the default setting, the normal user BO creation path is unchanged. v2 - ensure debug page-size allocation does not affect the default path (sashiko) - rework synchronization for concurrent access (sashiko) - refactor commit message for readability v3 - update user BO size alignment based on debug policy mode - reword commit message - ensure normal user flow is unchanged when debug policy is disabled v4(sashiko) - limit debug page-size policy application to VRAM BOs - do not override preexisting page-size requirement flags - advance mixed-mode index only after successful BO create ioctl completion - add overflow checks before ALIGN() in debug page-size handling - ensure CONFIG_DRM_XE_DEBUG_PAGE_SIZE enabled and it is dgfx v5(Himal) v5: - Guard debug page-size policy paths with CONFIG_DRM_XE_DEBUG_PAGE_SIZE - Leave the normal BO creation path unchanged when no debug mode is selected v8(Himal) - Avoid current index increment for system BO's - Simplify mixed mode align logic by changing array to struct array - Have a inline check if it is on debug mode or not - Avoid condition compiled debug in function code blocks Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-5-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_bo.c | 148 +++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index 1c0b34c2c4ac..dde309821237 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -2652,6 +2652,145 @@ static struct xe_bo *xe_bo_create_novm(struct xe_device *xe, struct xe_tile *til return ret ? ERR_PTR(ret) : bo; } +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +static void xe_bo_debug_mixed_mode_cur_index_advance(struct xe_device *xe, struct xe_bo *bo) +{ + if (!xe_debug_page_size_mode_is_mixed(xe)) + return; + + if (!(bo->flags & XE_BO_FLAG_VRAM_MASK) || + !(bo->flags & XE_BO_FLAG_USER)) + return; + + mutex_lock(&xe->page_size_alloc_ctrl.lock); + if (xe->page_size_alloc_ctrl.mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED) + xe->page_size_alloc_ctrl.cur_index++; + mutex_unlock(&xe->page_size_alloc_ctrl.lock); +} + +static bool xe_size_align_overflows(size_t size, size_t align) +{ + return size > SIZE_MAX - (align - 1); +} + +static u32 get_flag_from_cur_index_in_mixed_mode(struct xe_device *xe, size_t *align_size, + int *err) +{ + static const struct { + u32 flag; + size_t align; + } map[] = { + { 0, SZ_4K }, /* default: 4K, no flag */ + { XE_BO_FLAG_NEEDS_64K, SZ_64K }, + { XE_BO_FLAG_NEEDS_2M, SZ_2M }, + { XE_BO_FLAG_NEEDS_1G, SZ_1G }, + }; + u32 idx; + const typeof(*map) *entry; + + lockdep_assert_held(&xe->page_size_alloc_ctrl.lock); + + *err = 0; + idx = xe->page_size_alloc_ctrl.cur_index % ARRAY_SIZE(map); + + entry = &map[idx]; + + if (!entry->flag) + return 0; + + if (xe_size_align_overflows(*align_size, entry->align)) { + *err = -EINVAL; + return 0; + } + *align_size = ALIGN(*align_size, entry->align); + + return entry->flag; +} + +static int xe_bo_apply_debug_page_size_policy(struct xe_device *xe, + u32 *bo_flags, + size_t *size) +{ + enum xe_page_size_alloc_ctrl_mode mode; + u32 want = 0; + size_t align_size = *size; + int err = 0; + + /* + * The debug page-size policy is only meaningful for BOs placed in + * VRAM, where the downstream BO init path can + * actually honor the corresponding minimum page-size requirement. + */ + if (!(*bo_flags & XE_BO_FLAG_VRAM_MASK)) + return 0; + + /* + * Do not override existing page-size requirement flags, since they + * may reflect functional requirements for specific BO types. + */ + if (*bo_flags & (XE_BO_FLAG_NEEDS_64K | + XE_BO_FLAG_NEEDS_2M | + XE_BO_FLAG_NEEDS_1G)) + return 0; + + if (!READ_ONCE(xe->page_size_alloc_ctrl.mode)) + return 0; + + mutex_lock(&xe->page_size_alloc_ctrl.lock); + + mode = xe->page_size_alloc_ctrl.mode; + if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE) { + goto out_unlock; + } else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M) { + if (xe_size_align_overflows(align_size, SZ_2M)) { + err = -EINVAL; + goto out_unlock; + } + want = XE_BO_FLAG_NEEDS_2M; + align_size = ALIGN(align_size, SZ_2M); + } else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G) { + if (xe_size_align_overflows(align_size, SZ_1G)) { + err = -EINVAL; + goto out_unlock; + } + want = XE_BO_FLAG_NEEDS_1G; + align_size = ALIGN(align_size, SZ_1G); + } else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED) { + want = get_flag_from_cur_index_in_mixed_mode(xe, &align_size, &err); + if (err) + goto out_unlock; + } else { + goto out_unlock; + } + + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + + *bo_flags |= want; + /* + * Apply the debug page-size policy by rounding the user BO size up to + * the selected granularity. + */ + *size = align_size; + return err; + +out_unlock: + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + return err; +} +#else +static int xe_bo_apply_debug_page_size_policy(struct xe_device *xe, + u32 *bo_flags, + size_t *size) +{ + return 0; +} + +static void xe_bo_debug_mixed_mode_cur_index_advance(struct xe_device *xe, + struct xe_bo *bo) +{ +} +#endif + /** * xe_bo_create_user() - Create a user BO * @xe: The xe device. @@ -2672,9 +2811,16 @@ struct xe_bo *xe_bo_create_user(struct xe_device *xe, u32 flags, struct drm_exec *exec) { struct xe_bo *bo; + int err = 0; flags |= XE_BO_FLAG_USER; + if (xe_debug_page_size_mode_not_none(xe)) { + err = xe_bo_apply_debug_page_size_policy(xe, &flags, &size); + if (err) + return ERR_PTR(err); + } + if (vm || exec) { xe_assert(xe, exec); bo = __xe_bo_create_locked(xe, NULL, vm, size, 0, ~0ULL, @@ -3489,6 +3635,8 @@ int xe_gem_create_ioctl(struct drm_device *dev, void *data, if (err) goto out_bulk; + xe_bo_debug_mixed_mode_cur_index_advance(xe, bo); + args->handle = handle; goto out_put; From 41a74ed31f0bb87035eef9bd4efd92b4e82bcc99 Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:42 +0530 Subject: [PATCH 80/81] drm/xe/pt: allow selecting the bind leaf PTE level Add a target_leaf_level field to the page-table bind walk and use it to control the level at which leaf entries are emitted. By default, the bind walk emits level-0 leaf PTEs and relies on xe_pt_hugepte_possible() to select huge mappings when possible. Add an explicit target leaf level so the walk can stop earlier when the VMA requests a larger mapping size. Use level 1 for 2M PDE mappings and level 2 for 1G PDP mappings, while keeping level 0 for normal mappings. The existing huge-page heuristic is preserved for the default level-0 path. This allows the bind path to emit 2M and 1G leaf entries when requested by the VMA, while still validating alignment and size requirements. v2 - avoid using max_level to control walk depth - use target_leaf_level to preserve the normal walk behavior - keep the default huge-page heuristic only for the level-0 path - refine commit message v3 - reword commit message v4 - allow fallback to smaller huge-page levels for non-zero target_leaf_level - avoid constraining clear_pt walks by target_leaf_level v5(Himal) - Restrict only intended level in debug page size policy mode - Allow the normal path to proceed smoothly when no debug page-size mode is selected. v8 (Himal) - Drop https://patchwork.freedesktop.org/patch/740059/?series=168905&rev=5 patch and populate target_leaf_level from bo flags - populate target_leaf_level if it is in debug page size mode otherwise fill with 0 which is having no effect on the normal flow v10 (Himal) - use xe_bo_is_vram() instead of raw VRAM flag checks so huge-page selection is based on BO VRAM placement. Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-6-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/xe_pt.c | 76 +++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_pt.c b/drivers/gpu/drm/xe/xe_pt.c index 0e9d669620b9..8cd89c4f49d0 100644 --- a/drivers/gpu/drm/xe/xe_pt.c +++ b/drivers/gpu/drm/xe/xe_pt.c @@ -302,6 +302,14 @@ struct xe_pt_stage_bind_walk { bool needs_64K; /** @clear_pt: clear page table entries during the bind walk */ bool clear_pt; + /** + * @target_leaf_level: Page-table level at which to emit leaf PTEs + * 0 for normal 4K/64K mappings, 1 for 2M huge pages, and 2 for 1G huge + * pages. The walk still traverses from the root down; this field tells + * xe_pt_stage_bind_entry() to treat the selected level as a leaf instead + * of descending further. + */ + u32 target_leaf_level; /** * @vma: VMA being mapped */ @@ -514,6 +522,39 @@ xe_pt_is_pte_ps64K(u64 addr, u64 next, struct xe_pt_stage_bind_walk *xe_walk) return xe_walk->found_64K; } +static bool xe_pt_huge_leaf_allowed(u64 addr, u64 next, unsigned int level, + struct xe_pt_stage_bind_walk *xe_walk) +{ + if (xe_walk->clear_pt) + return xe_pt_hugepte_possible(addr, next, level, xe_walk); + + if (!xe_debug_page_size_supported(xe_walk->vm->xe)) + return xe_pt_hugepte_possible(addr, next, level, xe_walk); + + if (!xe_walk->target_leaf_level) + return xe_pt_hugepte_possible(addr, next, level, xe_walk); + + if (level == xe_walk->target_leaf_level) + return xe_pt_hugepte_possible(addr, next, level, xe_walk); + + return false; +} + +static bool xe_pt_exact_leaf_required_but_invalid(u64 addr, u64 next, + unsigned int level, + struct xe_pt_stage_bind_walk *xe_walk) +{ + struct xe_device *xe = xe_walk->vm->xe; + + if (!xe_debug_page_size_mode_not_none(xe)) + return false; + + return !xe_walk->clear_pt && + xe_walk->target_leaf_level && + level == xe_walk->target_leaf_level && + !xe_pt_hugepte_possible(addr, next, level, xe_walk); +} + static int xe_pt_stage_bind_entry(struct xe_ptw *parent, pgoff_t offset, unsigned int level, u64 addr, u64 next, @@ -531,8 +572,18 @@ xe_pt_stage_bind_entry(struct xe_ptw *parent, pgoff_t offset, int ret = 0; u64 pte; - /* Is this a leaf entry ?*/ - if (level == 0 || xe_pt_hugepte_possible(addr, next, level, xe_walk)) { + if (xe_pt_exact_leaf_required_but_invalid(addr, next, level, xe_walk)) + return -EINVAL; + + /* + * Is this a leaf entry? + * Always create a 4K leaf at level 0. For huge pages (level > 0), + * validate alignment and size with xe_pt_hugepte_possible(). + * When target_leaf_level is non-zero, only that huge-page level is + * accepted for normal bind walks. Clear walks remain unconstrained so + * existing huge leaves can be cleared without descending further. + */ + if (level == 0 || xe_pt_huge_leaf_allowed(addr, next, level, xe_walk)) { struct xe_res_cursor *curs = xe_walk->curs; struct xe_bo *bo = xe_vma_bo(xe_walk->vma); bool is_null_or_purged = xe_vma_is_null(xe_walk->vma) || @@ -682,6 +733,26 @@ static bool xe_atomic_for_system(struct xe_vm *vm, struct xe_vma *vma) (bo && xe_bo_has_single_placement(bo)))); } +static u32 xe_pt_target_leaf_level_from_bo(struct xe_device *xe, + struct xe_vma *vma) +{ + struct xe_bo *bo = xe_vma_bo(vma); + + if (!xe_debug_page_size_mode_not_none(xe)) + return 0; + + if (!bo || !xe_bo_is_vram(bo) || !(bo->flags & XE_BO_FLAG_USER)) + return 0; + + if (bo->flags & XE_BO_FLAG_NEEDS_1G) + return 2; + + if (bo->flags & XE_BO_FLAG_NEEDS_2M) + return 1; + + return 0; +} + /** * xe_pt_stage_bind() - Build a disconnected page-table tree for a given address * range. @@ -774,6 +845,7 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma, xe_svm_notifier_unlock(vm); } + xe_walk.target_leaf_level = xe_pt_target_leaf_level_from_bo(xe, vma); xe_walk.needs_64K = (vm->flags & XE_VM_FLAG_64K); if (clear_pt) { xe_assert(xe, !range); From 60693b580ab5f7f3158adcc2bab5ae816edd49ff Mon Sep 17 00:00:00 2001 From: Nareshkumar Gollakoti Date: Wed, 29 Jul 2026 17:48:43 +0530 Subject: [PATCH 81/81] drm/xe/tests: add live KUnit coverage for BO page-size allocation modes Add live KUnit coverage for the debug-controlled BO page-size allocation modes. The new tests cover forced 2M mode, forced 1G mode, and mixed mode. They verify that user BO creation applies the expected NEEDS_* flags, that no unexpected page-size flags are added in the forced modes, that BO size is rounded as expected, and that page_alignment matches the selected leaf size. The mixed-mode test does not assume a strict per-allocation rotation sequence, since the device-global mixed-mode index may be perturbed by concurrent BO creation on a live system. Instead, it validates that each allocation results in one valid mixed-mode page-size outcome. Treat transient VRAM allocation failures as skipped test cases so the tests can run in varying live environments without producing false failures. v3 - address review comments - rework mixed-mode test to avoid assuming strict rotation order - reword commit message v4 - skip VRAM-targeted live tests on non-dGFX devices v5 - advance the mixed-mode index in the test v6 - Gaurd kunit tests under CONFIG_DRM_XE_DEBUG_PAGE_SIZE v9 - consider XE_VRAM_FLAGS_NEED64K in mixed mode for certain platoform min alignment expectations. Signed-off-by: Nareshkumar Gollakoti Reviewed-by: Himal Prasad Ghimiray Link: https://patch.msgid.link/20260729121843.1255891-7-naresh.kumar.g@intel.com Signed-off-by: Himal Prasad Ghimiray --- drivers/gpu/drm/xe/tests/xe_bo.c | 242 ++++++++++++++++++++ drivers/gpu/drm/xe/tests/xe_live_test_mod.c | 6 + 2 files changed, 248 insertions(+) diff --git a/drivers/gpu/drm/xe/tests/xe_bo.c b/drivers/gpu/drm/xe/tests/xe_bo.c index 49c95ed67d7e..6a17e13d58cf 100644 --- a/drivers/gpu/drm/xe/tests/xe_bo.c +++ b/drivers/gpu/drm/xe/tests/xe_bo.c @@ -22,6 +22,231 @@ #include "xe_pci.h" #include "xe_pm.h" +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +struct page_size_alloc_saved { + enum xe_page_size_alloc_ctrl_mode mode; + u32 cur_index; +}; + +/* Caller must hold xe->page_size_alloc_ctrl.lock. */ +static void page_size_alloc_save(struct xe_device *xe, + struct page_size_alloc_saved *s) +{ + s->mode = xe->page_size_alloc_ctrl.mode; + s->cur_index = xe->page_size_alloc_ctrl.cur_index; +} + +static void page_size_alloc_restore(struct xe_device *xe, + const struct page_size_alloc_saved *s) +{ + mutex_lock(&xe->page_size_alloc_ctrl.lock); + xe->page_size_alloc_ctrl.mode = s->mode; + xe->page_size_alloc_ctrl.cur_index = s->cur_index; + mutex_unlock(&xe->page_size_alloc_ctrl.lock); +} + +/* Expected properties for a forced page-size allocation mode. */ +struct leaf_info { + u64 leaf; + u64 alloc_size; + u32 flag; + const char *name; +}; + +static const struct leaf_info leaf_2m = { + .leaf = SZ_2M, + .alloc_size = SZ_2M - PAGE_SIZE, + .flag = XE_BO_FLAG_NEEDS_2M, + .name = "2M", +}; + +static const struct leaf_info leaf_1g = { + .leaf = SZ_1G, + .alloc_size = SZ_1G - PAGE_SIZE, + .flag = XE_BO_FLAG_NEEDS_1G, + .name = "1G", +}; + +static void run_only_leaf(struct kunit *test, + enum xe_page_size_alloc_ctrl_mode mode, + const struct leaf_info *li) +{ + struct xe_device *xe = test->priv; + struct page_size_alloc_saved saved; + struct xe_bo *bo; + struct ttm_buffer_object *ttm_bo; + u32 other_flags; + + if (!IS_DGFX(xe)) { + kunit_skip(test, "requires dGFX VRAM"); + return; + } + + mutex_lock(&xe->page_size_alloc_ctrl.lock); + page_size_alloc_save(xe, &saved); + xe->page_size_alloc_ctrl.mode = mode; + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + + bo = xe_bo_create_user(xe, NULL, li->alloc_size, + DRM_XE_GEM_CPU_CACHING_WC, + XE_BO_FLAG_VRAM0, NULL); + if (IS_ERR(bo)) { + page_size_alloc_restore(xe, &saved); + if (PTR_ERR(bo) == -ENOSPC) { + kunit_skip(test, + "no contiguous %s VRAM available right now", + li->name); + return; + } + + KUNIT_FAIL(test, "%s BO alloc failed: %pe", li->name, bo); + return; + } + + ttm_bo = &bo->ttm; + + /* 1) The mode added the right NEEDS_* flag. */ + KUNIT_EXPECT_TRUE_MSG(test, bo->flags & li->flag, + "%s: flag missing, flags=0x%x", + li->name, bo->flags); + + /* 2) No other NEEDS_* flags accidentally tagged on. */ + other_flags = (XE_BO_FLAG_NEEDS_64K | + XE_BO_FLAG_NEEDS_2M | + XE_BO_FLAG_NEEDS_1G) & ~li->flag; + KUNIT_EXPECT_FALSE_MSG(test, bo->flags & other_flags, + "%s: stray flags=0x%x", + li->name, bo->flags); + /* 3) BO size was rounded up to the expected leaf size. */ + KUNIT_EXPECT_EQ_MSG(test, xe_bo_size(bo), li->leaf, + "%s: bo size=%llu expected=%llu", + li->name, + (u64)xe_bo_size(bo), + (u64)li->leaf); + /* + * 4) Allocator honored the requested alignment. + * ttm_bo->page_alignment is stored in PAGE_SIZE units, so compare against + * the expected leaf size converted with >> PAGE_SHIFT. + */ + KUNIT_EXPECT_EQ_MSG(test, ttm_bo->page_alignment, + li->leaf >> PAGE_SHIFT, + "%s: page_alignment=%u pages expected=%llu pages", + li->name, ttm_bo->page_alignment, + (u64)(li->leaf >> PAGE_SHIFT)); + + xe_bo_put(bo); + page_size_alloc_restore(xe, &saved); +} + +static void xe_bo_page_size_alloc_only_2m(struct kunit *test) +{ + run_only_leaf(test, XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M, &leaf_2m); +} + +static void xe_bo_page_size_alloc_only_1g(struct kunit *test) +{ + run_only_leaf(test, XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G, &leaf_1g); +} + +static void xe_bo_page_size_alloc_mixed_bos(struct kunit *test) +{ + struct xe_device *xe = test->priv; + struct page_size_alloc_saved saved; + struct xe_bo *bo; + struct ttm_buffer_object *ttm_bo; + u32 all_flags = XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M | + XE_BO_FLAG_NEEDS_1G; + u32 flags; + u64 expected_align; + int i; + const int n = 4; + + if (!IS_DGFX(xe)) { + kunit_skip(test, "requires dGFX VRAM"); + return; + } + + mutex_lock(&xe->page_size_alloc_ctrl.lock); + page_size_alloc_save(xe, &saved); + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + + for (i = 0; i < n; i++) { + mutex_lock(&xe->page_size_alloc_ctrl.lock); + xe->page_size_alloc_ctrl.mode = XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED; + xe->page_size_alloc_ctrl.cur_index = i; + mutex_unlock(&xe->page_size_alloc_ctrl.lock); + /* + * Request a size valid for any mixed-mode slot. Since cur_index is + * device-global and may be perturbed by concurrent allocations on + * a live system, do not assume this iteration will see a specific + * slot. + */ + bo = xe_bo_create_user(xe, NULL, SZ_1G, + DRM_XE_GEM_CPU_CACHING_WC, + XE_BO_FLAG_VRAM0, NULL); + if (IS_ERR(bo)) { + int err = PTR_ERR(bo); + + page_size_alloc_restore(xe, &saved); + if (err == -ENOSPC) { + kunit_skip(test, + "mixed mode BO allocation unavailable: %d", + err); + return; + } + KUNIT_FAIL(test, "iter=%d alloc failed: %pe", i, bo); + return; + } + + ttm_bo = &bo->ttm; + flags = bo->flags & all_flags; + /* + * Mixed mode may result in: + * 0-> default platform VRAM alignment + * XE_BO_FLAG_NEEDS_64K + * XE_BO_FLAG_NEEDS_2M + * XE_BO_FLAG_NEEDS_1G + * Any other combination is invalid. + */ + if (flags == 0) { + expected_align = SZ_4K; + if (xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K) + expected_align = SZ_64K; + } else if (flags == XE_BO_FLAG_NEEDS_64K) { + expected_align = SZ_64K; + } else if (flags == XE_BO_FLAG_NEEDS_2M) { + expected_align = SZ_2M; + } else if (flags == XE_BO_FLAG_NEEDS_1G) { + expected_align = SZ_1G; + } else { + KUNIT_FAIL(test, + "iter=%d invalid mixed-mode flags: 0x%x", + i, flags); + xe_bo_put(bo); + page_size_alloc_restore(xe, &saved); + return; + } + /* + * BO size should remain valid for the selected mode. Since the + * request is SZ_1G, it should remain unchanged regardless of the + * selected page-size policy. + */ + KUNIT_EXPECT_EQ_MSG(test, xe_bo_size(bo), (u64)SZ_1G, + "iter=%d size=%llu expected=%llu", + i, + (u64)xe_bo_size(bo), + (u64)SZ_1G); + KUNIT_EXPECT_EQ_MSG(test, ttm_bo->page_alignment, + expected_align >> PAGE_SHIFT, + "iter=%d flags=0x%x page_alignment=%u pages expected=%llu pages", + i, flags, ttm_bo->page_alignment, + (u64)(expected_align >> PAGE_SHIFT)); + xe_bo_put(bo); + } + page_size_alloc_restore(xe, &saved); +} +#endif + static int ccs_test_migrate(struct xe_tile *tile, struct xe_bo *bo, bool clear, u64 get_val, u64 assign_val, struct kunit *test, struct drm_exec *exec) @@ -609,6 +834,23 @@ static struct kunit_case xe_bo_tests[] = { {} }; +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +static struct kunit_case xe_bo_page_size_alloc_cases[] = { + KUNIT_CASE_PARAM(xe_bo_page_size_alloc_only_2m, xe_pci_live_device_gen_param), + KUNIT_CASE_PARAM(xe_bo_page_size_alloc_only_1g, xe_pci_live_device_gen_param), + KUNIT_CASE_PARAM(xe_bo_page_size_alloc_mixed_bos, xe_pci_live_device_gen_param), + {} +}; + +VISIBLE_IF_KUNIT +struct kunit_suite xe_bo_page_size_alloc_suite = { + .name = "xe_bo_page_size_alloc", + .test_cases = xe_bo_page_size_alloc_cases, + .init = xe_kunit_helper_xe_device_live_test_init, +}; +EXPORT_SYMBOL_IF_KUNIT(xe_bo_page_size_alloc_suite); +#endif + VISIBLE_IF_KUNIT struct kunit_suite xe_bo_test_suite = { .name = "xe_bo", diff --git a/drivers/gpu/drm/xe/tests/xe_live_test_mod.c b/drivers/gpu/drm/xe/tests/xe_live_test_mod.c index c55e46f1ae92..87cd7db20e5f 100644 --- a/drivers/gpu/drm/xe/tests/xe_live_test_mod.c +++ b/drivers/gpu/drm/xe/tests/xe_live_test_mod.c @@ -11,6 +11,9 @@ extern struct kunit_suite xe_dma_buf_test_suite; extern struct kunit_suite xe_migrate_test_suite; extern struct kunit_suite xe_mocs_test_suite; extern struct kunit_suite xe_guc_g2g_test_suite; +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +extern struct kunit_suite xe_bo_page_size_alloc_suite; +#endif kunit_test_suite(xe_bo_test_suite); kunit_test_suite(xe_bo_shrink_test_suite); @@ -18,6 +21,9 @@ kunit_test_suite(xe_dma_buf_test_suite); kunit_test_suite(xe_migrate_test_suite); kunit_test_suite(xe_mocs_test_suite); kunit_test_suite(xe_guc_g2g_test_suite); +#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE +kunit_test_suite(xe_bo_page_size_alloc_suite); +#endif MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL");