summaryrefslogtreecommitdiff
path: root/tests/i915/gem_pread.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2020-11-05 18:09:39 +0000
committerChris Wilson <chris@chris-wilson.co.uk>2020-11-06 22:50:36 +0000
commit624ea6a00b24f96cf5ec0ec34f76f442a6dfa55c (patch)
tree2b398e90bf9cbf3cca5d86e82ee5024c7f012046 /tests/i915/gem_pread.c
parent4c2ec0ad123b82f42f9fe2297e1a41fec73c9229 (diff)
i915/gem_pread,gem_pwrite: Exercise exhaustion
Use userfault to arbitrarily delay the completion of copy_from_user() in order to trap many, many threads inside the core of gem_pread/gem_pwrite. This allows us to exhaust the preferred paths and potentially trip over unexpected fallback paths. Suggested-by: Matthew Auld <matthew.auld@intel.com> Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Reviewed-by: Matthew Auld <matthew.auld@intel.com>
Diffstat (limited to 'tests/i915/gem_pread.c')
-rw-r--r--tests/i915/gem_pread.c157
1 files changed, 156 insertions, 1 deletions
diff --git a/tests/i915/gem_pread.c b/tests/i915/gem_pread.c
index 6d12b8e9..ad3a068b 100644
--- a/tests/i915/gem_pread.c
+++ b/tests/i915/gem_pread.c
@@ -25,6 +25,8 @@
*
*/
+#include <linux/userfaultfd.h>
+
#include "igt.h"
#include <unistd.h>
#include <stdlib.h>
@@ -34,11 +36,15 @@
#include <fcntl.h>
#include <inttypes.h>
#include <errno.h>
-#include <sys/stat.h>
#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
#include <sys/time.h>
+#include <pthread.h>
#include "drm.h"
+#include "igt_vgem.h"
+
#define MiB(x) ((x) * 1024 * 1024)
typedef void *(*mmap_fn_t)(int, uint32_t, uint64_t, uint64_t, unsigned int);
@@ -72,6 +78,152 @@ static void pread_self(int i915)
}
}
+static int userfaultfd(int flags)
+{
+ return syscall(SYS_userfaultfd, flags);
+}
+
+struct ufd_thread {
+ uint32_t *page;
+ int i915;
+ int vgem;
+ int err;
+};
+
+static uint32_t dmabuf_create_handle(int i915, int vgem)
+{
+ struct vgem_bo scratch;
+ uint32_t handle;
+ int dmabuf;
+
+ scratch.width = 64;
+ scratch.height = 64;
+ scratch.bpp = 32;
+ vgem_create(vgem, &scratch);
+
+ dmabuf = prime_handle_to_fd(vgem, scratch.handle);
+ handle = prime_fd_to_handle(i915, dmabuf);
+ close(dmabuf);
+
+ return handle;
+}
+
+static void *ufd_thread(void *arg)
+{
+ struct ufd_thread *t = arg;
+ uint32_t handle = dmabuf_create_handle(t->i915, t->vgem);
+
+ t->err = __gem_read(t->i915, handle, 0, t->page, 1);
+ gem_close(t->i915, handle);
+
+ return NULL;
+}
+
+static void write_value(const char *path, int value)
+{
+ char buf[80];
+ int fd, len;
+
+ len = sprintf(buf, "%d", value);
+ if (len < 0)
+ return;
+
+ fd = open(path, O_WRONLY);
+ if (fd != -1) {
+ write(fd, buf, len);
+ close(fd);
+ }
+}
+
+static void unlimited_processes(unsigned int limit)
+{
+ struct rlimit rlim;
+
+ write_value("/proc/sys/kernel/threads-max", 150000);
+ write_value("/proc/sys/vm/max_map_count", 500000);
+ write_value("/proc/sys/kernel/pid_max", 200000);
+
+ if (getrlimit(RLIMIT_NPROC, &rlim))
+ return;
+
+ rlim.rlim_cur = limit;
+ rlim.rlim_max = limit;
+ setrlimit(RLIMIT_NPROC, &rlim);
+}
+
+static void test_exhaustion(int i915)
+{
+ struct uffdio_api api = { .api = UFFD_API };
+ struct uffdio_register reg;
+ struct uffdio_copy copy;
+ struct ufd_thread t = {
+ .i915 = i915,
+ .vgem = drm_open_driver(DRIVER_VGEM),
+ };
+ struct uffd_msg msg;
+ unsigned long count;
+ pthread_t *thread;
+ char buf[4096];
+ int ufd;
+
+ unlimited_processes(1024 * 1024);
+
+ ufd = userfaultfd(0);
+ igt_require_f(ufd != -1, "kernel support for userfaultfd\n");
+ igt_require_f(ioctl(ufd, UFFDIO_API, &api) == 0 && api.api == UFFD_API,
+ "userfaultfd API v%lld:%lld\n", UFFD_API, api.api);
+
+
+ t.page = mmap(NULL, 4096, PROT_WRITE, MAP_SHARED | MAP_ANON, 0, 0);
+ igt_assert(t.page != MAP_FAILED);
+
+ /* Register our fault handler for t.page */
+ memset(&reg, 0, sizeof(reg));
+ reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+ reg.range.start = to_user_pointer(t.page);
+ reg.range.len = 4096;
+ do_ioctl(ufd, UFFDIO_REGISTER, &reg);
+ igt_assert(reg.ioctls == UFFD_API_RANGE_IOCTLS);
+
+ count = 0;
+ while (!READ_ONCE(t.err)) {
+ if (is_power_of_two(count)) {
+ unsigned long sz = count ? 2 * count : 1;
+ thread = realloc(thread, sz * sizeof(*thread));
+ igt_assert(thread);
+ }
+ if (pthread_create(&thread[count], NULL, ufd_thread, &t))
+ break;
+
+ if (count == 0) { /* Wait for the first userfault */
+ igt_assert_eq(read(ufd, &msg, sizeof(msg)), sizeof(msg));
+ igt_assert_eq(msg.event, UFFD_EVENT_PAGEFAULT);
+ igt_assert(from_user_pointer(msg.arg.pagefault.address) == t.page);
+ }
+
+ count++;
+ }
+ igt_assert(count);
+ if (t.err)
+ igt_warn("err:%d after %lu threads\n", t.err, count);
+
+ /* Service the fault; releasing the stuck ioctls */
+ memset(&copy, 0, sizeof(copy));
+ copy.dst = msg.arg.pagefault.address;
+ copy.src = to_user_pointer(memset(buf, 0xc5, sizeof(buf)));
+ copy.len = 4096;
+ do_ioctl(ufd, UFFDIO_COPY, &copy);
+
+ while (count--)
+ pthread_join(thread[count], NULL);
+ free(thread);
+
+ munmap(t.page, 4096);
+ close(ufd);
+
+ close(t.vgem);
+}
+
#define OBJECT_SIZE 16384
#define KGRN "\x1B[32m"
#define KRED "\x1B[31m"
@@ -172,6 +324,9 @@ igt_main_args("s:", NULL, help_str, opt_handler, NULL)
igt_subtest("self")
pread_self(fd);
+ igt_subtest("exhaustion")
+ test_exhaustion(fd);
+
for (c = cache; c->level != -1; c++) {
igt_subtest(c->name) {
gem_set_caching(fd, dst, c->level);