From: Francois Dugast <francois.dugast@intel.com>
To: Matthew Brost <matthew.brost@intel.com>
Cc: <intel-xe@lists.freedesktop.org>,
Copilot <223556219+Copilot@users.noreply.github.com>
Subject: Re: [PATCH v8 08/12] drm/xe: Chain page faults via queue-resident cache to avoid fault storms
Date: Wed, 29 Jul 2026 14:31:38 +0200 [thread overview]
Message-ID: <amnyqqDRREq2SJQs@fdugast-desk> (raw)
In-Reply-To: <20260724232601.1753977-9-matthew.brost@intel.com>
Hi,
Patch size makes review a bit longer but I cannot suggest a good way to break
it down.
On Fri, Jul 24, 2026 at 04:25:57PM -0700, Matthew Brost wrote:
> Some Xe platforms can generate pagefault storms where many faults target
> the same address range in a short time window (e.g. many EU threads
> faulting the same page). The current worker/locking model effectively
> serializes faults for a given range and repeatedly performs VMA/range
> lookups for each fault, which creates head-of-queue blocking and wastes
> CPU in the hot path.
>
> Introduce a page fault chaining cache that coalesces faults targeting
> the samr ASID and address range.
s/samr/same/
>
> Each worker tracks the active fault range it is servicing. Fault entries
> reside in stable queue storage, allowing the IRQ handler to match new
> faults against the worker cache and directly chain cache hits onto the
> active entry without allocation or waiting for dequeue. Once the leading
> fault completes, the worker acknowledges the entire chain.
>
> A small allocation state is added to each entry so queue, worker, and
> IRQ pathd can safely reference the same fault object. This prevents
s/pathd/paths/
> reuse while the fault is active and guarantees that chained faults
> remain valid until acknowledged.
>
> Fault handlers also record the serviced range so subsequent faults can
> be acknowledged without re-running the full resolution path.
>
> This removes repeated fault resolution during fault storms and
> significantly improves forward progress in SVM workloads.
>
> Since threaded prefetches now use a dedicated prefetch workqueue
> (usm.prefetch_wq) rather than sharing the page fault workqueue, page
> fault servicing can no longer deadlock on vm->lock, so this cache does
> not need an -EAGAIN retry path for a failed vm->lock acquisition.
>
> Assisted-by: ChatGPT:gpt-5 # Documentation
> Signed-off-by: Matthew Brost <matthew.brost@intel.com>
> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Please remove this "Co-authored-by".
(Already mentioned in patch #3 but repeating here not to forget)
> ---
> drivers/gpu/drm/xe/xe_pagefault.c | 435 +++++++++++++++++++++---
> drivers/gpu/drm/xe/xe_pagefault.h | 71 ++++
> drivers/gpu/drm/xe/xe_pagefault_types.h | 81 +++--
> drivers/gpu/drm/xe/xe_svm.c | 16 +-
> drivers/gpu/drm/xe/xe_svm.h | 9 +-
> 5 files changed, 527 insertions(+), 85 deletions(-)
>
> diff --git a/drivers/gpu/drm/xe/xe_pagefault.c b/drivers/gpu/drm/xe/xe_pagefault.c
> index 65be8dd09e2f..b897698a4cfc 100644
> --- a/drivers/gpu/drm/xe/xe_pagefault.c
> +++ b/drivers/gpu/drm/xe/xe_pagefault.c
> @@ -35,6 +35,70 @@
> * xe_pagefault.c implements the consumer layer.
> */
>
> +/**
> + * DOC: Xe page fault cache
> + *
> + * Some Xe hardware can trigger “fault storms,” which are many page faults to
> + * the same address within a short period of time. An example is many EU threads
> + * faulting on the same page simultaneously. With the current page fault locking
> + * structure, only one page fault for a given address range can be processed at
> + * a time. This causes head-of-queue blocking across workers, killing
> + * parallelism. If the page fault handler must repeatedly look up resources
> + * (VMAs, ranges) to determine that the pages are valid for each fault in the
> + * storm, the time complexity grows rapidly.
> + *
> + * To address this, each page fault worker maintains a cache of the active fault
> + * being processed. Subsequent faults that hit in the cache are chained to the
> + * pending fault, and all chained faults are acknowledged once the initial fault
> + * completes. This alleviates head-of-queue blocking and quickly chains faults
> + * in the upper layers, avoiding expensive lookups in the main fault-handling
> + * path.
> + *
> + * Faults are buffered in the page fault queue in a way that provides stable
> + * storage for outstanding faults. In particular, faults may be chained directly
> + * while still resident in the queue storage (i.e., outside the worker’s current
> + * head/tail dequeue position). This allows the IRQ handler to match newly
> + * arrived faults against the per-worker cache and immediately chain cache hits
> + * onto the active fault under the queue lock, without allocating memory or
> + * waiting for the worker to pop the fault first.
> + *
> + * A per-fault state field is used to assert correctness of these invariants.
> + * The state tracks whether an entry is free, queued, chained, or currently
> + * active. Transitions are performed under the page fault queue lock, and the
> + * worker acknowledges faults by walking the chain and returning entries to the
> + * free state once they are complete.
> + */
> +
> +/**
> + * enum xe_pagefault_alloc_state - lifetime state for a page fault queue entry
> + * @XE_PAGEFAULT_ALLOC_STATE_FREE:
> + * Entry is unused and may be overwritten by the producer, consumer retry
> + * or requeue..
> + * @XE_PAGEFAULT_ALLOC_STATE_QUEUED:
> + * Entry has been enqueued and may be dequeued by a worker.
> + * @XE_PAGEFAULT_ALLOC_STATE_ACTIVE:
> + * Entry has been dequeued and is the worker's currently serviced fault.
> + * The worker may attach additional faults to it via consumer.next.
> + * @XE_PAGEFAULT_ALLOC_STATE_CHAINED:
> + * Entry is not independently serviced; it has been chained onto an
> + * ACTIVE entry via consumer.next and will be acknowledged when the
> + * leading fault completes.
> + *
> + * The page fault queue provides stable storage for outstanding faults so the
> + * IRQ handler can chain new cache hits directly onto a worker's active fault.
> + * Because entries may remain referenced outside the consumer dequeue window,
> + * the producer must only write into entries in the FREE state.
> + *
> + * State transitions are protected by the page fault queue lock. Workers return
> + * entries to FREE after acknowledging the fault (either as ACTIVE or CHAINED).
> + */
> +enum xe_pagefault_alloc_state {
> + XE_PAGEFAULT_ALLOC_STATE_FREE = 0,
> + XE_PAGEFAULT_ALLOC_STATE_QUEUED = 1,
> + XE_PAGEFAULT_ALLOC_STATE_CHAINED = 2,
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE = 3,
> +};
> +
> static int xe_pagefault_entry_size(void)
> {
> /*
> @@ -77,7 +141,7 @@ static int xe_pagefault_begin(struct drm_exec *exec, struct xe_vma *vma,
> }
>
> static int xe_pagefault_handle_vma(struct xe_gt *gt, struct xe_vma *vma,
> - bool atomic)
> + struct xe_pagefault *pf, bool atomic)
> {
> struct xe_vm *vm = xe_vma_vm(vma);
> struct xe_tile *tile = gt_to_tile(gt);
> @@ -102,8 +166,11 @@ static int xe_pagefault_handle_vma(struct xe_gt *gt, struct xe_vma *vma,
>
> /* Check if VMA is valid, opportunistic check only */
> if (xe_vm_has_valid_gpu_mapping(tile, vma->tile_present,
> - vma->tile_invalidated) && !atomic)
> + vma->tile_invalidated) && !atomic) {
> + xe_pagefault_set_start_addr(pf, xe_vma_start(vma));
> + xe_pagefault_set_end_addr(pf, xe_vma_end(vma));
> return 0;
> + }
>
> do {
> if (xe_vma_is_userptr(vma) &&
> @@ -141,6 +208,10 @@ static int xe_pagefault_handle_vma(struct xe_gt *gt, struct xe_vma *vma,
> } while (err == -EAGAIN);
>
> if (!err) {
> + /* Give hint to immediately ack faults */
> + xe_pagefault_set_start_addr(pf, xe_vma_start(vma));
> + xe_pagefault_set_end_addr(pf, xe_vma_end(vma));
> +
> dma_fence_wait(fence, false);
> dma_fence_put(fence);
> }
> @@ -208,10 +279,10 @@ static int xe_pagefault_service(struct xe_pagefault *pf)
> atomic = xe_pagefault_access_is_atomic(pf->consumer.access_type);
>
> if (xe_vma_is_cpu_addr_mirror(vma))
> - err = xe_svm_handle_pagefault(vm, vma, gt,
> + err = xe_svm_handle_pagefault(vm, vma, pf, gt,
> pf->consumer.page_addr, atomic);
> else
> - err = xe_pagefault_handle_vma(gt, vma, atomic);
> + err = xe_pagefault_handle_vma(gt, vma, pf, atomic);
>
> unlock_vm:
> up_read(&vm->lock);
> @@ -220,21 +291,221 @@ static int xe_pagefault_service(struct xe_pagefault *pf)
> return err;
> }
>
> -static bool xe_pagefault_queue_pop(struct xe_pagefault_queue *pf_queue,
> - struct xe_pagefault *pf)
> +#define XE_PAGEFAULT_CACHE_START_INVALID U64_MAX
> +#define xe_pagefault_cache_start_invalidate(val) \
> + (val = XE_PAGEFAULT_CACHE_START_INVALID)
> +
> +static void
> +xe_pagefault_cache_invalidate(struct xe_pagefault_queue *pf_queue,
> + struct xe_pagefault_work *pf_work)
> {
> - bool found_fault = false;
> + lockdep_assert_held(&pf_queue->lock);
> +
> + xe_pagefault_cache_start_invalidate(pf_work->cache.start);
> +}
> +
> +static bool xe_pagefault_queue_full(struct xe_pagefault_queue *pf_queue)
> +{
> + lockdep_assert_held(&pf_queue->lock);
> +
> + return CIRC_SPACE(pf_queue->head, pf_queue->tail,
> + pf_queue->size) <= xe_pagefault_entry_size();
> +}
> +
> +static struct xe_pagefault *
> +xe_pagefault_queue_add(struct xe_pagefault_queue *pf_queue,
> + struct xe_pagefault *pf)
> +{
> + struct xe_device *xe = container_of(pf_queue, typeof(*xe),
> + usm.pf_queue);
> + struct xe_pagefault *lpf;
> +
> + lockdep_assert_held(&pf_queue->lock);
> +
> + do {
> + /* Not possible, warn on and drop page fault */
> + if (WARN_ON(xe_pagefault_queue_full(pf_queue)))
> + return NULL;
>
> - spin_lock_irq(&pf_queue->lock);
> - if (pf_queue->tail != pf_queue->head) {
> - memcpy(pf, pf_queue->data + pf_queue->tail, sizeof(*pf));
> - pf_queue->tail = (pf_queue->tail + xe_pagefault_entry_size()) %
> + lpf = (pf_queue->data + pf_queue->head);
> + pf_queue->head = (pf_queue->head + xe_pagefault_entry_size()) %
> pf_queue->size;
> - found_fault = true;
> + } while (lpf->consumer.alloc_state != XE_PAGEFAULT_ALLOC_STATE_FREE);
> +
> + xe_assert(xe, lpf != pf);
> + memcpy(lpf, pf, sizeof(*pf));
> + lpf->consumer.alloc_state = XE_PAGEFAULT_ALLOC_STATE_QUEUED;
> +
> + return lpf;
> +}
> +
> +static struct xe_pagefault *
> +xe_pagefault_queue_unchain_requeue(struct xe_pagefault_queue *pf_queue,
> + struct xe_pagefault *pf, struct xe_gt *gt)
> +{
> + struct xe_device *xe = container_of(pf_queue, typeof(*xe),
> + usm.pf_queue);
> + struct xe_pagefault *next = pf->consumer.next, *lpf;
> +
> + lockdep_assert_held(&pf_queue->lock);
> + xe_assert(xe, pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_CHAINED);
> +
> + pf->consumer.alloc_state = XE_PAGEFAULT_ALLOC_STATE_FREE;
> + lpf = xe_pagefault_queue_add(pf_queue, pf);
> + if (lpf) {
> + lpf->consumer.next = NULL;
> + lpf->consumer.fault_type_level |= XE_PAGEFAULT_REQUEUE_MASK;
> + }
> +
> + return next;
> +}
> +
> +static bool xe_pagefault_match(struct xe_pagefault *pf, u64 start,
> + u64 end, u64 cache_asid)
> +{
> + struct xe_device *xe = gt_to_xe(pf->gt);
> + u64 page_addr = pf->consumer.page_addr;
> + u32 pf_asid = pf->consumer.asid;
> +
> + xe_assert(xe, pf->consumer.alloc_state !=
> + XE_PAGEFAULT_ALLOC_STATE_FREE);
> +
> + return page_addr >= start && page_addr < end &&
> + pf_asid == cache_asid;
> +}
Sashiko's comment here seems valid: access type should be taken into consideration
when evaluating if the page faults match (atomic).
> +
> +static bool xe_pagefault_try_chain(struct xe_pagefault_queue *pf_queue,
> + struct xe_pagefault *pf)
> +{
> + struct xe_device *xe = container_of(pf_queue, typeof(*xe),
> + usm.pf_queue);
> + struct xe_pagefault_work *pf_work;
> + bool requeue = FIELD_GET(XE_PAGEFAULT_REQUEUE_MASK,
> + pf->consumer.fault_type_level);
> + int i;
> +
> + lockdep_assert_held(&pf_queue->lock);
> + xe_assert(xe, pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_QUEUED);
> +
> + /*
> + * If this is a retry, we may already have a chain attached. In that
> + * case, we cannot hit in the cache because chains cannot easily be
> + * combined.
> + */
> + if (pf->consumer.next)
> + return false;
> +
> + for (i = 0, pf_work = xe->usm.pf_workers;
> + i < xe->info.num_pf_work; ++i, ++pf_work) {
> + u64 start = pf_work->cache.start;
> + u64 end = requeue ? start + SZ_4K : pf_work->cache.end;
> + u32 asid = pf_work->cache.asid;
> +
> + if (xe_pagefault_match(pf, start, end, asid)) {
> + xe_assert(xe, pf_work->cache.pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE);
> +
> + pf->consumer.alloc_state =
> + XE_PAGEFAULT_ALLOC_STATE_CHAINED;
> + pf->consumer.next = pf_work->cache.pf->consumer.next;
> + pf_work->cache.pf->consumer.next = pf;
> +
> + return true;
> + }
> + }
> +
> + return false;
> +}
> +
> +static void xe_pagefault_queue_advance(struct xe_pagefault_queue *pf_queue)
> +{
> + lockdep_assert_held(&pf_queue->lock);
> +
> + pf_queue->tail = (pf_queue->tail + xe_pagefault_entry_size()) %
> + pf_queue->size;
> +}
> +
> +static struct xe_pagefault *
> +xe_pagefault_queue_tail_fault(struct xe_pagefault_queue *pf_queue)
> +{
> + lockdep_assert_held(&pf_queue->lock);
> +
> + return pf_queue->data + pf_queue->tail;
> +}
> +
> +static bool xe_pagefault_queue_empty(struct xe_pagefault_queue *pf_queue)
> +{
> + lockdep_assert_held(&pf_queue->lock);
> +
> + return pf_queue->head == pf_queue->tail;
> +}
> +
> +static bool xe_pagefault_queue_pop(struct xe_pagefault_queue *pf_queue,
> + struct xe_pagefault **pf, int id)
> +{
> + struct xe_device *xe = container_of(pf_queue, typeof(*xe),
> + usm.pf_queue);
> + struct xe_pagefault_work *pf_work;
> + struct xe_pagefault *lpf;
> + size_t align = SZ_2M;
> +
> + guard(spinlock_irq)(&pf_queue->lock);
> +
> + for (*pf = NULL; !*pf;) {
> + if (xe_pagefault_queue_empty(pf_queue))
> + return false;
> +
> + lpf = xe_pagefault_queue_tail_fault(pf_queue);
> + xe_pagefault_queue_advance(pf_queue);
> +
> + if (lpf->consumer.alloc_state !=
> + XE_PAGEFAULT_ALLOC_STATE_QUEUED)
> + continue;
> +
> + if (xe_pagefault_try_chain(pf_queue, lpf))
> + continue;
> +
> + *pf = lpf; /* Hand back page fault for processing */
> + }
> +
> + /*
> + * No cache hit; allocate a new cache entry. We assume most faults
> + * within a 2M range will hit the same pages. If this assumption proves
> + * false, the mismatched fault is requeued after the initial fault is
> + * acknowledged.
> + */
> + pf_work = xe->usm.pf_workers + id;
> + if (FIELD_GET(XE_PAGEFAULT_REQUEUE_MASK,
> + lpf->consumer.fault_type_level))
> + align = SZ_4K;
> + pf_work->cache.start = ALIGN_DOWN(lpf->consumer.page_addr, align);
> + pf_work->cache.end = pf_work->cache.start + align;
> + pf_work->cache.asid = lpf->consumer.asid;
> + pf_work->cache.pf = lpf;
> + lpf->consumer.alloc_state = XE_PAGEFAULT_ALLOC_STATE_ACTIVE;
> +
> + /* Drain queue until empty or new fault found */
> + while (1) {
> + if (xe_pagefault_queue_empty(pf_queue))
> + break;
> +
> + lpf = xe_pagefault_queue_tail_fault(pf_queue);
> +
> + if (lpf->consumer.alloc_state !=
> + XE_PAGEFAULT_ALLOC_STATE_QUEUED) {
> + xe_pagefault_queue_advance(pf_queue);
> + continue;
> + }
> +
> + if (!xe_pagefault_try_chain(pf_queue, lpf))
> + break;
> +
> + xe_pagefault_queue_advance(pf_queue);
> }
> - spin_unlock_irq(&pf_queue->lock);
>
> - return found_fault;
> + return true;
> }
>
> static void xe_pagefault_print(struct xe_pagefault *pf)
> @@ -295,36 +566,83 @@ static void xe_pagefault_queue_work(struct work_struct *w)
> container_of(w, typeof(*pf_work), work);
> struct xe_device *xe = pf_work->xe;
> struct xe_pagefault_queue *pf_queue = &xe->usm.pf_queue;
> - struct xe_pagefault pf;
> + struct xe_pagefault *pf;
> ktime_t start = xe_gt_stats_ktime_get();
> - struct xe_gt *gt = NULL;
> unsigned long threshold;
> + u64 cache_start = XE_PAGEFAULT_CACHE_START_INVALID, cache_end = 0;
> + u32 cache_asid = 0;
>
> #define USM_QUEUE_MAX_RUNTIME_MS 20
> threshold = jiffies + msecs_to_jiffies(USM_QUEUE_MAX_RUNTIME_MS);
>
> - while (xe_pagefault_queue_pop(pf_queue, &pf)) {
> - int err;
> + while (xe_pagefault_queue_pop(pf_queue, &pf, pf_work->id)) {
> + struct xe_gt *gt = pf->gt;
> + u32 asid = pf->consumer.asid;
> + int err = 0;
> + bool invalidated = false;
>
> - if (!pf.gt) /* Fault squashed during reset */
> - continue;
> + /* Last fault same address, ack immediately */
> + if (xe_pagefault_match(pf, cache_start, cache_end, cache_asid))
> + goto ack_fault;
> +
> + err = xe_pagefault_service(pf);
>
> - gt = pf.gt;
> - err = xe_pagefault_service(&pf);
> if (err) {
> - if (!(pf.consumer.access_type & XE_PAGEFAULT_ACCESS_PREFETCH)) {
> - xe_pagefault_save_to_vm(gt_to_xe(pf.gt), &pf);
> - xe_pagefault_print(&pf);
> - xe_gt_info(pf.gt, "Fault response: Unsuccessful %pe\n",
> + if (!(pf->consumer.access_type & XE_PAGEFAULT_ACCESS_PREFETCH)) {
> + xe_pagefault_save_to_vm(gt_to_xe(gt), pf);
> + xe_pagefault_cache_start_invalidate(cache_start);
> + xe_pagefault_print(pf);
> + xe_gt_info(pf->gt, "Fault response: Unsuccessful %pe\n",
> ERR_PTR(err));
> } else {
> - xe_gt_stats_incr(pf.gt, XE_GT_STATS_ID_INVALID_PREFETCH_PAGEFAULT_COUNT, 1);
> - xe_gt_dbg(pf.gt, "Prefetch Fault response: Unsuccessful %pe\n",
> + xe_gt_stats_incr(pf->gt, XE_GT_STATS_ID_INVALID_PREFETCH_PAGEFAULT_COUNT, 1);
> + xe_gt_dbg(pf->gt, "Prefetch Fault response: Unsuccessful %pe\n",
> ERR_PTR(err));
> }
> + } else {
> + /* Cache valid fault locally */
> + cache_start = xe_pagefault_start_addr(pf);
> + cache_end = xe_pagefault_end_addr(pf);
> + cache_asid = asid;
> }
>
> - pf.producer.ops->ack_fault(&pf, err);
> +ack_fault:
> + xe_assert(xe, pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE);
> + xe_assert(xe, pf == pf_work->cache.pf);
> +
> + while (pf) {
> + xe_assert(xe, pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE);
> +
> + pf->producer.ops->ack_fault(pf, err);
> +
> + spin_lock_irq(&pf_queue->lock);
> +
> + if (!invalidated) {
> + invalidated = true;
> + xe_pagefault_cache_invalidate(pf_queue,
> + pf_work);
> + }
> +
> + pf->consumer.alloc_state = XE_PAGEFAULT_ALLOC_STATE_FREE;
> + pf = pf->consumer.next;
> +
> + /*
> + * Requeue chained faults which do not match the last
> + * fault processed
> + */
> + while (pf && !xe_pagefault_match(pf, cache_start,
> + cache_end, cache_asid))
> + pf = xe_pagefault_queue_unchain_requeue(pf_queue, pf, gt);
> +
> +
Extra blank line.
> + /* Ensure resets are safe */
> + if (pf)
> + pf->consumer.alloc_state =
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE;
> + spin_unlock_irq(&pf_queue->lock);
> + }
>
> if (time_after(jiffies, threshold)) {
> queue_work(xe->usm.pagefault_wq, w);
> @@ -333,10 +651,8 @@ static void xe_pagefault_queue_work(struct work_struct *w)
> }
> #undef USM_QUEUE_MAX_RUNTIME_MS
>
> - if (gt)
> - xe_gt_stats_incr(xe_root_mmio_gt(gt_to_xe(gt)),
> - XE_GT_STATS_ID_PAGEFAULT_US,
> - xe_gt_stats_ktime_us_delta(start));
> + xe_gt_stats_incr(xe_root_mmio_gt(xe), XE_GT_STATS_ID_PAGEFAULT_US,
> + xe_gt_stats_ktime_us_delta(start));
> }
>
> static int xe_pagefault_queue_init(struct xe_device *xe,
> @@ -431,6 +747,7 @@ int xe_pagefault_init(struct xe_device *xe)
>
> pf_work->xe = xe;
> pf_work->id = i;
> + xe_pagefault_cache_start_invalidate(pf_work->cache.start);
> INIT_WORK(&pf_work->work, xe_pagefault_queue_work);
> }
>
> @@ -454,15 +771,23 @@ static void xe_pagefault_queue_reset(struct xe_device *xe, struct xe_gt *gt,
>
> /* Squash all pending faults on the GT */
>
> - spin_lock_irq(&pf_queue->lock);
> - for (i = pf_queue->tail; i != pf_queue->head;
> - i = (i + xe_pagefault_entry_size()) % pf_queue->size) {
> + guard(spinlock_irq)(&pf_queue->lock);
> +
> + for (i = 0; i < pf_queue->size; i += xe_pagefault_entry_size()) {
> struct xe_pagefault *pf = pf_queue->data + i;
> + bool active = pf->consumer.alloc_state ==
> + XE_PAGEFAULT_ALLOC_STATE_ACTIVE;
>
> - if (pf->gt == gt)
> - pf->gt = NULL;
> + if (pf->gt != gt || active) {
> + if (active)
> + pf->consumer.next = NULL;
> + continue;
> + }
> +
> + pf->consumer.alloc_state =
> + XE_PAGEFAULT_ALLOC_STATE_FREE;
> + pf->consumer.next = NULL;
> }
> - spin_unlock_irq(&pf_queue->lock);
> }
>
> /**
> @@ -478,14 +803,6 @@ void xe_pagefault_reset(struct xe_device *xe, struct xe_gt *gt)
> xe_pagefault_queue_reset(xe, gt, &xe->usm.pf_queue);
> }
>
> -static bool xe_pagefault_queue_full(struct xe_pagefault_queue *pf_queue)
> -{
> - lockdep_assert_held(&pf_queue->lock);
> -
> - return CIRC_SPACE(pf_queue->head, pf_queue->tail, pf_queue->size) <=
> - xe_pagefault_entry_size();
> -}
> -
> /*
> * This function can race with multiple page fault producers, but worst case we
> * stick a page fault on the same queue for consumption.
> @@ -511,18 +828,28 @@ int xe_pagefault_handler(struct xe_device *xe, struct xe_pagefault *pf)
> {
> struct xe_pagefault_queue *pf_queue = &xe->usm.pf_queue;
> unsigned long flags;
> - int work_index;
> bool full;
>
> spin_lock_irqsave(&pf_queue->lock, flags);
> - work_index = xe_pagefault_work_index(xe);
> full = xe_pagefault_queue_full(pf_queue);
> if (!full) {
> - memcpy(pf_queue->data + pf_queue->head, pf, sizeof(*pf));
> - pf_queue->head = (pf_queue->head + xe_pagefault_entry_size()) %
> - pf_queue->size;
> - queue_work(xe->usm.pagefault_wq,
> - &xe->usm.pf_workers[work_index].work);
> + struct xe_pagefault *lpf;
> + bool empty = xe_pagefault_queue_empty(pf_queue);
> +
> + lpf = xe_pagefault_queue_add(pf_queue, pf);
> + if (lpf) {
> + lpf->consumer.next = NULL;
> +
> + if (xe_pagefault_try_chain(pf_queue, lpf)) {
> + if (empty)
> + xe_pagefault_queue_advance(pf_queue);
> + } else {
> + int work_index = xe_pagefault_work_index(xe);
> +
> + queue_work(xe->usm.pagefault_wq,
> + &xe->usm.pf_workers[work_index].work);
> + }
> + }
> } else {
> drm_warn(&xe->drm,
> "PageFault Queue full, shouldn't be possible\n");
> diff --git a/drivers/gpu/drm/xe/xe_pagefault.h b/drivers/gpu/drm/xe/xe_pagefault.h
> index bd0cdf9ed37f..feaf2a69674a 100644
> --- a/drivers/gpu/drm/xe/xe_pagefault.h
> +++ b/drivers/gpu/drm/xe/xe_pagefault.h
> @@ -6,6 +6,8 @@
> #ifndef _XE_PAGEFAULT_H_
> #define _XE_PAGEFAULT_H_
>
> +#include "xe_pagefault_types.h"
> +
> struct xe_device;
> struct xe_gt;
> struct xe_pagefault;
> @@ -16,4 +18,73 @@ void xe_pagefault_reset(struct xe_device *xe, struct xe_gt *gt);
>
> int xe_pagefault_handler(struct xe_device *xe, struct xe_pagefault *pf);
>
> +#define XE_PAGEFAULT_END_ADDR_MASK (~0xfffull)
> +
> +/**
> + * xe_pagefault_set_end_addr() - store serviced range end for a pagefault
> + * @pf: Pagefault entry
> + * @end_addr: Inclusive end address of the serviced fault range
> + *
> + * The pagefault consumer stores the resolved fault range so subsequent faults
> + * hitting the same range can be immediately acknowledged without re-running
> + * the full fault handling path.
> + *
> + * The end address shares storage with other consumer metadata and therefore
> + * must be masked with %XE_PAGEFAULT_END_ADDR_MASK before storing. Bits outside
> + * the mask are reserved for internal state tracking and must be preserved.
> + */
> +static inline void
> +xe_pagefault_set_end_addr(struct xe_pagefault *pf, u64 end_addr)
> +{
> + pf->consumer.end_addr &= ~XE_PAGEFAULT_END_ADDR_MASK;
> + pf->consumer.end_addr |= end_addr;
> +}
> +
> +/**
> + * xe_pagefault_end_addr() - read serviced range end for a pagefault
> + * @pf: Pagefault entry
> + *
> + * Returns the inclusive end address of the range previously recorded by
> + * xe_pagefault_set_end_addr(). Only the bits covered by
> + * %XE_PAGEFAULT_END_ADDR_MASK are returned; other bits in the storage are
> + * reserved for internal state.
> + *
> + * Return: End address of the serviced fault range.
> + */
> +static inline u64 xe_pagefault_end_addr(struct xe_pagefault *pf)
> +{
> + return pf->consumer.end_addr & XE_PAGEFAULT_END_ADDR_MASK;
> +}
> +
> +#undef XE_PAGEFAULT_END_ADDR_MASK
> +
> +/**
> + * xe_pagefault_set_start_addr() - store serviced range start for a pagefault
> + * @pf: Pagefault entry
> + * @start_addr: Start address of the serviced fault range
> + *
> + * The pagefault consumer stores the resolved fault range so subsequent faults
> + * hitting the same range can be immediately acknowledged without re-running
> + * the full fault handling path.
> + */
> +static inline void
> +xe_pagefault_set_start_addr(struct xe_pagefault *pf, u64 start_addr)
> +{
> + pf->consumer.page_addr = start_addr;
> +}
> +
> +/**
> + * xe_pagefault_start_addr() - read serviced range start for a pagefault
> + * @pf: Pagefault entry
> + *
> + * Returns the inclusive start address of the range previously recorded by
> + * xe_pagefault_set_start_addr().
> + *
> + * Return: Start address of the serviced fault range.
> + */
> +static inline u64 xe_pagefault_start_addr(struct xe_pagefault *pf)
> +{
> + return pf->consumer.page_addr;
> +}
> +
> #endif
> diff --git a/drivers/gpu/drm/xe/xe_pagefault_types.h b/drivers/gpu/drm/xe/xe_pagefault_types.h
> index d349d79bc95e..a1fff0e47fa5 100644
> --- a/drivers/gpu/drm/xe/xe_pagefault_types.h
> +++ b/drivers/gpu/drm/xe/xe_pagefault_types.h
> @@ -60,36 +60,58 @@ struct xe_pagefault {
> /**
> * @consumer: State for the software handling the fault. Populated by
> * the producer and may be modified by the consumer to communicate
> - * information back to the producer upon fault acknowledgment.
> + * information back to the producer upon fault acknowledgment. After
> + * fault acknowledgment, the producer should only access consumer fields
> + * via well defined helpers.
> */
> struct {
> - /** @consumer.page_addr: address of page fault */
> - u64 page_addr;
> - /** @consumer.asid: address space ID */
> - u32 asid;
> /**
> - * @consumer.access_type: access type and prefetch flag packed
> - * into a u8.
> + * @consumer.page_addr: address of page fault, populated by
> + * consumer after fault completion
> */
> - u8 access_type;
> + u64 page_addr;
> + union {
> + struct {
> + /**
> + * @consumer.alloc_state: page fault allocation
> + * state
> + */
> + u8 alloc_state;
> + /**
> + * @consumer.access_type: access type, u8 rather
> + * than enum to keep size compact
> + */
> + u8 access_type;
> #define XE_PAGEFAULT_ACCESS_TYPE_MASK GENMASK(1, 0)
> #define XE_PAGEFAULT_ACCESS_PREFETCH BIT(7)
> - /**
> - * @consumer.fault_type_level: fault type and level, u8 rather
> - * than enum to keep size compact
> - */
> - u8 fault_type_level;
> + /**
> + * @consumer.fault_type_level: fault type and
> + * level, u8 rather than enum to keep size
> + * compact
> + */
> + u8 fault_type_level;
> #define XE_PAGEFAULT_TYPE_LEVEL_NACK 0xff /* Producer indicates nack fault */
> -#define XE_PAGEFAULT_LEVEL_MASK GENMASK(3, 0)
> -#define XE_PAGEFAULT_TYPE_MASK GENMASK(7, 4)
> - /** @consumer.engine_class_instance: engine class and instance */
> - u8 engine_class_instance;
> +#define XE_PAGEFAULT_LEVEL_MASK GENMASK(2, 0)
> +#define XE_PAGEFAULT_TYPE_MASK GENMASK(6, 3)
> +#define XE_PAGEFAULT_REQUEUE_MASK BIT(7)
> + /** @consumer.engine_class_instance: engine class and instance */
> + u8 engine_class_instance;
> #define XE_PAGEFAULT_ENGINE_CLASS_MASK GENMASK(3, 0)
> #define XE_PAGEFAULT_ENGINE_INSTANCE_MASK GENMASK(7, 4)
> - /** @pad: alignment padding */
> - u8 pad;
> - /** @consumer.reserved: reserved bits for future expansion */
> - u64 reserved;
> + /** @consumer.asid: address space ID */
> + u32 asid;
> + };
> + /**
> + * @consumer.end_addr: end address of page fault,
> + * populated by consumer after fault completion
> + */
> + u64 end_addr;
> + };
> + /**
> + * @consumer.next: next pagefault chained to this fault,
> + * protected by pf_queue lock
> + */
> + struct xe_pagefault *next;
> } consumer;
> /**
> * @producer: State for the producer (i.e., HW/FW interface). Populated
> @@ -131,7 +153,7 @@ struct xe_pagefault_queue {
> u32 head;
> /** @tail: Tail pointer in bytes, moved by consumer, protected by @lock */
> u32 tail;
> - /** @lock: protects page fault queue */
> + /** @lock: protects page fault queue, workers caches */
> spinlock_t lock;
> };
>
> @@ -146,6 +168,21 @@ struct xe_pagefault_work {
> struct xe_device *xe;
> /** @id: Identifier for this work item */
> int id;
> + /**
> + * @cache: Page fault cache for the currently processed fault
> + *
> + * Protected by the page fault queue lock.
> + */
> + struct {
> + /** @cache.start: Start address of the current page fault */
> + u64 start;
> + /** @cache.end: End address of the current page fault */
> + u64 end;
> + /** @cache.asid: Address space ID of the current page fault */
> + u32 asid;
> + /** @cache.pf: Pointer to the current page fault */
> + struct xe_pagefault *pf;
> + } cache;
> /** @work: Work item used to process the page fault */
> struct work_struct work;
> };
> diff --git a/drivers/gpu/drm/xe/xe_svm.c b/drivers/gpu/drm/xe/xe_svm.c
> index 6a470a02fee7..627a741293d5 100644
> --- a/drivers/gpu/drm/xe/xe_svm.c
> +++ b/drivers/gpu/drm/xe/xe_svm.c
> @@ -15,6 +15,7 @@
> #include "xe_gt_stats.h"
> #include "xe_migrate.h"
> #include "xe_module.h"
> +#include "xe_pagefault.h"
> #include "xe_pm.h"
> #include "xe_pt.h"
> #include "xe_svm.h"
> @@ -1264,8 +1265,8 @@ DECL_SVM_RANGE_US_STATS(bind, BIND)
> DECL_SVM_RANGE_US_STATS(fault, PAGEFAULT)
>
> static int __xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> - struct xe_gt *gt, u64 fault_addr,
> - bool need_vram)
> + struct xe_pagefault *pf, struct xe_gt *gt,
> + u64 fault_addr, bool need_vram)
> {
> int devmem_possible = IS_DGFX(vm->xe) &&
> IS_ENABLED(CONFIG_DRM_XE_PAGEMAP);
> @@ -1424,6 +1425,10 @@ static int __xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> xe_svm_range_bind_us_stats_incr(gt, range, bind_start);
>
> out:
> + /* Give hint to immediately ack faults */
> + xe_pagefault_set_start_addr(pf, xe_svm_range_start(range));
> + xe_pagefault_set_end_addr(pf, xe_svm_range_end(range));
> +
I have not checked the GT stats from later patches in the series but are we able
to chain enough faults by setting start/end so late during page fault handling?
Would there be a way to move it closer to the beginning of
__xe_svm_handle_pagefault()?
Francois
> xe_svm_range_fault_us_stats_incr(gt, range, start);
> mutex_unlock(&range->lock);
> drm_gpusvm_range_put(&range->base);
> @@ -1446,6 +1451,7 @@ static int __xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> * xe_svm_handle_pagefault() - SVM handle page fault
> * @vm: The VM.
> * @vma: The CPU address mirror VMA.
> + * @pf: Pagefault structure
> * @gt: The gt upon the fault occurred.
> * @fault_addr: The GPU fault address.
> * @atomic: The fault atomic access bit.
> @@ -1456,8 +1462,8 @@ static int __xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> * Return: 0 on success, negative error code on error.
> */
> int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> - struct xe_gt *gt, u64 fault_addr,
> - bool atomic)
> + struct xe_pagefault *pf, struct xe_gt *gt,
> + u64 fault_addr, bool atomic)
> {
> int need_vram, ret;
> retry:
> @@ -1465,7 +1471,7 @@ int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> if (need_vram < 0)
> return need_vram;
>
> - ret = __xe_svm_handle_pagefault(vm, vma, gt, fault_addr,
> + ret = __xe_svm_handle_pagefault(vm, vma, pf, gt, fault_addr,
> need_vram ? true : false);
> if (ret == -EAGAIN) {
> /*
> diff --git a/drivers/gpu/drm/xe/xe_svm.h b/drivers/gpu/drm/xe/xe_svm.h
> index 46be2e5c6f7f..2a0dc0d125c9 100644
> --- a/drivers/gpu/drm/xe/xe_svm.h
> +++ b/drivers/gpu/drm/xe/xe_svm.h
> @@ -21,6 +21,7 @@ struct drm_file;
> struct xe_bo;
> struct xe_gt;
> struct xe_device;
> +struct xe_pagefault;
> struct xe_vram_region;
> struct xe_tile;
> struct xe_vm;
> @@ -109,8 +110,8 @@ void xe_svm_fini(struct xe_vm *vm);
> void xe_svm_close(struct xe_vm *vm);
>
> int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> - struct xe_gt *gt, u64 fault_addr,
> - bool atomic);
> + struct xe_pagefault *pf, struct xe_gt *gt,
> + u64 fault_addr, bool atomic);
>
> bool xe_svm_has_mapping(struct xe_vm *vm, u64 start, u64 end);
>
> @@ -298,8 +299,8 @@ void xe_svm_close(struct xe_vm *vm)
>
> static inline
> int xe_svm_handle_pagefault(struct xe_vm *vm, struct xe_vma *vma,
> - struct xe_gt *gt, u64 fault_addr,
> - bool atomic)
> + struct xe_pagefault *pf, struct xe_gt *gt,
> + u64 fault_addr, bool atomic)
> {
> return 0;
> }
> --
> 2.34.1
>
next prev parent reply other threads:[~2026-07-29 12:31 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-24 23:25 [PATCH v8 00/12] Fine grained fault locking, threaded prefetch, storm cache Matthew Brost
2026-07-24 23:25 ` [PATCH v8 01/12] drm/xe: Fine grained page fault locking Matthew Brost
2026-07-24 23:25 ` [PATCH v8 02/12] drm/xe: Allow prefetch-only VM bind IOCTLs to use VM read lock Matthew Brost
2026-07-27 14:33 ` Francois Dugast
2026-07-24 23:25 ` [PATCH v8 03/12] drm/xe: Thread prefetch of SVM ranges Matthew Brost
2026-07-27 16:41 ` Francois Dugast
2026-07-27 19:21 ` Matthew Brost
2026-07-27 19:46 ` Matthew Brost
2026-07-27 16:49 ` Francois Dugast
2026-07-27 19:11 ` Matthew Brost
2026-07-24 23:25 ` [PATCH v8 04/12] drm/xe: Use a single page-fault queue with multiple workers Matthew Brost
2026-07-24 23:25 ` [PATCH v8 05/12] drm/xe: Add num_pf_work modparam Matthew Brost
2026-07-24 23:25 ` [PATCH v8 06/12] drm/xe: Engine class and instance into a u8 Matthew Brost
2026-07-24 23:25 ` [PATCH v8 07/12] drm/xe: Track pagefault worker runtime Matthew Brost
2026-07-24 23:25 ` [PATCH v8 08/12] drm/xe: Chain page faults via queue-resident cache to avoid fault storms Matthew Brost
2026-07-29 12:31 ` Francois Dugast [this message]
2026-07-29 18:20 ` Matthew Brost
2026-08-03 13:58 ` Francois Dugast
2026-07-24 23:25 ` [PATCH v8 09/12] drm/xe: Add pagefault chaining stats Matthew Brost
2026-07-24 23:25 ` [PATCH v8 10/12] drm/xe: Add debugfs pagefault_info Matthew Brost
2026-07-24 23:26 ` [PATCH v8 11/12] drm/xe: batch CT pagefault acks with periodic flush Matthew Brost
2026-08-04 12:55 ` Francois Dugast
2026-08-04 17:53 ` Matthew Brost
2026-08-05 6:49 ` Francois Dugast
2026-07-24 23:26 ` [PATCH v8 12/12] drm/xe: Track parallel page fault activity in GT stats Matthew Brost
2026-07-24 23:32 ` ✗ CI.checkpatch: warning for Fine grained fault locking, threaded prefetch, storm cache (rev8) Patchwork
2026-07-24 23:33 ` ✓ CI.KUnit: success " Patchwork
2026-07-25 0:17 ` ✓ Xe.CI.BAT: " 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=amnyqqDRREq2SJQs@fdugast-desk \
--to=francois.dugast@intel.com \
--cc=223556219+Copilot@users.noreply.github.com \
--cc=intel-xe@lists.freedesktop.org \
--cc=matthew.brost@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox