summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/mm/folio_order_check.c
blob: 5eafbcc1b4f3c5c01cdfa4d87c587cb697b32178 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: GPL-2.0
/*
 * Self-check for the vm_util folio-order helpers, is_backed_by_folio() and
 * is_range_backed_by_order(), which the khugepaged mTHP cases use to detect
 * collapse results.  For every anon THP order the kernel supports, fault
 * memory in with only that order enabled and require the helpers to report
 * exactly that order.
 */
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include "kselftest.h"
#include "vm_util.h"
#include <mm/hugepage_settings.h>

static int pagemap_fd;
static int kpageflags_fd;

static char *alloc_aligned(size_t size)
{
	size_t len = size * 2;
	char *p, *aligned;

	p = mmap(NULL, len, PROT_READ | PROT_WRITE,
		 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	if (p == MAP_FAILED)
		ksft_exit_fail_perror("mmap()");

	aligned = (char *)ALIGN((uintptr_t)p, size);
	if (aligned != p)
		munmap(p, aligned - p);
	if (aligned + size != p + len)
		munmap(aligned + size, p + len - aligned - size);

	return aligned;
}

static void check_order(int order)
{
	struct thp_settings settings = *thp_current_settings();
	size_t size = psize() << order;
	bool ok = true;
	char *p;
	int i;

	for (i = 0; i < NR_ORDERS; i++)
		settings.hugepages[i].enabled = THP_NEVER;
	if (order)
		settings.hugepages[order].enabled = THP_ALWAYS;
	thp_push_settings(&settings);

	p = alloc_aligned(size);
	*p = 1;

	if (!is_range_backed_by_order(p, size, order, pagemap_fd, kpageflags_fd)) {
		ksft_print_msg("order %d not detected after fault\n", order);
		ok = false;
	}

	/* A lower order must be rejected: the folio is larger */
	if (order && is_range_backed_by_order(p, size, order - 1,
					      pagemap_fd, kpageflags_fd)) {
		ksft_print_msg("order %d also reported as order %d\n",
			       order, order - 1);
		ok = false;
	}

	/* A large folio must not pass as order 0 */
	if (order && is_range_backed_by_order(p, size, 0,
					      pagemap_fd, kpageflags_fd)) {
		ksft_print_msg("order %d also reported as order 0\n", order);
		ok = false;
	}

	munmap(p, size);
	thp_pop_settings();

	ksft_test_result(ok, "order %d classified\n", order);
}

int main(void)
{
	struct thp_settings settings;
	unsigned long orders;
	int order;

	ksft_print_header();

	if (!thp_available())
		ksft_exit_skip("Transparent Hugepages not available\n");

	pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
	if (pagemap_fd < 0)
		ksft_exit_fail_perror("open(/proc/self/pagemap)");
	kpageflags_fd = open("/proc/kpageflags", O_RDONLY);
	if (kpageflags_fd < 0)
		ksft_exit_skip("open(/proc/kpageflags) requires root\n");

	orders = thp_supported_orders();
	if (!orders)
		ksft_exit_skip("No supported THP orders\n");

	ksft_set_plan(__builtin_popcountl(orders) + 1);

	thp_save_settings();
	thp_read_settings(&settings);
	/* Base of the settings stack; the bottom entry is never popped */
	thp_push_settings(&settings);

	check_order(0);
	for (order = 1; order < NR_ORDERS; order++) {
		if (!(orders & (1UL << order)))
			continue;
		check_order(order);
	}

	ksft_finished();
}