Skip to contentSkip to conversation

drivers: fsi: Add OCC driver with SBEFIFO in-kernel API

PATCH Superseded Eddie James 3 participants 7 messages May 12, 2017 – Jun 5, 2017

Subsystems (3)

v1 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

cover drivers: fsi: Add OCC driver with SBEFIFO in-kernel API

From: "Edward A. James" <[email protected]>

This patch series adds a driver to communicate with the POWER processor On-Chip Controller (OCC), from a service processor. Communication is performed via the SBEFIFO driver, which required a new in-kernel API.

The patch series includes device-tree updates to instantiate devices for the OCC driver from the SBEFIFO driver.

An application to test the OCC driver can be found at
https://github.com/eddiejames/occtest

Edward A. James (3):
drivers: fsi: sbefifo: Add in-kernel API
drivers: fsi: sbefifo: Add OCC driver
dts: aspeed: witherspoon: Add FSI devices

 arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts |  46 ++
 drivers/fsi/Kconfig                              |   9 +
 drivers/fsi/Makefile                             |   1 +
 drivers/fsi/fsi-sbefifo.c                        | 161 ++++--
 drivers/fsi/occ.c                                | 625 +++++++++++++++++++++++
 include/linux/fsi-sbefifo.h                      |  30 ++
 6 files changed, 842 insertions(+), 30 deletions(-)
 create mode 100644 drivers/fsi/occ.c
 create mode 100644 include/linux/fsi-sbefifo.h
Signature · 1 line
1.8.3.1
#1 lore raw

PATCH 1/3 drivers: fsi: sbefifo: Add in-kernel API

From: "Edward A. James" <[email protected]>

Add exported functions to the SBEFIFO driver to open/write/read/close from within the kernel.

---
 drivers/fsi/fsi-sbefifo.c   | 161 +++++++++++++++++++++++++++++++++++---------
 include/linux/fsi-sbefifo.h |  30 +++++++++
 2 files changed, 161 insertions(+), 30 deletions(-)
 create mode 100644 include/linux/fsi-sbefifo.h
drivers/fsi/fsi-sbefifo.c +131 -30
index b49aec2..56e6331 100644
@@ -15,9 +15,12 @@
 #include <linux/errno.h>
 #include <linux/idr.h>
 #include <linux/fsi.h>
+#include <linux/fsi-sbefifo.h>
 #include <linux/list.h>
 #include <linux/miscdevice.h>
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/poll.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
@@ -82,6 +85,7 @@ struct sbefifo_client {
 	struct list_head xfrs;
 	struct sbefifo *dev;
 	struct kref kref;
+	unsigned long f_flags;
 };
 
 static struct list_head sbefifo_fifos;
@@ -506,6 +510,7 @@ static int sbefifo_open(struct inode *inode, struct file *file)
 		return -ENOMEM;
 
 	file->private_data = client;
+	client->f_flags = file->f_flags;
 
 	return 0;
 }
@@ -530,24 +535,18 @@ static unsigned int sbefifo_poll(struct file *file, poll_table *wait)
 	return mask;
 }
 
-static ssize_t sbefifo_read(struct file *file, char __user *buf,
-		size_t len, loff_t *offset)
+static ssize_t sbefifo_read_common(struct sbefifo_client *client,
+				   char __user *ubuf, char *kbuf, size_t len)
 {
-	struct sbefifo_client *client = file->private_data;
 	struct sbefifo *sbefifo = client->dev;
 	struct sbefifo_xfr *xfr;
-	ssize_t ret = 0;
 	size_t n;
-
-	WARN_ON(*offset);
-
-	if (!access_ok(VERIFY_WRITE, buf, len))
-		return -EFAULT;
+	ssize_t ret = 0;
 
 	if ((len >> 2) << 2 != len)
 		return -EINVAL;
 
-	if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client))
+	if ((client->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client))
 		return -EAGAIN;
 
 	sbefifo_get_client(client);
@@ -566,10 +565,13 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf,
 
 	n = min_t(size_t, n, len);
 
-	if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) {
-		sbefifo_put_client(client);
-		return -EFAULT;
-	}
+	if (ubuf) {
+		if (copy_to_user(ubuf, READ_ONCE(client->rbuf.rpos), n)) {
+			sbefifo_put_client(client);
+			return -EFAULT;
+		}
+	} else
+		memcpy(kbuf, READ_ONCE(client->rbuf.rpos), n);
 
 	if (sbefifo_buf_readnb(&client->rbuf, n)) {
 		xfr = sbefifo_client_next_xfr(client);
@@ -592,20 +594,28 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf,
 	return n;
 }
 
-static ssize_t sbefifo_write(struct file *file, const char __user *buf,
+static ssize_t sbefifo_read(struct file *file, char __user *buf,
 		size_t len, loff_t *offset)
 {
 	struct sbefifo_client *client = file->private_data;
-	struct sbefifo *sbefifo = client->dev;
-	struct sbefifo_xfr *xfr;
-	ssize_t ret = 0;
-	size_t n;
 
 	WARN_ON(*offset);
 
-	if (!access_ok(VERIFY_READ, buf, len))
+	if (!access_ok(VERIFY_WRITE, buf, len))
 		return -EFAULT;
 
+	return sbefifo_read_common(client, buf, NULL, len);
+}
+
+static ssize_t sbefifo_write_common(struct sbefifo_client *client,
+				    const char __user *ubuf, const char *kbuf,
+				    size_t len)
+{
+	struct sbefifo *sbefifo = client->dev;
+	struct sbefifo_xfr *xfr;
+	ssize_t ret = 0;
+	size_t n;
+
 	if ((len >> 2) << 2 != len)
 		return -EINVAL;
 
@@ -617,7 +627,7 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf,
 	spin_lock_irq(&sbefifo->lock);
 	xfr = sbefifo_next_xfr(sbefifo);
 
-	if ((file->f_flags & O_NONBLOCK) && xfr && n < len) {
+	if ((client->f_flags & O_NONBLOCK) && xfr && n < len) {
 		spin_unlock_irq(&sbefifo->lock);
 		return -EAGAIN;
 	}
@@ -657,18 +667,25 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf,
 
 		n = min_t(size_t, n, len);
 
-		if (copy_from_user(READ_ONCE(client->wbuf.wpos), buf, n)) {
-			set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
-			sbefifo_get(sbefifo);
-			if (mod_timer(&sbefifo->poll_timer, jiffies))
-				sbefifo_put(sbefifo);
-			sbefifo_put_client(client);
-			return -EFAULT;
+		if (ubuf) {
+			if (copy_from_user(READ_ONCE(client->wbuf.wpos), ubuf,
+			    n)) {
+				set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
+				sbefifo_get(sbefifo);
+				if (mod_timer(&sbefifo->poll_timer, jiffies))
+					sbefifo_put(sbefifo);
+				sbefifo_put_client(client);
+				return -EFAULT;
+			}
+
+			ubuf += n;
+		} else {
+			memcpy(READ_ONCE(client->wbuf.wpos), kbuf, n);
+			kbuf += n;
 		}
 
 		sbefifo_buf_wrotenb(&client->wbuf, n);
 		len -= n;
-		buf += n;
 		ret += n;
 
 		/*
@@ -685,6 +702,19 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf,
 	return ret;
 }
 
+static ssize_t sbefifo_write(struct file *file, const char __user *buf,
+		size_t len, loff_t *offset)
+{
+	struct sbefifo_client *client = file->private_data;
+
+	WARN_ON(*offset);
+
+	if (!access_ok(VERIFY_READ, buf, len))
+		return -EFAULT;
+
+	return sbefifo_write_common(client, buf, NULL, len);
+}
+
 static int sbefifo_release(struct inode *inode, struct file *file)
 {
 	struct sbefifo_client *client = file->private_data;
@@ -704,12 +734,68 @@ static int sbefifo_release(struct inode *inode, struct file *file)
 	.release	= sbefifo_release,
 };
 
+struct sbefifo_client *sbefifo_drv_open(struct device *dev,
+					unsigned long flags)
+{
+	struct sbefifo_client *client = NULL;
+	struct sbefifo *sbefifo;
+	struct fsi_device *fsi_dev = to_fsi_dev(dev);
+
+	list_for_each_entry(sbefifo, &sbefifo_fifos, link) {
+		if (sbefifo->fsi_dev != fsi_dev)
+			continue;
+
+		client = sbefifo_new_client(sbefifo);
+		if (client)
+			client->f_flags = flags;
+	}
+
+	return client;
+}
+EXPORT_SYMBOL_GPL(sbefifo_drv_open);
+
+int sbefifo_drv_read(struct sbefifo_client *client, char *buf, size_t len)
+{
+	return sbefifo_read_common(client, NULL, buf, len);
+}
+EXPORT_SYMBOL_GPL(sbefifo_drv_read);
+
+int sbefifo_drv_write(struct sbefifo_client *client, const char *buf,
+		      size_t len)
+{
+	return sbefifo_write_common(client, NULL, buf, len);
+}
+EXPORT_SYMBOL_GPL(sbefifo_drv_write);
+
+void sbefifo_drv_release(struct sbefifo_client *client)
+{
+	if (!client)
+		return;
+
+	sbefifo_put_client(client);
+}
+EXPORT_SYMBOL_GPL(sbefifo_drv_release);
+
+static int sbefifo_unregister_child(struct device *dev, void *data)
+{
+	struct platform_device *child = to_platform_device(dev);
+
+	of_device_unregister(child);
+	if (dev->of_node)
+		of_node_clear_flag(dev->of_node, OF_POPULATED);
+
+	return 0;
+}
+
 static int sbefifo_probe(struct device *dev)
 {
 	struct fsi_device *fsi_dev = to_fsi_dev(dev);
 	struct sbefifo *sbefifo;
+	struct device_node *np;
+	struct platform_device *child;
+	char child_name[32];
 	u32 sts;
-	int ret;
+	int ret, child_idx = 0;
 
 	dev_info(dev, "Found sbefifo device\n");
 	sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL);
@@ -750,6 +836,18 @@ static int sbefifo_probe(struct device *dev)
 	init_waitqueue_head(&sbefifo->wait);
 	INIT_LIST_HEAD(&sbefifo->xfrs);
 
+	if (dev->of_node) {
+		/* create platform devs for dts child nodes (occ, etc) */
+		for_each_child_of_node(dev->of_node, np) {
+			snprintf(child_name, sizeof(child_name), "%s-dev%d",
+				 sbefifo->name, child_idx++);
+			child = of_platform_device_create(np, child_name, dev);
+			if (!child)
+				dev_warn(&sbefifo->fsi_dev->dev,
+					 "failed to create child node dev\n");
+		}
+	}
+
 	/* This bit of silicon doesn't offer any interrupts... */
 	setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer,
 			(unsigned long)sbefifo);
@@ -767,6 +865,9 @@ static int sbefifo_remove(struct device *dev)
 	list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) {
 		if (sbefifo->fsi_dev != fsi_dev)
 			continue;
+
+		device_for_each_child(dev, NULL, sbefifo_unregister_child);
+
 		misc_deregister(&sbefifo->mdev);
 		list_del(&sbefifo->link);
 		ida_simple_remove(&sbefifo_ida, sbefifo->idx);
include/linux/fsi-sbefifo.h new file +30 -0
new file mode 100644
index 0000000..1b46c63
@@ -0,0 +1,30 @@
+/*
+ * SBEFIFO FSI Client device driver
+ *
+ * Copyright (C) IBM Corporation 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __FSI_SBEFIFO_H__
+#define __FSI_SBEFIFO_H__
+
+struct device;
+struct sbefifo_client;
+
+extern struct sbefifo_client *sbefifo_drv_open(struct device *dev,
+					       unsigned long flags);
+extern int sbefifo_drv_read(struct sbefifo_client *client, char *buf,
+			    size_t len);
+extern int sbefifo_drv_write(struct sbefifo_client *client, const char *buf,
+			     size_t len);
+extern void sbefifo_drv_release(struct sbefifo_client *client);
+
+#endif /* __FSI_SBEFIFO_H__ */
Signature · 1 line
1.8.3.1
#2 lore raw

PATCH 2/3 drivers: fsi: sbefifo: Add OCC driver

In reply to Eddie James

From: "Edward A. James" <[email protected]>

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.

---
 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
drivers/fsi/Kconfig +9 -0
index 39527fa..f3d8593 100644
@@ -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
drivers/fsi/Makefile +1 -0
index 851182e..336d9d5 100644
@@ -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
drivers/fsi/occ.c new file +625 -0

625 lines not on this page · raw message

Signature · 1 line
1.8.3.1
#3 lore raw

Re: [PATCH linux dev-4.10 2/3] drivers: fsi: sbefifo: Add OCC driver

Hi Eddie,

Quoted 7 lines

From: "Edward A. James" <[email protected]>

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.

Quoted 7 lines

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.

Overall, this looks pretty good. The userspace ABI seems straightforward (which is the important part to get right early), which is great.

I do have a few comments though, provided inline. However, most of these are fairly minor. We do need to sort out the DT representation though, as that's harder to change post-merge.

Quoted 6 lines
drivers/fsi/Kconfig +0 -0
@@ -36,6 +36,15 @@ config FSI_SBEFIFO
 	---help---
 	This option enables an FSI based SBEFIFO device driver.
 

drivers/fsi/Kconfig config FSI_SBEFIFO · line 48

Quoted 10 lines
drivers/fsi/Kconfig +9 -0
+if FSI_SBEFIFO
+
+config OCC
+	tristate "OCC SBEFIFO client device driver"
+	---help---
+	This option enables an SBEFIFO based OCC device driver.
+
+endif
+
 endif

I don't think we need the if FSI_SBEFIFO there, as you've already got the depends on FSI_SBEFIFO (ie., there's no need to hide it from the menu).

Quoted 4 lines
drivers/fsi/Kconfig +4 -0
+#define OCC_SRAM_BYTES		4096
+#define OCC_CMD_DATA_BYTES	4090
+#define OCC_RESP_DATA_BYTES	4089
+

drivers/fsi/Kconfig config FSI_SBEFIFO

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+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;
+};

Since you have two locks there, can you describe what each lock protects?

Also, does occ_lock need to be a spin_lock? Looks like we're doing quite a lot with that held - can we change to a mutex instead?

Quoted 11 lines
drivers/fsi/Kconfig +11 -0
+struct occ_xfr;
+
+enum {
+	CLIENT_NONBLOCKING,
+};
+
+struct occ_client {
+	struct occ *occ;
+	struct occ_xfr *xfr;
+	spinlock_t lock;
+	wait_queue_head_t wait;

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+	size_t read_offset;
+	unsigned long flags;
+};
+
+enum {
+	XFR_IN_PROGRESS,
+	XFR_COMPLETE,
+	XFR_CANCELED,
+	XFR_WAITING,
+};

This looks like a set of states; but you're storing them in a bitmask (so, you can have multiple states asserted at the same time). Is that necessary, or are these mutually exclusive (which would be much simple to follow)?

If we do need to support multiple states, could you add a comment here indicating which combinations are valid, and the transitions between states?

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -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;

This is probably redundant, as you're (correctly) using copy_to_user below.

drivers/fsi/Kconfig

Quoted 12 lines
drivers/fsi/Kconfig +12 -0
+
+	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;

Super minor nit: closing curly brace and 'else' go on the same line.

Quoted 8 lines
drivers/fsi/Kconfig +8 -0
+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))

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+		return -EFAULT;
+
+	if (len > OCC_SRAM_BYTES)
+		return -EINVAL;
+
+	spin_lock_irq(&client->lock);
+	if (client->xfr) {
+		rc = -EDEADLK;
+		goto done;
+	}

I think -EBUSY would be better here.

In general, it looks like the code supports two separate processes performing independent writes. In that case, you already have the correct sequencing in the interface to the OCC, could we possibly support multiple queued writes on the one client? Assuming reads() return the responses in the order that they were written...

Or is there no point in doing that?

drivers/fsi/Kconfig

Quoted 6 lines
drivers/fsi/Kconfig +6 -0
+
+	xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
+	if (!xfr) {
+		rc = -ENOMEM;
+		goto done;
+	}

Do we need to allocate xfr on every read/write? Could we just allocate this as part of struct occfifo_client, and re-use that?

drivers/fsi/Kconfig

Quoted 11 lines
drivers/fsi/Kconfig +11 -0
+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);

No endianness concerns there? I haven't had a thorough look at the sbefifo_drv_read/_write API, do they define an endianness for the buffers?

drivers/fsi/Kconfig

Quoted 12 lines
drivers/fsi/Kconfig +12 -0
+
+	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;
+	}

Using a compound statements for only one (but not all) branches of an conditional expression freaks me out a bit, mainly from the bugs that have arisen from subsequent changes to the code.

Also, you're not passing on negative rc values to the caller?

drivers/fsi/Kconfig

Quoted 2 lines
drivers/fsi/Kconfig +2 -0
+static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0,
+			   u32 data1)

Does that make sense to split into two u32s, instead of a single u64?

I know a some of the existing scom interfaces split it up, but is there a particular reason we need to carry that over to this code?

drivers/fsi/Kconfig

Quoted 8 lines
drivers/fsi/Kconfig +8 -0
+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");

This probably isn't necessary; the presence of the device itself is a pretty good indication that we probed one (and you have a log on the error case).

Quoted 12 lines
drivers/fsi/Kconfig +12 -0
+	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", &reg);

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+		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);

We'll need a binding document for this. What is the reg property representing, hardware-wise?

Traditionally, reg is the address of the device on the parent bus, and so it'd totally find to have multiple devices in the system to share reg values (provided they're on different busses). So, I'm not sure that this is the usage that we want here; perhaps another property name would be better...

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -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) {

Do you ever hit this code? I'd have assumed that ->remove is going to be called once all open file descriptors have gone, and your ->release should have emptied the transfer lists at that point...

Cheers,

Jeremy

#4 lore raw

Re: [PATCH linux dev-4.10 2/3] drivers: fsi: sbefifo: Add OCC driver

Quoted 15 lines · Jeremy Kerr

Hi Eddie,

Quoted 13 lines

From: "Edward A. James" <[email protected]>

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.

Quoted 7 lines
Quoted 1 line

Overall, this looks pretty good. The userspace ABI seems straightforward (which is the important part to get right early), which is great.

I do have a few comments though, provided inline. However, most of these are fairly minor. We do need to sort out the DT representation though, as that's harder to change post-merge.

Hi, thanks for the review. I'm switching to a different way to access the OCC SRAM (SBE getSram/putSram calls), so some of this will be refactored.

Quoted 38 lines
Quoted 7 lines
drivers/fsi/Kconfig +0 -0
@@ -36,6 +36,15 @@ config FSI_SBEFIFO
  	---help---
  	This option enables an FSI based SBEFIFO device driver.
 

drivers/fsi/Kconfig config FSI_SBEFIFO · line 48

Quoted 10 lines
drivers/fsi/Kconfig +9 -0
+if FSI_SBEFIFO
+
+config OCC
+	tristate "OCC SBEFIFO client device driver"
+	---help---
+	This option enables an SBEFIFO based OCC device driver.
+
+endif
+
  endif

I don't think we need the if FSI_SBEFIFO there, as you've already got the depends on FSI_SBEFIFO (ie., there's no need to hide it from the menu).

Quoted 4 lines
drivers/fsi/Kconfig +4 -0
+#define OCC_SRAM_BYTES		4096
+#define OCC_CMD_DATA_BYTES	4090
+#define OCC_RESP_DATA_BYTES	4089
+

drivers/fsi/Kconfig config FSI_SBEFIFO

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+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;
+};

Since you have two locks there, can you describe what each lock protects?

Quoted 2 lines

Also, does occ_lock need to be a spin_lock? Looks like we're doing quite a lot with that held - can we change to a mutex instead?

Yes, we can probably use a mutex there. occ_lock controls access to the hardware, so clients don't step on each other. list_lock controls access to the list of xfrs when we're deleting and adding transfers. I'll add a comment.

Quoted 27 lines
Quoted 12 lines
drivers/fsi/Kconfig +11 -0
+struct occ_xfr;
+
+enum {
+	CLIENT_NONBLOCKING,
+};
+
+struct occ_client {
+	struct occ *occ;
+	struct occ_xfr *xfr;
+	spinlock_t lock;
+	wait_queue_head_t wait;

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+	size_t read_offset;
+	unsigned long flags;
+};
+
+enum {
+	XFR_IN_PROGRESS,
+	XFR_COMPLETE,
+	XFR_CANCELED,
+	XFR_WAITING,
+};

This looks like a set of states; but you're storing them in a bitmask (so, you can have multiple states asserted at the same time). Is that necessary, or are these mutually exclusive (which would be much simple to follow)?

Quoted 3 lines

If we do need to support multiple states, could you add a comment here indicating which combinations are valid, and the transitions between states?

XFR_WAITING can be combined with the others, and I'm planning another flag that will be in combination. I'll add a comment.

Quoted 48 lines

drivers/fsi/Kconfig

Quoted 11 lines
drivers/fsi/Kconfig +10 -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;

This is probably redundant, as you're (correctly) using copy_to_user below.

drivers/fsi/Kconfig

Quoted 12 lines
drivers/fsi/Kconfig +12 -0
+
+	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;

Super minor nit: closing curly brace and 'else' go on the same line.

Quoted 8 lines
drivers/fsi/Kconfig +8 -0
+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))

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+		return -EFAULT;
+
+	if (len > OCC_SRAM_BYTES)
+		return -EINVAL;
+
+	spin_lock_irq(&client->lock);
+	if (client->xfr) {
+		rc = -EDEADLK;
+		goto done;
+	}

I think -EBUSY would be better here.

Quoted 7 lines

In general, it looks like the code supports two separate processes performing independent writes. In that case, you already have the correct sequencing in the interface to the OCC, could we possibly support multiple queued writes on the one client? Assuming reads() return the responses in the order that they were written...

Or is there no point in doing that?

The idea was to allow multiple clients on multiple devices to do their thing and get their results in the right order. I could try and figure out multiple transfers pending on one client, but I couldn't quite see a use case for it. I'll think about it a bit more.

Quoted 9 lines

drivers/fsi/Kconfig

Quoted 7 lines
drivers/fsi/Kconfig +6 -0
+
+	xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
+	if (!xfr) {
+		rc = -ENOMEM;
+		goto done;
+	}

Do we need to allocate xfr on every read/write? Could we just allocate this as part of struct occfifo_client, and re-use that?

Yea if we stick with one xfr per client, that make more sense.

Quoted 15 lines

drivers/fsi/Kconfig

Quoted 12 lines
drivers/fsi/Kconfig +11 -0
+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);

No endianness concerns there? I haven't had a thorough look at the sbefifo_drv_read/_write API, do they define an endianness for the buffers?

sbefifo driver just passes the data through to the actual SBE, which expects data in big endian.

Quoted 17 lines

drivers/fsi/Kconfig

