summaryrefslogtreecommitdiff
path: root/arch/x86/mm/pat_rbtree.c
blob: d31ca773d4bb0b9e94627c4cf244a0eaf41c28ce (plain)
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// SPDX-License-Identifier: GPL-2.0
/*
 * Handle caching attributes in page tables (PAT)
 *
 * Authors: Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
 *          Suresh B Siddha <suresh.b.siddha@intel.com>
 *
 * Interval tree used to store the PAT memory type reservations.
 */

#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/kernel.h>
#include <linux/interval_tree_generic.h>
#include <linux/sched.h>
#include <linux/gfp.h>

#include <asm/pgtable.h>
#include <asm/pat.h>

#include "pat_internal.h"

/*
 * The memtype tree keeps track of memory type for specific
 * physical memory areas. Without proper tracking, conflicting memory
 * types in different mappings can cause CPU cache corruption.
 *
 * The tree is an interval tree (augmented rbtree) with tree ordered
 * on starting address. Tree can contain multiple entries for
 * different regions which overlap. All the aliases have the same
 * cache attributes of course.
 *
 * memtype_lock protects the rbtree.
 */
static inline u64 memtype_interval_start(struct memtype *memtype)
{
	return memtype->start;
}

static inline u64 memtype_interval_end(struct memtype *memtype)
{
	return memtype->end - 1;
}
INTERVAL_TREE_DEFINE(struct memtype, rb, u64, subtree_max_end,
		     memtype_interval_start, memtype_interval_end,
		     static, memtype_interval)

static struct rb_root_cached memtype_rbroot = RB_ROOT_CACHED;

enum {
	MEMTYPE_EXACT_MATCH	= 0,
	MEMTYPE_END_MATCH	= 1
};

static struct memtype *memtype_match(u64 start, u64 end, int match_type)
{
	struct memtype *match;

	match = memtype_interval_iter_first(&memtype_rbroot, start, end);
	while (match != NULL && match->start < end) {
		if ((match_type == MEMTYPE_EXACT_MATCH) &&
		    (match->start == start) && (match->end == end))
			return match;

		if ((match_type == MEMTYPE_END_MATCH) &&
		    (match->start < start) && (match->end == end))
			return match;

		match = memtype_interval_iter_next(match, start, end);
	}

	return NULL; /* Returns NULL if there is no match */
}

static int memtype_check_conflict(u64 start, u64 end,
				  enum page_cache_mode reqtype,
				  enum page_cache_mode *newtype)
{
	struct memtype *match;
	enum page_cache_mode found_type = reqtype;

	match = memtype_interval_iter_first(&memtype_rbroot, start, end);
	if (match == NULL)
		goto success;

	if (match->type != found_type && newtype == NULL)
		goto failure;

	dprintk("Overlap at 0x%Lx-0x%Lx\n", match->start, match->end);
	found_type = match->type;

	match = memtype_interval_iter_next(match, start, end);
	while (match) {
		if (match->type != found_type)
			goto failure;

		match = memtype_interval_iter_next(match, start, end);
	}
success:
	if (newtype)
		*newtype = found_type;

	return 0;

failure:
	pr_info("x86/PAT: %s:%d conflicting memory types %Lx-%Lx %s<->%s\n",
		current->comm, current->pid, start, end,
		cattr_name(found_type), cattr_name(match->type));
	return -EBUSY;
}

int rbt_memtype_check_insert(struct memtype *new,
			     enum page_cache_mode *ret_type)
{
	int err = 0;

	err = memtype_check_conflict(new->start, new->end, new->type, ret_type);
	if (err)
		return err;

	if (ret_type)
		new->type = *ret_type;

	memtype_interval_insert(new, &memtype_rbroot);
	return 0;
}

struct memtype *rbt_memtype_erase(u64 start, u64 end)
{
	struct memtype *data;

	/*
	 * Since the memtype_rbroot tree allows overlapping ranges,
	 * rbt_memtype_erase() checks with EXACT_MATCH first, i.e. free
	 * a whole node for the munmap case.  If no such entry is found,
	 * it then checks with END_MATCH, i.e. shrink the size of a node
	 * from the end for the mremap case.
	 */
	data = memtype_match(start, end, MEMTYPE_EXACT_MATCH);
	if (!data) {
		data = memtype_match(start, end, MEMTYPE_END_MATCH);
		if (!data)
			return ERR_PTR(-EINVAL);
	}

	if (data->start == start) {
		/* munmap: erase this node */
		memtype_interval_remove(data, &memtype_rbroot);
	} else {
		/* mremap: update the end value of this node */
		memtype_interval_remove(data, &memtype_rbroot);
		data->end = start;
		memtype_interval_insert(data, &memtype_rbroot);
		return NULL;
	}

	return data;
}

struct memtype *rbt_memtype_lookup(u64 addr)
{
	return memtype_interval_iter_first(&memtype_rbroot, addr,
					   addr + PAGE_SIZE);
}

#if defined(CONFIG_DEBUG_FS)
int rbt_memtype_copy_nth_element(struct memtype *out, loff_t pos)
{
	struct memtype *match;
	int i = 1;

	match = memtype_interval_iter_first(&memtype_rbroot, 0, ULONG_MAX);
	while (match && pos != i) {
		match = memtype_interval_iter_next(match, 0, ULONG_MAX);
		i++;
	}

	if (match) { /* pos == i */
		*out = *match;
		return 0;
	} else {
		return 1;
	}
}
#endif