forked from mirrors/linux
		
	selftests/mm: add simple VM_PFNMAP tests based on mmap'ing /dev/mem
Let's test some basic functionality using /dev/mem. These tests will implicitly cover some PAT (Page Attribute Handling) handling on x86. These tests will only run when /dev/mem access to the first two pages in physical address space is possible and allowed; otherwise, the tests are skipped. On current x86-64 with PAT inside a VM, all tests pass: TAP version 13 1..6 # Starting 6 tests from 1 test cases. # RUN pfnmap.madvise_disallowed ... # OK pfnmap.madvise_disallowed ok 1 pfnmap.madvise_disallowed # RUN pfnmap.munmap_split ... # OK pfnmap.munmap_split ok 2 pfnmap.munmap_split # RUN pfnmap.mremap_fixed ... # OK pfnmap.mremap_fixed ok 3 pfnmap.mremap_fixed # RUN pfnmap.mremap_shrink ... # OK pfnmap.mremap_shrink ok 4 pfnmap.mremap_shrink # RUN pfnmap.mremap_expand ... # OK pfnmap.mremap_expand ok 5 pfnmap.mremap_expand # RUN pfnmap.fork ... # OK pfnmap.fork ok 6 pfnmap.fork # PASSED: 6 / 6 tests passed. # Totals: pass:6 fail:0 xfail:0 xpass:0 skip:0 error:0 However, we are able to trigger: [ 27.888251] x86/PAT: pfnmap:1790 freeing invalid memtype [mem 0x00000000-0x00000fff] There are probably more things worth testing in the future, such as MAP_PRIVATE handling. But this set of tests is sufficient to cover most of the things we will rework regarding PAT handling. Link: https://lkml.kernel.org/r/20250509153033.952746-1-david@redhat.com Signed-off-by: David Hildenbrand <david@redhat.com> Reviewed-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com> Cc: Shuah Khan <shuah@kernel.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Peter Xu <peterx@redhat.com> Cc: Dev Jain <dev.jain@arm.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
		
							parent
							
								
									3f12680913
								
							
						
					
					
						commit
						2616b37032
					
				
					 4 changed files with 202 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								tools/testing/selftests/mm/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								tools/testing/selftests/mm/.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -20,6 +20,7 @@ mremap_test | |||
| on-fault-limit | ||||
| transhuge-stress | ||||
| pagemap_ioctl | ||||
| pfnmap | ||||
| *.tmp* | ||||
| protection_keys | ||||
| protection_keys_32 | ||||
|  |  | |||
|  | @ -84,6 +84,7 @@ TEST_GEN_FILES += mremap_test | |||
| TEST_GEN_FILES += mseal_test | ||||
| TEST_GEN_FILES += on-fault-limit | ||||
| TEST_GEN_FILES += pagemap_ioctl | ||||
| TEST_GEN_FILES += pfnmap | ||||
| TEST_GEN_FILES += thuge-gen | ||||
| TEST_GEN_FILES += transhuge-stress | ||||
| TEST_GEN_FILES += uffd-stress | ||||
|  |  | |||
							
								
								
									
										196
									
								
								tools/testing/selftests/mm/pfnmap.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								tools/testing/selftests/mm/pfnmap.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,196 @@ | |||
| // SPDX-License-Identifier: GPL-2.0-only
 | ||||
| /*
 | ||||
|  * Basic VM_PFNMAP tests relying on mmap() of '/dev/mem' | ||||
|  * | ||||
|  * Copyright 2025, Red Hat, Inc. | ||||
|  * | ||||
|  * Author(s): David Hildenbrand <david@redhat.com> | ||||
|  */ | ||||
| #define _GNU_SOURCE | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <stdint.h> | ||||
| #include <unistd.h> | ||||
| #include <errno.h> | ||||
| #include <fcntl.h> | ||||
| #include <signal.h> | ||||
| #include <setjmp.h> | ||||
| #include <linux/mman.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/wait.h> | ||||
| 
 | ||||
| #include "../kselftest_harness.h" | ||||
| #include "vm_util.h" | ||||
| 
 | ||||
| static sigjmp_buf sigjmp_buf_env; | ||||
| 
 | ||||
| static void signal_handler(int sig) | ||||
| { | ||||
| 	siglongjmp(sigjmp_buf_env, -EFAULT); | ||||
| } | ||||
| 
 | ||||
| static int test_read_access(char *addr, size_t size, size_t pagesize) | ||||
| { | ||||
| 	size_t offs; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (signal(SIGSEGV, signal_handler) == SIG_ERR) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	ret = sigsetjmp(sigjmp_buf_env, 1); | ||||
| 	if (!ret) { | ||||
| 		for (offs = 0; offs < size; offs += pagesize) | ||||
| 			/* Force a read that the compiler cannot optimize out. */ | ||||
| 			*((volatile char *)(addr + offs)); | ||||
| 	} | ||||
| 	if (signal(SIGSEGV, signal_handler) == SIG_ERR) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| FIXTURE(pfnmap) | ||||
| { | ||||
| 	size_t pagesize; | ||||
| 	int dev_mem_fd; | ||||
| 	char *addr1; | ||||
| 	size_t size1; | ||||
| 	char *addr2; | ||||
| 	size_t size2; | ||||
| }; | ||||
| 
 | ||||
| FIXTURE_SETUP(pfnmap) | ||||
| { | ||||
| 	self->pagesize = getpagesize(); | ||||
| 
 | ||||
| 	self->dev_mem_fd = open("/dev/mem", O_RDONLY); | ||||
| 	if (self->dev_mem_fd < 0) | ||||
| 		SKIP(return, "Cannot open '/dev/mem'\n"); | ||||
| 
 | ||||
| 	/* We'll require the first two pages throughout our tests ... */ | ||||
| 	self->size1 = self->pagesize * 2; | ||||
| 	self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED, | ||||
| 			   self->dev_mem_fd, 0); | ||||
| 	if (self->addr1 == MAP_FAILED) | ||||
| 		SKIP(return, "Cannot mmap '/dev/mem'\n"); | ||||
| 
 | ||||
| 	/* ... and want to be able to read from them. */ | ||||
| 	if (test_read_access(self->addr1, self->size1, self->pagesize)) | ||||
| 		SKIP(return, "Cannot read-access mmap'ed '/dev/mem'\n"); | ||||
| 
 | ||||
| 	self->size2 = 0; | ||||
| 	self->addr2 = MAP_FAILED; | ||||
| } | ||||
| 
 | ||||
