Merge tag 'drm-xe-next-2026-07-30' of https://gitlab.freedesktop.org/drm/xe/kernel into drm-next

- Wait on external BO kernel fences in exec IOCTL (Brost)
- General clean-up (Anas)
- Documentation fix (Rafael)
- Add a debugfs for pcode information (Karthik)
- Free madvise VMA array on L2 flush failure (Guangshuo)
- Page Table related fixes (Shuicheng, Zongyao)
- Improvements GuC error handling and GuC small fixes (Sk, Zhanjun, Arvind)
- Add new W/As	(Daniele, Harish)
- GuC paging engine support (Auld)
- Add and use more KLV helpers (Michal)
- Balance exec queue suspend/resume (Niranjana, Thomas)
- Fix BO prefetch with CONSULT_MEM_ADVISE_PREF_LOC (Himal)
- SRIOV: Disable display in admin only PF mode (Satya)
- Fix writable override for CRI NVM (Sasha)
- Fix VF CCS attach/detach race with in-flight BO moves (Brost)
- Introduce Xe Uncorrectable Error Handling (Riana)
- Fix WOPCM size for LNL+ (Daniele)
- Consolidate debugfs fault injection functions (Mallesh)
- Multi-queue related fixes and improvements (Niranjana, Jagmeet, Shuicheng)
- Add RAS GPU health indicator (Soham)
- PAT related improvements (Roper, Sanjay)
- NULL deref fix on migration on VF (Satya)
- i2c related fix (Raag)
- Fix SVM leak and clean up xe_vm_create (Shuicheng)
- Drop force_probe requirement for NVL-s (Gustavo)
- Optimise TT population for DONTNEED BOs (Auld)
- Add page size allocation mode control and coverage (Himal, Nareshkumar)

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Rodrigo Vivi <rodrigo.vivi@intel.com>
Link: https://patch.msgid.link/amt2kDVdyBK6VEyU@intel.com
This commit is contained in:
Dave Airlie
2026-08-01 07:06:31 +10:00
88 changed files with 3714 additions and 381 deletions

View File

@@ -0,0 +1,30 @@
What: /sys/bus/pci/drivers/xe/.../gpu_health
Date: July 2026
KernelVersion: 7.3
Contact: intel-xe@lists.freedesktop.org
Description:
This file exposes the current gpu health state and allows the gpu
health state to be updated.
This sysfs file is present only on Intel Xe platforms that support
the gpu health indicator interface for RAS. Reading the current
health state is available to all users, while updating the health
state is restricted to administrative users only.
Read returns a single line containing one of the valid values for
the current gpu health state. Writing one of the valid values
updates the current gpu health state.
The valid values for the gpu health state are:
ok
The gpu is healthy and operating within normal
parameters.
warning
The gpu is experiencing minor issues but remains
operational.
critical
The gpu is in a critical state and may not be
operational.

View File

@@ -8,3 +8,10 @@ Xe Device Wedging
.. kernel-doc:: drivers/gpu/drm/xe/xe_device.c
:doc: Xe Device Wedging
====================
GPU Health Indicator
====================
.. kernel-doc:: drivers/gpu/drm/xe/xe_ras.c
:doc: GPU Health Indicator

View File

@@ -86,6 +86,22 @@ config DRM_XE_KUNIT_TEST
If in doubt, say "N".
config DRM_XE_DEBUG_PAGE_SIZE
bool "Enable debug control for user BO page-size allocation"
depends on DRM_XE_DEBUG && DEBUG_FS
help
Expose a debugfs knob to override user BO page-size allocation
handling for validation and debug. Supported modes include forced
2M, forced 1G, and a mixed mode that exercises 4K, 64K, 2M, and
1G page-size paths on platforms that support them.
This is an unstable debugfs interface intended for development and
validation only. Its layout, contents, and existence may change or
be removed at any time with no regression warranty.
Recommended for driver developers only.
If in doubt, say "N".
config DRM_XE_DEBUG_GUC
bool "Enable extra GuC related debug options"
depends on DRM_XE_DEBUG

View File

@@ -152,6 +152,7 @@ enum xe_guc_action {
XE_GUC_ACTION_REPORT_PAGE_FAULT_REQ_DESC = 0x6002,
XE_GUC_ACTION_PAGE_FAULT_RES_DESC = 0x6003,
XE_GUC_ACTION_ACCESS_COUNTER_NOTIFY = 0x6004,
XE_GUC_ACTION_NOTIFY_UNCORRECTABLE_LOCAL_ERROR = 0x6005,
XE_GUC_ACTION_TLB_INVALIDATION = 0x7000,
XE_GUC_ACTION_TLB_INVALIDATION_DONE = 0x7001,
XE_GUC_ACTION_TLB_INVALIDATION_ALL = 0x7002,

View File

@@ -32,9 +32,10 @@ enum guc_capture_list_class_type {
GUC_CAPTURE_LIST_CLASS_VIDEOENHANCE = 2,
GUC_CAPTURE_LIST_CLASS_BLITTER = 3,
GUC_CAPTURE_LIST_CLASS_GSC_OTHER = 4,
GUC_CAPTURE_LIST_CLASS_PAGING = 5,
};
#define GUC_CAPTURE_LIST_CLASS_MAX (GUC_CAPTURE_LIST_CLASS_GSC_OTHER + 1)
#define GUC_CAPTURE_LIST_CLASS_MAX (GUC_CAPTURE_LIST_CLASS_PAGING + 1)
/**
* struct guc_mmio_reg - GuC MMIO reg state struct

View File

@@ -22,6 +22,7 @@
* | | | - `GuC Scheduling Policies KLVs`_ |
* | | | - `GuC VGT Policy KLVs`_ |
* | | | - `GuC VF Configuration KLVs`_ |
* | | | - `GuC Reserved KLVs`_ |
* | | | |
* | +-------+--------------------------------------------------------------+
* | | 15:0 | **LEN** - length of VALUE (in 32bit dwords) |
@@ -52,6 +53,12 @@
* _`GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE` : 0x3001
* Tells the driver whether scheduler groups are enabled or not.
* Requires GuC ABI 1.26+
*
* _`GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES` : 0x3003
* Tells the driver the paging engine configuration.
* Paging engine logical instances are guaranteed to be dense starting at
* index 0.
* Requires GuC ABI 1.36+
*/
#define GUC_KLV_GLOBAL_CFG_GMD_ID_KEY 0x3000u
@@ -60,6 +67,9 @@
#define GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_KEY 0x3001u
#define GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_LEN 1u
#define GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY 0x3003u
#define GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_LEN 1u
/**
* DOC: GuC Self Config KLVs
*
@@ -154,6 +164,11 @@ enum {
* (instead of waiting the full timeslice duration). The bit is instead set
* to one if a single context is queued on the engine, to avoid it being
* switched out if there isn't another context that can run in its place.
*
* _`GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION` : 0x4004
* This flag will enable notification from GuC to KMD via G2H message
* GUC_ACTION_GUC2HOST_NOTIFY_UNCORRECTABLE_LOCAL_ERROR upon receiving the
* same interrupt from the CS.
*/
#define GUC_KLV_OPT_IN_FEATURE_EXT_CAT_ERR_TYPE_KEY 0x4001
@@ -162,6 +177,9 @@ enum {
#define GUC_KLV_OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH_KEY 0x4003
#define GUC_KLV_OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH_LEN 0u
#define GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION_KEY 0x4004
#define GUC_KLV_OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION_LEN 0u
/**
* DOC: GuC Scheduling Policies KLVs
*
@@ -491,9 +509,10 @@ enum {
#define GUC_KLV_VF_CFG_ENGINE_GROUP_PREEMPT_TIMEOUT_MIN_LEN 1u
#define GUC_KLV_VF_CFG_ENGINE_GROUP_PREEMPT_TIMEOUT_MAX_LEN GUC_MAX_SCHED_GROUPS
/*
* Workaround keys:
* Feature and Workaround keys:
*/
enum xe_guc_klv_ids {
GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE = 0x5001,
GUC_WORKAROUND_KLV_BLOCK_INTERRUPTS_WHEN_MGSR_BLOCKED = 0x9002,
GUC_WORKAROUND_KLV_DISABLE_PSMI_INTERRUPTS_AT_C6_ENTRY_RESTORE_AT_EXIT = 0x9004,
GUC_WORKAROUND_KLV_ID_GAM_PFQ_SHADOW_TAIL_POLLING = 0x9005,
@@ -504,6 +523,24 @@ enum xe_guc_klv_ids {
GUC_WA_KLV_RESET_BB_STACK_PTR_ON_VF_SWITCH = 0x900b,
GUC_WA_KLV_RESTORE_UNSAVED_MEDIA_CONTROL_REG = 0x900c,
GUC_WA_KLV_CLR_CS_INDIRECT_RING_STATE_IF_IDLE_AT_CTX_REG = 0x900e,
GUC_WA_KLV_REMAP_RANGED_TLB_INV = 0x900f,
GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64 = 0x9010,
};
/**
* DOC: GuC Reserved KLVs
*
* Range of `GuC KLV`_ keys reserved for internal use by the GuC that will
* never be part of the offcial GuC ABI and can be reused by the drivers.
*
* Currently this range includes 1024 keys starting from:
*
* _`GUC_KLV_RESERVED_RANGE_START` : 0xF000
*
* See `Xe Driver KLVs`_ for the KLVs that the Xe driver is currently using.
*/
#define GUC_KLV_RESERVED_RANGE_START 0xf000u
#define GUC_KLV_RESERVED_RANGE_LEN 1024u
#endif

View File

@@ -21,7 +21,8 @@
#define GUC_BLITTER_CLASS 3
#define GUC_COMPUTE_CLASS 4
#define GUC_GSC_OTHER_CLASS 5
#define GUC_LAST_ENGINE_CLASS GUC_GSC_OTHER_CLASS
#define GUC_PAGING_CLASS 6
#define GUC_LAST_ENGINE_CLASS GUC_PAGING_CLASS
#define GUC_MAX_ENGINE_CLASSES 16
#define GUC_MAX_INSTANCES_PER_CLASS 32

View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2026 Intel Corporation
*/
#ifndef _ABI_XE_DRIVER_KLVS_ABI_H
#define _ABI_XE_DRIVER_KLVS_ABI_H
#include "abi/guc_klvs_abi.h"
/**
* DOC: Xe Driver KLVs
*
* The Xe driver uses the following keys from the `GuC Reserved KLVs`_ range:
*
* _`MIGRATION_KLV_DEVICE_DEVID_KEY` :
* PCI device ID of the migrated VF.
* _`MIGRATION_KLV_DEVICE_REVID_KEY` :
* PCI device revision ID of the migrated VF.
*/
#define MIGRATION_KLV_DEVICE_DEVID_KEY 0xf001u
#define MIGRATION_KLV_DEVICE_DEVID_LEN 1u
#define MIGRATION_KLV_DEVICE_REVID_KEY 0xf002u
#define MIGRATION_KLV_DEVICE_REVID_LEN 1u
#endif

View File

@@ -15,6 +15,8 @@
#define ENERGY_PKG REG_GENMASK64(31, 0)
#define ENERGY_CARD REG_GENMASK64(63, 32)
#define PUNIT_VERSION_OFFSET 0xA0
#define BMG_TELEMETRY_BASE_OFFSET 0xE0000
#define BMG_TELEMETRY_OFFSET (SOC_BASE + BMG_TELEMETRY_BASE_OFFSET)

View File

@@ -7,6 +7,8 @@
#include "regs/xe_reg_defs.h"
#define SWF_SCRATCHPAD(_idx) XE_REG(0x4f000 + (_idx) * 4)
#define SOC_BASE 0x280000
#define GU_CNTL_PROTECTED XE_REG(0x10100C)

View File

@@ -22,6 +22,231 @@
#include "xe_pci.h"
#include "xe_pm.h"
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
struct page_size_alloc_saved {
enum xe_page_size_alloc_ctrl_mode mode;
u32 cur_index;
};
/* Caller must hold xe->page_size_alloc_ctrl.lock. */
static void page_size_alloc_save(struct xe_device *xe,
struct page_size_alloc_saved *s)
{
s->mode = xe->page_size_alloc_ctrl.mode;
s->cur_index = xe->page_size_alloc_ctrl.cur_index;
}
static void page_size_alloc_restore(struct xe_device *xe,
const struct page_size_alloc_saved *s)
{
mutex_lock(&xe->page_size_alloc_ctrl.lock);
xe->page_size_alloc_ctrl.mode = s->mode;
xe->page_size_alloc_ctrl.cur_index = s->cur_index;
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
}
/* Expected properties for a forced page-size allocation mode. */
struct leaf_info {
u64 leaf;
u64 alloc_size;
u32 flag;
const char *name;
};
static const struct leaf_info leaf_2m = {
.leaf = SZ_2M,
.alloc_size = SZ_2M - PAGE_SIZE,
.flag = XE_BO_FLAG_NEEDS_2M,
.name = "2M",
};
static const struct leaf_info leaf_1g = {
.leaf = SZ_1G,
.alloc_size = SZ_1G - PAGE_SIZE,
.flag = XE_BO_FLAG_NEEDS_1G,
.name = "1G",
};
static void run_only_leaf(struct kunit *test,
enum xe_page_size_alloc_ctrl_mode mode,
const struct leaf_info *li)
{
struct xe_device *xe = test->priv;
struct page_size_alloc_saved saved;
struct xe_bo *bo;
struct ttm_buffer_object *ttm_bo;
u32 other_flags;
if (!IS_DGFX(xe)) {
kunit_skip(test, "requires dGFX VRAM");
return;
}
mutex_lock(&xe->page_size_alloc_ctrl.lock);
page_size_alloc_save(xe, &saved);
xe->page_size_alloc_ctrl.mode = mode;
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
bo = xe_bo_create_user(xe, NULL, li->alloc_size,
DRM_XE_GEM_CPU_CACHING_WC,
XE_BO_FLAG_VRAM0, NULL);
if (IS_ERR(bo)) {
page_size_alloc_restore(xe, &saved);
if (PTR_ERR(bo) == -ENOSPC) {
kunit_skip(test,
"no contiguous %s VRAM available right now",
li->name);
return;
}
KUNIT_FAIL(test, "%s BO alloc failed: %pe", li->name, bo);
return;
}
ttm_bo = &bo->ttm;
/* 1) The mode added the right NEEDS_* flag. */
KUNIT_EXPECT_TRUE_MSG(test, bo->flags & li->flag,
"%s: flag missing, flags=0x%x",
li->name, bo->flags);
/* 2) No other NEEDS_* flags accidentally tagged on. */
other_flags = (XE_BO_FLAG_NEEDS_64K |
XE_BO_FLAG_NEEDS_2M |
XE_BO_FLAG_NEEDS_1G) & ~li->flag;
KUNIT_EXPECT_FALSE_MSG(test, bo->flags & other_flags,
"%s: stray flags=0x%x",
li->name, bo->flags);
/* 3) BO size was rounded up to the expected leaf size. */
KUNIT_EXPECT_EQ_MSG(test, xe_bo_size(bo), li->leaf,
"%s: bo size=%llu expected=%llu",
li->name,
(u64)xe_bo_size(bo),
(u64)li->leaf);
/*
* 4) Allocator honored the requested alignment.
* ttm_bo->page_alignment is stored in PAGE_SIZE units, so compare against
* the expected leaf size converted with >> PAGE_SHIFT.
*/
KUNIT_EXPECT_EQ_MSG(test, ttm_bo->page_alignment,
li->leaf >> PAGE_SHIFT,
"%s: page_alignment=%u pages expected=%llu pages",
li->name, ttm_bo->page_alignment,
(u64)(li->leaf >> PAGE_SHIFT));
xe_bo_put(bo);
page_size_alloc_restore(xe, &saved);
}
static void xe_bo_page_size_alloc_only_2m(struct kunit *test)
{
run_only_leaf(test, XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M, &leaf_2m);
}
static void xe_bo_page_size_alloc_only_1g(struct kunit *test)
{
run_only_leaf(test, XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G, &leaf_1g);
}
static void xe_bo_page_size_alloc_mixed_bos(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct page_size_alloc_saved saved;
struct xe_bo *bo;
struct ttm_buffer_object *ttm_bo;
u32 all_flags = XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M |
XE_BO_FLAG_NEEDS_1G;
u32 flags;
u64 expected_align;
int i;
const int n = 4;
if (!IS_DGFX(xe)) {
kunit_skip(test, "requires dGFX VRAM");
return;
}
mutex_lock(&xe->page_size_alloc_ctrl.lock);
page_size_alloc_save(xe, &saved);
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
for (i = 0; i < n; i++) {
mutex_lock(&xe->page_size_alloc_ctrl.lock);
xe->page_size_alloc_ctrl.mode = XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED;
xe->page_size_alloc_ctrl.cur_index = i;
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
/*
* Request a size valid for any mixed-mode slot. Since cur_index is
* device-global and may be perturbed by concurrent allocations on
* a live system, do not assume this iteration will see a specific
* slot.
*/
bo = xe_bo_create_user(xe, NULL, SZ_1G,
DRM_XE_GEM_CPU_CACHING_WC,
XE_BO_FLAG_VRAM0, NULL);
if (IS_ERR(bo)) {
int err = PTR_ERR(bo);
page_size_alloc_restore(xe, &saved);
if (err == -ENOSPC) {
kunit_skip(test,
"mixed mode BO allocation unavailable: %d",
err);
return;
}
KUNIT_FAIL(test, "iter=%d alloc failed: %pe", i, bo);
return;
}
ttm_bo = &bo->ttm;
flags = bo->flags & all_flags;
/*
* Mixed mode may result in:
* 0-> default platform VRAM alignment
* XE_BO_FLAG_NEEDS_64K
* XE_BO_FLAG_NEEDS_2M
* XE_BO_FLAG_NEEDS_1G
* Any other combination is invalid.
*/
if (flags == 0) {
expected_align = SZ_4K;
if (xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K)
expected_align = SZ_64K;
} else if (flags == XE_BO_FLAG_NEEDS_64K) {
expected_align = SZ_64K;
} else if (flags == XE_BO_FLAG_NEEDS_2M) {
expected_align = SZ_2M;
} else if (flags == XE_BO_FLAG_NEEDS_1G) {
expected_align = SZ_1G;
} else {
KUNIT_FAIL(test,
"iter=%d invalid mixed-mode flags: 0x%x",
i, flags);
xe_bo_put(bo);
page_size_alloc_restore(xe, &saved);
return;
}
/*
* BO size should remain valid for the selected mode. Since the
* request is SZ_1G, it should remain unchanged regardless of the
* selected page-size policy.
*/
KUNIT_EXPECT_EQ_MSG(test, xe_bo_size(bo), (u64)SZ_1G,
"iter=%d size=%llu expected=%llu",
i,
(u64)xe_bo_size(bo),
(u64)SZ_1G);
KUNIT_EXPECT_EQ_MSG(test, ttm_bo->page_alignment,
expected_align >> PAGE_SHIFT,
"iter=%d flags=0x%x page_alignment=%u pages expected=%llu pages",
i, flags, ttm_bo->page_alignment,
(u64)(expected_align >> PAGE_SHIFT));
xe_bo_put(bo);
}
page_size_alloc_restore(xe, &saved);
}
#endif
static int ccs_test_migrate(struct xe_tile *tile, struct xe_bo *bo,
bool clear, u64 get_val, u64 assign_val,
struct kunit *test, struct drm_exec *exec)
@@ -609,6 +834,23 @@ static struct kunit_case xe_bo_tests[] = {
{}
};
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
static struct kunit_case xe_bo_page_size_alloc_cases[] = {
KUNIT_CASE_PARAM(xe_bo_page_size_alloc_only_2m, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_bo_page_size_alloc_only_1g, xe_pci_live_device_gen_param),
KUNIT_CASE_PARAM(xe_bo_page_size_alloc_mixed_bos, xe_pci_live_device_gen_param),
{}
};
VISIBLE_IF_KUNIT
struct kunit_suite xe_bo_page_size_alloc_suite = {
.name = "xe_bo_page_size_alloc",
.test_cases = xe_bo_page_size_alloc_cases,
.init = xe_kunit_helper_xe_device_live_test_init,
};
EXPORT_SYMBOL_IF_KUNIT(xe_bo_page_size_alloc_suite);
#endif
VISIBLE_IF_KUNIT
struct kunit_suite xe_bo_test_suite = {
.name = "xe_bo",

View File

@@ -0,0 +1,429 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2026 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/test-bug.h>
#define TEST_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3de)
#define TEST_GROUP_KEY (GUC_KLV_RESERVED_RANGE_START + 0x3f0)
#define TEST_PAD 0xdeadbeef
static bool fake_is_group_key(u16 key)
{
return is_reserved_key(key) && key >= TEST_GROUP_KEY;
}
static void test_count(struct kunit *test)
{
u32 value = 0x12345678;
u16 key = TEST_KEY;
u32 klvs[] = {
PREP_GUC_KLV(key + 0, 0),
PREP_GUC_KLV(key + 1, 1), value,
PREP_GUC_KLV(key + 2, 2), value, value,
PREP_GUC_KLV(key + 3, 0),
0, /* padding */
};
KUNIT_EXPECT_EQ(test, 0, xe_guc_klv_count(klvs, 0));
KUNIT_EXPECT_EQ(test, 1, xe_guc_klv_count(klvs, 1));
KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 2));
KUNIT_EXPECT_EQ(test, 2, xe_guc_klv_count(klvs, 3));
KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 4));
KUNIT_EXPECT_GT(test, 0, xe_guc_klv_count(klvs, 5));
KUNIT_EXPECT_EQ(test, 3, xe_guc_klv_count(klvs, 6));
KUNIT_EXPECT_EQ(test, 4, xe_guc_klv_count(klvs, 7));
/* 0 is treated as reserved KLV { KEY=0, LEN=0 } */
KUNIT_EXPECT_EQ(test, 5, xe_guc_klv_count(klvs, 8));
}
static void test_encode_u32(struct kunit *test)
{
u32 *fail = ERR_PTR(-ENOMEM);
u32 value = 0x12345678;
u16 key = TEST_KEY;
u32 klvs[16];
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u32(klvs, 0, key, value));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u32(klvs, 1, key, value));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe_guc_klv_encode_u32(klvs, 2, key, value));
KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, 1));
KUNIT_EXPECT_EQ(test, klvs[1], value);
KUNIT_EXPECT_EQ(test, klvs[2], TEST_PAD);
KUNIT_EXPECT_PTR_EQ(test, &klvs[2], xe_guc_klv_encode_u32(klvs, 2, key, value));
KUNIT_EXPECT_PTR_EQ(test,
xe_guc_klv_encode_u32(klvs, 2, key, value),
xe_guc_klv_encode_u32(klvs, ARRAY_SIZE(klvs), key, value));
KUNIT_ASSERT_PTR_EQ(test, fail, xe_guc_klv_encode_u32(fail, ARRAY_SIZE(klvs), key, value));
}
static void test_encode_u64(struct kunit *test)
{
u64 value = 0x123456789abcdef0;
u32 *fail = ERR_PTR(-ENOMEM);
u16 key = TEST_KEY;
u32 klvs[16];
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 0, key, value));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 1, key, value));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC), xe_guc_klv_encode_u64(klvs, 2, key, value));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, xe_guc_klv_encode_u64(klvs, 3, key, value));
KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, 2));
KUNIT_EXPECT_EQ(test, klvs[1], lower_32_bits(value));
KUNIT_EXPECT_EQ(test, klvs[2], upper_32_bits(value));
KUNIT_EXPECT_EQ(test, klvs[3], TEST_PAD);
KUNIT_EXPECT_PTR_EQ(test, &klvs[3], xe_guc_klv_encode_u64(klvs, 3, key, value));
KUNIT_EXPECT_PTR_EQ(test,
xe_guc_klv_encode_u64(klvs, 3, key, value),
xe_guc_klv_encode_u64(klvs, ARRAY_SIZE(klvs), key, value));
KUNIT_ASSERT_PTR_EQ(test, fail, xe_guc_klv_encode_u64(fail, ARRAY_SIZE(klvs), key, value));
}
static u32 str_klv_size(const char *string)
{
return GUC_KLV_LEN_MIN + to_num_dwords(strlen(string) + 1);
}
static void test_encode_string(struct kunit *test)
{
size_t longest_str = to_num_bytes(FIELD_MAX(GUC_KLV_0_LEN)) - 1;
u32 avail = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN) + 1;
const char *string = "abcdefghijklmnopqrstvwxyz";
u16 key = TEST_KEY;
u32 *klvs;
u32 *next;
char *buf;
u32 n;
klvs = kunit_kcalloc(test, avail, sizeof(u32), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, klvs);
buf = kunit_kzalloc(test, longest_str + 2, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
/* empty string, no space, must fail */
for (n = 0; n < str_klv_size(""); n++) {
klvs[0] = TEST_PAD;
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_string(klvs, n, key, ""));
KUNIT_EXPECT_EQ(test, klvs[0], TEST_PAD);
}
/* empty string, must pass */
KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(""),
xe_guc_klv_encode_string(klvs, str_klv_size(""), key, ""));
KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(""),
xe_guc_klv_encode_string(klvs, avail, key, ""));
/* demo string, no space, must fail */
for (n = 0; n < str_klv_size(string); n++) {
klvs[0] = TEST_PAD;
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_string(klvs, n, key, string));
KUNIT_EXPECT_EQ(test, klvs[0], TEST_PAD);
}
/* different string len, must pass */
for (n = 0; n <= strlen(string); n++) {
strscpy(buf, string, n + 1);
kunit_info(test, "%u: '%s'\n", n, buf);
KUNIT_ASSERT_EQ(test, n, strlen(buf));
memset32(klvs, TEST_PAD, avail);
next = xe_guc_klv_encode_string(klvs, str_klv_size(buf), key, buf);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, next);
KUNIT_EXPECT_PTR_EQ(test, next, klvs + str_klv_size(buf));
KUNIT_EXPECT_STREQ_MSG(test, buf, (char *)(klvs + GUC_KLV_LEN_MIN), "n=%u", n);
kunit_info(test, "%u: %*ph\n", n, (int)to_num_bytes(next - klvs), klvs);
KUNIT_EXPECT_NE(test, *(next - 1), TEST_PAD);
KUNIT_ASSERT_EQ(test, *next, TEST_PAD);
/* bigger buf doesn't matter */
KUNIT_EXPECT_PTR_EQ(test,
xe_guc_klv_encode_string(klvs, str_klv_size(buf), key, buf),
xe_guc_klv_encode_string(klvs, avail, key, buf));
}
/* don't crash if already failed */
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EROFS),
xe_guc_klv_encode_string(ERR_PTR(-EROFS), avail, key, ""));
/* too long string, must fail */
memset(buf, 'X', longest_str + 1);
buf[longest_str + 1] = '\0';
KUNIT_EXPECT_LT(test, longest_str, strlen(buf));
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-E2BIG),
xe_guc_klv_encode_string(klvs, avail, key, buf));
/* longest string, should pass */
buf[longest_str] = '\0';
KUNIT_EXPECT_EQ(test, longest_str, strlen(buf));
KUNIT_EXPECT_PTR_EQ(test, klvs + str_klv_size(buf),
xe_guc_klv_encode_string(klvs, avail, key, buf));
}
struct some_object {
u32 value1;
u64 value2;
} __packed;
static u32 *obj_raw_encoder(u32 *klvs, u32 avail, const void *arg)
{
const struct some_object *obj = arg;
size_t sz = sizeof(*obj);
u32 dwords = to_num_dwords(sz);
if (IS_ERR(klvs))
return klvs;
if (dwords > avail)
return ERR_PTR(-ENOSPC);
memcpy(klvs, obj, sz);
return klvs + dwords;
}
static u32 *obj_klv_encoder(u32 *klvs, u32 avail, const void *arg)
{
const struct some_object *obj = arg;
u32 *end = klvs + avail;
klvs = xe_guc_klv_encode_u32(klvs, end - klvs, TEST_KEY + 1, obj->value1);
klvs = xe_guc_klv_encode_u64(klvs, end - klvs, TEST_KEY + 2, obj->value2);
return klvs;
}
static u32 *obj_nested_encoder(u32 *klvs, u32 avail, const void *arg)
{
u32 *end = klvs + avail;
klvs = xe_guc_klv_encode_object(klvs, end - klvs, TEST_GROUP_KEY + 1,
arg, obj_klv_encoder);
klvs = xe_guc_klv_encode_object(klvs, end - klvs, TEST_GROUP_KEY + 2,
arg, obj_klv_encoder);
return klvs;
}
static void test_encode_object_raw(struct kunit *test)
{
const struct some_object obj = {
.value1 = 0xdead1234,
.value2 = 0xdead87654321dead,
};
u32 payload = to_num_dwords(sizeof(obj));
u32 *err = ERR_PTR(-ENOSPC);
u16 key = TEST_KEY;
u32 klvs[16];
u32 n;
/* too small, must fail */
for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) {
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_object(klvs, n, key, &obj,
obj_raw_encoder),
"buf size=%u dwords", n);
}
/* must pass */
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test,
xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload,
key, &obj, obj_raw_encoder));
KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload));
KUNIT_EXPECT_MEMEQ(test, &klvs[1], &obj, sizeof(obj));
/* already failed, must fail */
KUNIT_ASSERT_PTR_EQ(test, err,
xe_guc_klv_encode_object(err, ARRAY_SIZE(klvs), key,
&obj, obj_raw_encoder));
}
static void test_encode_object_klv(struct kunit *test)
{
const struct some_object obj = {
.value1 = 0xdead1234,
.value2 = 0xdead87654321dead,
};
u16 key = TEST_GROUP_KEY;
u32 payload = 0;
u32 klvs[16];
u32 n;
payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value1));
payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value2));
/* too small, must fail */
for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) {
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_object(klvs, n, key, &obj,
obj_klv_encoder),
"buf size=%u dwords", n);
}
/* must pass */
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test,
xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload,
key, &obj, obj_klv_encoder));
KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload));
KUNIT_EXPECT_EQ(test, klvs[1], PREP_GUC_KLV(TEST_KEY + 1, 1));
KUNIT_EXPECT_EQ(test, klvs[2], obj.value1);
KUNIT_EXPECT_EQ(test, klvs[3], PREP_GUC_KLV(TEST_KEY + 2, 2));
KUNIT_EXPECT_EQ(test, klvs[4], lower_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[5], upper_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[6], TEST_PAD);
}
static void test_encode_object_nested(struct kunit *test)
{
const struct some_object obj = {
.value1 = 0xdead1234,
.value2 = 0xdead87654321dead,
};
u16 key = TEST_GROUP_KEY;
u32 payload = 0;
u32 klvs[16];
u32 n;
payload += GUC_KLV_LEN_MIN;
payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value1));
payload += GUC_KLV_LEN_MIN + to_num_dwords(sizeof(obj.value2));
payload *= 2;
/* too small, must fail */
for (n = 0; n < GUC_KLV_LEN_MIN + payload; n++) {
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_EXPECT_PTR_EQ_MSG(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_object(klvs, n, key, &obj,
obj_nested_encoder),
"buf size=%u dwords", n);
}
/* must pass */
memset32(klvs, TEST_PAD, ARRAY_SIZE(klvs));
KUNIT_ASSERT_NOT_ERR_OR_NULL(test,
xe_guc_klv_encode_object(klvs, GUC_KLV_LEN_MIN + payload,
key, &obj, obj_nested_encoder));
KUNIT_EXPECT_EQ(test, klvs[0], PREP_GUC_KLV(key, payload));
KUNIT_EXPECT_EQ(test, klvs[1], PREP_GUC_KLV(TEST_GROUP_KEY + 1, 5));
KUNIT_EXPECT_EQ(test, klvs[2], PREP_GUC_KLV(TEST_KEY + 1, 1));
KUNIT_EXPECT_EQ(test, klvs[3], obj.value1);
KUNIT_EXPECT_EQ(test, klvs[4], PREP_GUC_KLV(TEST_KEY + 2, 2));
KUNIT_EXPECT_EQ(test, klvs[5], lower_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[6], upper_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[7], PREP_GUC_KLV(TEST_GROUP_KEY + 2, 5));
KUNIT_EXPECT_EQ(test, klvs[8], PREP_GUC_KLV(TEST_KEY + 1, 1));
KUNIT_EXPECT_EQ(test, klvs[9], obj.value1);
KUNIT_EXPECT_EQ(test, klvs[10], PREP_GUC_KLV(TEST_KEY + 2, 2));
KUNIT_EXPECT_EQ(test, klvs[11], lower_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[12], upper_32_bits(obj.value2));
KUNIT_EXPECT_EQ(test, klvs[13], TEST_PAD);
}
static u32 *obj_echo_encoder(u32 *klvs, u32 avail, const void *arg)
{
return ERR_CAST(arg);
}
static void test_encode_object_basic(struct kunit *test)
{
u32 longest = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN);
u32 avail = GUC_KLV_LEN_MIN + longest;
u16 key = TEST_GROUP_KEY;
u32 *klvs;
klvs = kunit_kcalloc(test, avail, sizeof(u32), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, klvs);
/* smallest */
KUNIT_EXPECT_PTR_EQ(test, klvs + GUC_KLV_LEN_MIN,
xe_guc_klv_encode_object(klvs, avail, key,
klvs + GUC_KLV_LEN_MIN,
obj_echo_encoder));
/* largest */
KUNIT_EXPECT_PTR_EQ(test, klvs + longest,
xe_guc_klv_encode_object(klvs, avail, key,
klvs + longest,
obj_echo_encoder));
/* already failed */
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EROFS),
xe_guc_klv_encode_object(ERR_PTR(-EROFS), avail, key,
klvs + GUC_KLV_LEN_MIN,
obj_echo_encoder));
/* encoding error */
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-EUCLEAN),
xe_guc_klv_encode_object(klvs, avail, key,
ERR_PTR(-EUCLEAN),
obj_echo_encoder));
/* no space */
KUNIT_EXPECT_PTR_EQ(test, ERR_PTR(-ENOSPC),
xe_guc_klv_encode_object(klvs, 0, key,
klvs + GUC_KLV_LEN_MIN,
obj_echo_encoder));
}
static void __drm_printfn_kunit(struct drm_printer *p, struct va_format *vaf)
{
struct kunit *test = p->arg;
kunit_info(test, "%pV", vaf);
}
static struct drm_printer drm_kunit_printer(void)
{
struct drm_printer p = {
.printfn = __drm_printfn_kunit,
.arg = kunit_get_current_test(),
};
return p;
}
static void test_print(struct kunit *test)
{
struct drm_printer p = drm_kunit_printer();
u32 zeros[] = { 0, 0, 0, /* padding */ };
u32 klvs[] = {
PREP_GUC_KLV(GUC_KLV_OPT_IN_FEATURE_EXT_CAT_ERR_TYPE_KEY, 0),
PREP_GUC_KLV(GUC_KLV_VF_CFG_NUM_CONTEXTS_KEY, 1), 1234,
PREP_GUC_KLV(GUC_KLV_VF_CFG_GGTT_SIZE_KEY, 2), 0x4000, 0x0123,
PREP_GUC_KLV(TEST_KEY, 3), 1, 2, 3,
PREP_GUC_KLV(TEST_GROUP_KEY, 5),
PREP_GUC_KLV(TEST_KEY + 1, 1), 1,
PREP_GUC_KLV(TEST_KEY + 2, 2), 1, 2,
};
kunit_activate_static_stub(test, is_group_key, fake_is_group_key);
xe_guc_klv_print(zeros, ARRAY_SIZE(zeros), &p);
xe_guc_klv_print(klvs, ARRAY_SIZE(klvs), &p);
}
static struct kunit_case guc_klv_helpers_test_cases[] = {
KUNIT_CASE(test_count),
KUNIT_CASE(test_encode_u32),
KUNIT_CASE(test_encode_u64),
KUNIT_CASE(test_encode_string),
KUNIT_CASE(test_encode_object_raw),
KUNIT_CASE(test_encode_object_klv),
KUNIT_CASE(test_encode_object_nested),
KUNIT_CASE(test_encode_object_basic),
KUNIT_CASE(test_print),
{}
};
static struct kunit_suite guc_klv_helpers_suite = {
.name = "guc_klv_helpers",
.test_cases = guc_klv_helpers_test_cases,
};
kunit_test_suite(guc_klv_helpers_suite);

