/*- * 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 * Functions to build and send ELS/CT/BLS commands and responses. */ /*! @defgroup els_api ELS/BLS/CT Command and Response Functions */ #include "ocs.h" #include "ocs_els.h" #include "ocs_scsi.h" #include "ocs_device.h" #define ELS_IOFMT "[i:%04x t:%04x h:%04x]" #define ELS_IOFMT_ARGS(els) els->init_task_tag, els->tgt_task_tag, els->hw_tag #define node_els_trace() \ do { \ if (OCS_LOG_ENABLE_ELS_TRACE(ocs)) \ ocs_log_info(ocs, "[%s] %-20s\n", node->display_name, __func__); \ } while (0) #define els_io_printf(els, fmt, ...) \ ocs_log_debug(els->node->ocs, "[%s]" ELS_IOFMT " %-8s " fmt, els->node->display_name, ELS_IOFMT_ARGS(els), els->display_name, ##__VA_ARGS__); static int32_t ocs_els_send(ocs_io_t *els, uint32_t reqlen, uint32_t timeout_sec, ocs_hw_srrs_cb_t cb); static int32_t ocs_els_send_rsp(ocs_io_t *els, uint32_t rsplen); static int32_t ocs_els_acc_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *arg); static ocs_io_t *ocs_bls_send_acc(ocs_io_t *io, uint32_t s_id, uint16_t ox_id, uint16_t rx_id); static int32_t ocs_bls_send_acc_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *app); static void ocs_io_transition(ocs_io_t *els, ocs_sm_function_t state, void *data); static ocs_io_t *ocs_els_abort_io(ocs_io_t *els, int send_abts); static void _ocs_els_io_free(void *arg); static void ocs_els_delay_timer_cb(void *arg); /** * @ingroup els_api * @brief ELS state machine transition wrapper. * *

Description

