/* * Copyright © 2013 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. * */ #define _GNU_SOURCE #include #include "igt.h" #include "igt_stats.h" #if defined(__x86_64__) || defined(__i386__) #define cpu_relax() __builtin_ia32_pause() #else #define cpu_relax() asm volatile("": : :"memory") #endif IGT_TEST_DESCRIPTION("Stress legacy cursor ioctl"); struct data { int fd; drmModeRes *resources; }; static uint32_t state = 0x12345678; static uint32_t hars_petruska_f54_1_random (void) { #define rol(x,k) ((x << k) | (x >> (32-k))) return state = (state ^ rol (state, 5) ^ rol (state, 24)) + 0x37798849; #undef rol } static void stress(struct data *data, uint32_t *crtc_id, unsigned num_crtcs, int num_children, unsigned mode, int timeout) { struct drm_mode_cursor arg; uint64_t *results; bool torture; int n; torture = false; if (num_children < 0) { torture = true; num_children = -num_children; } results = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); igt_assert(results != MAP_FAILED); memset(&arg, 0, sizeof(arg)); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = 0; arg.width = 64; arg.height = 64; arg.handle = gem_create(data->fd, 4*64*64); for (n = 0; n < num_crtcs; n++) { arg.crtc_id = crtc_id[n]; drmIoctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); } arg.flags = mode; igt_fork(child, num_children) { struct sched_param rt = {.sched_priority = 99 }; cpu_set_t allowed; unsigned long count = 0; sched_setscheduler(getpid(), SCHED_RR, &rt); CPU_ZERO(&allowed); CPU_SET(child, &allowed); sched_setaffinity(getpid(), sizeof(cpu_set_t), &allowed); state ^= child; igt_until_timeout(timeout) { arg.crtc_id = crtc_id[hars_petruska_f54_1_random() % num_crtcs]; do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); count++; } igt_debug("[%d] count=%lu\n", child, count); results[child] = count; } if (torture) { igt_fork(child, num_children) { struct sched_param rt = {.sched_priority = 1 }; cpu_set_t allowed; unsigned long long count = 0; sched_setscheduler(getpid(), SCHED_RR, &rt); CPU_ZERO(&allowed); CPU_SET(child, &allowed); sched_setaffinity(getpid(), sizeof(cpu_set_t), &allowed); igt_until_timeout(timeout) { count++; cpu_relax(); } igt_debug("[hog:%d] count=%llu\n", child, count); } } igt_waitchildren(); if (num_children > 1) { igt_stats_t stats; igt_stats_init_with_size(&stats, num_children); results[num_children] = 0; for (int child = 0; child < num_children; child++) { igt_stats_push(&stats, results[child]); results[num_children] += results[child]; } igt_info("Total updates %llu (median of %d processes is %.2f)\n", (long long)results[num_children], num_children, igt_stats_get_median(&stats)); igt_stats_fini(&stats); } else { igt_info("Total updates %llu\n", (long long)results[0]); } gem_close(data->fd, arg.handle); munmap(results, 4096); } static bool set_fb_on_crtc(struct data *data, int pipe, struct igt_fb *fb_info) { struct drm_mode_modeinfo *modes = malloc(4096*sizeof(*modes)); uint32_t encoders[32]; for (int o = 0; o < data->resources->count_connectors; o++) { struct drm_mode_get_connector conn; struct drm_mode_crtc set; int e, m; memset(&conn, 0, sizeof(conn)); conn.connector_id = data->resources->connectors[o]; conn.count_modes = 4096; conn.modes_ptr = (uintptr_t)modes; conn.count_encoders = 32; conn.encoders_ptr = (uintptr_t)encoders; drmIoctl(data->fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn); for (e = 0; e < conn.count_encoders; e++) { struct drm_mode_get_encoder enc; memset(&enc, 0, sizeof(enc)); enc.encoder_id = encoders[e]; drmIoctl(data->fd, DRM_IOCTL_MODE_GETENCODER, &enc); if (enc.possible_crtcs & (1 << pipe)) break; } if (e == conn.count_encoders) continue; for (m = 0; m < conn.count_modes; m++) { if (modes[m].hdisplay == fb_info->width && modes[m].vdisplay == fb_info->height) break; } if (m == conn.count_modes) continue; memset(&set, 0, sizeof(set)); set.crtc_id = data->resources->crtcs[pipe]; set.fb_id = fb_info->fb_id; set.set_connectors_ptr = (uintptr_t)&conn.connector_id; set.count_connectors = 1; set.mode = modes[m]; set.mode_valid = 1; if (drmIoctl(data->fd, DRM_IOCTL_MODE_SETCRTC, &set) == 0) return true; } return false; } static void flip(struct data *data, int cursor_pipe, int flip_pipe, int timeout) { struct drm_mode_cursor arg; uint64_t *results; struct igt_fb fb_info; uint32_t fb_id; results = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); igt_assert(results != MAP_FAILED); memset(&arg, 0, sizeof(arg)); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = data->resources->crtcs[cursor_pipe]; arg.width = 64; arg.height = 64; arg.handle = gem_create(data->fd, 4*64*64); drmIoctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); fb_id = igt_create_fb(data->fd, 1024, 768, DRM_FORMAT_XRGB8888, I915_TILING_NONE, &fb_info); igt_require(set_fb_on_crtc(data, flip_pipe, &fb_info)); arg.flags = DRM_MODE_CURSOR_MOVE; igt_fork(child, 1) { unsigned long count = 0; igt_until_timeout(timeout) { do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); count++; } igt_debug("cursor count=%lu\n", count); results[0] = count; } igt_fork(child, 1) { unsigned long count = 0; unsigned crtc = data->resources->crtcs[flip_pipe]; igt_until_timeout(timeout) { char buf[128]; drmModePageFlip(data->fd, crtc, fb_id, DRM_MODE_PAGE_FLIP_EVENT, NULL); while (read(data->fd, buf, sizeof(buf)) < 0 && (errno == EINTR || errno == EAGAIN)) ; count++; } igt_debug("flip count=%lu\n", count); results[1] = count; } igt_waitchildren(); gem_close(data->fd, arg.handle); munmap(results, 4096); } static inline uint32_t pipe_select(int pipe) { if (pipe > 1) return pipe << DRM_VBLANK_HIGH_CRTC_SHIFT; else if (pipe > 0) return DRM_VBLANK_SECONDARY; else return 0; } static unsigned get_vblank(int fd, int pipe, unsigned flags) { union drm_wait_vblank vbl; memset(&vbl, 0, sizeof(vbl)); vbl.request.type = DRM_VBLANK_RELATIVE | pipe_select(pipe) | flags; if (drmIoctl(fd, DRM_IOCTL_WAIT_VBLANK, &vbl)) return 0; return vbl.reply.sequence; } static void basic_flip_vs_cursor(struct data *data, int nloops) { struct drm_mode_cursor arg; struct drm_event_vblank vbl; struct igt_fb fb_info; unsigned vblank_start; int target; uint32_t fb_id; memset(&arg, 0, sizeof(arg)); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = data->resources->crtcs[0]; arg.width = 64; arg.height = 64; arg.handle = gem_create(data->fd, 4*64*64); drmIoctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); arg.flags = DRM_MODE_CURSOR_MOVE; fb_id = igt_create_fb(data->fd, 1024, 768, DRM_FORMAT_XRGB8888, I915_TILING_NONE, &fb_info); igt_require(set_fb_on_crtc(data, 0, &fb_info)); target = 4096; do { vblank_start = get_vblank(data->fd, 0, DRM_VBLANK_NEXTONMISS); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); for (int n = 0; n < target; n++) do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); target /= 2; if (get_vblank(data->fd, 0, 0) == vblank_start) break; } while (target); igt_require(target > 1); igt_debug("Using a target of %d cursor updates per half-vblank\n", target); vblank_start = get_vblank(data->fd, 0, DRM_VBLANK_NEXTONMISS); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); for (int n = 0; n < target; n++) do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); while (nloops--) { /* Start with a synchronous query to align with the vblank */ vblank_start = get_vblank(data->fd, 0, DRM_VBLANK_NEXTONMISS); do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); /* Schedule a nonblocking flip for the next vblank */ do_or_die(drmModePageFlip(data->fd, arg.crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, &fb_id)); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); for (int n = 0; n < target; n++) do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); igt_set_timeout(1, "Stuck page flip"); igt_ignore_warn(read(data->fd, &vbl, sizeof(vbl))); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start + 1); igt_reset_timeout(); } igt_remove_fb(data->fd, &fb_info); gem_close(data->fd, arg.handle); } static void basic_cursor_vs_flip(struct data *data, int nloops) { struct drm_mode_cursor arg; struct drm_event_vblank vbl; struct igt_fb fb_info; unsigned vblank_start, vblank_last; volatile unsigned long *shared; int target; uint32_t fb_id; shared = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); igt_assert(shared != MAP_FAILED); memset(&arg, 0, sizeof(arg)); arg.flags = DRM_MODE_CURSOR_BO; arg.crtc_id = data->resources->crtcs[0]; arg.width = 64; arg.height = 64; arg.handle = gem_create(data->fd, 4*64*64); drmIoctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); arg.flags = DRM_MODE_CURSOR_MOVE; fb_id = igt_create_fb(data->fd, 1024, 768, DRM_FORMAT_XRGB8888, I915_TILING_NONE, &fb_info); igt_require(set_fb_on_crtc(data, 0, &fb_info)); target = 4096; do { vblank_start = get_vblank(data->fd, 0, DRM_VBLANK_NEXTONMISS); igt_assert_eq(get_vblank(data->fd, 0, 0), vblank_start); for (int n = 0; n < target; n++) do_ioctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); target /= 2; if (get_vblank(data->fd, 0, 0) == vblank_start) break; } while (target); igt_require(target > 1); igt_debug("Using a target of %d cursor updates per half-vblank\n", target); for (int i = 0; i < nloops; i++) { shared[0] = 0; igt_fork(child, 1) { unsigned long count = 0; while (!shared[0]) { drmIoctl(data->fd, DRM_IOCTL_MODE_CURSOR, &arg); count++; } igt_debug("child: %lu cursor updates\n", count); shared[0] = count; } do_or_die(drmModePageFlip(data->fd, arg.crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, &fb_id)); igt_assert_eq(read(data->fd, &vbl, sizeof(vbl)), sizeof(vbl)); vblank_start = vblank_last = vbl.sequence; for (int n = 0; n < 60; n++) { do_or_die(drmModePageFlip(data->fd, arg.crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, &fb_id)); igt_assert_eq(read(data->fd, &vbl, sizeof(vbl)), sizeof(vbl)); if (vbl.sequence != vblank_last + 1) { igt_warn("page flip %d was delayed, missed %d frames\n", n, vbl.sequence - vblank_last - 1); } vblank_last = vbl.sequence; } igt_assert_eq(vbl.sequence, vblank_start + 60); shared[0] = 1; igt_waitchildren(); igt_assert_f(shared[0] > 60*target, "completed %lu cursor updated in a period of 60 flips, " "we expect to complete approximately %lu updateds, " "with the threshold set at %lu\n", shared[0], 2*60ul*target, 60ul*target); } igt_remove_fb(data->fd, &fb_info); gem_close(data->fd, arg.handle); munmap((void *)shared, 4096); } igt_main { const int ncpus = sysconf(_SC_NPROCESSORS_ONLN); struct data data = { .fd = -1 }; igt_skip_on_simulation(); igt_fixture { data.fd = drm_open_driver_master(DRIVER_INTEL); kmstest_set_vt_graphics_mode(); data.resources = drmModeGetResources(data.fd); igt_assert(data.resources); } igt_subtest_group { for (int n = 0; n < 26; n++) { uint32_t *crtcs = NULL; errno = 0; igt_fixture { igt_skip_on(n >= data.resources->count_crtcs); crtcs = &data.resources->crtcs[n]; } igt_subtest_f("single-%c-bo", 'A' + n) stress(&data, crtcs, 1, 1, DRM_MODE_CURSOR_BO, 20); igt_subtest_f("single-%c-move", 'A' + n) stress(&data, crtcs, 1, 1, DRM_MODE_CURSOR_MOVE, 20); igt_subtest_f("forked-%c-bo", 'A' + n) stress(&data, crtcs, 1, ncpus, DRM_MODE_CURSOR_BO, 20); igt_subtest_f("forked-%c-move", 'A' + n) stress(&data, crtcs, 1, ncpus, DRM_MODE_CURSOR_MOVE, 20); igt_subtest_f("torture-%c-bo", 'A' + n) stress(&data, crtcs, 1, -ncpus, DRM_MODE_CURSOR_BO, 20); igt_subtest_f("torture-%c-move", 'A' + n) stress(&data, crtcs, 1, -ncpus, DRM_MODE_CURSOR_MOVE, 20); } } igt_subtest("single-all-bo") stress(&data, data.resources->crtcs, data.resources->count_crtcs, 1, DRM_MODE_CURSOR_BO, 20); igt_subtest("single-all-move") stress(&data, data.resources->crtcs, data.resources->count_crtcs, 1, DRM_MODE_CURSOR_MOVE, 20); igt_subtest("forked-all-bo") stress(&data, data.resources->crtcs, data.resources->count_crtcs, ncpus, DRM_MODE_CURSOR_BO, 20); igt_subtest("forked-all-move") stress(&data, data.resources->crtcs, data.resources->count_crtcs, ncpus, DRM_MODE_CURSOR_MOVE, 20); igt_subtest("torture-all-bo") stress(&data, data.resources->crtcs, data.resources->count_crtcs, -ncpus, DRM_MODE_CURSOR_BO, 20); igt_subtest("torture-all-move") stress(&data, data.resources->crtcs, data.resources->count_crtcs, -ncpus, DRM_MODE_CURSOR_MOVE, 20); igt_subtest_group { igt_subtest("basic-flip-vs-cursor") basic_flip_vs_cursor(&data, 1); igt_subtest("long-flip-vs-cursor") basic_flip_vs_cursor(&data, 150); igt_subtest("basic-cursor-vs-flip") basic_cursor_vs_flip(&data, 1); igt_subtest("long-cursor-vs-flip") basic_cursor_vs_flip(&data, 150); igt_subtest("cursorA-vs-flipA") flip(&data, 0, 0, 10); igt_subtest("cursorA-vs-flipB") flip(&data, 0, 1, 10); igt_subtest("cursorB-vs-flipA") flip(&data, 1, 0, 10); igt_subtest("cursorB-vs-flipB") flip(&data, 1, 1, 10); } igt_fixture { drmModeFreeResources(data.resources); } }