#include <drm/drm_device.h>
 #include <drm/drm_file.h>
+#include <drm/drm_utils.h>
 #include <drm/xe_drm.h>
 
 #include "xe_device.h"
                         DRM_XE_UFENCE_WAIT_VM_ERROR)
 #define MAX_OP         DRM_XE_UFENCE_WAIT_LTE
 
+static unsigned long to_jiffies_timeout(struct drm_xe_wait_user_fence *args)
+{
+       unsigned long timeout;
+
+       if (args->flags & DRM_XE_UFENCE_WAIT_ABSTIME)
+               return drm_timeout_abs_to_jiffies(args->timeout);
+
+       if (args->timeout == MAX_SCHEDULE_TIMEOUT || args->timeout == 0)
+               return args->timeout;
+
+       timeout = nsecs_to_jiffies(args->timeout);
+
+       return timeout ?: 1;
+}
+
 int xe_wait_user_fence_ioctl(struct drm_device *dev, void *data,
                             struct drm_file *file)
 {
        int err;
        bool no_engines = args->flags & DRM_XE_UFENCE_WAIT_SOFT_OP ||
                args->flags & DRM_XE_UFENCE_WAIT_VM_ERROR;
-       unsigned long timeout = args->timeout;
+       unsigned long timeout;
+       ktime_t start;
 
        if (XE_IOCTL_ERR(xe, args->extensions) || XE_IOCTL_ERR(xe, args->pad) ||
            XE_IOCTL_ERR(xe, args->reserved[0] || args->reserved[1]))
                addr = vm->async_ops.error_capture.addr;
        }
 
-       if (XE_IOCTL_ERR(xe, timeout > MAX_SCHEDULE_TIMEOUT))
-               return -EINVAL;
+       /*
+        * 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;
+
+       timeout = to_jiffies_timeout(args);
+
+       start = ktime_get();
 
        /*
         * FIXME: Very simple implementation at the moment, single wait queue
        } else {
                remove_wait_queue(&xe->ufence_wq, &w_wait);
        }
+
+       if (!(args->flags & DRM_XE_UFENCE_WAIT_ABSTIME)) {
+               args->timeout -= ktime_to_ns(ktime_sub(ktime_get(), start));
+               if (args->timeout < 0)
+                       args->timeout = 0;
+       }
+
        if (XE_IOCTL_ERR(xe, err < 0))
                return err;
        else if (XE_IOCTL_ERR(xe, !timeout))
                return -ETIME;
 
-       /*
-        * Again very simple, return the time in jiffies that has past, may need
-        * a more precision
-        */
-       if (args->flags & DRM_XE_UFENCE_WAIT_ABSTIME)
-               args->timeout = args->timeout - timeout;
-
        return 0;
 }
 
 #define DRM_XE_UFENCE_WAIT_U64         0xffffffffffffffffu
        /** @mask: comparison mask */
        __u64 mask;
-
-       /** @timeout: how long to wait before bailing, value in jiffies */
+       /**
+        * @timeout: how long to wait before bailing, value in nanoseconds.
+        * Without DRM_XE_UFENCE_WAIT_ABSTIME flag set (relative timeout)
+        * it contains timeout expressed in nanoseconds to wait (fence will
+        * expire at now() + timeout).
+        * When DRM_XE_UFENCE_WAIT_ABSTIME flat is set (absolute timeout) wait
+        * will end at timeout (uses system MONOTONIC_CLOCK).
+        * Passing negative timeout leads to neverending wait.
+        *
+        * On relative timeout this value is updated with timeout left
+        * (for restarting the call in case of signal delivery).
+        * On absolute timeout this value stays intact (restarted call still
+        * expire at the same point of time).
+        */
        __s64 timeout;
 
        /**