From f57f7dfd2bbaeeb43b72cbccd436b876d3a52400 Mon Sep 17 00:00:00 2001 From: Paulo Zanoni Date: Tue, 2 Dec 2014 10:40:05 -0200 Subject: tests: add pm_fbc A new test for FBC. Signed-off-by: Paulo Zanoni --- tests/.gitignore | 1 + tests/Makefile.sources | 1 + tests/pm_fbc.c | 1295 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1297 insertions(+) create mode 100644 tests/pm_fbc.c diff --git a/tests/.gitignore b/tests/.gitignore index 0dc044e5..4ddccca1 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -135,6 +135,7 @@ kms_setmode kms_sink_crc_basic kms_universal_plane multi-tests.txt +pm_fbc pm_lpsp pm_psr pm_rc6_residency diff --git a/tests/Makefile.sources b/tests/Makefile.sources index 967dc8f8..6b6813a8 100644 --- a/tests/Makefile.sources +++ b/tests/Makefile.sources @@ -76,6 +76,7 @@ TESTS_progs_M = \ kms_rotation_crc \ kms_setmode \ kms_universal_plane \ + pm_fbc \ pm_lpsp \ pm_rpm \ pm_rps \ diff --git a/tests/pm_fbc.c b/tests/pm_fbc.c new file mode 100644 index 00000000..28e1c0bc --- /dev/null +++ b/tests/pm_fbc.c @@ -0,0 +1,1295 @@ +/* + * Copyright © 2014 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 +#include +#include + +#include "drmtest.h" +#include "igt_aux.h" +#include "igt_kms.h" +#include "igt_debugfs.h" +#include "intel_chipset.h" +#include "ioctl_wrappers.h" + +#define MAX_CONNECTORS 32 + +#define N_RECTS 6 + +int drm_fd; +int fbc_fd; +int devid; +int gen; +drmModeResPtr drm_res; +drmModeConnectorPtr drm_connectors[MAX_CONNECTORS]; + +igt_pipe_crc_t *pipe_crc; +igt_crc_t blue_crc, rect_crc[N_RECTS]; +igt_crc_t *custom_crc; + +struct igt_fb rect_fbs[N_RECTS]; + +bool check_status = true; +bool check_crc = true; +bool check_compression = true; +bool check_last_action = true; + +bool manual = false; + +bool supports_last_action = false; +bool supports_compressing = false; + +struct timespec last_action; + +enum fbc_status { + FBC_ENABLED, + FBC_DISABLED, + FBC_BAD, /* HW state doesn't match SW tracking state */ +}; + +enum draw_method { + DRAW_MMAP_CPU, + DRAW_MMAP_GTT, + DRAW_PWRITE, + DRAW_BLT, + DRAW_RENDER, + DRAW_METHOD_COUNT, +}; + +const char *draw_method_names[] = { + "mmap-cpu", + "mmap-gtt", + "pwrite", + "blt", + "render", +}; + +struct modeset_params { + uint32_t crtc_id; + uint32_t connector_id; + struct igt_fb fb; + drmModeModeInfoPtr mode; +}; + +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", +}; + +struct modeset_params fbc_mode_params; +struct modeset_params non_fbc_mode_params; + +#if 0 +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; +} +#endif + +static void fill_igt_fb(struct igt_fb *fb, uint32_t color) +{ + int i; + uint32_t *ptr; + + ptr = gem_mmap__gtt(drm_fd, fb->gem_handle, fb->size, PROT_WRITE); + for (i = 0; i < fb->size/sizeof(uint32_t); i++) + ptr[i] = color; + igt_assert(munmap(ptr, fb->size) == 0); +} + +static bool init_modeset_cached_params(void) +{ + int i; + uint32_t fbc_connector_id = 0, non_fbc_connector_id = 0; + drmModeModeInfoPtr fbc_mode = NULL, non_fbc_mode = NULL; + + for (i = 0; i < drm_res->count_connectors; i++) { + drmModeConnectorPtr c = drm_connectors[i]; + + if (c->connection == DRM_MODE_CONNECTED && c->count_modes) { + drmModeModeInfoPtr mode; + + /* 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; + else + mode = &c->modes[0]; + /* mode = get_connector_smallest_mode(c); */ + + if (!fbc_connector_id) { + fbc_connector_id = c->connector_id; + fbc_mode = mode; + } else if (!non_fbc_connector_id) { + non_fbc_connector_id = c->connector_id; + non_fbc_mode = mode; + } else { + break; + } + } + } + + if (!fbc_connector_id) + return false; + + igt_create_fb(drm_fd, fbc_mode->hdisplay, fbc_mode->vdisplay, + DRM_FORMAT_XRGB8888, I915_TILING_X, &fbc_mode_params.fb); + fill_igt_fb(&fbc_mode_params.fb, 0xFF); + + fbc_mode_params.crtc_id = drm_res->crtcs[0]; + fbc_mode_params.connector_id = fbc_connector_id; + fbc_mode_params.mode = fbc_mode; + + if (!non_fbc_connector_id) { + non_fbc_mode_params.connector_id = 0; + return true; + } + + igt_create_fb(drm_fd, non_fbc_mode->hdisplay, non_fbc_mode->vdisplay, + DRM_FORMAT_XRGB8888, I915_TILING_X, + &non_fbc_mode_params.fb); + fill_igt_fb(&non_fbc_mode_params.fb, 0x88); + + igt_assert(drm_res->count_crtcs >= 2); + non_fbc_mode_params.crtc_id = drm_res->crtcs[1]; + non_fbc_mode_params.connector_id = non_fbc_connector_id; + non_fbc_mode_params.mode = non_fbc_mode; + + return true; +} + +static bool set_mode_for_params(struct modeset_params *params) +{ + int rc; + + rc = drmModeSetCrtc(drm_fd, params->crtc_id, params->fb.fb_id, 0, 0, + ¶ms->connector_id, 1, params->mode); + return (rc == 0); +} + +#define DEBUGFS_MSG_SIZE 256 + +#if 0 +struct debugfs_data { + enum fbc_status status; + struct timespec last_action; + bool is_compressing; +}; + +static struct debugfs_data get_debugfs_data(void) { + struct debugfs_data data; + char buf[DEBUGFS_MSG_SIZE], *s; + ssize_t n_read; + + lseek(fbc_fd, 0, SEEK_SET); + + n_read = read(fbc_fd, buf, DEBUGFS_MSG_SIZE -1); + igt_assert(n_read >= 0); + buf[n_read] = '\0'; + + s = strstr(buf, "FBC enabled"); + data.status = s ? FBC_ENABLED : FBC_DISABLED; + + s = strstr(buf, "Compressing: yes"); + data.is_compressing = s != NULL; + + s = strstr(buf, "Last action:"); + igt_assert(s); + + n_read = sscanf(s, "Last action: %ld.%ld", + &data.last_action.tv_sec, &data.last_action.tv_nsec); + igt_assert(n_read == 2); + + return data; +} +#endif + +static void get_debugfs_string(char *buf) +{ + ssize_t n_read; + + lseek(fbc_fd, 0, SEEK_SET); + + n_read = read(fbc_fd, buf, DEBUGFS_MSG_SIZE -1); + igt_assert(n_read >= 0); + buf[n_read] = '\0'; +} + +static enum fbc_status get_fbc_status(void) +{ + char buf[DEBUGFS_MSG_SIZE]; + + get_debugfs_string(buf); + + if (strstr(buf, "FBC enabled")) + return FBC_ENABLED; + else + return FBC_DISABLED; +} + +static struct timespec get_last_action(void) +{ + struct timespec ret = { 0, 0 }; + char buf[DEBUGFS_MSG_SIZE]; + char *action; + ssize_t n_read; + + get_debugfs_string(buf); + + action = strstr(buf, "Last 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 last_action_changed(void) +{ + struct timespec t_new, t_old; + + t_old = last_action; + t_new = get_last_action(); + + last_action = t_new; + +#if 0 + igt_log(IGT_LOG_INFO, "old: %ld.%ld\n", + t_old.tv_sec, t_old.tv_nsec); + igt_log(IGT_LOG_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 update_last_action(void) +{ + if (!supports_last_action) + return; + + last_action = get_last_action(); + +#if 0 + igt_log(IGT_LOG_INFO, "Last action: %ld.%ld\n", + last_action.tv_sec, last_action.tv_nsec); +#endif +} + +static void setup_last_action(void) +{ + ssize_t n_read; + char buf[DEBUGFS_MSG_SIZE]; + char *action; + + get_debugfs_string(buf); + + action = strstr(buf, "Last action:"); + if (!action) { + igt_log(IGT_LOG_INFO, "Last action not supported\n"); + return; + } + + supports_last_action = true; + + n_read = sscanf(action, "Last action: %ld.%ld", + &last_action.tv_sec, &last_action.tv_nsec); + igt_assert(n_read == 2); +} + +static bool is_compressing(void) +{ + char buf[DEBUGFS_MSG_SIZE]; + + get_debugfs_string(buf); + return strstr(buf, "Compressing: yes") != NULL; +} + +static bool wait_for_compression(void) +{ + return igt_wait(is_compressing(), 5000, 1); +} + +static void setup_compressing(void) +{ + char buf[DEBUGFS_MSG_SIZE]; + + get_debugfs_string(buf); + + if (strstr(buf, "Compressing:")) { + supports_compressing = true; + } else { + igt_log(IGT_LOG_INFO, + "Compression information not supported\n"); + } +} + +static bool wait_for_status(enum fbc_status status) +{ + return igt_wait(get_fbc_status() == status, 5000, 1); +} + +static int swizzle_addr(struct igt_fb *fb, int addr, int swizzle) +{ + int bit6; + + if (swizzle == I915_BIT_6_SWIZZLE_9_10) { + bit6 = ((addr >> 6) & 1) ^ ((addr >> 9) & 1) ^ + ((addr >> 10) & 1); + addr &= ~(1 << 6); + addr |= (bit6 << 6); + } + + return addr; +} + +/* It's all in "pixel coordinates", so make sure you multiply/divide by the bpp + * if you need to. */ +static int linear_x_y_to_tiled_pos(struct igt_fb *fb, int x, int y, + int swizzle) +{ + int x_tile_size, y_tile_size; + int x_tile_n, y_tile_n, x_tile_off, y_tile_off; + int line_size, tile_size; + int tile_n, tile_off; + int tiled_pos, tiles_per_line; + int bpp; + + line_size = fb->stride; + x_tile_size = 512; + y_tile_size = 8; + tile_size = x_tile_size * y_tile_size; + tiles_per_line = line_size / x_tile_size; + bpp = sizeof(uint32_t); /* TODO: don't assume this. */ + + y_tile_n = y / y_tile_size; + y_tile_off = y % y_tile_size; + + x_tile_n = (x * bpp) / x_tile_size; + x_tile_off = (x * bpp) % x_tile_size; + + tile_n = y_tile_n * tiles_per_line + x_tile_n; + tile_off = y_tile_off * x_tile_size + x_tile_off; + tiled_pos = tile_n * tile_size + tile_off; + + tiled_pos = swizzle_addr(fb, tiled_pos, swizzle); + + return tiled_pos / bpp; +} + +/* It's all in "pixel coordinates", so make sure you multiply/divide by the bpp + * if you need to. */ +static void tiled_pos_to_x_y_linear(struct igt_fb *fb, int tiled_pos, + int swizzle, int *x, int *y) +{ + int tile_n, tile_off, tiles_per_line, line_size; + int x_tile_off, y_tile_off; + int x_tile_n, y_tile_n; + int x_tile_size, y_tile_size, tile_size; + int bpp; + + tiled_pos = swizzle_addr(fb, tiled_pos, swizzle); + + line_size = fb->stride; + x_tile_size = 512; + y_tile_size = 8; + tile_size = x_tile_size * y_tile_size; + tiles_per_line = line_size / x_tile_size; + bpp = sizeof(uint32_t); /* TODO: don't assume this. */ + + tile_n = tiled_pos / tile_size; + tile_off = tiled_pos % tile_size; + + y_tile_off = tile_off / x_tile_size; + x_tile_off = tile_off % x_tile_size; + + x_tile_n = tile_n % tiles_per_line; + y_tile_n = tile_n / tiles_per_line; + + *x = (x_tile_n * x_tile_size + x_tile_off) / bpp; + *y = y_tile_n * y_tile_size + y_tile_off; +} + +struct rect { + int x; + int y; + int w; + int h; + uint32_t color; +}; + +static struct rect get_rect(int r, struct igt_fb *fb) +{ + struct rect rect; + + switch (r) { + case 0: + rect.x = 0; + rect.y = 0; + rect.w = fb->width / 8; + rect.h = fb->height / 8; + rect.color = 0x00FF00; + break; + case 1: + rect.x = fb->width / 8; + rect.y = fb->height / 8; + rect.w = fb->width / 8; + rect.h = fb->height / 8; + rect.color = 0xFF0000; + break; + case 2: + rect.x = fb->width / 8 * 4; + rect.y = fb->height / 8 * 4; + rect.w = fb->width / 8 * 2; + rect.h = fb->height / 8 * 2; + rect.color = 0xFF00FF; + break; + case 3: + rect.x = fb->width / 16; + rect.y = fb->height / 16; + rect.w = fb->width / 8; + rect.h = fb->height / 8; + rect.color = 0x00FFFF; + break; + case 4: /* Cursor sized! */ + rect.x = 0; + rect.y = 0; + rect.w = 64; + rect.h = 64; + rect.color = 0xFFFF00; + break; + case 5: /* Cursor sized!(2) */ + rect.x = 64; + rect.y = 64; + rect.w = 64; + rect.h = 64; + rect.color = 0xFFFF00; + break; + default: + igt_assert(0); + } + + return rect; +} + +static void draw_rect_mmap_cpu(struct igt_fb *fb, int r) +{ + uint32_t *ptr; + int x, y, tiled_pos; + struct rect rect = get_rect(r, fb); + uint32_t tiling, swizzle; + + gem_set_domain(drm_fd, fb->gem_handle, I915_GEM_DOMAIN_CPU, + I915_GEM_DOMAIN_CPU); + gem_get_tiling(drm_fd, fb->gem_handle, &tiling, &swizzle); + + ptr = gem_mmap__cpu(drm_fd, fb->gem_handle, 0, fb->size, 0); + igt_assert(ptr); + + for (y = rect.y; y < rect.y + rect.h; y++) { + for (x = rect.x; x < rect.x + rect.w; x++) { + tiled_pos = linear_x_y_to_tiled_pos(fb, x, y, swizzle); + ptr[tiled_pos] = rect.color; + } + } + + gem_sw_finish(drm_fd, fb->gem_handle); + + igt_assert(munmap(ptr, fb->size) == 0); +#if 0 + gem_set_domain(drm_fd, fb->gem_handle, I915_GEM_DOMAIN_GTT, + I915_GEM_DOMAIN_GTT); +#endif +} + +static void draw_rect_mmap_gtt(struct igt_fb *fb, int r) +{ + uint32_t *ptr; + int w, h; + struct rect rect = get_rect(r, fb); + + ptr = gem_mmap__gtt(drm_fd, fb->gem_handle, fb->size, + PROT_READ | PROT_WRITE); + igt_assert(ptr); + + for (h = rect.y; h < rect.y + rect.h; h++) { + int line_begin = h * fb->stride / sizeof(uint32_t); + for (w = rect.x; w < rect.x + rect.w; w++) + ptr[line_begin + w] = rect.color; + } + + igt_assert(munmap(ptr, fb->size) == 0); +} + +static void draw_rect_pwrite(struct igt_fb *fb, int r) +{ + int i; + int tiled_pos, bpp, x, y; + uint32_t buf[1024]; + int buf_used = 0, buf_size = ARRAY_SIZE(buf); + bool flush_buf = false; + int buf_start_pos = 0; + struct rect rect = get_rect(r, fb); + uint32_t tiling, swizzle; + + gem_get_tiling(drm_fd, fb->gem_handle, &tiling, &swizzle); + bpp = sizeof(uint32_t); + + /* Instead of doing one pwrite per pixel, we try to group the maximum + * amount of consecutive green pixels we can in a single pwrite: that's + * why we use the "buf" variables. */ + for (i = 0; i < buf_size; i++) + buf[i] = rect.color; + + for (tiled_pos = 0; tiled_pos < fb->size; tiled_pos += bpp) { + tiled_pos_to_x_y_linear(fb, tiled_pos, swizzle, &x, &y); + + if (x >= rect.x && x < rect.x + rect.w && + y >= rect.y && y < rect.y + rect.h) { + if (buf_used == 0) + buf_start_pos = tiled_pos; + buf_used++; + } else { + flush_buf = true; + } + + if (buf_used == buf_size || (flush_buf && buf_used > 0)) { + gem_write(drm_fd, fb->gem_handle, buf_start_pos, buf, + buf_used * sizeof(uint32_t)); + flush_buf = false; + buf_used = 0; + } + } +} + +static void draw_rect_blt(struct igt_fb *fb, int r) +{ + int i, reloc_pos; + uint32_t batch_handle; + int batch_size = 8 * sizeof(uint32_t); + uint32_t batch_buf[batch_size]; + struct drm_i915_gem_execbuffer2 execbuf = {}; + struct drm_i915_gem_exec_object2 objs[2] = {{}, {}}; + struct drm_i915_gem_relocation_entry relocs[1] = {{}}; + struct drm_i915_gem_wait gem_wait; + int blt_cmd_len; + uint32_t presumed_dst_offset = 0; + uint32_t dst_handle = fb->gem_handle; + struct rect rect = get_rect(r, fb); + + blt_cmd_len = (gen >= 8) ? 0x5 : 0x4; + + i = 0; + batch_buf[i++] = XY_COLOR_BLT_CMD_NOLEN | XY_COLOR_BLT_WRITE_ALPHA | + XY_COLOR_BLT_WRITE_RGB | XY_COLOR_BLT_TILED | + blt_cmd_len; + batch_buf[i++] = (3 << 24) | (0xF0 << 16) | (fb->stride / 4); + batch_buf[i++] = (rect.y << 16) | rect.x; + batch_buf[i++] = ((rect.y + rect.h) << 16) | (rect.x + rect.w); + reloc_pos = i; + batch_buf[i++] = presumed_dst_offset; + if (gen >= 8) + batch_buf[i++] = 0; + batch_buf[i++] = rect.color; + + batch_buf[i++] = MI_BATCH_BUFFER_END; + if (gen < 8) + batch_buf[i++] = MI_NOOP; + + igt_assert(i * sizeof(uint32_t) == batch_size); + + batch_handle = gem_create(drm_fd, batch_size); + gem_write(drm_fd, batch_handle, 0, batch_buf, batch_size); + + relocs[0].target_handle = dst_handle; + relocs[0].delta = 0; + relocs[0].offset = reloc_pos * sizeof(uint32_t); + relocs[0].presumed_offset = presumed_dst_offset; + relocs[0].read_domains = 0; + relocs[0].write_domain = I915_GEM_DOMAIN_RENDER; + + objs[0].handle = dst_handle; + objs[0].alignment = 0; + + objs[1].handle = batch_handle; + objs[1].relocation_count = 1; + objs[1].relocs_ptr = (uintptr_t)relocs; + + execbuf.buffers_ptr = (uintptr_t)objs; + execbuf.buffer_count = 2; + execbuf.batch_len = batch_size; + execbuf.flags = I915_EXEC_BLT; + i915_execbuffer2_set_context_id(execbuf, 0); + + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_EXECBUFFER2, &execbuf); + + presumed_dst_offset = relocs[0].presumed_offset; + + gem_wait.flags = 0; + gem_wait.timeout_ns = 10000000000LL; /* 10s */ + +#if 0 + gem_wait.bo_handle = batch_handle; + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait); + + gem_wait.bo_handle = dst_handle; + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait); +#endif + gem_sync(drm_fd, batch_handle); + gem_sync(drm_fd, dst_handle); + + gem_close(drm_fd, batch_handle); +} + +static void draw_rect_render(struct igt_fb *fb, int r) +{ + drm_intel_bufmgr *bufmgr; + drm_intel_bo *src, *dst; + igt_render_copyfunc_t rendercopy = igt_get_render_copyfunc(devid); + struct rect rect = get_rect(r, fb); + struct igt_buf src_buf, dst_buf; + struct intel_batchbuffer *batch; + struct drm_i915_gem_wait gem_wait; + + bufmgr = drm_intel_bufmgr_gem_init(drm_fd, 4096); + igt_assert(bufmgr); + + igt_skip_on(!rendercopy); + + src = gem_handle_to_libdrm_bo(bufmgr, drm_fd, "", + rect_fbs[r].gem_handle); + igt_assert(src); + dst = gem_handle_to_libdrm_bo(bufmgr, drm_fd, "", fb->gem_handle); + igt_assert(dst); + + src_buf.bo = src; + src_buf.stride = rect_fbs[r].stride; + src_buf.tiling = rect_fbs[r].tiling; + src_buf.size = rect_fbs[r].size; + dst_buf.bo = dst; + dst_buf.stride = fb->stride; + dst_buf.tiling = fb->tiling; + dst_buf.size = fb->size; + + batch = intel_batchbuffer_alloc(bufmgr, devid); + igt_assert(batch); + + rendercopy(batch, NULL, &src_buf, rect.x, rect.y, rect.w, rect.h, + &dst_buf, rect.x, rect.y); + + gem_wait.flags = 0; + gem_wait.timeout_ns = 10000000000LL; /* 10s */ + gem_wait.bo_handle = fb->gem_handle; + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait); + + intel_batchbuffer_free(batch); + + drm_intel_bufmgr_destroy(bufmgr); +} + +static void draw_rect(struct igt_fb *fb, enum draw_method method, int r) +{ + switch (method) { + case DRAW_MMAP_CPU: + draw_rect_mmap_cpu(fb, r); + break; + case DRAW_MMAP_GTT: + draw_rect_mmap_gtt(fb, r); + break; + case DRAW_PWRITE: + draw_rect_pwrite(fb, r); + break; + case DRAW_BLT: + draw_rect_blt(fb, r); + break; + case DRAW_RENDER: + draw_rect_render(fb, r); + break; + default: + igt_assert(false); + break; + } +} + +static void get_base_crc_values(void) +{ + struct igt_fb blue; + int r, r_, rc; + + kmstest_unset_all_crtcs(drm_fd, drm_res); + + igt_create_fb(drm_fd, fbc_mode_params.mode->hdisplay, + fbc_mode_params.mode->vdisplay, DRM_FORMAT_XRGB8888, + I915_TILING_X, &blue); + + for (r = 0; r < N_RECTS; r++) + igt_create_fb(drm_fd, fbc_mode_params.mode->hdisplay, + fbc_mode_params.mode->vdisplay, + DRM_FORMAT_XRGB8888, I915_TILING_X, &rect_fbs[r]); + + /* Draw everything before setting the modes, in order to reduce the + * probability that some FBC bug will change the resulting CRC. */ + fill_igt_fb(&blue, 0xFF); + for (r = 0; r < N_RECTS; r++) + fill_igt_fb(&rect_fbs[r], 0xFF); + + /* We draw rect 0, then we draw rect 1 on top of the image that already + * contains rect 0, and so on. */ + for (r = 0; r < N_RECTS; r++) + for (r_ = 0; r_ <= r; r_++) + draw_rect_pwrite(&rect_fbs[r], r_); + + rc = drmModeSetCrtc(drm_fd, fbc_mode_params.crtc_id, + blue.fb_id, 0, 0, &fbc_mode_params.connector_id, 1, + fbc_mode_params.mode); + igt_assert(rc == 0); + igt_pipe_crc_collect_crc(pipe_crc, &blue_crc); + + for (r = 0; r < N_RECTS; r++) { + rc = drmModeSetCrtc(drm_fd, fbc_mode_params.crtc_id, + rect_fbs[r].fb_id, 0, 0, + &fbc_mode_params.connector_id, 1, + fbc_mode_params.mode); + igt_assert(rc == 0); + igt_pipe_crc_collect_crc(pipe_crc, &rect_crc[r]); + } + + igt_log(IGT_LOG_DEBUG, "Blue CRC: %s\n", + igt_crc_to_string(&blue_crc)); + for (r = 0; r < N_RECTS; r++) + igt_log(IGT_LOG_DEBUG, "Rect %d CRC: %s\n", r, + igt_crc_to_string(&rect_crc[r])); + + igt_remove_fb(drm_fd, &blue); +} + +static void setup_environment(void) +{ + int i; + + drm_fd = drm_open_any_master(); + igt_require(drm_fd >= 0); + + drm_res = drmModeGetResources(drm_fd); + igt_assert(drm_res->count_connectors <= MAX_CONNECTORS); + + for (i = 0; i < drm_res->count_connectors; i++) + drm_connectors[i] = drmModeGetConnector(drm_fd, + drm_res->connectors[i]); + + igt_require(init_modeset_cached_params()); + + kmstest_set_vt_graphics_mode(); + + fbc_fd = open("/sys/kernel/debug/dri/0/i915_fbc_status", O_RDONLY); + igt_assert(fbc_fd >= 0); + + setup_last_action(); + setup_compressing(); + + devid = intel_get_drm_devid(drm_fd); + gen = intel_gen(devid); + + pipe_crc = igt_pipe_crc_new(0, INTEL_PIPE_CRC_SOURCE_AUTO); + + get_base_crc_values(); +} + +static void teardown_environment(void) +{ + int i; + + for (i = 0; i < N_RECTS; i++) + igt_remove_fb(drm_fd, &rect_fbs[i]); + igt_pipe_crc_free(pipe_crc); + + close(fbc_fd); + + if (non_fbc_mode_params.connector_id) + igt_remove_fb(drm_fd, &non_fbc_mode_params.fb); + igt_remove_fb(drm_fd, &fbc_mode_params.fb); + + for (i = 0; i < drm_res->count_connectors; i++) + drmModeFreeConnector(drm_connectors[i]); + drmModeFreeResources(drm_res); + close(drm_fd); +} + +static void wait_user(void) +{ + if (!manual) + return; + + igt_log(IGT_LOG_INFO, "Press enter...\n"); + while (getchar() != '\n') + ; +} + +#define ASSERT_FBC_ENABLED (1 << 0) +#define ASSERT_FBC_DISABLED (1 << 1) + +#define ASSERT_LAST_ACTION_CHANGED (1 << 2) +#define ASSERT_NO_ACTION_CHANGE (1 << 3) + +#define ASSERT_CRC_SHIFT (5) +#define ASSERT_CRC_BLUE (1 << 5) +#define ASSERT_CRC_CUSTOM (1 << 6) +#define ASSERT_CRC_RECT(r) (1 << (r + 7)) + +#define do_crc_assertions(flags) do { \ + int flags__ = (flags); \ + int r_; \ + \ + igt_crc_t crc_, *wanted_crc_ = NULL; \ + \ + if (flags__ & ASSERT_CRC_BLUE) { \ + wanted_crc_ = &blue_crc; \ + } else if (flags__ & ASSERT_CRC_CUSTOM) { \ + wanted_crc_ = custom_crc; \ + } else { \ + for (r_ = 0; r_ < N_RECTS; r_++) \ + if (flags__ & ASSERT_CRC_RECT(r_)) \ + wanted_crc_ = &rect_crc[r_]; \ + } \ + igt_assert(wanted_crc_); \ + \ + igt_pipe_crc_collect_crc(pipe_crc, &crc_); \ + \ + igt_log(IGT_LOG_DEBUG, "Calculated CRC: %s\n", \ + igt_crc_to_string(&crc_)); \ + \ + if (check_crc) \ + igt_assert(igt_crc_equal(&crc_, wanted_crc_)); \ +} while (0) + +#define do_assertions(flags) do { \ + int flags_ = (flags); \ + \ + wait_user(); \ + \ + /* Check the CRC to make sure the response is immediate. */ \ + if (flags_ >> ASSERT_CRC_SHIFT) \ + do_crc_assertions(flags_); \ + \ + if (check_status) { \ + if (flags_ & ASSERT_FBC_ENABLED) { \ + igt_assert(wait_for_status(FBC_ENABLED)); \ + \ + if (supports_compressing && check_compression) \ + igt_assert(wait_for_compression()); \ + } else if (flags_ & ASSERT_FBC_DISABLED) { \ + igt_assert(wait_for_status(FBC_DISABLED)); \ + } \ + } else { \ + /* Make sure we settle before continuing. */ \ + sleep(1); \ + } \ + \ + /* Check CRC again to make sure the compressed screen is ok. */ \ + if (flags_ >> ASSERT_CRC_SHIFT) \ + do_crc_assertions(flags_); \ + \ + if (supports_last_action && check_last_action) { \ + if (flags_ & ASSERT_LAST_ACTION_CHANGED) \ + igt_assert(last_action_changed()); \ + else if (flags_ & ASSERT_NO_ACTION_CHANGE) \ + igt_assert(!last_action_changed()); \ + } \ + \ + wait_user(); \ +} while (0) + +static void enable_fbc_screen_and_wait(void) +{ + /* Repaint it every time because the front-buffer rendering tests may + * have painted it in other tests. */ + fill_igt_fb(&fbc_mode_params.fb, 0xFF); + set_mode_for_params(&fbc_mode_params); + + do_assertions(ASSERT_FBC_ENABLED); + update_last_action(); + do_assertions(ASSERT_CRC_BLUE); +} + +static void enable_non_fbc_screen_and_wait(void) +{ + set_mode_for_params(&non_fbc_mode_params); + do_assertions(ASSERT_FBC_ENABLED); +} + +static void rte_subtest(bool two_pipes) +{ + kmstest_unset_all_crtcs(drm_fd, drm_res); + do_assertions(ASSERT_FBC_DISABLED); + + enable_fbc_screen_and_wait(); + + if (two_pipes) { + enable_non_fbc_screen_and_wait(); + do_assertions(ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + } +} + +static void frontbuffer_subtest(enum draw_method method) +{ + struct igt_fb *fb = &fbc_mode_params.fb; + int r; + int assertions; + + kmstest_unset_all_crtcs(drm_fd, drm_res); + enable_fbc_screen_and_wait(); + + for (r = 0; r < N_RECTS; r++) { + draw_rect(fb, method, r); + + assertions = ASSERT_FBC_ENABLED | + ASSERT_CRC_RECT(r); + if (method != DRAW_MMAP_GTT) + assertions |= ASSERT_LAST_ACTION_CHANGED; + + do_assertions(assertions); + } +} + +static void flip_subtest(void) +{ + int r, rc; + + kmstest_unset_all_crtcs(drm_fd, drm_res); + enable_fbc_screen_and_wait(); + + for (r = 0; r < N_RECTS; r++) { + rc = drmModePageFlip(drm_fd, fbc_mode_params.crtc_id, + rect_fbs[r].fb_id, 0, NULL); + igt_assert(rc == 0); + + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_RECT(r) | + ASSERT_LAST_ACTION_CHANGED); + } +} + +static void backbuffer_subtest(enum draw_method method) +{ + struct igt_fb back_fb; + int r; + + igt_create_fb(drm_fd, 1024, 1024, DRM_FORMAT_XRGB8888, I915_TILING_X, + &back_fb); + fill_igt_fb(&back_fb, 0xFF0000); + + kmstest_unset_all_crtcs(drm_fd, drm_res); + enable_fbc_screen_and_wait(); + + for (r = 0; r < N_RECTS; r++) { + draw_rect(&back_fb, method, r); + + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + } + + igt_remove_fb(drm_fd, &back_fb); +} + +static void cursor_subtest(void) +{ + int i, rc; + int ntmp = 2; + struct igt_fb cursor, tmp[ntmp]; + igt_crc_t tmp_crc[ntmp]; + + kmstest_unset_all_crtcs(drm_fd, drm_res); + + /* First, draw the "cursors" using software and calculate the CRCs. */ + for (i = 0; i < ntmp; i++) { + igt_create_fb(drm_fd, fbc_mode_params.mode->hdisplay, + fbc_mode_params.mode->vdisplay, + DRM_FORMAT_XRGB8888, I915_TILING_X, &tmp[i]); + fill_igt_fb(&tmp[i], 0xFF); + } + + draw_rect_pwrite(&tmp[0], 4); + draw_rect_pwrite(&tmp[1], 5); + + for (i = 0; i < ntmp; i++) { + rc = drmModeSetCrtc(drm_fd, fbc_mode_params.crtc_id, + tmp[i].fb_id, + 0, 0, &fbc_mode_params.connector_id, 1, + fbc_mode_params.mode); + igt_assert(rc == 0); + igt_pipe_crc_collect_crc(pipe_crc, &tmp_crc[i]); + } + + for (i = 0; i < ntmp; i++) + igt_remove_fb(drm_fd, &tmp[i]); + + + /* Now that we have the CRCs, we can begin the test. */ + igt_create_fb(drm_fd, 64, 64, DRM_FORMAT_ARGB8888, I915_TILING_NONE, + &cursor); + fill_igt_fb(&cursor, 0xFFFFFF00); + + kmstest_unset_all_crtcs(drm_fd, drm_res); + enable_fbc_screen_and_wait(); + + rc = drmModeSetCursor(drm_fd, fbc_mode_params.crtc_id, 0, 0, 0); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeMoveCursor(drm_fd, fbc_mode_params.crtc_id, 0, 0); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeSetCursor(drm_fd, fbc_mode_params.crtc_id, + cursor.gem_handle, cursor.width, cursor.height); + igt_assert(rc == 0); + custom_crc = &tmp_crc[0]; + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_CUSTOM | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeMoveCursor(drm_fd, fbc_mode_params.crtc_id, 0, 0); + igt_assert(rc == 0); + custom_crc = &tmp_crc[0]; + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_CUSTOM | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeMoveCursor(drm_fd, fbc_mode_params.crtc_id, 64, 64); + igt_assert(rc == 0); + custom_crc = &tmp_crc[1]; + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_CUSTOM | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeSetCursor(drm_fd, fbc_mode_params.crtc_id, 0, 0, 0); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + + igt_remove_fb(drm_fd, &cursor); +} + +static void plane_subtest(void) +{ + int i, rc; + drmModePlaneResPtr planes; + uint32_t plane_id = 0; + struct igt_fb plane_fb1, plane_fb2; + struct rect rect = get_rect(0, &fbc_mode_params.fb); + + planes = drmModeGetPlaneResources(drm_fd); + for (i = 0; i < planes->count_planes && plane_id == 0; i++) { + drmModePlanePtr plane; + + plane = drmModeGetPlane(drm_fd, planes->planes[i]); + igt_assert(plane); + + /* We always use the first CRTC for FBC, so we can use 0x1 here + * as the index. */ + if (plane->possible_crtcs & 0x1) + plane_id = plane->plane_id; + + drmModeFreePlane(plane); + } + drmModeFreePlaneResources(planes); + igt_assert(plane_id); + + igt_create_fb(drm_fd, rect.w, rect.h, DRM_FORMAT_XRGB8888, + I915_TILING_X, &plane_fb1); + fill_igt_fb(&plane_fb1, rect.color); + + igt_create_fb(drm_fd, fbc_mode_params.fb.width, + fbc_mode_params.fb.height, DRM_FORMAT_XRGB8888, + I915_TILING_X, &plane_fb2); + fill_igt_fb(&plane_fb2, 0xFF); + draw_rect_pwrite(&plane_fb2, 0); + + kmstest_unset_all_crtcs(drm_fd, drm_res); + + enable_fbc_screen_and_wait(); + + rc = drmModeSetPlane(drm_fd, plane_id, fbc_mode_params.crtc_id, + plane_fb1.fb_id, 0, rect.x, rect.y, + plane_fb1.width, plane_fb1.height, + rect.x << 16, rect.y << 16, + plane_fb1.width << 16, plane_fb1.height << 16); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_RECT(0) | + ASSERT_NO_ACTION_CHANGE); + + rc = drmModeSetPlane(drm_fd, plane_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_NO_ACTION_CHANGE); + + /* A full-screen plane: FBC is useless here. */ + rc = drmModeSetPlane(drm_fd, plane_id, fbc_mode_params.crtc_id, + plane_fb2.fb_id, 0, 0, 0, + plane_fb2.width, plane_fb2.height, + 0 << 16, 0 << 16, + plane_fb2.width << 16, plane_fb2.height << 16); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_DISABLED | + ASSERT_CRC_RECT(0) | + ASSERT_LAST_ACTION_CHANGED); + + rc = drmModeSetPlane(drm_fd, plane_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + igt_assert(rc == 0); + do_assertions(ASSERT_FBC_ENABLED | + ASSERT_CRC_BLUE | + ASSERT_LAST_ACTION_CHANGED); + + igt_remove_fb(drm_fd, &plane_fb1); + igt_remove_fb(drm_fd, &plane_fb2); +} + +static int opt_handler(int opt, int opt_index) +{ + switch (opt) { + case 's': + check_status = false; + break; + case 'c': + check_crc = false; + break; + case 'o': + check_compression = false; + break; + case 'a': + check_last_action = false; + break; + case 'm': + manual = true; + 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-compression-check Don't check for the compression status\n" +" --no-action-check Don't check for last action\n" +" --manual Stop on each step so you can check the screen\n"; + +int main(int argc, char *argv[]) +{ + int i; + struct option long_options[] = { + { "no-status-check", 0, 0, 's'}, + { "no-crc-check", 0, 0, 'c'}, + { "no-compression-check", 0, 0, 'o'}, + { "no-action-check", 0, 0, 'a'}, + { "manual", 0, 0, 'm'}, + { 0, 0, 0, 0 } + }; + + igt_subtest_init_parse_opts(argc, argv, "", long_options, help_str, + opt_handler); + + igt_fixture + setup_environment(); + + igt_subtest("rte") + rte_subtest(false); + + /* Draw on the frontbuffer: make sure contents are visible. */ + for (i = 0; i < DRAW_METHOD_COUNT; i++) + igt_subtest_f("frontbuffer-%s", draw_method_names[i]) + frontbuffer_subtest(i); + + igt_subtest("flip") + flip_subtest(); + + /* Draw on a backbuffer: make sure FBC stays enabled. */ + for (i = 0; i < DRAW_METHOD_COUNT; i++) + igt_subtest_f("backbuffer-%s", draw_method_names[i]) + backbuffer_subtest(i); + + igt_subtest("cursor") + cursor_subtest(); + + igt_subtest("plane") + plane_subtest(); + + /* TODO: improve the multi-pipe tests after this one stops failing on + * the upstream trees. */ + igt_subtest("two-pipes-rte") { + igt_require(non_fbc_mode_params.connector_id); + rte_subtest(true); + } + + igt_fixture + teardown_environment(); + + igt_exit(); +} -- cgit v1.2.3