| /* |
| * 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(¶ms); |
| if (param == NULL) |
| break; |
| |
| p = scst_get_next_lexem(¶m); |
| if (*p == '\0') { |
| PRINT_ERROR("sqatgt: Syntax error at %s (target %s)", |
| param, target_name); |
| res = -EINVAL; |
| goto out; |
| } |
| |
| pp = scst_get_next_lexem(¶m); |
| 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(¶m)[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); |