diff mbox

[i-g-t,4/4] tests/syncobj: Add some wait and reset tests (v5)

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

Commit Message

Jason Ekstrand Aug. 10, 2017, 5:35 a.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
v4:
 - Use ioctl wrappers
 - Use VGEM instead of i915
 - Combine a bunch of the simple tests into one function
v5:
 - Combinatorially generate basic tests
 - Use sw_sync instead of using vgem directly

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

Comments

Chris Wilson Aug. 10, 2017, 9:27 a.m. UTC | #1
Quoting Jason Ekstrand (2017-08-10 06:35:43)
> +igt_main
> +{
> +       int fd;
> +
> +       igt_fixture {
> +               fd = drm_open_driver(DRIVER_ANY);

DRIVER_ANY | DRIVER_VGEM

(ANY really means KMS)

> +               igt_require(has_syncobj_wait(fd));
> +               igt_require_sw_sync();

We should add something like

igt_require(has_timer_resolution(CLOCK_MONOTONIC, SHORT_TIME_NSEC / 2));

static bool has_timer_resolution(clockid_t clk, uint64_t target_ns)
{
	struct timespec res;

	if (clock_getres(clk, &res))
		return false; /* clock unavailable */

	return res.tv_sec * NSEC_PER_SEC + res.tv_nsec <= target_ns;
}

That'll rule out testing on pnv most likely.
-Chris
Chris Wilson Aug. 10, 2017, 9:40 a.m. UTC | #2
Quoting Jason Ekstrand (2017-08-10 06:35:43)
> 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
> v4:
>  - Use ioctl wrappers
>  - Use VGEM instead of i915
>  - Combine a bunch of the simple tests into one function
> v5:
>  - Combinatorially generate basic tests
>  - Use sw_sync instead of using vgem directly

Aye, sw_sync looks to be quite useful here - a completely driver
agnostic method for signaling syncobj. Nice.

> +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
trigger_syncobj_delayed_func(union sigval sigval)
{
	int timeline = (intptr_t)sigval.sival_ptr;
	sw_sync_timeline_inc(timeline, 1);
	close(timeline);
}

static timer_t
trigger_syncobj_delayed(int fd, uint32_t *syncobjs, int count, uint64_t nsec)
{
        struct itimerspec its;
        struct sigevent sev;
	int timeline, fence;
        timer_t timer;

	timeline = sw_sync_timeline_create();
	fence = sw_sync_timeline_create_fence(timeline, 1);

	for (int i = 0; i < count; i++)
		syncobj_import_sync_file(fd, syncobjs[i], fence);
	close(fence);

	memset(&sev, 0, sizeof(sev));
	sev.sigev_notify = SIGEV_THREAD;
        sev.sigev_value.sival_ptr = (intptr_t)timeline;
        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;
}
Chris Wilson Aug. 10, 2017, 9:50 a.m. UTC | #3
Quoting Jason Ekstrand (2017-08-10 06:35:43)
> +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);

Won't this fail in 6000 years time... Perhaps not an issue, but
maximum duration is gettime_ns() + INT64_MAX (and these tests serve as
part of the ABI guide / documentation as well as checking the contract).
We don't have an encoding for an infinite wait, I guess several millennia
will just have to do.
-Chris
Jason Ekstrand Aug. 10, 2017, 4:02 p.m. UTC | #4
On Thu, Aug 10, 2017 at 2:40 AM, Chris Wilson <chris@chris-wilson.co.uk>
wrote:

> Quoting Jason Ekstrand (2017-08-10 06:35:43)
> > 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
> > v4:
> >  - Use ioctl wrappers
> >  - Use VGEM instead of i915
> >  - Combine a bunch of the simple tests into one function
> > v5:
> >  - Combinatorially generate basic tests
> >  - Use sw_sync instead of using vgem directly
>
> Aye, sw_sync looks to be quite useful here - a completely driver
> agnostic method for signaling syncobj. Nice.
>
> > +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
> trigger_syncobj_delayed_func(union sigval sigval)
> {
>         int timeline = (intptr_t)sigval.sival_ptr;
>         sw_sync_timeline_inc(timeline, 1);
>         close(timeline);
> }
>
> static timer_t
> trigger_syncobj_delayed(int fd, uint32_t *syncobjs, int count, uint64_t
> nsec)
> {
>         struct itimerspec its;
>         struct sigevent sev;
>         int timeline, fence;
>         timer_t timer;
>
>         timeline = sw_sync_timeline_create();
>         fence = sw_sync_timeline_create_fence(timeline, 1);
>
>         for (int i = 0; i < count; i++)
>                 syncobj_import_sync_file(fd, syncobjs[i], fence);
>         close(fence);
>
>         memset(&sev, 0, sizeof(sev));
>         sev.sigev_notify = SIGEV_THREAD;
>         sev.sigev_value.sival_ptr = (intptr_t)timeline;
>         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;
> }
>

As i continue improving things, it's morphing to look more like that.
Actually my current test do it both ways.  The original way for
WAIT_FOR_SUBMIT and the way described above for normal waits.  There will
be a v6...
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..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);
+}