ef270ab1b6
The ocs_fc(4) driver supports the following hardware: Emulex 16/8G FC GEN 5 HBAS LPe15004 FC Host Bus Adapters LPe160XX FC Host Bus Adapters Emulex 32/16G FC GEN 6 HBAS LPe3100X FC Host Bus Adapters LPe3200X FC Host Bus Adapters The driver supports target and initiator mode, and also supports FC-Tape. Note that the driver only currently works on little endian platforms. It is only included in the module build for amd64 and i386, and in GENERIC on amd64 only. Submitted by: Ram Kishore Vegesna <ram.vegesna@broadcom.com> Reviewed by: mav MFC after: 5 days Relnotes: yes Sponsored by: Broadcom Differential Revision: https://reviews.freebsd.org/D11423
2961 lines
84 KiB
C
2961 lines
84 KiB
C
/*-
|
|
* Copyright (c) 2017 Broadcom. All rights reserved.
|
|
* The term "Broadcom" refers to Broadcom Limited and/or its subsidiaries.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* OCS Linux SCSI API base driver implementation.
|
|
*/
|
|
|
|
/**
|
|
* @defgroup scsi_api_base SCSI Base Target/Initiator
|
|
*/
|
|
|
|
|
|
#include "ocs.h"
|
|
#include "ocs_els.h"
|
|
#include "ocs_scsi.h"
|
|
#if defined(OCS_ENABLE_VPD_SUPPORT)
|
|
#include "ocs_vpd.h"
|
|
#endif
|
|
#include "ocs_utils.h"
|
|
#include "ocs_device.h"
|
|
|
|
#define SCSI_IOFMT "[%04x][i:%0*x t:%0*x h:%04x]"
|
|
#define SCSI_ITT_SIZE(ocs) ((ocs->ocs_xport == OCS_XPORT_FC) ? 4 : 8)
|
|
|
|
#define SCSI_IOFMT_ARGS(io) io->instance_index, SCSI_ITT_SIZE(io->ocs), io->init_task_tag, SCSI_ITT_SIZE(io->ocs), io->tgt_task_tag, io->hw_tag
|
|
|
|
#define enable_tsend_auto_resp(ocs) ((ocs->ctrlmask & OCS_CTRLMASK_XPORT_DISABLE_AUTORSP_TSEND) == 0)
|
|
#define enable_treceive_auto_resp(ocs) ((ocs->ctrlmask & OCS_CTRLMASK_XPORT_DISABLE_AUTORSP_TRECEIVE) == 0)
|
|
|
|
#define scsi_io_printf(io, fmt, ...) ocs_log_info(io->ocs, "[%s]" SCSI_IOFMT fmt, \
|
|
io->node->display_name, SCSI_IOFMT_ARGS(io), ##__VA_ARGS__)
|
|
|
|
#define scsi_io_trace(io, fmt, ...) \
|
|
do { \
|
|
if (OCS_LOG_ENABLE_SCSI_TRACE(io->ocs)) \
|
|
scsi_io_printf(io, fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define scsi_log(ocs, fmt, ...) \
|
|
do { \
|
|
if (OCS_LOG_ENABLE_SCSI_TRACE(ocs)) \
|
|
ocs_log_info(ocs, fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static int32_t ocs_target_send_bls_resp(ocs_io_t *io, ocs_scsi_io_cb_t cb, void *arg);
|
|
static int32_t ocs_scsi_abort_io_cb(struct ocs_hw_io_s *hio, ocs_remote_node_t *rnode, uint32_t len, int32_t status,
|
|
uint32_t ext, void *arg);
|
|
|
|
static void ocs_scsi_io_free_ovfl(ocs_io_t *io);
|
|
static uint32_t ocs_scsi_count_sgls(ocs_hw_dif_info_t *hw_dif, ocs_scsi_sgl_t *sgl, uint32_t sgl_count);
|
|
static int ocs_scsi_dif_guard_is_crc(uint8_t direction, ocs_hw_dif_info_t *dif_info);
|
|
static ocs_scsi_io_status_e ocs_scsi_dif_check_unknown(ocs_io_t *io, uint32_t length, uint32_t check_length, int is_crc);
|
|
static uint32_t ocs_scsi_dif_check_guard(ocs_hw_dif_info_t *dif_info, ocs_scsi_vaddr_len_t addrlen[],
|
|
uint32_t addrlen_count, ocs_dif_t *dif, int is_crc);
|
|
static uint32_t ocs_scsi_dif_check_app_tag(ocs_t *ocs, ocs_hw_dif_info_t *dif_info, uint16_t exp_app_tag, ocs_dif_t *dif);
|
|
static uint32_t ocs_scsi_dif_check_ref_tag(ocs_t *ocs, ocs_hw_dif_info_t *dif_info, uint32_t exp_ref_tag, ocs_dif_t *dif);
|
|
static int32_t ocs_scsi_convert_dif_info(ocs_t *ocs, ocs_scsi_dif_info_t *scsi_dif_info,
|
|
ocs_hw_dif_info_t *hw_dif_info);
|
|
static int32_t ocs_scsi_io_dispatch_hw_io(ocs_io_t *io, ocs_hw_io_t *hio);
|
|
static int32_t ocs_scsi_io_dispatch_no_hw_io(ocs_io_t *io);
|
|
static void _ocs_scsi_io_free(void *arg);
|
|
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Returns a big-endian 32-bit value given a pointer.
|
|
*
|
|
* @param p Pointer to the 32-bit big-endian location.
|
|
*
|
|
* @return Returns the byte-swapped 32-bit value.
|
|
*/
|
|
|
|
static inline uint32_t
|
|
ocs_fc_getbe32(void *p)
|
|
{
|
|
return ocs_be32toh(*((uint32_t*)p));
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Enable IO allocation.
|
|
*
|
|
* @par Description
|
|
* The SCSI and Transport IO allocation functions are enabled. If the allocation functions
|
|
* are not enabled, then calls to ocs_scsi_io_alloc() (and ocs_els_io_alloc() for FC) will
|
|
* fail.
|
|
*
|
|
* @param node Pointer to node object.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_io_alloc_enable(ocs_node_t *node)
|
|
{
|
|
ocs_assert(node != NULL);
|
|
ocs_lock(&node->active_ios_lock);
|
|
node->io_alloc_enabled = TRUE;
|
|
ocs_unlock(&node->active_ios_lock);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Disable IO allocation
|
|
*
|
|
* @par Description
|
|
* The SCSI and Transport IO allocation functions are disabled. If the allocation functions
|
|
* are not enabled, then calls to ocs_scsi_io_alloc() (and ocs_els_io_alloc() for FC) will
|
|
* fail.
|
|
*
|
|
* @param node Pointer to node object
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_io_alloc_disable(ocs_node_t *node)
|
|
{
|
|
ocs_assert(node != NULL);
|
|
ocs_lock(&node->active_ios_lock);
|
|
node->io_alloc_enabled = FALSE;
|
|
ocs_unlock(&node->active_ios_lock);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Allocate a SCSI IO context.
|
|
*
|
|
* @par Description
|
|
* A SCSI IO context is allocated and associated with a @c node. This function
|
|
* is called by an initiator-client when issuing SCSI commands to remote
|
|
* target devices. On completion, ocs_scsi_io_free() is called.
|
|
* @n @n
|
|
* The returned ocs_io_t structure has an element of type ocs_scsi_ini_io_t named
|
|
* "ini_io" that is declared and used by an initiator-client for private information.
|
|
*
|
|
* @param node Pointer to the associated node structure.
|
|
* @param role Role for IO (originator/responder).
|
|
*
|
|
* @return Returns the pointer to the IO context, or NULL.
|
|
*
|
|
*/
|
|
|
|
ocs_io_t *
|
|
ocs_scsi_io_alloc(ocs_node_t *node, ocs_scsi_io_role_e role)
|
|
{
|
|
ocs_t *ocs;
|
|
ocs_xport_t *xport;
|
|
ocs_io_t *io;
|
|
|
|
ocs_assert(node, NULL);
|
|
ocs_assert(node->ocs, NULL);
|
|
|
|
ocs = node->ocs;
|
|
ocs_assert(ocs->xport, NULL);
|
|
xport = ocs->xport;
|
|
|
|
ocs_lock(&node->active_ios_lock);
|
|
|
|
if (!node->io_alloc_enabled) {
|
|
ocs_unlock(&node->active_ios_lock);
|
|
return NULL;
|
|
}
|
|
|
|
io = ocs_io_alloc(ocs);
|
|
if (io == NULL) {
|
|
ocs_atomic_add_return(&xport->io_alloc_failed_count, 1);
|
|
ocs_unlock(&node->active_ios_lock);
|
|
return NULL;
|
|
}
|
|
|
|
/* initialize refcount */
|
|
ocs_ref_init(&io->ref, _ocs_scsi_io_free, io);
|
|
|
|
if (io->hio != NULL) {
|
|
ocs_log_err(node->ocs, "assertion failed: io->hio is not NULL\n");
|
|
ocs_unlock(&node->active_ios_lock);
|
|
return NULL;
|
|
}
|
|
|
|
/* set generic fields */
|
|
io->ocs = ocs;
|
|
io->node = node;
|
|
|
|
/* set type and name */
|
|
io->io_type = OCS_IO_TYPE_IO;
|
|
io->display_name = "scsi_io";
|
|
|
|
switch (role) {
|
|
case OCS_SCSI_IO_ROLE_ORIGINATOR:
|
|
io->cmd_ini = TRUE;
|
|
io->cmd_tgt = FALSE;
|
|
break;
|
|
case OCS_SCSI_IO_ROLE_RESPONDER:
|
|
io->cmd_ini = FALSE;
|
|
io->cmd_tgt = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* Add to node's active_ios list */
|
|
ocs_list_add_tail(&node->active_ios, io);
|
|
|
|
ocs_unlock(&node->active_ios_lock);
|
|
|
|
return io;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Free a SCSI IO context (internal).
|
|
*
|
|
* @par Description
|
|
* The IO context previously allocated using ocs_scsi_io_alloc()
|
|
* is freed. This is called from within the transport layer,
|
|
* when the reference count goes to zero.
|
|
*
|
|
* @param arg Pointer to the IO context.
|
|
*
|
|
* @return None.
|
|
*/
|
|
static void
|
|
_ocs_scsi_io_free(void *arg)
|
|
{
|
|
ocs_io_t *io = (ocs_io_t *)arg;
|
|
ocs_t *ocs = io->ocs;
|
|
ocs_node_t *node = io->node;
|
|
int send_empty_event;
|
|
|
|
ocs_assert(io != NULL);
|
|
|
|
scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);
|
|
|
|
ocs_assert(ocs_io_busy(io));
|
|
|
|
ocs_lock(&node->active_ios_lock);
|
|
ocs_list_remove(&node->active_ios, io);
|
|
send_empty_event = (!node->io_alloc_enabled) && ocs_list_empty(&node->active_ios);
|
|
ocs_unlock(&node->active_ios_lock);
|
|
|
|
if (send_empty_event) {
|
|
ocs_node_post_event(node, OCS_EVT_NODE_ACTIVE_IO_LIST_EMPTY, NULL);
|
|
}
|
|
|
|
io->node = NULL;
|
|
ocs_io_free(ocs, io);
|
|
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Free a SCSI IO context.
|
|
*
|
|
* @par Description
|
|
* The IO context previously allocated using ocs_scsi_io_alloc() is freed.
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_io_free(ocs_io_t *io)
|
|
{
|
|
scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);
|
|
ocs_assert(ocs_ref_read_count(&io->ref) > 0);
|
|
ocs_ref_put(&io->ref); /* ocs_ref_get(): ocs_scsi_io_alloc() */
|
|
}
|
|
|
|
|
|
|
|
static int32_t
|
|
ocs_scsi_send_io(ocs_hw_io_type_e type, ocs_node_t *node, ocs_io_t *io, uint64_t lun,
|
|
ocs_scsi_tmf_cmd_e tmf, uint8_t *cdb, uint32_t cdb_len,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t wire_len, uint32_t first_burst,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg);
|
|
|
|
/**
|
|
* @brief Target response completion callback.
|
|
*
|
|
* @par Description
|
|
* Function is called upon the completion of a target IO request.
|
|
*
|
|
* @param hio Pointer to the HW IO structure.
|
|
* @param rnode Remote node associated with the IO that is completing.
|
|
* @param length Length of the response payload.
|
|
* @param status Completion status.
|
|
* @param ext_status Extended completion status.
|
|
* @param app Application-specific data (generally a pointer to the IO context).
|
|
*
|
|
* @return None.
|
|
*/
|
|
|
|
static void
|
|
ocs_target_io_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length,
|
|
int32_t status, uint32_t ext_status, void *app)
|
|
{
|
|
ocs_io_t *io = app;
|
|
ocs_t *ocs;
|
|
ocs_scsi_io_status_e scsi_status = OCS_SCSI_STATUS_GOOD;
|
|
uint16_t additional_length;
|
|
uint8_t edir;
|
|
uint8_t tdpv;
|
|
ocs_hw_dif_info_t *dif_info = &io->hw_dif;
|
|
int is_crc;
|
|
|
|
ocs_assert(io);
|
|
|
|
scsi_io_trace(io, "status x%x ext_status x%x\n", status, ext_status);
|
|
|
|
ocs = io->ocs;
|
|
ocs_assert(ocs);
|
|
|
|
ocs_scsi_io_free_ovfl(io);
|
|
|
|
io->transferred += length;
|
|
|
|
/* Call target server completion */
|
|
if (io->scsi_tgt_cb) {
|
|
ocs_scsi_io_cb_t cb = io->scsi_tgt_cb;
|
|
uint32_t flags = 0;
|
|
|
|
/* 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 |= OCS_SCSI_IO_CMPL_RSP_SENT;
|
|
else
|
|
flags |= OCS_SCSI_IO_CMPL;
|
|
|
|
switch (status) {
|
|
case SLI4_FC_WCQE_STATUS_SUCCESS:
|
|
scsi_status = OCS_SCSI_STATUS_GOOD;
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_DI_ERROR:
|
|
if (ext_status & SLI4_FC_DI_ERROR_GE) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_GUARD_ERROR;
|
|
} else if (ext_status & SLI4_FC_DI_ERROR_AE) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_APP_TAG_ERROR;
|
|
} else if (ext_status & SLI4_FC_DI_ERROR_RE) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_REF_TAG_ERROR;
|
|
} else {
|
|
additional_length = ((ext_status >> 16) & 0xFFFF);
|
|
|
|
/* Capture the EDIR and TDPV bits as 0 or 1 for easier printing. */
|
|
edir = !!(ext_status & SLI4_FC_DI_ERROR_EDIR);
|
|
tdpv = !!(ext_status & SLI4_FC_DI_ERROR_TDPV);
|
|
|
|
is_crc = ocs_scsi_dif_guard_is_crc(edir, dif_info);
|
|
|
|
if (edir == 0) {
|
|
/* For reads, we have everything in memory. Start checking from beginning. */
|
|
scsi_status = ocs_scsi_dif_check_unknown(io, 0, io->wire_len, is_crc);
|
|
} else {
|
|
/* For writes, use the additional length to determine where to look for the error.
|
|
* The additional_length field is set to 0 if it is not supported.
|
|
* The additional length field is valid if:
|
|
* . additional_length is not zero
|
|
* . Total Data Placed is valid
|
|
* . Error Direction is RX (1)
|
|
* . Operation is a pass thru (CRC or CKSUM on IN, and CRC or CHKSUM on OUT) (all pass-thru cases except raw)
|
|
*/
|
|
if ((additional_length != 0) && (tdpv != 0) &&
|
|
(dif_info->dif == SLI4_DIF_PASS_THROUGH) && (dif_info->dif_oper != OCS_HW_SGE_DIF_OP_IN_RAW_OUT_RAW) ) {
|
|
scsi_status = ocs_scsi_dif_check_unknown(io, length, additional_length, is_crc);
|
|
} else {
|
|
/* If we can't do additional checking, then fall-back to guard error */
|
|
scsi_status = OCS_SCSI_STATUS_DIF_GUARD_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_status = OCS_SCSI_STATUS_ABORTED;
|
|
break;
|
|
case SLI4_FC_LOCAL_REJECT_INVALID_RPI:
|
|
scsi_status = OCS_SCSI_STATUS_NEXUS_LOST;
|
|
break;
|
|
case SLI4_FC_LOCAL_REJECT_NO_XRI:
|
|
scsi_status = OCS_SCSI_STATUS_NO_IO;
|
|
break;
|
|
default:
|
|
/* TODO: we have seen 0x0d (TX_DMA_FAILED error) */
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SLI4_FC_WCQE_STATUS_TARGET_WQE_TIMEOUT:
|
|
/* target IO timed out */
|
|
scsi_status = OCS_SCSI_STATUS_TIMEDOUT_AND_ABORTED;
|
|
break;
|
|
|
|
case SLI4_FC_WCQE_STATUS_SHUTDOWN:
|
|
/* Target IO cancelled by HW */
|
|
scsi_status = OCS_SCSI_STATUS_SHUTDOWN;
|
|
break;
|
|
|
|
default:
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
|
|
cb(io, scsi_status, flags, io->scsi_tgt_cb_arg);
|
|
|
|
}
|
|
ocs_scsi_check_pending(ocs);
|
|
}
|
|
|
|
/**
|
|
* @brief Determine if an IO is using CRC for DIF guard format.
|
|
*
|
|
* @param direction IO direction: 1 for write, 0 for read.
|
|
* @param dif_info Pointer to HW DIF info data.
|
|
*
|
|
* @return Returns TRUE if using CRC, FALSE if not.
|
|
*/
|
|
static int
|
|
ocs_scsi_dif_guard_is_crc(uint8_t direction, ocs_hw_dif_info_t *dif_info)
|
|
{
|
|
int is_crc;
|
|
|
|
if (direction) {
|
|
/* For writes, check if operation is "OUT_CRC" or not */
|
|
switch(dif_info->dif_oper) {
|
|
case OCS_HW_SGE_DIF_OP_IN_NODIF_OUT_CRC:
|
|
case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CRC:
|
|
case OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_CRC:
|
|
is_crc = TRUE;
|
|
break;
|
|
default:
|
|
is_crc = FALSE;
|
|
break;
|
|
}
|
|
} else {
|
|
/* For reads, check if operation is "IN_CRC" or not */
|
|
switch(dif_info->dif_oper) {
|
|
case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_NODIF:
|
|
case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CRC:
|
|
case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CHKSUM:
|
|
is_crc = TRUE;
|
|
break;
|
|
default:
|
|
is_crc = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return is_crc;
|
|
}
|
|
|
|
/**
|
|
* @brief Check a block and DIF data, computing the appropriate SCSI status
|
|
*
|
|
* @par Description
|
|
* This function is used to check blocks and DIF when given an unknown DIF
|
|
* status using the following logic:
|
|
*
|
|
* Given the address of the last good block, and a length of bytes that includes
|
|
* the block with the DIF error, find the bad block. If a block is found with an
|
|
* app_tag or ref_tag error, then return the appropriate error. No block is expected
|
|
* to have a block guard error since hardware "fixes" the crc. So if no block in the
|
|
* range of blocks has an error, then it is presumed to be a BLOCK GUARD error.
|
|
*
|
|
* @param io Pointer to the IO object.
|
|
* @param length Length of bytes covering the good blocks.
|
|
* @param check_length Length of bytes that covers the bad block.
|
|
* @param is_crc True if guard is using CRC format.
|
|
*
|
|
* @return Returns SCSI status.
|
|
*/
|
|
|
|
static ocs_scsi_io_status_e
|
|
ocs_scsi_dif_check_unknown(ocs_io_t *io, uint32_t length, uint32_t check_length, int is_crc)
|
|
{
|
|
uint32_t i;
|
|
ocs_t *ocs = io->ocs;
|
|
ocs_hw_dif_info_t *dif_info = &io->hw_dif;
|
|
ocs_scsi_io_status_e scsi_status = OCS_SCSI_STATUS_DIF_GUARD_ERROR;
|
|
uint32_t blocksize; /* data block size */
|
|
uint64_t first_check_block; /* first block following total data placed */
|
|
uint64_t last_check_block; /* last block to check */
|
|
uint32_t check_count; /* count of blocks to check */
|
|
ocs_scsi_vaddr_len_t addrlen[4]; /* address-length pairs returned from target */
|
|
int32_t addrlen_count; /* count of address-length pairs */
|
|
ocs_dif_t *dif; /* pointer to DIF block returned from target */
|
|
ocs_scsi_dif_info_t scsi_dif_info = io->scsi_dif_info;
|
|
|
|
blocksize = ocs_hw_dif_mem_blocksize(&io->hw_dif, TRUE);
|
|
first_check_block = length / blocksize;
|
|
last_check_block = ((length + check_length) / blocksize);
|
|
check_count = last_check_block - first_check_block;
|
|
|
|
ocs_log_debug(ocs, "blocksize %d first check_block %" PRId64 " last_check_block %" PRId64 " check_count %d\n",
|
|
blocksize, first_check_block, last_check_block, check_count);
|
|
|
|
for (i = first_check_block; i < last_check_block; i++) {
|
|
addrlen_count = ocs_scsi_get_block_vaddr(io, (scsi_dif_info.lba + i), addrlen, ARRAY_SIZE(addrlen), (void**) &dif);
|
|
if (addrlen_count < 0) {
|
|
ocs_log_test(ocs, "ocs_scsi_get_block_vaddr() failed: %d\n", addrlen_count);
|
|
scsi_status = OCS_SCSI_STATUS_DIF_UNKNOWN_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (! ocs_scsi_dif_check_guard(dif_info, addrlen, addrlen_count, dif, is_crc)) {
|
|
ocs_log_debug(ocs, "block guard check error, lba %" PRId64 "\n", scsi_dif_info.lba + i);
|
|
scsi_status = OCS_SCSI_STATUS_DIF_GUARD_ERROR;
|
|
break;
|
|
}
|
|
if (! ocs_scsi_dif_check_app_tag(ocs, dif_info, scsi_dif_info.app_tag, dif)) {
|
|
ocs_log_debug(ocs, "app tag check error, lba %" PRId64 "\n", scsi_dif_info.lba + i);
|
|
scsi_status = OCS_SCSI_STATUS_DIF_APP_TAG_ERROR;
|
|
break;
|
|
}
|
|
if (! ocs_scsi_dif_check_ref_tag(ocs, dif_info, (scsi_dif_info.ref_tag + i), dif)) {
|
|
ocs_log_debug(ocs, "ref tag check error, lba %" PRId64 "\n", scsi_dif_info.lba + i);
|
|
scsi_status = OCS_SCSI_STATUS_DIF_REF_TAG_ERROR;
|
|
break;
|
|
}
|
|
|
|
}
|
|
return scsi_status;
|
|
}
|
|
|
|
/**
|
|
* @brief Check the block guard of block data
|
|
*
|
|
* @par Description
|
|
* Using the dif_info for the transfer, check the block guard value.
|
|
*
|
|
* @param dif_info Pointer to HW DIF info data.
|
|
* @param addrlen Array of address length pairs.
|
|
* @param addrlen_count Number of entries in the addrlen[] array.
|
|
* @param dif Pointer to the DIF data block being checked.
|
|
* @param is_crc True if guard is using CRC format.
|
|
*
|
|
* @return Returns TRUE if block guard check is ok.
|
|
*/
|
|
static uint32_t
|
|
ocs_scsi_dif_check_guard(ocs_hw_dif_info_t *dif_info, ocs_scsi_vaddr_len_t addrlen[], uint32_t addrlen_count,
|
|
ocs_dif_t *dif, int is_crc)
|
|
{
|
|
uint16_t crc = dif_info->dif_seed;
|
|
uint32_t i;
|
|
uint16_t checksum;
|
|
|
|
if ((dif == NULL) || !dif_info->check_guard) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (is_crc) {
|
|
for (i = 0; i < addrlen_count; i++) {
|
|
crc = ocs_scsi_dif_calc_crc(addrlen[i].vaddr, addrlen[i].length, crc);
|
|
}
|
|
return (crc == ocs_be16toh(dif->crc));
|
|
} else {
|
|
checksum = ocs_scsi_dif_calc_checksum(addrlen, addrlen_count);
|
|
|
|
return (checksum == dif->crc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check the app tag of dif data
|
|
*
|
|
* @par Description
|
|
* Using the dif_info for the transfer, check the app tag.
|
|
*
|
|
* @param ocs Pointer to the ocs structure for logging.
|
|
* @param dif_info Pointer to HW DIF info data.
|
|
* @param exp_app_tag The value the app tag is expected to be.
|
|
* @param dif Pointer to the DIF data block being checked.
|
|
*
|
|
* @return Returns TRUE if app tag check is ok.
|
|
*/
|
|
static uint32_t
|
|
ocs_scsi_dif_check_app_tag(ocs_t *ocs, ocs_hw_dif_info_t *dif_info, uint16_t exp_app_tag, ocs_dif_t *dif)
|
|
{
|
|
if ((dif == NULL) || !dif_info->check_app_tag) {
|
|
return TRUE;
|
|
}
|
|
|
|
ocs_log_debug(ocs, "expected app tag 0x%x, actual 0x%x\n",
|
|
exp_app_tag, ocs_be16toh(dif->app_tag));
|
|
|
|
return (exp_app_tag == ocs_be16toh(dif->app_tag));
|
|
}
|
|
|
|
/**
|
|
* @brief Check the ref tag of dif data
|
|
*
|
|
* @par Description
|
|
* Using the dif_info for the transfer, check the app tag.
|
|
*
|
|
* @param ocs Pointer to the ocs structure for logging.
|
|
* @param dif_info Pointer to HW DIF info data.
|
|
* @param exp_ref_tag The value the ref tag is expected to be.
|
|
* @param dif Pointer to the DIF data block being checked.
|
|
*
|
|
* @return Returns TRUE if ref tag check is ok.
|
|
*/
|
|
static uint32_t
|
|
ocs_scsi_dif_check_ref_tag(ocs_t *ocs, ocs_hw_dif_info_t *dif_info, uint32_t exp_ref_tag, ocs_dif_t *dif)
|
|
{
|
|
if ((dif == NULL) || !dif_info->check_ref_tag) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (exp_ref_tag != ocs_be32toh(dif->ref_tag)) {
|
|
ocs_log_debug(ocs, "expected ref tag 0x%x, actual 0x%x\n",
|
|
exp_ref_tag, ocs_be32toh(dif->ref_tag));
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Return count of SGE's required for request
|
|
*
|
|
* @par Description
|
|
* An accurate count of SGEs is computed and returned.
|
|
*
|
|
* @param hw_dif Pointer to HW dif information.
|
|
* @param sgl Pointer to SGL from back end.
|
|
* @param sgl_count Count of SGEs in SGL.
|
|
*
|
|
* @return Count of SGEs.
|
|
*/
|
|
static uint32_t
|
|
ocs_scsi_count_sgls(ocs_hw_dif_info_t *hw_dif, ocs_scsi_sgl_t *sgl, uint32_t sgl_count)
|
|
{
|
|
uint32_t count = 0;
|
|
uint32_t i;
|
|
|
|
/* Convert DIF Information */
|
|
if (hw_dif->dif_oper != OCS_HW_DIF_OPER_DISABLED) {
|
|
|
|
/* If we're not DIF separate, then emit a seed SGE */
|
|
if (!hw_dif->dif_separate) {
|
|
count++;
|
|
}
|
|
|
|
for (i = 0; i < sgl_count; i++) {
|
|
/* If DIF is enabled, and DIF is separate, then append a SEED then DIF SGE */
|
|
if (hw_dif->dif_separate) {
|
|
count += 2;
|
|
}
|
|
|
|
count++;
|
|
}
|
|
} else {
|
|
count = sgl_count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int32_t
|
|
ocs_scsi_build_sgls(ocs_hw_t *hw, ocs_hw_io_t *hio, ocs_hw_dif_info_t *hw_dif, ocs_scsi_sgl_t *sgl, uint32_t sgl_count, ocs_hw_io_type_e type)
|
|
{
|
|
int32_t rc;
|
|
uint32_t i;
|
|
ocs_t *ocs = hw->os;
|
|
uint32_t blocksize = 0;
|
|
uint32_t blockcount;
|
|
|
|
ocs_assert(hio, -1);
|
|
|
|
/* Initialize HW SGL */
|
|
rc = ocs_hw_io_init_sges(hw, hio, type);
|
|
if (rc) {
|
|
ocs_log_err(ocs, "ocs_hw_io_init_sges failed: %d\n", rc);
|
|
return -1;
|
|
}
|
|
|
|
/* Convert DIF Information */
|
|
if (hw_dif->dif_oper != OCS_HW_DIF_OPER_DISABLED) {
|
|
|
|
/* If we're not DIF separate, then emit a seed SGE */
|
|
if (!hw_dif->dif_separate) {
|
|
rc = ocs_hw_io_add_seed_sge(hw, hio, hw_dif);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* if we are doing DIF separate, then figure out the block size so that we
|
|
* can update the ref tag in the DIF seed SGE. Also verify that the
|
|
* the sgl lengths are all multiples of the blocksize
|
|
*/
|
|
if (hw_dif->dif_separate) {
|
|
switch(hw_dif->blk_size) {
|
|
case OCS_HW_DIF_BK_SIZE_512: blocksize = 512; break;
|
|
case OCS_HW_DIF_BK_SIZE_1024: blocksize = 1024; break;
|
|
case OCS_HW_DIF_BK_SIZE_2048: blocksize = 2048; break;
|
|
case OCS_HW_DIF_BK_SIZE_4096: blocksize = 4096; break;
|
|
case OCS_HW_DIF_BK_SIZE_520: blocksize = 520; break;
|
|
case OCS_HW_DIF_BK_SIZE_4104: blocksize = 4104; break;
|
|
default:
|
|
ocs_log_test(hw->os, "Inavlid hw_dif blocksize %d\n", hw_dif->blk_size);
|
|
return -1;
|
|
}
|
|
for (i = 0; i < sgl_count; i++) {
|
|
if ((sgl[i].len % blocksize) != 0) {
|
|
ocs_log_test(hw->os, "sgl[%d] len of %ld is not multiple of blocksize\n",
|
|
i, sgl[i].len);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sgl_count; i++) {
|
|
ocs_assert(sgl[i].addr, -1);
|
|
ocs_assert(sgl[i].len, -1);
|
|
|
|
/* If DIF is enabled, and DIF is separate, then append a SEED then DIF SGE */
|
|
if (hw_dif->dif_separate) {
|
|
rc = ocs_hw_io_add_seed_sge(hw, hio, hw_dif);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
rc = ocs_hw_io_add_dif_sge(hw, hio, sgl[i].dif_addr);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
/* Update the ref_tag for the next DIF seed SGE */
|
|
blockcount = sgl[i].len / blocksize;
|
|
if (hw_dif->dif_oper == OCS_HW_DIF_OPER_INSERT) {
|
|
hw_dif->ref_tag_repl += blockcount;
|
|
} else {
|
|
hw_dif->ref_tag_cmp += blockcount;
|
|
}
|
|
}
|
|
|
|
/* Add data SGE */
|
|
rc = ocs_hw_io_add_sge(hw, hio, sgl[i].addr, sgl[i].len);
|
|
if (rc) {
|
|
ocs_log_err(ocs, "ocs_hw_io_add_sge failed: count=%d rc=%d\n",
|
|
sgl_count, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < sgl_count; i++) {
|
|
ocs_assert(sgl[i].addr, -1);
|
|
ocs_assert(sgl[i].len, -1);
|
|
|
|
/* Add data SGE */
|
|
rc = ocs_hw_io_add_sge(hw, hio, sgl[i].addr, sgl[i].len);
|
|
if (rc) {
|
|
ocs_log_err(ocs, "ocs_hw_io_add_sge failed: count=%d rc=%d\n",
|
|
sgl_count, rc);
|
|
return rc;
|
|
}
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Convert SCSI API T10 DIF information into the FC HW format.
|
|
*
|
|
* @param ocs Pointer to the ocs structure for logging.
|
|
* @param scsi_dif_info Pointer to the SCSI API T10 DIF fields.
|
|
* @param hw_dif_info Pointer to the FC HW API T10 DIF fields.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_scsi_convert_dif_info(ocs_t *ocs, ocs_scsi_dif_info_t *scsi_dif_info, ocs_hw_dif_info_t *hw_dif_info)
|
|
{
|
|
uint32_t dif_seed;
|
|
ocs_memset(hw_dif_info, 0, sizeof(ocs_hw_dif_info_t));
|
|
|
|
if (scsi_dif_info == NULL) {
|
|
hw_dif_info->dif_oper = OCS_HW_DIF_OPER_DISABLED;
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_NA;
|
|
return 0;
|
|
}
|
|
|
|
/* Convert the DIF operation */
|
|
switch(scsi_dif_info->dif_oper) {
|
|
case OCS_SCSI_DIF_OPER_IN_NODIF_OUT_CRC:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_NODIF_OUT_CRC;
|
|
hw_dif_info->dif = SLI4_DIF_INSERT;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CRC_OUT_NODIF:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CRC_OUT_NODIF;
|
|
hw_dif_info->dif = SLI4_DIF_STRIP;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_NODIF_OUT_CHKSUM:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_NODIF_OUT_CHKSUM;
|
|
hw_dif_info->dif = SLI4_DIF_INSERT;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CHKSUM_OUT_NODIF:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_NODIF;
|
|
hw_dif_info->dif = SLI4_DIF_STRIP;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CRC_OUT_CRC:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CRC;
|
|
hw_dif_info->dif = SLI4_DIF_PASS_THROUGH;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CHKSUM_OUT_CHKSUM:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_CHKSUM;
|
|
hw_dif_info->dif = SLI4_DIF_PASS_THROUGH;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CRC_OUT_CHKSUM:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CHKSUM;
|
|
hw_dif_info->dif = SLI4_DIF_PASS_THROUGH;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_CHKSUM_OUT_CRC:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_CRC;
|
|
hw_dif_info->dif = SLI4_DIF_PASS_THROUGH;
|
|
break;
|
|
case OCS_SCSI_DIF_OPER_IN_RAW_OUT_RAW:
|
|
hw_dif_info->dif_oper = OCS_HW_SGE_DIF_OP_IN_RAW_OUT_RAW;
|
|
hw_dif_info->dif = SLI4_DIF_PASS_THROUGH;
|
|
break;
|
|
default:
|
|
ocs_log_test(ocs, "unhandled SCSI DIF operation %d\n",
|
|
scsi_dif_info->dif_oper);
|
|
return -1;
|
|
}
|
|
|
|
switch(scsi_dif_info->blk_size) {
|
|
case OCS_SCSI_DIF_BK_SIZE_512:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_512;
|
|
break;
|
|
case OCS_SCSI_DIF_BK_SIZE_1024:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_1024;
|
|
break;
|
|
case OCS_SCSI_DIF_BK_SIZE_2048:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_2048;
|
|
break;
|
|
case OCS_SCSI_DIF_BK_SIZE_4096:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_4096;
|
|
break;
|
|
case OCS_SCSI_DIF_BK_SIZE_520:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_520;
|
|
break;
|
|
case OCS_SCSI_DIF_BK_SIZE_4104:
|
|
hw_dif_info->blk_size = OCS_HW_DIF_BK_SIZE_4104;
|
|
break;
|
|
default:
|
|
ocs_log_test(ocs, "unhandled SCSI DIF block size %d\n",
|
|
scsi_dif_info->blk_size);
|
|
return -1;
|
|
}
|
|
|
|
/* If the operation is an INSERT the tags provided are the ones that should be
|
|
* inserted, otherwise they're the ones to be checked against. */
|
|
if (hw_dif_info->dif == SLI4_DIF_INSERT ) {
|
|
hw_dif_info->ref_tag_repl = scsi_dif_info->ref_tag;
|
|
hw_dif_info->app_tag_repl = scsi_dif_info->app_tag;
|
|
} else {
|
|
hw_dif_info->ref_tag_cmp = scsi_dif_info->ref_tag;
|
|
hw_dif_info->app_tag_cmp = scsi_dif_info->app_tag;
|
|
}
|
|
|
|
hw_dif_info->check_ref_tag = scsi_dif_info->check_ref_tag;
|
|
hw_dif_info->check_app_tag = scsi_dif_info->check_app_tag;
|
|
hw_dif_info->check_guard = scsi_dif_info->check_guard;
|
|
hw_dif_info->auto_incr_ref_tag = 1;
|
|
hw_dif_info->dif_separate = scsi_dif_info->dif_separate;
|
|
hw_dif_info->disable_app_ffff = scsi_dif_info->disable_app_ffff;
|
|
hw_dif_info->disable_app_ref_ffff = scsi_dif_info->disable_app_ref_ffff;
|
|
|
|
ocs_hw_get(&ocs->hw, OCS_HW_DIF_SEED, &dif_seed);
|
|
hw_dif_info->dif_seed = dif_seed;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief This function logs the SGLs for an IO.
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
*/
|
|
static void ocs_log_sgl(ocs_io_t *io)
|
|
{
|
|
ocs_hw_io_t *hio = io->hio;
|
|
sli4_sge_t *data = NULL;
|
|
uint32_t *dword = NULL;
|
|
uint32_t i;
|
|
uint32_t n_sge;
|
|
|
|
scsi_io_trace(io, "def_sgl at 0x%x 0x%08x\n",
|
|
ocs_addr32_hi(hio->def_sgl.phys),
|
|
ocs_addr32_lo(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 = (uint32_t*)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;
|
|
}
|
|
}
|
|
|
|
if (hio->ovfl_sgl != NULL &&
|
|
hio->sgl == hio->ovfl_sgl) {
|
|
scsi_io_trace(io, "Overflow at 0x%x 0x%08x\n",
|
|
ocs_addr32_hi(hio->ovfl_sgl->phys),
|
|
ocs_addr32_lo(hio->ovfl_sgl->phys));
|
|
for (i = 0, data = hio->ovfl_sgl->virt; i < hio->n_sge; i++, data++) {
|
|
dword = (uint32_t*)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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Check pending error asynchronous callback function.
|
|
*
|
|
* @par Description
|
|
* Invoke the HW callback function for a given IO. This function is called
|
|
* from the NOP mailbox completion context.
|
|
*
|
|
* @param hw Pointer to HW object.
|
|
* @param status Completion status.
|
|
* @param mqe Mailbox completion queue entry.
|
|
* @param arg General purpose argument.
|
|
*
|
|
* @return Returns 0.
|
|
*/
|
|
static int32_t
|
|
ocs_scsi_check_pending_async_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg)
|
|
{
|
|
ocs_io_t *io = arg;
|
|
|
|
if (io != NULL) {
|
|
if (io->hw_cb != NULL) {
|
|
ocs_hw_done_t cb = io->hw_cb;
|
|
|
|
io->hw_cb = NULL;
|
|
cb(io->hio, NULL, 0, SLI4_FC_WCQE_STATUS_DISPATCH_ERROR, 0, io);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Check for pending IOs to dispatch.
|
|
*
|
|
* @par Description
|
|
* If there are IOs on the pending list, and a HW IO is available, then
|
|
* dispatch the IOs.
|
|
*
|
|
* @param ocs Pointer to the OCS structure.
|
|
*
|
|
* @return None.
|
|
*/
|
|
|
|
void
|
|
ocs_scsi_check_pending(ocs_t *ocs)
|
|
{
|
|
ocs_xport_t *xport = ocs->xport;
|
|
ocs_io_t *io;
|
|
ocs_hw_io_t *hio;
|
|
int32_t status;
|
|
int count = 0;
|
|
int dispatch;
|
|
|
|
/* Guard against recursion */
|
|
if (ocs_atomic_add_return(&xport->io_pending_recursing, 1)) {
|
|
/* This function is already running. Decrement and return. */
|
|
ocs_atomic_sub_return(&xport->io_pending_recursing, 1);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
ocs_lock(&xport->io_pending_lock);
|
|
status = 0;
|
|
hio = NULL;
|
|
io = ocs_list_remove_head(&xport->io_pending_list);
|
|
if (io != NULL) {
|
|
if (io->io_type == OCS_IO_TYPE_ABORT) {
|
|
hio = NULL;
|
|
} else {
|
|
hio = ocs_hw_io_alloc(&ocs->hw);
|
|
if (hio == NULL) {
|
|
/*
|
|
* No HW IO available.
|
|
* Put IO back on the front of pending list
|
|
*/
|
|
ocs_list_add_head(&xport->io_pending_list, io);
|
|
io = NULL;
|
|
} else {
|
|
hio->eq = io->hw_priv;
|
|
}
|
|
}
|
|
}
|
|
/* Must drop the lock before dispatching the IO */
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
|
|
if (io != NULL) {
|
|
count++;
|
|
|
|
/*
|
|
* We pulled an IO off the pending list,
|
|
* and either got an HW IO or don't need one
|
|
*/
|
|
ocs_atomic_sub_return(&xport->io_pending_count, 1);
|
|
if (hio == NULL) {
|
|
status = ocs_scsi_io_dispatch_no_hw_io(io);
|
|
} else {
|
|
status = ocs_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
|
|
* ocs_hw_async_call()
|
|
*/
|
|
if (ocs_hw_async_call(&ocs->hw, ocs_scsi_check_pending_async_cb, io)) {
|
|
ocs_log_test(ocs, "call to ocs_hw_async_call() failed\n");
|
|
}
|
|
}
|
|
}
|
|
} while (io != NULL);
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (count == 0 ) {
|
|
dispatch = 0;
|
|
|
|
ocs_lock(&xport->io_pending_lock);
|
|
ocs_list_foreach(&xport->io_pending_list, io) {
|
|
if (io->io_type == OCS_IO_TYPE_ABORT) {
|
|
if (io->io_to_abort->hio != NULL) {
|
|
/* This IO has a HW IO, so it is active. Dispatch the abort. */
|
|
dispatch = 1;
|
|
} else {
|
|
/* Leave this abort on the pending list and keep looking */
|
|
dispatch = 0;
|
|
}
|
|
}
|
|
if (dispatch) {
|
|
ocs_list_remove(&xport->io_pending_list, io);
|
|
ocs_atomic_sub_return(&xport->io_pending_count, 1);
|
|
break;
|
|
}
|
|
}
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
|
|
if (dispatch) {
|
|
status = ocs_scsi_io_dispatch_no_hw_io(io);
|
|
if (status) {
|
|
if (ocs_hw_async_call(&ocs->hw, ocs_scsi_check_pending_async_cb, io)) {
|
|
ocs_log_test(ocs, "call to ocs_hw_async_call() failed\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ocs_atomic_sub_return(&xport->io_pending_recursing, 1);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief Attempt to dispatch a non-abort IO
|
|
*
|
|
* @par Description
|
|
* An IO is dispatched:
|
|
* - if the pending list is not empty, add IO to pending list
|
|
* and call a function to process the pending list.
|
|
* - if pending list is empty, try to allocate a HW IO. If none
|
|
* is available, place this IO at the tail of the pending IO
|
|
* list.
|
|
* - if HW IO is available, attach this IO to the HW IO and
|
|
* submit it.
|
|
*
|
|
* @param io Pointer to IO structure.
|
|
* @param cb Callback function.
|
|
*
|
|
* @return Returns 0 on success, a negative error code value on failure.
|
|
*/
|
|
|
|
int32_t
|
|
ocs_scsi_io_dispatch(ocs_io_t *io, void *cb)
|
|
{
|
|
ocs_hw_io_t *hio;
|
|
ocs_t *ocs = io->ocs;
|
|
ocs_xport_t *xport = ocs->xport;
|
|
|
|
ocs_assert(io->cmd_tgt || io->cmd_ini, -1);
|
|
ocs_assert((io->io_type != OCS_IO_TYPE_ABORT), -1);
|
|
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 != NULL) {
|
|
return ocs_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.
|
|
*/
|
|
ocs_lock(&xport->io_pending_lock);
|
|
if (!ocs_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) {
|
|
ocs_list_add_head(&xport->io_pending_list, io);
|
|
} else {
|
|
ocs_list_add_tail(&xport->io_pending_list, io);
|
|
}
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
ocs_atomic_add_return(&xport->io_pending_count, 1);
|
|
ocs_atomic_add_return(&xport->io_total_pending, 1);
|
|
|
|
/* process pending list */
|
|
ocs_scsi_check_pending(ocs);
|
|
return 0;
|
|
}
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
|
|
/*
|
|
* 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 = ocs_hw_io_alloc(&io->ocs->hw);
|
|
if (hio == NULL) {
|
|
|
|
/* Couldn't get a HW IO. Save this IO on the pending list */
|
|
ocs_lock(&xport->io_pending_lock);
|
|
ocs_list_add_tail(&xport->io_pending_list, io);
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
|
|
ocs_atomic_add_return(&xport->io_total_pending, 1);
|
|
ocs_atomic_add_return(&xport->io_pending_count, 1);
|
|
return 0;
|
|
}
|
|
|
|
/* We successfully allocated a HW IO; dispatch to HW */
|
|
return ocs_scsi_io_dispatch_hw_io(io, hio);
|
|
}
|
|
|
|
/**
|
|
* @brief Attempt to dispatch an Abort IO.
|
|
*
|
|
* @par Description
|
|
* An Abort IO is dispatched:
|
|
* - if the pending list is not empty, add IO to pending list
|
|
* and call a function to process the pending list.
|
|
* - if pending list is empty, send abort to the HW.
|
|
*
|
|
* @param io Pointer to IO structure.
|
|
* @param cb Callback function.
|
|
*
|
|
* @return Returns 0 on success, a negative error code value on failure.
|
|
*/
|
|
|
|
int32_t
|
|
ocs_scsi_io_dispatch_abort(ocs_io_t *io, void *cb)
|
|
{
|
|
ocs_t *ocs = io->ocs;
|
|
ocs_xport_t *xport = ocs->xport;
|
|
|
|
ocs_assert((io->io_type == OCS_IO_TYPE_ABORT), -1);
|
|
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.
|
|
*/
|
|
ocs_lock(&xport->io_pending_lock);
|
|
if (!ocs_list_empty(&xport->io_pending_list)) {
|
|
ocs_list_add_tail(&xport->io_pending_list, io);
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
ocs_atomic_add_return(&xport->io_pending_count, 1);
|
|
ocs_atomic_add_return(&xport->io_total_pending, 1);
|
|
|
|
/* process pending list */
|
|
ocs_scsi_check_pending(ocs);
|
|
return 0;
|
|
}
|
|
ocs_unlock(&xport->io_pending_lock);
|
|
|
|
/* nothing on pending list, dispatch abort */
|
|
return ocs_scsi_io_dispatch_no_hw_io(io);
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Dispatch IO
|
|
*
|
|
* @par Description
|
|
* An IO and its associated HW IO is dispatched to the HW.
|
|
*
|
|
* @param io Pointer to IO structure.
|
|
* @param hio Pointer to HW IO structure from which IO will be
|
|
* dispatched.
|
|
*
|
|
* @return Returns 0 on success, a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_scsi_io_dispatch_hw_io(ocs_io_t *io, ocs_hw_io_t *hio)
|
|
{
|
|
int32_t rc;
|
|
ocs_t *ocs = io->ocs;
|
|
|
|
/* 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 OCS_SCSI_WQ_STEERING_CLASS >> OCS_SCSI_WQ_STEERING_SHIFT:
|
|
hio->wq_steering = OCS_HW_WQ_STEERING_CLASS;
|
|
break;
|
|
case OCS_SCSI_WQ_STEERING_REQUEST >> OCS_SCSI_WQ_STEERING_SHIFT:
|
|
hio->wq_steering = OCS_HW_WQ_STEERING_REQUEST;
|
|
break;
|
|
case OCS_SCSI_WQ_STEERING_CPU >> OCS_SCSI_WQ_STEERING_SHIFT:
|
|
hio->wq_steering = OCS_HW_WQ_STEERING_CPU;
|
|
break;
|
|
}
|
|
|
|
|
|
switch (io->io_type) {
|
|
case OCS_IO_TYPE_IO: {
|
|
uint32_t max_sgl;
|
|
uint32_t total_count;
|
|
uint32_t host_allocated;
|
|
|
|
ocs_hw_get(&ocs->hw, OCS_HW_N_SGL, &max_sgl);
|
|
ocs_hw_get(&ocs->hw, OCS_HW_SGL_CHAINING_HOST_ALLOCATED, &host_allocated);
|
|
|
|
/*
|
|
* If the requested SGL is larger than the default size, then we can allocate
|
|
* an overflow SGL.
|
|
*/
|
|
total_count = ocs_scsi_count_sgls(&io->hw_dif, io->sgl, io->sgl_count);
|
|
|
|
/*
|
|
* Lancer requires us to allocate the chained memory area, but
|
|
* Skyhawk must use the SGL list associated with another XRI.
|
|
*/
|
|
if (host_allocated && total_count > max_sgl) {
|
|
/* Compute count needed, the number extra plus 1 for the link sge */
|
|
uint32_t count = total_count - max_sgl + 1;
|
|
rc = ocs_dma_alloc(ocs, &io->ovfl_sgl, count*sizeof(sli4_sge_t), 64);
|
|
if (rc) {
|
|
ocs_log_err(ocs, "ocs_dma_alloc overflow sgl failed\n");
|
|
break;
|
|
}
|
|
rc = ocs_hw_io_register_sgl(&ocs->hw, io->hio, &io->ovfl_sgl, count);
|
|
if (rc) {
|
|
ocs_scsi_io_free_ovfl(io);
|
|
ocs_log_err(ocs, "ocs_hw_io_register_sgl() failed\n");
|
|
break;
|
|
}
|
|
/* EVT: update chained_io_count */
|
|
io->node->chained_io_count++;
|
|
}
|
|
|
|
rc = ocs_scsi_build_sgls(&ocs->hw, io->hio, &io->hw_dif, io->sgl, io->sgl_count, io->hio_type);
|
|
if (rc) {
|
|
ocs_scsi_io_free_ovfl(io);
|
|
break;
|
|
}
|
|
|
|
if (OCS_LOG_ENABLE_SCSI_TRACE(ocs)) {
|
|
ocs_log_sgl(io);
|
|
}
|
|
|
|
if (io->app_id) {
|
|
io->iparam.fcp_tgt.app_id = io->app_id;
|
|
}
|
|
|
|
rc = ocs_hw_io_send(&io->ocs->hw, io->hio_type, io->hio, io->wire_len, &io->iparam, &io->node->rnode,
|
|
io->hw_cb, io);
|
|
break;
|
|
}
|
|
case OCS_IO_TYPE_ELS:
|
|
case OCS_IO_TYPE_CT: {
|
|
rc = ocs_hw_srrs_send(&ocs->hw, io->hio_type, io->hio,
|
|
&io->els_req, io->wire_len,
|
|
&io->els_rsp, &io->node->rnode, &io->iparam,
|
|
io->hw_cb, io);
|
|
break;
|
|
}
|
|
case OCS_IO_TYPE_CT_RESP: {
|
|
rc = ocs_hw_srrs_send(&ocs->hw, io->hio_type, io->hio,
|
|
&io->els_rsp, io->wire_len,
|
|
NULL, &io->node->rnode, &io->iparam,
|
|
io->hw_cb, io);
|
|
break;
|
|
}
|
|
case OCS_IO_TYPE_BLS_RESP: {
|
|
/* no need to update tgt_task_tag for BLS response since the RX_ID
|
|
* will be specified by the payload, not the XRI */
|
|
rc = ocs_hw_srrs_send(&ocs->hw, io->hio_type, io->hio,
|
|
NULL, 0, NULL, &io->node->rnode, &io->iparam, io->hw_cb, io);
|
|
break;
|
|
}
|
|
default:
|
|
scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Dispatch IO
|
|
*
|
|
* @par Description
|
|
* An IO that does require a HW IO is dispatched to the HW.
|
|
*
|
|
* @param io Pointer to IO structure.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_scsi_io_dispatch_no_hw_io(ocs_io_t *io)
|
|
{
|
|
int32_t rc;
|
|
|
|
switch (io->io_type) {
|
|
case OCS_IO_TYPE_ABORT: {
|
|
ocs_hw_io_t *hio_to_abort = NULL;
|
|
ocs_assert(io->io_to_abort, -1);
|
|
hio_to_abort = io->io_to_abort->hio;
|
|
|
|
if (hio_to_abort == NULL) {
|
|
/*
|
|
* 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: " SCSI_IOFMT " not active\n",
|
|
SCSI_IOFMT_ARGS(io->io_to_abort));
|
|
((ocs_hw_done_t)io->hw_cb)(io->hio, NULL, 0, SLI4_FC_WCQE_STATUS_SUCCESS, 0, io);
|
|
rc = 0;
|
|
} else {
|
|
/* HW IO is valid, abort it */
|
|
scsi_io_printf(io, "aborting " SCSI_IOFMT "\n", SCSI_IOFMT_ARGS(io->io_to_abort));
|
|
rc = ocs_hw_io_abort(&io->ocs->hw, hio_to_abort, io->send_abts,
|
|
io->hw_cb, io);
|
|
if (rc) {
|
|
int status = SLI4_FC_WCQE_STATUS_SUCCESS;
|
|
if ((rc != OCS_HW_RTN_IO_NOT_ACTIVE) &&
|
|
(rc != OCS_HW_RTN_IO_ABORT_IN_PROGRESS)) {
|
|
status = -1;
|
|
scsi_io_printf(io, "Failed to abort IO: " SCSI_IOFMT " status=%d\n",
|
|
SCSI_IOFMT_ARGS(io->io_to_abort), rc);
|
|
}
|
|
((ocs_hw_done_t)io->hw_cb)(io->hio, NULL, 0, status, 0, io);
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
scsi_io_printf(io, "Unknown IO type=%d\n", io->io_type);
|
|
rc = -1;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Send read/write data.
|
|
*
|
|
* @par Description
|
|
* This call is made by a target-server to initiate a SCSI read or write data phase, transferring
|
|
* data between the target to the remote initiator. The payload is specified by the
|
|
* scatter-gather list @c sgl of length @c sgl_count. The @c wire_len argument
|
|
* specifies the payload length (independent of the scatter-gather list cumulative length).
|
|
* @n @n
|
|
* The @c flags argument has one bit, OCS_SCSI_LAST_DATAPHASE, which is a hint to the base
|
|
* driver that it may use auto SCSI response features if the hardware supports it.
|
|
* @n @n
|
|
* Upon completion, the callback function @b cb is called with flags indicating that the
|
|
* IO has completed (OCS_SCSI_IO_COMPL) and another data phase or response may be sent;
|
|
* that the IO has completed and no response needs to be sent (OCS_SCSI_IO_COMPL_NO_RSP);
|
|
* or that the IO was aborted (OCS_SCSI_IO_ABORTED).
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
* @param flags Flags controlling the sending of data.
|
|
* @param dif_info Pointer to T10 DIF fields, or NULL if no DIF.
|
|
* @param sgl Pointer to the payload scatter-gather list.
|
|
* @param sgl_count Count of the scatter-gather list elements.
|
|
* @param xwire_len Length of the payload on wire, in bytes.
|
|
* @param type HW IO type.
|
|
* @param enable_ar Enable auto-response if true.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-supplied callback data.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static inline int32_t
|
|
ocs_scsi_xfer_data(ocs_io_t *io, uint32_t flags,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t xwire_len,
|
|
ocs_hw_io_type_e type, int enable_ar,
|
|
ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
ocs_t *ocs;
|
|
uint32_t disable_ar_tgt_dif = FALSE;
|
|
size_t residual = 0;
|
|
|
|
if ((dif_info != NULL) && (dif_info->dif_oper == OCS_SCSI_DIF_OPER_DISABLED)) {
|
|
dif_info = NULL;
|
|
}
|
|
|
|
ocs_assert(io, -1);
|
|
|
|
if (dif_info != NULL) {
|
|
ocs_hw_get(&io->ocs->hw, OCS_HW_DISABLE_AR_TGT_DIF, &disable_ar_tgt_dif);
|
|
if (disable_ar_tgt_dif) {
|
|
enable_ar = FALSE;
|
|
}
|
|
}
|
|
|
|
io->sgl_count = sgl_count;
|
|
|
|
/* If needed, copy SGL */
|
|
if (sgl && (sgl != io->sgl)) {
|
|
ocs_assert(sgl_count <= io->sgl_allocated, -1);
|
|
ocs_memcpy(io->sgl, sgl, sgl_count*sizeof(*io->sgl));
|
|
}
|
|
|
|
ocs = io->ocs;
|
|
ocs_assert(ocs, -1);
|
|
ocs_assert(io->node, -1);
|
|
|
|
scsi_io_trace(io, "%s wire_len %d\n", (type == OCS_HW_IO_TARGET_READ) ? "send" : "recv", xwire_len);
|
|
|
|
ocs_assert(sgl, -1);
|
|
ocs_assert(sgl_count > 0, -1);
|
|
ocs_assert(io->exp_xfer_len > io->transferred, -1);
|
|
|
|
io->hio_type = type;
|
|
|
|
io->scsi_tgt_cb = cb;
|
|
io->scsi_tgt_cb_arg = arg;
|
|
|
|
rc = ocs_scsi_convert_dif_info(ocs, dif_info, &io->hw_dif);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
/* If DIF is used, then save lba for error recovery */
|
|
if (dif_info) {
|
|
io->scsi_dif_info = *dif_info;
|
|
}
|
|
|
|
io->wire_len = MIN(xwire_len, io->exp_xfer_len - io->transferred);
|
|
residual = (xwire_len - io->wire_len);
|
|
|
|
ocs_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.dif_oper = io->hw_dif.dif;
|
|
io->iparam.fcp_tgt.blk_size = io->hw_dif.blk_size;
|
|
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 & OCS_SCSI_LAST_DATAPHASE) &&
|
|
(residual == 0) && ((io->transferred + io->wire_len) == io->exp_xfer_len) && (!(flags & OCS_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 ocs_scsi_send_resp
|
|
*/
|
|
io->transferred += residual;
|
|
|
|
/* Adjust the SGL size if there is overrun */
|
|
|
|
if (residual) {
|
|
ocs_scsi_sgl_t *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 & OCS_SCSI_LOW_LATENCY) != 0;
|
|
io->wq_steering = (flags & OCS_SCSI_WQ_STEERING_MASK) >> OCS_SCSI_WQ_STEERING_SHIFT;
|
|
io->wq_class = (flags & OCS_SCSI_WQ_CLASS_MASK) >> OCS_SCSI_WQ_CLASS_SHIFT;
|
|
|
|
return ocs_scsi_io_dispatch(io, ocs_target_io_cb);
|
|
}
|
|
|
|
|
|
int32_t
|
|
ocs_scsi_send_rd_data(ocs_io_t *io, uint32_t flags,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t len,
|
|
ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
return ocs_scsi_xfer_data(io, flags, dif_info, sgl, sgl_count, len, OCS_HW_IO_TARGET_READ,
|
|
enable_tsend_auto_resp(io->ocs), cb, arg);
|
|
}
|
|
|
|
int32_t
|
|
ocs_scsi_recv_wr_data(ocs_io_t *io, uint32_t flags,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t len,
|
|
ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
return ocs_scsi_xfer_data(io, flags, dif_info, sgl, sgl_count, len, OCS_HW_IO_TARGET_WRITE,
|
|
enable_treceive_auto_resp(io->ocs), cb, arg);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Free overflow SGL.
|
|
*
|
|
* @par Description
|
|
* Free the overflow SGL if it is present.
|
|
*
|
|
* @param io Pointer to IO object.
|
|
*
|
|
* @return None.
|
|
*/
|
|
static void
|
|
ocs_scsi_io_free_ovfl(ocs_io_t *io) {
|
|
if (io->ovfl_sgl.size) {
|
|
ocs_dma_free(io->ocs, &io->ovfl_sgl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Send response data.
|
|
*
|
|
* @par Description
|
|
* This function is used by a target-server to send the SCSI response data to a remote
|
|
* initiator node. The target-server populates the @c ocs_scsi_cmd_resp_t
|
|
* argument with scsi status, status qualifier, sense data, and response data, as
|
|
* needed.
|
|
* @n @n
|
|
* Upon completion, the callback function @c cb is invoked. The target-server will generally
|
|
* clean up its IO context resources and call ocs_scsi_io_complete().
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
* @param flags Flags to control sending of the SCSI response.
|
|
* @param rsp Pointer to the response data populated by the caller.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_send_resp(ocs_io_t *io, uint32_t flags, ocs_scsi_cmd_resp_t *rsp, ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
ocs_t *ocs;
|
|
int32_t residual;
|
|
int auto_resp = TRUE; /* Always try auto resp */
|
|
uint8_t scsi_status = 0;
|
|
uint16_t scsi_status_qualifier = 0;
|
|
uint8_t *sense_data = NULL;
|
|
uint32_t sense_data_length = 0;
|
|
|
|
ocs_assert(io, -1);
|
|
|
|
ocs = io->ocs;
|
|
ocs_assert(ocs, -1);
|
|
|
|
ocs_assert(io->node, -1);
|
|
|
|
ocs_scsi_convert_dif_info(ocs, NULL, &io->hw_dif);
|
|
|
|
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 = OCS_HW_IO_TARGET_RSP;
|
|
|
|
io->scsi_tgt_cb = cb;
|
|
io->scsi_tgt_cb_arg = arg;
|
|
|
|
ocs_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 & OCS_SCSI_LOW_LATENCY) != 0;
|
|
io->wq_steering = (flags & OCS_SCSI_WQ_STEERING_MASK) >> OCS_SCSI_WQ_STEERING_SHIFT;
|
|
io->wq_class = (flags & OCS_SCSI_WQ_CLASS_MASK) >> OCS_SCSI_WQ_CLASS_SHIFT;
|
|
|
|
if ((scsi_status != 0) || residual || sense_data_length) {
|
|
fcp_rsp_iu_t *fcprsp = io->rspbuf.virt;
|
|
|
|
if (!fcprsp) {
|
|
ocs_log_err(ocs, "NULL response buffer\n");
|
|
return -1;
|
|
}
|
|
|
|
auto_resp = FALSE;
|
|
|
|
ocs_memset(fcprsp, 0, sizeof(*fcprsp));
|
|
|
|
io->wire_len += (sizeof(*fcprsp) - sizeof(fcprsp->data));
|
|
|
|
fcprsp->scsi_status = scsi_status;
|
|
*((uint16_t*)fcprsp->status_qualifier) = ocs_htobe16(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
|
|
* then this is an overflow
|
|
*/
|
|
if (residual > 0) {
|
|
fcprsp->flags |= FCP_RESID_UNDER;
|
|
*((uint32_t *)fcprsp->fcp_resid) = ocs_htobe32(residual);
|
|
} else {
|
|
fcprsp->flags |= FCP_RESID_OVER;
|
|
*((uint32_t *)fcprsp->fcp_resid) = ocs_htobe32(-residual);
|
|
}
|
|
}
|
|
|
|
if (sense_data && sense_data_length) {
|
|
ocs_assert(sense_data_length <= sizeof(fcprsp->data), -1);
|
|
fcprsp->flags |= FCP_SNS_LEN_VALID;
|
|
ocs_memcpy(fcprsp->data, sense_data, sense_data_length);
|
|
*((uint32_t*)fcprsp->fcp_sns_len) = ocs_htobe32(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 ocs_scsi_io_dispatch(io, ocs_target_io_cb);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Send TMF response data.
|
|
*
|
|
* @par Description
|
|
* This function is used by a target-server to send SCSI TMF response data to a remote
|
|
* initiator node.
|
|
* Upon completion, the callback function @c cb is invoked. The target-server will generally
|
|
* clean up its IO context resources and call ocs_scsi_io_complete().
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
* @param rspcode TMF response code.
|
|
* @param addl_rsp_info Additional TMF response information (may be NULL for zero data).
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_send_tmf_resp(ocs_io_t *io, ocs_scsi_tmf_resp_e rspcode, uint8_t addl_rsp_info[3],
|
|
ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc = -1;
|
|
ocs_t *ocs = NULL;
|
|
fcp_rsp_iu_t *fcprsp = NULL;
|
|
fcp_rsp_info_t *rspinfo = NULL;
|
|
uint8_t fcp_rspcode;
|
|
|
|
ocs_assert(io, -1);
|
|
ocs_assert(io->ocs, -1);
|
|
ocs_assert(io->node, -1);
|
|
|
|
ocs = io->ocs;
|
|
|
|
io->wire_len = 0;
|
|
ocs_scsi_convert_dif_info(ocs, NULL, &io->hw_dif);
|
|
|
|
switch(rspcode) {
|
|
case OCS_SCSI_TMF_FUNCTION_COMPLETE:
|
|
fcp_rspcode = FCP_TMF_COMPLETE;
|
|
break;
|
|
case OCS_SCSI_TMF_FUNCTION_SUCCEEDED:
|
|
case OCS_SCSI_TMF_FUNCTION_IO_NOT_FOUND:
|
|
fcp_rspcode = FCP_TMF_SUCCEEDED;
|
|
break;
|
|
case OCS_SCSI_TMF_FUNCTION_REJECTED:
|
|
fcp_rspcode = FCP_TMF_REJECTED;
|
|
break;
|
|
case OCS_SCSI_TMF_INCORRECT_LOGICAL_UNIT_NUMBER:
|
|
fcp_rspcode = FCP_TMF_INCORRECT_LUN;
|
|
break;
|
|
case OCS_SCSI_TMF_SERVICE_DELIVERY:
|
|
fcp_rspcode = FCP_TMF_FAILED;
|
|
break;
|
|
default:
|
|
fcp_rspcode = FCP_TMF_REJECTED;
|
|
break;
|
|
}
|
|
|
|
io->hio_type = OCS_HW_IO_TARGET_RSP;
|
|
|
|
io->scsi_tgt_cb = cb;
|
|
io->scsi_tgt_cb_arg = arg;
|
|
|
|
if (io->tmf_cmd == OCS_SCSI_TMF_ABORT_TASK) {
|
|
rc = ocs_target_send_bls_resp(io, cb, arg);
|
|
return rc;
|
|
}
|
|
|
|
/* populate the FCP TMF response */
|
|
fcprsp = io->rspbuf.virt;
|
|
ocs_memset(fcprsp, 0, sizeof(*fcprsp));
|
|
|
|
fcprsp->flags |= FCP_RSP_LEN_VALID;
|
|
|
|
rspinfo = (fcp_rsp_info_t*) fcprsp->data;
|
|
if (addl_rsp_info != NULL) {
|
|
ocs_memcpy(rspinfo->addl_rsp_info, addl_rsp_info, sizeof(rspinfo->addl_rsp_info));
|
|
}
|
|
rspinfo->rsp_code = fcp_rspcode;
|
|
|
|
io->wire_len = sizeof(*fcprsp) - sizeof(fcprsp->data) + sizeof(*rspinfo);
|
|
|
|
*((uint32_t*)fcprsp->fcp_rsp_len) = ocs_htobe32(sizeof(*rspinfo));
|
|
|
|
io->sgl[0].addr = io->rspbuf.phys;
|
|
io->sgl[0].dif_addr = 0;
|
|
io->sgl[0].len = io->wire_len;
|
|
io->sgl_count = 1;
|
|
|
|
ocs_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 = ocs_scsi_io_dispatch(io, ocs_target_io_cb);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Process target abort callback.
|
|
*
|
|
* @par Description
|
|
* Accepts HW abort requests.
|
|
*
|
|
* @param hio HW IO context.
|
|
* @param rnode Remote node.
|
|
* @param length Length of response data.
|
|
* @param status Completion status.
|
|
* @param ext_status Extended completion status.
|
|
* @param app Application-specified callback data.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_target_abort_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *app)
|
|
{
|
|
ocs_io_t *io = app;
|
|
ocs_t *ocs;
|
|
ocs_scsi_io_status_e scsi_status;
|
|
|
|
ocs_assert(io, -1);
|
|
ocs_assert(io->ocs, -1);
|
|
|
|
ocs = io->ocs;
|
|
|
|
if (io->abort_cb) {
|
|
ocs_scsi_io_cb_t abort_cb = io->abort_cb;
|
|
void *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 = OCS_SCSI_STATUS_GOOD;
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_LOCAL_REJECT:
|
|
switch (ext_status) {
|
|
case SLI4_FC_LOCAL_REJECT_NO_XRI:
|
|
scsi_status = OCS_SCSI_STATUS_NO_IO;
|
|
break;
|
|
case SLI4_FC_LOCAL_REJECT_ABORT_IN_PROGRESS:
|
|
scsi_status = OCS_SCSI_STATUS_ABORT_IN_PROGRESS;
|
|
break;
|
|
default:
|
|
/* TODO: we have seen 0x15 (abort in progress) */
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_FCP_RSP_FAILURE:
|
|
scsi_status = OCS_SCSI_STATUS_CHECK_RESPONSE;
|
|
break;
|
|
default:
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
/* invoke callback */
|
|
abort_cb(io->io_to_abort, scsi_status, 0, abort_cb_arg);
|
|
}
|
|
|
|
ocs_assert(io != io->io_to_abort, -1);
|
|
|
|
/* done with IO to abort */
|
|
ocs_ref_put(&io->io_to_abort->ref); /* ocs_ref_get(): ocs_scsi_tgt_abort_io() */
|
|
|
|
ocs_io_free(ocs, io);
|
|
|
|
ocs_scsi_check_pending(ocs);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Abort a target IO.
|
|
*
|
|
* @par Description
|
|
* This routine is called from a SCSI target-server. It initiates an abort of a
|
|
* previously-issued target data phase or response request.
|
|
*
|
|
* @param io IO context.
|
|
* @param cb SCSI target server callback.
|
|
* @param arg SCSI target server supplied callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a non-zero value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_tgt_abort_io(ocs_io_t *io, ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
ocs_t *ocs;
|
|
ocs_xport_t *xport;
|
|
int32_t rc;
|
|
|
|
ocs_io_t *abort_io = NULL;
|
|
ocs_assert(io, -1);
|
|
ocs_assert(io->node, -1);
|
|
ocs_assert(io->ocs, -1);
|
|
|
|
ocs = io->ocs;
|
|
xport = ocs->xport;
|
|
|
|
/* take a reference on IO being aborted */
|
|
if ((ocs_ref_get_unless_zero(&io->ref) == 0)) {
|
|
/* command no longer active */
|
|
scsi_io_printf(io, "command no longer active\n");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* allocate a new IO to send the abort request. Use ocs_io_alloc() directly, as
|
|
* we need an IO object that will not fail allocation due to allocations being
|
|
* disabled (in ocs_scsi_io_alloc())
|
|
*/
|
|
abort_io = ocs_io_alloc(ocs);
|
|
if (abort_io == NULL) {
|
|
ocs_atomic_add_return(&xport->io_alloc_failed_count, 1);
|
|
ocs_ref_put(&io->ref); /* ocs_ref_get(): same function */
|
|
return -1;
|
|
}
|
|
|
|
/* Save the target server callback and argument */
|
|
ocs_assert(abort_io->hio == NULL, -1);
|
|
|
|
/* set generic fields */
|
|
abort_io->cmd_tgt = TRUE;
|
|
abort_io->node = io->node;
|
|
|
|
/* set type and abort-specific fields */
|
|
abort_io->io_type = OCS_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 = ocs_scsi_io_dispatch_abort(abort_io, ocs_target_abort_cb);
|
|
if (rc) {
|
|
ocs_ref_put(&io->ref); /* ocs_ref_get(): same function */
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @brief Process target BLS response callback.
|
|
*
|
|
* @par Description
|
|
* Accepts HW abort requests.
|
|
*
|
|
* @param hio HW IO context.
|
|
* @param rnode Remote node.
|
|
* @param length Length of response data.
|
|
* @param status Completion status.
|
|
* @param ext_status Extended completion status.
|
|
* @param app Application-specified callback data.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_target_bls_resp_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *app)
|
|
{
|
|
ocs_io_t *io = app;
|
|
ocs_t *ocs;
|
|
ocs_scsi_io_status_e bls_status;
|
|
|
|
ocs_assert(io, -1);
|
|
ocs_assert(io->ocs, -1);
|
|
|
|
ocs = io->ocs;
|
|
|
|
/* 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 = OCS_SCSI_STATUS_ERROR;
|
|
} else {
|
|
bls_status = OCS_SCSI_STATUS_GOOD;
|
|
}
|
|
|
|
if (io->bls_cb) {
|
|
ocs_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);
|
|
}
|
|
|
|
ocs_scsi_check_pending(ocs);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Complete abort request.
|
|
*
|
|
* @par Description
|
|
* An abort request is completed by posting a BA_ACC for the IO that requested the abort.
|
|
*
|
|
* @param io Pointer to the IO context.
|
|
* @param cb Callback function to invoke upon completion.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_target_send_bls_resp(ocs_io_t *io, ocs_scsi_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
fc_ba_acc_payload_t *acc;
|
|
|
|
ocs_assert(io, -1);
|
|
|
|
/* fill out IO structure with everything needed to send BA_ACC */
|
|
ocs_memset(&io->iparam, 0, sizeof(io->iparam));
|
|
io->iparam.bls.ox_id = io->init_task_tag;
|
|
io->iparam.bls.rx_id = io->abort_rx_id;
|
|
|
|
acc = (void *)io->iparam.bls.payload;
|
|
|
|
ocs_memset(io->iparam.bls.payload, 0, sizeof(io->iparam.bls.payload));
|
|
acc->ox_id = io->iparam.bls.ox_id;
|
|
acc->rx_id = io->iparam.bls.rx_id;
|
|
acc->high_seq_cnt = UINT16_MAX;
|
|
|
|
/* generic io fields have already been populated */
|
|
|
|
/* set type and BLS-specific fields */
|
|
io->io_type = OCS_IO_TYPE_BLS_RESP;
|
|
io->display_name = "bls_rsp";
|
|
io->hio_type = OCS_HW_BLS_ACC;
|
|
io->bls_cb = cb;
|
|
io->bls_cb_arg = arg;
|
|
|
|
/* dispatch IO */
|
|
rc = ocs_scsi_io_dispatch(io, ocs_target_bls_resp_cb);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Notify the base driver that the IO is complete.
|
|
*
|
|
* @par Description
|
|
* This function is called by a target-server to notify the base driver that an IO
|
|
* has completed, allowing for the base driver to free resources.
|
|
* @n
|
|
* @n @b Note: This function is not called by initiator-clients.
|
|
*
|
|
* @param io Pointer to IO context.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_io_complete(ocs_io_t *io)
|
|
{
|
|
ocs_assert(io);
|
|
|
|
if (!ocs_io_busy(io)) {
|
|
ocs_log_test(io->ocs, "Got completion for non-busy io with tag 0x%x\n", io->tag);
|
|
return;
|
|
}
|
|
|
|
scsi_io_trace(io, "freeing io 0x%p %s\n", io, io->display_name);
|
|
ocs_assert(ocs_ref_read_count(&io->ref) > 0);
|
|
ocs_ref_put(&io->ref); /* ocs_ref_get(): ocs_scsi_io_alloc() */
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Handle initiator IO completion.
|
|
*
|
|
* @par Description
|
|
* This callback is made upon completion of an initiator operation (initiator read/write command).
|
|
*
|
|
* @param hio HW IO context.
|
|
* @param rnode Remote node.
|
|
* @param length Length of completion data.
|
|
* @param status Completion status.
|
|
* @param ext_status Extended completion status.
|
|
* @param app Application-specified callback data.
|
|
*
|
|
* @return None.
|
|
*/
|
|
|
|
static void
|
|
ocs_initiator_io_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length,
|
|
int32_t status, uint32_t ext_status, void *app)
|
|
{
|
|
ocs_io_t *io = app;
|
|
ocs_t *ocs;
|
|
ocs_scsi_io_status_e scsi_status;
|
|
|
|
ocs_assert(io);
|
|
ocs_assert(io->scsi_ini_cb);
|
|
|
|
scsi_io_trace(io, "status x%x ext_status x%x\n", status, ext_status);
|
|
|
|
ocs = io->ocs;
|
|
ocs_assert(ocs);
|
|
|
|
ocs_scsi_io_free_ovfl(io);
|
|
|
|
/* Call target server completion */
|
|
if (io->scsi_ini_cb) {
|
|
fcp_rsp_iu_t *fcprsp = io->rspbuf.virt;
|
|
ocs_scsi_cmd_resp_t rsp;
|
|
ocs_scsi_rsp_io_cb_t cb = io->scsi_ini_cb;
|
|
uint32_t flags = 0;
|
|
uint8_t *pd = fcprsp->data;
|
|
|
|
/* Clear the callback before invoking the callback */
|
|
io->scsi_ini_cb = NULL;
|
|
|
|
ocs_memset(&rsp, 0, sizeof(rsp));
|
|
|
|
/* Unless status is FCP_RSP_FAILURE, fcprsp is not filled in */
|
|
switch (status) {
|
|
case SLI4_FC_WCQE_STATUS_SUCCESS:
|
|
scsi_status = OCS_SCSI_STATUS_GOOD;
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_FCP_RSP_FAILURE:
|
|
scsi_status = OCS_SCSI_STATUS_CHECK_RESPONSE;
|
|
rsp.scsi_status = fcprsp->scsi_status;
|
|
rsp.scsi_status_qualifier = ocs_be16toh(*((uint16_t*)fcprsp->status_qualifier));
|
|
|
|
if (fcprsp->flags & FCP_RSP_LEN_VALID) {
|
|
rsp.response_data = pd;
|
|
rsp.response_data_length = ocs_fc_getbe32(fcprsp->fcp_rsp_len);
|
|
pd += rsp.response_data_length;
|
|
}
|
|
if (fcprsp->flags & FCP_SNS_LEN_VALID) {
|
|
uint32_t sns_len = ocs_fc_getbe32(fcprsp->fcp_sns_len);
|
|
rsp.sense_data = pd;
|
|
rsp.sense_data_length = sns_len;
|
|
pd += sns_len;
|
|
}
|
|
/* Set residual */
|
|
if (fcprsp->flags & FCP_RESID_OVER) {
|
|
rsp.residual = -ocs_fc_getbe32(fcprsp->fcp_resid);
|
|
rsp.response_wire_length = length;
|
|
} else if (fcprsp->flags & FCP_RESID_UNDER) {
|
|
rsp.residual = ocs_fc_getbe32(fcprsp->fcp_resid);
|
|
rsp.response_wire_length = length;
|
|
}
|
|
|
|
/*
|
|
* Note: The FCP_RSP_FAILURE can be returned for initiator IOs when the total data
|
|
* placed does not match the requested length even if the status is good. If
|
|
* the status is all zeroes, then we have to assume that a frame(s) were
|
|
* dropped and change the status to LOCAL_REJECT/OUT_OF_ORDER_DATA
|
|
*/
|
|
if (length != io->wire_len) {
|
|
uint32_t rsp_len = ext_status;
|
|
uint8_t *rsp_bytes = io->rspbuf.virt;
|
|
uint32_t i;
|
|
uint8_t all_zeroes = (rsp_len > 0);
|
|
/* Check if the rsp is zero */
|
|
for (i = 0; i < rsp_len; i++) {
|
|
if (rsp_bytes[i] != 0) {
|
|
all_zeroes = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (all_zeroes) {
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
ocs_log_test(io->ocs, "[%s]" SCSI_IOFMT "local reject=0x%02x\n",
|
|
io->node->display_name, SCSI_IOFMT_ARGS(io),
|
|
SLI4_FC_LOCAL_REJECT_OUT_OF_ORDER_DATA);
|
|
}
|
|
}
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_LOCAL_REJECT:
|
|
if (ext_status == SLI4_FC_LOCAL_REJECT_SEQUENCE_TIMEOUT) {
|
|
scsi_status = OCS_SCSI_STATUS_COMMAND_TIMEOUT;
|
|
} else {
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
}
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_DI_ERROR:
|
|
if (ext_status & 0x01) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_GUARD_ERROR;
|
|
} else if (ext_status & 0x02) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_APP_TAG_ERROR;
|
|
} else if (ext_status & 0x04) {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_REF_TAG_ERROR;
|
|
} else {
|
|
scsi_status = OCS_SCSI_STATUS_DIF_UNKNOWN_ERROR;
|
|
}
|
|
break;
|
|
default:
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
|
|
cb(io, scsi_status, &rsp, flags, io->scsi_ini_cb_arg);
|
|
|
|
}
|
|
ocs_scsi_check_pending(ocs);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Initiate initiator read IO.
|
|
*
|
|
* @par Description
|
|
* This call is made by an initiator-client to send a SCSI read command. The payload
|
|
* for the command is given by a scatter-gather list @c sgl for @c sgl_count
|
|
* entries.
|
|
* @n @n
|
|
* Upon completion, the callback @b cb is invoked and passed request status.
|
|
* If the command completed successfully, the callback is given SCSI response data.
|
|
*
|
|
* @param node Pointer to the node.
|
|
* @param io Pointer to the IO context.
|
|
* @param lun LUN value.
|
|
* @param cdb Pointer to the CDB.
|
|
* @param cdb_len Length of the CDB.
|
|
* @param dif_info Pointer to the T10 DIF fields, or NULL if no DIF.
|
|
* @param sgl Pointer to the scatter-gather list.
|
|
* @param sgl_count Count of the scatter-gather list elements.
|
|
* @param wire_len Length of the payload.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_send_rd_io(ocs_node_t *node, ocs_io_t *io, uint64_t lun, void *cdb, uint32_t cdb_len,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t wire_len,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
|
|
rc = ocs_scsi_send_io(OCS_HW_IO_INITIATOR_READ, node, io, lun, 0, cdb, cdb_len, dif_info, sgl, sgl_count,
|
|
wire_len, 0, cb, arg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Initiate initiator write IO.
|
|
*
|
|
* @par Description
|
|
* This call is made by an initiator-client to send a SCSI write command. The payload
|
|
* for the command is given by a scatter-gather list @c sgl for @c sgl_count
|
|
* entries.
|
|
* @n @n
|
|
* Upon completion, the callback @c cb is invoked and passed request status. If the command
|
|
* completed successfully, the callback is given SCSI response data.
|
|
*
|
|
* @param node Pointer to the node.
|
|
* @param io Pointer to IO context.
|
|
* @param lun LUN value.
|
|
* @param cdb Pointer to the CDB.
|
|
* @param cdb_len Length of the CDB.
|
|
* @param dif_info Pointer to the T10 DIF fields, or NULL if no DIF.
|
|
* @param sgl Pointer to the scatter-gather list.
|
|
* @param sgl_count Count of the scatter-gather list elements.
|
|
* @param wire_len Length of the payload.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t ocs_scsi_send_wr_io(ocs_node_t *node, ocs_io_t *io, uint64_t lun, void *cdb, uint32_t cdb_len,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t wire_len,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
|
|
rc = ocs_scsi_send_io(OCS_HW_IO_INITIATOR_WRITE, node, io, lun, 0, cdb, cdb_len, dif_info, sgl, sgl_count,
|
|
wire_len, 0, cb, arg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Initiate initiator write IO.
|
|
*
|
|
* @par Description
|
|
* This call is made by an initiator-client to send a SCSI write command. The payload
|
|
* for the command is given by a scatter-gather list @c sgl for @c sgl_count
|
|
* entries.
|
|
* @n @n
|
|
* Upon completion, the callback @c cb is invoked and passed request status. If the command
|
|
* completed successfully, the callback is given SCSI response data.
|
|
*
|
|
* @param node Pointer to the node.
|
|
* @param io Pointer to IO context.
|
|
* @param lun LUN value.
|
|
* @param cdb Pointer to the CDB.
|
|
* @param cdb_len Length of the CDB.
|
|
* @param dif_info Pointer to the T10 DIF fields, or NULL if no DIF.
|
|
* @param sgl Pointer to the scatter-gather list.
|
|
* @param sgl_count Count of the scatter-gather list elements.
|
|
* @param wire_len Length of the payload.
|
|
* @param first_burst Number of first burst bytes to send.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_send_wr_io_first_burst(ocs_node_t *node, ocs_io_t *io, uint64_t lun, void *cdb, uint32_t cdb_len,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t wire_len, uint32_t first_burst,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
|
|
rc = ocs_scsi_send_io(OCS_HW_IO_INITIATOR_WRITE, node, io, lun, 0, cdb, cdb_len, dif_info, sgl, sgl_count,
|
|
wire_len, 0, cb, arg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Initiate initiator SCSI command with no data.
|
|
*
|
|
* @par Description
|
|
* This call is made by an initiator-client to send a SCSI command with no data.
|
|
* @n @n
|
|
* Upon completion, the callback @c cb is invoked and passed request status. If the command
|
|
* completed successfully, the callback is given SCSI response data.
|
|
*
|
|
* @param node Pointer to the node.
|
|
* @param io Pointer to the IO context.
|
|
* @param lun LUN value.
|
|
* @param cdb Pointer to the CDB.
|
|
* @param cdb_len Length of the CDB.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t ocs_scsi_send_nodata_io(ocs_node_t *node, ocs_io_t *io, uint64_t lun, void *cdb, uint32_t cdb_len,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
|
|
rc = ocs_scsi_send_io(OCS_HW_IO_INITIATOR_NODATA, node, io, lun, 0, cdb, cdb_len, NULL, NULL, 0, 0, 0, cb, arg);
|
|
|
|
return rc;
|
|
}
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Initiate initiator task management operation.
|
|
*
|
|
* @par Description
|
|
* This command is used to send a SCSI task management function command. If the command
|
|
* requires it (QUERY_TASK_SET for example), a payload may be associated with the command.
|
|
* If no payload is required, then @c sgl_count may be zero and @c sgl is ignored.
|
|
* @n @n
|
|
* Upon completion @c cb is invoked with status and SCSI response data.
|
|
*
|
|
* @param node Pointer to the node.
|
|
* @param io Pointer to the IO context.
|
|
* @param io_to_abort Pointer to the IO context to abort in the
|
|
* case of OCS_SCSI_TMF_ABORT_TASK. Note: this can point to the
|
|
* same the same ocs_io_t as @c io, provided that @c io does not
|
|
* have any outstanding work requests.
|
|
* @param lun LUN value.
|
|
* @param tmf Task management command.
|
|
* @param sgl Pointer to the scatter-gather list.
|
|
* @param sgl_count Count of the scatter-gather list elements.
|
|
* @param len Length of the payload.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
int32_t
|
|
ocs_scsi_send_tmf(ocs_node_t *node, ocs_io_t *io, ocs_io_t *io_to_abort, uint64_t lun, ocs_scsi_tmf_cmd_e tmf,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t len, ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
ocs_assert(io, -1);
|
|
|
|
if (tmf == OCS_SCSI_TMF_ABORT_TASK) {
|
|
ocs_assert(io_to_abort, -1);
|
|
|
|
/* take a reference on IO being aborted */
|
|
if ((ocs_ref_get_unless_zero(&io_to_abort->ref) == 0)) {
|
|
/* command no longer active */
|
|
scsi_io_printf(io, "command no longer active\n");
|
|
return -1;
|
|
}
|
|
/* generic io fields have already been populated */
|
|
|
|
/* abort-specific fields */
|
|
io->io_type = OCS_IO_TYPE_ABORT;
|
|
io->display_name = "abort_task";
|
|
io->io_to_abort = io_to_abort;
|
|
io->send_abts = TRUE;
|
|
io->scsi_ini_cb = cb;
|
|
io->scsi_ini_cb_arg = arg;
|
|
|
|
/* now dispatch IO */
|
|
rc = ocs_scsi_io_dispatch_abort(io, ocs_scsi_abort_io_cb);
|
|
if (rc) {
|
|
scsi_io_printf(io, "Failed to dispatch abort\n");
|
|
ocs_ref_put(&io->ref); /* ocs_ref_get(): same function */
|
|
}
|
|
} else {
|
|
io->display_name = "tmf";
|
|
rc = ocs_scsi_send_io(OCS_HW_IO_INITIATOR_READ, node, io, lun, tmf, NULL, 0, NULL,
|
|
sgl, sgl_count, len, 0, cb, arg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Send an FCP IO.
|
|
*
|
|
* @par Description
|
|
* An FCP read/write IO command, with optional task management flags, is sent to @c node.
|
|
*
|
|
* @param type HW IO type to send.
|
|
* @param node Pointer to the node destination of the IO.
|
|
* @param io Pointer to the IO context.
|
|
* @param lun LUN value.
|
|
* @param tmf Task management command.
|
|
* @param cdb Pointer to the SCSI CDB.
|
|
* @param cdb_len Length of the CDB, in bytes.
|
|
* @param dif_info Pointer to the T10 DIF fields, or NULL if no DIF.
|
|
* @param sgl Pointer to the scatter-gather list.
|
|
* @param sgl_count Number of SGL entries in SGL.
|
|
* @param wire_len Payload length, in bytes, of data on wire.
|
|
* @param first_burst Number of first burst bytes to send.
|
|
* @param cb Completion callback.
|
|
* @param arg Application-specified completion callback argument.
|
|
*
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
/* tc: could elminiate LUN, as it's part of the IO structure */
|
|
|
|
static int32_t ocs_scsi_send_io(ocs_hw_io_type_e type, ocs_node_t *node, ocs_io_t *io, uint64_t lun,
|
|
ocs_scsi_tmf_cmd_e tmf, uint8_t *cdb, uint32_t cdb_len,
|
|
ocs_scsi_dif_info_t *dif_info,
|
|
ocs_scsi_sgl_t *sgl, uint32_t sgl_count, uint32_t wire_len, uint32_t first_burst,
|
|
ocs_scsi_rsp_io_cb_t cb, void *arg)
|
|
{
|
|
int32_t rc;
|
|
ocs_t *ocs;
|
|
fcp_cmnd_iu_t *cmnd;
|
|
uint32_t cmnd_bytes = 0;
|
|
uint32_t *fcp_dl;
|
|
uint8_t tmf_flags = 0;
|
|
|
|
ocs_assert(io->node, -1);
|
|
ocs_assert(io->node == node, -1);
|
|
ocs_assert(io, -1);
|
|
ocs = io->ocs;
|
|
ocs_assert(cb, -1);
|
|
|
|
io->sgl_count = sgl_count;
|
|
|
|
/* Copy SGL if needed */
|
|
if (sgl != io->sgl) {
|
|
ocs_assert(sgl_count <= io->sgl_allocated, -1);
|
|
ocs_memcpy(io->sgl, sgl, sizeof(*io->sgl) * sgl_count);
|
|
}
|
|
|
|
/* save initiator and target task tags for debugging */
|
|
io->tgt_task_tag = 0xffff;
|
|
|
|
io->wire_len = wire_len;
|
|
io->hio_type = type;
|
|
|
|
if (OCS_LOG_ENABLE_SCSI_TRACE(ocs)) {
|
|
char buf[80];
|
|
ocs_textbuf_t txtbuf;
|
|
uint32_t i;
|
|
|
|
ocs_textbuf_init(ocs, &txtbuf, buf, sizeof(buf));
|
|
|
|
ocs_textbuf_printf(&txtbuf, "cdb%d: ", cdb_len);
|
|
for (i = 0; i < cdb_len; i ++) {
|
|
ocs_textbuf_printf(&txtbuf, "%02X%s", cdb[i], (i == (cdb_len-1)) ? "" : " ");
|
|
}
|
|
scsi_io_printf(io, "%s len %d, %s\n", (io->hio_type == OCS_HW_IO_INITIATOR_READ) ? "read" :
|
|
(io->hio_type == OCS_HW_IO_INITIATOR_WRITE) ? "write" : "", io->wire_len,
|
|
ocs_textbuf_get_buffer(&txtbuf));
|
|
}
|
|
|
|
|
|
ocs_assert(io->cmdbuf.virt, -1);
|
|
|
|
cmnd = io->cmdbuf.virt;
|
|
|
|
ocs_assert(sizeof(*cmnd) <= io->cmdbuf.size, -1);
|
|
|
|
ocs_memset(cmnd, 0, sizeof(*cmnd));
|
|
|
|
/* Default FCP_CMND IU doesn't include additional CDB bytes but does include FCP_DL */
|
|
cmnd_bytes = sizeof(fcp_cmnd_iu_t) - sizeof(cmnd->fcp_cdb_and_dl) + sizeof(uint32_t);
|
|
|
|
fcp_dl = (uint32_t*)(&(cmnd->fcp_cdb_and_dl));
|
|
|
|
if (cdb) {
|
|
if (cdb_len <= 16) {
|
|
ocs_memcpy(cmnd->fcp_cdb, cdb, cdb_len);
|
|
} else {
|
|
uint32_t addl_cdb_bytes;
|
|
|
|
ocs_memcpy(cmnd->fcp_cdb, cdb, 16);
|
|
addl_cdb_bytes = cdb_len - 16;
|
|
ocs_memcpy(cmnd->fcp_cdb_and_dl, &(cdb[16]), addl_cdb_bytes);
|
|
/* additional_fcp_cdb_length is in words, not bytes */
|
|
cmnd->additional_fcp_cdb_length = (addl_cdb_bytes + 3) / 4;
|
|
fcp_dl += cmnd->additional_fcp_cdb_length;
|
|
|
|
/* Round up additional CDB bytes */
|
|
cmnd_bytes += (addl_cdb_bytes + 3) & ~0x3;
|
|
}
|
|
}
|
|
|
|
be64enc(cmnd->fcp_lun, CAM_EXTLUN_BYTE_SWIZZLE(lun));
|
|
|
|
if (node->fcp2device) {
|
|
if(ocs_get_crn(node, &cmnd->command_reference_number,
|
|
lun)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
switch (tmf) {
|
|
case OCS_SCSI_TMF_QUERY_TASK_SET:
|
|
tmf_flags = FCP_QUERY_TASK_SET;
|
|
break;
|
|
case OCS_SCSI_TMF_ABORT_TASK_SET:
|
|
tmf_flags = FCP_ABORT_TASK_SET;
|
|
break;
|
|
case OCS_SCSI_TMF_CLEAR_TASK_SET:
|
|
tmf_flags = FCP_CLEAR_TASK_SET;
|
|
break;
|
|
case OCS_SCSI_TMF_QUERY_ASYNCHRONOUS_EVENT:
|
|
tmf_flags = FCP_QUERY_ASYNCHRONOUS_EVENT;
|
|
break;
|
|
case OCS_SCSI_TMF_LOGICAL_UNIT_RESET:
|
|
tmf_flags = FCP_LOGICAL_UNIT_RESET;
|
|
break;
|
|
case OCS_SCSI_TMF_CLEAR_ACA:
|
|
tmf_flags = FCP_CLEAR_ACA;
|
|
break;
|
|
case OCS_SCSI_TMF_TARGET_RESET:
|
|
tmf_flags = FCP_TARGET_RESET;
|
|
break;
|
|
default:
|
|
tmf_flags = 0;
|
|
}
|
|
cmnd->task_management_flags = tmf_flags;
|
|
|
|
*fcp_dl = ocs_htobe32(io->wire_len);
|
|
|
|
switch (io->hio_type) {
|
|
case OCS_HW_IO_INITIATOR_READ:
|
|
cmnd->rddata = 1;
|
|
break;
|
|
case OCS_HW_IO_INITIATOR_WRITE:
|
|
cmnd->wrdata = 1;
|
|
break;
|
|
case OCS_HW_IO_INITIATOR_NODATA:
|
|
/* sets neither */
|
|
break;
|
|
default:
|
|
ocs_log_test(ocs, "bad IO type %d\n", io->hio_type);
|
|
return -1;
|
|
}
|
|
|
|
rc = ocs_scsi_convert_dif_info(ocs, dif_info, &io->hw_dif);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
io->scsi_ini_cb = cb;
|
|
io->scsi_ini_cb_arg = arg;
|
|
|
|
/* set command and response buffers in the iparam */
|
|
io->iparam.fcp_ini.cmnd = &io->cmdbuf;
|
|
io->iparam.fcp_ini.cmnd_size = cmnd_bytes;
|
|
io->iparam.fcp_ini.rsp = &io->rspbuf;
|
|
io->iparam.fcp_ini.flags = 0;
|
|
io->iparam.fcp_ini.dif_oper = io->hw_dif.dif;
|
|
io->iparam.fcp_ini.blk_size = io->hw_dif.blk_size;
|
|
io->iparam.fcp_ini.timeout = io->timeout;
|
|
io->iparam.fcp_ini.first_burst = first_burst;
|
|
|
|
return ocs_scsi_io_dispatch(io, ocs_initiator_io_cb);
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Callback for an aborted IO.
|
|
*
|
|
* @par Description
|
|
* Callback function invoked upon completion of an IO abort request.
|
|
*
|
|
* @param hio HW IO context.
|
|
* @param rnode Remote node.
|
|
* @param len Response length.
|
|
* @param status Completion status.
|
|
* @param ext_status Extended completion status.
|
|
* @param arg Application-specific callback, usually IO context.
|
|
|
|
* @return Returns 0 on success, or a negative error code value on failure.
|
|
*/
|
|
|
|
static int32_t
|
|
ocs_scsi_abort_io_cb(struct ocs_hw_io_s *hio, ocs_remote_node_t *rnode, uint32_t len, int32_t status,
|
|
uint32_t ext_status, void *arg)
|
|
{
|
|
ocs_io_t *io = arg;
|
|
ocs_t *ocs;
|
|
ocs_scsi_io_status_e scsi_status = OCS_SCSI_STATUS_GOOD;
|
|
|
|
ocs_assert(io, -1);
|
|
ocs_assert(ocs_io_busy(io), -1);
|
|
ocs_assert(io->ocs, -1);
|
|
ocs_assert(io->io_to_abort, -1);
|
|
ocs = io->ocs;
|
|
|
|
ocs_log_debug(ocs, "status %d ext %d\n", status, ext_status);
|
|
|
|
/* done with IO to abort */
|
|
ocs_ref_put(&io->io_to_abort->ref); /* ocs_ref_get(): ocs_scsi_send_tmf() */
|
|
|
|
ocs_scsi_io_free_ovfl(io);
|
|
|
|
switch (status) {
|
|
case SLI4_FC_WCQE_STATUS_SUCCESS:
|
|
scsi_status = OCS_SCSI_STATUS_GOOD;
|
|
break;
|
|
case SLI4_FC_WCQE_STATUS_LOCAL_REJECT:
|
|
if (ext_status == SLI4_FC_LOCAL_REJECT_ABORT_REQUESTED) {
|
|
scsi_status = OCS_SCSI_STATUS_ABORTED;
|
|
} else if (ext_status == SLI4_FC_LOCAL_REJECT_NO_XRI) {
|
|
scsi_status = OCS_SCSI_STATUS_NO_IO;
|
|
} else if (ext_status == SLI4_FC_LOCAL_REJECT_ABORT_IN_PROGRESS) {
|
|
scsi_status = OCS_SCSI_STATUS_ABORT_IN_PROGRESS;
|
|
} else {
|
|
ocs_log_test(ocs, "Unhandled local reject 0x%x/0x%x\n", status, ext_status);
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
}
|
|
break;
|
|
default:
|
|
scsi_status = OCS_SCSI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (io->scsi_ini_cb) {
|
|
(*io->scsi_ini_cb)(io, scsi_status, NULL, 0, io->scsi_ini_cb_arg);
|
|
} else {
|
|
ocs_scsi_io_free(io);
|
|
}
|
|
|
|
ocs_scsi_check_pending(ocs);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Return SCSI API integer valued property.
|
|
*
|
|
* @par Description
|
|
* This function is called by a target-server or initiator-client to
|
|
* retrieve an integer valued property.
|
|
*
|
|
* @param ocs Pointer to the ocs.
|
|
* @param prop Property value to return.
|
|
*
|
|
* @return Returns a value, or 0 if invalid property was requested.
|
|
*/
|
|
uint32_t
|
|
ocs_scsi_get_property(ocs_t *ocs, ocs_scsi_property_e prop)
|
|
{
|
|
ocs_xport_t *xport = ocs->xport;
|
|
uint32_t val;
|
|
|
|
switch (prop) {
|
|
case OCS_SCSI_MAX_SGE:
|
|
if (0 == ocs_hw_get(&ocs->hw, OCS_HW_MAX_SGE, &val)) {
|
|
return val;
|
|
}
|
|
break;
|
|
case OCS_SCSI_MAX_SGL:
|
|
if (ocs->ctrlmask & OCS_CTRLMASK_TEST_CHAINED_SGLS) {
|
|
/*
|
|
* If chain SGL test-mode is enabled, the number of HW SGEs
|
|
* has been limited; report back original max.
|
|
*/
|
|
return (OCS_FC_MAX_SGL);
|
|
}
|
|
if (0 == ocs_hw_get(&ocs->hw, OCS_HW_N_SGL, &val)) {
|
|
return val;
|
|
}
|
|
break;
|
|
case OCS_SCSI_MAX_IOS:
|
|
return ocs_io_pool_allocated(xport->io_pool);
|
|
case OCS_SCSI_DIF_CAPABLE:
|
|
if (0 == ocs_hw_get(&ocs->hw, OCS_HW_DIF_CAPABLE, &val)) {
|
|
return val;
|
|
}
|
|
break;
|
|
case OCS_SCSI_MAX_FIRST_BURST:
|
|
return 0;
|
|
case OCS_SCSI_DIF_MULTI_SEPARATE:
|
|
if (ocs_hw_get(&ocs->hw, OCS_HW_DIF_MULTI_SEPARATE, &val) == 0) {
|
|
return val;
|
|
}
|
|
break;
|
|
case OCS_SCSI_ENABLE_TASK_SET_FULL:
|
|
/* Return FALSE if we are send frame capable */
|
|
if (ocs_hw_get(&ocs->hw, OCS_HW_SEND_FRAME_CAPABLE, &val) == 0) {
|
|
return ! val;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ocs_log_debug(ocs, "invalid property request %d\n", prop);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Return a property pointer.
|
|
*
|
|
* @par Description
|
|
* This function is called by a target-server or initiator-client to
|
|
* retrieve a pointer to the requested property.
|
|
*
|
|
* @param ocs Pointer to the ocs.
|
|
* @param prop Property value to return.
|
|
*
|
|
* @return Returns pointer to the requested property, or NULL otherwise.
|
|
*/
|
|
void *ocs_scsi_get_property_ptr(ocs_t *ocs, ocs_scsi_property_e prop)
|
|
{
|
|
void *rc = NULL;
|
|
|
|
switch (prop) {
|
|
case OCS_SCSI_WWNN:
|
|
rc = ocs_hw_get_ptr(&ocs->hw, OCS_HW_WWN_NODE);
|
|
break;
|
|
case OCS_SCSI_WWPN:
|
|
rc = ocs_hw_get_ptr(&ocs->hw, OCS_HW_WWN_PORT);
|
|
break;
|
|
case OCS_SCSI_PORTNUM:
|
|
rc = ocs_hw_get_ptr(&ocs->hw, OCS_HW_PORTNUM);
|
|
break;
|
|
case OCS_SCSI_BIOS_VERSION_STRING:
|
|
rc = ocs_hw_get_ptr(&ocs->hw, OCS_HW_BIOS_VERSION_STRING);
|
|
break;
|
|
#if defined(OCS_ENABLE_VPD_SUPPORT)
|
|
case OCS_SCSI_SERIALNUMBER:
|
|
{
|
|
uint8_t *pvpd;
|
|
uint32_t vpd_len;
|
|
|
|
if (ocs_hw_get(&ocs->hw, OCS_HW_VPD_LEN, &vpd_len)) {
|
|
ocs_log_test(ocs, "Can't get VPD length\n");
|
|
rc = "\012sn-unknown";
|
|
break;
|
|
}
|
|
|
|
pvpd = ocs_hw_get_ptr(&ocs->hw, OCS_HW_VPD);
|
|
if (pvpd) {
|
|
rc = ocs_find_vpd(pvpd, vpd_len, "SN");
|
|
}
|
|
|
|
if (rc == NULL ||
|
|
ocs_strlen(rc) == 0) {
|
|
/* Note: VPD is missing, using wwnn for serial number */
|
|
scsi_log(ocs, "Note: VPD is missing, using wwnn for serial number\n");
|
|
/* Use the last 32 bits of the WWN */
|
|
if ((ocs == NULL) || (ocs->domain == NULL) || (ocs->domain->sport == NULL)) {
|
|
rc = "\011(Unknown)";
|
|
} else {
|
|
rc = &ocs->domain->sport->wwnn_str[8];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OCS_SCSI_PARTNUMBER:
|
|
{
|
|
uint8_t *pvpd;
|
|
uint32_t vpd_len;
|
|
|
|
if (ocs_hw_get(&ocs->hw, OCS_HW_VPD_LEN, &vpd_len)) {
|
|
ocs_log_test(ocs, "Can't get VPD length\n");
|
|
rc = "\012pn-unknown";
|
|
break;
|
|
}
|
|
pvpd = ocs_hw_get_ptr(&ocs->hw, OCS_HW_VPD);
|
|
if (pvpd) {
|
|
rc = ocs_find_vpd(pvpd, vpd_len, "PN");
|
|
if (rc == NULL) {
|
|
rc = "\012pn-unknown";
|
|
}
|
|
} else {
|
|
rc = "\012pn-unknown";
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (rc == NULL) {
|
|
ocs_log_debug(ocs, "invalid property request %d\n", prop);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Notify that delete initiator is complete.
|
|
*
|
|
* @par Description
|
|
* Sent by the target-server to notify the base driver that the work started from
|
|
* ocs_scsi_del_initiator() is now complete and that it is safe for the node to
|
|
* release the rest of its resources.
|
|
*
|
|
* @param node Pointer to the node.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_del_initiator_complete(ocs_node_t *node)
|
|
{
|
|
/* Notify the node to resume */
|
|
ocs_node_post_event(node, OCS_EVT_NODE_DEL_INI_COMPLETE, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* @ingroup scsi_api_base
|
|
* @brief Notify that delete target is complete.
|
|
*
|
|
* @par Description
|
|
* Sent by the initiator-client to notify the base driver that the work started from
|
|
* ocs_scsi_del_target() is now complete and that it is safe for the node to
|
|
* release the rest of its resources.
|
|
*
|
|
* @param node Pointer to the node.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_del_target_complete(ocs_node_t *node)
|
|
{
|
|
/* Notify the node to resume */
|
|
ocs_node_post_event(node, OCS_EVT_NODE_DEL_TGT_COMPLETE, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Update transferred count
|
|
*
|
|
* @par Description
|
|
* Updates io->transferred, as required when using first burst, when the amount
|
|
* of first burst data processed differs from the amount of first burst
|
|
* data received.
|
|
*
|
|
* @param io Pointer to the io object.
|
|
* @param transferred Number of bytes transferred out of first burst buffers.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_update_first_burst_transferred(ocs_io_t *io, uint32_t transferred)
|
|
{
|
|
io->transferred = transferred;
|
|
}
|
|
|
|
/**
|
|
* @brief Register bounce callback for multi-threading.
|
|
*
|
|
* @par Description
|
|
* Register the back end bounce function.
|
|
*
|
|
* @param ocs Pointer to device object.
|
|
* @param fctn Function pointer of bounce function.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void
|
|
ocs_scsi_register_bounce(ocs_t *ocs, void(*fctn)(void(*fctn)(void *arg), void *arg, uint32_t s_id, uint32_t d_id,
|
|
uint32_t ox_id))
|
|
{
|
|
ocs_hw_rtn_e rc;
|
|
|
|
rc = ocs_hw_callback(&ocs->hw, OCS_HW_CB_BOUNCE, fctn, NULL);
|
|
if (rc) {
|
|
ocs_log_test(ocs, "ocs_hw_callback(OCS_HW_CB_BOUNCE) failed: %d\n", rc);
|
|
}
|
|
}
|