xref: /linux/arch/x86/kernel/cpu/mtrr/if.c (revision e26207a3819684e9b4450a2d30bdd065fa92d9c7)
1 #include <linux/capability.h>
2 #include <linux/seq_file.h>
3 #include <linux/uaccess.h>
4 #include <linux/proc_fs.h>
5 #include <linux/module.h>
6 #include <linux/ctype.h>
7 #include <linux/string.h>
8 #include <linux/init.h>
9 
10 #define LINE_SIZE 80
11 
12 #include <asm/mtrr.h>
13 
14 #include "mtrr.h"
15 
16 #define FILE_FCOUNT(f) (((struct seq_file *)((f)->private_data))->private)
17 
18 static const char *const mtrr_strings[MTRR_NUM_TYPES] =
19 {
20 	"uncachable",		/* 0 */
21 	"write-combining",	/* 1 */
22 	"?",			/* 2 */
23 	"?",			/* 3 */
24 	"write-through",	/* 4 */
25 	"write-protect",	/* 5 */
26 	"write-back",		/* 6 */
27 };
28 
29 const char *mtrr_attrib_to_str(int x)
30 {
31 	return (x <= 6) ? mtrr_strings[x] : "?";
32 }
33 
34 #ifdef CONFIG_PROC_FS
35 
36 static int
37 mtrr_file_add(unsigned long base, unsigned long size,
38 	      unsigned int type, bool increment, struct file *file, int page)
39 {
40 	unsigned int *fcount = FILE_FCOUNT(file);
41 	int reg, max;
42 
43 	max = num_var_ranges;
44 	if (fcount == NULL) {
45 		fcount = kzalloc(max * sizeof *fcount, GFP_KERNEL);
46 		if (!fcount)
47 			return -ENOMEM;
48 		FILE_FCOUNT(file) = fcount;
49 	}
50 	if (!page) {
51 		if ((base & (PAGE_SIZE - 1)) || (size & (PAGE_SIZE - 1)))
52 			return -EINVAL;
53 		base >>= PAGE_SHIFT;
54 		size >>= PAGE_SHIFT;
55 	}
56 	reg = mtrr_add_page(base, size, type, true);
57 	if (reg >= 0)
58 		++fcount[reg];
59 	return reg;
60 }
61 
62 static int
63 mtrr_file_del(unsigned long base, unsigned long size,
64 	      struct file *file, int page)
65 {
66 	unsigned int *fcount = FILE_FCOUNT(file);
67 	int reg;
68 
69 	if (!page) {
70 		if ((base & (PAGE_SIZE - 1)) || (size & (PAGE_SIZE - 1)))
71 			return -EINVAL;
72 		base >>= PAGE_SHIFT;
73 		size >>= PAGE_SHIFT;
74 	}
75 	reg = mtrr_del_page(-1, base, size);
76 	if (reg < 0)
77 		return reg;
78 	if (fcount == NULL)
79 		return reg;
80 	if (fcount[reg] < 1)
81 		return -EINVAL;
82 	--fcount[reg];
83 	return reg;
84 }
85 
86 /*
87  * seq_file can seek but we ignore it.
88  *
89  * Format of control line:
90  *    "base=%Lx size=%Lx type=%s" or "disable=%d"
91  */
92 static ssize_t
93 mtrr_write(struct file *file, const char __user *buf, size_t len, loff_t * ppos)
94 {
95 	int i, err;
96 	unsigned long reg;
97 	unsigned long long base, size;
98 	char *ptr;
99 	char line[LINE_SIZE];
100 	int length;
101 	size_t linelen;
102 
103 	if (!capable(CAP_SYS_ADMIN))
104 		return -EPERM;
105 
106 	memset(line, 0, LINE_SIZE);
107 
108 	length = len;
109 	length--;
110 
111 	if (length > LINE_SIZE - 1)
112 		length = LINE_SIZE - 1;
113 
114 	if (length < 0)
115 		return -EINVAL;
116 
117 	if (copy_from_user(line, buf, length))
118 		return -EFAULT;
119 
120 	linelen = strlen(line);
121 	ptr = line + linelen - 1;
122 	if (linelen && *ptr == '\n')
123 		*ptr = '\0';
124 
125 	if (!strncmp(line, "disable=", 8)) {
126 		reg = simple_strtoul(line + 8, &ptr, 0);
127 		err = mtrr_del_page(reg, 0, 0);
128 		if (err < 0)
129 			return err;
130 		return len;
131 	}
132 
133 	if (strncmp(line, "base=", 5))
134 		return -EINVAL;
135 
136 	base = simple_strtoull(line + 5, &ptr, 0);
137 	ptr = skip_spaces(ptr);
138 
139 	if (strncmp(ptr, "size=", 5))
140 		return -EINVAL;
141 
142 	size = simple_strtoull(ptr + 5, &ptr, 0);
143 	if ((base & 0xfff) || (size & 0xfff))
144 		return -EINVAL;
145 	ptr = skip_spaces(ptr);
146 
147 	if (strncmp(ptr, "type=", 5))
148 		return -EINVAL;
149 	ptr = skip_spaces(ptr + 5);
150 
151 	for (i = 0; i < MTRR_NUM_TYPES; ++i) {
152 		if (strcmp(ptr, mtrr_strings[i]))
153 			continue;
154 		base >>= PAGE_SHIFT;
155 		size >>= PAGE_SHIFT;
156 		err = mtrr_add_page((unsigned long)base, (unsigned long)size, i, true);
157 		if (err < 0)
158 			return err;
159 		return len;
160 	}
161 	return -EINVAL;
162 }
163 
164 static long
165 mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
166 {
167 	int err = 0;
168 	mtrr_type type;
169 	unsigned long size;
170 	struct mtrr_sentry sentry;
171 	struct mtrr_gentry gentry;
172 	void __user *arg = (void __user *) __arg;
173 
174 	switch (cmd) {
175 	case MTRRIOC_ADD_ENTRY:
176 	case MTRRIOC_SET_ENTRY:
177 	case MTRRIOC_DEL_ENTRY:
178 	case MTRRIOC_KILL_ENTRY:
179 	case MTRRIOC_ADD_PAGE_ENTRY:
180 	case MTRRIOC_SET_PAGE_ENTRY:
181 	case MTRRIOC_DEL_PAGE_ENTRY:
182 	case MTRRIOC_KILL_PAGE_ENTRY:
183 		if (copy_from_user(&sentry, arg, sizeof sentry))
184 			return -EFAULT;
185 		break;
186 	case MTRRIOC_GET_ENTRY:
187 	case MTRRIOC_GET_PAGE_ENTRY:
188 		if (copy_from_user(&gentry, arg, sizeof gentry))
189 			return -EFAULT;
190 		break;
191 #ifdef CONFIG_COMPAT
192 	case MTRRIOC32_ADD_ENTRY:
193 	case MTRRIOC32_SET_ENTRY:
194 	case MTRRIOC32_DEL_ENTRY:
195 	case MTRRIOC32_KILL_ENTRY:
196 	case MTRRIOC32_ADD_PAGE_ENTRY:
197 	case MTRRIOC32_SET_PAGE_ENTRY:
198 	case MTRRIOC32_DEL_PAGE_ENTRY:
199 	case MTRRIOC32_KILL_PAGE_ENTRY: {
200 		struct mtrr_sentry32 __user *s32;
201 
202 		s32 = (struct mtrr_sentry32 __user *)__arg;
203 		err = get_user(sentry.base, &s32->base);
204 		err |= get_user(sentry.size, &s32->size);
205 		err |= get_user(sentry.type, &s32->type);
206 		if (err)
207 			return err;
208 		break;
209 	}
210 	case MTRRIOC32_GET_ENTRY:
211 	case MTRRIOC32_GET_PAGE_ENTRY: {
212 		struct mtrr_gentry32 __user *g32;
213 
214 		g32 = (struct mtrr_gentry32 __user *)__arg;
215 		err = get_user(gentry.regnum, &g32->regnum);
216 		err |= get_user(gentry.base, &g32->base);
217 		err |= get_user(gentry.size, &g32->size);
218 		err |= get_user(gentry.type, &g32->type);
219 		if (err)
220 			return err;
221 		break;
222 	}
223 #endif
224 	}
225 
226 	switch (cmd) {
227 	default:
228 		return -ENOTTY;
229 	case MTRRIOC_ADD_ENTRY:
230 #ifdef CONFIG_COMPAT
231 	case MTRRIOC32_ADD_ENTRY:
232 #endif
233 		if (!capable(CAP_SYS_ADMIN))
234 			return -EPERM;
235 		err =
236 		    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
237 				  file, 0);
238 		break;
239 	case MTRRIOC_SET_ENTRY:
240 #ifdef CONFIG_COMPAT
241 	case MTRRIOC32_SET_ENTRY:
242 #endif
243 		if (!capable(CAP_SYS_ADMIN))
244 			return -EPERM;
245 		err = mtrr_add(sentry.base, sentry.size, sentry.type, false);
246 		break;
247 	case MTRRIOC_DEL_ENTRY:
248 #ifdef CONFIG_COMPAT
249 	case MTRRIOC32_DEL_ENTRY:
250 #endif
251 		if (!capable(CAP_SYS_ADMIN))
252 			return -EPERM;
253 		err = mtrr_file_del(sentry.base, sentry.size, file, 0);
254 		break;
255 	case MTRRIOC_KILL_ENTRY:
256 #ifdef CONFIG_COMPAT
257 	case MTRRIOC32_KILL_ENTRY:
258 #endif
259 		if (!capable(CAP_SYS_ADMIN))
260 			return -EPERM;
261 		err = mtrr_del(-1, sentry.base, sentry.size);
262 		break;
263 	case MTRRIOC_GET_ENTRY:
264 #ifdef CONFIG_COMPAT
265 	case MTRRIOC32_GET_ENTRY:
266 #endif
267 		if (gentry.regnum >= num_var_ranges)
268 			return -EINVAL;
269 		mtrr_if->get(gentry.regnum, &gentry.base, &size, &type);
270 
271 		/* Hide entries that go above 4GB */
272 		if (gentry.base + size - 1 >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT))
273 		    || size >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT)))
274 			gentry.base = gentry.size = gentry.type = 0;
275 		else {
276 			gentry.base <<= PAGE_SHIFT;
277 			gentry.size = size << PAGE_SHIFT;
278 			gentry.type = type;
279 		}
280 
281 		break;
282 	case MTRRIOC_ADD_PAGE_ENTRY:
283 #ifdef CONFIG_COMPAT
284 	case MTRRIOC32_ADD_PAGE_ENTRY:
285 #endif
286 		if (!capable(CAP_SYS_ADMIN))
287 			return -EPERM;
288 		err =
289 		    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
290 				  file, 1);
291 		break;
292 	case MTRRIOC_SET_PAGE_ENTRY:
293 #ifdef CONFIG_COMPAT
294 	case MTRRIOC32_SET_PAGE_ENTRY:
295 #endif
296 		if (!capable(CAP_SYS_ADMIN))
297 			return -EPERM;
298 		err =
299 		    mtrr_add_page(sentry.base, sentry.size, sentry.type, false);
300 		break;
301 	case MTRRIOC_DEL_PAGE_ENTRY:
302 #ifdef CONFIG_COMPAT
303 	case MTRRIOC32_DEL_PAGE_ENTRY:
304 #endif
305 		if (!capable(CAP_SYS_ADMIN))
306 			return -EPERM;
307 		err = mtrr_file_del(sentry.base, sentry.size, file, 1);
308 		break;
309 	case MTRRIOC_KILL_PAGE_ENTRY:
310 #ifdef CONFIG_COMPAT
311 	case MTRRIOC32_KILL_PAGE_ENTRY:
312 #endif
313 		if (!capable(CAP_SYS_ADMIN))
314 			return -EPERM;
315 		err = mtrr_del_page(-1, sentry.base, sentry.size);
316 		break;
317 	case MTRRIOC_GET_PAGE_ENTRY:
318 #ifdef CONFIG_COMPAT
319 	case MTRRIOC32_GET_PAGE_ENTRY:
320 #endif
321 		if (gentry.regnum >= num_var_ranges)
322 			return -EINVAL;
323 		mtrr_if->get(gentry.regnum, &gentry.base, &size, &type);
324 		/* Hide entries that would overflow */
325 		if (size != (__typeof__(gentry.size))size)
326 			gentry.base = gentry.size = gentry.type = 0;
327 		else {
328 			gentry.size = size;
329 			gentry.type = type;
330 		}
331 		break;
332 	}
333 
334 	if (err)
335 		return err;
336 
337 	switch (cmd) {
338 	case MTRRIOC_GET_ENTRY:
339 	case MTRRIOC_GET_PAGE_ENTRY:
340 		if (copy_to_user(arg, &gentry, sizeof gentry))
341 			err = -EFAULT;
342 		break;
343 #ifdef CONFIG_COMPAT
344 	case MTRRIOC32_GET_ENTRY:
345 	case MTRRIOC32_GET_PAGE_ENTRY: {
346 		struct mtrr_gentry32 __user *g32;
347 
348 		g32 = (struct mtrr_gentry32 __user *)__arg;
349 		err = put_user(gentry.base, &g32->base);
350 		err |= put_user(gentry.size, &g32->size);
351 		err |= put_user(gentry.regnum, &g32->regnum);
352 		err |= put_user(gentry.type, &g32->type);
353 		break;
354 	}
355 #endif
356 	}
357 	return err;
358 }
359 
360 static int mtrr_close(struct inode *ino, struct file *file)
361 {
362 	unsigned int *fcount = FILE_FCOUNT(file);
363 	int i, max;
364 
365 	if (fcount != NULL) {
366 		max = num_var_ranges;
367 		for (i = 0; i < max; ++i) {
368 			while (fcount[i] > 0) {
369 				mtrr_del(i, 0, 0);
370 				--fcount[i];
371 			}
372 		}
373 		kfree(fcount);
374 		FILE_FCOUNT(file) = NULL;
375 	}
376 	return single_release(ino, file);
377 }
378 
379 static int mtrr_seq_show(struct seq_file *seq, void *offset);
380 
381 static int mtrr_open(struct inode *inode, struct file *file)
382 {
383 	if (!mtrr_if)
384 		return -EIO;
385 	if (!mtrr_if->get)
386 		return -ENXIO;
387 	return single_open(file, mtrr_seq_show, NULL);
388 }
389 
390 static const struct file_operations mtrr_fops = {
391 	.owner			= THIS_MODULE,
392 	.open			= mtrr_open,
393 	.read			= seq_read,
394 	.llseek			= seq_lseek,
395 	.write			= mtrr_write,
396 	.unlocked_ioctl		= mtrr_ioctl,
397 	.compat_ioctl		= mtrr_ioctl,
398 	.release		= mtrr_close,
399 };
400 
401 static int mtrr_seq_show(struct seq_file *seq, void *offset)
402 {
403 	char factor;
404 	int i, max, len;
405 	mtrr_type type;
406 	unsigned long base, size;
407 
408 	len = 0;
409 	max = num_var_ranges;
410 	for (i = 0; i < max; i++) {
411 		mtrr_if->get(i, &base, &size, &type);
412 		if (size == 0) {
413 			mtrr_usage_table[i] = 0;
414 			continue;
415 		}
416 		if (size < (0x100000 >> PAGE_SHIFT)) {
417 			/* less than 1MB */
418 			factor = 'K';
419 			size <<= PAGE_SHIFT - 10;
420 		} else {
421 			factor = 'M';
422 			size >>= 20 - PAGE_SHIFT;
423 		}
424 		/* Base can be > 32bit */
425 		len += seq_printf(seq, "reg%02i: base=0x%06lx000 "
426 			"(%5luMB), size=%5lu%cB, count=%d: %s\n",
427 			i, base, base >> (20 - PAGE_SHIFT), size,
428 			factor, mtrr_usage_table[i],
429 			mtrr_attrib_to_str(type));
430 	}
431 	return 0;
432 }
433 
434 static int __init mtrr_if_init(void)
435 {
436 	struct cpuinfo_x86 *c = &boot_cpu_data;
437 
438 	if ((!cpu_has(c, X86_FEATURE_MTRR)) &&
439 	    (!cpu_has(c, X86_FEATURE_K6_MTRR)) &&
440 	    (!cpu_has(c, X86_FEATURE_CYRIX_ARR)) &&
441 	    (!cpu_has(c, X86_FEATURE_CENTAUR_MCR)))
442 		return -ENODEV;
443 
444 	proc_create("mtrr", S_IWUSR | S_IRUGO, NULL, &mtrr_fops);
445 	return 0;
446 }
447 arch_initcall(mtrr_if_init);
448 #endif			/*  CONFIG_PROC_FS  */
449