[RFC,v1,7/7] selftest/mm: test PAGESIZE unmapping UFFD WP marker HWPOISON pages

Message ID 20230428004139.2899856-8-jiaqiyan@google.com
State New
Headers
Series PAGE_SIZE Unmapping in Memory Failure Recovery for HugeTLB Pages |

Commit Message

Jiaqi Yan April 28, 2023, 12:41 a.m. UTC
  For not-yet-faulted hugepage containing HWPOISON raw page, test
1. only HWPOISON raw page will not be faulted, and a BUS_MCEERR_AR
   SIGBUS will be sent to userspace.
2. healthy raw pages are faulted in as normal. Since the hugepage
   has been writeprotect by UFFD, non BUS_MCEERR_AR SIGBUS will be
   sent to userspace.

Signed-off-by: Jiaqi Yan <jiaqiyan@google.com>
---
 tools/testing/selftests/mm/hugetlb-hgm.c | 170 +++++++++++++++++++++++
 1 file changed, 170 insertions(+)
  

Patch

diff --git a/tools/testing/selftests/mm/hugetlb-hgm.c b/tools/testing/selftests/mm/hugetlb-hgm.c
index bc9529986b66..81ee2d99fea8 100644
--- a/tools/testing/selftests/mm/hugetlb-hgm.c
+++ b/tools/testing/selftests/mm/hugetlb-hgm.c
@@ -515,6 +515,169 @@  static int uffd_register(int uffd, char *primary_map, unsigned long len,
 	return ioctl(uffd, UFFDIO_REGISTER, &reg);
 }
 
