1681f45deSUsama Arif // SPDX-License-Identifier: GPL-2.0 2681f45deSUsama Arif /* 3681f45deSUsama Arif * Basic tests for PR_GET/SET_THP_DISABLE prctl calls 4681f45deSUsama Arif * 5681f45deSUsama Arif * Author(s): Usama Arif <usamaarif642@gmail.com> 6681f45deSUsama Arif */ 7681f45deSUsama Arif #include <stdio.h> 8681f45deSUsama Arif #include <stdlib.h> 9681f45deSUsama Arif #include <string.h> 10681f45deSUsama Arif #include <unistd.h> 11681f45deSUsama Arif #include <sys/mman.h> 12681f45deSUsama Arif #include <linux/mman.h> 13681f45deSUsama Arif #include <sys/prctl.h> 14681f45deSUsama Arif #include <sys/wait.h> 15681f45deSUsama Arif 16681f45deSUsama Arif #include "../kselftest_harness.h" 17681f45deSUsama Arif #include "thp_settings.h" 18681f45deSUsama Arif #include "vm_util.h" 19681f45deSUsama Arif 20*6bb96144SUsama Arif #ifndef PR_THP_DISABLE_EXCEPT_ADVISED 21*6bb96144SUsama Arif #define PR_THP_DISABLE_EXCEPT_ADVISED (1 << 1) 22*6bb96144SUsama Arif #endif 23*6bb96144SUsama Arif 24681f45deSUsama Arif enum thp_collapse_type { 25681f45deSUsama Arif THP_COLLAPSE_NONE, 26681f45deSUsama Arif THP_COLLAPSE_MADV_NOHUGEPAGE, 27681f45deSUsama Arif THP_COLLAPSE_MADV_HUGEPAGE, /* MADV_HUGEPAGE before access */ 28681f45deSUsama Arif THP_COLLAPSE_MADV_COLLAPSE, /* MADV_COLLAPSE after access */ 29681f45deSUsama Arif }; 30681f45deSUsama Arif 31681f45deSUsama Arif /* 32681f45deSUsama Arif * Function to mmap a buffer, fault it in, madvise it appropriately (before 33681f45deSUsama Arif * page fault for MADV_HUGE, and after for MADV_COLLAPSE), and check if the 34681f45deSUsama Arif * mmap region is huge. 35681f45deSUsama Arif * Returns: 36681f45deSUsama Arif * 0 if test doesn't give hugepage 37681f45deSUsama Arif * 1 if test gives a hugepage 38681f45deSUsama Arif * -errno if mmap fails 39681f45deSUsama Arif */ 40681f45deSUsama Arif static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize) 41681f45deSUsama Arif { 42681f45deSUsama Arif char *mem, *mmap_mem; 43681f45deSUsama Arif size_t mmap_size; 44681f45deSUsama Arif int ret; 45681f45deSUsama Arif 46681f45deSUsama Arif /* For alignment purposes, we need twice the THP size. */ 47681f45deSUsama Arif mmap_size = 2 * pmdsize; 48681f45deSUsama Arif mmap_mem = (char *)mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, 49681f45deSUsama Arif MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 50681f45deSUsama Arif if (mmap_mem == MAP_FAILED) 51681f45deSUsama Arif return -errno; 52681f45deSUsama Arif 53681f45deSUsama Arif /* We need a THP-aligned memory area. */ 54681f45deSUsama Arif mem = (char *)(((uintptr_t)mmap_mem + pmdsize) & ~(pmdsize - 1)); 55681f45deSUsama Arif 56681f45deSUsama Arif if (madvise_buf == THP_COLLAPSE_MADV_HUGEPAGE) 57681f45deSUsama Arif madvise(mem, pmdsize, MADV_HUGEPAGE); 58681f45deSUsama Arif else if (madvise_buf == THP_COLLAPSE_MADV_NOHUGEPAGE) 59681f45deSUsama Arif madvise(mem, pmdsize, MADV_NOHUGEPAGE); 60681f45deSUsama Arif 61681f45deSUsama Arif /* Ensure memory is allocated */ 62681f45deSUsama Arif memset(mem, 1, pmdsize); 63681f45deSUsama Arif 64681f45deSUsama Arif if (madvise_buf == THP_COLLAPSE_MADV_COLLAPSE) 65681f45deSUsama Arif madvise(mem, pmdsize, MADV_COLLAPSE); 66681f45deSUsama Arif 67681f45deSUsama Arif /* HACK: make sure we have a separate VMA that we can check reliably. */ 68681f45deSUsama Arif mprotect(mem, pmdsize, PROT_READ); 69681f45deSUsama Arif 70681f45deSUsama Arif ret = check_huge_anon(mem, 1, pmdsize); 71681f45deSUsama Arif munmap(mmap_mem, mmap_size); 72681f45deSUsama Arif return ret; 73681f45deSUsama Arif } 74681f45deSUsama Arif 75681f45deSUsama Arif static void prctl_thp_disable_completely_test(struct __test_metadata *const _metadata, 76681f45deSUsama Arif size_t pmdsize, 77681f45deSUsama Arif enum thp_enabled thp_policy) 78681f45deSUsama Arif { 79681f45deSUsama Arif ASSERT_EQ(prctl(PR_GET_THP_DISABLE, NULL, NULL, NULL, NULL), 1); 80681f45deSUsama Arif 81681f45deSUsama Arif /* tests after prctl overrides global policy */ 82681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize), 0); 83681f45deSUsama Arif 84681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0); 85681f45deSUsama Arif 86681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize), 0); 87681f45deSUsama Arif 88681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 0); 89681f45deSUsama Arif 90681f45deSUsama Arif /* Reset to global policy */ 91681f45deSUsama Arif ASSERT_EQ(prctl(PR_SET_THP_DISABLE, 0, NULL, NULL, NULL), 0); 92681f45deSUsama Arif 93681f45deSUsama Arif /* tests after prctl is cleared, and only global policy is effective */ 94681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize), 95681f45deSUsama Arif thp_policy == THP_ALWAYS ? 1 : 0); 96681f45deSUsama Arif 97681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0); 98681f45deSUsama Arif 99681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize), 100681f45deSUsama Arif thp_policy == THP_NEVER ? 0 : 1); 101681f45deSUsama Arif 102681f45deSUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 1); 103681f45deSUsama Arif } 104681f45deSUsama Arif 105681f45deSUsama Arif FIXTURE(prctl_thp_disable_completely) 106681f45deSUsama Arif { 107681f45deSUsama Arif struct thp_settings settings; 108681f45deSUsama Arif size_t pmdsize; 109681f45deSUsama Arif }; 110681f45deSUsama Arif 111681f45deSUsama Arif FIXTURE_VARIANT(prctl_thp_disable_completely) 112681f45deSUsama Arif { 113681f45deSUsama Arif enum thp_enabled thp_policy; 114681f45deSUsama Arif }; 115681f45deSUsama Arif 116681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, never) 117681f45deSUsama Arif { 118681f45deSUsama Arif .thp_policy = THP_NEVER, 119681f45deSUsama Arif }; 120681f45deSUsama Arif 121681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, madvise) 122681f45deSUsama Arif { 123681f45deSUsama Arif .thp_policy = THP_MADVISE, 124681f45deSUsama Arif }; 125681f45deSUsama Arif 126681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, always) 127681f45deSUsama Arif { 128681f45deSUsama Arif .thp_policy = THP_ALWAYS, 129681f45deSUsama Arif }; 130681f45deSUsama Arif 131681f45deSUsama Arif FIXTURE_SETUP(prctl_thp_disable_completely) 132681f45deSUsama Arif { 133681f45deSUsama Arif if (!thp_available()) 134681f45deSUsama Arif SKIP(return, "Transparent Hugepages not available\n"); 135681f45deSUsama Arif 136681f45deSUsama Arif self->pmdsize = read_pmd_pagesize(); 137681f45deSUsama Arif if (!self->pmdsize) 138681f45deSUsama Arif SKIP(return, "Unable to read PMD size\n"); 139681f45deSUsama Arif 140681f45deSUsama Arif if (prctl(PR_SET_THP_DISABLE, 1, NULL, NULL, NULL)) 141681f45deSUsama Arif SKIP(return, "Unable to disable THPs completely for the process\n"); 142681f45deSUsama Arif 143681f45deSUsama Arif thp_save_settings(); 144681f45deSUsama Arif thp_read_settings(&self->settings); 145681f45deSUsama Arif self->settings.thp_enabled = variant->thp_policy; 146681f45deSUsama Arif self->settings.hugepages[sz2ord(self->pmdsize, getpagesize())].enabled = THP_INHERIT; 147681f45deSUsama Arif thp_write_settings(&self->settings); 148681f45deSUsama Arif } 149681f45deSUsama Arif 150681f45deSUsama Arif FIXTURE_TEARDOWN(prctl_thp_disable_completely) 151681f45deSUsama Arif { 152681f45deSUsama Arif thp_restore_settings(); 153681f45deSUsama Arif } 154681f45deSUsama Arif 155681f45deSUsama Arif TEST_F(prctl_thp_disable_completely, nofork) 156681f45deSUsama Arif { 157681f45deSUsama Arif prctl_thp_disable_completely_test(_metadata, self->pmdsize, variant->thp_policy); 158681f45deSUsama Arif } 159681f45deSUsama Arif 160681f45deSUsama Arif TEST_F(prctl_thp_disable_completely, fork) 161681f45deSUsama Arif { 162681f45deSUsama Arif int ret = 0; 163681f45deSUsama Arif pid_t pid; 164681f45deSUsama Arif 165681f45deSUsama Arif /* Make sure prctl changes are carried across fork */ 166681f45deSUsama Arif pid = fork(); 167681f45deSUsama Arif ASSERT_GE(pid, 0); 168681f45deSUsama Arif 169681f45deSUsama Arif if (!pid) { 170681f45deSUsama Arif prctl_thp_disable_completely_test(_metadata, self->pmdsize, variant->thp_policy); 171681f45deSUsama Arif return; 172681f45deSUsama Arif } 173681f45deSUsama Arif 174681f45deSUsama Arif wait(&ret); 175681f45deSUsama Arif if (WIFEXITED(ret)) 176681f45deSUsama Arif ret = WEXITSTATUS(ret); 177681f45deSUsama Arif else 178681f45deSUsama Arif ret = -EINVAL; 179681f45deSUsama Arif ASSERT_EQ(ret, 0); 180681f45deSUsama Arif } 181681f45deSUsama Arif 182*6bb96144SUsama Arif static void prctl_thp_disable_except_madvise_test(struct __test_metadata *const _metadata, 183*6bb96144SUsama Arif size_t pmdsize, 184*6bb96144SUsama Arif enum thp_enabled thp_policy) 185*6bb96144SUsama Arif { 186*6bb96144SUsama Arif ASSERT_EQ(prctl(PR_GET_THP_DISABLE, NULL, NULL, NULL, NULL), 3); 187*6bb96144SUsama Arif 188*6bb96144SUsama Arif /* tests after prctl overrides global policy */ 189*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize), 0); 190*6bb96144SUsama Arif 191*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0); 192*6bb96144SUsama Arif 193*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize), 194*6bb96144SUsama Arif thp_policy == THP_NEVER ? 0 : 1); 195*6bb96144SUsama Arif 196*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 1); 197*6bb96144SUsama Arif 198*6bb96144SUsama Arif /* Reset to global policy */ 199*6bb96144SUsama Arif ASSERT_EQ(prctl(PR_SET_THP_DISABLE, 0, NULL, NULL, NULL), 0); 200*6bb96144SUsama Arif 201*6bb96144SUsama Arif /* tests after prctl is cleared, and only global policy is effective */ 202*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize), 203*6bb96144SUsama Arif thp_policy == THP_ALWAYS ? 1 : 0); 204*6bb96144SUsama Arif 205*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0); 206*6bb96144SUsama Arif 207*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize), 208*6bb96144SUsama Arif thp_policy == THP_NEVER ? 0 : 1); 209*6bb96144SUsama Arif 210*6bb96144SUsama Arif ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 1); 211*6bb96144SUsama Arif } 212*6bb96144SUsama Arif 213*6bb96144SUsama Arif FIXTURE(prctl_thp_disable_except_madvise) 214*6bb96144SUsama Arif { 215*6bb96144SUsama Arif struct thp_settings settings; 216*6bb96144SUsama Arif size_t pmdsize; 217*6bb96144SUsama Arif }; 218*6bb96144SUsama Arif 219*6bb96144SUsama Arif FIXTURE_VARIANT(prctl_thp_disable_except_madvise) 220*6bb96144SUsama Arif { 221*6bb96144SUsama Arif enum thp_enabled thp_policy; 222*6bb96144SUsama Arif }; 223*6bb96144SUsama Arif 224*6bb96144SUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_except_madvise, never) 225*6bb96144SUsama Arif { 226*6bb96144SUsama Arif .thp_policy = THP_NEVER, 227*6bb96144SUsama Arif }; 228*6bb96144SUsama Arif 229*6bb96144SUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_except_madvise, madvise) 230*6bb96144SUsama Arif { 231*6bb96144SUsama Arif .thp_policy = THP_MADVISE, 232*6bb96144SUsama Arif }; 233*6bb96144SUsama Arif 234*6bb96144SUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_except_madvise, always) 235*6bb96144SUsama Arif { 236*6bb96144SUsama Arif .thp_policy = THP_ALWAYS, 237*6bb96144SUsama Arif }; 238*6bb96144SUsama Arif 239*6bb96144SUsama Arif FIXTURE_SETUP(prctl_thp_disable_except_madvise) 240*6bb96144SUsama Arif { 241*6bb96144SUsama Arif if (!thp_available()) 242*6bb96144SUsama Arif SKIP(return, "Transparent Hugepages not available\n"); 243*6bb96144SUsama Arif 244*6bb96144SUsama Arif self->pmdsize = read_pmd_pagesize(); 245*6bb96144SUsama Arif if (!self->pmdsize) 246*6bb96144SUsama Arif SKIP(return, "Unable to read PMD size\n"); 247*6bb96144SUsama Arif 248*6bb96144SUsama Arif if (prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, NULL, NULL)) 249*6bb96144SUsama Arif SKIP(return, "Unable to set PR_THP_DISABLE_EXCEPT_ADVISED\n"); 250*6bb96144SUsama Arif 251*6bb96144SUsama Arif thp_save_settings(); 252*6bb96144SUsama Arif thp_read_settings(&self->settings); 253*6bb96144SUsama Arif self->settings.thp_enabled = variant->thp_policy; 254*6bb96144SUsama Arif self->settings.hugepages[sz2ord(self->pmdsize, getpagesize())].enabled = THP_INHERIT; 255*6bb96144SUsama Arif thp_write_settings(&self->settings); 256*6bb96144SUsama Arif } 257*6bb96144SUsama Arif 258*6bb96144SUsama Arif FIXTURE_TEARDOWN(prctl_thp_disable_except_madvise) 259*6bb96144SUsama Arif { 260*6bb96144SUsama Arif thp_restore_settings(); 261*6bb96144SUsama Arif } 262*6bb96144SUsama Arif 263*6bb96144SUsama Arif TEST_F(prctl_thp_disable_except_madvise, nofork) 264*6bb96144SUsama Arif { 265*6bb96144SUsama Arif prctl_thp_disable_except_madvise_test(_metadata, self->pmdsize, variant->thp_policy); 266*6bb96144SUsama Arif } 267*6bb96144SUsama Arif 268*6bb96144SUsama Arif TEST_F(prctl_thp_disable_except_madvise, fork) 269*6bb96144SUsama Arif { 270*6bb96144SUsama Arif int ret = 0; 271*6bb96144SUsama Arif pid_t pid; 272*6bb96144SUsama Arif 273*6bb96144SUsama Arif /* Make sure prctl changes are carried across fork */ 274*6bb96144SUsama Arif pid = fork(); 275*6bb96144SUsama Arif ASSERT_GE(pid, 0); 276*6bb96144SUsama Arif 277*6bb96144SUsama Arif if (!pid) { 278*6bb96144SUsama Arif prctl_thp_disable_except_madvise_test(_metadata, self->pmdsize, 279*6bb96144SUsama Arif variant->thp_policy); 280*6bb96144SUsama Arif return; 281*6bb96144SUsama Arif } 282*6bb96144SUsama Arif 283*6bb96144SUsama Arif wait(&ret); 284*6bb96144SUsama Arif if (WIFEXITED(ret)) 285*6bb96144SUsama Arif ret = WEXITSTATUS(ret); 286*6bb96144SUsama Arif else 287*6bb96144SUsama Arif ret = -EINVAL; 288*6bb96144SUsama Arif ASSERT_EQ(ret, 0); 289*6bb96144SUsama Arif } 290*6bb96144SUsama Arif 291681f45deSUsama Arif TEST_HARNESS_MAIN 292