diff mbox series

[i-g-t] igt: add timeline test cases

Message ID 20181207100557.2536-1-david1.zhou@amd.com (mailing list archive)
State New, archived
Headers show
Series [i-g-t] igt: add timeline test cases | expand

Commit Message

Chunming Zhou Dec. 7, 2018, 10:05 a.m. UTC
Signed-off-by: Chunming Zhou <david1.zhou@amd.com>
---
 include/drm-uapi/drm.h   |   33 ++
 lib/igt_syncobj.c        |  204 +++++++
 lib/igt_syncobj.h        |   19 +
 tests/meson.build        |    1 +
 tests/syncobj_timeline.c | 1100 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 1357 insertions(+)
 create mode 100644 tests/syncobj_timeline.c

Comments

Chris Wilson Dec. 7, 2018, 10:29 a.m. UTC | #1
Quoting Patchwork (2018-12-07 10:27:46)
> == Series Details ==
> 
> Series: igt: add timeline test cases (rev2)
> URL   : https://patchwork.freedesktop.org/series/53743/
> State : failure
> 
> == Summary ==
> 
> CI Bug Log - changes from CI_DRM_5281 -> IGTPW_2133
> ====================================================
> 
> Summary
> -------
> 
>   **FAILURE**
> 
>   Serious unknown changes coming with IGTPW_2133 absolutely need to be
>   verified manually.
>   
>   If you think the reported changes have nothing to do with the changes
>   introduced in IGTPW_2133, please notify your bug team to allow them
>   to document this new failure mode, which will reduce false positives in CI.
> 
>   External URL: https://patchwork.freedesktop.org/api/1.0/series/53743/revisions/2/mbox/
> 
> Possible new issues
> -------------------
> 
>   Here are the unknown changes that may have been introduced in IGTPW_2133:
> 
> ### IGT changes ###
> 
> #### Possible regressions ####
> 
>   * igt@amdgpu/amd_basic@userptr:
>     - fi-kbl-8809g:       PASS -> DMESG-WARN

What fortuitous timing! Maybe you would like to take a stab at the
use-after-free in amdgpu's mmu_notifier.
-Chris
Chunming Zhou Dec. 7, 2018, 10:36 a.m. UTC | #2
On 2018年12月07日 18:29, Chris Wilson wrote:
> Quoting Patchwork (2018-12-07 10:27:46)
>> == Series Details ==
>>
>> Series: igt: add timeline test cases (rev2)
>> URL   : https://patchwork.freedesktop.org/series/53743/
>> State : failure
>>
>> == Summary ==
>>
>> CI Bug Log - changes from CI_DRM_5281 -> IGTPW_2133
>> ====================================================
>>
>> Summary
>> -------
>>
>>    **FAILURE**
>>
>>    Serious unknown changes coming with IGTPW_2133 absolutely need to be
>>    verified manually.
>>    
>>    If you think the reported changes have nothing to do with the changes
>>    introduced in IGTPW_2133, please notify your bug team to allow them
>>    to document this new failure mode, which will reduce false positives in CI.
>>
>>    External URL: https://patchwork.freedesktop.org/api/1.0/series/53743/revisions/2/mbox/
>>
>> Possible new issues
>> -------------------
>>
>>    Here are the unknown changes that may have been introduced in IGTPW_2133:
>>
>> ### IGT changes ###
>>
>> #### Possible regressions ####
>>
>>    * igt@amdgpu/amd_basic@userptr:
>>      - fi-kbl-8809g:       PASS -> DMESG-WARN
> What fortuitous timing! Maybe you would like to take a stab at the
> use-after-free in amdgpu's mmu_notifier.
It's totally new test cases and should be nothing with others, Do you 
mean I need to resend it after sometime?

-David
> -Chris
Chris Wilson Dec. 7, 2018, 10:42 a.m. UTC | #3
Quoting zhoucm1 (2018-12-07 10:36:37)
> 
> 
> On 2018年12月07日 18:29, Chris Wilson wrote:
> > Quoting Patchwork (2018-12-07 10:27:46)
> >> == Series Details ==
> >>
> >> Series: igt: add timeline test cases (rev2)
> >> URL   : https://patchwork.freedesktop.org/series/53743/
> >> State : failure
> >>
> >> == Summary ==
> >>
> >> CI Bug Log - changes from CI_DRM_5281 -> IGTPW_2133
> >> ====================================================
> >>
> >> Summary
> >> -------
> >>
> >>    **FAILURE**
> >>
> >>    Serious unknown changes coming with IGTPW_2133 absolutely need to be
> >>    verified manually.
> >>    
> >>    If you think the reported changes have nothing to do with the changes
> >>    introduced in IGTPW_2133, please notify your bug team to allow them
> >>    to document this new failure mode, which will reduce false positives in CI.
> >>
> >>    External URL: https://patchwork.freedesktop.org/api/1.0/series/53743/revisions/2/mbox/
> >>
> >> Possible new issues
> >> -------------------
> >>
> >>    Here are the unknown changes that may have been introduced in IGTPW_2133:
> >>
> >> ### IGT changes ###
> >>
> >> #### Possible regressions ####
> >>
> >>    * igt@amdgpu/amd_basic@userptr:
> >>      - fi-kbl-8809g:       PASS -> DMESG-WARN
> > What fortuitous timing! Maybe you would like to take a stab at the
> > use-after-free in amdgpu's mmu_notifier.
> It's totally new test cases and should be nothing with others, Do you 
> mean I need to resend it after sometime?

It's an unrelated failure that has been plaguing us ever since we added a
kbl-g to our farm. But as it is a use-after-free with core mm coupling,
it's quite serious.
-Chris
Daniel Vetter Dec. 7, 2018, 1:58 p.m. UTC | #4
On Fri, Dec 7, 2018 at 11:29 AM Chris Wilson <chris@chris-wilson.co.uk> wrote:
>
> Quoting Patchwork (2018-12-07 10:27:46)
> > == Series Details ==
> >
> > Series: igt: add timeline test cases (rev2)
> > URL   : https://patchwork.freedesktop.org/series/53743/
> > State : failure
> >
> > == Summary ==
> >
> > CI Bug Log - changes from CI_DRM_5281 -> IGTPW_2133
> > ====================================================
> >
> > Summary
> > -------
> >
> >   **FAILURE**
> >
> >   Serious unknown changes coming with IGTPW_2133 absolutely need to be
> >   verified manually.
> >
> >   If you think the reported changes have nothing to do with the changes
> >   introduced in IGTPW_2133, please notify your bug team to allow them
> >   to document this new failure mode, which will reduce false positives in CI.
> >
> >   External URL: https://patchwork.freedesktop.org/api/1.0/series/53743/revisions/2/mbox/
> >
> > Possible new issues
> > -------------------
> >
> >   Here are the unknown changes that may have been introduced in IGTPW_2133:
> >
> > ### IGT changes ###
> >
> > #### Possible regressions ####
> >
> >   * igt@amdgpu/amd_basic@userptr:
> >     - fi-kbl-8809g:       PASS -> DMESG-WARN
>
> What fortuitous timing! Maybe you would like to take a stab at the
> use-after-free in amdgpu's mmu_notifier.

