diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-21 13:39:18 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-21 13:39:19 +0100 |
| commit | 3399dbeef25bdb764d6c3aa28bb6fca9061dcb76 (patch) | |
| tree | 20de2cc0aab2f697fd030730f263428eff22bd33 /drivers/gpu/drm | |
| parent | 7a87bc22aff2b8ed731f46ee0aa012df80e47ebc (diff) | |
| parent | 26f7daea5b0667a0af06c60dc204e7cf4856182c (diff) | |
| download | linux-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')
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 = >->uc.guc.submission_state.idm; + mutex_init(>->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(>_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(>->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(>->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(>->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(>->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 = >->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(>->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(>->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 = >->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(¶ms.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(¶ms, 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(>->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(>->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, ®_val); + ret = xe_hwmon_pcode_read_power_limit(hwmon, attr, channel, ®_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, ®_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, ®_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, ®_val); + ret = xe_hwmon_pcode_read_power_limit(hwmon, PL1_HWMON_ATTR, channel, ®_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, ®_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); |
