linux/drivers/acpi/apei/erst-dbg.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * APEI Error Record Serialization Table debug support
   4 *
   5 * ERST is a way provided by APEI to save and retrieve hardware error
   6 * information to and from a persistent store. This file provide the
   7 * debugging/testing support for ERST kernel support and firmware
   8 * implementation.
   9 *
  10 * Copyright 2010 Intel Corp.
  11 *   Author: Huang Ying <ying.huang@intel.com>
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/uaccess.h>
  17#include <acpi/apei.h>
  18#include <linux/miscdevice.h>
  19
  20#include "apei-internal.h"
  21
  22#define ERST_DBG_PFX                    "ERST DBG: "
  23
  24#define ERST_DBG_RECORD_LEN_MAX         0x4000
  25
  26static void *erst_dbg_buf;
  27static unsigned int erst_dbg_buf_len;
  28
  29/* Prevent erst_dbg_read/write from being invoked concurrently */
  30static DEFINE_MUTEX(erst_dbg_mutex);
  31
  32static int erst_dbg_open(struct inode *inode, struct file *file)
  33{
  34        int rc, *pos;
  35
  36        if (erst_disable)
  37                return -ENODEV;
  38
  39        pos = (int *)&file->private_data;
  40
  41        rc = erst_get_record_id_begin(pos);
  42        if (rc)
  43                return rc;
  44
  45        return nonseekable_open(inode, file);
  46}
  47
  48static int erst_dbg_release(struct inode *inode, struct file *file)
  49{
  50        erst_get_record_id_end();
  51
  52        return 0;
  53}
  54
  55static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
  56{
  57        int rc;
  58        u64 record_id;
  59        u32 record_count;
  60
  61        switch (cmd) {
  62        case APEI_ERST_CLEAR_RECORD:
  63                rc = copy_from_user(&record_id, (void __user *)arg,
  64                                    sizeof(record_id));
  65                if (rc)
  66                        return -EFAULT;
  67                return erst_clear(record_id);
  68        case APEI_ERST_GET_RECORD_COUNT:
  69                rc = erst_get_record_count();
  70                if (rc < 0)
  71                        return rc;
  72                record_count = rc;
  73                rc = put_user(record_count, (u32 __user *)arg);
  74                if (rc)
  75                        return rc;
  76                return 0;
  77        default:
  78                return -ENOTTY;
  79        }
  80}
  81
  82static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
  83                             size_t usize, loff_t *off)
  84{
  85        int rc, *pos;
  86        ssize_t len = 0;
  87        u64 id;
  88
  89        if (*off)
  90                return -EINVAL;
  91
  92        if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
  93                return -EINTR;
  94
  95        pos = (int *)&filp->private_data;
  96
  97retry_next:
  98        rc = erst_get_record_id_next(pos, &id);
  99        if (rc)
 100                goto out;
 101        /* no more record */
 102        if (id == APEI_ERST_INVALID_RECORD_ID) {
 103                /*
 104                 * If the persistent store is empty initially, the function
 105                 * 'erst_read' below will return "-ENOENT" value. This causes
 106                 * 'retry_next' label is entered again. The returned value
 107                 * should be zero indicating the read operation is EOF.
 108                 */
 109                len = 0;
 110
 111                goto out;
 112        }
 113retry:
 114        rc = len = erst_read(id, erst_dbg_buf, erst_dbg_buf_len);
 115        /* The record may be cleared by others, try read next record */
 116        if (rc == -ENOENT)
 117                goto retry_next;
 118        if (rc < 0)
 119                goto out;
 120        if (len > ERST_DBG_RECORD_LEN_MAX) {
 121                pr_warn(ERST_DBG_PFX
 122                        "Record (ID: 0x%llx) length is too long: %zd\n", id, len);
 123                rc = -EIO;
 124                goto out;
 125        }
 126        if (len > erst_dbg_buf_len) {
 127                void *p;
 128                rc = -ENOMEM;
 129                p = kmalloc(len, GFP_KERNEL);
 130                if (!p)
 131                        goto out;
 132                kfree(erst_dbg_buf);
 133                erst_dbg_buf = p;
 134                erst_dbg_buf_len = len;
 135                goto retry;
 136        }
 137
 138        rc = -EINVAL;
 139        if (len > usize)
 140                goto out;
 141
 142        rc = -EFAULT;
 143        if (copy_to_user(ubuf, erst_dbg_buf, len))
 144                goto out;
 145        rc = 0;
 146out:
 147        mutex_unlock(&erst_dbg_mutex);
 148        return rc ? rc : len;
 149}
 150
 151static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
 152                              size_t usize, loff_t *off)
 153{
 154        int rc;
 155        struct cper_record_header *rcd;
 156
 157        if (!capable(CAP_SYS_ADMIN))
 158                return -EPERM;
 159
 160        if (usize > ERST_DBG_RECORD_LEN_MAX) {
 161                pr_err(ERST_DBG_PFX "Too long record to be written\n");
 162                return -EINVAL;
 163        }
 164
 165        if (mutex_lock_interruptible(&erst_dbg_mutex))
 166                return -EINTR;
 167        if (usize > erst_dbg_buf_len) {
 168                void *p;
 169                rc = -ENOMEM;
 170                p = kmalloc(usize, GFP_KERNEL);
 171                if (!p)
 172                        goto out;
 173                kfree(erst_dbg_buf);
 174                erst_dbg_buf = p;
 175                erst_dbg_buf_len = usize;
 176        }
 177        rc = copy_from_user(erst_dbg_buf, ubuf, usize);
 178        if (rc) {
 179                rc = -EFAULT;
 180                goto out;
 181        }
 182        rcd = erst_dbg_buf;
 183        rc = -EINVAL;
 184        if (rcd->record_length != usize)
 185                goto out;
 186
 187        rc = erst_write(erst_dbg_buf);
 188
 189out:
 190        mutex_unlock(&erst_dbg_mutex);
 191        return rc < 0 ? rc : usize;
 192}
 193
 194static const struct file_operations erst_dbg_ops = {
 195        .owner          = THIS_MODULE,
 196        .open           = erst_dbg_open,
 197        .release        = erst_dbg_release,
 198        .read           = erst_dbg_read,
 199        .write          = erst_dbg_write,
 200        .unlocked_ioctl = erst_dbg_ioctl,
 201        .llseek         = no_llseek,
 202};
 203
 204static struct miscdevice erst_dbg_dev = {
 205        .minor  = MISC_DYNAMIC_MINOR,
 206        .name   = "erst_dbg",
 207        .fops   = &erst_dbg_ops,
 208};
 209
 210static __init int erst_dbg_init(void)
 211{
 212        if (erst_disable) {
 213                pr_info(ERST_DBG_PFX "ERST support is disabled.\n");
 214                return -ENODEV;
 215        }
 216        return misc_register(&erst_dbg_dev);
 217}
 218
 219static __exit void erst_dbg_exit(void)
 220{
 221        misc_deregister(&erst_dbg_dev);
 222        kfree(erst_dbg_buf);
 223}
 224
 225module_init(erst_dbg_init);
 226module_exit(erst_dbg_exit);
 227
 228MODULE_AUTHOR("Huang Ying");
 229MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
 230MODULE_LICENSE("GPL");
 231