View File

@@ -11,6 +11,9 @@ extern struct kunit_suite xe_dma_buf_test_suite;
extern struct kunit_suite xe_migrate_test_suite;
extern struct kunit_suite xe_mocs_test_suite;
extern struct kunit_suite xe_guc_g2g_test_suite;
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
extern struct kunit_suite xe_bo_page_size_alloc_suite;
#endif
kunit_test_suite(xe_bo_test_suite);
kunit_test_suite(xe_bo_shrink_test_suite);
@@ -18,6 +21,9 @@ kunit_test_suite(xe_dma_buf_test_suite);
kunit_test_suite(xe_migrate_test_suite);
kunit_test_suite(xe_mocs_test_suite);
kunit_test_suite(xe_guc_g2g_test_suite);
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
kunit_test_suite(xe_bo_page_size_alloc_suite);
#endif
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");

View File

@@ -9,7 +9,6 @@
#include <kunit/test-bug.h>
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include <kunit/visibility.h>
#define PLATFORM_CASE(platform__, graphics_step__) \

View File

@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0 AND MIT
/*
* Copyright © 2026 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/test-bug.h>
#include "xe_device.h"
#include "xe_guc_klv_helpers.h"
#include "xe_kunit_helpers.h"
#include "xe_pci_test.h"
#define TEST_VF VFID(1)
static int sriov_packet_test_init(struct kunit *test)
{
struct xe_pci_fake_data fake = {
.sriov_mode = XE_SRIOV_MODE_PF,
.platform = XE_PANTHERLAKE, /* we need MEMIRQ */
.subplatform = XE_SUBPLATFORM_NONE,
.graphics_verx100 = 3000,
.media_verx100 = 3000,
};
struct xe_device *xe;
test->priv = &fake;
xe_kunit_helper_xe_device_test_init(test);
xe = test->priv;
/* pretend we can support at least VF1 */
xe->sriov.pf.device_total_vfs = 1;
xe->sriov.pf.driver_max_vfs = 1;
KUNIT_ASSERT_EQ(test, 0, xe_sriov_init(xe));
KUNIT_ASSERT_TRUE(test, xe_sriov_pf_migration_supported(xe));
return 0;
}
static void test_descriptor_init(struct kunit *test)
{
struct xe_device *xe = test->priv;
struct xe_sriov_packet **desc;
/* note: with lock held we should avoid KUNIT_ASSERT() */
guard(mutex)(pf_migration_mutex(xe, TEST_VF));
KUNIT_EXPECT_EQ(test, 0, pf_descriptor_init(xe, TEST_VF));
desc = pf_pick_descriptor(xe, TEST_VF);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, *desc);
if (!*desc)
return;
KUNIT_EXPECT_NE(test, (*desc)->hdr.version, 0);
KUNIT_EXPECT_EQ(test, (*desc)->hdr.version, XE_SRIOV_PACKET_SUPPORTED_VERSION);
KUNIT_EXPECT_EQ(test, (*desc)->hdr.type, XE_SRIOV_PACKET_TYPE_DESCRIPTOR);
KUNIT_EXPECT_NE(test, (*desc)->hdr.size, 0);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, (*desc)->vaddr);
if (!(*desc)->vaddr)
return;
KUNIT_EXPECT_EQ(test, 0, xe_sriov_packet_process_descriptor(xe, TEST_VF, *desc));
switch ((*desc)->hdr.version) {
case 1:
/* v1 is KLV based */
KUNIT_EXPECT_TRUE(test, IS_ALIGNED((*desc)->hdr.size, sizeof(u32)));
/* v1 has at least DEVID and REVID KLVs */
KUNIT_EXPECT_LE(test, 2,
xe_guc_klv_count((*desc)->vaddr,
(*desc)->hdr.size / sizeof(u32)));
break;
default:
kunit_mark_skipped(test, "no test code for version %u\n", (*desc)->hdr.version);
return;
}
}
static struct kunit_case sriov_packet_test_cases[] = {
KUNIT_CASE(test_descriptor_init),
{}
};
static struct kunit_suite sriov_packet_suite = {
.name = "sriov_packet",
.test_cases = sriov_packet_test_cases,
.init = sriov_packet_test_init,
};
kunit_test_suite(sriov_packet_suite);

View File

