[PATCH v3 linux dev-4.13 2/3] fsi/sbefifo: Add driver for the SBE FIFO

Andrew Jeffery andrew at aj.id.au
Mon May 21 13:46:55 AEST 2018


On Fri, 18 May 2018, at 11:04, Benjamin Herrenschmidt wrote:
> This driver provides an in-kernel and a user API for accessing
> the command FIFO of the SBE (Self Boot Engine) of the POWER9
> processor, via the FSI bus.
> 
> It provides an in-kernel interface to submit command and receive
> responses, along with a helper to locate and analyse the response
> status block. It's a simple synchronous submit() type API.
> 
> The user interface uses the write/read interface that an earlier
> version of this driver already provided, however it has some
> specific limitations in order to keep the driver simple and
> avoid using up a lot of kernel memory:
> 
>  - The user should perform a single write() with the command and
>    a single read() to get the response (with a buffer big enough
>    to hold the entire response).
> 
>  - On a write() the command is simply "stored" into a kernel buffer,
>    it is submitted as one operation on the subsequent read(). This
>    allows to have the code write directly from the FIFO into the user
>    buffer and avoid hogging the SBE between the write() and read()
>    syscall as it's critical that the SBE be freed asap to respond
>    to the host. An extra write() will simply replace the previously
>    written command.
> 
>  - A write of a single 4 bytes containing the value 0x52534554
>    in big endian will trigger a reset request. No read is necessary,
>    the write() call will return when the reset has been acknowledged
>    or times out.
> 
>  - The command is limited to 4K bytes.
> 
> Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
> ---
> 
> v2. Return -EAGAIN on read() when there's no pending message as
>     to be compatible with existing userspace (cronus) expectations
> 
> v3. Don't print "Initial HW cleanup failed" when the error is
>     -ESHUTDOWN (is the host is not booted).
> ---
>  drivers/fsi/Makefile        |   2 +-
>  drivers/fsi/fsi-sbefifo.c   | 867 ++++++++++++++++++++++++++++++++++++
>  include/linux/fsi-sbefifo.h |  26 ++
>  3 files changed, 894 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/fsi/fsi-sbefifo.c
>  create mode 100644 include/linux/fsi-sbefifo.h
> 
> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
> index 61e8e420c7ed..f331e929423c 100644
> --- a/drivers/fsi/Makefile
> +++ b/drivers/fsi/Makefile
> @@ -3,5 +3,5 @@ obj-$(CONFIG_FSI) += fsi-core.o
>  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_FSI_SBEFIFO) += fsi-sbefifo.o
>  #obj-$(CONFIG_FSI_OCC) += fsi-occ.o
> diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c
> new file mode 100644
> index 000000000000..f78e51a959bf
> --- /dev/null
> +++ b/drivers/fsi/fsi-sbefifo.c
> @@ -0,0 +1,867 @@
> +/*
> + * 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.
> + */

Should be SPDX-style?

