[igt-dev] [PATCH igt 2/2] tests/msm: Add GEM shrinker/eviction test

Rob Clark robdclark at gmail.com
Sun Dec 11 19:52:38 UTC 2022


From: Rob Clark <robdclark at chromium.org>

Adds a test to various subtests to stress shrinker/eviction.  Various
subtests also add mmap and dma-buf mmap into the mix (the latter because
it uncovered a memory corruption bug due to page mappings not being
correctly shot down).

Signed-off-by: Rob Clark <robdclark at chromium.org>
---
 lib/igt_msm.c          |   2 +-
 lib/igt_msm.h          |   6 +-
 tests/meson.build      |   1 +
 tests/msm/msm_shrink.c | 314 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 321 insertions(+), 2 deletions(-)
 create mode 100644 tests/msm/msm_shrink.c

diff --git a/lib/igt_msm.c b/lib/igt_msm.c
index e9cf588f..8c0380f4 100644
--- a/lib/igt_msm.c
+++ b/lib/igt_msm.c
@@ -268,7 +268,7 @@ igt_msm_cmd_submit(struct msm_cmd *cmd)
 		},
 	};
 	struct drm_msm_gem_submit req = {
-			.flags   = cmd->pipe->pipe | MSM_SUBMIT_FENCE_FD_OUT,
+			.flags   = cmd->pipe->pipe | MSM_SUBMIT_FENCE_FD_OUT | MSM_SUBMIT_NO_IMPLICIT,
 			.queueid = cmd->pipe->submitqueue_id,
 			.nr_cmds = ARRAY_SIZE(cmds),
 			.cmds    = VOID2U64(cmds),
diff --git a/lib/igt_msm.h b/lib/igt_msm.h
index 6008020b..413c7ea6 100644
--- a/lib/igt_msm.h
+++ b/lib/igt_msm.h
@@ -97,10 +97,14 @@ enum adreno_pm4_packet_type {
 
 enum adreno_pm4_type3_packets {
 	CP_NOP = 16,
+	CP_WAIT_MEM_WRITES = 18,
+	CP_WAIT_FOR_ME = 19,
 	CP_WAIT_MEM_GTE = 20,
+	CP_WAIT_FOR_IDLE = 38,
 	CP_WAIT_REG_MEM = 60,
 	CP_MEM_WRITE = 61,
 	CP_MEM_TO_MEM = 115,
+	CP_MEMCPY = 117,
 };
 
 static inline unsigned
@@ -157,7 +161,7 @@ struct msm_cmd {
 	struct msm_bo *cmdstream_bo;
 	uint32_t *cur;
 	uint32_t nr_bos;
-	struct msm_bo *bos[8];
+	struct msm_bo *bos[128];
 };
 
 struct msm_cmd *igt_msm_cmd_new(struct msm_pipe *pipe, size_t size);
diff --git a/tests/meson.build b/tests/meson.build
index 619c18b2..424f050f 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -253,6 +253,7 @@ i915_progs = [
 msm_progs = [
 	'msm_mapping',
 	'msm_recovery',
+	'msm_shrink',
 	'msm_submit'
 ]
 
diff --git a/tests/msm/msm_shrink.c b/tests/msm/msm_shrink.c
new file mode 100644
index 00000000..d0b098aa
--- /dev/null
+++ b/tests/msm/msm_shrink.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright © 2022 Google, Inc.
+ * 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 <fcntl.h>
+#include <sys/mman.h>
+
+#include "igt.h"
+#include "igt_msm.h"
+#include "igt_os.h"
+#include "igt_sysfs.h"
+
+#define SZ_1M (1024 * 1024)
+
+static void leak(uint64_t alloc)
+{
+	char *ptr;
+
+	ptr = mmap(NULL, alloc, PROT_READ | PROT_WRITE,
+		   MAP_ANON | MAP_PRIVATE | MAP_POPULATE,
+		   -1, 0);
+	if (ptr == MAP_FAILED)
+		return;
+
+	while (alloc > 4096) {
+		alloc -= 4096;
+		ptr[alloc] = 0;
+	}
+}
+
+static void madvise_dontneed(struct msm_bo *bo)
+{
+	struct drm_msm_gem_madvise req = {
+		.handle = bo->handle,
+		.madv = MSM_MADV_DONTNEED,
+	};
+	do_ioctl(bo->dev->fd, DRM_IOCTL_MSM_GEM_MADVISE, &req);
+}
+
+static struct msm_cmd *cmd_copy_gpu(struct msm_pipe *pipe, unsigned num_bos, struct msm_bo **bos)
+{
+	struct msm_cmd *cmd = igt_msm_cmd_new(pipe, 0x1000);
+
+	assert((num_bos % 2) == 0);
+
+	for (unsigned i = 0; i < (num_bos / 2); i++) {
+		struct msm_bo *dst = bos[2*i];
+		struct msm_bo *src = bos[2*i+1];
+
+		unsigned dwords = min(0x2000u, dst->size / 4);
+
+		msm_cmd_pkt7(cmd, CP_MEMCPY, 5);
+		msm_cmd_emit(cmd, dwords);          /* DWORDS */
+		msm_cmd_bo  (cmd, src, 0);          /* SRC_LO/HI */
+		msm_cmd_bo  (cmd, dst, 0);          /* DST_LO/HI */
+		msm_cmd_pkt7(cmd, CP_WAIT_MEM_WRITES, 0);
+		msm_cmd_pkt7(cmd, CP_WAIT_FOR_IDLE, 0);
+		msm_cmd_pkt7(cmd, CP_WAIT_FOR_ME, 0);
+	}
+
+	return cmd;
+}
+
+static void *map_dmabuf(struct msm_bo *bo)
+{
+	int fd, ret;
+	void *ptr;
+
+	ret = drmPrimeHandleToFD(bo->dev->fd, bo->handle, DRM_CLOEXEC | DRM_RDWR, &fd);
+	igt_assert_eq(ret, 0);
+
+	ptr = mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	igt_assert(ptr != MAP_FAILED);
+
+	close(fd);
+
+	return ptr;
+}
+
+struct test {
+	const char *name;
+	struct msm_cmd *(*cmd)(struct msm_pipe *pipe, unsigned num_bos, struct msm_bo **bos);
+	void *(*map)(struct msm_bo *bo);
+} tests[] = {
+	{ "copy-gpu", cmd_copy_gpu },
+	{ "copy-mmap", cmd_copy_gpu, igt_msm_bo_map },
+	{ "copy-mmap-dmabuf", cmd_copy_gpu, map_dmabuf },
+	{},
+};
+
+enum testmode {
+	SANITY_CHECK	= BIT(0),
+	SINGLE_THREAD	= BIT(1),
+	MADVISE		= BIT(2),
+	OOM		= BIT(3),
+};
+
+static const struct mode {
+	const char *suffix;
+	unsigned flags;
+} modes[] = {
+	{ "-sanitycheck", SANITY_CHECK },
+/* Disable by default to keep test runtime down:
+	{ "-singlethread", SINGLE_THREAD },
+ */
+	{ "", 0 },
+	{ "-madvise", MADVISE },
+	{ "-oom", OOM },
+	{ NULL },
+};
+
+static void do_test(int num_submits, uint64_t alloc_size_kb, int num_bos,
+		    unsigned timeout, bool do_madvise, const struct test *test)
+{
+	struct msm_device *dev = igt_msm_dev_open();
+	struct msm_pipe *pipe = igt_msm_pipe_open(dev, 0);
+	struct msm_bo *bo[num_submits][num_bos];
+	struct msm_cmd *cmd[num_submits];
+	void *map[num_submits][num_bos];
+	int fence_fd = -1;
+
+	/* Allocate the buffer objects and prepare the cmds: */
+	for (int i = 0; i < num_submits; i++) {
+		for (int j = 0; j < num_bos; j++) {
+			bo[i][j] = igt_msm_bo_new(dev, alloc_size_kb * 1024, MSM_BO_WC);
+		}
+		cmd[i] = test->cmd(pipe, num_bos, bo[i]);
+	}
+
+	/* Prepare the CPU maps, if necessary: */
+	if (test->map) {
+		for (int i = 0; i < num_submits; i++) {
+			for (int j = 0; j < num_bos; j++) {
+				map[i][j] = test->map(bo[i][j]);
+				memset(map[i][j], 0xde, bo[i][j]->size);
+			}
+		}
+	}
+
+	igt_until_timeout(timeout) {
+		for (int i = 0; i < num_submits / 2; i++) {
+			if (fence_fd > 0)
+				close(fence_fd);
+			fence_fd = igt_msm_cmd_submit(cmd[i]);
+		}
+
+		igt_wait_and_close(fence_fd);
+		fence_fd = -1;
+
+		if (test->map) {
+			for (int i = 0; i < num_submits; i++) {
+				for (int j = 0; j < num_bos; j++) {
+					memset(map[i][j], 0xde, bo[i][j]->size);
+				}
+			}
+		}
+
+		for (int i = num_submits / 2; i < num_submits; i++) {
+			if (fence_fd > 0)
+				close(fence_fd);
+			fence_fd = igt_msm_cmd_submit(cmd[i]);
+		}
+		igt_wait_and_close(fence_fd);
+		fence_fd = -1;
+	}
+
+	if (do_madvise) {
+		for (int i = 0; i < num_submits; i++) {
+			if (fence_fd > 0)
+				close(fence_fd);
+			fence_fd = igt_msm_cmd_submit(cmd[i]);
+			for (int j = 0; j < num_bos; j++) {
+				madvise_dontneed(bo[i][j]);
+			}
+		}
+		igt_wait_and_close(fence_fd);
+		fence_fd = -1;
+	}
+}
+
+static void run_test(int nchildren, uint64_t alloc_size_mb, unsigned num_bos,
+		     const struct test *test, unsigned flags)
+{
+	const int timeout = (test->map) || (flags & (SANITY_CHECK | MADVISE)) ? 1 : 10;
+	bool madvise = !!(flags & MADVISE);
+	uint64_t alloc_size_kb;
+
+	/* We are trying to use more GEM buffers that will fit in
+	 * memory, but less than 2x avail RAM.  Split across at
+	 * least two submits so we aren't getting into a scenario
+	 * where all the children are trying to pin all the memory
+	 * at the same time and get into a situation where no one
+	 * can make forward progress.
+	 */
+	unsigned num_submits = 8;
+
+	if (flags & SANITY_CHECK)
+		nchildren = 1;
+
+	alloc_size_kb = DIV_ROUND_UP(alloc_size_mb * 1024, num_bos * num_submits);
+
+	if (flags & SINGLE_THREAD) {
+		num_submits *= nchildren;
+		nchildren = 1;
+	}
+
+	igt_info("%s, %d submits, %d processes, and %d x %'"PRIu64"KiB bos per submit for total size of %'"PRIu64"KiB\n",
+		 test->name, num_submits, nchildren, num_bos, alloc_size_kb,
+		 num_bos * num_submits * nchildren * alloc_size_kb);
+
+	/* Background load */
+	if (flags & OOM) {
+		igt_fork(child, nchildren) {
+			for (int pass = 0; pass < nchildren; pass++)
+				leak(alloc_size_kb / 1024);
+		}
+	}
+
+	/* Exercise major ioctls */
+	igt_fork(child, nchildren) {
+		do_test(num_submits, alloc_size_kb, num_bos, timeout, madvise, test);
+	}
+	igt_waitchildren();
+}
+
+static const unsigned num_bos[] = { 8, 32 };
+
+igt_main
+{
+	struct msm_device *dev = NULL;
+	uint64_t alloc_size_mb = 0;
+	int num_processes = 0;
+
+	igt_fixture {
+		int params, ncpus;
+		uint64_t mem_size;
+		uint64_t swap_size;
+
+		/* Make sure we are running on the right hw: */
+		dev = igt_msm_dev_open();
+
+		igt_require(dev->gen >= 6);
+
+		/* Ensure that eviction is enabled: */
+		params = igt_params_open(dev->fd);
+		igt_sysfs_set(params, "enable_eviction", "1");
+		igt_sysfs_set(params, "address_space_size", "0x400000000");
+		close(params);
+
+		/* Figure out # of processes and allocation size: */
+		ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+		mem_size = igt_get_total_ram_mb();
+		swap_size = igt_get_total_swap_mb();
+
+		igt_require(swap_size > 0);
+
+		/*
+		 * Spawn enough processes to use all memory, but each only
+		 * uses a fraction of the available per-cpu memory.
+		 * Individually the processes would be ok, but en masse
+		 * we expect the shrinker to start purging objects,
+		 * and possibly fail.
+		 *
+		 * Note, consider only a fraction of avail swap when
+		 * calculating target size, as we have no good way to
+		 * determine if it is zram swap (which will consume an
+		 * increasing portion of RAM as it is filled)
+		 */
+		mem_size += swap_size / 4;
+		alloc_size_mb = (mem_size + ncpus - 1) / ncpus / 8;
+		num_processes = ncpus + (mem_size / alloc_size_mb);
+
+		igt_info("Using %d processes and %'"PRIu64"MiB per process for total size of %'"PRIu64"MiB\n",
+			 num_processes, alloc_size_mb, num_processes * alloc_size_mb);
+
+		igt_require_memory(num_processes, alloc_size_mb,
+				   CHECK_SWAP | CHECK_RAM);
+	}
+
+	for(const struct test *t = tests; t->name; t++) {
+		for(const struct mode *m = modes; m->suffix; m++) {
+			for (int i = 0; i < ARRAY_SIZE(num_bos); i++) {
+				igt_subtest_f("%s%s-%u", t->name, m->suffix, num_bos[i]) {
+					run_test(num_processes, alloc_size_mb,
+						 num_bos[i], t, m->flags);
+				}
+			}
+		}
+	}
+
+	igt_fixture {
+		igt_msm_dev_close(dev);
+	}
+}
-- 
2.38.1



More information about the igt-dev mailing list