Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Umesh Nerlige Ramappa" <umesh.nerlige.ramappa@intel.com>,
	intel-xe@lists.freedesktop.org
Subject: Re: [PATCH v2 3/3] drm/xe/oa: Quiesce OA streams on driver unbind and PCI reset
Date: Fri, 30 Oct 2026 23:14:48 +0000	[thread overview]
Message-ID: <sashiko-outbox-191625@kernel.org> (raw)
In-Reply-To: <20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] drm/xe/oa: NULL deref and dummy page race in xe_oa_buffer_fault()
- [High] drm/xe/oa: mapped OA BO left DMA-mapped and un-evicted in xe_oa_quiesce()
- [High] drm/xe/oa: ungated xe_bo_vm_access() in xe_oa_vm_ops after quiesce
- [High] drm/xe/oa: race between in-flight observation ioctls and xe_oa_fini()
- [High] drm/xe/oa: lock inversion holding mmap_mutex across xe_oa_quiesce_oa_unit()
- [High] drm/xe/oa: unwaited LRI job in xe_oa_quiesce_oa_unit() races devres teardown
--

commit 6eef6267f9216029752eabf4c50aa7de5a8ca9d1
Author: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>

drm/xe/oa: Quiesce OA streams on driver unbind and PCI reset

This commit adds xe_oa_quiesce() to disable active OA streams, cancel their
poll timers, unmap OA buffers, and release device-bound resources during
driver unbind and PCI slot reset when xe_oa_fini() runs.

> diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c
> index 4916101fce541..6faa40d7ebbe7 100644
> --- a/drivers/gpu/drm/xe/xe_oa.c
> +++ b/drivers/gpu/drm/xe/xe_oa.c

[ ... ]

> @@ -1731,8 +1775,11 @@ static vm_fault_t xe_oa_buffer_fault(struct vm_fault *vmf)
>  	 * Once the stream is dead its OA buffer may already be gone (or about
>  	 * to be). Refuse to (re-)install a PTE rather than letting the
>  	 * generic BO fault handler touch a buffer that's being torn down.
> +	 * drm_dev_unplug() zaps the mapping before the stream is marked dead,
> +	 * so also refuse once unplugged, the generic handler would install a
> +	 * dummy page.
>  	 */
> -	if (READ_ONCE(stream->dead))
> +	if (READ_ONCE(stream->dead) || drm_dev_is_unplugged(&stream->oa->xe->drm))
>  		return VM_FAULT_SIGBUS;
>  
>  	return xe_bo_cpu_fault(vmf);

[Severity: High]
Can xe_oa_buffer_fault() dereference a NULL pointer when racing with
xe_oa_fini(), or still install a dummy page PTE?

If a fault enters xe_oa_buffer_fault() and observes READ_ONCE(stream->dead)
as false just before xe_oa_quiesce_oa_unit() sets stream->dead = true,
xe_oa_fini() can finish xe_oa_quiesce() and clear oa->xe:

drivers/gpu/drm/xe/xe_oa.c:xe_oa_fini() {
	...
	xe_oa_quiesce(xe);

	idr_for_each(&oa->metrics_idr, destroy_config, oa);
	idr_destroy(&oa->metrics_idr);

	oa->xe = NULL;
}

When xe_oa_buffer_fault() then evaluates the second operand of the logical
or, stream->oa->xe is NULL, so &stream->oa->xe->drm is NULL and
drm_dev_is_unplugged() dereferences NULL in drm_dev_enter() (whereas
xe_oa_mmap(), xe_oa_destroy_locked(), and xe_oa_release() use
gt_to_xe(stream->gt)).

In addition, drm_dev_is_unplugged() immediately drops its SRCU read lock via
drm_dev_exit(). If drm_dev_unplug() and drm_vma_node_unmap() complete in the
window between drm_dev_is_unplugged() in xe_oa_buffer_fault() and
drm_dev_enter() in xe_bo_cpu_fault(), or if the device is wedged before
unplug (such as during PCI error recovery), xe_bo_cpu_fault() still hits:

drivers/gpu/drm/xe/xe_bo.c:xe_bo_cpu_fault() {
	...
	if (xe_wedged(xe) || !drm_dev_enter(&xe->drm, &idx))
		return ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot);

and installs a dummy page PTE instead of returning VM_FAULT_SIGBUS.

> @@ -1745,18 +1792,12 @@ static const struct vm_operations_struct xe_oa_vm_ops = {
>  	.access	= xe_bo_vm_access,
>  };

[Severity: High]
What prevents userspace from accessing the OA buffer via xe_bo_vm_access()
after xe_oa_quiesce()?

While xe_oa_vm_ops gates .fault with xe_oa_buffer_fault(), .access is wired
directly to xe_bo_vm_access():

drivers/gpu/drm/xe/xe_bo.c:xe_bo_vm_access() {
	struct ttm_buffer_object *ttm_bo = vma->vm_private_data;
	struct xe_bo *bo = ttm_to_xe_bo(ttm_bo);
	struct xe_device *xe = xe_bo_device(bo);

	guard(xe_pm_runtime)(xe);
	return ttm_bo_vm_access(vma, addr, buf, len, write);
}

