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