[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