diff mbox

[igt] igt/gem_wait: Use explicit timers

Message ID 20161012152224.18947-1-chris@chris-wilson.co.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Chris Wilson Oct. 12, 2016, 3:22 p.m. UTC
Rather than guestimating a workload that should take a certain amount of
time, use a sigitimer to terminate a batch (and so complete the wait)
after an exact amount of time. And in the process expand testing to
cover multiple rings and hangcheck.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
---
 lib/igt_core.h   |  27 ++++
 tests/gem_wait.c | 443 +++++++++++++++++++++++++++++++------------------------
 2 files changed, 277 insertions(+), 193 deletions(-)
diff mbox

Patch

diff --git a/lib/igt_core.h b/lib/igt_core.h
index 433b88c..03be757 100644
--- a/lib/igt_core.h
+++ b/lib/igt_core.h
@@ -403,6 +403,24 @@  static inline void igt_ignore_warn(bool value)
 	} while (0)
 
 /**
+ * igt_assert_cmps64:
+ * @n1: first value
+ * @cmp: compare operator
+ * @ncmp: negated version of @cmp
+ * @n2: second value
+ *
+ * Like igt_assert_cmpuint(), but for larger signed ints.
+ */
+#define igt_assert_cmps64(n1, cmp, ncmp, n2) \
+	do { \
+		int64_t __n1 = (n1), __n2 = (n2); \
+		if (__n1 cmp __n2) ; else \
+		__igt_fail_assert(IGT_LOG_DOMAIN, __FILE__, __LINE__, __func__, \
+				  #n1 " " #cmp " " #n2, \
+				  "error: %lld " #ncmp " %lld\n", (long long)__n1, (long long)__n2); \
+	} while (0)
+
+/**
  * igt_assert_cmpu64:
  * @n1: first value
  * @cmp: compare operator
@@ -461,6 +479,15 @@  static inline void igt_ignore_warn(bool value)
 #define igt_assert_eq_u32(n1, n2) igt_assert_cmpuint(n1, ==, !=, n2)
 
 /**
+ * igt_assert_eq_s64:
+ * @n1: first integer
+ * @n2: second integer
+ *
+ * Like igt_assert_eq_u32(), but for int64_t.
+ */
+#define igt_assert_eq_s64(n1, n2) igt_assert_cmps64(n1, ==, !=, n2)
+
+/**
  * igt_assert_eq_u64:
  * @n1: first integer
  * @n2: second integer
diff --git a/tests/gem_wait.c b/tests/gem_wait.c
index 461efdb..0ecb92f 100644
--- a/tests/gem_wait.c
+++ b/tests/gem_wait.c
@@ -26,233 +26,290 @@ 
  */
 
 #include "igt.h"
-#include <stdio.h>
+
+#include <signal.h>
 #include <time.h>
-#include <stdlib.h>
-#include <sys/ioctl.h>
-#include <stdio.h>
-#include <string.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-
-#include <drm.h>
-
-#include "intel_bufmgr.h"
-
-#define MSEC_PER_SEC	1000L
-#define USEC_PER_MSEC	1000L
-#define NSEC_PER_USEC	1000L
-#define NSEC_PER_MSEC	1000000L
-#define USEC_PER_SEC	1000000L
-#define NSEC_PER_SEC	1000000000L
-
-#define ENOUGH_WORK_IN_SECONDS 2
-#define BUF_SIZE (8<<20)
-#define BUF_PAGES ((8<<20)>>12)
-drm_intel_bo *dst, *dst2;
-
-/* returns time diff in milliseconds */
-static int64_t
-do_time_diff(struct timespec *end, struct timespec *start)
-{
-	int64_t ret;
-	ret = (MSEC_PER_SEC * difftime(end->tv_sec, start->tv_sec)) +
-	      ((end->tv_nsec/NSEC_PER_MSEC) - (start->tv_nsec/NSEC_PER_MSEC));
-	return ret;
-}
+#include <sys/syscall.h>
 
-static void blt_color_fill(struct intel_batchbuffer *batch,
-			   drm_intel_bo *buf,
-			   const unsigned int pages)
-{
-	const unsigned short height = pages/4;
-	const unsigned short width =  4096;
-
-	COLOR_BLIT_COPY_BATCH_START(COLOR_BLT_WRITE_ALPHA |
-				    XY_COLOR_BLT_WRITE_RGB);
-	OUT_BATCH((3 << 24)	| /* 32 Bit Color */
-		  (0xF0 << 16)	| /* Raster OP copy background register */
-		  0);		  /* Dest pitch is 0 */
-	OUT_BATCH(0);
-	OUT_BATCH(width << 16	|
-		  height);
-	OUT_RELOC_FENCED(buf, I915_GEM_DOMAIN_RENDER, I915_GEM_DOMAIN_RENDER, 0);
-	OUT_BATCH(rand()); /* random pattern */
-	ADVANCE_BATCH();
-}
+#define gettid() syscall(__NR_gettid)
+#define sigev_notify_thread_id _sigev_un._tid
 
-static void render_timeout(int fd)
+#define LOCAL_I915_EXEC_BSD_SHIFT      (13)
+#define LOCAL_I915_EXEC_BSD_MASK       (3 << LOCAL_I915_EXEC_BSD_SHIFT)
+
+#define ENGINE_MASK  (I915_EXEC_RING_MASK | LOCAL_I915_EXEC_BSD_MASK)
+
+static int __gem_wait(int fd, struct drm_i915_gem_wait *w)
 {
-	drm_intel_bufmgr *bufmgr;
-	struct intel_batchbuffer *batch;
-	int64_t timeout = ENOUGH_WORK_IN_SECONDS * NSEC_PER_SEC;
-	int64_t negative_timeout = -1;
-	int ret;
-	const bool do_signals = true; /* signals will seem to make the operation
-				       * use less process CPU time */
-	bool done = false;
-	int i, iter = 1;
+	int err;
 
-	igt_skip_on_simulation();
+	err = 0;
+	if (igt_ioctl(fd, DRM_IOCTL_I915_GEM_WAIT, w))
+		err = -errno;
 
-	bufmgr = drm_intel_bufmgr_gem_init(fd, 4096);
-	drm_intel_bufmgr_gem_enable_reuse(bufmgr);
-	batch = intel_batchbuffer_alloc(bufmgr, intel_get_drm_devid(fd));
-
-	dst = drm_intel_bo_alloc(bufmgr, "dst", BUF_SIZE, 4096);
-	dst2 = drm_intel_bo_alloc(bufmgr, "dst2", BUF_SIZE, 4096);
-
-	igt_skip_on_f(gem_wait(fd, dst->handle, &timeout) == -EINVAL,
-		      "kernel doesn't support wait_timeout, skipping test\n");
-	timeout = ENOUGH_WORK_IN_SECONDS * NSEC_PER_SEC;
-
-	/* Figure out a rough number of fills required to consume 1 second of
-	 * GPU work.
-	 */
-	do {
-		struct timespec start, end;
-		long diff;
-
-#ifndef CLOCK_MONOTONIC_RAW
-#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
-#endif
-
-		igt_assert(clock_gettime(CLOCK_MONOTONIC_RAW, &start) == 0);
-		for (i = 0; i < iter; i++)
-			blt_color_fill(batch, dst, BUF_PAGES);
-		intel_batchbuffer_flush(batch);
-		drm_intel_bo_wait_rendering(dst);
-		igt_assert(clock_gettime(CLOCK_MONOTONIC_RAW, &end) == 0);
-
-		diff = do_time_diff(&end, &start);
-		igt_assert(diff >= 0);
-
-		if ((diff / MSEC_PER_SEC) > ENOUGH_WORK_IN_SECONDS)
-			done = true;
-		else
-			iter <<= 1;
-	} while (!done && iter < 1000000);
-
-	igt_assert_lt(iter, 1000000);
-
-	igt_debug("%d iters is enough work\n", iter);
-	gem_quiescent_gpu(fd);
-	if (do_signals)
-		igt_fork_signal_helper();
-
-	/* We should be able to do half as much work in the same amount of time,
-	 * but because we might schedule almost twice as much as required, we
-	 * might accidentally time out. Hence add some fudge. */
-	for (i = 0; i < iter/3; i++)
-		blt_color_fill(batch, dst2, BUF_PAGES);
-
-	intel_batchbuffer_flush(batch);
-	igt_assert(gem_bo_busy(fd, dst2->handle) == true);
-
-	igt_assert_eq(gem_wait(fd, dst2->handle, &timeout), 0);
-	igt_assert(gem_bo_busy(fd, dst2->handle) == false);
-	igt_assert_neq(timeout, 0);
-	if (timeout ==  (ENOUGH_WORK_IN_SECONDS * NSEC_PER_SEC))
-		igt_info("Buffer was already done!\n");
-	else
-		igt_info("Finished with %fs remaining\n", timeout*1e-9);
-
-	/* check that polling with timeout=0 works. */
-	timeout = 0;
-	igt_assert_eq(gem_wait(fd, dst2->handle, &timeout), 0);
-	igt_assert_eq(timeout, 0);
-
-	/* Now check that we correctly time out, twice the auto-tune load should
-	 * be good enough. */
-	timeout = ENOUGH_WORK_IN_SECONDS * NSEC_PER_SEC;
-	for (i = 0; i < iter*2; i++)
-		blt_color_fill(batch, dst2, BUF_PAGES);
-
-	intel_batchbuffer_flush(batch);
-
-	ret = gem_wait(fd, dst2->handle, &timeout);
-	igt_assert_eq(ret, -ETIME);
-	igt_assert_eq(timeout, 0);
-	igt_assert(gem_bo_busy(fd, dst2->handle) == true);
-
-	/* check that polling with timeout=0 works. */
-	timeout = 0;
-	igt_assert_eq(gem_wait(fd, dst2->handle, &timeout), -ETIME);
-	igt_assert_eq(timeout, 0);
-
-
-	/* Now check that we can pass negative (infinite) timeouts. */
-	negative_timeout = -1;
-	for (i = 0; i < iter; i++)
-		blt_color_fill(batch, dst2, BUF_PAGES);
-
-	intel_batchbuffer_flush(batch);
-
-	igt_assert_eq(gem_wait(fd, dst2->handle, &negative_timeout), 0);
-	igt_assert_eq(negative_timeout, -1); /* infinity always remains */
-	igt_assert(gem_bo_busy(fd, dst2->handle) == false);
-
-	if (do_signals)
-		igt_stop_signal_helper();
-	drm_intel_bo_unreference(dst2);
-	drm_intel_bo_unreference(dst);
-	intel_batchbuffer_free(batch);
-	drm_intel_bufmgr_destroy(bufmgr);
+	return err;
 }
 
 static void invalid_flags(int fd)
 {
 	struct drm_i915_gem_wait wait;
-	int ret;
-	uint32_t handle;
 
-	handle = gem_create(fd, 4096);
-
-	wait.bo_handle = handle;
+	memset(&wait, 0, sizeof(wait));
+	wait.bo_handle = gem_create(fd, 4096);
 	wait.timeout_ns = 1;
 	/* NOTE: This test intentionally tests for just the next available flag.
 	 * Don't "fix" this testcase without the ABI testcases for new flags
 	 * first. */
 	wait.flags = 1;
-	ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_WAIT, &wait);
 
-	igt_assert(ret != 0 && errno == EINVAL);
+	igt_assert_eq(__gem_wait(fd, &wait), -EINVAL);
 
-	gem_close(fd, handle);
+	gem_close(fd, wait.bo_handle);
 }
 
 static void invalid_buf(int fd)
 {
 	struct drm_i915_gem_wait wait;
-	int ret;
 
-	wait.bo_handle = 0;
-	wait.timeout_ns = 1;
-	wait.flags = 0;
-	ret = drmIoctl(fd, DRM_IOCTL_I915_GEM_WAIT, &wait);
+	memset(&wait, 0, sizeof(wait));
+	igt_assert_eq(__gem_wait(fd, &wait), -ENOENT);
+}
+
+static uint32_t *batch;
 
