diff mbox

[i-g-t] syncobj: Add some wait and reset tests (v3)

Message ID 1502304574-12189-1-git-send-email-jason.ekstrand@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jason Ekstrand Aug. 9, 2017, 6:49 p.m. UTC
This adds both trivial error-checking tests as well as more complex
tests which actually test whether or not waits do what they're supposed
to do.  They only currently work on i915 but it should be simple to hook
them up for other drivers by simply implementing the little function
pointer hook provided at the top for triggering a syncobj.

v2:
 - Actually add the reset tests.
v3:
 - Only do one execbuf for trigger
 - Use do_ioctl and do_ioctl_err
 - Better check for syncobj support
 - Add local_/LOCAL_ defines of things
 - Use a timer instead of a pthread

Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
---
 tests/Makefile.sources |   1 +
 tests/syncobj_wait.c   | 691 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 692 insertions(+)
 create mode 100644 tests/syncobj_wait.c

Comments

Chris Wilson Aug. 9, 2017, 8:45 p.m. UTC | #1
Quoting Jason Ekstrand (2017-08-09 19:49:34)
> This adds both trivial error-checking tests as well as more complex
> tests which actually test whether or not waits do what they're supposed
> to do.  They only currently work on i915 but it should be simple to hook
> them up for other drivers by simply implementing the little function
> pointer hook provided at the top for triggering a syncobj.
> 
> v2:
>  - Actually add the reset tests.
> v3:
>  - Only do one execbuf for trigger
>  - Use do_ioctl and do_ioctl_err
>  - Better check for syncobj support
>  - Add local_/LOCAL_ defines of things
>  - Use a timer instead of a pthread
> 
> Signed-off-by: Jason Ekstrand <jason@jlekstrand.net>
> ---
>  tests/Makefile.sources |   1 +
>  tests/syncobj_wait.c   | 691 +++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 692 insertions(+)
>  create mode 100644 tests/syncobj_wait.c
> 
> 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..d584b96
> --- /dev/null
> +++ b/tests/syncobj_wait.c
> @@ -0,0 +1,691 @@
> +/*
> + * 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 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 LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0)
> +#define LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1)
> +struct local_syncobj_wait {
> +       __u64 handles;
> +       /* absolute timeout */
> +       __s64 timeout_nsec;
> +       __u32 count_handles;
> +       __u32 flags;
> +       __u32 first_signaled; /* only valid when not waiting all */
> +       __u32 pad;
> +};
> +
> +struct local_syncobj_reset {
> +       __u32 handle;
> +       __u32 flags;
> +};
> +
> +#define LOCAL_IOCTL_SYNCOBJ_WAIT       DRM_IOWR(0xC3, struct local_syncobj_wait)
> +#define LOCAL_IOCTL_SYNCOBJ_RESET      DRM_IOWR(0xC4, struct local_syncobj_reset)
> +
> +#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);

I recommend a pattern like:

int err;

static int __syncobj_create(int fd, uint32_t *syncobj)
{
	struct local_syncobj_create create = {};
	int err;

	if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_CREATE, &create))
		return -errno;

	*syncobj = create.handle;
	return 0;
}

static uint32_t syncobj_create(int fd)
{
	uint32_t syncobj;

	igt_assert_eq(__syncobj_create(fd, &syncobj), 0);
	igt_assert(syncobj);

	return syncobj;
}

igt_assert_eq() prints the comparison and its values, makes debugging
less guess work, and __syncobj_create() is much easier to understand
than the macro expansion inside the assert output.

You may want to pass the ioctl_arg or individual members as convenience
dictates.

> +       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(union sigval sigval)
> +{
> +       struct delayed_trigger *trigger = sigval.sival_ptr;
> +       struct timespec time;
> +
> +       trigger_syncobj(trigger->fd, trigger->syncobjs, trigger->count, true);
> +       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;
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);
> +}
> +
> +static void
> +test_wait_zero_handles(int fd)
> +{
> +       struct local_syncobj_wait wait = { 0 };
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &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);
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ENOENT);

Better check a valid + an invalid handle also generates an error.

EFAULT testing is missing, i.e. wait.handles = -1; handles is allowed to
be in read-only memory, so another good test would be:

wait.handles = -1;
igt_assert_eq(__syncobj_wait(fd, &eait), -EFAULT);

ptr = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1);
igt_assert(ptr != MAP_FAILED);

wait.handles = ptr;
igt_assert_eq(__syncobj_wait(fd, &eait), -ENOENT);

