File Index
Files and directories
Sign in
FILE

pfnmap.c

File preview with syntax highlighting.

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