diff options
Diffstat (limited to 'kernel/drivers/scsi/cxlflash')
-rw-r--r-- | kernel/drivers/scsi/cxlflash/Kconfig | 11 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/Makefile | 2 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/common.h | 204 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/lunmgt.c | 269 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/main.c | 2608 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/main.h | 109 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/sislite.h | 472 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/superpipe.c | 2143 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/superpipe.h | 151 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/vlun.c | 1273 | ||||
-rw-r--r-- | kernel/drivers/scsi/cxlflash/vlun.h | 86 |
11 files changed, 7328 insertions, 0 deletions
diff --git a/kernel/drivers/scsi/cxlflash/Kconfig b/kernel/drivers/scsi/cxlflash/Kconfig new file mode 100644 index 000000000..c052104e5 --- /dev/null +++ b/kernel/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 && EEH + default m + help + Allows CAPI Accelerated IO to Flash + If unsure, say N. diff --git a/kernel/drivers/scsi/cxlflash/Makefile b/kernel/drivers/scsi/cxlflash/Makefile new file mode 100644 index 000000000..9e39866d4 --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_CXLFLASH) += cxlflash.o +cxlflash-y += main.o superpipe.o lunmgt.o vlun.o diff --git a/kernel/drivers/scsi/cxlflash/common.h b/kernel/drivers/scsi/cxlflash/common.h new file mode 100644 index 000000000..c11cd193f --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/common.h @@ -0,0 +1,204 @@ +/* + * 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/rwsem.h> +#include <linux/types.h> +#include <scsi/scsi.h> +#include <scsi/scsi_device.h> + +extern const struct file_operations cxlflash_cxl_fops; + +#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 +}; + +enum cxlflash_state { + STATE_NORMAL, /* Normal running state, everything good */ + STATE_RESET, /* Reset state, trying to reset/recover */ + STATE_FAILTERM /* Failed/terminating state, error out users/threads */ +}; + +/* + * 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; + + struct work_struct work_q; + enum cxlflash_init_state init_state; + enum cxlflash_lr_state lr_state; + int lr_port; + atomic_t scan_host_needed; + + struct cxl_afu *cxl_afu; + struct pci_dev *parent_dev; + + atomic_t recovery_threads; + struct mutex ctx_recovery_mutex; + struct mutex ctx_tbl_list_mutex; + struct rw_semaphore ioctl_rwsem; + struct ctx_info *ctx_tbl[MAX_CONTEXT]; + struct list_head ctx_err_recovery; /* contexts w/ recovery pending */ + struct file_operations cxl_fops; + + /* Parameters that are LUN table related */ + int last_lun_index[CXLFLASH_NUM_FC_PORTS]; + int promote_lun_index; + struct list_head lluns; /* list of llun_info structs */ + + wait_queue_head_t tmf_waitq; + spinlock_t tmf_slock; + bool tmf_active; + wait_queue_head_t reset_waitq; + enum cxlflash_state state; +}; + +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 __iomem *afu_map; /* entire MMIO map */ + struct sisl_host_map __iomem *host_map; /* MC host map */ + struct sisl_ctrl_map __iomem *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[16]; + u64 interface_version; + + struct cxlflash_cfg *parent; /* Pointer back to parent cxlflash_cfg */ + +}; + +static inline u64 lun_to_lunid(u64 lun) +{ + __be64 lun_id; + + int_to_scsilun(lun, (struct scsi_lun *)&lun_id); + return be64_to_cpu(lun_id); +} + +int cxlflash_afu_sync(struct afu *, ctx_hndl_t, res_hndl_t, u8); +void cxlflash_list_init(void); +void cxlflash_term_global_luns(void); +void cxlflash_free_errpage(void); +int cxlflash_ioctl(struct scsi_device *, int, void __user *); +void cxlflash_stop_term_user_contexts(struct cxlflash_cfg *); +int cxlflash_mark_contexts_error(struct cxlflash_cfg *); +void cxlflash_term_local_luns(struct cxlflash_cfg *); +void cxlflash_restore_luntable(struct cxlflash_cfg *); + +#endif /* ifndef _CXLFLASH_COMMON_H */ diff --git a/kernel/drivers/scsi/cxlflash/lunmgt.c b/kernel/drivers/scsi/cxlflash/lunmgt.c new file mode 100644 index 000000000..a0923cade --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/lunmgt.c @@ -0,0 +1,269 @@ +/* + * 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 <misc/cxl.h> +#include <asm/unaligned.h> + +#include <scsi/scsi_host.h> +#include <uapi/scsi/cxlflash_ioctl.h> + +#include "sislite.h" +#include "common.h" +#include "vlun.h" +#include "superpipe.h" + +/** + * create_local() - allocate and initialize a local LUN information structure + * @sdev: SCSI device associated with LUN. + * @wwid: World Wide Node Name for LUN. + * + * Return: Allocated local llun_info structure on success, NULL on failure + */ +static struct llun_info *create_local(struct scsi_device *sdev, u8 *wwid) +{ + struct llun_info *lli = NULL; + + lli = kzalloc(sizeof(*lli), GFP_KERNEL); + if (unlikely(!lli)) { + pr_err("%s: could not allocate lli\n", __func__); + goto out; + } + + lli->sdev = sdev; + lli->host_no = sdev->host->host_no; + lli->in_table = false; + + memcpy(lli->wwid, wwid, DK_CXLFLASH_MANAGE_LUN_WWID_LEN); +out: + return lli; +} + +/** + * create_global() - allocate and initialize a global LUN information structure + * @sdev: SCSI device associated with LUN. + * @wwid: World Wide Node Name for LUN. + * + * Return: Allocated global glun_info structure on success, NULL on failure + */ +static struct glun_info *create_global(struct scsi_device *sdev, u8 *wwid) +{ + struct glun_info *gli = NULL; + + gli = kzalloc(sizeof(*gli), GFP_KERNEL); + if (unlikely(!gli)) { + pr_err("%s: could not allocate gli\n", __func__); + goto out; + } + + mutex_init(&gli->mutex); + memcpy(gli->wwid, wwid, DK_CXLFLASH_MANAGE_LUN_WWID_LEN); +out: + return gli; +} + +/** + * lookup_local() - find a local LUN information structure by WWID + * @cfg: Internal structure associated with the host. + * @wwid: WWID associated with LUN. + * + * Return: Found local lun_info structure on success, NULL on failure + */ +static struct llun_info *lookup_local(struct cxlflash_cfg *cfg, u8 *wwid) +{ + struct llun_info *lli, *temp; + + list_for_each_entry_safe(lli, temp, &cfg->lluns, list) + if (!memcmp(lli->wwid, wwid, DK_CXLFLASH_MANAGE_LUN_WWID_LEN)) + return lli; + + return NULL; +} + +/** + * lookup_global() - find a global LUN information structure by WWID + * @wwid: WWID associated with LUN. + * + * Return: Found global lun_info structure on success, NULL on failure + */ +static struct glun_info *lookup_global(u8 *wwid) +{ + struct glun_info *gli, *temp; + + list_for_each_entry_safe(gli, temp, &global.gluns, list) + if (!memcmp(gli->wwid, wwid, DK_CXLFLASH_MANAGE_LUN_WWID_LEN)) + return gli; + + return NULL; +} + +/** + * find_and_create_lun() - find or create a local LUN information structure + * @sdev: SCSI device associated with LUN. + * @wwid: WWID associated with LUN. + * + * The LUN is kept both in a local list (per adapter) and in a global list + * (across all adapters). Certain attributes of the LUN are local to the + * adapter (such as index, port selection mask, etc.). + * + * The block allocation map is shared across all adapters (i.e. associated + * wih the global list). Since different attributes are associated with + * the per adapter and global entries, allocate two separate structures for each + * LUN (one local, one global). + * + * Keep a pointer back from the local to the global entry. + * + * This routine assumes the caller holds the global mutex. + * + * Return: Found/Allocated local lun_info structure on success, NULL on failure + */ +static struct llun_info *find_and_create_lun(struct scsi_device *sdev, u8 *wwid) +{ + struct llun_info *lli = NULL; + struct glun_info *gli = NULL; + struct Scsi_Host *shost = sdev->host; + struct cxlflash_cfg *cfg = shost_priv(shost); + + if (unlikely(!wwid)) + goto out; + + lli = lookup_local(cfg, wwid); + if (lli) + goto out; + + lli = create_local(sdev, wwid); + if (unlikely(!lli)) + goto out; + + gli = lookup_global(wwid); + if (gli) { + lli->parent = gli; + list_add(&lli->list, &cfg->lluns); + goto out; + } + + gli = create_global(sdev, wwid); + if (unlikely(!gli)) { + kfree(lli); + lli = NULL; + goto out; + } + + lli->parent = gli; + list_add(&lli->list, &cfg->lluns); + + list_add(&gli->list, &global.gluns); + +out: + pr_debug("%s: returning %p\n", __func__, lli); + return lli; +} + +/** + * cxlflash_term_local_luns() - Delete all entries from local LUN list, free. + * @cfg: Internal structure associated with the host. + */ +void cxlflash_term_local_luns(struct cxlflash_cfg *cfg) +{ + struct llun_info *lli, *temp; + + mutex_lock(&global.mutex); + list_for_each_entry_safe(lli, temp, &cfg->lluns, list) { + list_del(&lli->list); + kfree(lli); + } + mutex_unlock(&global.mutex); +} + +/** + * cxlflash_list_init() - initializes the global LUN list + */ +void cxlflash_list_init(void) +{ + INIT_LIST_HEAD(&global.gluns); + mutex_init(&global.mutex); + global.err_page = NULL; +} + +/** + * cxlflash_term_global_luns() - frees resources associated with global LUN list + */ +void cxlflash_term_global_luns(void) +{ + struct glun_info *gli, *temp; + + mutex_lock(&global.mutex); + list_for_each_entry_safe(gli, temp, &global.gluns, list) { + list_del(&gli->list); + cxlflash_ba_terminate(&gli->blka.ba_lun); + kfree(gli); + } + mutex_unlock(&global.mutex); +} + +/** + * cxlflash_manage_lun() - handles LUN management activities + * @sdev: SCSI device associated with LUN. + * @manage: Manage ioctl data structure. + * + * This routine is used to notify the driver about a LUN's WWID and associate + * SCSI devices (sdev) with a global LUN instance. Additionally it serves to + * change a LUN's operating mode: legacy or superpipe. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_manage_lun(struct scsi_device *sdev, + struct dk_cxlflash_manage_lun *manage) +{ + int rc = 0; + struct llun_info *lli = NULL; + u64 flags = manage->hdr.flags; + u32 chan = sdev->channel; + + mutex_lock(&global.mutex); + lli = find_and_create_lun(sdev, manage->wwid); + pr_debug("%s: ENTER: WWID = %016llX%016llX, flags = %016llX li = %p\n", + __func__, get_unaligned_be64(&manage->wwid[0]), + get_unaligned_be64(&manage->wwid[8]), + manage->hdr.flags, lli); + if (unlikely(!lli)) { + rc = -ENOMEM; + goto out; + } + + if (flags & DK_CXLFLASH_MANAGE_LUN_ENABLE_SUPERPIPE) { + /* + * Update port selection mask based upon channel, store off LUN + * in unpacked, AFU-friendly format, and hang LUN reference in + * the sdev. + */ + lli->port_sel |= CHAN2PORT(chan); + lli->lun_id[chan] = lun_to_lunid(sdev->lun); + sdev->hostdata = lli; + } else if (flags & DK_CXLFLASH_MANAGE_LUN_DISABLE_SUPERPIPE) { + if (lli->parent->mode != MODE_NONE) + rc = -EBUSY; + else { + sdev->hostdata = NULL; + lli->port_sel &= ~CHAN2PORT(chan); + } + } + + pr_debug("%s: port_sel = %08X chan = %u lun_id = %016llX\n", __func__, + lli->port_sel, chan, lli->lun_id[chan]); + +out: + mutex_unlock(&global.mutex); + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} diff --git a/kernel/drivers/scsi/cxlflash/main.c b/kernel/drivers/scsi/cxlflash/main.c new file mode 100644 index 000000000..1e5bf0ca8 --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/main.c @@ -0,0 +1,2608 @@ +/* + * 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 <uapi/scsi/cxlflash_ioctl.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"); + +/** + * 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. + */ +static struct afu_cmd *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_devel("%s: returning found index=%d cmd=%p\n", + __func__, cmd->slot, cmd); + memset(cmd->buf, 0, CMD_BUFSIZE); + memset(cmd->rcb.cdb, 0, sizeof(cmd->rcb.cdb)); + return cmd; + } + } + + return NULL; +} + +/** + * 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. + */ +static void 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_devel("%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; + u32 resid; + + if (unlikely(!cmd)) + return; + + ioarcb = &(cmd->rcb); + ioasa = &(cmd->sa); + + if (ioasa->rc.flags & SISL_RC_FLAGS_UNDERRUN) { + resid = ioasa->resid; + scsi_set_resid(scp, resid); + pr_debug("%s: cmd underrun cmd = %p scp = %p, resid = %d\n", + __func__, cmd, scp, resid); + } + + 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_extra=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. + */ + 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_NO_CONNECT << 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; + 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.ioasc)) + process_cmd_err(cmd, scp); + else + scp->result = (DID_OK << 16); + + cmd_is_tmf = cmd->cmd_tmf; + cmd_checkin(cmd); /* Don't use cmd after here */ + + pr_debug_ratelimited("%s: calling scsi_done scp=%p result=%X " + "ioasc=%d\n", __func__, scp, scp->result, + cmd->sa.ioasc); + + scsi_dma_unmap(scp); + scp->scsi_done(scp); + + if (cmd_is_tmf) { + spin_lock_irqsave(&cfg->tmf_slock, lock_flags); + cfg->tmf_active = false; + wake_up_all_locked(&cfg->tmf_waitq); + spin_unlock_irqrestore(&cfg->tmf_slock, lock_flags); + } + } else + complete(&cmd->cevent); +} + +/** + * context_reset() - timeout handler for AFU commands + * @cmd: AFU command that timed out. + * + * Sends a reset to the AFU. + */ +static void context_reset(struct afu_cmd *cmd) +{ + int nretry = 0; + u64 rrin = 0x1; + u64 room = 0; + struct afu *afu = cmd->parent; + ulong lock_flags; + + pr_debug("%s: cmd=%p\n", __func__, cmd); + + spin_lock_irqsave(&cmd->slock, lock_flags); + + /* Already completed? */ + if (cmd->sa.host_use_b[0] & B_DONE) { + spin_unlock_irqrestore(&cmd->slock, lock_flags); + return; + } + + cmd->sa.host_use_b[0] |= (B_DONE | B_ERROR | B_TIMEOUT); + spin_unlock_irqrestore(&cmd->slock, lock_flags); + + /* + * We really want to send this reset at all costs, so spread + * out wait time on successive retries for available room. + */ + do { + room = readq_be(&afu->host_map->cmd_room); + atomic64_set(&afu->room, room); + if (room) + goto write_rrin; + udelay(nretry); + } while (nretry++ < MC_ROOM_RETRY_CNT); + + pr_err("%s: no cmd_room to send reset\n", __func__); + return; + +write_rrin: + nretry = 0; + writeq_be(rrin, &afu->host_map->ioarrin); + do { + rrin = readq_be(&afu->host_map->ioarrin); + if (rrin != 0x1) + break; + /* Double delay each time */ + udelay(2 << nretry); + } while (nretry++ < MC_ROOM_RETRY_CNT); +} + +/** + * send_cmd() - sends an AFU command + * @afu: AFU associated with the host. + * @cmd: AFU command to send. + * + * Return: + * 0 on success, SCSI_MLQUEUE_HOST_BUSY on failure + */ +static int send_cmd(struct afu *afu, struct afu_cmd *cmd) +{ + struct cxlflash_cfg *cfg = afu->parent; + struct device *dev = &cfg->dev->dev; + int nretry = 0; + int rc = 0; + u64 room; + long newval; + + /* + * This routine is used by critical users such an AFU sync and to + * send a task management function (TMF). Thus we want to retry a + * bit before returning an error. To avoid the performance penalty + * of MMIO, we spread the update of 'room' over multiple commands. + */ +retry: + newval = atomic64_dec_if_positive(&afu->room); + if (!newval) { + do { + room = readq_be(&afu->host_map->cmd_room); + atomic64_set(&afu->room, room); + if (room) + goto write_ioarrin; + udelay(nretry); + } while (nretry++ < MC_ROOM_RETRY_CNT); + + dev_err(dev, "%s: no cmd_room to send 0x%X\n", + __func__, cmd->rcb.cdb[0]); + + goto no_room; + } else if (unlikely(newval < 0)) { + /* This should be rare. i.e. Only if two threads race and + * decrement before the MMIO read is done. In this case + * just benefit from the other thread having updated + * afu->room. + */ + if (nretry++ < MC_ROOM_RETRY_CNT) { + udelay(nretry); + goto retry; + } + + goto no_room; + } + +write_ioarrin: + writeq_be((u64)&cmd->rcb, &afu->host_map->ioarrin); +out: + pr_devel("%s: cmd=%p len=%d ea=%p rc=%d\n", __func__, cmd, + cmd->rcb.data_len, (void *)cmd->rcb.data_ea, rc); + return rc; + +no_room: + afu->read_room = true; + schedule_work(&cfg->work_q); + rc = SCSI_MLQUEUE_HOST_BUSY; + goto out; +} + +/** + * wait_resp() - polls for a response or timeout to a sent AFU command + * @afu: AFU associated with the host. + * @cmd: AFU command that was sent. + */ +static void wait_resp(struct afu *afu, struct afu_cmd *cmd) +{ + ulong timeout = msecs_to_jiffies(cmd->rcb.timeout * 2 * 1000); + + timeout = wait_for_completion_timeout(&cmd->cevent, timeout); + if (!timeout) + context_reset(cmd); + + if (unlikely(cmd->sa.ioasc != 0)) + pr_err("%s: CMD 0x%X failed, IOASC: flags 0x%X, afu_rc 0x%X, " + "scsi_rc 0x%X, fc_rc 0x%X\n", __func__, cmd->rcb.cdb[0], + cmd->sa.rc.flags, cmd->sa.rc.afu_rc, cmd->sa.rc.scsi_rc, + cmd->sa.rc.fc_rc); +} + +/** + * 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 on failure + */ +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; + struct device *dev = &cfg->dev->dev; + ulong lock_flags; + int rc = 0; + ulong to; + + cmd = cmd_checkout(afu); + if (unlikely(!cmd)) { + dev_err(dev, "%s: could not get a free command\n", __func__); + rc = SCSI_MLQUEUE_HOST_BUSY; + goto out; + } + + /* When Task Management Function is active do not send another */ + spin_lock_irqsave(&cfg->tmf_slock, lock_flags); + if (cfg->tmf_active) + wait_event_interruptible_lock_irq(cfg->tmf_waitq, + !cfg->tmf_active, + cfg->tmf_slock); + cfg->tmf_active = true; + cmd->cmd_tmf = true; + spin_unlock_irqrestore(&cfg->tmf_slock, 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 = send_cmd(afu, cmd); + if (unlikely(rc)) { + cmd_checkin(cmd); + spin_lock_irqsave(&cfg->tmf_slock, lock_flags); + cfg->tmf_active = false; + spin_unlock_irqrestore(&cfg->tmf_slock, lock_flags); + goto out; + } + + spin_lock_irqsave(&cfg->tmf_slock, lock_flags); + to = msecs_to_jiffies(5000); + to = wait_event_interruptible_lock_irq_timeout(cfg->tmf_waitq, + !cfg->tmf_active, + cfg->tmf_slock, + to); + if (!to) { + cfg->tmf_active = false; + dev_err(dev, "%s: TMF timed out!\n", __func__); + rc = -1; + } + spin_unlock_irqrestore(&cfg->tmf_slock, 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 on failure + */ +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 device *dev = &cfg->dev->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; + + dev_dbg_ratelimited(dev, "%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_slock, lock_flags); + if (cfg->tmf_active) { + spin_unlock_irqrestore(&cfg->tmf_slock, lock_flags); + rc = SCSI_MLQUEUE_HOST_BUSY; + goto out; + } + spin_unlock_irqrestore(&cfg->tmf_slock, lock_flags); + + switch (cfg->state) { + case STATE_RESET: + dev_dbg_ratelimited(dev, "%s: device is in reset!\n", __func__); + rc = SCSI_MLQUEUE_HOST_BUSY; + goto out; + case STATE_FAILTERM: + dev_dbg_ratelimited(dev, "%s: device has failed!\n", __func__); + scp->result = (DID_NO_CONNECT << 16); + scp->scsi_done(scp); + rc = 0; + goto out; + default: + break; + } + + cmd = cmd_checkout(afu); + if (unlikely(!cmd)) { + dev_err(dev, "%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(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 = send_cmd(afu, cmd); + if (unlikely(rc)) { + cmd_checkin(cmd); + scsi_dma_unmap(scp); + } + +out: + pr_devel("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * cxlflash_wait_for_pci_err_recovery() - wait for error recovery during probe + * @cfg: 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->reset_waitq, + !pci_channel_offline(pdev), + CXLFLASH_PCI_ERROR_RECOVERY_TIMEOUT); +} + +/** + * free_mem() - free memory associated with the AFU + * @cfg: 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 + * @cfg: 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 __iomem *)afu->afu_map); + afu->afu_map = NULL; + } + } +} + +/** + * term_mc() - terminates the master context + * @cfg: 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; + struct device *dev = &cfg->dev->dev; + + if (!afu || !cfg->mcctx) { + dev_err(dev, "%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 + * @cfg: 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_slock, lock_flags); + if (cfg->tmf_active) + wait_event_interruptible_lock_irq(cfg->tmf_waitq, + !cfg->tmf_active, + cfg->tmf_slock); + spin_unlock_irqrestore(&cfg->tmf_slock, lock_flags); + + cfg->state = STATE_FAILTERM; + cxlflash_stop_term_user_contexts(cfg); + + switch (cfg->init_state) { + case INIT_STATE_SCSI: + cxlflash_term_local_luns(cfg); + scsi_remove_host(cfg->host); + /* fall through */ + case INIT_STATE_AFU: + term_afu(cfg); + cancel_work_sync(&cfg->work_q); + case INIT_STATE_PCI: + pci_release_regions(cfg->dev); + pci_disable_device(pdev); + case INIT_STATE_NONE: + free_mem(cfg); + scsi_host_put(cfg->host); + break; + } + + pr_debug("%s: returning\n", __func__); +} + +/** + * alloc_mem() - allocates the AFU and its command pool + * @cfg: 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; + struct device *dev = &cfg->dev->dev; + + /* AFU is ~12k, i.e. only one 64k page or up to four 4k pages */ + cfg->afu = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, + get_order(sizeof(struct afu))); + if (unlikely(!cfg->afu)) { + dev_err(dev, "%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)) { + dev_err(dev, + "%s: Allocate command buffers fail!\n", + __func__); + rc = -ENOMEM; + free_mem(cfg); + goto out; + } + } + + cfg->afu->cmd[i].buf = buf; + atomic_set(&cfg->afu->cmd[i].free, 1); + cfg->afu->cmd[i].slot = i; + } + +out: + return rc; +} + +/** + * init_pci() - initializes the host as a PCI device + * @cfg: Internal structure associated with the host. + * + * Return: 0 on success, -errno on failure + */ +static int init_pci(struct cxlflash_cfg *cfg) +{ + struct pci_dev *pdev = cfg->dev; + int rc = 0; + + cfg->cxlflash_regs_pci = pci_resource_start(pdev, 0); + rc = pci_request_regions(pdev, CXLFLASH_NAME); + if (rc < 0) { + dev_err(&pdev->dev, + "%s: Couldn't register memory range of registers\n", + __func__); + goto out; + } + + rc = pci_enable_device(pdev); + if (rc || pci_channel_offline(pdev)) { + if (pci_channel_offline(pdev)) { + cxlflash_wait_for_pci_err_recovery(cfg); + rc = pci_enable_device(pdev); + } + + if (rc) { + dev_err(&pdev->dev, "%s: Cannot enable adapter\n", + __func__); + cxlflash_wait_for_pci_err_recovery(cfg); + goto out_release_regions; + } + } + + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); + if (rc < 0) { + dev_dbg(&pdev->dev, "%s: Failed to set 64 bit PCI DMA mask\n", + __func__); + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + } + + if (rc < 0) { + dev_err(&pdev->dev, "%s: Failed to set PCI DMA mask\n", + __func__); + goto out_disable; + } + + pci_set_master(pdev); + + if (pci_channel_offline(pdev)) { + cxlflash_wait_for_pci_err_recovery(cfg); + if (pci_channel_offline(pdev)) { + rc = -EIO; + goto out_msi_disable; + } + } + + rc = pci_save_state(pdev); + + if (rc != PCIBIOS_SUCCESSFUL) { + dev_err(&pdev->dev, "%s: Failed to save PCI config space\n", + __func__); + rc = -EIO; + goto cleanup_nolog; + } + +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; + +cleanup_nolog: +out_msi_disable: + cxlflash_wait_for_pci_err_recovery(cfg); +out_disable: + pci_disable_device(pdev); +out_release_regions: + pci_release_regions(pdev); + goto out; + +} + +/** + * init_scsi() - adds the host to the SCSI stack and kicks off host scan + * @cfg: Internal structure associated with the host. + * + * Return: 0 on success, -errno on failure + */ +static int init_scsi(struct cxlflash_cfg *cfg) +{ + struct pci_dev *pdev = cfg->dev; + int rc = 0; + + rc = scsi_add_host(cfg->host, &pdev->dev); + if (rc) { + dev_err(&pdev->dev, "%s: scsi_add_host failed (rc=%d)\n", + __func__, rc); + goto out; + } + + scsi_scan_host(cfg->host); + +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * set_port_online() - transitions the specified host FC port to online state + * @fc_regs: Top of MMIO region defined for specified port. + * + * The provided MMIO region must be mapped prior to call. Online state means + * that the FC link layer has synced, completed the handshaking process, and + * is ready for login to start. + */ +static void set_port_online(__be64 __iomem *fc_regs) +{ + u64 cmdcfg; + + cmdcfg = readq_be(&fc_regs[FC_MTIP_CMDCONFIG / 8]); + cmdcfg &= (~FC_MTIP_CMDCONFIG_OFFLINE); /* clear OFF_LINE */ + cmdcfg |= (FC_MTIP_CMDCONFIG_ONLINE); /* set ON_LINE */ + writeq_be(cmdcfg, &fc_regs[FC_MTIP_CMDCONFIG / 8]); +} + +/** + * set_port_offline() - transitions the specified host FC port to offline state + * @fc_regs: Top of MMIO region defined for specified port. + * + * The provided MMIO region must be mapped prior to call. + */ +static void set_port_offline(__be64 __iomem *fc_regs) +{ + u64 cmdcfg; + + cmdcfg = readq_be(&fc_regs[FC_MTIP_CMDCONFIG / 8]); + cmdcfg &= (~FC_MTIP_CMDCONFIG_ONLINE); /* clear ON_LINE */ + cmdcfg |= (FC_MTIP_CMDCONFIG_OFFLINE); /* set OFF_LINE */ + writeq_be(cmdcfg, &fc_regs[FC_MTIP_CMDCONFIG / 8]); +} + +/** + * wait_port_online() - waits for the specified host FC port come online + * @fc_regs: Top of MMIO region defined for specified port. + * @delay_us: Number of microseconds to delay between reading port status. + * @nretry: Number of cycles to retry reading port status. + * + * The provided MMIO region must be mapped prior to call. This will timeout + * when the cable is not plugged in. + * + * Return: + * TRUE (1) when the specified port is online + * FALSE (0) when the specified port fails to come online after timeout + * -EINVAL when @delay_us is less than 1000 + */ +static int wait_port_online(__be64 __iomem *fc_regs, u32 delay_us, u32 nretry) +{ + u64 status; + + if (delay_us < 1000) { + pr_err("%s: invalid delay specified %d\n", __func__, delay_us); + return -EINVAL; + } + + do { + msleep(delay_us / 1000); + status = readq_be(&fc_regs[FC_MTIP_STATUS / 8]); + } while ((status & FC_MTIP_STATUS_MASK) != FC_MTIP_STATUS_ONLINE && + nretry--); + + return ((status & FC_MTIP_STATUS_MASK) == FC_MTIP_STATUS_ONLINE); +} + +/** + * wait_port_offline() - waits for the specified host FC port go offline + * @fc_regs: Top of MMIO region defined for specified port. + * @delay_us: Number of microseconds to delay between reading port status. + * @nretry: Number of cycles to retry reading port status. + * + * The provided MMIO region must be mapped prior to call. + * + * Return: + * TRUE (1) when the specified port is offline + * FALSE (0) when the specified port fails to go offline after timeout + * -EINVAL when @delay_us is less than 1000 + */ +static int wait_port_offline(__be64 __iomem *fc_regs, u32 delay_us, u32 nretry) +{ + u64 status; + + if (delay_us < 1000) { + pr_err("%s: invalid delay specified %d\n", __func__, delay_us); + return -EINVAL; + } + + do { + msleep(delay_us / 1000); + status = readq_be(&fc_regs[FC_MTIP_STATUS / 8]); + } while ((status & FC_MTIP_STATUS_MASK) != FC_MTIP_STATUS_OFFLINE && + nretry--); + + return ((status & FC_MTIP_STATUS_MASK) == FC_MTIP_STATUS_OFFLINE); +} + +/** + * afu_set_wwpn() - configures the WWPN for the specified host FC port + * @afu: AFU associated with the host that owns the specified FC port. + * @port: Port number being configured. + * @fc_regs: Top of MMIO region defined for specified port. + * @wwpn: The world-wide-port-number previously discovered for port. + * + * The provided MMIO region must be mapped prior to call. As part of the + * sequence to configure the WWPN, the port is toggled offline and then back + * online. This toggling action can cause this routine to delay up to a few + * seconds. When configured to use the internal LUN feature of the AFU, a + * failure to come online is overridden. + * + * Return: + * 0 when the WWPN is successfully written and the port comes back online + * -1 when the port fails to go offline or come back up online + */ +static int afu_set_wwpn(struct afu *afu, int port, __be64 __iomem *fc_regs, + u64 wwpn) +{ + int rc = 0; + + set_port_offline(fc_regs); + + if (!wait_port_offline(fc_regs, FC_PORT_STATUS_RETRY_INTERVAL_US, + FC_PORT_STATUS_RETRY_CNT)) { + pr_debug("%s: wait on port %d to go offline timed out\n", + __func__, port); + rc = -1; /* but continue on to leave the port back online */ + } + + if (rc == 0) + writeq_be(wwpn, &fc_regs[FC_PNAME / 8]); + + /* Always return success after programming WWPN */ + rc = 0; + + set_port_online(fc_regs); + + if (!wait_port_online(fc_regs, FC_PORT_STATUS_RETRY_INTERVAL_US, + FC_PORT_STATUS_RETRY_CNT)) { + pr_err("%s: wait on port %d to go online timed out\n", + __func__, port); + } + + pr_debug("%s: returning rc=%d\n", __func__, rc); + + return rc; +} + +/** + * afu_link_reset() - resets the specified host FC port + * @afu: AFU associated with the host that owns the specified FC port. + * @port: Port number being configured. + * @fc_regs: Top of MMIO region defined for specified port. + * + * The provided MMIO region must be mapped prior to call. The sequence to + * reset the port involves toggling it offline and then back online. This + * action can cause this routine to delay up to a few seconds. An effort + * is made to maintain link with the device by switching to host to use + * the alternate port exclusively while the reset takes place. + * failure to come online is overridden. + */ +static void afu_link_reset(struct afu *afu, int port, __be64 __iomem *fc_regs) +{ + u64 port_sel; + + /* first switch the AFU to the other links, if any */ + port_sel = readq_be(&afu->afu_map->global.regs.afu_port_sel); + port_sel &= ~(1ULL << port); + writeq_be(port_sel, &afu->afu_map->global.regs.afu_port_sel); + cxlflash_afu_sync(afu, 0, 0, AFU_GSYNC); + + set_port_offline(fc_regs); + if (!wait_port_offline(fc_regs, FC_PORT_STATUS_RETRY_INTERVAL_US, + FC_PORT_STATUS_RETRY_CNT)) + pr_err("%s: wait on port %d to go offline timed out\n", + __func__, port); + + set_port_online(fc_regs); + if (!wait_port_online(fc_regs, FC_PORT_STATUS_RETRY_INTERVAL_US, + FC_PORT_STATUS_RETRY_CNT)) + pr_err("%s: wait on port %d to go online timed out\n", + __func__, port); + + /* switch back to include this port */ + port_sel |= (1ULL << port); + writeq_be(port_sel, &afu->afu_map->global.regs.afu_port_sel); + cxlflash_afu_sync(afu, 0, 0, AFU_GSYNC); + + pr_debug("%s: returning port_sel=%lld\n", __func__, port_sel); +} + +/* + * Asynchronous interrupt information table + */ +static const struct asyc_intr_info ainfo[] = { + {SISL_ASTATUS_FC0_OTHER, "other error", 0, CLR_FC_ERROR | LINK_RESET}, + {SISL_ASTATUS_FC0_LOGO, "target initiated LOGO", 0, 0}, + {SISL_ASTATUS_FC0_CRC_T, "CRC threshold exceeded", 0, LINK_RESET}, + {SISL_ASTATUS_FC0_LOGI_R, "login timed out, retrying", 0, LINK_RESET}, + {SISL_ASTATUS_FC0_LOGI_F, "login failed", 0, CLR_FC_ERROR}, + {SISL_ASTATUS_FC0_LOGI_S, "login succeeded", 0, SCAN_HOST}, + {SISL_ASTATUS_FC0_LINK_DN, "link down", 0, 0}, + {SISL_ASTATUS_FC0_LINK_UP, "link up", 0, SCAN_HOST}, + {SISL_ASTATUS_FC1_OTHER, "other error", 1, CLR_FC_ERROR | LINK_RESET}, + {SISL_ASTATUS_FC1_LOGO, "target initiated LOGO", 1, 0}, + {SISL_ASTATUS_FC1_CRC_T, "CRC threshold exceeded", 1, LINK_RESET}, + {SISL_ASTATUS_FC1_LOGI_R, "login timed out, retrying", 1, 0}, + {SISL_ASTATUS_FC1_LOGI_F, "login failed", 1, CLR_FC_ERROR}, + {SISL_ASTATUS_FC1_LOGI_S, "login succeeded", 1, SCAN_HOST}, + {SISL_ASTATUS_FC1_LINK_DN, "link down", 1, 0}, + {SISL_ASTATUS_FC1_LINK_UP, "link up", 1, SCAN_HOST}, + {0x0, "", 0, 0} /* terminator */ +}; + +/** + * find_ainfo() - locates and returns asynchronous interrupt information + * @status: Status code set by AFU on error. + * + * Return: The located information or NULL when the status code is invalid. + */ +static const struct asyc_intr_info *find_ainfo(u64 status) +{ + const struct asyc_intr_info *info; + + for (info = &ainfo[0]; info->status; info++) + if (info->status == status) + return info; + + return NULL; +} + +/** + * afu_err_intr_init() - clears and initializes the AFU for error interrupts + * @afu: AFU associated with the host. + */ +static void afu_err_intr_init(struct afu *afu) +{ + int i; + u64 reg; + + /* global async interrupts: AFU clears afu_ctrl on context exit + * if async interrupts were sent to that context. This prevents + * the AFU form sending further async interrupts when + * there is + * nobody to receive them. + */ + + /* mask all */ + writeq_be(-1ULL, &afu->afu_map->global.regs.aintr_mask); + /* set LISN# to send and point to master context */ + reg = ((u64) (((afu->ctx_hndl << 8) | SISL_MSI_ASYNC_ERROR)) << 40); + + if (afu->internal_lun) + reg |= 1; /* Bit 63 indicates local lun */ + writeq_be(reg, &afu->afu_map->global.regs.afu_ctrl); + /* clear all */ + writeq_be(-1ULL, &afu->afu_map->global.regs.aintr_clear); + /* unmask bits that are of interest */ + /* note: afu can send an interrupt after this step */ + writeq_be(SISL_ASTATUS_MASK, &afu->afu_map->global.regs.aintr_mask); + /* clear again in case a bit came on after previous clear but before */ + /* unmask */ + writeq_be(-1ULL, &afu->afu_map->global.regs.aintr_clear); + + /* Clear/Set internal lun bits */ + reg = readq_be(&afu->afu_map->global.fc_regs[0][FC_CONFIG2 / 8]); + reg &= SISL_FC_INTERNAL_MASK; + if (afu->internal_lun) + reg |= ((u64)(afu->internal_lun - 1) << SISL_FC_INTERNAL_SHIFT); + writeq_be(reg, &afu->afu_map->global.fc_regs[0][FC_CONFIG2 / 8]); + + /* now clear FC errors */ + for (i = 0; i < NUM_FC_PORTS; i++) { + writeq_be(0xFFFFFFFFU, + &afu->afu_map->global.fc_regs[i][FC_ERROR / 8]); + writeq_be(0, &afu->afu_map->global.fc_regs[i][FC_ERRCAP / 8]); + } + + /* sync interrupts for master's IOARRIN write */ + /* note that unlike asyncs, there can be no pending sync interrupts */ + /* at this time (this is a fresh context and master has not written */ + /* IOARRIN yet), so there is nothing to clear. */ + + /* set LISN#, it is always sent to the context that wrote IOARRIN */ + writeq_be(SISL_MSI_SYNC_ERROR, &afu->host_map->ctx_ctrl); + writeq_be(SISL_ISTATUS_MASK, &afu->host_map->intr_mask); +} + +/** + * cxlflash_sync_err_irq() - interrupt handler for synchronous errors + * @irq: Interrupt number. + * @data: Private data provided at interrupt registration, the AFU. + * + * Return: Always return IRQ_HANDLED. + */ +static irqreturn_t cxlflash_sync_err_irq(int irq, void *data) +{ + struct afu *afu = (struct afu *)data; + u64 reg; + u64 reg_unmasked; + + reg = readq_be(&afu->host_map->intr_status); + reg_unmasked = (reg & SISL_ISTATUS_UNMASK); + + if (reg_unmasked == 0UL) { + pr_err("%s: %llX: spurious interrupt, intr_status %016llX\n", + __func__, (u64)afu, reg); + goto cxlflash_sync_err_irq_exit; + } + + pr_err("%s: %llX: unexpected interrupt, intr_status %016llX\n", + __func__, (u64)afu, reg); + + writeq_be(reg_unmasked, &afu->host_map->intr_clear); + +cxlflash_sync_err_irq_exit: + pr_debug("%s: returning rc=%d\n", __func__, IRQ_HANDLED); + return IRQ_HANDLED; +} + +/** + * cxlflash_rrq_irq() - interrupt handler for read-response queue (normal path) + * @irq: Interrupt number. + * @data: Private data provided at interrupt registration, the AFU. + * + * Return: Always return IRQ_HANDLED. + */ +static irqreturn_t cxlflash_rrq_irq(int irq, void *data) +{ + struct afu *afu = (struct afu *)data; + struct afu_cmd *cmd; + bool toggle = afu->toggle; + u64 entry, + *hrrq_start = afu->hrrq_start, + *hrrq_end = afu->hrrq_end, + *hrrq_curr = afu->hrrq_curr; + + /* Process however many RRQ entries that are ready */ + while (true) { + entry = *hrrq_curr; + + if ((entry & SISL_RESP_HANDLE_T_BIT) != toggle) + break; + + cmd = (struct afu_cmd *)(entry & ~SISL_RESP_HANDLE_T_BIT); + cmd_complete(cmd); + + /* Advance to next entry or wrap and flip the toggle bit */ + if (hrrq_curr < hrrq_end) + hrrq_curr++; + else { + hrrq_curr = hrrq_start; + toggle ^= SISL_RESP_HANDLE_T_BIT; + } + } + + afu->hrrq_curr = hrrq_curr; + afu->toggle = toggle; + + return IRQ_HANDLED; +} + +/** + * cxlflash_async_err_irq() - interrupt handler for asynchronous errors + * @irq: Interrupt number. + * @data: Private data provided at interrupt registration, the AFU. + * + * Return: Always return IRQ_HANDLED. + */ +static irqreturn_t cxlflash_async_err_irq(int irq, void *data) +{ + struct afu *afu = (struct afu *)data; + struct cxlflash_cfg *cfg = afu->parent; + struct device *dev = &cfg->dev->dev; + u64 reg_unmasked; + const struct asyc_intr_info *info; + struct sisl_global_map __iomem *global = &afu->afu_map->global; + u64 reg; + u8 port; + int i; + + reg = readq_be(&global->regs.aintr_status); + reg_unmasked = (reg & SISL_ASTATUS_UNMASK); + + if (reg_unmasked == 0) { + dev_err(dev, "%s: spurious interrupt, aintr_status 0x%016llX\n", + __func__, reg); + goto out; + } + + /* FYI, it is 'okay' to clear AFU status before FC_ERROR */ + writeq_be(reg_unmasked, &global->regs.aintr_clear); + + /* Check each bit that is on */ + for (i = 0; reg_unmasked; i++, reg_unmasked = (reg_unmasked >> 1)) { + info = find_ainfo(1ULL << i); + if (((reg_unmasked & 0x1) == 0) || !info) + continue; + + port = info->port; + + dev_err(dev, "%s: FC Port %d -> %s, fc_status 0x%08llX\n", + __func__, port, info->desc, + readq_be(&global->fc_regs[port][FC_STATUS / 8])); + + /* + * Do link reset first, some OTHER errors will set FC_ERROR + * again if cleared before or w/o a reset + */ + if (info->action & LINK_RESET) { + dev_err(dev, "%s: FC Port %d: resetting link\n", + __func__, port); + cfg->lr_state = LINK_RESET_REQUIRED; + cfg->lr_port = port; + schedule_work(&cfg->work_q); + } + + if (info->action & CLR_FC_ERROR) { + reg = readq_be(&global->fc_regs[port][FC_ERROR / 8]); + + /* + * Since all errors are unmasked, FC_ERROR and FC_ERRCAP + * should be the same and tracing one is sufficient. + */ + + dev_err(dev, "%s: fc %d: clearing fc_error 0x%08llX\n", + __func__, port, reg); + + writeq_be(reg, &global->fc_regs[port][FC_ERROR / 8]); + writeq_be(0, &global->fc_regs[port][FC_ERRCAP / 8]); + } + + if (info->action & SCAN_HOST) { + atomic_inc(&cfg->scan_host_needed); + schedule_work(&cfg->work_q); + } + } + +out: + dev_dbg(dev, "%s: returning IRQ_HANDLED, afu=%p\n", __func__, afu); + return IRQ_HANDLED; +} + +/** + * start_context() - starts the master context + * @cfg: Internal structure associated with the host. + * + * Return: A success or failure value from CXL services. + */ +static int start_context(struct cxlflash_cfg *cfg) +{ + int rc = 0; + + rc = cxl_start_context(cfg->mcctx, + cfg->afu->work.work_element_descriptor, + NULL); + + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * read_vpd() - obtains the WWPNs from VPD + * @cfg: Internal structure associated with the host. + * @wwpn: Array of size NUM_FC_PORTS to pass back WWPNs + * + * Return: 0 on success, -errno on failure + */ +static int read_vpd(struct cxlflash_cfg *cfg, u64 wwpn[]) +{ + struct pci_dev *dev = cfg->parent_dev; + int rc = 0; + int ro_start, ro_size, i, j, k; + ssize_t vpd_size; + char vpd_data[CXLFLASH_VPD_LEN]; + char tmp_buf[WWPN_BUF_LEN] = { 0 }; + char *wwpn_vpd_tags[NUM_FC_PORTS] = { "V5", "V6" }; + + /* Get the VPD data from the device */ + vpd_size = pci_read_vpd(dev, 0, sizeof(vpd_data), vpd_data); + if (unlikely(vpd_size <= 0)) { + dev_err(&dev->dev, "%s: Unable to read VPD (size = %ld)\n", + __func__, vpd_size); + rc = -ENODEV; + goto out; + } + + /* Get the read only section offset */ + ro_start = pci_vpd_find_tag(vpd_data, 0, vpd_size, + PCI_VPD_LRDT_RO_DATA); + if (unlikely(ro_start < 0)) { + dev_err(&dev->dev, "%s: VPD Read-only data not found\n", + __func__); + rc = -ENODEV; + goto out; + } + + /* Get the read only section size, cap when extends beyond read VPD */ + ro_size = pci_vpd_lrdt_size(&vpd_data[ro_start]); + j = ro_size; + i = ro_start + PCI_VPD_LRDT_TAG_SIZE; + if (unlikely((i + j) > vpd_size)) { + pr_debug("%s: Might need to read more VPD (%d > %ld)\n", + __func__, (i + j), vpd_size); + ro_size = vpd_size - i; + } + + /* + * Find the offset of the WWPN tag within the read only + * VPD data and validate the found field (partials are + * no good to us). Convert the ASCII data to an integer + * value. Note that we must copy to a temporary buffer + * because the conversion service requires that the ASCII + * string be terminated. + */ + for (k = 0; k < NUM_FC_PORTS; k++) { + j = ro_size; + i = ro_start + PCI_VPD_LRDT_TAG_SIZE; + + i = pci_vpd_find_info_keyword(vpd_data, i, j, wwpn_vpd_tags[k]); + if (unlikely(i < 0)) { + dev_err(&dev->dev, "%s: Port %d WWPN not found " + "in VPD\n", __func__, k); + rc = -ENODEV; + goto out; + } + + j = pci_vpd_info_field_size(&vpd_data[i]); + i += PCI_VPD_INFO_FLD_HDR_SIZE; + if (unlikely((i + j > vpd_size) || (j != WWPN_LEN))) { + dev_err(&dev->dev, "%s: Port %d WWPN incomplete or " + "VPD corrupt\n", + __func__, k); + rc = -ENODEV; + goto out; + } + + memcpy(tmp_buf, &vpd_data[i], WWPN_LEN); + rc = kstrtoul(tmp_buf, WWPN_LEN, (ulong *)&wwpn[k]); + if (unlikely(rc)) { + dev_err(&dev->dev, "%s: Fail to convert port %d WWPN " + "to integer\n", __func__, k); + rc = -ENODEV; + goto out; + } + } + +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * init_pcr() - initialize the provisioning and control registers + * @cfg: Internal structure associated with the host. + * + * Also sets up fast access to the mapped registers and initializes AFU + * command fields that never change. + */ +static void init_pcr(struct cxlflash_cfg *cfg) +{ + struct afu *afu = cfg->afu; + struct sisl_ctrl_map __iomem *ctrl_map; + int i; + + for (i = 0; i < MAX_CONTEXT; i++) { + ctrl_map = &afu->afu_map->ctrls[i].ctrl; + /* Disrupt any clients that could be running */ + /* e.g. clients that survived a master restart */ + writeq_be(0, &ctrl_map->rht_start); + writeq_be(0, &ctrl_map->rht_cnt_id); + writeq_be(0, &ctrl_map->ctx_cap); + } + + /* Copy frequently used fields into afu */ + afu->ctx_hndl = (u16) cxl_process_element(cfg->mcctx); + afu->host_map = &afu->afu_map->hosts[afu->ctx_hndl].host; + afu->ctrl_map = &afu->afu_map->ctrls[afu->ctx_hndl].ctrl; + + /* Program the Endian Control for the master context */ + writeq_be(SISL_ENDIAN_CTRL, &afu->host_map->endian_ctrl); + + /* Initialize cmd fields that never change */ + for (i = 0; i < CXLFLASH_NUM_CMDS; i++) { + afu->cmd[i].rcb.ctx_id = afu->ctx_hndl; + afu->cmd[i].rcb.msi = SISL_MSI_RRQ_UPDATED; + afu->cmd[i].rcb.rrq = 0x0; + } +} + +/** + * init_global() - initialize AFU global registers + * @cfg: Internal structure associated with the host. + */ +static int init_global(struct cxlflash_cfg *cfg) +{ + struct afu *afu = cfg->afu; + struct device *dev = &cfg->dev->dev; + u64 wwpn[NUM_FC_PORTS]; /* wwpn of AFU ports */ + int i = 0, num_ports = 0; + int rc = 0; + u64 reg; + + rc = read_vpd(cfg, &wwpn[0]); + if (rc) { + dev_err(dev, "%s: could not read vpd rc=%d\n", __func__, rc); + goto out; + } + + pr_debug("%s: wwpn0=0x%llX wwpn1=0x%llX\n", __func__, wwpn[0], wwpn[1]); + + /* Set up RRQ in AFU for master issued cmds */ + writeq_be((u64) afu->hrrq_start, &afu->host_map->rrq_start); + writeq_be((u64) afu->hrrq_end, &afu->host_map->rrq_end); + + /* AFU configuration */ + reg = readq_be(&afu->afu_map->global.regs.afu_config); + reg |= SISL_AFUCONF_AR_ALL|SISL_AFUCONF_ENDIAN; + /* enable all auto retry options and control endianness */ + /* leave others at default: */ + /* CTX_CAP write protected, mbox_r does not clear on read and */ + /* checker on if dual afu */ + writeq_be(reg, &afu->afu_map->global.regs.afu_config); + + /* Global port select: select either port */ + if (afu->internal_lun) { + /* Only use port 0 */ + writeq_be(PORT0, &afu->afu_map->global.regs.afu_port_sel); + num_ports = NUM_FC_PORTS - 1; + } else { + writeq_be(BOTH_PORTS, &afu->afu_map->global.regs.afu_port_sel); + num_ports = NUM_FC_PORTS; + } + + for (i = 0; i < num_ports; i++) { + /* Unmask all errors (but they are still masked at AFU) */ + writeq_be(0, &afu->afu_map->global.fc_regs[i][FC_ERRMSK / 8]); + /* Clear CRC error cnt & set a threshold */ + (void)readq_be(&afu->afu_map->global. + fc_regs[i][FC_CNT_CRCERR / 8]); + writeq_be(MC_CRC_THRESH, &afu->afu_map->global.fc_regs[i] + [FC_CRC_THRESH / 8]); + + /* Set WWPNs. If already programmed, wwpn[i] is 0 */ + if (wwpn[i] != 0 && + afu_set_wwpn(afu, i, + &afu->afu_map->global.fc_regs[i][0], + wwpn[i])) { + dev_err(dev, "%s: failed to set WWPN on port %d\n", + __func__, i); + rc = -EIO; + goto out; + } + /* Programming WWPN back to back causes additional + * offline/online transitions and a PLOGI + */ + msleep(100); + } + + /* Set up master's own CTX_CAP to allow real mode, host translation */ + /* tables, afu cmds and read/write GSCSI cmds. */ + /* First, unlock ctx_cap write by reading mbox */ + (void)readq_be(&afu->ctrl_map->mbox_r); /* unlock ctx_cap */ + writeq_be((SISL_CTX_CAP_REAL_MODE | SISL_CTX_CAP_HOST_XLATE | + SISL_CTX_CAP_READ_CMD | SISL_CTX_CAP_WRITE_CMD | + SISL_CTX_CAP_AFU_CMD | SISL_CTX_CAP_GSCSI_CMD), + &afu->ctrl_map->ctx_cap); + /* Initialize heartbeat */ + afu->hb = readq_be(&afu->afu_map->global.regs.afu_hb); + +out: + return rc; +} + +/** + * start_afu() - initializes and starts the AFU + * @cfg: Internal structure associated with the host. + */ +static int start_afu(struct cxlflash_cfg *cfg) +{ + struct afu *afu = cfg->afu; + struct afu_cmd *cmd; + + int i = 0; + int rc = 0; + + for (i = 0; i < CXLFLASH_NUM_CMDS; i++) { + cmd = &afu->cmd[i]; + + init_completion(&cmd->cevent); + spin_lock_init(&cmd->slock); + cmd->parent = afu; + } + + init_pcr(cfg); + + /* After an AFU reset, RRQ entries are stale, clear them */ + memset(&afu->rrq_entry, 0, sizeof(afu->rrq_entry)); + + /* Initialize RRQ pointers */ + afu->hrrq_start = &afu->rrq_entry[0]; + afu->hrrq_end = &afu->rrq_entry[NUM_RRQ_ENTRY - 1]; + afu->hrrq_curr = afu->hrrq_start; + afu->toggle = 1; + + rc = init_global(cfg); + + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * init_mc() - create and register as the master context + * @cfg: Internal structure associated with the host. + * + * Return: 0 on success, -errno on failure + */ +static int init_mc(struct cxlflash_cfg *cfg) +{ + struct cxl_context *ctx; + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + int rc = 0; + enum undo_level level; + + ctx = cxl_get_context(cfg->dev); + if (unlikely(!ctx)) + return -ENOMEM; + cfg->mcctx = ctx; + + /* Set it up as a master with the CXL */ + cxl_set_master(ctx); + + /* During initialization reset the AFU to start from a clean slate */ + rc = cxl_afu_reset(cfg->mcctx); + if (unlikely(rc)) { + dev_err(dev, "%s: initial AFU reset failed rc=%d\n", + __func__, rc); + level = RELEASE_CONTEXT; + goto out; + } + + rc = cxl_allocate_afu_irqs(ctx, 3); + if (unlikely(rc)) { + dev_err(dev, "%s: call to allocate_afu_irqs failed rc=%d!\n", + __func__, rc); + level = RELEASE_CONTEXT; + goto out; + } + + rc = cxl_map_afu_irq(ctx, 1, cxlflash_sync_err_irq, afu, + "SISL_MSI_SYNC_ERROR"); + if (unlikely(rc <= 0)) { + dev_err(dev, "%s: IRQ 1 (SISL_MSI_SYNC_ERROR) map failed!\n", + __func__); + level = FREE_IRQ; + goto out; + } + + rc = cxl_map_afu_irq(ctx, 2, cxlflash_rrq_irq, afu, + "SISL_MSI_RRQ_UPDATED"); + if (unlikely(rc <= 0)) { + dev_err(dev, "%s: IRQ 2 (SISL_MSI_RRQ_UPDATED) map failed!\n", + __func__); + level = UNMAP_ONE; + goto out; + } + + rc = cxl_map_afu_irq(ctx, 3, cxlflash_async_err_irq, afu, + "SISL_MSI_ASYNC_ERROR"); + if (unlikely(rc <= 0)) { + dev_err(dev, "%s: IRQ 3 (SISL_MSI_ASYNC_ERROR) map failed!\n", + __func__); + level = UNMAP_TWO; + goto out; + } + + rc = 0; + + /* This performs the equivalent of the CXL_IOCTL_START_WORK. + * The CXL_IOCTL_GET_PROCESS_ELEMENT is implicit in the process + * element (pe) that is embedded in the context (ctx) + */ + rc = start_context(cfg); + if (unlikely(rc)) { + dev_err(dev, "%s: start context failed rc=%d\n", __func__, rc); + level = UNMAP_THREE; + goto out; + } +ret: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +out: + term_mc(cfg, level); + goto ret; +} + +/** + * init_afu() - setup as master context and start AFU + * @cfg: Internal structure associated with the host. + * + * This routine is a higher level of control for configuring the + * AFU on probe and reset paths. + * + * Return: 0 on success, -errno on failure + */ +static int init_afu(struct cxlflash_cfg *cfg) +{ + u64 reg; + int rc = 0; + struct afu *afu = cfg->afu; + struct device *dev = &cfg->dev->dev; + + cxl_perst_reloads_same_image(cfg->cxl_afu, true); + + rc = init_mc(cfg); + if (rc) { + dev_err(dev, "%s: call to init_mc failed, rc=%d!\n", + __func__, rc); + goto out; + } + + /* Map the entire MMIO space of the AFU */ + afu->afu_map = cxl_psa_map(cfg->mcctx); + if (!afu->afu_map) { + dev_err(dev, "%s: call to cxl_psa_map failed!\n", __func__); + rc = -ENOMEM; + goto err1; + } + + /* No byte reverse on reading afu_version or string will be backwards */ + reg = readq(&afu->afu_map->global.regs.afu_version); + memcpy(afu->version, ®, sizeof(reg)); + afu->interface_version = + readq_be(&afu->afu_map->global.regs.interface_version); + if ((afu->interface_version + 1) == 0) { + pr_err("Back level AFU, please upgrade. AFU version %s " + "interface version 0x%llx\n", afu->version, + afu->interface_version); + rc = -EINVAL; + goto err2; + } + + pr_debug("%s: afu version %s, interface version 0x%llX\n", __func__, + afu->version, afu->interface_version); + + rc = start_afu(cfg); + if (rc) { + dev_err(dev, "%s: call to start_afu failed, rc=%d!\n", + __func__, rc); + goto err2; + } + + afu_err_intr_init(cfg->afu); + atomic64_set(&afu->room, readq_be(&afu->host_map->cmd_room)); + + /* Restore the LUN mappings */ + cxlflash_restore_luntable(cfg); +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; + +err2: + cxl_psa_unmap((void __iomem *)afu->afu_map); + afu->afu_map = NULL; +err1: + term_mc(cfg, UNDO_START); + goto out; +} + +/** + * cxlflash_afu_sync() - builds and sends an AFU sync command + * @afu: AFU associated with the host. + * @ctx_hndl_u: Identifies context requesting sync. + * @res_hndl_u: Identifies resource requesting sync. + * @mode: Type of sync to issue (lightweight, heavyweight, global). + * + * The AFU can only take 1 sync command at a time. This routine enforces this + * limitation by using a mutex to provide exclusive access to the AFU during + * the sync. This design point requires calling threads to not be on interrupt + * context due to the possibility of sleeping during concurrent sync operations. + * + * AFU sync operations are only necessary and allowed when the device is + * operating normally. When not operating normally, sync requests can occur as + * part of cleaning up resources associated with an adapter prior to removal. + * In this scenario, these requests are simply ignored (safe due to the AFU + * going away). + * + * Return: + * 0 on success + * -1 on failure + */ +int cxlflash_afu_sync(struct afu *afu, ctx_hndl_t ctx_hndl_u, + res_hndl_t res_hndl_u, u8 mode) +{ + struct cxlflash_cfg *cfg = afu->parent; + struct device *dev = &cfg->dev->dev; + struct afu_cmd *cmd = NULL; + int rc = 0; + int retry_cnt = 0; + static DEFINE_MUTEX(sync_active); + + if (cfg->state != STATE_NORMAL) { + pr_debug("%s: Sync not required! (%u)\n", __func__, cfg->state); + return 0; + } + + mutex_lock(&sync_active); +retry: + cmd = cmd_checkout(afu); + if (unlikely(!cmd)) { + retry_cnt++; + udelay(1000 * retry_cnt); + if (retry_cnt < MC_RETRY_CNT) + goto retry; + dev_err(dev, "%s: could not get a free command\n", __func__); + rc = -1; + goto out; + } + + pr_debug("%s: afu=%p cmd=%p %d\n", __func__, afu, cmd, ctx_hndl_u); + + memset(cmd->rcb.cdb, 0, sizeof(cmd->rcb.cdb)); + + cmd->rcb.req_flags = SISL_REQ_FLAGS_AFU_CMD; + cmd->rcb.port_sel = 0x0; /* NA */ + cmd->rcb.lun_id = 0x0; /* NA */ + cmd->rcb.data_len = 0x0; + cmd->rcb.data_ea = 0x0; + cmd->rcb.timeout = MC_AFU_SYNC_TIMEOUT; + + cmd->rcb.cdb[0] = 0xC0; /* AFU Sync */ + cmd->rcb.cdb[1] = mode; + + /* The cdb is aligned, no unaligned accessors required */ + *((__be16 *)&cmd->rcb.cdb[2]) = cpu_to_be16(ctx_hndl_u); + *((__be32 *)&cmd->rcb.cdb[4]) = cpu_to_be32(res_hndl_u); + + rc = send_cmd(afu, cmd); + if (unlikely(rc)) + goto out; + + wait_resp(afu, cmd); + + /* Set on timeout */ + if (unlikely((cmd->sa.ioasc != 0) || + (cmd->sa.host_use_b[0] & B_ERROR))) + rc = -1; +out: + mutex_unlock(&sync_active); + if (cmd) + cmd_checkin(cmd); + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * afu_reset() - resets the AFU + * @cfg: Internal structure associated with the host. + * + * Return: 0 on success, -errno on failure + */ +static int afu_reset(struct cxlflash_cfg *cfg) +{ + int rc = 0; + /* Stop the context before the reset. Since the context is + * no longer available restart it after the reset is complete + */ + + term_afu(cfg); + + rc = init_afu(cfg); + + pr_debug("%s: returning rc=%d\n", __func__, rc); + 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])); + +retry: + switch (cfg->state) { + case STATE_NORMAL: + rcr = send_tmf(afu, scp, TMF_LUN_RESET); + if (unlikely(rcr)) + rc = FAILED; + break; + case STATE_RESET: + wait_event(cfg->reset_waitq, cfg->state != STATE_RESET); + goto retry; + default: + rc = FAILED; + break; + } + + 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])); + + switch (cfg->state) { + case STATE_NORMAL: + cfg->state = STATE_RESET; + cxlflash_mark_contexts_error(cfg); + rcr = afu_reset(cfg); + if (rcr) { + rc = FAILED; + cfg->state = STATE_FAILTERM; + } else + cfg->state = STATE_NORMAL; + wake_up_all(&cfg->reset_waitq); + break; + case STATE_RESET: + wait_event(cfg->reset_waitq, cfg->state != STATE_RESET); + if (cfg->state == STATE_NORMAL) + break; + /* fall through */ + default: + rc = FAILED; + break; + } + + 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 + * @port: Desired port for status reporting. + * @afu: AFU owning the specified 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(u32 port, struct afu *afu, char *buf) +{ + char *disp_status; + u64 status; + __be64 __iomem *fc_regs; + + if (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]); + status &= 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 scnprintf(buf, PAGE_SIZE, "%s\n", disp_status); +} + +/** + * port0_show() - queries and presents the current status of port 0 + * @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 port0_show(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 cxlflash_show_port_status(0, afu, buf); +} + +/** + * port1_show() - queries and presents the current status of port 1 + * @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 port1_show(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 cxlflash_show_port_status(1, afu, buf); +} + +/** + * lun_mode_show() - 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 lun_mode_show(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 scnprintf(buf, PAGE_SIZE, "%u\n", afu->internal_lun); +} + +/** + * lun_mode_store() - 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 lun_mode_store(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; + afu_reset(cfg); + scsi_scan_host(cfg->host); + } + + return count; +} + +/** + * ioctl_version_show() - presents the current ioctl version of the host + * @dev: Generic device associated with the host. + * @attr: Device attribute representing the ioctl version. + * @buf: Buffer of length PAGE_SIZE to report back the ioctl version. + * + * Return: The size of the ASCII string returned in @buf. + */ +static ssize_t ioctl_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u\n", DK_CXLFLASH_VERSION_0); +} + +/** + * cxlflash_show_port_lun_table() - queries and presents the port LUN table + * @port: Desired port for status reporting. + * @afu: AFU owning the specified 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_lun_table(u32 port, + struct afu *afu, + char *buf) +{ + int i; + ssize_t bytes = 0; + __be64 __iomem *fc_port; + + if (port >= NUM_FC_PORTS) + return 0; + + fc_port = &afu->afu_map->global.fc_port[port][0]; + + for (i = 0; i < CXLFLASH_NUM_VLUNS; i++) + bytes += scnprintf(buf + bytes, PAGE_SIZE - bytes, + "%03d: %016llX\n", i, readq_be(&fc_port[i])); + return bytes; +} + +/** + * port0_lun_table_show() - presents the current LUN table of port 0 + * @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 port0_lun_table_show(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 cxlflash_show_port_lun_table(0, afu, buf); +} + +/** + * port1_lun_table_show() - presents the current LUN table of port 1 + * @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 port1_lun_table_show(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 cxlflash_show_port_lun_table(1, afu, buf); +} + +/** + * mode_show() - 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 mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", + sdev->hostdata ? "superpipe" : "legacy"); +} + +/* + * Host attributes + */ +static DEVICE_ATTR_RO(port0); +static DEVICE_ATTR_RO(port1); +static DEVICE_ATTR_RW(lun_mode); +static DEVICE_ATTR_RO(ioctl_version); +static DEVICE_ATTR_RO(port0_lun_table); +static DEVICE_ATTR_RO(port1_lun_table); + +static struct device_attribute *cxlflash_host_attrs[] = { + &dev_attr_port0, + &dev_attr_port1, + &dev_attr_lun_mode, + &dev_attr_ioctl_version, + &dev_attr_port0_lun_table, + &dev_attr_port1_lun_table, + NULL +}; + +/* + * Device attributes + */ +static DEVICE_ATTR_RO(mode); + +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, + .ioctl = cxlflash_ioctl, + .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); + +/** + * cxlflash_worker_thread() - work thread handler for the AFU + * @work: Work structure contained within cxlflash associated with host. + * + * Handles the following events: + * - Link reset which cannot be performed on interrupt context due to + * blocking up to a few seconds + * - Read AFU command room + * - Rescan the host + */ +static void cxlflash_worker_thread(struct work_struct *work) +{ + struct cxlflash_cfg *cfg = container_of(work, struct cxlflash_cfg, + work_q); + struct afu *afu = cfg->afu; + struct device *dev = &cfg->dev->dev; + int port; + ulong lock_flags; + + /* Avoid MMIO if the device has failed */ + + if (cfg->state != STATE_NORMAL) + return; + + spin_lock_irqsave(cfg->host->host_lock, lock_flags); + + if (cfg->lr_state == LINK_RESET_REQUIRED) { + port = cfg->lr_port; + if (port < 0) + dev_err(dev, "%s: invalid port index %d\n", + __func__, port); + else { + spin_unlock_irqrestore(cfg->host->host_lock, + lock_flags); + + /* The reset can block... */ + afu_link_reset(afu, port, + &afu->afu_map->global.fc_regs[port][0]); + spin_lock_irqsave(cfg->host->host_lock, lock_flags); + } + + cfg->lr_state = LINK_RESET_COMPLETE; + } + + if (afu->read_room) { + atomic64_set(&afu->room, readq_be(&afu->host_map->cmd_room)); + afu->read_room = false; + } + + spin_unlock_irqrestore(cfg->host->host_lock, lock_flags); + + if (atomic_dec_if_positive(&cfg->scan_host_needed) >= 0) + scsi_scan_host(cfg->host); +} + +/** + * cxlflash_probe() - PCI entry point to add host + * @pdev: PCI device associated with the host. + * @dev_id: PCI device id associated with device. + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_probe(struct pci_dev *pdev, + const struct pci_device_id *dev_id) +{ + struct Scsi_Host *host; + struct cxlflash_cfg *cfg = NULL; + struct device *phys_dev; + struct dev_dependent_vals *ddv; + int rc = 0; + + dev_dbg(&pdev->dev, "%s: Found CXLFLASH with IRQ: %d\n", + __func__, pdev->irq); + + ddv = (struct dev_dependent_vals *)dev_id->driver_data; + driver_template.max_sectors = ddv->max_sectors; + + host = scsi_host_alloc(&driver_template, sizeof(struct cxlflash_cfg)); + if (!host) { + dev_err(&pdev->dev, "%s: call to scsi_host_alloc failed!\n", + __func__); + rc = -ENOMEM; + goto out; + } + + host->max_id = CXLFLASH_MAX_NUM_TARGETS_PER_BUS; + host->max_lun = CXLFLASH_MAX_NUM_LUNS_PER_TARGET; + host->max_channel = NUM_FC_PORTS - 1; + host->unique_id = host->host_no; + host->max_cmd_len = CXLFLASH_MAX_CDB_LEN; + + cfg = (struct cxlflash_cfg *)host->hostdata; + cfg->host = host; + rc = alloc_mem(cfg); + if (rc) { + dev_err(&pdev->dev, "%s: call to alloc_mem failed!\n", + __func__); + rc = -ENOMEM; + scsi_host_put(cfg->host); + goto out; + } + + cfg->init_state = INIT_STATE_NONE; + cfg->dev = pdev; + cfg->cxl_fops = cxlflash_cxl_fops; + + /* + * The promoted LUNs move to the top of the LUN table. The rest stay + * on the bottom half. The bottom half grows from the end + * (index = 255), whereas the top half grows from the beginning + * (index = 0). + */ + cfg->promote_lun_index = 0; + cfg->last_lun_index[0] = CXLFLASH_NUM_VLUNS/2 - 1; + cfg->last_lun_index[1] = CXLFLASH_NUM_VLUNS/2 - 1; + + cfg->dev_id = (struct pci_device_id *)dev_id; + + init_waitqueue_head(&cfg->tmf_waitq); + init_waitqueue_head(&cfg->reset_waitq); + + INIT_WORK(&cfg->work_q, cxlflash_worker_thread); + cfg->lr_state = LINK_RESET_INVALID; + cfg->lr_port = -1; + spin_lock_init(&cfg->tmf_slock); + mutex_init(&cfg->ctx_tbl_list_mutex); + mutex_init(&cfg->ctx_recovery_mutex); + init_rwsem(&cfg->ioctl_rwsem); + INIT_LIST_HEAD(&cfg->ctx_err_recovery); + INIT_LIST_HEAD(&cfg->lluns); + + pci_set_drvdata(pdev, cfg); + + /* + * Use the special service provided to look up the physical + * PCI device, since we are called on the probe of the virtual + * PCI host bus (vphb) + */ + phys_dev = cxl_get_phys_dev(pdev); + if (!dev_is_pci(phys_dev)) { + dev_err(&pdev->dev, "%s: not a pci dev\n", __func__); + rc = -ENODEV; + goto out_remove; + } + cfg->parent_dev = to_pci_dev(phys_dev); + + cfg->cxl_afu = cxl_pci_to_afu(pdev); + + rc = init_pci(cfg); + if (rc) { + dev_err(&pdev->dev, "%s: call to init_pci " + "failed rc=%d!\n", __func__, rc); + goto out_remove; + } + cfg->init_state = INIT_STATE_PCI; + + rc = init_afu(cfg); + if (rc) { + dev_err(&pdev->dev, "%s: call to init_afu " + "failed rc=%d!\n", __func__, rc); + goto out_remove; + } + cfg->init_state = INIT_STATE_AFU; + + rc = init_scsi(cfg); + if (rc) { + dev_err(&pdev->dev, "%s: call to init_scsi " + "failed rc=%d!\n", __func__, rc); + goto out_remove; + } + cfg->init_state = INIT_STATE_SCSI; + +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; + +out_remove: + cxlflash_remove(pdev); + goto out; +} + +/** + * drain_ioctls() - wait until all currently executing ioctls have completed + * @cfg: Internal structure associated with the host. + * + * Obtain write access to read/write semaphore that wraps ioctl + * handling to 'drain' ioctls currently executing. + */ +static void drain_ioctls(struct cxlflash_cfg *cfg) +{ + down_write(&cfg->ioctl_rwsem); + up_write(&cfg->ioctl_rwsem); +} + +/** + * cxlflash_pci_error_detected() - called when a PCI error is detected + * @pdev: PCI device struct. + * @state: PCI channel state. + * + * Return: PCI_ERS_RESULT_NEED_RESET or PCI_ERS_RESULT_DISCONNECT + */ +static pci_ers_result_t cxlflash_pci_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + int rc = 0; + struct cxlflash_cfg *cfg = pci_get_drvdata(pdev); + struct device *dev = &cfg->dev->dev; + + dev_dbg(dev, "%s: pdev=%p state=%u\n", __func__, pdev, state); + + switch (state) { + case pci_channel_io_frozen: + cfg->state = STATE_RESET; + scsi_block_requests(cfg->host); + drain_ioctls(cfg); + rc = cxlflash_mark_contexts_error(cfg); + if (unlikely(rc)) + dev_err(dev, "%s: Failed to mark user contexts!(%d)\n", + __func__, rc); + term_mc(cfg, UNDO_START); + stop_afu(cfg); + return PCI_ERS_RESULT_NEED_RESET; + case pci_channel_io_perm_failure: + cfg->state = STATE_FAILTERM; + wake_up_all(&cfg->reset_waitq); + scsi_unblock_requests(cfg->host); + return PCI_ERS_RESULT_DISCONNECT; + default: + break; + } + return PCI_ERS_RESULT_NEED_RESET; +} + +/** + * cxlflash_pci_slot_reset() - called when PCI slot has been reset + * @pdev: PCI device struct. + * + * This routine is called by the pci error recovery code after the PCI + * slot has been reset, just before we should resume normal operations. + * + * Return: PCI_ERS_RESULT_RECOVERED or PCI_ERS_RESULT_DISCONNECT + */ +static pci_ers_result_t cxlflash_pci_slot_reset(struct pci_dev *pdev) +{ + int rc = 0; + struct cxlflash_cfg *cfg = pci_get_drvdata(pdev); + struct device *dev = &cfg->dev->dev; + + dev_dbg(dev, "%s: pdev=%p\n", __func__, pdev); + + rc = init_afu(cfg); + if (unlikely(rc)) { + dev_err(dev, "%s: EEH recovery failed! (%d)\n", __func__, rc); + return PCI_ERS_RESULT_DISCONNECT; + } + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * cxlflash_pci_resume() - called when normal operation can resume + * @pdev: PCI device struct + */ +static void cxlflash_pci_resume(struct pci_dev *pdev) +{ + struct cxlflash_cfg *cfg = pci_get_drvdata(pdev); + struct device *dev = &cfg->dev->dev; + + dev_dbg(dev, "%s: pdev=%p\n", __func__, pdev); + + cfg->state = STATE_NORMAL; + wake_up_all(&cfg->reset_waitq); + scsi_unblock_requests(cfg->host); +} + +static const struct pci_error_handlers cxlflash_err_handler = { + .error_detected = cxlflash_pci_error_detected, + .slot_reset = cxlflash_pci_slot_reset, + .resume = cxlflash_pci_resume, +}; + +/* + * PCI device structure + */ +static struct pci_driver cxlflash_driver = { + .name = CXLFLASH_NAME, + .id_table = cxlflash_pci_table, + .probe = cxlflash_probe, + .remove = cxlflash_remove, + .err_handler = &cxlflash_err_handler, +}; + +/** + * init_cxlflash() - module entry point + * + * Return: 0 on success, -errno on failure + */ +static int __init init_cxlflash(void) +{ + pr_info("%s: IBM Power CXL Flash Adapter: %s\n", + __func__, CXLFLASH_DRIVER_DATE); + + cxlflash_list_init(); + + return pci_register_driver(&cxlflash_driver); +} + +/** + * exit_cxlflash() - module exit point + */ +static void __exit exit_cxlflash(void) +{ + cxlflash_term_global_luns(); + cxlflash_free_errpage(); + + pci_unregister_driver(&cxlflash_driver); +} + +module_init(init_cxlflash); +module_exit(exit_cxlflash); diff --git a/kernel/drivers/scsi/cxlflash/main.h b/kernel/drivers/scsi/cxlflash/main.h new file mode 100644 index 000000000..60324566c --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/main.h @@ -0,0 +1,109 @@ +/* + * 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_MAIN_H +#define _CXLFLASH_MAIN_H + +#include <linux/list.h> +#include <linux/types.h> +#include <scsi/scsi.h> +#include <scsi/scsi_device.h> + +#define CXLFLASH_NAME "cxlflash" +#define CXLFLASH_ADAPTER_NAME "IBM POWER CXL Flash Adapter" +#define CXLFLASH_DRIVER_DATE "(August 13, 2015)" + +#define PCI_DEVICE_ID_IBM_CORSA 0x04F0 +#define CXLFLASH_SUBS_DEV_ID 0x04F0 + +/* Since there is only one target, make it 0 */ +#define CXLFLASH_TARGET 0 +#define CXLFLASH_MAX_CDB_LEN 16 + +/* Really only one target per bus since the Texan is directly attached */ +#define CXLFLASH_MAX_NUM_TARGETS_PER_BUS 1 +#define CXLFLASH_MAX_NUM_LUNS_PER_TARGET 65536 + +#define CXLFLASH_PCI_ERROR_RECOVERY_TIMEOUT (120 * HZ) + +#define NUM_FC_PORTS CXLFLASH_NUM_FC_PORTS /* ports per AFU */ + +/* FC defines */ +#define FC_MTIP_CMDCONFIG 0x010 +#define FC_MTIP_STATUS 0x018 + +#define FC_PNAME 0x300 +#define FC_CONFIG 0x320 +#define FC_CONFIG2 0x328 +#define FC_STATUS 0x330 +#define FC_ERROR 0x380 +#define FC_ERRCAP 0x388 +#define FC_ERRMSK 0x390 +#define FC_CNT_CRCERR 0x538 +#define FC_CRC_THRESH 0x580 + +#define FC_MTIP_CMDCONFIG_ONLINE 0x20ULL +#define FC_MTIP_CMDCONFIG_OFFLINE 0x40ULL + +#define FC_MTIP_STATUS_MASK 0x30ULL +#define FC_MTIP_STATUS_ONLINE 0x20ULL +#define FC_MTIP_STATUS_OFFLINE 0x10ULL + +/* TIMEOUT and RETRY definitions */ + +/* AFU command timeout values */ +#define MC_AFU_SYNC_TIMEOUT 5 /* 5 secs */ + +/* AFU command room retry limit */ +#define MC_ROOM_RETRY_CNT 10 + +/* FC CRC clear periodic timer */ +#define MC_CRC_THRESH 100 /* threshold in 5 mins */ + +#define FC_PORT_STATUS_RETRY_CNT 100 /* 100 100ms retries = 10 seconds */ +#define FC_PORT_STATUS_RETRY_INTERVAL_US 100000 /* microseconds */ + +/* VPD defines */ +#define CXLFLASH_VPD_LEN 256 +#define WWPN_LEN 16 +#define WWPN_BUF_LEN (WWPN_LEN + 1) + +enum undo_level { + RELEASE_CONTEXT = 0, + FREE_IRQ, + UNMAP_ONE, + UNMAP_TWO, + UNMAP_THREE, + UNDO_START +}; + +struct dev_dependent_vals { + u64 max_sectors; +}; + +struct asyc_intr_info { + u64 status; + char *desc; + u8 port; + u8 action; +#define CLR_FC_ERROR 0x01 +#define LINK_RESET 0x02 +#define SCAN_HOST 0x04 +}; + +#ifndef CONFIG_CXL_EEH +#define cxl_perst_reloads_same_image(_a, _b) do { } while (0) +#endif + +#endif /* _CXLFLASH_MAIN_H */ diff --git a/kernel/drivers/scsi/cxlflash/sislite.h b/kernel/drivers/scsi/cxlflash/sislite.h new file mode 100644 index 000000000..0b3366f5e --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/sislite.h @@ -0,0 +1,472 @@ +/* + * 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 _SISLITE_H +#define _SISLITE_H + +#include <linux/types.h> + +typedef u16 ctx_hndl_t; +typedef u32 res_hndl_t; + +#define SIZE_4K 4096 +#define SIZE_64K 65536 + +/* + * IOARCB: 64 bytes, min 16 byte alignment required, host native endianness + * except for SCSI CDB which remains big endian per SCSI standards. + */ +struct sisl_ioarcb { + u16 ctx_id; /* ctx_hndl_t */ + u16 req_flags; +#define SISL_REQ_FLAGS_RES_HNDL 0x8000U /* bit 0 (MSB) */ +#define SISL_REQ_FLAGS_PORT_LUN_ID 0x0000U + +#define SISL_REQ_FLAGS_SUP_UNDERRUN 0x4000U /* bit 1 */ + +#define SISL_REQ_FLAGS_TIMEOUT_SECS 0x0000U /* bits 8,9 */ +#define SISL_REQ_FLAGS_TIMEOUT_MSECS 0x0040U +#define SISL_REQ_FLAGS_TIMEOUT_USECS 0x0080U +#define SISL_REQ_FLAGS_TIMEOUT_CYCLES 0x00C0U + +#define SISL_REQ_FLAGS_TMF_CMD 0x0004u /* bit 13 */ + +#define SISL_REQ_FLAGS_AFU_CMD 0x0002U /* bit 14 */ + +#define SISL_REQ_FLAGS_HOST_WRITE 0x0001U /* bit 15 (LSB) */ +#define SISL_REQ_FLAGS_HOST_READ 0x0000U + + union { + u32 res_hndl; /* res_hndl_t */ + u32 port_sel; /* this is a selection mask: + * 0x1 -> port#0 can be selected, + * 0x2 -> port#1 can be selected. + * Can be bitwise ORed. + */ + }; + u64 lun_id; + u32 data_len; /* 4K for read/write */ + u32 ioadl_len; + union { + u64 data_ea; /* min 16 byte aligned */ + u64 ioadl_ea; + }; + u8 msi; /* LISN to send on RRQ write */ +#define SISL_MSI_CXL_PFAULT 0 /* reserved for CXL page faults */ +#define SISL_MSI_SYNC_ERROR 1 /* recommended for AFU sync error */ +#define SISL_MSI_RRQ_UPDATED 2 /* recommended for IO completion */ +#define SISL_MSI_ASYNC_ERROR 3 /* master only - for AFU async error */ + + u8 rrq; /* 0 for a single RRQ */ + u16 timeout; /* in units specified by req_flags */ + u32 rsvd1; + u8 cdb[16]; /* must be in big endian */ + struct scsi_cmnd *scp; +} __packed; + +struct sisl_rc { + u8 flags; +#define SISL_RC_FLAGS_SENSE_VALID 0x80U +#define SISL_RC_FLAGS_FCP_RSP_CODE_VALID 0x40U +#define SISL_RC_FLAGS_OVERRUN 0x20U +#define SISL_RC_FLAGS_UNDERRUN 0x10U + + u8 afu_rc; +#define SISL_AFU_RC_RHT_INVALID 0x01U /* user error */ +#define SISL_AFU_RC_RHT_UNALIGNED 0x02U /* should never happen */ +#define SISL_AFU_RC_RHT_OUT_OF_BOUNDS 0x03u /* user error */ +#define SISL_AFU_RC_RHT_DMA_ERR 0x04u /* see afu_extra + may retry if afu_retry is off + possible on master exit + */ +#define SISL_AFU_RC_RHT_RW_PERM 0x05u /* no RW perms, user error */ +#define SISL_AFU_RC_LXT_UNALIGNED 0x12U /* should never happen */ +#define SISL_AFU_RC_LXT_OUT_OF_BOUNDS 0x13u /* user error */ +#define SISL_AFU_RC_LXT_DMA_ERR 0x14u /* see afu_extra + may retry if afu_retry is off + possible on master exit + */ +#define SISL_AFU_RC_LXT_RW_PERM 0x15u /* no RW perms, user error */ + +#define SISL_AFU_RC_NOT_XLATE_HOST 0x1au /* possible if master exited */ + + /* NO_CHANNELS means the FC ports selected by dest_port in + * IOARCB or in the LXT entry are down when the AFU tried to select + * a FC port. If the port went down on an active IO, it will set + * fc_rc to =0x54(NOLOGI) or 0x57(LINKDOWN) instead. + */ +#define SISL_AFU_RC_NO_CHANNELS 0x20U /* see afu_extra, may retry */ +#define SISL_AFU_RC_CAP_VIOLATION 0x21U /* either user error or + afu reset/master restart + */ +#define SISL_AFU_RC_OUT_OF_DATA_BUFS 0x30U /* always retry */ +#define SISL_AFU_RC_DATA_DMA_ERR 0x31U /* see afu_extra + may retry if afu_retry is off + */ + + u8 scsi_rc; /* SCSI status byte, retry as appropriate */ +#define SISL_SCSI_RC_CHECK 0x02U +#define SISL_SCSI_RC_BUSY 0x08u + + u8 fc_rc; /* retry */ + /* + * We should only see fc_rc=0x57 (LINKDOWN) or 0x54(NOLOGI) for + * commands that are in flight when a link goes down or is logged out. + * If the link is down or logged out before AFU selects the port, either + * it will choose the other port or we will get afu_rc=0x20 (no_channel) + * if there is no valid port to use. + * + * ABORTPEND/ABORTOK/ABORTFAIL/TGTABORT can be retried, typically these + * would happen if a frame is dropped and something times out. + * NOLOGI or LINKDOWN can be retried if the other port is up. + * RESIDERR can be retried as well. + * + * ABORTFAIL might indicate that lots of frames are getting CRC errors. + * So it maybe retried once and reset the link if it happens again. + * The link can also be reset on the CRC error threshold interrupt. + */ +#define SISL_FC_RC_ABORTPEND 0x52 /* exchange timeout or abort request */ +#define SISL_FC_RC_WRABORTPEND 0x53 /* due to write XFER_RDY invalid */ +#define SISL_FC_RC_NOLOGI 0x54 /* port not logged in, in-flight cmds */ +#define SISL_FC_RC_NOEXP 0x55 /* FC protocol error or HW bug */ +#define SISL_FC_RC_INUSE 0x56 /* tag already in use, HW bug */ +#define SISL_FC_RC_LINKDOWN 0x57 /* link down, in-flight cmds */ +#define SISL_FC_RC_ABORTOK 0x58 /* pending abort completed w/success */ +#define SISL_FC_RC_ABORTFAIL 0x59 /* pending abort completed w/fail */ +#define SISL_FC_RC_RESID 0x5A /* ioasa underrun/overrun flags set */ +#define SISL_FC_RC_RESIDERR 0x5B /* actual data len does not match SCSI + reported len, possibly due to dropped + frames */ +#define SISL_FC_RC_TGTABORT 0x5C /* command aborted by target */ +}; + +#define SISL_SENSE_DATA_LEN 20 /* Sense data length */ + +/* + * IOASA: 64 bytes & must follow IOARCB, min 16 byte alignment required, + * host native endianness + */ +struct sisl_ioasa { + union { + struct sisl_rc rc; + u32 ioasc; +#define SISL_IOASC_GOOD_COMPLETION 0x00000000U + }; + u32 resid; + u8 port; + u8 afu_extra; + /* when afu_rc=0x04, 0x14, 0x31 (_xxx_DMA_ERR): + * afu_exta contains PSL response code. Useful codes are: + */ +#define SISL_AFU_DMA_ERR_PAGE_IN 0x0A /* AFU_retry_on_pagein Action + * Enabled N/A + * Disabled retry + */ +#define SISL_AFU_DMA_ERR_INVALID_EA 0x0B /* this is a hard error + * afu_rc Implies + * 0x04, 0x14 master exit. + * 0x31 user error. + */ + /* when afu rc=0x20 (no channels): + * afu_extra bits [4:5]: available portmask, [6:7]: requested portmask. + */ +#define SISL_AFU_NO_CLANNELS_AMASK(afu_extra) (((afu_extra) & 0x0C) >> 2) +#define SISL_AFU_NO_CLANNELS_RMASK(afu_extra) ((afu_extra) & 0x03) + + u8 scsi_extra; + u8 fc_extra; + u8 sense_data[SISL_SENSE_DATA_LEN]; + + /* These fields are defined by the SISlite architecture for the + * host to use as they see fit for their implementation. + */ + union { + u64 host_use[4]; + u8 host_use_b[32]; + }; +} __packed; + +#define SISL_RESP_HANDLE_T_BIT 0x1ULL /* Toggle bit */ + +/* MMIO space is required to support only 64-bit access */ + +/* + * This AFU has two mechanisms to deal with endian-ness. + * One is a global configuration (in the afu_config) register + * below that specifies the endian-ness of the host. + * The other is a per context (i.e. application) specification + * controlled by the endian_ctrl field here. Since the master + * context is one such application the master context's + * endian-ness is set to be the same as the host. + * + * As per the SISlite spec, the MMIO registers are always + * big endian. + */ +#define SISL_ENDIAN_CTRL_BE 0x8000000000000080ULL +#define SISL_ENDIAN_CTRL_LE 0x0000000000000000ULL + +#ifdef __BIG_ENDIAN +#define SISL_ENDIAN_CTRL SISL_ENDIAN_CTRL_BE +#else +#define SISL_ENDIAN_CTRL SISL_ENDIAN_CTRL_LE +#endif + +/* per context host transport MMIO */ +struct sisl_host_map { + __be64 endian_ctrl; /* Per context Endian Control. The AFU will + * operate on whatever the context is of the + * host application. + */ + + __be64 intr_status; /* this sends LISN# programmed in ctx_ctrl. + * Only recovery in a PERM_ERR is a context + * exit since there is no way to tell which + * command caused the error. + */ +#define SISL_ISTATUS_PERM_ERR_CMDROOM 0x0010ULL /* b59, user error */ +#define SISL_ISTATUS_PERM_ERR_RCB_READ 0x0008ULL /* b60, user error */ +#define SISL_ISTATUS_PERM_ERR_SA_WRITE 0x0004ULL /* b61, user error */ +#define SISL_ISTATUS_PERM_ERR_RRQ_WRITE 0x0002ULL /* b62, user error */ + /* Page in wait accessing RCB/IOASA/RRQ is reported in b63. + * Same error in data/LXT/RHT access is reported via IOASA. + */ +#define SISL_ISTATUS_TEMP_ERR_PAGEIN 0x0001ULL /* b63, can be generated + * only when AFU auto + * retry is disabled. + * If user can determine + * the command that + * caused the error, it + * can be retried. + */ +#define SISL_ISTATUS_UNMASK (0x001FULL) /* 1 means unmasked */ +#define SISL_ISTATUS_MASK ~(SISL_ISTATUS_UNMASK) /* 1 means masked */ + + __be64 intr_clear; + __be64 intr_mask; + __be64 ioarrin; /* only write what cmd_room permits */ + __be64 rrq_start; /* start & end are both inclusive */ + __be64 rrq_end; /* write sequence: start followed by end */ + __be64 cmd_room; + __be64 ctx_ctrl; /* least significant byte or b56:63 is LISN# */ + __be64 mbox_w; /* restricted use */ +}; + +/* per context provisioning & control MMIO */ +struct sisl_ctrl_map { + __be64 rht_start; + __be64 rht_cnt_id; + /* both cnt & ctx_id args must be ULL */ +#define SISL_RHT_CNT_ID(cnt, ctx_id) (((cnt) << 48) | ((ctx_id) << 32)) + + __be64 ctx_cap; /* afu_rc below is when the capability is violated */ +#define SISL_CTX_CAP_PROXY_ISSUE 0x8000000000000000ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_REAL_MODE 0x4000000000000000ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_HOST_XLATE 0x2000000000000000ULL /* afu_rc 0x1a */ +#define SISL_CTX_CAP_PROXY_TARGET 0x1000000000000000ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_AFU_CMD 0x0000000000000008ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_GSCSI_CMD 0x0000000000000004ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_WRITE_CMD 0x0000000000000002ULL /* afu_rc 0x21 */ +#define SISL_CTX_CAP_READ_CMD 0x0000000000000001ULL /* afu_rc 0x21 */ + __be64 mbox_r; +}; + +/* single copy global regs */ +struct sisl_global_regs { + __be64 aintr_status; + /* In cxlflash, each FC port/link gets a byte of status */ +#define SISL_ASTATUS_FC0_OTHER 0x8000ULL /* b48, other err, + FC_ERRCAP[31:20] */ +#define SISL_ASTATUS_FC0_LOGO 0x4000ULL /* b49, target sent FLOGI/PLOGI/LOGO + while logged in */ +#define SISL_ASTATUS_FC0_CRC_T 0x2000ULL /* b50, CRC threshold exceeded */ +#define SISL_ASTATUS_FC0_LOGI_R 0x1000ULL /* b51, login state machine timed out + and retrying */ +#define SISL_ASTATUS_FC0_LOGI_F 0x0800ULL /* b52, login failed, + FC_ERROR[19:0] */ +#define SISL_ASTATUS_FC0_LOGI_S 0x0400ULL /* b53, login succeeded */ +#define SISL_ASTATUS_FC0_LINK_DN 0x0200ULL /* b54, link online to offline */ +#define SISL_ASTATUS_FC0_LINK_UP 0x0100ULL /* b55, link offline to online */ + +#define SISL_ASTATUS_FC1_OTHER 0x0080ULL /* b56 */ +#define SISL_ASTATUS_FC1_LOGO 0x0040ULL /* b57 */ +#define SISL_ASTATUS_FC1_CRC_T 0x0020ULL /* b58 */ +#define SISL_ASTATUS_FC1_LOGI_R 0x0010ULL /* b59 */ +#define SISL_ASTATUS_FC1_LOGI_F 0x0008ULL /* b60 */ +#define SISL_ASTATUS_FC1_LOGI_S 0x0004ULL /* b61 */ +#define SISL_ASTATUS_FC1_LINK_DN 0x0002ULL /* b62 */ +#define SISL_ASTATUS_FC1_LINK_UP 0x0001ULL /* b63 */ + +#define SISL_FC_INTERNAL_UNMASK 0x0000000300000000ULL /* 1 means unmasked */ +#define SISL_FC_INTERNAL_MASK ~(SISL_FC_INTERNAL_UNMASK) +#define SISL_FC_INTERNAL_SHIFT 32 + +#define SISL_ASTATUS_UNMASK 0xFFFFULL /* 1 means unmasked */ +#define SISL_ASTATUS_MASK ~(SISL_ASTATUS_UNMASK) /* 1 means masked */ + + __be64 aintr_clear; + __be64 aintr_mask; + __be64 afu_ctrl; + __be64 afu_hb; + __be64 afu_scratch_pad; + __be64 afu_port_sel; +#define SISL_AFUCONF_AR_IOARCB 0x4000ULL +#define SISL_AFUCONF_AR_LXT 0x2000ULL +#define SISL_AFUCONF_AR_RHT 0x1000ULL +#define SISL_AFUCONF_AR_DATA 0x0800ULL +#define SISL_AFUCONF_AR_RSRC 0x0400ULL +#define SISL_AFUCONF_AR_IOASA 0x0200ULL +#define SISL_AFUCONF_AR_RRQ 0x0100ULL +/* Aggregate all Auto Retry Bits */ +#define SISL_AFUCONF_AR_ALL (SISL_AFUCONF_AR_IOARCB|SISL_AFUCONF_AR_LXT| \ + SISL_AFUCONF_AR_RHT|SISL_AFUCONF_AR_DATA| \ + SISL_AFUCONF_AR_RSRC|SISL_AFUCONF_AR_IOASA| \ + SISL_AFUCONF_AR_RRQ) +#ifdef __BIG_ENDIAN +#define SISL_AFUCONF_ENDIAN 0x0000ULL +#else +#define SISL_AFUCONF_ENDIAN 0x0020ULL +#endif +#define SISL_AFUCONF_MBOX_CLR_READ 0x0010ULL + __be64 afu_config; + __be64 rsvd[0xf8]; + __le64 afu_version; + __be64 interface_version; +}; + +#define CXLFLASH_NUM_FC_PORTS 2 +#define CXLFLASH_MAX_CONTEXT 512 /* how many contexts per afu */ +#define CXLFLASH_NUM_VLUNS 512 + +struct sisl_global_map { + union { + struct sisl_global_regs regs; + char page0[SIZE_4K]; /* page 0 */ + }; + + char page1[SIZE_4K]; /* page 1 */ + + /* pages 2 & 3 */ + __be64 fc_regs[CXLFLASH_NUM_FC_PORTS][CXLFLASH_NUM_VLUNS]; + + /* pages 4 & 5 (lun tbl) */ + __be64 fc_port[CXLFLASH_NUM_FC_PORTS][CXLFLASH_NUM_VLUNS]; + +}; + +/* + * CXL Flash Memory Map + * + * +-------------------------------+ + * | 512 * 64 KB User MMIO | + * | (per context) | + * | User Accessible | + * +-------------------------------+ + * | 512 * 128 B per context | + * | Provisioning and Control | + * | Trusted Process accessible | + * +-------------------------------+ + * | 64 KB Global | + * | Trusted Process accessible | + * +-------------------------------+ +*/ +struct cxlflash_afu_map { + union { + struct sisl_host_map host; + char harea[SIZE_64K]; /* 64KB each */ + } hosts[CXLFLASH_MAX_CONTEXT]; + + union { + struct sisl_ctrl_map ctrl; + char carea[cache_line_size()]; /* 128B each */ + } ctrls[CXLFLASH_MAX_CONTEXT]; + + union { + struct sisl_global_map global; + char garea[SIZE_64K]; /* 64KB single block */ + }; +}; + +/* + * LXT - LBA Translation Table + * LXT control blocks + */ +struct sisl_lxt_entry { + u64 rlba_base; /* bits 0:47 is base + * b48:55 is lun index + * b58:59 is write & read perms + * (if no perm, afu_rc=0x15) + * b60:63 is port_sel mask + */ +}; + +/* + * RHT - Resource Handle Table + * Per the SISlite spec, RHT entries are to be 16-byte aligned + */ +struct sisl_rht_entry { + struct sisl_lxt_entry *lxt_start; + u32 lxt_cnt; + u16 rsvd; + u8 fp; /* format & perm nibbles. + * (if no perm, afu_rc=0x05) + */ + u8 nmask; +} __packed __aligned(16); + +struct sisl_rht_entry_f1 { + u64 lun_id; + union { + struct { + u8 valid; + u8 rsvd[5]; + u8 fp; + u8 port_sel; + }; + + u64 dw; + }; +} __packed __aligned(16); + +/* make the fp byte */ +#define SISL_RHT_FP(fmt, perm) (((fmt) << 4) | (perm)) + +/* make the fp byte for a clone from a source fp and clone flags + * flags must be only 2 LSB bits. + */ +#define SISL_RHT_FP_CLONE(src_fp, cln_flags) ((src_fp) & (0xFC | (cln_flags))) + +#define RHT_PERM_READ 0x01U +#define RHT_PERM_WRITE 0x02U +#define RHT_PERM_RW (RHT_PERM_READ | RHT_PERM_WRITE) + +/* extract the perm bits from a fp */ +#define SISL_RHT_PERM(fp) ((fp) & RHT_PERM_RW) + +#define PORT0 0x01U +#define PORT1 0x02U +#define BOTH_PORTS (PORT0 | PORT1) + +/* AFU Sync Mode byte */ +#define AFU_LW_SYNC 0x0U +#define AFU_HW_SYNC 0x1U +#define AFU_GSYNC 0x2U + +/* Special Task Management Function CDB */ +#define TMF_LUN_RESET 0x1U +#define TMF_CLEAR_ACA 0x2U + + +#define SISLITE_MAX_WS_BLOCKS 512 + +#endif /* _SISLITE_H */ diff --git a/kernel/drivers/scsi/cxlflash/superpipe.c b/kernel/drivers/scsi/cxlflash/superpipe.c new file mode 100644 index 000000000..cac2e6a50 --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/superpipe.c @@ -0,0 +1,2143 @@ +/* + * 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/file.h> +#include <linux/syscalls.h> +#include <misc/cxl.h> +#include <asm/unaligned.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_eh.h> +#include <uapi/scsi/cxlflash_ioctl.h> + +#include "sislite.h" +#include "common.h" +#include "vlun.h" +#include "superpipe.h" + +struct cxlflash_global global; + +/** + * marshal_rele_to_resize() - translate release to resize structure + * @rele: Source structure from which to translate/copy. + * @resize: Destination structure for the translate/copy. + */ +static void marshal_rele_to_resize(struct dk_cxlflash_release *release, + struct dk_cxlflash_resize *resize) +{ + resize->hdr = release->hdr; + resize->context_id = release->context_id; + resize->rsrc_handle = release->rsrc_handle; +} + +/** + * marshal_det_to_rele() - translate detach to release structure + * @detach: Destination structure for the translate/copy. + * @rele: Source structure from which to translate/copy. + */ +static void marshal_det_to_rele(struct dk_cxlflash_detach *detach, + struct dk_cxlflash_release *release) +{ + release->hdr = detach->hdr; + release->context_id = detach->context_id; +} + +/** + * cxlflash_free_errpage() - frees resources associated with global error page + */ +void cxlflash_free_errpage(void) +{ + + mutex_lock(&global.mutex); + if (global.err_page) { + __free_page(global.err_page); + global.err_page = NULL; + } + mutex_unlock(&global.mutex); +} + +/** + * cxlflash_stop_term_user_contexts() - stops/terminates known user contexts + * @cfg: Internal structure associated with the host. + * + * When the host needs to go down, all users must be quiesced and their + * memory freed. This is accomplished by putting the contexts in error + * state which will notify the user and let them 'drive' the tear down. + * Meanwhile, this routine camps until all user contexts have been removed. + */ +void cxlflash_stop_term_user_contexts(struct cxlflash_cfg *cfg) +{ + struct device *dev = &cfg->dev->dev; + int i, found; + + cxlflash_mark_contexts_error(cfg); + + while (true) { + found = false; + + for (i = 0; i < MAX_CONTEXT; i++) + if (cfg->ctx_tbl[i]) { + found = true; + break; + } + + if (!found && list_empty(&cfg->ctx_err_recovery)) + return; + + dev_dbg(dev, "%s: Wait for user contexts to quiesce...\n", + __func__); + wake_up_all(&cfg->reset_waitq); + ssleep(1); + } +} + +/** + * find_error_context() - locates a context by cookie on the error recovery list + * @cfg: Internal structure associated with the host. + * @rctxid: Desired context by id. + * @file: Desired context by file. + * + * Return: Found context on success, NULL on failure + */ +static struct ctx_info *find_error_context(struct cxlflash_cfg *cfg, u64 rctxid, + struct file *file) +{ + struct ctx_info *ctxi; + + list_for_each_entry(ctxi, &cfg->ctx_err_recovery, list) + if ((ctxi->ctxid == rctxid) || (ctxi->file == file)) + return ctxi; + + return NULL; +} + +/** + * get_context() - obtains a validated and locked context reference + * @cfg: Internal structure associated with the host. + * @rctxid: Desired context (raw, un-decoded format). + * @arg: LUN information or file associated with request. + * @ctx_ctrl: Control information to 'steer' desired lookup. + * + * NOTE: despite the name pid, in linux, current->pid actually refers + * to the lightweight process id (tid) and can change if the process is + * multi threaded. The tgid remains constant for the process and only changes + * when the process of fork. For all intents and purposes, think of tgid + * as a pid in the traditional sense. + * + * Return: Validated context on success, NULL on failure + */ +struct ctx_info *get_context(struct cxlflash_cfg *cfg, u64 rctxid, + void *arg, enum ctx_ctrl ctx_ctrl) +{ + struct device *dev = &cfg->dev->dev; + struct ctx_info *ctxi = NULL; + struct lun_access *lun_access = NULL; + struct file *file = NULL; + struct llun_info *lli = arg; + u64 ctxid = DECODE_CTXID(rctxid); + int rc; + pid_t pid = current->tgid, ctxpid = 0; + + if (ctx_ctrl & CTX_CTRL_FILE) { + lli = NULL; + file = (struct file *)arg; + } + + if (ctx_ctrl & CTX_CTRL_CLONE) + pid = current->parent->tgid; + + if (likely(ctxid < MAX_CONTEXT)) { + while (true) { + mutex_lock(&cfg->ctx_tbl_list_mutex); + ctxi = cfg->ctx_tbl[ctxid]; + if (ctxi) + if ((file && (ctxi->file != file)) || + (!file && (ctxi->ctxid != rctxid))) + ctxi = NULL; + + if ((ctx_ctrl & CTX_CTRL_ERR) || + (!ctxi && (ctx_ctrl & CTX_CTRL_ERR_FALLBACK))) + ctxi = find_error_context(cfg, rctxid, file); + if (!ctxi) { + mutex_unlock(&cfg->ctx_tbl_list_mutex); + goto out; + } + + /* + * Need to acquire ownership of the context while still + * under the table/list lock to serialize with a remove + * thread. Use the 'try' to avoid stalling the + * table/list lock for a single context. + * + * Note that the lock order is: + * + * cfg->ctx_tbl_list_mutex -> ctxi->mutex + * + * Therefore release ctx_tbl_list_mutex before retrying. + */ + rc = mutex_trylock(&ctxi->mutex); + mutex_unlock(&cfg->ctx_tbl_list_mutex); + if (rc) + break; /* got the context's lock! */ + } + + if (ctxi->unavail) + goto denied; + + ctxpid = ctxi->pid; + if (likely(!(ctx_ctrl & CTX_CTRL_NOPID))) + if (pid != ctxpid) + goto denied; + + if (lli) { + list_for_each_entry(lun_access, &ctxi->luns, list) + if (lun_access->lli == lli) + goto out; + goto denied; + } + } + +out: + dev_dbg(dev, "%s: rctxid=%016llX ctxinfo=%p ctxpid=%u pid=%u " + "ctx_ctrl=%u\n", __func__, rctxid, ctxi, ctxpid, pid, + ctx_ctrl); + + return ctxi; + +denied: + mutex_unlock(&ctxi->mutex); + ctxi = NULL; + goto out; +} + +/** + * put_context() - release a context that was retrieved from get_context() + * @ctxi: Context to release. + * + * For now, releasing the context equates to unlocking it's mutex. + */ +void put_context(struct ctx_info *ctxi) +{ + mutex_unlock(&ctxi->mutex); +} + +/** + * afu_attach() - attach a context to the AFU + * @cfg: Internal structure associated with the host. + * @ctxi: Context to attach. + * + * Upon setting the context capabilities, they must be confirmed with + * a read back operation as the context might have been closed since + * the mailbox was unlocked. When this occurs, registration is failed. + * + * Return: 0 on success, -errno on failure + */ +static int afu_attach(struct cxlflash_cfg *cfg, struct ctx_info *ctxi) +{ + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + struct sisl_ctrl_map __iomem *ctrl_map = ctxi->ctrl_map; + int rc = 0; + u64 val; + + /* Unlock cap and restrict user to read/write cmds in translated mode */ + readq_be(&ctrl_map->mbox_r); + val = (SISL_CTX_CAP_READ_CMD | SISL_CTX_CAP_WRITE_CMD); + writeq_be(val, &ctrl_map->ctx_cap); + val = readq_be(&ctrl_map->ctx_cap); + if (val != (SISL_CTX_CAP_READ_CMD | SISL_CTX_CAP_WRITE_CMD)) { + dev_err(dev, "%s: ctx may be closed val=%016llX\n", + __func__, val); + rc = -EAGAIN; + goto out; + } + + /* Set up MMIO registers pointing to the RHT */ + writeq_be((u64)ctxi->rht_start, &ctrl_map->rht_start); + val = SISL_RHT_CNT_ID((u64)MAX_RHT_PER_CONTEXT, (u64)(afu->ctx_hndl)); + writeq_be(val, &ctrl_map->rht_cnt_id); +out: + dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * read_cap16() - issues a SCSI READ_CAP16 command + * @sdev: SCSI device associated with LUN. + * @lli: LUN destined for capacity request. + * + * The READ_CAP16 can take quite a while to complete. Should an EEH occur while + * in scsi_execute(), the EEH handler will attempt to recover. As part of the + * recovery, the handler drains all currently running ioctls, waiting until they + * have completed before proceeding with a reset. As this routine is used on the + * ioctl path, this can create a condition where the EEH handler becomes stuck, + * infinitely waiting for this ioctl thread. To avoid this behavior, temporarily + * unmark this thread as an ioctl thread by releasing the ioctl read semaphore. + * This will allow the EEH handler to proceed with a recovery while this thread + * is still running. Once the scsi_execute() returns, reacquire the ioctl read + * semaphore and check the adapter state in case it changed while inside of + * scsi_execute(). The state check will wait if the adapter is still being + * recovered or return a failure if the recovery failed. In the event that the + * adapter reset failed, simply return the failure as the ioctl would be unable + * to continue. + * + * Note that the above puts a requirement on this routine to only be called on + * an ioctl thread. + * + * Return: 0 on success, -errno on failure + */ +static int read_cap16(struct scsi_device *sdev, struct llun_info *lli) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct glun_info *gli = lli->parent; + u8 *cmd_buf = NULL; + u8 *scsi_cmd = NULL; + u8 *sense_buf = NULL; + int rc = 0; + int result = 0; + int retry_cnt = 0; + u32 to = CMD_TIMEOUT * HZ; + +retry: + cmd_buf = kzalloc(CMD_BUFSIZE, GFP_KERNEL); + scsi_cmd = kzalloc(MAX_COMMAND_SIZE, GFP_KERNEL); + sense_buf = kzalloc(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL); + if (unlikely(!cmd_buf || !scsi_cmd || !sense_buf)) { + rc = -ENOMEM; + goto out; + } + + scsi_cmd[0] = SERVICE_ACTION_IN_16; /* read cap(16) */ + scsi_cmd[1] = SAI_READ_CAPACITY_16; /* service action */ + put_unaligned_be32(CMD_BUFSIZE, &scsi_cmd[10]); + + dev_dbg(dev, "%s: %ssending cmd(0x%x)\n", __func__, + retry_cnt ? "re" : "", scsi_cmd[0]); + + /* Drop the ioctl read semahpore across lengthy call */ + up_read(&cfg->ioctl_rwsem); + result = scsi_execute(sdev, scsi_cmd, DMA_FROM_DEVICE, cmd_buf, + CMD_BUFSIZE, sense_buf, to, CMD_RETRIES, 0, NULL); + down_read(&cfg->ioctl_rwsem); + rc = check_state(cfg); + if (rc) { + dev_err(dev, "%s: Failed state! result=0x08%X\n", + __func__, result); + rc = -ENODEV; + goto out; + } + + if (driver_byte(result) == DRIVER_SENSE) { + result &= ~(0xFF<<24); /* DRIVER_SENSE is not an error */ + if (result & SAM_STAT_CHECK_CONDITION) { + struct scsi_sense_hdr sshdr; + + scsi_normalize_sense(sense_buf, SCSI_SENSE_BUFFERSIZE, + &sshdr); + switch (sshdr.sense_key) { + case NO_SENSE: + case RECOVERED_ERROR: + /* fall through */ + case NOT_READY: + result &= ~SAM_STAT_CHECK_CONDITION; + break; + case UNIT_ATTENTION: + switch (sshdr.asc) { + case 0x29: /* Power on Reset or Device Reset */ + /* fall through */ + case 0x2A: /* Device capacity changed */ + case 0x3F: /* Report LUNs changed */ + /* Retry the command once more */ + if (retry_cnt++ < 1) { + kfree(cmd_buf); + kfree(scsi_cmd); + kfree(sense_buf); + goto retry; + } + } + break; + default: + break; + } + } + } + + if (result) { + dev_err(dev, "%s: command failed, result=0x%x\n", + __func__, result); + rc = -EIO; + goto out; + } + + /* + * Read cap was successful, grab values from the buffer; + * note that we don't need to worry about unaligned access + * as the buffer is allocated on an aligned boundary. + */ + mutex_lock(&gli->mutex); + gli->max_lba = be64_to_cpu(*((__be64 *)&cmd_buf[0])); + gli->blk_len = be32_to_cpu(*((__be32 *)&cmd_buf[8])); + mutex_unlock(&gli->mutex); + +out: + kfree(cmd_buf); + kfree(scsi_cmd); + kfree(sense_buf); + + dev_dbg(dev, "%s: maxlba=%lld blklen=%d rc=%d\n", + __func__, gli->max_lba, gli->blk_len, rc); + return rc; +} + +/** + * get_rhte() - obtains validated resource handle table entry reference + * @ctxi: Context owning the resource handle. + * @rhndl: Resource handle associated with entry. + * @lli: LUN associated with request. + * + * Return: Validated RHTE on success, NULL on failure + */ +struct sisl_rht_entry *get_rhte(struct ctx_info *ctxi, res_hndl_t rhndl, + struct llun_info *lli) +{ + struct sisl_rht_entry *rhte = NULL; + + if (unlikely(!ctxi->rht_start)) { + pr_debug("%s: Context does not have allocated RHT!\n", + __func__); + goto out; + } + + if (unlikely(rhndl >= MAX_RHT_PER_CONTEXT)) { + pr_debug("%s: Bad resource handle! (%d)\n", __func__, rhndl); + goto out; + } + + if (unlikely(ctxi->rht_lun[rhndl] != lli)) { + pr_debug("%s: Bad resource handle LUN! (%d)\n", + __func__, rhndl); + goto out; + } + + rhte = &ctxi->rht_start[rhndl]; + if (unlikely(rhte->nmask == 0)) { + pr_debug("%s: Unopened resource handle! (%d)\n", + __func__, rhndl); + rhte = NULL; + goto out; + } + +out: + return rhte; +} + +/** + * rhte_checkout() - obtains free/empty resource handle table entry + * @ctxi: Context owning the resource handle. + * @lli: LUN associated with request. + * + * Return: Free RHTE on success, NULL on failure + */ +struct sisl_rht_entry *rhte_checkout(struct ctx_info *ctxi, + struct llun_info *lli) +{ + struct sisl_rht_entry *rhte = NULL; + int i; + + /* Find a free RHT entry */ + for (i = 0; i < MAX_RHT_PER_CONTEXT; i++) + if (ctxi->rht_start[i].nmask == 0) { + rhte = &ctxi->rht_start[i]; + ctxi->rht_out++; + break; + } + + if (likely(rhte)) + ctxi->rht_lun[i] = lli; + + pr_debug("%s: returning rhte=%p (%d)\n", __func__, rhte, i); + return rhte; +} + +/** + * rhte_checkin() - releases a resource handle table entry + * @ctxi: Context owning the resource handle. + * @rhte: RHTE to release. + */ +void rhte_checkin(struct ctx_info *ctxi, + struct sisl_rht_entry *rhte) +{ + u32 rsrc_handle = rhte - ctxi->rht_start; + + rhte->nmask = 0; + rhte->fp = 0; + ctxi->rht_out--; + ctxi->rht_lun[rsrc_handle] = NULL; + ctxi->rht_needs_ws[rsrc_handle] = false; +} + +/** + * rhte_format1() - populates a RHTE for format 1 + * @rhte: RHTE to populate. + * @lun_id: LUN ID of LUN associated with RHTE. + * @perm: Desired permissions for RHTE. + * @port_sel: Port selection mask + */ +static void rht_format1(struct sisl_rht_entry *rhte, u64 lun_id, u32 perm, + u32 port_sel) +{ + /* + * Populate the Format 1 RHT entry for direct access (physical + * LUN) using the synchronization sequence defined in the + * SISLite specification. + */ + struct sisl_rht_entry_f1 dummy = { 0 }; + struct sisl_rht_entry_f1 *rhte_f1 = (struct sisl_rht_entry_f1 *)rhte; + + memset(rhte_f1, 0, sizeof(*rhte_f1)); + rhte_f1->fp = SISL_RHT_FP(1U, 0); + dma_wmb(); /* Make setting of format bit visible */ + + rhte_f1->lun_id = lun_id; + dma_wmb(); /* Make setting of LUN id visible */ + + /* + * Use a dummy RHT Format 1 entry to build the second dword + * of the entry that must be populated in a single write when + * enabled (valid bit set to TRUE). + */ + dummy.valid = 0x80; + dummy.fp = SISL_RHT_FP(1U, perm); + dummy.port_sel = port_sel; + rhte_f1->dw = dummy.dw; + + dma_wmb(); /* Make remaining RHT entry fields visible */ +} + +/** + * cxlflash_lun_attach() - attaches a user to a LUN and manages the LUN's mode + * @gli: LUN to attach. + * @mode: Desired mode of the LUN. + * @locked: Mutex status on current thread. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_lun_attach(struct glun_info *gli, enum lun_mode mode, bool locked) +{ + int rc = 0; + + if (!locked) + mutex_lock(&gli->mutex); + + if (gli->mode == MODE_NONE) + gli->mode = mode; + else if (gli->mode != mode) { + pr_debug("%s: LUN operating in mode %d, requested mode %d\n", + __func__, gli->mode, mode); + rc = -EINVAL; + goto out; + } + + gli->users++; + WARN_ON(gli->users <= 0); +out: + pr_debug("%s: Returning rc=%d gli->mode=%u gli->users=%u\n", + __func__, rc, gli->mode, gli->users); + if (!locked) + mutex_unlock(&gli->mutex); + return rc; +} + +/** + * cxlflash_lun_detach() - detaches a user from a LUN and resets the LUN's mode + * @gli: LUN to detach. + * + * When resetting the mode, terminate block allocation resources as they + * are no longer required (service is safe to call even when block allocation + * resources were not present - such as when transitioning from physical mode). + * These resources will be reallocated when needed (subsequent transition to + * virtual mode). + */ +void cxlflash_lun_detach(struct glun_info *gli) +{ + mutex_lock(&gli->mutex); + WARN_ON(gli->mode == MODE_NONE); + if (--gli->users == 0) { + gli->mode = MODE_NONE; + cxlflash_ba_terminate(&gli->blka.ba_lun); + } + pr_debug("%s: gli->users=%u\n", __func__, gli->users); + WARN_ON(gli->users < 0); + mutex_unlock(&gli->mutex); +} + +/** + * _cxlflash_disk_release() - releases the specified resource entry + * @sdev: SCSI device associated with LUN. + * @ctxi: Context owning resources. + * @release: Release ioctl data structure. + * + * For LUNs in virtual mode, the virtual LUN associated with the specified + * resource handle is resized to 0 prior to releasing the RHTE. Note that the + * AFU sync should _not_ be performed when the context is sitting on the error + * recovery list. A context on the error recovery list is not known to the AFU + * due to reset. When the context is recovered, it will be reattached and made + * known again to the AFU. + * + * Return: 0 on success, -errno on failure + */ +int _cxlflash_disk_release(struct scsi_device *sdev, + struct ctx_info *ctxi, + struct dk_cxlflash_release *release) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct afu *afu = cfg->afu; + bool put_ctx = false; + + struct dk_cxlflash_resize size; + res_hndl_t rhndl = release->rsrc_handle; + + int rc = 0; + u64 ctxid = DECODE_CTXID(release->context_id), + rctxid = release->context_id; + + struct sisl_rht_entry *rhte; + struct sisl_rht_entry_f1 *rhte_f1; + + dev_dbg(dev, "%s: ctxid=%llu rhndl=0x%llx gli->mode=%u gli->users=%u\n", + __func__, ctxid, release->rsrc_handle, gli->mode, gli->users); + + if (!ctxi) { + ctxi = get_context(cfg, rctxid, lli, CTX_CTRL_ERR_FALLBACK); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%llu)\n", + __func__, ctxid); + rc = -EINVAL; + goto out; + } + + put_ctx = true; + } + + rhte = get_rhte(ctxi, rhndl, lli); + if (unlikely(!rhte)) { + dev_dbg(dev, "%s: Bad resource handle! (%d)\n", + __func__, rhndl); + rc = -EINVAL; + goto out; + } + + /* + * Resize to 0 for virtual LUNS by setting the size + * to 0. This will clear LXT_START and LXT_CNT fields + * in the RHT entry and properly sync with the AFU. + * + * Afterwards we clear the remaining fields. + */ + switch (gli->mode) { + case MODE_VIRTUAL: + marshal_rele_to_resize(release, &size); + size.req_size = 0; + rc = _cxlflash_vlun_resize(sdev, ctxi, &size); + if (rc) { + dev_dbg(dev, "%s: resize failed rc %d\n", __func__, rc); + goto out; + } + + break; + case MODE_PHYSICAL: + /* + * Clear the Format 1 RHT entry for direct access + * (physical LUN) using the synchronization sequence + * defined in the SISLite specification. + */ + rhte_f1 = (struct sisl_rht_entry_f1 *)rhte; + + rhte_f1->valid = 0; + dma_wmb(); /* Make revocation of RHT entry visible */ + + rhte_f1->lun_id = 0; + dma_wmb(); /* Make clearing of LUN id visible */ + + rhte_f1->dw = 0; + dma_wmb(); /* Make RHT entry bottom-half clearing visible */ + + if (!ctxi->err_recovery_active) + cxlflash_afu_sync(afu, ctxid, rhndl, AFU_HW_SYNC); + break; + default: + WARN(1, "Unsupported LUN mode!"); + goto out; + } + + rhte_checkin(ctxi, rhte); + cxlflash_lun_detach(gli); + +out: + if (put_ctx) + put_context(ctxi); + dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc); + return rc; +} + +int cxlflash_disk_release(struct scsi_device *sdev, + struct dk_cxlflash_release *release) +{ + return _cxlflash_disk_release(sdev, NULL, release); +} + +/** + * destroy_context() - releases a context + * @cfg: Internal structure associated with the host. + * @ctxi: Context to release. + * + * Note that the rht_lun member of the context was cut from a single + * allocation when the context was created and therefore does not need + * to be explicitly freed. Also note that we conditionally check for the + * existence of the context control map before clearing the RHT registers + * and context capabilities because it is possible to destroy a context + * while the context is in the error state (previous mapping was removed + * [so we don't have to worry about clearing] and context is waiting for + * a new mapping). + */ +static void destroy_context(struct cxlflash_cfg *cfg, + struct ctx_info *ctxi) +{ + struct afu *afu = cfg->afu; + + WARN_ON(!list_empty(&ctxi->luns)); + + /* Clear RHT registers and drop all capabilities for this context */ + if (afu->afu_map && ctxi->ctrl_map) { + writeq_be(0, &ctxi->ctrl_map->rht_start); + writeq_be(0, &ctxi->ctrl_map->rht_cnt_id); + writeq_be(0, &ctxi->ctrl_map->ctx_cap); + } + + /* Free memory associated with context */ + free_page((ulong)ctxi->rht_start); + kfree(ctxi->rht_needs_ws); + kfree(ctxi->rht_lun); + kfree(ctxi); +} + +/** + * create_context() - allocates and initializes a context + * @cfg: Internal structure associated with the host. + * @ctx: Previously obtained CXL context reference. + * @ctxid: Previously obtained process element associated with CXL context. + * @adap_fd: Previously obtained adapter fd associated with CXL context. + * @file: Previously obtained file associated with CXL context. + * @perms: User-specified permissions. + * + * The context's mutex is locked when an allocated context is returned. + * + * Return: Allocated context on success, NULL on failure + */ +static struct ctx_info *create_context(struct cxlflash_cfg *cfg, + struct cxl_context *ctx, int ctxid, + int adap_fd, struct file *file, + u32 perms) +{ + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + struct ctx_info *ctxi = NULL; + struct llun_info **lli = NULL; + u8 *ws = NULL; + struct sisl_rht_entry *rhte; + + ctxi = kzalloc(sizeof(*ctxi), GFP_KERNEL); + lli = kzalloc((MAX_RHT_PER_CONTEXT * sizeof(*lli)), GFP_KERNEL); + ws = kzalloc((MAX_RHT_PER_CONTEXT * sizeof(*ws)), GFP_KERNEL); + if (unlikely(!ctxi || !lli || !ws)) { + dev_err(dev, "%s: Unable to allocate context!\n", __func__); + goto err; + } + + rhte = (struct sisl_rht_entry *)get_zeroed_page(GFP_KERNEL); + if (unlikely(!rhte)) { + dev_err(dev, "%s: Unable to allocate RHT!\n", __func__); + goto err; + } + + ctxi->rht_lun = lli; + ctxi->rht_needs_ws = ws; + ctxi->rht_start = rhte; + ctxi->rht_perms = perms; + + ctxi->ctrl_map = &afu->afu_map->ctrls[ctxid].ctrl; + ctxi->ctxid = ENCODE_CTXID(ctxi, ctxid); + ctxi->lfd = adap_fd; + ctxi->pid = current->tgid; /* tgid = pid */ + ctxi->ctx = ctx; + ctxi->file = file; + mutex_init(&ctxi->mutex); + INIT_LIST_HEAD(&ctxi->luns); + INIT_LIST_HEAD(&ctxi->list); /* initialize for list_empty() */ + + mutex_lock(&ctxi->mutex); +out: + return ctxi; + +err: + kfree(ws); + kfree(lli); + kfree(ctxi); + ctxi = NULL; + goto out; +} + +/** + * _cxlflash_disk_detach() - detaches a LUN from a context + * @sdev: SCSI device associated with LUN. + * @ctxi: Context owning resources. + * @detach: Detach ioctl data structure. + * + * As part of the detach, all per-context resources associated with the LUN + * are cleaned up. When detaching the last LUN for a context, the context + * itself is cleaned up and released. + * + * Return: 0 on success, -errno on failure + */ +static int _cxlflash_disk_detach(struct scsi_device *sdev, + struct ctx_info *ctxi, + struct dk_cxlflash_detach *detach) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct lun_access *lun_access, *t; + struct dk_cxlflash_release rel; + bool put_ctx = false; + + int i; + int rc = 0; + int lfd; + u64 ctxid = DECODE_CTXID(detach->context_id), + rctxid = detach->context_id; + + dev_dbg(dev, "%s: ctxid=%llu\n", __func__, ctxid); + + if (!ctxi) { + ctxi = get_context(cfg, rctxid, lli, CTX_CTRL_ERR_FALLBACK); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%llu)\n", + __func__, ctxid); + rc = -EINVAL; + goto out; + } + + put_ctx = true; + } + + /* Cleanup outstanding resources tied to this LUN */ + if (ctxi->rht_out) { + marshal_det_to_rele(detach, &rel); + for (i = 0; i < MAX_RHT_PER_CONTEXT; i++) { + if (ctxi->rht_lun[i] == lli) { + rel.rsrc_handle = i; + _cxlflash_disk_release(sdev, ctxi, &rel); + } + + /* No need to loop further if we're done */ + if (ctxi->rht_out == 0) + break; + } + } + + /* Take our LUN out of context, free the node */ + list_for_each_entry_safe(lun_access, t, &ctxi->luns, list) + if (lun_access->lli == lli) { + list_del(&lun_access->list); + kfree(lun_access); + lun_access = NULL; + break; + } + + /* Tear down context following last LUN cleanup */ + if (list_empty(&ctxi->luns)) { + ctxi->unavail = true; + mutex_unlock(&ctxi->mutex); + mutex_lock(&cfg->ctx_tbl_list_mutex); + mutex_lock(&ctxi->mutex); + + /* Might not have been in error list so conditionally remove */ + if (!list_empty(&ctxi->list)) + list_del(&ctxi->list); + cfg->ctx_tbl[ctxid] = NULL; + mutex_unlock(&cfg->ctx_tbl_list_mutex); + mutex_unlock(&ctxi->mutex); + + lfd = ctxi->lfd; + destroy_context(cfg, ctxi); + ctxi = NULL; + put_ctx = false; + + /* + * As a last step, clean up external resources when not + * already on an external cleanup thread, i.e.: close(adap_fd). + * + * NOTE: this will free up the context from the CXL services, + * allowing it to dole out the same context_id on a future + * (or even currently in-flight) disk_attach operation. + */ + if (lfd != -1) + sys_close(lfd); + } + + /* Release the sdev reference that bound this LUN to the context */ + scsi_device_put(sdev); + +out: + if (put_ctx) + put_context(ctxi); + dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc); + return rc; +} + +static int cxlflash_disk_detach(struct scsi_device *sdev, + struct dk_cxlflash_detach *detach) +{ + return _cxlflash_disk_detach(sdev, NULL, detach); +} + +/** + * cxlflash_cxl_release() - release handler for adapter file descriptor + * @inode: File-system inode associated with fd. + * @file: File installed with adapter file descriptor. + * + * This routine is the release handler for the fops registered with + * the CXL services on an initial attach for a context. It is called + * when a close is performed on the adapter file descriptor returned + * to the user. Programmatically, the user is not required to perform + * the close, as it is handled internally via the detach ioctl when + * a context is being removed. Note that nothing prevents the user + * from performing a close, but the user should be aware that doing + * so is considered catastrophic and subsequent usage of the superpipe + * API with previously saved off tokens will fail. + * + * When initiated from an external close (either by the user or via + * a process tear down), the routine derives the context reference + * and calls detach for each LUN associated with the context. The + * final detach operation will cause the context itself to be freed. + * Note that the saved off lfd is reset prior to calling detach to + * signify that the final detach should not perform a close. + * + * When initiated from a detach operation as part of the tear down + * of a context, the context is first completely freed and then the + * close is performed. This routine will fail to derive the context + * reference (due to the context having already been freed) and then + * call into the CXL release entry point. + * + * Thus, with exception to when the CXL process element (context id) + * lookup fails (a case that should theoretically never occur), every + * call into this routine results in a complete freeing of a context. + * + * As part of the detach, all per-context resources associated with the LUN + * are cleaned up. When detaching the last LUN for a context, the context + * itself is cleaned up and released. + * + * Return: 0 on success + */ +static int cxlflash_cxl_release(struct inode *inode, struct file *file) +{ + struct cxl_context *ctx = cxl_fops_get_context(file); + struct cxlflash_cfg *cfg = container_of(file->f_op, struct cxlflash_cfg, + cxl_fops); + struct device *dev = &cfg->dev->dev; + struct ctx_info *ctxi = NULL; + struct dk_cxlflash_detach detach = { { 0 }, 0 }; + struct lun_access *lun_access, *t; + enum ctx_ctrl ctrl = CTX_CTRL_ERR_FALLBACK | CTX_CTRL_FILE; + int ctxid; + + ctxid = cxl_process_element(ctx); + if (unlikely(ctxid < 0)) { + dev_err(dev, "%s: Context %p was closed! (%d)\n", + __func__, ctx, ctxid); + goto out; + } + + ctxi = get_context(cfg, ctxid, file, ctrl); + if (unlikely(!ctxi)) { + ctxi = get_context(cfg, ctxid, file, ctrl | CTX_CTRL_CLONE); + if (!ctxi) { + dev_dbg(dev, "%s: Context %d already free!\n", + __func__, ctxid); + goto out_release; + } + + dev_dbg(dev, "%s: Another process owns context %d!\n", + __func__, ctxid); + put_context(ctxi); + goto out; + } + + dev_dbg(dev, "%s: close(%d) for context %d\n", + __func__, ctxi->lfd, ctxid); + + /* Reset the file descriptor to indicate we're on a close() thread */ + ctxi->lfd = -1; + detach.context_id = ctxi->ctxid; + list_for_each_entry_safe(lun_access, t, &ctxi->luns, list) + _cxlflash_disk_detach(lun_access->sdev, ctxi, &detach); +out_release: + cxl_fd_release(inode, file); +out: + dev_dbg(dev, "%s: returning\n", __func__); + return 0; +} + +/** + * unmap_context() - clears a previously established mapping + * @ctxi: Context owning the mapping. + * + * This routine is used to switch between the error notification page + * (dummy page of all 1's) and the real mapping (established by the CXL + * fault handler). + */ +static void unmap_context(struct ctx_info *ctxi) +{ + unmap_mapping_range(ctxi->file->f_mapping, 0, 0, 1); +} + +/** + * get_err_page() - obtains and allocates the error notification page + * + * Return: error notification page on success, NULL on failure + */ +static struct page *get_err_page(void) +{ + struct page *err_page = global.err_page; + + if (unlikely(!err_page)) { + err_page = alloc_page(GFP_KERNEL); + if (unlikely(!err_page)) { + pr_err("%s: Unable to allocate err_page!\n", __func__); + goto out; + } + + memset(page_address(err_page), -1, PAGE_SIZE); + + /* Serialize update w/ other threads to avoid a leak */ + mutex_lock(&global.mutex); + if (likely(!global.err_page)) + global.err_page = err_page; + else { + __free_page(err_page); + err_page = global.err_page; + } + mutex_unlock(&global.mutex); + } + +out: + pr_debug("%s: returning err_page=%p\n", __func__, err_page); + return err_page; +} + +/** + * cxlflash_mmap_fault() - mmap fault handler for adapter file descriptor + * @vma: VM area associated with mapping. + * @vmf: VM fault associated with current fault. + * + * To support error notification via MMIO, faults are 'caught' by this routine + * that was inserted before passing back the adapter file descriptor on attach. + * When a fault occurs, this routine evaluates if error recovery is active and + * if so, installs the error page to 'notify' the user about the error state. + * During normal operation, the fault is simply handled by the original fault + * handler that was installed by CXL services as part of initializing the + * adapter file descriptor. The VMA's page protection bits are toggled to + * indicate cached/not-cached depending on the memory backing the fault. + * + * Return: 0 on success, VM_FAULT_SIGBUS on failure + */ +static int cxlflash_mmap_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct file *file = vma->vm_file; + struct cxl_context *ctx = cxl_fops_get_context(file); + struct cxlflash_cfg *cfg = container_of(file->f_op, struct cxlflash_cfg, + cxl_fops); + struct device *dev = &cfg->dev->dev; + struct ctx_info *ctxi = NULL; + struct page *err_page = NULL; + enum ctx_ctrl ctrl = CTX_CTRL_ERR_FALLBACK | CTX_CTRL_FILE; + int rc = 0; + int ctxid; + + ctxid = cxl_process_element(ctx); + if (unlikely(ctxid < 0)) { + dev_err(dev, "%s: Context %p was closed! (%d)\n", + __func__, ctx, ctxid); + goto err; + } + + ctxi = get_context(cfg, ctxid, file, ctrl); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%d)\n", __func__, ctxid); + goto err; + } + + dev_dbg(dev, "%s: fault(%d) for context %d\n", + __func__, ctxi->lfd, ctxid); + + if (likely(!ctxi->err_recovery_active)) { + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + rc = ctxi->cxl_mmap_vmops->fault(vma, vmf); + } else { + dev_dbg(dev, "%s: err recovery active, use err_page!\n", + __func__); + + err_page = get_err_page(); + if (unlikely(!err_page)) { + dev_err(dev, "%s: Could not obtain error page!\n", + __func__); + rc = VM_FAULT_RETRY; + goto out; + } + + get_page(err_page); + vmf->page = err_page; + vma->vm_page_prot = pgprot_cached(vma->vm_page_prot); + } + +out: + if (likely(ctxi)) + put_context(ctxi); + dev_dbg(dev, "%s: returning rc=%d\n", __func__, rc); + return rc; + +err: + rc = VM_FAULT_SIGBUS; + goto out; +} + +/* + * Local MMAP vmops to 'catch' faults + */ +static const struct vm_operations_struct cxlflash_mmap_vmops = { + .fault = cxlflash_mmap_fault, +}; + +/** + * cxlflash_cxl_mmap() - mmap handler for adapter file descriptor + * @file: File installed with adapter file descriptor. + * @vma: VM area associated with mapping. + * + * Installs local mmap vmops to 'catch' faults for error notification support. + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_cxl_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cxl_context *ctx = cxl_fops_get_context(file); + struct cxlflash_cfg *cfg = container_of(file->f_op, struct cxlflash_cfg, + cxl_fops); + struct device *dev = &cfg->dev->dev; + struct ctx_info *ctxi = NULL; + enum ctx_ctrl ctrl = CTX_CTRL_ERR_FALLBACK | CTX_CTRL_FILE; + int ctxid; + int rc = 0; + + ctxid = cxl_process_element(ctx); + if (unlikely(ctxid < 0)) { + dev_err(dev, "%s: Context %p was closed! (%d)\n", + __func__, ctx, ctxid); + rc = -EIO; + goto out; + } + + ctxi = get_context(cfg, ctxid, file, ctrl); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%d)\n", __func__, ctxid); + rc = -EIO; + goto out; + } + + dev_dbg(dev, "%s: mmap(%d) for context %d\n", + __func__, ctxi->lfd, ctxid); + + rc = cxl_fd_mmap(file, vma); + if (likely(!rc)) { + /* Insert ourself in the mmap fault handler path */ + ctxi->cxl_mmap_vmops = vma->vm_ops; + vma->vm_ops = &cxlflash_mmap_vmops; + } + +out: + if (likely(ctxi)) + put_context(ctxi); + return rc; +} + +const struct file_operations cxlflash_cxl_fops = { + .owner = THIS_MODULE, + .mmap = cxlflash_cxl_mmap, + .release = cxlflash_cxl_release, +}; + +/** + * cxlflash_mark_contexts_error() - move contexts to error state and list + * @cfg: Internal structure associated with the host. + * + * A context is only moved over to the error list when there are no outstanding + * references to it. This ensures that a running operation has completed. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_mark_contexts_error(struct cxlflash_cfg *cfg) +{ + int i, rc = 0; + struct ctx_info *ctxi = NULL; + + mutex_lock(&cfg->ctx_tbl_list_mutex); + + for (i = 0; i < MAX_CONTEXT; i++) { + ctxi = cfg->ctx_tbl[i]; + if (ctxi) { + mutex_lock(&ctxi->mutex); + cfg->ctx_tbl[i] = NULL; + list_add(&ctxi->list, &cfg->ctx_err_recovery); + ctxi->err_recovery_active = true; + ctxi->ctrl_map = NULL; + unmap_context(ctxi); + mutex_unlock(&ctxi->mutex); + } + } + + mutex_unlock(&cfg->ctx_tbl_list_mutex); + return rc; +} + +/* + * Dummy NULL fops + */ +static const struct file_operations null_fops = { + .owner = THIS_MODULE, +}; + +/** + * check_state() - checks and responds to the current adapter state + * @cfg: Internal structure associated with the host. + * + * This routine can block and should only be used on process context. + * It assumes that the caller is an ioctl thread and holding the ioctl + * read semaphore. This is temporarily let up across the wait to allow + * for draining actively running ioctls. Also note that when waking up + * from waiting in reset, the state is unknown and must be checked again + * before proceeding. + * + * Return: 0 on success, -errno on failure + */ +int check_state(struct cxlflash_cfg *cfg) +{ + struct device *dev = &cfg->dev->dev; + int rc = 0; + +retry: + switch (cfg->state) { + case STATE_RESET: + dev_dbg(dev, "%s: Reset state, going to wait...\n", __func__); + up_read(&cfg->ioctl_rwsem); + rc = wait_event_interruptible(cfg->reset_waitq, + cfg->state != STATE_RESET); + down_read(&cfg->ioctl_rwsem); + if (unlikely(rc)) + break; + goto retry; + case STATE_FAILTERM: + dev_dbg(dev, "%s: Failed/Terminating!\n", __func__); + rc = -ENODEV; + break; + default: + break; + } + + return rc; +} + +/** + * cxlflash_disk_attach() - attach a LUN to a context + * @sdev: SCSI device associated with LUN. + * @attach: Attach ioctl data structure. + * + * Creates a context and attaches LUN to it. A LUN can only be attached + * one time to a context (subsequent attaches for the same context/LUN pair + * are not supported). Additional LUNs can be attached to a context by + * specifying the 'reuse' flag defined in the cxlflash_ioctl.h header. + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_disk_attach(struct scsi_device *sdev, + struct dk_cxlflash_attach *attach) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct cxl_ioctl_start_work *work; + struct ctx_info *ctxi = NULL; + struct lun_access *lun_access = NULL; + int rc = 0; + u32 perms; + int ctxid = -1; + u64 rctxid = 0UL; + struct file *file; + + struct cxl_context *ctx; + + int fd = -1; + + if (attach->num_interrupts > 4) { + dev_dbg(dev, "%s: Cannot support this many interrupts %llu\n", + __func__, attach->num_interrupts); + rc = -EINVAL; + goto out; + } + + if (gli->max_lba == 0) { + dev_dbg(dev, "%s: No capacity info for this LUN (%016llX)\n", + __func__, lli->lun_id[sdev->channel]); + rc = read_cap16(sdev, lli); + if (rc) { + dev_err(dev, "%s: Invalid device! (%d)\n", + __func__, rc); + rc = -ENODEV; + goto out; + } + dev_dbg(dev, "%s: LBA = %016llX\n", __func__, gli->max_lba); + dev_dbg(dev, "%s: BLK_LEN = %08X\n", __func__, gli->blk_len); + } + + if (attach->hdr.flags & DK_CXLFLASH_ATTACH_REUSE_CONTEXT) { + rctxid = attach->context_id; + ctxi = get_context(cfg, rctxid, NULL, 0); + if (!ctxi) { + dev_dbg(dev, "%s: Bad context! (%016llX)\n", + __func__, rctxid); + rc = -EINVAL; + goto out; + } + + list_for_each_entry(lun_access, &ctxi->luns, list) + if (lun_access->lli == lli) { + dev_dbg(dev, "%s: Already attached!\n", + __func__); + rc = -EINVAL; + goto out; + } + } + + rc = scsi_device_get(sdev); + if (unlikely(rc)) { + dev_err(dev, "%s: Unable to get sdev reference!\n", __func__); + goto out; + } + + lun_access = kzalloc(sizeof(*lun_access), GFP_KERNEL); + if (unlikely(!lun_access)) { + dev_err(dev, "%s: Unable to allocate lun_access!\n", __func__); + rc = -ENOMEM; + goto err0; + } + + lun_access->lli = lli; + lun_access->sdev = sdev; + + /* Non-NULL context indicates reuse */ + if (ctxi) { + dev_dbg(dev, "%s: Reusing context for LUN! (%016llX)\n", + __func__, rctxid); + list_add(&lun_access->list, &ctxi->luns); + fd = ctxi->lfd; + goto out_attach; + } + + ctx = cxl_dev_context_init(cfg->dev); + if (unlikely(IS_ERR_OR_NULL(ctx))) { + dev_err(dev, "%s: Could not initialize context %p\n", + __func__, ctx); + rc = -ENODEV; + goto err1; + } + + ctxid = cxl_process_element(ctx); + if (unlikely((ctxid > MAX_CONTEXT) || (ctxid < 0))) { + dev_err(dev, "%s: ctxid (%d) invalid!\n", __func__, ctxid); + rc = -EPERM; + goto err2; + } + + file = cxl_get_fd(ctx, &cfg->cxl_fops, &fd); + if (unlikely(fd < 0)) { + rc = -ENODEV; + dev_err(dev, "%s: Could not get file descriptor\n", __func__); + goto err2; + } + + /* Translate read/write O_* flags from fcntl.h to AFU permission bits */ + perms = SISL_RHT_PERM(attach->hdr.flags + 1); + + ctxi = create_context(cfg, ctx, ctxid, fd, file, perms); + if (unlikely(!ctxi)) { + dev_err(dev, "%s: Failed to create context! (%d)\n", + __func__, ctxid); + goto err3; + } + + work = &ctxi->work; + work->num_interrupts = attach->num_interrupts; + work->flags = CXL_START_WORK_NUM_IRQS; + + rc = cxl_start_work(ctx, work); + if (unlikely(rc)) { + dev_dbg(dev, "%s: Could not start context rc=%d\n", + __func__, rc); + goto err4; + } + + rc = afu_attach(cfg, ctxi); + if (unlikely(rc)) { + dev_err(dev, "%s: Could not attach AFU rc %d\n", __func__, rc); + goto err5; + } + + /* + * No error paths after this point. Once the fd is installed it's + * visible to user space and can't be undone safely on this thread. + * There is no need to worry about a deadlock here because no one + * knows about us yet; we can be the only one holding our mutex. + */ + list_add(&lun_access->list, &ctxi->luns); + mutex_unlock(&ctxi->mutex); + mutex_lock(&cfg->ctx_tbl_list_mutex); + mutex_lock(&ctxi->mutex); + cfg->ctx_tbl[ctxid] = ctxi; + mutex_unlock(&cfg->ctx_tbl_list_mutex); + fd_install(fd, file); + +out_attach: + attach->hdr.return_flags = 0; + attach->context_id = ctxi->ctxid; + attach->block_size = gli->blk_len; + attach->mmio_size = sizeof(afu->afu_map->hosts[0].harea); + attach->last_lba = gli->max_lba; + attach->max_xfer = sdev->host->max_sectors * MAX_SECTOR_UNIT; + attach->max_xfer /= gli->blk_len; + +out: + attach->adap_fd = fd; + + if (ctxi) + put_context(ctxi); + + dev_dbg(dev, "%s: returning ctxid=%d fd=%d bs=%lld rc=%d llba=%lld\n", + __func__, ctxid, fd, attach->block_size, rc, attach->last_lba); + return rc; + +err5: + cxl_stop_context(ctx); +err4: + put_context(ctxi); + destroy_context(cfg, ctxi); + ctxi = NULL; +err3: + /* + * Here, we're overriding the fops with a dummy all-NULL fops because + * fput() calls the release fop, which will cause us to mistakenly + * call into the CXL code. Rather than try to add yet more complexity + * to that routine (cxlflash_cxl_release) we should try to fix the + * issue here. + */ + file->f_op = &null_fops; + fput(file); + put_unused_fd(fd); + fd = -1; +err2: + cxl_release_context(ctx); +err1: + kfree(lun_access); +err0: + scsi_device_put(sdev); + goto out; +} + +/** + * recover_context() - recovers a context in error + * @cfg: Internal structure associated with the host. + * @ctxi: Context to release. + * + * Restablishes the state for a context-in-error. + * + * Return: 0 on success, -errno on failure + */ +static int recover_context(struct cxlflash_cfg *cfg, struct ctx_info *ctxi) +{ + struct device *dev = &cfg->dev->dev; + int rc = 0; + int old_fd, fd = -1; + int ctxid = -1; + struct file *file; + struct cxl_context *ctx; + struct afu *afu = cfg->afu; + + ctx = cxl_dev_context_init(cfg->dev); + if (unlikely(IS_ERR_OR_NULL(ctx))) { + dev_err(dev, "%s: Could not initialize context %p\n", + __func__, ctx); + rc = -ENODEV; + goto out; + } + + ctxid = cxl_process_element(ctx); + if (unlikely((ctxid > MAX_CONTEXT) || (ctxid < 0))) { + dev_err(dev, "%s: ctxid (%d) invalid!\n", __func__, ctxid); + rc = -EPERM; + goto err1; + } + + file = cxl_get_fd(ctx, &cfg->cxl_fops, &fd); + if (unlikely(fd < 0)) { + rc = -ENODEV; + dev_err(dev, "%s: Could not get file descriptor\n", __func__); + goto err1; + } + + rc = cxl_start_work(ctx, &ctxi->work); + if (unlikely(rc)) { + dev_dbg(dev, "%s: Could not start context rc=%d\n", + __func__, rc); + goto err2; + } + + /* Update with new MMIO area based on updated context id */ + ctxi->ctrl_map = &afu->afu_map->ctrls[ctxid].ctrl; + + rc = afu_attach(cfg, ctxi); + if (rc) { + dev_err(dev, "%s: Could not attach AFU rc %d\n", __func__, rc); + goto err3; + } + + /* + * No error paths after this point. Once the fd is installed it's + * visible to user space and can't be undone safely on this thread. + */ + old_fd = ctxi->lfd; + ctxi->ctxid = ENCODE_CTXID(ctxi, ctxid); + ctxi->lfd = fd; + ctxi->ctx = ctx; + ctxi->file = file; + + /* + * Put context back in table (note the reinit of the context list); + * we must first drop the context's mutex and then acquire it in + * order with the table/list mutex to avoid a deadlock - safe to do + * here because no one can find us at this moment in time. + */ + mutex_unlock(&ctxi->mutex); + mutex_lock(&cfg->ctx_tbl_list_mutex); + mutex_lock(&ctxi->mutex); + list_del_init(&ctxi->list); + cfg->ctx_tbl[ctxid] = ctxi; + mutex_unlock(&cfg->ctx_tbl_list_mutex); + fd_install(fd, file); + + /* Release the original adapter fd and associated CXL resources */ + sys_close(old_fd); +out: + dev_dbg(dev, "%s: returning ctxid=%d fd=%d rc=%d\n", + __func__, ctxid, fd, rc); + return rc; + +err3: + cxl_stop_context(ctx); +err2: + fput(file); + put_unused_fd(fd); +err1: + cxl_release_context(ctx); + goto out; +} + +/** + * cxlflash_afu_recover() - initiates AFU recovery + * @sdev: SCSI device associated with LUN. + * @recover: Recover ioctl data structure. + * + * Only a single recovery is allowed at a time to avoid exhausting CXL + * resources (leading to recovery failure) in the event that we're up + * against the maximum number of contexts limit. For similar reasons, + * a context recovery is retried if there are multiple recoveries taking + * place at the same time and the failure was due to CXL services being + * unable to keep up. + * + * Because a user can detect an error condition before the kernel, it is + * quite possible for this routine to act as the kernel's EEH detection + * source (MMIO read of mbox_r). Because of this, there is a window of + * time where an EEH might have been detected but not yet 'serviced' + * (callback invoked, causing the device to enter reset state). To avoid + * looping in this routine during that window, a 1 second sleep is in place + * between the time the MMIO failure is detected and the time a wait on the + * reset wait queue is attempted via check_state(). + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_afu_recover(struct scsi_device *sdev, + struct dk_cxlflash_recover_afu *recover) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct afu *afu = cfg->afu; + struct ctx_info *ctxi = NULL; + struct mutex *mutex = &cfg->ctx_recovery_mutex; + u64 ctxid = DECODE_CTXID(recover->context_id), + rctxid = recover->context_id; + long reg; + int lretry = 20; /* up to 2 seconds */ + int rc = 0; + + atomic_inc(&cfg->recovery_threads); + rc = mutex_lock_interruptible(mutex); + if (rc) + goto out; + + dev_dbg(dev, "%s: reason 0x%016llX rctxid=%016llX\n", + __func__, recover->reason, rctxid); + +retry: + /* Ensure that this process is attached to the context */ + ctxi = get_context(cfg, rctxid, lli, CTX_CTRL_ERR_FALLBACK); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%llu)\n", __func__, ctxid); + rc = -EINVAL; + goto out; + } + + if (ctxi->err_recovery_active) { +retry_recover: + rc = recover_context(cfg, ctxi); + if (unlikely(rc)) { + dev_err(dev, "%s: Recovery failed for context %llu (rc=%d)\n", + __func__, ctxid, rc); + if ((rc == -ENODEV) && + ((atomic_read(&cfg->recovery_threads) > 1) || + (lretry--))) { + dev_dbg(dev, "%s: Going to try again!\n", + __func__); + mutex_unlock(mutex); + msleep(100); + rc = mutex_lock_interruptible(mutex); + if (rc) + goto out; + goto retry_recover; + } + + goto out; + } + + ctxi->err_recovery_active = false; + recover->context_id = ctxi->ctxid; + recover->adap_fd = ctxi->lfd; + recover->mmio_size = sizeof(afu->afu_map->hosts[0].harea); + recover->hdr.return_flags |= + DK_CXLFLASH_RECOVER_AFU_CONTEXT_RESET; + goto out; + } + + /* Test if in error state */ + reg = readq_be(&afu->ctrl_map->mbox_r); + if (reg == -1) { + dev_dbg(dev, "%s: MMIO fail, wait for recovery.\n", __func__); + + /* + * Before checking the state, put back the context obtained with + * get_context() as it is no longer needed and sleep for a short + * period of time (see prolog notes). + */ + put_context(ctxi); + ctxi = NULL; + ssleep(1); + rc = check_state(cfg); + if (unlikely(rc)) + goto out; + goto retry; + } + + dev_dbg(dev, "%s: MMIO working, no recovery required!\n", __func__); +out: + if (likely(ctxi)) + put_context(ctxi); + mutex_unlock(mutex); + atomic_dec_if_positive(&cfg->recovery_threads); + return rc; +} + +/** + * process_sense() - evaluates and processes sense data + * @sdev: SCSI device associated with LUN. + * @verify: Verify ioctl data structure. + * + * Return: 0 on success, -errno on failure + */ +static int process_sense(struct scsi_device *sdev, + struct dk_cxlflash_verify *verify) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + u64 prev_lba = gli->max_lba; + struct scsi_sense_hdr sshdr = { 0 }; + int rc = 0; + + rc = scsi_normalize_sense((const u8 *)&verify->sense_data, + DK_CXLFLASH_VERIFY_SENSE_LEN, &sshdr); + if (!rc) { + dev_err(dev, "%s: Failed to normalize sense data!\n", __func__); + rc = -EINVAL; + goto out; + } + + switch (sshdr.sense_key) { + case NO_SENSE: + case RECOVERED_ERROR: + /* fall through */ + case NOT_READY: + break; + case UNIT_ATTENTION: + switch (sshdr.asc) { + case 0x29: /* Power on Reset or Device Reset */ + /* fall through */ + case 0x2A: /* Device settings/capacity changed */ + rc = read_cap16(sdev, lli); + if (rc) { + rc = -ENODEV; + break; + } + if (prev_lba != gli->max_lba) + dev_dbg(dev, "%s: Capacity changed old=%lld " + "new=%lld\n", __func__, prev_lba, + gli->max_lba); + break; + case 0x3F: /* Report LUNs changed, Rescan. */ + scsi_scan_host(cfg->host); + break; + default: + rc = -EIO; + break; + } + break; + default: + rc = -EIO; + break; + } +out: + dev_dbg(dev, "%s: sense_key %x asc %x ascq %x rc %d\n", __func__, + sshdr.sense_key, sshdr.asc, sshdr.ascq, rc); + return rc; +} + +/** + * cxlflash_disk_verify() - verifies a LUN is the same and handle size changes + * @sdev: SCSI device associated with LUN. + * @verify: Verify ioctl data structure. + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_disk_verify(struct scsi_device *sdev, + struct dk_cxlflash_verify *verify) +{ + int rc = 0; + struct ctx_info *ctxi = NULL; + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct sisl_rht_entry *rhte = NULL; + res_hndl_t rhndl = verify->rsrc_handle; + u64 ctxid = DECODE_CTXID(verify->context_id), + rctxid = verify->context_id; + u64 last_lba = 0; + + dev_dbg(dev, "%s: ctxid=%llu rhndl=%016llX, hint=%016llX, " + "flags=%016llX\n", __func__, ctxid, verify->rsrc_handle, + verify->hint, verify->hdr.flags); + + ctxi = get_context(cfg, rctxid, lli, 0); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%llu)\n", __func__, ctxid); + rc = -EINVAL; + goto out; + } + + rhte = get_rhte(ctxi, rhndl, lli); + if (unlikely(!rhte)) { + dev_dbg(dev, "%s: Bad resource handle! (%d)\n", + __func__, rhndl); + rc = -EINVAL; + goto out; + } + + /* + * Look at the hint/sense to see if it requires us to redrive + * inquiry (i.e. the Unit attention is due to the WWN changing). + */ + if (verify->hint & DK_CXLFLASH_VERIFY_HINT_SENSE) { + /* Can't hold mutex across process_sense/read_cap16, + * since we could have an intervening EEH event. + */ + ctxi->unavail = true; + mutex_unlock(&ctxi->mutex); + rc = process_sense(sdev, verify); + if (unlikely(rc)) { + dev_err(dev, "%s: Failed to validate sense data (%d)\n", + __func__, rc); + mutex_lock(&ctxi->mutex); + ctxi->unavail = false; + goto out; + } + mutex_lock(&ctxi->mutex); + ctxi->unavail = false; + } + + switch (gli->mode) { + case MODE_PHYSICAL: + last_lba = gli->max_lba; + break; + case MODE_VIRTUAL: + /* Cast lxt_cnt to u64 for multiply to be treated as 64bit op */ + last_lba = ((u64)rhte->lxt_cnt * MC_CHUNK_SIZE * gli->blk_len); + last_lba /= CXLFLASH_BLOCK_SIZE; + last_lba--; + break; + default: + WARN(1, "Unsupported LUN mode!"); + } + + verify->last_lba = last_lba; + +out: + if (likely(ctxi)) + put_context(ctxi); + dev_dbg(dev, "%s: returning rc=%d llba=%llX\n", + __func__, rc, verify->last_lba); + return rc; +} + +/** + * decode_ioctl() - translates an encoded ioctl to an easily identifiable string + * @cmd: The ioctl command to decode. + * + * Return: A string identifying the decoded ioctl. + */ +static char *decode_ioctl(int cmd) +{ + switch (cmd) { + case DK_CXLFLASH_ATTACH: + return __stringify_1(DK_CXLFLASH_ATTACH); + case DK_CXLFLASH_USER_DIRECT: + return __stringify_1(DK_CXLFLASH_USER_DIRECT); + case DK_CXLFLASH_USER_VIRTUAL: + return __stringify_1(DK_CXLFLASH_USER_VIRTUAL); + case DK_CXLFLASH_VLUN_RESIZE: + return __stringify_1(DK_CXLFLASH_VLUN_RESIZE); + case DK_CXLFLASH_RELEASE: + return __stringify_1(DK_CXLFLASH_RELEASE); + case DK_CXLFLASH_DETACH: + return __stringify_1(DK_CXLFLASH_DETACH); + case DK_CXLFLASH_VERIFY: + return __stringify_1(DK_CXLFLASH_VERIFY); + case DK_CXLFLASH_VLUN_CLONE: + return __stringify_1(DK_CXLFLASH_VLUN_CLONE); + case DK_CXLFLASH_RECOVER_AFU: + return __stringify_1(DK_CXLFLASH_RECOVER_AFU); + case DK_CXLFLASH_MANAGE_LUN: + return __stringify_1(DK_CXLFLASH_MANAGE_LUN); + } + + return "UNKNOWN"; +} + +/** + * cxlflash_disk_direct_open() - opens a direct (physical) disk + * @sdev: SCSI device associated with LUN. + * @arg: UDirect ioctl data structure. + * + * On successful return, the user is informed of the resource handle + * to be used to identify the direct lun and the size (in blocks) of + * the direct lun in last LBA format. + * + * Return: 0 on success, -errno on failure + */ +static int cxlflash_disk_direct_open(struct scsi_device *sdev, void *arg) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + + struct dk_cxlflash_udirect *pphys = (struct dk_cxlflash_udirect *)arg; + + u64 ctxid = DECODE_CTXID(pphys->context_id), + rctxid = pphys->context_id; + u64 lun_size = 0; + u64 last_lba = 0; + u64 rsrc_handle = -1; + u32 port = CHAN2PORT(sdev->channel); + + int rc = 0; + + struct ctx_info *ctxi = NULL; + struct sisl_rht_entry *rhte = NULL; + + pr_debug("%s: ctxid=%llu ls=0x%llx\n", __func__, ctxid, lun_size); + + rc = cxlflash_lun_attach(gli, MODE_PHYSICAL, false); + if (unlikely(rc)) { + dev_dbg(dev, "%s: Failed to attach to LUN! (PHYSICAL)\n", + __func__); + goto out; + } + + ctxi = get_context(cfg, rctxid, lli, 0); + if (unlikely(!ctxi)) { + dev_dbg(dev, "%s: Bad context! (%llu)\n", __func__, ctxid); + rc = -EINVAL; + goto err1; + } + + rhte = rhte_checkout(ctxi, lli); + if (unlikely(!rhte)) { + dev_dbg(dev, "%s: too many opens for this context\n", __func__); + rc = -EMFILE; /* too many opens */ + goto err1; + } + + rsrc_handle = (rhte - ctxi->rht_start); + + rht_format1(rhte, lli->lun_id[sdev->channel], ctxi->rht_perms, port); + cxlflash_afu_sync(afu, ctxid, rsrc_handle, AFU_LW_SYNC); + + last_lba = gli->max_lba; + pphys->hdr.return_flags = 0; + pphys->last_lba = last_lba; + pphys->rsrc_handle = rsrc_handle; + +out: + if (likely(ctxi)) + put_context(ctxi); + dev_dbg(dev, "%s: returning handle 0x%llx rc=%d llba %lld\n", + __func__, rsrc_handle, rc, last_lba); + return rc; + +err1: + cxlflash_lun_detach(gli); + goto out; +} + +/** + * ioctl_common() - common IOCTL handler for driver + * @sdev: SCSI device associated with LUN. + * @cmd: IOCTL command. + * + * Handles common fencing operations that are valid for multiple ioctls. Always + * allow through ioctls that are cleanup oriented in nature, even when operating + * in a failed/terminating state. + * + * Return: 0 on success, -errno on failure + */ +static int ioctl_common(struct scsi_device *sdev, int cmd) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + int rc = 0; + + if (unlikely(!lli)) { + dev_dbg(dev, "%s: Unknown LUN\n", __func__); + rc = -EINVAL; + goto out; + } + + rc = check_state(cfg); + if (unlikely(rc) && (cfg->state == STATE_FAILTERM)) { + switch (cmd) { + case DK_CXLFLASH_VLUN_RESIZE: + case DK_CXLFLASH_RELEASE: + case DK_CXLFLASH_DETACH: + dev_dbg(dev, "%s: Command override! (%d)\n", + __func__, rc); + rc = 0; + break; + } + } +out: + return rc; +} + +/** + * cxlflash_ioctl() - IOCTL handler for driver + * @sdev: SCSI device associated with LUN. + * @cmd: IOCTL command. + * @arg: Userspace ioctl data structure. + * + * A read/write semaphore is used to implement a 'drain' of currently + * running ioctls. The read semaphore is taken at the beginning of each + * ioctl thread and released upon concluding execution. Additionally the + * semaphore should be released and then reacquired in any ioctl execution + * path which will wait for an event to occur that is outside the scope of + * the ioctl (i.e. an adapter reset). To drain the ioctls currently running, + * a thread simply needs to acquire the write semaphore. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_ioctl(struct scsi_device *sdev, int cmd, void __user *arg) +{ + typedef int (*sioctl) (struct scsi_device *, void *); + + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct afu *afu = cfg->afu; + struct dk_cxlflash_hdr *hdr; + char buf[sizeof(union cxlflash_ioctls)]; + size_t size = 0; + bool known_ioctl = false; + int idx; + int rc = 0; + struct Scsi_Host *shost = sdev->host; + sioctl do_ioctl = NULL; + + static const struct { + size_t size; + sioctl ioctl; + } ioctl_tbl[] = { /* NOTE: order matters here */ + {sizeof(struct dk_cxlflash_attach), (sioctl)cxlflash_disk_attach}, + {sizeof(struct dk_cxlflash_udirect), cxlflash_disk_direct_open}, + {sizeof(struct dk_cxlflash_release), (sioctl)cxlflash_disk_release}, + {sizeof(struct dk_cxlflash_detach), (sioctl)cxlflash_disk_detach}, + {sizeof(struct dk_cxlflash_verify), (sioctl)cxlflash_disk_verify}, + {sizeof(struct dk_cxlflash_recover_afu), (sioctl)cxlflash_afu_recover}, + {sizeof(struct dk_cxlflash_manage_lun), (sioctl)cxlflash_manage_lun}, + {sizeof(struct dk_cxlflash_uvirtual), cxlflash_disk_virtual_open}, + {sizeof(struct dk_cxlflash_resize), (sioctl)cxlflash_vlun_resize}, + {sizeof(struct dk_cxlflash_clone), (sioctl)cxlflash_disk_clone}, + }; + + /* Hold read semaphore so we can drain if needed */ + down_read(&cfg->ioctl_rwsem); + + /* Restrict command set to physical support only for internal LUN */ + if (afu->internal_lun) + switch (cmd) { + case DK_CXLFLASH_RELEASE: + case DK_CXLFLASH_USER_VIRTUAL: + case DK_CXLFLASH_VLUN_RESIZE: + case DK_CXLFLASH_VLUN_CLONE: + dev_dbg(dev, "%s: %s not supported for lun_mode=%d\n", + __func__, decode_ioctl(cmd), afu->internal_lun); + rc = -EINVAL; + goto cxlflash_ioctl_exit; + } + + switch (cmd) { + case DK_CXLFLASH_ATTACH: + case DK_CXLFLASH_USER_DIRECT: + case DK_CXLFLASH_RELEASE: + case DK_CXLFLASH_DETACH: + case DK_CXLFLASH_VERIFY: + case DK_CXLFLASH_RECOVER_AFU: + case DK_CXLFLASH_USER_VIRTUAL: + case DK_CXLFLASH_VLUN_RESIZE: + case DK_CXLFLASH_VLUN_CLONE: + dev_dbg(dev, "%s: %s (%08X) on dev(%d/%d/%d/%llu)\n", + __func__, decode_ioctl(cmd), cmd, shost->host_no, + sdev->channel, sdev->id, sdev->lun); + rc = ioctl_common(sdev, cmd); + if (unlikely(rc)) + goto cxlflash_ioctl_exit; + + /* fall through */ + + case DK_CXLFLASH_MANAGE_LUN: + known_ioctl = true; + idx = _IOC_NR(cmd) - _IOC_NR(DK_CXLFLASH_ATTACH); + size = ioctl_tbl[idx].size; + do_ioctl = ioctl_tbl[idx].ioctl; + + if (likely(do_ioctl)) + break; + + /* fall through */ + default: + rc = -EINVAL; + goto cxlflash_ioctl_exit; + } + + if (unlikely(copy_from_user(&buf, arg, size))) { + dev_err(dev, "%s: copy_from_user() fail! " + "size=%lu cmd=%d (%s) arg=%p\n", + __func__, size, cmd, decode_ioctl(cmd), arg); + rc = -EFAULT; + goto cxlflash_ioctl_exit; + } + + hdr = (struct dk_cxlflash_hdr *)&buf; + if (hdr->version != DK_CXLFLASH_VERSION_0) { + dev_dbg(dev, "%s: Version %u not supported for %s\n", + __func__, hdr->version, decode_ioctl(cmd)); + rc = -EINVAL; + goto cxlflash_ioctl_exit; + } + + if (hdr->rsvd[0] || hdr->rsvd[1] || hdr->rsvd[2] || hdr->return_flags) { + dev_dbg(dev, "%s: Reserved/rflags populated!\n", __func__); + rc = -EINVAL; + goto cxlflash_ioctl_exit; + } + + rc = do_ioctl(sdev, (void *)&buf); + if (likely(!rc)) + if (unlikely(copy_to_user(arg, &buf, size))) { + dev_err(dev, "%s: copy_to_user() fail! " + "size=%lu cmd=%d (%s) arg=%p\n", + __func__, size, cmd, decode_ioctl(cmd), arg); + rc = -EFAULT; + } + + /* fall through to exit */ + +cxlflash_ioctl_exit: + up_read(&cfg->ioctl_rwsem); + if (unlikely(rc && known_ioctl)) + dev_err(dev, "%s: ioctl %s (%08X) on dev(%d/%d/%d/%llu) " + "returned rc %d\n", __func__, + decode_ioctl(cmd), cmd, shost->host_no, + sdev->channel, sdev->id, sdev->lun, rc); + else + dev_dbg(dev, "%s: ioctl %s (%08X) on dev(%d/%d/%d/%llu) " + "returned rc %d\n", __func__, decode_ioctl(cmd), + cmd, shost->host_no, sdev->channel, sdev->id, + sdev->lun, rc); + return rc; +} diff --git a/kernel/drivers/scsi/cxlflash/superpipe.h b/kernel/drivers/scsi/cxlflash/superpipe.h new file mode 100644 index 000000000..bede574bc --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/superpipe.h @@ -0,0 +1,151 @@ +/* + * 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_SUPERPIPE_H +#define _CXLFLASH_SUPERPIPE_H + +extern struct cxlflash_global global; + +/* + * Terminology: use afu (and not adapter) to refer to the HW. + * Adapter is the entire slot and includes PSL out of which + * only the AFU is visible to user space. + */ + +/* Chunk size parms: note sislite minimum chunk size is + 0x10000 LBAs corresponding to a NMASK or 16. +*/ +#define MC_CHUNK_SIZE (1 << MC_RHT_NMASK) /* in LBAs */ + +#define CMD_TIMEOUT 30 /* 30 secs */ +#define CMD_RETRIES 5 /* 5 retries for scsi_execute */ + +#define MAX_SECTOR_UNIT 512 /* max_sector is in 512 byte multiples */ + +#define CHAN2PORT(_x) ((_x) + 1) +#define PORT2CHAN(_x) ((_x) - 1) + +enum lun_mode { + MODE_NONE = 0, + MODE_VIRTUAL, + MODE_PHYSICAL +}; + +/* Global (entire driver, spans adapters) lun_info structure */ +struct glun_info { + u64 max_lba; /* from read cap(16) */ + u32 blk_len; /* from read cap(16) */ + enum lun_mode mode; /* NONE, VIRTUAL, PHYSICAL */ + int users; /* Number of users w/ references to LUN */ + + u8 wwid[16]; + + struct mutex mutex; + + struct blka blka; + struct list_head list; +}; + +/* Local (per-adapter) lun_info structure */ +struct llun_info { + u64 lun_id[CXLFLASH_NUM_FC_PORTS]; /* from REPORT_LUNS */ + u32 lun_index; /* Index in the LUN table */ + u32 host_no; /* host_no from Scsi_host */ + u32 port_sel; /* What port to use for this LUN */ + bool in_table; /* Whether a LUN table entry was created */ + + u8 wwid[16]; /* Keep a duplicate copy here? */ + + struct glun_info *parent; /* Pointer to entry in global LUN structure */ + struct scsi_device *sdev; + struct list_head list; +}; + +struct lun_access { + struct llun_info *lli; + struct scsi_device *sdev; + struct list_head list; +}; + +enum ctx_ctrl { + CTX_CTRL_CLONE = (1 << 1), + CTX_CTRL_ERR = (1 << 2), + CTX_CTRL_ERR_FALLBACK = (1 << 3), + CTX_CTRL_NOPID = (1 << 4), + CTX_CTRL_FILE = (1 << 5) +}; + +#define ENCODE_CTXID(_ctx, _id) (((((u64)_ctx) & 0xFFFFFFFF0ULL) << 28) | _id) +#define DECODE_CTXID(_val) (_val & 0xFFFFFFFF) + +struct ctx_info { + struct sisl_ctrl_map __iomem *ctrl_map; /* initialized at startup */ + struct sisl_rht_entry *rht_start; /* 1 page (req'd for alignment), + alloc/free on attach/detach */ + u32 rht_out; /* Number of checked out RHT entries */ + u32 rht_perms; /* User-defined permissions for RHT entries */ + struct llun_info **rht_lun; /* Mapping of RHT entries to LUNs */ + u8 *rht_needs_ws; /* User-desired write-same function per RHTE */ + + struct cxl_ioctl_start_work work; + u64 ctxid; + int lfd; + pid_t pid; + bool unavail; + bool err_recovery_active; + struct mutex mutex; /* Context protection */ + struct cxl_context *ctx; + struct list_head luns; /* LUNs attached to this context */ + const struct vm_operations_struct *cxl_mmap_vmops; + struct file *file; + struct list_head list; /* Link contexts in error recovery */ +}; + +struct cxlflash_global { + struct mutex mutex; + struct list_head gluns;/* list of glun_info structs */ + struct page *err_page; /* One page of all 0xF for error notification */ +}; + +int cxlflash_vlun_resize(struct scsi_device *, struct dk_cxlflash_resize *); +int _cxlflash_vlun_resize(struct scsi_device *, struct ctx_info *, + struct dk_cxlflash_resize *); + +int cxlflash_disk_release(struct scsi_device *, struct dk_cxlflash_release *); +int _cxlflash_disk_release(struct scsi_device *, struct ctx_info *, + struct dk_cxlflash_release *); + +int cxlflash_disk_clone(struct scsi_device *, struct dk_cxlflash_clone *); + +int cxlflash_disk_virtual_open(struct scsi_device *, void *); + +int cxlflash_lun_attach(struct glun_info *, enum lun_mode, bool); +void cxlflash_lun_detach(struct glun_info *); + +struct ctx_info *get_context(struct cxlflash_cfg *, u64, void *, enum ctx_ctrl); +void put_context(struct ctx_info *); + +struct sisl_rht_entry *get_rhte(struct ctx_info *, res_hndl_t, + struct llun_info *); + +struct sisl_rht_entry *rhte_checkout(struct ctx_info *, struct llun_info *); +void rhte_checkin(struct ctx_info *, struct sisl_rht_entry *); + +void cxlflash_ba_terminate(struct ba_lun *); + +int cxlflash_manage_lun(struct scsi_device *, struct dk_cxlflash_manage_lun *); + +int check_state(struct cxlflash_cfg *); + +#endif /* ifndef _CXLFLASH_SUPERPIPE_H */ diff --git a/kernel/drivers/scsi/cxlflash/vlun.c b/kernel/drivers/scsi/cxlflash/vlun.c new file mode 100644 index 000000000..a53f583e2 --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/vlun.c @@ -0,0 +1,1273 @@ +/* + * 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/syscalls.h> +#include <misc/cxl.h> +#include <asm/unaligned.h> +#include <asm/bitsperlong.h> + +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_host.h> +#include <uapi/scsi/cxlflash_ioctl.h> + +#include "sislite.h" +#include "common.h" +#include "vlun.h" +#include "superpipe.h" + +/** + * marshal_virt_to_resize() - translate uvirtual to resize structure + * @virt: Source structure from which to translate/copy. + * @resize: Destination structure for the translate/copy. + */ +static void marshal_virt_to_resize(struct dk_cxlflash_uvirtual *virt, + struct dk_cxlflash_resize *resize) +{ + resize->hdr = virt->hdr; + resize->context_id = virt->context_id; + resize->rsrc_handle = virt->rsrc_handle; + resize->req_size = virt->lun_size; + resize->last_lba = virt->last_lba; +} + +/** + * marshal_clone_to_rele() - translate clone to release structure + * @clone: Source structure from which to translate/copy. + * @rele: Destination structure for the translate/copy. + */ +static void marshal_clone_to_rele(struct dk_cxlflash_clone *clone, + struct dk_cxlflash_release *release) +{ + release->hdr = clone->hdr; + release->context_id = clone->context_id_dst; +} + +/** + * ba_init() - initializes a block allocator + * @ba_lun: Block allocator to initialize. + * + * Return: 0 on success, -errno on failure + */ +static int ba_init(struct ba_lun *ba_lun) +{ + struct ba_lun_info *bali = NULL; + int lun_size_au = 0, i = 0; + int last_word_underflow = 0; + u64 *lam; + + pr_debug("%s: Initializing LUN: lun_id = %llX, " + "ba_lun->lsize = %lX, ba_lun->au_size = %lX\n", + __func__, ba_lun->lun_id, ba_lun->lsize, ba_lun->au_size); + + /* Calculate bit map size */ + lun_size_au = ba_lun->lsize / ba_lun->au_size; + if (lun_size_au == 0) { + pr_debug("%s: Requested LUN size of 0!\n", __func__); + return -EINVAL; + } + + /* Allocate lun information container */ + bali = kzalloc(sizeof(struct ba_lun_info), GFP_KERNEL); + if (unlikely(!bali)) { + pr_err("%s: Failed to allocate lun_info for lun_id %llX\n", + __func__, ba_lun->lun_id); + return -ENOMEM; + } + + bali->total_aus = lun_size_au; + bali->lun_bmap_size = lun_size_au / BITS_PER_LONG; + + if (lun_size_au % BITS_PER_LONG) + bali->lun_bmap_size++; + + /* Allocate bitmap space */ + bali->lun_alloc_map = kzalloc((bali->lun_bmap_size * sizeof(u64)), + GFP_KERNEL); + if (unlikely(!bali->lun_alloc_map)) { + pr_err("%s: Failed to allocate lun allocation map: " + "lun_id = %llX\n", __func__, ba_lun->lun_id); + kfree(bali); + return -ENOMEM; + } + + /* Initialize the bit map size and set all bits to '1' */ + bali->free_aun_cnt = lun_size_au; + + for (i = 0; i < bali->lun_bmap_size; i++) + bali->lun_alloc_map[i] = 0xFFFFFFFFFFFFFFFFULL; + + /* If the last word not fully utilized, mark extra bits as allocated */ + last_word_underflow = (bali->lun_bmap_size * BITS_PER_LONG); + last_word_underflow -= bali->free_aun_cnt; + if (last_word_underflow > 0) { + lam = &bali->lun_alloc_map[bali->lun_bmap_size - 1]; + for (i = (HIBIT - last_word_underflow + 1); + i < BITS_PER_LONG; + i++) + clear_bit(i, (ulong *)lam); + } + + /* Initialize high elevator index, low/curr already at 0 from kzalloc */ + bali->free_high_idx = bali->lun_bmap_size; + + /* Allocate clone map */ + bali->aun_clone_map = kzalloc((bali->total_aus * sizeof(u8)), + GFP_KERNEL); + if (unlikely(!bali->aun_clone_map)) { + pr_err("%s: Failed to allocate clone map: lun_id = %llX\n", + __func__, ba_lun->lun_id); + kfree(bali->lun_alloc_map); + kfree(bali); + return -ENOMEM; + } + + /* Pass the allocated LUN info as a handle to the user */ + ba_lun->ba_lun_handle = bali; + + pr_debug("%s: Successfully initialized the LUN: " + "lun_id = %llX, bitmap size = %X, free_aun_cnt = %llX\n", + __func__, ba_lun->lun_id, bali->lun_bmap_size, + bali->free_aun_cnt); + return 0; +} + +/** + * find_free_range() - locates a free bit within the block allocator + * @low: First word in block allocator to start search. + * @high: Last word in block allocator to search. + * @bali: LUN information structure owning the block allocator to search. + * @bit_word: Passes back the word in the block allocator owning the free bit. + * + * Return: The bit position within the passed back word, -1 on failure + */ +static int find_free_range(u32 low, + u32 high, + struct ba_lun_info *bali, int *bit_word) +{ + int i; + u64 bit_pos = -1; + ulong *lam, num_bits; + + for (i = low; i < high; i++) + if (bali->lun_alloc_map[i] != 0) { + lam = (ulong *)&bali->lun_alloc_map[i]; + num_bits = (sizeof(*lam) * BITS_PER_BYTE); + bit_pos = find_first_bit(lam, num_bits); + + pr_devel("%s: Found free bit %llX in LUN " + "map entry %llX at bitmap index = %X\n", + __func__, bit_pos, bali->lun_alloc_map[i], + i); + + *bit_word = i; + bali->free_aun_cnt--; + clear_bit(bit_pos, lam); + break; + } + + return bit_pos; +} + +/** + * ba_alloc() - allocates a block from the block allocator + * @ba_lun: Block allocator from which to allocate a block. + * + * Return: The allocated block, -1 on failure + */ +static u64 ba_alloc(struct ba_lun *ba_lun) +{ + u64 bit_pos = -1; + int bit_word = 0; + struct ba_lun_info *bali = NULL; + + bali = ba_lun->ba_lun_handle; + + pr_debug("%s: Received block allocation request: " + "lun_id = %llX, free_aun_cnt = %llX\n", + __func__, ba_lun->lun_id, bali->free_aun_cnt); + + if (bali->free_aun_cnt == 0) { + pr_debug("%s: No space left on LUN: lun_id = %llX\n", + __func__, ba_lun->lun_id); + return -1ULL; + } + + /* Search to find a free entry, curr->high then low->curr */ + bit_pos = find_free_range(bali->free_curr_idx, + bali->free_high_idx, bali, &bit_word); + if (bit_pos == -1) { + bit_pos = find_free_range(bali->free_low_idx, + bali->free_curr_idx, + bali, &bit_word); + if (bit_pos == -1) { + pr_debug("%s: Could not find an allocation unit on LUN:" + " lun_id = %llX\n", __func__, ba_lun->lun_id); + return -1ULL; + } + } + + /* Update the free_curr_idx */ + if (bit_pos == HIBIT) + bali->free_curr_idx = bit_word + 1; + else + bali->free_curr_idx = bit_word; + + pr_debug("%s: Allocating AU number %llX, on lun_id %llX, " + "free_aun_cnt = %llX\n", __func__, + ((bit_word * BITS_PER_LONG) + bit_pos), ba_lun->lun_id, + bali->free_aun_cnt); + + return (u64) ((bit_word * BITS_PER_LONG) + bit_pos); +} + +/** + * validate_alloc() - validates the specified block has been allocated + * @ba_lun_info: LUN info owning the block allocator. + * @aun: Block to validate. + * + * Return: 0 on success, -1 on failure + */ +static int validate_alloc(struct ba_lun_info *bali, u64 aun) +{ + int idx = 0, bit_pos = 0; + + idx = aun / BITS_PER_LONG; + bit_pos = aun % BITS_PER_LONG; + + if (test_bit(bit_pos, (ulong *)&bali->lun_alloc_map[idx])) + return -1; + + return 0; +} + +/** + * ba_free() - frees a block from the block allocator + * @ba_lun: Block allocator from which to allocate a block. + * @to_free: Block to free. + * + * Return: 0 on success, -1 on failure + */ +static int ba_free(struct ba_lun *ba_lun, u64 to_free) +{ + int idx = 0, bit_pos = 0; + struct ba_lun_info *bali = NULL; + + bali = ba_lun->ba_lun_handle; + + if (validate_alloc(bali, to_free)) { + pr_debug("%s: The AUN %llX is not allocated on lun_id %llX\n", + __func__, to_free, ba_lun->lun_id); + return -1; + } + + pr_debug("%s: Received a request to free AU %llX on lun_id %llX, " + "free_aun_cnt = %llX\n", __func__, to_free, ba_lun->lun_id, + bali->free_aun_cnt); + + if (bali->aun_clone_map[to_free] > 0) { + pr_debug("%s: AUN %llX on lun_id %llX has been cloned. Clone " + "count = %X\n", __func__, to_free, ba_lun->lun_id, + bali->aun_clone_map[to_free]); + bali->aun_clone_map[to_free]--; + return 0; + } + + idx = to_free / BITS_PER_LONG; + bit_pos = to_free % BITS_PER_LONG; + + set_bit(bit_pos, (ulong *)&bali->lun_alloc_map[idx]); + bali->free_aun_cnt++; + + if (idx < bali->free_low_idx) + bali->free_low_idx = idx; + else if (idx > bali->free_high_idx) + bali->free_high_idx = idx; + + pr_debug("%s: Successfully freed AU at bit_pos %X, bit map index %X on " + "lun_id %llX, free_aun_cnt = %llX\n", __func__, bit_pos, idx, + ba_lun->lun_id, bali->free_aun_cnt); + + return 0; +} + +/** + * ba_clone() - Clone a chunk of the block allocation table + * @ba_lun: Block allocator from which to allocate a block. + * @to_free: Block to free. + * + * Return: 0 on success, -1 on failure + */ +static int ba_clone(struct ba_lun *ba_lun, u64 to_clone) +{ + struct ba_lun_info *bali = ba_lun->ba_lun_handle; + + if (validate_alloc(bali, to_clone)) { + pr_debug("%s: AUN %llX is not allocated on lun_id %llX\n", + __func__, to_clone, ba_lun->lun_id); + return -1; + } + + pr_debug("%s: Received a request to clone AUN %llX on lun_id %llX\n", + __func__, to_clone, ba_lun->lun_id); + + if (bali->aun_clone_map[to_clone] == MAX_AUN_CLONE_CNT) { + pr_debug("%s: AUN %llX on lun_id %llX hit max clones already\n", + __func__, to_clone, ba_lun->lun_id); + return -1; + } + + bali->aun_clone_map[to_clone]++; + + return 0; +} + +/** + * ba_space() - returns the amount of free space left in the block allocator + * @ba_lun: Block allocator. + * + * Return: Amount of free space in block allocator + */ +static u64 ba_space(struct ba_lun *ba_lun) +{ + struct ba_lun_info *bali = ba_lun->ba_lun_handle; + + return bali->free_aun_cnt; +} + +/** + * cxlflash_ba_terminate() - frees resources associated with the block allocator + * @ba_lun: Block allocator. + * + * Safe to call in a partially allocated state. + */ +void cxlflash_ba_terminate(struct ba_lun *ba_lun) +{ + struct ba_lun_info *bali = ba_lun->ba_lun_handle; + + if (bali) { + kfree(bali->aun_clone_map); + kfree(bali->lun_alloc_map); + kfree(bali); + ba_lun->ba_lun_handle = NULL; + } +} + +/** + * init_vlun() - initializes a LUN for virtual use + * @lun_info: LUN information structure that owns the block allocator. + * + * Return: 0 on success, -errno on failure + */ +static int init_vlun(struct llun_info *lli) +{ + int rc = 0; + struct glun_info *gli = lli->parent; + struct blka *blka = &gli->blka; + + memset(blka, 0, sizeof(*blka)); + mutex_init(&blka->mutex); + + /* LUN IDs are unique per port, save the index instead */ + blka->ba_lun.lun_id = lli->lun_index; + blka->ba_lun.lsize = gli->max_lba + 1; + blka->ba_lun.lba_size = gli->blk_len; + + blka->ba_lun.au_size = MC_CHUNK_SIZE; + blka->nchunk = blka->ba_lun.lsize / MC_CHUNK_SIZE; + + rc = ba_init(&blka->ba_lun); + if (unlikely(rc)) + pr_debug("%s: cannot init block_alloc, rc=%d\n", __func__, rc); + + pr_debug("%s: returning rc=%d lli=%p\n", __func__, rc, lli); + return rc; +} + +/** + * write_same16() - sends a SCSI WRITE_SAME16 (0) command to specified LUN + * @sdev: SCSI device associated with LUN. + * @lba: Logical block address to start write same. + * @nblks: Number of logical blocks to write same. + * + * The SCSI WRITE_SAME16 can take quite a while to complete. Should an EEH occur + * while in scsi_execute(), the EEH handler will attempt to recover. As part of + * the recovery, the handler drains all currently running ioctls, waiting until + * they have completed before proceeding with a reset. As this routine is used + * on the ioctl path, this can create a condition where the EEH handler becomes + * stuck, infinitely waiting for this ioctl thread. To avoid this behavior, + * temporarily unmark this thread as an ioctl thread by releasing the ioctl read + * semaphore. This will allow the EEH handler to proceed with a recovery while + * this thread is still running. Once the scsi_execute() returns, reacquire the + * ioctl read semaphore and check the adapter state in case it changed while + * inside of scsi_execute(). The state check will wait if the adapter is still + * being recovered or return a failure if the recovery failed. In the event that + * the adapter reset failed, simply return the failure as the ioctl would be + * unable to continue. + * + * Note that the above puts a requirement on this routine to only be called on + * an ioctl thread. + * + * Return: 0 on success, -errno on failure + */ +static int write_same16(struct scsi_device *sdev, + u64 lba, + u32 nblks) +{ + u8 *cmd_buf = NULL; + u8 *scsi_cmd = NULL; + u8 *sense_buf = NULL; + int rc = 0; + int result = 0; + int ws_limit = SISLITE_MAX_WS_BLOCKS; + u64 offset = lba; + int left = nblks; + u32 to = sdev->request_queue->rq_timeout; + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + + cmd_buf = kzalloc(CMD_BUFSIZE, GFP_KERNEL); + scsi_cmd = kzalloc(MAX_COMMAND_SIZE, GFP_KERNEL); + sense_buf = kzalloc(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL); + if (unlikely(!cmd_buf || !scsi_cmd || !sense_buf)) { + rc = -ENOMEM; + goto out; + } + + while (left > 0) { + + scsi_cmd[0] = WRITE_SAME_16; + put_unaligned_be64(offset, &scsi_cmd[2]); + put_unaligned_be32(ws_limit < left ? ws_limit : left, + &scsi_cmd[10]); + + /* Drop the ioctl read semahpore across lengthy call */ + up_read(&cfg->ioctl_rwsem); + result = scsi_execute(sdev, scsi_cmd, DMA_TO_DEVICE, cmd_buf, + CMD_BUFSIZE, sense_buf, to, CMD_RETRIES, + 0, NULL); + down_read(&cfg->ioctl_rwsem); + rc = check_state(cfg); + if (rc) { + dev_err(dev, "%s: Failed state! result=0x08%X\n", + __func__, result); + rc = -ENODEV; + goto out; + } + + if (result) { + dev_err_ratelimited(dev, "%s: command failed for " + "offset %lld result=0x%x\n", + __func__, offset, result); + rc = -EIO; + goto out; + } + left -= ws_limit; + offset += ws_limit; + } + +out: + kfree(cmd_buf); + kfree(scsi_cmd); + kfree(sense_buf); + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * grow_lxt() - expands the translation table associated with the specified RHTE + * @afu: AFU associated with the host. + * @sdev: SCSI device associated with LUN. + * @ctxid: Context ID of context owning the RHTE. + * @rhndl: Resource handle associated with the RHTE. + * @rhte: Resource handle entry (RHTE). + * @new_size: Number of translation entries associated with RHTE. + * + * By design, this routine employs a 'best attempt' allocation and will + * truncate the requested size down if there is not sufficient space in + * the block allocator to satisfy the request but there does exist some + * amount of space. The user is made aware of this by returning the size + * allocated. + * + * Return: 0 on success, -errno on failure + */ +static int grow_lxt(struct afu *afu, + struct scsi_device *sdev, + ctx_hndl_t ctxid, + res_hndl_t rhndl, + struct sisl_rht_entry *rhte, + u64 *new_size) +{ + struct sisl_lxt_entry *lxt = NULL, *lxt_old = NULL; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct blka *blka = &gli->blka; + u32 av_size; + u32 ngrps, ngrps_old; + u64 aun; /* chunk# allocated by block allocator */ + u64 delta = *new_size - rhte->lxt_cnt; + u64 my_new_size; + int i, rc = 0; + + /* + * Check what is available in the block allocator before re-allocating + * LXT array. This is done up front under the mutex which must not be + * released until after allocation is complete. + */ + mutex_lock(&blka->mutex); + av_size = ba_space(&blka->ba_lun); + if (unlikely(av_size <= 0)) { + pr_debug("%s: ba_space error: av_size %d\n", __func__, av_size); + mutex_unlock(&blka->mutex); + rc = -ENOSPC; + goto out; + } + + if (av_size < delta) + delta = av_size; + + lxt_old = rhte->lxt_start; + ngrps_old = LXT_NUM_GROUPS(rhte->lxt_cnt); + ngrps = LXT_NUM_GROUPS(rhte->lxt_cnt + delta); + + if (ngrps != ngrps_old) { + /* reallocate to fit new size */ + lxt = kzalloc((sizeof(*lxt) * LXT_GROUP_SIZE * ngrps), + GFP_KERNEL); + if (unlikely(!lxt)) { + mutex_unlock(&blka->mutex); + rc = -ENOMEM; + goto out; + } + + /* copy over all old entries */ + memcpy(lxt, lxt_old, (sizeof(*lxt) * rhte->lxt_cnt)); + } else + lxt = lxt_old; + + /* nothing can fail from now on */ + my_new_size = rhte->lxt_cnt + delta; + + /* add new entries to the end */ + for (i = rhte->lxt_cnt; i < my_new_size; i++) { + /* + * Due to the earlier check of available space, ba_alloc + * cannot fail here. If it did due to internal error, + * leave a rlba_base of -1u which will likely be a + * invalid LUN (too large). + */ + aun = ba_alloc(&blka->ba_lun); + if ((aun == -1ULL) || (aun >= blka->nchunk)) + pr_debug("%s: ba_alloc error: allocated chunk# %llX, " + "max %llX\n", __func__, aun, blka->nchunk - 1); + + /* select both ports, use r/w perms from RHT */ + lxt[i].rlba_base = ((aun << MC_CHUNK_SHIFT) | + (lli->lun_index << LXT_LUNIDX_SHIFT) | + (RHT_PERM_RW << LXT_PERM_SHIFT | + lli->port_sel)); + } + + mutex_unlock(&blka->mutex); + + /* + * The following sequence is prescribed in the SISlite spec + * for syncing up with the AFU when adding LXT entries. + */ + dma_wmb(); /* Make LXT updates are visible */ + + rhte->lxt_start = lxt; + dma_wmb(); /* Make RHT entry's LXT table update visible */ + + rhte->lxt_cnt = my_new_size; + dma_wmb(); /* Make RHT entry's LXT table size update visible */ + + cxlflash_afu_sync(afu, ctxid, rhndl, AFU_LW_SYNC); + + /* free old lxt if reallocated */ + if (lxt != lxt_old) + kfree(lxt_old); + *new_size = my_new_size; +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * shrink_lxt() - reduces translation table associated with the specified RHTE + * @afu: AFU associated with the host. + * @sdev: SCSI device associated with LUN. + * @rhndl: Resource handle associated with the RHTE. + * @rhte: Resource handle entry (RHTE). + * @ctxi: Context owning resources. + * @new_size: Number of translation entries associated with RHTE. + * + * Return: 0 on success, -errno on failure + */ +static int shrink_lxt(struct afu *afu, + struct scsi_device *sdev, + res_hndl_t rhndl, + struct sisl_rht_entry *rhte, + struct ctx_info *ctxi, + u64 *new_size) +{ + struct sisl_lxt_entry *lxt, *lxt_old; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct blka *blka = &gli->blka; + ctx_hndl_t ctxid = DECODE_CTXID(ctxi->ctxid); + bool needs_ws = ctxi->rht_needs_ws[rhndl]; + bool needs_sync = !ctxi->err_recovery_active; + u32 ngrps, ngrps_old; + u64 aun; /* chunk# allocated by block allocator */ + u64 delta = rhte->lxt_cnt - *new_size; + u64 my_new_size; + int i, rc = 0; + + lxt_old = rhte->lxt_start; + ngrps_old = LXT_NUM_GROUPS(rhte->lxt_cnt); + ngrps = LXT_NUM_GROUPS(rhte->lxt_cnt - delta); + + if (ngrps != ngrps_old) { + /* Reallocate to fit new size unless new size is 0 */ + if (ngrps) { + lxt = kzalloc((sizeof(*lxt) * LXT_GROUP_SIZE * ngrps), + GFP_KERNEL); + if (unlikely(!lxt)) { + rc = -ENOMEM; + goto out; + } + + /* Copy over old entries that will remain */ + memcpy(lxt, lxt_old, + (sizeof(*lxt) * (rhte->lxt_cnt - delta))); + } else + lxt = NULL; + } else + lxt = lxt_old; + + /* Nothing can fail from now on */ + my_new_size = rhte->lxt_cnt - delta; + + /* + * The following sequence is prescribed in the SISlite spec + * for syncing up with the AFU when removing LXT entries. + */ + rhte->lxt_cnt = my_new_size; + dma_wmb(); /* Make RHT entry's LXT table size update visible */ + + rhte->lxt_start = lxt; + dma_wmb(); /* Make RHT entry's LXT table update visible */ + + if (needs_sync) + cxlflash_afu_sync(afu, ctxid, rhndl, AFU_HW_SYNC); + + if (needs_ws) { + /* + * Mark the context as unavailable, so that we can release + * the mutex safely. + */ + ctxi->unavail = true; + mutex_unlock(&ctxi->mutex); + } + + /* Free LBAs allocated to freed chunks */ + mutex_lock(&blka->mutex); + for (i = delta - 1; i >= 0; i--) { + /* Mask the higher 48 bits before shifting, even though + * it is a noop + */ + aun = (lxt_old[my_new_size + i].rlba_base & SISL_ASTATUS_MASK); + aun = (aun >> MC_CHUNK_SHIFT); + if (needs_ws) + write_same16(sdev, aun, MC_CHUNK_SIZE); + ba_free(&blka->ba_lun, aun); + } + mutex_unlock(&blka->mutex); + + if (needs_ws) { + /* Make the context visible again */ + mutex_lock(&ctxi->mutex); + ctxi->unavail = false; + } + + /* Free old lxt if reallocated */ + if (lxt != lxt_old) + kfree(lxt_old); + *new_size = my_new_size; +out: + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * _cxlflash_vlun_resize() - changes the size of a virtual LUN + * @sdev: SCSI device associated with LUN owning virtual LUN. + * @ctxi: Context owning resources. + * @resize: Resize ioctl data structure. + * + * On successful return, the user is informed of the new size (in blocks) + * of the virtual LUN in last LBA format. When the size of the virtual + * LUN is zero, the last LBA is reflected as -1. See comment in the + * prologue for _cxlflash_disk_release() regarding AFU syncs and contexts + * on the error recovery list. + * + * Return: 0 on success, -errno on failure + */ +int _cxlflash_vlun_resize(struct scsi_device *sdev, + struct ctx_info *ctxi, + struct dk_cxlflash_resize *resize) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct afu *afu = cfg->afu; + bool put_ctx = false; + + res_hndl_t rhndl = resize->rsrc_handle; + u64 new_size; + u64 nsectors; + u64 ctxid = DECODE_CTXID(resize->context_id), + rctxid = resize->context_id; + + struct sisl_rht_entry *rhte; + + int rc = 0; + + /* + * The requested size (req_size) is always assumed to be in 4k blocks, + * so we have to convert it here from 4k to chunk size. + */ + nsectors = (resize->req_size * CXLFLASH_BLOCK_SIZE) / gli->blk_len; + new_size = DIV_ROUND_UP(nsectors, MC_CHUNK_SIZE); + + pr_debug("%s: ctxid=%llu rhndl=0x%llx, req_size=0x%llx," + "new_size=%llx\n", __func__, ctxid, resize->rsrc_handle, + resize->req_size, new_size); + + if (unlikely(gli->mode != MODE_VIRTUAL)) { + pr_debug("%s: LUN mode does not support resize! (%d)\n", + __func__, gli->mode); + rc = -EINVAL; + goto out; + + } + + if (!ctxi) { + ctxi = get_context(cfg, rctxid, lli, CTX_CTRL_ERR_FALLBACK); + if (unlikely(!ctxi)) { + pr_debug("%s: Bad context! (%llu)\n", __func__, ctxid); + rc = -EINVAL; + goto out; + } + + put_ctx = true; + } + + rhte = get_rhte(ctxi, rhndl, lli); + if (unlikely(!rhte)) { + pr_debug("%s: Bad resource handle! (%u)\n", __func__, rhndl); + rc = -EINVAL; + goto out; + } + + if (new_size > rhte->lxt_cnt) + rc = grow_lxt(afu, sdev, ctxid, rhndl, rhte, &new_size); + else if (new_size < rhte->lxt_cnt) + rc = shrink_lxt(afu, sdev, rhndl, rhte, ctxi, &new_size); + + resize->hdr.return_flags = 0; + resize->last_lba = (new_size * MC_CHUNK_SIZE * gli->blk_len); + resize->last_lba /= CXLFLASH_BLOCK_SIZE; + resize->last_lba--; + +out: + if (put_ctx) + put_context(ctxi); + pr_debug("%s: resized to %lld returning rc=%d\n", + __func__, resize->last_lba, rc); + return rc; +} + +int cxlflash_vlun_resize(struct scsi_device *sdev, + struct dk_cxlflash_resize *resize) +{ + return _cxlflash_vlun_resize(sdev, NULL, resize); +} + +/** + * cxlflash_restore_luntable() - Restore LUN table to prior state + * @cfg: Internal structure associated with the host. + */ +void cxlflash_restore_luntable(struct cxlflash_cfg *cfg) +{ + struct llun_info *lli, *temp; + u32 chan; + u32 lind; + struct afu *afu = cfg->afu; + struct sisl_global_map __iomem *agm = &afu->afu_map->global; + + mutex_lock(&global.mutex); + + list_for_each_entry_safe(lli, temp, &cfg->lluns, list) { + if (!lli->in_table) + continue; + + lind = lli->lun_index; + + if (lli->port_sel == BOTH_PORTS) { + writeq_be(lli->lun_id[0], &agm->fc_port[0][lind]); + writeq_be(lli->lun_id[1], &agm->fc_port[1][lind]); + pr_debug("%s: Virtual LUN on slot %d id0=%llx, " + "id1=%llx\n", __func__, lind, + lli->lun_id[0], lli->lun_id[1]); + } else { + chan = PORT2CHAN(lli->port_sel); + writeq_be(lli->lun_id[chan], &agm->fc_port[chan][lind]); + pr_debug("%s: Virtual LUN on slot %d chan=%d, " + "id=%llx\n", __func__, lind, chan, + lli->lun_id[chan]); + } + } + + mutex_unlock(&global.mutex); +} + +/** + * init_luntable() - write an entry in the LUN table + * @cfg: Internal structure associated with the host. + * @lli: Per adapter LUN information structure. + * + * On successful return, a LUN table entry is created. + * At the top for LUNs visible on both ports. + * At the bottom for LUNs visible only on one port. + * + * Return: 0 on success, -errno on failure + */ +static int init_luntable(struct cxlflash_cfg *cfg, struct llun_info *lli) +{ + u32 chan; + u32 lind; + int rc = 0; + struct afu *afu = cfg->afu; + struct sisl_global_map __iomem *agm = &afu->afu_map->global; + + mutex_lock(&global.mutex); + + if (lli->in_table) + goto out; + + if (lli->port_sel == BOTH_PORTS) { + /* + * If this LUN is visible from both ports, we will put + * it in the top half of the LUN table. + */ + if ((cfg->promote_lun_index == cfg->last_lun_index[0]) || + (cfg->promote_lun_index == cfg->last_lun_index[1])) { + rc = -ENOSPC; + goto out; + } + + lind = lli->lun_index = cfg->promote_lun_index; + writeq_be(lli->lun_id[0], &agm->fc_port[0][lind]); + writeq_be(lli->lun_id[1], &agm->fc_port[1][lind]); + cfg->promote_lun_index++; + pr_debug("%s: Virtual LUN on slot %d id0=%llx, id1=%llx\n", + __func__, lind, lli->lun_id[0], lli->lun_id[1]); + } else { + /* + * If this LUN is visible only from one port, we will put + * it in the bottom half of the LUN table. + */ + chan = PORT2CHAN(lli->port_sel); + if (cfg->promote_lun_index == cfg->last_lun_index[chan]) { + rc = -ENOSPC; + goto out; + } + + lind = lli->lun_index = cfg->last_lun_index[chan]; + writeq_be(lli->lun_id[chan], &agm->fc_port[chan][lind]); + cfg->last_lun_index[chan]--; + pr_debug("%s: Virtual LUN on slot %d chan=%d, id=%llx\n", + __func__, lind, chan, lli->lun_id[chan]); + } + + lli->in_table = true; +out: + mutex_unlock(&global.mutex); + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; +} + +/** + * cxlflash_disk_virtual_open() - open a virtual disk of specified size + * @sdev: SCSI device associated with LUN owning virtual LUN. + * @arg: UVirtual ioctl data structure. + * + * On successful return, the user is informed of the resource handle + * to be used to identify the virtual LUN and the size (in blocks) of + * the virtual LUN in last LBA format. When the size of the virtual LUN + * is zero, the last LBA is reflected as -1. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_disk_virtual_open(struct scsi_device *sdev, void *arg) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct device *dev = &cfg->dev->dev; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + + struct dk_cxlflash_uvirtual *virt = (struct dk_cxlflash_uvirtual *)arg; + struct dk_cxlflash_resize resize; + + u64 ctxid = DECODE_CTXID(virt->context_id), + rctxid = virt->context_id; + u64 lun_size = virt->lun_size; + u64 last_lba = 0; + u64 rsrc_handle = -1; + + int rc = 0; + + struct ctx_info *ctxi = NULL; + struct sisl_rht_entry *rhte = NULL; + + pr_debug("%s: ctxid=%llu ls=0x%llx\n", __func__, ctxid, lun_size); + + /* Setup the LUNs block allocator on first call */ + mutex_lock(&gli->mutex); + if (gli->mode == MODE_NONE) { + rc = init_vlun(lli); + if (rc) { + dev_err(dev, "%s: call to init_vlun failed rc=%d!\n", + __func__, rc); + rc = -ENOMEM; + goto err0; + } + } + + rc = cxlflash_lun_attach(gli, MODE_VIRTUAL, true); + if (unlikely(rc)) { + dev_err(dev, "%s: Failed to attach to LUN! (VIRTUAL)\n", + __func__); + goto err0; + } + mutex_unlock(&gli->mutex); + + rc = init_luntable(cfg, lli); + if (rc) { + dev_err(dev, "%s: call to init_luntable failed rc=%d!\n", + __func__, rc); + goto err1; + } + + ctxi = get_context(cfg, rctxid, lli, 0); + if (unlikely(!ctxi)) { + dev_err(dev, "%s: Bad context! (%llu)\n", __func__, ctxid); + rc = -EINVAL; + goto err1; + } + + rhte = rhte_checkout(ctxi, lli); + if (unlikely(!rhte)) { + dev_err(dev, "%s: too many opens for this context\n", __func__); + rc = -EMFILE; /* too many opens */ + goto err1; + } + + rsrc_handle = (rhte - ctxi->rht_start); + + /* Populate RHT format 0 */ + rhte->nmask = MC_RHT_NMASK; + rhte->fp = SISL_RHT_FP(0U, ctxi->rht_perms); + + /* Resize even if requested size is 0 */ + marshal_virt_to_resize(virt, &resize); + resize.rsrc_handle = rsrc_handle; + rc = _cxlflash_vlun_resize(sdev, ctxi, &resize); + if (rc) { + dev_err(dev, "%s: resize failed rc %d\n", __func__, rc); + goto err2; + } + last_lba = resize.last_lba; + + if (virt->hdr.flags & DK_CXLFLASH_UVIRTUAL_NEED_WRITE_SAME) + ctxi->rht_needs_ws[rsrc_handle] = true; + + virt->hdr.return_flags = 0; + virt->last_lba = last_lba; + virt->rsrc_handle = rsrc_handle; + +out: + if (likely(ctxi)) + put_context(ctxi); + pr_debug("%s: returning handle 0x%llx rc=%d llba %lld\n", + __func__, rsrc_handle, rc, last_lba); + return rc; + +err2: + rhte_checkin(ctxi, rhte); +err1: + cxlflash_lun_detach(gli); + goto out; +err0: + /* Special common cleanup prior to successful LUN attach */ + cxlflash_ba_terminate(&gli->blka.ba_lun); + mutex_unlock(&gli->mutex); + goto out; +} + +/** + * clone_lxt() - copies translation tables from source to destination RHTE + * @afu: AFU associated with the host. + * @blka: Block allocator associated with LUN. + * @ctxid: Context ID of context owning the RHTE. + * @rhndl: Resource handle associated with the RHTE. + * @rhte: Destination resource handle entry (RHTE). + * @rhte_src: Source resource handle entry (RHTE). + * + * Return: 0 on success, -errno on failure + */ +static int clone_lxt(struct afu *afu, + struct blka *blka, + ctx_hndl_t ctxid, + res_hndl_t rhndl, + struct sisl_rht_entry *rhte, + struct sisl_rht_entry *rhte_src) +{ + struct sisl_lxt_entry *lxt; + u32 ngrps; + u64 aun; /* chunk# allocated by block allocator */ + int i, j; + + ngrps = LXT_NUM_GROUPS(rhte_src->lxt_cnt); + + if (ngrps) { + /* allocate new LXTs for clone */ + lxt = kzalloc((sizeof(*lxt) * LXT_GROUP_SIZE * ngrps), + GFP_KERNEL); + if (unlikely(!lxt)) + return -ENOMEM; + + /* copy over */ + memcpy(lxt, rhte_src->lxt_start, + (sizeof(*lxt) * rhte_src->lxt_cnt)); + + /* clone the LBAs in block allocator via ref_cnt */ + mutex_lock(&blka->mutex); + for (i = 0; i < rhte_src->lxt_cnt; i++) { + aun = (lxt[i].rlba_base >> MC_CHUNK_SHIFT); + if (ba_clone(&blka->ba_lun, aun) == -1ULL) { + /* free the clones already made */ + for (j = 0; j < i; j++) { + aun = (lxt[j].rlba_base >> + MC_CHUNK_SHIFT); + ba_free(&blka->ba_lun, aun); + } + + mutex_unlock(&blka->mutex); + kfree(lxt); + return -EIO; + } + } + mutex_unlock(&blka->mutex); + } else { + lxt = NULL; + } + + /* + * The following sequence is prescribed in the SISlite spec + * for syncing up with the AFU when adding LXT entries. + */ + dma_wmb(); /* Make LXT updates are visible */ + + rhte->lxt_start = lxt; + dma_wmb(); /* Make RHT entry's LXT table update visible */ + + rhte->lxt_cnt = rhte_src->lxt_cnt; + dma_wmb(); /* Make RHT entry's LXT table size update visible */ + + cxlflash_afu_sync(afu, ctxid, rhndl, AFU_LW_SYNC); + + pr_debug("%s: returning\n", __func__); + return 0; +} + +/** + * cxlflash_disk_clone() - clone a context by making snapshot of another + * @sdev: SCSI device associated with LUN owning virtual LUN. + * @clone: Clone ioctl data structure. + * + * This routine effectively performs cxlflash_disk_open operation for each + * in-use virtual resource in the source context. Note that the destination + * context must be in pristine state and cannot have any resource handles + * open at the time of the clone. + * + * Return: 0 on success, -errno on failure + */ +int cxlflash_disk_clone(struct scsi_device *sdev, + struct dk_cxlflash_clone *clone) +{ + struct cxlflash_cfg *cfg = (struct cxlflash_cfg *)sdev->host->hostdata; + struct llun_info *lli = sdev->hostdata; + struct glun_info *gli = lli->parent; + struct blka *blka = &gli->blka; + struct afu *afu = cfg->afu; + struct dk_cxlflash_release release = { { 0 }, 0 }; + + struct ctx_info *ctxi_src = NULL, + *ctxi_dst = NULL; + struct lun_access *lun_access_src, *lun_access_dst; + u32 perms; + u64 ctxid_src = DECODE_CTXID(clone->context_id_src), + ctxid_dst = DECODE_CTXID(clone->context_id_dst), + rctxid_src = clone->context_id_src, + rctxid_dst = clone->context_id_dst; + int adap_fd_src = clone->adap_fd_src; + int i, j; + int rc = 0; + bool found; + LIST_HEAD(sidecar); + + pr_debug("%s: ctxid_src=%llu ctxid_dst=%llu adap_fd_src=%d\n", + __func__, ctxid_src, ctxid_dst, adap_fd_src); + + /* Do not clone yourself */ + if (unlikely(rctxid_src == rctxid_dst)) { + rc = -EINVAL; + goto out; + } + + if (unlikely(gli->mode != MODE_VIRTUAL)) { + rc = -EINVAL; + pr_debug("%s: Clone not supported on physical LUNs! (%d)\n", + __func__, gli->mode); + goto out; + } + + ctxi_src = get_context(cfg, rctxid_src, lli, CTX_CTRL_CLONE); + ctxi_dst = get_context(cfg, rctxid_dst, lli, 0); + if (unlikely(!ctxi_src || !ctxi_dst)) { + pr_debug("%s: Bad context! (%llu,%llu)\n", __func__, + ctxid_src, ctxid_dst); + rc = -EINVAL; + goto out; + } + + if (unlikely(adap_fd_src != ctxi_src->lfd)) { + pr_debug("%s: Invalid source adapter fd! (%d)\n", + __func__, adap_fd_src); + rc = -EINVAL; + goto out; + } + + /* Verify there is no open resource handle in the destination context */ + for (i = 0; i < MAX_RHT_PER_CONTEXT; i++) + if (ctxi_dst->rht_start[i].nmask != 0) { + rc = -EINVAL; + goto out; + } + + /* Clone LUN access list */ + list_for_each_entry(lun_access_src, &ctxi_src->luns, list) { + found = false; + list_for_each_entry(lun_access_dst, &ctxi_dst->luns, list) + if (lun_access_dst->sdev == lun_access_src->sdev) { + found = true; + break; + } + + if (!found) { + lun_access_dst = kzalloc(sizeof(*lun_access_dst), + GFP_KERNEL); + if (unlikely(!lun_access_dst)) { + pr_err("%s: Unable to allocate lun_access!\n", + __func__); + rc = -ENOMEM; + goto out; + } + + *lun_access_dst = *lun_access_src; + list_add(&lun_access_dst->list, &sidecar); + } + } + + if (unlikely(!ctxi_src->rht_out)) { + pr_debug("%s: Nothing to clone!\n", __func__); + goto out_success; + } + + /* User specified permission on attach */ + perms = ctxi_dst->rht_perms; + + /* + * Copy over checked-out RHT (and their associated LXT) entries by + * hand, stopping after we've copied all outstanding entries and + * cleaning up if the clone fails. + * + * Note: This loop is equivalent to performing cxlflash_disk_open and + * cxlflash_vlun_resize. As such, LUN accounting needs to be taken into + * account by attaching after each successful RHT entry clone. In the + * event that a clone failure is experienced, the LUN detach is handled + * via the cleanup performed by _cxlflash_disk_release. + */ + for (i = 0; i < MAX_RHT_PER_CONTEXT; i++) { + if (ctxi_src->rht_out == ctxi_dst->rht_out) + break; + if (ctxi_src->rht_start[i].nmask == 0) + continue; + + /* Consume a destination RHT entry */ + ctxi_dst->rht_out++; + ctxi_dst->rht_start[i].nmask = ctxi_src->rht_start[i].nmask; + ctxi_dst->rht_start[i].fp = + SISL_RHT_FP_CLONE(ctxi_src->rht_start[i].fp, perms); + ctxi_dst->rht_lun[i] = ctxi_src->rht_lun[i]; + + rc = clone_lxt(afu, blka, ctxid_dst, i, + &ctxi_dst->rht_start[i], + &ctxi_src->rht_start[i]); + if (rc) { + marshal_clone_to_rele(clone, &release); + for (j = 0; j < i; j++) { + release.rsrc_handle = j; + _cxlflash_disk_release(sdev, ctxi_dst, + &release); + } + + /* Put back the one we failed on */ + rhte_checkin(ctxi_dst, &ctxi_dst->rht_start[i]); + goto err; + } + + cxlflash_lun_attach(gli, gli->mode, false); + } + +out_success: + list_splice(&sidecar, &ctxi_dst->luns); + sys_close(adap_fd_src); + + /* fall through */ +out: + if (ctxi_src) + put_context(ctxi_src); + if (ctxi_dst) + put_context(ctxi_dst); + pr_debug("%s: returning rc=%d\n", __func__, rc); + return rc; + +err: + list_for_each_entry_safe(lun_access_src, lun_access_dst, &sidecar, list) + kfree(lun_access_src); + goto out; +} diff --git a/kernel/drivers/scsi/cxlflash/vlun.h b/kernel/drivers/scsi/cxlflash/vlun.h new file mode 100644 index 000000000..8b29a7494 --- /dev/null +++ b/kernel/drivers/scsi/cxlflash/vlun.h @@ -0,0 +1,86 @@ +/* + * 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_VLUN_H +#define _CXLFLASH_VLUN_H + +/* RHT - Resource Handle Table */ +#define MC_RHT_NMASK 16 /* in bits */ +#define MC_CHUNK_SHIFT MC_RHT_NMASK /* shift to go from LBA to chunk# */ + +#define HIBIT (BITS_PER_LONG - 1) + +#define MAX_AUN_CLONE_CNT 0xFF + +/* + * LXT - LBA Translation Table + * + * +-------+-------+-------+-------+-------+-------+-------+---+---+ + * | RLBA_BASE |LUN_IDX| P |SEL| + * +-------+-------+-------+-------+-------+-------+-------+---+---+ + * + * The LXT Entry contains the physical LBA where the chunk starts (RLBA_BASE). + * AFU ORes the low order bits from the virtual LBA (offset into the chunk) + * with RLBA_BASE. The result is the physical LBA to be sent to storage. + * The LXT Entry also contains an index to a LUN TBL and a bitmask of which + * outgoing (FC) * ports can be selected. The port select bit-mask is ANDed + * with a global port select bit-mask maintained by the driver. + * In addition, it has permission bits that are ANDed with the + * RHT permissions to arrive at the final permissions for the chunk. + * + * LXT tables are allocated dynamically in groups. This is done to avoid + * a malloc/free overhead each time the LXT has to grow or shrink. + * + * Based on the current lxt_cnt (used), it is always possible to know + * how many are allocated (used+free). The number of allocated entries is + * not stored anywhere. + * + * The LXT table is re-allocated whenever it needs to cross into another group. +*/ +#define LXT_GROUP_SIZE 8 +#define LXT_NUM_GROUPS(lxt_cnt) (((lxt_cnt) + 7)/8) /* alloc'ed groups */ +#define LXT_LUNIDX_SHIFT 8 /* LXT entry, shift for LUN index */ +#define LXT_PERM_SHIFT 4 /* LXT entry, shift for permission bits */ + +struct ba_lun_info { + u64 *lun_alloc_map; + u32 lun_bmap_size; + u32 total_aus; + u64 free_aun_cnt; + + /* indices to be used for elevator lookup of free map */ + u32 free_low_idx; + u32 free_curr_idx; + u32 free_high_idx; + + u8 *aun_clone_map; +}; + +struct ba_lun { + u64 lun_id; + u64 wwpn; + size_t lsize; /* LUN size in number of LBAs */ + size_t lba_size; /* LBA size in number of bytes */ + size_t au_size; /* Allocation Unit size in number of LBAs */ + struct ba_lun_info *ba_lun_handle; +}; + +/* Block Allocator */ +struct blka { + struct ba_lun ba_lun; + u64 nchunk; /* number of chunks */ + struct mutex mutex; +}; + +#endif /* ifndef _CXLFLASH_SUPERPIPE_H */ |