do_or_die(mprotect(ptr, 4096, PROT_READ));
igt_assert_eq(__syncobj_wait(fd, &eait), -ENOENT);

do_or_die(mprotect(ptr, 4096, PROT_NONE));
igt_assert_eq(__syncobj_wait(fd, &eait), -EFAULT);

munmap(ptr, 4096);


> +}
> +
> +static void
> +test_reset_bad_flags(int fd)
> +{
> +       struct local_syncobj_reset reset = { 0 };
> +       reset.flags = 0xdeadbeef;
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, EINVAL);
> +}
> +
> +static void
> +test_reset_illegal_handle(int fd)
> +{
> +       struct local_syncobj_reset reset = { 0 };
> +       reset.handle = 0;
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, ENOENT);

reset.handle = syncobj_create(fd);
do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, 0);

syncobj_destroy(fd, reset.handle);
do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, ENOENT);

Also good for test_wait_illegal_handle.

> +}
> +
> +static void
> +test_wait_unsignaled(int fd)
> +{
> +       uint32_t syncobj = syncobj_create(fd);
> +       struct local_syncobj_wait wait = { 0 };
> +
> +       wait.handles = to_user_pointer(&syncobj);
> +       wait.count_handles = 1;
> +       wait.timeout_nsec = short_timeout();
> +
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);

Does polling an unsubmitted syncobj also raise an error?

i.e.
wait.timeout_nsec = 0,
wait.timeout_nsec = short,
wait.timeout_nsec = infinity
all generate EINVAL?

> +
> +       syncobj_destroy(fd, syncobj);
> +}
> +
> +static void
> +test_wait_for_submit_unsignaled(int fd)
> +{
> +       uint32_t syncobj = syncobj_create(fd);
> +       struct local_syncobj_wait wait = { 0 };
> +
> +       wait.handles = to_user_pointer(&syncobj);
> +       wait.count_handles = 1;
> +       wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
> +       wait.timeout_nsec = short_timeout();
> +
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);

Same question about wait.timeout_nsec = 0.

> +
> +       syncobj_destroy(fd, syncobj);
> +}
> +
> +static void
> +test_wait_signaled(int fd)
> +{
> +       uint32_t syncobj = syncobj_create(fd);
> +       struct local_syncobj_wait wait = { 0 };
> +       int ret;
> +
> +       wait.handles = to_user_pointer(&syncobj);
> +       wait.count_handles = 1;

assert unsigned here.

> +
> +       trigger_syncobj(fd, &syncobj, 1, false);
> +
> +       wait.timeout_nsec = 0;
> +       ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
> +       igt_warn_on(ret != -1 || errno != ETIME);

It had better well be signaled at this point, i.e.

	wait.timeout_nsec = 0;
	while (drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait))
		;

should terminate (imo, consider it like glQuerySync). Hmm, this of course
rules out just using dma_fence_is_signaled() as the only condition due to
the lax rules on
dma_fence.

> +       wait.timeout_nsec = short_timeout();
> +       do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
> +
> +       syncobj_destroy(fd, syncobj);
> +}
> +
> +static void
> +test_wait_for_submit_signaled(int fd)
> +{
> +       uint32_t syncobj = syncobj_create(fd);
> +       struct local_syncobj_wait wait = { 0 };
> +       int ret;
> +
> +       wait.handles = to_user_pointer(&syncobj);
> +       wait.count_handles = 1;
> +       wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
> +
> +       trigger_syncobj(fd, &syncobj, 1, false);
> +
> +       wait.timeout_nsec = 0;
> +       ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
> +       igt_warn_on(ret != -1 || errno != ETIME);

Same arguments as above.

> +
> +       wait.timeout_nsec = short_timeout();
> +       do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
> +
> +       syncobj_destroy(fd, syncobj);
> +}
> +
> +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_wait_all_some_signaled(int fd)
> +{
> +       struct local_syncobj_wait wait = { 0 };
> +       uint32_t syncobjs[2];
> +
> +       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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
> +
> +       do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);

And in reverse order. Also include invalid in the subtest name, I was
expected to see a busy/idle pair, not signaled/unsubmitted.

test_invalid_wait_all_some_unsubmitted? Maybe covered below?

At some point, you just give in and start compiling the tests using
combinatorics. And pine for automated fuzz exploration.

