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