@@ -341,6 +341,18 @@ static void xe_evict_flags(struct ttm_buffer_object *tbo,
return;
}
if (xe_bo_madv_is_dontneed(bo)) {
/*
* We can't use purge_placement here, since we need to trigger
* our own purge procedure at the start of xe_bo_move(), which
* would otherwise be skipped. At the same time we don't want
* ttm to then populate the tt with dst pages, before the move
* callback, hence use sys_placement here.
*/
*placement = sys_placement;
return;
}
/*
* For xe, sg bos that are evicted to system just triggers a
* rebind of the sg list upon subsequent validation to XE_PL_TT.
@@ -1102,6 +1114,21 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
xe_pm_runtime_get_noresume(xe);
}
/*
* Attach CCS BBs before submitting the copy job below so a VF
* migration racing the copy sees valid, up to date attach state.
*/
if (IS_VF_CCS_READY(xe) &&
((move_lacks_source && new_mem->mem_type == XE_PL_TT) ||
(old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) &&
handle_system_ccs) {
ret = xe_sriov_vf_ccs_attach_bo(bo, new_mem);
if (ret) {
xe_pm_runtime_put(xe);
goto out;
}
}
if (move_lacks_source) {
u32 flags = 0;
@@ -1139,22 +1166,19 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict,
ttm_bo_move_null(ttm_bo, new_mem);
}
dma_fence_put(fence);
xe_pm_runtime_put(xe);
/*
* CCS meta data is migrated from TT -> SMEM. So, let us detach the
* BBs from BO as it is no longer needed.
* Detach must wait for the copy above to complete: a VF migration
* racing an in-flight copy must still see valid CCS BBs, so don't
* tear them down until the copy fence has signaled.
*/
if (IS_VF_CCS_READY(xe) && old_mem_type == XE_PL_TT &&
new_mem->mem_type == XE_PL_SYSTEM)
new_mem->mem_type == XE_PL_SYSTEM) {
dma_fence_wait(fence, false);
xe_sriov_vf_ccs_detach_bo(bo);
}
if (IS_VF_CCS_READY(xe) &&
((move_lacks_source && new_mem->mem_type == XE_PL_TT) ||
(old_mem_type == XE_PL_SYSTEM && new_mem->mem_type == XE_PL_TT)) &&
handle_system_ccs)
ret = xe_sriov_vf_ccs_attach_bo(bo);
dma_fence_put(fence);
xe_pm_runtime_put(xe);
out:
if ((!ttm_bo->resource || ttm_bo->resource->mem_type == XE_PL_SYSTEM) &&
@@ -1349,7 +1373,7 @@ int xe_bo_notifier_prepare_pinned(struct xe_bo *bo)
backup = xe_bo_init_locked(xe, NULL, NULL, bo->ttm.base.resv, NULL, xe_bo_size(bo),
DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel,
XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED, &exec);
XE_BO_FLAG_PINNED, NULL, &exec);
if (IS_ERR(backup)) {
drm_exec_retry_on_contention(&exec);
ret = PTR_ERR(backup);
@@ -1490,7 +1514,7 @@ int xe_bo_evict_pinned(struct xe_bo *bo)
xe_bo_size(bo),
DRM_XE_GEM_CPU_CACHING_WB, ttm_bo_type_kernel,
XE_BO_FLAG_SYSTEM | XE_BO_FLAG_NEEDS_CPU_ACCESS |
XE_BO_FLAG_PINNED, &exec);
XE_BO_FLAG_PINNED, NULL, &exec);
if (IS_ERR(backup)) {
drm_exec_retry_on_contention(&exec);
ret = PTR_ERR(backup);
@@ -1826,6 +1850,8 @@ static void xe_ttm_bo_destroy(struct ttm_buffer_object *ttm_bo)
if (bo->ttm.base.import_attach)
drm_prime_gem_destroy(&bo->ttm.base, NULL);
if (bo->dma_buf)
dma_buf_put(bo->dma_buf);
drm_gem_object_release(&bo->ttm.base);
xe_assert(xe, list_empty(&ttm_bo->base.gpuva.list));
@@ -2283,6 +2309,8 @@ void xe_bo_free(struct xe_bo *bo)
* @cpu_caching: The cpu caching used for system memory backing store.
* @type: The TTM buffer object type.
* @flags: XE_BO_FLAG_ flags.
* @dma_buf: The dma-buf to reference for the BO lifetime (imported BOs),
* or NULL.
* @exec: The drm_exec transaction to use for exhaustive eviction.
*
* Initialize or create an xe buffer object. On failure, any allocated buffer
@@ -2294,7 +2322,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
struct xe_tile *tile, struct dma_resv *resv,
struct ttm_lru_bulk_move *bulk, size_t size,
u16 cpu_caching, enum ttm_bo_type type,
u32 flags, struct drm_exec *exec)
u32 flags, struct dma_buf *dma_buf,
struct drm_exec *exec)
{
struct ttm_operation_ctx ctx = {
.interruptible = true,
@@ -2323,8 +2352,16 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
if (flags & (XE_BO_FLAG_VRAM_MASK | XE_BO_FLAG_STOLEN) &&
!(flags & XE_BO_FLAG_IGNORE_MIN_PAGE_SIZE) &&
((xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K) ||
(flags & (XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M)))) {
size_t align = flags & XE_BO_FLAG_NEEDS_2M ? SZ_2M : SZ_64K;
(flags & (XE_BO_FLAG_NEEDS_64K | XE_BO_FLAG_NEEDS_2M |
XE_BO_FLAG_NEEDS_1G)))) {
size_t align;
if (flags & XE_BO_FLAG_NEEDS_1G)
align = SZ_1G;
else if (flags & XE_BO_FLAG_NEEDS_2M)
align = SZ_2M;
else
align = SZ_64K;
aligned_size = ALIGN(size, align);
if (type != ttm_bo_type_device)
@@ -2383,6 +2420,17 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
placement = (type == ttm_bo_type_sg ||
bo->flags & XE_BO_FLAG_DEFER_BACKING) ? &sys_placement :
&bo->placement;
/*
* For imported BOs, keep the exporter dma-buf alive for the BO
* lifetime. Taken before ttm_bo_init_reserved() to also cover a
* creation failure there. Released in xe_ttm_bo_destroy().
*/
if (dma_buf) {
get_dma_buf(dma_buf);
bo->dma_buf = dma_buf;
}
err = ttm_bo_init_reserved(&xe->ttm, &bo->ttm, type,
placement, alignment,
&ctx, NULL, resv, xe_ttm_bo_destroy);
@@ -2500,7 +2548,7 @@ __xe_bo_create_locked(struct xe_device *xe,
vm && !xe_vm_in_fault_mode(vm) &&
flags & XE_BO_FLAG_USER ?
&vm->lru_bulk_move : NULL, size,
cpu_caching, type, flags, exec);
cpu_caching, type, flags, NULL, exec);
if (IS_ERR(bo))
return bo;
@@ -2604,6 +2652,145 @@ static struct xe_bo *xe_bo_create_novm(struct xe_device *xe, struct xe_tile *til
return ret ? ERR_PTR(ret) : bo;
}
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
static void xe_bo_debug_mixed_mode_cur_index_advance(struct xe_device *xe, struct xe_bo *bo)
{
if (!xe_debug_page_size_mode_is_mixed(xe))
return;
if (!(bo->flags & XE_BO_FLAG_VRAM_MASK) ||
!(bo->flags & XE_BO_FLAG_USER))
return;
mutex_lock(&xe->page_size_alloc_ctrl.lock);
if (xe->page_size_alloc_ctrl.mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED)
xe->page_size_alloc_ctrl.cur_index++;
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
}
static bool xe_size_align_overflows(size_t size, size_t align)
{
return size > SIZE_MAX - (align - 1);
}
static u32 get_flag_from_cur_index_in_mixed_mode(struct xe_device *xe, size_t *align_size,
int *err)
{
static const struct {
u32 flag;
size_t align;
} map[] = {
{ 0, SZ_4K }, /* default: 4K, no flag */
{ XE_BO_FLAG_NEEDS_64K, SZ_64K },
{ XE_BO_FLAG_NEEDS_2M, SZ_2M },
{ XE_BO_FLAG_NEEDS_1G, SZ_1G },
};
u32 idx;
const typeof(*map) *entry;
lockdep_assert_held(&xe->page_size_alloc_ctrl.lock);
*err = 0;
idx = xe->page_size_alloc_ctrl.cur_index % ARRAY_SIZE(map);
entry = &map[idx];
if (!entry->flag)
return 0;
if (xe_size_align_overflows(*align_size, entry->align)) {
*err = -EINVAL;
return 0;
}
*align_size = ALIGN(*align_size, entry->align);
return entry->flag;
}
static int xe_bo_apply_debug_page_size_policy(struct xe_device *xe,
u32 *bo_flags,
size_t *size)
{
enum xe_page_size_alloc_ctrl_mode mode;
u32 want = 0;
size_t align_size = *size;
int err = 0;
/*
* The debug page-size policy is only meaningful for BOs placed in
* VRAM, where the downstream BO init path can
* actually honor the corresponding minimum page-size requirement.
*/
if (!(*bo_flags & XE_BO_FLAG_VRAM_MASK))
return 0;
/*
* Do not override existing page-size requirement flags, since they
* may reflect functional requirements for specific BO types.
*/
if (*bo_flags & (XE_BO_FLAG_NEEDS_64K |
XE_BO_FLAG_NEEDS_2M |
XE_BO_FLAG_NEEDS_1G))
return 0;
if (!READ_ONCE(xe->page_size_alloc_ctrl.mode))
return 0;
mutex_lock(&xe->page_size_alloc_ctrl.lock);
mode = xe->page_size_alloc_ctrl.mode;
if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE) {
goto out_unlock;
} else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M) {
if (xe_size_align_overflows(align_size, SZ_2M)) {
err = -EINVAL;
goto out_unlock;
}
want = XE_BO_FLAG_NEEDS_2M;
align_size = ALIGN(align_size, SZ_2M);
} else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G) {
if (xe_size_align_overflows(align_size, SZ_1G)) {
err = -EINVAL;
goto out_unlock;
}
want = XE_BO_FLAG_NEEDS_1G;
align_size = ALIGN(align_size, SZ_1G);
} else if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED) {
want = get_flag_from_cur_index_in_mixed_mode(xe, &align_size, &err);
if (err)
goto out_unlock;
} else {
goto out_unlock;
}
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
*bo_flags |= want;
/*
* Apply the debug page-size policy by rounding the user BO size up to
* the selected granularity.
*/
*size = align_size;
return err;
out_unlock:
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
return err;
}
#else
static int xe_bo_apply_debug_page_size_policy(struct xe_device *xe,
u32 *bo_flags,
size_t *size)
{
return 0;
}
static void xe_bo_debug_mixed_mode_cur_index_advance(struct xe_device *xe,
struct xe_bo *bo)
{
}
#endif
/**
* xe_bo_create_user() - Create a user BO
* @xe: The xe device.
@@ -2624,9 +2811,16 @@ struct xe_bo *xe_bo_create_user(struct xe_device *xe,
u32 flags, struct drm_exec *exec)
{
struct xe_bo *bo;
int err = 0;
flags |= XE_BO_FLAG_USER;
if (xe_debug_page_size_mode_not_none(xe)) {
err = xe_bo_apply_debug_page_size_policy(xe, &flags, &size);
if (err)
return ERR_PTR(err);
}
if (vm || exec) {
xe_assert(xe, exec);
bo = __xe_bo_create_locked(xe, NULL, vm, size, 0, ~0ULL,
@@ -3441,6 +3635,8 @@ int xe_gem_create_ioctl(struct drm_device *dev, void *data,
if (err)
goto out_bulk;
xe_bo_debug_mixed_mode_cur_index_advance(xe, bo);
args->handle = handle;
goto out_put;

View File

@@ -52,6 +52,7 @@
#define XE_BO_FLAG_CPU_ADDR_MIRROR BIT(24)
#define XE_BO_FLAG_FORCE_USER_VRAM BIT(25)
#define XE_BO_FLAG_NO_COMPRESSION BIT(26)
#define XE_BO_FLAG_NEEDS_1G BIT(27)
/* this one is trigger internally only */
#define XE_BO_FLAG_INTERNAL_TEST BIT(30)
@@ -118,7 +119,8 @@ struct xe_bo *xe_bo_init_locked(struct xe_device *xe, struct xe_bo *bo,
struct xe_tile *tile, struct dma_resv *resv,
struct ttm_lru_bulk_move *bulk, size_t size,
u16 cpu_caching, enum ttm_bo_type type,
u32 flags, struct drm_exec *exec);
u32 flags, struct dma_buf *dma_buf,
struct drm_exec *exec);
struct xe_bo *xe_bo_create_locked(struct xe_device *xe, struct xe_tile *tile,
struct xe_vm *vm, size_t size,
enum ttm_bo_type type, u32 flags,

View File

@@ -36,6 +36,8 @@ struct xe_bo {
struct xe_bo *backup_obj;
/** @parent_obj: Ref to parent bo if this a backup_obj */
struct xe_bo *parent_obj;
/** @dma_buf: Imported dma-buf ref to keep its resv alive. */
struct dma_buf *dma_buf;
/** @flags: flags for this buffer object */
u32 flags;
/** @vm: VM this BO is attached to, for extobj this will be NULL */

View File

@@ -237,6 +237,18 @@
*
* This setting only takes effect when probing the device.
*
* Enable multi-queue
* ------------------
*
* Multi-queue support on the device is enabled by default where the
* hardware supports it. Writing 0 force-disables multi-queue support:
* multi-queue exec-queue group creation via ioctl is refused, and the
* GuC feature is disabled::
*
* # echo 0 > /sys/kernel/config/xe/0000:03:00.0/enable_multi_queue
*
* This attribute can only be set before binding to the device.
*
* Remove devices
* ==============
*
@@ -262,6 +274,7 @@ struct xe_config_group_device {
struct wa_bb ctx_restore_mid_bb[XE_ENGINE_CLASS_MAX];
bool survivability_mode;
bool enable_psmi;
bool enable_multi_queue;
struct {
unsigned int max_vfs;
bool admin_only_pf;
@@ -281,6 +294,7 @@ static const struct xe_config_device device_defaults = {
.engines_allowed = U64_MAX,
.survivability_mode = false,
.enable_psmi = false,
.enable_multi_queue = true,
.sriov = {
.max_vfs = XE_DEFAULT_MAX_VFS,
.admin_only_pf = XE_DEFAULT_ADMIN_ONLY_PF,
@@ -575,6 +589,33 @@ static ssize_t enable_psmi_store(struct config_item *item, const char *page, siz
return len;
}
static ssize_t enable_multi_queue_show(struct config_item *item, char *page)
{
struct xe_config_device *dev = to_xe_config_device(item);
return sprintf(page, "%d\n", dev->enable_multi_queue);
}
static ssize_t enable_multi_queue_store(struct config_item *item, const char *page,
size_t len)
{
struct xe_config_group_device *dev = to_xe_config_group_device(item);
bool val;
int ret;
ret = kstrtobool(page, &val);
if (ret)
return ret;
guard(mutex)(&dev->lock);
if (is_bound(dev))
return -EBUSY;
dev->config.enable_multi_queue = val;
return len;
}
static bool wa_bb_read_advance(bool dereference, char **p,
const char *append, size_t len,
size_t *max_size)
@@ -812,6 +853,7 @@ static ssize_t ctx_restore_post_bb_store(struct config_item *item,
CONFIGFS_ATTR(, ctx_restore_mid_bb);
CONFIGFS_ATTR(, ctx_restore_post_bb);
CONFIGFS_ATTR(, enable_multi_queue);
CONFIGFS_ATTR(, enable_psmi);
CONFIGFS_ATTR(, engines_allowed);
CONFIGFS_ATTR(, gt_types_allowed);
@@ -820,6 +862,7 @@ CONFIGFS_ATTR(, survivability_mode);
static struct configfs_attribute *xe_config_device_attrs[] = {
&attr_ctx_restore_mid_bb,
&attr_ctx_restore_post_bb,
&attr_enable_multi_queue,
&attr_enable_psmi,
&attr_engines_allowed,
&attr_gt_types_allowed,
@@ -1097,6 +1140,7 @@ static void dump_custom_dev_config(struct pci_dev *pdev,
PRI_CUSTOM_ATTR("%llx", gt_types_allowed);
PRI_CUSTOM_ATTR("%llx", engines_allowed);
PRI_CUSTOM_ATTR("%d", enable_multi_queue);
PRI_CUSTOM_ATTR("%d", enable_psmi);
PRI_CUSTOM_ATTR("%d", survivability_mode);
PRI_CUSTOM_ATTR("%u", sriov.admin_only_pf);
@@ -1225,6 +1269,27 @@ bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev)
return ret;
}
/**
* xe_configfs_get_enable_multi_queue - get configfs enable_multi_queue setting
* @pdev: pci device
*
* Return: true if multi-queue is enabled for this device (the default),
* false if it has been force-disabled via configfs.
*/
bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev)
{
struct xe_config_group_device *dev = find_xe_config_group_device(pdev);
bool ret;
if (!dev)
return true;
ret = dev->config.enable_multi_queue;
config_group_put(&dev->group);
return ret;
}
/**
* xe_configfs_get_ctx_restore_mid_bb - get configfs ctx_restore_mid_bb setting
* @pdev: pci device

View File

@@ -23,6 +23,7 @@ bool xe_configfs_primary_gt_allowed(struct pci_dev *pdev);
bool xe_configfs_media_gt_allowed(struct pci_dev *pdev);
u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev);
bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev);
bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev);
u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev,
enum xe_engine_class class,
const u32 **cs);
@@ -42,6 +43,7 @@ static inline bool xe_configfs_primary_gt_allowed(struct pci_dev *pdev) { return
static inline bool xe_configfs_media_gt_allowed(struct pci_dev *pdev) { return true; }
static inline u64 xe_configfs_get_engines_allowed(struct pci_dev *pdev) { return U64_MAX; }
static inline bool xe_configfs_get_psmi_enabled(struct pci_dev *pdev) { return false; }
static inline bool xe_configfs_get_enable_multi_queue(struct pci_dev *pdev) { return true; }
static inline u32 xe_configfs_get_ctx_restore_mid_bb(struct pci_dev *pdev,
enum xe_engine_class class,
const u32 **cs) { return 0; }

View File

@@ -5,6 +5,7 @@
#include "xe_debugfs.h"
#include <linux/bits.h>
#include <linux/debugfs.h>
#include <linux/fault-inject.h>
#include <linux/string_helpers.h>
@@ -21,6 +22,7 @@
#include "xe_guc_ads.h"
#include "xe_hw_engine.h"
#include "xe_mmio.h"
#include "xe_pcode.h"
#include "xe_pm.h"
#include "xe_psmi.h"
#include "xe_pxp_debugfs.h"
@@ -41,6 +43,55 @@
DECLARE_FAULT_ATTR(gt_reset_failure);
DECLARE_FAULT_ATTR(inject_csc_hw_error);
static bool csc_hw_error_available(struct xe_device *xe)
{
return !IS_SRIOV_VF(xe) && xe->info.platform == XE_BATTLEMAGE;
}
/*
* Fault injection table. Each entry registers a debugfs attribute; add a
* matching FAULT_ACTION() below for every entry added here.
*/
static struct {
const char *name;
struct fault_attr *attr;
bool (*is_visible)(struct xe_device *xe);
} xe_fault_inject_entry[] = {
{ .name = "fail_gt_reset",
.attr = &gt_reset_failure },
{ .name = "inject_csc_hw_error",
.attr = &inject_csc_hw_error,
.is_visible = csc_hw_error_available },
};
/*
* FAULT_ACTION(name, fault_attr) - generate xe_fault_<name>() accessor.
* Add one entry per row in xe_fault_inject_entry[].
*/
#define FAULT_ACTION(name, fault_attr) \
bool xe_fault_##name(void) \
{ \
return should_fail(&(fault_attr), 1); \
}
FAULT_ACTION(gt_reset, gt_reset_failure)
FAULT_ACTION(csc_hw_error, inject_csc_hw_error)
static void xe_fault_inject_debugfs_register(struct xe_device *xe,
struct dentry *root)
{
int i;
for (i = 0; i < ARRAY_SIZE(xe_fault_inject_entry); i++) {
if (xe_fault_inject_entry[i].is_visible &&
!xe_fault_inject_entry[i].is_visible(xe))
continue;
fault_create_debugfs_attr(xe_fault_inject_entry[i].name, root,
xe_fault_inject_entry[i].attr);
}
}
static void read_residency_counter(struct xe_device *xe, struct xe_mmio *mmio,
u32 offset, const char *name, struct drm_printer *p)
{
@@ -160,6 +211,22 @@ static int workaround_info(struct seq_file *m, void *data)
return 0;
}
static int pcode_info(struct seq_file *m, void *data)
{
struct xe_device *xe = node_to_xe(m->private);
struct drm_printer p = drm_seq_file_printer(m);
struct xe_pcode_version version;
int ret = 0;
ret = xe_get_pcode_version(xe, &version);
if (ret)
return ret;
drm_printf(&p, "pcode version: %u.%u.%u\n", version.major,
version.minor, version.engg);
return 0;
}
static int dgfx_pkg_residencies_show(struct seq_file *m, void *data)
{
struct xe_device *xe;
@@ -220,6 +287,10 @@ static const struct drm_info_list debugfs_list[] = {
{ .name = "workarounds", .show = workaround_info, },
};
static const struct drm_info_list pcode_info_debugfs[] = {
{ .name = "pcode_info", .show = pcode_info, },
};
static const struct drm_info_list debugfs_residencies[] = {
{ .name = "dgfx_pkg_residencies", .show = dgfx_pkg_residencies_show, },
{ .name = "dgfx_pcie_link_residencies", .show = dgfx_pcie_link_residencies_show, },
@@ -543,6 +614,72 @@ static const struct file_operations disable_late_binding_fops = {
.write = disable_late_binding_set,
};
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
static const char * const page_size_alloc_mode_names[] = {
[XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE] = "none",
[XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M] = "only_2m",
[XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G] = "only_1g",
[XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED] = "mixed",
};
static ssize_t page_size_alloc_mode_show(struct file *f, char __user *ubuf,
size_t size, loff_t *pos)
{
struct xe_device *xe = file_inode(f)->i_private;
char buf[32];
int len;
enum xe_page_size_alloc_ctrl_mode mode;
mode = READ_ONCE(xe->page_size_alloc_ctrl.mode);
if (mode >= ARRAY_SIZE(page_size_alloc_mode_names) ||
!page_size_alloc_mode_names[mode])
len = scnprintf(buf, sizeof(buf), "unknown\n");
else
len = scnprintf(buf, sizeof(buf), "%s\n",
page_size_alloc_mode_names[mode]);
return simple_read_from_buffer(ubuf, size, pos, buf, len);
}
static ssize_t page_size_alloc_mode_set(struct file *f, const char __user *ubuf,
size_t size, loff_t *pos)
{
struct xe_device *xe = file_inode(f)->i_private;
int ret;
char buf[32];
int mode;
if (*pos)
return -ESPIPE;
if (size > sizeof(buf) - 1)
return -EINVAL;
ret = simple_write_to_buffer(buf, sizeof(buf) - 1, pos, ubuf, size);
if (ret < 0)
return ret;
buf[ret] = '\0';
mode = sysfs_match_string(page_size_alloc_mode_names, buf);
if (mode < 0)
return mode;
mutex_lock(&xe->page_size_alloc_ctrl.lock);
if (mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED)
xe->page_size_alloc_ctrl.cur_index = 0;
WRITE_ONCE(xe->page_size_alloc_ctrl.mode,
(enum xe_page_size_alloc_ctrl_mode)mode);
mutex_unlock(&xe->page_size_alloc_ctrl.lock);
return size;
}
static const struct file_operations page_size_alloc_mode_fops = {
.owner = THIS_MODULE,
.read = page_size_alloc_mode_show,
.write = page_size_alloc_mode_set,
};
#endif
void xe_debugfs_register(struct xe_device *xe)
{
struct ttm_device *bdev = &xe->ttm;
@@ -562,10 +699,20 @@ void xe_debugfs_register(struct xe_device *xe)
drm_debugfs_create_files(debugfs_residencies,
ARRAY_SIZE(debugfs_residencies),
root, minor);
fault_create_debugfs_attr("inject_csc_hw_error", root,
&inject_csc_hw_error);
}
/*
* Pcode version read from PMT is currently only supported on CRI and BMG platforms in PF
* mode, as both platforms support the necessary telemetry read mechanism and have a fixed
* PUNIT_VERSION_OFFSET.
* Attempting this access on other platforms must be verified before enabling support.
*/
if (!IS_SRIOV_VF(xe) &&
(xe->info.platform == XE_CRESCENTISLAND || xe->info.platform == XE_BATTLEMAGE))
drm_debugfs_create_files(pcode_info_debugfs,
ARRAY_SIZE(pcode_info_debugfs),
root, minor);
debugfs_create_file("forcewake_all", 0400, root, xe,
&forcewake_all_fops);
@@ -584,6 +731,18 @@ void xe_debugfs_register(struct xe_device *xe)
debugfs_create_file("disable_late_binding", 0600, root, xe,
&disable_late_binding_fops);
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
/*
* Expose a debugfs knob to control user BO page-size allocation:
* "none" - default behavior
* "only_2m" - force 2M page allocations
* "only_1g" - force 1G page allocations
* "mixed" - select 4K, 64K, 2M, and 1G in round-robin order
*/
if (xe_debug_page_size_supported(xe))
debugfs_create_file("page_size_alloc_mode", 0600, root, xe,
&page_size_alloc_mode_fops);
#endif
/*
* Don't expose page reclaim configuration file if not supported by the
* hardware initially.
@@ -609,7 +768,7 @@ void xe_debugfs_register(struct xe_device *xe)
xe_psmi_debugfs_register(xe);
fault_create_debugfs_attr("fail_gt_reset", root, &gt_reset_failure);
xe_fault_inject_debugfs_register(xe, root);
if (IS_SRIOV_PF(xe))
xe_sriov_pf_debugfs_register(xe, root);

View File

@@ -6,11 +6,17 @@
#ifndef _XE_DEBUGFS_H_
#define _XE_DEBUGFS_H_
#include <linux/types.h>
struct xe_device;
#ifdef CONFIG_DEBUG_FS
bool xe_fault_gt_reset(void);
bool xe_fault_csc_hw_error(void);
void xe_debugfs_register(struct xe_device *xe);
#else
static inline bool xe_fault_gt_reset(void) { return false; }
static inline bool xe_fault_csc_hw_error(void) { return false; }
static inline void xe_debugfs_register(struct xe_device *xe) { }
#endif

View File

@@ -427,7 +427,6 @@ static const struct drm_ioctl_desc xe_ioctls_admin_only[] = {
static const struct drm_driver admin_only_driver = {
.driver_features =
XE_DISPLAY_DRIVER_FEATURES |
DRIVER_GEM | DRIVER_RENDER,
.open = xe_file_open,
.postclose = xe_file_close,
@@ -439,7 +438,6 @@ static const struct drm_driver admin_only_driver = {
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
XE_DISPLAY_DRIVER_OPS,
};
/**
@@ -581,7 +579,7 @@ int xe_device_init_early(struct xe_device *xe)
WQ_MEM_RECLAIM);
xe->ordered_wq = alloc_ordered_workqueue("xe-ordered-wq", 0);
xe->unordered_wq = alloc_workqueue("xe-unordered-wq", WQ_PERCPU, 0);
xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU, 0);
xe->destroy_wq = alloc_workqueue("xe-destroy-wq", WQ_PERCPU | WQ_MEM_RECLAIM, 0);
if (!xe->ordered_wq || !xe->unordered_wq ||
!xe->preempt_fence_wq || !xe->destroy_wq) {
/*
@@ -923,6 +921,27 @@ static void xe_device_wedged_fini(struct drm_device *drm, void *arg)
xe_pm_runtime_put(xe);
}
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
static int xe_debug_page_size_alloc_ctrl_init(struct xe_device *xe)
{
int err;
err = drmm_mutex_init(&xe->drm, &xe->page_size_alloc_ctrl.lock);
if (err)
return err;
xe->page_size_alloc_ctrl.mode = XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE;
xe->page_size_alloc_ctrl.cur_index = 0;
return 0;
}
#else
static int xe_debug_page_size_alloc_ctrl_init(struct xe_device *xe)
{
return 0;
}
#endif
int xe_device_probe(struct xe_device *xe)
{
struct xe_tile *tile;
@@ -1075,6 +1094,10 @@ int xe_device_probe(struct xe_device *xe)
if (err)
return err;
err = xe_debug_page_size_alloc_ctrl_init(xe);
if (err)
return err;
err = drm_dev_register(&xe->drm, 0);
if (err)
return err;

View File

@@ -212,6 +212,54 @@ static inline bool xe_device_wedged(struct xe_device *xe)
return atomic_read(&xe->wedged.flag);
}
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
static inline bool xe_debug_page_size_supported(struct xe_device *xe)
{
return IS_DGFX(xe);
}
static inline bool xe_debug_page_size_mode_not_none(struct xe_device *xe)
{
enum xe_page_size_alloc_ctrl_mode mode;
if (!xe_debug_page_size_supported(xe))
return false;
mode = READ_ONCE(xe->page_size_alloc_ctrl.mode);
return mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M ||
mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G ||
mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED;
}
static inline bool xe_debug_page_size_mode_is_mixed(struct xe_device *xe)
{
enum xe_page_size_alloc_ctrl_mode mode;
if (!xe_debug_page_size_supported(xe))
return false;
mode = READ_ONCE(xe->page_size_alloc_ctrl.mode);
return mode == XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED;
}
#else
static inline bool xe_debug_page_size_supported(struct xe_device *xe)
{
return false;
}
static inline bool xe_debug_page_size_mode_not_none(struct xe_device *xe)
{
return false;
}
static inline bool xe_debug_page_size_mode_is_mixed(struct xe_device *xe)
{
return false;
}
#endif
void xe_device_set_wedged_method(struct xe_device *xe, unsigned long method);
void xe_device_declare_wedged(struct xe_device *xe);
int xe_device_validate_wedged_mode(struct xe_device *xe, unsigned int mode);

View File

@@ -61,6 +61,23 @@ enum xe_wedged_mode {
XE_WEDGED_MODE_UPON_ANY_HANG_NO_RESET = 2,
};
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
/**
* enum xe_page_size_alloc_ctrl_mode - User BO page-size allocation control modes
* @XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE: Use the normal allocation policy
* @XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M: Force user BO allocations to 2M pages
* @XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G: Force user BO allocations to 1G pages
* @XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED: Select page sizes in round-robin order
* (4K, 64K, 2M, 1G)
*/
enum xe_page_size_alloc_ctrl_mode {
XE_PAGE_SIZE_ALLOC_CTRL_MODE_NONE = 0,
XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_2M,
XE_PAGE_SIZE_ALLOC_CTRL_MODE_ONLY_1G,
XE_PAGE_SIZE_ALLOC_CTRL_MODE_MIXED
};
#endif
#define XE_BO_INVALID_OFFSET LONG_MAX
#define GRAPHICS_VER(xe) ((xe)->info.graphics_verx100 / 100)
@@ -355,7 +372,7 @@ struct xe_device {
/** @unordered_wq: used to serialize unordered work */
struct workqueue_struct *unordered_wq;
/** @destroy_wq: used to serialize user destroy work, like queue */
/** @destroy_wq: used to serialize SVM pagemap destroy work */
struct workqueue_struct *destroy_wq;
/** @tiles: device tiles */
@@ -404,6 +421,10 @@ struct xe_device {
const struct xe_pat_table_entry *pat_primary_pta;
/** @pat.pat_media_pta: media GT PAT entry for page table accesses */
const struct xe_pat_table_entry *pat_media_pta;
/** @pat.pat_primary_tr_pta: primary GT PAT entry for TRTT page table accesses */
const struct xe_pat_table_entry *pat_primary_tr_pta;
/** @pat.pat_media_tr_pta: media GT PAT entry for TRTT page table accesses */
const struct xe_pat_table_entry *pat_media_tr_pta;
u16 idx[__XE_CACHE_LEVEL_COUNT];
} pat;
@@ -474,6 +495,20 @@ struct xe_device {
/** @late_bind: xe mei late bind interface */
struct xe_late_bind late_bind;
#ifdef CONFIG_DRM_XE_DEBUG_PAGE_SIZE
/**
* @page_size_alloc_ctrl: User BO page-size allocation
* debug control state
*/
struct {
/** @page_size_alloc_ctrl.mode: xe page size allocation control mode */
enum xe_page_size_alloc_ctrl_mode mode;
/** @page_size_alloc_ctrl.cur_index: Round-robin index used by mixed mode */
u32 cur_index;
/** @page_size_alloc_ctrl.lock: Protects @mode and @cur_index */
struct mutex lock;
} page_size_alloc_ctrl;
#endif
/** @oa: oa observation subsystem */
struct xe_oa oa;

View File

@@ -302,7 +302,7 @@ xe_dma_buf_create_obj(struct drm_device *dev, struct dma_buf *dma_buf)
bo = xe_bo_init_locked(xe, NULL, NULL, resv, NULL, dma_buf->size,
0, /* Will require 1way or 2way for vm_bind */
ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, &exec);
ttm_bo_type_sg, XE_BO_FLAG_SYSTEM, dma_buf, &exec);
drm_exec_retry_on_contention(&exec);
if (IS_ERR(bo)) {
ret = PTR_ERR(bo);

View File

@@ -7,6 +7,7 @@
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/iopoll.h>
#include <drm/drm_drv.h>
#include <generated/xe_wa_oob.h>
@@ -20,6 +21,7 @@
#include "xe_gt_printk.h"
#include "xe_gt_topology.h"
#include "xe_macros.h"
#include "xe_mmio.h"
#include "xe_observation.h"
#include "xe_pm.h"
#include "xe_trace.h"
@@ -27,8 +29,16 @@
#include "regs/xe_eu_stall_regs.h"
#include "regs/xe_gt_regs.h"
#include "regs/xe_regs.h"
#define POLL_PERIOD_MS 5
#define FW_WA_WAIT_TIMEOUT_US 10000
#define SWF_EUSTALL_MASK REG_GENMASK(6, 5)
#define REQ_EUSTALL_ENABLE REG_BIT(5)
#define ACK_EUSTALL_ENABLE REG_GENMASK(6, 5)
#define REQ_EUSTALL_DISABLE REG_BIT(6)
#define ACK_EUSTALL_DISABLE 0
static size_t per_xecore_buf_size = SZ_512K;
@@ -682,7 +692,7 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream)
struct per_xecore_buf *xecore_buf;
struct xe_gt *gt = stream->gt;
u16 group, instance;
int xecore;
int xecore, ret = 0;
/* Take runtime pm ref and forcewake to disable RC6 */
xe_pm_runtime_get(gt_to_xe(gt));
@@ -693,6 +703,18 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream)
return -ETIMEDOUT;
}
if (XE_GT_WA(gt, 14027054324)) {
/* Request the firmware to apply the workaround and wait for an ACK */
xe_mmio_write32(&gt->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_ENABLE);
ret = xe_mmio_wait32(&gt->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK,
ACK_EUSTALL_ENABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false);
if (ret) {
xe_gt_err(gt, "Timeout polling for EU stall enable ACK from firmware\n");
xe_force_wake_put(gt_to_fw(gt), stream->fw_ref);
xe_pm_runtime_put(gt_to_xe(gt));
return ret;
}
}
if (XE_GT_WA(gt, 22016596838))
xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2,
REG_MASKED_FIELD_ENABLE(DISABLE_DOP_GATING));
@@ -730,7 +752,7 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream)
reg_value |= XEHPC_EUSTALL_BASE_ENABLE_SAMPLING;
xe_gt_mcr_multicast_write(gt, XEHPC_EUSTALL_BASE, reg_value);
return 0;
return ret;
}
static void eu_stall_data_buf_poll_work_fn(struct work_struct *work)
@@ -840,6 +862,7 @@ static int xe_eu_stall_enable_locked(struct xe_eu_stall_data_stream *stream)
static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream)
{
struct xe_gt *gt = stream->gt;
int ret = 0;
if (!stream->enabled)
return 0;
@@ -853,11 +876,19 @@ static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream)
if (XE_GT_WA(gt, 22016596838))
xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2,
REG_MASKED_FIELD_DISABLE(DISABLE_DOP_GATING));
if (XE_GT_WA(gt, 14027054324)) {
/* Request the firmware to revert the workaround and wait for an ACK */
xe_mmio_write32(&gt->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_DISABLE);
ret = xe_mmio_wait32(&gt->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK,
ACK_EUSTALL_DISABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false);
if (ret)
xe_gt_err(gt, "Timeout polling for EU stall disable ACK from firmware\n");
}
xe_force_wake_put(gt_to_fw(gt), stream->fw_ref);
xe_pm_runtime_put(gt_to_xe(gt));
return 0;
return ret;
}
static long xe_eu_stall_stream_ioctl_locked(struct xe_eu_stall_data_stream *stream,

View File

@@ -292,13 +292,23 @@ int xe_exec_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
goto err_exec;
}
/* Wait behind rebinds */
/*
* Wait behind rebinds and any kernel operations (evictions, defrag
* moves, ...) on the VM and all external BOs. The VM's private BOs
* carry their kernel ops in the VM dma-resv KERNEL slot, while each
* external BO carries them in its own dma-resv KERNEL slot; both are
* covered by iterating every object locked by the exec, mirroring the
* drm_gpuvm_resv_add_fence() below.
*/
if (!xe_vm_in_lr_mode(vm)) {
err = xe_sched_job_add_deps(job,
xe_vm_resv(vm),
DMA_RESV_USAGE_KERNEL);
if (err)
goto err_put_job;
struct drm_gem_object *obj;
drm_exec_for_each_locked_object(exec, obj) {
err = xe_sched_job_add_deps(job, obj->resv,
DMA_RESV_USAGE_KERNEL);
if (err)
goto err_put_job;
}
}
for (i = 0; i < num_syncs && !err; i++)

View File

@@ -530,10 +530,7 @@ struct xe_exec_queue *xe_exec_queue_create_bind(struct xe_device *xe,
migrate_vm = xe_migrate_get_vm(tile->migrate);
if (xe->info.has_usm) {
struct xe_hw_engine *hwe = xe_gt_hw_engine(gt,
XE_ENGINE_CLASS_COPY,
gt->usm.reserved_bcs_instance,
false);
struct xe_hw_engine *hwe = gt->usm.paging_hwe0;
if (!hwe) {
xe_vm_put(migrate_vm);
@@ -842,6 +839,7 @@ static int xe_exec_queue_group_init(struct xe_device *xe, struct xe_exec_queue *
group->primary = q;
group->cgp_bo = bo;
INIT_LIST_HEAD(&group->list);
spin_lock_init(&group->suspend_lock);
xa_init_flags(&group->xa, XA_FLAGS_ALLOC1);
mutex_init(&group->list_lock);
q->multi_queue.group = group;
@@ -1056,6 +1054,7 @@ int xe_exec_queue_set_property_ioctl(struct drm_device *dev, void *data,
static int exec_queue_user_ext_check(struct xe_exec_queue *q, u64 properties)
{
struct xe_device *xe = gt_to_xe(q->gt);
u64 secondary_queue_valid_props = BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_GROUP) |
BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_QUEUE_PRIORITY);
@@ -1067,6 +1066,16 @@ static int exec_queue_user_ext_check(struct xe_exec_queue *q, u64 properties)
properties & ~secondary_queue_valid_props)
return -EINVAL;
/*
* HWDRM is the only supported PXP type today. It is display related and
* hence can't work with multi-queue. Reject the combination. The secondary
* queue path above already rejects any PXP property, so this also covers
* the multi-queue primary which would otherwise allow it.
*/
if (XE_IOCTL_DBG(xe, (properties & BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_MULTI_GROUP)) &&
(properties & BIT_ULL(DRM_XE_EXEC_QUEUE_SET_PROPERTY_PXP_TYPE))))
return -EINVAL;
return 0;
}

View File

@@ -62,6 +62,12 @@ struct xe_exec_queue_group {
struct list_head list;
/** @list_lock: Secondary queue list lock */
struct mutex list_lock;
/**
* @suspend_lock: Makes a secondary's suspend/resume and its forwarding
* to the primary atomic. Nested outside of the queue's message lock
* (@xe_guc_exec_queue.sched.msg_lock).
*/
spinlock_t suspend_lock;
/** @sync_pending: CGP_SYNC_DONE g2h response pending */
bool sync_pending;
/** @banned: Group banned */
@@ -200,6 +206,18 @@ struct xe_exec_queue {
u32 seqno;
/** @lr.link: link into VM's list of exec queues */
struct list_head link;
/**
* @lr.suspended: Tracks whether the consumer-issued suspend()
* succeeded and a matching resume() is still owed. suspend() can
* fail (e.g. killed/banned/wedged), leaving the queue
* un-suspended, so consumers must only resume() queues that were
* actually suspended. Set by the suspend caller on success and
* cleared by the resume caller. A queue is only ever suspended by
* a single consumer at a time (preempt-fence mode and hw engine
* group fault mode are mutually exclusive), so a single flag is
* sufficient.
*/
bool suspended;
} lr;
#define XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT 0
@@ -310,6 +328,15 @@ struct xe_exec_queue_ops {
* avoidance mechanism.
*/
int (*suspend_wait)(struct xe_exec_queue *q);
/**
* @suspend_wait_blocking: Like @suspend_wait, but waits uninterruptibly
* (does not abort on the calling task's signals). For cleanup/undo paths
* that must complete a suspend on behalf of a queue that may belong to a
* different process than the caller: a signal to the caller must not
* abandon the wait, which would leave the other process's queue
* suspended forever (cross-process DoS). A timeout bans like suspend_wait.
*/
int (*suspend_wait_blocking)(struct xe_exec_queue *q);
/**
* @resume: Resume exec queue execution, exec queue must be in a suspended
* state and dma fence returned from most recent suspend call must be

View File

@@ -468,6 +468,7 @@ static const struct xe_exec_queue_ops execlist_exec_queue_ops = {
.set_preempt_timeout = execlist_exec_queue_set_preempt_timeout,
.suspend = execlist_exec_queue_suspend,
.suspend_wait = execlist_exec_queue_suspend_wait,
.suspend_wait_blocking = execlist_exec_queue_suspend_wait,
.resume = execlist_exec_queue_resume,
.reset_status = execlist_exec_queue_reset_status,
};

View File

@@ -21,6 +21,7 @@
#include "regs/xe_gt_regs.h"
#include "xe_assert.h"
#include "xe_bb.h"
#include "xe_debugfs.h"
#include "xe_device.h"
#include "xe_eu_stall.h"
#include "xe_exec_queue.h"
@@ -926,7 +927,7 @@ static void gt_reset_worker(struct work_struct *w)
xe_gt_info(gt, "reset started\n");
if (xe_fault_inject_gt_reset()) {
if (xe_fault_gt_reset()) {
err = -ECANCELED;
goto err_fail;
}
@@ -986,7 +987,7 @@ void xe_gt_reset_async(struct xe_gt *gt)
return;
/* Don't do a reset while one is already in flight */
if (!xe_fault_inject_gt_reset() && xe_uc_reset_prepare(&gt->uc))
if (!xe_fault_gt_reset() && xe_uc_reset_prepare(&gt->uc))
return;
xe_gt_info(gt, "reset queued from %ps\n", __builtin_return_address(0));

View File

@@ -6,8 +6,6 @@
#ifndef _XE_GT_H_
#define _XE_GT_H_
#include <linux/fault-inject.h>
#include <drm/drm_util.h>
#include "xe_device.h"
@@ -38,12 +36,6 @@
xe_gt_is_media_type(gt_) ? MEDIA_VER(xe) : GRAPHICS_VER(xe); \
})
extern struct fault_attr gt_reset_failure;
static inline bool xe_fault_inject_gt_reset(void)
{
return IS_ENABLED(CONFIG_DEBUG_FS) && should_fail(&gt_reset_failure, 1);
}
struct xe_gt *xe_gt_alloc(struct xe_tile *tile);
int xe_gt_init_early(struct xe_gt *gt);
int xe_gt_init(struct xe_gt *gt);
@@ -137,10 +129,8 @@ static inline bool xe_gt_is_media_type(struct xe_gt *gt)
static inline bool xe_gt_is_usm_hwe(struct xe_gt *gt, struct xe_hw_engine *hwe)
{
struct xe_device *xe = gt_to_xe(gt);
return xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY &&
hwe->instance == gt->usm.reserved_bcs_instance;
return hwe->class == XE_ENGINE_CLASS_COPY &&
(gt->usm.paging_logical_mask & BIT(hwe->logical_instance));
}
/**

View File

@@ -381,10 +381,11 @@ static ssize_t sched_group_engines_read(struct file *file, char __user *buf,
if (group < num_groups) {
for_each_hw_engine(hwe, gt, id) {
u8 guc_class = xe_engine_class_to_guc_class(hwe->class);
u8 guc_class = xe_hwe_to_guc_class(hwe);
u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe);
u32 mask = groups[group].engines[guc_class];
if (mask & BIT(hwe->logical_instance)) {
if (mask & BIT(guc_logical_instance)) {
strlcat(engines, hwe->name, sizeof(engines));
strlcat(engines, " ", sizeof(engines));
}

View File

@@ -471,7 +471,8 @@ static void pf_sched_group_media_slices(struct xe_gt *gt, struct guc_sched_group
return;
for_each_hw_engine(hwe, gt, id) {
u8 guc_class = xe_engine_class_to_guc_class(hwe->class);
u8 guc_class = xe_hwe_to_guc_class(hwe);
u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe);
switch (hwe->class) {
case XE_ENGINE_CLASS_VIDEO_DECODE:
@@ -490,7 +491,7 @@ static void pf_sched_group_media_slices(struct xe_gt *gt, struct guc_sched_group
slice = 0;
}
values[slice_to_group[slice]].engines[guc_class] |= BIT(hwe->logical_instance);
values[slice_to_group[slice]].engines[guc_class] |= BIT(guc_logical_instance);
}
*groups = values;

View File

@@ -658,6 +658,33 @@ static int vf_cache_sched_groups_status(struct xe_gt *gt)
return 0;
}
static int vf_cache_num_paging_engines(struct xe_gt *gt)
{
struct xe_guc *guc = &gt->uc.guc;
struct xe_uc_fw_version guc_version;
u32 value = 0;
int err;
xe_gt_sriov_vf_guc_versions(gt, NULL, &guc_version);
if (MAKE_GUC_VER_STRUCT(guc_version) < MAKE_GUC_VER(1, 36, 0))
return 0;
err = guc_action_query_single_klv32(guc, GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY,
&value);
if (unlikely(err)) {
xe_gt_sriov_err(gt,
"Failed to obtain the number of paging instances (%pe)\n",
ERR_PTR(err));
return err;
}
gt->sriov.vf.runtime.num_paging_engine_instances = value;
xe_gt_sriov_dbg(gt, "num_paging_engines %u\n", value);
return 0;
}
/**
* xe_gt_sriov_vf_query_config - Query SR-IOV config data over MMIO.
* @gt: the &xe_gt
@@ -694,6 +721,10 @@ int xe_gt_sriov_vf_query_config(struct xe_gt *gt)
if (has_gmdid(xe))
vf_cache_gmdid(gt);
err = vf_cache_num_paging_engines(gt);
if (unlikely(err))
return err;
return 0;
}
@@ -731,6 +762,22 @@ u16 xe_gt_sriov_vf_guc_ids(struct xe_gt *gt)
return gt->sriov.vf.self_config.num_ctxs;
}
/**
* xe_gt_sriov_vf_paging_engines - Return the number of paging engine instances
* @gt: the &xe_gt
*
* This function is for VF use only.
*
* Return: number of GuC paging engine instances configured by the PF.
*/
u32 xe_gt_sriov_vf_paging_engines(struct xe_gt *gt)
{
xe_gt_assert(gt, IS_SRIOV_VF(gt_to_xe(gt)));
xe_gt_assert(gt, gt->sriov.vf.guc_version.major);
return gt->sriov.vf.runtime.num_paging_engine_instances;
}
static int relay_action_handshake(struct xe_gt *gt, u32 *major, u32 *minor)
{
u32 request[VF2PF_HANDSHAKE_REQUEST_MSG_LEN] = {

View File

@@ -31,6 +31,7 @@ u32 xe_gt_sriov_vf_gmdid(struct xe_gt *gt);
u16 xe_gt_sriov_vf_guc_ids(struct xe_gt *gt);
u64 xe_gt_sriov_vf_lmem(struct xe_gt *gt);
bool xe_gt_sriov_vf_sched_groups_enabled(struct xe_gt *gt);
u32 xe_gt_sriov_vf_paging_engines(struct xe_gt *gt);
u32 xe_gt_sriov_vf_read32(struct xe_gt *gt, struct xe_reg reg);
void xe_gt_sriov_vf_write32(struct xe_gt *gt, struct xe_reg reg, u32 val);

View File

@@ -29,6 +29,10 @@ struct xe_gt_sriov_vf_runtime {
u32 gmdid;
/** @uses_sched_groups: whether PF enabled sched groups or not. */
bool uses_sched_groups;
/**
* @num_paging_engine_instances: number of configured paging engines.
*/
u32 num_paging_engine_instances;
/** @regs_size: size of runtime register array. */
u32 regs_size;
/** @num_regs: number of runtime registers in the array. */

View File

@@ -144,6 +144,11 @@ struct xe_gt {
u8 id;
/** @info.has_indirect_ring_state: GT has indirect ring state support */
u8 has_indirect_ring_state:1;
/**
* @info.has_uncorrectable_error_reporting: GT has uncorrectable
* error reporting support
*/
u8 has_uncorrectable_error_reporting:1;
/**
* @info.has_xe2_blt_instructions: GT supports Xe2-style MEM_SET
* and MEM_COPY blitter functionality. Note that despite the
@@ -230,10 +235,16 @@ struct xe_gt {
*/
struct xe_sa_manager *bb_pool;
/**
* @usm.reserved_bcs_instance: reserved BCS instance used for USM
* operations (e.g. migrations, fixing page tables)
* @usm.paging_hwe0: The first designated paging engine.
* This is some reserved BCS instance used for USM operations
* (e.g. migrations, fixing page tables)
*/
u16 reserved_bcs_instance;
struct xe_hw_engine *paging_hwe0;
/**
* @usm.paging_logical_mask: logical mask of paging engines.
* Should be densely populated.
*/
u32 paging_logical_mask;
} usm;
/** @ordered_wq: used to serialize GT resets and TDRs */

View File

@@ -12,6 +12,7 @@
#include "abi/guc_actions_abi.h"
#include "abi/guc_errors_abi.h"
#include "abi/guc_klvs_abi.h"
#include "regs/xe_gt_regs.h"
#include "regs/xe_gtt_defs.h"
#include "regs/xe_guc_regs.h"
@@ -101,6 +102,14 @@ static u32 guc_ctl_feature_flags(struct xe_guc *guc)
if (xe_device_is_l2_flush_optimized(xe) && xe_gt_is_media_type(guc_to_gt(guc)))
flags |= GUC_CTL_ENABLE_L2FLUSH_OPT;
/*
* On GuC firmware 70.66 and above, the GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE
* Feature KLV is used instead.
*/
if (!xe_configfs_get_enable_multi_queue(to_pci_dev(xe->drm.dev)) &&
!GUC_FIRMWARE_VER_AT_LEAST(guc, 70, 66))
flags |= GUC_CTL_DISABLE_MULTI_QUEUE;
return flags;
}
@@ -641,6 +650,15 @@ int xe_guc_opt_in_features_enable(struct xe_guc *guc)
if (GUC_SUBMIT_VER(guc) >= MAKE_GUC_VER(1, 7, 0))
klvs[count++] = PREP_GUC_KLV_TAG(OPT_IN_FEATURE_EXT_CAT_ERR_TYPE);
/*
* The uncorrectable local error notification opt-in was added in
* GuC v70.38.0, which maps to compatibility version v1.18.0.
*/
if (GUC_SUBMIT_VER(guc) >= MAKE_GUC_VER(1, 18, 0) &&
guc_to_gt(guc)->info.has_uncorrectable_error_reporting)
klvs[count++] =
PREP_GUC_KLV_TAG(OPT_IN_FEATURE_UNCORRECTABLE_LOCAL_ERROR_NOTIFICATION);
if (supports_dynamic_ics(guc))
klvs[count++] = PREP_GUC_KLV_TAG(OPT_IN_FEATURE_DYNAMIC_INHIBIT_CONTEXT_SWITCH);
@@ -1846,6 +1864,58 @@ bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc)
return GT_VER(gt) >= 35;
}
bool xe_guc_has_paging_engine(struct xe_guc *guc)
{
struct xe_gt *gt = guc_to_gt(guc);
struct xe_device *xe = gt_to_xe(gt);
/*
* On newer platforms the GuC now has a dedicated engine class for the
* special PAGING engine, which is the driver reserved BCS engine used
* for KMD paging/binding operations. GuC requires KMD to refer to this
* using the special PAGING engine class. Note that there is no new hw
* engine here, this is purely a sw view in the GuC itself, which we
* need to respect.
*/
if (IS_SRIOV_VF(xe))
return xe_gt_sriov_vf_paging_engines(gt);
return xe->info.platform >= XE_NOVALAKE_S &&
GUC_FIRMWARE_VER_AT_LEAST(guc, 70, 69, 0);
}
/**
* xe_hwe_guc_logical_instance - Get the GuC-aligned logical instance of a
* hardware engine.
* @hwe: Hardware engine.
*
* For GuC backend usage, we should no longer use the raw logical instance
* directly. This helper must be used to retrieve the logical instance of the
* hardware engine, taking care of any necessary adjustments (such as the GuC
* PAGING engine mapping). This is assumed to be used in conjunction with the
* GuC engine class.
*
* Return: Logical instance, taking into account for stuff like GuC PAGING
* engine mapping.
*/
u16 xe_hwe_guc_logical_instance(struct xe_hw_engine *hwe)
{
struct xe_gt *gt = hwe->gt;
if (xe_guc_has_paging_engine(&hwe->gt->uc.guc) &&
xe_gt_is_usm_hwe(gt, hwe)) {
int shift = gt->usm.paging_hwe0->logical_instance;
xe_gt_assert(gt, shift <= hwe->logical_instance);
/* GUC_PAGING_CLASS:guc_logical_instance */
return hwe->logical_instance - shift;
}
return hwe->logical_instance;
}
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
#include "tests/xe_guc_g2g_test.c"
#endif

View File

@@ -62,31 +62,14 @@ void xe_guc_stop(struct xe_guc *guc);
int xe_guc_start(struct xe_guc *guc);
void xe_guc_declare_wedged(struct xe_guc *guc);
bool xe_guc_using_main_gamctrl_queues(struct xe_guc *guc);
bool xe_guc_has_paging_engine(struct xe_guc *guc);
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
int xe_guc_g2g_test_notification(struct xe_guc *guc, u32 *payload, u32 len);
#endif
static inline u16 xe_engine_class_to_guc_class(enum xe_engine_class class)
{
switch (class) {
case XE_ENGINE_CLASS_RENDER:
return GUC_RENDER_CLASS;
case XE_ENGINE_CLASS_VIDEO_DECODE:
return GUC_VIDEO_CLASS;
case XE_ENGINE_CLASS_VIDEO_ENHANCE:
return GUC_VIDEOENHANCE_CLASS;
case XE_ENGINE_CLASS_COPY:
return GUC_BLITTER_CLASS;
case XE_ENGINE_CLASS_COMPUTE:
return GUC_COMPUTE_CLASS;
case XE_ENGINE_CLASS_OTHER:
return GUC_GSC_OTHER_CLASS;
default:
XE_WARN_ON(class);
return -1;
}
}
u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe);
u16 xe_hwe_guc_logical_instance(struct xe_hw_engine *hwe);
static inline struct xe_gt *guc_to_gt(struct xe_guc *guc)
{

View File

@@ -16,6 +16,7 @@
#include "regs/xe_gt_regs.h"
#include "regs/xe_guc_regs.h"
#include "xe_bo.h"
#include "xe_configfs.h"
#include "xe_gt.h"
#include "xe_gt_ccs_mode.h"
#include "xe_gt_mcr.h"
@@ -251,16 +252,42 @@ static size_t calculate_regset_size(struct xe_gt *gt)
return count * sizeof(struct guc_mmio_reg);
}
static u32 engine_enable_mask(struct xe_gt *gt, enum xe_engine_class class)
static inline enum xe_engine_class guc_class_to_engine_class(u16 guc_class)
{
switch (guc_class) {
case GUC_RENDER_CLASS:
return XE_ENGINE_CLASS_RENDER;
case GUC_VIDEO_CLASS:
return XE_ENGINE_CLASS_VIDEO_DECODE;
case GUC_VIDEOENHANCE_CLASS:
return XE_ENGINE_CLASS_VIDEO_ENHANCE;
case GUC_BLITTER_CLASS:
case GUC_PAGING_CLASS:
return XE_ENGINE_CLASS_COPY;
case GUC_COMPUTE_CLASS:
return XE_ENGINE_CLASS_COMPUTE;
case GUC_GSC_OTHER_CLASS:
return XE_ENGINE_CLASS_OTHER;
default:
XE_WARN_ON(guc_class);
return -1;
}
}
static u32 engine_enable_mask(struct xe_gt *gt, u16 guc_class)
{
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
u32 mask = 0;
for_each_hw_engine(hwe, gt, id)
if (hwe->class == class)
if (xe_hwe_to_guc_class(hwe) == guc_class)
mask |= BIT(hwe->instance);
/* We expect at most one paging engine per GuC instance, for now */
if (guc_class == GUC_PAGING_CLASS)
xe_gt_assert(gt, !mask || is_power_of_2(mask));
return mask;
}
@@ -268,10 +295,13 @@ static size_t calculate_golden_lrc_size(struct xe_guc_ads *ads)
{
struct xe_gt *gt = ads_to_gt(ads);
size_t total_size = 0, alloc_size, real_size;
int class;
u16 guc_class;
for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) {
if (!engine_enable_mask(gt, class))
for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) {
enum xe_engine_class class =
guc_class_to_engine_class(guc_class);
if (!engine_enable_mask(gt, guc_class))
continue;
real_size = xe_gt_lrc_size(gt, class);
@@ -364,6 +394,28 @@ static void guc_waklv_init(struct xe_guc_ads *ads)
guc_waklv_enable(ads, NULL, 0, &offset, &remain,
GUC_WA_KLV_CLR_CS_INDIRECT_RING_STATE_IF_IDLE_AT_CTX_REG);
if (XE_GT_WA(gt, 22022079272) && GUC_FIRMWARE_VER_AT_LEAST(&gt->uc.guc, 70, 62))
guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_REMAP_RANGED_TLB_INV);
/* The GuC does not enable the sem_tok_64 feature on NVL-S */
if (XE_GT_WA(gt, 16029897822) && gt_to_xe(gt)->info.platform != XE_NOVALAKE_S &&
GUC_FIRMWARE_VER_AT_LEAST(&gt->uc.guc, 70, 69))
guc_waklv_enable(ads, NULL, 0, &offset, &remain,
GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64);
/*
* On GuC firmware 70.66 and above, use the Feature KLV (shared with the
* WA KLV buffer); older firmware uses GUC_CTL_DISABLE_MULTI_QUEUE in
* the init params instead.
*/
if (!xe_configfs_get_enable_multi_queue(to_pci_dev(gt_to_xe(gt)->drm.dev)) &&
GUC_FIRMWARE_VER_AT_LEAST(&gt->uc.guc, 70, 66)) {
u32 data = 1;
guc_waklv_enable(ads, &data, 1, &offset, &remain,
GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE);
}
size = guc_ads_waklv_size(ads) - remain;
if (!size)
return;
@@ -463,20 +515,36 @@ static void fill_engine_enable_masks(struct xe_gt *gt,
struct iosys_map *info_map)
{
struct xe_device *xe = gt_to_xe(gt);
u16 guc_class;
info_map_write(xe, info_map, engine_enabled_masks[GUC_RENDER_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_RENDER));
info_map_write(xe, info_map, engine_enabled_masks[GUC_BLITTER_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_COPY));
info_map_write(xe, info_map, engine_enabled_masks[GUC_VIDEO_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_VIDEO_DECODE));
info_map_write(xe, info_map,
engine_enabled_masks[GUC_VIDEOENHANCE_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_VIDEO_ENHANCE));
info_map_write(xe, info_map, engine_enabled_masks[GUC_COMPUTE_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_COMPUTE));
info_map_write(xe, info_map, engine_enabled_masks[GUC_GSC_OTHER_CLASS],
engine_enable_mask(gt, XE_ENGINE_CLASS_OTHER));
for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class)
info_map_write(xe, info_map, engine_enabled_masks[guc_class],
engine_enable_mask(gt, guc_class));
}
u16 xe_hwe_to_guc_class(struct xe_hw_engine *hwe)
{
if (xe_guc_has_paging_engine(&hwe->gt->uc.guc) &&
xe_gt_is_usm_hwe(hwe->gt, hwe))
return GUC_PAGING_CLASS;
switch (hwe->class) {
case XE_ENGINE_CLASS_RENDER:
return GUC_RENDER_CLASS;
case XE_ENGINE_CLASS_VIDEO_DECODE:
return GUC_VIDEO_CLASS;
case XE_ENGINE_CLASS_VIDEO_ENHANCE:
return GUC_VIDEOENHANCE_CLASS;
case XE_ENGINE_CLASS_COPY:
return GUC_BLITTER_CLASS;
case XE_ENGINE_CLASS_COMPUTE:
return GUC_COMPUTE_CLASS;
case XE_ENGINE_CLASS_OTHER:
return GUC_GSC_OTHER_CLASS;
default:
XE_WARN_ON(hwe->class);
return -1;
}
}
/*
@@ -491,15 +559,14 @@ static void guc_golden_lrc_init(struct xe_guc_ads *ads)
offsetof(struct __guc_ads_blob, system_info));
size_t alloc_size, real_size;
u32 addr_ggtt, offset;
int class;
u16 guc_class;
offset = guc_ads_golden_lrc_offset(ads);
addr_ggtt = xe_bo_ggtt_addr(ads->bo) + offset;
for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) {
u8 guc_class;
guc_class = xe_engine_class_to_guc_class(class);
for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) {
enum xe_engine_class class =
guc_class_to_engine_class(guc_class);
if (!info_map_read(xe, &info_map,
engine_enabled_masks[guc_class]))
@@ -548,11 +615,14 @@ static void guc_mapping_table_init(struct xe_gt *gt,
guc_mapping_table_init_invalid(gt, info_map);
for_each_hw_engine(hwe, gt, id) {
u16 guc_logical_instance;
u8 guc_class;
guc_class = xe_engine_class_to_guc_class(hwe->class);
guc_class = xe_hwe_to_guc_class(hwe);
guc_logical_instance = xe_hwe_guc_logical_instance(hwe);
info_map_write(xe, info_map,
mapping_table[guc_class][hwe->logical_instance],
mapping_table[guc_class][guc_logical_instance],
hwe->instance);
}
}
@@ -580,6 +650,9 @@ static u32 guc_get_capture_engine_mask(struct xe_gt *gt, struct iosys_map *info_
case GUC_CAPTURE_LIST_CLASS_GSC_OTHER:
mask = info_map_read(xe, info_map, engine_enabled_masks[GUC_GSC_OTHER_CLASS]);
break;
case GUC_CAPTURE_LIST_CLASS_PAGING:
mask = info_map_read(xe, info_map, engine_enabled_masks[GUC_PAGING_CLASS]);
break;
default:
mask = 0;
}
@@ -805,7 +878,7 @@ static void guc_mmio_reg_state_init(struct xe_guc_ads *ads)
* 2. Record in the header (ads.reg_state_list) the address
* location and number of entries
*/
gc = xe_engine_class_to_guc_class(hwe->class);
gc = xe_hwe_to_guc_class(hwe);
ads_blob_write(ads, ads.reg_state_list[gc][hwe->instance].address, addr);
ads_blob_write(ads, ads.reg_state_list[gc][hwe->instance].count, count);
@@ -948,14 +1021,13 @@ static void guc_golden_lrc_populate(struct xe_guc_ads *ads)
offsetof(struct __guc_ads_blob, system_info));
size_t total_size = 0, alloc_size, real_size;
u32 offset;
int class;
u16 guc_class;
offset = guc_ads_golden_lrc_offset(ads);
for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) {
u8 guc_class;
guc_class = xe_engine_class_to_guc_class(class);
for (guc_class = 0; guc_class <= GUC_LAST_ENGINE_CLASS; ++guc_class) {
enum xe_engine_class class =
guc_class_to_engine_class(guc_class);
if (!info_map_read(xe, &info_map,
engine_enabled_masks[guc_class]))

View File

@@ -249,6 +249,8 @@ static const struct __guc_mmio_reg_descr_group xe_hpg_lists[] = {
MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_BLITTER),
MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_GSC_OTHER),
MAKE_REGLIST(xe_lp_gsc_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_GSC_OTHER),
MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_PAGING),
MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_PAGING),
{}
};
@@ -265,6 +267,8 @@ static const struct __guc_mmio_reg_descr_group xe3p_lists[] = {
MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_BLITTER),
MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_GSC_OTHER),
MAKE_REGLIST(xe_lp_gsc_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_GSC_OTHER),
MAKE_REGLIST(empty_regs_list, PF, ENGINE_CLASS, GUC_CAPTURE_LIST_CLASS_PAGING),
MAKE_REGLIST(xe_blt_inst_regs, PF, ENGINE_INSTANCE, GUC_CAPTURE_LIST_CLASS_PAGING),
{}
};
static const char * const capture_list_type_names[] = {
@@ -279,6 +283,7 @@ static const char * const capture_engine_class_names[] = {
"VideoEnhance",
"Blitter",
"GSC-Other",
"Paging",
};
struct __guc_capture_ads_cache {
@@ -440,7 +445,7 @@ static void guc_capture_alloc_steered_lists(struct xe_guc *guc)
* to be extended
*/
for_each_hw_engine(hwe, gt, id) {
if (xe_engine_class_to_guc_capture_class(hwe->class) ==
if (xe_hwe_to_guc_capture_class(hwe) ==
GUC_CAPTURE_LIST_CLASS_RENDER_COMPUTE) {
has_rcs_ccs = true;
break;
@@ -772,6 +777,10 @@ size_t xe_guc_capture_ads_input_worst_size(struct xe_guc *guc)
total_size = PAGE_SIZE; /* Pad a page in front for empty lists */
for (i = 0; i < GUC_CAPTURE_LIST_INDEX_MAX; i++) {
for (j = 0; j < GUC_CAPTURE_LIST_CLASS_MAX; j++) {
if (!xe_guc_has_paging_engine(guc) &&
j == GUC_CAPTURE_LIST_CLASS_PAGING)
continue;
if (xe_guc_capture_getlistsize(guc, i,
GUC_STATE_CAPTURE_TYPE_ENGINE_CLASS,
j, &class_size) < 0)
@@ -818,7 +827,7 @@ static int guc_capture_output_size_est(struct xe_guc *guc)
for_each_hw_engine(hwe, gt, id) {
enum guc_capture_list_class_type capture_class;
capture_class = xe_engine_class_to_guc_capture_class(hwe->class);
capture_class = xe_hwe_to_guc_capture_class(hwe);
capture_size += sizeof(struct guc_state_capture_group_header_t) +
(3 * sizeof(struct guc_state_capture_header_t));
@@ -1626,7 +1635,7 @@ xe_engine_manual_capture(struct xe_hw_engine *hwe, struct xe_hw_engine_snapshot
if (!new)
return;
capture_class = xe_engine_class_to_guc_capture_class(hwe->class);
capture_class = xe_hwe_to_guc_capture_class(hwe);
for (type = GUC_STATE_CAPTURE_TYPE_GLOBAL; type < GUC_STATE_CAPTURE_TYPE_MAX; type++) {
struct gcap_reg_list_info *reginfo = &new->reginfo[type];
/*
@@ -1668,7 +1677,7 @@ xe_engine_manual_capture(struct xe_hw_engine *hwe, struct xe_hw_engine_snapshot
}
}
new->eng_class = xe_engine_class_to_guc_class(hwe->class);
new->eng_class = xe_hwe_to_guc_class(hwe);
new->eng_inst = hwe->instance;
new->guc_id = guc_id;
new->lrca = lrca;
@@ -1832,7 +1841,7 @@ void xe_engine_snapshot_print(struct xe_hw_engine_snapshot *snapshot, struct drm
xe_gt_assert(gt, snapshot->hwe);
capture_class = xe_engine_class_to_guc_capture_class(snapshot->hwe->class);
capture_class = xe_hwe_to_guc_capture_class(snapshot->hwe);
drm_printf(p, "%s (physical), logical instance=%d\n",
snapshot->name ? snapshot->name : "",
@@ -1904,7 +1913,7 @@ xe_guc_capture_get_matching_and_lock(struct xe_exec_queue *q)
for_each_hw_engine(hwe, q->gt, id) {
if (hwe != q->hwe)
continue;
guc_class = xe_engine_class_to_guc_class(hwe->class);
guc_class = xe_hwe_to_guc_class(hwe);
break;
}

View File

@@ -28,6 +28,8 @@ static inline enum guc_capture_list_class_type xe_guc_class_to_capture_class(u16
case GUC_VIDEOENHANCE_CLASS:
case GUC_BLITTER_CLASS:
return class;
case GUC_PAGING_CLASS:
return GUC_CAPTURE_LIST_CLASS_PAGING;
default:
XE_WARN_ON(class);
return GUC_CAPTURE_LIST_CLASS_MAX;
@@ -35,9 +37,9 @@ static inline enum guc_capture_list_class_type xe_guc_class_to_capture_class(u16
}
static inline enum guc_capture_list_class_type
xe_engine_class_to_guc_capture_class(enum xe_engine_class class)
xe_hwe_to_guc_capture_class(struct xe_hw_engine *hwe)
{
return xe_guc_class_to_capture_class(xe_engine_class_to_guc_class(class));
return xe_guc_class_to_capture_class(xe_hwe_to_guc_class(hwe));
}
void xe_guc_capture_process(struct xe_guc *guc);

View File

@@ -1065,6 +1065,11 @@ static int __guc_ct_send_locked(struct xe_guc_ct *ct, const u32 *action,
xe_gt_assert(gt, g2h_len || !num_g2h);
lockdep_assert_held(&ct->lock);
if (xe_device_wedged(ct_to_xe(ct))) {
ret = -ENOTRECOVERABLE;
goto out;
}
if (unlikely(ct->ctbs.h2g.info.broken)) {
ret = -EPIPE;
goto out;
@@ -1236,6 +1241,36 @@ static int guc_ct_send(struct xe_guc_ct *ct, const u32 *action, u32 len,
return ret;
}
/**
* xe_guc_ct_send - Send an HXG message to the GuC over CT
* @ct: the &xe_guc_ct
* @action: dword array with the HXG message (can't be NULL)
* @len: length of the HXG message in dwords (can't be 0)
* @g2h_len: G2H response space to reserve in dwords, or 0
* @num_g2h: number of G2H messages expected, or 0
*
* Return codes from the non-blocking send helpers are:
*
* * -ENOTRECOVERABLE: the xe device is wedged. Stop submitting new GuC work; the
* request cannot make progress until the device is recovered.
* * -EPIPE: the H2G CTB is marked broken. The channel stays unusable until the
* CT is restarted, which clears the broken flag.
* * -ENODEV: the CT channel is disabled, messages not expected in this state.
* Don't retry until it is enabled again.
* * -ECANCELED: the CT channel is stopped or a GT recovery is pending; the
* message was dropped. Often benign. Cancel-tolerant callers (e.g. TLB
* invalidations, GuC submission) rely on the stop/start flow to recover;
* others should retry once the CT is re-enabled or the reset/recovery
* completes.
* * -EDEADLK: no CTB room and the wait for space timed out. The send helpers
* have already requested an async GT reset before returning this error.
*
* -ENOMEM may also be returned if an internal allocation fails; the blocking
* xe_guc_ct_send_recv() path retries that allocation. -EBUSY and
* -EAGAIN are internal flow-control results handled by the send helpers.
*
* Return: 0 on success, or a negative error code on failure.
*/
int xe_guc_ct_send(struct xe_guc_ct *ct, const u32 *action, u32 len,
u32 g2h_len, u32 num_g2h)
{
@@ -1388,7 +1423,7 @@ static int guc_ct_send_recv(struct xe_guc_ct *ct, const u32 *action, u32 len,
if (g2h_fence.fail) {
if (g2h_fence.cancel) {
xe_gt_dbg(gt, "H2G request %#x canceled!\n", action[0]);
ret = -ECANCELED;
ret = xe_device_wedged(ct_to_xe(ct)) ? -ENOTRECOVERABLE : -ECANCELED;
goto unlock;
}
xe_gt_err(gt, "H2G request %#x failed: error %#x hint %#x\n",
@@ -1661,6 +1696,9 @@ static int process_g2h_msg(struct xe_guc_ct *ct, u32 *msg, u32 len)
ret = xe_guc_exec_queue_memory_cat_error_handler(guc, payload,
adj_len);
break;
case XE_GUC_ACTION_NOTIFY_UNCORRECTABLE_LOCAL_ERROR:
ret = xe_guc_uncorrectable_error_handler(guc, payload, adj_len);
break;
case XE_GUC_ACTION_REPORT_PAGE_FAULT_REQ_DESC:
ret = xe_guc_pagefault_handler(guc, payload, adj_len);
break;
@@ -1724,6 +1762,9 @@ static int g2h_read(struct xe_guc_ct *ct, u32 *msg, bool fast_path)
xe_gt_assert(gt, xe_guc_ct_initialized(ct));
lockdep_assert_held(&ct->fast_lock);
if (xe_device_wedged(xe))
return -ENOTRECOVERABLE;
if (ct->state == XE_GUC_CT_STATE_DISABLED)
return -ENODEV;

View File

@@ -27,7 +27,8 @@ static struct iosys_map engine_activity_map(struct xe_guc *guc, struct xe_hw_eng
{
struct xe_guc_engine_activity *engine_activity = &guc->engine_activity;
struct engine_activity_buffer *buffer;
u16 guc_class = xe_engine_class_to_guc_class(hwe->class);
u16 guc_class = xe_hwe_to_guc_class(hwe);
u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe);
size_t offset;
if (engine_activity->num_functions) {
@@ -39,7 +40,7 @@ static struct iosys_map engine_activity_map(struct xe_guc *guc, struct xe_hw_eng
}
offset += offsetof(struct guc_engine_activity_data,
engine_activity[guc_class][hwe->logical_instance]);
engine_activity[guc_class][guc_logical_instance]);
return IOSYS_MAP_INIT_OFFSET(&buffer->activity_bo->vmap, offset);
}
@@ -150,9 +151,10 @@ static struct engine_activity *hw_engine_to_engine_activity(struct xe_hw_engine
{
struct xe_guc *guc = &hwe->gt->uc.guc;
struct engine_activity_group *eag = &guc->engine_activity.eag[index];
u16 guc_class = xe_engine_class_to_guc_class(hwe->class);
u16 guc_class = xe_hwe_to_guc_class(hwe);
u16 guc_logical_instance = xe_hwe_guc_logical_instance(hwe);
return &eag->engine[guc_class][hwe->logical_instance];
return &eag->engine[guc_class][guc_logical_instance];
}
static u64 cpu_ns_to_guc_tsc_tick(ktime_t ns, u32 freq)
@@ -473,7 +475,7 @@ void xe_guc_engine_activity_enable_stats(struct xe_guc *guc)
ret = enable_engine_activity_stats(guc);
if (ret)
xe_gt_err(guc_to_gt(guc), "failed to enable activity stats%d\n", ret);
xe_gt_err(guc_to_gt(guc), "failed to enable activity stats: %pe\n", ERR_PTR(ret));
else
engine_activity_set_cpu_ts(guc, 0);
}

View File

@@ -10,6 +10,7 @@
#include <linux/workqueue.h>
#include "xe_gpu_scheduler_types.h"
#include "xe_hw_fence_types.h"
struct dma_fence;
struct xe_exec_queue;
@@ -24,6 +25,10 @@ struct xe_guc_exec_queue {
struct rcu_head rcu;
/** @sched: GPU scheduler for this xe_exec_queue */
struct xe_gpu_scheduler sched;
/**
* @name: Scheduler timeline name, kept with @sched until RCU free.
*/
char name[MAX_FENCE_NAME_LEN];
/** @entity: Scheduler entity for this xe_exec_queue */
struct xe_sched_entity entity;
/**
@@ -49,6 +54,13 @@ struct xe_guc_exec_queue {
wait_queue_head_t suspend_wait;
/** @suspend_pending: a suspend of the exec_queue is pending */
bool suspend_pending;
/**
* @suspend_count: Reference count of active suspend requests. The
* exec_queue remains suspended while this is non-zero, allowing
* multiple concurrent callers to independently hold a suspend without
* prematurely re-enabling the queue. Protected by @sched.msg_lock.
*/
int suspend_count;
/**
* @needs_cleanup: Needs a cleanup message during VF post migration
* recovery.

View File

@@ -68,6 +68,7 @@ struct guc_update_exec_queue_policy {
#define GUC_CTL_MAIN_GAMCTRL_QUEUES BIT(9)
#define GUC_CTL_DISABLE_SCHEDULER BIT(14)
#define GUC_CTL_ENABLE_L2FLUSH_OPT BIT(15)
#define GUC_CTL_DISABLE_MULTI_QUEUE BIT(24)
#define GUC_CTL_DEBUG 3
#define GUC_LOG_VERBOSITY REG_GENMASK(1, 0)

View File

@@ -4,14 +4,27 @@
*/
#include <linux/bitfield.h>
#include <kunit/static_stub.h>
#include <drm/drm_print.h>
#include "abi/guc_klvs_abi.h"
#include "abi/xe_driver_klvs_abi.h"
#include "xe_guc_klv_helpers.h"
#include "xe_guc_klv_thresholds_set.h"
#define make_u64(hi, lo) ((u64)((u64)(u32)(hi) << 32 | (u32)(lo)))
static bool is_group_key(u16 key)
{
KUNIT_STATIC_STUB_REDIRECT(is_group_key, key);
return false;
}
static bool is_reserved_key(u16 key)
{
return in_range(key, GUC_KLV_RESERVED_RANGE_START, GUC_KLV_RESERVED_RANGE_LEN);
}
/**
* xe_guc_klv_key_to_string - Convert KLV key into friendly name.
* @key: the `GuC KLV`_ key
@@ -24,6 +37,8 @@ const char *xe_guc_klv_key_to_string(u16 key)
/* GuC Global Config KLVs */
case GUC_KLV_GLOBAL_CFG_GROUP_SCHEDULING_AVAILABLE_KEY:
return "group_scheduling_available";
case GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY:
return "num_paging_engine_instances";
/* VGT POLICY keys */
case GUC_KLV_VGT_POLICY_SCHED_IF_IDLE_KEY:
return "sched_if_idle";
@@ -71,11 +86,63 @@ const char *xe_guc_klv_key_to_string(u16 key)
MAKE_XE_GUC_KLV_THRESHOLDS_SET(define_threshold_key_to_string_case)
#undef define_threshold_key_to_string_case
/* driver KLVs */
case MIGRATION_KLV_DEVICE_DEVID_KEY:
return "migration_devid";
case MIGRATION_KLV_DEVICE_REVID_KEY:
return "migration_revid";
default:
if (is_reserved_key(key))
return "(reserved)";
return "(unknown)";
}
}
/**
* xe_guc_klv_print_one() - Print single `GuC KLV`_.
* @key: KLV key
* @len: KLV length (in u32 dwords) of the KLV @value
* @value: KLV value (as array of @len u32 dwords)
* @p: the &drm_printer
*
* The buffer may contain more than one KLV.
*/
void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer *p)
{
const char *name = xe_guc_klv_key_to_string(key);
if (is_group_key(key)) {
struct drm_printer gp = drm_line_printer(p, name, 0);
drm_printf(p, "{ key %#06x : group %u dwords } # %s\n",
key, len, name);
/* print group recursively */
xe_guc_klv_print(value, len, &gp);
return;
}
switch (len) {
case 0:
drm_printf(p, "{ key %#06x : no value } # %s\n", key, name);
break;
case 1:
drm_printf(p, "{ key %#06x : 32b value %u } # %s\n",
key, value[0], name);
break;
case 2:
drm_printf(p, "{ key %#06x : 64b value %#llx } # %s\n",
key, make_u64(value[1], value[0]), name);
break;
default:
drm_printf(p, "{ key %#06x : %zu bytes %*ph } # %s\n",
key, len * sizeof(u32), (int)(len * sizeof(u32)),
value, name);
break;
}
}
/**
* xe_guc_klv_print - Print content of the buffer with `GuC KLV`_.
* @klvs: the buffer with KLVs
@@ -101,26 +168,7 @@ void xe_guc_klv_print(const u32 *klvs, u32 num_dwords, struct drm_printer *p)
return;
}
switch (len) {
case 0:
drm_printf(p, "{ key %#06x : no value } # %s\n",
key, xe_guc_klv_key_to_string(key));
break;
case 1:
drm_printf(p, "{ key %#06x : 32b value %u } # %s\n",
key, klvs[0], xe_guc_klv_key_to_string(key));
break;
case 2:
drm_printf(p, "{ key %#06x : 64b value %#llx } # %s\n",
key, make_u64(klvs[1], klvs[0]),
xe_guc_klv_key_to_string(key));
break;
default:
drm_printf(p, "{ key %#06x : %zu bytes %*ph } # %s\n",
key, len * sizeof(u32), (int)(len * sizeof(u32)),
klvs, xe_guc_klv_key_to_string(key));
break;
}
xe_guc_klv_print_one(key, len, klvs, p);
klvs += len;
num_dwords -= len;
@@ -155,3 +203,175 @@ int xe_guc_klv_count(const u32 *klvs, u32 num_dwords)
return num_dwords ? -ENODATA : num_klvs;
}
static size_t to_num_bytes(u16 dwords)
{
return dwords * sizeof(u32);
}
static u16 to_num_dwords(size_t size)
{
return round_up(size, sizeof(u32)) / sizeof(u32);
}
/**
* xe_guc_klv_encode_u32() - Encode 32-bit value as KLV.
* @klvs: the buffer where to place KLV
* @avail: number of dwords (u32) available in the buffer
* @key: key to be used
* @value: value to be encoded
*
* Return: pointer to the buffer location past the encoded KLV or
* an ERR_PTR if there was no space to encode the KLV.
*/
u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value)
{
u16 len = to_num_dwords(sizeof(u32));
if (IS_ERR(klvs))
return klvs;
if (avail < GUC_KLV_LEN_MIN + len)
return ERR_PTR(-ENOSPC);
*klvs++ = PREP_GUC_KLV(key, len);
*klvs++ = value;
return klvs;
}
/**
* xe_guc_klv_encode_u64() - Encode 64-bit value as KLV.
* @klvs: the buffer where to place KLV
* @avail: number of dwords (u32) available in the buffer
* @key: key to be used
* @value: value to be encoded
*
* Return: pointer to the buffer location past the encoded KLV or
* an ERR_PTR if there was no space to encode the KLV.
*/
u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value)
{
u16 len = to_num_dwords(sizeof(u64));
if (IS_ERR(klvs))
return klvs;
if (avail < GUC_KLV_LEN_MIN + len)
return ERR_PTR(-ENOSPC);
*klvs++ = PREP_GUC_KLV(key, len);
*klvs++ = lower_32_bits(value);
*klvs++ = upper_32_bits(value);
return klvs;
}
/**
* xe_guc_klv_encode_string() - Encode string as KLV.
* @klvs: the buffer where to place KLV
* @avail: number of dwords (u32) available in the buffer
* @key: key to be used
* @s: string to be encoded
*
* Return: pointer to the buffer location past the encoded KLV or
* an ERR_PTR if there was no space to encode the KLV.
*/
u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s)
{
size_t longest = to_num_bytes(FIELD_MAX(GUC_KLV_0_LEN));
size_t size = strnlen(s, longest) + 1; /* \0 */
u16 len = to_num_dwords(size);
if (IS_ERR(klvs))
return klvs;
if (size > longest)
return ERR_PTR(-E2BIG);
if (avail < GUC_KLV_LEN_MIN + len)
return ERR_PTR(-ENOSPC);
*klvs++ = PREP_GUC_KLV(key, len);
strscpy_pad((void *)klvs, s, to_num_bytes(len));
return klvs + len;
}
/**
* xe_guc_klv_encode_object() - Encode object using custom encoder as single KLV.
* @klvs: the buffer where to place KLV
* @avail: number of dwords (u32) available in the buffer
* @key: key to be used
* @obj: opaque object pointer
* @encoder: function pointer to the custom encoder
*
* Return: pointer to the buffer location past the encoded KLV or
* an ERR_PTR if there was no space to encode the KLV.
*/
u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj,
u32 *(*encoder)(u32 *klvs, u32 avail, const void *obj))
{
u32 *end;
if (IS_ERR(klvs))
return klvs;
if (avail < GUC_KLV_LEN_MIN)
return ERR_PTR(-ENOSPC);
if (avail > GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN))
avail = GUC_KLV_LEN_MIN + FIELD_MAX(GUC_KLV_0_LEN);
end = encoder(klvs + GUC_KLV_LEN_MIN, avail - GUC_KLV_LEN_MIN, obj);
if (IS_ERR(end))
return end;
if (WARN_ON(end < klvs + GUC_KLV_LEN_MIN))
return ERR_PTR(-EPIPE);
if (WARN_ON(end > klvs + avail))
return ERR_PTR(-EFBIG);
*klvs = PREP_GUC_KLV(key, end - (klvs + GUC_KLV_LEN_MIN));
return end;
}
/**
* xe_guc_klv_parser() - Parse and decode stream of KLVs.
* @klvs: the buffer with KLVs
* @num_dwords: number of dwords (u32) available in the buffer
* @obj: opaque pointer to be used by the @decoder function
* @decoder: pointer to the decoder function
*
* Return: The sum of all results returned by the decoder or
* an -errno on decoder or buffer failure.
*/
int xe_guc_klv_parser(const u32 *klvs, u32 num_dwords, void *obj,
int (*decoder)(void *obj, u16 key, u16 len, const u32 *value))
{
int total = 0;
int ret;
while (num_dwords >= GUC_KLV_LEN_MIN) {
u16 key = FIELD_GET(GUC_KLV_0_KEY, klvs[0]);
u16 len = FIELD_GET(GUC_KLV_0_LEN, klvs[0]);
klvs += GUC_KLV_LEN_MIN;
num_dwords -= GUC_KLV_LEN_MIN;
if (num_dwords < len)
return -ENODATA;
ret = decoder(obj, key, len, klvs);
if (ret < 0)
return ret;
total += ret;
klvs += len;
num_dwords -= len;
}
return total;
}
#if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST)
#include "tests/xe_guc_klv_helpers_kunit.c"
#endif

View File

@@ -13,9 +13,19 @@ struct drm_printer;
const char *xe_guc_klv_key_to_string(u16 key);
void xe_guc_klv_print_one(u16 key, u16 len, const u32 *value, struct drm_printer *p);
void xe_guc_klv_print(const u32 *klvs, u32 num_dwords, struct drm_printer *p);
int xe_guc_klv_count(const u32 *klvs, u32 num_dwords);
u32 *xe_guc_klv_encode_u32(u32 *klvs, u32 avail, u16 key, u32 value);
u32 *xe_guc_klv_encode_u64(u32 *klvs, u32 avail, u16 key, u64 value);
u32 *xe_guc_klv_encode_string(u32 *klvs, u32 avail, u16 key, const char *s);
u32 *xe_guc_klv_encode_object(u32 *klvs, u32 avail, u16 key, const void *obj,
u32 *(*encoder)(u32 *klvs, u32 avail, const void *obj));
int xe_guc_klv_parser(const u32 *klvs, u32 num_dwords, void *obj,
int (*decoder)(void *obj, u16 key, u16 len, const u32 *value));
/**
* PREP_GUC_KLV - Prepare KLV header value based on provided key and len.
* @key: KLV key

View File

@@ -10,6 +10,7 @@
#include <linux/circ_buf.h>
#include <linux/dma-fence-array.h>
#include <drm/drm_drv.h>
#include <drm/drm_managed.h>
#include "abi/guc_actions_abi.h"
@@ -37,6 +38,7 @@
#include "xe_macros.h"
#include "xe_map.h"
#include "xe_mocs.h"
#include "xe_module.h"
#include "xe_pm.h"
#include "xe_ring_ops_types.h"
#include "xe_sched_job.h"
@@ -232,17 +234,9 @@ static bool exec_queue_killed_or_banned_or_wedged(struct xe_exec_queue *q)
static void guc_submit_sw_fini(struct drm_device *drm, void *arg)
{
struct xe_guc *guc = arg;
struct xe_device *xe = guc_to_xe(guc);
struct xe_gt *gt = guc_to_gt(guc);
int ret;
ret = wait_event_timeout(guc->submission_state.fini_wq,
xa_empty(&guc->submission_state.exec_queue_lookup),
HZ * 5);
drain_workqueue(xe->destroy_wq);
xe_gt_assert(gt, ret);
xe_gt_assert(gt, xa_empty(&guc->submission_state.exec_queue_lookup));
xa_destroy(&guc->submission_state.exec_queue_lookup);
}
@@ -319,8 +313,6 @@ int xe_guc_submit_init(struct xe_guc *guc, unsigned int num_ids)
xa_init(&guc->submission_state.exec_queue_lookup);
init_waitqueue_head(&guc->submission_state.fini_wq);
primelockdep(guc);
guc->submission_state.initialized = true;
@@ -411,9 +403,6 @@ static void __release_guc_id(struct xe_guc *guc, struct xe_exec_queue *q,
xe_guc_id_mgr_release_locked(&guc->submission_state.idm,
q->guc->id, q->width);
if (xa_empty(&guc->submission_state.exec_queue_lookup))
wake_up(&guc->submission_state.fini_wq);
mutex_unlock(&guc->submission_state.lock);
}
@@ -972,6 +961,27 @@ static void __register_exec_queue(struct xe_guc *guc,
xe_guc_ct_send(&guc->ct, action, ARRAY_SIZE(action), 0, 0);
}
static u32 xe_hwe_guc_logical_to_submit_mask(struct xe_hw_engine *hwe, u32 logical_mask)
{
struct xe_gt *gt = hwe->gt;
if (xe_gt_is_usm_hwe(gt, hwe)) {
int shift = gt->usm.paging_hwe0->logical_instance;
u32 paging_logical_mask = gt->usm.paging_logical_mask;
xe_gt_assert(gt, (logical_mask & paging_logical_mask) == logical_mask);
/*
* Remap to GUC_PAGING_CLASS logical instance mask, if
* applicable.
*/
if (xe_guc_has_paging_engine(&hwe->gt->uc.guc))
return logical_mask >> shift;
}
return logical_mask;
}
static void register_exec_queue(struct xe_exec_queue *q, int ctx_type)
{
struct xe_guc *guc = exec_queue_to_guc(q);
@@ -984,8 +994,9 @@ static void register_exec_queue(struct xe_exec_queue *q, int ctx_type)
memset(&info, 0, sizeof(info));
info.context_idx = q->guc->id;
info.engine_class = xe_engine_class_to_guc_class(q->class);
info.engine_submit_mask = q->logical_mask;
info.engine_class = xe_hwe_to_guc_class(q->hwe);
info.engine_submit_mask =
xe_hwe_guc_logical_to_submit_mask(q->hwe, q->logical_mask);
info.hwlrca_lo = lower_32_bits(xe_lrc_descriptor(lrc));
info.hwlrca_hi = upper_32_bits(xe_lrc_descriptor(lrc));
info.flags = CONTEXT_REGISTRATION_FLAG_KMD |
@@ -1682,10 +1693,36 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job)
return DRM_GPU_SCHED_STAT_NO_HANG;
}
static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q);
static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q);
static void guc_exec_queue_fini(struct xe_exec_queue *q)
{
struct xe_guc_exec_queue *ge = q->guc;
struct xe_guc *guc = exec_queue_to_guc(q);
struct drm_device *drm = &guc_to_xe(guc)->drm;
/*
* A secondary can leave the group while still preempt suspended (e.g.
* xe_vm_remove_compute_exec_queue() forces its preempt fence to signal,
* which suspends it). It holds one forwarded suspend reference on the
* primary, so drop it and resume the primary if it was the last member
* that had it suspended. Primaries forward to nobody, so they don't need
* this.
*
* First make sure the primary's forwarded suspend has completed. If the
* secondary was killed/reset before its preempt fence worker ran, that
* worker skips suspend_wait() (see preempt_fence_work_func()), leaving
* the primary's suspend possibly in flight. drop_suspend() runs under a
* spinlock and cannot wait, so drain it here with the uninterruptible
* blocking wait; otherwise resuming the primary in drop_suspend() could
* trip the !suspend_pending assert.
*/
if (xe_exec_queue_is_multi_queue_secondary(q)) {
if (READ_ONCE(q->guc->suspend_count))
guc_exec_queue_suspend_wait_blocking(q);
guc_exec_queue_multi_queue_drop_suspend(q);
}
if (xe_exec_queue_is_multi_queue_secondary(q)) {
struct xe_exec_queue_group *group = q->multi_queue.group;
@@ -1704,36 +1741,52 @@ static void guc_exec_queue_fini(struct xe_exec_queue *q)
* (timeline name).
*/
kfree_rcu(ge, rcu);
drm_dev_put(drm);
}
static void guc_exec_queue_do_destroy(struct xe_exec_queue *q)
{
struct xe_guc_exec_queue *ge = q->guc;
struct xe_guc *guc = exec_queue_to_guc(q);
struct xe_device *xe = guc_to_xe(guc);
struct drm_device *drm = &xe->drm;
/*
* guc_exec_queue_fini() drops the queue's drm_device ref.
* Keep the device alive until the PM-runtime guard unwinds.
*/
drm_dev_get(drm);
scoped_guard(xe_pm_runtime, xe) {
trace_xe_exec_queue_destroy(q);
/* Confirm no work left behind accessing device structures */
cancel_delayed_work_sync(&ge->sched.base.work_tdr);
xe_exec_queue_fini(q);
}
drm_dev_put(drm);
}
static void __guc_exec_queue_destroy_async(struct work_struct *w)
{
struct xe_guc_exec_queue *ge =
container_of(w, struct xe_guc_exec_queue, destroy_async);
struct xe_exec_queue *q = ge->q;
struct xe_guc *guc = exec_queue_to_guc(q);
guard(xe_pm_runtime)(guc_to_xe(guc));
trace_xe_exec_queue_destroy(q);
/* Confirm no work left behind accessing device structures */
cancel_delayed_work_sync(&ge->sched.base.work_tdr);
xe_exec_queue_fini(q);
guc_exec_queue_do_destroy(ge->q);
}
static void guc_exec_queue_destroy_async(struct xe_exec_queue *q)
{
struct xe_guc *guc = exec_queue_to_guc(q);
struct xe_device *xe = guc_to_xe(guc);
INIT_WORK(&q->guc->destroy_async, __guc_exec_queue_destroy_async);
/* We must block on kernel engines so slabs are empty on driver unload */
if (q->flags & EXEC_QUEUE_FLAG_PERMANENT || exec_queue_wedged(q))
__guc_exec_queue_destroy_async(&q->guc->destroy_async);
guc_exec_queue_do_destroy(q);
else
queue_work(xe->destroy_wq, &q->guc->destroy_async);
xe_destroy_wq_queue(&q->guc->destroy_async);
}
static void __guc_exec_queue_destroy(struct xe_guc *guc, struct xe_exec_queue *q)
@@ -1928,6 +1981,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
{
struct xe_gpu_scheduler *sched;
struct xe_guc *guc = exec_queue_to_guc(q);
struct drm_device *drm = &guc_to_xe(guc)->drm;
struct workqueue_struct *submit_wq = NULL;
struct xe_guc_exec_queue *ge;
long timeout;
@@ -1939,6 +1993,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
if (!ge)
return -ENOMEM;
drm_dev_get(drm);
q->guc = ge;
ge->q = q;
init_rcu_head(&ge->rcu);
@@ -1956,6 +2012,8 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
xe_exec_queue_assign_name(q, q->guc->id);
strscpy(ge->name, q->name, sizeof(ge->name));
/*
* Use primary queue's submit_wq for all secondary queues of a
* multi queue group. This serialization avoids any locking around
@@ -1970,7 +2028,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
err = xe_sched_init(&ge->sched, &drm_sched_ops, &xe_sched_ops,
submit_wq, xe_lrc_ring_size() / MAX_JOB_SIZE_BYTES, 64,
timeout, guc_to_gt(guc)->ordered_wq, NULL,
q->name, gt_to_xe(q->gt)->drm.dev);
ge->name, gt_to_xe(q->gt)->drm.dev);
if (err)
goto err_release_id;
@@ -2015,6 +2073,7 @@ static int guc_exec_queue_init(struct xe_exec_queue *q)
release_guc_id(guc, q);
err_free:
kfree(ge);
drm_dev_put(drm);
return err;
}
@@ -2164,23 +2223,147 @@ static int guc_exec_queue_set_multi_queue_priority(struct xe_exec_queue *q,
return 0;
}
/*
* Core suspend: take a suspend reference on @q and, on the first reference,
* disable its GuC context so the GPU is actually preempted. Caller must have
* ensured @q is not killed/banned/wedged. Returns true if this was the first
* suspend reference (the 0->1 transition).
*/
static bool __guc_exec_queue_suspend(struct xe_exec_queue *q)
{
struct xe_guc_exec_queue *ge = q->guc;
struct xe_gpu_scheduler *sched = &ge->sched;
struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_SUSPEND;
bool first;
xe_sched_msg_lock(sched);
first = (++ge->suspend_count == 1);
if (first) {
bool added = guc_exec_queue_try_add_msg(q, msg, SUSPEND);
/* slot must be free at 0->1 */
xe_gt_assert(guc_to_gt(exec_queue_to_guc(q)), added);
ge->suspend_pending = true;
}
xe_sched_msg_unlock(sched);
return first;
}
/*
* Core resume: drop a suspend reference on @q and, on the last reference,
* re-enable its GuC context. Returns true if this dropped the last suspend
* reference (the 1->0 transition).
*/
static bool __guc_exec_queue_resume(struct xe_exec_queue *q)
{
struct xe_guc_exec_queue *ge = q->guc;
struct xe_gpu_scheduler *sched = &ge->sched;
struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME;
struct xe_guc *guc = exec_queue_to_guc(q);
bool last;
xe_sched_msg_lock(sched);
xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending);
xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0);
last = (--ge->suspend_count == 0);
if (last) {
bool added = guc_exec_queue_try_add_msg(q, msg, RESUME);
/* slot must be free at 1->0 */
xe_gt_assert(guc_to_gt(guc), added);
}
xe_sched_msg_unlock(sched);
return last;
}
static int guc_exec_queue_suspend(struct xe_exec_queue *q)
{
struct xe_gpu_scheduler *sched = &q->guc->sched;
struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_SUSPEND;
if (exec_queue_killed_or_banned_or_wedged(q))
return -EINVAL;
xe_sched_msg_lock(sched);
if (guc_exec_queue_try_add_msg(q, msg, SUSPEND))
q->guc->suspend_pending = true;
xe_sched_msg_unlock(sched);
/*
* Non-multi-queue queues and multi-queue primaries suspend themselves
* directly: their own msg_lock makes the suspend_count 0->1 transition
* and the suspend_pending update atomic, so no group level serialization
* is needed.
*/
if (!xe_exec_queue_is_multi_queue_secondary(q)) {
__guc_exec_queue_suspend(q);
return 0;
}
/*
* A secondary's suspend is meaningless once the primary - which owns the
* group's GuC context - is gone, so fail it too. This keeps the
* secondary's effective state consistent with guc_exec_queue_reset_status(),
* which already reports the primary's killed/banned/wedged state for
* secondaries. A primary killed *after* this check is still handled at
* message-processing time, where the SUSPEND is a no-op for a killed
* context; this only covers an already-dead primary.
*/
if (exec_queue_killed_or_banned_or_wedged(xe_exec_queue_multi_queue_primary(q)))
return -EINVAL;
/*
* A secondary doesn't interface with GuC: suspend it like any other
* queue (its own suspend_count drives its internally handled scheduler
* state) and, only on its own 0->1 transition, forward the suspend to the
* primary so the GPU is actually preempted. Hold @suspend_lock so that
* observing the secondary's transition and forwarding it to the primary
* happen atomically; this keeps the primary's refcount paired with member
* transitions even if the same secondary is suspended and resumed
* concurrently across rebind cycles.
*/
scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
if (__guc_exec_queue_suspend(q))
__guc_exec_queue_suspend(xe_exec_queue_multi_queue_primary(q));
}
return 0;
}
static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
static void guc_exec_queue_suspend_timeout_ban(struct xe_exec_queue *q)
{
struct xe_guc *guc = exec_queue_to_guc(q);
xe_gt_warn(guc_to_gt(guc),
"Suspend fence, guc_id=%d, failed to respond, banning queue",
q->guc->id);
/*
* The GuC failed to respond to the suspend within the timeout. This is
* not recoverable for this context, so ban it and tear it down via
* cleanup rather than leave it suspended forever. __suspend_fence_signal
* clears suspend_pending and wakes any waiter.
*
* @q is the primary here; it owns the group's GuC context, so a failure
* to suspend it wedges the whole group. Ban and tear down the entire
* group in the multi-queue case.
*/
if (xe_exec_queue_is_multi_queue(q)) {
set_exec_queue_group_banned(q);
__suspend_fence_signal(q);
xe_guc_exec_queue_group_trigger_cleanup(q);
} else {
set_exec_queue_banned(q);
__suspend_fence_signal(q);
xe_guc_exec_queue_trigger_cleanup(q);
}
}
/*
* Wait for @q's own suspend to complete: suspend_pending cleared, or the queue
* killed / GuC stopped. With @blocking, wait uninterruptibly and do not handle
* VF recovery (for callers that must complete on behalf of a possibly
* cross-process queue); otherwise wait interruptibly.
*
* Returns 0 on completion or -ETIME on timeout. Interruptible waits may also
* return -EAGAIN (VF recovery in progress, retry) or -ERESTARTSYS (aborted by a
* signal; suspend_pending may still be set, so callers must not resume()
* without re-confirming the suspend).
*/
static int guc_exec_queue_wait_suspend_done(struct xe_exec_queue *q, bool blocking)
{
struct xe_guc *guc = exec_queue_to_guc(q);
struct xe_device *xe = guc_to_xe(guc);
@@ -2196,44 +2379,146 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
xe_guc_read_stopped(guc))
retry:
if (IS_SRIOV_VF(xe))
if (blocking) {
if (IS_SRIOV_VF(xe))
ret = wait_event_timeout(guc->ct.wq, WAIT_COND, HZ * 5);
else
ret = wait_event_timeout(q->guc->suspend_wait, WAIT_COND,
HZ * 5);
} else if (IS_SRIOV_VF(xe)) {
ret = wait_event_interruptible_timeout(guc->ct.wq, WAIT_COND ||
vf_recovery(guc),
HZ * 5);
else
vf_recovery(guc), HZ * 5);
} else {
ret = wait_event_interruptible_timeout(q->guc->suspend_wait,
WAIT_COND, HZ * 5);
}
if (vf_recovery(guc) && !xe_device_wedged((guc_to_xe(guc))))
if (!blocking && vf_recovery(guc) && !xe_device_wedged(xe))
return -EAGAIN;
if (!ret) {
xe_gt_warn(guc_to_gt(guc),
"Suspend fence, guc_id=%d, failed to respond",
q->guc->id);
/* XXX: Trigger GT reset? */
if (!ret)
return -ETIME;
} else if (IS_SRIOV_VF(xe) && !WAIT_COND) {
else if (!blocking && IS_SRIOV_VF(xe) && !WAIT_COND)
/* Corner case on RESFIX DONE where vf_recovery() changes */
goto retry;
}
#undef WAIT_COND
return ret < 0 ? ret : 0;
}
static int guc_exec_queue_suspend_wait_common(struct xe_exec_queue *q, bool blocking)
{
int ret;
/*
* A secondary's suspend rides the sched-message worker (short-circuited,
* no GuC round-trip) and so is not synchronous with
* guc_exec_queue_suspend(): its own suspend_pending may still be set
* here. Waiting on the primary alone is not sufficient - if the primary
* was already suspended, the forward is a refcount-only transition that
* queues no new primary SUSPEND and leaves the primary's suspend_pending
* clear, so the primary wait would return immediately while the
* secondary's suspend is still in flight, and a later resume() would trip
* the secondary's !suspend_pending assert. So first wait for the
* secondary's own suspend to complete, then wait on the primary.
*
* A timeout on either bans the queue (being multi-queue, that tears down
* the whole group). A secondary suspend has no real GuC round-trip, so
* its timeout is a software scheduler stall rather than a GuC fault, but
* banning is still the safe recovery: otherwise the queue is left with
* suspend_pending set and a subsequent resume() trips the !suspend_pending
* assert.
*/
if (xe_exec_queue_is_multi_queue_secondary(q)) {
ret = guc_exec_queue_wait_suspend_done(q, blocking);
if (ret == -ETIME)
guc_exec_queue_suspend_timeout_ban(q);
if (ret)
return ret;
}
q = xe_exec_queue_multi_queue_primary(q);
ret = guc_exec_queue_wait_suspend_done(q, blocking);
if (ret == -ETIME)
guc_exec_queue_suspend_timeout_ban(q);
return ret;
}
static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
{
return guc_exec_queue_suspend_wait_common(q, false);
}
/*
* Uninterruptible variant of guc_exec_queue_suspend_wait() for callers that
* must complete the wait on behalf of a queue possibly owned by a different
* process (e.g. cleanup/undo paths). An interruptible wait could return
* -ERESTARTSYS if the calling task is signalled, leaving that queue suspended
* forever (cross-process DoS). VF recovery is deliberately not handled (no
* -EAGAIN) since a blocking caller cannot retry.
*/
static int guc_exec_queue_suspend_wait_blocking(struct xe_exec_queue *q)
{
return guc_exec_queue_suspend_wait_common(q, true);
}
static void guc_exec_queue_resume(struct xe_exec_queue *q)
{
struct xe_gpu_scheduler *sched = &q->guc->sched;
struct xe_sched_msg *msg = q->guc->static_msgs + STATIC_MSG_RESUME;
struct xe_guc *guc = exec_queue_to_guc(q);
/*
* Non-multi-queue queues and multi-queue primaries resume themselves
* directly; their own msg_lock is sufficient.
*/
if (!xe_exec_queue_is_multi_queue_secondary(q)) {
__guc_exec_queue_resume(q);
return;
}
xe_gt_assert(guc_to_gt(guc), !q->guc->suspend_pending);
/*
* Mirror of guc_exec_queue_suspend(): resume the secondary like any
* other queue and, only on its own 1->0 transition, forward the resume
* to the primary so the primary's GuC context is re-enabled once the
* last member that suspended it resumes. @suspend_lock keeps the
* secondary transition and the primary forward atomic.
*/
scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
if (__guc_exec_queue_resume(q))
__guc_exec_queue_resume(xe_exec_queue_multi_queue_primary(q));
}
}
xe_sched_msg_lock(sched);
guc_exec_queue_try_add_msg(q, msg, RESUME);
xe_sched_msg_unlock(sched);
/*
* Drop a leaving secondary's forwarded suspend reference on the primary and
* resume the primary if this was the last member that had it suspended.
* See guc_exec_queue_fini().
*/
static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q)
{
scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
struct xe_exec_queue *primary = xe_exec_queue_multi_queue_primary(q);
/*
* A suspended secondary holds exactly one suspend reference on the
* primary (forwarded on its 0->1 transition). If it leaves while
* still suspended, release that reference so the primary is not
* kept disabled forever.
*/
if (!READ_ONCE(q->guc->suspend_count))
break;
if (exec_queue_killed_or_banned_or_wedged(primary))
break;
/*
* No suspend_wait() here (and we can't - suspend_lock is a
* spinlock). guc_exec_queue_fini() has already drained the
* primary's forwarded suspend with the blocking wait, so its
* suspend has completed (suspend_pending cleared) by the time we
* resume it here. __guc_exec_queue_resume() asserts this.
*/
__guc_exec_queue_resume(primary);
}
}
static bool guc_exec_queue_reset_status(struct xe_exec_queue *q)
@@ -2262,6 +2547,7 @@ static const struct xe_exec_queue_ops guc_exec_queue_ops = {
.set_multi_queue_priority = guc_exec_queue_set_multi_queue_priority,
.suspend = guc_exec_queue_suspend,
.suspend_wait = guc_exec_queue_suspend_wait,
.suspend_wait_blocking = guc_exec_queue_suspend_wait_blocking,
.resume = guc_exec_queue_resume,
.reset_status = guc_exec_queue_reset_status,
};
@@ -3022,6 +3308,38 @@ int xe_guc_exec_queue_memory_cat_error_handler(struct xe_guc *guc, u32 *msg,
return 0;
}
int xe_guc_uncorrectable_error_handler(struct xe_guc *guc, u32 *msg, u32 len)
{
struct xe_gt *gt = guc_to_gt(guc);
struct xe_exec_queue *q;
u32 guc_id;
if (unlikely(!len || len > 1))
return -EPROTO;
guc_id = msg[0];
if (guc_id == GUC_ID_UNKNOWN) {
xe_gt_err(gt, "GuC: Uncorrectable local error with unknown GuC id\n");
return 0;
}
q = g2h_exec_queue_lookup(guc, guc_id);
if (unlikely(!q))
return -EPROTO;
xe_gt_err(gt,
"GuC: Uncorrectable local error! guc_id=%d class=%s, logical_mask=0x%x",
guc_id, xe_hw_engine_class_to_str(q->class), q->logical_mask);
trace_xe_guc_uncorrectable_error(q);
/* Treat the same as engine reset */
xe_guc_exec_queue_reset_trigger_cleanup(q);
return 0;
}
int xe_guc_exec_queue_reset_failure_handler(struct xe_guc *guc, u32 *msg, u32 len)
{
struct xe_gt *gt = guc_to_gt(guc);

View File

@@ -34,6 +34,7 @@ int xe_guc_deregister_done_handler(struct xe_guc *guc, u32 *msg, u32 len);
int xe_guc_exec_queue_reset_handler(struct xe_guc *guc, u32 *msg, u32 len);
int xe_guc_exec_queue_memory_cat_error_handler(struct xe_guc *guc, u32 *msg,
u32 len);
int xe_guc_uncorrectable_error_handler(struct xe_guc *guc, u32 *msg, u32 len);
int xe_guc_exec_queue_reset_failure_handler(struct xe_guc *guc, u32 *msg, u32 len);
int xe_guc_error_capture_handler(struct xe_guc *guc, u32 *msg, u32 len);
int xe_guc_exec_queue_cgp_sync_done_handler(struct xe_guc *guc, u32 *msg, u32 len);

View File

@@ -100,8 +100,6 @@ struct xe_guc {
* even initialized - before that not even the lock is valid
*/
bool initialized;
/** @submission_state.fini_wq: submit fini wait queue */
wait_queue_head_t fini_wq;
} submission_state;
/** @hwconfig: Hardware config state */

View File

@@ -647,10 +647,6 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe,
xe_hw_engine_enable_ring(hwe);
}
/* We reserve the highest BCS instance for USM */
if (xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY)
gt->usm.reserved_bcs_instance = hwe->instance;
/* Ensure IDLEDLY is lower than MAXCNT */
adjust_idledly(hwe);
@@ -662,20 +658,80 @@ static int hw_engine_init(struct xe_gt *gt, struct xe_hw_engine *hwe,
return err;
}
static void hw_engine_setup_logical_mapping(struct xe_gt *gt)
static int hw_engine_setup_logical_and_paging_mapping(struct xe_gt *gt)
{
struct xe_device *xe = gt_to_xe(gt);
unsigned int num_copy_engines = 0, num_paging_engines = 0;
unsigned int reserved_logical_bcs_start;
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
int class;
for_each_hw_engine(hwe, gt, id)
if (hwe->class == XE_ENGINE_CLASS_COPY)
num_copy_engines++;
if (num_copy_engines && xe->info.has_usm)
num_paging_engines = 1;
if (IS_SRIOV_VF(xe)) {
u32 vf_num_paging_engines;
/*
* PF could in theory reserve multiple paging engines, which
* internally the submission/scheduling backend can load balance
* from. Not something we currently expect, but we are at the
* mercy of the PF, so we just need try our best to mirror the
* paging configuration.
*/
vf_num_paging_engines = xe_gt_sriov_vf_paging_engines(gt);
if (vf_num_paging_engines) {
/* This should only be non-zero on NVL-S+ */
if (xe_gt_WARN_ON(gt, xe->info.platform < XE_NOVALAKE_S))
return -EINVAL;
num_paging_engines = vf_num_paging_engines;
}
}
if (xe_gt_WARN_ON(gt, num_paging_engines > num_copy_engines))
return -EINVAL;
/*
* On PF, we just reserve the highest BCS instance for USM.
*
* Note: This is now a requirement going forward. The PF must ALWAYS
* reserve BCS instances in top-down order, that way the VF has a chance
* of discovering the physical BCS instance mappings for paging engines,
* in conjunction with vf_num_paging_engines. In some places we might
* only have the physical instance, and from hw pov there is no such
* thing as a paging engine. For example, the page fault descriptor,
* which comes directly from the hw, will use the physical engine
* instance.
*/
reserved_logical_bcs_start = num_copy_engines - num_paging_engines;
/* FIXME: Doing a simple logical mapping that works for most hardware */
for (class = 0; class < XE_ENGINE_CLASS_MAX; ++class) {
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
int logical_instance = 0;
for_each_hw_engine(hwe, gt, id)
if (hwe->class == class)
for_each_hw_engine(hwe, gt, id) {
if (hwe->class == class) {
hwe->logical_instance = logical_instance++;
if (class == XE_ENGINE_CLASS_COPY &&
hwe->logical_instance >=
reserved_logical_bcs_start) {
if (!gt->usm.paging_hwe0)
gt->usm.paging_hwe0 = hwe;
gt->usm.paging_logical_mask |=
BIT(hwe->logical_instance);
}
}
}
}
return 0;
}
static void read_media_fuses(struct xe_gt *gt)
@@ -894,7 +950,10 @@ int xe_hw_engines_init(struct xe_gt *gt)
return err;
}
hw_engine_setup_logical_mapping(gt);
err = hw_engine_setup_logical_and_paging_mapping(gt);
if (err)
return err;
err = xe_hw_engine_setup_groups(gt);
if (err)
return err;
@@ -1043,8 +1102,7 @@ bool xe_hw_engine_is_reserved(struct xe_hw_engine *hwe)
hwe->logical_instance >= gt->ccs_mode)
return true;
return xe->info.has_usm && hwe->class == XE_ENGINE_CLASS_COPY &&
hwe->instance == gt->usm.reserved_bcs_instance;
return xe_gt_is_usm_hwe(gt, hwe);
}
const char *xe_hw_engine_class_to_str(enum xe_engine_class class)

