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/init.h>
   8
   9#define LINE_SIZE 80
  10
  11#include <asm/mtrr.h>
  12
  13#include "mtrr.h"
  14
  15#define FILE_FCOUNT(f) (((struct seq_file *)((f)->private_data))->private)
  16
  17static const char *const mtrr_strings[MTRR_NUM_TYPES] =
  18{
  19        "uncachable",           /* 0 */
  20        "write-combining",      /* 1 */
  21        "?",                    /* 2 */
  22        "?",                    /* 3 */
  23        "write-through",        /* 4 */
  24        "write-protect",        /* 5 */
  25        "write-back",           /* 6 */
  26};
  27
  28const char *mtrr_attrib_to_str(int x)
  29{
  30        return (x <= 6) ? mtrr_strings[x] : "?";
  31}
  32
  33#ifdef CONFIG_PROC_FS
  34
  35static int
  36mtrr_file_add(unsigned long base, unsigned long size,
  37              unsigned int type, bool increment, struct file *file, int page)
  38{
  39        unsigned int *fcount = FILE_FCOUNT(file);
  40        int reg, max;
  41
  42        max = num_var_ranges;
  43        if (fcount == NULL) {
  44                fcount = kzalloc(max * sizeof *fcount, GFP_KERNEL);
  45                if (!fcount)
  46                        return -ENOMEM;
  47                FILE_FCOUNT(file) = fcount;
  48        }
  49        if (!page) {
  50                if ((base & (PAGE_SIZE - 1)) || (size & (PAGE_SIZE - 1)))
  51                        return -EINVAL;
  52                base >>= PAGE_SHIFT;
  53                size >>= PAGE_SHIFT;
  54        }
  55        reg = mtrr_add_page(base, size, type, true);
  56        if (reg >= 0)
  57                ++fcount[reg];
  58        return reg;
  59}
  60
  61static int
  62mtrr_file_del(unsigned long base, unsigned long size,
  63              struct file *file, int page)
  64{
  65        unsigned int *fcount = FILE_FCOUNT(file);
  66        int reg;
  67
  68        if (!page) {
  69                if ((base & (PAGE_SIZE - 1)) || (size & (PAGE_SIZE - 1)))
  70                        return -EINVAL;
  71                base >>= PAGE_SHIFT;
  72                size >>= PAGE_SHIFT;
  73        }
  74        reg = mtrr_del_page(-1, base, size);
  75        if (reg < 0)
  76                return reg;
  77        if (fcount == NULL)
  78                return reg;
  79        if (fcount[reg] < 1)
  80                return -EINVAL;
  81        --fcount[reg];
  82        return reg;
  83}
  84
  85/*
  86 * seq_file can seek but we ignore it.
  87 *
  88 * Format of control line:
  89 *    "base=%Lx size=%Lx type=%s" or "disable=%d"
  90 */
  91static ssize_t
  92mtrr_write(struct file *file, const char __user *buf, size_t len, loff_t * ppos)
  93{
  94        int i, err;
  95        unsigned long reg;
  96        unsigned long long base, size;
  97        char *ptr;
  98        char line[LINE_SIZE];
  99        int length;
 100        size_t linelen;
 101
 102        if (!capable(CAP_SYS_ADMIN))
 103                return -EPERM;
 104
 105        memset(line, 0, LINE_SIZE);
 106
 107        length = len;
 108        length--;
 109
 110        if (length > LINE_SIZE - 1)
 111                length = LINE_SIZE - 1;
 112
 113        if (length < 0)
 114                return -EINVAL;
 115
 116        if (copy_from_user(line, buf, length))
 117                return -EFAULT;
 118
 119        linelen = strlen(line);
 120        ptr = line + linelen - 1;
 121        if (linelen && *ptr == '\n')
 122                *ptr = '\0';
 123
 124        if (!strncmp(line, "disable=", 8)) {
 125                reg = simple_strtoul(line + 8, &ptr, 0);
 126                err = mtrr_del_page(reg, 0, 0);
 127                if (err < 0)
 128                        return err;
 129                return len;
 130        }
 131
 132        if (strncmp(line, "base=", 5))
 133                return -EINVAL;
 134
 135        base = simple_strtoull(line + 5, &ptr, 0);
 136        while (isspace(*ptr))
 137                ptr++;
 138
 139        if (strncmp(ptr, "size=", 5))
 140                return -EINVAL;
 141
 142        size = simple_strtoull(ptr + 5, &ptr, 0);
 143        if ((base & 0xfff) || (size & 0xfff))
 144                return -EINVAL;
 145        while (isspace(*ptr))
 146                ptr++;
 147
 148        if (strncmp(ptr, "type=", 5))
 149                return -EINVAL;
 150        ptr += 5;
 151        while (isspace(*ptr))
 152                ptr++;
 153
 154        for (i = 0; i < MTRR_NUM_TYPES; ++i) {
 155                if (strcmp(ptr, mtrr_strings[i]))
 156                        continue;
 157                base >>= PAGE_SHIFT;
 158                size >>= PAGE_SHIFT;
 159                err = mtrr_add_page((unsigned long)base, (unsigned long)size, i, true);
 160                if (err < 0)
 161                        return err;
 162                return len;
 163        }
 164        return -EINVAL;
 165}
 166
 167static long
 168mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)
 169{
 170        int err = 0;
 171        mtrr_type type;
 172        unsigned long size;
 173        struct mtrr_sentry sentry;
 174        struct mtrr_gentry gentry;
 175        void __user *arg = (void __user *) __arg;
 176
 177        switch (cmd) {
 178        case MTRRIOC_ADD_ENTRY:
 179        case MTRRIOC_SET_ENTRY:
 180        case MTRRIOC_DEL_ENTRY:
 181        case MTRRIOC_KILL_ENTRY:
 182        case MTRRIOC_ADD_PAGE_ENTRY:
 183        case MTRRIOC_SET_PAGE_ENTRY:
 184        case MTRRIOC_DEL_PAGE_ENTRY:
 185        case MTRRIOC_KILL_PAGE_ENTRY:
 186                if (copy_from_user(&sentry, arg, sizeof sentry))
 187                        return -EFAULT;
 188                break;
 189        case MTRRIOC_GET_ENTRY:
 190        case MTRRIOC_GET_PAGE_ENTRY:
 191                if (copy_from_user(&gentry, arg, sizeof gentry))
 192                        return -EFAULT;
 193                break;
 194#ifdef CONFIG_COMPAT
 195        case MTRRIOC32_ADD_ENTRY:
 196        case MTRRIOC32_SET_ENTRY:
 197        case MTRRIOC32_DEL_ENTRY:
 198        case MTRRIOC32_KILL_ENTRY:
 199        case MTRRIOC32_ADD_PAGE_ENTRY:
 200        case MTRRIOC32_SET_PAGE_ENTRY:
 201        case MTRRIOC32_DEL_PAGE_ENTRY:
 202        case MTRRIOC32_KILL_PAGE_ENTRY: {
 203                struct mtrr_sentry32 __user *s32;
 204
 205                s32 = (struct mtrr_sentry32 __user *)__arg;
 206                err = get_user(sentry.base, &s32->base);
 207                err |= get_user(sentry.size, &s32->size);
 208                err |= get_user(sentry.type, &s32->type);
 209                if (err)
 210                        return err;
 211                break;
 212        }
 213        case MTRRIOC32_GET_ENTRY:
 214        case MTRRIOC32_GET_PAGE_ENTRY: {
 215                struct mtrr_gentry32 __user *g32;
 216
 217                g32 = (struct mtrr_gentry32 __user *)__arg;
 218                err = get_user(gentry.regnum, &g32->regnum);
 219                err |= get_user(gentry.base, &g32->base);
 220                err |= get_user(gentry.size, &g32->size);
 221                err |= get_user(gentry.type, &g32->type);
 222                if (err)
 223                        return err;
 224                break;
 225        }
 226#endif
 227        }
 228
 229        switch (cmd) {
 230        default:
 231                return -ENOTTY;
 232        case MTRRIOC_ADD_ENTRY:
 233#ifdef CONFIG_COMPAT
 234        case MTRRIOC32_ADD_ENTRY:
 235#endif
 236                if (!capable(CAP_SYS_ADMIN))
 237                        return -EPERM;
 238                err =
 239                    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
 240                                  file, 0);
 241                break;
 242        case MTRRIOC_SET_ENTRY:
 243#ifdef CONFIG_COMPAT
 244        case MTRRIOC32_SET_ENTRY:
 245#endif
 246                if (!capable(CAP_SYS_ADMIN))
 247                        return -EPERM;
 248                err = mtrr_add(sentry.base, sentry.size, sentry.type, false);
 249                break;
 250        case MTRRIOC_DEL_ENTRY:
 251#ifdef CONFIG_COMPAT
 252        case MTRRIOC32_DEL_ENTRY:
 253#endif
 254                if (!capable(CAP_SYS_ADMIN))
 255                        return -EPERM;
 256                err = mtrr_file_del(sentry.base, sentry.size, file, 0);
 257                break;
 258        case MTRRIOC_KILL_ENTRY:
 259#ifdef CONFIG_COMPAT
 260        case MTRRIOC32_KILL_ENTRY:
 261#endif
 262                if (!capable(CAP_SYS_ADMIN))
 263                        return -EPERM;
 264                err = mtrr_del(-1, sentry.base, sentry.size);
 265                break;
 266        case MTRRIOC_GET_ENTRY:
 267#ifdef CONFIG_COMPAT
 268        case MTRRIOC32_GET_ENTRY:
 269#endif
 270                if (gentry.regnum >= num_var_ranges)
 271                        return -EINVAL;
 272                mtrr_if->get(gentry.regnum, &gentry.base, &size, &type);
 273
 274                /* Hide entries that go above 4GB */
 275                if (gentry.base + size - 1 >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT))
 276                    || size >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT)))
 277                        gentry.base = gentry.size = gentry.type = 0;
 278                else {
 279                        gentry.base <<= PAGE_SHIFT;
 280                        gentry.size = size << PAGE_SHIFT;
 281                        gentry.type = type;
 282                }
 283
 284                break;
 285        case MTRRIOC_ADD_PAGE_ENTRY:
 286#ifdef CONFIG_COMPAT
 287        case MTRRIOC32_ADD_PAGE_ENTRY:
 288#endif
 289                if (!capable(CAP_SYS_ADMIN))
 290                        return -EPERM;
 291                err =
 292                    mtrr_file_add(sentry.base, sentry.size, sentry.type, true,
 293                                  file, 1);
 294                break;
 295        case MTRRIOC_SET_PAGE_ENTRY:
 296#ifdef CONFIG_COMPAT
 297        case MTRRIOC32_SET_PAGE_ENTRY:
 298#endif
 299                if (!capable(CAP_SYS_ADMIN))
 300                        return -EPERM;
 301                err =
 302                    mtrr_add_page(sentry.base, sentry.size, sentry.type, false);
 303                break;
 304        case MTRRIOC_DEL_PAGE_ENTRY:
 305#ifdef CONFIG_COMPAT
 306        case MTRRIOC32_DEL_PAGE_ENTRY:
 307#endif
 308                if (!capable(CAP_SYS_ADMIN))
 309                        return -EPERM;
 310                err = mtrr_file_del(sentry.base, sentry.size, file, 1);
 311                break;
 312        case MTRRIOC_KILL_PAGE_ENTRY:
 313#ifdef CONFIG_COMPAT
 314        case MTRRIOC32_KILL_PAGE_ENTRY:
 315#endif
 316                if (!capable(CAP_SYS_ADMIN))
 317                        return -EPERM;
 318                err = mtrr_del_page(-1, sentry.base, sentry.size);
 319                break;
 320        case MTRRIOC_GET_PAGE_ENTRY:
 321#ifdef CONFIG_COMPAT
 322        case MTRRIOC32_GET_PAGE_ENTRY:
 323#endif
 324                if (gentry.regnum >= num_var_ranges)
 325                        return -EINVAL;
 326                mtrr_if->get(gentry.regnum, &gentry.base, &size, &type);
 327                /* Hide entries that would overflow */
 328                if (size != (__typeof__(gentry.size))size)
 329                        gentry.base = gentry.size = gentry.type = 0;
 330                else {
 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