162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Error log support on PowerNV.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2013,2014 IBM Corp.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/interrupt.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/sysfs.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/vmalloc.h>
1562306a36Sopenharmony_ci#include <linux/fcntl.h>
1662306a36Sopenharmony_ci#include <linux/kobject.h>
1762306a36Sopenharmony_ci#include <linux/uaccess.h>
1862306a36Sopenharmony_ci#include <asm/opal.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct elog_obj {
2162306a36Sopenharmony_ci	struct kobject kobj;
2262306a36Sopenharmony_ci	struct bin_attribute raw_attr;
2362306a36Sopenharmony_ci	uint64_t id;
2462306a36Sopenharmony_ci	uint64_t type;
2562306a36Sopenharmony_ci	size_t size;
2662306a36Sopenharmony_ci	char *buffer;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci#define to_elog_obj(x) container_of(x, struct elog_obj, kobj)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct elog_attribute {
3162306a36Sopenharmony_ci	struct attribute attr;
3262306a36Sopenharmony_ci	ssize_t (*show)(struct elog_obj *elog, struct elog_attribute *attr,
3362306a36Sopenharmony_ci			char *buf);
3462306a36Sopenharmony_ci	ssize_t (*store)(struct elog_obj *elog, struct elog_attribute *attr,
3562306a36Sopenharmony_ci			 const char *buf, size_t count);
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci#define to_elog_attr(x) container_of(x, struct elog_attribute, attr)
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic ssize_t elog_id_show(struct elog_obj *elog_obj,
4062306a36Sopenharmony_ci			    struct elog_attribute *attr,
4162306a36Sopenharmony_ci			    char *buf)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	return sprintf(buf, "0x%llx\n", elog_obj->id);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic const char *elog_type_to_string(uint64_t type)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	switch (type) {
4962306a36Sopenharmony_ci	case 0: return "PEL";
5062306a36Sopenharmony_ci	default: return "unknown";
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic ssize_t elog_type_show(struct elog_obj *elog_obj,
5562306a36Sopenharmony_ci			      struct elog_attribute *attr,
5662306a36Sopenharmony_ci			      char *buf)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	return sprintf(buf, "0x%llx %s\n",
5962306a36Sopenharmony_ci		       elog_obj->type,
6062306a36Sopenharmony_ci		       elog_type_to_string(elog_obj->type));
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic ssize_t elog_ack_show(struct elog_obj *elog_obj,
6462306a36Sopenharmony_ci			     struct elog_attribute *attr,
6562306a36Sopenharmony_ci			     char *buf)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	return sprintf(buf, "ack - acknowledge log message\n");
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic ssize_t elog_ack_store(struct elog_obj *elog_obj,
7162306a36Sopenharmony_ci			      struct elog_attribute *attr,
7262306a36Sopenharmony_ci			      const char *buf,
7362306a36Sopenharmony_ci			      size_t count)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	/*
7662306a36Sopenharmony_ci	 * Try to self remove this attribute. If we are successful,
7762306a36Sopenharmony_ci	 * delete the kobject itself.
7862306a36Sopenharmony_ci	 */
7962306a36Sopenharmony_ci	if (sysfs_remove_file_self(&elog_obj->kobj, &attr->attr)) {
8062306a36Sopenharmony_ci		opal_send_ack_elog(elog_obj->id);
8162306a36Sopenharmony_ci		kobject_put(&elog_obj->kobj);
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci	return count;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic struct elog_attribute id_attribute =
8762306a36Sopenharmony_ci	__ATTR(id, 0444, elog_id_show, NULL);
8862306a36Sopenharmony_cistatic struct elog_attribute type_attribute =
8962306a36Sopenharmony_ci	__ATTR(type, 0444, elog_type_show, NULL);
9062306a36Sopenharmony_cistatic struct elog_attribute ack_attribute =
9162306a36Sopenharmony_ci	__ATTR(acknowledge, 0660, elog_ack_show, elog_ack_store);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic struct kset *elog_kset;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic ssize_t elog_attr_show(struct kobject *kobj,
9662306a36Sopenharmony_ci			      struct attribute *attr,
9762306a36Sopenharmony_ci			      char *buf)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct elog_attribute *attribute;
10062306a36Sopenharmony_ci	struct elog_obj *elog;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	attribute = to_elog_attr(attr);
10362306a36Sopenharmony_ci	elog = to_elog_obj(kobj);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (!attribute->show)
10662306a36Sopenharmony_ci		return -EIO;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return attribute->show(elog, attribute, buf);
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic ssize_t elog_attr_store(struct kobject *kobj,
11262306a36Sopenharmony_ci			       struct attribute *attr,
11362306a36Sopenharmony_ci			       const char *buf, size_t len)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	struct elog_attribute *attribute;
11662306a36Sopenharmony_ci	struct elog_obj *elog;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	attribute = to_elog_attr(attr);
11962306a36Sopenharmony_ci	elog = to_elog_obj(kobj);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	if (!attribute->store)
12262306a36Sopenharmony_ci		return -EIO;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return attribute->store(elog, attribute, buf, len);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic const struct sysfs_ops elog_sysfs_ops = {
12862306a36Sopenharmony_ci	.show = elog_attr_show,
12962306a36Sopenharmony_ci	.store = elog_attr_store,
13062306a36Sopenharmony_ci};
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic void elog_release(struct kobject *kobj)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	struct elog_obj *elog;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	elog = to_elog_obj(kobj);
13762306a36Sopenharmony_ci	kfree(elog->buffer);
13862306a36Sopenharmony_ci	kfree(elog);
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic struct attribute *elog_default_attrs[] = {
14262306a36Sopenharmony_ci	&id_attribute.attr,
14362306a36Sopenharmony_ci	&type_attribute.attr,
14462306a36Sopenharmony_ci	&ack_attribute.attr,
14562306a36Sopenharmony_ci	NULL,
14662306a36Sopenharmony_ci};
14762306a36Sopenharmony_ciATTRIBUTE_GROUPS(elog_default);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic struct kobj_type elog_ktype = {
15062306a36Sopenharmony_ci	.sysfs_ops = &elog_sysfs_ops,
15162306a36Sopenharmony_ci	.release = &elog_release,
15262306a36Sopenharmony_ci	.default_groups = elog_default_groups,
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/* Maximum size of a single log on FSP is 16KB */
15662306a36Sopenharmony_ci#define OPAL_MAX_ERRLOG_SIZE	16384
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic ssize_t raw_attr_read(struct file *filep, struct kobject *kobj,
15962306a36Sopenharmony_ci			     struct bin_attribute *bin_attr,
16062306a36Sopenharmony_ci			     char *buffer, loff_t pos, size_t count)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	int opal_rc;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	struct elog_obj *elog = to_elog_obj(kobj);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/* We may have had an error reading before, so let's retry */
16762306a36Sopenharmony_ci	if (!elog->buffer) {
16862306a36Sopenharmony_ci		elog->buffer = kzalloc(elog->size, GFP_KERNEL);
16962306a36Sopenharmony_ci		if (!elog->buffer)
17062306a36Sopenharmony_ci			return -EIO;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		opal_rc = opal_read_elog(__pa(elog->buffer),
17362306a36Sopenharmony_ci					 elog->size, elog->id);
17462306a36Sopenharmony_ci		if (opal_rc != OPAL_SUCCESS) {
17562306a36Sopenharmony_ci			pr_err_ratelimited("ELOG: log read failed for log-id=%llx\n",
17662306a36Sopenharmony_ci					   elog->id);
17762306a36Sopenharmony_ci			kfree(elog->buffer);
17862306a36Sopenharmony_ci			elog->buffer = NULL;
17962306a36Sopenharmony_ci			return -EIO;
18062306a36Sopenharmony_ci		}
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	memcpy(buffer, elog->buffer + pos, count);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return count;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic void create_elog_obj(uint64_t id, size_t size, uint64_t type)
18962306a36Sopenharmony_ci{
19062306a36Sopenharmony_ci	struct elog_obj *elog;
19162306a36Sopenharmony_ci	int rc;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	elog = kzalloc(sizeof(*elog), GFP_KERNEL);
19462306a36Sopenharmony_ci	if (!elog)
19562306a36Sopenharmony_ci		return;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	elog->kobj.kset = elog_kset;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	kobject_init(&elog->kobj, &elog_ktype);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	sysfs_bin_attr_init(&elog->raw_attr);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	elog->raw_attr.attr.name = "raw";
20462306a36Sopenharmony_ci	elog->raw_attr.attr.mode = 0400;
20562306a36Sopenharmony_ci	elog->raw_attr.size = size;
20662306a36Sopenharmony_ci	elog->raw_attr.read = raw_attr_read;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	elog->id = id;
20962306a36Sopenharmony_ci	elog->size = size;
21062306a36Sopenharmony_ci	elog->type = type;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	elog->buffer = kzalloc(elog->size, GFP_KERNEL);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	if (elog->buffer) {
21562306a36Sopenharmony_ci		rc = opal_read_elog(__pa(elog->buffer),
21662306a36Sopenharmony_ci					 elog->size, elog->id);
21762306a36Sopenharmony_ci		if (rc != OPAL_SUCCESS) {
21862306a36Sopenharmony_ci			pr_err("ELOG: log read failed for log-id=%llx\n",
21962306a36Sopenharmony_ci			       elog->id);
22062306a36Sopenharmony_ci			kfree(elog->buffer);
22162306a36Sopenharmony_ci			elog->buffer = NULL;
22262306a36Sopenharmony_ci		}
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	rc = kobject_add(&elog->kobj, NULL, "0x%llx", id);
22662306a36Sopenharmony_ci	if (rc) {
22762306a36Sopenharmony_ci		kobject_put(&elog->kobj);
22862306a36Sopenharmony_ci		return;
22962306a36Sopenharmony_ci	}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/*
23262306a36Sopenharmony_ci	 * As soon as the sysfs file for this elog is created/activated there is
23362306a36Sopenharmony_ci	 * a chance the opal_errd daemon (or any userspace) might read and
23462306a36Sopenharmony_ci	 * acknowledge the elog before kobject_uevent() is called. If that
23562306a36Sopenharmony_ci	 * happens then there is a potential race between
23662306a36Sopenharmony_ci	 * elog_ack_store->kobject_put() and kobject_uevent() which leads to a
23762306a36Sopenharmony_ci	 * use-after-free of a kernfs object resulting in a kernel crash.
23862306a36Sopenharmony_ci	 *
23962306a36Sopenharmony_ci	 * To avoid that, we need to take a reference on behalf of the bin file,
24062306a36Sopenharmony_ci	 * so that our reference remains valid while we call kobject_uevent().
24162306a36Sopenharmony_ci	 * We then drop our reference before exiting the function, leaving the
24262306a36Sopenharmony_ci	 * bin file to drop the last reference (if it hasn't already).
24362306a36Sopenharmony_ci	 */
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/* Take a reference for the bin file */
24662306a36Sopenharmony_ci	kobject_get(&elog->kobj);
24762306a36Sopenharmony_ci	rc = sysfs_create_bin_file(&elog->kobj, &elog->raw_attr);
24862306a36Sopenharmony_ci	if (rc == 0) {
24962306a36Sopenharmony_ci		kobject_uevent(&elog->kobj, KOBJ_ADD);
25062306a36Sopenharmony_ci	} else {
25162306a36Sopenharmony_ci		/* Drop the reference taken for the bin file */
25262306a36Sopenharmony_ci		kobject_put(&elog->kobj);
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/* Drop our reference */
25662306a36Sopenharmony_ci	kobject_put(&elog->kobj);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	return;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic irqreturn_t elog_event(int irq, void *data)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	__be64 size;
26462306a36Sopenharmony_ci	__be64 id;
26562306a36Sopenharmony_ci	__be64 type;
26662306a36Sopenharmony_ci	uint64_t elog_size;
26762306a36Sopenharmony_ci	uint64_t log_id;
26862306a36Sopenharmony_ci	uint64_t elog_type;
26962306a36Sopenharmony_ci	int rc;
27062306a36Sopenharmony_ci	char name[2+16+1];
27162306a36Sopenharmony_ci	struct kobject *kobj;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	rc = opal_get_elog_size(&id, &size, &type);
27462306a36Sopenharmony_ci	if (rc != OPAL_SUCCESS) {
27562306a36Sopenharmony_ci		pr_err("ELOG: OPAL log info read failed\n");
27662306a36Sopenharmony_ci		return IRQ_HANDLED;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	elog_size = be64_to_cpu(size);
28062306a36Sopenharmony_ci	log_id = be64_to_cpu(id);
28162306a36Sopenharmony_ci	elog_type = be64_to_cpu(type);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	WARN_ON(elog_size > OPAL_MAX_ERRLOG_SIZE);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	if (elog_size >= OPAL_MAX_ERRLOG_SIZE)
28662306a36Sopenharmony_ci		elog_size  =  OPAL_MAX_ERRLOG_SIZE;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	sprintf(name, "0x%llx", log_id);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	/* we may get notified twice, let's handle
29162306a36Sopenharmony_ci	 * that gracefully and not create two conflicting
29262306a36Sopenharmony_ci	 * entries.
29362306a36Sopenharmony_ci	 */
29462306a36Sopenharmony_ci	kobj = kset_find_obj(elog_kset, name);
29562306a36Sopenharmony_ci	if (kobj) {
29662306a36Sopenharmony_ci		/* Drop reference added by kset_find_obj() */
29762306a36Sopenharmony_ci		kobject_put(kobj);
29862306a36Sopenharmony_ci		return IRQ_HANDLED;
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	create_elog_obj(log_id, elog_size, elog_type);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	return IRQ_HANDLED;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ciint __init opal_elog_init(void)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	int rc = 0, irq;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	/* ELOG not supported by firmware */
31162306a36Sopenharmony_ci	if (!opal_check_token(OPAL_ELOG_READ))
31262306a36Sopenharmony_ci		return -1;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	elog_kset = kset_create_and_add("elog", NULL, opal_kobj);
31562306a36Sopenharmony_ci	if (!elog_kset) {
31662306a36Sopenharmony_ci		pr_warn("%s: failed to create elog kset\n", __func__);
31762306a36Sopenharmony_ci		return -1;
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	irq = opal_event_request(ilog2(OPAL_EVENT_ERROR_LOG_AVAIL));
32162306a36Sopenharmony_ci	if (!irq) {
32262306a36Sopenharmony_ci		pr_err("%s: Can't register OPAL event irq (%d)\n",
32362306a36Sopenharmony_ci		       __func__, irq);
32462306a36Sopenharmony_ci		return irq;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	rc = request_threaded_irq(irq, NULL, elog_event,
32862306a36Sopenharmony_ci			IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "opal-elog", NULL);
32962306a36Sopenharmony_ci	if (rc) {
33062306a36Sopenharmony_ci		pr_err("%s: Can't request OPAL event irq (%d)\n",
33162306a36Sopenharmony_ci		       __func__, rc);
33262306a36Sopenharmony_ci		return rc;
33362306a36Sopenharmony_ci	}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	/* We are now ready to pull error logs from opal. */
33662306a36Sopenharmony_ci	if (opal_check_token(OPAL_ELOG_RESEND))
33762306a36Sopenharmony_ci		opal_resend_pending_logs();
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	return 0;
34062306a36Sopenharmony_ci}
341