blob: de20da804847ff3fc5b7bce6b87d0dad44de1c64 [file] [log] [blame]
/*
* scst_qla2xxx.c
*
* SCST Cavium Adapter target interface driver.
*
* Based on initial work by:
* Copyright (C) 2004 - 2012 Vladislav Bolkhovitin <vst@vlnb.net>
* Copyright (C) 2004 - 2005 Leonid Stoljar
* Copyright (C) 2006 Nathaniel Clark <nate@misrule.us>
* Copyright (C) 2006 - 2010 ID7 Ltd.
* Copyright (C) 2010 - 2012 SCST Ltd.
*
* Port to Cavium in-kernel target driver by:
* Copyright (C) 2013 Dr. Greg Wettstein, Enjellic Systems Development, LLC
* Copyright (C) 2017 Cavium Inc
*
* 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, version 2
* of the License.
*
* 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.
*/
/*
* This driver is an interface between the SCST core and the Cavium target
* code (qla_target.c). It uses the LIO se_session and se_cmd data structures
* to exchange information between this interface driver and the qla2xxx
* driver.
*
* This interface driver relies heavily upon hardware and mutex locking
* supplied by the qla2xxx driver. The primary exception to this is the
* statically scoped sqa_mutex locks which provides exclusion locking between
* target registration and module unload.
*/
#define EXCLUDED 0
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <asm/unaligned.h>
#include <linux/vmalloc.h>
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#include <scst/scst_debug.h>
#else
#include "scst.h"
#include "scst_debug.h"
#endif
#include "qla_def.h"
#include "qla_target.h"
#include "scst_qla2xxx.h"
#define SQA_VERSION QLA2XXX_VERSION
#define SQA_MAX_HW_PENDING_TIME 60 /* in seconds */
#ifdef CONFIG_SCST_DEBUG
#define SQA_DEFAULT_LOG_FLAGS (TRACE_FUNCTION | TRACE_LINE | TRACE_PID | \
TRACE_OUT_OF_MEM | TRACE_MGMT | TRACE_MGMT_DEBUG | \
TRACE_MINOR | TRACE_SPECIAL)
#else
#ifdef CONFIG_SCST_TRACING
#define SQA_DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \
TRACE_SPECIAL)
#endif
#endif
static LIST_HEAD(sqa_tgt_glist);
/* Function definitions for callbacks from the SCST target core. */
static int sqa_target_release(struct scst_tgt *scst_tgt);
static int sqa_xmit_response(struct scst_cmd *scst_cmd);
static int sqa_rdy_to_xfer(struct scst_cmd *scst_cmd);
static void sqa_on_free_cmd(struct scst_cmd *scst_cmd);
static void sqa_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd);
static int sqa_get_initiator_port_transport_id(struct scst_tgt *tgt,
struct scst_session *scst_sess,
uint8_t **transport_id);
static void sqa_on_hw_pending_cmd_timeout(struct scst_cmd *scst_cmd);
static uint16_t sqa_get_scsi_transport_version(struct scst_tgt *scst_tgt);
static uint16_t sqa_get_phys_transport_version(struct scst_tgt *scst_tgt);
static int sqa_enable_tgt(struct scst_tgt *tgt, bool enable);
static bool sqa_is_tgt_enabled(struct scst_tgt *tgt);
static ssize_t sqa_add_vtarget(const char *target_name, char *params);
static ssize_t sqa_del_vtarget(const char *target_name);
/* Definitions for helper functions. */
static int sqa_get_target_name(uint8_t *wwn, char **ppwwn_name);
static int sqa_parse_wwn(const char *ns, u64 *nm);
/* Variables and function definitions for sysfs control plane. */
static ssize_t sqa_version_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static struct kobj_attribute sqa_version_attr =
__ATTR(version, S_IRUGO, sqa_version_show, NULL);
static const struct attribute *sqa_attrs[] = {
&sqa_version_attr.attr,
NULL,
};
static ssize_t sqa_show_expl_conf_enabled(struct kobject *kobj,
struct kobj_attribute *attr,
char *buffer);
static ssize_t sqa_store_expl_conf_enabled(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size);
static struct kobj_attribute sqa_expl_conf_attr =
__ATTR(explicit_confirmation, S_IRUGO|S_IWUSR,
sqa_show_expl_conf_enabled, sqa_store_expl_conf_enabled);
static ssize_t sqa_abort_isp_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size);
static struct kobj_attribute sqa_abort_isp_attr =
__ATTR(abort_isp, S_IWUSR, NULL, sqa_abort_isp_store);
static ssize_t sqa_hw_target_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static struct kobj_attribute sqa_hw_target_attr =
__ATTR(hw_target, S_IRUGO, sqa_hw_target_show, NULL);
static ssize_t sqa_node_name_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
#if EXCLUDED
static struct kobj_attribute sqa_vp_node_name_attr =
__ATTR(node_name, S_IRUGO, sqa_node_name_show, NULL);
#endif
static ssize_t sqa_node_name_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size);
static struct kobj_attribute sqa_hw_node_name_attr =
__ATTR(node_name, S_IRUGO|S_IWUSR, sqa_node_name_show,
sqa_node_name_store);
#if EXCLUDED
static ssize_t sqa_vp_parent_host_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static struct kobj_attribute sqa_vp_parent_host_attr =
__ATTR(parent_host, S_IRUGO, sqa_vp_parent_host_show, NULL);
#endif
static const struct attribute *sqa_tgt_attrs[] = {
&sqa_expl_conf_attr.attr,
&sqa_abort_isp_attr.attr,
NULL,
};
/*
* Statically scoped variables.
*/
#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
#define trace_flag sqa_trace_flag
static unsigned long sqa_trace_flag = SQA_DEFAULT_LOG_FLAGS;
#endif
//#define CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
static enum scst_exec_context scst_work_context = SCST_CONTEXT_THREAD;
#else
static enum scst_exec_context scst_work_context = SCST_CONTEXT_TASKLET;
#endif
static struct cmd_state_name {
uint8_t state;
char *str;
} cmd_str[] = {
{0xff, "unknown"},
{QLA_TGT_STATE_NEW, "new"},
{QLA_TGT_STATE_NEED_DATA, "NeedData"},
{QLA_TGT_STATE_DATA_IN, "DataIn"},
{QLA_TGT_STATE_PROCESSED, "Processed"},
};
static char *cmdstate_to_str(uint8_t state)
{
int i;
struct cmd_state_name *e;
for (i = 1; i < ARRAY_SIZE(cmd_str); i++) {
e = cmd_str + i;
if (state == e->state)
return e->str;
}
return cmd_str[0].str; /* unknown */
}
#if QLA_ENABLE_PI
static const int qla_tgt_supported_dif_block_size[] = {
512,
0, /* null terminated */
};
static inline void qla_tgt_set_cmd_prot_op(struct qla_tgt_cmd *cmd,
uint8_t xmit_rsp)
{
struct scst_cmd *scst_cmd = cmd->scst_cmd;
int dir = scst_cmd_get_data_direction(scst_cmd);
int action = scst_get_dif_action(
scst_get_tgt_dif_actions(scst_cmd->cmd_dif_actions));
if (action == SCST_DIF_ACTION_NONE) {
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
return;
}
if (scst_cmd->status && (dir == SCST_DATA_NONE)) {
/* scst_set_cmd_error() will over ride the data direction
* in the error case at the back end. Need to figure out
* the
*/
dir = scst_cmd_get_expected_data_direction(scst_cmd);
}
switch (action) {
case SCST_DIF_ACTION_STRIP:
switch (dir) {
case SCST_DATA_READ:
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_STRIP;
break;
case SCST_DATA_WRITE:
cmd->se_cmd.prot_op = TARGET_PROT_DIN_STRIP;
break;
case SCST_DATA_BIDI:
if (xmit_rsp)
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_STRIP;
else
cmd->se_cmd.prot_op = TARGET_PROT_DIN_STRIP;
break;
case SCST_DATA_NONE:
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
default:
EXTRACHECKS_BUG_ON(dir);
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
}
break;
case SCST_DIF_ACTION_INSERT:
switch (dir) {
case SCST_DATA_READ:
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_INSERT;
break;
case SCST_DATA_WRITE:
cmd->se_cmd.prot_op = TARGET_PROT_DIN_INSERT;
break;
case SCST_DATA_BIDI:
if (xmit_rsp)
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_INSERT;
else
cmd->se_cmd.prot_op = TARGET_PROT_DIN_INSERT;
break;
case SCST_DATA_NONE:
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
default:
EXTRACHECKS_BUG_ON(dir);
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
}
break;
case SCST_DIF_ACTION_PASS_CHECK:
switch (dir) {
case SCST_DATA_READ:
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_PASS;
break;
case SCST_DATA_WRITE:
cmd->se_cmd.prot_op = TARGET_PROT_DIN_PASS;
break;
case SCST_DATA_BIDI:
if (xmit_rsp) {
cmd->se_cmd.prot_op = TARGET_PROT_DOUT_PASS;
} else {
cmd->se_cmd.prot_op = TARGET_PROT_DIN_PASS;
}
break;
case SCST_DATA_NONE:
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
default:
EXTRACHECKS_BUG_ON(dir);
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
}
break;
case SCST_DIF_ACTION_PASS: // nocheck
switch (dir) {
case SCST_DATA_READ:
case SCST_DATA_WRITE:
case SCST_DATA_BIDI:
//cmd->prot_op = TARGET_PROT_PASS_NOCHECK; TODO
//cmd->se_cmd.prot_op = TARGET_PROT_PASS_NOCHECK; TODO
break;
case SCST_DATA_NONE:
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
default:
EXTRACHECKS_BUG_ON(dir);
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
break;
}
break;
default:
cmd->se_cmd.prot_op = TARGET_PROT_NORMAL;
EXTRACHECKS_BUG_ON(action);
break;
}
}
#endif /* QLA_ENABLE_PI */
static void sqa_qla2xxx_rel_cmd(struct qla_tgt_cmd *cmd)
{
struct fc_port *sess = cmd->sess;
struct sqa_scst_tgt *sqa_tgt = sess->vha->vha_tgt.target_lport_ptr;
#if QLT_USE_PERCPU_IDA
percpu_ida_free(&sqa_tgt->tgt_tag_pool, cmd->se_cmd.map_tag);
#elif QLT_USE_SBITMAP
sbitmap_queue_clear(&sqa_tgt->tgt_tag_pool, cmd->se_cmd.map_tag,
cmd->se_cmd.map_cpu);
#else
clear_bit(cmd->map_tag, sqa_tgt->tgt_tag_pool);
#endif
}
static struct qla_tgt_cmd *sqa_qla2xxx_get_cmd(struct fc_port *sess)
{
struct sqa_scst_tgt *sqa_tgt =
(struct sqa_scst_tgt *)sess->vha->vha_tgt.target_lport_ptr;
struct qla_tgt_cmd *cmd;
int tag = -ENOENT;
#if QLT_USE_PERCPU_IDA
tag = percpu_ida_alloc(&sqa_tgt->tgt_tag_pool, TASK_RUNNING);
#elif QLT_USE_SBITMAP
int cpu;
tag = sbitmap_queue_get(&sqa_tgt->tgt_tag_pool, &cpu);
#else
for (;;) {
tag = find_first_zero_bit(sqa_tgt->tgt_tag_pool,
sqa_tgt->tag_num);
if (test_and_set_bit(tag, sqa_tgt->tgt_tag_pool) == 0)
break;
if (tag >= sqa_tgt->tag_num) {
tag = -ENOENT;
break;
}
}
#endif
if (tag < 0)
return NULL;
cmd = &((struct qla_tgt_cmd *)sqa_tgt->tgt_cmd_map)[tag];
memset(cmd, 0, sizeof(struct qla_tgt_cmd));
#if QLT_USE_PERCPU_IDA || QLT_USE_SBITMAP
cmd->se_cmd.map_tag = tag;
#else
cmd->map_tag = tag;
#endif
#if QLT_USE_SBITMAP
cmd->se_cmd.map_cpu = cpu;
#endif
cmd->sess = sess;
cmd->vha = sess->vha;
return cmd;
}
static DEFINE_MUTEX(sqa_mutex);
/* Stub function for unimplemented functionality. */
static inline int scst_cmd_get_ppl_offset(struct scst_cmd *scst_cmd)
{
return 0;
}
static int sqa_qla2xxx_handle_cmd(scsi_qla_host_t *vha,
struct qla_tgt_cmd *cmd, unsigned char *cdb,
uint32_t data_length, int task_codes,
int data_dir, int bidi)
{
int res = 0;
struct fc_port *sess = cmd->sess;
struct scst_session *scst_sess = (struct scst_session *)
sess->se_sess->fabric_sess_ptr;
struct atio_from_isp *atio = &cmd->atio;
scst_data_direction dir;
TRACE_ENTRY();
TRACE_DBG("sqatgt(%ld/%d): Handling command: length=%d, fcp_task_attr=%d, direction=%d, bidirectional=%d lun=%llx cdb=%x tag=%d cmd %p ulpcmd %p\n",
vha->host_no, vha->vp_idx, data_length, task_codes,
data_dir, bidi, cmd->unpacked_lun,
atio->u.isp24.fcp_cmnd.cdb[0],
atio->u.isp24.exchange_addr, cmd, cmd->scst_cmd);
cmd->scst_cmd = scst_rx_cmd(scst_sess,
(uint8_t *)&atio->u.isp24.fcp_cmnd.lun,
sizeof(atio->u.isp24.fcp_cmnd.lun),
atio->u.isp24.fcp_cmnd.cdb,
sizeof(atio->u.isp24.fcp_cmnd.cdb) +
(atio->u.isp24.fcp_cmnd.add_cdb_len * 4),
SCST_ATOMIC);
if (cmd->scst_cmd == NULL) {
PRINT_ERROR("sqatgt(%ld/%d): scst_rx_cmd function failed.",
vha->host_no, vha->vp_idx);
res = -EFAULT;
goto out;
}
scst_cmd_set_tag(cmd->scst_cmd,
le32_to_cpu(atio->u.isp24.exchange_addr));
scst_cmd_set_tgt_priv(cmd->scst_cmd, cmd);
if (atio->u.isp24.fcp_cmnd.rddata && atio->u.isp24.fcp_cmnd.wrdata)
dir = SCST_DATA_BIDI;
else if (atio->u.isp24.fcp_cmnd.rddata)
dir = SCST_DATA_READ;
else if (atio->u.isp24.fcp_cmnd.wrdata)
dir = SCST_DATA_WRITE;
else
dir = SCST_DATA_NONE;
scst_cmd_set_expected(cmd->scst_cmd, dir, data_length);
/* task_code fr arg list is based on TCM #define. */
switch (atio->u.isp24.fcp_cmnd.task_attr) {
case ATIO_SIMPLE_QUEUE:
scst_cmd_set_queue_type(cmd->scst_cmd, SCST_CMD_QUEUE_SIMPLE);
break;
case ATIO_HEAD_OF_QUEUE:
scst_cmd_set_queue_type(cmd->scst_cmd,
SCST_CMD_QUEUE_HEAD_OF_QUEUE);
break;
case ATIO_ORDERED_QUEUE:
scst_cmd_set_queue_type(cmd->scst_cmd,
SCST_CMD_QUEUE_ORDERED);
break;
case ATIO_ACA_QUEUE:
scst_cmd_set_queue_type(cmd->scst_cmd,
SCST_CMD_QUEUE_ACA);
break;
case ATIO_UNTAGGED:
scst_cmd_set_queue_type(cmd->scst_cmd,
SCST_CMD_QUEUE_UNTAGGED);
break;
default:
PRINT_ERROR("sqatgt(%ld/%d): unknown task code %x, use ORDERED instead.",
vha->host_no, vha->vp_idx,
atio->u.isp24.fcp_cmnd.task_attr);
scst_cmd_set_queue_type(cmd->scst_cmd,
SCST_CMD_QUEUE_ORDERED);
break;
}
TRACE(TRACE_SCSI,
"sqatgt(%ld/%d): START Command=%p tag=%d, queue type=%x",
vha->host_no, vha->vp_idx, cmd, cmd->atio.u.isp24.exchange_addr,
scst_cmd_get_queue_type(cmd->scst_cmd));
/* we're being call by wq, so do direct */
scst_cmd_init_done(cmd->scst_cmd, SCST_CONTEXT_DIRECT);
out:
TRACE_EXIT_RES(res);
return res;
}
static void sqa_qla2xxx_handle_data(struct qla_tgt_cmd *cmd)
{
struct scst_cmd *scst_cmd = cmd->scst_cmd;
int rx_status;
unsigned long flags;
TRACE_ENTRY();
spin_lock_irqsave(&cmd->cmd_lock, flags);
if (cmd->aborted) {
spin_unlock_irqrestore(&cmd->cmd_lock, flags);
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_sense_internal_failure));
scst_rx_data(scst_cmd, SCST_RX_STATUS_ERROR_SENSE_SET,
SCST_CONTEXT_THREAD);
return;
}
spin_unlock_irqrestore(&cmd->cmd_lock, flags);
if (cmd->write_data_transferred) {
rx_status = SCST_RX_STATUS_SUCCESS;
} else {
rx_status = SCST_RX_STATUS_ERROR_SENSE_SET;
switch (cmd->dif_err_code) {
case DIF_ERR_GRD:
scst_dif_acc_guard_check_failed_tgt(scst_cmd);
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_logical_block_guard_check_failed));
break;
case DIF_ERR_REF:
scst_dif_acc_ref_check_failed_tgt(scst_cmd);
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_logical_block_ref_tag_check_failed));
break;
case DIF_ERR_APP:
scst_dif_acc_app_check_failed_tgt(scst_cmd);
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_logical_block_app_tag_check_failed));
break;
case DIF_ERR_NONE:
default:
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_sense_write_error));
break;
}
}
/* Avoid using SCST_CONTEXT_DIRECT, the caller might hold qpair
* lock. scst_rx_data(scst_context_direct) might call qlt_xmit_respond
* which will "re-grab" the qpair lock, creating a deadlock.
*/
scst_rx_data(scst_cmd, rx_status, scst_work_context);
TRACE_EXIT();
}
static int sqa_qla2xxx_handle_tmr(struct qla_tgt_mgmt_cmd *mcmd, u64 lun,
uint16_t tmr_func, uint32_t tag)
{
int res = 0, rc = -1, lun_size = sizeof(lun);
struct fc_port *sess = mcmd->sess;
struct scst_session *scst_sess = (struct scst_session *)
sess->se_sess->fabric_sess_ptr;
struct scsi_lun sl;
TRACE_ENTRY();
TRACE(TRACE_MGMT,
"sqatgt(%ld/%d): Received task management cmd: lun=%llu, type=%d, tag=%d\n",
sess->vha->host_no, sess->vha->vp_idx, lun, tmr_func, tag);
mcmd->tmr_func = tmr_func;
memset(&sl, 0, sizeof(sl));
int_to_scsilun(lun, &sl);
/*
* Call into SCST target core based on task management function
* type.
*/
switch (tmr_func) {
case QLA_TGT_CLEAR_ACA: //TMR_CLEAR_ACA:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d) CLEAR_ACA received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_CLEAR_ACA,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_TARGET_RESET: //TMR_TARGET_WARM_RESET:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d) TARGET_RESET received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_TARGET_RESET,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_LUN_RESET: //TMR_LUN_RESET:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d) LUN_RESET received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_LUN_RESET,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_CLEAR_TS: //TMR_CLEAR_TASK_SET:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d) CLEAR_TS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_CLEAR_TASK_SET,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_2G_ABORT_TASK:// TMR_ABORT_TASK:
case QLA_TGT_ABTS:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): TMR_ABORT_TASK received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_tag(scst_sess, SCST_ABORT_TASK, tag,
SCST_ATOMIC, mcmd);
break;
case QLA_TGT_ABORT_TS:// TMR_ABORT_TASK_SET:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d) ABORT_TS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_ABORT_TASK_SET,
&lun, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_ABORT_ALL: // TMR_TARGET_COLD_RESET:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): ABORT_ALL_TASKS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_ABORT_ALL_TASKS,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_ABORT_ALL_SESS: //TMR_TARGET_ABORT_ALL:
TRACE(TRACE_MGMT,
"sqatgt(%ld/%d): ABORT_ALL_TASKS_SESS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_ABORT_ALL_TASKS_SESS,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_NEXUS_LOSS_SESS: //TMR_NEXUS_LOSS_SESSION:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): NEXUS_LOSS_SESS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_NEXUS_LOSS_SESS,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
case QLA_TGT_NEXUS_LOSS: //TMR_NEXUS_LOSS:
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): NEXUS_LOSS received.",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx);
rc = scst_rx_mgmt_fn_lun(scst_sess, SCST_NEXUS_LOSS,
&sl, lun_size, SCST_ATOMIC, mcmd);
break;
default:
PRINT_ERROR("sqatgt(%ld/%d): Unknown task mgmt fn=0x%x",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx,
tmr_func);
res = -EFAULT;
goto done;
}
if (rc != 0) {
PRINT_ERROR("sqatgt(%ld/%d) scst_rx_mgmt_fn_lun() failed: tmf=%d, lun=%llu, code=%d",
sess->tgt->vha->host_no, sess->tgt->vha->vp_idx,
tmr_func, lun, rc);
res = -EFAULT;
}
done:
TRACE_EXIT_RES(res);
return res;
}
static void sqa_qla2xxx_free_cmd(struct qla_tgt_cmd *cmd)
{
struct scst_cmd *scst_cmd = cmd->scst_cmd;
TRACE_ENTRY();
cmd->trc_flags |= TRC_CMD_DONE;
scst_tgt_cmd_done(scst_cmd, scst_work_context);
TRACE_EXIT();
}
static void sqa_qla2xxx_free_mcmd(struct qla_tgt_mgmt_cmd *mcmd)
{
TRACE_ENTRY();
qlt_free_mcmd(mcmd);
TRACE_EXIT();
}
static void sqa_free_session_done(struct scst_session *scst_sess)
{
struct fc_port *fcport = scst_sess_get_tgt_priv(scst_sess);
if (fcport->unreg_done)
complete(fcport->unreg_done);
}
static void sqa_qla2xxx_free_session(struct fc_port *fcport)
{
struct scsi_qla_host *vha = fcport->vha;
struct se_session *se_sess = fcport->se_sess;
struct scst_session *scst_sess = se_sess->fabric_sess_ptr;
TRACE_ENTRY();
TRACE_MGMT_DBG("sqatgt(%ld/%d): Deleting session %8phC fcid=%3phC\n",
vha->host_no, vha->vp_idx, fcport->port_name, &fcport->d_id);
{
DECLARE_COMPLETION_ONSTACK(c);
fcport->unreg_done = &c;
scst_unregister_session(scst_sess, 1, sqa_free_session_done);
wait_for_completion(&c);
}
TRACE_MGMT_DBG("sqatgt(%ld/%d): Unregister completed %8phC done\n",
vha->host_no, vha->vp_idx, fcport->port_name);
kfree(se_sess);
TRACE_EXIT();
}
static void sqa_qla2xxx_update_sess(struct fc_port *sess, port_id_t s_id,
uint16_t loop_id, bool conf_compl_supported)
{
TRACE_ENTRY();
TRACE_EXIT();
}
static int sqa_qla2xxx_check_initiator_node_acl(scsi_qla_host_t *vha,
unsigned char *fc_wwpn, struct fc_port *fcport)
{
int res = -ENOMEM;
char *ini_name;
struct se_session *se_sess;
struct scst_session *scst_sess;
unsigned long flags;
struct sqa_scst_tgt *sqa_tgt;
TRACE_ENTRY();
PRINT_INFO("sqatgt(%ld/%d): Registering initiator: pwwn=%8phC",
vha->host_no, vha->vp_idx, fc_wwpn);
se_sess = kzalloc(sizeof(*se_sess), GFP_KERNEL);
if (!se_sess)
return res;
/* Create the SCST session. */
ini_name = kasprintf(GFP_KERNEL, "%8phC", fc_wwpn);
if (!ini_name)
goto free_sess;
memcpy(fcport->port_name, fc_wwpn, sizeof(fcport->port_name));
sqa_tgt = vha->vha_tgt.target_lport_ptr;
res = -ESRCH;
scst_sess = scst_register_session(sqa_tgt->scst_tgt, 0,
ini_name, fcport, NULL, NULL);
if (scst_sess == NULL) {
PRINT_ERROR("sqatgt(%ld/%d): SCST session registration failed, all commands will be refused: pwwn=%s",
vha->host_no, vha->vp_idx, ini_name);
goto free_sess;
}
res = 0;
spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags);
se_sess->fabric_sess_ptr = scst_sess;
fcport->se_sess = se_sess;
spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags);
out:
kfree(ini_name);
TRACE_EXIT_RES(res);
return res;
free_sess:
kfree(se_sess);
goto out;
}
static struct fc_port *sqa_qla2xxx_find_sess_by_s_id(scsi_qla_host_t *vha,
be_id_t s_id)
{
struct fc_port *sess;
TRACE_ENTRY();
#if 0
TRACE_DBG("sqatgt(%ld/%d): Looking up session for fcid: 0x%02x%02x%02x\n",
vha->host_no, vha->vp_idx, s_id[0], s_id[1], s_id[2]);
#endif
list_for_each_entry(sess, &vha->vp_fcports, list) {
if (sess->d_id.b.al_pa == s_id.al_pa &&
sess->d_id.b.area == s_id.area &&
sess->d_id.b.domain == s_id.domain &&
!sess->deleted && sess->se_sess)
return sess;
}
TRACE_EXIT();
return NULL;
}
static struct fc_port *sqa_qla2xxx_find_sess_by_loop_id(scsi_qla_host_t *vha,
const uint16_t loop_id)
{
struct fc_port *sess;
TRACE_ENTRY();
TRACE_DBG("sqatgt(%ld/%d): Looking up session for loop id: 0x%04x\n",
vha->host_no, vha->vp_idx, loop_id);
list_for_each_entry(sess, &vha->vp_fcports, list) {
if (loop_id == sess->loop_id && !sess->deleted && sess->se_sess)
return sess;
}
TRACE_EXIT();
return NULL;
}
static void sqa_qla2xxx_clear_nacl_from_fcport_map(struct fc_port *sess)
{
TRACE_ENTRY();
TRACE_EXIT();
}
static void sqa_qla2xxx_release_sess(struct kref *kref)
{
struct fc_port *fcport = container_of(kref, struct fc_port, sess_kref);
qlt_unreg_sess(fcport);
}
static void sqa_qla2xxx_put_sess(struct fc_port *sess)
{
TRACE_ENTRY();
kref_put(&sess->sess_kref, sqa_qla2xxx_release_sess);
TRACE_EXIT();
}
static int sqa_close_session(struct scst_session *scst_sess)
{
struct fc_port *fcport = scst_sess_get_tgt_priv(scst_sess);
unsigned long flags;
struct qla_hw_data *ha = fcport->vha->hw;
fcport->explicit_logout = 1;
spin_lock_irqsave(&ha->tgt.sess_lock, flags);
sqa_qla2xxx_put_sess(fcport);
spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
return 0;
}
static void sqa_qla2xxx_shutdown_sess(struct fc_port *sess)
{
TRACE_ENTRY();
TRACE_EXIT();
}
/*
* sysfs functions from here forward.
*/
static ssize_t sqa_version_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
sprintf(buf, "INTERFACE=%s\nSCST=%s\nQLOGIC=%s\n", SQA_VERSION,
SCST_VERSION_NAME, QLA2XXX_VERSION);
#ifdef CONFIG_SCST_EXTRACHECKS
strcat(buf, "EXTRACHECKS\n");
#endif
#ifdef CONFIG_SCST_TRACING
strcat(buf, "TRACING\n");
#endif
#ifdef CONFIG_SCST_DEBUG
strcat(buf, "DEBUG\n");
#endif
#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
strcat(buf, "QLA_TGT_DEBUG_WORK_IN_THREAD\n");
#endif
#ifdef CONFIG_QLA_TGT_DEBUG_SRR
strcat(buf, "DEBUG_SRR\n");
#endif
TRACE_EXIT();
return strlen(buf);
}
static ssize_t sqa_hw_target_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", 1);
}
static ssize_t sqa_node_name_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
ssize_t res = -ENOMEM;
char *wwn;
uint8_t *node_name;
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
mutex_lock(&sqa_mutex);
if (!sqa_tgt || !sqa_tgt->qla_tgt) {
mutex_unlock(&sqa_mutex);
goto out;
}
mutex_unlock(&sqa_mutex);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
if (qla_tgt_mode_enabled(tgt->vha) || qla_dual_mode_enabled(tgt->vha) ||
!ha->tgt.node_name_set)
node_name = tgt->vha->node_name;
else
node_name = ha->tgt.tgt_node_name;
res = sqa_get_target_name(node_name, &wwn);
if (res != 0)
goto out;
res = sprintf(buf, "%s\n", wwn);
if (ha->tgt.node_name_set)
res += sprintf(&buf[res], "%s\n", SCST_SYSFS_KEY_MARK);
kfree(wwn);
out:
return res;
}
static ssize_t sqa_node_name_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
u64 node_name, old_node_name;
int res;
TRACE_ENTRY();
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
if (size == 0)
goto out_default;
res = sqa_parse_wwn(buffer, &node_name);
if (res != 0) {
if ((buffer[0] == '\0') || (buffer[0] == '\n'))
goto out_default;
PRINT_ERROR("sqatgt(%ld/%d) Wrong node name",
tgt->vha->host_no, tgt->vha->vp_idx);
goto out;
}
old_node_name = wwn_to_u64(tgt->vha->node_name);
if (old_node_name == node_name)
goto out_success;
u64_to_wwn(node_name, ha->tgt.tgt_node_name);
ha->tgt.node_name_set = 1;
abort:
/*
* A substitute is needed for the following code sequence which
* was previously used within the following conditional:
*
* set_bit(ISP_ABORT_NEEDED, &tgt->vha->dpc_flags);
* qla2x00_wait_for_hba_online(tgt->vha);
*
* The substitute used will be to gracefully shutdown the
* target with subsequent restart.
*/
if (qla_tgt_mode_enabled(tgt->vha) ||
qla_dual_mode_enabled(tgt->vha)) {
qlt_stop_phase1(tgt);
qlt_stop_phase2(tgt);
qlt_enable_vha(tgt->vha);
}
out_success:
res = size;
out:
TRACE_EXIT_RES(res);
return res;
out_default:
ha->tgt.node_name_set = 0;
goto abort;
}
#if EXCLUDED
static ssize_t sqa_vp_parent_host_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
ssize_t res;
char *wwn;
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
res = sqa_get_target_name(tgt->vha->port_name, &wwn);
if (res != 0)
goto out;
res = sprintf(buf, "%s\n%s\n", wwn, SCST_SYSFS_KEY_MARK);
kfree(wwn);
out:
return res;
}
#endif
static ssize_t sqa_show_expl_conf_enabled(struct kobject *kobj,
struct kobj_attribute *attr,
char *buffer)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
ssize_t size;
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
size = scnprintf(buffer, PAGE_SIZE, "%d\n%s",
ha->base_qpair->enable_explicit_conf,
ha->base_qpair->enable_explicit_conf ?
SCST_SYSFS_KEY_MARK "\n" : "");
return size;
}
static ssize_t sqa_store_expl_conf_enabled(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
struct scsi_qla_host *vha;
unsigned long flags;
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
vha = tgt->vha;
spin_lock_irqsave(&ha->hardware_lock, flags);
switch (buffer[0]) {
case '0':
QLA_DIS_CONF(ha);
PRINT_INFO("sqatgt(%ld/%d) explicit conformations disabled",
vha->host_no, vha->vp_idx);
break;
case '1':
QLA_ENA_CONF(ha)
PRINT_INFO("sqatgt(%ld/%d) explicit conformations enabled",
vha->host_no, vha->vp_idx);
break;
default:
PRINT_ERROR("sqatgt(%ld/%d): Requested action not understood: %s",
vha->host_no, vha->vp_idx, buffer);
break;
}
spin_unlock_irqrestore(&ha->hardware_lock, flags);
return size;
}
static ssize_t sqa_abort_isp_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buffer, size_t size)
{
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
TRACE_ENTRY();
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
PRINT_INFO("sqatgt(%ld/%d) ISP abort not implemented.",
tgt->vha->host_no, tgt->vha->vp_idx);
TRACE_EXIT();
return -ENOSYS;
#if EXCLUDED
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct qla_hw_data *ha;
scst_tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
ha = tgt->ha;
PRINT_INFO("sqatgt(%ld/%d) Aborting ISP.", tgt->vha->host_no,
tgt->vha->vp_idx);
set_bit(ISP_ABORT_NEEDED, &tgt->vha->dpc_flags);
WARN_ON_ONCE(qla2x00_wait_for_hba_online(tgt->vha) != QLA_SUCCESS);
return size;
#endif
}
static int sqa_get_target_name(uint8_t *wwn, char **ppwwn_name)
{
*ppwwn_name = kasprintf(GFP_KERNEL,
"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
wwn[0], wwn[1], wwn[2], wwn[3],
wwn[4], wwn[5], wwn[6], wwn[7]);
return *ppwwn_name ? 0 : -ENOMEM;
}
static int sqa_parse_wwn(const char *ns, u64 *nm)
{
unsigned int i, j;
u8 wwn[8];
/* validate we have enough characters for WWPN */
if (strnlen(ns, 23) != 23)
return -EINVAL;
memset(wwn, 0, sizeof(wwn));
/* Validate and store the new name */
for (i = 0, j = 0; i < 16; i++) {
if ((*ns >= 'a') && (*ns <= 'f'))
j = ((j << 4) | ((*ns++ - 'a') + 10));
else if ((*ns >= 'A') && (*ns <= 'F'))
j = ((j << 4) | ((*ns++ - 'A') + 10));
else if ((*ns >= '0') && (*ns <= '9'))
j = ((j << 4) | (*ns++ - '0'));
else
return -EINVAL;
if (i % 2) {
wwn[i/2] = j & 0xff;
j = 0;
if ((i < 15) && (':' != *ns++))
return -EINVAL;
}
}
*nm = wwn_to_u64(wwn);
return 0;
}
/*
* The following structure definition provides descriptive parameeters
* and callbacks which will be used by the SCST target core to
* communicate with this target interface driver.
*/
static struct scst_tgt_template sqa_scst_template = {
.name = "qla2x00t",
.sg_tablesize = 0,
.use_clustering = 1,
#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
.xmit_response_atomic = 0,
.rdy_to_xfer_atomic = 0,
#else
.xmit_response_atomic = 1,
.rdy_to_xfer_atomic = 1,
#endif
#if QLA_ENABLE_PI
/* diff cap for individual adapter is set during sqa_qla2xxx_add_target */
.dif_supported = 0,
.hw_dif_type1_supported = 0,
.hw_dif_type2_supported = 0,
.hw_dif_type3_supported = 0,
.hw_dif_ip_supported = 0,
.hw_dif_same_sg_layout_required = 0,
#endif
.max_hw_pending_time = SQA_MAX_HW_PENDING_TIME,
.release = sqa_target_release,
.xmit_response = sqa_xmit_response,
.rdy_to_xfer = sqa_rdy_to_xfer,
.on_free_cmd = sqa_on_free_cmd,
.task_mgmt_fn_done = sqa_task_mgmt_fn_done,
.close_session = sqa_close_session,
.get_initiator_port_transport_id = sqa_get_initiator_port_transport_id,
.get_scsi_transport_version = sqa_get_scsi_transport_version,
.get_phys_transport_version = sqa_get_phys_transport_version,
.on_hw_pending_cmd_timeout = sqa_on_hw_pending_cmd_timeout,
.enable_target = sqa_enable_tgt,
.is_target_enabled = sqa_is_tgt_enabled,
.add_target = sqa_add_vtarget,
.del_target = sqa_del_vtarget,
.add_target_parameters = "node_name, parent_host",
.tgtt_attrs = sqa_attrs,
.tgt_attrs = sqa_tgt_attrs,
#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
.default_trace_flags = SQA_DEFAULT_LOG_FLAGS,
.trace_flags = &trace_flag,
#endif
};
/* call must hold sqa_mutex */
static int sqa_init_scst_tgt(struct scsi_qla_host *vha)
{
char *pwwn = NULL;
int res;
struct scst_tgt *scst_tgt;
struct sqa_scst_tgt *sqa_tgt;
uint tag_num = vha->hw->orig_fw_xcb_count ? vha->hw->orig_fw_xcb_count :
SQA_DEFAULT_TAGS;
u16 tag_size = sizeof(struct qla_tgt_cmd);
TRACE_ENTRY();
res = sqa_get_target_name(vha->port_name, &pwwn);
if (res)
goto done;
sqa_tgt = kzalloc(sizeof(*sqa_tgt), GFP_KERNEL);
if (!sqa_tgt) {
PRINT_ERROR("sqatgt(%ld/%d): alloc sqa_tgt failed",
vha->host_no, vha->vp_idx);
res = -ENOMEM;
goto done;
}
sqa_tgt->tgt_cmd_map = kvcalloc(tag_num, tag_size, GFP_KERNEL);
if (!sqa_tgt->tgt_cmd_map) {
PRINT_ERROR("sqatgt(%ld/%d): alloc tgt_cmd_map failed",
vha->host_no, vha->vp_idx);
kfree(sqa_tgt);
res = -ENOMEM;
goto done;
}
#if QLT_USE_PERCPU_IDA
res = percpu_ida_init(&sqa_tgt->tgt_tag_pool, tag_num);
#elif QLT_USE_SBITMAP
res = sbitmap_queue_init_node(&sqa_tgt->tgt_tag_pool, tag_num, -1,
false, GFP_KERNEL, NUMA_NO_NODE);
#else
sqa_tgt->tag_num = tag_num;
sqa_tgt->tgt_tag_pool = kzalloc(BITS_TO_LONGS(tag_num), GFP_KERNEL);
res = IS_ERR(sqa_tgt->tgt_tag_pool) ? PTR_ERR(sqa_tgt->tgt_tag_pool) :
0;
#endif
if (res < 0) {
pr_err("Unable to init se_sess->tgt_tag_pool, tag_num: %u\n",
tag_num);
kvfree(sqa_tgt->tgt_cmd_map);
kfree(sqa_tgt);
goto done;
}
PRINT_INFO("sqatgt(%ld/%d): Registering target pwwn=%s",
vha->host_no, vha->vp_idx, pwwn);
scst_tgt = scst_register_target(&sqa_scst_template, pwwn);
if (!scst_tgt) {
PRINT_ERROR("sqatgt(%ld/%d): SCST target registration failed.",
vha->host_no, vha->vp_idx);
res = -ENOMEM;
kfree(sqa_tgt);
goto done;
}
#if QLA_ENABLE_PI
if (IS_T10_PI_CAPABLE(vha->hw)) {
scst_tgt_set_supported_dif_block_sizes(scst_tgt,
qla_tgt_supported_dif_block_size);
scst_tgt_set_dif_supported(scst_tgt, true);
scst_tgt_set_hw_dif_type1_supported(scst_tgt, true);
scst_tgt_set_hw_dif_type3_supported(scst_tgt, true);
}
#endif
INIT_LIST_HEAD(&sqa_tgt->list);
sqa_tgt->scst_tgt = scst_tgt;
sqa_tgt->qla_tgt = vha->vha_tgt.qla_tgt;
scst_tgt_set_tgt_priv(scst_tgt, sqa_tgt);
scst_tgt_set_sg_tablesize(scst_tgt,
vha->vha_tgt.qla_tgt->sg_tablesize);
res = sysfs_create_link(scst_sysfs_get_tgt_kobj(scst_tgt),
&vha->host->shost_dev.kobj, "host");
if (res != 0)
PRINT_ERROR("sqatgt(%ld/%d) Unable to create \"host\" link for, target=%s",
vha->host_no, vha->vp_idx,
scst_get_tgt_name(scst_tgt));
res = sysfs_create_file(scst_sysfs_get_tgt_kobj(scst_tgt),
&sqa_hw_target_attr.attr);
if (res != 0)
PRINT_ERROR("sqatgt(%ld/%dd) Unable to create \"hw_target\" file, target=%s",
vha->host_no, vha->vp_idx,
scst_get_tgt_name(scst_tgt));
res = sysfs_create_file(scst_sysfs_get_tgt_kobj(scst_tgt),
&sqa_hw_node_name_attr.attr);
if (res != 0)
PRINT_ERROR("sqatgt(%ld/%d) Unable to create \"node_name\" file for HW target %s",
vha->host_no, vha->vp_idx,
scst_get_tgt_name(scst_tgt));
list_add_tail(&sqa_tgt->list, &sqa_tgt_glist);
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): Registering target pwwn=%s scst_tgt %p sqa_tgt %p",
vha->host_no, vha->vp_idx, pwwn, scst_tgt, sqa_tgt);
done:
kfree(pwwn);
return res;
}
static void sqa_get_target_list(void)
{
struct qla_tgt *tgt;
struct scsi_qla_host *vha;
mutex_lock(&sqa_mutex);
list_for_each_entry(tgt, &qla_tgt_glist, tgt_list_entry) {
vha = tgt->vha;
sqa_init_scst_tgt(vha);
}
mutex_unlock(&sqa_mutex);
}
static void sqa_qla2xxx_add_target(struct scsi_qla_host *vha)
{
if (!vha) {
dump_stack();
return;
}
TRACE_ENTRY();
PRINT_INFO("sqatgt: add target %8phC", vha->port_name);
if (!vha->vha_tgt.target_lport_ptr) {
mutex_lock(&sqa_mutex);
sqa_init_scst_tgt(vha);
mutex_unlock(&sqa_mutex);
}
TRACE_EXIT();
}
static void sqa_qla2xxx_remove_target(struct scsi_qla_host *vha)
{
struct sqa_scst_tgt *sqa_tgt =
(struct sqa_scst_tgt *)vha->vha_tgt.target_lport_ptr;
TRACE_ENTRY();
TRACE(TRACE_MGMT, "Unregistering target for host %ld(%p)",
vha->host_no, vha);
scst_unregister_target(sqa_tgt->scst_tgt);
TRACE_EXIT();
}
/*
* Must be called under tgt_host_action_mutex or sqa_unreg_rwsem write
* locked.
*/
static int sqa_target_release(struct scst_tgt *scst_tgt)
{
struct sqa_scst_tgt *sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
struct qla_tgt *tgt = sqa_tgt->qla_tgt;
struct scsi_qla_host *vha = tgt->vha;
TRACE_ENTRY();
if (vha->vha_tgt.target_lport_ptr) {
if (!vha->vha_tgt.qla_tgt->tgt_stop &&
!vha->vha_tgt.qla_tgt->tgt_stopped) {
PRINT_INFO("sqatgt(%ld:%d: calling qlt_stop_phase1.\n",
vha->host_no, vha->vp_idx);
qlt_stop_phase1(vha->vha_tgt.qla_tgt);
}
if (vha->vha_tgt.qla_tgt->tgt_stop &&
!vha->vha_tgt.qla_tgt->tgt_stopped) {
PRINT_INFO("sqatgt(%ld/%d): calling qlt_stop_phase2.\n",
vha->host_no, vha->vp_idx);
qlt_stop_phase2(vha->vha_tgt.qla_tgt);
}
qlt_lport_deregister(tgt->vha);
}
scst_tgt_set_tgt_priv(scst_tgt, NULL);
mutex_lock(&sqa_mutex);
sqa_tgt->qla_tgt = NULL;
list_del(&sqa_tgt->list);
mutex_unlock(&sqa_mutex);
TRACE(TRACE_MGMT, "sqatgt(%ld/%d): Target release finished sqa_tgt %p",
vha->host_no, tgt->vha->vp_idx, sqa_tgt);
kfree(sqa_tgt);
TRACE_EXIT();
return 0;
}
static int sqa_xmit_response(struct scst_cmd *scst_cmd)
{
int xmit_type = QLA_TGT_XMIT_DATA, res, residual = 0;
int is_send_status = scst_cmd_get_is_send_status(scst_cmd);
struct qla_tgt_cmd *cmd;
TRACE_ENTRY();
cmd = scst_cmd_get_tgt_priv(scst_cmd);
if (scst_cmd_aborted_on_xmit(scst_cmd)) {
TRACE_MGMT_DBG("sqatgt(%ld/%d): CMD_ABORTED cmd[%p]",
cmd->vha->host_no, cmd->vha->vp_idx,
cmd);
qlt_abort_cmd(cmd);
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_ABORTED);
scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_DIRECT);
return SCST_TGT_RES_SUCCESS;
}
#ifdef CONFIG_SCST_EXTRACHECKS
BUG_ON(cmd->bufflen > 0 && !is_send_status);
#endif
#ifdef CONFIG_QLA_TGT_DEBUG_WORK_IN_THREAD
EXTRACHECKS_BUG_ON(scst_cmd_atomic(scst_cmd));
#endif
if (is_send_status) {
const u8 *const sense_buf = scst_cmd_get_sense_buffer(scst_cmd);
u16 len = scst_cmd_get_sense_buffer_len(scst_cmd);
xmit_type |= QLA_TGT_XMIT_STATUS;
if (QLA_TGT_SENSE_VALID(sense_buf)) {
if (len > TRANSPORT_SENSE_BUFFER || len == 0)
len = TRANSPORT_SENSE_BUFFER;
memcpy(cmd->sense_buffer, sense_buf, len);
}
}
cmd->bufflen = scst_cmd_get_adjusted_resp_data_len(scst_cmd);
cmd->sg = scst_cmd_get_sg(scst_cmd);
cmd->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
cmd->dma_data_direction =
scst_to_tgt_dma_dir(scst_cmd_get_data_direction(scst_cmd));
cmd->offset = scst_cmd_get_ppl_offset(scst_cmd);
cmd->scsi_status = scst_cmd_get_status(scst_cmd);
cmd->cdb = (unsigned char *) scst_cmd_get_cdb(scst_cmd);
cmd->lba = scst_cmd_get_lba(scst_cmd);
cmd->trc_flags |= TRC_XMIT_STATUS;
#if QLA_ENABLE_PI
if (scst_get_tgt_dif_actions(scst_cmd->cmd_dif_actions)) {
cmd->blk_sz = scst_cmd_get_block_size(scst_cmd);
cmd->prot_sg_cnt = scst_cmd->dif_sg_cnt;
cmd->prot_sg = scst_cmd->dif_sg;
cmd->se_cmd.prot_type = scst_cmd_get_dif_prot_type(scst_cmd);
qla_tgt_set_cmd_prot_op(cmd, true);
TRACE_DBG("cmd[%p] ulpcmd[%p] dif_actions=0x%x, cdb=0x%x, prot_sg[%p] prot_sg_cnt[%x], prot_type[%x] prot_op[%x]",
cmd, cmd->scst_cmd, scst_cmd->cmd_dif_actions,
scst_cmd->cdb_buf[0], cmd->prot_sg, cmd->prot_sg_cnt,
cmd->se_cmd.prot_type, cmd->se_cmd.prot_op);
if ((cmd->se_cmd.prot_op == TARGET_PROT_DIN_INSERT) &&
(!cmd->prot_sg_cnt || !cmd->prot_sg)) {
PRINT_ERROR("qla2x00t(%ld): %s: DIN Insert w/out DIF buf[%p:%d]",
cmd->vha->host_no, __func__, cmd->prot_sg,
cmd->prot_sg_cnt);
}
}
#endif
/* SCST residual is opposite of qla
* scst : + = under, - = over
*/
if (scst_get_resid(scst_cmd, &residual, NULL)) {
TRACE_DBG("sqatgt(%ld/%d): Have residual, count=%d",
cmd->vha->host_no, cmd->vha->vp_idx, residual);
if (residual > 0)
cmd->se_cmd.se_cmd_flags |= SCF_UNDERFLOW_BIT;
if (residual < 0) {
cmd->se_cmd.se_cmd_flags |= SCF_OVERFLOW_BIT;
residual = -residual;
}
cmd->se_cmd.residual_count = residual;
}
TRACE_DBG("sqatgt(%ld/%d): is_send_status=%x, bufflen=%d, sg_cnt=%d, flipped dma_direction=%d resid=%d",
cmd->vha->host_no, cmd->vha->vp_idx, is_send_status,
cmd->bufflen, cmd->sg_cnt, cmd->dma_data_direction,
cmd->se_cmd.residual_count);
res = qlt_xmit_response(cmd, xmit_type, scst_cmd_get_status(scst_cmd));
switch (res) {
case 0:
res = SCST_TGT_RES_SUCCESS;
break;
case -EAGAIN:
res = SCST_TGT_RES_QUEUE_FULL;
break;
default:
res = SCST_TGT_RES_FATAL_ERROR;
break;
}
TRACE_EXIT();
return res;
}
static int sqa_rdy_to_xfer(struct scst_cmd *scst_cmd)
{
int res;
struct qla_tgt_cmd *cmd;
TRACE_ENTRY();
cmd = scst_cmd_get_tgt_priv(scst_cmd);
TRACE(TRACE_SCSI, "sqatgt(%ld/%d): tag=%lld", cmd->vha->host_no,
cmd->vha->vp_idx, scst_cmd_get_tag(scst_cmd));
cmd->bufflen = scst_cmd_get_write_fields(scst_cmd, &cmd->sg,
&cmd->sg_cnt);
cmd->dma_data_direction =
scst_to_tgt_dma_dir(scst_cmd_get_data_direction(scst_cmd));
cmd->cdb = scst_cmd_get_cdb(scst_cmd);
cmd->sg = scst_cmd_get_sg(scst_cmd);
cmd->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd);
cmd->scsi_status = scst_cmd_get_status(scst_cmd);
cmd->trc_flags |= TRC_XFR_RDY;
#if QLA_ENABLE_PI
if (scst_get_tgt_dif_actions(scst_cmd->cmd_dif_actions)) {
cmd->blk_sz = scst_cmd_get_block_size(scst_cmd);
cmd->se_cmd.prot_type = scst_cmd_get_dif_prot_type(scst_cmd);
cmd->prot_sg_cnt = scst_cmd->dif_sg_cnt;
cmd->prot_sg = scst_cmd->dif_sg;
/* translate dif_actions to prot_op */
qla_tgt_set_cmd_prot_op(cmd, false);
TRACE_DBG("%s: cmd[%p] ulpcmd[%p] dif_actions=0x%x, cdb=0x%x, prot_sg_cnt[%x], prot_type[%x] prot_op[%x], bufflen[%x]",
__func__, cmd, cmd->scst_cmd,
scst_cmd->cmd_dif_actions, scst_cmd->cdb_buf[0],
cmd->prot_sg_cnt, cmd->se_cmd.prot_type,
cmd->se_cmd.prot_op, cmd->bufflen);
}
#endif
/*
* Call the destination function in the low-level-driver and
* translate the return code into a value which can be
* interpreted by the SCST target core.
*/
res = qlt_rdy_to_xfer(cmd);
switch (res) {
case 0:
res = SCST_TGT_RES_SUCCESS;
break;
case -EAGAIN:
res = SCST_TGT_RES_QUEUE_FULL;
break;
default:
res = SCST_TGT_RES_FATAL_ERROR;
break;
}
TRACE_EXIT_RES(res);
return res;
}
static void sqa_on_free_cmd(struct scst_cmd *scst_cmd)
{
struct qla_tgt_cmd *cmd = scst_cmd_get_tgt_priv(scst_cmd);
TRACE_ENTRY();
TRACE(TRACE_SCSI, "sqatgt(%ld/%d): Freeing command %p: tag=%lld",
cmd->vha->host_no, cmd->vha->vp_idx, scst_cmd,
scst_cmd_get_tag(scst_cmd));
cmd->trc_flags |= TRC_CMD_FREE;
/* free resource below. */
qlt_free_cmd(cmd);
TRACE_EXIT();
}
static uint32_t sqa_convert_to_fc_tm_status(int scst_mstatus)
{
uint32_t res;
switch (scst_mstatus) {
case SCST_MGMT_STATUS_SUCCESS:
res = FC_TM_SUCCESS;
break;
case SCST_MGMT_STATUS_TASK_NOT_EXIST:
res = FC_TM_BAD_CMD;
break;
case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
case SCST_MGMT_STATUS_REJECTED:
res = FC_TM_REJECT;
break;
case SCST_MGMT_STATUS_LUN_NOT_EXIST:
case SCST_MGMT_STATUS_FAILED:
default:
res = FC_TM_FAILED;
break;
}
TRACE_EXIT_RES(res);
return res;
}
/* SCST Callback */
static void sqa_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd)
{
struct qla_tgt_mgmt_cmd *mcmd;
struct scsi_qla_host *vha;
TRACE_ENTRY();
mcmd = scst_mgmt_cmd_get_tgt_priv(scst_mcmd);
if (unlikely(mcmd == NULL)) {
PRINT_ERROR("sqatgt: Null target pointer for SCST task management, command=%p",
scst_mcmd);
goto out;
}
vha = mcmd->sess->vha;
TRACE_MGMT_DBG("sqatgt(%ld/%d): scst_mcmd %p status %#x state %#x; mcmd %p flags %x\n",
vha->host_no, vha->vp_idx, scst_mcmd, scst_mcmd->status,
scst_mcmd->state, mcmd, mcmd->flags);
TRACE_MGMT_DBG("sqatgt(%ld/%d): scst_mcmd (%p) status %#x state %#x",
vha->host_no, vha->vp_idx, scst_mcmd,
scst_mcmd->status, scst_mcmd->state);
mcmd->fc_tm_rsp = sqa_convert_to_fc_tm_status(
scst_mgmt_cmd_get_status(scst_mcmd));
qlt_xmit_tm_rsp(mcmd);
out:
TRACE_EXIT();
}
static int sqa_get_initiator_port_transport_id(struct scst_tgt *tgt,
struct scst_session *scst_sess,
uint8_t **transport_id)
{
struct fc_port *sess;
int res = 0;
int tr_id_size;
uint8_t *tr_id;
TRACE_ENTRY();
if (scst_sess == NULL) {
res = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
goto out;
}
sess = scst_sess_get_tgt_priv(scst_sess);
if (sess == NULL) {
res = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
goto out;
}
TRACE_DBG("sqatgt(%ld/%d): Creating transport id: target session=%p, initiator=%8phC, fcid=%3phC, loop=0x%04x",
sess->vha->host_no, sess->vha->vp_idx, sess,
sess->port_name, &sess->d_id.b, sess->loop_id);
tr_id_size = 24;
tr_id = kzalloc(tr_id_size, GFP_KERNEL);
if (tr_id == NULL) {
PRINT_ERROR("sqatgt(%ld/%d): Allocation of TransportID size=%d failed.",
sess->vha->host_no, sess->vha->vp_idx, tr_id_size);
res = -ENOMEM;
goto out;
}
tr_id[0] = SCSI_TRANSPORTID_PROTOCOLID_FCP2;
BUILD_BUG_ON(sizeof(sess->port_name) != 8);
memcpy(&tr_id[8], sess->port_name, 8);
*transport_id = tr_id;
TRACE_DBG("sqatgt(%ld/%d): Created tid.", sess->vha->host_no,
sess->vha->vp_idx);
TRACE_BUFF_FLAG(TRACE_DEBUG, "tid buffer", tr_id, tr_id_size);
out:
TRACE_EXIT_RES(res);
return res;
}
static uint16_t sqa_get_scsi_transport_version(struct scst_tgt *scst_tgt)
{
/* FCP-2 */
return 0x0900;
}
static uint16_t sqa_get_phys_transport_version(struct scst_tgt *scst_tgt)
{
/* FC-FS */
return 0x0DA0;
}
static int sqa_qla2xxx_chk_dif_tags(uint32_t tag)
{
return tag & SCST_DIF_CHECK_REF_TAG;
}
static int sqa_qla2xxx_dif_tags(struct qla_tgt_cmd *cmd,
uint16_t *pfw_prot_opts)
{
struct scst_cmd *scst_cmd = cmd->scst_cmd;
uint32_t t32 = 0;
t32 = scst_get_dif_checks(scst_cmd->cmd_dif_actions);
if (!(t32 & SCST_DIF_CHECK_GUARD_TAG))
*pfw_prot_opts |= PO_DISABLE_GUARD_CHECK;
if (!(t32 & SCST_DIF_CHECK_APP_TAG))
*pfw_prot_opts |= PO_DIS_APP_TAG_VALD;
return t32;
}
static void sqa_cleanup_hw_pending_cmd(scsi_qla_host_t *vha,
struct qla_tgt_cmd *cmd)
{
uint32_t h;
struct qla_qpair *qpair = cmd->qpair;
for (h = 1; h < qpair->req->num_outstanding_cmds; h++) {
if (qpair->req->outstanding_cmds[h] == (srb_t *)cmd) {
printk(KERN_INFO "Clearing handle %d for cmd %p", h,
cmd);
//TRACE_DBG("Clearing handle %d for cmd %p", h, cmd);
qpair->req->outstanding_cmds[h] = NULL;
break;
}
}
}
static void sqa_on_hw_pending_cmd_timeout(struct scst_cmd *scst_cmd)
{
struct qla_tgt_cmd *cmd = scst_cmd_get_tgt_priv(scst_cmd);
struct scsi_qla_host *vha = cmd->vha;
struct qla_qpair *qpair = cmd->qpair;
uint8_t aborted = cmd->aborted;
unsigned long flags;
TRACE_ENTRY();
TRACE_MGMT_DBG("sqatgt(%ld/%d): Cmd %p HW pending for too long (state %s) %s; %s;",
vha->host_no, vha->vp_idx, cmd,
cmdstate_to_str((uint8_t)cmd->state),
cmd->cmd_sent_to_fw ? "sent to fw" : "not sent to fw",
aborted ? "aborted" : "not aborted");
qlt_abort_cmd(cmd);
spin_lock_irqsave(qpair->qp_lock_ptr, flags);
switch (cmd->state) {
case QLA_TGT_STATE_NEW:
case QLA_TGT_STATE_DATA_IN:
PRINT_ERROR("sqa(%ld): A command in state (%s) should not be HW pending. %s",
vha->host_no, cmdstate_to_str((uint8_t)cmd->state),
aborted ? "aborted" : "not aborted");
break;
case QLA_TGT_STATE_NEED_DATA:
/* the abort will nudge it out of FW */
TRACE_MGMT_DBG("Force rx_data cmd %p", cmd);
sqa_cleanup_hw_pending_cmd(vha, cmd);
scst_set_cmd_error(scst_cmd,
SCST_LOAD_SENSE(scst_sense_internal_failure));
scst_rx_data(scst_cmd, SCST_RX_STATUS_ERROR_SENSE_SET,
SCST_CONTEXT_THREAD);
break;
case QLA_TGT_STATE_PROCESSED:
if (!cmd->cmd_sent_to_fw)
PRINT_ERROR("sqa(%ld): command should not be in HW pending. It's already processed. ",
vha->host_no);
else
TRACE_MGMT_DBG("Force finishing cmd %p", cmd);
sqa_cleanup_hw_pending_cmd(vha, cmd);
scst_set_delivery_status(scst_cmd, SCST_CMD_DELIVERY_FAILED);
scst_tgt_cmd_done(scst_cmd, SCST_CONTEXT_THREAD);
break;
}
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);
TRACE_EXIT();
}
/*
* The following structure defines the callbacks which will be executed
* from functions in the qla_target.c file back to this interface
* driver.
*/
static struct qla_tgt_func_tmpl sqa_qla2xxx_template = {
.handle_cmd = sqa_qla2xxx_handle_cmd,
.handle_data = sqa_qla2xxx_handle_data,
.handle_tmr = sqa_qla2xxx_handle_tmr,
.get_cmd = sqa_qla2xxx_get_cmd,
.rel_cmd = sqa_qla2xxx_rel_cmd,
.free_cmd = sqa_qla2xxx_free_cmd,
.free_mcmd = sqa_qla2xxx_free_mcmd,
.free_session = sqa_qla2xxx_free_session,
.update_sess = sqa_qla2xxx_update_sess,
.check_initiator_node_acl = sqa_qla2xxx_check_initiator_node_acl,
.find_sess_by_s_id = sqa_qla2xxx_find_sess_by_s_id,
.find_sess_by_loop_id = sqa_qla2xxx_find_sess_by_loop_id,
.clear_nacl_from_fcport_map = sqa_qla2xxx_clear_nacl_from_fcport_map,
.put_sess = sqa_qla2xxx_put_sess,
.shutdown_sess = sqa_qla2xxx_shutdown_sess,
.get_dif_tags = sqa_qla2xxx_dif_tags,
.chk_dif_tags = sqa_qla2xxx_chk_dif_tags,
.add_target = sqa_qla2xxx_add_target,
.remove_target = sqa_qla2xxx_remove_target,
};
static int sqa_lport_callback(struct scsi_qla_host *vha,
void *target_lport_ptr, u64 npiv_wwpn, u64 npiv_wwnn)
{
struct qla_hw_data *ha = vha->hw;
TRACE_ENTRY();
ha->tgt.tgt_ops = &sqa_qla2xxx_template;
vha->vha_tgt.target_lport_ptr = target_lport_ptr;
TRACE_EXIT();
return 0;
}
static int sqa_enable_tgt(struct scst_tgt *scst_tgt, bool enable)
{
struct sqa_scst_tgt *sqa_tgt;
struct qla_tgt *tgt;
struct scsi_qla_host *vha;
int rc;
TRACE_ENTRY();
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
vha = tgt->vha;
if (enable && (qla_tgt_mode_enabled(tgt->vha) ||
qla_dual_mode_enabled(tgt->vha))) {
PRINT_INFO("sqatgt(%ld/%d): Target already enabled.",
vha->host_no, vha->vp_idx);
return -EINVAL;
}
if (!enable && (!qla_tgt_mode_enabled(vha) &&
!qla_dual_mode_enabled(vha))) {
PRINT_INFO("sqatgt(%ld/%d): Target already disabled.",
vha->host_no, vha->vp_idx);
return -EINVAL;
}
PRINT_INFO("sqatgt(%ld/%d): %s target pwwn=%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
vha->host_no, vha->vp_idx, enable ? "Enabling" : "Disabling",
vha->port_name[0], vha->port_name[1],
vha->port_name[2], vha->port_name[3],
vha->port_name[4], vha->port_name[5],
vha->port_name[6], vha->port_name[7]);
if (enable) {
qlt_lport_register(sqa_tgt, wwn_to_u64(vha->port_name),
0, 0, sqa_lport_callback);
qlt_enable_vha(vha);
} else {
rc = qlt_stop_phase1(tgt);
if (!rc)
qlt_stop_phase2(tgt);
}
TRACE_EXIT();
return 0;
}
static bool sqa_is_tgt_enabled(struct scst_tgt *scst_tgt)
{
int res;
struct qla_tgt *tgt;
struct sqa_scst_tgt *sqa_tgt;
TRACE_ENTRY();
sqa_tgt = scst_tgt_get_tgt_priv(scst_tgt);
tgt = sqa_tgt->qla_tgt;
res = qla_tgt_mode_enabled(tgt->vha) || qla_dual_mode_enabled(tgt->vha);
TRACE_EXIT();
return res;
}
static ssize_t sqa_add_vtarget(const char *target_name, char *params)
{
int res;
char *param, *p, *pp;
u64 port_name, node_name;
u64 parent_host;
u64 pnode_name = 0, pparent_host = 0;
TRACE_ENTRY();
res = sqa_parse_wwn(target_name, &port_name);
if (res) {
PRINT_ERROR("sqatgt: Syntax error at target name %s",
target_name);
goto out;
}
while (1) {
param = scst_get_next_token_str(&params);
if (param == NULL)
break;
p = scst_get_next_lexem(&param);
if (*p == '\0') {
PRINT_ERROR("sqatgt: Syntax error at %s (target %s)",
param, target_name);
res = -EINVAL;
goto out;
}
pp = scst_get_next_lexem(&param);
if (*pp == '\0') {
PRINT_ERROR("sqatgt: Parameter %s value missed for target %s",
p, target_name);
res = -EINVAL;
goto out;
}
if (scst_get_next_lexem(&param)[0] != '\0') {
PRINT_ERROR("sqatgt: Too many parameter's %s values (target %s)",
p, target_name);
res = -EINVAL;
goto out;
}
if (!strcasecmp("node_name", p)) {
res = sqa_parse_wwn(pp, &node_name);
if (res) {
PRINT_ERROR("sqatgt: Illegal node_name %s (target %s)",
pp, target_name);
res = -EINVAL;
goto out;
}
pnode_name = node_name;
continue;
}
if (!strcasecmp("parent_host", p)) {
res = sqa_parse_wwn(pp, &parent_host);
if (res != 0) {
PRINT_ERROR("sqatgt: Illegal parent_host %s (target %s)",
pp, target_name);
goto out;
}
pparent_host = parent_host;
continue;
}
PRINT_ERROR("sqatgt: Unknown parameter %s (target %s)", p,
target_name);
res = -EINVAL;
goto out;
}
if (!pnode_name) {
PRINT_ERROR("sqatgt: Missing parameter node_name (target %s)",
target_name);
res = -EINVAL;
goto out;
}
if (!pparent_host) {
PRINT_ERROR("sqatgt: Missing parameter parent_host (target %s)",
target_name);
res = -EINVAL;
goto out;
}
res = qlt_add_vtarget(port_name, pnode_name, pparent_host);
out:
TRACE_EXIT_RES(res);
return res;
}
static ssize_t sqa_del_vtarget(const char *target_name)
{
int res;
u64 port_name;
TRACE_ENTRY();
res = sqa_parse_wwn(target_name, &port_name);
if (res) {
PRINT_ERROR("sqatgt: Syntax error at target name %s",
target_name);
goto out;
}
res = qlt_del_vtarget(port_name);
out:
TRACE_EXIT_RES(res);
return res;
}
static int __init sqa_init(void)
{
int res = 0;
TRACE_ENTRY();
PRINT_INFO("sqatgt: Initializing SCST Cavium adapter target driver interface - driver version=%s, SCST version=%s, Cavium version=%s",
SQA_VERSION, SCST_VERSION_NAME, QLA2XXX_VERSION);
res = scst_register_target_template(&sqa_scst_template);
if (res) {
PRINT_ERROR("sqatgt: fail to register template.");
goto out;
}
sqa_get_target_list();
out:
TRACE_EXIT_RES(res);
return res;
}
static void __exit sqa_exit(void)
{
struct sqa_scst_tgt *sqa_tgt, *t;
struct scsi_qla_host *vha;
TRACE_ENTRY();
PRINT_INFO("sqatgt: Unloading SCST Cavium adapter target driver interface.");
list_for_each_entry_safe(sqa_tgt, t, &sqa_tgt_glist, list) {
vha = sqa_tgt->qla_tgt->vha;
if (vha->vp_idx) {
/* qla driver will not allow a physical host
* to be stopped if there's an Npiv host still
* active. If the NPIV host is not taken down,
* the scst_unregister_target_template call will
* hang. It assumes various cleanup has already
* taken place.
*/
PRINT_INFO("sqatgt: Stopping NPIV host%ld.\n",
vha->host_no);
qlt_stop_phase1(sqa_tgt->qla_tgt);
scst_unregister_target(sqa_tgt->scst_tgt);
qlt_del_vtarget(wwn_to_u64(vha->port_name));
}
}
list_for_each_entry_safe(sqa_tgt, t, &sqa_tgt_glist, list) {
vha = sqa_tgt->qla_tgt->vha;
if (!vha->vp_idx) {
PRINT_INFO("sqatgt: Stopping host%ld.\n",
vha->host_no);
qlt_stop_phase1(sqa_tgt->qla_tgt);
scst_unregister_target(sqa_tgt->scst_tgt);
}
}
scst_unregister_target_template(&sqa_scst_template);
TRACE_EXIT();
}
#ifdef MODULE
module_init(sqa_init);
module_exit(sqa_exit);
#else
late_initcall(sqa_init);
#endif
MODULE_DESCRIPTION("SCST Cavium adapter target interface driver.");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(SCST);
MODULE_IMPORT_NS(QLA32GB);
MODULE_VERSION(SQA_VERSION);