diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index bb013c7..430b637 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -230,6 +230,7 @@ TESTS_progs = \
 	prime_vgem \
 	sw_sync \
 	syncobj_basic \
+	syncobj_wait \
 	template \
 	tools_test \
 	vgem_basic \
diff --git a/tests/syncobj_wait.c b/tests/syncobj_wait.c
new file mode 100644
index 0000000..263d530
--- /dev/null
+++ b/tests/syncobj_wait.c
@@ -0,0 +1,477 @@
+/*
+ * Copyright © 2017 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 "drm.h"
+
+IGT_TEST_DESCRIPTION("Tests for the drm sync object wait 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 uint64_t
+short_timeout(void)
+{
+	return gettime_ns() + SHORT_TIME_NSEC;
+}
+
+static int
+syncobj_attach_sw_sync(int fd, uint32_t handle)
+{
+	struct drm_syncobj_handle;
+	int timeline, fence;
+
+	timeline = sw_sync_timeline_create();
+	fence = sw_sync_timeline_create_fence(timeline, 1);
+	syncobj_import_sync_file(fd, handle, fence);
+	close(fence);
+
+	return timeline;
+}
+
+static void
+syncobj_trigger(int fd, uint32_t handle)
+{
+	int timeline = syncobj_attach_sw_sync(fd, handle);
+	sw_sync_timeline_inc(timeline, 1);
+}
+
+struct delayed_trigger {
+	int fd;
+	uint32_t *syncobjs;
+	int count;
+	uint64_t nsec;
+};
+
+static void
+trigger_syncobj_delayed_func(union sigval sigval)
+{
+	struct delayed_trigger *trigger = sigval.sival_ptr;
+	int i;
+
+	for (i = 0; i < trigger->count; i++)
+		syncobj_trigger(trigger->fd, trigger->syncobjs[i]);
+	free(trigger);
+}
+
+static timer_t
+trigger_syncobj_delayed(int fd, uint32_t *syncobjs, int count, uint64_t nsec)
+{
+	struct delayed_trigger *trigger;
+        timer_t timer;
+        struct sigevent sev;
+        struct itimerspec its;
+
+	trigger = malloc(sizeof(*trigger));
+	trigger->fd = fd;
+	trigger->syncobjs = syncobjs;
+	trigger->count = count;
+	trigger->nsec = nsec;
+
+        memset(&sev, 0, sizeof(sev));
+        sev.sigev_notify = SIGEV_THREAD;
+        sev.sigev_value.sival_ptr = trigger;
+        sev.sigev_notify_function = trigger_syncobj_delayed_func;
+        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;
+}
+
+static void
+test_wait_bad_flags(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	wait.flags = 0xdeadbeef;
+	igt_assert_eq(__syncobj_wait(fd, &wait), -EINVAL);
+}
+
+static void
+test_wait_zero_handles(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	igt_assert_eq(__syncobj_wait(fd, &wait), -EINVAL);
+}
+
+static void
+test_wait_illegal_handle(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t handle = 0;
+
+	wait.count_handles = 1;
+	wait.handles = to_user_pointer(&handle);
+	igt_assert_eq(__syncobj_wait(fd, &wait), -ENOENT);
+}
+
+static void
+test_reset_bad_flags(int fd)
+{
+	struct local_syncobj_reset reset = { 0 };
+	int ret;
+
+	reset.flags = 0xdeadbeef;
+	ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset);
+	igt_assert(ret == -1 && errno ==  EINVAL);
+}
+
+static void
+test_reset_illegal_handle(int fd)
+{
+	struct local_syncobj_reset reset = { 0 };
+	int ret;
+
+	reset.handle = 0;
+	ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset);
+	igt_assert(ret == -1 && errno ==  ENOENT);
+}
+
+#define WAIT_FOR_SUBMIT		(1 << 0)
+#define WAIT_ALL		(1 << 1)
+#define WAIT_UNSUBMITTED	(1 << 2)
+#define WAIT_SUBMITTED		(1 << 3)
+#define WAIT_SIGNALED		(1 << 4)
+#define WAIT_FLAGS_MAX		(1 << 5) - 1
+
+static void
+test_single_wait(int fd, uint32_t test_flags, int expect)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	uint32_t flags = 0;
+	int timeline;
+
+	if (test_flags & WAIT_FOR_SUBMIT)
+		flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	if (test_flags & WAIT_ALL)
+		flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	if (test_flags & (WAIT_SUBMITTED | WAIT_SIGNALED))
+		timeline = syncobj_attach_sw_sync(fd, syncobj);
+
+	if (test_flags & WAIT_SIGNALED)
+		sw_sync_timeline_inc(timeline, 1);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, flags), expect);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, short_timeout(),
+				       flags), expect);
+
+	if (expect != -ETIME) {
+		igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, UINT64_MAX,
+					       flags), expect);
+	}
+
+	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 = 0;
+	int i, fidx, timeline;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+	syncobjs[2] = syncobj_create(fd);
+
+	if (test_flags & WAIT_FOR_SUBMIT)
+		flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	if (test_flags & WAIT_ALL)
+		flags |= LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	test_flags &= ~(WAIT_ALL | WAIT_FOR_SUBMIT);
+
+	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]);
+		if (tflag & WAIT_SIGNALED)
+			sw_sync_timeline_inc(timeline, 1);
+	}
+
+	igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, 0, flags), expect);
+
+	igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, short_timeout(),
+				       flags), expect);
+
+	if (expect != -ETIME) {
+		igt_assert_eq(syncobj_wait_err(fd, syncobjs, 3, UINT64_MAX,
+					       flags), expect);
+	}
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+	syncobj_destroy(fd, syncobjs[2]);
+}
+
+static void
+test_wait_for_submit_delayed_signal(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_syncobj_wait wait = { 0 };
+	timer_t timer;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	timer = trigger_syncobj_delayed(fd, &syncobj, 1, SHORT_TIME_NSEC);
+
+	wait.timeout_nsec = 0;
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	wait.timeout_nsec = gettime_ns() + SHORT_TIME_NSEC * 2;
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	timer_delete(timer);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL);
+
+	syncobj_reset(fd, syncobj);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+
+	syncobj_trigger(fd, syncobj);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), 0);
+
+	syncobj_reset(fd, syncobj);
+
+	igt_assert_eq(syncobj_wait_err(fd, &syncobj, 1, 0, 0), -EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_all_for_submit_some_delayed_signal(int fd)
+{
+	uint32_t syncobjs[2];
+	uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+			 LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	timer_t timer;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	syncobj_trigger(fd, syncobjs[0]);
+
+	timer = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	igt_assert_eq(syncobj_wait_err(fd, syncobjs, 2, 0, flags), -ETIME);
+
+	igt_assert(syncobj_wait(fd, syncobjs, 2,
+				gettime_ns() + SHORT_TIME_NSEC * 2,
+				flags, NULL));
+
+	timer_delete(timer);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_for_submit_some_delayed_signal(int fd)
+{
+	uint32_t syncobjs[2];
+	uint32_t flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	uint32_t first;
+	timer_t timer;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	timer = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	igt_assert_eq(syncobj_wait_err(fd, syncobjs, 2, 0, flags), -ETIME);
+
+	igt_assert(syncobj_wait(fd, syncobjs, 2,
+				gettime_ns() + SHORT_TIME_NSEC * 2,
+				flags, &first));
+	igt_assert_eq(first, 1);
+
+	timer_delete(timer);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static bool
+has_syncobj_wait(int fd)
+{
+	struct local_syncobj_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, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	return ret == -1 && errno == ENOENT;
+}
+
+igt_main
+{
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_ANY);
+		igt_require(has_syncobj_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-reset-bad-flags")
+		test_reset_bad_flags(fd);
+
+	igt_subtest("invalid-reset-illegal-handle")
+		test_reset_illegal_handle(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))
+			err = -ETIME;
+		else
+			err = 0;
+
+		igt_subtest_f("%ssingle-wait%s%s%s%s%s",
+			      err == -EINVAL ? "invalid-" : "",
+			      (flags & WAIT_ALL) ? "-all" : "",
+			      (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "",
+			      (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "",
+			      (flags & WAIT_SUBMITTED) ? "-submitted" : "",
+			      (flags & WAIT_SIGNALED) ? "-signaled" : "")
+			test_single_wait(fd, flags, err);
+	}
+
+	igt_subtest("reset-unsignaled")
+		test_reset_unsignaled(fd);
+
+	igt_subtest("reset-signaled")
+		test_reset_signaled(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;
+		} else {
+			if (!(flags & WAIT_SIGNALED))
+				err = -ETIME;
+		}
+
+		igt_subtest_f("%smulti-wait%s%s%s%s%s",
+			      err == -EINVAL ? "invalid-" : "",
+			      (flags & WAIT_ALL) ? "-all" : "",
+			      (flags & WAIT_FOR_SUBMIT) ? "-for-submit" : "",
+			      (flags & WAIT_UNSUBMITTED) ? "-unsubmitted" : "",
+			      (flags & WAIT_SUBMITTED) ? "-submitted" : "",
+			      (flags & WAIT_SIGNALED) ? "-signaled" : "")
+			test_multi_wait(fd, flags, err);
+	}
+
+
+	igt_subtest("wait-for-submit-delayed-signal")
+		test_wait_for_submit_delayed_signal(fd);
+
+	igt_subtest("wait-all-for-submit-some-delayed-signal")
+		test_wait_all_for_submit_some_delayed_signal(fd);
+
+	igt_subtest("wait-any-for-submit-some-delayed-signal")
+		test_wait_any_for_submit_some_delayed_signal(fd);
+}
