[PATCH] powerpc/powernv: Read opal error log and export it through sysfs interface.
Mahesh J Salgaonkar
mahesh at linux.vnet.ibm.com
Mon Dec 16 20:58:01 EST 2013
From: Mahesh Salgaonkar <mahesh at linux.vnet.ibm.com>
This patch adds support to read error logs from OPAL and export them
to userspace through sysfs interface /sys/firmware/opa/opal-elog.
This patch buffers 128 error log records until it is consumed by userspace
tool. This patch provides an sysfs interface '/sys/firmware/opa/opal-elog-ack'
to user to receive an acknowledgement of successful log consumption.
This is what user space tool would do:
- Read error log from /sys/firmware/opa/opal-elog.
- Save it to the disk.
- Send an acknowledgement on successful consumption by writing error log
id to /sys/firmware/opa/opal-elog-ack.
Signed-off-by: Mamatha Inamdar <mamatha4 at linux.vnet.ibm.com>
Signed-off-by: Mahesh Salgaonkar <mahesh at linux.vnet.ibm.com>
Signed-off-by: Vasant Hegde <hegdevasant at linux.vnet.ibm.com>
---
arch/powerpc/include/asm/opal.h | 11 +
arch/powerpc/platforms/powernv/Makefile | 2
arch/powerpc/platforms/powernv/opal-elog.c | 309 ++++++++++++++++++++++++
arch/powerpc/platforms/powernv/opal-wrappers.S | 5
arch/powerpc/platforms/powernv/opal.c | 2
5 files changed, 328 insertions(+), 1 deletion(-)
create mode 100644 arch/powerpc/platforms/powernv/opal-elog.c
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index 5462fa7..723a7db 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -129,6 +129,11 @@ extern int opal_enter_rtas(struct rtas_args *args,
#define OPAL_LPC_READ 67
#define OPAL_LPC_WRITE 68
#define OPAL_RETURN_CPU 69
+#define OPAL_ELOG_READ 71
+#define OPAL_ELOG_WRITE 72
+#define OPAL_ELOG_ACK 73
+#define OPAL_ELOG_RESEND 74
+#define OPAL_ELOG_SIZE 75
#define OPAL_FLASH_VALIDATE 76
#define OPAL_FLASH_MANAGE 77
#define OPAL_FLASH_UPDATE 78
@@ -727,6 +732,11 @@ int64_t opal_lpc_write(uint32_t chip_id, enum OpalLPCAddressType addr_type,
uint32_t addr, uint32_t data, uint32_t sz);
int64_t opal_lpc_read(uint32_t chip_id, enum OpalLPCAddressType addr_type,
uint32_t addr, uint32_t *data, uint32_t sz);
+int64_t opal_read_elog(uint64_t buffer, size_t size, uint64_t log_id);
+int64_t opal_get_elog_size(uint64_t *log_id, size_t *size, uint64_t *elog_type);
+int64_t opal_write_elog(uint64_t buffer, uint64_t size, uint64_t offset);
+int64_t opal_send_ack_elog(uint64_t log_id);
+void opal_resend_pending_logs(void);
int64_t opal_validate_flash(uint64_t buffer, uint32_t *size, uint32_t *result);
int64_t opal_manage_flash(uint8_t op);
int64_t opal_update_flash(uint64_t blk_list);
@@ -761,6 +771,7 @@ extern void opal_get_rtc_time(struct rtc_time *tm);
extern unsigned long opal_get_boot_time(void);
extern void opal_nvram_init(void);
extern void opal_flash_init(void);
+extern int opal_elog_init(void);
extern int opal_machine_check(struct pt_regs *regs);
extern bool opal_mce_check_early_recovery(struct pt_regs *regs);
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
index 873fa13..0f692eb 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -1,6 +1,6 @@
obj-y += setup.o opal-takeover.o opal-wrappers.o opal.o
obj-y += opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
-obj-y += rng.o
+obj-y += rng.o opal-elog.o
obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_PCI) += pci.o pci-p5ioc2.o pci-ioda.o
diff --git a/arch/powerpc/platforms/powernv/opal-elog.c b/arch/powerpc/platforms/powernv/opal-elog.c
new file mode 100644
index 0000000..fc891ae
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-elog.c
@@ -0,0 +1,309 @@
+/*
+ * Error log support on PowerNV.
+ *
+ * Copyright 2013 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/fcntl.h>
+#include <asm/uaccess.h>
+#include <asm/opal.h>
+
+/* Maximum size of a single log on FSP is 16KB */
+#define OPAL_MAX_ERRLOG_SIZE 16384
+
+/* maximu number of records powernv can hold */
+#define MAX_NUM_RECORD 128
+
+struct opal_err_log {
+ struct list_head link;
+ uint64_t opal_log_id;
+ size_t opal_log_size;
+ uint8_t data[OPAL_MAX_ERRLOG_SIZE];
+};
+
+/* Pre-allocated temp buffer to pull error log from opal. */
+static uint8_t err_log_data[OPAL_MAX_ERRLOG_SIZE];
+/* Protect err_log_data buf */
+static DEFINE_MUTEX(err_log_data_mutex);
+
+static uint64_t total_log_size;
+static bool opal_log_available;
+static LIST_HEAD(elog_list);
+static LIST_HEAD(elog_ack_list);
+
+/* lock to protect elog_list and elog-ack_list. */
+static DEFINE_SPINLOCK(opal_elog_lock);
+
+static DECLARE_WAIT_QUEUE_HEAD(opal_log_wait);
+
+/*
+ * Interface for user to acknowledge the error log.
+ *
+ * Once user acknowledge the log, we delete that record entry from the
+ * list and move it ack list.
+ */
+void opal_elog_ack(uint64_t ack_id)
+{
+ unsigned long flags;
+ struct opal_err_log *record, *next;
+ bool found = false;
+
+ printk(KERN_INFO "OPAL Log ACK=%llx", ack_id);
+
+ /* once user acknowledge a log delete record from list */
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ list_for_each_entry_safe(record, next, &elog_list, link) {
+ if (ack_id == record->opal_log_id) {
+ list_del(&record->link);
+ list_add(&record->link, &elog_ack_list);
+ total_log_size -= OPAL_MAX_ERRLOG_SIZE;
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+
+ /* Send acknowledgement to FSP */
+ if (found)
+ opal_send_ack_elog(ack_id);
+ return;
+}
+
+
+static ssize_t elog_ack_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ uint32_t log_ack_id;
+ log_ack_id = *(uint32_t *) buf;
+
+ /* send acknowledgment to FSP */
+ opal_elog_ack(log_ack_id);
+ return 0;
+}
+
+/*
+ * Show error log records to user.
+ */
+static ssize_t opal_elog_show(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ unsigned long flags;
+ struct opal_err_log *record, *next;
+ size_t size = 0;
+ size_t data_to_copy = 0;
+ int error = 0;
+
+ /* Display one log at a time. */
+ if (count > OPAL_MAX_ERRLOG_SIZE)
+ count = OPAL_MAX_ERRLOG_SIZE;
+
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ /* Align the pos to point within total errlog size. */
+ if (total_log_size && pos > total_log_size)
+ pos = pos % total_log_size;
+
+ /*
+ * if pos goes beyond total_log_size then we know we don't have any
+ * new record to show.
+ */
+ if (total_log_size == 0 || pos >= total_log_size) {
+ opal_log_available = 0;
+ if (filp->f_flags & O_NONBLOCK) {
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+ error = -EAGAIN;
+ goto out;
+ }
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+ pos = 0;
+
+ /* Wait until we get log from sapphire */
+ error = wait_event_interruptible(opal_log_wait,
+ opal_log_available);
+ if (error)
+ goto out;
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ }
+
+ /*
+ * Show log record one by one through /sys/firmware/opal/opal_elog
+ */
+ list_for_each_entry_safe(record, next, &elog_list, link) {
+ if ((pos >= size) && (pos < (size + OPAL_MAX_ERRLOG_SIZE))) {
+ data_to_copy = OPAL_MAX_ERRLOG_SIZE - (pos - size);
+ if (count > data_to_copy)
+ count = data_to_copy;
+ memcpy(buf, record->data + (pos - size), count);
+ error = count;
+ break;
+ }
+ size += OPAL_MAX_ERRLOG_SIZE;
+ }
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+out:
+ return error;
+}
+
+/* Interface to read log from OPAL */
+static void opal_elog_read(void)
+{
+ struct opal_err_log *record;
+ size_t elog_size;
+ uint64_t log_id;
+ uint64_t elog_type;
+
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ if (list_empty(&elog_ack_list)) {
+ /*
+ * We have no more room to read logs. Ignore it for now,
+ * will read it later when we have enough space.
+ */
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+ return;
+ }
+
+ /* Pull out the free node. */
+ record = list_entry(elog_ack_list.next, struct opal_err_log, link);
+ list_del(&record->link);
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+
+ /* read log size and log ID from OPAL */
+ rc = opal_get_elog_size(&log_id, &elog_size, &elog_type);
+ if (rc != OPAL_SUCCESS) {
+ pr_err("ELOG: Opal log read failed\n");
+ return;
+ }
+ if (elog_size >= OPAL_MAX_ERRLOG_SIZE)
+ elog_size = OPAL_MAX_ERRLOG_SIZE;
+
+ record->opal_log_id = log_id;
+ record->opal_log_size = elog_size;
+ memset(record->data, 0, sizeof(record->data));
+
+ mutex_lock(&err_log_data_mutex);
+ rc = opal_read_elog(__pa(err_log_data), elog_size, log_id);
+ if (rc != OPAL_SUCCESS) {
+ mutex_unlock(&err_log_data_mutex);
+ pr_err("ELOG: log read failed for log-id=%llx\n", log_id);
+ /* put back the free node. */
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ list_add(&record->link, &elog_ack_list);
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+ return;
+ }
+ memcpy(record->data, err_log_data, elog_size);
+ mutex_unlock(&err_log_data_mutex);
+
+ spin_lock_irqsave(&opal_elog_lock, flags);
+ list_add_tail(&record->link, &elog_list);
+ total_log_size += OPAL_MAX_ERRLOG_SIZE;
+ spin_unlock_irqrestore(&opal_elog_lock, flags);
+
+ opal_log_available = 1;
+ wake_up_interruptible(&opal_log_wait);
+ return;
+}
+
+static void elog_work_fn(struct work_struct *work)
+{
+ opal_elog_read();
+}
+
+static DECLARE_WORK(elog_work, elog_work_fn);
+
+static int elog_event(struct notifier_block *nb,
+ unsigned long events, void *change)
+{
+ /* check for error log event */
+ if (events & OPAL_EVENT_ERROR_LOG_AVAIL)
+ schedule_work(&elog_work);
+ return 0;
+}
+
+/* Initialize sysfs file */
+static struct kobj_attribute opal_elog_ack_attr = __ATTR(opal_elog_ack,
+ 0200, NULL, elog_ack_store);
+
+static struct notifier_block elog_nb = {
+ .notifier_call = elog_event,
+ .next = NULL,
+ .priority = 0
+};
+
+static struct bin_attribute opal_elog_attr = {
+ .attr = {.name = "opal_elog", .mode = 0400},
+ .read = opal_elog_show,
+};
+
+/*
+ * Pre-allocate a buffer to hold handful of error logs until user space
+ * consumes it.
+ */
+static int init_err_log_buffer(void)
+{
+ int i = 0;
+ struct opal_err_log *buf_ptr;
+
+ buf_ptr = vmalloc(sizeof(struct opal_err_log) * MAX_NUM_RECORD);
+ if (!buf_ptr) {
+ printk(KERN_ERR "ELOG: failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+ memset(buf_ptr, 0, sizeof(struct opal_err_log) * MAX_NUM_RECORD);
+
+ /* Initialize ack list will all free nodes. */
+ for (i = 0; i < MAX_NUM_RECORD; i++, buf_ptr++)
+ list_add(&buf_ptr->link, &elog_ack_list);
+ return 0;
+}
+
+/* Initialize error logging */
+int __init opal_elog_init(void)
+{
+ int rc = 0;
+
+ rc = init_err_log_buffer();
+ if (rc)
+ return rc;
+
+ rc = sysfs_create_bin_file(opal_kobj, &opal_elog_attr);
+ if (rc) {
+ printk(KERN_ERR "ELOG: unable to create sysfs file"
+ "opal_elog (%d)\n", rc);
+ return rc;
+ }
+
+ rc = sysfs_create_file(opal_kobj, &opal_elog_ack_attr.attr);
+ if (rc) {
+ printk(KERN_ERR "ELOG: unable to create sysfs file"
+ " opal_elog_ack (%d)\n", rc);
+ return rc;
+ }
+
+ rc = opal_notifier_register(&elog_nb);
+ if (rc) {
+ pr_err("%s: Can't register OPAL event notifier (%d)\n",
+ __func__, rc);
+ return rc;
+ }
+
+ /* We are now ready to pull error logs from opal. */
+ opal_resend_pending_logs();
+
+ return 0;
+}
diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
index e780650..a040b02 100644
--- a/arch/powerpc/platforms/powernv/opal-wrappers.S
+++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
@@ -123,6 +123,11 @@ OPAL_CALL(opal_xscom_write, OPAL_XSCOM_WRITE);
OPAL_CALL(opal_lpc_read, OPAL_LPC_READ);
OPAL_CALL(opal_lpc_write, OPAL_LPC_WRITE);
OPAL_CALL(opal_return_cpu, OPAL_RETURN_CPU);
+OPAL_CALL(opal_read_elog, OPAL_ELOG_READ);
+OPAL_CALL(opal_send_ack_elog, OPAL_ELOG_ACK);
+OPAL_CALL(opal_get_elog_size, OPAL_ELOG_SIZE);
+OPAL_CALL(opal_resend_pending_logs, OPAL_ELOG_RESEND);
+OPAL_CALL(opal_write_elog, OPAL_ELOG_WRITE);
OPAL_CALL(opal_validate_flash, OPAL_FLASH_VALIDATE);
OPAL_CALL(opal_manage_flash, OPAL_FLASH_MANAGE);
OPAL_CALL(opal_update_flash, OPAL_FLASH_UPDATE);
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index 31053be..20e1834 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -475,6 +475,8 @@ static int __init opal_init(void)
/* Create "opal" kobject under /sys/firmware */
rc = opal_sysfs_init();
if (rc == 0) {
+ /* Setup error log interface */
+ rc = opal_elog_init();
/* Setup code update interface */
opal_flash_init();
}
More information about the Linuxppc-dev
mailing list