Commit b1f4c00e authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman
Browse files

Merge tag 'fsi-for-v5.16' of...

Merge tag 'fsi-for-v5.16' of git://git.kernel.org/pub/scm/linux/kernel/git/joel/fsi into char-misc-next

Joel writes:

FSI changes for v5.16

 - SBEFIFO usersapce interfaces to perform FFDC (First Failure
   Data Capture) and detect timeouts

 - A fix to handle multiple messages in flight

* tag 'fsi-for-v5.16' of git://git.kernel.org/pub/scm/linux/kernel/git/joel/fsi:
  fsi: sbefifo: Use interruptible mutex locking
  fsi: sbefifo: Add sysfs file indicating a timeout error
  docs: ABI: testing: Document the SBEFIFO timeout interface
  hwmon: (occ) Provide the SBEFIFO FFDC in binary sysfs
  docs: ABI: testing: Document the OCC hwmon FFDC binary interface
  fsi: occ: Store the SBEFIFO FFDC in the user response buffer
  fsi: occ: Use a large buffer for responses
  hwmon: (occ) Remove sequence numbering and checksum calculation
  fsi: occ: Force sequence numbering per OCC
parents 8ac33b8b 7cc2f34e
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
What:		/sys/bus/fsi/devices/XX.XX.00:06/sbefifoX/timeout
KernelVersion:	5.15
Contact:	eajames@linux.ibm.com
Description:
		Indicates whether or not this SBE device has experienced a
		timeout; i.e. the SBE did not respond within the time allotted
		by the driver. A value of 1 indicates that a timeout has
		ocurred and no transfers have completed since the timeout. A
		value of 0 indicates that no timeout has ocurred, or if one
		has, more recent transfers have completed successful.
+13 −0
Original line number Diff line number Diff line
What:		/sys/bus/platform/devices/occ-hwmon.X/ffdc
KernelVersion:	5.15
Contact:	eajames@linux.ibm.com
Description:
		Contains the First Failure Data Capture from the SBEFIFO
		hardware, if there is any from a previous transfer. Otherwise,
		the file is empty. The data is cleared when it's been
		completely read by a user. As the name suggests, only the data
		from the first error is saved, until it's cleared upon read. The OCC hwmon driver, running on
		a Baseboard Management Controller (BMC), communicates with
		POWER9 and up processors over the Self-Boot Engine (SBE) FIFO.
		In many error conditions, the SBEFIFO will return error data
		indicating the type of error and system state, etc.
+123 −95
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/fsi-occ.h>
@@ -33,13 +34,6 @@

#define OCC_P10_SRAM_MODE	0x58	/* Normal mode, OCB channel 2 */

/*
 * Assume we don't have much FFDC, if we do we'll overflow and
 * fail the command. This needs to be big enough for simple
 * commands as well.
 */
#define OCC_SBE_STATUS_WORDS	32

#define OCC_TIMEOUT_MS		1000
#define OCC_CMD_IN_PRG_WAIT_MS	50

