Commit c3315502 authored by Peter Xu's avatar Peter Xu Committed by Andrew Morton
Browse files

selftests/mm: move zeropage test into uffd unit tests

Simplifies it a bit along the way, e.g., drop the never used offset field
(which was always the 1st page so offset=0).

Introduce uffd_register_with_ioctls() out of uffd_register() to detect
uffdio_register.ioctls got returned.  Check that automatically when testing
UFFDIO_ZEROPAGE on different types of memory (and kernel).

Link: https://lkml.kernel.org/r/20230412164404.328815-1-peterx@redhat.com


Signed-off-by: default avatarPeter Xu <peterx@redhat.com>
Cc: Axel Rasmussen <axelrasmussen@google.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Dmitry Safonov <0x7f454c46@gmail.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Mike Rapoport (IBM) <rppt@kernel.org>
Cc: Zach O'Keefe <zokeefe@google.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 73c1ea93
Loading
Loading
Loading
Loading
+1 −93
Original line number Diff line number Diff line
@@ -109,15 +109,6 @@ static inline uint64_t uffd_minor_feature(void)
		return 0;
}

static int my_bcmp(char *str1, char *str2, size_t n)
{
	unsigned long i;
	for (i = 0; i < n; i++)
		if (str1[i] != str2[i])
			return 1;
	return 0;
}

static void *locking_thread(void *arg)
{
	unsigned long cpu = (unsigned long) arg;
@@ -273,89 +264,6 @@ static int stress(struct uffd_args *args)
	return 0;
}

static void retry_uffdio_zeropage(int ufd,
				  struct uffdio_zeropage *uffdio_zeropage,
				  unsigned long offset)
{
	uffd_test_ops->alias_mapping(&uffdio_zeropage->range.start,
				     uffdio_zeropage->range.len,
				     offset);
	if (ioctl(ufd, UFFDIO_ZEROPAGE, uffdio_zeropage)) {
		if (uffdio_zeropage->zeropage != -EEXIST)
			err("UFFDIO_ZEROPAGE error: %"PRId64,
			    (int64_t)uffdio_zeropage->zeropage);
	} else {
		err("UFFDIO_ZEROPAGE error: %"PRId64,
		    (int64_t)uffdio_zeropage->zeropage);
	}
}

static int __uffdio_zeropage(int ufd, unsigned long offset)
{
	struct uffdio_zeropage uffdio_zeropage;
	int ret;
	bool has_zeropage = !(test_type == TEST_HUGETLB);
	__s64 res;

	if (offset >= nr_pages * page_size)
		err("unexpected offset %lu", offset);
	uffdio_zeropage.range.start = (unsigned long) area_dst + offset;
	uffdio_zeropage.range.len = page_size;
	uffdio_zeropage.mode = 0;
	ret = ioctl(ufd, UFFDIO_ZEROPAGE, &uffdio_zeropage);
	res = uffdio_zeropage.zeropage;
	if (ret) {
		/* real retval in ufdio_zeropage.zeropage */
		if (has_zeropage)
			err("UFFDIO_ZEROPAGE error: %"PRId64, (int64_t)res);
		else if (res != -EINVAL)
			err("UFFDIO_ZEROPAGE not -EINVAL");
	} else if (has_zeropage) {
		if (res != page_size) {
			err("UFFDIO_ZEROPAGE unexpected size");
		} else {
			retry_uffdio_zeropage(ufd, &uffdio_zeropage,
					      offset);
			return 1;
		}
	} else
		err("UFFDIO_ZEROPAGE succeeded");

	return 0;
}

static int uffdio_zeropage(int ufd, unsigned long offset)
{
	return __uffdio_zeropage(ufd, offset);
}

/* exercise UFFDIO_ZEROPAGE */
static int userfaultfd_zeropage_test(void)
{
	printf("testing UFFDIO_ZEROPAGE: ");
	fflush(stdout);

	uffd_test_ctx_init(0);

	if (uffd_register(uffd, area_dst, nr_pages * page_size,
			  true, test_uffdio_wp, false))
		err("register failure");

	if (area_dst_alias) {
		/* Needed this to test zeropage-retry on shared memory */
		if (uffd_register(uffd, area_dst_alias, nr_pages * page_size,
				  true, test_uffdio_wp, false))
			err("register failure");
	}

	if (uffdio_zeropage(uffd, 0))
		if (my_bcmp(area_dst, zeropage, page_size))
			err("zeropage is not zero");

	printf("done.\n");
	return 0;
}

static int userfaultfd_stress(void)
{
	void *area;
@@ -467,7 +375,7 @@ static int userfaultfd_stress(void)
		uffd_stats_report(args, nr_cpus);
	}

	return userfaultfd_zeropage_test();
	return 0;
}

static void set_test_type(const char *type)
+93 −0
Original line number Diff line number Diff line
@@ -660,7 +660,100 @@ static void uffd_events_wp_test(void)
	uffd_events_test_common(true);
}

