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