xref: /linux/tools/testing/selftests/mm/pfnmap.c (revision fd1f8473503e5bf897bd3e8efe3545c0352954e6)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Basic VM_PFNMAP tests relying on mmap() of '/dev/mem'
4  *
5  * Copyright 2025, Red Hat, Inc.
6  *
7  * Author(s): David Hildenbrand <david@redhat.com>
8  */
9 #define _GNU_SOURCE
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdint.h>
13 #include <unistd.h>
14 #include <errno.h>
15 #include <stdio.h>
16 #include <ctype.h>
17 #include <fcntl.h>
18 #include <signal.h>
19 #include <setjmp.h>
20 #include <linux/mman.h>
21 #include <sys/mman.h>
22 #include <sys/wait.h>
23 
24 #include "../kselftest_harness.h"
25 #include "vm_util.h"
26 
27 static sigjmp_buf sigjmp_buf_env;
28 
29 static void signal_handler(int sig)
30 {
31 	siglongjmp(sigjmp_buf_env, -EFAULT);
32 }
33 
34 static int test_read_access(char *addr, size_t size, size_t pagesize)
35 {
36 	size_t offs;
37 	int ret;
38 
39 	if (signal(SIGSEGV, signal_handler) == SIG_ERR)
40 		return -EINVAL;
41 
42 	ret = sigsetjmp(sigjmp_buf_env, 1);
43 	if (!ret) {
44 		for (offs = 0; offs < size; offs += pagesize)
45 			/* Force a read that the compiler cannot optimize out. */
46 			*((volatile char *)(addr + offs));
47 	}
48 	if (signal(SIGSEGV, SIG_DFL) == SIG_ERR)
49 		return -EINVAL;
50 
51 	return ret;
52 }
53 
54 static int find_ram_target(off_t *phys_addr,
55 		unsigned long long pagesize)
56 {
57 	unsigned long long start, end;
58 	char line[80], *end_ptr;
59 	FILE *file;
60 
61 	/* Search /proc/iomem for the first suitable "System RAM" range. */
62 	file = fopen("/proc/iomem", "r");
63 	if (!file)
64 		return -errno;
65 
66 	while (fgets(line, sizeof(line), file)) {
67 		/* Ignore any child nodes. */
68 		if (!isalnum(line[0]))
69 			continue;
70 
71 		if (!strstr(line, "System RAM\n"))
72 			continue;
73 
74 		start = strtoull(line, &end_ptr, 16);
75 		/* Skip over the "-" */
76 		end_ptr++;
77 		/* Make end "exclusive". */
78 		end = strtoull(end_ptr, NULL, 16) + 1;
79 
80 		/* Actual addresses are not exported */
81 		if (!start && !end)
82 			break;
83 
84 		/* We need full pages. */
85 		start = (start + pagesize - 1) & ~(pagesize - 1);
86 		end &= ~(pagesize - 1);
87 
88 		if (start != (off_t)start)
89 			break;
90 
91 		/* We need two pages. */
92 		if (end > start + 2 * pagesize) {
93 			fclose(file);
94 			*phys_addr = start;
95 			return 0;
96 		}
97 	}
98 	return -ENOENT;
99 }
100 
101 FIXTURE(pfnmap)
102 {
103 	off_t phys_addr;
104 	size_t pagesize;
105 	int dev_mem_fd;
106 	char *addr1;
107 	size_t size1;
108 	char *addr2;
109 	size_t size2;
110 };
111 
112 FIXTURE_SETUP(pfnmap)
113 {
114 	self->pagesize = getpagesize();
115 
116 	/* We'll require two physical pages throughout our tests ... */
117 	if (find_ram_target(&self->phys_addr, self->pagesize))
118 		SKIP(return, "Cannot find ram target in '/proc/iomem'\n");
119 
120 	self->dev_mem_fd = open("/dev/mem", O_RDONLY);
121 	if (self->dev_mem_fd < 0)
122 		SKIP(return, "Cannot open '/dev/mem'\n");
123 
124 	self->size1 = self->pagesize * 2;
125 	self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED,
126 			   self->dev_mem_fd, self->phys_addr);
127 	if (self->addr1 == MAP_FAILED)
128 		SKIP(return, "Cannot mmap '/dev/mem'\n");
129 
130 	/* ... and want to be able to read from them. */
131 	if (test_read_access(self->addr1, self->size1, self->pagesize))
132 		SKIP(return, "Cannot read-access mmap'ed '/dev/mem'\n");
133 
134 	self->size2 = 0;
135 	self->addr2 = MAP_FAILED;
136 }
137 
138 FIXTURE_TEARDOWN(pfnmap)
139 {
140 	if (self->addr2 != MAP_FAILED)
141 		munmap(self->addr2, self->size2);
142 	if (self->addr1 != MAP_FAILED)
143 		munmap(self->addr1, self->size1);
144 	if (self->dev_mem_fd >= 0)
145 		close(self->dev_mem_fd);
146 }
147 
148 TEST_F(pfnmap, madvise_disallowed)
149 {
150 	int advices[] = {
151 		MADV_DONTNEED,
152 		MADV_DONTNEED_LOCKED,
153 		MADV_FREE,
154 		MADV_WIPEONFORK,
155 		MADV_COLD,
156 		MADV_PAGEOUT,
157 		MADV_POPULATE_READ,
158 		MADV_POPULATE_WRITE,
159 	};
160 	int i;
161 
162 	/* All these advices must be rejected. */
163 	for (i = 0; i < ARRAY_SIZE(advices); i++) {
164 		EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0);
165 		EXPECT_EQ(errno, EINVAL);
166 	}
167 }
168 
169 TEST_F(pfnmap, munmap_split)
170 {
171 	/*
172 	 * Unmap the first page. This munmap() call is not really expected to
173 	 * fail, but we might be able to trigger other internal issues.
174 	 */
175 	ASSERT_EQ(munmap(self->addr1, self->pagesize), 0);
176 
177 	/*
178 	 * Remap the first page while the second page is still mapped. This
179 	 * makes sure that any PAT tracking on x86 will allow for mmap()'ing
180 	 * a page again while some parts of the first mmap() are still
181 	 * around.
182 	 */
183 	self->size2 = self->pagesize;
184 	self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED,
185 			   self->dev_mem_fd, self->phys_addr);
186 	ASSERT_NE(self->addr2, MAP_FAILED);
187 }
188 
189 TEST_F(pfnmap, mremap_fixed)
190 {
191 	char *ret;
192 
193 	/* Reserve a destination area. */
194 	self->size2 = self->size1;
195 	self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE,
196 			   -1, 0);
197 	ASSERT_NE(self->addr2, MAP_FAILED);
198 
199 	/* mremap() over our destination. */
200 	ret = mremap(self->addr1, self->size1, self->size2,
201 		     MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2);
202 	ASSERT_NE(ret, MAP_FAILED);
203 }
204 
205 TEST_F(pfnmap, mremap_shrink)
206 {
207 	char *ret;
208 
209 	/* Shrinking is expected to work. */
210 	ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0);
211 	ASSERT_NE(ret, MAP_FAILED);
212 }
213 
214 TEST_F(pfnmap, mremap_expand)
215 {
216 	/*
217 	 * Growing is not expected to work, and getting it right would
218 	 * be challenging. So this test primarily serves as an early warning
219 	 * that something that probably should never work suddenly works.
220 	 */
221 	self->size2 = self->size1 + self->pagesize;
222 	self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE);
223 	ASSERT_EQ(self->addr2, MAP_FAILED);
224 }
225 
226 TEST_F(pfnmap, fork)
227 {
228 	pid_t pid;
229 	int ret;
230 
231 	/* fork() a child and test if the child can access the pages. */
232 	pid = fork();
233 	ASSERT_GE(pid, 0);
234 
235 	if (!pid) {
236 		EXPECT_EQ(test_read_access(self->addr1, self->size1,
237 					   self->pagesize), 0);
238 		exit(0);
239 	}
240 
241 	wait(&ret);
242 	if (WIFEXITED(ret))
243 		ret = WEXITSTATUS(ret);
244 	else
245 		ret = -EINVAL;
246 	ASSERT_EQ(ret, 0);
247 }
248 
249 TEST_HARNESS_MAIN
250