// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Broadcom. All Rights Reserved. The term * “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. */ #include "efct_driver.h" #include "efct_hw.h" #define enable_tsend_auto_resp(efct) 1 #define enable_treceive_auto_resp(efct) 0 #define SCSI_IOFMT "[%04x][i:%04x t:%04x h:%04x]" #define scsi_io_printf(io, fmt, ...) \ efc_log_debug(io->efct, "[%s]" SCSI_IOFMT fmt, \ io->node->display_name, io->instance_index,\ io->init_task_tag, io->tgt_task_tag, io->hw_tag, ##__VA_ARGS__) #define EFCT_LOG_ENABLE_SCSI_TRACE(efct) \ (((efct) != NULL) ? (((efct)->logmask & (1U << 2)) != 0) : 0) #define scsi_io_trace(io, fmt, ...) \ do { \ if (EFCT_LOG_ENABLE_SCSI_TRACE(io->efct)) \ scsi_io_printf(io, fmt, ##__VA_ARGS__); \ } while (0) struct efct_io * efct_scsi_io_alloc(struct efct_node *node) { struct efct *efct; struct efct_xport *xport; struct efct_io *io; unsigned long flags; efct = node->efct; xport = efct->xport; io = efct_io_pool_io_alloc(efct->xport->io_pool); if (!io) { efc_log_err(efct, "IO alloc Failed\n"); atomic_add_return(1, &xport->io_alloc_failed_count); return NULL; } /* initialize refcount */ kref_init(&io->ref); io->release = _efct_scsi_io_free; /* set generic fields */ io->efct = efct; io->node = node; kref_get(&node->ref); /* set type and name */ io->io_type = EFCT_IO_TYPE_IO; io->display_name = "scsi_io"; io->cmd_ini = false; io->cmd_tgt = true; /* Add to node's active_ios list */ INIT_LIST_HEAD(&io->list_entry); spin_lock_irqsave(&node->active_ios_lock, flags); list_add(&io->list_entry, &node->active_ios); spin_unlock_irqrestore(&node->active_ios_lock, flags); return io; } void _efct_scsi_io_free(struct kref *arg) { struct efct_io *io = container_of(arg, struct efct_io, ref); struct efct *efct = io->efct; struct efct_node *node = io->node; unsigned long flags = 0; scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name); if (io->io_free) { efc_log_err(efct, "IO already freed.\n"); return; } spin_lock_irqsave(&node->active_ios_lock, flags); list_del_init(&io->list_entry); spin_unlock_irqrestore(&node->active_ios_lock, flags); kref_put(&node->ref, node->release); io->node = NULL; efct_io_pool_io_free(efct->xport->io_pool, io); } void efct_scsi_io_free(struct efct_io *io) { scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name); WARN_ON(!refcount_read(&io->ref.refcount)); kref_put(&io->ref, io->release); } static void efct_target_io_cb(struct efct_hw_io *hio, u32 length, int status, u32 ext_status, void *app) { u32 flags = 0; struct efct_io *io = app; struct efct *efct; enum efct_scsi_io_status scsi_stat = EFCT_SCSI_STATUS_GOOD; efct_scsi_io_cb_t cb; if (!io || !io->efct) { pr_err("%s: IO can not be NULL\n", __func__); return; } scsi_io_trace(io, "status x%x ext_status x%x\n", status, ext_status); efct = io->efct; io->transferred += length; if (!io->scsi_tgt_cb) { efct_scsi_check_pending(efct); return; } /* Call target server completion */ cb = io->scsi_tgt_cb; /* Clear the callback before invoking the callback */ io->scsi_tgt_cb = NULL; /* if status was good, and auto-good-response was set, * then callback target-server with IO_CMPL_RSP_SENT, * otherwise send IO_CMPL */ if (status == 0 && io->auto_resp) flags |= EFCT_SCSI_IO_CMPL_RSP_SENT; else flags |= EFCT_SCSI_IO_CMPL; switch (status) { case SLI4_FC_WCQE_STATUS_SUCCESS: scsi_stat = EFCT_SCSI_STATUS_GOOD; break; case SLI4_FC_WCQE_STATUS_DI_ERROR: if (ext_status & SLI4_FC_DI_ERROR_GE) scsi_stat = EFCT_SCSI_STATUS_DIF_GUARD_ERR; else if (ext_status & SLI4_FC_DI_ERROR_AE) scsi_stat = EFCT_SCSI_STATUS_DIF_APP_TAG_ERROR; else if (ext_status & SLI4_FC_DI_ERROR_RE) scsi_stat = EFCT_SCSI_STATUS_DIF_REF_TAG_ERROR; else scsi_stat = EFCT_SCSI_STATUS_DIF_UNKNOWN_ERROR; break; case SLI4_FC_WCQE_STATUS_LOCAL_REJECT: switch (ext_status) { case SLI4_FC_LOCAL_REJECT_INVALID_RELOFFSET: case SLI4_FC_LOCAL_REJECT_ABORT_REQUESTED: scsi_stat = EFCT_SCSI_STATUS_ABORTED; break; case SLI4_FC_LOCAL_REJECT_INVALID_RPI: scsi_stat = EFCT_SCSI_STATUS_NEXUS_LOST; break; case SLI4_FC_LOCAL_REJECT_NO_XRI: scsi_stat = EFCT_SCSI_STATUS_NO_IO; break; default: /*we have seen 0x0d(TX_DMA_FAILED err)*/ scsi_stat = EFCT_SCSI_STATUS_ERROR; break; } break; case SLI4_FC_WCQE_STATUS_TARGET_WQE_TIMEOUT: /* target IO timed out */ scsi_stat = EFCT_SCSI_STATUS_TIMEDOUT_AND_ABORTED; break; case SLI4_FC_WCQE_STATUS_SHUTDOWN: /* Target IO cancelled by HW */ scsi_stat = EFCT_SCSI_STATUS_SHUTDOWN; break; default: scsi_stat = EFCT_SCSI_STATUS_ERROR; break; } cb(io, scsi_stat, flags, io->scsi_tgt_cb_arg); efct_scsi_check_pending(efct); } static int efct_scsi_build_sgls(struct efct_hw *hw, struct efct_hw_io *hio, struct efct_scsi_sgl *sgl, u32 sgl_count, enum efct_hw_io_type type) { int rc; u32 i; struct efct *efct = hw->os; /* Initialize HW SGL */ rc = efct_hw_io_init_sges(hw, hio, type); if (rc) { efc_log_err(efct, "efct_hw_io_init_sges failed: %d\n", rc); return -EIO; } for (i = 0; i < sgl_count; i++) { /* Add data SGE */ rc = efct_hw_io_add_sge(hw, hio, sgl[i].addr, sgl[i].len); if (rc) { efc_log_err(efct, "add sge failed cnt=%d rc=%d\n", sgl_count, rc); return rc; } } return 0; } static void efc_log_sgl(struct efct_io *io) { struct efct_hw_io *hio = io->hio; struct sli4_sge *data = NULL; u32 *dword = NULL; u32 i; u32 n_sge; scsi_io_trace(io, "def_sgl at 0x%x 0x%08x\n", upper_32_bits(hio->def_sgl.phys), lower_32_bits(hio->def_sgl.phys)); n_sge = (hio->sgl == &hio->def_sgl) ? hio->n_sge : hio->def_sgl_count; for (i = 0, data = hio->def_sgl.virt; i < n_sge; i++, data++) { dword = (u32 *)data; scsi_io_trace(io, "SGL %2d 0x%08x 0x%08x 0x%08x 0x%08x\n", i, dword[0], dword[1], dword[2], dword[3]); if (dword[2] & (1U << 31)) break; } } static void efct_scsi_check_pending_async_cb(struct efct_hw *hw, int status, u8 *mqe, void *arg) { struct efct_io *io = arg; if (io) { efct_hw_done_t cb = io->hw_cb; if (!io->hw_cb) return; io->hw_cb = NULL; (cb)(io->hio, 0, SLI4_FC_WCQE_STATUS_DISPATCH_ERROR, 0, io); } } static int efct_scsi_io_dispatch_hw_io(struct efct_io *io, struct efct_hw_io *hio) { int rc = 0; struct efct *efct = io->efct; /* Got a HW IO; * update ini/tgt_task_tag with HW IO info and dispatch */ io->hio = hio; if (io->cmd_tgt) io->tgt_task_tag = hio->indicator; else if (io->cmd_ini) io->init_task_tag = hio->indicator; io->hw_tag = hio->reqtag; hio->eq = io->hw_priv; /* Copy WQ steering */ switch (io->wq_steering) { case EFCT_SCSI_WQ_STEERING_CLASS >> EFCT_SCSI_WQ_STEERING_SHIFT: hio->wq_steering = EFCT_HW_WQ_STEERING_CLASS; break; case EFCT_SCSI_WQ_STEERING_REQUEST >> EFCT_SCSI_WQ_STEERING_SHIFT: hio->wq_steering = EFCT_HW_WQ_STEERING_REQUEST; break; case EFCT_SCSI_WQ_STEERING_CPU >> EFCT_SCSI_WQ_STEERING_SHIFT: hio->wq_steering = EFCT_HW_WQ_STEERING_CPU; break; } switch (io->io_type) { case EFCT_IO_TYPE_IO: rc = efct_scsi_build_sgls(&efct->hw, io->hio, io->sgl, io->sgl_count, io->hio_type); if (rc) break; if (EFCT_LOG_ENABLE_SCSI_TRACE(efct)) efc_log_sgl(io); if (io->app_id) io->iparam.fcp_tgt.app_id = io->app_id; io->iparam.fcp_tgt.vpi = io->node->vpi; io->iparam.fcp_tgt.rpi = io->node->rpi; io->iparam.fcp_tgt.s_id = io->node->port_fc_id; io->iparam.fcp_tgt.d_id = io->node->node_fc_id; io->iparam.fcp_tgt.xmit_len = io->wire_len; rc = efct_hw_io_send(&io->efct->hw, io->hio_type, io->hio, &io->iparam, io->hw_cb, io); break; default: scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type); rc = -EIO; break; } return rc; } static int efct_scsi_io_dispatch_no_hw_io(struct efct_io *io) { int rc; switch (io->io_type) { case EFCT_IO_TYPE_ABORT: { struct efct_hw_io *hio_to_abort = NULL; hio_to_abort = io->io_to_abort->hio; if (!hio_to_abort) { /* * If "IO to abort" does not have an * associated HW IO, immediately make callback with * success. The command must have been sent to * the backend, but the data phase has not yet * started, so we don't have a HW IO. * * Note: since the backend shims should be * taking a reference on io_to_abort, it should not * be possible to have been completed and freed by * the backend before the abort got here. */ scsi_io_printf(io, "IO: not active\n"); ((efct_hw_done_t)io->hw_cb)(io->hio, 0, SLI4_FC_WCQE_STATUS_SUCCESS, 0, io); rc = 0; break; } /* HW IO is valid, abort it */ scsi_io_printf(io, "aborting\n"); rc = efct_hw_io_abort(&io->efct->hw, hio_to_abort, io->send_abts, io->hw_cb, io); if (rc) { int status = SLI4_FC_WCQE_STATUS_SUCCESS; efct_hw_done_t cb = io->hw_cb; if (rc != -ENOENT && rc != -EINPROGRESS) { status = -1; scsi_io_printf(io, "Failed to abort IO rc=%d\n", rc); } cb(io->hio, 0, status, 0, io); rc = 0; } break; } default: scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type); rc = -EIO; break; } return rc; } static struct efct_io * efct_scsi_dispatch_pending(struct efct *efct) { struct efct_xport *xport = efct->xport; struct efct_io *io = NULL; struct efct_hw_io *hio; unsigned long flags = 0; int status; spin_lock_irqsave(&xport->io_pending_lock, flags); if (!list_empty(&xport->io_pending_list)) { io = list_first_entry(&xport->io_pending_list, struct efct_io, io_pending_link); list_del_init(&io->io_pending_link); } if (!io) { spin_unlock_irqrestore(&xport->io_pending_lock, flags); return NULL; } if (io->io_type == EFCT_IO_TYPE_ABORT) { hio = NULL; } else { hio = efct_hw_io_alloc(&efct->hw); if (!hio) { /* * No HW IO available.Put IO back on * the front of pending list */ list_add(&xport->io_pending_list, &io->io_pending_link); io = NULL; } else { hio->eq = io->hw_priv; } } /* Must drop the lock before dispatching the IO */ spin_unlock_irqrestore(&xport->io_pending_lock, flags); if (!io) return NULL; /* * We pulled an IO off the pending list, * and either got an HW IO or don't need one */ atomic_sub_return(1, &xport->io_pending_count); if (!hio) status = efct_scsi_io_dispatch_no_hw_io(io); else status = efct_scsi_io_dispatch_hw_io(io, hio); if (status) { /* * Invoke the HW callback, but do so in the * separate execution context,provided by the * NOP mailbox completion processing context * by using efct_hw_async_call() */ if (efct_hw_async_call(&efct->hw, efct_scsi_check_pending_async_cb, io)) { efc_log_debug(efct, "call hw async failed\n"); } } return io; } void efct_scsi_check_pending(struct efct *efct) { struct efct_xport *xport = efct->xport; struct efct_io *io = NULL; int count = 0; unsigned long flags = 0; int dispatch = 0; /* Guard against recursion */ if (atomic_add_return(1, &xport->io_pending_recursing)) { /* This function is already running. Decrement and return. */ atomic_sub_return(1, &xport->io_pending_recursing); return; } while (efct_scsi_dispatch_pending(efct)) count++; if (count) { atomic_sub_return(1, &xport->io_pending_recursing); return; } /* * If nothing was removed from the list, * we might be in a case where we need to abort an * active IO and the abort is on the pending list. * Look for an abort we can dispatch. */ spin_lock_irqsave(&xport->io_pending_lock, flags); list_for_each_entry(io, &xport->io_pending_list, io_pending_link) { if (io->io_type == EFCT_IO_TYPE_ABORT && io->io_to_abort->hio) { /* This IO has a HW IO, so it is * active. Dispatch the abort. */ dispatch = 1; list_del_init(&io->io_pending_link); atomic_sub_return(1, &xport->io_pending_count); break; } } spin_unlock_irqrestore(&xport->io_pending_lock, flags); if (dispatch) { if (efct_scsi_io_dispatch_no_hw_io(io)) { if (efct_hw_async_call(&efct->hw, efct_scsi_check_pending_async_cb, io)) { efc_log_debug(efct, "hw async failed\n"); } } } atomic_sub_return(1, &xport->io_pending_recursing); } int efct_scsi_io_dispatch(struct efct_io *io, void *cb) { struct efct_hw_io *hio; struct efct *efct = io->efct; struct efct_xport *xport = efct->xport; unsigned long flags = 0; io->hw_cb = cb; /* * if this IO already has a HW IO, then this is either * not the first phase of the IO. Send it to the HW. */ if (io->hio) return efct_scsi_io_dispatch_hw_io(io, io->hio); /* * We don't already have a HW IO associated with the IO. First check * the pending list. If not empty, add IO to the tail and process the * pending list. */ spin_lock_irqsave(&xport->io_pending_lock, flags); if (!list_empty(&xport->io_pending_list)) { /* * If this is a low latency request, * the put at the front of the IO pending * queue, otherwise put it at the end of the queue. */ if (io->low_latency) { INIT_LIST_HEAD(&io->io_pending_link); list_add(&xport->io_pending_list, &io->io_pending_link); } else { INIT_LIST_HEAD(&io->io_pending_link); list_add_tail(&io->io_pending_link, &xport->io_pending_list); } spin_unlock_irqrestore(&xport->io_pending_lock, flags); atomic_add_return(1, &xport->io_pending_count); atomic_add_return(1, &xport->io_total_pending); /* process pending list */ efct_scsi_check_pending(efct); return 0; } spin_unlock_irqrestore(&xport->io_pending_lock, flags); /* * We don't have a HW IO associated with the IO and there's nothing * on the pending list. Attempt to allocate a HW IO and dispatch it. */ hio = efct_hw_io_alloc(&io->efct->hw); if (!hio) { /* Couldn't get a HW IO. Save this IO on the pending list */ spin_lock_irqsave(&xport->io_pending_lock, flags); INIT_LIST_HEAD(&io->io_pending_link); list_add_tail(&io->io_pending_link, &xport->io_pending_list); spin_unlock_irqrestore(&xport->io_pending_lock, flags); atomic_add_return(1, &xport->io_total_pending); atomic_add_return(1, &xport->io_pending_count); return 0; } /* We successfully allocated a HW IO; dispatch to HW */ return efct_scsi_io_dispatch_hw_io(io, hio); } int efct_scsi_io_dispatch_abort(struct efct_io *io, void *cb) { struct efct *efct = io->efct; struct efct_xport *xport = efct->xport; unsigned long flags = 0; io->hw_cb = cb; /* * For aborts, we don't need a HW IO, but we still want * to pass through the pending list to preserve ordering. * Thus, if the pending list is not empty, add this abort * to the pending list and process the pending list. */ spin_lock_irqsave(&xport->io_pending_lock, flags); if (!list_empty(&xport->io_pending_list)) { INIT_LIST_HEAD(&io->io_pending_link); list_add_tail(&io->io_pending_link, &xport->io_pending_list); spin_unlock_irqrestore(&xport->io_pending_lock, flags); atomic_add_return(1, &xport->io_pending_count); atomic_add_return(1, &xport->io_total_pending); /* process pending list */ efct_scsi_check_pending(efct); return 0; } spin_unlock_irqrestore(&xport->io_pending_lock, flags); /* nothing on pending list, dispatch abort */ return efct_scsi_io_dispatch_no_hw_io(io); } static inline int efct_scsi_xfer_data(struct efct_io *io, u32 flags, struct efct_scsi_sgl *sgl, u32 sgl_count, u64 xwire_len, enum efct_hw_io_type type, int enable_ar, efct_scsi_io_cb_t cb, void *arg) { struct efct *efct; size_t residual = 0; io->sgl_count = sgl_count; efct = io->efct; scsi_io_trace(io, "%s wire_len %llu\n", (type == EFCT_HW_IO_TARGET_READ) ? "send" : "recv", xwire_len); io->hio_type = type; io->scsi_tgt_cb = cb; io->scsi_tgt_cb_arg = arg; residual = io->exp_xfer_len - io->transferred; io->wire_len = (xwire_len < residual) ? xwire_len : residual; residual = (xwire_len - io->wire_len); memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.fcp_tgt.ox_id = io->init_task_tag; io->iparam.fcp_tgt.offset = io->transferred; io->iparam.fcp_tgt.cs_ctl = io->cs_ctl; io->iparam.fcp_tgt.timeout = io->timeout; /* if this is the last data phase and there is no residual, enable * auto-good-response */ if (enable_ar && (flags & EFCT_SCSI_LAST_DATAPHASE) && residual == 0 && ((io->transferred + io->wire_len) == io->exp_xfer_len) && (!(flags & EFCT_SCSI_NO_AUTO_RESPONSE))) { io->iparam.fcp_tgt.flags |= SLI4_IO_AUTO_GOOD_RESPONSE; io->auto_resp = true; } else { io->auto_resp = false; } /* save this transfer length */ io->xfer_req = io->wire_len; /* Adjust the transferred count to account for overrun * when the residual is calculated in efct_scsi_send_resp */ io->transferred += residual; /* Adjust the SGL size if there is overrun */ if (residual) { struct efct_scsi_sgl *sgl_ptr = &io->sgl[sgl_count - 1]; while (residual) { size_t len = sgl_ptr->len; if (len > residual) { sgl_ptr->len = len - residual; residual = 0; } else { sgl_ptr->len = 0; residual -= len; io->sgl_count--; } sgl_ptr--; } } /* Set latency and WQ steering */ io->low_latency = (flags & EFCT_SCSI_LOW_LATENCY) != 0; io->wq_steering = (flags & EFCT_SCSI_WQ_STEERING_MASK) >> EFCT_SCSI_WQ_STEERING_SHIFT; io->wq_class = (flags & EFCT_SCSI_WQ_CLASS_MASK) >> EFCT_SCSI_WQ_CLASS_SHIFT; if (efct->xport) { struct efct_xport *xport = efct->xport; if (type == EFCT_HW_IO_TARGET_READ) { xport->fcp_stats.input_requests++; xport->fcp_stats.input_bytes += xwire_len; } else if (type == EFCT_HW_IO_TARGET_WRITE) { xport->fcp_stats.output_requests++; xport->fcp_stats.output_bytes += xwire_len; } } return efct_scsi_io_dispatch(io, efct_target_io_cb); } int efct_scsi_send_rd_data(struct efct_io *io, u32 flags, struct efct_scsi_sgl *sgl, u32 sgl_count, u64 len, efct_scsi_io_cb_t cb, void *arg) { return efct_scsi_xfer_data(io, flags, sgl, sgl_count, len, EFCT_HW_IO_TARGET_READ, enable_tsend_auto_resp(io->efct), cb, arg); } int efct_scsi_recv_wr_data(struct efct_io *io, u32 flags, struct efct_scsi_sgl *sgl, u32 sgl_count, u64 len, efct_scsi_io_cb_t cb, void *arg) { return efct_scsi_xfer_data(io, flags, sgl, sgl_count, len, EFCT_HW_IO_TARGET_WRITE, enable_treceive_auto_resp(io->efct), cb, arg); } int efct_scsi_send_resp(struct efct_io *io, u32 flags, struct efct_scsi_cmd_resp *rsp, efct_scsi_io_cb_t cb, void *arg) { struct efct *efct; int residual; /* Always try auto resp */ bool auto_resp = true; u8 scsi_status = 0; u16 scsi_status_qualifier = 0; u8 *sense_data = NULL; u32 sense_data_length = 0; efct = io->efct; if (rsp) { scsi_status = rsp->scsi_status; scsi_status_qualifier = rsp->scsi_status_qualifier; sense_data = rsp->sense_data; sense_data_length = rsp->sense_data_length; residual = rsp->residual; } else { residual = io->exp_xfer_len - io->transferred; } io->wire_len = 0; io->hio_type = EFCT_HW_IO_TARGET_RSP; io->scsi_tgt_cb = cb; io->scsi_tgt_cb_arg = arg; memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.fcp_tgt.ox_id = io->init_task_tag; io->iparam.fcp_tgt.offset = 0; io->iparam.fcp_tgt.cs_ctl = io->cs_ctl; io->iparam.fcp_tgt.timeout = io->timeout; /* Set low latency queueing request */ io->low_latency = (flags & EFCT_SCSI_LOW_LATENCY) != 0; io->wq_steering = (flags & EFCT_SCSI_WQ_STEERING_MASK) >> EFCT_SCSI_WQ_STEERING_SHIFT; io->wq_class = (flags & EFCT_SCSI_WQ_CLASS_MASK) >> EFCT_SCSI_WQ_CLASS_SHIFT; if (scsi_status != 0 || residual || sense_data_length) { struct fcp_resp_with_ext *fcprsp = io->rspbuf.virt; u8 *sns_data; if (!fcprsp) { efc_log_err(efct, "NULL response buffer\n"); return -EIO; } sns_data = (u8 *)io->rspbuf.virt + sizeof(*fcprsp); auto_resp = false; memset(fcprsp, 0, sizeof(*fcprsp)); io->wire_len += sizeof(*fcprsp); fcprsp->resp.fr_status = scsi_status; fcprsp->resp.fr_retry_delay = cpu_to_be16(scsi_status_qualifier); /* set residual status if necessary */ if (residual != 0) { /* FCP: if data transferred is less than the * amount expected, then this is an underflow. * If data transferred would have been greater * than the amount expected this is an overflow */ if (residual > 0) { fcprsp->resp.fr_flags |= FCP_RESID_UNDER; fcprsp->ext.fr_resid = cpu_to_be32(residual); } else { fcprsp->resp.fr_flags |= FCP_RESID_OVER; fcprsp->ext.fr_resid = cpu_to_be32(-residual); } } if (EFCT_SCSI_SNS_BUF_VALID(sense_data) && sense_data_length) { if (sense_data_length > SCSI_SENSE_BUFFERSIZE) { efc_log_err(efct, "Sense exceeds max size.\n"); return -EIO; } fcprsp->resp.fr_flags |= FCP_SNS_LEN_VAL; memcpy(sns_data, sense_data, sense_data_length); fcprsp->ext.fr_sns_len = cpu_to_be32(sense_data_length); io->wire_len += sense_data_length; } io->sgl[0].addr = io->rspbuf.phys; io->sgl[0].dif_addr = 0; io->sgl[0].len = io->wire_len; io->sgl_count = 1; } if (auto_resp) io->iparam.fcp_tgt.flags |= SLI4_IO_AUTO_GOOD_RESPONSE; return efct_scsi_io_dispatch(io, efct_target_io_cb); } static int efct_target_bls_resp_cb(struct efct_hw_io *hio, u32 length, int status, u32 ext_status, void *app) { struct efct_io *io = app; struct efct *efct; enum efct_scsi_io_status bls_status; efct = io->efct; /* BLS isn't really a "SCSI" concept, but use SCSI status */ if (status) { io_error_log(io, "s=%#x x=%#x\n", status, ext_status); bls_status = EFCT_SCSI_STATUS_ERROR; } else { bls_status = EFCT_SCSI_STATUS_GOOD; } if (io->bls_cb) { efct_scsi_io_cb_t bls_cb = io->bls_cb; void *bls_cb_arg = io->bls_cb_arg; io->bls_cb = NULL; io->bls_cb_arg = NULL; /* invoke callback */ bls_cb(io, bls_status, 0, bls_cb_arg); } efct_scsi_check_pending(efct); return 0; } static int efct_target_send_bls_resp(struct efct_io *io, efct_scsi_io_cb_t cb, void *arg) { struct efct_node *node = io->node; struct sli_bls_params *bls = &io->iparam.bls; struct efct *efct = node->efct; struct fc_ba_acc *acc; int rc; /* fill out IO structure with everything needed to send BA_ACC */ memset(&io->iparam, 0, sizeof(io->iparam)); bls->ox_id = io->init_task_tag; bls->rx_id = io->abort_rx_id; bls->vpi = io->node->vpi; bls->rpi = io->node->rpi; bls->s_id = U32_MAX; bls->d_id = io->node->node_fc_id; bls->rpi_registered = true; acc = (void *)bls->payload; acc->ba_ox_id = cpu_to_be16(bls->ox_id); acc->ba_rx_id = cpu_to_be16(bls->rx_id); acc->ba_high_seq_cnt = cpu_to_be16(U16_MAX); /* generic io fields have already been populated */ /* set type and BLS-specific fields */ io->io_type = EFCT_IO_TYPE_BLS_RESP; io->display_name = "bls_rsp"; io->hio_type = EFCT_HW_BLS_ACC; io->bls_cb = cb; io->bls_cb_arg = arg; /* dispatch IO */ rc = efct_hw_bls_send(efct, FC_RCTL_BA_ACC, bls, efct_target_bls_resp_cb, io); return rc; } static int efct_bls_send_rjt_cb(struct efct_hw_io *hio, u32 length, int status, u32 ext_status, void *app) { struct efct_io *io = app; efct_scsi_io_free(io); return 0; } struct efct_io * efct_bls_send_rjt(struct efct_io *io, struct fc_frame_header *hdr) { struct efct_node *node = io->node; struct sli_bls_params *bls = &io->iparam.bls; struct efct *efct = node->efct; struct fc_ba_rjt *acc; int rc; /* fill out BLS Response-specific fields */ io->io_type = EFCT_IO_TYPE_BLS_RESP; io->display_name = "ba_rjt"; io->hio_type = EFCT_HW_BLS_RJT; io->init_task_tag = be16_to_cpu(hdr->fh_ox_id); /* fill out iparam fields */ memset(&io->iparam, 0, sizeof(io->iparam)); bls->ox_id = be16_to_cpu(hdr->fh_ox_id); bls->rx_id = be16_to_cpu(hdr->fh_rx_id); bls->vpi = io->node->vpi; bls->rpi = io->node->rpi; bls->s_id = U32_MAX; bls->d_id = io->node->node_fc_id; bls->rpi_registered = true; acc = (void *)bls->payload; acc->br_reason = ELS_RJT_UNAB; acc->br_explan = ELS_EXPL_NONE; rc = efct_hw_bls_send(efct, FC_RCTL_BA_RJT, bls, efct_bls_send_rjt_cb, io); if (rc) { efc_log_err(efct, "efct_scsi_io_dispatch() failed: %d\n", rc); efct_scsi_io_free(io); io = NULL; } return io; } int efct_scsi_send_tmf_resp(struct efct_io *io, enum efct_scsi_tmf_resp rspcode, u8 addl_rsp_info[3], efct_scsi_io_cb_t cb, void *arg) { int rc; struct { struct fcp_resp_with_ext rsp_ext; struct fcp_resp_rsp_info info; } *fcprsp; u8 fcp_rspcode; io->wire_len = 0; switch (rspcode) { case EFCT_SCSI_TMF_FUNCTION_COMPLETE: fcp_rspcode = FCP_TMF_CMPL; break; case EFCT_SCSI_TMF_FUNCTION_SUCCEEDED: case EFCT_SCSI_TMF_FUNCTION_IO_NOT_FOUND: fcp_rspcode = FCP_TMF_CMPL; break; case EFCT_SCSI_TMF_FUNCTION_REJECTED: fcp_rspcode = FCP_TMF_REJECTED; break; case EFCT_SCSI_TMF_INCORRECT_LOGICAL_UNIT_NUMBER: fcp_rspcode = FCP_TMF_INVALID_LUN; break; case EFCT_SCSI_TMF_SERVICE_DELIVERY: fcp_rspcode = FCP_TMF_FAILED; break; default: fcp_rspcode = FCP_TMF_REJECTED; break; } io->hio_type = EFCT_HW_IO_TARGET_RSP; io->scsi_tgt_cb = cb; io->scsi_tgt_cb_arg = arg; if (io->tmf_cmd == EFCT_SCSI_TMF_ABORT_TASK) { rc = efct_target_send_bls_resp(io, cb, arg); return rc; } /* populate the FCP TMF response */ fcprsp = io->rspbuf.virt; memset(fcprsp, 0, sizeof(*fcprsp)); fcprsp->rsp_ext.resp.fr_flags |= FCP_SNS_LEN_VAL; if (addl_rsp_info) { memcpy(fcprsp->info._fr_resvd, addl_rsp_info, sizeof(fcprsp->info._fr_resvd)); } fcprsp->info.rsp_code = fcp_rspcode; io->wire_len = sizeof(*fcprsp); fcprsp->rsp_ext.ext.fr_rsp_len = cpu_to_be32(sizeof(struct fcp_resp_rsp_info)); io->sgl[0].addr = io->rspbuf.phys; io->sgl[0].dif_addr = 0; io->sgl[0].len = io->wire_len; io->sgl_count = 1; memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.fcp_tgt.ox_id = io->init_task_tag; io->iparam.fcp_tgt.offset = 0; io->iparam.fcp_tgt.cs_ctl = io->cs_ctl; io->iparam.fcp_tgt.timeout = io->timeout; rc = efct_scsi_io_dispatch(io, efct_target_io_cb); return rc; } static int efct_target_abort_cb(struct efct_hw_io *hio, u32 length, int status, u32 ext_status, void *app) { struct efct_io *io = app; struct efct *efct; enum efct_scsi_io_status scsi_status; efct_scsi_io_cb_t abort_cb; void *abort_cb_arg; efct = io->efct; if (!io->abort_cb) goto done; abort_cb = io->abort_cb; abort_cb_arg = io->abort_cb_arg; io->abort_cb = NULL; io->abort_cb_arg = NULL; switch (status) { case SLI4_FC_WCQE_STATUS_SUCCESS: scsi_status = EFCT_SCSI_STATUS_GOOD; break; case SLI4_FC_WCQE_STATUS_LOCAL_REJECT: switch (ext_status) { case SLI4_FC_LOCAL_REJECT_NO_XRI: scsi_status = EFCT_SCSI_STATUS_NO_IO; break; case SLI4_FC_LOCAL_REJECT_ABORT_IN_PROGRESS: scsi_status = EFCT_SCSI_STATUS_ABORT_IN_PROGRESS; break; default: /*we have seen 0x15 (abort in progress)*/ scsi_status = EFCT_SCSI_STATUS_ERROR; break; } break; case SLI4_FC_WCQE_STATUS_FCP_RSP_FAILURE: scsi_status = EFCT_SCSI_STATUS_CHECK_RESPONSE; break; default: scsi_status = EFCT_SCSI_STATUS_ERROR; break; } /* invoke callback */ abort_cb(io->io_to_abort, scsi_status, 0, abort_cb_arg); done: /* done with IO to abort,efct_ref_get(): efct_scsi_tgt_abort_io() */ kref_put(&io->io_to_abort->ref, io->io_to_abort->release); efct_io_pool_io_free(efct->xport->io_pool, io); efct_scsi_check_pending(efct); return 0; } int efct_scsi_tgt_abort_io(struct efct_io *io, efct_scsi_io_cb_t cb, void *arg) { struct efct *efct; struct efct_xport *xport; int rc; struct efct_io *abort_io = NULL; efct = io->efct; xport = efct->xport; /* take a reference on IO being aborted */ if (kref_get_unless_zero(&io->ref) == 0) { /* command no longer active */ scsi_io_printf(io, "command no longer active\n"); return -EIO; } /* * allocate a new IO to send the abort request. Use efct_io_alloc() * directly, as we need an IO object that will not fail allocation * due to allocations being disabled (in efct_scsi_io_alloc()) */ abort_io = efct_io_pool_io_alloc(efct->xport->io_pool); if (!abort_io) { atomic_add_return(1, &xport->io_alloc_failed_count); kref_put(&io->ref, io->release); return -EIO; } /* Save the target server callback and argument */ /* set generic fields */ abort_io->cmd_tgt = true; abort_io->node = io->node; /* set type and abort-specific fields */ abort_io->io_type = EFCT_IO_TYPE_ABORT; abort_io->display_name = "tgt_abort"; abort_io->io_to_abort = io; abort_io->send_abts = false; abort_io->abort_cb = cb; abort_io->abort_cb_arg = arg; /* now dispatch IO */ rc = efct_scsi_io_dispatch_abort(abort_io, efct_target_abort_cb); if (rc) kref_put(&io->ref, io->release); return rc; } void efct_scsi_io_complete(struct efct_io *io) { if (io->io_free) { efc_log_debug(io->efct, "completion for non-busy io tag 0x%x\n", io->tag); return; } scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name); kref_put(&io->ref, io->release); }