18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PowerNV OPAL IPMI driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2014 IBM Corp. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "ipmi-powernv: " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/ipmi_smi.h> 118c2ecf20Sopenharmony_ci#include <linux/list.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 158c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <asm/opal.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct ipmi_smi_powernv { 218c2ecf20Sopenharmony_ci u64 interface_id; 228c2ecf20Sopenharmony_ci struct ipmi_smi *intf; 238c2ecf20Sopenharmony_ci unsigned int irq; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci /** 268c2ecf20Sopenharmony_ci * We assume that there can only be one outstanding request, so 278c2ecf20Sopenharmony_ci * keep the pending message in cur_msg. We protect this from concurrent 288c2ecf20Sopenharmony_ci * updates through send & recv calls, (and consequently opal_msg, which 298c2ecf20Sopenharmony_ci * is in-use when cur_msg is set) with msg_lock 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_ci spinlock_t msg_lock; 328c2ecf20Sopenharmony_ci struct ipmi_smi_msg *cur_msg; 338c2ecf20Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic int ipmi_powernv_start_processing(void *send_info, struct ipmi_smi *intf) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci smi->intf = intf; 418c2ecf20Sopenharmony_ci return 0; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic void send_error_reply(struct ipmi_smi_powernv *smi, 458c2ecf20Sopenharmony_ci struct ipmi_smi_msg *msg, u8 completion_code) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci msg->rsp[0] = msg->data[0] | 0x4; 488c2ecf20Sopenharmony_ci msg->rsp[1] = msg->data[1]; 498c2ecf20Sopenharmony_ci msg->rsp[2] = completion_code; 508c2ecf20Sopenharmony_ci msg->rsp_size = 3; 518c2ecf20Sopenharmony_ci ipmi_smi_msg_received(smi->intf, msg); 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic void ipmi_powernv_send(void *send_info, struct ipmi_smi_msg *msg) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 578c2ecf20Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 588c2ecf20Sopenharmony_ci unsigned long flags; 598c2ecf20Sopenharmony_ci int comp, rc; 608c2ecf20Sopenharmony_ci size_t size; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* ensure data_len will fit in the opal_ipmi_msg buffer... */ 638c2ecf20Sopenharmony_ci if (msg->data_size > IPMI_MAX_MSG_LENGTH) { 648c2ecf20Sopenharmony_ci comp = IPMI_REQ_LEN_EXCEEDED_ERR; 658c2ecf20Sopenharmony_ci goto err; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci /* ... and that we at least have netfn and cmd bytes */ 698c2ecf20Sopenharmony_ci if (msg->data_size < 2) { 708c2ecf20Sopenharmony_ci comp = IPMI_REQ_LEN_INVALID_ERR; 718c2ecf20Sopenharmony_ci goto err; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci spin_lock_irqsave(&smi->msg_lock, flags); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (smi->cur_msg) { 778c2ecf20Sopenharmony_ci comp = IPMI_NODE_BUSY_ERR; 788c2ecf20Sopenharmony_ci goto err_unlock; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* format our data for the OPAL API */ 828c2ecf20Sopenharmony_ci opal_msg = smi->opal_msg; 838c2ecf20Sopenharmony_ci opal_msg->version = OPAL_IPMI_MSG_FORMAT_VERSION_1; 848c2ecf20Sopenharmony_ci opal_msg->netfn = msg->data[0]; 858c2ecf20Sopenharmony_ci opal_msg->cmd = msg->data[1]; 868c2ecf20Sopenharmony_ci if (msg->data_size > 2) 878c2ecf20Sopenharmony_ci memcpy(opal_msg->data, msg->data + 2, msg->data_size - 2); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* data_size already includes the netfn and cmd bytes */ 908c2ecf20Sopenharmony_ci size = sizeof(*opal_msg) + msg->data_size - 2; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci pr_devel("%s: opal_ipmi_send(0x%llx, %p, %ld)\n", __func__, 938c2ecf20Sopenharmony_ci smi->interface_id, opal_msg, size); 948c2ecf20Sopenharmony_ci rc = opal_ipmi_send(smi->interface_id, opal_msg, size); 958c2ecf20Sopenharmony_ci pr_devel("%s: -> %d\n", __func__, rc); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (!rc) { 988c2ecf20Sopenharmony_ci smi->cur_msg = msg; 998c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1008c2ecf20Sopenharmony_ci return; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci comp = IPMI_ERR_UNSPECIFIED; 1048c2ecf20Sopenharmony_cierr_unlock: 1058c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1068c2ecf20Sopenharmony_cierr: 1078c2ecf20Sopenharmony_ci send_error_reply(smi, msg, comp); 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int ipmi_powernv_recv(struct ipmi_smi_powernv *smi) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 1138c2ecf20Sopenharmony_ci struct ipmi_smi_msg *msg; 1148c2ecf20Sopenharmony_ci unsigned long flags; 1158c2ecf20Sopenharmony_ci uint64_t size; 1168c2ecf20Sopenharmony_ci int rc; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci pr_devel("%s: opal_ipmi_recv(%llx, msg, sz)\n", __func__, 1198c2ecf20Sopenharmony_ci smi->interface_id); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci spin_lock_irqsave(&smi->msg_lock, flags); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci if (!smi->cur_msg) { 1248c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1258c2ecf20Sopenharmony_ci pr_warn("no current message?\n"); 1268c2ecf20Sopenharmony_ci return 0; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci msg = smi->cur_msg; 1308c2ecf20Sopenharmony_ci opal_msg = smi->opal_msg; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci size = cpu_to_be64(sizeof(*opal_msg) + IPMI_MAX_MSG_LENGTH); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci rc = opal_ipmi_recv(smi->interface_id, 1358c2ecf20Sopenharmony_ci opal_msg, 1368c2ecf20Sopenharmony_ci &size); 1378c2ecf20Sopenharmony_ci size = be64_to_cpu(size); 1388c2ecf20Sopenharmony_ci pr_devel("%s: -> %d (size %lld)\n", __func__, 1398c2ecf20Sopenharmony_ci rc, rc == 0 ? size : 0); 1408c2ecf20Sopenharmony_ci if (rc) { 1418c2ecf20Sopenharmony_ci /* If came via the poll, and response was not yet ready */ 1428c2ecf20Sopenharmony_ci if (rc == OPAL_EMPTY) { 1438c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1448c2ecf20Sopenharmony_ci return 0; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci smi->cur_msg = NULL; 1488c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1498c2ecf20Sopenharmony_ci send_error_reply(smi, msg, IPMI_ERR_UNSPECIFIED); 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (size < sizeof(*opal_msg)) { 1548c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1558c2ecf20Sopenharmony_ci pr_warn("unexpected IPMI message size %lld\n", size); 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (opal_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { 1608c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1618c2ecf20Sopenharmony_ci pr_warn("unexpected IPMI message format (version %d)\n", 1628c2ecf20Sopenharmony_ci opal_msg->version); 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci msg->rsp[0] = opal_msg->netfn; 1678c2ecf20Sopenharmony_ci msg->rsp[1] = opal_msg->cmd; 1688c2ecf20Sopenharmony_ci if (size > sizeof(*opal_msg)) 1698c2ecf20Sopenharmony_ci memcpy(&msg->rsp[2], opal_msg->data, size - sizeof(*opal_msg)); 1708c2ecf20Sopenharmony_ci msg->rsp_size = 2 + size - sizeof(*opal_msg); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci smi->cur_msg = NULL; 1738c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 1748c2ecf20Sopenharmony_ci ipmi_smi_msg_received(smi->intf, msg); 1758c2ecf20Sopenharmony_ci return 0; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void ipmi_powernv_request_events(void *send_info) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic void ipmi_powernv_set_run_to_completion(void *send_info, 1838c2ecf20Sopenharmony_ci bool run_to_completion) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void ipmi_powernv_poll(void *send_info) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ipmi_powernv_recv(smi); 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_cistatic const struct ipmi_smi_handlers ipmi_powernv_smi_handlers = { 1958c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1968c2ecf20Sopenharmony_ci .start_processing = ipmi_powernv_start_processing, 1978c2ecf20Sopenharmony_ci .sender = ipmi_powernv_send, 1988c2ecf20Sopenharmony_ci .request_events = ipmi_powernv_request_events, 1998c2ecf20Sopenharmony_ci .set_run_to_completion = ipmi_powernv_set_run_to_completion, 2008c2ecf20Sopenharmony_ci .poll = ipmi_powernv_poll, 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic irqreturn_t ipmi_opal_event(int irq, void *data) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *smi = data; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci ipmi_powernv_recv(smi); 2088c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic int ipmi_powernv_probe(struct platform_device *pdev) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *ipmi; 2148c2ecf20Sopenharmony_ci struct device *dev; 2158c2ecf20Sopenharmony_ci u32 prop; 2168c2ecf20Sopenharmony_ci int rc; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci if (!pdev || !pdev->dev.of_node) 2198c2ecf20Sopenharmony_ci return -ENODEV; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci dev = &pdev->dev; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci ipmi = devm_kzalloc(dev, sizeof(*ipmi), GFP_KERNEL); 2248c2ecf20Sopenharmony_ci if (!ipmi) 2258c2ecf20Sopenharmony_ci return -ENOMEM; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci spin_lock_init(&ipmi->msg_lock); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci rc = of_property_read_u32(dev->of_node, "ibm,ipmi-interface-id", 2308c2ecf20Sopenharmony_ci &prop); 2318c2ecf20Sopenharmony_ci if (rc) { 2328c2ecf20Sopenharmony_ci dev_warn(dev, "No interface ID property\n"); 2338c2ecf20Sopenharmony_ci goto err_free; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci ipmi->interface_id = prop; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci rc = of_property_read_u32(dev->of_node, "interrupts", &prop); 2388c2ecf20Sopenharmony_ci if (rc) { 2398c2ecf20Sopenharmony_ci dev_warn(dev, "No interrupts property\n"); 2408c2ecf20Sopenharmony_ci goto err_free; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci ipmi->irq = irq_of_parse_and_map(dev->of_node, 0); 2448c2ecf20Sopenharmony_ci if (!ipmi->irq) { 2458c2ecf20Sopenharmony_ci dev_info(dev, "Unable to map irq from device tree\n"); 2468c2ecf20Sopenharmony_ci ipmi->irq = opal_event_request(prop); 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci rc = request_irq(ipmi->irq, ipmi_opal_event, IRQ_TYPE_LEVEL_HIGH, 2508c2ecf20Sopenharmony_ci "opal-ipmi", ipmi); 2518c2ecf20Sopenharmony_ci if (rc) { 2528c2ecf20Sopenharmony_ci dev_warn(dev, "Unable to request irq\n"); 2538c2ecf20Sopenharmony_ci goto err_dispose; 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci ipmi->opal_msg = devm_kmalloc(dev, 2578c2ecf20Sopenharmony_ci sizeof(*ipmi->opal_msg) + IPMI_MAX_MSG_LENGTH, 2588c2ecf20Sopenharmony_ci GFP_KERNEL); 2598c2ecf20Sopenharmony_ci if (!ipmi->opal_msg) { 2608c2ecf20Sopenharmony_ci rc = -ENOMEM; 2618c2ecf20Sopenharmony_ci goto err_unregister; 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi, dev, 0); 2658c2ecf20Sopenharmony_ci if (rc) { 2668c2ecf20Sopenharmony_ci dev_warn(dev, "IPMI SMI registration failed (%d)\n", rc); 2678c2ecf20Sopenharmony_ci goto err_free_msg; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci dev_set_drvdata(dev, ipmi); 2718c2ecf20Sopenharmony_ci return 0; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cierr_free_msg: 2748c2ecf20Sopenharmony_ci devm_kfree(dev, ipmi->opal_msg); 2758c2ecf20Sopenharmony_cierr_unregister: 2768c2ecf20Sopenharmony_ci free_irq(ipmi->irq, ipmi); 2778c2ecf20Sopenharmony_cierr_dispose: 2788c2ecf20Sopenharmony_ci irq_dispose_mapping(ipmi->irq); 2798c2ecf20Sopenharmony_cierr_free: 2808c2ecf20Sopenharmony_ci devm_kfree(dev, ipmi); 2818c2ecf20Sopenharmony_ci return rc; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic int ipmi_powernv_remove(struct platform_device *pdev) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci ipmi_unregister_smi(smi->intf); 2898c2ecf20Sopenharmony_ci free_irq(smi->irq, smi); 2908c2ecf20Sopenharmony_ci irq_dispose_mapping(smi->irq); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return 0; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic const struct of_device_id ipmi_powernv_match[] = { 2968c2ecf20Sopenharmony_ci { .compatible = "ibm,opal-ipmi" }, 2978c2ecf20Sopenharmony_ci { }, 2988c2ecf20Sopenharmony_ci}; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic struct platform_driver powernv_ipmi_driver = { 3028c2ecf20Sopenharmony_ci .driver = { 3038c2ecf20Sopenharmony_ci .name = "ipmi-powernv", 3048c2ecf20Sopenharmony_ci .of_match_table = ipmi_powernv_match, 3058c2ecf20Sopenharmony_ci }, 3068c2ecf20Sopenharmony_ci .probe = ipmi_powernv_probe, 3078c2ecf20Sopenharmony_ci .remove = ipmi_powernv_remove, 3088c2ecf20Sopenharmony_ci}; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_cimodule_platform_driver(powernv_ipmi_driver); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ipmi_powernv_match); 3148c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("powernv IPMI driver"); 3158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>"); 3168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 317