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..6689d34
--- /dev/null
+++ b/tests/syncobj_wait.c
@@ -0,0 +1,624 @@
+/*
+ * 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 <unistd.h>
+#include <time.h>
+#include <sys/ioctl.h>
+#include "drm.h"
+
+IGT_TEST_DESCRIPTION("Tests for the drm sync object wait API");
+
+/* One one tenth of a second */
+#define SHORT_TIME_NSEC 100000000ull
+
+/** A per-platform function which triggers a set of sync objects
+ *
+ * If wait is set, the function should wait for the work to complete so
+ * that an immediate call to SYNCOBJ_WAIT will return success.  If wait is
+ * not set, then the function should try to submit enough work that an
+ * immediate call to SYNCOBJ_WAIT with a timeout of 0 will time out.
+ */
+void (*trigger_syncobj)(int fd, uint32_t *syncobjs, int count, bool wait);
+
+#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 uint32_t
+syncobj_create(int fd)
+{
+	struct drm_syncobj_create create = { 0 };
+	int ret;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_CREATE, &create);
+	igt_assert(ret == 0);
+	igt_assert(create.handle > 0);
+
+	return create.handle;
+}
+
+static void
+syncobj_destroy(int fd, uint32_t handle)
+{
+	struct drm_syncobj_destroy destroy = { 0 };
+	int ret;
+
+	destroy.handle = handle;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_DESTROY, &destroy);
+	igt_assert(ret == 0);
+}
+
+struct delayed_trigger {
+	int fd;
+	uint32_t *syncobjs;
+	int count;
+	uint64_t nsec;
+};
+
+static void *
+trigger_syncobj_delayed_func(void *data)
+{
+	struct delayed_trigger *trigger = data;
+	struct timespec time;
+
+	time.tv_sec = trigger->nsec / NSECS_PER_SEC;
+	time.tv_nsec = trigger->nsec % NSECS_PER_SEC;
+
+	nanosleep(&time, NULL);
+	trigger_syncobj(trigger->fd, trigger->syncobjs, trigger->count, true);
+	free(data);
+
+	return NULL;
+}
+
+static pthread_t
+trigger_syncobj_delayed(int fd, uint32_t *syncobjs, int count, uint64_t nsec)
+{
+	struct delayed_trigger *trigger;
+	pthread_t thread;
+	int ret;
+
+	trigger = malloc(sizeof(*trigger));
+	trigger->fd = fd;
+	trigger->syncobjs = syncobjs;
+	trigger->count = count;
+	trigger->nsec = nsec;
+
+	ret = pthread_create(&thread, NULL,
+			     trigger_syncobj_delayed_func, trigger);
+	igt_assert(ret == 0);
+
+	return thread;
+}
+
+static void
+test_wait_bad_flags(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.flags = 0xdeadbeef;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+
+static void
+test_wait_zero_handles(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == EINVAL);
+}
+
+static void
+test_wait_illegal_handle(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t handle = 2;
+	int ret;
+
+	wait.count_handles = 1;
+	wait.handles = (unsigned long)(void *)&handle;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ENOENT);
+}
+
+static void
+test_wait_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.timeout_nsec = short_timeout();
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_for_submit_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	wait.timeout_nsec = short_timeout();
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ETIME);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+
+	trigger_syncobj(fd, &syncobj, 1, false);
+
+	wait.timeout_nsec = 0;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_warn_on(ret != -1 || errno != ETIME);
+
+	wait.timeout_nsec = short_timeout();
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_for_submit_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	trigger_syncobj(fd, &syncobj, 1, false);
+
+	wait.timeout_nsec = 0;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_warn_on(ret != -1 || errno != ETIME);
+
+	wait.timeout_nsec = short_timeout();
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_for_submit_delayed_signal(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct drm_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	trigger_syncobj_delayed(fd, &syncobj, 1, SHORT_TIME_NSEC);
+
+	wait.timeout_nsec = 0;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ETIME);
+
+	wait.timeout_nsec = gettime_ns() + SHORT_TIME_NSEC * 2;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_all_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 2, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_all_some_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 1, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == EINVAL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 2, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_some_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 1, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+
+	/* Even though we're waiting for anything, the kernel should still
+	 * reject the unsignaled syncobj.
+	 */
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == EINVAL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_for_submit_all_some_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 1, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+		     DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ETIME);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_for_submit_any_some_signaled(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t tmp, syncobjs[2];
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 1, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0 && wait.first_signaled == 0);
+
+	/* Swap and try again */
+	tmp = syncobjs[0];
+	syncobjs[0] = syncobjs[1];
+	syncobjs[1] = tmp;
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0 && wait.first_signaled == 1);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_all_for_submit_some_delayed_signal(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	pthread_t thread;
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	trigger_syncobj(fd, syncobjs, 1, true);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+		     DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	thread = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ETIME);
+
+	wait.timeout_nsec = gettime_ns() + 2 * SHORT_TIME_NSEC;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0);
+
+	pthread_join(thread, NULL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_for_submit_some_delayed_signal(int fd)
+{
+	struct drm_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	pthread_t thread;
+	int ret;
+
+	syncobjs[0] = syncobj_create(fd);
+	syncobjs[1] = syncobj_create(fd);
+
+	wait.handles = to_user_pointer(&syncobjs);
+	wait.count_handles = 2;
+	wait.timeout_nsec = short_timeout();
+	wait.flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	thread = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == -1 && errno == ETIME);
+
+	wait.timeout_nsec = gettime_ns() + 2 * SHORT_TIME_NSEC;
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(ret == 0 && wait.first_signaled == 1);
+
+	pthread_join(thread, NULL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+/******** i915 specific bits ******/
+struct {
+	uint32_t batch;
+} i915;
+
+#define I915_BATCH_COUNT 128
+
+static void
+i915_trigger_syncobj(int fd, uint32_t *syncobjs, int count, bool wait)
+{
+	struct drm_i915_gem_exec_object2 exec_obj = { 0 };
+	struct drm_i915_gem_exec_fence *fence_array;
+	struct drm_i915_gem_execbuffer2 execbuf = { 0 };
+	struct drm_i915_gem_wait gem_wait;
+	int i, ret;
+
+	fence_array = calloc(count, sizeof(*fence_array));
+	for (i = 0; i < count; i++) {
+		fence_array[i].handle = syncobjs[i];
+		fence_array[i].flags = I915_EXEC_FENCE_SIGNAL;
+	}
+
+	exec_obj.handle = i915.batch;
+
+	execbuf.buffers_ptr = to_user_pointer(&exec_obj);
+	execbuf.buffer_count = 1;
+	execbuf.batch_start_offset = 0;
+	execbuf.batch_len = 8;
+	execbuf.flags = I915_EXEC_RENDER | I915_EXEC_FENCE_ARRAY;
+
+	for (i = 0; i < I915_BATCH_COUNT; i++) {
+		if (i == I915_BATCH_COUNT - 1) {
+			execbuf.cliprects_ptr = to_user_pointer(fence_array);
+			execbuf.num_cliprects = count;
+		}
+		ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_EXECBUFFER2, &execbuf);
+		igt_assert(ret == 0);
+	}
+
+	free(fence_array);
+
+	if (wait) {
+		gem_wait.bo_handle = i915.batch;
+		gem_wait.flags = 0;
+		gem_wait.timeout_ns = INT64_MAX;
+		ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait);
+		igt_assert(ret == 0);
+	}
+}
+
+static void
+i915_init(int fd)
+{
+	uint32_t batch_data[2] = {0, MI_BATCH_BUFFER_END};
+
+	i915.batch = gem_create(fd, 4096);
+	gem_write(fd, i915.batch, 0, batch_data, sizeof(batch_data));
+	trigger_syncobj = i915_trigger_syncobj;
+}
+/******** end of i915 bits ******/
+
+static bool
+has_syncobj_wait(int fd)
+{
+	struct drm_syncobj_wait wait = { 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 */
+	ret = ioctl(fd, DRM_IOCTL_SYNCOBJ_WAIT, &wait);
+	return ret == -1 && errno == EINVAL;
+}
+
+igt_main
+{
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver_render(DRIVER_INTEL);
+		igt_require_gem(fd);
+		igt_require(has_syncobj_wait(fd));
+
+		if (is_i915_device(fd))
+			i915_init(fd);
+	}
+
+	igt_subtest("wait-bad-flags")
+		test_wait_bad_flags(fd);
+
+	igt_subtest("wait-zero-handles")
+		test_wait_zero_handles(fd);
+
+	igt_subtest("wait-illegal-handle")
+		test_wait_illegal_handle(fd);
+
+	igt_subtest("wait-unsignaled")
+		test_wait_unsignaled(fd);
+
+	igt_subtest("wait-for-submit-unsignaled")
+		test_wait_for_submit_unsignaled(fd);
+
+	igt_subtest("wait-signaled")
+		test_wait_signaled(fd);
+
+	igt_subtest("wait-for-submit-signaled")
+		test_wait_for_submit_signaled(fd);
+
+	igt_subtest("wait-for-submit-delayed-signal")
+		test_wait_for_submit_delayed_signal(fd);
+
+	igt_subtest("wait-all-signaled")
+		test_wait_all_signaled(fd);
+
+	igt_subtest("wait-all-some-signaled")
+		test_wait_all_some_signaled(fd);
+
+	igt_subtest("wait-any-signaled")
+		test_wait_any_signaled(fd);
+
+	igt_subtest("wait-any-some-signaled")
+		test_wait_any_some_signaled(fd);
+
+	igt_subtest("wait-for-submit-all-some-signaled")
+		test_wait_for_submit_all_some_signaled(fd);
+
+	igt_subtest("wait-for-submit-any-some-signaled")
+		test_wait_for_submit_any_some_signaled(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);
+}
