linux/drivers/acpi/apei/erst-dbg.c
<<
>>
Prefs
   1/*
   2 * APEI Error Record Serialization Table debug support
   3 *
   4 * ERST is a way provided by APEI to save and retrieve hardware error
   5 * information to and from a persistent store. This file provide the
   6 * debugging/testing support for ERST kernel support and firmware
   7 * implementation.
   8 *
   9 * Copyright 2010 Intel Corp.
  10 *   Author: Huang Ying <ying.huang@intel.com>
  11 *
  12 * This program is free software; you can redistribute it and/or
  13 * modify it under the terms of the GNU General Public License version
  14 * 2 as published by the Free Software Foundation.
  15 *
  16 * This program is distributed in the hope that it will be useful,
  17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19 * GNU General Public License for more details.
  20 *
  21 * You should have received a copy of the GNU General Public License
  22 * along with this program; if not, write to the Free Software
  23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  24 */
  25
  26#include <linux/kernel.h>
  27#include <linux/module.h>
  28#include <linux/uaccess.h>
  29#include <acpi/apei.h>
  30#include <linux/miscdevice.h>
  31
  32#include "apei-internal.h"
  33
  34#define ERST_DBG_PFX                    "ERST DBG: "
  35
  36#define ERST_DBG_RECORD_LEN_MAX         4096
  37
  38static void *erst_dbg_buf;
  39static unsigned int erst_dbg_buf_len;
  40
  41/* Prevent erst_dbg_read/write from being invoked concurrently */
  42static DEFINE_MUTEX(erst_dbg_mutex);
  43
  44static int erst_dbg_open(struct inode *inode, struct file *file)
  45{
  46        if (erst_disable)
  47                return -ENODEV;
  48
  49        return nonseekable_open(inode, file);
  50}
  51
  52static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
  53{
  54        int rc;
  55        u64 record_id;
  56        u32 record_count;
  57
  58        switch (cmd) {
  59        case APEI_ERST_CLEAR_RECORD:
  60                rc = copy_from_user(&record_id, (void __user *)arg,
  61                                    sizeof(record_id));
  62                if (rc)
  63                        return -EFAULT;
  64                return erst_clear(record_id);
  65        case APEI_ERST_GET_RECORD_COUNT:
  66                rc = erst_get_record_count();
  67                if (rc < 0)
  68                        return rc;
  69                record_count = rc;
  70                rc = put_user(record_count, (u32 __user *)arg);
  71                if (rc)
  72                        return rc;
  73                return 0;
  74        default:
  75                return -ENOTTY;
  76        }
  77}
  78
  79static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
  80                             size_t usize, loff_t *off)
  81{
  82        int rc;
  83        ssize_t len = 0;
  84        u64 id;
  85
  86        if (*off != 0)
  87                return -EINVAL;
  88
  89        if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
  90                return -EINTR;
  91
  92retry_next:
  93        rc = erst_get_next_record_id(&id);
  94        if (rc)
  95                goto out;
  96        /* no more record */
  97        if (id == APEI_ERST_INVALID_RECORD_ID)
  98                goto out;
  99retry:
 100        rc = len = erst_read(id, erst_dbg_buf, erst_dbg_buf_len);
 101        /* The record may be cleared by others, try read next record */
 102        if (rc == -ENOENT)
 103                goto retry_next;
 104        if (rc < 0)
 105                goto out;
 106        if (len > ERST_DBG_RECORD_LEN_MAX) {
 107                pr_warning(ERST_DBG_PFX
 108                           "Record (ID: 0x%llx) length is too long: %zd\n",
 109                           id, len);
 110                rc = -EIO;
 111                goto out;
 112        }
 113        if (len > erst_dbg_buf_len) {
 114                void *p;
 115                rc = -ENOMEM;
 116                p = kmalloc(len, GFP_KERNEL);
 117                if (!p)
 118                        goto out;
 119                kfree(erst_dbg_buf);
 120                erst_dbg_buf = p;
 121                erst_dbg_buf_len = len;
 122                goto retry;
 123        }
 124
 125        rc = -EINVAL;
 126        if (len > usize)
 127                goto out;
 128
 129        rc = -EFAULT;
 130        if (copy_to_user(ubuf, erst_dbg_buf, len))
 131                goto out;
 132        rc = 0;
 133out:
 134        mutex_unlock(&erst_dbg_mutex);
 135        return rc ? rc : len;
 136}
 137
 138static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
 139                              size_t usize, loff_t *off)
 140{
 141        int rc;
 142        struct cper_record_header *rcd;
 143
 144        if (!capable(CAP_SYS_ADMIN))
 145                return -EPERM;
 146
 147        if (usize > ERST_DBG_RECORD_LEN_MAX) {
 148                pr_err(ERST_DBG_PFX "Too long record to be written\n");
 149                return -EINVAL;
 150        }
 151
 152        if (mutex_lock_interruptible(&erst_dbg_mutex))
 153                return -EINTR;
 154        if (usize > erst_dbg_buf_len) {
 155                void *p;
 156                rc = -ENOMEM;
 157                p = kmalloc(usize, GFP_KERNEL);
 158                if (!p)
 159                        goto out;
 160                kfree(erst_dbg_buf);
 161                erst_dbg_buf = p;
 162                erst_dbg_buf_len = usize;
 163        }
 164        rc = copy_from_user(erst_dbg_buf, ubuf, usize);
 165        if (rc) {
 166                rc = -EFAULT;
 167                goto out;
 168        }
 169        rcd = erst_dbg_buf;
 170        rc = -EINVAL;
 171        if (rcd->record_length != usize)
 172                goto out;
 173
 174        rc = erst_write(erst_dbg_buf);
 175
 176out:
 177        mutex_unlock(&erst_dbg_mutex);
 178        return rc < 0 ? rc : usize;
 179}
 180
 181static const struct file_operations erst_dbg_ops = {
 182        .owner          = THIS_MODULE,
 183        .open           = erst_dbg_open,
 184        .read           = erst_dbg_read,
 185        .write          = erst_dbg_write,
 186        .unlocked_ioctl = erst_dbg_ioctl,
 187        .llseek         = no_llseek,
 188};
 189
 190static struct miscdevice erst_dbg_dev = {
 191        .minor  = MISC_DYNAMIC_MINOR,
 192        .name   = "erst_dbg",
 193        .fops   = &erst_dbg_ops,
 194};
 195
 196static __init int erst_dbg_init(void)
 197{
 198        return misc_register(&erst_dbg_dev);
 199}
 200
 201static __exit void erst_dbg_exit(void)
 202{
 203        misc_deregister(&erst_dbg_dev);
 204        kfree(erst_dbg_buf);
 205}
 206
 207module_init(erst_dbg_init);
 208module_exit(erst_dbg_exit);
 209
 210MODULE_AUTHOR("Huang Ying");
 211MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
 212MODULE_LICENSE("GPL");
 213