diff --git a/tests/.gitignore b/tests/.gitignore
index a3f3143..dcead2c 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -134,6 +134,7 @@ kms_flip
 kms_flip_event_leak
 kms_flip_tiling
 kms_force_connector
+kms_frontbuffer_tracking
 kms_legacy_colorkey
 kms_mmio_vs_cs_flip
 kms_pipe_b_c_ivb
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 994c31b..3c93337 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -66,6 +66,7 @@ TESTS_progs_M = \
 	kms_flip \
 	kms_flip_event_leak \
 	kms_flip_tiling \
+	kms_frontbuffer_tracking \
 	kms_legacy_colorkey \
 	kms_mmio_vs_cs_flip \
 	kms_pipe_b_c_ivb \
diff --git a/tests/kms_frontbuffer_tracking.c b/tests/kms_frontbuffer_tracking.c
new file mode 100644
index 0000000..f6554f9
--- /dev/null
+++ b/tests/kms_frontbuffer_tracking.c
@@ -0,0 +1,1825 @@
+/*
+ * Copyright © 2015 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.
+ *
+ * Authors: Paulo Zanoni <paulo.r.zanoni@intel.com>
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "drmtest.h"
+#include "igt_aux.h"
+#include "igt_draw.h"
+#include "igt_kms.h"
+#include "igt_debugfs.h"
+#include "intel_chipset.h"
+#include "ioctl_wrappers.h"
+
+#define FBC_PARAM_PATH "/sys/module/i915/parameters/enable_fbc"
+#define PSR_PARAM_PATH "/sys/module/i915/parameters/enable_psr"
+
+struct test_mode {
+	enum {
+		PIPE_SINGLE = 0,
+		PIPE_DUAL,
+		PIPE_COUNT,
+	} pipes;
+
+	enum {
+		SCREEN_PRIM = 0,
+		SCREEN_SCND,
+		SCREEN_OFFSCREEN,
+		SCREEN_COUNT,
+	} screen;
+
+	enum {
+		PLANE_PRI = 0,
+		PLANE_CUR,
+		PLANE_SPR,
+		PLANE_COUNT,
+	} plane;
+
+	enum {
+		FEATURE_NONE = 0,
+		FEATURE_FBC,
+		FEATURE_PSR,
+		FEATURE_COUNT,
+	} feature;
+
+	enum igt_draw_method method;
+};
+
+enum feature_status {
+	ENABLED,
+	DISABLED,
+};
+
+struct rect {
+	int x;
+	int y;
+	int w;
+	int h;
+	uint32_t color;
+};
+
+#define MAX_CONNECTORS 32
+struct {
+	int fd;
+	drmModeResPtr res;
+	drmModeConnectorPtr connectors[MAX_CONNECTORS];
+	drmModePlaneResPtr planes;
+	drm_intel_bufmgr *bufmgr;
+} drm;
+
+struct {
+	int fd;
+
+	char param_original_value[16];
+
+	bool supports_compressing;
+	bool supports_last_action;
+
+	struct timespec last_action;
+} fbc = {
+	.fd = -1,
+	.supports_last_action = false,
+	.supports_compressing = false,
+};
+
+struct {
+	int fd;
+	bool can_test;
+
+	char param_original_value[16];
+} psr = {
+	.fd = -1,
+	.can_test = false,
+};
+
+
+#define SINK_CRC_SIZE 12
+typedef struct {
+	char data[SINK_CRC_SIZE];
+} sink_crc_t;
+
+struct both_crcs {
+	igt_crc_t pipe;
+	sink_crc_t sink;
+};
+
+igt_pipe_crc_t *pipe_crc;
+struct both_crcs blue_crc;
+struct both_crcs *wanted_crc;
+
+struct {
+	int fd;
+} sink_crc;
+
+struct draw_pattern_info {
+	bool initialized;
+	bool frames_stack;
+	int n_rects;
+	struct both_crcs *crcs;
+	struct rect (*get_rect)(struct igt_fb *fb, int r);
+};
+
+/* Draw big rectangles on the screen. */
+struct draw_pattern_info pattern1;
+/* 64x64 rectangles at x:0,y:0, just so we can draw on the cursor and sprite. */
+struct draw_pattern_info pattern2;
+/* 64x64 rectangles at different positions, same color, for the move test. */
+struct draw_pattern_info pattern3;
+/* Just a fullscreen green square. */
+struct draw_pattern_info pattern4;
+
+/* Command line parameters. */
+struct {
+	bool check_status;
+	bool check_crc;
+	bool fbc_check_compression;
+	bool fbc_check_last_action;
+	bool no_edp;
+	bool small_modes;
+	int step;
+	int only_feature;
+	int only_pipes;
+} opt = {
+	.check_status = true,
+	.check_crc = true,
+	.fbc_check_compression = true,
+	.fbc_check_last_action = true,
+	.no_edp = false,
+	.small_modes = false,
+	.step = 0,
+	.only_feature = FEATURE_COUNT,
+	.only_pipes = PIPE_COUNT,
+};
+
+struct modeset_params {
+	uint32_t crtc_id;
+	uint32_t connector_id;
+	uint32_t sprite_id;
+	drmModeModeInfoPtr mode;
+	struct igt_fb fb;
+	struct igt_fb cursor;
+	struct igt_fb sprite;
+};
+
+struct modeset_params prim_mode_params;
+struct modeset_params scnd_mode_params;
+struct igt_fb offscreen_fb;
+
+static drmModeModeInfoPtr get_connector_smallest_mode(drmModeConnectorPtr c)
+{
+	int i;
+	drmModeModeInfoPtr smallest = NULL;
+
+	for (i = 0; i < c->count_modes; i++) {
+		drmModeModeInfoPtr mode = &c->modes[i];
+
+		if (!smallest)
+			smallest = mode;
+
+		if (mode->hdisplay * mode->vdisplay <
+		    smallest->hdisplay * smallest->vdisplay)
+			smallest = mode;
+	}
+
+	return smallest;
+}
+
+static drmModeConnectorPtr get_connector(uint32_t id)
+{
+	int i;
+
+	for (i = 0; i < drm.res->count_connectors; i++)
+		if (drm.res->connectors[i] == id)
+			return drm.connectors[i];
+
+	igt_assert(false);
+}
+
+static void print_mode_info(const char *screen, struct modeset_params *params)
+{
+	drmModeConnectorPtr c = get_connector(params->connector_id);
+
+	igt_info("%s screen: %s %s\n",
+		 screen,
+		 kmstest_connector_type_str(c->connector_type),
+		 params->mode->name);
+}
+
+static void init_mode_params(struct modeset_params *params, uint32_t crtc_id,
+			     int crtc_index, uint32_t connector_id,
+			     drmModeModeInfoPtr mode)
+{
+	uint32_t plane_id = 0;
+	int i;
+
+	igt_create_fb(drm.fd, mode->hdisplay, mode->vdisplay,
+		      DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED,
+		      &params->fb);
+	igt_create_fb(drm.fd, 64, 64, DRM_FORMAT_ARGB8888,
+		      LOCAL_DRM_FORMAT_MOD_NONE, &params->cursor);
+	igt_create_fb(drm.fd, 64, 64, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &params->sprite);
+
+	for (i = 0; i < drm.planes->count_planes && plane_id == 0; i++) {
+		drmModePlanePtr plane;
+
+		plane = drmModeGetPlane(drm.fd, drm.planes->planes[i]);
+		igt_assert(plane);
+
+		if (plane->possible_crtcs & (1 << crtc_index))
+			plane_id = plane->plane_id;
+
+		drmModeFreePlane(plane);
+	}
+	igt_assert(plane_id);
+
+	params->crtc_id = crtc_id;
+	params->connector_id = connector_id;
+	params->mode = mode;
+	params->sprite_id = plane_id;
+}
+
+drmModeModeInfo std_1024_mode = {
+	.clock = 65000,
+	.hdisplay = 1024,
+	.hsync_start = 1048,
+	.hsync_end = 1184,
+	.htotal = 1344,
+	.vtotal = 806,
+	.hskew = 0,
+	.vdisplay = 768,
+	.vsync_start = 771,
+	.vsync_end = 777,
+	.vtotal = 806,
+	.vscan = 0,
+	.vrefresh = 60,
+	.flags = 0xA,
+	.type = 0x40,
+	.name = "Custom 1024x768",
+};
+
+static bool connector_get_mode(drmModeConnectorPtr c, drmModeModeInfoPtr *mode)
+{
+	*mode = NULL;
+
+	if (c->connection != DRM_MODE_CONNECTED || !c->count_modes)
+		return false;
+
+	if (c->connector_type == DRM_MODE_CONNECTOR_eDP && opt.no_edp)
+		return false;
+
+	if (opt.small_modes)
+		*mode = get_connector_smallest_mode(c);
+	else
+		*mode = &c->modes[0];
+
+	/* Because on some machines we don't have enough stolen memory to fit in
+	 * those 3k panels. And on HSW the CRC WA is so awful that it makes you
+	 * think everything is bugged. */
+	if (c->connector_type == DRM_MODE_CONNECTOR_eDP)
+		*mode = &std_1024_mode;
+
+	return true;
+}
+
+static bool init_modeset_cached_params(void)
+{
+	int i;
+	uint32_t prim_connector_id = 0, scnd_connector_id = 0;
+	drmModeModeInfoPtr prim_mode = NULL, scnd_mode = NULL;
+	drmModeModeInfoPtr tmp_mode;
+
+	/* First, try to find an eDP monitor since it's the only possible type
+	 * for PSR.  */
+	for (i = 0; i < drm.res->count_connectors; i++) {
+		if (drm.connectors[i]->connector_type != DRM_MODE_CONNECTOR_eDP)
+			continue;
+
+		if (connector_get_mode(drm.connectors[i], &tmp_mode)) {
+			prim_connector_id = drm.res->connectors[i];
+			prim_mode = tmp_mode;
+		}
+	}
+	for (i = 0; i < drm.res->count_connectors; i++) {
+		/* Don't pick again what we just selected on the above loop. */
+		if (drm.res->connectors[i] == prim_connector_id)
+			continue;
+
+		if (connector_get_mode(drm.connectors[i], &tmp_mode)) {
+			if (!prim_connector_id) {
+				prim_connector_id = drm.res->connectors[i];
+				prim_mode = tmp_mode;
+			} else if (!scnd_connector_id) {
+				scnd_connector_id = drm.res->connectors[i];
+				scnd_mode = tmp_mode;
+				break;
+			}
+		}
+	}
+
+	if (!prim_connector_id)
+		return false;
+
+	init_mode_params(&prim_mode_params, drm.res->crtcs[0], 0,
+			 prim_connector_id, prim_mode);
+	print_mode_info("Primary", &prim_mode_params);
+
+	if (!scnd_connector_id) {
+		scnd_mode_params.connector_id = 0;
+		return true;
+	}
+
+	igt_assert(drm.res->count_crtcs >= 2);
+	init_mode_params(&scnd_mode_params, drm.res->crtcs[1], 1,
+			 scnd_connector_id, scnd_mode);
+	print_mode_info("Secondary", &scnd_mode_params);
+
+	return true;
+}
+
+static bool set_mode_for_params(struct modeset_params *params)
+{
+	int rc;
+
+	rc = drmModeSetCrtc(drm.fd, params->crtc_id, params->fb.fb_id, 0, 0,
+			    &params->connector_id, 1, params->mode);
+	return (rc == 0);
+}
+
+#define DEBUGFS_MSG_SIZE 256
+
+static void get_debugfs_string(int fd, char *buf)
+{
+	ssize_t n_read;
+
+	lseek(fd, 0, SEEK_SET);
+
+	n_read = read(fd, buf, DEBUGFS_MSG_SIZE -1);
+	igt_assert(n_read >= 0);
+	buf[n_read] = '\0';
+}
+
+static enum feature_status fbc_get_status(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(fbc.fd, buf);
+
+	if (strstr(buf, "FBC enabled\n"))
+		return ENABLED;
+	else
+		return DISABLED;
+}
+
+static enum feature_status psr_get_status(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(psr.fd, buf);
+
+	if (strstr(buf, "\nActive: yes\n"))
+		return ENABLED;
+	else
+		return DISABLED;
+}
+
+static struct timespec fbc_get_last_action(void)
+{
+	struct timespec ret = { 0, 0 };
+	char buf[DEBUGFS_MSG_SIZE];
+	char *action;
+	ssize_t n_read;
+
+	get_debugfs_string(fbc.fd, buf);
+
+	action = strstr(buf, "\nLast action:");
+	igt_assert(action);
+
+	n_read = sscanf(action, "Last action: %ld.%ld",
+			&ret.tv_sec, &ret.tv_nsec);
+	igt_assert(n_read == 2);
+
+	return ret;
+}
+
+static bool fbc_last_action_changed(void)
+{
+	struct timespec t_new, t_old;
+
+	t_old = fbc.last_action;
+	t_new = fbc_get_last_action();
+
+	fbc.last_action = t_new;
+
+#if 0
+	igt_info("old: %ld.%ld\n", t_old.tv_sec, t_old.tv_nsec);
+	igt_info("new: %ld.%ld\n", t_new.tv_sec, t_new.tv_nsec);
+#endif
+
+	return t_old.tv_sec != t_new.tv_sec ||
+	       t_old.tv_nsec != t_new.tv_nsec;
+}
+
+static void fbc_update_last_action(void)
+{
+	if (!fbc.supports_last_action)
+		return;
+
+	fbc.last_action = fbc_get_last_action();
+
+#if 0
+	igt_info("Last action: %ld.%ld\n",
+		 fbc.last_action.tv_sec, fbc.last_action.tv_nsec);
+#endif
+}
+
+static void fbc_setup_last_action(void)
+{
+	ssize_t n_read;
+	char buf[DEBUGFS_MSG_SIZE];
+	char *action;
+
+	get_debugfs_string(fbc.fd, buf);
+
+	action = strstr(buf, "\nLast action:");
+	if (!action) {
+		igt_info("FBC last action not supported\n");
+		return;
+	}
+
+	fbc.supports_last_action = true;
+
+	n_read = sscanf(action, "Last action: %ld.%ld",
+			&fbc.last_action.tv_sec, &fbc.last_action.tv_nsec);
+	igt_assert(n_read == 2);
+}
+
+static bool fbc_is_compressing(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(fbc.fd, buf);
+	return strstr(buf, "\nCompressing: yes\n") != NULL;
+}
+
+static bool fbc_wait_for_compression(void)
+{
+	return igt_wait(fbc_is_compressing(), 5000, 1);
+}
+
+static void fbc_setup_compressing(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(fbc.fd, buf);
+
+	if (strstr(buf, "\nCompressing:"))
+		fbc.supports_compressing = true;
+	else
+		igt_info("FBC compression information not supported\n");
+}
+
+static bool fbc_wait_for_status(enum feature_status status)
+{
+	return igt_wait(fbc_get_status() == status, 5000, 1);
+}
+
+static bool psr_wait_for_status(enum feature_status status)
+{
+	return igt_wait(psr_get_status() == status, 5000, 1);
+}
+
+static void set_param(const char *path, bool enable)
+{
+	int fd;
+	const char *str;
+
+	fd = open(path, O_RDWR);
+	igt_assert(fd >= 0);
+
+	str = enable ? "1\n" : "0\n";
+	igt_assert(write(fd, str, 2) == 2);
+
+	igt_assert(close(fd) == 0);
+}
+#define fbc_enable() set_param(FBC_PARAM_PATH, true)
+#define fbc_disable() set_param(FBC_PARAM_PATH, false)
+#define psr_enable() set_param(PSR_PARAM_PATH, true)
+#define psr_disable() set_param(PSR_PARAM_PATH, false)
+
+static void save_param(const char *file_path, char *param_value)
+{
+	int fd;
+	ssize_t n;
+
+	fd = open(file_path, O_RDWR);
+	igt_assert(fd >= 0);
+
+	n = read(fd, param_value, 15);
+	igt_assert(n > 0);
+	param_value[n] = '\0';
+	igt_assert(close(fd) == 0);
+}
+
+static void get_sink_crc(sink_crc_t *crc)
+{
+	lseek(sink_crc.fd, 0, SEEK_SET);
+
+	igt_assert(read(sink_crc.fd, crc->data, SINK_CRC_SIZE) ==
+		   SINK_CRC_SIZE);
+}
+
+static bool sink_crc_equal(sink_crc_t *a, sink_crc_t *b)
+{
+	return (memcmp(a->data, b->data, SINK_CRC_SIZE) == 0);
+}
+
+#define assert_sink_crc_equal(a, b) igt_assert(sink_crc_equal(a, b))
+
+static struct rect pat1_get_rect(struct igt_fb *fb, int r)
+{
+	struct rect rect;
+
+	switch (r) {
+	case 0:
+		rect.x = 0;
+		rect.y = 0;
+		rect.w = fb->width  / 8;
+		rect.h = fb->height / 8;
+		rect.color = 0x00FF00;
+		break;
+	case 1:
+		rect.x = fb->width  / 8;
+		rect.y = fb->height / 8;
+		rect.w = fb->width  / 8;
+		rect.h = fb->height / 8;
+		rect.color = 0xFF0000;
+		break;
+	case 2:
+		rect.x = fb->width  / 8 * 4;
+		rect.y = fb->height / 8 * 4;
+		rect.w = fb->width  / 8 * 2;
+		rect.h = fb->height / 8 * 2;
+		rect.color = 0xFF00FF;
+		break;
+	case 3:
+		rect.x = fb->width  / 16;
+		rect.y = fb->height / 16;
+		rect.w = fb->width  / 8;
+		rect.h = fb->height / 8;
+		rect.color = 0x00FFFF;
+		break;
+	case 4:
+		rect.x = fb->width - 64;
+		rect.y = fb->height - 64;
+		rect.w = 64;
+		rect.h = 64;
+		rect.color = 0xFFFFFF;
+		break;
+	default:
+		igt_assert(0);
+	}
+
+	return rect;
+}
+
+static struct rect pat2_get_rect(struct igt_fb *fb, int r)
+{
+	struct rect rect;
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.w = 64;
+	rect.h = 64;
+
+	switch (r) {
+	case 0:
+		rect.color = 0xFF00FF00;
+		break;
+	case 1:
+		rect.w = 32;
+		rect.h = 32;
+		rect.color = 0xFFFF0000;
+		break;
+	case 2:
+		rect.x = 32;
+		rect.y = 32;
+		rect.w = 32;
+		rect.h = 32;
+		rect.color = 0xFFFF00FF;
+		break;
+	case 3:
+		rect.x = 16;
+		rect.y = 16;
+		rect.w = 32;
+		rect.h = 32;
+		rect.color = 0xFF00FFFF;
+		break;
+	case 4:
+		rect.color = 0xFFFFFF00;
+		break;
+	default:
+		igt_assert(0);
+	}
+
+	return rect;
+}
+
+static struct rect pat3_get_rect(struct igt_fb *fb, int r)
+{
+	struct rect rect;
+
+	rect.w = 64;
+	rect.h = 64;
+	rect.color = 0xFF00FF00;
+
+	switch (r) {
+	case 0:
+		rect.x = 0;
+		rect.y = 0;
+		break;
+	case 1:
+		rect.x = 64;
+		rect.y = 64;
+		break;
+	case 2:
+		rect.x = 1;
+		rect.y = 1;
+		break;
+	case 3:
+		rect.x = fb->width - 64;
+		rect.y = fb->height - 64;
+		break;
+	case 4:
+		rect.x = fb->width / 2 - 32;
+		rect.y = fb->height / 2 - 32;
+		break;
+	default:
+		igt_assert(0);
+	}
+
+	return rect;
+}
+
+static struct rect pat4_get_rect(struct igt_fb *fb, int r)
+{
+	struct rect rect;
+
+	igt_assert(r == 0);
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.w = fb->width;
+	rect.h = fb->height;
+	rect.color = 0xFF00FF00;
+
+	return rect;
+}
+
+static void draw_rect(struct draw_pattern_info *pattern, struct igt_fb *fb,
+		      enum igt_draw_method method, int r)
+{
+	struct rect rect = pattern->get_rect(fb, r);
+
+	igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, fb, method, rect.x, rect.y,
+			 rect.w, rect.h, rect.color);
+}
+
+static void unset_all_crtcs(void)
+{
+	int i, rc;
+
+	for (i = 0; i < drm.res->count_crtcs; i++) {
+		rc = drmModeSetCrtc(drm.fd, drm.res->crtcs[i], -1, 0, 0, NULL,
+				    0, NULL);
+		igt_assert(rc == 0);
+
+		rc = drmModeSetCursor(drm.fd, drm.res->crtcs[i], 0, 0, 0);
+		igt_assert(rc == 0);
+	}
+
+	for (i = 0; i < drm.planes->count_planes; i++) {
+		rc = drmModeSetPlane(drm.fd, drm.planes->planes[i], 0, 0, 0, 0,
+				     0, 0, 0, 0, 0, 0, 0);
+		igt_assert(rc == 0);
+	}
+}
+
+static void disable_features(void)
+{
+	fbc_disable();
+	psr_disable();
+}
+
+static void print_crc(const char *str, struct both_crcs *crc)
+{
+	int i;
+	char *pipe_str;
+
+	pipe_str = igt_crc_to_string(&crc->pipe);
+
+	igt_debug("%s pipe:[%s] sink:[", str, pipe_str);
+	for (i = 0; i < SINK_CRC_SIZE; i++)
+		igt_debug("%c", crc->sink.data[i]);
+	igt_debug("]\n");
+
+	free(pipe_str);
+}
+
+static void collect_crcs(struct both_crcs *crcs)
+{
+	drmModeConnectorPtr c;
+
+	igt_pipe_crc_collect_crc(pipe_crc, &crcs->pipe);
+
+	c = get_connector(prim_mode_params.connector_id);
+	if (c->connector_type == DRM_MODE_CONNECTOR_eDP)
+		get_sink_crc(&crcs->sink);
+	else
+		memcpy(&crcs->sink, "unsupported!", SINK_CRC_SIZE);
+}
+
+static void init_blue_crc(void)
+{
+	struct igt_fb blue;
+	int rc;
+
+	disable_features();
+	unset_all_crtcs();
+
+	igt_create_fb(drm.fd, prim_mode_params.mode->hdisplay,
+		      prim_mode_params.mode->vdisplay, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &blue);
+
+	igt_draw_fill_fb(drm.fd, &blue, 0xFF);
+
+	rc = drmModeSetCrtc(drm.fd, prim_mode_params.crtc_id,
+			    blue.fb_id, 0, 0, &prim_mode_params.connector_id, 1,
+			    prim_mode_params.mode);
+	igt_assert(rc == 0);
+	collect_crcs(&blue_crc);
+
+	print_crc("Blue CRC:  ", &blue_crc);
+
+	igt_remove_fb(drm.fd, &blue);
+}
+
+static void init_crcs(struct draw_pattern_info *pattern)
+{
+	int r, r_, rc;
+	struct igt_fb fbs[pattern->n_rects];
+
+	if (pattern->initialized)
+		return;
+
+	pattern->crcs = calloc(pattern->n_rects, sizeof(*(pattern->crcs)));
+
+	for (r = 0; r < pattern->n_rects; r++)
+		igt_create_fb(drm.fd, prim_mode_params.mode->hdisplay,
+			      prim_mode_params.mode->vdisplay,
+			      DRM_FORMAT_XRGB8888,
+			      LOCAL_I915_FORMAT_MOD_X_TILED, &fbs[r]);
+
+	for (r = 0; r < pattern->n_rects; r++)
+		igt_draw_fill_fb(drm.fd, &fbs[r], 0xFF);
+
+	if (pattern->frames_stack) {
+		for (r = 0; r < pattern->n_rects; r++)
+			for (r_ = 0; r_ <= r; r_++)
+				draw_rect(pattern, &fbs[r], IGT_DRAW_PWRITE,
+					  r_);
+	} else {
+		for (r = 0; r < pattern->n_rects; r++)
+			draw_rect(pattern, &fbs[r], IGT_DRAW_PWRITE, r);
+	}
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		rc = drmModeSetCrtc(drm.fd, prim_mode_params.crtc_id,
+				   fbs[r].fb_id, 0, 0,
+				   &prim_mode_params.connector_id, 1,
+				   prim_mode_params.mode);
+		igt_assert(rc == 0);
+		collect_crcs(&pattern->crcs[r]);
+	}
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		igt_debug("Rect %d CRC:", r);
+		print_crc("", &pattern->crcs[r]);
+	}
+
+	unset_all_crtcs();
+
+	for (r = 0; r < pattern->n_rects; r++)
+		igt_remove_fb(drm.fd, &fbs[r]);
+
+	pattern->initialized = true;
+}
+
+static void setup_drm(void)
+{
+	int i;
+
+	drm.fd = drm_open_any_master();
+	igt_require(drm.fd >= 0);
+
+	drm.res = drmModeGetResources(drm.fd);
+	igt_assert(drm.res->count_connectors <= MAX_CONNECTORS);
+
+	for (i = 0; i < drm.res->count_connectors; i++)
+		drm.connectors[i] = drmModeGetConnector(drm.fd,
+						drm.res->connectors[i]);
+
+	drm.planes = drmModeGetPlaneResources(drm.fd);
+
+	drm.bufmgr = drm_intel_bufmgr_gem_init(drm.fd, 4096);
+	igt_assert(drm.bufmgr);
+	drm_intel_bufmgr_gem_enable_reuse(drm.bufmgr);
+}
+
+static void teardown_drm(void)
+{
+	int i;
+
+	drm_intel_bufmgr_destroy(drm.bufmgr);
+
+	drmModeFreePlaneResources(drm.planes);
+
+	for (i = 0; i < drm.res->count_connectors; i++)
+		drmModeFreeConnector(drm.connectors[i]);
+
+	drmModeFreeResources(drm.res);
+	close(drm.fd);
+}
+
+static void setup_modeset(void)
+{
+	igt_require(init_modeset_cached_params());
+
+	kmstest_set_vt_graphics_mode();
+
+	igt_create_fb(drm.fd, 1024, 1024, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &offscreen_fb);
+}
+
+static void teardown_modeset(void)
+{
+	if (scnd_mode_params.connector_id) {
+		igt_remove_fb(drm.fd, &scnd_mode_params.fb);
+		igt_remove_fb(drm.fd, &scnd_mode_params.cursor);
+		igt_remove_fb(drm.fd, &scnd_mode_params.sprite);
+	}
+	igt_remove_fb(drm.fd, &prim_mode_params.fb);
+	igt_remove_fb(drm.fd, &prim_mode_params.cursor);
+	igt_remove_fb(drm.fd, &prim_mode_params.sprite);
+	igt_remove_fb(drm.fd, &offscreen_fb);
+}
+
+static void setup_crcs(void)
+{
+	pipe_crc = igt_pipe_crc_new(0, INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	sink_crc.fd = open("/sys/kernel/debug/dri/0/i915_sink_crc_eDP1",
+			   O_RDONLY);
+	igt_assert(sink_crc.fd >= 0);
+
+	init_blue_crc();
+
+	pattern1.initialized = false;
+	pattern1.frames_stack = true;
+	pattern1.n_rects = 5;
+	pattern1.crcs = NULL;
+	pattern1.get_rect = pat1_get_rect;
+
+	pattern2.initialized = false;
+	pattern2.frames_stack = true;
+	pattern2.n_rects = 5;
+	pattern2.crcs = NULL;
+	pattern2.get_rect = pat2_get_rect;
+
+	pattern3.initialized = false;
+	pattern3.frames_stack = false;
+	pattern3.n_rects = 5;
+	pattern3.crcs = NULL;
+	pattern3.get_rect = pat3_get_rect;
+
+	pattern4.initialized = false;
+	pattern4.frames_stack = false;
+	pattern4.n_rects = 1;
+	pattern4.crcs = NULL;
+	pattern4.get_rect = pat4_get_rect;
+}
+
+static void teardown_crcs(void)
+{
+	if (pattern1.crcs)
+		free(pattern1.crcs);
+	if (pattern2.crcs)
+		free(pattern2.crcs);
+	if (pattern3.crcs)
+		free(pattern3.crcs);
+	if (pattern4.crcs)
+		free(pattern4.crcs);
+
+	close(sink_crc.fd);
+
+	igt_pipe_crc_free(pipe_crc);
+}
+
+static void restore_param(const char *file_path, char *param_value)
+{
+	int fd;
+
+	fd = open(file_path, O_RDWR);
+	if (fd >= 0) {
+		write(fd, param_value, strlen(param_value));
+		close(fd);
+	}
+}
+
+static void exit_handler(int sig)
+{
+	restore_param(FBC_PARAM_PATH, fbc.param_original_value);
+	restore_param(PSR_PARAM_PATH, psr.param_original_value);
+}
+
+static void setup_fbc(void)
+{
+	fbc.fd = open("/sys/kernel/debug/dri/0/i915_fbc_status", O_RDONLY);
+	igt_assert(fbc.fd >= 0);
+
+	save_param(FBC_PARAM_PATH, fbc.param_original_value);
+
+	fbc_setup_last_action();
+	fbc_setup_compressing();
+}
+
+static void teardown_fbc(void)
+{
+	if (fbc.fd != -1)
+		close(fbc.fd);
+}
+
+static bool psr_sink_has_support(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(psr.fd, buf);
+
+	return strstr(buf, "Sink_Support: yes\n");
+}
+
+static void setup_psr(void)
+{
+	if (get_connector(prim_mode_params.connector_id)->connector_type !=
+	    DRM_MODE_CONNECTOR_eDP) {
+		igt_info("Can't test PSR: no usable eDP screen.\n");
+		return;
+	}
+
+	psr.fd = open("/sys/kernel/debug/dri/0/i915_edp_psr_status", O_RDONLY);
+	igt_assert(psr.fd >= 0);
+
+	if (!psr_sink_has_support()) {
+		igt_info("Can't test PSR: not supported by sink.\n");
+		return;
+	}
+	psr.can_test = true;
+
+	save_param(PSR_PARAM_PATH, psr.param_original_value);
+}
+
+static void teardown_psr(void)
+{
+	if (psr.fd != -1)
+		close(psr.fd);
+}
+
+static void setup_environment(void)
+{
+	setup_drm();
+	setup_modeset();
+
+	igt_install_exit_handler(exit_handler);
+	setup_fbc();
+	setup_psr();
+
+	setup_crcs();
+}
+
+static void teardown_environment(void)
+{
+	teardown_crcs();
+	teardown_psr();
+	teardown_fbc();
+	teardown_modeset();
+	teardown_drm();
+}
+
+static void wait_user(void)
+{
+	igt_info("Press enter...\n");
+	while (getchar() != '\n')
+		;
+}
+
+static struct modeset_params *pick_params(const struct test_mode *t)
+{
+	switch (t->screen) {
+	case SCREEN_PRIM:
+		return &prim_mode_params;
+	case SCREEN_SCND:
+		return &scnd_mode_params;
+	case SCREEN_OFFSCREEN:
+		return NULL;
+	default:
+		igt_assert(false);
+	}
+}
+
+static struct igt_fb *pick_target(const struct test_mode *t,
+				  struct modeset_params *params)
+{
+	if (!params)
+		return &offscreen_fb;
+
+	switch (t->plane) {
+	case PLANE_PRI:
+		return &params->fb;
+	case PLANE_CUR:
+		return &params->cursor;
+	case PLANE_SPR:
+		return &params->sprite;
+	default:
+		igt_assert(false);
+	}
+}
+
+static void do_flush(const struct test_mode *t)
+{
+	struct modeset_params *params = pick_params(t);
+	struct igt_fb *target = pick_target(t, params);
+
+	gem_set_domain(drm.fd, target->gem_handle, I915_GEM_DOMAIN_GTT, 0);
+}
+
+#define DONT_ASSERT_CRC			(1 << 0)
+
+#define FBC_ASSERT_FLAGS		(0xF << 1)
+#define ASSERT_FBC_ENABLED		(1 << 1)
+#define ASSERT_FBC_DISABLED		(1 << 2)
+#define ASSERT_LAST_ACTION_CHANGED	(1 << 3)
+#define ASSERT_NO_ACTION_CHANGE		(1 << 4)
+
+#define PSR_ASSERT_FLAGS		(3 << 5)
+#define ASSERT_PSR_ENABLED		(1 << 5)
+#define ASSERT_PSR_DISABLED		(1 << 6)
+
+static int adjust_assertion_flags(const struct test_mode *t, int flags)
+{
+	if (!(flags & ASSERT_FBC_DISABLED))
+		flags |= ASSERT_FBC_ENABLED;
+	if (!(flags & ASSERT_PSR_DISABLED))
+		flags |= ASSERT_PSR_ENABLED;
+
+	if (t->feature != FEATURE_FBC)
+		flags &= ~FBC_ASSERT_FLAGS;
+	if (t->feature != FEATURE_PSR)
+		flags &= ~PSR_ASSERT_FLAGS;
+
+	return flags;
+}
+
+#define do_crc_assertions(flags) do {					\
+	int flags__ = (flags);						\
+	struct both_crcs crc_;						\
+									\
+	if (!opt.check_crc || (flags__ & DONT_ASSERT_CRC))		\
+		break;							\
+									\
+	collect_crcs(&crc_);						\
+	print_crc("Calculated CRC:", &crc_);				\
+									\
+	igt_assert(wanted_crc);						\
+	igt_assert_crc_equal(&crc_.pipe, &wanted_crc->pipe);		\
+	assert_sink_crc_equal(&crc_.sink, &wanted_crc->sink);		\
+} while (0)
+
+#define do_assertions(flags) do {					\
+	int flags_ = adjust_assertion_flags(t, (flags));		\
+									\
+	if (opt.step > 1)						\
+		wait_user();						\
+									\
+	/* Check the CRC to make sure the drawing operations work	\
+	 * immediately, independently of the features being enabled */	\
+	do_crc_assertions(flags_);					\
+									\
+	/* Now we can flush things to make the test faster. */		\
+	do_flush(t);							\
+									\
+	if (opt.check_status) {						\
+		if (flags_ & ASSERT_FBC_ENABLED) {			\
+			igt_assert(fbc_wait_for_status(ENABLED));	\
+									\
+			if (fbc.supports_compressing && 		\
+			    opt.fbc_check_compression)			\
+				igt_assert(fbc_wait_for_compression());	\
+		} else if (flags_ & ASSERT_FBC_DISABLED) {		\
+			igt_assert(fbc_wait_for_status(DISABLED));	\
+		}							\
+									\
+		if (flags_ & ASSERT_PSR_ENABLED)			\
+			igt_assert(psr_wait_for_status(ENABLED));	\
+		else if (flags_ & ASSERT_PSR_DISABLED)			\
+			igt_assert(psr_wait_for_status(DISABLED));	\
+	} else {							\
+		/* Make sure we settle before continuing. */		\
+		sleep(1);						\
+	}								\
+									\
+	/* Check CRC again to make sure the compressed screen is ok. */	\
+	do_crc_assertions(flags_);					\
+									\
+	if (fbc.supports_last_action && opt.fbc_check_last_action) {	\
+		if (flags_ & ASSERT_LAST_ACTION_CHANGED)		\
+			igt_assert(fbc_last_action_changed());		\
+		else if (flags_ & ASSERT_NO_ACTION_CHANGE)		\
+			igt_assert(!fbc_last_action_changed());		\
+	}								\
+									\
+	if (opt.step)							\
+		wait_user();						\
+} while (0)
+
+static void enable_prim_screen_and_wait(const struct test_mode *t)
+{
+	igt_draw_fill_fb(drm.fd, &prim_mode_params.fb, 0xFF);
+	set_mode_for_params(&prim_mode_params);
+
+	wanted_crc = &blue_crc;
+	fbc_update_last_action();
+
+	do_assertions(ASSERT_NO_ACTION_CHANGE);
+}
+
+static void enable_scnd_screen_and_wait(const struct test_mode *t)
+{
+	igt_draw_fill_fb(drm.fd, &scnd_mode_params.fb, 0x80);
+	set_mode_for_params(&scnd_mode_params);
+	do_assertions(ASSERT_NO_ACTION_CHANGE);
+}
+
+static void set_cursor_for_test(const struct test_mode *t,
+				struct modeset_params *params)
+{
+	int rc;
+
+	igt_draw_fill_fb(drm.fd, &params->cursor, 0xFF0000FF);
+
+	rc = drmModeMoveCursor(drm.fd, params->crtc_id, 0, 0);
+	igt_assert(rc == 0);
+
+	rc = drmModeSetCursor(drm.fd, params->crtc_id,
+			      params->cursor.gem_handle, params->cursor.width,
+			      params->cursor.height);
+	igt_assert(rc == 0);
+
+	do_assertions(ASSERT_NO_ACTION_CHANGE);
+}
+
+static void set_sprite_for_test(const struct test_mode *t,
+				struct modeset_params *params)
+{
+	int rc;
+
+	igt_draw_fill_fb(drm.fd, &params->sprite, 0xFF0000FF);
+
+	rc = drmModeSetPlane(drm.fd, params->sprite_id, params->crtc_id,
+			     params->sprite.fb_id, 0, 0, 0,
+			     params->sprite.width, params->sprite.height,
+			     0, 0, params->sprite.width << 16,
+			     params->sprite.height << 16);
+	igt_assert(rc == 0);
+
+	do_assertions(ASSERT_NO_ACTION_CHANGE);
+}
+
+static void enable_features_for_test(const struct test_mode *t)
+{
+	switch (t->feature) {
+	case FEATURE_NONE:
+		break;
+	case FEATURE_FBC:
+		fbc_enable();
+		break;
+	case FEATURE_PSR:
+		psr_enable();
+		break;
+	default:
+		igt_assert(false);
+	}
+}
+
+static void check_test_requirements(const struct test_mode *t)
+{
+	if (t->pipes == PIPE_DUAL)
+		igt_require(scnd_mode_params.connector_id);
+
+	if (t->feature == FEATURE_PSR)
+		igt_require(psr.can_test);
+
+	if (opt.only_feature != FEATURE_COUNT)
+		igt_require(t->feature == opt.only_feature);
+
+	if (opt.only_pipes != PIPE_COUNT)
+		igt_require(t->pipes == opt.only_pipes);
+}
+
+static void set_screens_for_test(const struct test_mode *t,
+				 struct draw_pattern_info *pattern)
+{
+	check_test_requirements(t);
+
+	if (t->screen == SCREEN_OFFSCREEN)
+		igt_draw_fill_fb(drm.fd, &offscreen_fb, 0x80);
+
+	disable_features();
+	unset_all_crtcs();
+	init_crcs(pattern);
+	enable_features_for_test(t);
+
+	enable_prim_screen_and_wait(t);
+	if (t->screen == SCREEN_PRIM) {
+		if (t->plane == PLANE_CUR)
+			set_cursor_for_test(t, &prim_mode_params);
+		if (t->plane == PLANE_SPR)
+			set_sprite_for_test(t, &prim_mode_params);
+	}
+
+	if (t->pipes == PIPE_SINGLE)
+		return;
+
+	enable_scnd_screen_and_wait(t);
+	if (t->screen == SCREEN_SCND) {
+		if (t->plane == PLANE_CUR)
+			set_cursor_for_test(t, &scnd_mode_params);
+		if (t->plane == PLANE_SPR)
+			set_sprite_for_test(t, &scnd_mode_params);
+	}
+}
+
+static void rte_subtest(const struct test_mode *t)
+{
+	check_test_requirements(t);
+
+	disable_features();
+	enable_features_for_test(t);
+	unset_all_crtcs();
+	do_assertions(ASSERT_FBC_DISABLED | ASSERT_PSR_DISABLED |
+		      DONT_ASSERT_CRC);
+
+	enable_prim_screen_and_wait(t);
+	set_cursor_for_test(t, &prim_mode_params);
+	set_sprite_for_test(t, &prim_mode_params);
+
+	if (t->pipes == PIPE_SINGLE)
+		return;
+
+	enable_scnd_screen_and_wait(t);
+	set_cursor_for_test(t, &scnd_mode_params);
+	set_sprite_for_test(t, &scnd_mode_params);
+}
+
+static void update_wanted_crc(const struct test_mode *t, struct both_crcs *crc)
+{
+	if (t->screen == SCREEN_PRIM)
+		wanted_crc = crc;
+}
+
+static void draw_subtest(const struct test_mode *t)
+{
+	int r;
+	int assertions = 0;
+	struct draw_pattern_info *pattern;
+	struct modeset_params *params = pick_params(t);
+	struct igt_fb *target = pick_target(t, params);
+
+	switch (t->screen) {
+	case SCREEN_PRIM:
+		if (t->method != IGT_DRAW_MMAP_GTT && t->plane == PLANE_PRI)
+			assertions |= ASSERT_LAST_ACTION_CHANGED;
+		break;
+	case SCREEN_SCND:
+	case SCREEN_OFFSCREEN:
+		assertions |= ASSERT_NO_ACTION_CHANGE;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	switch (t->plane) {
+	case PLANE_PRI:
+		pattern = &pattern1;
+		break;
+	case PLANE_CUR:
+	case PLANE_SPR:
+		pattern = &pattern2;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	set_screens_for_test(t, pattern);
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		draw_rect(pattern, target, t->method, r);
+		update_wanted_crc(t, &pattern->crcs[r]);
+		do_assertions(assertions);
+	}
+}
+
+static void flip_subtest(const struct test_mode *t)
+{
+	int r, rc;
+	int assertions = 0;
+	struct igt_fb fb2, *target;
+	struct modeset_params *params = pick_params(t);
+	struct draw_pattern_info *pattern = &pattern1;
+	uint32_t bg_color;
+
+	switch (t->screen) {
+	case SCREEN_PRIM:
+		assertions |= ASSERT_LAST_ACTION_CHANGED;
+		bg_color = 0xFF;
+		break;
+	case SCREEN_SCND:
+		assertions |= ASSERT_NO_ACTION_CHANGE;
+		bg_color = 0x80;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	set_screens_for_test(t, pattern);
+
+	igt_create_fb(drm.fd, params->mode->hdisplay, params->mode->vdisplay,
+		      DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED, &fb2);
+	igt_draw_fill_fb(drm.fd, &fb2, bg_color);
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		target = (r % 2 == 0) ? &fb2 : &params->fb;
+
+		if (r != 0)
+			draw_rect(pattern, target, t->method, r - 1);
+		draw_rect(pattern, target, t->method, r);
+		update_wanted_crc(t, &pattern->crcs[r]);
+
+		rc = drmModePageFlip(drm.fd, params->crtc_id, target->fb_id, 0,
+				     NULL);
+		igt_assert(rc == 0);
+
+		do_assertions(assertions);
+	}
+
+	igt_remove_fb(drm.fd, &fb2);
+}
+
+static void move_subtest(const struct test_mode *t)
+{
+	int r, rc;
+	int assertions = ASSERT_NO_ACTION_CHANGE;
+	struct modeset_params *params = pick_params(t);
+	struct draw_pattern_info *pattern = &pattern3;
+	bool repeat = false;
+
+	set_screens_for_test(t, pattern);
+
+	/* Just paint the right color since we start at 0x0. */
+	draw_rect(pattern, pick_target(t, params), t->method, 0);
+	update_wanted_crc(t, &pattern->crcs[0]);
+
+	do_assertions(assertions);
+
+	for (r = 1; r < pattern->n_rects; r++) {
+		struct rect rect = pattern->get_rect(&params->fb, r);
+
+		switch (t->plane) {
+		case PLANE_CUR:
+			rc = drmModeMoveCursor(drm.fd, params->crtc_id, rect.x,
+					       rect.y);
+			igt_assert(rc == 0);
+			break;
+		case PLANE_SPR:
+			rc = drmModeSetPlane(drm.fd, params->sprite_id,
+					     params->crtc_id,
+					     params->sprite.fb_id, 0,
+					     rect.x, rect.y, rect.w,
+					     rect.h, 0, 0, rect.w << 16,
+					     rect.h << 16);
+			igt_assert(rc == 0);
+			break;
+		default:
+			igt_assert(false);
+		}
+		update_wanted_crc(t, &pattern->crcs[r]);
+
+		do_assertions(assertions);
+
+		/* "Move" the last rect to the same position just to make sure
+		 * this works too. */
+		if (r+1 == pattern->n_rects && !repeat) {
+			repeat = true;
+			r--;
+		}
+	}
+}
+
+static void onoff_subtest(const struct test_mode *t)
+{
+	int r, rc;
+	int assertions = ASSERT_NO_ACTION_CHANGE;
+	struct modeset_params *params = pick_params(t);
+	struct draw_pattern_info *pattern = &pattern3;
+
+	set_screens_for_test(t, pattern);
+
+	/* Just paint the right color since we start at 0x0. */
+	draw_rect(pattern, pick_target(t, params), t->method, 0);
+	update_wanted_crc(t, &pattern->crcs[0]);
+	do_assertions(assertions);
+
+	for (r = 0; r < 4; r++) {
+		if (r % 2 == 0) {
+			switch (t->plane) {
+			case PLANE_CUR:
+				rc = drmModeSetCursor(drm.fd, params->crtc_id,
+						      0, 0, 0);
+				igt_assert(rc == 0);
+				break;
+			case PLANE_SPR:
+				rc = drmModeSetPlane(drm.fd, params->sprite_id,
+						     0, 0, 0, 0, 0, 0, 0, 0, 0,
+						     0, 0);
+				igt_assert(rc == 0);
+				break;
+			default:
+				igt_assert(false);
+			}
+			update_wanted_crc(t, &blue_crc);
+
+		} else {
+			switch (t->plane) {
+			case PLANE_CUR:
+				rc = drmModeSetCursor(drm.fd, params->crtc_id,
+						      params->cursor.gem_handle,
+						      params->cursor.width,
+						      params->cursor.height);
+				igt_assert(rc == 0);
+				break;
+			case PLANE_SPR:
+				rc = drmModeSetPlane(drm.fd, params->sprite_id,
+						     params->crtc_id,
+						     params->sprite.fb_id, 0,
+						     0, 0, params->sprite.width,
+						     params->sprite.height, 0,
+						     0,
+						     params->sprite.width << 16,
+						     params->sprite.height << 16);
+				igt_assert(rc == 0);
+				break;
+			default:
+				igt_assert(false);
+			}
+			update_wanted_crc(t, &pattern->crcs[0]);
+
+		}
+
+		do_assertions(assertions);
+	}
+}
+
+static void fullscreen_plane_subtest(const struct test_mode *t)
+{
+	struct draw_pattern_info *pattern = &pattern4;
+	struct igt_fb fullscreen_fb;
+	struct rect rect;
+	struct modeset_params *params = pick_params(t);
+	int assertions;
+	int rc;
+
+	set_screens_for_test(t, pattern);
+
+	rect = pattern->get_rect(&params->fb, 0);
+	igt_create_fb(drm.fd, rect.w, rect.h, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &fullscreen_fb);
+	igt_draw_fill_fb(drm.fd, &fullscreen_fb, rect.color);
+
+	rc = drmModeSetPlane(drm.fd, params->sprite_id, params->crtc_id,
+			     fullscreen_fb.fb_id, 0, 0, 0, fullscreen_fb.width,
+			     fullscreen_fb.height, 0, 0,
+			     fullscreen_fb.width << 16,
+			     fullscreen_fb.height << 16);
+	igt_assert(rc == 0);
+	update_wanted_crc(t, &pattern->crcs[0]);
+
+	switch (t->screen) {
+	case SCREEN_PRIM:
+		assertions = ASSERT_FBC_DISABLED |
+			     ASSERT_LAST_ACTION_CHANGED;
+		break;
+	case SCREEN_SCND:
+		assertions = ASSERT_NO_ACTION_CHANGE;
+		break;
+	default:
+		igt_assert(false);
+	}
+	do_assertions(assertions);
+
+	rc = drmModeSetPlane(drm.fd, params->sprite_id, 0, 0, 0, 0, 0, 0, 0, 0,
+			     0, 0, 0);
+	igt_assert(rc == 0);
+
+	if (t->screen == SCREEN_PRIM)
+		assertions = ASSERT_LAST_ACTION_CHANGED;
+	update_wanted_crc(t, &blue_crc);
+	do_assertions(assertions);
+
+	igt_remove_fb(drm.fd, &fullscreen_fb);
+}
+
+static int opt_handler(int option, int option_index)
+{
+	switch (option) {
+	case 's':
+		opt.check_status = false;
+		break;
+	case 'c':
+		opt.check_crc = false;
+		break;
+	case 'o':
+		opt.fbc_check_compression = false;
+		break;
+	case 'a':
+		opt.fbc_check_last_action = false;
+		break;
+	case 'e':
+		opt.no_edp = true;
+		break;
+	case 'm':
+		opt.small_modes = true;
+		break;
+	case 't':
+		opt.step++;
+		break;
+	case 'n':
+		igt_assert(opt.only_feature == FEATURE_COUNT);
+		opt.only_feature = FEATURE_NONE;
+		break;
+	case 'f':
+		igt_assert(opt.only_feature == FEATURE_COUNT);
+		opt.only_feature = FEATURE_FBC;
+		break;
+	case 'p':
+		igt_assert(opt.only_feature == FEATURE_COUNT);
+		opt.only_feature = FEATURE_PSR;
+		break;
+	case '1':
+		igt_assert(opt.only_pipes == PIPE_COUNT);
+		opt.only_pipes = PIPE_SINGLE;
+		break;
+	case '2':
+		igt_assert(opt.only_pipes == PIPE_COUNT);
+		opt.only_pipes = PIPE_DUAL;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	return 0;
+}
+
+const char *help_str =
+"  --no-status-check           Don't check for enable/disable status\n"
+"  --no-crc-check              Don't check for CRC values\n"
+"  --no-fbc-compression-check  Don't check for the FBC compression status\n"
+"  --no-fbc-action-check       Don't check for the FBC last action\n"
+"  --no-edp                    Don't use eDP monitors\n"
+"  --use-small-modes           Use smaller resolutions for the modes\n"
+"  --step                      Stop on each step so you can check the screen\n"
+"  --nop-only                  Only run the \"nop\" feature subtests\n"
+"  --fbc-only                  Only run the \"fbc\" feature subtests\n"
+"  --psr-only                  Only run the \"psr\" feature subtests\n"
+"  --1p-only                   Only run subtests that use 1 pipe\n"
+"  --2p-only                   Only run subtests that use 2 pipes\n";
+
+static const char *pipes_str(int pipes)
+{
+	switch (pipes) {
+	case PIPE_SINGLE:
+		return "1p";
+	case PIPE_DUAL:
+		return "2p";
+	default:
+		igt_assert(false);
+	}
+}
+
+static const char *screen_str(int screen)
+{
+	switch (screen) {
+	case SCREEN_PRIM:
+		return "primscrn";
+	case SCREEN_SCND:
+		return "scndscrn";
+	case SCREEN_OFFSCREEN:
+		return "offscren";
+	default:
+		igt_assert(false);
+	}
+}
+
+static const char *plane_str(int plane)
+{
+	switch (plane) {
+	case PLANE_PRI:
+		return "pri";
+	case PLANE_CUR:
+		return "cur";
+	case PLANE_SPR:
+		return "spr";
+	default:
+		igt_assert(false);
+	}
+}
+
+static const char *feature_str(int feature)
+{
+	switch (feature) {
+	case FEATURE_NONE:
+		return "nop";
+	case FEATURE_FBC:
+		return "fbc";
+	case FEATURE_PSR:
+		return "psr";
+	default:
+		igt_assert(false);
+	}
+}
+
+#define TEST_MODE_ITER_BEGIN(t) \
+	for (t.pipes = 0; t.pipes < PIPE_COUNT; t.pipes++) { \
+	for (t.screen = 0; t.screen < SCREEN_COUNT; t.screen++) { \
+	for (t.plane = 0; t.plane < PLANE_COUNT; t.plane++) { \
+	for (t.method = 0; t.method < IGT_DRAW_METHOD_COUNT; t.method++) { \
+	for (t.feature = 0; t.feature < FEATURE_COUNT; t.feature++) { \
+		if (t.pipes == PIPE_SINGLE && t.screen == SCREEN_SCND) \
+			continue;
+
+#define TEST_MODE_ITER_END } } } } }
+
+int main(int argc, char *argv[])
+{
+	struct test_mode t;
+	struct option long_options[] = {
+		{ "no-status-check",          0, 0, 's'},
+		{ "no-crc-check",             0, 0, 'c'},
+		{ "no-fbc-compression-check", 0, 0, 'o'},
+		{ "no-fbc-action-check",      0, 0, 'a'},
+		{ "no-edp",                   0, 0, 'e'},
+		{ "use-small-modes",          0, 0, 'm'},
+		{ "step",                     0, 0, 't'},
+		{ "nop-only",                 0, 0, 'n'},
+		{ "fbc-only",                 0, 0, 'f'},
+		{ "psr-only",                 0, 0, 'p'},
+		{ "1p-only",                  0, 0, '1'},
+		{ "2p-only",                  0, 0, '2'},
+		{ 0, 0, 0, 0 }
+	};
+
+	igt_subtest_init_parse_opts(&argc, argv, "", long_options, help_str,
+				    opt_handler);
+
+	igt_fixture
+		setup_environment();
+
+	for (t.pipes = 0; t.pipes < PIPE_COUNT; t.pipes++) {
+		for (t.feature = 0; t.feature < FEATURE_COUNT; t.feature++) {
+			t.screen = SCREEN_PRIM;
+			t.plane = PLANE_PRI;
+			/* Make sure nothing is using this value. */
+			t.method = -1;
+
+			igt_subtest_f("%s-rte-%s",
+				      pipes_str(t.pipes),
+				      feature_str(t.feature))
+				rte_subtest(&t);
+		}
+	}
+
+	TEST_MODE_ITER_BEGIN(t)
+		igt_subtest_f("%s-%s-%s-draw-%s-%s",
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      igt_draw_get_method_name(t.method),
+			      feature_str(t.feature))
+			draw_subtest(&t);
+	TEST_MODE_ITER_END
+
+
+	TEST_MODE_ITER_BEGIN(t)
+		if (t.plane != PLANE_PRI)
+			continue;
+
+		if (t.screen == SCREEN_OFFSCREEN)
+			continue;
+
+		igt_subtest_f("%s-%s-flip-%s-%s",
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      igt_draw_get_method_name(t.method),
+			      feature_str(t.feature))
+			flip_subtest(&t);
+	TEST_MODE_ITER_END
+
+	TEST_MODE_ITER_BEGIN(t)
+		if (t.screen == SCREEN_OFFSCREEN)
+			continue;
+		if (t.method != IGT_DRAW_BLT)
+			continue;
+		if (t.plane == PLANE_PRI)
+			continue;
+
+		igt_subtest_f("%s-%s-%s-move-%s",
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      feature_str(t.feature))
+			move_subtest(&t);
+
+		igt_subtest_f("%s-%s-%s-onoff-%s",
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      feature_str(t.feature))
+			onoff_subtest(&t);
+	TEST_MODE_ITER_END
+
+	TEST_MODE_ITER_BEGIN(t)
+		if (t.screen == SCREEN_OFFSCREEN)
+			continue;
+		if (t.method != IGT_DRAW_BLT)
+			continue;
+		if (t.plane != PLANE_SPR)
+			continue;
+
+		igt_subtest_f("%s-%s-%s-fullscreen-%s",
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      feature_str(t.feature))
+			fullscreen_plane_subtest(&t);
+	TEST_MODE_ITER_END
+
+	/*
+	 * TODO: ideas for subtests:
+	 * - Add a new pipe configuration where both pipes can share a big
+	 *   framebuffer (instead of each pipe having its own FB). This will
+	 *   possibly require some wrapping of struct igt_fb to make the
+	 *   implementation easier.
+	 * - Add a test that alternates between different writing methods. Don't
+	 *   forget to add the proper domain handling.
+	 * - Add a new enum to struct test_mode that allows us to specify the
+	 *   BPP/depth configuration.
+	 */
+
+	igt_fixture
+		teardown_environment();
+
+	igt_exit();
+}
