[igt-dev] [PATCH i-g-t] tests/gem_exec_alignment.c: Add priority inversion test.

Dominik Grzegorzek dominik.grzegorzek at intel.com
Mon Mar 9 07:51:00 UTC 2020


 Updated subtest many, that was unusable due to the long
 time of execution. For that purpose global timount was added.
 Moreover priority inversion test case was implemented, which can point out
 that a low priority client causes a delay of a high priority client.

Signed-off-by: Dominik Grzegorzek <dominik.grzegorzek at intel.com>
Cc: Zbigniew Kempczyński <zbigniew.kempczynski at intel.com>
Cc: Chris Wilson <chris at chris-wilson.co.uk>
---
 tests/i915/gem_exec_alignment.c | 295 +++++++++++++++++++++++++++++---
 1 file changed, 273 insertions(+), 22 deletions(-)

diff --git a/tests/i915/gem_exec_alignment.c b/tests/i915/gem_exec_alignment.c
index a10571c9..fe323d9f 100644
--- a/tests/i915/gem_exec_alignment.c
+++ b/tests/i915/gem_exec_alignment.c
@@ -36,7 +36,13 @@
 #include <errno.h>
 #include <sys/stat.h>
 #include <sys/time.h>
+#include <signal.h>
+#include <sched.h>
 #include "drm.h"
+#include "semaphore.h"
+
+#define MANY_TIMEOUT 10
+#define LP_COUNT 512
 
 IGT_TEST_DESCRIPTION("Exercises the basic execbuffer using object alignments");
 
@@ -65,13 +71,240 @@ static uint32_t file_max(void)
 	return max;
 }
 
