1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /*
4 * Copyright (c) Zilogic Systems Pvt. Ltd., 2018
5 * Email: code@zilogic.com
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
15 * the GNU General Public License for more details.
16 */
17
18 /*
19 * Test: Validating memfd_create() with MFD_HUGETLB flag.
20 *
21 * Test case 1: --WRITE CALL IN HUGEPAGES TEST--
22 * Huge pages are write protected. Any writes to
23 * the file should return EINVAL error.
24 *
25 * Test case 2: --PAGE SIZE OF CREATED FILE TEST--
26 * Default huge page sized pages are created with
27 * MFD_HUGETLB flag. Any attempt to unmap memory-mapped
28 * huge pages with an unmapping length less than
29 * huge page size should return EINVAL error.
30 *
31 * Test case 3: --HUGEPAGE ALLOCATION LIMIT TEST--
32 * Number of huge pages currently available to use should be
33 * atmost total number of allowed huge pages. Memory-mapping
34 * more than allowed huge pages should return ENOMEM error.
35 */
36
37 #define _GNU_SOURCE
38
39 #include "tst_test.h"
40 #include "memfd_create_common.h"
41
42 #include <stdio.h>
43 #include <errno.h>
44
45 #define TOTAL_HP_PATH "/proc/sys/vm/nr_hugepages"
46 #define MEMINFO_PATH "/proc/meminfo"
47 #define FREE_HP "HugePages_Free:\t%ld"
48 #define DEFAULT_HPS "Hugepagesize:\t%ld kB"
49
50 static int hugepages_allocated;
51 static long og_total_pages;
52
check_huge_mmapable(int fd,unsigned long size)53 static void *check_huge_mmapable(int fd, unsigned long size)
54 {
55 void *mem;
56
57 mem = SAFE_MMAP(NULL, size, PROT_WRITE, MAP_PRIVATE, fd, 0);
58
59 memset((char *)mem, 0, 1);
60
61 tst_res(TINFO,
62 "mmap(%p, %lu, %d, %d, %d, %d) succeeded",
63 NULL, size, PROT_WRITE, MAP_PRIVATE, fd, 0);
64
65 return mem;
66 }
67
test_write_protect(int fd)68 static void test_write_protect(int fd)
69 {
70 ssize_t ret;
71 char test_str[] = "LTP";
72
73 ret = write(fd, test_str, strlen(test_str));
74 if (ret < 0) {
75 if (errno != EINVAL) {
76 tst_res(TFAIL | TERRNO,
77 "write(%d, \"%s\", %zu) didn't fail as expected\n",
78 fd, test_str, strlen(test_str));
79 return;
80 }
81 } else {
82 tst_res(TFAIL,
83 "write(%d, \"%s\", %zu) succeeded unexpectedly\n",
84 fd, test_str, strlen(test_str));
85 return;
86 }
87
88 tst_res(TPASS,
89 "write(%d, \"%s\", %zu) failed as expected\n",
90 fd, test_str, strlen(test_str));
91 }
92
test_def_pagesize(int fd)93 static void test_def_pagesize(int fd)
94 {
95 unsigned int i;
96 int unmap_size;
97 int ret;
98 long hps;
99 void *mem;
100
101 SAFE_FILE_LINES_SCANF(MEMINFO_PATH, DEFAULT_HPS, &hps);
102 hps = hps << 10;
103 unmap_size = hps / 4;
104 mem = check_huge_mmapable(fd, hps);
105
106 for (i = unmap_size; i < hps; i += unmap_size) {
107 ret = munmap(mem, i);
108 if (ret == -1) {
109 if (errno == EINVAL) {
110 tst_res(TINFO,
111 "munmap(%p, %dkB) failed as expected",
112 mem, i/1024);
113 } else {
114 tst_res(TFAIL | TERRNO,
115 "munmap(%p, %dkB) failed unexpectedly",
116 mem, i/1024);
117 return;
118 }
119 } else {
120 tst_res(TFAIL,
121 "munmap(%p, %dkB) suceeded unexpectedly\n",
122 mem, i);
123 return;
124 }
125 }
126
127 SAFE_MUNMAP(mem, hps);
128
129 tst_res(TPASS,
130 "munmap() fails for page sizes less than %ldkB\n", hps/1024);
131 }
132
test_max_hugepages(int fd)133 static void test_max_hugepages(int fd)
134 {
135 int new_fd;
136 long hps;
137 long free_pages;
138 void *mem;
139 void *new_mem;
140
141 SAFE_FILE_LINES_SCANF(MEMINFO_PATH, FREE_HP, &free_pages);
142 SAFE_FILE_LINES_SCANF(MEMINFO_PATH, DEFAULT_HPS, &hps);
143 hps = hps << 10;
144 mem = check_huge_mmapable(fd, free_pages * hps);
145
146 new_fd = sys_memfd_create("new_file", MFD_HUGETLB);
147 if (new_fd < 0)
148 tst_brk(TFAIL | TERRNO, "memfd_create() failed");
149 tst_res(TINFO, "memfd_create() succeeded");
150
151 new_mem = mmap(NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
152 if (new_mem == MAP_FAILED) {
153 if (errno == ENOMEM)
154 tst_res(TPASS,
155 "mmap(%p, %lu, %d, %d, %d, %d) failed as expected",
156 NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
157 else
158 tst_res(TFAIL | TERRNO,
159 "mmap(%p, %lu, %d, %d, %d, %d) failed unexpectedly",
160 NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
161 } else {
162 tst_res(TFAIL,
163 "mmap(%p, %lu, %d, %d, %d, %d) succeeded",
164 NULL, hps, 0, MAP_PRIVATE, new_fd, 0);
165 SAFE_MUNMAP(new_mem, hps);
166 }
167
168 SAFE_CLOSE(new_fd);
169
170 SAFE_MUNMAP(mem, free_pages * hps);
171 }
172
173 static const struct tcase {
174 void (*func)(int fd);
175 const char *desc;
176 } tcases[] = {
177 {&test_write_protect, "--TESTING WRITE CALL IN HUGEPAGES--"},
178 {&test_def_pagesize, "--TESTING PAGE SIZE OF CREATED FILE--"},
179 {&test_max_hugepages, "--TESTING HUGEPAGE ALLOCATION LIMIT--"},
180 };
181
memfd_huge_controller(unsigned int n)182 static void memfd_huge_controller(unsigned int n)
183 {
184 int fd;
185 const struct tcase *tc;
186
187 tc = &tcases[n];
188
189 tst_res(TINFO, "%s", tc->desc);
190
191 fd = sys_memfd_create("test_file", MFD_HUGETLB);
192 if (fd < 0)
193 tst_brk(TFAIL | TERRNO, "memfd_create() failed");
194 tst_res(TINFO, "memfd_create() succeeded");
195
196 tc->func(fd);
197
198 SAFE_CLOSE(fd);
199 }
200
setup(void)201 static void setup(void)
202 {
203 char buf[8];
204 int fd;
205 long free_pages;
206 long total_pages;
207
208 if (access(MEMINFO_PATH, F_OK) ||
209 access("/sys/kernel/mm/hugepages", F_OK) ||
210 access(TOTAL_HP_PATH, F_OK))
211 tst_brk(TCONF, "Huge page is not supported");
212
213 SAFE_FILE_LINES_SCANF(MEMINFO_PATH, FREE_HP, &free_pages);
214 if (free_pages > 0)
215 return;
216
217 SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &og_total_pages);
218 sprintf(buf, "%ld", og_total_pages + 1);
219
220 fd = open(TOTAL_HP_PATH, O_RDWR | O_TRUNC);
221
222 if (write(fd, buf, strlen(buf)) == -1)
223 tst_brk(TCONF | TERRNO,
224 "write() fail: Hugepage allocation failed");
225
226 SAFE_CLOSE(fd);
227
228 SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &total_pages);
229 if (total_pages != (og_total_pages + 1))
230 tst_brk(TCONF, "Hugepage allocation failed");
231
232 hugepages_allocated = 1;
233 }
234
cleanup(void)235 static void cleanup(void)
236 {
237 char buf[8];
238 int fd;
239 long total_pages;
240
241 if (hugepages_allocated == 0)
242 return;
243
244 sprintf(buf, "%ld", og_total_pages);
245
246 fd = open(TOTAL_HP_PATH, O_RDWR | O_TRUNC);
247
248 if (write(fd, buf, strlen(buf)) == -1)
249 tst_brk(TCONF | TERRNO, "Clean-up failed: write() failed");
250
251 SAFE_CLOSE(fd);
252
253 SAFE_FILE_LINES_SCANF(TOTAL_HP_PATH, "%ld", &total_pages);
254 if (og_total_pages != total_pages)
255 tst_brk(TCONF, "Clean-up failed");
256 }
257
258 static struct tst_test test = {
259 .setup = setup,
260 .test = memfd_huge_controller,
261 .tcnt = ARRAY_SIZE(tcases),
262 .needs_root = 1,
263 .min_kver = "4.14",
264 .cleanup = cleanup,
265 };
266