// SPDX-License-Identifier: GPL-2.0
/*
* 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.
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/fsi.h>
#include <linux/fsi-sbefifo.h>
#include <linux/kernel.h>
#include <linux/cdev.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>
#include <linux/uio.h>
#include <linux/vmalloc.h>
#include <linux/mm.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_ASYNC_FFDC 0x40000000
#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_USER_CMD_LEN (0x100000 + PAGE_SIZE)
#define SBEFIFO_RESET_MAGIC 0x52534554 /* "RSET" */
struct sbefifo {
uint32_t magic;
#define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */
struct fsi_device *fsi_dev;
struct device dev;
struct cdev cdev;
struct mutex lock;
bool broken;
bool dead;
bool async_ffdc;
};
struct sbefifo_user {
struct sbefifo *sbefifo;
struct mutex file_lock;
void *cmd_page;
void *pending_cmd;
size_t pending_len;
};
static DEFINE_MUTEX(sbefifo_ffdc_mutex);
static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc,
size_t ffdc_sz, bool internal)
{
int pack = 0;
#define FFDC_LSIZE 60
static char ffdc_line[FFDC_LSIZE];
char *p = ffdc_line;
while (ffdc_sz) {
u32 w0, w1, w2, i;
if (ffdc_sz < 3) {
dev_err(dev, "SBE invalid FFDC package size %zd\n", ffdc_sz);
return;
}
w0 = be32_to_cpu(*(ffdc++));
w1 = be32_to_cpu(*(ffdc++));
w2 = be32_to_cpu(*(ffdc++));
ffdc_sz -= 3;
if ((w0 >> 16) != 0xFFDC) {
dev_err(dev, "SBE invalid FFDC package signature %08x %08x %08x\n",
w0, w1, w2);
break;
}
w0 &= 0xffff;
if (w0 > ffdc_sz) {
dev_err(dev, "SBE FFDC package len %d words but only %zd remaining\n",
w0, ffdc_sz);
w0 = ffdc_sz;
break;
}
if (internal) {
dev_warn(dev, "+---- SBE FFDC package %d for async err -----+\n",
pack++);
} else {
dev_warn(dev, "+---- SBE FFDC package %d for cmd %02x:%02x -----+\n",
pack++, (w1 >> 8) & 0xff, w1 & 0xff);
}
dev_warn(dev, "| Response code: %08x |\n