/*- * 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 * Defines and implements the Hardware Abstraction Layer (HW). * All interaction with the hardware is performed through the HW, which abstracts * the details of the underlying SLI-4 implementation. */ /** * @defgroup devInitShutdown Device Initialization and Shutdown * @defgroup domain Domain Functions * @defgroup port Port Functions * @defgroup node Remote Node Functions * @defgroup io IO Functions * @defgroup interrupt Interrupt handling * @defgroup os OS Required Functions */ #include "ocs.h" #include "ocs_os.h" #include "ocs_hw.h" #include "ocs_hw_queues.h" #define OCS_HW_MQ_DEPTH 128 #define OCS_HW_READ_FCF_SIZE 4096 #define OCS_HW_DEFAULT_AUTO_XFER_RDY_IOS 256 #define OCS_HW_WQ_TIMER_PERIOD_MS 500 /* values used for setting the auto xfer rdy parameters */ #define OCS_HW_AUTO_XFER_RDY_BLK_SIZE_DEFAULT 0 /* 512 bytes */ #define OCS_HW_AUTO_XFER_RDY_REF_TAG_IS_LBA_DEFAULT TRUE #define OCS_HW_AUTO_XFER_RDY_APP_TAG_VALID_DEFAULT FALSE #define OCS_HW_AUTO_XFER_RDY_APP_TAG_VALUE_DEFAULT 0 #define OCS_HW_REQUE_XRI_REGTAG 65534 /* max command and response buffer lengths -- arbitrary at the moment */ #define OCS_HW_DMTF_CLP_CMD_MAX 256 #define OCS_HW_DMTF_CLP_RSP_MAX 256 /* HW global data */ ocs_hw_global_t hw_global; static void ocs_hw_queue_hash_add(ocs_queue_hash_t *, uint16_t, uint16_t); static void ocs_hw_adjust_wqs(ocs_hw_t *hw); static uint32_t ocs_hw_get_num_chutes(ocs_hw_t *hw); static int32_t ocs_hw_cb_link(void *, void *); static int32_t ocs_hw_cb_fip(void *, void *); static int32_t ocs_hw_command_process(ocs_hw_t *, int32_t, uint8_t *, size_t); static int32_t ocs_hw_mq_process(ocs_hw_t *, int32_t, sli4_queue_t *); static int32_t ocs_hw_cb_read_fcf(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_node_attach(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_node_free(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_node_free_all(ocs_hw_t *, int32_t, uint8_t *, void *); static ocs_hw_rtn_e ocs_hw_setup_io(ocs_hw_t *); static ocs_hw_rtn_e ocs_hw_init_io(ocs_hw_t *); static int32_t ocs_hw_flush(ocs_hw_t *); static int32_t ocs_hw_command_cancel(ocs_hw_t *); static int32_t ocs_hw_io_cancel(ocs_hw_t *); static void ocs_hw_io_quarantine(ocs_hw_t *hw, hw_wq_t *wq, ocs_hw_io_t *io); static void ocs_hw_io_restore_sgl(ocs_hw_t *, ocs_hw_io_t *); static int32_t ocs_hw_io_ini_sge(ocs_hw_t *, ocs_hw_io_t *, ocs_dma_t *, uint32_t, ocs_dma_t *); static ocs_hw_rtn_e ocs_hw_firmware_write_lancer(ocs_hw_t *hw, ocs_dma_t *dma, uint32_t size, uint32_t offset, int last, ocs_hw_fw_cb_t cb, void *arg); static int32_t ocs_hw_cb_fw_write(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_sfp(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_temp(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_link_stat(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t ocs_hw_cb_host_stat(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg); static void ocs_hw_dmtf_clp_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg); static int32_t ocs_hw_clp_resp_get_value(ocs_hw_t *hw, const char *keyword, char *value, uint32_t value_len, const char *resp, uint32_t resp_len); typedef void (*ocs_hw_dmtf_clp_cb_t)(ocs_hw_t *hw, int32_t status, uint32_t result_len, void *arg); static ocs_hw_rtn_e ocs_hw_exec_dmtf_clp_cmd(ocs_hw_t *hw, ocs_dma_t *dma_cmd, ocs_dma_t *dma_resp, uint32_t opts, ocs_hw_dmtf_clp_cb_t cb, void *arg); static void ocs_hw_linkcfg_dmtf_clp_cb(ocs_hw_t *hw, int32_t status, uint32_t result_len, void *arg); static int32_t __ocs_read_topology_cb(ocs_hw_t *, int32_t, uint8_t *, void *); static ocs_hw_rtn_e ocs_hw_get_linkcfg(ocs_hw_t *, uint32_t, ocs_hw_port_control_cb_t, void *); static ocs_hw_rtn_e ocs_hw_get_linkcfg_lancer(ocs_hw_t *, uint32_t, ocs_hw_port_control_cb_t, void *); static ocs_hw_rtn_e ocs_hw_get_linkcfg_skyhawk(ocs_hw_t *, uint32_t, ocs_hw_port_control_cb_t, void *); static ocs_hw_rtn_e ocs_hw_set_linkcfg(ocs_hw_t *, ocs_hw_linkcfg_e, uint32_t, ocs_hw_port_control_cb_t, void *); static ocs_hw_rtn_e ocs_hw_set_linkcfg_lancer(ocs_hw_t *, ocs_hw_linkcfg_e, uint32_t, ocs_hw_port_control_cb_t, void *); static ocs_hw_rtn_e ocs_hw_set_linkcfg_skyhawk(ocs_hw_t *, ocs_hw_linkcfg_e, uint32_t, ocs_hw_port_control_cb_t, void *); static void ocs_hw_init_linkcfg_cb(int32_t status, uintptr_t value, void *arg); static ocs_hw_rtn_e ocs_hw_set_eth_license(ocs_hw_t *hw, uint32_t license); static ocs_hw_rtn_e ocs_hw_set_dif_seed(ocs_hw_t *hw); static ocs_hw_rtn_e ocs_hw_set_dif_mode(ocs_hw_t *hw); static void ocs_hw_io_free_internal(void *arg); static void ocs_hw_io_free_port_owned(void *arg); static ocs_hw_rtn_e ocs_hw_config_auto_xfer_rdy_t10pi(ocs_hw_t *hw, uint8_t *buf); static ocs_hw_rtn_e ocs_hw_config_set_fdt_xfer_hint(ocs_hw_t *hw, uint32_t fdt_xfer_hint); static void ocs_hw_wq_process_abort(void *arg, uint8_t *cqe, int32_t status); static int32_t ocs_hw_config_mrq(ocs_hw_t *hw, uint8_t, uint16_t, uint16_t); static ocs_hw_rtn_e ocs_hw_config_watchdog_timer(ocs_hw_t *hw); static ocs_hw_rtn_e ocs_hw_config_sli_port_health_check(ocs_hw_t *hw, uint8_t query, uint8_t enable); /* HW domain database operations */ static int32_t ocs_hw_domain_add(ocs_hw_t *, ocs_domain_t *); static int32_t ocs_hw_domain_del(ocs_hw_t *, ocs_domain_t *); /* Port state machine */ static void *__ocs_hw_port_alloc_init(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_port_alloc_read_sparm64(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_port_alloc_init_vpi(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_port_done(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_port_free_unreg_vpi(ocs_sm_ctx_t *, ocs_sm_event_t, void *); /* Domain state machine */ static void *__ocs_hw_domain_init(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_domain_alloc_reg_fcfi(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void * __ocs_hw_domain_alloc_init_vfi(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_domain_free_unreg_vfi(ocs_sm_ctx_t *, ocs_sm_event_t, void *); static void *__ocs_hw_domain_free_unreg_fcfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data); static int32_t __ocs_hw_domain_cb(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t __ocs_hw_port_cb(ocs_hw_t *, int32_t, uint8_t *, void *); static int32_t __ocs_hw_port_realloc_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg); /* BZ 161832 */ static void ocs_hw_check_sec_hio_list(ocs_hw_t *hw); /* WQE timeouts */ static void target_wqe_timer_cb(void *arg); static void shutdown_target_wqe_timer(ocs_hw_t *hw); static inline void ocs_hw_add_io_timed_wqe(ocs_hw_t *hw, ocs_hw_io_t *io) { if (hw->config.emulate_tgt_wqe_timeout && io->tgt_wqe_timeout) { /* * Active WQE list currently only used for * target WQE timeouts. */ ocs_lock(&hw->io_lock); ocs_list_add_tail(&hw->io_timed_wqe, io); io->submit_ticks = ocs_get_os_ticks(); ocs_unlock(&hw->io_lock); } } static inline void ocs_hw_remove_io_timed_wqe(ocs_hw_t *hw, ocs_hw_io_t *io) { if (hw->config.emulate_tgt_wqe_timeout) { /* * If target wqe timeouts are enabled, * remove from active wqe list. */ ocs_lock(&hw->io_lock); if (ocs_list_on_list(&io->wqe_link)) { ocs_list_remove(&hw->io_timed_wqe, io); } ocs_unlock(&hw->io_lock); } } static uint8_t ocs_hw_iotype_is_originator(uint16_t io_type) { switch (io_type) { case OCS_HW_IO_INITIATOR_READ: case OCS_HW_IO_INITIATOR_WRITE: case OCS_HW_IO_INITIATOR_NODATA: case OCS_HW_FC_CT: case OCS_HW_ELS_REQ: return 1; default: return 0; } } static uint8_t ocs_hw_wcqe_abort_needed(uint16_t status, uint8_t ext, uint8_t xb) { /* if exchange not active, nothing to abort */ if (!xb) { return FALSE; } if (status == SLI4_FC_WCQE_STATUS_LOCAL_REJECT) { switch (ext) { /* exceptions where abort is not needed */ case SLI4_FC_LOCAL_REJECT_INVALID_RPI: /* lancer returns this after unreg_rpi */ case SLI4_FC_LOCAL_REJECT_ABORT_REQUESTED: /* abort already in progress */ return FALSE; default: break; } } return TRUE; } /** * @brief Determine the number of chutes on the device. * * @par Description * Some devices require queue resources allocated per protocol processor * (chute). This function returns the number of chutes on this device. * * @param hw Hardware context allocated by the caller. * * @return Returns the number of chutes on the device for protocol. */ static uint32_t ocs_hw_get_num_chutes(ocs_hw_t *hw) { uint32_t num_chutes = 1; if (sli_get_is_dual_ulp_capable(&hw->sli) && sli_get_is_ulp_enabled(&hw->sli, 0) && sli_get_is_ulp_enabled(&hw->sli, 1)) { num_chutes = 2; } return num_chutes; } static ocs_hw_rtn_e ocs_hw_link_event_init(ocs_hw_t *hw) { ocs_hw_assert(hw); hw->link.status = SLI_LINK_STATUS_MAX; hw->link.topology = SLI_LINK_TOPO_NONE; hw->link.medium = SLI_LINK_MEDIUM_MAX; hw->link.speed = 0; hw->link.loop_map = NULL; hw->link.fc_id = UINT32_MAX; return OCS_HW_RTN_SUCCESS; } /** * @ingroup devInitShutdown * @brief If this is physical port 0, then read the max dump size. * * @par Description * Queries the FW for the maximum dump size * * @param hw Hardware context allocated by the caller. * * @return Returns 0 on success, or a non-zero value on failure. */ static ocs_hw_rtn_e ocs_hw_read_max_dump_size(ocs_hw_t *hw) { uint8_t buf[SLI4_BMBX_SIZE]; uint8_t bus, dev, func; int rc; /* lancer only */ if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_debug(hw->os, "Function only supported for I/F type 2\n"); return OCS_HW_RTN_ERROR; } /* * Make sure the FW is new enough to support this command. If the FW * is too old, the FW will UE. */ if (hw->workaround.disable_dump_loc) { ocs_log_test(hw->os, "FW version is too old for this feature\n"); return OCS_HW_RTN_ERROR; } /* attempt to detemine the dump size for function 0 only. */ ocs_get_bus_dev_func(hw->os, &bus, &dev, &func); if (func == 0) { if (sli_cmd_common_set_dump_location(&hw->sli, buf, SLI4_BMBX_SIZE, 1, 0, NULL, 0)) { sli4_res_common_set_dump_location_t *rsp = (sli4_res_common_set_dump_location_t *) (buf + offsetof(sli4_cmd_sli_config_t, payload.embed)); rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "set dump location command failed\n"); return rc; } else { hw->dump_size = rsp->buffer_length; ocs_log_debug(hw->os, "Dump size %x\n", rsp->buffer_length); } } } return OCS_HW_RTN_SUCCESS; } /** * @ingroup devInitShutdown * @brief Set up the Hardware Abstraction Layer module. * * @par Description * Calls set up to configure the hardware. * * @param hw Hardware context allocated by the caller. * @param os Device abstraction. * @param port_type Protocol type of port, such as FC and NIC. * * @todo Why is port_type a parameter? * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_setup(ocs_hw_t *hw, ocs_os_handle_t os, sli4_port_type_e port_type) { uint32_t i; char prop_buf[32]; if (hw == NULL) { ocs_log_err(os, "bad parameter(s) hw=%p\n", hw); return OCS_HW_RTN_ERROR; } if (hw->hw_setup_called) { /* Setup run-time workarounds. * Call for each setup, to allow for hw_war_version */ ocs_hw_workaround_setup(hw); return OCS_HW_RTN_SUCCESS; } /* * ocs_hw_init() relies on NULL pointers indicating that a structure * needs allocation. If a structure is non-NULL, ocs_hw_init() won't * free/realloc that memory */ ocs_memset(hw, 0, sizeof(ocs_hw_t)); hw->hw_setup_called = TRUE; hw->os = os; ocs_lock_init(hw->os, &hw->cmd_lock, "HW_cmd_lock[%d]", ocs_instance(hw->os)); ocs_list_init(&hw->cmd_head, ocs_command_ctx_t, link); ocs_list_init(&hw->cmd_pending, ocs_command_ctx_t, link); hw->cmd_head_count = 0; ocs_lock_init(hw->os, &hw->io_lock, "HW_io_lock[%d]", ocs_instance(hw->os)); ocs_lock_init(hw->os, &hw->io_abort_lock, "HW_io_abort_lock[%d]", ocs_instance(hw->os)); ocs_atomic_init(&hw->io_alloc_failed_count, 0); hw->config.speed = FC_LINK_SPEED_AUTO_16_8_4; hw->config.dif_seed = 0; hw->config.auto_xfer_rdy_blk_size_chip = OCS_HW_AUTO_XFER_RDY_BLK_SIZE_DEFAULT; hw->config.auto_xfer_rdy_ref_tag_is_lba = OCS_HW_AUTO_XFER_RDY_REF_TAG_IS_LBA_DEFAULT; hw->config.auto_xfer_rdy_app_tag_valid = OCS_HW_AUTO_XFER_RDY_APP_TAG_VALID_DEFAULT; hw->config.auto_xfer_rdy_app_tag_value = OCS_HW_AUTO_XFER_RDY_APP_TAG_VALUE_DEFAULT; if (sli_setup(&hw->sli, hw->os, port_type)) { ocs_log_err(hw->os, "SLI setup failed\n"); return OCS_HW_RTN_ERROR; } ocs_memset(hw->domains, 0, sizeof(hw->domains)); ocs_memset(hw->fcf_index_fcfi, 0, sizeof(hw->fcf_index_fcfi)); ocs_hw_link_event_init(hw); sli_callback(&hw->sli, SLI4_CB_LINK, ocs_hw_cb_link, hw); sli_callback(&hw->sli, SLI4_CB_FIP, ocs_hw_cb_fip, hw); /* * Set all the queue sizes to the maximum allowed. These values may * be changes later by the adjust and workaround functions. */ for (i = 0; i < ARRAY_SIZE(hw->num_qentries); i++) { hw->num_qentries[i] = sli_get_max_qentries(&hw->sli, i); } /* * The RQ assignment for RQ pair mode. */ hw->config.rq_default_buffer_size = OCS_HW_RQ_SIZE_PAYLOAD; hw->config.n_io = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_XRI); if (ocs_get_property("auto_xfer_rdy_xri_cnt", prop_buf, sizeof(prop_buf)) == 0) { hw->config.auto_xfer_rdy_xri_cnt = ocs_strtoul(prop_buf, 0, 0); } /* by default, enable initiator-only auto-ABTS emulation */ hw->config.i_only_aab = TRUE; /* Setup run-time workarounds */ ocs_hw_workaround_setup(hw); /* HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB */ if (hw->workaround.override_fcfi) { hw->first_domain_idx = -1; } /* Must be done after the workaround setup */ if (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli)) { (void)ocs_hw_read_max_dump_size(hw); } /* calculate the number of WQs required. */ ocs_hw_adjust_wqs(hw); /* Set the default dif mode */ if (! sli_is_dif_inline_capable(&hw->sli)) { ocs_log_test(hw->os, "not inline capable, setting mode to separate\n"); hw->config.dif_mode = OCS_HW_DIF_MODE_SEPARATE; } /* Workaround: BZ 161832 */ if (hw->workaround.use_dif_sec_xri) { ocs_list_init(&hw->sec_hio_wait_list, ocs_hw_io_t, link); } /* * Figure out the starting and max ULP to spread the WQs across the * ULPs. */ if (sli_get_is_dual_ulp_capable(&hw->sli)) { if (sli_get_is_ulp_enabled(&hw->sli, 0) && sli_get_is_ulp_enabled(&hw->sli, 1)) { hw->ulp_start = 0; hw->ulp_max = 1; } else if (sli_get_is_ulp_enabled(&hw->sli, 0)) { hw->ulp_start = 0; hw->ulp_max = 0; } else { hw->ulp_start = 1; hw->ulp_max = 1; } } else { if (sli_get_is_ulp_enabled(&hw->sli, 0)) { hw->ulp_start = 0; hw->ulp_max = 0; } else { hw->ulp_start = 1; hw->ulp_max = 1; } } ocs_log_debug(hw->os, "ulp_start %d, ulp_max %d\n", hw->ulp_start, hw->ulp_max); hw->config.queue_topology = hw_global.queue_topology_string; hw->qtop = ocs_hw_qtop_parse(hw, hw->config.queue_topology); hw->config.n_eq = hw->qtop->entry_counts[QTOP_EQ]; hw->config.n_cq = hw->qtop->entry_counts[QTOP_CQ]; hw->config.n_rq = hw->qtop->entry_counts[QTOP_RQ]; hw->config.n_wq = hw->qtop->entry_counts[QTOP_WQ]; hw->config.n_mq = hw->qtop->entry_counts[QTOP_MQ]; /* Verify qtop configuration against driver supported configuration */ if (hw->config.n_rq > OCE_HW_MAX_NUM_MRQ_PAIRS) { ocs_log_crit(hw->os, "Max supported MRQ pairs = %d\n", OCE_HW_MAX_NUM_MRQ_PAIRS); return OCS_HW_RTN_ERROR; } if (hw->config.n_eq > OCS_HW_MAX_NUM_EQ) { ocs_log_crit(hw->os, "Max supported EQs = %d\n", OCS_HW_MAX_NUM_EQ); return OCS_HW_RTN_ERROR; } if (hw->config.n_cq > OCS_HW_MAX_NUM_CQ) { ocs_log_crit(hw->os, "Max supported CQs = %d\n", OCS_HW_MAX_NUM_CQ); return OCS_HW_RTN_ERROR; } if (hw->config.n_wq > OCS_HW_MAX_NUM_WQ) { ocs_log_crit(hw->os, "Max supported WQs = %d\n", OCS_HW_MAX_NUM_WQ); return OCS_HW_RTN_ERROR; } if (hw->config.n_mq > OCS_HW_MAX_NUM_MQ) { ocs_log_crit(hw->os, "Max supported MQs = %d\n", OCS_HW_MAX_NUM_MQ); return OCS_HW_RTN_ERROR; } return OCS_HW_RTN_SUCCESS; } /** * @ingroup devInitShutdown * @brief Allocate memory structures to prepare for the device operation. * * @par Description * Allocates memory structures needed by the device and prepares the device * for operation. * @n @n @b Note: This function may be called more than once (for example, at * initialization and then after a reset), but the size of the internal resources * may not be changed without tearing down the HW (ocs_hw_teardown()). * * @param hw Hardware context allocated by the caller. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_init(ocs_hw_t *hw) { ocs_hw_rtn_e rc; uint32_t i = 0; uint8_t buf[SLI4_BMBX_SIZE]; uint32_t max_rpi; int rem_count; int written_size = 0; uint32_t count; char prop_buf[32]; uint32_t ramdisc_blocksize = 512; uint32_t q_count = 0; /* * Make sure the command lists are empty. If this is start-of-day, * they'll be empty since they were just initialized in ocs_hw_setup. * If we've just gone through a reset, the command and command pending * lists should have been cleaned up as part of the reset (ocs_hw_reset()). */ ocs_lock(&hw->cmd_lock); if (!ocs_list_empty(&hw->cmd_head)) { ocs_log_test(hw->os, "command found on cmd list\n"); ocs_unlock(&hw->cmd_lock); return OCS_HW_RTN_ERROR; } if (!ocs_list_empty(&hw->cmd_pending)) { ocs_log_test(hw->os, "command found on pending list\n"); ocs_unlock(&hw->cmd_lock); return OCS_HW_RTN_ERROR; } ocs_unlock(&hw->cmd_lock); /* Free RQ buffers if prevously allocated */ ocs_hw_rx_free(hw); /* * The IO queues must be initialized here for the reset case. The * ocs_hw_init_io() function will re-add the IOs to the free list. * The cmd_head list should be OK since we free all entries in * ocs_hw_command_cancel() that is called in the ocs_hw_reset(). */ /* If we are in this function due to a reset, there may be stale items * on lists that need to be removed. Clean them up. */ rem_count=0; if (ocs_list_valid(&hw->io_wait_free)) { while ((!ocs_list_empty(&hw->io_wait_free))) { rem_count++; ocs_list_remove_head(&hw->io_wait_free); } if (rem_count > 0) { ocs_log_debug(hw->os, "removed %d items from io_wait_free list\n", rem_count); } } rem_count=0; if (ocs_list_valid(&hw->io_inuse)) { while ((!ocs_list_empty(&hw->io_inuse))) { rem_count++; ocs_list_remove_head(&hw->io_inuse); } if (rem_count > 0) { ocs_log_debug(hw->os, "removed %d items from io_inuse list\n", rem_count); } } rem_count=0; if (ocs_list_valid(&hw->io_free)) { while ((!ocs_list_empty(&hw->io_free))) { rem_count++; ocs_list_remove_head(&hw->io_free); } if (rem_count > 0) { ocs_log_debug(hw->os, "removed %d items from io_free list\n", rem_count); } } if (ocs_list_valid(&hw->io_port_owned)) { while ((!ocs_list_empty(&hw->io_port_owned))) { ocs_list_remove_head(&hw->io_port_owned); } } ocs_list_init(&hw->io_inuse, ocs_hw_io_t, link); ocs_list_init(&hw->io_free, ocs_hw_io_t, link); ocs_list_init(&hw->io_port_owned, ocs_hw_io_t, link); ocs_list_init(&hw->io_wait_free, ocs_hw_io_t, link); ocs_list_init(&hw->io_timed_wqe, ocs_hw_io_t, wqe_link); ocs_list_init(&hw->io_port_dnrx, ocs_hw_io_t, dnrx_link); /* If MRQ not required, Make sure we dont request feature. */ if (hw->config.n_rq == 1) { hw->sli.config.features.flag.mrqp = FALSE; } if (sli_init(&hw->sli)) { ocs_log_err(hw->os, "SLI failed to initialize\n"); return OCS_HW_RTN_ERROR; } /* * Enable the auto xfer rdy feature if requested. */ hw->auto_xfer_rdy_enabled = FALSE; if (sli_get_auto_xfer_rdy_capable(&hw->sli) && hw->config.auto_xfer_rdy_size > 0) { if (hw->config.esoc){ if (ocs_get_property("ramdisc_blocksize", prop_buf, sizeof(prop_buf)) == 0) { ramdisc_blocksize = ocs_strtoul(prop_buf, 0, 0); } written_size = sli_cmd_config_auto_xfer_rdy_hp(&hw->sli, buf, SLI4_BMBX_SIZE, hw->config.auto_xfer_rdy_size, 1, ramdisc_blocksize); } else { written_size = sli_cmd_config_auto_xfer_rdy(&hw->sli, buf, SLI4_BMBX_SIZE, hw->config.auto_xfer_rdy_size); } if (written_size) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "config auto xfer rdy failed\n"); return rc; } } hw->auto_xfer_rdy_enabled = TRUE; if (hw->config.auto_xfer_rdy_t10_enable) { rc = ocs_hw_config_auto_xfer_rdy_t10pi(hw, buf); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "set parameters auto xfer rdy T10 PI failed\n"); return rc; } } } if(hw->sliport_healthcheck) { rc = ocs_hw_config_sli_port_health_check(hw, 0, 1); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "Enabling Sliport Health check failed \n"); return rc; } } /* * Set FDT transfer hint, only works on Lancer */ if ((hw->sli.if_type == SLI4_IF_TYPE_LANCER_FC_ETH) && (OCS_HW_FDT_XFER_HINT != 0)) { /* * Non-fatal error. In particular, we can disregard failure to set OCS_HW_FDT_XFER_HINT on * devices with legacy firmware that do not support OCS_HW_FDT_XFER_HINT feature. */ ocs_hw_config_set_fdt_xfer_hint(hw, OCS_HW_FDT_XFER_HINT); } /* * Verify that we have not exceeded any queue sizes */ q_count = MIN(sli_get_max_queue(&hw->sli, SLI_QTYPE_EQ), OCS_HW_MAX_NUM_EQ); if (hw->config.n_eq > q_count) { ocs_log_err(hw->os, "requested %d EQ but %d allowed\n", hw->config.n_eq, q_count); return OCS_HW_RTN_ERROR; } q_count = MIN(sli_get_max_queue(&hw->sli, SLI_QTYPE_CQ), OCS_HW_MAX_NUM_CQ); if (hw->config.n_cq > q_count) { ocs_log_err(hw->os, "requested %d CQ but %d allowed\n", hw->config.n_cq, q_count); return OCS_HW_RTN_ERROR; } q_count = MIN(sli_get_max_queue(&hw->sli, SLI_QTYPE_MQ), OCS_HW_MAX_NUM_MQ); if (hw->config.n_mq > q_count) { ocs_log_err(hw->os, "requested %d MQ but %d allowed\n", hw->config.n_mq, q_count); return OCS_HW_RTN_ERROR; } q_count = MIN(sli_get_max_queue(&hw->sli, SLI_QTYPE_RQ), OCS_HW_MAX_NUM_RQ); if (hw->config.n_rq > q_count) { ocs_log_err(hw->os, "requested %d RQ but %d allowed\n", hw->config.n_rq, q_count); return OCS_HW_RTN_ERROR; } q_count = MIN(sli_get_max_queue(&hw->sli, SLI_QTYPE_WQ), OCS_HW_MAX_NUM_WQ); if (hw->config.n_wq > q_count) { ocs_log_err(hw->os, "requested %d WQ but %d allowed\n", hw->config.n_wq, q_count); return OCS_HW_RTN_ERROR; } /* zero the hashes */ ocs_memset(hw->cq_hash, 0, sizeof(hw->cq_hash)); ocs_log_debug(hw->os, "Max CQs %d, hash size = %d\n", OCS_HW_MAX_NUM_CQ, OCS_HW_Q_HASH_SIZE); ocs_memset(hw->rq_hash, 0, sizeof(hw->rq_hash)); ocs_log_debug(hw->os, "Max RQs %d, hash size = %d\n", OCS_HW_MAX_NUM_RQ, OCS_HW_Q_HASH_SIZE); ocs_memset(hw->wq_hash, 0, sizeof(hw->wq_hash)); ocs_log_debug(hw->os, "Max WQs %d, hash size = %d\n", OCS_HW_MAX_NUM_WQ, OCS_HW_Q_HASH_SIZE); rc = ocs_hw_init_queues(hw, hw->qtop); if (rc != OCS_HW_RTN_SUCCESS) { return rc; } max_rpi = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_RPI); i = sli_fc_get_rpi_requirements(&hw->sli, max_rpi); if (i) { ocs_dma_t payload_memory; rc = OCS_HW_RTN_ERROR; if (hw->rnode_mem.size) { ocs_dma_free(hw->os, &hw->rnode_mem); } if (ocs_dma_alloc(hw->os, &hw->rnode_mem, i, 4096)) { ocs_log_err(hw->os, "remote node memory allocation fail\n"); return OCS_HW_RTN_NO_MEMORY; } payload_memory.size = 0; if (sli_cmd_fcoe_post_hdr_templates(&hw->sli, buf, SLI4_BMBX_SIZE, &hw->rnode_mem, UINT16_MAX, &payload_memory)) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (payload_memory.size != 0) { /* The command was non-embedded - need to free the dma buffer */ ocs_dma_free(hw->os, &payload_memory); } } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "header template registration failed\n"); return rc; } } /* Allocate and post RQ buffers */ rc = ocs_hw_rx_allocate(hw); if (rc) { ocs_log_err(hw->os, "rx_allocate failed\n"); return rc; } /* Populate hw->seq_free_list */ if (hw->seq_pool == NULL) { uint32_t count = 0; uint32_t i; /* Sum up the total number of RQ entries, to use to allocate the sequence object pool */ for (i = 0; i < hw->hw_rq_count; i++) { count += hw->hw_rq[i]->entry_count; } hw->seq_pool = ocs_array_alloc(hw->os, sizeof(ocs_hw_sequence_t), count); if (hw->seq_pool == NULL) { ocs_log_err(hw->os, "malloc seq_pool failed\n"); return OCS_HW_RTN_NO_MEMORY; } } if(ocs_hw_rx_post(hw)) { ocs_log_err(hw->os, "WARNING - error posting RQ buffers\n"); } /* Allocate rpi_ref if not previously allocated */ if (hw->rpi_ref == NULL) { hw->rpi_ref = ocs_malloc(hw->os, max_rpi * sizeof(*hw->rpi_ref), OCS_M_ZERO | OCS_M_NOWAIT); if (hw->rpi_ref == NULL) { ocs_log_err(hw->os, "rpi_ref allocation failure (%d)\n", i); return OCS_HW_RTN_NO_MEMORY; } } for (i = 0; i < max_rpi; i ++) { ocs_atomic_init(&hw->rpi_ref[i].rpi_count, 0); ocs_atomic_init(&hw->rpi_ref[i].rpi_attached, 0); } ocs_memset(hw->domains, 0, sizeof(hw->domains)); /* HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB */ if (hw->workaround.override_fcfi) { hw->first_domain_idx = -1; } ocs_memset(hw->fcf_index_fcfi, 0, sizeof(hw->fcf_index_fcfi)); /* Register a FCFI to allow unsolicited frames to be routed to the driver */ if (sli_get_medium(&hw->sli) == SLI_LINK_MEDIUM_FC) { if (hw->hw_mrq_count) { ocs_log_debug(hw->os, "using REG_FCFI MRQ\n"); rc = ocs_hw_config_mrq(hw, SLI4_CMD_REG_FCFI_SET_FCFI_MODE, 0, 0); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "REG_FCFI_MRQ FCFI registration failed\n"); return rc; } rc = ocs_hw_config_mrq(hw, SLI4_CMD_REG_FCFI_SET_MRQ_MODE, 0, 0); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "REG_FCFI_MRQ MRQ registration failed\n"); return rc; } } else { sli4_cmd_rq_cfg_t rq_cfg[SLI4_CMD_REG_FCFI_NUM_RQ_CFG]; ocs_log_debug(hw->os, "using REG_FCFI standard\n"); /* Set the filter match/mask values from hw's filter_def values */ for (i = 0; i < SLI4_CMD_REG_FCFI_NUM_RQ_CFG; i++) { rq_cfg[i].rq_id = 0xffff; rq_cfg[i].r_ctl_mask = (uint8_t) hw->config.filter_def[i]; rq_cfg[i].r_ctl_match = (uint8_t) (hw->config.filter_def[i] >> 8); rq_cfg[i].type_mask = (uint8_t) (hw->config.filter_def[i] >> 16); rq_cfg[i].type_match = (uint8_t) (hw->config.filter_def[i] >> 24); } /* * Update the rq_id's of the FCF configuration (don't update more than the number * of rq_cfg elements) */ for (i = 0; i < OCS_MIN(hw->hw_rq_count, SLI4_CMD_REG_FCFI_NUM_RQ_CFG); i++) { hw_rq_t *rq = hw->hw_rq[i]; uint32_t j; for (j = 0; j < SLI4_CMD_REG_FCFI_NUM_RQ_CFG; j++) { uint32_t mask = (rq->filter_mask != 0) ? rq->filter_mask : 1; if (mask & (1U << j)) { rq_cfg[j].rq_id = rq->hdr->id; ocs_log_debug(hw->os, "REG_FCFI: filter[%d] %08X -> RQ[%d] id=%d\n", j, hw->config.filter_def[j], i, rq->hdr->id); } } } rc = OCS_HW_RTN_ERROR; if (sli_cmd_reg_fcfi(&hw->sli, buf, SLI4_BMBX_SIZE, 0, rq_cfg, 0)) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "FCFI registration failed\n"); return rc; } hw->fcf_indicator = ((sli4_cmd_reg_fcfi_t *)buf)->fcfi; } } /* * Allocate the WQ request tag pool, if not previously allocated (the request tag value is 16 bits, * thus the pool allocation size of 64k) */ rc = ocs_hw_reqtag_init(hw); if (rc) { ocs_log_err(hw->os, "ocs_pool_alloc hw_wq_callback_t failed: %d\n", rc); return rc; } rc = ocs_hw_setup_io(hw); if (rc) { ocs_log_err(hw->os, "IO allocation failure\n"); return rc; } rc = ocs_hw_init_io(hw); if (rc) { ocs_log_err(hw->os, "IO initialization failure\n"); return rc; } ocs_queue_history_init(hw->os, &hw->q_hist); /* get hw link config; polling, so callback will be called immediately */ hw->linkcfg = OCS_HW_LINKCFG_NA; ocs_hw_get_linkcfg(hw, OCS_CMD_POLL, ocs_hw_init_linkcfg_cb, hw); /* if lancer ethernet, ethernet ports need to be enabled */ if ((hw->sli.if_type == SLI4_IF_TYPE_LANCER_FC_ETH) && (sli_get_medium(&hw->sli) == SLI_LINK_MEDIUM_ETHERNET)) { if (ocs_hw_set_eth_license(hw, hw->eth_license)) { /* log warning but continue */ ocs_log_err(hw->os, "Failed to set ethernet license\n"); } } /* Set the DIF seed - only for lancer right now */ if (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli) && ocs_hw_set_dif_seed(hw) != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "Failed to set DIF seed value\n"); return rc; } /* Set the DIF mode - skyhawk only */ if (SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli) && sli_get_dif_capable(&hw->sli)) { rc = ocs_hw_set_dif_mode(hw); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "Failed to set DIF mode value\n"); return rc; } } /* * Arming the EQ allows (e.g.) interrupts when CQ completions write EQ entries */ for (i = 0; i < hw->eq_count; i++) { sli_queue_arm(&hw->sli, &hw->eq[i], TRUE); } /* * Initialize RQ hash */ for (i = 0; i < hw->rq_count; i++) { ocs_hw_queue_hash_add(hw->rq_hash, hw->rq[i].id, i); } /* * Initialize WQ hash */ for (i = 0; i < hw->wq_count; i++) { ocs_hw_queue_hash_add(hw->wq_hash, hw->wq[i].id, i); } /* * Arming the CQ allows (e.g.) MQ completions to write CQ entries */ for (i = 0; i < hw->cq_count; i++) { ocs_hw_queue_hash_add(hw->cq_hash, hw->cq[i].id, i); sli_queue_arm(&hw->sli, &hw->cq[i], TRUE); } /* record the fact that the queues are functional */ hw->state = OCS_HW_STATE_ACTIVE; /* Note: Must be after the IOs are setup and the state is active*/ if (ocs_hw_rqpair_init(hw)) { ocs_log_err(hw->os, "WARNING - error initializing RQ pair\n"); } /* finally kick off periodic timer to check for timed out target WQEs */ if (hw->config.emulate_tgt_wqe_timeout) { ocs_setup_timer(hw->os, &hw->wqe_timer, target_wqe_timer_cb, hw, OCS_HW_WQ_TIMER_PERIOD_MS); } /* * Allocate a HW IOs for send frame. Allocate one for each Class 1 WQ, or if there * are none of those, allocate one for WQ[0] */ if ((count = ocs_varray_get_count(hw->wq_class_array[1])) > 0) { for (i = 0; i < count; i++) { hw_wq_t *wq = ocs_varray_iter_next(hw->wq_class_array[1]); wq->send_frame_io = ocs_hw_io_alloc(hw); if (wq->send_frame_io == NULL) { ocs_log_err(hw->os, "ocs_hw_io_alloc for send_frame_io failed\n"); } } } else { hw->hw_wq[0]->send_frame_io = ocs_hw_io_alloc(hw); if (hw->hw_wq[0]->send_frame_io == NULL) { ocs_log_err(hw->os, "ocs_hw_io_alloc for send_frame_io failed\n"); } } /* Initialize send frame frame sequence id */ ocs_atomic_init(&hw->send_frame_seq_id, 0); /* Initialize watchdog timer if enabled by user */ hw->expiration_logged = 0; if(hw->watchdog_timeout) { if((hw->watchdog_timeout < 1) || (hw->watchdog_timeout > 65534)) { ocs_log_err(hw->os, "watchdog_timeout out of range: Valid range is 1 - 65534\n"); }else if(!ocs_hw_config_watchdog_timer(hw)) { ocs_log_info(hw->os, "watchdog timer configured with timeout = %d seconds \n", hw->watchdog_timeout); } } if (ocs_dma_alloc(hw->os, &hw->domain_dmem, 112, 4)) { ocs_log_err(hw->os, "domain node memory allocation fail\n"); return OCS_HW_RTN_NO_MEMORY; } if (ocs_dma_alloc(hw->os, &hw->fcf_dmem, OCS_HW_READ_FCF_SIZE, OCS_HW_READ_FCF_SIZE)) { ocs_log_err(hw->os, "domain fcf memory allocation fail\n"); return OCS_HW_RTN_NO_MEMORY; } if ((0 == hw->loop_map.size) && ocs_dma_alloc(hw->os, &hw->loop_map, SLI4_MIN_LOOP_MAP_BYTES, 4)) { ocs_log_err(hw->os, "Loop dma alloc failed size:%d \n", hw->loop_map.size); } return OCS_HW_RTN_SUCCESS; } /** * @brief Configure Multi-RQ * * @param hw Hardware context allocated by the caller. * @param mode 1 to set MRQ filters and 0 to set FCFI index * @param vlanid valid in mode 0 * @param fcf_index valid in mode 0 * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_config_mrq(ocs_hw_t *hw, uint8_t mode, uint16_t vlanid, uint16_t fcf_index) { uint8_t buf[SLI4_BMBX_SIZE], mrq_bitmask = 0; hw_rq_t *rq; sli4_cmd_reg_fcfi_mrq_t *rsp = NULL; uint32_t i, j; sli4_cmd_rq_cfg_t rq_filter[SLI4_CMD_REG_FCFI_MRQ_NUM_RQ_CFG]; int32_t rc; if (mode == SLI4_CMD_REG_FCFI_SET_FCFI_MODE) { goto issue_cmd; } /* Set the filter match/mask values from hw's filter_def values */ for (i = 0; i < SLI4_CMD_REG_FCFI_NUM_RQ_CFG; i++) { rq_filter[i].rq_id = 0xffff; rq_filter[i].r_ctl_mask = (uint8_t) hw->config.filter_def[i]; rq_filter[i].r_ctl_match = (uint8_t) (hw->config.filter_def[i] >> 8); rq_filter[i].type_mask = (uint8_t) (hw->config.filter_def[i] >> 16); rq_filter[i].type_match = (uint8_t) (hw->config.filter_def[i] >> 24); } /* Accumulate counts for each filter type used, build rq_ids[] list */ for (i = 0; i < hw->hw_rq_count; i++) { rq = hw->hw_rq[i]; for (j = 0; j < SLI4_CMD_REG_FCFI_MRQ_NUM_RQ_CFG; j++) { if (rq->filter_mask & (1U << j)) { if (rq_filter[j].rq_id != 0xffff) { /* Already used. Bailout ifts not RQset case */ if (!rq->is_mrq || (rq_filter[j].rq_id != rq->base_mrq_id)) { ocs_log_err(hw->os, "Wrong queue topology.\n"); return OCS_HW_RTN_ERROR; } continue; } if (rq->is_mrq) { rq_filter[j].rq_id = rq->base_mrq_id; mrq_bitmask |= (1U << j); } else { rq_filter[j].rq_id = rq->hdr->id; } } } } issue_cmd: /* Invoke REG_FCFI_MRQ */ rc = sli_cmd_reg_fcfi_mrq(&hw->sli, buf, /* buf */ SLI4_BMBX_SIZE, /* size */ mode, /* mode 1 */ fcf_index, /* fcf_index */ vlanid, /* vlan_id */ hw->config.rq_selection_policy, /* RQ selection policy*/ mrq_bitmask, /* MRQ bitmask */ hw->hw_mrq_count, /* num_mrqs */ rq_filter); /* RQ filter */ if (rc == 0) { ocs_log_err(hw->os, "sli_cmd_reg_fcfi_mrq() failed: %d\n", rc); return OCS_HW_RTN_ERROR; } rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); rsp = (sli4_cmd_reg_fcfi_mrq_t *)buf; if ((rc != OCS_HW_RTN_SUCCESS) || (rsp->hdr.status)) { ocs_log_err(hw->os, "FCFI MRQ registration failed. cmd = %x status = %x\n", rsp->hdr.command, rsp->hdr.status); return OCS_HW_RTN_ERROR; } if (mode == SLI4_CMD_REG_FCFI_SET_FCFI_MODE) { hw->fcf_indicator = rsp->fcfi; } return 0; } /** * @brief Callback function for getting linkcfg during HW initialization. * * @param status Status of the linkcfg get operation. * @param value Link configuration enum to which the link configuration is set. * @param arg Callback argument (ocs_hw_t *). * * @return None. */ static void ocs_hw_init_linkcfg_cb(int32_t status, uintptr_t value, void *arg) { ocs_hw_t *hw = (ocs_hw_t *)arg; if (status == 0) { hw->linkcfg = (ocs_hw_linkcfg_e)value; } else { hw->linkcfg = OCS_HW_LINKCFG_NA; } ocs_log_debug(hw->os, "linkcfg=%d\n", hw->linkcfg); } /** * @ingroup devInitShutdown * @brief Tear down the Hardware Abstraction Layer module. * * @par Description * Frees memory structures needed by the device, and shuts down the device. Does * not free the HW context memory (which is done by the caller). * * @param hw Hardware context allocated by the caller. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_teardown(ocs_hw_t *hw) { uint32_t i = 0; uint32_t iters = 10;/*XXX*/ uint32_t max_rpi; uint32_t destroy_queues; uint32_t free_memory; if (!hw) { ocs_log_err(NULL, "bad parameter(s) hw=%p\n", hw); return OCS_HW_RTN_ERROR; } destroy_queues = (hw->state == OCS_HW_STATE_ACTIVE); free_memory = (hw->state != OCS_HW_STATE_UNINITIALIZED); /* shutdown target wqe timer */ shutdown_target_wqe_timer(hw); /* Cancel watchdog timer if enabled */ if(hw->watchdog_timeout) { hw->watchdog_timeout = 0; ocs_hw_config_watchdog_timer(hw); } /* Cancel Sliport Healthcheck */ if(hw->sliport_healthcheck) { hw->sliport_healthcheck = 0; ocs_hw_config_sli_port_health_check(hw, 0, 0); } if (hw->state != OCS_HW_STATE_QUEUES_ALLOCATED) { hw->state = OCS_HW_STATE_TEARDOWN_IN_PROGRESS; ocs_hw_flush(hw); /* If there are outstanding commands, wait for them to complete */ while (!ocs_list_empty(&hw->cmd_head) && iters) { ocs_udelay(10000); ocs_hw_flush(hw); iters--; } if (ocs_list_empty(&hw->cmd_head)) { ocs_log_debug(hw->os, "All commands completed on MQ queue\n"); } else { ocs_log_debug(hw->os, "Some commands still pending on MQ queue\n"); } /* Cancel any remaining commands */ ocs_hw_command_cancel(hw); } else { hw->state = OCS_HW_STATE_TEARDOWN_IN_PROGRESS; } ocs_lock_free(&hw->cmd_lock); /* Free unregistered RPI if workaround is in force */ if (hw->workaround.use_unregistered_rpi) { sli_resource_free(&hw->sli, SLI_RSRC_FCOE_RPI, hw->workaround.unregistered_rid); } max_rpi = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_RPI); if (hw->rpi_ref) { for (i = 0; i < max_rpi; i++) { if (ocs_atomic_read(&hw->rpi_ref[i].rpi_count)) { ocs_log_debug(hw->os, "non-zero ref [%d]=%d\n", i, ocs_atomic_read(&hw->rpi_ref[i].rpi_count)); } } ocs_free(hw->os, hw->rpi_ref, max_rpi * sizeof(*hw->rpi_ref)); hw->rpi_ref = NULL; } ocs_dma_free(hw->os, &hw->rnode_mem); if (hw->io) { for (i = 0; i < hw->config.n_io; i++) { if (hw->io[i] && (hw->io[i]->sgl != NULL) && (hw->io[i]->sgl->virt != NULL)) { if(hw->io[i]->is_port_owned) { ocs_lock_free(&hw->io[i]->axr_lock); } ocs_dma_free(hw->os, hw->io[i]->sgl); } ocs_free(hw->os, hw->io[i], sizeof(ocs_hw_io_t)); hw->io[i] = NULL; } ocs_free(hw->os, hw->wqe_buffs, hw->config.n_io * hw->sli.config.wqe_size); hw->wqe_buffs = NULL; ocs_free(hw->os, hw->io, hw->config.n_io * sizeof(ocs_hw_io_t *)); hw->io = NULL; } ocs_dma_free(hw->os, &hw->xfer_rdy); ocs_dma_free(hw->os, &hw->dump_sges); ocs_dma_free(hw->os, &hw->loop_map); ocs_lock_free(&hw->io_lock); ocs_lock_free(&hw->io_abort_lock); for (i = 0; i < hw->wq_count; i++) { sli_queue_free(&hw->sli, &hw->wq[i], destroy_queues, free_memory); } for (i = 0; i < hw->rq_count; i++) { sli_queue_free(&hw->sli, &hw->rq[i], destroy_queues, free_memory); } for (i = 0; i < hw->mq_count; i++) { sli_queue_free(&hw->sli, &hw->mq[i], destroy_queues, free_memory); } for (i = 0; i < hw->cq_count; i++) { sli_queue_free(&hw->sli, &hw->cq[i], destroy_queues, free_memory); } for (i = 0; i < hw->eq_count; i++) { sli_queue_free(&hw->sli, &hw->eq[i], destroy_queues, free_memory); } ocs_hw_qtop_free(hw->qtop); /* Free rq buffers */ ocs_hw_rx_free(hw); hw_queue_teardown(hw); ocs_hw_rqpair_teardown(hw); if (sli_teardown(&hw->sli)) { ocs_log_err(hw->os, "SLI teardown failed\n"); } ocs_queue_history_free(&hw->q_hist); /* record the fact that the queues are non-functional */ hw->state = OCS_HW_STATE_UNINITIALIZED; /* free sequence free pool */ ocs_array_free(hw->seq_pool); hw->seq_pool = NULL; /* free hw_wq_callback pool */ ocs_pool_free(hw->wq_reqtag_pool); ocs_dma_free(hw->os, &hw->domain_dmem); ocs_dma_free(hw->os, &hw->fcf_dmem); /* Mark HW setup as not having been called */ hw->hw_setup_called = FALSE; return OCS_HW_RTN_SUCCESS; } ocs_hw_rtn_e ocs_hw_reset(ocs_hw_t *hw, ocs_hw_reset_e reset) { uint32_t i; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint32_t iters; ocs_hw_state_e prev_state = hw->state; if (hw->state != OCS_HW_STATE_ACTIVE) { ocs_log_test(hw->os, "HW state %d is not active\n", hw->state); } hw->state = OCS_HW_STATE_RESET_IN_PROGRESS; /* shutdown target wqe timer */ shutdown_target_wqe_timer(hw); ocs_hw_flush(hw); /* * If an mailbox command requiring a DMA is outstanding (i.e. SFP/DDM), * then the FW will UE when the reset is issued. So attempt to complete * all mailbox commands. */ iters = 10; while (!ocs_list_empty(&hw->cmd_head) && iters) { ocs_udelay(10000); ocs_hw_flush(hw); iters--; } if (ocs_list_empty(&hw->cmd_head)) { ocs_log_debug(hw->os, "All commands completed on MQ queue\n"); } else { ocs_log_debug(hw->os, "Some commands still pending on MQ queue\n"); } /* Reset the chip */ switch(reset) { case OCS_HW_RESET_FUNCTION: ocs_log_debug(hw->os, "issuing function level reset\n"); if (sli_reset(&hw->sli)) { ocs_log_err(hw->os, "sli_reset failed\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_RESET_FIRMWARE: ocs_log_debug(hw->os, "issuing firmware reset\n"); if (sli_fw_reset(&hw->sli)) { ocs_log_err(hw->os, "sli_soft_reset failed\n"); rc = OCS_HW_RTN_ERROR; } /* * Because the FW reset leaves the FW in a non-running state, * follow that with a regular reset. */ ocs_log_debug(hw->os, "issuing function level reset\n"); if (sli_reset(&hw->sli)) { ocs_log_err(hw->os, "sli_reset failed\n"); rc = OCS_HW_RTN_ERROR; } break; default: ocs_log_test(hw->os, "unknown reset type - no reset performed\n"); hw->state = prev_state; return OCS_HW_RTN_ERROR; } /* Not safe to walk command/io lists unless they've been initialized */ if (prev_state != OCS_HW_STATE_UNINITIALIZED) { ocs_hw_command_cancel(hw); /* Clean up the inuse list, the free list and the wait free list */ ocs_hw_io_cancel(hw); ocs_memset(hw->domains, 0, sizeof(hw->domains)); ocs_memset(hw->fcf_index_fcfi, 0, sizeof(hw->fcf_index_fcfi)); ocs_hw_link_event_init(hw); ocs_lock(&hw->io_lock); /* The io lists should be empty, but remove any that didn't get cleaned up. */ while (!ocs_list_empty(&hw->io_timed_wqe)) { ocs_list_remove_head(&hw->io_timed_wqe); } /* Don't clean up the io_inuse list, the backend will do that when it finishes the IO */ while (!ocs_list_empty(&hw->io_free)) { ocs_list_remove_head(&hw->io_free); } while (!ocs_list_empty(&hw->io_wait_free)) { ocs_list_remove_head(&hw->io_wait_free); } /* Reset the request tag pool, the HW IO request tags are reassigned in ocs_hw_setup_io() */ ocs_hw_reqtag_reset(hw); ocs_unlock(&hw->io_lock); } if (prev_state != OCS_HW_STATE_UNINITIALIZED) { for (i = 0; i < hw->wq_count; i++) { sli_queue_reset(&hw->sli, &hw->wq[i]); } for (i = 0; i < hw->rq_count; i++) { sli_queue_reset(&hw->sli, &hw->rq[i]); } for (i = 0; i < hw->hw_rq_count; i++) { hw_rq_t *rq = hw->hw_rq[i]; if (rq->rq_tracker != NULL) { uint32_t j; for (j = 0; j < rq->entry_count; j++) { rq->rq_tracker[j] = NULL; } } } for (i = 0; i < hw->mq_count; i++) { sli_queue_reset(&hw->sli, &hw->mq[i]); } for (i = 0; i < hw->cq_count; i++) { sli_queue_reset(&hw->sli, &hw->cq[i]); } for (i = 0; i < hw->eq_count; i++) { sli_queue_reset(&hw->sli, &hw->eq[i]); } /* Free rq buffers */ ocs_hw_rx_free(hw); /* Teardown the HW queue topology */ hw_queue_teardown(hw); } else { /* Free rq buffers */ ocs_hw_rx_free(hw); } /* * Re-apply the run-time workarounds after clearing the SLI config * fields in sli_reset. */ ocs_hw_workaround_setup(hw); hw->state = OCS_HW_STATE_QUEUES_ALLOCATED; return rc; } int32_t ocs_hw_get_num_eq(ocs_hw_t *hw) { return hw->eq_count; } static int32_t ocs_hw_get_fw_timed_out(ocs_hw_t *hw) { /* The error values below are taken from LOWLEVEL_SET_WATCHDOG_TIMER_rev1.pdf * No further explanation is given in the document. * */ return (sli_reg_read(&hw->sli, SLI4_REG_SLIPORT_ERROR1) == 0x2 && sli_reg_read(&hw->sli, SLI4_REG_SLIPORT_ERROR2) == 0x10); } ocs_hw_rtn_e ocs_hw_get(ocs_hw_t *hw, ocs_hw_property_e prop, uint32_t *value) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; int32_t tmp; if (!value) { return OCS_HW_RTN_ERROR; } *value = 0; switch (prop) { case OCS_HW_N_IO: *value = hw->config.n_io; break; case OCS_HW_N_SGL: *value = (hw->config.n_sgl - SLI4_SGE_MAX_RESERVED); break; case OCS_HW_MAX_IO: *value = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_XRI); break; case OCS_HW_MAX_NODES: *value = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_RPI); break; case OCS_HW_MAX_RQ_ENTRIES: *value = hw->num_qentries[SLI_QTYPE_RQ]; break; case OCS_HW_RQ_DEFAULT_BUFFER_SIZE: *value = hw->config.rq_default_buffer_size; break; case OCS_HW_AUTO_XFER_RDY_CAPABLE: *value = sli_get_auto_xfer_rdy_capable(&hw->sli); break; case OCS_HW_AUTO_XFER_RDY_XRI_CNT: *value = hw->config.auto_xfer_rdy_xri_cnt; break; case OCS_HW_AUTO_XFER_RDY_SIZE: *value = hw->config.auto_xfer_rdy_size; break; case OCS_HW_AUTO_XFER_RDY_BLK_SIZE: switch (hw->config.auto_xfer_rdy_blk_size_chip) { case 0: *value = 512; break; case 1: *value = 1024; break; case 2: *value = 2048; break; case 3: *value = 4096; break; case 4: *value = 520; break; default: *value = 0; rc = OCS_HW_RTN_ERROR; break; } break; case OCS_HW_AUTO_XFER_RDY_T10_ENABLE: *value = hw->config.auto_xfer_rdy_t10_enable; break; case OCS_HW_AUTO_XFER_RDY_P_TYPE: *value = hw->config.auto_xfer_rdy_p_type; break; case OCS_HW_AUTO_XFER_RDY_REF_TAG_IS_LBA: *value = hw->config.auto_xfer_rdy_ref_tag_is_lba; break; case OCS_HW_AUTO_XFER_RDY_APP_TAG_VALID: *value = hw->config.auto_xfer_rdy_app_tag_valid; break; case OCS_HW_AUTO_XFER_RDY_APP_TAG_VALUE: *value = hw->config.auto_xfer_rdy_app_tag_value; break; case OCS_HW_MAX_SGE: *value = sli_get_max_sge(&hw->sli); break; case OCS_HW_MAX_SGL: *value = sli_get_max_sgl(&hw->sli); break; case OCS_HW_TOPOLOGY: /* * Infer link.status based on link.speed. * Report OCS_HW_TOPOLOGY_NONE if the link is down. */ if (hw->link.speed == 0) { *value = OCS_HW_TOPOLOGY_NONE; break; } switch (hw->link.topology) { case SLI_LINK_TOPO_NPORT: *value = OCS_HW_TOPOLOGY_NPORT; break; case SLI_LINK_TOPO_LOOP: *value = OCS_HW_TOPOLOGY_LOOP; break; case SLI_LINK_TOPO_NONE: *value = OCS_HW_TOPOLOGY_NONE; break; default: ocs_log_test(hw->os, "unsupported topology %#x\n", hw->link.topology); rc = OCS_HW_RTN_ERROR; break; } break; case OCS_HW_CONFIG_TOPOLOGY: *value = hw->config.topology; break; case OCS_HW_LINK_SPEED: *value = hw->link.speed; break; case OCS_HW_LINK_CONFIG_SPEED: switch (hw->config.speed) { case FC_LINK_SPEED_10G: *value = 10000; break; case FC_LINK_SPEED_AUTO_16_8_4: *value = 0; break; case FC_LINK_SPEED_2G: *value = 2000; break; case FC_LINK_SPEED_4G: *value = 4000; break; case FC_LINK_SPEED_8G: *value = 8000; break; case FC_LINK_SPEED_16G: *value = 16000; break; case FC_LINK_SPEED_32G: *value = 32000; break; default: ocs_log_test(hw->os, "unsupported speed %#x\n", hw->config.speed); rc = OCS_HW_RTN_ERROR; break; } break; case OCS_HW_IF_TYPE: *value = sli_get_if_type(&hw->sli); break; case OCS_HW_SLI_REV: *value = sli_get_sli_rev(&hw->sli); break; case OCS_HW_SLI_FAMILY: *value = sli_get_sli_family(&hw->sli); break; case OCS_HW_DIF_CAPABLE: *value = sli_get_dif_capable(&hw->sli); break; case OCS_HW_DIF_SEED: *value = hw->config.dif_seed; break; case OCS_HW_DIF_MODE: *value = hw->config.dif_mode; break; case OCS_HW_DIF_MULTI_SEPARATE: /* Lancer supports multiple DIF separates */ if (hw->sli.if_type == SLI4_IF_TYPE_LANCER_FC_ETH) { *value = TRUE; } else { *value = FALSE; } break; case OCS_HW_DUMP_MAX_SIZE: *value = hw->dump_size; break; case OCS_HW_DUMP_READY: *value = sli_dump_is_ready(&hw->sli); break; case OCS_HW_DUMP_PRESENT: *value = sli_dump_is_present(&hw->sli); break; case OCS_HW_RESET_REQUIRED: tmp = sli_reset_required(&hw->sli); if(tmp < 0) { rc = OCS_HW_RTN_ERROR; } else { *value = tmp; } break; case OCS_HW_FW_ERROR: *value = sli_fw_error_status(&hw->sli); break; case OCS_HW_FW_READY: *value = sli_fw_ready(&hw->sli); break; case OCS_HW_FW_TIMED_OUT: *value = ocs_hw_get_fw_timed_out(hw); break; case OCS_HW_HIGH_LOGIN_MODE: *value = sli_get_hlm_capable(&hw->sli); break; case OCS_HW_PREREGISTER_SGL: *value = sli_get_sgl_preregister_required(&hw->sli); break; case OCS_HW_HW_REV1: *value = sli_get_hw_revision(&hw->sli, 0); break; case OCS_HW_HW_REV2: *value = sli_get_hw_revision(&hw->sli, 1); break; case OCS_HW_HW_REV3: *value = sli_get_hw_revision(&hw->sli, 2); break; case OCS_HW_LINKCFG: *value = hw->linkcfg; break; case OCS_HW_ETH_LICENSE: *value = hw->eth_license; break; case OCS_HW_LINK_MODULE_TYPE: *value = sli_get_link_module_type(&hw->sli); break; case OCS_HW_NUM_CHUTES: *value = ocs_hw_get_num_chutes(hw); break; case OCS_HW_DISABLE_AR_TGT_DIF: *value = hw->workaround.disable_ar_tgt_dif; break; case OCS_HW_EMULATE_I_ONLY_AAB: *value = hw->config.i_only_aab; break; case OCS_HW_EMULATE_TARGET_WQE_TIMEOUT: *value = hw->config.emulate_tgt_wqe_timeout; break; case OCS_HW_VPD_LEN: *value = sli_get_vpd_len(&hw->sli); break; case OCS_HW_SGL_CHAINING_CAPABLE: *value = sli_get_is_sgl_chaining_capable(&hw->sli) || hw->workaround.sglc_misreported; break; case OCS_HW_SGL_CHAINING_ALLOWED: /* * SGL Chaining is allowed in the following cases: * 1. Lancer with host SGL Lists * 2. Skyhawk with pre-registered SGL Lists */ *value = FALSE; if ((sli_get_is_sgl_chaining_capable(&hw->sli) || hw->workaround.sglc_misreported) && !sli_get_sgl_preregister(&hw->sli) && SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli)) { *value = TRUE; } if ((sli_get_is_sgl_chaining_capable(&hw->sli) || hw->workaround.sglc_misreported) && sli_get_sgl_preregister(&hw->sli) && ((SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) || (SLI4_IF_TYPE_BE3_SKH_VF == sli_get_if_type(&hw->sli)))) { *value = TRUE; } break; case OCS_HW_SGL_CHAINING_HOST_ALLOCATED: /* Only lancer supports host allocated SGL Chaining buffers. */ *value = ((sli_get_is_sgl_chaining_capable(&hw->sli) || hw->workaround.sglc_misreported) && (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli))); break; case OCS_HW_SEND_FRAME_CAPABLE: if (hw->workaround.ignore_send_frame) { *value = 0; } else { /* Only lancer is capable */ *value = sli_get_if_type(&hw->sli) == SLI4_IF_TYPE_LANCER_FC_ETH; } break; case OCS_HW_RQ_SELECTION_POLICY: *value = hw->config.rq_selection_policy; break; case OCS_HW_RR_QUANTA: *value = hw->config.rr_quanta; break; case OCS_HW_MAX_VPORTS: *value = sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_VPI); break; default: ocs_log_test(hw->os, "unsupported property %#x\n", prop); rc = OCS_HW_RTN_ERROR; } return rc; } void * ocs_hw_get_ptr(ocs_hw_t *hw, ocs_hw_property_e prop) { void *rc = NULL; switch (prop) { case OCS_HW_WWN_NODE: rc = sli_get_wwn_node(&hw->sli); break; case OCS_HW_WWN_PORT: rc = sli_get_wwn_port(&hw->sli); break; case OCS_HW_VPD: /* make sure VPD length is non-zero */ if (sli_get_vpd_len(&hw->sli)) { rc = sli_get_vpd(&hw->sli); } break; case OCS_HW_FW_REV: rc = sli_get_fw_name(&hw->sli, 0); break; case OCS_HW_FW_REV2: rc = sli_get_fw_name(&hw->sli, 1); break; case OCS_HW_IPL: rc = sli_get_ipl_name(&hw->sli); break; case OCS_HW_PORTNUM: rc = sli_get_portnum(&hw->sli); break; case OCS_HW_BIOS_VERSION_STRING: rc = sli_get_bios_version_string(&hw->sli); break; default: ocs_log_test(hw->os, "unsupported property %#x\n", prop); } return rc; } ocs_hw_rtn_e ocs_hw_set(ocs_hw_t *hw, ocs_hw_property_e prop, uint32_t value) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; switch (prop) { case OCS_HW_N_IO: if (value > sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_XRI) || value == 0) { ocs_log_test(hw->os, "IO value out of range %d vs %d\n", value, sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_XRI)); rc = OCS_HW_RTN_ERROR; } else { hw->config.n_io = value; } break; case OCS_HW_N_SGL: value += SLI4_SGE_MAX_RESERVED; if (value > sli_get_max_sgl(&hw->sli)) { ocs_log_test(hw->os, "SGL value out of range %d vs %d\n", value, sli_get_max_sgl(&hw->sli)); rc = OCS_HW_RTN_ERROR; } else { hw->config.n_sgl = value; } break; case OCS_HW_TOPOLOGY: if ((sli_get_medium(&hw->sli) != SLI_LINK_MEDIUM_FC) && (value != OCS_HW_TOPOLOGY_AUTO)) { ocs_log_test(hw->os, "unsupported topology=%#x medium=%#x\n", value, sli_get_medium(&hw->sli)); rc = OCS_HW_RTN_ERROR; break; } switch (value) { case OCS_HW_TOPOLOGY_AUTO: if (sli_get_medium(&hw->sli) == SLI_LINK_MEDIUM_FC) { sli_set_topology(&hw->sli, SLI4_READ_CFG_TOPO_FC); } else { sli_set_topology(&hw->sli, SLI4_READ_CFG_TOPO_FCOE); } break; case OCS_HW_TOPOLOGY_NPORT: sli_set_topology(&hw->sli, SLI4_READ_CFG_TOPO_FC_DA); break; case OCS_HW_TOPOLOGY_LOOP: sli_set_topology(&hw->sli, SLI4_READ_CFG_TOPO_FC_AL); break; default: ocs_log_test(hw->os, "unsupported topology %#x\n", value); rc = OCS_HW_RTN_ERROR; } hw->config.topology = value; break; case OCS_HW_LINK_SPEED: if (sli_get_medium(&hw->sli) != SLI_LINK_MEDIUM_FC) { switch (value) { case 0: /* Auto-speed negotiation */ case 10000: /* FCoE speed */ hw->config.speed = FC_LINK_SPEED_10G; break; default: ocs_log_test(hw->os, "unsupported speed=%#x medium=%#x\n", value, sli_get_medium(&hw->sli)); rc = OCS_HW_RTN_ERROR; } break; } switch (value) { case 0: /* Auto-speed negotiation */ hw->config.speed = FC_LINK_SPEED_AUTO_16_8_4; break; case 2000: /* FC speeds */ hw->config.speed = FC_LINK_SPEED_2G; break; case 4000: hw->config.speed = FC_LINK_SPEED_4G; break; case 8000: hw->config.speed = FC_LINK_SPEED_8G; break; case 16000: hw->config.speed = FC_LINK_SPEED_16G; break; case 32000: hw->config.speed = FC_LINK_SPEED_32G; break; default: ocs_log_test(hw->os, "unsupported speed %d\n", value); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_DIF_SEED: /* Set the DIF seed - only for lancer right now */ if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_test(hw->os, "DIF seed not supported for this device\n"); rc = OCS_HW_RTN_ERROR; } else { hw->config.dif_seed = value; } break; case OCS_HW_DIF_MODE: switch (value) { case OCS_HW_DIF_MODE_INLINE: /* * Make sure we support inline DIF. * * Note: Having both bits clear means that we have old * FW that doesn't set the bits. */ if (sli_is_dif_inline_capable(&hw->sli)) { hw->config.dif_mode = value; } else { ocs_log_test(hw->os, "chip does not support DIF inline\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_DIF_MODE_SEPARATE: /* Make sure we support DIF separates. */ if (sli_is_dif_separate_capable(&hw->sli)) { hw->config.dif_mode = value; } else { ocs_log_test(hw->os, "chip does not support DIF separate\n"); rc = OCS_HW_RTN_ERROR; } } break; case OCS_HW_RQ_PROCESS_LIMIT: { hw_rq_t *rq; uint32_t i; /* For each hw_rq object, set its parent CQ limit value */ for (i = 0; i < hw->hw_rq_count; i++) { rq = hw->hw_rq[i]; hw->cq[rq->cq->instance].proc_limit = value; } break; } case OCS_HW_RQ_DEFAULT_BUFFER_SIZE: hw->config.rq_default_buffer_size = value; break; case OCS_HW_AUTO_XFER_RDY_XRI_CNT: hw->config.auto_xfer_rdy_xri_cnt = value; break; case OCS_HW_AUTO_XFER_RDY_SIZE: hw->config.auto_xfer_rdy_size = value; break; case OCS_HW_AUTO_XFER_RDY_BLK_SIZE: switch (value) { case 512: hw->config.auto_xfer_rdy_blk_size_chip = 0; break; case 1024: hw->config.auto_xfer_rdy_blk_size_chip = 1; break; case 2048: hw->config.auto_xfer_rdy_blk_size_chip = 2; break; case 4096: hw->config.auto_xfer_rdy_blk_size_chip = 3; break; case 520: hw->config.auto_xfer_rdy_blk_size_chip = 4; break; default: ocs_log_err(hw->os, "Invalid block size %d\n", value); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_AUTO_XFER_RDY_T10_ENABLE: hw->config.auto_xfer_rdy_t10_enable = value; break; case OCS_HW_AUTO_XFER_RDY_P_TYPE: hw->config.auto_xfer_rdy_p_type = value; break; case OCS_HW_AUTO_XFER_RDY_REF_TAG_IS_LBA: hw->config.auto_xfer_rdy_ref_tag_is_lba = value; break; case OCS_HW_AUTO_XFER_RDY_APP_TAG_VALID: hw->config.auto_xfer_rdy_app_tag_valid = value; break; case OCS_HW_AUTO_XFER_RDY_APP_TAG_VALUE: hw->config.auto_xfer_rdy_app_tag_value = value; break; case OCS_ESOC: hw->config.esoc = value; break; case OCS_HW_HIGH_LOGIN_MODE: rc = sli_set_hlm(&hw->sli, value); break; case OCS_HW_PREREGISTER_SGL: rc = sli_set_sgl_preregister(&hw->sli, value); break; case OCS_HW_ETH_LICENSE: hw->eth_license = value; break; case OCS_HW_EMULATE_I_ONLY_AAB: hw->config.i_only_aab = value; break; case OCS_HW_EMULATE_TARGET_WQE_TIMEOUT: hw->config.emulate_tgt_wqe_timeout = value; break; case OCS_HW_BOUNCE: hw->config.bounce = value; break; case OCS_HW_RQ_SELECTION_POLICY: hw->config.rq_selection_policy = value; break; case OCS_HW_RR_QUANTA: hw->config.rr_quanta = value; break; default: ocs_log_test(hw->os, "unsupported property %#x\n", prop); rc = OCS_HW_RTN_ERROR; } return rc; } ocs_hw_rtn_e ocs_hw_set_ptr(ocs_hw_t *hw, ocs_hw_property_e prop, void *value) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; switch (prop) { case OCS_HW_WAR_VERSION: hw->hw_war_version = value; break; case OCS_HW_FILTER_DEF: { char *p = value; uint32_t idx = 0; for (idx = 0; idx < ARRAY_SIZE(hw->config.filter_def); idx++) { hw->config.filter_def[idx] = 0; } for (idx = 0; (idx < ARRAY_SIZE(hw->config.filter_def)) && (p != NULL) && *p; ) { hw->config.filter_def[idx++] = ocs_strtoul(p, 0, 0); p = ocs_strchr(p, ','); if (p != NULL) { p++; } } break; } default: ocs_log_test(hw->os, "unsupported property %#x\n", prop); rc = OCS_HW_RTN_ERROR; break; } return rc; } /** * @ingroup interrupt * @brief Check for the events associated with the interrupt vector. * * @param hw Hardware context. * @param vector Zero-based interrupt vector number. * * @return Returns 0 on success, or a non-zero value on failure. */ int32_t ocs_hw_event_check(ocs_hw_t *hw, uint32_t vector) { int32_t rc = 0; if (!hw) { ocs_log_err(NULL, "HW context NULL?!?\n"); return -1; } if (vector > hw->eq_count) { ocs_log_err(hw->os, "vector %d. max %d\n", vector, hw->eq_count); return -1; } /* * The caller should disable interrupts if they wish to prevent us * from processing during a shutdown. The following states are defined: * OCS_HW_STATE_UNINITIALIZED - No queues allocated * OCS_HW_STATE_QUEUES_ALLOCATED - The state after a chip reset, * queues are cleared. * OCS_HW_STATE_ACTIVE - Chip and queues are operational * OCS_HW_STATE_RESET_IN_PROGRESS - reset, we still want completions * OCS_HW_STATE_TEARDOWN_IN_PROGRESS - We still want mailbox * completions. */ if (hw->state != OCS_HW_STATE_UNINITIALIZED) { rc = sli_queue_is_empty(&hw->sli, &hw->eq[vector]); /* Re-arm queue if there are no entries */ if (rc != 0) { sli_queue_arm(&hw->sli, &hw->eq[vector], TRUE); } } return rc; } void ocs_hw_unsol_process_bounce(void *arg) { ocs_hw_sequence_t *seq = arg; ocs_hw_t *hw = seq->hw; ocs_hw_assert(hw != NULL); ocs_hw_assert(hw->callback.unsolicited != NULL); hw->callback.unsolicited(hw->args.unsolicited, seq); } int32_t ocs_hw_process(ocs_hw_t *hw, uint32_t vector, uint32_t max_isr_time_msec) { hw_eq_t *eq; int32_t rc = 0; CPUTRACE(""); /* * The caller should disable interrupts if they wish to prevent us * from processing during a shutdown. The following states are defined: * OCS_HW_STATE_UNINITIALIZED - No queues allocated * OCS_HW_STATE_QUEUES_ALLOCATED - The state after a chip reset, * queues are cleared. * OCS_HW_STATE_ACTIVE - Chip and queues are operational * OCS_HW_STATE_RESET_IN_PROGRESS - reset, we still want completions * OCS_HW_STATE_TEARDOWN_IN_PROGRESS - We still want mailbox * completions. */ if (hw->state == OCS_HW_STATE_UNINITIALIZED) { return 0; } /* Get pointer to hw_eq_t */ eq = hw->hw_eq[vector]; OCS_STAT(eq->use_count++); rc = ocs_hw_eq_process(hw, eq, max_isr_time_msec); return rc; } /** * @ingroup interrupt * @brief Process events associated with an EQ. * * @par Description * Loop termination: * @n @n Without a mechanism to terminate the completion processing loop, it * is possible under some workload conditions for the loop to never terminate * (or at least take longer than the OS is happy to have an interrupt handler * or kernel thread context hold a CPU without yielding). * @n @n The approach taken here is to periodically check how much time * we have been in this * processing loop, and if we exceed a predetermined time (multiple seconds), the * loop is terminated, and ocs_hw_process() returns. * * @param hw Hardware context. * @param eq Pointer to HW EQ object. * @param max_isr_time_msec Maximum time in msec to stay in this function. * * @return Returns 0 on success, or a non-zero value on failure. */ int32_t ocs_hw_eq_process(ocs_hw_t *hw, hw_eq_t *eq, uint32_t max_isr_time_msec) { uint8_t eqe[sizeof(sli4_eqe_t)] = { 0 }; uint32_t done = FALSE; uint32_t tcheck_count; time_t tstart; time_t telapsed; tcheck_count = OCS_HW_TIMECHECK_ITERATIONS; tstart = ocs_msectime(); CPUTRACE(""); while (!done && !sli_queue_read(&hw->sli, eq->queue, eqe)) { uint16_t cq_id = 0; int32_t rc; rc = sli_eq_parse(&hw->sli, eqe, &cq_id); if (unlikely(rc)) { if (rc > 0) { uint32_t i; /* * Received a sentinel EQE indicating the EQ is full. * Process all CQs */ for (i = 0; i < hw->cq_count; i++) { ocs_hw_cq_process(hw, hw->hw_cq[i]); } continue; } else { return rc; } } else { int32_t index = ocs_hw_queue_hash_find(hw->cq_hash, cq_id); if (likely(index >= 0)) { ocs_hw_cq_process(hw, hw->hw_cq[index]); } else { ocs_log_err(hw->os, "bad CQ_ID %#06x\n", cq_id); } } if (eq->queue->n_posted > (eq->queue->posted_limit)) { sli_queue_arm(&hw->sli, eq->queue, FALSE); } if (tcheck_count && (--tcheck_count == 0)) { tcheck_count = OCS_HW_TIMECHECK_ITERATIONS; telapsed = ocs_msectime() - tstart; if (telapsed >= max_isr_time_msec) { done = TRUE; } } } sli_queue_eq_arm(&hw->sli, eq->queue, TRUE); return 0; } /** * @brief Submit queued (pending) mbx commands. * * @par Description * Submit queued mailbox commands. * --- Assumes that hw->cmd_lock is held --- * * @param hw Hardware context. * * @return Returns 0 on success, or a negative error code value on failure. */ static int32_t ocs_hw_cmd_submit_pending(ocs_hw_t *hw) { ocs_command_ctx_t *ctx; int32_t rc = 0; /* Assumes lock held */ /* Only submit MQE if there's room */ while (hw->cmd_head_count < (OCS_HW_MQ_DEPTH - 1)) { ctx = ocs_list_remove_head(&hw->cmd_pending); if (ctx == NULL) { break; } ocs_list_add_tail(&hw->cmd_head, ctx); hw->cmd_head_count++; if (sli_queue_write(&hw->sli, hw->mq, ctx->buf) < 0) { ocs_log_test(hw->os, "sli_queue_write failed: %d\n", rc); rc = -1; break; } } return rc; } /** * @ingroup io * @brief Issue a SLI command. * * @par Description * Send a mailbox command to the hardware, and either wait for a completion * (OCS_CMD_POLL) or get an optional asynchronous completion (OCS_CMD_NOWAIT). * * @param hw Hardware context. * @param cmd Buffer containing a formatted command and results. * @param opts Command options: * - OCS_CMD_POLL - Command executes synchronously and busy-waits for the completion. * - OCS_CMD_NOWAIT - Command executes asynchronously. Uses callback. * @param cb Function callback used for asynchronous mode. May be NULL. * @n Prototype is (*cb)(void *arg, uint8_t *cmd). * @n @n @b Note: If the * callback function pointer is NULL, the results of the command are silently * discarded, allowing this pointer to exist solely on the stack. * @param arg Argument passed to an asynchronous callback. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_command(ocs_hw_t *hw, uint8_t *cmd, uint32_t opts, void *cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; /* * If the chip is in an error state (UE'd) then reject this mailbox * command. */ if (sli_fw_error_status(&hw->sli) > 0) { uint32_t err1 = sli_reg_read(&hw->sli, SLI4_REG_SLIPORT_ERROR1); uint32_t err2 = sli_reg_read(&hw->sli, SLI4_REG_SLIPORT_ERROR2); if (hw->expiration_logged == 0 && err1 == 0x2 && err2 == 0x10) { hw->expiration_logged = 1; ocs_log_crit(hw->os,"Emulex: Heartbeat expired after %d seconds\n", hw->watchdog_timeout); } ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); ocs_log_crit(hw->os, "status=%#x error1=%#x error2=%#x\n", sli_reg_read(&hw->sli, SLI4_REG_SLIPORT_STATUS), err1, err2); return OCS_HW_RTN_ERROR; } if (OCS_CMD_POLL == opts) { ocs_lock(&hw->cmd_lock); if (hw->mq->length && !sli_queue_is_empty(&hw->sli, hw->mq)) { /* * Can't issue Boot-strap mailbox command with other * mail-queue commands pending as this interaction is * undefined */ rc = OCS_HW_RTN_ERROR; } else { void *bmbx = hw->sli.bmbx.virt; ocs_memset(bmbx, 0, SLI4_BMBX_SIZE); ocs_memcpy(bmbx, cmd, SLI4_BMBX_SIZE); if (sli_bmbx_command(&hw->sli) == 0) { rc = OCS_HW_RTN_SUCCESS; ocs_memcpy(cmd, bmbx, SLI4_BMBX_SIZE); } } ocs_unlock(&hw->cmd_lock); } else if (OCS_CMD_NOWAIT == opts) { ocs_command_ctx_t *ctx = NULL; ctx = ocs_malloc(hw->os, sizeof(ocs_command_ctx_t), OCS_M_ZERO | OCS_M_NOWAIT); if (!ctx) { ocs_log_err(hw->os, "can't allocate command context\n"); return OCS_HW_RTN_NO_RESOURCES; } if (hw->state != OCS_HW_STATE_ACTIVE) { ocs_log_err(hw->os, "Can't send command, HW state=%d\n", hw->state); ocs_free(hw->os, ctx, sizeof(*ctx)); return OCS_HW_RTN_ERROR; } if (cb) { ctx->cb = cb; ctx->arg = arg; } ctx->buf = cmd; ctx->ctx = hw; ocs_lock(&hw->cmd_lock); /* Add to pending list */ ocs_list_add_tail(&hw->cmd_pending, ctx); /* Submit as much of the pending list as we can */ if (ocs_hw_cmd_submit_pending(hw) == 0) { rc = OCS_HW_RTN_SUCCESS; } ocs_unlock(&hw->cmd_lock); } return rc; } /** * @ingroup devInitShutdown * @brief Register a callback for the given event. * * @param hw Hardware context. * @param which Event of interest. * @param func Function to call when the event occurs. * @param arg Argument passed to the callback function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_callback(ocs_hw_t *hw, ocs_hw_callback_e which, void *func, void *arg) { if (!hw || !func || (which >= OCS_HW_CB_MAX)) { ocs_log_err(NULL, "bad parameter hw=%p which=%#x func=%p\n", hw, which, func); return OCS_HW_RTN_ERROR; } switch (which) { case OCS_HW_CB_DOMAIN: hw->callback.domain = func; hw->args.domain = arg; break; case OCS_HW_CB_PORT: hw->callback.port = func; hw->args.port = arg; break; case OCS_HW_CB_UNSOLICITED: hw->callback.unsolicited = func; hw->args.unsolicited = arg; break; case OCS_HW_CB_REMOTE_NODE: hw->callback.rnode = func; hw->args.rnode = arg; break; case OCS_HW_CB_BOUNCE: hw->callback.bounce = func; hw->args.bounce = arg; break; default: ocs_log_test(hw->os, "unknown callback %#x\n", which); return OCS_HW_RTN_ERROR; } return OCS_HW_RTN_SUCCESS; } /** * @ingroup port * @brief Allocate a port object. * * @par Description * This function allocates a VPI object for the port and stores it in the * indicator field of the port object. * * @param hw Hardware context. * @param sport SLI port object used to connect to the domain. * @param domain Domain object associated with this port (may be NULL). * @param wwpn Port's WWPN in big-endian order, or NULL to use default. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_port_alloc(ocs_hw_t *hw, ocs_sli_port_t *sport, ocs_domain_t *domain, uint8_t *wwpn) { uint8_t *cmd = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint32_t index; sport->indicator = UINT32_MAX; sport->hw = hw; sport->ctx.app = sport; sport->sm_free_req_pending = 0; /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } if (wwpn) { ocs_memcpy(&sport->sli_wwpn, wwpn, sizeof(sport->sli_wwpn)); } if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_VPI, &sport->indicator, &index)) { ocs_log_err(hw->os, "FCOE_VPI allocation failure\n"); return OCS_HW_RTN_ERROR; } if (domain != NULL) { ocs_sm_function_t next = NULL; cmd = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (!cmd) { ocs_log_err(hw->os, "command memory allocation failed\n"); rc = OCS_HW_RTN_NO_MEMORY; goto ocs_hw_port_alloc_out; } /* If the WWPN is NULL, fetch the default WWPN and WWNN before * initializing the VPI */ if (!wwpn) { next = __ocs_hw_port_alloc_read_sparm64; } else { next = __ocs_hw_port_alloc_init_vpi; } ocs_sm_transition(&sport->ctx, next, cmd); } else if (!wwpn) { /* This is the convention for the HW, not SLI */ ocs_log_test(hw->os, "need WWN for physical port\n"); rc = OCS_HW_RTN_ERROR; } else { /* domain NULL and wwpn non-NULL */ ocs_sm_transition(&sport->ctx, __ocs_hw_port_alloc_init, NULL); } ocs_hw_port_alloc_out: if (rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, cmd, SLI4_BMBX_SIZE); sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VPI, sport->indicator); } return rc; } /** * @ingroup port * @brief Attach a physical/virtual SLI port to a domain. * * @par Description * This function registers a previously-allocated VPI with the * device. * * @param hw Hardware context. * @param sport Pointer to the SLI port object. * @param fc_id Fibre Channel ID to associate with this port. * * @return Returns OCS_HW_RTN_SUCCESS on success, or an error code on failure. */ ocs_hw_rtn_e ocs_hw_port_attach(ocs_hw_t *hw, ocs_sli_port_t *sport, uint32_t fc_id) { uint8_t *buf = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (!hw || !sport) { ocs_log_err(hw ? hw->os : NULL, "bad parameter(s) hw=%p sport=%p\n", hw, sport); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } sport->fc_id = fc_id; ocs_sm_post_event(&sport->ctx, OCS_EVT_HW_PORT_REQ_ATTACH, buf); return rc; } /** * @brief Called when the port control command completes. * * @par Description * We only need to free the mailbox command buffer. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * * @return Returns 0. */ static int32_t ocs_hw_cb_port_control(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @ingroup port * @brief Control a port (initialize, shutdown, or set link configuration). * * @par Description * This function controls a port depending on the @c ctrl parameter: * - @b OCS_HW_PORT_INIT - * Issues the CONFIG_LINK and INIT_LINK commands for the specified port. * The HW generates an OCS_HW_DOMAIN_FOUND event when the link comes up. * . * - @b OCS_HW_PORT_SHUTDOWN - * Issues the DOWN_LINK command for the specified port. * The HW generates an OCS_HW_DOMAIN_LOST event when the link is down. * . * - @b OCS_HW_PORT_SET_LINK_CONFIG - * Sets the link configuration. * * @param hw Hardware context. * @param ctrl Specifies the operation: * - OCS_HW_PORT_INIT * - OCS_HW_PORT_SHUTDOWN * - OCS_HW_PORT_SET_LINK_CONFIG * * @param value Operation-specific value. * - OCS_HW_PORT_INIT - Selective reset AL_PA * - OCS_HW_PORT_SHUTDOWN - N/A * - OCS_HW_PORT_SET_LINK_CONFIG - An enum #ocs_hw_linkcfg_e value. * * @param cb Callback function to invoke the following operation. * - OCS_HW_PORT_INIT/OCS_HW_PORT_SHUTDOWN - NULL (link events * are handled by the OCS_HW_CB_DOMAIN callbacks). * - OCS_HW_PORT_SET_LINK_CONFIG - Invoked after linkcfg mailbox command * completes. * * @param arg Callback argument invoked after the command completes. * - OCS_HW_PORT_INIT/OCS_HW_PORT_SHUTDOWN - NULL (link events * are handled by the OCS_HW_CB_DOMAIN callbacks). * - OCS_HW_PORT_SET_LINK_CONFIG - Invoked after linkcfg mailbox command * completes. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_port_control(ocs_hw_t *hw, ocs_hw_port_e ctrl, uintptr_t value, ocs_hw_port_control_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; switch (ctrl) { case OCS_HW_PORT_INIT: { uint8_t *init_link; uint32_t speed = 0; uint8_t reset_alpa = 0; if (SLI_LINK_MEDIUM_FC == sli_get_medium(&hw->sli)) { uint8_t *cfg_link; cfg_link = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (cfg_link == NULL) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_config_link(&hw->sli, cfg_link, SLI4_BMBX_SIZE)) { rc = ocs_hw_command(hw, cfg_link, OCS_CMD_NOWAIT, ocs_hw_cb_port_control, NULL); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, cfg_link, SLI4_BMBX_SIZE); ocs_log_err(hw->os, "CONFIG_LINK failed\n"); break; } speed = hw->config.speed; reset_alpa = (uint8_t)(value & 0xff); } else { speed = FC_LINK_SPEED_10G; } /* * Bring link up, unless FW version is not supported */ if (hw->workaround.fw_version_too_low) { if (SLI4_IF_TYPE_LANCER_FC_ETH == hw->sli.if_type) { ocs_log_err(hw->os, "Cannot bring up link. Please update firmware to %s or later (current version is %s)\n", OCS_FW_VER_STR(OCS_MIN_FW_VER_LANCER), (char *) sli_get_fw_name(&hw->sli,0)); } else { ocs_log_err(hw->os, "Cannot bring up link. Please update firmware to %s or later (current version is %s)\n", OCS_FW_VER_STR(OCS_MIN_FW_VER_SKYHAWK), (char *) sli_get_fw_name(&hw->sli, 0)); } return OCS_HW_RTN_ERROR; } rc = OCS_HW_RTN_ERROR; /* Allocate a new buffer for the init_link command */ init_link = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (init_link == NULL) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_init_link(&hw->sli, init_link, SLI4_BMBX_SIZE, speed, reset_alpa)) { rc = ocs_hw_command(hw, init_link, OCS_CMD_NOWAIT, ocs_hw_cb_port_control, NULL); } /* Free buffer on error, since no callback is coming */ if (rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, init_link, SLI4_BMBX_SIZE); ocs_log_err(hw->os, "INIT_LINK failed\n"); } break; } case OCS_HW_PORT_SHUTDOWN: { uint8_t *down_link; down_link = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (down_link == NULL) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_down_link(&hw->sli, down_link, SLI4_BMBX_SIZE)) { rc = ocs_hw_command(hw, down_link, OCS_CMD_NOWAIT, ocs_hw_cb_port_control, NULL); } /* Free buffer on error, since no callback is coming */ if (rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, down_link, SLI4_BMBX_SIZE); ocs_log_err(hw->os, "DOWN_LINK failed\n"); } break; } case OCS_HW_PORT_SET_LINK_CONFIG: rc = ocs_hw_set_linkcfg(hw, (ocs_hw_linkcfg_e)value, OCS_CMD_NOWAIT, cb, arg); break; default: ocs_log_test(hw->os, "unhandled control %#x\n", ctrl); break; } return rc; } /** * @ingroup port * @brief Free port resources. * * @par Description * Issue the UNREG_VPI command to free the assigned VPI context. * * @param hw Hardware context. * @param sport SLI port object used to connect to the domain. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_port_free(ocs_hw_t *hw, ocs_sli_port_t *sport) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (!hw || !sport) { ocs_log_err(hw ? hw->os : NULL, "bad parameter(s) hw=%p sport=%p\n", hw, sport); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } ocs_sm_post_event(&sport->ctx, OCS_EVT_HW_PORT_REQ_FREE, NULL); return rc; } /** * @ingroup domain * @brief Allocate a fabric domain object. * * @par Description * This function starts a series of commands needed to connect to the domain, including * - REG_FCFI * - INIT_VFI * - READ_SPARMS * . * @b Note: Not all SLI interface types use all of the above commands. * @n @n Upon successful allocation, the HW generates a OCS_HW_DOMAIN_ALLOC_OK * event. On failure, it generates a OCS_HW_DOMAIN_ALLOC_FAIL event. * * @param hw Hardware context. * @param domain Pointer to the domain object. * @param fcf FCF index. * @param vlan VLAN ID. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_domain_alloc(ocs_hw_t *hw, ocs_domain_t *domain, uint32_t fcf, uint32_t vlan) { uint8_t *cmd = NULL; uint32_t index; if (!hw || !domain || !domain->sport) { ocs_log_err(NULL, "bad parameter(s) hw=%p domain=%p sport=%p\n", hw, domain, domain ? domain->sport : NULL); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } cmd = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (!cmd) { ocs_log_err(hw->os, "command memory allocation failed\n"); return OCS_HW_RTN_NO_MEMORY; } domain->dma = hw->domain_dmem; domain->hw = hw; domain->sm.app = domain; domain->fcf = fcf; domain->fcf_indicator = UINT32_MAX; domain->vlan_id = vlan; domain->indicator = UINT32_MAX; if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_VFI, &domain->indicator, &index)) { ocs_log_err(hw->os, "FCOE_VFI allocation failure\n"); ocs_free(hw->os, cmd, SLI4_BMBX_SIZE); return OCS_HW_RTN_ERROR; } ocs_sm_transition(&domain->sm, __ocs_hw_domain_init, cmd); return OCS_HW_RTN_SUCCESS; } /** * @ingroup domain * @brief Attach a SLI port to a domain. * * @param hw Hardware context. * @param domain Pointer to the domain object. * @param fc_id Fibre Channel ID to associate with this port. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_domain_attach(ocs_hw_t *hw, ocs_domain_t *domain, uint32_t fc_id) { uint8_t *buf = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (!hw || !domain) { ocs_log_err(hw ? hw->os : NULL, "bad parameter(s) hw=%p domain=%p\n", hw, domain); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } domain->sport->fc_id = fc_id; ocs_sm_post_event(&domain->sm, OCS_EVT_HW_DOMAIN_REQ_ATTACH, buf); return rc; } /** * @ingroup domain * @brief Free a fabric domain object. * * @par Description * Free both the driver and SLI port resources associated with the domain. * * @param hw Hardware context. * @param domain Pointer to the domain object. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_domain_free(ocs_hw_t *hw, ocs_domain_t *domain) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (!hw || !domain) { ocs_log_err(hw ? hw->os : NULL, "bad parameter(s) hw=%p domain=%p\n", hw, domain); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } ocs_sm_post_event(&domain->sm, OCS_EVT_HW_DOMAIN_REQ_FREE, NULL); return rc; } /** * @ingroup domain * @brief Free a fabric domain object. * * @par Description * Free the driver resources associated with the domain. The difference between * this call and ocs_hw_domain_free() is that this call assumes resources no longer * exist on the SLI port, due to a reset or after some error conditions. * * @param hw Hardware context. * @param domain Pointer to the domain object. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_domain_force_free(ocs_hw_t *hw, ocs_domain_t *domain) { if (!hw || !domain) { ocs_log_err(NULL, "bad parameter(s) hw=%p domain=%p\n", hw, domain); return OCS_HW_RTN_ERROR; } sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VFI, domain->indicator); return OCS_HW_RTN_SUCCESS; } /** * @ingroup node * @brief Allocate a remote node object. * * @param hw Hardware context. * @param rnode Allocated remote node object to initialize. * @param fc_addr FC address of the remote node. * @param sport SLI port used to connect to remote node. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_node_alloc(ocs_hw_t *hw, ocs_remote_node_t *rnode, uint32_t fc_addr, ocs_sli_port_t *sport) { /* Check for invalid indicator */ if (UINT32_MAX != rnode->indicator) { ocs_log_err(hw->os, "FCOE_RPI allocation failure addr=%#x rpi=%#x\n", fc_addr, rnode->indicator); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } /* NULL SLI port indicates an unallocated remote node */ rnode->sport = NULL; if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_RPI, &rnode->indicator, &rnode->index)) { ocs_log_err(hw->os, "FCOE_RPI allocation failure addr=%#x\n", fc_addr); return OCS_HW_RTN_ERROR; } rnode->fc_id = fc_addr; rnode->sport = sport; return OCS_HW_RTN_SUCCESS; } /** * @ingroup node * @brief Update a remote node object with the remote port's service parameters. * * @param hw Hardware context. * @param rnode Allocated remote node object to initialize. * @param sparms DMA buffer containing the remote port's service parameters. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_node_attach(ocs_hw_t *hw, ocs_remote_node_t *rnode, ocs_dma_t *sparms) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; uint8_t *buf = NULL; uint32_t count = 0; if (!hw || !rnode || !sparms) { ocs_log_err(NULL, "bad parameter(s) hw=%p rnode=%p sparms=%p\n", hw, rnode, sparms); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } /* * If the attach count is non-zero, this RPI has already been registered. * Otherwise, register the RPI */ if (rnode->index == UINT32_MAX) { ocs_log_err(NULL, "bad parameter rnode->index invalid\n"); ocs_free(hw->os, buf, SLI4_BMBX_SIZE); return OCS_HW_RTN_ERROR; } count = ocs_atomic_add_return(&hw->rpi_ref[rnode->index].rpi_count, 1); if (count) { /* * Can't attach multiple FC_ID's to a node unless High Login * Mode is enabled */ if (sli_get_hlm(&hw->sli) == FALSE) { ocs_log_test(hw->os, "attach to already attached node HLM=%d count=%d\n", sli_get_hlm(&hw->sli), count); rc = OCS_HW_RTN_SUCCESS; } else { rnode->node_group = TRUE; rnode->attached = ocs_atomic_read(&hw->rpi_ref[rnode->index].rpi_attached); rc = rnode->attached ? OCS_HW_RTN_SUCCESS_SYNC : OCS_HW_RTN_SUCCESS; } } else { rnode->node_group = FALSE; ocs_display_sparams("", "reg rpi", 0, NULL, sparms->virt); if (sli_cmd_reg_rpi(&hw->sli, buf, SLI4_BMBX_SIZE, rnode->fc_id, rnode->indicator, rnode->sport->indicator, sparms, 0, (hw->auto_xfer_rdy_enabled && hw->config.auto_xfer_rdy_t10_enable))) { rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, ocs_hw_cb_node_attach, rnode); } } if (count || rc) { if (rc < OCS_HW_RTN_SUCCESS) { ocs_atomic_sub_return(&hw->rpi_ref[rnode->index].rpi_count, 1); ocs_log_err(hw->os, "%s error\n", count ? "HLM" : "REG_RPI"); } ocs_free(hw->os, buf, SLI4_BMBX_SIZE); } return rc; } /** * @ingroup node * @brief Free a remote node resource. * * @param hw Hardware context. * @param rnode Remote node object to free. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_node_free_resources(ocs_hw_t *hw, ocs_remote_node_t *rnode) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (!hw || !rnode) { ocs_log_err(NULL, "bad parameter(s) hw=%p rnode=%p\n", hw, rnode); return OCS_HW_RTN_ERROR; } if (rnode->sport) { if (!rnode->attached) { if (rnode->indicator != UINT32_MAX) { if (sli_resource_free(&hw->sli, SLI_RSRC_FCOE_RPI, rnode->indicator)) { ocs_log_err(hw->os, "FCOE_RPI free failure RPI %d addr=%#x\n", rnode->indicator, rnode->fc_id); rc = OCS_HW_RTN_ERROR; } else { rnode->node_group = FALSE; rnode->indicator = UINT32_MAX; rnode->index = UINT32_MAX; rnode->free_group = FALSE; } } } else { ocs_log_err(hw->os, "Error: rnode is still attached\n"); rc = OCS_HW_RTN_ERROR; } } return rc; } /** * @ingroup node * @brief Free a remote node object. * * @param hw Hardware context. * @param rnode Remote node object to free. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_node_detach(ocs_hw_t *hw, ocs_remote_node_t *rnode) { uint8_t *buf = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS_SYNC; uint32_t index = UINT32_MAX; if (!hw || !rnode) { ocs_log_err(NULL, "bad parameter(s) hw=%p rnode=%p\n", hw, rnode); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } index = rnode->index; if (rnode->sport) { uint32_t count = 0; uint32_t fc_id; if (!rnode->attached) { return OCS_HW_RTN_SUCCESS_SYNC; } buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } count = ocs_atomic_sub_return(&hw->rpi_ref[index].rpi_count, 1); if (count <= 1) { /* There are no other references to this RPI * so unregister it and free the resource. */ fc_id = UINT32_MAX; rnode->node_group = FALSE; rnode->free_group = TRUE; } else { if (sli_get_hlm(&hw->sli) == FALSE) { ocs_log_test(hw->os, "Invalid count with HLM disabled, count=%d\n", count); } fc_id = rnode->fc_id & 0x00ffffff; } rc = OCS_HW_RTN_ERROR; if (sli_cmd_unreg_rpi(&hw->sli, buf, SLI4_BMBX_SIZE, rnode->indicator, SLI_RSRC_FCOE_RPI, fc_id)) { rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, ocs_hw_cb_node_free, rnode); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "UNREG_RPI failed\n"); ocs_free(hw->os, buf, SLI4_BMBX_SIZE); rc = OCS_HW_RTN_ERROR; } } return rc; } /** * @ingroup node * @brief Free all remote node objects. * * @param hw Hardware context. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_node_free_all(ocs_hw_t *hw) { uint8_t *buf = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; if (!hw) { ocs_log_err(NULL, "bad parameter hw=%p\n", hw); return OCS_HW_RTN_ERROR; } /* * Check if the chip is in an error state (UE'd) before proceeding. */ if (sli_fw_error_status(&hw->sli) > 0) { ocs_log_crit(hw->os, "Chip is in an error state - reset needed\n"); return OCS_HW_RTN_ERROR; } buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_unreg_rpi(&hw->sli, buf, SLI4_BMBX_SIZE, 0xffff, SLI_RSRC_FCOE_FCFI, UINT32_MAX)) { rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, ocs_hw_cb_node_free_all, NULL); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "UNREG_RPI failed\n"); ocs_free(hw->os, buf, SLI4_BMBX_SIZE); rc = OCS_HW_RTN_ERROR; } return rc; } ocs_hw_rtn_e ocs_hw_node_group_alloc(ocs_hw_t *hw, ocs_remote_node_group_t *ngroup) { if (!hw || !ngroup) { ocs_log_err(NULL, "bad parameter hw=%p ngroup=%p\n", hw, ngroup); return OCS_HW_RTN_ERROR; } if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_RPI, &ngroup->indicator, &ngroup->index)) { ocs_log_err(hw->os, "FCOE_RPI allocation failure addr=%#x\n", ngroup->indicator); return OCS_HW_RTN_ERROR; } return OCS_HW_RTN_SUCCESS; } ocs_hw_rtn_e ocs_hw_node_group_attach(ocs_hw_t *hw, ocs_remote_node_group_t *ngroup, ocs_remote_node_t *rnode) { if (!hw || !ngroup || !rnode) { ocs_log_err(NULL, "bad parameter hw=%p ngroup=%p rnode=%p\n", hw, ngroup, rnode); return OCS_HW_RTN_ERROR; } if (rnode->attached) { ocs_log_err(hw->os, "node already attached RPI=%#x addr=%#x\n", rnode->indicator, rnode->fc_id); return OCS_HW_RTN_ERROR; } if (sli_resource_free(&hw->sli, SLI_RSRC_FCOE_RPI, rnode->indicator)) { ocs_log_err(hw->os, "FCOE_RPI free failure RPI=%#x\n", rnode->indicator); return OCS_HW_RTN_ERROR; } rnode->indicator = ngroup->indicator; rnode->index = ngroup->index; return OCS_HW_RTN_SUCCESS; } ocs_hw_rtn_e ocs_hw_node_group_free(ocs_hw_t *hw, ocs_remote_node_group_t *ngroup) { int ref; if (!hw || !ngroup) { ocs_log_err(NULL, "bad parameter hw=%p ngroup=%p\n", hw, ngroup); return OCS_HW_RTN_ERROR; } ref = ocs_atomic_read(&hw->rpi_ref[ngroup->index].rpi_count); if (ref) { /* Hmmm, the reference count is non-zero */ ocs_log_debug(hw->os, "node group reference=%d (RPI=%#x)\n", ref, ngroup->indicator); if (sli_resource_free(&hw->sli, SLI_RSRC_FCOE_RPI, ngroup->indicator)) { ocs_log_err(hw->os, "FCOE_RPI free failure RPI=%#x\n", ngroup->indicator); return OCS_HW_RTN_ERROR; } ocs_atomic_set(&hw->rpi_ref[ngroup->index].rpi_count, 0); } ngroup->indicator = UINT32_MAX; ngroup->index = UINT32_MAX; return OCS_HW_RTN_SUCCESS; } /** * @brief Initialize IO fields on each free call. * * @n @b Note: This is done on each free call (as opposed to each * alloc call) because port-owned XRIs are not * allocated with ocs_hw_io_alloc() but are freed with this * function. * * @param io Pointer to HW IO. */ static inline void ocs_hw_init_free_io(ocs_hw_io_t *io) { /* * Set io->done to NULL, to avoid any callbacks, should * a completion be received for one of these IOs */ io->done = NULL; io->abort_done = NULL; io->status_saved = 0; io->abort_in_progress = FALSE; io->port_owned_abort_count = 0; io->rnode = NULL; io->type = 0xFFFF; io->wq = NULL; io->ul_io = NULL; io->tgt_wqe_timeout = 0; } /** * @ingroup io * @brief Lockless allocate a HW IO object. * * @par Description * Assume that hw->ocs_lock is held. This function is only used if * use_dif_sec_xri workaround is being used. * * @param hw Hardware context. * * @return Returns a pointer to an object on success, or NULL on failure. */ static inline ocs_hw_io_t * _ocs_hw_io_alloc(ocs_hw_t *hw) { ocs_hw_io_t *io = NULL; if (NULL != (io = ocs_list_remove_head(&hw->io_free))) { ocs_list_add_tail(&hw->io_inuse, io); io->state = OCS_HW_IO_STATE_INUSE; io->quarantine = FALSE; io->quarantine_first_phase = TRUE; io->abort_reqtag = UINT32_MAX; ocs_ref_init(&io->ref, ocs_hw_io_free_internal, io); } else { ocs_atomic_add_return(&hw->io_alloc_failed_count, 1); } return io; } /** * @ingroup io * @brief Allocate a HW IO object. * * @par Description * @n @b Note: This function applies to non-port owned XRIs * only. * * @param hw Hardware context. * * @return Returns a pointer to an object on success, or NULL on failure. */ ocs_hw_io_t * ocs_hw_io_alloc(ocs_hw_t *hw) { ocs_hw_io_t *io = NULL; ocs_lock(&hw->io_lock); io = _ocs_hw_io_alloc(hw); ocs_unlock(&hw->io_lock); return io; } /** * @ingroup io * @brief Allocate/Activate a port owned HW IO object. * * @par Description * This function is called by the transport layer when an XRI is * allocated by the SLI-Port. This will "activate" the HW IO * associated with the XRI received from the SLI-Port to mirror * the state of the XRI. * @n @n @b Note: This function applies to port owned XRIs only. * * @param hw Hardware context. * @param io Pointer HW IO to activate/allocate. * * @return Returns a pointer to an object on success, or NULL on failure. */ ocs_hw_io_t * ocs_hw_io_activate_port_owned(ocs_hw_t *hw, ocs_hw_io_t *io) { if (ocs_ref_read_count(&io->ref) > 0) { ocs_log_err(hw->os, "Bad parameter: refcount > 0\n"); return NULL; } if (io->wq != NULL) { ocs_log_err(hw->os, "XRI %x already in use\n", io->indicator); return NULL; } ocs_ref_init(&io->ref, ocs_hw_io_free_port_owned, io); io->xbusy = TRUE; return io; } /** * @ingroup io * @brief When an IO is freed, depending on the exchange busy flag, and other * workarounds, move it to the correct list. * * @par Description * @n @b Note: Assumes that the hw->io_lock is held and the item has been removed * from the busy or wait_free list. * * @param hw Hardware context. * @param io Pointer to the IO object to move. */ static void ocs_hw_io_free_move_correct_list(ocs_hw_t *hw, ocs_hw_io_t *io) { if (io->xbusy) { /* add to wait_free list and wait for XRI_ABORTED CQEs to clean up */ ocs_list_add_tail(&hw->io_wait_free, io); io->state = OCS_HW_IO_STATE_WAIT_FREE; } else { /* IO not busy, add to free list */ ocs_list_add_tail(&hw->io_free, io); io->state = OCS_HW_IO_STATE_FREE; } /* BZ 161832 workaround */ if (hw->workaround.use_dif_sec_xri) { ocs_hw_check_sec_hio_list(hw); } } /** * @ingroup io * @brief Free a HW IO object. Perform cleanup common to * port and host-owned IOs. * * @param hw Hardware context. * @param io Pointer to the HW IO object. */ static inline void ocs_hw_io_free_common(ocs_hw_t *hw, ocs_hw_io_t *io) { /* initialize IO fields */ ocs_hw_init_free_io(io); /* Restore default SGL */ ocs_hw_io_restore_sgl(hw, io); } /** * @ingroup io * @brief Free a HW IO object associated with a port-owned XRI. * * @param arg Pointer to the HW IO object. */ static void ocs_hw_io_free_port_owned(void *arg) { ocs_hw_io_t *io = (ocs_hw_io_t *)arg; ocs_hw_t *hw = io->hw; /* * For auto xfer rdy, if the dnrx bit is set, then add it to the list of XRIs * waiting for buffers. */ if (io->auto_xfer_rdy_dnrx) { ocs_lock(&hw->io_lock); /* take a reference count because we still own the IO until the buffer is posted */ ocs_ref_init(&io->ref, ocs_hw_io_free_port_owned, io); ocs_list_add_tail(&hw->io_port_dnrx, io); ocs_unlock(&hw->io_lock); } /* perform common cleanup */ ocs_hw_io_free_common(hw, io); } /** * @ingroup io * @brief Free a previously-allocated HW IO object. Called when * IO refcount goes to zero (host-owned IOs only). * * @param arg Pointer to the HW IO object. */ static void ocs_hw_io_free_internal(void *arg) { ocs_hw_io_t *io = (ocs_hw_io_t *)arg; ocs_hw_t *hw = io->hw; /* perform common cleanup */ ocs_hw_io_free_common(hw, io); ocs_lock(&hw->io_lock); /* remove from in-use list */ ocs_list_remove(&hw->io_inuse, io); ocs_hw_io_free_move_correct_list(hw, io); ocs_unlock(&hw->io_lock); } /** * @ingroup io * @brief Free a previously-allocated HW IO object. * * @par Description * @n @b Note: This function applies to port and host owned XRIs. * * @param hw Hardware context. * @param io Pointer to the HW IO object. * * @return Returns a non-zero value if HW IO was freed, 0 if references * on the IO still exist, or a negative value if an error occurred. */ int32_t ocs_hw_io_free(ocs_hw_t *hw, ocs_hw_io_t *io) { /* just put refcount */ if (ocs_ref_read_count(&io->ref) <= 0) { ocs_log_err(hw->os, "Bad parameter: refcount <= 0 xri=%x tag=%x\n", io->indicator, io->reqtag); return -1; } return ocs_ref_put(&io->ref); /* ocs_ref_get(): ocs_hw_io_alloc() */ } /** * @ingroup io * @brief Check if given HW IO is in-use * * @par Description * This function returns TRUE if the given HW IO has been * allocated and is in-use, and FALSE otherwise. It applies to * port and host owned XRIs. * * @param hw Hardware context. * @param io Pointer to the HW IO object. * * @return TRUE if an IO is in use, or FALSE otherwise. */ uint8_t ocs_hw_io_inuse(ocs_hw_t *hw, ocs_hw_io_t *io) { return (ocs_ref_read_count(&io->ref) > 0); } /** * @brief Write a HW IO to a work queue. * * @par Description * A HW IO is written to a work queue. * * @param wq Pointer to work queue. * @param wqe Pointer to WQ entry. * * @n @b Note: Assumes the SLI-4 queue lock is held. * * @return Returns 0 on success, or a negative error code value on failure. */ static int32_t _hw_wq_write(hw_wq_t *wq, ocs_hw_wqe_t *wqe) { int32_t rc; int32_t queue_rc; /* Every so often, set the wqec bit to generate comsummed completions */ if (wq->wqec_count) { wq->wqec_count--; } if (wq->wqec_count == 0) { sli4_generic_wqe_t *genwqe = (void*)wqe->wqebuf; genwqe->wqec = 1; wq->wqec_count = wq->wqec_set_count; } /* Decrement WQ free count */ wq->free_count--; queue_rc = _sli_queue_write(&wq->hw->sli, wq->queue, wqe->wqebuf); if (queue_rc < 0) { rc = -1; } else { rc = 0; ocs_queue_history_wq(&wq->hw->q_hist, (void *) wqe->wqebuf, wq->queue->id, queue_rc); } return rc; } /** * @brief Write a HW IO to a work queue. * * @par Description * A HW IO is written to a work queue. * * @param wq Pointer to work queue. * @param wqe Pointer to WQE entry. * * @n @b Note: Takes the SLI-4 queue lock. * * @return Returns 0 on success, or a negative error code value on failure. */ int32_t hw_wq_write(hw_wq_t *wq, ocs_hw_wqe_t *wqe) { int32_t rc = 0; sli_queue_lock(wq->queue); if ( ! ocs_list_empty(&wq->pending_list)) { ocs_list_add_tail(&wq->pending_list, wqe); OCS_STAT(wq->wq_pending_count++;) while ((wq->free_count > 0) && ((wqe = ocs_list_remove_head(&wq->pending_list)) != NULL)) { rc = _hw_wq_write(wq, wqe); if (rc < 0) { break; } if (wqe->abort_wqe_submit_needed) { wqe->abort_wqe_submit_needed = 0; sli_abort_wqe(&wq->hw->sli, wqe->wqebuf, wq->hw->sli.config.wqe_size, SLI_ABORT_XRI, wqe->send_abts, wqe->id, 0, wqe->abort_reqtag, SLI4_CQ_DEFAULT ); ocs_list_add_tail(&wq->pending_list, wqe); OCS_STAT(wq->wq_pending_count++;) } } } else { if (wq->free_count > 0) { rc = _hw_wq_write(wq, wqe); } else { ocs_list_add_tail(&wq->pending_list, wqe); OCS_STAT(wq->wq_pending_count++;) } } sli_queue_unlock(wq->queue); return rc; } /** * @brief Update free count and submit any pending HW IOs * * @par Description * The WQ free count is updated, and any pending HW IOs are submitted that * will fit in the queue. * * @param wq Pointer to work queue. * @param update_free_count Value added to WQs free count. * * @return None. */ static void hw_wq_submit_pending(hw_wq_t *wq, uint32_t update_free_count) { ocs_hw_wqe_t *wqe; sli_queue_lock(wq->queue); /* Update free count with value passed in */ wq->free_count += update_free_count; while ((wq->free_count > 0) && ((wqe = ocs_list_remove_head(&wq->pending_list)) != NULL)) { _hw_wq_write(wq, wqe); if (wqe->abort_wqe_submit_needed) { wqe->abort_wqe_submit_needed = 0; sli_abort_wqe(&wq->hw->sli, wqe->wqebuf, wq->hw->sli.config.wqe_size, SLI_ABORT_XRI, wqe->send_abts, wqe->id, 0, wqe->abort_reqtag, SLI4_CQ_DEFAULT); ocs_list_add_tail(&wq->pending_list, wqe); OCS_STAT(wq->wq_pending_count++;) } } sli_queue_unlock(wq->queue); } /** * @brief Check to see if there are any BZ 161832 workaround waiting IOs * * @par Description * Checks hw->sec_hio_wait_list, if an IO is waiting for a HW IO, then try * to allocate a secondary HW io, and dispatch it. * * @n @b Note: hw->io_lock MUST be taken when called. * * @param hw pointer to HW object * * @return none */ static void ocs_hw_check_sec_hio_list(ocs_hw_t *hw) { ocs_hw_io_t *io; ocs_hw_io_t *sec_io; int rc = 0; while (!ocs_list_empty(&hw->sec_hio_wait_list)) { uint16_t flags; sec_io = _ocs_hw_io_alloc(hw); if (sec_io == NULL) { break; } io = ocs_list_remove_head(&hw->sec_hio_wait_list); ocs_list_add_tail(&hw->io_inuse, io); io->state = OCS_HW_IO_STATE_INUSE; io->sec_hio = sec_io; /* mark secondary XRI for second and subsequent data phase as quarantine */ if (io->xbusy) { sec_io->quarantine = TRUE; } flags = io->sec_iparam.fcp_tgt.flags; if (io->xbusy) { flags |= SLI4_IO_CONTINUATION; } else { flags &= ~SLI4_IO_CONTINUATION; } io->tgt_wqe_timeout = io->sec_iparam.fcp_tgt.timeout; /* Complete (continue) TRECV IO */ if (io->xbusy) { if (sli_fcp_cont_treceive64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, io->sec_iparam.fcp_tgt.offset, io->sec_len, io->indicator, io->sec_hio->indicator, io->reqtag, SLI4_CQ_DEFAULT, io->sec_iparam.fcp_tgt.ox_id, io->rnode->indicator, io->rnode, flags, io->sec_iparam.fcp_tgt.dif_oper, io->sec_iparam.fcp_tgt.blk_size, io->sec_iparam.fcp_tgt.cs_ctl, io->sec_iparam.fcp_tgt.app_id)) { ocs_log_test(hw->os, "TRECEIVE WQE error\n"); break; } } else { if (sli_fcp_treceive64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, io->sec_iparam.fcp_tgt.offset, io->sec_len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, io->sec_iparam.fcp_tgt.ox_id, io->rnode->indicator, io->rnode, flags, io->sec_iparam.fcp_tgt.dif_oper, io->sec_iparam.fcp_tgt.blk_size, io->sec_iparam.fcp_tgt.cs_ctl, io->sec_iparam.fcp_tgt.app_id)) { ocs_log_test(hw->os, "TRECEIVE WQE error\n"); break; } } if (io->wq == NULL) { io->wq = ocs_hw_queue_next_wq(hw, io); ocs_hw_assert(io->wq != NULL); } io->xbusy = TRUE; /* * Add IO to active io wqe list before submitting, in case the * wcqe processing preempts this thread. */ ocs_hw_add_io_timed_wqe(hw, io); rc = hw_wq_write(io->wq, &io->wqe); if (rc >= 0) { /* non-negative return is success */ rc = 0; } else { /* failed to write wqe, remove from active wqe list */ ocs_log_err(hw->os, "sli_queue_write failed: %d\n", rc); io->xbusy = FALSE; ocs_hw_remove_io_timed_wqe(hw, io); } } } /** * @ingroup io * @brief Send a Single Request/Response Sequence (SRRS). * * @par Description * This routine supports communication sequences consisting of a single * request and single response between two endpoints. Examples include: * - Sending an ELS request. * - Sending an ELS response - To send an ELS reponse, the caller must provide * the OX_ID from the received request. * - Sending a FC Common Transport (FC-CT) request - To send a FC-CT request, * the caller must provide the R_CTL, TYPE, and DF_CTL * values to place in the FC frame header. * . * @n @b Note: The caller is expected to provide both send and receive * buffers for requests. In the case of sending a response, no receive buffer * is necessary and the caller may pass in a NULL pointer. * * @param hw Hardware context. * @param type Type of sequence (ELS request/response, FC-CT). * @param io Previously-allocated HW IO object. * @param send DMA memory holding data to send (for example, ELS request, BLS response). * @param len Length, in bytes, of data to send. * @param receive Optional DMA memory to hold a response. * @param rnode Destination of data (that is, a remote node). * @param iparam IO parameters (ELS response and FC-CT). * @param cb Function call upon completion of sending the data (may be NULL). * @param arg Argument to pass to IO completion function. * * @return Returns 0 on success, or a non-zero on failure. */ ocs_hw_rtn_e ocs_hw_srrs_send(ocs_hw_t *hw, ocs_hw_io_type_e type, ocs_hw_io_t *io, ocs_dma_t *send, uint32_t len, ocs_dma_t *receive, ocs_remote_node_t *rnode, ocs_hw_io_param_t *iparam, ocs_hw_srrs_cb_t cb, void *arg) { sli4_sge_t *sge = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint16_t local_flags = 0; if (!hw || !io || !rnode || !iparam) { ocs_log_err(NULL, "bad parm hw=%p io=%p send=%p receive=%p rnode=%p iparam=%p\n", hw, io, send, receive, rnode, iparam); return OCS_HW_RTN_ERROR; } if (hw->state != OCS_HW_STATE_ACTIVE) { ocs_log_test(hw->os, "cannot send SRRS, HW state=%d\n", hw->state); return OCS_HW_RTN_ERROR; } if (ocs_hw_is_xri_port_owned(hw, io->indicator)) { /* We must set the XC bit for port owned XRIs */ local_flags |= SLI4_IO_CONTINUATION; } io->rnode = rnode; io->type = type; io->done = cb; io->arg = arg; sge = io->sgl->virt; /* clear both SGE */ ocs_memset(io->sgl->virt, 0, 2 * sizeof(sli4_sge_t)); if (send) { sge[0].buffer_address_high = ocs_addr32_hi(send->phys); sge[0].buffer_address_low = ocs_addr32_lo(send->phys); sge[0].sge_type = SLI4_SGE_TYPE_DATA; sge[0].buffer_length = len; } if ((OCS_HW_ELS_REQ == type) || (OCS_HW_FC_CT == type)) { sge[1].buffer_address_high = ocs_addr32_hi(receive->phys); sge[1].buffer_address_low = ocs_addr32_lo(receive->phys); sge[1].sge_type = SLI4_SGE_TYPE_DATA; sge[1].buffer_length = receive->size; sge[1].last = TRUE; } else { sge[0].last = TRUE; } switch (type) { case OCS_HW_ELS_REQ: if ( (!send) || sli_els_request64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, io->sgl, *((uint8_t *)(send->virt)), /* req_type */ len, receive->size, iparam->els.timeout, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rnode)) { ocs_log_err(hw->os, "REQ WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_ELS_RSP: if ( (!send) || sli_xmit_els_rsp64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, send, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->els.ox_id, rnode, local_flags, UINT32_MAX)) { ocs_log_err(hw->os, "RSP WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_ELS_RSP_SID: if ( (!send) || sli_xmit_els_rsp64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, send, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->els_sid.ox_id, rnode, local_flags, iparam->els_sid.s_id)) { ocs_log_err(hw->os, "RSP (SID) WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_FC_CT: if ( (!send) || sli_gen_request64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, io->sgl, len, receive->size, iparam->fc_ct.timeout, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rnode, iparam->fc_ct.r_ctl, iparam->fc_ct.type, iparam->fc_ct.df_ctl)) { ocs_log_err(hw->os, "GEN WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_FC_CT_RSP: if ( (!send) || sli_xmit_sequence64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, io->sgl, len, iparam->fc_ct_rsp.timeout, iparam->fc_ct_rsp.ox_id, io->indicator, io->reqtag, rnode, iparam->fc_ct_rsp.r_ctl, iparam->fc_ct_rsp.type, iparam->fc_ct_rsp.df_ctl)) { ocs_log_err(hw->os, "XMIT SEQ WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_BLS_ACC: case OCS_HW_BLS_RJT: { sli_bls_payload_t bls; if (OCS_HW_BLS_ACC == type) { bls.type = SLI_BLS_ACC; ocs_memcpy(&bls.u.acc, iparam->bls.payload, sizeof(bls.u.acc)); } else { bls.type = SLI_BLS_RJT; ocs_memcpy(&bls.u.rjt, iparam->bls.payload, sizeof(bls.u.rjt)); } bls.ox_id = iparam->bls.ox_id; bls.rx_id = iparam->bls.rx_id; if (sli_xmit_bls_rsp64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &bls, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rnode, UINT32_MAX)) { ocs_log_err(hw->os, "XMIT_BLS_RSP64 WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; } case OCS_HW_BLS_ACC_SID: { sli_bls_payload_t bls; bls.type = SLI_BLS_ACC; ocs_memcpy(&bls.u.acc, iparam->bls_sid.payload, sizeof(bls.u.acc)); bls.ox_id = iparam->bls_sid.ox_id; bls.rx_id = iparam->bls_sid.rx_id; if (sli_xmit_bls_rsp64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &bls, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rnode, iparam->bls_sid.s_id)) { ocs_log_err(hw->os, "XMIT_BLS_RSP64 WQE SID error\n"); rc = OCS_HW_RTN_ERROR; } break; } case OCS_HW_BCAST: if ( (!send) || sli_xmit_bcast64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, send, len, iparam->bcast.timeout, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rnode, iparam->bcast.r_ctl, iparam->bcast.type, iparam->bcast.df_ctl)) { ocs_log_err(hw->os, "XMIT_BCAST64 WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; default: ocs_log_err(hw->os, "bad SRRS type %#x\n", type); rc = OCS_HW_RTN_ERROR; } if (OCS_HW_RTN_SUCCESS == rc) { if (io->wq == NULL) { io->wq = ocs_hw_queue_next_wq(hw, io); ocs_hw_assert(io->wq != NULL); } io->xbusy = TRUE; /* * Add IO to active io wqe list before submitting, in case the * wcqe processing preempts this thread. */ OCS_STAT(io->wq->use_count++); ocs_hw_add_io_timed_wqe(hw, io); rc = hw_wq_write(io->wq, &io->wqe); if (rc >= 0) { /* non-negative return is success */ rc = 0; } else { /* failed to write wqe, remove from active wqe list */ ocs_log_err(hw->os, "sli_queue_write failed: %d\n", rc); io->xbusy = FALSE; ocs_hw_remove_io_timed_wqe(hw, io); } } return rc; } /** * @ingroup io * @brief Send a read, write, or response IO. * * @par Description * This routine supports sending a higher-level IO (for example, FCP) between two endpoints * as a target or initiator. Examples include: * - Sending read data and good response (target). * - Sending a response (target with no data or after receiving write data). * . * This routine assumes all IOs use the SGL associated with the HW IO. Prior to * calling this routine, the data should be loaded using ocs_hw_io_add_sge(). * * @param hw Hardware context. * @param type Type of IO (target read, target response, and so on). * @param io Previously-allocated HW IO object. * @param len Length, in bytes, of data to send. * @param iparam IO parameters. * @param rnode Destination of data (that is, a remote node). * @param cb Function call upon completion of sending data (may be NULL). * @param arg Argument to pass to IO completion function. * * @return Returns 0 on success, or a non-zero value on failure. * * @todo * - Support specifiying relative offset. * - Use a WQ other than 0. */ ocs_hw_rtn_e ocs_hw_io_send(ocs_hw_t *hw, ocs_hw_io_type_e type, ocs_hw_io_t *io, uint32_t len, ocs_hw_io_param_t *iparam, ocs_remote_node_t *rnode, void *cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint32_t rpi; uint8_t send_wqe = TRUE; CPUTRACE(""); if (!hw || !io || !rnode || !iparam) { ocs_log_err(NULL, "bad parm hw=%p io=%p iparam=%p rnode=%p\n", hw, io, iparam, rnode); return OCS_HW_RTN_ERROR; } if (hw->state != OCS_HW_STATE_ACTIVE) { ocs_log_err(hw->os, "cannot send IO, HW state=%d\n", hw->state); return OCS_HW_RTN_ERROR; } rpi = rnode->indicator; if (hw->workaround.use_unregistered_rpi && (rpi == UINT32_MAX)) { rpi = hw->workaround.unregistered_rid; ocs_log_test(hw->os, "using unregistered RPI: %d\n", rpi); } /* * Save state needed during later stages */ io->rnode = rnode; io->type = type; io->done = cb; io->arg = arg; /* * Format the work queue entry used to send the IO */ switch (type) { case OCS_HW_IO_INITIATOR_READ: /* * If use_dif_quarantine workaround is in effect, and dif_separates then mark the * initiator read IO for quarantine */ if (hw->workaround.use_dif_quarantine && (hw->config.dif_mode == OCS_HW_DIF_MODE_SEPARATE) && (iparam->fcp_tgt.dif_oper != OCS_HW_DIF_OPER_DISABLED)) { io->quarantine = TRUE; } ocs_hw_io_ini_sge(hw, io, iparam->fcp_ini.cmnd, iparam->fcp_ini.cmnd_size, iparam->fcp_ini.rsp); if (sli_fcp_iread64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rpi, rnode, iparam->fcp_ini.dif_oper, iparam->fcp_ini.blk_size, iparam->fcp_ini.timeout)) { ocs_log_err(hw->os, "IREAD WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_IO_INITIATOR_WRITE: ocs_hw_io_ini_sge(hw, io, iparam->fcp_ini.cmnd, iparam->fcp_ini.cmnd_size, iparam->fcp_ini.rsp); if (sli_fcp_iwrite64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, len, iparam->fcp_ini.first_burst, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rpi, rnode, iparam->fcp_ini.dif_oper, iparam->fcp_ini.blk_size, iparam->fcp_ini.timeout)) { ocs_log_err(hw->os, "IWRITE WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_IO_INITIATOR_NODATA: ocs_hw_io_ini_sge(hw, io, iparam->fcp_ini.cmnd, iparam->fcp_ini.cmnd_size, iparam->fcp_ini.rsp); if (sli_fcp_icmnd64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, rpi, rnode, iparam->fcp_ini.timeout)) { ocs_log_err(hw->os, "ICMND WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; case OCS_HW_IO_TARGET_WRITE: { uint16_t flags = iparam->fcp_tgt.flags; fcp_xfer_rdy_iu_t *xfer = io->xfer_rdy.virt; /* * Fill in the XFER_RDY for IF_TYPE 0 devices */ *((uint32_t *)xfer->fcp_data_ro) = ocs_htobe32(iparam->fcp_tgt.offset); *((uint32_t *)xfer->fcp_burst_len) = ocs_htobe32(len); *((uint32_t *)xfer->rsvd) = 0; if (io->xbusy) { flags |= SLI4_IO_CONTINUATION; } else { flags &= ~SLI4_IO_CONTINUATION; } io->tgt_wqe_timeout = iparam->fcp_tgt.timeout; /* * If use_dif_quarantine workaround is in effect, and this is a DIF enabled IO * then mark the target write IO for quarantine */ if (hw->workaround.use_dif_quarantine && (hw->config.dif_mode == OCS_HW_DIF_MODE_SEPARATE) && (iparam->fcp_tgt.dif_oper != OCS_HW_DIF_OPER_DISABLED)) { io->quarantine = TRUE; } /* * BZ 161832 Workaround: * Check for use_dif_sec_xri workaround. Note, even though the first dataphase * doesn't really need a secondary XRI, we allocate one anyway, as this avoids the * potential for deadlock where all XRI's are allocated as primaries to IOs that * are on hw->sec_hio_wait_list. If this secondary XRI is not for the first * data phase, it is marked for quarantine. */ if (hw->workaround.use_dif_sec_xri && (iparam->fcp_tgt.dif_oper != OCS_HW_DIF_OPER_DISABLED)) { /* * If we have allocated a chained SGL for skyhawk, then * we can re-use this for the sec_hio. */ if (io->ovfl_io != NULL) { io->sec_hio = io->ovfl_io; io->sec_hio->quarantine = TRUE; } else { io->sec_hio = ocs_hw_io_alloc(hw); } if (io->sec_hio == NULL) { /* Failed to allocate, so save full request context and put * this IO on the wait list */ io->sec_iparam = *iparam; io->sec_len = len; ocs_lock(&hw->io_lock); ocs_list_remove(&hw->io_inuse, io); ocs_list_add_tail(&hw->sec_hio_wait_list, io); io->state = OCS_HW_IO_STATE_WAIT_SEC_HIO; hw->sec_hio_wait_count++; ocs_unlock(&hw->io_lock); send_wqe = FALSE; /* Done */ break; } /* We quarantine the secondary IO if this is the second or subsequent data phase */ if (io->xbusy) { io->sec_hio->quarantine = TRUE; } } /* * If not the first data phase, and io->sec_hio has been allocated, then issue * FCP_CONT_TRECEIVE64 WQE, otherwise use the usual FCP_TRECEIVE64 WQE */ if (io->xbusy && (io->sec_hio != NULL)) { if (sli_fcp_cont_treceive64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, iparam->fcp_tgt.offset, len, io->indicator, io->sec_hio->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->fcp_tgt.ox_id, rpi, rnode, flags, iparam->fcp_tgt.dif_oper, iparam->fcp_tgt.blk_size, iparam->fcp_tgt.cs_ctl, iparam->fcp_tgt.app_id)) { ocs_log_err(hw->os, "TRECEIVE WQE error\n"); rc = OCS_HW_RTN_ERROR; } } else { if (sli_fcp_treceive64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, iparam->fcp_tgt.offset, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->fcp_tgt.ox_id, rpi, rnode, flags, iparam->fcp_tgt.dif_oper, iparam->fcp_tgt.blk_size, iparam->fcp_tgt.cs_ctl, iparam->fcp_tgt.app_id)) { ocs_log_err(hw->os, "TRECEIVE WQE error\n"); rc = OCS_HW_RTN_ERROR; } } break; } case OCS_HW_IO_TARGET_READ: { uint16_t flags = iparam->fcp_tgt.flags; if (io->xbusy) { flags |= SLI4_IO_CONTINUATION; } else { flags &= ~SLI4_IO_CONTINUATION; } io->tgt_wqe_timeout = iparam->fcp_tgt.timeout; if (sli_fcp_tsend64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, io->first_data_sge, iparam->fcp_tgt.offset, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->fcp_tgt.ox_id, rpi, rnode, flags, iparam->fcp_tgt.dif_oper, iparam->fcp_tgt.blk_size, iparam->fcp_tgt.cs_ctl, iparam->fcp_tgt.app_id)) { ocs_log_err(hw->os, "TSEND WQE error\n"); rc = OCS_HW_RTN_ERROR; } else if (hw->workaround.retain_tsend_io_length) { io->length = len; } break; } case OCS_HW_IO_TARGET_RSP: { uint16_t flags = iparam->fcp_tgt.flags; if (io->xbusy) { flags |= SLI4_IO_CONTINUATION; } else { flags &= ~SLI4_IO_CONTINUATION; } /* post a new auto xfer ready buffer */ if (hw->auto_xfer_rdy_enabled && io->is_port_owned) { if ((io->auto_xfer_rdy_dnrx = ocs_hw_rqpair_auto_xfer_rdy_buffer_post(hw, io, 1))) { flags |= SLI4_IO_DNRX; } } io->tgt_wqe_timeout = iparam->fcp_tgt.timeout; if (sli_fcp_trsp64_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, &io->def_sgl, len, io->indicator, io->reqtag, SLI4_CQ_DEFAULT, iparam->fcp_tgt.ox_id, rpi, rnode, flags, iparam->fcp_tgt.cs_ctl, io->is_port_owned, iparam->fcp_tgt.app_id)) { ocs_log_err(hw->os, "TRSP WQE error\n"); rc = OCS_HW_RTN_ERROR; } break; } default: ocs_log_err(hw->os, "unsupported IO type %#x\n", type); rc = OCS_HW_RTN_ERROR; } if (send_wqe && (OCS_HW_RTN_SUCCESS == rc)) { if (io->wq == NULL) { io->wq = ocs_hw_queue_next_wq(hw, io); ocs_hw_assert(io->wq != NULL); } io->xbusy = TRUE; /* * Add IO to active io wqe list before submitting, in case the * wcqe processing preempts this thread. */ OCS_STAT(hw->tcmd_wq_submit[io->wq->instance]++); OCS_STAT(io->wq->use_count++); ocs_hw_add_io_timed_wqe(hw, io); rc = hw_wq_write(io->wq, &io->wqe); if (rc >= 0) { /* non-negative return is success */ rc = 0; } else { /* failed to write wqe, remove from active wqe list */ ocs_log_err(hw->os, "sli_queue_write failed: %d\n", rc); io->xbusy = FALSE; ocs_hw_remove_io_timed_wqe(hw, io); } } return rc; } /** * @brief Send a raw frame * * @par Description * Using the SEND_FRAME_WQE, a frame consisting of header and payload is sent. * * @param hw Pointer to HW object. * @param hdr Pointer to a little endian formatted FC header. * @param sof Value to use as the frame SOF. * @param eof Value to use as the frame EOF. * @param payload Pointer to payload DMA buffer. * @param ctx Pointer to caller provided send frame context. * @param callback Callback function. * @param arg Callback function argument. * * @return Returns 0 on success, or a negative error code value on failure. */ ocs_hw_rtn_e ocs_hw_send_frame(ocs_hw_t *hw, fc_header_le_t *hdr, uint8_t sof, uint8_t eof, ocs_dma_t *payload, ocs_hw_send_frame_context_t *ctx, void (*callback)(void *arg, uint8_t *cqe, int32_t status), void *arg) { int32_t rc; ocs_hw_wqe_t *wqe; uint32_t xri; hw_wq_t *wq; wqe = &ctx->wqe; /* populate the callback object */ ctx->hw = hw; /* Fetch and populate request tag */ ctx->wqcb = ocs_hw_reqtag_alloc(hw, callback, arg); if (ctx->wqcb == NULL) { ocs_log_err(hw->os, "can't allocate request tag\n"); return OCS_HW_RTN_NO_RESOURCES; } /* Choose a work queue, first look for a class[1] wq, otherwise just use wq[0] */ wq = ocs_varray_iter_next(hw->wq_class_array[1]); if (wq == NULL) { wq = hw->hw_wq[0]; } /* Set XRI and RX_ID in the header based on which WQ, and which send_frame_io we are using */ xri = wq->send_frame_io->indicator; /* Build the send frame WQE */ rc = sli_send_frame_wqe(&hw->sli, wqe->wqebuf, hw->sli.config.wqe_size, sof, eof, (uint32_t*) hdr, payload, payload->len, OCS_HW_SEND_FRAME_TIMEOUT, xri, ctx->wqcb->instance_index); if (rc) { ocs_log_err(hw->os, "sli_send_frame_wqe failed: %d\n", rc); return OCS_HW_RTN_ERROR; } /* Write to WQ */ rc = hw_wq_write(wq, wqe); if (rc) { ocs_log_err(hw->os, "hw_wq_write failed: %d\n", rc); return OCS_HW_RTN_ERROR; } OCS_STAT(wq->use_count++); return OCS_HW_RTN_SUCCESS; } ocs_hw_rtn_e ocs_hw_io_register_sgl(ocs_hw_t *hw, ocs_hw_io_t *io, ocs_dma_t *sgl, uint32_t sgl_count) { if (sli_get_sgl_preregister(&hw->sli)) { ocs_log_err(hw->os, "can't use temporary SGL with pre-registered SGLs\n"); return OCS_HW_RTN_ERROR; } io->ovfl_sgl = sgl; io->ovfl_sgl_count = sgl_count; io->ovfl_io = NULL; return OCS_HW_RTN_SUCCESS; } static void ocs_hw_io_restore_sgl(ocs_hw_t *hw, ocs_hw_io_t *io) { /* Restore the default */ io->sgl = &io->def_sgl; io->sgl_count = io->def_sgl_count; /* * For skyhawk, we need to free the IO allocated for the chained * SGL. For all devices, clear the overflow fields on the IO. * * Note: For DIF IOs, we may be using the same XRI for the sec_hio and * the chained SGLs. If so, then we clear the ovfl_io field * when the sec_hio is freed. */ if (io->ovfl_io != NULL) { ocs_hw_io_free(hw, io->ovfl_io); io->ovfl_io = NULL; } /* Clear the overflow SGL */ io->ovfl_sgl = NULL; io->ovfl_sgl_count = 0; io->ovfl_lsp = NULL; } /** * @ingroup io * @brief Initialize the scatter gather list entries of an IO. * * @param hw Hardware context. * @param io Previously-allocated HW IO object. * @param type Type of IO (target read, target response, and so on). * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_io_init_sges(ocs_hw_t *hw, ocs_hw_io_t *io, ocs_hw_io_type_e type) { sli4_sge_t *data = NULL; uint32_t i = 0; uint32_t skips = 0; if (!hw || !io) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p\n", hw, io); return OCS_HW_RTN_ERROR; } /* Clear / reset the scatter-gather list */ io->sgl = &io->def_sgl; io->sgl_count = io->def_sgl_count; io->first_data_sge = 0; ocs_memset(io->sgl->virt, 0, 2 * sizeof(sli4_sge_t)); io->n_sge = 0; io->sge_offset = 0; io->type = type; data = io->sgl->virt; /* * Some IO types have underlying hardware requirements on the order * of SGEs. Process all special entries here. */ switch (type) { case OCS_HW_IO_INITIATOR_READ: case OCS_HW_IO_INITIATOR_WRITE: case OCS_HW_IO_INITIATOR_NODATA: /* * No skips, 2 special for initiator I/Os * The addresses and length are written later */ /* setup command pointer */ data->sge_type = SLI4_SGE_TYPE_DATA; data++; /* setup response pointer */ data->sge_type = SLI4_SGE_TYPE_DATA; if (OCS_HW_IO_INITIATOR_NODATA == type) { data->last = TRUE; } data++; io->n_sge = 2; break; case OCS_HW_IO_TARGET_WRITE: #define OCS_TARGET_WRITE_SKIPS 2 skips = OCS_TARGET_WRITE_SKIPS; /* populate host resident XFER_RDY buffer */ data->sge_type = SLI4_SGE_TYPE_DATA; data->buffer_address_high = ocs_addr32_hi(io->xfer_rdy.phys); data->buffer_address_low = ocs_addr32_lo(io->xfer_rdy.phys); data->buffer_length = io->xfer_rdy.size; data++; skips--; io->n_sge = 1; break; case OCS_HW_IO_TARGET_READ: /* * For FCP_TSEND64, the first 2 entries are SKIP SGE's */ #define OCS_TARGET_READ_SKIPS 2 skips = OCS_TARGET_READ_SKIPS; break; case OCS_HW_IO_TARGET_RSP: /* * No skips, etc. for FCP_TRSP64 */ break; default: ocs_log_err(hw->os, "unsupported IO type %#x\n", type); return OCS_HW_RTN_ERROR; } /* * Write skip entries */ for (i = 0; i < skips; i++) { data->sge_type = SLI4_SGE_TYPE_SKIP; data++; } io->n_sge += skips; /* * Set last */ data->last = TRUE; return OCS_HW_RTN_SUCCESS; } /** * @ingroup io * @brief Add a T10 PI seed scatter gather list entry. * * @param hw Hardware context. * @param io Previously-allocated HW IO object. * @param dif_info Pointer to T10 DIF fields, or NULL if no DIF. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_io_add_seed_sge(ocs_hw_t *hw, ocs_hw_io_t *io, ocs_hw_dif_info_t *dif_info) { sli4_sge_t *data = NULL; sli4_diseed_sge_t *dif_seed; /* If no dif_info, or dif_oper is disabled, then just return success */ if ((dif_info == NULL) || (dif_info->dif_oper == OCS_HW_DIF_OPER_DISABLED)) { return OCS_HW_RTN_SUCCESS; } if (!hw || !io) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p dif_info=%p\n", hw, io, dif_info); return OCS_HW_RTN_ERROR; } data = io->sgl->virt; data += io->n_sge; /* If we are doing T10 DIF add the DIF Seed SGE */ ocs_memset(data, 0, sizeof(sli4_diseed_sge_t)); dif_seed = (sli4_diseed_sge_t *)data; dif_seed->ref_tag_cmp = dif_info->ref_tag_cmp; dif_seed->ref_tag_repl = dif_info->ref_tag_repl; dif_seed->app_tag_repl = dif_info->app_tag_repl; dif_seed->repl_app_tag = dif_info->repl_app_tag; if (SLI4_IF_TYPE_LANCER_FC_ETH != hw->sli.if_type) { dif_seed->atrt = dif_info->disable_app_ref_ffff; dif_seed->at = dif_info->disable_app_ffff; } dif_seed->sge_type = SLI4_SGE_TYPE_DISEED; /* Workaround for SKH (BZ157233) */ if (((io->type == OCS_HW_IO_TARGET_WRITE) || (io->type == OCS_HW_IO_INITIATOR_READ)) && (SLI4_IF_TYPE_LANCER_FC_ETH != hw->sli.if_type) && dif_info->dif_separate) { dif_seed->sge_type = SLI4_SGE_TYPE_SKIP; } dif_seed->app_tag_cmp = dif_info->app_tag_cmp; dif_seed->dif_blk_size = dif_info->blk_size; dif_seed->auto_incr_ref_tag = dif_info->auto_incr_ref_tag; dif_seed->check_app_tag = dif_info->check_app_tag; dif_seed->check_ref_tag = dif_info->check_ref_tag; dif_seed->check_crc = dif_info->check_guard; dif_seed->new_ref_tag = dif_info->repl_ref_tag; switch(dif_info->dif_oper) { case OCS_HW_SGE_DIF_OP_IN_NODIF_OUT_CRC: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_NODIF_OUT_CRC; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_NODIF_OUT_CRC; break; case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_NODIF: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CRC_OUT_NODIF; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CRC_OUT_NODIF; break; case OCS_HW_SGE_DIF_OP_IN_NODIF_OUT_CHKSUM: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_NODIF_OUT_CHKSUM; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_NODIF_OUT_CHKSUM; break; case OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_NODIF: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_NODIF; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_NODIF; break; case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CRC: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CRC_OUT_CRC; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CRC_OUT_CRC; break; case OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_CHKSUM: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_CHKSUM; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_CHKSUM; break; case OCS_HW_SGE_DIF_OP_IN_CRC_OUT_CHKSUM: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CRC_OUT_CHKSUM; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CRC_OUT_CHKSUM; break; case OCS_HW_SGE_DIF_OP_IN_CHKSUM_OUT_CRC: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_CRC; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_CHKSUM_OUT_CRC; break; case OCS_HW_SGE_DIF_OP_IN_RAW_OUT_RAW: dif_seed->dif_op_rx = SLI4_SGE_DIF_OP_IN_RAW_OUT_RAW; dif_seed->dif_op_tx = SLI4_SGE_DIF_OP_IN_RAW_OUT_RAW; break; default: ocs_log_err(hw->os, "unsupported DIF operation %#x\n", dif_info->dif_oper); return OCS_HW_RTN_ERROR; } /* * Set last, clear previous last */ data->last = TRUE; if (io->n_sge) { data[-1].last = FALSE; } io->n_sge++; return OCS_HW_RTN_SUCCESS; } static ocs_hw_rtn_e ocs_hw_io_overflow_sgl(ocs_hw_t *hw, ocs_hw_io_t *io) { sli4_lsp_sge_t *lsp; /* fail if we're already pointing to the overflow SGL */ if (io->sgl == io->ovfl_sgl) { return OCS_HW_RTN_ERROR; } /* * For skyhawk, we can use another SGL to extend the SGL list. The * Chained entry must not be in the first 4 entries. * * Note: For DIF enabled IOs, we will use the ovfl_io for the sec_hio. */ if (sli_get_sgl_preregister(&hw->sli) && io->def_sgl_count > 4 && io->ovfl_io == NULL && ((SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) || (SLI4_IF_TYPE_BE3_SKH_VF == sli_get_if_type(&hw->sli)))) { io->ovfl_io = ocs_hw_io_alloc(hw); if (io->ovfl_io != NULL) { /* * Note: We can't call ocs_hw_io_register_sgl() here * because it checks that SGLs are not pre-registered * and for shyhawk, preregistered SGLs are required. */ io->ovfl_sgl = &io->ovfl_io->def_sgl; io->ovfl_sgl_count = io->ovfl_io->def_sgl_count; } } /* fail if we don't have an overflow SGL registered */ if (io->ovfl_io == NULL || io->ovfl_sgl == NULL) { return OCS_HW_RTN_ERROR; } /* * Overflow, we need to put a link SGE in the last location of the current SGL, after * copying the the last SGE to the overflow SGL */ ((sli4_sge_t*)io->ovfl_sgl->virt)[0] = ((sli4_sge_t*)io->sgl->virt)[io->n_sge - 1]; lsp = &((sli4_lsp_sge_t*)io->sgl->virt)[io->n_sge - 1]; ocs_memset(lsp, 0, sizeof(*lsp)); if ((SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) || (SLI4_IF_TYPE_BE3_SKH_VF == sli_get_if_type(&hw->sli))) { sli_skh_chain_sge_build(&hw->sli, (sli4_sge_t*)lsp, io->ovfl_io->indicator, 0, /* frag_num */ 0); /* offset */ } else { lsp->buffer_address_high = ocs_addr32_hi(io->ovfl_sgl->phys); lsp->buffer_address_low = ocs_addr32_lo(io->ovfl_sgl->phys); lsp->sge_type = SLI4_SGE_TYPE_LSP; lsp->last = 0; io->ovfl_lsp = lsp; io->ovfl_lsp->segment_length = sizeof(sli4_sge_t); } /* Update the current SGL pointer, and n_sgl */ io->sgl = io->ovfl_sgl; io->sgl_count = io->ovfl_sgl_count; io->n_sge = 1; return OCS_HW_RTN_SUCCESS; } /** * @ingroup io * @brief Add a scatter gather list entry to an IO. * * @param hw Hardware context. * @param io Previously-allocated HW IO object. * @param addr Physical address. * @param length Length of memory pointed to by @c addr. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_io_add_sge(ocs_hw_t *hw, ocs_hw_io_t *io, uintptr_t addr, uint32_t length) { sli4_sge_t *data = NULL; if (!hw || !io || !addr || !length) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p addr=%lx length=%u\n", hw, io, addr, length); return OCS_HW_RTN_ERROR; } if ((length != 0) && (io->n_sge + 1) > io->sgl_count) { if (ocs_hw_io_overflow_sgl(hw, io) != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "SGL full (%d)\n", io->n_sge); return OCS_HW_RTN_ERROR; } } if (length > sli_get_max_sge(&hw->sli)) { ocs_log_err(hw->os, "length of SGE %d bigger than allowed %d\n", length, sli_get_max_sge(&hw->sli)); return OCS_HW_RTN_ERROR; } data = io->sgl->virt; data += io->n_sge; data->sge_type = SLI4_SGE_TYPE_DATA; data->buffer_address_high = ocs_addr32_hi(addr); data->buffer_address_low = ocs_addr32_lo(addr); data->buffer_length = length; data->data_offset = io->sge_offset; /* * Always assume this is the last entry and mark as such. * If this is not the first entry unset the "last SGE" * indication for the previous entry */ data->last = TRUE; if (io->n_sge) { data[-1].last = FALSE; } /* Set first_data_bde if not previously set */ if (io->first_data_sge == 0) { io->first_data_sge = io->n_sge; } io->sge_offset += length; io->n_sge++; /* Update the linked segment length (only executed after overflow has begun) */ if (io->ovfl_lsp != NULL) { io->ovfl_lsp->segment_length = io->n_sge * sizeof(sli4_sge_t); } return OCS_HW_RTN_SUCCESS; } /** * @ingroup io * @brief Add a T10 DIF scatter gather list entry to an IO. * * @param hw Hardware context. * @param io Previously-allocated HW IO object. * @param addr DIF physical address. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_io_add_dif_sge(ocs_hw_t *hw, ocs_hw_io_t *io, uintptr_t addr) { sli4_dif_sge_t *data = NULL; if (!hw || !io || !addr) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p addr=%lx\n", hw, io, addr); return OCS_HW_RTN_ERROR; } if ((io->n_sge + 1) > hw->config.n_sgl) { if (ocs_hw_io_overflow_sgl(hw, io) != OCS_HW_RTN_ERROR) { ocs_log_err(hw->os, "SGL full (%d)\n", io->n_sge); return OCS_HW_RTN_ERROR; } } data = io->sgl->virt; data += io->n_sge; data->sge_type = SLI4_SGE_TYPE_DIF; /* Workaround for SKH (BZ157233) */ if (((io->type == OCS_HW_IO_TARGET_WRITE) || (io->type == OCS_HW_IO_INITIATOR_READ)) && (SLI4_IF_TYPE_LANCER_FC_ETH != hw->sli.if_type)) { data->sge_type = SLI4_SGE_TYPE_SKIP; } data->buffer_address_high = ocs_addr32_hi(addr); data->buffer_address_low = ocs_addr32_lo(addr); /* * Always assume this is the last entry and mark as such. * If this is not the first entry unset the "last SGE" * indication for the previous entry */ data->last = TRUE; if (io->n_sge) { data[-1].last = FALSE; } io->n_sge++; return OCS_HW_RTN_SUCCESS; } /** * @ingroup io * @brief Abort a previously-started IO. * * @param hw Hardware context. * @param io_to_abort The IO to abort. * @param send_abts Boolean to have the hardware automatically * generate an ABTS. * @param cb Function call upon completion of the abort (may be NULL). * @param arg Argument to pass to abort completion function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_io_abort(ocs_hw_t *hw, ocs_hw_io_t *io_to_abort, uint32_t send_abts, void *cb, void *arg) { sli4_abort_type_e atype = SLI_ABORT_MAX; uint32_t id = 0, mask = 0; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; hw_wq_callback_t *wqcb; if (!hw || !io_to_abort) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p\n", hw, io_to_abort); return OCS_HW_RTN_ERROR; } if (hw->state != OCS_HW_STATE_ACTIVE) { ocs_log_err(hw->os, "cannot send IO abort, HW state=%d\n", hw->state); return OCS_HW_RTN_ERROR; } /* take a reference on IO being aborted */ if (ocs_ref_get_unless_zero(&io_to_abort->ref) == 0) { /* command no longer active */ ocs_log_test(hw ? hw->os : NULL, "io not active xri=0x%x tag=0x%x\n", io_to_abort->indicator, io_to_abort->reqtag); return OCS_HW_RTN_IO_NOT_ACTIVE; } /* non-port owned XRI checks */ /* Must have a valid WQ reference */ if (io_to_abort->wq == NULL) { ocs_log_test(hw->os, "io_to_abort xri=0x%x not active on WQ\n", io_to_abort->indicator); ocs_ref_put(&io_to_abort->ref); /* ocs_ref_get(): same function */ return OCS_HW_RTN_IO_NOT_ACTIVE; } /* Validation checks complete; now check to see if already being aborted */ ocs_lock(&hw->io_abort_lock); if (io_to_abort->abort_in_progress) { ocs_unlock(&hw->io_abort_lock); ocs_ref_put(&io_to_abort->ref); /* ocs_ref_get(): same function */ ocs_log_debug(hw ? hw->os : NULL, "io already being aborted xri=0x%x tag=0x%x\n", io_to_abort->indicator, io_to_abort->reqtag); return OCS_HW_RTN_IO_ABORT_IN_PROGRESS; } /* * This IO is not already being aborted. Set flag so we won't try to * abort it again. After all, we only have one abort_done callback. */ io_to_abort->abort_in_progress = 1; ocs_unlock(&hw->io_abort_lock); /* * If we got here, the possibilities are: * - host owned xri * - io_to_abort->wq_index != UINT32_MAX * - submit ABORT_WQE to same WQ * - port owned xri: * - rxri: io_to_abort->wq_index == UINT32_MAX * - submit ABORT_WQE to any WQ * - non-rxri * - io_to_abort->index != UINT32_MAX * - submit ABORT_WQE to same WQ * - io_to_abort->index == UINT32_MAX * - submit ABORT_WQE to any WQ */ io_to_abort->abort_done = cb; io_to_abort->abort_arg = arg; atype = SLI_ABORT_XRI; id = io_to_abort->indicator; /* Allocate a request tag for the abort portion of this IO */ wqcb = ocs_hw_reqtag_alloc(hw, ocs_hw_wq_process_abort, io_to_abort); if (wqcb == NULL) { ocs_log_err(hw->os, "can't allocate request tag\n"); return OCS_HW_RTN_NO_RESOURCES; } io_to_abort->abort_reqtag = wqcb->instance_index; /* * If the wqe is on the pending list, then set this wqe to be * aborted when the IO's wqe is removed from the list. */ if (io_to_abort->wq != NULL) { sli_queue_lock(io_to_abort->wq->queue); if (ocs_list_on_list(&io_to_abort->wqe.link)) { io_to_abort->wqe.abort_wqe_submit_needed = 1; io_to_abort->wqe.send_abts = send_abts; io_to_abort->wqe.id = id; io_to_abort->wqe.abort_reqtag = io_to_abort->abort_reqtag; sli_queue_unlock(io_to_abort->wq->queue); return 0; } sli_queue_unlock(io_to_abort->wq->queue); } if (sli_abort_wqe(&hw->sli, io_to_abort->wqe.wqebuf, hw->sli.config.wqe_size, atype, send_abts, id, mask, io_to_abort->abort_reqtag, SLI4_CQ_DEFAULT)) { ocs_log_err(hw->os, "ABORT WQE error\n"); io_to_abort->abort_reqtag = UINT32_MAX; ocs_hw_reqtag_free(hw, wqcb); rc = OCS_HW_RTN_ERROR; } if (OCS_HW_RTN_SUCCESS == rc) { if (io_to_abort->wq == NULL) { io_to_abort->wq = ocs_hw_queue_next_wq(hw, io_to_abort); ocs_hw_assert(io_to_abort->wq != NULL); } /* ABORT_WQE does not actually utilize an XRI on the Port, * therefore, keep xbusy as-is to track the exchange's state, * not the ABORT_WQE's state */ rc = hw_wq_write(io_to_abort->wq, &io_to_abort->wqe); if (rc > 0) { /* non-negative return is success */ rc = 0; /* can't abort an abort so skip adding to timed wqe list */ } } if (OCS_HW_RTN_SUCCESS != rc) { ocs_lock(&hw->io_abort_lock); io_to_abort->abort_in_progress = 0; ocs_unlock(&hw->io_abort_lock); ocs_ref_put(&io_to_abort->ref); /* ocs_ref_get(): same function */ } return rc; } /** * @ingroup io * @brief Return the OX_ID/RX_ID of the IO. * * @param hw Hardware context. * @param io HW IO object. * * @return Returns X_ID on success, or -1 on failure. */ int32_t ocs_hw_io_get_xid(ocs_hw_t *hw, ocs_hw_io_t *io) { if (!hw || !io) { ocs_log_err(hw ? hw->os : NULL, "bad parameter hw=%p io=%p\n", hw, io); return -1; } return io->indicator; } typedef struct ocs_hw_fw_write_cb_arg { ocs_hw_fw_cb_t cb; void *arg; } ocs_hw_fw_write_cb_arg_t; typedef struct ocs_hw_sfp_cb_arg { ocs_hw_sfp_cb_t cb; void *arg; ocs_dma_t payload; } ocs_hw_sfp_cb_arg_t; typedef struct ocs_hw_temp_cb_arg { ocs_hw_temp_cb_t cb; void *arg; } ocs_hw_temp_cb_arg_t; typedef struct ocs_hw_link_stat_cb_arg { ocs_hw_link_stat_cb_t cb; void *arg; } ocs_hw_link_stat_cb_arg_t; typedef struct ocs_hw_host_stat_cb_arg { ocs_hw_host_stat_cb_t cb; void *arg; } ocs_hw_host_stat_cb_arg_t; typedef struct ocs_hw_dump_get_cb_arg { ocs_hw_dump_get_cb_t cb; void *arg; void *mbox_cmd; } ocs_hw_dump_get_cb_arg_t; typedef struct ocs_hw_dump_clear_cb_arg { ocs_hw_dump_clear_cb_t cb; void *arg; void *mbox_cmd; } ocs_hw_dump_clear_cb_arg_t; /** * @brief Write a portion of a firmware image to the device. * * @par Description * Calls the correct firmware write function based on the device type. * * @param hw Hardware context. * @param dma DMA structure containing the firmware image chunk. * @param size Size of the firmware image chunk. * @param offset Offset, in bytes, from the beginning of the firmware image. * @param last True if this is the last chunk of the image. * Causes the image to be committed to flash. * @param cb Pointer to a callback function that is called when the command completes. * The callback function prototype is * void cb(int32_t status, uint32_t bytes_written, void *arg). * @param arg Pointer to be passed to the callback function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_firmware_write(ocs_hw_t *hw, ocs_dma_t *dma, uint32_t size, uint32_t offset, int last, ocs_hw_fw_cb_t cb, void *arg) { if (hw->sli.if_type == SLI4_IF_TYPE_LANCER_FC_ETH) { return ocs_hw_firmware_write_lancer(hw, dma, size, offset, last, cb, arg); } else { /* Write firmware_write for BE3/Skyhawk not supported */ return -1; } } /** * @brief Write a portion of a firmware image to the Emulex XE201 ASIC (Lancer). * * @par Description * Creates a SLI_CONFIG mailbox command, fills it with the correct values to write a * firmware image chunk, and then sends the command with ocs_hw_command(). On completion, * the callback function ocs_hw_fw_write_cb() gets called to free the mailbox * and to signal the caller that the write has completed. * * @param hw Hardware context. * @param dma DMA structure containing the firmware image chunk. * @param size Size of the firmware image chunk. * @param offset Offset, in bytes, from the beginning of the firmware image. * @param last True if this is the last chunk of the image. Causes the image to be committed to flash. * @param cb Pointer to a callback function that is called when the command completes. * The callback function prototype is * void cb(int32_t status, uint32_t bytes_written, void *arg). * @param arg Pointer to be passed to the callback function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_firmware_write_lancer(ocs_hw_t *hw, ocs_dma_t *dma, uint32_t size, uint32_t offset, int last, ocs_hw_fw_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; uint8_t *mbxdata; ocs_hw_fw_write_cb_arg_t *cb_arg; int noc=0; /* No Commit bit - set to 1 for testing */ if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_test(hw->os, "Function only supported for I/F type 2\n"); return OCS_HW_RTN_ERROR; } mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_fw_write_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; if (sli_cmd_common_write_object(&hw->sli, mbxdata, SLI4_BMBX_SIZE, noc, last, size, offset, "/prg/", dma)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_cb_fw_write, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "COMMON_WRITE_OBJECT failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_fw_write_cb_arg_t)); } return rc; } /** * @brief Called when the WRITE OBJECT command completes. * * @par Description * Get the number of bytes actually written out of the response, free the mailbox * that was malloc'd by ocs_hw_firmware_write(), * then call the callback and pass the status and bytes written. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is void cb(int32_t status, uint32_t bytes_written). * * @return Returns 0. */ static int32_t ocs_hw_cb_fw_write(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_sli_config_t* mbox_rsp = (sli4_cmd_sli_config_t*) mqe; sli4_res_common_write_object_t* wr_obj_rsp = (sli4_res_common_write_object_t*) &(mbox_rsp->payload.embed); ocs_hw_fw_write_cb_arg_t *cb_arg = arg; uint32_t bytes_written; uint16_t mbox_status; uint32_t change_status; bytes_written = wr_obj_rsp->actual_write_length; mbox_status = mbox_rsp->hdr.status; change_status = wr_obj_rsp->change_status; ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_status) { status = mbox_status; } cb_arg->cb(status, bytes_written, change_status, cb_arg->arg); } ocs_free(hw->os, cb_arg, sizeof(ocs_hw_fw_write_cb_arg_t)); } return 0; } /** * @brief Called when the READ_TRANSCEIVER_DATA command completes. * * @par Description * Get the number of bytes read out of the response, free the mailbox that was malloc'd * by ocs_hw_get_sfp(), then call the callback and pass the status and bytes written. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is * void cb(int32_t status, uint32_t bytes_written, uint32_t *data, void *arg). * * @return Returns 0. */ static int32_t ocs_hw_cb_sfp(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_sfp_cb_arg_t *cb_arg = arg; ocs_dma_t *payload = NULL; sli4_res_common_read_transceiver_data_t* mbox_rsp = NULL; uint32_t bytes_written; if (cb_arg) { payload = &(cb_arg->payload); if (cb_arg->cb) { mbox_rsp = (sli4_res_common_read_transceiver_data_t*) payload->virt; bytes_written = mbox_rsp->hdr.response_length; if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(hw->os, status, bytes_written, mbox_rsp->page_data, cb_arg->arg); } ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_sfp_cb_arg_t)); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @ingroup io * @brief Function to retrieve the SFP information. * * @param hw Hardware context. * @param page The page of SFP data to retrieve (0xa0 or 0xa2). * @param cb Function call upon completion of sending the data (may be NULL). * @param arg Argument to pass to IO completion function. * * @return Returns OCS_HW_RTN_SUCCESS, OCS_HW_RTN_ERROR, or OCS_HW_RTN_NO_MEMORY. */ ocs_hw_rtn_e ocs_hw_get_sfp(ocs_hw_t *hw, uint16_t page, ocs_hw_sfp_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; ocs_hw_sfp_cb_arg_t *cb_arg; uint8_t *mbxdata; /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_sfp_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; /* payload holds the non-embedded portion */ if (ocs_dma_alloc(hw->os, &cb_arg->payload, sizeof(sli4_res_common_read_transceiver_data_t), OCS_MIN_DMA_ALIGNMENT)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_sfp_cb_arg_t)); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } /* Send the HW command */ if (sli_cmd_common_read_transceiver_data(&hw->sli, mbxdata, SLI4_BMBX_SIZE, page, &cb_arg->payload)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_cb_sfp, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "READ_TRANSCEIVER_DATA failed with status %d\n", rc); ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_sfp_cb_arg_t)); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); } return rc; } /** * @brief Function to retrieve the temperature information. * * @param hw Hardware context. * @param cb Function call upon completion of sending the data (may be NULL). * @param arg Argument to pass to IO completion function. * * @return Returns OCS_HW_RTN_SUCCESS, OCS_HW_RTN_ERROR, or OCS_HW_RTN_NO_MEMORY. */ ocs_hw_rtn_e ocs_hw_get_temperature(ocs_hw_t *hw, ocs_hw_temp_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; ocs_hw_temp_cb_arg_t *cb_arg; uint8_t *mbxdata; mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_temp_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; if (sli_cmd_dump_type4(&hw->sli, mbxdata, SLI4_BMBX_SIZE, SLI4_WKI_TAG_SAT_TEM)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_cb_temp, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "DUMP_TYPE4 failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_temp_cb_arg_t)); } return rc; } /** * @brief Called when the DUMP command completes. * * @par Description * Get the temperature data out of the response, free the mailbox that was malloc'd * by ocs_hw_get_temperature(), then call the callback and pass the status and data. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is defined by ocs_hw_temp_cb_t. * * @return Returns 0. */ static int32_t ocs_hw_cb_temp(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_dump4_t* mbox_rsp = (sli4_cmd_dump4_t*) mqe; ocs_hw_temp_cb_arg_t *cb_arg = arg; uint32_t curr_temp = mbox_rsp->resp_data[0]; /* word 5 */ uint32_t crit_temp_thrshld = mbox_rsp->resp_data[1]; /* word 6*/ uint32_t warn_temp_thrshld = mbox_rsp->resp_data[2]; /* word 7 */ uint32_t norm_temp_thrshld = mbox_rsp->resp_data[3]; /* word 8 */ uint32_t fan_off_thrshld = mbox_rsp->resp_data[4]; /* word 9 */ uint32_t fan_on_thrshld = mbox_rsp->resp_data[5]; /* word 10 */ if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(status, curr_temp, crit_temp_thrshld, warn_temp_thrshld, norm_temp_thrshld, fan_off_thrshld, fan_on_thrshld, cb_arg->arg); } ocs_free(hw->os, cb_arg, sizeof(ocs_hw_temp_cb_arg_t)); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @brief Function to retrieve the link statistics. * * @param hw Hardware context. * @param req_ext_counters If TRUE, then the extended counters will be requested. * @param clear_overflow_flags If TRUE, then overflow flags will be cleared. * @param clear_all_counters If TRUE, the counters will be cleared. * @param cb Function call upon completion of sending the data (may be NULL). * @param arg Argument to pass to IO completion function. * * @return Returns OCS_HW_RTN_SUCCESS, OCS_HW_RTN_ERROR, or OCS_HW_RTN_NO_MEMORY. */ ocs_hw_rtn_e ocs_hw_get_link_stats(ocs_hw_t *hw, uint8_t req_ext_counters, uint8_t clear_overflow_flags, uint8_t clear_all_counters, ocs_hw_link_stat_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; ocs_hw_link_stat_cb_arg_t *cb_arg; uint8_t *mbxdata; mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_link_stat_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; if (sli_cmd_read_link_stats(&hw->sli, mbxdata, SLI4_BMBX_SIZE, req_ext_counters, clear_overflow_flags, clear_all_counters)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_cb_link_stat, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "READ_LINK_STATS failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_link_stat_cb_arg_t)); } return rc; } /** * @brief Called when the READ_LINK_STAT command completes. * * @par Description * Get the counters out of the response, free the mailbox that was malloc'd * by ocs_hw_get_link_stats(), then call the callback and pass the status and data. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is defined by ocs_hw_link_stat_cb_t. * * @return Returns 0. */ static int32_t ocs_hw_cb_link_stat(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_read_link_stats_t* mbox_rsp = (sli4_cmd_read_link_stats_t*) mqe; ocs_hw_link_stat_cb_arg_t *cb_arg = arg; ocs_hw_link_stat_counts_t counts[OCS_HW_LINK_STAT_MAX]; uint32_t num_counters = (mbox_rsp->gec ? 20 : 13); ocs_memset(counts, 0, sizeof(ocs_hw_link_stat_counts_t) * OCS_HW_LINK_STAT_MAX); counts[OCS_HW_LINK_STAT_LINK_FAILURE_COUNT].overflow = mbox_rsp->w02of; counts[OCS_HW_LINK_STAT_LOSS_OF_SYNC_COUNT].overflow = mbox_rsp->w03of; counts[OCS_HW_LINK_STAT_LOSS_OF_SIGNAL_COUNT].overflow = mbox_rsp->w04of; counts[OCS_HW_LINK_STAT_PRIMITIVE_SEQ_COUNT].overflow = mbox_rsp->w05of; counts[OCS_HW_LINK_STAT_INVALID_XMIT_WORD_COUNT].overflow = mbox_rsp->w06of; counts[OCS_HW_LINK_STAT_CRC_COUNT].overflow = mbox_rsp->w07of; counts[OCS_HW_LINK_STAT_PRIMITIVE_SEQ_TIMEOUT_COUNT].overflow = mbox_rsp->w08of; counts[OCS_HW_LINK_STAT_ELASTIC_BUFFER_OVERRUN_COUNT].overflow = mbox_rsp->w09of; counts[OCS_HW_LINK_STAT_ARB_TIMEOUT_COUNT].overflow = mbox_rsp->w10of; counts[OCS_HW_LINK_STAT_ADVERTISED_RCV_B2B_CREDIT].overflow = mbox_rsp->w11of; counts[OCS_HW_LINK_STAT_CURR_RCV_B2B_CREDIT].overflow = mbox_rsp->w12of; counts[OCS_HW_LINK_STAT_ADVERTISED_XMIT_B2B_CREDIT].overflow = mbox_rsp->w13of; counts[OCS_HW_LINK_STAT_CURR_XMIT_B2B_CREDIT].overflow = mbox_rsp->w14of; counts[OCS_HW_LINK_STAT_RCV_EOFA_COUNT].overflow = mbox_rsp->w15of; counts[OCS_HW_LINK_STAT_RCV_EOFDTI_COUNT].overflow = mbox_rsp->w16of; counts[OCS_HW_LINK_STAT_RCV_EOFNI_COUNT].overflow = mbox_rsp->w17of; counts[OCS_HW_LINK_STAT_RCV_SOFF_COUNT].overflow = mbox_rsp->w18of; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_AER_COUNT].overflow = mbox_rsp->w19of; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_RPI_COUNT].overflow = mbox_rsp->w20of; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_XRI_COUNT].overflow = mbox_rsp->w21of; counts[OCS_HW_LINK_STAT_LINK_FAILURE_COUNT].counter = mbox_rsp->link_failure_error_count; counts[OCS_HW_LINK_STAT_LOSS_OF_SYNC_COUNT].counter = mbox_rsp->loss_of_sync_error_count; counts[OCS_HW_LINK_STAT_LOSS_OF_SIGNAL_COUNT].counter = mbox_rsp->loss_of_signal_error_count; counts[OCS_HW_LINK_STAT_PRIMITIVE_SEQ_COUNT].counter = mbox_rsp->primitive_sequence_error_count; counts[OCS_HW_LINK_STAT_INVALID_XMIT_WORD_COUNT].counter = mbox_rsp->invalid_transmission_word_error_count; counts[OCS_HW_LINK_STAT_CRC_COUNT].counter = mbox_rsp->crc_error_count; counts[OCS_HW_LINK_STAT_PRIMITIVE_SEQ_TIMEOUT_COUNT].counter = mbox_rsp->primitive_sequence_event_timeout_count; counts[OCS_HW_LINK_STAT_ELASTIC_BUFFER_OVERRUN_COUNT].counter = mbox_rsp->elastic_buffer_overrun_error_count; counts[OCS_HW_LINK_STAT_ARB_TIMEOUT_COUNT].counter = mbox_rsp->arbitration_fc_al_timout_count; counts[OCS_HW_LINK_STAT_ADVERTISED_RCV_B2B_CREDIT].counter = mbox_rsp->advertised_receive_bufftor_to_buffer_credit; counts[OCS_HW_LINK_STAT_CURR_RCV_B2B_CREDIT].counter = mbox_rsp->current_receive_buffer_to_buffer_credit; counts[OCS_HW_LINK_STAT_ADVERTISED_XMIT_B2B_CREDIT].counter = mbox_rsp->advertised_transmit_buffer_to_buffer_credit; counts[OCS_HW_LINK_STAT_CURR_XMIT_B2B_CREDIT].counter = mbox_rsp->current_transmit_buffer_to_buffer_credit; counts[OCS_HW_LINK_STAT_RCV_EOFA_COUNT].counter = mbox_rsp->received_eofa_count; counts[OCS_HW_LINK_STAT_RCV_EOFDTI_COUNT].counter = mbox_rsp->received_eofdti_count; counts[OCS_HW_LINK_STAT_RCV_EOFNI_COUNT].counter = mbox_rsp->received_eofni_count; counts[OCS_HW_LINK_STAT_RCV_SOFF_COUNT].counter = mbox_rsp->received_soff_count; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_AER_COUNT].counter = mbox_rsp->received_dropped_no_aer_count; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_RPI_COUNT].counter = mbox_rsp->received_dropped_no_available_rpi_resources_count; counts[OCS_HW_LINK_STAT_RCV_DROPPED_NO_XRI_COUNT].counter = mbox_rsp->received_dropped_no_available_xri_resources_count; if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(status, num_counters, counts, cb_arg->arg); } ocs_free(hw->os, cb_arg, sizeof(ocs_hw_link_stat_cb_arg_t)); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @brief Function to retrieve the link and host statistics. * * @param hw Hardware context. * @param cc clear counters, if TRUE all counters will be cleared. * @param cb Function call upon completion of receiving the data. * @param arg Argument to pass to pointer fc hosts statistics structure. * * @return Returns OCS_HW_RTN_SUCCESS, OCS_HW_RTN_ERROR, or OCS_HW_RTN_NO_MEMORY. */ ocs_hw_rtn_e ocs_hw_get_host_stats(ocs_hw_t *hw, uint8_t cc, ocs_hw_host_stat_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; ocs_hw_host_stat_cb_arg_t *cb_arg; uint8_t *mbxdata; mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_host_stat_cb_arg_t), 0); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; /* Send the HW command to get the host stats */ if (sli_cmd_read_status(&hw->sli, mbxdata, SLI4_BMBX_SIZE, cc)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_cb_host_stat, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "READ_HOST_STATS failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_host_stat_cb_arg_t)); } return rc; } /** * @brief Called when the READ_STATUS command completes. * * @par Description * Get the counters out of the response, free the mailbox that was malloc'd * by ocs_hw_get_host_stats(), then call the callback and pass * the status and data. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is defined by * ocs_hw_host_stat_cb_t. * * @return Returns 0. */ static int32_t ocs_hw_cb_host_stat(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_read_status_t* mbox_rsp = (sli4_cmd_read_status_t*) mqe; ocs_hw_host_stat_cb_arg_t *cb_arg = arg; ocs_hw_host_stat_counts_t counts[OCS_HW_HOST_STAT_MAX]; uint32_t num_counters = OCS_HW_HOST_STAT_MAX; ocs_memset(counts, 0, sizeof(ocs_hw_host_stat_counts_t) * OCS_HW_HOST_STAT_MAX); counts[OCS_HW_HOST_STAT_TX_KBYTE_COUNT].counter = mbox_rsp->transmit_kbyte_count; counts[OCS_HW_HOST_STAT_RX_KBYTE_COUNT].counter = mbox_rsp->receive_kbyte_count; counts[OCS_HW_HOST_STAT_TX_FRAME_COUNT].counter = mbox_rsp->transmit_frame_count; counts[OCS_HW_HOST_STAT_RX_FRAME_COUNT].counter = mbox_rsp->receive_frame_count; counts[OCS_HW_HOST_STAT_TX_SEQ_COUNT].counter = mbox_rsp->transmit_sequence_count; counts[OCS_HW_HOST_STAT_RX_SEQ_COUNT].counter = mbox_rsp->receive_sequence_count; counts[OCS_HW_HOST_STAT_TOTAL_EXCH_ORIG].counter = mbox_rsp->total_exchanges_originator; counts[OCS_HW_HOST_STAT_TOTAL_EXCH_RESP].counter = mbox_rsp->total_exchanges_responder; counts[OCS_HW_HOSY_STAT_RX_P_BSY_COUNT].counter = mbox_rsp->receive_p_bsy_count; counts[OCS_HW_HOST_STAT_RX_F_BSY_COUNT].counter = mbox_rsp->receive_f_bsy_count; counts[OCS_HW_HOST_STAT_DROP_FRM_DUE_TO_NO_RQ_BUF_COUNT].counter = mbox_rsp->dropped_frames_due_to_no_rq_buffer_count; counts[OCS_HW_HOST_STAT_EMPTY_RQ_TIMEOUT_COUNT].counter = mbox_rsp->empty_rq_timeout_count; counts[OCS_HW_HOST_STAT_DROP_FRM_DUE_TO_NO_XRI_COUNT].counter = mbox_rsp->dropped_frames_due_to_no_xri_count; counts[OCS_HW_HOST_STAT_EMPTY_XRI_POOL_COUNT].counter = mbox_rsp->empty_xri_pool_count; if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(status, num_counters, counts, cb_arg->arg); } ocs_free(hw->os, cb_arg, sizeof(ocs_hw_host_stat_cb_arg_t)); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @brief HW link configuration enum to the CLP string value mapping. * * This structure provides a mapping from the ocs_hw_linkcfg_e * enum (enum exposed for the OCS_HW_PORT_SET_LINK_CONFIG port * control) to the CLP string that is used * in the DMTF_CLP_CMD mailbox command. */ typedef struct ocs_hw_linkcfg_map_s { ocs_hw_linkcfg_e linkcfg; const char *clp_str; } ocs_hw_linkcfg_map_t; /** * @brief Mapping from the HW linkcfg enum to the CLP command value * string. */ static ocs_hw_linkcfg_map_t linkcfg_map[] = { {OCS_HW_LINKCFG_4X10G, "ELX_4x10G"}, {OCS_HW_LINKCFG_1X40G, "ELX_1x40G"}, {OCS_HW_LINKCFG_2X16G, "ELX_2x16G"}, {OCS_HW_LINKCFG_4X8G, "ELX_4x8G"}, {OCS_HW_LINKCFG_4X1G, "ELX_4x1G"}, {OCS_HW_LINKCFG_2X10G, "ELX_2x10G"}, {OCS_HW_LINKCFG_2X10G_2X8G, "ELX_2x10G_2x8G"}}; /** * @brief HW link configuration enum to Skyhawk link config ID mapping. * * This structure provides a mapping from the ocs_hw_linkcfg_e * enum (enum exposed for the OCS_HW_PORT_SET_LINK_CONFIG port * control) to the link config ID numbers used by Skyhawk */ typedef struct ocs_hw_skyhawk_linkcfg_map_s { ocs_hw_linkcfg_e linkcfg; uint32_t config_id; } ocs_hw_skyhawk_linkcfg_map_t; /** * @brief Mapping from the HW linkcfg enum to the Skyhawk link config IDs */ static ocs_hw_skyhawk_linkcfg_map_t skyhawk_linkcfg_map[] = { {OCS_HW_LINKCFG_4X10G, 0x0a}, {OCS_HW_LINKCFG_1X40G, 0x09}, }; /** * @brief Helper function for getting the HW linkcfg enum from the CLP * string value * * @param clp_str CLP string value from OEMELX_LinkConfig. * * @return Returns the HW linkcfg enum corresponding to clp_str. */ static ocs_hw_linkcfg_e ocs_hw_linkcfg_from_clp(const char *clp_str) { uint32_t i; for (i = 0; i < ARRAY_SIZE(linkcfg_map); i++) { if (ocs_strncmp(linkcfg_map[i].clp_str, clp_str, ocs_strlen(clp_str)) == 0) { return linkcfg_map[i].linkcfg; } } return OCS_HW_LINKCFG_NA; } /** * @brief Helper function for getting the CLP string value from the HW * linkcfg enum. * * @param linkcfg HW linkcfg enum. * * @return Returns the OEMELX_LinkConfig CLP string value corresponding to * given linkcfg. */ static const char * ocs_hw_clp_from_linkcfg(ocs_hw_linkcfg_e linkcfg) { uint32_t i; for (i = 0; i < ARRAY_SIZE(linkcfg_map); i++) { if (linkcfg_map[i].linkcfg == linkcfg) { return linkcfg_map[i].clp_str; } } return NULL; } /** * @brief Helper function for getting a Skyhawk link config ID from the HW * linkcfg enum. * * @param linkcfg HW linkcfg enum. * * @return Returns the Skyhawk link config ID corresponding to * given linkcfg. */ static uint32_t ocs_hw_config_id_from_linkcfg(ocs_hw_linkcfg_e linkcfg) { uint32_t i; for (i = 0; i < ARRAY_SIZE(skyhawk_linkcfg_map); i++) { if (skyhawk_linkcfg_map[i].linkcfg == linkcfg) { return skyhawk_linkcfg_map[i].config_id; } } return 0; } /** * @brief Helper function for getting the HW linkcfg enum from a * Skyhawk config ID. * * @param config_id Skyhawk link config ID. * * @return Returns the HW linkcfg enum corresponding to config_id. */ static ocs_hw_linkcfg_e ocs_hw_linkcfg_from_config_id(const uint32_t config_id) { uint32_t i; for (i = 0; i < ARRAY_SIZE(skyhawk_linkcfg_map); i++) { if (skyhawk_linkcfg_map[i].config_id == config_id) { return skyhawk_linkcfg_map[i].linkcfg; } } return OCS_HW_LINKCFG_NA; } /** * @brief Link configuration callback argument. */ typedef struct ocs_hw_linkcfg_cb_arg_s { ocs_hw_port_control_cb_t cb; void *arg; uint32_t opts; int32_t status; ocs_dma_t dma_cmd; ocs_dma_t dma_resp; uint32_t result_len; } ocs_hw_linkcfg_cb_arg_t; /** * @brief Set link configuration. * * @param hw Hardware context. * @param value Link configuration enum to which the link configuration is * set. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_linkcfg(ocs_hw_t *hw, ocs_hw_linkcfg_e value, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { if (!sli_link_is_configurable(&hw->sli)) { ocs_log_debug(hw->os, "Function not supported\n"); return OCS_HW_RTN_ERROR; } if (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli)) { return ocs_hw_set_linkcfg_lancer(hw, value, opts, cb, arg); } else if ((SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) || (SLI4_IF_TYPE_BE3_SKH_VF == sli_get_if_type(&hw->sli))) { return ocs_hw_set_linkcfg_skyhawk(hw, value, opts, cb, arg); } else { ocs_log_test(hw->os, "Function not supported for this IF_TYPE\n"); return OCS_HW_RTN_ERROR; } } /** * @brief Set link configuration for Lancer * * @param hw Hardware context. * @param value Link configuration enum to which the link configuration is * set. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_linkcfg_lancer(ocs_hw_t *hw, ocs_hw_linkcfg_e value, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { char cmd[OCS_HW_DMTF_CLP_CMD_MAX]; ocs_hw_linkcfg_cb_arg_t *cb_arg; const char *value_str = NULL; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* translate ocs_hw_linkcfg_e to CLP string */ value_str = ocs_hw_clp_from_linkcfg(value); /* allocate memory for callback argument */ cb_arg = ocs_malloc(hw->os, sizeof(*cb_arg), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); return OCS_HW_RTN_NO_MEMORY; } ocs_snprintf(cmd, OCS_HW_DMTF_CLP_CMD_MAX, "set / OEMELX_LinkConfig=%s", value_str); /* allocate DMA for command */ if (ocs_dma_alloc(hw->os, &cb_arg->dma_cmd, ocs_strlen(cmd)+1, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); return OCS_HW_RTN_NO_MEMORY; } ocs_memset(cb_arg->dma_cmd.virt, 0, ocs_strlen(cmd)+1); ocs_memcpy(cb_arg->dma_cmd.virt, cmd, ocs_strlen(cmd)); /* allocate DMA for response */ if (ocs_dma_alloc(hw->os, &cb_arg->dma_resp, OCS_HW_DMTF_CLP_RSP_MAX, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->opts = opts; rc = ocs_hw_exec_dmtf_clp_cmd(hw, &cb_arg->dma_cmd, &cb_arg->dma_resp, opts, ocs_hw_linkcfg_dmtf_clp_cb, cb_arg); if (opts == OCS_CMD_POLL || rc != OCS_HW_RTN_SUCCESS) { /* if failed, or polling, free memory here; if success and not * polling, will free in callback function */ if (rc) { ocs_log_test(hw->os, "CLP cmd=\"%s\" failed\n", (char *)cb_arg->dma_cmd.virt); } ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_dma_free(hw->os, &cb_arg->dma_resp); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } return rc; } /** * @brief Callback for ocs_hw_set_linkcfg_skyhawk * * @param hw Hardware context. * @param status Status from the RECONFIG_GET_LINK_INFO command. * @param mqe Mailbox response structure. * @param arg Pointer to a callback argument. * * @return none */ static void ocs_hw_set_active_link_config_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_linkcfg_cb_arg_t *cb_arg = (ocs_hw_linkcfg_cb_arg_t *)arg; if (status) { ocs_log_test(hw->os, "SET_RECONFIG_LINK_ID failed, status=%d\n", status); } /* invoke callback */ if (cb_arg->cb) { cb_arg->cb(status, 0, cb_arg->arg); } /* if polling, will free memory in calling function */ if (cb_arg->opts != OCS_CMD_POLL) { ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } } /** * @brief Set link configuration for a Skyhawk * * @param hw Hardware context. * @param value Link configuration enum to which the link configuration is * set. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_linkcfg_skyhawk(ocs_hw_t *hw, ocs_hw_linkcfg_e value, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { uint8_t *mbxdata; ocs_hw_linkcfg_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint32_t config_id; config_id = ocs_hw_config_id_from_linkcfg(value); if (config_id == 0) { ocs_log_test(hw->os, "Link config %d not supported by Skyhawk\n", value); return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_linkcfg_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; if (sli_cmd_common_set_reconfig_link_id(&hw->sli, mbxdata, SLI4_BMBX_SIZE, NULL, 0, config_id)) { rc = ocs_hw_command(hw, mbxdata, opts, ocs_hw_set_active_link_config_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "SET_RECONFIG_LINK_ID failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_linkcfg_cb_arg_t)); } else if (opts == OCS_CMD_POLL) { /* if we're polling we have to call the callback here. */ ocs_hw_set_active_link_config_cb(hw, 0, mbxdata, cb_arg); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_linkcfg_cb_arg_t)); } else { /* We weren't poling, so the callback got called */ ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); } return rc; } /** * @brief Get link configuration. * * @param hw Hardware context. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_get_linkcfg(ocs_hw_t *hw, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { if (!sli_link_is_configurable(&hw->sli)) { ocs_log_debug(hw->os, "Function not supported\n"); return OCS_HW_RTN_ERROR; } if (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli)) { return ocs_hw_get_linkcfg_lancer(hw, opts, cb, arg); } else if ((SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) || (SLI4_IF_TYPE_BE3_SKH_VF == sli_get_if_type(&hw->sli))) { return ocs_hw_get_linkcfg_skyhawk(hw, opts, cb, arg); } else { ocs_log_test(hw->os, "Function not supported for this IF_TYPE\n"); return OCS_HW_RTN_ERROR; } } /** * @brief Get link configuration for a Lancer * * @param hw Hardware context. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_get_linkcfg_lancer(ocs_hw_t *hw, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { char cmd[OCS_HW_DMTF_CLP_CMD_MAX]; ocs_hw_linkcfg_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* allocate memory for callback argument */ cb_arg = ocs_malloc(hw->os, sizeof(*cb_arg), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); return OCS_HW_RTN_NO_MEMORY; } ocs_snprintf(cmd, OCS_HW_DMTF_CLP_CMD_MAX, "show / OEMELX_LinkConfig"); /* allocate DMA for command */ if (ocs_dma_alloc(hw->os, &cb_arg->dma_cmd, ocs_strlen(cmd)+1, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); return OCS_HW_RTN_NO_MEMORY; } /* copy CLP command to DMA command */ ocs_memset(cb_arg->dma_cmd.virt, 0, ocs_strlen(cmd)+1); ocs_memcpy(cb_arg->dma_cmd.virt, cmd, ocs_strlen(cmd)); /* allocate DMA for response */ if (ocs_dma_alloc(hw->os, &cb_arg->dma_resp, OCS_HW_DMTF_CLP_RSP_MAX, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->opts = opts; rc = ocs_hw_exec_dmtf_clp_cmd(hw, &cb_arg->dma_cmd, &cb_arg->dma_resp, opts, ocs_hw_linkcfg_dmtf_clp_cb, cb_arg); if (opts == OCS_CMD_POLL || rc != OCS_HW_RTN_SUCCESS) { /* if failed or polling, free memory here; if not polling and success, * will free in callback function */ if (rc) { ocs_log_test(hw->os, "CLP cmd=\"%s\" failed\n", (char *)cb_arg->dma_cmd.virt); } ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_dma_free(hw->os, &cb_arg->dma_resp); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } return rc; } /** * @brief Get the link configuration callback. * * @param hw Hardware context. * @param status Status from the RECONFIG_GET_LINK_INFO command. * @param mqe Mailbox response structure. * @param arg Pointer to a callback argument. * * @return none */ static void ocs_hw_get_active_link_config_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_linkcfg_cb_arg_t *cb_arg = (ocs_hw_linkcfg_cb_arg_t *)arg; sli4_res_common_get_reconfig_link_info_t *rsp = cb_arg->dma_cmd.virt; ocs_hw_linkcfg_e value = OCS_HW_LINKCFG_NA; if (status) { ocs_log_test(hw->os, "GET_RECONFIG_LINK_INFO failed, status=%d\n", status); } else { /* Call was successful */ value = ocs_hw_linkcfg_from_config_id(rsp->active_link_config_id); } /* invoke callback */ if (cb_arg->cb) { cb_arg->cb(status, value, cb_arg->arg); } /* if polling, will free memory in calling function */ if (cb_arg->opts != OCS_CMD_POLL) { ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } } /** * @brief Get link configuration for a Skyhawk. * * @param hw Hardware context. * @param opts Mailbox command options (OCS_CMD_NOWAIT/POLL). * @param cb Callback function to invoke following mbx command. * @param arg Callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_get_linkcfg_skyhawk(ocs_hw_t *hw, uint32_t opts, ocs_hw_port_control_cb_t cb, void *arg) { uint8_t *mbxdata; ocs_hw_linkcfg_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_linkcfg_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->opts = opts; /* dma_mem holds the non-embedded portion */ if (ocs_dma_alloc(hw->os, &cb_arg->dma_cmd, sizeof(sli4_res_common_get_reconfig_link_info_t), 4)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_linkcfg_cb_arg_t)); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_common_get_reconfig_link_info(&hw->sli, mbxdata, SLI4_BMBX_SIZE, &cb_arg->dma_cmd)) { rc = ocs_hw_command(hw, mbxdata, opts, ocs_hw_get_active_link_config_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "GET_RECONFIG_LINK_INFO failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_linkcfg_cb_arg_t)); } else if (opts == OCS_CMD_POLL) { /* if we're polling we have to call the callback here. */ ocs_hw_get_active_link_config_cb(hw, 0, mbxdata, cb_arg); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_linkcfg_cb_arg_t)); } else { /* We weren't poling, so the callback got called */ ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); } return rc; } /** * @brief Sets the DIF seed value. * * @param hw Hardware context. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_dif_seed(ocs_hw_t *hw) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t buf[SLI4_BMBX_SIZE]; sli4_req_common_set_features_dif_seed_t seed_param; ocs_memset(&seed_param, 0, sizeof(seed_param)); seed_param.seed = hw->config.dif_seed; /* send set_features command */ if (sli_cmd_common_set_features(&hw->sli, buf, SLI4_BMBX_SIZE, SLI4_SET_FEATURES_DIF_SEED, 4, (uint32_t*)&seed_param)) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_err(hw->os, "ocs_hw_command returns %d\n", rc); } else { ocs_log_debug(hw->os, "DIF seed set to 0x%x\n", hw->config.dif_seed); } } else { ocs_log_err(hw->os, "sli_cmd_common_set_features failed\n"); rc = OCS_HW_RTN_ERROR; } return rc; } /** * @brief Sets the DIF mode value. * * @param hw Hardware context. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_dif_mode(ocs_hw_t *hw) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t buf[SLI4_BMBX_SIZE]; sli4_req_common_set_features_t10_pi_mem_model_t mode_param; ocs_memset(&mode_param, 0, sizeof(mode_param)); mode_param.tmm = (hw->config.dif_mode == OCS_HW_DIF_MODE_INLINE ? 0 : 1); /* send set_features command */ if (sli_cmd_common_set_features(&hw->sli, buf, SLI4_BMBX_SIZE, SLI4_SET_FEATURES_DIF_MEMORY_MODE, sizeof(mode_param), (uint32_t*)&mode_param)) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_err(hw->os, "ocs_hw_command returns %d\n", rc); } else { ocs_log_test(hw->os, "DIF mode set to %s\n", (hw->config.dif_mode == OCS_HW_DIF_MODE_INLINE ? "inline" : "separate")); } } else { ocs_log_err(hw->os, "sli_cmd_common_set_features failed\n"); rc = OCS_HW_RTN_ERROR; } return rc; } static void ocs_hw_watchdog_timer_cb(void *arg) { ocs_hw_t *hw = (ocs_hw_t *)arg; ocs_hw_config_watchdog_timer(hw); return; } static void ocs_hw_cb_cfg_watchdog(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { uint16_t timeout = hw->watchdog_timeout; if (status != 0) { ocs_log_err(hw->os, "config watchdog timer failed, rc = %d\n", status); } else { if(timeout != 0) { /* keeping callback 500ms before timeout to keep heartbeat alive */ ocs_setup_timer(hw->os, &hw->watchdog_timer, ocs_hw_watchdog_timer_cb, hw, (timeout*1000 - 500) ); }else { ocs_del_timer(&hw->watchdog_timer); } } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return; } /** * @brief Set configuration parameters for watchdog timer feature. * * @param hw Hardware context. * @param timeout Timeout for watchdog timer in seconds * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_config_watchdog_timer(ocs_hw_t *hw) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t *buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } sli4_cmd_lowlevel_set_watchdog(&hw->sli, buf, SLI4_BMBX_SIZE, hw->watchdog_timeout); rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, ocs_hw_cb_cfg_watchdog, NULL); if (rc) { ocs_free(hw->os, buf, SLI4_BMBX_SIZE); ocs_log_err(hw->os, "config watchdog timer failed, rc = %d\n", rc); } return rc; } /** * @brief Set configuration parameters for auto-generate xfer_rdy T10 PI feature. * * @param hw Hardware context. * @param buf Pointer to a mailbox buffer area. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_config_auto_xfer_rdy_t10pi(ocs_hw_t *hw, uint8_t *buf) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; sli4_req_common_set_features_xfer_rdy_t10pi_t param; ocs_memset(¶m, 0, sizeof(param)); param.rtc = (hw->config.auto_xfer_rdy_ref_tag_is_lba ? 0 : 1); param.atv = (hw->config.auto_xfer_rdy_app_tag_valid ? 1 : 0); param.tmm = ((hw->config.dif_mode == OCS_HW_DIF_MODE_INLINE) ? 0 : 1); param.app_tag = hw->config.auto_xfer_rdy_app_tag_value; param.blk_size = hw->config.auto_xfer_rdy_blk_size_chip; switch (hw->config.auto_xfer_rdy_p_type) { case 1: param.p_type = 0; break; case 3: param.p_type = 2; break; default: ocs_log_err(hw->os, "unsupported p_type %d\n", hw->config.auto_xfer_rdy_p_type); return OCS_HW_RTN_ERROR; } /* build the set_features command */ sli_cmd_common_set_features(&hw->sli, buf, SLI4_BMBX_SIZE, SLI4_SET_FEATURES_SET_CONFIG_AUTO_XFER_RDY_T10PI, sizeof(param), ¶m); rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_err(hw->os, "ocs_hw_command returns %d\n", rc); } else { ocs_log_test(hw->os, "Auto XFER RDY T10 PI configured rtc:%d atv:%d p_type:%d app_tag:%x blk_size:%d\n", param.rtc, param.atv, param.p_type, param.app_tag, param.blk_size); } return rc; } /** * @brief enable sli port health check * * @param hw Hardware context. * @param buf Pointer to a mailbox buffer area. * @param query current status of the health check feature enabled/disabled * @param enable if 1: enable 0: disable * @param buf Pointer to a mailbox buffer area. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_config_sli_port_health_check(ocs_hw_t *hw, uint8_t query, uint8_t enable) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t buf[SLI4_BMBX_SIZE]; sli4_req_common_set_features_health_check_t param; ocs_memset(¶m, 0, sizeof(param)); param.hck = enable; param.qry = query; /* build the set_features command */ sli_cmd_common_set_features(&hw->sli, buf, SLI4_BMBX_SIZE, SLI4_SET_FEATURES_SLI_PORT_HEALTH_CHECK, sizeof(param), ¶m); rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_err(hw->os, "ocs_hw_command returns %d\n", rc); } else { ocs_log_test(hw->os, "SLI Port Health Check is enabled \n"); } return rc; } /** * @brief Set FTD transfer hint feature * * @param hw Hardware context. * @param fdt_xfer_hint size in bytes where read requests are segmented. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_config_set_fdt_xfer_hint(ocs_hw_t *hw, uint32_t fdt_xfer_hint) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t buf[SLI4_BMBX_SIZE]; sli4_req_common_set_features_set_fdt_xfer_hint_t param; ocs_memset(¶m, 0, sizeof(param)); param.fdt_xfer_hint = fdt_xfer_hint; /* build the set_features command */ sli_cmd_common_set_features(&hw->sli, buf, SLI4_BMBX_SIZE, SLI4_SET_FEATURES_SET_FTD_XFER_HINT, sizeof(param), ¶m); rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_warn(hw->os, "set FDT hint %d failed: %d\n", fdt_xfer_hint, rc); } else { ocs_log_debug(hw->os, "Set FTD transfer hint to %d\n", param.fdt_xfer_hint); } return rc; } /** * @brief Get the link configuration callback. * * @param hw Hardware context. * @param status Status from the DMTF CLP command. * @param result_len Length, in bytes, of the DMTF CLP result. * @param arg Pointer to a callback argument. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static void ocs_hw_linkcfg_dmtf_clp_cb(ocs_hw_t *hw, int32_t status, uint32_t result_len, void *arg) { int32_t rval; char retdata_str[64]; ocs_hw_linkcfg_cb_arg_t *cb_arg = (ocs_hw_linkcfg_cb_arg_t *)arg; ocs_hw_linkcfg_e linkcfg = OCS_HW_LINKCFG_NA; if (status) { ocs_log_test(hw->os, "CLP cmd failed, status=%d\n", status); } else { /* parse CLP response to get return data */ rval = ocs_hw_clp_resp_get_value(hw, "retdata", retdata_str, sizeof(retdata_str), cb_arg->dma_resp.virt, result_len); if (rval <= 0) { ocs_log_err(hw->os, "failed to get retdata %d\n", result_len); } else { /* translate string into hw enum */ linkcfg = ocs_hw_linkcfg_from_clp(retdata_str); } } /* invoke callback */ if (cb_arg->cb) { cb_arg->cb(status, linkcfg, cb_arg->arg); } /* if polling, will free memory in calling function */ if (cb_arg->opts != OCS_CMD_POLL) { ocs_dma_free(hw->os, &cb_arg->dma_cmd); ocs_dma_free(hw->os, &cb_arg->dma_resp); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } } /** * @brief Set the Lancer dump location * @par Description * This function tells a Lancer chip to use a specific DMA * buffer as a dump location rather than the internal flash. * * @param hw Hardware context. * @param num_buffers The number of DMA buffers to hold the dump (1..n). * @param dump_buffers DMA buffers to hold the dump. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ ocs_hw_rtn_e ocs_hw_set_dump_location(ocs_hw_t *hw, uint32_t num_buffers, ocs_dma_t *dump_buffers, uint8_t fdb) { uint8_t bus, dev, func; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint8_t buf[SLI4_BMBX_SIZE]; /* * Make sure the FW is new enough to support this command. If the FW * is too old, the FW will UE. */ if (hw->workaround.disable_dump_loc) { ocs_log_test(hw->os, "FW version is too old for this feature\n"); return OCS_HW_RTN_ERROR; } /* This command is only valid for physical port 0 */ ocs_get_bus_dev_func(hw->os, &bus, &dev, &func); if (fdb == 0 && func != 0) { ocs_log_test(hw->os, "function only valid for pci function 0, %d passed\n", func); return OCS_HW_RTN_ERROR; } /* * If a single buffer is used, then it may be passed as is to the chip. For multiple buffers, * We must allocate a SGL list and then pass the address of the list to the chip. */ if (num_buffers > 1) { uint32_t sge_size = num_buffers * sizeof(sli4_sge_t); sli4_sge_t *sge; uint32_t i; if (hw->dump_sges.size < sge_size) { ocs_dma_free(hw->os, &hw->dump_sges); if (ocs_dma_alloc(hw->os, &hw->dump_sges, sge_size, OCS_MIN_DMA_ALIGNMENT)) { ocs_log_err(hw->os, "SGE DMA allocation failed\n"); return OCS_HW_RTN_NO_MEMORY; } } /* build the SGE list */ ocs_memset(hw->dump_sges.virt, 0, hw->dump_sges.size); hw->dump_sges.len = sge_size; sge = hw->dump_sges.virt; for (i = 0; i < num_buffers; i++) { sge[i].buffer_address_high = ocs_addr32_hi(dump_buffers[i].phys); sge[i].buffer_address_low = ocs_addr32_lo(dump_buffers[i].phys); sge[i].last = (i == num_buffers - 1 ? 1 : 0); sge[i].buffer_length = dump_buffers[i].size; } rc = sli_cmd_common_set_dump_location(&hw->sli, (void *)buf, SLI4_BMBX_SIZE, FALSE, TRUE, &hw->dump_sges, fdb); } else { dump_buffers->len = dump_buffers->size; rc = sli_cmd_common_set_dump_location(&hw->sli, (void *)buf, SLI4_BMBX_SIZE, FALSE, FALSE, dump_buffers, fdb); } if (rc) { rc = ocs_hw_command(hw, buf, OCS_CMD_POLL, NULL, NULL); if (rc) { ocs_log_err(hw->os, "ocs_hw_command returns %d\n", rc); } } else { ocs_log_err(hw->os, "sli_cmd_common_set_dump_location failed\n"); rc = OCS_HW_RTN_ERROR; } return rc; } /** * @brief Set the Ethernet license. * * @par Description * This function sends the appropriate mailbox command (DMTF * CLP) to set the Ethernet license to the given license value. * Since it is used during the time of ocs_hw_init(), the mailbox * command is sent via polling (the BMBX route). * * @param hw Hardware context. * @param license 32-bit license value. * * @return Returns OCS_HW_RTN_SUCCESS on success. */ static ocs_hw_rtn_e ocs_hw_set_eth_license(ocs_hw_t *hw, uint32_t license) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; char cmd[OCS_HW_DMTF_CLP_CMD_MAX]; ocs_dma_t dma_cmd; ocs_dma_t dma_resp; /* only for lancer right now */ if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_test(hw->os, "Function only supported for I/F type 2\n"); return OCS_HW_RTN_ERROR; } ocs_snprintf(cmd, OCS_HW_DMTF_CLP_CMD_MAX, "set / OEMELX_Ethernet_License=%X", license); /* allocate DMA for command */ if (ocs_dma_alloc(hw->os, &dma_cmd, ocs_strlen(cmd)+1, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); return OCS_HW_RTN_NO_MEMORY; } ocs_memset(dma_cmd.virt, 0, ocs_strlen(cmd)+1); ocs_memcpy(dma_cmd.virt, cmd, ocs_strlen(cmd)); /* allocate DMA for response */ if (ocs_dma_alloc(hw->os, &dma_resp, OCS_HW_DMTF_CLP_RSP_MAX, 4096)) { ocs_log_err(hw->os, "malloc failed\n"); ocs_dma_free(hw->os, &dma_cmd); return OCS_HW_RTN_NO_MEMORY; } /* send DMTF CLP command mbx and poll */ if (ocs_hw_exec_dmtf_clp_cmd(hw, &dma_cmd, &dma_resp, OCS_CMD_POLL, NULL, NULL)) { ocs_log_err(hw->os, "CLP cmd=\"%s\" failed\n", (char *)dma_cmd.virt); rc = OCS_HW_RTN_ERROR; } ocs_dma_free(hw->os, &dma_cmd); ocs_dma_free(hw->os, &dma_resp); return rc; } /** * @brief Callback argument structure for the DMTF CLP commands. */ typedef struct ocs_hw_clp_cb_arg_s { ocs_hw_dmtf_clp_cb_t cb; ocs_dma_t *dma_resp; int32_t status; uint32_t opts; void *arg; } ocs_hw_clp_cb_arg_t; /** * @brief Execute the DMTF CLP command. * * @param hw Hardware context. * @param dma_cmd DMA buffer containing the CLP command. * @param dma_resp DMA buffer that will contain the response (if successful). * @param opts Mailbox command options (such as OCS_CMD_NOWAIT and POLL). * @param cb Callback function. * @param arg Callback argument. * * @return Returns the number of bytes written to the response * buffer on success, or a negative value if failed. */ static ocs_hw_rtn_e ocs_hw_exec_dmtf_clp_cmd(ocs_hw_t *hw, ocs_dma_t *dma_cmd, ocs_dma_t *dma_resp, uint32_t opts, ocs_hw_dmtf_clp_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; ocs_hw_clp_cb_arg_t *cb_arg; uint8_t *mbxdata; /* allocate DMA for mailbox */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* allocate memory for callback argument */ cb_arg = ocs_malloc(hw->os, sizeof(*cb_arg), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->dma_resp = dma_resp; cb_arg->opts = opts; /* Send the HW command */ if (sli_cmd_dmtf_exec_clp_cmd(&hw->sli, mbxdata, SLI4_BMBX_SIZE, dma_cmd, dma_resp)) { rc = ocs_hw_command(hw, mbxdata, opts, ocs_hw_dmtf_clp_cb, cb_arg); if (opts == OCS_CMD_POLL && rc == OCS_HW_RTN_SUCCESS) { /* if we're polling, copy response and invoke callback to * parse result */ ocs_memcpy(mbxdata, hw->sli.bmbx.virt, SLI4_BMBX_SIZE); ocs_hw_dmtf_clp_cb(hw, 0, mbxdata, cb_arg); /* set rc to resulting or "parsed" status */ rc = cb_arg->status; } /* if failed, or polling, free memory here */ if (opts == OCS_CMD_POLL || rc != OCS_HW_RTN_SUCCESS) { if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "ocs_hw_command failed\n"); } ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } } else { ocs_log_test(hw->os, "sli_cmd_dmtf_exec_clp_cmd failed\n"); rc = OCS_HW_RTN_ERROR; ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); } return rc; } /** * @brief Called when the DMTF CLP command completes. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback argument. * * @return None. * */ static void ocs_hw_dmtf_clp_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { int32_t cb_status = 0; sli4_cmd_sli_config_t* mbox_rsp = (sli4_cmd_sli_config_t*) mqe; sli4_res_dmtf_exec_clp_cmd_t *clp_rsp = (sli4_res_dmtf_exec_clp_cmd_t *) mbox_rsp->payload.embed; ocs_hw_clp_cb_arg_t *cb_arg = arg; uint32_t result_len = 0; int32_t stat_len; char stat_str[8]; /* there are several status codes here, check them all and condense * into a single callback status */ if (status || mbox_rsp->hdr.status || clp_rsp->clp_status) { ocs_log_debug(hw->os, "status=x%x/x%x/x%x addl=x%x clp=x%x detail=x%x\n", status, mbox_rsp->hdr.status, clp_rsp->hdr.status, clp_rsp->hdr.additional_status, clp_rsp->clp_status, clp_rsp->clp_detailed_status); if (status) { cb_status = status; } else if (mbox_rsp->hdr.status) { cb_status = mbox_rsp->hdr.status; } else { cb_status = clp_rsp->clp_status; } } else { result_len = clp_rsp->resp_length; } if (cb_status) { goto ocs_hw_cb_dmtf_clp_done; } if ((result_len == 0) || (cb_arg->dma_resp->size < result_len)) { ocs_log_test(hw->os, "Invalid response length: resp_len=%zu result len=%d\n", cb_arg->dma_resp->size, result_len); cb_status = -1; goto ocs_hw_cb_dmtf_clp_done; } /* parse CLP response to get status */ stat_len = ocs_hw_clp_resp_get_value(hw, "status", stat_str, sizeof(stat_str), cb_arg->dma_resp->virt, result_len); if (stat_len <= 0) { ocs_log_test(hw->os, "failed to get status %d\n", stat_len); cb_status = -1; goto ocs_hw_cb_dmtf_clp_done; } if (ocs_strcmp(stat_str, "0") != 0) { ocs_log_test(hw->os, "CLP status indicates failure=%s\n", stat_str); cb_status = -1; goto ocs_hw_cb_dmtf_clp_done; } ocs_hw_cb_dmtf_clp_done: /* save status in cb_arg for callers with NULL cb's + polling */ cb_arg->status = cb_status; if (cb_arg->cb) { cb_arg->cb(hw, cb_status, result_len, cb_arg->arg); } /* if polling, caller will free memory */ if (cb_arg->opts != OCS_CMD_POLL) { ocs_free(hw->os, cb_arg, sizeof(*cb_arg)); ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); } } /** * @brief Parse the CLP result and get the value corresponding to the given * keyword. * * @param hw Hardware context. * @param keyword CLP keyword for which the value is returned. * @param value Location to which the resulting value is copied. * @param value_len Length of the value parameter. * @param resp Pointer to the response buffer that is searched * for the keyword and value. * @param resp_len Length of response buffer passed in. * * @return Returns the number of bytes written to the value * buffer on success, or a negative vaue on failure. */ static int32_t ocs_hw_clp_resp_get_value(ocs_hw_t *hw, const char *keyword, char *value, uint32_t value_len, const char *resp, uint32_t resp_len) { char *start = NULL; char *end = NULL; /* look for specified keyword in string */ start = ocs_strstr(resp, keyword); if (start == NULL) { ocs_log_test(hw->os, "could not find keyword=%s in CLP response\n", keyword); return -1; } /* now look for '=' and go one past */ start = ocs_strchr(start, '='); if (start == NULL) { ocs_log_test(hw->os, "could not find \'=\' in CLP response for keyword=%s\n", keyword); return -1; } start++; /* \r\n terminates value */ end = ocs_strstr(start, "\r\n"); if (end == NULL) { ocs_log_test(hw->os, "could not find \\r\\n for keyword=%s in CLP response\n", keyword); return -1; } /* make sure given result array is big enough */ if ((end - start + 1) > value_len) { ocs_log_test(hw->os, "value len=%d not large enough for actual=%ld\n", value_len, (end-start)); return -1; } ocs_strncpy(value, start, (end - start)); value[end-start] = '\0'; return (end-start+1); } /** * @brief Cause chip to enter an unrecoverable error state. * * @par Description * Cause chip to enter an unrecoverable error state. This is * used when detecting unexpected FW behavior so that the FW can be * hwted from the driver as soon as the error is detected. * * @param hw Hardware context. * @param dump Generate dump as part of reset. * * @return Returns 0 on success, or a non-zero value on failure. * */ ocs_hw_rtn_e ocs_hw_raise_ue(ocs_hw_t *hw, uint8_t dump) { ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; if (sli_raise_ue(&hw->sli, dump) != 0) { rc = OCS_HW_RTN_ERROR; } else { if (hw->state != OCS_HW_STATE_UNINITIALIZED) { hw->state = OCS_HW_STATE_QUEUES_ALLOCATED; } } return rc; } /** * @brief Called when the OBJECT_GET command completes. * * @par Description * Get the number of bytes actually written out of the response, free the mailbox * that was malloc'd by ocs_hw_dump_get(), then call the callback * and pass the status and bytes read. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is void cb(int32_t status, uint32_t bytes_read). * * @return Returns 0. */ static int32_t ocs_hw_cb_dump_get(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_sli_config_t* mbox_rsp = (sli4_cmd_sli_config_t*) mqe; sli4_res_common_read_object_t* rd_obj_rsp = (sli4_res_common_read_object_t*) mbox_rsp->payload.embed; ocs_hw_dump_get_cb_arg_t *cb_arg = arg; uint32_t bytes_read; uint8_t eof; bytes_read = rd_obj_rsp->actual_read_length; eof = rd_obj_rsp->eof; if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(status, bytes_read, eof, cb_arg->arg); } ocs_free(hw->os, cb_arg->mbox_cmd, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_dump_get_cb_arg_t)); } return 0; } /** * @brief Read a dump image to the host. * * @par Description * Creates a SLI_CONFIG mailbox command, fills in the correct values to read a * dump image chunk, then sends the command with the ocs_hw_command(). On completion, * the callback function ocs_hw_cb_dump_get() gets called to free the mailbox * and signal the caller that the read has completed. * * @param hw Hardware context. * @param dma DMA structure to transfer the dump chunk into. * @param size Size of the dump chunk. * @param offset Offset, in bytes, from the beginning of the dump. * @param cb Pointer to a callback function that is called when the command completes. * The callback function prototype is * void cb(int32_t status, uint32_t bytes_read, uint8_t eof, void *arg). * @param arg Pointer to be passed to the callback function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_dump_get(ocs_hw_t *hw, ocs_dma_t *dma, uint32_t size, uint32_t offset, ocs_hw_dump_get_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; uint8_t *mbxdata; ocs_hw_dump_get_cb_arg_t *cb_arg; uint32_t opts = (hw->state == OCS_HW_STATE_ACTIVE ? OCS_CMD_NOWAIT : OCS_CMD_POLL); if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_test(hw->os, "Function only supported for I/F type 2\n"); return OCS_HW_RTN_ERROR; } if (1 != sli_dump_is_present(&hw->sli)) { ocs_log_test(hw->os, "No dump is present\n"); return OCS_HW_RTN_ERROR; } if (1 == sli_reset_required(&hw->sli)) { ocs_log_test(hw->os, "device reset required\n"); return OCS_HW_RTN_ERROR; } mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_dump_get_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->mbox_cmd = mbxdata; if (sli_cmd_common_read_object(&hw->sli, mbxdata, SLI4_BMBX_SIZE, size, offset, "/dbg/dump.bin", dma)) { rc = ocs_hw_command(hw, mbxdata, opts, ocs_hw_cb_dump_get, cb_arg); if (rc == 0 && opts == OCS_CMD_POLL) { ocs_memcpy(mbxdata, hw->sli.bmbx.virt, SLI4_BMBX_SIZE); rc = ocs_hw_cb_dump_get(hw, 0, mbxdata, cb_arg); } } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "COMMON_READ_OBJECT failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_dump_get_cb_arg_t)); } return rc; } /** * @brief Called when the OBJECT_DELETE command completes. * * @par Description * Free the mailbox that was malloc'd * by ocs_hw_dump_clear(), then call the callback and pass the status. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * The callback function prototype is void cb(int32_t status, void *arg). * * @return Returns 0. */ static int32_t ocs_hw_cb_dump_clear(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_dump_clear_cb_arg_t *cb_arg = arg; sli4_cmd_sli_config_t* mbox_rsp = (sli4_cmd_sli_config_t*) mqe; if (cb_arg) { if (cb_arg->cb) { if ((status == 0) && mbox_rsp->hdr.status) { status = mbox_rsp->hdr.status; } cb_arg->cb(status, cb_arg->arg); } ocs_free(hw->os, cb_arg->mbox_cmd, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_dump_clear_cb_arg_t)); } return 0; } /** * @brief Clear a dump image from the device. * * @par Description * Creates a SLI_CONFIG mailbox command, fills it with the correct values to clear * the dump, then sends the command with ocs_hw_command(). On completion, * the callback function ocs_hw_cb_dump_clear() gets called to free the mailbox * and to signal the caller that the write has completed. * * @param hw Hardware context. * @param cb Pointer to a callback function that is called when the command completes. * The callback function prototype is * void cb(int32_t status, uint32_t bytes_written, void *arg). * @param arg Pointer to be passed to the callback function. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_dump_clear(ocs_hw_t *hw, ocs_hw_dump_clear_cb_t cb, void *arg) { ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; uint8_t *mbxdata; ocs_hw_dump_clear_cb_arg_t *cb_arg; uint32_t opts = (hw->state == OCS_HW_STATE_ACTIVE ? OCS_CMD_NOWAIT : OCS_CMD_POLL); if (SLI4_IF_TYPE_LANCER_FC_ETH != sli_get_if_type(&hw->sli)) { ocs_log_test(hw->os, "Function only supported for I/F type 2\n"); return OCS_HW_RTN_ERROR; } mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_dump_clear_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = arg; cb_arg->mbox_cmd = mbxdata; if (sli_cmd_common_delete_object(&hw->sli, mbxdata, SLI4_BMBX_SIZE, "/dbg/dump.bin")) { rc = ocs_hw_command(hw, mbxdata, opts, ocs_hw_cb_dump_clear, cb_arg); if (rc == 0 && opts == OCS_CMD_POLL) { ocs_memcpy(mbxdata, hw->sli.bmbx.virt, SLI4_BMBX_SIZE); rc = ocs_hw_cb_dump_clear(hw, 0, mbxdata, cb_arg); } } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "COMMON_DELETE_OBJECT failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_dump_clear_cb_arg_t)); } return rc; } typedef struct ocs_hw_get_port_protocol_cb_arg_s { ocs_get_port_protocol_cb_t cb; void *arg; uint32_t pci_func; ocs_dma_t payload; } ocs_hw_get_port_protocol_cb_arg_t; /** * @brief Called for the completion of get_port_profile for a * user request. * * @param hw Hardware context. * @param status The status from the MQE. * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_get_port_protocol_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_get_port_protocol_cb_arg_t *cb_arg = arg; ocs_dma_t *payload = &(cb_arg->payload); sli4_res_common_get_profile_config_t* response = (sli4_res_common_get_profile_config_t*) payload->virt; ocs_hw_port_protocol_e port_protocol; int num_descriptors; sli4_resource_descriptor_v1_t *desc_p; sli4_pcie_resource_descriptor_v1_t *pcie_desc_p; int i; port_protocol = OCS_HW_PORT_PROTOCOL_OTHER; num_descriptors = response->desc_count; desc_p = (sli4_resource_descriptor_v1_t *)response->desc; for (i=0; idescriptor_type == SLI4_RESOURCE_DESCRIPTOR_TYPE_PCIE) { pcie_desc_p = (sli4_pcie_resource_descriptor_v1_t*) desc_p; if (pcie_desc_p->pf_number == cb_arg->pci_func) { switch(pcie_desc_p->pf_type) { case 0x02: port_protocol = OCS_HW_PORT_PROTOCOL_ISCSI; break; case 0x04: port_protocol = OCS_HW_PORT_PROTOCOL_FCOE; break; case 0x10: port_protocol = OCS_HW_PORT_PROTOCOL_FC; break; default: port_protocol = OCS_HW_PORT_PROTOCOL_OTHER; break; } } } desc_p = (sli4_resource_descriptor_v1_t *) ((uint8_t *)desc_p + desc_p->descriptor_length); } if (cb_arg->cb) { cb_arg->cb(status, port_protocol, cb_arg->arg); } ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_port_protocol_cb_arg_t)); ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @ingroup io * @brief Get the current port protocol. * @par Description * Issues a SLI4 COMMON_GET_PROFILE_CONFIG mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param pci_func PCI function to query for current protocol. * @param cb Callback function to be called when the command completes. * @param ul_arg An argument that is passed to the callback function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ ocs_hw_rtn_e ocs_hw_get_port_protocol(ocs_hw_t *hw, uint32_t pci_func, ocs_get_port_protocol_cb_t cb, void* ul_arg) { uint8_t *mbxdata; ocs_hw_get_port_protocol_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* Only supported on Skyhawk */ if (sli_get_if_type(&hw->sli) != SLI4_IF_TYPE_BE3_SKH_PF) { return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_get_port_protocol_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; cb_arg->pci_func = pci_func; /* dma_mem holds the non-embedded portion */ if (ocs_dma_alloc(hw->os, &cb_arg->payload, 4096, 4)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_port_protocol_cb_arg_t)); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_common_get_profile_config(&hw->sli, mbxdata, SLI4_BMBX_SIZE, &cb_arg->payload)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_get_port_protocol_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "GET_PROFILE_CONFIG failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_fw_write_cb_arg_t)); ocs_dma_free(hw->os, &cb_arg->payload); } return rc; } typedef struct ocs_hw_set_port_protocol_cb_arg_s { ocs_set_port_protocol_cb_t cb; void *arg; ocs_dma_t payload; uint32_t new_protocol; uint32_t pci_func; } ocs_hw_set_port_protocol_cb_arg_t; /** * @brief Called for the completion of set_port_profile for a * user request. * * @par Description * This is the second of two callbacks for the set_port_protocol * function. The set operation is a read-modify-write. This * callback is called when the write (SET_PROFILE_CONFIG) * completes. * * @param hw Hardware context. * @param status The status from the MQE. * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return 0 on success, non-zero otherwise */ static int32_t ocs_hw_set_port_protocol_cb2(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_set_port_protocol_cb_arg_t *cb_arg = arg; if (cb_arg->cb) { cb_arg->cb( status, cb_arg->arg); } ocs_dma_free(hw->os, &(cb_arg->payload)); ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, arg, sizeof(ocs_hw_set_port_protocol_cb_arg_t)); return 0; } /** * @brief Called for the completion of set_port_profile for a * user request. * * @par Description * This is the first of two callbacks for the set_port_protocol * function. The set operation is a read-modify-write. This * callback is called when the read completes * (GET_PROFILE_CONFG). It will updated the resource * descriptors, then queue the write (SET_PROFILE_CONFIG). * * On entry there are three memory areas that were allocated by * ocs_hw_set_port_protocol. If a failure is detected in this * function those need to be freed. If this function succeeds * it allocates three more areas. * * @param hw Hardware context. * @param status The status from the MQE * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value otherwise. */ static int32_t ocs_hw_set_port_protocol_cb1(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_set_port_protocol_cb_arg_t *cb_arg = arg; ocs_dma_t *payload = &(cb_arg->payload); sli4_res_common_get_profile_config_t* response = (sli4_res_common_get_profile_config_t*) payload->virt; int num_descriptors; sli4_resource_descriptor_v1_t *desc_p; sli4_pcie_resource_descriptor_v1_t *pcie_desc_p; int i; ocs_hw_set_port_protocol_cb_arg_t *new_cb_arg; ocs_hw_port_protocol_e new_protocol; uint8_t *dst; sli4_isap_resouce_descriptor_v1_t *isap_desc_p; uint8_t *mbxdata; int pci_descriptor_count; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; int num_fcoe_ports = 0; int num_iscsi_ports = 0; new_protocol = (ocs_hw_port_protocol_e)cb_arg->new_protocol; num_descriptors = response->desc_count; /* Count PCI descriptors */ pci_descriptor_count = 0; desc_p = (sli4_resource_descriptor_v1_t *)response->desc; for (i=0; idescriptor_type == SLI4_RESOURCE_DESCRIPTOR_TYPE_PCIE) { ++pci_descriptor_count; } desc_p = (sli4_resource_descriptor_v1_t *) ((uint8_t *)desc_p + desc_p->descriptor_length); } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ new_cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_set_port_protocol_cb_arg_t), OCS_M_NOWAIT); if (new_cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } new_cb_arg->cb = cb_arg->cb; new_cb_arg->arg = cb_arg->arg; /* Allocate memory for the descriptors we're going to send. This is * one for each PCI descriptor plus one ISAP descriptor. */ if (ocs_dma_alloc(hw->os, &new_cb_arg->payload, sizeof(sli4_req_common_set_profile_config_t) + (pci_descriptor_count * sizeof(sli4_pcie_resource_descriptor_v1_t)) + sizeof(sli4_isap_resouce_descriptor_v1_t), 4)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, new_cb_arg, sizeof(ocs_hw_set_port_protocol_cb_arg_t)); return OCS_HW_RTN_NO_MEMORY; } sli_cmd_common_set_profile_config(&hw->sli, mbxdata, SLI4_BMBX_SIZE, &new_cb_arg->payload, 0, pci_descriptor_count+1, 1); /* Point dst to the first descriptor entry in the SET_PROFILE_CONFIG command */ dst = (uint8_t *)&(((sli4_req_common_set_profile_config_t *) new_cb_arg->payload.virt)->desc); /* Loop over all descriptors. If the descriptor is a PCIe descriptor, copy it * to the SET_PROFILE_CONFIG command to be written back. If it's the descriptor * that we're trying to change also set its pf_type. */ desc_p = (sli4_resource_descriptor_v1_t *)response->desc; for (i=0; idescriptor_type == SLI4_RESOURCE_DESCRIPTOR_TYPE_PCIE) { pcie_desc_p = (sli4_pcie_resource_descriptor_v1_t*) desc_p; if (pcie_desc_p->pf_number == cb_arg->pci_func) { /* This is the PCIe descriptor for this OCS instance. * Update it with the new pf_type */ switch(new_protocol) { case OCS_HW_PORT_PROTOCOL_FC: pcie_desc_p->pf_type = SLI4_PROTOCOL_FC; break; case OCS_HW_PORT_PROTOCOL_FCOE: pcie_desc_p->pf_type = SLI4_PROTOCOL_FCOE; break; case OCS_HW_PORT_PROTOCOL_ISCSI: pcie_desc_p->pf_type = SLI4_PROTOCOL_ISCSI; break; default: pcie_desc_p->pf_type = SLI4_PROTOCOL_DEFAULT; break; } } if (pcie_desc_p->pf_type == SLI4_PROTOCOL_FCOE) { ++num_fcoe_ports; } if (pcie_desc_p->pf_type == SLI4_PROTOCOL_ISCSI) { ++num_iscsi_ports; } ocs_memcpy(dst, pcie_desc_p, sizeof(sli4_pcie_resource_descriptor_v1_t)); dst += sizeof(sli4_pcie_resource_descriptor_v1_t); } desc_p = (sli4_resource_descriptor_v1_t *) ((uint8_t *)desc_p + desc_p->descriptor_length); } /* Create an ISAP resource descriptor */ isap_desc_p = (sli4_isap_resouce_descriptor_v1_t*)dst; isap_desc_p->descriptor_type = SLI4_RESOURCE_DESCRIPTOR_TYPE_ISAP; isap_desc_p->descriptor_length = sizeof(sli4_isap_resouce_descriptor_v1_t); if (num_iscsi_ports > 0) { isap_desc_p->iscsi_tgt = 1; isap_desc_p->iscsi_ini = 1; isap_desc_p->iscsi_dif = 1; } if (num_fcoe_ports > 0) { isap_desc_p->fcoe_tgt = 1; isap_desc_p->fcoe_ini = 1; isap_desc_p->fcoe_dif = 1; } /* At this point we're done with the memory allocated by ocs_port_set_protocol */ ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_set_port_protocol_cb_arg_t)); /* Send a SET_PROFILE_CONFIG mailbox command with the new descriptors */ rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_set_port_protocol_cb2, new_cb_arg); if (rc) { ocs_log_err(hw->os, "Error posting COMMON_SET_PROFILE_CONFIG\n"); /* Call the upper level callback to report a failure */ if (new_cb_arg->cb) { new_cb_arg->cb( rc, new_cb_arg->arg); } /* Free the memory allocated by this function */ ocs_dma_free(hw->os, &new_cb_arg->payload); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, new_cb_arg, sizeof(ocs_hw_set_port_protocol_cb_arg_t)); } return rc; } /** * @ingroup io * @brief Set the port protocol. * @par Description * Setting the port protocol is a read-modify-write operation. * This function submits a GET_PROFILE_CONFIG command to read * the current settings. The callback function will modify the * settings and issue the write. * * On successful completion this function will have allocated * two regular memory areas and one dma area which will need to * get freed later in the callbacks. * * @param hw Hardware context. * @param new_protocol New protocol to use. * @param pci_func PCI function to configure. * @param cb Callback function to be called when the command completes. * @param ul_arg An argument that is passed to the callback function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ ocs_hw_rtn_e ocs_hw_set_port_protocol(ocs_hw_t *hw, ocs_hw_port_protocol_e new_protocol, uint32_t pci_func, ocs_set_port_protocol_cb_t cb, void *ul_arg) { uint8_t *mbxdata; ocs_hw_set_port_protocol_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; /* Only supported on Skyhawk */ if (sli_get_if_type(&hw->sli) != SLI4_IF_TYPE_BE3_SKH_PF) { return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_set_port_protocol_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; cb_arg->new_protocol = new_protocol; cb_arg->pci_func = pci_func; /* dma_mem holds the non-embedded portion */ if (ocs_dma_alloc(hw->os, &cb_arg->payload, 4096, 4)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_port_protocol_cb_arg_t)); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_common_get_profile_config(&hw->sli, mbxdata, SLI4_BMBX_SIZE, &cb_arg->payload)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_set_port_protocol_cb1, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "GET_PROFILE_CONFIG failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_fw_write_cb_arg_t)); ocs_dma_free(hw->os, &cb_arg->payload); } return rc; } typedef struct ocs_hw_get_profile_list_cb_arg_s { ocs_get_profile_list_cb_t cb; void *arg; ocs_dma_t payload; } ocs_hw_get_profile_list_cb_arg_t; /** * @brief Called for the completion of get_profile_list for a * user request. * @par Description * This function is called when the COMMMON_GET_PROFILE_LIST * mailbox completes. The response will be in * ctx->non_embedded_mem.virt. This function parses the * response and creates a ocs_hw_profile_list, then calls the * mgmt_cb callback function and passes that list to it. * * @param hw Hardware context. * @param status The status from the MQE * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_get_profile_list_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_profile_list_t *list; ocs_hw_get_profile_list_cb_arg_t *cb_arg = arg; ocs_dma_t *payload = &(cb_arg->payload); sli4_res_common_get_profile_list_t *response = (sli4_res_common_get_profile_list_t *)payload->virt; int i; int num_descriptors; list = ocs_malloc(hw->os, sizeof(ocs_hw_profile_list_t), OCS_M_ZERO); list->num_descriptors = response->profile_descriptor_count; num_descriptors = list->num_descriptors; if (num_descriptors > OCS_HW_MAX_PROFILES) { num_descriptors = OCS_HW_MAX_PROFILES; } for (i=0; idescriptors[i].profile_id = response->profile_descriptor[i].profile_id; list->descriptors[i].profile_index = response->profile_descriptor[i].profile_index; ocs_strcpy(list->descriptors[i].profile_description, (char *)response->profile_descriptor[i].profile_description); } if (cb_arg->cb) { cb_arg->cb(status, list, cb_arg->arg); } else { ocs_free(hw->os, list, sizeof(*list)); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_profile_list_cb_arg_t)); return 0; } /** * @ingroup io * @brief Get a list of available profiles. * @par Description * Issues a SLI-4 COMMON_GET_PROFILE_LIST mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param cb Callback function to be called when the * command completes. * @param ul_arg An argument that is passed to the callback * function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ ocs_hw_rtn_e ocs_hw_get_profile_list(ocs_hw_t *hw, ocs_get_profile_list_cb_t cb, void* ul_arg) { uint8_t *mbxdata; ocs_hw_get_profile_list_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* Only supported on Skyhawk */ if (sli_get_if_type(&hw->sli) != SLI4_IF_TYPE_BE3_SKH_PF) { return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_get_profile_list_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; /* dma_mem holds the non-embedded portion */ if (ocs_dma_alloc(hw->os, &cb_arg->payload, sizeof(sli4_res_common_get_profile_list_t), 4)) { ocs_log_err(hw->os, "Failed to allocate DMA buffer\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_profile_list_cb_arg_t)); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_common_get_profile_list(&hw->sli, mbxdata, SLI4_BMBX_SIZE, 0, &cb_arg->payload)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_get_profile_list_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "GET_PROFILE_LIST failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_dma_free(hw->os, &cb_arg->payload); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_profile_list_cb_arg_t)); } return rc; } typedef struct ocs_hw_get_active_profile_cb_arg_s { ocs_get_active_profile_cb_t cb; void *arg; } ocs_hw_get_active_profile_cb_arg_t; /** * @brief Called for the completion of get_active_profile for a * user request. * * @param hw Hardware context. * @param status The status from the MQE * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_get_active_profile_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_get_active_profile_cb_arg_t *cb_arg = arg; sli4_cmd_sli_config_t* mbox_rsp = (sli4_cmd_sli_config_t*) mqe; sli4_res_common_get_active_profile_t* response = (sli4_res_common_get_active_profile_t*) mbox_rsp->payload.embed; uint32_t active_profile; active_profile = response->active_profile_id; if (cb_arg->cb) { cb_arg->cb(status, active_profile, cb_arg->arg); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_active_profile_cb_arg_t)); return 0; } /** * @ingroup io * @brief Get the currently active profile. * @par Description * Issues a SLI-4 COMMON_GET_ACTIVE_PROFILE mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param cb Callback function to be called when the * command completes. * @param ul_arg An argument that is passed to the callback * function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ int32_t ocs_hw_get_active_profile(ocs_hw_t *hw, ocs_get_active_profile_cb_t cb, void* ul_arg) { uint8_t *mbxdata; ocs_hw_get_active_profile_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* Only supported on Skyhawk */ if (sli_get_if_type(&hw->sli) != SLI4_IF_TYPE_BE3_SKH_PF) { return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_get_active_profile_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; if (sli_cmd_common_get_active_profile(&hw->sli, mbxdata, SLI4_BMBX_SIZE)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_get_active_profile_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "GET_ACTIVE_PROFILE failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_active_profile_cb_arg_t)); } return rc; } typedef struct ocs_hw_get_nvparms_cb_arg_s { ocs_get_nvparms_cb_t cb; void *arg; } ocs_hw_get_nvparms_cb_arg_t; /** * @brief Called for the completion of get_nvparms for a * user request. * * @param hw Hardware context. * @param status The status from the MQE. * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return 0 on success, non-zero otherwise */ static int32_t ocs_hw_get_nvparms_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_get_nvparms_cb_arg_t *cb_arg = arg; sli4_cmd_read_nvparms_t* mbox_rsp = (sli4_cmd_read_nvparms_t*) mqe; if (cb_arg->cb) { cb_arg->cb(status, mbox_rsp->wwpn, mbox_rsp->wwnn, mbox_rsp->hard_alpa, mbox_rsp->preferred_d_id, cb_arg->arg); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_nvparms_cb_arg_t)); return 0; } /** * @ingroup io * @brief Read non-volatile parms. * @par Description * Issues a SLI-4 READ_NVPARMS mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param cb Callback function to be called when the * command completes. * @param ul_arg An argument that is passed to the callback * function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ int32_t ocs_hw_get_nvparms(ocs_hw_t *hw, ocs_get_nvparms_cb_t cb, void* ul_arg) { uint8_t *mbxdata; ocs_hw_get_nvparms_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_get_nvparms_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; if (sli_cmd_read_nvparms(&hw->sli, mbxdata, SLI4_BMBX_SIZE)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_get_nvparms_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "READ_NVPARMS failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_nvparms_cb_arg_t)); } return rc; } typedef struct ocs_hw_set_nvparms_cb_arg_s { ocs_set_nvparms_cb_t cb; void *arg; } ocs_hw_set_nvparms_cb_arg_t; /** * @brief Called for the completion of set_nvparms for a * user request. * * @param hw Hardware context. * @param status The status from the MQE. * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_set_nvparms_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_set_nvparms_cb_arg_t *cb_arg = arg; if (cb_arg->cb) { cb_arg->cb(status, cb_arg->arg); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_set_nvparms_cb_arg_t)); return 0; } /** * @ingroup io * @brief Write non-volatile parms. * @par Description * Issues a SLI-4 WRITE_NVPARMS mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param cb Callback function to be called when the * command completes. * @param wwpn Port's WWPN in big-endian order, or NULL to use default. * @param wwnn Port's WWNN in big-endian order, or NULL to use default. * @param hard_alpa A hard AL_PA address setting used during loop * initialization. If no hard AL_PA is required, set to 0. * @param preferred_d_id A preferred D_ID address setting * that may be overridden with the CONFIG_LINK mailbox command. * If there is no preference, set to 0. * @param ul_arg An argument that is passed to the callback * function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ int32_t ocs_hw_set_nvparms(ocs_hw_t *hw, ocs_set_nvparms_cb_t cb, uint8_t *wwpn, uint8_t *wwnn, uint8_t hard_alpa, uint32_t preferred_d_id, void* ul_arg) { uint8_t *mbxdata; ocs_hw_set_nvparms_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_set_nvparms_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; if (sli_cmd_write_nvparms(&hw->sli, mbxdata, SLI4_BMBX_SIZE, wwpn, wwnn, hard_alpa, preferred_d_id)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_set_nvparms_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "SET_NVPARMS failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_set_nvparms_cb_arg_t)); } return rc; } /** * @brief Called to obtain the count for the specified type. * * @param hw Hardware context. * @param io_count_type IO count type (inuse, free, wait_free). * * @return Returns the number of IOs on the specified list type. */ uint32_t ocs_hw_io_get_count(ocs_hw_t *hw, ocs_hw_io_count_type_e io_count_type) { ocs_hw_io_t *io = NULL; uint32_t count = 0; ocs_lock(&hw->io_lock); switch (io_count_type) { case OCS_HW_IO_INUSE_COUNT : ocs_list_foreach(&hw->io_inuse, io) { count++; } break; case OCS_HW_IO_FREE_COUNT : ocs_list_foreach(&hw->io_free, io) { count++; } break; case OCS_HW_IO_WAIT_FREE_COUNT : ocs_list_foreach(&hw->io_wait_free, io) { count++; } break; case OCS_HW_IO_PORT_OWNED_COUNT: ocs_list_foreach(&hw->io_port_owned, io) { count++; } break; case OCS_HW_IO_N_TOTAL_IO_COUNT : count = hw->config.n_io; break; } ocs_unlock(&hw->io_lock); return count; } /** * @brief Called to obtain the count of produced RQs. * * @param hw Hardware context. * * @return Returns the number of RQs produced. */ uint32_t ocs_hw_get_rqes_produced_count(ocs_hw_t *hw) { uint32_t count = 0; uint32_t i; uint32_t j; for (i = 0; i < hw->hw_rq_count; i++) { hw_rq_t *rq = hw->hw_rq[i]; if (rq->rq_tracker != NULL) { for (j = 0; j < rq->entry_count; j++) { if (rq->rq_tracker[j] != NULL) { count++; } } } } return count; } typedef struct ocs_hw_set_active_profile_cb_arg_s { ocs_set_active_profile_cb_t cb; void *arg; } ocs_hw_set_active_profile_cb_arg_t; /** * @brief Called for the completion of set_active_profile for a * user request. * * @param hw Hardware context. * @param status The status from the MQE * @param mqe Pointer to mailbox command buffer. * @param arg Pointer to a callback argument. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_set_active_profile_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_set_active_profile_cb_arg_t *cb_arg = arg; if (cb_arg->cb) { cb_arg->cb(status, cb_arg->arg); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_get_active_profile_cb_arg_t)); return 0; } /** * @ingroup io * @brief Set the currently active profile. * @par Description * Issues a SLI4 COMMON_GET_ACTIVE_PROFILE mailbox. When the * command completes the provided mgmt callback function is * called. * * @param hw Hardware context. * @param profile_id Profile ID to activate. * @param cb Callback function to be called when the command completes. * @param ul_arg An argument that is passed to the callback function. * * @return * - OCS_HW_RTN_SUCCESS on success. * - OCS_HW_RTN_NO_MEMORY if a malloc fails. * - OCS_HW_RTN_NO_RESOURCES if unable to get a command * context. * - OCS_HW_RTN_ERROR on any other error. */ int32_t ocs_hw_set_active_profile(ocs_hw_t *hw, ocs_set_active_profile_cb_t cb, uint32_t profile_id, void* ul_arg) { uint8_t *mbxdata; ocs_hw_set_active_profile_cb_arg_t *cb_arg; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; /* Only supported on Skyhawk */ if (sli_get_if_type(&hw->sli) != SLI4_IF_TYPE_BE3_SKH_PF) { return OCS_HW_RTN_ERROR; } /* mbxdata holds the header of the command */ mbxdata = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mbxdata == NULL) { ocs_log_err(hw->os, "failed to malloc mbox\n"); return OCS_HW_RTN_NO_MEMORY; } /* cb_arg holds the data that will be passed to the callback on completion */ cb_arg = ocs_malloc(hw->os, sizeof(ocs_hw_set_active_profile_cb_arg_t), OCS_M_NOWAIT); if (cb_arg == NULL) { ocs_log_err(hw->os, "failed to malloc cb_arg\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); return OCS_HW_RTN_NO_MEMORY; } cb_arg->cb = cb; cb_arg->arg = ul_arg; if (sli_cmd_common_set_active_profile(&hw->sli, mbxdata, SLI4_BMBX_SIZE, 0, profile_id)) { rc = ocs_hw_command(hw, mbxdata, OCS_CMD_NOWAIT, ocs_hw_set_active_profile_cb, cb_arg); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "SET_ACTIVE_PROFILE failed\n"); ocs_free(hw->os, mbxdata, SLI4_BMBX_SIZE); ocs_free(hw->os, cb_arg, sizeof(ocs_hw_set_active_profile_cb_arg_t)); } return rc; } /* * Private functions */ /** * @brief Update the queue hash with the ID and index. * * @param hash Pointer to hash table. * @param id ID that was created. * @param index The index into the hash object. */ static void ocs_hw_queue_hash_add(ocs_queue_hash_t *hash, uint16_t id, uint16_t index) { uint32_t hash_index = id & (OCS_HW_Q_HASH_SIZE - 1); /* * Since the hash is always bigger than the number of queues, then we * never have to worry about an infinite loop. */ while(hash[hash_index].in_use) { hash_index = (hash_index + 1) & (OCS_HW_Q_HASH_SIZE - 1); } /* not used, claim the entry */ hash[hash_index].id = id; hash[hash_index].in_use = 1; hash[hash_index].index = index; } /** * @brief Find index given queue ID. * * @param hash Pointer to hash table. * @param id ID to find. * * @return Returns the index into the HW cq array or -1 if not found. */ int32_t ocs_hw_queue_hash_find(ocs_queue_hash_t *hash, uint16_t id) { int32_t rc = -1; int32_t index = id & (OCS_HW_Q_HASH_SIZE - 1); /* * Since the hash is always bigger than the maximum number of Qs, then we * never have to worry about an infinite loop. We will always find an * unused entry. */ do { if (hash[index].in_use && hash[index].id == id) { rc = hash[index].index; } else { index = (index + 1) & (OCS_HW_Q_HASH_SIZE - 1); } } while(rc == -1 && hash[index].in_use); return rc; } static int32_t ocs_hw_domain_add(ocs_hw_t *hw, ocs_domain_t *domain) { int32_t rc = OCS_HW_RTN_ERROR; uint16_t fcfi = UINT16_MAX; if ((hw == NULL) || (domain == NULL)) { ocs_log_err(NULL, "bad parameter hw=%p domain=%p\n", hw, domain); return OCS_HW_RTN_ERROR; } fcfi = domain->fcf_indicator; if (fcfi < SLI4_MAX_FCFI) { uint16_t fcf_index = UINT16_MAX; ocs_log_debug(hw->os, "adding domain %p @ %#x\n", domain, fcfi); hw->domains[fcfi] = domain; /* HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB */ if (hw->workaround.override_fcfi) { if (hw->first_domain_idx < 0) { hw->first_domain_idx = fcfi; } } fcf_index = domain->fcf; if (fcf_index < SLI4_MAX_FCF_INDEX) { ocs_log_debug(hw->os, "adding map of FCF index %d to FCFI %d\n", fcf_index, fcfi); hw->fcf_index_fcfi[fcf_index] = fcfi; rc = OCS_HW_RTN_SUCCESS; } else { ocs_log_test(hw->os, "FCF index %d out of range (max %d)\n", fcf_index, SLI4_MAX_FCF_INDEX); hw->domains[fcfi] = NULL; } } else { ocs_log_test(hw->os, "FCFI %#x out of range (max %#x)\n", fcfi, SLI4_MAX_FCFI); } return rc; } static int32_t ocs_hw_domain_del(ocs_hw_t *hw, ocs_domain_t *domain) { int32_t rc = OCS_HW_RTN_ERROR; uint16_t fcfi = UINT16_MAX; if ((hw == NULL) || (domain == NULL)) { ocs_log_err(NULL, "bad parameter hw=%p domain=%p\n", hw, domain); return OCS_HW_RTN_ERROR; } fcfi = domain->fcf_indicator; if (fcfi < SLI4_MAX_FCFI) { uint16_t fcf_index = UINT16_MAX; ocs_log_debug(hw->os, "deleting domain %p @ %#x\n", domain, fcfi); if (domain != hw->domains[fcfi]) { ocs_log_test(hw->os, "provided domain %p does not match stored domain %p\n", domain, hw->domains[fcfi]); return OCS_HW_RTN_ERROR; } hw->domains[fcfi] = NULL; /* HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB */ if (hw->workaround.override_fcfi) { if (hw->first_domain_idx == fcfi) { hw->first_domain_idx = -1; } } fcf_index = domain->fcf; if (fcf_index < SLI4_MAX_FCF_INDEX) { if (hw->fcf_index_fcfi[fcf_index] == fcfi) { hw->fcf_index_fcfi[fcf_index] = 0; rc = OCS_HW_RTN_SUCCESS; } else { ocs_log_test(hw->os, "indexed FCFI %#x doesn't match provided %#x @ %d\n", hw->fcf_index_fcfi[fcf_index], fcfi, fcf_index); } } else { ocs_log_test(hw->os, "FCF index %d out of range (max %d)\n", fcf_index, SLI4_MAX_FCF_INDEX); } } else { ocs_log_test(hw->os, "FCFI %#x out of range (max %#x)\n", fcfi, SLI4_MAX_FCFI); } return rc; } ocs_domain_t * ocs_hw_domain_get(ocs_hw_t *hw, uint16_t fcfi) { if (hw == NULL) { ocs_log_err(NULL, "bad parameter hw=%p\n", hw); return NULL; } if (fcfi < SLI4_MAX_FCFI) { return hw->domains[fcfi]; } else { ocs_log_test(hw->os, "FCFI %#x out of range (max %#x)\n", fcfi, SLI4_MAX_FCFI); return NULL; } } static ocs_domain_t * ocs_hw_domain_get_indexed(ocs_hw_t *hw, uint16_t fcf_index) { if (hw == NULL) { ocs_log_err(NULL, "bad parameter hw=%p\n", hw); return NULL; } if (fcf_index < SLI4_MAX_FCF_INDEX) { return ocs_hw_domain_get(hw, hw->fcf_index_fcfi[fcf_index]); } else { ocs_log_test(hw->os, "FCF index %d out of range (max %d)\n", fcf_index, SLI4_MAX_FCF_INDEX); return NULL; } } /** * @brief Quaratine an IO by taking a reference count and adding it to the * quarantine list. When the IO is popped from the list then the * count is released and the IO MAY be freed depending on whether * it is still referenced by the IO. * * @n @b Note: BZ 160124 - If this is a target write or an initiator read using * DIF, then we must add the XRI to a quarantine list until we receive * 4 more completions of this same type. * * @param hw Hardware context. * @param wq Pointer to the WQ associated with the IO object to quarantine. * @param io Pointer to the io object to quarantine. */ static void ocs_hw_io_quarantine(ocs_hw_t *hw, hw_wq_t *wq, ocs_hw_io_t *io) { ocs_quarantine_info_t *q_info = &wq->quarantine_info; uint32_t index; ocs_hw_io_t *free_io = NULL; /* return if the QX bit was clear */ if (!io->quarantine) { return; } /* increment the IO refcount to prevent it from being freed before the quarantine is over */ if (ocs_ref_get_unless_zero(&io->ref) == 0) { /* command no longer active */ ocs_log_debug(hw ? hw->os : NULL, "io not active xri=0x%x tag=0x%x\n", io->indicator, io->reqtag); return; } sli_queue_lock(wq->queue); index = q_info->quarantine_index; free_io = q_info->quarantine_ios[index]; q_info->quarantine_ios[index] = io; q_info->quarantine_index = (index + 1) % OCS_HW_QUARANTINE_QUEUE_DEPTH; sli_queue_unlock(wq->queue); if (free_io != NULL) { ocs_ref_put(&free_io->ref); /* ocs_ref_get(): same function */ } } /** * @brief Process entries on the given completion queue. * * @param hw Hardware context. * @param cq Pointer to the HW completion queue object. * * @return None. */ void ocs_hw_cq_process(ocs_hw_t *hw, hw_cq_t *cq) { uint8_t cqe[sizeof(sli4_mcqe_t)]; uint16_t rid = UINT16_MAX; sli4_qentry_e ctype; /* completion type */ int32_t status; uint32_t n_processed = 0; time_t tstart; time_t telapsed; tstart = ocs_msectime(); while (!sli_queue_read(&hw->sli, cq->queue, cqe)) { status = sli_cq_parse(&hw->sli, cq->queue, cqe, &ctype, &rid); /* * The sign of status is significant. If status is: * == 0 : call completed correctly and the CQE indicated success * > 0 : call completed correctly and the CQE indicated an error * < 0 : call failed and no information is available about the CQE */ if (status < 0) { if (status == -2) { /* Notification that an entry was consumed, but not completed */ continue; } break; } switch (ctype) { case SLI_QENTRY_ASYNC: CPUTRACE("async"); sli_cqe_async(&hw->sli, cqe); break; case SLI_QENTRY_MQ: /* * Process MQ entry. Note there is no way to determine * the MQ_ID from the completion entry. */ CPUTRACE("mq"); ocs_hw_mq_process(hw, status, hw->mq); break; case SLI_QENTRY_OPT_WRITE_CMD: ocs_hw_rqpair_process_auto_xfr_rdy_cmd(hw, cq, cqe); break; case SLI_QENTRY_OPT_WRITE_DATA: ocs_hw_rqpair_process_auto_xfr_rdy_data(hw, cq, cqe); break; case SLI_QENTRY_WQ: CPUTRACE("wq"); ocs_hw_wq_process(hw, cq, cqe, status, rid); break; case SLI_QENTRY_WQ_RELEASE: { uint32_t wq_id = rid; int32_t index = ocs_hw_queue_hash_find(hw->wq_hash, wq_id); if (unlikely(index < 0)) { ocs_log_err(hw->os, "unknown idx=%#x rid=%#x\n", index, rid); break; } hw_wq_t *wq = hw->hw_wq[index]; /* Submit any HW IOs that are on the WQ pending list */ hw_wq_submit_pending(wq, wq->wqec_set_count); break; } case SLI_QENTRY_RQ: CPUTRACE("rq"); ocs_hw_rqpair_process_rq(hw, cq, cqe); break; case SLI_QENTRY_XABT: { CPUTRACE("xabt"); ocs_hw_xabt_process(hw, cq, cqe, rid); break; } default: ocs_log_test(hw->os, "unhandled ctype=%#x rid=%#x\n", ctype, rid); break; } n_processed++; if (n_processed == cq->queue->proc_limit) { break; } if (cq->queue->n_posted >= (cq->queue->posted_limit)) { sli_queue_arm(&hw->sli, cq->queue, FALSE); } } sli_queue_arm(&hw->sli, cq->queue, TRUE); if (n_processed > cq->queue->max_num_processed) { cq->queue->max_num_processed = n_processed; } telapsed = ocs_msectime() - tstart; if (telapsed > cq->queue->max_process_time) { cq->queue->max_process_time = telapsed; } } /** * @brief Process WQ completion queue entries. * * @param hw Hardware context. * @param cq Pointer to the HW completion queue object. * @param cqe Pointer to WQ completion queue. * @param status Completion status. * @param rid Resource ID (IO tag). * * @return none */ void ocs_hw_wq_process(ocs_hw_t *hw, hw_cq_t *cq, uint8_t *cqe, int32_t status, uint16_t rid) { hw_wq_callback_t *wqcb; ocs_queue_history_cqe(&hw->q_hist, SLI_QENTRY_WQ, (void *)cqe, ((sli4_fc_wcqe_t *)cqe)->status, cq->queue->id, ((cq->queue->index - 1) & (cq->queue->length - 1))); if(rid == OCS_HW_REQUE_XRI_REGTAG) { if(status) { ocs_log_err(hw->os, "reque xri failed, status = %d \n", status); } return; } wqcb = ocs_hw_reqtag_get_instance(hw, rid); if (wqcb == NULL) { ocs_log_err(hw->os, "invalid request tag: x%x\n", rid); return; } if (wqcb->callback == NULL) { ocs_log_err(hw->os, "wqcb callback is NULL\n"); return; } (*wqcb->callback)(wqcb->arg, cqe, status); } /** * @brief Process WQ completions for IO requests * * @param arg Generic callback argument * @param cqe Pointer to completion queue entry * @param status Completion status * * @par Description * @n @b Note: Regarding io->reqtag, the reqtag is assigned once when HW IOs are initialized * in ocs_hw_setup_io(), and don't need to be returned to the hw->wq_reqtag_pool. * * @return None. */ static void ocs_hw_wq_process_io(void *arg, uint8_t *cqe, int32_t status) { ocs_hw_io_t *io = arg; ocs_hw_t *hw = io->hw; sli4_fc_wcqe_t *wcqe = (void *)cqe; uint32_t len = 0; uint32_t ext = 0; uint8_t out_of_order_axr_cmd = 0; uint8_t out_of_order_axr_data = 0; uint8_t lock_taken = 0; #if defined(OCS_DISC_SPIN_DELAY) uint32_t delay = 0; char prop_buf[32]; #endif /* * For the primary IO, this will also be used for the * response. So it is important to only set/clear this * flag on the first data phase of the IO because * subsequent phases will be done on the secondary XRI. */ if (io->quarantine && io->quarantine_first_phase) { io->quarantine = (wcqe->qx == 1); ocs_hw_io_quarantine(hw, io->wq, io); } io->quarantine_first_phase = FALSE; /* BZ 161832 - free secondary HW IO */ if (io->sec_hio != NULL && io->sec_hio->quarantine) { /* * If the quarantine flag is set on the * IO, then set it on the secondary IO * based on the quarantine XRI (QX) bit * sent by the FW. */ io->sec_hio->quarantine = (wcqe->qx == 1); /* use the primary io->wq because it is not set on the secondary IO. */ ocs_hw_io_quarantine(hw, io->wq, io->sec_hio); } ocs_hw_remove_io_timed_wqe(hw, io); /* clear xbusy flag if WCQE[XB] is clear */ if (io->xbusy && wcqe->xb == 0) { io->xbusy = FALSE; } /* get extended CQE status */ switch (io->type) { case OCS_HW_BLS_ACC: case OCS_HW_BLS_ACC_SID: break; case OCS_HW_ELS_REQ: sli_fc_els_did(&hw->sli, cqe, &ext); len = sli_fc_response_length(&hw->sli, cqe); break; case OCS_HW_ELS_RSP: case OCS_HW_ELS_RSP_SID: case OCS_HW_FC_CT_RSP: break; case OCS_HW_FC_CT: len = sli_fc_response_length(&hw->sli, cqe); break; case OCS_HW_IO_TARGET_WRITE: len = sli_fc_io_length(&hw->sli, cqe); #if defined(OCS_DISC_SPIN_DELAY) if (ocs_get_property("disk_spin_delay", prop_buf, sizeof(prop_buf)) == 0) { delay = ocs_strtoul(prop_buf, 0, 0); ocs_udelay(delay); } #endif break; case OCS_HW_IO_TARGET_READ: len = sli_fc_io_length(&hw->sli, cqe); /* * if_type == 2 seems to return 0 "total length placed" on * FCP_TSEND64_WQE completions. If this appears to happen, * use the CTIO data transfer length instead. */ if (hw->workaround.retain_tsend_io_length && !len && !status) { len = io->length; } break; case OCS_HW_IO_TARGET_RSP: if(io->is_port_owned) { ocs_lock(&io->axr_lock); lock_taken = 1; if(io->axr_buf->call_axr_cmd) { out_of_order_axr_cmd = 1; } if(io->axr_buf->call_axr_data) { out_of_order_axr_data = 1; } } break; case OCS_HW_IO_INITIATOR_READ: len = sli_fc_io_length(&hw->sli, cqe); break; case OCS_HW_IO_INITIATOR_WRITE: len = sli_fc_io_length(&hw->sli, cqe); break; case OCS_HW_IO_INITIATOR_NODATA: break; case OCS_HW_IO_DNRX_REQUEUE: /* release the count for re-posting the buffer */ //ocs_hw_io_free(hw, io); break; default: ocs_log_test(hw->os, "XXX unhandled io type %#x for XRI 0x%x\n", io->type, io->indicator); break; } if (status) { ext = sli_fc_ext_status(&hw->sli, cqe); /* Emulate IAAB=0 for initiator WQEs only; i.e. automatically * abort exchange if an error occurred and exchange is still busy. */ if (hw->config.i_only_aab && (ocs_hw_iotype_is_originator(io->type)) && (ocs_hw_wcqe_abort_needed(status, ext, wcqe->xb))) { ocs_hw_rtn_e rc; ocs_log_debug(hw->os, "aborting xri=%#x tag=%#x\n", io->indicator, io->reqtag); /* * Because the initiator will not issue another IO phase, then it is OK to to issue the * callback on the abort completion, but for consistency with the target, wait for the * XRI_ABORTED CQE to issue the IO callback. */ rc = ocs_hw_io_abort(hw, io, TRUE, NULL, NULL); if (rc == OCS_HW_RTN_SUCCESS) { /* latch status to return after abort is complete */ io->status_saved = 1; io->saved_status = status; io->saved_ext = ext; io->saved_len = len; goto exit_ocs_hw_wq_process_io; } else if (rc == OCS_HW_RTN_IO_ABORT_IN_PROGRESS) { /* * Already being aborted by someone else (ABTS * perhaps). Just fall through and return original * error. */ ocs_log_debug(hw->os, "abort in progress xri=%#x tag=%#x\n", io->indicator, io->reqtag); } else { /* Failed to abort for some other reason, log error */ ocs_log_test(hw->os, "Failed to abort xri=%#x tag=%#x rc=%d\n", io->indicator, io->reqtag, rc); } } /* * If we're not an originator IO, and XB is set, then issue abort for the IO from within the HW */ if ( (! ocs_hw_iotype_is_originator(io->type)) && wcqe->xb) { ocs_hw_rtn_e rc; ocs_log_debug(hw->os, "aborting xri=%#x tag=%#x\n", io->indicator, io->reqtag); /* * Because targets may send a response when the IO completes using the same XRI, we must * wait for the XRI_ABORTED CQE to issue the IO callback */ rc = ocs_hw_io_abort(hw, io, FALSE, NULL, NULL); if (rc == OCS_HW_RTN_SUCCESS) { /* latch status to return after abort is complete */ io->status_saved = 1; io->saved_status = status; io->saved_ext = ext; io->saved_len = len; goto exit_ocs_hw_wq_process_io; } else if (rc == OCS_HW_RTN_IO_ABORT_IN_PROGRESS) { /* * Already being aborted by someone else (ABTS * perhaps). Just fall through and return original * error. */ ocs_log_debug(hw->os, "abort in progress xri=%#x tag=%#x\n", io->indicator, io->reqtag); } else { /* Failed to abort for some other reason, log error */ ocs_log_test(hw->os, "Failed to abort xri=%#x tag=%#x rc=%d\n", io->indicator, io->reqtag, rc); } } } /* BZ 161832 - free secondary HW IO */ if (io->sec_hio != NULL) { ocs_hw_io_free(hw, io->sec_hio); io->sec_hio = NULL; } if (io->done != NULL) { ocs_hw_done_t done = io->done; void *arg = io->arg; io->done = NULL; if (io->status_saved) { /* use latched status if exists */ status = io->saved_status; len = io->saved_len; ext = io->saved_ext; io->status_saved = 0; } /* Restore default SGL */ ocs_hw_io_restore_sgl(hw, io); done(io, io->rnode, len, status, ext, arg); } if(out_of_order_axr_cmd) { /* bounce enabled, single RQ, we snoop the ox_id to choose the cpuidx */ if (hw->config.bounce) { fc_header_t *hdr = io->axr_buf->cmd_seq->header->dma.virt; uint32_t s_id = fc_be24toh(hdr->s_id); uint32_t d_id = fc_be24toh(hdr->d_id); uint32_t ox_id = ocs_be16toh(hdr->ox_id); if (hw->callback.bounce != NULL) { (*hw->callback.bounce)(ocs_hw_unsol_process_bounce, io->axr_buf->cmd_seq, s_id, d_id, ox_id); } }else { hw->callback.unsolicited(hw->args.unsolicited, io->axr_buf->cmd_seq); } if(out_of_order_axr_data) { /* bounce enabled, single RQ, we snoop the ox_id to choose the cpuidx */ if (hw->config.bounce) { fc_header_t *hdr = io->axr_buf->seq.header->dma.virt; uint32_t s_id = fc_be24toh(hdr->s_id); uint32_t d_id = fc_be24toh(hdr->d_id); uint32_t ox_id = ocs_be16toh(hdr->ox_id); if (hw->callback.bounce != NULL) { (*hw->callback.bounce)(ocs_hw_unsol_process_bounce, &io->axr_buf->seq, s_id, d_id, ox_id); } }else { hw->callback.unsolicited(hw->args.unsolicited, &io->axr_buf->seq); } } } exit_ocs_hw_wq_process_io: if(lock_taken) { ocs_unlock(&io->axr_lock); } } /** * @brief Process WQ completions for abort requests. * * @param arg Generic callback argument. * @param cqe Pointer to completion queue entry. * @param status Completion status. * * @return None. */ static void ocs_hw_wq_process_abort(void *arg, uint8_t *cqe, int32_t status) { ocs_hw_io_t *io = arg; ocs_hw_t *hw = io->hw; uint32_t ext = 0; uint32_t len = 0; hw_wq_callback_t *wqcb; /* * For IOs that were aborted internally, we may need to issue the callback here depending * on whether a XRI_ABORTED CQE is expected ot not. If the status is Local Reject/No XRI, then * issue the callback now. */ ext = sli_fc_ext_status(&hw->sli, cqe); if (status == SLI4_FC_WCQE_STATUS_LOCAL_REJECT && ext == SLI4_FC_LOCAL_REJECT_NO_XRI && io->done != NULL) { ocs_hw_done_t done = io->done; void *arg = io->arg; io->done = NULL; /* * Use latched status as this is always saved for an internal abort * * Note: We wont have both a done and abort_done function, so don't worry about * clobbering the len, status and ext fields. */ status = io->saved_status; len = io->saved_len; ext = io->saved_ext; io->status_saved = 0; done(io, io->rnode, len, status, ext, arg); } if (io->abort_done != NULL) { ocs_hw_done_t done = io->abort_done; void *arg = io->abort_arg; io->abort_done = NULL; done(io, io->rnode, len, status, ext, arg); } ocs_lock(&hw->io_abort_lock); /* clear abort bit to indicate abort is complete */ io->abort_in_progress = 0; ocs_unlock(&hw->io_abort_lock); /* Free the WQ callback */ ocs_hw_assert(io->abort_reqtag != UINT32_MAX); wqcb = ocs_hw_reqtag_get_instance(hw, io->abort_reqtag); ocs_hw_reqtag_free(hw, wqcb); /* * Call ocs_hw_io_free() because this releases the WQ reservation as * well as doing the refcount put. Don't duplicate the code here. */ (void)ocs_hw_io_free(hw, io); } /** * @brief Process XABT completions * * @param hw Hardware context. * @param cq Pointer to the HW completion queue object. * @param cqe Pointer to WQ completion queue. * @param rid Resource ID (IO tag). * * * @return None. */ void ocs_hw_xabt_process(ocs_hw_t *hw, hw_cq_t *cq, uint8_t *cqe, uint16_t rid) { /* search IOs wait free list */ ocs_hw_io_t *io = NULL; io = ocs_hw_io_lookup(hw, rid); ocs_queue_history_cqe(&hw->q_hist, SLI_QENTRY_XABT, (void *)cqe, 0, cq->queue->id, ((cq->queue->index - 1) & (cq->queue->length - 1))); if (io == NULL) { /* IO lookup failure should never happen */ ocs_log_err(hw->os, "Error: xabt io lookup failed rid=%#x\n", rid); return; } if (!io->xbusy) { ocs_log_debug(hw->os, "xabt io not busy rid=%#x\n", rid); } else { /* mark IO as no longer busy */ io->xbusy = FALSE; } if (io->is_port_owned) { ocs_lock(&hw->io_lock); /* Take reference so that below callback will not free io before reque */ ocs_ref_get(&io->ref); ocs_unlock(&hw->io_lock); } /* For IOs that were aborted internally, we need to issue any pending callback here. */ if (io->done != NULL) { ocs_hw_done_t done = io->done; void *arg = io->arg; /* Use latched status as this is always saved for an internal abort */ int32_t status = io->saved_status; uint32_t len = io->saved_len; uint32_t ext = io->saved_ext; io->done = NULL; io->status_saved = 0; done(io, io->rnode, len, status, ext, arg); } /* Check to see if this is a port owned XRI */ if (io->is_port_owned) { ocs_lock(&hw->io_lock); ocs_hw_reque_xri(hw, io); ocs_unlock(&hw->io_lock); /* Not hanlding reque xri completion, free io */ ocs_hw_io_free(hw, io); return; } ocs_lock(&hw->io_lock); if ((io->state == OCS_HW_IO_STATE_INUSE) || (io->state == OCS_HW_IO_STATE_WAIT_FREE)) { /* if on wait_free list, caller has already freed IO; * remove from wait_free list and add to free list. * if on in-use list, already marked as no longer busy; * just leave there and wait for caller to free. */ if (io->state == OCS_HW_IO_STATE_WAIT_FREE) { io->state = OCS_HW_IO_STATE_FREE; ocs_list_remove(&hw->io_wait_free, io); ocs_hw_io_free_move_correct_list(hw, io); } } ocs_unlock(&hw->io_lock); } /** * @brief Adjust the number of WQs and CQs within the HW. * * @par Description * Calculates the number of WQs and associated CQs needed in the HW based on * the number of IOs. Calculates the starting CQ index for each WQ, RQ and * MQ. * * @param hw Hardware context allocated by the caller. */ static void ocs_hw_adjust_wqs(ocs_hw_t *hw) { uint32_t max_wq_num = sli_get_max_queue(&hw->sli, SLI_QTYPE_WQ); uint32_t max_wq_entries = hw->num_qentries[SLI_QTYPE_WQ]; uint32_t max_cq_entries = hw->num_qentries[SLI_QTYPE_CQ]; /* * possibly adjust the the size of the WQs so that the CQ is twice as * big as the WQ to allow for 2 completions per IO. This allows us to * handle multi-phase as well as aborts. */ if (max_cq_entries < max_wq_entries * 2) { max_wq_entries = hw->num_qentries[SLI_QTYPE_WQ] = max_cq_entries / 2; } /* * Calculate the number of WQs to use base on the number of IOs. * * Note: We need to reserve room for aborts which must be sent down * the same WQ as the IO. So we allocate enough WQ space to * handle 2 times the number of IOs. Half of the space will be * used for normal IOs and the other hwf is reserved for aborts. */ hw->config.n_wq = ((hw->config.n_io * 2) + (max_wq_entries - 1)) / max_wq_entries; /* * For performance reasons, it is best to use use a minimum of 4 WQs * for BE3 and Skyhawk. */ if (hw->config.n_wq < 4 && SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) { hw->config.n_wq = 4; } /* * For dual-chute support, we need to have at least one WQ per chute. */ if (hw->config.n_wq < 2 && ocs_hw_get_num_chutes(hw) > 1) { hw->config.n_wq = 2; } /* make sure we haven't exceeded the max supported in the HW */ if (hw->config.n_wq > OCS_HW_MAX_NUM_WQ) { hw->config.n_wq = OCS_HW_MAX_NUM_WQ; } /* make sure we haven't exceeded the chip maximum */ if (hw->config.n_wq > max_wq_num) { hw->config.n_wq = max_wq_num; } /* * Using Queue Topology string, we divide by number of chutes */ hw->config.n_wq /= ocs_hw_get_num_chutes(hw); } static int32_t ocs_hw_command_process(ocs_hw_t *hw, int32_t status, uint8_t *mqe, size_t size) { ocs_command_ctx_t *ctx = NULL; ocs_lock(&hw->cmd_lock); if (NULL == (ctx = ocs_list_remove_head(&hw->cmd_head))) { ocs_log_err(hw->os, "XXX no command context?!?\n"); ocs_unlock(&hw->cmd_lock); return -1; } hw->cmd_head_count--; /* Post any pending requests */ ocs_hw_cmd_submit_pending(hw); ocs_unlock(&hw->cmd_lock); if (ctx->cb) { if (ctx->buf) { ocs_memcpy(ctx->buf, mqe, size); } ctx->cb(hw, status, ctx->buf, ctx->arg); } ocs_memset(ctx, 0, sizeof(ocs_command_ctx_t)); ocs_free(hw->os, ctx, sizeof(ocs_command_ctx_t)); return 0; } /** * @brief Process entries on the given mailbox queue. * * @param hw Hardware context. * @param status CQE status. * @param mq Pointer to the mailbox queue object. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_mq_process(ocs_hw_t *hw, int32_t status, sli4_queue_t *mq) { uint8_t mqe[SLI4_BMBX_SIZE]; if (!sli_queue_read(&hw->sli, mq, mqe)) { ocs_hw_command_process(hw, status, mqe, mq->size); } return 0; } /** * @brief Read a FCF table entry. * * @param hw Hardware context. * @param index Table index to read. Use SLI4_FCOE_FCF_TABLE_FIRST for the first * read and the next_index field from the FCOE_READ_FCF_TABLE command * for subsequent reads. * * @return Returns 0 on success, or a non-zero value on failure. */ static ocs_hw_rtn_e ocs_hw_read_fcf(ocs_hw_t *hw, uint32_t index) { uint8_t *buf = NULL; int32_t rc = OCS_HW_RTN_ERROR; buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } if (sli_cmd_fcoe_read_fcf_table(&hw->sli, buf, SLI4_BMBX_SIZE, &hw->fcf_dmem, index)) { rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, ocs_hw_cb_read_fcf, &hw->fcf_dmem); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "FCOE_READ_FCF_TABLE failed\n"); ocs_free(hw->os, buf, SLI4_BMBX_SIZE); } return rc; } /** * @brief Callback function for the FCOE_READ_FCF_TABLE command. * * @par Description * Note that the caller has allocated: * - DMA memory to hold the table contents * - DMA memory structure * - Command/results buffer * . * Each of these must be freed here. * * @param hw Hardware context. * @param status Hardware status. * @param mqe Pointer to the mailbox command/results buffer. * @param arg Pointer to the DMA memory structure. * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_cb_read_fcf(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_dma_t *dma = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; if (status || hdr->status) { ocs_log_test(hw->os, "bad status cqe=%#x mqe=%#x\n", status, hdr->status); } else if (dma->virt) { sli4_res_fcoe_read_fcf_table_t *read_fcf = dma->virt; /* if FC or FCOE and FCF entry valid, process it */ if (read_fcf->fcf_entry.fc || (read_fcf->fcf_entry.val && !read_fcf->fcf_entry.sol)) { if (hw->callback.domain != NULL) { ocs_domain_record_t drec = {0}; if (read_fcf->fcf_entry.fc) { /* * This is a pseudo FCF entry. Create a domain * record based on the read topology information */ drec.speed = hw->link.speed; drec.fc_id = hw->link.fc_id; drec.is_fc = TRUE; if (SLI_LINK_TOPO_LOOP == hw->link.topology) { drec.is_loop = TRUE; ocs_memcpy(drec.map.loop, hw->link.loop_map, sizeof(drec.map.loop)); } else if (SLI_LINK_TOPO_NPORT == hw->link.topology) { drec.is_nport = TRUE; } } else { drec.index = read_fcf->fcf_entry.fcf_index; drec.priority = read_fcf->fcf_entry.fip_priority; /* copy address, wwn and vlan_bitmap */ ocs_memcpy(drec.address, read_fcf->fcf_entry.fcf_mac_address, sizeof(drec.address)); ocs_memcpy(drec.wwn, read_fcf->fcf_entry.fabric_name_id, sizeof(drec.wwn)); ocs_memcpy(drec.map.vlan, read_fcf->fcf_entry.vlan_bitmap, sizeof(drec.map.vlan)); drec.is_ethernet = TRUE; drec.is_nport = TRUE; } hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_FOUND, &drec); } } else { /* if FCOE and FCF is not valid, ignore it */ ocs_log_test(hw->os, "ignore invalid FCF entry\n"); } if (SLI4_FCOE_FCF_TABLE_LAST != read_fcf->next_index) { ocs_hw_read_fcf(hw, read_fcf->next_index); } } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); //ocs_dma_free(hw->os, dma); //ocs_free(hw->os, dma, sizeof(ocs_dma_t)); return 0; } /** * @brief Callback function for the SLI link events. * * @par Description * This function allocates memory which must be freed in its callback. * * @param ctx Hardware context pointer (that is, ocs_hw_t *). * @param e Event structure pointer (that is, sli4_link_event_t *). * * @return Returns 0 on success, or a non-zero value on failure. */ static int32_t ocs_hw_cb_link(void *ctx, void *e) { ocs_hw_t *hw = ctx; sli4_link_event_t *event = e; ocs_domain_t *d = NULL; uint32_t i = 0; int32_t rc = OCS_HW_RTN_ERROR; ocs_t *ocs = hw->os; ocs_hw_link_event_init(hw); switch (event->status) { case SLI_LINK_STATUS_UP: hw->link = *event; if (SLI_LINK_TOPO_NPORT == event->topology) { device_printf(ocs->dev, "Link Up, NPORT, speed is %d\n", event->speed); ocs_hw_read_fcf(hw, SLI4_FCOE_FCF_TABLE_FIRST); } else if (SLI_LINK_TOPO_LOOP == event->topology) { uint8_t *buf = NULL; device_printf(ocs->dev, "Link Up, LOOP, speed is %d\n", event->speed); buf = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (!buf) { ocs_log_err(hw->os, "no buffer for command\n"); break; } if (sli_cmd_read_topology(&hw->sli, buf, SLI4_BMBX_SIZE, &hw->loop_map)) { rc = ocs_hw_command(hw, buf, OCS_CMD_NOWAIT, __ocs_read_topology_cb, NULL); } if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_test(hw->os, "READ_TOPOLOGY failed\n"); ocs_free(hw->os, buf, SLI4_BMBX_SIZE); } } else { device_printf(ocs->dev, "Link Up, unsupported topology (%#x), speed is %d\n", event->topology, event->speed); } break; case SLI_LINK_STATUS_DOWN: device_printf(ocs->dev, "Link Down\n"); hw->link.status = event->status; for (i = 0; i < SLI4_MAX_FCFI; i++) { d = hw->domains[i]; if (d != NULL && hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_LOST, d); } } break; default: ocs_log_test(hw->os, "unhandled link status %#x\n", event->status); break; } return 0; } static int32_t ocs_hw_cb_fip(void *ctx, void *e) { ocs_hw_t *hw = ctx; ocs_domain_t *domain = NULL; sli4_fip_event_t *event = e; ocs_hw_assert(event); ocs_hw_assert(hw); /* Find the associated domain object */ if (event->type == SLI4_FCOE_FIP_FCF_CLEAR_VLINK) { ocs_domain_t *d = NULL; uint32_t i = 0; /* Clear VLINK is different from the other FIP events as it passes back * a VPI instead of a FCF index. Check all attached SLI ports for a * matching VPI */ for (i = 0; i < SLI4_MAX_FCFI; i++) { d = hw->domains[i]; if (d != NULL) { ocs_sport_t *sport = NULL; ocs_list_foreach(&d->sport_list, sport) { if (sport->indicator == event->index) { domain = d; break; } } if (domain != NULL) { break; } } } } else { domain = ocs_hw_domain_get_indexed(hw, event->index); } switch (event->type) { case SLI4_FCOE_FIP_FCF_DISCOVERED: ocs_hw_read_fcf(hw, event->index); break; case SLI4_FCOE_FIP_FCF_DEAD: if (domain != NULL && hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_LOST, domain); } break; case SLI4_FCOE_FIP_FCF_CLEAR_VLINK: if (domain != NULL && hw->callback.domain != NULL) { /* * We will want to issue rediscover FCF when this domain is free'd in order * to invalidate the FCF table */ domain->req_rediscover_fcf = TRUE; hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_LOST, domain); } break; case SLI4_FCOE_FIP_FCF_MODIFIED: if (domain != NULL && hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_LOST, domain); } ocs_hw_read_fcf(hw, event->index); break; default: ocs_log_test(hw->os, "unsupported event %#x\n", event->type); } return 0; } static int32_t ocs_hw_cb_node_attach(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_remote_node_t *rnode = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_hw_remote_node_event_e evt = 0; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status cqe=%#x mqe=%#x\n", status, hdr->status); ocs_atomic_sub_return(&hw->rpi_ref[rnode->index].rpi_count, 1); rnode->attached = FALSE; ocs_atomic_set(&hw->rpi_ref[rnode->index].rpi_attached, 0); evt = OCS_HW_NODE_ATTACH_FAIL; } else { rnode->attached = TRUE; ocs_atomic_set(&hw->rpi_ref[rnode->index].rpi_attached, 1); evt = OCS_HW_NODE_ATTACH_OK; } if (hw->callback.rnode != NULL) { hw->callback.rnode(hw->args.rnode, evt, rnode); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } static int32_t ocs_hw_cb_node_free(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_remote_node_t *rnode = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_hw_remote_node_event_e evt = OCS_HW_NODE_FREE_FAIL; int32_t rc = 0; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status cqe=%#x mqe=%#x\n", status, hdr->status); /* * In certain cases, a non-zero MQE status is OK (all must be true): * - node is attached * - if High Login Mode is enabled, node is part of a node group * - status is 0x1400 */ if (!rnode->attached || ((sli_get_hlm(&hw->sli) == TRUE) && !rnode->node_group) || (hdr->status != SLI4_MBOX_STATUS_RPI_NOT_REG)) { rc = -1; } } if (rc == 0) { rnode->node_group = FALSE; rnode->attached = FALSE; if (ocs_atomic_read(&hw->rpi_ref[rnode->index].rpi_count) == 0) { ocs_atomic_set(&hw->rpi_ref[rnode->index].rpi_attached, 0); } evt = OCS_HW_NODE_FREE_OK; } if (hw->callback.rnode != NULL) { hw->callback.rnode(hw->args.rnode, evt, rnode); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return rc; } static int32_t ocs_hw_cb_node_free_all(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_hw_remote_node_event_e evt = OCS_HW_NODE_FREE_FAIL; int32_t rc = 0; uint32_t i; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status cqe=%#x mqe=%#x\n", status, hdr->status); } else { evt = OCS_HW_NODE_FREE_ALL_OK; } if (evt == OCS_HW_NODE_FREE_ALL_OK) { for (i = 0; i < sli_get_max_rsrc(&hw->sli, SLI_RSRC_FCOE_RPI); i++) { ocs_atomic_set(&hw->rpi_ref[i].rpi_count, 0); } if (sli_resource_reset(&hw->sli, SLI_RSRC_FCOE_RPI)) { ocs_log_test(hw->os, "FCOE_RPI free all failure\n"); rc = -1; } } if (hw->callback.rnode != NULL) { hw->callback.rnode(hw->args.rnode, evt, NULL); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return rc; } /** * @brief Initialize the pool of HW IO objects. * * @param hw Hardware context. * * @return Returns 0 on success, or a non-zero value on failure. */ static ocs_hw_rtn_e ocs_hw_setup_io(ocs_hw_t *hw) { uint32_t i = 0; ocs_hw_io_t *io = NULL; uintptr_t xfer_virt = 0; uintptr_t xfer_phys = 0; uint32_t index; uint8_t new_alloc = TRUE; if (NULL == hw->io) { hw->io = ocs_malloc(hw->os, hw->config.n_io * sizeof(ocs_hw_io_t *), OCS_M_ZERO | OCS_M_NOWAIT); if (NULL == hw->io) { ocs_log_err(hw->os, "IO pointer memory allocation failed, %d Ios at size %zu\n", hw->config.n_io, sizeof(ocs_hw_io_t *)); return OCS_HW_RTN_NO_MEMORY; } for (i = 0; i < hw->config.n_io; i++) { hw->io[i] = ocs_malloc(hw->os, sizeof(ocs_hw_io_t), OCS_M_ZERO | OCS_M_NOWAIT); if (hw->io[i] == NULL) { ocs_log_err(hw->os, "IO(%d) memory allocation failed\n", i); goto error; } } /* Create WQE buffs for IO */ hw->wqe_buffs = ocs_malloc(hw->os, hw->config.n_io * hw->sli.config.wqe_size, OCS_M_ZERO | OCS_M_NOWAIT); if (NULL == hw->wqe_buffs) { ocs_free(hw->os, hw->io, hw->config.n_io * sizeof(ocs_hw_io_t)); ocs_log_err(hw->os, "%s: IO WQE buff allocation failed, %d Ios at size %zu\n", __func__, hw->config.n_io, hw->sli.config.wqe_size); return OCS_HW_RTN_NO_MEMORY; } } else { /* re-use existing IOs, including SGLs */ new_alloc = FALSE; } if (new_alloc) { if (ocs_dma_alloc(hw->os, &hw->xfer_rdy, sizeof(fcp_xfer_rdy_iu_t) * hw->config.n_io, 4/*XXX what does this need to be? */)) { ocs_log_err(hw->os, "XFER_RDY buffer allocation failed\n"); return OCS_HW_RTN_NO_MEMORY; } } xfer_virt = (uintptr_t)hw->xfer_rdy.virt; xfer_phys = hw->xfer_rdy.phys; for (i = 0; i < hw->config.n_io; i++) { hw_wq_callback_t *wqcb; io = hw->io[i]; /* initialize IO fields */ io->hw = hw; /* Assign a WQE buff */ io->wqe.wqebuf = &hw->wqe_buffs[i * hw->sli.config.wqe_size]; /* Allocate the request tag for this IO */ wqcb = ocs_hw_reqtag_alloc(hw, ocs_hw_wq_process_io, io); if (wqcb == NULL) { ocs_log_err(hw->os, "can't allocate request tag\n"); return OCS_HW_RTN_NO_RESOURCES; } io->reqtag = wqcb->instance_index; /* Now for the fields that are initialized on each free */ ocs_hw_init_free_io(io); /* The XB flag isn't cleared on IO free, so initialize it to zero here */ io->xbusy = 0; if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_XRI, &io->indicator, &index)) { ocs_log_err(hw->os, "sli_resource_alloc failed @ %d\n", i); return OCS_HW_RTN_NO_MEMORY; } if (new_alloc && ocs_dma_alloc(hw->os, &io->def_sgl, hw->config.n_sgl * sizeof(sli4_sge_t), 64)) { ocs_log_err(hw->os, "ocs_dma_alloc failed @ %d\n", i); ocs_memset(&io->def_sgl, 0, sizeof(ocs_dma_t)); return OCS_HW_RTN_NO_MEMORY; } io->def_sgl_count = hw->config.n_sgl; io->sgl = &io->def_sgl; io->sgl_count = io->def_sgl_count; if (hw->xfer_rdy.size) { io->xfer_rdy.virt = (void *)xfer_virt; io->xfer_rdy.phys = xfer_phys; io->xfer_rdy.size = sizeof(fcp_xfer_rdy_iu_t); xfer_virt += sizeof(fcp_xfer_rdy_iu_t); xfer_phys += sizeof(fcp_xfer_rdy_iu_t); } } return OCS_HW_RTN_SUCCESS; error: for (i = 0; i < hw->config.n_io && hw->io[i]; i++) { ocs_free(hw->os, hw->io[i], sizeof(ocs_hw_io_t)); hw->io[i] = NULL; } return OCS_HW_RTN_NO_MEMORY; } static ocs_hw_rtn_e ocs_hw_init_io(ocs_hw_t *hw) { uint32_t i = 0, io_index = 0; uint32_t prereg = 0; ocs_hw_io_t *io = NULL; uint8_t cmd[SLI4_BMBX_SIZE]; ocs_hw_rtn_e rc = OCS_HW_RTN_SUCCESS; uint32_t nremaining; uint32_t n = 0; uint32_t sgls_per_request = 256; ocs_dma_t **sgls = NULL; ocs_dma_t reqbuf = { 0 }; prereg = sli_get_sgl_preregister(&hw->sli); if (prereg) { sgls = ocs_malloc(hw->os, sizeof(*sgls) * sgls_per_request, OCS_M_NOWAIT); if (sgls == NULL) { ocs_log_err(hw->os, "ocs_malloc sgls failed\n"); return OCS_HW_RTN_NO_MEMORY; } rc = ocs_dma_alloc(hw->os, &reqbuf, 32 + sgls_per_request*16, OCS_MIN_DMA_ALIGNMENT); if (rc) { ocs_log_err(hw->os, "ocs_dma_alloc reqbuf failed\n"); ocs_free(hw->os, sgls, sizeof(*sgls) * sgls_per_request); return OCS_HW_RTN_NO_MEMORY; } } io = hw->io[io_index]; for (nremaining = hw->config.n_io; nremaining; nremaining -= n) { if (prereg) { /* Copy address of SGL's into local sgls[] array, break out if the xri * is not contiguous. */ for (n = 0; n < MIN(sgls_per_request, nremaining); n++) { /* Check that we have contiguous xri values */ if (n > 0) { if (hw->io[io_index + n]->indicator != (hw->io[io_index + n-1]->indicator+1)) { break; } } sgls[n] = hw->io[io_index + n]->sgl; } if (sli_cmd_fcoe_post_sgl_pages(&hw->sli, cmd, sizeof(cmd), io->indicator, n, sgls, NULL, &reqbuf)) { if (ocs_hw_command(hw, cmd, OCS_CMD_POLL, NULL, NULL)) { rc = OCS_HW_RTN_ERROR; ocs_log_err(hw->os, "SGL post failed\n"); break; } } } else { n = nremaining; } /* Add to tail if successful */ for (i = 0; i < n; i ++) { io->is_port_owned = 0; io->state = OCS_HW_IO_STATE_FREE; ocs_list_add_tail(&hw->io_free, io); io = hw->io[io_index+1]; io_index++; } } if (prereg) { ocs_dma_free(hw->os, &reqbuf); ocs_free(hw->os, sgls, sizeof(*sgls) * sgls_per_request); } return rc; } static int32_t ocs_hw_flush(ocs_hw_t *hw) { uint32_t i = 0; /* Process any remaining completions */ for (i = 0; i < hw->eq_count; i++) { ocs_hw_process(hw, i, ~0); } return 0; } static int32_t ocs_hw_command_cancel(ocs_hw_t *hw) { ocs_lock(&hw->cmd_lock); /* * Manually clean up remaining commands. Note: since this calls * ocs_hw_command_process(), we'll also process the cmd_pending * list, so no need to manually clean that out. */ while (!ocs_list_empty(&hw->cmd_head)) { uint8_t mqe[SLI4_BMBX_SIZE] = { 0 }; ocs_command_ctx_t *ctx = ocs_list_get_head(&hw->cmd_head); ocs_log_test(hw->os, "hung command %08x\n", NULL == ctx ? UINT32_MAX : (NULL == ctx->buf ? UINT32_MAX : *((uint32_t *)ctx->buf))); ocs_unlock(&hw->cmd_lock); ocs_hw_command_process(hw, -1/*Bad status*/, mqe, SLI4_BMBX_SIZE); ocs_lock(&hw->cmd_lock); } ocs_unlock(&hw->cmd_lock); return 0; } /** * @brief Find IO given indicator (xri). * * @param hw Hal context. * @param indicator Indicator (xri) to look for. * * @return Returns io if found, NULL otherwise. */ ocs_hw_io_t * ocs_hw_io_lookup(ocs_hw_t *hw, uint32_t xri) { uint32_t ioindex; ioindex = xri - hw->sli.config.extent[SLI_RSRC_FCOE_XRI].base[0]; return hw->io[ioindex]; } /** * @brief Issue any pending callbacks for an IO and remove off the timer and pending lists. * * @param hw Hal context. * @param io Pointer to the IO to cleanup. */ static void ocs_hw_io_cancel_cleanup(ocs_hw_t *hw, ocs_hw_io_t *io) { ocs_hw_done_t done = io->done; ocs_hw_done_t abort_done = io->abort_done; /* first check active_wqe list and remove if there */ if (ocs_list_on_list(&io->wqe_link)) { ocs_list_remove(&hw->io_timed_wqe, io); } /* Remove from WQ pending list */ if ((io->wq != NULL) && ocs_list_on_list(&io->wq->pending_list)) { ocs_list_remove(&io->wq->pending_list, io); } if (io->done) { void *arg = io->arg; io->done = NULL; ocs_unlock(&hw->io_lock); done(io, io->rnode, 0, SLI4_FC_WCQE_STATUS_SHUTDOWN, 0, arg); ocs_lock(&hw->io_lock); } if (io->abort_done != NULL) { void *abort_arg = io->abort_arg; io->abort_done = NULL; ocs_unlock(&hw->io_lock); abort_done(io, io->rnode, 0, SLI4_FC_WCQE_STATUS_SHUTDOWN, 0, abort_arg); ocs_lock(&hw->io_lock); } } static int32_t ocs_hw_io_cancel(ocs_hw_t *hw) { ocs_hw_io_t *io = NULL; ocs_hw_io_t *tmp_io = NULL; uint32_t iters = 100; /* One second limit */ /* * Manually clean up outstanding IO. * Only walk through list once: the backend will cleanup any IOs when done/abort_done is called. */ ocs_lock(&hw->io_lock); ocs_list_foreach_safe(&hw->io_inuse, io, tmp_io) { ocs_hw_done_t done = io->done; ocs_hw_done_t abort_done = io->abort_done; ocs_hw_io_cancel_cleanup(hw, io); /* * Since this is called in a reset/shutdown * case, If there is no callback, then just * free the IO. * * Note: A port owned XRI cannot be on * the in use list. We cannot call * ocs_hw_io_free() because we already * hold the io_lock. */ if (done == NULL && abort_done == NULL) { /* * Since this is called in a reset/shutdown * case, If there is no callback, then just * free the IO. */ ocs_hw_io_free_common(hw, io); ocs_list_remove(&hw->io_inuse, io); ocs_hw_io_free_move_correct_list(hw, io); } } /* * For port owned XRIs, they are not on the in use list, so * walk though XRIs and issue any callbacks. */ ocs_list_foreach_safe(&hw->io_port_owned, io, tmp_io) { /* check list and remove if there */ if (ocs_list_on_list(&io->dnrx_link)) { ocs_list_remove(&hw->io_port_dnrx, io); ocs_ref_put(&io->ref); /* ocs_ref_get(): same function */ } ocs_hw_io_cancel_cleanup(hw, io); ocs_list_remove(&hw->io_port_owned, io); ocs_hw_io_free_common(hw, io); } ocs_unlock(&hw->io_lock); /* Give time for the callbacks to complete */ do { ocs_udelay(10000); iters--; } while (!ocs_list_empty(&hw->io_inuse) && iters); /* Leave a breadcrumb that cleanup is not yet complete. */ if (!ocs_list_empty(&hw->io_inuse)) { ocs_log_test(hw->os, "io_inuse list is not empty\n"); } return 0; } static int32_t ocs_hw_io_ini_sge(ocs_hw_t *hw, ocs_hw_io_t *io, ocs_dma_t *cmnd, uint32_t cmnd_size, ocs_dma_t *rsp) { sli4_sge_t *data = NULL; if (!hw || !io) { ocs_log_err(NULL, "bad parm hw=%p io=%p\n", hw, io); return OCS_HW_RTN_ERROR; } data = io->def_sgl.virt; /* setup command pointer */ data->buffer_address_high = ocs_addr32_hi(cmnd->phys); data->buffer_address_low = ocs_addr32_lo(cmnd->phys); data->buffer_length = cmnd_size; data++; /* setup response pointer */ data->buffer_address_high = ocs_addr32_hi(rsp->phys); data->buffer_address_low = ocs_addr32_lo(rsp->phys); data->buffer_length = rsp->size; return 0; } static int32_t __ocs_read_topology_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_read_topology_t *read_topo = (sli4_cmd_read_topology_t *)mqe; if (status || read_topo->hdr.status) { ocs_log_debug(hw->os, "bad status cqe=%#x mqe=%#x\n", status, read_topo->hdr.status); ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return -1; } switch (read_topo->attention_type) { case SLI4_READ_TOPOLOGY_LINK_UP: hw->link.status = SLI_LINK_STATUS_UP; break; case SLI4_READ_TOPOLOGY_LINK_DOWN: hw->link.status = SLI_LINK_STATUS_DOWN; break; case SLI4_READ_TOPOLOGY_LINK_NO_ALPA: hw->link.status = SLI_LINK_STATUS_NO_ALPA; break; default: hw->link.status = SLI_LINK_STATUS_MAX; break; } switch (read_topo->topology) { case SLI4_READ_TOPOLOGY_NPORT: hw->link.topology = SLI_LINK_TOPO_NPORT; break; case SLI4_READ_TOPOLOGY_FC_AL: hw->link.topology = SLI_LINK_TOPO_LOOP; if (SLI_LINK_STATUS_UP == hw->link.status) { hw->link.loop_map = hw->loop_map.virt; } hw->link.fc_id = read_topo->acquired_al_pa; break; default: hw->link.topology = SLI_LINK_TOPO_MAX; break; } hw->link.medium = SLI_LINK_MEDIUM_FC; switch (read_topo->link_current.link_speed) { case SLI4_READ_TOPOLOGY_SPEED_1G: hw->link.speed = 1 * 1000; break; case SLI4_READ_TOPOLOGY_SPEED_2G: hw->link.speed = 2 * 1000; break; case SLI4_READ_TOPOLOGY_SPEED_4G: hw->link.speed = 4 * 1000; break; case SLI4_READ_TOPOLOGY_SPEED_8G: hw->link.speed = 8 * 1000; break; case SLI4_READ_TOPOLOGY_SPEED_16G: hw->link.speed = 16 * 1000; hw->link.loop_map = NULL; break; case SLI4_READ_TOPOLOGY_SPEED_32G: hw->link.speed = 32 * 1000; hw->link.loop_map = NULL; break; } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); ocs_hw_read_fcf(hw, SLI4_FCOE_FCF_TABLE_FIRST); return 0; } static int32_t __ocs_hw_port_common(const char *funcname, ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_EXIT: /* ignore */ break; case OCS_EVT_HW_PORT_REQ_FREE: case OCS_EVT_HW_PORT_REQ_ATTACH: if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } /* fall through */ default: ocs_log_test(hw->os, "%s %-20s not handled\n", funcname, ocs_sm_event_name(evt)); break; } return 0; } static void * __ocs_hw_port_free_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_FREE_FAIL, sport); } break; default: break; } return NULL; } static void * __ocs_hw_port_freed(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* free SLI resource */ if (sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VPI, sport->indicator)) { ocs_log_err(hw->os, "FCOE_VPI free failure addr=%#x\n", sport->fc_id); } /* free mailbox buffer */ if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_FREE_OK, sport); } break; default: break; } return NULL; } static void * __ocs_hw_port_attach_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* free SLI resource */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VPI, sport->indicator); /* free mailbox buffer */ if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_ATTACH_FAIL, sport); } if (sport->sm_free_req_pending) { ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); } break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_free_unreg_vpi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; uint8_t *cmd = NULL; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* allocate memory and send unreg_vpi */ cmd = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (!cmd) { ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (0 == sli_cmd_unreg_vpi(&hw->sli, cmd, SLI4_BMBX_SIZE, sport->indicator, SLI4_UNREG_TYPE_PORT)) { ocs_log_err(hw->os, "UNREG_VPI format failure\n"); ocs_free(hw->os, cmd, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, cmd, OCS_CMD_NOWAIT, __ocs_hw_port_cb, sport)) { ocs_log_err(hw->os, "UNREG_VPI command failure\n"); ocs_free(hw->os, cmd, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_port_freed, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_port_free_report_fail, data); break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_free_nop(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* Forward to execute in mailbox completion processing context */ if (ocs_hw_async_call(hw, __ocs_hw_port_realloc_cb, sport)) { ocs_log_err(hw->os, "ocs_hw_async_call failed\n"); } break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_port_freed, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_port_free_report_fail, data); break; default: break; } return NULL; } static void * __ocs_hw_port_attached(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_ATTACH_OK, sport); } if (sport->sm_free_req_pending) { ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); } break; case OCS_EVT_HW_PORT_REQ_FREE: /* virtual/physical port request free */ ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_attach_reg_vpi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: if (0 == sli_cmd_reg_vpi(&hw->sli, data, SLI4_BMBX_SIZE, sport, FALSE)) { ocs_log_err(hw->os, "REG_VPI format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_port_cb, sport)) { ocs_log_err(hw->os, "REG_VPI command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_port_attached, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_port_attach_report_fail, data); break; case OCS_EVT_HW_PORT_REQ_FREE: /* Wait for attach response and then free */ sport->sm_free_req_pending = 1; break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_done(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* free SLI resource */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VPI, sport->indicator); /* free mailbox buffer */ if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_allocated(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_ALLOC_OK, sport); } /* If there is a pending free request, then handle it now */ if (sport->sm_free_req_pending) { ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); } break; case OCS_EVT_HW_PORT_REQ_ATTACH: /* virtual port requests attach */ ocs_sm_transition(ctx, __ocs_hw_port_attach_reg_vpi, data); break; case OCS_EVT_HW_PORT_ATTACH_OK: /* physical port attached (as part of attaching domain) */ ocs_sm_transition(ctx, __ocs_hw_port_attached, data); break; case OCS_EVT_HW_PORT_REQ_FREE: /* virtual port request free */ if (SLI4_IF_TYPE_LANCER_FC_ETH == sli_get_if_type(&hw->sli)) { ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); } else { /* * Note: BE3/Skyhawk will respond with a status of 0x20 * unless the reg_vpi has been issued, so we can * skip the unreg_vpi for these adapters. * * Send a nop to make sure that free doesn't occur in * same context */ ocs_sm_transition(ctx, __ocs_hw_port_free_nop, NULL); } break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_alloc_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* free SLI resource */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VPI, sport->indicator); /* free mailbox buffer */ if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } if (hw->callback.port != NULL) { hw->callback.port(hw->args.port, OCS_HW_PORT_ALLOC_FAIL, sport); } /* If there is a pending free request, then handle it now */ if (sport->sm_free_req_pending) { ocs_sm_transition(ctx, __ocs_hw_port_free_unreg_vpi, NULL); } break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_alloc_read_sparm64(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; uint8_t *payload = NULL; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* allocate memory for the service parameters */ if (ocs_dma_alloc(hw->os, &sport->dma, 112, 4)) { ocs_log_err(hw->os, "Failed to allocate DMA memory\n"); ocs_sm_transition(ctx, __ocs_hw_port_done, data); break; } if (0 == sli_cmd_read_sparm64(&hw->sli, data, SLI4_BMBX_SIZE, &sport->dma, sport->indicator)) { ocs_log_err(hw->os, "READ_SPARM64 allocation failure\n"); ocs_dma_free(hw->os, &sport->dma); ocs_sm_transition(ctx, __ocs_hw_port_done, data); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_port_cb, sport)) { ocs_log_err(hw->os, "READ_SPARM64 command failure\n"); ocs_dma_free(hw->os, &sport->dma); ocs_sm_transition(ctx, __ocs_hw_port_done, data); break; } break; case OCS_EVT_RESPONSE: payload = sport->dma.virt; ocs_display_sparams(sport->display_name, "sport sparm64", 0, NULL, payload); ocs_memcpy(&sport->sli_wwpn, payload + SLI4_READ_SPARM64_WWPN_OFFSET, sizeof(sport->sli_wwpn)); ocs_memcpy(&sport->sli_wwnn, payload + SLI4_READ_SPARM64_WWNN_OFFSET, sizeof(sport->sli_wwnn)); ocs_dma_free(hw->os, &sport->dma); ocs_sm_transition(ctx, __ocs_hw_port_alloc_init_vpi, data); break; case OCS_EVT_ERROR: ocs_dma_free(hw->os, &sport->dma); ocs_sm_transition(ctx, __ocs_hw_port_alloc_report_fail, data); break; case OCS_EVT_HW_PORT_REQ_FREE: /* Wait for attach response and then free */ sport->sm_free_req_pending = 1; break; case OCS_EVT_EXIT: break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_alloc_init(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* no-op */ break; case OCS_EVT_HW_PORT_ALLOC_OK: ocs_sm_transition(ctx, __ocs_hw_port_allocated, NULL); break; case OCS_EVT_HW_PORT_ALLOC_FAIL: ocs_sm_transition(ctx, __ocs_hw_port_alloc_report_fail, NULL); break; case OCS_EVT_HW_PORT_REQ_FREE: /* Wait for attach response and then free */ sport->sm_free_req_pending = 1; break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_port_alloc_init_vpi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_sli_port_t *sport = ctx->app; ocs_hw_t *hw = sport->hw; smtrace("port"); switch (evt) { case OCS_EVT_ENTER: /* If there is a pending free request, then handle it now */ if (sport->sm_free_req_pending) { ocs_sm_transition(ctx, __ocs_hw_port_freed, NULL); return NULL; } /* TODO XXX transitioning to done only works if this is called * directly from ocs_hw_port_alloc BUT not if called from * read_sparm64. In the later case, we actually want to go * through report_ok/fail */ if (0 == sli_cmd_init_vpi(&hw->sli, data, SLI4_BMBX_SIZE, sport->indicator, sport->domain->indicator)) { ocs_log_err(hw->os, "INIT_VPI allocation failure\n"); ocs_sm_transition(ctx, __ocs_hw_port_done, data); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_port_cb, sport)) { ocs_log_err(hw->os, "INIT_VPI command failure\n"); ocs_sm_transition(ctx, __ocs_hw_port_done, data); break; } break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_port_allocated, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_port_alloc_report_fail, data); break; case OCS_EVT_HW_PORT_REQ_FREE: /* Wait for attach response and then free */ sport->sm_free_req_pending = 1; break; case OCS_EVT_EXIT: break; default: __ocs_hw_port_common(__func__, ctx, evt, data); break; } return NULL; } static int32_t __ocs_hw_port_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_sli_port_t *sport = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_sm_event_t evt; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status vpi=%#x st=%x hdr=%x\n", sport->indicator, status, hdr->status); evt = OCS_EVT_ERROR; } else { evt = OCS_EVT_RESPONSE; } ocs_sm_post_event(&sport->ctx, evt, mqe); return 0; } static int32_t __ocs_hw_port_realloc_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_sli_port_t *sport = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_sm_event_t evt; uint8_t *mqecpy; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status vpi=%#x st=%x hdr=%x\n", sport->indicator, status, hdr->status); evt = OCS_EVT_ERROR; } else { evt = OCS_EVT_RESPONSE; } /* * In this case we have to malloc a mailbox command buffer, as it is reused * in the state machine post event call, and eventually freed */ mqecpy = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (mqecpy == NULL) { ocs_log_err(hw->os, "malloc mqecpy failed\n"); return -1; } ocs_memcpy(mqecpy, mqe, SLI4_BMBX_SIZE); ocs_sm_post_event(&sport->ctx, evt, mqecpy); return 0; } /*************************************************************************** * Domain state machine */ static int32_t __ocs_hw_domain_common(const char *funcname, ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_EXIT: /* ignore */ break; default: ocs_log_test(hw->os, "%s %-20s not handled\n", funcname, ocs_sm_event_name(evt)); break; } return 0; } static void * __ocs_hw_domain_alloc_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: /* free command buffer */ if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } /* free SLI resources */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VFI, domain->indicator); /* TODO how to free FCFI (or do we at all)? */ if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_ALLOC_FAIL, domain); } break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_attached(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: /* free mailbox buffer and send alloc ok to physical sport */ ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(&domain->sport->ctx, OCS_EVT_HW_PORT_ATTACH_OK, NULL); /* now inform registered callbacks */ if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_ATTACH_OK, domain); } break; case OCS_EVT_HW_DOMAIN_REQ_FREE: ocs_sm_transition(ctx, __ocs_hw_domain_free_unreg_vfi, NULL); break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_attach_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (data != NULL) { ocs_free(hw->os, data, SLI4_BMBX_SIZE); } /* free SLI resources */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VFI, domain->indicator); /* TODO how to free FCFI (or do we at all)? */ if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_ATTACH_FAIL, domain); } break; case OCS_EVT_EXIT: break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_attach_reg_vfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: ocs_display_sparams("", "reg vpi", 0, NULL, domain->dma.virt); if (0 == sli_cmd_reg_vfi(&hw->sli, data, SLI4_BMBX_SIZE, domain)) { ocs_log_err(hw->os, "REG_VFI format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "REG_VFI command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_domain_attached, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_domain_attach_report_fail, data); break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_allocated(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: /* free mailbox buffer and send alloc ok to physical sport */ ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(&domain->sport->ctx, OCS_EVT_HW_PORT_ALLOC_OK, NULL); ocs_hw_domain_add(hw, domain); /* now inform registered callbacks */ if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_ALLOC_OK, domain); } break; case OCS_EVT_HW_DOMAIN_REQ_ATTACH: ocs_sm_transition(ctx, __ocs_hw_domain_attach_reg_vfi, data); break; case OCS_EVT_HW_DOMAIN_REQ_FREE: /* unreg_fcfi/vfi */ if (SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) { ocs_sm_transition(ctx, __ocs_hw_domain_free_unreg_fcfi, NULL); } else { ocs_sm_transition(ctx, __ocs_hw_domain_free_unreg_vfi, NULL); } break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_alloc_read_sparm64(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (0 == sli_cmd_read_sparm64(&hw->sli, data, SLI4_BMBX_SIZE, &domain->dma, SLI4_READ_SPARM64_VPI_DEFAULT)) { ocs_log_err(hw->os, "READ_SPARM64 format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "READ_SPARM64 command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_EXIT: break; case OCS_EVT_RESPONSE: ocs_display_sparams(domain->display_name, "domain sparm64", 0, NULL, domain->dma.virt); ocs_sm_transition(ctx, __ocs_hw_domain_allocated, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_domain_alloc_report_fail, data); break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_alloc_init_vfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_sli_port_t *sport = domain->sport; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (0 == sli_cmd_init_vfi(&hw->sli, data, SLI4_BMBX_SIZE, domain->indicator, domain->fcf_indicator, sport->indicator)) { ocs_log_err(hw->os, "INIT_VFI format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "INIT_VFI command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_EXIT: break; case OCS_EVT_RESPONSE: ocs_sm_transition(ctx, __ocs_hw_domain_alloc_read_sparm64, data); break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_domain_alloc_report_fail, data); break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_alloc_reg_fcfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: { sli4_cmd_rq_cfg_t rq_cfg[SLI4_CMD_REG_FCFI_NUM_RQ_CFG]; uint32_t i; /* Set the filter match/mask values from hw's filter_def values */ for (i = 0; i < SLI4_CMD_REG_FCFI_NUM_RQ_CFG; i++) { rq_cfg[i].rq_id = 0xffff; rq_cfg[i].r_ctl_mask = (uint8_t) hw->config.filter_def[i]; rq_cfg[i].r_ctl_match = (uint8_t) (hw->config.filter_def[i] >> 8); rq_cfg[i].type_mask = (uint8_t) (hw->config.filter_def[i] >> 16); rq_cfg[i].type_match = (uint8_t) (hw->config.filter_def[i] >> 24); } /* Set the rq_id for each, in order of RQ definition */ for (i = 0; i < hw->hw_rq_count; i++) { if (i >= ARRAY_SIZE(rq_cfg)) { ocs_log_warn(hw->os, "more RQs than REG_FCFI filter entries\n"); break; } rq_cfg[i].rq_id = hw->hw_rq[i]->hdr->id; } if (!data) { ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (hw->hw_mrq_count) { if (OCS_HW_RTN_SUCCESS != ocs_hw_config_mrq(hw, SLI4_CMD_REG_FCFI_SET_FCFI_MODE, domain->vlan_id, domain->fcf)) { ocs_log_err(hw->os, "REG_FCFI_MRQ format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } } else { if (0 == sli_cmd_reg_fcfi(&hw->sli, data, SLI4_BMBX_SIZE, domain->fcf, rq_cfg, domain->vlan_id)) { ocs_log_err(hw->os, "REG_FCFI format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "REG_FCFI command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; } case OCS_EVT_EXIT: break; case OCS_EVT_RESPONSE: if (!data) { ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } domain->fcf_indicator = ((sli4_cmd_reg_fcfi_t *)data)->fcfi; /* * IF_TYPE 0 devices do not support explicit VFI and VPI initialization * and instead rely on implicit initialization during VFI registration. * Short circuit normal processing here for those devices. */ if (SLI4_IF_TYPE_BE3_SKH_PF == sli_get_if_type(&hw->sli)) { ocs_sm_transition(ctx, __ocs_hw_domain_alloc_read_sparm64, data); } else { ocs_sm_transition(ctx, __ocs_hw_domain_alloc_init_vfi, data); } break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_domain_alloc_report_fail, data); break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_init(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (sli_get_medium(&hw->sli) == SLI_LINK_MEDIUM_FC) { /* * For FC, the HW alread registered a FCFI * Copy FCF information into the domain and jump to INIT_VFI */ domain->fcf_indicator = hw->fcf_indicator; ocs_sm_transition(&domain->sm, __ocs_hw_domain_alloc_init_vfi, data); } else { ocs_sm_transition(&domain->sm, __ocs_hw_domain_alloc_reg_fcfi, data); } break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_free_report_fail(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (domain != NULL) { ocs_hw_t *hw = domain->hw; ocs_hw_domain_del(hw, domain); if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_FREE_FAIL, domain); } } /* free command buffer */ if (data != NULL) { ocs_free(domain != NULL ? domain->hw->os : NULL, data, SLI4_BMBX_SIZE); } break; case OCS_EVT_EXIT: break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_freed(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: /* Free DMA and mailbox buffer */ if (domain != NULL) { ocs_hw_t *hw = domain->hw; /* free VFI resource */ sli_resource_free(&hw->sli, SLI_RSRC_FCOE_VFI, domain->indicator); ocs_hw_domain_del(hw, domain); /* inform registered callbacks */ if (hw->callback.domain != NULL) { hw->callback.domain(hw->args.domain, OCS_HW_DOMAIN_FREE_OK, domain); } } if (data != NULL) { ocs_free(NULL, data, SLI4_BMBX_SIZE); } break; case OCS_EVT_EXIT: break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_free_redisc_fcf(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: /* if we're in the middle of a teardown, skip sending rediscover */ if (hw->state == OCS_HW_STATE_TEARDOWN_IN_PROGRESS) { ocs_sm_transition(ctx, __ocs_hw_domain_freed, data); break; } if (0 == sli_cmd_fcoe_rediscover_fcf(&hw->sli, data, SLI4_BMBX_SIZE, domain->fcf)) { ocs_log_err(hw->os, "REDISCOVER_FCF format failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "REDISCOVER_FCF command failure\n"); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); } break; case OCS_EVT_RESPONSE: case OCS_EVT_ERROR: /* REDISCOVER_FCF can fail if none exist */ ocs_sm_transition(ctx, __ocs_hw_domain_freed, data); break; case OCS_EVT_EXIT: break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_free_unreg_fcfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; smtrace("domain"); switch (evt) { case OCS_EVT_ENTER: if (data == NULL) { data = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (!data) { ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } } if (0 == sli_cmd_unreg_fcfi(&hw->sli, data, SLI4_BMBX_SIZE, domain->fcf_indicator)) { ocs_log_err(hw->os, "UNREG_FCFI format failure\n"); ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "UNREG_FCFI command failure\n"); ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_RESPONSE: if (domain->req_rediscover_fcf) { domain->req_rediscover_fcf = FALSE; ocs_sm_transition(ctx, __ocs_hw_domain_free_redisc_fcf, data); } else { ocs_sm_transition(ctx, __ocs_hw_domain_freed, data); } break; case OCS_EVT_ERROR: ocs_sm_transition(ctx, __ocs_hw_domain_free_report_fail, data); break; case OCS_EVT_EXIT: break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } static void * __ocs_hw_domain_free_unreg_vfi(ocs_sm_ctx_t *ctx, ocs_sm_event_t evt, void *data) { ocs_domain_t *domain = ctx->app; ocs_hw_t *hw = domain->hw; uint8_t is_fc = FALSE; smtrace("domain"); is_fc = (sli_get_medium(&hw->sli) == SLI_LINK_MEDIUM_FC); switch (evt) { case OCS_EVT_ENTER: if (data == NULL) { data = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_ZERO | OCS_M_NOWAIT); if (!data) { ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } } if (0 == sli_cmd_unreg_vfi(&hw->sli, data, SLI4_BMBX_SIZE, domain, SLI4_UNREG_TYPE_DOMAIN)) { ocs_log_err(hw->os, "UNREG_VFI format failure\n"); ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } if (ocs_hw_command(hw, data, OCS_CMD_NOWAIT, __ocs_hw_domain_cb, domain)) { ocs_log_err(hw->os, "UNREG_VFI command failure\n"); ocs_free(hw->os, data, SLI4_BMBX_SIZE); ocs_sm_post_event(ctx, OCS_EVT_ERROR, NULL); break; } break; case OCS_EVT_ERROR: if (is_fc) { ocs_sm_transition(ctx, __ocs_hw_domain_free_report_fail, data); } else { ocs_sm_transition(ctx, __ocs_hw_domain_free_unreg_fcfi, data); } break; case OCS_EVT_RESPONSE: if (is_fc) { ocs_sm_transition(ctx, __ocs_hw_domain_freed, data); } else { ocs_sm_transition(ctx, __ocs_hw_domain_free_unreg_fcfi, data); } break; default: __ocs_hw_domain_common(__func__, ctx, evt, data); break; } return NULL; } /* callback for domain alloc/attach/free */ static int32_t __ocs_hw_domain_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_domain_t *domain = arg; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; ocs_sm_event_t evt; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status vfi=%#x st=%x hdr=%x\n", domain->indicator, status, hdr->status); evt = OCS_EVT_ERROR; } else { evt = OCS_EVT_RESPONSE; } ocs_sm_post_event(&domain->sm, evt, mqe); return 0; } static int32_t target_wqe_timer_nop_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_io_t *io = NULL; ocs_hw_io_t *io_next = NULL; uint64_t ticks_current = ocs_get_os_ticks(); uint32_t sec_elapsed; ocs_hw_rtn_e rc; sli4_mbox_command_header_t *hdr = (sli4_mbox_command_header_t *)mqe; if (status || hdr->status) { ocs_log_debug(hw->os, "bad status st=%x hdr=%x\n", status, hdr->status); /* go ahead and proceed with wqe timer checks... */ } /* loop through active WQE list and check for timeouts */ ocs_lock(&hw->io_lock); ocs_list_foreach_safe(&hw->io_timed_wqe, io, io_next) { sec_elapsed = ((ticks_current - io->submit_ticks) / ocs_get_os_tick_freq()); /* * If elapsed time > timeout, abort it. No need to check type since * it wouldn't be on this list unless it was a target WQE */ if (sec_elapsed > io->tgt_wqe_timeout) { ocs_log_test(hw->os, "IO timeout xri=0x%x tag=0x%x type=%d\n", io->indicator, io->reqtag, io->type); /* remove from active_wqe list so won't try to abort again */ ocs_list_remove(&hw->io_timed_wqe, io); /* save status of "timed out" for when abort completes */ io->status_saved = 1; io->saved_status = SLI4_FC_WCQE_STATUS_TARGET_WQE_TIMEOUT; io->saved_ext = 0; io->saved_len = 0; /* now abort outstanding IO */ rc = ocs_hw_io_abort(hw, io, FALSE, NULL, NULL); if (rc) { ocs_log_test(hw->os, "abort failed xri=%#x tag=%#x rc=%d\n", io->indicator, io->reqtag, rc); } } /* * need to go through entire list since each IO could have a * different timeout value */ } ocs_unlock(&hw->io_lock); /* if we're not in the middle of shutting down, schedule next timer */ if (!hw->active_wqe_timer_shutdown) { ocs_setup_timer(hw->os, &hw->wqe_timer, target_wqe_timer_cb, hw, OCS_HW_WQ_TIMER_PERIOD_MS); } hw->in_active_wqe_timer = FALSE; return 0; } static void target_wqe_timer_cb(void *arg) { ocs_hw_t *hw = (ocs_hw_t *)arg; /* delete existing timer; will kick off new timer after checking wqe timeouts */ hw->in_active_wqe_timer = TRUE; ocs_del_timer(&hw->wqe_timer); /* Forward timer callback to execute in the mailbox completion processing context */ if (ocs_hw_async_call(hw, target_wqe_timer_nop_cb, hw)) { ocs_log_test(hw->os, "ocs_hw_async_call failed\n"); } } static void shutdown_target_wqe_timer(ocs_hw_t *hw) { uint32_t iters = 100; if (hw->config.emulate_tgt_wqe_timeout) { /* request active wqe timer shutdown, then wait for it to complete */ hw->active_wqe_timer_shutdown = TRUE; /* delete WQE timer and wait for timer handler to complete (if necessary) */ ocs_del_timer(&hw->wqe_timer); /* now wait for timer handler to complete (if necessary) */ while (hw->in_active_wqe_timer && iters) { /* * if we happen to have just sent NOP mailbox command, make sure * completions are being processed */ ocs_hw_flush(hw); iters--; } if (iters == 0) { ocs_log_test(hw->os, "Failed to shutdown active wqe timer\n"); } } } /** * @brief Determine if HW IO is owned by the port. * * @par Description * Determines if the given HW IO has been posted to the chip. * * @param hw Hardware context allocated by the caller. * @param io HW IO. * * @return Returns TRUE if given HW IO is port-owned. */ uint8_t ocs_hw_is_io_port_owned(ocs_hw_t *hw, ocs_hw_io_t *io) { /* Check to see if this is a port owned XRI */ return io->is_port_owned; } /** * @brief Return TRUE if exchange is port-owned. * * @par Description * Test to see if the xri is a port-owned xri. * * @param hw Hardware context. * @param xri Exchange indicator. * * @return Returns TRUE if XRI is a port owned XRI. */ uint8_t ocs_hw_is_xri_port_owned(ocs_hw_t *hw, uint32_t xri) { ocs_hw_io_t *io = ocs_hw_io_lookup(hw, xri); return (io == NULL ? FALSE : io->is_port_owned); } /** * @brief Returns an XRI from the port owned list to the host. * * @par Description * Used when the POST_XRI command fails as well as when the RELEASE_XRI completes. * * @param hw Hardware context. * @param xri_base The starting XRI number. * @param xri_count The number of XRIs to free from the base. */ static void ocs_hw_reclaim_xri(ocs_hw_t *hw, uint16_t xri_base, uint16_t xri_count) { ocs_hw_io_t *io; uint32_t i; for (i = 0; i < xri_count; i++) { io = ocs_hw_io_lookup(hw, xri_base + i); /* * if this is an auto xfer rdy XRI, then we need to release any * buffer attached to the XRI before moving the XRI back to the free pool. */ if (hw->auto_xfer_rdy_enabled) { ocs_hw_rqpair_auto_xfer_rdy_move_to_host(hw, io); } ocs_lock(&hw->io_lock); ocs_list_remove(&hw->io_port_owned, io); io->is_port_owned = 0; ocs_list_add_tail(&hw->io_free, io); ocs_unlock(&hw->io_lock); } } /** * @brief Called when the POST_XRI command completes. * * @par Description * Free the mailbox command buffer and reclaim the XRIs on failure. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * * @return Returns 0. */ static int32_t ocs_hw_cb_post_xri(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_post_xri_t *post_xri = (sli4_cmd_post_xri_t*)mqe; /* Reclaim the XRIs as host owned if the command fails */ if (status != 0) { ocs_log_debug(hw->os, "Status 0x%x for XRI base 0x%x, cnt =x%x\n", status, post_xri->xri_base, post_xri->xri_count); ocs_hw_reclaim_xri(hw, post_xri->xri_base, post_xri->xri_count); } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @brief Issues a mailbox command to move XRIs from the host-controlled pool to the port. * * @param hw Hardware context. * @param xri_start The starting XRI to post. * @param num_to_post The number of XRIs to post. * * @return Returns OCS_HW_RTN_NO_MEMORY, OCS_HW_RTN_ERROR, or OCS_HW_RTN_SUCCESS. */ static ocs_hw_rtn_e ocs_hw_post_xri(ocs_hw_t *hw, uint32_t xri_start, uint32_t num_to_post) { uint8_t *post_xri; ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; /* Since we need to allocate for mailbox queue, just always allocate */ post_xri = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (post_xri == NULL) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } /* Register the XRIs */ if (sli_cmd_post_xri(&hw->sli, post_xri, SLI4_BMBX_SIZE, xri_start, num_to_post)) { rc = ocs_hw_command(hw, post_xri, OCS_CMD_NOWAIT, ocs_hw_cb_post_xri, NULL); if (rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, post_xri, SLI4_BMBX_SIZE); ocs_log_err(hw->os, "post_xri failed\n"); } } return rc; } /** * @brief Move XRIs from the host-controlled pool to the port. * * @par Description * Removes IOs from the free list and moves them to the port. * * @param hw Hardware context. * @param num_xri The number of XRIs being requested to move to the chip. * * @return Returns the number of XRIs that were moved. */ uint32_t ocs_hw_xri_move_to_port_owned(ocs_hw_t *hw, uint32_t num_xri) { ocs_hw_io_t *io; uint32_t i; uint32_t num_posted = 0; /* * Note: We cannot use ocs_hw_io_alloc() because that would place the * IO on the io_inuse list. We need to move from the io_free to * the io_port_owned list. */ ocs_lock(&hw->io_lock); for (i = 0; i < num_xri; i++) { if (NULL != (io = ocs_list_remove_head(&hw->io_free))) { ocs_hw_rtn_e rc; /* * if this is an auto xfer rdy XRI, then we need to attach a * buffer to the XRI before submitting it to the chip. If a * buffer is unavailable, then we cannot post it, so return it * to the free pool. */ if (hw->auto_xfer_rdy_enabled) { /* Note: uses the IO lock to get the auto xfer rdy buffer */ ocs_unlock(&hw->io_lock); rc = ocs_hw_rqpair_auto_xfer_rdy_move_to_port(hw, io); ocs_lock(&hw->io_lock); if (rc != OCS_HW_RTN_SUCCESS) { ocs_list_add_head(&hw->io_free, io); break; } } ocs_lock_init(hw->os, &io->axr_lock, "HW_axr_lock[%d]", io->indicator); io->is_port_owned = 1; ocs_list_add_tail(&hw->io_port_owned, io); /* Post XRI */ if (ocs_hw_post_xri(hw, io->indicator, 1) != OCS_HW_RTN_SUCCESS ) { ocs_hw_reclaim_xri(hw, io->indicator, i); break; } num_posted++; } else { /* no more free XRIs */ break; } } ocs_unlock(&hw->io_lock); return num_posted; } /** * @brief Called when the RELEASE_XRI command completes. * * @par Description * Move the IOs back to the free pool on success. * * @param hw Hardware context. * @param status Status field from the mbox completion. * @param mqe Mailbox response structure. * @param arg Pointer to a callback function that signals the caller that the command is done. * * @return Returns 0. */ static int32_t ocs_hw_cb_release_xri(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { sli4_cmd_release_xri_t *release_xri = (sli4_cmd_release_xri_t*)mqe; uint8_t i; /* Reclaim the XRIs as host owned if the command fails */ if (status != 0) { ocs_log_err(hw->os, "Status 0x%x\n", status); } else { for (i = 0; i < release_xri->released_xri_count; i++) { uint16_t xri = ((i & 1) == 0 ? release_xri->xri_tbl[i/2].xri_tag0 : release_xri->xri_tbl[i/2].xri_tag1); ocs_hw_reclaim_xri(hw, xri, 1); } } ocs_free(hw->os, mqe, SLI4_BMBX_SIZE); return 0; } /** * @brief Move XRIs from the port-controlled pool to the host. * * Requests XRIs from the FW to return to the host-owned pool. * * @param hw Hardware context. * @param num_xri The number of XRIs being requested to moved from the chip. * * @return Returns 0 for success, or a negative error code value for failure. */ ocs_hw_rtn_e ocs_hw_xri_move_to_host_owned(ocs_hw_t *hw, uint8_t num_xri) { uint8_t *release_xri; ocs_hw_rtn_e rc = OCS_HW_RTN_ERROR; /* non-local buffer required for mailbox queue */ release_xri = ocs_malloc(hw->os, SLI4_BMBX_SIZE, OCS_M_NOWAIT); if (release_xri == NULL) { ocs_log_err(hw->os, "no buffer for command\n"); return OCS_HW_RTN_NO_MEMORY; } /* release the XRIs */ if (sli_cmd_release_xri(&hw->sli, release_xri, SLI4_BMBX_SIZE, num_xri)) { rc = ocs_hw_command(hw, release_xri, OCS_CMD_NOWAIT, ocs_hw_cb_release_xri, NULL); if (rc != OCS_HW_RTN_SUCCESS) { ocs_log_err(hw->os, "release_xri failed\n"); } } /* If we are polling or an error occurred, then free the mailbox buffer */ if (release_xri != NULL && rc != OCS_HW_RTN_SUCCESS) { ocs_free(hw->os, release_xri, SLI4_BMBX_SIZE); } return rc; } /** * @brief Allocate an ocs_hw_rx_buffer_t array. * * @par Description * An ocs_hw_rx_buffer_t array is allocated, along with the required DMA memory. * * @param hw Pointer to HW object. * @param rqindex RQ index for this buffer. * @param count Count of buffers in array. * @param size Size of buffer. * * @return Returns the pointer to the allocated ocs_hw_rq_buffer_t array. */ static ocs_hw_rq_buffer_t * ocs_hw_rx_buffer_alloc(ocs_hw_t *hw, uint32_t rqindex, uint32_t count, uint32_t size) { ocs_t *ocs = hw->os; ocs_hw_rq_buffer_t *rq_buf = NULL; ocs_hw_rq_buffer_t *prq; uint32_t i; if (count != 0) { rq_buf = ocs_malloc(hw->os, sizeof(*rq_buf) * count, OCS_M_NOWAIT | OCS_M_ZERO); if (rq_buf == NULL) { ocs_log_err(hw->os, "Failure to allocate unsolicited DMA trackers\n"); return NULL; } for (i = 0, prq = rq_buf; i < count; i ++, prq++) { prq->rqindex = rqindex; if (ocs_dma_alloc(ocs, &prq->dma, size, OCS_MIN_DMA_ALIGNMENT)) { ocs_log_err(hw->os, "DMA allocation failed\n"); ocs_free(hw->os, rq_buf, sizeof(*rq_buf) * count); rq_buf = NULL; break; } } } return rq_buf; } /** * @brief Free an ocs_hw_rx_buffer_t array. * * @par Description * The ocs_hw_rx_buffer_t array is freed, along with allocated DMA memory. * * @param hw Pointer to HW object. * @param rq_buf Pointer to ocs_hw_rx_buffer_t array. * @param count Count of buffers in array. * * @return None. */ static void ocs_hw_rx_buffer_free(ocs_hw_t *hw, ocs_hw_rq_buffer_t *rq_buf, uint32_t count) { ocs_t *ocs = hw->os; uint32_t i; ocs_hw_rq_buffer_t *prq; if (rq_buf != NULL) { for (i = 0, prq = rq_buf; i < count; i++, prq++) { ocs_dma_free(ocs, &prq->dma); } ocs_free(hw->os, rq_buf, sizeof(*rq_buf) * count); } } /** * @brief Allocate the RQ data buffers. * * @param hw Pointer to HW object. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_rx_allocate(ocs_hw_t *hw) { ocs_t *ocs = hw->os; uint32_t i; int32_t rc = OCS_HW_RTN_SUCCESS; uint32_t rqindex = 0; hw_rq_t *rq; uint32_t hdr_size = OCS_HW_RQ_SIZE_HDR; uint32_t payload_size = hw->config.rq_default_buffer_size; rqindex = 0; for (i = 0; i < hw->hw_rq_count; i++) { rq = hw->hw_rq[i]; /* Allocate header buffers */ rq->hdr_buf = ocs_hw_rx_buffer_alloc(hw, rqindex, rq->entry_count, hdr_size); if (rq->hdr_buf == NULL) { ocs_log_err(ocs, "ocs_hw_rx_buffer_alloc hdr_buf failed\n"); rc = OCS_HW_RTN_ERROR; break; } ocs_log_debug(hw->os, "rq[%2d] rq_id %02d header %4d by %4d bytes\n", i, rq->hdr->id, rq->entry_count, hdr_size); rqindex++; /* Allocate payload buffers */ rq->payload_buf = ocs_hw_rx_buffer_alloc(hw, rqindex, rq->entry_count, payload_size); if (rq->payload_buf == NULL) { ocs_log_err(ocs, "ocs_hw_rx_buffer_alloc fb_buf failed\n"); rc = OCS_HW_RTN_ERROR; break; } ocs_log_debug(hw->os, "rq[%2d] rq_id %02d default %4d by %4d bytes\n", i, rq->data->id, rq->entry_count, payload_size); rqindex++; } return rc ? OCS_HW_RTN_ERROR : OCS_HW_RTN_SUCCESS; } /** * @brief Post the RQ data buffers to the chip. * * @param hw Pointer to HW object. * * @return Returns 0 on success, or a non-zero value on failure. */ ocs_hw_rtn_e ocs_hw_rx_post(ocs_hw_t *hw) { uint32_t i; uint32_t idx; uint32_t rq_idx; int32_t rc = 0; /* * In RQ pair mode, we MUST post the header and payload buffer at the * same time. */ for (rq_idx = 0, idx = 0; rq_idx < hw->hw_rq_count; rq_idx++) { hw_rq_t *rq = hw->hw_rq[rq_idx]; for (i = 0; i < rq->entry_count-1; i++) { ocs_hw_sequence_t *seq = ocs_array_get(hw->seq_pool, idx++); ocs_hw_assert(seq != NULL); seq->header = &rq->hdr_buf[i]; seq->payload = &rq->payload_buf[i]; rc = ocs_hw_sequence_free(hw, seq); if (rc) { break; } } if (rc) { break; } } return rc; } /** * @brief Free the RQ data buffers. * * @param hw Pointer to HW object. * */ void ocs_hw_rx_free(ocs_hw_t *hw) { hw_rq_t *rq; uint32_t i; /* Free hw_rq buffers */ for (i = 0; i < hw->hw_rq_count; i++) { rq = hw->hw_rq[i]; if (rq != NULL) { ocs_hw_rx_buffer_free(hw, rq->hdr_buf, rq->entry_count); rq->hdr_buf = NULL; ocs_hw_rx_buffer_free(hw, rq->payload_buf, rq->entry_count); rq->payload_buf = NULL; } } } /** * @brief HW async call context structure. */ typedef struct { ocs_hw_async_cb_t callback; void *arg; uint8_t cmd[SLI4_BMBX_SIZE]; } ocs_hw_async_call_ctx_t; /** * @brief HW async callback handler * * @par Description * This function is called when the NOP mailbox command completes. The callback stored * in the requesting context is invoked. * * @param hw Pointer to HW object. * @param status Completion status. * @param mqe Pointer to mailbox completion queue entry. * @param arg Caller-provided argument. * * @return None. */ static void ocs_hw_async_cb(ocs_hw_t *hw, int32_t status, uint8_t *mqe, void *arg) { ocs_hw_async_call_ctx_t *ctx = arg; if (ctx != NULL) { if (ctx->callback != NULL) { (*ctx->callback)(hw, status, mqe, ctx->arg); } ocs_free(hw->os, ctx, sizeof(*ctx)); } } /** * @brief Make an async callback using NOP mailbox command * * @par Description * Post a NOP mailbox command; the callback with argument is invoked upon completion * while in the event processing context. * * @param hw Pointer to HW object. * @param callback Pointer to callback function. * @param arg Caller-provided callback. * * @return Returns 0 on success, or a negative error code value on failure. */ int32_t ocs_hw_async_call(ocs_hw_t *hw, ocs_hw_async_cb_t callback, void *arg) { int32_t rc = 0; ocs_hw_async_call_ctx_t *ctx; /* * Allocate a callback context (which includes the mailbox command buffer), we need * this to be persistent as the mailbox command submission may be queued and executed later * execution. */ ctx = ocs_malloc(hw->os, sizeof(*ctx), OCS_M_ZERO | OCS_M_NOWAIT); if (ctx == NULL) { ocs_log_err(hw->os, "failed to malloc async call context\n"); return OCS_HW_RTN_NO_MEMORY; } ctx->callback = callback; ctx->arg = arg; /* Build and send a NOP mailbox command */ if (sli_cmd_common_nop(&hw->sli, ctx->cmd, sizeof(ctx->cmd), 0) == 0) { ocs_log_err(hw->os, "COMMON_NOP format failure\n"); ocs_free(hw->os, ctx, sizeof(*ctx)); rc = -1; } if (ocs_hw_command(hw, ctx->cmd, OCS_CMD_NOWAIT, ocs_hw_async_cb, ctx)) { ocs_log_err(hw->os, "COMMON_NOP command failure\n"); ocs_free(hw->os, ctx, sizeof(*ctx)); rc = -1; } return rc; } /** * @brief Initialize the reqtag pool. * * @par Description * The WQ request tag pool is initialized. * * @param hw Pointer to HW object. * * @return Returns 0 on success, or a negative error code value on failure. */ ocs_hw_rtn_e ocs_hw_reqtag_init(ocs_hw_t *hw) { if (hw->wq_reqtag_pool == NULL) { hw->wq_reqtag_pool = ocs_pool_alloc(hw->os, sizeof(hw_wq_callback_t), 65536, TRUE); if (hw->wq_reqtag_pool == NULL) { ocs_log_err(hw->os, "ocs_pool_alloc hw_wq_callback_t failed\n"); return OCS_HW_RTN_NO_MEMORY; } } ocs_hw_reqtag_reset(hw); return OCS_HW_RTN_SUCCESS; } /** * @brief Allocate a WQ request tag. * * Allocate and populate a WQ request tag from the WQ request tag pool. * * @param hw Pointer to HW object. * @param callback Callback function. * @param arg Pointer to callback argument. * * @return Returns pointer to allocated WQ request tag, or NULL if object cannot be allocated. */ hw_wq_callback_t * ocs_hw_reqtag_alloc(ocs_hw_t *hw, void (*callback)(void *arg, uint8_t *cqe, int32_t status), void *arg) { hw_wq_callback_t *wqcb; ocs_hw_assert(callback != NULL); wqcb = ocs_pool_get(hw->wq_reqtag_pool); if (wqcb != NULL) { ocs_hw_assert(wqcb->callback == NULL); wqcb->callback = callback; wqcb->arg = arg; } return wqcb; } /** * @brief Free a WQ request tag. * * Free the passed in WQ request tag. * * @param hw Pointer to HW object. * @param wqcb Pointer to WQ request tag object to free. * * @return None. */ void ocs_hw_reqtag_free(ocs_hw_t *hw, hw_wq_callback_t *wqcb) { ocs_hw_assert(wqcb->callback != NULL); wqcb->callback = NULL; wqcb->arg = NULL; ocs_pool_put(hw->wq_reqtag_pool, wqcb); } /** * @brief Return WQ request tag by index. * * @par Description * Return pointer to WQ request tag object given an index. * * @param hw Pointer to HW object. * @param instance_index Index of WQ request tag to return. * * @return Pointer to WQ request tag, or NULL. */ hw_wq_callback_t * ocs_hw_reqtag_get_instance(ocs_hw_t *hw, uint32_t instance_index) { hw_wq_callback_t *wqcb; wqcb = ocs_pool_get_instance(hw->wq_reqtag_pool, instance_index); if (wqcb == NULL) { ocs_log_err(hw->os, "wqcb for instance %d is null\n", instance_index); } return wqcb; } /** * @brief Reset the WQ request tag pool. * * @par Description * Reset the WQ request tag pool, returning all to the free list. * * @param hw pointer to HW object. * * @return None. */ void ocs_hw_reqtag_reset(ocs_hw_t *hw) { hw_wq_callback_t *wqcb; uint32_t i; /* Remove all from freelist */ while(ocs_pool_get(hw->wq_reqtag_pool) != NULL) { ; } /* Put them all back */ for (i = 0; ((wqcb = ocs_pool_get_instance(hw->wq_reqtag_pool, i)) != NULL); i++) { wqcb->instance_index = i; wqcb->callback = NULL; wqcb->arg = NULL; ocs_pool_put(hw->wq_reqtag_pool, wqcb); } } /** * @brief Handle HW assertion * * HW assert, display diagnostic message, and abort. * * @param cond string describing failing assertion condition * @param filename file name * @param linenum line number * * @return none */ void _ocs_hw_assert(const char *cond, const char *filename, int linenum) { ocs_printf("%s(%d): HW assertion (%s) failed\n", filename, linenum, cond); ocs_abort(); /* no return */ } /** * @brief Handle HW verify * * HW verify, display diagnostic message, dump stack and return. * * @param cond string describing failing verify condition * @param filename file name * @param linenum line number * * @return none */ void _ocs_hw_verify(const char *cond, const char *filename, int linenum) { ocs_printf("%s(%d): HW verify (%s) failed\n", filename, linenum, cond); ocs_print_stack(); } /** * @brief Reque XRI * * @par Description * Reque XRI * * @param hw Pointer to HW object. * @param io Pointer to HW IO * * @return Return 0 if successful else returns -1 */ int32_t ocs_hw_reque_xri( ocs_hw_t *hw, ocs_hw_io_t *io ) { int32_t rc = 0; rc = ocs_hw_rqpair_auto_xfer_rdy_buffer_post(hw, io, 1); if (rc) { ocs_list_add_tail(&hw->io_port_dnrx, io); rc = -1; goto exit_ocs_hw_reque_xri; } io->auto_xfer_rdy_dnrx = 0; io->type = OCS_HW_IO_DNRX_REQUEUE; if (sli_requeue_xri_wqe(&hw->sli, io->wqe.wqebuf, hw->sli.config.wqe_size, io->indicator, OCS_HW_REQUE_XRI_REGTAG, SLI4_CQ_DEFAULT)) { /* Clear buffer from XRI */ ocs_pool_put(hw->auto_xfer_rdy_buf_pool, io->axr_buf); io->axr_buf = NULL; ocs_log_err(hw->os, "requeue_xri WQE error\n"); ocs_list_add_tail(&hw->io_port_dnrx, io); rc = -1; goto exit_ocs_hw_reque_xri; } if (io->wq == NULL) { io->wq = ocs_hw_queue_next_wq(hw, io); ocs_hw_assert(io->wq != NULL); } /* * Add IO to active io wqe list before submitting, in case the * wcqe processing preempts this thread. */ OCS_STAT(hw->tcmd_wq_submit[io->wq->instance]++); OCS_STAT(io->wq->use_count++); rc = hw_wq_write(io->wq, &io->wqe); if (rc < 0) { ocs_log_err(hw->os, "sli_queue_write reque xri failed: %d\n", rc); rc = -1; } exit_ocs_hw_reque_xri: return 0; } uint32_t ocs_hw_get_def_wwn(ocs_t *ocs, uint32_t chan, uint64_t *wwpn, uint64_t *wwnn) { sli4_t *sli4 = &ocs->hw.sli; ocs_dma_t dma; uint8_t *payload = NULL; int indicator = sli4->config.extent[SLI_RSRC_FCOE_VPI].base[0] + chan; /* allocate memory for the service parameters */ if (ocs_dma_alloc(ocs, &dma, 112, 4)) { ocs_log_err(ocs, "Failed to allocate DMA memory\n"); return 1; } if (0 == sli_cmd_read_sparm64(sli4, sli4->bmbx.virt, SLI4_BMBX_SIZE, &dma, indicator)) { ocs_log_err(ocs, "READ_SPARM64 allocation failure\n"); ocs_dma_free(ocs, &dma); return 1; } if (sli_bmbx_command(sli4)) { ocs_log_err(ocs, "READ_SPARM64 command failure\n"); ocs_dma_free(ocs, &dma); return 1; } payload = dma.virt; ocs_memcpy(wwpn, payload + SLI4_READ_SPARM64_WWPN_OFFSET, sizeof(*wwpn)); ocs_memcpy(wwnn, payload + SLI4_READ_SPARM64_WWNN_OFFSET, sizeof(*wwnn)); ocs_dma_free(ocs, &dma); return 0; } /** * @page fc_hw_api_overview HW APIs * - @ref devInitShutdown * - @ref domain * - @ref port * - @ref node * - @ref io * - @ref interrupt * *
* The Hardware Abstraction Layer (HW) insulates the higher-level code from the SLI-4 * message details, but the higher level code must still manage domains, ports, * IT nexuses, and IOs. The HW API is designed to help the higher level manage * these objects.

* * The HW uses function callbacks to notify the higher-level code of events * that are received from the chip. There are currently three types of * functions that may be registered: * *
  • domain – This function is called whenever a domain event is generated * within the HW. Examples include a new FCF is discovered, a connection * to a domain is disrupted, and allocation callbacks.
  • *
  • unsolicited – This function is called whenever new data is received in * the SLI-4 receive queue.
  • *
  • rnode – This function is called for remote node events, such as attach status * and allocation callbacks.
* * Upper layer functions may be registered by using the ocs_hw_callback() function. * * FC/FCoE HW *

FC/FCoE HW API

* The FC/FCoE HW component builds upon the SLI-4 component to establish a flexible * interface for creating the necessary common objects and sending I/Os. It may be used * “as is” in customer implementations or it can serve as an example of typical interactions * between a driver and the SLI-4 hardware. The broad categories of functionality include: * *
  • Setting-up and tearing-down of the HW.
  • *
  • Allocating and using the common objects (SLI Port, domain, remote node).
  • *
  • Sending and receiving I/Os.
* *

HW Setup

* To set up the HW: * *
    *
  1. Set up the HW object using ocs_hw_setup().
    * This step performs a basic configuration of the SLI-4 component and the HW to * enable querying the hardware for its capabilities. At this stage, the HW is not * capable of general operations (such as, receiving events or sending I/Os).


  2. *
  3. Configure the HW according to the driver requirements.
    * The HW provides functions to discover hardware capabilities (ocs_hw_get()), as * well as configures the amount of resources required (ocs_hw_set()). The driver * must also register callback functions (ocs_hw_callback()) to receive notification of * various asynchronous events.

    * @b Note: Once configured, the driver must initialize the HW (ocs_hw_init()). This * step creates the underlying queues, commits resources to the hardware, and * prepares the hardware for operation. While the hardware is operational, the * port is not online, and cannot send or receive data.


  4. *

    *
  5. Finally, the driver can bring the port online (ocs_hw_port_control()).
    * When the link comes up, the HW determines if a domain is present and notifies the * driver using the domain callback function. This is the starting point of the driver's * interaction with the common objects.

    * @b Note: For FCoE, there may be more than one domain available and, therefore, * more than one callback.
  6. *
* *

Allocating and Using Common Objects

* Common objects provide a mechanism through which the various OneCore Storage * driver components share and track information. These data structures are primarily * used to track SLI component information but can be extended by other components, if * needed. The main objects are: * *
  • DMA – the ocs_dma_t object describes a memory region suitable for direct * memory access (DMA) transactions.
  • *
  • SCSI domain – the ocs_domain_t object represents the SCSI domain, including * any infrastructure devices such as FC switches and FC forwarders. The domain * object contains both an FCFI and a VFI.
  • *
  • SLI Port (sport) – the ocs_sli_port_t object represents the connection between * the driver and the SCSI domain. The SLI Port object contains a VPI.
  • *
  • Remote node – the ocs_remote_node_t represents a connection between the SLI * Port and another device in the SCSI domain. The node object contains an RPI.
* * Before the driver can send I/Os, it must allocate the SCSI domain, SLI Port, and remote * node common objects and establish the connections between them. The goal is to * connect the driver to the SCSI domain to exchange I/Os with other devices. These * common object connections are shown in the following figure, FC Driver Common Objects: * FC Driver Common Objects * * The first step is to create a connection to the domain by allocating an SLI Port object. * The SLI Port object represents a particular FC ID and must be initialized with one. With * the SLI Port object, the driver can discover the available SCSI domain(s). On identifying * a domain, the driver allocates a domain object and attaches to it using the previous SLI * port object.

* * @b Note: In some cases, the driver may need to negotiate service parameters (that is, * FLOGI) with the domain before attaching.

* * Once attached to the domain, the driver can discover and attach to other devices * (remote nodes). The exact discovery method depends on the driver, but it typically * includes using a position map, querying the fabric name server, or an out-of-band * method. In most cases, it is necessary to log in with devices before performing I/Os. * Prior to sending login-related ELS commands (ocs_hw_srrs_send()), the driver must * allocate a remote node object (ocs_hw_node_alloc()). If the login negotiation is * successful, the driver must attach the nodes (ocs_hw_node_attach()) to the SLI Port * before exchanging FCP I/O.

* * @b Note: The HW manages both the well known fabric address and the name server as * nodes in the domain. Therefore, the driver must allocate node objects prior to * communicating with either of these entities. * *

Sending and Receiving I/Os

* The HW provides separate interfaces for sending BLS/ ELS/ FC-CT and FCP, but the * commands are conceptually similar. Since the commands complete asynchronously, * the caller must provide a HW I/O object that maintains the I/O state, as well as * provide a callback function. The driver may use the same callback function for all I/O * operations, but each operation must use a unique HW I/O object. In the SLI-4 * architecture, there is a direct association between the HW I/O object and the SGL used * to describe the data. Therefore, a driver typically performs the following operations: * *
  • Allocates a HW I/O object (ocs_hw_io_alloc()).
  • *
  • Formats the SGL, specifying both the HW I/O object and the SGL. * (ocs_hw_io_init_sges() and ocs_hw_io_add_sge()).
  • *
  • Sends the HW I/O (ocs_hw_io_send()).
* *

HW Tear Down

* To tear-down the HW: * *
  1. Take the port offline (ocs_hw_port_control()) to prevent receiving further * data andevents.
  2. *
  3. Destroy the HW object (ocs_hw_teardown()).
  4. *
  5. Free any memory used by the HW, such as buffers for unsolicited data.
*
*
* */ /** * This contains all hw runtime workaround code. Based on the asic type, * asic revision, and range of fw revisions, a particular workaround may be enabled. * * A workaround may consist of overriding a particular HW/SLI4 value that was initialized * during ocs_hw_setup() (for example the MAX_QUEUE overrides for mis-reported queue * sizes). Or if required, elements of the ocs_hw_workaround_t structure may be set to * control specific runtime behavior. * * It is intended that the controls in ocs_hw_workaround_t be defined functionally. So we * would have the driver look like: "if (hw->workaround.enable_xxx) then ...", rather than * what we might previously see as "if this is a BE3, then do xxx" * */ #define HW_FWREV_ZERO (0ull) #define HW_FWREV_MAX (~0ull) #define SLI4_ASIC_TYPE_ANY 0 #define SLI4_ASIC_REV_ANY 0 /** * @brief Internal definition of workarounds */ typedef enum { HW_WORKAROUND_TEST = 1, HW_WORKAROUND_MAX_QUEUE, /**< Limits all queues */ HW_WORKAROUND_MAX_RQ, /**< Limits only the RQ */ HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH, HW_WORKAROUND_WQE_COUNT_METHOD, HW_WORKAROUND_RQE_COUNT_METHOD, HW_WORKAROUND_USE_UNREGISTERD_RPI, HW_WORKAROUND_DISABLE_AR_TGT_DIF, /**< Disable of auto-response target DIF */ HW_WORKAROUND_DISABLE_SET_DUMP_LOC, HW_WORKAROUND_USE_DIF_QUARANTINE, HW_WORKAROUND_USE_DIF_SEC_XRI, /**< Use secondary xri for multiple data phases */ HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB, /**< FCFI reported in SRB not correct, use "first" registered domain */ HW_WORKAROUND_FW_VERSION_TOO_LOW, /**< The FW version is not the min version supported by this driver */ HW_WORKAROUND_SGLC_MISREPORTED, /**< Chip supports SGL Chaining but SGLC is not set in SLI4_PARAMS */ HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE, /**< Don't use SEND_FRAME capable if FW version is too old */ } hw_workaround_e; /** * @brief Internal workaround structure instance */ typedef struct { sli4_asic_type_e asic_type; sli4_asic_rev_e asic_rev; uint64_t fwrev_low; uint64_t fwrev_high; hw_workaround_e workaround; uint32_t value; } hw_workaround_t; static hw_workaround_t hw_workarounds[] = { {SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_TEST, 999}, /* Bug: 127585: if_type == 2 returns 0 for total length placed on * FCP_TSEND64_WQE completions. Note, original driver code enables this * workaround for all asic types */ {SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH, 0}, /* Bug: unknown, Lancer A0 has mis-reported max queue depth */ {SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_A0, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_MAX_QUEUE, 2048}, /* Bug: 143399, BE3 has mis-reported max RQ queue depth */ {SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,6,293,0), HW_WORKAROUND_MAX_RQ, 2048}, /* Bug: 143399, skyhawk has mis-reported max RQ queue depth */ {SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(10,0,594,0), HW_WORKAROUND_MAX_RQ, 2048}, /* Bug: 103487, BE3 before f/w 4.2.314.0 has mis-reported WQE count method */ {SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,2,314,0), HW_WORKAROUND_WQE_COUNT_METHOD, 1}, /* Bug: 103487, BE3 before f/w 4.2.314.0 has mis-reported RQE count method */ {SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,2,314,0), HW_WORKAROUND_RQE_COUNT_METHOD, 1}, /* Bug: 142968, BE3 UE with RPI == 0xffff */ {SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_USE_UNREGISTERD_RPI, 0}, /* Bug: unknown, Skyhawk won't support auto-response on target T10-PI */ {SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_DISABLE_AR_TGT_DIF, 0}, {SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(1,1,65,0), HW_WORKAROUND_DISABLE_SET_DUMP_LOC, 0}, /* Bug: 160124, Skyhawk quarantine DIF XRIs */ {SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_USE_DIF_QUARANTINE, 0}, /* Bug: 161832, Skyhawk use secondary XRI for multiple data phase TRECV */ {SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_USE_DIF_SEC_XRI, 0}, /* Bug: xxxxxx, FCFI reported in SRB not corrrect */ {SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB, 0}, #if 0 /* Bug: 165642, FW version check for driver */ {SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_1(OCS_MIN_FW_VER_LANCER), HW_WORKAROUND_FW_VERSION_TOO_LOW, 0}, #endif {SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_1(OCS_MIN_FW_VER_SKYHAWK), HW_WORKAROUND_FW_VERSION_TOO_LOW, 0}, /* Bug 177061, Lancer FW does not set the SGLC bit */ {SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_SGLC_MISREPORTED, 0}, /* BZ 181208/183914, enable this workaround for ALL revisions */ {SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX, HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE, 0}, }; /** * @brief Function prototypes */ static int32_t ocs_hw_workaround_match(ocs_hw_t *hw, hw_workaround_t *w); /** * @brief Parse the firmware version (name) * * Parse a string of the form a.b.c.d, returning a uint64_t packed as defined * by the HW_FWREV() macro * * @param fwrev_string pointer to the firmware string * * @return packed firmware revision value */ static uint64_t parse_fw_version(const char *fwrev_string) { int v[4] = {0}; const char *p; int i; for (p = fwrev_string, i = 0; *p && (i < 4); i ++) { v[i] = ocs_strtoul(p, 0, 0); while(*p && *p != '.') { p ++; } if (*p) { p ++; } } /* Special case for bootleg releases with f/w rev 0.0.9999.0, set to max value */ if (v[2] == 9999) { return HW_FWREV_MAX; } else { return HW_FWREV(v[0], v[1], v[2], v[3]); } } /** * @brief Test for a workaround match * * Looks at the asic type, asic revision, and fw revision, and returns TRUE if match. * * @param hw Pointer to the HW structure * @param w Pointer to a workaround structure entry * * @return Return TRUE for a match */ static int32_t ocs_hw_workaround_match(ocs_hw_t *hw, hw_workaround_t *w) { return (((w->asic_type == SLI4_ASIC_TYPE_ANY) || (w->asic_type == hw->sli.asic_type)) && ((w->asic_rev == SLI4_ASIC_REV_ANY) || (w->asic_rev == hw->sli.asic_rev)) && (w->fwrev_low <= hw->workaround.fwrev) && ((w->fwrev_high == HW_FWREV_MAX) || (hw->workaround.fwrev < w->fwrev_high))); } /** * @brief Setup HW runtime workarounds * * The function is called at the end of ocs_hw_setup() to setup any runtime workarounds * based on the HW/SLI setup. * * @param hw Pointer to HW structure * * @return none */ void ocs_hw_workaround_setup(struct ocs_hw_s *hw) { hw_workaround_t *w; sli4_t *sli4 = &hw->sli; uint32_t i; /* Initialize the workaround settings */ ocs_memset(&hw->workaround, 0, sizeof(hw->workaround)); /* If hw_war_version is non-null, then its a value that was set by a module parameter * (sorry for the break in abstraction, but workarounds are ... well, workarounds) */ if (hw->hw_war_version) { hw->workaround.fwrev = parse_fw_version(hw->hw_war_version); } else { hw->workaround.fwrev = parse_fw_version((char*) sli4->config.fw_name[0]); } /* Walk the workaround list, if a match is found, then handle it */ for (i = 0, w = hw_workarounds; i < ARRAY_SIZE(hw_workarounds); i++, w++) { if (ocs_hw_workaround_match(hw, w)) { switch(w->workaround) { case HW_WORKAROUND_TEST: { ocs_log_debug(hw->os, "Override: test: %d\n", w->value); break; } case HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH: { ocs_log_debug(hw->os, "HW Workaround: retain TSEND IO length\n"); hw->workaround.retain_tsend_io_length = 1; break; } case HW_WORKAROUND_MAX_QUEUE: { sli4_qtype_e q; ocs_log_debug(hw->os, "HW Workaround: override max_qentries: %d\n", w->value); for (q = SLI_QTYPE_EQ; q < SLI_QTYPE_MAX; q++) { if (hw->num_qentries[q] > w->value) { hw->num_qentries[q] = w->value; } } break; } case HW_WORKAROUND_MAX_RQ: { ocs_log_debug(hw->os, "HW Workaround: override RQ max_qentries: %d\n", w->value); if (hw->num_qentries[SLI_QTYPE_RQ] > w->value) { hw->num_qentries[SLI_QTYPE_RQ] = w->value; } break; } case HW_WORKAROUND_WQE_COUNT_METHOD: { ocs_log_debug(hw->os, "HW Workaround: set WQE count method=%d\n", w->value); sli4->config.count_method[SLI_QTYPE_WQ] = w->value; sli_calc_max_qentries(sli4); break; } case HW_WORKAROUND_RQE_COUNT_METHOD: { ocs_log_debug(hw->os, "HW Workaround: set RQE count method=%d\n", w->value); sli4->config.count_method[SLI_QTYPE_RQ] = w->value; sli_calc_max_qentries(sli4); break; } case HW_WORKAROUND_USE_UNREGISTERD_RPI: ocs_log_debug(hw->os, "HW Workaround: use unreg'd RPI if rnode->indicator == 0xFFFF\n"); hw->workaround.use_unregistered_rpi = TRUE; /* * Allocate an RPI that is never registered, to be used in the case where * a node has been unregistered, and its indicator (RPI) value is set to 0xFFFF */ if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_RPI, &hw->workaround.unregistered_rid, &hw->workaround.unregistered_index)) { ocs_log_err(hw->os, "sli_resource_alloc unregistered RPI failed\n"); hw->workaround.use_unregistered_rpi = FALSE; } break; case HW_WORKAROUND_DISABLE_AR_TGT_DIF: ocs_log_debug(hw->os, "HW Workaround: disable AR on T10-PI TSEND\n"); hw->workaround.disable_ar_tgt_dif = TRUE; break; case HW_WORKAROUND_DISABLE_SET_DUMP_LOC: ocs_log_debug(hw->os, "HW Workaround: disable set_dump_loc\n"); hw->workaround.disable_dump_loc = TRUE; break; case HW_WORKAROUND_USE_DIF_QUARANTINE: ocs_log_debug(hw->os, "HW Workaround: use DIF quarantine\n"); hw->workaround.use_dif_quarantine = TRUE; break; case HW_WORKAROUND_USE_DIF_SEC_XRI: ocs_log_debug(hw->os, "HW Workaround: use DIF secondary xri\n"); hw->workaround.use_dif_sec_xri = TRUE; break; case HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB: ocs_log_debug(hw->os, "HW Workaround: override FCFI in SRB\n"); hw->workaround.override_fcfi = TRUE; break; case HW_WORKAROUND_FW_VERSION_TOO_LOW: ocs_log_debug(hw->os, "HW Workaround: fw version is below the minimum for this driver\n"); hw->workaround.fw_version_too_low = TRUE; break; case HW_WORKAROUND_SGLC_MISREPORTED: ocs_log_debug(hw->os, "HW Workaround: SGLC misreported - chaining is enabled\n"); hw->workaround.sglc_misreported = TRUE; break; case HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE: ocs_log_debug(hw->os, "HW Workaround: not SEND_FRAME capable - disabled\n"); hw->workaround.ignore_send_frame = TRUE; break; } /* switch(w->workaround) */ } } }