summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.sources1
-rw-r--r--tests/kms_frontbuffer_tracking.c2225
3 files changed, 2227 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore
index f816ded9..c17264c2 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 d2a44e84..551c6007 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 00000000..32397794
--- /dev/null
+++ b/tests/kms_frontbuffer_tracking.c
@@ -0,0 +1,2225 @@
+/*
+ * 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(const char *msg)
+{
+ igt_info("%s Press enter...\n", msg);
+ 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("Paused before assertions."); \
+ \
+ /* 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 no 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("Paused after assertions."); \
+} 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++) {
+ igt_debug("Drawing rect %d\n", 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();
+}