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
27static sigjmp_buf sigjmp_buf_env;
28
29static void signal_handler(int sig)
30{
31 siglongjmp(sigjmp_buf_env, -EFAULT);
32}
33
34static 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
54static 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
101FIXTURE(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
112FIXTURE_SETUP(pfnmap)
113{
114 self->pagesize = getpagesize();
115
116 /* We'll require two physical pages throughout our tests ... */
117 if (find_ram_target(phys_addr: &self->phys_addr, pagesize: 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(addr: self->addr1, size: self->size1, pagesize: 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
138FIXTURE_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
148TEST_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
169TEST_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
189TEST_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
205TEST_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
214TEST_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
226TEST_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
249TEST_HARNESS_MAIN
250

source code of linux/tools/testing/selftests/mm/pfnmap.c