From: Matthew Brost <matthew.brost@intel.com>
To: Francois Dugast <francois.dugast@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 11:20:39 -0700 [thread overview]
Message-ID: <ampEd1vMgFDIUxga@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <amnyqqDRREq2SJQs@fdugast-desk>
On Wed, Jul 29, 2026 at 02:31:38PM +0200, Francois Dugast wrote:
> 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/
>
+1
> >
> > 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/
>
+1
> > 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)
>
+1
> > ---
> > 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).
>
I don't think that is a major concern. Generally, when we move memory,
you end up with the highest permission level available. Atomics are an
exception because we typically have to move the memory. We more or less
always attempt a move, but we try much harder for atomics.
If, for some reason, we encounter a read or write fault, fail to move
the memory, and then chain an atomic fault and acknowledgment, the
worst-case outcome is that the atomic operation will fault again, and
we'll handle it at that point.
> > +
> > +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.
>
Will fix.
> > + /* 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?
>
This doesn't affect the chain-building process. We blindly attempt to
build chains on 2 MB-aligned boundaries. This is about communicating
back that we serviced for the fault and determining whether faults in
the chain should be acknowledged or retried. As a result, the placement
doesn't matter.
I had considered dynamically updating the chain size/window after
looking up the range or VMA, but that isn't implemented in this series.
If we went that route, then yes, we'd want to do it immediately after
the range/VMA lookup. We'd also probably want to use try-locking on the
VMA lock to avoid HoQ blocking.
The chain reconstruction is a bit tricky here, which is why I haven't
implemented it yet.
Matt
> 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 18:20 UTC|newest]
Thread overview: 24+ 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
2026-07-29 18:20 ` Matthew Brost [this message]
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-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=ampEd1vMgFDIUxga@gsse-cloud1.jf.intel.com \
--to=matthew.brost@intel.com \
--cc=223556219+Copilot@users.noreply.github.com \
--cc=francois.dugast@intel.com \
--cc=intel-xe@lists.freedesktop.org \
/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