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