static void retry_uffdio_zeropage(int ufd,
				  struct uffdio_zeropage *uffdio_zeropage)
{
	uffd_test_ops->alias_mapping(&uffdio_zeropage->range.start,
				     uffdio_zeropage->range.len,
				     0);
	if (ioctl(ufd, UFFDIO_ZEROPAGE, uffdio_zeropage)) {
		if (uffdio_zeropage->zeropage != -EEXIST)
			err("UFFDIO_ZEROPAGE error: %"PRId64,
			    (int64_t)uffdio_zeropage->zeropage);
	} else {
		err("UFFDIO_ZEROPAGE error: %"PRId64,
		    (int64_t)uffdio_zeropage->zeropage);
	}
}

static bool do_uffdio_zeropage(int ufd, bool has_zeropage)
{
	struct uffdio_zeropage uffdio_zeropage = { 0 };
	int ret;
	__s64 res;

	uffdio_zeropage.range.start = (unsigned long) area_dst;
	uffdio_zeropage.range.len = page_size;
	uffdio_zeropage.mode = 0;
	ret = ioctl(ufd, UFFDIO_ZEROPAGE, &uffdio_zeropage);
	res = uffdio_zeropage.zeropage;
	if (ret) {
		/* real retval in ufdio_zeropage.zeropage */
		if (has_zeropage)
			err("UFFDIO_ZEROPAGE error: %"PRId64, (int64_t)res);
		else if (res != -EINVAL)
			err("UFFDIO_ZEROPAGE not -EINVAL");
	} else if (has_zeropage) {
		if (res != page_size)
			err("UFFDIO_ZEROPAGE unexpected size");
		else
			retry_uffdio_zeropage(ufd, &uffdio_zeropage);
		return true;
	} else
		err("UFFDIO_ZEROPAGE succeeded");

	return false;
}

/*
 * Registers a range with MISSING mode only for zeropage test.  Return true
 * if UFFDIO_ZEROPAGE supported, false otherwise. Can't use uffd_register()
 * because we want to detect .ioctls along the way.
 */
static bool
uffd_register_detect_zeropage(int uffd, void *addr, uint64_t len)
{
	uint64_t ioctls = 0;

	if (uffd_register_with_ioctls(uffd, addr, len, true,
				      false, false, &ioctls))
		err("zeropage register fail");

	return ioctls & (1 << _UFFDIO_ZEROPAGE);
}

/* exercise UFFDIO_ZEROPAGE */
static void uffd_zeropage_test(void)
{
	bool has_zeropage;
	int i;

	has_zeropage = uffd_register_detect_zeropage(uffd, area_dst, page_size);
	if (area_dst_alias)
		/* Ignore the retval; we already have it */
		uffd_register_detect_zeropage(uffd, area_dst_alias, page_size);

	if (do_uffdio_zeropage(uffd, has_zeropage))
		for (i = 0; i < page_size; i++)
			if (area_dst[i] != 0)
				err("data non-zero at offset %d\n", i);

	if (uffd_unregister(uffd, area_dst, page_size))
		err("unregister");

	if (area_dst_alias && uffd_unregister(uffd, area_dst_alias, page_size))
		err("unregister");

	uffd_test_pass();
}

uffd_test_case_t uffd_tests[] = {
	{
		.name = "zeropage",
		.uffd_fn = uffd_zeropage_test,
		.mem_targets = MEM_ALL,
		.uffd_feature_required = 0,
	},
	{
		.name = "pagemap",
		.uffd_fn = uffd_pagemap_test,
+12 −2
Original line number Diff line number Diff line
@@ -198,8 +198,9 @@ unsigned long default_huge_page_size(void)
	return hps;
}

int uffd_register(int uffd, void *addr, uint64_t len,
		  bool miss, bool wp, bool minor)
/* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */
int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
			      bool miss, bool wp, bool minor, uint64_t *ioctls)
{
	struct uffdio_register uffdio_register = { 0 };
	uint64_t mode = 0;
@@ -218,10 +219,19 @@ int uffd_register(int uffd, void *addr, uint64_t len,

	if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
		ret = -errno;
	else if (ioctls)
		*ioctls = uffdio_register.ioctls;

	return ret;
}

int uffd_register(int uffd, void *addr, uint64_t len,
		  bool miss, bool wp, bool minor)
{
	return uffd_register_with_ioctls(uffd, addr, len,
					 miss, wp, minor, NULL);
}

int uffd_unregister(int uffd, void *addr, uint64_t len)
{
	struct uffdio_range range = { .start = (uintptr_t)addr, .len = len };
+2 −0
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ int uffd_open_dev(unsigned int flags);
int uffd_open_sys(unsigned int flags);
int uffd_open(unsigned int flags);
int uffd_get_features(uint64_t *features);
int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
			      bool miss, bool wp, bool minor, uint64_t *ioctls);

/*
 * On ppc64 this will only work with radix 2M hugepage size