staticint ql2xtgt_tape_enable;
module_param(ql2xtgt_tape_enable, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(ql2xtgt_tape_enable, "Enables Sequence level error recovery (aka FC Tape). Default is 0 - no SLER. 1 - Enable SLER.");
staticchar *qlini_mode = QLA2XXX_INI_MODE_STR_ENABLED;
module_param(qlini_mode, charp, S_IRUGO);
MODULE_PARM_DESC(qlini_mode, "Determines when initiator mode will be enabled. Possible values: " "\"exclusive\" - initiator mode will be enabled on load, " "disabled on enabling target mode and then on disabling target mode " "enabled back; " "\"disabled\" - initiator mode will never be enabled; " "\"dual\" - Initiator Modes will be enabled. Target Mode can be activated " "when ready " "\"enabled\" (default) - initiator mode will always stay enabled.");
int ql2xuctrlirq = 1;
module_param(ql2xuctrlirq, int, 0644);
MODULE_PARM_DESC(ql2xuctrlirq, "User to control IRQ placement via smp_affinity." "Valid with qlini_mode=disabled." "1(default): enable");
staticconstchar *prot_op_str(u32 prot_op)
{ switch (prot_op) { case TARGET_PROT_NORMAL: return"NORMAL"; case TARGET_PROT_DIN_INSERT: return"DIN_INSERT"; case TARGET_PROT_DOUT_INSERT: return"DOUT_INSERT"; case TARGET_PROT_DIN_STRIP: return"DIN_STRIP"; case TARGET_PROT_DOUT_STRIP: return"DOUT_STRIP"; case TARGET_PROT_DIN_PASS: return"DIN_PASS"; case TARGET_PROT_DOUT_PASS: return"DOUT_PASS"; default: return"UNKNOWN";
}
}
/* This API intentionally takes dest as a parameter, rather than returning
* int value to avoid caller forgetting to issue wmb() after the store */ void qlt_do_generation_tick(struct scsi_qla_host *vha, int *dest)
{
scsi_qla_host_t *base_vha = pci_get_drvdata(vha->hw->pdev);
*dest = atomic_inc_return(&base_vha->generation_tick); /* memory barrier */
wmb();
}
/* Might release hw lock, then reaquire!! */ staticinlineint qlt_issue_marker(struct scsi_qla_host *vha, int vha_locked)
{ /* Send marker if required */ if (unlikely(vha->marker_needed != 0)) { int rc = qla2x00_issue_marker(vha, vha_locked);
if (tgt->tgt_stop) {
ql_dbg(ql_dbg_async, vha, 0x502c, "qla_target(%d): dropping unknown ATIO_TYPE7, because tgt is being stopped",
vha->vp_idx); goto out_term;
}
u = kzalloc(sizeof(*u), GFP_ATOMIC); if (u == NULL) goto out_term;
if (sess && sess->local) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04d, "qla_target(%u): local session for " "port %8phC (loop ID %d) became global\n", vha->vp_idx,
fcport->port_name, sess->loop_id);
sess->local = 0;
}
spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
ha->tgt.tgt_ops->put_sess(sess);
}
/* * This is a zero-base ref-counting solution, since hardware_lock * guarantees that ref_count is not modified concurrently. * Upon successful return content of iocb is undefined
*/ staticstruct qlt_plogi_ack_t *
qlt_plogi_ack_find_add(struct scsi_qla_host *vha, port_id_t *id, struct imm_ntfy_from_isp *iocb)
{ struct qlt_plogi_ack_t *pla;
lockdep_assert_held(&vha->hw->hardware_lock);
list_for_each_entry(pla, &vha->plogi_ack_list, list) { if (pla->id.b24 == id->b24) {
ql_dbg(ql_dbg_disc + ql_dbg_verbose, vha, 0x210d, "%s %d %8phC Term INOT due to new INOT",
__func__, __LINE__,
pla->iocb.u.isp24.port_name);
qlt_send_term_imm_notif(vha, &pla->iocb, 1);
memcpy(&pla->iocb, iocb, sizeof(pla->iocb)); return pla;
}
}
pla = kmem_cache_zalloc(qla_tgt_plogi_cachep, GFP_ATOMIC); if (!pla) {
ql_dbg(ql_dbg_async, vha, 0x5088, "qla_target(%d): Allocation of plogi_ack failed\n",
vha->vp_idx); return NULL;
}
if (link == QLT_PLOGI_LINK_CONFLICT) { switch (sess->disc_state) { case DSC_DELETED: case DSC_DELETE_PEND:
pla->ref_count--; return; default: break;
}
}
if (sess->plogi_link[link])
qlt_plogi_ack_unref(vha, sess->plogi_link[link]);
if (link == QLT_PLOGI_LINK_SAME_WWN)
pla->fcport = sess;
sess->plogi_link[link] = pla;
}
typedefstruct { /* These fields must be initialized by the caller */
port_id_t id; /* * number of cmds dropped while we were waiting for * initiator to ack LOGO initialize to 1 if LOGO is * triggered by a command, otherwise, to 0
*/ int cmd_count;
/* These fields are used by callee */ struct list_head list;
} qlt_port_logo_t;
while (!READ_ONCE(sess->logout_completed)) { if (!traced) {
ql_dbg(ql_dbg_disc, vha, 0xf086, "%s: waiting for sess %p logout\n",
__func__, sess);
traced = true;
}
msleep(100);
cnt++; /* * Driver timeout is set to 22 Sec, update count value to loop * long enough for log-out to complete before advancing. Otherwise, * straddling logout can interfere with re-login attempt.
*/ if (cnt > 230) break;
}
spin_lock_irqsave(&sess->vha->work_lock, flags); if (sess->free_pending) {
spin_unlock_irqrestore(&sess->vha->work_lock, flags); return;
}
sess->free_pending = 1; /* * Use FCF_ASYNC_SENT flag to block other cmds used in sess * management from being sent.
*/
sess->flags |= FCF_ASYNC_SENT;
sess->deleted = QLA_SESS_DELETION_IN_PROGRESS;
spin_unlock_irqrestore(&sess->vha->work_lock, flags);
if (sess->se_sess)
vha->hw->tgt.tgt_ops->clear_nacl_from_fcport_map(sess);
/* * Adds an extra ref to allow to drop hw lock after adding sess to the list. * Caller must put it.
*/ staticstruct fc_port *qlt_create_sess( struct scsi_qla_host *vha,
fc_port_t *fcport, bool local)
{ struct qla_hw_data *ha = vha->hw; struct fc_port *sess = fcport; unsignedlong flags;
if (vha->vha_tgt.qla_tgt->tgt_stop) return NULL;
if (fcport->se_sess) { if (!kref_get_unless_zero(&sess->sess_kref)) {
ql_dbg(ql_dbg_disc, vha, 0x20f6, "%s: kref_get_unless_zero failed for %8phC\n",
__func__, sess->port_name); return NULL;
} return fcport;
}
sess->tgt = vha->vha_tgt.qla_tgt;
sess->local = local;
/* * Under normal circumstances we want to logout from firmware when * session eventually ends and release corresponding nport handle. * In the exception cases (e.g. when new PLOGI is waiting) corresponding * code will adjust these flags as necessary.
*/
sess->logout_on_delete = 1;
sess->keep_nport_handle = 0;
sess->logout_completed = 0;
if (ha->tgt.tgt_ops->check_initiator_node_acl(vha,
&fcport->port_name[0], sess) < 0) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf015, "(%d) %8phC check_initiator_node_acl failed\n",
vha->vp_idx, fcport->port_name); return NULL;
} else {
kref_init(&fcport->sess_kref); /* * Take an extra reference to ->sess_kref here to handle * fc_port access across ->tgt.sess_lock reaquire.
*/ if (!kref_get_unless_zero(&sess->sess_kref)) {
ql_dbg(ql_dbg_disc, vha, 0x20f7, "%s: kref_get_unless_zero failed for %8phC\n",
__func__, sess->port_name); return NULL;
}
spin_lock_irqsave(&ha->tgt.sess_lock, flags); if (!IS_SW_RESV_ADDR(sess->d_id))
vha->vha_tgt.qla_tgt->sess_count++;
staticinlineint test_tgt_sess_count(struct qla_tgt *tgt)
{ struct qla_hw_data *ha = tgt->ha; unsignedlong flags; int res; /* * We need to protect against race, when tgt is freed before or * inside wake_up()
*/
spin_lock_irqsave(&ha->tgt.sess_lock, flags);
ql_dbg(ql_dbg_tgt, tgt->vha, 0xe002, "tgt %p, sess_count=%d\n",
tgt, tgt->sess_count);
res = (tgt->sess_count == 0);
spin_unlock_irqrestore(&ha->tgt.sess_lock, flags);
return res;
}
/* Called by tcm_qla2xxx configfs code */ int qlt_stop_phase1(struct qla_tgt *tgt)
{ struct scsi_qla_host *vha = tgt->vha; struct qla_hw_data *ha = tgt->ha; unsignedlong flags;
if (tgt->tgt_stop || tgt->tgt_stopped) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04e, "Already in tgt->tgt_stop or tgt_stopped state\n");
mutex_unlock(&qla_tgt_mutex);
mutex_unlock(&ha->optrom_mutex); return -EPERM;
}
ql_dbg(ql_dbg_tgt_mgt, vha, 0xe003, "Stopping target for host %ld(%p)\n",
vha->host_no, vha); /* * Mutex needed to sync with qla_tgt_fc_port_[added,deleted]. * Lock is needed, because we still can get an incoming packet.
*/
mutex_lock(&vha->vha_tgt.tgt_mutex);
tgt->tgt_stop = 1;
qlt_clear_tgt_db(tgt);
mutex_unlock(&vha->vha_tgt.tgt_mutex);
mutex_unlock(&qla_tgt_mutex);
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf009, "Waiting for sess works (tgt %p)", tgt);
spin_lock_irqsave(&tgt->sess_work_lock, flags); do {
spin_unlock_irqrestore(&tgt->sess_work_lock, flags);
flush_work(&tgt->sess_work);
spin_lock_irqsave(&tgt->sess_work_lock, flags);
} while (!list_empty(&tgt->sess_works_list));
spin_unlock_irqrestore(&tgt->sess_work_lock, flags);
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00a, "Waiting for tgt %p: sess_count=%d\n", tgt, tgt->sess_count);
if (vha->vp_idx) if (ha->tgt.tgt_ops &&
ha->tgt.tgt_ops->remove_target &&
vha->vha_tgt.target_lport_ptr)
ha->tgt.tgt_ops->remove_target(vha);
vha->vha_tgt.qla_tgt = NULL;
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf00d, "Release of tgt %p finished\n", tgt);
kfree(tgt);
}
/* ha->hardware_lock supposed to be held on entry */ staticint qlt_sched_sess_work(struct qla_tgt *tgt, int type, constvoid *param, unsignedint param_size)
{ struct qla_tgt_sess_work_param *prm; unsignedlong flags;
prm = kzalloc(sizeof(*prm), GFP_ATOMIC); if (!prm) {
ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf050, "qla_target(%d): Unable to create session " "work, command will be refused", 0); return -ENOMEM;
}
ql_dbg(ql_dbg_tgt_mgt, tgt->vha, 0xf00e, "Scheduling work (type %d, prm %p)" " to find session for param %p (size %d, tgt %p)\n",
type, prm, param, param_size, tgt);
h = qlt_make_handle(qpair); if (unlikely(h == QLA_TGT_NULL_HANDLE)) { /* * CTIO type 7 from the firmware doesn't provide a way to * know the initiator's LOOP ID, hence we can't find * the session and, so, the command.
*/ return -EAGAIN;
} else {
qpair->req->outstanding_cmds[h] = (srb_t *)mcmd;
}
if (mcmd)
qlt_build_abts_resp_iocb(mcmd); else
qlt_24xx_send_abts_resp(qpair,
(struct abts_recv_from_24xx *)entry, FCP_TMF_CMPL, true);
}
/* drop cmds for the given lun * XXX only looks for cmds on the port through which lun reset was recieved * XXX does not go through the list of other port (which may have cmds * for the same lun)
*/ staticvoid abort_cmds_for_lun(struct scsi_qla_host *vha, u64 lun, be_id_t s_id)
{ struct qla_tgt_sess_op *op; struct qla_tgt_cmd *cmd;
uint32_t key; unsignedlong flags;
if (vha->flags.qpairs_available) {
h = btree_lookup64(&tgt->lun_qpair_map, unpacked_lun); if (!h)
h = &tgt->qphints[0];
} else {
h = &tgt->qphints[0];
}
if (rc != 0) {
spin_lock_irqsave(mcmd->qpair->qp_lock_ptr, flags); switch (mcmd->tmr_func) { case QLA_TGT_ABTS:
mcmd->fc_tm_rsp = FCP_TMF_REJECTED;
qlt_build_abts_resp_iocb(mcmd); break; case QLA_TGT_LUN_RESET: case QLA_TGT_CLEAR_TS: case QLA_TGT_ABORT_TS: case QLA_TGT_CLEAR_ACA: case QLA_TGT_TARGET_RESET:
qlt_send_busy(mcmd->qpair, &mcmd->orig_iocb.atio,
qla_sam_status); break;
case QLA_TGT_ABORT_ALL: case QLA_TGT_NEXUS_LOSS_SESS: case QLA_TGT_NEXUS_LOSS:
qlt_send_notify_ack(mcmd->qpair,
&mcmd->orig_iocb.imm_ntfy, 0, 0, 0, 0, 0, 0); break;
}
spin_unlock_irqrestore(mcmd->qpair->qp_lock_ptr, flags);
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf013, "TM response mcmd (%p) status %#x state %#x",
mcmd, mcmd->fc_tm_rsp, mcmd->flags);
spin_lock_irqsave(qpair->qp_lock_ptr, flags);
if (!vha->flags.online || mcmd->reset_count != qpair->chip_reset) { /* * Either the port is not online or this request was from * previous life, just abort the processing.
*/
ql_dbg(ql_dbg_async, vha, 0xe100, "RESET-TMR online/active/old-count/new-count = %d/%d/%d/%d.\n",
vha->flags.online, qla2x00_reset_active(vha),
mcmd->reset_count, qpair->chip_reset);
ha->tgt.tgt_ops->free_mcmd(mcmd);
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags); return;
}
if (mcmd->flags == QLA24XX_MGMT_SEND_NACK) { switch (mcmd->orig_iocb.imm_ntfy.u.isp24.status_subcode) { case ELS_LOGO: case ELS_PRLO: case ELS_TPRLO:
ql_dbg(ql_dbg_disc, vha, 0x2106, "TM response logo %8phC status %#x state %#x",
mcmd->sess->port_name, mcmd->fc_tm_rsp,
mcmd->flags);
qlt_schedule_sess_for_deletion(mcmd->sess); break; default:
qlt_send_notify_ack(vha->hw->base_qpair,
&mcmd->orig_iocb.imm_ntfy, 0, 0, 0, 0, 0, 0); break;
}
} else { if (mcmd->orig_iocb.atio.u.raw.entry_type == ABTS_RECV_24XX) {
qlt_build_abts_resp_iocb(mcmd);
free_mcmd = false;
} else
qlt_24xx_send_task_mgmt_ctio(qpair, mcmd,
mcmd->fc_tm_rsp);
} /* * Make the callback for ->free_mcmd() to queue_work() and invoke * target_put_sess_cmd() to drop cmd_kref to 1. The final * target_put_sess_cmd() call will be made from TFO->check_stop_free() * -> tcm_qla2xxx_check_stop_free() to release the TMR associated se_cmd * descriptor after TFO->queue_tm_rsp() -> tcm_qla2xxx_queue_tm_rsp() -> * qlt_xmit_tm_rsp() returns here..
*/ if (free_mcmd)
ha->tgt.tgt_ops->free_mcmd(mcmd);
if (unlikely(req->cnt < (req_cnt + 2))) return -EAGAIN;
}
req->cnt -= req_cnt;
return 0;
}
/* * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
*/ staticinlinevoid *qlt_get_req_pkt(struct req_que *req)
{ /* Adjust ring index. */
req->ring_index++; if (req->ring_index == req->length) {
req->ring_index = 0;
req->ring_ptr = req->ring;
} else {
req->ring_ptr++;
} return (cont_entry_t *)req->ring_ptr;
}
/* ha->hardware_lock supposed to be held on entry */ staticinline uint32_t qlt_make_handle(struct qla_qpair *qpair)
{
uint32_t h; int index;
uint8_t found = 0; struct req_que *req = qpair->req;
h = req->current_outstanding_cmd;
for (index = 1; index < req->num_outstanding_cmds; index++) {
h++; if (h == req->num_outstanding_cmds)
h = 1;
if (h == QLA_TGT_SKIP_HANDLE) continue;
if (!req->outstanding_cmds[h]) {
found = 1; break;
}
}
if (found) {
req->current_outstanding_cmd = h;
} else {
ql_dbg(ql_dbg_io, qpair->vha, 0x305b, "qla_target(%d): Ran out of empty cmd slots\n",
qpair->vha->vp_idx);
h = QLA_TGT_NULL_HANDLE;
}
return h;
}
/* ha->hardware_lock supposed to be held on entry */ staticint qlt_24xx_build_ctio_pkt(struct qla_qpair *qpair, struct qla_tgt_prm *prm)
{
uint32_t h; struct ctio7_to_24xx *pkt; struct atio_from_isp *atio = &prm->cmd->atio;
uint16_t temp; struct qla_tgt_cmd *cmd = prm->cmd;
h = qlt_make_handle(qpair); if (unlikely(h == QLA_TGT_NULL_HANDLE)) { /* * CTIO type 7 from the firmware doesn't provide a way to * know the initiator's LOOP ID, hence we can't find * the session and, so, the command.
*/ return -EAGAIN;
} else
qpair->req->outstanding_cmds[h] = (srb_t *)prm->cmd;
if (cmd->edif) { if (cmd->dma_data_direction == DMA_TO_DEVICE)
prm->cmd->sess->edif.rx_bytes += cmd->bufflen; if (cmd->dma_data_direction == DMA_FROM_DEVICE)
prm->cmd->sess->edif.tx_bytes += cmd->bufflen;
pkt->u.status0.edif_flags |= EF_EN_EDIF;
}
return 0;
}
/* * ha->hardware_lock supposed to be held on entry. We have already made sure * that there is sufficient amount of request entries to not drop it.
*/ staticvoid qlt_load_cont_data_segments(struct qla_tgt_prm *prm)
{ int cnt; struct dsd64 *cur_dsd;
/* * ha->hardware_lock supposed to be held on entry. We have already made sure * that there is sufficient amount of request entries to not drop it.
*/ staticvoid qlt_load_data_segments(struct qla_tgt_prm *prm)
{ int cnt; struct dsd64 *cur_dsd; struct ctio7_to_24xx *pkt24 = (struct ctio7_to_24xx *)prm->pkt;
if (xmit_type & QLA_TGT_XMIT_STATUS) { /* * If QLA_TGT_XMIT_DATA is not set, add_status_pkt will be * ignored in *xmit_response() below
*/ if (qlt_has_data(cmd)) { if (QLA_TGT_SENSE_VALID(prm->sense_buffer) ||
(IS_FWI2_CAPABLE(cmd->vha->hw) &&
(prm->rq_result != 0))) {
prm->add_status_pkt = 1;
(*full_req_cnt)++;
}
}
}
return 0;
}
staticinlineint qlt_need_explicit_conf(struct qla_tgt_cmd *cmd, int sending_sense)
{ if (cmd->qpair->enable_class_2) return 0;
if (sending_sense) return cmd->conf_compl_supported; else return cmd->qpair->enable_explicit_conf &&
cmd->conf_compl_supported;
}
staticinlineint
qlt_hba_err_chk_enabled(struct se_cmd *se_cmd)
{ switch (se_cmd->prot_op) { case TARGET_PROT_DOUT_INSERT: case TARGET_PROT_DIN_STRIP: if (ql2xenablehba_err_chk >= 1) return 1; break; case TARGET_PROT_DOUT_PASS: case TARGET_PROT_DIN_PASS: if (ql2xenablehba_err_chk >= 2) return 1; break; case TARGET_PROT_DIN_INSERT: case TARGET_PROT_DOUT_STRIP: return 1; default: break;
} return 0;
}
staticinlineint
qla_tgt_ref_mask_check(struct se_cmd *se_cmd)
{ switch (se_cmd->prot_op) { case TARGET_PROT_DIN_INSERT: case TARGET_PROT_DOUT_INSERT: case TARGET_PROT_DIN_STRIP: case TARGET_PROT_DOUT_STRIP: case TARGET_PROT_DIN_PASS: case TARGET_PROT_DOUT_PASS: return 1; default: return 0;
} return 0;
}
/* * wait till Mode Sense/Select cmd, modepage Ah, subpage 2 * have been immplemented by TCM, before AppTag is avail. * Look for modesense_handlers[]
*/
ctx->app_tag = 0;
ctx->app_tag_mask[0] = 0x0;
ctx->app_tag_mask[1] = 0x0;
switch (se_cmd->prot_type) { case TARGET_DIF_TYPE0_PROT: /* * No check for ql2xenablehba_err_chk, as it * would be an I/O error if hba tag generation * is not done.
*/
ctx->ref_tag = cpu_to_le32(lba); /* enable ALL bytes of the ref tag */
ctx->ref_tag_mask[0] = 0xff;
ctx->ref_tag_mask[1] = 0xff;
ctx->ref_tag_mask[2] = 0xff;
ctx->ref_tag_mask[3] = 0xff; break; case TARGET_DIF_TYPE1_PROT: /* * For TYPE 1 protection: 16 bit GUARD tag, 32 bit * REF tag, and 16 bit app tag.
*/
ctx->ref_tag = cpu_to_le32(lba); if (!qla_tgt_ref_mask_check(se_cmd) ||
!(ha->tgt.tgt_ops->chk_dif_tags(t32))) {
*pfw_prot_opts |= PO_DIS_REF_TAG_VALD; break;
} /* enable ALL bytes of the ref tag */
ctx->ref_tag_mask[0] = 0xff;
ctx->ref_tag_mask[1] = 0xff;
ctx->ref_tag_mask[2] = 0xff;
ctx->ref_tag_mask[3] = 0xff; break; case TARGET_DIF_TYPE2_PROT: /* * For TYPE 2 protection: 16 bit GUARD + 32 bit REF * tag has to match LBA in CDB + N
*/
ctx->ref_tag = cpu_to_le32(lba); if (!qla_tgt_ref_mask_check(se_cmd) ||
!(ha->tgt.tgt_ops->chk_dif_tags(t32))) {
*pfw_prot_opts |= PO_DIS_REF_TAG_VALD; break;
} /* enable ALL bytes of the ref tag */
ctx->ref_tag_mask[0] = 0xff;
ctx->ref_tag_mask[1] = 0xff;
ctx->ref_tag_mask[2] = 0xff;
ctx->ref_tag_mask[3] = 0xff; break; case TARGET_DIF_TYPE3_PROT: /* For TYPE 3 protection: 16 bit GUARD only */
*pfw_prot_opts |= PO_DIS_REF_TAG_VALD;
ctx->ref_tag_mask[0] = ctx->ref_tag_mask[1] =
ctx->ref_tag_mask[2] = ctx->ref_tag_mask[3] = 0x00; break;
}
}
/* Compute dif len and adjust data len to incude protection */
data_bytes = cmd->bufflen;
dif_bytes = (data_bytes / cmd->blk_sz) * 8;
switch (se_cmd->prot_op) { case TARGET_PROT_DIN_INSERT: case TARGET_PROT_DOUT_STRIP:
transfer_length = data_bytes; if (cmd->prot_sg_cnt)
data_bytes += dif_bytes; break; case TARGET_PROT_DIN_STRIP: case TARGET_PROT_DOUT_INSERT: case TARGET_PROT_DIN_PASS: case TARGET_PROT_DOUT_PASS:
transfer_length = data_bytes + dif_bytes; break; default:
BUG(); break;
}
switch (se_cmd->prot_op) { case TARGET_PROT_DIN_INSERT: case TARGET_PROT_DOUT_INSERT:
fw_prot_opts |= PO_MODE_DIF_INSERT; break; case TARGET_PROT_DIN_STRIP: case TARGET_PROT_DOUT_STRIP:
fw_prot_opts |= PO_MODE_DIF_REMOVE; break; case TARGET_PROT_DIN_PASS: case TARGET_PROT_DOUT_PASS:
fw_prot_opts |= PO_MODE_DIF_PASS; /* FUTURE: does tcm require T10CRC<->IPCKSUM conversion? */ break; default:/* Normal Request */
fw_prot_opts |= PO_MODE_DIF_PASS; break;
}
/* ---- PKT ---- */ /* Update entry type to indicate Command Type CRC_2 IOCB */
pkt->entry_type = CTIO_CRC2;
pkt->entry_count = 1;
pkt->vp_index = cmd->vp_idx;
h = qlt_make_handle(qpair); if (unlikely(h == QLA_TGT_NULL_HANDLE)) { /* * CTIO type 7 from the firmware doesn't provide a way to * know the initiator's LOOP ID, hence we can't find * the session and, so, the command.
*/ return -EAGAIN;
} else
qpair->req->outstanding_cmds[h] = (srb_t *)prm->cmd;
/* Set transfer direction */ if (cmd->dma_data_direction == DMA_TO_DEVICE)
pkt->flags = cpu_to_le16(CTIO7_FLAGS_DATA_IN); elseif (cmd->dma_data_direction == DMA_FROM_DEVICE)
pkt->flags = cpu_to_le16(CTIO7_FLAGS_DATA_OUT);
res = qlt_pre_xmit_response(cmd, &prm, xmit_type, scsi_status,
&full_req_cnt); if (unlikely(res != 0)) { return res;
}
spin_lock_irqsave(qpair->qp_lock_ptr, flags);
if (xmit_type == QLA_TGT_XMIT_STATUS)
qpair->tgt_counters.core_qla_snd_status++; else
qpair->tgt_counters.core_qla_que_buf++;
if (!qpair->fw_started || cmd->reset_count != qpair->chip_reset) { /* * Either the port is not online or this request was from * previous life, just abort the processing.
*/
cmd->state = QLA_TGT_STATE_PROCESSED;
ql_dbg_qp(ql_dbg_async, qpair, 0xe101, "RESET-RSP online/active/old-count/new-count = %d/%d/%d/%d.\n",
vha->flags.online, qla2x00_reset_active(vha),
cmd->reset_count, qpair->chip_reset);
res = 0; goto out_unmap_unlock;
}
/* Does F/W have an IOCBs for this request */
res = qlt_check_reserve_free_req(qpair, full_req_cnt); if (unlikely(res)) goto out_unmap_unlock;
if (cmd->se_cmd.prot_op && (xmit_type & QLA_TGT_XMIT_DATA))
res = qlt_build_ctio_crc2_pkt(qpair, &prm); else
res = qlt_24xx_build_ctio_pkt(qpair, &prm); if (unlikely(res != 0)) {
qpair->req->cnt += full_req_cnt; goto out_unmap_unlock;
}
} else { /* * We have already made sure that there is sufficient * amount of request entries to not drop HW lock in * req_pkt().
*/ struct ctio7_to_24xx *ctio =
(struct ctio7_to_24xx *)qlt_get_req_pkt(
qpair->req);
ql_dbg_qp(ql_dbg_tgt, qpair, 0x305e, "Building additional status packet 0x%p.\n",
ctio);
/* Real finish is ctio_m1's finish */
pkt->handle |= CTIO_INTERMEDIATE_HANDLE_MARK;
pkt->u.status0.flags |= cpu_to_le16(
CTIO7_FLAGS_DONT_RET_CTIO);
/* qlt_24xx_init_ctio_to_isp will correct * all neccessary fields that's part of CTIO7. * There should be no residual of CTIO-CRC2 data.
*/
qlt_24xx_init_ctio_to_isp((struct ctio7_to_24xx *)ctio,
&prm);
}
} else
qlt_24xx_init_ctio_to_isp(pkt, &prm);
if (!qpair->fw_started || (cmd->reset_count != qpair->chip_reset) ||
(cmd->sess && cmd->sess->deleted)) { /* * Either the port is not online or this request was from * previous life, just abort the processing.
*/
cmd->aborted = 1;
cmd->write_data_transferred = 0;
cmd->state = QLA_TGT_STATE_DATA_IN;
vha->hw->tgt.tgt_ops->handle_data(cmd);
ql_dbg_qp(ql_dbg_async, qpair, 0xe102, "RESET-XFR online/active/old-count/new-count = %d/%d/%d/%d.\n",
vha->flags.online, qla2x00_reset_active(vha),
cmd->reset_count, qpair->chip_reset); return 0;
}
/* Calculate number of entries and segments required */ if (qlt_pci_map_calc_cnt(&prm) != 0) return -EAGAIN;
spin_lock_irqsave(qpair->qp_lock_ptr, flags); /* Does F/W have an IOCBs for this request */
res = qlt_check_reserve_free_req(qpair, prm.req_cnt); if (res != 0) goto out_unlock_free_unmap; if (cmd->se_cmd.prot_op)
res = qlt_build_ctio_crc2_pkt(qpair, &prm); else
res = qlt_24xx_build_ctio_pkt(qpair, &prm);
qlt_send_resp_ctio(qpair, cmd, scsi_status, sense_key, asc,
ascq); /* assume scsi status gets out on the wire. * Will not wait for completion.
*/
vha->hw->tgt.tgt_ops->free_cmd(cmd); break;
}
}
/* If hardware_lock held on entry, might drop it, then reaquire */ /* This function sends the appropriate CTIO to ISP 2xxx or 24xx */ staticint __qlt_send_term_imm_notif(struct scsi_qla_host *vha, struct imm_ntfy_from_isp *ntfy)
{ struct nack_to_isp *nack; struct qla_hw_data *ha = vha->hw;
request_t *pkt; int ret = 0;
ql_dbg(ql_dbg_tgt_tmr, vha, 0xe01c, "Sending TERM ELS CTIO (ha=%p)\n", ha);
/* * If hardware_lock held on entry, might drop it, then reaquire * This function sends the appropriate CTIO to ISP 2xxx or 24xx
*/ staticint __qlt_send_term_exchange(struct qla_qpair *qpair, struct qla_tgt_cmd *cmd, struct atio_from_isp *atio)
{ struct scsi_qla_host *vha = qpair->vha; struct ctio7_to_24xx *ctio24; struct qla_hw_data *ha = vha->hw;
request_t *pkt; int ret = 0;
uint16_t temp;
ql_dbg(ql_dbg_tgt, vha, 0xe009, "Sending TERM EXCH CTIO (ha=%p)\n", ha);
list_for_each_entry_safe(cmd, tcmd, &free_list, cmd_list) {
list_del(&cmd->cmd_list); /* This cmd was never sent to TCM. There is no need * to schedule free or call free_cmd
*/
qlt_free_cmd(cmd);
vha->hw->tgt.num_qfull_cmds_alloc--;
}
}
vha->hw->tgt.num_qfull_cmds_dropped = 0;
}
/* * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
*/ staticint qlt_term_ctio_exchange(struct qla_qpair *qpair, void *ctio, struct qla_tgt_cmd *cmd, uint32_t status)
{ int term = 0; struct scsi_qla_host *vha = qpair->vha;
cmd = req->outstanding_cmds[h]; if (unlikely(cmd == NULL)) {
ql_dbg(ql_dbg_async, vha, 0xe053, "qla_target(%d): Suspicious: unable to find the command with handle %x req->id %d rsp->id %d\n",
vha->vp_idx, handle, req->id, rsp->id); return NULL;
}
req->outstanding_cmds[h] = NULL;
} elseif (ctio != NULL) { /* We can't get loop ID from CTIO7 */
ql_dbg(ql_dbg_tgt, vha, 0xe054, "qla_target(%d): Wrong CTIO received: QLA24xx doesn't " "support NULL handles\n", vha->vp_idx); return NULL;
}
return cmd;
}
/* * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
*/ staticvoid qlt_do_ctio_completion(struct scsi_qla_host *vha, struct rsp_que *rsp, uint32_t handle, uint32_t status, void *ctio)
{ struct qla_hw_data *ha = vha->hw; struct se_cmd *se_cmd; struct qla_tgt_cmd *cmd; struct qla_qpair *qpair = rsp->qpair;
if (handle & CTIO_INTERMEDIATE_HANDLE_MARK) { /* That could happen only in case of an error/reset/abort */ if (status != CTIO_SUCCESS) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01d, "Intermediate CTIO received" " (status %x)\n", status);
} return;
}
if (unlikely(status != CTIO_SUCCESS)) { switch (status & 0xFFFF) { case CTIO_INVALID_RX_ID: if (printk_ratelimit())
dev_info(&vha->hw->pdev->dev, "qla_target(%d): CTIO with INVALID_RX_ID ATIO attr %x CTIO Flags %x|%x\n",
vha->vp_idx, cmd->atio.u.isp24.attr,
((cmd->ctio_flags >> 9) & 0xf),
cmd->ctio_flags);
break; case CTIO_LIP_RESET: case CTIO_TARGET_RESET: case CTIO_ABORTED: /* driver request abort via Terminate exchange */ case CTIO_TIMEOUT: /* They are OK */
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf058, "qla_target(%d): CTIO with " "status %#x received, state %x, se_cmd %p, " "(LIP_RESET=e, ABORTED=2, TARGET_RESET=17, " "TIMEOUT=b, INVALID_RX_ID=8)\n", vha->vp_idx,
status, cmd->state, se_cmd); break;
case CTIO_PORT_LOGGED_OUT: case CTIO_PORT_UNAVAILABLE:
{ int logged_out =
(status & 0xFFFF) == CTIO_PORT_LOGGED_OUT;
if (logged_out && cmd->sess) { /* * Session is already logged out, but we need * to notify initiator, who's not aware of this
*/
cmd->sess->send_els_logo = 1;
ql_dbg(ql_dbg_disc, vha, 0x20f8, "%s %d %8phC post del sess\n",
__func__, __LINE__, cmd->sess->port_name);
case CTIO_FAST_AUTH_ERR: case CTIO_FAST_INCOMP_PAD_LEN: case CTIO_FAST_INVALID_REQ: case CTIO_FAST_SPI_ERR:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05b, "qla_target(%d): CTIO with EDIF error status 0x%x received (state %x, se_cmd %p\n",
vha->vp_idx, status, cmd->state, se_cmd); break;
default:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05b, "qla_target(%d): CTIO with error status 0x%x received (state %x, se_cmd %p\n",
vha->vp_idx, status, cmd->state, se_cmd); break;
}
/* "cmd->aborted" means * cmd is already aborted/terminated, we don't * need to terminate again. The exchange is already * cleaned up/freed at FW level. Just cleanup at driver * level.
*/ if ((cmd->state != QLA_TGT_STATE_NEED_DATA) &&
(!cmd->aborted)) {
cmd->trc_flags |= TRC_CTIO_ERR; if (qlt_term_ctio_exchange(qpair, ctio, cmd, status)) return;
}
}
ret = ha->tgt.tgt_ops->handle_cmd(vha, cmd, cdb, data_length,
fcp_task_attr, data_dir, bidi); if (ret != 0) goto out_term; /* * Drop extra session reference from qlt_handle_cmd_for_atio().
*/
ha->tgt.tgt_ops->put_sess(sess); return;
out_term:
ql_dbg(ql_dbg_io, vha, 0x3060, "Terminating work cmd %p", cmd); /* * cmd has not sent to target yet, so pass NULL as the second * argument to qlt_send_term_exchange() and free the memory here.
*/
cmd->trc_flags |= TRC_DO_WORK_ERR;
spin_lock_irqsave(qpair->qp_lock_ptr, flags);
qlt_send_term_exchange(qpair, NULL, &cmd->atio, 1, 0);
if (vha->flags.qpairs_available) {
h = btree_lookup64(&tgt->lun_qpair_map, cmd->unpacked_lun); if (unlikely(!h)) { /* spread lun to qpair ratio evently */ int lcnt = 0, rc; struct scsi_qla_host *base_vha =
pci_get_drvdata(vha->hw->pdev);
qpair = vha->hw->base_qpair; if (qpair->lun_cnt == 0) {
qpair->lun_cnt++;
h = qla_qpair_to_hint(tgt, qpair);
BUG_ON(!h);
rc = btree_insert64(&tgt->lun_qpair_map,
cmd->unpacked_lun, h, GFP_ATOMIC); if (rc) {
qpair->lun_cnt--;
ql_log(ql_log_info, vha, 0xd037, "Unable to insert lun %llx into lun_qpair_map\n",
cmd->unpacked_lun);
} goto out;
} else {
lcnt = qpair->lun_cnt;
}
h = NULL;
list_for_each_entry(qp, &base_vha->qp_list,
qp_list_elem) { if (qp->lun_cnt == 0) {
qp->lun_cnt++;
h = qla_qpair_to_hint(tgt, qp);
BUG_ON(!h);
rc = btree_insert64(&tgt->lun_qpair_map,
cmd->unpacked_lun, h, GFP_ATOMIC); if (rc) {
qp->lun_cnt--;
ql_log(ql_log_info, vha, 0xd038, "Unable to insert lun %llx into lun_qpair_map\n",
cmd->unpacked_lun);
}
qpair = qp; goto out;
} else { if (qp->lun_cnt < lcnt) {
lcnt = qp->lun_cnt;
qpair = qp; continue;
}
}
}
BUG_ON(!qpair);
qpair->lun_cnt++;
h = qla_qpair_to_hint(tgt, qpair);
BUG_ON(!h);
rc = btree_insert64(&tgt->lun_qpair_map,
cmd->unpacked_lun, h, GFP_ATOMIC); if (rc) {
qpair->lun_cnt--;
ql_log(ql_log_info, vha, 0xd039, "Unable to insert lun %llx into lun_qpair_map\n",
cmd->unpacked_lun);
}
}
} else {
h = &tgt->qphints[0];
}
out:
cmd->qpair = h->qpair;
cmd->se_cmd.cpuid = h->cpuid;
}
/* ha->hardware_lock supposed to be held on entry */ staticint qlt_handle_cmd_for_atio(struct scsi_qla_host *vha, struct atio_from_isp *atio)
{ struct qla_hw_data *ha = vha->hw; struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; struct fc_port *sess; struct qla_tgt_cmd *cmd; unsignedlong flags;
port_id_t id;
if (unlikely(tgt->tgt_stop)) {
ql_dbg(ql_dbg_io, vha, 0x3061, "New command while device %p is shutting down\n", tgt); return -ENODEV;
}
id = be_to_port_id(atio->u.isp24.fcp_hdr.s_id); if (IS_SW_RESV_ADDR(id)) return -EBUSY;
sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, atio->u.isp24.fcp_hdr.s_id); if (unlikely(!sess)) return -EFAULT;
/* Another WWN used to have our s_id. Our PLOGI scheduled its
* session deletion, but it's still in sess_del_work wq */ if (sess->deleted) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf002, "New command while old session %p is being deleted\n",
sess); return -EFAULT;
}
/* * Do kref_get() before returning + dropping qla_hw_data->hardware_lock.
*/ if (!kref_get_unless_zero(&sess->sess_kref)) {
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf004, "%s: kref_get fail, %8phC oxid %x \n",
__func__, sess->port_name,
be16_to_cpu(atio->u.isp24.fcp_hdr.ox_id)); return -EFAULT;
}
/* ha->hardware_lock supposed to be held on entry */ staticint qlt_issue_task_mgmt(struct fc_port *sess, u64 lun, int fn, void *iocb, int flags)
{ struct scsi_qla_host *vha = sess->vha; struct qla_hw_data *ha = vha->hw; struct qla_tgt_mgmt_cmd *mcmd; struct atio_from_isp *a = (struct atio_from_isp *)iocb; struct qla_qpair_hint *h = &vha->vha_tgt.qla_tgt->qphints[0];
mcmd = mempool_alloc(qla_tgt_mgmt_cmd_mempool, GFP_ATOMIC); if (!mcmd) {
ql_dbg(ql_dbg_tgt_tmr, vha, 0x10009, "qla_target(%d): Allocation of management " "command failed, some commands and their data could " "leak\n", vha->vp_idx); return -ENOMEM;
}
memset(mcmd, 0, sizeof(*mcmd));
mcmd->sess = sess;
/* find other sess with nport_id collision */ if (port_id.b24 == other_sess->d_id.b24) { if (loop_id != other_sess->loop_id) {
ql_dbg(ql_dbg_disc, vha, 0x1000c, "Invalidating sess %p loop_id %d wwn %llx.\n",
other_sess, other_sess->loop_id, other_wwn);
/* * logout_on_delete is set by default, but another * session that has the same s_id/loop_id combo * might have cleared it when requested this session * deletion, so don't touch it
*/
qlt_schedule_sess_for_deletion(other_sess);
} else { /* * Another wwn used to have our s_id/loop_id * kill the session, but don't free the loop_id
*/
ql_dbg(ql_dbg_disc, vha, 0xf01b, "Invalidating sess %p loop_id %d wwn %llx.\n",
other_sess, other_sess->loop_id, other_wwn);
if (iocb->u.isp24.status_subcode == ELS_PLOGI) { /* remote port has assigned Port ID */ if (N2N_TOPO(vha->hw) && fcport_is_bigger(sess))
vha->d_id = sess->d_id;
switch (sess->disc_state) { case DSC_DELETED: case DSC_LOGIN_PEND:
qlt_plogi_ack_unref(vha, pla); break;
default: /* * Under normal circumstances we want to release nport handle * during LOGO process to avoid nport handle leaks inside FW. * The exception is when LOGO is done while another PLOGI with * the same nport handle is waiting as might be the case here. * Note: there is always a possibily of a race where session * deletion has already started for other reasons (e.g. ACL * removal) and now PLOGI arrives: * 1. if PLOGI arrived in FW after nport handle has been freed, * FW must have assigned this PLOGI a new/same handle and we * can proceed ACK'ing it as usual when session deletion * completes. * 2. if PLOGI arrived in FW before LOGO with LCF_FREE_NPORT * bit reached it, the handle has now been released. We'll * get an error when we ACK this PLOGI. Nothing will be sent * back to initiator. Initiator should eventually retry * PLOGI and situation will correct itself.
*/
sess->keep_nport_handle = ((sess->loop_id == loop_id) &&
(sess->d_id.b24 == port_id.b24));
ql_dbg(ql_dbg_disc, vha, 0x20f9, "%s %d %8phC post del sess\n",
__func__, __LINE__, sess->port_name);
ql_dbg(ql_dbg_disc, vha, 0xf026, "qla_target(%d): Port ID: %02x:%02x:%02x ELS opcode: 0x%02x lid %d %8phC\n",
vha->vp_idx, iocb->u.isp24.port_id[2],
iocb->u.isp24.port_id[1], iocb->u.isp24.port_id[0],
iocb->u.isp24.status_subcode, loop_id,
iocb->u.isp24.port_name);
/* res = 1 means ack at the end of thread * res = 0 means ack async/later.
*/ switch (iocb->u.isp24.status_subcode) { case ELS_PLOGI:
res = qlt_handle_login(vha, iocb); break;
case ELS_PRLI: if (N2N_TOPO(ha)) {
sess = qla2x00_find_fcport_by_wwpn(vha,
iocb->u.isp24.port_name, 1);
if (vha->hw->flags.edif_enabled && sess &&
(!(sess->flags & FCF_FCSP_DEVICE) ||
!sess->edif.authok)) {
ql_dbg(ql_dbg_disc, vha, 0xffff, "%s %d %8phC Term PRLI due to unauthorize PRLI\n",
__func__, __LINE__, iocb->u.isp24.port_name);
qlt_send_term_imm_notif(vha, iocb, 1); break;
}
if (sess && sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN]) {
ql_dbg(ql_dbg_disc, vha, 0xffff, "%s %d %8phC Term PRLI due to PLOGI ACK not completed\n",
__func__, __LINE__,
iocb->u.isp24.port_name);
qlt_send_term_imm_notif(vha, iocb, 1); break;
}
case DSC_LOGIN_PEND: case DSC_GPDB: case DSC_LOGIN_COMPLETE: case DSC_ADISC: delete = false; break; default: break;
}
if (delete) {
spin_unlock_irqrestore(&tgt->ha->tgt.sess_lock,
flags); /* * Impatient initiator sent PRLI before last * PLOGI could finish. Will force him to re-try, * while last one finishes.
*/
ql_log(ql_log_warn, sess->vha, 0xf095, "sess %p PRLI received, before plogi ack.\n",
sess);
qlt_send_term_imm_notif(vha, iocb, 1);
res = 0; break;
}
/* * This shouldn't happen under normal circumstances, * since we have deleted the old session during PLOGI
*/
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf096, "PRLI (loop_id %#04x) for existing sess %p (loop_id %#04x)\n",
sess->loop_id, sess, iocb->u.isp24.nport_handle);
res = qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS_SESS);
ql_dbg(ql_dbg_disc, vha, 0x20fc, "%s: logo %llx res %d sess %p ",
__func__, wwn, res, sess); if (res == 0) { /* * cmd went upper layer, look for qlt_xmit_tm_rsp() * for LOGO_ACK & sess delete
*/
BUG_ON(!sess);
res = 0;
} else { /* cmd did not go to upper layer. */ if (sess) {
qlt_schedule_sess_for_deletion(sess);
res = 0;
} /* else logo will be ack */
} break; case ELS_PDISC: case ELS_ADISC:
{ struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
case IMM_NTFY_LIP_LINK_REINIT:
{ struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf033, "qla_target(%d): LINK REINIT (loop %#x, " "subcode %x)\n", vha->vp_idx,
le16_to_cpu(iocb->u.isp24.nport_handle),
iocb->u.isp24.status_subcode); if (tgt->link_reinit_iocb_pending) {
qlt_send_notify_ack(ha->base_qpair,
&tgt->link_reinit_iocb, 0, 0, 0, 0, 0, 0);
}
memcpy(&tgt->link_reinit_iocb, iocb, sizeof(*iocb));
tgt->link_reinit_iocb_pending = 1; /* * QLogic requires to wait after LINK REINIT for possible * PDISC or ADISC ELS commands
*/
send_notify_ack = 0; break;
}
case IMM_NTFY_PORT_LOGOUT:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf034, "qla_target(%d): Port logout (loop " "%#x, subcode %x)\n", vha->vp_idx,
le16_to_cpu(iocb->u.isp24.nport_handle),
iocb->u.isp24.status_subcode);
if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS_SESS) == 0)
send_notify_ack = 0; /* The sessions will be cleared in the callback, if needed */ break;
case IMM_NTFY_GLBL_TPRLO:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf035, "qla_target(%d): Global TPRLO (%x)\n", vha->vp_idx, status); if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS) == 0)
send_notify_ack = 0; /* The sessions will be cleared in the callback, if needed */ break;
case IMM_NTFY_PORT_CONFIG:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf036, "qla_target(%d): Port config changed (%x)\n", vha->vp_idx,
status); if (qlt_reset(vha, iocb, QLA_TGT_ABORT_ALL) == 0)
send_notify_ack = 0; /* The sessions will be cleared in the callback, if needed */ break;
case IMM_NTFY_GLBL_LOGO:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06a, "qla_target(%d): Link failure detected\n",
vha->vp_idx); /* I_T nexus loss */ if (qlt_reset(vha, iocb, QLA_TGT_NEXUS_LOSS) == 0)
send_notify_ack = 0; break;
case IMM_NTFY_IOCB_OVERFLOW:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06b, "qla_target(%d): Cannot provide requested " "capability (IOCB overflowed the immediate notify " "resource count)\n", vha->vp_idx); break;
case IMM_NTFY_ABORT_TASK:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf037, "qla_target(%d): Abort Task (S %08x I %#x -> " "L %#x)\n", vha->vp_idx,
le16_to_cpu(iocb->u.isp2x.seq_id),
GET_TARGET_ID(ha, (struct atio_from_isp *)iocb),
le16_to_cpu(iocb->u.isp2x.lun)); if (qlt_abort_task(vha, iocb) == 0)
send_notify_ack = 0; break;
case IMM_NTFY_RESOURCE:
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf06c, "qla_target(%d): Out of resources, host %ld\n",
vha->vp_idx, vha->host_no); break;
/* * This routine is used to allocate a command for either a QFull condition * (ie reply SAM_STAT_BUSY) or to terminate an exchange that did not go * out previously.
*/ staticvoid
qlt_alloc_qfull_cmd(struct scsi_qla_host *vha, struct atio_from_isp *atio, uint16_t status, int qfull)
{ struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; struct qla_hw_data *ha = vha->hw; struct fc_port *sess; struct qla_tgt_cmd *cmd; unsignedlong flags;
if (unlikely(tgt->tgt_stop)) {
ql_dbg(ql_dbg_io, vha, 0x300a, "New command while device %p is shutting down\n", tgt); return;
}
if ((vha->hw->tgt.num_qfull_cmds_alloc + 1) > MAX_QFULL_CMDS_ALLOC) {
vha->hw->tgt.num_qfull_cmds_dropped++; if (vha->hw->tgt.num_qfull_cmds_dropped >
vha->qla_stats.stat_max_qfull_cmds_dropped)
vha->qla_stats.stat_max_qfull_cmds_dropped =
vha->hw->tgt.num_qfull_cmds_dropped;
if (ha->tgt.num_pend_cmds < Q_FULL_THRESH_HOLD(ha)) return 0;
if (!ha_locked)
spin_lock_irqsave(&ha->hardware_lock, flags);
qlt_send_busy(qpair, atio, qla_sam_status); if (!ha_locked)
spin_unlock_irqrestore(&ha->hardware_lock, flags);
return 1;
}
/* ha->hardware_lock supposed to be held on entry */ /* called via callback from qla2xxx */ staticvoid qlt_24xx_atio_pkt(struct scsi_qla_host *vha, struct atio_from_isp *atio, uint8_t ha_locked)
{ struct qla_hw_data *ha = vha->hw; struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; int rc; unsignedlong flags = 0;
if (unlikely(tgt == NULL)) {
ql_dbg(ql_dbg_tgt, vha, 0x3064, "ATIO pkt, but no tgt (ha %p)", ha); return;
} /* * In tgt_stop mode we also should allow all requests to pass. * Otherwise, some commands can stuck.
*/
tgt->atio_irq_cmd_count++;
switch (atio->u.raw.entry_type) { case ATIO_TYPE7: if (unlikely(atio->u.isp24.exchange_addr ==
cpu_to_le32(ATIO_EXCHANGE_ADDRESS_UNKNOWN))) {
ql_dbg(ql_dbg_io, vha, 0x3065, "qla_target(%d): ATIO_TYPE7 " "received with UNKNOWN exchange address, " "sending QUEUE_FULL\n", vha->vp_idx); if (!ha_locked)
spin_lock_irqsave(&ha->hardware_lock, flags);
qlt_send_busy(ha->base_qpair, atio, qla_sam_status); if (!ha_locked)
spin_unlock_irqrestore(&ha->hardware_lock,
flags); break;
}
if (likely(atio->u.isp24.fcp_cmnd.task_mgmt_flags == 0)) {
rc = qlt_chk_qfull_thresh_hold(vha, ha->base_qpair,
atio, ha_locked); if (rc != 0) {
tgt->atio_irq_cmd_count--; return;
}
rc = qlt_handle_cmd_for_atio(vha, atio);
} else {
rc = qlt_handle_task_mgmt(vha, atio);
} if (unlikely(rc != 0)) { if (!ha_locked)
spin_lock_irqsave(&ha->hardware_lock, flags); switch (rc) { case -ENODEV:
ql_dbg(ql_dbg_tgt, vha, 0xe05f, "qla_target: Unable to send command to target\n"); break; case -EBADF:
ql_dbg(ql_dbg_tgt, vha, 0xe05f, "qla_target: Unable to send command to target, sending TERM EXCHANGE for rsp\n");
qlt_send_term_exchange(ha->base_qpair, NULL,
atio, 1, 0); break; case -EBUSY:
ql_dbg(ql_dbg_tgt, vha, 0xe060, "qla_target(%d): Unable to send command to target, sending BUSY status\n",
vha->vp_idx);
qlt_send_busy(ha->base_qpair, atio,
tc_sam_status); break; default:
ql_dbg(ql_dbg_tgt, vha, 0xe060, "qla_target(%d): Unable to send command to target, sending BUSY status\n",
vha->vp_idx);
qlt_send_busy(ha->base_qpair, atio,
qla_sam_status); break;
} if (!ha_locked)
spin_unlock_irqrestore(&ha->hardware_lock,
flags);
} break;
case IMMED_NOTIFY_TYPE:
{ if (unlikely(atio->u.isp2x.entry_status != 0)) {
ql_dbg(ql_dbg_tgt, vha, 0xe05b, "qla_target(%d): Received ATIO packet %x " "with error status %x\n", vha->vp_idx,
atio->u.raw.entry_type,
atio->u.isp2x.entry_status); break;
}
ql_dbg(ql_dbg_tgt, vha, 0xe02e, "%s", "IMMED_NOTIFY ATIO");
if (!ha_locked)
spin_lock_irqsave(&ha->hardware_lock, flags);
qlt_handle_imm_notify(vha, (struct imm_ntfy_from_isp *)atio); if (!ha_locked)
spin_unlock_irqrestore(&ha->hardware_lock, flags); break;
}
/* * qpair lock is assume to be held * rc = 0 : send terminate & abts respond * rc != 0: do not send term & abts respond
*/ staticint qlt_chk_unresolv_exchg(struct scsi_qla_host *vha, struct qla_qpair *qpair, struct abts_resp_from_24xx_fw *entry)
{ struct qla_hw_data *ha = vha->hw; int rc = 0;
/* * Detect unresolved exchange. If the same ABTS is unable * to terminate an existing command and the same ABTS loops * between FW & Driver, then force FW dump. Under 1 jiff, * we should see multiple loops.
*/ if (qpair->retry_term_exchg_addr == entry->exchange_addr_to_abort &&
qpair->retry_term_jiff == jiffies) { /* found existing exchange */
qpair->retry_term_cnt++; if (qpair->retry_term_cnt >= 5) {
rc = -EIO;
qpair->retry_term_cnt = 0;
ql_log(ql_log_warn, vha, 0xffff, "Unable to send ABTS Respond. Dumping firmware.\n");
ql_dump_buffer(ql_dbg_tgt_mgt + ql_dbg_buffer,
vha, 0xffff, (uint8_t *)entry, sizeof(*entry));
if (qpair == ha->base_qpair)
ha->isp_ops->fw_dump(vha); else
qla2xxx_dump_fw(vha);
/* ha->hardware_lock supposed to be held on entry */ /* called via callback from qla2xxx */ staticvoid qlt_response_pkt(struct scsi_qla_host *vha, struct rsp_que *rsp, response_t *pkt)
{ struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
if (unlikely(tgt == NULL)) {
ql_dbg(ql_dbg_tgt, vha, 0xe05d, "qla_target(%d): Response pkt %x received, but no tgt (ha %p)\n",
vha->vp_idx, pkt->entry_type, vha->hw); return;
}
/* * In tgt_stop mode we also should allow all requests to pass. * Otherwise, some commands can stuck.
*/
switch (pkt->entry_type) { case CTIO_CRC2: case CTIO_TYPE7:
{ struct ctio7_from_24xx *entry = (struct ctio7_from_24xx *)pkt;
/* * ha->hardware_lock supposed to be held on entry. Might drop it, then reaquire
*/ void qlt_async_event(uint16_t code, struct scsi_qla_host *vha,
uint16_t *mailbox)
{ struct qla_hw_data *ha = vha->hw; struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; int login_code;
if (!tgt || tgt->tgt_stop || tgt->tgt_stopped) return;
if (((code == MBA_POINT_TO_POINT) || (code == MBA_CHG_IN_CONNECTION)) &&
IS_QLA2100(ha)) return; /* * In tgt_stop mode we also should allow all requests to pass. * Otherwise, some commands can stuck.
*/
switch (code) { case MBA_RESET: /* Reset */ case MBA_SYSTEM_ERR: /* System Error */ case MBA_REQ_TRANSFER_ERR: /* Request Transfer Error */ case MBA_RSP_TRANSFER_ERR: /* Response Transfer Error */
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf03a, "qla_target(%d): System error async event %#x " "occurred", vha->vp_idx, code); break; case MBA_WAKEUP_THRES: /* Request Queue Wake-up. */
set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); break;
switch (vha->host->active_mode) { case MODE_INITIATOR: case MODE_DUAL: if (newfcport) { if (!IS_IIDMA_CAPABLE(vha->hw) || !vha->hw->flags.gpsc_supported) {
qla24xx_sched_upd_fcport(fcport);
} else {
ql_dbg(ql_dbg_disc, vha, 0x20ff, "%s %d %8phC post gpsc fcp_cnt %d\n",
__func__, __LINE__, fcport->port_name, vha->fcport_count);
qla24xx_post_gpsc_work(vha, fcport);
}
} break;
case MODE_TARGET: default: break;
} if (del)
qla2x00_free_fcport(del);
return fcport;
}
/* Must be called under tgt_mutex */ staticstruct fc_port *qlt_make_local_sess(struct scsi_qla_host *vha,
be_id_t s_id)
{ struct fc_port *sess = NULL;
fc_port_t *fcport = NULL; int rc, global_resets;
uint16_t loop_id = 0;
if (s_id.domain == 0xFF && s_id.area == 0xFC) { /* * This is Domain Controller, so it should be * OK to drop SCSI commands from it.
*/
ql_dbg(ql_dbg_tgt_mgt, vha, 0xf042, "Unable to find initiator with S_ID %x:%x:%x",
s_id.domain, s_id.area, s_id.al_pa); return NULL;
}
/* * This work can be scheduled on several CPUs at time, so we * must delete the entry to eliminate double processing
*/
list_del(&prm->sess_works_list_entry);
/* Must be called under tgt_host_action_mutex */ int qlt_add_target(struct qla_hw_data *ha, struct scsi_qla_host *base_vha)
{ struct qla_tgt *tgt; int rc, i; struct qla_qpair_hint *h;
if (!QLA_TGT_MODE_ENABLED()) return 0;
if (!IS_TGT_MODE_CAPABLE(ha)) {
ql_log(ql_log_warn, base_vha, 0xe070, "This adapter does not support target mode.\n"); return 0;
}
ql_dbg(ql_dbg_tgt, base_vha, 0xe03b, "Registering target for host %ld(%p).\n", base_vha->host_no, ha);
BUG_ON(base_vha->vha_tgt.qla_tgt != NULL);
tgt = kzalloc(sizeof(struct qla_tgt), GFP_KERNEL); if (!tgt) {
ql_dbg(ql_dbg_tgt, base_vha, 0xe066, "Unable to allocate struct qla_tgt\n"); return -ENOMEM;
}
if (ha->tgt.tgt_ops && ha->tgt.tgt_ops->add_target)
ha->tgt.tgt_ops->add_target(base_vha);
return 0;
}
/* Must be called under tgt_host_action_mutex */ int qlt_remove_target(struct qla_hw_data *ha, struct scsi_qla_host *vha)
{ if (!vha->vha_tgt.qla_tgt) return 0;
if (vha->fc_vport) {
qlt_release(vha->vha_tgt.qla_tgt); return 0;
}
/* free left over qfull cmds */
qlt_init_term_exchange(vha);
/* Must be called under HW lock */ void qlt_set_mode(struct scsi_qla_host *vha)
{ switch (vha->qlini_mode) { case QLA2XXX_INI_MODE_DISABLED: case QLA2XXX_INI_MODE_EXCLUSIVE:
vha->host->active_mode = MODE_TARGET; break; case QLA2XXX_INI_MODE_ENABLED:
vha->host->active_mode = MODE_INITIATOR; break; case QLA2XXX_INI_MODE_DUAL:
vha->host->active_mode = MODE_DUAL; break; default: break;
}
}
/* Must be called under HW lock */ staticvoid qlt_clear_mode(struct scsi_qla_host *vha)
{ switch (vha->qlini_mode) { case QLA2XXX_INI_MODE_DISABLED:
vha->host->active_mode = MODE_UNKNOWN; break; case QLA2XXX_INI_MODE_EXCLUSIVE:
vha->host->active_mode = MODE_INITIATOR; break; case QLA2XXX_INI_MODE_ENABLED: case QLA2XXX_INI_MODE_DUAL:
vha->host->active_mode = MODE_INITIATOR; break; default: break;
}
}
/* * We are expecting the offline state. * QLA_FUNCTION_FAILED means that adapter is offline.
*/ if (qla2x00_wait_for_hba_online(vha) != QLA_SUCCESS)
ql_dbg(ql_dbg_tgt, vha, 0xe081, "adapter is offline\n");
}
/* * Called from qla_init.c:qla24xx_vport_create() contex to setup * the target mode specific struct scsi_qla_host and struct qla_hw_data * members.
*/ void
qlt_vport_create(struct scsi_qla_host *vha, struct qla_hw_data *ha)
{
vha->vha_tgt.qla_tgt = NULL;
/* * NOTE: Currently the value is kept the same for <24xx and * >=24xx ISPs. If it is necessary to change it, * the check should be added for specific ISPs, * assigning the value appropriately.
*/
ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX;
qlt_add_target(ha, vha);
}
u8
qlt_rff_id(struct scsi_qla_host *vha)
{
u8 fc4_feature = 0; /* * FC-4 Feature bit 0 indicates target functionality to the name server.
*/ if (qla_tgt_mode_enabled(vha)) {
fc4_feature = BIT_0;
} elseif (qla_ini_mode_enabled(vha)) {
fc4_feature = BIT_1;
} elseif (qla_dual_mode_enabled(vha))
fc4_feature = BIT_0 | BIT_1;
return fc4_feature;
}
/* * qlt_init_atio_q_entries() - Initializes ATIO queue entries. * @ha: HA context * * Beginning of ATIO ring has initialization control block already built * by nvram config routine. * * Returns 0 on success.
*/ void
qlt_init_atio_q_entries(struct scsi_qla_host *vha)
{ struct qla_hw_data *ha = vha->hw;
uint16_t cnt; struct atio_from_isp *pkt = (struct atio_from_isp *)ha->tgt.atio_ring;
if (unlikely(fcpcmd_is_corrupted(ha->tgt.atio_ring_ptr))) { /* * This packet is corrupted. The header + payload * can not be trusted. There is no point in passing * it further up.
*/
ql_log(ql_log_warn, vha, 0xd03c, "corrupted fcp frame SID[%3phN] OXID[%04x] EXCG[%x] %64phN\n",
&pkt->u.isp24.fcp_hdr.s_id,
be16_to_cpu(pkt->u.isp24.fcp_hdr.ox_id),
pkt->u.isp24.exchange_addr, pkt);
if (qla_tgt_mode_enabled(vha) || qla_dual_mode_enabled(vha)) { if (!ha->tgt.saved_set) { /* We save only once */
ha->tgt.saved_exchange_count = nv->exchange_count;
ha->tgt.saved_firmware_options_1 =
nv->firmware_options_1;
ha->tgt.saved_firmware_options_2 =
nv->firmware_options_2;
ha->tgt.saved_firmware_options_3 =
nv->firmware_options_3;
ha->tgt.saved_set = 1;
}
/* Disable ini mode, if requested */ if (qla_tgt_mode_enabled(vha))
nv->firmware_options_1 |= cpu_to_le32(BIT_5);
/* Disable Full Login after LIP */
nv->firmware_options_1 &= cpu_to_le32(~BIT_13); /* Enable initial LIP */
nv->firmware_options_1 &= cpu_to_le32(~BIT_9); if (ql2xtgt_tape_enable) /* Enable FC Tape support */
nv->firmware_options_2 |= cpu_to_le32(BIT_12); else /* Disable FC Tape support */
nv->firmware_options_2 &= cpu_to_le32(~BIT_12);
/* Disable Full Login after LIP */
nv->host_p &= cpu_to_le32(~BIT_10);
/* * clear BIT 15 explicitly as we have seen at least * a couple of instances where this was set and this * was causing the firmware to not be initialized.
*/
nv->firmware_options_1 &= cpu_to_le32(~BIT_15); /* Enable target PRLI control */
nv->firmware_options_2 |= cpu_to_le32(BIT_14);
if (qla_tgt_mode_enabled(vha) || qla_dual_mode_enabled(vha)) { if (!ha->tgt.saved_set) { /* We save only once */
ha->tgt.saved_exchange_count = nv->exchange_count;
ha->tgt.saved_firmware_options_1 =
nv->firmware_options_1;
ha->tgt.saved_firmware_options_2 =
nv->firmware_options_2;
ha->tgt.saved_firmware_options_3 =
nv->firmware_options_3;
ha->tgt.saved_set = 1;
}
/* Disable ini mode, if requested */ if (qla_tgt_mode_enabled(vha))
nv->firmware_options_1 |= cpu_to_le32(BIT_5); /* Disable Full Login after LIP */
nv->firmware_options_1 &= cpu_to_le32(~BIT_13); /* Enable initial LIP */
nv->firmware_options_1 &= cpu_to_le32(~BIT_9); /* * clear BIT 15 explicitly as we have seen at * least a couple of instances where this was set * and this was causing the firmware to not be * initialized.
*/
nv->firmware_options_1 &= cpu_to_le32(~BIT_15); if (ql2xtgt_tape_enable) /* Enable FC tape support */
nv->firmware_options_2 |= cpu_to_le32(BIT_12); else /* Disable FC tape support */
nv->firmware_options_2 &= cpu_to_le32(~BIT_12);
/* Disable Full Login after LIP */
nv->host_p &= cpu_to_le32(~BIT_10); /* Enable target PRLI control */
nv->firmware_options_2 |= cpu_to_le32(BIT_14);
if (!op) { /* do not reach for ATIO queue here. This is best effort err * recovery at this point.
*/
qlt_response_pkt_all_vps(vha, rsp, pkt); return;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.131Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.