xref: /linux/tools/testing/selftests/mm/pfnmap.c (revision fd1f8473503e5bf897bd3e8efe3545c0352954e6)
12616b370SDavid Hildenbrand // SPDX-License-Identifier: GPL-2.0-only
22616b370SDavid Hildenbrand /*
32616b370SDavid Hildenbrand  * Basic VM_PFNMAP tests relying on mmap() of '/dev/mem'
42616b370SDavid Hildenbrand  *
52616b370SDavid Hildenbrand  * Copyright 2025, Red Hat, Inc.
62616b370SDavid Hildenbrand  *
72616b370SDavid Hildenbrand  * Author(s): David Hildenbrand <david@redhat.com>
82616b370SDavid Hildenbrand  */
92616b370SDavid Hildenbrand #define _GNU_SOURCE
102616b370SDavid Hildenbrand #include <stdlib.h>
112616b370SDavid Hildenbrand #include <string.h>
122616b370SDavid Hildenbrand #include <stdint.h>
132616b370SDavid Hildenbrand #include <unistd.h>
142616b370SDavid Hildenbrand #include <errno.h>
15*bb084994SDavid Hildenbrand #include <stdio.h>
16*bb084994SDavid Hildenbrand #include <ctype.h>
172616b370SDavid Hildenbrand #include <fcntl.h>
182616b370SDavid Hildenbrand #include <signal.h>
192616b370SDavid Hildenbrand #include <setjmp.h>
202616b370SDavid Hildenbrand #include <linux/mman.h>
212616b370SDavid Hildenbrand #include <sys/mman.h>
222616b370SDavid Hildenbrand #include <sys/wait.h>
232616b370SDavid Hildenbrand 
242616b370SDavid Hildenbrand #include "../kselftest_harness.h"
252616b370SDavid Hildenbrand #include "vm_util.h"
262616b370SDavid Hildenbrand 
272616b370SDavid Hildenbrand static sigjmp_buf sigjmp_buf_env;
282616b370SDavid Hildenbrand 
292616b370SDavid Hildenbrand static void signal_handler(int sig)
302616b370SDavid Hildenbrand {
312616b370SDavid Hildenbrand 	siglongjmp(sigjmp_buf_env, -EFAULT);
322616b370SDavid Hildenbrand }
332616b370SDavid Hildenbrand 
342616b370SDavid Hildenbrand static int test_read_access(char *addr, size_t size, size_t pagesize)
352616b370SDavid Hildenbrand {
362616b370SDavid Hildenbrand 	size_t offs;
372616b370SDavid Hildenbrand 	int ret;
382616b370SDavid Hildenbrand 
392616b370SDavid Hildenbrand 	if (signal(SIGSEGV, signal_handler) == SIG_ERR)
402616b370SDavid Hildenbrand 		return -EINVAL;
412616b370SDavid Hildenbrand 
422616b370SDavid Hildenbrand 	ret = sigsetjmp(sigjmp_buf_env, 1);
432616b370SDavid Hildenbrand 	if (!ret) {
442616b370SDavid Hildenbrand 		for (offs = 0; offs < size; offs += pagesize)
452616b370SDavid Hildenbrand 			/* Force a read that the compiler cannot optimize out. */
462616b370SDavid Hildenbrand 			*((volatile char *)(addr + offs));
472616b370SDavid Hildenbrand 	}
48*bb084994SDavid Hildenbrand 	if (signal(SIGSEGV, SIG_DFL) == SIG_ERR)
492616b370SDavid Hildenbrand 		return -EINVAL;
502616b370SDavid Hildenbrand 
512616b370SDavid Hildenbrand 	return ret;
522616b370SDavid Hildenbrand }
532616b370SDavid Hildenbrand 
54*bb084994SDavid Hildenbrand static int find_ram_target(off_t *phys_addr,
55*bb084994SDavid Hildenbrand 		unsigned long long pagesize)
56*bb084994SDavid Hildenbrand {
57*bb084994SDavid Hildenbrand 	unsigned long long start, end;
58*bb084994SDavid Hildenbrand 	char line[80], *end_ptr;
59*bb084994SDavid Hildenbrand 	FILE *file;
60*bb084994SDavid Hildenbrand 
61*bb084994SDavid Hildenbrand 	/* Search /proc/iomem for the first suitable "System RAM" range. */
62*bb084994SDavid Hildenbrand 	file = fopen("/proc/iomem", "r");
63*bb084994SDavid Hildenbrand 	if (!file)
64*bb084994SDavid Hildenbrand 		return -errno;
65*bb084994SDavid Hildenbrand 
66*bb084994SDavid Hildenbrand 	while (fgets(line, sizeof(line), file)) {
67*bb084994SDavid Hildenbrand 		/* Ignore any child nodes. */
68*bb084994SDavid Hildenbrand 		if (!isalnum(line[0]))
69*bb084994SDavid Hildenbrand 			continue;
70*bb084994SDavid Hildenbrand 
71*bb084994SDavid Hildenbrand 		if (!strstr(line, "System RAM\n"))
72*bb084994SDavid Hildenbrand 			continue;
73*bb084994SDavid Hildenbrand 
74*bb084994SDavid Hildenbrand 		start = strtoull(line, &end_ptr, 16);
75*bb084994SDavid Hildenbrand 		/* Skip over the "-" */
76*bb084994SDavid Hildenbrand 		end_ptr++;
77*bb084994SDavid Hildenbrand 		/* Make end "exclusive". */
78*bb084994SDavid Hildenbrand 		end = strtoull(end_ptr, NULL, 16) + 1;
79*bb084994SDavid Hildenbrand 
80*bb084994SDavid Hildenbrand 		/* Actual addresses are not exported */
81*bb084994SDavid Hildenbrand 		if (!start && !end)
82*bb084994SDavid Hildenbrand 			break;
83*bb084994SDavid Hildenbrand 
84*bb084994SDavid Hildenbrand 		/* We need full pages. */
85*bb084994SDavid Hildenbrand 		start = (start + pagesize - 1) & ~(pagesize - 1);
86*bb084994SDavid Hildenbrand 		end &= ~(pagesize - 1);
87*bb084994SDavid Hildenbrand 
88*bb084994SDavid Hildenbrand 		if (start != (off_t)start)
89*bb084994SDavid Hildenbrand 			break;
90*bb084994SDavid Hildenbrand 
91*bb084994SDavid Hildenbrand 		/* We need two pages. */
92*bb084994SDavid Hildenbrand 		if (end > start + 2 * pagesize) {
93*bb084994SDavid Hildenbrand 			fclose(file);
94*bb084994SDavid Hildenbrand 			*phys_addr = start;
95*bb084994SDavid Hildenbrand 			return 0;
96*bb084994SDavid Hildenbrand 		}
97*bb084994SDavid Hildenbrand 	}
98*bb084994SDavid Hildenbrand 	return -ENOENT;
99*bb084994SDavid Hildenbrand }
100*bb084994SDavid Hildenbrand 
1012616b370SDavid Hildenbrand FIXTURE(pfnmap)
1022616b370SDavid Hildenbrand {
103*bb084994SDavid Hildenbrand 	off_t phys_addr;
1042616b370SDavid Hildenbrand 	size_t pagesize;
1052616b370SDavid Hildenbrand 	int dev_mem_fd;
1062616b370SDavid Hildenbrand 	char *addr1;
1072616b370SDavid Hildenbrand 	size_t size1;
1082616b370SDavid Hildenbrand 	char *addr2;
1092616b370SDavid Hildenbrand 	size_t size2;
1102616b370SDavid Hildenbrand };
1112616b370SDavid Hildenbrand 
1122616b370SDavid Hildenbrand FIXTURE_SETUP(pfnmap)
1132616b370SDavid Hildenbrand {
1142616b370SDavid Hildenbrand 	self->pagesize = getpagesize();
1152616b370SDavid Hildenbrand 
116*bb084994SDavid Hildenbrand 	/* We'll require two physical pages throughout our tests ... */
117*bb084994SDavid Hildenbrand 	if (find_ram_target(&self->phys_addr, self->pagesize))
118*bb084994SDavid Hildenbrand 		SKIP(return, "Cannot find ram target in '/proc/iomem'\n");
119*bb084994SDavid Hildenbrand 
1202616b370SDavid Hildenbrand 	self->dev_mem_fd = open("/dev/mem", O_RDONLY);
1212616b370SDavid Hildenbrand 	if (self->dev_mem_fd < 0)
1222616b370SDavid Hildenbrand 		SKIP(return, "Cannot open '/dev/mem'\n");
1232616b370SDavid Hildenbrand 
1242616b370SDavid Hildenbrand 	self->size1 = self->pagesize * 2;
1252616b370SDavid Hildenbrand 	self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED,
126*bb084994SDavid Hildenbrand 			   self->dev_mem_fd, self->phys_addr);
1272616b370SDavid Hildenbrand 	if (self->addr1 == MAP_FAILED)
1282616b370SDavid Hildenbrand 		SKIP(return, "Cannot mmap '/dev/mem'\n");
1292616b370SDavid Hildenbrand 
1302616b370SDavid Hildenbrand 	/* ... and want to be able to read from them. */
1312616b370SDavid Hildenbrand 	if (test_read_access(self->addr1, self->size1, self->pagesize))
1322616b370SDavid Hildenbrand 		SKIP(return, "Cannot read-access mmap'ed '/dev/mem'\n");
1332616b370SDavid Hildenbrand 
1342616b370SDavid Hildenbrand 	self->size2 = 0;
1352616b370SDavid Hildenbrand 	self->addr2 = MAP_FAILED;
1362616b370SDavid Hildenbrand }
1372616b370SDavid Hildenbrand 
1382616b370SDavid Hildenbrand FIXTURE_TEARDOWN(pfnmap)
1392616b370SDavid Hildenbrand {
1402616b370SDavid Hildenbrand 	if (self->addr2 != MAP_FAILED)
1412616b370SDavid Hildenbrand 		munmap(self->addr2, self->size2);
1422616b370SDavid Hildenbrand 	if (self->addr1 != MAP_FAILED)
1432616b370SDavid Hildenbrand 		munmap(self->addr1, self->size1);
1442616b370SDavid Hildenbrand 	if (self->dev_mem_fd >= 0)
1452616b370SDavid Hildenbrand 		close(self->dev_mem_fd);
1462616b370SDavid Hildenbrand }
1472616b370SDavid Hildenbrand 
1482616b370SDavid Hildenbrand TEST_F(pfnmap, madvise_disallowed)
1492616b370SDavid Hildenbrand {
1502616b370SDavid Hildenbrand 	int advices[] = {
1512616b370SDavid Hildenbrand 		MADV_DONTNEED,
1522616b370SDavid Hildenbrand 		MADV_DONTNEED_LOCKED,
1532616b370SDavid Hildenbrand 		MADV_FREE,
1542616b370SDavid Hildenbrand 		MADV_WIPEONFORK,
1552616b370SDavid Hildenbrand 		MADV_COLD,
1562616b370SDavid Hildenbrand 		MADV_PAGEOUT,
1572616b370SDavid Hildenbrand 		MADV_POPULATE_READ,
1582616b370SDavid Hildenbrand 		MADV_POPULATE_WRITE,
1592616b370SDavid Hildenbrand 	};
1602616b370SDavid Hildenbrand 	int i;
1612616b370SDavid Hildenbrand 
1622616b370SDavid Hildenbrand 	/* All these advices must be rejected. */
1632616b370SDavid Hildenbrand 	for (i = 0; i < ARRAY_SIZE(advices); i++) {
1642616b370SDavid Hildenbrand 		EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0);
1652616b370SDavid Hildenbrand 		EXPECT_EQ(errno, EINVAL);
1662616b370SDavid Hildenbrand 	}
1672616b370SDavid Hildenbrand }
1682616b370SDavid Hildenbrand 
1692616b370SDavid Hildenbrand TEST_F(pfnmap, munmap_split)
1702616b370SDavid Hildenbrand {
1712616b370SDavid Hildenbrand 	/*
1722616b370SDavid Hildenbrand 	 * Unmap the first page. This munmap() call is not really expected to
1732616b370SDavid Hildenbrand 	 * fail, but we might be able to trigger other internal issues.
1742616b370SDavid Hildenbrand 	 */
1752616b370SDavid Hildenbrand 	ASSERT_EQ(munmap(self->addr1, self->pagesize), 0);
1762616b370SDavid Hildenbrand 
1772616b370SDavid Hildenbrand 	/*
1782616b370SDavid Hildenbrand 	 * Remap the first page while the second page is still mapped. This
1792616b370SDavid Hildenbrand 	 * makes sure that any PAT tracking on x86 will allow for mmap()'ing
1802616b370SDavid Hildenbrand 	 * a page again while some parts of the first mmap() are still
1812616b370SDavid Hildenbrand 	 * around.
1822616b370SDavid Hildenbrand 	 */
1832616b370SDavid Hildenbrand 	self->size2 = self->pagesize;
1842616b370SDavid Hildenbrand 	self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED,
185*bb084994SDavid Hildenbrand 			   self->dev_mem_fd, self->phys_addr);
1862616b370SDavid Hildenbrand 	ASSERT_NE(self->addr2, MAP_FAILED);
1872616b370SDavid Hildenbrand }
1882616b370SDavid Hildenbrand 
1892616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_fixed)
1902616b370SDavid Hildenbrand {
1912616b370SDavid Hildenbrand 	char *ret;
1922616b370SDavid Hildenbrand 
1932616b370SDavid Hildenbrand 	/* Reserve a destination area. */
1942616b370SDavid Hildenbrand 	self->size2 = self->size1;
1952616b370SDavid Hildenbrand 	self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE,
1962616b370SDavid Hildenbrand 			   -1, 0);
1972616b370SDavid Hildenbrand 	ASSERT_NE(self->addr2, MAP_FAILED);
1982616b370SDavid Hildenbrand 
1992616b370SDavid Hildenbrand 	/* mremap() over our destination. */
2002616b370SDavid Hildenbrand 	ret = mremap(self->addr1, self->size1, self->size2,
2012616b370SDavid Hildenbrand 		     MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2);
2022616b370SDavid Hildenbrand 	ASSERT_NE(ret, MAP_FAILED);
2032616b370SDavid Hildenbrand }
2042616b370SDavid Hildenbrand 
2052616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_shrink)
2062616b370SDavid Hildenbrand {
2072616b370SDavid Hildenbrand 	char *ret;
2082616b370SDavid Hildenbrand 
2092616b370SDavid Hildenbrand 	/* Shrinking is expected to work. */
2102616b370SDavid Hildenbrand 	ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0);
2112616b370SDavid Hildenbrand 	ASSERT_NE(ret, MAP_FAILED);
2122616b370SDavid Hildenbrand }
2132616b370SDavid Hildenbrand 
2142616b370SDavid Hildenbrand TEST_F(pfnmap, mremap_expand)
2152616b370SDavid Hildenbrand {
2162616b370SDavid Hildenbrand 	/*
2172616b370SDavid Hildenbrand 	 * Growing is not expected to work, and getting it right would
2182616b370SDavid Hildenbrand 	 * be challenging. So this test primarily serves as an early warning
2192616b370SDavid Hildenbrand 	 * that something that probably should never work suddenly works.
2202616b370SDavid Hildenbrand 	 */
2212616b370SDavid Hildenbrand 	self->size2 = self->size1 + self->pagesize;
2222616b370SDavid Hildenbrand 	self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE);
2232616b370SDavid Hildenbrand 	ASSERT_EQ(self->addr2, MAP_FAILED);
2242616b370SDavid Hildenbrand }
2252616b370SDavid Hildenbrand 
2262616b370SDavid Hildenbrand TEST_F(pfnmap, fork)
2272616b370SDavid Hildenbrand {
2282616b370SDavid Hildenbrand 	pid_t pid;
2292616b370SDavid Hildenbrand 	int ret;
2302616b370SDavid Hildenbrand 
2312616b370SDavid Hildenbrand 	/* fork() a child and test if the child can access the pages. */
2322616b370SDavid Hildenbrand 	pid = fork();
2332616b370SDavid Hildenbrand 	ASSERT_GE(pid, 0);
2342616b370SDavid Hildenbrand 
2352616b370SDavid Hildenbrand 	if (!pid) {
2362616b370SDavid Hildenbrand 		EXPECT_EQ(test_read_access(self->addr1, self->size1,
2372616b370SDavid Hildenbrand 					   self->pagesize), 0);
2382616b370SDavid Hildenbrand 		exit(0);
2392616b370SDavid Hildenbrand 	}
2402616b370SDavid Hildenbrand 
2412616b370SDavid Hildenbrand 	wait(&ret);
2422616b370SDavid Hildenbrand 	if (WIFEXITED(ret))
2432616b370SDavid Hildenbrand 		ret = WEXITSTATUS(ret);
2442616b370SDavid Hildenbrand 	else
2452616b370SDavid Hildenbrand 		ret = -EINVAL;
2462616b370SDavid Hildenbrand 	ASSERT_EQ(ret, 0);
2472616b370SDavid Hildenbrand }
2482616b370SDavid Hildenbrand 
2492616b370SDavid Hildenbrand TEST_HARNESS_MAIN
250