> +static void
> +test_wait_for_submit_any_some_signaled(int fd)
> +{
> +       struct local_syncobj_wait wait = { 0 };
> +       uint32_t tmp, syncobjs[2];
> +
> +       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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
> +
> +       do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
> +       igt_assert(wait.first_signaled == 0);

Whenever possible try to use igt_assert_eq(wait.first_signaled, 0);
Quite often we wait for an error before going overboard in adding
igt_assert_f(), but igt_assert_eq() are easy to add.

Looking good though.
-Chris
Chris Wilson Aug. 9, 2017, 9:15 p.m. UTC | #2
Quoting Jason Ekstrand (2017-08-09 19:49:34)
> This adds both trivial error-checking tests as well as more complex
> tests which actually test whether or not waits do what they're supposed
> to do.  They only currently work on i915 but it should be simple to hook
> them up for other drivers by simply implementing the little function
> pointer hook provided at the top for triggering a syncobj.
> 
> v2:
>  - Actually add the reset tests.
> v3:
>  - Only do one execbuf for trigger
>  - Use do_ioctl and do_ioctl_err
>  - Better check for syncobj support
>  - Add local_/LOCAL_ defines of things
>  - Use a timer instead of a pthread

Do we want to bake the snapshot of fences into the ABI? It's a pretty
fundamental detail (at least imo)...

test_fences_snapshot {
	syncobj[] = ...

	bind_to_cpu(0); /* tie both to the same processor/runqueue */

	igt_fork(child, 1) {
		wait_synbocj()
	}

	yield(); /* force child to run + wait */

	/* swap fences in syncobj[] */
	/* signal new fences */

	yield();
	igt_assert(kill(childpid, 0));
	igt_waitchildren()

Details are hazy, but we have to force the child in sleeping on the
snapshot of fences, then change the syncobj[] in a detectable manner.
-Chris
diff mbox

Patch

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..d584b96
--- /dev/null
+++ b/tests/syncobj_wait.c
@@ -0,0 +1,691 @@ 
+/*
+ * 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 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 LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0)
+#define LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1)
+struct local_syncobj_wait {
+       __u64 handles;
+       /* absolute timeout */
+       __s64 timeout_nsec;
+       __u32 count_handles;
+       __u32 flags;
+       __u32 first_signaled; /* only valid when not waiting all */
+       __u32 pad;
+};
+
+struct local_syncobj_reset {
+       __u32 handle;
+       __u32 flags;
+};
+
+#define LOCAL_IOCTL_SYNCOBJ_WAIT	DRM_IOWR(0xC3, struct local_syncobj_wait)
+#define LOCAL_IOCTL_SYNCOBJ_RESET	DRM_IOWR(0xC4, struct local_syncobj_reset)
+
+#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(union sigval sigval)
+{
+	struct delayed_trigger *trigger = sigval.sival_ptr;
+	struct timespec time;
+
+	trigger_syncobj(trigger->fd, trigger->syncobjs, trigger->count, true);
+	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;
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);
+}
+
+static void
+test_wait_zero_handles(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &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);
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ENOENT);
+}
+
+static void
+test_reset_bad_flags(int fd)
+{
+	struct local_syncobj_reset reset = { 0 };
+	reset.flags = 0xdeadbeef;
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, EINVAL);
+}
+
+static void
+test_reset_illegal_handle(int fd)
+{
+	struct local_syncobj_reset reset = { 0 };
+	reset.handle = 0;
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset, ENOENT);
+}
+
+static void
+test_wait_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_syncobj_wait wait = { 0 };
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.timeout_nsec = short_timeout();
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_for_submit_unsignaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_syncobj_wait wait = { 0 };
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	wait.timeout_nsec = short_timeout();
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_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 = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_warn_on(ret != -1 || errno != ETIME);
+
+	wait.timeout_nsec = short_timeout();
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_for_submit_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_syncobj_wait wait = { 0 };
+	int ret;
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	trigger_syncobj(fd, &syncobj, 1, false);
+
+	wait.timeout_nsec = 0;
+	ret = drmIoctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_warn_on(ret != -1 || errno != ETIME);
+
+	wait.timeout_nsec = short_timeout();
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+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);
+	struct local_syncobj_reset reset = { 0 };
+	struct local_syncobj_wait wait = { 0 };
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	wait.timeout_nsec = 0;
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	reset.handle = syncobj;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset);
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_reset_signaled(int fd)
+{
+	uint32_t syncobj = syncobj_create(fd);
+	struct local_syncobj_reset reset = { 0 };
+	struct local_syncobj_wait wait = { 0 };
+
+	trigger_syncobj(fd, &syncobj, 1, true);
+
+	wait.handles = to_user_pointer(&syncobj);
+	wait.count_handles = 1;
+	wait.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+	wait.timeout_nsec = 0;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	reset.handle = syncobj;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_RESET, &reset);
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	syncobj_destroy(fd, syncobj);
+}
+
+static void
+test_wait_all_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+
+	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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_all_some_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+
+	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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL;
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+
+	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();
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_any_some_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+
+	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.
+	 */
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, EINVAL);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_for_submit_all_some_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+
+	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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+		     LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+static void
+test_wait_for_submit_any_some_signaled(int fd)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t tmp, syncobjs[2];
+
+	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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(wait.first_signaled == 0);
+
+	/* Swap and try again */
+	tmp = syncobjs[0];
+	syncobjs[0] = syncobjs[1];
+	syncobjs[1] = tmp;
+
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(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 local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	timer_t timer;
+
+	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.flags = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
+		     LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	timer = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	wait.timeout_nsec = 0;
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	wait.timeout_nsec = gettime_ns() + 2 * SHORT_TIME_NSEC;
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+
+	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)
+{
+	struct local_syncobj_wait wait = { 0 };
+	uint32_t syncobjs[2];
+	timer_t timer;
+
+	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 = LOCAL_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT;
+
+	timer = trigger_syncobj_delayed(fd, &syncobjs[1], 1, SHORT_TIME_NSEC);
+
+	do_ioctl_err(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait, ETIME);
+
+	wait.timeout_nsec = gettime_ns() + 2 * SHORT_TIME_NSEC;
+	do_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_WAIT, &wait);
+	igt_assert(wait.first_signaled == 1);
+
+	timer_delete(timer);
+
+	syncobj_destroy(fd, syncobjs[0]);
+	syncobj_destroy(fd, syncobjs[1]);
+}
+
+/******** i915 specific bits ******/
+struct local_i915_gem_exec_fence {
+       /**
+        * User's handle for a dma-fence to wait on or signal.
+        */
+       __u32 handle;
+
+#define LOCAL_EXEC_FENCE_WAIT            (1<<0)
+#define LOCAL_EXEC_FENCE_SIGNAL          (1<<1)
+       __u32 flags;
+};
+
+#define LOCAL_EXEC_FENCE_ARRAY   (1<<19)
+
+static void
+i915_trigger_syncobj(int fd, uint32_t *syncobjs, int count, bool wait)
+{
+	uint32_t batch_data[2] = {0, MI_BATCH_BUFFER_END};
+	struct drm_i915_gem_exec_object2 exec_obj = { 0 };
+	struct local_i915_gem_exec_fence *fence_array;
+	struct drm_i915_gem_execbuffer2 execbuf = { 0 };
+	int i;
+
+	fence_array = calloc(count, sizeof(*fence_array));
+	for (i = 0; i < count; i++) {
+		fence_array[i].handle = syncobjs[i];
+		fence_array[i].flags = LOCAL_EXEC_FENCE_SIGNAL;
+	}
+
+	exec_obj.handle = gem_create(fd, 4096);
+	gem_write(fd, exec_obj.handle, 0, batch_data, sizeof(batch_data));
+
+	execbuf.buffers_ptr = to_user_pointer(&exec_obj);
+	execbuf.buffer_count = 1;
+	execbuf.batch_start_offset = 0;
+	execbuf.batch_len = 8;
+	execbuf.cliprects_ptr = to_user_pointer(fence_array);
+	execbuf.num_cliprects = count;
+	execbuf.flags = I915_EXEC_RENDER | LOCAL_EXEC_FENCE_ARRAY;
+
+	gem_execbuf(fd, &execbuf);
+
+	free(fence_array);
+
+	if (wait)
+		gem_sync(fd, exec_obj.handle);
+
+	gem_close(fd, exec_obj.handle);
+}
+
+static void
+i915_init(int fd)
+{
+	trigger_syncobj = i915_trigger_syncobj;
+}
+/******** end of i915 bits ******/
+
+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_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("reset-bad-flags")
+		test_reset_bad_flags(fd);
+
+	igt_subtest("reset-illegal-handle")
+		test_reset_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("reset-unsignaled")
+		test_reset_unsignaled(fd);
+
+	igt_subtest("reset-signaled")
+		test_reset_signaled(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);
+}