View File

@@ -34,6 +34,15 @@ hw_engine_group_resume_lr_jobs_func(struct work_struct *w)
if (!xe_vm_in_fault_mode(q->vm))
continue;
/*
* Only resume queues that were actually suspended. A queue whose
* suspend() failed (e.g. killed/banned/wedged) was never
* suspended, so it must not be resumed.
*/
if (!READ_ONCE(q->lr.suspended))
continue;
WRITE_ONCE(q->lr.suspended, false);
q->ops->resume(q);
}
@@ -140,7 +149,18 @@ int xe_hw_engine_group_add_exec_queue(struct xe_hw_engine_group *group, struct x
return err;
if (xe_vm_in_fault_mode(q->vm) && group->cur_mode == EXEC_MODE_DMA_FENCE) {
q->ops->suspend(q);
/*
* suspend() can fail (e.g. killed/banned/wedged), leaving the
* queue un-suspended. Propagate the failure so the queue is not
* added; on failure nothing was suspended, so there is nothing to
* undo. Only record the queue as suspended (and later resume it)
* once suspend() has succeeded.
*/
err = q->ops->suspend(q);
if (err)
goto err_suspend;
WRITE_ONCE(q->lr.suspended, true);
err = q->ops->suspend_wait(q);
if (err)
goto err_suspend;
@@ -216,8 +236,22 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
return -EAGAIN;
xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_COUNT, 1);
/*
* suspend() only fails when the queue is killed/banned/wedged.
* Such a queue is being torn down (its removal from HW is handled
* by the kill/ban teardown), so it is not a live fault-mode
* context the mode switch must preempt. Skip it rather than
* failing the switch, otherwise one dying sibling would block a
* dma-fence submission on the healthy queues in the group. Only
* queues recorded as suspended below are later waited on and
* resumed.
*/
err = q->ops->suspend(q);
if (err)
continue;
WRITE_ONCE(q->lr.suspended, true);
need_resume = true;
q->ops->suspend(q);
gt = q->gt;
}
@@ -225,9 +259,13 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
if (!xe_vm_in_fault_mode(q->vm))
continue;
/* Only wait on queues that were actually suspended above. */
if (!READ_ONCE(q->lr.suspended))
continue;
err = q->ops->suspend_wait(q);
if (err)
return err;
goto err_resume;
}
if (gt) {
@@ -240,6 +278,47 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
xe_hw_engine_group_resume_faulting_lr_jobs(group);
return 0;
err_resume:
/*
* A suspend_wait() failed partway through the mode switch. Resume the
* sibling queues that were already suspended in this call so they are
* not left suspended forever.
*
* resume() requires the suspend to have completed (suspend_pending
* cleared) or it trips the !suspend_pending assert. So skip the resume
* when either:
* - suspend_wait_blocking() fails: on a GuC timeout it bans the queue
* and triggers cleanup, so the queue is being torn down; or
* - reset_status() is true: the queue was reset/killed/banned/wedged.
* suspend_wait() can return success in this case via its killed/
* stopped wait condition while suspend_pending is still set, and the
* queue is being torn down anyway, so its state is resolved by
* teardown rather than by a resume here.
* In either case leave the queue marked suspended.
*
* Use the *blocking* (uninterruptible) wait here: the queues resumed on
* this path may belong to a different process than the one that
* triggered the mode switch. An interruptible suspend_wait() would
* return -ERESTARTSYS if the triggering task is signalled, skip the
* resume, and leave the other process's queue suspended forever
* (cross-process DoS).
*/
list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {
if (!xe_vm_in_fault_mode(q->vm))
continue;
if (!READ_ONCE(q->lr.suspended))
continue;
if (q->ops->suspend_wait_blocking(q) || q->ops->reset_status(q))
continue;
WRITE_ONCE(q->lr.suspended, false);
q->ops->resume(q);
}
return err;
}
/**

View File

@@ -114,7 +114,12 @@ struct xe_hw_engine {
enum xe_engine_class class;
/** @instance: physical instance of this hw engine */
u16 instance;
/** @logical_instance: logical instance of this hw engine */
/**
* @logical_instance: logical instance of this hw engine.
*
* Note: For GuC usage, always use xe_hwe_guc_logical_instance().
* For GuC usage, we should no longer use the raw logical instance.
*/
u16 logical_instance;
/** @irq_offset: IRQ offset of this hw engine */
u16 irq_offset;

