162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include <sys/mman.h>
462306a36Sopenharmony_ci#include <sys/prctl.h>
562306a36Sopenharmony_ci#include <sys/wait.h>
662306a36Sopenharmony_ci#include <stdbool.h>
762306a36Sopenharmony_ci#include <time.h>
862306a36Sopenharmony_ci#include <string.h>
962306a36Sopenharmony_ci#include <numa.h>
1062306a36Sopenharmony_ci#include <unistd.h>
1162306a36Sopenharmony_ci#include <fcntl.h>
1262306a36Sopenharmony_ci#include <stdint.h>
1362306a36Sopenharmony_ci#include <err.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "../kselftest.h"
1662306a36Sopenharmony_ci#include <include/vdso/time64.h>
1762306a36Sopenharmony_ci#include "vm_util.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define KSM_SYSFS_PATH "/sys/kernel/mm/ksm/"
2062306a36Sopenharmony_ci#define KSM_FP(s) (KSM_SYSFS_PATH s)
2162306a36Sopenharmony_ci#define KSM_SCAN_LIMIT_SEC_DEFAULT 120
2262306a36Sopenharmony_ci#define KSM_PAGE_COUNT_DEFAULT 10l
2362306a36Sopenharmony_ci#define KSM_PROT_STR_DEFAULT "rw"
2462306a36Sopenharmony_ci#define KSM_USE_ZERO_PAGES_DEFAULT false
2562306a36Sopenharmony_ci#define KSM_MERGE_ACROSS_NODES_DEFAULT true
2662306a36Sopenharmony_ci#define KSM_MERGE_TYPE_DEFAULT 0
2762306a36Sopenharmony_ci#define MB (1ul << 20)
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct ksm_sysfs {
3062306a36Sopenharmony_ci	unsigned long max_page_sharing;
3162306a36Sopenharmony_ci	unsigned long merge_across_nodes;
3262306a36Sopenharmony_ci	unsigned long pages_to_scan;
3362306a36Sopenharmony_ci	unsigned long run;
3462306a36Sopenharmony_ci	unsigned long sleep_millisecs;
3562306a36Sopenharmony_ci	unsigned long stable_node_chains_prune_millisecs;
3662306a36Sopenharmony_ci	unsigned long use_zero_pages;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cienum ksm_merge_type {
4062306a36Sopenharmony_ci	KSM_MERGE_MADVISE,
4162306a36Sopenharmony_ci	KSM_MERGE_PRCTL,
4262306a36Sopenharmony_ci	KSM_MERGE_LAST = KSM_MERGE_PRCTL
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cienum ksm_test_name {
4662306a36Sopenharmony_ci	CHECK_KSM_MERGE,
4762306a36Sopenharmony_ci	CHECK_KSM_UNMERGE,
4862306a36Sopenharmony_ci	CHECK_KSM_GET_MERGE_TYPE,
4962306a36Sopenharmony_ci	CHECK_KSM_ZERO_PAGE_MERGE,
5062306a36Sopenharmony_ci	CHECK_KSM_NUMA_MERGE,
5162306a36Sopenharmony_ci	KSM_MERGE_TIME,
5262306a36Sopenharmony_ci	KSM_MERGE_TIME_HUGE_PAGES,
5362306a36Sopenharmony_ci	KSM_UNMERGE_TIME,
5462306a36Sopenharmony_ci	KSM_COW_TIME
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ciint debug;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int ksm_write_sysfs(const char *file_path, unsigned long val)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	FILE *f = fopen(file_path, "w");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (!f) {
6462306a36Sopenharmony_ci		fprintf(stderr, "f %s\n", file_path);
6562306a36Sopenharmony_ci		perror("fopen");
6662306a36Sopenharmony_ci		return 1;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci	if (fprintf(f, "%lu", val) < 0) {
6962306a36Sopenharmony_ci		perror("fprintf");
7062306a36Sopenharmony_ci		fclose(f);
7162306a36Sopenharmony_ci		return 1;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci	fclose(f);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int ksm_read_sysfs(const char *file_path, unsigned long *val)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	FILE *f = fopen(file_path, "r");
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!f) {
8362306a36Sopenharmony_ci		fprintf(stderr, "f %s\n", file_path);
8462306a36Sopenharmony_ci		perror("fopen");
8562306a36Sopenharmony_ci		return 1;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci	if (fscanf(f, "%lu", val) != 1) {
8862306a36Sopenharmony_ci		perror("fscanf");
8962306a36Sopenharmony_ci		fclose(f);
9062306a36Sopenharmony_ci		return 1;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci	fclose(f);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void ksm_print_sysfs(void)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	unsigned long max_page_sharing, pages_sharing, pages_shared;
10062306a36Sopenharmony_ci	unsigned long full_scans, pages_unshared, pages_volatile;
10162306a36Sopenharmony_ci	unsigned long stable_node_chains, stable_node_dups;
10262306a36Sopenharmony_ci	long general_profit;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (ksm_read_sysfs(KSM_FP("pages_shared"), &pages_shared) ||
10562306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("pages_sharing"), &pages_sharing) ||
10662306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("max_page_sharing"), &max_page_sharing) ||
10762306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("full_scans"), &full_scans) ||
10862306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("pages_unshared"), &pages_unshared) ||
10962306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("pages_volatile"), &pages_volatile) ||
11062306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("stable_node_chains"), &stable_node_chains) ||
11162306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("stable_node_dups"), &stable_node_dups) ||
11262306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("general_profit"), (unsigned long *)&general_profit))
11362306a36Sopenharmony_ci		return;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	printf("pages_shared      : %lu\n", pages_shared);
11662306a36Sopenharmony_ci	printf("pages_sharing     : %lu\n", pages_sharing);
11762306a36Sopenharmony_ci	printf("max_page_sharing  : %lu\n", max_page_sharing);
11862306a36Sopenharmony_ci	printf("full_scans        : %lu\n", full_scans);
11962306a36Sopenharmony_ci	printf("pages_unshared    : %lu\n", pages_unshared);
12062306a36Sopenharmony_ci	printf("pages_volatile    : %lu\n", pages_volatile);
12162306a36Sopenharmony_ci	printf("stable_node_chains: %lu\n", stable_node_chains);
12262306a36Sopenharmony_ci	printf("stable_node_dups  : %lu\n", stable_node_dups);
12362306a36Sopenharmony_ci	printf("general_profit    : %ld\n", general_profit);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic void ksm_print_procfs(void)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	const char *file_name = "/proc/self/ksm_stat";
12962306a36Sopenharmony_ci	char buffer[512];
13062306a36Sopenharmony_ci	FILE *f = fopen(file_name, "r");
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (!f) {
13362306a36Sopenharmony_ci		fprintf(stderr, "f %s\n", file_name);
13462306a36Sopenharmony_ci		perror("fopen");
13562306a36Sopenharmony_ci		return;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	while (fgets(buffer, sizeof(buffer), f))
13962306a36Sopenharmony_ci		printf("%s", buffer);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	fclose(f);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int str_to_prot(char *prot_str)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	int prot = 0;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if ((strchr(prot_str, 'r')) != NULL)
14962306a36Sopenharmony_ci		prot |= PROT_READ;
15062306a36Sopenharmony_ci	if ((strchr(prot_str, 'w')) != NULL)
15162306a36Sopenharmony_ci		prot |= PROT_WRITE;
15262306a36Sopenharmony_ci	if ((strchr(prot_str, 'x')) != NULL)
15362306a36Sopenharmony_ci		prot |= PROT_EXEC;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	return prot;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void print_help(void)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	printf("usage: ksm_tests [-h] <test type> [-a prot] [-p page_count] [-l timeout]\n"
16162306a36Sopenharmony_ci	       "[-z use_zero_pages] [-m merge_across_nodes] [-s size]\n");
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	printf("Supported <test type>:\n"
16462306a36Sopenharmony_ci	       " -M (page merging)\n"
16562306a36Sopenharmony_ci	       " -Z (zero pages merging)\n"
16662306a36Sopenharmony_ci	       " -N (merging of pages in different NUMA nodes)\n"
16762306a36Sopenharmony_ci	       " -U (page unmerging)\n"
16862306a36Sopenharmony_ci	       " -P evaluate merging time and speed.\n"
16962306a36Sopenharmony_ci	       "    For this test, the size of duplicated memory area (in MiB)\n"
17062306a36Sopenharmony_ci	       "    must be provided using -s option\n"
17162306a36Sopenharmony_ci	       " -H evaluate merging time and speed of area allocated mostly with huge pages\n"
17262306a36Sopenharmony_ci	       "    For this test, the size of duplicated memory area (in MiB)\n"
17362306a36Sopenharmony_ci	       "    must be provided using -s option\n"
17462306a36Sopenharmony_ci	       " -D evaluate unmerging time and speed when disabling KSM.\n"
17562306a36Sopenharmony_ci	       "    For this test, the size of duplicated memory area (in MiB)\n"
17662306a36Sopenharmony_ci	       "    must be provided using -s option\n"
17762306a36Sopenharmony_ci	       " -C evaluate the time required to break COW of merged pages.\n\n");
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	printf(" -a: specify the access protections of pages.\n"
18062306a36Sopenharmony_ci	       "     <prot> must be of the form [rwx].\n"
18162306a36Sopenharmony_ci	       "     Default: %s\n", KSM_PROT_STR_DEFAULT);
18262306a36Sopenharmony_ci	printf(" -p: specify the number of pages to test.\n"
18362306a36Sopenharmony_ci	       "     Default: %ld\n", KSM_PAGE_COUNT_DEFAULT);
18462306a36Sopenharmony_ci	printf(" -l: limit the maximum running time (in seconds) for a test.\n"
18562306a36Sopenharmony_ci	       "     Default: %d seconds\n", KSM_SCAN_LIMIT_SEC_DEFAULT);
18662306a36Sopenharmony_ci	printf(" -z: change use_zero_pages tunable\n"
18762306a36Sopenharmony_ci	       "     Default: %d\n", KSM_USE_ZERO_PAGES_DEFAULT);
18862306a36Sopenharmony_ci	printf(" -m: change merge_across_nodes tunable\n"
18962306a36Sopenharmony_ci	       "     Default: %d\n", KSM_MERGE_ACROSS_NODES_DEFAULT);
19062306a36Sopenharmony_ci	printf(" -d: turn debugging output on\n");
19162306a36Sopenharmony_ci	printf(" -s: the size of duplicated memory area (in MiB)\n");
19262306a36Sopenharmony_ci	printf(" -t: KSM merge type\n"
19362306a36Sopenharmony_ci	       "     Default: 0\n"
19462306a36Sopenharmony_ci	       "     0: madvise merging\n"
19562306a36Sopenharmony_ci	       "     1: prctl merging\n");
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	exit(0);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic void  *allocate_memory(void *ptr, int prot, int mapping, char data, size_t map_size)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	void *map_ptr = mmap(ptr, map_size, PROT_WRITE, mapping, -1, 0);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (!map_ptr) {
20562306a36Sopenharmony_ci		perror("mmap");
20662306a36Sopenharmony_ci		return NULL;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci	memset(map_ptr, data, map_size);
20962306a36Sopenharmony_ci	if (mprotect(map_ptr, map_size, prot)) {
21062306a36Sopenharmony_ci		perror("mprotect");
21162306a36Sopenharmony_ci		munmap(map_ptr, map_size);
21262306a36Sopenharmony_ci		return NULL;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	return map_ptr;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int ksm_do_scan(int scan_count, struct timespec start_time, int timeout)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct timespec cur_time;
22162306a36Sopenharmony_ci	unsigned long cur_scan, init_scan;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	if (ksm_read_sysfs(KSM_FP("full_scans"), &init_scan))
22462306a36Sopenharmony_ci		return 1;
22562306a36Sopenharmony_ci	cur_scan = init_scan;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	while (cur_scan < init_scan + scan_count) {
22862306a36Sopenharmony_ci		if (ksm_read_sysfs(KSM_FP("full_scans"), &cur_scan))
22962306a36Sopenharmony_ci			return 1;
23062306a36Sopenharmony_ci		if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time)) {
23162306a36Sopenharmony_ci			perror("clock_gettime");
23262306a36Sopenharmony_ci			return 1;
23362306a36Sopenharmony_ci		}
23462306a36Sopenharmony_ci		if ((cur_time.tv_sec - start_time.tv_sec) > timeout) {
23562306a36Sopenharmony_ci			printf("Scan time limit exceeded\n");
23662306a36Sopenharmony_ci			return 1;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	return 0;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int ksm_merge_pages(int merge_type, void *addr, size_t size,
24462306a36Sopenharmony_ci			struct timespec start_time, int timeout)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	if (merge_type == KSM_MERGE_MADVISE) {
24762306a36Sopenharmony_ci		if (madvise(addr, size, MADV_MERGEABLE)) {
24862306a36Sopenharmony_ci			perror("madvise");
24962306a36Sopenharmony_ci			return 1;
25062306a36Sopenharmony_ci		}
25162306a36Sopenharmony_ci	} else if (merge_type == KSM_MERGE_PRCTL) {
25262306a36Sopenharmony_ci		if (prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0)) {
25362306a36Sopenharmony_ci			perror("prctl");
25462306a36Sopenharmony_ci			return 1;
25562306a36Sopenharmony_ci		}
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (ksm_write_sysfs(KSM_FP("run"), 1))
25962306a36Sopenharmony_ci		return 1;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	/* Since merging occurs only after 2 scans, make sure to get at least 2 full scans */
26262306a36Sopenharmony_ci	if (ksm_do_scan(2, start_time, timeout))
26362306a36Sopenharmony_ci		return 1;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	return 0;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic int ksm_unmerge_pages(void *addr, size_t size,
26962306a36Sopenharmony_ci			     struct timespec start_time, int timeout)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	if (madvise(addr, size, MADV_UNMERGEABLE)) {
27262306a36Sopenharmony_ci		perror("madvise");
27362306a36Sopenharmony_ci		return 1;
27462306a36Sopenharmony_ci	}
27562306a36Sopenharmony_ci	return 0;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic bool assert_ksm_pages_count(long dupl_page_count)
27962306a36Sopenharmony_ci{
28062306a36Sopenharmony_ci	unsigned long max_page_sharing, pages_sharing, pages_shared;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	if (ksm_read_sysfs(KSM_FP("pages_shared"), &pages_shared) ||
28362306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("pages_sharing"), &pages_sharing) ||
28462306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("max_page_sharing"), &max_page_sharing))
28562306a36Sopenharmony_ci		return false;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (debug) {
28862306a36Sopenharmony_ci		ksm_print_sysfs();
28962306a36Sopenharmony_ci		ksm_print_procfs();
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/*
29362306a36Sopenharmony_ci	 * Since there must be at least 2 pages for merging and 1 page can be
29462306a36Sopenharmony_ci	 * shared with the limited number of pages (max_page_sharing), sometimes
29562306a36Sopenharmony_ci	 * there are 'leftover' pages that cannot be merged. For example, if there
29662306a36Sopenharmony_ci	 * are 11 pages and max_page_sharing = 10, then only 10 pages will be
29762306a36Sopenharmony_ci	 * merged and the 11th page won't be affected. As a result, when the number
29862306a36Sopenharmony_ci	 * of duplicate pages is divided by max_page_sharing and the remainder is 1,
29962306a36Sopenharmony_ci	 * pages_shared and pages_sharing values will be equal between dupl_page_count
30062306a36Sopenharmony_ci	 * and dupl_page_count - 1.
30162306a36Sopenharmony_ci	 */
30262306a36Sopenharmony_ci	if (dupl_page_count % max_page_sharing == 1 || dupl_page_count % max_page_sharing == 0) {
30362306a36Sopenharmony_ci		if (pages_shared == dupl_page_count / max_page_sharing &&
30462306a36Sopenharmony_ci		    pages_sharing == pages_shared * (max_page_sharing - 1))
30562306a36Sopenharmony_ci			return true;
30662306a36Sopenharmony_ci	} else {
30762306a36Sopenharmony_ci		if (pages_shared == (dupl_page_count / max_page_sharing + 1) &&
30862306a36Sopenharmony_ci		    pages_sharing == dupl_page_count - pages_shared)
30962306a36Sopenharmony_ci			return true;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	return false;
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic int ksm_save_def(struct ksm_sysfs *ksm_sysfs)
31662306a36Sopenharmony_ci{
31762306a36Sopenharmony_ci	if (ksm_read_sysfs(KSM_FP("max_page_sharing"), &ksm_sysfs->max_page_sharing) ||
31862306a36Sopenharmony_ci	    numa_available() ? 0 :
31962306a36Sopenharmony_ci		ksm_read_sysfs(KSM_FP("merge_across_nodes"), &ksm_sysfs->merge_across_nodes) ||
32062306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("sleep_millisecs"), &ksm_sysfs->sleep_millisecs) ||
32162306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("pages_to_scan"), &ksm_sysfs->pages_to_scan) ||
32262306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("run"), &ksm_sysfs->run) ||
32362306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("stable_node_chains_prune_millisecs"),
32462306a36Sopenharmony_ci			   &ksm_sysfs->stable_node_chains_prune_millisecs) ||
32562306a36Sopenharmony_ci	    ksm_read_sysfs(KSM_FP("use_zero_pages"), &ksm_sysfs->use_zero_pages))
32662306a36Sopenharmony_ci		return 1;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	return 0;
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_cistatic int ksm_restore(struct ksm_sysfs *ksm_sysfs)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	if (ksm_write_sysfs(KSM_FP("max_page_sharing"), ksm_sysfs->max_page_sharing) ||
33462306a36Sopenharmony_ci	    numa_available() ? 0 :
33562306a36Sopenharmony_ci		ksm_write_sysfs(KSM_FP("merge_across_nodes"), ksm_sysfs->merge_across_nodes) ||
33662306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("pages_to_scan"), ksm_sysfs->pages_to_scan) ||
33762306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("run"), ksm_sysfs->run) ||
33862306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("sleep_millisecs"), ksm_sysfs->sleep_millisecs) ||
33962306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("stable_node_chains_prune_millisecs"),
34062306a36Sopenharmony_ci			    ksm_sysfs->stable_node_chains_prune_millisecs) ||
34162306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("use_zero_pages"), ksm_sysfs->use_zero_pages))
34262306a36Sopenharmony_ci		return 1;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	return 0;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int check_ksm_merge(int merge_type, int mapping, int prot,
34862306a36Sopenharmony_ci			long page_count, int timeout, size_t page_size)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	void *map_ptr;
35162306a36Sopenharmony_ci	struct timespec start_time;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
35462306a36Sopenharmony_ci		perror("clock_gettime");
35562306a36Sopenharmony_ci		return KSFT_FAIL;
35662306a36Sopenharmony_ci	}
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	/* fill pages with the same data and merge them */
35962306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, '*', page_size * page_count);
36062306a36Sopenharmony_ci	if (!map_ptr)
36162306a36Sopenharmony_ci		return KSFT_FAIL;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout))
36462306a36Sopenharmony_ci		goto err_out;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	/* verify that the right number of pages are merged */
36762306a36Sopenharmony_ci	if (assert_ksm_pages_count(page_count)) {
36862306a36Sopenharmony_ci		printf("OK\n");
36962306a36Sopenharmony_ci		munmap(map_ptr, page_size * page_count);
37062306a36Sopenharmony_ci		if (merge_type == KSM_MERGE_PRCTL)
37162306a36Sopenharmony_ci			prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0);
37262306a36Sopenharmony_ci		return KSFT_PASS;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_cierr_out:
37662306a36Sopenharmony_ci	printf("Not OK\n");
37762306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
37862306a36Sopenharmony_ci	return KSFT_FAIL;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int check_ksm_unmerge(int merge_type, int mapping, int prot, int timeout, size_t page_size)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	void *map_ptr;
38462306a36Sopenharmony_ci	struct timespec start_time;
38562306a36Sopenharmony_ci	int page_count = 2;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
38862306a36Sopenharmony_ci		perror("clock_gettime");
38962306a36Sopenharmony_ci		return KSFT_FAIL;
39062306a36Sopenharmony_ci	}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	/* fill pages with the same data and merge them */
39362306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, '*', page_size * page_count);
39462306a36Sopenharmony_ci	if (!map_ptr)
39562306a36Sopenharmony_ci		return KSFT_FAIL;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout))
39862306a36Sopenharmony_ci		goto err_out;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	/* change 1 byte in each of the 2 pages -- KSM must automatically unmerge them */
40162306a36Sopenharmony_ci	memset(map_ptr, '-', 1);
40262306a36Sopenharmony_ci	memset(map_ptr + page_size, '+', 1);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	/* get at least 1 scan, so KSM can detect that the pages were modified */
40562306a36Sopenharmony_ci	if (ksm_do_scan(1, start_time, timeout))
40662306a36Sopenharmony_ci		goto err_out;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	/* check that unmerging was successful and 0 pages are currently merged */
40962306a36Sopenharmony_ci	if (assert_ksm_pages_count(0)) {
41062306a36Sopenharmony_ci		printf("OK\n");
41162306a36Sopenharmony_ci		munmap(map_ptr, page_size * page_count);
41262306a36Sopenharmony_ci		return KSFT_PASS;
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cierr_out:
41662306a36Sopenharmony_ci	printf("Not OK\n");
41762306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
41862306a36Sopenharmony_ci	return KSFT_FAIL;
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_cistatic int check_ksm_zero_page_merge(int merge_type, int mapping, int prot, long page_count,
42262306a36Sopenharmony_ci				int timeout, bool use_zero_pages, size_t page_size)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	void *map_ptr;
42562306a36Sopenharmony_ci	struct timespec start_time;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
42862306a36Sopenharmony_ci		perror("clock_gettime");
42962306a36Sopenharmony_ci		return KSFT_FAIL;
43062306a36Sopenharmony_ci	}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	if (ksm_write_sysfs(KSM_FP("use_zero_pages"), use_zero_pages))
43362306a36Sopenharmony_ci		return KSFT_FAIL;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* fill pages with zero and try to merge them */
43662306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, 0, page_size * page_count);
43762306a36Sopenharmony_ci	if (!map_ptr)
43862306a36Sopenharmony_ci		return KSFT_FAIL;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout))
44162306a36Sopenharmony_ci		goto err_out;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci       /*
44462306a36Sopenharmony_ci	* verify that the right number of pages are merged:
44562306a36Sopenharmony_ci	* 1) if use_zero_pages is set to 1, empty pages are merged
44662306a36Sopenharmony_ci	*    with the kernel zero page instead of with each other;
44762306a36Sopenharmony_ci	* 2) if use_zero_pages is set to 0, empty pages are not treated specially
44862306a36Sopenharmony_ci	*    and merged as usual.
44962306a36Sopenharmony_ci	*/
45062306a36Sopenharmony_ci	if (use_zero_pages && !assert_ksm_pages_count(0))
45162306a36Sopenharmony_ci		goto err_out;
45262306a36Sopenharmony_ci	else if (!use_zero_pages && !assert_ksm_pages_count(page_count))
45362306a36Sopenharmony_ci		goto err_out;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	printf("OK\n");
45662306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
45762306a36Sopenharmony_ci	return KSFT_PASS;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_cierr_out:
46062306a36Sopenharmony_ci	printf("Not OK\n");
46162306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
46262306a36Sopenharmony_ci	return KSFT_FAIL;
46362306a36Sopenharmony_ci}
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_cistatic int get_next_mem_node(int node)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	long node_size;
46962306a36Sopenharmony_ci	int mem_node = 0;
47062306a36Sopenharmony_ci	int i, max_node = numa_max_node();
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	for (i = node + 1; i <= max_node + node; i++) {
47362306a36Sopenharmony_ci		mem_node = i % (max_node + 1);
47462306a36Sopenharmony_ci		node_size = numa_node_size(mem_node, NULL);
47562306a36Sopenharmony_ci		if (node_size > 0)
47662306a36Sopenharmony_ci			break;
47762306a36Sopenharmony_ci	}
47862306a36Sopenharmony_ci	return mem_node;
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_cistatic int get_first_mem_node(void)
48262306a36Sopenharmony_ci{
48362306a36Sopenharmony_ci	return get_next_mem_node(numa_max_node());
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cistatic int check_ksm_numa_merge(int merge_type, int mapping, int prot, int timeout,
48762306a36Sopenharmony_ci				bool merge_across_nodes, size_t page_size)
48862306a36Sopenharmony_ci{
48962306a36Sopenharmony_ci	void *numa1_map_ptr, *numa2_map_ptr;
49062306a36Sopenharmony_ci	struct timespec start_time;
49162306a36Sopenharmony_ci	int page_count = 2;
49262306a36Sopenharmony_ci	int first_node;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
49562306a36Sopenharmony_ci		perror("clock_gettime");
49662306a36Sopenharmony_ci		return KSFT_FAIL;
49762306a36Sopenharmony_ci	}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (numa_available() < 0) {
50062306a36Sopenharmony_ci		perror("NUMA support not enabled");
50162306a36Sopenharmony_ci		return KSFT_SKIP;
50262306a36Sopenharmony_ci	}
50362306a36Sopenharmony_ci	if (numa_num_configured_nodes() <= 1) {
50462306a36Sopenharmony_ci		printf("At least 2 NUMA nodes must be available\n");
50562306a36Sopenharmony_ci		return KSFT_SKIP;
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci	if (ksm_write_sysfs(KSM_FP("merge_across_nodes"), merge_across_nodes))
50862306a36Sopenharmony_ci		return KSFT_FAIL;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	/* allocate 2 pages in 2 different NUMA nodes and fill them with the same data */
51162306a36Sopenharmony_ci	first_node = get_first_mem_node();
51262306a36Sopenharmony_ci	numa1_map_ptr = numa_alloc_onnode(page_size, first_node);
51362306a36Sopenharmony_ci	numa2_map_ptr = numa_alloc_onnode(page_size, get_next_mem_node(first_node));
51462306a36Sopenharmony_ci	if (!numa1_map_ptr || !numa2_map_ptr) {
51562306a36Sopenharmony_ci		perror("numa_alloc_onnode");
51662306a36Sopenharmony_ci		return KSFT_FAIL;
51762306a36Sopenharmony_ci	}
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	memset(numa1_map_ptr, '*', page_size);
52062306a36Sopenharmony_ci	memset(numa2_map_ptr, '*', page_size);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	/* try to merge the pages */
52362306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, numa1_map_ptr, page_size, start_time, timeout) ||
52462306a36Sopenharmony_ci	    ksm_merge_pages(merge_type, numa2_map_ptr, page_size, start_time, timeout))
52562306a36Sopenharmony_ci		goto err_out;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci       /*
52862306a36Sopenharmony_ci	* verify that the right number of pages are merged:
52962306a36Sopenharmony_ci	* 1) if merge_across_nodes was enabled, 2 duplicate pages will be merged;
53062306a36Sopenharmony_ci	* 2) if merge_across_nodes = 0, there must be 0 merged pages, since there is
53162306a36Sopenharmony_ci	*    only 1 unique page in each node and they can't be shared.
53262306a36Sopenharmony_ci	*/
53362306a36Sopenharmony_ci	if (merge_across_nodes && !assert_ksm_pages_count(page_count))
53462306a36Sopenharmony_ci		goto err_out;
53562306a36Sopenharmony_ci	else if (!merge_across_nodes && !assert_ksm_pages_count(0))
53662306a36Sopenharmony_ci		goto err_out;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	numa_free(numa1_map_ptr, page_size);
53962306a36Sopenharmony_ci	numa_free(numa2_map_ptr, page_size);
54062306a36Sopenharmony_ci	printf("OK\n");
54162306a36Sopenharmony_ci	return KSFT_PASS;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_cierr_out:
54462306a36Sopenharmony_ci	numa_free(numa1_map_ptr, page_size);
54562306a36Sopenharmony_ci	numa_free(numa2_map_ptr, page_size);
54662306a36Sopenharmony_ci	printf("Not OK\n");
54762306a36Sopenharmony_ci	return KSFT_FAIL;
54862306a36Sopenharmony_ci}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_cistatic int ksm_merge_hugepages_time(int merge_type, int mapping, int prot,
55162306a36Sopenharmony_ci				int timeout, size_t map_size)
55262306a36Sopenharmony_ci{
55362306a36Sopenharmony_ci	void *map_ptr, *map_ptr_orig;
55462306a36Sopenharmony_ci	struct timespec start_time, end_time;
55562306a36Sopenharmony_ci	unsigned long scan_time_ns;
55662306a36Sopenharmony_ci	int pagemap_fd, n_normal_pages, n_huge_pages;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	map_size *= MB;
55962306a36Sopenharmony_ci	size_t len = map_size;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	len -= len % HPAGE_SIZE;
56262306a36Sopenharmony_ci	map_ptr_orig = mmap(NULL, len + HPAGE_SIZE, PROT_READ | PROT_WRITE,
56362306a36Sopenharmony_ci			MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE, -1, 0);
56462306a36Sopenharmony_ci	map_ptr = map_ptr_orig + HPAGE_SIZE - (uintptr_t)map_ptr_orig % HPAGE_SIZE;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	if (map_ptr_orig == MAP_FAILED)
56762306a36Sopenharmony_ci		err(2, "initial mmap");
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci	if (madvise(map_ptr, len, MADV_HUGEPAGE))
57062306a36Sopenharmony_ci		err(2, "MADV_HUGEPAGE");
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
57362306a36Sopenharmony_ci	if (pagemap_fd < 0)
57462306a36Sopenharmony_ci		err(2, "open pagemap");
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	n_normal_pages = 0;
57762306a36Sopenharmony_ci	n_huge_pages = 0;
57862306a36Sopenharmony_ci	for (void *p = map_ptr; p < map_ptr + len; p += HPAGE_SIZE) {
57962306a36Sopenharmony_ci		if (allocate_transhuge(p, pagemap_fd) < 0)
58062306a36Sopenharmony_ci			n_normal_pages++;
58162306a36Sopenharmony_ci		else
58262306a36Sopenharmony_ci			n_huge_pages++;
58362306a36Sopenharmony_ci	}
58462306a36Sopenharmony_ci	printf("Number of normal pages:    %d\n", n_normal_pages);
58562306a36Sopenharmony_ci	printf("Number of huge pages:    %d\n", n_huge_pages);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	memset(map_ptr, '*', len);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
59062306a36Sopenharmony_ci		perror("clock_gettime");
59162306a36Sopenharmony_ci		goto err_out;
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout))
59462306a36Sopenharmony_ci		goto err_out;
59562306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
59662306a36Sopenharmony_ci		perror("clock_gettime");
59762306a36Sopenharmony_ci		goto err_out;
59862306a36Sopenharmony_ci	}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	scan_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
60162306a36Sopenharmony_ci		       (end_time.tv_nsec - start_time.tv_nsec);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	printf("Total size:    %lu MiB\n", map_size / MB);
60462306a36Sopenharmony_ci	printf("Total time:    %ld.%09ld s\n", scan_time_ns / NSEC_PER_SEC,
60562306a36Sopenharmony_ci	       scan_time_ns % NSEC_PER_SEC);
60662306a36Sopenharmony_ci	printf("Average speed:  %.3f MiB/s\n", (map_size / MB) /
60762306a36Sopenharmony_ci					       ((double)scan_time_ns / NSEC_PER_SEC));
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	munmap(map_ptr_orig, len + HPAGE_SIZE);
61062306a36Sopenharmony_ci	return KSFT_PASS;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_cierr_out:
61362306a36Sopenharmony_ci	printf("Not OK\n");
61462306a36Sopenharmony_ci	munmap(map_ptr_orig, len + HPAGE_SIZE);
61562306a36Sopenharmony_ci	return KSFT_FAIL;
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic int ksm_merge_time(int merge_type, int mapping, int prot, int timeout, size_t map_size)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	void *map_ptr;
62162306a36Sopenharmony_ci	struct timespec start_time, end_time;
62262306a36Sopenharmony_ci	unsigned long scan_time_ns;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	map_size *= MB;
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, '*', map_size);
62762306a36Sopenharmony_ci	if (!map_ptr)
62862306a36Sopenharmony_ci		return KSFT_FAIL;
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
63162306a36Sopenharmony_ci		perror("clock_gettime");
63262306a36Sopenharmony_ci		goto err_out;
63362306a36Sopenharmony_ci	}
63462306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout))
63562306a36Sopenharmony_ci		goto err_out;
63662306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
63762306a36Sopenharmony_ci		perror("clock_gettime");
63862306a36Sopenharmony_ci		goto err_out;
63962306a36Sopenharmony_ci	}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	scan_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
64262306a36Sopenharmony_ci		       (end_time.tv_nsec - start_time.tv_nsec);
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	printf("Total size:    %lu MiB\n", map_size / MB);
64562306a36Sopenharmony_ci	printf("Total time:    %ld.%09ld s\n", scan_time_ns / NSEC_PER_SEC,
64662306a36Sopenharmony_ci	       scan_time_ns % NSEC_PER_SEC);
64762306a36Sopenharmony_ci	printf("Average speed:  %.3f MiB/s\n", (map_size / MB) /
64862306a36Sopenharmony_ci					       ((double)scan_time_ns / NSEC_PER_SEC));
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	munmap(map_ptr, map_size);
65162306a36Sopenharmony_ci	return KSFT_PASS;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_cierr_out:
65462306a36Sopenharmony_ci	printf("Not OK\n");
65562306a36Sopenharmony_ci	munmap(map_ptr, map_size);
65662306a36Sopenharmony_ci	return KSFT_FAIL;
65762306a36Sopenharmony_ci}
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_cistatic int ksm_unmerge_time(int merge_type, int mapping, int prot, int timeout, size_t map_size)
66062306a36Sopenharmony_ci{
66162306a36Sopenharmony_ci	void *map_ptr;
66262306a36Sopenharmony_ci	struct timespec start_time, end_time;
66362306a36Sopenharmony_ci	unsigned long scan_time_ns;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	map_size *= MB;
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, '*', map_size);
66862306a36Sopenharmony_ci	if (!map_ptr)
66962306a36Sopenharmony_ci		return KSFT_FAIL;
67062306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
67162306a36Sopenharmony_ci		perror("clock_gettime");
67262306a36Sopenharmony_ci		goto err_out;
67362306a36Sopenharmony_ci	}
67462306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, map_size, start_time, timeout))
67562306a36Sopenharmony_ci		goto err_out;
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
67862306a36Sopenharmony_ci		perror("clock_gettime");
67962306a36Sopenharmony_ci		goto err_out;
68062306a36Sopenharmony_ci	}
68162306a36Sopenharmony_ci	if (ksm_unmerge_pages(map_ptr, map_size, start_time, timeout))
68262306a36Sopenharmony_ci		goto err_out;
68362306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
68462306a36Sopenharmony_ci		perror("clock_gettime");
68562306a36Sopenharmony_ci		goto err_out;
68662306a36Sopenharmony_ci	}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	scan_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
68962306a36Sopenharmony_ci		       (end_time.tv_nsec - start_time.tv_nsec);
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	printf("Total size:    %lu MiB\n", map_size / MB);
69262306a36Sopenharmony_ci	printf("Total time:    %ld.%09ld s\n", scan_time_ns / NSEC_PER_SEC,
69362306a36Sopenharmony_ci	       scan_time_ns % NSEC_PER_SEC);
69462306a36Sopenharmony_ci	printf("Average speed:  %.3f MiB/s\n", (map_size / MB) /
69562306a36Sopenharmony_ci					       ((double)scan_time_ns / NSEC_PER_SEC));
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	munmap(map_ptr, map_size);
69862306a36Sopenharmony_ci	return KSFT_PASS;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_cierr_out:
70162306a36Sopenharmony_ci	printf("Not OK\n");
70262306a36Sopenharmony_ci	munmap(map_ptr, map_size);
70362306a36Sopenharmony_ci	return KSFT_FAIL;
70462306a36Sopenharmony_ci}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_cistatic int ksm_cow_time(int merge_type, int mapping, int prot, int timeout, size_t page_size)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	void *map_ptr;
70962306a36Sopenharmony_ci	struct timespec start_time, end_time;
71062306a36Sopenharmony_ci	unsigned long cow_time_ns;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	/* page_count must be less than 2*page_size */
71362306a36Sopenharmony_ci	size_t page_count = 4000;
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci	map_ptr = allocate_memory(NULL, prot, mapping, '*', page_size * page_count);
71662306a36Sopenharmony_ci	if (!map_ptr)
71762306a36Sopenharmony_ci		return KSFT_FAIL;
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
72062306a36Sopenharmony_ci		perror("clock_gettime");
72162306a36Sopenharmony_ci		return KSFT_FAIL;
72262306a36Sopenharmony_ci	}
72362306a36Sopenharmony_ci	for (size_t i = 0; i < page_count - 1; i = i + 2)
72462306a36Sopenharmony_ci		memset(map_ptr + page_size * i, '-', 1);
72562306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
72662306a36Sopenharmony_ci		perror("clock_gettime");
72762306a36Sopenharmony_ci		return KSFT_FAIL;
72862306a36Sopenharmony_ci	}
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	cow_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
73162306a36Sopenharmony_ci		       (end_time.tv_nsec - start_time.tv_nsec);
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	printf("Total size:    %lu MiB\n\n", (page_size * page_count) / MB);
73462306a36Sopenharmony_ci	printf("Not merged pages:\n");
73562306a36Sopenharmony_ci	printf("Total time:     %ld.%09ld s\n", cow_time_ns / NSEC_PER_SEC,
73662306a36Sopenharmony_ci	       cow_time_ns % NSEC_PER_SEC);
73762306a36Sopenharmony_ci	printf("Average speed:  %.3f MiB/s\n\n", ((page_size * (page_count / 2)) / MB) /
73862306a36Sopenharmony_ci					       ((double)cow_time_ns / NSEC_PER_SEC));
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	/* Create 2000 pairs of duplicate pages */
74162306a36Sopenharmony_ci	for (size_t i = 0; i < page_count - 1; i = i + 2) {
74262306a36Sopenharmony_ci		memset(map_ptr + page_size * i, '+', i / 2 + 1);
74362306a36Sopenharmony_ci		memset(map_ptr + page_size * (i + 1), '+', i / 2 + 1);
74462306a36Sopenharmony_ci	}
74562306a36Sopenharmony_ci	if (ksm_merge_pages(merge_type, map_ptr, page_size * page_count, start_time, timeout))
74662306a36Sopenharmony_ci		goto err_out;
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
74962306a36Sopenharmony_ci		perror("clock_gettime");
75062306a36Sopenharmony_ci		goto err_out;
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci	for (size_t i = 0; i < page_count - 1; i = i + 2)
75362306a36Sopenharmony_ci		memset(map_ptr + page_size * i, '-', 1);
75462306a36Sopenharmony_ci	if (clock_gettime(CLOCK_MONOTONIC_RAW, &end_time)) {
75562306a36Sopenharmony_ci		perror("clock_gettime");
75662306a36Sopenharmony_ci		goto err_out;
75762306a36Sopenharmony_ci	}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	cow_time_ns = (end_time.tv_sec - start_time.tv_sec) * NSEC_PER_SEC +
76062306a36Sopenharmony_ci		       (end_time.tv_nsec - start_time.tv_nsec);
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	printf("Merged pages:\n");
76362306a36Sopenharmony_ci	printf("Total time:     %ld.%09ld s\n", cow_time_ns / NSEC_PER_SEC,
76462306a36Sopenharmony_ci	       cow_time_ns % NSEC_PER_SEC);
76562306a36Sopenharmony_ci	printf("Average speed:  %.3f MiB/s\n", ((page_size * (page_count / 2)) / MB) /
76662306a36Sopenharmony_ci					       ((double)cow_time_ns / NSEC_PER_SEC));
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
76962306a36Sopenharmony_ci	return KSFT_PASS;
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cierr_out:
77262306a36Sopenharmony_ci	printf("Not OK\n");
77362306a36Sopenharmony_ci	munmap(map_ptr, page_size * page_count);
77462306a36Sopenharmony_ci	return KSFT_FAIL;
77562306a36Sopenharmony_ci}
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ciint main(int argc, char *argv[])
77862306a36Sopenharmony_ci{
77962306a36Sopenharmony_ci	int ret, opt;
78062306a36Sopenharmony_ci	int prot = 0;
78162306a36Sopenharmony_ci	int ksm_scan_limit_sec = KSM_SCAN_LIMIT_SEC_DEFAULT;
78262306a36Sopenharmony_ci	int merge_type = KSM_MERGE_TYPE_DEFAULT;
78362306a36Sopenharmony_ci	long page_count = KSM_PAGE_COUNT_DEFAULT;
78462306a36Sopenharmony_ci	size_t page_size = sysconf(_SC_PAGESIZE);
78562306a36Sopenharmony_ci	struct ksm_sysfs ksm_sysfs_old;
78662306a36Sopenharmony_ci	int test_name = CHECK_KSM_MERGE;
78762306a36Sopenharmony_ci	bool use_zero_pages = KSM_USE_ZERO_PAGES_DEFAULT;
78862306a36Sopenharmony_ci	bool merge_across_nodes = KSM_MERGE_ACROSS_NODES_DEFAULT;
78962306a36Sopenharmony_ci	long size_MB = 0;
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	while ((opt = getopt(argc, argv, "dha:p:l:z:m:s:t:MUZNPCHD")) != -1) {
79262306a36Sopenharmony_ci		switch (opt) {
79362306a36Sopenharmony_ci		case 'a':
79462306a36Sopenharmony_ci			prot = str_to_prot(optarg);
79562306a36Sopenharmony_ci			break;
79662306a36Sopenharmony_ci		case 'p':
79762306a36Sopenharmony_ci			page_count = atol(optarg);
79862306a36Sopenharmony_ci			if (page_count <= 0) {
79962306a36Sopenharmony_ci				printf("The number of pages must be greater than 0\n");
80062306a36Sopenharmony_ci				return KSFT_FAIL;
80162306a36Sopenharmony_ci			}
80262306a36Sopenharmony_ci			break;
80362306a36Sopenharmony_ci		case 'l':
80462306a36Sopenharmony_ci			ksm_scan_limit_sec = atoi(optarg);
80562306a36Sopenharmony_ci			if (ksm_scan_limit_sec <= 0) {
80662306a36Sopenharmony_ci				printf("Timeout value must be greater than 0\n");
80762306a36Sopenharmony_ci				return KSFT_FAIL;
80862306a36Sopenharmony_ci			}
80962306a36Sopenharmony_ci			break;
81062306a36Sopenharmony_ci		case 'h':
81162306a36Sopenharmony_ci			print_help();
81262306a36Sopenharmony_ci			break;
81362306a36Sopenharmony_ci		case 'z':
81462306a36Sopenharmony_ci			if (strcmp(optarg, "0") == 0)
81562306a36Sopenharmony_ci				use_zero_pages = 0;
81662306a36Sopenharmony_ci			else
81762306a36Sopenharmony_ci				use_zero_pages = 1;
81862306a36Sopenharmony_ci			break;
81962306a36Sopenharmony_ci		case 'm':
82062306a36Sopenharmony_ci			if (strcmp(optarg, "0") == 0)
82162306a36Sopenharmony_ci				merge_across_nodes = 0;
82262306a36Sopenharmony_ci			else
82362306a36Sopenharmony_ci				merge_across_nodes = 1;
82462306a36Sopenharmony_ci			break;
82562306a36Sopenharmony_ci		case 'd':
82662306a36Sopenharmony_ci			debug = 1;
82762306a36Sopenharmony_ci			break;
82862306a36Sopenharmony_ci		case 's':
82962306a36Sopenharmony_ci			size_MB = atoi(optarg);
83062306a36Sopenharmony_ci			if (size_MB <= 0) {
83162306a36Sopenharmony_ci				printf("Size must be greater than 0\n");
83262306a36Sopenharmony_ci				return KSFT_FAIL;
83362306a36Sopenharmony_ci			}
83462306a36Sopenharmony_ci			break;
83562306a36Sopenharmony_ci		case 't':
83662306a36Sopenharmony_ci			{
83762306a36Sopenharmony_ci				int tmp = atoi(optarg);
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci				if (tmp < 0 || tmp > KSM_MERGE_LAST) {
84062306a36Sopenharmony_ci					printf("Invalid merge type\n");
84162306a36Sopenharmony_ci					return KSFT_FAIL;
84262306a36Sopenharmony_ci				}
84362306a36Sopenharmony_ci				merge_type = tmp;
84462306a36Sopenharmony_ci			}
84562306a36Sopenharmony_ci			break;
84662306a36Sopenharmony_ci		case 'M':
84762306a36Sopenharmony_ci			break;
84862306a36Sopenharmony_ci		case 'U':
84962306a36Sopenharmony_ci			test_name = CHECK_KSM_UNMERGE;
85062306a36Sopenharmony_ci			break;
85162306a36Sopenharmony_ci		case 'Z':
85262306a36Sopenharmony_ci			test_name = CHECK_KSM_ZERO_PAGE_MERGE;
85362306a36Sopenharmony_ci			break;
85462306a36Sopenharmony_ci		case 'N':
85562306a36Sopenharmony_ci			test_name = CHECK_KSM_NUMA_MERGE;
85662306a36Sopenharmony_ci			break;
85762306a36Sopenharmony_ci		case 'P':
85862306a36Sopenharmony_ci			test_name = KSM_MERGE_TIME;
85962306a36Sopenharmony_ci			break;
86062306a36Sopenharmony_ci		case 'H':
86162306a36Sopenharmony_ci			test_name = KSM_MERGE_TIME_HUGE_PAGES;
86262306a36Sopenharmony_ci			break;
86362306a36Sopenharmony_ci		case 'D':
86462306a36Sopenharmony_ci			test_name = KSM_UNMERGE_TIME;
86562306a36Sopenharmony_ci			break;
86662306a36Sopenharmony_ci		case 'C':
86762306a36Sopenharmony_ci			test_name = KSM_COW_TIME;
86862306a36Sopenharmony_ci			break;
86962306a36Sopenharmony_ci		default:
87062306a36Sopenharmony_ci			return KSFT_FAIL;
87162306a36Sopenharmony_ci		}
87262306a36Sopenharmony_ci	}
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_ci	if (prot == 0)
87562306a36Sopenharmony_ci		prot = str_to_prot(KSM_PROT_STR_DEFAULT);
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	if (access(KSM_SYSFS_PATH, F_OK)) {
87862306a36Sopenharmony_ci		printf("Config KSM not enabled\n");
87962306a36Sopenharmony_ci		return KSFT_SKIP;
88062306a36Sopenharmony_ci	}
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	if (ksm_save_def(&ksm_sysfs_old)) {
88362306a36Sopenharmony_ci		printf("Cannot save default tunables\n");
88462306a36Sopenharmony_ci		return KSFT_FAIL;
88562306a36Sopenharmony_ci	}
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	if (ksm_write_sysfs(KSM_FP("run"), 2) ||
88862306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("sleep_millisecs"), 0) ||
88962306a36Sopenharmony_ci	    numa_available() ? 0 :
89062306a36Sopenharmony_ci		ksm_write_sysfs(KSM_FP("merge_across_nodes"), 1) ||
89162306a36Sopenharmony_ci	    ksm_write_sysfs(KSM_FP("pages_to_scan"), page_count))
89262306a36Sopenharmony_ci		return KSFT_FAIL;
89362306a36Sopenharmony_ci
89462306a36Sopenharmony_ci	switch (test_name) {
89562306a36Sopenharmony_ci	case CHECK_KSM_MERGE:
89662306a36Sopenharmony_ci		ret = check_ksm_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot, page_count,
89762306a36Sopenharmony_ci				      ksm_scan_limit_sec, page_size);
89862306a36Sopenharmony_ci		break;
89962306a36Sopenharmony_ci	case CHECK_KSM_UNMERGE:
90062306a36Sopenharmony_ci		ret = check_ksm_unmerge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
90162306a36Sopenharmony_ci					ksm_scan_limit_sec, page_size);
90262306a36Sopenharmony_ci		break;
90362306a36Sopenharmony_ci	case CHECK_KSM_ZERO_PAGE_MERGE:
90462306a36Sopenharmony_ci		ret = check_ksm_zero_page_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
90562306a36Sopenharmony_ci						page_count, ksm_scan_limit_sec, use_zero_pages,
90662306a36Sopenharmony_ci						page_size);
90762306a36Sopenharmony_ci		break;
90862306a36Sopenharmony_ci	case CHECK_KSM_NUMA_MERGE:
90962306a36Sopenharmony_ci		ret = check_ksm_numa_merge(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
91062306a36Sopenharmony_ci					ksm_scan_limit_sec, merge_across_nodes, page_size);
91162306a36Sopenharmony_ci		break;
91262306a36Sopenharmony_ci	case KSM_MERGE_TIME:
91362306a36Sopenharmony_ci		if (size_MB == 0) {
91462306a36Sopenharmony_ci			printf("Option '-s' is required.\n");
91562306a36Sopenharmony_ci			return KSFT_FAIL;
91662306a36Sopenharmony_ci		}
91762306a36Sopenharmony_ci		ret = ksm_merge_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
91862306a36Sopenharmony_ci				ksm_scan_limit_sec, size_MB);
91962306a36Sopenharmony_ci		break;
92062306a36Sopenharmony_ci	case KSM_MERGE_TIME_HUGE_PAGES:
92162306a36Sopenharmony_ci		if (size_MB == 0) {
92262306a36Sopenharmony_ci			printf("Option '-s' is required.\n");
92362306a36Sopenharmony_ci			return KSFT_FAIL;
92462306a36Sopenharmony_ci		}
92562306a36Sopenharmony_ci		ret = ksm_merge_hugepages_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
92662306a36Sopenharmony_ci				ksm_scan_limit_sec, size_MB);
92762306a36Sopenharmony_ci		break;
92862306a36Sopenharmony_ci	case KSM_UNMERGE_TIME:
92962306a36Sopenharmony_ci		if (size_MB == 0) {
93062306a36Sopenharmony_ci			printf("Option '-s' is required.\n");
93162306a36Sopenharmony_ci			return KSFT_FAIL;
93262306a36Sopenharmony_ci		}
93362306a36Sopenharmony_ci		ret = ksm_unmerge_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
93462306a36Sopenharmony_ci				       ksm_scan_limit_sec, size_MB);
93562306a36Sopenharmony_ci		break;
93662306a36Sopenharmony_ci	case KSM_COW_TIME:
93762306a36Sopenharmony_ci		ret = ksm_cow_time(merge_type, MAP_PRIVATE | MAP_ANONYMOUS, prot,
93862306a36Sopenharmony_ci				ksm_scan_limit_sec, page_size);
93962306a36Sopenharmony_ci		break;
94062306a36Sopenharmony_ci	}
94162306a36Sopenharmony_ci
94262306a36Sopenharmony_ci	if (ksm_restore(&ksm_sysfs_old)) {
94362306a36Sopenharmony_ci		printf("Cannot restore default tunables\n");
94462306a36Sopenharmony_ci		return KSFT_FAIL;
94562306a36Sopenharmony_ci	}
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ci	return ret;
94862306a36Sopenharmony_ci}
949