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