162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PowerNV OPAL IPMI driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2014 IBM Corp. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) "ipmi-powernv: " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/ipmi_smi.h> 1162306a36Sopenharmony_ci#include <linux/list.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/of_irq.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <asm/opal.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct ipmi_smi_powernv { 2162306a36Sopenharmony_ci u64 interface_id; 2262306a36Sopenharmony_ci struct ipmi_smi *intf; 2362306a36Sopenharmony_ci unsigned int irq; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci /** 2662306a36Sopenharmony_ci * We assume that there can only be one outstanding request, so 2762306a36Sopenharmony_ci * keep the pending message in cur_msg. We protect this from concurrent 2862306a36Sopenharmony_ci * updates through send & recv calls, (and consequently opal_msg, which 2962306a36Sopenharmony_ci * is in-use when cur_msg is set) with msg_lock 3062306a36Sopenharmony_ci */ 3162306a36Sopenharmony_ci spinlock_t msg_lock; 3262306a36Sopenharmony_ci struct ipmi_smi_msg *cur_msg; 3362306a36Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic int ipmi_powernv_start_processing(void *send_info, struct ipmi_smi *intf) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci smi->intf = intf; 4162306a36Sopenharmony_ci return 0; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic void send_error_reply(struct ipmi_smi_powernv *smi, 4562306a36Sopenharmony_ci struct ipmi_smi_msg *msg, u8 completion_code) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci msg->rsp[0] = msg->data[0] | 0x4; 4862306a36Sopenharmony_ci msg->rsp[1] = msg->data[1]; 4962306a36Sopenharmony_ci msg->rsp[2] = completion_code; 5062306a36Sopenharmony_ci msg->rsp_size = 3; 5162306a36Sopenharmony_ci ipmi_smi_msg_received(smi->intf, msg); 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic void ipmi_powernv_send(void *send_info, struct ipmi_smi_msg *msg) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 5762306a36Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 5862306a36Sopenharmony_ci unsigned long flags; 5962306a36Sopenharmony_ci int comp, rc; 6062306a36Sopenharmony_ci size_t size; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* ensure data_len will fit in the opal_ipmi_msg buffer... */ 6362306a36Sopenharmony_ci if (msg->data_size > IPMI_MAX_MSG_LENGTH) { 6462306a36Sopenharmony_ci comp = IPMI_REQ_LEN_EXCEEDED_ERR; 6562306a36Sopenharmony_ci goto err; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* ... and that we at least have netfn and cmd bytes */ 6962306a36Sopenharmony_ci if (msg->data_size < 2) { 7062306a36Sopenharmony_ci comp = IPMI_REQ_LEN_INVALID_ERR; 7162306a36Sopenharmony_ci goto err; 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci spin_lock_irqsave(&smi->msg_lock, flags); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (smi->cur_msg) { 7762306a36Sopenharmony_ci comp = IPMI_NODE_BUSY_ERR; 7862306a36Sopenharmony_ci goto err_unlock; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci /* format our data for the OPAL API */ 8262306a36Sopenharmony_ci opal_msg = smi->opal_msg; 8362306a36Sopenharmony_ci opal_msg->version = OPAL_IPMI_MSG_FORMAT_VERSION_1; 8462306a36Sopenharmony_ci opal_msg->netfn = msg->data[0]; 8562306a36Sopenharmony_ci opal_msg->cmd = msg->data[1]; 8662306a36Sopenharmony_ci if (msg->data_size > 2) 8762306a36Sopenharmony_ci memcpy(opal_msg->data, msg->data + 2, msg->data_size - 2); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* data_size already includes the netfn and cmd bytes */ 9062306a36Sopenharmony_ci size = sizeof(*opal_msg) + msg->data_size - 2; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci pr_devel("%s: opal_ipmi_send(0x%llx, %p, %ld)\n", __func__, 9362306a36Sopenharmony_ci smi->interface_id, opal_msg, size); 9462306a36Sopenharmony_ci rc = opal_ipmi_send(smi->interface_id, opal_msg, size); 9562306a36Sopenharmony_ci pr_devel("%s: -> %d\n", __func__, rc); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (!rc) { 9862306a36Sopenharmony_ci smi->cur_msg = msg; 9962306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 10062306a36Sopenharmony_ci return; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci comp = IPMI_ERR_UNSPECIFIED; 10462306a36Sopenharmony_cierr_unlock: 10562306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 10662306a36Sopenharmony_cierr: 10762306a36Sopenharmony_ci send_error_reply(smi, msg, comp); 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic int ipmi_powernv_recv(struct ipmi_smi_powernv *smi) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct opal_ipmi_msg *opal_msg; 11362306a36Sopenharmony_ci struct ipmi_smi_msg *msg; 11462306a36Sopenharmony_ci unsigned long flags; 11562306a36Sopenharmony_ci uint64_t size; 11662306a36Sopenharmony_ci int rc; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci pr_devel("%s: opal_ipmi_recv(%llx, msg, sz)\n", __func__, 11962306a36Sopenharmony_ci smi->interface_id); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci spin_lock_irqsave(&smi->msg_lock, flags); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (!smi->cur_msg) { 12462306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 12562306a36Sopenharmony_ci pr_warn("no current message?\n"); 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci msg = smi->cur_msg; 13062306a36Sopenharmony_ci opal_msg = smi->opal_msg; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci size = cpu_to_be64(sizeof(*opal_msg) + IPMI_MAX_MSG_LENGTH); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci rc = opal_ipmi_recv(smi->interface_id, 13562306a36Sopenharmony_ci opal_msg, 13662306a36Sopenharmony_ci &size); 13762306a36Sopenharmony_ci size = be64_to_cpu(size); 13862306a36Sopenharmony_ci pr_devel("%s: -> %d (size %lld)\n", __func__, 13962306a36Sopenharmony_ci rc, rc == 0 ? size : 0); 14062306a36Sopenharmony_ci if (rc) { 14162306a36Sopenharmony_ci /* If came via the poll, and response was not yet ready */ 14262306a36Sopenharmony_ci if (rc == OPAL_EMPTY) { 14362306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci smi->cur_msg = NULL; 14862306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 14962306a36Sopenharmony_ci send_error_reply(smi, msg, IPMI_ERR_UNSPECIFIED); 15062306a36Sopenharmony_ci return 0; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (size < sizeof(*opal_msg)) { 15462306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 15562306a36Sopenharmony_ci pr_warn("unexpected IPMI message size %lld\n", size); 15662306a36Sopenharmony_ci return 0; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (opal_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { 16062306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 16162306a36Sopenharmony_ci pr_warn("unexpected IPMI message format (version %d)\n", 16262306a36Sopenharmony_ci opal_msg->version); 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci msg->rsp[0] = opal_msg->netfn; 16762306a36Sopenharmony_ci msg->rsp[1] = opal_msg->cmd; 16862306a36Sopenharmony_ci if (size > sizeof(*opal_msg)) 16962306a36Sopenharmony_ci memcpy(&msg->rsp[2], opal_msg->data, size - sizeof(*opal_msg)); 17062306a36Sopenharmony_ci msg->rsp_size = 2 + size - sizeof(*opal_msg); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci smi->cur_msg = NULL; 17362306a36Sopenharmony_ci spin_unlock_irqrestore(&smi->msg_lock, flags); 17462306a36Sopenharmony_ci ipmi_smi_msg_received(smi->intf, msg); 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void ipmi_powernv_request_events(void *send_info) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic void ipmi_powernv_set_run_to_completion(void *send_info, 18362306a36Sopenharmony_ci bool run_to_completion) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic void ipmi_powernv_poll(void *send_info) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci struct ipmi_smi_powernv *smi = send_info; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ipmi_powernv_recv(smi); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic const struct ipmi_smi_handlers ipmi_powernv_smi_handlers = { 19562306a36Sopenharmony_ci .owner = THIS_MODULE, 19662306a36Sopenharmony_ci .start_processing = ipmi_powernv_start_processing, 19762306a36Sopenharmony_ci .sender = ipmi_powernv_send, 19862306a36Sopenharmony_ci .request_events = ipmi_powernv_request_events, 19962306a36Sopenharmony_ci .set_run_to_completion = ipmi_powernv_set_run_to_completion, 20062306a36Sopenharmony_ci .poll = ipmi_powernv_poll, 20162306a36Sopenharmony_ci}; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic irqreturn_t ipmi_opal_event(int irq, void *data) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct ipmi_smi_powernv *smi = data; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci ipmi_powernv_recv(smi); 20862306a36Sopenharmony_ci return IRQ_HANDLED; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic int ipmi_powernv_probe(struct platform_device *pdev) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci struct ipmi_smi_powernv *ipmi; 21462306a36Sopenharmony_ci struct device *dev; 21562306a36Sopenharmony_ci u32 prop; 21662306a36Sopenharmony_ci int rc; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (!pdev || !pdev->dev.of_node) 21962306a36Sopenharmony_ci return -ENODEV; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci dev = &pdev->dev; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci ipmi = devm_kzalloc(dev, sizeof(*ipmi), GFP_KERNEL); 22462306a36Sopenharmony_ci if (!ipmi) 22562306a36Sopenharmony_ci return -ENOMEM; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci spin_lock_init(&ipmi->msg_lock); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci rc = of_property_read_u32(dev->of_node, "ibm,ipmi-interface-id", 23062306a36Sopenharmony_ci &prop); 23162306a36Sopenharmony_ci if (rc) { 23262306a36Sopenharmony_ci dev_warn(dev, "No interface ID property\n"); 23362306a36Sopenharmony_ci goto err_free; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci ipmi->interface_id = prop; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci rc = of_property_read_u32(dev->of_node, "interrupts", &prop); 23862306a36Sopenharmony_ci if (rc) { 23962306a36Sopenharmony_ci dev_warn(dev, "No interrupts property\n"); 24062306a36Sopenharmony_ci goto err_free; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci ipmi->irq = irq_of_parse_and_map(dev->of_node, 0); 24462306a36Sopenharmony_ci if (!ipmi->irq) { 24562306a36Sopenharmony_ci dev_info(dev, "Unable to map irq from device tree\n"); 24662306a36Sopenharmony_ci ipmi->irq = opal_event_request(prop); 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci rc = request_irq(ipmi->irq, ipmi_opal_event, IRQ_TYPE_LEVEL_HIGH, 25062306a36Sopenharmony_ci "opal-ipmi", ipmi); 25162306a36Sopenharmony_ci if (rc) { 25262306a36Sopenharmony_ci dev_warn(dev, "Unable to request irq\n"); 25362306a36Sopenharmony_ci goto err_dispose; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci ipmi->opal_msg = devm_kmalloc(dev, 25762306a36Sopenharmony_ci sizeof(*ipmi->opal_msg) + IPMI_MAX_MSG_LENGTH, 25862306a36Sopenharmony_ci GFP_KERNEL); 25962306a36Sopenharmony_ci if (!ipmi->opal_msg) { 26062306a36Sopenharmony_ci rc = -ENOMEM; 26162306a36Sopenharmony_ci goto err_unregister; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi, dev, 0); 26562306a36Sopenharmony_ci if (rc) { 26662306a36Sopenharmony_ci dev_warn(dev, "IPMI SMI registration failed (%d)\n", rc); 26762306a36Sopenharmony_ci goto err_free_msg; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci dev_set_drvdata(dev, ipmi); 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cierr_free_msg: 27462306a36Sopenharmony_ci devm_kfree(dev, ipmi->opal_msg); 27562306a36Sopenharmony_cierr_unregister: 27662306a36Sopenharmony_ci free_irq(ipmi->irq, ipmi); 27762306a36Sopenharmony_cierr_dispose: 27862306a36Sopenharmony_ci irq_dispose_mapping(ipmi->irq); 27962306a36Sopenharmony_cierr_free: 28062306a36Sopenharmony_ci devm_kfree(dev, ipmi); 28162306a36Sopenharmony_ci return rc; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic int ipmi_powernv_remove(struct platform_device *pdev) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci ipmi_unregister_smi(smi->intf); 28962306a36Sopenharmony_ci free_irq(smi->irq, smi); 29062306a36Sopenharmony_ci irq_dispose_mapping(smi->irq); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic const struct of_device_id ipmi_powernv_match[] = { 29662306a36Sopenharmony_ci { .compatible = "ibm,opal-ipmi" }, 29762306a36Sopenharmony_ci { }, 29862306a36Sopenharmony_ci}; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic struct platform_driver powernv_ipmi_driver = { 30262306a36Sopenharmony_ci .driver = { 30362306a36Sopenharmony_ci .name = "ipmi-powernv", 30462306a36Sopenharmony_ci .of_match_table = ipmi_powernv_match, 30562306a36Sopenharmony_ci }, 30662306a36Sopenharmony_ci .probe = ipmi_powernv_probe, 30762306a36Sopenharmony_ci .remove = ipmi_powernv_remove, 30862306a36Sopenharmony_ci}; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cimodule_platform_driver(powernv_ipmi_driver); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ipmi_powernv_match); 31462306a36Sopenharmony_ciMODULE_DESCRIPTION("powernv IPMI driver"); 31562306a36Sopenharmony_ciMODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>"); 31662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 317