1 // SPDX-License-Identifier: GPL-2.0
2 #include <fcntl.h>
3 #include <limits.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 
9 #include "thp_settings.h"
10 
11 #define THP_SYSFS "/sys/kernel/mm/transparent_hugepage/"
12 #define MAX_SETTINGS_DEPTH 4
13 static struct thp_settings settings_stack[MAX_SETTINGS_DEPTH];
14 static int settings_index;
15 static struct thp_settings saved_settings;
16 static char dev_queue_read_ahead_path[PATH_MAX];
17 
18 static const char * const thp_enabled_strings[] = {
19 	"never",
20 	"always",
21 	"inherit",
22 	"madvise",
23 	NULL
24 };
25 
26 static const char * const thp_defrag_strings[] = {
27 	"always",
28 	"defer",
29 	"defer+madvise",
30 	"madvise",
31 	"never",
32 	NULL
33 };
34 
35 static const char * const shmem_enabled_strings[] = {
36 	"never",
37 	"always",
38 	"within_size",
39 	"advise",
40 	"inherit",
41 	"deny",
42 	"force",
43 	NULL
44 };
45 
read_file(const char * path,char * buf,size_t buflen)46 int read_file(const char *path, char *buf, size_t buflen)
47 {
48 	int fd;
49 	ssize_t numread;
50 
51 	fd = open(path, O_RDONLY);
52 	if (fd == -1)
53 		return 0;
54 
55 	numread = read(fd, buf, buflen - 1);
56 	if (numread < 1) {
57 		close(fd);
58 		return 0;
59 	}
60 
61 	buf[numread] = '\0';
62 	close(fd);
63 
64 	return (unsigned int) numread;
65 }
66 
write_file(const char * path,const char * buf,size_t buflen)67 int write_file(const char *path, const char *buf, size_t buflen)
68 {
69 	int fd;
70 	ssize_t numwritten;
71 
72 	fd = open(path, O_WRONLY);
73 	if (fd == -1) {
74 		printf("open(%s)\n", path);
75 		exit(EXIT_FAILURE);
76 		return 0;
77 	}
78 
79 	numwritten = write(fd, buf, buflen - 1);
80 	close(fd);
81 	if (numwritten < 1) {
82 		printf("write(%s)\n", buf);
83 		exit(EXIT_FAILURE);
84 		return 0;
85 	}
86 
87 	return (unsigned int) numwritten;
88 }
89 
read_num(const char * path)90 const unsigned long read_num(const char *path)
91 {
92 	char buf[21];
93 
94 	if (read_file(path, buf, sizeof(buf)) < 0) {
95 		perror("read_file()");
96 		exit(EXIT_FAILURE);
97 	}
98 
99 	return strtoul(buf, NULL, 10);
100 }
101 
write_num(const char * path,unsigned long num)102 void write_num(const char *path, unsigned long num)
103 {
104 	char buf[21];
105 
106 	sprintf(buf, "%ld", num);
107 	if (!write_file(path, buf, strlen(buf) + 1)) {
108 		perror(path);
109 		exit(EXIT_FAILURE);
110 	}
111 }
112 
thp_read_string(const char * name,const char * const strings[])113 int thp_read_string(const char *name, const char * const strings[])
114 {
115 	char path[PATH_MAX];
116 	char buf[256];
117 	char *c;
118 	int ret;
119 
120 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
121 	if (ret >= PATH_MAX) {
122 		printf("%s: Pathname is too long\n", __func__);
123 		exit(EXIT_FAILURE);
124 	}
125 
126 	if (!read_file(path, buf, sizeof(buf))) {
127 		perror(path);
128 		exit(EXIT_FAILURE);
129 	}
130 
131 	c = strchr(buf, '[');
132 	if (!c) {
133 		printf("%s: Parse failure\n", __func__);
134 		exit(EXIT_FAILURE);
135 	}
136 
137 	c++;
138 	memmove(buf, c, sizeof(buf) - (c - buf));
139 
140 	c = strchr(buf, ']');
141 	if (!c) {
142 		printf("%s: Parse failure\n", __func__);
143 		exit(EXIT_FAILURE);
144 	}
145 	*c = '\0';
146 
147 	ret = 0;
148 	while (strings[ret]) {
149 		if (!strcmp(strings[ret], buf))
150 			return ret;
151 		ret++;
152 	}
153 
154 	printf("Failed to parse %s\n", name);
155 	exit(EXIT_FAILURE);
156 }
157 
thp_write_string(const char * name,const char * val)158 void thp_write_string(const char *name, const char *val)
159 {
160 	char path[PATH_MAX];
161 	int ret;
162 
163 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
164 	if (ret >= PATH_MAX) {
165 		printf("%s: Pathname is too long\n", __func__);
166 		exit(EXIT_FAILURE);
167 	}
168 
169 	if (!write_file(path, val, strlen(val) + 1)) {
170 		perror(path);
171 		exit(EXIT_FAILURE);
172 	}
173 }
174 
thp_read_num(const char * name)175 const unsigned long thp_read_num(const char *name)
176 {
177 	char path[PATH_MAX];
178 	int ret;
179 
180 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
181 	if (ret >= PATH_MAX) {
182 		printf("%s: Pathname is too long\n", __func__);
183 		exit(EXIT_FAILURE);
184 	}
185 	return read_num(path);
186 }
187 
thp_write_num(const char * name,unsigned long num)188 void thp_write_num(const char *name, unsigned long num)
189 {
190 	char path[PATH_MAX];
191 	int ret;
192 
193 	ret = snprintf(path, PATH_MAX, THP_SYSFS "%s", name);
194 	if (ret >= PATH_MAX) {
195 		printf("%s: Pathname is too long\n", __func__);
196 		exit(EXIT_FAILURE);
197 	}
198 	write_num(path, num);
199 }
200 
thp_read_settings(struct thp_settings * settings)201 void thp_read_settings(struct thp_settings *settings)
202 {
203 	unsigned long orders = thp_supported_orders();
204 	unsigned long shmem_orders = thp_shmem_supported_orders();
205 	char path[PATH_MAX];
206 	int i;
207 
208 	*settings = (struct thp_settings) {
209 		.thp_enabled = thp_read_string("enabled", thp_enabled_strings),
210 		.thp_defrag = thp_read_string("defrag", thp_defrag_strings),
211 		.shmem_enabled =
212 			thp_read_string("shmem_enabled", shmem_enabled_strings),
213 		.use_zero_page = thp_read_num("use_zero_page"),
214 	};
215 	settings->khugepaged = (struct khugepaged_settings) {
216 		.defrag = thp_read_num("khugepaged/defrag"),
217 		.alloc_sleep_millisecs =
218 			thp_read_num("khugepaged/alloc_sleep_millisecs"),
219 		.scan_sleep_millisecs =
220 			thp_read_num("khugepaged/scan_sleep_millisecs"),
221 		.max_ptes_none = thp_read_num("khugepaged/max_ptes_none"),
222 		.max_ptes_swap = thp_read_num("khugepaged/max_ptes_swap"),
223 		.max_ptes_shared = thp_read_num("khugepaged/max_ptes_shared"),
224 		.pages_to_scan = thp_read_num("khugepaged/pages_to_scan"),
225 	};
226 	if (dev_queue_read_ahead_path[0])
227 		settings->read_ahead_kb = read_num(dev_queue_read_ahead_path);
228 
229 	for (i = 0; i < NR_ORDERS; i++) {
230 		if (!((1 << i) & orders)) {
231 			settings->hugepages[i].enabled = THP_NEVER;
232 			continue;
233 		}
234 		snprintf(path, PATH_MAX, "hugepages-%ukB/enabled",
235 			(getpagesize() >> 10) << i);
236 		settings->hugepages[i].enabled =
237 			thp_read_string(path, thp_enabled_strings);
238 	}
239 
240 	for (i = 0; i < NR_ORDERS; i++) {
241 		if (!((1 << i) & shmem_orders)) {
242 			settings->shmem_hugepages[i].enabled = SHMEM_NEVER;
243 			continue;
244 		}
245 		snprintf(path, PATH_MAX, "hugepages-%ukB/shmem_enabled",
246 			(getpagesize() >> 10) << i);
247 		settings->shmem_hugepages[i].enabled =
248 			thp_read_string(path, shmem_enabled_strings);
249 	}
250 }
251 
thp_write_settings(struct thp_settings * settings)252 void thp_write_settings(struct thp_settings *settings)
253 {
254 	struct khugepaged_settings *khugepaged = &settings->khugepaged;
255 	unsigned long orders = thp_supported_orders();
256 	unsigned long shmem_orders = thp_shmem_supported_orders();
257 	char path[PATH_MAX];
258 	int enabled;
259 	int i;
260 
261 	thp_write_string("enabled", thp_enabled_strings[settings->thp_enabled]);
262 	thp_write_string("defrag", thp_defrag_strings[settings->thp_defrag]);
263 	thp_write_string("shmem_enabled",
264 			shmem_enabled_strings[settings->shmem_enabled]);
265 	thp_write_num("use_zero_page", settings->use_zero_page);
266 
267 	thp_write_num("khugepaged/defrag", khugepaged->defrag);
268 	thp_write_num("khugepaged/alloc_sleep_millisecs",
269 			khugepaged->alloc_sleep_millisecs);
270 	thp_write_num("khugepaged/scan_sleep_millisecs",
271 			khugepaged->scan_sleep_millisecs);
272 	thp_write_num("khugepaged/max_ptes_none", khugepaged->max_ptes_none);
273 	thp_write_num("khugepaged/max_ptes_swap", khugepaged->max_ptes_swap);
274 	thp_write_num("khugepaged/max_ptes_shared", khugepaged->max_ptes_shared);
275 	thp_write_num("khugepaged/pages_to_scan", khugepaged->pages_to_scan);
276 
277 	if (dev_queue_read_ahead_path[0])
278 		write_num(dev_queue_read_ahead_path, settings->read_ahead_kb);
279 
280 	for (i = 0; i < NR_ORDERS; i++) {
281 		if (!((1 << i) & orders))
282 			continue;
283 		snprintf(path, PATH_MAX, "hugepages-%ukB/enabled",
284 			(getpagesize() >> 10) << i);
285 		enabled = settings->hugepages[i].enabled;
286 		thp_write_string(path, thp_enabled_strings[enabled]);
287 	}
288 
289 	for (i = 0; i < NR_ORDERS; i++) {
290 		if (!((1 << i) & shmem_orders))
291 			continue;
292 		snprintf(path, PATH_MAX, "hugepages-%ukB/shmem_enabled",
293 			(getpagesize() >> 10) << i);
294 		enabled = settings->shmem_hugepages[i].enabled;
295 		thp_write_string(path, shmem_enabled_strings[enabled]);
296 	}
297 }
298 
thp_current_settings(void)299 struct thp_settings *thp_current_settings(void)
300 {
301 	if (!settings_index) {
302 		printf("Fail: No settings set");
303 		exit(EXIT_FAILURE);
304 	}
305 	return settings_stack + settings_index - 1;
306 }
307 
thp_push_settings(struct thp_settings * settings)308 void thp_push_settings(struct thp_settings *settings)
309 {
310 	if (settings_index >= MAX_SETTINGS_DEPTH) {
311 		printf("Fail: Settings stack exceeded");
312 		exit(EXIT_FAILURE);
313 	}
314 	settings_stack[settings_index++] = *settings;
315 	thp_write_settings(thp_current_settings());
316 }
317 
thp_pop_settings(void)318 void thp_pop_settings(void)
319 {
320 	if (settings_index <= 0) {
321 		printf("Fail: Settings stack empty");
322 		exit(EXIT_FAILURE);
323 	}
324 	--settings_index;
325 	thp_write_settings(thp_current_settings());
326 }
327 
thp_restore_settings(void)328 void thp_restore_settings(void)
329 {
330 	thp_write_settings(&saved_settings);
331 }
332 
thp_save_settings(void)333 void thp_save_settings(void)
334 {
335 	thp_read_settings(&saved_settings);
336 }
337 
thp_set_read_ahead_path(char * path)338 void thp_set_read_ahead_path(char *path)
339 {
340 	if (!path) {
341 		dev_queue_read_ahead_path[0] = '\0';
342 		return;
343 	}
344 
345 	strncpy(dev_queue_read_ahead_path, path,
346 		sizeof(dev_queue_read_ahead_path));
347 	dev_queue_read_ahead_path[sizeof(dev_queue_read_ahead_path) - 1] = '\0';
348 }
349 
__thp_supported_orders(bool is_shmem)350 static unsigned long __thp_supported_orders(bool is_shmem)
351 {
352 	unsigned long orders = 0;
353 	char path[PATH_MAX];
354 	char buf[256];
355 	int ret, i;
356 	char anon_dir[] = "enabled";
357 	char shmem_dir[] = "shmem_enabled";
358 
359 	for (i = 0; i < NR_ORDERS; i++) {
360 		ret = snprintf(path, PATH_MAX, THP_SYSFS "hugepages-%ukB/%s",
361 			       (getpagesize() >> 10) << i, is_shmem ? shmem_dir : anon_dir);
362 		if (ret >= PATH_MAX) {
363 			printf("%s: Pathname is too long\n", __func__);
364 			exit(EXIT_FAILURE);
365 		}
366 
367 		ret = read_file(path, buf, sizeof(buf));
368 		if (ret)
369 			orders |= 1UL << i;
370 	}
371 
372 	return orders;
373 }
374 
thp_supported_orders(void)375 unsigned long thp_supported_orders(void)
376 {
377 	return __thp_supported_orders(false);
378 }
379 
thp_shmem_supported_orders(void)380 unsigned long thp_shmem_supported_orders(void)
381 {
382 	return __thp_supported_orders(true);
383 }
384