18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Error log support on PowerNV. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2013,2014 IBM Corp. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/kernel.h> 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 138c2ecf20Sopenharmony_ci#include <linux/fs.h> 148c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 158c2ecf20Sopenharmony_ci#include <linux/fcntl.h> 168c2ecf20Sopenharmony_ci#include <linux/kobject.h> 178c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 188c2ecf20Sopenharmony_ci#include <asm/opal.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct elog_obj { 218c2ecf20Sopenharmony_ci struct kobject kobj; 228c2ecf20Sopenharmony_ci struct bin_attribute raw_attr; 238c2ecf20Sopenharmony_ci uint64_t id; 248c2ecf20Sopenharmony_ci uint64_t type; 258c2ecf20Sopenharmony_ci size_t size; 268c2ecf20Sopenharmony_ci char *buffer; 278c2ecf20Sopenharmony_ci}; 288c2ecf20Sopenharmony_ci#define to_elog_obj(x) container_of(x, struct elog_obj, kobj) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct elog_attribute { 318c2ecf20Sopenharmony_ci struct attribute attr; 328c2ecf20Sopenharmony_ci ssize_t (*show)(struct elog_obj *elog, struct elog_attribute *attr, 338c2ecf20Sopenharmony_ci char *buf); 348c2ecf20Sopenharmony_ci ssize_t (*store)(struct elog_obj *elog, struct elog_attribute *attr, 358c2ecf20Sopenharmony_ci const char *buf, size_t count); 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci#define to_elog_attr(x) container_of(x, struct elog_attribute, attr) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic ssize_t elog_id_show(struct elog_obj *elog_obj, 408c2ecf20Sopenharmony_ci struct elog_attribute *attr, 418c2ecf20Sopenharmony_ci char *buf) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci return sprintf(buf, "0x%llx\n", elog_obj->id); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic const char *elog_type_to_string(uint64_t type) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci switch (type) { 498c2ecf20Sopenharmony_ci case 0: return "PEL"; 508c2ecf20Sopenharmony_ci default: return "unknown"; 518c2ecf20Sopenharmony_ci } 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic ssize_t elog_type_show(struct elog_obj *elog_obj, 558c2ecf20Sopenharmony_ci struct elog_attribute *attr, 568c2ecf20Sopenharmony_ci char *buf) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci return sprintf(buf, "0x%llx %s\n", 598c2ecf20Sopenharmony_ci elog_obj->type, 608c2ecf20Sopenharmony_ci elog_type_to_string(elog_obj->type)); 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic ssize_t elog_ack_show(struct elog_obj *elog_obj, 648c2ecf20Sopenharmony_ci struct elog_attribute *attr, 658c2ecf20Sopenharmony_ci char *buf) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci return sprintf(buf, "ack - acknowledge log message\n"); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic ssize_t elog_ack_store(struct elog_obj *elog_obj, 718c2ecf20Sopenharmony_ci struct elog_attribute *attr, 728c2ecf20Sopenharmony_ci const char *buf, 738c2ecf20Sopenharmony_ci size_t count) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci /* 768c2ecf20Sopenharmony_ci * Try to self remove this attribute. If we are successful, 778c2ecf20Sopenharmony_ci * delete the kobject itself. 788c2ecf20Sopenharmony_ci */ 798c2ecf20Sopenharmony_ci if (sysfs_remove_file_self(&elog_obj->kobj, &attr->attr)) { 808c2ecf20Sopenharmony_ci opal_send_ack_elog(elog_obj->id); 818c2ecf20Sopenharmony_ci kobject_put(&elog_obj->kobj); 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci return count; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic struct elog_attribute id_attribute = 878c2ecf20Sopenharmony_ci __ATTR(id, 0444, elog_id_show, NULL); 888c2ecf20Sopenharmony_cistatic struct elog_attribute type_attribute = 898c2ecf20Sopenharmony_ci __ATTR(type, 0444, elog_type_show, NULL); 908c2ecf20Sopenharmony_cistatic struct elog_attribute ack_attribute = 918c2ecf20Sopenharmony_ci __ATTR(acknowledge, 0660, elog_ack_show, elog_ack_store); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic struct kset *elog_kset; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic ssize_t elog_attr_show(struct kobject *kobj, 968c2ecf20Sopenharmony_ci struct attribute *attr, 978c2ecf20Sopenharmony_ci char *buf) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci struct elog_attribute *attribute; 1008c2ecf20Sopenharmony_ci struct elog_obj *elog; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci attribute = to_elog_attr(attr); 1038c2ecf20Sopenharmony_ci elog = to_elog_obj(kobj); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (!attribute->show) 1068c2ecf20Sopenharmony_ci return -EIO; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci return attribute->show(elog, attribute, buf); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic ssize_t elog_attr_store(struct kobject *kobj, 1128c2ecf20Sopenharmony_ci struct attribute *attr, 1138c2ecf20Sopenharmony_ci const char *buf, size_t len) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci struct elog_attribute *attribute; 1168c2ecf20Sopenharmony_ci struct elog_obj *elog; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci attribute = to_elog_attr(attr); 1198c2ecf20Sopenharmony_ci elog = to_elog_obj(kobj); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (!attribute->store) 1228c2ecf20Sopenharmony_ci return -EIO; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci return attribute->store(elog, attribute, buf, len); 1258c2ecf20Sopenharmony_ci} 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic const struct sysfs_ops elog_sysfs_ops = { 1288c2ecf20Sopenharmony_ci .show = elog_attr_show, 1298c2ecf20Sopenharmony_ci .store = elog_attr_store, 1308c2ecf20Sopenharmony_ci}; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void elog_release(struct kobject *kobj) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct elog_obj *elog; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci elog = to_elog_obj(kobj); 1378c2ecf20Sopenharmony_ci kfree(elog->buffer); 1388c2ecf20Sopenharmony_ci kfree(elog); 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic struct attribute *elog_default_attrs[] = { 1428c2ecf20Sopenharmony_ci &id_attribute.attr, 1438c2ecf20Sopenharmony_ci &type_attribute.attr, 1448c2ecf20Sopenharmony_ci &ack_attribute.attr, 1458c2ecf20Sopenharmony_ci NULL, 1468c2ecf20Sopenharmony_ci}; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic struct kobj_type elog_ktype = { 1498c2ecf20Sopenharmony_ci .sysfs_ops = &elog_sysfs_ops, 1508c2ecf20Sopenharmony_ci .release = &elog_release, 1518c2ecf20Sopenharmony_ci .default_attrs = elog_default_attrs, 1528c2ecf20Sopenharmony_ci}; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci/* Maximum size of a single log on FSP is 16KB */ 1558c2ecf20Sopenharmony_ci#define OPAL_MAX_ERRLOG_SIZE 16384 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic ssize_t raw_attr_read(struct file *filep, struct kobject *kobj, 1588c2ecf20Sopenharmony_ci struct bin_attribute *bin_attr, 1598c2ecf20Sopenharmony_ci char *buffer, loff_t pos, size_t count) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci int opal_rc; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci struct elog_obj *elog = to_elog_obj(kobj); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* We may have had an error reading before, so let's retry */ 1668c2ecf20Sopenharmony_ci if (!elog->buffer) { 1678c2ecf20Sopenharmony_ci elog->buffer = kzalloc(elog->size, GFP_KERNEL); 1688c2ecf20Sopenharmony_ci if (!elog->buffer) 1698c2ecf20Sopenharmony_ci return -EIO; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci opal_rc = opal_read_elog(__pa(elog->buffer), 1728c2ecf20Sopenharmony_ci elog->size, elog->id); 1738c2ecf20Sopenharmony_ci if (opal_rc != OPAL_SUCCESS) { 1748c2ecf20Sopenharmony_ci pr_err("ELOG: log read failed for log-id=%llx\n", 1758c2ecf20Sopenharmony_ci elog->id); 1768c2ecf20Sopenharmony_ci kfree(elog->buffer); 1778c2ecf20Sopenharmony_ci elog->buffer = NULL; 1788c2ecf20Sopenharmony_ci return -EIO; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci memcpy(buffer, elog->buffer + pos, count); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return count; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void create_elog_obj(uint64_t id, size_t size, uint64_t type) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct elog_obj *elog; 1908c2ecf20Sopenharmony_ci int rc; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci elog = kzalloc(sizeof(*elog), GFP_KERNEL); 1938c2ecf20Sopenharmony_ci if (!elog) 1948c2ecf20Sopenharmony_ci return; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci elog->kobj.kset = elog_kset; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci kobject_init(&elog->kobj, &elog_ktype); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci sysfs_bin_attr_init(&elog->raw_attr); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci elog->raw_attr.attr.name = "raw"; 2038c2ecf20Sopenharmony_ci elog->raw_attr.attr.mode = 0400; 2048c2ecf20Sopenharmony_ci elog->raw_attr.size = size; 2058c2ecf20Sopenharmony_ci elog->raw_attr.read = raw_attr_read; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci elog->id = id; 2088c2ecf20Sopenharmony_ci elog->size = size; 2098c2ecf20Sopenharmony_ci elog->type = type; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci elog->buffer = kzalloc(elog->size, GFP_KERNEL); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (elog->buffer) { 2148c2ecf20Sopenharmony_ci rc = opal_read_elog(__pa(elog->buffer), 2158c2ecf20Sopenharmony_ci elog->size, elog->id); 2168c2ecf20Sopenharmony_ci if (rc != OPAL_SUCCESS) { 2178c2ecf20Sopenharmony_ci pr_err("ELOG: log read failed for log-id=%llx\n", 2188c2ecf20Sopenharmony_ci elog->id); 2198c2ecf20Sopenharmony_ci kfree(elog->buffer); 2208c2ecf20Sopenharmony_ci elog->buffer = NULL; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci rc = kobject_add(&elog->kobj, NULL, "0x%llx", id); 2258c2ecf20Sopenharmony_ci if (rc) { 2268c2ecf20Sopenharmony_ci kobject_put(&elog->kobj); 2278c2ecf20Sopenharmony_ci return; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* 2318c2ecf20Sopenharmony_ci * As soon as the sysfs file for this elog is created/activated there is 2328c2ecf20Sopenharmony_ci * a chance the opal_errd daemon (or any userspace) might read and 2338c2ecf20Sopenharmony_ci * acknowledge the elog before kobject_uevent() is called. If that 2348c2ecf20Sopenharmony_ci * happens then there is a potential race between 2358c2ecf20Sopenharmony_ci * elog_ack_store->kobject_put() and kobject_uevent() which leads to a 2368c2ecf20Sopenharmony_ci * use-after-free of a kernfs object resulting in a kernel crash. 2378c2ecf20Sopenharmony_ci * 2388c2ecf20Sopenharmony_ci * To avoid that, we need to take a reference on behalf of the bin file, 2398c2ecf20Sopenharmony_ci * so that our reference remains valid while we call kobject_uevent(). 2408c2ecf20Sopenharmony_ci * We then drop our reference before exiting the function, leaving the 2418c2ecf20Sopenharmony_ci * bin file to drop the last reference (if it hasn't already). 2428c2ecf20Sopenharmony_ci */ 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* Take a reference for the bin file */ 2458c2ecf20Sopenharmony_ci kobject_get(&elog->kobj); 2468c2ecf20Sopenharmony_ci rc = sysfs_create_bin_file(&elog->kobj, &elog->raw_attr); 2478c2ecf20Sopenharmony_ci if (rc == 0) { 2488c2ecf20Sopenharmony_ci kobject_uevent(&elog->kobj, KOBJ_ADD); 2498c2ecf20Sopenharmony_ci } else { 2508c2ecf20Sopenharmony_ci /* Drop the reference taken for the bin file */ 2518c2ecf20Sopenharmony_ci kobject_put(&elog->kobj); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci /* Drop our reference */ 2558c2ecf20Sopenharmony_ci kobject_put(&elog->kobj); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci return; 2588c2ecf20Sopenharmony_ci} 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic irqreturn_t elog_event(int irq, void *data) 2618c2ecf20Sopenharmony_ci{ 2628c2ecf20Sopenharmony_ci __be64 size; 2638c2ecf20Sopenharmony_ci __be64 id; 2648c2ecf20Sopenharmony_ci __be64 type; 2658c2ecf20Sopenharmony_ci uint64_t elog_size; 2668c2ecf20Sopenharmony_ci uint64_t log_id; 2678c2ecf20Sopenharmony_ci uint64_t elog_type; 2688c2ecf20Sopenharmony_ci int rc; 2698c2ecf20Sopenharmony_ci char name[2+16+1]; 2708c2ecf20Sopenharmony_ci struct kobject *kobj; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci rc = opal_get_elog_size(&id, &size, &type); 2738c2ecf20Sopenharmony_ci if (rc != OPAL_SUCCESS) { 2748c2ecf20Sopenharmony_ci pr_err("ELOG: OPAL log info read failed\n"); 2758c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci elog_size = be64_to_cpu(size); 2798c2ecf20Sopenharmony_ci log_id = be64_to_cpu(id); 2808c2ecf20Sopenharmony_ci elog_type = be64_to_cpu(type); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci WARN_ON(elog_size > OPAL_MAX_ERRLOG_SIZE); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci if (elog_size >= OPAL_MAX_ERRLOG_SIZE) 2858c2ecf20Sopenharmony_ci elog_size = OPAL_MAX_ERRLOG_SIZE; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci sprintf(name, "0x%llx", log_id); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci /* we may get notified twice, let's handle 2908c2ecf20Sopenharmony_ci * that gracefully and not create two conflicting 2918c2ecf20Sopenharmony_ci * entries. 2928c2ecf20Sopenharmony_ci */ 2938c2ecf20Sopenharmony_ci kobj = kset_find_obj(elog_kset, name); 2948c2ecf20Sopenharmony_ci if (kobj) { 2958c2ecf20Sopenharmony_ci /* Drop reference added by kset_find_obj() */ 2968c2ecf20Sopenharmony_ci kobject_put(kobj); 2978c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci create_elog_obj(log_id, elog_size, elog_type); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3038c2ecf20Sopenharmony_ci} 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ciint __init opal_elog_init(void) 3068c2ecf20Sopenharmony_ci{ 3078c2ecf20Sopenharmony_ci int rc = 0, irq; 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci /* ELOG not supported by firmware */ 3108c2ecf20Sopenharmony_ci if (!opal_check_token(OPAL_ELOG_READ)) 3118c2ecf20Sopenharmony_ci return -1; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci elog_kset = kset_create_and_add("elog", NULL, opal_kobj); 3148c2ecf20Sopenharmony_ci if (!elog_kset) { 3158c2ecf20Sopenharmony_ci pr_warn("%s: failed to create elog kset\n", __func__); 3168c2ecf20Sopenharmony_ci return -1; 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci irq = opal_event_request(ilog2(OPAL_EVENT_ERROR_LOG_AVAIL)); 3208c2ecf20Sopenharmony_ci if (!irq) { 3218c2ecf20Sopenharmony_ci pr_err("%s: Can't register OPAL event irq (%d)\n", 3228c2ecf20Sopenharmony_ci __func__, irq); 3238c2ecf20Sopenharmony_ci return irq; 3248c2ecf20Sopenharmony_ci } 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci rc = request_threaded_irq(irq, NULL, elog_event, 3278c2ecf20Sopenharmony_ci IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "opal-elog", NULL); 3288c2ecf20Sopenharmony_ci if (rc) { 3298c2ecf20Sopenharmony_ci pr_err("%s: Can't request OPAL event irq (%d)\n", 3308c2ecf20Sopenharmony_ci __func__, rc); 3318c2ecf20Sopenharmony_ci return rc; 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci /* We are now ready to pull error logs from opal. */ 3358c2ecf20Sopenharmony_ci if (opal_check_token(OPAL_ELOG_RESEND)) 3368c2ecf20Sopenharmony_ci opal_resend_pending_logs(); 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_ci return 0; 3398c2ecf20Sopenharmony_ci} 340