diff mbox

[igt] tests: add kms_frontbuffer_tracking

Message ID 1433799782-1773-1-git-send-email-przanoni@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Paulo Zanoni June 8, 2015, 9:43 p.m. UTC
From: Paulo Zanoni <paulo.r.zanoni@intel.com>

This is a new test that should exercise the frontbuffer tracking
feature of the Kernel in a number of different ways. We use different
drawing methods, we use the primary, cursor and sprite planes, we can
test both on single and dual pipes, also on buffers not associated
with any CRTCs, etc.

We currently have assertions for both FBC and PSR, and we also have a
"nop" test mode that should disable both FBC and PSR, and can be
used for debugging.

This test is also capable of testing both FBC and PSR even if they are
disabled by default on the Kernel: the test knows how to change the
i915.ko parameters and then set them back after testing.

I am getting a significant number of failures when I run this test,
which means we have some work to do on the Kernel.

I also still have a small list of additional subtests that I plan to
add to this test, and those tests are documented on the main function.

v2:
 - Use igt_debugfs_open() (Thomas).
 - Use igt_test_description() (Thomas).
 - Don't check drm_open_any_master()'s result (Thomas).
 - Use igt_require_f() in some cases (Thomas).
 - Standardize some assertions.
 - Use the new module param functions.
 - Check if FBC is supported by the chipset.
 - Add new subtests (multidraw, enum fbs, fbc+psr).
 - Make tests a little shorter.
 - Reorganize which tests ara ran by default.
 - Better comments everywhere.
 - Rebase.

Signed-off-by: Paulo Zanoni <paulo.r.zanoni@intel.com>
---
 tests/.gitignore                 |    1 +
 tests/Makefile.sources           |    1 +
 tests/kms_frontbuffer_tracking.c | 2224 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 2226 insertions(+)
 create mode 100644 tests/kms_frontbuffer_tracking.c
diff mbox

Patch

