// SPDX-License-Identifier: MIT /* * Copyright © 2022 Intel Corporation */ #include "xe_wait_user_fence.h" #include #include #include #include #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; }