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 38 static void *erst_dbg_buf; 39 static unsigned int erst_dbg_buf_len; 40 41 /* Prevent erst_dbg_read/write from being invoked concurrently */ 42 static DEFINE_MUTEX(erst_dbg_mutex); 43 44 static 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 52 static 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 79 static 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 92 retry_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; 99 retry: 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; 133 out: 134 mutex_unlock(&erst_dbg_mutex); 135 return rc ? rc : len; 136 } 137 138 static 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 176 out: 177 mutex_unlock(&erst_dbg_mutex); 178 return rc < 0 ? rc : usize; 179 } 180 181 static 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 190 static struct miscdevice erst_dbg_dev = { 191 .minor = MISC_DYNAMIC_MINOR, 192 .name = "erst_dbg", 193 .fops = &erst_dbg_ops, 194 }; 195 196 static __init int erst_dbg_init(void) 197 { 198 return misc_register(&erst_dbg_dev); 199 } 200 201 static __exit void erst_dbg_exit(void) 202 { 203 misc_deregister(&erst_dbg_dev); 204 kfree(erst_dbg_buf); 205 } 206 207 module_init(erst_dbg_init); 208 module_exit(erst_dbg_exit); 209 210 MODULE_AUTHOR("Huang Ying"); 211 MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support"); 212 MODULE_LICENSE("GPL"); 213