mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 13:23:02 -04:00
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:
30
Documentation/ABI/testing/sysfs-driver-intel-xe-ras
Normal file
30
Documentation/ABI/testing/sysfs-driver-intel-xe-ras
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
27
drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h
Normal file
27
drivers/gpu/drm/xe/abi/xe_driver_klvs_abi.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
429
drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c
Normal file
429
drivers/gpu/drm/xe/tests/xe_guc_klv_helpers_kunit.c
Normal 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);
|
||||
@@ -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");
|
||||
|
||||
@@ -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__) \
|
||||
|
||||
89
drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c
Normal file
89
drivers/gpu/drm/xe/tests/xe_sriov_packet_kunit.c
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 = >_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, >_reset_failure);
|
||||
xe_fault_inject_debugfs_register(xe, root);
|
||||
|
||||
if (IS_SRIOV_PF(xe))
|
||||
xe_sriov_pf_debugfs_register(xe, root);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(>->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_ENABLE);
|
||||
ret = xe_mmio_wait32(>->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK,
|
||||
ACK_EUSTALL_ENABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false);
|
||||
if (ret) {
|
||||
xe_gt_err(gt, "Timeout polling for EU stall enable ACK from firmware\n");
|
||||
xe_force_wake_put(gt_to_fw(gt), stream->fw_ref);
|
||||
xe_pm_runtime_put(gt_to_xe(gt));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (XE_GT_WA(gt, 22016596838))
|
||||
xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2,
|
||||
REG_MASKED_FIELD_ENABLE(DISABLE_DOP_GATING));
|
||||
@@ -730,7 +752,7 @@ static int xe_eu_stall_stream_enable(struct xe_eu_stall_data_stream *stream)
|
||||
reg_value |= XEHPC_EUSTALL_BASE_ENABLE_SAMPLING;
|
||||
xe_gt_mcr_multicast_write(gt, XEHPC_EUSTALL_BASE, reg_value);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void eu_stall_data_buf_poll_work_fn(struct work_struct *work)
|
||||
@@ -840,6 +862,7 @@ static int xe_eu_stall_enable_locked(struct xe_eu_stall_data_stream *stream)
|
||||
static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream)
|
||||
{
|
||||
struct xe_gt *gt = stream->gt;
|
||||
int ret = 0;
|
||||
|
||||
if (!stream->enabled)
|
||||
return 0;
|
||||
@@ -853,11 +876,19 @@ static int xe_eu_stall_disable_locked(struct xe_eu_stall_data_stream *stream)
|
||||
if (XE_GT_WA(gt, 22016596838))
|
||||
xe_gt_mcr_multicast_write(gt, ROW_CHICKEN2,
|
||||
REG_MASKED_FIELD_DISABLE(DISABLE_DOP_GATING));
|
||||
if (XE_GT_WA(gt, 14027054324)) {
|
||||
/* Request the firmware to revert the workaround and wait for an ACK */
|
||||
xe_mmio_write32(>->mmio, SWF_SCRATCHPAD(0), REQ_EUSTALL_DISABLE);
|
||||
ret = xe_mmio_wait32(>->mmio, SWF_SCRATCHPAD(0), SWF_EUSTALL_MASK,
|
||||
ACK_EUSTALL_DISABLE, FW_WA_WAIT_TIMEOUT_US, NULL, false);
|
||||
if (ret)
|
||||
xe_gt_err(gt, "Timeout polling for EU stall disable ACK from firmware\n");
|
||||
}
|
||||
|
||||
xe_force_wake_put(gt_to_fw(gt), stream->fw_ref);
|
||||
xe_pm_runtime_put(gt_to_xe(gt));
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long xe_eu_stall_stream_ioctl_locked(struct xe_eu_stall_data_stream *stream,
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "regs/xe_gt_regs.h"
|
||||
#include "xe_assert.h"
|
||||
#include "xe_bb.h"
|
||||
#include "xe_debugfs.h"
|
||||
#include "xe_device.h"
|
||||
#include "xe_eu_stall.h"
|
||||
#include "xe_exec_queue.h"
|
||||
@@ -926,7 +927,7 @@ static void gt_reset_worker(struct work_struct *w)
|
||||
|
||||
xe_gt_info(gt, "reset started\n");
|
||||
|
||||
if (xe_fault_inject_gt_reset()) {
|
||||
if (xe_fault_gt_reset()) {
|
||||
err = -ECANCELED;
|
||||
goto err_fail;
|
||||
}
|
||||
@@ -986,7 +987,7 @@ void xe_gt_reset_async(struct xe_gt *gt)
|
||||
return;
|
||||
|
||||
/* Don't do a reset while one is already in flight */
|
||||
if (!xe_fault_inject_gt_reset() && xe_uc_reset_prepare(>->uc))
|
||||
if (!xe_fault_gt_reset() && xe_uc_reset_prepare(>->uc))
|
||||
return;
|
||||
|
||||
xe_gt_info(gt, "reset queued from %ps\n", __builtin_return_address(0));
|
||||
|
||||
@@ -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(>_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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -658,6 +658,33 @@ static int vf_cache_sched_groups_status(struct xe_gt *gt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vf_cache_num_paging_engines(struct xe_gt *gt)
|
||||
{
|
||||
struct xe_guc *guc = >->uc.guc;
|
||||
struct xe_uc_fw_version guc_version;
|
||||
u32 value = 0;
|
||||
int err;
|
||||
|
||||
xe_gt_sriov_vf_guc_versions(gt, NULL, &guc_version);
|
||||
|
||||
if (MAKE_GUC_VER_STRUCT(guc_version) < MAKE_GUC_VER(1, 36, 0))
|
||||
return 0;
|
||||
|
||||
err = guc_action_query_single_klv32(guc, GUC_KLV_GLOBAL_CFG_NUM_PAGING_ENGINE_INSTANCES_KEY,
|
||||
&value);
|
||||
if (unlikely(err)) {
|
||||
xe_gt_sriov_err(gt,
|
||||
"Failed to obtain the number of paging instances (%pe)\n",
|
||||
ERR_PTR(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
gt->sriov.vf.runtime.num_paging_engine_instances = value;
|
||||
|
||||
xe_gt_sriov_dbg(gt, "num_paging_engines %u\n", value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_gt_sriov_vf_query_config - Query SR-IOV config data over MMIO.
|
||||
* @gt: the &xe_gt
|
||||
@@ -694,6 +721,10 @@ int xe_gt_sriov_vf_query_config(struct xe_gt *gt)
|
||||
if (has_gmdid(xe))
|
||||
vf_cache_gmdid(gt);
|
||||
|
||||
err = vf_cache_num_paging_engines(gt);
|
||||
if (unlikely(err))
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -731,6 +762,22 @@ u16 xe_gt_sriov_vf_guc_ids(struct xe_gt *gt)
|
||||
return gt->sriov.vf.self_config.num_ctxs;
|
||||
}
|
||||
|
||||
/**
|
||||
* xe_gt_sriov_vf_paging_engines - Return the number of paging engine instances
|
||||
* @gt: the &xe_gt
|
||||
*
|
||||
* This function is for VF use only.
|
||||
*
|
||||
* Return: number of GuC paging engine instances configured by the PF.
|
||||
*/
|
||||
u32 xe_gt_sriov_vf_paging_engines(struct xe_gt *gt)
|
||||
{
|
||||
xe_gt_assert(gt, IS_SRIOV_VF(gt_to_xe(gt)));
|
||||
xe_gt_assert(gt, gt->sriov.vf.guc_version.major);
|
||||
|
||||
return gt->sriov.vf.runtime.num_paging_engine_instances;
|
||||
}
|
||||
|
||||
static int relay_action_handshake(struct xe_gt *gt, u32 *major, u32 *minor)
|
||||
{
|
||||
u32 request[VF2PF_HANDSHAKE_REQUEST_MSG_LEN] = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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(>->uc.guc, 70, 62))
|
||||
guc_waklv_enable(ads, NULL, 0, &offset, &remain, GUC_WA_KLV_REMAP_RANGED_TLB_INV);
|
||||
|
||||
/* The GuC does not enable the sem_tok_64 feature on NVL-S */
|
||||
if (XE_GT_WA(gt, 16029897822) && gt_to_xe(gt)->info.platform != XE_NOVALAKE_S &&
|
||||
GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 69))
|
||||
guc_waklv_enable(ads, NULL, 0, &offset, &remain,
|
||||
GUC_WA_KLV_IGNORE_MMIO_READ_SEM_TOKEN_64);
|
||||
|
||||
/*
|
||||
* On GuC firmware 70.66 and above, use the Feature KLV (shared with the
|
||||
* WA KLV buffer); older firmware uses GUC_CTL_DISABLE_MULTI_QUEUE in
|
||||
* the init params instead.
|
||||
*/
|
||||
if (!xe_configfs_get_enable_multi_queue(to_pci_dev(gt_to_xe(gt)->drm.dev)) &&
|
||||
GUC_FIRMWARE_VER_AT_LEAST(>->uc.guc, 70, 66)) {
|
||||
u32 data = 1;
|
||||
|
||||
guc_waklv_enable(ads, &data, 1, &offset, &remain,
|
||||
GUC_FEATURE_KLV_DISABLE_MULTI_QUEUE);
|
||||
}
|
||||
|
||||
size = guc_ads_waklv_size(ads) - remain;
|
||||
if (!size)
|
||||
return;
|
||||
@@ -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]))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
0x4800, 0x4804, \
|
||||
0x4848, 0x484c)
|
||||
#define _PAT_PTA 0x4820
|
||||
#define _PAT_TR_PTA 0x48cc
|
||||
|
||||
#define XE2_NO_PROMOTE REG_BIT(10)
|
||||
#define XE2_COMP_EN REG_BIT(9)
|
||||
@@ -256,6 +257,7 @@ static const struct xe_pat_table_entry xe3p_xpc_pat_table[] = {
|
||||
|
||||
static const struct xe_pat_table_entry xe3p_primary_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 3);
|
||||
static const struct xe_pat_table_entry xe3p_media_pat_pta = XE2_PAT(0, 0, 0, 0, 0, 2);
|
||||
static const struct xe_pat_table_entry xe3p_pat_tr_pta = XE2_PAT(0, 0, 0, 0, 0, 0);
|
||||
|
||||
static const struct xe_pat_table_entry xe3p_lpg_pat_table[] = {
|
||||
[ 0] = XE2_PAT( 0, 0, 0, 0, 3, 0 ),
|
||||
@@ -325,11 +327,26 @@ static const struct xe_pat_table_entry *gt_pta_entry(struct xe_gt *gt)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct xe_pat_table_entry *gt_tr_pta_entry(struct xe_gt *gt)
|
||||
{
|
||||
struct xe_device *xe = gt_to_xe(gt);
|
||||
|
||||
if (xe_gt_is_main_type(gt))
|
||||
return xe->pat.pat_primary_tr_pta;
|
||||
|
||||
if (xe_gt_is_media_type(gt))
|
||||
return xe->pat.pat_media_tr_pta;
|
||||
|
||||
xe_assert(xe, false);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[],
|
||||
int n_entries)
|
||||
{
|
||||
struct xe_device *xe = gt_to_xe(gt);
|
||||
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
|
||||
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
|
||||
|
||||
for (int i = 0; i < n_entries; i++) {
|
||||
struct xe_reg reg = XE_REG(_PAT_INDEX(i));
|
||||
@@ -342,6 +359,9 @@ static void program_pat(struct xe_gt *gt, const struct xe_pat_table_entry table[
|
||||
|
||||
if (pta_entry)
|
||||
xe_mmio_write32(>->mmio, XE_REG(_PAT_PTA), pta_entry->value);
|
||||
|
||||
if (tr_pta_entry)
|
||||
xe_mmio_write32(>->mmio, XE_REG(_PAT_TR_PTA), tr_pta_entry->value);
|
||||
}
|
||||
|
||||
static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry table[],
|
||||
@@ -349,6 +369,7 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta
|
||||
{
|
||||
struct xe_device *xe = gt_to_xe(gt);
|
||||
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
|
||||
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
|
||||
|
||||
for (int i = 0; i < n_entries; i++) {
|
||||
struct xe_reg_mcr reg_mcr = XE_REG_MCR(_PAT_INDEX(i));
|
||||
@@ -361,6 +382,9 @@ static void program_pat_mcr(struct xe_gt *gt, const struct xe_pat_table_entry ta
|
||||
|
||||
if (pta_entry)
|
||||
xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_PTA), pta_entry->value);
|
||||
|
||||
if (tr_pta_entry)
|
||||
xe_gt_mcr_multicast_write(gt, XE_REG_MCR(_PAT_TR_PTA), tr_pta_entry->value);
|
||||
}
|
||||
|
||||
static int xelp_dump(struct xe_gt *gt, struct drm_printer *p)
|
||||
@@ -531,6 +555,16 @@ static int xe2_dump(struct xe_gt *gt, struct drm_printer *p)
|
||||
drm_printf(p, "Page Table Access:\n");
|
||||
xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false);
|
||||
|
||||
if (gt_tr_pta_entry(gt)) {
|
||||
if (xe_gt_is_media_type(gt))
|
||||
pat = xe_mmio_read32(>->mmio, XE_REG(_PAT_TR_PTA));
|
||||
else
|
||||
pat = xe_gt_mcr_unicast_read_any(gt, XE_REG_MCR(_PAT_TR_PTA));
|
||||
|
||||
drm_printf(p, "TRTT Page Table Access:\n");
|
||||
xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false);
|
||||
}
|
||||
|
||||
if (xe_gt_is_media_type(gt))
|
||||
pat = xe_mmio_read32(>->mmio, XE_REG(_PAT_ATS));
|
||||
else
|
||||
@@ -577,6 +611,8 @@ void xe_pat_init_early(struct xe_device *xe)
|
||||
if (!IS_DGFX(xe)) {
|
||||
xe->pat.pat_primary_pta = &xe3p_primary_pat_pta;
|
||||
xe->pat.pat_media_pta = &xe3p_media_pat_pta;
|
||||
xe->pat.pat_primary_tr_pta = &xe3p_pat_tr_pta;
|
||||
xe->pat.pat_media_tr_pta = &xe3p_pat_tr_pta;
|
||||
}
|
||||
xe->pat.n_entries = ARRAY_SIZE(xe3p_lpg_pat_table);
|
||||
xe->pat.idx[XE_CACHE_NONE] = 3;
|
||||
@@ -701,6 +737,7 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p)
|
||||
{
|
||||
struct xe_device *xe = gt_to_xe(gt);
|
||||
const struct xe_pat_table_entry *pta_entry = gt_pta_entry(gt);
|
||||
const struct xe_pat_table_entry *tr_pta_entry = gt_tr_pta_entry(gt);
|
||||
char label[PAT_LABEL_LEN];
|
||||
|
||||
if (!xe->pat.table || !xe->pat.n_entries)
|
||||
@@ -731,6 +768,13 @@ int xe_pat_dump_sw_config(struct xe_gt *gt, struct drm_printer *p)
|
||||
xe->pat.ops->entry_dump(p, "PTA_MODE", pat, false);
|
||||
}
|
||||
|
||||
if (tr_pta_entry) {
|
||||
u32 pat = tr_pta_entry->value;
|
||||
|
||||
drm_printf(p, "TRTT Page Table Access:\n");
|
||||
xe->pat.ops->entry_dump(p, "TR_PTA_MODE", pat, false);
|
||||
}
|
||||
|
||||
if (xe->pat.pat_ats) {
|
||||
u32 pat = xe->pat.pat_ats->value;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) \
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user