@@ -77,3 +77,10 @@ config ACPI_APEI_ERST_DEBUG
error information to and from a persistent store. Enable this
if you want to debugging and testing the ERST kernel support
and firmware implementation.
+
+config ACPI_APEI_BERT_DATA
+ tristate "APEI BERT data driver"
+ depends on ACPI_APEI
+ help
+ This driver provides read access to the error record that
+ the ACPI/APEI/BERT table points at.
@@ -2,5 +2,6 @@ obj-$(CONFIG_ACPI_APEI) += apei.o
obj-$(CONFIG_ACPI_APEI_GHES) += ghes.o
obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o
obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o
+obj-$(CONFIG_ACPI_APEI_BERT_DATA) += bert-data.o
apei-y := apei-base.o hest.o erst.o bert.o
new file mode 100644
@@ -0,0 +1,107 @@
+/*
+ * bert-data: driver to provide read access to the error record(s)
+ * provided by the ACPI BERT table.
+ * See ACPI specification section 18.3.1 "Boot Error Source"
+ *
+ * Copyright (C) 2017 Intel Corporation
+ *
+ * Author:
+ * Tony Luck <tony.luck@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#define pr_fmt(fmt) "bert-data: " fmt
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <acpi/acpiosxf.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/uaccess.h>
+
+static u32 bert_size;
+static __iomem void *bert_data;
+
+static int bert_chrdev_open(struct inode *inode, struct file *file)
+{
+ if (file->f_flags & (O_WRONLY | O_RDWR))
+ return -EPERM;
+ inode->i_size = bert_size;
+ return 0;
+}
+
+static ssize_t bert_chrdev_read(struct file *filp, char __user *ubuf,
+ size_t usize, loff_t *off)
+{
+ if (*off > bert_size)
+ return -EINVAL;
+ if (*off + usize > bert_size)
+ usize = bert_size - *off;
+ if (copy_to_user(ubuf, bert_data + *off, usize))
+ return -EFAULT;
+ *off += usize;
+ return usize;
+}
+
+static const struct file_operations bert_chrdev_ops = {
+ .open = bert_chrdev_open,
+ .read = bert_chrdev_read,
+ .llseek = default_llseek,
+};
+
+static struct miscdevice bert_chrdev_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "bert-data",
+ .fops = &bert_chrdev_ops,
+ .mode = 0444,
+};
+
+static __init int bert_init(void)
+{
+ struct acpi_table_bert *bert;
+ acpi_status stat;
+ int err;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ stat = acpi_get_table(ACPI_SIG_BERT, 0,
+ (struct acpi_table_header **)&bert);
+ if (stat == AE_NOT_FOUND)
+ return -ENODEV;
+ if (ACPI_FAILURE(stat)) {
+ pr_err("get table failed, %s.\n", acpi_format_exception(stat));
+ return -EINVAL;
+ }
+
+ bert_size = bert->region_length;
+ bert_data = acpi_os_map_memory(bert->address, bert->region_length);
+ acpi_put_table((struct acpi_table_header *)bert);
+ if (!bert_data)
+ return -ENOMEM;
+ err = misc_register(&bert_chrdev_device);
+ if (err)
+ acpi_os_unmap_memory(bert_data, bert_size);
+
+ return err;
+}
+module_init(bert_init);
+
+static __exit void bert_exit(void)
+{
+ acpi_os_unmap_memory(bert_data, bert_size);
+ misc_deregister(&bert_chrdev_device);
+}
+module_exit(bert_exit);
+
+MODULE_DESCRIPTION("ACPI Boot Error Data");
+MODULE_LICENSE("GPL v2");