> +
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/fs.h>
> +#include <linux/fsi.h>
> +#include <linux/fsi-sbefifo.h>
> +#include <linux/idr.h>
> +#include <linux/kernel.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/delay.h>
> +
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/sbefifo.h>
> +
> +/*
> + * The SBEFIFO is a pipe-like FSI device for communicating with
> + * the self boot engine on POWER processors.
> + */
> +
> +#define DEVICE_NAME		"sbefifo"
> +#define FSI_ENGID_SBE		0x22
> +
> +/*
> + * Register layout
> + */
> +
> +/* Register banks */
> +#define SBEFIFO_UP		0x00		/* FSI -> Host */
> +#define SBEFIFO_DOWN		0x40		/* Host -> FSI */
> +
> +/* Per-bank registers */
> +#define SBEFIFO_FIFO		0x00		/* The FIFO itself */
> +#define SBEFIFO_STS		0x04		/* Status register */
> +#define   SBEFIFO_STS_PARITY_ERR	0x20000000
> +#define   SBEFIFO_STS_RESET_REQ		0x02000000
> +#define   SBEFIFO_STS_GOT_EOT		0x00800000
> +#define   SBEFIFO_STS_MAX_XFER_LIMIT	0x00400000
> +#define   SBEFIFO_STS_FULL		0x00200000
> +#define   SBEFIFO_STS_EMPTY		0x00100000
> +#define   SBEFIFO_STS_ECNT_MASK		0x000f0000
> +#define   SBEFIFO_STS_ECNT_SHIFT	16
> +#define   SBEFIFO_STS_VALID_MASK	0x0000ff00
> +#define   SBEFIFO_STS_VALID_SHIFT	8
> +#define   SBEFIFO_STS_EOT_MASK		0x000000ff
> +#define   SBEFIFO_STS_EOT_SHIFT		0
> +#define SBEFIFO_EOT_RAISE	0x08		/* (Up only) Set End Of Transfer */
> +#define SBEFIFO_REQ_RESET	0x0C		/* (Up only) Reset Request */
> +#define SBEFIFO_PERFORM_RESET	0x10		/* (Down only) Perform Reset */
> +#define SBEFIFO_EOT_ACK		0x14		/* (Down only) Acknowledge EOT */
> +#define SBEFIFO_DOWN_MAX	0x18		/* (Down only) Max transfer */
> +
> +/* CFAM GP Mailbox SelfBoot Message register */
> +#define CFAM_GP_MBOX_SBM_ADDR	0x2824	/* Converted 0x2809 */
> +
> +#define CFAM_SBM_SBE_BOOTED		0x80000000
> +#define CFAM_SBM_SBE_STATE_MASK		0x00f00000
> +#define CFAM_SBM_SBE_STATE_SHIFT	20
> +
> +enum sbe_state
> +{
> +	SBE_STATE_UNKNOWN = 0x0, // Unkown, initial state
> +	SBE_STATE_IPLING  = 0x1, // IPL'ing - autonomous mode (transient)
> +	SBE_STATE_ISTEP   = 0x2, // ISTEP - Running IPL by steps (transient)
> +	SBE_STATE_MPIPL   = 0x3, // MPIPL
> +	SBE_STATE_RUNTIME = 0x4, // SBE Runtime
> +	SBE_STATE_DMT     = 0x5, // Dead Man Timer State (transient)
> +	SBE_STATE_DUMP    = 0x6, // Dumping
> +	SBE_STATE_FAILURE = 0x7, // Internal SBE failure
> +	SBE_STATE_QUIESCE = 0x8, // Final state - needs SBE reset to get out
> +};
> +
> +/* FIFO depth */
> +#define SBEFIFO_FIFO_DEPTH		8
> +
> +/* Helpers */
> +#define sbefifo_empty(sts)	((sts) & SBEFIFO_STS_EMPTY)
> +#define sbefifo_full(sts)	((sts) & SBEFIFO_STS_FULL)
> +#define sbefifo_parity_err(sts)	((sts) & SBEFIFO_STS_PARITY_ERR)
> +#define sbefifo_populated(sts)	(((sts) & SBEFIFO_STS_ECNT_MASK) >> 
> SBEFIFO_STS_ECNT_SHIFT)
> +#define sbefifo_vacant(sts)	(SBEFIFO_FIFO_DEPTH - 
> sbefifo_populated(sts))
> +#define sbefifo_eot_set(sts)	(((sts) & SBEFIFO_STS_EOT_MASK) >> 
> SBEFIFO_STS_EOT_SHIFT)
> +
> +/* Reset request timeout in ms */
> +#define SBEFIFO_RESET_TIMEOUT		10000
> +
> +/* Timeouts for commands in ms */
> +#define SBEFIFO_TIMEOUT_START_CMD	10000
> +#define SBEFIFO_TIMEOUT_IN_CMD		1000
> +#define SBEFIFO_TIMEOUT_START_RSP	10000
> +#define SBEFIFO_TIMEOUT_IN_RSP		1000
> +
> +/* Other constants */
> +#define SBEFIFO_MAX_CMD_LEN		PAGE_SIZE
> +#define SBEFIFO_RESET_MAGIC		0x52534554 /* "RSET" */
> +
> +struct sbefifo {
> +	uint32_t		magic;
> +#define SBEFIFO_MAGIC		0x53424546 /* "SBEF" */
> +	struct fsi_device	*fsi_dev;
> +	struct miscdevice	mdev;
> +	struct mutex		lock;
> +	char			name[32];
> +	int			idx;
> +	bool			broken;
> +};
> +
> +struct sbefifo_user {
> +	struct sbefifo		*sbefifo;
> +	struct mutex		file_lock;
> +	void			*pending_cmd;
> +	size_t			pending_len;
> +};
> +
> +static DEFINE_IDA(sbefifo_ida);
> +
> +static int sbefifo_regr(struct sbefifo *sbefifo, int reg, u32 *word)
> +{
> +	__be32 raw_word;
> +	int rc;
> +
> +	rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word,
> +			     sizeof(raw_word));
> +	if (rc)
> +		return rc;
> +
> +	*word = be32_to_cpu(raw_word);
> +
> +	return 0;
> +}
> +
> +static int sbefifo_regw(struct sbefifo *sbefifo, int reg, u32 word)
> +{
> +	__be32 raw_word = cpu_to_be32(word);
> +
> +	return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word,
> +				sizeof(raw_word));
> +}
> +
> +static int sbefifo_get_sbe_state(struct sbefifo *sbefifo)

This isn't so much getting the state as ensuring it's in a usable state, or it's conflating error codes with the SBE state. Maybe sbefifo_is_ready()?

> +{
> +	__be32 raw_word;
> +	u32 sbm;
> +	int rc;
> +
> +	rc = fsi_slave_read(sbefifo->fsi_dev->slave, CFAM_GP_MBOX_SBM_ADDR,
> +			    &raw_word, sizeof(raw_word));
> +	if (rc)
> +		return rc;
> +	sbm = be32_to_cpu(raw_word);
> +
> +	/* SBE booted at all ? */
> +	if (!(sbm & CFAM_SBM_SBE_BOOTED))
> +		return -ESHUTDOWN;
> +
> +	/* Check its state */
> +	switch ((sbm & CFAM_SBM_SBE_STATE_MASK) >> CFAM_SBM_SBE_STATE_SHIFT) {
> +	case SBE_STATE_UNKNOWN:
> +		return -ESHUTDOWN;
> +	case SBE_STATE_IPLING:
> +	case SBE_STATE_ISTEP:
> +	case SBE_STATE_MPIPL:
> +	case SBE_STATE_DMT:
> +		return -EBUSY;
> +	case SBE_STATE_RUNTIME:
> +	case SBE_STATE_DUMP: /* Not sure about that one */
> +		break;
> +	case SBE_STATE_FAILURE:
> +	case SBE_STATE_QUIESCE:
> +		return -ESHUTDOWN;
> +	}
> +	return 0;
> +}
> +
> +/* Don't flip endianness of data to/from FIFO, just pass through. */
> +static int sbefifo_down_read(struct sbefifo *sbefifo, u32 *word)
> +{
> +	return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DOWN, word,
> +			       sizeof(*word));
> +}
> +
> +static int sbefifo_up_write(struct sbefifo *sbefifo, u32 word)
> +{
> +	return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word,
> +				sizeof(word));
> +}
> +
> +static int sbefifo_request_reset(struct sbefifo *sbefifo)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	u32 status, timeout;
> +	int rc;
> +
> +	dev_dbg(dev, "Requesting FIFO reset\n");
> +
> +	/* Mark broken first, will be cleared if reset succeeds */
> +	sbefifo->broken = true;
> +
> +	/* Send reset request */
> +	rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_REQ_RESET, 1);
> +	if (rc) {
> +		dev_err(dev, "Sending reset request failed, rc=%d\n", rc);
> +		return rc;
> +	}
> +
> +	/* Wait for it to complete */
> +	for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) {
> +		rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status);
> +		if (rc) {
> +			dev_err(dev, "Failed to read UP fifo status during reset"
> +				" , rc=%d\n", rc);
> +			return rc;
> +		}
> +
> +		if (!(status & SBEFIFO_STS_RESET_REQ)) {
> +			dev_dbg(dev, "FIFO reset done\n");
> +			sbefifo->broken = false;
> +			return 0;
> +		}
> +
> +		msleep(1);
> +	}
> +	dev_err(dev, "FIFO reset timed out\n");
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int sbefifo_cleanup_hw(struct sbefifo *sbefifo)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	u32 up_status, down_status;
> +	bool need_reset = false;
> +	int rc;
> +
> +	rc = sbefifo_get_sbe_state(sbefifo);
> +	if (rc) {
> +		dev_dbg(dev, "SBE state=%d\n", rc);
> +		return rc;
> +	}
> +
> +	/* If broken, we don't need to look at status, go straight to reset */
> +	if (sbefifo->broken)
> +		goto do_reset;
> +
> +	rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &up_status);
> +	if (rc) {
> +		dev_err(dev, "Cleanup: Reading UP status failed, rc=%d\n", rc);
> +
> +		/* Will try reset again on next attempt at using it */
> +		sbefifo->broken = true;
> +		return rc;
> +	}
> +
> +	rc = sbefifo_regr(sbefifo, SBEFIFO_DOWN | SBEFIFO_STS, &down_status);
> +	if (rc) {
> +		dev_err(dev, "Cleanup: Reading DOWN status failed, rc=%d\n", rc);
> +
> +		/* Will try reset again on next attempt at using it */
> +		sbefifo->broken = true;
> +		return rc;
> +	}
> +
> +	/* The FIFO already contains a reset request from the SBE ? */
> +	if (down_status & SBEFIFO_STS_RESET_REQ) {
> +		dev_info(dev, "Cleanup: FIFO reset request set, resetting\n");
> +		rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET);
> +		if (rc) {
> +			sbefifo->broken = true;
> +			dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc);
> +			return rc;
> +		}
> +		sbefifo->broken = false;
> +		return 0;
> +	}
> +
> +	/* Parity error on either FIFO ? */
> +	if ((up_status | down_status) & SBEFIFO_STS_PARITY_ERR)
> +		need_reset = true;
> +
> +	/* Either FIFO not empty ? */
> +	if (!((up_status & down_status) & SBEFIFO_STS_EMPTY))
> +		need_reset = true;
> +
> +	if (!need_reset)
> +		return 0;
> +
> +	dev_info(dev, "Cleanup: FIFO not clean (up=0x%08x down=0x%08x)\n",
> +		 up_status, down_status);
> +
> + do_reset:
> +
> +	/* Mark broken, will be cleared if/when reset succeeds */
> +	return sbefifo_request_reset(sbefifo);
> +}
> +
> +static int sbefifo_wait(struct sbefifo *sbefifo, bool up,
> +			u32 *status, unsigned long timeout)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	unsigned long end_time;
> +	bool ready = false;
> +	u32 addr, sts = 0;
> +	int rc;
> +
> +	dev_vdbg(dev, "Wait on %s fifo...\n", up ? "up" : "down");
> +
> +	addr = (up ? SBEFIFO_UP : SBEFIFO_DOWN) | SBEFIFO_STS;
> +
> +	end_time = jiffies + timeout;
> +	while (!time_after(jiffies, end_time)) {
> +		cond_resched();
> +		rc = sbefifo_regr(sbefifo, addr, &sts);
> +		if (rc < 0) {
> +			dev_err(dev, "FSI error %d reading status register\n", rc);
> +			return rc;
> +		}
> +		if (!up && sbefifo_parity_err(sts)) {
> +			dev_err(dev, "Parity error in DOWN FIFO\n");
> +			return -ENXIO;
> +		}
> +		ready = !(up ? sbefifo_full(sts) : sbefifo_empty(sts));
> +		if (ready)
> +			break;
> +	}
> +	if (!ready) {
> +		dev_err(dev, "%s FIFO Timeout ! status=%08x\n", up ? "UP" : "DOWN", 
> sts);
> +		return -ETIMEDOUT;
> +	}
> +	dev_vdbg(dev, "End of wait status: %08x\n", sts);
> +
> +	*status = sts;
> +
> +	return 0;
> +}
> +
> +static int sbefifo_send_command(struct sbefifo *sbefifo,
> +				const __be32 *command, size_t cmd_len)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	size_t len, chunk, vacant = 0, remaining = cmd_len;
> +	unsigned long timeout;
> +	u32 status;
> +	int rc;
> +
> +	dev_vdbg(dev, "sending command (%d words, cmd=%04x)\n",
> +		 cmd_len, be32_to_cpu(command[1]));
> +
> +	/* As long as there's something to send */
> +	timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_CMD);
> +	while (remaining) {
> +		/* Wait for room in the FIFO */
> +		rc = sbefifo_wait(sbefifo, true, &status, timeout);
> +		if (rc < 0)
> +			return rc;
> +		timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD);
> +
> +		vacant = sbefifo_vacant(status);
> +		len = chunk = min(vacant, remaining);
> +
> +		dev_vdbg(dev, "  status=%08x vacant=%d chunk=%d\n",
> +			 status, vacant, chunk);
> +
> +		/* Write as much as we can */
> +		while (len--) {
> +			rc = sbefifo_up_write(sbefifo, *(command++));
> +			if (rc) {
> +				dev_err(dev, "FSI error %d writing UP FIFO\n", rc);
> +				return rc;
> +			}
> +		}
> +		remaining -= chunk;
> +		vacant -= chunk;
> +	}
> +
> +	/* If there's no room left, wait for some to write EOT */
> +	if (!vacant) {
> +		rc = sbefifo_wait(sbefifo, true, &status, timeout);
> +		if (rc)
> +			return rc;
> +	}
> +
> +	/* Send an EOT */
> +	rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_EOT_RAISE, 0);
> +	if (rc)
> +		dev_err(dev, "FSI error %d writing EOT\n", rc);
> +	return rc;
> +}
> +
> +static int sbefifo_read_response(struct sbefifo *sbefifo,
> +				 __be32 __user *response, size_t *resp_len)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	u32 data, status, eot_set;
> +	size_t len, remaining;
> +	unsigned long timeout;
> +	bool overflow = false;
> +	int rc;
> +
> +	remaining = *resp_len;
> +	*resp_len = 0;
> +
> +	dev_vdbg(dev, "reading response, buflen = %d\n", remaining);
> +
> +	timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_RSP);
> +	for (;;) {
> +		/* Grab FIFO status (this will handle parity errors) */
> +		rc = sbefifo_wait(sbefifo, false, &status, timeout);
> +		if (rc < 0)
> +			return rc;
> +		timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_RSP);
> +
> +		/* Decode status */
> +		len = sbefifo_populated(status);
> +		eot_set = sbefifo_eot_set(status);
> +
> +		dev_vdbg(dev, "  chunk size %d eot_set=0x%x\n", len, eot_set);
> +
> +		/* Go through the chunk */
> +		while(len--) {
> +			/* Read the data */
> +			rc = sbefifo_down_read(sbefifo, &data);
> +			if (rc < 0)
> +				return rc;
> +
> +			/* Was it an EOT ? */
> +			if (eot_set & 0x80) {
> +				/*
> +				 * There should be nothing else in the FIFO,
> +				 * if there is, mark broken, this will force
> +				 * a reset on next use, but don't fail the
> +				 * command.
> +				 */
> +				if (len) {
> +					dev_warn(dev, "FIFO read hit"
> +						 " EOT with still %d data\n",
> +						 len);
> +					sbefifo->broken = true;
> +				}
> +
> +				/* We are done */
> +				rc = sbefifo_regw(sbefifo,
> +						  SBEFIFO_DOWN | SBEFIFO_EOT_ACK, 0);
> +
> +				/*
> +				 * If that write fail, still complete the request but mark
> +				 * the fifo as broken for subsequent reset (not much else
> +				 * we can do here).
> +				 */
> +				if (rc) {
> +					dev_err(dev, "FSI error %d ack'ing EOT\n", rc);
> +					sbefifo->broken = true;
> +				}
> +
> +				dev_vdbg(dev, "got %d words response%s\n", *resp_len,
> +					 overflow ? "with overflow !" : "");
> +
> +				/* Tell whether we overflowed */
> +				return overflow ? -EOVERFLOW : 0;
> +			}
> +
> +			/* Store it if there is room */
> +			if (remaining) {
> +				rc = put_user(data, response++);
> +				if (rc)
> +					return rc;
> +				remaining--;
> +				(*resp_len)++;
> +			} else
> +				overflow = true;
> +
> +			/* Next EOT bit */
> +			eot_set <<= 1;
> +		}
> +	}
> +	/* Shouldn't happen */
> +	return -EIO;
> +}
> +
> +static int __sbefifo_submit(struct sbefifo *sbefifo,
> +			    const __be32 *command, size_t cmd_len,
> +			    __be32 __user *response, size_t *resp_len)
> +{
> +	struct device *dev = &sbefifo->fsi_dev->dev;
> +	int rc;
> +
> +	if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) {
> +		dev_vdbg(dev, "Invalid command len %d (header: %d)\n",
> +			 cmd_len, be32_to_cpu(command[0]));
> +		return -EINVAL;
> +	}
> +
> +	/* First ensure the HW is in a clean state */
> +	rc = sbefifo_cleanup_hw(sbefifo);
> +	if (rc)
> +		return rc;
> +
> +	/* Try sending the command */
> +	rc = sbefifo_send_command(sbefifo, command, cmd_len);
> +	if (rc)
> +		goto fail;
> +
> +	/* Now, get the response */
> +	rc = sbefifo_read_response(sbefifo, response, resp_len);
> +	if (rc != 0 && rc != -EOVERFLOW)
> +		goto fail;
> +	return rc;
> + fail:
> +	/*
> +	 * On failure, attempt a reset. Ignore the result, it will mark
> +	 * the fifo broken if the reset fails
> +	 */
> +        sbefifo_request_reset(sbefifo);
> +
> +	/* Return original error */
> +	return rc;
> +}
> +
> +/**
> + * sbefifo_submit() - Submit and SBE fifo command and receive response
> + * @dev: The sbefifo device
> + * @command: The raw command data
> + * @cmd_len: The command size (in 32-bit words)
> + * @response: The output response buffer
> + * @resp_len: In: Response buffer size, Out: Response size
> + *
> + * This will perform the entire operation. If the reponse buffer
> + * overflows, returns -EOVERFLOW
> + */
> +int sbefifo_submit(struct device *dev, const __be32 *command, size_t 
> cmd_len,
> +		   __be32 *response, size_t *resp_len)
> +{
> +	struct sbefifo *sbefifo = dev_get_drvdata(dev);
> +	mm_segment_t old_fs;
> +	int rc;
> +
> +	if (!dev || !sbefifo)
> +		return -ENODEV;
> +	if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC))
> +		return -ENODEV;
> +	if (!resp_len || !command || !response || cmd_len > 
> SBEFIFO_MAX_CMD_LEN)
> +		return -EINVAL;
> +
> +	mutex_lock(&sbefifo->lock);
> +	old_fs = get_fs();
> +	set_fs(KERNEL_DS);
> +	rc = __sbefifo_submit(sbefifo, command, cmd_len,
> +			      response, resp_len);
> +	set_fs(old_fs);