View File

@@ -4,12 +4,12 @@
*/
#include <linux/bitmap.h>
#include <linux/fault-inject.h>
#include "regs/xe_gsc_regs.h"
#include "regs/xe_hw_error_regs.h"
#include "regs/xe_irq_regs.h"
#include "xe_debugfs.h"
#include "xe_device.h"
#include "xe_drm_ras.h"
#include "xe_hw_error.h"
@@ -25,8 +25,6 @@
(PVC_COR_ERR_MASK & REG_BIT(err_bit)) : \
(PVC_FAT_ERR_MASK & REG_BIT(err_bit)))
extern struct fault_attr inject_csc_hw_error;
static const char * const error_severity[] = DRM_XE_RAS_ERROR_SEVERITY_NAMES;
static const char * const hec_uncorrected_fw_errors[] = {
@@ -167,11 +165,6 @@ static_assert(ARRAY_SIZE(pvc_master_local_nonfatal_err_reg) == XE_RAS_REG_SIZE);
pvc_master_local_fatal_err_reg : \
pvc_master_local_nonfatal_err_reg)
static bool fault_inject_csc_hw_error(void)
{
return IS_ENABLED(CONFIG_DEBUG_FS) && should_fail(&inject_csc_hw_error, 1);
}
static void csc_hw_error_work(struct work_struct *work)
{
struct xe_tile *tile = container_of(work, typeof(*tile), csc_hw_error_work);
@@ -517,7 +510,7 @@ void xe_hw_error_irq_handler(struct xe_tile *tile, const u32 master_ctl)
{
enum hardware_error hw_err;
if (fault_inject_csc_hw_error())
if (xe_fault_csc_hw_error())
schedule_work(&tile->csc_hw_error_work);
for (hw_err = 0; hw_err < HARDWARE_ERROR_MAX; hw_err++) {

View File

@@ -95,18 +95,21 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c)
struct platform_device *pdev;
struct fwnode_handle *fwnode;
int ret;
u32 id;
fwnode = fwnode_create_software_node(xe_i2c_adapter_properties, NULL);
if (IS_ERR(fwnode))
return PTR_ERR(fwnode);
id = (pci_domain_nr(pci->bus) << 16) | pci_dev_id(pci);
/*
* Not using platform_device_register_full() here because we don't have
* a handle to the platform_device before it returns. xe_i2c_notifier()
* uses that handle, but it may be called before
* platform_device_register_full() is done.
*/
pdev = platform_device_alloc(adapter_name, pci_dev_id(pci));
pdev = platform_device_alloc(adapter_name, id);
if (!pdev) {
ret = -ENOMEM;
goto err_fwnode_remove;

View File

@@ -117,6 +117,27 @@ static void xe_migrate_fini(void *arg)
xe_exec_queue_put(m->q);
}
static inline u16 xe_migrate_pat_index(struct xe_device *xe,
enum ttm_caching caching,
bool is_comp_pte)
{
enum xe_cache_level cache_level;
/*
* Select the appropriate PAT index for buffer object PTEs programmed
* by emit_pte(). We choose not to mess with xe_migrate_prepare_vm()
* yet, for simplicity.
*/
if (is_comp_pte && GRAPHICS_VERx100(xe) >= 2000)
cache_level = XE_CACHE_NONE_COMPRESSION;
else if (caching == ttm_cached)
cache_level = XE_CACHE_WB;
else
cache_level = XE_CACHE_NONE;
return xe_cache_pat_idx(xe, cache_level);
}
static u64 xe_migrate_vm_addr(u64 slot, u32 level)
{
XE_WARN_ON(slot >= NUM_PT_SLOTS);
@@ -383,27 +404,6 @@ static void xe_migrate_suballoc_manager_init(struct xe_migrate *m, u32 map_ofs)
NUM_VMUSA_UNIT_PER_PAGE, 0);
}
/*
* Including the reserved copy engine is required to avoid deadlocks due to
* migrate jobs servicing the faults gets stuck behind the job that faulted.
*/
static u32 xe_migrate_usm_logical_mask(struct xe_gt *gt)
{
u32 logical_mask = 0;
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
for_each_hw_engine(hwe, gt, id) {
if (hwe->class != XE_ENGINE_CLASS_COPY)
continue;
if (xe_gt_is_usm_hwe(gt, hwe))
logical_mask |= BIT(hwe->logical_instance);
}
return logical_mask;
}
static bool xe_migrate_needs_ccs_emit(struct xe_device *xe)
{
return xe_device_has_flat_ccs(xe) && !(GRAPHICS_VER(xe) >= 20 && IS_DGFX(xe));
@@ -479,13 +479,10 @@ int xe_migrate_init(struct xe_migrate *m)
goto err_out;
if (xe->info.has_usm) {
struct xe_hw_engine *hwe = xe_gt_hw_engine(primary_gt,
XE_ENGINE_CLASS_COPY,
primary_gt->usm.reserved_bcs_instance,
false);
u32 logical_mask = xe_migrate_usm_logical_mask(primary_gt);
struct xe_hw_engine *hwe0 = primary_gt->usm.paging_hwe0;
u32 logical_mask = primary_gt->usm.paging_logical_mask;
if (!hwe || !logical_mask) {
if (!hwe0 || !logical_mask) {
err = -EINVAL;
goto err_out;
}
@@ -494,7 +491,7 @@ int xe_migrate_init(struct xe_migrate *m)
* XXX: Currently only reserving 1 (likely slow) BCS instance on
* PVC, may want to revisit if performance is needed.
*/
m->q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe,
m->q = xe_exec_queue_create(xe, vm, logical_mask, 1, hwe0,
EXEC_QUEUE_FLAG_KERNEL |
EXEC_QUEUE_FLAG_PERMANENT |
EXEC_QUEUE_FLAG_HIGH_PRIORITY |
@@ -631,17 +628,17 @@ static void emit_pte(struct xe_migrate *m,
{
struct xe_device *xe = tile_to_xe(m->tile);
struct xe_vm *vm = m->q->vm;
struct xe_bo *bo = ttm_to_xe_bo(res->bo);
enum ttm_caching caching = ttm_cached;
u16 pat_index;
u32 ptes;
u64 ofs = (u64)at_pt * XE_PAGE_SIZE;
u64 cur_ofs;
/* Indirect access needs compression enabled uncached PAT index */
if (GRAPHICS_VERx100(xe) >= 2000)
pat_index = is_comp_pte ? xe_cache_pat_idx(xe, XE_CACHE_NONE_COMPRESSION) :
xe_cache_pat_idx(xe, XE_CACHE_WB);
else
pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
if (!is_vram && bo->ttm.ttm)
caching = bo->ttm.ttm->caching;
pat_index = xe_migrate_pat_index(xe, caching, is_comp_pte);
ptes = DIV_ROUND_UP(size, XE_PAGE_SIZE);
@@ -1166,6 +1163,8 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags)
* @tile: Tile whose migration context to be used.
* @q : Execution to be used along with migration context.
* @src_bo: The buffer object @src is currently bound to.
* @new_mem: The (not yet committed) destination resource @src_bo is being
* moved into; src_bo->ttm.resource is still the old resource.
* @read_write : Creates BB commands for CCS read/write.
*
* Creates batch buffer instructions to copy CCS metadata from CCS pool to
@@ -1177,12 +1176,13 @@ static int emit_flush_invalidate(u32 *dw, int i, u32 flags)
*/
int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q,
struct xe_bo *src_bo,
struct ttm_resource *new_mem,
enum xe_sriov_vf_ccs_rw_ctxs read_write)
{
bool src_is_pltt = read_write == XE_SRIOV_VF_CCS_READ_CTX;
bool dst_is_pltt = read_write == XE_SRIOV_VF_CCS_WRITE_CTX;
struct ttm_resource *src = src_bo->ttm.resource;
struct ttm_resource *src = new_mem;
struct xe_migrate *m = tile->migrate;
struct xe_gt *gt = tile->primary_gt;
u32 batch_size, batch_size_allocated;
@@ -1310,6 +1310,7 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q,
* content.
* @src_bo: The buffer object @src is currently bound to.
* @read_write : Creates BB commands for CCS read/write.
* @bound: Device is bound
*
* Directly clearing the BB lacks atomicity and can lead to undefined
* behavior if the vCPU is halted mid-operation during the clearing
@@ -1322,7 +1323,8 @@ int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q,
* Returns: None.
*/
void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo,
enum xe_sriov_vf_ccs_rw_ctxs read_write)
enum xe_sriov_vf_ccs_rw_ctxs read_write,
bool bound)
{
struct xe_mem_pool_node *bb = src_bo->bb_ccs[read_write];
struct xe_device *xe = xe_bo_device(src_bo);
@@ -1336,13 +1338,15 @@ void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo,
bb_pool = ctx->mem.ccs_bb_pool;
scoped_guard(mutex, xe_mem_pool_bo_swap_guard(bb_pool)) {
xe_mem_pool_swap_shadow_locked(bb_pool);
if (bound) {
xe_mem_pool_swap_shadow_locked(bb_pool);
cs = xe_mem_pool_node_cpu_addr(bb);
memset(cs, MI_NOOP, bb->sa_node.size);
xe_sriov_vf_ccs_rw_update_bb_addr(ctx);
cs = xe_mem_pool_node_cpu_addr(bb);
memset(cs, MI_NOOP, bb->sa_node.size);
xe_sriov_vf_ccs_rw_update_bb_addr(ctx);
xe_mem_pool_sync_shadow_locked(bb);
xe_mem_pool_sync_shadow_locked(bb);
}
xe_mem_pool_free_node(bb);
src_bo->bb_ccs[read_write] = NULL;
}

View File

@@ -138,10 +138,12 @@ struct dma_fence *xe_migrate_resolve(struct xe_migrate *m,
int xe_migrate_ccs_rw_copy(struct xe_tile *tile, struct xe_exec_queue *q,
struct xe_bo *src_bo,
struct ttm_resource *new_mem,
enum xe_sriov_vf_ccs_rw_ctxs read_write);
void xe_migrate_ccs_rw_copy_clear(struct xe_bo *src_bo,
enum xe_sriov_vf_ccs_rw_ctxs read_write);
enum xe_sriov_vf_ccs_rw_ctxs read_write,
bool bound);
struct xe_lrc *xe_migrate_lrc(struct xe_migrate *migrate);
struct xe_exec_queue *xe_migrate_exec_queue(struct xe_migrate *migrate);

View File

@@ -7,6 +7,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <drm/drm_module.h>
@@ -88,6 +89,50 @@ static int xe_check_nomodeset(void)
return 0;
}
static struct workqueue_struct *xe_destroy_wq;
static int __init xe_destroy_wq_module_init(void)
{
xe_destroy_wq = alloc_workqueue("xe-guc-destroy-wq", WQ_UNBOUND, 0);
if (!xe_destroy_wq)
return -ENOMEM;
return 0;
}
static void xe_destroy_wq_module_exit(void)
{
if (xe_destroy_wq)
destroy_workqueue(xe_destroy_wq);
xe_destroy_wq = NULL;
}
/**
* xe_destroy_wq_queue() - Queue work on the destroy workqueue
* @work: work item to queue
*
* The destroy workqueue has module lifetime and is used for GuC exec queue
* teardown that can outlive a single xe_device. SVM pagemap destroy uses the
* per-device xe->destroy_wq instead.
*
* Return: %true if @work was queued, %false if it was already pending.
*/
bool xe_destroy_wq_queue(struct work_struct *work)
{
return queue_work(xe_destroy_wq, work);
}
/**
* xe_destroy_wq_flush() - Flush the destroy workqueue
*
* Drains all pending destroy work. Called from PCI remove to ensure
* teardown ordering before the device is destroyed.
*/
void xe_destroy_wq_flush(void)
{
if (xe_destroy_wq)
flush_workqueue(xe_destroy_wq);
}
struct init_funcs {
int (*init)(void);
void (*exit)(void);
@@ -109,6 +154,10 @@ static const struct init_funcs init_funcs[] = {
.init = xe_sched_job_module_init,
.exit = xe_sched_job_module_exit,
},
{
.init = xe_destroy_wq_module_init,
.exit = xe_destroy_wq_module_exit,
},
{
.init = xe_register_pci_driver,
.exit = xe_unregister_pci_driver,

View File

@@ -8,6 +8,8 @@
#include <linux/types.h>
struct work_struct;
/* Module modprobe variables */
struct xe_modparam {
bool probe_display;
@@ -26,5 +28,8 @@ struct xe_modparam {
extern struct xe_modparam xe_modparam;
bool xe_destroy_wq_queue(struct work_struct *work);
void xe_destroy_wq_flush(void);
#endif

View File

@@ -60,35 +60,40 @@ static bool xe_nvm_writable_override(struct xe_device *xe)
struct xe_mmio *mmio = xe_root_tile_mmio(xe);
bool writable_override;
struct xe_reg reg;
u32 test_bit;
u32 test_bit, test_val;
switch (xe->info.platform) {
case XE_CRESCENTISLAND:
reg = PCODE_SCRATCH(0);
test_bit = FDO_MODE;
test_val = FDO_MODE;
break;
case XE_BATTLEMAGE:
reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE);
test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE;
test_val = 0;
break;
case XE_PVC:
reg = HECI_FWSTS2(PVC_GSC_HECI2_BASE);
test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE;
test_val = 0;
break;
case XE_DG2:
reg = HECI_FWSTS2(DG2_GSC_HECI2_BASE);
test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE;
test_val = 0;
break;
case XE_DG1:
reg = HECI_FWSTS2(DG1_GSC_HECI2_BASE);
test_bit = HECI_FW_STATUS_2_NVM_ACCESS_MODE;
test_val = 0;
break;
default:
drm_err(&xe->drm, "Unknown platform\n");
return true;
}
writable_override = !(xe_mmio_read32(mmio, reg) & test_bit);
writable_override = (xe_mmio_read32(mmio, reg) & test_bit) == test_val;
if (writable_override)
drm_info(&xe->drm, "NVM access overridden by jumper\n");
return writable_override;

View File

@@ -25,6 +25,7 @@
0x4800, 0x4804, \
0x4848, 0x484c)
#define _PAT_PTA 0x4820
#define _PAT_TR_PTA 0x48cc
#define XE2_NO_PROMOTE REG_BIT(10)
#define XE2_COMP_EN REG_BIT(9)
@@ -256,6 +257,7 @@ static const struct xe_pat_table_entry xe3p_xpc_pat_table[] = {
static const struct xe_pat_table_entry xe3p_primary_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 3);
static const struct xe_pat_table_entry xe3p_media_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 2);
static const struct xe_pat_table_entry xe3p_pat_tr_pta = XE2_PAT(0, 0, 0, 0, 0, 0);
static const struct xe_pat_table_entry xe3p_lpg_pat_table[] = {
[ 0] = XE2_PAT( 0, 0, 0, 0, 3, 0 ),
@@ -325,11 +327,26 @@ static const struct xe_pat_table_entry *gt_pta_entry(struct xe_gt *gt)
return NULL;
}
static const struct xe_pat_table_entry *gt_tr_pta_entry(struct xe_gt *gt)
{
struct xe_device *xe = gt_to_xe(gt);
if (xe_gt_is_main_type(gt))
return xe->pat.pat_primary_tr_pta;
if (xe_gt_is_media_type(gt))
return xe->pat.pat_media_tr_pta;
xe_assert(xe, false);
return NULL;
}
static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[],
int n_entries)
{
struct xe_device *xe = gt_to_xe(gt);
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
for (int i = 0; i < n_entries; i++) {
struct xe_reg reg = XE_REG(_PAT_INDEX(i));
@@ -342,6 +359,9 @@ static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[
if (pta_entry)
xe_mmio_write32(&gt->mmio, XE_REG(_PAT_PTA), pta_entry->value);
if (tr_pta_entry)
xe_mmio_write32(&gt->mmio, XE_REG(_PAT_TR_PTA), tr_pta_entry->value);
}
static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry table[],
@@ -349,6 +369,7 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta
{
struct xe_device *xe = gt_to_xe(gt);
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
for (int i = 0; i < n_entries; i++) {
struct xe_reg_mcr reg_mcr = XE_REG_MCR(_PAT_INDEX(i));
@@ -361,6 +382,9 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta
if (pta_entry)
xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_PTA), pta_entry->value);
if (tr_pta_entry)
xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_TR_PTA), tr_pta_entry->value);
}
static int xelp_dump(struct xe_gt *gt, struct drm_printer *p)
@@ -531,6 +555,16 @@ static int xe2_dump(struct xe_gt *gt, struct drm_printer *p)
drm_printf(p, "Page Table Access:\n");
xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false);
if (gt_tr_pta_entry(gt)) {
if (xe_gt_is_media_type(gt))
pat = xe_mmio_read32(&gt->mmio, XE_REG(_PAT_TR_PTA));
else
pat = xe_gt_mcr_unicast_read_any(gt, XE_REG_MCR(_PAT_TR_PTA));
drm_printf(p, "TRTT Page Table Access:\n");
xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false);
}
if (xe_gt_is_media_type(gt))
pat = xe_mmio_read32(&gt->mmio, XE_REG(_PAT_ATS));
else
@@ -577,6 +611,8 @@ void xe_pat_init_early(struct xe_device *xe)
if (!IS_DGFX(xe)) {
xe->pat.pat_primary_pta = &xe3p_primary_pat_pta;
xe->pat.pat_media_pta = &xe3p_media_pat_pta;
xe->pat.pat_primary_tr_pta = &xe3p_pat_tr_pta;
xe->pat.pat_media_tr_pta = &xe3p_pat_tr_pta;
}
xe->pat.n_entries = ARRAY_SIZE(xe3p_lpg_pat_table);
xe->pat.idx[XE_CACHE_NONE] = 3;
@@ -701,6 +737,7 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p)
{
struct xe_device *xe = gt_to_xe(gt);
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
char label[PAT_LABEL_LEN];
if (!xe->pat.table || !xe->pat.n_entries)
@@ -731,6 +768,13 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p)
xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false);
}
if (tr_pta_entry) {
u32 pat = tr_pta_entry->value;
drm_printf(p, "TRTT Page Table Access:\n");
xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false);
}
if (xe->pat.pat_ats) {
u32 pat = xe->pat.pat_ats->value;

View File

@@ -22,6 +22,7 @@
#include "xe_device.h"
#include "xe_drv.h"
#include "xe_gt.h"
#include "xe_gt_printk.h"
#include "xe_gt_sriov_vf.h"
#include "xe_guc.h"
#include "xe_mmio.h"
@@ -36,6 +37,7 @@
#include "xe_step.h"
#include "xe_survivability_mode.h"
#include "xe_tile.h"
#include "xe_tile_printk.h"
enum toggle_d3cold {
D3COLD_DISABLE,
@@ -120,6 +122,7 @@ static const struct xe_graphics_desc graphics_xe2 = {
static const struct xe_graphics_desc graphics_xe3p_lpg = {
XE2_GFX_FEATURES,
.has_indirect_ring_state = 1,
.has_uncorrectable_error_reporting = 1,
.multi_queue_engine_class_mask = BIT(XE_ENGINE_CLASS_COPY) | BIT(XE_ENGINE_CLASS_COMPUTE),
.num_geometry_xecore_fuse_regs = 3,
.num_compute_xecore_fuse_regs = 3,
@@ -129,6 +132,7 @@ static const struct xe_graphics_desc graphics_xe3p_xpc = {
XE2_GFX_FEATURES,
.has_access_counter = 0,
.has_indirect_ring_state = 1,
.has_uncorrectable_error_reporting = 1,
.hw_engine_mask =
GENMASK(XE_HW_ENGINE_BCS8, XE_HW_ENGINE_BCS1) |
GENMASK(XE_HW_ENGINE_CCS3, XE_HW_ENGINE_CCS0),
@@ -151,6 +155,14 @@ static const struct xe_media_desc media_xelpmp = {
BIT(XE_HW_ENGINE_GSCCS0)
};
static const struct xe_media_desc media_xe3p_hpm = {
.has_uncorrectable_error_reporting = 1,
.hw_engine_mask =
GENMASK(XE_HW_ENGINE_VCS7, XE_HW_ENGINE_VCS0) |
GENMASK(XE_HW_ENGINE_VECS3, XE_HW_ENGINE_VECS0) |
BIT(XE_HW_ENGINE_GSCCS0)
};
/* Pre-GMDID Graphics IPs */
static const struct xe_ip graphics_ip_xelp = { 1200, "Xe_LP", &graphics_xelp };
static const struct xe_ip graphics_ip_xelpp = { 1210, "Xe_LP+", &graphics_xelp };
@@ -186,7 +198,7 @@ static const struct xe_ip media_ips[] = {
{ 3000, "Xe3_LPM", &media_xelpmp },
{ 3002, "Xe3_LPM", &media_xelpmp },
{ 3500, "Xe3p_LPM", &media_xelpmp },
{ 3503, "Xe3p_HPM", &media_xelpmp },
{ 3503, "Xe3p_HPM", &media_xe3p_hpm },
};
#define MULTI_LRC_MASK \
@@ -449,7 +461,6 @@ static const struct xe_device_desc nvls_desc = {
.has_sriov = true,
.max_gt_per_tile = 2,
MULTI_LRC_MASK,
.require_force_probe = true,
.va_bits = 48,
.vm_max_level = 4,
};
@@ -809,7 +820,8 @@ static int xe_info_init_early(struct xe_device *xe,
xe->info.probe_display = IS_ENABLED(CONFIG_DRM_XE_DISPLAY) &&
xe_modparam.probe_display &&
desc->has_display;
desc->has_display &&
!xe_device_is_admin_only(xe);
xe_assert(xe, desc->max_gt_per_tile > 0);
xe_assert(xe, desc->max_gt_per_tile <= XE_MAX_GT_PER_TILE);
@@ -864,7 +876,7 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile,
struct xe_gt *gt;
if (!xe_configfs_primary_gt_allowed(to_pci_dev(xe->drm.dev))) {
xe_info(xe, "Primary GT disabled via configfs\n");
xe_tile_info(tile, "Primary GT disabled via configfs\n");
return NULL;
}
@@ -875,7 +887,13 @@ static struct xe_gt *alloc_primary_gt(struct xe_tile *tile,
gt->info.type = XE_GT_TYPE_MAIN;
gt->info.id = tile->id * xe->info.max_gt_per_tile;
gt->info.has_indirect_ring_state = graphics_desc->has_indirect_ring_state;
gt->info.has_uncorrectable_error_reporting =
graphics_desc->has_uncorrectable_error_reporting;
gt->info.multi_queue_engine_class_mask = graphics_desc->multi_queue_engine_class_mask;
if (!xe_configfs_get_enable_multi_queue(to_pci_dev(xe->drm.dev))) {
xe_gt_info(gt, "Multi-queue disabled via configfs\n");
gt->info.multi_queue_engine_class_mask = 0;
}
gt->info.engine_mask = graphics_desc->hw_engine_mask;
gt->info.num_geometry_xecore_fuse_regs = graphics_desc->num_geometry_xecore_fuse_regs;
gt->info.num_compute_xecore_fuse_regs = graphics_desc->num_compute_xecore_fuse_regs;
@@ -906,7 +924,7 @@ static struct xe_gt *alloc_media_gt(struct xe_tile *tile,
struct xe_gt *gt;
if (!xe_configfs_media_gt_allowed(to_pci_dev(xe->drm.dev))) {
xe_info(xe, "Media GT disabled via configfs\n");
xe_tile_info(tile, "Media GT disabled via configfs\n");
return NULL;
}
@@ -920,6 +938,7 @@ static struct xe_gt *alloc_media_gt(struct xe_tile *tile,
gt->info.type = XE_GT_TYPE_MEDIA;
gt->info.id = tile->id * xe->info.max_gt_per_tile + 1;
gt->info.has_indirect_ring_state = media_desc->has_indirect_ring_state;
gt->info.has_uncorrectable_error_reporting = media_desc->has_uncorrectable_error_reporting;
gt->info.engine_mask = media_desc->hw_engine_mask;
return gt;
@@ -1098,6 +1117,12 @@ static void xe_pci_remove(struct pci_dev *pdev)
return;
xe_device_remove(xe);
/*
* Preserve remove-time flush after moving destroy work to module
* lifetime.
*/
xe_destroy_wq_flush();
xe_pm_fini(xe);
}

View File

@@ -10,6 +10,7 @@
#include "xe_pci.h"
#include "xe_pm.h"
#include "xe_printk.h"
#include "xe_ras.h"
#include "xe_survivability_mode.h"
static void prepare_device_for_reset(struct pci_dev *pdev)
@@ -34,6 +35,21 @@ static void prepare_device_for_reset(struct pci_dev *pdev)
pci_disable_device(pdev);
}
static pci_ers_result_t ras_action_to_pci_result(struct pci_dev *pdev, u8 action)
{
switch (action) {
case XE_RAS_RECOVERY_ACTION_RECOVERED:
return PCI_ERS_RESULT_RECOVERED;
case XE_RAS_RECOVERY_ACTION_RESET:
prepare_device_for_reset(pdev);
return PCI_ERS_RESULT_NEED_RESET;
case XE_RAS_RECOVERY_ACTION_DISCONNECT:
return PCI_ERS_RESULT_DISCONNECT;
default:
return PCI_ERS_RESULT_DISCONNECT;
}
}
static pci_ers_result_t xe_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state)
{
struct xe_device *xe = pdev_to_xe_device(pdev);
@@ -62,11 +78,12 @@ static pci_ers_result_t xe_pci_error_detected(struct pci_dev *pdev, pci_channel_
static pci_ers_result_t xe_pci_error_mmio_enabled(struct pci_dev *pdev)
{
struct xe_device *xe = pdev_to_xe_device(pdev);
enum xe_ras_recovery_action action;
xe_info(xe, "PCI error: MMIO enabled\n");
action = xe_ras_process_errors(xe);
/* TODO: Query system controller for the type of error and take appropriate action */
return PCI_ERS_RESULT_RECOVERED;
return ras_action_to_pci_result(pdev, action);
}
static pci_ers_result_t xe_pci_error_slot_reset(struct pci_dev *pdev)

View File

@@ -79,12 +79,14 @@ struct xe_graphics_desc {
u8 has_ctx_tlb_inval:1;
u8 has_usm:1;
u8 has_64bit_timestamp:1;
u8 has_uncorrectable_error_reporting:1;
};
struct xe_media_desc {
u64 hw_engine_mask; /* hardware engines provided by media IP */
u8 has_indirect_ring_state:1;
u8 has_uncorrectable_error_reporting:1;
};
struct xe_ip {

View File

@@ -11,10 +11,14 @@
#include <drm/drm_managed.h>
#include "regs/xe_pmt.h"
#include "xe_assert.h"
#include "xe_device.h"
#include "xe_mmio.h"
#include "xe_pcode_api.h"
#include "xe_pm.h"
#include "xe_printk.h"
#include "xe_vsec.h"
/**
* DOC: PCODE
@@ -350,3 +354,31 @@ int xe_pcode_probe_early(struct xe_device *xe)
return xe_pcode_ready(xe, false);
}
ALLOW_ERROR_INJECTION(xe_pcode_probe_early, ERRNO); /* See xe_pci_probe */
/**
* xe_get_pcode_version - Read pcode version via PMT telemetry
* @xe: xe instance
* @version: pointer to struct xe_pcode_version to store version info
*
* Reads the pcode version from PMT telemetry and fills the
* provided @version structure.
*
* Return: 0 on success, negative error code on failure.
*/
int xe_get_pcode_version(struct xe_device *xe, struct xe_pcode_version *version)
{
int ret = 0;
guard(xe_pm_runtime)(xe);
ret = xe_pmt_telem_read(xe->drm.dev,
xe_mmio_read32(xe_root_tile_mmio(xe), PUNIT_TELEMETRY_GUID),
(u64 *)version, PUNIT_VERSION_OFFSET, sizeof(*version));
if (ret != sizeof(*version)) {
xe_warn(xe, "pcode version read from PMT failed, ret %pe\n", ERR_PTR(ret));
return ret;
}
xe_dbg(xe, "pcode version major %u minor %u engg %u\n", version->major,
version->minor, version->engg);
return 0;
}

View File

@@ -12,6 +12,12 @@ struct drm_device;
struct xe_device;
struct xe_tile;
struct xe_pcode_version {
u16 minor;
u16 major;
u32 engg;
};
int xe_pcode_init_early(struct xe_tile *tile);
int xe_pcode_probe_early(struct xe_device *xe);
int xe_pcode_ready(struct xe_device *xe, bool locked);
@@ -22,6 +28,7 @@ int xe_pcode_write_timeout(struct xe_tile *tile, u32 mbox, u32 val,
int timeout_ms);
int xe_pcode_write64_timeout(struct xe_tile *tile, u32 mbox, u32 data0,
u32 data1, int timeout);
int xe_get_pcode_version(struct xe_device *xe, struct xe_pcode_version *version);
#define xe_pcode_write(tile, mbox, val) \
xe_pcode_write_timeout(tile, mbox, val, 1)

View File

@@ -74,6 +74,13 @@ static bool preempt_fence_enable_signaling(struct dma_fence *fence)
struct xe_exec_queue *q = pfence->q;
pfence->error = q->ops->suspend(q);
/*
* Record a successful suspend so the rebind worker only resumes queues
* that were actually suspended; a failed suspend() leaves the queue
* un-suspended and must not be paired with a resume().
*/
if (!pfence->error)
WRITE_ONCE(q->lr.suspended, true);
queue_work(q->vm->xe->preempt_fence_wq, &pfence->preempt_work);
return true;
}

View File

@@ -302,6 +302,14 @@ struct xe_pt_stage_bind_walk {
bool needs_64K;
/** @clear_pt: clear page table entries during the bind walk */
bool clear_pt;
/**
* @target_leaf_level: Page-table level at which to emit leaf PTEs
* 0 for normal 4K/64K mappings, 1 for 2M huge pages, and 2 for 1G huge
* pages. The walk still traverses from the root down; this field tells
* xe_pt_stage_bind_entry() to treat the selected level as a leaf instead
* of descending further.
*/
u32 target_leaf_level;
/**
* @vma: VMA being mapped
*/
@@ -443,10 +451,6 @@ static bool xe_pt_hugepte_possible(u64 addr, u64 next, unsigned int level,
if (!xe_pt_covers(addr, next, level, &xe_walk->base))
return false;
/* Does the DMA segment cover the whole pte? */
if (next - xe_walk->va_curs_start > xe_walk->curs->size)
return false;
/* null VMA's and purged BO's do not have dma addresses */
if (xe_vma_is_null(xe_walk->vma) || (bo && xe_bo_is_purged(bo)))
return true;
@@ -455,6 +459,10 @@ static bool xe_pt_hugepte_possible(u64 addr, u64 next, unsigned int level,
if (xe_walk->clear_pt)
return true;
/* Does the DMA segment cover the whole pte? */
if (next - xe_walk->va_curs_start > xe_walk->curs->size)
return false;
/* Is the DMA address huge PTE size aligned? */
size = next - addr;
dma = addr - xe_walk->va_curs_start + xe_res_dma(xe_walk->curs);
@@ -514,6 +522,39 @@ xe_pt_is_pte_ps64K(u64 addr, u64 next, struct xe_pt_stage_bind_walk *xe_walk)
return xe_walk->found_64K;
}
static bool xe_pt_huge_leaf_allowed(u64 addr, u64 next, unsigned int level,
struct xe_pt_stage_bind_walk *xe_walk)
{
if (xe_walk->clear_pt)
return xe_pt_hugepte_possible(addr, next, level, xe_walk);
if (!xe_debug_page_size_supported(xe_walk->vm->xe))
return xe_pt_hugepte_possible(addr, next, level, xe_walk);
if (!xe_walk->target_leaf_level)
return xe_pt_hugepte_possible(addr, next, level, xe_walk);
if (level == xe_walk->target_leaf_level)
return xe_pt_hugepte_possible(addr, next, level, xe_walk);
return false;
}
static bool xe_pt_exact_leaf_required_but_invalid(u64 addr, u64 next,
unsigned int level,
struct xe_pt_stage_bind_walk *xe_walk)
{
struct xe_device *xe = xe_walk->vm->xe;
if (!xe_debug_page_size_mode_not_none(xe))
return false;
return !xe_walk->clear_pt &&
xe_walk->target_leaf_level &&
level == xe_walk->target_leaf_level &&
!xe_pt_hugepte_possible(addr, next, level, xe_walk);
}
static int
xe_pt_stage_bind_entry(struct xe_ptw *parent, pgoff_t offset,
unsigned int level, u64 addr, u64 next,
@@ -531,8 +572,18 @@ xe_pt_stage_bind_entry(struct xe_ptw *parent, pgoff_t offset,
int ret = 0;
u64 pte;
/* Is this a leaf entry ?*/
if (level == 0 || xe_pt_hugepte_possible(addr, next, level, xe_walk)) {
if (xe_pt_exact_leaf_required_but_invalid(addr, next, level, xe_walk))
return -EINVAL;
/*
* Is this a leaf entry?
* Always create a 4K leaf at level 0. For huge pages (level > 0),
* validate alignment and size with xe_pt_hugepte_possible().
* When target_leaf_level is non-zero, only that huge-page level is
* accepted for normal bind walks. Clear walks remain unconstrained so
* existing huge leaves can be cleared without descending further.
*/
if (level == 0 || xe_pt_huge_leaf_allowed(addr, next, level, xe_walk)) {
struct xe_res_cursor *curs = xe_walk->curs;
struct xe_bo *bo = xe_vma_bo(xe_walk->vma);
bool is_null_or_purged = xe_vma_is_null(xe_walk->vma) ||
@@ -682,6 +733,26 @@ static bool xe_atomic_for_system(struct xe_vm *vm, struct xe_vma *vma)
(bo && xe_bo_has_single_placement(bo))));
}
static u32 xe_pt_target_leaf_level_from_bo(struct xe_device *xe,
struct xe_vma *vma)
{
struct xe_bo *bo = xe_vma_bo(vma);
if (!xe_debug_page_size_mode_not_none(xe))
return 0;
if (!bo || !xe_bo_is_vram(bo) || !(bo->flags & XE_BO_FLAG_USER))
return 0;
if (bo->flags & XE_BO_FLAG_NEEDS_1G)
return 2;
if (bo->flags & XE_BO_FLAG_NEEDS_2M)
return 1;
return 0;
}
/**
* xe_pt_stage_bind() - Build a disconnected page-table tree for a given address
* range.
@@ -774,9 +845,13 @@ xe_pt_stage_bind(struct xe_tile *tile, struct xe_vma *vma,
xe_svm_notifier_unlock(vm);
}
xe_walk.target_leaf_level = xe_pt_target_leaf_level_from_bo(xe, vma);
xe_walk.needs_64K = (vm->flags & XE_VM_FLAG_64K);
if (clear_pt)
if (clear_pt) {
xe_assert(xe, !range);
curs.size = xe_vma_size(vma);
goto walk_pt;
}
if (vma->gpuva.flags & XE_VMA_ATOMIC_PTE_BIT) {
xe_walk.default_vram_pte = xe_atomic_for_vram(vm, vma) ? XE_USM_PPGTT_PTE_AE : 0;
@@ -1418,6 +1493,7 @@ static int xe_pt_pre_commit(struct xe_migrate_pt_update *pt_update)
pt_update_ops, rftree);
}
#if IS_ENABLED(CONFIG_DRM_GPUSVM)
/*
* Acquire/release the svm notifier_lock around xe_pt_svm_userptr_pre_commit()
* and the matching late release in xe_pt_update_ops_run(). Read mode by
@@ -1444,6 +1520,10 @@ static void xe_pt_svm_userptr_notifier_unlock(struct xe_vm *vm)
xe_svm_notifier_unlock(vm);
#endif
}
#else
static inline void xe_pt_svm_userptr_notifier_lock(struct xe_vm *vm) { }
static inline void xe_pt_svm_userptr_notifier_unlock(struct xe_vm *vm) { }
#endif
#if IS_ENABLED(CONFIG_DRM_GPUSVM)
#ifdef CONFIG_DRM_XE_USERPTR_INVAL_INJECT
@@ -2366,8 +2446,11 @@ static void
xe_pt_update_ops_init(struct xe_vm_pgtable_update_ops *pt_update_ops)
{
init_llist_head(&pt_update_ops->deferred);
pt_update_ops->current_op = 0;
pt_update_ops->start = ~0x0ull;
pt_update_ops->last = 0x0ull;
pt_update_ops->needs_svm_lock = false;
pt_update_ops->needs_invalidation = false;
xe_page_reclaim_list_init(&pt_update_ops->prl);
}

View File

@@ -8,12 +8,22 @@
#include "xe_pm.h"
#include "xe_printk.h"
#include "xe_ras.h"
#include "xe_ras_types.h"
#include "xe_survivability_mode.h"
#include "xe_sysctrl.h"
#include "xe_sysctrl_event_types.h"
#include "xe_sysctrl_mailbox.h"
#include "xe_sysctrl_mailbox_types.h"
#define CORE_COMPUTE_UNCORR_TYPE GENMASK(26, 25)
/*
* Uncorrectable error type for core compute errors.
* 0 - Correctable Error
* 1 - Local Uncorrectable Error
* 2 - Global Uncorrectable Error
* 3 - Informational Error
*/
#define GLOBAL_UNCORR_ERROR 2
/* Severity of detected errors */
enum xe_ras_severity {
XE_RAS_SEV_NOT_SUPPORTED = 0,
@@ -46,6 +56,14 @@ enum xe_ras_response_status {
XE_RAS_STATUS_MAX
};
/* GPU health values */
enum xe_ras_health {
XE_RAS_HEALTH_OK = 0,
XE_RAS_HEALTH_WARNING,
XE_RAS_HEALTH_CRITICAL,
XE_RAS_HEALTH_MAX
};
static const char *const xe_ras_severities[] = {
[XE_RAS_SEV_NOT_SUPPORTED] = "Not Supported",
[XE_RAS_SEV_CORRECTABLE] = "Correctable Error",
@@ -65,6 +83,13 @@ static const char *const xe_ras_components[] = {
};
static_assert(ARRAY_SIZE(xe_ras_components) == XE_RAS_COMP_MAX);
static const char * const gpu_health_states[] = {
[XE_RAS_HEALTH_OK] = "ok",
[XE_RAS_HEALTH_WARNING] = "warning",
[XE_RAS_HEALTH_CRITICAL] = "critical",
};
static_assert(ARRAY_SIZE(gpu_health_states) == XE_RAS_HEALTH_MAX);
static u8 drm_to_xe_ras_severity(u8 severity)
{
switch (severity) {
@@ -193,6 +218,94 @@ static void ras_usp_aer_init(struct xe_device *xe)
dev_dbg(&usp->dev, "Uncorrectable Internal Errors downgraded and unmasked\n");
}
static u8 handle_core_compute_errors(struct xe_ras_error_array *arr)
{
struct xe_ras_compute_error *error_info = (void *)arr->details;
u8 uncorr_type;
uncorr_type = FIELD_GET(CORE_COMPUTE_UNCORR_TYPE, error_info->log_header);
/* Request a reset if error is global */
if (uncorr_type == GLOBAL_UNCORR_ERROR)
return XE_RAS_RECOVERY_ACTION_RESET;
/*
* No action needed for other errors.
* Local errors are recovered using an engine reset by GuC.
*/
return XE_RAS_RECOVERY_ACTION_RECOVERED;
}
static u8 handle_soc_internal_errors(struct xe_device *xe, struct xe_ras_error_array *arr)
{
struct xe_ras_soc_error *info = (void *)arr->details;
struct xe_ras_soc_error_source *source = &info->source;
struct xe_ras_error_class *counter = &arr->counter;
if (source->csc) {
struct xe_ras_csc_error *csc_error = (void *)info->details;
/*
* CSC uncorrectable errors are classified as hardware errors and firmware errors.
* CSC firmware errors are critical errors that can be recovered only by firmware
* update via SPI driver. On a CSC firmware error, PCODE enables FDO mode and sets
* the bit in the capability register. On receiving this error, the driver enables
* runtime survivability mode which notifies userspace that a firmware update
* is required.
*/
if (csc_error->hec_fw_error) {
xe_err(xe, "[RAS]: CSC %s detected: 0x%x\n",
sev_to_str(counter->common.severity),
csc_error->hec_fw_error);
xe_survivability_mode_runtime_enable(xe);
return XE_RAS_RECOVERY_ACTION_DISCONNECT;
}
} else if (source->ieh) {
struct xe_ras_ieh_error *ieh_error = (void *)info->details;
if (ieh_error->global_error_status & XE_RAS_SOC_IEH_PUNIT) {
xe_err(xe, "[RAS]: PUNIT %s detected: 0x%x\n",
sev_to_str(counter->common.severity),
ieh_error->global_error_status);
/* TODO: Add PUNIT error handling */
return XE_RAS_RECOVERY_ACTION_DISCONNECT;
}
}
/* For other SoC internal errors, request a reset as recovery mechanism */
return XE_RAS_RECOVERY_ACTION_RESET;
}
static u8 handle_device_memory_errors(struct xe_device *xe, struct xe_ras_error_array *arr)
{
struct xe_ras_memory_error *info = (void *)arr->details;
/*
* For memory errors, the recovery action depends on the error category
*
* TODO: Double-bit ECC errors: Page offlining
* Poison and data parity errors: Log only
* For any other memory errors, request a reset as recovery mechanism
*/
switch (info->category) {
case XE_RAS_MEMORY_POISON:
xe_info(xe, "[RAS]: Poison error detected\n");
break;
case XE_RAS_MEMORY_DATA_PARITY:
xe_info(xe, "[RAS]: Data parity error detected\n");
break;
case XE_RAS_MEMORY_DB_ECC:
xe_info(xe, "[RAS]: Double-bit ECC error detected at sw address 0x%llx\n",
info->sw_address);
/* TODO: Add page offlining for Double-bit ECC error */
fallthrough;
default:
return XE_RAS_RECOVERY_ACTION_RESET;
}
return XE_RAS_RECOVERY_ACTION_RECOVERED;
}
void xe_ras_counter_threshold_crossed(struct xe_device *xe,
struct xe_sysctrl_event_response *response)
{
@@ -254,6 +367,99 @@ static int get_counter(struct xe_device *xe, struct xe_ras_error_class *counter,
return 0;
}
/**
* xe_ras_process_errors() - Process and contain hardware errors
* @xe: xe device instance
*
* Get error details from system controller and return recovery
* method.
*
* Returns: recovery action to be taken
*/
enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe)
{
struct xe_sysctrl_mailbox_command command = {0};
enum xe_ras_recovery_action final_action;
u32 remaining = XE_SYSCTRL_FLOOD_LIMIT;
struct xe_ras_get_soc_error response;
size_t rlen;
int ret;
if (!xe->info.has_sysctrl)
return XE_RAS_RECOVERY_ACTION_RESET;
/* Default action */
final_action = XE_RAS_RECOVERY_ACTION_RECOVERED;
xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_GET_SOC_ERROR,
NULL, 0, &response, sizeof(response));
do {
memset(&response, 0, sizeof(response));
ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen);
if (ret) {
xe_err(xe, "sysctrl: failed to get soc error %d\n", ret);
goto err;
}
if (rlen != sizeof(response)) {
xe_err(xe, "sysctrl: unexpected get soc error response length %zu (expected %zu)\n",
rlen, sizeof(response));
goto err;
}
/* Report if number of errors exceeds the maximum errors supported */
if (response.num_errors > XE_RAS_NUM_ERROR_ARR)
xe_err(xe, "sysctrl: number of errors received %d out of bound (%d)\n",
response.num_errors, XE_RAS_NUM_ERROR_ARR);
for (int i = 0; i < response.num_errors && i < XE_RAS_NUM_ERROR_ARR; i++) {
struct xe_ras_error_array *arr = &response.arr[i];
enum xe_ras_recovery_action action;
u8 component, severity;
component = arr->counter.common.component;
severity = arr->counter.common.severity;
xe_info(xe, "[RAS]: %s %s detected\n", comp_to_str(component),
sev_to_str(severity));
switch (component) {
case XE_RAS_COMP_CORE_COMPUTE:
action = handle_core_compute_errors(arr);
break;
case XE_RAS_COMP_SOC_INTERNAL:
action = handle_soc_internal_errors(xe, arr);
break;
case XE_RAS_COMP_DEVICE_MEMORY:
action = handle_device_memory_errors(xe, arr);
break;
default:
/* For any other component, reset */
action = XE_RAS_RECOVERY_ACTION_RESET;
break;
}
/* Process and log all errors and then trigger highest recovery action */
if (action > final_action)
final_action = action;
}
/* Treat flooding as a system controller error */
if (!--remaining) {
xe_err(xe, "[RAS]: sysctrl: get soc error response flooding\n");
goto err;
}
} while (response.additional_errors);
return final_action;
err:
return XE_RAS_RECOVERY_ACTION_RESET;
}
/**
* xe_ras_get_counter() - Get error counter value
* @xe: Xe device instance
@@ -332,6 +538,139 @@ int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component)
return 0;
}
static ssize_t gpu_health_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct xe_ras_get_health_response response = {0};
struct xe_sysctrl_mailbox_command command = {0};
struct xe_ras_get_health_request request = {0};
struct xe_device *xe = kdev_to_xe_device(dev);
const char *health;
size_t rlen;
int ret;
xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_GET_HEALTH,
&request, sizeof(request), &response, sizeof(response));
guard(xe_pm_runtime)(xe);
ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen);
if (ret) {
xe_err(xe, "sysctrl: failed to get health %d\n", ret);
return ret;
}
if (rlen != sizeof(response)) {
xe_err(xe, "sysctrl: unexpected get health response length %zu (expected %zu)\n",
rlen, sizeof(response));
return -EIO;
}
if (response.health >= XE_RAS_HEALTH_MAX) {
xe_err(xe, "sysctrl: invalid health state %u\n",
response.health);
return -EIO;
}
health = gpu_health_states[response.health];
xe_dbg(xe, "[RAS]: get health: %s\n", health);
return sysfs_emit(buf, "%s\n", health);
}
static ssize_t gpu_health_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct xe_ras_set_health_response response = {0};
struct xe_sysctrl_mailbox_command command = {0};
struct xe_ras_set_health_request request = {0};
struct xe_device *xe = kdev_to_xe_device(dev);
const char *health;
size_t rlen;
int state;
int ret;
state = sysfs_match_string(gpu_health_states, buf);
if (state < 0)
return -EINVAL;
request.health = state;
xe_sysctrl_create_command(&command, XE_SYSCTRL_GROUP_GFSP, XE_SYSCTRL_CMD_SET_HEALTH,
&request, sizeof(request), &response, sizeof(response));
guard(xe_pm_runtime)(xe);
ret = xe_sysctrl_send_command(&xe->sc, &command, &rlen);
if (ret) {
xe_err(xe, "sysctrl: failed to set health %d\n", ret);
return ret;
}
if (rlen != sizeof(response)) {
xe_err(xe, "sysctrl: unexpected set health response length %zu (expected %zu)\n",
rlen, sizeof(response));
return -EIO;
}
ret = ras_status_to_errno(response.status);
if (ret) {
xe_err(xe, "sysctrl: set health command failed with status %#x\n",
response.status);
return ret;
}
if (response.health >= XE_RAS_HEALTH_MAX) {
xe_err(xe, "sysctrl: invalid health state %u\n",
response.health);
return -EIO;
}
health = gpu_health_states[response.health];
xe_dbg(xe, "[RAS]: set health: %s\n", health);
return count;
}
static DEVICE_ATTR_RW(gpu_health);
static struct attribute *gpu_health_attrs[] = {
&dev_attr_gpu_health.attr,
NULL
};
/**
* DOC: GPU Health Indicator
*
* On Intel Xe platforms that support the gpu health indicator interface,
* the driver exposes this sysfs attribute for in-band access to the gpu
* health state::
*
* /sys/bus/pci/devices/<device>/gpu_health
*
* Reading the attribute is available to all users and returns a single
* line containing the current gpu health state, whereas writing is
* restricted to administrative users and updates the state to one of the
* valid values.
*
* Management tools and administrators use this interface to query the
* current gpu health state (e.g. for telemetry/monitoring) and to
* update it - for example, to mark the gpu as ``warning`` or ``critical``
* after diagnostics, or reset it back to ``ok`` once remediated.
*
* The valid values for the gpu health state are:
*
* - ``ok``
* The gpu is healthy and operating within normal parameters.
*
* - ``warning``
* The gpu is experiencing minor issues but remains operational.
*
* - ``critical``
* The gpu is in a critical state and may not be operational.
*
* See Documentation/ABI/testing/sysfs-driver-intel-xe-ras for the ABI
* specification.
*/
static const struct attribute_group gpu_health_group = {
.attrs = gpu_health_attrs,
};
/**
* xe_ras_init - Initialize Xe RAS
* @xe: xe device instance
@@ -340,6 +679,8 @@ int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component)
*/
void xe_ras_init(struct xe_device *xe)
{
int ret;
if (!xe->info.has_drm_ras)
return;
@@ -350,4 +691,14 @@ void xe_ras_init(struct xe_device *xe)
if (IS_ENABLED(CONFIG_PCIEAER))
ras_usp_aer_init(xe);
/*
* During probe, process and log any errors detected by firmware while the driver was not
* loaded. Critical errors such as Punit and CSC are reported through Pcode init failure,
* causing the driver to enter survivability mode.
*/
xe_ras_process_errors(xe);
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);
}

View File

@@ -7,6 +7,7 @@
#define _XE_RAS_H_
#include <linux/types.h>
#include "xe_ras_types.h"
struct xe_device;
struct xe_sysctrl_event_response;
@@ -16,5 +17,6 @@ void xe_ras_counter_threshold_crossed(struct xe_device *xe,
int xe_ras_get_counter(struct xe_device *xe, u8 severity, u8 component, u32 *value);
int xe_ras_clear_counter(struct xe_device *xe, u8 severity, u8 component);
void xe_ras_init(struct xe_device *xe);
enum xe_ras_recovery_action xe_ras_process_errors(struct xe_device *xe);
#endif

View File

@@ -9,6 +9,31 @@
#include <linux/types.h>
#define XE_RAS_NUM_COUNTERS 16
#define XE_RAS_NUM_ERROR_ARR 3
/* Error bits in IEH global error status register */
#define XE_RAS_SOC_IEH_PUNIT BIT(1)
/* Device memory error categories */
#define XE_RAS_MEMORY_DB_ECC BIT(1)
#define XE_RAS_MEMORY_POISON BIT(2)
#define XE_RAS_MEMORY_DATA_PARITY BIT(5)
/**
* enum xe_ras_recovery_action - RAS recovery actions
*
* @XE_RAS_RECOVERY_ACTION_RECOVERED: Error recovered
* @XE_RAS_RECOVERY_ACTION_RESET: Requires reset
* @XE_RAS_RECOVERY_ACTION_DISCONNECT: Requires disconnect
* @XE_RAS_RECOVERY_ACTION_MAX: Max action value
*
* This enum defines the possible recovery actions that can be taken in response
* to RAS errors.
*/
enum xe_ras_recovery_action {
XE_RAS_RECOVERY_ACTION_RECOVERED = 0,
XE_RAS_RECOVERY_ACTION_RESET,
XE_RAS_RECOVERY_ACTION_DISCONNECT,
XE_RAS_RECOVERY_ACTION_MAX
};
/**
* struct xe_ras_error_common - Error fields that are common across all products
@@ -121,4 +146,144 @@ struct xe_ras_clear_counter_response {
/** @reserved1: Reserved for future use */
u32 reserved1[3];
} __packed;
/**
* struct xe_ras_error_array - Details of the error types
*/
struct xe_ras_error_array {
/** @value: Counter value of the detailed error */
u32 value;
/** @counter: Error counter */
struct xe_ras_error_class counter;
/** @timestamp: Timestamp */
u64 timestamp;
/** @details: Error details specific to the counter */
u32 details[XE_RAS_NUM_COUNTERS];
} __packed;
/**
* struct xe_ras_get_soc_error - Response from get soc error command
*/
struct xe_ras_get_soc_error {
/** @num_errors: Number of errors reported in this response */
u8 num_errors;
/** @additional_errors: Indicates if the errors are pending */
u8 additional_errors;
/** @arr: Array of up to 3 errors */
struct xe_ras_error_array arr[XE_RAS_NUM_ERROR_ARR];
} __packed;
/**
* struct xe_ras_compute_error - Error details of Core Compute error
*/
struct xe_ras_compute_error {
/** @log_header: Error Source and type */
u32 log_header;
/** @reserved: Reserved */
u32 reserved[15];
} __packed;
/**
* struct xe_ras_soc_error_source - Source of SoC error
*/
struct xe_ras_soc_error_source {
/** @csc: CSC */
u32 csc:1;
/** @ieh: IEH (Integrated Error Handler) */
u32 ieh:1;
/** @reserved: Reserved for future use */
u32 reserved:30;
} __packed;
/**
* struct xe_ras_soc_error - Error details of SoC internal error
*/
struct xe_ras_soc_error {
/** @source: Error source */
struct xe_ras_soc_error_source source;
/** @details: Error details specific to the error source */
u32 details[15];
} __packed;
/**
* struct xe_ras_csc_error - CSC error details
*/
struct xe_ras_csc_error {
/** @reserved: Reserved for future use */
u32 reserved;
/** @hec_fw_error: CSC firmware error */
u32 hec_fw_error;
} __packed;
/**
* struct xe_ras_ieh_error - IEH (Integrated Error Handler) error details
*/
struct xe_ras_ieh_error {
/** @reserved: Reserved for future use */
u32 reserved;
/** @global_error_status: Global error status */
u32 global_error_status;
/** @reserved1: Reserved for future use */
u32 reserved1[2];
/** @info: Additional information */
u32 info[10];
} __packed;
/**
* struct xe_ras_memory_error - Device memory error details
*/
struct xe_ras_memory_error {
/** @category: Device memory error category */
u8 category;
/** @reserved: Reserved for future use */
u8 reserved[7];
/** @reserved1: Reserved for future use */
u64 reserved1;
/** @sw_address: Software address where error occurred */
u64 sw_address;
/** @reserved2: Reserved for future use */
u32 reserved2[10];
} __packed;
/**
* struct xe_ras_get_health_request - Request structure for obtaining gpu health
*/
struct xe_ras_get_health_request {
/** @reserved: Reserved for future use. */
u32 reserved[2];
} __packed;
/**
* struct xe_ras_get_health_response - Response structure for obtaining gpu health
*/
struct xe_ras_get_health_response {
/** @health: gpu health value */
u8 health;
/** @reserved: Reserved for future use */
u8 reserved[3];
} __packed;
/**
* struct xe_ras_set_health_request - Request structure for setting gpu health
*/
struct xe_ras_set_health_request {
/** @health: gpu health value */
u8 health;
/** @reserved: Reserved for future use */
u8 reserved[3];
} __packed;
/**
* struct xe_ras_set_health_response - Response structure for setting gpu health
*/
struct xe_ras_set_health_response {
/** @status: Status of set health operation */
u32 status;
/** @health: Resulting gpu health value */
u8 health;
/** @reserved: Reserved for future use */
u8 reserved[3];
/** @reserved1: Reserved for future use */
u32 reserved1[2];
} __packed;
#endif

View File

@@ -3,6 +3,8 @@
* Copyright © 2025 Intel Corporation
*/
#include "abi/xe_driver_klvs_abi.h"
#include "xe_bo.h"
#include "xe_device.h"
#include "xe_guc_klv_helpers.h"
@@ -352,19 +354,13 @@ ssize_t xe_sriov_packet_write_single(struct xe_device *xe, unsigned int vfid,
return copied;
}
#define MIGRATION_KLV_DEVICE_DEVID_KEY 0xf001u
#define MIGRATION_KLV_DEVICE_DEVID_LEN 1u
#define MIGRATION_KLV_DEVICE_REVID_KEY 0xf002u
#define MIGRATION_KLV_DEVICE_REVID_LEN 1u
#define MIGRATION_DESCRIPTOR_DWORDS (GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_DEVID_LEN + \
GUC_KLV_LEN_MIN + MIGRATION_KLV_DEVICE_REVID_LEN)
static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid)
{
struct xe_sriov_packet **desc = pf_pick_descriptor(xe, vfid);
struct xe_sriov_packet *data;
unsigned int len = 0;
u32 *klvs;
u32 *klvs, *end;
int ret;
data = xe_sriov_packet_alloc(xe);
@@ -379,20 +375,55 @@ static int pf_descriptor_init(struct xe_device *xe, unsigned int vfid)
}
klvs = data->vaddr;
klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_DEVID_KEY,
MIGRATION_KLV_DEVICE_DEVID_LEN);
klvs[len++] = xe->info.devid;
klvs[len++] = PREP_GUC_KLV_CONST(MIGRATION_KLV_DEVICE_REVID_KEY,
MIGRATION_KLV_DEVICE_REVID_LEN);
klvs[len++] = xe->info.revid;
end = klvs + MIGRATION_DESCRIPTOR_DWORDS;
xe_assert(xe, len == MIGRATION_DESCRIPTOR_DWORDS);
klvs = xe_guc_klv_encode_u32(klvs, end - klvs,
MIGRATION_KLV_DEVICE_DEVID_KEY,
xe->info.devid);
klvs = xe_guc_klv_encode_u32(klvs, end - klvs,
MIGRATION_KLV_DEVICE_REVID_KEY,
xe->info.revid);
xe_assert(xe, !IS_ERR(klvs));
xe_assert(xe, klvs == end);
*desc = data;
return 0;
}
static int descriptor_decoder(void *arg, u16 key, u16 len, const u32 *value)
{
struct xe_device *xe = arg;
xe_sriov_dbg_verbose(xe, "found KLV %#x %s\n", key, xe_guc_klv_key_to_string(key));
switch (key) {
case MIGRATION_KLV_DEVICE_DEVID_KEY:
if (*value != xe->info.devid) {
xe_sriov_warn(xe, "Aborting migration, devid mismatch %#06x!=%#06x\n",
*value, xe->info.devid);
return -ENODEV;
}
break;
case MIGRATION_KLV_DEVICE_REVID_KEY:
if (*value != xe->info.revid) {
xe_sriov_warn(xe, "Aborting migration, revid mismatch %#06x!=%#06x\n",
*value, xe->info.revid);
return -ENODEV;
}
break;
default:
if (IS_ENABLED(CONFIG_DRM_XE_DEBUG)) {
struct drm_printer p = xe_dbg_printer(xe);
xe_sriov_dbg(xe, "unexpected KLV %#x in descriptor!\n", key);
xe_guc_klv_print_one(key, len, value, &p);
}
return 0;
}
return 1;
}
/**
* xe_sriov_packet_process_descriptor() - Process migration data descriptor packet.
* @xe: the &xe_device
@@ -409,6 +440,7 @@ int xe_sriov_packet_process_descriptor(struct xe_device *xe, unsigned int vfid,
{
u32 num_dwords = data->hdr.size / sizeof(u32);
u32 *klvs = data->vaddr;
int ret;
xe_assert(xe, data->hdr.type == XE_SRIOV_PACKET_TYPE_DESCRIPTOR);
@@ -418,47 +450,18 @@ int xe_sriov_packet_process_descriptor(struct xe_device *xe, unsigned int vfid,
return -EINVAL;
}
while (num_dwords >= GUC_KLV_LEN_MIN) {
u32 key = FIELD_GET(GUC_KLV_0_KEY, klvs[0]);
u32 len = FIELD_GET(GUC_KLV_0_LEN, klvs[0]);
ret = xe_guc_klv_count(klvs, num_dwords);
if (ret < 0) {
xe_sriov_warn(xe, "Aborting migration, corrupted descriptor KLVs (%pe)\n",
ERR_PTR(ret));
return ret;
}
klvs += GUC_KLV_LEN_MIN;
num_dwords -= GUC_KLV_LEN_MIN;
if (len > num_dwords) {
xe_sriov_warn(xe, "Aborting migration, truncated KLV %#x, len %u\n",
key, len);
return -EINVAL;
}
switch (key) {
case MIGRATION_KLV_DEVICE_DEVID_KEY:
if (*klvs != xe->info.devid) {
xe_sriov_warn(xe,
"Aborting migration, devid mismatch %#06x!=%#06x\n",
*klvs, xe->info.devid);
return -ENODEV;
}
break;
case MIGRATION_KLV_DEVICE_REVID_KEY:
if (*klvs != xe->info.revid) {
xe_sriov_warn(xe,
"Aborting migration, revid mismatch %#06x!=%#06x\n",
*klvs, xe->info.revid);
return -ENODEV;
}
break;
default:
xe_sriov_dbg(xe,
"Skipping unknown migration KLV %#x, len=%u\n",
key, len);
print_hex_dump_bytes("desc: ", DUMP_PREFIX_OFFSET, klvs,
min(SZ_64, len * sizeof(u32)));
break;
}
klvs += len;
num_dwords -= len;
ret = xe_guc_klv_parser(klvs, num_dwords, xe, descriptor_decoder);
if (ret < 0) {
xe_sriov_warn(xe, "Aborting migration, descriptor parsing failed (%pe)\n",
ERR_PTR(ret));
return ret;
}
return 0;
@@ -519,3 +522,7 @@ int xe_sriov_packet_save_init(struct xe_device *xe, unsigned int vfid)
return 0;
}
#if IS_BUILTIN(CONFIG_DRM_XE_KUNIT_TEST)
#include "tests/xe_sriov_packet_kunit.c"
#endif

View File

@@ -3,6 +3,8 @@
* Copyright © 2025 Intel Corporation
*/
#include <drm/drm_drv.h>
#include "instructions/xe_mi_commands.h"
#include "instructions/xe_gpu_commands.h"
#include "xe_bb.h"
@@ -404,6 +406,8 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx)
/**
* xe_sriov_vf_ccs_attach_bo - Insert CCS read write commands in the BO.
* @bo: the &buffer object to which batch buffer commands will be added.
* @new_mem: the (not yet committed) destination resource @bo is being moved
* into; bo->ttm.resource is still the old resource at this point.
*
* This function shall be called only by VF. It inserts the PTEs and copy
* command instructions in the BO by calling xe_migrate_ccs_rw_copy()
@@ -411,7 +415,7 @@ void xe_sriov_vf_ccs_rw_update_bb_addr(struct xe_sriov_vf_ccs_ctx *ctx)
*
* Returns: 0 if successful, negative error code on failure.
*/
int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo)
int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem)
{
struct xe_device *xe = xe_bo_device(bo);
enum xe_sriov_vf_ccs_rw_ctxs ctx_id;
@@ -430,7 +434,21 @@ int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo)
xe_assert(xe, !bb);
ctx = &xe->sriov.vf.ccs.contexts[ctx_id];
err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, ctx_id);
err = xe_migrate_ccs_rw_copy(tile, ctx->mig_q, bo, new_mem, ctx_id);
if (err)
goto err_unwind;
}
return 0;
err_unwind:
/*
* Clean up any contexts already attached. Can't reuse
* xe_sriov_vf_ccs_detach_bo() here as it requires both contexts
* attached before cleaning up either one.
*/
for_each_ccs_rw_ctx(ctx_id) {
if (bo->bb_ccs[ctx_id])
xe_migrate_ccs_rw_copy_clear(bo, ctx_id, true);
}
return err;
}
@@ -450,19 +468,27 @@ int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo)
struct xe_device *xe = xe_bo_device(bo);
enum xe_sriov_vf_ccs_rw_ctxs ctx_id;
struct xe_mem_pool_node *bb;
bool bound;
int idx;
xe_assert(xe, IS_VF_CCS_READY(xe));
if (!xe_bo_has_valid_ccs_bb(bo))
return 0;
bound = drm_dev_enter(&xe->drm, &idx);
for_each_ccs_rw_ctx(ctx_id) {
bb = bo->bb_ccs[ctx_id];
if (!bb)
continue;
xe_migrate_ccs_rw_copy_clear(bo, ctx_id);
xe_migrate_ccs_rw_copy_clear(bo, ctx_id, bound);
}
if (bound)
drm_dev_exit(idx);
return 0;
}

