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