162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <string.h> 362306a36Sopenharmony_ci#include <fcntl.h> 462306a36Sopenharmony_ci#include <dirent.h> 562306a36Sopenharmony_ci#include <sys/ioctl.h> 662306a36Sopenharmony_ci#include <linux/userfaultfd.h> 762306a36Sopenharmony_ci#include <sys/syscall.h> 862306a36Sopenharmony_ci#include <unistd.h> 962306a36Sopenharmony_ci#include "../kselftest.h" 1062306a36Sopenharmony_ci#include "vm_util.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define PMD_SIZE_FILE_PATH "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size" 1362306a36Sopenharmony_ci#define SMAP_FILE_PATH "/proc/self/smaps" 1462306a36Sopenharmony_ci#define MAX_LINE_LENGTH 500 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ciunsigned int __page_size; 1762306a36Sopenharmony_ciunsigned int __page_shift; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ciuint64_t pagemap_get_entry(int fd, char *start) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci const unsigned long pfn = (unsigned long)start / getpagesize(); 2262306a36Sopenharmony_ci uint64_t entry; 2362306a36Sopenharmony_ci int ret; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci ret = pread(fd, &entry, sizeof(entry), pfn * sizeof(entry)); 2662306a36Sopenharmony_ci if (ret != sizeof(entry)) 2762306a36Sopenharmony_ci ksft_exit_fail_msg("reading pagemap failed\n"); 2862306a36Sopenharmony_ci return entry; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cibool pagemap_is_softdirty(int fd, char *start) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci return pagemap_get_entry(fd, start) & PM_SOFT_DIRTY; 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cibool pagemap_is_swapped(int fd, char *start) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci return pagemap_get_entry(fd, start) & PM_SWAP; 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cibool pagemap_is_populated(int fd, char *start) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci return pagemap_get_entry(fd, start) & (PM_PRESENT | PM_SWAP); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ciunsigned long pagemap_get_pfn(int fd, char *start) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci uint64_t entry = pagemap_get_entry(fd, start); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci /* If present (63th bit), PFN is at bit 0 -- 54. */ 5162306a36Sopenharmony_ci if (entry & PM_PRESENT) 5262306a36Sopenharmony_ci return entry & 0x007fffffffffffffull; 5362306a36Sopenharmony_ci return -1ul; 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_civoid clear_softdirty(void) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci int ret; 5962306a36Sopenharmony_ci const char *ctrl = "4"; 6062306a36Sopenharmony_ci int fd = open("/proc/self/clear_refs", O_WRONLY); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (fd < 0) 6362306a36Sopenharmony_ci ksft_exit_fail_msg("opening clear_refs failed\n"); 6462306a36Sopenharmony_ci ret = write(fd, ctrl, strlen(ctrl)); 6562306a36Sopenharmony_ci close(fd); 6662306a36Sopenharmony_ci if (ret != strlen(ctrl)) 6762306a36Sopenharmony_ci ksft_exit_fail_msg("writing clear_refs failed\n"); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cibool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci while (fgets(buf, len, fp)) { 7362306a36Sopenharmony_ci if (!strncmp(buf, pattern, strlen(pattern))) 7462306a36Sopenharmony_ci return true; 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci return false; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ciuint64_t read_pmd_pagesize(void) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci int fd; 8262306a36Sopenharmony_ci char buf[20]; 8362306a36Sopenharmony_ci ssize_t num_read; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci fd = open(PMD_SIZE_FILE_PATH, O_RDONLY); 8662306a36Sopenharmony_ci if (fd == -1) 8762306a36Sopenharmony_ci return 0; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci num_read = read(fd, buf, 19); 9062306a36Sopenharmony_ci if (num_read < 1) { 9162306a36Sopenharmony_ci close(fd); 9262306a36Sopenharmony_ci return 0; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci buf[num_read] = '\0'; 9562306a36Sopenharmony_ci close(fd); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return strtoul(buf, NULL, 10); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cibool __check_huge(void *addr, char *pattern, int nr_hpages, 10162306a36Sopenharmony_ci uint64_t hpage_size) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci uint64_t thp = -1; 10462306a36Sopenharmony_ci int ret; 10562306a36Sopenharmony_ci FILE *fp; 10662306a36Sopenharmony_ci char buffer[MAX_LINE_LENGTH]; 10762306a36Sopenharmony_ci char addr_pattern[MAX_LINE_LENGTH]; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci ret = snprintf(addr_pattern, MAX_LINE_LENGTH, "%08lx-", 11062306a36Sopenharmony_ci (unsigned long) addr); 11162306a36Sopenharmony_ci if (ret >= MAX_LINE_LENGTH) 11262306a36Sopenharmony_ci ksft_exit_fail_msg("%s: Pattern is too long\n", __func__); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci fp = fopen(SMAP_FILE_PATH, "r"); 11562306a36Sopenharmony_ci if (!fp) 11662306a36Sopenharmony_ci ksft_exit_fail_msg("%s: Failed to open file %s\n", __func__, SMAP_FILE_PATH); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (!check_for_pattern(fp, addr_pattern, buffer, sizeof(buffer))) 11962306a36Sopenharmony_ci goto err_out; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* 12262306a36Sopenharmony_ci * Fetch the pattern in the same block and check the number of 12362306a36Sopenharmony_ci * hugepages. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_ci if (!check_for_pattern(fp, pattern, buffer, sizeof(buffer))) 12662306a36Sopenharmony_ci goto err_out; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci snprintf(addr_pattern, MAX_LINE_LENGTH, "%s%%9ld kB", pattern); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (sscanf(buffer, addr_pattern, &thp) != 1) 13162306a36Sopenharmony_ci ksft_exit_fail_msg("Reading smap error\n"); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cierr_out: 13462306a36Sopenharmony_ci fclose(fp); 13562306a36Sopenharmony_ci return thp == (nr_hpages * (hpage_size >> 10)); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cibool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci return __check_huge(addr, "AnonHugePages: ", nr_hpages, hpage_size); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cibool check_huge_file(void *addr, int nr_hpages, uint64_t hpage_size) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci return __check_huge(addr, "FilePmdMapped:", nr_hpages, hpage_size); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cibool check_huge_shmem(void *addr, int nr_hpages, uint64_t hpage_size) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci return __check_huge(addr, "ShmemPmdMapped:", nr_hpages, hpage_size); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ciint64_t allocate_transhuge(void *ptr, int pagemap_fd) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci uint64_t ent[2]; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* drop pmd */ 15862306a36Sopenharmony_ci if (mmap(ptr, HPAGE_SIZE, PROT_READ | PROT_WRITE, 15962306a36Sopenharmony_ci MAP_FIXED | MAP_ANONYMOUS | 16062306a36Sopenharmony_ci MAP_NORESERVE | MAP_PRIVATE, -1, 0) != ptr) 16162306a36Sopenharmony_ci errx(2, "mmap transhuge"); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (madvise(ptr, HPAGE_SIZE, MADV_HUGEPAGE)) 16462306a36Sopenharmony_ci err(2, "MADV_HUGEPAGE"); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* allocate transparent huge page */ 16762306a36Sopenharmony_ci *(volatile void **)ptr = ptr; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (pread(pagemap_fd, ent, sizeof(ent), 17062306a36Sopenharmony_ci (uintptr_t)ptr >> (pshift() - 3)) != sizeof(ent)) 17162306a36Sopenharmony_ci err(2, "read pagemap"); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (PAGEMAP_PRESENT(ent[0]) && PAGEMAP_PRESENT(ent[1]) && 17462306a36Sopenharmony_ci PAGEMAP_PFN(ent[0]) + 1 == PAGEMAP_PFN(ent[1]) && 17562306a36Sopenharmony_ci !(PAGEMAP_PFN(ent[0]) & ((1 << (HPAGE_SHIFT - pshift())) - 1))) 17662306a36Sopenharmony_ci return PAGEMAP_PFN(ent[0]); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return -1; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ciunsigned long default_huge_page_size(void) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci unsigned long hps = 0; 18462306a36Sopenharmony_ci char *line = NULL; 18562306a36Sopenharmony_ci size_t linelen = 0; 18662306a36Sopenharmony_ci FILE *f = fopen("/proc/meminfo", "r"); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (!f) 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci while (getline(&line, &linelen, f) > 0) { 19162306a36Sopenharmony_ci if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) { 19262306a36Sopenharmony_ci hps <<= 10; 19362306a36Sopenharmony_ci break; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci free(line); 19862306a36Sopenharmony_ci fclose(f); 19962306a36Sopenharmony_ci return hps; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ciint detect_hugetlb_page_sizes(size_t sizes[], int max) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci DIR *dir = opendir("/sys/kernel/mm/hugepages/"); 20562306a36Sopenharmony_ci int count = 0; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (!dir) 20862306a36Sopenharmony_ci return 0; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci while (count < max) { 21162306a36Sopenharmony_ci struct dirent *entry = readdir(dir); 21262306a36Sopenharmony_ci size_t kb; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (!entry) 21562306a36Sopenharmony_ci break; 21662306a36Sopenharmony_ci if (entry->d_type != DT_DIR) 21762306a36Sopenharmony_ci continue; 21862306a36Sopenharmony_ci if (sscanf(entry->d_name, "hugepages-%zukB", &kb) != 1) 21962306a36Sopenharmony_ci continue; 22062306a36Sopenharmony_ci sizes[count++] = kb * 1024; 22162306a36Sopenharmony_ci ksft_print_msg("[INFO] detected hugetlb page size: %zu KiB\n", 22262306a36Sopenharmony_ci kb); 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci closedir(dir); 22562306a36Sopenharmony_ci return count; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci/* If `ioctls' non-NULL, the allowed ioctls will be returned into the var */ 22962306a36Sopenharmony_ciint uffd_register_with_ioctls(int uffd, void *addr, uint64_t len, 23062306a36Sopenharmony_ci bool miss, bool wp, bool minor, uint64_t *ioctls) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct uffdio_register uffdio_register = { 0 }; 23362306a36Sopenharmony_ci uint64_t mode = 0; 23462306a36Sopenharmony_ci int ret = 0; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (miss) 23762306a36Sopenharmony_ci mode |= UFFDIO_REGISTER_MODE_MISSING; 23862306a36Sopenharmony_ci if (wp) 23962306a36Sopenharmony_ci mode |= UFFDIO_REGISTER_MODE_WP; 24062306a36Sopenharmony_ci if (minor) 24162306a36Sopenharmony_ci mode |= UFFDIO_REGISTER_MODE_MINOR; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci uffdio_register.range.start = (unsigned long)addr; 24462306a36Sopenharmony_ci uffdio_register.range.len = len; 24562306a36Sopenharmony_ci uffdio_register.mode = mode; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) 24862306a36Sopenharmony_ci ret = -errno; 24962306a36Sopenharmony_ci else if (ioctls) 25062306a36Sopenharmony_ci *ioctls = uffdio_register.ioctls; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci return ret; 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ciint uffd_register(int uffd, void *addr, uint64_t len, 25662306a36Sopenharmony_ci bool miss, bool wp, bool minor) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci return uffd_register_with_ioctls(uffd, addr, len, 25962306a36Sopenharmony_ci miss, wp, minor, NULL); 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ciint uffd_unregister(int uffd, void *addr, uint64_t len) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct uffdio_range range = { .start = (uintptr_t)addr, .len = len }; 26562306a36Sopenharmony_ci int ret = 0; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if (ioctl(uffd, UFFDIO_UNREGISTER, &range) == -1) 26862306a36Sopenharmony_ci ret = -errno; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return ret; 27162306a36Sopenharmony_ci} 272