| FIXTURE_TEARDOWN(pfnmap) | ||||
| { | ||||
| 	if (self->addr2 != MAP_FAILED) | ||||
| 		munmap(self->addr2, self->size2); | ||||
| 	if (self->addr1 != MAP_FAILED) | ||||
| 		munmap(self->addr1, self->size1); | ||||
| 	if (self->dev_mem_fd >= 0) | ||||
| 		close(self->dev_mem_fd); | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, madvise_disallowed) | ||||
| { | ||||
| 	int advices[] = { | ||||
| 		MADV_DONTNEED, | ||||
| 		MADV_DONTNEED_LOCKED, | ||||
| 		MADV_FREE, | ||||
| 		MADV_WIPEONFORK, | ||||
| 		MADV_COLD, | ||||
| 		MADV_PAGEOUT, | ||||
| 		MADV_POPULATE_READ, | ||||
| 		MADV_POPULATE_WRITE, | ||||
| 	}; | ||||
| 	int i; | ||||
| 
 | ||||
| 	/* All these advices must be rejected. */ | ||||
| 	for (i = 0; i < ARRAY_SIZE(advices); i++) { | ||||
| 		EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0); | ||||
| 		EXPECT_EQ(errno, EINVAL); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, munmap_split) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Unmap the first page. This munmap() call is not really expected to | ||||
| 	 * fail, but we might be able to trigger other internal issues. | ||||
| 	 */ | ||||
| 	ASSERT_EQ(munmap(self->addr1, self->pagesize), 0); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Remap the first page while the second page is still mapped. This | ||||
| 	 * makes sure that any PAT tracking on x86 will allow for mmap()'ing | ||||
| 	 * a page again while some parts of the first mmap() are still | ||||
| 	 * around. | ||||
| 	 */ | ||||
| 	self->size2 = self->pagesize; | ||||
| 	self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED, | ||||
| 			   self->dev_mem_fd, 0); | ||||
| 	ASSERT_NE(self->addr2, MAP_FAILED); | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, mremap_fixed) | ||||
| { | ||||
| 	char *ret; | ||||
| 
 | ||||
| 	/* Reserve a destination area. */ | ||||
| 	self->size2 = self->size1; | ||||
| 	self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE, | ||||
| 			   -1, 0); | ||||
| 	ASSERT_NE(self->addr2, MAP_FAILED); | ||||
| 
 | ||||
| 	/* mremap() over our destination. */ | ||||
| 	ret = mremap(self->addr1, self->size1, self->size2, | ||||
| 		     MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2); | ||||
| 	ASSERT_NE(ret, MAP_FAILED); | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, mremap_shrink) | ||||
| { | ||||
| 	char *ret; | ||||
| 
 | ||||
| 	/* Shrinking is expected to work. */ | ||||
| 	ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0); | ||||
| 	ASSERT_NE(ret, MAP_FAILED); | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, mremap_expand) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Growing is not expected to work, and getting it right would | ||||
| 	 * be challenging. So this test primarily serves as an early warning | ||||
| 	 * that something that probably should never work suddenly works. | ||||
| 	 */ | ||||
| 	self->size2 = self->size1 + self->pagesize; | ||||
| 	self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE); | ||||
| 	ASSERT_EQ(self->addr2, MAP_FAILED); | ||||
| } | ||||
| 
 | ||||
| TEST_F(pfnmap, fork) | ||||
| { | ||||
| 	pid_t pid; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* fork() a child and test if the child can access the pages. */ | ||||
| 	pid = fork(); | ||||
| 	ASSERT_GE(pid, 0); | ||||
| 
 | ||||
| 	if (!pid) { | ||||
| 		EXPECT_EQ(test_read_access(self->addr1, self->size1, | ||||
| 					   self->pagesize), 0); | ||||
| 		exit(0); | ||||
| 	} | ||||
| 
 | ||||
| 	wait(&ret); | ||||
| 	if (WIFEXITED(ret)) | ||||
| 		ret = WEXITSTATUS(ret); | ||||
| 	else | ||||
| 		ret = -EINVAL; | ||||
| 	ASSERT_EQ(ret, 0); | ||||
| } | ||||
| 
 | ||||
| TEST_HARNESS_MAIN | ||||
|  | @ -63,6 +63,8 @@ separated by spaces: | |||
| 	test soft dirty page bit semantics | ||||
| - pagemap | ||||
| 	test pagemap_scan IOCTL | ||||
| - pfnmap | ||||
| 	tests for VM_PFNMAP handling | ||||
| - cow | ||||
| 	test copy-on-write semantics | ||||
| - thp | ||||
|  | @ -472,6 +474,8 @@ fi | |||
| 
 | ||||
| CATEGORY="pagemap" run_test ./pagemap_ioctl | ||||
| 
 | ||||
| CATEGORY="pfnmap" run_test ./pfnmap | ||||
| 
 | ||||
| # COW tests | ||||
| CATEGORY="cow" run_test ./cow | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 David Hildenbrand
						David Hildenbrand