* This function is the transition wrapper for the ELS state machine. It grabs * the node lock prior to making the transition to protect * against multiple threads accessing a particular ELS. For example, * one thread transitioning from __els_init to * __ocs_els_wait_resp and another thread (tasklet) handling the * completion of that ELS request. * * @param els Pointer to the IO context. * @param state State to transition to. * @param data Data to pass in with the transition. * * @return None. */ static void ocs_io_transition(ocs_io_t *els, ocs_sm_function_t state, void *data) { /* protect ELS events with node lock */ ocs_node_t *node = els->node; ocs_node_lock(node); ocs_sm_transition(&els->els_sm, state, data); ocs_node_unlock(node); } /** * @ingroup els_api * @brief ELS state machine post event wrapper. * *

Description

* Post an event wrapper for the ELS state machine. This function grabs * the node lock prior to posting the event. * * @param els Pointer to the IO context. * @param evt Event to process. * @param data Data to pass in with the transition. * * @return None. */ void ocs_els_post_event(ocs_io_t *els, ocs_sm_event_t evt, void *data) { /* protect ELS events with node lock */ ocs_node_t *node = els->node; ocs_node_lock(node); els->els_evtdepth ++; ocs_sm_post_event(&els->els_sm, evt, data); els->els_evtdepth --; ocs_node_unlock(node); if (els->els_evtdepth == 0 && els->els_req_free) { ocs_els_io_free(els); } } /** * @ingroup els_api * @brief Allocate an IO structure for an ELS IO context. * *

Description

* Allocate an IO for an ELS context. Uses OCS_ELS_RSP_LEN as response size. * * @param node node to associate ELS IO with * @param reqlen Length of ELS request * @param role Role of ELS (originator/responder) * * @return pointer to IO structure allocated */ ocs_io_t * ocs_els_io_alloc(ocs_node_t *node, uint32_t reqlen, ocs_els_role_e role) { return ocs_els_io_alloc_size(node, reqlen, OCS_ELS_RSP_LEN, role); } /** * @ingroup els_api * @brief Allocate an IO structure for an ELS IO context. * *

Description

* Allocate an IO for an ELS context, allowing the caller to specify the size of the response. * * @param node node to associate ELS IO with * @param reqlen Length of ELS request * @param rsplen Length of ELS response * @param role Role of ELS (originator/responder) * * @return pointer to IO structure allocated */ ocs_io_t * ocs_els_io_alloc_size(ocs_node_t *node, uint32_t reqlen, uint32_t rsplen, ocs_els_role_e role) { ocs_t *ocs; ocs_xport_t *xport; ocs_io_t *els; 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_log_debug(ocs, "called with io_alloc_enabled = FALSE\n"); ocs_unlock(&node->active_ios_lock); return NULL; } els = ocs_io_alloc(ocs); if (els == NULL) { ocs_atomic_add_return(&xport->io_alloc_failed_count, 1); ocs_unlock(&node->active_ios_lock); return NULL; } /* initialize refcount */ ocs_ref_init(&els->ref, _ocs_els_io_free, els); switch (role) { case OCS_ELS_ROLE_ORIGINATOR: els->cmd_ini = TRUE; els->cmd_tgt = FALSE; break; case OCS_ELS_ROLE_RESPONDER: els->cmd_ini = FALSE; els->cmd_tgt = TRUE; break; } /* IO should not have an associated HW IO yet. Assigned below. */ if (els->hio != NULL) { ocs_log_err(ocs, "assertion failed. HIO is not null\n"); ocs_io_free(ocs, els); ocs_unlock(&node->active_ios_lock); return NULL; } /* populate generic io fields */ els->ocs = ocs; els->node = node; /* set type and ELS-specific fields */ els->io_type = OCS_IO_TYPE_ELS; els->display_name = "pending"; if (reqlen > OCS_ELS_REQ_LEN) { ocs_log_err(ocs, "ELS command request len greater than allocated\n"); ocs_io_free(ocs, els); ocs_unlock(&node->active_ios_lock); return NULL; } if (rsplen > OCS_ELS_GID_PT_RSP_LEN) { ocs_log_err(ocs, "ELS command response len: %d " "greater than allocated\n", rsplen); ocs_io_free(ocs, els); ocs_unlock(&node->active_ios_lock); return NULL; } els->els_req.size = reqlen; els->els_rsp.size = rsplen; if (els != NULL) { ocs_memset(&els->els_sm, 0, sizeof(els->els_sm)); els->els_sm.app = els; /* initialize fields */ els->els_retries_remaining = OCS_FC_ELS_DEFAULT_RETRIES; els->els_evtdepth = 0; els->els_pend = 0; els->els_active = 0; /* add els structure to ELS IO list */ ocs_list_add_tail(&node->els_io_pend_list, els); els->els_pend = 1; } ocs_unlock(&node->active_ios_lock); return els; } /** * @ingroup els_api * @brief Free IO structure for an ELS IO context. * *

Description

Free IO for an ELS * IO context * * @param els ELS IO structure for which IO is allocated * * @return None */ void ocs_els_io_free(ocs_io_t *els) { ocs_ref_put(&els->ref); } /** * @ingroup els_api * @brief Free IO structure for an ELS IO context. * *

Description

Free IO for an ELS * IO context * * @param arg ELS IO structure for which IO is allocated * * @return None */ static void _ocs_els_io_free(void *arg) { ocs_io_t *els = (ocs_io_t *)arg; ocs_t *ocs; ocs_node_t *node; int send_empty_event = FALSE; ocs_assert(els); ocs_assert(els->node); ocs_assert(els->node->ocs); ocs = els->node->ocs; node = els->node; ocs = node->ocs; ocs_lock(&node->active_ios_lock); if (els->els_active) { /* if active, remove from active list and check empty */ ocs_list_remove(&node->els_io_active_list, els); /* Send list empty event if the IO allocator is disabled, and the list is empty * If node->io_alloc_enabled was not checked, the event would be posted continually */ send_empty_event = (!node->io_alloc_enabled) && ocs_list_empty(&node->els_io_active_list); els->els_active = 0; } else if (els->els_pend) { /* if pending, remove from pending list; node shutdown isn't * gated off the pending list (only the active list), so no * need to check if pending list is empty */ ocs_list_remove(&node->els_io_pend_list, els); els->els_pend = 0; } else { ocs_log_err(ocs, "assertion failed: niether els->els_pend nor els->active set\n"); ocs_unlock(&node->active_ios_lock); return; } ocs_unlock(&node->active_ios_lock); ocs_io_free(ocs, els); if (send_empty_event) { ocs_node_post_event(node, OCS_EVT_ALL_CHILD_NODES_FREE, NULL); } ocs_scsi_check_pending(ocs); } /** * @ingroup els_api * @brief Make ELS IO active * * @param els Pointer to the IO context to make active. * * @return Returns 0 on success; or a negative error code value on failure. */ static void ocs_els_make_active(ocs_io_t *els) { ocs_node_t *node = els->node; /* move ELS from pending list to active list */ ocs_lock(&node->active_ios_lock); if (els->els_pend) { if (els->els_active) { ocs_log_err(node->ocs, "assertion failed: both els->els_pend and els->active set\n"); ocs_unlock(&node->active_ios_lock); return; } else { /* remove from pending list */ ocs_list_remove(&node->els_io_pend_list, els); els->els_pend = 0; /* add els structure to ELS IO list */ ocs_list_add_tail(&node->els_io_active_list, els); els->els_active = 1; } } else { /* must be retrying; make sure it's already active */ if (!els->els_active) { ocs_log_err(node->ocs, "assertion failed: niether els->els_pend nor els->active set\n"); } } ocs_unlock(&node->active_ios_lock); } /** * @ingroup els_api * @brief Send the ELS command. * *

Description

* The command, given by the \c els IO context, is sent to the node that the IO was * configured with, using ocs_hw_srrs_send(). Upon completion, * the \c cb callback is invoked, * with the application-specific argument set to the \c els IO context. * * @param els Pointer to the IO context. * @param reqlen Byte count in the payload to send. * @param timeout_sec Command timeout, in seconds (0 -> 2*R_A_TOV). * @param cb Completion callback. * * @return Returns 0 on success; or a negative error code value on failure. */ static int32_t ocs_els_send(ocs_io_t *els, uint32_t reqlen, uint32_t timeout_sec, ocs_hw_srrs_cb_t cb) { ocs_node_t *node = els->node; /* update ELS request counter */ node->els_req_cnt++; /* move ELS from pending list to active list */ ocs_els_make_active(els); els->wire_len = reqlen; return ocs_scsi_io_dispatch(els, cb); } /** * @ingroup els_api * @brief Send the ELS response. * *

Description

* The ELS response, given by the \c els IO context, is sent to the node * that the IO was configured with, using ocs_hw_srrs_send(). * * @param els Pointer to the IO context. * @param rsplen Byte count in the payload to send. * * @return Returns 0 on success; or a negative error value on failure. */ static int32_t ocs_els_send_rsp(ocs_io_t *els, uint32_t rsplen) { ocs_node_t *node = els->node; /* increment ELS completion counter */ node->els_cmpl_cnt++; /* move ELS from pending list to active list */ ocs_els_make_active(els); els->wire_len = rsplen; return ocs_scsi_io_dispatch(els, ocs_els_acc_cb); } /** * @ingroup els_api * @brief Handle ELS IO request completions. * *

Description

* This callback is used for several ELS send operations. * * @param hio Pointer to the HW IO context that completed. * @param rnode Pointer to the remote node. * @param length Length of the returned payload data. * @param status Status of the completion. * @param ext_status Extended status of the completion. * @param arg Application-specific argument (generally a pointer to the ELS IO context). * * @return Returns 0 on success; or a negative error value on failure. */ static int32_t ocs_els_req_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *arg) { ocs_io_t *els; ocs_node_t *node; ocs_t *ocs; ocs_node_cb_t cbdata; ocs_io_t *io; ocs_assert(arg, -1); io = arg; els = io; ocs_assert(els, -1); ocs_assert(els->node, -1); node = els->node; ocs_assert(node->ocs, -1); ocs = node->ocs; ocs_assert(io->hio, -1); ocs_assert(hio == io->hio, -1); if (status != 0) { els_io_printf(els, "status x%x ext x%x\n", status, ext_status); } /* set the response len element of els->rsp */ els->els_rsp.len = length; cbdata.status = status; cbdata.ext_status = ext_status; cbdata.header = NULL; cbdata.els = els; /* FW returns the number of bytes received on the link in * the WCQE, not the amount placed in the buffer; use this info to * check if there was an overrun. */ if (length > els->els_rsp.size) { ocs_log_warn(ocs, "ELS response returned len=%d > buflen=%zu\n", length, els->els_rsp.size); ocs_els_post_event(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &cbdata); return 0; } /* Post event to ELS IO object */ switch (status) { case SLI4_FC_WCQE_STATUS_SUCCESS: ocs_els_post_event(els, OCS_EVT_SRRS_ELS_REQ_OK, &cbdata); break; case SLI4_FC_WCQE_STATUS_LS_RJT: ocs_els_post_event(els, OCS_EVT_SRRS_ELS_REQ_RJT, &cbdata); break; case SLI4_FC_WCQE_STATUS_LOCAL_REJECT: switch (ext_status) { case SLI4_FC_LOCAL_REJECT_SEQUENCE_TIMEOUT: ocs_els_post_event(els, OCS_EVT_ELS_REQ_TIMEOUT, &cbdata); break; case SLI4_FC_LOCAL_REJECT_ABORT_REQUESTED: ocs_els_post_event(els, OCS_EVT_ELS_REQ_ABORTED, &cbdata); break; default: ocs_els_post_event(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &cbdata); break; } break; default: ocs_log_warn(ocs, "els req complete: failed status x%x, ext_status, x%x\n", status, ext_status); ocs_els_post_event(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &cbdata); break; } return 0; } /** * @ingroup els_api * @brief Handle ELS IO accept/response completions. * *

Description

* This callback is used for several ELS send operations. * * @param hio Pointer to the HW IO context that completed. * @param rnode Pointer to the remote node. * @param length Length of the returned payload data. * @param status Status of the completion. * @param ext_status Extended status of the completion. * @param arg Application-specific argument (generally a pointer to the ELS IO context). * * @return Returns 0 on success; or a negative error value on failure. */ static int32_t ocs_els_acc_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *arg) { ocs_io_t *els; ocs_node_t *node; ocs_t *ocs; ocs_node_cb_t cbdata; ocs_io_t *io; ocs_assert(arg, -1); io = arg; els = io; ocs_assert(els, -1); ocs_assert(els->node, -1); node = els->node; ocs_assert(node->ocs, -1); ocs = node->ocs; ocs_assert(io->hio, -1); ocs_assert(hio == io->hio, -1); cbdata.status = status; cbdata.ext_status = ext_status; cbdata.header = NULL; cbdata.els = els; /* Post node event */ switch (status) { case SLI4_FC_WCQE_STATUS_SUCCESS: ocs_node_post_event(node, OCS_EVT_SRRS_ELS_CMPL_OK, &cbdata); break; default: ocs_log_warn(ocs, "[%s] %-8s failed status x%x, ext_status x%x\n", node->display_name, els->display_name, status, ext_status); ocs_log_warn(ocs, "els acc complete: failed status x%x, ext_status, x%x\n", status, ext_status); ocs_node_post_event(node, OCS_EVT_SRRS_ELS_CMPL_FAIL, &cbdata); break; } /* If this IO has a callback, invoke it */ if (els->els_callback) { (*els->els_callback)(node, &cbdata, els->els_callback_arg); } ocs_els_io_free(els); return 0; } /** * @ingroup els_api * @brief Format and send a PLOGI ELS command. * *

Description

* Construct a PLOGI payload using the domain SLI port service parameters, * and send to the \c node. * * @param node Node to which the PLOGI is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_plogi(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, void (*cb)(ocs_node_t *node, ocs_node_cb_t *cbdata, void *arg), void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fc_plogi_payload_t *plogi; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*plogi), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "plogi"; /* Build PLOGI request */ plogi = els->els_req.virt; ocs_memcpy(plogi, node->sport->service_params, sizeof(*plogi)); plogi->command_code = FC_ELS_CMD_PLOGI; plogi->resv1 = 0; ocs_display_sparams(node->display_name, "plogi send req", 0, NULL, plogi->common_service_parameters); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Format and send a FLOGI ELS command. * *

