| // 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_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) { |
| 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; |
| } |