Contributors: 9
Author Tokens Token Proportion Commits Commit Proportion
Matthew Brost 443 58.60% 1 7.14%
Zbigniew Kempczyński 109 14.42% 1 7.14%
Bommu Krishnaiah 102 13.49% 2 14.29%
Fei Yang 57 7.54% 1 7.14%
Chris Moeller 19 2.51% 1 7.14%
Francois Dugast 11 1.46% 4 28.57%
Lucas De Marchi 7 0.93% 2 14.29%
Rodrigo Vivi 7 0.93% 1 7.14%
Colin Ian King 1 0.13% 1 7.14%
Total 756 14


// 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;
}