summaryrefslogtreecommitdiff
path: root/tests/i915/gem_exec_fence.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/i915/gem_exec_fence.c')
-rw-r--r--tests/i915/gem_exec_fence.c1731
1 files changed, 1731 insertions, 0 deletions
diff --git a/tests/i915/gem_exec_fence.c b/tests/i915/gem_exec_fence.c
new file mode 100644
index 00000000..ba46595d
--- /dev/null
+++ b/tests/i915/gem_exec_fence.c
@@ -0,0 +1,1731 @@
+/*
+ * Copyright © 2016 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.
+ */
+
+#include "igt.h"
+#include "igt_sysfs.h"
+#include "igt_vgem.h"
+#include "sw_sync.h"
+#include "i915/gem_ring.h"
+
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/signal.h>
+
+IGT_TEST_DESCRIPTION("Check that execbuf waits for explicit fences");
+
+#define LOCAL_EXEC_FENCE_IN (1 << 16)
+#define LOCAL_EXEC_FENCE_OUT (1 << 17)
+#define LOCAL_EXEC_FENCE_SUBMIT (1 << 20)
+
+#define LOCAL_EXEC_FENCE_ARRAY (1 << 19)
+struct local_gem_exec_fence {
+ uint32_t handle;
+ uint32_t flags;
+#define LOCAL_EXEC_FENCE_WAIT (1 << 0)
+#define LOCAL_EXEC_FENCE_SIGNAL (1 << 1)
+};
+
+#ifndef SYNC_IOC_MERGE
+struct sync_merge_data {
+ char name[32];
+ int32_t fd2;
+ int32_t fence;
+ uint32_t flags;
+ uint32_t pad;
+};
+#define SYNC_IOC_MAGIC '>'
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)
+#endif
+
+static void store(int fd, unsigned ring, int fence, uint32_t target, unsigned offset_value)
+{
+ const int SCRATCH = 0;
+ const int BATCH = 1;
+ const int gen = intel_gen(intel_get_drm_devid(fd));
+ struct drm_i915_gem_exec_object2 obj[2];
+ struct drm_i915_gem_relocation_entry reloc;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ uint32_t batch[16];
+ int i;
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(obj);
+ execbuf.buffer_count = 2;
+ execbuf.flags = ring | LOCAL_EXEC_FENCE_IN;
+ execbuf.rsvd2 = fence;
+ if (gen < 6)
+ execbuf.flags |= I915_EXEC_SECURE;
+
+ memset(obj, 0, sizeof(obj));
+ obj[SCRATCH].handle = target;
+
+ obj[BATCH].handle = gem_create(fd, 4096);
+ obj[BATCH].relocs_ptr = to_user_pointer(&reloc);
+ obj[BATCH].relocation_count = 1;
+ memset(&reloc, 0, sizeof(reloc));
+
+ i = 0;
+ reloc.target_handle = obj[SCRATCH].handle;
+ reloc.presumed_offset = -1;
+ reloc.offset = sizeof(uint32_t) * (i + 1);
+ reloc.delta = sizeof(uint32_t) * offset_value;
+ reloc.read_domains = I915_GEM_DOMAIN_INSTRUCTION;
+ reloc.write_domain = I915_GEM_DOMAIN_INSTRUCTION;
+ batch[i] = MI_STORE_DWORD_IMM | (gen < 6 ? 1 << 22 : 0);
+ if (gen >= 8) {
+ batch[++i] = reloc.delta;
+ batch[++i] = 0;
+ } else if (gen >= 4) {
+ batch[++i] = 0;
+ batch[++i] = reloc.delta;
+ reloc.offset += sizeof(uint32_t);
+ } else {
+ batch[i]--;
+ batch[++i] = reloc.delta;
+ }
+ batch[++i] = offset_value;
+ batch[++i] = MI_BATCH_BUFFER_END;
+ gem_write(fd, obj[BATCH].handle, 0, batch, sizeof(batch));
+ gem_execbuf(fd, &execbuf);
+ gem_close(fd, obj[BATCH].handle);
+}
+
+static bool fence_busy(int fence)
+{
+ return poll(&(struct pollfd){fence, POLLIN}, 1, 0) == 0;
+}
+
+#define HANG 0x1
+#define NONBLOCK 0x2
+#define WAIT 0x4
+
+static void test_fence_busy(int fd, unsigned ring, 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 timespec tv;
+ uint32_t *batch;
+ int fence, i, timeout;
+
+ gem_quiescent_gpu(fd);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = ring | LOCAL_EXEC_FENCE_OUT;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+
+ obj.relocs_ptr = to_user_pointer(&reloc);
+ obj.relocation_count = 1;
+ memset(&reloc, 0, sizeof(reloc));
+
+ batch = gem_mmap__wc(fd, obj.handle, 0, 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;
+ }
+ }
+ i++;
+
+ execbuf.rsvd2 = -1;
+ gem_execbuf_wr(fd, &execbuf);
+ fence = execbuf.rsvd2 >> 32;
+ igt_assert(fence != -1);
+
+ igt_assert(gem_bo_busy(fd, obj.handle));
+ igt_assert(fence_busy(fence));
+
+ timeout = 120;
+ if ((flags & HANG) == 0) {
+ *batch = MI_BATCH_BUFFER_END;
+ __sync_synchronize();
+ timeout = 1;
+ }
+ munmap(batch, 4096);
+
+ if (flags & WAIT) {
+ struct pollfd pfd = { .fd = fence, .events = POLLIN };
+ igt_assert(poll(&pfd, 1, timeout*1000) == 1);
+ } else {
+ memset(&tv, 0, sizeof(tv));
+ while (fence_busy(fence))
+ igt_assert(igt_seconds_elapsed(&tv) < timeout);
+ }
+
+ igt_assert(!gem_bo_busy(fd, obj.handle));
+ igt_assert_eq(sync_fence_status(fence),
+ flags & HANG ? -EIO : SYNC_FENCE_OK);
+
+ close(fence);
+ gem_close(fd, obj.handle);
+
+ gem_quiescent_gpu(fd);
+}
+
+static void test_fence_busy_all(int fd, 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 timespec tv;
+ uint32_t *batch;
+ unsigned int engine;
+ int all, i, timeout;
+
+ gem_quiescent_gpu(fd);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+
+ obj.relocs_ptr = to_user_pointer(&reloc);
+ obj.relocation_count = 1;
+ memset(&reloc, 0, sizeof(reloc));
+
+ batch = gem_mmap__wc(fd, obj.handle, 0, 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;
+ }
+ }
+ i++;
+
+ all = -1;
+ for_each_engine(fd, engine) {
+ int fence, new;
+
+ execbuf.flags = engine | LOCAL_EXEC_FENCE_OUT;
+ execbuf.rsvd2 = -1;
+ gem_execbuf_wr(fd, &execbuf);
+ fence = execbuf.rsvd2 >> 32;
+ igt_assert(fence != -1);
+
+ if (all < 0) {
+ all = fence;
+ break;
+ }
+
+ new = sync_fence_merge(all, fence);
+ igt_assert_lte(0, new);
+ close(all);
+ close(fence);
+
+ all = new;
+ }
+
+ igt_assert(gem_bo_busy(fd, obj.handle));
+ igt_assert(fence_busy(all));
+
+ timeout = 120;
+ if ((flags & HANG) == 0) {
+ *batch = MI_BATCH_BUFFER_END;
+ __sync_synchronize();
+ timeout = 1;
+ }
+ munmap(batch, 4096);
+
+ if (flags & WAIT) {
+ struct pollfd pfd = { .fd = all, .events = POLLIN };
+ igt_assert(poll(&pfd, 1, timeout*1000) == 1);
+ } else {
+ memset(&tv, 0, sizeof(tv));
+ while (fence_busy(all))
+ igt_assert(igt_seconds_elapsed(&tv) < timeout);
+ }
+
+ igt_assert(!gem_bo_busy(fd, obj.handle));
+ igt_assert_eq(sync_fence_status(all),
+ flags & HANG ? -EIO : SYNC_FENCE_OK);
+
+ close(all);
+ gem_close(fd, obj.handle);
+
+ gem_quiescent_gpu(fd);
+}
+
+static void test_fence_await(int fd, unsigned ring, 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;
+ uint32_t scratch = gem_create(fd, 4096);
+ uint32_t *batch, *out;
+ unsigned engine;
+ int fence, i;
+
+ igt_require(gem_can_store_dword(fd, 0));
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = ring | LOCAL_EXEC_FENCE_OUT;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+
+ obj.relocs_ptr = to_user_pointer(&reloc);
+ obj.relocation_count = 1;
+ memset(&reloc, 0, sizeof(reloc));
+
+ out = gem_mmap__wc(fd, scratch, 0, 4096, PROT_WRITE);
+ gem_set_domain(fd, obj.handle,
+ I915_GEM_DOMAIN_GTT, I915_GEM_DOMAIN_GTT);
+
+ batch = gem_mmap__wc(fd, obj.handle, 0, 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;
+ }
+ }
+ i++;
+
+ execbuf.rsvd2 = -1;
+ gem_execbuf_wr(fd, &execbuf);
+ gem_close(fd, obj.handle);
+ fence = execbuf.rsvd2 >> 32;
+ igt_assert(fence != -1);
+
+ i = 0;
+ for_each_physical_engine(fd, engine) {
+ if (!gem_can_store_dword(fd, engine))
+ continue;
+
+ if (flags & NONBLOCK) {
+ store(fd, engine, fence, scratch, i);
+ } else {
+ igt_fork(child, 1)
+ store(fd, engine, fence, scratch, i);
+ }
+
+ i++;
+ }
+ close(fence);
+
+ sleep(1);
+
+ /* Check for invalidly completing the task early */
+ for (int n = 0; n < i; n++)
+ igt_assert_eq_u32(out[n], 0);
+
+ if ((flags & HANG) == 0) {
+ *batch = MI_BATCH_BUFFER_END;
+ __sync_synchronize();
+ }
+ munmap(batch, 4096);
+
+ igt_waitchildren();
+
+ gem_set_domain(fd, scratch, I915_GEM_DOMAIN_GTT, 0);
+ while (i--)
+ igt_assert_eq_u32(out[i], i);
+ munmap(out, 4096);
+ gem_close(fd, scratch);
+}
+
+static void resubmit(int fd, uint32_t handle, unsigned int ring, int count)
+{
+ struct drm_i915_gem_exec_object2 obj = { .handle = handle };
+ struct drm_i915_gem_execbuffer2 execbuf = {
+ .buffers_ptr = to_user_pointer(&obj),
+ .buffer_count = 1,
+ .flags = ring,
+ };
+ while (count--)
+ gem_execbuf(fd, &execbuf);
+}
+
+static void alarm_handler(int sig)
+{
+}
+
+static int __execbuf(int fd, struct drm_i915_gem_execbuffer2 *execbuf)
+{
+ int err;
+
+ err = 0;
+ if (ioctl(fd, DRM_IOCTL_I915_GEM_EXECBUFFER2_WR, execbuf))
+ err = -errno;
+
+ errno = 0;
+ return err;
+}
+
+static void test_parallel(int fd, unsigned int master)
+{
+ const int SCRATCH = 0;
+ const int BATCH = 1;
+ const int gen = intel_gen(intel_get_drm_devid(fd));
+ struct drm_i915_gem_exec_object2 obj[2];
+ struct drm_i915_gem_relocation_entry reloc[2];
+ struct drm_i915_gem_execbuffer2 execbuf;
+ uint32_t scratch = gem_create(fd, 4096);
+ uint32_t *out = gem_mmap__wc(fd, scratch, 0, 4096, PROT_READ);
+ uint32_t handle[16];
+ uint32_t batch[16];
+ igt_spin_t *spin;
+ unsigned engine;
+ IGT_CORK_HANDLE(c);
+ uint32_t plug;
+ int i, x = 0;
+
+ plug = igt_cork_plug(&c, fd);
+
+ /* Fill the queue with many requests so that the next one has to
+ * wait before it can be executed by the hardware.
+ */
+ spin = igt_spin_batch_new(fd, .engine = master, .dependency = plug);
+ resubmit(fd, spin->handle, master, 16);
+
+ /* Now queue the master request and its secondaries */
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(obj);
+ execbuf.buffer_count = 2;
+ execbuf.flags = master | LOCAL_EXEC_FENCE_OUT;
+ if (gen < 6)
+ execbuf.flags |= I915_EXEC_SECURE;
+
+ memset(obj, 0, sizeof(obj));
+ obj[SCRATCH].handle = scratch;
+
+ obj[BATCH].handle = gem_create(fd, 4096);
+ handle[x] = obj[BATCH].handle;
+ obj[BATCH].relocs_ptr = to_user_pointer(&reloc);
+ obj[BATCH].relocation_count = 2;
+ memset(reloc, 0, sizeof(reloc));
+
+ i = 0;
+
+ reloc[0].target_handle = obj[SCRATCH].handle;
+ reloc[0].presumed_offset = -1;
+ reloc[0].offset = sizeof(uint32_t) * (i + 1);
+ reloc[0].delta = sizeof(uint32_t) * x++;
+ reloc[0].read_domains = I915_GEM_DOMAIN_INSTRUCTION;
+ reloc[0].write_domain = 0; /* lies */
+
+ batch[i] = MI_STORE_DWORD_IMM | (gen < 6 ? 1 << 22 : 0);
+ if (gen >= 8) {
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ batch[++i] = (reloc[0].presumed_offset + reloc[0].delta) >> 32;
+ } else if (gen >= 4) {
+ batch[++i] = 0;
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ reloc[0].offset += sizeof(uint32_t);
+ } else {
+ batch[i]--;
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ }
+ batch[++i] = ~0u ^ x;
+
+ reloc[1].target_handle = obj[BATCH].handle; /* recurse */
+ reloc[1].presumed_offset = 0;
+ reloc[1].offset = sizeof(uint32_t) * (i + 2);
+ reloc[1].delta = 0;
+ reloc[1].read_domains = I915_GEM_DOMAIN_COMMAND;
+ reloc[1].write_domain = 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[1].delta = 1;
+ }
+ }
+ batch[++i] = MI_BATCH_BUFFER_END;
+ igt_assert(i < sizeof(batch)/sizeof(batch[0]));
+ gem_write(fd, obj[BATCH].handle, 0, batch, sizeof(batch));
+ gem_execbuf_wr(fd, &execbuf);
+
+ igt_assert(execbuf.rsvd2);
+ execbuf.rsvd2 >>= 32; /* out fence -> in fence */
+ obj[BATCH].relocation_count = 1;
+
+ /* Queue all secondaries */
+ for_each_physical_engine(fd, engine) {
+ if (engine == master)
+ continue;
+
+ execbuf.flags = engine | LOCAL_EXEC_FENCE_SUBMIT;
+ if (gen < 6)
+ execbuf.flags |= I915_EXEC_SECURE;
+
+ obj[BATCH].handle = gem_create(fd, 4096);
+ handle[x] = obj[BATCH].handle;
+
+ i = 0;
+ reloc[0].delta = sizeof(uint32_t) * x++;
+ batch[i] = MI_STORE_DWORD_IMM | (gen < 6 ? 1 << 22 : 0);
+ if (gen >= 8) {
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ batch[++i] = (reloc[0].presumed_offset + reloc[0].delta) >> 32;
+ } else if (gen >= 4) {
+ batch[++i] = 0;
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ } else {
+ batch[i]--;
+ batch[++i] = reloc[0].presumed_offset + reloc[0].delta;
+ }
+ batch[++i] = ~0u ^ x;
+ batch[++i] = MI_BATCH_BUFFER_END;
+ gem_write(fd, obj[BATCH].handle, 0, batch, sizeof(batch));
+ gem_execbuf(fd, &execbuf);
+ }
+ igt_assert(gem_bo_busy(fd, spin->handle));
+ close(execbuf.rsvd2);
+
+ /* No secondary should be executed since master is stalled. If there
+ * was no dependency chain at all, the secondaries would start
+ * immediately.
+ */
+ for (i = 0; i < x; i++) {
+ igt_assert_eq_u32(out[i], 0);
+ igt_assert(gem_bo_busy(fd, handle[i]));
+ }
+
+ /* Unblock the master */
+ igt_cork_unplug(&c);
+ gem_close(fd, plug);
+ igt_spin_batch_end(spin);
+
+ /* Wait for all secondaries to complete. If we used a regular fence
+ * then the secondaries would not start until the master was complete.
+ * In this case that can only happen with a GPU reset, and so we run
+ * under the hang detector and double check that the master is still
+ * running afterwards.
+ */
+ for (i = 1; i < x; i++) {
+ while (gem_bo_busy(fd, handle[i]))
+ sleep(0);
+
+ igt_assert_f(out[i], "Missing output from engine %d\n", i);
+ gem_close(fd, handle[i]);
+ }
+ munmap(out, 4096);
+ gem_close(fd, obj[SCRATCH].handle);
+
+ /* Master should still be spinning, but all output should be written */
+ igt_assert(gem_bo_busy(fd, handle[0]));
+ out = gem_mmap__wc(fd, handle[0], 0, 4096, PROT_WRITE);
+ out[0] = MI_BATCH_BUFFER_END;
+ munmap(out, 4096);
+ gem_close(fd, handle[0]);
+}
+
+static uint32_t batch_create(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ uint32_t handle;
+
+ handle = gem_create(fd, 4096);
+ gem_write(fd, handle, 0, &bbe, sizeof(bbe));
+
+ return handle;
+}
+
+static inline uint32_t lower_32_bits(uint64_t x)
+{
+ return x & 0xffffffff;
+}
+
+static inline uint32_t upper_32_bits(uint64_t x)
+{
+ return x >> 32;
+}
+
+static void test_keep_in_fence(int fd, unsigned int engine, unsigned int flags)
+{
+ struct sigaction sa = { .sa_handler = alarm_handler };
+ struct drm_i915_gem_exec_object2 obj = {
+ .handle = batch_create(fd),
+ };
+ struct drm_i915_gem_execbuffer2 execbuf = {
+ .buffers_ptr = to_user_pointer(&obj),
+ .buffer_count = 1,
+ .flags = engine | LOCAL_EXEC_FENCE_OUT,
+ };
+ unsigned long count, last;
+ struct itimerval itv;
+ igt_spin_t *spin;
+ int fence;
+
+ spin = igt_spin_batch_new(fd, .engine = engine);
+
+ gem_execbuf_wr(fd, &execbuf);
+ fence = upper_32_bits(execbuf.rsvd2);
+
+ sigaction(SIGALRM, &sa, NULL);
+ itv.it_interval.tv_sec = 0;
+ itv.it_interval.tv_usec = 1000;
+ itv.it_value.tv_sec = 0;
+ itv.it_value.tv_usec = 10000;
+ setitimer(ITIMER_REAL, &itv, NULL);
+
+ execbuf.flags |= LOCAL_EXEC_FENCE_IN;
+ execbuf.rsvd2 = fence;
+
+ last = -1;
+ count = 0;
+ do {
+ int err = __execbuf(fd, &execbuf);
+
+ igt_assert_eq(lower_32_bits(execbuf.rsvd2), fence);
+
+ if (err == 0) {
+ close(fence);
+
+ fence = upper_32_bits(execbuf.rsvd2);
+ execbuf.rsvd2 = fence;
+
+ count++;
+ continue;
+ }
+
+ igt_assert_eq(err, -EINTR);
+ igt_assert_eq(upper_32_bits(execbuf.rsvd2), 0);
+
+ if (last == count)
+ break;
+
+ last = count;
+ } while (1);
+
+ memset(&itv, 0, sizeof(itv));
+ setitimer(ITIMER_REAL, &itv, NULL);
+
+ gem_close(fd, obj.handle);
+ close(fence);
+
+ igt_spin_batch_free(fd, spin);
+ gem_quiescent_gpu(fd);
+}
+
+#define EXPIRED 0x10000
+static void test_long_history(int fd, long ring_size, unsigned flags)
+{
+ const uint32_t sz = 1 << 20;
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj[2];
+ struct drm_i915_gem_execbuffer2 execbuf;
+ unsigned int engines[16], engine;
+ unsigned int nengine, n, s;
+ unsigned long limit;
+ int all_fences;
+ IGT_CORK_HANDLE(c);
+
+ limit = -1;
+ if (!gem_uses_full_ppgtt(fd))
+ limit = ring_size / 3;
+
+ nengine = 0;
+ for_each_physical_engine(fd, engine)
+ engines[nengine++] = engine;
+ igt_require(nengine);
+
+ gem_quiescent_gpu(fd);
+
+ memset(obj, 0, sizeof(obj));
+ obj[1].handle = gem_create(fd, sz);
+ gem_write(fd, obj[1].handle, sz - sizeof(bbe), &bbe, sizeof(bbe));
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj[1]);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_OUT;
+
+ gem_execbuf_wr(fd, &execbuf);
+ all_fences = execbuf.rsvd2 >> 32;
+
+ execbuf.buffers_ptr = to_user_pointer(obj);
+ execbuf.buffer_count = 2;
+
+ obj[0].handle = igt_cork_plug(&c, fd);
+
+ igt_until_timeout(5) {
+ execbuf.rsvd1 = gem_context_create(fd);
+
+ for (n = 0; n < nengine; n++) {
+ struct sync_merge_data merge;
+
+ execbuf.flags = engines[n] | LOCAL_EXEC_FENCE_OUT;
+ if (__gem_execbuf_wr(fd, &execbuf))
+ continue;
+
+ memset(&merge, 0, sizeof(merge));
+ merge.fd2 = execbuf.rsvd2 >> 32;
+ strcpy(merge.name, "igt");
+
+ do_ioctl(all_fences, SYNC_IOC_MERGE, &merge);
+
+ close(all_fences);
+ close(merge.fd2);
+
+ all_fences = merge.fence;
+ }
+
+ gem_context_destroy(fd, execbuf.rsvd1);
+ if (!--limit)
+ break;
+ }
+ igt_cork_unplug(&c);
+
+ igt_info("History depth = %d\n", sync_fence_count(all_fences));
+
+ if (flags & EXPIRED)
+ gem_sync(fd, obj[1].handle);
+
+ execbuf.buffers_ptr = to_user_pointer(&obj[1]);
+ execbuf.buffer_count = 1;
+ execbuf.rsvd2 = all_fences;
+ execbuf.rsvd1 = 0;
+
+ for (s = 0; s < ring_size; s++) {
+ for (n = 0; n < nengine; n++) {
+ execbuf.flags = engines[n] | LOCAL_EXEC_FENCE_IN;
+ if (__gem_execbuf_wr(fd, &execbuf))
+ continue;
+ }
+ }
+
+ close(all_fences);
+
+ gem_sync(fd, obj[1].handle);
+ gem_close(fd, obj[1].handle);
+ gem_close(fd, obj[0].handle);
+}
+
+static void test_fence_flip(int i915)
+{
+ igt_skip_on_f(1, "no fence-in for atomic flips\n");
+}
+
+static bool has_submit_fence(int fd)
+{
+ struct drm_i915_getparam gp;
+ int value = 0;
+
+ memset(&gp, 0, sizeof(gp));
+ gp.param = 0xdeadbeef ^ 51; /* I915_PARAM_HAS_EXEC_SUBMIT_FENCE */
+ gp.value = &value;
+
+ ioctl(fd, DRM_IOCTL_I915_GETPARAM, &gp, sizeof(gp));
+ errno = 0;
+
+ return value;
+}
+
+static bool has_syncobj(int fd)
+{
+ struct drm_get_cap cap = { .capability = 0x13 };
+ ioctl(fd, DRM_IOCTL_GET_CAP, &cap);
+ return cap.value;
+}
+
+static bool exec_has_fence_array(int fd)
+{
+ struct drm_i915_getparam gp;
+ int value = 0;
+
+ memset(&gp, 0, sizeof(gp));
+ gp.param = 49; /* I915_PARAM_HAS_EXEC_FENCE_ARRAY */
+ gp.value = &value;
+
+ ioctl(fd, DRM_IOCTL_I915_GETPARAM, &gp, sizeof(gp));
+ errno = 0;
+
+ return value;
+}
+
+static void test_invalid_fence_array(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct drm_i915_gem_exec_object2 obj;
+ struct local_gem_exec_fence fence;
+ void *ptr;
+
+ /* create an otherwise valid execbuf */
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ gem_execbuf(fd, &execbuf);
+
+ execbuf.flags |= LOCAL_EXEC_FENCE_ARRAY;
+ gem_execbuf(fd, &execbuf);
+
+ /* Now add a few invalid fence-array pointers */
+ if (sizeof(execbuf.num_cliprects) == sizeof(size_t)) {
+ execbuf.num_cliprects = -1;
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -EINVAL);
+ }
+
+ execbuf.num_cliprects = 1;
+ execbuf.cliprects_ptr = -1;
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -EFAULT);
+
+ memset(&fence, 0, sizeof(fence));
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -ENOENT);
+
+ ptr = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
+ igt_assert(ptr != MAP_FAILED);
+ execbuf.cliprects_ptr = to_user_pointer(ptr);
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -ENOENT);
+
+ do_or_die(mprotect(ptr, 4096, PROT_READ));
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -ENOENT);
+
+ do_or_die(mprotect(ptr, 4096, PROT_NONE));
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -EFAULT);
+
+ munmap(ptr, 4096);
+}
+
+static uint32_t __syncobj_create(int fd)
+{
+ struct local_syncobj_create {
+ uint32_t handle, flags;
+ } arg;
+#define LOCAL_IOCTL_SYNCOBJ_CREATE DRM_IOWR(0xBF, struct local_syncobj_create)
+
+ memset(&arg, 0, sizeof(arg));
+ igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_CREATE, &arg);
+
+ return arg.handle;
+}
+
+static uint32_t syncobj_create(int fd)
+{
+ uint32_t ret;
+
+ igt_assert_neq((ret = __syncobj_create(fd)), 0);
+
+ return ret;
+}
+
+static int __syncobj_destroy(int fd, uint32_t handle)
+{
+ struct local_syncobj_destroy {
+ uint32_t handle, flags;
+ } arg;
+#define LOCAL_IOCTL_SYNCOBJ_DESTROY DRM_IOWR(0xC0, struct local_syncobj_destroy)
+ int err = 0;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.handle = handle;
+ if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_DESTROY, &arg))
+ err = -errno;
+
+ errno = 0;
+ return err;
+}
+
+static void syncobj_destroy(int fd, uint32_t handle)
+{
+ igt_assert_eq(__syncobj_destroy(fd, handle), 0);
+}
+
+static int __syncobj_to_sync_file(int fd, uint32_t handle)
+{
+ struct local_syncobj_handle {
+ uint32_t handle;
+ uint32_t flags;
+ int32_t fd;
+ uint32_t pad;
+ } arg;
+#define LOCAL_IOCTL_SYNCOBJ_HANDLE_TO_FD DRM_IOWR(0xC1, struct local_syncobj_handle)
+
+ memset(&arg, 0, sizeof(arg));
+ arg.handle = handle;
+ arg.flags = 1 << 0; /* EXPORT_SYNC_FILE */
+ if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_HANDLE_TO_FD, &arg))
+ arg.fd = -errno;
+
+ errno = 0;
+ return arg.fd;
+}
+
+static int syncobj_to_sync_file(int fd, uint32_t handle)
+{
+ int ret;
+
+ igt_assert_lte(0, (ret = __syncobj_to_sync_file(fd, handle)));
+
+ return ret;
+}
+
+static int __syncobj_from_sync_file(int fd, uint32_t handle, int sf)
+{
+ struct local_syncobj_handle {
+ uint32_t handle;
+ uint32_t flags;
+ int32_t fd;
+ uint32_t pad;
+ } arg;
+#define LOCAL_IOCTL_SYNCOBJ_FD_TO_HANDLE DRM_IOWR(0xC2, struct local_syncobj_handle)
+ int err = 0;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.handle = handle;
+ arg.fd = sf;
+ arg.flags = 1 << 0; /* IMPORT_SYNC_FILE */
+ if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_FD_TO_HANDLE, &arg))
+ err = -errno;
+
+ errno = 0;
+ return err;
+}
+
+static void syncobj_from_sync_file(int fd, uint32_t handle, int sf)
+{
+ igt_assert_eq(__syncobj_from_sync_file(fd, handle, sf), 0);
+}
+
+static int __syncobj_export(int fd, uint32_t handle, int *syncobj)
+{
+ struct local_syncobj_handle {
+ uint32_t handle;
+ uint32_t flags;
+ int32_t fd;
+ uint32_t pad;
+ } arg;
+ int err;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.handle = handle;
+
+ err = 0;
+ if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_HANDLE_TO_FD, &arg))
+ err = -errno;
+
+ errno = 0;
+ *syncobj = arg.fd;
+ return err;
+}
+
+static int syncobj_export(int fd, uint32_t handle)
+{
+ int syncobj;
+
+ igt_assert_eq(__syncobj_export(fd, handle, &syncobj), 0);
+
+ return syncobj;
+}
+
+static int __syncobj_import(int fd, int syncobj, uint32_t *handle)
+{
+ struct local_syncobj_handle {
+ uint32_t handle;
+ uint32_t flags;
+ int32_t fd;
+ uint32_t pad;
+ } arg;
+#define LOCAL_IOCTL_SYNCOBJ_FD_TO_HANDLE DRM_IOWR(0xC2, struct local_syncobj_handle)
+ int err;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.fd = syncobj;
+
+ err = 0;
+ if (igt_ioctl(fd, LOCAL_IOCTL_SYNCOBJ_FD_TO_HANDLE, &arg))
+ err = -errno;
+
+ errno = 0;
+ *handle = arg.handle;
+ return err;
+}
+
+static uint32_t syncobj_import(int fd, int syncobj)
+{
+ uint32_t handle;
+
+ igt_assert_eq(__syncobj_import(fd, syncobj, &handle), 0);
+
+
+ return handle;
+}
+
+static bool syncobj_busy(int fd, uint32_t handle)
+{
+ bool result;
+ int sf;
+
+ sf = syncobj_to_sync_file(fd, handle);
+ result = poll(&(struct pollfd){sf, POLLIN}, 1, 0) == 0;
+ close(sf);
+
+ return result;
+}
+
+static void test_syncobj_unused_fence(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+ igt_spin_t *spin = igt_spin_batch_new(fd);
+
+ /* sanity check our syncobj_to_sync_file interface */
+ igt_assert_eq(__syncobj_to_sync_file(fd, 0), -ENOENT);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ gem_execbuf(fd, &execbuf);
+
+ /* no flags, the fence isn't created */
+ igt_assert_eq(__syncobj_to_sync_file(fd, fence.handle), -EINVAL);
+ igt_assert(gem_bo_busy(fd, obj.handle));
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, fence.handle);
+
+ igt_spin_batch_free(fd, spin);
+}
+
+static void test_syncobj_invalid_wait(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ /* waiting before the fence is set is invalid */
+ fence.flags = LOCAL_EXEC_FENCE_WAIT;
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -EINVAL);
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, fence.handle);
+}
+
+static void test_syncobj_invalid_flags(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ /* set all flags to hit an invalid one */
+ fence.flags = ~0;
+ igt_assert_eq(__gem_execbuf(fd, &execbuf), -EINVAL);
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, fence.handle);
+}
+
+static void test_syncobj_signal(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+ igt_spin_t *spin = igt_spin_batch_new(fd);
+
+ /* Check that the syncobj is signaled only when our request/fence is */
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ fence.flags = LOCAL_EXEC_FENCE_SIGNAL;
+ gem_execbuf(fd, &execbuf);
+
+ igt_assert(gem_bo_busy(fd, obj.handle));
+ igt_assert(syncobj_busy(fd, fence.handle));
+
+ igt_spin_batch_free(fd, spin);
+
+ gem_sync(fd, obj.handle);
+ igt_assert(!gem_bo_busy(fd, obj.handle));
+ igt_assert(!syncobj_busy(fd, fence.handle));
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, fence.handle);
+}
+
+static void test_syncobj_wait(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+ igt_spin_t *spin;
+ unsigned engine;
+ unsigned handle[16];
+ int n;
+
+ /* Check that we can use the syncobj to asynchronous wait prior to
+ * execution.
+ */
+
+ gem_quiescent_gpu(fd);
+
+ spin = igt_spin_batch_new(fd);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ /* Queue a signaler from the blocked engine */
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+ fence.flags = LOCAL_EXEC_FENCE_SIGNAL;
+ gem_execbuf(fd, &execbuf);
+ igt_assert(gem_bo_busy(fd, spin->handle));
+
+ gem_close(fd, obj.handle);
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ n = 0;
+ for_each_engine(fd, engine) {
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ /* No inter-engine synchronisation, will complete */
+ if (engine == I915_EXEC_BLT) {
+ execbuf.flags = engine;
+ execbuf.cliprects_ptr = 0;
+ execbuf.num_cliprects = 0;
+ gem_execbuf(fd, &execbuf);
+ gem_sync(fd, obj.handle);
+ igt_assert(gem_bo_busy(fd, spin->handle));
+ }
+ igt_assert(gem_bo_busy(fd, spin->handle));
+
+ /* Now wait upon the blocked engine */
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY | engine;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+ fence.flags = LOCAL_EXEC_FENCE_WAIT;
+ gem_execbuf(fd, &execbuf);
+
+ igt_assert(gem_bo_busy(fd, obj.handle));
+ handle[n++] = obj.handle;
+ }
+ syncobj_destroy(fd, fence.handle);
+
+ for (int i = 0; i < n; i++)
+ igt_assert(gem_bo_busy(fd, handle[i]));
+
+ igt_spin_batch_free(fd, spin);
+
+ for (int i = 0; i < n; i++) {
+ gem_sync(fd, handle[i]);
+ gem_close(fd, handle[i]);
+ }
+}
+
+static void test_syncobj_export(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence fence = {
+ .handle = syncobj_create(fd),
+ };
+ int export[2];
+ igt_spin_t *spin = igt_spin_batch_new(fd);
+
+ /* Check that if we export the syncobj prior to use it picks up
+ * the later fence. This allows a syncobj to establish a channel
+ * between clients that may be updated to a later fence by either
+ * end.
+ */
+ for (int n = 0; n < ARRAY_SIZE(export); n++)
+ export[n] = syncobj_export(fd, fence.handle);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ fence.flags = LOCAL_EXEC_FENCE_SIGNAL;
+ gem_execbuf(fd, &execbuf);
+
+ igt_assert(syncobj_busy(fd, fence.handle));
+ igt_assert(gem_bo_busy(fd, obj.handle));
+
+ for (int n = 0; n < ARRAY_SIZE(export); n++) {
+ uint32_t import = syncobj_import(fd, export[n]);
+ igt_assert(syncobj_busy(fd, import));
+ syncobj_destroy(fd, import);
+ }
+
+ igt_spin_batch_free(fd, spin);
+
+ gem_sync(fd, obj.handle);
+ igt_assert(!gem_bo_busy(fd, obj.handle));
+ igt_assert(!syncobj_busy(fd, fence.handle));
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, fence.handle);
+
+ for (int n = 0; n < ARRAY_SIZE(export); n++) {
+ uint32_t import = syncobj_import(fd, export[n]);
+ igt_assert(!syncobj_busy(fd, import));
+ syncobj_destroy(fd, import);
+ close(export[n]);
+ }
+}
+
+static void test_syncobj_repeat(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ const unsigned nfences = 4096;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ struct local_gem_exec_fence *fence;
+ int export;
+ igt_spin_t *spin = igt_spin_batch_new(fd);
+
+ /* Check that we can wait on the same fence multiple times */
+ fence = calloc(nfences, sizeof(*fence));
+ fence->handle = syncobj_create(fd);
+ export = syncobj_export(fd, fence->handle);
+ for (int i = 1; i < nfences; i++)
+ fence[i].handle = syncobj_import(fd, export);
+ close(export);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(fence);
+ execbuf.num_cliprects = nfences;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ for (int i = 0; i < nfences; i++)
+ fence[i].flags = LOCAL_EXEC_FENCE_SIGNAL;
+
+ gem_execbuf(fd, &execbuf);
+
+ for (int i = 0; i < nfences; i++) {
+ igt_assert(syncobj_busy(fd, fence[i].handle));
+ fence[i].flags |= LOCAL_EXEC_FENCE_WAIT;
+ }
+ igt_assert(gem_bo_busy(fd, obj.handle));
+
+ gem_execbuf(fd, &execbuf);
+
+ for (int i = 0; i < nfences; i++)
+ igt_assert(syncobj_busy(fd, fence[i].handle));
+ igt_assert(gem_bo_busy(fd, obj.handle));
+
+ igt_spin_batch_free(fd, spin);
+
+ gem_sync(fd, obj.handle);
+ gem_close(fd, obj.handle);
+
+ for (int i = 0; i < nfences; i++) {
+ igt_assert(!syncobj_busy(fd, fence[i].handle));
+ syncobj_destroy(fd, fence[i].handle);
+ }
+ free(fence);
+}
+
+static void test_syncobj_import(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ igt_spin_t *spin = igt_spin_batch_new(fd);
+ uint32_t sync = syncobj_create(fd);
+ int fence;
+
+ /* Check that we can create a syncobj from an explicit fence (which
+ * uses sync_file) and that it acts just like a regular fence.
+ */
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_OUT;
+ execbuf.rsvd2 = -1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ gem_execbuf_wr(fd, &execbuf);
+
+ fence = execbuf.rsvd2 >> 32;
+ igt_assert(fence_busy(fence));
+ syncobj_from_sync_file(fd, sync, fence);
+ close(fence);
+
+ igt_assert(gem_bo_busy(fd, obj.handle));
+ igt_assert(syncobj_busy(fd, sync));
+
+ igt_spin_batch_free(fd, spin);
+
+ gem_sync(fd, obj.handle);
+ igt_assert(!gem_bo_busy(fd, obj.handle));
+ igt_assert(!syncobj_busy(fd, sync));
+
+ gem_close(fd, obj.handle);
+ syncobj_destroy(fd, sync);
+}
+
+static void test_syncobj_channel(int fd)
+{
+ const uint32_t bbe = MI_BATCH_BUFFER_END;
+ struct drm_i915_gem_exec_object2 obj;
+ struct drm_i915_gem_execbuffer2 execbuf;
+ unsigned int *control;
+ int syncobj[3];
+
+ /* Create a pair of channels (like a pipe) between two clients
+ * and try to create races on the syncobj.
+ */
+
+ control = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
+ igt_assert(control != MAP_FAILED);
+
+ memset(&execbuf, 0, sizeof(execbuf));
+ execbuf.buffers_ptr = to_user_pointer(&obj);
+ execbuf.buffer_count = 1;
+ execbuf.flags = LOCAL_EXEC_FENCE_OUT;
+ execbuf.rsvd2 = -1;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.handle = gem_create(fd, 4096);
+ gem_write(fd, obj.handle, 0, &bbe, sizeof(bbe));
+
+ for (int i = 0; i < ARRAY_SIZE(syncobj); i++) {
+ struct local_gem_exec_fence fence;
+
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ /* Create a primed fence */
+ fence.handle = syncobj_create(fd);
+ fence.flags = LOCAL_EXEC_FENCE_SIGNAL;
+
+ gem_execbuf(fd, &execbuf);
+
+ syncobj[i] = fence.handle;
+ }
+
+ /* Two processes in ping-pong unison (pipe), one out of sync */
+ igt_fork(child, 1) {
+ struct local_gem_exec_fence fence[3];
+ unsigned long count;
+
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(fence);
+ execbuf.num_cliprects = 3;
+
+ fence[0].handle = syncobj[0];
+ fence[0].flags = LOCAL_EXEC_FENCE_SIGNAL;
+
+ fence[1].handle = syncobj[1];
+ fence[1].flags = LOCAL_EXEC_FENCE_WAIT;
+
+ fence[2].handle = syncobj[2];
+ fence[2].flags = LOCAL_EXEC_FENCE_WAIT;
+
+ count = 0;
+ while (!*(volatile unsigned *)control) {
+ gem_execbuf(fd, &execbuf);
+ count++;
+ }
+
+ control[1] = count;
+ }
+ igt_fork(child, 1) {
+ struct local_gem_exec_fence fence[3];
+ unsigned long count;
+
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(fence);
+ execbuf.num_cliprects = 3;
+
+ fence[0].handle = syncobj[0];
+ fence[0].flags = LOCAL_EXEC_FENCE_WAIT;
+
+ fence[1].handle = syncobj[1];
+ fence[1].flags = LOCAL_EXEC_FENCE_SIGNAL;
+
+ fence[2].handle = syncobj[2];
+ fence[2].flags = LOCAL_EXEC_FENCE_WAIT;
+
+ count = 0;
+ while (!*(volatile unsigned *)control) {
+ gem_execbuf(fd, &execbuf);
+ count++;
+ }
+ control[2] = count;
+ }
+ igt_fork(child, 1) {
+ struct local_gem_exec_fence fence;
+ unsigned long count;
+
+ execbuf.flags = LOCAL_EXEC_FENCE_ARRAY;
+ execbuf.cliprects_ptr = to_user_pointer(&fence);
+ execbuf.num_cliprects = 1;
+
+ fence.handle = syncobj[2];
+ fence.flags = LOCAL_EXEC_FENCE_SIGNAL;
+
+ count = 0;
+ while (!*(volatile unsigned *)control) {
+ gem_execbuf(fd, &execbuf);
+ count++;
+ }
+ control[3] = count;
+ }
+
+ sleep(1);
+ *control = 1;
+ igt_waitchildren();
+
+ igt_info("Pipe=[%u, %u], gooseberry=%u\n",
+ control[1], control[2], control[3]);
+ munmap(control, 4096);
+
+ gem_sync(fd, obj.handle);
+ gem_close(fd, obj.handle);
+
+ for (int i = 0; i < ARRAY_SIZE(syncobj); i++)
+ syncobj_destroy(fd, syncobj[i]);
+}
+
+igt_main
+{
+ const struct intel_execution_engine *e;
+ int i915 = -1;
+
+ igt_fixture {
+ i915 = drm_open_driver_master(DRIVER_INTEL);
+ igt_require_gem(i915);
+ igt_require(gem_has_exec_fence(i915));
+ gem_require_mmap_wc(i915);
+
+ gem_submission_print_method(i915);
+ }
+
+ igt_subtest_group {
+ igt_fixture {
+ igt_fork_hang_detector(i915);
+ }
+
+ igt_subtest("basic-busy-all")
+ test_fence_busy_all(i915, 0);
+ igt_subtest("basic-wait-all")
+ test_fence_busy_all(i915, WAIT);
+
+ igt_fixture {
+ igt_stop_hang_detector();
+ }
+
+ igt_subtest("busy-hang-all")
+ test_fence_busy_all(i915, HANG);
+ igt_subtest("wait-hang-all")
+ test_fence_busy_all(i915, WAIT | HANG);
+ }
+
+ for (e = intel_execution_engines; e->name; e++) {
+ igt_subtest_group {
+ igt_fixture {
+ igt_require(gem_has_ring(i915, e->exec_id | e->flags));
+ igt_require(gem_can_store_dword(i915, e->exec_id | e->flags));
+ }
+
+ igt_subtest_group {
+ igt_fixture {
+ igt_fork_hang_detector(i915);
+ }
+
+ igt_subtest_f("%sbusy-%s",
+ e->exec_id == 0 ? "basic-" : "",
+ e->name)
+ test_fence_busy(i915, e->exec_id | e->flags, 0);
+ igt_subtest_f("%swait-%s",
+ e->exec_id == 0 ? "basic-" : "",
+ e->name)
+ test_fence_busy(i915, e->exec_id | e->flags, WAIT);
+ igt_subtest_f("%sawait-%s",
+ e->exec_id == 0 ? "basic-" : "",
+ e->name)
+ test_fence_await(i915, e->exec_id | e->flags, 0);
+ igt_subtest_f("nb-await-%s", e->name)
+ test_fence_await(i915, e->exec_id | e->flags, NONBLOCK);
+
+ igt_subtest_f("keep-in-fence-%s", e->name)
+ test_keep_in_fence(i915, e->exec_id | e->flags, 0);
+
+ if (e->exec_id &&
+ !(e->exec_id == I915_EXEC_BSD && !e->flags)) {
+ igt_subtest_f("parallel-%s", e->name) {
+ igt_require(has_submit_fence(i915));
+ igt_until_timeout(2)
+ test_parallel(i915, e->exec_id | e->flags);
+ }
+ }
+
+ igt_fixture {
+ igt_stop_hang_detector();
+ }
+ }
+
+ igt_subtest_group {
+ igt_hang_t hang;
+
+ igt_skip_on_simulation();
+
+ igt_fixture {
+ hang = igt_allow_hang(i915, 0, 0);
+ }
+
+ igt_subtest_f("busy-hang-%s", e->name)
+ test_fence_busy(i915, e->exec_id | e->flags, HANG);
+ igt_subtest_f("wait-hang-%s", e->name)
+ test_fence_busy(i915, e->exec_id | e->flags, HANG | WAIT);
+ igt_subtest_f("await-hang-%s", e->name)
+ test_fence_await(i915, e->exec_id | e->flags, HANG);
+ igt_subtest_f("nb-await-hang-%s", e->name)
+ test_fence_await(i915, e->exec_id | e->flags, NONBLOCK | HANG);
+ igt_fixture {
+ igt_disallow_hang(i915, hang);
+ }
+ }
+ }
+ }
+
+ igt_subtest_group {
+ long ring_size = 0;
+
+ igt_fixture {
+ ring_size = gem_measure_ring_inflight(i915, ALL_ENGINES, 0) - 1;
+ igt_info("Ring size: %ld batches\n", ring_size);
+ igt_require(ring_size);
+
+ gem_require_contexts(i915);
+ }
+
+ igt_subtest("long-history")
+ test_long_history(i915, ring_size, 0);
+
+ igt_subtest("expired-history")
+ test_long_history(i915, ring_size, EXPIRED);
+ }
+
+ igt_subtest("flip") {
+ gem_quiescent_gpu(i915);
+ test_fence_flip(i915);
+ }
+
+ igt_subtest_group { /* syncobj */
+ igt_fixture {
+ igt_require(exec_has_fence_array(i915));
+ igt_assert(has_syncobj(i915));
+ igt_fork_hang_detector(i915);
+ }
+
+ igt_subtest("invalid-fence-array")
+ test_invalid_fence_array(i915);
+
+ igt_subtest("syncobj-unused-fence")
+ test_syncobj_unused_fence(i915);
+
+ igt_subtest("syncobj-invalid-wait")
+ test_syncobj_invalid_wait(i915);
+
+ igt_subtest("syncobj-invalid-flags")
+ test_syncobj_invalid_flags(i915);
+
+ igt_subtest("syncobj-signal")
+ test_syncobj_signal(i915);
+
+ igt_subtest("syncobj-wait")
+ test_syncobj_wait(i915);
+
+ igt_subtest("syncobj-export")
+ test_syncobj_export(i915);
+
+ igt_subtest("syncobj-repeat")
+ test_syncobj_repeat(i915);
+
+ igt_subtest("syncobj-import")
+ test_syncobj_import(i915);
+
+ igt_subtest("syncobj-channel")
+ test_syncobj_channel(i915);
+
+ igt_fixture {
+ igt_stop_hang_detector();
+ }
+ }
+
+ igt_fixture {
+ close(i915);
+ }
+}