diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
new file mode 100644
index 0000000..3f2f9a9
--- /dev/null
+++ b/.settings/language.settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project>
+	<configuration id="cdt.managedbuild.config.gnu.cross.exe.debug.2128817656" name="Debug">
+		<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+			<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+			<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+			<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+			<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="-953487821198006981" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
+				<language-scope id="org.eclipse.cdt.core.gcc"/>
+				<language-scope id="org.eclipse.cdt.core.g++"/>
+			</provider>
+		</extension>
+	</configuration>
+	<configuration id="cdt.managedbuild.config.gnu.cross.exe.release.884103109" name="Release">
+		<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+			<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+			<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+			<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+			<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="-953487821198006981" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
+				<language-scope id="org.eclipse.cdt.core.gcc"/>
+				<language-scope id="org.eclipse.cdt.core.g++"/>
+			</provider>
+		</extension>
+	</configuration>
+</project>
diff --git a/lib/sw_sync.c b/lib/sw_sync.c
index 861119e..862f8c4 100644
--- a/lib/sw_sync.c
+++ b/lib/sw_sync.c
@@ -110,7 +110,7 @@ void sw_sync_timeline_inc(int fd, uint32_t count)
 	do_ioctl(fd, SW_SYNC_IOC_INC, &arg);
 }
 
-int sw_sync_merge(int fd1, int fd2)
+int sync_merge(int fd1, int fd2)
 {
 	struct sync_merge_data data = {};
 	int err;
@@ -126,7 +126,7 @@ int sw_sync_merge(int fd1, int fd2)
 	return data.fence;
 }
 
-int sw_sync_wait(int fd, int timeout)
+int sync_wait(int fd, int timeout)
 {
 	struct pollfd fds = {0};
 	int ret;
@@ -151,7 +151,7 @@ int sw_sync_wait(int fd, int timeout)
 	return ret;
 }
 
-int sw_sync_fence_count(int fd)
+int sync_fence_count(int fd)
 {
 	struct sync_file_info info;
 
@@ -162,7 +162,7 @@ int sw_sync_fence_count(int fd)
 	return info.num_fences;
 }
 
-static int __sw_sync_fence_count_status(int fd, int status)
+static int __sync_fence_count_status(int fd, int status)
 {
 	struct sync_file_info info;
 	struct sync_fence_info *fence_info;
@@ -191,9 +191,9 @@ static int __sw_sync_fence_count_status(int fd, int status)
 	return count;
 }
 
-int sw_sync_fence_count_status(int fd, int status)
+int sync_fence_count_status(int fd, int status)
 {
-	int count = __sw_sync_fence_count_status(fd, status);
+	int count = __sync_fence_count_status(fd, status);
 	igt_assert(count >= 0);
 	return count;
 }
diff --git a/lib/sw_sync.h b/lib/sw_sync.h
index fee9249..1f47b29 100644
--- a/lib/sw_sync.h
+++ b/lib/sw_sync.h
@@ -39,10 +39,10 @@ int sw_sync_timeline_create(void);
 int __sw_sync_fence_create(int fd, uint32_t seqno);
 int sw_sync_fence_create(int fd, uint32_t seqno);
 void sw_sync_timeline_inc(int fd, uint32_t count);
-int sw_sync_merge(int fd1, int fd2);
-int sw_sync_wait(int fence, int timeout);
-int sw_sync_fence_count(int fd);
-int sw_sync_fence_count_status(int fd, int status);
+int sync_merge(int fd1, int fd2);
+int sync_wait(int fence, int timeout);
+int sync_fence_count(int fd);
+int sync_fence_count_status(int fd, int status);
 
 #endif
 
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 65e0792..8848094 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -131,6 +131,7 @@ TESTS_progs_M = \
 	prime_mmap_kms \
 	prime_self_import \
 	prime_vgem \
+	sw_sync \
 	template \
 	vgem_basic \
 	vgem_slow \
