From: sashiko-bot@kernel.org
To: "Umesh Nerlige Ramappa" <umesh.nerlige.ramappa@intel.com>,
intel-xe@lists.freedesktop.org
Subject: Re: [PATCH v3 3/3] drm/xe/oa: Quiesce OA streams on driver unbind and PCI reset
Date: Sat, 31 Oct 2026 00:04:23 +0000 [thread overview]
Message-ID: <sashiko-outbox-191663@kernel.org> (raw)
In-Reply-To: <20261030233839.2284694-8-umesh.nerlige.ramappa@intel.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] drm/xe/oa: NULL deref and dummy page race in xe_oa_buffer_fault()
- [High] drm/xe/oa: race between in-flight observation ioctls and xe_oa_fini()
- [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: unwaited LRI job in xe_oa_quiesce_oa_unit() races devres teardown
--
commit 57d7033e8652d53b8521bba4a5103beb77f1ab8d
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 clean up active OA stream hardware state
and device-bound resources during driver unbind or PCI reset while keeping the
stream structure allocated until the file descriptor is closed. It also adds
stream->mmap_mutex to avoid a lock ordering inversion between mmap_lock and
stream_lock and introduces a per-GT quiesced flag to block new streams during
teardown.
> diff --git a/drivers/gpu/drm/xe/xe_oa.c b/drivers/gpu/drm/xe/xe_oa.c
> index 4916101fce541..f737ae4976a82 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]
As reported by Sashiko on v2 [1], can xe_oa_buffer_fault() dereference a NULL
pointer or still install a dummy page when racing with device removal?
In drivers/gpu/drm/xe/xe_oa.c, xe_oa_buffer_fault() runs without holding
stream_lock, mmap_mutex, or gt_lock. If a fault evaluates
READ_ONCE(stream->dead) as false just before xe_oa_quiesce_oa_unit() sets
stream->dead = true, and xe_oa_fini() completes and sets oa->xe = NULL before
the second operand of the logical OR is evaluated:
xe_oa_fini()
xe_oa_quiesce(xe);
...
oa->xe = NULL;
then &stream->oa->xe->drm evaluates to NULL (since drm is at offset 0 of
struct xe_device), and drm_dev_is_unplugged(NULL) calls drm_dev_enter(NULL,
&idx), dereferencing a NULL pointer (whereas xe_oa_mmap(),
xe_oa_destroy_locked(), and xe_oa_release() use gt_to_xe(stream->gt)).
Additionally, drm_dev_is_unplugged() immediately drops its SRCU read lock via
drm_dev_exit(). If drm_dev_unplug() and drm_vma_node_unmap() run between
drm_dev_is_unplugged() in xe_oa_buffer_fault() and drm_dev_enter() in
xe_bo_cpu_fault(), or if the device was marked wedged prior to unplug (such as
in xe_pci_error_detected()), xe_bo_cpu_fault() hits:
if (xe_wedged(xe) || !drm_dev_enter(&xe->drm, &idx))
return ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot);
Would that still install 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]
As reported by Sashiko on v2 [2], does leaving .access wired directly to
xe_bo_vm_access in xe_oa_vm_ops allow userspace to access the OA buffer and
device state after xe_oa_quiesce()?
When xe_oa_quiesce_oa_unit() calls drm_vma_node_unmap(), PTEs are zapped, but
any existing userspace VMA remains open with VM_PFNMAP set and retains a GEM
reference on stream->oa_buffer.bo in vma->vm_private_data. If userspace reads
the mapping via /proc/self/mem, process_vm_readv(), or ptrace after
xe_oa_quiesce(), get_user_pages() receives VM_FAULT_SIGBUS from
xe_oa_buffer_fault() and __access_remote_vm() falls back to
vma->vm_ops->access(), calling xe_bo_vm_access():
int xe_bo_vm_access(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write)
{
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 xe_bo_vm_access() does not check stream->dead or drm_dev_enter(), can
this acquire runtime PM after xe_pm_fini() and, when the OA buffer is in VRAM
under mert_wa_14026633728, dereference vram->mapping in xe_ttm_access_memory()
after vram_fini() has cleared and unmapped it?
[ ... ]
> @@ -2617,6 +2689,97 @@ 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);
> + if (stream->enabled && hw_accessible)
> + xe_oa_disable_locked(stream);
> +
> + stream->enabled = false;
> +
> + /*
> + * Once dead is set under mmap_mutex, no new xe_oa_mmap() can reference the OA buffer.
> + * Don't hold mmap_mutex any longer than that: it nests under mmap_lock, and the HW
> + * teardown below submits batches, taking vm->lock and the vm resv.
> + */
> + mutex_lock(&stream->mmap_mutex);
> + spin_lock_irqsave(&stream->oa_buffer.ptr_lock, flags);
> + WRITE_ONCE(stream->dead, true);
> + spin_unlock_irqrestore(&stream->oa_buffer.ptr_lock, flags);
> + mutex_unlock(&stream->mmap_mutex);
> +
> + 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);
[Severity: High]
As reported by Sashiko on v2 [3], when stream->exec_q is set, does
xe_oa_stream_hw_teardown() submit an asynchronous batchbuffer job without
waiting for it to complete before devres tears down GuC submission and fence
IRQs?
In xe_oa_quiesce_oa_unit(), xe_oa_stream_hw_teardown() calls
xe_oa_disable_metric_set(), which calls:
if (stream->exec_q)
xe_oa_configure_oa_context(stream, false);
xe_oa_configure_oa_context() -> xe_oa_load_with_lri() submits a batchbuffer on
stream->exec_q via xe_oa_submit_bb(), passes the unsignaled fence to
xe_bb_free(bb, fence), and returns immediately without waiting for completion:
fence = xe_oa_submit_bb(stream, XE_OA_SUBMIT_NO_DEPS, bb);
if (IS_ERR(fence)) {
err = PTR_ERR(fence);
goto free_bb;
}
xe_bb_free(bb, fence);
dma_fence_put(fence);
xe_oa_stream_hw_teardown() and xe_oa_quiesce_oa_unit() then immediately drop
forcewake, the runtime PM reference, and stream->exec_q, and devres proceeds
directly to guc_submit_fini() and xe_gt_fini() -> xe_hw_fence_irq_finish().
Can that leave the job's fence pending in irq->pending when
xe_hw_fence_irq_finish() checks XE_WARN_ON(!list_empty(&irq->pending)), or arm
the fence after xe_hw_fence_irq_finish() has already run?
> +
> + xe_oa_stream_free_resources(stream);
[Severity: High]
As reported by Sashiko on v2 [4], if userspace has an active mmap VMA on the
OA stream across driver unbind or PCI slot reset, does unpinning
stream->oa_buffer.bo here leave the BO neither evicted nor DMA-unmapped after
PCI device removal?
In xe_oa_mmap_locked(), drm_gem_mmap_obj() takes a GEM reference on
stream->oa_buffer.bo that is held until ttm_bo_vm_close() runs when the VMA is
closed. On remove:
1. xe_device_remove() calls xe_bo_pci_dev_remove_all(xe). Because xe_oa_fini()
is a devm callback that has not run yet, stream->oa_buffer.bo still has
pin_count == 1, so ttm_resource_manager_evict_all() skips it and
xe_bo_pci_dev_remove_pinned() only unmaps xe->pinned.late.external.
2. During devres release, xe_oa_fini() runs before xe_bo_pinned_fini().
drm_vma_node_unmap() only zaps PTEs without closing the VMA, and
xe_oa_stream_free_resources() -> xe_oa_free_oa_buffer() calls
xe_bo_unpin_map_no_vm(stream->oa_buffer.bo). That unpins the BO, removes
bo->pinned_link from xe->pinned.early.kernel_bo_present, and drops the
stream's BO reference, but the BO stays alive because the VMA still holds a
reference.
3. When xe_bo_pinned_fini() then runs, stream->oa_buffer.bo is no longer on
xe->pinned.early.kernel_bo_present, so xe_bo_dma_unmap_pinned() never runs on
it either.
Doesn't this leave the OA buffer's DMA mapping and TTM resource active after
the PCI device is removed until userspace eventually unmaps the VMA?
[ ... ]
> @@ -2928,6 +3091,12 @@ static void xe_oa_fini(void *arg)
> if (!oa->xe)
> return;
>
> + /*
> + * Devm action, so this runs on unbind and PCI slot reset after xe_device_remove() even
> + * if streams are still open. Quiesce them before the configs they hold are destroyed.
> + */
> + xe_oa_quiesce(xe);
> +
> idr_for_each(&oa->metrics_idr, destroy_config, oa);
> idr_destroy(&oa->metrics_idr);
>
[Severity: High]
As reported by Sashiko on v2 [5], can an in-flight observation ioctl that
entered drm_ioctl() before drm_dev_unplug() race with xe_oa_fini()
destroying oa->metrics_idr and setting oa->xe = NULL?
While xe_oa_stream_open_ioctl_locked() checks param->hwe->gt->oa.quiesced
under gt_lock, xe_oa_stream_open_ioctl() only checks !oa->xe unlocked at
entry before sleeping in copy_from_user() inside xe_oa_user_extensions() and
before acquiring gt_lock:
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, ¶m);
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 after the initial
check, can xe_oa_stream_open_ioctl() dereference NULL oa->xe in
xe_oa_lookup_oa_unit(), xe_root_mmio_gt(oa->xe), oa_unit_supports_oa_format(),
or drm_dbg(&oa->xe->drm, ...) in xe_oa_stream_open_ioctl_locked(), or read
MMIO in oa_exponent_to_ns() -> xe_oa_timestamp_frequency() before taking
gt_lock?
Similarly, since xe_oa_fini() destroys oa->metrics_idr without taking
oa->metrics_lock, can an in-flight xe_oa_add_config_ioctl() or
xe_oa_remove_config_ioctl() dereference NULL oa->xe in
xe_oa_is_valid_config_reg() or access oa->metrics_idr concurrently with
idr_destroy()?
[1] https://lore.kernel.org/r/20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com
[2] https://lore.kernel.org/r/20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com
[3] https://lore.kernel.org/r/20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com
[4] https://lore.kernel.org/r/20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com
[5] https://lore.kernel.org/r/20261030223056.2266638-8-umesh.nerlige.ramappa@intel.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030233839.2284694-5-umesh.nerlige.ramappa@intel.com?part=3
next prev parent reply other threads:[~2026-10-31 0:04 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 23:38 [PATCH v3 0/3] Cleanup OA stream on PCI unbind/remove Umesh Nerlige Ramappa
2026-10-30 23:38 ` [PATCH v3 1/3] drm/xe/oa: Use the drm device address_space for the OA stream fd Umesh Nerlige Ramappa
2026-10-30 23:38 ` [PATCH v3 2/3] drm/xe/oa: Gate OA buffer mmap faults Umesh Nerlige Ramappa
2026-10-30 23:58 ` sashiko-bot
2026-10-30 23:38 ` [PATCH v3 3/3] drm/xe/oa: Quiesce OA streams on driver unbind and PCI reset Umesh Nerlige Ramappa
2026-10-31 0:04 ` sashiko-bot [this message]
2026-10-30 23:55 ` ✓ CI.KUnit: success for Cleanup OA stream on PCI unbind/remove (rev2) Patchwork
2026-10-31 0:33 ` ✓ 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-191663@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