View File

@@ -11,11 +11,12 @@
#include "xe_sriov_vf_ccs_types.h"
struct drm_printer;
struct ttm_resource;
struct xe_device;
struct xe_bo;
int xe_sriov_vf_ccs_init(struct xe_device *xe);
int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo);
int xe_sriov_vf_ccs_attach_bo(struct xe_bo *bo, struct ttm_resource *new_mem);
int xe_sriov_vf_ccs_detach_bo(struct xe_bo *bo);
int xe_sriov_vf_ccs_register_context(struct xe_device *xe);
void xe_sriov_vf_ccs_rebase(struct xe_device *xe);

View File

@@ -54,7 +54,6 @@
* # cat /sys/bus/pci/devices/<device>/survivability_mode
* Boot
*
*
* Any additional debug information if present will be visible under the directory
* ``survivability_info``::
*
@@ -98,6 +97,15 @@
* # cat /sys/bus/pci/devices/<device>/survivability_mode
* Runtime
*
* On some CSC firmware errors, PCODE sets FDO mode and the only recovery possible is through
* firmware flash using SPI driver. Userspace can check if FDO mode is set by checking the below
* sysfs entry.
*
* .. code-block:: shell
*
* # cat /sys/bus/pci/devices/<device>/survivability_info/fdo_mode
* enabled
*
* When such errors occur, userspace is notified with the drm device wedged uevent and runtime
* survivability mode. User can then initiate a firmware flash using userspace tools like fwupd
* to restore device to normal operation.
@@ -296,7 +304,8 @@ static int create_survivability_sysfs(struct pci_dev *pdev)
if (ret)
return ret;
if (check_boot_failure(xe)) {
/* Survivability info is not required if enabled via configfs */
if (!xe_configfs_get_survivability_mode(pdev)) {
ret = devm_device_add_group(dev, &survivability_info_group);
if (ret)
return ret;

View File

@@ -16,7 +16,7 @@ static void get_pending_event(struct xe_sysctrl *sc, struct xe_sysctrl_mailbox_c
{
struct xe_sysctrl_event_response *response = command->data_out;
struct xe_device *xe = sc_to_xe(sc);
u32 count = XE_SYSCTRL_EVENT_FLOOD;
u32 count = XE_SYSCTRL_FLOOD_LIMIT;
size_t len;
int ret;

View File

@@ -10,9 +10,6 @@
#define XE_SYSCTRL_EVENT_DATA_LEN 59
/* Modify as needed */
#define XE_SYSCTRL_EVENT_FLOOD 16
/**
* enum xe_sysctrl_event - Events reported by System Controller
*

View File

@@ -22,14 +22,20 @@ enum xe_sysctrl_group {
/**
* enum xe_sysctrl_gfsp_cmd - Commands supported by GFSP group
*
* @XE_SYSCTRL_CMD_GET_SOC_ERROR: Retrieve basic error information
* @XE_SYSCTRL_CMD_GET_COUNTER: Get error counter value
* @XE_SYSCTRL_CMD_CLEAR_COUNTER: Clear error counter value
* @XE_SYSCTRL_CMD_GET_PENDING_EVENT: Retrieve pending event
* @XE_SYSCTRL_CMD_GET_HEALTH: Retrieve gpu health
* @XE_SYSCTRL_CMD_SET_HEALTH: Set gpu health
*/
enum xe_sysctrl_gfsp_cmd {
XE_SYSCTRL_CMD_GET_SOC_ERROR = 0x01,
XE_SYSCTRL_CMD_GET_COUNTER = 0x03,
XE_SYSCTRL_CMD_CLEAR_COUNTER = 0x04,
XE_SYSCTRL_CMD_GET_PENDING_EVENT = 0x07,
XE_SYSCTRL_CMD_GET_HEALTH = 0x0B,
XE_SYSCTRL_CMD_SET_HEALTH = 0x0C,
};
/**
@@ -52,6 +58,9 @@ struct xe_sysctrl_mailbox_command {
size_t data_out_len;
};
/* Modify as needed */
#define XE_SYSCTRL_FLOOD_LIMIT 16
#define XE_SYSCTRL_MB_FRAME_SIZE 16
#define XE_SYSCTRL_MB_MAX_FRAMES 64
#define XE_SYSCTRL_MB_MAX_MESSAGE_SIZE \

View File

@@ -213,6 +213,11 @@ DEFINE_EVENT(xe_exec_queue, xe_exec_queue_memory_cat_error,
TP_ARGS(q)
);
DEFINE_EVENT(xe_exec_queue, xe_guc_uncorrectable_error,
TP_PROTO(struct xe_exec_queue *q),
TP_ARGS(q)
);
DEFINE_EVENT(xe_exec_queue, xe_exec_queue_cgp_context_error,
TP_PROTO(struct xe_exec_queue *q),
TP_ARGS(q)

View File

@@ -115,6 +115,7 @@ struct fw_blobs_by_type {
#define XE_GT_TYPE_ANY XE_GT_TYPE_UNINITIALIZED
#define XE_GUC_FIRMWARE_DEFS(fw_def, mmp_ver, major_ver) \
fw_def(NOVALAKE_S, GT_TYPE_ANY, major_ver(xe, guc, nvl, 70, 71, 0)) \
fw_def(PANTHERLAKE, GT_TYPE_ANY, major_ver(xe, guc, ptl, 70, 54, 0)) \
fw_def(BATTLEMAGE, GT_TYPE_ANY, major_ver(xe, guc, bmg, 70, 54, 0)) \
fw_def(LUNARLAKE, GT_TYPE_ANY, major_ver(xe, guc, lnl, 70, 53, 0)) \

View File

@@ -206,7 +206,23 @@ static void resume_and_reinstall_preempt_fences(struct xe_vm *vm,
xe_vm_assert_held(vm);
list_for_each_entry(q, &vm->preempt.exec_queues, lr.link) {
q->ops->resume(q);
/*
* Only resume queues whose suspend() actually succeeded. A
* failed suspend() (e.g. killed/banned/wedged) leaves the queue
* un-suspended, so it must not be resumed.
*
* Also skip queues that have since been reset/killed/banned/
* wedged: their suspend may not have completed (suspend_pending
* can still be set, e.g. a preempt fence signalled with -ENOENT
* without waiting), so resuming would trip the !suspend_pending
* assert in the backend. Such queues are being torn down anyway,
* so leave them marked suspended and let teardown resolve their
* state.
*/
if (READ_ONCE(q->lr.suspended) && !q->ops->reset_status(q)) {
WRITE_ONCE(q->lr.suspended, false);
q->ops->resume(q);
}
drm_gpuvm_resv_add_fence(&vm->gpuvm, exec, q->lr.pfence,
DMA_RESV_USAGE_BOOKKEEP, DMA_RESV_USAGE_BOOKKEEP);
@@ -1629,7 +1645,7 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
if (xef)
vm->xef = xe_file_get(xef);
/**
/*
* GSC VMs are kernel-owned, only used for PXP ops and can sometimes be
* manipulated under the PXP mutex. However, the PXP mutex can be taken
* under a user-VM lock when the PXP session is started at exec_queue
@@ -1749,10 +1765,8 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
vm->batch_invalidate_tlb = true;
}
if (vm->flags & XE_VM_FLAG_LR_MODE) {
INIT_WORK(&vm->preempt.rebind_work, preempt_rebind_work_func);
if (vm->flags & XE_VM_FLAG_LR_MODE)
vm->batch_invalidate_tlb = false;
}
/* Fill pt_root after allocating scratch tables */
for_each_tile(tile, xe, id) {
@@ -1809,10 +1823,10 @@ struct xe_vm *xe_vm_create(struct xe_device *xe, u32 flags, struct xe_file *xef)
return ERR_PTR(err);
err_svm_fini:
if (flags & XE_VM_FLAG_FAULT_MODE) {
vm->size = 0; /* close the vm */
xe_svm_fini(vm);
}
vm->size = 0; /* close the vm */
if (flags & XE_VM_FLAG_FAULT_MODE)
xe_svm_close(vm);
xe_svm_fini(vm);
err_no_resv:
mutex_destroy(&vm->snap_mutex);
for_each_tile(tile, xe, id)
@@ -3255,11 +3269,26 @@ static int op_lock_and_prep(struct drm_exec *exec, struct xe_vm *vm,
.request_decompress = false,
.check_purged = true,
});
if (!err && !xe_vma_has_no_bo(vma))
err = xe_bo_migrate(xe_vma_bo(vma),
region_to_mem_type[region],
NULL,
exec);
if (!err && !xe_vma_has_no_bo(vma)) {
struct xe_bo *bo = xe_vma_bo(vma);
u32 mem_type;
if (region == DRM_XE_CONSULT_MEM_ADVISE_PREF_LOC) {
unsigned int i;
mem_type = XE_PL_TT;
for (i = 0; i < bo->placement.num_placement; i++) {
if (mem_type_is_vram(bo->placements[i].mem_type)) {
mem_type = bo->placements[i].mem_type;
break;
}
}
} else {
mem_type = region_to_mem_type[region];
}
err = xe_bo_migrate(bo, mem_type, NULL, exec);
}
break;
}
default:

View File

@@ -657,7 +657,7 @@ int xe_vm_madvise_ioctl(struct drm_device *dev, void *data, struct drm_file *fil
xe_device_is_l2_flush_optimized(xe) &&
(pat_index != 19 && coh_mode != XE_COH_2WAY))) {
err = -EINVAL;
goto madv_fini;
goto free_vmas;
}
}

View File

@@ -66,3 +66,9 @@
14025883347 MEDIA_VERSION_RANGE(1301, 3503)
GRAPHICS_VERSION_RANGE(2004, 3005)
16029380221 MEDIA_VERSION(3500)
22022079272 MEDIA_VERSION(3503)
GRAPHICS_VERSION(3510)
GRAPHICS_VERSION(3511)
16029897822 MEDIA_VERSION(3500)
GRAPHICS_VERSION(3510)
14027054324 GRAPHICS_VERSION(3511)

View File

@@ -49,9 +49,9 @@
*/
/* Default WOPCM size is 2MB from Gen11, 1MB on previous platforms */
/* FIXME: Larger size require for 2 tile PVC, do a proper probe sooner or later */
/* FIXME: Larger size require for some platforms, do a proper probe sooner or later */
#define DGFX_WOPCM_SIZE SZ_4M
/* FIXME: Larger size require for MTL, do a proper probe sooner or later */
#define LNL_WOPCM_SIZE SZ_8M
#define MTL_WOPCM_SIZE SZ_4M
#define WOPCM_SIZE SZ_2M
@@ -179,9 +179,14 @@ static int __wopcm_init_regs(struct xe_device *xe, struct xe_gt *gt,
u32 xe_wopcm_size(struct xe_device *xe)
{
return IS_DGFX(xe) ? DGFX_WOPCM_SIZE :
xe->info.platform == XE_METEORLAKE ? MTL_WOPCM_SIZE :
WOPCM_SIZE;
if (xe->info.platform >= XE_LUNARLAKE)
return LNL_WOPCM_SIZE;
else if (IS_DGFX(xe))
return DGFX_WOPCM_SIZE;
else if (xe->info.platform == XE_METEORLAKE)
return MTL_WOPCM_SIZE;
else
return WOPCM_SIZE;
}
static u32 max_wopcm_size(struct xe_device *xe)

View File

@@ -2537,21 +2537,21 @@ struct drm_xe_exec_queue_set_property {
* Refer to Documentation/netlink/specs/drm_ras.yaml for complete interface specification.
*
* Node Registration
* =================
* -----------------
*
* The driver registers DRM RAS nodes for each error severity level.
* enum drm_xe_ras_error_severity defines the node-id, while DRM_XE_RAS_ERROR_SEVERITY_NAMES maps
* node-id to node-name.
*
* Error Classification
* ====================
* --------------------
*
* Each node contains a list of error counters. Each error is identified by a error-id and
* an error-name. enum drm_xe_ras_error_component defines the error-id, while
* DRM_XE_RAS_ERROR_COMPONENT_NAMES maps error-id to error-name.
*
* User Interface
* ==============
* --------------
*
* To retrieve error values of a error counter, userspace applications should
* follow the below steps: