From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 3wPgJD5pqszDqck for ; Sat, 13 May 2017 05:38:36 +1000 (AEST) Received: from pps.filterd (m0098404.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.20/8.16.0.20) with SMTP id v4CJcWjB062370 for ; Fri, 12 May 2017 15:38:32 -0400 Received: from e35.co.us.ibm.com (e35.co.us.ibm.com [32.97.110.153]) by mx0a-001b2d01.pphosted.com with ESMTP id 2adk6arbnc-1 (version=TLSv1.2 cipher=AES256-SHA bits=256 verify=NOT) for ; Fri, 12 May 2017 15:38:32 -0400 Received: from localhost by e35.co.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Fri, 12 May 2017 13:38:30 -0600 Received: from b03cxnp08027.gho.boulder.ibm.com (9.17.130.19) by e35.co.us.ibm.com (192.168.1.135) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Fri, 12 May 2017 13:38:28 -0600 Received: from b03ledav001.gho.boulder.ibm.com (b03ledav001.gho.boulder.ibm.com [9.17.130.232]) by b03cxnp08027.gho.boulder.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id v4CJcRZi7930232; Fri, 12 May 2017 12:38:27 -0700 Received: from b03ledav001.gho.boulder.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 86E1A6E040; Fri, 12 May 2017 13:38:27 -0600 (MDT) Received: from oc3016140333.ibm.com (unknown [9.41.179.225]) by b03ledav001.gho.boulder.ibm.com (Postfix) with ESMTP id 19DFE6E03A; Fri, 12 May 2017 13:38:27 -0600 (MDT) From: Eddie James To: openbmc@lists.ozlabs.org Cc: joel@jms.id.au, bradleyb@fuzziesquirrel.com, cbostic@linux.vnet.ibm.com, "Edward A. James" Subject: [PATCH linux dev-4.10 2/3] drivers: fsi: sbefifo: Add OCC driver Date: Fri, 12 May 2017 14:38:19 -0500 X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1494617900-32369-1-git-send-email-eajames@linux.vnet.ibm.com> References: <1494617900-32369-1-git-send-email-eajames@linux.vnet.ibm.com> X-TM-AS-GCONF: 00 x-cbid: 17051219-0012-0000-0000-0000143A01B0 X-IBM-SpamModules-Scores: X-IBM-SpamModules-Versions: BY=3.00007052; HX=3.00000241; KW=3.00000007; PH=3.00000004; SC=3.00000211; SDB=6.00859642; UDB=6.00426138; IPR=6.00639225; BA=6.00005345; NDR=6.00000001; ZLA=6.00000005; ZF=6.00000009; ZB=6.00000000; ZP=6.00000000; ZH=6.00000000; ZU=6.00000002; MB=3.00015432; XFM=3.00000015; UTC=2017-05-12 19:38:29 X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 17051219-0013-0000-0000-00004DA28007 Message-Id: <1494617900-32369-3-git-send-email-eajames@linux.vnet.ibm.com> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2017-05-12_11:, , signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 suspectscore=1 malwarescore=0 phishscore=0 adultscore=0 bulkscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1703280000 definitions=main-1705120367 X-BeenThere: openbmc@lists.ozlabs.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Development list for OpenBMC List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 12 May 2017 19:38:37 -0000 From: "Edward A. James" This driver provides an atomic communications channel between the OCC on the POWER9 processor and a service processor (a BMC). The driver is dependent on the FSI SBEIFO driver to get hardware access to the OCC SRAM. The format of the communication is a command followed by a response. Since the command and response must be performed atomically, the driver will perform this operations asynchronously. In this way, a write operation starts the command, and a read will gather the response data once it is complete. Signed-off-by: Edward A. James --- drivers/fsi/Kconfig | 9 + drivers/fsi/Makefile | 1 + drivers/fsi/occ.c | 625 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 635 insertions(+) create mode 100644 drivers/fsi/occ.c diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 39527fa..f3d8593 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -36,6 +36,15 @@ config FSI_SBEFIFO ---help--- This option enables an FSI based SBEFIFO device driver. +if FSI_SBEFIFO + +config OCC + tristate "OCC SBEFIFO client device driver" + ---help--- + This option enables an SBEFIFO based OCC device driver. + +endif + endif endmenu diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 851182e..336d9d5 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +obj-$(CONFIG_OCC) += occ.o diff --git a/drivers/fsi/occ.c b/drivers/fsi/occ.c new file mode 100644 index 0000000..74272c8 --- /dev/null +++ b/drivers/fsi/occ.c @@ -0,0 +1,625 @@ +/* + * Copyright 2017 IBM Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OCC_SRAM_BYTES 4096 +#define OCC_CMD_DATA_BYTES 4090 +#define OCC_RESP_DATA_BYTES 4089 + +struct occ { + struct device *sbefifo; + char name[32]; + int idx; + struct miscdevice mdev; + struct list_head xfrs; + spinlock_t list_lock; + spinlock_t occ_lock; + struct work_struct work; +}; + +#define to_occ(x) container_of((x), struct occ, mdev) + +struct occ_command { + u8 seq_no; + u8 cmd_type; + u16 data_length; + u8 data[OCC_CMD_DATA_BYTES]; + u16 checksum; +}; + +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + u16 data_length; + u8 data[OCC_RESP_DATA_BYTES]; + u16 checksum; +}; + +struct occ_xfr; + +enum { + CLIENT_NONBLOCKING, +}; + +struct occ_client { + struct occ *occ; + struct occ_xfr *xfr; + spinlock_t lock; + wait_queue_head_t wait; + size_t read_offset; + unsigned long flags; +}; + +enum { + XFR_IN_PROGRESS, + XFR_COMPLETE, + XFR_CANCELED, + XFR_WAITING, +}; + +struct occ_xfr { + struct list_head link; + struct occ_client *client; + int rc; + u8 buf[OCC_SRAM_BYTES]; + size_t cmd_data_length; + size_t resp_data_length; + unsigned long flags; +}; + +static struct workqueue_struct *occ_wq; + +static DEFINE_IDA(occ_ida); + +static void occ_enqueue_xfr(struct occ_xfr *xfr) +{ + int empty; + struct occ *occ = xfr->client->occ; + + spin_lock_irq(&occ->list_lock); + empty = list_empty(&occ->xfrs); + list_add_tail(&xfr->link, &occ->xfrs); + spin_unlock(&occ->list_lock); + + if (empty) + queue_work(occ_wq, &occ->work); +} + +static int occ_open(struct inode *inode, struct file *file) +{ + struct occ_client *client; + struct miscdevice *mdev = file->private_data; + struct occ *occ = to_occ(mdev); + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + client->occ = occ; + spin_lock_init(&client->lock); + init_waitqueue_head(&client->wait); + + if (file->f_flags & O_NONBLOCK) + set_bit(CLIENT_NONBLOCKING, &client->flags); + + file->private_data = client; + + return 0; +} + +static ssize_t occ_read(struct file *file, char __user *buf, size_t len, + loff_t *offset) +{ + int rc; + size_t bytes; + struct occ_xfr *xfr; + struct occ_client *client = file->private_data; + + if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + + if (len > OCC_SRAM_BYTES) + return -EINVAL; + + spin_lock_irq(&client->lock); + if (!client->xfr) { + /* we just finished reading all data, return 0 */ + if (client->read_offset) { + rc = 0; + client->read_offset = 0; + } else + rc = -ENOMSG; + + goto done; + } + + xfr = client->xfr; + + if (!test_bit(XFR_COMPLETE, &xfr->flags)) { + if (client->flags & CLIENT_NONBLOCKING) { + rc = -ERESTARTSYS; + goto done; + } + + set_bit(XFR_WAITING, &xfr->flags); + spin_unlock(&client->lock); + + rc = wait_event_interruptible(client->wait, + test_bit(XFR_COMPLETE, &xfr->flags) || + test_bit(XFR_CANCELED, &xfr->flags)); + + spin_lock_irq(&client->lock); + if (test_bit(XFR_CANCELED, &xfr->flags)) { + kfree(xfr); + spin_unlock(&client->lock); + kfree(client); + return -EBADFD; + } + + clear_bit(XFR_WAITING, &xfr->flags); + if (!test_bit(XFR_COMPLETE, &xfr->flags)) { + rc = -EINTR; + goto done; + } + } + + if (xfr->rc) { + rc = xfr->rc; + goto done; + } + + bytes = min(len, xfr->resp_data_length - client->read_offset); + if (copy_to_user(buf, &xfr->buf[client->read_offset], bytes)) { + rc = -EFAULT; + goto done; + } + + client->read_offset += bytes; + + /* xfr done */ + if (client->read_offset == xfr->resp_data_length) { + kfree(xfr); + client->xfr = NULL; + } + + rc = bytes; + +done: + spin_unlock(&client->lock); + return rc; +} + +static ssize_t occ_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + int rc; + struct occ_xfr *xfr; + struct occ_client *client = file->private_data; + + if (!access_ok(VERIFY_READ, buf, len)) + return -EFAULT; + + if (len > OCC_SRAM_BYTES) + return -EINVAL; + + spin_lock_irq(&client->lock); + if (client->xfr) { + rc = -EDEADLK; + goto done; + } + + xfr = kzalloc(sizeof(*xfr), GFP_KERNEL); + if (!xfr) { + rc = -ENOMEM; + goto done; + } + + if (copy_from_user(xfr->buf, buf, len)) { + kfree(xfr); + rc = -EFAULT; + goto done; + } + + xfr->client = client; + xfr->cmd_data_length = len; + client->xfr = xfr; + client->read_offset = 0; + + occ_enqueue_xfr(xfr); + + rc = len; + +done: + spin_unlock(&client->lock); + return rc; +} + +static int occ_release(struct inode *inode, struct file *file) +{ + struct occ_xfr *xfr; + struct occ_client *client = file->private_data; + struct occ *occ = client->occ; + + spin_lock_irq(&client->lock); + xfr = client->xfr; + if (!xfr) { + spin_unlock(&client->lock); + kfree(client); + return 0; + } + + spin_lock_irq(&occ->list_lock); + set_bit(XFR_CANCELED, &xfr->flags); + if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) { + /* already deleted from list if complete */ + if (!test_bit(XFR_COMPLETE, &xfr->flags)) + list_del(&xfr->link); + + spin_unlock(&occ->list_lock); + + if (test_bit(XFR_WAITING, &xfr->flags)) { + /* blocking read; let reader clean up */ + wake_up_interruptible(&client->wait); + spin_unlock(&client->lock); + return 0; + } + + kfree(xfr); + spin_unlock(&client->lock); + kfree(client); + return 0; + } + + /* operation is in progress; let worker clean up*/ + spin_unlock(&occ->list_lock); + spin_unlock(&client->lock); + return 0; +} + +static const struct file_operations occ_fops = { + .owner = THIS_MODULE, + .open = occ_open, + .read = occ_read, + .write = occ_write, + .release = occ_release, +}; + +static int occ_getscom(struct device *sbefifo, u32 address, u8 *data) +{ + int rc; + u32 buf[4]; + struct sbefifo_client *client; + const size_t len = sizeof(buf); + + buf[0] = cpu_to_be32(0x4); + buf[1] = cpu_to_be32(0xa201); + buf[2] = 0; + buf[3] = cpu_to_be32(address); + + client = sbefifo_drv_open(sbefifo, 0); + if (!client) + return -ENODEV; + + rc = sbefifo_drv_write(client, (const char *)buf, len); + if (rc < 0) + goto done; + else if (rc != len) { + rc = -EMSGSIZE; + goto done; + } + + rc = sbefifo_drv_read(client, (char *)buf, len); + if (rc < 0) + goto done; + else if (rc != len) { + rc = -EMSGSIZE; + goto done; + } + + /* check for good response */ + if ((be32_to_cpu(buf[2]) >> 16) != 0xC0DE) { + rc = -EFAULT; + goto done; + } + + rc = 0; + + memcpy(data, buf, sizeof(u64)); + +done: + sbefifo_drv_release(client); + return rc; +} + +static int occ_putscom(struct device *sbefifo, u32 address, u8 *data) +{ + int rc; + u32 buf[6]; + struct sbefifo_client *client; + const size_t len = sizeof(buf); + + buf[0] = cpu_to_be32(0x6); + buf[1] = cpu_to_be32(0xa202); + buf[2] = 0; + buf[3] = cpu_to_be32(address); + memcpy(&buf[4], data, sizeof(u64)); + + client = sbefifo_drv_open(sbefifo, 0); + if (!client) + return -ENODEV; + + rc = sbefifo_drv_write(client, (const char *)buf, len); + if (rc < 0) + goto done; + else if (rc != len) { + rc = -EMSGSIZE; + goto done; + } + + rc = 0; + + /* ignore response */ + sbefifo_drv_read(client, (char *)buf, len); + +done: + sbefifo_drv_release(client); + return rc; +} + +static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0, + u32 data1) +{ + u8 buf[8]; + u32 raw_data0 = cpu_to_be32(data0), raw_data1 = cpu_to_be32(data1); + + memcpy(buf, &raw_data0, 4); + memcpy(buf + 4, &raw_data1, 4); + + return occ_putscom(sbefifo, address, buf); +} + +static void occ_worker(struct work_struct *work) +{ + int i, empty, canceled, waiting, rc; + u16 resp_data_length; + struct occ *occ = container_of(work, struct occ, work); + struct device *sbefifo = occ->sbefifo; + struct occ_client *client; + struct occ_xfr *xfr; + struct occ_response *resp; + +again: + spin_lock_irq(&occ->list_lock); + xfr = list_first_entry(&occ->xfrs, struct occ_xfr, link); + if (!xfr) { + spin_unlock(&occ->list_lock); + return; + } + + set_bit(XFR_IN_PROGRESS, &xfr->flags); + spin_unlock(&occ->list_lock); + + resp = (struct occ_response *)xfr->buf; + + spin_lock_irq(&occ->occ_lock); + + /* set address reg to occ sram command buffer */ + rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBE000, 0); + if (rc) + goto done; + + /* write cmd data */ + for (i = 0; i < xfr->cmd_data_length; i += 8) { + rc = occ_putscom(sbefifo, 0x6D055, &xfr->buf[i]); + if (rc) + goto done; + } + + /* trigger attention */ + rc = occ_putscom_u32(sbefifo, 0x6D035, 0x20010000, 0); + if (rc) + goto done; + + /* set address reg to occ sram response buffer */ + rc = occ_putscom_u32(sbefifo, 0x6D050, 0xFFFBF000, 0); + if (rc) + goto done; + + rc = occ_getscom(sbefifo, 0x6D055, xfr->buf); + if (rc) + goto done; + + xfr->resp_data_length += 8; + + resp_data_length = be16_to_cpu(get_unaligned(&resp->data_length)); + if (resp_data_length > OCC_RESP_DATA_BYTES) { + rc = -EFAULT; + goto done; + } + + /* already read 3 bytes of resp data, but also need 2 bytes chksum */ + for (i = 8; i < resp_data_length + 7; i += 8) { + rc = occ_getscom(sbefifo, 0x6D055, &xfr->buf[i]); + if (rc) + goto done; + + xfr->resp_data_length += 8; + } + + /* no errors, got all data */ + xfr->resp_data_length = resp_data_length + 7; + +done: + spin_unlock(&occ->occ_lock); + + xfr->rc = rc; + client = xfr->client; + + /* lock client to prevent race with read() */ + spin_lock_irq(&client->lock); + set_bit(XFR_COMPLETE, &xfr->flags); + waiting = test_bit(XFR_WAITING, &xfr->flags); + spin_unlock(&client->lock); + + spin_lock_irq(&occ->list_lock); + clear_bit(XFR_IN_PROGRESS, &xfr->flags); + list_del(&xfr->link); + empty = list_empty(&occ->xfrs); + canceled = test_bit(XFR_CANCELED, &xfr->flags); + spin_unlock(&occ->list_lock); + + if (waiting) + wake_up_interruptible(&client->wait); + else if (canceled) { + kfree(xfr); + kfree(xfr->client); + } + + if (!empty) + goto again; +} + +static int occ_probe(struct platform_device *pdev) +{ + int rc; + u32 reg; + struct occ *occ; + struct device *dev = &pdev->dev; + + dev_info(dev, "Found occ device\n"); + occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL); + if (!occ) + return -ENOMEM; + + occ->sbefifo = dev->parent; + INIT_LIST_HEAD(&occ->xfrs); + spin_lock_init(&occ->list_lock); + spin_lock_init(&occ->occ_lock); + INIT_WORK(&occ->work, occ_worker); + + if (dev->of_node) { + rc = of_property_read_u32(dev->of_node, "reg", ®); + if (!rc) { + /* make sure we don't have a duplicate from dts */ + occ->idx = ida_simple_get(&occ_ida, reg, reg + 1, + GFP_KERNEL); + if (occ->idx < 0) + occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, + GFP_KERNEL); + } else + occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, + GFP_KERNEL); + } else + occ->idx = ida_simple_get(&occ_ida, 1, INT_MAX, GFP_KERNEL); + + snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx); + occ->mdev.fops = &occ_fops; + occ->mdev.minor = MISC_DYNAMIC_MINOR; + occ->mdev.name = occ->name; + occ->mdev.parent = dev; + + rc = misc_register(&occ->mdev); + if (rc) { + dev_err(dev, "failed to register miscdevice\n"); + return rc; + } + + platform_set_drvdata(pdev, occ); + + return 0; +} + +static int occ_remove(struct platform_device *pdev) +{ + struct occ_xfr *xfr, *tmp; + struct occ *occ = platform_get_drvdata(pdev); + struct occ_client *client; + + misc_deregister(&occ->mdev); + + spin_lock_irq(&occ->list_lock); + list_for_each_entry_safe(xfr, tmp, &occ->xfrs, link) { + client = xfr->client; + set_bit(XFR_CANCELED, &xfr->flags); + + if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) { + list_del(&xfr->link); + + spin_lock_irq(&client->lock); + if (test_bit(XFR_WAITING, &xfr->flags)) { + wake_up_interruptible(&client->wait); + spin_unlock(&client->lock); + } else { + kfree(xfr); + spin_unlock(&client->lock); + kfree(client); + } + } + } + spin_unlock(&occ->list_lock); + + flush_work(&occ->work); + + ida_simple_remove(&occ_ida, occ->idx); + + return 0; +} + +static const struct of_device_id occ_match[] = { + { .compatible = "ibm,p9-occ" }, + { }, +}; + +static struct platform_driver occ_driver = { + .driver = { + .name = "occ", + .of_match_table = occ_match, + }, + .probe = occ_probe, + .remove = occ_remove, +}; + +static int occ_init(void) +{ + occ_wq = create_singlethread_workqueue("occ"); + if (!occ_wq) + return -ENOMEM; + + return platform_driver_register(&occ_driver); +} + +static void occ_exit(void) +{ + destroy_workqueue(occ_wq); + + platform_driver_unregister(&occ_driver); +} + +module_init(occ_init); +module_exit(occ_exit); + +MODULE_AUTHOR("Eddie James "); +MODULE_DESCRIPTION("BMC P9 OCC driver"); +MODULE_LICENSE("GPL"); + -- 1.8.3.1