231 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * APEI Error Record Serialization Table debug support4 *5 * ERST is a way provided by APEI to save and retrieve hardware error6 * information to and from a persistent store. This file provide the7 * debugging/testing support for ERST kernel support and firmware8 * 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 0x400025 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 function105 * 'erst_read' below will return "-ENOENT" value. This causes106 * 'retry_next' label is entered again. The returned value107 * 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_PFX123 "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};203 204static struct miscdevice erst_dbg_dev = {205 .minor = MISC_DYNAMIC_MINOR,206 .name = "erst_dbg",207 .fops = &erst_dbg_ops,208};209 210static __init int erst_dbg_init(void)211{212 if (erst_disable) {213 pr_info(ERST_DBG_PFX "ERST support is disabled.\n");214 return -ENODEV;215 }216 return misc_register(&erst_dbg_dev);217}218 219static __exit void erst_dbg_exit(void)220{221 misc_deregister(&erst_dbg_dev);222 kfree(erst_dbg_buf);223}224 225module_init(erst_dbg_init);226module_exit(erst_dbg_exit);227 228MODULE_AUTHOR("Huang Ying");229MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");230MODULE_LICENSE("GPL");231