/* * 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 * */ #include "igt.h" #include "igt_sysfs.h" #include "igt_psr.h" #include #include #include #include #include IGT_TEST_DESCRIPTION("Test the Kernel's frontbuffer tracking mechanism and " "its related features: FBC, PSR and DRRS"); /* * 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_INDIVIDUAL = 0, FBS_SHARED, FBS_COUNT, } fbs; /* Which features are we going to test now? This is a mask! * FEATURE_DEFAULT is a special value which instruct the test to just * keep what's already enabled by default in the Kernel. */ enum { FEATURE_NONE = 0, FEATURE_FBC = 1, FEATURE_PSR = 2, FEATURE_DRRS = 4, FEATURE_COUNT = 8, FEATURE_DEFAULT = 8, } feature; /* Possible pixel formats. We just use FORMAT_DEFAULT for most tests and * only test a few things on the other formats. */ enum pixel_format { FORMAT_RGB888 = 0, FORMAT_RGB565, FORMAT_RGB101010, FORMAT_COUNT, FORMAT_DEFAULT = FORMAT_RGB888, } format; /* There are multiple APIs where we can do the equivalent of a page flip * and they exercise slightly different codepaths inside the Kernel. */ enum flip_type { FLIP_PAGEFLIP, FLIP_MODESET, FLIP_PLANES, FLIP_COUNT, } flip; enum igt_draw_method method; }; enum color { COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_SCND_BG, COLOR_PRIM_BG = COLOR_BLUE, COLOR_OFFSCREEN_BG = COLOR_SCND_BG, }; struct rect { int x; int y; int w; int h; uint32_t color; }; struct { int fd; int debugfs; igt_display_t display; drm_intel_bufmgr *bufmgr; } drm; struct { bool can_test; bool supports_last_action; struct timespec last_action; } fbc = { .can_test = false, .supports_last_action = false, }; struct { bool can_test; } psr = { .can_test = false, }; #define MAX_DRRS_STATUS_BUF_LEN 256 struct { bool can_test; } drrs = { .can_test = false, }; igt_pipe_crc_t *pipe_crc; igt_crc_t *wanted_crc; struct { bool initialized; igt_crc_t crc; } blue_crcs[FORMAT_COUNT]; /* 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 { igt_plane_t *plane; struct igt_fb *fb; int x; int y; int w; int h; }; struct draw_pattern_info { bool frames_stack; int n_rects; struct rect (*get_rect)(struct fb_region *fb, int r); bool initialized[FORMAT_COUNT]; igt_crc_t *crcs[FORMAT_COUNT]; }; /* 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_pipes; int shared_fb_x_offset; int shared_fb_y_offset; uint64_t tiling; } 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_pipes = PIPE_COUNT, .shared_fb_x_offset = 248, .shared_fb_y_offset = 500, .tiling = LOCAL_I915_FORMAT_MOD_X_TILED, }; struct modeset_params { enum pipe pipe; igt_output_t *output; drmModeModeInfo mode_copy, *mode; struct fb_region primary; 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 screen_fbs { bool initialized; 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[FORMAT_COUNT]; struct { pthread_t thread; bool stop; uint32_t handle; uint32_t size; uint32_t stride; int width; int height; uint32_t color; int bpp; uint32_t tiling; } busy_thread = { .stop = true, }; static const drmModeModeInfo *get_connector_smallest_mode(igt_output_t *output) { drmModeConnector *c = output->config.connector; const drmModeModeInfo *smallest = NULL; int i; for (i = 0; i < c->count_modes; i++) { drmModeModeInfo *mode = &c->modes[i]; if (!smallest) smallest = mode; if (mode->hdisplay * mode->vdisplay < smallest->hdisplay * smallest->vdisplay) smallest = mode; } if (c->connector_type == DRM_MODE_CONNECTOR_eDP) smallest = igt_std_1024_mode_get(); return smallest; } static const drmModeModeInfo *connector_get_mode(igt_output_t *output) { const drmModeModeInfo *mode = NULL; if (opt.small_modes) mode = get_connector_smallest_mode(output); else mode = &output->config.default_mode; /* On HSW the CRC WA is so awful that it makes you think everything is * bugged. */ if (IS_HASWELL(intel_get_drm_devid(drm.fd)) && output->config.connector->connector_type == DRM_MODE_CONNECTOR_eDP) mode = igt_std_1024_mode_get(); return mode; } static void init_mode_params(struct modeset_params *params, igt_output_t *output, enum pipe pipe) { const drmModeModeInfo *mode; igt_output_override_mode(output, NULL); mode = connector_get_mode(output); params->pipe = pipe; params->output = output; params->mode_copy = *mode; params->mode = ¶ms->mode_copy; params->primary.plane = igt_pipe_get_plane_type(&drm.display.pipes[pipe], DRM_PLANE_TYPE_PRIMARY); params->primary.fb = NULL; params->primary.x = 0; params->primary.y = 0; params->primary.w = mode->hdisplay; params->primary.h = mode->vdisplay; params->cursor.plane = igt_pipe_get_plane_type(&drm.display.pipes[pipe], DRM_PLANE_TYPE_CURSOR); params->cursor.fb = NULL; params->cursor.x = 0; params->cursor.y = 0; params->cursor.w = 64; params->cursor.h = 64; params->sprite.plane = igt_pipe_get_plane_type(&drm.display.pipes[pipe], DRM_PLANE_TYPE_OVERLAY); igt_require(params->sprite.plane); params->sprite.fb = NULL; params->sprite.x = 0; params->sprite.y = 0; params->sprite.w = 64; params->sprite.h = 64; } static bool find_connector(bool edp_only, bool pipe_a, igt_output_t *forbidden_output, enum pipe forbidden_pipe, igt_output_t **ret_output, enum pipe *ret_pipe) { igt_output_t *output; enum pipe pipe; for_each_pipe_with_valid_output(&drm.display, pipe, output) { drmModeConnectorPtr c = output->config.connector; if (edp_only && c->connector_type != DRM_MODE_CONNECTOR_eDP) continue; if (pipe_a && pipe != PIPE_A) continue; if (output == forbidden_output || pipe == forbidden_pipe) continue; if (c->connector_type == DRM_MODE_CONNECTOR_eDP && opt.no_edp) continue; *ret_output = output; *ret_pipe = pipe; return true; } return false; } static bool init_modeset_cached_params(void) { igt_output_t *prim_output = NULL, *scnd_output = NULL; enum pipe prim_pipe, scnd_pipe; /* * We have this problem where PSR is only present on eDP monitors and * FBC is only present on pipe A for some platforms. So we search first * for the ideal case of eDP supporting pipe A, and try the less optimal * configs later, sacrificing one of the features. * TODO: refactor the code in a way that allows us to have different * sets of prim/scnd structs for different features. */ find_connector(true, true, NULL, PIPE_NONE, &prim_output, &prim_pipe); if (!prim_output) find_connector(true, false, NULL, PIPE_NONE, &prim_output, &prim_pipe); if (!prim_output) find_connector(false, true, NULL, PIPE_NONE, &prim_output, &prim_pipe); if (!prim_output) find_connector(false, false, NULL, PIPE_NONE, &prim_output, &prim_pipe); if (!prim_output) return false; find_connector(false, false, prim_output, prim_pipe, &scnd_output, &scnd_pipe); init_mode_params(&prim_mode_params, prim_output, prim_pipe); if (!scnd_output) { scnd_mode_params.pipe = PIPE_NONE; scnd_mode_params.output = NULL; return true; } init_mode_params(&scnd_mode_params, scnd_output, scnd_pipe); return true; } static void create_fb(enum pixel_format pformat, int width, int height, uint64_t tiling, int plane, struct igt_fb *fb) { uint32_t format; uint64_t size; unsigned int stride; uint64_t tiling_for_size; switch (pformat) { case FORMAT_RGB888: if (plane == PLANE_CUR) format = DRM_FORMAT_ARGB8888; else format = DRM_FORMAT_XRGB8888; break; case FORMAT_RGB565: /* Only the primary plane supports 16bpp! */ if (plane == PLANE_PRI) format = DRM_FORMAT_RGB565; else if (plane == PLANE_CUR) format = DRM_FORMAT_ARGB8888; else format = DRM_FORMAT_XRGB8888; break; case FORMAT_RGB101010: if (plane == PLANE_PRI) format = DRM_FORMAT_XRGB2101010; else if (plane == PLANE_CUR) format = DRM_FORMAT_ARGB8888; else format = DRM_FORMAT_XRGB8888; break; default: igt_assert(false); } /* We want all frontbuffers with the same width/height/format to have * the same size regardless of tiling since we want to properly exercise * the Kernel's specific tiling-checking code paths without accidentally * hitting size-checking ones first. */ if (plane == PLANE_CUR) tiling_for_size = LOCAL_DRM_FORMAT_MOD_NONE; else tiling_for_size = opt.tiling; igt_calc_fb_size(drm.fd, width, height, format, tiling_for_size, &size, &stride); igt_create_fb_with_bo_size(drm.fd, width, height, format, tiling, IGT_COLOR_YCBCR_BT709, IGT_COLOR_YCBCR_LIMITED_RANGE, fb, size, stride); } static uint32_t pick_color(struct igt_fb *fb, enum color ecolor) { uint32_t color, r, g, b, b2, a; bool alpha = false; switch (fb->drm_format) { case DRM_FORMAT_RGB565: a = 0x0; r = 0x1F << 11; g = 0x3F << 5; b = 0x1F; b2 = 0x10; break; case DRM_FORMAT_ARGB8888: alpha = true; case DRM_FORMAT_XRGB8888: a = 0xFF << 24; r = 0xFF << 16; g = 0xFF << 8; b = 0xFF; b2 = 0x80; break; case DRM_FORMAT_ARGB2101010: alpha = true; case DRM_FORMAT_XRGB2101010: a = 0x3 << 30; r = 0x3FF << 20; g = 0x3FF << 10; b = 0x3FF; b2 = 0x200; break; default: igt_assert(false); } switch (ecolor) { case COLOR_RED: color = r; break; case COLOR_GREEN: color = g; break; case COLOR_BLUE: color = b; break; case COLOR_MAGENTA: color = r | b; break; case COLOR_CYAN: color = g | b; break; case COLOR_SCND_BG: color = b2; break; default: igt_assert(false); } if (alpha) color |= a; return color; } static void fill_fb(struct igt_fb *fb, enum color ecolor) { igt_draw_fill_fb(drm.fd, fb, pick_color(fb, ecolor)); } /* * This is how the prim, scnd and offscreen FBs should be positioned inside the * shared FB. The prim buffer starts at the X and Y offsets defined by * opt.shared_fb_{x,y}_offset, then scnd starts at the same X pixel offset, * right after prim ends on the Y axis, then the offscreen fb starts after scnd * ends. Just like the picture: * * +-------------------------+ * | shared fb | * | +------------------+ | * | | prim | | * | | | | * | | | | * | | | | * | +------------------+--+ * | | scnd | * | | | * | | | * | +---------------+-----+ * | | offscreen | | * | | | | * | | | | * +---+---------------+-----+ * * We do it vertically instead of the more common horizontal case in order to * avoid super huge strides not supported by FBC. */ static void create_shared_fb(enum pixel_format format) { int prim_w, prim_h, scnd_w, scnd_h, offs_w, offs_h, big_w, big_h; struct screen_fbs *s = &fbs[format]; prim_w = prim_mode_params.mode->hdisplay; prim_h = prim_mode_params.mode->vdisplay; if (scnd_mode_params.output) { 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; if (scnd_w > big_w) big_w = scnd_w; if (offs_w > big_w) big_w = offs_w; big_w += opt.shared_fb_x_offset; big_h = prim_h + scnd_h + offs_h + opt.shared_fb_y_offset; create_fb(format, big_w, big_h, opt.tiling, PLANE_PRI, &s->big); } static void destroy_fbs(enum pixel_format format) { struct screen_fbs *s = &fbs[format]; if (!s->initialized) return; if (scnd_mode_params.output) { igt_remove_fb(drm.fd, &s->scnd_pri); igt_remove_fb(drm.fd, &s->scnd_cur); igt_remove_fb(drm.fd, &s->scnd_spr); } igt_remove_fb(drm.fd, &s->prim_pri); igt_remove_fb(drm.fd, &s->prim_cur); igt_remove_fb(drm.fd, &s->prim_spr); igt_remove_fb(drm.fd, &s->offscreen); igt_remove_fb(drm.fd, &s->big); } static void create_fbs(enum pixel_format format) { struct screen_fbs *s = &fbs[format]; if (s->initialized) destroy_fbs(format); s->initialized = true; create_fb(format, prim_mode_params.mode->hdisplay, prim_mode_params.mode->vdisplay, opt.tiling, PLANE_PRI, &s->prim_pri); create_fb(format, prim_mode_params.cursor.w, prim_mode_params.cursor.h, LOCAL_DRM_FORMAT_MOD_NONE, PLANE_CUR, &s->prim_cur); create_fb(format, prim_mode_params.sprite.w, prim_mode_params.sprite.h, opt.tiling, PLANE_SPR, &s->prim_spr); create_fb(format, offscreen_fb.w, offscreen_fb.h, opt.tiling, PLANE_PRI, &s->offscreen); create_shared_fb(format); if (!scnd_mode_params.output) return; create_fb(format, scnd_mode_params.mode->hdisplay, scnd_mode_params.mode->vdisplay, opt.tiling, PLANE_PRI, &s->scnd_pri); create_fb(format, scnd_mode_params.cursor.w, scnd_mode_params.cursor.h, LOCAL_DRM_FORMAT_MOD_NONE, PLANE_CUR, &s->scnd_cur); create_fb(format, scnd_mode_params.sprite.w, scnd_mode_params.sprite.h, opt.tiling, PLANE_SPR, &s->scnd_spr); } static void __set_prim_plane_for_params(struct modeset_params *params) { igt_plane_set_fb(params->primary.plane, params->primary.fb); igt_plane_set_position(params->primary.plane, 0, 0); igt_plane_set_size(params->primary.plane, params->mode->hdisplay, params->mode->vdisplay); igt_fb_set_position(params->primary.fb, params->primary.plane, params->primary.x, params->primary.y); igt_fb_set_size(params->primary.fb, params->primary.plane, params->mode->hdisplay, params->mode->vdisplay); } static void __set_mode_for_params(struct modeset_params *params) { igt_output_override_mode(params->output, params->mode); igt_output_set_pipe(params->output, params->pipe); __set_prim_plane_for_params(params); } static void set_mode_for_params(struct modeset_params *params) { __set_mode_for_params(params); igt_display_commit(&drm.display); } static void __debugfs_read(const char *param, char *buf, int len) { len = igt_debugfs_simple_read(drm.debugfs, param, buf, len); if (len < 0) igt_assert_eq(len, -ENODEV); } static int __debugfs_write(const char *param, char *buf, int len) { return igt_sysfs_write(drm.debugfs, param, buf, len - 1); } #define debugfs_read(p, arr) __debugfs_read(p, arr, sizeof(arr)) #define debugfs_write(p, arr) __debugfs_write(p, arr, sizeof(arr)) static char last_fbc_buf[128]; static bool fbc_is_enabled(int lvl) { char buf[128]; bool print = true; debugfs_read("i915_fbc_status", buf); if (lvl != IGT_LOG_DEBUG) last_fbc_buf[0] = '\0'; else if (strcmp(last_fbc_buf, buf)) strcpy(last_fbc_buf, buf); else print = false; if (print) igt_log(IGT_LOG_DOMAIN, lvl, "fbc_is_enabled()?\n%s", buf); return strstr(buf, "FBC enabled\n"); } static void drrs_set(unsigned int val) { char buf[2]; int ret; igt_debug("Manually %sabling DRRS. %u\n", val ? "en" : "dis", val); snprintf(buf, sizeof(buf), "%d", val); ret = debugfs_write("i915_drrs_ctl", buf); /* * drrs_enable() is called on DRRS capable platform only, * whereas drrs_disable() is called on all platforms. * So handle the failure of debugfs_write only for drrs_enable(). */ if (val) igt_assert_f(ret == (sizeof(buf) - 1), "debugfs_write failed"); } static bool is_drrs_high(void) { char buf[MAX_DRRS_STATUS_BUF_LEN]; debugfs_read("i915_drrs_status", buf); return strstr(buf, "DRRS_HIGH_RR"); } static bool is_drrs_low(void) { char buf[MAX_DRRS_STATUS_BUF_LEN]; debugfs_read("i915_drrs_status", buf); return strstr(buf, "DRRS_LOW_RR"); } static bool is_drrs_supported(void) { char buf[MAX_DRRS_STATUS_BUF_LEN]; debugfs_read("i915_drrs_status", buf); return strstr(buf, "DRRS Supported: Yes"); } static bool is_drrs_inactive(void) { char buf[MAX_DRRS_STATUS_BUF_LEN]; debugfs_read("i915_drrs_status", buf); if (strstr(buf, "DRRS_State: ")) return false; return true; } static void drrs_print_status(void) { char buf[MAX_DRRS_STATUS_BUF_LEN]; debugfs_read("i915_drrs_status", buf); igt_info("DRRS STATUS :\n%s\n", buf); } static struct timespec fbc_get_last_action(void) { struct timespec ret = { 0, 0 }; char buf[128]; char *action; ssize_t n_read; debugfs_read("i915_fbc_status", 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[128]; char *action; debugfs_read("i915_fbc_status", 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[128]; debugfs_read("i915_fbc_status", buf); return strstr(buf, "\nCompressing: yes\n") != NULL; } static bool fbc_wait_for_compression(void) { return igt_wait(fbc_is_compressing(), 2000, 1); } static bool fbc_not_enough_stolen(void) { char buf[128]; debugfs_read("i915_fbc_status", buf); return strstr(buf, "FBC disabled: not enough stolen memory\n"); } static bool fbc_stride_not_supported(void) { char buf[128]; debugfs_read("i915_fbc_status", buf); return strstr(buf, "FBC disabled: framebuffer stride not supported\n"); } static bool fbc_mode_too_large(void) { char buf[128]; debugfs_read("i915_fbc_status", buf); return strstr(buf, "FBC disabled: mode too large for compression\n"); } static bool fbc_wait_until_enabled(void) { last_fbc_buf[0] = '\0'; return igt_wait(fbc_is_enabled(IGT_LOG_DEBUG), 2000, 1); } static bool drrs_wait_until_rr_switch_to_low(void) { return igt_wait(is_drrs_low(), 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 drrs_enable() drrs_set(1) #define drrs_disable() drrs_set(0) 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 = pick_color(fb->fb, COLOR_GREEN); 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 = pick_color(fb->fb, COLOR_RED); 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 = pick_color(fb->fb, COLOR_MAGENTA); break; case 3: rect.x = fb->w - 1; rect.y = fb->h - 1; rect.w = 1; rect.h = 1; rect.color = pick_color(fb->fb, COLOR_CYAN); 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 = pick_color(fb->fb, COLOR_GREEN); break; case 1: rect.x = 31; rect.y = 31; rect.w = 31; rect.h = 31; rect.color = pick_color(fb->fb, COLOR_RED); break; case 2: rect.x = 16; rect.y = 16; rect.w = 32; rect.h = 32; rect.color = pick_color(fb->fb, COLOR_MAGENTA); break; case 3: rect.color = pick_color(fb->fb, COLOR_CYAN); 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 = pick_color(fb->fb, COLOR_GREEN); 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_eq(r, 0); rect.x = 0; rect.y = 0; rect.w = fb->w; rect.h = fb->h; rect.color = pick_color(fb->fb, COLOR_GREEN); return rect; } static void fb_dirty_ioctl(struct fb_region *fb, struct rect *rect) { int rc; drmModeClip clip = { .x1 = rect->x, .x2 = rect->x + rect->w, .y1 = rect->y, .y2 = rect->y + rect->h, }; rc = drmModeDirtyFB(drm.fd, fb->fb->fb_id, &clip, 1); igt_assert(rc == 0 || rc == -ENOSYS); } 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); fb_dirty_ioctl(fb, &rect); } 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 fill_fb_region(struct fb_region *region, enum color ecolor) { uint32_t color = pick_color(region->fb, ecolor); igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, region->fb, IGT_DRAW_BLT, region->x, region->y, region->w, region->h, color); } static void unset_all_crtcs(void) { igt_display_reset(&drm.display); igt_display_commit(&drm.display); } static bool disable_features(const struct test_mode *t) { if (t->feature == FEATURE_DEFAULT) return false; fbc_disable(); drrs_disable(); return psr.can_test ? psr_disable(drm.debugfs) : false; } static void *busy_thread_func(void *data) { while (!busy_thread.stop) igt_draw_rect(drm.fd, drm.bufmgr, NULL, busy_thread.handle, busy_thread.size, busy_thread.stride, busy_thread.tiling, IGT_DRAW_BLT, 0, 0, busy_thread.width, busy_thread.height, busy_thread.color, busy_thread.bpp); pthread_exit(0); } static void start_busy_thread(struct igt_fb *fb) { int rc; igt_assert(busy_thread.stop == true); busy_thread.stop = false; busy_thread.handle = fb->gem_handle; busy_thread.size = fb->size; busy_thread.stride = fb->strides[0]; busy_thread.width = fb->width; busy_thread.height = fb->height; busy_thread.color = pick_color(fb, COLOR_PRIM_BG); busy_thread.bpp = igt_drm_format_to_bpp(fb->drm_format); busy_thread.tiling = igt_fb_mod_to_tiling(fb->modifier); rc = pthread_create(&busy_thread.thread, NULL, busy_thread_func, NULL); igt_assert_eq(rc, 0); } static void stop_busy_thread(void) { if (!busy_thread.stop) { busy_thread.stop = true; igt_assert(pthread_join(busy_thread.thread, NULL) == 0); } } static void print_crc(const char *str, igt_crc_t *crc) { char *pipe_str; pipe_str = igt_crc_to_string(crc); igt_debug("%s pipe:[%s]\n", str, pipe_str); free(pipe_str); } static void collect_crc(igt_crc_t *crc) { igt_pipe_crc_collect_crc(pipe_crc, crc); } static void init_blue_crc(enum pixel_format format) { struct igt_fb blue; if (blue_crcs[format].initialized) return; create_fb(format, prim_mode_params.mode->hdisplay, prim_mode_params.mode->vdisplay, opt.tiling, PLANE_PRI, &blue); fill_fb(&blue, COLOR_PRIM_BG); igt_output_set_pipe(prim_mode_params.output, prim_mode_params.pipe); igt_output_override_mode(prim_mode_params.output, prim_mode_params.mode); igt_plane_set_fb(prim_mode_params.primary.plane, &blue); igt_display_commit(&drm.display); if (!pipe_crc) { pipe_crc = igt_pipe_crc_new(drm.fd, prim_mode_params.pipe, INTEL_PIPE_CRC_SOURCE_AUTO); igt_assert(pipe_crc); } collect_crc(&blue_crcs[format].crc); print_crc("Blue CRC: ", &blue_crcs[format].crc); igt_display_reset(&drm.display); igt_remove_fb(drm.fd, &blue); blue_crcs[format].initialized = true; } static void init_crcs(enum pixel_format format, struct draw_pattern_info *pattern) { int r, r_; struct igt_fb tmp_fbs[pattern->n_rects]; if (pattern->initialized[format]) return; pattern->crcs[format] = calloc(pattern->n_rects, sizeof(*(pattern->crcs[format]))); for (r = 0; r < pattern->n_rects; r++) create_fb(format, prim_mode_params.mode->hdisplay, prim_mode_params.mode->vdisplay, opt.tiling, PLANE_PRI, &tmp_fbs[r]); for (r = 0; r < pattern->n_rects; r++) fill_fb(&tmp_fbs[r], COLOR_PRIM_BG); 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); } igt_output_set_pipe(prim_mode_params.output, prim_mode_params.pipe); igt_output_override_mode(prim_mode_params.output, prim_mode_params.mode); for (r = 0; r < pattern->n_rects; r++) { igt_plane_set_fb(prim_mode_params.primary.plane, &tmp_fbs[r]); igt_display_commit(&drm.display); collect_crc(&pattern->crcs[format][r]); } for (r = 0; r < pattern->n_rects; r++) { igt_debug("Rect %d CRC:", r); print_crc("", &pattern->crcs[format][r]); } igt_display_reset(&drm.display); for (r = 0; r < pattern->n_rects; r++) igt_remove_fb(drm.fd, &tmp_fbs[r]); pattern->initialized[format] = true; } static void setup_drm(void) { drm.fd = drm_open_driver_master(DRIVER_INTEL); drm.debugfs = igt_debugfs_dir(drm.fd); kmstest_set_vt_graphics_mode(); igt_display_require(&drm.display, 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) { drm_intel_bufmgr_destroy(drm.bufmgr); igt_display_fini(&drm.display); 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(FORMAT_DEFAULT); } static void teardown_modeset(void) { enum pixel_format f; for (f = 0; f < FORMAT_COUNT; f++) destroy_fbs(f); } static void setup_crcs(void) { enum pixel_format f; for (f = 0; f < FORMAT_COUNT; f++) blue_crcs[f].initialized = false; pattern1.frames_stack = true; pattern1.n_rects = 4; pattern1.get_rect = pat1_get_rect; for (f = 0; f < FORMAT_COUNT; f++) { pattern1.initialized[f] = false; pattern1.crcs[f] = NULL; } pattern2.frames_stack = true; pattern2.n_rects = 4; pattern2.get_rect = pat2_get_rect; for (f = 0; f < FORMAT_COUNT; f++) { pattern2.initialized[f] = false; pattern2.crcs[f] = NULL; } pattern3.frames_stack = false; pattern3.n_rects = 5; pattern3.get_rect = pat3_get_rect; for (f = 0; f < FORMAT_COUNT; f++) { pattern3.initialized[f] = false; pattern3.crcs[f] = NULL; } pattern4.frames_stack = false; pattern4.n_rects = 1; pattern4.get_rect = pat4_get_rect; for (f = 0; f < FORMAT_COUNT; f++) { pattern4.initialized[f] = false; pattern4.crcs[f] = NULL; } } static void teardown_crcs(void) { enum pixel_format f; for (f = 0; f < FORMAT_COUNT; f++) { if (pattern1.crcs[f]) free(pattern1.crcs[f]); if (pattern2.crcs[f]) free(pattern2.crcs[f]); if (pattern3.crcs[f]) free(pattern3.crcs[f]); if (pattern4.crcs[f]) free(pattern4.crcs[f]); } igt_pipe_crc_free(pipe_crc); } static bool fbc_supported_on_chipset(void) { char buf[128]; debugfs_read("i915_fbc_status", buf); if (*buf == '\0') return false; return !strstr(buf, "FBC unsupported on this chipset\n"); } static void setup_fbc(void) { int devid = intel_get_drm_devid(drm.fd); if (!fbc_supported_on_chipset()) { igt_info("Can't test FBC: not supported on this chipset\n"); return; } /* * While some platforms do allow FBC on pipes B/C, this test suite * is not prepared for that yet. * TODO: solve this. */ if (prim_mode_params.pipe != PIPE_A) { igt_info("Can't test FBC: primary connector doesn't support " "pipe A\n"); return; } /* Early Generations are not able to report compression status. */ if (!AT_LEAST_GEN(devid, 7)) opt.fbc_check_compression = false; fbc.can_test = true; fbc_setup_last_action(); } static void teardown_fbc(void) { } static void setup_psr(void) { if (prim_mode_params.output->config.connector->connector_type != DRM_MODE_CONNECTOR_eDP) { igt_info("Can't test PSR: no usable eDP screen.\n"); return; } if (!psr_sink_support(drm.debugfs, PSR_MODE_1)) { igt_info("Can't test PSR: not supported by sink.\n"); return; } psr.can_test = true; } static void teardown_psr(void) { } static void setup_drrs(void) { if (prim_mode_params.output->config.connector->connector_type != DRM_MODE_CONNECTOR_eDP) { igt_info("Can't test DRRS: no usable eDP screen.\n"); return; } if (!is_drrs_supported()) { igt_info("Can't test DRRS: Not supported.\n"); return; } drrs.can_test = true; } static void setup_environment(void) { setup_drm(); setup_modeset(); setup_fbc(); setup_psr(); setup_drrs(); setup_crcs(); } static void teardown_environment(void) { stop_busy_thread(); teardown_crcs(); teardown_psr(); teardown_fbc(); teardown_modeset(); teardown_drm(); } static void wait_user(int step, const char *msg) { if (opt.step < step) return; 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->primary; 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 DONT_ASSERT_FEATURE_STATUS (1 << 1) #define DONT_ASSERT_FBC_STATUS (1 << 12) #define FBC_ASSERT_FLAGS (0xF << 2) #define ASSERT_FBC_ENABLED (1 << 2) #define ASSERT_FBC_DISABLED (1 << 3) #define ASSERT_LAST_ACTION_CHANGED (1 << 4) #define ASSERT_NO_ACTION_CHANGE (1 << 5) #define PSR_ASSERT_FLAGS (3 << 6) #define ASSERT_PSR_ENABLED (1 << 6) #define ASSERT_PSR_DISABLED (1 << 7) #define DRRS_ASSERT_FLAGS (7 << 8) #define ASSERT_DRRS_HIGH (1 << 8) #define ASSERT_DRRS_LOW (1 << 9) #define ASSERT_DRRS_INACTIVE (1 << 10) #define ASSERT_NO_IDLE_GPU (1 << 11) static int adjust_assertion_flags(const struct test_mode *t, int flags) { if (!(flags & DONT_ASSERT_FEATURE_STATUS)) { if (!(flags & ASSERT_FBC_DISABLED)) flags |= ASSERT_FBC_ENABLED; if (!(flags & ASSERT_PSR_DISABLED)) flags |= ASSERT_PSR_ENABLED; if (!((flags & ASSERT_DRRS_LOW) || (flags & ASSERT_DRRS_INACTIVE))) flags |= ASSERT_DRRS_HIGH; } if ((t->feature & FEATURE_FBC) == 0 || (flags & DONT_ASSERT_FBC_STATUS)) flags &= ~FBC_ASSERT_FLAGS; if ((t->feature & FEATURE_PSR) == 0) flags &= ~PSR_ASSERT_FLAGS; if ((t->feature & FEATURE_DRRS) == 0) flags &= ~DRRS_ASSERT_FLAGS; return flags; } static void do_crc_assertions(int flags) { igt_crc_t crc; if (!opt.check_crc || (flags & DONT_ASSERT_CRC)) return; collect_crc(&crc); print_crc("Calculated CRC:", &crc); igt_assert(wanted_crc); igt_assert_crc_equal(&crc, wanted_crc); } static void do_status_assertions(int flags) { if (!opt.check_status) { /* Make sure we settle before continuing. */ sleep(1); return; } if (flags & ASSERT_DRRS_HIGH) { if (!is_drrs_high()) { drrs_print_status(); igt_assert_f(false, "DRRS HIGH\n"); } } else if (flags & ASSERT_DRRS_LOW) { if (!drrs_wait_until_rr_switch_to_low()) { drrs_print_status(); igt_assert_f(false, "DRRS LOW\n"); } } else if (flags & ASSERT_DRRS_INACTIVE) { if (!is_drrs_inactive()) { drrs_print_status(); igt_assert_f(false, "DRRS INACTIVE\n"); } } if (flags & ASSERT_FBC_ENABLED) { igt_require(!fbc_not_enough_stolen()); igt_require(!fbc_stride_not_supported()); igt_require(!fbc_mode_too_large()); if (!fbc_wait_until_enabled()) { igt_assert_f(fbc_is_enabled(IGT_LOG_WARN), "FBC disabled\n"); } if (opt.fbc_check_compression) igt_assert(fbc_wait_for_compression()); } else if (flags & ASSERT_FBC_DISABLED) { igt_assert(!fbc_wait_until_enabled()); } if (flags & ASSERT_PSR_ENABLED) igt_assert_f(psr_wait_entry(drm.debugfs, PSR_MODE_1), "PSR still disabled\n"); else if (flags & ASSERT_PSR_DISABLED) igt_assert_f(psr_wait_update(drm.debugfs, PSR_MODE_1), "PSR still enabled\n"); } static void __do_assertions(const struct test_mode *t, int flags, int line) { flags = adjust_assertion_flags(t, flags); /* Make sure any submitted rendering is now idle. */ if (!(flags & ASSERT_NO_IDLE_GPU)) gem_quiescent_gpu(drm.fd); igt_debug("checking asserts in line %i\n", line); wait_user(2, "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); do_status_assertions(flags); /* 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()); } wait_user(1, "Paused after assertions."); } #define do_assertions(__flags) __do_assertions(t, (__flags), __LINE__) static void enable_prim_screen_and_wait(const struct test_mode *t) { fill_fb_region(&prim_mode_params.primary, COLOR_PRIM_BG); set_mode_for_params(&prim_mode_params); wanted_crc = &blue_crcs[t->format].crc; fbc_update_last_action(); do_assertions(ASSERT_NO_ACTION_CHANGE); } static void enable_both_screens_and_wait(const struct test_mode *t) { fill_fb_region(&prim_mode_params.primary, COLOR_PRIM_BG); fill_fb_region(&scnd_mode_params.primary, COLOR_SCND_BG); __set_mode_for_params(&prim_mode_params); __set_mode_for_params(&scnd_mode_params); igt_display_commit2(&drm.display, drm.display.is_atomic ? COMMIT_ATOMIC : COMMIT_LEGACY); wanted_crc = &blue_crcs[t->format].crc; fbc_update_last_action(); do_assertions(ASSERT_NO_ACTION_CHANGE); } static void set_region_for_test(const struct test_mode *t, struct fb_region *reg) { fill_fb_region(reg, COLOR_PRIM_BG); igt_plane_set_fb(reg->plane, reg->fb); igt_plane_set_position(reg->plane, 0, 0); igt_plane_set_size(reg->plane, reg->w, reg->h); igt_fb_set_size(reg->fb, reg->plane, reg->w, reg->h); igt_display_commit(&drm.display); do_assertions(ASSERT_NO_ACTION_CHANGE); } static bool enable_features_for_test(const struct test_mode *t) { bool ret = false; if (t->feature == FEATURE_DEFAULT) return false; if (t->feature & FEATURE_FBC) fbc_enable(); if (t->feature & FEATURE_PSR) ret = psr_enable(drm.debugfs, PSR_MODE_1); if (t->feature & FEATURE_DRRS) drrs_enable(); return ret; } static void check_test_requirements(const struct test_mode *t) { if (t->pipes == PIPE_DUAL) igt_require_f(scnd_mode_params.output, "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 (t->feature & FEATURE_DRRS) igt_require_f(drrs.can_test, "Can't test DRRS with the current outputs\n"); /* * In kernel, When PSR is enabled, DRRS will be disabled. So If a test * case needs DRRS + PSR enabled, that will be skipped. */ igt_require_f(!((t->feature & FEATURE_PSR) && (t->feature & FEATURE_DRRS)), "Can't test PSR and DRRS together\n"); if (opt.only_pipes != PIPE_COUNT) igt_require(t->pipes == opt.only_pipes); } static void set_crtc_fbs(const struct test_mode *t) { struct screen_fbs *s = &fbs[t->format]; create_fbs(t->format); switch (t->fbs) { case FBS_INDIVIDUAL: prim_mode_params.primary.fb = &s->prim_pri; scnd_mode_params.primary.fb = &s->scnd_pri; offscreen_fb.fb = &s->offscreen; prim_mode_params.primary.x = 0; scnd_mode_params.primary.x = 0; offscreen_fb.x = 0; prim_mode_params.primary.y = 0; scnd_mode_params.primary.y = 0; offscreen_fb.y = 0; break; case FBS_SHARED: /* Please see the comment at the top of create_shared_fb(). */ prim_mode_params.primary.fb = &s->big; scnd_mode_params.primary.fb = &s->big; offscreen_fb.fb = &s->big; prim_mode_params.primary.x = opt.shared_fb_x_offset; scnd_mode_params.primary.x = opt.shared_fb_x_offset; offscreen_fb.x = opt.shared_fb_x_offset; prim_mode_params.primary.y = opt.shared_fb_y_offset; scnd_mode_params.primary.y = prim_mode_params.primary.y + prim_mode_params.primary.h; offscreen_fb.y = scnd_mode_params.primary.y + scnd_mode_params.primary.h; break; default: igt_assert(false); } prim_mode_params.cursor.fb = &s->prim_cur; prim_mode_params.sprite.fb = &s->prim_spr; scnd_mode_params.cursor.fb = &s->scnd_cur; scnd_mode_params.sprite.fb = &s->scnd_spr; } static void prepare_subtest_data(const struct test_mode *t, struct draw_pattern_info *pattern) { bool need_modeset; check_test_requirements(t); stop_busy_thread(); need_modeset = disable_features(t); set_crtc_fbs(t); if (t->screen == SCREEN_OFFSCREEN) fill_fb_region(&offscreen_fb, COLOR_OFFSCREEN_BG); igt_display_reset(&drm.display); if (need_modeset) igt_display_commit(&drm.display); init_blue_crc(t->format); if (pattern) init_crcs(t->format, pattern); need_modeset = enable_features_for_test(t); if (need_modeset) igt_display_commit(&drm.display); } static void prepare_subtest_screens(const struct test_mode *t) { if (t->pipes == PIPE_DUAL) enable_both_screens_and_wait(t); else enable_prim_screen_and_wait(t); if (t->screen == SCREEN_PRIM) { if (t->plane == PLANE_CUR) set_region_for_test(t, &prim_mode_params.cursor); if (t->plane == PLANE_SPR) set_region_for_test(t, &prim_mode_params.sprite); } if (t->pipes == PIPE_DUAL && t->screen == SCREEN_SCND) { if (t->plane == PLANE_CUR) set_region_for_test(t, &scnd_mode_params.cursor); if (t->plane == PLANE_SPR) set_region_for_test(t, &scnd_mode_params.sprite); } } static void prepare_subtest(const struct test_mode *t, struct draw_pattern_info *pattern) { prepare_subtest_data(t, pattern); prepare_subtest_screens(t); } /* * 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) { prepare_subtest_data(t, NULL); unset_all_crtcs(); do_assertions(ASSERT_FBC_DISABLED | ASSERT_PSR_DISABLED | DONT_ASSERT_CRC | ASSERT_DRRS_INACTIVE); if (t->pipes == PIPE_SINGLE) enable_prim_screen_and_wait(t); else enable_both_screens_and_wait(t); set_region_for_test(t, &prim_mode_params.cursor); set_region_for_test(t, &prim_mode_params.sprite); if (t->pipes == PIPE_DUAL) { set_region_for_test(t, &scnd_mode_params.cursor); set_region_for_test(t, &scnd_mode_params.sprite); } } static void update_wanted_crc(const struct test_mode *t, igt_crc_t *crc) { if (t->screen == SCREEN_PRIM) wanted_crc = crc; } static bool op_disables_psr(const struct test_mode *t, enum igt_draw_method method) { if (method != IGT_DRAW_MMAP_GTT) return false; if (t->screen == SCREEN_PRIM) return true; /* On FBS_SHARED, even if the target is not the PSR screen * (SCREEN_PRIM), all primary planes share the same frontbuffer, so a * write to the second screen primary plane - or offscreen plane - will * touch the framebuffer that's also used by the primary screen. */ if (t->fbs == FBS_SHARED && t->plane == PLANE_PRI) return true; return false; } /* * 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; igt_skip_on(t->method == IGT_DRAW_MMAP_GTT && !gem_has_mappable_ggtt(drm.fd)); 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); } if (op_disables_psr(t, t->method)) assertions |= ASSERT_PSR_DISABLED; /* * On FBS_INDIVIDUAL, write to offscreen plane will not touch the * current frambuffer. Hence assert for DRRS_LOW. */ if ((t->fbs == FBS_INDIVIDUAL) && (t->screen == SCREEN_OFFSCREEN)) assertions |= ASSERT_DRRS_LOW; prepare_subtest(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[t->format][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 of drawing methods. * * 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 m1, m2, used_method; bool wc_used = false; switch (t->plane) { case PLANE_PRI: pattern = &pattern1; break; case PLANE_CUR: case PLANE_SPR: pattern = &pattern2; break; default: igt_assert(false); } prepare_subtest(t, pattern); target = pick_target(t, params); for (m1 = 0; m1 < IGT_DRAW_METHOD_COUNT; m1++) { for (m2 = m1 + 1; m2 < IGT_DRAW_METHOD_COUNT; m2++) { igt_debug("Methods %s and %s\n", igt_draw_get_method_name(m1), igt_draw_get_method_name(m2)); if ((m1 == IGT_DRAW_MMAP_GTT || m2 == IGT_DRAW_MMAP_GTT) && !gem_has_mappable_ggtt(drm.fd)) continue; for (r = 0; r < pattern->n_rects; r++) { used_method = (r % 2 == 0) ? m1 : m2; igt_debug("Used method %s\n", igt_draw_get_method_name(used_method)); draw_rect(pattern, target, used_method, r); if (used_method == IGT_DRAW_MMAP_WC || used_method == IGT_DRAW_MMAP_GTT) wc_used = true; update_wanted_crc(t, &pattern->crcs[t->format][r]); assertions = used_method != IGT_DRAW_MMAP_GTT ? ASSERT_LAST_ACTION_CHANGED : ASSERT_NO_ACTION_CHANGE; if (op_disables_psr(t, used_method) && !wc_used) assertions |= ASSERT_PSR_DISABLED; do_assertions(assertions); } fill_fb_region(target, COLOR_PRIM_BG); update_wanted_crc(t, &blue_crcs[t->format].crc); do_assertions(ASSERT_NO_ACTION_CHANGE); } } } static bool format_is_valid(int feature_flags, enum pixel_format format) { int devid = intel_get_drm_devid(drm.fd); if (!(feature_flags & FEATURE_FBC)) return true; switch (format) { case FORMAT_RGB888: return true; case FORMAT_RGB565: if (IS_GEN2(devid) || IS_G4X(devid)) return false; return true; case FORMAT_RGB101010: return false; default: igt_assert(false); } } /* * badformat - test pixel formats that are not supported by at least one feature * * METHOD * We just do a modeset on a buffer with the given pixel format and check the * status of the relevant features. * * EXPECTED RESULTS * No assertion failures :) * * FAILURES * If you get a feature enabled/disabled assertion failure, then you should * probably check the Kernel code for the feature that checks the pixel * formats. If you get a CRC assertion failure, then you should use the * appropriate command line arguments that will allow you to look at the * screen, then judge what to do based on what you see. */ static void badformat_subtest(const struct test_mode *t) { bool fbc_valid = format_is_valid(FEATURE_FBC, t->format); bool psr_valid = format_is_valid(FEATURE_PSR, t->format); int assertions = ASSERT_NO_ACTION_CHANGE; prepare_subtest_data(t, NULL); fill_fb_region(&prim_mode_params.primary, COLOR_PRIM_BG); set_mode_for_params(&prim_mode_params); wanted_crc = &blue_crcs[t->format].crc; if (!fbc_valid) assertions |= ASSERT_FBC_DISABLED; if (!psr_valid) assertions |= ASSERT_PSR_DISABLED; do_assertions(assertions); } /* * format_draw - test pixel formats that are not FORMAT_DEFAULT * * METHOD * The real subtest to be executed depends on whether the pixel format is * supported by the features being tested or not. Check the documentation of * each subtest. * * EXPECTED RESULTS * See the documentation for each subtest. * * FAILURES * See the documentation for each subtest. */ static void format_draw_subtest(const struct test_mode *t) { if (format_is_valid(t->feature, t->format)) draw_subtest(t); else badformat_subtest(t); } /* * slow_draw - sleep a little bit between drawing operations * * METHOD * This test is basically the same as the draw subtest, except that we sleep a * little bit after each drawing operation. The goal is to detect problems * that can happen in case a drawing operation is done while the machine is in * some deep sleep states. * * EXPECTED RESULTS * The pattern appears on the screen as expected. * * FAILURES * I've seen this happen in a SKL machine and still haven't investigated it. * My guess would be that preventing deep sleep states fixes the problem. */ static void slow_draw_subtest(const struct test_mode *t) { int r; struct draw_pattern_info *pattern = &pattern1; struct modeset_params *params = pick_params(t); struct fb_region *target; prepare_subtest(t, pattern); sleep(2); target = pick_target(t, params); for (r = 0; r < pattern->n_rects; r++) { sleep(2); draw_rect(pattern, target, t->method, r); sleep(2); update_wanted_crc(t, &pattern->crcs[t->format][r]); if (t->feature & FEATURE_DRRS) do_assertions(ASSERT_DRRS_LOW); else do_assertions(0); } } static void flip_handler(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *data) { igt_debug("Flip event received.\n"); } static void wait_flip_event(void) { int rc; drmEventContext evctx; struct pollfd pfd; evctx.version = 2; evctx.vblank_handler = NULL; evctx.page_flip_handler = flip_handler; pfd.fd = drm.fd; pfd.events = POLLIN; pfd.revents = 0; rc = poll(&pfd, 1, 1000); switch (rc) { case 0: igt_assert_f(false, "Poll timeout\n"); break; case 1: rc = drmHandleEvent(drm.fd, &evctx); igt_assert_eq(rc, 0); break; default: igt_assert_f(false, "Unexpected poll rc %d\n", rc); break; } } static void set_prim_plane_for_params(struct modeset_params *params) { __set_prim_plane_for_params(params); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); } static void page_flip_for_params(struct modeset_params *params, enum flip_type type) { int rc; switch (type) { case FLIP_PAGEFLIP: rc = drmModePageFlip(drm.fd, drm.display.pipes[params->pipe].crtc_id, params->primary.fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL); igt_assert_eq(rc, 0); wait_flip_event(); break; case FLIP_MODESET: set_mode_for_params(params); break; case FLIP_PLANES: set_prim_plane_for_params(params); break; default: igt_assert(false); } } /* * 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. We can flip both using the dedicated * pageflip IOCTL or the modeset IOCTL. * * 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; int assertions = 0; struct igt_fb fb2, *orig_fb; struct modeset_params *params = pick_params(t); struct draw_pattern_info *pattern = &pattern1; enum color bg_color; igt_skip_on(t->method == IGT_DRAW_MMAP_GTT && !gem_has_mappable_ggtt(drm.fd)); switch (t->screen) { case SCREEN_PRIM: assertions |= ASSERT_LAST_ACTION_CHANGED; bg_color = COLOR_PRIM_BG; break; case SCREEN_SCND: assertions |= ASSERT_NO_ACTION_CHANGE; bg_color = COLOR_SCND_BG; break; default: igt_assert(false); } prepare_subtest(t, pattern); create_fb(t->format, params->primary.fb->width, params->primary.fb->height, opt.tiling, t->plane, &fb2); fill_fb(&fb2, bg_color); orig_fb = params->primary.fb; for (r = 0; r < pattern->n_rects; r++) { params->primary.fb = (r % 2 == 0) ? &fb2 : orig_fb; if (r != 0) draw_rect(pattern, ¶ms->primary, t->method, r - 1); draw_rect(pattern, ¶ms->primary, t->method, r); update_wanted_crc(t, &pattern->crcs[t->format][r]); page_flip_for_params(params, t->flip); do_assertions(assertions); } igt_remove_fb(drm.fd, &fb2); } /* * fliptrack - check if the hardware tracking works after page flips * * METHOD * Flip to a new buffer, then draw on it using MMAP_GTT and check the CRC to * make sure the hardware tracking detected the write. * * EXPECTED RESULTS * Everything works as expected, screen contents are properly updated. * * FAILURES * First you need to check if the draw and flip subtests pass. Only after both * are passing this test can be useful. If we're failing only on this subtest, * then maybe we are not properly updating the hardware tracking registers * during the flip operations. */ static void fliptrack_subtest(const struct test_mode *t, enum flip_type type) { int r; struct igt_fb fb2, *orig_fb; struct modeset_params *params = pick_params(t); struct draw_pattern_info *pattern = &pattern1; igt_skip_on(t->method == IGT_DRAW_MMAP_GTT && !gem_has_mappable_ggtt(drm.fd)); prepare_subtest(t, pattern); create_fb(t->format, params->primary.fb->width, params->primary.fb->height, opt.tiling, t->plane, &fb2); fill_fb(&fb2, COLOR_PRIM_BG); orig_fb = params->primary.fb; for (r = 0; r < pattern->n_rects; r++) { params->primary.fb = (r % 2 == 0) ? &fb2 : orig_fb; if (r != 0) draw_rect(pattern, ¶ms->primary, t->method, r - 1); page_flip_for_params(params, type); do_assertions(0); draw_rect(pattern, ¶ms->primary, t->method, r); update_wanted_crc(t, &pattern->crcs[t->format][r]); do_assertions(ASSERT_PSR_DISABLED); } 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; int assertions = ASSERT_NO_ACTION_CHANGE; struct modeset_params *params = pick_params(t); struct draw_pattern_info *pattern = &pattern3; struct fb_region *reg = pick_target(t, params); bool repeat = false; prepare_subtest(t, pattern); /* Just paint the right color since we start at 0x0. */ draw_rect(pattern, reg, t->method, 0); update_wanted_crc(t, &pattern->crcs[t->format][0]); do_assertions(assertions); for (r = 1; r < pattern->n_rects; r++) { struct rect rect = pattern->get_rect(¶ms->primary, r); igt_plane_set_fb(reg->plane, reg->fb); igt_plane_set_position(reg->plane, rect.x, rect.y); igt_plane_set_size(reg->plane, rect.w, rect.h); igt_fb_set_size(reg->fb, reg->plane, rect.w, rect.h); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); update_wanted_crc(t, &pattern->crcs[t->format][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; int assertions = ASSERT_NO_ACTION_CHANGE; struct modeset_params *params = pick_params(t); struct draw_pattern_info *pattern = &pattern3; prepare_subtest(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[t->format][0]); do_assertions(assertions); for (r = 0; r < 4; r++) { struct fb_region *reg = pick_target(t, params); if (r % 2 == 0) { igt_plane_set_fb(reg->plane, NULL); igt_display_commit(&drm.display); update_wanted_crc(t, &blue_crcs[t->format].crc); } else { igt_plane_set_fb(reg->plane, reg->fb); igt_plane_set_size(reg->plane, reg->w, reg->h); igt_fb_set_size(reg->fb, reg->plane, reg->w, reg->h); igt_display_commit(&drm.display); update_wanted_crc(t, &pattern->crcs[t->format][0]); } do_assertions(assertions); } } static bool prim_plane_disabled(void) { /* * Cannot use igt_plane_get_prop here to retrieve fb_id, * the testsuite doesn't require ATOMIC. */ return !prim_mode_params.primary.plane->values[IGT_PLANE_FB_ID]; } /* * 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; prepare_subtest(t, pattern); rect = pattern->get_rect(¶ms->primary, 0); create_fb(t->format, rect.w, rect.h, opt.tiling, t->plane, &fullscreen_fb); /* Call pick_color() again since PRI and SPR may not support the same * pixel formats. */ rect.color = pick_color(&fullscreen_fb, COLOR_GREEN); igt_draw_fill_fb(drm.fd, &fullscreen_fb, rect.color); igt_plane_set_fb(params->sprite.plane, &fullscreen_fb); igt_display_commit(&drm.display); update_wanted_crc(t, &pattern->crcs[t->format][0]); switch (t->screen) { case SCREEN_PRIM: assertions = ASSERT_LAST_ACTION_CHANGED; if (prim_plane_disabled()) assertions |= ASSERT_FBC_DISABLED; break; case SCREEN_SCND: assertions = ASSERT_NO_ACTION_CHANGE; break; default: igt_assert(false); } do_assertions(assertions); igt_plane_set_fb(params->sprite.plane, NULL); igt_display_commit(&drm.display); if (t->screen == SCREEN_PRIM) assertions = ASSERT_LAST_ACTION_CHANGED; update_wanted_crc(t, &blue_crcs[t->format].crc); do_assertions(assertions); igt_remove_fb(drm.fd, &fullscreen_fb); } /* * scaledprimary - try different primary plane scaling strategies * * METHOD * Enable the primary plane, use drmModeSetPlane to force scaling in * different ways. * * EXPECTED RESULTS * SKIP on platforms that don't support primary plane scaling. Success on all * others. * * FAILURES * TODO: although we're exercising the code here, we're not really doing * assertions in order to check if things are working properly. The biggest * issue this code would be able to find would be an incorrectly calculated * CFB size, and today we don't have means to assert this. One day we might * implement some sort of stolen memory checking mechanism, then we'll be able * to force it to run after every drmModeSetPlane call here, so we'll be * checking if the expected CFB size is actually what we think it is. */ static void scaledprimary_subtest(const struct test_mode *t) { struct igt_fb new_fb, *old_fb; struct modeset_params *params = pick_params(t); struct fb_region *reg = ¶ms->primary; int gen = intel_gen(intel_get_drm_devid(drm.fd)); int src_y_upscale = ALIGN(reg->h / 4, 4); igt_require_f(gen >= 9, "Can't test primary plane scaling before gen 9\n"); prepare_subtest(t, NULL); old_fb = reg->fb; create_fb(t->format, reg->fb->width, reg->fb->height, opt.tiling, t->plane, &new_fb); fill_fb(&new_fb, COLOR_BLUE); igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, &new_fb, t->method, reg->x, reg->y, reg->w / 2, reg->h / 2, pick_color(&new_fb, COLOR_GREEN)); igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, &new_fb, t->method, reg->x + reg->w / 2, reg->y + reg->h / 2, reg->w / 2, reg->h / 2, pick_color(&new_fb, COLOR_RED)); igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, &new_fb, t->method, reg->x + reg->w / 2, reg->y + reg->h / 2, reg->w / 4, reg->h / 4, pick_color(&new_fb, COLOR_MAGENTA)); /* No scaling. */ igt_plane_set_fb(reg->plane, &new_fb); igt_fb_set_position(&new_fb, reg->plane, reg->x, reg->y); igt_fb_set_size(&new_fb, reg->plane, reg->w, reg->h); igt_plane_set_size(reg->plane, params->mode->hdisplay, params->mode->vdisplay); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(DONT_ASSERT_CRC); /* Source upscaling. */ igt_fb_set_size(&new_fb, reg->plane, reg->w / 2, reg->h / 2); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(DONT_ASSERT_CRC); /* Destination doesn't fill the entire CRTC, no scaling. */ igt_fb_set_size(&new_fb, reg->plane, reg->w / 2, reg->h / 2); igt_plane_set_position(reg->plane, params->mode->hdisplay / 4, params->mode->vdisplay / 4); igt_plane_set_size(reg->plane, params->mode->hdisplay / 2, params->mode->vdisplay / 2); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(DONT_ASSERT_CRC); /* Destination doesn't fill the entire CRTC, upscaling. */ igt_fb_set_position(&new_fb, reg->plane, reg->x + reg->w / 4, reg->y + src_y_upscale); igt_fb_set_size(&new_fb, reg->plane, reg->w / 2, reg->h / 2); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(DONT_ASSERT_CRC); /* * On gen >= 9 HW, FBC is not enabled on a plane with a Y offset * that isn't divisible by 4, because it causes FIFO underruns. * * Check that FBC is disabled. */ igt_fb_set_position(&new_fb, reg->plane, reg->x + reg->w / 4, (reg->y + src_y_upscale) | 3); igt_fb_set_size(&new_fb, reg->plane, reg->w / 2, reg->h / 2); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(DONT_ASSERT_CRC | (gen >= 9 ? ASSERT_FBC_DISABLED : 0)); /* Back to the good and old blue fb. */ igt_plane_set_fb(reg->plane, old_fb); igt_plane_set_position(params->primary.plane, 0, 0); igt_plane_set_size(reg->plane, params->mode->hdisplay, params->mode->vdisplay); igt_fb_set_position(reg->fb, reg->plane, reg->x, reg->y); igt_fb_set_size(reg->fb, reg->plane, reg->w, reg->h); igt_display_commit2(&drm.display, COMMIT_UNIVERSAL); do_assertions(0); igt_remove_fb(drm.fd, &new_fb); } /** * modesetfrombusy - modeset from a busy buffer to a non-busy buffer * * METHOD * Set a mode, make the frontbuffer busy using BLT writes, do a modeset to a * non-busy buffer, then check if the features are enabled. The goal of this * test is to exercise a bug we had on the frontbuffer tracking infrastructure * code. * * EXPECTED RESULTS * No assertions fail. * * FAILURES * If you're failing this test, then you probably need "drm/i915: Clear * fb_tracking.busy_bits also for synchronous flips" or any other patch that * properly updates dev_priv->fb_tracking.busy_bits when we're alternating * between buffers with different busyness. */ static void modesetfrombusy_subtest(const struct test_mode *t) { struct modeset_params *params = pick_params(t); struct igt_fb fb2; prepare_subtest(t, NULL); create_fb(t->format, params->primary.fb->width, params->primary.fb->height, opt.tiling, t->plane, &fb2); fill_fb(&fb2, COLOR_PRIM_BG); start_busy_thread(params->primary.fb); usleep(10000); unset_all_crtcs(); params->primary.fb = &fb2; set_mode_for_params(params); do_assertions(ASSERT_NO_IDLE_GPU); stop_busy_thread(); igt_remove_fb(drm.fd, &fb2); } /** * suspend - make sure suspend/resume keeps us on the same state * * METHOD * Set a mode, assert FBC is there, suspend, resume, assert FBC is still * there. Unset modes, assert FBC is disabled, resuspend, resume, assert FBC * is still disabled. * * EXPECTED RESULTS * Suspend/resume doesn't affect the FBC state. * * FAILURES * A lot of different things could lead to a bug here, you'll have to check * the Kernel code. */ static void suspend_subtest(const struct test_mode *t) { struct modeset_params *params = pick_params(t); prepare_subtest(t, NULL); igt_system_suspend_autoresume(SUSPEND_STATE_MEM, SUSPEND_TEST_NONE); do_assertions(ASSERT_DRRS_LOW); unset_all_crtcs(); igt_system_suspend_autoresume(SUSPEND_STATE_MEM, SUSPEND_TEST_NONE); do_assertions(ASSERT_FBC_DISABLED | ASSERT_PSR_DISABLED | DONT_ASSERT_CRC | ASSERT_DRRS_INACTIVE); set_mode_for_params(params); do_assertions(0); } /** * farfromfence - test drawing as far from the fence start as possible * * METHOD * One of the possible problems with FBC is that if the mode being displayed * is very far away from the fence we might setup the hardware frontbuffer * tracking in the wrong way. So this test tries to set a really tall FB, * makes the CRTC point to the bottom of that FB, then it tries to exercise * the hardware frontbuffer tracking through GTT mmap operations. * * EXPECTED RESULTS * Everything succeeds. * * FAILURES * If you're getting wrong CRC calulations, then the hardware tracking might * be misconfigured and needs to be checked. If we're failing because FBC is * disabled and the reason is that there's not enough stolen memory, then the * Kernel might be calculating the amount of stolen memory needed based on the * whole framebuffer size, and not just on the needed size: in this case, you * need a newer Kernel. */ static void farfromfence_subtest(const struct test_mode *t) { int r; struct igt_fb tall_fb; struct modeset_params *params = pick_params(t); struct draw_pattern_info *pattern = &pattern1; struct fb_region *target; int max_height, assertions = 0; int gen = intel_gen(intel_get_drm_devid(drm.fd)); igt_skip_on(t->method == IGT_DRAW_MMAP_GTT && !gem_has_mappable_ggtt(drm.fd)); switch (gen) { case 2: max_height = 2048; break; case 3: max_height = 4096; break; default: max_height = 8192; break; } /* Gen 9 doesn't do the same dspaddr_offset magic as the older * gens, so FBC may not be enabled there. */ if (gen >= 9) assertions |= DONT_ASSERT_FEATURE_STATUS; prepare_subtest(t, pattern); target = pick_target(t, params); create_fb(t->format, params->mode->hdisplay, max_height, opt.tiling, t->plane, &tall_fb); fill_fb(&tall_fb, COLOR_PRIM_BG); params->primary.fb = &tall_fb; params->primary.x = 0; params->primary.y = max_height - params->mode->vdisplay; set_mode_for_params(params); do_assertions(assertions); for (r = 0; r < pattern->n_rects; r++) { draw_rect(pattern, target, t->method, r); update_wanted_crc(t, &pattern->crcs[t->format][r]); /* GTT draws disable PSR. */ do_assertions(assertions | ASSERT_PSR_DISABLED); } igt_remove_fb(drm.fd, &tall_fb); } static void try_invalid_strides(void) { uint32_t gem_handle; int rc; /* Sizes that the Kernel shouldn't even allow for tiled */ gem_handle = gem_create(drm.fd, 2048); /* Smaller than 512, yet still 64-byte aligned. */ rc = __gem_set_tiling(drm.fd, gem_handle, I915_TILING_X, 448); igt_assert_eq(rc, -EINVAL); /* Bigger than 512, but not 64-byte aligned. */ rc = __gem_set_tiling(drm.fd, gem_handle, I915_TILING_X, 1022); igt_assert_eq(rc, -EINVAL); /* Just make sure something actually works. */ rc = __gem_set_tiling(drm.fd, gem_handle, I915_TILING_X, 1024); igt_assert_eq(rc, 0); gem_close(drm.fd, gem_handle); } /** * badstride - try to use buffers with strides that are not supported * * METHOD * First we try to create buffers with strides that are not allowed for tiled * surfaces and assert the Kernel rejects them. Then we create buffers with * strides that are allowed by the Kernel, but that are incompatible with FBC * and we assert that FBC stays disabled after we set a mode on those buffers. * * EXPECTED RESULTS * The invalid strides are rejected, and the valid strides that are * incompatible with FBC result in FBC disabled. * * FAILURES * There are two possible places where the Kernel can be broken: either the * code that checks valid strides for tiled buffers or the code that checks * the valid strides for FBC. */ static void badstride_subtest(const struct test_mode *t) { struct igt_fb wide_fb, *old_fb; struct modeset_params *params = pick_params(t); int rc; if (gem_available_fences(drm.fd)) try_invalid_strides(); prepare_subtest(t, NULL); old_fb = params->primary.fb; create_fb(t->format, params->primary.fb->width + 4096, params->primary.fb->height, opt.tiling, t->plane, &wide_fb); igt_assert(wide_fb.strides[0] > 16384); fill_fb(&wide_fb, COLOR_PRIM_BG); /* Try a simple modeset with the new fb. */ params->primary.fb = &wide_fb; set_mode_for_params(params); do_assertions(ASSERT_FBC_DISABLED); /* Go back to the old fb so FBC works again. */ params->primary.fb = old_fb; set_mode_for_params(params); do_assertions(0); /* We're doing the equivalent of a modeset, but with the planes API. */ params->primary.fb = &wide_fb; set_prim_plane_for_params(params); do_assertions(ASSERT_FBC_DISABLED); params->primary.fb = old_fb; set_mode_for_params(params); do_assertions(0); /* * We previously couldn't use the page flip IOCTL to flip to a buffer * with a different stride. With the atomic page flip helper we can, * so allow page flip to fail and succeed. */ rc = drmModePageFlip(drm.fd, drm.display.pipes[params->pipe].crtc_id, wide_fb.fb_id, 0, NULL); igt_assert(rc == -EINVAL || rc == 0); do_assertions(rc == 0 ? ASSERT_FBC_DISABLED : 0); igt_remove_fb(drm.fd, &wide_fb); } /** * stridechange - change the frontbuffer stride by doing a modeset * * METHOD * This test sets a mode on a CRTC, then creates a buffer with a different * stride - still compatible with FBC -, and sets the mode on it. The Kernel * currently shortcuts the modeset path for this case, so it won't trigger * calls to xx_crtc_enable or xx_crtc_disable, and that could lead to * problems, so test the case. * * EXPECTED RESULTS * With the current Kernel, FBC may or may not remain enabled on this case, * but we can still check the CRC values. * * FAILURES * A bad Kernel may just not resize the CFB while keeping FBC enabled, and * this can lead to underruns or stolen memory corruption. Underruns usually * lead to CRC check errors, and stolen memory corruption can't be easily * checked currently. A bad Kernel may also just throw some WARNs on dmesg. */ static void stridechange_subtest(const struct test_mode *t) { struct igt_fb *new_fb, *old_fb; struct modeset_params *params = pick_params(t); int rc; prepare_subtest(t, NULL); old_fb = params->primary.fb; /* We can't assert that FBC will be enabled since there may not be * enough space for the CFB, but we can check the CRC. */ new_fb = &fbs[t->format].big; igt_assert(old_fb->strides[0] != new_fb->strides[0]); params->primary.fb = new_fb; fill_fb_region(¶ms->primary, COLOR_PRIM_BG); set_mode_for_params(params); do_assertions(DONT_ASSERT_FBC_STATUS); /* Go back to the fb that can have FBC. */ params->primary.fb = old_fb; set_mode_for_params(params); do_assertions(0); /* This operation is the same as above, but with the planes API. */ params->primary.fb = new_fb; set_prim_plane_for_params(params); do_assertions(DONT_ASSERT_FBC_STATUS); params->primary.fb = old_fb; set_prim_plane_for_params(params); do_assertions(0); /* * Try to set a new stride. with the page flip api. This is allowed * with the atomic page flip helper, but not with the legacy page flip. */ rc = drmModePageFlip(drm.fd, drm.display.pipes[params->pipe].crtc_id, new_fb->fb_id, 0, NULL); igt_assert(rc == -EINVAL || rc == 0); do_assertions(rc ? 0 : DONT_ASSERT_FBC_STATUS); } /** * tilingchange - alternate between tiled and untiled in multiple ways * * METHOD * This test alternates between tiled and untiled frontbuffers of the same * size and format through multiple different APIs: the page flip IOCTL, * normal modesets and the plane APIs. * * EXPECTED RESULTS * FBC gets properly disabled for the untiled FB and reenabled for the * tiled FB. * * FAILURES * Bad Kernels may somehow leave FBC enabled, which can cause FIFO underruns * that lead to CRC assertion failures. */ static void tilingchange_subtest(const struct test_mode *t) { struct igt_fb new_fb, *old_fb; struct modeset_params *params = pick_params(t); enum flip_type flip_type; prepare_subtest(t, NULL); old_fb = params->primary.fb; create_fb(t->format, params->primary.fb->width, params->primary.fb->height, LOCAL_DRM_FORMAT_MOD_NONE, t->plane, &new_fb); fill_fb(&new_fb, COLOR_PRIM_BG); for (flip_type = 0; flip_type < FLIP_COUNT; flip_type++) { igt_debug("Flip type: %d\n", flip_type); /* Set a buffer with no tiling. */ params->primary.fb = &new_fb; page_flip_for_params(params, flip_type); do_assertions(ASSERT_FBC_DISABLED); /* Put FBC back in a working state. */ params->primary.fb = old_fb; page_flip_for_params(params, flip_type); do_assertions(0); } igt_remove_fb(drm.fd, &new_fb); } /* * basic - do some basic operations regardless of which features are enabled * * METHOD * This subtest does page flips and draw operations and checks the CRCs of the * results. The big difference between this and the others is that here we * don't enable/disable any features such as FBC or PSR: we go with whatever * the Kernel has enabled by default for us. This subtest only does things * that are exercised by the other subtests and in a less exhaustive way: it's * completely redundant. On the other hand, it is very quick and was created * with the CI system in mind: it's a quick way to detect regressions, so if * it fails, then we can run the other subtests to find out why. * * EXPECTED RESULTS * Passed CRC assertions. * * FAILURES * If you get a failure here, you should run the more specific draw and flip * subtests of each feature in order to discover what exactly is failing and * why. */ static void basic_subtest(const struct test_mode *t) { struct draw_pattern_info *pattern = &pattern1; struct modeset_params *params = pick_params(t); enum igt_draw_method method; struct igt_fb *fb1, fb2; int r; int assertions = DONT_ASSERT_FEATURE_STATUS; prepare_subtest(t, pattern); create_fb(t->format, params->primary.fb->width, params->primary.fb->height, opt.tiling, t->plane, &fb2); fb1 = params->primary.fb; for (r = 0, method = 0; method < IGT_DRAW_METHOD_COUNT; method++) { if (method == IGT_DRAW_MMAP_GTT && !gem_has_mappable_ggtt(drm.fd)) continue; if (r == pattern->n_rects) { params->primary.fb = (params->primary.fb == fb1) ? &fb2 : fb1; fill_fb_region(¶ms->primary, COLOR_PRIM_BG); update_wanted_crc(t, &blue_crcs[t->format].crc); page_flip_for_params(params, t->flip); do_assertions(assertions); r = 0; } draw_rect(pattern, ¶ms->primary, method, r); update_wanted_crc(t, &pattern->crcs[t->format][r]); do_assertions(assertions); r++; } igt_remove_fb(drm.fd, &fb2); } 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 'x': errno = 0; opt.shared_fb_x_offset = strtol(optarg, NULL, 0); if (errno != 0) return IGT_OPT_HANDLER_ERROR; break; case 'y': errno = 0; opt.shared_fb_y_offset = strtol(optarg, NULL, 0); if (errno != 0) return IGT_OPT_HANDLER_ERROR; break; case '1': if (opt.only_pipes != PIPE_COUNT) return IGT_OPT_HANDLER_ERROR; opt.only_pipes = PIPE_SINGLE; break; case '2': if (opt.only_pipes != PIPE_COUNT) return IGT_OPT_HANDLER_ERROR; opt.only_pipes = PIPE_DUAL; break; case 'l': if (!strcmp(optarg, "x")) opt.tiling = LOCAL_I915_FORMAT_MOD_X_TILED; else if (!strcmp(optarg, "y")) opt.tiling = LOCAL_I915_FORMAT_MOD_Y_TILED; else { igt_warn("Bad tiling value: %s\n", optarg); return IGT_OPT_HANDLER_ERROR; } break; default: return IGT_OPT_HANDLER_ERROR; } return IGT_OPT_HANDLER_SUCCESS; } 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" " --shared-fb-x offset Use 'offset' as the X offset for the shared FB\n" " --shared-fb-y offset Use 'offset' as the Y offset for the shared FB\n" " --1p-only Only run subtests that use 1 pipe\n" " --2p-only Only run subtests that use 2 pipes\n" " --tiling tiling Use 'tiling' as the tiling mode, which can be\n" " either 'x' (default) or 'y'\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_INDIVIDUAL: return "indfb"; case FBS_SHARED: return "shrfb"; 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"; case FEATURE_DRRS: return "drrs"; case FEATURE_FBC | FEATURE_DRRS: return "fbcdrrs"; case FEATURE_PSR | FEATURE_DRRS: return "psrdrrs"; case FEATURE_FBC | FEATURE_PSR | FEATURE_DRRS: return "fbcpsrdrrs"; default: igt_assert(false); } } static const char *format_str(enum pixel_format format) { switch (format) { case FORMAT_RGB888: return "rgb888"; case FORMAT_RGB565: return "rgb565"; case FORMAT_RGB101010: return "rgb101010"; default: igt_assert(false); } } static const char *flip_str(enum flip_type flip) { switch (flip) { case FLIP_PAGEFLIP: return "pg"; case FLIP_MODESET: return "ms"; case FLIP_PLANES: return "pl"; default: igt_assert(false); } } #define TEST_MODE_ITER_BEGIN(t) \ t.format = FORMAT_DEFAULT; \ t.flip = FLIP_PAGEFLIP; \ 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 (t.screen == SCREEN_OFFSCREEN && t.plane != PLANE_PRI) \ continue; \ if (!opt.show_hidden && t.pipes == PIPE_DUAL && \ t.screen == SCREEN_OFFSCREEN) \ continue; \ if (!opt.show_hidden && t.feature == FEATURE_NONE) \ continue; \ if (!opt.show_hidden && t.fbs == FBS_SHARED && \ (t.plane == PLANE_CUR || t.plane == PLANE_SPR)) \ continue; #define TEST_MODE_ITER_END } } } } } } 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'}, { "shared-fb-x", 1, 0, 'x'}, { "shared-fb-y", 1, 0, 'y'}, { "1p-only", 0, 0, '1'}, { "2p-only", 0, 0, '2'}, { "tiling", 1, 0, 'l'}, { 0, 0, 0, 0 } }; igt_main_args("", long_options, help_str, opt_handler, NULL) { struct test_mode t; igt_fixture setup_environment(); for (t.feature = 0; t.feature < FEATURE_COUNT; t.feature++) { if (!opt.show_hidden && 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_INDIVIDUAL; t.format = FORMAT_DEFAULT; /* Make sure nothing is using these values. */ t.flip = -1; 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 || t.screen == SCREEN_OFFSCREEN || (!opt.show_hidden && t.method != IGT_DRAW_BLT)) continue; for (t.flip = 0; t.flip < FLIP_COUNT; t.flip++) igt_subtest_f("%s-%s-%s-%s-%sflip-%s", feature_str(t.feature), pipes_str(t.pipes), screen_str(t.screen), fbs_str(t.fbs), flip_str(t.flip), igt_draw_get_method_name(t.method)) flip_subtest(&t); TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.plane != PLANE_PRI || t.screen != SCREEN_PRIM || t.method != IGT_DRAW_MMAP_GTT || (t.feature & FEATURE_FBC) == 0) continue; igt_subtest_f("%s-%s-%s-fliptrack", feature_str(t.feature), pipes_str(t.pipes), fbs_str(t.fbs)) fliptrack_subtest(&t, FLIP_PAGEFLIP); TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.screen == SCREEN_OFFSCREEN || t.method != IGT_DRAW_BLT || 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 || t.method != IGT_DRAW_BLT || 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 || t.method != IGT_DRAW_BLT || (!opt.show_hidden && t.plane != PLANE_PRI) || (!opt.show_hidden && t.fbs != FBS_INDIVIDUAL)) continue; igt_subtest_f("%s-%s-%s-%s-multidraw", feature_str(t.feature), pipes_str(t.pipes), plane_str(t.plane), fbs_str(t.fbs)) multidraw_subtest(&t); TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.pipes != PIPE_SINGLE || t.screen != SCREEN_PRIM || t.plane != PLANE_PRI || t.fbs != FBS_INDIVIDUAL || t.method != IGT_DRAW_MMAP_GTT) continue; igt_subtest_f("%s-farfromfence", feature_str(t.feature)) farfromfence_subtest(&t); TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.pipes != PIPE_SINGLE || t.screen != SCREEN_PRIM || t.plane != PLANE_PRI || t.fbs != FBS_INDIVIDUAL) continue; for (t.format = 0; t.format < FORMAT_COUNT; t.format++) { /* Skip what we already tested. */ if (t.format == FORMAT_DEFAULT) continue; igt_subtest_f("%s-%s-draw-%s", feature_str(t.feature), format_str(t.format), igt_draw_get_method_name(t.method)) format_draw_subtest(&t); } TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.pipes != PIPE_SINGLE || t.screen != SCREEN_PRIM || t.plane != PLANE_PRI || t.method != IGT_DRAW_BLT) continue; igt_subtest_f("%s-%s-scaledprimary", feature_str(t.feature), fbs_str(t.fbs)) scaledprimary_subtest(&t); TEST_MODE_ITER_END TEST_MODE_ITER_BEGIN(t) if (t.pipes != PIPE_SINGLE || t.screen != SCREEN_PRIM || t.plane != PLANE_PRI || t.fbs != FBS_INDIVIDUAL || t.method != IGT_DRAW_BLT) continue; igt_subtest_f("%s-modesetfrombusy", feature_str(t.feature)) modesetfrombusy_subtest(&t); if (t.feature & FEATURE_FBC) { igt_subtest_f("%s-badstride", feature_str(t.feature)) badstride_subtest(&t); igt_subtest_f("%s-stridechange", feature_str(t.feature)) stridechange_subtest(&t); igt_subtest_f("%s-tilingchange", feature_str(t.feature)) tilingchange_subtest(&t); } if ((t.feature & FEATURE_PSR) || (t.feature & FEATURE_DRRS)) igt_subtest_f("%s-slowdraw", feature_str(t.feature)) slow_draw_subtest(&t); igt_subtest_f("%s-suspend", feature_str(t.feature)) suspend_subtest(&t); TEST_MODE_ITER_END t.pipes = PIPE_SINGLE; t.screen = SCREEN_PRIM; t.plane = PLANE_PRI; t.fbs = FBS_INDIVIDUAL; t.feature = FEATURE_DEFAULT; t.format = FORMAT_DEFAULT; t.flip = FLIP_PAGEFLIP; igt_subtest("basic") { igt_require_gem(drm.fd); basic_subtest(&t); } igt_fixture teardown_environment(); }