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