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