Adding Christian König.
-Daniel
Christian König Dec. 7, 2018, 2:12 p.m. UTC | #5
Am 07.12.18 um 14:58 schrieb Daniel Vetter:
> On Fri, Dec 7, 2018 at 11:29 AM Chris Wilson <chris@chris-wilson.co.uk> wrote:
>> Quoting Patchwork (2018-12-07 10:27:46)
>>> == Series Details ==
>>>
>>> Series: igt: add timeline test cases (rev2)
>>> URL   : https://patchwork.freedesktop.org/series/53743/
>>> State : failure
>>>
>>> == Summary ==
>>>
>>> CI Bug Log - changes from CI_DRM_5281 -> IGTPW_2133
>>> ====================================================
>>>
>>> Summary
>>> -------
>>>
>>>    **FAILURE**
>>>
>>>    Serious unknown changes coming with IGTPW_2133 absolutely need to be
>>>    verified manually.
>>>
>>>    If you think the reported changes have nothing to do with the changes
>>>    introduced in IGTPW_2133, please notify your bug team to allow them
>>>    to document this new failure mode, which will reduce false positives in CI.
>>>
>>>    External URL: https://patchwork.freedesktop.org/api/1.0/series/53743/revisions/2/mbox/
>>>
>>> Possible new issues
>>> -------------------
>>>
>>>    Here are the unknown changes that may have been introduced in IGTPW_2133:
>>>
>>> ### IGT changes ###
>>>
>>> #### Possible regressions ####
>>>
>>>    * igt@amdgpu/amd_basic@userptr:
>>>      - fi-kbl-8809g:       PASS -> DMESG-WARN
>> What fortuitous timing! Maybe you would like to take a stab at the
>> use-after-free in amdgpu's mmu_notifier.
> Adding Christian König.

Philip Yang is already working on this. We want to replace the MMU notifier with HMM as soon as possible.

Christian.

> -Daniel
diff mbox series

Patch

