[Intel-gfx] [PATCH igt] tests: add kms_frontbuffer_tracking

Paulo Zanoni przanoni at gmail.com
Mon May 25 14:40:58 PDT 2015


From: Paulo Zanoni <paulo.r.zanoni at 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 small 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.

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

Some interesting details:

On a tree from 2015, May 06, I get 18 failures and 357 successes. The
only FBC failures I get are from the MMAP_WC draw method, which is
maybe lacking proper frontbuffer tracking support on the Kernel. The
other 16 failures are for PSR, most of them with GTT MMAPs.

But if I use a tree from 2015, May 25, every single PSR test fails. We
need to investigate that. Maybe if I had finished this earlier we
would have an automated bisect. This also highlights the importance of
testing stuff even when they are disabled by default! I plan to patch
the other tests to do the same thing.

I am also seeing some FBC failures that happen right after booting. It
seems that the very first tests fail until I run a test that uses the
render ring.  I'll have to investigate this.

I am also seeing some occasional corruptions on my eDP panel, but on
these cases both the pipe and sink CRC tests succeed! Maybe this is
some weird panel malfunction caused by the fact that we're doing tons
and tons of modesets on the panel.

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 at 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();
+}
-- 
2.1.4



More information about the Intel-gfx mailing list