summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-21 13:39:18 +0100
committerMark Brown <broonie@kernel.org>2026-10-21 13:39:19 +0100
commit3399dbeef25bdb764d6c3aa28bb6fca9061dcb76 (patch)
tree20de2cc0aab2f697fd030730f263428eff22bd33 /drivers/gpu/drm
parent7a87bc22aff2b8ed731f46ee0aa012df80e47ebc (diff)
parent26f7daea5b0667a0af06c60dc204e7cf4856182c (diff)
downloadlinux-next-3399dbeef25bdb764d6c3aa28bb6fca9061dcb76.tar.gz
linux-next-3399dbeef25bdb764d6c3aa28bb6fca9061dcb76.zip
Merge branch 'drm-xe-next' of https://gitlab.freedesktop.org/drm/xe/kernel.git
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r--drivers/gpu/drm/xe/Kconfig15
-rw-r--r--drivers/gpu/drm/xe/Kconfig.profile12
-rw-r--r--drivers/gpu/drm/xe/Makefile1
-rw-r--r--drivers/gpu/drm/xe/abi/xe_log_abi.h3
-rw-r--r--drivers/gpu/drm/xe/abi/xe_sigid_abi.h16
-rw-r--r--drivers/gpu/drm/xe/regs/xe_gt_regs.h3
-rw-r--r--drivers/gpu/drm/xe/regs/xe_pcode_regs.h4
-rw-r--r--drivers/gpu/drm/xe/tests/xe_dma_buf.c89
-rw-r--r--drivers/gpu/drm/xe/tests/xe_gt_sriov_pf_config_kunit.c5
-rw-r--r--drivers/gpu/drm/xe/tests/xe_guc_id_mgr_test.c689
-rw-r--r--drivers/gpu/drm/xe/xe_bo.c12
-rw-r--r--drivers/gpu/drm/xe/xe_debugfs.c4
-rw-r--r--drivers/gpu/drm/xe/xe_devcoredump.c4
-rw-r--r--drivers/gpu/drm/xe/xe_device.c37
-rw-r--r--drivers/gpu/drm/xe/xe_device.h2
-rw-r--r--drivers/gpu/drm/xe/xe_device_sysfs.c50
-rw-r--r--drivers/gpu/drm/xe/xe_device_types.h15
-rw-r--r--drivers/gpu/drm/xe/xe_drm_ras.c33
-rw-r--r--drivers/gpu/drm/xe/xe_drm_ras_types.h3
-rw-r--r--drivers/gpu/drm/xe/xe_execlist.c6
-rw-r--r--drivers/gpu/drm/xe/xe_ggtt.c9
-rw-r--r--drivers/gpu/drm/xe/xe_gpu_scheduler_types.h5
-rw-r--r--drivers/gpu/drm/xe/xe_gt.c29
-rw-r--r--drivers/gpu/drm/xe/xe_gt_sriov_pf_config.c21
-rw-r--r--drivers/gpu/drm/xe/xe_gt_sriov_pf_service.c1
-rw-r--r--drivers/gpu/drm/xe/xe_guc.c44
-rw-r--r--drivers/gpu/drm/xe/xe_guc_ct.c348
-rw-r--r--drivers/gpu/drm/xe/xe_guc_ct.h4
-rw-r--r--drivers/gpu/drm/xe/xe_guc_ct_types.h3
-rw-r--r--drivers/gpu/drm/xe/xe_guc_exec_queue_types.h5
-rw-r--r--drivers/gpu/drm/xe/xe_guc_id_mgr.c461
-rw-r--r--drivers/gpu/drm/xe/xe_guc_id_mgr.h31
-rw-r--r--drivers/gpu/drm/xe/xe_guc_log.c222
-rw-r--r--drivers/gpu/drm/xe/xe_guc_log.h11
-rw-r--r--drivers/gpu/drm/xe/xe_guc_log_types.h20
-rw-r--r--drivers/gpu/drm/xe/xe_guc_submit.c93
-rw-r--r--drivers/gpu/drm/xe/xe_guc_submit.h2
-rw-r--r--drivers/gpu/drm/xe/xe_guc_types.h8
-rw-r--r--drivers/gpu/drm/xe/xe_hw_engine.c54
-rw-r--r--drivers/gpu/drm/xe/xe_hw_error.c6
-rw-r--r--drivers/gpu/drm/xe/xe_hwmon.c611
-rw-r--r--drivers/gpu/drm/xe/xe_irq.c3
-rw-r--r--drivers/gpu/drm/xe/xe_lmtt.c10
-rw-r--r--drivers/gpu/drm/xe/xe_lrc.c4
-rw-r--r--drivers/gpu/drm/xe/xe_memirq.c10
-rw-r--r--drivers/gpu/drm/xe/xe_mmio.c12
-rw-r--r--drivers/gpu/drm/xe/xe_module.c24
-rw-r--r--drivers/gpu/drm/xe/xe_nvm.c13
-rw-r--r--drivers/gpu/drm/xe/xe_pat.c17
-rw-r--r--drivers/gpu/drm/xe/xe_pat.h4
-rw-r--r--drivers/gpu/drm/xe/xe_pci.c14
-rw-r--r--drivers/gpu/drm/xe/xe_pci_types.h2
-rw-r--r--drivers/gpu/drm/xe/xe_pcode_api.h2
-rw-r--r--drivers/gpu/drm/xe/xe_ras.c312
-rw-r--r--drivers/gpu/drm/xe/xe_ras.h5
-rw-r--r--drivers/gpu/drm/xe/xe_ras_types.h85
-rw-r--r--drivers/gpu/drm/xe/xe_reg_sr.c11
-rw-r--r--drivers/gpu/drm/xe/xe_shrinker.c213
-rw-r--r--drivers/gpu/drm/xe/xe_shrinker.h7
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_debugfs.c500
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_debugfs.h14
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_event.c14
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_mailbox.c138
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_mailbox.h6
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h43
-rw-r--r--drivers/gpu/drm/xe/xe_sysctrl_types.h47
-rw-r--r--drivers/gpu/drm/xe/xe_ttm_vram_mgr.c68
-rw-r--r--drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h6
-rw-r--r--drivers/gpu/drm/xe/xe_tuning.c2
-rw-r--r--drivers/gpu/drm/xe/xe_uc_fw.c16
-rw-r--r--drivers/gpu/drm/xe/xe_validation.h2
-rw-r--r--drivers/gpu/drm/xe/xe_vm.c20
-rw-r--r--drivers/gpu/drm/xe/xe_vram.c40
73 files changed, 3891 insertions, 764 deletions
diff --git a/drivers/gpu/drm/xe/Kconfig b/drivers/gpu/drm/xe/Kconfig
index 4d7dcaff2b91..0d87251c5d7c 100644
--- a/drivers/gpu/drm/xe/Kconfig
+++ b/drivers/gpu/drm/xe/Kconfig
@@ -101,6 +101,21 @@ config DRM_XE_PAGEMAP
If in doubt say "Y".
+config DRM_XE_COMPRESS_DUMP
+ bool "Enable ZSTD compression for error dumps"
+ depends on DRM_XE
+ default y
+ select ZSTD_COMPRESS
+ help
+ Enable this option to compress GuC log and CTB dumps with ZSTD
+ compression. This can significantly reduce the size of error dumps
+ but requires additional memory for compression workspace.
+
+ Disable this option if you want to save memory or prefer uncompressed
+ error dumps for debugging.
+
+ If in doubt say "Y".
+
config DRM_XE_FORCE_PROBE
string "Force probe xe for selected Intel hardware IDs"
depends on DRM_XE
diff --git a/drivers/gpu/drm/xe/Kconfig.profile b/drivers/gpu/drm/xe/Kconfig.profile
index e07517d120e0..23045ed2aa94 100644
--- a/drivers/gpu/drm/xe/Kconfig.profile
+++ b/drivers/gpu/drm/xe/Kconfig.profile
@@ -74,3 +74,15 @@ config DRM_XE_ENABLE_SCHEDTIMEOUT_LIMIT
to apply to applicable user. For elevated user, all above MIN
and MAX values will apply when this configuration is enable to
apply limitation. By default limitation is applied.
+config DRM_XE_SHRINKER_NON_PURGEABLE_SEEKS
+ int "Default seeks value of the non-purgeable bo shrinker"
+ default 8
+ range 1 64
+ help
+ Default for the per-device shrinker_non_purgeable_seeks sysfs
+ attribute. This is the cost, as reported to the core shrinker, of
+ recreating non-purgeable buffer objects relative to purgeable
+ ones, which use DEFAULT_SEEKS (2). Higher values reduce memory
+ reclaim pressure on non-purgeable buffer objects, which need
+ GPU idling and writeback to be reclaimed, in favour of purgeable
+ ones and other kernel caches.
diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile
index 563c2f859361..10930c924070 100644
--- a/drivers/gpu/drm/xe/Makefile
+++ b/drivers/gpu/drm/xe/Makefile
@@ -129,6 +129,7 @@ xe-y += xe_bb.o \
xe_survivability_mode.o \
xe_sync.o \
xe_sysctrl.o \
+ xe_sysctrl_debugfs.o \
xe_sysctrl_event.o \
xe_sysctrl_mailbox.o \
xe_tile.o \
diff --git a/drivers/gpu/drm/xe/abi/xe_log_abi.h b/drivers/gpu/drm/xe/abi/xe_log_abi.h
index 526aadf85fe9..d7bd85ea9673 100644
--- a/drivers/gpu/drm/xe/abi/xe_log_abi.h
+++ b/drivers/gpu/drm/xe/abi/xe_log_abi.h
@@ -151,6 +151,9 @@ enum xe_log_location_bits {
define(DRIVER_HARDWARE, 3, GT, GT_TDR, "Graphics Technology") \
define(DRIVER_HARDWARE, 4, LMTT, IO_BUS, "LMEM Translation Table") \
define(DRIVER_HARDWARE, 5, MEMIRQ, IO_BUS, "Memory Based IRQ") \
+ define(DRIVER_HARDWARE, 6, VRAM, IO_BUS, "Video Memory/Local Memory") \
+ define(DRIVER_HARDWARE, 7, PAT, IO_BUS, "Page Attribute Table") \
+ define(DRIVER_HARDWARE, 8, HWMON, IO_BUS, "Hardware Monitoring") \
/* */ \
define(DRIVER_FEATURE, 1, PF, SW, "SR-IOV Physical Function") \
define(DRIVER_FEATURE, 2, VF, SW, "SR-IOV Virtual Function") \
diff --git a/drivers/gpu/drm/xe/abi/xe_sigid_abi.h b/drivers/gpu/drm/xe/abi/xe_sigid_abi.h
index 2f3440505f69..277b15fce9cc 100644
--- a/drivers/gpu/drm/xe/abi/xe_sigid_abi.h
+++ b/drivers/gpu/drm/xe/abi/xe_sigid_abi.h
@@ -10,14 +10,14 @@
* DOC: Xe Error Signatures (SIGID)
*
* What SIGID stands for
- * ---------------------
+ * =====================
*
* SIGID is short for *Signature Identifier*. It is a small, stable integer
* that names one of the *recognised fault sites* -- nothing more. It is the
* primary handle used for triage and maps directly to a specific report site.
*
* Numbering
- * ---------
+ * =========
*
* SIGIDs are a single flat list numbered sequentially within the assigned range,
* in the order the fault sites were introduced. Values are stable: once assigned
@@ -25,7 +25,7 @@
* SIGID value is deprecated in place, never re-purposed.
*
* Why this exists
- * ---------------
+ * ===============
*
* Today the driver reports faults with ad-hoc ``xe_err()`` / ``xe_gt_err()``
* strings that have no stable shape. That is fine for a human reading dmesg,
@@ -37,7 +37,7 @@
* firmware versions, and (eventually) across other Intel devices in a node.
*
* What a SIGID is not
- * -------------------
+ * ===================
*
* SIGID deliberately does not encode the detailed reason or the outcome. Those
* are carried alongside it::
@@ -51,7 +51,7 @@
* different severities depending on the instance and the recovery taken.
*
* When to use SIGID logging
- * -------------------------
+ * =========================
*
* The xe_log_*() helpers are for these recognised fault sites only --
* important, operator-relevant faults and events. The driver's only job is to
@@ -63,7 +63,7 @@
* those that correspond to a published fault site.
*
* SIGID log output (dmesg vs. the machine record)
- * -----------------------------------------------
+ * ===============================================
*
* The dmesg line stays close to a normal xe error message so it remains
* readable for admins; the only stable, machine-matchable token on it is
@@ -74,7 +74,7 @@
* carrying the same SIGID (generation is a planned follow-up).
*
* How to pick a SIGID (the uniqueness rule)
- * -----------------------------------------
+ * =========================================
*
* Pick per *report site*, not per incident. Each site emits the single most
* specific recognised SIGID *for that site* -- so the question is never
@@ -95,7 +95,7 @@
* after making significant changes to the site that emits this SIGID.
*
* Scope: software vs hardware emitted signatures
- * ----------------------------------------------
+ * ==============================================
*
* Some SIGID represents fault sites that the *driver itself* detects and
* reports from the software POV: probe abort, wedged, survivability, driver-
diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h
index 650f117c3e13..f5cd3a94e47f 100644
--- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h
+++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h
@@ -301,6 +301,9 @@
#define SERVICE_COPY_ENABLE XE_REG(0x9170)
#define FUSE_SERVICE_COPY_ENABLE_MASK REG_GENMASK(7, 0)
+#define RESOURCE_COPY_ENABLE XE_REG(0x9174)
+#define FUSE_RESOURCE_COPY_ENABLE_MASK REG_GENMASK(15, 0)
+
#define GDRST XE_REG(0x941c)
#define GRDOM_GUC REG_BIT(3)
#define GRDOM_FULL REG_BIT(0)
diff --git a/drivers/gpu/drm/xe/regs/xe_pcode_regs.h b/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
index c63b409d7a82..d9e4847dd68e 100644
--- a/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
+++ b/drivers/gpu/drm/xe/regs/xe_pcode_regs.h
@@ -28,6 +28,10 @@
#define BMG_PACKAGE_TEMPERATURE XE_REG(0x138434)
#define CRI_PACKAGE_ENERGY_STATUS XE_REG(0x138120)
+#define CRI_MSU_VRAM_ENABLE XE_REG(0x138340)
+#define CRI_PACKAGE_TEMPERATURE XE_REG(0x138344)
+#define CRI_VRAM_TEMPERATURE XE_REG(0x138348)
#define CRI_PLATFORM_ENERGY_STATUS XE_REG(0x138458)
+#define CRI_VRAM_TEMPERATURE_N(n) XE_REG(0x138a00 + ((n) * (sizeof(u32))))
#endif /* _XE_PCODE_REGS_H_ */
diff --git a/drivers/gpu/drm/xe/tests/xe_dma_buf.c b/drivers/gpu/drm/xe/tests/xe_dma_buf.c
index 0be8440b3976..792376e965f1 100644
--- a/drivers/gpu/drm/xe/tests/xe_dma_buf.c
+++ b/drivers/gpu/drm/xe/tests/xe_dma_buf.c
@@ -195,6 +195,79 @@ out:
drm_gem_object_put(&bo->ttm.base);
}
+/*
+ * Reproduce the move_notify crash path: an imported BO whose ttm.resource is
+ * NULL (as happens mid-teardown) must survive xe_dma_buf_move_notify(), which
+ * evicts it via xe_bo_evict() -> xe_evict_flags(), instead of dereferencing
+ * the NULL resource.
+ */
+static void xe_test_dmabuf_move_notify_no_resource(struct xe_device *xe)
+{
+ struct kunit *test = kunit_get_current_test();
+ struct dma_buf_test_params *params = to_dma_buf_test_params(test->priv);
+ struct drm_gem_object *import;
+ struct dma_buf *dmabuf;
+ struct xe_bo *bo;
+
+ /* Needs a separate, dynamically attached importer bo with move_notify. */
+ if (!params->force_different_devices || !is_dynamic(params))
+ return;
+
+ kunit_info(test, "running %s\n", __func__);
+ bo = xe_bo_create_user(xe, NULL, PAGE_SIZE, DRM_XE_GEM_CPU_CACHING_WC,
+ params->mem_mask, NULL);
+ if (IS_ERR(bo)) {
+ KUNIT_FAIL(test, "xe_bo_create() failed with err=%ld\n",
+ PTR_ERR(bo));
+ return;
+ }
+
+ dmabuf = xe_gem_prime_export(&bo->ttm.base, 0);
+ if (IS_ERR(dmabuf)) {
+ KUNIT_FAIL(test, "xe_gem_prime_export() failed with err=%ld\n",
+ PTR_ERR(dmabuf));
+ goto out;
+ }
+ bo->ttm.base.dma_buf = dmabuf;
+
+ import = xe_gem_prime_import(&xe->drm, dmabuf);
+ if (IS_ERR(import)) {
+ KUNIT_FAIL(test, "xe_gem_prime_import failed with err=%ld\n",
+ PTR_ERR(import));
+ } else {
+ struct xe_bo *import_bo = gem_to_xe_bo(import);
+ struct ttm_operation_ctx ctx = {
+ .interruptible = false,
+ .gfp_retry_mayfail = true,
+ };
+ struct ttm_placement empty = {};
+ int ret;
+
+ xe_bo_lock(import_bo, false);
+
+ /*
+ * Gut the importer to drop its resource the same way ttm does
+ * on teardown (validate to an empty placement), leaving a live,
+ * still-attached BO with no resource.
+ */
+ ret = ttm_bo_validate(&import_bo->ttm, &empty, &ctx);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_TRUE(test, !import_bo->ttm.resource);
+
+ /* Crash path: move_notify -> xe_bo_evict -> xe_evict_flags. */
+ xe_dma_buf_move_notify(import->import_attach);
+ KUNIT_EXPECT_TRUE(test, !import_bo->ttm.resource);
+
+ xe_bo_unlock(import_bo);
+ drm_gem_object_put(import);
+ }
+
+ bo->ttm.base.dma_buf = NULL;
+ dma_buf_put(dmabuf);
+out:
+ drm_gem_object_put(&bo->ttm.base);
+}
+
static const struct dma_buf_attach_ops nop2p_attach_ops = {
.allow_peer2peer = false,
.invalidate_mappings = xe_dma_buf_move_notify
@@ -274,6 +347,22 @@ static int dma_buf_run_device(struct xe_device *xe)
xe_test_dmabuf_import_same_driver(xe);
}
+ /*
+ * Regression for the move_notify NULL-resource crash. Independent of
+ * residency, so run once with a dynamic, different-device attachment.
+ */
+ {
+ struct dma_buf_test_params p = {
+ .mem_mask = XE_BO_FLAG_SYSTEM,
+ .attach_ops = &xe_dma_buf_attach_ops,
+ .force_different_devices = true,
+ };
+
+ p.base.id = XE_TEST_LIVE_DMA_BUF;
+ test->priv = &p;
+ xe_test_dmabuf_move_notify_no_resource(xe);
+ }
+
/* A non-zero return would halt iteration over driver devices */
return 0;
}
diff --git a/drivers/gpu/drm/xe/tests/xe_gt_sriov_pf_config_kunit.c b/drivers/gpu/drm/xe/tests/xe_gt_sriov_pf_config_kunit.c
index e6eaa94d4d30..4d72411cd570 100644
--- a/drivers/gpu/drm/xe/tests/xe_gt_sriov_pf_config_kunit.c
+++ b/drivers/gpu/drm/xe/tests/xe_gt_sriov_pf_config_kunit.c
@@ -68,6 +68,7 @@ static int pf_gt_config_test_init(struct kunit *test)
.graphics_verx100 = 2001,
};
struct xe_vram_region *vram;
+ struct xe_guc_id_mgr *idm;
struct xe_device *xe;
struct xe_gt *gt;
@@ -102,6 +103,10 @@ static int pf_gt_config_test_init(struct kunit *test)
pf_set_admin_mode(xe, false);
KUNIT_ASSERT_EQ(test, xe_sriov_init(xe), 0);
+ idm = &gt->uc.guc.submission_state.idm;
+ mutex_init(&gt->uc.guc.submission_state.lock);
+ KUNIT_ASSERT_EQ(test, xe_guc_id_mgr_init_shared(idm), 0);
+
/* more sanity checks */
KUNIT_EXPECT_EQ(test, GUC_ID_MAX + 1, SZ_64K);
KUNIT_EXPECT_EQ(test, GUC_NUM_DOORBELLS, SZ_256);
diff --git a/drivers/gpu/drm/xe/tests/xe_guc_id_mgr_test.c b/drivers/gpu/drm/xe/tests/xe_guc_id_mgr_test.c
index ee30a1939eb0..5c74a7876d8a 100644
--- a/drivers/gpu/drm/xe/tests/xe_guc_id_mgr_test.c
+++ b/drivers/gpu/drm/xe/tests/xe_guc_id_mgr_test.c
@@ -7,13 +7,30 @@
#include "xe_device.h"
#include "xe_kunit_helpers.h"
+#include "xe_pci_test.h"
+
+#define IDM_TEST_POOL_SIZE 8
+#define IDM_TEST_PARTIAL_POOL_SIZE ((GUC_ID_MAX / 3) * 2)
static int guc_id_mgr_test_init(struct kunit *test)
{
+ struct xe_pci_fake_data *fake;
struct xe_guc_id_mgr *idm;
+ /* Keep one logical suite: use PF fixture when IOV is enabled. */
+ fake = kunit_kzalloc(test, sizeof(*fake), GFP_KERNEL);
+ if (!fake)
+ return -ENOMEM;
+
+ *fake = (struct xe_pci_fake_data) {
+ .sriov_mode = XE_SRIOV_MODE_PF,
+ .platform = XE_TIGERLAKE, /* some random platform */
+ .subplatform = XE_SUBPLATFORM_NONE,
+ };
+ test->priv = fake;
+
xe_kunit_helper_xe_device_test_init(test);
- idm = &xe_device_get_gt(test->priv, 0)->uc.guc.submission_state.idm;
+ idm = &xe_root_mmio_gt(test->priv)->uc.guc.submission_state.idm;
mutex_init(idm_mutex(idm));
test->priv = idm;
@@ -24,104 +41,692 @@ static void bad_init(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
- KUNIT_EXPECT_EQ(test, -EINVAL, xe_guc_id_mgr_init(idm, 0));
- KUNIT_EXPECT_EQ(test, -ERANGE, xe_guc_id_mgr_init(idm, GUC_ID_MAX + 1));
+ KUNIT_EXPECT_EQ(test, -EINVAL, idm_init(idm, 0, 0));
+ KUNIT_EXPECT_EQ(test, -ERANGE, idm_init(idm, GUC_ID_MAX + 1, 0));
}
static void no_init(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
- mutex_lock(idm_mutex(idm));
- KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve_locked(idm, 0));
- mutex_unlock(idm_mutex(idm));
+ scoped_guard(mutex, idm_mutex(idm))
+ KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve_usable_locked(idm, 0));
- KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve(idm, 1, 1));
+ KUNIT_EXPECT_EQ(test, -ENODATA, xe_guc_id_mgr_reserve_shareable(idm, 1, 1));
}
static void init_fini(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
- KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, -1));
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, GUC_ID_MAX, 0));
KUNIT_EXPECT_NOT_NULL(test, idm->bitmap);
- KUNIT_EXPECT_EQ(test, idm->total, GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), GUC_ID_MAX);
__fini_idm(NULL, idm);
KUNIT_EXPECT_NULL(test, idm->bitmap);
- KUNIT_EXPECT_EQ(test, idm->total, 0);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), 0);
+}
+
+static void check_init_small(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ unsigned int count = 8;
+
+ KUNIT_ASSERT_EQ(test, xe_guc_id_mgr_init_small(idm, count), 0);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_usable(idm), count);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_shareable(idm), 0);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), count);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_usable(idm, count), 0);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_usable(idm, 1), -ENOSPC);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable(idm, 0, count), 0);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_shareable(idm, 1, 0), -ENODATA);
+}
+
+static void require_iov_config_or_skip(struct kunit *test)
+{
+ if (!IS_ENABLED(CONFIG_PCI_IOV))
+ kunit_skip(test, "requires CONFIG_PCI_IOV");
+}
+
+static void check_init_shared(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_TRUE(test, IS_SRIOV_PF(idm_to_xe(idm)));
+ KUNIT_ASSERT_EQ(test, xe_guc_id_mgr_init_shared(idm), 0);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_usable(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_shareable(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), GUC_ID_MAX);
+}
+
+static unsigned int test_idm_used_total(struct xe_guc_id_mgr *idm)
+{
+ lockdep_assert_held(idm_mutex(idm));
+
+ return bitmap_weight(idm->bitmap, idm_total(idm));
}
static void check_used(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
unsigned int n;
+ int id;
- KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, 2));
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, 2, 0));
- mutex_lock(idm_mutex(idm));
+ guard(mutex)(idm_mutex(idm));
- for (n = 0; n < idm->total; n++) {
+ for (n = 0; n < idm_total(idm); n++) {
kunit_info(test, "n=%u", n);
- KUNIT_EXPECT_EQ(test, idm->used, n);
- KUNIT_EXPECT_GE(test, idm_reserve_chunk_locked(idm, 1, 0), 0);
- KUNIT_EXPECT_EQ(test, idm->used, n + 1);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), n);
+ id = xe_guc_id_mgr_reserve_usable_locked(idm, 1);
+ KUNIT_EXPECT_GE(test, id, 0);
+ if (id < 0)
+ break;
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), n + 1);
}
- KUNIT_EXPECT_EQ(test, idm->used, idm->total);
- idm_release_chunk_locked(idm, 0, idm->used);
- KUNIT_EXPECT_EQ(test, idm->used, 0);
-
- mutex_unlock(idm_mutex(idm));
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), idm_total(idm));
+ if (n)
+ idm_release_chunk_locked(idm, 0, n);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
}
static void check_quota(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
- unsigned int n;
+ const unsigned int overlap = 10;
+ const unsigned int tail = 10;
+ const unsigned int usable = GUC_ID_MAX - tail;
+ const unsigned int shareable = overlap + tail;
+ const unsigned int spare = overlap + 1;
+ int usable_id, tail_id;
- KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, 2));
+ require_iov_config_or_skip(test);
- mutex_lock(idm_mutex(idm));
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, usable, shareable));
- for (n = 0; n < idm->total - 1; n++) {
- kunit_info(test, "n=%u", n);
- KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, 1, idm->total), -EDQUOT);
- KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, 1, idm->total - n), -EDQUOT);
- KUNIT_EXPECT_EQ(test, idm_reserve_chunk_locked(idm, idm->total - n, 1), -EDQUOT);
- KUNIT_EXPECT_GE(test, idm_reserve_chunk_locked(idm, 1, 1), 0);
+ guard(mutex)(idm_mutex(idm));
+
+ /* spare is larger than the overlap, but the PF-exclusive range is free. */
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, usable - spare);
+ KUNIT_EXPECT_GE(test, usable_id, 0);
+ if (usable_id < 0)
+ return;
+
+ tail_id = xe_guc_id_mgr_reserve_shareable_locked(idm, tail, spare);
+ KUNIT_EXPECT_EQ(test, tail_id, usable);
+ if (tail_id < 0) {
+ idm_release_chunk_locked(idm, usable_id, usable - spare);
+ return;
}
- KUNIT_EXPECT_LE(test, 0, idm_reserve_chunk_locked(idm, 1, 0));
- KUNIT_EXPECT_EQ(test, idm->used, idm->total);
- idm_release_chunk_locked(idm, 0, idm->total);
- KUNIT_EXPECT_EQ(test, idm->used, 0);
- mutex_unlock(idm_mutex(idm));
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_reserve_shareable_locked(idm, 1, spare), -EDQUOT);
+
+ idm_release_chunk_locked(idm, usable_id, usable - spare);
+ idm_release_chunk_locked(idm, tail_id, tail);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
}
static void check_all(struct kunit *test)
{
struct xe_guc_id_mgr *idm = test->priv;
unsigned int n;
+ int id;
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, GUC_ID_MAX, 0));
- KUNIT_ASSERT_EQ(test, 0, xe_guc_id_mgr_init(idm, -1));
+ guard(mutex)(idm_mutex(idm));
- mutex_lock(idm_mutex(idm));
+ for (n = 0; n < idm_total(idm); n++) {
+ id = xe_guc_id_mgr_reserve_usable_locked(idm, 1);
+ KUNIT_EXPECT_GE(test, id, 0);
+ if (id < 0)
+ break;
+ }
+ if (n < idm_total(idm)) {
+ if (n)
+ idm_release_chunk_locked(idm, 0, n);
+ return;
+ }
- for (n = 0; n < idm->total; n++)
- KUNIT_EXPECT_LE(test, 0, idm_reserve_chunk_locked(idm, 1, 0));
- KUNIT_EXPECT_EQ(test, idm->used, idm->total);
- for (n = 0; n < idm->total; n++)
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), idm_total(idm));
+ for (n = 0; n < idm_total(idm); n++)
idm_release_chunk_locked(idm, n, 1);
+}
+
+static void check_limits(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int usable = 10;
+ const unsigned int shareable = 20;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, usable, shareable));
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_usable(idm), usable);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_shareable(idm), shareable);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), usable + shareable);
+}
+
+static void check_overlap(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, GUC_ID_MAX, GUC_ID_MAX));
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_usable(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_shareable(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, idm_total(idm), GUC_ID_MAX);
+}
+
+static void check_overlap_alloc(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int usable_count = 2;
+ const unsigned int shareable_count = 3;
+ unsigned int total;
+ int usable_id, shareable_id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, GUC_ID_MAX, GUC_ID_MAX));
+
+ guard(mutex)(idm_mutex(idm));
+ total = idm_total(idm);
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, usable_count);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0)
+ return;
+
+ shareable_id = xe_guc_id_mgr_reserve_shareable_locked(idm, shareable_count, 0);
+ KUNIT_EXPECT_EQ(test, shareable_id, total - shareable_count);
+ if (shareable_id < 0) {
+ idm_release_chunk_locked(idm, usable_id, usable_count);
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), usable_count + shareable_count);
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), usable_count + shareable_count);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), usable_count + shareable_count);
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_usable_locked(idm, usable_id, usable_count), 0);
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_id,
+ shareable_count), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_overlap_release(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int count = 4;
+ unsigned int total;
+ int id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, GUC_ID_MAX, GUC_ID_MAX));
+
+ guard(mutex)(idm_mutex(idm));
+ total = idm_total(idm);
+
+ id = xe_guc_id_mgr_reserve_usable_locked(idm, count);
+ KUNIT_EXPECT_EQ(test, id, 0);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_shareable_locked(idm, id, count), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+
+ id = xe_guc_id_mgr_reserve_shareable_locked(idm, count, 0);
+ KUNIT_EXPECT_EQ(test, id, total - count);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable_locked(idm, id, count), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable_locked(idm, 0, 0), -EINVAL);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_shareable_locked(idm, 0, 0), -EINVAL);
+}
+
+static void check_overlap_partial_init(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int pools_size = IDM_TEST_PARTIAL_POOL_SIZE;
+ unsigned int shareable_start;
+ unsigned int rest;
+ int usable_id, shareable_id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, pools_size, pools_size));
+
+ guard(mutex)(idm_mutex(idm));
+
+ shareable_start = idm_shareable_start(idm);
+ rest = idm_total(idm) - pools_size;
+
+ KUNIT_EXPECT_EQ(test, idm_total(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_usable(idm), pools_size);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_max_shareable(idm), pools_size);
+ KUNIT_EXPECT_EQ(test, shareable_start, GUC_ID_MAX - pools_size);
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, pools_size);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0)
+ return;
+
+ shareable_id = xe_guc_id_mgr_reserve_shareable_locked(idm, rest, 0);
+ KUNIT_EXPECT_EQ(test, shareable_id, pools_size);
+ if (shareable_id < 0) {
+ idm_release_chunk_locked(idm, usable_id, pools_size);
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), GUC_ID_MAX);
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), pools_size);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), pools_size);
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_usable_locked(idm, usable_id, pools_size), 0);
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_id, rest), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_overlap_partial_shared_range_blocking(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int pools_size = IDM_TEST_PARTIAL_POOL_SIZE;
+ int usable_id, shareable_range_id, shareable_overlap_id;
+ unsigned int shareable_start;
+ unsigned int nonoverlap_size;
+ unsigned int overlap_size;
+ unsigned int total;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, pools_size, pools_size));
+
+ guard(mutex)(idm_mutex(idm));
+
+ total = idm_total(idm);
+ shareable_start = idm_shareable_start(idm);
+ nonoverlap_size = total - pools_size;
+ overlap_size = pools_size - shareable_start;
+
+ KUNIT_EXPECT_GT(test, nonoverlap_size, 0);
+ KUNIT_EXPECT_GT(test, overlap_size, 0);
+
+ shareable_range_id = xe_guc_id_mgr_reserve_shareable_locked(idm, nonoverlap_size, 0);
+ KUNIT_EXPECT_EQ(test, shareable_range_id, pools_size);
+ if (shareable_range_id < 0)
+ return;
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, pools_size);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0) {
+ idm_release_chunk_locked(idm, shareable_range_id, nonoverlap_size);
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_shareable_locked(idm, 1, 0), -ENOSPC);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable_locked(idm, usable_id, pools_size), 0);
+
+ shareable_overlap_id = xe_guc_id_mgr_reserve_shareable_locked(idm, overlap_size, 0);
+ KUNIT_EXPECT_EQ(test, shareable_overlap_id, shareable_start);
+ if (shareable_overlap_id < 0) {
+ idm_release_chunk_locked(idm, shareable_range_id, nonoverlap_size);
+ return;
+ }
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, shareable_start);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0) {
+ idm_release_chunk_locked(idm, shareable_overlap_id, overlap_size);
+ idm_release_chunk_locked(idm, shareable_range_id, nonoverlap_size);
+ return;
+ }
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_usable_locked(idm, 1), -ENOSPC);
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_usable_locked(idm, usable_id, shareable_start),
+ 0);
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_overlap_id,
+ overlap_size),
+ 0);
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_range_id,
+ nonoverlap_size),
+ 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_used_usable(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int count = 2;
+ int id;
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, IDM_TEST_POOL_SIZE, 0));
+
+ guard(mutex)(idm_mutex(idm));
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), 0);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), 0);
+
+ id = xe_guc_id_mgr_reserve_usable_locked(idm, count);
+ KUNIT_EXPECT_GE(test, id, 0);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), count);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), count);
+
+ idm_release_chunk_locked(idm, id, count);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_used_shareable(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int count = 3;
+ unsigned int shareable_start;
+ int id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, IDM_TEST_POOL_SIZE, IDM_TEST_POOL_SIZE));
+
+ guard(mutex)(idm_mutex(idm));
+ shareable_start = idm_shareable_start(idm);
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), 0);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), 0);
+
+ id = xe_guc_id_mgr_reserve_shareable_locked(idm, count, 0);
+ KUNIT_EXPECT_EQ(test, id, shareable_start);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), 0);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), count);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), count);
+
+ idm_release_chunk_locked(idm, id, count);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_used_mixed(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int usable_count = 2;
+ const unsigned int shareable_count = 3;
+ unsigned int shareable_start;
+ int usable_id, shareable_id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, IDM_TEST_POOL_SIZE, IDM_TEST_POOL_SIZE));
+
+ guard(mutex)(idm_mutex(idm));
+ shareable_start = idm_shareable_start(idm);
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, usable_count);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0)
+ return;
+
+ shareable_id = xe_guc_id_mgr_reserve_shareable_locked(idm, shareable_count, 0);
+ KUNIT_EXPECT_EQ(test, shareable_id, shareable_start);
+ if (shareable_id < 0) {
+ idm_release_chunk_locked(idm, usable_id, usable_count);
+ return;
+ }
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), usable_count);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), shareable_count);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), usable_count + shareable_count);
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_usable_locked(idm, usable_id, usable_count), 0);
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_id,
+ shareable_count), 0);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_release_usable_range(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int count = 4;
+ int id;
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, IDM_TEST_POOL_SIZE, 0));
+
+ guard(mutex)(idm_mutex(idm));
+
+ id = xe_guc_id_mgr_reserve_usable_locked(idm, count);
+ KUNIT_EXPECT_EQ(test, id, 0);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_usable_locked(idm, count,
+ IDM_TEST_POOL_SIZE - count + 1),
+ -ERANGE);
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), count);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable_locked(idm, 0, 0), -EINVAL);
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), count);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_usable_locked(idm, id, count), 0);
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), 0);
+}
+
+static void check_release_shareable_range(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int count = 4;
+ unsigned int shareable_start;
+ int id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, IDM_TEST_POOL_SIZE, IDM_TEST_POOL_SIZE));
+
+ guard(mutex)(idm_mutex(idm));
+
+ shareable_start = idm_shareable_start(idm);
+ id = xe_guc_id_mgr_reserve_shareable_locked(idm, count, 0);
+ KUNIT_EXPECT_EQ(test, id, shareable_start);
+ if (id < 0)
+ return;
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_start - 1, count),
+ -ERANGE);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), count);
+
+ KUNIT_EXPECT_EQ(test,
+ xe_guc_id_mgr_release_shareable_locked(idm, shareable_start, 0),
+ -EINVAL);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), count);
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_release_shareable_locked(idm, id, count), 0);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), 0);
+}
+
+static void check_split_boundaries(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int pool_size = IDM_TEST_POOL_SIZE;
+ unsigned int shareable_start;
+ int usable_id, shareable_id;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, pool_size, pool_size));
+
+ guard(mutex)(idm_mutex(idm));
+
+ shareable_start = idm_shareable_start(idm);
+
+ usable_id = xe_guc_id_mgr_reserve_usable_locked(idm, pool_size);
+ KUNIT_EXPECT_EQ(test, usable_id, 0);
+ if (usable_id < 0)
+ return;
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_usable_locked(idm, 1), -ENOSPC);
+
+ shareable_id = xe_guc_id_mgr_reserve_shareable_locked(idm, pool_size, 0);
+ KUNIT_EXPECT_EQ(test, shareable_id, shareable_start);
+ if (shareable_id < 0) {
+ idm_release_chunk_locked(idm, usable_id, pool_size);
+ return;
+ }
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_shareable_locked(idm, 1, 0), -ENOSPC);
+
+ KUNIT_EXPECT_EQ(test, idm_used_usable(idm), pool_size);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), pool_size);
+
+ idm_release_chunk_locked(idm, usable_id, pool_size);
+ idm_release_chunk_locked(idm, shareable_id, pool_size);
+ KUNIT_EXPECT_EQ(test, test_idm_used_total(idm), 0);
+}
+
+static void check_shareable_spare(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int pools_size = IDM_TEST_PARTIAL_POOL_SIZE;
+ unsigned int shareable_start;
+ unsigned int overlap_size;
+ unsigned int shared_tail;
+ int id_tail, id_overlap, id_extra;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, pools_size, pools_size));
+
+ guard(mutex)(idm_mutex(idm));
+ shareable_start = idm_shareable_start(idm);
+ overlap_size = idm->usable - shareable_start;
+ shared_tail = idm_total(idm) - idm->usable;
+
+ KUNIT_EXPECT_GT(test, overlap_size, 0);
+ KUNIT_EXPECT_GT(test, shared_tail, 0);
+
+ id_tail = xe_guc_id_mgr_reserve_shareable_locked(idm, shared_tail, 1);
+ KUNIT_EXPECT_EQ(test, id_tail, idm->usable);
+ if (id_tail < 0)
+ return;
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), shared_tail);
+
+ id_overlap = xe_guc_id_mgr_reserve_shareable_locked(idm, overlap_size - 1, 1);
+ KUNIT_EXPECT_EQ(test, id_overlap, shareable_start + 1);
+ if (id_overlap < 0) {
+ idm_release_chunk_locked(idm, id_tail, shared_tail);
+ return;
+ }
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), idm->shareable - 1);
+
+ id_extra = xe_guc_id_mgr_reserve_shareable_locked(idm, 1, 1);
+ KUNIT_EXPECT_EQ(test, id_extra, shareable_start);
+ if (id_extra < 0) {
+ idm_release_chunk_locked(idm, id_tail, shared_tail);
+ idm_release_chunk_locked(idm, id_overlap, overlap_size - 1);
+ return;
+ }
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), idm->shareable);
+
+ idm_release_chunk_locked(idm, id_tail, shared_tail);
+ idm_release_chunk_locked(idm, id_overlap, overlap_size - 1);
+ idm_release_chunk_locked(idm, id_extra, 1);
+ KUNIT_EXPECT_EQ(test, idm_used_shareable(idm), 0);
+}
+
+static void check_shareable_spare_fragmented_tail(struct kunit *test)
+{
+ struct xe_guc_id_mgr *idm = test->priv;
+ const unsigned int overlap = 40;
+ const unsigned int tail = 50;
+ const unsigned int usable = GUC_ID_MAX - tail;
+ const unsigned int shareable = overlap + tail;
+ const unsigned int stride = 5;
+ const unsigned int count = overlap - 1;
+ const unsigned int spare = 10;
+ const unsigned int overlap_free = 30;
+ const unsigned int tail_free = 20;
+ unsigned int shareable_start;
+ unsigned int i;
+
+ require_iov_config_or_skip(test);
+
+ KUNIT_ASSERT_EQ(test, 0, idm_init(idm, usable, shareable));
+
+ guard(mutex)(idm_mutex(idm));
+ shareable_start = idm_shareable_start(idm);
+ KUNIT_EXPECT_EQ(test, idm->usable - shareable_start, overlap);
+
+ /*
+ * Fragmented tail cannot fit @count IDs, so the request must fall back
+ * to the overlap, where it is limited by @spare.
+ */
+ bitmap_fill(idm->bitmap, idm_total(idm));
+ bitmap_clear(idm->bitmap, shareable_start + 1, count);
+ for (i = idm->usable; i < idm_total(idm); i++) {
+ if ((i - idm->usable) % stride)
+ bitmap_clear(idm->bitmap, i, 1);
+ }
+
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_shareable_locked(idm, count, spare),
+ -EDQUOT);
+
+ bitmap_fill(idm->bitmap, idm_total(idm));
+ bitmap_clear(idm->bitmap, shareable_start, overlap_free);
+ bitmap_clear(idm->bitmap, idm_total(idm) - tail_free, tail_free);
+ KUNIT_EXPECT_EQ(test, xe_guc_id_mgr_reserve_shareable_locked(idm, overlap_free, 1),
+ -EDQUOT);
- mutex_unlock(idm_mutex(idm));
+ bitmap_zero(idm->bitmap, idm_total(idm));
}
static struct kunit_case guc_id_mgr_test_cases[] = {
KUNIT_CASE(bad_init),
KUNIT_CASE(no_init),
KUNIT_CASE(init_fini),
+ KUNIT_CASE(check_init_small),
+ KUNIT_CASE(check_init_shared),
KUNIT_CASE(check_used),
KUNIT_CASE(check_quota),
KUNIT_CASE_SLOW(check_all),
+ KUNIT_CASE(check_limits),
+ KUNIT_CASE(check_overlap),
+ KUNIT_CASE(check_overlap_alloc),
+ KUNIT_CASE(check_overlap_release),
+ KUNIT_CASE(check_overlap_partial_init),
+ KUNIT_CASE(check_overlap_partial_shared_range_blocking),
+ KUNIT_CASE(check_used_usable),
+ KUNIT_CASE(check_used_shareable),
+ KUNIT_CASE(check_used_mixed),
+ KUNIT_CASE(check_release_usable_range),
+ KUNIT_CASE(check_release_shareable_range),
+ KUNIT_CASE(check_split_boundaries),
+ KUNIT_CASE(check_shareable_spare),
+ KUNIT_CASE(check_shareable_spare_fragmented_tail),
{}
};
diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c
index 12e523c61c1f..1b74331b15ec 100644
--- a/drivers/gpu/drm/xe/xe_bo.c
+++ b/drivers/gpu/drm/xe/xe_bo.c
@@ -456,9 +456,9 @@ static void xe_ttm_tt_account_add(struct xe_device *xe, struct ttm_tt *tt)
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);
if (xe_tt->purgeable)
- xe_shrinker_mod_pages(xe->mem.shrinker, 0, tt->num_pages);
+ xe_shrinker_mod_pages(xe->mem.shrinker, 0, tt->num_pages, 1);
else
- xe_shrinker_mod_pages(xe->mem.shrinker, tt->num_pages, 0);
+ xe_shrinker_mod_pages(xe->mem.shrinker, tt->num_pages, 0, 1);
}
static void xe_ttm_tt_account_subtract(struct xe_device *xe, struct ttm_tt *tt)
@@ -466,9 +466,9 @@ static void xe_ttm_tt_account_subtract(struct xe_device *xe, struct ttm_tt *tt)
struct xe_ttm_tt *xe_tt = container_of(tt, struct xe_ttm_tt, ttm);
if (xe_tt->purgeable)
- xe_shrinker_mod_pages(xe->mem.shrinker, 0, -(long)tt->num_pages);
+ xe_shrinker_mod_pages(xe->mem.shrinker, 0, -(long)tt->num_pages, -1);
else
- xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages, 0);
+ xe_shrinker_mod_pages(xe->mem.shrinker, -(long)tt->num_pages, 0, -1);
}
static bool xe_ttm_bo_is_imported(struct ttm_buffer_object *tbo)
@@ -984,11 +984,11 @@ static void xe_bo_set_purgeable_shrinker(struct xe_bo *bo,
if (!xe_tt->purgeable && new_state == XE_MADV_PURGEABLE_DONTNEED) {
xe_tt->purgeable = true;
/* Transfer pages from shrinkable to purgeable count */
- xe_shrinker_mod_pages(xe->mem.shrinker, -tt_pages, tt_pages);
+ xe_shrinker_mod_pages(xe->mem.shrinker, -tt_pages, tt_pages, 0);
} else if (xe_tt->purgeable && new_state == XE_MADV_PURGEABLE_WILLNEED) {
xe_tt->purgeable = false;
/* Transfer pages from purgeable to shrinkable count */
- xe_shrinker_mod_pages(xe->mem.shrinker, tt_pages, -tt_pages);
+ xe_shrinker_mod_pages(xe->mem.shrinker, tt_pages, -tt_pages, 0);
}
}
diff --git a/drivers/gpu/drm/xe/xe_debugfs.c b/drivers/gpu/drm/xe/xe_debugfs.c
index 80f62634fae5..926020c7526e 100644
--- a/drivers/gpu/drm/xe/xe_debugfs.c
+++ b/drivers/gpu/drm/xe/xe_debugfs.c
@@ -31,6 +31,7 @@
#include "xe_sriov_pf_debugfs.h"
#include "xe_sriov_vf.h"
#include "xe_step.h"
+#include "xe_sysctrl_debugfs.h"
#include "xe_tile_debugfs.h"
#include "xe_ttm_vram_mgr.h"
#include "xe_vsec.h"
@@ -836,6 +837,9 @@ void xe_debugfs_register(struct xe_device *xe)
xe_fault_inject_debugfs_register(xe, root);
+ if (xe->info.has_sysctrl)
+ xe_sysctrl_debugfs_register(&xe->sc, root);
+
if (IS_SRIOV_PF(xe))
xe_sriov_pf_debugfs_register(xe, root);
else if (IS_SRIOV_VF(xe))
diff --git a/drivers/gpu/drm/xe/xe_devcoredump.c b/drivers/gpu/drm/xe/xe_devcoredump.c
index ab681b148869..133cbe196d66 100644
--- a/drivers/gpu/drm/xe/xe_devcoredump.c
+++ b/drivers/gpu/drm/xe/xe_devcoredump.c
@@ -110,9 +110,9 @@ static ssize_t __xe_devcoredump_read(char *buffer, ssize_t count,
drm_printf(&p, "\tTile: %d\n", ss->gt->tile->id);
drm_puts(&p, "\n**** GuC Log ****\n");
- xe_guc_log_snapshot_print(ss->guc.log, &p);
+ xe_guc_log_snapshot_print(ss->guc.log, &p, ss->gt);
drm_puts(&p, "\n**** GuC CT ****\n");
- xe_guc_ct_snapshot_print(ss->guc.ct, &p);
+ xe_guc_ct_snapshot_print(ss->guc.ct, &p, ss->gt);
drm_puts(&p, "\n**** Contexts ****\n");
xe_guc_exec_queue_snapshot_print(ss->ge, &p);
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index e1979bbcf7bc..db0b883322ef 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -373,6 +373,9 @@ bool xe_device_is_admin_only(const struct xe_device *xe)
}
#endif
+/* Number of allocated struct xe_device */
+static atomic_t xe_device_count;
+
static void xe_device_destroy(struct drm_device *dev, void *dummy)
{
struct xe_device *xe = to_xe_device(dev);
@@ -392,6 +395,9 @@ static void xe_device_destroy(struct drm_device *dev, void *dummy)
destroy_workqueue(xe->destroy_wq);
ttm_device_fini(&xe->ttm);
+
+ if (atomic_dec_and_test(&xe_device_count))
+ wake_up_var(&xe_device_count);
}
/**
@@ -462,6 +468,7 @@ int xe_device_init_early(struct xe_device *xe)
return err;
xe_bo_dev_init(&xe->bo_device);
+ atomic_inc(&xe_device_count);
err = drmm_add_action_or_reset(&xe->drm, xe_device_destroy, NULL);
if (err)
return err;
@@ -888,7 +895,9 @@ int xe_device_probe(struct xe_device *xe)
xe_uid_probe(xe);
- xe_pat_init_early(xe);
+ err = xe_pat_init_early(xe);
+ if (err)
+ return err;
err = xe_sriov_init(xe);
if (err)
@@ -967,7 +976,9 @@ int xe_device_probe(struct xe_device *xe)
if (err)
return err;
- xe_ras_init(xe);
+ err = xe_ras_init(xe);
+ if (err)
+ return err;
/*
* Now that GT is initialized (TTM in particular),
@@ -1515,3 +1526,25 @@ struct xe_vm *xe_device_asid_to_vm(struct xe_device *xe, u32 asid)
return vm;
}
+
+/**
+ * xe_device_exit() - Device subsystem exit function.
+ *
+ * Exit function to be called at module unload time.
+ */
+void xe_device_exit(void)
+{
+ /*
+ * Wait for all devices to be freed. 20s is well above the typical
+ * maximum dma_fence signalling time, so warn and keep waiting if
+ * we're still not done by then, since it may indicate a leaked
+ * xe_device reference is stalling module unload.
+ */
+ if (!wait_var_event_timeout(&xe_device_count,
+ !atomic_read(&xe_device_count),
+ HZ * 20)) {
+ pr_warn("%s: Waiting for %d xe device(s) to be freed before unloading.\n",
+ DRIVER_NAME, atomic_read(&xe_device_count));
+ wait_var_event(&xe_device_count, !atomic_read(&xe_device_count));
+ }
+}
diff --git a/drivers/gpu/drm/xe/xe_device.h b/drivers/gpu/drm/xe/xe_device.h
index 6d3d6d5eba29..83d6dafab53c 100644
--- a/drivers/gpu/drm/xe/xe_device.h
+++ b/drivers/gpu/drm/xe/xe_device.h
@@ -283,6 +283,8 @@ static inline bool xe_device_is_admin_only(const struct xe_device *xe)
}
#endif
+void xe_device_exit(void);
+
/*
* Occasionally it is seen that the G2H worker starts running after a delay of more than
* a second even after being queued and activated by the Linux workqueue subsystem. This
diff --git a/drivers/gpu/drm/xe/xe_device_sysfs.c b/drivers/gpu/drm/xe/xe_device_sysfs.c
index 18783a286a83..ff5d25dd867c 100644
--- a/drivers/gpu/drm/xe/xe_device_sysfs.c
+++ b/drivers/gpu/drm/xe/xe_device_sysfs.c
@@ -15,6 +15,7 @@
#include "xe_pcode_api.h"
#include "xe_pcode.h"
#include "xe_pm.h"
+#include "xe_shrinker.h"
/**
* DOC: Xe device sysfs
@@ -31,6 +32,12 @@
*
* lb_voltage_regulator_version - Voltage regulator version provisioned by late
* binding. Exposed only if supported by the device.
+ *
+ * shrinker_non_purgeable_seeks - Report/change the cost, relative to
+ * purgeable buffer objects, of recreating non-purgeable buffer objects
+ * reclaimed by the shrinker. Higher values reduce reclaim pressure on
+ * non-purgeable buffer objects. Valid values are 1 to 64. Defaults to
+ * CONFIG_DRM_XE_SHRINKER_NON_PURGEABLE_SEEKS.
*/
static ssize_t
@@ -294,11 +301,54 @@ static const struct attribute_group device_uid_attr_group = {
.attrs = device_uid_attrs,
};
+static ssize_t
+shrinker_non_purgeable_seeks_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct xe_device *xe = pdev_to_xe_device(to_pci_dev(dev));
+
+ return sysfs_emit(buf, "%d\n",
+ xe_shrinker_non_purgeable_seeks(xe->mem.shrinker));
+}
+
+static ssize_t
+shrinker_non_purgeable_seeks_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct xe_device *xe = pdev_to_xe_device(to_pci_dev(dev));
+ unsigned int seeks;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &seeks);
+ if (ret)
+ return ret;
+
+ ret = xe_shrinker_set_non_purgeable_seeks(xe->mem.shrinker, seeks);
+
+ return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(shrinker_non_purgeable_seeks);
+
+static struct attribute *shrinker_attrs[] = {
+ &dev_attr_shrinker_non_purgeable_seeks.attr,
+ NULL
+};
+
+static const struct attribute_group shrinker_attr_group = {
+ .attrs = shrinker_attrs,
+};
+
int xe_device_sysfs_init(struct xe_device *xe)
{
struct device *dev = xe->drm.dev;
int ret;
+ ret = devm_device_add_group(dev, &shrinker_attr_group);
+ if (ret)
+ return ret;
+
if (xe->d3cold.capable) {
ret = devm_device_add_group(dev, &vram_attr_group);
if (ret)
diff --git a/drivers/gpu/drm/xe/xe_device_types.h b/drivers/gpu/drm/xe/xe_device_types.h
index 41f703a87916..aa3a98ddb457 100644
--- a/drivers/gpu/drm/xe/xe_device_types.h
+++ b/drivers/gpu/drm/xe/xe_device_types.h
@@ -21,6 +21,7 @@
#include "xe_platform_types.h"
#include "xe_pmu_types.h"
#include "xe_pt_types.h"
+#include "xe_ras_types.h"
#include "xe_sriov_pf_types.h"
#include "xe_sriov_types.h"
#include "xe_sriov_vf_types.h"
@@ -185,6 +186,8 @@ struct xe_device {
u8 has_drm_ras:1;
/** @info.has_fan_control: Device supports fan control */
u8 has_fan_control:1;
+ /** @info.has_fixed_vram_channels: Device has fixed VRAM temperature channels */
+ u8 has_fixed_vram_channels:1;
/** @info.has_flat_ccs: Whether flat CCS metadata is used */
u8 has_flat_ccs:1;
/** @info.has_gsc_nvm: Device has gsc non-volatile memory */
@@ -227,6 +230,8 @@ struct xe_device {
u8 has_sriov:1;
/** @info.has_sysctrl: Supports System Controller */
u8 has_sysctrl:1;
+ /** @info.has_temp_sentinel: Device has temperature-unavailable sentinels */
+ u8 has_temp_sentinel:1;
/** @info.has_usm: Device has unified shared memory support */
u8 has_usm:1;
/** @info.has_64bit_timestamp: Device supports 64-bit timestamps */
@@ -576,8 +581,14 @@ struct xe_device {
/** @pmu: performance monitoring unit */
struct xe_pmu pmu;
- /** @ras: RAS structure for device */
- struct xe_drm_ras ras;
+ /** @ras: RAS (Reliability, Availability, Serviceability) structures */
+ struct {
+ /** @ras.nl_data: drm-ras netlink data */
+ struct xe_drm_ras nl_data;
+
+ /** @ras.state: RAS device and firmware state */
+ struct xe_ras_state state;
+ } ras;
/** @i2c: I2C host controller */
struct xe_i2c *i2c;
diff --git a/drivers/gpu/drm/xe/xe_drm_ras.c b/drivers/gpu/drm/xe/xe_drm_ras.c
index 7f3695707611..7554803aae11 100644
--- a/drivers/gpu/drm/xe/xe_drm_ras.c
+++ b/drivers/gpu/drm/xe/xe_drm_ras.c
@@ -16,11 +16,22 @@
static const char * const error_components[] = DRM_XE_RAS_ERROR_COMPONENT_NAMES;
static const char * const error_severity[] = DRM_XE_RAS_ERROR_SEVERITY_NAMES;
+static bool drm_ras_counter_is_valid(u32 severity, u32 component)
+{
+ if (!in_range(severity, DRM_XE_RAS_ERR_SEV_CORRECTABLE, DRM_XE_RAS_ERR_SEV_MAX))
+ return false;
+
+ if (!in_range(component, DRM_XE_RAS_ERR_COMP_CORE_COMPUTE, DRM_XE_RAS_ERR_COMP_MAX - 1))
+ return false;
+
+ return true;
+}
+
static int query_error_counter(struct xe_device *xe,
enum drm_xe_ras_error_severity severity,
u32 error_id, const char **name, u32 *val)
{
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[severity];
if (!info || !info[error_id].name)
@@ -41,7 +52,7 @@ static int clear_error_counter(struct xe_device *xe,
enum drm_xe_ras_error_severity severity,
u32 error_id)
{
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[severity];
if (!info || !info[error_id].name)
@@ -90,7 +101,7 @@ static int query_correctable_error_threshold(struct drm_ras_node *ep, u32 error_
const char **name, u32 *threshold)
{
struct xe_device *xe = ep->priv;
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[DRM_XE_RAS_ERR_SEV_CORRECTABLE];
if (!info || !info[error_id].name)
@@ -106,7 +117,7 @@ static int query_correctable_error_threshold(struct drm_ras_node *ep, u32 error_
static int set_correctable_error_threshold(struct drm_ras_node *ep, u32 error_id, u32 threshold)
{
struct xe_device *xe = ep->priv;
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[DRM_XE_RAS_ERR_SEV_CORRECTABLE];
if (!info || !info[error_id].name)
@@ -142,7 +153,7 @@ static int assign_node_params(struct xe_device *xe, struct drm_ras_node *node,
const enum drm_xe_ras_error_severity severity)
{
struct pci_dev *pdev = to_pci_dev(xe->drm.dev);
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
const char *device_name;
device_name = kasprintf(GFP_KERNEL, "%04x:%02x:%02x.%d",
@@ -190,7 +201,7 @@ static void cleanup_node(struct drm_device *drm, void *node)
static int register_nodes(struct xe_device *xe)
{
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct drm_ras_node *node;
int i, ret;
@@ -230,8 +241,8 @@ null_info:
*/
void xe_drm_ras_event(struct xe_device *xe, u32 component, u32 severity, u32 value)
{
- struct xe_drm_ras *ras = &xe->ras;
- struct xe_drm_ras_counter *info = ras->info[severity];
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
+ struct xe_drm_ras_counter *info;
struct drm_ras_node *node;
int ret;
@@ -239,7 +250,11 @@ void xe_drm_ras_event(struct xe_device *xe, u32 component, u32 severity, u32 val
if (!xe->info.has_drm_ras)
return;
+ if (!drm_ras_counter_is_valid(severity, component))
+ return;
+
node = &ras->node[severity];
+ info = ras->info[severity];
if (!info || !info[component].name)
return;
@@ -260,7 +275,7 @@ void xe_drm_ras_event(struct xe_device *xe, u32 component, u32 severity, u32 val
*/
int xe_drm_ras_init(struct xe_device *xe)
{
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct drm_ras_node *node;
int err;
diff --git a/drivers/gpu/drm/xe/xe_drm_ras_types.h b/drivers/gpu/drm/xe/xe_drm_ras_types.h
index 0be218ba2db7..8d729ad6a264 100644
--- a/drivers/gpu/drm/xe/xe_drm_ras_types.h
+++ b/drivers/gpu/drm/xe/xe_drm_ras_types.h
@@ -43,9 +43,6 @@ struct xe_drm_ras {
/** @info: info array for all types of errors */
struct xe_drm_ras_counter *info[DRM_XE_RAS_ERR_SEV_MAX];
-
- /** @disable_vram_page_offline: cached configfs policy, immutable after init */
- bool disable_vram_page_offline;
};
#endif
diff --git a/drivers/gpu/drm/xe/xe_execlist.c b/drivers/gpu/drm/xe/xe_execlist.c
index a36db39dcda8..a6984999962d 100644
--- a/drivers/gpu/drm/xe/xe_execlist.c
+++ b/drivers/gpu/drm/xe/xe_execlist.c
@@ -5,6 +5,7 @@
#include "xe_execlist.h"
+#include <drm/drm_drv.h>
#include <drm/drm_managed.h>
#include "instructions/xe_mi_commands.h"
@@ -21,6 +22,7 @@
#include "xe_macros.h"
#include "xe_mmio.h"
#include "xe_mocs.h"
+#include "xe_module.h"
#include "xe_ring_ops_types.h"
#include "xe_sched_job.h"
@@ -402,6 +404,7 @@ static void execlist_exec_queue_destroy_async(struct work_struct *w)
spin_unlock_irqrestore(&exl->port->lock, flags);
xe_exec_queue_fini(q);
+ drm_dev_put(&xe->drm);
}
static void execlist_exec_queue_kill(struct xe_exec_queue *q)
@@ -411,8 +414,9 @@ static void execlist_exec_queue_kill(struct xe_exec_queue *q)
static void execlist_exec_queue_destroy(struct xe_exec_queue *q)
{
+ drm_dev_get(&gt_to_xe(q->gt)->drm);
INIT_WORK(&q->execlist->destroy_async, execlist_exec_queue_destroy_async);
- queue_work(system_dfl_wq, &q->execlist->destroy_async);
+ xe_destroy_wq_queue(&q->execlist->destroy_async);
}
static int execlist_exec_queue_set_priority(struct xe_exec_queue *q,
diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c
index 63e8bf193605..773777afc68a 100644
--- a/drivers/gpu/drm/xe/xe_ggtt.c
+++ b/drivers/gpu/drm/xe/xe_ggtt.c
@@ -23,6 +23,7 @@
#include "xe_gt.h"
#include "xe_gt_printk.h"
#include "xe_gt_types.h"
+#include "xe_log.h"
#include "xe_map.h"
#include "xe_mmio.h"
#include "xe_pat.h"
@@ -403,7 +404,8 @@ int xe_ggtt_init_early(struct xe_ggtt *ggtt)
else
gsm_size = probe_gsm_size(pdev);
if (gsm_size == 0) {
- xe_tile_err(ggtt->tile, "Hardware reported no preallocated GSM\n");
+ xe_log_err_fatal(ggtt->tile, GGTT, -ENOMEM,
+ "Hardware reported no preallocated GSM\n");
return -ENOMEM;
}
ggtt_start = wopcm;
@@ -414,8 +416,9 @@ int xe_ggtt_init_early(struct xe_ggtt *ggtt)
if (ggtt_start < wopcm ||
ggtt_start + ggtt_size > GUC_GGTT_TOP) {
- xe_tile_err(ggtt->tile, "Invalid GGTT configuration: %#llx-%#llx\n",
- ggtt_start, ggtt_start + ggtt_size - 1);
+ xe_log_err_fatal(ggtt->tile, GGTT, -ERANGE,
+ "Invalid GGTT configuration: %#llx-%#llx\n",
+ ggtt_start, ggtt_start + ggtt_size - 1);
return -ERANGE;
}
}
diff --git a/drivers/gpu/drm/xe/xe_gpu_scheduler_types.h b/drivers/gpu/drm/xe/xe_gpu_scheduler_types.h
index 63d9bf92583c..78ef2e8ded4f 100644
--- a/drivers/gpu/drm/xe/xe_gpu_scheduler_types.h
+++ b/drivers/gpu/drm/xe/xe_gpu_scheduler_types.h
@@ -47,7 +47,10 @@ struct xe_gpu_scheduler {
const struct xe_sched_backend_ops *ops;
/** @msgs: list of messages to be processed in @work_process_msg */
struct list_head msgs;
- /** @msg_lock: Message lock */
+ /**
+ * @msg_lock: Protects @msgs and guc->suspend_pending (indicates a
+ * suspend message is in flight) of exec queues on this scheduler.
+ */
spinlock_t msg_lock;
/** @work_process_msg: processes messages */
struct work_struct work_process_msg;
diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c
index 775c826b68b4..bc9bf6f0b5a7 100644
--- a/drivers/gpu/drm/xe/xe_gt.c
+++ b/drivers/gpu/drm/xe/xe_gt.c
@@ -630,8 +630,21 @@ static int gt_init_with_gt_forcewake(struct xe_gt *gt)
return 0;
}
+static void gt_reset_worker_disable(void *arg)
+{
+ struct xe_gt *gt = arg;
+
+ if (disable_work_sync(&gt->reset.worker))
+ /*
+ * If gt_reset_worker was halted from executing, take care of
+ * releasing the rpm reference here.
+ */
+ xe_pm_runtime_put(gt_to_xe(gt));
+}
+
static int gt_init_with_all_forcewake(struct xe_gt *gt)
{
+ struct xe_device *xe = gt_to_xe(gt);
int err;
CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), XE_FORCEWAKE_ALL);
@@ -698,6 +711,11 @@ static int gt_init_with_all_forcewake(struct xe_gt *gt)
if (IS_SRIOV_PF(gt_to_xe(gt)))
xe_gt_sriov_pf_init_hw(gt);
+ /* Last action registered here so devres drains resets before teardown */
+ err = devm_add_action_or_reset(xe->drm.dev, gt_reset_worker_disable, gt);
+ if (err)
+ return err;
+
return 0;
}
@@ -706,12 +724,7 @@ static void xe_gt_fini(void *arg)
struct xe_gt *gt = arg;
int i;
- if (disable_work_sync(&gt->reset.worker))
- /*
- * If gt_reset_worker was halted from executing, take care of
- * releasing the rpm reference here.
- */
- xe_pm_runtime_put(gt_to_xe(gt));
+ gt_reset_worker_disable(gt);
for (i = 0; i < XE_ENGINE_CLASS_MAX; ++i)
xe_hw_fence_irq_finish(&gt->fence_irq[i]);
@@ -754,8 +767,10 @@ int xe_gt_init(struct xe_gt *gt)
xe_force_wake_init_engines(gt, gt_to_fw(gt));
err = gt_init_with_all_forcewake(gt);
- if (err)
+ if (err) {
+ gt_reset_worker_disable(gt);
return err;
+ }
xe_gt_record_user_engines(gt);
diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_config.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_config.c
index 6947aeed458e..0a47458e5bf2 100644
--- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_config.c
+++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_config.c
@@ -889,7 +889,7 @@ static int pf_set_spare_ctxs(struct xe_gt *gt, u32 spare)
xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt)));
lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt));
- if (spare > GUC_ID_MAX)
+ if (spare > xe_guc_id_mgr_max_shareable(&gt->uc.guc.submission_state.idm))
return -EINVAL;
if (spare && spare < pf_get_min_spare_ctxs(gt))
@@ -906,7 +906,7 @@ static int pf_reserve_ctxs(struct xe_gt *gt, u32 num)
struct xe_guc_id_mgr *idm = &gt->uc.guc.submission_state.idm;
unsigned int spare = pf_get_spare_ctxs(gt);
- return xe_guc_id_mgr_reserve(idm, num, spare);
+ return xe_guc_id_mgr_reserve_shareable(idm, num, spare);
}
static void pf_release_ctxs(struct xe_gt *gt, u32 start, u32 num)
@@ -933,7 +933,7 @@ static int pf_provision_vf_ctxs(struct xe_gt *gt, unsigned int vfid, u32 num_ctx
xe_gt_assert(gt, vfid);
- if (num_ctxs > GUC_ID_MAX)
+ if (num_ctxs > xe_guc_id_mgr_max_shareable(&gt->uc.guc.submission_state.idm))
return -EINVAL;
if (config->num_ctxs) {
@@ -1170,23 +1170,30 @@ int xe_gt_sriov_pf_config_bulk_set_ctxs(struct xe_gt *gt, unsigned int vfid,
static u32 pf_profile_fair_ctxs(struct xe_gt *gt, unsigned int num_vfs)
{
+ u32 max_shareable = xe_guc_id_mgr_max_shareable(&gt->uc.guc.submission_state.idm);
bool admin_only_pf = xe_sriov_pf_admin_only(gt_to_xe(gt));
if (admin_only_pf && num_vfs == 1)
- return ALIGN_DOWN(GUC_ID_MAX, SZ_1K);
+ return ALIGN_DOWN(max_shareable, SZ_1K);
- return rounddown_pow_of_two(GUC_ID_MAX / num_vfs);
+ return rounddown_pow_of_two(max_shareable / num_vfs);
}
static u32 pf_estimate_fair_ctxs(struct xe_gt *gt, unsigned int num_vfs)
{
struct xe_guc_id_mgr *idm = &gt->uc.guc.submission_state.idm;
+ u32 max_shareable = xe_guc_id_mgr_max_shareable(idm);
u32 spare = pf_get_spare_ctxs(gt);
- u32 fair = (idm->total - spare) / num_vfs;
+ u32 fair;
int ret;
+ if (max_shareable <= spare)
+ return 0;
+
+ fair = (max_shareable - spare) / num_vfs;
+
for (; fair; --fair) {
- ret = xe_guc_id_mgr_reserve(idm, fair * num_vfs, spare);
+ ret = xe_guc_id_mgr_reserve_shareable(idm, fair * num_vfs, spare);
if (ret < 0)
continue;
xe_guc_id_mgr_release(idm, ret, fair * num_vfs);
diff --git a/drivers/gpu/drm/xe/xe_gt_sriov_pf_service.c b/drivers/gpu/drm/xe/xe_gt_sriov_pf_service.c
index 6586df2fcb91..fba2d5e1e94c 100644
--- a/drivers/gpu/drm/xe/xe_gt_sriov_pf_service.c
+++ b/drivers/gpu/drm/xe/xe_gt_sriov_pf_service.c
@@ -114,6 +114,7 @@ static const struct xe_reg ver_35_runtime_regs[] = {
XE3P_XPC_GT_GEOMETRY_DSS_3, /* _MMIO(0x915c) */
XE3P_XPC_GT_COMPUTE_DSS_3, /* _MMIO(0x9160) */
SERVICE_COPY_ENABLE, /* _MMIO(0x9170) */
+ RESOURCE_COPY_ENABLE, /* _MMIO(0x9174) */
};
static const struct xe_reg *pick_runtime_regs(struct xe_device *xe, unsigned int *count)
diff --git a/drivers/gpu/drm/xe/xe_guc.c b/drivers/gpu/drm/xe/xe_guc.c
index 3ab4cb9e4964..b9ec21a8cad1 100644
--- a/drivers/gpu/drm/xe/xe_guc.c
+++ b/drivers/gpu/drm/xe/xe_guc.c
@@ -777,7 +777,7 @@ int xe_guc_init_noalloc(struct xe_guc *guc)
return 0;
out:
- xe_gt_err(gt, "GuC init failed with %pe\n", ERR_PTR(ret));
+ xe_log_err(gt, GUC, ret, "Early initialization error\n");
return ret;
}
@@ -845,7 +845,7 @@ int xe_guc_init(struct xe_guc *guc)
return 0;
out:
- xe_gt_err(gt, "GuC init failed with %pe\n", ERR_PTR(ret));
+ xe_log_err(gt, GUC, ret, "Initialization error\n");
return ret;
}
@@ -853,7 +853,7 @@ static int vf_guc_init_post_hwconfig(struct xe_guc *guc)
{
int err;
- err = xe_guc_submit_init(guc, xe_gt_sriov_vf_guc_ids(guc_to_gt(guc)));
+ err = xe_guc_submit_init(guc);
if (err)
return err;
@@ -897,7 +897,7 @@ int xe_guc_init_post_hwconfig(struct xe_guc *guc)
guc_init_params_post_hwconfig(guc);
- ret = xe_guc_submit_init(guc, ~0);
+ ret = xe_guc_submit_init(guc);
if (ret)
return ret;
@@ -998,15 +998,15 @@ int xe_guc_reset(struct xe_guc *guc)
ret = xe_mmio_wait32(mmio, GDRST, GRDOM_GUC, 0, 5000, &gdrst, false);
if (ret) {
- xe_gt_err(gt, "GuC reset timed out, GDRST=%#x\n", gdrst);
+ xe_log_err(gt, GUC, ret, "Reset timeout, GDRST=%#x\n", gdrst);
goto err_out;
}
guc_status = xe_mmio_read32(mmio, GUC_STATUS);
if (!(guc_status & GS_MIA_IN_RESET)) {
- xe_gt_err(gt, "GuC status: %#x, MIA core expected to be in reset\n",
- guc_status);
ret = -EIO;
+ xe_log_err(gt, GUC, ret, "MIA core not in reset, GUC_STATUS=%#x\n",
+ guc_status);
goto err_out;
}
@@ -1144,8 +1144,8 @@ static void print_load_status_err(struct xe_gt *gt, u32 status)
* Check GUC_STATUS looking for known terminal states (either completion or
* failure) of either the microkernel status field or the boot ROM status field.
*
- * Returns 1 for successful completion, -1 for failure and 0 for any
- * intermediate state.
+ * Returns 0 for successful completion, -EBUSY for any intermediate state and
+ * other errno values for failure cases.
*/
static int guc_load_done(struct xe_gt *gt, u32 *status, u32 *tries)
{
@@ -1157,20 +1157,22 @@ static int guc_load_done(struct xe_gt *gt, u32 *status, u32 *tries)
switch (ukernel) {
case XE_GUC_LOAD_STATUS_READY:
- return 1;
+ return 0;
case XE_GUC_LOAD_STATUS_ERROR_DEVID_BUILD_MISMATCH:
case XE_GUC_LOAD_STATUS_GUC_PREPROD_BUILD_MISMATCH:
case XE_GUC_LOAD_STATUS_ERROR_DEVID_INVALID_GUCTYPE:
case XE_GUC_LOAD_STATUS_HWCONFIG_ERROR:
case XE_GUC_LOAD_STATUS_BOOTROM_VERSION_MISMATCH:
+ return -ENOEXEC;
case XE_GUC_LOAD_STATUS_DPC_ERROR:
case XE_GUC_LOAD_STATUS_EXCEPTION:
+ return -EIO;
case XE_GUC_LOAD_STATUS_INIT_DATA_INVALID:
case XE_GUC_LOAD_STATUS_MPU_DATA_INVALID:
case XE_GUC_LOAD_STATUS_INIT_MMIO_SAVE_RESTORE_INVALID:
case XE_GUC_LOAD_STATUS_KLV_WORKAROUND_INIT_ERROR:
case XE_GUC_LOAD_STATUS_INVALID_FTR_FLAG:
- return -1;
+ return -EINVAL;
}
switch (bootrom) {
@@ -1184,7 +1186,7 @@ static int guc_load_done(struct xe_gt *gt, u32 *status, u32 *tries)
case XE_BOOTROM_STATUS_MPUMAP_INCORRECT:
case XE_BOOTROM_STATUS_EXCEPTION:
case XE_BOOTROM_STATUS_PROD_KEY_CHECK_FAILURE:
- return -1;
+ return -ENOEXEC;
}
if (++*tries >= 100) {
@@ -1197,7 +1199,7 @@ static int guc_load_done(struct xe_gt *gt, u32 *status, u32 *tries)
*status, ukernel, bootrom);
}
- return 0;
+ return -EBUSY;
}
static int guc_wait_ucode(struct xe_guc *guc)
@@ -1213,21 +1215,21 @@ static int guc_wait_ucode(struct xe_guc *guc)
before_freq = xe_guc_pc_get_act_freq(guc_pc);
before = ktime_get();
- ret = poll_timeout_us(load_result = guc_load_done(gt, &status, &tries), load_result,
- 10 * USEC_PER_MSEC,
+ ret = poll_timeout_us(load_result = guc_load_done(gt, &status, &tries),
+ load_result != -EBUSY, 10 * USEC_PER_MSEC,
GUC_LOAD_TIMEOUT_SEC * USEC_PER_SEC, false);
delta_ms = ktime_to_ms(ktime_sub(ktime_get(), before));
act_freq = xe_guc_pc_get_act_freq(guc_pc);
cur_freq = xe_guc_pc_get_cur_freq_fw(guc_pc);
- if (ret || load_result <= 0) {
- xe_gt_err(gt, "load failed: status = 0x%08X, time = %lldms, freq = %dMHz (req %dMHz)\n",
- status, delta_ms, xe_guc_pc_get_act_freq(guc_pc),
- xe_guc_pc_get_cur_freq_fw(guc_pc));
+ if (ret || load_result) {
+ xe_log_err(gt, GUC, ret ?: load_result,
+ "Load failed, GUC_STATUS=%#x, time = %lldms, freq = %uMHz (req %uMHz)\n",
+ status, delta_ms, act_freq, cur_freq);
print_load_status_err(gt, status);
- return -EPROTO;
+ return ret ?: load_result;
}
if (delta_ms > GUC_LOAD_TIME_WARN_MSEC) {
@@ -1461,7 +1463,7 @@ int xe_guc_suspend(struct xe_guc *guc)
ret = xe_guc_softreset(guc);
if (ret) {
- xe_gt_err(gt, "GuC suspend failed: %pe\n", ERR_PTR(ret));
+ xe_log_err(gt, GUC, ret, "Suspend aborted\n");
return ret;
}
diff --git a/drivers/gpu/drm/xe/xe_guc_ct.c b/drivers/gpu/drm/xe/xe_guc_ct.c
index da5ce97484b8..e9fc55917fa3 100644
--- a/drivers/gpu/drm/xe/xe_guc_ct.c
+++ b/drivers/gpu/drm/xe/xe_guc_ct.c
@@ -30,6 +30,7 @@
#include "xe_guc_relay.h"
#include "xe_guc_submit.h"
#include "xe_guc_tlb_inval.h"
+#include "xe_log.h"
#include "xe_map.h"
#include "xe_page_reclaim.h"
#include "xe_pm.h"
@@ -37,6 +38,95 @@
#include "xe_sriov_vf.h"
#include "xe_trace_guc.h"
+/**
+ * DOC: GuC CT Error Codes
+ *
+ * Send path - xe_guc_ct_send(), xe_guc_ct_send_locked(), h2g_write()
+ * ------------------------------------------------------------------
+ *
+ * Channel-state rejections - the message was never queued, nothing is corrupt:
+ *
+ * ``-ENOTRECOVERABLE``: the device is wedged. No CT traffic can make progress
+ * until the device is recovered.
+ * ``-ECONNABORTED``: the H2G CTB was flagged broken by an earlier corruption
+ * check, so the driver aborted the channel. It stays unusable until the CT is
+ * restarted, which clears the flag.
+ * ``-ENODEV``: the CT channel is disabled. Don't retry until it is enabled.
+ * ``-ECANCELED``: the CT channel is stopped or a GT recovery is pending, so the
+ * message was dropped. Often benign; retry after the reset/recovery completes.
+ *
+ * Internal flow control - never seen by callers, handled by the send helpers:
+ *
+ * ``-EBUSY``: not enough H2G room or G2H credits. Triggers a flush, a wait and
+ * a retry.
+ * ``-EAGAIN``: the message would wrap the H2G ring, so the tail was NOP-filled
+ * and reset to 0. The write is reissued immediately.
+ *
+ * Hard failures:
+ *
+ * ``-EPIPE``: H2G descriptor corruption found before writing - non-zero status,
+ * or head/tail out of range. Marks the CTB broken.
+ * ``-EDEADLK``: H2G credits never recovered after waiting ~1s. An async GT reset
+ * is requested before this is returned.
+ * ``-EPROTO``: the MMIO CT enable/disable handshake got an unexpected reply.
+ * ``-ENOMEM``: internal allocation failure (fence lookup under GFP_ATOMIC, or the
+ * G2H workqueue at init). The blocking path retries the allocation with
+ * GFP_KERNEL.
+ *
+ * Blocking send only (xe_guc_ct_send_recv()) - the H2G went out, the reply is the
+ * problem:
+ *
+ * ``-ETIME``: no G2H response within 1s, even after flushing the G2H worker.
+ * ``-EIO``: the GuC replied RESPONSE_FAILURE; error and hint codes are logged.
+ * ``-ELOOP``: the GuC kept replying NO_RESPONSE_RETRY until the request hit
+ * GUC_SEND_RETRY_LIMIT. (NO_RESPONSE_BUSY is not an error - the waiter re-arms.)
+ * ``-ECANCELED``: the pending fence was canceled while waiting, because the CT
+ * was stopped underneath the waiter. Retry after the reset completes.
+ * ``-ENOTRECOVERABLE``: same as above, but the device got wedged in the
+ * meantime, so there is nothing to retry.
+ *
+ * Receive path - g2h_read(), parse_g2h_msg(), dequeue_one_g2h()
+ * -------------------------------------------------------------
+ *
+ * Channel-state rejections - the same four codes as the send path, checked against
+ * the G2H CTB. g2h_err_is_fatal() treats exactly these as benign; anything else
+ * declares the CT dead and kicks a reset:
+ *
+ * ``-ENOTRECOVERABLE``: device wedged.
+ * ``-ENODEV``: CT disabled.
+ * ``-ECANCELED``: CT stopped.
+ * ``-ECONNABORTED``: G2H CTB already flagged broken.
+ *
+ * Hard failures:
+ *
+ * ``-EPIPE``: G2H descriptor corruption - non-zero status, head/tail out of
+ * range, or a declared message length larger than the data actually available.
+ * ``-EBADMSG``: the G2H origin field is not the GuC.
+ * ``-EINVAL``: a FAST_REQUEST H2G came back rejected. FAST_REQUEST fences
+ * aren't tracked, so there is no caller to report to and the failure is
+ * escalated to a reset instead.
+ * ``-EPROTO``: a FAST_REQUEST H2G got a reply that is neither a rejection nor
+ * a valid response type.
+ * ``-EOPNOTSUPP``: G2H carried an HXG type this driver does not handle.
+ *
+ * Reported but not returned
+ * -------------------------
+ *
+ * These never reach a caller: they are detected in a void path or in a G2H
+ * handler whose return value is swallowed. They are reported and the CT is
+ * declared dead, which is the only escalation available:
+ *
+ * ``-EHOSTDOWN``: the GuC sent a crash dump or an exception notification, i.e.
+ * the firmware itself died. A reset is kicked to reload it.
+ * ``-ETOOMANYREFS``: G2H credit accounting went wrong - a release would push
+ * the free space past the usable size of the CTB, or there was no outstanding
+ * G2H to release. Indicates a driver-side bookkeeping bug, not a GuC one.
+ * Any error from a G2H action handler: the handler for a given G2H failed while
+ * processing it. The code is whatever the handler returned and is reported
+ * along with the action and the raw message, from both the worker
+ * (process_g2h_msg()) and the IRQ fast path (g2h_fast_path()).
+ */
+
static void receive_g2h(struct xe_guc_ct *ct);
static void g2h_worker_func(struct work_struct *w);
static void safe_mode_worker_func(struct work_struct *w);
@@ -59,7 +149,6 @@ static struct xe_device *ct_to_xe(struct xe_guc_ct *ct)
return gt_to_xe(ct_to_gt(ct));
}
-#if IS_ENABLED(CONFIG_DRM_XE_DEBUG)
enum {
/* Internal states, not error conditions */
CT_DEAD_STATE_REARM, /* 0x0001 */
@@ -82,8 +171,12 @@ enum {
CT_DEAD_CRASH, /* 0x8000 */
};
+#define ct_error_id(reason_code) CT_DEAD_##reason_code
+static void process_ct_dead(struct xe_guc_ct *ct, struct guc_ctb *ctb, u32 reason_code);
+#define CT_DEAD(ct, ctb, reason_code) process_ct_dead((ct), (ctb), ct_error_id(reason_code))
+
+#if IS_ENABLED(CONFIG_DRM_XE_DEBUG)
static void ct_dead_worker_func(struct work_struct *w);
-static void ct_dead_capture(struct xe_guc_ct *ct, struct guc_ctb *ctb, u32 reason_code);
static void ct_dead_fini(struct xe_guc_ct *ct)
{
@@ -162,9 +255,6 @@ static void fast_req_track(struct xe_guc_ct *ct, u16 fence, u16 action)
ct->fast_req[slot].fence = fence;
ct->fast_req[slot].action = action;
}
-
-#define CT_DEAD(ct, ctb, reason_code) ct_dead_capture((ct), (ctb), CT_DEAD_##reason_code)
-
#else
static void ct_dead_fini(struct xe_guc_ct *ct) { }
@@ -173,13 +263,6 @@ static void ct_dead_init(struct xe_guc_ct *ct) { }
static void fast_req_report(struct xe_guc_ct *ct, u16 fence) { }
static void fast_req_track(struct xe_guc_ct *ct, u16 fence, u16 action) { }
-#define CT_DEAD(ct, ctb, reason) \
- do { \
- struct guc_ctb *_ctb = (ctb); \
- if (_ctb) \
- _ctb->info.broken = true; \
- } while (0)
-
#endif
/* Used when a CT send wants to block and / or receive data */
@@ -681,7 +764,7 @@ static int __xe_guc_ct_start(struct xe_guc_ct *ct, bool needs_register)
return 0;
err_out:
- xe_gt_err(gt, "Failed to enable GuC CT (%pe)\n", ERR_PTR(err));
+ xe_log_err(gt, GUC, err, "CT: Failed to enable\n");
CT_DEAD(ct, NULL, SETUP);
return err;
@@ -805,8 +888,9 @@ static bool h2g_has_room(struct xe_guc_ct *ct, u32 cmd_len)
desc_write(xe, h2g, status, desc_status | GUC_CTB_STATUS_OVERFLOW);
- xe_gt_err(ct_to_gt(ct), "CT: invalid head offset %u >= %u)\n",
- h2g->info.head, h2g->info.size);
+ xe_log_err(ct_to_gt(ct), GUC, -EPIPE,
+ "CT: invalid head offset %u >= %u)\n",
+ h2g->info.head, h2g->info.size);
CT_DEAD(ct, h2g, H2G_HAS_ROOM);
return false;
}
@@ -875,12 +959,13 @@ static void __g2h_release_space(struct xe_guc_ct *ct, u32 g2h_len)
bad |= !ct->g2h_outstanding;
if (bad) {
- xe_gt_err(ct_to_gt(ct), "Invalid G2H release: %d + %d vs %d - %d -> %d vs %d, outstanding = %d!\n",
- ct->ctbs.g2h.info.space, g2h_len,
- ct->ctbs.g2h.info.size, ct->ctbs.g2h.info.resv_space,
- ct->ctbs.g2h.info.space + g2h_len,
- ct->ctbs.g2h.info.size - ct->ctbs.g2h.info.resv_space,
- ct->g2h_outstanding);
+ xe_log_err(ct_to_gt(ct), GUC, -ETOOMANYREFS,
+ "CT: Invalid G2H release: %d + %d vs %d - %d -> %d vs %d, outstanding = %d!\n",
+ ct->ctbs.g2h.info.space, g2h_len,
+ ct->ctbs.g2h.info.size, ct->ctbs.g2h.info.resv_space,
+ ct->ctbs.g2h.info.space + g2h_len,
+ ct->ctbs.g2h.info.size - ct->ctbs.g2h.info.resv_space,
+ ct->g2h_outstanding);
CT_DEAD(ct, &ct->ctbs.g2h, G2H_RELEASE);
return;
}
@@ -938,6 +1023,23 @@ static bool vf_action_can_safely_fail(struct xe_device *xe, u32 action)
action == XE_GUC_ACTION_REGISTER_CONTEXT);
}
+static int ct_corrupted(struct xe_guc_ct *ct, struct guc_ctb *ctb,
+ u32 reason_code, const char *msg, ...)
+{
+ struct va_format vaf;
+ va_list va_args;
+ int err = -EPIPE;
+
+ va_start(va_args, msg);
+ vaf.fmt = msg;
+ vaf.va = &va_args;
+ xe_log_err(ct_to_gt(ct), GUC, err, "CT: %pV", &vaf);
+ va_end(va_args);
+
+ process_ct_dead(ct, ctb, reason_code);
+ return err;
+}
+
#define H2G_CT_HEADERS (GUC_CTB_HDR_LEN + 1) /* one DW CTB header and one DW HxG header */
static int h2g_write(struct xe_guc_ct *ct, const u32 *action, u32 len,
@@ -962,23 +1064,22 @@ static int h2g_write(struct xe_guc_ct *ct, const u32 *action, u32 len,
u32 desc_status;
desc_status = desc_read(xe, h2g, status);
- if (desc_status) {
- xe_gt_err(gt, "CT write: non-zero status: %u\n", desc_status);
- goto corrupted;
- }
+ if (desc_status)
+ return ct_corrupted(ct, h2g, ct_error_id(H2G_WRITE),
+ "write: non-zero status: %u\n", desc_status);
if (tail > h2g->info.size) {
desc_write(xe, h2g, status, desc_status | GUC_CTB_STATUS_OVERFLOW);
- xe_gt_err(gt, "CT write: tail out of range: %u vs %u\n",
- tail, h2g->info.size);
- goto corrupted;
+ return ct_corrupted(ct, h2g, ct_error_id(H2G_WRITE),
+ "write: tail out of range: %u vs %u\n",
+ tail, h2g->info.size);
}
if (desc_head >= h2g->info.size) {
desc_write(xe, h2g, status, desc_status | GUC_CTB_STATUS_OVERFLOW);
- xe_gt_err(gt, "CT write: invalid head offset %u >= %u)\n",
- desc_head, h2g->info.size);
- goto corrupted;
+ return ct_corrupted(ct, h2g, ct_error_id(H2G_WRITE),
+ "write: invalid head offset %u >= %u)\n",
+ desc_head, h2g->info.size);
}
}
@@ -1043,10 +1144,6 @@ static int h2g_write(struct xe_guc_ct *ct, const u32 *action, u32 len,
desc_read(xe, h2g, head), h2g->info.tail);
return 0;
-
-corrupted:
- CT_DEAD(ct, &ct->ctbs.h2g, H2G_WRITE);
- return -EPIPE;
}
static int __guc_ct_send_locked(struct xe_guc_ct *ct, const u32 *action,
@@ -1070,7 +1167,7 @@ static int __guc_ct_send_locked(struct xe_guc_ct *ct, const u32 *action,
}
if (unlikely(ct->ctbs.h2g.info.broken)) {
- ret = -EPIPE;
+ ret = -ECONNABORTED;
goto out;
}
@@ -1222,7 +1319,7 @@ try_again:
return ret;
broken:
- xe_gt_err(gt, "No forward process on H2G, reset required\n");
+ xe_log_err(gt, GUC, -EDEADLK, "CT: No forward progress on H2G\n");
CT_DEAD(ct, &ct->ctbs.h2g, DEADLOCK);
return -EDEADLK;
@@ -1257,6 +1354,9 @@ static int guc_ct_send(struct xe_guc_ct *ct, const u32 *action, u32 len,
* 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.
+ * * -ECONNABORTED: the H2G CTB is marked broken, so the driver aborted the
+ * channel. It 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
@@ -1380,7 +1480,8 @@ int xe_guc_ct_send_g2h_handler(struct xe_guc_ct *ct, const u32 *action, u32 len)
*/
static bool retry_failure(struct xe_guc_ct *ct, int ret)
{
- if (!(ret == -EDEADLK || ret == -EPIPE || ret == -ENODEV))
+ if (!(ret == -EDEADLK || ret == -EPIPE || ret == -ECONNABORTED ||
+ ret == -ENODEV))
return false;
#define ct_alive(ct) \
@@ -1568,22 +1669,18 @@ static int parse_g2h_event(struct xe_guc_ct *ct, u32 *msg, u32 len)
return 0;
}
-static int guc_crash_process_msg(struct xe_guc_ct *ct, u32 action)
+static void guc_handle_crash_dump_msg(struct xe_guc_ct *ct)
{
- struct xe_gt *gt = ct_to_gt(ct);
-
- if (action == XE_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED)
- xe_gt_err(gt, "GuC Crash dump notification\n");
- else if (action == XE_GUC_ACTION_NOTIFY_EXCEPTION)
- xe_gt_err(gt, "GuC Exception notification\n");
- else
- xe_gt_err(gt, "Unknown GuC crash notification: 0x%04X\n", action);
-
+ xe_log_err(ct_to_gt(ct), GUC, -EHOSTDOWN, "Crash dump notification\n");
CT_DEAD(ct, NULL, CRASH);
-
kick_reset(ct);
+}
- return 0;
+static void guc_handle_exception_msg(struct xe_guc_ct *ct)
+{
+ xe_log_err(ct_to_gt(ct), GUC, -EHOSTDOWN, "Exception notification\n");
+ CT_DEAD(ct, NULL, CRASH);
+ kick_reset(ct);
}
static int parse_g2h_response(struct xe_guc_ct *ct, u32 *msg, u32 len)
@@ -1606,14 +1703,21 @@ static int parse_g2h_response(struct xe_guc_ct *ct, u32 *msg, u32 len)
* failure to trigger a reset.
*/
if (fence & CT_SEQNO_UNTRACKED) {
- if (type == GUC_HXG_TYPE_RESPONSE_FAILURE)
- xe_gt_err(gt, "FAST_REQ H2G fence 0x%x failed! e=0x%x, h=%u\n",
- fence,
- FIELD_GET(GUC_HXG_FAILURE_MSG_0_ERROR, hxg[0]),
- FIELD_GET(GUC_HXG_FAILURE_MSG_0_HINT, hxg[0]));
- else
- xe_gt_err(gt, "unexpected response %u for FAST_REQ H2G fence 0x%x!\n",
- type, fence);
+ int err;
+
+ if (type == GUC_HXG_TYPE_RESPONSE_FAILURE) {
+ err = -EINVAL;
+ xe_log_err(gt, GUC, err,
+ "CT: FAST_REQ H2G fence 0x%x failed! e=0x%x, h=%u\n",
+ fence,
+ FIELD_GET(GUC_HXG_FAILURE_MSG_0_ERROR, hxg[0]),
+ FIELD_GET(GUC_HXG_FAILURE_MSG_0_HINT, hxg[0]));
+ } else {
+ err = -EPROTO;
+ xe_log_err(gt, GUC, err,
+ "CT: unexpected response %u for FAST_REQ H2G fence 0x%x!\n",
+ type, fence);
+ }
fast_req_report(ct, fence);
@@ -1623,7 +1727,7 @@ static int parse_g2h_response(struct xe_guc_ct *ct, u32 *msg, u32 len)
CT_DEAD(ct, NULL, PARSE_G2H_RESPONSE);
- return -EPROTO;
+ return err;
}
/* don't erase as we still expect a final response with the same fence */
@@ -1689,11 +1793,12 @@ static int parse_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len)
origin = FIELD_GET(GUC_HXG_MSG_0_ORIGIN, hxg[0]);
if (unlikely(origin != GUC_HXG_ORIGIN_GUC)) {
- xe_gt_err(gt, "G2H channel broken on read, origin=%u, reset required\n",
- origin);
+ int err = -EBADMSG;
+
+ xe_log_err(gt, GUC, err, "CT: Invalid G2H origin=%u\n", origin);
CT_DEAD(ct, &ct->ctbs.g2h, PARSE_G2H_ORIGIN);
- return -EPROTO;
+ return err;
}
type = FIELD_GET(GUC_HXG_MSG_0_TYPE, hxg[0]);
@@ -1708,8 +1813,7 @@ static int parse_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len)
ret = parse_g2h_response(ct, msg, len);
break;
default:
- xe_gt_err(gt, "G2H channel broken on read, type=%u, reset required\n",
- type);
+ xe_log_err(gt, GUC, -EOPNOTSUPP, "CT: Unexpected G2H message type %u\n", type);
CT_DEAD(ct, &ct->ctbs.g2h, PARSE_G2H_TYPE);
ret = -EOPNOTSUPP;
@@ -1787,8 +1891,10 @@ static int process_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len)
ret = xe_gt_sriov_pf_monitor_process_guc2pf(gt, hxg, hxg_len);
break;
case XE_GUC_ACTION_NOTIFY_CRASH_DUMP_POSTED:
+ guc_handle_crash_dump_msg(ct);
+ break;
case XE_GUC_ACTION_NOTIFY_EXCEPTION:
- ret = guc_crash_process_msg(ct, action);
+ guc_handle_exception_msg(ct);
break;
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
case XE_GUC_ACTION_TEST_G2G_RECV:
@@ -1807,8 +1913,8 @@ static int process_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len)
}
if (ret) {
- xe_gt_err(gt, "G2H action %#04x failed (%pe) len %u msg %*ph\n",
- action, ERR_PTR(ret), hxg_len, (int)sizeof(u32) * hxg_len, hxg);
+ xe_log_err(gt, GUC, ret, "CT: G2H action %#04x failed len %u msg %*ph\n",
+ action, hxg_len, (int)sizeof(u32) * hxg_len, hxg);
CT_DEAD(ct, NULL, PROCESS_FAILED);
}
@@ -1828,6 +1934,7 @@ 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);
+ /* Keep in sync with g2h_err_is_fatal() */
if (xe_device_wedged(xe))
return -ENOTRECOVERABLE;
@@ -1838,7 +1945,7 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
return -ECANCELED;
if (g2h->info.broken)
- return -EPIPE;
+ return -ECONNABORTED;
xe_gt_assert(gt, xe_guc_ct_enabled(ct));
@@ -1854,10 +1961,9 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
desc_status &= ~GUC_CTB_STATUS_DISABLED;
}
- if (desc_status) {
- xe_gt_err(gt, "CT read: non-zero status: %u\n", desc_status);
- goto corrupted;
- }
+ if (desc_status)
+ return ct_corrupted(ct, g2h, ct_error_id(G2H_READ),
+ "read: non-zero status: %u\n", desc_status);
}
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
@@ -1878,24 +1984,24 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
*
if (g2h->info.head != desc_head) {
desc_write(xe, g2h, status, desc_status | GUC_CTB_STATUS_MISMATCH);
- xe_gt_err(gt, "CT read: head was modified %u != %u\n",
- desc_head, g2h->info.head);
- goto corrupted;
+ return ct_corrupted(ct, g2h, ct_error_id(G2H_READ),
+ "read: head was modified %u != %u\n",
+ desc_head, g2h->info.head);
}
*/
if (g2h->info.head > g2h->info.size) {
desc_write(xe, g2h, status, desc_status | GUC_CTB_STATUS_OVERFLOW);
- xe_gt_err(gt, "CT read: head out of range: %u vs %u\n",
- g2h->info.head, g2h->info.size);
- goto corrupted;
+ return ct_corrupted(ct, g2h, ct_error_id(G2H_READ),
+ "read: head out of range: %u vs %u\n",
+ g2h->info.head, g2h->info.size);
}
if (desc_tail >= g2h->info.size) {
desc_write(xe, g2h, status, desc_status | GUC_CTB_STATUS_OVERFLOW);
- xe_gt_err(gt, "CT read: invalid tail offset %u >= %u)\n",
- desc_tail, g2h->info.size);
- goto corrupted;
+ return ct_corrupted(ct, g2h, ct_error_id(G2H_READ),
+ "read: invalid tail offset %u >= %u)\n",
+ desc_tail, g2h->info.size);
}
}
@@ -1912,11 +2018,10 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
xe_map_memcpy_from(xe, msg, &g2h->cmds, sizeof(u32) * g2h->info.head,
sizeof(u32));
len = FIELD_GET(GUC_CTB_MSG_0_NUM_DWORDS, msg[0]) + GUC_CTB_MSG_MIN_LEN;
- if (len > avail) {
- xe_gt_err(gt, "G2H channel broken on read, avail=%d, len=%d, reset required\n",
- avail, len);
- goto corrupted;
- }
+ if (len > avail)
+ return ct_corrupted(ct, g2h, ct_error_id(G2H_READ),
+ "G2H channel broken on read, avail=%d, len=%d\n",
+ avail, len);
head = (g2h->info.head + 1) % g2h->info.size;
avail = len - 1;
@@ -1962,10 +2067,6 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
action, len, g2h->info.head, tail);
return len;
-
-corrupted:
- CT_DEAD(ct, &ct->ctbs.g2h, G2H_READ);
- return -EPROTO;
}
static void g2h_fast_path(struct xe_guc_ct *ct, u32 *msg, u32 len)
@@ -1996,8 +2097,7 @@ static void g2h_fast_path(struct xe_guc_ct *ct, u32 *msg, u32 len)
}
if (ret) {
- xe_gt_err(gt, "G2H action 0x%04x failed (%pe)\n",
- action, ERR_PTR(ret));
+ xe_log_err(gt, GUC, ret, "CT: G2H action 0x%04x failed\n", action);
CT_DEAD(ct, NULL, FAST_G2H);
}
}
@@ -2057,6 +2157,27 @@ static int dequeue_one_g2h(struct xe_guc_ct *ct)
return 1;
}
+/*
+ * Errors reported by dequeue_one_g2h() come in two flavours: either the channel
+ * is simply not available right now, which is expected and handled gracefully,
+ * or the channel state or the message itself is inconsistent, in which case the
+ * only way forward is to declare the CT dead and reset the GuC. Since the
+ * former is a short and well known list, check against that and treat anything
+ * else as fatal, so that new error codes don't silently escape the escalation.
+ */
+static bool g2h_err_is_fatal(int err)
+{
+ switch (err) {
+ case -ENOTRECOVERABLE: /* device wedged */
+ case -ENODEV: /* CT disabled */
+ case -ECANCELED: /* CT stopped */
+ case -ECONNABORTED: /* CT already declared broken */
+ return false;
+ default:
+ return true;
+ }
+}
+
static void receive_g2h(struct xe_guc_ct *ct)
{
bool ongoing;
@@ -2094,8 +2215,8 @@ static void receive_g2h(struct xe_guc_ct *ct)
ret = dequeue_one_g2h(ct);
mutex_unlock(&ct->lock);
- if (unlikely(ret == -EPROTO || ret == -EOPNOTSUPP)) {
- xe_gt_err(ct_to_gt(ct), "CT dequeue failed: %d\n", ret);
+ if (unlikely(ret < 0 && g2h_err_is_fatal(ret))) {
+ xe_log_err(ct_to_gt(ct), GUC, ret, "CT: dequeue failed, forcing GT reset\n");
CT_DEAD(ct, NULL, G2H_RECV);
kick_reset(ct);
}
@@ -2201,11 +2322,12 @@ struct xe_guc_ct_snapshot *xe_guc_ct_snapshot_capture(struct xe_guc_ct *ct)
* xe_guc_ct_snapshot_print - Print out a given GuC CT snapshot.
* @snapshot: GuC CT snapshot object.
* @p: drm_printer where it will be printed out.
+ * @gt: GT structure for error reporting
*
* This function prints out a given GuC CT snapshot object.
*/
void xe_guc_ct_snapshot_print(struct xe_guc_ct_snapshot *snapshot,
- struct drm_printer *p)
+ struct drm_printer *p, struct xe_gt *gt)
{
if (!snapshot)
return;
@@ -2219,11 +2341,11 @@ void xe_guc_ct_snapshot_print(struct xe_guc_ct_snapshot *snapshot,
drm_printf(p, "\tg2h outstanding: %d\n",
snapshot->g2h_outstanding);
- if (snapshot->ctb) {
- drm_printf(p, "[CTB].length: 0x%zx\n", snapshot->ctb_size);
- xe_print_blob_ascii85(p, "[CTB].data", '\n',
- snapshot->ctb, 0, snapshot->ctb_size);
- }
+ if (snapshot->ctb)
+ xe_guc_print_blob(gt, p, "[CTB]", "[CTB].data",
+ &snapshot->ctb, 1,
+ snapshot->ctb_size, snapshot->ctb_size,
+ &snapshot->dump_cache);
} else {
drm_puts(p, "CT disabled\n");
}
@@ -2241,6 +2363,7 @@ void xe_guc_ct_snapshot_free(struct xe_guc_ct_snapshot *snapshot)
if (!snapshot)
return;
+ xe_guc_dump_cache_fini(&snapshot->dump_cache);
kfree(snapshot->ctb);
kfree(snapshot);
}
@@ -2257,9 +2380,10 @@ void xe_guc_ct_snapshot_free(struct xe_guc_ct_snapshot *snapshot)
void xe_guc_ct_print(struct xe_guc_ct *ct, struct drm_printer *p, bool want_ctb)
{
struct xe_guc_ct_snapshot *snapshot;
+ struct xe_gt *gt = ct_to_gt(ct);
snapshot = guc_ct_snapshot_capture(ct, false, want_ctb);
- xe_guc_ct_snapshot_print(snapshot, p);
+ xe_guc_ct_snapshot_print(snapshot, p, gt);
xe_guc_ct_snapshot_free(snapshot);
}
@@ -2282,7 +2406,7 @@ ALLOW_ERROR_INJECTION(xe_is_injection_active, ERRNO);
int xe_is_injection_active(void) { return 0; }
#endif
-static void ct_dead_capture(struct xe_guc_ct *ct, struct guc_ctb *ctb, u32 reason_code)
+static void process_ct_dead(struct xe_guc_ct *ct, struct guc_ctb *ctb, u32 reason_code)
{
struct xe_guc_log_snapshot *snapshot_log;
struct xe_guc_ct_snapshot *snapshot_ct;
@@ -2357,10 +2481,10 @@ static void ct_dead_print(struct xe_dead_ct *dead)
drm_printf(&lp, "\tTile: %d\n", gt->tile->id);
drm_puts(&lp, "**** GuC Log ****\n");
- xe_guc_log_snapshot_print(dead->snapshot_log, &lp);
+ xe_guc_log_snapshot_print(dead->snapshot_log, &lp, gt);
drm_puts(&lp, "**** GuC CT ****\n");
- xe_guc_ct_snapshot_print(dead->snapshot_ct, &lp);
+ xe_guc_ct_snapshot_print(dead->snapshot_ct, &lp, gt);
drm_puts(&lp, "Done.\n");
}
@@ -2368,6 +2492,8 @@ static void ct_dead_print(struct xe_dead_ct *dead)
static void ct_dead_worker_func(struct work_struct *w)
{
struct xe_guc_ct *ct = container_of(w, struct xe_guc_ct, dead.worker);
+ struct xe_guc_log_snapshot *snapshot_log;
+ struct xe_guc_ct_snapshot *snapshot_ct;
if (!ct->dead.reported) {
ct->dead.reported = true;
@@ -2376,9 +2502,9 @@ static void ct_dead_worker_func(struct work_struct *w)
spin_lock_irq(&ct->dead.lock);
- xe_guc_log_snapshot_free(ct->dead.snapshot_log);
+ snapshot_log = ct->dead.snapshot_log;
ct->dead.snapshot_log = NULL;
- xe_guc_ct_snapshot_free(ct->dead.snapshot_ct);
+ snapshot_ct = ct->dead.snapshot_ct;
ct->dead.snapshot_ct = NULL;
if (ct->dead.reason & (1 << CT_DEAD_STATE_REARM)) {
@@ -2388,5 +2514,15 @@ static void ct_dead_worker_func(struct work_struct *w)
}
spin_unlock_irq(&ct->dead.lock);
+
+ /* Printing may have vmalloc'ed a compression cache; vfree() can sleep */
+ xe_guc_log_snapshot_free(snapshot_log);
+ xe_guc_ct_snapshot_free(snapshot_ct);
+}
+#else
+static void process_ct_dead(struct xe_guc_ct *ct, struct guc_ctb *ctb, u32 reason_code)
+{
+ if (ctb)
+ ctb->info.broken = true;
}
#endif
diff --git a/drivers/gpu/drm/xe/xe_guc_ct.h b/drivers/gpu/drm/xe/xe_guc_ct.h
index e2a33249d65d..f3af15c95c75 100644
--- a/drivers/gpu/drm/xe/xe_guc_ct.h
+++ b/drivers/gpu/drm/xe/xe_guc_ct.h
@@ -12,6 +12,7 @@
struct drm_printer;
struct xe_device;
+struct xe_gt;
int xe_guc_ct_init_noalloc(struct xe_guc_ct *ct);
int xe_guc_ct_init(struct xe_guc_ct *ct);
@@ -26,7 +27,8 @@ void xe_guc_ct_flush_and_stop(struct xe_guc_ct *ct);
void xe_guc_ct_fast_path(struct xe_guc_ct *ct);
struct xe_guc_ct_snapshot *xe_guc_ct_snapshot_capture(struct xe_guc_ct *ct);
-void xe_guc_ct_snapshot_print(struct xe_guc_ct_snapshot *snapshot, struct drm_printer *p);
+void xe_guc_ct_snapshot_print(struct xe_guc_ct_snapshot *snapshot, struct drm_printer *p,
+ struct xe_gt *gt);
void xe_guc_ct_snapshot_free(struct xe_guc_ct_snapshot *snapshot);
void xe_guc_ct_print(struct xe_guc_ct *ct, struct drm_printer *p, bool want_ctb);
diff --git a/drivers/gpu/drm/xe/xe_guc_ct_types.h b/drivers/gpu/drm/xe/xe_guc_ct_types.h
index f88e588af0d3..5e33f9fd4cb0 100644
--- a/drivers/gpu/drm/xe/xe_guc_ct_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_ct_types.h
@@ -14,6 +14,7 @@
#include <linux/xarray.h>
#include "abi/guc_communication_ctb_abi.h"
+#include "xe_guc_log_types.h"
struct xe_bo;
@@ -75,6 +76,8 @@ struct xe_guc_ct_snapshot {
size_t ctb_size;
/** @ctb: snapshot of the entire CTB */
void *ctb;
+ /** @dump_cache: compressed copy of @ctb, built on first print */
+ struct xe_guc_dump_cache dump_cache;
};
/**
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 d27826b36649..74b711abe257 100644
--- a/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_exec_queue_types.h
@@ -52,7 +52,10 @@ struct xe_guc_exec_queue {
u16 id;
/** @suspend_wait: wait queue used to wait on pending suspends */
wait_queue_head_t suspend_wait;
- /** @suspend_pending: a suspend of the exec_queue is pending */
+ /**
+ * @suspend_pending: a suspend of the exec_queue is pending.
+ * Protected by @sched.msg_lock.
+ */
bool suspend_pending;
/**
* @suspend_count: Reference count of active suspend requests. The
diff --git a/drivers/gpu/drm/xe/xe_guc_id_mgr.c b/drivers/gpu/drm/xe/xe_guc_id_mgr.c
index e845425d670b..7c7f5aaecfa0 100644
--- a/drivers/gpu/drm/xe/xe_guc_id_mgr.c
+++ b/drivers/gpu/drm/xe/xe_guc_id_mgr.c
@@ -34,6 +34,34 @@ static struct xe_device *idm_to_xe(struct xe_guc_id_mgr *idm)
static void idm_print_locked(struct xe_guc_id_mgr *idm, struct drm_printer *p, int indent);
+static unsigned int idm_total(struct xe_guc_id_mgr *idm)
+{
+ if (idm->usable > GUC_ID_MAX - idm->shareable)
+ return GUC_ID_MAX;
+
+ return idm->usable + idm->shareable;
+}
+
+static unsigned int idm_shareable_start(struct xe_guc_id_mgr *idm)
+{
+ return idm_total(idm) - idm->shareable;
+}
+
+static unsigned int idm_used_usable(struct xe_guc_id_mgr *idm)
+{
+ lockdep_assert_held(idm_mutex(idm));
+
+ return bitmap_weight(idm->bitmap, idm->usable);
+}
+
+static unsigned int idm_used_shareable(struct xe_guc_id_mgr *idm)
+{
+ lockdep_assert_held(idm_mutex(idm));
+
+ return bitmap_weight(idm->bitmap, idm_total(idm)) -
+ bitmap_weight(idm->bitmap, idm_shareable_start(idm));
+}
+
static void __fini_idm(struct drm_device *drm, void *arg)
{
struct xe_guc_id_mgr *idm = arg;
@@ -41,117 +69,221 @@ static void __fini_idm(struct drm_device *drm, void *arg)
mutex_lock(idm_mutex(idm));
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
- unsigned int weight = bitmap_weight(idm->bitmap, idm->total);
+ unsigned int weight = bitmap_weight(idm->bitmap, idm_total(idm));
if (weight) {
struct drm_printer p = xe_gt_info_printer(idm_to_gt(idm));
xe_gt_err(idm_to_gt(idm), "GUC ID manager unclean (%u/%u)\n",
- weight, idm->total);
+ weight, idm_total(idm));
idm_print_locked(idm, &p, 1);
}
}
bitmap_free(idm->bitmap);
idm->bitmap = NULL;
- idm->total = 0;
- idm->used = 0;
+ idm->usable = 0;
+ idm->shareable = 0;
mutex_unlock(idm_mutex(idm));
}
-/**
- * xe_guc_id_mgr_init() - Initialize GuC context ID Manager.
- * @idm: the &xe_guc_id_mgr to initialize
- * @limit: number of IDs to manage
- *
- * The bare-metal or PF driver can pass ~0 as &limit to indicate that all
- * context IDs supported by the GuC firmware are available for use.
- *
- * Only VF drivers will have to provide explicit number of context IDs
- * that they can use.
- *
- * Return: 0 on success or a negative error code on failure.
- */
-int xe_guc_id_mgr_init(struct xe_guc_id_mgr *idm, unsigned int limit)
+static int idm_init(struct xe_guc_id_mgr *idm, unsigned int usable, unsigned int shareable)
{
+ bool overlap = usable > GUC_ID_MAX - shareable;
+ unsigned int total;
int ret;
- idm_assert(idm, !idm->bitmap);
- idm_assert(idm, !idm->total);
- idm_assert(idm, !idm->used);
-
- if (limit == ~0)
- limit = GUC_ID_MAX;
- else if (limit > GUC_ID_MAX)
- return -ERANGE;
- else if (!limit)
+ if (!usable)
return -EINVAL;
+ if (usable > GUC_ID_MAX || shareable > GUC_ID_MAX)
+ return -ERANGE;
+
+ idm_assert(idm, !idm->bitmap);
+ idm_assert(idm, !shareable || IS_SRIOV_PF(idm_to_xe(idm)));
+ total = overlap ? GUC_ID_MAX : usable + shareable;
- idm->bitmap = bitmap_zalloc(limit, GFP_KERNEL);
+ idm->bitmap = bitmap_zalloc(total, GFP_KERNEL);
if (!idm->bitmap)
return -ENOMEM;
- idm->total = limit;
+
+ idm->usable = usable;
+ idm->shareable = shareable;
ret = drmm_add_action_or_reset(&idm_to_xe(idm)->drm, __fini_idm, idm);
if (ret)
return ret;
- xe_gt_dbg(idm_to_gt(idm), "using %u GuC ID%s\n",
- idm->total, str_plural(idm->total));
+ xe_gt_dbg(idm_to_gt(idm), "using %u GuC ID%s, %u GuC ID%s shareable with VFs\n",
+ idm->usable, str_plural(idm->usable), idm->shareable, str_plural(idm->shareable));
return 0;
}
-static unsigned int find_last_zero_area(unsigned long *bitmap,
- unsigned int total,
- unsigned int count)
+/**
+ * xe_guc_id_mgr_init() - Initialize GuC ID manager for native.
+ * @idm: the &xe_guc_id_mgr to initialize
+ *
+ * This function initializes the GuC ID manager to manage the full range of
+ * GuC context IDs (0..GUC_ID_MAX - 1) for use by the GuC submission code.
+ * No GuC context IDs will be reserved for sharing with VFs.
+ *
+ * Can only be called when the device is not in SR-IOV mode.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int xe_guc_id_mgr_init(struct xe_guc_id_mgr *idm)
+{
+ xe_gt_assert(idm_to_gt(idm), !IS_SRIOV(idm_to_xe(idm)));
+
+ return idm_init(idm, GUC_ID_MAX, 0);
+}
+
+/**
+ * xe_guc_id_mgr_init_small() - Initialize GuC ID manager for small count.
+ * @idm: the &xe_guc_id_mgr to initialize
+ * @count: the number of usable GuC context IDs
+ *
+ * This function initializes the GuC ID manager to manage a small number of
+ * GuC context IDs (0..count-1) for use by the GuC submission code.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_init_small(struct xe_guc_id_mgr *idm, unsigned int count)
+{
+ return idm_init(idm, count, 0);
+}
+
+#ifdef CONFIG_PCI_IOV
+/**
+ * xe_guc_id_mgr_init_shared() - Initialize GuC ID manager for PF.
+ * @idm: the &xe_guc_id_mgr to initialize
+ *
+ * This function initializes the GuC ID manager to manage the full range of
+ * GuC context IDs (0..GUC_ID_MAX - 1) for use by the GuC submission code.
+ * All GuC context IDs will be shareable with VFs.
+ *
+ * Can only be called when the device is in SR-IOV PF mode.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_init_shared(struct xe_guc_id_mgr *idm)
+{
+ xe_gt_assert(idm_to_gt(idm), IS_SRIOV_PF(idm_to_xe(idm)));
+
+ return idm_init(idm, GUC_ID_MAX, GUC_ID_MAX);
+}
+#endif /* CONFIG_PCI_IOV */
+
+/**
+ * xe_guc_id_mgr_max_usable() - Get maximum number of usable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr to query
+ *
+ * Return: maximum number of GuC context IDs that can be used by @idm for GuC submission.
+ */
+unsigned int xe_guc_id_mgr_max_usable(struct xe_guc_id_mgr *idm)
+{
+ return idm->usable;
+}
+
+/**
+ * xe_guc_id_mgr_max_shareable() - Get maximum number of shareable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr to query
+ *
+ * Return: maximum number of GuC context IDs that can be shared with VFs.
+ */
+unsigned int xe_guc_id_mgr_max_shareable(struct xe_guc_id_mgr *idm)
{
- unsigned int found = total;
- unsigned int rs, re, range;
+ return idm->shareable;
+}
- for_each_clear_bitrange(rs, re, bitmap, total) {
- range = re - rs;
+static int idm_reserve_chunk_usable_locked(struct xe_guc_id_mgr *idm, unsigned int count)
+{
+ int id;
+
+ idm_assert(idm, count);
+ lockdep_assert_held(idm_mutex(idm));
+
+ if (!idm->usable)
+ return -ENODATA;
+
+ id = bitmap_find_next_zero_area(idm->bitmap, idm->usable, 0, count, 0);
+ if (id >= idm->usable)
+ return -ENOSPC;
+
+ bitmap_set(idm->bitmap, id, count);
+
+ return id;
+}
+
+static unsigned int idm_find_last_zero_area_in_range(unsigned long *bitmap,
+ unsigned int range_start,
+ unsigned int range_end,
+ unsigned int count)
+{
+ unsigned int found = range_end;
+ unsigned int rs, re;
+
+ for_each_clear_bitrange(rs, re, bitmap, range_end) {
+ unsigned int start = max(rs, range_start);
+ unsigned int end = min(re, range_end);
+ unsigned int range;
+
+ if (start >= end)
+ continue;
+
+ range = end - start;
if (range < count)
continue;
- found = rs + (range - count);
+
+ found = start + (range - count);
}
+
return found;
}
-static int idm_reserve_chunk_locked(struct xe_guc_id_mgr *idm,
- unsigned int count, unsigned int retain)
+static int idm_check_quota(unsigned int used, unsigned int count,
+ unsigned int spare, unsigned int limit)
{
+ return used + count + spare > limit ? -EDQUOT : 0;
+}
+
+static int idm_reserve_chunk_shareable_locked(struct xe_guc_id_mgr *idm, unsigned int count,
+ unsigned int spare)
+{
+ unsigned int shareable_start = idm_shareable_start(idm);
+ unsigned int total = idm_total(idm);
int id;
idm_assert(idm, count);
lockdep_assert_held(idm_mutex(idm));
- if (!idm->total)
+ if (!idm->shareable)
return -ENODATA;
- if (retain) {
- /*
- * For IDs reservations (used on PF for VFs) we want to make
- * sure there will be at least 'retain' available for the PF
- */
- if (idm->used + count + retain > idm->total)
- return -EDQUOT;
- /*
- * ... and we want to reserve highest IDs close to the end.
- */
- id = find_last_zero_area(idm->bitmap, idm->total, count);
- } else {
- /*
- * For regular IDs reservations (used by submission code)
- * we start searching from the lower range of IDs.
- */
- id = bitmap_find_next_zero_area(idm->bitmap, idm->total, 0, count, 0);
- }
- if (id >= idm->total)
+ if (shareable_start < idm->usable)
+ id = idm_find_last_zero_area_in_range(idm->bitmap, shareable_start, total, count);
+ else
+ id = bitmap_find_next_zero_area(idm->bitmap, total, shareable_start, count, 0);
+
+ if (id >= total)
return -ENOSPC;
+ /*
+ * Spare is meaningful only for IDs that are shared between usable and
+ * shareable pools. Only IDs taken from the overlapping part reduce PF
+ * usable capacity, so a chunk located entirely in the VF-exclusive part
+ * (or any chunk from non-overlapping pools) is never limited by the spare.
+ */
+ if (spare && id < idm->usable) {
+ unsigned int need_overlap = min(id + count, idm->usable) - id;
+ int err;
+
+ err = idm_check_quota(idm_used_usable(idm), need_overlap, spare, idm->usable);
+ if (err)
+ return err;
+ }
+
bitmap_set(idm->bitmap, id, count);
- idm->used += count;
return id;
}
@@ -160,9 +292,8 @@ static void idm_release_chunk_locked(struct xe_guc_id_mgr *idm,
unsigned int start, unsigned int count)
{
idm_assert(idm, count);
- idm_assert(idm, count <= idm->used);
- idm_assert(idm, start < idm->total);
- idm_assert(idm, start + count - 1 < idm->total);
+ idm_assert(idm, start < idm_total(idm));
+ idm_assert(idm, start + count - 1 < idm_total(idm));
lockdep_assert_held(idm_mutex(idm));
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
@@ -172,22 +303,61 @@ static void idm_release_chunk_locked(struct xe_guc_id_mgr *idm,
idm_assert(idm, test_bit(start + n, idm->bitmap));
}
bitmap_clear(idm->bitmap, start, count);
- idm->used -= count;
+}
+
+static int idm_release_chunk_in_range_locked(struct xe_guc_id_mgr *idm,
+ unsigned int start,
+ unsigned int count,
+ unsigned int range_start,
+ unsigned int range_end)
+{
+ lockdep_assert_held(idm_mutex(idm));
+
+ if (!count)
+ return -EINVAL;
+
+ if (start < range_start || start >= range_end)
+ return -ERANGE;
+
+ if (count > range_end - start)
+ return -ERANGE;
+
+ idm_release_chunk_locked(idm, start, count);
+
+ return 0;
}
/**
- * xe_guc_id_mgr_reserve_locked() - Reserve one or more GuC context IDs.
+ * xe_guc_id_mgr_reserve_usable_locked() - Reserve one or more usable GuC context IDs.
* @idm: the &xe_guc_id_mgr
- * @count: number of IDs to allocate (can't be 0)
+ * @count: number of usable GuC context IDs to reserve (can't be 0)
*
- * This function is dedicated for the use by the GuC submission code,
- * where submission lock is already taken.
+ * This function is dedicated for use by the GuC submission code when the
+ * submission lock is already held.
*
* Return: ID of allocated GuC context or a negative error code on failure.
*/
-int xe_guc_id_mgr_reserve_locked(struct xe_guc_id_mgr *idm, unsigned int count)
+int xe_guc_id_mgr_reserve_usable_locked(struct xe_guc_id_mgr *idm, unsigned int count)
{
- return idm_reserve_chunk_locked(idm, count, 0);
+ return idm_reserve_chunk_usable_locked(idm, count);
+}
+
+/**
+ * xe_guc_id_mgr_reserve_shareable_locked() - Reserve one or more shareable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr
+ * @count: number of shareable GuC context IDs to reserve (can't be 0)
+ * @spare: number of usable IDs to keep available for the PF
+ *
+ * This function is dedicated for use by the PF driver, which expects the
+ * reserved range of IDs to be contiguous. The function expects that the
+ * caller has already taken the lock.
+ *
+ * Return: ID of allocated GuC context or a negative error code on failure.
+ */
+int xe_guc_id_mgr_reserve_shareable_locked(struct xe_guc_id_mgr *idm, unsigned int count,
+ unsigned int spare)
+{
+ return idm_reserve_chunk_shareable_locked(idm, count, spare);
}
/**
@@ -206,60 +376,156 @@ void xe_guc_id_mgr_release_locked(struct xe_guc_id_mgr *idm, unsigned int id,
}
/**
- * xe_guc_id_mgr_reserve() - Reserve a range of GuC context IDs.
+ * xe_guc_id_mgr_release_usable_locked() - Release one or more usable GuC context IDs.
* @idm: the &xe_guc_id_mgr
- * @count: number of GuC context IDs to reserve (can't be 0)
- * @retain: number of GuC context IDs to keep available (can't be 0)
+ * @id: the first usable GuC context ID to release
+ * @count: number of usable GuC context IDs to release
+ *
+ * This function is dedicated for use by the GuC submission code when the
+ * submission lock is already held.
*
- * This function is dedicated for the use by the PF driver which expects that
- * reserved range of IDs will be contiguous and that there will be at least
- * &retain IDs still available for the PF after this reservation.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_release_usable_locked(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count)
+{
+ return idm_release_chunk_in_range_locked(idm, id, count, 0, idm->usable);
+}
+
+/**
+ * xe_guc_id_mgr_release_shareable_locked() - Release one or more shareable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr
+ * @id: the first shareable GuC context ID to release
+ * @count: number of shareable GuC context IDs to release
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_release_shareable_locked(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count)
+{
+ return idm_release_chunk_in_range_locked(idm, id, count,
+ idm_shareable_start(idm), idm_total(idm));
+}
+
+/**
+ * xe_guc_id_mgr_reserve_usable() - Reserve one or more usable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr
+ * @count: number of usable GuC context IDs to reserve (can't be 0)
+ *
+ * Return: ID of allocated GuC context or a negative error code on failure.
+ */
+int xe_guc_id_mgr_reserve_usable(struct xe_guc_id_mgr *idm, unsigned int count)
+{
+ guard(mutex)(idm_mutex(idm));
+
+ idm_assert(idm, count);
+
+ return idm_reserve_chunk_usable_locked(idm, count);
+}
+
+/**
+ * xe_guc_id_mgr_reserve_shareable() - Reserve one or more shareable GuC context IDs for VFs.
+ * @idm: the &xe_guc_id_mgr
+ * @count: number of shareable GuC context IDs to reserve (can't be 0)
+ * @spare: number of usable IDs to keep available for the PF
+ *
+ * This function is dedicated for use by the PF driver, which expects the
+ * reserved range of IDs to be contiguous.
*
* Return: starting ID of the allocated GuC context ID range or
* a negative error code on failure.
*/
-int xe_guc_id_mgr_reserve(struct xe_guc_id_mgr *idm,
- unsigned int count, unsigned int retain)
+int xe_guc_id_mgr_reserve_shareable(struct xe_guc_id_mgr *idm, unsigned int count,
+ unsigned int spare)
{
- int ret;
+ guard(mutex)(idm_mutex(idm));
idm_assert(idm, count);
- idm_assert(idm, retain);
-
- mutex_lock(idm_mutex(idm));
- ret = idm_reserve_chunk_locked(idm, count, retain);
- mutex_unlock(idm_mutex(idm));
- return ret;
+ return idm_reserve_chunk_shareable_locked(idm, count, spare);
}
/**
- * xe_guc_id_mgr_release() - Release a range of GuC context IDs.
+ * xe_guc_id_mgr_release() - Release one or more GuC context IDs.
* @idm: the &xe_guc_id_mgr
- * @start: the starting ID of GuC context range to release
+ * @start: the first GuC context ID to release
* @count: number of GuC context IDs to release
*/
void xe_guc_id_mgr_release(struct xe_guc_id_mgr *idm,
unsigned int start, unsigned int count)
{
- mutex_lock(idm_mutex(idm));
+ guard(mutex)(idm_mutex(idm));
+
idm_release_chunk_locked(idm, start, count);
- mutex_unlock(idm_mutex(idm));
}
-static void idm_print_locked(struct xe_guc_id_mgr *idm, struct drm_printer *p, int indent)
+/**
+ * xe_guc_id_mgr_release_usable() - Release one or more usable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr
+ * @id: the first usable GuC context ID to release
+ * @count: number of usable GuC context IDs to release
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_release_usable(struct xe_guc_id_mgr *idm, unsigned int id, unsigned int count)
+{
+ guard(mutex)(idm_mutex(idm));
+
+ return xe_guc_id_mgr_release_usable_locked(idm, id, count);
+}
+
+/**
+ * xe_guc_id_mgr_release_shareable() - Release one or more shareable GuC context IDs.
+ * @idm: the &xe_guc_id_mgr
+ * @id: the first shareable GuC context ID to release
+ * @count: number of shareable GuC context IDs to release
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int xe_guc_id_mgr_release_shareable(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count)
+{
+ guard(mutex)(idm_mutex(idm));
+
+ return xe_guc_id_mgr_release_shareable_locked(idm, id, count);
+}
+
+static void idm_print_range_locked(struct xe_guc_id_mgr *idm,
+ struct drm_printer *p,
+ int indent,
+ const char *name,
+ unsigned int range_start,
+ unsigned int range_end,
+ unsigned int used)
{
unsigned int rs, re;
lockdep_assert_held(idm_mutex(idm));
- drm_printf_indent(p, indent, "total %u\n", idm->total);
+ drm_printf_indent(p, indent, "%s used %u\n", name, used);
+ for_each_set_bitrange(rs, re, idm->bitmap, range_end) {
+ rs = max(rs, range_start);
+ re = min(re, range_end);
+
+ if (rs < re)
+ drm_printf_indent(p, indent, "%s range %u..%u (%u)\n",
+ name, rs, re - 1, re - rs);
+ }
+}
+
+static void idm_print_locked(struct xe_guc_id_mgr *idm, struct drm_printer *p, int indent)
+{
+ lockdep_assert_held(idm_mutex(idm));
+
+ drm_printf_indent(p, indent, "total %u\n", idm_total(idm));
if (!idm->bitmap)
return;
- drm_printf_indent(p, indent, "used %u\n", idm->used);
- for_each_set_bitrange(rs, re, idm->bitmap, idm->total)
- drm_printf_indent(p, indent, "range %u..%u (%u)\n", rs, re - 1, re - rs);
+ idm_print_range_locked(idm, p, indent, "usable", 0, idm->usable,
+ idm_used_usable(idm));
+ idm_print_range_locked(idm, p, indent, "shareable",
+ idm_shareable_start(idm), idm_total(idm),
+ idm_used_shareable(idm));
}
/**
@@ -270,9 +536,8 @@ static void idm_print_locked(struct xe_guc_id_mgr *idm, struct drm_printer *p, i
*/
void xe_guc_id_mgr_print(struct xe_guc_id_mgr *idm, struct drm_printer *p, int indent)
{
- mutex_lock(idm_mutex(idm));
+ guard(mutex)(idm_mutex(idm));
idm_print_locked(idm, p, indent);
- mutex_unlock(idm_mutex(idm));
}
#if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST)
diff --git a/drivers/gpu/drm/xe/xe_guc_id_mgr.h b/drivers/gpu/drm/xe/xe_guc_id_mgr.h
index 368f8c80e4c7..844d68440b0a 100644
--- a/drivers/gpu/drm/xe/xe_guc_id_mgr.h
+++ b/drivers/gpu/drm/xe/xe_guc_id_mgr.h
@@ -6,16 +6,41 @@
#ifndef _XE_GUC_ID_MGR_H_
#define _XE_GUC_ID_MGR_H_
+#include <linux/errno.h>
+
struct drm_printer;
struct xe_guc_id_mgr;
-int xe_guc_id_mgr_init(struct xe_guc_id_mgr *idm, unsigned int count);
+int xe_guc_id_mgr_init(struct xe_guc_id_mgr *idm);
+int xe_guc_id_mgr_init_small(struct xe_guc_id_mgr *idm, unsigned int count);
+#ifdef CONFIG_PCI_IOV
+int xe_guc_id_mgr_init_shared(struct xe_guc_id_mgr *idm);
+#else
+static inline int xe_guc_id_mgr_init_shared(struct xe_guc_id_mgr *idm)
+{
+ return -ENODEV;
+}
+#endif
+
+unsigned int xe_guc_id_mgr_max_usable(struct xe_guc_id_mgr *idm);
+unsigned int xe_guc_id_mgr_max_shareable(struct xe_guc_id_mgr *idm);
-int xe_guc_id_mgr_reserve_locked(struct xe_guc_id_mgr *idm, unsigned int count);
+int xe_guc_id_mgr_reserve_usable_locked(struct xe_guc_id_mgr *idm, unsigned int count);
+int xe_guc_id_mgr_reserve_usable(struct xe_guc_id_mgr *idm, unsigned int count);
+int xe_guc_id_mgr_reserve_shareable_locked(struct xe_guc_id_mgr *idm, unsigned int count,
+ unsigned int spare);
+int xe_guc_id_mgr_reserve_shareable(struct xe_guc_id_mgr *idm, unsigned int count,
+ unsigned int spare);
void xe_guc_id_mgr_release_locked(struct xe_guc_id_mgr *idm, unsigned int id, unsigned int count);
+int xe_guc_id_mgr_release_usable_locked(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count);
+int xe_guc_id_mgr_release_shareable_locked(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count);
-int xe_guc_id_mgr_reserve(struct xe_guc_id_mgr *idm, unsigned int count, unsigned int retain);
void xe_guc_id_mgr_release(struct xe_guc_id_mgr *idm, unsigned int start, unsigned int count);
+int xe_guc_id_mgr_release_usable(struct xe_guc_id_mgr *idm, unsigned int id, unsigned int count);
+int xe_guc_id_mgr_release_shareable(struct xe_guc_id_mgr *idm, unsigned int id,
+ unsigned int count);
void xe_guc_id_mgr_print(struct xe_guc_id_mgr *idm, struct drm_printer *p, int indent);
diff --git a/drivers/gpu/drm/xe/xe_guc_log.c b/drivers/gpu/drm/xe/xe_guc_log.c
index 7d006268ce91..90aeaccea596 100644
--- a/drivers/gpu/drm/xe/xe_guc_log.c
+++ b/drivers/gpu/drm/xe/xe_guc_log.c
@@ -6,6 +6,9 @@
#include "xe_guc_log.h"
#include <linux/fault-inject.h>
+#if IS_ENABLED(CONFIG_DRM_XE_COMPRESS_DUMP)
+#include <linux/zstd.h>
+#endif
#include <linux/utsname.h>
#include <drm/drm_managed.h>
@@ -175,6 +178,7 @@ void xe_guc_log_snapshot_free(struct xe_guc_log_snapshot *snapshot)
kfree(snapshot->copy);
}
+ xe_guc_dump_cache_fini(&snapshot->dump_cache);
kfree(snapshot);
}
@@ -226,16 +230,207 @@ struct xe_guc_log_snapshot *xe_guc_log_snapshot_capture(struct xe_guc_log *log,
return snapshot;
}
+#if IS_ENABLED(CONFIG_DRM_XE_COMPRESS_DUMP)
+static void xe_guc_compress_blob(struct xe_gt *gt, struct xe_guc_dump_cache *cache,
+ void **chunks, int num_chunks,
+ size_t chunk_size, size_t total_size)
+{
+ zstd_parameters params;
+ zstd_cstream *cstream;
+ zstd_out_buffer outbuf;
+ size_t wksp_size, dst_size;
+ size_t remain, ret;
+ void *wksp = NULL;
+ char *dst = NULL;
+ int i;
+
+ /* One shot only: every later print replays whatever this pass produced. */
+ cache->attempted = true;
+
+ /*
+ * Pass 0 as estimated_src_size to use zstd level-3 defaults
+ * (windowLog=18, 256KB window). Passing the full total_size would
+ * cause zstd to inflate windowLog proportionally (e.g. windowLog=24
+ * for a 19MB log), making the workspace tens of MB. With 0, the
+ * workspace stays ~1.5MB, well within kmalloc limits.
+ */
+ params = zstd_get_params(3, 0);
+
+ wksp_size = zstd_cstream_workspace_bound(&params.cParams);
+ wksp = kvzalloc(wksp_size, GFP_KERNEL);
+ if (!wksp)
+ goto err;
+
+ dst_size = zstd_compress_bound(total_size);
+ /*
+ * zstd may only write dst_size bytes, but xe_print_blob_ascii85() reads
+ * the blob a u32 at a time and so touches up to three bytes past the
+ * last compressed byte. Round-up allocation to a u32 boundary rather than
+ * rely on dst_size happening to be aligned. kvzalloc() leaves that
+ * padding zeroed, which also keeps the encoded tail reproducible.
+ */
+ dst = kvzalloc(round_up(dst_size, sizeof(u32)), GFP_KERNEL);
+ if (!dst)
+ goto err;
+
+ cstream = zstd_init_cstream(&params, 0, wksp, wksp_size);
+ if (!cstream) {
+ xe_gt_err(gt, "failed to init zstd cstream\n");
+ goto err;
+ }
+
+ outbuf.dst = dst;
+ outbuf.size = dst_size;
+ outbuf.pos = 0;
+
+ remain = total_size;
+ for (i = 0; i < num_chunks && remain; i++) {
+ size_t size = min(chunk_size, remain);
+ zstd_in_buffer inbuf = { .src = chunks[i], .size = size, .pos = 0 };
+
+ while (inbuf.pos < inbuf.size) {
+ /*
+ * zstd only guarantees forward progress while the output
+ * buffer has room. zstd_compress_bound() sizes dst so that
+ * a single frame always fits, but never risk spinning
+ * forever in an error-dump path.
+ */
+ if (outbuf.pos == outbuf.size) {
+ xe_gt_err(gt, "zstd output buffer exhausted\n");
+ goto err;
+ }
+
+ ret = zstd_compress_stream(cstream, &outbuf, &inbuf);
+ if (ZSTD_isError(ret)) {
+ xe_gt_err(gt, "zstd_compress_stream failed: %s\n",
+ ZSTD_getErrorName(ret));
+ goto err;
+ }
+ }
+ remain -= size;
+ }
+
+ /*
+ * zstd_end_stream() must be called until it returns 0, otherwise the
+ * frame epilogue is missing and no decompressor will accept the result.
+ * Running out of output before that happens is a hard error, not a
+ * reason to stop.
+ */
+ do {
+ if (outbuf.pos == outbuf.size) {
+ xe_gt_err(gt, "zstd output buffer exhausted\n");
+ goto err;
+ }
+
+ ret = zstd_end_stream(cstream, &outbuf);
+ if (ZSTD_isError(ret)) {
+ xe_gt_err(gt, "zstd_end_stream failed: %s\n",
+ ZSTD_getErrorName(ret));
+ goto err;
+ }
+ } while (ret);
+
+ cache->data = dst;
+ cache->size = outbuf.pos;
+ kvfree(wksp);
+ return;
+
+err:
+ kvfree(dst);
+ kvfree(wksp);
+}
+#endif
+
/**
- * xe_guc_log_snapshot_print - dump a previously saved copy of the GuC log to some useful location
- * @snapshot: a snapshot of the GuC log
- * @p: the printer object to output to
+ * xe_guc_dump_cache_fini - release a dump blob's compression cache
+ * @cache: cache embedded in the snapshot being freed
*/
-void xe_guc_log_snapshot_print(struct xe_guc_log_snapshot *snapshot, struct drm_printer *p)
+void xe_guc_dump_cache_fini(struct xe_guc_dump_cache *cache)
+{
+ kvfree(cache->data);
+ cache->data = NULL;
+ cache->size = 0;
+}
+
+/**
+ * xe_guc_print_blob - Print binary data (compressed or uncompressed) as ascii85
+ * @gt: GT structure for error reporting
+ * @p: drm_printer for output
+ * @length_label: label prefix for the length/metadata line (e.g. "[LOG]" or "[CTB]")
+ * @data_label: label for the ascii85 blob (e.g. "[LOG].data" or "[CTB].data")
+ * @chunks: array of pointers to source data chunks
+ * @num_chunks: number of elements in @chunks
+ * @chunk_size: nominal size of each chunk; the last chunk may hold fewer valid bytes
+ * @total_size: total uncompressed byte count across all chunks
+ * @cache: per-snapshot compression cache, zeroed at capture time
+ *
+ * If CONFIG_DRM_XE_COMPRESS_DUMP is enabled, compresses @total_size bytes using
+ * zstd level-3 streaming compression and prints the result via @p using ascii85
+ * encoding. Otherwise, directly prints the uncompressed data.
+ *
+ * The compressed blob is produced once and kept in @cache, because a snapshot
+ * is printed repeatedly: __xe_devcoredump_read() measures the output before
+ * emitting it, and re-emits it a chunk at a time. Compressing per pass would
+ * let the measured size and the emitted bytes disagree.
+ *
+ * If compression fails the raw data is printed instead, so the dump is never
+ * lost. @cache->attempted latches that decision, so every later pass makes the same
+ * choice and produces byte-identical output.
+ */
+void xe_guc_print_blob(struct xe_gt *gt __maybe_unused, struct drm_printer *p,
+ const char *length_label, const char *data_label,
+ void **chunks, int num_chunks,
+ size_t chunk_size, size_t total_size,
+ struct xe_guc_dump_cache *cache __maybe_unused)
{
size_t remain;
int i;
+#if IS_ENABLED(CONFIG_DRM_XE_COMPRESS_DUMP)
+ if (!cache->attempted)
+ xe_guc_compress_blob(gt, cache, chunks, num_chunks,
+ chunk_size, total_size);
+
+ if (cache->data) {
+ drm_printf(p, "%s.length: 0x%zx -> 0x%zx Algo: ZSTD\n",
+ length_label, total_size, cache->size);
+ xe_print_blob_ascii85(p, data_label, '\n', cache->data, 0,
+ round_up(cache->size, sizeof(u32)));
+ return;
+ }
+
+ /* Compression failed - fallback to emit the blob uncompressed. */
+#endif
+
+ drm_printf(p, "%s.length: 0x%zx Algo: RAW\n", length_label, total_size);
+
+ /*
+ * Print each chunk directly without merging into a single buffer.
+ * xe_print_blob_ascii85() supports multiple calls - use suffix=0
+ * for all but the last chunk to omit the newline between chunks.
+ */
+ remain = total_size;
+ for (i = 0; i < num_chunks && remain; i++) {
+ size_t size = min(chunk_size, remain);
+ const char *prefix = i ? NULL : data_label;
+ /* Add suffix only on the last chunk */
+ char suffix = (remain == size) ? '\n' : 0;
+
+ xe_print_blob_ascii85(p, prefix, suffix, chunks[i], 0,
+ DIV_ROUND_UP(size, sizeof(u32)) * sizeof(u32));
+ remain -= size;
+ }
+}
+
+/**
+ * xe_guc_log_snapshot_print - dump a previously saved copy of the GuC log to some useful location
+ * @snapshot: a snapshot of the GuC log
+ * @p: the printer object to output to
+ * @gt: GT structure of the GuC log
+ */
+void xe_guc_log_snapshot_print(struct xe_guc_log_snapshot *snapshot, struct drm_printer *p,
+ struct xe_gt *gt)
+{
if (!snapshot) {
drm_printf(p, "GuC log snapshot not allocated!\n");
return;
@@ -249,16 +444,10 @@ void xe_guc_log_snapshot_print(struct xe_guc_log_snapshot *snapshot, struct drm_
drm_printf(p, "GuC timestamp: 0x%08llX [%llu]\n", snapshot->stamp, snapshot->stamp);
drm_printf(p, "Log level: %u\n", snapshot->level);
- drm_printf(p, "[LOG].length: 0x%zx\n", snapshot->size);
- remain = snapshot->size;
- for (i = 0; i < snapshot->num_chunks; i++) {
- size_t size = min(GUC_LOG_CHUNK_SIZE, remain);
- const char *prefix = i ? NULL : "[LOG].data";
- char suffix = i == snapshot->num_chunks - 1 ? '\n' : 0;
-
- xe_print_blob_ascii85(p, prefix, suffix, snapshot->copy[i], 0, size);
- remain -= size;
- }
+ xe_guc_print_blob(gt, p, "[LOG]", "[LOG].data",
+ snapshot->copy, snapshot->num_chunks,
+ GUC_LOG_CHUNK_SIZE, snapshot->size,
+ &snapshot->dump_cache);
}
static inline void lfd_output_binary(struct drm_printer *p, char *buf, int buf_size)
@@ -599,12 +788,13 @@ void xe_guc_log_print_dmesg(struct xe_guc_log *log)
void xe_guc_log_print(struct xe_guc_log *log, struct drm_printer *p)
{
struct xe_guc_log_snapshot *snapshot;
+ struct xe_gt *gt = log_to_gt(log);
drm_printf(p, "**** GuC Log ****\n");
snapshot = xe_guc_log_snapshot_capture(log, false);
- drm_printf(p, "CS reference clock: %u\n", log_to_gt(log)->info.reference_clock);
- xe_guc_log_snapshot_print(snapshot, p);
+ drm_printf(p, "CS reference clock: %u\n", gt->info.reference_clock);
+ xe_guc_log_snapshot_print(snapshot, p, gt);
xe_guc_log_snapshot_free(snapshot);
}
diff --git a/drivers/gpu/drm/xe/xe_guc_log.h b/drivers/gpu/drm/xe/xe_guc_log.h
index 4649a260755e..bb6415271e5f 100644
--- a/drivers/gpu/drm/xe/xe_guc_log.h
+++ b/drivers/gpu/drm/xe/xe_guc_log.h
@@ -11,6 +11,7 @@
struct drm_printer;
struct xe_device;
+struct xe_gt;
#if IS_ENABLED(CONFIG_DRM_XE_DEBUG_GUC)
#define XE_GUC_LOG_EVENT_DATA_BUFFER_SIZE SZ_16M
@@ -59,7 +60,15 @@ void xe_guc_log_print(struct xe_guc_log *log, struct drm_printer *p);
void xe_guc_log_print_lfd(struct xe_guc_log *log, struct drm_printer *p);
void xe_guc_log_print_dmesg(struct xe_guc_log *log);
struct xe_guc_log_snapshot *xe_guc_log_snapshot_capture(struct xe_guc_log *log, bool atomic);
-void xe_guc_log_snapshot_print(struct xe_guc_log_snapshot *snapshot, struct drm_printer *p);
+
+void xe_guc_print_blob(struct xe_gt *gt, struct drm_printer *p, const char *length_label,
+ const char *data_label, void **chunks, int num_chunks,
+ size_t chunk_size, size_t total_size,
+ struct xe_guc_dump_cache *cache);
+void xe_guc_dump_cache_fini(struct xe_guc_dump_cache *cache);
+
+void xe_guc_log_snapshot_print(struct xe_guc_log_snapshot *snapshot, struct drm_printer *p,
+ struct xe_gt *gt);
void xe_guc_log_snapshot_free(struct xe_guc_log_snapshot *snapshot);
static inline u32
diff --git a/drivers/gpu/drm/xe/xe_guc_log_types.h b/drivers/gpu/drm/xe/xe_guc_log_types.h
index 02851b924aa4..a38e9619c95d 100644
--- a/drivers/gpu/drm/xe/xe_guc_log_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_log_types.h
@@ -14,6 +14,24 @@
struct xe_bo;
/**
+ * struct xe_guc_dump_cache - cached compressed copy of a dump blob
+ *
+ * A snapshot is printed more than once: __xe_devcoredump_read() walks it first
+ * with a NULL buffer to measure the output size, then again - once per chunk -
+ * to fill the user buffer. Compressing on every pass would be both wasteful and
+ * unsafe, as any difference between the measured size and the emitted bytes
+ * corrupts the dump. So compress at most once and replay the result.
+ */
+struct xe_guc_dump_cache {
+ /** @data: compressed bytes, allocation padded up to a u32 boundary */
+ char *data;
+ /** @size: number of valid compressed bytes in @data */
+ size_t size;
+ /** @attempted: compression has been attempted; @data is final either way */
+ bool attempted;
+};
+
+/**
* struct xe_guc_log_snapshot:
* Capture of the GuC log plus various state useful for decoding the log
*/
@@ -36,6 +54,8 @@ struct xe_guc_log_snapshot {
struct xe_uc_fw_version ver_want;
/** @path: Path of GuC firmware blob */
const char *path;
+ /** @dump_cache: compressed copy of @copy, built on first print */
+ struct xe_guc_dump_cache dump_cache;
};
/**
diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
index 0b231228bde8..bc39cea08ecc 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.c
+++ b/drivers/gpu/drm/xe/xe_guc_submit.c
@@ -286,20 +286,26 @@ static void primelockdep(struct xe_guc *guc)
fs_reclaim_release(GFP_KERNEL);
}
+static int guc_id_mgr_init(struct xe_guc *guc)
+{
+ if (IS_SRIOV_PF(guc_to_xe(guc)))
+ return xe_guc_id_mgr_init_shared(&guc->submission_state.idm);
+ else if (IS_SRIOV_VF(guc_to_xe(guc)))
+ return xe_guc_id_mgr_init_small(&guc->submission_state.idm,
+ xe_gt_sriov_vf_guc_ids(guc_to_gt(guc)));
+ else
+ return xe_guc_id_mgr_init(&guc->submission_state.idm);
+}
+
/**
* xe_guc_submit_init() - Initialize GuC submission.
* @guc: the &xe_guc to initialize
- * @num_ids: number of GuC context IDs to use
- *
- * The bare-metal or PF driver can pass ~0 as &num_ids to indicate that all
- * GuC context IDs supported by the GuC firmware should be used for submission.
*
- * Only VF drivers will have to provide explicit number of GuC context IDs
- * that they can use for submission.
+ * This function initializes the GuC submission state.
*
* Return: 0 on success or a negative error code on failure.
*/
-int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids)
+int xe_guc_submit_init(struct xe_guc *guc)
{
struct xe_device *xe = guc_to_xe(guc);
struct xe_gt *gt = guc_to_gt(guc);
@@ -309,7 +315,7 @@ int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids)
if (err)
return err;
- err = xe_guc_id_mgr_init(&guc->submission_state.idm, num_ids);
+ err = guc_id_mgr_init(guc);
if (err)
return err;
@@ -415,8 +421,7 @@ static int alloc_guc_id(struct xe_guc *guc, struct xe_exec_queue *q)
int ret, i;
mutex_lock(&guc->submission_state.lock);
- ret = xe_guc_id_mgr_reserve_locked(&guc->submission_state.idm,
- q->width);
+ ret = xe_guc_id_mgr_reserve_usable_locked(&guc->submission_state.idm, q->width);
mutex_unlock(&guc->submission_state.lock);
if (ret < 0)
return ret;
@@ -1599,6 +1604,7 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job)
int err = -ETIME;
pid_t pid = -1;
bool wedged = false, wedge_device = false, skip_timeout_check;
+ int idx;
xe_gt_assert(guc_to_gt(guc), !exec_queue_destroyed(q));
@@ -1614,6 +1620,11 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job)
vf_recovery(guc))
return DRM_GPU_SCHED_STAT_NO_HANG;
+ if (!drm_dev_enter(&xe->drm, &idx)) {
+ xe_sched_job_set_error(job, -ENODEV);
+ return DRM_GPU_SCHED_STAT_NO_HANG;
+ }
+
/* Kill the run_job entry point */
if (xe_exec_queue_is_multi_queue(q))
xe_guc_exec_queue_group_stop(q);
@@ -1786,6 +1797,7 @@ trigger_reset:
* We want the job added back to the pending list so it gets freed; this
* is what DRM_GPU_SCHED_STAT_NO_HANG does.
*/
+ drm_dev_exit(idx);
return DRM_GPU_SCHED_STAT_NO_HANG;
rearm:
@@ -1799,6 +1811,7 @@ rearm:
else
xe_sched_submission_start(sched);
handle_vf_resume:
+ drm_dev_exit(idx);
return DRM_GPU_SCHED_STAT_NO_HANG;
}
@@ -1935,16 +1948,11 @@ static void __guc_exec_queue_process_msg_set_sched_props(struct xe_sched_msg *ms
kfree(msg);
}
-static void __suspend_fence_signal(struct xe_exec_queue *q)
+static void suspend_wait_wake(struct xe_exec_queue *q)
{
struct xe_guc *guc = exec_queue_to_guc(q);
struct xe_device *xe = guc_to_xe(guc);
- if (!q->guc->suspend_pending)
- return;
-
- WRITE_ONCE(q->guc->suspend_pending, false);
-
/*
* We use a GuC shared wait queue for VFs because the VF resfix start
* interrupt must be able to wake all instances of suspend_wait. This
@@ -1957,6 +1965,15 @@ static void __suspend_fence_signal(struct xe_exec_queue *q)
wake_up(&q->guc->suspend_wait);
}
+static void __suspend_fence_signal(struct xe_exec_queue *q)
+{
+ if (!q->guc->suspend_pending)
+ return;
+
+ WRITE_ONCE(q->guc->suspend_pending, false);
+ suspend_wait_wake(q);
+}
+
static void suspend_fence_signal(struct xe_exec_queue *q)
{
struct xe_guc *guc = exec_queue_to_guc(q);
@@ -2210,7 +2227,13 @@ static void guc_exec_queue_kill(struct xe_exec_queue *q)
{
trace_xe_exec_queue_kill(q);
set_exec_queue_killed(q);
- __suspend_fence_signal(q);
+ /*
+ * Do not signal the suspend fence here, the queue may still be running
+ * on the hardware. Only wake any suspend waiter so it observes the
+ * killed state instead of waiting out the full timeout, suspend_pending
+ * stays set until the queue is torn down.
+ */
+ suspend_wait_wake(q);
xe_guc_exec_queue_trigger_cleanup(q);
}
@@ -2399,7 +2422,12 @@ static bool __guc_exec_queue_resume(struct xe_exec_queue *q)
bool last;
xe_sched_msg_lock(sched);
- xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending);
+ /*
+ * A killed, banned or wedged queue keeps suspend_pending set until it
+ * is torn down, so only assert the flag is clear otherwise.
+ */
+ xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending ||
+ exec_queue_killed_or_banned_or_wedged(q));
xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0);
last = (--ge->suspend_count == 0);
if (last) {
@@ -2469,8 +2497,10 @@ static void guc_exec_queue_suspend_timeout_ban(struct xe_exec_queue *q)
/*
* 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.
+ * cleanup rather than leave it suspended forever. The suspend fence is
+ * not signalled here, the queue may still be running on the hardware,
+ * so suspend_pending stays set until teardown. The waiter that timed
+ * out is the caller, so no wake up is needed either.
*
* @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
@@ -2478,11 +2508,9 @@ static void guc_exec_queue_suspend_timeout_ban(struct xe_exec_queue *q)
*/
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);
}
}
@@ -2505,9 +2533,9 @@ static int guc_exec_queue_wait_suspend_done(struct xe_exec_queue *q, bool blocki
int ret;
/*
- * Likely don't need to check exec_queue_killed() as we clear
- * suspend_pending upon kill but to be paranoid but races in which
- * suspend_pending is set after kill also check kill here.
+ * A kill does not clear suspend_pending, the queue may still be running
+ * on the hardware, so exec_queue_killed() is what ends the wait in that
+ * case and the queue is torn down asynchronously.
*/
#define WAIT_COND \
(!READ_ONCE(q->guc->suspend_pending) || exec_queue_killed(q) || \
@@ -2716,10 +2744,12 @@ static void guc_exec_queue_stop(struct xe_guc *guc, struct xe_exec_queue *q)
if (exec_queue_destroyed(q))
do_destroy = true;
}
+ xe_sched_msg_lock(sched);
if (q->guc->suspend_pending) {
set_exec_queue_suspended(q);
suspend_fence_signal(q);
}
+ xe_sched_msg_unlock(sched);
atomic_and(EXEC_QUEUE_STATE_WEDGED | EXEC_QUEUE_STATE_BANNED |
EXEC_QUEUE_STATE_KILLED | EXEC_QUEUE_STATE_DESTROYED |
EXEC_QUEUE_STATE_SUSPENDED,
@@ -3281,7 +3311,7 @@ g2h_exec_queue_lookup(struct xe_guc *guc, u32 guc_id)
struct xe_gt *gt = guc_to_gt(guc);
struct xe_exec_queue *q;
- if (unlikely(guc_id >= GUC_ID_MAX)) {
+ if (unlikely(guc_id >= xe_guc_id_mgr_max_usable(&guc->submission_state.idm))) {
xe_gt_err(gt, "Invalid guc_id %u\n", guc_id);
return NULL;
}
@@ -3333,13 +3363,20 @@ static void handle_sched_done(struct xe_guc *guc, struct xe_exec_queue *q,
smp_wmb();
wake_up_all(&guc->ct.wq);
} else {
+ bool was_pending;
+
xe_gt_assert(guc_to_gt(guc), runnable_state == 0);
xe_gt_assert(guc_to_gt(guc), exec_queue_pending_disable(q));
- if (q->guc->suspend_pending) {
+ xe_sched_msg_lock(&q->guc->sched);
+ was_pending = q->guc->suspend_pending;
+ if (was_pending) {
clear_exec_queue_pending_disable(q);
suspend_fence_signal(q);
- } else {
+ }
+ xe_sched_msg_unlock(&q->guc->sched);
+
+ if (!was_pending) {
if (exec_queue_banned(q)) {
smp_wmb();
wake_up_all(&guc->ct.wq);
diff --git a/drivers/gpu/drm/xe/xe_guc_submit.h b/drivers/gpu/drm/xe/xe_guc_submit.h
index 29abf07d8f04..7a81d0aa19bb 100644
--- a/drivers/gpu/drm/xe/xe_guc_submit.h
+++ b/drivers/gpu/drm/xe/xe_guc_submit.h
@@ -13,7 +13,7 @@ struct xe_exec_queue;
struct xe_guc;
struct xe_hw_engine;
-int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids);
+int xe_guc_submit_init(struct xe_guc *guc);
int xe_guc_submit_enable(struct xe_guc *guc);
void xe_guc_submit_disable(struct xe_guc *guc);
diff --git a/drivers/gpu/drm/xe/xe_guc_types.h b/drivers/gpu/drm/xe/xe_guc_types.h
index 3dbcb1331690..6da43691e54c 100644
--- a/drivers/gpu/drm/xe/xe_guc_types.h
+++ b/drivers/gpu/drm/xe/xe_guc_types.h
@@ -42,10 +42,10 @@ struct xe_guc_db_mgr {
struct xe_guc_id_mgr {
/** @bitmap: bitmap to track allocated IDs */
unsigned long *bitmap;
- /** @total: total number of IDs being managed */
- unsigned int total;
- /** @used: number of IDs currently in use */
- unsigned int used;
+ /** @usable: number of IDs being managed for GuC submission */
+ unsigned int usable;
+ /** @shareable: number of IDs that can be shared with VFs */
+ unsigned int shareable;
};
/**
diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c
index 9680d2a5adec..55ee0c892c1e 100644
--- a/drivers/gpu/drm/xe/xe_hw_engine.c
+++ b/drivers/gpu/drm/xe/xe_hw_engine.c
@@ -275,11 +275,22 @@ static const struct engine_info engine_infos[] = {
static void hw_engine_fini(void *arg)
{
struct xe_hw_engine *hwe = arg;
+ struct xe_gt *gt = hwe->gt;
+
+ if (gt) {
+ struct xe_device *xe = gt_to_xe(gt);
+ unsigned long flags;
- if (hwe->exl_port)
- xe_execlist_port_destroy(hwe->exl_port);
+ if (hwe->exl_port)
+ xe_execlist_port_destroy(hwe->exl_port);
- hwe->gt = NULL;
+ spin_lock_irqsave(&xe->irq.lock, flags);
+ hwe->name = NULL;
+ hwe->gt = NULL;
+ spin_unlock_irqrestore(&xe->irq.lock, flags);
+ } else {
+ hwe->name = NULL;
+ }
}
/**
@@ -875,23 +886,42 @@ static u32 read_svccopy_fuses(struct xe_gt *gt)
xe_mmio_read32(&gt->mmio, SERVICE_COPY_ENABLE));
}
+static u32 read_rescopy_fuses(struct xe_gt *gt)
+{
+ u32 fuse = REG_FIELD_GET(FUSE_RESOURCE_COPY_ENABLE_MASK,
+ xe_mmio_read32(&gt->mmio, RESOURCE_COPY_ENABLE));
+
+ /*
+ * Although the register field is 16-bits wide, we don't expect to ever
+ * see more than one resource copy engine (BCS0 in bit 0) enabled.
+ */
+ xe_gt_WARN(gt, fuse & GENMASK(15, 1), "Unexpected resource copy fuse (%#x)\n", fuse);
+
+ return fuse & BIT(0);
+}
+
static void read_copy_fuses(struct xe_gt *gt)
{
struct xe_device *xe = gt_to_xe(gt);
- u32 bcs_mask;
+ u32 bcs_mask, svc_copy_mask, res_copy_mask;
xe_force_wake_assert_held(gt_to_fw(gt), XE_FW_GT);
- if (GRAPHICS_VER(xe) >= 35)
- bcs_mask = read_svccopy_fuses(gt);
- else if (GRAPHICS_VERx100(xe) == 1260)
- bcs_mask = infer_svccopy_from_meml3(gt);
- else
+ if (GRAPHICS_VER(xe) >= 35) {
+ svc_copy_mask = read_svccopy_fuses(gt);
+ res_copy_mask = read_rescopy_fuses(gt);
+ } else if (GRAPHICS_VERx100(xe) == 1260) {
+ svc_copy_mask = infer_svccopy_from_meml3(gt);
+ /* Only BCS1-BCS8 may be fused off */
+ res_copy_mask = 0x1;
+ } else {
return;
+ }
+
+ bcs_mask = svc_copy_mask << XE_HW_ENGINE_BCS1 |
+ res_copy_mask << XE_HW_ENGINE_BCS0;
- /* Only BCS1-BCS8 may be fused off */
- bcs_mask <<= XE_HW_ENGINE_BCS1;
- for (int i = XE_HW_ENGINE_BCS1; i <= XE_HW_ENGINE_BCS8; ++i) {
+ for (int i = XE_HW_ENGINE_BCS0; i <= XE_HW_ENGINE_BCS8; ++i) {
if (!(gt->info.engine_mask & BIT(i)))
continue;
diff --git a/drivers/gpu/drm/xe/xe_hw_error.c b/drivers/gpu/drm/xe/xe_hw_error.c
index 5f2abc9485ff..f53a6b3055de 100644
--- a/drivers/gpu/drm/xe/xe_hw_error.c
+++ b/drivers/gpu/drm/xe/xe_hw_error.c
@@ -240,7 +240,7 @@ static void log_soc_error(struct xe_tile *tile, const char * const *reg_info,
{
const char *severity_str = error_severity[severity];
struct xe_device *xe = tile_to_xe(tile);
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[severity];
const char *name;
@@ -260,7 +260,7 @@ static void gt_hw_error_handler(struct xe_tile *tile, const enum hardware_error
{
const enum drm_xe_ras_error_severity severity = hw_err_to_severity(hw_err);
struct xe_device *xe = tile_to_xe(tile);
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[severity];
struct xe_mmio *mmio = &tile->mmio;
unsigned long err_stat = 0;
@@ -422,7 +422,7 @@ static void hw_error_source_handler(struct xe_tile *tile, const enum hardware_er
const enum drm_xe_ras_error_severity severity = hw_err_to_severity(hw_err);
const char *severity_str = error_severity[severity];
struct xe_device *xe = tile_to_xe(tile);
- struct xe_drm_ras *ras = &xe->ras;
+ struct xe_drm_ras *ras = &xe->ras.nl_data;
struct xe_drm_ras_counter *info = ras->info[severity];
unsigned long flags, err_src;
u32 err_bit;
diff --git a/drivers/gpu/drm/xe/xe_hwmon.c b/drivers/gpu/drm/xe/xe_hwmon.c
index 5edeac961ec3..42acfeb8818c 100644
--- a/drivers/gpu/drm/xe/xe_hwmon.c
+++ b/drivers/gpu/drm/xe/xe_hwmon.c
@@ -15,6 +15,7 @@
#include "regs/xe_pcode_regs.h"
#include "xe_device.h"
#include "xe_hwmon.h"
+#include "xe_log.h"
#include "xe_mmio.h"
#include "xe_pcode.h"
#include "xe_pcode_api.h"
@@ -39,7 +40,11 @@ enum xe_hwmon_reg_operation {
REG_READ64,
};
-#define MAX_VRAM_CHANNELS (16)
+#define MAX_VRAM_CHANNELS 80
+#define FIXED_VRAM_CHANNELS 16
+
+/* Each MSU enable bit maps to one VRAM subsystem of 4 channels. */
+#define VRAM_CHANNELS_PER_MSU 4
enum xe_hwmon_channel {
CHANNEL_CARD,
@@ -48,6 +53,7 @@ enum xe_hwmon_channel {
CHANNEL_MCTRL,
CHANNEL_PCIE,
CHANNEL_VRAM_N,
+ /* Compile-time upper bound; actual channel count is hwmon->temp.vram_count */
CHANNEL_VRAM_N_MAX = CHANNEL_VRAM_N + MAX_VRAM_CHANNELS - 1,
CHANNEL_MAX,
};
@@ -101,8 +107,22 @@ enum sensor_attr_power {
#define PWR_ATTR_TO_STR(attr) (((attr) == hwmon_power_max) ? "PL1" : "PL2")
-/* Index of memory controller in READ_THERMAL_DATA output */
+/* Sensor byte indices in READ_THERMAL_DATA output for BMG. */
#define TEMP_INDEX_MCTRL 2
+#define TEMP_INDEX_PCIE 10
+#define BMG_G21_TEMP_INDEX_PCIE 8
+
+/* Sensor byte indices in READ_THERMAL_DATA output for CRI. */
+#define CRI_TEMP_INDEX_MCTRL 16
+#define CRI_TEMP_MCTRL_COUNT 4
+#define CRI_TEMP_INDEX_PCIE 20
+
+/* Mailbox sentinel reported for an unavailable temperature sensor */
+#define TEMP_MBX_INVALID 0xFF
+
+/* Mailbox temperature is sign-magnitude: bit 7 sign, bits 6:0 magnitude. */
+#define TEMP_MBX_SIGN BIT(7)
+#define TEMP_MBX_MAG GENMASK(6, 0)
/* Maximum characters in hwmon label name */
#define MAX_LABEL_SIZE 16
@@ -134,15 +154,25 @@ struct xe_hwmon_thermal_info {
union {
/** @limit: temperatures limits */
u8 limit[TEMP_LIMIT_MAX];
- /** @data: temperature limits in dwords */
- u32 data[DIV_ROUND_UP(TEMP_LIMIT_MAX, sizeof(u32))];
+ /** @data: temperature limits as little-endian dwords */
+ __le32 data[DIV_ROUND_UP(TEMP_LIMIT_MAX, sizeof(u32))];
};
- /** @count: no of temperature sensors available for the platform */
+ /** @count: temperature sensors count from READ_THERMAL_CONFIG */
u8 count;
- /** @value: signed value from each sensor */
- s8 value[U8_MAX];
- /** @vram_label: vram label names */
- char vram_label[MAX_VRAM_CHANNELS][MAX_LABEL_SIZE];
+ /** @vram_count: exclusive upper bound for VRAM temperature channel indices */
+ u8 vram_count;
+ /** @available: temperature sensor availability cached at registration */
+ bool available[CHANNEL_MAX];
+ /** @msu_mask: VRAM subsystem enable mask read from MMIO */
+ u32 msu_mask;
+ union {
+ /** @value: per-sensor raw mailbox temperature; bit7=sign, bits6:0=magnitude */
+ u8 value[U8_MAX + 1];
+ /** @dword: sensor values as little-endian dwords, u32-aligned for pcode reads */
+ __le32 dword[DIV_ROUND_UP(U8_MAX + 1, sizeof(u32))];
+ };
+ /** @vram_label: vram label names, dynamically allocated based on vram_count */
+ char (*vram_label)[MAX_LABEL_SIZE];
};
/**
@@ -165,8 +195,8 @@ struct xe_hwmon {
struct xe_hwmon_energy_info ei[CHANNEL_MAX];
/** @fi: Fan info for fanN_input */
struct xe_hwmon_fan_info fi[FAN_MAX];
- /** @boot_power_limit_read: is boot power limits read */
- bool boot_power_limit_read;
+ /** @read_power_limit_from_pcode: pcode accepted power limits write for the channel */
+ bool read_power_limit_from_pcode[CHANNEL_MAX];
/** @pl1_on_boot: power limit PL1 on boot */
u32 pl1_on_boot[CHANNEL_MAX];
/** @pl2_on_boot: power limit PL2 on boot */
@@ -175,9 +205,9 @@ struct xe_hwmon {
struct xe_hwmon_thermal_info temp;
};
-static inline int prepare_power_limit_param2(const struct xe_hwmon *hwmon)
+static inline int prepare_power_limit_param2(const struct xe_hwmon *hwmon, int channel)
{
- if (hwmon->boot_power_limit_read) {
+ if (hwmon->read_power_limit_from_pcode[channel]) {
if (hwmon->xe->info.platform >= XE_CRESCENTISLAND)
return READ_PL_ACCEPTED;
else
@@ -198,7 +228,8 @@ static int xe_hwmon_pcode_read_power_limit(const struct xe_hwmon *hwmon, u32 att
(channel == CHANNEL_CARD) ?
READ_PSYSGPU_POWER_LIMIT :
READ_PACKAGE_POWER_LIMIT,
- prepare_power_limit_param2(hwmon)), &val0, &val1);
+ prepare_power_limit_param2(hwmon, channel)),
+ &val0, &val1);
if (ret) {
drm_dbg(&hwmon->xe->drm, "read failed ch %d val0 0x%08x, val1 0x%08x, ret %d\n",
@@ -213,14 +244,14 @@ static int xe_hwmon_pcode_read_power_limit(const struct xe_hwmon *hwmon, u32 att
else if (attr == PL2_HWMON_ATTR)
*uval = (val1 & PWR_LIM_EN) ? val1 : 0;
else if (attr == hwmon_power_label)
- *uval = (val0 & PWR_LIM_EN) ? 1 : (val1 & PWR_LIM_EN) ? 1 : 0;
+ *uval = (val0 | val1) & PWR_LIM_EN;
else
*uval = 0;
return ret;
}
-static int xe_hwmon_pcode_rmw_power_limit(const struct xe_hwmon *hwmon, u32 attr, u8 channel,
+static int xe_hwmon_pcode_rmw_power_limit(struct xe_hwmon *hwmon, u32 attr, u8 channel,
u32 clr, u32 set)
{
struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
@@ -231,10 +262,13 @@ static int xe_hwmon_pcode_rmw_power_limit(const struct xe_hwmon *hwmon, u32 attr
(channel == CHANNEL_CARD) ?
READ_PSYSGPU_POWER_LIMIT :
READ_PACKAGE_POWER_LIMIT,
- prepare_power_limit_param2(hwmon)), &val0, &val1);
- if (ret)
- drm_dbg(&hwmon->xe->drm, "read failed ch %d val0 0x%08x, val1 0x%08x, ret %d\n",
+ prepare_power_limit_param2(hwmon, channel)),
+ &val0, &val1);
+ if (ret) {
+ drm_err(&hwmon->xe->drm, "read failed ch %d val0 0x%08x, val1 0x%08x, ret %d\n",
channel, val0, val1, ret);
+ return ret;
+ }
if (attr == PL1_HWMON_ATTR)
val0 = (val0 & ~clr) | set;
@@ -248,12 +282,47 @@ static int xe_hwmon_pcode_rmw_power_limit(const struct xe_hwmon *hwmon, u32 attr
WRITE_PSYSGPU_POWER_LIMIT :
WRITE_PACKAGE_POWER_LIMIT, 0),
val0, val1, PCODE_DEFAULT_TIMEOUT_MS);
- if (ret)
+ if (ret) {
drm_dbg(&hwmon->xe->drm, "write failed ch %d val0 0x%08x, val1 0x%08x, ret %d\n",
channel, val0, val1, ret);
+ return ret;
+ }
+
+ /* Read from pcode once it holds a driver-written limit */
+ if (!hwmon->read_power_limit_from_pcode[channel])
+ hwmon->read_power_limit_from_pcode[channel] = true;
+
+ return 0;
+}
+
+static int xe_hwmon_pcode_set_default_power_limits(struct xe_hwmon *hwmon)
+{
+ int ret = 0;
+
+ ret |= xe_hwmon_pcode_rmw_power_limit(hwmon, PL1_HWMON_ATTR, CHANNEL_CARD,
+ PWR_LIM | PWR_LIM_TIME,
+ hwmon->pl1_on_boot[CHANNEL_CARD]);
+ ret |= xe_hwmon_pcode_rmw_power_limit(hwmon, PL1_HWMON_ATTR, CHANNEL_PKG,
+ PWR_LIM | PWR_LIM_TIME,
+ hwmon->pl1_on_boot[CHANNEL_PKG]);
+ ret |= xe_hwmon_pcode_rmw_power_limit(hwmon, PL2_HWMON_ATTR, CHANNEL_CARD,
+ PWR_LIM | PWR_LIM_TIME,
+ hwmon->pl2_on_boot[CHANNEL_CARD]);
+ ret |= xe_hwmon_pcode_rmw_power_limit(hwmon, PL2_HWMON_ATTR, CHANNEL_PKG,
+ PWR_LIM | PWR_LIM_TIME,
+ hwmon->pl2_on_boot[CHANNEL_PKG]);
+
return ret;
}
+static bool xe_hwmon_vram_channel_enabled(const struct xe_hwmon *hwmon, int index)
+{
+ if (hwmon->xe->info.platform == XE_CRESCENTISLAND)
+ return hwmon->temp.msu_mask & BIT(index / VRAM_CHANNELS_PER_MSU);
+
+ return true;
+}
+
static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg hwmon_reg,
int channel)
{
@@ -261,12 +330,21 @@ static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg
switch (hwmon_reg) {
case REG_TEMP:
- if (xe->info.platform == XE_BATTLEMAGE || xe->info.platform == XE_CRESCENTISLAND) {
+ if (xe->info.platform == XE_CRESCENTISLAND) {
+ if (channel == CHANNEL_PKG)
+ return CRI_PACKAGE_TEMPERATURE;
+ else if (channel == CHANNEL_VRAM)
+ return CRI_VRAM_TEMPERATURE;
+ else if (in_range(channel, CHANNEL_VRAM_N, hwmon->temp.vram_count) &&
+ xe_hwmon_vram_channel_enabled(hwmon, channel - CHANNEL_VRAM_N))
+ return CRI_VRAM_TEMPERATURE_N(channel - CHANNEL_VRAM_N);
+ } else if (xe->info.platform == XE_BATTLEMAGE) {
if (channel == CHANNEL_PKG)
return BMG_PACKAGE_TEMPERATURE;
else if (channel == CHANNEL_VRAM)
return BMG_VRAM_TEMPERATURE;
- else if (in_range(channel, CHANNEL_VRAM_N, MAX_VRAM_CHANNELS))
+ else if (in_range(channel, CHANNEL_VRAM_N, hwmon->temp.vram_count) &&
+ xe_hwmon_vram_channel_enabled(hwmon, channel - CHANNEL_VRAM_N))
return BMG_VRAM_TEMPERATURE_N(channel - CHANNEL_VRAM_N);
} else if (xe->info.platform == XE_DG2) {
if (channel == CHANNEL_PKG)
@@ -333,17 +411,20 @@ static struct xe_reg xe_hwmon_get_reg(struct xe_hwmon *hwmon, enum xe_hwmon_reg
* same pattern for sysfs, allow arbitrary PL1 limits to be set but display
* clamped values when read.
*/
-static void xe_hwmon_power_max_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *value)
+static int xe_hwmon_power_max_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *value)
{
u32 reg_val = 0;
struct xe_device *xe = hwmon->xe;
struct xe_reg rapl_limit, pkg_power_sku;
struct xe_mmio *mmio = xe_root_tile_mmio(xe);
+ int ret = 0;
mutex_lock(&hwmon->hwmon_lock);
if (hwmon->xe->info.has_mbx_power_limits) {
- xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, &reg_val);
+ ret = xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, &reg_val);
+ if (ret)
+ goto unlock;
} else {
rapl_limit = xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT, channel);
pkg_power_sku = xe_hwmon_get_reg(hwmon, REG_PKG_POWER_SKU, channel);
@@ -375,6 +456,7 @@ static void xe_hwmon_power_max_read(struct xe_hwmon *hwmon, u32 attr, int channe
}
unlock:
mutex_unlock(&hwmon->hwmon_lock);
+ return ret;
}
static int xe_hwmon_power_max_write(struct xe_hwmon *hwmon, u32 attr, int channel, long value)
@@ -394,8 +476,12 @@ static int xe_hwmon_power_max_write(struct xe_hwmon *hwmon, u32 attr, int channe
if (hwmon->xe->info.has_mbx_power_limits) {
drm_dbg(&hwmon->xe->drm, "disabling %s on channel %d\n",
PWR_ATTR_TO_STR(attr), channel);
- xe_hwmon_pcode_rmw_power_limit(hwmon, attr, channel, PWR_LIM_EN, 0);
- xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, &reg_val);
+ ret = xe_hwmon_pcode_rmw_power_limit(hwmon, attr, channel, PWR_LIM_EN, 0);
+ if (ret)
+ goto unlock;
+ ret = xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, &reg_val);
+ if (ret)
+ goto unlock;
} else {
reg_val = xe_mmio_rmw32(mmio, rapl_limit, PWR_LIM_EN, 0);
reg_val = xe_mmio_read32(mmio, rapl_limit);
@@ -452,15 +538,18 @@ unlock:
return ret;
}
-static void xe_hwmon_power_rated_max_read(struct xe_hwmon *hwmon, u32 attr, int channel,
- long *value)
+static int xe_hwmon_power_rated_max_read(struct xe_hwmon *hwmon, u32 attr, int channel,
+ long *value)
{
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
u32 reg_val;
+ int ret = 0;
if (hwmon->xe->info.has_mbx_power_limits) {
/* PL1 is rated max if supported. */
- xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, channel, &reg_val);
+ ret = xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, channel, &reg_val);
+ if (ret)
+ return ret;
} else {
/*
* This sysfs file won't be visible if REG_PKG_POWER_SKU is invalid, so valid check
@@ -474,6 +563,7 @@ static void xe_hwmon_power_rated_max_read(struct xe_hwmon *hwmon, u32 attr, int
reg_val = REG_FIELD_GET(PKG_TDP, reg_val);
*value = mul_u64_u32_shr(reg_val, SF_POWER, hwmon->scl_shift_power);
+ return ret;
}
/*
@@ -496,7 +586,7 @@ static void xe_hwmon_power_rated_max_read(struct xe_hwmon *hwmon, u32 attr, int
* hwmon->scl_shift_energy of 14 bits we have 57 (63 - 20 + 14) bits before
* energyN_input overflows. This at 1000 W is an overflow duration of 278 years.
*/
-static void
+static int
xe_hwmon_energy_get(struct xe_hwmon *hwmon, int channel, long *energy)
{
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
@@ -507,7 +597,7 @@ xe_hwmon_energy_get(struct xe_hwmon *hwmon, int channel, long *energy)
/* Energy is supported only for card and pkg */
if (channel > CHANNEL_PKG) {
*energy = 0;
- return;
+ return 0;
}
if (hwmon->xe->info.platform == XE_BATTLEMAGE) {
@@ -519,7 +609,7 @@ xe_hwmon_energy_get(struct xe_hwmon *hwmon, int channel, long *energy)
if (ret != sizeof(pmt_val)) {
drm_warn(&hwmon->xe->drm, "energy read from pmt failed, ret %d\n", ret);
*energy = 0;
- return;
+ return ret < 0 ? ret : -EIO;
}
if (channel == CHANNEL_PKG)
@@ -536,6 +626,7 @@ xe_hwmon_energy_get(struct xe_hwmon *hwmon, int channel, long *energy)
*energy = mul_u64_u32_shr(ei->accum_energy, SF_ENERGY,
hwmon->scl_shift_energy);
+ return 0;
}
static ssize_t
@@ -558,10 +649,11 @@ xe_hwmon_power_max_interval_show(struct device *dev, struct device_attribute *at
if (hwmon->xe->info.has_mbx_power_limits) {
ret = xe_hwmon_pcode_read_power_limit(hwmon, power_attr, channel, &reg_val);
if (ret) {
- drm_err(&hwmon->xe->drm,
- "power interval read fail, ch %d, attr %d, val 0x%08x, ret %d\n",
- channel, power_attr, reg_val, ret);
- reg_val = 0;
+ xe_log_err(hwmon->xe, HWMON, ret,
+ "Power interval read fail, ch %d, attr %d, val 0x%08x\n",
+ channel, power_attr, reg_val);
+ mutex_unlock(&hwmon->hwmon_lock);
+ return ret;
}
} else {
reg_val = xe_mmio_read32(mmio, xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT,
@@ -679,15 +771,19 @@ xe_hwmon_power_max_interval_store(struct device *dev, struct device_attribute *a
mutex_lock(&hwmon->hwmon_lock);
- if (hwmon->xe->info.has_mbx_power_limits)
- xe_hwmon_pcode_rmw_power_limit(hwmon, power_attr, channel, PWR_LIM_TIME, rxy);
- else
+ ret = count;
+ if (hwmon->xe->info.has_mbx_power_limits) {
+ ret = xe_hwmon_pcode_rmw_power_limit(hwmon, power_attr, channel, PWR_LIM_TIME, rxy);
+ if (!ret)
+ ret = count;
+ } else {
r = xe_mmio_rmw32(mmio, xe_hwmon_get_reg(hwmon, REG_PKG_RAPL_LIMIT, channel),
PWR_LIM_TIME, rxy);
+ }
mutex_unlock(&hwmon->hwmon_lock);
- return count;
+ return ret;
}
/* PSYS PL1 */
@@ -749,30 +845,24 @@ static const struct attribute_group *hwmon_groups[] = {
NULL
};
+static const u32 hwmon_temp_config[CHANNEL_MAX + 1] = {
+ [CHANNEL_CARD] = HWMON_T_LABEL,
+ [CHANNEL_PKG] = HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT |
+ HWMON_T_LABEL | HWMON_T_MAX,
+ [CHANNEL_VRAM ... CHANNEL_PCIE] = HWMON_T_CRIT | HWMON_T_EMERGENCY |
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ [CHANNEL_VRAM_N ... CHANNEL_VRAM_N_MAX] = HWMON_T_CRIT | HWMON_T_EMERGENCY |
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ [CHANNEL_MAX] = 0, /* hwmon config terminator */
+};
+
+static const struct hwmon_channel_info hwmon_temp_info = {
+ .type = hwmon_temp,
+ .config = hwmon_temp_config,
+};
+
static const struct hwmon_channel_info * const hwmon_info[] = {
- HWMON_CHANNEL_INFO(temp,
- HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL |
- HWMON_T_MAX,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL,
- HWMON_T_CRIT | HWMON_T_EMERGENCY | HWMON_T_INPUT | HWMON_T_LABEL),
+ &hwmon_temp_info,
HWMON_CHANNEL_INFO(power, HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL | HWMON_P_CRIT |
HWMON_P_CAP,
HWMON_P_MAX | HWMON_P_RATED_MAX | HWMON_P_LABEL | HWMON_P_CAP),
@@ -786,16 +876,39 @@ static const struct hwmon_channel_info * const hwmon_info[] = {
static int xe_hwmon_pcode_read_thermal_info(struct xe_hwmon *hwmon)
{
struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
- u32 config = 0;
+ struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
+ u32 config = 0, data0 = 0, data1 = 0;
int ret;
+ /*
+ * Only CRI reports dynamic VRAM channel state. Fixed-channel platforms
+ * set the count up front so those sensors stay visible even if the thermal
+ * mailbox reads below fail.
+ */
+ if (hwmon->xe->info.has_fixed_vram_channels)
+ hwmon->temp.vram_count = FIXED_VRAM_CHANNELS;
+
+ if (hwmon->xe->info.platform == XE_CRESCENTISLAND) {
+ hwmon->temp.msu_mask = xe_mmio_read32(mmio, CRI_MSU_VRAM_ENABLE);
+ hwmon->temp.vram_count = fls(hwmon->temp.msu_mask) * VRAM_CHANNELS_PER_MSU;
+ drm_dbg(&hwmon->xe->drm, "MSU VRAM enable mask 0x%x, VRAM channel bound %d\n",
+ hwmon->temp.msu_mask, hwmon->temp.vram_count);
+ if (hwmon->temp.vram_count > MAX_VRAM_CHANNELS) {
+ drm_warn(&hwmon->xe->drm,
+ "VRAM channel bound %d exceeds max %d, clamping\n",
+ hwmon->temp.vram_count, MAX_VRAM_CHANNELS);
+ hwmon->temp.vram_count = MAX_VRAM_CHANNELS;
+ }
+ }
+
ret = xe_pcode_read(root_tile, PCODE_MBOX(PCODE_THERMAL_INFO, READ_THERMAL_LIMITS, 0),
- &hwmon->temp.data[0], &hwmon->temp.data[1]);
+ &data0, &data1);
if (ret)
return ret;
- drm_dbg(&hwmon->xe->drm, "thermal info read val 0x%x val1 0x%x\n",
- hwmon->temp.data[0], hwmon->temp.data[1]);
+ drm_dbg(&hwmon->xe->drm, "thermal info read val 0x%x val1 0x%x\n", data0, data1);
+ hwmon->temp.data[0] = cpu_to_le32(data0);
+ hwmon->temp.data[1] = cpu_to_le32(data1);
ret = xe_pcode_read(root_tile, PCODE_MBOX(PCODE_THERMAL_INFO, READ_THERMAL_CONFIG, 0),
&config, NULL);
@@ -808,46 +921,133 @@ static int xe_hwmon_pcode_read_thermal_info(struct xe_hwmon *hwmon)
return ret;
}
+static inline bool mmio_temp_valid(const struct xe_hwmon *hwmon, u32 value)
+{
+ /*
+ * On platforms lacking temperature sentinel support, a value of 0 is treated
+ * as invalid. While 0°C is theoretically possible, it is highly unlikely for
+ * a discrete GPU in normal environments, making it a reasonable fallback
+ * indicator for invalid or uninitialized temperature readings.
+ * All-ones is returned when the device is off the bus, so reject it too.
+ */
+ if (value == U32_MAX)
+ return false;
+
+ return hwmon->xe->info.has_temp_sentinel || value != 0;
+}
+
+/* Mailbox temperature is sign-magnitude (bit 7 sign, bits 6:0 magnitude). */
+static inline bool mbx_temp_valid(const struct xe_hwmon *hwmon, u8 value)
+{
+ if (value == TEMP_MBX_INVALID)
+ return false;
+
+ return hwmon->xe->info.has_temp_sentinel || value != 0;
+}
+
+/* Decode an 8-bit sign-magnitude mailbox temperature to degrees Celsius. */
+static int mbx_temp_decode(u8 value)
+{
+ int temp = value & TEMP_MBX_MAG;
+
+ return (value & TEMP_MBX_SIGN) ? -temp : temp;
+}
+
+static u8 xe_hwmon_pcie_temp_index(const struct xe_hwmon *hwmon)
+{
+ if (hwmon->xe->info.platform == XE_CRESCENTISLAND)
+ return CRI_TEMP_INDEX_PCIE;
+
+ if (hwmon->xe->info.subplatform == XE_SUBPLATFORM_BATTLEMAGE_G21)
+ return BMG_G21_TEMP_INDEX_PCIE;
+
+ return TEMP_INDEX_PCIE;
+}
+
static int get_mc_temp(struct xe_hwmon *hwmon, long *val)
{
struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
- u32 *dword = (u32 *)hwmon->temp.value;
+ int ret, i, start, end, count = 0;
s32 average = 0;
- int ret, i;
+ u32 data = 0;
+
+ guard(mutex)(&hwmon->hwmon_lock);
+
+ /* CRI relocated the memory controller temperatures to READ_THERMAL_DATA bytes 16..19. */
+ if (hwmon->xe->info.platform == XE_CRESCENTISLAND) {
+ start = CRI_TEMP_INDEX_MCTRL;
+ end = CRI_TEMP_INDEX_MCTRL + CRI_TEMP_MCTRL_COUNT;
+ } else {
+ start = TEMP_INDEX_MCTRL;
+ end = min_t(int, hwmon->temp.count - 1, xe_hwmon_pcie_temp_index(hwmon));
+ }
- for (i = 0; i < DIV_ROUND_UP(TEMP_LIMIT_MAX, sizeof(u32)); i++) {
+ if (end <= start || end > hwmon->temp.count) {
+ drm_warn(&hwmon->xe->drm,
+ "memory controller range [%d, %d) is invalid for temperature count %d\n",
+ start, end, hwmon->temp.count);
+ return -ENODATA;
+ }
+
+ for (i = start / sizeof(u32); i < DIV_ROUND_UP(end, sizeof(u32)); i++) {
ret = xe_pcode_read(root_tile, PCODE_MBOX(PCODE_THERMAL_INFO, READ_THERMAL_DATA, i),
- (dword + i), NULL);
+ &data, NULL);
if (ret)
return ret;
- drm_dbg(&hwmon->xe->drm, "thermal data for group %d val 0x%x\n", i, dword[i]);
+ drm_dbg(&hwmon->xe->drm, "thermal data for group %d val 0x%x\n", i, data);
+ hwmon->temp.dword[i] = cpu_to_le32(data);
+ }
+
+ for (i = start; i < end; i++) {
+ if (!mbx_temp_valid(hwmon, hwmon->temp.value[i]))
+ continue;
+ average += mbx_temp_decode(hwmon->temp.value[i]);
+ count++;
+ }
+
+ if (!count) {
+ drm_dbg(&hwmon->xe->drm, "Memory controller temperature unavailable!\n");
+ return -ENXIO;
}
- for (i = TEMP_INDEX_MCTRL; i < hwmon->temp.count - 1; i++)
- average += hwmon->temp.value[i];
+ average /= count;
+
+ if (val)
+ *val = average * MILLIDEGREE_PER_DEGREE;
- average /= (hwmon->temp.count - TEMP_INDEX_MCTRL - 1);
- *val = average * MILLIDEGREE_PER_DEGREE;
return 0;
}
-static int get_pcie_temp(struct xe_hwmon *hwmon, long *val)
+static int get_pcie_temp(const struct xe_hwmon *hwmon, long *val)
{
struct xe_tile *root_tile = xe_device_get_root_tile(hwmon->xe);
u32 data = 0;
+ int index;
int ret;
+ index = xe_hwmon_pcie_temp_index(hwmon);
+ if (index >= hwmon->temp.count) {
+ drm_warn(&hwmon->xe->drm,
+ "PCIe temperature index %d out of range for temperature count %d\n",
+ index, hwmon->temp.count);
+ return -ENODATA;
+ }
+
ret = xe_pcode_read(root_tile, PCODE_MBOX(PCODE_THERMAL_INFO, READ_THERMAL_DATA,
- PCIE_SENSOR_GROUP_ID), &data, NULL);
+ index / sizeof(data)),
+ &data, NULL);
if (ret)
return ret;
- /* Sensor offset is different for G21 */
- if (hwmon->xe->info.subplatform != XE_SUBPLATFORM_BATTLEMAGE_G21)
- data = REG_FIELD_GET(PCIE_SENSOR_MASK, data);
-
+ data >>= (index % sizeof(data)) * BITS_PER_BYTE;
data = REG_FIELD_GET(TEMP_MASK, data);
- *val = (s8)data * MILLIDEGREE_PER_DEGREE;
+ if (!mbx_temp_valid(hwmon, data)) {
+ drm_dbg(&hwmon->xe->drm, "PCIe temperature not available\n");
+ return -ENXIO;
+ }
+
+ if (val)
+ *val = mbx_temp_decode(data) * MILLIDEGREE_PER_DEGREE;
return 0;
}
@@ -946,19 +1146,70 @@ static void xe_hwmon_get_voltage(struct xe_hwmon *hwmon, int channel, long *valu
*value = DIV_ROUND_CLOSEST(REG_FIELD_GET(VOLTAGE_MASK, reg_val) * 2500, SF_VOLTAGE);
}
-static inline bool is_vram_ch_available(struct xe_hwmon *hwmon, int channel)
+static bool xe_hwmon_temp_probe(struct xe_hwmon *hwmon, int channel)
{
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
- int vram_id = channel - CHANNEL_VRAM_N;
- struct xe_reg vram_reg;
+ struct xe_reg reg;
+ u32 reg_val;
+
+ switch (channel) {
+ case CHANNEL_PKG:
+ case CHANNEL_VRAM:
+ case CHANNEL_VRAM_N...CHANNEL_VRAM_N_MAX:
+ reg = xe_hwmon_get_reg(hwmon, REG_TEMP, channel);
+ if (!xe_reg_is_valid(reg))
+ return false;
- vram_reg = xe_hwmon_get_reg(hwmon, REG_TEMP, channel);
- if (!xe_reg_is_valid(vram_reg) || !xe_mmio_read32(mmio, vram_reg))
+ /*
+ * On CRI, VRAM channel presence comes from the MSU enable mask
+ * (applied in xe_hwmon_get_reg()); the temperature register's
+ * 0xffffffff sentinel is not reliable for VRAM channels.
+ */
+ if (hwmon->xe->info.platform == XE_CRESCENTISLAND && channel >= CHANNEL_VRAM_N)
+ return true;
+
+ reg_val = xe_mmio_read32(mmio, reg);
+ if (!mmio_temp_valid(hwmon, reg_val)) {
+ drm_dbg(&hwmon->xe->drm,
+ "channel %d temperature unavailable, val 0x%x\n", channel, reg_val);
+ return false;
+ }
+
+ return true;
+ case CHANNEL_MCTRL:
+ return hwmon->temp.count && !get_mc_temp(hwmon, NULL);
+ case CHANNEL_PCIE:
+ return hwmon->temp.count && !get_pcie_temp(hwmon, NULL);
+ default:
return false;
+ }
+}
- /* Create label only for available vram channel */
- sprintf(hwmon->temp.vram_label[vram_id], "vram_ch_%d", vram_id);
- return true;
+static int xe_hwmon_init_temp_info(struct xe_hwmon *hwmon)
+{
+ struct device *dev = hwmon->xe->drm.dev;
+ int channel;
+
+ if (hwmon->xe->info.has_mbx_thermal_info && xe_hwmon_pcode_read_thermal_info(hwmon))
+ drm_warn(&hwmon->xe->drm, "Thermal mailbox not supported by card firmware\n");
+
+ /* vram_count is known only after reading thermal info above. */
+ if (hwmon->temp.vram_count) {
+ hwmon->temp.vram_label = devm_kcalloc(dev, hwmon->temp.vram_count,
+ MAX_LABEL_SIZE, GFP_KERNEL);
+ if (!hwmon->temp.vram_label)
+ return -ENOMEM;
+ }
+
+ for (channel = 0; channel < CHANNEL_MAX; channel++) {
+ hwmon->temp.available[channel] = xe_hwmon_temp_probe(hwmon, channel);
+ if (hwmon->temp.available[channel] && hwmon->temp.vram_label &&
+ in_range(channel, CHANNEL_VRAM_N, hwmon->temp.vram_count))
+ snprintf(hwmon->temp.vram_label[channel - CHANNEL_VRAM_N], MAX_LABEL_SIZE,
+ "vram_ch_%d", channel - CHANNEL_VRAM_N);
+ }
+
+ return 0;
}
static umode_t
@@ -968,14 +1219,13 @@ xe_hwmon_temp_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
case hwmon_temp_emergency:
switch (channel) {
case CHANNEL_PKG:
- return hwmon->temp.limit[TEMP_LIMIT_PKG_SHUTDOWN] ? 0444 : 0;
- case CHANNEL_VRAM:
- return hwmon->temp.limit[TEMP_LIMIT_MEM_SHUTDOWN] ? 0444 : 0;
case CHANNEL_MCTRL:
case CHANNEL_PCIE:
- return hwmon->temp.count ? 0444 : 0;
+ return (hwmon->temp.available[channel] &&
+ hwmon->temp.limit[TEMP_LIMIT_PKG_SHUTDOWN]) ? 0444 : 0;
+ case CHANNEL_VRAM:
case CHANNEL_VRAM_N...CHANNEL_VRAM_N_MAX:
- return (is_vram_ch_available(hwmon, channel) &&
+ return (hwmon->temp.available[channel] &&
hwmon->temp.limit[TEMP_LIMIT_MEM_SHUTDOWN]) ? 0444 : 0;
default:
return 0;
@@ -983,14 +1233,13 @@ xe_hwmon_temp_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
case hwmon_temp_crit:
switch (channel) {
case CHANNEL_PKG:
- return hwmon->temp.limit[TEMP_LIMIT_PKG_CRIT] ? 0444 : 0;
- case CHANNEL_VRAM:
- return hwmon->temp.limit[TEMP_LIMIT_MEM_CRIT] ? 0444 : 0;
case CHANNEL_MCTRL:
case CHANNEL_PCIE:
- return hwmon->temp.count ? 0444 : 0;
+ return (hwmon->temp.available[channel] &&
+ hwmon->temp.limit[TEMP_LIMIT_PKG_CRIT]) ? 0444 : 0;
+ case CHANNEL_VRAM:
case CHANNEL_VRAM_N...CHANNEL_VRAM_N_MAX:
- return (is_vram_ch_available(hwmon, channel) &&
+ return (hwmon->temp.available[channel] &&
hwmon->temp.limit[TEMP_LIMIT_MEM_CRIT]) ? 0444 : 0;
default:
return 0;
@@ -998,7 +1247,8 @@ xe_hwmon_temp_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
case hwmon_temp_max:
switch (channel) {
case CHANNEL_PKG:
- return hwmon->temp.limit[TEMP_LIMIT_PKG_MAX] ? 0444 : 0;
+ return (hwmon->temp.available[channel] &&
+ hwmon->temp.limit[TEMP_LIMIT_PKG_MAX]) ? 0444 : 0;
default:
return 0;
}
@@ -1007,13 +1257,10 @@ xe_hwmon_temp_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
switch (channel) {
case CHANNEL_PKG:
case CHANNEL_VRAM:
- return xe_reg_is_valid(xe_hwmon_get_reg(hwmon, REG_TEMP,
- channel)) ? 0444 : 0;
case CHANNEL_MCTRL:
case CHANNEL_PCIE:
- return hwmon->temp.count ? 0444 : 0;
case CHANNEL_VRAM_N...CHANNEL_VRAM_N_MAX:
- return is_vram_ch_available(hwmon, channel) ? 0444 : 0;
+ return hwmon->temp.available[channel] ? 0444 : 0;
default:
return 0;
}
@@ -1022,6 +1269,70 @@ xe_hwmon_temp_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
}
}
+#define IEEE754_SIGN REG_BIT(31)
+#define IEEE754_EXP_MASK REG_GENMASK(30, 23)
+#define IEEE754_MANTISSA_MASK REG_GENMASK(22, 0)
+#define IEEE754_EXP_BIAS 127 /* Exponent bias to obtain the actual exponent */
+#define IEEE754_MANTISSA_BITS 23 /* Mantissa width and implicit leading-1 bit position */
+#define IEEE754_EXP_SPECIAL 0xff /* reserved exponent value encoding infinity or NaN */
+
+/**
+ * xe_hwmon_temperature_decode - Decode an IEEE-754 temperature register value
+ * @reg_val: The raw register value to decode
+ * @temperature: Pointer to store the decoded temperature in millidegrees Celsius.
+ *
+ * IEEE-754 single-precision format:
+ *
+ * 31 30 23 22 0
+ * +--+-----------------------------+----------------------------+
+ * | S| exponent | fraction |
+ * +--+-----------------------------+----------------------------+
+ *
+ * value = (-1)^S * 1.fraction * 2^(exponent - 127)
+ *
+ * Return: 0 on success or an error if @reg_val is non-finite or cannot be
+ * represented in millidegrees Celsius.
+ */
+static int xe_hwmon_temperature_decode(u32 reg_val, long *temperature)
+{
+ u32 exponent = REG_FIELD_GET(IEEE754_EXP_MASK, reg_val);
+ u32 mantissa = REG_FIELD_GET(IEEE754_MANTISSA_MASK, reg_val);
+ s64 value;
+ int shift;
+
+ /* Exponent 0xff encodes infinity or NaN. */
+ if (exponent == IEEE754_EXP_SPECIAL)
+ return -EINVAL;
+
+ /* Subnormal values are smaller than one millidegree Celsius. */
+ if (!exponent) {
+ *temperature = 0;
+ return 0;
+ }
+
+ value = (s64)(mantissa | BIT(IEEE754_MANTISSA_BITS)) * MILLIDEGREE_PER_DEGREE;
+ shift = exponent - (IEEE754_EXP_BIAS + IEEE754_MANTISSA_BITS);
+ if (shift >= 0) {
+ if (shift >= BITS_PER_LONG || value > (LONG_MAX >> shift))
+ return -ERANGE;
+ value <<= shift;
+ } else if (shift > -64) {
+ value >>= -shift;
+ } else {
+ value = 0;
+ }
+
+ /* Ensure the magnitude fits in long, which is 32-bit on some arches. */
+ if (value > LONG_MAX)
+ return -ERANGE;
+
+ if (reg_val & IEEE754_SIGN)
+ value = -value;
+
+ *temperature = value;
+ return 0;
+}
+
static int
xe_hwmon_temp_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
{
@@ -1031,26 +1342,33 @@ xe_hwmon_temp_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
switch (attr) {
case hwmon_temp_input:
switch (channel) {
- case CHANNEL_PKG:
- case CHANNEL_VRAM:
- reg_val = xe_mmio_read32(mmio, xe_hwmon_get_reg(hwmon, REG_TEMP, channel));
-
- /* HW register value is in degrees Celsius, convert to millidegrees. */
- *val = REG_FIELD_GET(TEMP_MASK, reg_val) * MILLIDEGREE_PER_DEGREE;
- return 0;
case CHANNEL_MCTRL:
return get_mc_temp(hwmon, val);
case CHANNEL_PCIE:
return get_pcie_temp(hwmon, val);
+ case CHANNEL_PKG:
+ case CHANNEL_VRAM:
case CHANNEL_VRAM_N...CHANNEL_VRAM_N_MAX:
reg_val = xe_mmio_read32(mmio, xe_hwmon_get_reg(hwmon, REG_TEMP, channel));
- /*
- * This temperature format is 24 bit [31:8] signed integer and 8 bit
- * [7:0] fraction.
- */
- *val = (s32)(REG_FIELD_GET(TEMP_MASK_VRAM_N, reg_val)) *
- (REG_FIELD_GET(TEMP_SIGN_MASK, reg_val) ? -1 : 1) *
- MILLIDEGREE_PER_DEGREE;
+ if (!mmio_temp_valid(hwmon, reg_val)) {
+ drm_dbg(&hwmon->xe->drm,
+ "channel %d temperature invalid, val 0x%llx\n",
+ channel, reg_val);
+ return -ENXIO;
+ }
+ if (hwmon->xe->info.platform >= XE_CRESCENTISLAND) {
+ return xe_hwmon_temperature_decode((u32)reg_val, val);
+ } else if (channel >= CHANNEL_VRAM_N) {
+ /*
+ * This temperature format is 24 bit [31:8] signed integer and 8 bit
+ * [7:0] fraction for platforms before CRI.
+ */
+ *val = (s32)REG_FIELD_GET(TEMP_MASK_VRAM_N, reg_val) *
+ (REG_FIELD_GET(TEMP_SIGN_MASK, reg_val) ? -1 : 1) *
+ MILLIDEGREE_PER_DEGREE;
+ } else {
+ *val = REG_FIELD_GET(TEMP_MASK, reg_val) * MILLIDEGREE_PER_DEGREE;
+ }
return 0;
default:
return -EOPNOTSUPP;
@@ -1132,7 +1450,8 @@ xe_hwmon_power_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
}
case hwmon_power_crit:
if (channel == CHANNEL_CARD) {
- xe_hwmon_pcode_read_i1(hwmon, &uval);
+ if (xe_hwmon_pcode_read_i1(hwmon, &uval))
+ return 0;
return (uval & POWER_SETUP_I1_WATTS) ? 0644 : 0;
}
break;
@@ -1151,7 +1470,8 @@ xe_hwmon_power_is_visible(struct xe_hwmon *hwmon, u32 attr, int channel)
}
}
if ((!(uval & PWR_LIM_EN)) && channel == CHANNEL_CARD) {
- xe_hwmon_pcode_read_i1(hwmon, &uval);
+ if (xe_hwmon_pcode_read_i1(hwmon, &uval))
+ return 0;
return (uval & POWER_SETUP_I1_WATTS) ? 0444 : 0;
}
return (uval) ? 0444 : 0;
@@ -1167,11 +1487,9 @@ xe_hwmon_power_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
switch (attr) {
case hwmon_power_max:
case hwmon_power_cap:
- xe_hwmon_power_max_read(hwmon, attr, channel, val);
- return 0;
+ return xe_hwmon_power_max_read(hwmon, attr, channel, val);
case hwmon_power_rated_max:
- xe_hwmon_power_rated_max_read(hwmon, attr, channel, val);
- return 0;
+ return xe_hwmon_power_rated_max_read(hwmon, attr, channel, val);
case hwmon_power_crit:
return xe_hwmon_power_curr_crit_read(hwmon, channel, val, SF_POWER);
default:
@@ -1288,8 +1606,7 @@ xe_hwmon_energy_read(struct xe_hwmon *hwmon, u32 attr, int channel, long *val)
{
switch (attr) {
case hwmon_energy_input:
- xe_hwmon_energy_get(hwmon, channel, val);
- return 0;
+ return xe_hwmon_energy_get(hwmon, channel, val);
default:
return -EOPNOTSUPP;
}
@@ -1458,8 +1775,10 @@ static int xe_hwmon_read_label(struct device *dev,
*str = "mctrl";
else if (channel == CHANNEL_PCIE)
*str = "pcie";
- else if (in_range(channel, CHANNEL_VRAM_N, MAX_VRAM_CHANNELS))
+ else if (in_range(channel, CHANNEL_VRAM_N, hwmon->temp.vram_count))
*str = hwmon->temp.vram_label[channel - CHANNEL_VRAM_N];
+ else
+ return -EOPNOTSUPP;
return 0;
case hwmon_power:
case hwmon_energy:
@@ -1487,7 +1806,7 @@ static const struct hwmon_chip_info hwmon_chip_info = {
.info = hwmon_info,
};
-static void
+static int
xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
{
struct xe_mmio *mmio = xe_root_tile_mmio(hwmon->xe);
@@ -1511,22 +1830,11 @@ xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
} else {
drm_info(&hwmon->xe->drm, "Using mailbox commands for power limits\n");
/* Write default limits to read from pcode from now on. */
- xe_hwmon_pcode_rmw_power_limit(hwmon, PL1_HWMON_ATTR,
- CHANNEL_CARD, PWR_LIM | PWR_LIM_TIME,
- hwmon->pl1_on_boot[CHANNEL_CARD]);
- xe_hwmon_pcode_rmw_power_limit(hwmon, PL1_HWMON_ATTR,
- CHANNEL_PKG, PWR_LIM | PWR_LIM_TIME,
- hwmon->pl1_on_boot[CHANNEL_PKG]);
- xe_hwmon_pcode_rmw_power_limit(hwmon, PL2_HWMON_ATTR,
- CHANNEL_CARD, PWR_LIM | PWR_LIM_TIME,
- hwmon->pl2_on_boot[CHANNEL_CARD]);
- xe_hwmon_pcode_rmw_power_limit(hwmon, PL2_HWMON_ATTR,
- CHANNEL_PKG, PWR_LIM | PWR_LIM_TIME,
- hwmon->pl2_on_boot[CHANNEL_PKG]);
+ if (xe_hwmon_pcode_set_default_power_limits(hwmon))
+ drm_warn(&hwmon->xe->drm, "Failed to set default power limits\n");
hwmon->scl_shift_power = PWR_UNIT;
hwmon->scl_shift_energy = ENERGY_UNIT;
hwmon->scl_shift_time = TIME_UNIT;
- hwmon->boot_power_limit_read = true;
}
} else {
drm_info(&hwmon->xe->drm, "Using register for power limits\n");
@@ -1555,8 +1863,7 @@ xe_hwmon_get_preregistration_info(struct xe_hwmon *hwmon)
if (xe_hwmon_is_visible(hwmon, hwmon_fan, hwmon_fan_input, channel))
xe_hwmon_fan_input_read(hwmon, channel, &fan_speed);
- if (hwmon->xe->info.has_mbx_thermal_info && xe_hwmon_pcode_read_thermal_info(hwmon))
- drm_warn(&hwmon->xe->drm, "Thermal mailbox not supported by card firmware\n");
+ return xe_hwmon_init_temp_info(hwmon);
}
int xe_hwmon_register(struct xe_device *xe)
@@ -1585,7 +1892,9 @@ int xe_hwmon_register(struct xe_device *xe)
hwmon->xe = xe;
xe->hwmon = hwmon;
- xe_hwmon_get_preregistration_info(hwmon);
+ ret = xe_hwmon_get_preregistration_info(hwmon);
+ if (ret)
+ goto err_null_hwmon;
drm_dbg(&xe->drm, "Register xe hwmon interface\n");
@@ -1594,11 +1903,15 @@ int xe_hwmon_register(struct xe_device *xe)
&hwmon_chip_info,
hwmon_groups);
if (IS_ERR(hwmon->hwmon_dev)) {
- drm_err(&xe->drm, "Failed to register xe hwmon (%pe)\n", hwmon->hwmon_dev);
- xe->hwmon = NULL;
- return PTR_ERR(hwmon->hwmon_dev);
+ ret = PTR_ERR(hwmon->hwmon_dev);
+ xe_log_err(xe, HWMON, ret, "Failed to register xe hwmon\n");
+ goto err_null_hwmon;
}
return 0;
+
+err_null_hwmon:
+ xe->hwmon = NULL;
+ return ret;
}
MODULE_IMPORT_NS("INTEL_PMT_TELEMETRY");
diff --git a/drivers/gpu/drm/xe/xe_irq.c b/drivers/gpu/drm/xe/xe_irq.c
index 9e49e2241da4..b394c8edf89c 100644
--- a/drivers/gpu/drm/xe/xe_irq.c
+++ b/drivers/gpu/drm/xe/xe_irq.c
@@ -908,6 +908,7 @@ static irqreturn_t xe_irq_msix_default_hwe_handler(int irq, void *arg)
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
struct xe_tile *tile;
+ unsigned long flags;
struct xe_gt *gt;
if (!atomic_read(&xe->irq.enabled))
@@ -922,8 +923,10 @@ static irqreturn_t xe_irq_msix_default_hwe_handler(int irq, void *arg)
if (gt->tile != tile)
continue;
+ spin_lock_irqsave(&xe->irq.lock, flags);
for_each_hw_engine(hwe, gt, id)
xe_memirq_hwe_handler(memirq, hwe);
+ spin_unlock_irqrestore(&xe->irq.lock, flags);
}
}
diff --git a/drivers/gpu/drm/xe/xe_lmtt.c b/drivers/gpu/drm/xe/xe_lmtt.c
index 0c726eda9390..b6b3d892a35e 100644
--- a/drivers/gpu/drm/xe/xe_lmtt.c
+++ b/drivers/gpu/drm/xe/xe_lmtt.c
@@ -14,6 +14,7 @@
#include "xe_bo.h"
#include "xe_tlb_inval.h"
#include "xe_lmtt.h"
+#include "xe_log.h"
#include "xe_map.h"
#include "xe_mert.h"
#include "xe_mmio.h"
@@ -302,14 +303,15 @@ void xe_lmtt_invalidate_hw(struct xe_lmtt *lmtt)
err = lmtt_invalidate_hw(lmtt);
if (err)
- xe_tile_sriov_err(tile, "LMTT invalidation failed (%pe)",
- ERR_PTR(err));
+ xe_log_err(tile, LMTT, err, "%s invalidation failed.\n",
+ xe_sriov_printk_prefix(xe));
if (xe_device_has_mert(xe) && xe_tile_is_root(tile)) {
err = xe_mert_invalidate_lmtt(xe);
if (err)
- xe_tile_sriov_err(tile, "MERT LMTT invalidation failed (%pe)",
- ERR_PTR(err));
+ xe_log_err(tile, LMTT, err,
+ "%s MERT invalidation failed.\n",
+ xe_sriov_printk_prefix(xe));
}
}
diff --git a/drivers/gpu/drm/xe/xe_lrc.c b/drivers/gpu/drm/xe/xe_lrc.c
index de9a9560ecf3..fe2909b7b84d 100644
--- a/drivers/gpu/drm/xe/xe_lrc.c
+++ b/drivers/gpu/drm/xe/xe_lrc.c
@@ -2718,8 +2718,8 @@ void xe_lrc_snapshot_print(struct xe_lrc_snapshot *snapshot, struct drm_printer
}
drm_printf(p, "\n\t[HWCTX].length: 0x%lx\n", snapshot->lrc_size - LRC_PPHWSP_SIZE);
- drm_printf(p, "\n\t[HWCTX].replay_offset: 0x%lx\n", snapshot->replay_offset);
- drm_printf(p, "\n\t[HWCTX].replay_length: 0x%lx\n", snapshot->replay_size);
+ drm_printf(p, "\t[HWCTX].replay_offset: 0x%lx\n", snapshot->replay_offset);
+ drm_printf(p, "\t[HWCTX].replay_length: 0x%lx\n", snapshot->replay_size);
drm_puts(p, "\t[HWCTX].data: ");
for (; i < snapshot->lrc_size; i += sizeof(u32)) {
diff --git a/drivers/gpu/drm/xe/xe_memirq.c b/drivers/gpu/drm/xe/xe_memirq.c
index 9dfe965cb46e..3433518cc095 100644
--- a/drivers/gpu/drm/xe/xe_memirq.c
+++ b/drivers/gpu/drm/xe/xe_memirq.c
@@ -15,6 +15,7 @@
#include "xe_gt.h"
#include "xe_guc.h"
#include "xe_hw_engine.h"
+#include "xe_log.h"
#include "xe_memirq.h"
#include "xe_tile_printk.h"
@@ -232,7 +233,7 @@ static int memirq_alloc_pages(struct xe_memirq *memirq)
return 0;
out:
- memirq_err(memirq, "Failed to allocate memirq page (%pe)\n", ERR_PTR(err));
+ xe_log_err(tile, MEMIRQ, err, "Failed to allocate memirq page\n");
return err;
}
@@ -370,8 +371,8 @@ int xe_memirq_init_guc(struct xe_memirq *memirq, struct xe_guc *guc)
return 0;
failed:
- memirq_err(memirq, "Failed to setup report pages in %s (%pe)\n",
- guc_name(guc), ERR_PTR(err));
+ xe_log_err(memirq_to_tile(memirq), MEMIRQ, err,
+ "Failed to setup report pages in %s\n", guc_name(guc));
return err;
}
@@ -555,6 +556,7 @@ void xe_memirq_handler(struct xe_memirq *memirq)
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
struct iosys_map map;
+ unsigned long flags;
unsigned int gtid;
struct xe_gt *gt;
@@ -568,8 +570,10 @@ void xe_memirq_handler(struct xe_memirq *memirq)
if (gt->tile != tile)
continue;
+ spin_lock_irqsave(&xe->irq.lock, flags);
for_each_hw_engine(hwe, gt, id)
xe_memirq_hwe_handler(memirq, hwe);
+ spin_unlock_irqrestore(&xe->irq.lock, flags);
}
/* GuC and media GuC (if present) must be checked separately */
diff --git a/drivers/gpu/drm/xe/xe_mmio.c b/drivers/gpu/drm/xe/xe_mmio.c
index 455a07d79cd9..2674146ced76 100644
--- a/drivers/gpu/drm/xe/xe_mmio.c
+++ b/drivers/gpu/drm/xe/xe_mmio.c
@@ -15,6 +15,7 @@
#include "regs/xe_bars.h"
#include "xe_device.h"
#include "xe_gt_sriov_vf.h"
+#include "xe_log.h"
#include "xe_printk.h"
#include "xe_sriov.h"
#include "xe_tile_printk.h"
@@ -71,8 +72,8 @@ int xe_mmio_probe_tiles(struct xe_device *xe)
return 0;
if (xe->mmio.size < xe->info.tile_count * tile_mmio_size) {
- xe_err(xe, "GTTMMADR_BAR is too small for %d tiles: %zu\n",
- xe->info.tile_count, xe->mmio.size);
+ xe_log_err_fatal(xe, REGS, -EIO, "GTTMMADR_BAR is too small for %d tiles: %zu\n",
+ xe->info.tile_count, xe->mmio.size);
return -EIO;
}
@@ -105,13 +106,16 @@ int xe_mmio_probe_early(struct xe_device *xe)
xe->mmio.regs = pcim_iomap(pdev, GTTMMADR_BAR, 0);
if (!xe->mmio.regs) {
- xe_err(xe, "Failed to map GTTMMADR_BAR\n");
+ xe_log_err_fatal(xe, REGS, -EIO,
+ "Failed to map GTTMMADR_BAR\n");
return -EIO;
}
xe->mmio.size = pci_resource_len(pdev, GTTMMADR_BAR);
if (xe->mmio.size < SZ_16M) {
- xe_err(xe, "GTTMMADR_BAR is too small: %zu\n", xe->mmio.size);
+ xe_log_err_fatal(xe, REGS, -EIO,
+ "GTTMMADR_BAR is too small: %zu\n",
+ xe->mmio.size);
return -EIO;
}
diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index 4bc28dfc1992..897724cb5cfb 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -12,7 +12,7 @@
#include <drm/drm_module.h>
#include "xe_defaults.h"
-#include "xe_device_types.h"
+#include "xe_device.h"
#include "xe_drv.h"
#include "xe_configfs.h"
#include "xe_hw_fence.h"
@@ -114,8 +114,10 @@ static void xe_destroy_wq_module_exit(void)
* 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
+ * The destroy workqueue has module lifetime, and is guaranteed to outlive
+ * any xe_device, and to be drained before the module is unloaded. It is used
+ * for GuC exec queue and xe_vm/xe_vma teardown that can be deferred past the
+ * lifetime of the xe_device that triggered it. SVM pagemap destroy uses the
* per-device xe->destroy_wq instead.
*
* Return: %true if @work was queued, %false if it was already pending.
@@ -162,6 +164,22 @@ static const struct init_funcs init_funcs[] = {
.init = xe_destroy_wq_module_init,
.exit = xe_destroy_wq_module_exit,
},
+ /*
+ * xe_destroy_wq_module_exit() must run after xe_device_exit()
+ * (below), since freeing a device can still queue work on
+ * xe_destroy_wq that must be drained before the module can safely
+ * unload. At the same time, xe_device_exit() must run after
+ * xe_unregister_pci_driver() (below), and xe_destroy_wq_module_exit()
+ * must run before xe_sched_job_module_exit() and
+ * xe_hw_fence_module_exit() (above), whose kmem_caches are still used
+ * by work drained from xe_destroy_wq. Exit functions run in reverse
+ * array order, so this entry must sit between the
+ * xe_destroy_wq_module_init entry above and the xe_register_pci_driver
+ * entry below.
+ */
+ {
+ .exit = xe_device_exit,
+ },
{
.init = xe_register_pci_driver,
.exit = xe_unregister_pci_driver,
diff --git a/drivers/gpu/drm/xe/xe_nvm.c b/drivers/gpu/drm/xe/xe_nvm.c
index 51a1a431ad85..783c3103a8a4 100644
--- a/drivers/gpu/drm/xe/xe_nvm.c
+++ b/drivers/gpu/drm/xe/xe_nvm.c
@@ -56,10 +56,11 @@ static bool xe_nvm_non_posted_erase(struct xe_device *xe)
}
}
-static bool xe_nvm_writable_override(struct xe_device *xe)
+static bool xe_nvm_writable_override(struct xe_device *xe, size_t *survivability_size)
{
struct xe_mmio *mmio = xe_root_tile_mmio(xe);
bool writable_override;
+ size_t s_size = 0;
struct xe_reg reg;
u32 test_bit, test_val;
@@ -68,6 +69,7 @@ static bool xe_nvm_writable_override(struct xe_device *xe)
reg = PCODE_SCRATCH(0);
test_bit = FDO_MODE;
test_val = FDO_MODE;
+ s_size = SZ_8M;
break;
case XE_BATTLEMAGE:
reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE);
@@ -91,12 +93,17 @@ static bool xe_nvm_writable_override(struct xe_device *xe)
break;
default:
drm_err(&xe->drm, "Unknown platform\n");
+ *survivability_size = 0;
return true;
}
writable_override = (xe_mmio_read32(mmio, reg) & test_bit) == test_val;
- if (writable_override)
+ if (writable_override) {
drm_info(&xe->drm, "NVM access overridden by jumper\n");
+ *survivability_size = s_size;
+ } else {
+ *survivability_size = 0;
+ }
return writable_override;
}
@@ -143,7 +150,7 @@ int xe_nvm_init(struct xe_device *xe)
if (!nvm)
return -ENOMEM;
- nvm->writable_override = xe_nvm_writable_override(xe);
+ nvm->writable_override = xe_nvm_writable_override(xe, &nvm->survivability_size);
nvm->non_posted_erase = xe_nvm_non_posted_erase(xe);
nvm->bar.parent = &pdev->resource[0];
nvm->bar.start = GEN12_GUNIT_NVM_BASE + pdev->resource[0].start;
diff --git a/drivers/gpu/drm/xe/xe_pat.c b/drivers/gpu/drm/xe/xe_pat.c
index a5fe1beec652..3dfcd364ac67 100644
--- a/drivers/gpu/drm/xe/xe_pat.c
+++ b/drivers/gpu/drm/xe/xe_pat.c
@@ -16,6 +16,7 @@
#include "xe_force_wake.h"
#include "xe_gt.h"
#include "xe_gt_mcr.h"
+#include "xe_log.h"
#include "xe_mmio.h"
#include "xe_sriov.h"
#include "xe_wa.h"
@@ -590,7 +591,13 @@ static const struct xe_pat_ops xe3p_xpc_pat_ops = {
.entry_dump = xe3p_xpc_pat_entry_dump,
};
-void xe_pat_init_early(struct xe_device *xe)
+/**
+ * xe_pat_init_early() - Init early pat table operation.
+ * @xe: the &xe_device
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int xe_pat_init_early(struct xe_device *xe)
{
xe->pat.idx[XE_CACHE_WB_COMPRESSION] = XE_PAT_INVALID_IDX;
xe->pat.idx[XE_CACHE_NONE_COMPRESSION] = XE_PAT_INVALID_IDX;
@@ -686,14 +693,18 @@ void xe_pat_init_early(struct xe_device *xe)
* raise an error rather than trying to silently inherit the
* most recent platform's behavior.
*/
- drm_err(&xe->drm, "Missing PAT table for platform with graphics version %d.%02d!\n",
- GRAPHICS_VER(xe), GRAPHICS_VERx100(xe) % 100);
+ xe_log_err(xe, PAT, -ENODATA,
+ "Missing PAT table for platform with graphics version %d.%02d!\n",
+ GRAPHICS_VER(xe), GRAPHICS_VERx100(xe) % 100);
+ return -ENODATA;
}
+ xe_assert(xe, xe->pat.ops);
xe_assert(xe, xe->pat.ops->dump);
xe_assert(xe, xe->pat.ops->program_graphics);
xe_assert(xe, MEDIA_VER(xe) < 13 || xe->pat.ops->program_media);
xe_assert(xe, GRAPHICS_VER(xe) < 20 || xe->pat.ops->entry_dump);
+ return 0;
}
void xe_pat_init(struct xe_gt *gt)
diff --git a/drivers/gpu/drm/xe/xe_pat.h b/drivers/gpu/drm/xe/xe_pat.h
index 7060f66e1d63..6398c3ab4805 100644
--- a/drivers/gpu/drm/xe/xe_pat.h
+++ b/drivers/gpu/drm/xe/xe_pat.h
@@ -42,8 +42,10 @@ struct xe_pat_table_entry {
/**
* xe_pat_init_early - SW initialization, setting up data based on device
* @xe: xe device
+ *
+ * Return: 0 on success or a negative error code on failure.
*/
-void xe_pat_init_early(struct xe_device *xe);
+int xe_pat_init_early(struct xe_device *xe);
/**
* xe_pat_init - Program HW PAT table
diff --git a/drivers/gpu/drm/xe/xe_pci.c b/drivers/gpu/drm/xe/xe_pci.c
index 50e4920e8e08..44587b5f4df6 100644
--- a/drivers/gpu/drm/xe/xe_pci.c
+++ b/drivers/gpu/drm/xe/xe_pci.c
@@ -417,6 +417,7 @@ static const struct xe_device_desc bmg_desc = {
.dma_mask_size = 46,
.has_display = true,
.has_fan_control = true,
+ .has_fixed_vram_channels = true,
.has_flat_ccs = 1,
.has_mbx_power_limits = true,
.has_mbx_thermal_info = true,
@@ -486,6 +487,7 @@ static const struct xe_device_desc cri_desc = {
.has_soc_remapper_telem = true,
.has_sriov = true,
.has_sysctrl = true,
+ .has_temp_sentinel = true,
.max_gt_per_tile = 2,
MULTI_LRC_MASK,
.require_force_probe = true,
@@ -721,8 +723,9 @@ static int handle_gmdid(struct xe_device *xe,
*graphics_ip = find_graphics_ip(ver);
if (!*graphics_ip) {
- drm_err(&xe->drm, "Hardware reports unknown graphics version %u.%02u\n",
- ver / 100, ver % 100);
+ xe_log_err(xe, REGS, -EINVAL,
+ "Hardware reports unknown graphics version %u.%02u\n",
+ ver / 100, ver % 100);
}
ret = read_gmdid(xe, GMDID_MEDIA, &ver, media_revid);
@@ -735,8 +738,9 @@ static int handle_gmdid(struct xe_device *xe,
*media_ip = find_media_ip(ver);
if (!*media_ip) {
- drm_err(&xe->drm, "Hardware reports unknown media version %u.%02u\n",
- ver / 100, ver % 100);
+ xe_log_err(xe, REGS, -EINVAL,
+ "Hardware reports unknown media version %u.%02u\n",
+ ver / 100, ver % 100);
}
return 0;
@@ -797,6 +801,7 @@ static int xe_info_init_early(struct xe_device *xe,
xe->info.has_device_uid = desc->has_device_uid;
xe->info.has_drm_ras = desc->has_drm_ras;
xe->info.has_fan_control = desc->has_fan_control;
+ xe->info.has_fixed_vram_channels = desc->has_fixed_vram_channels;
/* runtime fusing may force flat_ccs to disabled later */
xe->info.has_flat_ccs = desc->has_flat_ccs;
xe->info.has_mbx_power_limits = desc->has_mbx_power_limits;
@@ -817,6 +822,7 @@ static int xe_info_init_early(struct xe_device *xe,
xe->info.has_sriov = xe_configfs_primary_gt_allowed(to_pci_dev(xe->drm.dev)) &&
desc->has_sriov;
xe->info.has_sysctrl = desc->has_sysctrl;
+ xe->info.has_temp_sentinel = desc->has_temp_sentinel;
xe->info.skip_guc_pc = desc->skip_guc_pc;
xe->info.skip_pcode = desc->skip_pcode;
xe->info.needs_scratch = desc->needs_scratch;
diff --git a/drivers/gpu/drm/xe/xe_pci_types.h b/drivers/gpu/drm/xe/xe_pci_types.h
index 448bbe5ba1d7..ce0b0f6c5b18 100644
--- a/drivers/gpu/drm/xe/xe_pci_types.h
+++ b/drivers/gpu/drm/xe/xe_pci_types.h
@@ -43,6 +43,7 @@ struct xe_device_desc {
u8 has_device_uid:1;
u8 has_drm_ras:1;
u8 has_fan_control:1;
+ u8 has_fixed_vram_channels:1;
u8 has_flat_ccs:1;
u8 has_gsc_nvm:1;
u8 has_heci_gscfi:1;
@@ -61,6 +62,7 @@ struct xe_device_desc {
u8 has_soc_remapper_telem:1;
u8 has_sriov:1;
u8 has_sysctrl:1;
+ u8 has_temp_sentinel:1;
u8 needs_scratch:1;
u8 skip_guc_pc:1;
u8 skip_pcode:1;
diff --git a/drivers/gpu/drm/xe/xe_pcode_api.h b/drivers/gpu/drm/xe/xe_pcode_api.h
index 94575c476e3d..00c4fc7d0124 100644
--- a/drivers/gpu/drm/xe/xe_pcode_api.h
+++ b/drivers/gpu/drm/xe/xe_pcode_api.h
@@ -58,8 +58,6 @@
#define READ_THERMAL_LIMITS 0x0
#define READ_THERMAL_CONFIG 0x1
#define READ_THERMAL_DATA 0x2
-#define PCIE_SENSOR_GROUP_ID 0x2
-#define PCIE_SENSOR_MASK REG_GENMASK(31, 16)
#define PCODE_LATE_BINDING 0x5C
#define GET_CAPABILITY_STATUS 0x0
diff --git a/drivers/gpu/drm/xe/xe_ras.c b/drivers/gpu/drm/xe/xe_ras.c
index 7a85735c57d5..9c15ce8f0b6a 100644
--- a/drivers/gpu/drm/xe/xe_ras.c
+++ b/drivers/gpu/drm/xe/xe_ras.c
@@ -3,6 +3,8 @@
* Copyright © 2026 Intel Corporation
*/
+#include "xe_assert.h"
+#include "xe_bo.h"
#include "xe_configfs.h"
#include "xe_debugfs.h"
#include "xe_device.h"
@@ -16,6 +18,10 @@
#include "xe_sysctrl_event_types.h"
#include "xe_sysctrl_mailbox.h"
#include "xe_sysctrl_mailbox_types.h"
+#include "xe_ttm_vram_mgr.h"
+
+/* Any non-null entry marks the page as offlined by firmware */
+#define XE_RAS_PAGE_OFFLINED xa_mk_value(1)
#define CORE_COMPUTE_UNCORR_TYPE GENMASK(26, 25)
/*
@@ -201,6 +207,142 @@ static inline const char *comp_to_str(u8 component)
return xe_ras_components[component];
}
+static int send_page_offline_cmd(struct xe_device *xe, u64 page_address,
+ enum xe_ras_page_action action)
+{
+ struct xe_sysctrl_mailbox_command command = {0};
+ struct xe_ras_page_offline_request request = {0};
+ struct xe_ras_page_offline_response response = {0};
+ struct xe_ras_state *state = &xe->ras.state;
+ size_t rlen;
+ int ret;
+
+ if (!xe->info.has_sysctrl)
+ return 0;
+
+ xe_assert(xe, action < XE_RAS_PAGE_ACTION_MAX);
+
+ request.page_address = page_address;
+ request.action = action;
+
+ if (action == XE_RAS_PAGE_ACTION_OFFLINE)
+ xe_log_err(xe, DEVICE_MEMORY, 0, "Requesting firmware to offline page 0x%llx\n",
+ page_address);
+ else
+ xe_log_err(xe, DEVICE_MEMORY, 0, "Requesting firmware to remove page 0x%llx from queue\n",
+ page_address);
+
+ xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_PAGE_OFFLINE,
+ &request, sizeof(request), &response, sizeof(response));
+
+ ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen);
+ if (ret) {
+ xe_log_err(xe, SYSCTRL, ret, "failed to send page offline command\n");
+ return ret;
+ }
+
+ if (rlen != sizeof(response)) {
+ xe_log_err(xe, SYSCTRL, -EINVAL,
+ "unexpected page offline response length %zu (expected %zu)\n",
+ rlen, sizeof(response));
+ return -EINVAL;
+ }
+
+ ret = ras_status_to_errno(response.status);
+ if (ret) {
+ xe_log_err(xe, SYSCTRL, ret, "page offline command failed with status %u\n",
+ response.status);
+ return ret;
+ }
+
+ if (action == XE_RAS_PAGE_ACTION_OFFLINE) {
+ ret = xa_err(xa_store(&state->offlined_pages,
+ page_address >> XE_PTE_SHIFT,
+ XE_RAS_PAGE_OFFLINED, GFP_KERNEL));
+ if (ret)
+ xe_err(xe, "Failed to store offlined page 0x%llx: %d\n", page_address,
+ ret);
+ }
+
+ return 0;
+}
+
+static int handle_page_offline(struct xe_device *xe, u64 page_address, bool notify_fw)
+{
+ struct xe_ras_state *state = &xe->ras.state;
+ enum xe_ras_page_action action;
+ u64 addr;
+ int ret;
+
+ if (!IS_ALIGNED(page_address, XE_PAGE_SIZE)) {
+ xe_log_err(xe, SYSCTRL, -EINVAL, "Unaligned physical page address: 0x%llx\n",
+ page_address);
+ return -EINVAL;
+ }
+
+ /* User policy set to disable page offlining */
+ if (xe_ras_get_disable_vram_page_offline(xe)) {
+ action = XE_RAS_PAGE_ACTION_REMOVE;
+ xe_log_err(xe, DEVICE_MEMORY, -EOPNOTSUPP, "User policy set to disable page offlining\n");
+ goto notify;
+ }
+
+ addr = ALIGN_DOWN(page_address, PAGE_SIZE);
+
+ ret = xe_ttm_vram_handle_addr_fault(xe, addr);
+
+ /*
+ * Handle return code from address fault handling function:
+ * 0: Page softofflined, remove from firmware queue
+ * -EIO: Address belongs to a critical BO/stolen area that cannot be offlined
+ * -ENOMEM: Failed to allocate BO for soft-offlining the page
+ * -EEXIST: Address is soft offlined but yet to be offlined by firmware for second
+ * occurrence
+ */
+
+ switch (ret) {
+ case 0:
+ action = XE_RAS_PAGE_ACTION_REMOVE;
+ xe_log_err(xe, DEVICE_MEMORY, ret,
+ "Poison detected at physical address 0x%llx, page soft-offlined\n",
+ page_address);
+ break;
+ case -EIO:
+ xe_log_err(xe, DEVICE_MEMORY, ret,
+ "Poison detected at physical address 0x%llx, page belongs to critical BO and cannot be soft-offlined\n",
+ page_address);
+ return ret;
+ case -EEXIST:
+ if (xa_load(&state->offlined_pages, page_address >> XE_PTE_SHIFT))
+ action = XE_RAS_PAGE_ACTION_REMOVE;
+ else
+ action = XE_RAS_PAGE_ACTION_OFFLINE;
+
+ xe_log_err(xe, DEVICE_MEMORY, ret,
+ "Double-bit ECC error detected at physical address 0x%llx, page soft-offlined\n",
+ page_address);
+ break;
+ case -ENOMEM:
+ xe_log_err(xe, DEVICE_MEMORY, ret,
+ "Poison detected at physical address 0x%llx, failed to soft-offline\n",
+ page_address);
+ return ret;
+ default:
+ xe_log_err(xe, DEVICE_MEMORY, ret, "Failed to handle address fault at physical address 0x%llx\n",
+ page_address);
+ return 0;
+ }
+
+ notify:
+ if (notify_fw) {
+ ret = send_page_offline_cmd(xe, page_address, action);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static bool ras_counter_is_valid(struct xe_device *xe, struct xe_ras_error_class *counter)
{
u8 severity = counter->common.severity;
@@ -219,6 +361,106 @@ static bool ras_counter_is_valid(struct xe_device *xe, struct xe_ras_error_class
return true;
}
+/*
+ * Fetch and process offline list and queue from firmware via mailbox. @notify_fw controls
+ * whether firmware is notified of the offline/remove action.
+ */
+static void process_offline_pages(struct xe_device *xe, u32 cmd, void *req, size_t req_size,
+ void *resp, size_t resp_size,
+ struct xe_ras_offline_common *common, bool notify_fw)
+{
+ struct xe_sysctrl_mailbox_command command = {0};
+ struct xe_ras_offline_list_request *list_req;
+ struct xe_ras_state *state = &xe->ras.state;
+ unsigned long index;
+ u32 total_pages = 0, count = 0;
+ size_t rlen;
+ int ret, i;
+
+ list_req = req;
+
+ xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, cmd, req, req_size, resp,
+ resp_size);
+
+ do {
+ memset(resp, 0, resp_size);
+
+ if (list_req)
+ list_req->index = count;
+
+ ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen);
+ if (ret) {
+ xe_log_err(xe, SYSCTRL, ret, "failed to get page offline data, cmd=%#x\n",
+ cmd);
+ return;
+ }
+
+ if (rlen != resp_size) {
+ xe_log_err(xe, SYSCTRL, -EINVAL,
+ "unexpected page offline response length %zu (expected %zu), cmd=%#x\n",
+ rlen, resp_size, cmd);
+ return;
+ }
+
+ /* Populate the maximum number of pages from offline list */
+ if (list_req && !state->max_pages)
+ state->max_pages =
+ ((struct xe_ras_offline_list_response *)resp)->max_entries;
+
+ for (i = 0; i < common->pages_returned && i < XE_RAS_NUM_PAGES; i++) {
+ handle_page_offline(xe, common->page_addresses[i], notify_fw);
+ /* The pages are already offlined by firmware */
+ if (!notify_fw) {
+ index = common->page_addresses[i] >> XE_PTE_SHIFT;
+ ret = xa_err(xa_store(&state->offlined_pages, index,
+ XE_RAS_PAGE_OFFLINED, GFP_KERNEL));
+ if (ret)
+ xe_err(xe, "Failed to store offlined page 0x%llx: %d\n",
+ common->page_addresses[i], ret);
+ }
+ }
+
+ count += common->pages_returned;
+ if (!common->pages_returned)
+ break;
+
+ if (!total_pages)
+ total_pages = common->total_pages;
+
+ if (count > total_pages) {
+ xe_log_err(xe, SYSCTRL, -EINVAL,
+ "Pages returned exceed total pages %u, returned %u, cmd=%#x\n",
+ total_pages, count, cmd);
+ return;
+ }
+ } while (common->additional_data);
+}
+
+/*
+ * Retrieve firmware queued pages from warm-retained SRAM during probe. Handle and soft-offline
+ * each page as required.
+ */
+static void get_offline_queue(struct xe_device *xe)
+{
+ struct xe_ras_offline_common response = {0};
+
+ process_offline_pages(xe, XE_SYSCTRL_CMD_GET_OFFLINE_QUEUE, NULL, 0, &response,
+ sizeof(response), &response, true);
+}
+
+/*
+ * Retrieve permanently offlined pages from firmware's non-volatile storage during probe,
+ * allowing the driver to soft-offline them before kernel allocations begin.
+ */
+static void get_offline_list(struct xe_device *xe)
+{
+ struct xe_ras_offline_list_response response = {0};
+ struct xe_ras_offline_list_request request = {0};
+
+ process_offline_pages(xe, XE_SYSCTRL_CMD_GET_OFFLINE_LIST, &request, sizeof(request),
+ &response, sizeof(response), &response.common, false);
+}
+
static struct pci_dev *find_usp_dev(struct pci_dev *pdev)
{
struct pci_dev *vsp;
@@ -368,11 +610,12 @@ static u8 handle_soc_internal_errors(struct xe_device *xe, struct xe_ras_error_a
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;
+ int ret;
/*
* For memory errors, the recovery action depends on the error category
*
- * TODO: Double-bit ECC errors: Page offlining
+ * Double-bit ECC errors: Page offlining
* Poison and data parity errors: Log only
* For any other memory errors, request a reset as recovery mechanism
*/
@@ -384,10 +627,10 @@ static u8 handle_device_memory_errors(struct xe_device *xe, struct xe_ras_error_
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;
+ ret = handle_page_offline(xe, info->sw_address, true);
+ if (ret)
+ return XE_RAS_RECOVERY_ACTION_RESET;
+ break;
default:
return XE_RAS_RECOVERY_ACTION_RESET;
}
@@ -906,33 +1149,72 @@ static const struct attribute_group gpu_health_group = {
.attrs = gpu_health_attrs,
};
+static void ras_fini(void *arg)
+{
+ struct xe_device *xe = arg;
+
+ xa_destroy(&xe->ras.state.offlined_pages);
+}
+
+/**
+ * xe_ras_get_disable_vram_page_offline - Get the vram page offline user policy
+ * @xe: xe device instance
+ *
+ * Return: true if the vram page offline policy is disabled, false otherwise.
+ */
+bool xe_ras_get_disable_vram_page_offline(struct xe_device *xe)
+{
+ return xe->ras.state.disable_vram_page_offline;
+}
+
+/**
+ * xe_ras_get_max_pages - Get the maximum number of pages
+ * @xe: xe device instance
+ *
+ * Return: Maximum number of pages stored by the firmware in flash
+ */
+u32 xe_ras_get_max_pages(struct xe_device *xe)
+{
+ return xe->ras.state.max_pages;
+}
+
/**
* xe_ras_init - Initialize Xe RAS
* @xe: xe device instance
*
* Initialize Xe RAS
+ *
+ * Return: 0 on success, negative error code otherwise
*/
-void xe_ras_init(struct xe_device *xe)
+int xe_ras_init(struct xe_device *xe)
{
+ struct xe_ras_state *state = &xe->ras.state;
int ret;
- /*
- * TODO: Replace platform check with xe->info.has_disable_vram_page_offline
- * once the feature flag is plumbed through device info.
- */
- if (xe->info.platform == XE_CRESCENTISLAND)
- xe->ras.disable_vram_page_offline =
- xe_configfs_get_disable_vram_page_offline(to_pci_dev(xe->drm.dev));
-
xe_drm_ras_init(xe);
if (!xe->info.has_sysctrl)
- return;
+ return 0;
+
+ /*
+ * TODO: Replace with xe->info.has_disable_vram_page_offline once the feature flag is
+ * plumbed through device info.
+ */
+ xe->ras.state.disable_vram_page_offline =
+ xe_configfs_get_disable_vram_page_offline(to_pci_dev(xe->drm.dev));
if (IS_ENABLED(CONFIG_PCIEAER))
ras_usp_aer_init(xe);
+ xa_init(&state->offlined_pages);
+
+ /* Fetch the offline list before queue to avoid duplication */
+ get_offline_list(xe);
+ get_offline_queue(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);
+
+ return devm_add_action_or_reset(xe->drm.dev, ras_fini, xe);
}
diff --git a/drivers/gpu/drm/xe/xe_ras.h b/drivers/gpu/drm/xe/xe_ras.h
index 0b8669f28d56..1b358c7d040f 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 <linux/types.h>
+
#include "xe_ras_types.h"
struct xe_device;
@@ -18,7 +19,9 @@ int xe_ras_get_counter(struct xe_device *xe, u8 severity, u8 component, u32 *val
int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component);
int xe_ras_get_threshold(struct xe_device *xe, u8 severity, u8 component, u32 *threshold);
int xe_ras_set_threshold(struct xe_device *xe, u8 severity, u8 component, u32 threshold);
-void xe_ras_init(struct xe_device *xe);
+int xe_ras_init(struct xe_device *xe);
enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe);
+bool xe_ras_get_disable_vram_page_offline(struct xe_device *xe);
+u32 xe_ras_get_max_pages(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 fe6f3658a2a4..a86f9d2b8c92 100644
--- a/drivers/gpu/drm/xe/xe_ras_types.h
+++ b/drivers/gpu/drm/xe/xe_ras_types.h
@@ -7,9 +7,11 @@
#define _XE_RAS_TYPES_H_
#include <linux/types.h>
+#include <linux/xarray.h>
#define XE_RAS_NUM_COUNTERS 16
#define XE_RAS_NUM_ERROR_ARR 3
+#define XE_RAS_NUM_PAGES 25
/* Error bits in IEH global error status register */
#define XE_RAS_SOC_IEH_PUNIT BIT(1)
/* Device memory error categories */
@@ -18,6 +20,19 @@
#define XE_RAS_MEMORY_DATA_PARITY BIT(5)
/**
+ * enum xe_ras_page_action - Page offline actions for page offline request
+ *
+ * @XE_RAS_PAGE_ACTION_OFFLINE: Instruct firmware to offline the page
+ * @XE_RAS_PAGE_ACTION_REMOVE: Instruct firmware to remove the page from queue
+ * @XE_RAS_PAGE_ACTION_MAX: Max value
+ */
+enum xe_ras_page_action {
+ XE_RAS_PAGE_ACTION_OFFLINE,
+ XE_RAS_PAGE_ACTION_REMOVE,
+ XE_RAS_PAGE_ACTION_MAX
+};
+
+/**
* enum xe_ras_recovery_action - RAS recovery actions
*
* @XE_RAS_RECOVERY_ACTION_RECOVERED: Error recovered
@@ -296,6 +311,62 @@ struct xe_ras_memory_error {
} __packed;
/**
+ * struct xe_ras_page_offline_request - Request for page offline command
+ */
+struct xe_ras_page_offline_request {
+ /** @page_address: Page address (4KB aligned) */
+ u64 page_address;
+ /** @action: Action to be performed, see &enum xe_ras_page_action */
+ u32 action;
+ /** @reserved: Reserved for future use */
+ u32 reserved;
+} __packed;
+
+/**
+ * struct xe_ras_page_offline_response - Response from page offline command
+ */
+struct xe_ras_page_offline_response {
+ /** @status: Status of the page offline request */
+ u32 status;
+ /** @reserved: Reserved for future use */
+ u32 reserved;
+} __packed;
+
+/**
+ * struct xe_ras_offline_common - Common structure for offline list and queue
+ */
+struct xe_ras_offline_common {
+ /** @total_pages: Total number of queued pages */
+ u32 total_pages;
+ /** @pages_returned: Number of pages returned in this response */
+ u32 pages_returned;
+ /** @page_addresses: Array of page addresses (4KB aligned) */
+ u64 page_addresses[XE_RAS_NUM_PAGES];
+ /** @additional_data: Indicates if more data is available */
+ u8 additional_data;
+ /** @reserved: Reserved for future use */
+ u8 reserved[3];
+} __packed;
+
+/**
+ * struct xe_ras_offline_list_request - Request for get offline list command
+ */
+struct xe_ras_offline_list_request {
+ /** @index: Zero-based index into the offline page list */
+ u32 index;
+} __packed;
+
+/**
+ * struct xe_ras_offline_list_response - Response from get offline list command
+ */
+struct xe_ras_offline_list_response {
+ /** @max_entries: Total no of pages that can be stored in flash */
+ u32 max_entries;
+ /** @common: Common offline page information */
+ struct xe_ras_offline_common common;
+} __packed;
+
+/**
* struct xe_ras_get_health_request - Request structure for obtaining gpu health
*/
struct xe_ras_get_health_request {
@@ -336,4 +407,18 @@ struct xe_ras_set_health_response {
/** @reserved1: Reserved for future use */
u32 reserved1[2];
} __packed;
+
+/* Device structures */
+
+/**
+ * struct xe_ras_state - RAS device and firmware state
+ */
+struct xe_ras_state {
+ /** @disable_vram_page_offline: cached configfs policy, immutable after init */
+ bool disable_vram_page_offline;
+ /** @max_pages: Total number of pages that can be stored by firmware */
+ u32 max_pages;
+ /** @offlined_pages: XArray of pages offlined by firmware */
+ struct xarray offlined_pages;
+};
#endif
diff --git a/drivers/gpu/drm/xe/xe_reg_sr.c b/drivers/gpu/drm/xe/xe_reg_sr.c
index e328f5072557..eab84a528d12 100644
--- a/drivers/gpu/drm/xe/xe_reg_sr.c
+++ b/drivers/gpu/drm/xe/xe_reg_sr.c
@@ -21,6 +21,7 @@
#include "xe_gt_printk.h"
#include "xe_gt_types.h"
#include "xe_hw_engine_types.h"
+#include "xe_log.h"
#include "xe_lrc.h"
#include "xe_mmio.h"
#include "xe_rtp_types.h"
@@ -87,15 +88,15 @@ static struct xe_reg sanitize_mcr(struct xe_reg_sr *sr,
if (is_mcr && !reg.mcr) {
reg.mcr = 1;
- xe_gt_notice(gt, "xe_reg_sr_entry using non-MCR register for address 0x%x, forcing MCR\n",
- reg.addr);
+ xe_log_info(gt, RTP, "xe_reg_sr_entry using non-MCR register for address 0x%x, forcing MCR\n",
+ reg.addr);
reg_sr_inc_error(sr);
}
if (!is_mcr && reg.mcr) {
reg.mcr = 0;
- xe_gt_notice(gt, "xe_reg_sr_entry using MCR register for address 0x%x, forcing non-MCR\n",
- reg.addr);
+ xe_log_info(gt, RTP, "xe_reg_sr_entry using MCR register for address 0x%x, forcing non-MCR\n",
+ reg.addr);
reg_sr_inc_error(sr);
}
@@ -219,7 +220,7 @@ void xe_reg_sr_apply_mmio(struct xe_reg_sr *sr, struct xe_gt *gt)
CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), XE_FORCEWAKE_ALL);
if (!xe_force_wake_ref_has_domain(fw_ref.domains, XE_FORCEWAKE_ALL)) {
- xe_gt_err(gt, "Failed to apply, err=-ETIMEDOUT\n");
+ xe_log_err(gt, REGS, -ETIMEDOUT, "Failed to apply\n");
return;
}
diff --git a/drivers/gpu/drm/xe/xe_shrinker.c b/drivers/gpu/drm/xe/xe_shrinker.c
index deb4378c1ec1..772dab9b3ed7 100644
--- a/drivers/gpu/drm/xe/xe_shrinker.c
+++ b/drivers/gpu/drm/xe/xe_shrinker.c
@@ -14,13 +14,30 @@
#include "xe_pm.h"
#include "xe_shrinker.h"
+/*
+ * Reclaiming non-purgeable objects requires GPU idling and writeback to a
+ * backup store, and restoring them requires reading the contents back.
+ * Tell the shrinker core they're costlier to recreate than purgeable
+ * objects, which use DEFAULT_SEEKS, so that purgeable objects are
+ * preferentially reclaimed. The default is
+ * CONFIG_DRM_XE_SHRINKER_NON_PURGEABLE_SEEKS, overridable through sysfs.
+ * Zero is disallowed, since the shrinker core treats it specially and
+ * reads the value unlocked.
+ */
+#define XE_SHRINKER_NON_PURGEABLE_SEEKS_MIN 1
+#define XE_SHRINKER_NON_PURGEABLE_SEEKS_MAX 64
+
/**
* struct xe_shrinker - per-device shrinker
* @xe: Back pointer to the device.
* @lock: Lock protecting accounting.
* @shrinkable_pages: Number of pages that are currently shrinkable.
* @purgeable_pages: Number of pages that are currently purgeable.
- * @shrink: Pointer to the mm shrinker.
+ * @populated_tts: Number of populated ttm_tts currently shrinkable or purgeable.
+ * @batch_avg: Exponential moving average of the preferred non-purgeable
+ * batch size, based on the average populated ttm_tt size.
+ * @shrink: Pointer to the mm shrinker handling non-purgeable (shrinkable) objects.
+ * @purge_shrink: Pointer to the mm shrinker handling purgeable objects.
* @pm_worker: Worker to wake up the device if required.
*/
struct xe_shrinker {
@@ -28,7 +45,10 @@ struct xe_shrinker {
rwlock_t lock;
long shrinkable_pages;
long purgeable_pages;
+ long populated_tts;
+ long batch_avg;
struct shrinker *shrink;
+ struct shrinker *purge_shrink;
struct work_struct pm_worker;
};
@@ -38,19 +58,23 @@ static struct xe_shrinker *to_xe_shrinker(struct shrinker *shrink)
}
/**
- * xe_shrinker_mod_pages() - Modify shrinker page accounting
+ * xe_shrinker_mod_pages() - Modify shrinker page and object accounting
* @shrinker: Pointer to the struct xe_shrinker.
* @shrinkable: Shrinkable pages delta. May be negative.
- * @purgeable: Purgeable page delta. May be negative.
+ * @purgeable: Purgeable pages delta. May be negative.
+ * @tts: Populated ttm_tt count delta. May be negative.
*
- * Modifies the shrinkable and purgeable pages accounting.
+ * Updates the shrinkable and purgeable page counts and the populated
+ * ttm_tt count.
*/
void
-xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgeable)
+xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgeable,
+ long tts)
{
write_lock(&shrinker->lock);
shrinker->shrinkable_pages += shrinkable;
shrinker->purgeable_pages += purgeable;
+ shrinker->populated_tts += tts;
write_unlock(&shrinker->lock);
}
@@ -113,6 +137,9 @@ static int __xe_shrinker_walk(struct xe_shrinker *shrinker,
continue;
lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned);
+ /* Like the TTM LRU walk, skip bos we can't shrink now. */
+ if (lret == -EBUSY)
+ continue;
if (lret < 0) {
ret = lret;
goto out;
@@ -171,22 +198,67 @@ static int xe_shrinker_walk(struct xe_shrinker *shrinker,
}
static unsigned long
+xe_shrinker_count_purgeable(struct shrinker *shrink, struct shrink_control *sc)
+{
+ struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
+ unsigned long num_pages;
+
+ read_lock(&shrinker->lock);
+ num_pages = shrinker->purgeable_pages;
+ read_unlock(&shrinker->lock);
+
+ return num_pages ? num_pages : SHRINK_EMPTY;
+}
+
+/**
+ * DOC: Batch size calculation
+ *
+ * Update our preferred vmscan batch size for the non-purgeable shrinker
+ * on count passes that allow backup (__GFP_FS), to avoid invoking
+ * scan_objects() for requests too small to free even a single
+ * average-sized GEM object. Our rough estimate for an effective batch
+ * is twice the average number of pages per populated ttm_tt across
+ * the full shrinkable and purgeable population. The factor of two
+ * provides headroom so that most scan invocations can free at least one
+ * GEM object despite variability in object sizes.
+ *
+ * The batch value is updated as an exponential moving average,
+ * (old_batch + avg) / 2, to smooth out sudden changes in the object
+ * population. It is floored at 128 pages, the kernel default
+ * SHRINK_BATCH, to ensure the shrinker remains responsive when there
+ * are very few objects.
+ *
+ * We only do this for the non-purgeable shrinker: purgeable objects are
+ * reclaimed without writeback or GPU idling, so shrinking them is cheap
+ * enough that the core default batch size is fine.
+ */
+static unsigned long
xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
{
struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
unsigned long num_pages;
bool can_backup = !!(sc->gfp_mask & __GFP_FS);
+ if (!can_backup)
+ return SHRINK_EMPTY;
+
num_pages = ttm_backup_bytes_avail() >> PAGE_SHIFT;
- read_lock(&shrinker->lock);
+ /* Consistently use write_lock() for simplicity. */
+ write_lock(&shrinker->lock);
- if (can_backup)
- num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages);
- else
- num_pages = 0;
+ num_pages = min_t(unsigned long, num_pages, shrinker->shrinkable_pages);
- num_pages += shrinker->purgeable_pages;
- read_unlock(&shrinker->lock);
+ if (shrinker->populated_tts) {
+ unsigned long avg = 2 * (shrinker->shrinkable_pages +
+ shrinker->purgeable_pages) /
+ shrinker->populated_tts;
+
+ shrinker->batch_avg = max((shrinker->batch_avg + (long)avg) >> 1,
+ 128L /* default SHRINK_BATCH */);
+ WRITE_ONCE(shrink->batch, shrinker->batch_avg);
+ }
+
+ write_unlock(&shrinker->lock);
return num_pages ? num_pages : SHRINK_EMPTY;
}
@@ -198,8 +270,7 @@ xe_shrinker_count(struct shrinker *shrink, struct shrink_control *sc)
* If bo shrinking needs an rpm reference and we don't have it (yet),
* that bo will be skipped anyway.
*/
-static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force,
- unsigned long nr_to_scan, bool can_backup)
+static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker)
{
struct xe_device *xe = shrinker->xe;
@@ -207,63 +278,52 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force,
!ttm_backup_bytes_avail())
return false;
- if (!force) {
- read_lock(&shrinker->lock);
- force = (nr_to_scan > shrinker->purgeable_pages && can_backup);
- read_unlock(&shrinker->lock);
- if (!force)
- return false;
- }
-
return __xe_shrinker_runtime_pm_get(shrinker);
}
-static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
+static unsigned long xe_shrinker_scan_purgeable(struct shrinker *shrink,
+ struct shrink_control *sc)
{
struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
struct ttm_operation_ctx ctx = {
.interruptible = false,
.no_wait_gpu = ttm_bo_shrink_avoid_wait(),
};
- unsigned long nr_to_scan, nr_scanned = 0, freed = 0;
+ unsigned long nr_to_scan = sc->nr_to_scan, nr_scanned = 0, freed = 0;
struct xe_bo_shrink_flags shrink_flags = {
.purge = true,
- /* Don't request writeback without __GFP_IO. */
- .writeback = !ctx.no_wait_gpu && (sc->gfp_mask & __GFP_IO),
};
- bool runtime_pm;
- bool purgeable;
- bool can_backup = !!(sc->gfp_mask & __GFP_FS);
-
- nr_to_scan = sc->nr_to_scan;
- read_lock(&shrinker->lock);
- purgeable = !!shrinker->purgeable_pages;
- read_unlock(&shrinker->lock);
-
- /* Might need runtime PM. Try to wake early if it looks like it. */
- runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup);
-
- if (purgeable && nr_scanned < nr_to_scan)
- xe_shrinker_walk(shrinker, &ctx, shrink_flags,
- nr_to_scan, &nr_scanned, &freed);
+ xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed);
sc->nr_scanned = nr_scanned;
- if (nr_scanned >= nr_to_scan || !can_backup)
- goto out;
- /* If we didn't wake before, try to do it now if needed. */
- if (!runtime_pm)
- runtime_pm = xe_shrinker_runtime_pm_get(shrinker, true, 0, can_backup);
+ return nr_scanned ? freed : SHRINK_STOP;
+}
+
+static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
+{
+ struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
+ struct ttm_operation_ctx ctx = {
+ .interruptible = false,
+ .no_wait_gpu = ttm_bo_shrink_avoid_wait(),
+ };
+ unsigned long nr_to_scan = sc->nr_to_scan, nr_scanned = 0, freed = 0;
+ struct xe_bo_shrink_flags shrink_flags = {
+ .purge = false,
+ /* Don't request writeback without __GFP_IO. */
+ .writeback = !ctx.no_wait_gpu && (sc->gfp_mask & __GFP_IO),
+ };
+ bool runtime_pm;
- shrink_flags.purge = false;
+ /* Might need runtime PM to access backed-up or CCS-compressed data. */
+ runtime_pm = xe_shrinker_runtime_pm_get(shrinker);
- xe_shrinker_walk(shrinker, &ctx, shrink_flags,
- nr_to_scan, &nr_scanned, &freed);
+ xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed);
sc->nr_scanned = nr_scanned;
-out:
xe_shrinker_runtime_pm_put(shrinker, runtime_pm);
+
return nr_scanned ? freed : SHRINK_STOP;
}
@@ -283,12 +343,48 @@ static void xe_shrinker_fini(struct drm_device *drm, void *arg)
xe_assert(shrinker->xe, !shrinker->shrinkable_pages);
xe_assert(shrinker->xe, !shrinker->purgeable_pages);
+ xe_assert(shrinker->xe, !shrinker->populated_tts);
shrinker_free(shrinker->shrink);
+ shrinker_free(shrinker->purge_shrink);
flush_work(&shrinker->pm_worker);
kfree(shrinker);
}
/**
+ * xe_shrinker_non_purgeable_seeks() - Get the non-purgeable shrinker seeks
+ * @shrinker: Pointer to the struct xe_shrinker.
+ *
+ * Return: The seeks value of the non-purgeable shrinker.
+ */
+int xe_shrinker_non_purgeable_seeks(struct xe_shrinker *shrinker)
+{
+ return READ_ONCE(shrinker->shrink->seeks);
+}
+
+/**
+ * xe_shrinker_set_non_purgeable_seeks() - Set the non-purgeable shrinker seeks
+ * @shrinker: Pointer to the struct xe_shrinker.
+ * @seeks: The new seeks value.
+ *
+ * Set the cost of recreating non-purgeable objects relative to purgeable
+ * objects, which use DEFAULT_SEEKS. Higher values reduce reclaim pressure
+ * on non-purgeable objects.
+ *
+ * Return: %0 on success, -EINVAL if @seeks is out of range.
+ */
+int xe_shrinker_set_non_purgeable_seeks(struct xe_shrinker *shrinker,
+ unsigned int seeks)
+{
+ if (seeks < XE_SHRINKER_NON_PURGEABLE_SEEKS_MIN ||
+ seeks > XE_SHRINKER_NON_PURGEABLE_SEEKS_MAX)
+ return -EINVAL;
+
+ WRITE_ONCE(shrinker->shrink->seeks, seeks);
+
+ return 0;
+}
+
+/**
* xe_shrinker_create() - Create an xe per-device shrinker
* @xe: Pointer to the xe device.
*
@@ -301,8 +397,15 @@ int xe_shrinker_create(struct xe_device *xe)
if (!shrinker)
return -ENOMEM;
- shrinker->shrink = shrinker_alloc(0, "drm-xe_gem:%s", xe->drm.unique);
+ shrinker->purge_shrink = shrinker_alloc(0, "drm-xe_gem-purgeable:%s", xe->drm.unique);
+ if (!shrinker->purge_shrink) {
+ kfree(shrinker);
+ return -ENOMEM;
+ }
+
+ shrinker->shrink = shrinker_alloc(0, "drm-xe_gem-shrinkable:%s", xe->drm.unique);
if (!shrinker->shrink) {
+ shrinker_free(shrinker->purge_shrink);
kfree(shrinker);
return -ENOMEM;
}
@@ -310,10 +413,18 @@ int xe_shrinker_create(struct xe_device *xe)
INIT_WORK(&shrinker->pm_worker, xe_shrinker_pm);
shrinker->xe = xe;
rwlock_init(&shrinker->lock);
+
+ shrinker->purge_shrink->count_objects = xe_shrinker_count_purgeable;
+ shrinker->purge_shrink->scan_objects = xe_shrinker_scan_purgeable;
+ shrinker->purge_shrink->private_data = shrinker;
+ shrinker_register(shrinker->purge_shrink);
+
shrinker->shrink->count_objects = xe_shrinker_count;
shrinker->shrink->scan_objects = xe_shrinker_scan;
shrinker->shrink->private_data = shrinker;
+ shrinker->shrink->seeks = CONFIG_DRM_XE_SHRINKER_NON_PURGEABLE_SEEKS;
shrinker_register(shrinker->shrink);
+
xe->mem.shrinker = shrinker;
return drmm_add_action_or_reset(&xe->drm, xe_shrinker_fini, shrinker);
diff --git a/drivers/gpu/drm/xe/xe_shrinker.h b/drivers/gpu/drm/xe/xe_shrinker.h
index 5132ae5192e1..4b3c73689619 100644
--- a/drivers/gpu/drm/xe/xe_shrinker.h
+++ b/drivers/gpu/drm/xe/xe_shrinker.h
@@ -9,8 +9,13 @@
struct xe_shrinker;
struct xe_device;
-void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgeable);
+void xe_shrinker_mod_pages(struct xe_shrinker *shrinker, long shrinkable, long purgeable,
+ long tts);
int xe_shrinker_create(struct xe_device *xe);
+int xe_shrinker_non_purgeable_seeks(struct xe_shrinker *shrinker);
+int xe_shrinker_set_non_purgeable_seeks(struct xe_shrinker *shrinker,
+ unsigned int seeks);
+
#endif
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_debugfs.c b/drivers/gpu/drm/xe/xe_sysctrl_debugfs.c
new file mode 100644
index 000000000000..e0db099ef0ad
--- /dev/null
+++ b/drivers/gpu/drm/xe/xe_sysctrl_debugfs.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/kstrtox.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/string_choices.h>
+#include <linux/uaccess.h>
+
+#include "xe_pm.h"
+#include "xe_printk.h"
+#include "xe_sysctrl.h"
+#include "xe_sysctrl_debugfs.h"
+#include "xe_sysctrl_mailbox.h"
+#include "xe_sysctrl_mailbox_types.h"
+#include "xe_sysctrl_types.h"
+
+static ssize_t xe_sysctrl_loopback_write(struct file *file, const char __user *ubuf,
+ size_t len, loff_t *offp)
+{
+ char *kbuf __free(kfree) = NULL;
+ u8 *input __free(kfree) = NULL;
+ struct seq_file *m = file->private_data;
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ struct xe_device *xe = sc_to_xe(entry->sc);
+ struct xe_sysctrl_mailbox_command cmd = {};
+ char *token, *tmp;
+ unsigned long val;
+ size_t input_len = 0;
+ size_t max_input;
+ size_t out_len = 0;
+ int status;
+
+ if (*offp)
+ return -ESPIPE;
+
+ if (len == 0 || len >= PAGE_SIZE)
+ return -EINVAL;
+
+ kbuf = kmalloc(len + 1, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ max_input = min_t(size_t, len, XE_SYSCTRL_MB_MAX_DATA_SIZE);
+ input = kmalloc(max_input, GFP_KERNEL);
+ if (!input)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, ubuf, len))
+ return -EFAULT;
+ kbuf[len] = '\0';
+
+ tmp = kbuf;
+ while ((token = strsep(&tmp, " \t\n")) != NULL) {
+ if (*token == '\0')
+ continue;
+
+ if (input_len >= max_input) {
+ xe_err(xe, "sysctrl: loopback payload too large (max %zu bytes)\n",
+ max_input);
+ return -EINVAL;
+ }
+
+ if (kstrtoul(token, 0, &val) || val > 0xFF) {
+ xe_err(xe, "sysctrl: invalid loopback token '%s'\n", token);
+ return -EINVAL;
+ }
+
+ input[input_len++] = (u8)val;
+ }
+
+ if (input_len == 0) {
+ xe_err(xe, "sysctrl: no loopback payload given\n");
+ return -EINVAL;
+ }
+
+ xe_sysctrl_create_command(&cmd, entry->group, entry->command,
+ input, input_len, entry->response_buf, input_len);
+
+ scoped_guard(mutex, &entry->lock) {
+ guard(xe_pm_runtime)(xe);
+ entry->status = xe_sysctrl_send_command(entry->sc, &cmd, &out_len);
+ entry->response_len = entry->status ? 0 : out_len;
+ status = entry->status;
+ }
+
+ return status ? status : len;
+}
+
+static int xe_sysctrl_loopback_show(struct seq_file *m, void *data)
+{
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ size_t i;
+
+ guard(mutex)(&entry->lock);
+
+ seq_printf(m, "Command: group=0x%02x cmd=0x%02x\n", entry->group, entry->command);
+ seq_printf(m, "Status: %d (%s)\n", entry->status, entry->status ? "FAILED" : "SUCCESS");
+ seq_printf(m, "Response: %zu bytes\n", entry->response_len);
+
+ if (entry->response_len) {
+ seq_puts(m, "Response data:\n");
+ for (i = 0; i < entry->response_len; i++) {
+ if (i && (i % 16) == 0)
+ seq_putc(m, '\n');
+ seq_printf(m, "%02x ", entry->response_buf[i]);
+ }
+ seq_putc(m, '\n');
+ }
+
+ seq_puts(m, "\nUsage:\n");
+ seq_puts(m, " echo \"<byte0> [byte1 ...]\" > loopback\n");
+ seq_puts(m, " cat loopback\n");
+
+ return 0;
+}
+
+static int xe_sysctrl_loopback_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, xe_sysctrl_loopback_show, inode->i_private);
+}
+
+static const struct file_operations xe_sysctrl_loopback_fops = {
+ .owner = THIS_MODULE,
+ .open = xe_sysctrl_loopback_open,
+ .read = seq_read,
+ .write = xe_sysctrl_loopback_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int sysctrl_parse_ras_error_inject(struct xe_device *xe, char *tmp,
+ struct xe_sysctrl_diag_ras_err_inj_req *req)
+{
+ unsigned int nfields = 0;
+ char *token;
+ unsigned long val;
+
+ while ((token = strsep(&tmp, " \t\n")) != NULL) {
+ if (*token == '\0')
+ continue;
+
+ if (kstrtoul(token, 0, &val))
+ goto inval;
+
+ switch (nfields) {
+ case 0:
+ if (val > U16_MAX)
+ goto inval;
+ req->ras_block_id = val;
+ break;
+ case 1:
+ if (val > U16_MAX)
+ goto inval;
+ req->ras_sub_block_id = val;
+ break;
+ case 2:
+ if (val > U16_MAX)
+ goto inval;
+ req->err_type = val;
+ break;
+ case 3:
+ if (val > U32_MAX)
+ goto inval;
+ req->params = val;
+ break;
+ default:
+ xe_err(xe, "sysctrl: too many ras_error_inject arguments\n");
+ return -EINVAL;
+ }
+ nfields++;
+ }
+
+ if (nfields < 3) {
+ xe_err(xe,
+ "sysctrl: usage: <ras_block_id> <ras_sub_block_id> <err_type> [params]\n");
+ return -EINVAL;
+ }
+
+ return 0;
+
+inval:
+ xe_err(xe, "sysctrl: invalid ras_error_inject token '%s'\n", token);
+ return -EINVAL;
+}
+
+static ssize_t xe_sysctrl_ras_error_inject_write(struct file *file, const char __user *ubuf,
+ size_t len, loff_t *offp)
+{
+ char *kbuf __free(kfree) = NULL;
+ struct seq_file *m = file->private_data;
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ struct xe_device *xe = sc_to_xe(entry->sc);
+ struct xe_sysctrl_diag_ras_err_inj_req req = {};
+ struct xe_sysctrl_mailbox_command cmd = {};
+ u8 resp_hdr_only[sizeof(u32)];
+ size_t out_len = 0;
+ int status;
+
+ if (*offp)
+ return -ESPIPE;
+
+ if (len == 0 || len >= PAGE_SIZE)
+ return -EINVAL;
+
+ kbuf = kmalloc(len + 1, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, ubuf, len))
+ return -EFAULT;
+ kbuf[len] = '\0';
+
+ status = sysctrl_parse_ras_error_inject(xe, kbuf, &req);
+ if (status)
+ return status;
+
+ xe_sysctrl_create_command(&cmd, entry->group, entry->command,
+ &req, sizeof(req), resp_hdr_only,
+ sizeof(resp_hdr_only));
+
+ scoped_guard(mutex, &entry->lock) {
+ guard(xe_pm_runtime)(xe);
+ status = xe_sysctrl_send_command(entry->sc, &cmd, &out_len);
+ entry->status = status;
+ }
+
+ return status ? status : len;
+}
+
+static int xe_sysctrl_ras_error_inject_show(struct seq_file *m, void *data)
+{
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ struct xe_device *xe = sc_to_xe(entry->sc);
+ bool fw_ready;
+
+ scoped_guard(xe_pm_runtime, xe)
+ fw_ready = xe_sysctrl_is_diag_fw_ready(xe);
+
+ guard(mutex)(&entry->lock);
+
+ seq_printf(m, "Command: group=0x%02x cmd=0x%02x\n", entry->group, entry->command);
+ seq_printf(m, "Diag firmware ready: %s\n", str_yes_no(fw_ready));
+ seq_printf(m, "Status: %d (%s)\n", entry->status, entry->status ? "FAILED" : "SUCCESS");
+
+ seq_puts(m, "\nUsage:\n");
+ seq_puts(m, " echo \"<ras_block_id> <ras_sub_block_id> <err_type> [params]\" > ras_error_inject\n");
+ seq_puts(m, " cat ras_error_inject\n");
+
+ return 0;
+}
+
+static int xe_sysctrl_ras_error_inject_open(struct inode *inode, struct file *file)
+{
+ struct xe_sysctrl_debugfs_entry *entry = inode->i_private;
+ struct xe_device *xe = sc_to_xe(entry->sc);
+ bool fw_ready;
+
+ scoped_guard(xe_pm_runtime, xe)
+ fw_ready = xe_sysctrl_is_diag_fw_ready(xe);
+
+ if (!fw_ready) {
+ xe_err(xe, "sysctrl: diag firmware not ready, ras_error_inject unavailable\n");
+ return -ENODEV;
+ }
+
+ return single_open(file, xe_sysctrl_ras_error_inject_show, inode->i_private);
+}
+
+static const struct file_operations xe_sysctrl_ras_error_inject_fops = {
+ .owner = THIS_MODULE,
+ .open = xe_sysctrl_ras_error_inject_open,
+ .read = seq_read,
+ .write = xe_sysctrl_ras_error_inject_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void xe_sysctrl_register_entry(struct dentry *root, struct xe_sysctrl_debugfs_entry *entry,
+ struct xe_sysctrl *sc, const char *name,
+ u8 group, u8 command,
+ const struct file_operations *fops)
+{
+ struct xe_device *xe = sc_to_xe(sc);
+
+ if (devm_mutex_init(xe->drm.dev, &entry->lock)) {
+ xe_err(xe, "sysctrl: failed to init %s debugfs entry lock\n", name);
+ return;
+ }
+
+ entry->sc = sc;
+ entry->group = group;
+ entry->command = command;
+ entry->response_len = 0;
+ entry->status = 0;
+ entry->timeout_ms = 0;
+
+ debugfs_create_file(name, 0600, root, entry, fops);
+}
+
+static char *sysctrl_mailbox_next_token(char **tmp)
+{
+ char *token;
+
+ do {
+ token = strsep(tmp, " \t\n");
+ } while (token && *token == '\0');
+
+ return token;
+}
+
+static ssize_t xe_sysctrl_mailbox_write(struct file *file, const char __user *ubuf,
+ size_t len, loff_t *offp)
+{
+ char *kbuf __free(kfree) = NULL;
+ u8 *input __free(kfree) = NULL;
+ struct seq_file *m = file->private_data;
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ struct xe_device *xe = sc_to_xe(entry->sc);
+ struct xe_sysctrl_mailbox_command cmd = {};
+ char *token, *tmp;
+ unsigned long val;
+ unsigned int timeout_ms = 0;
+ size_t input_len = 0;
+ size_t max_input;
+ size_t out_len = 0;
+ size_t resp_len = XE_SYSCTRL_MB_MAX_DATA_SIZE;
+ u8 group, command;
+ int status;
+
+ if (*offp)
+ return -ESPIPE;
+
+ if (len == 0 || len >= PAGE_SIZE)
+ return -EINVAL;
+
+ kbuf = kmalloc(len + 1, GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ max_input = min_t(size_t, len, XE_SYSCTRL_MB_MAX_DATA_SIZE);
+ input = kmalloc(max_input, GFP_KERNEL);
+ if (!input)
+ return -ENOMEM;
+
+ if (copy_from_user(kbuf, ubuf, len))
+ return -EFAULT;
+ kbuf[len] = '\0';
+
+ tmp = kbuf;
+
+ token = sysctrl_mailbox_next_token(&tmp);
+ if (!token || kstrtoul(token, 0, &val) || val > 0xFF) {
+ xe_err(xe, "sysctrl: invalid mailbox group id\n");
+ return -EINVAL;
+ }
+ group = (u8)val;
+
+ token = sysctrl_mailbox_next_token(&tmp);
+ if (!token || kstrtoul(token, 0, &val) || val > SYSCTRL_HDR_COMMAND_MAX) {
+ xe_err(xe, "sysctrl: invalid mailbox command id (max 0x%x)\n",
+ SYSCTRL_HDR_COMMAND_MAX);
+ return -EINVAL;
+ }
+ command = (u8)val;
+
+ token = sysctrl_mailbox_next_token(&tmp);
+ if (token && strstarts(token, "timeout=")) {
+ if (kstrtouint(token + strlen("timeout="), 0, &timeout_ms) ||
+ timeout_ms > XE_SYSCTRL_MB_MAX_TIMEOUT_MS) {
+ xe_err(xe, "sysctrl: invalid mailbox timeout (max %u ms)\n",
+ XE_SYSCTRL_MB_MAX_TIMEOUT_MS);
+ return -EINVAL;
+ }
+ token = sysctrl_mailbox_next_token(&tmp);
+ }
+
+ if (token && strstarts(token, "resplen=")) {
+ unsigned int resplen;
+
+ if (kstrtouint(token + strlen("resplen="), 0, &resplen) ||
+ !resplen || resplen > XE_SYSCTRL_MB_MAX_DATA_SIZE) {
+ xe_err(xe, "sysctrl: invalid mailbox resplen (1-%zu bytes)\n",
+ (size_t)XE_SYSCTRL_MB_MAX_DATA_SIZE);
+ return -EINVAL;
+ }
+ resp_len = resplen;
+ token = sysctrl_mailbox_next_token(&tmp);
+ }
+
+ while (token) {
+ if (input_len >= max_input) {
+ xe_err(xe, "sysctrl: mailbox payload too large (max %zu bytes)\n",
+ max_input);
+ return -EINVAL;
+ }
+
+ if (kstrtoul(token, 0, &val) || val > 0xFF) {
+ xe_err(xe, "sysctrl: invalid mailbox payload byte '%s'\n", token);
+ return -EINVAL;
+ }
+
+ input[input_len++] = (u8)val;
+ token = sysctrl_mailbox_next_token(&tmp);
+ }
+
+ scoped_guard(mutex, &entry->lock) {
+ entry->group = group;
+ entry->command = command;
+ entry->timeout_ms = timeout_ms;
+
+ xe_sysctrl_create_command(&cmd, group, command, input_len ? input : NULL, input_len,
+ entry->response_buf, resp_len);
+ cmd.timeout_ms = timeout_ms;
+
+ guard(xe_pm_runtime)(xe);
+ status = xe_sysctrl_send_command(entry->sc, &cmd, &out_len);
+ entry->status = status;
+ entry->response_len = status ? 0 : out_len;
+ }
+
+ return status ? status : len;
+}
+
+static int xe_sysctrl_mailbox_show(struct seq_file *m, void *data)
+{
+ struct xe_sysctrl_debugfs_entry *entry = m->private;
+ size_t i;
+
+ guard(mutex)(&entry->lock);
+
+ seq_printf(m, "Command: group=0x%02x cmd=0x%02x\n", entry->group, entry->command);
+ seq_printf(m, "Timeout: %u ms%s\n", entry->timeout_ms ?: XE_SYSCTRL_MB_DEFAULT_TIMEOUT_MS,
+ entry->timeout_ms ? "" : " (default)");
+ seq_printf(m, "Status: %d (%s)\n", entry->status, entry->status ? "FAILED" : "SUCCESS");
+ seq_printf(m, "Response: %zu bytes\n", entry->response_len);
+
+ if (entry->response_len) {
+ seq_puts(m, "Response data:\n");
+ for (i = 0; i < entry->response_len; i++) {
+ if (i && (i % 16) == 0)
+ seq_putc(m, '\n');
+ seq_printf(m, "%02x ", entry->response_buf[i]);
+ }
+ seq_putc(m, '\n');
+ }
+
+ seq_puts(m, "\nUsage:\n");
+ seq_puts(m, " echo \"<group> <command> [timeout=<ms>] [resplen=<N>] [byte0 byte1 ...]\" > mailbox\n");
+ seq_puts(m, " cat mailbox\n");
+
+ return 0;
+}
+
+static int xe_sysctrl_mailbox_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, xe_sysctrl_mailbox_show, inode->i_private);
+}
+
+static const struct file_operations xe_sysctrl_mailbox_fops = {
+ .owner = THIS_MODULE,
+ .open = xe_sysctrl_mailbox_open,
+ .read = seq_read,
+ .write = xe_sysctrl_mailbox_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/**
+ * xe_sysctrl_debugfs_register - Register debugfs entries for System Controller
+ * @sc: xe_sysctrl instance
+ * @parent: parent debugfs directory
+ */
+void xe_sysctrl_debugfs_register(struct xe_sysctrl *sc, struct dentry *parent)
+{
+ struct dentry *root;
+
+ root = debugfs_create_dir("sc", parent);
+ if (IS_ERR(root))
+ return;
+
+ sc->debugfs.root = root;
+
+ xe_sysctrl_register_entry(root, &sc->debugfs.loopback, sc, "loopback",
+ XE_SYSCTRL_GROUP_CORE, XE_SYSCTRL_CMD_LOOPBACK,
+ &xe_sysctrl_loopback_fops);
+
+ xe_sysctrl_register_entry(root, &sc->debugfs.ras_error_inject, sc, "ras_error_inject",
+ XE_SYSCTRL_GROUP_DIAG, XE_SYSCTRL_CMD_DIAG_RAS_ERR_INJECT,
+ &xe_sysctrl_ras_error_inject_fops);
+
+ xe_sysctrl_register_entry(root, &sc->debugfs.mailbox, sc, "mailbox", 0, 0,
+ &xe_sysctrl_mailbox_fops);
+}
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_debugfs.h b/drivers/gpu/drm/xe/xe_sysctrl_debugfs.h
new file mode 100644
index 000000000000..d1414ac3562e
--- /dev/null
+++ b/drivers/gpu/drm/xe/xe_sysctrl_debugfs.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#ifndef _XE_SYSCTRL_DEBUGFS_H_
+#define _XE_SYSCTRL_DEBUGFS_H_
+
+struct dentry;
+struct xe_sysctrl;
+
+void xe_sysctrl_debugfs_register(struct xe_sysctrl *sc, struct dentry *parent);
+
+#endif
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_event.c b/drivers/gpu/drm/xe/xe_sysctrl_event.c
index 15ddd2d8bc6d..3239c110a66a 100644
--- a/drivers/gpu/drm/xe/xe_sysctrl_event.c
+++ b/drivers/gpu/drm/xe/xe_sysctrl_event.c
@@ -5,6 +5,7 @@
#include "xe_device.h"
#include "xe_irq.h"
+#include "xe_log.h"
#include "xe_printk.h"
#include "xe_ras.h"
#include "xe_sysctrl.h"
@@ -25,23 +26,26 @@ static void get_pending_event(struct xe_sysctrl *sc, struct xe_sysctrl_mailbox_c
ret = xe_sysctrl_send_command(sc, command, &len);
if (ret) {
- xe_err(xe, "sysctrl: failed to get pending event %d\n", ret);
+ xe_log_err(xe, SYSCTRL, ret, "Failed to get pending event\n");
return;
}
if (len != sizeof(*response)) {
- xe_err(xe, "sysctrl: unexpected event response length %zu (expected %zu)\n",
- len, sizeof(*response));
+ xe_log_err(xe, SYSCTRL, -EPROTO,
+ "Unexpected event response length %zu (expected %zu)\n",
+ len, sizeof(*response));
return;
}
if (response->event == XE_SYSCTRL_EVENT_THRESHOLD_CROSSED)
xe_ras_counter_threshold_crossed(xe, response);
else
- xe_warn(xe, "sysctrl: unexpected event %#x\n", response->event);
+ xe_log_err_info(xe, SYSCTRL, -ENOPKG,
+ "Unexpected event %#x\n", response->event);
if (!--count) {
- xe_err(xe, "sysctrl: event flooding\n");
+ xe_log_err(xe, SYSCTRL, -EXFULL,
+ "Ignored %u events due to flooding\n", response->count);
return;
}
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c
index 7701348f0676..f1a231dfec1e 100644
--- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c
+++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.c
@@ -7,10 +7,12 @@
#include <linux/cleanup.h>
#include <linux/minmax.h>
#include <linux/slab.h>
+#include <linux/sprintf.h>
#include <linux/string.h>
#include "regs/xe_sysctrl_regs.h"
#include "xe_device.h"
+#include "xe_log.h"
#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_printk.h"
@@ -23,41 +25,43 @@ struct xe_sysctrl_mailbox_msg_hdr {
};
#define XE_SYSCTRL_HDR_GROUP_ID(hdr) \
- FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, (hdr)->data)
+ REG_FIELD_GET(SYSCTRL_HDR_GROUP_ID_MASK, (hdr)->data)
#define XE_SYSCTRL_HDR_COMMAND(hdr) \
- FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, (hdr)->data)
+ REG_FIELD_GET(SYSCTRL_HDR_COMMAND_MASK, (hdr)->data)
#define XE_SYSCTRL_HDR_IS_RESPONSE(hdr) \
- FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, (hdr)->data)
+ REG_FIELD_GET(SYSCTRL_HDR_IS_RESPONSE, (hdr)->data)
#define XE_SYSCTRL_HDR_RESULT(hdr) \
- FIELD_GET(SYSCTRL_HDR_RESULT_MASK, (hdr)->data)
+ REG_FIELD_GET(SYSCTRL_HDR_RESULT_MASK, (hdr)->data)
-static bool sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask,
- unsigned int timeout_ms)
+static int sysctrl_wait_bit_clear(struct xe_sysctrl *sc, u32 bit_mask,
+ unsigned int timeout_ms, u32 *out_val)
{
- int ret;
-
- ret = xe_mmio_wait32_not(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
- timeout_ms * 1000, NULL, false);
-
- return ret == 0;
+ return xe_mmio_wait32_not(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
+ timeout_ms * 1000, out_val, false);
}
-static bool sysctrl_wait_bit_set(struct xe_sysctrl *sc, u32 bit_mask,
- unsigned int timeout_ms)
+static int sysctrl_wait_bit_set(struct xe_sysctrl *sc, u32 bit_mask,
+ unsigned int timeout_ms, u32 *out_val)
{
- int ret;
-
- ret = xe_mmio_wait32(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
- timeout_ms * 1000, NULL, false);
+ return xe_mmio_wait32(sc->mmio, SYSCTRL_MB_CTRL, bit_mask, bit_mask,
+ timeout_ms * 1000, out_val, false);
+}
- return ret == 0;
+static void sysctrl_mb_ctrl_desc(u32 ctrl_reg, char *buf, size_t size)
+{
+ scnprintf(buf, size, "frame=%u/%u busy=%d resp=%d ctrl=%#010x",
+ REG_FIELD_GET(SYSCTRL_FRAME_CURRENT_MASK, ctrl_reg),
+ REG_FIELD_GET(SYSCTRL_FRAME_TOTAL_MASK, ctrl_reg),
+ !!(ctrl_reg & SYSCTRL_MB_CTRL_RUN_BUSY),
+ !!(ctrl_reg & SYSCTRL_MB_CTRL_RUN_BUSY_OUT),
+ ctrl_reg);
}
-static int sysctrl_write_frame(struct xe_sysctrl *sc, const void *frame,
- size_t len)
+static void sysctrl_write_frame(struct xe_sysctrl *sc, const void *frame,
+ size_t len)
{
static const struct xe_reg regs[] = {
SYSCTRL_MB_DATA0, SYSCTRL_MB_DATA1, SYSCTRL_MB_DATA2, SYSCTRL_MB_DATA3
@@ -73,12 +77,10 @@ static int sysctrl_write_frame(struct xe_sysctrl *sc, const void *frame,
for (i = 0; i < dw; i++)
xe_mmio_write32(sc->mmio, regs[i], val[i]);
-
- return 0;
}
-static int sysctrl_read_frame(struct xe_sysctrl *sc, void *frame,
- size_t len)
+static void sysctrl_read_frame(struct xe_sysctrl *sc, void *frame,
+ size_t len)
{
static const struct xe_reg regs[] = {
SYSCTRL_MB_DATA0, SYSCTRL_MB_DATA1, SYSCTRL_MB_DATA2, SYSCTRL_MB_DATA3
@@ -94,8 +96,6 @@ static int sysctrl_read_frame(struct xe_sysctrl *sc, void *frame,
val[i] = xe_mmio_read32(sc->mmio, regs[i]);
memcpy(frame, val, len);
-
- return 0;
}
static void sysctrl_clear_response(struct xe_sysctrl *sc)
@@ -114,10 +114,7 @@ static int sysctrl_prepare_command(struct xe_device *xe,
xe_assert(xe, command <= SYSCTRL_HDR_COMMAND_MAX);
- if (data_in_len > XE_SYSCTRL_MB_MAX_MESSAGE_SIZE - sizeof(*hdr)) {
- xe_err(xe, "sysctrl: Input data too large: %zu bytes\n", data_in_len);
- return -EINVAL;
- }
+ xe_assert(xe, data_in_len <= XE_SYSCTRL_MB_MAX_MESSAGE_SIZE - sizeof(*hdr));
size = sizeof(*hdr) + data_in_len;
@@ -126,8 +123,8 @@ static int sysctrl_prepare_command(struct xe_device *xe,
return -ENOMEM;
hdr = (struct xe_sysctrl_mailbox_msg_hdr *)buffer;
- hdr->data = FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) |
- FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command);
+ hdr->data = REG_FIELD_PREP(SYSCTRL_HDR_GROUP_ID_MASK, group_id) |
+ REG_FIELD_PREP(SYSCTRL_HDR_COMMAND_MASK, command);
if (data_in && data_in_len)
memcpy(buffer + sizeof(*hdr), data_in, data_in_len);
@@ -142,16 +139,24 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc,
const u8 *mbox_cmd,
size_t cmd_size, unsigned int timeout_ms)
{
+ const struct xe_sysctrl_mailbox_msg_hdr *hdr =
+ (const struct xe_sysctrl_mailbox_msg_hdr *)mbox_cmd;
struct xe_device *xe = sc_to_xe(sc);
u32 ctrl_reg, total_frames, frame;
size_t bytes_sent, frame_size;
+ char ctrl_desc[48];
bool phase;
+ int ret;
total_frames = DIV_ROUND_UP(cmd_size, XE_SYSCTRL_MB_FRAME_SIZE);
- if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) {
- xe_err(xe, "sysctrl: Mailbox busy\n");
- return -EBUSY;
+ ret = sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms, &ctrl_reg);
+ if (ret) {
+ sysctrl_mb_ctrl_desc(ctrl_reg, ctrl_desc, sizeof(ctrl_desc));
+ xe_log_err(xe, SYSCTRL, ret, "Mailbox busy %#x.%#x %s\n",
+ XE_SYSCTRL_HDR_GROUP_ID(hdr), XE_SYSCTRL_HDR_COMMAND(hdr),
+ ctrl_desc);
+ return ret;
}
ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL);
@@ -161,10 +166,7 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc,
for (frame = 0; frame < total_frames; frame++) {
frame_size = min_t(size_t, cmd_size - bytes_sent, XE_SYSCTRL_MB_FRAME_SIZE);
- if (sysctrl_write_frame(sc, mbox_cmd + bytes_sent, frame_size)) {
- xe_err(xe, "sysctrl: Failed to write frame %u\n", frame);
- return -EIO;
- }
+ sysctrl_write_frame(sc, mbox_cmd + bytes_sent, frame_size);
ctrl_reg = SYSCTRL_MB_CTRL_RUN_BUSY |
REG_FIELD_PREP(SYSCTRL_FRAME_CURRENT_MASK, frame) |
@@ -174,9 +176,13 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc,
xe_mmio_write32(sc->mmio, SYSCTRL_MB_CTRL, ctrl_reg);
- if (!sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms)) {
- xe_err(xe, "sysctrl: Frame %u acknowledgment timeout\n", frame);
- return -ETIMEDOUT;
+ ret = sysctrl_wait_bit_clear(sc, SYSCTRL_MB_CTRL_RUN_BUSY, timeout_ms, &ctrl_reg);
+ if (ret) {
+ sysctrl_mb_ctrl_desc(ctrl_reg, ctrl_desc, sizeof(ctrl_desc));
+ xe_log_err(xe, SYSCTRL, ret, "Frame acknowledgment timeout %#x.%#x %s\n",
+ XE_SYSCTRL_HDR_GROUP_ID(hdr), XE_SYSCTRL_HDR_COMMAND(hdr),
+ ctrl_desc);
+ return ret;
}
bytes_sent += frame_size;
@@ -185,27 +191,30 @@ static int sysctrl_send_frames(struct xe_sysctrl *sc,
return 0;
}
-static int sysctrl_process_frame(struct xe_sysctrl *sc, void *out,
- size_t frame_size, unsigned int timeout_ms,
+static int sysctrl_process_frame(struct xe_sysctrl *sc,
+ const struct xe_sysctrl_mailbox_msg_hdr *req,
+ void *out, size_t frame_size, unsigned int timeout_ms,
bool *done)
{
u32 curr_frame, total_frames, ctrl_reg;
struct xe_device *xe = sc_to_xe(sc);
+ char ctrl_desc[48];
int ret;
- if (!sysctrl_wait_bit_set(sc, SYSCTRL_MB_CTRL_RUN_BUSY_OUT, timeout_ms)) {
- xe_err(xe, "sysctrl: Response frame timeout\n");
- return -ETIMEDOUT;
+ ret = sysctrl_wait_bit_set(sc, SYSCTRL_MB_CTRL_RUN_BUSY_OUT, timeout_ms, &ctrl_reg);
+ if (ret) {
+ sysctrl_mb_ctrl_desc(ctrl_reg, ctrl_desc, sizeof(ctrl_desc));
+ xe_log_err(xe, SYSCTRL, ret, "Response frame timeout %#x.%#x %s\n",
+ XE_SYSCTRL_HDR_GROUP_ID(req), XE_SYSCTRL_HDR_COMMAND(req),
+ ctrl_desc);
+ return ret;
}
ctrl_reg = xe_mmio_read32(sc->mmio, SYSCTRL_MB_CTRL);
- total_frames = FIELD_GET(SYSCTRL_FRAME_TOTAL_MASK, ctrl_reg);
- curr_frame = FIELD_GET(SYSCTRL_FRAME_CURRENT_MASK, ctrl_reg);
-
- ret = sysctrl_read_frame(sc, out, frame_size);
- if (ret)
- return ret;
+ total_frames = REG_FIELD_GET(SYSCTRL_FRAME_TOTAL_MASK, ctrl_reg);
+ curr_frame = REG_FIELD_GET(SYSCTRL_FRAME_CURRENT_MASK, ctrl_reg);
+ sysctrl_read_frame(sc, out, frame_size);
sysctrl_clear_response(sc);
if (curr_frame == total_frames)
@@ -235,7 +244,7 @@ static int sysctrl_receive_frames(struct xe_sysctrl *sc,
while (!done && remain) {
frame_size = min_t(size_t, remain, XE_SYSCTRL_MB_FRAME_SIZE);
- ret = sysctrl_process_frame(sc, out, frame_size, timeout_ms,
+ ret = sysctrl_process_frame(sc, req, out, frame_size, timeout_ms,
&done);
if (ret)
return ret;
@@ -249,13 +258,16 @@ static int sysctrl_receive_frames(struct xe_sysctrl *sc,
if (!XE_SYSCTRL_HDR_IS_RESPONSE(hdr) ||
XE_SYSCTRL_HDR_GROUP_ID(hdr) != XE_SYSCTRL_HDR_GROUP_ID(req) ||
XE_SYSCTRL_HDR_COMMAND(hdr) != XE_SYSCTRL_HDR_COMMAND(req)) {
- xe_err(xe, "sysctrl: Response header mismatch\n");
+ xe_log_err(xe, SYSCTRL, -EPROTO,
+ "Response header mismatch: %#x != %#x\n",
+ hdr->data, req->data);
return -EPROTO;
}
if (XE_SYSCTRL_HDR_RESULT(hdr) != 0) {
- xe_err(xe, "sysctrl: Firmware error: 0x%02lx\n",
- XE_SYSCTRL_HDR_RESULT(hdr));
+ xe_log_err(xe, SYSCTRL, -EIO, "Firmware error: %#04x %#x.%#x\n",
+ XE_SYSCTRL_HDR_RESULT(hdr),
+ XE_SYSCTRL_HDR_GROUP_ID(req), XE_SYSCTRL_HDR_COMMAND(req));
return -EIO;
}
@@ -311,8 +323,8 @@ void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 gr
{
struct xe_sysctrl_app_msg_hdr header = {0};
- header.data = FIELD_PREP(APP_HDR_GROUP_ID_MASK, group_id) |
- FIELD_PREP(APP_HDR_COMMAND_MASK, cmd_id);
+ header.data = REG_FIELD_PREP(APP_HDR_GROUP_ID_MASK, group_id) |
+ REG_FIELD_PREP(APP_HDR_COMMAND_MASK, cmd_id);
command->header = header;
command->data_in = request;
@@ -372,7 +384,8 @@ int xe_sysctrl_send_command(struct xe_sysctrl *sc,
cmd->data_in, cmd->data_in_len,
&mbox_cmd, &cmd_size);
if (ret) {
- xe_err(xe, "sysctrl: Failed to prepare command: %pe\n", ERR_PTR(ret));
+ xe_log_err(xe, SYSCTRL, ret, "Failed to prepare command %#x.%#x\n",
+ group_id, command_code);
return ret;
}
@@ -382,7 +395,8 @@ int xe_sysctrl_send_command(struct xe_sysctrl *sc,
cmd->data_out, cmd->data_out_len, rdata_len,
timeout_ms);
if (ret)
- xe_err(xe, "sysctrl: Mailbox command failed: %pe\n", ERR_PTR(ret));
+ xe_log_err(xe, SYSCTRL, ret, "Mailbox command failed to send %#x.%#x\n",
+ group_id, command_code);
kfree(mbox_cmd);
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h
index f0d5e3d7f5e3..5a3dd14e3a11 100644
--- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h
+++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox.h
@@ -15,13 +15,13 @@ struct xe_sysctrl;
struct xe_sysctrl_mailbox_command;
#define XE_SYSCTRL_APP_HDR_GROUP_ID(hdr) \
- FIELD_GET(APP_HDR_GROUP_ID_MASK, (hdr)->data)
+ REG_FIELD_GET(APP_HDR_GROUP_ID_MASK, (hdr)->data)
#define XE_SYSCTRL_APP_HDR_COMMAND(hdr) \
- FIELD_GET(APP_HDR_COMMAND_MASK, (hdr)->data)
+ REG_FIELD_GET(APP_HDR_COMMAND_MASK, (hdr)->data)
#define XE_SYSCTRL_APP_HDR_VERSION(hdr) \
- FIELD_GET(APP_HDR_VERSION_MASK, (hdr)->data)
+ REG_FIELD_GET(APP_HDR_VERSION_MASK, (hdr)->data)
void xe_sysctrl_create_command(struct xe_sysctrl_mailbox_command *command, u8 group_id, u8 cmd_id,
void *request, size_t request_len, void *response,
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h
index e98fa6877fec..1f3f782d770e 100644
--- a/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h
+++ b/drivers/gpu/drm/xe/xe_sysctrl_mailbox_types.h
@@ -14,10 +14,12 @@
* enum xe_sysctrl_group - System Controller command groups
*
* @XE_SYSCTRL_GROUP_GFSP: GFSP group
+ * @XE_SYSCTRL_GROUP_DIAG: Diag group
* @XE_SYSCTRL_GROUP_CORE: Core group
*/
enum xe_sysctrl_group {
XE_SYSCTRL_GROUP_GFSP = 0x01,
+ XE_SYSCTRL_GROUP_DIAG = 0x02,
XE_SYSCTRL_GROUP_CORE = 0xFF,
};
@@ -30,6 +32,9 @@ enum xe_sysctrl_group {
* @XE_SYSCTRL_CMD_GET_THRESHOLD: Retrieve error threshold
* @XE_SYSCTRL_CMD_SET_THRESHOLD: Set error threshold
* @XE_SYSCTRL_CMD_GET_PENDING_EVENT: Retrieve pending event
+ * @XE_SYSCTRL_CMD_PAGE_OFFLINE: Instruct firmware to offline/remove a page
+ * @XE_SYSCTRL_CMD_GET_OFFLINE_LIST: Retrieve list of all offlined pages from flash
+ * @XE_SYSCTRL_CMD_GET_OFFLINE_QUEUE: Retrieve list of offlined queued pages from firmware
* @XE_SYSCTRL_CMD_GET_HEALTH: Retrieve gpu health
* @XE_SYSCTRL_CMD_SET_HEALTH: Set gpu health
*/
@@ -40,6 +45,9 @@ enum xe_sysctrl_gfsp_cmd {
XE_SYSCTRL_CMD_GET_THRESHOLD = 0x05,
XE_SYSCTRL_CMD_SET_THRESHOLD = 0x06,
XE_SYSCTRL_CMD_GET_PENDING_EVENT = 0x07,
+ XE_SYSCTRL_CMD_PAGE_OFFLINE = 0x08,
+ XE_SYSCTRL_CMD_GET_OFFLINE_LIST = 0x09,
+ XE_SYSCTRL_CMD_GET_OFFLINE_QUEUE = 0x0A,
XE_SYSCTRL_CMD_GET_HEALTH = 0x0B,
XE_SYSCTRL_CMD_SET_HEALTH = 0x0C,
};
@@ -47,13 +55,44 @@ enum xe_sysctrl_gfsp_cmd {
/**
* enum xe_sysctrl_core_cmd - Commands supported by Core group
*
+ * @XE_SYSCTRL_CMD_LOOPBACK: Loopback test command
* @XE_SYSCTRL_CMD_GET_APP_STATUS_BY_ID: Retrieve application status by ID
*/
enum xe_sysctrl_core_cmd {
+ XE_SYSCTRL_CMD_LOOPBACK = 0x03,
XE_SYSCTRL_CMD_GET_APP_STATUS_BY_ID = 0x05,
};
/**
+ * enum xe_sysctrl_diag_cmd - Commands supported by Diag group
+ *
+ * @XE_SYSCTRL_CMD_DIAG_RAS_ERR_INJECT: RAS error injection
+ */
+enum xe_sysctrl_diag_cmd {
+ XE_SYSCTRL_CMD_DIAG_RAS_ERR_INJECT = 0x7E,
+};
+
+/**
+ * struct xe_sysctrl_diag_ras_err_inj_req - DIAG_RAS_ERR_INJECT request payload
+ *
+ * Request payload for XE_SYSCTRL_CMD_DIAG_RAS_ERR_INJECT. The mailbox layer
+ * prepends the application message header before sending.
+ *
+ * @ras_block_id: RAS block (subsystem) to inject the error into
+ * @ras_sub_block_id: RAS sub-block (IP) within @ras_block_id
+ * @err_type: Type of test error to inject
+ * @reserved: Must be zero
+ * @params: Optional injection parameters (default 0)
+ */
+struct xe_sysctrl_diag_ras_err_inj_req {
+ u16 ras_block_id;
+ u16 ras_sub_block_id;
+ u16 err_type;
+ u16 reserved;
+ u32 params;
+} __packed;
+
+/**
* struct xe_sysctrl_app_status_req - Get application status request
*
* @app_id: Application ID for which to retrieve status
@@ -118,6 +157,10 @@ struct xe_sysctrl_mailbox_command {
#define XE_SYSCTRL_MB_MAX_MESSAGE_SIZE \
(XE_SYSCTRL_MB_FRAME_SIZE * XE_SYSCTRL_MB_MAX_FRAMES)
+#define XE_SYSCTRL_MB_MAX_DATA_SIZE \
+ (XE_SYSCTRL_MB_MAX_MESSAGE_SIZE - sizeof(u32))
+
#define XE_SYSCTRL_MB_DEFAULT_TIMEOUT_MS 500
+#define XE_SYSCTRL_MB_MAX_TIMEOUT_MS 60000
#endif
diff --git a/drivers/gpu/drm/xe/xe_sysctrl_types.h b/drivers/gpu/drm/xe/xe_sysctrl_types.h
index 98c2f473f7c6..f12455f2886d 100644
--- a/drivers/gpu/drm/xe/xe_sysctrl_types.h
+++ b/drivers/gpu/drm/xe/xe_sysctrl_types.h
@@ -10,7 +10,39 @@
#include <linux/types.h>
#include <linux/workqueue_types.h>
+#include "xe_sysctrl_mailbox_types.h"
+
struct xe_mmio;
+struct dentry;
+
+/**
+ * struct xe_sysctrl_debugfs_entry - Debugfs entry for a raw mailbox test command
+ */
+struct xe_sysctrl_debugfs_entry {
+ /** @sc: Back pointer to parent sysctrl instance */
+ struct xe_sysctrl *sc;
+
+ /** @group: Command group ID */
+ u8 group;
+
+ /** @command: Command ID within group */
+ u8 command;
+
+ /** @lock: Protects @status, @response_len and @response_buf below */
+ struct mutex lock;
+
+ /** @response_buf: Response data buffer, sized to the maximum mailbox message */
+ u8 response_buf[XE_SYSCTRL_MB_MAX_MESSAGE_SIZE];
+
+ /** @response_len: Actual response length from firmware */
+ size_t response_len;
+
+ /** @status: Last command result */
+ int status;
+
+ /** @timeout_ms: Last mailbox response timeout used (0 for default) */
+ unsigned int timeout_ms;
+};
/**
* struct xe_sysctrl - System Controller driver context
@@ -31,6 +63,21 @@ struct xe_sysctrl {
/** @event_lock: Mutex protecting pending events */
struct mutex event_lock;
+
+ /** @debugfs: Debugfs entries */
+ struct {
+ /** @debugfs.root: Root debugfs directory */
+ struct dentry *root;
+
+ /** @debugfs.loopback: Loopback test entry */
+ struct xe_sysctrl_debugfs_entry loopback;
+
+ /** @debugfs.ras_error_inject: RAS error injection test entry */
+ struct xe_sysctrl_debugfs_entry ras_error_inject;
+
+ /** @debugfs.mailbox: Generic, user-parameterized mailbox entry */
+ struct xe_sysctrl_debugfs_entry mailbox;
+ } debugfs;
};
#endif
diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
index 9a514d983e90..36d47de8b11b 100644
--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
@@ -24,6 +24,7 @@
#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_printk.h"
+#include "xe_ras.h"
#include "xe_res_cursor.h"
#include "xe_ttm_stolen_mgr.h"
#include "xe_ttm_vram_mgr.h"
@@ -243,9 +244,9 @@ static void xe_ttm_vram_retry_queued_pages(struct xe_ttm_vram_mgr *mgr)
continue;
}
--mgr->n_queued_pages;
- list_del_rcu(&pos->queued_link);
+ list_del(&pos->queued_link);
++mgr->n_offlined_pages;
- list_add_rcu(&pos->offlined_link, &mgr->offlined_pages);
+ list_add(&pos->offlined_link, &mgr->offlined_pages);
}
}
@@ -394,17 +395,17 @@ static void xe_ttm_vram_free_bad_pages(struct xe_ttm_vram_mgr *mgr)
struct xe_ttm_vram_offline_resource *pos, *n;
list_for_each_entry_safe(pos, n, &mgr->offlined_pages, offlined_link) {
- list_del_rcu(&pos->offlined_link);
+ list_del(&pos->offlined_link);
xe_ttm_vram_buddy_free(mgr, &pos->blocks, pos->used_visible_size);
--mgr->n_offlined_pages;
- kfree_rcu(pos, rcu);
+ kfree(pos);
}
list_for_each_entry_safe(pos, n, &mgr->queued_pages, queued_link) {
- list_del_rcu(&pos->queued_link);
+ list_del(&pos->queued_link);
/* queued entries have no buddy reservation yet */
xe_ttm_vram_buddy_free(mgr, &pos->blocks, 0);
--mgr->n_queued_pages;
- kfree_rcu(pos, rcu);
+ kfree(pos);
}
}
@@ -687,6 +688,19 @@ static int xe_ttm_vram_purge_page(struct xe_device *xe, struct xe_bo *bo)
goto out;
}
+ /*
+ * The caller's user+pinned check was lockless (mgr->lock only), so an
+ * external pin can race in before we get here. Re-validate now that
+ * we hold the reservation lock and reject the purge instead of
+ * unpinning a BO userspace still expects to be resident. Request SBR
+ * the same way the caller's own critical-BO check does.
+ */
+ if (xe_bo_is_user(bo) && xe_bo_is_pinned(bo)) {
+ xe_bo_unlock(bo);
+ ret = -EIO;
+ goto out;
+ }
+
xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED);
ttm_bo_unmap_virtual(&bo->ttm); /* nuke CPU mmap + VRAM IO mappings */
if (xe_bo_is_pinned(bo))
@@ -810,7 +824,7 @@ static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
}
/* Queue free(to-be-purged) pages */
++vram_mgr->n_queued_pages;
- list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
+ list_add(&nentry->queued_link, &vram_mgr->queued_pages);
} else {
/* Immediately offline unoccupied pages */
/* Queue free(to-be-reserved) pages */
@@ -824,11 +838,11 @@ static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
"Page at addr:0x%llx still busy (%d), deferring reservation\n",
addr, ret);
++vram_mgr->n_queued_pages;
- list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
+ list_add(&nentry->queued_link, &vram_mgr->queued_pages);
return 0;
}
++vram_mgr->n_offlined_pages;
- list_add_rcu(&nentry->offlined_link, &vram_mgr->offlined_pages);
+ list_add(&nentry->offlined_link, &vram_mgr->offlined_pages);
return ret;
}
}
@@ -851,6 +865,13 @@ static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
*/
ret = xe_ttm_vram_purge_page(xe, pbo);
xe_bo_put(pbo);
+ if (ret == -EIO) {
+ /* Raced into an external pin after the lockless check above */
+ drm_err(&xe->drm,
+ "%s: addr: 0x%llx became externally pinned, requesting SBR\n",
+ __func__, addr);
+ return ret;
+ }
if (ret)
drm_warn(&xe->drm, "Purge failed at addr:0x%llx, ret:%d\n", addr, ret);
}
@@ -886,11 +907,11 @@ static struct xe_vram_region *xe_ttm_vram_addr_to_region(struct xe_device *xe, u
}
/**
- * xe_ttm_vram_handle_addr_fault - Handle vram physical address error flaged
+ * xe_ttm_vram_handle_addr_fault - Handle vram physical address error flagged
* @xe: pointer to parent device
* @addr: physical faulty address
*
- * Handle the physcial faulty address error on specific tile.
+ * Handle the physical faulty address error on specific tile.
*
* Returns 0 for success, negative error code otherwise as follow:
* * %-EIO - critical BO or address outside any VRAM region; next action is reset.
@@ -933,7 +954,7 @@ int xe_ttm_vram_handle_addr_fault(struct xe_device *xe, u64 addr)
vram_mgr = &vr->ttm;
mm = &vram_mgr->mm;
- if (xe->ras.disable_vram_page_offline) {
+ if (xe_ras_get_disable_vram_page_offline(xe)) {
xe_err(xe, "0x%llx is reported as corrupted address by HW\n",
addr);
return -EOPNOTSUPP;
@@ -953,7 +974,7 @@ EXPORT_SYMBOL(xe_ttm_vram_handle_addr_fault);
* debugfs interface for testing page offlining.
*
* Note: Executing this test will permanently retire the allocated
- * memory tracking pages. The driver must be rebinded (unbind and bind)
+ * memory tracking pages. The driver must be rebound (unbind and bind)
* post-test execution to reclaim the reserved space, as these pages
* cannot be freed or reclaimed dynamically while the current instance
* remains active.
@@ -968,11 +989,12 @@ int xe_ttm_vram_inject_fault(struct xe_device *xe)
struct gpu_buddy *mm = &vram_mgr->mm;
u64 addr;
- if (vr->actual_physical_size < PAGE_SIZE)
+ /* Stay within the buddy-managed range; CCS/GSM/DSM sit beyond usable_size */
+ if (vr->usable_size < PAGE_SIZE)
return -ENOSPC;
- addr = vr->actual_physical_size - PAGE_SIZE;
- while (addr < vr->actual_physical_size) {
+ addr = ALIGN_DOWN(vr->usable_size - PAGE_SIZE, PAGE_SIZE);
+ while (addr < vr->usable_size) {
struct gpu_buddy_block *block;
bool found = false;
@@ -1011,11 +1033,7 @@ static int vram_bad_pages_show(struct seq_file *m, void *unused)
struct xe_tile *tile;
u8 id;
- man = ttm_manager_type(&xe->ttm, XE_PL_VRAM0);
- if (man)
- /* TODO Hook with RAS to show max_pages fetched from FW */
- seq_printf(m, "max_pages: %d\n",
- to_xe_ttm_vram_mgr(man)->max_pages);
+ seq_printf(m, "max_pages: %u\n", xe_ras_get_max_pages(xe));
for_each_tile(tile, xe, id) {
struct xe_vram_region *vr = tile->mem.vram;
@@ -1025,16 +1043,16 @@ static int vram_bad_pages_show(struct seq_file *m, void *unused)
continue;
mgr = to_xe_ttm_vram_mgr(man);
- rcu_read_lock();
+ mutex_lock(&mgr->lock);
- list_for_each_entry_rcu(pos, &mgr->offlined_pages, offlined_link) {
+ list_for_each_entry(pos, &mgr->offlined_pages, offlined_link) {
u64 pfn;
pfn = (pos->addr + vr->dpa_base) >> PAGE_SHIFT;
seq_printf(m, "0x%016llx : 0x%016lx : R\n", pfn, PAGE_SIZE);
}
- list_for_each_entry_rcu(pos, &mgr->queued_pages, queued_link) {
+ list_for_each_entry(pos, &mgr->queued_pages, queued_link) {
u64 pfn;
pfn = (pos->addr + vr->dpa_base) >> PAGE_SHIFT;
@@ -1042,7 +1060,7 @@ static int vram_bad_pages_show(struct seq_file *m, void *unused)
pfn, PAGE_SIZE, pos->status ? 'F' : 'P');
}
- rcu_read_unlock();
+ mutex_unlock(&mgr->lock);
}
return 0;
diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h b/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
index efcf3e1d4e80..4a2cd27a5b61 100644
--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
+++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
@@ -27,7 +27,7 @@ struct xe_ttm_vram_mgr {
struct list_head queued_pages;
/** @n_queued_pages: Number of queued pages */
u16 n_queued_pages;
- /** @visible_size: Proped size of the CPU visible portion */
+ /** @visible_size: Probed size of the CPU visible portion */
u64 visible_size;
/** @visible_avail: CPU visible portion still unallocated */
u64 visible_avail;
@@ -37,8 +37,6 @@ struct xe_ttm_vram_mgr {
struct mutex lock;
/** @mem_type: The TTM memory type */
u32 mem_type;
- /** @max_pages: max pages that can be in offline queue retrieved from FW */
- u16 max_pages;
};
/**
@@ -81,8 +79,6 @@ struct xe_ttm_vram_offline_resource {
u64 addr;
/** @status: buddy reservation status */
enum xe_page_reserve_status status;
- /** @rcu: RCU head for deferred freeing */
- struct rcu_head rcu;
};
#endif
diff --git a/drivers/gpu/drm/xe/xe_tuning.c b/drivers/gpu/drm/xe/xe_tuning.c
index a9bd3f8969a5..457c6dd1e0da 100644
--- a/drivers/gpu/drm/xe/xe_tuning.c
+++ b/drivers/gpu/drm/xe/xe_tuning.c
@@ -120,7 +120,7 @@ static const struct xe_rtp_table_sr engine_tunings = XE_RTP_TABLE_SR(
},
{ XE_RTP_NAME("Tuning: Disable NULL query for Anyhit Shader"),
XE_RTP_RULES(GRAPHICS_VERSION_RANGE(2000, XE_RTP_END_VERSION_UNDEFINED),
- FUNC(xe_rtp_match_first_render_or_compute)),
+ ENGINE_CLASS(RENDER)),
XE_RTP_ACTIONS(SET(RT_CTRL, DIS_NULL_QUERY))
},
{ XE_RTP_NAME("Tuning: disable HW reporting of ctx switch to GHWSP"),
diff --git a/drivers/gpu/drm/xe/xe_uc_fw.c b/drivers/gpu/drm/xe/xe_uc_fw.c
index f61910431cf2..9a92251e2312 100644
--- a/drivers/gpu/drm/xe/xe_uc_fw.c
+++ b/drivers/gpu/drm/xe/xe_uc_fw.c
@@ -18,6 +18,7 @@
#include "xe_gt_sriov_vf.h"
#include "xe_gt_types.h"
#include "xe_guc.h"
+#include "xe_log.h"
#include "xe_map.h"
#include "xe_mmio.h"
#include "xe_module.h"
@@ -134,6 +135,7 @@ struct fw_blobs_by_type {
fw_def(LUNARLAKE, GT_TYPE_ANY, no_ver(xe, huc, lnl)) \
fw_def(METEORLAKE, GT_TYPE_ANY, no_ver(i915, huc_gsc, mtl)) \
fw_def(DG1, GT_TYPE_ANY, no_ver(i915, huc, dg1)) \
+ fw_def(ALDERLAKE_N, GT_TYPE_ANY, no_ver(i915, huc, tgl)) \
fw_def(ALDERLAKE_P, GT_TYPE_ANY, no_ver(i915, huc, tgl)) \
fw_def(ALDERLAKE_S, GT_TYPE_ANY, no_ver(i915, huc, tgl)) \
fw_def(ROCKETLAKE, GT_TYPE_ANY, no_ver(i915, huc, tgl)) \
@@ -141,7 +143,7 @@ struct fw_blobs_by_type {
/* for the GSC FW we match the compatibility version and not the release one */
#define XE_GSC_FIRMWARE_DEFS(fw_def, major_ver) \
- fw_def(NOVALAKE_S, GT_TYPE_ANY, major_ver(xe, gsc, nvl, 108, 0, 0)) \
+ fw_def(NOVALAKE_S, GT_TYPE_ANY, major_ver(xe, gsc, nvl, 108, 1, 0)) \
fw_def(PANTHERLAKE, GT_TYPE_ANY, major_ver(xe, gsc, ptl, 105, 1, 0)) \
fw_def(LUNARLAKE, GT_TYPE_ANY, major_ver(xe, gsc, lnl, 104, 1, 0)) \
fw_def(METEORLAKE, GT_TYPE_ANY, major_ver(i915, gsc, mtl, 102, 1, 0))
@@ -911,8 +913,12 @@ static int uc_fw_xfer(struct xe_uc_fw *uc_fw, u32 offset, u32 dma_flags)
int xe_uc_fw_upload(struct xe_uc_fw *uc_fw, u32 offset, u32 dma_flags)
{
struct xe_device *xe = uc_fw_to_xe(uc_fw);
+ struct xe_gt *gt = uc_fw_to_gt(uc_fw);
int err;
+ xe_gt_assert(gt, uc_fw->type == XE_UC_FW_TYPE_GUC ||
+ uc_fw->type == XE_UC_FW_TYPE_HUC);
+
/* make sure the status was cleared the last time we reset the uc */
xe_assert(xe, !xe_uc_fw_is_loaded(uc_fw));
@@ -928,9 +934,11 @@ int xe_uc_fw_upload(struct xe_uc_fw *uc_fw, u32 offset, u32 dma_flags)
return 0;
fail:
- drm_err(&xe->drm, "Failed to load %s firmware %s (%d)\n",
- xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
- err);
+ if (uc_fw->type == XE_UC_FW_TYPE_GUC)
+ xe_log_err(gt, GUC, err, "Failed to load firmware %s\n", uc_fw->path);
+ else
+ xe_log_err(gt, HUC, err, "Failed to load firmware %s\n", uc_fw->path);
+
xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_LOAD_FAIL);
return err;
}
diff --git a/drivers/gpu/drm/xe/xe_validation.h b/drivers/gpu/drm/xe/xe_validation.h
index 4cd955ce6cd2..e3a5385ef1c3 100644
--- a/drivers/gpu/drm/xe/xe_validation.h
+++ b/drivers/gpu/drm/xe/xe_validation.h
@@ -48,9 +48,11 @@ static inline void xe_validation_lockdep(void)
* XE_VALIDATION_OPT_OUT is for simplification of kunit tests where
* exhaustive eviction isn't necessary.
*/
+#ifdef CONFIG_DRM_XE_DEBUG
#define __XE_VAL_UNIMPLEMENTED -EINVAL
#define XE_VALIDATION_UNIMPLEMENTED (xe_validation_lockdep(), \
(struct drm_exec *)ERR_PTR(__XE_VAL_UNIMPLEMENTED))
+#endif
#define __XE_VAL_UNSUPPORTED -EOPNOTSUPP
#define XE_VALIDATION_UNSUPPORTED ((struct drm_exec *)ERR_PTR(__XE_VAL_UNSUPPORTED))
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index fb05d525086d..8cd0afb79126 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -30,6 +30,7 @@
#include "xe_exec_queue.h"
#include "xe_gt.h"
#include "xe_migrate.h"
+#include "xe_module.h"
#include "xe_pagefault.h"
#include "xe_pat.h"
#include "xe_pm.h"
@@ -700,7 +701,7 @@ static void xe_vm_clear_fault_entries(struct xe_vm *vm)
vm->faults.len = 0;
}
-static int xe_vma_ops_alloc(struct xe_vma_ops *vops, bool array_of_binds)
+static noinline int xe_vma_ops_alloc(struct xe_vma_ops *vops, bool array_of_binds)
{
int i;
@@ -1268,7 +1269,14 @@ static void vma_destroy_cb(struct dma_fence *fence,
struct xe_vma *vma = container_of(cb, struct xe_vma, destroy_cb);
INIT_WORK(&vma->destroy_work, vma_destroy_work_func);
- queue_work(system_dfl_wq, &vma->destroy_work);
+
+ /*
+ * The destroy work puts a vm reference which may put the last
+ * device reference. Hence we can't queue this work on the device
+ * destroy queue since that may deadlock. Use the module-wide
+ * destroy queue.
+ */
+ xe_destroy_wq_queue(&vma->destroy_work);
}
static void xe_vm_assert_write_mode_or_garbage_collector(struct xe_vm *vm)
@@ -2070,8 +2078,12 @@ static void xe_vm_free(struct drm_gpuvm *gpuvm)
{
struct xe_vm *vm = container_of(gpuvm, struct xe_vm, gpuvm);
- /* To destroy the VM we need to be able to sleep */
- queue_work(system_dfl_wq, &vm->destroy_work);
+ /*
+ * To destroy the VM we need to be able to sleep.
+ * drm_gpuvm keeps at least one device reference at this
+ * point so xe->destroy_wq must still be alive.
+ */
+ queue_work(vm->xe->destroy_wq, &vm->destroy_work);
}
struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id)
diff --git a/drivers/gpu/drm/xe/xe_vram.c b/drivers/gpu/drm/xe/xe_vram.c
index dcedd8cfd731..1c02889ba29b 100644
--- a/drivers/gpu/drm/xe/xe_vram.c
+++ b/drivers/gpu/drm/xe/xe_vram.c
@@ -17,6 +17,7 @@
#include "xe_device.h"
#include "xe_force_wake.h"
#include "xe_gt_mcr.h"
+#include "xe_log.h"
#include "xe_map.h"
#include "xe_migrate.h"
#include "xe_mmio.h"
@@ -46,7 +47,8 @@ static int determine_lmem_bar_size(struct xe_device *xe, struct xe_vram_region *
struct pci_dev *pdev = to_pci_dev(xe->drm.dev);
if (!resource_is_valid(pdev, LMEM_BAR)) {
- drm_err(&xe->drm, "pci resource is not valid\n");
+ xe_log_err_fatal(xe, VRAM, -ENXIO,
+ "PCI resource is not valid\n");
return -ENXIO;
}
@@ -227,23 +229,23 @@ struct xe_vram_region *xe_vram_region_alloc(struct xe_device *xe, u8 id, u32 pla
static void print_vram_region_info(struct xe_device *xe, struct xe_vram_region *vram)
{
- struct drm_device *drm = &xe->drm;
-
if (vram->io_size < vram->usable_size)
- drm_info(drm, "Small BAR device\n");
-
- drm_info(drm,
- "VRAM[%u]: Actual physical size %pa, usable size exclude stolen %pa, CPU accessible size %pa\n",
- vram->id, &vram->actual_physical_size, &vram->usable_size, &vram->io_size);
- drm_info(drm, "VRAM[%u]: DPA range: [%pa-%llx], io range: [%pa-%llx]\n",
- vram->id, &vram->dpa_base, vram->dpa_base + (u64)vram->actual_physical_size,
- &vram->io_start, vram->io_start + (u64)vram->io_size);
+ xe_log_info(xe, VRAM, "Small BAR device\n");
+
+ xe_log_info(xe, VRAM,
+ "[%u]: Actual physical size %pa, usable size exclude stolen %pa, CPU accessible size %pa\n",
+ vram->id, &vram->actual_physical_size, &vram->usable_size, &vram->io_size);
+ xe_log_info(xe, VRAM, "[%u]: DPA range: [%pa-%llx], io range: [%pa-%llx]\n",
+ vram->id, &vram->dpa_base, vram->dpa_base + (u64)vram->actual_physical_size,
+ &vram->io_start, vram->io_start + (u64)vram->io_size);
}
static int vram_region_init(struct xe_device *xe, struct xe_vram_region *vram,
struct xe_vram_region *lmem_bar, u64 offset, u64 usable_size,
u64 region_size, resource_size_t remain_io_size)
{
+ struct xe_tile *tile = vram != xe->mem.vram ? &xe->tiles[vram->id] : NULL;
+
/* Check if VRAM region is already initialized */
if (vram->mapping)
return 0;
@@ -253,11 +255,15 @@ static int vram_region_init(struct xe_device *xe, struct xe_vram_region *vram,
vram->io_size = min_t(u64, usable_size, remain_io_size);
if (!vram->io_size) {
- drm_err(&xe->drm, "Tile without any CPU visible VRAM. Aborting.\n");
+ if (tile)
+ xe_log_err_fatal(tile, VRAM, -ENODEV, "No CPU visible VRAM. Aborting.\n");
+ else
+ xe_log_err_fatal(xe, VRAM, -ENODEV, "No CPU visible VRAM. Aborting.\n");
+
return -ENODEV;
}
- if (vram != xe->mem.vram) {
+ if (tile) {
struct pci_dev *pdev = to_pci_dev(xe->drm.dev);
vram->mapping = devm_ioremap_wc(&pdev->dev, vram->io_start, vram->io_size);
@@ -318,7 +324,8 @@ int xe_vram_probe(struct xe_device *xe)
err = determine_lmem_bar_size(xe, &lmem_bar);
if (err)
return err;
- drm_info(&xe->drm, "VISIBLE VRAM: %pa, %pa\n", &lmem_bar.io_start, &lmem_bar.io_size);
+ xe_log_info(xe, VRAM, "VISIBLE VRAM: start %pa, size %#llx\n",
+ &lmem_bar.io_start, (u64)lmem_bar.io_size);
remain_io_size = lmem_bar.io_size;
@@ -340,8 +347,9 @@ int xe_vram_probe(struct xe_device *xe)
return err;
if (total_size > lmem_bar.io_size) {
- drm_info(&xe->drm, "VRAM: %pa is larger than resource %pa\n",
- &total_size, &lmem_bar.io_size);
+ xe_log_info(xe, VRAM,
+ "Total size %#llx is larger than resource %#llx\n",
+ total_size, (u64)lmem_bar.io_size);
}
remain_io_size -= min_t(u64, tile->mem.vram->actual_physical_size, remain_io_size);