xref: /linux/tools/testing/selftests/mm/gup_test.c (revision 1901472fa880e5706f90926cd85a268d2d16bf84)
1*1901472fSMichael Ellerman #define __SANE_USERSPACE_TYPES__ // Use ll64
2baa489faSSeongJae Park #include <fcntl.h>
3baa489faSSeongJae Park #include <errno.h>
4baa489faSSeongJae Park #include <stdio.h>
5baa489faSSeongJae Park #include <stdlib.h>
6baa489faSSeongJae Park #include <unistd.h>
7baa489faSSeongJae Park #include <dirent.h>
8baa489faSSeongJae Park #include <sys/ioctl.h>
9baa489faSSeongJae Park #include <sys/mman.h>
10baa489faSSeongJae Park #include <sys/stat.h>
11baa489faSSeongJae Park #include <sys/types.h>
12baa489faSSeongJae Park #include <pthread.h>
13baa489faSSeongJae Park #include <assert.h>
14baa489faSSeongJae Park #include <mm/gup_test.h>
15baa489faSSeongJae Park #include "../kselftest.h"
16af605d26SPeter Xu #include "vm_util.h"
17baa489faSSeongJae Park 
18baa489faSSeongJae Park #define MB (1UL << 20)
19baa489faSSeongJae Park 
20baa489faSSeongJae Park /* Just the flags we need, copied from mm.h: */
21baa489faSSeongJae Park #define FOLL_WRITE	0x01	/* check pte is writable */
22baa489faSSeongJae Park #define FOLL_TOUCH	0x02	/* mark page accessed */
23baa489faSSeongJae Park 
24baa489faSSeongJae Park #define GUP_TEST_FILE "/sys/kernel/debug/gup_test"
25baa489faSSeongJae Park 
26baa489faSSeongJae Park static unsigned long cmd = GUP_FAST_BENCHMARK;
27baa489faSSeongJae Park static int gup_fd, repeats = 1;
28baa489faSSeongJae Park static unsigned long size = 128 * MB;
29baa489faSSeongJae Park /* Serialize prints */
30baa489faSSeongJae Park static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
31baa489faSSeongJae Park 
32baa489faSSeongJae Park static char *cmd_to_str(unsigned long cmd)
33baa489faSSeongJae Park {
34baa489faSSeongJae Park 	switch (cmd) {
35baa489faSSeongJae Park 	case GUP_FAST_BENCHMARK:
36baa489faSSeongJae Park 		return "GUP_FAST_BENCHMARK";
37baa489faSSeongJae Park 	case PIN_FAST_BENCHMARK:
38baa489faSSeongJae Park 		return "PIN_FAST_BENCHMARK";
39baa489faSSeongJae Park 	case PIN_LONGTERM_BENCHMARK:
40baa489faSSeongJae Park 		return "PIN_LONGTERM_BENCHMARK";
41baa489faSSeongJae Park 	case GUP_BASIC_TEST:
42baa489faSSeongJae Park 		return "GUP_BASIC_TEST";
43baa489faSSeongJae Park 	case PIN_BASIC_TEST:
44baa489faSSeongJae Park 		return "PIN_BASIC_TEST";
45baa489faSSeongJae Park 	case DUMP_USER_PAGES_TEST:
46baa489faSSeongJae Park 		return "DUMP_USER_PAGES_TEST";
47baa489faSSeongJae Park 	}
48baa489faSSeongJae Park 	return "Unknown command";
49baa489faSSeongJae Park }
50baa489faSSeongJae Park 
51baa489faSSeongJae Park void *gup_thread(void *data)
52baa489faSSeongJae Park {
53baa489faSSeongJae Park 	struct gup_test gup = *(struct gup_test *)data;
54cb6e7caeSMuhammad Usama Anjum 	int i, status;
55baa489faSSeongJae Park 
56baa489faSSeongJae Park 	/* Only report timing information on the *_BENCHMARK commands: */
57baa489faSSeongJae Park 	if ((cmd == PIN_FAST_BENCHMARK) || (cmd == GUP_FAST_BENCHMARK) ||
58baa489faSSeongJae Park 	     (cmd == PIN_LONGTERM_BENCHMARK)) {
59baa489faSSeongJae Park 		for (i = 0; i < repeats; i++) {
60baa489faSSeongJae Park 			gup.size = size;
61cb6e7caeSMuhammad Usama Anjum 			status = ioctl(gup_fd, cmd, &gup);
62cb6e7caeSMuhammad Usama Anjum 			if (status)
63cb6e7caeSMuhammad Usama Anjum 				break;
64baa489faSSeongJae Park 
65baa489faSSeongJae Park 			pthread_mutex_lock(&print_mutex);
66cb6e7caeSMuhammad Usama Anjum 			ksft_print_msg("%s: Time: get:%lld put:%lld us",
67baa489faSSeongJae Park 				       cmd_to_str(cmd), gup.get_delta_usec,
68baa489faSSeongJae Park 				       gup.put_delta_usec);
69baa489faSSeongJae Park 			if (gup.size != size)
70cb6e7caeSMuhammad Usama Anjum 				ksft_print_msg(", truncated (size: %lld)", gup.size);
71cb6e7caeSMuhammad Usama Anjum 			ksft_print_msg("\n");
72baa489faSSeongJae Park 			pthread_mutex_unlock(&print_mutex);
73baa489faSSeongJae Park 		}
74baa489faSSeongJae Park 	} else {
75baa489faSSeongJae Park 		gup.size = size;
76cb6e7caeSMuhammad Usama Anjum 		status = ioctl(gup_fd, cmd, &gup);
77cb6e7caeSMuhammad Usama Anjum 		if (status)
78cb6e7caeSMuhammad Usama Anjum 			goto return_;
79baa489faSSeongJae Park 
80baa489faSSeongJae Park 		pthread_mutex_lock(&print_mutex);
81cb6e7caeSMuhammad Usama Anjum 		ksft_print_msg("%s: done\n", cmd_to_str(cmd));
82baa489faSSeongJae Park 		if (gup.size != size)
83cb6e7caeSMuhammad Usama Anjum 			ksft_print_msg("Truncated (size: %lld)\n", gup.size);
84baa489faSSeongJae Park 		pthread_mutex_unlock(&print_mutex);
85baa489faSSeongJae Park 	}
86baa489faSSeongJae Park 
87cb6e7caeSMuhammad Usama Anjum return_:
88cb6e7caeSMuhammad Usama Anjum 	ksft_test_result(!status, "ioctl status %d\n", status);
89baa489faSSeongJae Park 	return NULL;
90baa489faSSeongJae Park }
91baa489faSSeongJae Park 
92baa489faSSeongJae Park int main(int argc, char **argv)
93baa489faSSeongJae Park {
94baa489faSSeongJae Park 	struct gup_test gup = { 0 };
95baa489faSSeongJae Park 	int filed, i, opt, nr_pages = 1, thp = -1, write = 1, nthreads = 1, ret;
96baa489faSSeongJae Park 	int flags = MAP_PRIVATE, touch = 0;
97baa489faSSeongJae Park 	char *file = "/dev/zero";
98baa489faSSeongJae Park 	pthread_t *tid;
99baa489faSSeongJae Park 	char *p;
100baa489faSSeongJae Park 
101baa489faSSeongJae Park 	while ((opt = getopt(argc, argv, "m:r:n:F:f:abcj:tTLUuwWSHpz")) != -1) {
102baa489faSSeongJae Park 		switch (opt) {
103baa489faSSeongJae Park 		case 'a':
104baa489faSSeongJae Park 			cmd = PIN_FAST_BENCHMARK;
105baa489faSSeongJae Park 			break;
106baa489faSSeongJae Park 		case 'b':
107baa489faSSeongJae Park 			cmd = PIN_BASIC_TEST;
108baa489faSSeongJae Park 			break;
109baa489faSSeongJae Park 		case 'L':
110baa489faSSeongJae Park 			cmd = PIN_LONGTERM_BENCHMARK;
111baa489faSSeongJae Park 			break;
112baa489faSSeongJae Park 		case 'c':
113baa489faSSeongJae Park 			cmd = DUMP_USER_PAGES_TEST;
114baa489faSSeongJae Park 			/*
115baa489faSSeongJae Park 			 * Dump page 0 (index 1). May be overridden later, by
116baa489faSSeongJae Park 			 * user's non-option arguments.
117baa489faSSeongJae Park 			 *
118baa489faSSeongJae Park 			 * .which_pages is zero-based, so that zero can mean "do
119baa489faSSeongJae Park 			 * nothing".
120baa489faSSeongJae Park 			 */
121baa489faSSeongJae Park 			gup.which_pages[0] = 1;
122baa489faSSeongJae Park 			break;
123baa489faSSeongJae Park 		case 'p':
124baa489faSSeongJae Park 			/* works only with DUMP_USER_PAGES_TEST */
125baa489faSSeongJae Park 			gup.test_flags |= GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;
126baa489faSSeongJae Park 			break;
127baa489faSSeongJae Park 		case 'F':
128baa489faSSeongJae Park 			/* strtol, so you can pass flags in hex form */
129baa489faSSeongJae Park 			gup.gup_flags = strtol(optarg, 0, 0);
130baa489faSSeongJae Park 			break;
131baa489faSSeongJae Park 		case 'j':
132baa489faSSeongJae Park 			nthreads = atoi(optarg);
133baa489faSSeongJae Park 			break;
134baa489faSSeongJae Park 		case 'm':
135baa489faSSeongJae Park 			size = atoi(optarg) * MB;
136baa489faSSeongJae Park 			break;
137baa489faSSeongJae Park 		case 'r':
138baa489faSSeongJae Park 			repeats = atoi(optarg);
139baa489faSSeongJae Park 			break;
140baa489faSSeongJae Park 		case 'n':
141baa489faSSeongJae Park 			nr_pages = atoi(optarg);
142baa489faSSeongJae Park 			break;
143baa489faSSeongJae Park 		case 't':
144baa489faSSeongJae Park 			thp = 1;
145baa489faSSeongJae Park 			break;
146baa489faSSeongJae Park 		case 'T':
147baa489faSSeongJae Park 			thp = 0;
148baa489faSSeongJae Park 			break;
149baa489faSSeongJae Park 		case 'U':
150baa489faSSeongJae Park 			cmd = GUP_BASIC_TEST;
151baa489faSSeongJae Park 			break;
152baa489faSSeongJae Park 		case 'u':
153baa489faSSeongJae Park 			cmd = GUP_FAST_BENCHMARK;
154baa489faSSeongJae Park 			break;
155baa489faSSeongJae Park 		case 'w':
156baa489faSSeongJae Park 			write = 1;
157baa489faSSeongJae Park 			break;
158baa489faSSeongJae Park 		case 'W':
159baa489faSSeongJae Park 			write = 0;
160baa489faSSeongJae Park 			break;
161baa489faSSeongJae Park 		case 'f':
162baa489faSSeongJae Park 			file = optarg;
163baa489faSSeongJae Park 			break;
164baa489faSSeongJae Park 		case 'S':
165baa489faSSeongJae Park 			flags &= ~MAP_PRIVATE;
166baa489faSSeongJae Park 			flags |= MAP_SHARED;
167baa489faSSeongJae Park 			break;
168baa489faSSeongJae Park 		case 'H':
169baa489faSSeongJae Park 			flags |= (MAP_HUGETLB | MAP_ANONYMOUS);
170baa489faSSeongJae Park 			break;
171baa489faSSeongJae Park 		case 'z':
172baa489faSSeongJae Park 			/* fault pages in gup, do not fault in userland */
173baa489faSSeongJae Park 			touch = 1;
174baa489faSSeongJae Park 			break;
175baa489faSSeongJae Park 		default:
176cb6e7caeSMuhammad Usama Anjum 			ksft_exit_fail_msg("Wrong argument\n");
177baa489faSSeongJae Park 		}
178baa489faSSeongJae Park 	}
179baa489faSSeongJae Park 
180baa489faSSeongJae Park 	if (optind < argc) {
181baa489faSSeongJae Park 		int extra_arg_count = 0;
182baa489faSSeongJae Park 		/*
183baa489faSSeongJae Park 		 * For example:
184baa489faSSeongJae Park 		 *
185baa489faSSeongJae Park 		 *   ./gup_test -c 0 1 0x1001
186baa489faSSeongJae Park 		 *
187baa489faSSeongJae Park 		 * ...to dump pages 0, 1, and 4097
188baa489faSSeongJae Park 		 */
189baa489faSSeongJae Park 
190baa489faSSeongJae Park 		while ((optind < argc) &&
191baa489faSSeongJae Park 		       (extra_arg_count < GUP_TEST_MAX_PAGES_TO_DUMP)) {
192baa489faSSeongJae Park 			/*
193baa489faSSeongJae Park 			 * Do the 1-based indexing here, so that the user can
194baa489faSSeongJae Park 			 * use normal 0-based indexing on the command line.
195baa489faSSeongJae Park 			 */
196baa489faSSeongJae Park 			long page_index = strtol(argv[optind], 0, 0) + 1;
197baa489faSSeongJae Park 
198baa489faSSeongJae Park 			gup.which_pages[extra_arg_count] = page_index;
199baa489faSSeongJae Park 			extra_arg_count++;
200baa489faSSeongJae Park 			optind++;
201baa489faSSeongJae Park 		}
202baa489faSSeongJae Park 	}
203baa489faSSeongJae Park 
204cb6e7caeSMuhammad Usama Anjum 	ksft_print_header();
205cb6e7caeSMuhammad Usama Anjum 	ksft_set_plan(nthreads);
206cb6e7caeSMuhammad Usama Anjum 
2078b65ef5aSVitaly Chikunov 	filed = open(file, O_RDWR|O_CREAT, 0664);
208cb6e7caeSMuhammad Usama Anjum 	if (filed < 0)
209cb6e7caeSMuhammad Usama Anjum 		ksft_exit_fail_msg("Unable to open %s: %s\n", file, strerror(errno));
210baa489faSSeongJae Park 
211baa489faSSeongJae Park 	gup.nr_pages_per_call = nr_pages;
212baa489faSSeongJae Park 	if (write)
213baa489faSSeongJae Park 		gup.gup_flags |= FOLL_WRITE;
214baa489faSSeongJae Park 
215baa489faSSeongJae Park 	gup_fd = open(GUP_TEST_FILE, O_RDWR);
216baa489faSSeongJae Park 	if (gup_fd == -1) {
217baa489faSSeongJae Park 		switch (errno) {
218baa489faSSeongJae Park 		case EACCES:
219baa489faSSeongJae Park 			if (getuid())
220cb6e7caeSMuhammad Usama Anjum 				ksft_print_msg("Please run this test as root\n");
221baa489faSSeongJae Park 			break;
222baa489faSSeongJae Park 		case ENOENT:
223cb6e7caeSMuhammad Usama Anjum 			if (opendir("/sys/kernel/debug") == NULL)
224cb6e7caeSMuhammad Usama Anjum 				ksft_print_msg("mount debugfs at /sys/kernel/debug\n");
225cb6e7caeSMuhammad Usama Anjum 			ksft_print_msg("check if CONFIG_GUP_TEST is enabled in kernel config\n");
226baa489faSSeongJae Park 			break;
227baa489faSSeongJae Park 		default:
228cb6e7caeSMuhammad Usama Anjum 			ksft_print_msg("failed to open %s: %s\n", GUP_TEST_FILE, strerror(errno));
229baa489faSSeongJae Park 			break;
230baa489faSSeongJae Park 		}
231cb6e7caeSMuhammad Usama Anjum 		ksft_test_result_skip("Please run this test as root\n");
23269e545edSNathan Chancellor 		ksft_exit_pass();
233baa489faSSeongJae Park 	}
234baa489faSSeongJae Park 
235baa489faSSeongJae Park 	p = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, filed, 0);
236cb6e7caeSMuhammad Usama Anjum 	if (p == MAP_FAILED)
237cb6e7caeSMuhammad Usama Anjum 		ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
238baa489faSSeongJae Park 	gup.addr = (unsigned long)p;
239baa489faSSeongJae Park 
240baa489faSSeongJae Park 	if (thp == 1)
241baa489faSSeongJae Park 		madvise(p, size, MADV_HUGEPAGE);
242baa489faSSeongJae Park 	else if (thp == 0)
243baa489faSSeongJae Park 		madvise(p, size, MADV_NOHUGEPAGE);
244baa489faSSeongJae Park 
245baa489faSSeongJae Park 	/*
246baa489faSSeongJae Park 	 * FOLL_TOUCH, in gup_test, is used as an either/or case: either
247baa489faSSeongJae Park 	 * fault pages in from the kernel via FOLL_TOUCH, or fault them
248baa489faSSeongJae Park 	 * in here, from user space. This allows comparison of performance
249baa489faSSeongJae Park 	 * between those two cases.
250baa489faSSeongJae Park 	 */
251baa489faSSeongJae Park 	if (touch) {
252baa489faSSeongJae Park 		gup.gup_flags |= FOLL_TOUCH;
253baa489faSSeongJae Park 	} else {
254af605d26SPeter Xu 		for (; (unsigned long)p < gup.addr + size; p += psize())
255baa489faSSeongJae Park 			p[0] = 0;
256baa489faSSeongJae Park 	}
257baa489faSSeongJae Park 
258baa489faSSeongJae Park 	tid = malloc(sizeof(pthread_t) * nthreads);
259baa489faSSeongJae Park 	assert(tid);
260baa489faSSeongJae Park 	for (i = 0; i < nthreads; i++) {
261baa489faSSeongJae Park 		ret = pthread_create(&tid[i], NULL, gup_thread, &gup);
262baa489faSSeongJae Park 		assert(ret == 0);
263baa489faSSeongJae Park 	}
264baa489faSSeongJae Park 	for (i = 0; i < nthreads; i++) {
265baa489faSSeongJae Park 		ret = pthread_join(tid[i], NULL);
266baa489faSSeongJae Park 		assert(ret == 0);
267baa489faSSeongJae Park 	}
268cb6e7caeSMuhammad Usama Anjum 
269baa489faSSeongJae Park 	free(tid);
270baa489faSSeongJae Park 
27169e545edSNathan Chancellor 	ksft_exit_pass();
272baa489faSSeongJae Park }
273