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