diff --git a/tests/drm_fence b/tests/drm_fence
new file mode 100755
index 0000000000000000000000000000000000000000..e6dd304879891827c182f517db4c6f972e082554
GIT binary patch
diff --git a/tests/kms_atomic.c.orig b/tests/kms_atomic.c.orig
new file mode 100644
index 0000000..3b4ef84
--- /dev/null
+++ b/tests/kms_atomic.c.orig
@@ -0,0 +1,1803 @@
+
+/*
+ * Copyright © 2015 Intel Corporation
+ * Copyright © 2014-2015 Collabora, Ltd.
+ *
+ * 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.
+ *
+ * Authors:
+ *    Micah Fedke <micah.fedke@collabora.co.uk>
+ *    Daniel Stone <daniels@collabora.com>
+ *    Pekka Paalanen <pekka.paalanen@collabora.co.uk>
+ */
+
+/*
+ * Testcase: testing atomic modesetting API
+ */
+
+#include <limits.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <xf86drmMode.h>
+#include <cairo.h>
+#include "drm.h"
+#include "ioctl_wrappers.h"
+#include "drmtest.h"
+#include "igt.h"
+#include "igt_aux.h"
+#include "sw_sync.h"
+
+#ifndef DRM_CAP_CURSOR_WIDTH
+#define DRM_CAP_CURSOR_WIDTH 0x8
+#endif
+
+#ifndef DRM_CAP_CURSOR_HEIGHT
+#define DRM_CAP_CURSOR_HEIGHT 0x9
+#endif
+
+IGT_TEST_DESCRIPTION("Test atomic modesetting API");
+
+enum kms_atomic_check_relax {
+	ATOMIC_RELAX_NONE = 0,
+	CRTC_RELAX_MODE = (1 << 0),
+	PLANE_RELAX_FB = (1 << 1)
+};
+
+/**
+ * KMS plane type enum
+ *
+ * KMS plane types are represented by enums, which do not have stable numeric
+ * values, but must be looked up by their string value each time.
+ *
+ * To make the code more simple, we define a plane_type enum which maps to
+ * each KMS enum value. These values must be looked up through the map, and
+ * cannot be passed directly to KMS functions.
+ */
+enum plane_type {
+	PLANE_TYPE_PRIMARY = 0,
+	PLANE_TYPE_OVERLAY,
+	PLANE_TYPE_CURSOR,
+	NUM_PLANE_TYPE_PROPS
+};
+
+static const char *plane_type_prop_names[NUM_PLANE_TYPE_PROPS] = {
+	"Primary",
+	"Overlay",
+	"Cursor"
+};
+
+struct kms_atomic_blob {
+	uint32_t id; /* 0 if not already allocated */
+	size_t len;
+	void *data;
+};
+
+struct kms_atomic_connector_state {
+	struct kms_atomic_state *state;
+	uint32_t obj;
+	uint32_t crtc_id;
+};
+
+struct kms_atomic_plane_state {
+	struct kms_atomic_state *state;
+	uint32_t obj;
+	enum plane_type type;
+	uint32_t crtc_mask;
+	uint32_t crtc_id; /* 0 to disable */
+	uint32_t fb_id; /* 0 to disable */
+	uint32_t src_x, src_y, src_w, src_h; /* 16.16 fixed-point */
+	uint32_t crtc_x, crtc_y, crtc_w, crtc_h; /* normal integers */
+	int fence_fd;
+};
+
+struct kms_atomic_crtc_state {
+	struct kms_atomic_state *state;
+	uint32_t obj;
+	int idx;
+	bool active;
+	struct kms_atomic_blob mode;
+};
+
+struct kms_atomic_state {
+	struct kms_atomic_connector_state *connectors;
+	int num_connectors;
+	struct kms_atomic_crtc_state *crtcs;
+	int num_crtcs;
+	struct kms_atomic_plane_state *planes;
+	int num_planes;
+	struct kms_atomic_desc *desc;
+};
+
+struct kms_atomic_desc {
+	int fd;
+	uint32_t props_connector[IGT_NUM_CONNECTOR_PROPS];
+	uint32_t props_crtc[IGT_NUM_CRTC_PROPS];
+	uint32_t props_plane[IGT_NUM_PLANE_PROPS];
+	uint64_t props_plane_type[NUM_PLANE_TYPE_PROPS];
+};
+
+static uint32_t blob_duplicate(int fd, uint32_t id_orig)
+{
+	drmModePropertyBlobPtr orig = drmModeGetPropertyBlob(fd, id_orig);
+	uint32_t id_new;
+
+	igt_assert(orig);
+	do_or_die(drmModeCreatePropertyBlob(fd, orig->data, orig->length,
+					    &id_new));
+	drmModeFreePropertyBlob(orig);
+
+	return id_new;
+}
+
+#define crtc_set_prop(req, crtc, prop, value) \
+	igt_assert_lt(0, drmModeAtomicAddProperty(req, crtc->obj, \
+						  crtc->state->desc->props_crtc[prop], \
+						  value));
+
+#define plane_set_prop(req, plane, prop, value) \
+	igt_assert_lt(0, drmModeAtomicAddProperty(req, plane->obj, \
+						  plane->state->desc->props_plane[prop], \
+						  value));
+
+#define do_atomic_commit(fd, req, flags) \
+	do_or_die(drmModeAtomicCommit(fd, req, flags, NULL));
+
+#define do_atomic_commit_err(fd, req, flags, err) { \
+	igt_assert_neq(drmModeAtomicCommit(fd, req, flags, NULL), 0); \
+	igt_assert_eq(errno, err); \
+}
+
+#define crtc_commit_atomic(crtc, plane, req, relax) { \
+	drmModeAtomicSetCursor(req, 0); \
+	crtc_populate_req(crtc, req); \
+	plane_populate_req(plane, req); \
+	do_atomic_commit((crtc)->state->desc->fd, req, 0); \
+	crtc_check_current_state(crtc, plane, relax); \
+	plane_check_current_state(plane, relax); \
+}
+
+#define crtc_commit_atomic_err(crtc, plane, crtc_old, plane_old, req, relax, e) { \
+	drmModeAtomicSetCursor(req, 0); \
+	crtc_populate_req(crtc, req); \
+	plane_populate_req(plane, req); \
+	do_atomic_commit_err((crtc)->state->desc->fd, req, 0, e); \
+	crtc_check_current_state(crtc_old, plane_old, relax); \
+	plane_check_current_state(plane_old, relax); \
+}
+
+#define plane_commit_atomic(plane, req, relax) { \
+	drmModeAtomicSetCursor(req, 0); \
+	plane_populate_req(plane, req); \
+	do_atomic_commit((plane)->state->desc->fd, req, 0); \
+	plane_check_current_state(plane, relax); \
+}
+
+#define plane_commit_atomic_err(plane, plane_old, req, relax, e) { \
+	drmModeAtomicSetCursor(req, 0); \
+	plane_populate_req(plane, req); \
+	do_atomic_commit_err((plane)->state->desc->fd, req, 0, e); \
+	plane_check_current_state(plane_old, relax); \
+}
+
+static void
+connector_get_current_state(struct kms_atomic_connector_state *connector)
+{
+	drmModeObjectPropertiesPtr props;
+	int i;
+
+	props = drmModeObjectGetProperties(connector->state->desc->fd,
+					   connector->obj,
+					   DRM_MODE_OBJECT_CONNECTOR);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		uint32_t *prop_ids = connector->state->desc->props_connector;
+
+		if (props->props[i] == prop_ids[IGT_CONNECTOR_CRTC_ID])
+			connector->crtc_id = props->prop_values[i];
+	}
+	drmModeFreeObjectProperties(props);
+}
+
+#if 0
+/* XXX: Checking this repeatedly actually hangs the GPU. I have literally no
+ *      idea why. */
+static void
+connector_check_current_state(struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_connector_state connector_kernel;
+	drmModeConnectorPtr legacy;
+	uint32_t crtc_id;
+
+	legacy = drmModeGetConnectorCurrent(connector->state->desc->fd,
+					    connector->obj);
+	igt_assert(legacy);
+
+	if (legacy->encoder_id) {
+		drmModeEncoderPtr legacy_enc;
+
+		legacy_enc = drmModeGetEncoder(connector->state->desc->fd,
+					       legacy->encoder_id);
+		igt_assert(legacy_enc);
+
+		crtc_id = legacy_enc->crtc_id;
+		drmModeFreeEncoder(legacy_enc);
+	} else {
+		crtc_id = 0;
+	}
+
+	igt_assert_eq_u32(crtc_id, connector->crtc_id);
+
+	memcpy(&connector_kernel, connector, sizeof(connector_kernel));
+	connector_get_current_state(&connector_kernel);
+	do_or_die(memcmp(&connector_kernel, connector,
+			 sizeof(connector_kernel)));
+
+	drmModeFreeConnector(legacy);
+}
+#endif
+
+static struct kms_atomic_connector_state *
+find_connector(struct kms_atomic_state *state,
+	       struct kms_atomic_crtc_state *crtc)
+{
+	int i;
+
+	for (i = 0; i < state->num_connectors; i++) {
+		struct kms_atomic_connector_state *connector =
+			&state->connectors[i];
+
+		if (!connector->obj)
+			continue;
+		if (crtc && connector->crtc_id != crtc->obj)
+			continue;
+
+		return connector;
+	}
+
+	return NULL;
+}
+
+static void plane_populate_req(struct kms_atomic_plane_state *plane,
+			       drmModeAtomicReq *req)
+{
+	if (plane->fence_fd > 0)
+		plane_set_prop(req, plane, IGT_PLANE_FENCE_FD, plane->fence_fd);
+	plane_set_prop(req, plane, IGT_PLANE_CRTC_ID, plane->crtc_id);
+	plane_set_prop(req, plane, IGT_PLANE_FB_ID, plane->fb_id);
+	plane_set_prop(req, plane, IGT_PLANE_SRC_X, plane->src_x);
+	plane_set_prop(req, plane, IGT_PLANE_SRC_Y, plane->src_y);
+	plane_set_prop(req, plane, IGT_PLANE_SRC_W, plane->src_w);
+	plane_set_prop(req, plane, IGT_PLANE_SRC_H, plane->src_h);
+	plane_set_prop(req, plane, IGT_PLANE_CRTC_X, plane->crtc_x);
+	plane_set_prop(req, plane, IGT_PLANE_CRTC_Y, plane->crtc_y);
+	plane_set_prop(req, plane, IGT_PLANE_CRTC_W, plane->crtc_w);
+	plane_set_prop(req, plane, IGT_PLANE_CRTC_H, plane->crtc_h);
+}
+
+static void plane_get_current_state(struct kms_atomic_plane_state *plane)
+{
+	struct kms_atomic_desc *desc = plane->state->desc;
+	drmModeObjectPropertiesPtr props;
+	int i;
+
+	props = drmModeObjectGetProperties(desc->fd, plane->obj,
+					   DRM_MODE_OBJECT_PLANE);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		uint32_t *prop_ids = desc->props_plane;
+
+		if (props->props[i] == prop_ids[IGT_PLANE_CRTC_ID])
+			plane->crtc_id = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_FB_ID])
+			plane->fb_id = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_CRTC_X])
+			plane->crtc_x = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_CRTC_Y])
+			plane->crtc_y = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_CRTC_W])
+			plane->crtc_w = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_CRTC_H])
+			plane->crtc_h = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_SRC_X])
+			plane->src_x = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_SRC_Y])
+			plane->src_y = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_SRC_W])
+			plane->src_w = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_SRC_H])
+			plane->src_h = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_FENCE_FD])
+			plane->fence_fd = props->prop_values[i];
+		else if (props->props[i] == prop_ids[IGT_PLANE_TYPE]) {
+			int j;
+
+			for (j = 0; j < ARRAY_SIZE(desc->props_plane_type); j++) {
+				if (props->prop_values[i] == desc->props_plane_type[j]) {
+					plane->type = j;
+					break;
+				}
+			}
+		}
+	}
+
+	drmModeFreeObjectProperties(props);
+}
+
+static void plane_check_current_state(struct kms_atomic_plane_state *plane,
+				      enum kms_atomic_check_relax relax)
+{
+	drmModePlanePtr legacy;
+	struct kms_atomic_plane_state plane_kernel;
+
+	legacy = drmModeGetPlane(plane->state->desc->fd, plane->obj);
+	igt_assert(legacy);
+
+	igt_assert_eq_u32(legacy->crtc_id, plane->crtc_id);
+
+	if (!(relax & PLANE_RELAX_FB))
+		igt_assert_eq_u32(legacy->fb_id, plane->fb_id);
+
+	memcpy(&plane_kernel, plane, sizeof(plane_kernel));
+	plane_get_current_state(&plane_kernel);
+
+	/* Legacy cursor ioctls create their own, unknowable, internal
+	 * framebuffer which we can't reason about. */
+	if (relax & PLANE_RELAX_FB)
+		plane_kernel.fb_id = plane->fb_id;
+	do_or_die(memcmp(&plane_kernel, plane, sizeof(plane_kernel)));
+
+	drmModeFreePlane(legacy);
+}
+
+static void plane_commit_legacy(struct kms_atomic_plane_state *plane,
+				enum kms_atomic_check_relax relax)
+{
+	do_or_die(drmModeSetPlane(plane->state->desc->fd, plane->obj,
+				  plane->crtc_id,
+				  plane->fb_id, 0,
+				  plane->crtc_x, plane->crtc_y,
+				  plane->crtc_w, plane->crtc_h,
+				  plane->src_x, plane->src_y,
+				  plane->src_w, plane->src_h));
+	plane_check_current_state(plane, relax);
+}
+
+static struct kms_atomic_plane_state *
+find_plane(struct kms_atomic_state *state, enum plane_type type,
+	   struct kms_atomic_crtc_state *crtc)
+{
+	int i;
+
+	for (i = 0; i < state->num_planes; i++) {
+		struct kms_atomic_plane_state *plane = &state->planes[i];
+
+		if (!plane->obj)
+			continue;
+		if (type != NUM_PLANE_TYPE_PROPS && plane->type != type)
+			continue;
+		if (crtc && !(plane->crtc_mask & (1 << crtc->idx)))
+			continue;
+
+		plane_get_current_state(plane);
+		return plane;
+	}
+
+	return NULL;
+}
+
+static void crtc_populate_req(struct kms_atomic_crtc_state *crtc,
+			      drmModeAtomicReq *req)
+{
+	crtc_set_prop(req, crtc, IGT_CRTC_MODE_ID, crtc->mode.id);
+	crtc_set_prop(req, crtc, IGT_CRTC_ACTIVE, crtc->active);
+}
+
+static void crtc_get_current_state(struct kms_atomic_crtc_state *crtc)
+{
+	drmModeObjectPropertiesPtr props;
+	int i;
+
+	props = drmModeObjectGetProperties(crtc->state->desc->fd, crtc->obj,
+					   DRM_MODE_OBJECT_CRTC);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		uint32_t *prop_ids = crtc->state->desc->props_crtc;
+
+		if (props->props[i] == prop_ids[IGT_CRTC_MODE_ID]) {
+			drmModePropertyBlobPtr blob;
+
+			crtc->mode.id = props->prop_values[i];
+			if (!crtc->mode.id) {
+				crtc->mode.len = 0;
+				continue;
+			}
+
+			blob = drmModeGetPropertyBlob(crtc->state->desc->fd,
+						      crtc->mode.id);
+			igt_assert(blob);
+			igt_assert_eq_u32(blob->length,
+					  sizeof(struct drm_mode_modeinfo));
+
+			if (!crtc->mode.data ||
+			    memcmp(crtc->mode.data, blob->data, blob->length) != 0)
+				crtc->mode.data = blob->data;
+			crtc->mode.len = blob->length;
+		}
+		else if (props->props[i] == prop_ids[IGT_CRTC_ACTIVE]) {
+			crtc->active = props->prop_values[i];
+		}
+	}
+
+	drmModeFreeObjectProperties(props);
+}
+
+static void crtc_check_current_state(struct kms_atomic_crtc_state *crtc,
+				     struct kms_atomic_plane_state *primary,
+				     enum kms_atomic_check_relax relax)
+{
+	struct kms_atomic_crtc_state crtc_kernel;
+	drmModeCrtcPtr legacy;
+
+	legacy = drmModeGetCrtc(crtc->state->desc->fd, crtc->obj);
+	igt_assert(legacy);
+
+	igt_assert_eq_u32(legacy->crtc_id, crtc->obj);
+	igt_assert_eq_u32(legacy->x, primary->src_x >> 16);
+	igt_assert_eq_u32(legacy->y, primary->src_y >> 16);
+
+	if (crtc->active)
+		igt_assert_eq_u32(legacy->buffer_id, primary->fb_id);
+	else
+		igt_assert_eq_u32(legacy->buffer_id, 0);
+
+	if (legacy->mode_valid) {
+		igt_assert_neq(legacy->mode_valid, 0);
+		igt_assert_eq(crtc->mode.len,
+			      sizeof(struct drm_mode_modeinfo));
+		do_or_die(memcmp(&legacy->mode, crtc->mode.data,
+				 crtc->mode.len));
+		igt_assert_eq(legacy->width, legacy->mode.hdisplay);
+		igt_assert_eq(legacy->height, legacy->mode.vdisplay);
+	} else {
+		igt_assert_eq(legacy->mode_valid, 0);
+	}
+
+	memcpy(&crtc_kernel, crtc, sizeof(crtc_kernel));
+	crtc_get_current_state(&crtc_kernel);
+
+	if (crtc_kernel.mode.id != 0)
+		igt_assert_eq(crtc_kernel.mode.len,
+			      sizeof(struct drm_mode_modeinfo));
+
+	/* Optionally relax the check for MODE_ID: using the legacy SetCrtc
+	 * API can potentially change MODE_ID even if the mode itself remains
+	 * unchanged. */
+	if (((relax & CRTC_RELAX_MODE) &&
+	    (crtc_kernel.mode.id != crtc->mode.id &&
+	     crtc_kernel.mode.id != 0 && crtc->mode.id != 0)) &&
+	    memcmp(crtc_kernel.mode.data, crtc->mode.data,
+		   sizeof(struct drm_mode_modeinfo)) == 0) {
+		crtc_kernel.mode.id = crtc->mode.id;
+		crtc_kernel.mode.data = crtc->mode.data;
+	}
+
+	do_or_die(memcmp(&crtc_kernel, crtc, sizeof(crtc_kernel)));
+
+	drmModeFreeCrtc(legacy);
+}
+
+static void crtc_commit_legacy(struct kms_atomic_crtc_state *crtc,
+			       struct kms_atomic_plane_state *plane,
+			       enum kms_atomic_check_relax relax)
+{
+	drmModeObjectPropertiesPtr props;
+	uint32_t *connectors;
+	int num_connectors = 0;
+	int i;
+
+	if (!crtc->active) {
+		do_or_die(drmModeSetCrtc(crtc->state->desc->fd,
+					 crtc->obj, 0, 0, 0, NULL, 0, NULL));
+		return;
+	}
+
+	connectors = calloc(crtc->state->num_connectors,
+			    sizeof(*connectors));
+	igt_assert(connectors);
+
+	igt_assert_neq_u32(crtc->mode.id, 0);
+
+	for (i = 0; i < crtc->state->num_connectors; i++) {
+		struct kms_atomic_connector_state *connector =
+			&crtc->state->connectors[i];
+
+		if (connector->crtc_id != crtc->obj)
+			continue;
+
+		connectors[num_connectors++] = connector->obj;
+	}
+
+	do_or_die(drmModeSetCrtc(crtc->state->desc->fd, crtc->obj,
+				 plane->fb_id,
+				 plane->src_x >> 16, plane->src_y >> 16,
+				 (num_connectors) ? connectors : NULL,
+				 num_connectors,
+				 crtc->mode.data));
+	/* When doing a legacy commit, the core may update MODE_ID to be a new
+	 * blob implicitly created by the legacy request. Hence we backfill
+	 * the value in the state object to ensure they match. */
+	props = drmModeObjectGetProperties(crtc->state->desc->fd, crtc->obj,
+					   DRM_MODE_OBJECT_CRTC);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		if (props->props[i] !=
+		    crtc->state->desc->props_crtc[IGT_CRTC_MODE_ID])
+			continue;
+		crtc->mode.id = props->prop_values[i];
+		break;
+	}
+
+	drmModeFreeObjectProperties(props);
+
+	crtc_check_current_state(crtc, plane, relax);
+	plane_check_current_state(plane, relax);
+}
+
+static struct kms_atomic_crtc_state *find_crtc(struct kms_atomic_state *state,
+					       bool must_be_enabled)
+{
+	int i;
+
+	for (i = 0; i < state->num_crtcs; i++) {
+		struct kms_atomic_crtc_state *crtc = &state->crtcs[i];
+
+		if (!crtc->obj)
+			continue;
+		if (must_be_enabled && !crtc->active)
+			continue;
+
+		crtc_get_current_state(crtc);
+		return crtc;
+	}
+
+	return NULL;
+}
+
+static void fill_obj_props(int fd, uint32_t id, int type, int num_props,
+			   const char **prop_names, uint32_t *prop_ids)
+{
+	drmModeObjectPropertiesPtr props;
+	int i, j;
+
+	props = drmModeObjectGetProperties(fd, id, type);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		drmModePropertyPtr prop =
+			drmModeGetProperty(fd, props->props[i]);
+
+		for (j = 0; j < num_props; j++) {
+			if (strcmp(prop->name, prop_names[j]) != 0)
+				continue;
+			prop_ids[j] = props->props[i];
+			break;
+		}
+
+		drmModeFreeProperty(prop);
+	}
+
+	drmModeFreeObjectProperties(props);
+}
+
+static void fill_obj_prop_map(int fd, uint32_t id, int type, const char *name,
+			      int num_enums, const char **enum_names,
+			      uint64_t *enum_ids)
+{
+	drmModeObjectPropertiesPtr props;
+	int i, j, k;
+
+	props = drmModeObjectGetProperties(fd, id, type);
+	igt_assert(props);
+
+	for (i = 0; i < props->count_props; i++) {
+		drmModePropertyPtr prop =
+			drmModeGetProperty(fd, props->props[i]);
+
+		igt_assert(prop);
+
+		if (strcmp(prop->name, name) != 0) {
+			drmModeFreeProperty(prop);
+			continue;
+		}
+
+		for (j = 0; j < prop->count_enums; j++) {
+			struct drm_mode_property_enum *e = &prop->enums[j];
+
+			for (k = 0; k < num_enums; k++) {
+				if (strcmp(e->name, enum_names[k]) != 0)
+					continue;
+
+				enum_ids[k] = e->value;
+				break;
+			}
+		}
+
+		drmModeFreeProperty(prop);
+	}
+}
+
+static void atomic_setup(struct kms_atomic_state *state)
+{
+	struct kms_atomic_desc *desc = state->desc;
+	drmModeResPtr res;
+	drmModePlaneResPtr res_plane;
+	const char **crtc_prop_names;
+	const char **plane_prop_names;
+	const char **connector_prop_names;
+	int i;
+
+	desc->fd = drm_open_driver_master(DRIVER_ANY);
+	igt_assert_fd(desc->fd);
+
+	igt_skip_on(drmSetClientCap(desc->fd, DRM_CLIENT_CAP_ATOMIC, 1));
+
+	res = drmModeGetResources(desc->fd);
+	res_plane = drmModeGetPlaneResources(desc->fd);
+	igt_assert(res);
+	igt_assert(res_plane);
+
+	igt_assert_lt(0, res->count_crtcs);
+	state->num_crtcs = res->count_crtcs;
+	state->crtcs = calloc(state->num_crtcs, sizeof(*state->crtcs));
+	igt_assert(state->crtcs);
+
+	igt_assert_lt(0, res_plane->count_planes);
+	state->num_planes = res_plane->count_planes;
+	state->planes = calloc(state->num_planes, sizeof(*state->planes));
+	igt_assert(state->planes);
+
+	igt_assert_lt(0, res->count_connectors);
+	state->num_connectors = res->count_connectors;
+	state->connectors = calloc(state->num_connectors,
+				   sizeof(*state->connectors));
+	igt_assert(state->connectors);
+
+	crtc_prop_names = igt_atomic_get_prop_names(DRM_MODE_OBJECT_CONNECTOR);
+	plane_prop_names = igt_atomic_get_prop_names(DRM_MODE_OBJECT_PLANE);
+	connector_prop_names = igt_atomic_get_prop_names(DRM_MODE_OBJECT_CONNECTOR);
+
+	fill_obj_props(desc->fd, res->crtcs[0],
+		       DRM_MODE_OBJECT_CRTC, IGT_NUM_CRTC_PROPS,
+		       crtc_prop_names, desc->props_crtc);
+
+	fill_obj_props(desc->fd, res_plane->planes[0],
+		       DRM_MODE_OBJECT_PLANE, IGT_NUM_PLANE_PROPS,
+		       plane_prop_names, desc->props_plane);
+	fill_obj_prop_map(desc->fd, res_plane->planes[0],
+			  DRM_MODE_OBJECT_PLANE, "type",
+			  NUM_PLANE_TYPE_PROPS, plane_type_prop_names,
+			  desc->props_plane_type);
+
+	fill_obj_props(desc->fd, res->connectors[0],
+		       DRM_MODE_OBJECT_CONNECTOR, IGT_NUM_CONNECTOR_PROPS,
+		       connector_prop_names, desc->props_connector);
+
+	for (i = 0; i < state->num_crtcs; i++) {
+		struct kms_atomic_crtc_state *crtc = &state->crtcs[i];
+
+		crtc->state = state;
+		crtc->obj = res->crtcs[i];
+		crtc->idx = i;
+		crtc_get_current_state(crtc);
+
+		/* The blob pointed to by MODE_ID could well be transient,
+		 * and lose its last reference as we switch away from it.
+		 * Duplicate the blob here so we have a reference we know we
+		 * own. */
+		if (crtc->mode.id != 0)
+		    crtc->mode.id = blob_duplicate(desc->fd, crtc->mode.id);
+	}
+
+	for (i = 0; i < state->num_planes; i++) {
+		drmModePlanePtr plane =
+			drmModeGetPlane(desc->fd, res_plane->planes[i]);
+		igt_assert(plane);
+
+		state->planes[i].state = state;
+		state->planes[i].obj = res_plane->planes[i];
+		state->planes[i].crtc_mask = plane->possible_crtcs;
+		plane_get_current_state(&state->planes[i]);
+	}
+
+	for (i = 0; i < state->num_connectors; i++) {
+		state->connectors[i].state = state;
+		state->connectors[i].obj = res->connectors[i];
+		connector_get_current_state(&state->connectors[i]);
+	}
+
+	drmModeFreePlaneResources(res_plane);
+	drmModeFreeResources(res);
+}
+
+static struct kms_atomic_state *
+atomic_state_dup(const struct kms_atomic_state *state)
+{
+	struct kms_atomic_state *ret = calloc(1, sizeof(*ret));
+
+	igt_assert(ret);
+	*ret = *state;
+
+	ret->crtcs = calloc(ret->num_crtcs, sizeof(*ret->crtcs));
+	igt_assert(ret->crtcs);
+	memcpy(ret->crtcs, state->crtcs, ret->num_crtcs * sizeof(*ret->crtcs));
+
+	ret->planes = calloc(ret->num_planes, sizeof(*ret->planes));
+	igt_assert(ret->planes);
+	memcpy(ret->planes, state->planes,
+	       ret->num_planes * sizeof(*ret->planes));
+
+	ret->connectors = calloc(ret->num_connectors, sizeof(*ret->connectors));
+	igt_assert(ret->connectors);
+	memcpy(ret->connectors, state->connectors,
+	       ret->num_connectors * sizeof(*ret->connectors));
+
+	return ret;
+}
+
+static void atomic_state_free(struct kms_atomic_state *state)
+{
+	free(state->crtcs);
+	free(state->planes);
+	free(state->connectors);
+	free(state);
+}
+
+static void fb_destroy(struct kms_atomic_desc *desc,
+		       uint32_t bo_id, uint32_t fb_id)
+{
+	struct drm_mode_destroy_dumb arg;
+
+	drmModeRmFB(desc->fd, fb_id);
+
+	memset(&arg, 0, sizeof(arg));
+	arg.handle = bo_id;
+	drmIoctl(desc->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &arg);
+}
+
+static void fb_create(struct kms_atomic_desc *desc,
+		      struct kms_atomic_plane_state *plane,
+		      uint32_t *bo_id, uint32_t *fb_id)
+{
+	uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
+	unsigned int width = plane->crtc_w;
+	unsigned int height = plane->crtc_h;
+	unsigned int fourcc = DRM_FORMAT_XRGB8888;
+	unsigned int stride, size;
+	unsigned int ret;
+
+	*bo_id = kmstest_dumb_create(desc->fd, width, height, 32,
+					  &stride, &size);
+	handles[0] = *bo_id;
+	pitches[0] = stride;
+	offsets[0] = 0;
+
+	ret = drmModeAddFB2(desc->fd, width, height, fourcc, handles,
+			    pitches, offsets, fb_id, 0);
+	if (ret != 0) {
+		fb_destroy(desc, *bo_id, *fb_id);
+		igt_assert(false);
+	}
+}
+
+static uint32_t plane_get_igt_format(struct kms_atomic_plane_state *plane)
+{
+	drmModePlanePtr plane_kms;
+	const uint32_t *igt_formats;
+	uint32_t ret = 0;
+	int num_igt_formats;
+	int i;
+
+	plane_kms = drmModeGetPlane(plane->state->desc->fd, plane->obj);
+	igt_assert(plane_kms);
+
+	igt_get_all_cairo_formats(&igt_formats, &num_igt_formats);
+	for (i = 0; i < num_igt_formats; i++) {
+		int j;
+
+		for (j = 0; j < plane_kms->count_formats; j++) {
+			if (plane_kms->formats[j] == igt_formats[i]) {
+				ret = plane_kms->formats[j];
+				break;
+			}
+		}
+	}
+
+	drmModeFreePlane(plane_kms);
+	return ret;
+}
+
+static void sigalrm_fail(int signal)
+{
+	igt_assert_f(false, "SIGALRM subtest failure.\n");
+}
+
+typedef struct
+{
+	int timeline;
+	bool executed;
+} sigalrm_timeline_increment_data_t;
+static void sigalrm_timeline_increment(int signal)
+{
+	sigalrm_timeline_increment_data_t *data = __igt_set_custom_timeout_data;
+
+	sw_sync_timeline_inc(data->timeline, 1);
+	igt_set_timeout_custom(sigalrm_fail, data, 1);
+
+	data->executed = true;
+}
+
+static void plane_overlay(struct kms_atomic_crtc_state *crtc,
+			  struct kms_atomic_plane_state *plane_old,
+			  struct kms_atomic_connector_state *connector)
+{
+	struct drm_mode_modeinfo *mode = crtc->mode.data;
+	struct kms_atomic_plane_state plane = *plane_old;
+	uint32_t format = plane_get_igt_format(&plane);
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+	struct igt_fb fb;
+
+	igt_require(req);
+	igt_require(format != 0);
+
+	plane.src_x = 0;
+	plane.src_y = 0;
+	plane.src_w = (mode->hdisplay / 2) << 16;
+	plane.src_h = (mode->vdisplay / 2) << 16;
+	plane.crtc_x = mode->hdisplay / 4;
+	plane.crtc_y = mode->vdisplay / 4;
+	plane.crtc_w = mode->hdisplay / 2;
+	plane.crtc_h = mode->vdisplay / 2;
+	plane.crtc_id = crtc->obj;
+	plane.fb_id = igt_create_pattern_fb(plane.state->desc->fd,
+					    plane.crtc_w, plane.crtc_h,
+					    format, I915_TILING_NONE, &fb);
+
+	/* Enable the overlay plane using the atomic API, and double-check
+	 * state is what we think it should be. */
+	plane_commit_atomic(&plane, req, ATOMIC_RELAX_NONE);
+
+	/* Disable the plane and check the state matches the old. */
+	plane_commit_atomic(plane_old, req, ATOMIC_RELAX_NONE);
+
+	/* Re-enable the plane through the legacy plane API, and verify through
+	 * atomic. */
+	plane_commit_legacy(&plane, ATOMIC_RELAX_NONE);
+
+	/* Restore the plane to its original settings through the legacy plane
+	 * API, and verify through atomic. */
+	plane_commit_legacy(plane_old, ATOMIC_RELAX_NONE);
+
+	drmModeAtomicFree(req);
+}
+
+static void plane_primary(struct kms_atomic_crtc_state *crtc,
+			  struct kms_atomic_plane_state *plane_old,
+			  struct kms_atomic_connector_state *connector)
+{
+	struct drm_mode_modeinfo *mode = crtc->mode.data;
+	struct kms_atomic_plane_state plane = *plane_old;
+	uint32_t format = plane_get_igt_format(&plane);
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+	struct igt_fb fb;
+
+	igt_require(format != 0);
+
+	plane.src_x = 0;
+	plane.src_y = 0;
+	plane.src_w = mode->hdisplay << 16;
+	plane.src_h = mode->vdisplay << 16;
+	plane.crtc_x = 0;
+	plane.crtc_y = 0;
+	plane.crtc_w = mode->hdisplay;
+	plane.crtc_h = mode->vdisplay;
+	plane.crtc_id = crtc->obj;
+	plane.fb_id = igt_create_pattern_fb(plane.state->desc->fd,
+					    plane.crtc_w, plane.crtc_h,
+					    format, I915_TILING_NONE, &fb);
+
+	/* Flip the primary plane using the atomic API, and double-check
+	 * state is what we think it should be. */
+	crtc_commit_atomic(crtc, &plane, req, ATOMIC_RELAX_NONE);
+
+	/* Restore the primary plane and check the state matches the old. */
+	crtc_commit_atomic(crtc, plane_old, req, ATOMIC_RELAX_NONE);
+
+	/* Re-enable the plane through the legacy CRTC/primary-plane API, and
+	 * verify through atomic. */
+	crtc_commit_legacy(crtc, &plane, CRTC_RELAX_MODE);
+
+	/* Restore the plane to its original settings through the legacy CRTC
+	 * API, and verify through atomic. */
+	crtc_commit_legacy(crtc, plane_old, CRTC_RELAX_MODE);
+
+	/* Finally, restore to the original state. */
+	crtc_commit_atomic(crtc, plane_old, req, ATOMIC_RELAX_NONE);
+
+	drmModeAtomicFree(req);
+}
+
+static void plane_cursor(struct kms_atomic_crtc_state *crtc,
+			 struct kms_atomic_plane_state *plane_old,
+			 struct kms_atomic_connector_state *connector)
+{
+	struct drm_mode_modeinfo *mode = crtc->mode.data;
+	struct kms_atomic_plane_state plane = *plane_old;
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+	struct igt_fb fb;
+	uint64_t width, height;
+
+	igt_assert(req);
+
+	/* Any kernel new enough for atomic, also has the cursor size caps. */
+	do_or_die(drmGetCap(plane.state->desc->fd,
+			    DRM_CAP_CURSOR_WIDTH, &width));
+	do_or_die(drmGetCap(plane.state->desc->fd,
+			    DRM_CAP_CURSOR_HEIGHT, &height));
+
+	plane.src_x = 0;
+	plane.src_y = 0;
+	plane.src_w = width << 16;
+	plane.src_h = height << 16;
+	plane.crtc_x = mode->hdisplay / 2;
+	plane.crtc_y = mode->vdisplay / 2;
+	plane.crtc_w = width;
+	plane.crtc_h = height;
+	plane.crtc_id = crtc->obj;
+	plane.fb_id = igt_create_color_fb(plane.state->desc->fd,
+					  width, height,
+					  DRM_FORMAT_ARGB8888,
+					  LOCAL_DRM_FORMAT_MOD_NONE,
+					  0.0, 0.0, 0.0,
+					  &fb);
+	igt_assert_neq_u32(plane.fb_id, 0);
+
+	/* Flip the cursor plane using the atomic API, and double-check
+	 * state is what we think it should be. */
+	plane_commit_atomic(&plane, req, ATOMIC_RELAX_NONE);
+
+	/* Restore the cursor plane and check the state matches the old. */
+	plane_commit_atomic(plane_old, req, ATOMIC_RELAX_NONE);
+
+	/* Re-enable the plane through the legacy cursor API, and verify
+	 * through atomic. */
+	do_or_die(drmModeMoveCursor(plane.state->desc->fd, plane.crtc_id,
+				    plane.crtc_x, plane.crtc_y));
+	do_or_die(drmModeSetCursor(plane.state->desc->fd, plane.crtc_id,
+				   fb.gem_handle, width, height));
+	plane_check_current_state(&plane, PLANE_RELAX_FB);
+
+	/* Wiggle. */
+	plane.crtc_x -= 16;
+	plane.crtc_y -= 16;
+	do_or_die(drmModeMoveCursor(plane.state->desc->fd, plane.crtc_id,
+				    plane.crtc_x, plane.crtc_y));
+	plane_check_current_state(&plane, PLANE_RELAX_FB);
+
+	/* Restore the plane to its original settings through the legacy cursor
+	 * API, and verify through atomic. */
+	do_or_die(drmModeSetCursor2(plane.state->desc->fd, plane.crtc_id,
+				    0, 0, 0, 0, 0));
+	plane_check_current_state(plane_old, ATOMIC_RELAX_NONE);
+
+	/* Finally, restore to the original state. */
+	plane_commit_atomic(plane_old, req, ATOMIC_RELAX_NONE);
+
+	drmModeAtomicFree(req);
+}
+
+static void plane_invalid_params(struct kms_atomic_crtc_state *crtc,
+				 struct kms_atomic_plane_state *plane_old,
+				 struct kms_atomic_connector_state *conn)
+{
+	struct drm_mode_modeinfo *mode = crtc->mode.data;
+	struct kms_atomic_plane_state plane = *plane_old;
+	uint32_t format = plane_get_igt_format(&plane);
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+	struct igt_fb fb;
+
+	/* Pass a series of invalid object IDs for the FB ID. */
+	plane.fb_id = plane.obj;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.fb_id = crtc->obj;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.fb_id = conn->obj;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.fb_id = crtc->mode.id;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.fb_id = plane_old->fb_id;
+	plane_commit_atomic(&plane, req, ATOMIC_RELAX_NONE);
+
+	/* Pass a series of invalid object IDs for the CRTC ID. */
+	plane.crtc_id = plane.obj;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.crtc_id = plane.fb_id;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.crtc_id = conn->obj;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.crtc_id = crtc->mode.id;
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, EINVAL);
+
+	plane.crtc_id = plane_old->crtc_id;
+	plane_commit_atomic(&plane, req, ATOMIC_RELAX_NONE);
+
+	/* Create a framebuffer too small for the plane configuration. */
+	igt_require(format != 0);
+
+	plane.src_x = 0;
+	plane.src_y = 0;
+	plane.src_w = mode->hdisplay << 16;
+	plane.src_h = mode->vdisplay << 16;
+	plane.crtc_x = 0;
+	plane.crtc_y = 0;
+	plane.crtc_w = mode->hdisplay;
+	plane.crtc_h = mode->vdisplay;
+	plane.crtc_id = crtc->obj;
+	plane.fb_id = igt_create_pattern_fb(plane.state->desc->fd,
+					    plane.crtc_w - 1, plane.crtc_h - 1,
+					    format, I915_TILING_NONE, &fb);
+
+	plane_commit_atomic_err(&plane, plane_old, req,
+				ATOMIC_RELAX_NONE, ENOSPC);
+
+	/* Restore the primary plane and check the state matches the old. */
+	plane_commit_atomic(plane_old, req, ATOMIC_RELAX_NONE);
+
+	drmModeAtomicFree(req);
+}
+
+static void crtc_invalid_params(struct kms_atomic_crtc_state *crtc_old,
+				struct kms_atomic_plane_state *plane,
+				struct kms_atomic_connector_state *conn)
+{
+	struct kms_atomic_crtc_state crtc = *crtc_old;
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+
+	igt_assert(req);
+
+	/* Pass a series of invalid object IDs for the mode ID. */
+	crtc.mode.id = plane->obj;
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+	crtc.mode.id = crtc.obj;
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+	crtc.mode.id = conn->obj;
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+	crtc.mode.id = plane->fb_id;
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+	crtc.mode.id = crtc_old->mode.id;
+	crtc_commit_atomic(&crtc, plane, req, ATOMIC_RELAX_NONE);
+
+	/* Create a blob which is the wrong size to be a valid mode. */
+	do_or_die(drmModeCreatePropertyBlob(crtc.state->desc->fd,
+					    crtc.mode.data,
+					    sizeof(struct drm_mode_modeinfo) - 1,
+					    &crtc.mode.id));
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+
+	do_or_die(drmModeCreatePropertyBlob(crtc.state->desc->fd,
+					    crtc.mode.data,
+					    sizeof(struct drm_mode_modeinfo) + 1,
+					    &crtc.mode.id));
+	crtc_commit_atomic_err(&crtc, plane, crtc_old, plane, req,
+			       ATOMIC_RELAX_NONE, EINVAL);
+
+	/* Restore the CRTC and check the state matches the old. */
+	crtc_commit_atomic(crtc_old, plane, req, ATOMIC_RELAX_NONE);
+
+	drmModeAtomicFree(req);
+}
+
+/* Abuse the atomic ioctl directly in order to test various invalid conditions,
+ * which the libdrm wrapper won't allow us to create. */
+static void atomic_invalid_params(struct kms_atomic_crtc_state *crtc,
+				  struct kms_atomic_plane_state *plane,
+				  struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	struct drm_mode_atomic ioc;
+	uint32_t obj_raw[16]; /* array of objects (sized by count_objs) */
+	uint32_t num_props_raw[16]; /* array of num props per obj (ditto) */
+	uint32_t props_raw[256]; /* array of props (sum of count_props) */
+	uint64_t values_raw[256]; /* array of values for properties (ditto) */
+	int i;
+
+	memset(&ioc, 0, sizeof(ioc));
+
+	/* An empty request should do nothing. */
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	for (i = 0; i < ARRAY_SIZE(obj_raw); i++)
+		obj_raw[i] = 0;
+	for (i = 0; i < ARRAY_SIZE(num_props_raw); i++)
+		num_props_raw[i] = 0;
+	for (i = 0; i < ARRAY_SIZE(props_raw); i++)
+		props_raw[i] = 0;
+	for (i = 0; i < ARRAY_SIZE(values_raw); i++)
+		values_raw[i] = 0;
+
+	ioc.objs_ptr = (uintptr_t) obj_raw;
+	ioc.count_props_ptr = (uintptr_t) num_props_raw;
+	ioc.props_ptr = (uintptr_t) props_raw;
+	ioc.prop_values_ptr = (uintptr_t) values_raw;
+
+	/* Valid pointers, but still should copy nothing. */
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Nonsense flags. */
+	ioc.flags = 0xdeadbeef;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EINVAL);
+
+	/* Specifically forbidden combination. */
+	ioc.flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_PAGE_FLIP_EVENT;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EINVAL);
+
+	ioc.flags = 0;
+	/* Safety check that flags is reset properly. */
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Reserved/MBZ. */
+	ioc.reserved = 1;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EINVAL);
+	ioc.reserved = 0;
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Zero is not a valid object ID. */
+	ioc.count_objs = ARRAY_SIZE(obj_raw);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+
+	/* Invalid object type (not a thing we can set properties on). */
+	ioc.count_objs = 1;
+	obj_raw[0] = crtc->mode.id;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+	obj_raw[0] = plane->fb_id;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+
+	/* Filled object but with no properties; no-op. */
+	for (i = 0; i < ARRAY_SIZE(obj_raw); i++)
+		obj_raw[i] = crtc->obj;
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Pass in all sorts of things other than the property ID. */
+	num_props_raw[0] = 1;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+	props_raw[0] = crtc->obj;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+	props_raw[0] = plane->obj;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+	props_raw[0] = connector->obj;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+	props_raw[0] = crtc->mode.id;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+
+	/* Valid property, valid value. */
+	for (i = 0; i < ARRAY_SIZE(props_raw); i++) {
+		props_raw[i] = desc->props_crtc[IGT_CRTC_MODE_ID];
+		values_raw[i] = crtc->mode.id;
+	}
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Setting the same thing multiple times is OK. */
+	for (i = 0; i < ARRAY_SIZE(obj_raw); i++)
+		num_props_raw[i] = ARRAY_SIZE(props_raw) / ARRAY_SIZE(obj_raw);
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+	ioc.count_objs = ARRAY_SIZE(obj_raw);
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Pass a series of outlandish addresses. */
+	ioc.objs_ptr = 0;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	ioc.objs_ptr = (uintptr_t) obj_raw;
+	ioc.count_props_ptr = 0;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	ioc.count_props_ptr = (uintptr_t) num_props_raw;
+	ioc.props_ptr = 0;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	ioc.props_ptr = (uintptr_t) props_raw;
+	ioc.prop_values_ptr = 0;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	ioc.prop_values_ptr = (uintptr_t) values_raw;
+	do_ioctl(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc);
+
+	/* Attempt to overflow and/or trip various boundary conditions. */
+	ioc.count_objs = UINT32_MAX / sizeof(uint32_t);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, ENOENT);
+
+	ioc.count_objs = ARRAY_SIZE(obj_raw);
+	ioc.objs_ptr = UINT64_MAX - sizeof(uint32_t);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+	ioc.count_objs = 1;
+	ioc.objs_ptr = UINT64_MAX - sizeof(uint32_t);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	num_props_raw[0] = UINT32_MAX / sizeof(uint32_t);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+	num_props_raw[0] = UINT32_MAX - 1;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+
+	for (i = 0; i < ARRAY_SIZE(obj_raw); i++)
+		num_props_raw[i] = (UINT32_MAX / ARRAY_SIZE(obj_raw)) + 1;
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+	for (i = 0; i < ARRAY_SIZE(obj_raw); i++)
+		num_props_raw[i] = ARRAY_SIZE(props_raw) / ARRAY_SIZE(obj_raw);
+	do_ioctl_err(desc->fd, DRM_IOCTL_MODE_ATOMIC, &ioc, EFAULT);
+}
+
+static void get_events(struct kms_atomic_crtc_state *crtc,
+		       int *page_flips,
+		       int *vblank_count,
+		       int msec)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	struct timeval timeout = { .tv_sec = 0, .tv_usec = msec * 1000 };
+	fd_set fds;
+	char buffer[1024];
+	int idx, len, ret;
+
+	FD_ZERO(&fds);
+	FD_SET(desc->fd, &fds);
+
+	ret = select(1, &fds, NULL, NULL, &timeout);
+	if (ret < 1)
+		return;
+
+	len = read(desc->fd, buffer, sizeof(buffer));
+	if (len == 0)
+		return;
+
+	idx = 0;
+	while (idx < len) {
+		struct drm_event event;
+
+		igt_assert_lte(sizeof(event), len - idx);
+
+		memcpy(&event, &buffer[idx], sizeof(event));
+		switch (event.type) {
+		case DRM_EVENT_FLIP_COMPLETE:
+			(*page_flips)++;
+			break;
+		case DRM_EVENT_VBLANK:
+			(*vblank_count)++;
+			break;
+		default:
+			break;
+		}
+
+		idx += event.length;
+	}
+}
+
+static void *fence_in_commit_thread(void *arg)
+{
+	int timeline = *((int *) arg);
+
+	pthread_detach(pthread_self());
+
+	usleep(1000);
+	sw_sync_timeline_inc(timeline, 1);
+	return NULL;
+}
+
+static void fence_in_commit(struct kms_atomic_crtc_state *crtc,
+			    struct kms_atomic_plane_state *plane,
+			    struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	int timeline;
+	int in_fence;
+	uint32_t fb_id, bo_id;
+	drmModeAtomicReq *req;
+	pthread_t thread;
+	int ret;
+
+	timeline = sw_sync_timeline_create();
+	in_fence = sw_sync_fence_create(timeline, 1);
+	fb_create(desc, plane, &bo_id, &fb_id);
+
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	plane->fence_fd = in_fence;
+	plane->fb_id = fb_id;
+	plane->crtc_id = crtc->obj;
+	plane_populate_req(plane, req);
+
+	ret = pthread_create(&thread, NULL, fence_in_commit_thread, &timeline);
+	igt_assert_eq(ret, 0);
+
+	igt_set_timeout(1, "");
+	do_atomic_commit(desc->fd, req, 0);
+	igt_reset_timeout();
+
+	drmModeAtomicFree(req);
+	fb_destroy(desc, bo_id, fb_id);
+	sw_sync_fence_destroy(in_fence);
+	sw_sync_timeline_destroy(timeline);
+}
+
+static void fence_in_commit_n(struct kms_atomic_crtc_state *crtc,
+			      struct kms_atomic_plane_state *plane,
+			      struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	int timeline;
+	uint32_t timeline_seqno = 0;
+	int in_fence;
+	uint32_t fb_id[2], bo_id[2];
+	drmModeAtomicReq *req;
+	int nbr_iterations = 1000;
+	int ret, i;
+
+	timeline = sw_sync_timeline_create();
+	fb_create(desc, plane, &bo_id[0], &fb_id[0]);
+	fb_create(desc, plane, &bo_id[1], &fb_id[1]);
+
+	for (i = 0; i < nbr_iterations; i++)
+	{
+		pthread_t thread;
+		in_fence = sw_sync_fence_create(timeline, timeline_seqno);
+
+		req = drmModeAtomicAlloc();
+		igt_assert(req != NULL);
+		plane->fence_fd = in_fence;
+		plane->fb_id = fb_id[i & 1];
+		plane->crtc_id = crtc->obj;
+		plane_populate_req(plane, req);
+
+		ret = pthread_create(&thread, NULL, fence_in_commit_thread, &timeline);
+		igt_assert_eq(ret, 0);
+
+		igt_set_timeout(1, "");
+		do_atomic_commit(desc->fd, req, 0);
+		igt_reset_timeout();
+
+		timeline_seqno++;
+		drmModeAtomicFree(req);
+		sw_sync_timeline_inc(timeline, 1);
+		sw_sync_fence_destroy(in_fence);
+	}
+
+	fb_destroy(desc, bo_id[0], fb_id[0]);
+	fb_destroy(desc, bo_id[1], fb_id[1]);
+	sw_sync_timeline_destroy(timeline);
+}
+
+static void fence_in_overwrite_fence_fd(struct kms_atomic_crtc_state *crtc,
+					struct kms_atomic_plane_state *plane,
+					struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	int in_fence[2];
+	uint32_t bo_id, fb_id;
+	drmModeAtomicReq *req;
+	int timeline;
+
+	timeline = sw_sync_timeline_create();
+	fb_create(desc, plane, &bo_id, &fb_id);
+
+	/* Set first FENCE_FD. */
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	in_fence[0] = sw_sync_fence_create(timeline, INT_MAX);
+	plane->fence_fd = in_fence[0];
+	plane->fb_id = fb_id;
+	plane->crtc_id = crtc->obj;
+	plane_populate_req(plane, req);
+	do_atomic_commit(desc->fd, req, DRM_MODE_ATOMIC_TEST_ONLY);
+
+	/* Set second FENCE_FD. */
+	in_fence[1] = sw_sync_fence_create(timeline, 0);
+	plane->fence_fd = in_fence[1];
+	plane->fb_id = fb_id;
+	plane->crtc_id = crtc->obj;
+	plane_populate_req(plane, req);
+	do_atomic_commit(desc->fd, req, 0);
+
+	fb_destroy(desc, bo_id, fb_id);
+	drmModeAtomicFree(req);
+	sw_sync_fence_destroy(in_fence[0]);
+	sw_sync_fence_destroy(in_fence[1]);
+	sw_sync_timeline_destroy(timeline);
+}
+
+static void fence_in_wait_signal(struct kms_atomic_crtc_state *crtc,
+				 struct kms_atomic_plane_state *plane,
+				 struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	int timeline;
+	int in_fence;
+	uint32_t bo_id, fb_id;
+	drmModeAtomicReq *req;
+	sigalrm_timeline_increment_data_t data;
+
+	timeline = sw_sync_timeline_create();
+
+	in_fence = sw_sync_fence_create(timeline, 1);
+	fb_create(desc, plane, &bo_id, &fb_id);
+
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	plane->fence_fd = in_fence;
+	plane->fb_id = fb_id;
+	plane->crtc_id = crtc->obj;
+	plane_populate_req(plane, req);
+
+	/* Set timeout and atomically commit, expecting it to block. */
+	data.timeline = timeline;
+	data.executed = false;
+	igt_set_timeout_custom(sigalrm_timeline_increment, &data, 1);
+	do_atomic_commit(desc->fd, req, 0);
+	igt_reset_timeout();
+
+	fb_destroy(desc, bo_id, fb_id);
+	drmModeAtomicFree(req);
+	sw_sync_fence_destroy(in_fence);
+	sw_sync_timeline_destroy(timeline);
+
+	igt_assert_eq(data.executed, 1);
+}
+
+static void fence_in_commit_invalid(struct kms_atomic_crtc_state *crtc,
+				    struct kms_atomic_plane_state *plane,
+				    struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	int timeline;
+	int in_fence;
+	drmModeAtomicReq *req;
+
+	timeline = sw_sync_timeline_create();
+	in_fence = sw_sync_fence_create(timeline, 0);
+
+	/* This atomic req lacks a FB and should return an EINVAL. */
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	plane->fence_fd = in_fence;
+	plane_populate_req(plane, req);
+
+	do_atomic_commit_err(desc->fd, req, 0, EINVAL);
+
+	drmModeAtomicFree(req);
+	sw_sync_fence_destroy(in_fence);
+	sw_sync_timeline_destroy(timeline);
+}
+
+static void fence_out_commit(struct kms_atomic_crtc_state *crtc,
+			     struct kms_atomic_plane_state *plane,
+			     struct kms_atomic_connector_state *connector)
+  {
+	  struct kms_atomic_desc *desc = crtc->state->desc;
+	  struct drm_mode_atomic ioc;
+	  const int num_crtcs = crtc->state->num_crtcs;
+	  const int count_out_fences = num_crtcs;
+	  struct drm_out_fences out_fences_raw[count_out_fences];
+	  drmModeAtomicReq *req;
+	  uint32_t bo_id, fb_id;
+	  int i, flags;
+
+	  fb_create(desc, plane, &bo_id, &fb_id);
+
+	  memset(&ioc, 0, sizeof(ioc));
+	  memset(&out_fences_raw, 0, count_out_fences * sizeof(*out_fences_raw));
+
+	  for (i = 0; i < count_out_fences; i++)
+	  {
+		  out_fences_raw[i].fd = -1;
+		  out_fences_raw[i].crtc_id = crtc->state->crtcs[i].obj;
+	  }
+
+	  req = drmModeAtomicAlloc();
+	  igt_assert(req != NULL);
+	  drmModeAtomicAddOutFences(req, out_fences_raw, count_out_fences);
+	  plane->crtc_id = crtc->obj;
+	  plane->fb_id = fb_id;
+	  crtc_populate_req(crtc, req);
+	  plane_populate_req(plane, req);
+
+	  flags = DRM_MODE_ATOMIC_OUT_FENCE;
+	  do_atomic_commit(desc->fd, req, flags);
+
+	  fb_destroy(desc, bo_id, fb_id);
+	  drmModeAtomicFree(req);
+}
+
+static void fence_out_commit_n(struct kms_atomic_crtc_state *crtc,
+			       struct kms_atomic_plane_state *plane,
+			       struct kms_atomic_connector_state *connector)
+  {
+	  struct kms_atomic_desc *desc = crtc->state->desc;
+	  const int num_crtcs = crtc->state->num_crtcs;
+	  const int count_out_fences = num_crtcs;
+	  struct drm_out_fences out_fences_raw[count_out_fences];
+	  drmModeAtomicReq *req;
+	  uint32_t bo_id[2], fb_id[2];
+	  int nbr_iterations = 20;
+	  int i, j, flags;
+
+	  fb_create(desc, plane, &bo_id[0], &fb_id[0]);
+	  fb_create(desc, plane, &bo_id[1], &fb_id[1]);
+
+	  for (i = 0; i < nbr_iterations; i++) {
+		  struct drm_mode_atomic ioc;
+
+		  memset(&ioc, 0, sizeof(ioc));
+		  memset(&out_fences_raw, 0, count_out_fences * sizeof(*out_fences_raw));
+
+		  for (i = 0; i < count_out_fences; i++)
+		  {
+			  out_fences_raw[i].fd = -1;
+			  out_fences_raw[i].crtc_id = crtc->state->crtcs[i].obj;
+		  }
+
+		  req = drmModeAtomicAlloc();
+		  igt_assert(req != NULL);
+		  drmModeAtomicAddOutFences(req, out_fences_raw, count_out_fences);
+		  plane->fb_id = fb_id[i & 1];
+		  plane->crtc_id = crtc->obj;
+		  crtc_populate_req(crtc, req);
+		  plane_populate_req(plane, req);
+
+		  flags = DRM_MODE_ATOMIC_OUT_FENCE;
+		  do_atomic_commit(desc->fd, req, flags);
+
+		igt_set_timeout(1, "fence_out_wait failed");
+		sw_sync_wait(out_fences_raw[0].fd, 1);
+		sw_sync_fence_destroy(out_fences_raw[0].fd);
+
+		igt_reset_timeout();
+
+		  drmModeAtomicFree(req);
+	  }
+
+	  fb_destroy(desc, bo_id[0], fb_id[0]);
+	  fb_destroy(desc, bo_id[1], fb_id[1]);
+}
+
+static void fence_out_wait(struct kms_atomic_crtc_state *crtc,
+			   struct kms_atomic_plane_state *plane,
+			   struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	struct drm_mode_atomic ioc;
+	const int num_crtcs = crtc->state->num_crtcs;
+	const int count_out_fences = num_crtcs;
+	struct drm_out_fences out_fences_raw[count_out_fences];
+	drmModeAtomicReq *req;
+	uint32_t bo_id, fb_id;
+	int i, flags;
+
+	fb_create(desc, plane, &bo_id, &fb_id);
+
+	memset(&ioc, 0, sizeof(ioc));
+	memset(&out_fences_raw, 0, count_out_fences * sizeof(*out_fences_raw));
+
+	for (i = 0; i < count_out_fences; i++)
+	{
+		out_fences_raw[i].fd = -1;
+		out_fences_raw[i].crtc_id = crtc->state->crtcs[i].obj;
+	}
+
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	drmModeAtomicAddOutFences(req, out_fences_raw, count_out_fences);
+	plane->crtc_id = crtc->obj;
+	plane->fb_id = fb_id;
+	plane_populate_req(plane, req);
+	crtc_populate_req(crtc, req);
+	flags = DRM_MODE_ATOMIC_OUT_FENCE;
+	do_atomic_commit(desc->fd, req, flags);
+
+	igt_set_timeout(1, "fence_out_wait failed");
+	for (i = 0; i < count_out_fences; i++)
+	{
+		sw_sync_wait(out_fences_raw[i].fd, 1000);
+		sw_sync_fence_destroy(out_fences_raw[i].fd);
+	}
+
+	igt_reset_timeout();
+
+	fb_destroy(desc, bo_id, fb_id);
+	drmModeAtomicFree(req);
+}
+
+static void fence_out_flip(struct kms_atomic_crtc_state *crtc,
+			   struct kms_atomic_plane_state *plane,
+			   struct kms_atomic_connector_state *connector)
+{
+	struct kms_atomic_desc *desc = crtc->state->desc;
+	struct drm_mode_atomic ioc;
+	const int num_crtcs = crtc->state->num_crtcs;
+	const int count_out_fences = num_crtcs;
+	struct drm_out_fences out_fences_raw[count_out_fences];
+	drmModeAtomicReq *req;
+	uint32_t bo_id, fb_id;
+	int i, flags, vblank_count = 0, pageflip_count = 0;
+
+	fb_create(desc, plane, &bo_id, &fb_id);
+
+	memset(&ioc, 0, sizeof(ioc));
+	memset(&out_fences_raw, 0, count_out_fences * sizeof(*out_fences_raw));
+
+	for (i = 0; i < count_out_fences; i++)
+	{
+		out_fences_raw[0].fd = -1;
+		out_fences_raw[0].crtc_id = crtc->state->crtcs[i].obj;
+	}
+
+	req = drmModeAtomicAlloc();
+	igt_assert(req != NULL);
+	drmModeAtomicAddOutFences(req, out_fences_raw, count_out_fences);
+	plane->fb_id = fb_id;
+	plane->crtc_id = crtc->obj;
+	crtc_populate_req(crtc, req);
+	plane_populate_req(plane, req);
+	flags = DRM_MODE_ATOMIC_OUT_FENCE | DRM_MODE_PAGE_FLIP_EVENT;
+	do_atomic_commit(desc->fd, req, flags);
+
+	get_events(crtc, &pageflip_count, &vblank_count, 20);
+	igt_assert_eq(1, pageflip_count);
+
+	igt_set_timeout(1, "fence_out_wait failed");
+	for (i = 0; i < count_out_fences; i++)
+	{
+		sw_sync_wait(out_fences_raw[i].fd, 1000);
+		sw_sync_fence_destroy(out_fences_raw[i].fd);
+	}
+
+	fb_destroy(desc, bo_id, fb_id);
+	drmModeAtomicFree(req);
+}
+
+static void run_test(struct kms_atomic_state *current,
+			void (*test)(
+				struct kms_atomic_crtc_state *crtc,
+				struct kms_atomic_plane_state *plane,
+				struct kms_atomic_connector_state *connector),
+		     enum plane_type type)
+
+{
+	struct kms_atomic_state *scratch = atomic_state_dup(current);
+	struct kms_atomic_crtc_state *crtc;
+	struct kms_atomic_plane_state *plane;
+	struct kms_atomic_connector_state *connector;
+
+	crtc = find_crtc(scratch, true);
+	igt_require(crtc);
+
+	plane = find_plane(scratch, type, crtc);
+	igt_require(plane);
+
+	connector = find_connector(scratch, crtc);
+	igt_require(connector);
+
+	test(crtc, plane, connector);
+	atomic_state_free(scratch);
+}
+
+igt_main
+{
+	struct kms_atomic_desc desc;
+	struct kms_atomic_state *current;
+
+	memset(&desc, 0, sizeof(desc));
+
+	current = calloc(1, sizeof(*current));
+	igt_assert(current);
+	current->desc = &desc;
+
+	igt_fixture
+		atomic_setup(current);
+
+	igt_subtest("plane_overlay_legacy")
+		run_test(current, plane_overlay, PLANE_TYPE_OVERLAY);
+
+	igt_subtest("plane_primary_legacy")
+		run_test(current, plane_primary, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("plane_cursor_legacy")
+		run_test(current, plane_cursor, PLANE_TYPE_CURSOR);
+
+	igt_subtest("plane_invalid_params")
+		run_test(current, plane_invalid_params, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("crtc_invalid_params")
+		run_test(current, crtc_invalid_params, NUM_PLANE_TYPE_PROPS);
+
+	igt_subtest("atomic_invalid_params")
+		run_test(current, atomic_invalid_params, NUM_PLANE_TYPE_PROPS);
+
+	igt_subtest("fence_in_commit")
+		run_test(current, fence_in_commit, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_in_commit_n")
+		run_test(current, fence_in_commit_n, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_in_overwrite_fence_fd")
+		run_test(current, fence_in_overwrite_fence_fd, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_in_wait_signal")
+		run_test(current, fence_in_wait_signal, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_in_commit_invalid")
+		run_test(current, fence_in_commit_invalid, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_out_commit")
+		run_test(current, fence_out_commit, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_out_commit_n")
+		run_test(current, fence_out_commit_n, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_out_wait")
+		run_test(current, fence_out_wait, PLANE_TYPE_PRIMARY);
+
+	igt_subtest("fence_out_flip")
+		run_test(current, fence_out_flip, PLANE_TYPE_PRIMARY);
+
+	atomic_state_free(current);
+
+	igt_fixture
+		close(desc.fd);
+}
diff --git a/tests/kms_atomic.c.rej b/tests/kms_atomic.c.rej
new file mode 100644
index 0000000..3ab73c8
--- /dev/null
+++ b/tests/kms_atomic.c.rej
@@ -0,0 +1,15 @@
+--- tests/kms_atomic.c
++++ tests/kms_atomic.c
+@@ -1627,10 +1625,10 @@ static void fence_out_commit_n(struct kms_atomic_crtc_state *crtc,
+ 		  memset(&ioc, 0, sizeof(ioc));
+ 		  memset(&out_fences_raw, 0, count_out_fences * sizeof(*out_fences_raw));
+ 
+-		  for (j = 0; j < count_out_fences; j++)
++		  for (i = 0; i < count_out_fences; i++)
+ 		  {
+ 			  out_fences_raw[0].fd = -1;
+-			  out_fences_raw[0].crtc_id = crtc->state->crtcs[j].obj;
++			  out_fences_raw[0].crtc_id = crtc->state->crtcs[i].obj;
+ 		  }
+ 
+ 		  req = drmModeAtomicAlloc();
diff --git a/tests/kms_fence b/tests/kms_fence
new file mode 100755
index 0000000000000000000000000000000000000000..1cd691fb0d7d75bb398db780ede63ab361b01943
GIT binary patch
diff --git a/tests/sw_sync.c b/tests/sw_sync.c
new file mode 100644
index 0000000..47e2dfa
--- /dev/null
+++ b/tests/sw_sync.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 Google, Inc
+ * Copyright © 2016 Collabora, Ltd.
+ *
+ * Based on the implementation from the Android Open Source Project
+ *
+ * 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.
+ *
+ * Authors:
+ *    Robert Foss <robert.foss@collabora.com>
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include "sw_sync.h"
+#include "igt.h"
+#include "igt_aux.h"
+
+IGT_TEST_DESCRIPTION("Test SW Sync Framework");
+
+static void test_alloc_timeline(void)
+{
+	int timeline;
+
+	timeline = sw_sync_timeline_create();
+	close(timeline);
+}
+
+igt_main
+{
+	igt_subtest("alloc_timeline")
+		test_alloc_timeline();
+}
+
diff --git a/tests/sw_sync.c.orig b/tests/sw_sync.c.orig
new file mode 100644
index 0000000..1af9a6b
--- /dev/null
+++ b/tests/sw_sync.c.orig
@@ -0,0 +1,694 @@
+/*
+ * Copyright 2012 Google, Inc
+ * Copyright © 2016 Collabora, Ltd.
+ *
+ * Based on the implementation from the Android Open Source Project
+ *
+ * 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.
+ *
+ * Authors:
+ *    Robert Foss <robert.foss@collabora.com>
+ */
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "sw_sync.h"
+#include "igt.h"
+#include "igt_aux.h"
+
+IGT_TEST_DESCRIPTION("Test SW Sync Framework");
+
+typedef struct {
+	int timeline;
+	uint32_t thread_id;
+	uint32_t nbr_threads;
+	uint32_t nbr_iterations;
+	volatile uint32_t * volatile counter;
+	sem_t *sem;
+} data_t;
+
+static void test_alloc_timeline(void)
+{
+	int timeline;
+
+	timeline = sw_sync_timeline_create();
+	close(timeline);
+}
+
+static void test_alloc_fence(void)
+{
+	int in_fence;
+	int timeline;
+
+	timeline = sw_sync_timeline_create();
+	in_fence = sw_sync_fence_create(timeline, 0);
+
+	close(in_fence);
+	close(timeline);
+}
+
+static void test_alloc_fence_invalid_timeline(void)
+{
+	igt_assert(__sw_sync_fence_create(-1, 0) < 0);
+}
+
+static void test_alloc_merge_fence(void)
+{
+	int in_fence[2];
+	int fence_merge;
+	int timeline[2];
+
+	timeline[0] = sw_sync_timeline_create();
+	timeline[1] = sw_sync_timeline_create();
+
+	in_fence[0] = sw_sync_fence_create(timeline[0], 1);
+	in_fence[1] = sw_sync_fence_create(timeline[1], 1);
+	fence_merge = sw_sync_merge(in_fence[1], in_fence[0]);
+
+	close(in_fence[0]);
+	close(in_fence[1]);
+	close(fence_merge);
+	close(timeline[0]);
+	close(timeline[1]);
+}
+
+static void test_sync_wait(void)
+{
+	int fence, ret;
+	int timeline;
+
+	timeline = sw_sync_timeline_create();
+	fence = sw_sync_fence_create(timeline, 5);
+
+	/* Wait on fence until timeout */
+	ret = sw_sync_wait(fence, 0);
+	igt_assert_f(ret == 0, "Failure waiting on fence until timeout\n");
+
+	/* Advance timeline from 0 -> 1 */
+	sw_sync_timeline_inc(timeline, 1);
+
+	/* Wait on fence until timeout */
+	ret = sw_sync_wait(fence, 0);
+	igt_assert_f(ret == 0, "Failure waiting on fence until timeout\n");
+
+	/* Signal the fence */
+	sw_sync_timeline_inc(timeline, 4);
+
+	/* Wait successfully */
+	ret = sw_sync_wait(fence, 0);
+	igt_assert_f(ret > 0, "Failure waiting on fence\n");
+
+	/* Go even further, and confirm wait still succeeds */
+	sw_sync_timeline_inc(timeline, 10);
+	ret = sw_sync_wait(fence, 0);
+	igt_assert_f(ret > 0, "Failure waiting ahead\n");
+
+	close(fence);
+	close(timeline);
+}
+
+static void test_sync_merge(void)
+{
+	int in_fence[3];
+	int fence_merge;
+	int timeline;
+	int active, signaled;
+
+	timeline = sw_sync_timeline_create();
+	in_fence[0] = sw_sync_fence_create(timeline, 1);
+	in_fence[1] = sw_sync_fence_create(timeline, 2);
+	in_fence[2] = sw_sync_fence_create(timeline, 3);
+
+	fence_merge = sw_sync_merge(in_fence[0], in_fence[1]);
+	fence_merge = sw_sync_merge(in_fence[2], fence_merge);
+
+	/* confirm all fences have one active point (even d) */
+	active = sw_sync_fence_count_status(in_fence[0],
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 1, "in_fence[0] has too many active fences\n");
+	active = sw_sync_fence_count_status(in_fence[1],
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 1, "in_fence[1] has too many active fences\n");
+	active = sw_sync_fence_count_status(in_fence[2],
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 1, "in_fence[2] has too many active fences\n");
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 1, "fence_merge has too many active fences\n");
+
+	/* confirm that fence_merge is not signaled until the max of fence 0,1,2 */
+	sw_sync_timeline_inc(timeline, 1);
+	signaled = sw_sync_fence_count_status(in_fence[0],
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(signaled == 1, "in_fence[0] did not signal\n");
+	igt_assert_f(active == 1, "fence_merge signaled too early\n");
+
+	sw_sync_timeline_inc(timeline, 1);
+	signaled = sw_sync_fence_count_status(in_fence[1],
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(signaled == 1, "in_fence[1] did not signal\n");
+	igt_assert_f(active == 1, "fence_merge signaled too early\n");
+
+	sw_sync_timeline_inc(timeline, 1);
+	signaled = sw_sync_fence_count_status(in_fence[2],
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(signaled == 1, "in_fence[2] did not signal\n");
+	signaled = sw_sync_fence_count_status(fence_merge,
+					       SW_SYNC_FENCE_STATUS_SIGNALED);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 0 && signaled == 1,
+		     "fence_merge did not signal\n");
+
+	close(in_fence[0]);
+	close(in_fence[1]);
+	close(in_fence[2]);
+	close(fence_merge);
+	close(timeline);
+}
+
+static void test_sync_merge_same(void)
+{
+	int in_fence[2];
+	int timeline;
+	int signaled;
+
+	timeline = sw_sync_timeline_create();
+	in_fence[0] = sw_sync_fence_create(timeline, 1);
+	in_fence[1] = sw_sync_merge(in_fence[0], in_fence[0]);
+
+	signaled = sw_sync_fence_count_status(in_fence[0],
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(signaled == 0, "fence signaled too early\n");
+
+	sw_sync_timeline_inc(timeline, 1);
+	signaled = sw_sync_fence_count_status(in_fence[0],
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(signaled == 1, "fence did not signal\n");
+
+	close(in_fence[0]);
+	close(in_fence[1]);
+	close(timeline);
+}
+
+static void test_sync_multi_timeline_wait(void)
+{
+	int timeline[3];
+	int in_fence[3];
+	int fence_merge;
+	int active, signaled, ret;
+
+	timeline[0] = sw_sync_timeline_create();
+	timeline[1] = sw_sync_timeline_create();
+	timeline[2] = sw_sync_timeline_create();
+
+	in_fence[0] = sw_sync_fence_create(timeline[0], 5);
+	in_fence[1] = sw_sync_fence_create(timeline[1], 5);
+	in_fence[2] = sw_sync_fence_create(timeline[2], 5);
+
+	fence_merge = sw_sync_merge(in_fence[0], in_fence[1]);
+	fence_merge = sw_sync_merge(in_fence[2], fence_merge);
+
+	/* Confirm fence isn't signaled */
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	igt_assert_f(active == 3, "Fence signaled too early\n");
+
+	ret = sw_sync_wait(fence_merge, 0);
+	igt_assert_f(ret == 0, "Failure waiting on fence until timeout\n");
+
+	sw_sync_timeline_inc(timeline[0], 5);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	signaled = sw_sync_fence_count_status(fence_merge,
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(active == 2 && signaled == 1,
+		    "Fence did not signal properly\n");
+
+	sw_sync_timeline_inc(timeline[1], 5);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	signaled = sw_sync_fence_count_status(fence_merge,
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(active == 1 && signaled == 2,
+		    "Fence did not signal properly\n");
+
+	sw_sync_timeline_inc(timeline[2], 5);
+	active = sw_sync_fence_count_status(fence_merge,
+					    SW_SYNC_FENCE_STATUS_ACTIVE);
+	signaled = sw_sync_fence_count_status(fence_merge,
+					      SW_SYNC_FENCE_STATUS_SIGNALED);
+	igt_assert_f(active == 0 && signaled == 3,
+		     "Fence did not signal properly\n");
+
+	/* confirm you can successfully wait */
+	ret = sw_sync_wait(fence_merge, 100);
+	igt_assert_f(ret > 0, "Failure waiting on signaled fence\n");
+
+	close(in_fence[0]);
+	close(in_fence[1]);
+	close(in_fence[2]);
+	close(fence_merge);
+	close(timeline[0]);
+	close(timeline[1]);
+	close(timeline[2]);
+}
+
+static void * test_sync_multi_consumer_thread(void *arg)
+{
+	data_t *data = arg;
+	int thread_id = data->thread_id;
+	int nbr_threads = data->nbr_threads;
+	int timeline = data->timeline;
+	int iterations = data->nbr_iterations;
+	int ret, i;
+
+	for (i = 0; i < iterations; i++) {
+		int next_point = i * nbr_threads + thread_id;
+		int fence = sw_sync_fence_create(timeline, next_point);
+
+		ret = sw_sync_wait(fence, 1000);
+		if (ret <= 0)
+		{
+			return (void *) 1;
+		}
+
+		if (*(data->counter) != next_point)
+		{
+			return (void *) 1;
+		}
+
+		sem_post(data->sem);
+		close(fence);
+	}
+	return NULL;
+}
+
+static void test_sync_multi_consumer(void)
+{
+	const uint32_t nbr_threads = 8;
+	const uint32_t nbr_iterations = 1 << 14;
+	data_t data_arr[nbr_threads];
+	pthread_t thread_arr[nbr_threads];
+	sem_t sem;
+	int timeline;
+	volatile uint32_t counter = 0;
+	uintptr_t thread_ret = 0;
+	data_t data;
+	int i, ret;
+
+	sem_init(&sem, 0, 0);
+	timeline = sw_sync_timeline_create();
+
+	data.nbr_iterations = nbr_iterations;
+	data.nbr_threads = nbr_threads;
+	data.counter = &counter;
+	data.timeline = timeline;
+	data.sem = &sem;
+
+	/* Start sync threads. */
+	for (i = 0; i < nbr_threads; i++)
+	{
+		data_arr[i] = data;
+		data_arr[i].thread_id = i;
+		ret = pthread_create(&thread_arr[i], NULL,
+				     test_sync_multi_consumer_thread,
+				     (void *) &(data_arr[i]));
+		igt_assert_eq(ret, 0);
+	}
+
+	/* Produce 'content'. */
+	for (i = 0; i < nbr_threads * nbr_iterations; i++)
+	{
+		sem_wait(&sem);
+
+		counter++;
+		sw_sync_timeline_inc(timeline, 1);
+	}
+
+	/* Wait for threads to complete. */
+	for (i = 0; i < nbr_threads; i++)
+	{
+		uintptr_t local_thread_ret;
+		pthread_join(thread_arr[i], (void **)&local_thread_ret);
+		thread_ret |= local_thread_ret;
+	}
+
+	close(timeline);
+	sem_destroy(&sem);
+
+	igt_assert_f(counter == nbr_threads * nbr_iterations,
+		     "Counter has unexpected value.\n");
+
+	igt_assert_f(thread_ret == 0, "A sync thread reported failure.\n");
+}
+
+static void * test_sync_multi_consumer_producer_thread(void *arg)
+{
+	data_t *data = arg;
+	int thread_id = data->thread_id;
+	int nbr_threads = data->nbr_threads;
+	int timeline = data->timeline;
+	int iterations = data->nbr_iterations;
+	int ret, i;
+
+	for (i = 0; i < iterations; i++) {
+		int next_point = i * nbr_threads + thread_id;
+		int fence = sw_sync_fence_create(timeline, next_point);
+
+		ret = sw_sync_wait(fence, 1000);
+		if (ret <= 0)
+		{
+			return (void *) 1;
+		}
+
+		if (*(data->counter) != next_point)
+		{
+			return (void *) 1;
+		}
+
+		(*data->counter)++;
+
+		/* Kick off the next thread. */
+		sw_sync_timeline_inc(timeline, 1);
+
+		close(fence);
+	}
+	return NULL;
+}
+
+static void test_sync_multi_consumer_producer(void)
+{
+	const uint32_t nbr_threads = 8;
+	const uint32_t nbr_iterations = 1 << 14;
+	data_t data_arr[nbr_threads];
+	pthread_t thread_arr[nbr_threads];
+	int timeline;
+	volatile uint32_t counter = 0;
+	uintptr_t thread_ret = 0;
+	data_t data;
+	int i, ret;
+
+	timeline = sw_sync_timeline_create();
+
+	data.nbr_iterations = nbr_iterations;
+	data.nbr_threads = nbr_threads;
+	data.counter = &counter;
+	data.timeline = timeline;
+
+	/* Start consumer threads. */
+	for (i = 0; i < nbr_threads; i++)
+	{
+		data_arr[i] = data;
+		data_arr[i].thread_id = i;
+		ret = pthread_create(&thread_arr[i], NULL,
+				     test_sync_multi_consumer_producer_thread,
+				     (void *) &(data_arr[i]));
+		igt_assert_eq(ret, 0);
+	}
+
+	/* Wait for threads to complete. */
+	for (i = 0; i < nbr_threads; i++)
+	{
+		uintptr_t local_thread_ret;
+		pthread_join(thread_arr[i], (void **)&local_thread_ret);
+		thread_ret |= local_thread_ret;
+	}
+
+	close(timeline);
+
+	igt_assert_f(counter == nbr_threads * nbr_iterations,
+		     "Counter has unexpected value.\n");
+
+	igt_assert_f(thread_ret == 0, "A sync thread reported failure.\n");
+}
+
+static int test_mspc_wait_on_fence(int fence)
+{
+	int error, active;
+
+	do {
+		error = sw_sync_fence_count_status(fence,
+						   SW_SYNC_FENCE_STATUS_ERROR);
+		igt_assert_f(error == 0, "Error occurred on fence\n");
+		active = sw_sync_fence_count_status(fence,
+						    SW_SYNC_FENCE_STATUS_ACTIVE);
+	} while (active);
+
+	return 0;
+}
+
+static struct {
+	int iterations;
+	int threads;
+	int counter;
+	int cons_timeline;
+	int *prod_timeline;
+	pthread_mutex_t lock;
+} test_mpsc_data;
+
+static int mpsc_producer_thread(void *d)
+{
+	int id = (long)d;
+	int fence, i;
+	int *prod_timeline = test_mpsc_data.prod_timeline;
+	int cons_timeline = test_mpsc_data.cons_timeline;
+	int iterations = test_mpsc_data.iterations;
+
+	for (i = 0; i < iterations; i++) {
+		fence = sw_sync_fence_create(cons_timeline, i);
+
+		/* Wait for the consumer to finish. Use alternate
+		 * means of waiting on the fence
+		 */
+		if ((iterations + id) % 8 != 0) {
+			igt_assert_f(sw_sync_wait(fence, -1) > 0,
+				     "Failure waiting on fence\n");
+		} else {
+			igt_assert_f(test_mspc_wait_on_fence(fence) == 0,
+				     "Failure waiting on fence\n");
+		}
+
+		/* Every producer increments the counter, the consumer
+		 * checks and erases it
+		 */
+		pthread_mutex_lock(&test_mpsc_data.lock);
+		test_mpsc_data.counter++;
+		pthread_mutex_unlock(&test_mpsc_data.lock);
+
+		sw_sync_timeline_inc(prod_timeline[id], 1);
+		close(fence);
+	}
+
+	return 0;
+}
+
+static int mpsc_consumer_thread(void)
+{
+	int fence, merged, tmp, it, i;
+	int *prod_timeline = test_mpsc_data.prod_timeline;
+	int cons_timeline = test_mpsc_data.cons_timeline;
+	int iterations = test_mpsc_data.iterations;
+	int n = test_mpsc_data.threads;
+
+	for (it = 1; it <= iterations; it++) {
+		fence = sw_sync_fence_create(prod_timeline[0], it);
+		for (i = 1; i < n; i++) {
+			tmp = sw_sync_fence_create(prod_timeline[i], it);
+			merged = sw_sync_merge(tmp, fence);
+			close(tmp);
+			close(fence);
+			fence = merged;
+		}
+
+		/* Make sure we see an increment from every producer thread.
+		 * Vary the means by which we wait.
+		 */
+		if (iterations % 8 != 0) {
+			igt_assert_f(sw_sync_wait(fence, -1) == 0,
+				    "Producers did not increment as expected\n");
+		} else {
+			igt_assert_f(test_mspc_wait_on_fence(fence) == 0,
+				     "Failure waiting on fence\n");
+		}
+
+		igt_assert_f(test_mpsc_data.counter == n * it,
+			     "Counter value mismatch\n");
+
+		/* Release the producer threads */
+		sw_sync_timeline_inc(cons_timeline, 1);
+		close(fence);
+	}
+
+	return 0;
+}
+
+/* IMPORTANT NOTE: if you see this test failing on your system, it may be
+ * due to a shortage of file descriptors. Please ensure your system has
+ * a sensible limit for this test to finish correctly.
+ */
+static void test_sync_multi_producer_single_consumer(void)
+{
+	int iterations = 1 << 12;
+	int n = 5;
+	int prod_timeline[n];
+	int cons_timeline;
+	pthread_t threads[n];
+	long i;
+
+	cons_timeline = sw_sync_timeline_create();
+	for (i = 0; i < n; i++)
+		prod_timeline[i] = sw_sync_timeline_create();
+
+	test_mpsc_data.prod_timeline = prod_timeline;
+	test_mpsc_data.cons_timeline = cons_timeline;
+	test_mpsc_data.iterations = iterations;
+	test_mpsc_data.threads = n;
+	test_mpsc_data.counter = 0;
+	pthread_mutex_init(&test_mpsc_data.lock, NULL);
+
+	for (i = 0; i < n; i++) {
+		pthread_create(&threads[i], NULL, (void * (*)(void *))
+			       mpsc_producer_thread,
+			       (void *)i);
+	}
+
+	mpsc_consumer_thread();
+
+	for (i = 0; i < n; i++)
+		pthread_join(threads[i], NULL);
+}
+
+static void test_sync_random_merge(void)
+{
+	int i, size, ret;
+	const int nbr_timeline = 32;
+	const int nbr_merge = 1024;
+	int fence_map[nbr_timeline];
+	int timeline_arr[nbr_timeline];
+	int fence, tmpfence, merged;
+	int timeline, timeline_offset, sync_pt;
+
+	srand(time(NULL));
+
+	for (i = 0; i < nbr_timeline; i++) {
+		timeline_arr[i] = sw_sync_timeline_create();
+		fence_map[i] = -1;
+	}
+
+	sync_pt = rand();
+	fence = sw_sync_fence_create(timeline_arr[0], sync_pt);
+
+	fence_map[0] = sync_pt;
+
+	/* Randomly create syncpoints out of a fixed set of timelines,
+	 * and merge them together.
+	 */
+	for (i = 0; i < nbr_merge; i++) {
+		/* Generate syncpoint. */
+		timeline_offset = rand() % nbr_timeline;
+		timeline = timeline_arr[timeline_offset];
+		sync_pt = rand();
+
+		/* Keep track of the latest sync_pt in each timeline. */
+		if (fence_map[timeline_offset] == -1)
+			fence_map[timeline_offset] = sync_pt;
+		else if (fence_map[timeline_offset] < sync_pt)
+			fence_map[timeline_offset] = sync_pt;
+
+		/* Merge. */
+		tmpfence = sw_sync_fence_create(timeline, sync_pt);
+		merged = sw_sync_merge(tmpfence, fence);
+		close(tmpfence);
+		close(fence);
+		fence = merged;
+	}
+
+	size = 0;
+	for (i = 0; i < nbr_timeline; i++)
+		if (fence_map[i] != -1)
+			size++;
+
+	/* Trigger the merged fence. */
+	for (i = 0; i < nbr_timeline; i++) {
+		if (fence_map[i] != -1) {
+			ret = sw_sync_wait(fence, 0);
+			igt_assert_f(ret == 0,
+				    "Failure waiting on fence until timeout\n");
+			/* Increment the timeline to the last sync_pt */
+			sw_sync_timeline_inc(timeline_arr[i], fence_map[i]);
+		}
+	}
+
+	/* Check that the fence is triggered. */
+	ret = sw_sync_wait(fence, 0);
+	igt_assert_f(ret > 0, "Failure triggering fence\n");
+
+	close(fence);
+	for (i = 0; i < nbr_timeline; i++)
+		close(timeline_arr[i]);
+}
+
+igt_main
+{
+	igt_subtest("alloc_timeline")
+		test_alloc_timeline();
+
+	igt_subtest("alloc_fence")
+		test_alloc_fence();
+
+	igt_subtest("alloc_fence_invalid_timeline")
+		test_alloc_fence_invalid_timeline();
+
+	igt_subtest("alloc_merge_fence")
+		test_alloc_merge_fence();
+
+	igt_subtest("sync_wait")
+		test_sync_wait();
+
+	igt_subtest("sync_merge")
+		test_sync_merge();
+
+	igt_subtest("sync_merge_same")
+		test_sync_merge_same();
+
+	igt_subtest("sync_multi_timeline_wait")
+		test_sync_multi_timeline_wait();
+
+	igt_subtest("sync_multi_consumer")
+		test_sync_multi_consumer();
+
+	igt_subtest("sync_multi_consumer_producer")
+		test_sync_multi_consumer_producer();
+
+	igt_subtest("sync_multi_producer_single_consumer")
+		test_sync_multi_producer_single_consumer();
+
+	igt_subtest("sync_random_merge")
+		test_sync_random_merge();
+}
+
