xref: /linux/tools/testing/selftests/mm/hugetlb-mremap.c (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * hugetlb-mremap:
4  *
5  * Example of remapping huge page memory in a user application using the
6  * mremap system call.  The path to a file in a hugetlbfs filesystem must
7  * be passed as the last argument to this test.  The amount of memory used
8  * by this test in MBs can optionally be passed as an argument.  If no memory
9  * amount is passed, the default amount is 10MB.
10  *
11  * To make sure the test triggers pmd sharing and goes through the 'unshare'
12  * path in the mremap code use 1GB (1024) or more.
13  */
14 
15 #define _GNU_SOURCE
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <sys/mman.h>
20 #include <errno.h>
21 #include <fcntl.h> /* Definition of O_* constants */
22 #include <sys/syscall.h> /* Definition of SYS_* constants */
23 #include <linux/userfaultfd.h>
24 #include <sys/ioctl.h>
25 #include <string.h>
26 #include <stdbool.h>
27 #include "kselftest.h"
28 #include "vm_util.h"
29 #include "hugepage_settings.h"
30 
31 #define DEFAULT_LENGTH_MB 10UL
32 #define MB_TO_BYTES(x) (x * 1024 * 1024)
33 
34 #define PROTECTION (PROT_READ | PROT_WRITE | PROT_EXEC)
35 #define FLAGS (MAP_HUGETLB | MAP_SHARED)
36 
37 static void check_bytes(char *addr)
38 {
39 	ksft_print_msg("First hex is %x\n", *((unsigned int *)addr));
40 }
41 
42 static void write_bytes(char *addr, size_t len)
43 {
44 	unsigned long i;
45 
46 	for (i = 0; i < len; i++)
47 		*(addr + i) = (char)i;
48 }
49 
50 static int read_bytes(char *addr, size_t len)
51 {
52 	unsigned long i;
53 
54 	check_bytes(addr);
55 	for (i = 0; i < len; i++)
56 		if (*(addr + i) != (char)i) {
57 			ksft_print_msg("Mismatch at %lu\n", i);
58 			return 1;
59 		}
60 	return 0;
61 }
62 
63 static void register_region_with_uffd(char *addr, size_t len)
64 {
65 	long uffd; /* userfaultfd file descriptor */
66 	struct uffdio_api uffdio_api;
67 
68 	/* Create and enable userfaultfd object. */
69 	uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
70 	if (uffd == -1) {
71 		switch (errno) {
72 		case EPERM:
73 			ksft_exit_skip("Insufficient permissions, try running as root.\n");
74 			break;
75 		case ENOSYS:
76 			ksft_exit_skip("userfaultfd is not supported/not enabled.\n");
77 			break;
78 		default:
79 			ksft_exit_fail_msg("userfaultfd failed with %s\n", strerror(errno));
80 			break;
81 		}
82 	}
83 
84 	uffdio_api.api = UFFD_API;
85 	uffdio_api.features = 0;
86 	if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
87 		ksft_exit_fail_msg("ioctl-UFFDIO_API: %s\n", strerror(errno));
88 
89 	/* Register the passed memory range for handling by the userfaultfd object.
90 	 * In mode, we request to track missing pages
91 	 * (i.e., pages that have not yet been faulted in).
92 	 */
93 	if (uffd_register(uffd, addr, len, true, false, false))
94 		ksft_exit_fail_msg("ioctl-UFFDIO_REGISTER: %s\n", strerror(errno));
95 
96 	ksft_print_msg("Registered memory at address %p with userfaultfd\n", addr);
97 }
98 
99 int main(int argc, char *argv[])
100 {
101 	unsigned long hugepage_size;
102 	int ret = 0, fd, nr;
103 	size_t length = 0;
104 
105 	ksft_print_header();
106 	ksft_set_plan(1);
107 
108 	if (argc >= 2 && !strcmp(argv[1], "-h"))
109 		ksft_exit_fail_msg("Usage: %s [length_in_MB]\n", argv[0]);
110 
111 	/* Read memory length as the first arg if valid, otherwise fallback to
112 	 * the default length.
113 	 */
114 	if (argc >= 2)
115 		length = (size_t)atoi(argv[1]);
116 	else
117 		length = DEFAULT_LENGTH_MB;
118 
119 	hugepage_size = default_huge_page_size();
120 	if (!hugepage_size)
121 		ksft_exit_skip("Could not detect default hugetlb page size\n");
122 	length = MB_TO_BYTES(length);
123 	length = (length + hugepage_size - 1) & ~(hugepage_size - 1);
124 	nr = length / hugepage_size;
125 
126 	if (!hugetlb_setup_default(nr))
127 		ksft_exit_skip("Not enough huge pages\n");
128 
129 	fd = memfd_create(argv[0], MFD_HUGETLB);
130 	if (fd < 0)
131 		ksft_exit_fail_msg("Open failed: %s\n", strerror(errno));
132 
133 	/* mmap to a PUD aligned address to hopefully trigger pmd sharing. */
134 	unsigned long suggested_addr = 0x7eaa40000000;
135 	void *haddr = mmap((void *)suggested_addr, length, PROTECTION, FLAGS, fd, 0);
136 	ksft_print_msg("Map haddr: Returned address is %p\n", haddr);
137 	if (haddr == MAP_FAILED)
138 		ksft_exit_fail_msg("mmap1: %s\n", strerror(errno));
139 
140 	/* mmap again to a dummy address to hopefully trigger pmd sharing. */
141 	suggested_addr = 0x7daa40000000;
142 	void *daddr = mmap((void *)suggested_addr, length, PROTECTION, FLAGS, fd, 0);
143 	ksft_print_msg("Map daddr: Returned address is %p\n", daddr);
144 	if (daddr == MAP_FAILED)
145 		ksft_exit_fail_msg("mmap3: %s\n", strerror(errno));
146 
147 	suggested_addr = 0x7faa40000000;
148 	void *vaddr = mmap((void *)suggested_addr, length, PROTECTION, FLAGS, fd, 0);
149 	ksft_print_msg("Map vaddr: Returned address is %p\n", vaddr);
150 	if (vaddr == MAP_FAILED)
151 		ksft_exit_fail_msg("mmap2: %s\n", strerror(errno));
152 
153 	register_region_with_uffd(haddr, length);
154 
155 	void *addr = mremap(haddr, length, length,
156 			    MREMAP_MAYMOVE | MREMAP_FIXED, vaddr);
157 	if (addr == MAP_FAILED)
158 		ksft_exit_fail_msg("mremap: %s\n", strerror(errno));
159 
160 	ksft_print_msg("Mremap: Returned address is %p\n", addr);
161 	check_bytes(addr);
162 	write_bytes(addr, length);
163 	ret = read_bytes(addr, length);
164 
165 	munmap(addr, length);
166 
167 	addr = mremap(addr, length, length, 0);
168 	if (addr != MAP_FAILED)
169 		ksft_exit_fail_msg("mremap: Expected failure, but call succeeded\n");
170 
171 	close(fd);
172 
173 	ksft_test_result(!ret, "Read same data\n");
174 	ksft_exit(!ret);
175 }
176