brintos

brintos / linux-shallow public Read only

0
0
Text · 4.8 KiB · 2460763 Raw
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