summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/ibmvscsi/ibmvfc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/ibmvscsi/ibmvfc.c')
-rw-r--r--drivers/scsi/ibmvscsi/ibmvfc.c3910
1 files changed, 3910 insertions, 0 deletions
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c
new file mode 100644
index 000000000000..eb702b96d57c
--- /dev/null
+++ b/drivers/scsi/ibmvscsi/ibmvfc.c
@@ -0,0 +1,3910 @@
+/*
+ * ibmvfc.c -- driver for IBM Power Virtual Fibre Channel Adapter
+ *
+ * Written By: Brian King <brking@linux.vnet.ibm.com>, IBM Corporation
+ *
+ * Copyright (C) IBM Corporation, 2008
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/of.h>
+#include <linux/stringify.h>
+#include <asm/firmware.h>
+#include <asm/irq.h>
+#include <asm/vio.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_transport_fc.h>
+#include "ibmvfc.h"
+
+static unsigned int init_timeout = IBMVFC_INIT_TIMEOUT;
+static unsigned int default_timeout = IBMVFC_DEFAULT_TIMEOUT;
+static unsigned int max_lun = IBMVFC_MAX_LUN;
+static unsigned int max_targets = IBMVFC_MAX_TARGETS;
+static unsigned int max_requests = IBMVFC_MAX_REQUESTS_DEFAULT;
+static unsigned int disc_threads = IBMVFC_MAX_DISC_THREADS;
+static unsigned int dev_loss_tmo = IBMVFC_DEV_LOSS_TMO;
+static unsigned int ibmvfc_debug = IBMVFC_DEBUG;
+static unsigned int log_level = IBMVFC_DEFAULT_LOG_LEVEL;
+static LIST_HEAD(ibmvfc_head);
+static DEFINE_SPINLOCK(ibmvfc_driver_lock);
+static struct scsi_transport_template *ibmvfc_transport_template;
+
+MODULE_DESCRIPTION("IBM Virtual Fibre Channel Driver");
+MODULE_AUTHOR("Brian King <brking@linux.vnet.ibm.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(IBMVFC_DRIVER_VERSION);
+
+module_param_named(init_timeout, init_timeout, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(init_timeout, "Initialization timeout in seconds. "
+ "[Default=" __stringify(IBMVFC_INIT_TIMEOUT) "]");
+module_param_named(default_timeout, default_timeout, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(default_timeout,
+ "Default timeout in seconds for initialization and EH commands. "
+ "[Default=" __stringify(IBMVFC_DEFAULT_TIMEOUT) "]");
+module_param_named(max_requests, max_requests, uint, S_IRUGO);
+MODULE_PARM_DESC(max_requests, "Maximum requests for this adapter. "
+ "[Default=" __stringify(IBMVFC_MAX_REQUESTS_DEFAULT) "]");
+module_param_named(max_lun, max_lun, uint, S_IRUGO);
+MODULE_PARM_DESC(max_lun, "Maximum allowed LUN. "
+ "[Default=" __stringify(IBMVFC_MAX_LUN) "]");
+module_param_named(max_targets, max_targets, uint, S_IRUGO);
+MODULE_PARM_DESC(max_targets, "Maximum allowed targets. "
+ "[Default=" __stringify(IBMVFC_MAX_TARGETS) "]");
+module_param_named(disc_threads, disc_threads, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(disc_threads, "Number of device discovery threads to use. "
+ "[Default=" __stringify(IBMVFC_MAX_DISC_THREADS) "]");
+module_param_named(debug, ibmvfc_debug, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Enable driver debug information. "
+ "[Default=" __stringify(IBMVFC_DEBUG) "]");
+module_param_named(dev_loss_tmo, dev_loss_tmo, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dev_loss_tmo, "Maximum number of seconds that the FC "
+ "transport should insulate the loss of a remote port. Once this "
+ "value is exceeded, the scsi target is removed. "
+ "[Default=" __stringify(IBMVFC_DEV_LOSS_TMO) "]");
+module_param_named(log_level, log_level, uint, 0);
+MODULE_PARM_DESC(log_level, "Set to 0 - 4 for increasing verbosity of device driver. "
+ "[Default=" __stringify(IBMVFC_DEFAULT_LOG_LEVEL) "]");
+
+static const struct {
+ u16 status;
+ u16 error;
+ u8 result;
+ u8 retry;
+ int log;
+ char *name;
+} cmd_status [] = {
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_UNABLE_TO_ESTABLISH, DID_ERROR, 1, 1, "unable to establish" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_XPORT_FAULT, DID_OK, 1, 0, "transport fault" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_CMD_TIMEOUT, DID_TIME_OUT, 1, 1, "command timeout" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_ENETDOWN, DID_NO_CONNECT, 1, 1, "network down" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_HW_FAILURE, DID_ERROR, 1, 1, "hardware failure" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_LINK_DOWN_ERR, DID_REQUEUE, 0, 0, "link down" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_LINK_DEAD_ERR, DID_ERROR, 0, 0, "link dead" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_UNABLE_TO_REGISTER, DID_ERROR, 1, 1, "unable to register" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_XPORT_BUSY, DID_BUS_BUSY, 1, 0, "transport busy" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_XPORT_DEAD, DID_ERROR, 0, 1, "transport dead" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_CONFIG_ERROR, DID_ERROR, 1, 1, "configuration error" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_NAME_SERVER_FAIL, DID_ERROR, 1, 1, "name server failure" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_LINK_HALTED, DID_REQUEUE, 0, 0, "link halted" },
+ { IBMVFC_FABRIC_MAPPED, IBMVFC_XPORT_GENERAL, DID_OK, 1, 0, "general transport error" },
+
+ { IBMVFC_VIOS_FAILURE, IBMVFC_CRQ_FAILURE, DID_REQUEUE, 1, 1, "CRQ failure" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_SW_FAILURE, DID_ERROR, 0, 1, "software failure" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_INVALID_PARAMETER, DID_ABORT, 0, 1, "invalid parameter" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_MISSING_PARAMETER, DID_ABORT, 0, 1, "missing parameter" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_HOST_IO_BUS, DID_ERROR, 1, 1, "host I/O bus failure" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_TRANS_CANCELLED, DID_ABORT, 0, 1, "transaction cancelled" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_TRANS_CANCELLED_IMPLICIT, DID_ABORT, 0, 1, "transaction cancelled implicit" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_INSUFFICIENT_RESOURCE, DID_REQUEUE, 1, 1, "insufficient resources" },
+ { IBMVFC_VIOS_FAILURE, IBMVFC_COMMAND_FAILED, DID_ERROR, 1, 1, "command failed" },
+
+ { IBMVFC_FC_FAILURE, IBMVFC_INVALID_ELS_CMD_CODE, DID_ERROR, 0, 1, "invalid ELS command code" },
+ { IBMVFC_FC_FAILURE, IBMVFC_INVALID_VERSION, DID_ERROR, 0, 1, "invalid version level" },
+ { IBMVFC_FC_FAILURE, IBMVFC_LOGICAL_ERROR, DID_ERROR, 1, 1, "logical error" },
+ { IBMVFC_FC_FAILURE, IBMVFC_INVALID_CT_IU_SIZE, DID_ERROR, 0, 1, "invalid CT_IU size" },
+ { IBMVFC_FC_FAILURE, IBMVFC_LOGICAL_BUSY, DID_REQUEUE, 1, 0, "logical busy" },
+ { IBMVFC_FC_FAILURE, IBMVFC_PROTOCOL_ERROR, DID_ERROR, 1, 1, "protocol error" },
+ { IBMVFC_FC_FAILURE, IBMVFC_UNABLE_TO_PERFORM_REQ, DID_ERROR, 1, 1, "unable to perform request" },
+ { IBMVFC_FC_FAILURE, IBMVFC_CMD_NOT_SUPPORTED, DID_ERROR, 0, 0, "command not supported" },
+ { IBMVFC_FC_FAILURE, IBMVFC_SERVER_NOT_AVAIL, DID_ERROR, 0, 1, "server not available" },
+ { IBMVFC_FC_FAILURE, IBMVFC_CMD_IN_PROGRESS, DID_ERROR, 0, 1, "command already in progress" },
+ { IBMVFC_FC_FAILURE, IBMVFC_VENDOR_SPECIFIC, DID_ERROR, 1, 1, "vendor specific" },
+
+ { IBMVFC_FC_SCSI_ERROR, 0, DID_OK, 1, 0, "SCSI error" },
+};
+
+static void ibmvfc_npiv_login(struct ibmvfc_host *);
+static void ibmvfc_tgt_send_prli(struct ibmvfc_target *);
+static void ibmvfc_tgt_send_plogi(struct ibmvfc_target *);
+static void ibmvfc_tgt_query_target(struct ibmvfc_target *);
+
+static const char *unknown_error = "unknown error";
+
+#ifdef CONFIG_SCSI_IBMVFC_TRACE
+/**
+ * ibmvfc_trc_start - Log a start trace entry
+ * @evt: ibmvfc event struct
+ *
+ **/
+static void ibmvfc_trc_start(struct ibmvfc_event *evt)
+{
+ struct ibmvfc_host *vhost = evt->vhost;
+ struct ibmvfc_cmd *vfc_cmd = &evt->iu.cmd;
+ struct ibmvfc_mad_common *mad = &evt->iu.mad_common;
+ struct ibmvfc_trace_entry *entry;
+
+ entry = &vhost->trace[vhost->trace_index++];
+ entry->evt = evt;
+ entry->time = jiffies;
+ entry->fmt = evt->crq.format;
+ entry->type = IBMVFC_TRC_START;
+
+ switch (entry->fmt) {
+ case IBMVFC_CMD_FORMAT:
+ entry->op_code = vfc_cmd->iu.cdb[0];
+ entry->scsi_id = vfc_cmd->tgt_scsi_id;
+ entry->lun = scsilun_to_int(&vfc_cmd->iu.lun);
+ entry->tmf_flags = vfc_cmd->iu.tmf_flags;
+ entry->u.start.xfer_len = vfc_cmd->iu.xfer_len;
+ break;
+ case IBMVFC_MAD_FORMAT:
+ entry->op_code = mad->opcode;
+ break;
+ default:
+ break;
+ };
+}
+
+/**
+ * ibmvfc_trc_end - Log an end trace entry
+ * @evt: ibmvfc event struct
+ *
+ **/
+static void ibmvfc_trc_end(struct ibmvfc_event *evt)
+{
+ struct ibmvfc_host *vhost = evt->vhost;
+ struct ibmvfc_cmd *vfc_cmd = &evt->xfer_iu->cmd;
+ struct ibmvfc_mad_common *mad = &evt->xfer_iu->mad_common;
+ struct ibmvfc_trace_entry *entry = &vhost->trace[vhost->trace_index++];
+
+ entry->evt = evt;
+ entry->time = jiffies;
+ entry->fmt = evt->crq.format;
+ entry->type = IBMVFC_TRC_END;
+
+ switch (entry->fmt) {
+ case IBMVFC_CMD_FORMAT:
+ entry->op_code = vfc_cmd->iu.cdb[0];
+ entry->scsi_id = vfc_cmd->tgt_scsi_id;
+ entry->lun = scsilun_to_int(&vfc_cmd->iu.lun);
+ entry->tmf_flags = vfc_cmd->iu.tmf_flags;
+ entry->u.end.status = vfc_cmd->status;
+ entry->u.end.error = vfc_cmd->error;
+ entry->u.end.fcp_rsp_flags = vfc_cmd->rsp.flags;
+ entry->u.end.rsp_code = vfc_cmd->rsp.data.info.rsp_code;
+ entry->u.end.scsi_status = vfc_cmd->rsp.scsi_status;
+ break;
+ case IBMVFC_MAD_FORMAT:
+ entry->op_code = mad->opcode;
+ entry->u.end.status = mad->status;
+ break;
+ default:
+ break;
+
+ };
+}
+
+#else
+#define ibmvfc_trc_start(evt) do { } while (0)
+#define ibmvfc_trc_end(evt) do { } while (0)
+#endif
+
+/**
+ * ibmvfc_get_err_index - Find the index into cmd_status for the fcp response
+ * @status: status / error class
+ * @error: error
+ *
+ * Return value:
+ * index into cmd_status / -EINVAL on failure
+ **/
+static int ibmvfc_get_err_index(u16 status, u16 error)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cmd_status); i++)
+ if ((cmd_status[i].status & status) == cmd_status[i].status &&
+ cmd_status[i].error == error)
+ return i;
+
+ return -EINVAL;
+}
+
+/**
+ * ibmvfc_get_cmd_error - Find the error description for the fcp response
+ * @status: status / error class
+ * @error: error
+ *
+ * Return value:
+ * error description string
+ **/
+static const char *ibmvfc_get_cmd_error(u16 status, u16 error)
+{
+ int rc = ibmvfc_get_err_index(status, error);
+ if (rc >= 0)
+ return cmd_status[rc].name;
+ return unknown_error;
+}
+
+/**
+ * ibmvfc_get_err_result - Find the scsi status to return for the fcp response
+ * @vfc_cmd: ibmvfc command struct
+ *
+ * Return value:
+ * SCSI result value to return for completed command
+ **/
+static int ibmvfc_get_err_result(struct ibmvfc_cmd *vfc_cmd)
+{
+ int err;
+ struct ibmvfc_fcp_rsp *rsp = &vfc_cmd->rsp;
+ int fc_rsp_len = rsp->fcp_rsp_len;
+
+ if ((rsp->flags & FCP_RSP_LEN_VALID) &&
+ ((!fc_rsp_len && fc_rsp_len != 4 && fc_rsp_len != 8) ||
+ rsp->data.info.rsp_code))
+ return DID_ERROR << 16;
+
+ if (!vfc_cmd->status) {
+ if (rsp->flags & FCP_RESID_OVER)
+ return rsp->scsi_status | (DID_ERROR << 16);
+ else
+ return rsp->scsi_status | (DID_OK << 16);
+ }
+
+ err = ibmvfc_get_err_index(vfc_cmd->status, vfc_cmd->error);
+ if (err >= 0)
+ return rsp->scsi_status | (cmd_status[err].result << 16);
+ return rsp->scsi_status | (DID_ERROR << 16);
+}
+
+/**
+ * ibmvfc_retry_cmd - Determine if error status is retryable
+ * @status: status / error class
+ * @error: error
+ *
+ * Return value:
+ * 1 if error should be retried / 0 if it should not
+ **/
+static int ibmvfc_retry_cmd(u16 status, u16 error)
+{
+ int rc = ibmvfc_get_err_index(status, error);
+
+ if (rc >= 0)
+ return cmd_status[rc].retry;
+ return 1;
+}
+
+static const char *unknown_fc_explain = "unknown fc explain";
+
+static const struct {
+ u16 fc_explain;
+ char *name;
+} ls_explain [] = {
+ { 0x00, "no additional explanation" },
+ { 0x01, "service parameter error - options" },
+ { 0x03, "service parameter error - initiator control" },
+ { 0x05, "service parameter error - recipient control" },
+ { 0x07, "service parameter error - received data field size" },
+ { 0x09, "service parameter error - concurrent seq" },
+ { 0x0B, "service parameter error - credit" },
+ { 0x0D, "invalid N_Port/F_Port_Name" },
+ { 0x0E, "invalid node/Fabric Name" },
+ { 0x0F, "invalid common service parameters" },
+ { 0x11, "invalid association header" },
+ { 0x13, "association header required" },
+ { 0x15, "invalid originator S_ID" },
+ { 0x17, "invalid OX_ID-RX-ID combination" },
+ { 0x19, "command (request) already in progress" },
+ { 0x1E, "N_Port Login requested" },
+ { 0x1F, "Invalid N_Port_ID" },
+};
+
+static const struct {
+ u16 fc_explain;
+ char *name;
+} gs_explain [] = {
+ { 0x00, "no additional explanation" },
+ { 0x01, "port identifier not registered" },
+ { 0x02, "port name not registered" },
+ { 0x03, "node name not registered" },
+ { 0x04, "class of service not registered" },
+ { 0x06, "initial process associator not registered" },
+ { 0x07, "FC-4 TYPEs not registered" },
+ { 0x08, "symbolic port name not registered" },
+ { 0x09, "symbolic node name not registered" },
+ { 0x0A, "port type not registered" },
+ { 0xF0, "authorization exception" },
+ { 0xF1, "authentication exception" },
+ { 0xF2, "data base full" },
+ { 0xF3, "data base empty" },
+ { 0xF4, "processing request" },
+ { 0xF5, "unable to verify connection" },
+ { 0xF6, "devices not in a common zone" },
+};
+
+/**
+ * ibmvfc_get_ls_explain - Return the FC Explain description text
+ * @status: FC Explain status
+ *
+ * Returns:
+ * error string
+ **/
+static const char *ibmvfc_get_ls_explain(u16 status)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ls_explain); i++)
+ if (ls_explain[i].fc_explain == status)
+ return ls_explain[i].name;
+
+ return unknown_fc_explain;
+}
+
+/**
+ * ibmvfc_get_gs_explain - Return the FC Explain description text
+ * @status: FC Explain status
+ *
+ * Returns:
+ * error string
+ **/
+static const char *ibmvfc_get_gs_explain(u16 status)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(gs_explain); i++)
+ if (gs_explain[i].fc_explain == status)
+ return gs_explain[i].name;
+
+ return unknown_fc_explain;
+}
+
+static const struct {
+ enum ibmvfc_fc_type fc_type;
+ char *name;
+} fc_type [] = {
+ { IBMVFC_FABRIC_REJECT, "fabric reject" },
+ { IBMVFC_PORT_REJECT, "port reject" },
+ { IBMVFC_LS_REJECT, "ELS reject" },
+ { IBMVFC_FABRIC_BUSY, "fabric busy" },
+ { IBMVFC_PORT_BUSY, "port busy" },
+ { IBMVFC_BASIC_REJECT, "basic reject" },
+};
+
+static const char *unknown_fc_type = "unknown fc type";
+
+/**
+ * ibmvfc_get_fc_type - Return the FC Type description text
+ * @status: FC Type error status
+ *
+ * Returns:
+ * error string
+ **/
+static const char *ibmvfc_get_fc_type(u16 status)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fc_type); i++)
+ if (fc_type[i].fc_type == status)
+ return fc_type[i].name;
+
+ return unknown_fc_type;
+}
+
+/**
+ * ibmvfc_set_tgt_action - Set the next init action for the target
+ * @tgt: ibmvfc target struct
+ * @action: action to perform
+ *
+ **/
+static void ibmvfc_set_tgt_action(struct ibmvfc_target *tgt,
+ enum ibmvfc_target_action action)
+{
+ switch (tgt->action) {
+ case IBMVFC_TGT_ACTION_DEL_RPORT:
+ break;
+ default:
+ tgt->action = action;
+ break;
+ }
+}
+
+/**
+ * ibmvfc_set_host_state - Set the state for the host
+ * @vhost: ibmvfc host struct
+ * @state: state to set host to
+ *
+ * Returns:
+ * 0 if state changed / non-zero if not changed
+ **/
+static int ibmvfc_set_host_state(struct ibmvfc_host *vhost,
+ enum ibmvfc_host_state state)
+{
+ int rc = 0;
+
+ switch (vhost->state) {
+ case IBMVFC_HOST_OFFLINE:
+ rc = -EINVAL;
+ break;
+ default:
+ vhost->state = state;
+ break;
+ };
+
+ return rc;
+}
+
+/**
+ * ibmvfc_set_host_action - Set the next init action for the host
+ * @vhost: ibmvfc host struct
+ * @action: action to perform
+ *
+ **/
+static void ibmvfc_set_host_action(struct ibmvfc_host *vhost,
+ enum ibmvfc_host_action action)
+{
+ switch (action) {
+ case IBMVFC_HOST_ACTION_ALLOC_TGTS:
+ if (vhost->action == IBMVFC_HOST_ACTION_INIT_WAIT)
+ vhost->action = action;
+ break;
+ case IBMVFC_HOST_ACTION_INIT_WAIT:
+ if (vhost->action == IBMVFC_HOST_ACTION_INIT)
+ vhost->action = action;
+ break;
+ case IBMVFC_HOST_ACTION_QUERY:
+ switch (vhost->action) {
+ case IBMVFC_HOST_ACTION_INIT_WAIT:
+ case IBMVFC_HOST_ACTION_NONE:
+ case IBMVFC_HOST_ACTION_TGT_ADD:
+ vhost->action = action;
+ break;
+ default:
+ break;
+ };
+ break;
+ case IBMVFC_HOST_ACTION_TGT_INIT:
+ if (vhost->action == IBMVFC_HOST_ACTION_ALLOC_TGTS)
+ vhost->action = action;
+ break;
+ case IBMVFC_HOST_ACTION_INIT:
+ case IBMVFC_HOST_ACTION_TGT_DEL:
+ case IBMVFC_HOST_ACTION_QUERY_TGTS:
+ case IBMVFC_HOST_ACTION_TGT_ADD:
+ case IBMVFC_HOST_ACTION_NONE:
+ default:
+ vhost->action = action;
+ break;
+ };
+}
+
+/**
+ * ibmvfc_reinit_host - Re-start host initialization (no NPIV Login)
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * nothing
+ **/
+static void ibmvfc_reinit_host(struct ibmvfc_host *vhost)
+{
+ if (vhost->action == IBMVFC_HOST_ACTION_NONE) {
+ scsi_block_requests(vhost->host);
+ ibmvfc_set_host_state(vhost, IBMVFC_INITIALIZING);
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_QUERY);
+ } else
+ vhost->reinit = 1;
+
+ wake_up(&vhost->work_wait_q);
+}
+
+/**
+ * ibmvfc_link_down - Handle a link down event from the adapter
+ * @vhost: ibmvfc host struct
+ * @state: ibmvfc host state to enter
+ *
+ **/
+static void ibmvfc_link_down(struct ibmvfc_host *vhost,
+ enum ibmvfc_host_state state)
+{
+ struct ibmvfc_target *tgt;
+
+ ENTER;
+ scsi_block_requests(vhost->host);
+ list_for_each_entry(tgt, &vhost->targets, queue)
+ ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_DEL_RPORT);
+ ibmvfc_set_host_state(vhost, state);
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_TGT_DEL);
+ vhost->events_to_log |= IBMVFC_AE_LINKDOWN;
+ wake_up(&vhost->work_wait_q);
+ LEAVE;
+}
+
+/**
+ * ibmvfc_init_host - Start host initialization
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * nothing
+ **/
+static void ibmvfc_init_host(struct ibmvfc_host *vhost)
+{
+ struct ibmvfc_target *tgt;
+
+ if (vhost->action == IBMVFC_HOST_ACTION_INIT_WAIT) {
+ if (++vhost->init_retries > IBMVFC_MAX_INIT_RETRIES) {
+ dev_err(vhost->dev,
+ "Host initialization retries exceeded. Taking adapter offline\n");
+ ibmvfc_link_down(vhost, IBMVFC_HOST_OFFLINE);
+ return;
+ }
+ }
+
+ if (!ibmvfc_set_host_state(vhost, IBMVFC_INITIALIZING)) {
+ list_for_each_entry(tgt, &vhost->targets, queue)
+ tgt->need_login = 1;
+ scsi_block_requests(vhost->host);
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT);
+ vhost->job_step = ibmvfc_npiv_login;
+ wake_up(&vhost->work_wait_q);
+ }
+}
+
+/**
+ * ibmvfc_send_crq - Send a CRQ
+ * @vhost: ibmvfc host struct
+ * @word1: the first 64 bits of the data
+ * @word2: the second 64 bits of the data
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_send_crq(struct ibmvfc_host *vhost, u64 word1, u64 word2)
+{
+ struct vio_dev *vdev = to_vio_dev(vhost->dev);
+ return plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, word1, word2);
+}
+
+/**
+ * ibmvfc_send_crq_init - Send a CRQ init message
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_send_crq_init(struct ibmvfc_host *vhost)
+{
+ ibmvfc_dbg(vhost, "Sending CRQ init\n");
+ return ibmvfc_send_crq(vhost, 0xC001000000000000LL, 0);
+}
+
+/**
+ * ibmvfc_send_crq_init_complete - Send a CRQ init complete message
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_send_crq_init_complete(struct ibmvfc_host *vhost)
+{
+ ibmvfc_dbg(vhost, "Sending CRQ init complete\n");
+ return ibmvfc_send_crq(vhost, 0xC002000000000000LL, 0);
+}
+
+/**
+ * ibmvfc_release_crq_queue - Deallocates data and unregisters CRQ
+ * @vhost: ibmvfc host struct
+ *
+ * Frees irq, deallocates a page for messages, unmaps dma, and unregisters
+ * the crq with the hypervisor.
+ **/
+static void ibmvfc_release_crq_queue(struct ibmvfc_host *vhost)
+{
+ long rc;
+ struct vio_dev *vdev = to_vio_dev(vhost->dev);
+ struct ibmvfc_crq_queue *crq = &vhost->crq;
+
+ ibmvfc_dbg(vhost, "Releasing CRQ\n");
+ free_irq(vdev->irq, vhost);
+ do {
+ rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address);
+ } while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+ vhost->state = IBMVFC_NO_CRQ;
+ dma_unmap_single(vhost->dev, crq->msg_token, PAGE_SIZE, DMA_BIDIRECTIONAL);
+ free_page((unsigned long)crq->msgs);
+}
+
+/**
+ * ibmvfc_reenable_crq_queue - reenables the CRQ
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_reenable_crq_queue(struct ibmvfc_host *vhost)
+{
+ int rc;
+ struct vio_dev *vdev = to_vio_dev(vhost->dev);
+
+ /* Re-enable the CRQ */
+ do {
+ rc = plpar_hcall_norets(H_ENABLE_CRQ, vdev->unit_address);
+ } while (rc == H_IN_PROGRESS || rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+ if (rc)
+ dev_err(vhost->dev, "Error enabling adapter (rc=%d)\n", rc);
+
+ return rc;
+}
+
+/**
+ * ibmvfc_reset_crq - resets a crq after a failure
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_reset_crq(struct ibmvfc_host *vhost)
+{
+ int rc;
+ struct vio_dev *vdev = to_vio_dev(vhost->dev);
+ struct ibmvfc_crq_queue *crq = &vhost->crq;
+
+ /* Close the CRQ */
+ do {
+ rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address);
+ } while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+ vhost->state = IBMVFC_NO_CRQ;
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_NONE);
+
+ /* Clean out the queue */
+ memset(crq->msgs, 0, PAGE_SIZE);
+ crq->cur = 0;
+
+ /* And re-open it again */
+ rc = plpar_hcall_norets(H_REG_CRQ, vdev->unit_address,
+ crq->msg_token, PAGE_SIZE);
+
+ if (rc == H_CLOSED)
+ /* Adapter is good, but other end is not ready */
+ dev_warn(vhost->dev, "Partner adapter not ready\n");
+ else if (rc != 0)
+ dev_warn(vhost->dev, "Couldn't register crq (rc=%d)\n", rc);
+
+ return rc;
+}
+
+/**
+ * ibmvfc_valid_event - Determines if event is valid.
+ * @pool: event_pool that contains the event
+ * @evt: ibmvfc event to be checked for validity
+ *
+ * Return value:
+ * 1 if event is valid / 0 if event is not valid
+ **/
+static int ibmvfc_valid_event(struct ibmvfc_event_pool *pool,
+ struct ibmvfc_event *evt)
+{
+ int index = evt - pool->events;
+ if (index < 0 || index >= pool->size) /* outside of bounds */
+ return 0;
+ if (evt != pool->events + index) /* unaligned */
+ return 0;
+ return 1;
+}
+
+/**
+ * ibmvfc_free_event - Free the specified event
+ * @evt: ibmvfc_event to be freed
+ *
+ **/
+static void ibmvfc_free_event(struct ibmvfc_event *evt)
+{
+ struct ibmvfc_host *vhost = evt->vhost;
+ struct ibmvfc_event_pool *pool = &vhost->pool;
+
+ BUG_ON(!ibmvfc_valid_event(pool, evt));
+ BUG_ON(atomic_inc_return(&evt->free) != 1);
+ list_add_tail(&evt->queue, &vhost->free);
+}
+
+/**
+ * ibmvfc_scsi_eh_done - EH done function for queuecommand commands
+ * @evt: ibmvfc event struct
+ *
+ * This function does not setup any error status, that must be done
+ * before this function gets called.
+ **/
+static void ibmvfc_scsi_eh_done(struct ibmvfc_event *evt)
+{
+ struct scsi_cmnd *cmnd = evt->cmnd;
+
+ if (cmnd) {
+ scsi_dma_unmap(cmnd);
+ cmnd->scsi_done(cmnd);
+ }
+
+ ibmvfc_free_event(evt);
+}
+
+/**
+ * ibmvfc_fail_request - Fail request with specified error code
+ * @evt: ibmvfc event struct
+ * @error_code: error code to fail request with
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_fail_request(struct ibmvfc_event *evt, int error_code)
+{
+ if (evt->cmnd) {
+ evt->cmnd->result = (error_code << 16);
+ evt->done = ibmvfc_scsi_eh_done;
+ } else
+ evt->xfer_iu->mad_common.status = IBMVFC_MAD_DRIVER_FAILED;
+
+ list_del(&evt->queue);
+ del_timer(&evt->timer);
+ ibmvfc_trc_end(evt);
+ evt->done(evt);
+}
+
+/**
+ * ibmvfc_purge_requests - Our virtual adapter just shut down. Purge any sent requests
+ * @vhost: ibmvfc host struct
+ * @error_code: error code to fail requests with
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_purge_requests(struct ibmvfc_host *vhost, int error_code)
+{
+ struct ibmvfc_event *evt, *pos;
+
+ ibmvfc_dbg(vhost, "Purging all requests\n");
+ list_for_each_entry_safe(evt, pos, &vhost->sent, queue)
+ ibmvfc_fail_request(evt, error_code);
+}
+
+/**
+ * __ibmvfc_reset_host - Reset the connection to the server (no locking)
+ * @vhost: struct ibmvfc host to reset
+ **/
+static void __ibmvfc_reset_host(struct ibmvfc_host *vhost)
+{
+ int rc;
+
+ scsi_block_requests(vhost->host);
+ ibmvfc_purge_requests(vhost, DID_ERROR);
+ if ((rc = ibmvfc_reset_crq(vhost)) ||
+ (rc = ibmvfc_send_crq_init(vhost)) ||
+ (rc = vio_enable_interrupts(to_vio_dev(vhost->dev)))) {
+ dev_err(vhost->dev, "Error after reset rc=%d\n", rc);
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DEAD);
+ } else
+ ibmvfc_link_down(vhost, IBMVFC_LINK_DOWN);
+}
+
+/**
+ * ibmvfc_reset_host - Reset the connection to the server
+ * @vhost: struct ibmvfc host to reset
+ **/
+static void ibmvfc_reset_host(struct ibmvfc_host *vhost)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(vhost->host->host_lock, flags);
+ __ibmvfc_reset_host(vhost);
+ spin_unlock_irqrestore(vhost->host->host_lock, flags);
+}
+
+/**
+ * ibmvfc_retry_host_init - Retry host initialization if allowed
+ * @vhost: ibmvfc host struct
+ *
+ **/
+static void ibmvfc_retry_host_init(struct ibmvfc_host *vhost)
+{
+ if (vhost->action == IBMVFC_HOST_ACTION_INIT_WAIT) {
+ if (++vhost->init_retries > IBMVFC_MAX_INIT_RETRIES) {
+ dev_err(vhost->dev,
+ "Host initialization retries exceeded. Taking adapter offline\n");
+ ibmvfc_link_down(vhost, IBMVFC_HOST_OFFLINE);
+ } else if (vhost->init_retries == IBMVFC_MAX_INIT_RETRIES)
+ __ibmvfc_reset_host(vhost);
+ else
+ ibmvfc_set_host_action(vhost, IBMVFC_HOST_ACTION_INIT);
+ }
+
+ wake_up(&vhost->work_wait_q);
+}
+
+/**
+ * __ibmvfc_find_target - Find the specified scsi_target (no locking)
+ * @starget: scsi target struct
+ *
+ * Return value:
+ * ibmvfc_target struct / NULL if not found
+ **/
+static struct ibmvfc_target *__ibmvfc_find_target(struct scsi_target *starget)
+{
+ struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+ struct ibmvfc_host *vhost = shost_priv(shost);
+ struct ibmvfc_target *tgt;
+
+ list_for_each_entry(tgt, &vhost->targets, queue)
+ if (tgt->target_id == starget->id)
+ return tgt;
+ return NULL;
+}
+
+/**
+ * ibmvfc_find_target - Find the specified scsi_target
+ * @starget: scsi target struct
+ *
+ * Return value:
+ * ibmvfc_target struct / NULL if not found
+ **/
+static struct ibmvfc_target *ibmvfc_find_target(struct scsi_target *starget)
+{
+ struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+ struct ibmvfc_target *tgt;
+ unsigned long flags;
+
+ spin_lock_irqsave(shost->host_lock, flags);
+ tgt = __ibmvfc_find_target(starget);
+ spin_unlock_irqrestore(shost->host_lock, flags);
+ return tgt;
+}
+
+/**
+ * ibmvfc_get_host_speed - Get host port speed
+ * @shost: scsi host struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_get_host_speed(struct Scsi_Host *shost)
+{
+ struct ibmvfc_host *vhost = shost_priv(shost);
+ unsigned long flags;
+
+ spin_lock_irqsave(shost->host_lock, flags);
+ if (vhost->state == IBMVFC_ACTIVE) {
+ switch (vhost->login_buf->resp.link_speed / 100) {
+ case 1:
+ fc_host_speed(shost) = FC_PORTSPEED_1GBIT;
+ break;
+ case 2:
+ fc_host_speed(shost) = FC_PORTSPEED_2GBIT;
+ break;
+ case 4:
+ fc_host_speed(shost) = FC_PORTSPEED_4GBIT;
+ break;
+ case 8:
+ fc_host_speed(shost) = FC_PORTSPEED_8GBIT;
+ break;
+ case 10:
+ fc_host_speed(shost) = FC_PORTSPEED_10GBIT;
+ break;
+ case 16:
+ fc_host_speed(shost) = FC_PORTSPEED_16GBIT;
+ break;
+ default:
+ ibmvfc_log(vhost, 3, "Unknown port speed: %ld Gbit\n",
+ vhost->login_buf->resp.link_speed / 100);
+ fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN;
+ break;
+ }
+ } else
+ fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN;
+ spin_unlock_irqrestore(shost->host_lock, flags);
+}
+
+/**
+ * ibmvfc_get_host_port_state - Get host port state
+ * @shost: scsi host struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_get_host_port_state(struct Scsi_Host *shost)
+{
+ struct ibmvfc_host *vhost = shost_priv(shost);
+ unsigned long flags;
+
+ spin_lock_irqsave(shost->host_lock, flags);
+ switch (vhost->state) {
+ case IBMVFC_INITIALIZING:
+ case IBMVFC_ACTIVE:
+ fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
+ break;
+ case IBMVFC_LINK_DOWN:
+ fc_host_port_state(shost) = FC_PORTSTATE_LINKDOWN;
+ break;
+ case IBMVFC_LINK_DEAD:
+ case IBMVFC_HOST_OFFLINE:
+ fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
+ break;
+ case IBMVFC_HALTED:
+ fc_host_port_state(shost) = FC_PORTSTATE_BLOCKED;
+ break;
+ default:
+ ibmvfc_log(vhost, 3, "Unknown port state: %d\n", vhost->state);
+ fc_host_port_state(shost) = FC_PORTSTATE_UNKNOWN;
+ break;
+ }
+ spin_unlock_irqrestore(shost->host_lock, flags);
+}
+
+/**
+ * ibmvfc_set_rport_dev_loss_tmo - Set rport's device loss timeout
+ * @rport: rport struct
+ * @timeout: timeout value
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_set_rport_dev_loss_tmo(struct fc_rport *rport, u32 timeout)
+{
+ if (timeout)
+ rport->dev_loss_tmo = timeout;
+ else
+ rport->dev_loss_tmo = 1;
+}
+
+/**
+ * ibmvfc_get_starget_node_name - Get SCSI target's node name
+ * @starget: scsi target struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_get_starget_node_name(struct scsi_target *starget)
+{
+ struct ibmvfc_target *tgt = ibmvfc_find_target(starget);
+ fc_starget_port_name(starget) = tgt ? tgt->ids.node_name : 0;
+}
+
+/**
+ * ibmvfc_get_starget_port_name - Get SCSI target's port name
+ * @starget: scsi target struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_get_starget_port_name(struct scsi_target *starget)
+{
+ struct ibmvfc_target *tgt = ibmvfc_find_target(starget);
+ fc_starget_port_name(starget) = tgt ? tgt->ids.port_name : 0;
+}
+
+/**
+ * ibmvfc_get_starget_port_id - Get SCSI target's port ID
+ * @starget: scsi target struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_get_starget_port_id(struct scsi_target *starget)
+{
+ struct ibmvfc_target *tgt = ibmvfc_find_target(starget);
+ fc_starget_port_id(starget) = tgt ? tgt->scsi_id : -1;
+}
+
+/**
+ * ibmvfc_wait_while_resetting - Wait while the host resets
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_wait_while_resetting(struct ibmvfc_host *vhost)
+{
+ long timeout = wait_event_timeout(vhost->init_wait_q,
+ (vhost->state == IBMVFC_ACTIVE ||
+ vhost->state == IBMVFC_HOST_OFFLINE ||
+ vhost->state == IBMVFC_LINK_DEAD),
+ (init_timeout * HZ));
+
+ return timeout ? 0 : -EIO;
+}
+
+/**
+ * ibmvfc_issue_fc_host_lip - Re-initiate link initialization
+ * @shost: scsi host struct
+ *
+ * Return value:
+ * 0 on success / other on failure
+ **/
+static int ibmvfc_issue_fc_host_lip(struct Scsi_Host *shost)
+{
+ struct ibmvfc_host *vhost = shost_priv(shost);
+
+ dev_err(vhost->dev, "Initiating host LIP. Resetting connection\n");
+ ibmvfc_reset_host(vhost);
+ return ibmvfc_wait_while_resetting(vhost);
+}
+
+/**
+ * ibmvfc_gather_partition_info - Gather info about the LPAR
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_gather_partition_info(struct ibmvfc_host *vhost)
+{
+ struct device_node *rootdn;
+ const char *name;
+ const unsigned int *num;
+
+ rootdn = of_find_node_by_path("/");
+ if (!rootdn)
+ return;
+
+ name = of_get_property(rootdn, "ibm,partition-name", NULL);
+ if (name)
+ strncpy(vhost->partition_name, name, sizeof(vhost->partition_name));
+ num = of_get_property(rootdn, "ibm,partition-no", NULL);
+ if (num)
+ vhost->partition_number = *num;
+ of_node_put(rootdn);
+}
+
+/**
+ * ibmvfc_set_login_info - Setup info for NPIV login
+ * @vhost: ibmvfc host struct
+ *
+ * Return value:
+ * none
+ **/
+static void ibmvfc_set_login_info(struct ibmvfc_host *vhost)
+{
+ struct ibmvfc_npiv_login *login_info = &vhost->login_info;
+ struct device_node *of_node = vhost->dev->archdata.of_node;
+ const char *location;
+
+ memset(login_info, 0, sizeof(*login_info));
+
+ login_info->ostype = IBMVFC_OS_LINUX;
+ login_info->max_dma_len = IBMVFC_MAX_SECTORS << 9;
+ login_info->max_payload = sizeof(struct ibmvfc_fcp_cmd_iu);
+ login_info->max_response = sizeof(struct ibmvfc_fcp_rsp);
+ login_info->partition_num = vhost->partition_number;
+ login_info->vfc_frame_version = 1;
+ login_info->fcp_version = 3;
+ if (vhost->client_migrated)
+ login_info->flags = IBMVFC_CLIENT_MIGRATED;
+
+ login_info->max_cmds = max_requests + IBMVFC_NUM_INTERNAL_REQ;
+ login_info->capabilities = IBMVFC_CAN_MIGRATE;
+ login_info->async.va = vhost->async_crq.msg_token;
+ login_info->async.len = vhost->async_crq.size;
+ strncpy(login_info->partition_name, vhost->partition_name, IBMVFC_MAX_NAME);
+ strncpy(login_info->device_name,
+ vhost->host->shost_gendev.bus_id, IBMVFC_MAX_NAME);
+
+ location = of_get_property(of_node, "ibm,loc-code", NULL);
+ location = location ? location : vhost->dev->bus_id;
+ strncpy(login