aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStewart Smith <stewart@linux.vnet.ibm.com>2014-03-03 11:51:41 +1100
committerEli Qiao <taget@linux.vnet.ibm.com>2014-03-04 11:48:03 +0800
commitd2895d01ec17fefd631f636211d250984b69e5da (patch)
tree11c3eac6cb68d4b5bf8a32c6c56a0219f90f1f72
parent70466f3b4c793eca47b5a33712f19c16cdb58ee1 (diff)
downloadpowerkvm-d2895d01ec17fefd631f636211d250984b69e5da.tar.gz
powerpc/powernv: Read OPAL error log and export it through sysfs
Cherry pick 3b3f89ac6614d6bc2e2edb32e49d4906d931c795, implementing the error log reading code we're pushing upstream. This changes the userspace interface for reading and acknowledging error logs, so userspace code will have to change if it relied on the old way. Based on a patch by: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com> This patch adds support to read error logs from OPAL and export them to userspace through a sysfs interface. We export each log entry as a directory in /sys/firmware/opal/elog/ Currently, OPAL will buffer up to 128 error log records, we don't need to have any knowledge of this limit on the Linux side as that is actually largely transparent to us. Each error log entry has the following files: id, type, acknowledge, raw. Currently we just export the raw binary error log in the 'raw' attribute. In a future patch, we may parse more of the error log to make it a bit easier for userspace (e.g. to be able to display a brief summary in petitboot without having to have a full parser). If we have >128 logs from OPAL, we'll only be notified of 128 until userspace starts acknowledging them. This limitation may be lifted in the future and with this patch, that should "just work" from the linux side. A userspace daemon should: - wait for error log entries using normal mechanisms (we announce creation) - read error log entry - save error log entry safely to disk - acknowledge the error log entry - rinse, repeat. On the Linux side, we read the error log when we're notified of it. This possibly isn't ideal as it would be better to only read them on-demand. However, this doesn't really work with current OPAL interface, so we read the error log immediately when notified at the moment. I've tested this pretty extensively and am rather confident that the linux side of things works rather well. There is currently an issue with the service processor side of things for >128 error logs though. Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com> Conflicts: arch/powerpc/include/asm/opal.h arch/powerpc/platforms/powernv/Makefile arch/powerpc/platforms/powernv/opal-elog.c
-rw-r--r--Documentation/ABI/stable/sysfs-firmware-opal-elog60
-rw-r--r--arch/powerpc/platforms/powernv/opal-elog.c427
2 files changed, 275 insertions, 212 deletions
diff --git a/Documentation/ABI/stable/sysfs-firmware-opal-elog b/Documentation/ABI/stable/sysfs-firmware-opal-elog
new file mode 100644
index 0000000000000..e1f3058f5954d
--- /dev/null
+++ b/Documentation/ABI/stable/sysfs-firmware-opal-elog
@@ -0,0 +1,60 @@
+What: /sys/firmware/opal/elog
+Date: Feb 2014
+Contact: Stewart Smith <stewart@linux.vnet.ibm.com>
+Description:
+ This directory exposes error log entries retrieved
+ through the OPAL firmware interface.
+
+ Each error log is identified by a unique ID and will
+ exist until explicitly acknowledged to firmware.
+
+ Each log entry has a directory in /sys/firmware/opal/elog.
+
+ Log entries may be purged by the service processor
+ before retrieved by firmware or retrieved/acknowledged by
+ Linux if there is no room for more log entries.
+
+ In the event that Linux has retrieved the log entries
+ but not explicitly acknowledged them to firmware and
+ the service processor needs more room for log entries,
+ the only remaining copy of a log message may be in
+ Linux.
+
+ Typically, a user space daemon will monitor for new
+ entries, read them out and acknowledge them.
+
+ The service processor may be able to store more log
+ entries than firmware can, so after you acknowledge
+ an event from Linux you may instantly get another one
+ from the queue that was generated some time in the past.
+
+ The raw log format is a binary format. We currently
+ do not parse this at all in kernel, leaving it up to
+ user space to solve the problem. In future, we may
+ do more parsing in kernel and add more files to make
+ it easier for simple user space processes to extract
+ more information.
+
+ For each log entry (directory), there are the following
+ files:
+
+ id: An ASCII representation of the ID of the
+ error log, in hex - e.g. "0x01".
+
+ type: An ASCII representation of the type id and
+ description of the type of error log.
+ Currently just "0x00 PEL" - platform error log.
+ In the future there may be additional types.
+
+ raw: A read-only binary file that can be read
+ to get the raw log entry. These are
+ <16kb, often just hundreds of bytes and
+ "average" 2kb.
+
+ acknowledge: Writing 'ack' to this file will acknowledge
+ the error log to firmware (and in turn
+ the service processor, if applicable).
+ Shortly after acknowledging it, the log
+ entry will be removed from sysfs.
+ Reading this file will list the supported
+ operations (curently just acknowledge). \ No newline at end of file
diff --git a/arch/powerpc/platforms/powernv/opal-elog.c b/arch/powerpc/platforms/powernv/opal-elog.c
index fc891ae59e9bc..61e2ef3375f18 100644
--- a/arch/powerpc/platforms/powernv/opal-elog.c
+++ b/arch/powerpc/platforms/powernv/opal-elog.c
@@ -1,7 +1,7 @@
/*
* Error log support on PowerNV.
*
- * Copyright 2013 IBM Corp.
+ * Copyright 2013,2014 IBM Corp.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -19,209 +19,256 @@
#include <asm/uaccess.h>
#include <asm/opal.h>
-/* Maximum size of a single log on FSP is 16KB */
-#define OPAL_MAX_ERRLOG_SIZE 16384
+struct elog_obj {
+ struct kobject kobj;
+ struct bin_attribute raw_attr;
+ uint64_t id;
+ uint64_t type;
+ size_t size;
+ char *buffer;
+};
+#define to_elog_obj(x) container_of(x, struct elog_obj, kobj)
+
+struct elog_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct elog_obj *elog, struct elog_attribute *attr,
+ char *buf);
+ ssize_t (*store)(struct elog_obj *elog, struct elog_attribute *attr,
+ const char *buf, size_t count);
+};
+#define to_elog_attr(x) container_of(x, struct elog_attribute, attr)
-/* maximu number of records powernv can hold */
-#define MAX_NUM_RECORD 128
+static ssize_t elog_id_show(struct elog_obj *elog_obj,
+ struct elog_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "0x%llx\n", elog_obj->id);
+}
-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];
-};
+static const char *elog_type_to_string(uint64_t type)
+{
+ switch (type) {
+ case 0: return "PEL";
+ default: return "unknown";
+ }
+}
+
+static ssize_t elog_type_show(struct elog_obj *elog_obj,
+ struct elog_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "0x%llx %s\n",
+ elog_obj->type,
+ elog_type_to_string(elog_obj->type));
+}
-/* 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 ssize_t elog_ack_show(struct elog_obj *elog_obj,
+ struct elog_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "ack - acknowledge log message\n");
+}
-static uint64_t total_log_size;
-static bool opal_log_available;
-static LIST_HEAD(elog_list);
-static LIST_HEAD(elog_ack_list);
+static void delay_release_kobj(void *kobj)
+{
+ kobject_put((struct kobject *)kobj);
+}
-/* lock to protect elog_list and elog-ack_list. */
-static DEFINE_SPINLOCK(opal_elog_lock);
+static ssize_t elog_ack_store(struct elog_obj *elog_obj,
+ struct elog_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ opal_send_ack_elog(elog_obj->id);
+ sysfs_schedule_callback(&elog_obj->kobj, delay_release_kobj,
+ &elog_obj->kobj, THIS_MODULE);
+ return count;
+}
-static DECLARE_WAIT_QUEUE_HEAD(opal_log_wait);
+static struct elog_attribute id_attribute =
+ __ATTR(id, 0666, elog_id_show, NULL);
+static struct elog_attribute type_attribute =
+ __ATTR(type, 0666, elog_type_show, NULL);
+static struct elog_attribute ack_attribute =
+ __ATTR(acknowledge, 0660, elog_ack_show, elog_ack_store);
-/*
- * 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)
+static struct kset *elog_kset;
+
+static ssize_t elog_attr_show(struct kobject *kobj,
+ struct attribute *attr,
+ char *buf)
{
- 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);
+ struct elog_attribute *attribute;
+ struct elog_obj *elog;
+
+ attribute = to_elog_attr(attr);
+ elog = to_elog_obj(kobj);
- /* Send acknowledgement to FSP */
- if (found)
- opal_send_ack_elog(ack_id);
- return;
+ if (!attribute->show)
+ return -EIO;
+
+ return attribute->show(elog, attribute, buf);
}
+static ssize_t elog_attr_store(struct kobject *kobj,
+ struct attribute *attr,
+ const char *buf, size_t len)
+{
+ struct elog_attribute *attribute;
+ struct elog_obj *elog;
+
+ attribute = to_elog_attr(attr);
+ elog = to_elog_obj(kobj);
+
+ if (!attribute->store)
+ return -EIO;
-static ssize_t elog_ack_store(struct kobject *kobj,
- struct kobj_attribute *attr,
- const char *buf, size_t count)
+ return attribute->store(elog, attribute, buf, len);
+}
+
+static const struct sysfs_ops elog_sysfs_ops = {
+ .show = elog_attr_show,
+ .store = elog_attr_store,
+};
+
+static void elog_release(struct kobject *kobj)
{
- uint32_t log_ack_id;
- log_ack_id = *(uint32_t *) buf;
+ struct elog_obj *elog;
- /* send acknowledgment to FSP */
- opal_elog_ack(log_ack_id);
- return 0;
+ elog = to_elog_obj(kobj);
+ kfree(elog->buffer);
+ kfree(elog);
}
-/*
- * 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)
+static struct attribute *elog_default_attrs[] = {
+ &id_attribute.attr,
+ &type_attribute.attr,
+ &ack_attribute.attr,
+ NULL,
+};
+
+static struct kobj_type elog_ktype = {
+ .sysfs_ops = &elog_sysfs_ops,
+ .release = &elog_release,
+ .default_attrs = elog_default_attrs,
+};
+
+/* Maximum size of a single log on FSP is 16KB */
+#define OPAL_MAX_ERRLOG_SIZE 16384
+
+static ssize_t raw_attr_read(struct file *filep, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buffer, 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;
+ int opal_rc;
+
+ struct elog_obj *elog = to_elog_obj(kobj);
+
+ /* We may have had an error reading before, so let's retry */
+ if (!elog->buffer) {
+ elog->buffer = kzalloc(elog->size, GFP_KERNEL);
+ if (!elog->buffer)
+ return -EIO;
+
+ opal_rc = opal_read_elog(__pa(elog->buffer),
+ elog->size, elog->id);
+ if (opal_rc != OPAL_SUCCESS) {
+ pr_err("ELOG: log read failed for log-id=%llx\n",
+ elog->id);
+ kfree(elog->buffer);
+ elog->buffer = NULL;
+ return -EIO;
}
- 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;
+ memcpy(buffer, elog->buffer + pos, count);
+
+ return count;
+}
+
+static struct elog_obj *create_elog_obj(uint64_t id, size_t size, uint64_t type)
+{
+ struct elog_obj *elog;
+ int rc;
+
+ elog = kzalloc(sizeof(*elog), GFP_KERNEL);
+ if (!elog)
+ return NULL;
+
+ elog->kobj.kset = elog_kset;
+
+ kobject_init(&elog->kobj, &elog_ktype);
+
+ sysfs_bin_attr_init(&elog->raw_attr);
+
+ elog->raw_attr.attr.name = "raw";
+ elog->raw_attr.attr.mode = 0400;
+ elog->raw_attr.size = size;
+ elog->raw_attr.read = raw_attr_read;
+
+ elog->id = id;
+ elog->size = size;
+ elog->type = type;
+
+ elog->buffer = kzalloc(elog->size, GFP_KERNEL);
+
+ if (elog->buffer) {
+ rc = opal_read_elog(__pa(elog->buffer),
+ elog->size, elog->id);
+ if (rc != OPAL_SUCCESS) {
+ pr_err("ELOG: log read failed for log-id=%llx\n",
+ elog->id);
+ kfree(elog->buffer);
+ elog->buffer = NULL;
}
- size += OPAL_MAX_ERRLOG_SIZE;
}
- spin_unlock_irqrestore(&opal_elog_lock, flags);
-out:
- return error;
+
+ rc = kobject_add(&elog->kobj, NULL, "0x%llx", id);
+ if (rc) {
+ kobject_put(&elog->kobj);
+ return NULL;
+ }
+
+ rc = sysfs_create_bin_file(&elog->kobj, &elog->raw_attr);
+ if (rc) {
+ kobject_put(&elog->kobj);
+ return NULL;
+ }
+
+ kobject_uevent(&elog->kobj, KOBJ_ADD);
+
+ return elog;
}
-/* Interface to read log from OPAL */
-static void opal_elog_read(void)
+static void elog_work_fn(struct work_struct *work)
{
- struct opal_err_log *record;
size_t elog_size;
uint64_t log_id;
uint64_t elog_type;
+ int rc;
+ char name[2+16+1];
- 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;
}
+
+ BUG_ON(elog_size > OPAL_MAX_ERRLOG_SIZE);
+
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));
+ sprintf(name, "0x%llx", log_id);
- 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);
+ /* we may get notified twice, let's handle
+ * that gracefully and not create two conflicting
+ * entries.
+ */
+ if (kset_find_obj(elog_kset, name))
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();
+ create_elog_obj(log_id, elog_size, elog_type);
}
static DECLARE_WORK(elog_work, elog_work_fn);
@@ -235,64 +282,20 @@ static int elog_event(struct notifier_block *nb,
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;
+ elog_kset = kset_create_and_add("elog", NULL, opal_kobj);
+ if (!elog_kset) {
+ pr_warn("%s: failed to create elog kset\n", __func__);
+ return -1;
}
rc = opal_notifier_register(&elog_nb);