Quoted 13 lines
drivers/fsi/Kconfig +12 -0
+
+	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;
+	}

Using a compound statements for only one (but not all) branches of an conditional expression freaks me out a bit, mainly from the bugs that have arisen from subsequent changes to the code.

Quoted 8 lines

Also, you're not passing on negative rc values to the caller?

drivers/fsi/Kconfig

Quoted 2 lines
drivers/fsi/Kconfig +2 -0
+static int occ_putscom_u32(struct device *sbefifo, u32 address, u32 data0,
+			   u32 data1)

Does that make sense to split into two u32s, instead of a single u64?

I know a some of the existing scom interfaces split it up, but is there a particular reason we need to carry that over to this code?

Mostly due to the endian swapping, SBE expects things in words for some reason, so we have to swap them in 32 bit chunks.

Quoted 38 lines

drivers/fsi/Kconfig

Quoted 9 lines
drivers/fsi/Kconfig +8 -0
+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");

This probably isn't necessary; the presence of the device itself is a pretty good indication that we probed one (and you have a log on the error case).

Quoted 12 lines
drivers/fsi/Kconfig +12 -0
+	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", &reg);

drivers/fsi/Kconfig

Quoted 10 lines
drivers/fsi/Kconfig +10 -0
+		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);

We'll need a binding document for this. What is the reg property representing, hardware-wise?

Quoted 5 lines

Traditionally, reg is the address of the device on the parent bus, and so it'd totally find to have multiple devices in the system to share reg values (provided they're on different busses). So, I'm not sure that this is the usage that we want here; perhaps another property name would be better...

reg just represents the processor index. Maybe "proc"?

Quoted 14 lines

drivers/fsi/Kconfig

