xref: /openbmc/linux/drivers/acpi/apei/erst-dbg.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
22ff729d5SHuang Ying /*
32ff729d5SHuang Ying  * APEI Error Record Serialization Table debug support
42ff729d5SHuang Ying  *
52ff729d5SHuang Ying  * ERST is a way provided by APEI to save and retrieve hardware error
658f87ed0SLucas De Marchi  * information to and from a persistent store. This file provide the
72ff729d5SHuang Ying  * debugging/testing support for ERST kernel support and firmware
82ff729d5SHuang Ying  * implementation.
92ff729d5SHuang Ying  *
102ff729d5SHuang Ying  * Copyright 2010 Intel Corp.
112ff729d5SHuang Ying  *   Author: Huang Ying <ying.huang@intel.com>
122ff729d5SHuang Ying  */
132ff729d5SHuang Ying 
142ff729d5SHuang Ying #include <linux/kernel.h>
152ff729d5SHuang Ying #include <linux/module.h>
162ff729d5SHuang Ying #include <linux/uaccess.h>
172ff729d5SHuang Ying #include <acpi/apei.h>
182ff729d5SHuang Ying #include <linux/miscdevice.h>
192ff729d5SHuang Ying 
202ff729d5SHuang Ying #include "apei-internal.h"
212ff729d5SHuang Ying 
222ff729d5SHuang Ying #define ERST_DBG_PFX			"ERST DBG: "
232ff729d5SHuang Ying 
24d37afc50SChen Gong #define ERST_DBG_RECORD_LEN_MAX		0x4000
252ff729d5SHuang Ying 
262ff729d5SHuang Ying static void *erst_dbg_buf;
272ff729d5SHuang Ying static unsigned int erst_dbg_buf_len;
282ff729d5SHuang Ying 
292ff729d5SHuang Ying /* Prevent erst_dbg_read/write from being invoked concurrently */
302ff729d5SHuang Ying static DEFINE_MUTEX(erst_dbg_mutex);
312ff729d5SHuang Ying 
erst_dbg_open(struct inode * inode,struct file * file)322ff729d5SHuang Ying static int erst_dbg_open(struct inode *inode, struct file *file)
332ff729d5SHuang Ying {
34885b976fSHuang Ying 	int rc, *pos;
35885b976fSHuang Ying 
362ff729d5SHuang Ying 	if (erst_disable)
372ff729d5SHuang Ying 		return -ENODEV;
382ff729d5SHuang Ying 
39885b976fSHuang Ying 	pos = (int *)&file->private_data;
40885b976fSHuang Ying 
41885b976fSHuang Ying 	rc = erst_get_record_id_begin(pos);
42885b976fSHuang Ying 	if (rc)
43885b976fSHuang Ying 		return rc;
44885b976fSHuang Ying 
452ff729d5SHuang Ying 	return nonseekable_open(inode, file);
462ff729d5SHuang Ying }
472ff729d5SHuang Ying 
erst_dbg_release(struct inode * inode,struct file * file)48885b976fSHuang Ying static int erst_dbg_release(struct inode *inode, struct file *file)
49885b976fSHuang Ying {
50885b976fSHuang Ying 	erst_get_record_id_end();
51885b976fSHuang Ying 
52885b976fSHuang Ying 	return 0;
53885b976fSHuang Ying }
54885b976fSHuang Ying 
erst_dbg_ioctl(struct file * f,unsigned int cmd,unsigned long arg)552ff729d5SHuang Ying static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
562ff729d5SHuang Ying {
572ff729d5SHuang Ying 	int rc;
582ff729d5SHuang Ying 	u64 record_id;
592ff729d5SHuang Ying 	u32 record_count;
602ff729d5SHuang Ying 
612ff729d5SHuang Ying 	switch (cmd) {
622ff729d5SHuang Ying 	case APEI_ERST_CLEAR_RECORD:
632ff729d5SHuang Ying 		rc = copy_from_user(&record_id, (void __user *)arg,
642ff729d5SHuang Ying 				    sizeof(record_id));
652ff729d5SHuang Ying 		if (rc)
662ff729d5SHuang Ying 			return -EFAULT;
672ff729d5SHuang Ying 		return erst_clear(record_id);
682ff729d5SHuang Ying 	case APEI_ERST_GET_RECORD_COUNT:
692ff729d5SHuang Ying 		rc = erst_get_record_count();
702ff729d5SHuang Ying 		if (rc < 0)
712ff729d5SHuang Ying 			return rc;
722ff729d5SHuang Ying 		record_count = rc;
732ff729d5SHuang Ying 		rc = put_user(record_count, (u32 __user *)arg);
742ff729d5SHuang Ying 		if (rc)
752ff729d5SHuang Ying 			return rc;
762ff729d5SHuang Ying 		return 0;
772ff729d5SHuang Ying 	default:
782ff729d5SHuang Ying 		return -ENOTTY;
792ff729d5SHuang Ying 	}
802ff729d5SHuang Ying }
812ff729d5SHuang Ying 
erst_dbg_read(struct file * filp,char __user * ubuf,size_t usize,loff_t * off)822ff729d5SHuang Ying static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf,
832ff729d5SHuang Ying 			     size_t usize, loff_t *off)
842ff729d5SHuang Ying {
85885b976fSHuang Ying 	int rc, *pos;
862ff729d5SHuang Ying 	ssize_t len = 0;
872ff729d5SHuang Ying 	u64 id;
882ff729d5SHuang Ying 
89885b976fSHuang Ying 	if (*off)
902ff729d5SHuang Ying 		return -EINVAL;
912ff729d5SHuang Ying 
922ff729d5SHuang Ying 	if (mutex_lock_interruptible(&erst_dbg_mutex) != 0)
932ff729d5SHuang Ying 		return -EINTR;
942ff729d5SHuang Ying 
95885b976fSHuang Ying 	pos = (int *)&filp->private_data;
96885b976fSHuang Ying 
972ff729d5SHuang Ying retry_next:
98885b976fSHuang Ying 	rc = erst_get_record_id_next(pos, &id);
992ff729d5SHuang Ying 	if (rc)
1002ff729d5SHuang Ying 		goto out;
1012ff729d5SHuang Ying 	/* no more record */
1026780aa68SAdrian Huang 	if (id == APEI_ERST_INVALID_RECORD_ID) {
1036780aa68SAdrian Huang 		/*
1046780aa68SAdrian Huang 		 * If the persistent store is empty initially, the function
1056780aa68SAdrian Huang 		 * 'erst_read' below will return "-ENOENT" value. This causes
1066780aa68SAdrian Huang 		 * 'retry_next' label is entered again. The returned value
1076780aa68SAdrian Huang 		 * should be zero indicating the read operation is EOF.
1086780aa68SAdrian Huang 		 */
1096780aa68SAdrian Huang 		len = 0;
1106780aa68SAdrian Huang 
1112ff729d5SHuang Ying 		goto out;
1126780aa68SAdrian Huang 	}
1132ff729d5SHuang Ying retry:
114*a0909315SLiu Xinpeng 	rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len,
115*a0909315SLiu Xinpeng 			erst_dbg_buf_len, NULL);
1162ff729d5SHuang Ying 	/* The record may be cleared by others, try read next record */
1172ff729d5SHuang Ying 	if (rc == -ENOENT)
1182ff729d5SHuang Ying 		goto retry_next;
1192ff729d5SHuang Ying 	if (rc < 0)
1202ff729d5SHuang Ying 		goto out;
1212ff729d5SHuang Ying 	if (len > ERST_DBG_RECORD_LEN_MAX) {
122933ca4e3SKefeng Wang 		pr_warn(ERST_DBG_PFX
123933ca4e3SKefeng Wang 			"Record (ID: 0x%llx) length is too long: %zd\n", id, len);
1242ff729d5SHuang Ying 		rc = -EIO;
1252ff729d5SHuang Ying 		goto out;
1262ff729d5SHuang Ying 	}
1272ff729d5SHuang Ying 	if (len > erst_dbg_buf_len) {
12823f124caSHuang Ying 		void *p;
1292ff729d5SHuang Ying 		rc = -ENOMEM;
13023f124caSHuang Ying 		p = kmalloc(len, GFP_KERNEL);
13123f124caSHuang Ying 		if (!p)
1322ff729d5SHuang Ying 			goto out;
13323f124caSHuang Ying 		kfree(erst_dbg_buf);
13423f124caSHuang Ying 		erst_dbg_buf = p;
1352ff729d5SHuang Ying 		erst_dbg_buf_len = len;
1362ff729d5SHuang Ying 		goto retry;
1372ff729d5SHuang Ying 	}
1382ff729d5SHuang Ying 
1392ff729d5SHuang Ying 	rc = -EINVAL;
1402ff729d5SHuang Ying 	if (len > usize)
1412ff729d5SHuang Ying 		goto out;
1422ff729d5SHuang Ying 
1432ff729d5SHuang Ying 	rc = -EFAULT;
1442ff729d5SHuang Ying 	if (copy_to_user(ubuf, erst_dbg_buf, len))
1452ff729d5SHuang Ying 		goto out;
1462ff729d5SHuang Ying 	rc = 0;
1472ff729d5SHuang Ying out:
1482ff729d5SHuang Ying 	mutex_unlock(&erst_dbg_mutex);
1492ff729d5SHuang Ying 	return rc ? rc : len;
1502ff729d5SHuang Ying }
1512ff729d5SHuang Ying 
erst_dbg_write(struct file * filp,const char __user * ubuf,size_t usize,loff_t * off)1522ff729d5SHuang Ying static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf,
1532ff729d5SHuang Ying 			      size_t usize, loff_t *off)
1542ff729d5SHuang Ying {
1552ff729d5SHuang Ying 	int rc;
1562ff729d5SHuang Ying 	struct cper_record_header *rcd;
1572ff729d5SHuang Ying 
1582ff729d5SHuang Ying 	if (!capable(CAP_SYS_ADMIN))
1592ff729d5SHuang Ying 		return -EPERM;
1602ff729d5SHuang Ying 
1612ff729d5SHuang Ying 	if (usize > ERST_DBG_RECORD_LEN_MAX) {
1622ff729d5SHuang Ying 		pr_err(ERST_DBG_PFX "Too long record to be written\n");
1632ff729d5SHuang Ying 		return -EINVAL;
1642ff729d5SHuang Ying 	}
1652ff729d5SHuang Ying 
1662ff729d5SHuang Ying 	if (mutex_lock_interruptible(&erst_dbg_mutex))
1672ff729d5SHuang Ying 		return -EINTR;
1682ff729d5SHuang Ying 	if (usize > erst_dbg_buf_len) {
16923f124caSHuang Ying 		void *p;
1702ff729d5SHuang Ying 		rc = -ENOMEM;
17123f124caSHuang Ying 		p = kmalloc(usize, GFP_KERNEL);
17223f124caSHuang Ying 		if (!p)
1732ff729d5SHuang Ying 			goto out;
17423f124caSHuang Ying 		kfree(erst_dbg_buf);
17523f124caSHuang Ying 		erst_dbg_buf = p;
1762ff729d5SHuang Ying 		erst_dbg_buf_len = usize;
1772ff729d5SHuang Ying 	}
1782ff729d5SHuang Ying 	rc = copy_from_user(erst_dbg_buf, ubuf, usize);
1792ff729d5SHuang Ying 	if (rc) {
1802ff729d5SHuang Ying 		rc = -EFAULT;
1812ff729d5SHuang Ying 		goto out;
1822ff729d5SHuang Ying 	}
1832ff729d5SHuang Ying 	rcd = erst_dbg_buf;
1842ff729d5SHuang Ying 	rc = -EINVAL;
1852ff729d5SHuang Ying 	if (rcd->record_length != usize)
1862ff729d5SHuang Ying 		goto out;
1872ff729d5SHuang Ying 
1882ff729d5SHuang Ying 	rc = erst_write(erst_dbg_buf);
1892ff729d5SHuang Ying 
1902ff729d5SHuang Ying out:
1912ff729d5SHuang Ying 	mutex_unlock(&erst_dbg_mutex);
1922ff729d5SHuang Ying 	return rc < 0 ? rc : usize;
1932ff729d5SHuang Ying }
1942ff729d5SHuang Ying 
1952ff729d5SHuang Ying static const struct file_operations erst_dbg_ops = {
1962ff729d5SHuang Ying 	.owner		= THIS_MODULE,
1972ff729d5SHuang Ying 	.open		= erst_dbg_open,
198885b976fSHuang Ying 	.release	= erst_dbg_release,
1992ff729d5SHuang Ying 	.read		= erst_dbg_read,
2002ff729d5SHuang Ying 	.write		= erst_dbg_write,
2012ff729d5SHuang Ying 	.unlocked_ioctl	= erst_dbg_ioctl,
2026038f373SArnd Bergmann 	.llseek		= no_llseek,
2032ff729d5SHuang Ying };
2042ff729d5SHuang Ying 
2052ff729d5SHuang Ying static struct miscdevice erst_dbg_dev = {
2062ff729d5SHuang Ying 	.minor	= MISC_DYNAMIC_MINOR,
2072ff729d5SHuang Ying 	.name	= "erst_dbg",
2082ff729d5SHuang Ying 	.fops	= &erst_dbg_ops,
2092ff729d5SHuang Ying };
2102ff729d5SHuang Ying 
erst_dbg_init(void)2112ff729d5SHuang Ying static __init int erst_dbg_init(void)
2122ff729d5SHuang Ying {
213ca7cc511SHuang Ying 	if (erst_disable) {
214ca7cc511SHuang Ying 		pr_info(ERST_DBG_PFX "ERST support is disabled.\n");
215ca7cc511SHuang Ying 		return -ENODEV;
216ca7cc511SHuang Ying 	}
2172ff729d5SHuang Ying 	return misc_register(&erst_dbg_dev);
2182ff729d5SHuang Ying }
2192ff729d5SHuang Ying 
erst_dbg_exit(void)2202ff729d5SHuang Ying static __exit void erst_dbg_exit(void)
2212ff729d5SHuang Ying {
2222ff729d5SHuang Ying 	misc_deregister(&erst_dbg_dev);
2232ff729d5SHuang Ying 	kfree(erst_dbg_buf);
2242ff729d5SHuang Ying }
2252ff729d5SHuang Ying 
2262ff729d5SHuang Ying module_init(erst_dbg_init);
2272ff729d5SHuang Ying module_exit(erst_dbg_exit);
2282ff729d5SHuang Ying 
2292ff729d5SHuang Ying MODULE_AUTHOR("Huang Ying");
2302ff729d5SHuang Ying MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support");
2312ff729d5SHuang Ying MODULE_LICENSE("GPL");
232