root/drivers/gpu/drm/xe/xe_wait_user_fence.c
// 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 <uapi/drm/xe_drm.h>

#include "xe_device.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) {
                        LNL_FLUSH_WORKQUEUE(xe->ordered_wq);
                        err = do_compare(addr, args->value, args->mask,
                                         args->op);
                        if (err <= 0) {
                                drm_dbg(&xe->drm, "LNL_FLUSH_WORKQUEUE resolved ufence timeout\n");
                                break;
                        }
                        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 (q)
                xe_exec_queue_put(q);

        return err;
}