mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-03-03 18:28:01 +01:00
When dma_fence_chain_alloc() fails, properly release the user fence
reference to prevent a memory leak.
Fixes: 0995c2fc39 ("drm/xe: Enforce correct user fence signaling order using")
Cc: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Shuicheng Lin <shuicheng.lin@intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patch.msgid.link/20260219233516.2938172-6-shuicheng.lin@intel.com
(cherry picked from commit a5d5634cde48a9fcd68c8504aa07f89f175074a0)
Cc: stable@vger.kernel.org
Signed-off-by: Rodrigo Vivi <rodrigo.vivi@intel.com>
447 lines
11 KiB
C
447 lines
11 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2021 Intel Corporation
|
|
*/
|
|
|
|
#include "xe_sync.h"
|
|
|
|
#include <linux/dma-fence-array.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/sched/mm.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_syncobj.h>
|
|
#include <uapi/drm/xe_drm.h>
|
|
|
|
#include "xe_device.h"
|
|
#include "xe_exec_queue.h"
|
|
#include "xe_macros.h"
|
|
#include "xe_sched_job_types.h"
|
|
|
|
struct xe_user_fence {
|
|
struct xe_device *xe;
|
|
struct kref refcount;
|
|
struct dma_fence_cb cb;
|
|
struct work_struct worker;
|
|
struct mm_struct *mm;
|
|
u64 __user *addr;
|
|
u64 value;
|
|
int signalled;
|
|
};
|
|
|
|
static void user_fence_destroy(struct kref *kref)
|
|
{
|
|
struct xe_user_fence *ufence = container_of(kref, struct xe_user_fence,
|
|
refcount);
|
|
|
|
mmdrop(ufence->mm);
|
|
kfree(ufence);
|
|
}
|
|
|
|
static void user_fence_get(struct xe_user_fence *ufence)
|
|
{
|
|
kref_get(&ufence->refcount);
|
|
}
|
|
|
|
static void user_fence_put(struct xe_user_fence *ufence)
|
|
{
|
|
kref_put(&ufence->refcount, user_fence_destroy);
|
|
}
|
|
|
|
static struct xe_user_fence *user_fence_create(struct xe_device *xe, u64 addr,
|
|
u64 value)
|
|
{
|
|
struct xe_user_fence *ufence;
|
|
u64 __user *ptr = u64_to_user_ptr(addr);
|
|
u64 __maybe_unused prefetch_val;
|
|
|
|
if (get_user(prefetch_val, ptr))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
ufence = kzalloc_obj(*ufence);
|
|
if (!ufence)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ufence->xe = xe;
|
|
kref_init(&ufence->refcount);
|
|
ufence->addr = ptr;
|
|
ufence->value = value;
|
|
ufence->mm = current->mm;
|
|
mmgrab(ufence->mm);
|
|
|
|
return ufence;
|
|
}
|
|
|
|
static void user_fence_worker(struct work_struct *w)
|
|
{
|
|
struct xe_user_fence *ufence = container_of(w, struct xe_user_fence, worker);
|
|
|
|
WRITE_ONCE(ufence->signalled, 1);
|
|
if (mmget_not_zero(ufence->mm)) {
|
|
kthread_use_mm(ufence->mm);
|
|
if (copy_to_user(ufence->addr, &ufence->value, sizeof(ufence->value)))
|
|
XE_WARN_ON("Copy to user failed");
|
|
kthread_unuse_mm(ufence->mm);
|
|
mmput(ufence->mm);
|
|
} else {
|
|
drm_dbg(&ufence->xe->drm, "mmget_not_zero() failed, ufence wasn't signaled\n");
|
|
}
|
|
|
|
/*
|
|
* Wake up waiters only after updating the ufence state, allowing the UMD
|
|
* to safely reuse the same ufence without encountering -EBUSY errors.
|
|
*/
|
|
wake_up_all(&ufence->xe->ufence_wq);
|
|
user_fence_put(ufence);
|
|
}
|
|
|
|
static void kick_ufence(struct xe_user_fence *ufence, struct dma_fence *fence)
|
|
{
|
|
INIT_WORK(&ufence->worker, user_fence_worker);
|
|
queue_work(ufence->xe->ordered_wq, &ufence->worker);
|
|
dma_fence_put(fence);
|
|
}
|
|
|
|
static void user_fence_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
|
|
{
|
|
struct xe_user_fence *ufence = container_of(cb, struct xe_user_fence, cb);
|
|
|
|
kick_ufence(ufence, fence);
|
|
}
|
|
|
|
int xe_sync_entry_parse(struct xe_device *xe, struct xe_file *xef,
|
|
struct xe_sync_entry *sync,
|
|
struct drm_xe_sync __user *sync_user,
|
|
struct drm_syncobj *ufence_syncobj,
|
|
u64 ufence_timeline_value,
|
|
unsigned int flags)
|
|
{
|
|
struct drm_xe_sync sync_in;
|
|
int err;
|
|
bool exec = flags & SYNC_PARSE_FLAG_EXEC;
|
|
bool in_lr_mode = flags & SYNC_PARSE_FLAG_LR_MODE;
|
|
bool disallow_user_fence = flags & SYNC_PARSE_FLAG_DISALLOW_USER_FENCE;
|
|
bool signal;
|
|
|
|
if (copy_from_user(&sync_in, sync_user, sizeof(*sync_user)))
|
|
return -EFAULT;
|
|
|
|
if (XE_IOCTL_DBG(xe, sync_in.flags & ~DRM_XE_SYNC_FLAG_SIGNAL) ||
|
|
XE_IOCTL_DBG(xe, sync_in.reserved[0] || sync_in.reserved[1]))
|
|
return -EINVAL;
|
|
|
|
signal = sync_in.flags & DRM_XE_SYNC_FLAG_SIGNAL;
|
|
switch (sync_in.type) {
|
|
case DRM_XE_SYNC_TYPE_SYNCOBJ:
|
|
if (XE_IOCTL_DBG(xe, in_lr_mode && signal))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (XE_IOCTL_DBG(xe, upper_32_bits(sync_in.addr)))
|
|
return -EINVAL;
|
|
|
|
sync->syncobj = drm_syncobj_find(xef->drm, sync_in.handle);
|
|
if (XE_IOCTL_DBG(xe, !sync->syncobj))
|
|
return -ENOENT;
|
|
|
|
if (!signal) {
|
|
sync->fence = drm_syncobj_fence_get(sync->syncobj);
|
|
if (XE_IOCTL_DBG(xe, !sync->fence)) {
|
|
err = -EINVAL;
|
|
goto free_sync;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DRM_XE_SYNC_TYPE_TIMELINE_SYNCOBJ:
|
|
if (XE_IOCTL_DBG(xe, in_lr_mode && signal))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (XE_IOCTL_DBG(xe, upper_32_bits(sync_in.addr)))
|
|
return -EINVAL;
|
|
|
|
if (XE_IOCTL_DBG(xe, sync_in.timeline_value == 0))
|
|
return -EINVAL;
|
|
|
|
sync->syncobj = drm_syncobj_find(xef->drm, sync_in.handle);
|
|
if (XE_IOCTL_DBG(xe, !sync->syncobj))
|
|
return -ENOENT;
|
|
|
|
if (signal) {
|
|
sync->chain_fence = dma_fence_chain_alloc();
|
|
if (!sync->chain_fence) {
|
|
err = -ENOMEM;
|
|
goto free_sync;
|
|
}
|
|
} else {
|
|
sync->fence = drm_syncobj_fence_get(sync->syncobj);
|
|
if (XE_IOCTL_DBG(xe, !sync->fence)) {
|
|
err = -EINVAL;
|
|
goto free_sync;
|
|
}
|
|
|
|
err = dma_fence_chain_find_seqno(&sync->fence,
|
|
sync_in.timeline_value);
|
|
if (err)
|
|
goto free_sync;
|
|
}
|
|
break;
|
|
|
|
case DRM_XE_SYNC_TYPE_USER_FENCE:
|
|
if (XE_IOCTL_DBG(xe, disallow_user_fence))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (XE_IOCTL_DBG(xe, !signal))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (XE_IOCTL_DBG(xe, sync_in.addr & 0x7))
|
|
return -EINVAL;
|
|
|
|
if (exec) {
|
|
sync->addr = sync_in.addr;
|
|
} else {
|
|
sync->ufence_timeline_value = ufence_timeline_value;
|
|
sync->ufence = user_fence_create(xe, sync_in.addr,
|
|
sync_in.timeline_value);
|
|
if (XE_IOCTL_DBG(xe, IS_ERR(sync->ufence)))
|
|
return PTR_ERR(sync->ufence);
|
|
sync->ufence_chain_fence = dma_fence_chain_alloc();
|
|
if (!sync->ufence_chain_fence) {
|
|
err = -ENOMEM;
|
|
goto free_sync;
|
|
}
|
|
sync->ufence_syncobj = ufence_syncobj;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
sync->type = sync_in.type;
|
|
sync->flags = sync_in.flags;
|
|
sync->timeline_value = sync_in.timeline_value;
|
|
|
|
return 0;
|
|
|
|
free_sync:
|
|
xe_sync_entry_cleanup(sync);
|
|
return err;
|
|
}
|
|
ALLOW_ERROR_INJECTION(xe_sync_entry_parse, ERRNO);
|
|
|
|
int xe_sync_entry_add_deps(struct xe_sync_entry *sync, struct xe_sched_job *job)
|
|
{
|
|
if (sync->fence)
|
|
return drm_sched_job_add_dependency(&job->drm,
|
|
dma_fence_get(sync->fence));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* xe_sync_entry_wait() - Wait on in-sync
|
|
* @sync: Sync object
|
|
*
|
|
* If the sync is in an in-sync, wait on the sync to signal.
|
|
*
|
|
* Return: 0 on success, -ERESTARTSYS on failure (interruption)
|
|
*/
|
|
int xe_sync_entry_wait(struct xe_sync_entry *sync)
|
|
{
|
|
return xe_sync_needs_wait(sync) ?
|
|
dma_fence_wait(sync->fence, true) : 0;
|
|
}
|
|
|
|
/**
|
|
* xe_sync_needs_wait() - Sync needs a wait (input dma-fence not signaled)
|
|
* @sync: Sync object
|
|
*
|
|
* Return: True if sync needs a wait, False otherwise
|
|
*/
|
|
bool xe_sync_needs_wait(struct xe_sync_entry *sync)
|
|
{
|
|
return sync->fence &&
|
|
!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &sync->fence->flags);
|
|
}
|
|
|
|
void xe_sync_entry_signal(struct xe_sync_entry *sync, struct dma_fence *fence)
|
|
{
|
|
if (!(sync->flags & DRM_XE_SYNC_FLAG_SIGNAL))
|
|
return;
|
|
|
|
if (sync->chain_fence) {
|
|
drm_syncobj_add_point(sync->syncobj, sync->chain_fence,
|
|
fence, sync->timeline_value);
|
|
/*
|
|
* The chain's ownership is transferred to the
|
|
* timeline.
|
|
*/
|
|
sync->chain_fence = NULL;
|
|
} else if (sync->syncobj) {
|
|
drm_syncobj_replace_fence(sync->syncobj, fence);
|
|
} else if (sync->ufence) {
|
|
int err;
|
|
|
|
drm_syncobj_add_point(sync->ufence_syncobj,
|
|
sync->ufence_chain_fence,
|
|
fence, sync->ufence_timeline_value);
|
|
sync->ufence_chain_fence = NULL;
|
|
|
|
fence = drm_syncobj_fence_get(sync->ufence_syncobj);
|
|
user_fence_get(sync->ufence);
|
|
err = dma_fence_add_callback(fence, &sync->ufence->cb,
|
|
user_fence_cb);
|
|
if (err == -ENOENT) {
|
|
kick_ufence(sync->ufence, fence);
|
|
} else if (err) {
|
|
XE_WARN_ON("failed to add user fence");
|
|
user_fence_put(sync->ufence);
|
|
dma_fence_put(fence);
|
|
}
|
|
}
|
|
}
|
|
|
|
void xe_sync_entry_cleanup(struct xe_sync_entry *sync)
|
|
{
|
|
if (sync->syncobj)
|
|
drm_syncobj_put(sync->syncobj);
|
|
dma_fence_put(sync->fence);
|
|
dma_fence_chain_free(sync->chain_fence);
|
|
dma_fence_chain_free(sync->ufence_chain_fence);
|
|
if (!IS_ERR_OR_NULL(sync->ufence))
|
|
user_fence_put(sync->ufence);
|
|
}
|
|
|
|
/**
|
|
* xe_sync_in_fence_get() - Get a fence from syncs, exec queue, and VM
|
|
* @sync: input syncs
|
|
* @num_sync: number of syncs
|
|
* @q: exec queue
|
|
* @vm: VM
|
|
*
|
|
* Get a fence from syncs, exec queue, and VM. If syncs contain in-fences create
|
|
* and return a composite fence of all in-fences + last fence. If no in-fences
|
|
* return last fence on input exec queue. Caller must drop reference to
|
|
* returned fence.
|
|
*
|
|
* Return: fence on success, ERR_PTR(-ENOMEM) on failure
|
|
*/
|
|
struct dma_fence *
|
|
xe_sync_in_fence_get(struct xe_sync_entry *sync, int num_sync,
|
|
struct xe_exec_queue *q, struct xe_vm *vm)
|
|
{
|
|
struct dma_fence **fences = NULL;
|
|
struct dma_fence_array *cf = NULL;
|
|
struct dma_fence *fence;
|
|
int i, num_fence = 0, current_fence = 0;
|
|
|
|
lockdep_assert_held(&vm->lock);
|
|
|
|
/* Reject in fences */
|
|
for (i = 0; i < num_sync; ++i)
|
|
if (sync[i].fence)
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
|
|
if (q->flags & EXEC_QUEUE_FLAG_VM) {
|
|
struct xe_exec_queue *__q;
|
|
struct xe_tile *tile;
|
|
u8 id;
|
|
|
|
for_each_tile(tile, vm->xe, id) {
|
|
num_fence++;
|
|
for_each_tlb_inval(i)
|
|
num_fence++;
|
|
}
|
|
|
|
fences = kmalloc_objs(*fences, num_fence);
|
|
if (!fences)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
fences[current_fence++] =
|
|
xe_exec_queue_last_fence_get(q, vm);
|
|
for_each_tlb_inval(i)
|
|
fences[current_fence++] =
|
|
xe_exec_queue_tlb_inval_last_fence_get(q, vm, i);
|
|
list_for_each_entry(__q, &q->multi_gt_list,
|
|
multi_gt_link) {
|
|
fences[current_fence++] =
|
|
xe_exec_queue_last_fence_get(__q, vm);
|
|
for_each_tlb_inval(i)
|
|
fences[current_fence++] =
|
|
xe_exec_queue_tlb_inval_last_fence_get(__q, vm, i);
|
|
}
|
|
|
|
xe_assert(vm->xe, current_fence == num_fence);
|
|
cf = dma_fence_array_create(num_fence, fences,
|
|
dma_fence_context_alloc(1),
|
|
1, false);
|
|
if (!cf)
|
|
goto err_out;
|
|
|
|
return &cf->base;
|
|
}
|
|
|
|
fence = xe_exec_queue_last_fence_get(q, vm);
|
|
return fence;
|
|
|
|
err_out:
|
|
while (current_fence)
|
|
dma_fence_put(fences[--current_fence]);
|
|
kfree(fences);
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/**
|
|
* __xe_sync_ufence_get() - Get user fence from user fence
|
|
* @ufence: input user fence
|
|
*
|
|
* Get a user fence reference from user fence
|
|
*
|
|
* Return: xe_user_fence pointer with reference
|
|
*/
|
|
struct xe_user_fence *__xe_sync_ufence_get(struct xe_user_fence *ufence)
|
|
{
|
|
user_fence_get(ufence);
|
|
|
|
return ufence;
|
|
}
|
|
|
|
/**
|
|
* xe_sync_ufence_get() - Get user fence from sync
|
|
* @sync: input sync
|
|
*
|
|
* Get a user fence reference from sync.
|
|
*
|
|
* Return: xe_user_fence pointer with reference
|
|
*/
|
|
struct xe_user_fence *xe_sync_ufence_get(struct xe_sync_entry *sync)
|
|
{
|
|
user_fence_get(sync->ufence);
|
|
|
|
return sync->ufence;
|
|
}
|
|
|
|
/**
|
|
* xe_sync_ufence_put() - Put user fence reference
|
|
* @ufence: user fence reference
|
|
*
|
|
*/
|
|
void xe_sync_ufence_put(struct xe_user_fence *ufence)
|
|
{
|
|
user_fence_put(ufence);
|
|
}
|
|
|
|
/**
|
|
* xe_sync_ufence_get_status() - Get user fence status
|
|
* @ufence: user fence
|
|
*
|
|
* Return: 1 if signalled, 0 not signalled, <0 on error
|
|
*/
|
|
int xe_sync_ufence_get_status(struct xe_user_fence *ufence)
|
|
{
|
|
return READ_ONCE(ufence->signalled);
|
|
}
|