Because an existing VMA holds a GEM reference to stream->oa_buffer.bo in
vma->vm_private_data until munmap() or process exit, reading the VMA via
/proc/self/mem, ptrace, or process_vm_readv() after xe_oa_quiesce() will get
VM_FAULT_SIGBUS from xe_oa_buffer_fault() and fall back to
vma->vm_ops->access() -> xe_bo_vm_access(). That acquires runtime PM on the
removed device and accesses the BO backing store (including unmapped
vram->mapping or torn-down migration state when the OA buffer is in VRAM
under mert_wa_14026633728()). Should .access also be wrapped to check
stream->dead and drm_dev_enter()?

[ ... ]

> @@ -1925,6 +1988,12 @@ static int xe_oa_stream_open_ioctl_locked(struct xe_oa *oa,
>  	int stream_fd;
>  	int ret;
>  
> +	if (param->hwe->gt->oa.quiesced) {
> +		drm_dbg(&oa->xe->drm, "OA is quiesced\n");
> +		ret = -ENODEV;
> +		goto exit;
> +	}
> +

[Severity: High]
Could an in-flight observation ioctl that entered drm_ioctl() before
drm_dev_unplug() race with xe_oa_fini() before reaching gt_lock?

In xe_oa_stream_open_ioctl(), oa->xe is only checked unlocked at entry before
sleeping copy_from_user() calls in xe_oa_user_extensions() and before taking
gt_lock:

drivers/gpu/drm/xe/xe_oa.c:xe_oa_stream_open_ioctl() {
	...
	if (!oa->xe) {
		drm_dbg(&xe->drm, "xe oa interface not available for this system\n");
		return -ENODEV;
	}

	param.xef = xef;
	param.period_exponent = -1;
	ret = xe_oa_user_extensions(oa, XE_OA_USER_EXTN_FROM_OPEN, data, 0, &param);
	if (ret)
		return ret;

	/* If not provided, OA unit defaults to OA unit 0 as per uapi */
	if (!param.oa_unit)
		param.oa_unit = &xe_root_mmio_gt(oa->xe)->oa.oa_unit[0];

If xe_oa_fini() runs concurrently and sets oa->xe = NULL right after
xe_oa_quiesce(), an in-flight xe_oa_stream_open_ioctl() can dereference NULL
oa->xe in xe_oa_lookup_oa_unit(), xe_root_mmio_gt(oa->xe),
oa_unit_supports_oa_format(), or even in drm_dbg(&oa->xe->drm, ...) inside
xe_oa_stream_open_ioctl_locked() above. When param.period_exponent >= 0,
xe_oa_stream_open_ioctl() also calls oa_exponent_to_ns() ->
xe_oa_timestamp_frequency() -> xe_mmio_read32() before acquiring gt_lock and
checking gt->oa.quiesced.

Similarly, in-flight xe_oa_add_config_ioctl() and xe_oa_remove_config_ioctl()
calls that already passed their unlocked !oa->xe check can dereference NULL
oa->xe in xe_oa_is_valid_config_reg() or access oa->metrics_idr while
xe_oa_fini() destroys oa->metrics_idr without holding oa->metrics_lock.

[ ... ]

> @@ -2617,6 +2686,92 @@ static void xe_oa_unregister(void *arg)
>  	oa->metrics_kobj = NULL;
>  }
>  
> +static void xe_oa_quiesce_oa_unit(struct xe_oa_unit *u, bool hw_accessible)
> +{
> +	struct xe_oa_stream *stream = u->exclusive_stream;
> +	unsigned long flags;
> +
> +	if (!stream)
> +		return;
> +
> +	mutex_lock(&stream->stream_lock);
> +	mutex_lock(&stream->mmap_mutex);
> +	if (stream->enabled && hw_accessible)
> +		xe_oa_disable_locked(stream);
> +
> +	stream->enabled = false;
> +
> +	spin_lock_irqsave(&stream->oa_buffer.ptr_lock, flags);
> +	WRITE_ONCE(stream->dead, true);
> +	spin_unlock_irqrestore(&stream->oa_buffer.ptr_lock, flags);
> +
> +	if (stream->sample)
> +		hrtimer_cancel(&stream->poll_check_timer);
> +
> +	wake_up_all(&stream->poll_wq);
> +
> +	/* Zap userspace mappings of this OA buffer only, others in the address_space stay */
> +	drm_vma_node_unmap(&stream->oa_buffer.bo->ttm.base.vma_node,
> +			   stream->oa->xe->drm.anon_inode->i_mapping);
> +
> +	/*
> +	 * Restore the HW state while the stream still holds its runtime PM ref and exec_q.
> +	 * The device is not reset on unbind, so this can't be left to a later reset.
> +	 */
> +	if (hw_accessible)
> +		xe_oa_stream_hw_teardown(stream);
> +
> +	xe_oa_stream_free_resources(stream);

[Severity: High]
Does holding stream->mmap_mutex across xe_oa_stream_hw_teardown() and
xe_oa_stream_free_resources() introduce a lock ordering inversion with
mm->mmap_lock?

In xe_oa_mmap(), stream->mmap_mutex is taken inside file->f_op->mmap under
mm->mmap_lock, which is why struct xe_oa_stream documents mmap_mutex as a
leaf lock.

However, xe_oa_quiesce_oa_unit() holds stream->mmap_mutex across:
- xe_oa_stream_hw_teardown() -> xe_oa_disable_metric_set() ->
  xe_oa_configure_oa_context() -> xe_oa_load_with_lri() ->
  xe_oa_submit_bb() -> xe_oa_lock_vma(), which takes down_read(&q->vm->lock)
  and xe_vm_lock(q->vm, false)
- xe_oa_stream_free_resources() -> xe_oa_free_oa_buffer() ->
  xe_bo_unpin_map_no_vm(), which locks the BO dma_resv

Since normal VM and fault paths establish vm->lock -> mm->mmap_lock ->
dma_resv, taking vm->lock and dma_resv under stream->mmap_mutex inverts the
lock hierarchy. Since xe_oa_mmap_locked() only checks READ_ONCE(stream->dead)
and maps stream->oa_buffer.bo, could stream->mmap_mutex be held only around
setting stream->dead = true?

[Severity: High]
When userspace has an active mmap VMA on the OA stream during unbind or PCI
slot reset, does stream->oa_buffer.bo miss both eviction and DMA unmapping?

In xe_oa_mmap_locked(), drm_gem_mmap_obj() takes a GEM reference on
stream->oa_buffer.bo that is held by vma->vm_private_data until munmap() or
process exit. During device removal:

1. xe_device_remove() calls xe_bo_pci_dev_remove_all(xe) before devm actions
   run. At that point stream->oa_buffer.bo is still pinned (pin_count == 1 on
   xe->pinned.early.kernel_bo_present), so ttm_resource_manager_evict_all()
   skips it.
2. Next, devres runs xe_oa_fini() -> xe_oa_quiesce_oa_unit() ->
   xe_oa_stream_free_resources() -> xe_oa_free_oa_buffer() ->
   xe_bo_unpin_map_no_vm(stream->oa_buffer.bo). Because drm_vma_node_unmap()
   only zaps PTEs without closing the VMA, xe_bo_unpin_map_no_vm() unpins the
   BO, removes bo->pinned_link from xe->pinned.early.kernel_bo_present, and
   drops one reference, leaving the BO alive with refcount >= 1.
3. When the devm action xe_bo_pinned_fini() subsequently runs to call
   xe_bo_dma_unmap_pinned() on kernel_bo_present, stream->oa_buffer.bo is no
   longer on the list.

As a result, the OA BO remains allocated with its DMA mapping and TTM
resource active after PCI device removal until userspace closes the VMA.

[Severity: High]
In xe_oa_quiesce_oa_unit(), can the asynchronous batchbuffer submitted by
xe_oa_stream_hw_teardown() race with subsequent devres teardown of GuC
submission and fence IRQs?

When hw_accessible is true and stream->exec_q is set,
xe_oa_stream_hw_teardown() calls:

  xe_oa_disable_metric_set()
    -> xe_oa_configure_oa_context(stream, false)
      -> xe_oa_load_with_lri()

which submits a batchbuffer job onto stream->exec_q via xe_oa_submit_bb(),
hands the unsignaled fence to xe_bb_free(bb, fence), and returns immediately
without waiting for the fence to signal.

xe_oa_quiesce_oa_unit() then immediately drops forcewake, the runtime PM
reference, and stream->exec_q, and returns to devres_release_all(), which
proceeds to guc_submit_fini() and xe_gt_fini() -> xe_hw_fence_irq_finish().
If the scheduler arms job->fence onto gt->fence_irq[i].pending before GuC
teardown / TDR signals it, won't xe_hw_fence_irq_finish() trigger
XE_WARN_ON(!list_empty(&irq->pending)) or leave the context restore LRI
unexecuted?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261030223056.2266638-5-umesh.nerlige.ramappa@intel.com?part=3

  reply	other threads:[~2026-10-30 23:15 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-30 22:30 [PATCH v2 0/3] Cleanup OA stream on PCI unbind/remove Umesh Nerlige Ramappa
2026-10-30 22:30 ` [PATCH v2 1/3] drm/xe/oa: Use the drm device address_space for the OA stream fd Umesh Nerlige Ramappa
2026-10-30 22:30 ` [PATCH v2 2/3] drm/xe/oa: Gate OA buffer mmap faults Umesh Nerlige Ramappa
2026-10-30 22:52   ` sashiko-bot
2026-10-30 22:31 ` [PATCH v2 3/3] drm/xe/oa: Quiesce OA streams on driver unbind and PCI reset Umesh Nerlige Ramappa
2026-10-30 23:14   ` sashiko-bot [this message]
2026-10-30 22:36 ` [PATCH v2 0/3] Cleanup OA stream on PCI unbind/remove Umesh Nerlige Ramappa
2026-10-30 22:40 ` ✓ CI.KUnit: success for " Patchwork
2026-10-30 23:14 ` ✓ 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=sashiko-outbox-191625@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=umesh.nerlige.ramappa@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