162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * APEI Error Record Serialization Table debug support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * ERST is a way provided by APEI to save and retrieve hardware error 662306a36Sopenharmony_ci * information to and from a persistent store. This file provide the 762306a36Sopenharmony_ci * debugging/testing support for ERST kernel support and firmware 862306a36Sopenharmony_ci * implementation. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Copyright 2010 Intel Corp. 1162306a36Sopenharmony_ci * Author: Huang Ying <ying.huang@intel.com> 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/uaccess.h> 1762306a36Sopenharmony_ci#include <acpi/apei.h> 1862306a36Sopenharmony_ci#include <linux/miscdevice.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "apei-internal.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define ERST_DBG_PFX "ERST DBG: " 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define ERST_DBG_RECORD_LEN_MAX 0x4000 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic void *erst_dbg_buf; 2762306a36Sopenharmony_cistatic unsigned int erst_dbg_buf_len; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* Prevent erst_dbg_read/write from being invoked concurrently */ 3062306a36Sopenharmony_cistatic DEFINE_MUTEX(erst_dbg_mutex); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic int erst_dbg_open(struct inode *inode, struct file *file) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci int rc, *pos; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (erst_disable) 3762306a36Sopenharmony_ci return -ENODEV; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci pos = (int *)&file->private_data; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci rc = erst_get_record_id_begin(pos); 4262306a36Sopenharmony_ci if (rc) 4362306a36Sopenharmony_ci return rc; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci return nonseekable_open(inode, file); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int erst_dbg_release(struct inode *inode, struct file *file) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci erst_get_record_id_end(); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci int rc; 5862306a36Sopenharmony_ci u64 record_id; 5962306a36Sopenharmony_ci u32 record_count; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci switch (cmd) { 6262306a36Sopenharmony_ci case APEI_ERST_CLEAR_RECORD: 6362306a36Sopenharmony_ci rc = copy_from_user(&record_id, (void __user *)arg, 6462306a36Sopenharmony_ci sizeof(record_id)); 6562306a36Sopenharmony_ci if (rc) 6662306a36Sopenharmony_ci return -EFAULT; 6762306a36Sopenharmony_ci return erst_clear(record_id); 6862306a36Sopenharmony_ci case APEI_ERST_GET_RECORD_COUNT: 6962306a36Sopenharmony_ci rc = erst_get_record_count(); 7062306a36Sopenharmony_ci if (rc < 0) 7162306a36Sopenharmony_ci return rc; 7262306a36Sopenharmony_ci record_count = rc; 7362306a36Sopenharmony_ci rc = put_user(record_count, (u32 __user *)arg); 7462306a36Sopenharmony_ci if (rc) 7562306a36Sopenharmony_ci return rc; 7662306a36Sopenharmony_ci return 0; 7762306a36Sopenharmony_ci default: 7862306a36Sopenharmony_ci return -ENOTTY; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic ssize_t erst_dbg_read(struct file *filp, char __user *ubuf, 8362306a36Sopenharmony_ci size_t usize, loff_t *off) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci int rc, *pos; 8662306a36Sopenharmony_ci ssize_t len = 0; 8762306a36Sopenharmony_ci u64 id; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (*off) 9062306a36Sopenharmony_ci return -EINVAL; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (mutex_lock_interruptible(&erst_dbg_mutex) != 0) 9362306a36Sopenharmony_ci return -EINTR; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci pos = (int *)&filp->private_data; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ciretry_next: 9862306a36Sopenharmony_ci rc = erst_get_record_id_next(pos, &id); 9962306a36Sopenharmony_ci if (rc) 10062306a36Sopenharmony_ci goto out; 10162306a36Sopenharmony_ci /* no more record */ 10262306a36Sopenharmony_ci if (id == APEI_ERST_INVALID_RECORD_ID) { 10362306a36Sopenharmony_ci /* 10462306a36Sopenharmony_ci * If the persistent store is empty initially, the function 10562306a36Sopenharmony_ci * 'erst_read' below will return "-ENOENT" value. This causes 10662306a36Sopenharmony_ci * 'retry_next' label is entered again. The returned value 10762306a36Sopenharmony_ci * should be zero indicating the read operation is EOF. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_ci len = 0; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci goto out; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ciretry: 11462306a36Sopenharmony_ci rc = len = erst_read_record(id, erst_dbg_buf, erst_dbg_buf_len, 11562306a36Sopenharmony_ci erst_dbg_buf_len, NULL); 11662306a36Sopenharmony_ci /* The record may be cleared by others, try read next record */ 11762306a36Sopenharmony_ci if (rc == -ENOENT) 11862306a36Sopenharmony_ci goto retry_next; 11962306a36Sopenharmony_ci if (rc < 0) 12062306a36Sopenharmony_ci goto out; 12162306a36Sopenharmony_ci if (len > ERST_DBG_RECORD_LEN_MAX) { 12262306a36Sopenharmony_ci pr_warn(ERST_DBG_PFX 12362306a36Sopenharmony_ci "Record (ID: 0x%llx) length is too long: %zd\n", id, len); 12462306a36Sopenharmony_ci rc = -EIO; 12562306a36Sopenharmony_ci goto out; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci if (len > erst_dbg_buf_len) { 12862306a36Sopenharmony_ci void *p; 12962306a36Sopenharmony_ci rc = -ENOMEM; 13062306a36Sopenharmony_ci p = kmalloc(len, GFP_KERNEL); 13162306a36Sopenharmony_ci if (!p) 13262306a36Sopenharmony_ci goto out; 13362306a36Sopenharmony_ci kfree(erst_dbg_buf); 13462306a36Sopenharmony_ci erst_dbg_buf = p; 13562306a36Sopenharmony_ci erst_dbg_buf_len = len; 13662306a36Sopenharmony_ci goto retry; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci rc = -EINVAL; 14062306a36Sopenharmony_ci if (len > usize) 14162306a36Sopenharmony_ci goto out; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci rc = -EFAULT; 14462306a36Sopenharmony_ci if (copy_to_user(ubuf, erst_dbg_buf, len)) 14562306a36Sopenharmony_ci goto out; 14662306a36Sopenharmony_ci rc = 0; 14762306a36Sopenharmony_ciout: 14862306a36Sopenharmony_ci mutex_unlock(&erst_dbg_mutex); 14962306a36Sopenharmony_ci return rc ? rc : len; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf, 15362306a36Sopenharmony_ci size_t usize, loff_t *off) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci int rc; 15662306a36Sopenharmony_ci struct cper_record_header *rcd; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 15962306a36Sopenharmony_ci return -EPERM; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (usize > ERST_DBG_RECORD_LEN_MAX) { 16262306a36Sopenharmony_ci pr_err(ERST_DBG_PFX "Too long record to be written\n"); 16362306a36Sopenharmony_ci return -EINVAL; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (mutex_lock_interruptible(&erst_dbg_mutex)) 16762306a36Sopenharmony_ci return -EINTR; 16862306a36Sopenharmony_ci if (usize > erst_dbg_buf_len) { 16962306a36Sopenharmony_ci void *p; 17062306a36Sopenharmony_ci rc = -ENOMEM; 17162306a36Sopenharmony_ci p = kmalloc(usize, GFP_KERNEL); 17262306a36Sopenharmony_ci if (!p) 17362306a36Sopenharmony_ci goto out; 17462306a36Sopenharmony_ci kfree(erst_dbg_buf); 17562306a36Sopenharmony_ci erst_dbg_buf = p; 17662306a36Sopenharmony_ci erst_dbg_buf_len = usize; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci rc = copy_from_user(erst_dbg_buf, ubuf, usize); 17962306a36Sopenharmony_ci if (rc) { 18062306a36Sopenharmony_ci rc = -EFAULT; 18162306a36Sopenharmony_ci goto out; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci rcd = erst_dbg_buf; 18462306a36Sopenharmony_ci rc = -EINVAL; 18562306a36Sopenharmony_ci if (rcd->record_length != usize) 18662306a36Sopenharmony_ci goto out; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci rc = erst_write(erst_dbg_buf); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ciout: 19162306a36Sopenharmony_ci mutex_unlock(&erst_dbg_mutex); 19262306a36Sopenharmony_ci return rc < 0 ? rc : usize; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic const struct file_operations erst_dbg_ops = { 19662306a36Sopenharmony_ci .owner = THIS_MODULE, 19762306a36Sopenharmony_ci .open = erst_dbg_open, 19862306a36Sopenharmony_ci .release = erst_dbg_release, 19962306a36Sopenharmony_ci .read = erst_dbg_read, 20062306a36Sopenharmony_ci .write = erst_dbg_write, 20162306a36Sopenharmony_ci .unlocked_ioctl = erst_dbg_ioctl, 20262306a36Sopenharmony_ci .llseek = no_llseek, 20362306a36Sopenharmony_ci}; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic struct miscdevice erst_dbg_dev = { 20662306a36Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 20762306a36Sopenharmony_ci .name = "erst_dbg", 20862306a36Sopenharmony_ci .fops = &erst_dbg_ops, 20962306a36Sopenharmony_ci}; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic __init int erst_dbg_init(void) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci if (erst_disable) { 21462306a36Sopenharmony_ci pr_info(ERST_DBG_PFX "ERST support is disabled.\n"); 21562306a36Sopenharmony_ci return -ENODEV; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci return misc_register(&erst_dbg_dev); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic __exit void erst_dbg_exit(void) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci misc_deregister(&erst_dbg_dev); 22362306a36Sopenharmony_ci kfree(erst_dbg_buf); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cimodule_init(erst_dbg_init); 22762306a36Sopenharmony_cimodule_exit(erst_dbg_exit); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ciMODULE_AUTHOR("Huang Ying"); 23062306a36Sopenharmony_ciMODULE_DESCRIPTION("APEI Error Record Serialization Table debug support"); 23162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 232