linux/arch/x86/kernel/cpu/mtrr/if.c
<<
>>
Prefs
   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/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 = kzalloc(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        if (!capable(CAP_SYS_ADMIN))
 105                return -EPERM;
 106
 107        memset(line, 0, LINE_SIZE);
 108
 109        length = len;
 110        length--;
 111
 112        if (length > LINE_SIZE - 1)
 113                length = LINE_SIZE - 1;
 114
 115        if (length < 0)
 116                return -EINVAL;
 117
 118        if (copy_from_user(line, buf, length))
 119                return -EFAULT;
 120
 121        linelen = strlen(line);
 122        ptr = line + linelen - 1;
 123        if (linelen && *ptr == '\n')
 124                *ptr = '\0';
 125
 126        if (!strncmp(line, "disable=", 8)) {
 127                reg = simple_strtoul(line + 8, &ptr, 0);
 128                err = mtrr_del_page(reg, 0, 0);
 129                if (err < 0)
 130                        return err;
 131                return len;
 132        }
 133
 134        if (strncmp(line, "base=", 5))
 135                return -EINVAL;
 136
 137        base = simple_strtoull(line + 5, &ptr, 0);
 138        ptr = skip_spaces(ptr);
 139
 140        if (strncmp(ptr, "size=", 5))
 141                return -EINVAL;
 142
 143        size = simple_strtoull(ptr + 5, &ptr, 0);
 144        if ((base & 0xfff) || (size & 0xfff))
 145                return -EINVAL;
 146        ptr = skip_spaces(ptr);
 147
 148        if (strncmp(ptr, "type=", 5))
 149                return -EINVAL;
 150        ptr = skip_spaces(ptr + 5);
 151
 152        for (i = 0; i < MTRR_NUM_TYPES; ++i) {
 153                if (strcmp(ptr, mtrr_strings[i]))
 154                        continue;
 155                base >>= PAGE_SHIFT;
 156                size >>= PAGE_SHIFT;
 157                err = mtrr_add_page((unsigned long)base, (unsigned long)size, i, true);
 158                if (err < 0)
 159                        return err;
 160                return len;
 161        }
 162        return -EINVAL;
 163}
 164
 165static long
 166mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
 167{
 168        int err = 0;
 169        mtrr_type type;
 170        unsigned long base;
 171        unsigned long size;
 172        struct mtrr_sentry sentry;
 173        struct mtrr_gentry gentry;
 174        void __user *arg = (void __user *) __arg;
 175
 176        switch (cmd) {
 177        case MTRRIOC_ADD_ENTRY:
 178        case MTRRIOC_SET_ENTRY:
 179        case MTRRIOC_DEL_ENTRY:
 180        case MTRRIOC_KILL_ENTRY:
 181        case MTRRIOC_ADD_PAGE_ENTRY:
 182        case MTRRIOC_SET_PAGE_ENTRY:
 183        case MTRRIOC_DEL_PAGE_ENTRY:
 184        case MTRRIOC_KILL_PAGE_ENTRY:
 185                if (copy_from_user(&sentry, arg, sizeof sentry))
 186                        return -EFAULT;
 187                break;
 188        case MTRRIOC_GET_ENTRY:
 189        case MTRRIOC_GET_PAGE_ENTRY:
 190                if (copy_from_user(&gentry, arg, sizeof gentry))
 191                        return -EFAULT;
 192                break;
 193#ifdef CONFIG_COMPAT
 194        case MTRRIOC32_ADD_ENTRY:
 195        case MTRRIOC32_SET_ENTRY:
 196        case MTRRIOC32_DEL_ENTRY:
 197        case MTRRIOC32_KILL_ENTRY:
 198        case MTRRIOC32_ADD_PAGE_ENTRY:
 199        case MTRRIOC32_SET_PAGE_ENTRY:
 200        case MTRRIOC32_DEL_PAGE_ENTRY:
 201        case MTRRIOC32_KILL_PAGE_ENTRY: {
 202                struct mtrr_sentry32 __user *s32;
 203
 204                s32 = (struct mtrr_sentry32 __user *)__arg;
 205                err = get_user(sentry.base, &s32->base);
 206                err |= get_user(sentry.size, &s32->size);
 207                err |= get_user(sentry.type, &s32->type);
 208                if (err)
 209                        return err;
 210                break;
 211        }
 212        case MTRRIOC32_GET_ENTRY:
 213        case MTRRIOC32_GET_PAGE_ENTRY: {
 214                struct mtrr_gentry32 __user *g32;
 215
 216                g32 = (struct mtrr_gentry32 __user *)__arg;
 217                err = get_user(gentry.regnum, &g32->regnum);
 218                err |= get_user(gentry.base, &g32->base);
 219                err |= get_user(gentry.size, &g32->size);
 220                err |= get_user(gentry.type, &g32->type);
 221                if (err)
 222                        return err;
 223                break;
 224        }
 225#endif
 226        }
 227
 228        switch (cmd) {
 229        default:
 230                return -ENOTTY;
 231        case MTRRIOC_ADD_ENTRY:
 232#ifdef CONFIG_COMPAT
 233        case MTRRIOC32_ADD_ENTRY:
 234#endif
 235                if (!capable(CAP_SYS_ADMIN))
 236                        return -EPERM;
 237                err =
 238                    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
 239                                  file, 0);
 240                break;
 241        case MTRRIOC_SET_ENTRY:
 242#ifdef CONFIG_COMPAT
 243        case MTRRIOC32_SET_ENTRY:
 244#endif
 245                if (!capable(CAP_SYS_ADMIN))
 246                        return -EPERM;
 247                err = mtrr_add(sentry.base, sentry.size, sentry.type, false);
 248                break;
 249        case MTRRIOC_DEL_ENTRY:
 250#ifdef CONFIG_COMPAT
 251        case MTRRIOC32_DEL_ENTRY:
 252#endif
 253                if (!capable(CAP_SYS_ADMIN))
 254                        return -EPERM;
 255                err = mtrr_file_del(sentry.base, sentry.size, file, 0);
 256                break;
 257        case MTRRIOC_KILL_ENTRY:
 258#ifdef CONFIG_COMPAT
 259        case MTRRIOC32_KILL_ENTRY:
 260#endif
 261                if (!capable(CAP_SYS_ADMIN))
 262                        return -EPERM;
 263                err = mtrr_del(-1, sentry.base, sentry.size);
 264                break;
 265        case MTRRIOC_GET_ENTRY:
 266#ifdef CONFIG_COMPAT
 267        case MTRRIOC32_GET_ENTRY:
 268#endif
 269                if (gentry.regnum >= num_var_ranges)
 270                        return -EINVAL;
 271                mtrr_if->get(gentry.regnum, &base, &size, &type);
 272
 273                /* Hide entries that go above 4GB */
 274                if (base + size - 1 >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT))
 275                    || size >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT)))
 276                        gentry.base = gentry.size = gentry.type = 0;
 277                else {
 278                        gentry.base = base << PAGE_SHIFT;
 279                        gentry.size = size << PAGE_SHIFT;
 280                        gentry.type = type;
 281                }
 282
 283                break;
 284        case MTRRIOC_ADD_PAGE_ENTRY:
 285#ifdef CONFIG_COMPAT
 286        case MTRRIOC32_ADD_PAGE_ENTRY:
 287#endif
 288                if (!capable(CAP_SYS_ADMIN))
 289                        return -EPERM;
 290                err =
 291                    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
 292                                  file, 1);
 293                break;
 294        case MTRRIOC_SET_PAGE_ENTRY:
 295#ifdef CONFIG_COMPAT
 296        case MTRRIOC32_SET_PAGE_ENTRY:
 297#endif
 298                if (!capable(CAP_SYS_ADMIN))
 299                        return -EPERM;
 300                err =
 301                    mtrr_add_page(sentry.base, sentry.size, sentry.type, false);
 302                break;
 303        case MTRRIOC_DEL_PAGE_ENTRY:
 304#ifdef CONFIG_COMPAT
 305        case MTRRIOC32_DEL_PAGE_ENTRY:
 306#endif
 307                if (!capable(CAP_SYS_ADMIN))
 308                        return -EPERM;
 309                err = mtrr_file_del(sentry.base, sentry.size, file, 1);
 310                break;
 311        case MTRRIOC_KILL_PAGE_ENTRY:
 312#ifdef CONFIG_COMPAT
 313        case MTRRIOC32_KILL_PAGE_ENTRY:
 314#endif
 315                if (!capable(CAP_SYS_ADMIN))
 316                        return -EPERM;
 317                err = mtrr_del_page(-1, sentry.base, sentry.size);
 318                break;
 319        case MTRRIOC_GET_PAGE_ENTRY:
 320#ifdef CONFIG_COMPAT
 321        case MTRRIOC32_GET_PAGE_ENTRY:
 322#endif
 323                if (gentry.regnum >= num_var_ranges)
 324                        return -EINVAL;
 325                mtrr_if->get(gentry.regnum, &base, &size, &type);
 326                /* Hide entries that would overflow */
 327                if (size != (__typeof__(gentry.size))size)
 328                        gentry.base = gentry.size = gentry.type = 0;
 329                else {
 330                        gentry.base = base;
 331                        gentry.size = size;
 332                        gentry.type = type;
 333                }
 334                break;
 335        }
 336
 337        if (err)
 338                return err;
 339
 340        switch (cmd) {
 341        case MTRRIOC_GET_ENTRY:
 342        case MTRRIOC_GET_PAGE_ENTRY:
 343                if (copy_to_user(arg, &gentry, sizeof gentry))
 344                        err = -EFAULT;
 345                break;
 346#ifdef CONFIG_COMPAT
 347        case MTRRIOC32_GET_ENTRY:
 348        case MTRRIOC32_GET_PAGE_ENTRY: {
 349                struct mtrr_gentry32 __user *g32;
 350
 351                g32 = (struct mtrr_gentry32 __user *)__arg;
 352                err = put_user(gentry.base, &g32->base);
 353                err |= put_user(gentry.size, &g32->size);
 354                err |= put_user(gentry.regnum, &g32->regnum);
 355                err |= put_user(gentry.type, &g32->type);
 356                break;
 357        }
 358#endif
 359        }
 360        return err;
 361}
 362
 363static int mtrr_close(struct inode *ino, struct file *file)
 364{
 365        unsigned int *fcount = FILE_FCOUNT(file);
 366        int i, max;
 367
 368        if (fcount != NULL) {
 369                max = num_var_ranges;
 370                for (i = 0; i < max; ++i) {
 371                        while (fcount[i] > 0) {
 372                                mtrr_del(i, 0, 0);
 373                                --fcount[i];
 374                        }
 375                }
 376                kfree(fcount);
 377                FILE_FCOUNT(file) = NULL;
 378        }
 379        return single_release(ino, file);
 380}
 381
 382static int mtrr_seq_show(struct seq_file *seq, void *offset);
 383
 384static int mtrr_open(struct inode *inode, struct file *file)
 385{
 386        if (!mtrr_if)
 387                return -EIO;
 388        if (!mtrr_if->get)
 389                return -ENXIO;
 390        return single_open(file, mtrr_seq_show, NULL);
 391}
 392
 393static const struct file_operations mtrr_fops = {
 394        .owner                  = THIS_MODULE,
 395        .open                   = mtrr_open,
 396        .read                   = seq_read,
 397        .llseek                 = seq_lseek,
 398        .write                  = mtrr_write,
 399        .unlocked_ioctl         = mtrr_ioctl,
 400        .compat_ioctl           = mtrr_ioctl,
 401        .release                = mtrr_close,
 402};
 403
 404static int mtrr_seq_show(struct seq_file *seq, void *offset)
 405{
 406        char factor;
 407        int i, max, len;
 408        mtrr_type type;
 409        unsigned long base, size;
 410
 411        len = 0;
 412        max = num_var_ranges;
 413        for (i = 0; i < max; i++) {
 414                mtrr_if->get(i, &base, &size, &type);
 415                if (size == 0) {
 416                        mtrr_usage_table[i] = 0;
 417                        continue;
 418                }
 419                if (size < (0x100000 >> PAGE_SHIFT)) {
 420                        /* less than 1MB */
 421                        factor = 'K';
 422                        size <<= PAGE_SHIFT - 10;
 423                } else {
 424                        factor = 'M';
 425                        size >>= 20 - PAGE_SHIFT;
 426                }
 427                /* Base can be > 32bit */
 428                len += seq_printf(seq, "reg%02i: base=0x%06lx000 "
 429                        "(%5luMB), size=%5lu%cB, count=%d: %s\n",
 430                        i, base, base >> (20 - PAGE_SHIFT), size,
 431                        factor, mtrr_usage_table[i],
 432                        mtrr_attrib_to_str(type));
 433        }
 434        return 0;
 435}
 436
 437static int __init mtrr_if_init(void)
 438{
 439        struct cpuinfo_x86 *c = &boot_cpu_data;
 440
 441        if ((!cpu_has(c, X86_FEATURE_MTRR)) &&
 442            (!cpu_has(c, X86_FEATURE_K6_MTRR)) &&
 443            (!cpu_has(c, X86_FEATURE_CYRIX_ARR)) &&
 444            (!cpu_has(c, X86_FEATURE_CENTAUR_MCR)))
 445                return -ENODEV;
 446
 447        proc_create("mtrr", S_IWUSR | S_IRUGO, NULL, &mtrr_fops);
 448        return 0;
 449}
 450arch_initcall(mtrr_if_init);
 451#endif                  /*  CONFIG_PROC_FS  */
 452