1// SPDX-License-Identifier: GPL-2.0
2/* Test selecting other page sizes for mmap/shmget.
3
4   Before running this huge pages for each huge page size must have been
5   reserved.
6   For large pages beyond MAX_PAGE_ORDER (like 1GB on x86) boot options must
7   be used. 1GB wouldn't be tested if it isn't available.
8   Also shmmax must be increased.
9   And you need to run as root to work around some weird permissions in shm.
10   And nothing using huge pages should run in parallel.
11   When the program aborts you may need to clean up the shm segments with
12   ipcrm -m by hand, like this
13   sudo ipcs | awk '$1 == "0x00000000" {print $2}' | xargs -n1 sudo ipcrm -m
14   (warning this will remove all if someone else uses them) */
15
16#define _GNU_SOURCE 1
17#include <sys/mman.h>
18#include <stdlib.h>
19#include <stdio.h>
20#include <sys/ipc.h>
21#include <sys/shm.h>
22#include <sys/stat.h>
23#include <glob.h>
24#include <assert.h>
25#include <unistd.h>
26#include <stdarg.h>
27#include <string.h>
28#include "vm_util.h"
29#include "../kselftest.h"
30
31#define MAP_HUGE_2MB    (21 << MAP_HUGE_SHIFT)
32#define MAP_HUGE_1GB    (30 << MAP_HUGE_SHIFT)
33#define MAP_HUGE_SHIFT  26
34#define MAP_HUGE_MASK   0x3f
35#if !defined(MAP_HUGETLB)
36#define MAP_HUGETLB	0x40000
37#endif
38
39#define SHM_HUGETLB     04000   /* segment will use huge TLB pages */
40#define SHM_HUGE_SHIFT  26
41#define SHM_HUGE_MASK   0x3f
42#define SHM_HUGE_2MB    (21 << SHM_HUGE_SHIFT)
43#define SHM_HUGE_1GB    (30 << SHM_HUGE_SHIFT)
44
45#define NUM_PAGESIZES   5
46#define NUM_PAGES 4
47
48unsigned long page_sizes[NUM_PAGESIZES];
49int num_page_sizes;
50
51int ilog2(unsigned long v)
52{
53	int l = 0;
54	while ((1UL << l) < v)
55		l++;
56	return l;
57}
58
59void show(unsigned long ps)
60{
61	char buf[100];
62
63	if (ps == getpagesize())
64		return;
65
66	ksft_print_msg("%luMB: ", ps >> 20);
67
68	fflush(stdout);
69	snprintf(buf, sizeof buf,
70		"cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
71		ps >> 10);
72	system(buf);
73}
74
75unsigned long read_sysfs(int warn, char *fmt, ...)
76{
77	char *line = NULL;
78	size_t linelen = 0;
79	char buf[100];
80	FILE *f;
81	va_list ap;
82	unsigned long val = 0;
83
84	va_start(ap, fmt);
85	vsnprintf(buf, sizeof buf, fmt, ap);
86	va_end(ap);
87
88	f = fopen(buf, "r");
89	if (!f) {
90		if (warn)
91			ksft_print_msg("missing %s\n", buf);
92		return 0;
93	}
94	if (getline(&line, &linelen, f) > 0) {
95		sscanf(line, "%lu", &val);
96	}
97	fclose(f);
98	free(line);
99	return val;
100}
101
102unsigned long read_free(unsigned long ps)
103{
104	return read_sysfs(ps != getpagesize(),
105			  "/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
106			  ps >> 10);
107}
108
109void test_mmap(unsigned long size, unsigned flags)
110{
111	char *map;
112	unsigned long before, after;
113
114	before = read_free(size);
115	map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
116			MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, -1, 0);
117	if (map == MAP_FAILED)
118		ksft_exit_fail_msg("mmap: %s\n", strerror(errno));
119
120	memset(map, 0xff, size*NUM_PAGES);
121	after = read_free(size);
122
123	show(size);
124	ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
125			 "%s mmap\n", __func__);
126
127	if (munmap(map, size * NUM_PAGES))
128		ksft_exit_fail_msg("%s: unmap %s\n", __func__, strerror(errno));
129}
130
131void test_shmget(unsigned long size, unsigned flags)
132{
133	int id;
134	unsigned long before, after;
135	struct shm_info i;
136	char *map;
137
138	before = read_free(size);
139	id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
140	if (id < 0) {
141		if (errno == EPERM) {
142			ksft_test_result_skip("shmget requires root privileges: %s\n",
143					      strerror(errno));
144			return;
145		}
146		ksft_exit_fail_msg("shmget: %s\n", strerror(errno));
147	}
148
149	if (shmctl(id, SHM_INFO, (void *)&i) < 0)
150		ksft_exit_fail_msg("shmctl: %s\n", strerror(errno));
151
152	map = shmat(id, NULL, 0600);
153	if (map == MAP_FAILED)
154		ksft_exit_fail_msg("shmat: %s\n", strerror(errno));
155
156	shmctl(id, IPC_RMID, NULL);
157
158	memset(map, 0xff, size*NUM_PAGES);
159	after = read_free(size);
160
161	show(size);
162	ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
163			 "%s: mmap\n", __func__);
164	if (shmdt(map))
165		ksft_exit_fail_msg("%s: shmdt: %s\n", __func__, strerror(errno));
166}
167
168void find_pagesizes(void)
169{
170	unsigned long largest = getpagesize();
171	int i;
172	glob_t g;
173
174	glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
175	assert(g.gl_pathc <= NUM_PAGESIZES);
176	for (i = 0; (i < g.gl_pathc) && (num_page_sizes < NUM_PAGESIZES); i++) {
177		sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
178				&page_sizes[num_page_sizes]);
179		page_sizes[num_page_sizes] <<= 10;
180		ksft_print_msg("Found %luMB\n", page_sizes[i] >> 20);
181
182		if (page_sizes[num_page_sizes] > largest)
183			largest = page_sizes[i];
184
185		if (read_free(page_sizes[num_page_sizes]) >= NUM_PAGES)
186			num_page_sizes++;
187		else
188			ksft_print_msg("SKIP for size %lu MB as not enough huge pages, need %u\n",
189				       page_sizes[num_page_sizes] >> 20, NUM_PAGES);
190	}
191	globfree(&g);
192
193	if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest)
194		ksft_exit_fail_msg("Please do echo %lu > /proc/sys/kernel/shmmax",
195				   largest * NUM_PAGES);
196
197#if defined(__x86_64__)
198	if (largest != 1U<<30) {
199		ksft_exit_fail_msg("No GB pages available on x86-64\n"
200				   "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
201	}
202#endif
203}
204
205int main(void)
206{
207	unsigned default_hps = default_huge_page_size();
208	int i;
209
210	ksft_print_header();
211
212	find_pagesizes();
213
214	if (!num_page_sizes)
215		ksft_finished();
216
217	ksft_set_plan(2 * num_page_sizes + 3);
218
219	for (i = 0; i < num_page_sizes; i++) {
220		unsigned long ps = page_sizes[i];
221		int arg = ilog2(ps) << MAP_HUGE_SHIFT;
222
223		ksft_print_msg("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
224		test_mmap(ps, MAP_HUGETLB | arg);
225	}
226
227	ksft_print_msg("Testing default huge mmap\n");
228	test_mmap(default_hps, MAP_HUGETLB);
229
230	ksft_print_msg("Testing non-huge shmget\n");
231	test_shmget(getpagesize(), 0);
232
233	for (i = 0; i < num_page_sizes; i++) {
234		unsigned long ps = page_sizes[i];
235		int arg = ilog2(ps) << SHM_HUGE_SHIFT;
236		ksft_print_msg("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
237		test_shmget(ps, SHM_HUGETLB | arg);
238	}
239
240	ksft_print_msg("default huge shmget\n");
241	test_shmget(default_hps, SHM_HUGETLB);
242
243	ksft_finished();
244}
245