mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
4.8 KiB
230 lines
4.8 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* APEI Error Record Serialization Table debug support |
|
* |
|
* ERST is a way provided by APEI to save and retrieve hardware error |
|
* information to and from a persistent store. This file provide the |
|
* debugging/testing support for ERST kernel support and firmware |
|
* implementation. |
|
* |
|
* Copyright 2010 Intel Corp. |
|
* Author: Huang Ying <[email protected]> |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/uaccess.h> |
|
#include <acpi/apei.h> |
|
#include <linux/miscdevice.h> |
|
|
|
#include "apei-internal.h" |
|
|
|
#define ERST_DBG_PFX "ERST DBG: " |
|
|
|
#define ERST_DBG_RECORD_LEN_MAX 0x4000 |
|
|
|
static void *erst_dbg_buf; |
|
static unsigned int erst_dbg_buf_len; |
|
|
|
/* Prevent erst_dbg_read/write from being invoked concurrently */ |
|
static DEFINE_MUTEX(erst_dbg_mutex); |
|
|
|
static int erst_dbg_open(struct inode *inode, struct file *file) |
|
{ |
|
int rc, *pos; |
|
|
|
if (erst_disable) |
|
return -ENODEV; |
|
|
|
pos = (int *)&file->private_data; |
|
|
|
rc = erst_get_record_id_begin(pos); |
|
if (rc) |
|
return rc; |
|
|
|
return nonseekable_open(inode, file); |
|
} |
|
|
|
static int erst_dbg_release(struct inode *inode, struct file *file) |
|
{ |
|
erst_get_record_id_end(); |
|
|
|
return 0; |
|
} |
|
|
|
static long erst_dbg_ioctl(struct file *f, unsigned int cmd, unsigned long arg) |
|
{ |
|
int rc; |
|
u64 record_id; |
|
u32 record_count; |
|
|
|
switch (cmd) { |
|
case APEI_ERST_CLEAR_RECORD: |
|
rc = copy_from_user(&record_id, (void __user *)arg, |
|
sizeof(record_id)); |
|
if (rc) |
|
return -EFAULT; |
|
return erst_clear(record_id); |
|
case APEI_ERST_GET_RECORD_COUNT: |
|
rc = erst_get_record_count(); |
|
if (rc < 0) |
|
return rc; |
|
record_count = rc; |
|
rc = put_user(record_count, (u32 __user *)arg); |
|
if (rc) |
|
return rc; |
|
return 0; |
|
default: |
|
return -ENOTTY; |
|
} |
|
} |
|
|
|
static ssize_t erst_dbg_read(struct file *filp, char __user *ubuf, |
|
size_t usize, loff_t *off) |
|
{ |
|
int rc, *pos; |
|
ssize_t len = 0; |
|
u64 id; |
|
|
|
if (*off) |
|
return -EINVAL; |
|
|
|
if (mutex_lock_interruptible(&erst_dbg_mutex) != 0) |
|
return -EINTR; |
|
|
|
pos = (int *)&filp->private_data; |
|
|
|
retry_next: |
|
rc = erst_get_record_id_next(pos, &id); |
|
if (rc) |
|
goto out; |
|
/* no more record */ |
|
if (id == APEI_ERST_INVALID_RECORD_ID) { |
|
/* |
|
* If the persistent store is empty initially, the function |
|
* 'erst_read' below will return "-ENOENT" value. This causes |
|
* 'retry_next' label is entered again. The returned value |
|
* should be zero indicating the read operation is EOF. |
|
*/ |
|
len = 0; |
|
|
|
goto out; |
|
} |
|
retry: |
|
rc = len = erst_read(id, erst_dbg_buf, erst_dbg_buf_len); |
|
/* The record may be cleared by others, try read next record */ |
|
if (rc == -ENOENT) |
|
goto retry_next; |
|
if (rc < 0) |
|
goto out; |
|
if (len > ERST_DBG_RECORD_LEN_MAX) { |
|
pr_warn(ERST_DBG_PFX |
|
"Record (ID: 0x%llx) length is too long: %zd\n", id, len); |
|
rc = -EIO; |
|
goto out; |
|
} |
|
if (len > erst_dbg_buf_len) { |
|
void *p; |
|
rc = -ENOMEM; |
|
p = kmalloc(len, GFP_KERNEL); |
|
if (!p) |
|
goto out; |
|
kfree(erst_dbg_buf); |
|
erst_dbg_buf = p; |
|
erst_dbg_buf_len = len; |
|
goto retry; |
|
} |
|
|
|
rc = -EINVAL; |
|
if (len > usize) |
|
goto out; |
|
|
|
rc = -EFAULT; |
|
if (copy_to_user(ubuf, erst_dbg_buf, len)) |
|
goto out; |
|
rc = 0; |
|
out: |
|
mutex_unlock(&erst_dbg_mutex); |
|
return rc ? rc : len; |
|
} |
|
|
|
static ssize_t erst_dbg_write(struct file *filp, const char __user *ubuf, |
|
size_t usize, loff_t *off) |
|
{ |
|
int rc; |
|
struct cper_record_header *rcd; |
|
|
|
if (!capable(CAP_SYS_ADMIN)) |
|
return -EPERM; |
|
|
|
if (usize > ERST_DBG_RECORD_LEN_MAX) { |
|
pr_err(ERST_DBG_PFX "Too long record to be written\n"); |
|
return -EINVAL; |
|
} |
|
|
|
if (mutex_lock_interruptible(&erst_dbg_mutex)) |
|
return -EINTR; |
|
if (usize > erst_dbg_buf_len) { |
|
void *p; |
|
rc = -ENOMEM; |
|
p = kmalloc(usize, GFP_KERNEL); |
|
if (!p) |
|
goto out; |
|
kfree(erst_dbg_buf); |
|
erst_dbg_buf = p; |
|
erst_dbg_buf_len = usize; |
|
} |
|
rc = copy_from_user(erst_dbg_buf, ubuf, usize); |
|
if (rc) { |
|
rc = -EFAULT; |
|
goto out; |
|
} |
|
rcd = erst_dbg_buf; |
|
rc = -EINVAL; |
|
if (rcd->record_length != usize) |
|
goto out; |
|
|
|
rc = erst_write(erst_dbg_buf); |
|
|
|
out: |
|
mutex_unlock(&erst_dbg_mutex); |
|
return rc < 0 ? rc : usize; |
|
} |
|
|
|
static const struct file_operations erst_dbg_ops = { |
|
.owner = THIS_MODULE, |
|
.open = erst_dbg_open, |
|
.release = erst_dbg_release, |
|
.read = erst_dbg_read, |
|
.write = erst_dbg_write, |
|
.unlocked_ioctl = erst_dbg_ioctl, |
|
.llseek = no_llseek, |
|
}; |
|
|
|
static struct miscdevice erst_dbg_dev = { |
|
.minor = MISC_DYNAMIC_MINOR, |
|
.name = "erst_dbg", |
|
.fops = &erst_dbg_ops, |
|
}; |
|
|
|
static __init int erst_dbg_init(void) |
|
{ |
|
if (erst_disable) { |
|
pr_info(ERST_DBG_PFX "ERST support is disabled.\n"); |
|
return -ENODEV; |
|
} |
|
return misc_register(&erst_dbg_dev); |
|
} |
|
|
|
static __exit void erst_dbg_exit(void) |
|
{ |
|
misc_deregister(&erst_dbg_dev); |
|
kfree(erst_dbg_buf); |
|
} |
|
|
|
module_init(erst_dbg_init); |
|
module_exit(erst_dbg_exit); |
|
|
|
MODULE_AUTHOR("Huang Ying"); |
|
MODULE_DESCRIPTION("APEI Error Record Serialization Table debug support"); |
|
MODULE_LICENSE("GPL");
|
|
|