Description

* Construct an FLOGI payload, and send to the \c node. * * @param node Node to which the FLOGI is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_flogi(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs; fc_plogi_payload_t *flogi; ocs_assert(node, NULL); ocs_assert(node->ocs, NULL); ocs_assert(node->sport, NULL); ocs = node->ocs; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*flogi), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "flogi"; /* Build FLOGI request */ flogi = els->els_req.virt; ocs_memcpy(flogi, node->sport->service_params, sizeof(*flogi)); flogi->command_code = FC_ELS_CMD_FLOGI; flogi->resv1 = 0; /* Priority tagging support */ flogi->common_service_parameters[1] |= ocs_htobe32(1U << 23); ocs_display_sparams(node->display_name, "flogi send req", 0, NULL, flogi->common_service_parameters); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Format and send a FDISC ELS command. * *

Description

* Construct an FDISC payload, and send to the \c node. * * @param node Node to which the FDISC is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_fdisc(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs; fc_plogi_payload_t *fdisc; ocs_assert(node, NULL); ocs_assert(node->ocs, NULL); ocs = node->ocs; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*fdisc), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "fdisc"; /* Build FDISC request */ fdisc = els->els_req.virt; ocs_memcpy(fdisc, node->sport->service_params, sizeof(*fdisc)); fdisc->command_code = FC_ELS_CMD_FDISC; fdisc->resv1 = 0; ocs_display_sparams(node->display_name, "fdisc send req", 0, NULL, fdisc->common_service_parameters); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a PRLI ELS command. * *

Description

* Construct a PRLI ELS command, and send to the \c node. * * @param node Node to which the PRLI is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_prli(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_t *ocs = node->ocs; ocs_io_t *els; fc_prli_payload_t *prli; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*prli), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "prli"; /* Build PRLI request */ prli = els->els_req.virt; ocs_memset(prli, 0, sizeof(*prli)); prli->command_code = FC_ELS_CMD_PRLI; prli->page_length = 16; prli->payload_length = ocs_htobe16(sizeof(fc_prli_payload_t)); prli->type = FC_TYPE_FCP; prli->type_ext = 0; prli->flags = ocs_htobe16(FC_PRLI_ESTABLISH_IMAGE_PAIR); prli->service_params = ocs_htobe16(FC_PRLI_READ_XRDY_DISABLED | (node->sport->enable_ini ? FC_PRLI_INITIATOR_FUNCTION : 0) | (node->sport->enable_tgt ? FC_PRLI_TARGET_FUNCTION : 0)); /* For Tape Drive support */ prli->service_params |= ocs_htobe16(FC_PRLI_CONFIRMED_COMPLETION | FC_PRLI_RETRY | FC_PRLI_TASK_RETRY_ID_REQ| FC_PRLI_REC_SUPPORT); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a PRLO ELS command. * *

Description

* Construct a PRLO ELS command, and send to the \c node. * * @param node Node to which the PRLO is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_prlo(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_t *ocs = node->ocs; ocs_io_t *els; fc_prlo_payload_t *prlo; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*prlo), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "prlo"; /* Build PRLO request */ prlo = els->els_req.virt; ocs_memset(prlo, 0, sizeof(*prlo)); prlo->command_code = FC_ELS_CMD_PRLO; prlo->page_length = 16; prlo->payload_length = ocs_htobe16(sizeof(fc_prlo_payload_t)); prlo->type = FC_TYPE_FCP; prlo->type_ext = 0; els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a LOGO ELS command. * *

Description