diff --git a/tests/.gitignore b/tests/.gitignore
index f816ded..c17264c 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 d2a44e8..551c600 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..4e92133
--- /dev/null
+++ b/tests/kms_frontbuffer_tracking.c
@@ -0,0 +1,2224 @@ 
+/*
+ * 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"
+
+IGT_TEST_DESCRIPTION("Test the Kernel's frontbuffer tracking mechanism and "
+		     "its related features: FBC and PSR");
+
+/*
+ * One of the aspects of this test is that, for every subtest, we try different
+ * combinations of the parameters defined by the struct below. Because of this,
+ * a single addition of a new parameter or subtest function can lead to hundreds
+ * of new subtests.
+ *
+ * In order to reduce the number combinations we cut the cases that don't make
+ * sense, such as writing on the secondary screen when there is only a single
+ * pipe, or flipping when the target is the offscreen buffer. We also hide some
+ * combinations that are somewhat redundant and don't add much value to the
+ * test. For example, since we already do the offscreen testing with a single
+ * pipe enabled, there's no much value in doing it again with dual pipes. If you
+ * still want to try these redundant tests, you need to use the --show-hidden
+ * option.
+ *
+ * The most important hidden thing is the FEATURE_NONE set of tests. Whenever
+ * you get a failure on any test, it is important to check whether the same test
+ * fails with FEATURE_NONE - replace the feature name for "nop". If the nop test
+ * also fails, then it's likely the problem will be on the IGT side instead of
+ * the Kernel side. We don't expose this set of tests by default because (i)
+ * they take a long time to test; and (ii) if the feature tests work, then it's
+ * very likely that the nop tests will also work.
+ */
+struct test_mode {
+	/* Are we going to enable just one monitor, or are we going to setup a
+	 * dual screen environment for the test? */
+	enum {
+		PIPE_SINGLE = 0,
+		PIPE_DUAL,
+		PIPE_COUNT,
+	} pipes;
+
+	/* The primary screen is the one that's supposed to have the "feature"
+	 * enabled on, but we have the option to draw on the secondary screen or
+	 * on some offscreen buffer. We also only theck the CRC of the primary
+	 * screen. */
+	enum {
+		SCREEN_PRIM = 0,
+		SCREEN_SCND,
+		SCREEN_OFFSCREEN,
+		SCREEN_COUNT,
+	} screen;
+
+	/* When we draw, we can draw directly on the primary plane, on the
+	 * cursor or on the sprite plane. */
+	enum {
+		PLANE_PRI = 0,
+		PLANE_CUR,
+		PLANE_SPR,
+		PLANE_COUNT,
+	} plane;
+
+	/* We can organize the screens in a way that each screen has its own
+	 * framebuffer, or in a way that all screens point to the same
+	 * framebuffer, but on different places. This includes the offscreen
+	 * screen. */
+	enum {
+		FBS_SINGLE = 0,
+		FBS_MULTI,
+		FBS_COUNT
+	} fbs;
+
+	/* Which features are we going to test now? This is a mask! */
+	enum {
+		FEATURE_NONE  = 0,
+		FEATURE_FBC   = 1,
+		FEATURE_PSR   = 2,
+		FEATURE_COUNT = 4,
+	} 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;
+	bool can_test;
+
+	bool supports_compressing;
+	bool supports_last_action;
+
+	struct timespec last_action;
+} fbc = {
+	.fd = -1,
+	.can_test = false,
+	.supports_last_action = false,
+	.supports_compressing = false,
+};
+
+struct {
+	int fd;
+	bool can_test;
+} 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;
+
+/* The goal of this structure is to easily allow us to deal with cases where we
+ * have a big framebuffer and the CRTC is just displaying a subregion of this
+ * big FB. */
+struct fb_region {
+	struct igt_fb *fb;
+	int x;
+	int y;
+	int w;
+	int h;
+};
+
+struct draw_pattern_info {
+	bool initialized;
+	bool frames_stack;
+	int n_rects;
+	struct both_crcs *crcs;
+	struct rect (*get_rect)(struct fb_region *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;
+	bool show_hidden;
+	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,
+	.show_hidden= 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 fb_region fb;
+	struct fb_region cursor;
+	struct fb_region sprite;
+};
+
+struct modeset_params prim_mode_params;
+struct modeset_params scnd_mode_params;
+struct fb_region offscreen_fb;
+struct {
+	struct igt_fb prim_pri;
+	struct igt_fb prim_cur;
+	struct igt_fb prim_spr;
+
+	struct igt_fb scnd_pri;
+	struct igt_fb scnd_cur;
+	struct igt_fb scnd_spr;
+
+	struct igt_fb offscreen;
+	struct igt_fb big;
+} fbs;
+
+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;
+
+	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;
+
+	params->fb.fb = NULL;
+	params->fb.w = mode->hdisplay;
+	params->fb.h = mode->vdisplay;
+
+	params->cursor.fb = NULL;
+	params->cursor.x = 0;
+	params->cursor.y = 0;
+	params->cursor.w = 64;
+	params->cursor.h = 64;
+
+	params->sprite.fb = NULL;
+	params->sprite.x = 0;
+	params->sprite.y = 0;
+	params->sprite.w = 64;
+	params->sprite.h = 64;
+}
+
+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;
+}
+
+/*
+ * This is how the prim, scnd and offscreens FB should be positioned inside the
+ * big FB. The prim buffer starts at a 500x500 offset, then scnd starts at the
+ * same 500 pixel Y offset, right after prim ends on the X axis, then the
+ * offscreen fb starts after scnd ends.
+ * +------------------------------------+
+ * | big                                |
+ * |   +--------+-----------+-----------+
+ * |   | prim   | scnd      | offscreen |
+ * |   |        |           |           |
+ * |   |        +-----------+           |
+ * |   |        |           +-----------+
+ * +---+--------+-----------------------+
+ */
+static void create_big_fb(void)
+{
+	int prim_w, prim_h, scnd_w, scnd_h, offs_w, offs_h, big_w, big_h;
+
+	prim_w = prim_mode_params.mode->hdisplay;
+	prim_h = prim_mode_params.mode->vdisplay;
+
+	if (scnd_mode_params.connector_id) {
+		scnd_w = scnd_mode_params.mode->hdisplay;
+		scnd_h = scnd_mode_params.mode->vdisplay;
+	} else {
+		scnd_w = 0;
+		scnd_h = 0;
+	}
+	offs_w = offscreen_fb.w;
+	offs_h = offscreen_fb.h;
+
+	big_w = prim_w + scnd_w + offs_w + 500;
+
+	big_h = prim_h;
+	if (scnd_h > big_h)
+		big_h = scnd_h;
+	if (offs_h > big_h)
+		big_h = offs_h;
+	big_h += 500;
+
+	igt_create_fb(drm.fd, big_w, big_h, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &fbs.big);
+}
+
+static void create_fbs(void)
+{
+	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.prim_pri);
+	igt_create_fb(drm.fd, prim_mode_params.cursor.w,
+		      prim_mode_params.cursor.h, DRM_FORMAT_ARGB8888,
+		      LOCAL_DRM_FORMAT_MOD_NONE, &fbs.prim_cur);
+	igt_create_fb(drm.fd, prim_mode_params.sprite.w,
+		      prim_mode_params.sprite.h, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &fbs.prim_spr);
+
+	igt_create_fb(drm.fd, offscreen_fb.w, offscreen_fb.h,
+		      DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED,
+		      &fbs.offscreen);
+
+	create_big_fb();
+
+	if (!scnd_mode_params.connector_id)
+		return;
+
+	igt_create_fb(drm.fd, scnd_mode_params.mode->hdisplay,
+		      scnd_mode_params.mode->vdisplay,
+		      DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED,
+		      &fbs.scnd_pri);
+	igt_create_fb(drm.fd, scnd_mode_params.cursor.w,
+		      scnd_mode_params.cursor.h, DRM_FORMAT_ARGB8888,
+		      LOCAL_DRM_FORMAT_MOD_NONE, &fbs.scnd_cur);
+	igt_create_fb(drm.fd, scnd_mode_params.sprite.w,
+		      scnd_mode_params.sprite.h, DRM_FORMAT_XRGB8888,
+		      LOCAL_I915_FORMAT_MOD_X_TILED, &fbs.scnd_spr);
+}
+
+static bool set_mode_for_params(struct modeset_params *params)
+{
+	int rc;
+
+	rc = drmModeSetCrtc(drm.fd, params->crtc_id, params->fb.fb->fb_id,
+			    params->fb.x, params->fb.y,
+			    &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);
+}
+
+#define fbc_enable() igt_set_module_param_int("enable_fbc", 1)
+#define fbc_disable() igt_set_module_param_int("enable_fbc", 0)
+#define psr_enable() igt_set_module_param_int("enable_psr", 1)
+#define psr_disable() igt_set_module_param_int("enable_psr", 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 fb_region *fb, int r)
+{
+	struct rect rect;
+
+	switch (r) {
+	case 0:
+		rect.x = 0;
+		rect.y = 0;
+		rect.w = fb->w / 8;
+		rect.h = fb->h / 8;
+		rect.color = 0x00FF00;
+		break;
+	case 1:
+		rect.x = fb->w / 8 * 4;
+		rect.y = fb->h / 8 * 4;
+		rect.w = fb->w / 8 * 2;
+		rect.h = fb->h / 8 * 2;
+		rect.color = 0xFF0000;
+		break;
+	case 2:
+		rect.x = fb->w / 16 + 1;
+		rect.y = fb->h / 16 + 1;
+		rect.w = fb->w / 8 + 1;
+		rect.h = fb->h / 8 + 1;
+		rect.color = 0xFF00FF;
+		break;
+	case 3:
+		rect.x = fb->w - 64;
+		rect.y = fb->h - 64;
+		rect.w = 64;
+		rect.h = 64;
+		rect.color = 0x00FFFF;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	return rect;
+}
+
+static struct rect pat2_get_rect(struct fb_region *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.x = 31;
+		rect.y = 31;
+		rect.w = 31;
+		rect.h = 31;
+		rect.color = 0xFFFF0000;
+		break;
+	case 2:
+		rect.x = 16;
+		rect.y = 16;
+		rect.w = 32;
+		rect.h = 32;
+		rect.color = 0xFFFF00FF;
+		break;
+	case 3:
+		rect.color = 0xFF00FFFF;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	return rect;
+}
+
+static struct rect pat3_get_rect(struct fb_region *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->w - 64;
+		rect.y = fb->h - 64;
+		break;
+	case 4:
+		rect.x = fb->w / 2 - 32;
+		rect.y = fb->h / 2 - 32;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	return rect;
+}
+
+static struct rect pat4_get_rect(struct fb_region *fb, int r)
+{
+	struct rect rect;
+
+	igt_assert(r == 0);
+
+	rect.x = 0;
+	rect.y = 0;
+	rect.w = fb->w;
+	rect.h = fb->h;
+	rect.color = 0xFF00FF00;
+
+	return rect;
+}
+
+static void draw_rect(struct draw_pattern_info *pattern, struct fb_region *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->fb, method,
+			 fb->x + rect.x, fb->y + rect.y,
+			 rect.w, rect.h, rect.color);
+}
+
+static void draw_rect_igt_fb(struct draw_pattern_info *pattern,
+			     struct igt_fb *fb, enum igt_draw_method method,
+			     int r)
+{
+	struct fb_region region = {
+		.fb = fb,
+		.x = 0,
+		.y = 0,
+		.w = fb->width,
+		.h = fb->height,
+	};
+
+	draw_rect(pattern, &region, method, r);
+}
+
+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 tmp_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, &tmp_fbs[r]);
+
+	for (r = 0; r < pattern->n_rects; r++)
+		igt_draw_fill_fb(drm.fd, &tmp_fbs[r], 0xFF);
+
+	if (pattern->frames_stack) {
+		for (r = 0; r < pattern->n_rects; r++)
+			for (r_ = 0; r_ <= r; r_++)
+				draw_rect_igt_fb(pattern, &tmp_fbs[r],
+						 IGT_DRAW_PWRITE, r_);
+	} else {
+		for (r = 0; r < pattern->n_rects; r++)
+			draw_rect_igt_fb(pattern, &tmp_fbs[r], IGT_DRAW_PWRITE,
+					 r);
+	}
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		rc = drmModeSetCrtc(drm.fd, prim_mode_params.crtc_id,
+				   tmp_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, &tmp_fbs[r]);
+
+	pattern->initialized = true;
+}
+
+static void setup_drm(void)
+{
+	int i;
+
+	drm.fd = drm_open_any_master();
+
+	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());
+	offscreen_fb.fb = NULL;
+	offscreen_fb.w = 1024;
+	offscreen_fb.h = 1024;
+	create_fbs();
+	kmstest_set_vt_graphics_mode();
+}
+
+static void teardown_modeset(void)
+{
+	if (scnd_mode_params.connector_id) {
+		igt_remove_fb(drm.fd, &fbs.scnd_pri);
+		igt_remove_fb(drm.fd, &fbs.scnd_cur);
+		igt_remove_fb(drm.fd, &fbs.scnd_spr);
+	}
+	igt_remove_fb(drm.fd, &fbs.prim_pri);
+	igt_remove_fb(drm.fd, &fbs.prim_cur);
+	igt_remove_fb(drm.fd, &fbs.prim_spr);
+	igt_remove_fb(drm.fd, &fbs.offscreen);
+	igt_remove_fb(drm.fd, &fbs.big);
+}
+
+static void setup_crcs(void)
+{
+	pipe_crc = igt_pipe_crc_new(0, INTEL_PIPE_CRC_SOURCE_AUTO);
+
+	sink_crc.fd = igt_debugfs_open("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 = 4;
+	pattern1.crcs = NULL;
+	pattern1.get_rect = pat1_get_rect;
+
+	pattern2.initialized = false;
+	pattern2.frames_stack = true;
+	pattern2.n_rects = 4;
+	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 bool fbc_supported_on_chipset(void)
+{
+	char buf[DEBUGFS_MSG_SIZE];
+
+	get_debugfs_string(fbc.fd, buf);
+
+	return !strstr(buf, "FBC unsupported on this chipset\n");
+}
+
+static void setup_fbc(void)
+{
+	fbc.fd = igt_debugfs_open("i915_fbc_status", O_RDONLY);
+	igt_assert(fbc.fd >= 0);
+
+	if (!fbc_supported_on_chipset()) {
+		igt_info("Can't test FBC: not supported on this chipset\n");
+		return;
+	}
+	fbc.can_test = true;
+
+	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 = igt_debugfs_open("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;
+}
+
+static void teardown_psr(void)
+{
+	if (psr.fd != -1)
+		close(psr.fd);
+}
+
+static void setup_environment(void)
+{
+	setup_drm();
+	setup_modeset();
+
+	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 fb_region *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 fb_region *target = pick_target(t, params);
+
+	gem_set_domain(drm.fd, target->fb->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) == 0)
+		flags &= ~FBC_ASSERT_FLAGS;
+	if ((t->feature & FEATURE_PSR) == 0)
+		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,	\
+	 * except if we're not drawing on the primary screen. On this	\
+	 * case, the first check should be enough and a new CRC check	\
+	 * would only delay the test suite while adding anyreal value	\
+	 * to the test suite. */					\
+	if (t->screen == SCREEN_PRIM)					\
+		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 fill_fb_region(struct fb_region *region, uint32_t color)
+{
+	igt_draw_rect_fb(drm.fd, NULL, NULL, region->fb, IGT_DRAW_MMAP_GTT,
+			 region->x, region->y, region->w, region->h,
+			 color);
+}
+
+static void enable_prim_screen_and_wait(const struct test_mode *t)
+{
+	fill_fb_region(&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)
+{
+	fill_fb_region(&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;
+
+	fill_fb_region(&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.fb->gem_handle,
+			      params->cursor.w,
+			      params->cursor.h);
+	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;
+
+	fill_fb_region(&params->sprite, 0xFF0000FF);
+
+	rc = drmModeSetPlane(drm.fd, params->sprite_id, params->crtc_id,
+			     params->sprite.fb->fb_id, 0, 0, 0,
+			     params->sprite.w, params->sprite.h,
+			     0, 0, params->sprite.w << 16,
+			     params->sprite.h << 16);
+	igt_assert(rc == 0);
+
+	do_assertions(ASSERT_NO_ACTION_CHANGE);
+}
+
+static void enable_features_for_test(const struct test_mode *t)
+{
+	if (t->feature & FEATURE_FBC)
+		fbc_enable();
+	if (t->feature & FEATURE_PSR)
+		psr_enable();
+}
+
+static void check_test_requirements(const struct test_mode *t)
+{
+	if (t->pipes == PIPE_DUAL)
+		igt_require_f(scnd_mode_params.connector_id,
+			    "Can't test dual pipes with the current outputs\n");
+
+	if (t->feature & FEATURE_FBC)
+		igt_require_f(fbc.can_test,
+			      "Can't test FBC with this chipset\n");
+
+	if (t->feature & FEATURE_PSR)
+		igt_require_f(psr.can_test,
+			      "Can't test PSR with the current outputs\n");
+
+	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_crtc_fbs(const struct test_mode *t)
+{
+	switch (t->fbs) {
+	case FBS_SINGLE:
+		prim_mode_params.fb.fb = &fbs.prim_pri;
+		scnd_mode_params.fb.fb = &fbs.scnd_pri;
+		offscreen_fb.fb = &fbs.offscreen;
+
+		prim_mode_params.fb.x = 0;
+		scnd_mode_params.fb.x = 0;
+		offscreen_fb.x = 0;
+
+		prim_mode_params.fb.y = 0;
+		scnd_mode_params.fb.y = 0;
+		offscreen_fb.y = 0;
+		break;
+	case FBS_MULTI:
+		/* Please see the comment at the top of create_big_fb(). */
+		prim_mode_params.fb.fb = &fbs.big;
+		scnd_mode_params.fb.fb = &fbs.big;
+		offscreen_fb.fb = &fbs.big;
+
+		prim_mode_params.fb.x = 500;
+		scnd_mode_params.fb.x = prim_mode_params.fb.x +
+					prim_mode_params.fb.w;
+		offscreen_fb.x = scnd_mode_params.fb.x + scnd_mode_params.fb.w;
+
+		prim_mode_params.fb.y = 500;
+		scnd_mode_params.fb.y = 500;
+		offscreen_fb.y = 500;
+		break;
+	default:
+		igt_assert(false);
+	}
+
+	prim_mode_params.cursor.fb = &fbs.prim_cur;
+	prim_mode_params.sprite.fb = &fbs.prim_spr;
+	scnd_mode_params.cursor.fb = &fbs.scnd_cur;
+	scnd_mode_params.sprite.fb = &fbs.scnd_spr;
+}
+
+static void set_screens_for_test(const struct test_mode *t,
+				 struct draw_pattern_info *pattern)
+{
+	check_test_requirements(t);
+
+	disable_features();
+	set_crtc_fbs(t);
+
+	if (t->screen == SCREEN_OFFSCREEN)
+		fill_fb_region(&offscreen_fb, 0x80);
+
+	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);
+	}
+}
+
+/*
+ * rte - the basic sanity test
+ *
+ * METHOD
+ *   Just disable all screens, assert everything is disabled, then enable all
+ *   screens - including primary, cursor and sprite planes - and assert that
+ *   the tested feature is enabled.
+ *
+ * EXPECTED RESULTS
+ *   Blue screens and t->feature enabled.
+ *
+ * FAILURES
+ *   A failure here means that every other subtest will probably fail too. It
+ *   probably means that the Kernel is just not enabling the feature we want.
+ */
+static void rte_subtest(const struct test_mode *t)
+{
+	check_test_requirements(t);
+
+	disable_features();
+	set_crtc_fbs(t);
+
+	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;
+}
+
+/*
+ * draw - draw a set of rectangles on the screen using the provided method
+ *
+ * METHOD
+ *   Just set the screens as appropriate and then start drawing a series of
+ *   rectangles on the target screen. The important guy here is the drawing
+ *   method used.
+ *
+ * EXPECTED RESULTS
+ *   The feature either stays enabled or gets reenabled after the oprations. You
+ *   will also see the rectangles on the target screen.
+ *
+ * FAILURES
+ *   A failure here indicates a problem somewhere between the Kernel's
+ *   frontbuffer tracking infrastructure or the feature itself. You need to pay
+ *   attention to which drawing method is being used.
+ */
+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 fb_region *target;
+
+	switch (t->screen) {
+	case SCREEN_PRIM:
+		if (t->method != IGT_DRAW_MMAP_GTT && t->plane == PLANE_PRI)
+			assertions |= ASSERT_LAST_ACTION_CHANGED;
+		else
+			assertions |= ASSERT_NO_ACTION_CHANGE;
+		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);
+	target = pick_target(t, params);
+
+	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);
+	}
+}
+
+/*
+ * multidraw - draw a set of rectangles on the screen using alternated drawing
+ *             methods
+ *
+ * METHOD
+ *   This is just like the draw subtest, but now we keep alternating between two
+ *   drawing methods. Each time we run multidraw_subtest we will test all the
+ *   possible pairs containing t->method.
+ *
+ * EXPECTED RESULTS
+ *   The same as the draw subtest.
+ *
+ * FAILURES
+ *   If you get a failure here, first you need to check whether you also get
+ *   failures on the individual draw subtests. If yes, then go fix every single
+ *   draw subtest first. If all the draw subtests pass but this one fails, then
+ *   you have to study how one drawing method is stopping the other from
+ *   properly working.
+ */
+static void multidraw_subtest(const struct test_mode *t)
+{
+	int r;
+	int assertions = 0;
+	struct draw_pattern_info *pattern;
+	struct modeset_params *params = pick_params(t);
+	struct fb_region *target;
+	enum igt_draw_method m, used_method;
+	uint32_t color;
+
+	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);
+	target = pick_target(t, params);
+
+	for (m = 0; m < IGT_DRAW_METHOD_COUNT; m++) {
+		if (m == t->method)
+			continue;
+
+		igt_debug("Method %s\n", igt_draw_get_method_name(m));
+		for (r = 0; r < pattern->n_rects; r++) {
+
+			used_method = (r % 2 == 0) ? t->method : m;
+
+			draw_rect(pattern, target, used_method, r);
+			update_wanted_crc(t, &pattern->crcs[r]);
+
+			assertions = used_method != IGT_DRAW_MMAP_GTT ?
+				     ASSERT_LAST_ACTION_CHANGED :
+				     ASSERT_NO_ACTION_CHANGE;
+			do_assertions(assertions);
+		}
+
+		switch (t->plane) {
+		case PLANE_PRI:
+			color = 0xFF;
+			break;
+		case PLANE_CUR:
+		case PLANE_SPR:
+			color = 0xFF0000FF;
+			break;
+		default:
+			igt_assert(false);
+		}
+		fill_fb_region(target, color);
+
+		update_wanted_crc(t, &blue_crc);
+		do_assertions(ASSERT_NO_ACTION_CHANGE);
+	}
+}
+
+/*
+ * flip - just exercise page flips with the patterns we have
+ *
+ * METHOD
+ *   We draw the pattern on a backbuffer using the provided method, then we
+ *   flip, making this the frontbuffer.
+ *
+ * EXPECTED RESULTS
+ *   Everything works as expected, screen contents are properly updated.
+ *
+ * FAILURES
+ *   On a failure here you need to go directly to the Kernel's flip code and see
+ *   how it interacts with the feature being tested.
+ */
+static void flip_subtest(const struct test_mode *t)
+{
+	int r, rc;
+	int assertions = 0;
+	struct igt_fb fb2;
+	struct fb_region fb2_region, *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->fb.fb->width, params->fb.fb->height,
+		      DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED, &fb2);
+	igt_draw_fill_fb(drm.fd, &fb2, bg_color);
+	fb2_region.fb = &fb2;
+	fb2_region.x = params->fb.x;
+	fb2_region.y = params->fb.y;
+	fb2_region.w = params->fb.w;
+	fb2_region.h = params->fb.h;
+
+	for (r = 0; r < pattern->n_rects; r++) {
+		target = (r % 2 == 0) ? &fb2_region : &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->fb_id,
+				     0, NULL);
+		igt_assert(rc == 0);
+
+		do_assertions(assertions);
+	}
+
+	igt_remove_fb(drm.fd, &fb2);
+}
+
+/*
+ * move - just move the sprite or cursor around
+ *
+ * METHOD
+ *   Move the surface around, following the defined pattern.
+ *
+ * EXPECTED RESULTS
+ *   The move operations are properly detected by the Kernel, and the screen is
+ *   properly updated every time.
+ *
+ * FAILURES
+ *   If you get a failure here, check how the Kernel is enabling or disabling
+ *   your feature when it moves the planes around.
+ */
+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->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--;
+		}
+	}
+}
+
+/*
+ * onoff - just enable and disable the sprite or cursor plane a few times
+ *
+ * METHOD
+ *   Just enable and disable the desired plane a few times.
+ *
+ * EXPECTED RESULTS
+ *   Everything is properly detected by the Kernel and the screen contents are
+ *   accurate.
+ *
+ * FAILURES
+ *   As usual, if you get a failure here you need to check how the feature is
+ *   being handled when the planes are enabled or disabled.
+ */
+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.fb->gem_handle,
+						  params->cursor.w,
+						  params->cursor.h);
+				igt_assert(rc == 0);
+				break;
+			case PLANE_SPR:
+				rc = drmModeSetPlane(drm.fd, params->sprite_id,
+						     params->crtc_id,
+						     params->sprite.fb->fb_id,
+						     0, 0, 0, params->sprite.w,
+						     params->sprite.h, 0,
+						     0,
+						     params->sprite.w << 16,
+						     params->sprite.h << 16);
+				igt_assert(rc == 0);
+				break;
+			default:
+				igt_assert(false);
+			}
+			update_wanted_crc(t, &pattern->crcs[0]);
+
+		}
+
+		do_assertions(assertions);
+	}
+}
+
+/*
+ * fullscreen_plane - put a fullscreen plane covering the whole screen
+ *
+ * METHOD
+ *   As simple as the description above.
+ *
+ * EXPECTED RESULTS
+ *   It depends on the feature being tested. FBC gets disabled, but PSR doesn't.
+ *
+ * FAILURES
+ *   Again, if you get failures here you need to dig into the Kernel code, see
+ *   how it is handling your feature on this specific case.
+ */
+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, void *data)
+{
+	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 'i':
+		opt.show_hidden = 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"
+"  --show-hidden               Show hidden subtests\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 *fbs_str(int fb)
+{
+	switch (fb) {
+	case FBS_SINGLE:
+		return "sfb";
+	case FBS_MULTI:
+		return "mfb";
+	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";
+	case FEATURE_FBC | FEATURE_PSR:
+		return "fbcpsr";
+	default:
+		igt_assert(false);
+	}
+}
+
+#define TEST_MODE_ITER_BEGIN(t) \
+	for (t.feature = 0; t.feature < FEATURE_COUNT; t.feature++) {	   \
+	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.fbs = 0; t.fbs < FBS_COUNT; t.fbs++) {			   \
+	for (t.method = 0; t.method < IGT_DRAW_METHOD_COUNT; t.method++) { \
+		if (t.pipes == PIPE_SINGLE && t.screen == SCREEN_SCND)	   \
+			continue;					   \
+		if (!opt.show_hidden && t.pipes == PIPE_DUAL &&		   \
+		    t.screen == SCREEN_OFFSCREEN)			   \
+			continue;					   \
+		if ((!opt.show_hidden && opt.only_feature != FEATURE_NONE) \
+		    && t.feature == FEATURE_NONE)			   \
+			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'},
+		{ "show-hidden",              0, 0, 'i'},
+		{ "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, NULL);
+
+	igt_fixture
+		setup_environment();
+
+	for (t.feature = 0; t.feature < FEATURE_COUNT; t.feature++) {
+		if ((!opt.show_hidden && opt.only_feature != FEATURE_NONE)
+		    && t.feature == FEATURE_NONE)
+			continue;
+		for (t.pipes = 0; t.pipes < PIPE_COUNT; t.pipes++) {
+			t.screen = SCREEN_PRIM;
+			t.plane = PLANE_PRI;
+			t.fbs = FBS_SINGLE;
+			/* Make sure nothing is using this value. */
+			t.method = -1;
+
+			igt_subtest_f("%s-%s-rte",
+				      feature_str(t.feature),
+				      pipes_str(t.pipes))
+				rte_subtest(&t);
+		}
+	}
+
+	TEST_MODE_ITER_BEGIN(t)
+		igt_subtest_f("%s-%s-%s-%s-%s-draw-%s",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      fbs_str(t.fbs),
+			      igt_draw_get_method_name(t.method))
+			draw_subtest(&t);
+	TEST_MODE_ITER_END
+
+	TEST_MODE_ITER_BEGIN(t)
+		if (t.plane != PLANE_PRI)
+			continue;
+		if (t.screen == SCREEN_OFFSCREEN)
+			continue;
+		if (!opt.show_hidden && t.method != IGT_DRAW_BLT)
+			continue;
+
+		igt_subtest_f("%s-%s-%s-%s-flip-%s",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      fbs_str(t.fbs),
+			      igt_draw_get_method_name(t.method))
+			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-%s-%s-move",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      fbs_str(t.fbs))
+			move_subtest(&t);
+
+		igt_subtest_f("%s-%s-%s-%s-%s-onoff",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      fbs_str(t.fbs))
+			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-%s-%s-fullscreen",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      screen_str(t.screen),
+			      plane_str(t.plane),
+			      fbs_str(t.fbs))
+			fullscreen_plane_subtest(&t);
+	TEST_MODE_ITER_END
+
+	TEST_MODE_ITER_BEGIN(t)
+		if (t.screen != SCREEN_PRIM)
+			continue;
+		if (!opt.show_hidden && t.fbs != FBS_SINGLE)
+			continue;
+
+		igt_subtest_f("%s-%s-%s-%s-multidraw-%s",
+			      feature_str(t.feature),
+			      pipes_str(t.pipes),
+			      plane_str(t.plane),
+			      fbs_str(t.fbs),
+			      igt_draw_get_method_name(t.method))
+			multidraw_subtest(&t);
+	TEST_MODE_ITER_END
+
+	/*
+	 * TODO: ideas for subtests:
+	 * - Add a new enum to struct test_mode that allows us to specify the
+	 *   BPP/depth configuration.
+	 */
+
+	igt_fixture
+		teardown_environment();
+
+	igt_exit();
+}