@@ -50,6 +44,11 @@ struct occ {
	struct device *sbefifo;
	char name[32];
	int idx;
	u8 sequence_number;
	void *buffer;
	void *client_buffer;
	size_t client_buffer_size;
	size_t client_response_size;
	enum versions version;
	struct miscdevice mdev;
	struct mutex occ_lock;
@@ -141,8 +140,7 @@ static ssize_t occ_write(struct file *file, const char __user *buf,
{
	struct occ_client *client = file->private_data;
	size_t rlen, data_length;
	u16 checksum = 0;
	ssize_t rc, i;
	ssize_t rc;
	u8 *cmd;

	if (!client)
@@ -156,9 +154,6 @@ static ssize_t occ_write(struct file *file, const char __user *buf,
	/* Construct the command */
	cmd = client->buffer;

	/* Sequence number (we could increment and compare with response) */
	cmd[0] = 1;

	/*
	 * Copy the user command (assume user data follows the occ command
	 * format)
@@ -178,14 +173,7 @@ static ssize_t occ_write(struct file *file, const char __user *buf,
		goto done;
	}

	/* Calculate checksum */
	for (i = 0; i < data_length + 4; ++i)
		checksum += cmd[i];

	cmd[data_length + 4] = checksum >> 8;
	cmd[data_length + 5] = checksum & 0xFF;

	/* Submit command */
	/* Submit command; 4 bytes before the data and 2 bytes after */
	rlen = PAGE_SIZE;
	rc = fsi_occ_submit(client->occ->dev, cmd, data_length + 6, cmd,
			    &rlen);
@@ -223,6 +211,22 @@ static const struct file_operations occ_fops = {
	.release = occ_release,
};

static void occ_save_ffdc(struct occ *occ, __be32 *resp, size_t parsed_len,
			  size_t resp_len)
{
	if (resp_len > parsed_len) {
		size_t dh = resp_len - parsed_len;
		size_t ffdc_len = (dh - 1) * 4; /* SBE words are four bytes */
		__be32 *ffdc = &resp[parsed_len];

		if (ffdc_len > occ->client_buffer_size)
			ffdc_len = occ->client_buffer_size;

		memcpy(occ->client_buffer, ffdc, ffdc_len);
		occ->client_response_size = ffdc_len;
	}
}

static int occ_verify_checksum(struct occ *occ, struct occ_response *resp,
			       u16 data_length)
{
@@ -251,8 +255,10 @@ static int occ_verify_checksum(struct occ *occ, struct occ_response *resp,
static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
{
	u32 data_len = ((len + 7) / 8) * 8;	/* must be multiples of 8 B */
	size_t cmd_len, resp_len, resp_data_len;
	__be32 *resp, cmd[6];
	size_t cmd_len, parsed_len, resp_data_len;
	size_t resp_len = OCC_MAX_RESP_WORDS;
	__be32 *resp = occ->buffer;
	__be32 cmd[6];
	int idx = 0, rc;

	/*
@@ -279,21 +285,22 @@ static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
	cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM);
	cmd[4 + idx] = cpu_to_be32(data_len);

	resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS;
	resp = kzalloc(resp_len << 2, GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len);
	if (rc)
		goto free;
		return rc;

	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_GET_OCC_SRAM,
				  resp, resp_len, &resp_len);
	if (rc)
		goto free;
				  resp, resp_len, &parsed_len);
	if (rc > 0) {
		dev_err(occ->dev, "SRAM read returned failure status: %08x\n",
			rc);
		occ_save_ffdc(occ, resp, parsed_len, resp_len);
		return -ECOMM;
	} else if (rc) {
		return rc;
	}

	resp_data_len = be32_to_cpu(resp[resp_len - 1]);
	resp_data_len = be32_to_cpu(resp[parsed_len - 1]);
	if (resp_data_len != data_len) {
		dev_err(occ->dev, "SRAM read expected %d bytes got %zd\n",
			data_len, resp_data_len);
@@ -302,37 +309,21 @@ static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
		memcpy(data, resp, len);
	}

free:
	/* Convert positive SBEI status */
	if (rc > 0) {
		dev_err(occ->dev, "SRAM read returned failure status: %08x\n",
			rc);
		rc = -EBADMSG;
	}

	kfree(resp);
	return rc;
}

static int occ_putsram(struct occ *occ, const void *data, ssize_t len)
static int occ_putsram(struct occ *occ, const void *data, ssize_t len,
		       u8 seq_no, u16 checksum)
{
	size_t cmd_len, buf_len, resp_len, resp_data_len;
	u32 data_len = ((len + 7) / 8) * 8;	/* must be multiples of 8 B */
	__be32 *buf;
	size_t cmd_len, parsed_len, resp_data_len;
	size_t resp_len = OCC_MAX_RESP_WORDS;
	__be32 *buf = occ->buffer;
	u8 *byte_buf;
	int idx = 0, rc;

	cmd_len = (occ->version == occ_p10) ? 6 : 5;

	/*
	 * We use the same buffer for command and response, make
	 * sure it's big enough
	 */
	resp_len = OCC_SBE_STATUS_WORDS;
	cmd_len += data_len >> 2;
	buf_len = max(cmd_len, resp_len);
	buf = kzalloc(buf_len << 2, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	/*
	 * Magic sequence to do SBE putsram command. SBE will transfer
@@ -358,18 +349,33 @@ static int occ_putsram(struct occ *occ, const void *data, ssize_t len)
	buf[4 + idx] = cpu_to_be32(data_len);
	memcpy(&buf[5 + idx], data, len);

	byte_buf = (u8 *)&buf[5 + idx];
	/*
	 * Overwrite the first byte with our sequence number and the last two
	 * bytes with the checksum.
	 */
	byte_buf[0] = seq_no;
	byte_buf[len - 2] = checksum >> 8;
	byte_buf[len - 1] = checksum & 0xff;

	rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
	if (rc)
		goto free;
		return rc;

	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
				  buf, resp_len, &resp_len);
	if (rc)
		goto free;
				  buf, resp_len, &parsed_len);
	if (rc > 0) {
		dev_err(occ->dev, "SRAM write returned failure status: %08x\n",
			rc);
		occ_save_ffdc(occ, buf, parsed_len, resp_len);
		return -ECOMM;
	} else if (rc) {
		return rc;
	}

	if (resp_len != 1) {
	if (parsed_len != 1) {
		dev_err(occ->dev, "SRAM write response length invalid: %zd\n",
			resp_len);
			parsed_len);
		rc = -EBADMSG;
	} else {
		resp_data_len = be32_to_cpu(buf[0]);
@@ -381,27 +387,16 @@ static int occ_putsram(struct occ *occ, const void *data, ssize_t len)
		}
	}

free:
	/* Convert positive SBEI status */
	if (rc > 0) {
		dev_err(occ->dev, "SRAM write returned failure status: %08x\n",
			rc);
		rc = -EBADMSG;
	}

	kfree(buf);
	return rc;
}

static int occ_trigger_attn(struct occ *occ)
{
	__be32 buf[OCC_SBE_STATUS_WORDS];
	size_t cmd_len, resp_len, resp_data_len;
	__be32 *buf = occ->buffer;
	size_t cmd_len, parsed_len, resp_data_len;
	size_t resp_len = OCC_MAX_RESP_WORDS;
	int idx = 0, rc;

	BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 8);
	resp_len = OCC_SBE_STATUS_WORDS;

	switch (occ->version) {
	default:
	case occ_p9:
@@ -426,16 +421,22 @@ static int occ_trigger_attn(struct occ *occ)

	rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
	if (rc)
		goto error;
		return rc;

	rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
				  buf, resp_len, &resp_len);
	if (rc)
		goto error;
				  buf, resp_len, &parsed_len);
	if (rc > 0) {
		dev_err(occ->dev, "SRAM attn returned failure status: %08x\n",
			rc);
		occ_save_ffdc(occ, buf, parsed_len, resp_len);
		return -ECOMM;
	} else if (rc) {
		return rc;
	}

	if (resp_len != 1) {
	if (parsed_len != 1) {
		dev_err(occ->dev, "SRAM attn response length invalid: %zd\n",
			resp_len);
			parsed_len);
		rc = -EBADMSG;
	} else {
		resp_data_len = be32_to_cpu(buf[0]);
@@ -447,14 +448,6 @@ static int occ_trigger_attn(struct occ *occ)
		}
	}

 error:
	/* Convert positive SBEI status */
	if (rc > 0) {
		dev_err(occ->dev, "SRAM attn returned failure status: %08x\n",
			rc);
		rc = -EBADMSG;
	}

	return rc;
}

@@ -466,24 +459,49 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
		msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
	struct occ *occ = dev_get_drvdata(dev);
	struct occ_response *resp = response;
	size_t user_resp_len = *resp_len;
	u8 seq_no;
	u16 checksum = 0;
	u16 resp_data_length;
	const u8 *byte_request = (const u8 *)request;
	unsigned long start;
	int rc;
	size_t i;

	*resp_len = 0;

	if (!occ)
		return -ENODEV;

	if (*resp_len < 7) {
		dev_dbg(dev, "Bad resplen %zd\n", *resp_len);
	if (user_resp_len < 7) {
		dev_dbg(dev, "Bad resplen %zd\n", user_resp_len);
		return -EINVAL;
	}

	/* Checksum the request, ignoring first byte (sequence number). */
	for (i = 1; i < req_len - 2; ++i)
		checksum += byte_request[i];

	mutex_lock(&occ->occ_lock);

	/* Extract the seq_no from the command (first byte) */
	seq_no = *(const u8 *)request;
	rc = occ_putsram(occ, request, req_len);
	occ->client_buffer = response;
	occ->client_buffer_size = user_resp_len;
	occ->client_response_size = 0;

	/*
	 * Get a sequence number and update the counter. Avoid a sequence
	 * number of 0 which would pass the response check below even if the
	 * OCC response is uninitialized. Any sequence number the user is
	 * trying to send is overwritten since this function is the only common
	 * interface to the OCC and therefore the only place we can guarantee
	 * unique sequence numbers.
	 */
	seq_no = occ->sequence_number++;
	if (!occ->sequence_number)
		occ->sequence_number = 1;
	checksum += seq_no;

	rc = occ_putsram(occ, request, req_len, seq_no, checksum);
	if (rc)
		goto done;

@@ -520,7 +538,7 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
	resp_data_length = get_unaligned_be16(&resp->data_length);

	/* Message size is data length + 5 bytes header + 2 bytes checksum */
	if ((resp_data_length + 7) > *resp_len) {
	if ((resp_data_length + 7) > user_resp_len) {
		rc = -EMSGSIZE;
		goto done;
	}
@@ -536,10 +554,11 @@ int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
			goto done;
	}

	*resp_len = resp_data_length + 7;
	occ->client_response_size = resp_data_length + 7;
	rc = occ_verify_checksum(occ, resp, resp_data_length);

 done:
	*resp_len = occ->client_response_size;
	mutex_unlock(&occ->occ_lock);

	return rc;
@@ -571,9 +590,15 @@ static int occ_probe(struct platform_device *pdev)
	if (!occ)
		return -ENOMEM;

	/* SBE words are always four bytes */
	occ->buffer = kvmalloc(OCC_MAX_RESP_WORDS * 4, GFP_KERNEL);
	if (!occ->buffer)
		return -ENOMEM;

	occ->version = (uintptr_t)of_device_get_match_data(dev);
	occ->dev = dev;
	occ->sbefifo = dev->parent;
	occ->sequence_number = 1;
	mutex_init(&occ->occ_lock);

	if (dev->of_node) {
@@ -605,6 +630,7 @@ static int occ_probe(struct platform_device *pdev)
	if (rc) {
		dev_err(dev, "failed to register miscdevice: %d\n", rc);
		ida_simple_remove(&occ_ida, occ->idx);
		kvfree(occ->buffer);
		return rc;
	}

@@ -620,6 +646,8 @@ static int occ_remove(struct platform_device *pdev)
{
	struct occ *occ = platform_get_drvdata(pdev);

	kvfree(occ->buffer);

	misc_deregister(&occ->mdev);

	device_for_each_child(&pdev->dev, NULL, occ_unregister_child);
+25 −3
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ struct sbefifo {
	bool			broken;
	bool			dead;
	bool			async_ffdc;
	bool			timed_out;
};

struct sbefifo_user {
@@ -136,6 +137,14 @@ struct sbefifo_user {

static DEFINE_MUTEX(sbefifo_ffdc_mutex);

static ssize_t timeout_show(struct device *dev, struct device_attribute *attr,
			    char *buf)
{
	struct sbefifo *sbefifo = container_of(dev, struct sbefifo, dev);

	return sysfs_emit(buf, "%d\n", sbefifo->timed_out ? 1 : 0);
}
static DEVICE_ATTR_RO(timeout);

static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc,
				size_t ffdc_sz, bool internal)
@@ -462,11 +471,14 @@ static int sbefifo_wait(struct sbefifo *sbefifo, bool up,
			break;
	}
	if (!ready) {
		sysfs_notify(&sbefifo->dev.kobj, NULL, dev_attr_timeout.attr.name);
		sbefifo->timed_out = true;
		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);

	sbefifo->timed_out = false;
	*status = sts;

	return 0;
@@ -740,7 +752,9 @@ int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len,
        iov_iter_kvec(&resp_iter, WRITE, &resp_iov, 1, rbytes);

	/* Perform the command */
	mutex_lock(&sbefifo->lock);
	rc = mutex_lock_interruptible(&sbefifo->lock);
	if (rc)
		return rc;
	rc = __sbefifo_submit(sbefifo, command, cmd_len, &resp_iter);
	mutex_unlock(&sbefifo->lock);

@@ -820,7 +834,9 @@ static ssize_t sbefifo_user_read(struct file *file, char __user *buf,
	iov_iter_init(&resp_iter, WRITE, &resp_iov, 1, len);

	/* Perform the command */
	mutex_lock(&sbefifo->lock);
	rc = mutex_lock_interruptible(&sbefifo->lock);
	if (rc)
		goto bail;
	rc = __sbefifo_submit(sbefifo, user->pending_cmd, cmd_len, &resp_iter);
	mutex_unlock(&sbefifo->lock);
	if (rc < 0)
@@ -875,7 +891,9 @@ static ssize_t sbefifo_user_write(struct file *file, const char __user *buf,
		user->pending_len = 0;

		/* Trigger reset request */
		mutex_lock(&sbefifo->lock);
		rc = mutex_lock_interruptible(&sbefifo->lock);
		if (rc)
			goto bail;
		rc = sbefifo_request_reset(user->sbefifo);
		mutex_unlock(&sbefifo->lock);
		if (rc == 0)
@@ -993,6 +1011,8 @@ static int sbefifo_probe(struct device *dev)
				 child_name);
	}

	device_create_file(&sbefifo->dev, &dev_attr_timeout);

	return 0;
 err_free_minor:
	fsi_free_minor(sbefifo->dev.devt);
@@ -1018,6 +1038,8 @@ static int sbefifo_remove(struct device *dev)

	dev_dbg(dev, "Removing sbefifo device...\n");

	device_remove_file(&sbefifo->dev, &dev_attr_timeout);

	mutex_lock(&sbefifo->lock);
	sbefifo->dead = true;
	mutex_unlock(&sbefifo->lock);
+12 −18
Original line number Diff line number Diff line
@@ -132,22 +132,20 @@ struct extended_sensor {
static int occ_poll(struct occ *occ)
{
	int rc;
	u16 checksum = occ->poll_cmd_data + occ->seq_no + 1;
	u8 cmd[8];
	u8 cmd[7];
	struct occ_poll_response_header *header;

	/* big endian */
	cmd[0] = occ->seq_no++;		/* sequence number */
	cmd[0] = 0;			/* sequence number */
	cmd[1] = 0;			/* cmd type */
	cmd[2] = 0;			/* data length msb */
	cmd[3] = 1;			/* data length lsb */
	cmd[4] = occ->poll_cmd_data;	/* data */
	cmd[5] = checksum >> 8;		/* checksum msb */
	cmd[6] = checksum & 0xFF;	/* checksum lsb */
	cmd[7] = 0;
	cmd[5] = 0;			/* checksum msb */
	cmd[6] = 0;			/* checksum lsb */

	/* mutex should already be locked if necessary */
	rc = occ->send_cmd(occ, cmd);
	rc = occ->send_cmd(occ, cmd, sizeof(cmd));
	if (rc) {
		occ->last_error = rc;
		if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD)
@@ -184,25 +182,23 @@ static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap)
{
	int rc;
	u8 cmd[8];
	u16 checksum = 0x24;
	__be16 user_power_cap_be = cpu_to_be16(user_power_cap);

	cmd[0] = 0;
	cmd[1] = 0x22;
	cmd[2] = 0;
	cmd[3] = 2;
	cmd[0] = 0;	/* sequence number */
	cmd[1] = 0x22;	/* cmd type */
	cmd[2] = 0;	/* data length msb */
	cmd[3] = 2;	/* data length lsb */

	memcpy(&cmd[4], &user_power_cap_be, 2);

	checksum += cmd[4] + cmd[5];
	cmd[6] = checksum >> 8;
	cmd[7] = checksum & 0xFF;
	cmd[6] = 0;	/* checksum msb */
	cmd[7] = 0;	/* checksum lsb */

	rc = mutex_lock_interruptible(&occ->lock);
	if (rc)
		return rc;

	rc = occ->send_cmd(occ, cmd);
	rc = occ->send_cmd(occ, cmd, sizeof(cmd));

	mutex_unlock(&occ->lock);

@@ -1144,8 +1140,6 @@ int occ_setup(struct occ *occ, const char *name)
{
	int rc;

	/* start with 1 to avoid false match with zero-initialized SRAM buffer */
	occ->seq_no = 1;
	mutex_init(&occ->lock);
	occ->groups[0] = &occ->group;

Loading