blob: f69721339201d68aa28bce9610d89add4059c986 [file] [log] [blame]
// SPDX-License-Identifier: MIT
/*
* Copyright © 2022 Intel Corporation
*/
#include "xe_wait_user_fence.h"
#include <drm/drm_device.h>
#include <drm/drm_file.h>
#include <drm/drm_utils.h>
#include <drm/xe_drm.h>
#include "xe_device.h"
#include "xe_gt.h"
#include "xe_macros.h"
#include "xe_exec_queue.h"
static int do_compare(u64 addr, u64 value, u64 mask, u16 op)
{
u64 rvalue;
int err;
bool passed;
err = copy_from_user(&rvalue, u64_to_user_ptr(addr), sizeof(rvalue));
if (err)
return -EFAULT;
switch (op) {
case DRM_XE_UFENCE_WAIT_OP_EQ:
passed = (rvalue & mask) == (value & mask);
break;
case DRM_XE_UFENCE_WAIT_OP_NEQ:
passed = (rvalue & mask) != (value & mask);
break;
case DRM_XE_UFENCE_WAIT_OP_GT:
passed = (rvalue & mask) > (value & mask);
break;
case DRM_XE_UFENCE_WAIT_OP_GTE:
passed = (rvalue & mask) >= (value & mask);
break;
case DRM_XE_UFENCE_WAIT_OP_LT:
passed = (rvalue & mask) < (value & mask);
break;
case DRM_XE_UFENCE_WAIT_OP_LTE:
passed = (rvalue & mask) <= (value & mask);
break;
default:
XE_WARN_ON("Not possible");
return -EINVAL;
}
return passed ? 0 : 1;
}
#define VALID_FLAGS DRM_XE_UFENCE_WAIT_FLAG_ABSTIME
#define MAX_OP DRM_XE_UFENCE_WAIT_OP_LTE
static long to_jiffies_timeout(struct xe_device *xe,
struct drm_xe_wait_user_fence *args)
{
unsigned long long t;
long timeout;
/*
* For negative timeout we want to wait "forever" by setting
* MAX_SCHEDULE_TIMEOUT. But we have to assign this value also
* to args->timeout to avoid being zeroed on the signal delivery
* (see arithmetics after wait).
*/
if (args->timeout < 0) {
args->timeout = MAX_SCHEDULE_TIMEOUT;
return MAX_SCHEDULE_TIMEOUT;
}
if (args->timeout == 0)
return 0;
/*
* Save the timeout to an u64 variable because nsecs_to_jiffies
* might return a value that overflows s32 variable.
*/
if (args->flags & DRM_XE_UFENCE_WAIT_FLAG_ABSTIME)
t = drm_timeout_abs_to_jiffies(args->timeout);
else
t = nsecs_to_jiffies(args->timeout);
/*
* Anything greater then MAX_SCHEDULE_TIMEOUT is meaningless,
* also we don't want to cap it at MAX_SCHEDULE_TIMEOUT because
* apparently user doesn't mean to wait forever, otherwise the
* args->timeout should have been set to a negative value.
*/
if (t > MAX_SCHEDULE_TIMEOUT)
timeout = MAX_SCHEDULE_TIMEOUT - 1;
else
timeout = t;
return timeout ?: 1;
}
int xe_wait_user_fence_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct xe_device *xe = to_xe_device(dev);
struct xe_file *xef = to_xe_file(file);
DEFINE_WAIT_FUNC(w_wait, woken_wake_function);
struct drm_xe_wait_user_fence *args = data;
struct xe_exec_queue *q = NULL;
u64 addr = args->addr;
int err = 0;
long timeout;
ktime_t start;
if (XE_IOCTL_DBG(xe, args->extensions) || XE_IOCTL_DBG(xe, args->pad) ||
XE_IOCTL_DBG(xe, args->pad2) ||
XE_IOCTL_DBG(xe, args->reserved[0] || args->reserved[1]))
return -EINVAL;
if (XE_IOCTL_DBG(xe, args->flags & ~VALID_FLAGS))
return -EINVAL;
if (XE_IOCTL_DBG(xe, args->op > MAX_OP))
return -EINVAL;
if (XE_IOCTL_DBG(xe, addr & 0x7))
return -EINVAL;
if (args->exec_queue_id) {
q = xe_exec_queue_lookup(xef, args->exec_queue_id);
if (XE_IOCTL_DBG(xe, !q))
return -ENOENT;
}
timeout = to_jiffies_timeout(xe, args);
start = ktime_get();
add_wait_queue(&xe->ufence_wq, &w_wait);
for (;;) {
err = do_compare(addr, args->value, args->mask, args->op);
if (err <= 0)
break;
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
if (q) {
if (q->ops->reset_status(q)) {
drm_info(&xe->drm, "exec queue reset detected\n");
err = -EIO;
break;
}
}
if (!timeout) {
err = -ETIME;
break;
}
timeout = wait_woken(&w_wait, TASK_INTERRUPTIBLE, timeout);
}
remove_wait_queue(&xe->ufence_wq, &w_wait);
if (!(args->flags & DRM_XE_UFENCE_WAIT_FLAG_ABSTIME)) {
args->timeout -= ktime_to_ns(ktime_sub(ktime_get(), start));
if (args->timeout < 0)
args->timeout = 0;
}
if (!timeout && !(err < 0))
err = -ETIME;
if (q)
xe_exec_queue_put(q);
return err;
}