+static int setup_present_map(char *present_map, size_t len)
+{
+	size_t offset = 0;
+	unsigned char iter = 0;
+	unsigned long pagesize = getpagesize();
+	uint64_t size;
+
+	for (size = len/2; size >= pagesize;
+			offset += size, size /= 2) {
+		iter++;
+		memset(present_map + offset, iter, size);
+	}
+	return 0;
+}
+
+static enum test_status test_hwpoison_absent_uffd_wp(int fd, size_t hugepagesize, size_t len)
+{
+	int uffd;
+	char *absent_map, *present_map;
+	struct uffdio_api api;
+	int register_args;
+	struct sigaction new, old;
+	enum test_status status = TEST_SKIPPED;
+	const unsigned long pagesize = getpagesize();
+	const unsigned long hwpoison_index = 128;
+	char *hwpoison_addr;
+
+	if (hwpoison_index >= (len / pagesize)) {
+		printf(ERROR_PREFIX "hwpoison_index out of range");
+		return TEST_FAILED;
+	}
+
+	if (ftruncate(fd, len) < 0) {
+		perror(ERROR_PREFIX "ftruncate failed");
+		return TEST_FAILED;
+	}
+
+	uffd = userfaultfd(O_CLOEXEC);
+	if (uffd < 0) {
+		perror(ERROR_PREFIX "uffd not created");
+		return TEST_FAILED;
+	}
+
+	absent_map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (absent_map == MAP_FAILED) {
+		perror(ERROR_PREFIX "mmap for ABSENT mapping failed");
+		goto close_uffd;
+	}
+	printf(PREFIX "ABSENT mapping: %p\n", absent_map);
+
+	api.api = UFFD_API;
+	api.features = UFFD_FEATURE_SIGBUS | UFFD_FEATURE_EXACT_ADDRESS |
+		UFFD_FEATURE_EVENT_FORK;
+	if (ioctl(uffd, UFFDIO_API, &api) == -1) {
+		perror(ERROR_PREFIX "UFFDIO_API failed");
+		goto unmap_absent;
+	}
+
+	/*
+	 * Register with UFFDIO_REGISTER_MODE_WP to have UFFD WP bit on
+	 * the HugeTLB page table entry.
+	 */
+	register_args = UFFDIO_REGISTER_MODE_MISSING | UFFDIO_REGISTER_MODE_WP;
+	if (uffd_register(uffd, absent_map, len, register_args)) {
+		perror(ERROR_PREFIX "UFFDIO_REGISTER failed");
+		goto unmap_absent;
+	}
+
+	new.sa_sigaction = &sigbus_handler;
+	new.sa_flags = SA_SIGINFO;
+	if (sigaction(SIGBUS, &new, &old) < 0) {
+		perror(ERROR_PREFIX "could not setup SIGBUS handler");
+		goto unmap_absent;
+	}
+
+	/*
+	 * Set WP markers to the absent huge mapping. With HGM enabled in
+	 * kernel CONFIG, memory_failure will enabled HGM in kernel,
+	 * so no need to enable HGM from userspace.
+	 */
+	if (userfaultfd_writeprotect(uffd, absent_map, len, true) < 0) {
+		status = TEST_FAILED;
+		goto unmap_absent;
+	}
+
+	status = TEST_PASSED;
+
+	/*
+	 * With MAP_SHARED hugetlb memory, we cna inject memory error to
+	 * not-yet-faulted mapping (absent_map) by injecting memory error
+	 * to a already faulted mapping (present_map).
+	 */
+	present_map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (present_map == MAP_FAILED) {
+		perror(ERROR_PREFIX "mmap for non present mapping failed");
+		goto close_uffd;
+	}
+	printf(PREFIX "PRESENT mapping: %p\n", present_map);
+	setup_present_map(present_map, len);
+
+	hwpoison_addr = present_map + hwpoison_index * pagesize;
+	if (madvise(hwpoison_addr, pagesize, MADV_HWPOISON)) {
+		perror(PREFIX "MADV_HWPOISON a page in PRESENT mapping failed");
+		status = TEST_FAILED;
+		goto unmap_present;
+	}
+
+	printf(PREFIX "checking poisoned range [%p, %p) (len=%#lx) in PRESENT mapping\n",
+	       hwpoison_addr, hwpoison_addr + pagesize, pagesize);
+	if (test_sigbus(hwpoison_addr, true) < 0) {
+		status = TEST_FAILED;
+		goto done;
+	}
+	printf(PREFIX "checking healthy pages in PRESENT mapping\n");
+	unsigned long hwpoison_addrs[] = {
+		(unsigned long)hwpoison_addr,
+		(unsigned long)hwpoison_addr,
+		(unsigned long)hwpoison_addr
+	};
+	status = verify_raw_pages(present_map, len, hwpoison_addrs);
+	if (status != TEST_PASSED) {
+		printf(ERROR_PREFIX "checking healthy pages failed\n");
+		goto done;
+	}
+
+	for (int i = 0; i < len; i += pagesize) {
+		if (i == hwpoison_index * pagesize) {
+			printf(PREFIX "checking poisoned range [%p, %p) (len=%#lx) in ABSENT mapping\n",
+				absent_map + i, absent_map + i + pagesize, pagesize);
+			if (test_sigbus(absent_map + i, true) < 0) {
+				status = TEST_FAILED;
+				break;
+			}
+		} else {
+			/*
+			 * With UFFD_FEATURE_SIGBUS, we should get a SIGBUS for
+			 * every not faulted (non present) page/byte.
+			 */
+			if (test_sigbus(absent_map + i, false) < 0) {
+				printf(PREFIX "checking healthy range [%p, %p) (len=%#lx) in ABSENT mapping failed\n",
+					absent_map + i, absent_map + i + pagesize, pagesize);
+				status = TEST_FAILED;
+				break;
+			}
+		}
+	}
+done:
+	if (ftruncate(fd, 0) < 0) {
+		perror(ERROR_PREFIX "ftruncate back to 0 failed");
+		status = TEST_FAILED;
+	}
+unmap_present:
+	printf(PREFIX "Unmap PRESENT mapping=%p\n", absent_map);
+	munmap(present_map, len);
+unmap_absent:
+	printf(PREFIX "Unmap ABSENT mapping=%p\n", absent_map);
+	munmap(absent_map, len);
+close_uffd:
+	printf(PREFIX "Close UFFD\n");
+	close(uffd);
+	return status;
+}
+
 enum test_type {
 	TEST_DEFAULT,
 	TEST_UFFDWP,
@@ -744,6 +907,13 @@  int main(void)
 	printf("HGM hwpoison test: %s\n", status_to_str(status));
 	if (status == TEST_FAILED)
 		ret = -1;
+
+	printf("HGM hwpoison UFFD-WP marker test...\n");
+	status = test_hwpoison_absent_uffd_wp(fd, hugepagesize, len);
+	printf("HGM hwpoison UFFD-WP marker test: %s\n",
+		status_to_str(status));
+	if (status == TEST_FAILED)
+		ret = -1;
 close:
 	close(fd);