* Format a LOGO, and send to the \c node. * * @param node Node to which the LOGO is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_logo(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs; fc_logo_payload_t *logo; fc_plogi_payload_t *sparams; ocs = node->ocs; node_els_trace(); sparams = (fc_plogi_payload_t*) node->sport->service_params; els = ocs_els_io_alloc(node, sizeof(*logo), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "logo"; /* Build LOGO request */ logo = els->els_req.virt; ocs_memset(logo, 0, sizeof(*logo)); logo->command_code = FC_ELS_CMD_LOGO; logo->resv1 = 0; logo->port_id = fc_htobe24(node->rnode.sport->fc_id); logo->port_name_hi = sparams->port_name_hi; logo->port_name_lo = sparams->port_name_lo; els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send an ADISC ELS command. * *

Description

* Construct an ADISC ELS command, and send to the \c node. * * @param node Node to which the ADISC is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_adisc(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs; fc_adisc_payload_t *adisc; fc_plogi_payload_t *sparams; ocs_sport_t *sport = node->sport; ocs = node->ocs; node_els_trace(); sparams = (fc_plogi_payload_t*) node->sport->service_params; els = ocs_els_io_alloc(node, sizeof(*adisc), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "adisc"; /* Build ADISC request */ adisc = els->els_req.virt; sparams = (fc_plogi_payload_t*) node->sport->service_params; ocs_memset(adisc, 0, sizeof(*adisc)); adisc->command_code = FC_ELS_CMD_ADISC; adisc->hard_address = fc_htobe24(sport->fc_id); adisc->port_name_hi = sparams->port_name_hi; adisc->port_name_lo = sparams->port_name_lo; adisc->node_name_hi = sparams->node_name_hi; adisc->node_name_lo = sparams->node_name_lo; adisc->port_id = fc_htobe24(node->rnode.sport->fc_id); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a PDISC ELS command. * *

Description

* Construct a PDISC ELS command, and send to the \c node. * * @param node Node to which the PDISC is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_pdisc(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fc_plogi_payload_t *pdisc; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*pdisc), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "pdisc"; pdisc = els->els_req.virt; ocs_memcpy(pdisc, node->sport->service_params, sizeof(*pdisc)); pdisc->command_code = FC_ELS_CMD_PDISC; pdisc->resv1 = 0; els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send an SCR ELS command. * *

Description

* Format an SCR, and send to the \c node. * * @param node Node to which the SCR is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function * @param cbarg Callback function arg * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_scr(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fc_scr_payload_t *req; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*req), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "scr"; req = els->els_req.virt; ocs_memset(req, 0, sizeof(*req)); req->command_code = FC_ELS_CMD_SCR; req->function = FC_SCR_REG_FULL; els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send an RRQ ELS command. * *

Description

* Format an RRQ, and send to the \c node. * * @param node Node to which the RRQ is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function * @param cbarg Callback function arg * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_rrq(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fc_scr_payload_t *req; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*req), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "scr"; req = els->els_req.virt; ocs_memset(req, 0, sizeof(*req)); req->command_code = FC_ELS_CMD_RRQ; req->function = FC_SCR_REG_FULL; els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send an RSCN ELS command. * *

Description

* Format an RSCN, and send to the \c node. * * @param node Node to which the RRQ is sent. * @param timeout_sec Command timeout, in seconds. * @param retries Number of times to retry errors before reporting a failure. * @param port_ids Pointer to port IDs * @param port_ids_count Count of port IDs * @param cb Callback function * @param cbarg Callback function arg * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_rscn(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, void *port_ids, uint32_t port_ids_count, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fc_rscn_payload_t *req; uint32_t payload_length = sizeof(fc_rscn_affected_port_id_page_t)*(port_ids_count - 1) + sizeof(fc_rscn_payload_t); node_els_trace(); els = ocs_els_io_alloc(node, payload_length, OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->els_timeout_sec = timeout_sec; els->els_retries_remaining = retries; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "rscn"; req = els->els_req.virt; req->command_code = FC_ELS_CMD_RSCN; req->page_length = sizeof(fc_rscn_affected_port_id_page_t); req->payload_length = ocs_htobe16(sizeof(*req) + sizeof(fc_rscn_affected_port_id_page_t)*(port_ids_count-1)); els->hio_type = OCS_HW_ELS_REQ; els->iparam.els.timeout = timeout_sec; /* copy in the payload */ ocs_memcpy(req->port_list, port_ids, port_ids_count*sizeof(fc_rscn_affected_port_id_page_t)); /* Submit the request */ ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @brief Send an LS_RJT ELS response. * *

Description

* Send an LS_RJT ELS response. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID being responded to. * @param reason_code Reason code value for LS_RJT. * @param reason_code_expl Reason code explanation value for LS_RJT. * @param vendor_unique Vendor-unique value for LS_RJT. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_ls_rjt(ocs_io_t *io, uint32_t ox_id, uint32_t reason_code, uint32_t reason_code_expl, uint32_t vendor_unique, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_ls_rjt_payload_t *rjt; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "ls_rjt"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; rjt = io->els_req.virt; ocs_memset(rjt, 0, sizeof(*rjt)); rjt->command_code = FC_ELS_CMD_RJT; rjt->reason_code = reason_code; rjt->reason_code_exp = reason_code_expl; io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*rjt)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a PLOGI accept response. * *

Description

* Construct a PLOGI LS_ACC, and send to the \c node, using the originator exchange ID * \c ox_id. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID being responsed to. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_plogi_acc(ocs_io_t *io, uint32_t ox_id, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_plogi_payload_t *plogi; fc_plogi_payload_t *req = (fc_plogi_payload_t *)node->service_params; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "plog_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; plogi = io->els_req.virt; /* copy our port's service parameters to payload */ ocs_memcpy(plogi, node->sport->service_params, sizeof(*plogi)); plogi->command_code = FC_ELS_CMD_ACC; plogi->resv1 = 0; /* Set Application header support bit if requested */ if (req->common_service_parameters[1] & ocs_htobe32(1U << 24)) { plogi->common_service_parameters[1] |= ocs_htobe32(1U << 24); } /* Priority tagging support. */ if (req->common_service_parameters[1] & ocs_htobe32(1U << 23)) { plogi->common_service_parameters[1] |= ocs_htobe32(1U << 23); } ocs_display_sparams(node->display_name, "plogi send resp", 0, NULL, plogi->common_service_parameters); io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*plogi)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send an FLOGI accept response for point-to-point negotiation. * *

Description

* Construct an FLOGI accept response, and send to the \c node using the originator * exchange id \c ox_id. The \c s_id is used for the response frame source FC ID. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID for the response. * @param s_id Source FC ID to be used in the response frame. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_flogi_p2p_acc(ocs_io_t *io, uint32_t ox_id, uint32_t s_id, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_plogi_payload_t *flogi; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "flogi_p2p_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els_sid.ox_id = ox_id; io->iparam.els_sid.s_id = s_id; flogi = io->els_req.virt; /* copy our port's service parameters to payload */ ocs_memcpy(flogi, node->sport->service_params, sizeof(*flogi)); flogi->command_code = FC_ELS_CMD_ACC; flogi->resv1 = 0; ocs_memset(flogi->class1_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class2_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class3_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class4_service_parameters, 0, sizeof(flogi->class1_service_parameters)); io->hio_type = OCS_HW_ELS_RSP_SID; if ((rc = ocs_els_send_rsp(io, sizeof(*flogi)))) { ocs_els_io_free(io); io = NULL; } return io; } ocs_io_t * ocs_send_flogi_acc(ocs_io_t *io, uint32_t ox_id, uint32_t is_fport, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_plogi_payload_t *flogi; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "flogi_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els_sid.ox_id = ox_id; io->iparam.els_sid.s_id = io->node->sport->fc_id; flogi = io->els_req.virt; /* copy our port's service parameters to payload */ ocs_memcpy(flogi, node->sport->service_params, sizeof(*flogi)); /* Set F_port */ if (is_fport) { /* Set F_PORT and Multiple N_PORT_ID Assignment */ flogi->common_service_parameters[1] |= ocs_be32toh(3U << 28); } flogi->command_code = FC_ELS_CMD_ACC; flogi->resv1 = 0; ocs_display_sparams(node->display_name, "flogi send resp", 0, NULL, flogi->common_service_parameters); ocs_memset(flogi->class1_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class2_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class3_service_parameters, 0, sizeof(flogi->class1_service_parameters)); ocs_memset(flogi->class4_service_parameters, 0, sizeof(flogi->class1_service_parameters)); io->hio_type = OCS_HW_ELS_RSP_SID; if ((rc = ocs_els_send_rsp(io, sizeof(*flogi)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a PRLI accept response * *

Description

* Construct a PRLI LS_ACC response, and send to the \c node, using the originator * \c ox_id exchange ID. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_prli_acc(ocs_io_t *io, uint32_t ox_id, uint8_t fc_type, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_prli_payload_t *prli; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "prli_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; prli = io->els_req.virt; ocs_memset(prli, 0, sizeof(*prli)); prli->command_code = FC_ELS_CMD_ACC; prli->page_length = 16; prli->payload_length = ocs_htobe16(sizeof(fc_prli_payload_t)); prli->type = fc_type; prli->type_ext = 0; prli->flags = ocs_htobe16(FC_PRLI_ESTABLISH_IMAGE_PAIR | FC_PRLI_REQUEST_EXECUTED); prli->service_params = ocs_htobe16(FC_PRLI_READ_XRDY_DISABLED | (node->sport->enable_ini ? FC_PRLI_INITIATOR_FUNCTION : 0) | (node->sport->enable_tgt ? FC_PRLI_TARGET_FUNCTION : 0)); io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*prli)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a PRLO accept response. * *

Description

* Construct a PRLO LS_ACC response, and send to the \c node, using the originator * exchange ID \c ox_id. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_prlo_acc(ocs_io_t *io, uint32_t ox_id, uint8_t fc_type, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_prlo_acc_payload_t *prlo_acc; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "prlo_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; prlo_acc = io->els_req.virt; ocs_memset(prlo_acc, 0, sizeof(*prlo_acc)); prlo_acc->command_code = FC_ELS_CMD_ACC; prlo_acc->page_length = 16; prlo_acc->payload_length = ocs_htobe16(sizeof(fc_prlo_acc_payload_t)); prlo_acc->type = fc_type; prlo_acc->type_ext = 0; prlo_acc->response_code = FC_PRLO_REQUEST_EXECUTED; io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*prlo_acc)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a generic LS_ACC response without a payload. * *

Description

* A generic LS_ACC response is sent to the \c node using the originator exchange ID * \c ox_id. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange id. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_ls_acc(ocs_io_t *io, uint32_t ox_id, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_acc_payload_t *acc; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "ls_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; acc = io->els_req.virt; ocs_memset(acc, 0, sizeof(*acc)); acc->command_code = FC_ELS_CMD_ACC; io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*acc)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a LOGO accept response. * *

Description

* Construct a LOGO LS_ACC response, and send to the \c node, using the originator * exchange ID \c ox_id. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_logo_acc(ocs_io_t *io, uint32_t ox_id, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; ocs_t *ocs = node->ocs; fc_acc_payload_t *logo; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "logo_acc"; io->init_task_tag = ox_id; ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; logo = io->els_req.virt; ocs_memset(logo, 0, sizeof(*logo)); logo->command_code = FC_ELS_CMD_ACC; logo->resv1 = 0; io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*logo)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send an ADISC accept response. * *

Description

* Construct an ADISC LS__ACC, and send to the \c node, using the originator * exchange id \c ox_id. * * @param io Pointer to a SCSI IO object. * @param ox_id Originator exchange ID. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_send_adisc_acc(ocs_io_t *io, uint32_t ox_id, els_cb_t cb, void *cbarg) { ocs_node_t *node = io->node; int32_t rc; fc_adisc_payload_t *adisc; fc_plogi_payload_t *sparams; ocs_t *ocs; ocs_assert(node, NULL); ocs_assert(node->ocs, NULL); ocs = node->ocs; node_els_trace(); io->els_callback = cb; io->els_callback_arg = cbarg; io->display_name = "adisc_acc"; io->init_task_tag = ox_id; /* Go ahead and send the ELS_ACC */ ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.els.ox_id = ox_id; sparams = (fc_plogi_payload_t*) node->sport->service_params; adisc = io->els_req.virt; ocs_memset(adisc, 0, sizeof(fc_adisc_payload_t)); adisc->command_code = FC_ELS_CMD_ACC; adisc->hard_address = 0; adisc->port_name_hi = sparams->port_name_hi; adisc->port_name_lo = sparams->port_name_lo; adisc->node_name_hi = sparams->node_name_hi; adisc->node_name_lo = sparams->node_name_lo; adisc->port_id = fc_htobe24(node->rnode.sport->fc_id); io->hio_type = OCS_HW_ELS_RSP; if ((rc = ocs_els_send_rsp(io, sizeof(*adisc)))) { ocs_els_io_free(io); io = NULL; } return io; } /** * @ingroup els_api * @brief Send a RFTID CT request. * *

Description

* Construct an RFTID CT request, and send to the \c node. * * @param node Node to which the RFTID request is sent. * @param timeout_sec Time, in seconds, to wait before timing out the ELS. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_ns_send_rftid(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fcct_rftid_req_t *rftid; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*rftid), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->iparam.fc_ct.r_ctl = FC_RCTL_ELS; els->iparam.fc_ct.type = FC_TYPE_GS; els->iparam.fc_ct.df_ctl = 0; els->iparam.fc_ct.timeout = timeout_sec; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "rftid"; rftid = els->els_req.virt; ocs_memset(rftid, 0, sizeof(*rftid)); fcct_build_req_header(&rftid->hdr, FC_GS_NAMESERVER_RFT_ID, (OCS_ELS_RSP_LEN - sizeof(rftid->hdr))); rftid->port_id = ocs_htobe32(node->rnode.sport->fc_id); rftid->fc4_types[FC_GS_TYPE_WORD(FC_TYPE_FCP)] = ocs_htobe32(1 << FC_GS_TYPE_BIT(FC_TYPE_FCP)); els->hio_type = OCS_HW_FC_CT; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a RFFID CT request. * *

Description

* Construct an RFFID CT request, and send to the \c node. * * @param node Node to which the RFFID request is sent. * @param timeout_sec Time, in seconds, to wait before timing out the ELS. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_ns_send_rffid(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fcct_rffid_req_t *rffid; node_els_trace(); els = ocs_els_io_alloc(node, sizeof(*rffid), OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->iparam.fc_ct.r_ctl = FC_RCTL_ELS; els->iparam.fc_ct.type = FC_TYPE_GS; els->iparam.fc_ct.df_ctl = 0; els->iparam.fc_ct.timeout = timeout_sec; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "rffid"; rffid = els->els_req.virt; ocs_memset(rffid, 0, sizeof(*rffid)); fcct_build_req_header(&rffid->hdr, FC_GS_NAMESERVER_RFF_ID, (OCS_ELS_RSP_LEN - sizeof(rffid->hdr))); rffid->port_id = ocs_htobe32(node->rnode.sport->fc_id); if (node->sport->enable_ini) { rffid->fc4_feature_bits |= FC4_FEATURE_INITIATOR; } if (node->sport->enable_tgt) { rffid->fc4_feature_bits |= FC4_FEATURE_TARGET; } rffid->type = FC_TYPE_FCP; els->hio_type = OCS_HW_FC_CT; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a GIDPT CT request. * *

Description

* Construct a GIDPT CT request, and send to the \c node. * * @param node Node to which the GIDPT request is sent. * @param timeout_sec Time, in seconds, to wait before timing out the ELS. * @param retries Number of times to retry errors before reporting a failure. * @param cb Callback function. * @param cbarg Callback function argument. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_ns_send_gidpt(ocs_node_t *node, uint32_t timeout_sec, uint32_t retries, els_cb_t cb, void *cbarg) { ocs_io_t *els; ocs_t *ocs = node->ocs; fcct_gidpt_req_t *gidpt; node_els_trace(); els = ocs_els_io_alloc_size(node, sizeof(*gidpt), OCS_ELS_GID_PT_RSP_LEN, OCS_ELS_ROLE_ORIGINATOR); if (els == NULL) { ocs_log_err(ocs, "IO alloc failed\n"); } else { els->iparam.fc_ct.r_ctl = FC_RCTL_ELS; els->iparam.fc_ct.type = FC_TYPE_GS; els->iparam.fc_ct.df_ctl = 0; els->iparam.fc_ct.timeout = timeout_sec; els->els_callback = cb; els->els_callback_arg = cbarg; els->display_name = "gidpt"; gidpt = els->els_req.virt; ocs_memset(gidpt, 0, sizeof(*gidpt)); fcct_build_req_header(&gidpt->hdr, FC_GS_NAMESERVER_GID_PT, (OCS_ELS_GID_PT_RSP_LEN - sizeof(gidpt->hdr)) ); gidpt->domain_id_scope = 0; gidpt->area_id_scope = 0; gidpt->port_type = 0x7f; els->hio_type = OCS_HW_FC_CT; ocs_io_transition(els, __ocs_els_init, NULL); } return els; } /** * @ingroup els_api * @brief Send a BA_ACC given the request's FC header * *

Description

* Using the S_ID/D_ID from the request's FC header, generate a BA_ACC. * * @param io Pointer to a SCSI IO object. * @param hdr Pointer to the FC header. * * @return Returns pointer to IO object, or NULL if error. */ ocs_io_t * ocs_bls_send_acc_hdr(ocs_io_t *io, fc_header_t *hdr) { uint16_t ox_id = ocs_be16toh(hdr->ox_id); uint16_t rx_id = ocs_be16toh(hdr->rx_id); uint32_t d_id = fc_be24toh(hdr->d_id); return ocs_bls_send_acc(io, d_id, ox_id, rx_id); } /** * @ingroup els_api * @brief Send a BLS BA_ACC response. * *

Description

* Construct a BLS BA_ACC response, and send to the \c node. * * @param io Pointer to a SCSI IO object. * @param s_id S_ID to use for the response. If UINT32_MAX, then use our SLI port * (sport) S_ID. * @param ox_id Originator exchange ID. * @param rx_id Responder exchange ID. * * @return Returns pointer to IO object, or NULL if error. */ static ocs_io_t * ocs_bls_send_acc(ocs_io_t *io, uint32_t s_id, uint16_t ox_id, uint16_t rx_id) { ocs_node_t *node = io->node; int32_t rc; fc_ba_acc_payload_t *acc; ocs_t *ocs; ocs_assert(node, NULL); ocs_assert(node->ocs, NULL); ocs = node->ocs; if (node->rnode.sport->fc_id == s_id) { s_id = UINT32_MAX; } /* fill out generic fields */ io->ocs = ocs; io->node = node; io->cmd_tgt = TRUE; /* fill out BLS Response-specific fields */ io->io_type = OCS_IO_TYPE_BLS_RESP; io->display_name = "ba_acc"; io->hio_type = OCS_HW_BLS_ACC_SID; io->init_task_tag = ox_id; /* fill out iparam fields */ ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->iparam.bls_sid.s_id = s_id; io->iparam.bls_sid.ox_id = ox_id; io->iparam.bls_sid.rx_id = rx_id; acc = (void *)io->iparam.bls_sid.payload; ocs_memset(io->iparam.bls_sid.payload, 0, sizeof(io->iparam.bls_sid.payload)); acc->ox_id = io->iparam.bls_sid.ox_id; acc->rx_id = io->iparam.bls_sid.rx_id; acc->high_seq_cnt = UINT16_MAX; if ((rc = ocs_scsi_io_dispatch(io, ocs_bls_send_acc_cb))) { ocs_log_err(ocs, "ocs_scsi_io_dispatch() failed: %d\n", rc); ocs_scsi_io_free(io); io = NULL; } return io; } /** * @brief Handle the BLS accept completion. * *

Description

* Upon completion of sending a BA_ACC, this callback is invoked by the HW. * * @param hio Pointer to the HW IO object. * @param rnode Pointer to the HW remote node. * @param length Length of the response payload, in bytes. * @param status Completion status. * @param ext_status Extended completion status. * @param app Callback private argument. * * @return Returns 0 on success; or a negative error value on failure. */ static int32_t ocs_bls_send_acc_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_assert(io, -1); ocs_scsi_io_free(io); return 0; } /** * @brief ELS abort callback. * *

Description

* This callback is invoked by the HW when an ELS IO is aborted. * * @param hio Pointer to the HW IO object. * @param rnode Pointer to the HW remote node. * @param length Length of the response payload, in bytes. * @param status Completion status. * @param ext_status Extended completion status. * @param app Callback private argument. * * @return Returns 0 on success; or a negative error value on failure. */ static int32_t ocs_els_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 *els; ocs_io_t *abort_io = NULL; /* IO structure used to abort ELS */ ocs_t *ocs; ocs_assert(app, -1); abort_io = app; els = abort_io->io_to_abort; ocs_assert(els->node, -1); ocs_assert(els->node->ocs, -1); ocs = els->node->ocs; if (status != 0) { ocs_log_warn(ocs, "status x%x ext x%x\n", status, ext_status); } /* now free the abort IO */ ocs_io_free(ocs, abort_io); /* send completion event to indicate abort process is complete * Note: The ELS SM will already be receiving ELS_REQ_OK/FAIL/RJT/ABORTED */ ocs_els_post_event(els, OCS_EVT_ELS_ABORT_CMPL, NULL); /* done with ELS IO to abort */ ocs_ref_put(&els->ref); /* ocs_ref_get(): ocs_els_abort_io() */ return 0; } /** * @brief Abort an ELS IO. * *

Description

* The ELS IO is aborted by making a HW abort IO request, * optionally requesting that an ABTS is sent. * * \b Note: This function allocates a HW IO, and associates the HW IO * with the ELS IO that it is aborting. It does not associate * the HW IO with the node directly, like for ELS requests. The * abort completion is propagated up to the node once the * original WQE and the abort WQE are complete (the original WQE * completion is not propagated up to node). * * @param els Pointer to the ELS IO. * @param send_abts Boolean to indicate if hardware will automatically generate an ABTS. * * @return Returns pointer to Abort IO object, or NULL if error. */ static ocs_io_t * ocs_els_abort_io(ocs_io_t *els, int send_abts) { ocs_t *ocs; ocs_xport_t *xport; int32_t rc; ocs_io_t *abort_io = NULL; ocs_assert(els, NULL); ocs_assert(els->node, NULL); ocs_assert(els->node->ocs, NULL); ocs = els->node->ocs; ocs_assert(ocs->xport, NULL); xport = ocs->xport; /* take a reference on IO being aborted */ if ((ocs_ref_get_unless_zero(&els->ref) == 0)) { /* command no longer active */ ocs_log_debug(ocs, "els no longer active\n"); return NULL; } /* allocate IO structure to send abort */ abort_io = ocs_io_alloc(ocs); if (abort_io == NULL) { ocs_atomic_add_return(&xport->io_alloc_failed_count, 1); } else { ocs_assert(abort_io->hio == NULL, NULL); /* set generic fields */ abort_io->ocs = ocs; abort_io->node = els->node; abort_io->cmd_ini = TRUE; /* set type and ABORT-specific fields */ abort_io->io_type = OCS_IO_TYPE_ABORT; abort_io->display_name = "abort_els"; abort_io->io_to_abort = els; abort_io->send_abts = send_abts; /* now dispatch IO */ if ((rc = ocs_scsi_io_dispatch_abort(abort_io, ocs_els_abort_cb))) { ocs_log_err(ocs, "ocs_scsi_io_dispatch failed: %d\n", rc); ocs_io_free(ocs, abort_io); abort_io = NULL; } } /* if something failed, put reference on ELS to abort */ if (abort_io == NULL) { ocs_ref_put(&els->ref); /* ocs_ref_get(): same function */ } return abort_io; } /* * ELS IO State Machine */ #define std_els_state_decl(...) \ ocs_io_t *els = NULL; \ ocs_node_t *node = NULL; \ ocs_t *ocs = NULL; \ ocs_assert(ctx != NULL, NULL); \ els = ctx->app; \ ocs_assert(els != NULL, NULL); \ node = els->node; \ ocs_assert(node != NULL, NULL); \ ocs = node->ocs; \ ocs_assert(ocs != NULL, NULL); #define els_sm_trace(...) \ do { \ if (OCS_LOG_ENABLE_ELS_TRACE(ocs)) \ ocs_log_info(ocs, "[%s] %-8s %-20s %-20s\n", node->display_name, els->display_name, \ __func__, ocs_sm_event_name(evt)); \ } while (0) /** * @brief Cleanup an ELS IO * *

Description

* Cleans up an ELS IO by posting the requested event to the owning node object; * invoking the callback, if one is provided; and then freeing the * ELS IO object. * * @param els Pointer to the ELS IO. * @param node_evt Node SM event to post. * @param arg Node SM event argument. * * @return None. */ void ocs_els_io_cleanup(ocs_io_t *els, ocs_sm_event_t node_evt, void *arg) { ocs_assert(els); /* don't want further events that could come; e.g. abort requests * from the node state machine; thus, disable state machine */ ocs_sm_disable(&els->els_sm); ocs_node_post_event(els->node, node_evt, arg); /* If this IO has a callback, invoke it */ if (els->els_callback) { (*els->els_callback)(els->node, arg, els->els_callback_arg); } els->els_req_free = 1; } /** * @brief Common event handler for the ELS IO state machine. * *

Description

* Provide handler for events for which default actions are desired. * * @param funcname Name of the calling function (for logging). * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_common(const char *funcname, ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); switch(evt) { case OCS_EVT_ENTER: case OCS_EVT_REENTER: case OCS_EVT_EXIT: break; /* If ELS_REQ_FAIL is not handled in state, then we'll terminate this ELS and * pass the event to the node */ case OCS_EVT_SRRS_ELS_REQ_FAIL: ocs_log_warn(els->node->ocs, "[%s] %-20s %-20s not handled - terminating ELS\n", node->display_name, funcname, ocs_sm_event_name(evt)); ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, arg); break; default: ocs_log_warn(els->node->ocs, "[%s] %-20s %-20s not handled\n", node->display_name, funcname, ocs_sm_event_name(evt)); break; } return NULL; } /** * @brief Initial ELS IO state * *

Description

* This is the initial ELS IO state. Upon entry, the requested ELS/CT is submitted to * the hardware. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_init(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { int32_t rc = 0; std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_ENTER: { rc = ocs_els_send(els, els->els_req.size, els->els_timeout_sec, ocs_els_req_cb); if (rc) { ocs_node_cb_t cbdata; cbdata.status = cbdata.ext_status = (~0); cbdata.els = els; ocs_log_err(ocs, "ocs_els_send failed: %d\n", rc); ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &cbdata); } else { ocs_io_transition(els, __ocs_els_wait_resp, NULL); } break; } default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for the ELS request to complete. * *

Description

* This is the ELS IO state that waits for the submitted ELS event to complete. * If an error completion event is received, the requested ELS is aborted. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_wait_resp(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { ocs_io_t *io; std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_SRRS_ELS_REQ_OK: { ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_OK, arg); break; } case OCS_EVT_SRRS_ELS_REQ_FAIL: { ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, arg); break; } case OCS_EVT_ELS_REQ_TIMEOUT: { els_io_printf(els, "Timed out, retry (%d tries remaining)\n", els->els_retries_remaining-1); ocs_io_transition(els, __ocs_els_retry, NULL); break; } case OCS_EVT_SRRS_ELS_REQ_RJT: { ocs_node_cb_t *cbdata = arg; uint32_t reason_code = (cbdata->ext_status >> 16) & 0xff; /* delay and retry if reason code is Logical Busy */ switch (reason_code) { case FC_REASON_LOGICAL_BUSY: els->node->els_req_cnt--; els_io_printf(els, "LS_RJT Logical Busy response, delay and retry\n"); ocs_io_transition(els, __ocs_els_delay_retry, NULL); break; default: ocs_els_io_cleanup(els, evt, arg); break; } break; } case OCS_EVT_ABORT_ELS: { /* request to abort this ELS without an ABTS */ els_io_printf(els, "ELS abort requested\n"); els->els_retries_remaining = 0; /* Set retries to zero, we are done */ io = ocs_els_abort_io(els, FALSE); if (io == NULL) { ocs_log_err(ocs, "ocs_els_send failed\n"); ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, arg); } else { ocs_io_transition(els, __ocs_els_aborting, NULL); } break; } default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for the ELS IO abort request to complete, and retry the ELS. * *

Description

* This state is entered when waiting for an abort of an ELS * request to complete so the request can be retried. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_retry(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { int32_t rc = 0; std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_ENTER: { /* handle event for ABORT_XRI WQE * once abort is complete, retry if retries left; * don't need to wait for OCS_EVT_SRRS_ELS_REQ_* event because we got * by receiving OCS_EVT_ELS_REQ_TIMEOUT */ ocs_node_cb_t node_cbdata; node_cbdata.status = node_cbdata.ext_status = (~0); node_cbdata.els = els; if (els->els_retries_remaining && --els->els_retries_remaining) { /* Use a different XRI for the retry (would like a new oxid), * so free the HW IO (dispatch will allocate a new one). It's an * optimization to only free the HW IO here and not the ocs_io_t; * Freeing the ocs_io_t object would require copying all the necessary * info from the old ocs_io_t object to the * new one; and allocating * a new ocs_io_t could fail. */ ocs_assert(els->hio, NULL); ocs_hw_io_free(&ocs->hw, els->hio); els->hio = NULL; /* result isn't propagated up to node sm, need to decrement req cnt */ ocs_assert(els->node->els_req_cnt, NULL); els->node->els_req_cnt--; rc = ocs_els_send(els, els->els_req.size, els->els_timeout_sec, ocs_els_req_cb); if (rc) { ocs_log_err(ocs, "ocs_els_send failed: %d\n", rc); ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &node_cbdata); } ocs_io_transition(els, __ocs_els_wait_resp, NULL); } else { els_io_printf(els, "Retries exhausted\n"); ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, &node_cbdata); } break; } default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for a retry timer to expire having received an abort request * *

Description

* This state is entered when waiting for a timer event, after having received * an abort request, to avoid a race condition with the timer handler * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_aborted_delay_retry(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_ENTER: /* mod/resched the timer for a short duration */ ocs_mod_timer(&els->delay_timer, 1); break; case OCS_EVT_TIMER_EXPIRED: /* Cancel the timer, skip post node event, and free the io */ node->els_req_cnt++; ocs_els_io_cleanup(els, OCS_EVT_SRRS_ELS_REQ_FAIL, arg); break; default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for a retry timer to expire * *

Description

* This state is entered when waiting for a timer event, so that * the ELS request can be retried. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_delay_retry(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_ENTER: ocs_setup_timer(ocs, &els->delay_timer, ocs_els_delay_timer_cb, els, 5000); break; case OCS_EVT_TIMER_EXPIRED: /* Retry delay timer expired, retry the ELS request, Free the HW IO so * that a new oxid is used. */ if (els->hio != NULL) { ocs_hw_io_free(&ocs->hw, els->hio); els->hio = NULL; } ocs_io_transition(els, __ocs_els_init, NULL); break; case OCS_EVT_ABORT_ELS: ocs_io_transition(els, __ocs_els_aborted_delay_retry, NULL); break; default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for the ELS IO abort request to complete. * *

Description

* This state is entered after we abort an ELS WQE and are * waiting for either the original ELS WQE request or the abort * to complete. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_aborting(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_SRRS_ELS_REQ_OK: case OCS_EVT_SRRS_ELS_REQ_FAIL: case OCS_EVT_SRRS_ELS_REQ_RJT: case OCS_EVT_ELS_REQ_TIMEOUT: case OCS_EVT_ELS_REQ_ABORTED: { /* completion for ELS received first, transition to wait for abort cmpl */ els_io_printf(els, "request cmpl evt=%s\n", ocs_sm_event_name(evt)); ocs_io_transition(els, __ocs_els_aborting_wait_abort_cmpl, NULL); break; } case OCS_EVT_ELS_ABORT_CMPL: { /* completion for abort was received first, transition to wait for req cmpl */ els_io_printf(els, "abort cmpl evt=%s\n", ocs_sm_event_name(evt)); ocs_io_transition(els, __ocs_els_aborting_wait_req_cmpl, NULL); break; } case OCS_EVT_ABORT_ELS: /* nothing we can do but wait */ break; default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief cleanup ELS after abort * * @param els ELS IO to cleanup * * @return Returns None. */ static void ocs_els_abort_cleanup(ocs_io_t *els) { /* handle event for ABORT_WQE * whatever state ELS happened to be in, propagate aborted event up * to node state machine in lieu of OCS_EVT_SRRS_ELS_* event */ ocs_node_cb_t cbdata; cbdata.status = cbdata.ext_status = 0; cbdata.els = els; els_io_printf(els, "Request aborted\n"); ocs_els_io_cleanup(els, OCS_EVT_ELS_REQ_ABORTED, &cbdata); } /** * @brief Wait for the ELS IO abort request to complete. * *

Description

* This state is entered after we abort an ELS WQE, we received * the abort completion first and are waiting for the original * ELS WQE request to complete. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_aborting_wait_req_cmpl(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_SRRS_ELS_REQ_OK: case OCS_EVT_SRRS_ELS_REQ_FAIL: case OCS_EVT_SRRS_ELS_REQ_RJT: case OCS_EVT_ELS_REQ_TIMEOUT: case OCS_EVT_ELS_REQ_ABORTED: { /* completion for ELS that was aborted */ ocs_els_abort_cleanup(els); break; } case OCS_EVT_ABORT_ELS: /* nothing we can do but wait */ break; default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Wait for the ELS IO abort request to complete. * *

Description

* This state is entered after we abort an ELS WQE, we received * the original ELS WQE request completion first and are waiting * for the abort to complete. * * @param ctx Remote node SM context. * @param evt Event to process. * @param arg Per event optional argument. * * @return Returns NULL. */ void * __ocs_els_aborting_wait_abort_cmpl(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *arg) { std_els_state_decl(); els_sm_trace(); switch(evt) { case OCS_EVT_ELS_ABORT_CMPL: { ocs_els_abort_cleanup(els); break; } case OCS_EVT_ABORT_ELS: /* nothing we can do but wait */ break; default: __ocs_els_common(__func__, ctx, evt, arg); break; } return NULL; } /** * @brief Generate ELS context ddump data. * *

Description

* Generate the ddump data for an ELS context. * * @param textbuf Pointer to the text buffer. * @param els Pointer to the ELS context. * * @return None. */ void ocs_ddump_els(ocs_textbuf_t *textbuf, ocs_io_t *els) { ocs_ddump_section(textbuf, "els", -1); ocs_ddump_value(textbuf, "req_free", "%d", els->els_req_free); ocs_ddump_value(textbuf, "evtdepth", "%d", els->els_evtdepth); ocs_ddump_value(textbuf, "pend", "%d", els->els_pend); ocs_ddump_value(textbuf, "active", "%d", els->els_active); ocs_ddump_io(textbuf, els); ocs_ddump_endsection(textbuf, "els", -1); } /** * @brief return TRUE if given ELS list is empty (while taking proper locks) * * Test if given ELS list is empty while holding the node->active_ios_lock. * * @param node pointer to node object * @param list pointer to list * * @return TRUE if els_io_list is empty */ int32_t ocs_els_io_list_empty(ocs_node_t *node, ocs_list_t *list) { int empty; ocs_lock(&node->active_ios_lock); empty = ocs_list_empty(list); ocs_unlock(&node->active_ios_lock); return empty; } /** * @brief Handle CT send response completion * * Called when CT response completes, free IO * * @param hio Pointer to the HW IO context that completed. * @param rnode Pointer to the remote node. * @param length Length of the returned payload data. * @param status Status of the completion. * @param ext_status Extended status of the completion. * @param arg Application-specific argument (generally a pointer to the ELS IO context). * * @return returns 0 */ static int32_t ocs_ct_acc_cb(ocs_hw_io_t *hio, ocs_remote_node_t *rnode, uint32_t length, int32_t status, uint32_t ext_status, void *arg) { ocs_io_t *io = arg; ocs_els_io_free(io); return 0; } /** * @brief Send CT response * * Sends a CT response frame with payload * * @param io Pointer to the IO context. * @param ox_id Originator exchange ID * @param ct_hdr Pointer to the CT IU * @param cmd_rsp_code CT response code * @param reason_code Reason code * @param reason_code_explanation Reason code explanation * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_send_ct_rsp(ocs_io_t *io, uint32_t ox_id, fcct_iu_header_t *ct_hdr, uint32_t cmd_rsp_code, uint32_t reason_code, uint32_t reason_code_explanation) { fcct_iu_header_t *rsp = io->els_rsp.virt; io->io_type = OCS_IO_TYPE_CT_RESP; *rsp = *ct_hdr; fcct_build_req_header(rsp, cmd_rsp_code, 0); rsp->reason_code = reason_code; rsp->reason_code_explanation = reason_code_explanation; io->display_name = "ct response"; io->init_task_tag = ox_id; io->wire_len += sizeof(*rsp); ocs_memset(&io->iparam, 0, sizeof(io->iparam)); io->io_type = OCS_IO_TYPE_CT_RESP; io->hio_type = OCS_HW_FC_CT_RSP; io->iparam.fc_ct_rsp.ox_id = ocs_htobe16(ox_id); io->iparam.fc_ct_rsp.r_ctl = 3; io->iparam.fc_ct_rsp.type = FC_TYPE_GS; io->iparam.fc_ct_rsp.df_ctl = 0; io->iparam.fc_ct_rsp.timeout = 5; if (ocs_scsi_io_dispatch(io, ocs_ct_acc_cb) < 0) { ocs_els_io_free(io); return -1; } return 0; } /** * @brief Handle delay retry timeout * * Callback is invoked when the delay retry timer expires. * * @param arg pointer to the ELS IO object * * @return none */ static void ocs_els_delay_timer_cb(void *arg) { ocs_io_t *els = arg; ocs_node_t *node = els->node; /* * There is a potential deadlock here since is Linux executes timers * in a soft IRQ context. The lock may be aready locked by the interrupt * thread. Handle this case by attempting to take the node lock and reset the * timer if we fail to acquire the lock. * * Note: This code relies on the fact that the node lock is recursive. */ if (ocs_node_lock_try(node)) { ocs_els_post_event(els, OCS_EVT_TIMER_EXPIRED, NULL); ocs_node_unlock(node); } else { ocs_setup_timer(els->ocs, &els->delay_timer, ocs_els_delay_timer_cb, els, 1); } }