+static bool timed_out;
+static struct timespec start;
+static void alignment_alarm_handler(int signal)
+{
+	igt_debug("alignment test timed out!\n");
+
+	/**
+	 * If ioctl was't interrupted immidiatelly after reaching signal
+	 * that means kernel is not interaptible.
+	 */
+
+	if (start.tv_sec - MANY_TIMEOUT > 1 && !strcmp(igt_test_name(), "many"))
+		igt_debug("is kernel interuptible? %s\n",
+			  (start.tv_sec - MANY_TIMEOUT > 1) ? "no" : "yes");
+
+	timed_out = true;
+}
+
+static void alignment_set_timeout(unsigned int sec, unsigned int usec)
+{
+	struct sigaction sa = {};
+	struct itimerval itv = {};
+
+	sa.sa_handler = alignment_alarm_handler;
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = 0;
+	sigaction(SIGALRM, &sa, NULL);
+
+	timed_out = false;
+	itv.it_value.tv_sec = sec;
+	itv.it_value.tv_usec = usec;
+	setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static inline void alignment_reset_timeout(void)
+{
+	struct itimerval itv = {};
+
+	sigaction(SIGALRM, NULL, NULL);
+	setitimer(ITIMER_REAL, &itv, NULL);
+}
+
+static void prio_inversion(int fd)
+{
+	uint32_t bbe = MI_BATCH_BUFFER_END;
+	struct drm_i915_gem_exec_object2 *execobj, *execobj_hp, *empty;
+	struct drm_i915_gem_execbuffer2 execbuf;
+	struct timespec begin, now, begin_lp, now_lp;
+	uint64_t gtt_size, ram_size;
+	uint64_t max_alignment, count, i, flags;
+	uint32_t lp, hp, zero = 0, *res;
+	volatile uint32_t *result;
+	double time, time_lp;
+	sem_t *sem;
+
+	/*
+	 * First low pritority client create mass of holes in their
+	 * own address space, then launch a batch with oodles of object with
+	 * alignment that doesn't match previous one. While lp execbufer
+	 * is performing we want to start high priority task
+	 * and we expect it will not be blocked.
+	 */
+
+	igt_require(gem_uses_full_ppgtt(fd));
+
+	/* Using two pointers to avoid warnings about volatile discarding. */
+	result = res = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
+	memset(res, 0, 4096);
+	igt_assert(result != MAP_FAILED);
+
+	memset(&start, 0, sizeof(start));
+	memset(&execbuf, 0, sizeof(execbuf));
+
+	/* Calc number of objects */
+	gtt_size = gem_aperture_size(fd); /* We have to *share* our GTT! */
+	ram_size = intel_get_total_ram_mb();
+	ram_size *= 1024 * 1024;
+	count = ram_size / 4096;
+
+	if (count > file_max()) /* vfs cap */
+		count = file_max();
+	max_alignment = find_last_bit(gtt_size / count);
+	if (max_alignment <= 13)
+		max_alignment = 4096;
+	else
+		max_alignment = 1ull << (max_alignment - 1);
+	count = gtt_size / max_alignment / 2;
+
+	flags = (gtt_size-1) >> 32 ? 1<<3 : 0; /* EXEC_OBJECT_SUPPORTS_48B_ADDRESS */
+
+	execobj = calloc(sizeof(*execobj), count + 1);
+	igt_assert(execobj);
+
+	execobj_hp = calloc(sizeof(*execobj_hp), 1);
+	igt_assert(execobj_hp);
+
+	/* Fill the low-priority address space */
+	for (i = 0; i < count; i++) {
+		execobj[i].handle = gem_create(fd, 4096);
+		gem_write(fd, execobj[i].handle, 0, &zero, sizeof(zero));
+		execobj[i].flags = flags;
+		execobj[i].alignment = 4096;
+	}
+	execobj[i].handle = gem_create(fd, 4096);
+	execobj[i].alignment = 4096;
+	execobj[i].flags = flags;
+
+	gem_write(fd, execobj[i].handle, 0, &bbe, sizeof(bbe));
+	memset(&execbuf, 0, sizeof(execbuf));
+	execbuf.buffers_ptr = to_user_pointer(execobj + count);
+	execbuf.buffer_count = 1;
+
+	/* Warm up both (hi/lo) contexts */
+	hp = execbuf.rsvd1 = gem_context_create(fd);
+	gem_context_set_priority(fd, execbuf.rsvd1,
+				 I915_CONTEXT_MAX_USER_PRIORITY);
+	gem_execbuf(fd, &execbuf);
+	gem_sync(fd, execobj[i].handle);
+
+	/*
+	 * Create holes in address space by running a batch with 4Kb alignment
+	 * and then closing objects that dont match 8Kb alignment.
+	 * Finally create new LP_COUNT of objects.
+	 */
+
+	lp = execbuf.rsvd1 = gem_context_create(fd);
+	gem_context_set_priority(fd, execbuf.rsvd1,
+				 I915_CONTEXT_MIN_USER_PRIORITY);
+	execbuf.buffer_count = count + 1;
+	execbuf.buffers_ptr = to_user_pointer(execobj);
+	gem_execbuf(fd, &execbuf);
+	gem_sync(fd, execobj[count].handle);
+	igt_debug("Filled low-priority address space with %ld pages\n", count);
+
+	empty = &execobj[1];
+	for (i = 0; i < count - 2; i += 2) {
+		gem_close(fd, execobj[i+1].handle);
+		*empty++ = execobj[i+2];
+	}
+
+	for (i = 0; i < LP_COUNT; i++)
+		empty++->handle = gem_create(fd, 4096);
+
+	*empty = execobj[count];
+
+	memset(&execbuf, 0, sizeof(execbuf));
+	igt_debug("Starting quiescent\n");
+	gem_quiescent_gpu(fd);
+	igt_debug("Ending quiescent\n");
+
+	for (i = 0; i <= count/2 + LP_COUNT; i++)
+		execobj[i].alignment = 8192;
+
+	sem = sem_open("test_semaphore", O_CREAT|O_EXCL, 0, 1);
+	sem_unlink("test_semaphore");
+	sem_wait(sem);
+
+	igt_fork(child, 1) {
+		igt_debug("[H] In fork\n");
+		execobj_hp[0].handle = gem_create(fd, 4096);
+		igt_debug("[H] After create\n");
+
+		gem_write(fd, execobj_hp[0].handle, 0, &bbe, sizeof(bbe));
+		result[0] = hp != execbuf.rsvd1;
+
+		execbuf.rsvd1 = hp;
+		execbuf.buffer_count = 1;
+		execbuf.buffers_ptr =
+				to_user_pointer(execobj_hp);
+
+		/* Child sleeps waiting for hp task to start. */
+		sem_wait(sem);
+		sched_yield();
+		sem_post(sem);
+
+		clock_gettime(CLOCK_MONOTONIC, &begin);
+		usleep(100000);
+		alignment_set_timeout(0, 100000);
+		igt_debug("[H] HP child executing\n");
+		gem_execbuf(fd, &execbuf);
+		igt_debug("[H] HP exec submitted\n");
+		gem_sync(fd, execobj_hp[0].handle);
+		igt_debug("[H] HP sync\n");
+
+		alignment_reset_timeout();
+		clock_gettime(CLOCK_MONOTONIC, &now);
+		time = igt_time_elapsed(&begin, &now);
+		igt_debug("[H] HP exec performed in %.6fs\n", time);
+
+		result[1] = timed_out;
+		gem_close(fd, execobj_hp[0].handle);
+	}
+
+	/* Relinquish CPU just to allow child to create a context */
+	sleep(1);
+	igt_assert_f(result[0], "HP context (child) not created\n");
+
+	execbuf.buffer_count = count/2 + LP_COUNT + 1;
+	execbuf.buffers_ptr = to_user_pointer(execobj);
+	execbuf.rsvd1 = lp;
+
+	igt_debug("[L] LP parent executing\n");
+	clock_gettime(CLOCK_MONOTONIC, &begin_lp);
+
+	alignment_set_timeout(10, 0);
+	sem_post(sem);
+	gem_execbuf(fd, &execbuf);
+	gem_sync(fd, execobj[count].handle);
+	alignment_reset_timeout();
+
+	clock_gettime(CLOCK_MONOTONIC, &now_lp);
+	time_lp = igt_time_elapsed(&begin_lp, &now_lp);
+	igt_debug("[L] LP exec performed in %.6fs\n", time_lp);
+
+	igt_waitchildren();
+	igt_assert_f(!result[1], "HP child unable to submit within 100ms\n");
+
+	gem_context_destroy(fd, lp);
+	gem_context_destroy(fd, hp);
+
+	for (i = 0; i <= count/2 + LP_COUNT; i++)
+		gem_close(fd, execobj[i].handle);
+
+	munmap(res, 4096);
+}
+
 static void many(int fd)
 {
 	uint32_t bbe = MI_BATCH_BUFFER_END;
 	struct drm_i915_gem_exec_object2 *execobj;
 	struct drm_i915_gem_execbuffer2 execbuf;
-	uint64_t gtt_size, ram_size;
-	uint64_t alignment, max_alignment, count, i;
+	uint64_t gtt_size, ram_size, flags;
+	uint64_t alignment, max_alignment, count, max_count, curr_count, i;
+	int ret;
 
 	gtt_size = gem_aperture_size(fd);
 	if (!gem_uses_full_ppgtt(fd))
@@ -86,7 +319,9 @@ static void many(int fd)
 		max_alignment = 4096;
 	else
 		max_alignment = 1ull << (max_alignment - 1);
-	count = gtt_size / max_alignment / 2;
+	max_count = count = gtt_size / max_alignment / 2;
+
+	flags = (gtt_size-1) >> 32 ? 1<<3 : 0; /* EXEC_OBJECT_SUPPORTS_48B_ADDRESS */
 
 	igt_info("gtt_size=%lld MiB, max-alignment=%lld, count=%lld\n",
 		 (long long)gtt_size/1024/1024,
@@ -99,40 +334,54 @@ static void many(int fd)
 
 	for (i = 0; i < count; i++) {
 		execobj[i].handle = gem_create(fd, 4096);
-		if ((gtt_size-1) >> 32)
-			execobj[i].flags = 1<<3; /* EXEC_OBJECT_SUPPORTS_48B_ADDRESS */
+		execobj[i].flags = flags;
 	}
 	execobj[i].handle = gem_create(fd, 4096);
-	if ((gtt_size-1) >> 32)
-		execobj[i].flags = 1<<3; /* EXEC_OBJECT_SUPPORTS_48B_ADDRESS */
+	execobj[i].flags = flags;
 	gem_write(fd, execobj[i].handle, 0, &bbe, sizeof(bbe));
 
+
 	memset(&execbuf, 0, sizeof(execbuf));
 	execbuf.buffers_ptr = to_user_pointer(execobj);
 	execbuf.buffer_count = count + 1;
 	igt_require(__gem_execbuf(fd, &execbuf) == 0);
 
-	for (alignment = 4096; alignment < gtt_size; alignment <<= 1) {
-		for (i = 0; i < count; i++)
-			execobj[i].alignment = alignment;
+	alignment_set_timeout(MANY_TIMEOUT, 0);
+	clock_gettime(CLOCK_MONOTONIC, &start);
+
+	for (alignment = 4096; alignment < gtt_size && !timed_out; alignment <<= 1) {
 		if (alignment > max_alignment) {
 			uint64_t factor = alignment / max_alignment;
-			execbuf.buffer_count = 2*count / factor;
-			execbuf.buffers_ptr =
-				to_user_pointer(execobj + count - execbuf.buffer_count + 1);
+			max_count = 2 * count / factor;
 		}
 
-		igt_debug("testing %lld x alignment=%#llx [%db]\n",
-			  (long long)execbuf.buffer_count - 1,
-			  (long long)alignment,
-			  find_last_bit(alignment)-1);
-		gem_execbuf(fd, &execbuf);
-		for(i = count - execbuf.buffer_count + 1; i < count; i++) {
-			igt_assert_eq_u64(execobj[i].alignment, alignment);
-			igt_assert_eq_u64(execobj[i].offset % alignment, 0);
+		for (i = 0; i < count; i++)
+			execobj[i].alignment = alignment;
+
+		for (curr_count = 1; curr_count < max_count && !timed_out;
+		     curr_count <<= 1) {
+
+			execbuf.buffer_count = curr_count;
+			execbuf.buffers_ptr =
+					to_user_pointer(execobj + count - execbuf.buffer_count + 1);
+
+			igt_debug("testing %lld x alignment=%#llx [%db]\n",
+				  (long long)execbuf.buffer_count,
+				  (long long)alignment,
+				  find_last_bit(alignment)-1);
+			ret = __gem_execbuf(fd, &execbuf);
+
+			if (ret == 0)
+				for (i = count - execbuf.buffer_count + 1; i < count; i++) {
+					igt_assert_eq_u64(execobj[i].alignment, alignment);
+					igt_assert_eq_u64(execobj[i].offset % alignment, 0);
+				}
+
+			if (ret != EINTR)
+				igt_assert_eq(ret, 0);
 		}
 	}
-
+	alignment_reset_timeout();
 	for (i = 0; i < count; i++)
 		gem_close(fd, execobj[i].handle);
 	gem_close(fd, execobj[i].handle);
@@ -208,5 +457,7 @@ igt_main
 		single(fd);
 	igt_subtest("many")
 		many(fd);
+	igt_subtest("pi")
+		prio_inversion(fd);
 
 }
-- 
2.20.1



More information about the igt-dev mailing list