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