|
@@ -34,6 +34,7 @@
|
|
|
#include <linux/cper.h>
|
|
|
#include <linux/nmi.h>
|
|
|
#include <linux/hardirq.h>
|
|
|
+#include <linux/pstore.h>
|
|
|
#include <acpi/apei.h>
|
|
|
|
|
|
#include "apei-internal.h"
|
|
@@ -781,6 +782,128 @@ static int erst_check_table(struct acpi_table_erst *erst_tab)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static size_t erst_reader(u64 *id, enum pstore_type_id *type,
|
|
|
+ struct timespec *time);
|
|
|
+static u64 erst_writer(enum pstore_type_id type, size_t size);
|
|
|
+
|
|
|
+static struct pstore_info erst_info = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .name = "erst",
|
|
|
+ .read = erst_reader,
|
|
|
+ .write = erst_writer,
|
|
|
+ .erase = erst_clear
|
|
|
+};
|
|
|
+
|
|
|
+#define CPER_CREATOR_PSTORE \
|
|
|
+ UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \
|
|
|
+ 0x64, 0x90, 0xb8, 0x9d)
|
|
|
+#define CPER_SECTION_TYPE_DMESG \
|
|
|
+ UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \
|
|
|
+ 0x94, 0x19, 0xeb, 0x12)
|
|
|
+#define CPER_SECTION_TYPE_MCE \
|
|
|
+ UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \
|
|
|
+ 0x04, 0x4a, 0x38, 0xfc)
|
|
|
+
|
|
|
+struct cper_pstore_record {
|
|
|
+ struct cper_record_header hdr;
|
|
|
+ struct cper_section_descriptor sec_hdr;
|
|
|
+ char data[];
|
|
|
+} __packed;
|
|
|
+
|
|
|
+static size_t erst_reader(u64 *id, enum pstore_type_id *type,
|
|
|
+ struct timespec *time)
|
|
|
+{
|
|
|
+ int rc;
|
|
|
+ ssize_t len;
|
|
|
+ unsigned long flags;
|
|
|
+ u64 record_id;
|
|
|
+ struct cper_pstore_record *rcd = (struct cper_pstore_record *)
|
|
|
+ (erst_info.buf - sizeof(*rcd));
|
|
|
+
|
|
|
+ if (erst_disable)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ raw_spin_lock_irqsave(&erst_lock, flags);
|
|
|
+skip:
|
|
|
+ rc = __erst_get_next_record_id(&record_id);
|
|
|
+ if (rc) {
|
|
|
+ raw_spin_unlock_irqrestore(&erst_lock, flags);
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+ /* no more record */
|
|
|
+ if (record_id == APEI_ERST_INVALID_RECORD_ID) {
|
|
|
+ raw_spin_unlock_irqrestore(&erst_lock, flags);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ len = __erst_read(record_id, &rcd->hdr, sizeof(*rcd) +
|
|
|
+ erst_erange.size);
|
|
|
+ if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0)
|
|
|
+ goto skip;
|
|
|
+ raw_spin_unlock_irqrestore(&erst_lock, flags);
|
|
|
+
|
|
|
+ *id = record_id;
|
|
|
+ if (uuid_le_cmp(rcd->sec_hdr.section_type,
|
|
|
+ CPER_SECTION_TYPE_DMESG) == 0)
|
|
|
+ *type = PSTORE_TYPE_DMESG;
|
|
|
+ else if (uuid_le_cmp(rcd->sec_hdr.section_type,
|
|
|
+ CPER_SECTION_TYPE_MCE) == 0)
|
|
|
+ *type = PSTORE_TYPE_MCE;
|
|
|
+ else
|
|
|
+ *type = PSTORE_TYPE_UNKNOWN;
|
|
|
+
|
|
|
+ if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP)
|
|
|
+ time->tv_sec = rcd->hdr.timestamp;
|
|
|
+ else
|
|
|
+ time->tv_sec = 0;
|
|
|
+ time->tv_nsec = 0;
|
|
|
+
|
|
|
+ return len - sizeof(*rcd);
|
|
|
+}
|
|
|
+
|
|
|
+static u64 erst_writer(enum pstore_type_id type, size_t size)
|
|
|
+{
|
|
|
+ struct cper_pstore_record *rcd = (struct cper_pstore_record *)
|
|
|
+ (erst_info.buf - sizeof(*rcd));
|
|
|
+
|
|
|
+ memset(rcd, 0, sizeof(*rcd));
|
|
|
+ memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE);
|
|
|
+ rcd->hdr.revision = CPER_RECORD_REV;
|
|
|
+ rcd->hdr.signature_end = CPER_SIG_END;
|
|
|
+ rcd->hdr.section_count = 1;
|
|
|
+ rcd->hdr.error_severity = CPER_SEV_FATAL;
|
|
|
+ /* timestamp valid. platform_id, partition_id are invalid */
|
|
|
+ rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP;
|
|
|
+ rcd->hdr.timestamp = get_seconds();
|
|
|
+ rcd->hdr.record_length = sizeof(*rcd) + size;
|
|
|
+ rcd->hdr.creator_id = CPER_CREATOR_PSTORE;
|
|
|
+ rcd->hdr.notification_type = CPER_NOTIFY_MCE;
|
|
|
+ rcd->hdr.record_id = cper_next_record_id();
|
|
|
+ rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR;
|
|
|
+
|
|
|
+ rcd->sec_hdr.section_offset = sizeof(*rcd);
|
|
|
+ rcd->sec_hdr.section_length = size;
|
|
|
+ rcd->sec_hdr.revision = CPER_SEC_REV;
|
|
|
+ /* fru_id and fru_text is invalid */
|
|
|
+ rcd->sec_hdr.validation_bits = 0;
|
|
|
+ rcd->sec_hdr.flags = CPER_SEC_PRIMARY;
|
|
|
+ switch (type) {
|
|
|
+ case PSTORE_TYPE_DMESG:
|
|
|
+ rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG;
|
|
|
+ break;
|
|
|
+ case PSTORE_TYPE_MCE:
|
|
|
+ rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ rcd->sec_hdr.section_severity = CPER_SEV_FATAL;
|
|
|
+
|
|
|
+ erst_write(&rcd->hdr);
|
|
|
+
|
|
|
+ return rcd->hdr.record_id;
|
|
|
+}
|
|
|
+
|
|
|
static int __init erst_init(void)
|
|
|
{
|
|
|
int rc = 0;
|
|
@@ -788,6 +911,7 @@ static int __init erst_init(void)
|
|
|
struct apei_exec_context ctx;
|
|
|
struct apei_resources erst_resources;
|
|
|
struct resource *r;
|
|
|
+ char *buf;
|
|
|
|
|
|
if (acpi_disabled)
|
|
|
goto err;
|
|
@@ -854,6 +978,18 @@ static int __init erst_init(void)
|
|
|
if (!erst_erange.vaddr)
|
|
|
goto err_release_erange;
|
|
|
|
|
|
+ buf = kmalloc(erst_erange.size, GFP_KERNEL);
|
|
|
+ mutex_init(&erst_info.buf_mutex);
|
|
|
+ if (buf) {
|
|
|
+ erst_info.buf = buf + sizeof(struct cper_pstore_record);
|
|
|
+ erst_info.bufsize = erst_erange.size -
|
|
|
+ sizeof(struct cper_pstore_record);
|
|
|
+ if (pstore_register(&erst_info)) {
|
|
|
+ pr_info(ERST_PFX "Could not register with persistent store\n");
|
|
|
+ kfree(buf);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
pr_info(ERST_PFX
|
|
|
"Error Record Serialization Table (ERST) support is initialized.\n");
|
|
|
|