summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/cxlflash
diff options
context:
space:
mode:
authorMatthew R. Ochs <mrochs@linux.vnet.ibm.com>2015-06-09 17:15:52 -0500
committerJames Bottomley <JBottomley@Odin.com>2015-07-30 21:02:01 -0700
commitc21e0bbfc48509a776ec4a39bd9a0fb45a9c315b (patch)
treeec2bc76bd16eb59c3efbd7af05c8364eb4a9ecf0 /drivers/scsi/cxlflash
parent2dc127bb299d1c7436a08e79193bd0251068356e (diff)
cxlflash: Base support for IBM CXL Flash Adapter
SCSI device driver to support filesystem access on the IBM CXL Flash adapter. Supported-by: Stephen Bates <stephen.bates@pmcs.com> Reviewed-by: Michael Neuling <mikey@neuling.org> Signed-off-by: Matthew R. Ochs <mrochs@linux.vnet.ibm.com> Signed-off-by: Manoj N. Kumar <manoj@linux.vnet.ibm.com> Reviewed-by: Brian King <brking@linux.vnet.ibm.com> Signed-off-by: James Bottomley <JBottomley@Odin.com>
Diffstat (limited to 'drivers/scsi/cxlflash')
-rw-r--r--drivers/scsi/cxlflash/Kconfig11
-rw-r--r--drivers/scsi/cxlflash/Makefile2
-rw-r--r--drivers/scsi/cxlflash/common.h180
-rw-r--r--drivers/scsi/cxlflash/main.c2294
-rw-r--r--drivers/scsi/cxlflash/main.h104
-rwxr-xr-xdrivers/scsi/cxlflash/sislite.h465
6 files changed, 3056 insertions, 0 deletions
diff --git a/drivers/scsi/cxlflash/Kconfig b/drivers/scsi/cxlflash/Kconfig
new file mode 100644
index 000000000000..c7075084cfdb
--- /dev/null
+++ b/drivers/scsi/cxlflash/Kconfig
@@ -0,0 +1,11 @@
+#
+# IBM CXL-attached Flash Accelerator SCSI Driver
+#
+
+config CXLFLASH
+ tristate "Support for IBM CAPI Flash"
+ depends on PCI && SCSI && CXL
+ default m
+ help
+ Allows CAPI Accelerated IO to Flash
+ If unsure, say N.
diff --git a/drivers/scsi/cxlflash/Makefile b/drivers/scsi/cxlflash/Makefile
new file mode 100644
index 000000000000..dc95e203e3af
--- /dev/null
+++ b/drivers/scsi/cxlflash/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_CXLFLASH) += cxlflash.o
+cxlflash-y += main.o
diff --git a/drivers/scsi/cxlflash/common.h b/drivers/scsi/cxlflash/common.h
new file mode 100644
index 000000000000..5f43608dc8a1
--- /dev/null
+++ b/drivers/scsi/cxlflash/common.h
@@ -0,0 +1,180 @@
+/*
+ * CXL Flash Device Driver
+ *
+ * Written by: Manoj N. Kumar <manoj@linux.vnet.ibm.com>, IBM Corporation
+ * Matthew R. Ochs <mrochs@linux.vnet.ibm.com>, IBM Corporation
+ *
+ * Copyright (C) 2015 IBM Corporation
+ *
+ * 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.
+ */
+
+#ifndef _CXLFLASH_COMMON_H
+#define _CXLFLASH_COMMON_H
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_device.h>
+
+
+#define MAX_CONTEXT CXLFLASH_MAX_CONTEXT /* num contexts per afu */
+
+#define CXLFLASH_BLOCK_SIZE 4096 /* 4K blocks */
+#define CXLFLASH_MAX_XFER_SIZE 16777216 /* 16MB transfer */
+#define CXLFLASH_MAX_SECTORS (CXLFLASH_MAX_XFER_SIZE/512) /* SCSI wants
+ max_sectors
+ in units of
+ 512 byte
+ sectors
+ */
+
+#define NUM_RRQ_ENTRY 16 /* for master issued cmds */
+#define MAX_RHT_PER_CONTEXT (PAGE_SIZE / sizeof(struct sisl_rht_entry))
+
+/* AFU command retry limit */
+#define MC_RETRY_CNT 5 /* sufficient for SCSI check and
+ certain AFU errors */
+
+/* Command management definitions */
+#define CXLFLASH_NUM_CMDS (2 * CXLFLASH_MAX_CMDS) /* Must be a pow2 for
+ alignment and more
+ efficient array
+ index derivation
+ */
+
+#define CXLFLASH_MAX_CMDS 16
+#define CXLFLASH_MAX_CMDS_PER_LUN CXLFLASH_MAX_CMDS
+
+
+static inline void check_sizes(void)
+{
+ BUILD_BUG_ON_NOT_POWER_OF_2(CXLFLASH_NUM_CMDS);
+}
+
+/* AFU defines a fixed size of 4K for command buffers (borrow 4K page define) */
+#define CMD_BUFSIZE SIZE_4K
+
+/* flags in IOA status area for host use */
+#define B_DONE 0x01
+#define B_ERROR 0x02 /* set with B_DONE */
+#define B_TIMEOUT 0x04 /* set with B_DONE & B_ERROR */
+
+enum cxlflash_lr_state {
+ LINK_RESET_INVALID,
+ LINK_RESET_REQUIRED,
+ LINK_RESET_COMPLETE
+};
+
+enum cxlflash_init_state {
+ INIT_STATE_NONE,
+ INIT_STATE_PCI,
+ INIT_STATE_AFU,
+ INIT_STATE_SCSI
+};
+
+/*
+ * Each context has its own set of resource handles that is visible
+ * only from that context.
+ */
+
+struct cxlflash_cfg {
+ struct afu *afu;
+ struct cxl_context *mcctx;
+
+ struct pci_dev *dev;
+ struct pci_device_id *dev_id;
+ struct Scsi_Host *host;
+
+ ulong cxlflash_regs_pci;
+
+ wait_queue_head_t eeh_waitq;
+
+ struct work_struct work_q;
+ enum cxlflash_init_state init_state;
+ enum cxlflash_lr_state lr_state;
+ int lr_port;
+
+ struct cxl_afu *cxl_afu;
+
+ struct pci_pool *cxlflash_cmd_pool;
+ struct pci_dev *parent_dev;
+
+ wait_queue_head_t tmf_waitq;
+ bool tmf_active;
+ u8 err_recovery_active:1;
+};
+
+struct afu_cmd {
+ struct sisl_ioarcb rcb; /* IOARCB (cache line aligned) */
+ struct sisl_ioasa sa; /* IOASA must follow IOARCB */
+ spinlock_t slock;
+ struct completion cevent;
+ char *buf; /* per command buffer */
+ struct afu *parent;
+ int slot;
+ atomic_t free;
+
+ u8 cmd_tmf:1;
+
+ /* As per the SISLITE spec the IOARCB EA has to be 16-byte aligned.
+ * However for performance reasons the IOARCB/IOASA should be
+ * cache line aligned.
+ */
+} __aligned(cache_line_size());
+
+struct afu {
+ /* Stuff requiring alignment go first. */
+
+ u64 rrq_entry[NUM_RRQ_ENTRY]; /* 128B RRQ */
+ /*
+ * Command & data for AFU commands.
+ */
+ struct afu_cmd cmd[CXLFLASH_NUM_CMDS];
+
+ /* Beware of alignment till here. Preferably introduce new
+ * fields after this point
+ */
+
+ /* AFU HW */
+ struct cxl_ioctl_start_work work;
+ struct cxlflash_afu_map *afu_map; /* entire MMIO map */
+ struct sisl_host_map *host_map; /* MC host map */
+ struct sisl_ctrl_map *ctrl_map; /* MC control map */
+
+ ctx_hndl_t ctx_hndl; /* master's context handle */
+ u64 *hrrq_start;
+ u64 *hrrq_end;
+ u64 *hrrq_curr;
+ bool toggle;
+ bool read_room;
+ atomic64_t room;
+ u64 hb;
+ u32 cmd_couts; /* Number of command checkouts */
+ u32 internal_lun; /* User-desired LUN mode for this AFU */
+
+ char version[8];
+ u64 interface_version;
+
+ struct cxlflash_cfg *parent; /* Pointer back to parent cxlflash_cfg */
+
+};
+
+static inline u64 lun_to_lunid(u64 lun)
+{
+ u64 lun_id;
+
+ int_to_scsilun(lun, (struct scsi_lun *)&lun_id);
+ return swab64(lun_id);
+}
+
+int cxlflash_send_cmd(struct afu *, struct afu_cmd *);
+void cxlflash_wait_resp(struct afu *, struct afu_cmd *);
+int cxlflash_afu_reset(struct cxlflash_cfg *);
+struct afu_cmd *cxlflash_cmd_checkout(struct afu *);
+void cxlflash_cmd_checkin(struct afu_cmd *);
+int cxlflash_afu_sync(struct afu *, ctx_hndl_t, res_hndl_t, u8);
+#endif /* ifndef _CXLFLASH_COMMON_H */
diff --git a/drivers/scsi/cxlflash/main.c b/drivers/scsi/cxlflash/main.c
new file mode 100644
index 000000000000..0720d2f13c2a
--- /dev/null
+++ b/drivers/scsi/cxlflash/main.c
@@ -0,0 +1,2294 @@
+/*
+ * CXL Flash Device Driver
+ *
+ * Written by: Manoj N. Kumar <manoj@linux.vnet.ibm.com>, IBM Corporation
+ * Matthew R. Ochs <mrochs@linux.vnet.ibm.com>, IBM Corporation
+ *
+ * Copyright (C) 2015 IBM Corporation
+ *
+ * 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 <linux/delay.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include <asm/unaligned.h>
+
+#include <misc/cxl.h>
+
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_host.h>
+
+#include "main.h"
+#include "sislite.h"
+#include "common.h"
+
+MODULE_DESCRIPTION(CXLFLASH_ADAPTER_NAME);
+MODULE_AUTHOR("Manoj N. Kumar <manoj@linux.vnet.ibm.com>");
+MODULE_AUTHOR("Matthew R. Ochs <mrochs@linux.vnet.ibm.com>");
+MODULE_LICENSE("GPL");
+
+
+/**
+ * cxlflash_cmd_checkout() - checks out an AFU command
+ * @afu: AFU to checkout from.
+ *
+ * Commands are checked out in a round-robin fashion. Note that since
+ * the command pool is larger than the hardware queue, the majority of
+ * times we will only loop once or twice before getting a command. The
+ * buffer and CDB within the command are initialized (zeroed) prior to
+ * returning.
+ *
+ * Return: The checked out command or NULL when command pool is empty.
+ */
+struct afu_cmd *cxlflash_cmd_checkout(struct afu *afu)
+{
+ int k, dec = CXLFLASH_NUM_CMDS;
+ struct afu_cmd *cmd;
+
+ while (dec--) {
+ k = (afu->cmd_couts++ & (CXLFLASH_NUM_CMDS - 1));
+
+ cmd = &afu->cmd[k];
+
+ if (!atomic_dec_if_positive(&cmd->free)) {
+ pr_debug("%s: returning found index=%d\n",
+ __func__, cmd->slot);
+ memset(cmd->buf, 0, CMD_BUFSIZE);
+ memset(cmd->rcb.cdb, 0, sizeof(cmd->rcb.cdb));
+ return cmd;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * cxlflash_cmd_checkin() - checks in an AFU command
+ * @cmd: AFU command to checkin.
+ *
+ * Safe to pass commands that have already been checked in. Several
+ * internal tracking fields are reset as part of the checkin. Note
+ * that these are intentionally reset prior to toggling the free bit
+ * to avoid clobbering values in the event that the command is checked
+ * out right away.
+ */
+void cxlflash_cmd_checkin(struct afu_cmd *cmd)
+{
+ cmd->rcb.scp = NULL;
+ cmd->rcb.timeout = 0;
+ cmd->sa.ioasc = 0;
+ cmd->cmd_tmf = false;
+ cmd->sa.host_use[0] = 0; /* clears both completion and retry bytes */
+
+ if (unlikely(atomic_inc_return(&cmd->free) != 1)) {
+ pr_err("%s: Freeing cmd (%d) that is not in use!\n",
+ __func__, cmd->slot);
+ return;
+ }
+
+ pr_debug("%s: released cmd %p index=%d\n", __func__, cmd, cmd->slot);
+}
+
+/**
+ * process_cmd_err() - command error handler
+ * @cmd: AFU command that experienced the error.
+ * @scp: SCSI command associated with the AFU command in error.
+ *
+ * Translates error bits from AFU command to SCSI command results.
+ */
+static void process_cmd_err(struct afu_cmd *cmd, struct scsi_cmnd *scp)
+{
+ struct sisl_ioarcb *ioarcb;
+ struct sisl_ioasa *ioasa;
+
+ if (unlikely(!cmd))
+ return;
+
+ ioarcb = &(cmd->rcb);
+ ioasa = &(cmd->sa);
+
+ if (ioasa->rc.flags & SISL_RC_FLAGS_UNDERRUN) {
+ pr_debug("%s: cmd underrun cmd = %p scp = %p\n",
+ __func__, cmd, scp);
+ scp->result = (DID_ERROR << 16);
+ }
+
+ if (ioasa->rc.flags & SISL_RC_FLAGS_OVERRUN) {
+ pr_debug("%s: cmd underrun cmd = %p scp = %p\n",
+ __func__, cmd, scp);
+ scp->result = (DID_ERROR << 16);
+ }
+
+ pr_debug("%s: cmd failed afu_rc=%d scsi_rc=%d fc_rc=%d "
+ "afu_extra=0x%X, scsi_entra=0x%X, fc_extra=0x%X\n",
+ __func__, ioasa->rc.afu_rc, ioasa->rc.scsi_rc,
+ ioasa->rc.fc_rc, ioasa->afu_extra, ioasa->scsi_extra,
+ ioasa->fc_extra);
+
+ if (ioasa->rc.scsi_rc) {
+ /* We have a SCSI status */
+ if (ioasa->rc.flags & SISL_RC_FLAGS_SENSE_VALID) {
+ memcpy(scp->sense_buffer, ioasa->sense_data,
+ SISL_SENSE_DATA_LEN);
+ scp->result = ioasa->rc.scsi_rc;
+ } else
+ scp->result = ioasa->rc.scsi_rc | (DID_ERROR << 16);
+ }
+
+ /*
+ * We encountered an error. Set scp->result based on nature
+ * of error.
+ */
+ if (ioasa->rc.fc_rc) {
+ /* We have an FC status */
+ switch (ioasa->rc.fc_rc) {
+ case SISL_FC_RC_LINKDOWN:
+ scp->result = (DID_REQUEUE << 16);
+ break;
+ case SISL_FC_RC_RESID:
+ /* This indicates an FCP resid underrun */
+ if (!(ioasa->rc.flags & SISL_RC_FLAGS_OVERRUN)) {
+ /* If the SISL_RC_FLAGS_OVERRUN flag was set,
+ * then we will handle this error else where.
+ * If not then we must handle it here.
+ * This is probably an AFU bug. We will
+ * attempt a retry to see if that resolves it.
+ */
+ scp->result = (DID_ERROR << 16);
+ }
+ break;
+ case SISL_FC_RC_RESIDERR:
+ /* Resid mismatch between adapter and device */
+ case SISL_FC_RC_TGTABORT:
+ case SISL_FC_RC_ABORTOK:
+ case SISL_FC_RC_ABORTFAIL:
+ case SISL_FC_RC_NOLOGI:
+ case SISL_FC_RC_ABORTPEND:
+ case SISL_FC_RC_WRABORTPEND:
+ case SISL_FC_RC_NOEXP:
+ case SISL_FC_RC_INUSE:
+ scp->result = (DID_ERROR << 16);
+ break;
+ }
+ }
+
+ if (ioasa->rc.afu_rc) {
+ /* We have an AFU error */
+ switch (ioasa->rc.afu_rc) {
+ case SISL_AFU_RC_NO_CHANNELS:
+ scp->result = (DID_MEDIUM_ERROR << 16);
+ break;
+ case SISL_AFU_RC_DATA_DMA_ERR:
+ switch (ioasa->afu_extra) {
+ case SISL_AFU_DMA_ERR_PAGE_IN:
+ /* Retry */
+ scp->result = (DID_IMM_RETRY << 16);
+ break;
+ case SISL_AFU_DMA_ERR_INVALID_EA:
+ default:
+ scp->result = (DID_ERROR << 16);
+ }
+ break;
+ case SISL_AFU_RC_OUT_OF_DATA_BUFS:
+ /* Retry */
+ scp->result = (DID_ALLOC_FAILURE << 16);
+ break;
+ default:
+ scp->result = (DID_ERROR << 16);
+ }
+ }
+}
+
+/**
+ * cmd_complete() - command completion handler
+ * @cmd: AFU command that has completed.
+ *
+ * Prepares and submits command that has either completed or timed out to
+ * the SCSI stack. Checks AFU command back into command pool for non-internal
+ * (rcb.scp populated) commands.
+ */
+static void cmd_complete(struct afu_cmd *cmd)
+{
+ struct scsi_cmnd *scp;
+ u32 resid;
+ ulong lock_flags;
+ struct afu *afu = cmd->parent;
+ struct cxlflash_cfg *cfg = afu->parent;
+ bool cmd_is_tmf;
+
+ spin_lock_irqsave(&cmd->slock, lock_flags);
+ cmd->sa.host_use_b[0] |= B_DONE;
+ spin_unlock_irqrestore(&cmd->slock, lock_flags);
+
+ if (cmd->rcb.scp) {
+ scp = cmd->rcb.scp;
+ if (unlikely(cmd->sa.rc.afu_rc ||
+ cmd->sa.rc.scsi_rc ||
+ cmd->sa.rc.fc_rc))
+ process_cmd_err(cmd, scp);
+ else
+ scp->result = (DID_OK << 16);
+
+ resid = cmd->sa.resid;
+ cmd_is_tmf = cmd->cmd_tmf;
+ cxlflash_cmd_checkin(cmd); /* Don't use cmd after here */
+
+ pr_debug("%s: calling scsi_set_resid, scp=%p "
+ "result=%X resid=%d\n", __func__,
+ scp, scp->result, resid);
+
+ scsi_set_resid(scp, resid);
+ scsi_dma_unmap(scp);
+ scp->scsi_done(scp);
+
+ if (cmd_is_tmf) {
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ cfg->tmf_active = false;
+ wake_up_all_locked(&cfg->tmf_waitq);
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock,
+ lock_flags);
+ }
+ } else
+ complete(&cmd->cevent);
+}
+
+/**
+ * send_tmf() - sends a Task Management Function (TMF)
+ * @afu: AFU to checkout from.
+ * @scp: SCSI command from stack.
+ * @tmfcmd: TMF command to send.
+ *
+ * Return:
+ * 0 on success
+ * SCSI_MLQUEUE_HOST_BUSY when host is busy
+ */
+static int send_tmf(struct afu *afu, struct scsi_cmnd *scp, u64 tmfcmd)
+{
+ struct afu_cmd *cmd;
+
+ u32 port_sel = scp->device->channel + 1;
+ short lflag = 0;
+ struct Scsi_Host *host = scp->device->host;
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)host->hostdata;
+ ulong lock_flags;
+ int rc = 0;
+
+ cmd = cxlflash_cmd_checkout(afu);
+ if (unlikely(!cmd)) {
+ pr_err("%s: could not get a free command\n", __func__);
+ rc = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ }
+
+ /* If a Task Management Function is active, do not send one more.
+ */
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ if (cfg->tmf_active)
+ wait_event_interruptible_locked_irq(cfg->tmf_waitq,
+ !cfg->tmf_active);
+ cfg->tmf_active = true;
+ cmd->cmd_tmf = true;
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+
+ cmd->rcb.ctx_id = afu->ctx_hndl;
+ cmd->rcb.port_sel = port_sel;
+ cmd->rcb.lun_id = lun_to_lunid(scp->device->lun);
+
+ lflag = SISL_REQ_FLAGS_TMF_CMD;
+
+ cmd->rcb.req_flags = (SISL_REQ_FLAGS_PORT_LUN_ID |
+ SISL_REQ_FLAGS_SUP_UNDERRUN | lflag);
+
+ /* Stash the scp in the reserved field, for reuse during interrupt */
+ cmd->rcb.scp = scp;
+
+ /* Copy the CDB from the cmd passed in */
+ memcpy(cmd->rcb.cdb, &tmfcmd, sizeof(tmfcmd));
+
+ /* Send the command */
+ rc = cxlflash_send_cmd(afu, cmd);
+ if (unlikely(rc)) {
+ cxlflash_cmd_checkin(cmd);
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ cfg->tmf_active = false;
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+ goto out;
+ }
+
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ wait_event_interruptible_locked_irq(cfg->tmf_waitq, !cfg->tmf_active);
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+out:
+ return rc;
+}
+
+/**
+ * cxlflash_driver_info() - information handler for this host driver
+ * @host: SCSI host associated with device.
+ *
+ * Return: A string describing the device.
+ */
+static const char *cxlflash_driver_info(struct Scsi_Host *host)
+{
+ return CXLFLASH_ADAPTER_NAME;
+}
+
+/**
+ * cxlflash_queuecommand() - sends a mid-layer request
+ * @host: SCSI host associated with device.
+ * @scp: SCSI command to send.
+ *
+ * Return:
+ * 0 on success
+ * SCSI_MLQUEUE_HOST_BUSY when host is busy
+ */
+static int cxlflash_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *scp)
+{
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)host->hostdata;
+ struct afu *afu = cfg->afu;
+ struct pci_dev *pdev = cfg->dev;
+ struct afu_cmd *cmd;
+ u32 port_sel = scp->device->channel + 1;
+ int nseg, i, ncount;
+ struct scatterlist *sg;
+ ulong lock_flags;
+ short lflag = 0;
+ int rc = 0;
+
+ pr_debug("%s: (scp=%p) %d/%d/%d/%llu cdb=(%08X-%08X-%08X-%08X)\n",
+ __func__, scp, host->host_no, scp->device->channel,
+ scp->device->id, scp->device->lun,
+ get_unaligned_be32(&((u32 *)scp->cmnd)[0]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[1]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[2]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[3]));
+
+ /* If a Task Management Function is active, wait for it to complete
+ * before continuing with regular commands.
+ */
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ if (cfg->tmf_active) {
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+ rc = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ }
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+
+ cmd = cxlflash_cmd_checkout(afu);
+ if (unlikely(!cmd)) {
+ pr_err("%s: could not get a free command\n", __func__);
+ rc = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ }
+
+ cmd->rcb.ctx_id = afu->ctx_hndl;
+ cmd->rcb.port_sel = port_sel;
+ cmd->rcb.lun_id = lun_to_lunid(scp->device->lun);
+
+ if (scp->sc_data_direction == DMA_TO_DEVICE)
+ lflag = SISL_REQ_FLAGS_HOST_WRITE;
+ else
+ lflag = SISL_REQ_FLAGS_HOST_READ;
+
+ cmd->rcb.req_flags = (SISL_REQ_FLAGS_PORT_LUN_ID |
+ SISL_REQ_FLAGS_SUP_UNDERRUN | lflag);
+
+ /* Stash the scp in the reserved field, for reuse during interrupt */
+ cmd->rcb.scp = scp;
+
+ nseg = scsi_dma_map(scp);
+ if (unlikely(nseg < 0)) {
+ dev_err(&pdev->dev, "%s: Fail DMA map! nseg=%d\n",
+ __func__, nseg);
+ rc = SCSI_MLQUEUE_HOST_BUSY;
+ goto out;
+ }
+
+ ncount = scsi_sg_count(scp);
+ scsi_for_each_sg(scp, sg, ncount, i) {
+ cmd->rcb.data_len = sg_dma_len(sg);
+ cmd->rcb.data_ea = sg_dma_address(sg);
+ }
+
+ /* Copy the CDB from the scsi_cmnd passed in */
+ memcpy(cmd->rcb.cdb, scp->cmnd, sizeof(cmd->rcb.cdb));
+
+ /* Send the command */
+ rc = cxlflash_send_cmd(afu, cmd);
+ if (unlikely(rc)) {
+ cxlflash_cmd_checkin(cmd);
+ scsi_dma_unmap(scp);
+ }
+
+out:
+ return rc;
+}
+
+/**
+ * cxlflash_eh_device_reset_handler() - reset a single LUN
+ * @scp: SCSI command to send.
+ *
+ * Return:
+ * SUCCESS as defined in scsi/scsi.h
+ * FAILED as defined in scsi/scsi.h
+ */
+static int cxlflash_eh_device_reset_handler(struct scsi_cmnd *scp)
+{
+ int rc = SUCCESS;
+ struct Scsi_Host *host = scp->device->host;
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)host->hostdata;
+ struct afu *afu = cfg->afu;
+ int rcr = 0;
+
+ pr_debug("%s: (scp=%p) %d/%d/%d/%llu "
+ "cdb=(%08X-%08X-%08X-%08X)\n", __func__, scp,
+ host->host_no, scp->device->channel,
+ scp->device->id, scp->device->lun,
+ get_unaligned_be32(&((u32 *)scp->cmnd)[0]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[1]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[2]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[3]));
+
+ rcr = send_tmf(afu, scp, TMF_LUN_RESET);
+ if (unlikely(rcr))
+ rc = FAILED;
+
+ pr_debug("%s: returning rc=%d\n", __func__, rc);
+ return rc;
+}
+
+/**
+ * cxlflash_eh_host_reset_handler() - reset the host adapter
+ * @scp: SCSI command from stack identifying host.
+ *
+ * Return:
+ * SUCCESS as defined in scsi/scsi.h
+ * FAILED as defined in scsi/scsi.h
+ */
+static int cxlflash_eh_host_reset_handler(struct scsi_cmnd *scp)
+{
+ int rc = SUCCESS;
+ int rcr = 0;
+ struct Scsi_Host *host = scp->device->host;
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)host->hostdata;
+
+ pr_debug("%s: (scp=%p) %d/%d/%d/%llu "
+ "cdb=(%08X-%08X-%08X-%08X)\n", __func__, scp,
+ host->host_no, scp->device->channel,
+ scp->device->id, scp->device->lun,
+ get_unaligned_be32(&((u32 *)scp->cmnd)[0]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[1]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[2]),
+ get_unaligned_be32(&((u32 *)scp->cmnd)[3]));
+
+ rcr = cxlflash_afu_reset(cfg);
+ if (rcr == 0)
+ rc = SUCCESS;
+ else
+ rc = FAILED;
+
+ pr_debug("%s: returning rc=%d\n", __func__, rc);
+ return rc;
+}
+
+/**
+ * cxlflash_change_queue_depth() - change the queue depth for the device
+ * @sdev: SCSI device destined for queue depth change.
+ * @qdepth: Requested queue depth value to set.
+ *
+ * The requested queue depth is capped to the maximum supported value.
+ *
+ * Return: The actual queue depth set.
+ */
+static int cxlflash_change_queue_depth(struct scsi_device *sdev, int qdepth)
+{
+
+ if (qdepth > CXLFLASH_MAX_CMDS_PER_LUN)
+ qdepth = CXLFLASH_MAX_CMDS_PER_LUN;
+
+ scsi_change_queue_depth(sdev, qdepth);
+ return sdev->queue_depth;
+}
+
+/**
+ * cxlflash_show_port_status() - queries and presents the current port status
+ * @dev: Generic device associated with the host owning the port.
+ * @attr: Device attribute representing the port.
+ * @buf: Buffer of length PAGE_SIZE to report back port status in ASCII.
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t cxlflash_show_port_status(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)shost->hostdata;
+ struct afu *afu = cfg->afu;
+
+ char *disp_status;
+ int rc;
+ u32 port;
+ u64 status;
+ u64 *fc_regs;
+
+ rc = kstrtouint((attr->attr.name + 4), 10, &port);
+ if (rc || (port > NUM_FC_PORTS))
+ return 0;
+
+ fc_regs = &afu->afu_map->global.fc_regs[port][0];
+ status =
+ (readq_be(&fc_regs[FC_MTIP_STATUS / 8]) & FC_MTIP_STATUS_MASK);
+
+ if (status == FC_MTIP_STATUS_ONLINE)
+ disp_status = "online";
+ else if (status == FC_MTIP_STATUS_OFFLINE)
+ disp_status = "offline";
+ else
+ disp_status = "unknown";
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", disp_status);
+}
+
+/**
+ * cxlflash_show_lun_mode() - presents the current LUN mode of the host
+ * @dev: Generic device associated with the host.
+ * @attr: Device attribute representing the lun mode.
+ * @buf: Buffer of length PAGE_SIZE to report back the LUN mode in ASCII.
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t cxlflash_show_lun_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)shost->hostdata;
+ struct afu *afu = cfg->afu;
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", afu->internal_lun);
+}
+
+/**
+ * cxlflash_store_lun_mode() - sets the LUN mode of the host
+ * @dev: Generic device associated with the host.
+ * @attr: Device attribute representing the lun mode.
+ * @buf: Buffer of length PAGE_SIZE containing the LUN mode in ASCII.
+ * @count: Length of data resizing in @buf.
+ *
+ * The CXL Flash AFU supports a dummy LUN mode where the external
+ * links and storage are not required. Space on the FPGA is used
+ * to create 1 or 2 small LUNs which are presented to the system
+ * as if they were a normal storage device. This feature is useful
+ * during development and also provides manufacturing with a way
+ * to test the AFU without an actual device.
+ *
+ * 0 = external LUN[s] (default)
+ * 1 = internal LUN (1 x 64K, 512B blocks, id 0)
+ * 2 = internal LUN (1 x 64K, 4K blocks, id 0)
+ * 3 = internal LUN (2 x 32K, 512B blocks, ids 0,1)
+ * 4 = internal LUN (2 x 32K, 4K blocks, ids 0,1)
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t cxlflash_store_lun_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct Scsi_Host *shost = class_to_shost(dev);
+ struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)shost->hostdata;
+ struct afu *afu = cfg->afu;
+ int rc;
+ u32 lun_mode;
+
+ rc = kstrtouint(buf, 10, &lun_mode);
+ if (!rc && (lun_mode < 5) && (lun_mode != afu->internal_lun)) {
+ afu->internal_lun = lun_mode;
+ cxlflash_afu_reset(cfg);
+ scsi_scan_host(cfg->host);
+ }
+
+ return count;
+}
+
+/**
+ * cxlflash_show_dev_mode() - presents the current mode of the device
+ * @dev: Generic device associated with the device.
+ * @attr: Device attribute representing the device mode.
+ * @buf: Buffer of length PAGE_SIZE to report back the dev mode in ASCII.
+ *
+ * Return: The size of the ASCII string returned in @buf.
+ */
+static ssize_t cxlflash_show_dev_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%s\n",
+ sdev->hostdata ? "superpipe" : "legacy");
+}
+
+/**
+ * cxlflash_wait_for_pci_err_recovery() - wait for error recovery during probe
+ * @cxlflash: Internal structure associated with the host.
+ */
+static void cxlflash_wait_for_pci_err_recovery(struct cxlflash_cfg *cfg)
+{
+ struct pci_dev *pdev = cfg->dev;
+
+ if (pci_channel_offline(pdev))
+ wait_event_timeout(cfg->eeh_waitq,
+ !pci_channel_offline(pdev),
+ CXLFLASH_PCI_ERROR_RECOVERY_TIMEOUT);
+}
+
+/*
+ * Host attributes
+ */
+static DEVICE_ATTR(port0, S_IRUGO, cxlflash_show_port_status, NULL);
+static DEVICE_ATTR(port1, S_IRUGO, cxlflash_show_port_status, NULL);
+static DEVICE_ATTR(lun_mode, S_IRUGO | S_IWUSR, cxlflash_show_lun_mode,
+ cxlflash_store_lun_mode);
+
+static struct device_attribute *cxlflash_host_attrs[] = {
+ &dev_attr_port0,
+ &dev_attr_port1,
+ &dev_attr_lun_mode,
+ NULL
+};
+
+/*
+ * Device attributes
+ */
+static DEVICE_ATTR(mode, S_IRUGO, cxlflash_show_dev_mode, NULL);
+
+static struct device_attribute *cxlflash_dev_attrs[] = {
+ &dev_attr_mode,
+ NULL
+};
+
+/*
+ * Host template
+ */
+static struct scsi_host_template driver_template = {
+ .module = THIS_MODULE,
+ .name = CXLFLASH_ADAPTER_NAME,
+ .info = cxlflash_driver_info,
+ .proc_name = CXLFLASH_NAME,
+ .queuecommand = cxlflash_queuecommand,
+ .eh_device_reset_handler = cxlflash_eh_device_reset_handler,
+ .eh_host_reset_handler = cxlflash_eh_host_reset_handler,
+ .change_queue_depth = cxlflash_change_queue_depth,
+ .cmd_per_lun = 16,
+ .can_queue = CXLFLASH_MAX_CMDS,
+ .this_id = -1,
+ .sg_tablesize = SG_NONE, /* No scatter gather support. */
+ .max_sectors = CXLFLASH_MAX_SECTORS,
+ .use_clustering = ENABLE_CLUSTERING,
+ .shost_attrs = cxlflash_host_attrs,
+ .sdev_attrs = cxlflash_dev_attrs,
+};
+
+/*
+ * Device dependent values
+ */
+static struct dev_dependent_vals dev_corsa_vals = { CXLFLASH_MAX_SECTORS };
+
+/*
+ * PCI device binding table
+ */
+static struct pci_device_id cxlflash_pci_table[] = {
+ {PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_CORSA,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&dev_corsa_vals},
+ {}
+};
+
+MODULE_DEVICE_TABLE(pci, cxlflash_pci_table);
+
+/**
+ * free_mem() - free memory associated with the AFU
+ * @cxlflash: Internal structure associated with the host.
+ */
+static void free_mem(struct cxlflash_cfg *cfg)
+{
+ int i;
+ char *buf = NULL;
+ struct afu *afu = cfg->afu;
+
+ if (cfg->afu) {
+ for (i = 0; i < CXLFLASH_NUM_CMDS; i++) {
+ buf = afu->cmd[i].buf;
+ if (!((u64)buf & (PAGE_SIZE - 1)))
+ free_page((ulong)buf);
+ }
+
+ free_pages((ulong)afu, get_order(sizeof(struct afu)));
+ cfg->afu = NULL;
+ }
+}
+
+/**
+ * stop_afu() - stops the AFU command timers and unmaps the MMIO space
+ * @cxlflash: Internal structure associated with the host.
+ *
+ * Safe to call with AFU in a partially allocated/initialized state.
+ */
+static void stop_afu(struct cxlflash_cfg *cfg)
+{
+ int i;
+ struct afu *afu = cfg->afu;
+
+ if (likely(afu)) {
+ for (i = 0; i < CXLFLASH_NUM_CMDS; i++)
+ complete(&afu->cmd[i].cevent);
+
+ if (likely(afu->afu_map)) {
+ cxl_psa_unmap((void *)afu->afu_map);
+ afu->afu_map = NULL;
+ }
+ }
+}
+
+/**
+ * term_mc() - terminates the master context
+ * @cxlflash: Internal structure associated with the host.
+ * @level: Depth of allocation, where to begin waterfall tear down.
+ *
+ * Safe to call with AFU/MC in partially allocated/initialized state.
+ */
+static void term_mc(struct cxlflash_cfg *cfg, enum undo_level level)
+{
+ int rc = 0;
+ struct afu *afu = cfg->afu;
+
+ if (!afu || !cfg->mcctx) {
+ pr_err("%s: returning from term_mc with NULL afu or MC\n",
+ __func__);
+ return;
+ }
+
+ switch (level) {
+ case UNDO_START:
+ rc = cxl_stop_context(cfg->mcctx);
+ BUG_ON(rc);
+ case UNMAP_THREE:
+ cxl_unmap_afu_irq(cfg->mcctx, 3, afu);
+ case UNMAP_TWO:
+ cxl_unmap_afu_irq(cfg->mcctx, 2, afu);
+ case UNMAP_ONE:
+ cxl_unmap_afu_irq(cfg->mcctx, 1, afu);
+ case FREE_IRQ:
+ cxl_free_afu_irqs(cfg->mcctx);
+ case RELEASE_CONTEXT:
+ cfg->mcctx = NULL;
+ }
+}
+
+/**
+ * term_afu() - terminates the AFU
+ * @cxlflash: Internal structure associated with the host.
+ *
+ * Safe to call with AFU/MC in partially allocated/initialized state.
+ */
+static void term_afu(struct cxlflash_cfg *cfg)
+{
+ term_mc(cfg, UNDO_START);
+
+ if (cfg->afu)
+ stop_afu(cfg);
+
+ pr_debug("%s: returning\n", __func__);
+}
+
+/**
+ * cxlflash_remove() - PCI entry point to tear down host
+ * @pdev: PCI device associated with the host.
+ *
+ * Safe to use as a cleanup in partially allocated/initialized state.
+ */
+static void cxlflash_remove(struct pci_dev *pdev)
+{
+ struct cxlflash_cfg *cfg = pci_get_drvdata(pdev);
+ ulong lock_flags;
+
+ /* If a Task Management Function is active, wait for it to complete
+ * before continuing with remove.
+ */
+ spin_lock_irqsave(&cfg->tmf_waitq.lock, lock_flags);
+ if (cfg->tmf_active)
+ wait_event_interruptible_locked_irq(cfg->tmf_waitq,
+ !cfg->tmf_active);
+ spin_unlock_irqrestore(&cfg->tmf_waitq.lock, lock_flags);
+
+ switch (cfg->init_state) {
+ case INIT_STATE_SCSI:
+ scsi_remove_host(cfg->host);
+ scsi_host_put(cfg->host);
+ /* Fall through */
+ case INIT_STATE_AFU:
+ term_afu(cfg);
+ case INIT_STATE_PCI:
+ pci_release_regions(cfg->dev);
+ pci_disable_device(pdev);
+ case INIT_STATE_NONE:
+ flush_work(&cfg->work_q);
+ free_mem(cfg);
+ break;
+ }
+
+ pr_debug("%s: returning\n", __func__);
+}
+
+/**
+ * alloc_mem() - allocates the AFU and its command pool
+ * @cxlflash: Internal structure associated with the host.
+ *
+ * A partially allocated state remains on failure.
+ *
+ * Return:
+ * 0 on success
+ * -ENOMEM on failure to allocate memory
+ */
+static int alloc_mem(struct cxlflash_cfg *cfg)
+{
+ int rc = 0;
+ int i;
+ char *buf = NULL;
+
+ /* This allocation is about 12K, i.e. only 1 64k page
+ * and upto 4 4k pages
+ */
+ cfg->afu = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(sizeof(struct afu)));
+ if (unlikely(!cfg->afu)) {
+ pr_err("%s: cannot get %d free pages\n",
+ __func__, get_order(sizeof(struct afu)));
+ rc = -ENOMEM;
+ goto out;
+ }
+ cfg->afu->parent = cfg;
+ cfg->afu->afu_map = NULL;
+
+ for (i = 0; i < CXLFLASH_NUM_CMDS; buf += CMD_BUFSIZE, i++) {
+ if (!((u64)buf & (PAGE_SIZE - 1))) {
+ buf = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (unlikely(!buf)) {
+ pr_err("%s: Allocate command buffers fail!\n",
+ __func__);
+ rc = -ENOMEM;
+ free_mem(cfg);
+ goto out;
+ }
+ }
+
+ cfg->afu->cm