We store every client name, pid and runtime under sysfs. Better check
that matches with the actual client.

Signed-off-by: Chris Wilson <ch...@chris-wilson.co.uk>
---
 lib/igt_sysfs.c            |  36 +++
 lib/igt_sysfs.h            |   3 +
 tests/Makefile.sources     |   3 +
 tests/i915/sysfs_clients.c | 450 +++++++++++++++++++++++++++++++++++++
 tests/meson.build          |   1 +
 5 files changed, 493 insertions(+)
 create mode 100644 tests/i915/sysfs_clients.c

diff --git a/lib/igt_sysfs.c b/lib/igt_sysfs.c
index 6aafe5349..e734143ba 100644
--- a/lib/igt_sysfs.c
+++ b/lib/igt_sysfs.c
@@ -378,6 +378,42 @@ uint32_t igt_sysfs_get_u32(int dir, const char *attr)
        return result;
 }
 
+/**
+ * igt_sysfs_get_u64:
+ * @dir: directory for the device from igt_sysfs_open()
+ * @attr: name of the sysfs node to open
+ *
+ * Convenience wrapper to read a unsigned 64bit integer from a sysfs file.
+ *
+ * Returns:
+ * The value read.
+ */
+uint64_t igt_sysfs_get_u64(int dir, const char *attr)
+{
+       uint64_t result;
+
+       if (igt_sysfs_scanf(dir, attr, "%"PRIu64, &result) != 1)
+               return 0;
+
+       return result;
+}
+
+/**
+ * igt_sysfs_set_u64:
+ * @dir: directory for the device from igt_sysfs_open()
+ * @attr: name of the sysfs node to open
+ * @value: value to set
+ *
+ * Convenience wrapper to write a unsigned 64bit integer to a sysfs file.
+ *
+ * Returns:
+ * True if successfully written
+ */
+bool igt_sysfs_set_u64(int dir, const char *attr, uint64_t value)
+{
+       return igt_sysfs_printf(dir, attr, "%"PRIu64, value) > 0;
+}
+
 /**
  * igt_sysfs_set_u32:
  * @dir: directory for the device from igt_sysfs_open()
diff --git a/lib/igt_sysfs.h b/lib/igt_sysfs.h
index 64935a5ca..56741a0a3 100644
--- a/lib/igt_sysfs.h
+++ b/lib/igt_sysfs.h
@@ -47,6 +47,9 @@ int igt_sysfs_printf(int dir, const char *attr, const char 
*fmt, ...)
 uint32_t igt_sysfs_get_u32(int dir, const char *attr);
 bool igt_sysfs_set_u32(int dir, const char *attr, uint32_t value);
 
+uint64_t igt_sysfs_get_u64(int dir, const char *attr);
+bool igt_sysfs_set_u64(int dir, const char *attr, uint64_t value);
+
 bool igt_sysfs_get_boolean(int dir, const char *attr);
 bool igt_sysfs_set_boolean(int dir, const char *attr, bool value);
 
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index 1c227e750..3f663fe7e 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -114,6 +114,9 @@ TESTS_progs = \
 TESTS_progs += api_intel_bb
 api_intel_bb_SOURCES = i915/api_intel_bb.c
 
+TESTS_progs += sysfs_clients
+sysfs_clients_SOURCES = i915/sysfs_clients.c
+
 TESTS_progs += sysfs_defaults
 sysfs_defaults_SOURCES = i915/sysfs_defaults.c
 
diff --git a/tests/i915/sysfs_clients.c b/tests/i915/sysfs_clients.c
new file mode 100644
index 000000000..a77adec6d
--- /dev/null
+++ b/tests/i915/sysfs_clients.c
@@ -0,0 +1,450 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2021 Intel Corporation
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "drmtest.h"
+#include "i915/gem.h"
+#include "i915/gem_context.h"
+#include "i915/gem_engine_topology.h"
+#include "igt_aux.h"
+#include "igt_dummyload.h"
+#include "igt_sysfs.h"
+#include "ioctl_wrappers.h"
+
+static void pidname(int i915, int clients)
+{
+       struct dirent *de;
+       int sv[2], rv[2];
+       char buf[280];
+       int me = -1;
+       long count;
+       pid_t pid;
+       DIR *dir;
+       int len;
+
+       dir = fdopendir(dup(clients));
+       igt_assert(dir);
+       rewinddir(dir);
+
+       count = 0;
+       while ((de = readdir(dir))) {
+               if (!isdigit(de->d_name[0]))
+                       continue;
+
+               snprintf(buf, sizeof(buf), "%s/name", de->d_name);
+               len = igt_sysfs_read(clients, buf, buf, sizeof(buf));
+               igt_assert_f(len > 0, "failed to open '%s/name'\n", de->d_name);
+               buf[len - 1] = '\0';
+               igt_debug("%s: %s\n", de->d_name, buf);
+
+               /* Ignore closed clients created by drm_driver_open() */
+               if (*buf == '<')
+                       continue;
+
+               close(me);
+               me = openat(clients, de->d_name, O_DIRECTORY | O_RDONLY);
+               count++;
+       }
+       closedir(dir);
+
+       /* We expect there to be only the single client (us) running */
+       igt_assert_eq(count, 1);
+       igt_assert(me >= 0);
+
+       len = igt_sysfs_read(me, "name", buf, sizeof(buf));
+       igt_assert(len > 0);
+       buf[len - 1] = '\0';
+
+       igt_info("My name: %s\n", buf);
+       igt_assert(strcmp(buf, igt_test_name()) == 0);
+
+       igt_assert(pipe(sv) == 0);
+       igt_assert(pipe(rv) == 0);
+
+       /* If give our fd to someone else, they take over ownership of client */
+       igt_fork(child, 1) {
+               read(sv[0], &pid, sizeof(pid));
+
+               gem_context_destroy(i915, gem_context_create(i915));
+
+               pid = getpid();
+               write(rv[1], &pid, sizeof(pid));
+       }
+       close(sv[0]);
+       close(rv[1]);
+
+       /* Child exists, but not yet running, we still own the client */
+       len = igt_sysfs_read(me, "pid", buf, sizeof(buf));
+       igt_assert(len > 0);
+       buf[len - 1] = '\0';
+
+       pid = getpid();
+       igt_info("My pid: %s\n", buf);
+       igt_assert_eq(atoi(buf), pid);
+
+       /* Release and wait for the child */
+       igt_assert_eq(write(sv[1], &pid, sizeof(pid)), sizeof(pid));
+       igt_assert_eq(read(rv[0], &pid, sizeof(pid)), sizeof(pid));
+
+       /* Now child owns the client and pid should be updated to match */
+       len = igt_sysfs_read(me, "pid", buf, sizeof(buf));
+       igt_assert(len > 0);
+       buf[len - 1] = '\0';
+
+       igt_info("New pid: %s\n", buf);
+       igt_assert_eq(atoi(buf), pid);
+       igt_waitchildren();
+
+       /* Child has definitely gone, but the client should remain */
+       len = igt_sysfs_read(me, "pid", buf, sizeof(buf));
+       igt_assert(len > 0);
+       buf[len - 1] = '\0';
+
+       igt_info("Old pid: %s\n", buf);
+       igt_assert_eq(atoi(buf), pid);
+
+       close(sv[1]);
+       close(rv[0]);
+       close(me);
+}
+
+static long count_clients(int clients)
+{
+       struct dirent *de;
+       long count = 0;
+       char buf[280];
+       DIR *dir;
+
+       dir = fdopendir(dup(clients));
+       igt_assert(dir);
+       rewinddir(dir);
+
+       while ((de = readdir(dir))) {
+               int len;
+
+               if (!isdigit(de->d_name[0]))
+                       continue;
+
+               snprintf(buf, sizeof(buf), "%s/name", de->d_name);
+               len = igt_sysfs_read(clients, buf, buf, sizeof(buf));
+               if (len < 0)
+                       continue;
+
+               count += *buf != '<';
+       }
+       closedir(dir);
+
+       return count;
+}
+
+static void create(int i915, int clients)
+{
+       int fd[16];
+
+       /* Each new open("/dev/dri/cardN") is a new client */
+       igt_assert_eq(count_clients(clients), 1);
+       for (int i = 0; i < ARRAY_SIZE(fd); i++) {
+               fd[i] = gem_reopen_driver(i915);
+               igt_assert_eq(count_clients(clients), i + 2);
+       }
+
+       for (int i = 0; i < ARRAY_SIZE(fd); i++)
+               close(fd[i]);
+
+       /* Cleanup delayed behind rcu */
+       igt_until_timeout(30) {
+               usleep(0);
+               if (count_clients(clients) == 1)
+                       break;
+               usleep(10000);
+       }
+       igt_assert_eq(count_clients(clients), 1);
+}
+
+static int find_me(int clients, pid_t pid)
+{
+       struct dirent *de;
+       char buf[280];
+       int me = -1;
+       DIR *dir;
+
+       dir = fdopendir(dup(clients));
+       igt_assert(dir);
+       rewinddir(dir);
+
+       while ((de = readdir(dir))) {
+               int ret;
+
+               if (!isdigit(de->d_name[0]))
+                       continue;
+
+               snprintf(buf, sizeof(buf), "%s/pid", de->d_name);
+               ret = igt_sysfs_read(clients, buf, buf, sizeof(buf));
+               igt_assert_f(ret > 0, "failed to open '%s/pid'\n", de->d_name);
+               if (atoi(buf) != pid)
+                       continue;
+
+               me = openat(clients, de->d_name, O_DIRECTORY | O_RDONLY);
+               break;
+       }
+
+       closedir(dir);
+       return me;
+}
+
+#define MAX_CLASS 64
+static int read_runtime(int client, uint64_t *runtime)
+{
+       int fd = openat(client, "busy", O_DIRECTORY | O_RDONLY);
+       DIR *dir = fdopendir(fd);
+       struct dirent *de;
+       int count = 0;
+
+       memset(runtime, 0, sizeof(*runtime) * MAX_CLASS);
+       while ((de = readdir(dir))) {
+               int class;
+
+               if (!isdigit(de->d_name[0]))
+                       continue;
+
+               class = atoi(de->d_name);
+               igt_assert(class < MAX_CLASS);
+               runtime[class] = igt_sysfs_get_u64(fd, de->d_name);
+
+               count += runtime[class] != 0;
+       }
+       closedir(dir);
+
+       return count;
+}
+
+static uint64_t measured_usleep(unsigned int usec)
+{
+       struct timespec tv;
+       unsigned int slept;
+
+       slept = igt_nsec_elapsed(memset(&tv, 0, sizeof(tv)));
+       igt_assert(slept == 0);
+       do {
+               usleep(usec - slept);
+               slept = igt_nsec_elapsed(&tv) / 1000;
+       } while (slept < usec);
+
+       return igt_nsec_elapsed(&tv);
+}
+
+static void
+busy_one(int device, int clients, const struct intel_execution_engine2 *e)
+{
+       uint64_t active[MAX_CLASS];
+       uint64_t idle[MAX_CLASS];
+       uint64_t old[MAX_CLASS];
+       igt_spin_t *spin;
+       int64_t delay;
+       int i915;
+       int me;
+
+       /* Create a fresh client with 0 runtime */
+       i915 = gem_reopen_driver(device);
+       gem_context_copy_engines(device, 0, i915, 0);
+
+       me = find_me(clients, getpid());
+       igt_assert(me != -1);
+       igt_require(faccessat(me, "busy", 0, F_OK) == 0);
+
+       spin = igt_spin_new(i915,
+                           gem_context_clone_with_engines(i915, 0),
+                           .engine = e->flags,
+                           .flags = IGT_SPIN_POLL_RUN);
+       igt_spin_busywait_until_started(spin);
+
+       delay = -500000; /* 500us slack */
+       memset(old, 0, sizeof(old));
+       for (int pass = 0; pass < 5; pass++) {
+               delay += measured_usleep(1000);
+               igt_debug("delay: %'"PRIu64"ns\n", delay);
+
+               /* Check that we accumulate the runtime, while active */
+               igt_assert_eq(read_runtime(me, active), 1);
+               igt_info("active1[%d]: %'"PRIu64"ns\n", pass, active[e->class]);
+               igt_assert(active[e->class] > old[e->class]); /* monotonic */
+               igt_assert(active[e->class] > delay); /* within reason */
+       }
+
+       gem_quiescent_gpu(i915);
+
+       /* And again now idle */
+       igt_assert_eq(read_runtime(me, idle), 1);
+       igt_info("idle: %'"PRIu64"ns\n", idle[e->class]);
+       igt_assert(idle[e->class] >= active[e->class]);
+
+       gem_context_destroy(i915, spin->execbuf.rsvd1);
+
+       /* And finally after the executing context is no more */
+       igt_assert_eq(read_runtime(me, old), 1);
+       igt_info("old: %'"PRIu64"ns\n", old[e->class]);
+       igt_assert_eq_u64(old[e->class], idle[e->class]);
+
+       /* Once more on the default context for good luck */
+       igt_spin_reset(spin);
+       spin->execbuf.rsvd1 = 0;
+       gem_execbuf(i915, &spin->execbuf);
+       igt_spin_busywait_until_started(spin);
+
+       for (int pass = 0; pass < 5; pass++) {
+               delay += measured_usleep(1000);
+               igt_debug("delay: %'"PRIu64"ns\n", delay);
+
+               /* Check that we accumulate the runtime, while active */
+               igt_assert_eq(read_runtime(me, active), 1);
+               igt_info("active0[%d]: %'"PRIu64"ns\n", pass, active[e->class]);
+               igt_assert(active[e->class] > old[e->class]); /* monotonic */
+               igt_assert(active[e->class] > delay); /* within reason */
+       }
+
+       gem_quiescent_gpu(i915);
+
+
+       igt_spin_free(i915, spin);
+       close(i915);
+}
+
+static void busy_all(int device, int clients)
+{
+       const struct intel_execution_engine2 *e;
+       uint64_t active[MAX_CLASS];
+       uint64_t idle[MAX_CLASS];
+       uint64_t old[MAX_CLASS];
+       uint64_t classes = 0;
+       igt_spin_t *spin;
+       int expect = 0;
+       int64_t delay;
+       int i915;
+       int me;
+
+       /* Create a fresh client with 0 runtime */
+       i915 = gem_reopen_driver(device);
+       gem_context_copy_engines(device, 0, i915, 0);
+
+       me = find_me(clients, getpid());
+       igt_assert(me != -1);
+       igt_require(faccessat(me, "busy", 0, F_OK) == 0);
+
+       spin = igt_spin_new(i915,
+                           gem_context_clone_with_engines(i915, 0),
+                           .flags = IGT_SPIN_POLL_RUN);
+       __for_each_physical_engine(i915, e) {
+               spin->execbuf.flags &= ~63;
+               spin->execbuf.flags |= e->flags;
+               gem_execbuf(i915, &spin->execbuf);
+
+               if (!(classes & (1ull << e->class)))
+                       expect++;
+               classes |= 1ull << e->class;
+       }
+       igt_spin_busywait_until_started(spin);
+
+       delay = -500000; /* 500us slack */
+       memset(old, 0, sizeof(old));
+       for (int pass = 0; pass < 5; pass++) {
+               delay += measured_usleep(1000);
+               igt_debug("delay: %'"PRIu64"ns\n", delay);
+
+               /* Check that we accumulate the runtime, while active */
+               igt_assert_eq(read_runtime(me, active), expect);
+               for (int i = 0; i < ARRAY_SIZE(active); i++) {
+                       if (!active[i])
+                               continue;
+
+                       igt_info("active[%d]: %'"PRIu64"ns\n", i, active[i]);
+                       igt_assert(active[i] > old[i]); /* monotonic */
+                       igt_assert(active[i] > delay); /* within reason */
+               }
+       }
+
+       gem_quiescent_gpu(i915);
+
+       /* And again now idle */
+       igt_assert_eq(read_runtime(me, idle), expect);
+       for (int i = 0; i < ARRAY_SIZE(idle); i++) {
+               if (!idle[i])
+                       continue;
+
+               igt_info("idle[%d]: %'"PRIu64"ns\n", i, idle[i]);
+               igt_assert(idle[i] >= active[i]);
+       }
+
+       gem_context_destroy(i915, spin->execbuf.rsvd1);
+       igt_spin_free(i915, spin);
+
+       /* And finally after the executing context is no more */
+       igt_assert_eq(read_runtime(me, old), expect);
+       for (int i = 0; i < ARRAY_SIZE(old); i++) {
+               if (!old[i])
+                       continue;
+
+               igt_info("old[%d]: %'"PRIu64"ns\n", i, old[i]);
+               igt_assert_eq_u64(old[i], idle[i]);
+       }
+
+       close(i915);
+}
+
+igt_main
+{
+       const struct intel_execution_engine2 *e;
+       int i915 = -1, clients = -1;
+
+       igt_fixture {
+               int sys;
+
+               /* Don't allow [too many] extra clients to be opened */
+               i915 = __drm_open_driver(DRIVER_INTEL);
+               igt_require_gem(i915);
+
+               sys = igt_sysfs_open(i915);
+               igt_require(sys != -1);
+
+               clients = openat(sys, "clients", O_RDONLY);
+               igt_require(clients != -1);
+
+               close(sys);
+       }
+
+       igt_subtest("pidname")
+               pidname(i915, clients);
+
+       igt_subtest("create")
+               create(i915, clients);
+
+       igt_subtest_with_dynamic("busy") {
+               __for_each_physical_engine(i915, e) {
+                       igt_dynamic_f("%s", e->name) {
+                               igt_fork(child, 1)
+                                       busy_one(i915, clients, e);
+                               igt_waitchildren();
+                       }
+               }
+       }
+
+       igt_subtest("busy-all") {
+               igt_fork(child, 1)
+                       busy_all(i915, clients);
+               igt_waitchildren();
+       }
+
+       igt_fixture {
+               close(clients);
+               close(i915);
+       }
+}
diff --git a/tests/meson.build b/tests/meson.build
index ff924ff99..825e01833 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -236,6 +236,7 @@ i915_progs = [
        'i915_query',
        'i915_selftest',
        'i915_suspend',
+       'sysfs_clients',
        'sysfs_defaults',
        'sysfs_heartbeat_interval',
        'sysfs_preempt_timeout',
-- 
2.30.0

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to