From acdf7171264a5139caabb78127c9eef5080c0682 Mon Sep 17 00:00:00 2001 From: Daniel Vetter Date: Sat, 15 Dec 2012 00:48:47 +0100 Subject: tests: s/flip_test/kms_flip OCD wins! Signed-off-by: Daniel Vetter --- tests/.gitignore | 2 +- tests/Makefile.am | 4 +- tests/flip_test.c | 1066 ----------------------------------------------------- tests/kms_flip.c | 1066 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1069 insertions(+), 1069 deletions(-) delete mode 100644 tests/flip_test.c create mode 100644 tests/kms_flip.c diff --git a/tests/.gitignore b/tests/.gitignore index 8f7961f..446e62c 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,4 +1,4 @@ -flip_test +kms_flip drm_vma_limiter drm_vma_limiter_cached drm_vma_limiter_cpu diff --git a/tests/Makefile.am b/tests/Makefile.am index da3e8c4..9cafe4c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,8 +28,8 @@ TESTS_progs_M = \ gem_ringfill \ gem_set_tiling_vs_blt \ gem_tiled_partial_pwrite_pread \ - flip_test \ $(NOUVEAU_TESTS_M) \ + kms_flip \ $(NULL) TESTS_progs = \ @@ -180,7 +180,7 @@ gem_fence_thrash_LDADD = $(LDADD) -lpthread gem_threaded_access_tiled_LDADD = $(LDADD) -lpthread gem_wait_render_timeout_LDADD = $(LDADD) -lrt -flip_test_LDADD = $(LDADD) -lrt +kms_flip = $(LDADD) -lrt gem_ctx_basic_LDADD = $(LDADD) -lpthread diff --git a/tests/flip_test.c b/tests/flip_test.c deleted file mode 100644 index e05efb1..0000000 --- a/tests/flip_test.c +++ /dev/null @@ -1,1066 +0,0 @@ -/* - * Copyright 2012 Intel Corporation - * Jesse Barnes - * - * 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 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 "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "i915_drm.h" -#include "drmtest.h" -#include "testdisplay.h" -#include "intel_bufmgr.h" -#include "intel_batchbuffer.h" -#include "intel_gpu_tools.h" - -#define TEST_DPMS (1 << 0) -#define TEST_WITH_DUMMY_LOAD (1 << 1) -#define TEST_PAN (1 << 2) -#define TEST_MODESET (1 << 3) -#define TEST_CHECK_TS (1 << 4) -#define TEST_EBUSY (1 << 5) -#define TEST_EINVAL (1 << 6) -#define TEST_FLIP (1 << 7) -#define TEST_VBLANK (1 << 8) -#define TEST_VBLANK_BLOCK (1 << 9) -#define TEST_VBLANK_ABSOLUTE (1 << 10) -#define TEST_VBLANK_EXPIRED_SEQ (1 << 11) -#define TEST_FB_RECREATE (1 << 12) -#define TEST_RMFB (1 << 13) - -#define EVENT_FLIP (1 << 0) -#define EVENT_VBLANK (1 << 1) - -#ifndef DRM_CAP_TIMESTAMP_MONOTONIC -#define DRM_CAP_TIMESTAMP_MONOTONIC 6 -#endif - -drmModeRes *resources; -int drm_fd; -static drm_intel_bufmgr *bufmgr; -struct intel_batchbuffer *batch; -uint32_t devid; -int test_time = 3; -static bool monotonic_timestamp; - -uint32_t *fb_ptr; - -struct type_name { - int type; - const char *name; -}; - -struct event_state { - const char *name; - - /* - * Event data for the last event that has already passed our check. - * Updated using the below current_* vars in update_state(). - */ - struct timeval last_ts; /* kernel reported timestamp */ - struct timeval last_received_ts; /* the moment we received it */ - unsigned int last_seq; /* kernel reported seq. num */ - - /* - * Event data for for the current event that we just received and - * going to check for validity. Set in event_handler(). - */ - struct timeval current_ts; /* kernel reported timestamp */ - struct timeval current_received_ts; /* the moment we received it */ - unsigned int current_seq; /* kernel reported seq. num */ - - int count; /* # of events of this type */ - - /* Step between the current and next 'target' sequence number. */ - int seq_step; -}; - -struct test_output { - const char *test_name; - uint32_t id; - int mode_valid; - drmModeModeInfo mode; - drmModeEncoder *encoder; - drmModeConnector *connector; - int crtc; - int pipe; - int flags; - unsigned int current_fb_id; - unsigned int fb_width; - unsigned int fb_height; - unsigned int fb_ids[2]; - int bpp, depth; - struct kmstest_fb fb_info[2]; - - struct event_state flip_state; - struct event_state vblank_state; - unsigned int pending_events; -}; - - -static unsigned long gettime_us(void) -{ - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; -} - -static void emit_dummy_load(struct test_output *o) -{ - int i, limit; - drm_intel_bo *dummy_bo, *target_bo, *tmp_bo; - struct kmstest_fb *fb_info = &o->fb_info[o->current_fb_id]; - unsigned pitch = fb_info->stride; - - limit = intel_gen(devid) < 6 ? 500 : 5000; - - dummy_bo = drm_intel_bo_alloc(bufmgr, "dummy_bo", fb_info->size, 4096); - assert(dummy_bo); - target_bo = gem_handle_to_libdrm_bo(bufmgr, drm_fd, "imported", fb_info->gem_handle); - assert(target_bo); - - for (i = 0; i < limit; i++) { - BEGIN_BATCH(8); - OUT_BATCH(XY_SRC_COPY_BLT_CMD | - XY_SRC_COPY_BLT_WRITE_ALPHA | - XY_SRC_COPY_BLT_WRITE_RGB); - OUT_BATCH((3 << 24) | /* 32 bits */ - (0xcc << 16) | /* copy ROP */ - pitch); - OUT_BATCH(0 << 16 | 0); - OUT_BATCH((o->mode.vdisplay) << 16 | (o->mode.hdisplay)); - OUT_RELOC_FENCED(dummy_bo, I915_GEM_DOMAIN_RENDER, I915_GEM_DOMAIN_RENDER, 0); - OUT_BATCH(0 << 16 | 0); - OUT_BATCH(pitch); - OUT_RELOC_FENCED(target_bo, I915_GEM_DOMAIN_RENDER, 0, 0); - ADVANCE_BATCH(); - - if (IS_GEN6(devid) || IS_GEN7(devid)) { - BEGIN_BATCH(3); - OUT_BATCH(XY_SETUP_CLIP_BLT_CMD); - OUT_BATCH(0); - OUT_BATCH(0); - ADVANCE_BATCH(); - } - - tmp_bo = dummy_bo; - dummy_bo = target_bo; - target_bo = tmp_bo; - } - intel_batchbuffer_flush(batch); - - drm_intel_bo_unreference(dummy_bo); - drm_intel_bo_unreference(target_bo); -} - -static int set_dpms(struct test_output *o, int mode) -{ - int i, dpms = 0; - - for (i = 0; i < o->connector->count_props; i++) { - struct drm_mode_get_property prop; - - prop.prop_id = o->connector->props[i]; - prop.count_values = 0; - prop.count_enum_blobs = 0; - if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)) - continue; - - if (strcmp(prop.name, "DPMS")) - continue; - - dpms = prop.prop_id; - break; - } - if (!dpms) { - fprintf(stderr, "DPMS property not found on %d\n", o->id); - errno = ENOENT; - return -1; - } - - return drmModeConnectorSetProperty(drm_fd, o->id, dpms, mode); -} - -static void set_flag(unsigned int *v, unsigned int flag) -{ - assert(!(*v & flag)); - *v |= flag; -} - -static void clear_flag(unsigned int *v, unsigned int flag) -{ - assert(*v & flag); - *v &= ~flag; -} - -static int do_page_flip(struct test_output *o, int fb_id) -{ - int ret; - - ret = drmModePageFlip(drm_fd, o->crtc, fb_id, DRM_MODE_PAGE_FLIP_EVENT, - o); - if (ret == 0) - set_flag(&o->pending_events, EVENT_FLIP); - - return ret; -} - -struct vblank_reply { - unsigned int sequence; - struct timeval ts; -}; - -static int __wait_for_vblank(unsigned int flags, int crtc_idx, - int target_seq, unsigned long ret_data, - struct vblank_reply *reply) -{ - drmVBlank wait_vbl; - int ret; - unsigned crtc_idx_mask; - bool event = !(flags & TEST_VBLANK_BLOCK); - - memset(&wait_vbl, 0, sizeof(wait_vbl)); - - crtc_idx_mask = crtc_idx << DRM_VBLANK_HIGH_CRTC_SHIFT; - assert(!(crtc_idx_mask & ~DRM_VBLANK_HIGH_CRTC_MASK)); - - wait_vbl.request.type = crtc_idx_mask; - if (flags & TEST_VBLANK_ABSOLUTE) - wait_vbl.request.type |= DRM_VBLANK_ABSOLUTE; - else - wait_vbl.request.type |= DRM_VBLANK_RELATIVE; - if (event) { - wait_vbl.request.type |= DRM_VBLANK_EVENT; - wait_vbl.request.signal = ret_data; - } - wait_vbl.request.sequence = target_seq; - - ret = drmWaitVBlank(drm_fd, &wait_vbl); - - if (ret == 0) { - reply->ts.tv_sec = wait_vbl.reply.tval_sec; - reply->ts.tv_usec = wait_vbl.reply.tval_usec; - reply->sequence = wait_vbl.reply.sequence; - } else - ret = -errno; - - return ret; -} - -static int do_wait_for_vblank(struct test_output *o, int pipe_id, - int target_seq, struct vblank_reply *reply) -{ - int ret; - - ret = __wait_for_vblank(o->flags, pipe_id, target_seq, (unsigned long)o, - reply); - if (ret == 0 && !(o->flags & TEST_VBLANK_BLOCK)) - set_flag(&o->pending_events, EVENT_VBLANK); - - return ret; -} - -static bool -analog_tv_connector(struct test_output *o) -{ - uint32_t connector_type = o->connector->connector_type; - - return connector_type == DRM_MODE_CONNECTOR_TV || - connector_type == DRM_MODE_CONNECTOR_9PinDIN || - connector_type == DRM_MODE_CONNECTOR_SVIDEO || - connector_type == DRM_MODE_CONNECTOR_Composite; -} - -static void event_handler(struct event_state *es, unsigned int frame, - unsigned int sec, unsigned int usec) -{ - struct timeval now; - - if (monotonic_timestamp) { - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - now.tv_sec = ts.tv_sec; - now.tv_usec = ts.tv_nsec / 1000; - } else { - gettimeofday(&now, NULL); - } - es->current_received_ts = now; - - es->current_ts.tv_sec = sec; - es->current_ts.tv_usec = usec; - es->current_seq = frame; -} - -static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, - unsigned int usec, void *data) -{ - struct test_output *o = data; - - clear_flag(&o->pending_events, EVENT_FLIP); - event_handler(&o->flip_state, frame, sec, usec); -} - -static double frame_time(struct test_output *o) -{ - return 1000.0 * 1000.0 / o->mode.vrefresh; -} - -static void fixup_premature_vblank_ts(struct test_output *o, - struct event_state *es) -{ - /* - * In case a power off event preempts the completion of a - * wait-for-vblank event the kernel will return a wf-vblank event with - * a zeroed-out timestamp. In order that check_state() doesn't - * complain replace this ts with a valid ts. As we can't calculate the - * exact timestamp, just use the time we received the event. - */ - struct timeval tv; - - if (!(o->flags & (TEST_DPMS | TEST_MODESET))) - return; - - if (o->vblank_state.current_ts.tv_sec != 0 || - o->vblank_state.current_ts.tv_usec != 0) - return; - - tv.tv_sec = 0; - tv.tv_usec = 1; - timersub(&es->current_received_ts, &tv, &es->current_ts); -} - -static void vblank_handler(int fd, unsigned int frame, unsigned int sec, - unsigned int usec, void *data) -{ - struct test_output *o = data; - - clear_flag(&o->pending_events, EVENT_VBLANK); - event_handler(&o->vblank_state, frame, sec, usec); - fixup_premature_vblank_ts(o, &o->vblank_state); -} - -static void check_state(struct test_output *o, struct event_state *es) -{ - struct timeval diff; - double usec_interflip; - - timersub(&es->current_ts, &es->current_received_ts, &diff); - if ((!analog_tv_connector(o)) && - (diff.tv_sec > 0 || (diff.tv_sec == 0 && diff.tv_usec > 2000))) { - fprintf(stderr, "%s ts delayed for too long: %is, %iusec\n", - es->name, (int)diff.tv_sec, (int)diff.tv_usec); - exit(5); - } - - if (es->count == 0) - return; - - if (!timercmp(&es->last_received_ts, &es->current_ts, <)) { - fprintf(stderr, "%s ts before the %s was issued!\n", - es->name, es->name); - - timersub(&es->current_ts, &es->last_received_ts, &diff); - fprintf(stderr, "timerdiff %is, %ius\n", - (int) diff.tv_sec, (int) diff.tv_usec); - exit(6); - } - - /* This bounding matches the one in DRM_IOCTL_WAIT_VBLANK. */ - if (!(o->flags & (TEST_DPMS | TEST_MODESET))) { - /* check only valid if no modeset happens in between, that - * increments by (1 << 23) on each step. */ - if (es->current_seq - (es->last_seq + es->seq_step) > 1UL << 23) { - fprintf(stderr, "unexpected %s seq %u, should be >= %u\n", - es->name, es->current_seq, es->last_seq + es->seq_step); - exit(10); - } - } - - if ((o->flags & TEST_CHECK_TS) && (!analog_tv_connector(o))) { - timersub(&es->current_ts, &es->last_ts, &diff); - usec_interflip = (double)es->seq_step * frame_time(o); - if (fabs((((double) diff.tv_usec) - usec_interflip) / - usec_interflip) > 0.005) { - fprintf(stderr, "inter-%s ts jitter: %is, %ius\n", - es->name, - (int) diff.tv_sec, (int) diff.tv_usec); - exit(9); - } - - if (es->current_seq != es->last_seq + es->seq_step) { - fprintf(stderr, "unexpected %s seq %u, expected %u\n", - es->name, es->current_seq, - es->last_seq + es->seq_step); - exit(9); - } - } -} - -static void check_state_correlation(struct test_output *o, - struct event_state *es1, - struct event_state *es2) -{ - struct timeval tv_diff; - double ftime; - double usec_diff; - int seq_diff; - - if (es1->count == 0 || es2->count == 0) - return; - - timersub(&es2->current_ts, &es1->current_ts, &tv_diff); - usec_diff = tv_diff.tv_sec * 1000 * 1000 + tv_diff.tv_usec; - - seq_diff = es2->current_seq - es1->current_seq; - ftime = frame_time(o); - usec_diff -= seq_diff * ftime; - - if (fabs(usec_diff) / ftime > 0.005) { - fprintf(stderr, - "timestamp mismatch between %s and %s (diff %.4f sec)\n", - es1->name, es2->name, usec_diff / 1000 / 1000); - exit(14); - } -} - -static void check_all_state(struct test_output *o, - unsigned int completed_events) -{ - bool flip, vblank; - - flip = completed_events & EVENT_FLIP; - vblank = completed_events & EVENT_VBLANK; - - if (flip) - check_state(o, &o->flip_state); - if (vblank) - check_state(o, &o->vblank_state); - - if (flip && vblank) - check_state_correlation(o, &o->flip_state, &o->vblank_state); -} - -static void recreate_fb(struct test_output *o) -{ - drmModeFBPtr r; - struct kmstest_fb *fb_info = &o->fb_info[o->current_fb_id]; - uint32_t new_fb_id; - - /* Call rmfb/getfb/addfb to ensure those don't introduce stalls */ - r = drmModeGetFB(drm_fd, fb_info->fb_id); - assert(r); - - do_or_die(drmModeAddFB(drm_fd, o->fb_width, o->fb_height, o->depth, - o->bpp, fb_info->stride, - r->handle, &new_fb_id)); - - drmFree(r); - gem_close(drm_fd, r->handle); - do_or_die(drmModeRmFB(drm_fd, fb_info->fb_id)); - - o->fb_ids[o->current_fb_id] = new_fb_id; - o->fb_info[o->current_fb_id].fb_id = new_fb_id; -} - -/* Return mask of completed events. */ -static unsigned int run_test_step(struct test_output *o) -{ - unsigned int new_fb_id; - /* for funny reasons page_flip returns -EBUSY on disabled crtcs ... */ - int expected_einval = o->flags & TEST_MODESET ? -EBUSY : -EINVAL; - unsigned int completed_events = 0; - bool do_flip; - bool do_vblank; - struct vblank_reply vbl_reply; - unsigned int target_seq; - - target_seq = o->vblank_state.seq_step; - if (o->flags & TEST_VBLANK_ABSOLUTE) - target_seq += o->vblank_state.last_seq; - - /* - * It's possible that we don't have a pending flip here, in case both - * wf-vblank and flip were scheduled and the wf-vblank event was - * delivered earlier. The same applies to vblank events w.r.t flip. - */ - do_flip = (o->flags & TEST_FLIP) && !(o->pending_events & EVENT_FLIP); - do_vblank = (o->flags & TEST_VBLANK) && - !(o->pending_events & EVENT_VBLANK); - - if (o->flags & TEST_WITH_DUMMY_LOAD) - emit_dummy_load(o); - - - o->current_fb_id = !o->current_fb_id; - if (o->flags & TEST_FB_RECREATE) - recreate_fb(o); - new_fb_id = o->fb_ids[o->current_fb_id]; - - if ((o->flags & TEST_VBLANK_EXPIRED_SEQ) && - !(o->pending_events & EVENT_VBLANK) && o->flip_state.count > 0) { - struct vblank_reply reply; - unsigned int exp_seq; - unsigned long start; - - exp_seq = o->flip_state.current_seq; - start = gettime_us(); - do_or_die(__wait_for_vblank(TEST_VBLANK_ABSOLUTE | - TEST_VBLANK_BLOCK, o->pipe, exp_seq, - 0, &reply)); - assert(gettime_us() - start < 500); - assert(reply.sequence == exp_seq); - assert(timercmp(&reply.ts, &o->flip_state.last_ts, ==)); - } - - if (do_flip && (o->flags & TEST_EINVAL) && o->flip_state.count > 0) - assert(do_page_flip(o, new_fb_id) == expected_einval); - - if (do_vblank && (o->flags & TEST_EINVAL) && o->vblank_state.count > 0) - assert(do_wait_for_vblank(o, o->pipe, target_seq, &vbl_reply) - == -EINVAL); - - if (o->flags & TEST_MODESET) { - if (drmModeSetCrtc(drm_fd, o->crtc, - o->fb_ids[o->current_fb_id], - 0, 0, - &o->id, 1, &o->mode)) { - fprintf(stderr, "failed to restore output mode: %s\n", - strerror(errno)); - exit(7); - } - } - - if (o->flags & TEST_DPMS) - do_or_die(set_dpms(o, DRM_MODE_DPMS_ON)); - - printf("."); fflush(stdout); - - if (do_flip) - do_or_die(do_page_flip(o, new_fb_id)); - - if (do_vblank) { - do_or_die(do_wait_for_vblank(o, o->pipe, target_seq, - &vbl_reply)); - if (o->flags & TEST_VBLANK_BLOCK) { - event_handler(&o->vblank_state, vbl_reply.sequence, - vbl_reply.ts.tv_sec, - vbl_reply.ts.tv_usec); - completed_events = EVENT_VBLANK; - } - } - - if (do_flip && (o->flags & TEST_EBUSY)) - assert(do_page_flip(o, new_fb_id) == -EBUSY); - - if (do_flip && (o->flags & TEST_RMFB)) - recreate_fb(o); - - /* pan before the flip completes */ - if (o->flags & TEST_PAN) { - int count = do_flip ? - o->flip_state.count : o->vblank_state.count; - int x_ofs = count * 10 > o->mode.hdisplay ? - o->mode.hdisplay : count * 10; - - if (drmModeSetCrtc(drm_fd, o->crtc, o->fb_ids[o->current_fb_id], - x_ofs, 0, &o->id, 1, &o->mode)) { - fprintf(stderr, "failed to pan (%dx%d@%dHz): %s\n", - o->fb_width, o->fb_height, - o->mode.vrefresh, strerror(errno)); - exit(7); - } - } - - if (o->flags & TEST_DPMS) - do_or_die(set_dpms(o, DRM_MODE_DPMS_OFF)); - - if (o->flags & TEST_MODESET && !(o->flags & TEST_RMFB)) { - if (drmModeSetCrtc(drm_fd, o->crtc, - 0, /* no fb */ - 0, 0, - NULL, 0, NULL)) { - fprintf(stderr, "failed to disable output: %s\n", - strerror(errno)); - exit(7); - } - } - - if (do_vblank && (o->flags & TEST_EINVAL) && o->vblank_state.count > 0) - assert(do_wait_for_vblank(o, o->pipe, target_seq, &vbl_reply) - == -EINVAL); - - if (do_flip && (o->flags & TEST_EINVAL)) - assert(do_page_flip(o, new_fb_id) == expected_einval); - - return completed_events; -} - -static void update_state(struct event_state *es) -{ - es->last_received_ts = es->current_received_ts; - es->last_ts = es->current_ts; - es->last_seq = es->current_seq; - es->count++; -} - -static void update_all_state(struct test_output *o, - unsigned int completed_events) -{ - if (completed_events & EVENT_FLIP) - update_state(&o->flip_state); - - if (completed_events & EVENT_VBLANK) - update_state(&o->vblank_state); -} - -static void connector_find_preferred_mode(struct test_output *o, int crtc_id) -{ - drmModeConnector *connector; - drmModeEncoder *encoder = NULL; - int i, j; - - /* First, find the connector & mode */ - o->mode_valid = 0; - o->crtc = 0; - connector = drmModeGetConnector(drm_fd, o->id); - assert(connector); - - if (connector->connection != DRM_MODE_CONNECTED) { - drmModeFreeConnector(connector); - return; - } - - if (!connector->count_modes) { - fprintf(stderr, "connector %d has no modes\n", o->id); - drmModeFreeConnector(connector); - return; - } - - if (connector->connector_id != o->id) { - fprintf(stderr, "connector id doesn't match (%d != %d)\n", - connector->connector_id, o->id); - drmModeFreeConnector(connector); - return; - } - - for (j = 0; j < connector->count_modes; j++) { - o->mode = connector->modes[j]; - if (o->mode.type & DRM_MODE_TYPE_PREFERRED) { - o->mode_valid = 1; - break; - } - } - - if (!o->mode_valid) { - if (connector->count_modes > 0) { - /* use the first mode as test mode */ - o->mode = connector->modes[0]; - o->mode_valid = 1; - } - else { - fprintf(stderr, "failed to find any modes on connector %d\n", - o->id); - return; - } - } - - /* Now get the encoder */ - for (i = 0; i < connector->count_encoders; i++) { - encoder = drmModeGetEncoder(drm_fd, connector->encoders[i]); - - if (!encoder) { - fprintf(stderr, "could not get encoder %i: %s\n", - resources->encoders[i], strerror(errno)); - drmModeFreeEncoder(encoder); - continue; - } - - break; - } - - o->encoder = encoder; - - if (i == resources->count_encoders) { - fprintf(stderr, "failed to find encoder\n"); - o->mode_valid = 0; - return; - } - - /* Find first CRTC not in use */ - for (i = 0; i < resources->count_crtcs; i++) { - if (resources->crtcs[i] != crtc_id) - continue; - if (resources->crtcs[i] && - (o->encoder->possible_crtcs & (1<crtc = resources->crtcs[i]; - break; - } - } - - if (!o->crtc) { - fprintf(stderr, "could not find requested crtc %d\n", crtc_id); - o->mode_valid = 0; - return; - } - - o->connector = connector; -} - -static void -paint_flip_mode(cairo_t *cr, int width, int height, void *priv) -{ - bool odd_frame = (bool) priv; - - if (odd_frame) - cairo_rectangle(cr, width/4, height/2, width/4, height/8); - else - cairo_rectangle(cr, width/2, height/2, width/4, height/8); - - cairo_set_source_rgb(cr, 1, 1, 1); - cairo_fill(cr); -} - -static int -fb_is_bound(struct test_output *o, int fb) -{ - struct drm_mode_crtc mode; - - mode.crtc_id = o->crtc; - if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETCRTC, &mode)) - return 0; - - return mode.mode_valid && mode.fb_id == fb; -} - -static void check_final_state(struct test_output *o, struct event_state *es, - unsigned int ellapsed) -{ - if (es->count == 0) { - fprintf(stderr, "no %s event received\n", es->name); - exit(12); - } - - /* Verify we drop no frames, but only if it's not a TV encoder, since - * those use some funny fake timings behind userspace's back. */ - if (o->flags & TEST_CHECK_TS && !analog_tv_connector(o)) { - int expected; - int count = es->count; - - count *= es->seq_step; - expected = ellapsed * o->mode.vrefresh / (1000 * 1000); - if (count < expected * 99/100) { - fprintf(stderr, "dropped frames, expected %d, counted %d, encoder type %d\n", - expected, count, o->encoder->encoder_type); - exit(3); - } - } -} - -/* - * Wait until at least one pending event completes. Return mask of completed - * events. - */ -static unsigned int wait_for_events(struct test_output *o) -{ - drmEventContext evctx; - struct timeval timeout = { .tv_sec = 3, .tv_usec = 0 }; - fd_set fds; - unsigned int event_mask; - int ret; - - event_mask = o->pending_events; - assert(event_mask); - - memset(&evctx, 0, sizeof evctx); - evctx.version = DRM_EVENT_CONTEXT_VERSION; - evctx.vblank_handler = vblank_handler; - evctx.page_flip_handler = page_flip_handler; - - /* make timeout lax with the dummy load */ - if (o->flags & TEST_WITH_DUMMY_LOAD) - timeout.tv_sec *= 10; - - FD_ZERO(&fds); - FD_SET(0, &fds); - FD_SET(drm_fd, &fds); - ret = select(drm_fd + 1, &fds, NULL, NULL, &timeout); - - if (ret <= 0) { - fprintf(stderr, "select timed out or error (ret %d)\n", - ret); - exit(1); - } else if (FD_ISSET(0, &fds)) { - fprintf(stderr, "no fds active, breaking\n"); - exit(2); - } - - do_or_die(drmHandleEvent(drm_fd, &evctx)); - - event_mask ^= o->pending_events; - assert(event_mask); - - return event_mask; -} - -/* Returned the ellapsed time in us */ -static unsigned event_loop(struct test_output *o, unsigned duration_sec) -{ - unsigned long start, end; - - start = gettime_us(); - - while (1) { - unsigned int completed_events; - - completed_events = run_test_step(o); - if (o->pending_events) - completed_events |= wait_for_events(o); - check_all_state(o, completed_events); - update_all_state(o, completed_events); - - if ((gettime_us() - start) / 1000000 >= duration_sec) - break; - } - - end = gettime_us(); - - /* Flush any remaining events */ - if (o->pending_events) - wait_for_events(o); - - return end - start; -} - -static void run_test_on_crtc(struct test_output *o, int crtc, int duration) -{ - unsigned ellapsed; - - o->bpp = 32; - o->depth = 24; - - connector_find_preferred_mode(o, crtc); - if (!o->mode_valid) - return; - - fprintf(stdout, "Beginning %s on crtc %d, connector %d\n", - o->test_name, crtc, o->id); - - o->fb_width = o->mode.hdisplay; - o->fb_height = o->mode.vdisplay; - - if (o->flags & TEST_PAN) - o->fb_width *= 2; - - o->fb_ids[0] = kmstest_create_fb(drm_fd, o->fb_width, o->fb_height, - o->bpp, o->depth, false, &o->fb_info[0], - paint_flip_mode, (void *)false); - o->fb_ids[1] = kmstest_create_fb(drm_fd, o->fb_width, o->fb_height, - o->bpp, o->depth, false, &o->fb_info[1], - paint_flip_mode, (void *)true); - - if (!o->fb_ids[0] || !o->fb_ids[1]) { - fprintf(stderr, "failed to create fbs\n"); - exit(3); - } - - kmstest_dump_mode(&o->mode); - if (drmModeSetCrtc(drm_fd, o->crtc, o->fb_ids[0], 0, 0, - &o->id, 1, &o->mode)) { - fprintf(stderr, "failed to set mode (%dx%d@%dHz): %s\n", - o->fb_width, o->fb_height, o->mode.vrefresh, - strerror(errno)); - exit(3); - } - assert(fb_is_bound(o, o->fb_ids[0])); - - /* quiescent the hw a bit so ensure we don't miss a single frame */ - if (o->flags & TEST_CHECK_TS) - sleep(1); - - if (do_page_flip(o, o->fb_ids[1])) { - fprintf(stderr, "failed to page flip: %s\n", strerror(errno)); - exit(4); - } - wait_for_events(o); - - o->current_fb_id = 1; - o->flip_state.seq_step = 1; - if (o->flags & TEST_VBLANK_ABSOLUTE) - o->vblank_state.seq_step = 5; - else - o->vblank_state.seq_step = 1; - - ellapsed = event_loop(o, duration); - - if (o->flags & TEST_FLIP) - check_final_state(o, &o->flip_state, ellapsed); - if (o->flags & TEST_VBLANK) - check_final_state(o, &o->vblank_state, ellapsed); - - fprintf(stdout, "\n%s on crtc %d, connector %d: PASSED\n\n", - o->test_name, crtc, o->id); - - kmstest_remove_fb(drm_fd, o->fb_ids[1]); - kmstest_remove_fb(drm_fd, o->fb_ids[0]); - - drmModeFreeEncoder(o->encoder); - drmModeFreeConnector(o->connector); -} - -static int get_pipe_from_crtc_id(int crtc_id) -{ - struct drm_i915_get_pipe_from_crtc_id pfci; - int ret; - - memset(&pfci, 0, sizeof(pfci)); - pfci.crtc_id = crtc_id; - ret = drmIoctl(drm_fd, DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID, &pfci); - assert(ret == 0); - - return pfci.pipe; -} - -static int run_test(int duration, int flags, const char *test_name) -{ - struct test_output o; - int c, i; - - resources = drmModeGetResources(drm_fd); - if (!resources) { - fprintf(stderr, "drmModeGetResources failed: %s\n", - strerror(errno)); - exit(5); - } - - /* Find any connected displays */ - for (c = 0; c < resources->count_connectors; c++) { - for (i = 0; i < resources->count_crtcs; i++) { - int crtc; - - memset(&o, 0, sizeof(o)); - o.test_name = test_name; - o.id = resources->connectors[c]; - o.flags = flags; - o.flip_state.name = "flip"; - o.vblank_state.name = "vblank"; - crtc = resources->crtcs[i]; - o.pipe = get_pipe_from_crtc_id(crtc); - - run_test_on_crtc(&o, crtc, duration); - } - } - - drmModeFreeResources(resources); - return 1; -} - -static void get_timestamp_format(void) -{ - uint64_t cap_mono; - int ret; - - ret = drmGetCap(drm_fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap_mono); - assert(ret == 0 || errno == EINVAL); - monotonic_timestamp = ret == 0 && cap_mono == 1; - printf("Using %s timestamps\n", - monotonic_timestamp ? "monotonic" : "real"); -} - -int main(int argc, char **argv) -{ - struct { - int duration; - int flags; - const char *name; - } tests[] = { - { 15, TEST_VBLANK, "wf_vblank" }, - { 15, TEST_VBLANK | TEST_CHECK_TS, "wf_vblank-ts-check" }, - { 15, TEST_VBLANK | TEST_VBLANK_BLOCK | TEST_CHECK_TS, - "blocking-wf_vblank" }, - { 5, TEST_VBLANK | TEST_VBLANK_ABSOLUTE, - "absolute-wf_vblank" }, - { 5, TEST_VBLANK | TEST_VBLANK_BLOCK | TEST_VBLANK_ABSOLUTE, - "blocking-absolute-wf_vblank" }, - { 30, TEST_VBLANK | TEST_DPMS | TEST_EINVAL, "wf_vblank-vs-dpms" }, - { 30, TEST_VBLANK | TEST_DPMS | TEST_WITH_DUMMY_LOAD, - "delayed-wf_vblank-vs-dpms" }, - { 30, TEST_VBLANK | TEST_MODESET | TEST_EINVAL, "wf_vblank-vs-modeset" }, - { 30, TEST_VBLANK | TEST_MODESET | TEST_WITH_DUMMY_LOAD, - "delayed-wf_vblank-vs-modeset" }, - - { 15, TEST_FLIP | TEST_EBUSY , "plain-flip" }, - { 15, TEST_FLIP | TEST_CHECK_TS | TEST_EBUSY , "plain-flip-ts-check" }, - { 15, TEST_FLIP | TEST_CHECK_TS | TEST_EBUSY | TEST_FB_RECREATE, - "plain-flip-fb-recreate" }, - { 15, TEST_FLIP | TEST_EBUSY | TEST_RMFB | TEST_MODESET , "flip-vs-rmfb" }, - { 30, TEST_FLIP | TEST_DPMS | TEST_EINVAL, "flip-vs-dpms" }, - { 30, TEST_FLIP | TEST_DPMS | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-dpms" }, - { 5, TEST_FLIP | TEST_PAN, "flip-vs-panning" }, - { 30, TEST_FLIP | TEST_PAN | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-panning" }, - { 30, TEST_FLIP | TEST_MODESET | TEST_EINVAL, "flip-vs-modeset" }, - { 30, TEST_FLIP | TEST_MODESET | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-modeset" }, - { 5, TEST_FLIP | TEST_VBLANK_EXPIRED_SEQ, - "flip-vs-expired-vblank" }, - - { 15, TEST_FLIP | TEST_VBLANK | TEST_VBLANK_ABSOLUTE | - TEST_CHECK_TS, "flip-vs-absolute-wf_vblank" }, - { 15, TEST_FLIP | TEST_VBLANK | TEST_CHECK_TS, - "flip-vs-wf_vblank" }, - { 15, TEST_FLIP | TEST_VBLANK | TEST_VBLANK_BLOCK | - TEST_CHECK_TS, "flip-vs-blocking-wf-vblank" }, - }; - int i; - - drmtest_subtest_init(argc, argv); - - drm_fd = drm_open_any(); - - if (!drmtest_only_list_subtests()) - get_timestamp_format(); - - bufmgr = drm_intel_bufmgr_gem_init(drm_fd, 4096); - devid = intel_get_drm_devid(drm_fd); - batch = intel_batchbuffer_alloc(bufmgr, devid); - - for (i = 0; i < sizeof(tests) / sizeof (tests[0]); i++) { - if (drmtest_run_subtest(tests[i].name)) { - printf("running testcase: %s\n", tests[i].name); - run_test(tests[i].duration, tests[i].flags, tests[i].name); - } - } - - close(drm_fd); - - return 0; -} diff --git a/tests/kms_flip.c b/tests/kms_flip.c new file mode 100644 index 0000000..e05efb1 --- /dev/null +++ b/tests/kms_flip.c @@ -0,0 +1,1066 @@ +/* + * Copyright 2012 Intel Corporation + * Jesse Barnes + * + * 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 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 "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i915_drm.h" +#include "drmtest.h" +#include "testdisplay.h" +#include "intel_bufmgr.h" +#include "intel_batchbuffer.h" +#include "intel_gpu_tools.h" + +#define TEST_DPMS (1 << 0) +#define TEST_WITH_DUMMY_LOAD (1 << 1) +#define TEST_PAN (1 << 2) +#define TEST_MODESET (1 << 3) +#define TEST_CHECK_TS (1 << 4) +#define TEST_EBUSY (1 << 5) +#define TEST_EINVAL (1 << 6) +#define TEST_FLIP (1 << 7) +#define TEST_VBLANK (1 << 8) +#define TEST_VBLANK_BLOCK (1 << 9) +#define TEST_VBLANK_ABSOLUTE (1 << 10) +#define TEST_VBLANK_EXPIRED_SEQ (1 << 11) +#define TEST_FB_RECREATE (1 << 12) +#define TEST_RMFB (1 << 13) + +#define EVENT_FLIP (1 << 0) +#define EVENT_VBLANK (1 << 1) + +#ifndef DRM_CAP_TIMESTAMP_MONOTONIC +#define DRM_CAP_TIMESTAMP_MONOTONIC 6 +#endif + +drmModeRes *resources; +int drm_fd; +static drm_intel_bufmgr *bufmgr; +struct intel_batchbuffer *batch; +uint32_t devid; +int test_time = 3; +static bool monotonic_timestamp; + +uint32_t *fb_ptr; + +struct type_name { + int type; + const char *name; +}; + +struct event_state { + const char *name; + + /* + * Event data for the last event that has already passed our check. + * Updated using the below current_* vars in update_state(). + */ + struct timeval last_ts; /* kernel reported timestamp */ + struct timeval last_received_ts; /* the moment we received it */ + unsigned int last_seq; /* kernel reported seq. num */ + + /* + * Event data for for the current event that we just received and + * going to check for validity. Set in event_handler(). + */ + struct timeval current_ts; /* kernel reported timestamp */ + struct timeval current_received_ts; /* the moment we received it */ + unsigned int current_seq; /* kernel reported seq. num */ + + int count; /* # of events of this type */ + + /* Step between the current and next 'target' sequence number. */ + int seq_step; +}; + +struct test_output { + const char *test_name; + uint32_t id; + int mode_valid; + drmModeModeInfo mode; + drmModeEncoder *encoder; + drmModeConnector *connector; + int crtc; + int pipe; + int flags; + unsigned int current_fb_id; + unsigned int fb_width; + unsigned int fb_height; + unsigned int fb_ids[2]; + int bpp, depth; + struct kmstest_fb fb_info[2]; + + struct event_state flip_state; + struct event_state vblank_state; + unsigned int pending_events; +}; + + +static unsigned long gettime_us(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +} + +static void emit_dummy_load(struct test_output *o) +{ + int i, limit; + drm_intel_bo *dummy_bo, *target_bo, *tmp_bo; + struct kmstest_fb *fb_info = &o->fb_info[o->current_fb_id]; + unsigned pitch = fb_info->stride; + + limit = intel_gen(devid) < 6 ? 500 : 5000; + + dummy_bo = drm_intel_bo_alloc(bufmgr, "dummy_bo", fb_info->size, 4096); + assert(dummy_bo); + target_bo = gem_handle_to_libdrm_bo(bufmgr, drm_fd, "imported", fb_info->gem_handle); + assert(target_bo); + + for (i = 0; i < limit; i++) { + BEGIN_BATCH(8); + OUT_BATCH(XY_SRC_COPY_BLT_CMD | + XY_SRC_COPY_BLT_WRITE_ALPHA | + XY_SRC_COPY_BLT_WRITE_RGB); + OUT_BATCH((3 << 24) | /* 32 bits */ + (0xcc << 16) | /* copy ROP */ + pitch); + OUT_BATCH(0 << 16 | 0); + OUT_BATCH((o->mode.vdisplay) << 16 | (o->mode.hdisplay)); + OUT_RELOC_FENCED(dummy_bo, I915_GEM_DOMAIN_RENDER, I915_GEM_DOMAIN_RENDER, 0); + OUT_BATCH(0 << 16 | 0); + OUT_BATCH(pitch); + OUT_RELOC_FENCED(target_bo, I915_GEM_DOMAIN_RENDER, 0, 0); + ADVANCE_BATCH(); + + if (IS_GEN6(devid) || IS_GEN7(devid)) { + BEGIN_BATCH(3); + OUT_BATCH(XY_SETUP_CLIP_BLT_CMD); + OUT_BATCH(0); + OUT_BATCH(0); + ADVANCE_BATCH(); + } + + tmp_bo = dummy_bo; + dummy_bo = target_bo; + target_bo = tmp_bo; + } + intel_batchbuffer_flush(batch); + + drm_intel_bo_unreference(dummy_bo); + drm_intel_bo_unreference(target_bo); +} + +static int set_dpms(struct test_output *o, int mode) +{ + int i, dpms = 0; + + for (i = 0; i < o->connector->count_props; i++) { + struct drm_mode_get_property prop; + + prop.prop_id = o->connector->props[i]; + prop.count_values = 0; + prop.count_enum_blobs = 0; + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)) + continue; + + if (strcmp(prop.name, "DPMS")) + continue; + + dpms = prop.prop_id; + break; + } + if (!dpms) { + fprintf(stderr, "DPMS property not found on %d\n", o->id); + errno = ENOENT; + return -1; + } + + return drmModeConnectorSetProperty(drm_fd, o->id, dpms, mode); +} + +static void set_flag(unsigned int *v, unsigned int flag) +{ + assert(!(*v & flag)); + *v |= flag; +} + +static void clear_flag(unsigned int *v, unsigned int flag) +{ + assert(*v & flag); + *v &= ~flag; +} + +static int do_page_flip(struct test_output *o, int fb_id) +{ + int ret; + + ret = drmModePageFlip(drm_fd, o->crtc, fb_id, DRM_MODE_PAGE_FLIP_EVENT, + o); + if (ret == 0) + set_flag(&o->pending_events, EVENT_FLIP); + + return ret; +} + +struct vblank_reply { + unsigned int sequence; + struct timeval ts; +}; + +static int __wait_for_vblank(unsigned int flags, int crtc_idx, + int target_seq, unsigned long ret_data, + struct vblank_reply *reply) +{ + drmVBlank wait_vbl; + int ret; + unsigned crtc_idx_mask; + bool event = !(flags & TEST_VBLANK_BLOCK); + + memset(&wait_vbl, 0, sizeof(wait_vbl)); + + crtc_idx_mask = crtc_idx << DRM_VBLANK_HIGH_CRTC_SHIFT; + assert(!(crtc_idx_mask & ~DRM_VBLANK_HIGH_CRTC_MASK)); + + wait_vbl.request.type = crtc_idx_mask; + if (flags & TEST_VBLANK_ABSOLUTE) + wait_vbl.request.type |= DRM_VBLANK_ABSOLUTE; + else + wait_vbl.request.type |= DRM_VBLANK_RELATIVE; + if (event) { + wait_vbl.request.type |= DRM_VBLANK_EVENT; + wait_vbl.request.signal = ret_data; + } + wait_vbl.request.sequence = target_seq; + + ret = drmWaitVBlank(drm_fd, &wait_vbl); + + if (ret == 0) { + reply->ts.tv_sec = wait_vbl.reply.tval_sec; + reply->ts.tv_usec = wait_vbl.reply.tval_usec; + reply->sequence = wait_vbl.reply.sequence; + } else + ret = -errno; + + return ret; +} + +static int do_wait_for_vblank(struct test_output *o, int pipe_id, + int target_seq, struct vblank_reply *reply) +{ + int ret; + + ret = __wait_for_vblank(o->flags, pipe_id, target_seq, (unsigned long)o, + reply); + if (ret == 0 && !(o->flags & TEST_VBLANK_BLOCK)) + set_flag(&o->pending_events, EVENT_VBLANK); + + return ret; +} + +static bool +analog_tv_connector(struct test_output *o) +{ + uint32_t connector_type = o->connector->connector_type; + + return connector_type == DRM_MODE_CONNECTOR_TV || + connector_type == DRM_MODE_CONNECTOR_9PinDIN || + connector_type == DRM_MODE_CONNECTOR_SVIDEO || + connector_type == DRM_MODE_CONNECTOR_Composite; +} + +static void event_handler(struct event_state *es, unsigned int frame, + unsigned int sec, unsigned int usec) +{ + struct timeval now; + + if (monotonic_timestamp) { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + now.tv_sec = ts.tv_sec; + now.tv_usec = ts.tv_nsec / 1000; + } else { + gettimeofday(&now, NULL); + } + es->current_received_ts = now; + + es->current_ts.tv_sec = sec; + es->current_ts.tv_usec = usec; + es->current_seq = frame; +} + +static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data) +{ + struct test_output *o = data; + + clear_flag(&o->pending_events, EVENT_FLIP); + event_handler(&o->flip_state, frame, sec, usec); +} + +static double frame_time(struct test_output *o) +{ + return 1000.0 * 1000.0 / o->mode.vrefresh; +} + +static void fixup_premature_vblank_ts(struct test_output *o, + struct event_state *es) +{ + /* + * In case a power off event preempts the completion of a + * wait-for-vblank event the kernel will return a wf-vblank event with + * a zeroed-out timestamp. In order that check_state() doesn't + * complain replace this ts with a valid ts. As we can't calculate the + * exact timestamp, just use the time we received the event. + */ + struct timeval tv; + + if (!(o->flags & (TEST_DPMS | TEST_MODESET))) + return; + + if (o->vblank_state.current_ts.tv_sec != 0 || + o->vblank_state.current_ts.tv_usec != 0) + return; + + tv.tv_sec = 0; + tv.tv_usec = 1; + timersub(&es->current_received_ts, &tv, &es->current_ts); +} + +static void vblank_handler(int fd, unsigned int frame, unsigned int sec, + unsigned int usec, void *data) +{ + struct test_output *o = data; + + clear_flag(&o->pending_events, EVENT_VBLANK); + event_handler(&o->vblank_state, frame, sec, usec); + fixup_premature_vblank_ts(o, &o->vblank_state); +} + +static void check_state(struct test_output *o, struct event_state *es) +{ + struct timeval diff; + double usec_interflip; + + timersub(&es->current_ts, &es->current_received_ts, &diff); + if ((!analog_tv_connector(o)) && + (diff.tv_sec > 0 || (diff.tv_sec == 0 && diff.tv_usec > 2000))) { + fprintf(stderr, "%s ts delayed for too long: %is, %iusec\n", + es->name, (int)diff.tv_sec, (int)diff.tv_usec); + exit(5); + } + + if (es->count == 0) + return; + + if (!timercmp(&es->last_received_ts, &es->current_ts, <)) { + fprintf(stderr, "%s ts before the %s was issued!\n", + es->name, es->name); + + timersub(&es->current_ts, &es->last_received_ts, &diff); + fprintf(stderr, "timerdiff %is, %ius\n", + (int) diff.tv_sec, (int) diff.tv_usec); + exit(6); + } + + /* This bounding matches the one in DRM_IOCTL_WAIT_VBLANK. */ + if (!(o->flags & (TEST_DPMS | TEST_MODESET))) { + /* check only valid if no modeset happens in between, that + * increments by (1 << 23) on each step. */ + if (es->current_seq - (es->last_seq + es->seq_step) > 1UL << 23) { + fprintf(stderr, "unexpected %s seq %u, should be >= %u\n", + es->name, es->current_seq, es->last_seq + es->seq_step); + exit(10); + } + } + + if ((o->flags & TEST_CHECK_TS) && (!analog_tv_connector(o))) { + timersub(&es->current_ts, &es->last_ts, &diff); + usec_interflip = (double)es->seq_step * frame_time(o); + if (fabs((((double) diff.tv_usec) - usec_interflip) / + usec_interflip) > 0.005) { + fprintf(stderr, "inter-%s ts jitter: %is, %ius\n", + es->name, + (int) diff.tv_sec, (int) diff.tv_usec); + exit(9); + } + + if (es->current_seq != es->last_seq + es->seq_step) { + fprintf(stderr, "unexpected %s seq %u, expected %u\n", + es->name, es->current_seq, + es->last_seq + es->seq_step); + exit(9); + } + } +} + +static void check_state_correlation(struct test_output *o, + struct event_state *es1, + struct event_state *es2) +{ + struct timeval tv_diff; + double ftime; + double usec_diff; + int seq_diff; + + if (es1->count == 0 || es2->count == 0) + return; + + timersub(&es2->current_ts, &es1->current_ts, &tv_diff); + usec_diff = tv_diff.tv_sec * 1000 * 1000 + tv_diff.tv_usec; + + seq_diff = es2->current_seq - es1->current_seq; + ftime = frame_time(o); + usec_diff -= seq_diff * ftime; + + if (fabs(usec_diff) / ftime > 0.005) { + fprintf(stderr, + "timestamp mismatch between %s and %s (diff %.4f sec)\n", + es1->name, es2->name, usec_diff / 1000 / 1000); + exit(14); + } +} + +static void check_all_state(struct test_output *o, + unsigned int completed_events) +{ + bool flip, vblank; + + flip = completed_events & EVENT_FLIP; + vblank = completed_events & EVENT_VBLANK; + + if (flip) + check_state(o, &o->flip_state); + if (vblank) + check_state(o, &o->vblank_state); + + if (flip && vblank) + check_state_correlation(o, &o->flip_state, &o->vblank_state); +} + +static void recreate_fb(struct test_output *o) +{ + drmModeFBPtr r; + struct kmstest_fb *fb_info = &o->fb_info[o->current_fb_id]; + uint32_t new_fb_id; + + /* Call rmfb/getfb/addfb to ensure those don't introduce stalls */ + r = drmModeGetFB(drm_fd, fb_info->fb_id); + assert(r); + + do_or_die(drmModeAddFB(drm_fd, o->fb_width, o->fb_height, o->depth, + o->bpp, fb_info->stride, + r->handle, &new_fb_id)); + + drmFree(r); + gem_close(drm_fd, r->handle); + do_or_die(drmModeRmFB(drm_fd, fb_info->fb_id)); + + o->fb_ids[o->current_fb_id] = new_fb_id; + o->fb_info[o->current_fb_id].fb_id = new_fb_id; +} + +/* Return mask of completed events. */ +static unsigned int run_test_step(struct test_output *o) +{ + unsigned int new_fb_id; + /* for funny reasons page_flip returns -EBUSY on disabled crtcs ... */ + int expected_einval = o->flags & TEST_MODESET ? -EBUSY : -EINVAL; + unsigned int completed_events = 0; + bool do_flip; + bool do_vblank; + struct vblank_reply vbl_reply; + unsigned int target_seq; + + target_seq = o->vblank_state.seq_step; + if (o->flags & TEST_VBLANK_ABSOLUTE) + target_seq += o->vblank_state.last_seq; + + /* + * It's possible that we don't have a pending flip here, in case both + * wf-vblank and flip were scheduled and the wf-vblank event was + * delivered earlier. The same applies to vblank events w.r.t flip. + */ + do_flip = (o->flags & TEST_FLIP) && !(o->pending_events & EVENT_FLIP); + do_vblank = (o->flags & TEST_VBLANK) && + !(o->pending_events & EVENT_VBLANK); + + if (o->flags & TEST_WITH_DUMMY_LOAD) + emit_dummy_load(o); + + + o->current_fb_id = !o->current_fb_id; + if (o->flags & TEST_FB_RECREATE) + recreate_fb(o); + new_fb_id = o->fb_ids[o->current_fb_id]; + + if ((o->flags & TEST_VBLANK_EXPIRED_SEQ) && + !(o->pending_events & EVENT_VBLANK) && o->flip_state.count > 0) { + struct vblank_reply reply; + unsigned int exp_seq; + unsigned long start; + + exp_seq = o->flip_state.current_seq; + start = gettime_us(); + do_or_die(__wait_for_vblank(TEST_VBLANK_ABSOLUTE | + TEST_VBLANK_BLOCK, o->pipe, exp_seq, + 0, &reply)); + assert(gettime_us() - start < 500); + assert(reply.sequence == exp_seq); + assert(timercmp(&reply.ts, &o->flip_state.last_ts, ==)); + } + + if (do_flip && (o->flags & TEST_EINVAL) && o->flip_state.count > 0) + assert(do_page_flip(o, new_fb_id) == expected_einval); + + if (do_vblank && (o->flags & TEST_EINVAL) && o->vblank_state.count > 0) + assert(do_wait_for_vblank(o, o->pipe, target_seq, &vbl_reply) + == -EINVAL); + + if (o->flags & TEST_MODESET) { + if (drmModeSetCrtc(drm_fd, o->crtc, + o->fb_ids[o->current_fb_id], + 0, 0, + &o->id, 1, &o->mode)) { + fprintf(stderr, "failed to restore output mode: %s\n", + strerror(errno)); + exit(7); + } + } + + if (o->flags & TEST_DPMS) + do_or_die(set_dpms(o, DRM_MODE_DPMS_ON)); + + printf("."); fflush(stdout); + + if (do_flip) + do_or_die(do_page_flip(o, new_fb_id)); + + if (do_vblank) { + do_or_die(do_wait_for_vblank(o, o->pipe, target_seq, + &vbl_reply)); + if (o->flags & TEST_VBLANK_BLOCK) { + event_handler(&o->vblank_state, vbl_reply.sequence, + vbl_reply.ts.tv_sec, + vbl_reply.ts.tv_usec); + completed_events = EVENT_VBLANK; + } + } + + if (do_flip && (o->flags & TEST_EBUSY)) + assert(do_page_flip(o, new_fb_id) == -EBUSY); + + if (do_flip && (o->flags & TEST_RMFB)) + recreate_fb(o); + + /* pan before the flip completes */ + if (o->flags & TEST_PAN) { + int count = do_flip ? + o->flip_state.count : o->vblank_state.count; + int x_ofs = count * 10 > o->mode.hdisplay ? + o->mode.hdisplay : count * 10; + + if (drmModeSetCrtc(drm_fd, o->crtc, o->fb_ids[o->current_fb_id], + x_ofs, 0, &o->id, 1, &o->mode)) { + fprintf(stderr, "failed to pan (%dx%d@%dHz): %s\n", + o->fb_width, o->fb_height, + o->mode.vrefresh, strerror(errno)); + exit(7); + } + } + + if (o->flags & TEST_DPMS) + do_or_die(set_dpms(o, DRM_MODE_DPMS_OFF)); + + if (o->flags & TEST_MODESET && !(o->flags & TEST_RMFB)) { + if (drmModeSetCrtc(drm_fd, o->crtc, + 0, /* no fb */ + 0, 0, + NULL, 0, NULL)) { + fprintf(stderr, "failed to disable output: %s\n", + strerror(errno)); + exit(7); + } + } + + if (do_vblank && (o->flags & TEST_EINVAL) && o->vblank_state.count > 0) + assert(do_wait_for_vblank(o, o->pipe, target_seq, &vbl_reply) + == -EINVAL); + + if (do_flip && (o->flags & TEST_EINVAL)) + assert(do_page_flip(o, new_fb_id) == expected_einval); + + return completed_events; +} + +static void update_state(struct event_state *es) +{ + es->last_received_ts = es->current_received_ts; + es->last_ts = es->current_ts; + es->last_seq = es->current_seq; + es->count++; +} + +static void update_all_state(struct test_output *o, + unsigned int completed_events) +{ + if (completed_events & EVENT_FLIP) + update_state(&o->flip_state); + + if (completed_events & EVENT_VBLANK) + update_state(&o->vblank_state); +} + +static void connector_find_preferred_mode(struct test_output *o, int crtc_id) +{ + drmModeConnector *connector; + drmModeEncoder *encoder = NULL; + int i, j; + + /* First, find the connector & mode */ + o->mode_valid = 0; + o->crtc = 0; + connector = drmModeGetConnector(drm_fd, o->id); + assert(connector); + + if (connector->connection != DRM_MODE_CONNECTED) { + drmModeFreeConnector(connector); + return; + } + + if (!connector->count_modes) { + fprintf(stderr, "connector %d has no modes\n", o->id); + drmModeFreeConnector(connector); + return; + } + + if (connector->connector_id != o->id) { + fprintf(stderr, "connector id doesn't match (%d != %d)\n", + connector->connector_id, o->id); + drmModeFreeConnector(connector); + return; + } + + for (j = 0; j < connector->count_modes; j++) { + o->mode = connector->modes[j]; + if (o->mode.type & DRM_MODE_TYPE_PREFERRED) { + o->mode_valid = 1; + break; + } + } + + if (!o->mode_valid) { + if (connector->count_modes > 0) { + /* use the first mode as test mode */ + o->mode = connector->modes[0]; + o->mode_valid = 1; + } + else { + fprintf(stderr, "failed to find any modes on connector %d\n", + o->id); + return; + } + } + + /* Now get the encoder */ + for (i = 0; i < connector->count_encoders; i++) { + encoder = drmModeGetEncoder(drm_fd, connector->encoders[i]); + + if (!encoder) { + fprintf(stderr, "could not get encoder %i: %s\n", + resources->encoders[i], strerror(errno)); + drmModeFreeEncoder(encoder); + continue; + } + + break; + } + + o->encoder = encoder; + + if (i == resources->count_encoders) { + fprintf(stderr, "failed to find encoder\n"); + o->mode_valid = 0; + return; + } + + /* Find first CRTC not in use */ + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] != crtc_id) + continue; + if (resources->crtcs[i] && + (o->encoder->possible_crtcs & (1<crtc = resources->crtcs[i]; + break; + } + } + + if (!o->crtc) { + fprintf(stderr, "could not find requested crtc %d\n", crtc_id); + o->mode_valid = 0; + return; + } + + o->connector = connector; +} + +static void +paint_flip_mode(cairo_t *cr, int width, int height, void *priv) +{ + bool odd_frame = (bool) priv; + + if (odd_frame) + cairo_rectangle(cr, width/4, height/2, width/4, height/8); + else + cairo_rectangle(cr, width/2, height/2, width/4, height/8); + + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); +} + +static int +fb_is_bound(struct test_output *o, int fb) +{ + struct drm_mode_crtc mode; + + mode.crtc_id = o->crtc; + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_GETCRTC, &mode)) + return 0; + + return mode.mode_valid && mode.fb_id == fb; +} + +static void check_final_state(struct test_output *o, struct event_state *es, + unsigned int ellapsed) +{ + if (es->count == 0) { + fprintf(stderr, "no %s event received\n", es->name); + exit(12); + } + + /* Verify we drop no frames, but only if it's not a TV encoder, since + * those use some funny fake timings behind userspace's back. */ + if (o->flags & TEST_CHECK_TS && !analog_tv_connector(o)) { + int expected; + int count = es->count; + + count *= es->seq_step; + expected = ellapsed * o->mode.vrefresh / (1000 * 1000); + if (count < expected * 99/100) { + fprintf(stderr, "dropped frames, expected %d, counted %d, encoder type %d\n", + expected, count, o->encoder->encoder_type); + exit(3); + } + } +} + +/* + * Wait until at least one pending event completes. Return mask of completed + * events. + */ +static unsigned int wait_for_events(struct test_output *o) +{ + drmEventContext evctx; + struct timeval timeout = { .tv_sec = 3, .tv_usec = 0 }; + fd_set fds; + unsigned int event_mask; + int ret; + + event_mask = o->pending_events; + assert(event_mask); + + memset(&evctx, 0, sizeof evctx); + evctx.version = DRM_EVENT_CONTEXT_VERSION; + evctx.vblank_handler = vblank_handler; + evctx.page_flip_handler = page_flip_handler; + + /* make timeout lax with the dummy load */ + if (o->flags & TEST_WITH_DUMMY_LOAD) + timeout.tv_sec *= 10; + + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(drm_fd, &fds); + ret = select(drm_fd + 1, &fds, NULL, NULL, &timeout); + + if (ret <= 0) { + fprintf(stderr, "select timed out or error (ret %d)\n", + ret); + exit(1); + } else if (FD_ISSET(0, &fds)) { + fprintf(stderr, "no fds active, breaking\n"); + exit(2); + } + + do_or_die(drmHandleEvent(drm_fd, &evctx)); + + event_mask ^= o->pending_events; + assert(event_mask); + + return event_mask; +} + +/* Returned the ellapsed time in us */ +static unsigned event_loop(struct test_output *o, unsigned duration_sec) +{ + unsigned long start, end; + + start = gettime_us(); + + while (1) { + unsigned int completed_events; + + completed_events = run_test_step(o); + if (o->pending_events) + completed_events |= wait_for_events(o); + check_all_state(o, completed_events); + update_all_state(o, completed_events); + + if ((gettime_us() - start) / 1000000 >= duration_sec) + break; + } + + end = gettime_us(); + + /* Flush any remaining events */ + if (o->pending_events) + wait_for_events(o); + + return end - start; +} + +static void run_test_on_crtc(struct test_output *o, int crtc, int duration) +{ + unsigned ellapsed; + + o->bpp = 32; + o->depth = 24; + + connector_find_preferred_mode(o, crtc); + if (!o->mode_valid) + return; + + fprintf(stdout, "Beginning %s on crtc %d, connector %d\n", + o->test_name, crtc, o->id); + + o->fb_width = o->mode.hdisplay; + o->fb_height = o->mode.vdisplay; + + if (o->flags & TEST_PAN) + o->fb_width *= 2; + + o->fb_ids[0] = kmstest_create_fb(drm_fd, o->fb_width, o->fb_height, + o->bpp, o->depth, false, &o->fb_info[0], + paint_flip_mode, (void *)false); + o->fb_ids[1] = kmstest_create_fb(drm_fd, o->fb_width, o->fb_height, + o->bpp, o->depth, false, &o->fb_info[1], + paint_flip_mode, (void *)true); + + if (!o->fb_ids[0] || !o->fb_ids[1]) { + fprintf(stderr, "failed to create fbs\n"); + exit(3); + } + + kmstest_dump_mode(&o->mode); + if (drmModeSetCrtc(drm_fd, o->crtc, o->fb_ids[0], 0, 0, + &o->id, 1, &o->mode)) { + fprintf(stderr, "failed to set mode (%dx%d@%dHz): %s\n", + o->fb_width, o->fb_height, o->mode.vrefresh, + strerror(errno)); + exit(3); + } + assert(fb_is_bound(o, o->fb_ids[0])); + + /* quiescent the hw a bit so ensure we don't miss a single frame */ + if (o->flags & TEST_CHECK_TS) + sleep(1); + + if (do_page_flip(o, o->fb_ids[1])) { + fprintf(stderr, "failed to page flip: %s\n", strerror(errno)); + exit(4); + } + wait_for_events(o); + + o->current_fb_id = 1; + o->flip_state.seq_step = 1; + if (o->flags & TEST_VBLANK_ABSOLUTE) + o->vblank_state.seq_step = 5; + else + o->vblank_state.seq_step = 1; + + ellapsed = event_loop(o, duration); + + if (o->flags & TEST_FLIP) + check_final_state(o, &o->flip_state, ellapsed); + if (o->flags & TEST_VBLANK) + check_final_state(o, &o->vblank_state, ellapsed); + + fprintf(stdout, "\n%s on crtc %d, connector %d: PASSED\n\n", + o->test_name, crtc, o->id); + + kmstest_remove_fb(drm_fd, o->fb_ids[1]); + kmstest_remove_fb(drm_fd, o->fb_ids[0]); + + drmModeFreeEncoder(o->encoder); + drmModeFreeConnector(o->connector); +} + +static int get_pipe_from_crtc_id(int crtc_id) +{ + struct drm_i915_get_pipe_from_crtc_id pfci; + int ret; + + memset(&pfci, 0, sizeof(pfci)); + pfci.crtc_id = crtc_id; + ret = drmIoctl(drm_fd, DRM_IOCTL_I915_GET_PIPE_FROM_CRTC_ID, &pfci); + assert(ret == 0); + + return pfci.pipe; +} + +static int run_test(int duration, int flags, const char *test_name) +{ + struct test_output o; + int c, i; + + resources = drmModeGetResources(drm_fd); + if (!resources) { + fprintf(stderr, "drmModeGetResources failed: %s\n", + strerror(errno)); + exit(5); + } + + /* Find any connected displays */ + for (c = 0; c < resources->count_connectors; c++) { + for (i = 0; i < resources->count_crtcs; i++) { + int crtc; + + memset(&o, 0, sizeof(o)); + o.test_name = test_name; + o.id = resources->connectors[c]; + o.flags = flags; + o.flip_state.name = "flip"; + o.vblank_state.name = "vblank"; + crtc = resources->crtcs[i]; + o.pipe = get_pipe_from_crtc_id(crtc); + + run_test_on_crtc(&o, crtc, duration); + } + } + + drmModeFreeResources(resources); + return 1; +} + +static void get_timestamp_format(void) +{ + uint64_t cap_mono; + int ret; + + ret = drmGetCap(drm_fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap_mono); + assert(ret == 0 || errno == EINVAL); + monotonic_timestamp = ret == 0 && cap_mono == 1; + printf("Using %s timestamps\n", + monotonic_timestamp ? "monotonic" : "real"); +} + +int main(int argc, char **argv) +{ + struct { + int duration; + int flags; + const char *name; + } tests[] = { + { 15, TEST_VBLANK, "wf_vblank" }, + { 15, TEST_VBLANK | TEST_CHECK_TS, "wf_vblank-ts-check" }, + { 15, TEST_VBLANK | TEST_VBLANK_BLOCK | TEST_CHECK_TS, + "blocking-wf_vblank" }, + { 5, TEST_VBLANK | TEST_VBLANK_ABSOLUTE, + "absolute-wf_vblank" }, + { 5, TEST_VBLANK | TEST_VBLANK_BLOCK | TEST_VBLANK_ABSOLUTE, + "blocking-absolute-wf_vblank" }, + { 30, TEST_VBLANK | TEST_DPMS | TEST_EINVAL, "wf_vblank-vs-dpms" }, + { 30, TEST_VBLANK | TEST_DPMS | TEST_WITH_DUMMY_LOAD, + "delayed-wf_vblank-vs-dpms" }, + { 30, TEST_VBLANK | TEST_MODESET | TEST_EINVAL, "wf_vblank-vs-modeset" }, + { 30, TEST_VBLANK | TEST_MODESET | TEST_WITH_DUMMY_LOAD, + "delayed-wf_vblank-vs-modeset" }, + + { 15, TEST_FLIP | TEST_EBUSY , "plain-flip" }, + { 15, TEST_FLIP | TEST_CHECK_TS | TEST_EBUSY , "plain-flip-ts-check" }, + { 15, TEST_FLIP | TEST_CHECK_TS | TEST_EBUSY | TEST_FB_RECREATE, + "plain-flip-fb-recreate" }, + { 15, TEST_FLIP | TEST_EBUSY | TEST_RMFB | TEST_MODESET , "flip-vs-rmfb" }, + { 30, TEST_FLIP | TEST_DPMS | TEST_EINVAL, "flip-vs-dpms" }, + { 30, TEST_FLIP | TEST_DPMS | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-dpms" }, + { 5, TEST_FLIP | TEST_PAN, "flip-vs-panning" }, + { 30, TEST_FLIP | TEST_PAN | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-panning" }, + { 30, TEST_FLIP | TEST_MODESET | TEST_EINVAL, "flip-vs-modeset" }, + { 30, TEST_FLIP | TEST_MODESET | TEST_WITH_DUMMY_LOAD, "delayed-flip-vs-modeset" }, + { 5, TEST_FLIP | TEST_VBLANK_EXPIRED_SEQ, + "flip-vs-expired-vblank" }, + + { 15, TEST_FLIP | TEST_VBLANK | TEST_VBLANK_ABSOLUTE | + TEST_CHECK_TS, "flip-vs-absolute-wf_vblank" }, + { 15, TEST_FLIP | TEST_VBLANK | TEST_CHECK_TS, + "flip-vs-wf_vblank" }, + { 15, TEST_FLIP | TEST_VBLANK | TEST_VBLANK_BLOCK | + TEST_CHECK_TS, "flip-vs-blocking-wf-vblank" }, + }; + int i; + + drmtest_subtest_init(argc, argv); + + drm_fd = drm_open_any(); + + if (!drmtest_only_list_subtests()) + get_timestamp_format(); + + bufmgr = drm_intel_bufmgr_gem_init(drm_fd, 4096); + devid = intel_get_drm_devid(drm_fd); + batch = intel_batchbuffer_alloc(bufmgr, devid); + + for (i = 0; i < sizeof(tests) / sizeof (tests[0]); i++) { + if (drmtest_run_subtest(tests[i].name)) { + printf("running testcase: %s\n", tests[i].name); + run_test(tests[i].duration, tests[i].flags, tests[i].name); + } + } + + close(drm_fd); + + return 0; +} -- cgit v1.2.3