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