diff options
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefile.sources | 1 | ||||
-rw-r--r-- | tests/kms_frontbuffer_tracking.c | 2225 |
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, + ¶ms->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, ®ion, 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 ¶ms->fb; + case PLANE_CUR: + return ¶ms->cursor; + case PLANE_SPR: + return ¶ms->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(¶ms->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(¶ms->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 : ¶ms->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(¶ms->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(¶ms->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(); +} |