-	igt_assert(ret != 0 && errno == ENOENT);
+static void sigiter(int sig, siginfo_t *info, void *arg)
+{
+	*batch = MI_BATCH_BUFFER_END;
+	__sync_synchronize();
 }
 
-int drm_fd;
+#define MSEC_PER_SEC (1000)
+#define USEC_PER_SEC (1000 * MSEC_PER_SEC)
+#define NSEC_PER_SEC (1000 * USEC_PER_SEC)
+
+#define BUSY 1
+#define HANG 2
+static void basic(int fd, unsigned engine, unsigned flags)
+{
+	const int gen = intel_gen(intel_get_drm_devid(fd));
+	struct drm_i915_gem_exec_object2 obj;
+	struct drm_i915_gem_relocation_entry reloc;
+	struct drm_i915_gem_execbuffer2 execbuf;
+	struct drm_i915_gem_wait wait;
+	unsigned engines[16];
+	unsigned nengine;
+	int i, timeout;
+
+	nengine = 0;
+	if (engine == -1) {
+		for_each_engine(fd, engine)
+			if (engine) engines[nengine++] = engine;
+	} else {
+		igt_require(gem_has_ring(fd, engine));
+		engines[nengine++] = engine;
+	}
+	igt_require(nengine);
+
+	memset(&execbuf, 0, sizeof(execbuf));
+	execbuf.buffers_ptr = (uintptr_t)&obj;
+	execbuf.buffer_count = 1;
+
+	memset(&obj, 0, sizeof(obj));
+	obj.handle = gem_create(fd, 4096);
+
+	obj.relocs_ptr = (uintptr_t)&reloc;
+	obj.relocation_count = 1;
+	memset(&reloc, 0, sizeof(reloc));
+
+	batch = gem_mmap__gtt(fd, obj.handle, 4096, PROT_WRITE);
+	gem_set_domain(fd, obj.handle,
+			I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);
+
+	reloc.target_handle = obj.handle; /* recurse */
+	reloc.presumed_offset = 0;
+	reloc.offset = sizeof(uint32_t);
+	reloc.delta = 0;
+	reloc.read_domains = I915_GEM_DOMAIN_COMMAND;
+	reloc.write_domain = 0;
+
+	i = 0;
+	batch[i] = MI_BATCH_BUFFER_START;
+	if (gen >= 8) {
+		batch[i] |= 1 << 8 | 1;
+		batch[++i] = 0;
+		batch[++i] = 0;
+	} else if (gen >= 6) {
+		batch[i] |= 1 << 8;
+		batch[++i] = 0;
+	} else {
+		batch[i] |= 2 << 6;
+		batch[++i] = 0;
+		if (gen < 4) {
+			batch[i] |= 1;
+			reloc.delta = 1;
+		}
+	}
+
+	for (i = 0; i < nengine; i++) {
+		execbuf.flags &= ~ENGINE_MASK;
+		execbuf.flags |= engines[i];
+		gem_execbuf(fd, &execbuf);
+	}
+
+	memset(&wait, 0, sizeof(wait));
+	wait.bo_handle = obj.handle;
+	igt_assert_eq(__gem_wait(fd, &wait), -ETIME);
+
+	if (flags & BUSY) {
+		struct timespec tv;
+
+		timeout = 120;
+		if ((flags & HANG) == 0) {
+			*batch = MI_BATCH_BUFFER_END;
+			__sync_synchronize();
+			timeout = 1;
+		}
+		munmap(batch, 4096);
+
+		memset(&tv, 0, sizeof(tv));
+		while (__gem_wait(fd, &wait) == -ETIME)
+			igt_assert(igt_seconds_elapsed(&tv) < timeout);
+	} else {
+		timer_t timer;
+
+		if ((flags & HANG) == 0) {
+			struct sigevent sev;
+			struct sigaction act;
+			struct itimerspec its;
+
+			memset(&sev, 0, sizeof(sev));
+			sev.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID;
+			sev.sigev_notify_thread_id = gettid();
+			sev.sigev_signo = SIGRTMIN + 1;
+			igt_assert(timer_create(CLOCK_MONOTONIC, &sev, &timer) == 0);
+
+			memset(&act, 0, sizeof(act));
+			act.sa_sigaction = sigiter;
+			act.sa_flags = SA_SIGINFO;
+			igt_assert(sigaction(SIGRTMIN + 1, &act, NULL) == 0);
+
+			memset(&its, 0, sizeof(its));
+			its.it_value.tv_nsec = 0;
+			its.it_value.tv_sec = 1;
+			igt_assert(timer_settime(timer, 0, &its, NULL) == 0);
+		}
+
+		wait.timeout_ns = NSEC_PER_SEC / 2; /* 0.5s */
+		igt_assert_eq(__gem_wait(fd, &wait), -ETIME);
+		igt_assert_eq_s64(wait.timeout_ns, 0);
+
+		if ((flags & HANG) == 0) {
+			wait.timeout_ns = NSEC_PER_SEC; /* 1.0s */
+			igt_assert_eq(__gem_wait(fd, &wait), 0);
+			igt_assert(wait.timeout_ns > 0);
+		} else {
+			wait.timeout_ns = -1;
+			igt_assert_eq(__gem_wait(fd, &wait), 0);
+			igt_assert(wait.timeout_ns == -1);
+		}
+
+		wait.timeout_ns = 0;
+		igt_assert_eq(__gem_wait(fd, &wait), 0);
+		igt_assert(wait.timeout_ns == 0);
+
+		if ((flags & HANG) == 0)
+			timer_delete(timer);
+	}
+
+	gem_close(fd, obj.handle);
+}
 
 igt_main
 {
-	igt_fixture
-		drm_fd = drm_open_driver(DRIVER_INTEL);
+	const struct intel_execution_engine *e;
+	int fd = -1;
 
-	igt_subtest("render_timeout")
-		render_timeout(drm_fd);
+	igt_skip_on_simulation();
+
+	igt_fixture {
+		fd = drm_open_driver_master(DRIVER_INTEL);
+	}
 
 	igt_subtest("invalid-flags")
-		invalid_flags(drm_fd);
+		invalid_flags(fd);
 
 	igt_subtest("invalid-buf")
-		invalid_buf(drm_fd);
-
-	igt_fixture
-		close(drm_fd);
+		invalid_buf(fd);
+
+	igt_subtest_group {
+		igt_fixture {
+			igt_fork_hang_detector(fd);
+			igt_fork_signal_helper();
+		}
+
+		igt_subtest("basic-busy-all") {
+			gem_quiescent_gpu(fd);
+			basic(fd, -1, BUSY);
+		}
+		igt_subtest("basic-wait-all") {
+			gem_quiescent_gpu(fd);
+			basic(fd, -1, 0);
+		}
+
+		for (e = intel_execution_engines; e->name; e++) {
+			igt_subtest_group {
+				igt_subtest_f("busy-%s", e->name) {
+					gem_quiescent_gpu(fd);
+					basic(fd, e->exec_id | e->flags, BUSY);
+				}
+				igt_subtest_f("wait-%s", e->name) {
+					gem_quiescent_gpu(fd);
+					basic(fd, e->exec_id | e->flags, 0);
+				}
+			}
+		}
+
+		igt_fixture {
+			igt_stop_signal_helper();
+			igt_stop_hang_detector();
+		}
+	}
+
+	igt_subtest_group {
+		igt_hang_t hang;
+
+		igt_fixture {
+			hang = igt_allow_hang(fd, 0, 0);
+			igt_fork_signal_helper();
+		}
+
+		igt_subtest("hang-busy-all") {
+			gem_quiescent_gpu(fd);
+			basic(fd, -1, BUSY | HANG);
+		}
+		igt_subtest("hang-wait-all") {
+			gem_quiescent_gpu(fd);
+			basic(fd, -1, HANG);
+		}
+
+		for (e = intel_execution_engines; e->name; e++) {
+			igt_subtest_f("hang-busy-%s", e->name) {
+				gem_quiescent_gpu(fd);
+				basic(fd, e->exec_id | e->flags, HANG | BUSY);
+			}
+			igt_subtest_f("hang-wait-%s", e->name) {
+				gem_quiescent_gpu(fd);
+				basic(fd, e->exec_id | e->flags, HANG);
+			}
+		}
+
+		igt_fixture {
+			igt_stop_signal_helper();
+			igt_disallow_hang(fd, hang);
+		}
+	}
+
+	igt_fixture {
+		close(fd);
+	}
 }