xref: /linux/tools/testing/selftests/mm/hugetlb-soft-offline.c (revision 9557b4376d02088a33e5f4116bcc324d35a3b64c)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Test soft offline behavior for HugeTLB pages:
4  * - if enable_soft_offline = 0, hugepages should stay intact and soft
5  *   offlining failed with EOPNOTSUPP.
6  * - if enable_soft_offline = 1, a hugepage should be dissolved and
7  *   nr_hugepages/free_hugepages should be reduced by 1.
8  *
9  * Before running, make sure more than 2 hugepages of default_hugepagesz
10  * are allocated. For example, if /proc/meminfo/Hugepagesize is 2048kB:
11  *   echo 8 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
12  */
13 
14 #define _GNU_SOURCE
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <unistd.h>
20 
21 #include <linux/magic.h>
22 #include <linux/memfd.h>
23 #include <sys/mman.h>
24 #include <sys/statfs.h>
25 #include <sys/types.h>
26 
27 #include "../kselftest.h"
28 
29 #ifndef MADV_SOFT_OFFLINE
30 #define MADV_SOFT_OFFLINE 101
31 #endif
32 
33 #define EPREFIX " !!! "
34 
35 static int do_soft_offline(int fd, size_t len, int expect_errno)
36 {
37 	char *filemap = NULL;
38 	char *hwp_addr = NULL;
39 	const unsigned long pagesize = getpagesize();
40 	int ret = 0;
41 
42 	if (ftruncate(fd, len) < 0) {
43 		ksft_perror(EPREFIX "ftruncate to len failed");
44 		return -1;
45 	}
46 
47 	filemap = mmap(NULL, len, PROT_READ | PROT_WRITE,
48 		       MAP_SHARED | MAP_POPULATE, fd, 0);
49 	if (filemap == MAP_FAILED) {
50 		ksft_perror(EPREFIX "mmap failed");
51 		ret = -1;
52 		goto untruncate;
53 	}
54 
55 	memset(filemap, 0xab, len);
56 	ksft_print_msg("Allocated %#lx bytes of hugetlb pages\n", len);
57 
58 	hwp_addr = filemap + len / 2;
59 	ret = madvise(hwp_addr, pagesize, MADV_SOFT_OFFLINE);
60 	ksft_print_msg("MADV_SOFT_OFFLINE %p ret=%d, errno=%d\n",
61 		       hwp_addr, ret, errno);
62 	if (ret != 0)
63 		ksft_perror(EPREFIX "madvise failed");
64 
65 	if (errno == expect_errno)
66 		ret = 0;
67 	else {
68 		ksft_print_msg("MADV_SOFT_OFFLINE should ret %d\n",
69 			       expect_errno);
70 		ret = -1;
71 	}
72 
73 	munmap(filemap, len);
74 untruncate:
75 	if (ftruncate(fd, 0) < 0)
76 		ksft_perror(EPREFIX "ftruncate back to 0 failed");
77 
78 	return ret;
79 }
80 
81 static int set_enable_soft_offline(int value)
82 {
83 	char cmd[256] = {0};
84 	FILE *cmdfile = NULL;
85 
86 	if (value != 0 && value != 1)
87 		return -EINVAL;
88 
89 	sprintf(cmd, "echo %d > /proc/sys/vm/enable_soft_offline", value);
90 	cmdfile = popen(cmd, "r");
91 
92 	if (cmdfile)
93 		ksft_print_msg("enable_soft_offline => %d\n", value);
94 	else {
95 		ksft_perror(EPREFIX "failed to set enable_soft_offline");
96 		return errno;
97 	}
98 
99 	pclose(cmdfile);
100 	return 0;
101 }
102 
103 static int read_nr_hugepages(unsigned long hugepage_size,
104 			     unsigned long *nr_hugepages)
105 {
106 	char buffer[256] = {0};
107 	char cmd[256] = {0};
108 
109 	sprintf(cmd, "cat /sys/kernel/mm/hugepages/hugepages-%ldkB/nr_hugepages",
110 		hugepage_size);
111 	FILE *cmdfile = popen(cmd, "r");
112 
113 	if (cmdfile == NULL) {
114 		ksft_perror(EPREFIX "failed to popen nr_hugepages");
115 		return -1;
116 	}
117 
118 	if (!fgets(buffer, sizeof(buffer), cmdfile)) {
119 		ksft_perror(EPREFIX "failed to read nr_hugepages");
120 		pclose(cmdfile);
121 		return -1;
122 	}
123 
124 	*nr_hugepages = atoll(buffer);
125 	pclose(cmdfile);
126 	return 0;
127 }
128 
129 static int create_hugetlbfs_file(struct statfs *file_stat)
130 {
131 	int fd;
132 
133 	fd = memfd_create("hugetlb_tmp", MFD_HUGETLB);
134 	if (fd < 0) {
135 		ksft_perror(EPREFIX "could not open hugetlbfs file");
136 		return -1;
137 	}
138 
139 	memset(file_stat, 0, sizeof(*file_stat));
140 	if (fstatfs(fd, file_stat)) {
141 		ksft_perror(EPREFIX "fstatfs failed");
142 		goto close;
143 	}
144 	if (file_stat->f_type != HUGETLBFS_MAGIC) {
145 		ksft_print_msg(EPREFIX "not hugetlbfs file\n");
146 		goto close;
147 	}
148 
149 	return fd;
150 close:
151 	close(fd);
152 	return -1;
153 }
154 
155 static void test_soft_offline_common(int enable_soft_offline)
156 {
157 	int fd;
158 	int expect_errno = enable_soft_offline ? 0 : EOPNOTSUPP;
159 	struct statfs file_stat;
160 	unsigned long hugepagesize_kb = 0;
161 	unsigned long nr_hugepages_before = 0;
162 	unsigned long nr_hugepages_after = 0;
163 	int ret;
164 
165 	ksft_print_msg("Test soft-offline when enabled_soft_offline=%d\n",
166 		       enable_soft_offline);
167 
168 	fd = create_hugetlbfs_file(&file_stat);
169 	if (fd < 0)
170 		ksft_exit_fail_msg("Failed to create hugetlbfs file\n");
171 
172 	hugepagesize_kb = file_stat.f_bsize / 1024;
173 	ksft_print_msg("Hugepagesize is %ldkB\n", hugepagesize_kb);
174 
175 	if (set_enable_soft_offline(enable_soft_offline) != 0) {
176 		close(fd);
177 		ksft_exit_fail_msg("Failed to set enable_soft_offline\n");
178 	}
179 
180 	if (read_nr_hugepages(hugepagesize_kb, &nr_hugepages_before) != 0) {
181 		close(fd);
182 		ksft_exit_fail_msg("Failed to read nr_hugepages\n");
183 	}
184 
185 	ksft_print_msg("Before MADV_SOFT_OFFLINE nr_hugepages=%ld\n",
186 		       nr_hugepages_before);
187 
188 	ret = do_soft_offline(fd, 2 * file_stat.f_bsize, expect_errno);
189 
190 	if (read_nr_hugepages(hugepagesize_kb, &nr_hugepages_after) != 0) {
191 		close(fd);
192 		ksft_exit_fail_msg("Failed to read nr_hugepages\n");
193 	}
194 
195 	ksft_print_msg("After MADV_SOFT_OFFLINE nr_hugepages=%ld\n",
196 		nr_hugepages_after);
197 
198 	// No need for the hugetlbfs file from now on.
199 	close(fd);
200 
201 	if (enable_soft_offline) {
202 		if (nr_hugepages_before != nr_hugepages_after + 1) {
203 			ksft_test_result_fail("MADV_SOFT_OFFLINE should reduced 1 hugepage\n");
204 			return;
205 		}
206 	} else {
207 		if (nr_hugepages_before != nr_hugepages_after) {
208 			ksft_test_result_fail("MADV_SOFT_OFFLINE reduced %lu hugepages\n",
209 				nr_hugepages_before - nr_hugepages_after);
210 			return;
211 		}
212 	}
213 
214 	ksft_test_result(ret == 0,
215 			 "Test soft-offline when enabled_soft_offline=%d\n",
216 			 enable_soft_offline);
217 }
218 
219 int main(int argc, char **argv)
220 {
221 	ksft_print_header();
222 	ksft_set_plan(2);
223 
224 	test_soft_offline_common(1);
225 	test_soft_offline_common(0);
226 
227 	ksft_finished();
228 }
229