From: Tejas Upadhyay <tejas.upadhyay@intel.com>
To: intel-xe@lists.freedesktop.org
Cc: himal.prasad.ghimiray@intel.com, rodrigo.vivi@intel.com,
Matthew Brost <matthew.brost@intel.com>,
Tejas Upadhyay <tejas.upadhyay@intel.com>
Subject: [PATCH V20 09/15] drm/xe/vram: Add VRAM page offline fault handler
Date: Wed, 2 Sep 2026 20:23:52 +0530 [thread overview]
Message-ID: <20260902145343.465686-26-tejas.upadhyay@intel.com> (raw)
In-Reply-To: <20260902145343.465686-17-tejas.upadhyay@intel.com>
Add the core VRAM page offlining logic to handle HW-reported faulty
physical addresses:
- xe_ttm_vram_purge_page(): Purges the BO containing the faulty
address. Bans the associated VM (if page table BO) and exec queue
(if LRC BO). Moves xe_exec_queue_kill() outside xe_bo_lock() to
avoid AB-BA deadlock with vm->lock. Uses READ_ONCE(bo->q) to
safely access the exec queue pointer.
- xe_ttm_vram_page_already_processed(): Checks if an address is
already tracked in offlined_pages or queued_pages lists to avoid
double-processing.
- xe_ttm_vram_reserve_page_at_addr(): Two-phase reservation that
first queues the page, purges the BO outside the lock, then
reserves the buddy block. Handles both allocated (BO present)
and free page cases. Returns -EIO for critical kernel BOs to
trigger system reset.
- xe_ttm_vram_addr_to_region(): Maps a DPA to its corresponding
VRAM region. Checks if the address falls within usable space,
or infrastructure zones (CCS, GSM, DSM) where it returns NULL
to flag a reset path. If the target address is outside any
known region returns ERR_PTR(-EOPNOTSUPP)
- xe_ttm_vram_handle_addr_fault(): Entry point called by RAS.
Returns -EEXIST if already processed, -EIO for GSM/critical BO,
-EOPNOTSUPP if out of bounds.
v14(Sashiko/MattB):
- Retry queued pages during xe_ttm_vram_mgr_del()
- Move all changes to find owner of bo to its API
v13(Himal):
- Remove redundant code
v12(Sashiko):
- Handle multi tile and add assert for 4K align
- Remove unaligned action comment for ENXIO
v11(Himal):
- match everywhere with enum vs bool for status member
- Fix comment and remove unused var
- if purge fail let next alloc confirm failure
- pass absolute address, useful for multi tile
Co-authored-by: Copilot
Reviewed-by: Himal Prasad Ghimiray <himal.prasad.ghimiray@intel.com>
Signed-off-by: Tejas Upadhyay <tejas.upadhyay@intel.com>
---
drivers/gpu/drm/xe/xe_ttm_vram_mgr.c | 330 +++++++++++++++++++++++++++
drivers/gpu/drm/xe/xe_ttm_vram_mgr.h | 1 +
2 files changed, 331 insertions(+)
diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
index d97739a84a2d..0ab92bc3c699 100644
--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
@@ -13,10 +13,16 @@
#include <drm/ttm/ttm_placement.h>
#include <drm/ttm/ttm_range_manager.h>
+#include "regs/xe_regs.h"
#include "xe_bo.h"
+#include "xe_configfs.h"
#include "xe_device.h"
+#include "xe_exec_queue.h"
+#include "xe_lrc.h"
+#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_res_cursor.h"
+#include "xe_ttm_stolen_mgr.h"
#include "xe_ttm_vram_mgr.h"
#include "xe_vram_types.h"
@@ -208,6 +214,36 @@ static void xe_ttm_vram_buddy_free(struct xe_ttm_vram_mgr *mgr,
mgr->visible_avail += used_visible;
}
+/*
+ * Retry pending page-offline reservations.
+ *
+ * A reservation can fail because the blocks backing the bad page are still
+ * allocated: either the owning BO could not be purged, or the purge was
+ * pipelined and TTM handed the resource to a ghost object which frees it
+ * only once the move fences signal. Rather than giving up, entries stay on
+ * @queued_pages and are retried here every time VRAM blocks come back.
+ *
+ * Called with @mgr->lock held.
+ */
+static void xe_ttm_vram_retry_queued_pages(struct xe_ttm_vram_mgr *mgr)
+{
+ struct xe_ttm_vram_offline_resource *pos, *n;
+
+ lockdep_assert_held(&mgr->lock);
+
+ list_for_each_entry_safe(pos, n, &mgr->queued_pages, queued_link) {
+ if (xe_ttm_vram_buddy_alloc(mgr, pos->addr, pos->addr + SZ_4K,
+ SZ_4K, SZ_4K, &pos->blocks,
+ GPU_BUDDY_RANGE_ALLOCATION, NULL,
+ &pos->used_visible_size))
+ continue;
+ --mgr->n_queued_pages;
+ list_del_rcu(&pos->queued_link);
+ ++mgr->n_offlined_pages;
+ list_add_rcu(&pos->offlined_link, &mgr->offlined_pages);
+ }
+}
+
static void xe_ttm_vram_mgr_del(struct ttm_resource_manager *man,
struct ttm_resource *res)
{
@@ -217,6 +253,8 @@ static void xe_ttm_vram_mgr_del(struct ttm_resource_manager *man,
mutex_lock(&mgr->lock);
xe_ttm_vram_buddy_free(mgr, &vres->blocks, vres->used_visible_size);
+ if (unlikely(!list_empty(&mgr->queued_pages)))
+ xe_ttm_vram_retry_queued_pages(mgr);
mutex_unlock(&mgr->lock);
ttm_resource_fini(man, res);
@@ -580,3 +618,295 @@ u64 xe_ttm_vram_get_avail(struct ttm_resource_manager *man)
return avail;
}
+
+static int xe_ttm_vram_purge_page(struct xe_device *xe, struct xe_bo *bo)
+{
+ struct ttm_operation_ctx ctx = {};
+ struct xe_exec_queue *q_to_put = NULL;
+ struct xe_exec_queue *q = NULL;
+ struct xe_vm *vm = NULL;
+ u32 flags;
+ int ret = 0;
+
+ xe_bo_lock(bo, false);
+ if (bo->vm)
+ vm = xe_vm_get(bo->vm);
+ flags = bo->flags;
+ xe_bo_unlock(bo);
+ /* Ban VM if BO is PPGTT */
+ if (vm && (flags & XE_BO_FLAG_PAGETABLE)) {
+ down_write(&vm->lock);
+ xe_vm_kill(vm, true);
+ up_write(&vm->lock);
+ }
+ if (vm)
+ xe_vm_put(vm);
+
+ xe_bo_lock(bo, false);
+ q = READ_ONCE(bo->q);
+ /* Ban exec queue if BO is lrc */
+ if (q && xe_exec_queue_get_unless_zero(q)) {
+ /* ban queue */
+ q_to_put = q;
+ }
+
+ if (bo->purgeable.state == XE_MADV_PURGEABLE_PURGED) {
+ /* Already purged by shrinker during unlocked window — nothing to do */
+ xe_bo_unlock(bo);
+ goto out;
+ }
+
+ xe_bo_set_purgeable_state(bo, XE_MADV_PURGEABLE_DONTNEED);
+ ttm_bo_unmap_virtual(&bo->ttm); /* nuke CPU mmap + VRAM IO mappings */
+ if (xe_bo_is_pinned(bo))
+ xe_bo_unpin(bo);
+ ret = xe_ttm_bo_purge(&bo->ttm, &ctx);
+ xe_bo_unlock(bo);
+
+out:
+ if (q_to_put) {
+ xe_exec_queue_kill(q_to_put);
+ xe_exec_queue_put(q_to_put);
+ }
+
+ return ret;
+}
+
+static bool xe_ttm_vram_page_already_processed(struct xe_ttm_vram_mgr *mgr,
+ u64 addr)
+{
+ struct xe_ttm_vram_offline_resource *pos;
+
+ lockdep_assert_held(&mgr->lock);
+
+ list_for_each_entry(pos, &mgr->offlined_pages, offlined_link) {
+ if (pos->addr == addr)
+ return true;
+ }
+
+ list_for_each_entry(pos, &mgr->queued_pages, queued_link) {
+ if (pos->addr == addr)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Resolve the BO currently owning @block and take a reference on it.
+ *
+ * Called with @mgr->lock held, which serializes against
+ * xe_ttm_vram_buddy_free() clearing block->private.
+ *
+ * Returns NULL when there is no xe_bo we can act on: either the block is
+ * free, or the resource is temporarily owned by a TTM ghost object because
+ * a move or a pipelined gutting is still in flight. In both cases the
+ * blocks will hit xe_ttm_vram_mgr_del() on their own and the pending
+ * reservation is retried from there.
+ */
+static struct xe_bo *xe_ttm_vram_block_owner_get(struct gpu_buddy_block *block)
+{
+ struct ttm_resource *res = block->private;
+ struct ttm_buffer_object *tbo;
+ struct xe_bo *bo;
+
+ if (!res)
+ return NULL;
+
+ /*
+ * res->bo is updated under bdev->lru_lock by ttm_resource_set_bo().
+ * Racing with a ghost transfer here is benign: we either see the old
+ * owner (whose purge is a no-op and the retry path recovers) or the
+ * ghost (rejected below).
+ *
+ * A ghost is a bare ttm_transfer_obj, not an xe_bo, so ttm_to_xe_bo()
+ * on one would be out of bounds. xe_bo_is_xe_bo() rejects it since
+ * only our own BOs carry xe_ttm_bo_destroy().
+ */
+ tbo = READ_ONCE(res->bo);
+ if (!tbo || !xe_bo_is_xe_bo(tbo))
+ return NULL;
+
+ bo = ttm_to_xe_bo(tbo);
+
+ /* The BO may already be in teardown with a zero refcount */
+ return xe_bo_get_unless_zero(bo) ? bo : NULL;
+}
+
+static int xe_ttm_vram_reserve_page_at_addr(struct xe_device *xe, u64 addr,
+ struct xe_ttm_vram_mgr *vram_mgr, struct gpu_buddy *mm)
+{
+ struct xe_ttm_vram_offline_resource *pos, *n;
+ struct xe_ttm_vram_offline_resource *nentry;
+ struct xe_bo *pbo_to_put = NULL;
+ struct xe_bo *pbo = NULL;
+ struct gpu_buddy_block *block;
+ u64 size = SZ_4K;
+ int ret = 0;
+
+ scoped_guard(mutex, &vram_mgr->lock) {
+ if (xe_ttm_vram_page_already_processed(vram_mgr, addr))
+ return -EEXIST;
+ block = gpu_buddy_allocated_addr_to_block(mm, addr);
+ if (WARN_ON(IS_ERR(block)))
+ return PTR_ERR(block);
+
+ nentry = kzalloc_obj(*nentry);
+ if (!nentry)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&nentry->blocks);
+ nentry->status = XE_PAGE_RESERVE_PENDING;
+ nentry->addr = addr;
+
+ if (block) {
+ pbo = xe_ttm_vram_block_owner_get(block);
+
+ /*
+ * Critical kernel BO? Best-effort check without resv lock;
+ * worst case a concurrent pin causes reset path unnecessarily.
+ */
+ if (pbo && ((pbo->ttm.type == ttm_bo_type_kernel &&
+ !(pbo->flags & XE_BO_FLAG_PINNED_LATE_RESTORE)) ||
+ (xe_bo_is_user(pbo) && xe_bo_is_pinned(pbo)))) {
+ kfree(nentry);
+ pbo_to_put = pbo;
+ drm_err(&xe->drm,
+ "%s: addr: 0x%llx is critical kernel bo, requesting SBR\n",
+ __func__, addr);
+ break;
+ }
+ }
+ /* Queue both free and occupied (to-be-purged) pages */
+ ++vram_mgr->n_queued_pages;
+ list_add_rcu(&nentry->queued_link, &vram_mgr->queued_pages);
+ }
+
+ /* Deferred put outside lock to avoid recursive deadlock */
+ if (pbo_to_put) {
+ xe_bo_put(pbo_to_put);
+ /* Hint System controller driver for reset with -EIO */
+ return -EIO;
+ }
+
+ if (pbo) {
+ /*
+ * Purge BO containing address - reference held from above.
+ * This does not necessarily free the blocks synchronously: if
+ * the BO is not idle, ttm_bo_pipeline_gutting() hands the
+ * resource to a ghost object and it is released only once the
+ * move fences signal. The reservation below then fails and is
+ * retried from xe_ttm_vram_mgr_del().
+ */
+ ret = xe_ttm_vram_purge_page(xe, pbo);
+ xe_bo_put(pbo);
+ if (ret)
+ drm_warn(&xe->drm, "Purge failed at addr:0x%llx, ret:%d\n", addr, ret);
+ }
+
+ scoped_guard(mutex, &vram_mgr->lock) {
+ ret = xe_ttm_vram_buddy_alloc(vram_mgr, addr, addr + size,
+ size, size, &nentry->blocks,
+ GPU_BUDDY_RANGE_ALLOCATION,
+ NULL, &nentry->used_visible_size);
+ if (ret) {
+ nentry->status = XE_PAGE_RESERVE_FAIL;
+ drm_dbg(&xe->drm,
+ "Page at addr:0x%llx still busy (%d), deferring reservation\n",
+ addr, ret);
+ return 0;
+ }
+
+ list_for_each_entry_safe(pos, n, &vram_mgr->queued_pages, queued_link) {
+ if (pos->addr == nentry->addr) {
+ --vram_mgr->n_queued_pages;
+ list_del_rcu(&pos->queued_link);
+ break;
+ }
+ }
+ ++vram_mgr->n_offlined_pages;
+ list_add_rcu(&nentry->offlined_link, &vram_mgr->offlined_pages);
+ /* RAS will send command to FW for offlining page based on ret value */
+ }
+ /* Success */
+ return ret;
+}
+
+static struct xe_vram_region *xe_ttm_vram_addr_to_region(struct xe_device *xe, u64 addr)
+{
+ struct xe_tile *tile;
+ u8 id;
+
+ for_each_tile(tile, xe, id) {
+ struct xe_vram_region *vr = tile->mem.vram;
+
+ if (!vr)
+ continue;
+
+ if (addr >= vr->dpa_base && addr < (vr->dpa_base + vr->usable_size))
+ return vr;
+
+ /* CCS, GSM, or DSM — infrastructure zone, needs reset */
+ if (addr >= (vr->dpa_base + vr->usable_size) &&
+ addr < (vr->dpa_base + vr->actual_physical_size))
+ return NULL;
+ }
+
+ /*
+ * Return an explicit error pointer so the caller knows the addr
+ * is invalid and should be ignored, NOT SBR.
+ */
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+/**
+ * xe_ttm_vram_handle_addr_fault - Handle vram physical address error flaged
+ * @xe: pointer to parent device
+ * @addr: physical faulty address
+ *
+ * Handle the physcial faulty address error on specific tile.
+ *
+ * Returns 0 for success, negative error code otherwise as follow:
+ * * %-EIO - critical BO or address outside any VRAM region; next action is reset.
+ * * %-EOPNOTSUPP - log-only policy or unknown address; no further action.
+ * * %-ENOMEM - allocation failure; next action is reset.
+ * * %-ENXIO - address not found in buddy; no further action.
+ * * %-EEXIST - address already processed; no further action.
+ *
+ * A return of 0 means the page is tracked. It may still be listed as
+ * pending if the blocks backing it could not be freed immediately; the
+ * reservation is then completed from xe_ttm_vram_mgr_del().
+ */
+int xe_ttm_vram_handle_addr_fault(struct xe_device *xe, u64 addr)
+{
+ struct xe_ttm_vram_mgr *vram_mgr;
+ struct xe_vram_region *vr;
+ struct gpu_buddy *mm;
+
+ /* Assert that the address is 4K aligned */
+ if (WARN_ON_ONCE(!IS_ALIGNED(addr, SZ_4K))) {
+ drm_err(&xe->drm, "Address %llx is not 4K aligned!\n", addr);
+ return -EINVAL;
+ }
+
+ vr = xe_ttm_vram_addr_to_region(xe, addr);
+ if (IS_ERR(vr)) {
+ /*
+ * The addr is outside VRAM and GSM.
+ * Log a debug message if needed, and safely exit/ignore.
+ */
+ drm_dbg(&xe->drm, "Address %llx is out of bounds, ignoring fault.\n", addr);
+ return PTR_ERR(vr);
+ }
+ if (!vr) {
+ drm_err(&xe->drm, "%s:%d GSM addr:%llx error requesting SBR\n",
+ __func__, __LINE__, addr);
+ /* Hint System controller driver for reset with -EIO */
+ return -EIO;
+ }
+ vram_mgr = &vr->ttm;
+ mm = &vram_mgr->mm;
+
+ /* Reserve page at address */
+ return xe_ttm_vram_reserve_page_at_addr(xe, addr - vr->dpa_base, vram_mgr, mm);
+}
+EXPORT_SYMBOL(xe_ttm_vram_handle_addr_fault);
diff --git a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.h b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.h
index 87b7fae5edba..d5392beff30c 100644
--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.h
+++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.h
@@ -31,6 +31,7 @@ u64 xe_ttm_vram_get_cpu_visible_size(struct ttm_resource_manager *man);
void xe_ttm_vram_get_used(struct ttm_resource_manager *man,
u64 *used, u64 *used_visible);
+int xe_ttm_vram_handle_addr_fault(struct xe_device *xe, u64 addr);
static inline struct xe_ttm_vram_mgr_resource *
to_xe_ttm_vram_mgr_resource(struct ttm_resource *res)
{
--
2.52.0
next prev parent reply other threads:[~2026-09-02 14:55 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 14:53 [PATCH V20 00/15] Add memory page offlining support Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 01/15] drm/xe: Link VRAM object with gpu buddy Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 02/15] drm/xe: Link LRC BO and its execution queue with safe lifetime rules Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 03/15] drm/xe: Export xe_ttm_bo_purge() Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 04/15] drm/xe: Handle NULL resource and allow purging of VRAM pages Tejas Upadhyay
2026-09-02 15:28 ` sashiko-bot
2026-09-02 14:53 ` [PATCH V20 05/15] drm/xe/bo: Make xe_bo_is_user() public Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 06/15] drm/xe: Guard teardown paths against purged BOs Tejas Upadhyay
2026-09-02 15:57 ` sashiko-bot
2026-09-02 17:52 ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 07/15] drm/xe/vram: Extract buddy allocation and free helpers Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 08/15] drm/xe/vram: Add page offline data structures and lifecycle Tejas Upadhyay
2026-09-02 14:53 ` Tejas Upadhyay [this message]
2026-09-02 16:25 ` [PATCH V20 09/15] drm/xe/vram: Add VRAM page offline fault handler sashiko-bot
2026-09-02 23:04 ` Matthew Brost
2026-09-03 4:22 ` Matthew Brost
2026-09-03 7:57 ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 10/15] drm/xe/configfs: Add disable_vram_page_offline attribute Tejas Upadhyay
2026-09-02 16:35 ` sashiko-bot
2026-09-02 17:56 ` Upadhyay, Tejas
2026-09-02 14:53 ` [PATCH V20 11/15] drm/xe/ras: Cache disable_vram_page_offline policy at init Tejas Upadhyay
2026-09-02 14:53 ` [PATCH V20 12/15] drm/xe/vram: Check disable_vram_page_offline policy in fault handler Tejas Upadhyay
2026-09-02 16:48 ` sashiko-bot
2026-09-02 17:57 ` Upadhyay, Tejas
2026-09-02 18:31 ` Michal Wajdeczko
2026-09-02 14:53 ` [PATCH V20 13/15] drm/xe: Expose bad VRAM pages via debugfs Tejas Upadhyay
2026-09-02 16:56 ` sashiko-bot
2026-09-02 18:00 ` Upadhyay, Tejas
2026-09-02 23:08 ` Matthew Brost
2026-09-02 18:36 ` Michal Wajdeczko
2026-09-02 14:53 ` [PATCH V20 14/15] drm/xe/uapi: Expose ban reason in EXEC_QUEUE_GET_PROPERTY_BAN Tejas Upadhyay
2026-09-02 17:10 ` sashiko-bot
2026-09-02 23:12 ` Matthew Brost
2026-09-02 14:53 ` [PATCH V20 15/15] drm/xe: Add fault-inject based VRAM page offline injection Tejas Upadhyay
2026-09-02 17:02 ` ✗ CI.checkpatch: warning for Add memory page offlining support (rev24) Patchwork
2026-09-02 17:04 ` ✓ CI.KUnit: success " Patchwork
2026-09-02 17:42 ` ✓ Xe.CI.BAT: " Patchwork
2026-09-03 6:57 ` ✓ Xe.CI.FULL: " Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260902145343.465686-26-tejas.upadhyay@intel.com \
--to=tejas.upadhyay@intel.com \
--cc=himal.prasad.ghimiray@intel.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=matthew.brost@intel.com \
--cc=rodrigo.vivi@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.