/* * Copyright © 2020 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 * Karthik B S */ #include "igt.h" #include "igt_aux.h" #include #include #include #define CURSOR_POS 128 /* * These constants can be tuned in case we start getting unexpected * results in CI. */ #define RUN_TIME 2 #define MIN_FLIPS_PER_FRAME 5 IGT_TEST_DESCRIPTION("Test asynchronous page flips."); typedef struct { int drm_fd; uint32_t crtc_id; uint32_t refresh_rate; struct igt_fb bufs[4]; igt_display_t display; drmModeConnectorPtr connector; unsigned long flip_timestamp_us; double flip_interval; igt_pipe_crc_t *pipe_crc; igt_crc_t ref_crc; int flip_count; int frame_count; bool flip_pending; } data_t; static drmModeConnectorPtr find_connector_for_modeset(data_t *data) { igt_output_t *output; drmModeConnectorPtr ret = NULL; for_each_connected_output(&data->display, output) { if (output->config.connector->count_modes > 0) { ret = output->config.connector; break; } } igt_assert_f(ret, "Connector NOT found\n"); return ret; } static void flip_handler(int fd_, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *_data) { data_t *data = _data; static double last_ms; double cur_ms; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); cur_ms = ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; if (last_ms) data->flip_interval = cur_ms - last_ms; else data->flip_interval = 0; last_ms = cur_ms; data->flip_timestamp_us = tv_sec * 1000000l + tv_usec; } static void wait_flip_event(data_t *data) { int ret; drmEventContext evctx; struct pollfd pfd; evctx.version = 2; evctx.vblank_handler = NULL; evctx.page_flip_handler = flip_handler; pfd.fd = data->drm_fd; pfd.events = POLLIN; pfd.revents = 0; ret = poll(&pfd, 1, 2000); switch (ret) { case 0: igt_assert_f(0, "Flip Timeout\n"); break; case 1: ret = drmHandleEvent(data->drm_fd, &evctx); igt_assert(ret == 0); break; default: /* unexpected */ igt_assert(0); } } static void make_fb(data_t *data, struct igt_fb *fb, drmModeConnectorPtr connector, int index) { uint32_t width, height; int rec_width; width = connector->modes[0].hdisplay; height = connector->modes[0].vdisplay; rec_width = width / (ARRAY_SIZE(data->bufs) * 2); igt_create_fb(data->drm_fd, width, height, DRM_FORMAT_XRGB8888, I915_FORMAT_MOD_X_TILED, fb); igt_draw_fill_fb(data->drm_fd, fb, 0x88); igt_draw_rect_fb(data->drm_fd, NULL, 0, fb, IGT_DRAW_MMAP_CPU, rec_width * 2 + rec_width * index, height / 4, rec_width, height / 2, rand()); } static void require_monotonic_timestamp(int fd) { igt_require_f(igt_has_drm_cap(fd, DRM_CAP_TIMESTAMP_MONOTONIC), "Monotonic timestamps not supported\n"); } static void test_async_flip(data_t *data, bool alternate_sync_async) { int ret, frame; long long int fps; struct timeval start, end, diff; require_monotonic_timestamp(data->drm_fd); gettimeofday(&start, NULL); frame = 1; do { int flags = DRM_MODE_PAGE_FLIP_ASYNC | DRM_MODE_PAGE_FLIP_EVENT; if (alternate_sync_async) { flags &= ~DRM_MODE_PAGE_FLIP_ASYNC; ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[frame % 4].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); flags |= DRM_MODE_PAGE_FLIP_ASYNC; /* * In older platforms (<= Gen10), async address update bit is double buffered. * So flip timestamp can be verified only from the second flip. * The first async flip just enables the async address update. */ if (is_i915_device(data->drm_fd)) { uint32_t devid = intel_get_drm_devid(data->drm_fd); if (IS_GEN9(devid) || IS_GEN10(devid)) { ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[frame % 4].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); } } } ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[frame % 4].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); gettimeofday(&end, NULL); timersub(&end, &start, &diff); if (alternate_sync_async) { igt_assert_f(data->flip_interval < 1000.0 / (data->refresh_rate * MIN_FLIPS_PER_FRAME), "Flip interval not significantly smaller than vblank interval\n" "Flip interval: %lfms, Refresh Rate = %dHz, Threshold = %d\n", data->flip_interval, data->refresh_rate, MIN_FLIPS_PER_FRAME); } frame++; } while (diff.tv_sec < RUN_TIME); if (!alternate_sync_async) { fps = frame * 1000 / RUN_TIME; igt_assert_f((fps / 1000) > (data->refresh_rate * MIN_FLIPS_PER_FRAME), "FPS should be significantly higher than the refresh rate\n"); } } static void wait_for_vblank(data_t *data, unsigned long *vbl_time, unsigned int *seq) { drmVBlank wait_vbl; uint32_t pipe_id_flag; int pipe; memset(&wait_vbl, 0, sizeof(wait_vbl)); pipe = kmstest_get_pipe_from_crtc_id(data->drm_fd, data->crtc_id); pipe_id_flag = kmstest_get_vbl_flag(pipe); wait_vbl.request.type = DRM_VBLANK_RELATIVE | pipe_id_flag; wait_vbl.request.sequence = 1; igt_assert(drmIoctl(data->drm_fd, DRM_IOCTL_WAIT_VBLANK, &wait_vbl) == 0); *vbl_time = wait_vbl.reply.tval_sec * 1000000 + wait_vbl.reply.tval_usec; *seq = wait_vbl.reply.sequence; } static void test_timestamp(data_t *data) { int flags = DRM_MODE_PAGE_FLIP_ASYNC | DRM_MODE_PAGE_FLIP_EVENT; unsigned long vbl_time, vbl_time1; unsigned int seq, seq1; int ret; require_monotonic_timestamp(data->drm_fd); /* * In older platforms(<= gen10), async address update bit is double buffered. * So flip timestamp can be verified only from the second flip. * The first async flip just enables the async address update. */ ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[0].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); wait_for_vblank(data, &vbl_time, &seq); ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[0].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); wait_for_vblank(data, &vbl_time1, &seq1); /* TODO: Make changes to do as many flips as possbile between two vblanks */ igt_assert_f(seq1 == seq + 1, "Vblank sequence is expected to be incremented by one(%d != (%d + 1)\n", seq1, seq); igt_info("vbl1_timestamp = %ldus\nflip_timestamp = %ldus\nvbl2_timestamp = %ldus\n", vbl_time, data->flip_timestamp_us, vbl_time1); igt_assert_f(vbl_time <= data->flip_timestamp_us && vbl_time1 > data->flip_timestamp_us, "Async flip time stamp is expected to be in between 2 vblank time stamps\n"); } static void test_cursor(data_t *data) { int flags = DRM_MODE_PAGE_FLIP_ASYNC | DRM_MODE_PAGE_FLIP_EVENT; int ret; uint64_t width, height; struct igt_fb cursor_fb; struct drm_mode_cursor cur; do_or_die(drmGetCap(data->drm_fd, DRM_CAP_CURSOR_WIDTH, &width)); do_or_die(drmGetCap(data->drm_fd, DRM_CAP_CURSOR_WIDTH, &height)); igt_create_color_fb(data->drm_fd, width, height, DRM_FORMAT_ARGB8888, DRM_FORMAT_MOD_LINEAR, 1., 1., 1., &cursor_fb); cur.flags = DRM_MODE_CURSOR_BO; cur.crtc_id = data->crtc_id; cur.width = width; cur.height = height; cur.handle = cursor_fb.gem_handle; do_ioctl(data->drm_fd, DRM_IOCTL_MODE_CURSOR, &cur); ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[0].fb_id, flags, data); igt_assert(ret == 0); wait_flip_event(data); cur.flags = DRM_MODE_CURSOR_MOVE; cur.x = CURSOR_POS; cur.y = CURSOR_POS; do_ioctl(data->drm_fd, DRM_IOCTL_MODE_CURSOR, &cur); igt_remove_fb(data->drm_fd, &cursor_fb); } static void test_invalid(data_t *data) { int flags = DRM_MODE_PAGE_FLIP_ASYNC | DRM_MODE_PAGE_FLIP_EVENT; int ret; uint32_t width, height; struct igt_fb fb; width = data->connector->modes[0].hdisplay; height = data->connector->modes[0].vdisplay; igt_require(igt_display_has_format_mod(&data->display, DRM_FORMAT_XRGB8888, I915_FORMAT_MOD_Y_TILED)); igt_create_fb(data->drm_fd, width, height, DRM_FORMAT_XRGB8888, I915_FORMAT_MOD_Y_TILED, &fb); /* Flip with a different fb modifier which is expected to be rejected */ ret = drmModePageFlip(data->drm_fd, data->crtc_id, fb.fb_id, flags, data); igt_assert(ret == -EINVAL); /* TODO: Add verification for changes in stride, pixel format */ igt_remove_fb(data->drm_fd, &fb); } static void test_init(data_t *data) { drmModeResPtr res; int i, ret; res = drmModeGetResources(data->drm_fd); igt_assert(res); kmstest_unset_all_crtcs(data->drm_fd, res); data->connector = find_connector_for_modeset(data); data->crtc_id = kmstest_find_crtc_for_connector(data->drm_fd, res, data->connector, 0); data->refresh_rate = data->connector->modes[0].vrefresh; for (i = 0; i < ARRAY_SIZE(data->bufs); i++) make_fb(data, &data->bufs[i], data->connector, i); ret = drmModeSetCrtc(data->drm_fd, data->crtc_id, data->bufs[0].fb_id, 0, 0, &data->connector->connector_id, 1, &data->connector->modes[0]); igt_assert(ret == 0); drmModeFreeResources(res); } static void queue_vblank(data_t *data) { drmVBlank wait_vbl = { .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT | kmstest_get_pipe_from_crtc_id(data->drm_fd, data->crtc_id), .request.sequence = 1, .request.signal = (long)data, }; igt_assert(drmIoctl(data->drm_fd, DRM_IOCTL_WAIT_VBLANK, &wait_vbl) == 0); } static void vblank_handler_crc(int fd_, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *_data) { data_t *data = _data; igt_crc_t crc; data->frame_count++; igt_pipe_crc_get_single(data->pipe_crc, &crc); igt_assert_crc_equal(&data->ref_crc, &crc); /* check again next vblank */ queue_vblank(data); } static void flip_handler_crc(int fd_, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *_data) { data_t *data = _data; data->flip_pending = false; data->flip_count++; } static void wait_events_crc(data_t *data) { drmEventContext evctx = { .version = 2, .vblank_handler = vblank_handler_crc, .page_flip_handler = flip_handler_crc, }; while (data->flip_pending) { struct pollfd pfd = { .fd = data->drm_fd, .events = POLLIN, }; int ret; ret = poll(&pfd, 1, 2000); switch (ret) { case 0: igt_assert_f(0, "Flip Timeout\n"); break; case 1: ret = drmHandleEvent(data->drm_fd, &evctx); igt_assert(ret == 0); break; default: /* unexpected */ igt_assert(0); } } } static unsigned int clock_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } static void paint_fb(int fd, struct igt_fb *fb, uint32_t color) { igt_draw_rect_fb(fd, NULL, 0, fb, gem_has_mappable_ggtt(fd) ? IGT_DRAW_MMAP_GTT : IGT_DRAW_MMAP_WC, 0, 0, 1, fb->height, color); } static void test_crc(data_t *data) { unsigned int frame = 0; unsigned int start; int ret; data->flip_count = 0; data->frame_count = 0; data->flip_pending = false; igt_draw_fill_fb(data->drm_fd, &data->bufs[frame], 0xff0000ff); igt_draw_fill_fb(data->drm_fd, &data->bufs[!frame], 0xff0000ff); ret = drmModeSetCrtc(data->drm_fd, data->crtc_id, data->bufs[frame].fb_id, 0, 0, &data->connector->connector_id, 1, &data->connector->modes[0]); igt_assert_eq(ret, 0); data->pipe_crc = igt_pipe_crc_new(data->drm_fd, kmstest_get_pipe_from_crtc_id(data->drm_fd, data->crtc_id), INTEL_PIPE_CRC_SOURCE_AUTO); igt_pipe_crc_start(data->pipe_crc); igt_pipe_crc_get_single(data->pipe_crc, &data->ref_crc); queue_vblank(data); start = clock_ms(); while (clock_ms() - start < 2000) { /* fill the next fb with the expected color */ paint_fb(data->drm_fd, &data->bufs[frame], 0xff0000ff); data->flip_pending = true; ret = drmModePageFlip(data->drm_fd, data->crtc_id, data->bufs[frame].fb_id, DRM_MODE_PAGE_FLIP_ASYNC | DRM_MODE_PAGE_FLIP_EVENT, data); igt_assert_eq(ret, 0); wait_events_crc(data); /* clobber the previous fb which should no longer be scanned out */ frame = !frame; paint_fb(data->drm_fd, &data->bufs[frame], rand()); } igt_pipe_crc_stop(data->pipe_crc); igt_pipe_crc_free(data->pipe_crc); /* make sure we got at a reasonable number of async flips done */ igt_assert_lt(data->frame_count * 2, data->flip_count); } igt_main { static data_t data; int i; igt_fixture { data.drm_fd = drm_open_driver_master(DRIVER_ANY); kmstest_set_vt_graphics_mode(); igt_display_require(&data.display, data.drm_fd); igt_display_require_output(&data.display); igt_require_f(igt_has_drm_cap(data.drm_fd, DRM_CAP_ASYNC_PAGE_FLIP), "Async Flip is not supported\n"); } igt_describe("Verify the async flip functionality and the fps during async flips"); igt_subtest_group { igt_fixture test_init(&data); igt_describe("Wait for page flip events in between successive asynchronous flips"); igt_subtest("async-flip-with-page-flip-events") test_async_flip(&data, false); igt_describe("Alternate between sync and async flips"); igt_subtest("alternate-sync-async-flip") test_async_flip(&data, true); igt_describe("Verify that the async flip timestamp does not coincide with either previous or next vblank"); igt_subtest("test-time-stamp") test_timestamp(&data); igt_describe("Verify that the DRM_IOCTL_MODE_CURSOR passes after async flip"); igt_subtest("test-cursor") test_cursor(&data); igt_describe("Negative case to verify if changes in fb are rejected from kernel as expected"); igt_subtest("invalid-async-flip") test_invalid(&data); igt_describe("Use CRC to verify async flip scans out the correct framebuffer"); igt_subtest("crc") test_crc(&data); igt_fixture { for (i = 0; i < ARRAY_SIZE(data.bufs); i++) igt_remove_fb(data.drm_fd, &data.bufs[i]); } } igt_fixture igt_display_fini(&data.display); }