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