Quoted 11 lines
drivers/fsi/Kconfig +10 -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) {

Do you ever hit this code? I'd have assumed that ->remove is going to be called once all open file descriptors have gone, and your ->release should have emptied the transfer lists at that point...

Good to know, I don't think this ever gets used, I was just worried about FSI going away and removing everything mid-transfer.

Thanks,
Eddie

Quoted 6 lines

Cheers,

Jeremy

#5 lore raw

PATCH 3/3 dts: aspeed: witherspoon: Add FSI devices

In reply to Eddie James

From: "Edward A. James" <[email protected]>

---
 arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts | 46 ++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +46 -0
index f20aaf4..4138c93 100644
@@ -65,6 +65,8 @@
 
 	gpio-fsi {
 		compatible = "fsi-master-gpio", "fsi-master";
+		#address-cells = <2>;
+		#size-cells = <0>;
 
 		status = "okay";
 
@@ -73,6 +75,50 @@
 		mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>;
 		enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>;
 		trans-gpios = <&gpio ASPEED_GPIO(R, 2) GPIO_ACTIVE_HIGH>;
+
+		cfam@0,0 {
+			reg = <0 0>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			sbefifo@2400 {
+				compatible = "ibm,p9-sbefifo";
+				reg = <0x2400 0x400>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				occ@1 {
+					compatible = "ibm,p9-occ";
+					reg = <1>;
+				};
+			};
+
+			hub@3400 {
+				compatible = "fsi-master-hub";
+				reg = <0x3400 0x400>;
+				#address-cells = <2>;
+				#size-cells = <0>;
+
+				cfam@1,0 {
+					reg = <1 0>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					sbefifo@2400 {
+						compatible = "ibm,p9-sbefifo";
+						reg = <0x2400 0x400>;
+						#address-cells = <1>;
+						#size-cells = <0>;
+
+						occ@2 {
+							compatible =
+								"ibm,p9-occ";
+							reg = <2>;
+						};
+					};
+				};
+			};
+		};
 	};
 
 	iio-hwmon {
Signature · 1 line
1.8.3.1
maintainer
#6 lore raw

Re: [PATCH linux dev-4.10 3/3] dts: aspeed: witherspoon: Add FSI devices

Quoted 21 lines · Eddie James

From: "Edward A. James" <[email protected]>

---
 arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts | 46 ++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +2 -0
index f20aaf4..4138c93 100644
@@ -65,6 +65,8 @@
 
        gpio-fsi {
                compatible = "fsi-master-gpio", "fsi-master";
+               #address-cells = <2>;
+               #size-cells = <0>;
 
                status = "okay";
 

arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts line 82

Quoted 8 lines
arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +5 -0
@@ -73,6 +75,50 @@
                mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>;
                enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>;
                trans-gpios = <&gpio ASPEED_GPIO(R, 2) GPIO_ACTIVE_HIGH>;
+
+               cfam@0,0 {
+                       reg = <0 0>;
+                       #address-cells = <1>;
+                       #size-cells = <1>;

Since this snippet will be shared by all power9 machines, I suggest we put it in a dtsi file that is included by them all.

I will resend with my proposal.

Cheers,

Joel

Quoted 45 lines
arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +39 -1
+
+                       sbefifo@2400 {
+                               compatible = "ibm,p9-sbefifo";
+                               reg = <0x2400 0x400>;
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+
+                               occ@1 {
+                                       compatible = "ibm,p9-occ";
+                                       reg = <1>;
+                               };
+                       };
+
+                       hub@3400 {
+                               compatible = "fsi-master-hub";
+                               reg = <0x3400 0x400>;
+                               #address-cells = <2>;
+                               #size-cells = <0>;
+
+                               cfam@1,0 {
+                                       reg = <1 0>;
+                                       #address-cells = <1>;
+                                       #size-cells = <1>;
+
+                                       sbefifo@2400 {
+                                               compatible = "ibm,p9-sbefifo";
+                                               reg = <0x2400 0x400>;
+                                               #address-cells = <1>;
+                                               #size-cells = <0>;
+
+                                               occ@2 {
+                                                       compatible =
+                                                               "ibm,p9-occ";
+                                                       reg = <2>;
+                                               };
+                                       };
+                               };
+                       };
+               };
        };
 
        iio-hwmon {
--

1.8.3.1