diff --git a/include/drm-uapi/drm.h b/include/drm-uapi/drm.h
index f0bd91de..0f5df0b6 100644
--- a/include/drm-uapi/drm.h
+++ b/include/drm-uapi/drm.h
@@ -715,6 +715,8 @@  struct drm_syncobj_handle {
 
 #define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0)
 #define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1)
+/* wait for time point to become available */
+#define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE (1 << 2)
 struct drm_syncobj_wait {
 	__u64 handles;
 	/* absolute timeout */
@@ -725,11 +727,37 @@  struct drm_syncobj_wait {
 	__u32 pad;
 };
 
+struct drm_syncobj_timeline_wait {
+        __u64 handles;
+        /* wait on specific timeline point for every handles*/
+        __u64 points;
+        /* absolute timeout */
+        __s64 timeout_nsec;
+        __u32 count_handles;
+        __u32 flags;
+        __u32 first_signaled; /* only valid when not waiting all */
+        __u32 pad;
+};
+
 struct drm_syncobj_array {
 	__u64 handles;
 	__u32 count_handles;
 	__u32 pad;
 };
+struct drm_syncobj_timeline_array {
+       __u64 handles;
+       __u64 points;
+       __u32 count_handles;
+       __u32 pad;
+};
+
+struct drm_syncobj_transfer {
+        __u32 binary_handle;
+        __u32 timeline_handle;
+        __u64 point;
+        __u32 flags;
+        __u32 pad;
+};
 
 /* Query current scanout sequence number */
 struct drm_crtc_get_sequence {
@@ -886,6 +914,11 @@  extern "C" {
 #define DRM_IOCTL_MODE_LIST_LESSEES	DRM_IOWR(0xC7, struct drm_mode_list_lessees)
 #define DRM_IOCTL_MODE_GET_LEASE	DRM_IOWR(0xC8, struct drm_mode_get_lease)
 #define DRM_IOCTL_MODE_REVOKE_LEASE	DRM_IOWR(0xC9, struct drm_mode_revoke_lease)
+#define DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT DRM_IOWR(0xCA, struct drm_syncobj_timeline_wait)
+#define DRM_IOCTL_SYNCOBJ_QUERY         DRM_IOWR(0xCB, struct drm_syncobj_timeline_array)
+#define DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE    DRM_IOWR(0xCC, struct drm_syncobj_transfer)
+#define DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY    DRM_IOWR(0xCD, struct drm_syncobj_transfer)
+#define DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL    DRM_IOWR(0xCE, struct drm_syncobj_timeline_array)
 
 /**
  * Device specific ioctls should only be in their respective headers
diff --git a/lib/igt_syncobj.c b/lib/igt_syncobj.c
index d9114ca8..a3b6a90b 100644
--- a/lib/igt_syncobj.c
+++ b/lib/igt_syncobj.c
@@ -286,3 +286,207 @@  syncobj_signal(int fd, uint32_t *handles, uint32_t count)
 {
 	igt_assert_eq(__syncobj_signal(fd, handles, count), 0);
 }
+
+static int
+__syncobj_timeline_signal(int fd, uint32_t *handles, uint64_t *points, uint32_t count)
+{
+	struct drm_syncobj_timeline_array array = { 0 };
+	int err = 0;
+
+	array.handles = to_user_pointer(handles);
+	array.points = to_user_pointer(points);
+	array.count_handles = count;
+	if (drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &array))
+		err = -errno;
+	return err;
+}
+
+/**
+ * syncobj_signal:
+ * @fd: The DRM file descriptor.
+ * @handles: Array of syncobj handles to signal
+ * @points: List of point of handles to signal.
+ * @count: Count of syncobj handles.
+ *
+ * Signal a set of syncobjs.
+ */
+void
+syncobj_timeline_signal(int fd, uint32_t *handles, uint64_t *points, uint32_t count)
+{
+	igt_assert_eq(__syncobj_timeline_signal(fd, handles, points, count), 0);
+}
+int
+__syncobj_timeline_wait_ioctl(int fd, struct drm_syncobj_timeline_wait *args)
+{
+	int err = 0;
+	if (drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, args))
+		err = -errno;
+	return err;
+}
+static int
+__syncobj_timeline_wait(int fd, uint32_t *handles, uint64_t *points,
+			unsigned num_handles,
+			int64_t timeout_nsec, unsigned flags,
+			uint32_t *first_signaled)
+{
+	struct drm_syncobj_timeline_wait args;
+	int ret;
+
+	args.handles = to_user_pointer(handles);
+	args.points = (uint64_t)to_user_pointer(points);
+	args.timeout_nsec = timeout_nsec;
+	args.count_handles = num_handles;
+	args.flags = flags;
+	args.first_signaled = 0;
+	args.pad = 0;
+
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, &args);
+	if (ret < 0)
+		return -errno;
+
+	if (first_signaled)
+		*first_signaled = args.first_signaled;
+
+	return ret;
+}
+int
+syncobj_timeline_wait_err(int fd, uint32_t *handles, uint64_t *points,
+			  unsigned num_handles,
+			  int64_t timeout_nsec, unsigned flags)
+{
+	return __syncobj_timeline_wait(fd, handles, points, num_handles,
+				       timeout_nsec, flags, NULL);
+}
+
+/**
+ * syncobj_timeline_wait:
+ * @fd: The DRM file descriptor
+ * @handles: List of syncobj handles to wait for.
+ * @points: List of point of handles to wait for.
+ * @num_handles: Count of handles
+ * @timeout_nsec: Absolute wait timeout in nanoseconds.
+ * @flags: Wait ioctl flags.
+ * @first_signaled: Returned handle for first signaled syncobj.
+ *
+ * Waits in the kernel for any/all the requested syncobjs timeline point
+ * using the timeout and flags.
+ * Returns: bool value - false = timedout, true = signaled
+ */
+bool
+syncobj_timeline_wait(int fd, uint32_t *handles, uint64_t *points,
+		      unsigned num_handles,
+		      int64_t timeout_nsec, unsigned flags,
+		      uint32_t *first_signaled)
+{
+	int ret;
+
+	ret = __syncobj_timeline_wait(fd, handles, points, num_handles,
+				      timeout_nsec, flags, first_signaled);
+	if (ret == -ETIME)
+		return false;
+	igt_assert_eq(ret, 0);
+
+	return true;
+
+}
+
+static int
+__syncobj_timeline_query(int fd, uint32_t *handles, uint64_t **points,
+			 uint32_t handle_count)
+{
+	struct drm_syncobj_timeline_array args;
+	int ret;
+
+	args.handles = to_user_pointer(handles);
+	args.points = (uint64_t)to_user_pointer(points);
+	args.count_handles = handle_count;
+	args.pad = 0;
+
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &args);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+/**
+ * syncobj_timeline_query:
+ * @fd: The DRM file descriptor.
+ * @handles: Array of syncobj handles.
+ * @points: Array of syncobj points queried.
+ * @count: Count of syncobj handles.
+ *
+ * query a set of syncobjs.
+ */
+void
+syncobj_timeline_query(int fd, uint32_t *handles, uint64_t **points,
+		       uint32_t count)
+{
+	igt_assert_eq(__syncobj_timeline_query(fd, handles, points, count), 0);
+}
+
+static int
+__syncobj_binary_to_timeline(int fd, uint32_t timeline_handle,
+			     uint64_t point, uint32_t binary_handle)
+{
+	struct drm_syncobj_transfer args;
+
+	args.binary_handle = binary_handle;
+	args.timeline_handle = timeline_handle;
+	args.point = point;
+	args.flags = 0;
+	args.pad = 0;
+	return drmIoctl(fd, DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE, &args);
+}
+
+/**
+ * syncobj_binary_to_timeline:
+ * @fd: The DRM file descriptor.
+ * @timeline_handles: Array of timeline syncobj handles.
+ * @point: syncobj point.
+ * @binary_handle: Array of binary syncobj handles.
+ *
+ * query a set of syncobjs.
+ */
+void
+syncobj_binary_to_timeline(int fd, uint32_t timeline_handle,
+			   uint64_t point, uint32_t binary_handle)
+{
+	igt_assert_eq(__syncobj_binary_to_timeline(fd, timeline_handle, point,
+						   binary_handle), 0);
+}
+
+static int
+__syncobj_timeline_to_binary(int fd, uint32_t binary_handle,
+			     uint32_t timeline_handle,
+			     uint64_t point,
+			     uint32_t flags)
+{
+	struct drm_syncobj_transfer args;
+
+	args.binary_handle = binary_handle;
+	args.timeline_handle = timeline_handle;
+	args.point = point;
+	args.flags = flags;
+	args.pad = 0;
+	return drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY, &args);
+}
+
+/**
+ * syncobj_binary_to_timeline:
+ * @fd: The DRM file descriptor.
+ * @timeline_handles: Array of timeline syncobj handles.
+ * @point: syncobj point.
+ * @binary_handle: Array of binary syncobj handles.
+ *
+ * query a set of syncobjs.
+ */
+void
+syncobj_timeline_to_binary(int fd, uint32_t binary_handle,
+			   uint32_t timeline_handle,
+			   uint64_t point,
+			   uint32_t flags)
+{
+	igt_assert_eq(__syncobj_timeline_to_binary(fd, binary_handle,
+						   timeline_handle, point,
+						   flags), 0);
+}
diff --git a/lib/igt_syncobj.h b/lib/igt_syncobj.h
index 44d1378d..175e5027 100644
--- a/lib/igt_syncobj.h
+++ b/lib/igt_syncobj.h
@@ -65,7 +65,26 @@  int syncobj_wait_err(int fd, uint32_t *handles, uint32_t count,
 bool syncobj_wait(int fd, uint32_t *handles, uint32_t count,
 		  uint64_t abs_timeout_nsec, uint32_t flags,
 		  uint32_t *first_signaled);
+int __syncobj_timeline_wait_ioctl(int fd,
+				  struct drm_syncobj_timeline_wait *args);
+bool syncobj_timeline_wait(int fd, uint32_t *handles, uint64_t *points,
+			   unsigned num_handles,
+			   int64_t timeout_nsec, unsigned flags,
+			   uint32_t *first_signaled);
+int syncobj_timeline_wait_err(int fd, uint32_t *handles, uint64_t *points,
+			      unsigned num_handles,
+			      int64_t timeout_nsec, unsigned flags);
 void syncobj_reset(int fd, uint32_t *handles, uint32_t count);
 void syncobj_signal(int fd, uint32_t *handles, uint32_t count);
+void syncobj_timeline_query(int fd, uint32_t *handles, uint64_t **points,
+			    uint32_t count);
+void syncobj_binary_to_timeline(int fd, uint32_t timeline_handle,
+				uint64_t point, uint32_t binary_handle);
+void syncobj_timeline_to_binary(int fd, uint32_t binary_handle,
+				uint32_t timeline_handle,
+				uint64_t point,
+				uint32_t flags);
+void syncobj_timeline_signal(int fd, uint32_t *handles, uint64_t *points,
+			     uint32_t count);
 
 #endif /* IGT_SYNCOBJ_H */
diff --git a/tests/meson.build b/tests/meson.build
index 3020f798..8b5db541 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -83,6 +83,7 @@  test_progs = [
 	'sw_sync',
 	'syncobj_basic',
 	'syncobj_wait',
+	'syncobj_timeline',
 	'template',
 	'tools_test',
 	'vc4_create_bo',
diff --git a/tests/syncobj_timeline.c b/tests/syncobj_timeline.c
new file mode 100644
index 00000000..aaf58a19
--- /dev/null
+++ b/tests/syncobj_timeline.c
@@ -0,0 +1,1100 @@ 
+/*
+ * Copyright © 2018 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "igt.h"
+#include "sw_sync.h"
+#include "igt_syncobj.h"
+#include <unistd.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include <pthread.h>
+#include <signal.h>
+#include "drm.h"
+
+IGT_TEST_DESCRIPTION("Tests for the drm timeline sync object API");
+
+/* One tenth of a second */
+#define SHORT_TIME_NSEC 100000000ull
+
+#define NSECS_PER_SEC 1000000000ull
+
+static uint64_t
+gettime_ns(void)
+{
+	struct timespec current;
+	clock_gettime(CLOCK_MONOTONIC, &current);
+	return (uint64_t)current.tv_sec * NSECS_PER_SEC + current.tv_nsec;
+}
+
+static void
+sleep_nsec(uint64_t time_nsec)
+{
+	struct timespec t;
+	t.tv_sec = time_nsec / NSECS_PER_SEC;
+	t.tv_nsec = time_nsec % NSECS_PER_SEC;
+	igt_assert_eq(nanosleep(&t, NULL), 0);
+}
+
+static uint64_t
+short_timeout(void)
+{
+	return gettime_ns() + SHORT_TIME_NSEC;
+}
+
+static int
+syncobj_attach_sw_sync(int fd, uint32_t handle, uint64_t point)
+{
+	struct drm_syncobj_handle;
+	uint32_t syncobj = syncobj_create(fd, 0);
+	int timeline, fence;
+
+	timeline = sw_sync_timeline_create();
+	fence = sw_sync_timeline_create_fence(timeline, 1);
+	syncobj_import_sync_file(fd, syncobj, fence);
+	syncobj_binary_to_timeline(fd, handle, point, syncobj);
+	close(fence);
+
+	syncobj_destroy(fd, syncobj);
+	return timeline;
+}
+
+static void
+syncobj_trigger(int fd, uint32_t handle, uint64_t point)
+{
+	int timeline = syncobj_attach_sw_sync(fd, handle, point);
+	sw_sync_timeline_inc(timeline, 1);
+	close(timeline);
+}
+
+static timer_t
+set_timer(void (*cb)(union sigval), void *ptr, int i, uint64_t nsec)
+{
+        timer_t timer;
+        struct sigevent sev;
+        struct itimerspec its;
+
+        memset(&sev, 0, sizeof(sev));
+        sev.sigev_notify = SIGEV_THREAD;
+	if (ptr)
+		sev.sigev_value.sival_ptr = ptr;
+	else
+		sev.sigev_value.sival_int = i;
+        sev.sigev_notify_function = cb;
+        igt_assert(timer_create(CLOCK_MONOTONIC, &sev, &timer) == 0);
+
+        memset(&its, 0, sizeof(its));
+        its.it_value.tv_sec = nsec / NSEC_PER_SEC;
+        its.it_value.tv_nsec = nsec % NSEC_PER_SEC;
+        igt_assert(timer_settime(timer, 0, &its, NULL) == 0);
+
+	return timer;
+}
+
+struct fd_handle_pair {
+	int fd;
+	uint32_t handle;
+	uint64_t point;
+};
+
+static void
+timeline_inc_func(union sigval sigval)
+{
+	sw_sync_timeline_inc(sigval.sival_int, 1);
+}
+
+static void
+syncobj_trigger_free_pair_func(union sigval sigval)
+{
+	struct fd_handle_pair *pair = sigval.sival_ptr;
+	syncobj_trigger(pair->fd, pair->handle, pair->point);
+	free(pair);
+}
+
+static timer_t
+syncobj_trigger_delayed(int fd, uint32_t syncobj, uint64_t point, uint64_t nsec)
+{
+	struct fd_handle_pair *pair = malloc(sizeof(*pair));
+
+	pair->fd = fd;
+	pair->handle = syncobj;
+	pair->point = syncobj;
+
+	return set_timer(syncobj_trigger_free_pair_func, pair, 0, nsec);
+}
+
+static void
+test_wait_bad_flags(int fd)
+{
+	struct drm_syncobj_timeline_wait wait = { 0 };
+	wait.flags = 0xdeadbeef;
+	igt_assert_eq(__syncobj_timeline_wait_ioctl(fd, &wait), -EINVAL);
+}
+
+static void
+test_wait_zero_handles(int fd)
+{
+	struct drm_syncobj_timeline_wait wait = { 0 };
+	igt_assert_eq(__syncobj_timeline_wait_ioctl(fd, &wait), -EINVAL);
+}
+
+static void
+test_wait_illegal_handle(int fd)
+{
+	struct drm_syncobj_timeline_wait wait = { 0 };
+	uint32_t handle = 0;
+
+	wait.count_handles = 1;
+	wait.handles = to_user_pointer(&handle);
+	igt_assert_eq(__syncobj_timeline_wait_ioctl(fd, &wait), -ENOENT);
+}
+
+static void
+test_query_zero_handles(int fd)
+{
+	struct drm_syncobj_timeline_array args = { 0 };
+	int ret;
+
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &args);
+	igt_assert(ret == -1 && errno ==  EINVAL);
+}
+
+static void
+test_query_illegal_handle(int fd)
+{
+	struct drm_syncobj_timeline_array args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.count_handles = 1;
+	args.handles = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_query_one_illegal_handle(int fd)
+{
+	struct drm_syncobj_timeline_array array = { 0 };
+	uint32_t syncobjs[3];
+	uint64_t initial_point = 1;
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd, 0);
+	syncobjs[1] = 0;
+	syncobjs[2] = syncobj_create(fd, 0);
+
+	syncobj_timeline_signal(fd, &syncobjs[0], &initial_point, 1);
+	syncobj_timeline_signal(fd, &syncobjs[2], &initial_point, 1);
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobjs[0],
+						&initial_point, 1, 0, 0), 0);
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobjs[2],
+						&initial_point, 1, 0, 0), 0);
+
+	array.count_handles = 3;
+	array.handles = to_user_pointer(syncobjs);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &array);
+	igt_assert(ret == -1 && errno == ENOENT);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[2]);
+}
+
+static void
+test_query_bad_pad(int fd)
+{
+	struct drm_syncobj_timeline_array array = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	array.pad = 0xdeadbeef;
+	array.count_handles = 1;
+	array.handles = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_QUERY, &array);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+
+static void
+test_signal_zero_handles(int fd)
+{
+	struct drm_syncobj_timeline_array args = { 0 };
+	int ret;
+
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args);
+	igt_assert(ret == -1 && errno ==  EINVAL);
+}
+
+static void
+test_signal_illegal_handle(int fd)
+{
+	struct drm_syncobj_timeline_array args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.count_handles = 1;
+	args.handles = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_signal_illegal_point(int fd)
+{
+	struct drm_syncobj_timeline_array args = { 0 };
+	uint32_t handle = 1;
+	uint64_t point = 0;
+	int ret;
+
+	args.count_handles = 1;
+	args.handles = to_user_pointer(&handle);
+	args.points = to_user_pointer(&point);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+static void
+test_signal_one_illegal_handle(int fd)
+{
+	struct drm_syncobj_timeline_array array = { 0 };
+	uint32_t syncobjs[3];
+	uint64_t initial_point = 1;
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd, 0);
+	syncobjs[1] = 0;
+	syncobjs[2] = syncobj_create(fd, 0);
+
+	syncobj_timeline_signal(fd, &syncobjs[0], &initial_point, 1);
+	syncobj_timeline_signal(fd, &syncobjs[2], &initial_point, 1);
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobjs[0],
+						&initial_point, 1, 0, 0), 0);
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobjs[2],
+						&initial_point, 1, 0, 0), 0);
+
+	array.count_handles = 3;
+	array.handles = to_user_pointer(syncobjs);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &array);
+	igt_assert(ret == -1 && errno == ENOENT);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[2]);
+}
+
+static void
+test_signal_bad_pad(int fd)
+{
+	struct drm_syncobj_timeline_array array = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	array.pad = 0xdeadbeef;
+	array.count_handles = 1;
+	array.handles = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, &array);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+
+static void
+test_binary_to_timeline_illegal_binary_handle(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.binary_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_binary_to_timeline_illegal_timeline_handle(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.timeline_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_binary_to_timeline_illegal_point(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle1 = 1;
+	uint32_t handle2 = 2;
+	int ret;
+
+	args.binary_handle = to_user_pointer(&handle1);
+	args.timeline_handle = to_user_pointer(&handle2);
+	args.point = 0;
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_binary_to_timeline_bad_pad(int fd)
+{
+	struct drm_syncobj_transfer arg = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	arg.pad = 0xdeadbeef;
+	arg.binary_handle = to_user_pointer(&handle);
+	arg.timeline_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_BINARY_TO_TIMELINE, &arg);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+static void
+test_timeline_to_binary_illegal_binary_handle(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.binary_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_timeline_to_binary_illegal_timeline_handle(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	args.timeline_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_timeline_to_binary_illegal_point(int fd)
+{
+	struct drm_syncobj_transfer args = { 0 };
+	uint32_t handle1 = 1;
+	uint32_t handle2 = 2;
+	int ret;
+
+	args.binary_handle = to_user_pointer(&handle1);
+	args.timeline_handle = to_user_pointer(&handle2);
+	args.point = 0;
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY, &args);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_timeline_to_binary_bad_pad(int fd)
+{
+	struct drm_syncobj_transfer arg = { 0 };
+	uint32_t handle = 0;
+	int ret;
+
+	arg.pad = 0xdeadbeef;
+	arg.binary_handle = to_user_pointer(&handle);
+	arg.timeline_handle = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_TO_BINARY, &arg);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+#define WAIT_FOR_SUBMIT		(1 << 0)
+#define WAIT_ALL		(1 << 1)
+#define WAIT_AVAILABLE		(1 << 2)
+#define WAIT_UNSUBMITTED	(1 << 3)
+#define WAIT_SUBMITTED		(1 << 4)
+#define WAIT_SIGNALED		(1 << 5)
+#define WAIT_FLAGS_MAX		(1 << 6) - 1
+
+static uint32_t
+flags_for_test_flags(uint32_t test_flags)
+{
+	uint32_t flags = 0;
+
+	if (test_flags & WAIT_FOR_SUBMIT)
+		flags |= DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	if (test_flags & WAIT_AVAILABLE)
+		flags |= DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE;
+
+	if (test_flags & WAIT_ALL)
+		flags |= DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	return flags;
+}
+
+static void
+test_single_wait(int fd, uint32_t test_flags, int expect)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint32_t flags = flags_for_test_flags(test_flags);
+	uint64_t point = 1;
+	int timeline = -1;
+
+	if (test_flags & (WAIT_SUBMITTED | WAIT_SIGNALED))
+		timeline = syncobj_attach_sw_sync(fd, syncobj, point);
+
+	if (test_flags & WAIT_SIGNALED)
+		sw_sync_timeline_inc(timeline, 1);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point, 1,
+						0, flags), expect);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point, 1,
+						short_timeout(), flags), expect);
+
+	if (expect != -ETIME) {
+		igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point, 1,
+							UINT64_MAX, flags), expect);
+	}
+
+	syncobj_destroy(fd, syncobj);
+	if (timeline != -1)
+		close(timeline);
+}
+
+static void
+test_wait_delayed_signal(int fd, uint32_t test_flags)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint32_t flags = flags_for_test_flags(test_flags);
+	uint64_t point = 1;
+	int timeline = -1;
+	timer_t timer;
+
+	if (test_flags & WAIT_FOR_SUBMIT) {
+		timer = syncobj_trigger_delayed(fd, syncobj, point, SHORT_TIME_NSEC);
+	} else {
+		timeline = syncobj_attach_sw_sync(fd, syncobj, point);
+		timer = set_timer(timeline_inc_func, NULL,
+				  timeline, SHORT_TIME_NSEC);
+	}
+
+	igt_assert(syncobj_timeline_wait(fd, &syncobj, &point, 1,
+				gettime_ns() + SHORT_TIME_NSEC * 2,
+				flags, NULL));
+
+	timer_delete(timer);
+
+	if (timeline != -1)
+		close(timeline);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint64_t point = 1;
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, 0), -EINVAL);
+
+	syncobj_reset(fd, &syncobj, 1);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, 0), -EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint64_t point = 1;
+
+	syncobj_trigger(fd, syncobj, point);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, 0), 0);
+
+	syncobj_reset(fd, &syncobj, 1);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, 0), -EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_multiple_signaled(int fd)
+{
+	uint64_t points[3] = {1, 1, 1};
+	uint32_t syncobjs[3];
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		syncobjs[i] = syncobj_create(fd, 0);
+		syncobj_trigger(fd, syncobjs[i], points[i]);
+	}
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, syncobjs, points, 3, 0, 0), 0);
+
+	syncobj_reset(fd, syncobjs, 3);
+
+	for (i = 0; i < 3; i++) {
+		igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobjs[i],
+							&points[i], 1,
+							0, 0), -EINVAL);
+		syncobj_destroy(fd, syncobjs[i]);
+	}
+}
+
+static void
+reset_and_trigger_func(union sigval sigval)
+{
+	struct fd_handle_pair *pair = sigval.sival_ptr;
+	syncobj_reset(pair->fd, &pair->handle, 1);
+	syncobj_trigger(pair->fd, pair->handle, pair->point);
+}
+
+static void
+test_reset_during_wait_for_submit(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	struct fd_handle_pair pair;
+	uint64_t point = 1;
+	timer_t timer;
+
+	pair.fd = fd;
+	pair.handle = syncobj;
+	timer = set_timer(reset_and_trigger_func, &pair, 0, SHORT_TIME_NSEC);
+
+	/* A reset should be a no-op even if we're in the middle of a wait */
+	igt_assert(syncobj_timeline_wait(fd, &syncobj, &point, 1,
+				gettime_ns() + SHORT_TIME_NSEC * 2,
+				flags, NULL));
+
+	timer_delete(timer);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_signal(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	uint64_t point = 1;
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, 0), -EINVAL);
+	igt_assert_eq(syncobj_timeline_wait_err(fd, &syncobj, &point,
+						1, 0, flags), -ETIME);
+
+	syncobj_timeline_signal(fd, &syncobj, &point, 1);
+
+	igt_assert(syncobj_timeline_wait(fd, &syncobj, &point, 1, 0, 0, NULL));
+	igt_assert(syncobj_timeline_wait(fd, &syncobj, &point, 1, 0, flags, NULL));
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_multi_wait(int fd, uint32_t test_flags, int expect)
+{
+	uint32_t syncobjs[3];
+	uint32_t tflag, flags;
+	int i, fidx, timeline;
+	uint64_t points[3] = {1, 1, 1};
+
+	syncobjs[0] = syncobj_create(fd, 0);
+	syncobjs[1] = syncobj_create(fd, 0);
+	syncobjs[2] = syncobj_create(fd, 0);
+
+	flags = flags_for_test_flags(test_flags);
+	test_flags &= ~(WAIT_ALL | WAIT_FOR_SUBMIT | WAIT_AVAILABLE);
+
+	for (i = 0; i < 3; i++) {
+		fidx = ffs(test_flags) - 1;
+		tflag = (1 << fidx);
+
+		if (test_flags & ~tflag)
+			test_flags &= ~tflag;
+
+		if (tflag & (WAIT_SUBMITTED | WAIT_SIGNALED))
+			timeline = syncobj_attach_sw_sync(fd, syncobjs[i],
+							  points[i]);
+		if (tflag & WAIT_SIGNALED)
+			sw_sync_timeline_inc(timeline, 1);
+	}
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, syncobjs, points, 3, 0, flags), expect);
+
+	igt_assert_eq(syncobj_timeline_wait_err(fd, syncobjs, points, 3, short_timeout(),
+				       flags), expect);
+
+	if (expect != -ETIME) {
+		igt_assert_eq(syncobj_timeline_wait_err(fd, syncobjs, points, 3, UINT64_MAX,
+					       flags), expect);
+	}
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+	syncobj_destroy(fd, syncobjs[2]);
+}
+
+struct wait_thread_data {
+	int fd;
+	struct drm_syncobj_timeline_wait wait;
+};
+
+static void *
+wait_thread_func(void *data)
+{
+	struct wait_thread_data *wait = data;
+	igt_assert_eq(__syncobj_timeline_wait_ioctl(wait->fd, &wait->wait), 0);
+	return NULL;
+}
+
+static void
+test_wait_snapshot(int fd, uint32_t test_flags)
+{
+	struct wait_thread_data wait = { 0 };
+	uint32_t syncobjs[2];
+	uint64_t points[2] = {1, 1};
+	int timelines[3] = { -1, -1, -1 };
+	pthread_t thread;
+
+	syncobjs[0] = syncobj_create(fd, 0);
+	syncobjs[1] = syncobj_create(fd, 0);
+
+	if (!(test_flags & WAIT_FOR_SUBMIT)) {
+		timelines[0] = syncobj_attach_sw_sync(fd, syncobjs[0], points[0]);
+		timelines[1] = syncobj_attach_sw_sync(fd, syncobjs[1], points[1]);
+	}
+
+	wait.fd = fd;
+	wait.wait.handles = to_user_pointer(syncobjs);
+	wait.wait.count_handles = 2;
+	wait.wait.points = to_user_pointer(points);
+	wait.wait.timeout_nsec = short_timeout();
+	wait.wait.flags = flags_for_test_flags(test_flags);
+
+	igt_assert_eq(pthread_create(&thread, NULL, wait_thread_func, &wait), 0);
+
+	sleep_nsec(SHORT_TIME_NSEC / 5);
+
+	/* Try to fake the kernel out by triggering or partially triggering
+	 * the first fence.
+	 */
+	if (test_flags & WAIT_ALL) {
+		/* If it's WAIT_ALL, actually trigger it */
+		if (timelines[0] == -1)
+			syncobj_trigger(fd, syncobjs[0], points[0]);
+		else
+			sw_sync_timeline_inc(timelines[0], 1);
+	} else if (test_flags & WAIT_FOR_SUBMIT) {
+		timelines[0] = syncobj_attach_sw_sync(fd, syncobjs[0], points[0]);
+	}
+
+	sleep_nsec(SHORT_TIME_NSEC / 5);
+
+	/* Then reset it */
+	syncobj_reset(fd, &syncobjs[0], 1);
+
+	sleep_nsec(SHORT_TIME_NSEC / 5);
+
+	/* Then "submit" it in a way that will never trigger.  This way, if
+	 * the kernel picks up on the new fence (it shouldn't), we'll get a
+	 * timeout.
+	 */
+	timelines[2] = syncobj_attach_sw_sync(fd, syncobjs[0], points[0]);
+
+	sleep_nsec(SHORT_TIME_NSEC / 5);
+
+	/* Now trigger the second fence to complete the wait */
+
+	if (timelines[1] == -1)
+		syncobj_trigger(fd, syncobjs[1], points[1]);
+	else
+		sw_sync_timeline_inc(timelines[1], 1);
+
+	pthread_join(thread, NULL);
+
+	if (!(test_flags & WAIT_ALL))
+		igt_assert_eq(wait.wait.first_signaled, 1);
+
+	close(timelines[0]);
+	close(timelines[1]);
+	close(timelines[2]);
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+/* The numbers 0-7, each repeated 5x and shuffled. */
+static const unsigned shuffled_0_7_x4[] = {
+	2, 0, 6, 1, 1, 4, 5, 2, 0, 7, 1, 7, 6, 3, 4, 5,
+	0, 2, 7, 3, 5, 4, 0, 6, 7, 3, 2, 5, 6, 1, 4, 3,
+};
+
+enum syncobj_stage {
+	STAGE_UNSUBMITTED,
+	STAGE_SUBMITTED,
+	STAGE_SIGNALED,
+	STAGE_RESET,
+	STAGE_RESUBMITTED,
+};
+
+static void
+test_wait_complex(int fd, uint32_t test_flags)
+{
+	struct wait_thread_data wait = { 0 };
+	uint32_t syncobjs[8];
+	uint64_t points[8] = {1, 1, 1, 1, 1, 1, 1, 1};
+	enum syncobj_stage stage[8];
+	int i, j, timelines[8];
+	uint32_t first_signaled = -1, num_signaled = 0;
+	pthread_t thread;
+
+	for (i = 0; i < 8; i++) {
+		stage[i] = STAGE_UNSUBMITTED;
+		syncobjs[i] = syncobj_create(fd, 0);
+	}
+
+	if (test_flags & WAIT_FOR_SUBMIT) {
+		for (i = 0; i < 8; i++)
+			timelines[i] = -1;
+	} else {
+		for (i = 0; i < 8; i++)
+			timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i],
+							      points[i]);
+	}
+
+	wait.fd = fd;
+	wait.wait.handles = to_user_pointer(syncobjs);
+	wait.wait.count_handles = 2;
+	wait.wait.points = to_user_pointer(points);
+	wait.wait.timeout_nsec = gettime_ns() + NSECS_PER_SEC;
+	wait.wait.flags = flags_for_test_flags(test_flags);
+
+	igt_assert_eq(pthread_create(&thread, NULL, wait_thread_func, &wait), 0);
+
+	sleep_nsec(NSECS_PER_SEC / 50);
+
+	num_signaled = 0;
+	for (j = 0; j < ARRAY_SIZE(shuffled_0_7_x4); j++) {
+		i = shuffled_0_7_x4[j];
+		igt_assert_lt(i, ARRAY_SIZE(syncobjs));
+
+		switch (stage[i]++) {
+		case STAGE_UNSUBMITTED:
+			/* We need to submit attach a fence */
+			if (!(test_flags & WAIT_FOR_SUBMIT)) {
+				/* We had to attach one up-front */
+				igt_assert_neq(timelines[i], -1);
+				break;
+			}
+			timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i],
+							      points[i]);
+			break;
+
+		case STAGE_SUBMITTED:
+			/* We have a fence, trigger it */
+			igt_assert_neq(timelines[i], -1);
+			sw_sync_timeline_inc(timelines[i], 1);
+			close(timelines[i]);
+			timelines[i] = -1;
+			if (num_signaled == 0)
+				first_signaled = i;
+			num_signaled++;
+			break;
+
+		case STAGE_SIGNALED:
+			/* We're already signaled, reset */
+			syncobj_reset(fd, &syncobjs[i], 1);
+			break;
+
+		case STAGE_RESET:
+			/* We're reset, submit and don't signal */
+			timelines[i] = syncobj_attach_sw_sync(fd, syncobjs[i],
+							      points[i]);
+			break;
+
+		case STAGE_RESUBMITTED:
+			igt_assert(!"Should not reach this stage");
+			break;
+		}
+
+		if (test_flags & WAIT_ALL) {
+			if (num_signaled == ARRAY_SIZE(syncobjs))
+				break;
+		} else {
+			if (num_signaled > 0)
+				break;
+		}
+
+		sleep_nsec(NSECS_PER_SEC / 100);
+	}
+
+	pthread_join(thread, NULL);
+
+	if (test_flags & WAIT_ALL) {
+		igt_assert_eq(num_signaled, ARRAY_SIZE(syncobjs));
+	} else {
+		igt_assert_eq(num_signaled, 1);
+		igt_assert_eq(wait.wait.first_signaled, first_signaled);
+	}
+
+	for (i = 0; i < 8; i++) {
+		close(timelines[i]);
+		syncobj_destroy(fd, syncobjs[i]);
+	}
+}
+
+static void
+test_wait_interrupted(int fd, uint32_t test_flags)
+{
+	struct drm_syncobj_timeline_wait wait = { 0 };
+	uint32_t syncobj = syncobj_create(fd, 0);
+	uint64_t point = 1;
+	int timeline;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.points = to_user_pointer(&point);
+	wait.count_handles = 1;
+	wait.flags = flags_for_test_flags(test_flags);
+
+	if (test_flags & WAIT_FOR_SUBMIT) {
+		wait.timeout_nsec = short_timeout();
+		igt_while_interruptible(true)
+			igt_assert_eq(__syncobj_timeline_wait_ioctl(fd, &wait), -ETIME);
+	}
+
+	timeline = syncobj_attach_sw_sync(fd, syncobj, point);
+
+	wait.timeout_nsec = short_timeout();
+	igt_while_interruptible(true)
+		igt_assert_eq(__syncobj_timeline_wait_ioctl(fd, &wait), -ETIME);
+
+	syncobj_destroy(fd, syncobj);
+	close(timeline);
+}
+
+static bool
+has_syncobj_timeline_wait(int fd)
+{
+	struct drm_syncobj_timeline_wait wait = { 0 };
+	uint32_t handle = 0;
+	uint64_t value;
+	int ret;
+
+	if (drmGetCap(fd, DRM_CAP_SYNCOBJ, &value))
+		return false;
+	if (!value)
+		return false;
+
+	/* Try waiting for zero sync objects should fail with EINVAL */
+	wait.count_handles = 1;
+	wait.handles = to_user_pointer(&handle);
+	ret = drmIoctl(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, &wait);
+	return ret == -1 && errno == ENOENT;
+}
+
+igt_main
+{
+	int fd = -1;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_ANY);
+		igt_require(has_syncobj_timeline_wait(fd));
+		igt_require_sw_sync();
+	}
+
+	igt_subtest("invalid-wait-bad-flags")
+		test_wait_bad_flags(fd);
+
+	igt_subtest("invalid-wait-zero-handles")
+		test_wait_zero_handles(fd);
+
+	igt_subtest("invalid-wait-illegal-handle")
+		test_wait_illegal_handle(fd);
+
+	igt_subtest("invalid-query-zero-handles")
+		test_query_zero_handles(fd);
+
+	igt_subtest("invalid-query-illegal-handle")
+		test_query_illegal_handle(fd);
+
+	igt_subtest("invalid-query-one-illegal-handle")
+		test_query_one_illegal_handle(fd);
+
+	igt_subtest("invalid-query-bad-pad")
+		test_query_bad_pad(fd);
+
+	igt_subtest("invalid-signal-zero-handles")
+		test_signal_zero_handles(fd);
+
+	igt_subtest("invalid-signal-illegal-handle")
+		test_signal_illegal_handle(fd);
+
+	igt_subtest("invalid-signal-illegal-point")
+		test_signal_illegal_point(fd);
+
+	igt_subtest("invalid-signal-one-illegal-handle")
+		test_signal_one_illegal_handle(fd);
+
+	igt_subtest("invalid-signal-bad-pad")
+		test_signal_bad_pad(fd);
+
+	igt_subtest("invalid-binary_to_timeline-illegal-binary-handle")
+		test_binary_to_timeline_illegal_binary_handle(fd);
+
+	igt_subtest("invalid-binary_to_timeline-illegal-timeline-handle")
+		test_binary_to_timeline_illegal_timeline_handle(fd);
+
+	igt_subtest("invalid-binary_to_timeline-illegal-point")
+		test_binary_to_timeline_illegal_point(fd);
+
+	igt_subtest("invalid-bianry_to_timeline-bad-pad")
+		test_binary_to_timeline_bad_pad(fd);
+
+	igt_subtest("invalid-timeline_to_binary-illegal-binary-handle")
+		test_timeline_to_binary_illegal_binary_handle(fd);
+
+	igt_subtest("invalid-timeline_to_binary-illegal-timeline-handle")
+		test_timeline_to_binary_illegal_timeline_handle(fd);
+
+	igt_subtest("invalid-timeline_to_binary-illegal-point")
+		test_timeline_to_binary_illegal_point(fd);
+
+	igt_subtest("invalid-timeline_to_binary-bad-pad")
+		test_timeline_to_binary_bad_pad(fd);
+
+	for (unsigned flags = 0; flags < WAIT_FLAGS_MAX; flags++) {
+		int err;
+
+		/* Only one wait mode for single-wait tests */
+		if (__builtin_popcount(flags & (WAIT_UNSUBMITTED |
+						WAIT_SUBMITTED |
+						WAIT_SIGNALED)) != 1)
+			continue;
+
+		if ((flags & WAIT_UNSUBMITTED) && !(flags & WAIT_FOR_SUBMIT))
+			err = -EINVAL;
+		else if (!(flags & WAIT_SIGNALED) && !((flags & WAIT_SUBMITTED) && (flags & WAIT_AVAILABLE)))
+			err = -ETIME;
+		else
+			err = 0;
+
+		igt_subtest_f("%ssingle-wait%s%s%s%s%s%s",
+			      err == -EINVAL ? "invalid-" : err == -ETIME ? "etime-" : "",
+			      (flags & WAIT_ALL) ? "-all" : "",
+			      (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "",
+			      (flags & WAIT_AVAILABLE) ? "-available" : "",
+			      (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "",
+			      (flags & WAIT_SUBMITTED) ? "-submitted" : "",
+			      (flags & WAIT_SIGNALED) ? "-signaled" : "")
+			test_single_wait(fd, flags, err);
+	}
+
+	igt_subtest("wait-delayed-signal")
+		test_wait_delayed_signal(fd, 0);
+
+	igt_subtest("wait-for-submit-delayed-submit")
+		test_wait_delayed_signal(fd, WAIT_FOR_SUBMIT);
+
+	igt_subtest("wait-all-delayed-signal")
+		test_wait_delayed_signal(fd, WAIT_ALL);
+
+	igt_subtest("wait-all-for-submit-delayed-submit")
+		test_wait_delayed_signal(fd, WAIT_ALL | WAIT_FOR_SUBMIT);
+
+	igt_subtest("reset-unsignaled")
+		test_reset_unsignaled(fd);
+
+	igt_subtest("reset-signaled")
+		test_reset_signaled(fd);
+
+	igt_subtest("reset-multiple-signaled")
+		test_reset_multiple_signaled(fd);
+
+	igt_subtest("reset-during-wait-for-submit")
+		test_reset_during_wait_for_submit(fd);
+
+	igt_subtest("signal")
+		test_signal(fd);
+
+	for (unsigned flags = 0; flags < WAIT_FLAGS_MAX; flags++) {
+		int err;
+
+		/* At least one wait mode for multi-wait tests */
+		if (!(flags & (WAIT_UNSUBMITTED |
+			       WAIT_SUBMITTED |
+			       WAIT_SIGNALED)))
+			continue;
+
+		err = 0;
+		if ((flags & WAIT_UNSUBMITTED) && !(flags & WAIT_FOR_SUBMIT)) {
+			err = -EINVAL;
+		} else if (flags & WAIT_ALL) {
+			if (flags & (WAIT_UNSUBMITTED | WAIT_SUBMITTED))
+				err = -ETIME;
+			if (!(flags & WAIT_UNSUBMITTED) && (flags & WAIT_SUBMITTED) && (flags & WAIT_AVAILABLE))
+				err = 0;
+		} else {
+			if (!(flags & WAIT_SIGNALED) && !((flags & WAIT_SUBMITTED) && (flags & WAIT_AVAILABLE)))
+				err = -ETIME;
+		}
+
+		igt_subtest_f("%smulti-wait%s%s%s%s%s%s",
+			      err == -EINVAL ? "invalid-" : err == -ETIME ? "etime-" : "",
+			      (flags & WAIT_ALL) ? "-all" : "",
+			      (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "",
+			      (flags & WAIT_AVAILABLE) ? "-available" : "",
+			      (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "",
+			      (flags & WAIT_SUBMITTED) ? "-submitted" : "",
+			      (flags & WAIT_SIGNALED) ? "-signaled" : "")
+			test_multi_wait(fd, flags, err);
+	}
+	igt_subtest("wait-any-snapshot")
+		test_wait_snapshot(fd, 0);
+
+	igt_subtest("wait-all-snapshot")
+		test_wait_snapshot(fd, WAIT_ALL);
+
+	igt_subtest("wait-for-submit-snapshot")
+		test_wait_snapshot(fd, WAIT_FOR_SUBMIT);
+
+	igt_subtest("wait-all-for-submit-snapshot")
+		test_wait_snapshot(fd, WAIT_ALL | WAIT_FOR_SUBMIT);
+
+	igt_subtest("wait-any-complex")
+		test_wait_complex(fd, 0);
+
+	igt_subtest("wait-all-complex")
+		test_wait_complex(fd, WAIT_ALL);
+
+	igt_subtest("wait-for-submit-complex")
+		test_wait_complex(fd, WAIT_FOR_SUBMIT);
+
+	igt_subtest("wait-all-for-submit-complex")
+		test_wait_complex(fd, WAIT_ALL | WAIT_FOR_SUBMIT);
+
+	igt_subtest("wait-any-interrupted")
+		test_wait_interrupted(fd, 0);
+
+	igt_subtest("wait-all-interrupted")
+		test_wait_interrupted(fd, WAIT_ALL);
+}