Can you talk about why the get_fs()/set_fs()/set_fs() sequence is the right way to go here when there's been discussion in the past about removing the whole show?

https://lwn.net/Articles/722267/

> +	mutex_unlock(&sbefifo->lock);
> +	return rc;
> +}
> +EXPORT_SYMBOL_GPL(sbefifo_submit);
> +
> +int sbefifo_parse_status(u16 cmd, __be32 *response, size_t resp_len, 
> size_t *data_len)
> +{
> +	u32 dh, s0, s1;
> +
> +	if (resp_len < 3) {
> +		pr_debug("sbefifo: cmd %04x, response too small: %d\n",
> +			 cmd, resp_len);
> +		return -ENXIO;
> +	}
> +	dh = be32_to_cpu(response[resp_len - 1]);
> +	if (dh > resp_len || dh < 3) {
> +		pr_debug("sbefifo: cmd %04x, status offset out of range: %d/%d\n",
> +			 cmd, dh, resp_len);
> +		return -ENXIO;
> +	}
> +	s0 = be32_to_cpu(response[resp_len - dh]);
> +	s1 = be32_to_cpu(response[resp_len - dh + 1]);
> +	if (((s0 >> 16) != 0xC0DE) || ((s0 & 0xffff) != cmd)) {
> +		pr_debug("sbefifo: cmd %04x, status signature invalid: 0x%08x 0x%08x
> \n",
> +			 cmd, s0, s1);
> +		return -ENXIO;
> +	}
> +	if (s1 != 0) {
> +		pr_debug("sbefifo: cmd %04x, error status: prim=%04x sec=%04x\n",
> +			 cmd, s1 >> 16, s1 & 0xffff);
> +	}
> +	if (data_len)
> +		*data_len = resp_len - dh;
> +
> +	/*
> +	 * Primary status don't have the top bit set, so can't be confused with
> +	 * Linux negative error codes, so return the status word whole.
> +	 */
> +	return s1;
> +}
> +EXPORT_SYMBOL_GPL(sbefifo_parse_status);
> +
> +/*
> + * Char device interface
> + */
> +static int sbefifo_user_open(struct inode *inode, struct file *file)
> +{
> +	struct sbefifo *sbefifo = container_of(file->private_data,
> +					       struct sbefifo, mdev);
> +	struct sbefifo_user *user;
> +
> +	user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL);
> +	if (!user)
> +		return -ENOMEM;
> +
> +	file->private_data = user;
> +	user->sbefifo = sbefifo;
> +	user->pending_cmd = (void *)__get_free_page(GFP_KERNEL);
> +	if (!user->pending_cmd) {
> +		kfree(user);
> +		return -ENOMEM;
> +	}
> +	mutex_init(&user->file_lock);
> +
> +	return 0;
> +}
> +
> +static ssize_t sbefifo_user_read(struct file *file, char __user *buf,
> +				 size_t len, loff_t *offset)
> +{
> +	struct sbefifo_user *user = file->private_data;
> +	struct sbefifo *sbefifo;
> +	size_t clen, rlen;
> +	int rc;
> +
> +	if (!user)
> +		return -EINVAL;
> +	sbefifo = user->sbefifo;
> +	if (len & 3)
> +		return -EINVAL;
> +
> +	mutex_lock(&user->file_lock);
> +
> +	/* Cronus relies on -EAGAIN after a short read */
> +	if (user->pending_len == 0) {
> +		rc = -EAGAIN;
> +		goto bail;
> +	}
> +	if (user->pending_len < 8) {
> +		rc = -EINVAL;
> +		goto bail;
> +	}
> +	mutex_lock(&sbefifo->lock);
> +	clen = user->pending_len >> 2;
> +	rlen = len >> 2;
> +	rc = __sbefifo_submit(sbefifo, user->pending_cmd, clen,
> +			      (__be32 __user *)buf, &rlen);
> +	mutex_unlock(&sbefifo->lock);
> +	if (rc < 0)
> +		goto bail;
> +	rc = rlen << 2;
> + bail:
> +	user->pending_len = 0;
> +	mutex_unlock(&user->file_lock);
> +	return rc;
> +}
> +
> +static ssize_t sbefifo_user_write(struct file *file, const char __user 
> *buf,
> +				  size_t len, loff_t *offset)
> +{
> +	struct sbefifo_user *user = file->private_data;
> +	struct sbefifo *sbefifo;
> +	int rc = len;
> +
> +	if (!user)
> +		return -EINVAL;
> +	sbefifo = user->sbefifo;
> +	if (len > SBEFIFO_MAX_CMD_LEN)
> +		return -EINVAL;
> +	if (len & 3)
> +		return -EINVAL;
> +
> +	mutex_lock(&user->file_lock);
> +
> +	/* Copy the command into the staging buffer */
> +	if (copy_from_user(user->pending_cmd, buf, len)) {
> +		rc = -EFAULT;
> +		goto bail;
> +	}
> +
> +	/* Check for the magic reset command */
> +	if (len == 4 && be32_to_cpu(*(__be32 *)user->pending_cmd) ==
> +	    SBEFIFO_RESET_MAGIC)  {
> +
> +		/* Clear out any pending command */
> +		user->pending_len = 0;
> +
> +		/* Trigger reset request */
> +		mutex_lock(&sbefifo->lock);
> +		rc = sbefifo_request_reset(user->sbefifo);
> +		mutex_unlock(&sbefifo->lock);
> +		if (rc == 0)
> +			rc = 4;
> +		goto bail;
> +	}
> +
> +	/* Update the staging buffer size */
> +	user->pending_len = len;
> + bail:
> +	mutex_unlock(&user->file_lock);
> +
> +	/* And that's it, we'll issue the command on a read */
> +	return rc;
> +}
> +
> +static int sbefifo_user_release(struct inode *inode, struct file *file)
> +{
> +	struct sbefifo_user *user = file->private_data;
> +
> +	if (!user)
> +		return -EINVAL;
> +
> +	free_page((unsigned long)user->pending_cmd);
> +	kfree(user);
> +
> +	return 0;
> +}
> +
> +static const struct file_operations sbefifo_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= sbefifo_user_open,
> +	.read		= sbefifo_user_read,
> +	.write		= sbefifo_user_write,
> +	.release	= sbefifo_user_release,
> +};
> +
> +/*
> + * Probe/remove
> + */
> +
> +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];
> +	int rc, child_idx = 0;
> +
> +	dev_dbg(dev, "Found sbefifo device\n");
> +
> +	sbefifo = devm_kzalloc(dev, sizeof(*sbefifo), GFP_KERNEL);
> +	if (!sbefifo)
> +		return -ENOMEM;
> +	sbefifo->magic = SBEFIFO_MAGIC;
> +	sbefifo->fsi_dev = fsi_dev;
> +	mutex_init(&sbefifo->lock);
> +
> +	/*
> +	 * Try cleaning up the FIFO. If this fails, we still register the
> +	 * driver and will try cleaning things up again on the next access.
> +	 */
> +	rc = sbefifo_cleanup_hw(sbefifo);
> +	if (rc && rc != -ESHUTDOWN)
> +		dev_err(dev, "Initial HW cleanup failed, will retry later\n");
> +
> +	sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL);
> +	snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d",
> +		 sbefifo->idx);
> +
> +	dev_set_drvdata(dev, sbefifo);
> +
> +	/* Create misc chardev for userspace access */
> +	sbefifo->mdev.minor = MISC_DYNAMIC_MINOR;
> +	sbefifo->mdev.fops = &sbefifo_fops;
> +	sbefifo->mdev.name = sbefifo->name;
> +	sbefifo->mdev.parent = dev;
> +	rc = misc_register(&sbefifo->mdev);
> +	if (rc) {
> +		dev_err(dev, "Failed to register miscdevice: %d\n", rc);
> +		ida_simple_remove(&sbefifo_ida, sbefifo->idx);
> +		return rc;
> +	}
> +
> +	/* Create platform devs for dts child nodes (occ, etc) */
> +	for_each_available_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(dev, "failed to create child %s dev\n",
> +				 child_name);
> +	}
> +
> +	return 0;
> +}
> +
> +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_remove(struct device *dev)
> +{
> +	struct sbefifo *sbefifo = dev_get_drvdata(dev);
> +
> +	dev_dbg(dev, "Removing sbefifo device...\n");
> +
> +	misc_deregister(&sbefifo->mdev);
> +	device_for_each_child(dev, NULL, sbefifo_unregister_child);
> +
> +	ida_simple_remove(&sbefifo_ida, sbefifo->idx);
> +
> +	return 0;
> +}
> +
> +static struct fsi_device_id sbefifo_ids[] = {
> +	{
> +		.engine_type = FSI_ENGID_SBE,
> +		.version = FSI_VERSION_ANY,
> +	},
> +	{ 0 }
> +};
> +
> +static struct fsi_driver sbefifo_drv = {
> +	.id_table = sbefifo_ids,
> +	.drv = {
> +		.name = DEVICE_NAME,
> +		.bus = &fsi_bus_type,
> +		.probe = sbefifo_probe,
> +		.remove = sbefifo_remove,
> +	}
> +};
> +
> +static int sbefifo_init(void)
> +{
> +	return fsi_driver_register(&sbefifo_drv);
> +}
> +
> +static void sbefifo_exit(void)
> +{
> +	fsi_driver_unregister(&sbefifo_drv);
> +
> +	ida_destroy(&sbefifo_ida);
> +}
> +
> +module_init(sbefifo_init);
> +module_exit(sbefifo_exit);
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Brad Bishop <bradleyb at fuzziesquirrel.com>");
> +MODULE_AUTHOR("Eddie James <eajames at linux.vnet.ibm.com>");
> +MODULE_AUTHOR("Andrew Jeffery <andrew at aj.id.au>");
> +MODULE_AUTHOR("Benjamin Herrenschmidt <benh at kernel.crashing.org>");
> +MODULE_DESCRIPTION("Linux device interface to the POWER Self Boot 
> Engine");
> diff --git a/include/linux/fsi-sbefifo.h b/include/linux/fsi-sbefifo.h
> new file mode 100644
> index 000000000000..15729b3194a2
> --- /dev/null
> +++ b/include/linux/fsi-sbefifo.h
> @@ -0,0 +1,26 @@
> +/*
> + * 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 LINUX_FSI_SBEFIFO_H
> +#define LINUX_FSI_SBEFIFO_H
> +
> +struct device;
> +
> +int sbefifo_submit(struct device *dev, const __be32 *command, size_t 
> cmd_len,
> +		   __be32 *response, size_t *resp_len);
> +
> +int sbefifo_parse_status(u16 cmd, __be32 *response, size_t resp_len, 
> size_t *data_len);
> +
> +#endif /* LINUX_FSI_SBEFIFO_H */
> -- 
> 2.17.0
> 


More information about the openbmc mailing list