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        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  */
 426