162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * KSM functional tests 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2022, Red Hat, Inc. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author(s): David Hildenbrand <david@redhat.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#define _GNU_SOURCE 1062306a36Sopenharmony_ci#include <stdlib.h> 1162306a36Sopenharmony_ci#include <string.h> 1262306a36Sopenharmony_ci#include <stdbool.h> 1362306a36Sopenharmony_ci#include <stdint.h> 1462306a36Sopenharmony_ci#include <unistd.h> 1562306a36Sopenharmony_ci#include <errno.h> 1662306a36Sopenharmony_ci#include <fcntl.h> 1762306a36Sopenharmony_ci#include <sys/mman.h> 1862306a36Sopenharmony_ci#include <sys/prctl.h> 1962306a36Sopenharmony_ci#include <sys/syscall.h> 2062306a36Sopenharmony_ci#include <sys/ioctl.h> 2162306a36Sopenharmony_ci#include <sys/wait.h> 2262306a36Sopenharmony_ci#include <linux/userfaultfd.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "../kselftest.h" 2562306a36Sopenharmony_ci#include "vm_util.h" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define KiB 1024u 2862306a36Sopenharmony_ci#define MiB (1024 * KiB) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic int mem_fd; 3162306a36Sopenharmony_cistatic int ksm_fd; 3262306a36Sopenharmony_cistatic int ksm_full_scans_fd; 3362306a36Sopenharmony_cistatic int proc_self_ksm_stat_fd; 3462306a36Sopenharmony_cistatic int proc_self_ksm_merging_pages_fd; 3562306a36Sopenharmony_cistatic int ksm_use_zero_pages_fd; 3662306a36Sopenharmony_cistatic int pagemap_fd; 3762306a36Sopenharmony_cistatic size_t pagesize; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic bool range_maps_duplicates(char *addr, unsigned long size) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci unsigned long offs_a, offs_b, pfn_a, pfn_b; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci /* 4462306a36Sopenharmony_ci * There is no easy way to check if there are KSM pages mapped into 4562306a36Sopenharmony_ci * this range. We only check that the range does not map the same PFN 4662306a36Sopenharmony_ci * twice by comparing each pair of mapped pages. 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_ci for (offs_a = 0; offs_a < size; offs_a += pagesize) { 4962306a36Sopenharmony_ci pfn_a = pagemap_get_pfn(pagemap_fd, addr + offs_a); 5062306a36Sopenharmony_ci /* Page not present or PFN not exposed by the kernel. */ 5162306a36Sopenharmony_ci if (pfn_a == -1ul || !pfn_a) 5262306a36Sopenharmony_ci continue; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci for (offs_b = offs_a + pagesize; offs_b < size; 5562306a36Sopenharmony_ci offs_b += pagesize) { 5662306a36Sopenharmony_ci pfn_b = pagemap_get_pfn(pagemap_fd, addr + offs_b); 5762306a36Sopenharmony_ci if (pfn_b == -1ul || !pfn_b) 5862306a36Sopenharmony_ci continue; 5962306a36Sopenharmony_ci if (pfn_a == pfn_b) 6062306a36Sopenharmony_ci return true; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci return false; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic long get_my_ksm_zero_pages(void) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci char buf[200]; 6962306a36Sopenharmony_ci char *substr_ksm_zero; 7062306a36Sopenharmony_ci size_t value_pos; 7162306a36Sopenharmony_ci ssize_t read_size; 7262306a36Sopenharmony_ci unsigned long my_ksm_zero_pages; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci if (!proc_self_ksm_stat_fd) 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci read_size = pread(proc_self_ksm_stat_fd, buf, sizeof(buf) - 1, 0); 7862306a36Sopenharmony_ci if (read_size < 0) 7962306a36Sopenharmony_ci return -errno; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci buf[read_size] = 0; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci substr_ksm_zero = strstr(buf, "ksm_zero_pages"); 8462306a36Sopenharmony_ci if (!substr_ksm_zero) 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci value_pos = strcspn(substr_ksm_zero, "0123456789"); 8862306a36Sopenharmony_ci my_ksm_zero_pages = strtol(substr_ksm_zero + value_pos, NULL, 10); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci return my_ksm_zero_pages; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_cistatic long get_my_merging_pages(void) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci char buf[10]; 9662306a36Sopenharmony_ci ssize_t ret; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (proc_self_ksm_merging_pages_fd < 0) 9962306a36Sopenharmony_ci return proc_self_ksm_merging_pages_fd; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci ret = pread(proc_self_ksm_merging_pages_fd, buf, sizeof(buf) - 1, 0); 10262306a36Sopenharmony_ci if (ret <= 0) 10362306a36Sopenharmony_ci return -errno; 10462306a36Sopenharmony_ci buf[ret] = 0; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return strtol(buf, NULL, 10); 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic long ksm_get_full_scans(void) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci char buf[10]; 11262306a36Sopenharmony_ci ssize_t ret; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci ret = pread(ksm_full_scans_fd, buf, sizeof(buf) - 1, 0); 11562306a36Sopenharmony_ci if (ret <= 0) 11662306a36Sopenharmony_ci return -errno; 11762306a36Sopenharmony_ci buf[ret] = 0; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci return strtol(buf, NULL, 10); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int ksm_merge(void) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci long start_scans, end_scans; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Wait for two full scans such that any possible merging happened. */ 12762306a36Sopenharmony_ci start_scans = ksm_get_full_scans(); 12862306a36Sopenharmony_ci if (start_scans < 0) 12962306a36Sopenharmony_ci return start_scans; 13062306a36Sopenharmony_ci if (write(ksm_fd, "1", 1) != 1) 13162306a36Sopenharmony_ci return -errno; 13262306a36Sopenharmony_ci do { 13362306a36Sopenharmony_ci end_scans = ksm_get_full_scans(); 13462306a36Sopenharmony_ci if (end_scans < 0) 13562306a36Sopenharmony_ci return end_scans; 13662306a36Sopenharmony_ci } while (end_scans < start_scans + 2); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int ksm_unmerge(void) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci if (write(ksm_fd, "2", 1) != 1) 14462306a36Sopenharmony_ci return -errno; 14562306a36Sopenharmony_ci return 0; 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic char *mmap_and_merge_range(char val, unsigned long size, int prot, 14962306a36Sopenharmony_ci bool use_prctl) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci char *map; 15262306a36Sopenharmony_ci int ret; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* Stabilize accounting by disabling KSM completely. */ 15562306a36Sopenharmony_ci if (ksm_unmerge()) { 15662306a36Sopenharmony_ci ksft_test_result_fail("Disabling (unmerging) KSM failed\n"); 15762306a36Sopenharmony_ci goto unmap; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (get_my_merging_pages() > 0) { 16162306a36Sopenharmony_ci ksft_test_result_fail("Still pages merged\n"); 16262306a36Sopenharmony_ci goto unmap; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci map = mmap(NULL, size, PROT_READ|PROT_WRITE, 16662306a36Sopenharmony_ci MAP_PRIVATE|MAP_ANON, -1, 0); 16762306a36Sopenharmony_ci if (map == MAP_FAILED) { 16862306a36Sopenharmony_ci ksft_test_result_fail("mmap() failed\n"); 16962306a36Sopenharmony_ci return MAP_FAILED; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Don't use THP. Ignore if THP are not around on a kernel. */ 17362306a36Sopenharmony_ci if (madvise(map, size, MADV_NOHUGEPAGE) && errno != EINVAL) { 17462306a36Sopenharmony_ci ksft_test_result_fail("MADV_NOHUGEPAGE failed\n"); 17562306a36Sopenharmony_ci goto unmap; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* Make sure each page contains the same values to merge them. */ 17962306a36Sopenharmony_ci memset(map, val, size); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (mprotect(map, size, prot)) { 18262306a36Sopenharmony_ci ksft_test_result_skip("mprotect() failed\n"); 18362306a36Sopenharmony_ci goto unmap; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (use_prctl) { 18762306a36Sopenharmony_ci ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); 18862306a36Sopenharmony_ci if (ret < 0 && errno == EINVAL) { 18962306a36Sopenharmony_ci ksft_test_result_skip("PR_SET_MEMORY_MERGE not supported\n"); 19062306a36Sopenharmony_ci goto unmap; 19162306a36Sopenharmony_ci } else if (ret) { 19262306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 failed\n"); 19362306a36Sopenharmony_ci goto unmap; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci } else if (madvise(map, size, MADV_MERGEABLE)) { 19662306a36Sopenharmony_ci ksft_test_result_fail("MADV_MERGEABLE failed\n"); 19762306a36Sopenharmony_ci goto unmap; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci /* Run KSM to trigger merging and wait. */ 20162306a36Sopenharmony_ci if (ksm_merge()) { 20262306a36Sopenharmony_ci ksft_test_result_fail("Running KSM failed\n"); 20362306a36Sopenharmony_ci goto unmap; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* 20762306a36Sopenharmony_ci * Check if anything was merged at all. Ignore the zero page that is 20862306a36Sopenharmony_ci * accounted differently (depending on kernel support). 20962306a36Sopenharmony_ci */ 21062306a36Sopenharmony_ci if (val && !get_my_merging_pages()) { 21162306a36Sopenharmony_ci ksft_test_result_fail("No pages got merged\n"); 21262306a36Sopenharmony_ci goto unmap; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return map; 21662306a36Sopenharmony_ciunmap: 21762306a36Sopenharmony_ci munmap(map, size); 21862306a36Sopenharmony_ci return MAP_FAILED; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic void test_unmerge(void) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 22462306a36Sopenharmony_ci char *map; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false); 22962306a36Sopenharmony_ci if (map == MAP_FAILED) 23062306a36Sopenharmony_ci return; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (madvise(map, size, MADV_UNMERGEABLE)) { 23362306a36Sopenharmony_ci ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 23462306a36Sopenharmony_ci goto unmap; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 23862306a36Sopenharmony_ci "Pages were unmerged\n"); 23962306a36Sopenharmony_ciunmap: 24062306a36Sopenharmony_ci munmap(map, size); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic void test_unmerge_zero_pages(void) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 24662306a36Sopenharmony_ci char *map; 24762306a36Sopenharmony_ci unsigned int offs; 24862306a36Sopenharmony_ci unsigned long pages_expected; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (proc_self_ksm_stat_fd < 0) { 25362306a36Sopenharmony_ci ksft_test_result_skip("open(\"/proc/self/ksm_stat\") failed\n"); 25462306a36Sopenharmony_ci return; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci if (ksm_use_zero_pages_fd < 0) { 25762306a36Sopenharmony_ci ksft_test_result_skip("open \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n"); 25862306a36Sopenharmony_ci return; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci if (write(ksm_use_zero_pages_fd, "1", 1) != 1) { 26162306a36Sopenharmony_ci ksft_test_result_skip("write \"/sys/kernel/mm/ksm/use_zero_pages\" failed\n"); 26262306a36Sopenharmony_ci return; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* Let KSM deduplicate zero pages. */ 26662306a36Sopenharmony_ci map = mmap_and_merge_range(0x00, size, PROT_READ | PROT_WRITE, false); 26762306a36Sopenharmony_ci if (map == MAP_FAILED) 26862306a36Sopenharmony_ci return; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* Check if ksm_zero_pages is updated correctly after KSM merging */ 27162306a36Sopenharmony_ci pages_expected = size / pagesize; 27262306a36Sopenharmony_ci if (pages_expected != get_my_ksm_zero_pages()) { 27362306a36Sopenharmony_ci ksft_test_result_fail("'ksm_zero_pages' updated after merging\n"); 27462306a36Sopenharmony_ci goto unmap; 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci /* Try to unmerge half of the region */ 27862306a36Sopenharmony_ci if (madvise(map, size / 2, MADV_UNMERGEABLE)) { 27962306a36Sopenharmony_ci ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 28062306a36Sopenharmony_ci goto unmap; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci /* Check if ksm_zero_pages is updated correctly after unmerging */ 28462306a36Sopenharmony_ci pages_expected /= 2; 28562306a36Sopenharmony_ci if (pages_expected != get_my_ksm_zero_pages()) { 28662306a36Sopenharmony_ci ksft_test_result_fail("'ksm_zero_pages' updated after unmerging\n"); 28762306a36Sopenharmony_ci goto unmap; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Trigger unmerging of the other half by writing to the pages. */ 29162306a36Sopenharmony_ci for (offs = size / 2; offs < size; offs += pagesize) 29262306a36Sopenharmony_ci *((unsigned int *)&map[offs]) = offs; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* Now we should have no zeropages remaining. */ 29562306a36Sopenharmony_ci if (get_my_ksm_zero_pages()) { 29662306a36Sopenharmony_ci ksft_test_result_fail("'ksm_zero_pages' updated after write fault\n"); 29762306a36Sopenharmony_ci goto unmap; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* Check if ksm zero pages are really unmerged */ 30162306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 30262306a36Sopenharmony_ci "KSM zero pages were unmerged\n"); 30362306a36Sopenharmony_ciunmap: 30462306a36Sopenharmony_ci munmap(map, size); 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic void test_unmerge_discarded(void) 30862306a36Sopenharmony_ci{ 30962306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 31062306a36Sopenharmony_ci char *map; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false); 31562306a36Sopenharmony_ci if (map == MAP_FAILED) 31662306a36Sopenharmony_ci return; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci /* Discard half of all mapped pages so we have pte_none() entries. */ 31962306a36Sopenharmony_ci if (madvise(map, size / 2, MADV_DONTNEED)) { 32062306a36Sopenharmony_ci ksft_test_result_fail("MADV_DONTNEED failed\n"); 32162306a36Sopenharmony_ci goto unmap; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci if (madvise(map, size, MADV_UNMERGEABLE)) { 32562306a36Sopenharmony_ci ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 32662306a36Sopenharmony_ci goto unmap; 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 33062306a36Sopenharmony_ci "Pages were unmerged\n"); 33162306a36Sopenharmony_ciunmap: 33262306a36Sopenharmony_ci munmap(map, size); 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci#ifdef __NR_userfaultfd 33662306a36Sopenharmony_cistatic void test_unmerge_uffd_wp(void) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci struct uffdio_writeprotect uffd_writeprotect; 33962306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 34062306a36Sopenharmony_ci struct uffdio_api uffdio_api; 34162306a36Sopenharmony_ci char *map; 34262306a36Sopenharmony_ci int uffd; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false); 34762306a36Sopenharmony_ci if (map == MAP_FAILED) 34862306a36Sopenharmony_ci return; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci /* See if UFFD is around. */ 35162306a36Sopenharmony_ci uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 35262306a36Sopenharmony_ci if (uffd < 0) { 35362306a36Sopenharmony_ci ksft_test_result_skip("__NR_userfaultfd failed\n"); 35462306a36Sopenharmony_ci goto unmap; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci /* See if UFFD-WP is around. */ 35862306a36Sopenharmony_ci uffdio_api.api = UFFD_API; 35962306a36Sopenharmony_ci uffdio_api.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP; 36062306a36Sopenharmony_ci if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) { 36162306a36Sopenharmony_ci ksft_test_result_fail("UFFDIO_API failed\n"); 36262306a36Sopenharmony_ci goto close_uffd; 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci if (!(uffdio_api.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP)) { 36562306a36Sopenharmony_ci ksft_test_result_skip("UFFD_FEATURE_PAGEFAULT_FLAG_WP not available\n"); 36662306a36Sopenharmony_ci goto close_uffd; 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci /* Register UFFD-WP, no need for an actual handler. */ 37062306a36Sopenharmony_ci if (uffd_register(uffd, map, size, false, true, false)) { 37162306a36Sopenharmony_ci ksft_test_result_fail("UFFDIO_REGISTER_MODE_WP failed\n"); 37262306a36Sopenharmony_ci goto close_uffd; 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci /* Write-protect the range using UFFD-WP. */ 37662306a36Sopenharmony_ci uffd_writeprotect.range.start = (unsigned long) map; 37762306a36Sopenharmony_ci uffd_writeprotect.range.len = size; 37862306a36Sopenharmony_ci uffd_writeprotect.mode = UFFDIO_WRITEPROTECT_MODE_WP; 37962306a36Sopenharmony_ci if (ioctl(uffd, UFFDIO_WRITEPROTECT, &uffd_writeprotect)) { 38062306a36Sopenharmony_ci ksft_test_result_fail("UFFDIO_WRITEPROTECT failed\n"); 38162306a36Sopenharmony_ci goto close_uffd; 38262306a36Sopenharmony_ci } 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci if (madvise(map, size, MADV_UNMERGEABLE)) { 38562306a36Sopenharmony_ci ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 38662306a36Sopenharmony_ci goto close_uffd; 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 39062306a36Sopenharmony_ci "Pages were unmerged\n"); 39162306a36Sopenharmony_ciclose_uffd: 39262306a36Sopenharmony_ci close(uffd); 39362306a36Sopenharmony_ciunmap: 39462306a36Sopenharmony_ci munmap(map, size); 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci#endif 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci/* Verify that KSM can be enabled / queried with prctl. */ 39962306a36Sopenharmony_cistatic void test_prctl(void) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci int ret; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); 40662306a36Sopenharmony_ci if (ret < 0 && errno == EINVAL) { 40762306a36Sopenharmony_ci ksft_test_result_skip("PR_SET_MEMORY_MERGE not supported\n"); 40862306a36Sopenharmony_ci return; 40962306a36Sopenharmony_ci } else if (ret) { 41062306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 failed\n"); 41162306a36Sopenharmony_ci return; 41262306a36Sopenharmony_ci } 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci ret = prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0); 41562306a36Sopenharmony_ci if (ret < 0) { 41662306a36Sopenharmony_ci ksft_test_result_fail("PR_GET_MEMORY_MERGE failed\n"); 41762306a36Sopenharmony_ci return; 41862306a36Sopenharmony_ci } else if (ret != 1) { 41962306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 not effective\n"); 42062306a36Sopenharmony_ci return; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci ret = prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0); 42462306a36Sopenharmony_ci if (ret) { 42562306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 failed\n"); 42662306a36Sopenharmony_ci return; 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci ret = prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0); 43062306a36Sopenharmony_ci if (ret < 0) { 43162306a36Sopenharmony_ci ksft_test_result_fail("PR_GET_MEMORY_MERGE failed\n"); 43262306a36Sopenharmony_ci return; 43362306a36Sopenharmony_ci } else if (ret != 0) { 43462306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 not effective\n"); 43562306a36Sopenharmony_ci return; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci ksft_test_result_pass("Setting/clearing PR_SET_MEMORY_MERGE works\n"); 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci/* Verify that prctl ksm flag is inherited. */ 44262306a36Sopenharmony_cistatic void test_prctl_fork(void) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci int ret, status; 44562306a36Sopenharmony_ci pid_t child_pid; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0); 45062306a36Sopenharmony_ci if (ret < 0 && errno == EINVAL) { 45162306a36Sopenharmony_ci ksft_test_result_skip("PR_SET_MEMORY_MERGE not supported\n"); 45262306a36Sopenharmony_ci return; 45362306a36Sopenharmony_ci } else if (ret) { 45462306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=1 failed\n"); 45562306a36Sopenharmony_ci return; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci child_pid = fork(); 45962306a36Sopenharmony_ci if (!child_pid) { 46062306a36Sopenharmony_ci exit(prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0)); 46162306a36Sopenharmony_ci } else if (child_pid < 0) { 46262306a36Sopenharmony_ci ksft_test_result_fail("fork() failed\n"); 46362306a36Sopenharmony_ci return; 46462306a36Sopenharmony_ci } 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci if (waitpid(child_pid, &status, 0) < 0) { 46762306a36Sopenharmony_ci ksft_test_result_fail("waitpid() failed\n"); 46862306a36Sopenharmony_ci return; 46962306a36Sopenharmony_ci } else if (WEXITSTATUS(status) != 1) { 47062306a36Sopenharmony_ci ksft_test_result_fail("unexpected PR_GET_MEMORY_MERGE result in child\n"); 47162306a36Sopenharmony_ci return; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0)) { 47562306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 failed\n"); 47662306a36Sopenharmony_ci return; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci ksft_test_result_pass("PR_SET_MEMORY_MERGE value is inherited\n"); 48062306a36Sopenharmony_ci} 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_cistatic void test_prctl_unmerge(void) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 48562306a36Sopenharmony_ci char *map; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, true); 49062306a36Sopenharmony_ci if (map == MAP_FAILED) 49162306a36Sopenharmony_ci return; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci if (prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0)) { 49462306a36Sopenharmony_ci ksft_test_result_fail("PR_SET_MEMORY_MERGE=0 failed\n"); 49562306a36Sopenharmony_ci goto unmap; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 49962306a36Sopenharmony_ci "Pages were unmerged\n"); 50062306a36Sopenharmony_ciunmap: 50162306a36Sopenharmony_ci munmap(map, size); 50262306a36Sopenharmony_ci} 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_cistatic void test_prot_none(void) 50562306a36Sopenharmony_ci{ 50662306a36Sopenharmony_ci const unsigned int size = 2 * MiB; 50762306a36Sopenharmony_ci char *map; 50862306a36Sopenharmony_ci int i; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci ksft_print_msg("[RUN] %s\n", __func__); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci map = mmap_and_merge_range(0x11, size, PROT_NONE, false); 51362306a36Sopenharmony_ci if (map == MAP_FAILED) 51462306a36Sopenharmony_ci goto unmap; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci /* Store a unique value in each page on one half using ptrace */ 51762306a36Sopenharmony_ci for (i = 0; i < size / 2; i += pagesize) { 51862306a36Sopenharmony_ci lseek(mem_fd, (uintptr_t) map + i, SEEK_SET); 51962306a36Sopenharmony_ci if (write(mem_fd, &i, sizeof(i)) != sizeof(i)) { 52062306a36Sopenharmony_ci ksft_test_result_fail("ptrace write failed\n"); 52162306a36Sopenharmony_ci goto unmap; 52262306a36Sopenharmony_ci } 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci /* Trigger unsharing on the other half. */ 52662306a36Sopenharmony_ci if (madvise(map + size / 2, size / 2, MADV_UNMERGEABLE)) { 52762306a36Sopenharmony_ci ksft_test_result_fail("MADV_UNMERGEABLE failed\n"); 52862306a36Sopenharmony_ci goto unmap; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci ksft_test_result(!range_maps_duplicates(map, size), 53262306a36Sopenharmony_ci "Pages were unmerged\n"); 53362306a36Sopenharmony_ciunmap: 53462306a36Sopenharmony_ci munmap(map, size); 53562306a36Sopenharmony_ci} 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ciint main(int argc, char **argv) 53862306a36Sopenharmony_ci{ 53962306a36Sopenharmony_ci unsigned int tests = 7; 54062306a36Sopenharmony_ci int err; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci#ifdef __NR_userfaultfd 54362306a36Sopenharmony_ci tests++; 54462306a36Sopenharmony_ci#endif 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci ksft_print_header(); 54762306a36Sopenharmony_ci ksft_set_plan(tests); 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci pagesize = getpagesize(); 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci mem_fd = open("/proc/self/mem", O_RDWR); 55262306a36Sopenharmony_ci if (mem_fd < 0) 55362306a36Sopenharmony_ci ksft_exit_fail_msg("opening /proc/self/mem failed\n"); 55462306a36Sopenharmony_ci ksm_fd = open("/sys/kernel/mm/ksm/run", O_RDWR); 55562306a36Sopenharmony_ci if (ksm_fd < 0) 55662306a36Sopenharmony_ci ksft_exit_skip("open(\"/sys/kernel/mm/ksm/run\") failed\n"); 55762306a36Sopenharmony_ci ksm_full_scans_fd = open("/sys/kernel/mm/ksm/full_scans", O_RDONLY); 55862306a36Sopenharmony_ci if (ksm_full_scans_fd < 0) 55962306a36Sopenharmony_ci ksft_exit_skip("open(\"/sys/kernel/mm/ksm/full_scans\") failed\n"); 56062306a36Sopenharmony_ci pagemap_fd = open("/proc/self/pagemap", O_RDONLY); 56162306a36Sopenharmony_ci if (pagemap_fd < 0) 56262306a36Sopenharmony_ci ksft_exit_skip("open(\"/proc/self/pagemap\") failed\n"); 56362306a36Sopenharmony_ci proc_self_ksm_stat_fd = open("/proc/self/ksm_stat", O_RDONLY); 56462306a36Sopenharmony_ci proc_self_ksm_merging_pages_fd = open("/proc/self/ksm_merging_pages", 56562306a36Sopenharmony_ci O_RDONLY); 56662306a36Sopenharmony_ci ksm_use_zero_pages_fd = open("/sys/kernel/mm/ksm/use_zero_pages", O_RDWR); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci test_unmerge(); 56962306a36Sopenharmony_ci test_unmerge_zero_pages(); 57062306a36Sopenharmony_ci test_unmerge_discarded(); 57162306a36Sopenharmony_ci#ifdef __NR_userfaultfd 57262306a36Sopenharmony_ci test_unmerge_uffd_wp(); 57362306a36Sopenharmony_ci#endif 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci test_prot_none(); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci test_prctl(); 57862306a36Sopenharmony_ci test_prctl_fork(); 57962306a36Sopenharmony_ci test_prctl_unmerge(); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci err = ksft_get_fail_cnt(); 58262306a36Sopenharmony_ci if (err) 58362306a36Sopenharmony_ci ksft_exit_fail_msg("%d out of %d tests failed\n", 58462306a36Sopenharmony_ci err, ksft_test_num()); 58562306a36Sopenharmony_ci return ksft_exit_pass(); 58662306a36Sopenharmony_ci} 587