/* * Copyright © 2016 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. */ #include "igt.h" #include #include #include IGT_TEST_DESCRIPTION("Basic check of KMS ABI with busy framebuffers."); static igt_output_t * set_fb_on_crtc(igt_display_t *dpy, int pipe, struct igt_fb *fb) { drmModeModeInfoPtr mode; igt_plane_t *primary; igt_output_t *output; output = igt_get_single_output_for_pipe(dpy, pipe); igt_output_set_pipe(output, pipe); mode = igt_output_get_mode(output); igt_create_pattern_fb(dpy->drm_fd, mode->hdisplay, mode->vdisplay, DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED, fb); primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_plane_set_fb(primary, fb); return output; } static void do_cleanup_display(igt_display_t *dpy) { enum pipe pipe; igt_output_t *output; igt_plane_t *plane; for_each_pipe(dpy, pipe) for_each_plane_on_pipe(dpy, pipe, plane) igt_plane_set_fb(plane, NULL); for_each_connected_output(dpy, output) igt_output_set_pipe(output, PIPE_NONE); igt_display_commit2(dpy, dpy->is_atomic ? COMMIT_ATOMIC : COMMIT_LEGACY); } static void flip_to_fb(igt_display_t *dpy, int pipe, igt_output_t *output, struct igt_fb *fb, unsigned ring, const char *name, bool modeset) { struct pollfd pfd = { .fd = dpy->drm_fd, .events = POLLIN }; const int timeout = modeset ? 8500 : 100; struct drm_event_vblank ev; igt_spin_t *t = igt_spin_new(dpy->drm_fd, .engine = ring, .dependency = fb->gem_handle); if (modeset) { /* * We want to check that a modeset actually waits for the * spin batch to complete, but we keep a bigger timeout for * disable than required for flipping. * * As a result, the GPU reset code may kick in, which we neuter * here to be sure there's no premature completion. */ igt_set_module_param_int("enable_hangcheck", 0); } igt_fork(child, 1) { igt_assert(gem_bo_busy(dpy->drm_fd, fb->gem_handle)); if (!modeset) do_or_die(drmModePageFlip(dpy->drm_fd, dpy->pipes[pipe].crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, fb)); else { igt_plane_set_fb(igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY), fb); igt_output_set_pipe(output, PIPE_NONE); igt_display_commit_atomic(dpy, DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); } igt_assert_f(poll(&pfd, 1, timeout) == 0, "flip completed whilst %s was busy [%d]\n", name, gem_bo_busy(dpy->drm_fd, fb->gem_handle)); igt_assert(gem_bo_busy(dpy->drm_fd, fb->gem_handle)); } igt_waitchildren_timeout(5 * timeout, "flip blocked waiting for busy bo\n"); igt_spin_end(t); igt_assert(read(dpy->drm_fd, &ev, sizeof(ev)) == sizeof(ev)); igt_assert(poll(&pfd, 1, 0) == 0); if (modeset) { gem_quiescent_gpu(dpy->drm_fd); igt_set_module_param_int("enable_hangcheck", 1); /* Clear old mode blob. */ igt_pipe_refresh(dpy, pipe, true); igt_output_set_pipe(output, pipe); igt_display_commit2(dpy, COMMIT_ATOMIC); } igt_spin_free(dpy->drm_fd, t); } static void test_flip(igt_display_t *dpy, unsigned ring, int pipe, bool modeset) { struct igt_fb fb[2]; int warmup[] = { 0, 1, 0, -1 }; igt_output_t *output; if (modeset) igt_require(dpy->is_atomic); output = set_fb_on_crtc(dpy, pipe, &fb[0]); igt_display_commit2(dpy, COMMIT_LEGACY); igt_create_pattern_fb(dpy->drm_fd, fb[0].width, fb[0].height, DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED, &fb[1]); /* Bind both fb to the display (such that they are ready for future * flips without stalling for the bind) leaving fb[0] as bound. */ for (int i = 0; warmup[i] != -1; i++) { struct drm_event_vblank ev; do_or_die(drmModePageFlip(dpy->drm_fd, dpy->pipes[pipe].crtc_id, fb[warmup[i]].fb_id, DRM_MODE_PAGE_FLIP_EVENT, &fb[warmup[i]])); igt_assert(read(dpy->drm_fd, &ev, sizeof(ev)) == sizeof(ev)); } /* Make the frontbuffer busy and try to flip to itself */ flip_to_fb(dpy, pipe, output, &fb[0], ring, "fb[0]", modeset); /* Repeat for flip to second buffer */ flip_to_fb(dpy, pipe, output, &fb[1], ring, "fb[1]", modeset); do_cleanup_display(dpy); igt_remove_fb(dpy->drm_fd, &fb[1]); igt_remove_fb(dpy->drm_fd, &fb[0]); } static void test_atomic_commit_hang(igt_display_t *dpy, igt_plane_t *primary, struct igt_fb *busy_fb, unsigned ring) { igt_spin_t *t = igt_spin_new(dpy->drm_fd, .engine = ring, .dependency = busy_fb->gem_handle); struct pollfd pfd = { .fd = dpy->drm_fd, .events = POLLIN }; unsigned flags = 0; struct drm_event_vblank ev; flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; flags |= DRM_MODE_ATOMIC_NONBLOCK; flags |= DRM_MODE_PAGE_FLIP_EVENT; igt_display_commit_atomic(dpy, flags, NULL); igt_fork(child, 1) { /* * bit of a hack, just set atomic commit to NULL fb to make sure * that we don't wait for the new update to complete. */ igt_plane_set_fb(primary, NULL); igt_display_commit_atomic(dpy, 0, NULL); igt_assert_f(poll(&pfd, 1, 1) > 0, "nonblocking update completed whilst fb[%d] was still busy [%d]\n", busy_fb->fb_id, gem_bo_busy(dpy->drm_fd, busy_fb->gem_handle)); } igt_waitchildren(); igt_assert(read(dpy->drm_fd, &ev, sizeof(ev)) == sizeof(ev)); igt_spin_end(t); } static void test_hang(igt_display_t *dpy, unsigned ring, enum pipe pipe, bool modeset, bool hang_newfb) { struct igt_fb fb[2]; igt_output_t *output; igt_plane_t *primary; output = set_fb_on_crtc(dpy, pipe, &fb[0]); igt_display_commit2(dpy, COMMIT_ATOMIC); primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_create_pattern_fb(dpy->drm_fd, fb[0].width, fb[0].height, DRM_FORMAT_XRGB8888, LOCAL_I915_FORMAT_MOD_X_TILED, &fb[1]); if (modeset) { /* Test modeset disable with hang */ igt_output_set_pipe(output, PIPE_NONE); igt_plane_set_fb(primary, &fb[1]); test_atomic_commit_hang(dpy, primary, &fb[hang_newfb], ring); /* Test modeset enable with hang */ igt_plane_set_fb(primary, &fb[0]); igt_output_set_pipe(output, pipe); test_atomic_commit_hang(dpy, primary, &fb[!hang_newfb], ring); } else { /* * Test what happens with a single hanging pageflip. * This always completes early, because we have some * timeouts taking care of it. */ igt_plane_set_fb(primary, &fb[1]); test_atomic_commit_hang(dpy, primary, &fb[hang_newfb], ring); } do_cleanup_display(dpy); igt_remove_fb(dpy->drm_fd, &fb[1]); igt_remove_fb(dpy->drm_fd, &fb[0]); } static void test_pageflip_modeset_hang(igt_display_t *dpy, unsigned ring, enum pipe pipe) { struct igt_fb fb; struct drm_event_vblank ev; igt_output_t *output; igt_plane_t *primary; igt_spin_t *t; output = set_fb_on_crtc(dpy, pipe, &fb); primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY); igt_display_commit2(dpy, dpy->is_atomic ? COMMIT_ATOMIC : COMMIT_LEGACY); t = igt_spin_new(dpy->drm_fd, .engine = ring, .dependency = fb.gem_handle); do_or_die(drmModePageFlip(dpy->drm_fd, dpy->pipes[pipe].crtc_id, fb.fb_id, DRM_MODE_PAGE_FLIP_EVENT, &fb)); /* Kill crtc with hung fb */ igt_plane_set_fb(primary, NULL); igt_output_set_pipe(output, PIPE_NONE); igt_display_commit2(dpy, dpy->is_atomic ? COMMIT_ATOMIC : COMMIT_LEGACY); igt_assert(read(dpy->drm_fd, &ev, sizeof(ev)) == sizeof(ev)); igt_spin_end(t); igt_remove_fb(dpy->drm_fd, &fb); } igt_main { igt_display_t display = { .drm_fd = -1, .n_pipes = IGT_MAX_PIPES }; /* we only test on render */ const struct intel_execution_engine *e = &intel_execution_engines[1]; enum pipe n; igt_skip_on_simulation(); igt_fixture { int fd = drm_open_driver_master(DRIVER_INTEL); igt_require_gem(fd); gem_require_mmap_wc(fd); kmstest_set_vt_graphics_mode(); igt_display_require(&display, fd); } /* XXX Extend to cover atomic rendering tests to all planes + legacy */ for_each_pipe_static(n) igt_subtest_group { igt_hang_t hang; errno = 0; igt_fixture { igt_display_require_output_on_pipe(&display, n); } igt_subtest_f("basic-flip-%s", kmstest_pipe_name(n)) { igt_require(gem_has_ring(display.drm_fd, e->exec_id | e->flags)); test_flip(&display, e->exec_id | e->flags, n, false); } igt_subtest_f("basic-modeset-%s", kmstest_pipe_name(n)) { igt_require(gem_has_ring(display.drm_fd, e->exec_id | e->flags)); test_flip(&display, e->exec_id | e->flags, n, true); } igt_fixture { igt_require(gem_has_ring(display.drm_fd, e->exec_id | e->flags)); hang = igt_allow_hang(display.drm_fd, 0, 0); } igt_subtest_f("extended-pageflip-modeset-hang-oldfb-%s-%s", e->name, kmstest_pipe_name(n)) { igt_require(gem_has_ring(display.drm_fd, e->exec_id | e->flags)); test_pageflip_modeset_hang(&display, e->exec_id | e->flags, n); } igt_fixture igt_require(display.is_atomic); igt_subtest_f("extended-pageflip-hang-oldfb-%s-%s", e->name, kmstest_pipe_name(n)) test_hang(&display, e->exec_id | e->flags, n, false, false); igt_subtest_f("extended-pageflip-hang-newfb-%s-%s", e->name, kmstest_pipe_name(n)) test_hang(&display, e->exec_id | e->flags, n, false, true); igt_subtest_f("extended-modeset-hang-oldfb-%s-%s", e->name, kmstest_pipe_name(n)) test_hang(&display, e->exec_id | e->flags, n, true, false); igt_subtest_f("extended-modeset-hang-newfb-%s-%s", e->name, kmstest_pipe_name(n)) test_hang(&display, e->exec_id | e->flags, n, true, true); igt_subtest_f("extended-modeset-hang-oldfb-with-reset-%s-%s", e->name, kmstest_pipe_name(n)) { igt_set_module_param_int("force_reset_modeset_test", 1); test_hang(&display, e->exec_id | e->flags, n, true, false); igt_set_module_param_int("force_reset_modeset_test", 0); } igt_subtest_f("extended-modeset-hang-newfb-with-reset-%s-%s", e->name, kmstest_pipe_name(n)) { igt_set_module_param_int("force_reset_modeset_test", 1); test_hang(&display, e->exec_id | e->flags, n, true, true); igt_set_module_param_int("force_reset_modeset_test", 0); } igt_fixture { igt_disallow_hang(display.drm_fd, hang); } } igt_fixture { igt_display_fini(&display); } }