1*b2466bb3SRyan Roberts // SPDX-License-Identifier: GPL-2.0-only 2*b2466bb3SRyan Roberts 3*b2466bb3SRyan Roberts #define _GNU_SOURCE 4*b2466bb3SRyan Roberts #include <stdbool.h> 5*b2466bb3SRyan Roberts #include <stdint.h> 6*b2466bb3SRyan Roberts #include <fcntl.h> 7*b2466bb3SRyan Roberts #include <assert.h> 8*b2466bb3SRyan Roberts #include <linux/mman.h> 9*b2466bb3SRyan Roberts #include <sys/mman.h> 10*b2466bb3SRyan Roberts #include "../kselftest.h" 11*b2466bb3SRyan Roberts #include "thp_settings.h" 12*b2466bb3SRyan Roberts #include "uffd-common.h" 13*b2466bb3SRyan Roberts 14*b2466bb3SRyan Roberts static int pagemap_fd; 15*b2466bb3SRyan Roberts static size_t pagesize; 16*b2466bb3SRyan Roberts static int nr_pagesizes = 1; 17*b2466bb3SRyan Roberts static int nr_thpsizes; 18*b2466bb3SRyan Roberts static size_t thpsizes[20]; 19*b2466bb3SRyan Roberts static int nr_hugetlbsizes; 20*b2466bb3SRyan Roberts static size_t hugetlbsizes[10]; 21*b2466bb3SRyan Roberts 22*b2466bb3SRyan Roberts static int sz2ord(size_t size) 23*b2466bb3SRyan Roberts { 24*b2466bb3SRyan Roberts return __builtin_ctzll(size / pagesize); 25*b2466bb3SRyan Roberts } 26*b2466bb3SRyan Roberts 27*b2466bb3SRyan Roberts static int detect_thp_sizes(size_t sizes[], int max) 28*b2466bb3SRyan Roberts { 29*b2466bb3SRyan Roberts int count = 0; 30*b2466bb3SRyan Roberts unsigned long orders; 31*b2466bb3SRyan Roberts size_t kb; 32*b2466bb3SRyan Roberts int i; 33*b2466bb3SRyan Roberts 34*b2466bb3SRyan Roberts /* thp not supported at all. */ 35*b2466bb3SRyan Roberts if (!read_pmd_pagesize()) 36*b2466bb3SRyan Roberts return 0; 37*b2466bb3SRyan Roberts 38*b2466bb3SRyan Roberts orders = thp_supported_orders(); 39*b2466bb3SRyan Roberts 40*b2466bb3SRyan Roberts for (i = 0; orders && count < max; i++) { 41*b2466bb3SRyan Roberts if (!(orders & (1UL << i))) 42*b2466bb3SRyan Roberts continue; 43*b2466bb3SRyan Roberts orders &= ~(1UL << i); 44*b2466bb3SRyan Roberts kb = (pagesize >> 10) << i; 45*b2466bb3SRyan Roberts sizes[count++] = kb * 1024; 46*b2466bb3SRyan Roberts ksft_print_msg("[INFO] detected THP size: %zu KiB\n", kb); 47*b2466bb3SRyan Roberts } 48*b2466bb3SRyan Roberts 49*b2466bb3SRyan Roberts return count; 50*b2466bb3SRyan Roberts } 51*b2466bb3SRyan Roberts 52*b2466bb3SRyan Roberts static void *mmap_aligned(size_t size, int prot, int flags) 53*b2466bb3SRyan Roberts { 54*b2466bb3SRyan Roberts size_t mmap_size = size * 2; 55*b2466bb3SRyan Roberts char *mmap_mem, *mem; 56*b2466bb3SRyan Roberts 57*b2466bb3SRyan Roberts mmap_mem = mmap(NULL, mmap_size, prot, flags, -1, 0); 58*b2466bb3SRyan Roberts if (mmap_mem == MAP_FAILED) 59*b2466bb3SRyan Roberts return mmap_mem; 60*b2466bb3SRyan Roberts 61*b2466bb3SRyan Roberts mem = (char *)(((uintptr_t)mmap_mem + size - 1) & ~(size - 1)); 62*b2466bb3SRyan Roberts munmap(mmap_mem, mem - mmap_mem); 63*b2466bb3SRyan Roberts munmap(mem + size, mmap_mem + mmap_size - mem - size); 64*b2466bb3SRyan Roberts 65*b2466bb3SRyan Roberts return mem; 66*b2466bb3SRyan Roberts } 67*b2466bb3SRyan Roberts 68*b2466bb3SRyan Roberts static void *alloc_one_folio(size_t size, bool private, bool hugetlb) 69*b2466bb3SRyan Roberts { 70*b2466bb3SRyan Roberts bool thp = !hugetlb && size > pagesize; 71*b2466bb3SRyan Roberts int flags = MAP_ANONYMOUS; 72*b2466bb3SRyan Roberts int prot = PROT_READ | PROT_WRITE; 73*b2466bb3SRyan Roberts char *mem, *addr; 74*b2466bb3SRyan Roberts 75*b2466bb3SRyan Roberts assert((size & (size - 1)) == 0); 76*b2466bb3SRyan Roberts 77*b2466bb3SRyan Roberts if (private) 78*b2466bb3SRyan Roberts flags |= MAP_PRIVATE; 79*b2466bb3SRyan Roberts else 80*b2466bb3SRyan Roberts flags |= MAP_SHARED; 81*b2466bb3SRyan Roberts 82*b2466bb3SRyan Roberts /* 83*b2466bb3SRyan Roberts * For THP, we must explicitly enable the THP size, allocate twice the 84*b2466bb3SRyan Roberts * required space then manually align. 85*b2466bb3SRyan Roberts */ 86*b2466bb3SRyan Roberts if (thp) { 87*b2466bb3SRyan Roberts struct thp_settings settings = *thp_current_settings(); 88*b2466bb3SRyan Roberts 89*b2466bb3SRyan Roberts if (private) 90*b2466bb3SRyan Roberts settings.hugepages[sz2ord(size)].enabled = THP_ALWAYS; 91*b2466bb3SRyan Roberts else 92*b2466bb3SRyan Roberts settings.shmem_hugepages[sz2ord(size)].enabled = SHMEM_ALWAYS; 93*b2466bb3SRyan Roberts 94*b2466bb3SRyan Roberts thp_push_settings(&settings); 95*b2466bb3SRyan Roberts 96*b2466bb3SRyan Roberts mem = mmap_aligned(size, prot, flags); 97*b2466bb3SRyan Roberts } else { 98*b2466bb3SRyan Roberts if (hugetlb) { 99*b2466bb3SRyan Roberts flags |= MAP_HUGETLB; 100*b2466bb3SRyan Roberts flags |= __builtin_ctzll(size) << MAP_HUGE_SHIFT; 101*b2466bb3SRyan Roberts } 102*b2466bb3SRyan Roberts 103*b2466bb3SRyan Roberts mem = mmap(NULL, size, prot, flags, -1, 0); 104*b2466bb3SRyan Roberts } 105*b2466bb3SRyan Roberts 106*b2466bb3SRyan Roberts if (mem == MAP_FAILED) { 107*b2466bb3SRyan Roberts mem = NULL; 108*b2466bb3SRyan Roberts goto out; 109*b2466bb3SRyan Roberts } 110*b2466bb3SRyan Roberts 111*b2466bb3SRyan Roberts assert(((uintptr_t)mem & (size - 1)) == 0); 112*b2466bb3SRyan Roberts 113*b2466bb3SRyan Roberts /* 114*b2466bb3SRyan Roberts * Populate the folio by writing the first byte and check that all pages 115*b2466bb3SRyan Roberts * are populated. Finally set the whole thing to non-zero data to avoid 116*b2466bb3SRyan Roberts * kernel from mapping it back to the zero page. 117*b2466bb3SRyan Roberts */ 118*b2466bb3SRyan Roberts mem[0] = 1; 119*b2466bb3SRyan Roberts for (addr = mem; addr < mem + size; addr += pagesize) { 120*b2466bb3SRyan Roberts if (!pagemap_is_populated(pagemap_fd, addr)) { 121*b2466bb3SRyan Roberts munmap(mem, size); 122*b2466bb3SRyan Roberts mem = NULL; 123*b2466bb3SRyan Roberts goto out; 124*b2466bb3SRyan Roberts } 125*b2466bb3SRyan Roberts } 126*b2466bb3SRyan Roberts memset(mem, 1, size); 127*b2466bb3SRyan Roberts out: 128*b2466bb3SRyan Roberts if (thp) 129*b2466bb3SRyan Roberts thp_pop_settings(); 130*b2466bb3SRyan Roberts 131*b2466bb3SRyan Roberts return mem; 132*b2466bb3SRyan Roberts } 133*b2466bb3SRyan Roberts 134*b2466bb3SRyan Roberts static bool check_uffd_wp_state(void *mem, size_t size, bool expect) 135*b2466bb3SRyan Roberts { 136*b2466bb3SRyan Roberts uint64_t pte; 137*b2466bb3SRyan Roberts void *addr; 138*b2466bb3SRyan Roberts 139*b2466bb3SRyan Roberts for (addr = mem; addr < mem + size; addr += pagesize) { 140*b2466bb3SRyan Roberts pte = pagemap_get_entry(pagemap_fd, addr); 141*b2466bb3SRyan Roberts if (!!(pte & PM_UFFD_WP) != expect) { 142*b2466bb3SRyan Roberts ksft_test_result_fail("uffd-wp not %s for pte %lu!\n", 143*b2466bb3SRyan Roberts expect ? "set" : "clear", 144*b2466bb3SRyan Roberts (addr - mem) / pagesize); 145*b2466bb3SRyan Roberts return false; 146*b2466bb3SRyan Roberts } 147*b2466bb3SRyan Roberts } 148*b2466bb3SRyan Roberts 149*b2466bb3SRyan Roberts return true; 150*b2466bb3SRyan Roberts } 151*b2466bb3SRyan Roberts 152*b2466bb3SRyan Roberts static bool range_is_swapped(void *addr, size_t size) 153*b2466bb3SRyan Roberts { 154*b2466bb3SRyan Roberts for (; size; addr += pagesize, size -= pagesize) 155*b2466bb3SRyan Roberts if (!pagemap_is_swapped(pagemap_fd, addr)) 156*b2466bb3SRyan Roberts return false; 157*b2466bb3SRyan Roberts return true; 158*b2466bb3SRyan Roberts } 159*b2466bb3SRyan Roberts 160*b2466bb3SRyan Roberts static void test_one_folio(size_t size, bool private, bool swapout, bool hugetlb) 161*b2466bb3SRyan Roberts { 162*b2466bb3SRyan Roberts struct uffdio_writeprotect wp_prms; 163*b2466bb3SRyan Roberts uint64_t features = 0; 164*b2466bb3SRyan Roberts void *addr = NULL; 165*b2466bb3SRyan Roberts void *mem = NULL; 166*b2466bb3SRyan Roberts 167*b2466bb3SRyan Roberts assert(!(hugetlb && swapout)); 168*b2466bb3SRyan Roberts 169*b2466bb3SRyan Roberts ksft_print_msg("[RUN] %s(size=%zu, private=%s, swapout=%s, hugetlb=%s)\n", 170*b2466bb3SRyan Roberts __func__, 171*b2466bb3SRyan Roberts size, 172*b2466bb3SRyan Roberts private ? "true" : "false", 173*b2466bb3SRyan Roberts swapout ? "true" : "false", 174*b2466bb3SRyan Roberts hugetlb ? "true" : "false"); 175*b2466bb3SRyan Roberts 176*b2466bb3SRyan Roberts /* Allocate a folio of required size and type. */ 177*b2466bb3SRyan Roberts mem = alloc_one_folio(size, private, hugetlb); 178*b2466bb3SRyan Roberts if (!mem) { 179*b2466bb3SRyan Roberts ksft_test_result_fail("alloc_one_folio() failed\n"); 180*b2466bb3SRyan Roberts goto out; 181*b2466bb3SRyan Roberts } 182*b2466bb3SRyan Roberts 183*b2466bb3SRyan Roberts /* Register range for uffd-wp. */ 184*b2466bb3SRyan Roberts if (userfaultfd_open(&features)) { 185*b2466bb3SRyan Roberts ksft_test_result_fail("userfaultfd_open() failed\n"); 186*b2466bb3SRyan Roberts goto out; 187*b2466bb3SRyan Roberts } 188*b2466bb3SRyan Roberts if (uffd_register(uffd, mem, size, false, true, false)) { 189*b2466bb3SRyan Roberts ksft_test_result_fail("uffd_register() failed\n"); 190*b2466bb3SRyan Roberts goto out; 191*b2466bb3SRyan Roberts } 192*b2466bb3SRyan Roberts wp_prms.mode = UFFDIO_WRITEPROTECT_MODE_WP; 193*b2466bb3SRyan Roberts wp_prms.range.start = (uintptr_t)mem; 194*b2466bb3SRyan Roberts wp_prms.range.len = size; 195*b2466bb3SRyan Roberts if (ioctl(uffd, UFFDIO_WRITEPROTECT, &wp_prms)) { 196*b2466bb3SRyan Roberts ksft_test_result_fail("ioctl(UFFDIO_WRITEPROTECT) failed\n"); 197*b2466bb3SRyan Roberts goto out; 198*b2466bb3SRyan Roberts } 199*b2466bb3SRyan Roberts 200*b2466bb3SRyan Roberts if (swapout) { 201*b2466bb3SRyan Roberts madvise(mem, size, MADV_PAGEOUT); 202*b2466bb3SRyan Roberts if (!range_is_swapped(mem, size)) { 203*b2466bb3SRyan Roberts ksft_test_result_skip("MADV_PAGEOUT did not work, is swap enabled?\n"); 204*b2466bb3SRyan Roberts goto out; 205*b2466bb3SRyan Roberts } 206*b2466bb3SRyan Roberts } 207*b2466bb3SRyan Roberts 208*b2466bb3SRyan Roberts /* Check that uffd-wp is set for all PTEs in range. */ 209*b2466bb3SRyan Roberts if (!check_uffd_wp_state(mem, size, true)) 210*b2466bb3SRyan Roberts goto out; 211*b2466bb3SRyan Roberts 212*b2466bb3SRyan Roberts /* 213*b2466bb3SRyan Roberts * Move the mapping to a new, aligned location. Since 214*b2466bb3SRyan Roberts * UFFD_FEATURE_EVENT_REMAP is not set, we expect the uffd-wp bit for 215*b2466bb3SRyan Roberts * each PTE to be cleared in the new mapping. 216*b2466bb3SRyan Roberts */ 217*b2466bb3SRyan Roberts addr = mmap_aligned(size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS); 218*b2466bb3SRyan Roberts if (addr == MAP_FAILED) { 219*b2466bb3SRyan Roberts ksft_test_result_fail("mmap_aligned() failed\n"); 220*b2466bb3SRyan Roberts goto out; 221*b2466bb3SRyan Roberts } 222*b2466bb3SRyan Roberts if (mremap(mem, size, size, MREMAP_FIXED | MREMAP_MAYMOVE, addr) == MAP_FAILED) { 223*b2466bb3SRyan Roberts ksft_test_result_fail("mremap() failed\n"); 224*b2466bb3SRyan Roberts munmap(addr, size); 225*b2466bb3SRyan Roberts goto out; 226*b2466bb3SRyan Roberts } 227*b2466bb3SRyan Roberts mem = addr; 228*b2466bb3SRyan Roberts 229*b2466bb3SRyan Roberts /* Check that uffd-wp is cleared for all PTEs in range. */ 230*b2466bb3SRyan Roberts if (!check_uffd_wp_state(mem, size, false)) 231*b2466bb3SRyan Roberts goto out; 232*b2466bb3SRyan Roberts 233*b2466bb3SRyan Roberts ksft_test_result_pass("%s(size=%zu, private=%s, swapout=%s, hugetlb=%s)\n", 234*b2466bb3SRyan Roberts __func__, 235*b2466bb3SRyan Roberts size, 236*b2466bb3SRyan Roberts private ? "true" : "false", 237*b2466bb3SRyan Roberts swapout ? "true" : "false", 238*b2466bb3SRyan Roberts hugetlb ? "true" : "false"); 239*b2466bb3SRyan Roberts out: 240*b2466bb3SRyan Roberts if (mem) 241*b2466bb3SRyan Roberts munmap(mem, size); 242*b2466bb3SRyan Roberts if (uffd >= 0) { 243*b2466bb3SRyan Roberts close(uffd); 244*b2466bb3SRyan Roberts uffd = -1; 245*b2466bb3SRyan Roberts } 246*b2466bb3SRyan Roberts } 247*b2466bb3SRyan Roberts 248*b2466bb3SRyan Roberts struct testcase { 249*b2466bb3SRyan Roberts size_t *sizes; 250*b2466bb3SRyan Roberts int *nr_sizes; 251*b2466bb3SRyan Roberts bool private; 252*b2466bb3SRyan Roberts bool swapout; 253*b2466bb3SRyan Roberts bool hugetlb; 254*b2466bb3SRyan Roberts }; 255*b2466bb3SRyan Roberts 256*b2466bb3SRyan Roberts static const struct testcase testcases[] = { 257*b2466bb3SRyan Roberts /* base pages. */ 258*b2466bb3SRyan Roberts { 259*b2466bb3SRyan Roberts .sizes = &pagesize, 260*b2466bb3SRyan Roberts .nr_sizes = &nr_pagesizes, 261*b2466bb3SRyan Roberts .private = false, 262*b2466bb3SRyan Roberts .swapout = false, 263*b2466bb3SRyan Roberts .hugetlb = false, 264*b2466bb3SRyan Roberts }, 265*b2466bb3SRyan Roberts { 266*b2466bb3SRyan Roberts .sizes = &pagesize, 267*b2466bb3SRyan Roberts .nr_sizes = &nr_pagesizes, 268*b2466bb3SRyan Roberts .private = true, 269*b2466bb3SRyan Roberts .swapout = false, 270*b2466bb3SRyan Roberts .hugetlb = false, 271*b2466bb3SRyan Roberts }, 272*b2466bb3SRyan Roberts { 273*b2466bb3SRyan Roberts .sizes = &pagesize, 274*b2466bb3SRyan Roberts .nr_sizes = &nr_pagesizes, 275*b2466bb3SRyan Roberts .private = false, 276*b2466bb3SRyan Roberts .swapout = true, 277*b2466bb3SRyan Roberts .hugetlb = false, 278*b2466bb3SRyan Roberts }, 279*b2466bb3SRyan Roberts { 280*b2466bb3SRyan Roberts .sizes = &pagesize, 281*b2466bb3SRyan Roberts .nr_sizes = &nr_pagesizes, 282*b2466bb3SRyan Roberts .private = true, 283*b2466bb3SRyan Roberts .swapout = true, 284*b2466bb3SRyan Roberts .hugetlb = false, 285*b2466bb3SRyan Roberts }, 286*b2466bb3SRyan Roberts 287*b2466bb3SRyan Roberts /* thp. */ 288*b2466bb3SRyan Roberts { 289*b2466bb3SRyan Roberts .sizes = thpsizes, 290*b2466bb3SRyan Roberts .nr_sizes = &nr_thpsizes, 291*b2466bb3SRyan Roberts .private = false, 292*b2466bb3SRyan Roberts .swapout = false, 293*b2466bb3SRyan Roberts .hugetlb = false, 294*b2466bb3SRyan Roberts }, 295*b2466bb3SRyan Roberts { 296*b2466bb3SRyan Roberts .sizes = thpsizes, 297*b2466bb3SRyan Roberts .nr_sizes = &nr_thpsizes, 298*b2466bb3SRyan Roberts .private = true, 299*b2466bb3SRyan Roberts .swapout = false, 300*b2466bb3SRyan Roberts .hugetlb = false, 301*b2466bb3SRyan Roberts }, 302*b2466bb3SRyan Roberts { 303*b2466bb3SRyan Roberts .sizes = thpsizes, 304*b2466bb3SRyan Roberts .nr_sizes = &nr_thpsizes, 305*b2466bb3SRyan Roberts .private = false, 306*b2466bb3SRyan Roberts .swapout = true, 307*b2466bb3SRyan Roberts .hugetlb = false, 308*b2466bb3SRyan Roberts }, 309*b2466bb3SRyan Roberts { 310*b2466bb3SRyan Roberts .sizes = thpsizes, 311*b2466bb3SRyan Roberts .nr_sizes = &nr_thpsizes, 312*b2466bb3SRyan Roberts .private = true, 313*b2466bb3SRyan Roberts .swapout = true, 314*b2466bb3SRyan Roberts .hugetlb = false, 315*b2466bb3SRyan Roberts }, 316*b2466bb3SRyan Roberts 317*b2466bb3SRyan Roberts /* hugetlb. */ 318*b2466bb3SRyan Roberts { 319*b2466bb3SRyan Roberts .sizes = hugetlbsizes, 320*b2466bb3SRyan Roberts .nr_sizes = &nr_hugetlbsizes, 321*b2466bb3SRyan Roberts .private = false, 322*b2466bb3SRyan Roberts .swapout = false, 323*b2466bb3SRyan Roberts .hugetlb = true, 324*b2466bb3SRyan Roberts }, 325*b2466bb3SRyan Roberts { 326*b2466bb3SRyan Roberts .sizes = hugetlbsizes, 327*b2466bb3SRyan Roberts .nr_sizes = &nr_hugetlbsizes, 328*b2466bb3SRyan Roberts .private = true, 329*b2466bb3SRyan Roberts .swapout = false, 330*b2466bb3SRyan Roberts .hugetlb = true, 331*b2466bb3SRyan Roberts }, 332*b2466bb3SRyan Roberts }; 333*b2466bb3SRyan Roberts 334*b2466bb3SRyan Roberts int main(int argc, char **argv) 335*b2466bb3SRyan Roberts { 336*b2466bb3SRyan Roberts struct thp_settings settings; 337*b2466bb3SRyan Roberts int i, j, plan = 0; 338*b2466bb3SRyan Roberts 339*b2466bb3SRyan Roberts pagesize = getpagesize(); 340*b2466bb3SRyan Roberts nr_thpsizes = detect_thp_sizes(thpsizes, ARRAY_SIZE(thpsizes)); 341*b2466bb3SRyan Roberts nr_hugetlbsizes = detect_hugetlb_page_sizes(hugetlbsizes, 342*b2466bb3SRyan Roberts ARRAY_SIZE(hugetlbsizes)); 343*b2466bb3SRyan Roberts 344*b2466bb3SRyan Roberts /* If THP is supported, save THP settings and initially disable THP. */ 345*b2466bb3SRyan Roberts if (nr_thpsizes) { 346*b2466bb3SRyan Roberts thp_save_settings(); 347*b2466bb3SRyan Roberts thp_read_settings(&settings); 348*b2466bb3SRyan Roberts for (i = 0; i < NR_ORDERS; i++) { 349*b2466bb3SRyan Roberts settings.hugepages[i].enabled = THP_NEVER; 350*b2466bb3SRyan Roberts settings.shmem_hugepages[i].enabled = SHMEM_NEVER; 351*b2466bb3SRyan Roberts } 352*b2466bb3SRyan Roberts thp_push_settings(&settings); 353*b2466bb3SRyan Roberts } 354*b2466bb3SRyan Roberts 355*b2466bb3SRyan Roberts for (i = 0; i < ARRAY_SIZE(testcases); i++) 356*b2466bb3SRyan Roberts plan += *testcases[i].nr_sizes; 357*b2466bb3SRyan Roberts ksft_set_plan(plan); 358*b2466bb3SRyan Roberts 359*b2466bb3SRyan Roberts pagemap_fd = open("/proc/self/pagemap", O_RDONLY); 360*b2466bb3SRyan Roberts if (pagemap_fd < 0) 361*b2466bb3SRyan Roberts ksft_exit_fail_msg("opening pagemap failed\n"); 362*b2466bb3SRyan Roberts 363*b2466bb3SRyan Roberts for (i = 0; i < ARRAY_SIZE(testcases); i++) { 364*b2466bb3SRyan Roberts const struct testcase *tc = &testcases[i]; 365*b2466bb3SRyan Roberts 366*b2466bb3SRyan Roberts for (j = 0; j < *tc->nr_sizes; j++) 367*b2466bb3SRyan Roberts test_one_folio(tc->sizes[j], tc->private, tc->swapout, 368*b2466bb3SRyan Roberts tc->hugetlb); 369*b2466bb3SRyan Roberts } 370*b2466bb3SRyan Roberts 371*b2466bb3SRyan Roberts /* If THP is supported, restore original THP settings. */ 372*b2466bb3SRyan Roberts if (nr_thpsizes) 373*b2466bb3SRyan Roberts thp_restore_settings(); 374*b2466bb3SRyan Roberts 375*b2466bb3SRyan Roberts i = ksft_get_fail_cnt(); 376*b2466bb3SRyan Roberts if (i) 377*b2466bb3SRyan Roberts ksft_exit_fail_msg("%d out of %d tests failed\n", 378*b2466bb3SRyan Roberts i, ksft_test_num()); 379*b2466bb3SRyan Roberts ksft_exit_pass(); 380*b2466bb3SRyan Roberts } 381