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 34 static void *erst_dbg_buf; 35 static unsigned int erst_dbg_buf_len; 36 37 /* Prevent erst_dbg_read/write from being invoked concurrently */ 38 static DEFINE_MUTEX(erst_dbg_mutex); 39 40 static 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 56 static int erst_dbg_release(struct inode *inode, struct file *file) 57 { 58 erst_get_record_id_end(); 59 60 return 0; 61 } 62 63 static 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 90 static 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 105 retry_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 } 121 retry: 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; 155 out: 156 mutex_unlock(&erst_dbg_mutex); 157 return rc ? rc : len; 158 } 159 160 static 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 198 out: 199 mutex_unlock(&erst_dbg_mutex); 200 return rc < 0 ? rc : usize; 201 } 202 203 static 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 213 static struct miscdevice erst_dbg_dev = { 214 .minor = MISC_DYNAMIC_MINOR, 215 .name = "erst_dbg", 216 .fops = &erst_dbg_ops, 217 }; 218 219 static __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 228 static __exit void erst_dbg_exit(void) 229 { 230 misc_deregister(&erst_dbg_dev); 231 kfree(erst_dbg_buf); 232 } 233 234 module_init(erst_dbg_init); 235 module_exit(erst_dbg_exit); 236 237 MODULE_AUTHOR("Huang Ying"); 238 MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support"); 239 MODULE_LICENSE("GPL"); 240