/*- * 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$ */ /** * @defgroup scsi_api_target SCSI Target API * @defgroup scsi_api_initiator SCSI Initiator API * @defgroup cam_api Common Access Method (CAM) API * @defgroup cam_io CAM IO */ /** * @file * Provides CAM functionality. */ #include "ocs.h" #include "ocs_scsi.h" #include "ocs_device.h" /* Default IO timeout value for initiators is 30 seconds */ #define OCS_CAM_IO_TIMEOUT 30 typedef struct { ocs_scsi_sgl_t *sgl; uint32_t sgl_max; uint32_t sgl_count; int32_t rc; } ocs_dmamap_load_arg_t; static void ocs_action(struct cam_sim *, union ccb *); static void ocs_poll(struct cam_sim *); static ocs_tgt_resource_t *ocs_tgt_resource_get(ocs_fcport *, struct ccb_hdr *, uint32_t *); static int32_t ocs_tgt_resource_abort(struct ocs_softc *, ocs_tgt_resource_t *); static uint32_t ocs_abort_initiator_io(struct ocs_softc *ocs, union ccb *accb); static void ocs_abort_inot(struct ocs_softc *ocs, union ccb *ccb); static void ocs_abort_atio(struct ocs_softc *ocs, union ccb *ccb); static int32_t ocs_target_tmf_cb(ocs_io_t *, ocs_scsi_io_status_e, uint32_t, void *); static int32_t ocs_io_abort_cb(ocs_io_t *, ocs_scsi_io_status_e, uint32_t, void *); static int32_t ocs_task_set_full_or_busy(ocs_io_t *io); static int32_t ocs_initiator_tmf_cb(ocs_io_t *, ocs_scsi_io_status_e, ocs_scsi_cmd_resp_t *, uint32_t, void *); static uint32_t ocs_fcp_change_role(struct ocs_softc *ocs, ocs_fcport *fcp, uint32_t new_role); static void ocs_ldt(void *arg); static void ocs_ldt_task(void *arg, int pending); static void ocs_delete_target(ocs_t *ocs, ocs_fcport *fcp, int tgt); uint32_t ocs_add_new_tgt(ocs_node_t *node, ocs_fcport *fcp); uint32_t ocs_update_tgt(ocs_node_t *node, ocs_fcport *fcp, uint32_t tgt_id); int32_t ocs_tgt_find(ocs_fcport *fcp, ocs_node_t *node); static inline ocs_io_t *ocs_scsi_find_io(struct ocs_softc *ocs, uint32_t tag) { return ocs_io_get_instance(ocs, tag); } static inline void ocs_target_io_free(ocs_io_t *io) { io->tgt_io.state = OCS_CAM_IO_FREE; io->tgt_io.flags = 0; io->tgt_io.app = NULL; ocs_scsi_io_complete(io); if(io->ocs->io_in_use != 0) atomic_subtract_acq_32(&io->ocs->io_in_use, 1); } static int32_t ocs_attach_port(ocs_t *ocs, int chan) { struct cam_sim *sim = NULL; struct cam_path *path = NULL; uint32_t max_io = ocs_scsi_get_property(ocs, OCS_SCSI_MAX_IOS); ocs_fcport *fcp = FCPORT(ocs, chan); if (NULL == (sim = cam_sim_alloc(ocs_action, ocs_poll, device_get_name(ocs->dev), ocs, device_get_unit(ocs->dev), &ocs->sim_lock, max_io, max_io, ocs->devq))) { device_printf(ocs->dev, "Can't allocate SIM\n"); return 1; } mtx_lock(&ocs->sim_lock); if (CAM_SUCCESS != xpt_bus_register(sim, ocs->dev, chan)) { device_printf(ocs->dev, "Can't register bus %d\n", 0); mtx_unlock(&ocs->sim_lock); cam_sim_free(sim, FALSE); return 1; } mtx_unlock(&ocs->sim_lock); if (CAM_REQ_CMP != xpt_create_path(&path, NULL, cam_sim_path(sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD)) { device_printf(ocs->dev, "Can't create path\n"); xpt_bus_deregister(cam_sim_path(sim)); mtx_unlock(&ocs->sim_lock); cam_sim_free(sim, FALSE); return 1; } fcp->ocs = ocs; fcp->sim = sim; fcp->path = path; callout_init_mtx(&fcp->ldt, &ocs->sim_lock, 0); TASK_INIT(&fcp->ltask, 1, ocs_ldt_task, fcp); return 0; } static int32_t ocs_detach_port(ocs_t *ocs, int32_t chan) { ocs_fcport *fcp = NULL; struct cam_sim *sim = NULL; struct cam_path *path = NULL; fcp = FCPORT(ocs, chan); sim = fcp->sim; path = fcp->path; callout_drain(&fcp->ldt); ocs_ldt_task(fcp, 0); if (fcp->sim) { mtx_lock(&ocs->sim_lock); ocs_tgt_resource_abort(ocs, &fcp->targ_rsrc_wildcard); if (path) { xpt_async(AC_LOST_DEVICE, path, NULL); xpt_free_path(path); fcp->path = NULL; } xpt_bus_deregister(cam_sim_path(sim)); cam_sim_free(sim, FALSE); fcp->sim = NULL; mtx_unlock(&ocs->sim_lock); } return 0; } int32_t ocs_cam_attach(ocs_t *ocs) { struct cam_devq *devq = NULL; int i = 0; uint32_t max_io = ocs_scsi_get_property(ocs, OCS_SCSI_MAX_IOS); if (NULL == (devq = cam_simq_alloc(max_io))) { device_printf(ocs->dev, "Can't allocate SIMQ\n"); return -1; } ocs->devq = devq; if (mtx_initialized(&ocs->sim_lock) == 0) { mtx_init(&ocs->sim_lock, "ocs_sim_lock", NULL, MTX_DEF); } for (i = 0; i < (ocs->num_vports + 1); i++) { if (ocs_attach_port(ocs, i)) { ocs_log_err(ocs, "Attach port failed for chan: %d\n", i); goto detach_port; } } ocs->io_high_watermark = max_io; ocs->io_in_use = 0; return 0; detach_port: while (--i >= 0) { ocs_detach_port(ocs, i); } cam_simq_free(ocs->devq); if (mtx_initialized(&ocs->sim_lock)) mtx_destroy(&ocs->sim_lock); return 1; } int32_t ocs_cam_detach(ocs_t *ocs) { int i = 0; for (i = (ocs->num_vports); i >= 0; i--) { ocs_detach_port(ocs, i); } cam_simq_free(ocs->devq); if (mtx_initialized(&ocs->sim_lock)) mtx_destroy(&ocs->sim_lock); return 0; } /*************************************************************************** * Functions required by SCSI base driver API */ /** * @ingroup scsi_api_target * @brief Attach driver to the BSD SCSI layer (a.k.a CAM) * * Allocates + initializes CAM related resources and attaches to the CAM * * @param ocs the driver instance's software context * * @return 0 on success, non-zero otherwise */ int32_t ocs_scsi_tgt_new_device(ocs_t *ocs) { ocs->enable_task_set_full = ocs_scsi_get_property(ocs, OCS_SCSI_ENABLE_TASK_SET_FULL); ocs_log_debug(ocs, "task set full processing is %s\n", ocs->enable_task_set_full ? "enabled" : "disabled"); return 0; } /** * @ingroup scsi_api_target * @brief Tears down target members of ocs structure. * * Called by OS code when device is removed. * * @param ocs pointer to ocs * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_tgt_del_device(ocs_t *ocs) { return 0; } /** * @ingroup scsi_api_target * @brief accept new domain notification * * Called by base drive when new domain is discovered. A target-server * will use this call to prepare for new remote node notifications * arising from ocs_scsi_new_initiator(). * * The domain context has an element ocs_scsi_tgt_domain_t tgt_domain * which is declared by the target-server code and is used for target-server * private data. * * This function will only be called if the base-driver has been enabled for * target capability. * * Note that this call is made to target-server backends, * the ocs_scsi_ini_new_domain() function is called to initiator-client backends. * * @param domain pointer to domain * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_tgt_new_domain(ocs_domain_t *domain) { return 0; } /** * @ingroup scsi_api_target * @brief accept domain lost notification * * Called by base-driver when a domain goes away. A target-server will * use this call to clean up all domain scoped resources. * * Note that this call is made to target-server backends, * the ocs_scsi_ini_del_domain() function is called to initiator-client backends. * * @param domain pointer to domain * * @return returns 0 for success, a negative error code value for failure. */ void ocs_scsi_tgt_del_domain(ocs_domain_t *domain) { } /** * @ingroup scsi_api_target * @brief accept new sli port (sport) notification * * Called by base drive when new sport is discovered. A target-server * will use this call to prepare for new remote node notifications * arising from ocs_scsi_new_initiator(). * * The domain context has an element ocs_scsi_tgt_sport_t tgt_sport * which is declared by the target-server code and is used for * target-server private data. * * This function will only be called if the base-driver has been enabled for * target capability. * * Note that this call is made to target-server backends, * the ocs_scsi_tgt_new_domain() is called to initiator-client backends. * * @param sport pointer to SLI port * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_tgt_new_sport(ocs_sport_t *sport) { ocs_t *ocs = sport->ocs; if(!sport->is_vport) { sport->tgt_data = FCPORT(ocs, 0); } return 0; } /** * @ingroup scsi_api_target * @brief accept SLI port gone notification * * Called by base-driver when a sport goes away. A target-server will * use this call to clean up all sport scoped resources. * * Note that this call is made to target-server backends, * the ocs_scsi_ini_del_sport() is called to initiator-client backends. * * @param sport pointer to SLI port * * @return returns 0 for success, a negative error code value for failure. */ void ocs_scsi_tgt_del_sport(ocs_sport_t *sport) { return; } /** * @ingroup scsi_api_target * @brief receive notification of a new SCSI initiator node * * Sent by base driver to notify a target-server of the presense of a new * remote initiator. The target-server may use this call to prepare for * inbound IO from this node. * * The ocs_node_t structure has and elment of type ocs_scsi_tgt_node_t named * tgt_node that is declared and used by a target-server for private * information. * * This function is only called if the target capability is enabled in driver. * * @param node pointer to new remote initiator node * * @return returns 0 for success, a negative error code value for failure. * * @note */ int32_t ocs_scsi_new_initiator(ocs_node_t *node) { ocs_t *ocs = node->ocs; struct ac_contract ac; struct ac_device_changed *adc; ocs_fcport *fcp = NULL; fcp = node->sport->tgt_data; if (fcp == NULL) { ocs_log_err(ocs, "FCP is NULL \n"); return 1; } /* * Update the IO watermark by decrementing it by the * number of IOs reserved for each initiator. */ atomic_subtract_acq_32(&ocs->io_high_watermark, OCS_RSVD_INI_IO); ac.contract_number = AC_CONTRACT_DEV_CHG; adc = (struct ac_device_changed *) ac.contract_data; adc->wwpn = ocs_node_get_wwpn(node); adc->port = node->rnode.fc_id; adc->target = node->instance_index; adc->arrived = 1; xpt_async(AC_CONTRACT, fcp->path, &ac); return 0; } /** * @ingroup scsi_api_target * @brief validate new initiator * * Sent by base driver to validate a remote initiatiator. The target-server * returns TRUE if this initiator should be accepted. * * This function is only called if the target capability is enabled in driver. * * @param node pointer to remote initiator node to validate * * @return TRUE if initiator should be accepted, FALSE if it should be rejected * * @note */ int32_t ocs_scsi_validate_initiator(ocs_node_t *node) { return 1; } /** * @ingroup scsi_api_target * @brief Delete a SCSI initiator node * * Sent by base driver to notify a target-server that a remote initiator * is now gone. The base driver will have terminated all outstanding IOs * and the target-server will receive appropriate completions. * * This function is only called if the base driver is enabled for * target capability. * * @param node pointer node being deleted * @param reason Reason why initiator is gone. * * @return OCS_SCSI_CALL_COMPLETE to indicate that all work was completed * * @note */ int32_t ocs_scsi_del_initiator(ocs_node_t *node, ocs_scsi_del_initiator_reason_e reason) { ocs_t *ocs = node->ocs; struct ac_contract ac; struct ac_device_changed *adc; ocs_fcport *fcp = NULL; fcp = node->sport->tgt_data; if (fcp == NULL) { ocs_log_err(ocs, "FCP is NULL \n"); return 1; } ac.contract_number = AC_CONTRACT_DEV_CHG; adc = (struct ac_device_changed *) ac.contract_data; adc->wwpn = ocs_node_get_wwpn(node); adc->port = node->rnode.fc_id; adc->target = node->instance_index; adc->arrived = 0; xpt_async(AC_CONTRACT, fcp->path, &ac); if (reason == OCS_SCSI_INITIATOR_MISSING) { return OCS_SCSI_CALL_COMPLETE; } /* * Update the IO watermark by incrementing it by the * number of IOs reserved for each initiator. */ atomic_add_acq_32(&ocs->io_high_watermark, OCS_RSVD_INI_IO); return OCS_SCSI_CALL_COMPLETE; } /** * @ingroup scsi_api_target * @brief receive FCP SCSI Command * * Called by the base driver when a new SCSI command has been received. The * target-server will process the command, and issue data and/or response phase * requests to the base driver. * * The IO context (ocs_io_t) structure has and element of type * ocs_scsi_tgt_io_t named tgt_io that is declared and used by * a target-server for private information. * * @param io pointer to IO context * @param lun LUN for this IO * @param cdb pointer to SCSI CDB * @param cdb_len length of CDB in bytes * @param flags command flags * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_recv_cmd(ocs_io_t *io, uint64_t lun, uint8_t *cdb, uint32_t cdb_len, uint32_t flags) { ocs_t *ocs = io->ocs; struct ccb_accept_tio *atio = NULL; ocs_node_t *node = io->node; ocs_tgt_resource_t *trsrc = NULL; int32_t rc = -1; ocs_fcport *fcp = NULL; fcp = node->sport->tgt_data; if (fcp == NULL) { ocs_log_err(ocs, "FCP is NULL \n"); return 1; } atomic_add_acq_32(&ocs->io_in_use, 1); /* set target io timeout */ io->timeout = ocs->target_io_timer_sec; if (ocs->enable_task_set_full && (ocs->io_in_use >= ocs->io_high_watermark)) { return ocs_task_set_full_or_busy(io); } else { atomic_store_rel_32(&io->node->tgt_node.busy_sent, FALSE); } if ((lun < OCS_MAX_LUN) && fcp->targ_rsrc[lun].enabled) { trsrc = &fcp->targ_rsrc[lun]; } else if (fcp->targ_rsrc_wildcard.enabled) { trsrc = &fcp->targ_rsrc_wildcard; } if (trsrc) { atio = (struct ccb_accept_tio *)STAILQ_FIRST(&trsrc->atio); } if (atio) { STAILQ_REMOVE_HEAD(&trsrc->atio, sim_links.stqe); atio->ccb_h.status = CAM_CDB_RECVD; atio->ccb_h.target_lun = lun; atio->sense_len = 0; atio->init_id = node->instance_index; atio->tag_id = io->tag; atio->ccb_h.ccb_io_ptr = io; if (flags & OCS_SCSI_CMD_SIMPLE) atio->tag_action = MSG_SIMPLE_Q_TAG; else if (flags & OCS_SCSI_CMD_HEAD_OF_QUEUE) atio->tag_action = MSG_HEAD_OF_Q_TAG; else if (flags & OCS_SCSI_CMD_ORDERED) atio->tag_action = MSG_ORDERED_Q_TAG; else if (flags & OCS_SCSI_CMD_ACA) atio->tag_action = MSG_ACA_TASK; else atio->tag_action = CAM_TAG_ACTION_NONE; atio->priority = (flags & OCS_SCSI_PRIORITY_MASK) >> OCS_SCSI_PRIORITY_SHIFT; atio->cdb_len = cdb_len; ocs_memcpy(atio->cdb_io.cdb_bytes, cdb, cdb_len); io->tgt_io.flags = 0; io->tgt_io.state = OCS_CAM_IO_COMMAND; io->tgt_io.lun = lun; xpt_done((union ccb *)atio); rc = 0; } else { device_printf( ocs->dev, "%s: no ATIO for LUN %lx (en=%s) OX_ID %#x\n", __func__, (unsigned long)lun, trsrc ? (trsrc->enabled ? "T" : "F") : "X", be16toh(io->init_task_tag)); io->tgt_io.state = OCS_CAM_IO_MAX; ocs_target_io_free(io); } return rc; } /** * @ingroup scsi_api_target * @brief receive FCP SCSI Command with first burst data. * * Receive a new FCP SCSI command from the base driver with first burst data. * * @param io pointer to IO context * @param lun LUN for this IO * @param cdb pointer to SCSI CDB * @param cdb_len length of CDB in bytes * @param flags command flags * @param first_burst_buffers first burst buffers * @param first_burst_buffer_count The number of bytes received in the first burst * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_recv_cmd_first_burst(ocs_io_t *io, uint64_t lun, uint8_t *cdb, uint32_t cdb_len, uint32_t flags, ocs_dma_t first_burst_buffers[], uint32_t first_burst_buffer_count) { return -1; } /** * @ingroup scsi_api_target * @brief receive a TMF command IO * * Called by the base driver when a SCSI TMF command has been received. The * target-server will process the command, aborting commands as needed, and post * a response using ocs_scsi_send_resp() * * The IO context (ocs_io_t) structure has and element of type ocs_scsi_tgt_io_t named * tgt_io that is declared and used by a target-server for private information. * * If the target-server walks the nodes active_ios linked list, and starts IO * abort processing, the code must be sure not to abort the IO passed into the * ocs_scsi_recv_tmf() command. * * @param tmfio pointer to IO context * @param lun logical unit value * @param cmd command request * @param abortio pointer to IO object to abort for TASK_ABORT (NULL for all other TMF) * @param flags flags * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_recv_tmf(ocs_io_t *tmfio, uint64_t lun, ocs_scsi_tmf_cmd_e cmd, ocs_io_t *abortio, uint32_t flags) { ocs_t *ocs = tmfio->ocs; ocs_node_t *node = tmfio->node; ocs_tgt_resource_t *trsrc = NULL; struct ccb_immediate_notify *inot = NULL; int32_t rc = -1; ocs_fcport *fcp = NULL; fcp = node->sport->tgt_data; if (fcp == NULL) { ocs_log_err(ocs, "FCP is NULL \n"); return 1; } if ((lun < OCS_MAX_LUN) && fcp->targ_rsrc[lun].enabled) { trsrc = &fcp->targ_rsrc[lun]; } else if (fcp->targ_rsrc_wildcard.enabled) { trsrc = &fcp->targ_rsrc_wildcard; } device_printf(tmfio->ocs->dev, "%s: io=%p cmd=%#x LU=%lx en=%s\n", __func__, tmfio, cmd, (unsigned long)lun, trsrc ? (trsrc->enabled ? "T" : "F") : "X"); if (trsrc) { inot = (struct ccb_immediate_notify *)STAILQ_FIRST(&trsrc->inot); } if (!inot) { device_printf( ocs->dev, "%s: no INOT for LUN %llx (en=%s) OX_ID %#x\n", __func__, (unsigned long long)lun, trsrc ? (trsrc->enabled ? "T" : "F") : "X", be16toh(tmfio->init_task_tag)); if (abortio) { ocs_scsi_io_complete(abortio); } ocs_scsi_io_complete(tmfio); goto ocs_scsi_recv_tmf_out; } tmfio->tgt_io.app = abortio; STAILQ_REMOVE_HEAD(&trsrc->inot, sim_links.stqe); inot->tag_id = tmfio->tag; inot->seq_id = tmfio->tag; if ((lun < OCS_MAX_LUN) && fcp->targ_rsrc[lun].enabled) { inot->initiator_id = node->instance_index; } else { inot->initiator_id = CAM_TARGET_WILDCARD; } inot->ccb_h.status = CAM_MESSAGE_RECV; inot->ccb_h.target_lun = lun; switch (cmd) { case OCS_SCSI_TMF_ABORT_TASK: inot->arg = MSG_ABORT_TASK; inot->seq_id = abortio->tag; device_printf(ocs->dev, "%s: ABTS IO.%#x st=%#x\n", __func__, abortio->tag, abortio->tgt_io.state); abortio->tgt_io.flags |= OCS_CAM_IO_F_ABORT_RECV; abortio->tgt_io.flags |= OCS_CAM_IO_F_ABORT_NOTIFY; break; case OCS_SCSI_TMF_QUERY_TASK_SET: device_printf(ocs->dev, "%s: OCS_SCSI_TMF_QUERY_TASK_SET not supported\n", __func__); STAILQ_INSERT_TAIL(&trsrc->inot, &inot->ccb_h, sim_links.stqe); ocs_scsi_io_complete(tmfio); goto ocs_scsi_recv_tmf_out; break; case OCS_SCSI_TMF_ABORT_TASK_SET: inot->arg = MSG_ABORT_TASK_SET; break; case OCS_SCSI_TMF_CLEAR_TASK_SET: inot->arg = MSG_CLEAR_TASK_SET; break; case OCS_SCSI_TMF_QUERY_ASYNCHRONOUS_EVENT: inot->arg = MSG_QUERY_ASYNC_EVENT; break; case OCS_SCSI_TMF_LOGICAL_UNIT_RESET: inot->arg = MSG_LOGICAL_UNIT_RESET; break; case OCS_SCSI_TMF_CLEAR_ACA: inot->arg = MSG_CLEAR_ACA; break; case OCS_SCSI_TMF_TARGET_RESET: inot->arg = MSG_TARGET_RESET; break; default: device_printf(ocs->dev, "%s: unsupported TMF %#x\n", __func__, cmd); STAILQ_INSERT_TAIL(&trsrc->inot, &inot->ccb_h, sim_links.stqe); goto ocs_scsi_recv_tmf_out; } rc = 0; xpt_print(inot->ccb_h.path, "%s: func=%#x stat=%#x id=%#x lun=%#x" " flags=%#x tag=%#x seq=%#x ini=%#x arg=%#x\n", __func__, inot->ccb_h.func_code, inot->ccb_h.status, inot->ccb_h.target_id, (unsigned int)inot->ccb_h.target_lun, inot->ccb_h.flags, inot->tag_id, inot->seq_id, inot->initiator_id, inot->arg); xpt_done((union ccb *)inot); if (abortio) { abortio->tgt_io.flags |= OCS_CAM_IO_F_ABORT_DEV; rc = ocs_scsi_tgt_abort_io(abortio, ocs_io_abort_cb, tmfio); } ocs_scsi_recv_tmf_out: return rc; } /** * @ingroup scsi_api_initiator * @brief Initializes any initiator fields on the ocs structure. * * Called by OS initialization code when a new device is discovered. * * @param ocs pointer to ocs * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_new_device(ocs_t *ocs) { return 0; } /** * @ingroup scsi_api_initiator * @brief Tears down initiator members of ocs structure. * * Called by OS code when device is removed. * * @param ocs pointer to ocs * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_del_device(ocs_t *ocs) { return 0; } /** * @ingroup scsi_api_initiator * @brief accept new domain notification * * Called by base drive when new domain is discovered. An initiator-client * will accept this call to prepare for new remote node notifications * arising from ocs_scsi_new_target(). * * The domain context has the element ocs_scsi_ini_domain_t ini_domain * which is declared by the initiator-client code and is used for * initiator-client private data. * * This function will only be called if the base-driver has been enabled for * initiator capability. * * Note that this call is made to initiator-client backends, * the ocs_scsi_tgt_new_domain() function is called to target-server backends. * * @param domain pointer to domain * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_new_domain(ocs_domain_t *domain) { return 0; } /** * @ingroup scsi_api_initiator * @brief accept domain lost notification * * Called by base-driver when a domain goes away. An initiator-client will * use this call to clean up all domain scoped resources. * * This function will only be called if the base-driver has been enabled for * initiator capability. * * Note that this call is made to initiator-client backends, * the ocs_scsi_tgt_del_domain() function is called to target-server backends. * * @param domain pointer to domain * * @return returns 0 for success, a negative error code value for failure. */ void ocs_scsi_ini_del_domain(ocs_domain_t *domain) { } /** * @ingroup scsi_api_initiator * @brief accept new sli port notification * * Called by base drive when new sli port (sport) is discovered. * A target-server will use this call to prepare for new remote node * notifications arising from ocs_scsi_new_initiator(). * * This function will only be called if the base-driver has been enabled for * target capability. * * Note that this call is made to target-server backends, * the ocs_scsi_ini_new_sport() function is called to initiator-client backends. * * @param sport pointer to sport * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_new_sport(ocs_sport_t *sport) { ocs_t *ocs = sport->ocs; ocs_fcport *fcp = FCPORT(ocs, 0); if (!sport->is_vport) { sport->tgt_data = fcp; fcp->fc_id = sport->fc_id; } return 0; } /** * @ingroup scsi_api_initiator * @brief accept sli port gone notification * * Called by base-driver when a sport goes away. A target-server will * use this call to clean up all sport scoped resources. * * Note that this call is made to target-server backends, * the ocs_scsi_ini_del_sport() function is called to initiator-client backends. * * @param sport pointer to SLI port * * @return returns 0 for success, a negative error code value for failure. */ void ocs_scsi_ini_del_sport(ocs_sport_t *sport) { ocs_t *ocs = sport->ocs; ocs_fcport *fcp = FCPORT(ocs, 0); if (!sport->is_vport) { fcp->fc_id = 0; } } void ocs_scsi_sport_deleted(ocs_sport_t *sport) { ocs_t *ocs = sport->ocs; ocs_fcport *fcp = NULL; ocs_xport_stats_t value; if (!sport->is_vport) { return; } fcp = sport->tgt_data; ocs_xport_status(ocs->xport, OCS_XPORT_PORT_STATUS, &value); if (value.value == 0) { ocs_log_debug(ocs, "PORT offline,.. skipping\n"); return; } if ((fcp->role != KNOB_ROLE_NONE)) { if(fcp->vport->sport != NULL) { ocs_log_debug(ocs,"sport is not NULL, skipping\n"); return; } ocs_sport_vport_alloc(ocs->domain, fcp->vport); return; } } int32_t ocs_tgt_find(ocs_fcport *fcp, ocs_node_t *node) { ocs_fc_target_t *tgt = NULL; uint32_t i; for (i = 0; i < OCS_MAX_TARGETS; i++) { tgt = &fcp->tgt[i]; if (tgt->state == OCS_TGT_STATE_NONE) continue; if (ocs_node_get_wwpn(node) == tgt->wwpn) { return i; } } return -1; } /** * @ingroup scsi_api_initiator * @brief receive notification of a new SCSI target node * * Sent by base driver to notify an initiator-client of the presense of a new * remote target. The initiator-server may use this call to prepare for * inbound IO from this node. * * This function is only called if the base driver is enabled for * initiator capability. * * @param node pointer to new remote initiator node * * @return none * * @note */ uint32_t ocs_update_tgt(ocs_node_t *node, ocs_fcport *fcp, uint32_t tgt_id) { ocs_fc_target_t *tgt = NULL; tgt = &fcp->tgt[tgt_id]; tgt->node_id = node->instance_index; tgt->state = OCS_TGT_STATE_VALID; tgt->port_id = node->rnode.fc_id; tgt->wwpn = ocs_node_get_wwpn(node); tgt->wwnn = ocs_node_get_wwnn(node); return 0; } uint32_t ocs_add_new_tgt(ocs_node_t *node, ocs_fcport *fcp) { uint32_t i; struct ocs_softc *ocs = node->ocs; union ccb *ccb = NULL; for (i = 0; i < OCS_MAX_TARGETS; i++) { if (fcp->tgt[i].state == OCS_TGT_STATE_NONE) break; } if (NULL == (ccb = xpt_alloc_ccb_nowait())) { device_printf(ocs->dev, "%s: ccb allocation failed\n", __func__); return -1; } if (CAM_REQ_CMP != xpt_create_path(&ccb->ccb_h.path, xpt_periph, cam_sim_path(fcp->sim), i, CAM_LUN_WILDCARD)) { device_printf( ocs->dev, "%s: target path creation failed\n", __func__); xpt_free_ccb(ccb); return -1; } ocs_update_tgt(node, fcp, i); xpt_rescan(ccb); return 0; } int32_t ocs_scsi_new_target(ocs_node_t *node) { ocs_fcport *fcp = NULL; int32_t i; fcp = node->sport->tgt_data; if (fcp == NULL) { printf("%s:FCP is NULL \n", __func__); return 0; } i = ocs_tgt_find(fcp, node); if (i < 0) { ocs_add_new_tgt(node, fcp); return 0; } ocs_update_tgt(node, fcp, i); return 0; } static void ocs_delete_target(ocs_t *ocs, ocs_fcport *fcp, int tgt) { struct cam_path *cpath = NULL; if (!fcp->sim) { device_printf(ocs->dev, "%s: calling with NULL sim\n", __func__); return; } if (CAM_REQ_CMP == xpt_create_path(&cpath, NULL, cam_sim_path(fcp->sim), tgt, CAM_LUN_WILDCARD)) { xpt_async(AC_LOST_DEVICE, cpath, NULL); xpt_free_path(cpath); } } /* * Device Lost Timer Function- when we have decided that a device was lost, * we wait a specific period of time prior to telling the OS about lost device. * * This timer function gets activated when the device was lost. * This function fires once a second and then scans the port database * for devices that are marked dead but still have a virtual target assigned. * We decrement a counter for that port database entry, and when it hits zero, * we tell the OS the device was lost. Timer will be stopped when the device * comes back active or removed from the OS. */ static void ocs_ldt(void *arg) { ocs_fcport *fcp = arg; taskqueue_enqueue(taskqueue_thread, &fcp->ltask); } static void ocs_ldt_task(void *arg, int pending) { ocs_fcport *fcp = arg; ocs_t *ocs = fcp->ocs; int i, more_to_do = 0; ocs_fc_target_t *tgt = NULL; for (i = 0; i < OCS_MAX_TARGETS; i++) { tgt = &fcp->tgt[i]; if (tgt->state != OCS_TGT_STATE_LOST) { continue; } if ((tgt->gone_timer != 0) && (ocs->attached)){ tgt->gone_timer -= 1; more_to_do++; continue; } ocs_delete_target(ocs, fcp, i); tgt->state = OCS_TGT_STATE_NONE; } if (more_to_do) { callout_reset(&fcp->ldt, hz, ocs_ldt, fcp); } else { callout_deactivate(&fcp->ldt); } } /** * @ingroup scsi_api_initiator * @brief Delete a SCSI target node * * Sent by base driver to notify a initiator-client that a remote target * is now gone. The base driver will have terminated all outstanding IOs * and the initiator-client will receive appropriate completions. * * The ocs_node_t structure has and elment of type ocs_scsi_ini_node_t named * ini_node that is declared and used by a target-server for private * information. * * This function is only called if the base driver is enabled for * initiator capability. * * @param node pointer node being deleted * @param reason reason for deleting the target * * @return Returns OCS_SCSI_CALL_ASYNC if target delete is queued for async * completion and OCS_SCSI_CALL_COMPLETE if call completed or error. * * @note */ int32_t ocs_scsi_del_target(ocs_node_t *node, ocs_scsi_del_target_reason_e reason) { struct ocs_softc *ocs = node->ocs; ocs_fcport *fcp = NULL; ocs_fc_target_t *tgt = NULL; int32_t tgt_id; if (ocs == NULL) { ocs_log_err(ocs,"OCS is NULL \n"); return -1; } fcp = node->sport->tgt_data; if (fcp == NULL) { ocs_log_err(ocs,"FCP is NULL \n"); return -1; } tgt_id = ocs_tgt_find(fcp, node); if (tgt_id == -1) { ocs_log_err(ocs,"target is invalid\n"); return -1; } tgt = &fcp->tgt[tgt_id]; // IF in shutdown delete target. if(!ocs->attached) { ocs_delete_target(ocs, fcp, tgt_id); } else { tgt->state = OCS_TGT_STATE_LOST; tgt->gone_timer = 30; if (!callout_active(&fcp->ldt)) { callout_reset(&fcp->ldt, hz, ocs_ldt, fcp); } } return 0; } /** * @brief Initialize SCSI IO * * Initialize SCSI IO, this function is called once per IO during IO pool * allocation so that the target server may initialize any of its own private * data. * * @param io pointer to SCSI IO object * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_tgt_io_init(ocs_io_t *io) { return 0; } /** * @brief Uninitialize SCSI IO * * Uninitialize target server private data in a SCSI io object * * @param io pointer to SCSI IO object * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_tgt_io_exit(ocs_io_t *io) { return 0; } /** * @brief Initialize SCSI IO * * Initialize SCSI IO, this function is called once per IO during IO pool * allocation so that the initiator client may initialize any of its own private * data. * * @param io pointer to SCSI IO object * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_io_init(ocs_io_t *io) { return 0; } /** * @brief Uninitialize SCSI IO * * Uninitialize initiator client private data in a SCSI io object * * @param io pointer to SCSI IO object * * @return returns 0 for success, a negative error code value for failure. */ int32_t ocs_scsi_ini_io_exit(ocs_io_t *io) { return 0; } /* * End of functions required by SCSI base driver API ***************************************************************************/ static __inline void ocs_set_ccb_status(union ccb *ccb, cam_status status) { ccb->ccb_h.status &= ~CAM_STATUS_MASK; ccb->ccb_h.status |= status; } static int32_t ocs_task_set_full_or_busy_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, uint32_t flags, void *arg) { ocs_target_io_free(io); return 0; } /** * @brief send SCSI task set full or busy status * * A SCSI task set full or busy response is sent depending on whether * another IO is already active on the LUN. * * @param io pointer to IO context * * @return returns 0 for success, a negative error code value for failure. */ static int32_t ocs_task_set_full_or_busy(ocs_io_t *io) { ocs_scsi_cmd_resp_t rsp = { 0 }; ocs_t *ocs = io->ocs; /* * If there is another command for the LUN, then send task set full, * if this is the first one, then send the busy status. * * if 'busy sent' is FALSE, set it to TRUE and send BUSY * otherwise send FULL */ if (atomic_cmpset_acq_32(&io->node->tgt_node.busy_sent, FALSE, TRUE)) { rsp.scsi_status = SCSI_STATUS_BUSY; /* Busy */ printf("%s: busy [%s] tag=%x iiu=%d ihw=%d\n", __func__, io->node->display_name, io->tag, io->ocs->io_in_use, io->ocs->io_high_watermark); } else { rsp.scsi_status = SCSI_STATUS_TASK_SET_FULL; /* Task set full */ printf("%s: full tag=%x iiu=%d\n", __func__, io->tag, io->ocs->io_in_use); } /* Log a message here indicating a busy or task set full state */ if (OCS_LOG_ENABLE_Q_FULL_BUSY_MSG(ocs)) { /* Log Task Set Full */ if (rsp.scsi_status == SCSI_STATUS_TASK_SET_FULL) { /* Task Set Full Message */ ocs_log_info(ocs, "OCS CAM TASK SET FULL. Tasks >= %d\n", ocs->io_high_watermark); } else if (rsp.scsi_status == SCSI_STATUS_BUSY) { /* Log Busy Message */ ocs_log_info(ocs, "OCS CAM SCSI BUSY\n"); } } /* Send the response */ return ocs_scsi_send_resp(io, 0, &rsp, ocs_task_set_full_or_busy_cb, NULL); } /** * @ingroup cam_io * @brief Process target IO completions * * @param io * @param scsi_status did the IO complete successfully * @param flags * @param arg application specific pointer provided in the call to ocs_target_io() * * @todo */ static int32_t ocs_scsi_target_io_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, uint32_t flags, void *arg) { union ccb *ccb = arg; struct ccb_scsiio *csio = &ccb->csio; struct ocs_softc *ocs = csio->ccb_h.ccb_ocs_ptr; uint32_t cam_dir = ccb->ccb_h.flags & CAM_DIR_MASK; uint32_t io_is_done = (ccb->ccb_h.flags & CAM_SEND_STATUS) == CAM_SEND_STATUS; ccb->ccb_h.status &= ~CAM_SIM_QUEUED; if (CAM_DIR_NONE != cam_dir) { bus_dmasync_op_t op; if (CAM_DIR_IN == cam_dir) { op = BUS_DMASYNC_POSTREAD; } else { op = BUS_DMASYNC_POSTWRITE; } /* Synchronize the DMA memory with the CPU and free the mapping */ bus_dmamap_sync(ocs->buf_dmat, io->tgt_io.dmap, op); if (io->tgt_io.flags & OCS_CAM_IO_F_DMAPPED) { bus_dmamap_unload(ocs->buf_dmat, io->tgt_io.dmap); } } if (io->tgt_io.sendresp) { io->tgt_io.sendresp = 0; ocs_scsi_cmd_resp_t resp = { 0 }; io->tgt_io.state = OCS_CAM_IO_RESP; resp.scsi_status = scsi_status; if (ccb->ccb_h.flags & CAM_SEND_SENSE) { resp.sense_data = (uint8_t *)&csio->sense_data; resp.sense_data_length = csio->sense_len; } resp.residual = io->exp_xfer_len - io->transferred; return ocs_scsi_send_resp(io, 0, &resp, ocs_scsi_target_io_cb, ccb); } switch (scsi_status) { case OCS_SCSI_STATUS_GOOD: ocs_set_ccb_status(ccb, CAM_REQ_CMP); break; case OCS_SCSI_STATUS_ABORTED: ocs_set_ccb_status(ccb, CAM_REQ_ABORTED); break; default: ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); } if (io_is_done) { if ((io->tgt_io.flags & OCS_CAM_IO_F_ABORT_NOTIFY) == 0) { ocs_target_io_free(io); } } else { io->tgt_io.state = OCS_CAM_IO_DATA_DONE; /*device_printf(ocs->dev, "%s: CTIO state=%d tag=%#x\n", __func__, io->tgt_io.state, io->tag);*/ } xpt_done(ccb); return 0; } /** * @note 1. Since the CCB is assigned to the ocs_io_t on an XPT_CONT_TARGET_IO * action, if an initiator aborts a command prior to the SIM receiving * a CTIO, the IO's CCB will be NULL. */ static int32_t ocs_io_abort_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, uint32_t flags, void *arg) { struct ocs_softc *ocs = NULL; ocs_io_t *tmfio = arg; ocs_scsi_tmf_resp_e tmf_resp = OCS_SCSI_TMF_FUNCTION_COMPLETE; int32_t rc = 0; ocs = io->ocs; io->tgt_io.flags &= ~OCS_CAM_IO_F_ABORT_DEV; /* A good status indicates the IO was aborted and will be completed in * the IO's completion handler. Handle the other cases here. */ switch (scsi_status) { case OCS_SCSI_STATUS_GOOD: break; case OCS_SCSI_STATUS_NO_IO: break; default: device_printf(ocs->dev, "%s: unhandled status %d\n", __func__, scsi_status); tmf_resp = OCS_SCSI_TMF_FUNCTION_REJECTED; rc = -1; } ocs_scsi_send_tmf_resp(tmfio, tmf_resp, NULL, ocs_target_tmf_cb, NULL); return rc; } /** * @ingroup cam_io * @brief Process initiator IO completions * * @param io * @param scsi_status did the IO complete successfully * @param rsp pointer to response buffer * @param flags * @param arg application specific pointer provided in the call to ocs_target_io() * * @todo */ static int32_t ocs_scsi_initiator_io_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, ocs_scsi_cmd_resp_t *rsp, uint32_t flags, void *arg) { union ccb *ccb = arg; struct ccb_scsiio *csio = &ccb->csio; struct ocs_softc *ocs = csio->ccb_h.ccb_ocs_ptr; uint32_t cam_dir = ccb->ccb_h.flags & CAM_DIR_MASK; cam_status ccb_status= CAM_REQ_CMP_ERR; if (CAM_DIR_NONE != cam_dir) { bus_dmasync_op_t op; if (CAM_DIR_IN == cam_dir) { op = BUS_DMASYNC_POSTREAD; } else { op = BUS_DMASYNC_POSTWRITE; } /* Synchronize the DMA memory with the CPU and free the mapping */ bus_dmamap_sync(ocs->buf_dmat, io->tgt_io.dmap, op); if (io->tgt_io.flags & OCS_CAM_IO_F_DMAPPED) { bus_dmamap_unload(ocs->buf_dmat, io->tgt_io.dmap); } } if (scsi_status == OCS_SCSI_STATUS_CHECK_RESPONSE) { csio->scsi_status = rsp->scsi_status; if (SCSI_STATUS_OK != rsp->scsi_status) ccb_status = CAM_SCSI_STATUS_ERROR; else ccb_status = CAM_REQ_CMP; csio->resid = rsp->residual; /* * If we've already got a SCSI error, prefer that because it * will have more detail. */ if ((rsp->residual < 0) && (ccb_status == CAM_REQ_CMP)) { ccb_status = CAM_DATA_RUN_ERR; } if ((rsp->sense_data_length) && !(ccb->ccb_h.flags & (CAM_SENSE_PHYS | CAM_SENSE_PTR))) { uint32_t sense_len = 0; ccb->ccb_h.status |= CAM_AUTOSNS_VALID; if (rsp->sense_data_length < csio->sense_len) { csio->sense_resid = csio->sense_len - rsp->sense_data_length; sense_len = rsp->sense_data_length; } else { csio->sense_resid = 0; sense_len = csio->sense_len; } ocs_memcpy(&csio->sense_data, rsp->sense_data, sense_len); } } else if (scsi_status != OCS_SCSI_STATUS_GOOD) { ccb_status = CAM_REQ_CMP_ERR; } else { ccb_status = CAM_REQ_CMP; } ocs_set_ccb_status(ccb, ccb_status); ocs_scsi_io_free(io); csio->ccb_h.ccb_io_ptr = NULL; csio->ccb_h.ccb_ocs_ptr = NULL; ccb->ccb_h.status &= ~CAM_SIM_QUEUED; if ((ccb_status != CAM_REQ_CMP) && ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0)) { ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(ccb->ccb_h.path, 1); } xpt_done(ccb); return 0; } /** * @brief Load scatter-gather list entries into an IO * * This routine relies on the driver instance's software context pointer and * the IO object pointer having been already assigned to hooks in the CCB. * Although the routine does not return success/fail, callers can look at the * n_sge member to determine if the mapping failed (0 on failure). * * @param arg pointer to the CAM ccb for this IO * @param seg DMA address/length pairs * @param nseg number of DMA address/length pairs * @param error any errors while mapping the IO */ static void ocs_scsi_dmamap_load(void *arg, bus_dma_segment_t *seg, int nseg, int error) { ocs_dmamap_load_arg_t *sglarg = (ocs_dmamap_load_arg_t*) arg; if (error) { printf("%s: seg=%p nseg=%d error=%d\n", __func__, seg, nseg, error); sglarg->rc = -1; } else { uint32_t i = 0; uint32_t c = 0; if ((sglarg->sgl_count + nseg) > sglarg->sgl_max) { printf("%s: sgl_count=%d nseg=%d max=%d\n", __func__, sglarg->sgl_count, nseg, sglarg->sgl_max); sglarg->rc = -2; return; } for (i = 0, c = sglarg->sgl_count; i < nseg; i++, c++) { sglarg->sgl[c].addr = seg[i].ds_addr; sglarg->sgl[c].len = seg[i].ds_len; } sglarg->sgl_count = c; sglarg->rc = 0; } } /** * @brief Build a scatter-gather list from a CAM CCB * * @param ocs the driver instance's software context * @param ccb pointer to the CCB * @param io pointer to the previously allocated IO object * @param sgl pointer to SGL * @param sgl_max number of entries in sgl * * @return 0 on success, non-zero otherwise */ static int32_t ocs_build_scsi_sgl(struct ocs_softc *ocs, union ccb *ccb, ocs_io_t *io, ocs_scsi_sgl_t *sgl, uint32_t sgl_max) { ocs_dmamap_load_arg_t dmaarg; int32_t err = 0; if (!ocs || !ccb || !io || !sgl) { printf("%s: bad param o=%p c=%p i=%p s=%p\n", __func__, ocs, ccb, io, sgl); return -1; } io->tgt_io.flags &= ~OCS_CAM_IO_F_DMAPPED; dmaarg.sgl = sgl; dmaarg.sgl_count = 0; dmaarg.sgl_max = sgl_max; dmaarg.rc = 0; err = bus_dmamap_load_ccb(ocs->buf_dmat, io->tgt_io.dmap, ccb, ocs_scsi_dmamap_load, &dmaarg, 0); if (err || dmaarg.rc) { device_printf( ocs->dev, "%s: bus_dmamap_load_ccb error (%d %d)\n", __func__, err, dmaarg.rc); return -1; } io->tgt_io.flags |= OCS_CAM_IO_F_DMAPPED; return dmaarg.sgl_count; } /** * @ingroup cam_io * @brief Send a target IO * * @param ocs the driver instance's software context * @param ccb pointer to the CCB * * @return 0 on success, non-zero otherwise */ static int32_t ocs_target_io(struct ocs_softc *ocs, union ccb *ccb) { struct ccb_scsiio *csio = &ccb->csio; ocs_io_t *io = NULL; uint32_t cam_dir = ccb->ccb_h.flags & CAM_DIR_MASK; bool sendstatus = ccb->ccb_h.flags & CAM_SEND_STATUS; uint32_t xferlen = csio->dxfer_len; int32_t rc = 0; io = ocs_scsi_find_io(ocs, csio->tag_id); if (io == NULL) { ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); panic("bad tag value"); return 1; } /* Received an ABORT TASK for this IO */ if (io->tgt_io.flags & OCS_CAM_IO_F_ABORT_RECV) { /*device_printf(ocs->dev, "%s: XPT_CONT_TARGET_IO state=%d tag=%#x xid=%#x flags=%#x\n", __func__, io->tgt_io.state, io->tag, io->init_task_tag, io->tgt_io.flags);*/ io->tgt_io.flags |= OCS_CAM_IO_F_ABORT_CAM; if (ccb->ccb_h.flags & CAM_SEND_STATUS) { ocs_set_ccb_status(ccb, CAM_REQ_CMP); ocs_target_io_free(io); return 1; } ocs_set_ccb_status(ccb, CAM_REQ_ABORTED); return 1; } io->tgt_io.app = ccb; ocs_set_ccb_status(ccb, CAM_REQ_INPROG); ccb->ccb_h.status |= CAM_SIM_QUEUED; csio->ccb_h.ccb_ocs_ptr = ocs; csio->ccb_h.ccb_io_ptr = io; if ((sendstatus && (xferlen == 0))) { ocs_scsi_cmd_resp_t resp = { 0 }; ocs_assert(ccb->ccb_h.flags & CAM_SEND_STATUS, -1); io->tgt_io.state = OCS_CAM_IO_RESP; resp.scsi_status = csio->scsi_status; if (ccb->ccb_h.flags & CAM_SEND_SENSE) { resp.sense_data = (uint8_t *)&csio->sense_data; resp.sense_data_length = csio->sense_len; } resp.residual = io->exp_xfer_len - io->transferred; rc = ocs_scsi_send_resp(io, 0, &resp, ocs_scsi_target_io_cb, ccb); } else if (xferlen != 0) { ocs_scsi_sgl_t *sgl; int32_t sgl_count = 0; io->tgt_io.state = OCS_CAM_IO_DATA; if (sendstatus) io->tgt_io.sendresp = 1; sgl = io->sgl; sgl_count = ocs_build_scsi_sgl(ocs, ccb, io, sgl, io->sgl_allocated); if (sgl_count > 0) { if (cam_dir == CAM_DIR_IN) { rc = ocs_scsi_send_rd_data(io, 0, NULL, sgl, sgl_count, csio->dxfer_len, ocs_scsi_target_io_cb, ccb); } else if (cam_dir == CAM_DIR_OUT) { rc = ocs_scsi_recv_wr_data(io, 0, NULL, sgl, sgl_count, csio->dxfer_len, ocs_scsi_target_io_cb, ccb); } else { device_printf(ocs->dev, "%s:" " unknown CAM direction %#x\n", __func__, cam_dir); ocs_set_ccb_status(ccb, CAM_REQ_INVALID); rc = 1; } } else { device_printf(ocs->dev, "%s: building SGL failed\n", __func__); ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); rc = 1; } } else { device_printf(ocs->dev, "%s: Wrong value xfer and sendstatus" " are 0 \n", __func__); ocs_set_ccb_status(ccb, CAM_REQ_INVALID); rc = 1; } if (rc) { ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); ccb->ccb_h.status &= ~CAM_SIM_QUEUED; io->tgt_io.state = OCS_CAM_IO_DATA_DONE; device_printf(ocs->dev, "%s: CTIO state=%d tag=%#x\n", __func__, io->tgt_io.state, io->tag); if ((sendstatus && (xferlen == 0))) { ocs_target_io_free(io); } } return rc; } static int32_t ocs_target_tmf_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, uint32_t flags, void *arg) { /*device_printf(io->ocs->dev, "%s: tag=%x io=%p s=%#x\n", __func__, io->tag, io, scsi_status);*/ ocs_scsi_io_complete(io); return 0; } /** * @ingroup cam_io * @brief Send an initiator IO * * @param ocs the driver instance's software context * @param ccb pointer to the CCB * * @return 0 on success, non-zero otherwise */ static int32_t ocs_initiator_io(struct ocs_softc *ocs, union ccb *ccb) { int32_t rc; struct ccb_scsiio *csio = &ccb->csio; struct ccb_hdr *ccb_h = &csio->ccb_h; ocs_node_t *node = NULL; ocs_io_t *io = NULL; ocs_scsi_sgl_t *sgl; int32_t flags, sgl_count; ocs_fcport *fcp; fcp = FCPORT(ocs, cam_sim_bus(xpt_path_sim((ccb)->ccb_h.path))); if (fcp->tgt[ccb_h->target_id].state == OCS_TGT_STATE_LOST) { device_printf(ocs->dev, "%s: device LOST %d\n", __func__, ccb_h->target_id); return CAM_REQUEUE_REQ; } if (fcp->tgt[ccb_h->target_id].state == OCS_TGT_STATE_NONE) { device_printf(ocs->dev, "%s: device not ready %d\n", __func__, ccb_h->target_id); return CAM_SEL_TIMEOUT; } node = ocs_node_get_instance(ocs, fcp->tgt[ccb_h->target_id].node_id); if (node == NULL) { device_printf(ocs->dev, "%s: no device %d\n", __func__, ccb_h->target_id); return CAM_SEL_TIMEOUT; } if (!node->targ) { device_printf(ocs->dev, "%s: not target device %d\n", __func__, ccb_h->target_id); return CAM_SEL_TIMEOUT; } io = ocs_scsi_io_alloc(node, OCS_SCSI_IO_ROLE_ORIGINATOR); if (io == NULL) { device_printf(ocs->dev, "%s: unable to alloc IO\n", __func__); return -1; } /* eventhough this is INI, use target structure as ocs_build_scsi_sgl * only references the tgt_io part of an ocs_io_t */ io->tgt_io.app = ccb; csio->ccb_h.ccb_ocs_ptr = ocs; csio->ccb_h.ccb_io_ptr = io; sgl = io->sgl; sgl_count = ocs_build_scsi_sgl(ocs, ccb, io, sgl, io->sgl_allocated); if (sgl_count < 0) { ocs_scsi_io_free(io); device_printf(ocs->dev, "%s: building SGL failed\n", __func__); return -1; } if (ccb->ccb_h.timeout == CAM_TIME_INFINITY) { io->timeout = 0; } else if (ccb->ccb_h.timeout == CAM_TIME_DEFAULT) { io->timeout = OCS_CAM_IO_TIMEOUT; } else { io->timeout = ccb->ccb_h.timeout; } switch (csio->tag_action) { case MSG_HEAD_OF_Q_TAG: flags = OCS_SCSI_CMD_HEAD_OF_QUEUE; break; case MSG_ORDERED_Q_TAG: flags = OCS_SCSI_CMD_ORDERED; break; case MSG_ACA_TASK: flags = OCS_SCSI_CMD_ACA; break; case CAM_TAG_ACTION_NONE: case MSG_SIMPLE_Q_TAG: default: flags = OCS_SCSI_CMD_SIMPLE; break; } flags |= (csio->priority << OCS_SCSI_PRIORITY_SHIFT) & OCS_SCSI_PRIORITY_MASK; switch (ccb->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_NONE: rc = ocs_scsi_send_nodata_io(node, io, ccb_h->target_lun, ccb->ccb_h.flags & CAM_CDB_POINTER ? csio->cdb_io.cdb_ptr: csio->cdb_io.cdb_bytes, csio->cdb_len, ocs_scsi_initiator_io_cb, ccb, flags); break; case CAM_DIR_IN: rc = ocs_scsi_send_rd_io(node, io, ccb_h->target_lun, ccb->ccb_h.flags & CAM_CDB_POINTER ? csio->cdb_io.cdb_ptr: csio->cdb_io.cdb_bytes, csio->cdb_len, NULL, sgl, sgl_count, csio->dxfer_len, ocs_scsi_initiator_io_cb, ccb, flags); break; case CAM_DIR_OUT: rc = ocs_scsi_send_wr_io(node, io, ccb_h->target_lun, ccb->ccb_h.flags & CAM_CDB_POINTER ? csio->cdb_io.cdb_ptr: csio->cdb_io.cdb_bytes, csio->cdb_len, NULL, sgl, sgl_count, csio->dxfer_len, ocs_scsi_initiator_io_cb, ccb, flags); break; default: panic("%s invalid data direction %08x\n", __func__, ccb->ccb_h.flags); break; } return rc; } static uint32_t ocs_fcp_change_role(struct ocs_softc *ocs, ocs_fcport *fcp, uint32_t new_role) { uint32_t rc = 0, was = 0, i = 0; ocs_vport_spec_t *vport = fcp->vport; for (was = 0, i = 0; i < (ocs->num_vports + 1); i++) { if (FCPORT(ocs, i)->role != KNOB_ROLE_NONE) was++; } // Physical port if ((was == 0) || (vport == NULL)) { fcp->role = new_role; if (vport == NULL) { ocs->enable_ini = (new_role & KNOB_ROLE_INITIATOR)? 1:0; ocs->enable_tgt = (new_role & KNOB_ROLE_TARGET)? 1:0; } else { vport->enable_ini = (new_role & KNOB_ROLE_INITIATOR)? 1:0; vport->enable_tgt = (new_role & KNOB_ROLE_TARGET)? 1:0; } rc = ocs_xport_control(ocs->xport, OCS_XPORT_PORT_OFFLINE); if (rc) { ocs_log_debug(ocs, "port offline failed : %d\n", rc); } rc = ocs_xport_control(ocs->xport, OCS_XPORT_PORT_ONLINE); if (rc) { ocs_log_debug(ocs, "port online failed : %d\n", rc); } return 0; } if ((fcp->role != KNOB_ROLE_NONE)){ fcp->role = new_role; vport->enable_ini = (new_role & KNOB_ROLE_INITIATOR)? 1:0; vport->enable_tgt = (new_role & KNOB_ROLE_TARGET)? 1:0; /* New Sport will be created in sport deleted cb */ return ocs_sport_vport_del(ocs, ocs->domain, vport->wwpn, vport->wwnn); } fcp->role = new_role; vport->enable_ini = (new_role & KNOB_ROLE_INITIATOR)? 1:0; vport->enable_tgt = (new_role & KNOB_ROLE_TARGET)? 1:0; if (fcp->role != KNOB_ROLE_NONE) { return ocs_sport_vport_alloc(ocs->domain, vport); } return (0); } /** * @ingroup cam_api * @brief Process CAM actions * * The driver supplies this routine to the CAM during intialization and * is the main entry point for processing CAM Control Blocks (CCB) * * @param sim pointer to the SCSI Interface Module * @param ccb CAM control block * * @todo * - populate path inquiry data via info retrieved from SLI port */ static void ocs_action(struct cam_sim *sim, union ccb *ccb) { struct ocs_softc *ocs = (struct ocs_softc *)cam_sim_softc(sim); struct ccb_hdr *ccb_h = &ccb->ccb_h; int32_t rc, bus; bus = cam_sim_bus(sim); switch (ccb_h->func_code) { case XPT_SCSI_IO: if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) { if ((ccb->ccb_h.flags & CAM_CDB_PHYS) != 0) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); break; } } rc = ocs_initiator_io(ocs, ccb); if (0 == rc) { ocs_set_ccb_status(ccb, CAM_REQ_INPROG | CAM_SIM_QUEUED); break; } else { if (rc == CAM_REQUEUE_REQ) { cam_freeze_devq(ccb->ccb_h.path); cam_release_devq(ccb->ccb_h.path, RELSIM_RELEASE_AFTER_TIMEOUT, 0, 100, 0); ccb->ccb_h.status = CAM_REQUEUE_REQ; xpt_done(ccb); break; } ccb->ccb_h.status &= ~CAM_SIM_QUEUED; if (rc > 0) { ocs_set_ccb_status(ccb, rc); } else { ocs_set_ccb_status(ccb, CAM_SEL_TIMEOUT); } } xpt_done(ccb); break; case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; struct ccb_pathinq_settings_fc *fc = &cpi->xport_specific.fc; ocs_fcport *fcp = FCPORT(ocs, bus); uint64_t wwn = 0; ocs_xport_stats_t value; cpi->version_num = 1; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_SPC; if (ocs->ocs_xport == OCS_XPORT_FC) { cpi->transport = XPORT_FC; } else { cpi->transport = XPORT_UNKNOWN; } cpi->transport_version = 0; /* Set the transport parameters of the SIM */ ocs_xport_status(ocs->xport, OCS_XPORT_LINK_SPEED, &value); fc->bitrate = value.value * 1000; /* speed in Mbps */ wwn = *((uint64_t *)ocs_scsi_get_property_ptr(ocs, OCS_SCSI_WWPN)); fc->wwpn = be64toh(wwn); wwn = *((uint64_t *)ocs_scsi_get_property_ptr(ocs, OCS_SCSI_WWNN)); fc->wwnn = be64toh(wwn); fc->port = fcp->fc_id; if (ocs->config_tgt) { cpi->target_sprt = PIT_PROCESSOR | PIT_DISCONNECT | PIT_TERM_IO; } cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED; cpi->hba_misc |= PIM_EXTLUNS | PIM_NOSCAN; cpi->hba_inquiry = PI_TAG_ABLE; cpi->max_target = OCS_MAX_TARGETS; cpi->initiator_id = ocs->max_remote_nodes + 1; if (!ocs->enable_ini) { cpi->hba_misc |= PIM_NOINITIATOR; } cpi->max_lun = OCS_MAX_LUN; cpi->bus_id = cam_sim_bus(sim); /* Need to supply a base transfer speed prior to linking up * Worst case, this would be FC 1Gbps */ cpi->base_transfer_speed = 1 * 1000 * 1000; /* Calculate the max IO supported * Worst case would be an OS page per SGL entry */ cpi->maxio = PAGE_SIZE * (ocs_scsi_get_property(ocs, OCS_SCSI_MAX_SGL) - 1); strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strncpy(cpi->hba_vid, "Emulex", HBA_IDLEN); strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->unit_number = cam_sim_unit(sim); cpi->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts = &ccb->cts; struct ccb_trans_settings_scsi *scsi = &cts->proto_specific.scsi; struct ccb_trans_settings_fc *fc = &cts->xport_specific.fc; ocs_xport_stats_t value; ocs_fcport *fcp = FCPORT(ocs, bus); ocs_fc_target_t *tgt = NULL; if (ocs->ocs_xport != OCS_XPORT_FC) { ocs_set_ccb_status(ccb, CAM_REQ_INVALID); xpt_done(ccb); break; } if (cts->ccb_h.target_id > OCS_MAX_TARGETS) { ocs_set_ccb_status(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); break; } tgt = &fcp->tgt[cts->ccb_h.target_id]; if (tgt->state == OCS_TGT_STATE_NONE) { ocs_set_ccb_status(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); break; } cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_SPC2; cts->transport = XPORT_FC; cts->transport_version = 2; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; /* speed in Mbps */ ocs_xport_status(ocs->xport, OCS_XPORT_LINK_SPEED, &value); fc->bitrate = value.value * 100; fc->wwpn = tgt->wwpn; fc->wwnn = tgt->wwnn; fc->port = tgt->port_id; fc->valid = CTS_FC_VALID_SPEED | CTS_FC_VALID_WWPN | CTS_FC_VALID_WWNN | CTS_FC_VALID_PORT; ocs_set_ccb_status(ccb, CAM_REQ_CMP); xpt_done(ccb); break; } case XPT_SET_TRAN_SETTINGS: ocs_set_ccb_status(ccb, CAM_REQ_CMP); xpt_done(ccb); break; case XPT_CALC_GEOMETRY: cam_calc_geometry(&ccb->ccg, TRUE); xpt_done(ccb); break; case XPT_GET_SIM_KNOB: { struct ccb_sim_knob *knob = &ccb->knob; uint64_t wwn = 0; ocs_fcport *fcp = FCPORT(ocs, bus); if (ocs->ocs_xport != OCS_XPORT_FC) { ocs_set_ccb_status(ccb, CAM_REQ_INVALID); xpt_done(ccb); break; } if (bus == 0) { wwn = *((uint64_t *)ocs_scsi_get_property_ptr(ocs, OCS_SCSI_WWNN)); knob->xport_specific.fc.wwnn = be64toh(wwn); wwn = *((uint64_t *)ocs_scsi_get_property_ptr(ocs, OCS_SCSI_WWPN)); knob->xport_specific.fc.wwpn = be64toh(wwn); } else { knob->xport_specific.fc.wwnn = fcp->vport->wwnn; knob->xport_specific.fc.wwpn = fcp->vport->wwpn; } knob->xport_specific.fc.role = fcp->role; knob->xport_specific.fc.valid = KNOB_VALID_ADDRESS | KNOB_VALID_ROLE; ocs_set_ccb_status(ccb, CAM_REQ_CMP); xpt_done(ccb); break; } case XPT_SET_SIM_KNOB: { struct ccb_sim_knob *knob = &ccb->knob; bool role_changed = FALSE; ocs_fcport *fcp = FCPORT(ocs, bus); if (ocs->ocs_xport != OCS_XPORT_FC) { ocs_set_ccb_status(ccb, CAM_REQ_INVALID); xpt_done(ccb); break; } if (knob->xport_specific.fc.valid & KNOB_VALID_ADDRESS) { device_printf(ocs->dev, "%s: XPT_SET_SIM_KNOB wwnn=%llx wwpn=%llx\n", __func__, (unsigned long long)knob->xport_specific.fc.wwnn, (unsigned long long)knob->xport_specific.fc.wwpn); } if (knob->xport_specific.fc.valid & KNOB_VALID_ROLE) { switch (knob->xport_specific.fc.role) { case KNOB_ROLE_NONE: if (fcp->role != KNOB_ROLE_NONE) { role_changed = TRUE; } break; case KNOB_ROLE_TARGET: if (fcp->role != KNOB_ROLE_TARGET) { role_changed = TRUE; } break; case KNOB_ROLE_INITIATOR: if (fcp->role != KNOB_ROLE_INITIATOR) { role_changed = TRUE; } break; case KNOB_ROLE_BOTH: if (fcp->role != KNOB_ROLE_BOTH) { role_changed = TRUE; } break; default: device_printf(ocs->dev, "%s: XPT_SET_SIM_KNOB unsupported role: %d\n", __func__, knob->xport_specific.fc.role); } if (role_changed) { device_printf(ocs->dev, "BUS:%d XPT_SET_SIM_KNOB old_role: %d new_role: %d\n", bus, fcp->role, knob->xport_specific.fc.role); ocs_fcp_change_role(ocs, fcp, knob->xport_specific.fc.role); } } ocs_set_ccb_status(ccb, CAM_REQ_CMP); xpt_done(ccb); break; } case XPT_ABORT: { union ccb *accb = ccb->cab.abort_ccb; switch (accb->ccb_h.func_code) { case XPT_ACCEPT_TARGET_IO: ocs_abort_atio(ocs, ccb); break; case XPT_IMMEDIATE_NOTIFY: ocs_abort_inot(ocs, ccb); break; case XPT_SCSI_IO: rc = ocs_abort_initiator_io(ocs, accb); if (rc) { ccb->ccb_h.status = CAM_UA_ABORT; } else { ccb->ccb_h.status = CAM_REQ_CMP; } break; default: printf("abort of unknown func %#x\n", accb->ccb_h.func_code); ccb->ccb_h.status = CAM_REQ_INVALID; break; } break; } case XPT_RESET_BUS: if (ocs_xport_control(ocs->xport, OCS_XPORT_PORT_OFFLINE) == 0) { rc = ocs_xport_control(ocs->xport, OCS_XPORT_PORT_ONLINE); if (rc) { ocs_log_debug(ocs, "Failed to bring port online" " : %d\n", rc); } ocs_set_ccb_status(ccb, CAM_REQ_CMP); } else { ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); } xpt_done(ccb); break; case XPT_RESET_DEV: { ocs_node_t *node = NULL; ocs_io_t *io = NULL; int32_t rc = 0; ocs_fcport *fcp = FCPORT(ocs, bus); node = ocs_node_get_instance(ocs, fcp->tgt[ccb_h->target_id].node_id); if (node == NULL) { device_printf(ocs->dev, "%s: no device %d\n", __func__, ccb_h->target_id); ocs_set_ccb_status(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); break; } io = ocs_scsi_io_alloc(node, OCS_SCSI_IO_ROLE_ORIGINATOR); if (io == NULL) { device_printf(ocs->dev, "%s: unable to alloc IO\n", __func__); ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); xpt_done(ccb); break; } rc = ocs_scsi_send_tmf(node, io, NULL, ccb_h->target_lun, OCS_SCSI_TMF_LOGICAL_UNIT_RESET, NULL, 0, 0, /* sgl, sgl_count, length */ ocs_initiator_tmf_cb, NULL/*arg*/); if (rc) { ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); } else { ocs_set_ccb_status(ccb, CAM_REQ_CMP); } if (node->fcp2device) { ocs_reset_crn(node, ccb_h->target_lun); } xpt_done(ccb); break; } case XPT_EN_LUN: /* target support */ { ocs_tgt_resource_t *trsrc = NULL; uint32_t status = 0; ocs_fcport *fcp = FCPORT(ocs, bus); device_printf(ocs->dev, "XPT_EN_LUN %sable %d:%d\n", ccb->cel.enable ? "en" : "dis", ccb->ccb_h.target_id, (unsigned int)ccb->ccb_h.target_lun); trsrc = ocs_tgt_resource_get(fcp, &ccb->ccb_h, &status); if (trsrc) { trsrc->enabled = ccb->cel.enable; /* Abort all ATIO/INOT on LUN disable */ if (trsrc->enabled == FALSE) { ocs_tgt_resource_abort(ocs, trsrc); } else { STAILQ_INIT(&trsrc->atio); STAILQ_INIT(&trsrc->inot); } status = CAM_REQ_CMP; } ocs_set_ccb_status(ccb, status); xpt_done(ccb); break; } /* * The flow of target IOs in CAM is: * - CAM supplies a number of CCBs to the driver used for received * commands. * - when the driver receives a command, it copies the relevant * information to the CCB and returns it to the CAM using xpt_done() * - after the target server processes the request, it creates * a new CCB containing information on how to continue the IO and * passes that to the driver * - the driver processes the "continue IO" (a.k.a CTIO) CCB * - once the IO completes, the driver returns the CTIO to the CAM * using xpt_done() */ case XPT_ACCEPT_TARGET_IO: /* used to inform upper layer of received CDB (a.k.a. ATIO) */ case XPT_IMMEDIATE_NOTIFY: /* used to inform upper layer of other event (a.k.a. INOT) */ { ocs_tgt_resource_t *trsrc = NULL; uint32_t status = 0; ocs_fcport *fcp = FCPORT(ocs, bus); /*printf("XPT_%s %p\n", ccb_h->func_code == XPT_ACCEPT_TARGET_IO ? "ACCEPT_TARGET_IO" : "IMMEDIATE_NOTIFY", ccb);*/ trsrc = ocs_tgt_resource_get(fcp, &ccb->ccb_h, &status); if (trsrc == NULL) { ocs_set_ccb_status(ccb, CAM_DEV_NOT_THERE); xpt_done(ccb); break; } if (XPT_ACCEPT_TARGET_IO == ccb->ccb_h.func_code) { struct ccb_accept_tio *atio = NULL; atio = (struct ccb_accept_tio *)ccb; atio->init_id = 0x0badbeef; atio->tag_id = 0xdeadc0de; STAILQ_INSERT_TAIL(&trsrc->atio, &ccb->ccb_h, sim_links.stqe); } else { STAILQ_INSERT_TAIL(&trsrc->inot, &ccb->ccb_h, sim_links.stqe); } ccb->ccb_h.ccb_io_ptr = NULL; ccb->ccb_h.ccb_ocs_ptr = ocs; ocs_set_ccb_status(ccb, CAM_REQ_INPROG); /* * These actions give resources to the target driver. * If we didn't return here, this function would call * xpt_done(), signaling to the upper layers that an * IO or other event had arrived. */ break; } case XPT_NOTIFY_ACKNOWLEDGE: { ocs_io_t *io = NULL; ocs_io_t *abortio = NULL; /* Get the IO reference for this tag */ io = ocs_scsi_find_io(ocs, ccb->cna2.tag_id); if (io == NULL) { device_printf(ocs->dev, "%s: XPT_NOTIFY_ACKNOWLEDGE no IO with tag %#x\n", __func__, ccb->cna2.tag_id); ocs_set_ccb_status(ccb, CAM_REQ_CMP_ERR); xpt_done(ccb); break; } abortio = io->tgt_io.app; if (abortio) { abortio->tgt_io.flags &= ~OCS_CAM_IO_F_ABORT_NOTIFY; device_printf(ocs->dev, "%s: XPT_NOTIFY_ACK state=%d tag=%#x xid=%#x" " flags=%#x\n", __func__, abortio->tgt_io.state, abortio->tag, abortio->init_task_tag, abortio->tgt_io.flags); /* TMF response was sent in abort callback */ } else { ocs_scsi_send_tmf_resp(io, OCS_SCSI_TMF_FUNCTION_COMPLETE, NULL, ocs_target_tmf_cb, NULL); } ocs_set_ccb_status(ccb, CAM_REQ_CMP); xpt_done(ccb); break; } case XPT_CONT_TARGET_IO: /* continue target IO, sending data/response (a.k.a. CTIO) */ if (ocs_target_io(ocs, ccb)) { device_printf(ocs->dev, "XPT_CONT_TARGET_IO failed flags=%x tag=%#x\n", ccb->ccb_h.flags, ccb->csio.tag_id); xpt_done(ccb); } break; default: device_printf(ocs->dev, "unhandled func_code = %#x\n", ccb_h->func_code); ccb_h->status = CAM_REQ_INVALID; xpt_done(ccb); break; } } /** * @ingroup cam_api * @brief Process events * * @param sim pointer to the SCSI Interface Module * */ static void ocs_poll(struct cam_sim *sim) { printf("%s\n", __func__); } static int32_t ocs_initiator_tmf_cb(ocs_io_t *io, ocs_scsi_io_status_e scsi_status, ocs_scsi_cmd_resp_t *rsp, uint32_t flags, void *arg) { int32_t rc = 0; switch (scsi_status) { case OCS_SCSI_STATUS_GOOD: case OCS_SCSI_STATUS_NO_IO: break; case OCS_SCSI_STATUS_CHECK_RESPONSE: if (rsp->response_data_length == 0) { ocs_log_test(io->ocs, "check response without data?!?\n"); rc = -1; break; } if (rsp->response_data[3] != 0) { ocs_log_test(io->ocs, "TMF status %08x\n", be32toh(*((uint32_t *)rsp->response_data))); rc = -1; break; } break; default: ocs_log_test(io->ocs, "status=%#x\n", scsi_status); rc = -1; } ocs_scsi_io_free(io); return rc; } /** * @brief lookup target resource structure * * Arbitrarily support * - wildcard target ID + LU * - 0 target ID + non-wildcard LU * * @param ocs the driver instance's software context * @param ccb_h pointer to the CCB header * @param status returned status value * * @return pointer to the target resource, NULL if none available (e.g. if LU * is not enabled) */ static ocs_tgt_resource_t *ocs_tgt_resource_get(ocs_fcport *fcp, struct ccb_hdr *ccb_h, uint32_t *status) { target_id_t tid = ccb_h->target_id; lun_id_t lun = ccb_h->target_lun; if (CAM_TARGET_WILDCARD == tid) { if (CAM_LUN_WILDCARD != lun) { *status = CAM_LUN_INVALID; return NULL; } return &fcp->targ_rsrc_wildcard; } else { if (lun < OCS_MAX_LUN) { return &fcp->targ_rsrc[lun]; } else { *status = CAM_LUN_INVALID; return NULL; } } } static int32_t ocs_tgt_resource_abort(struct ocs_softc *ocs, ocs_tgt_resource_t *trsrc) { union ccb *ccb = NULL; do { ccb = (union ccb *)STAILQ_FIRST(&trsrc->atio); if (ccb) { STAILQ_REMOVE_HEAD(&trsrc->atio, sim_links.stqe); ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); } } while (ccb); do { ccb = (union ccb *)STAILQ_FIRST(&trsrc->inot); if (ccb) { STAILQ_REMOVE_HEAD(&trsrc->inot, sim_links.stqe); ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); } } while (ccb); return 0; } static void ocs_abort_atio(struct ocs_softc *ocs, union ccb *ccb) { ocs_io_t *aio = NULL; ocs_tgt_resource_t *trsrc = NULL; uint32_t status = CAM_REQ_INVALID; struct ccb_hdr *cur = NULL; union ccb *accb = ccb->cab.abort_ccb; int bus = cam_sim_bus(xpt_path_sim((ccb)->ccb_h.path)); ocs_fcport *fcp = FCPORT(ocs, bus); trsrc = ocs_tgt_resource_get(fcp, &accb->ccb_h, &status); if (trsrc != NULL) { STAILQ_FOREACH(cur, &trsrc->atio, sim_links.stqe) { if (cur != &accb->ccb_h) continue; STAILQ_REMOVE(&trsrc->atio, cur, ccb_hdr, sim_links.stqe); accb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(accb); ocs_set_ccb_status(ccb, CAM_REQ_CMP); return; } } /* if the ATIO has a valid IO pointer, CAM is telling * the driver that the ATIO (which represents the entire * exchange) has been aborted. */ aio = accb->ccb_h.ccb_io_ptr; if (aio == NULL) { ccb->ccb_h.status = CAM_UA_ABORT; return; } device_printf(ocs->dev, "%s: XPT_ABORT ATIO state=%d tag=%#x" " xid=%#x flags=%#x\n", __func__, aio->tgt_io.state, aio->tag, aio->init_task_tag, aio->tgt_io.flags); /* Expectations are: * - abort task was received * - already aborted IO in the DEVICE * - already received NOTIFY ACKNOWLEDGE */ if ((aio->tgt_io.flags & OCS_CAM_IO_F_ABORT_RECV) == 0) { device_printf(ocs->dev, "%s: abort not received or io completed \n", __func__); ocs_set_ccb_status(ccb, CAM_REQ_CMP); return; } aio->tgt_io.flags |= OCS_CAM_IO_F_ABORT_CAM; ocs_target_io_free(aio); ocs_set_ccb_status(ccb, CAM_REQ_CMP); return; } static void ocs_abort_inot(struct ocs_softc *ocs, union ccb *ccb) { ocs_tgt_resource_t *trsrc = NULL; uint32_t status = CAM_REQ_INVALID; struct ccb_hdr *cur = NULL; union ccb *accb = ccb->cab.abort_ccb; int bus = cam_sim_bus(xpt_path_sim((ccb)->ccb_h.path)); ocs_fcport *fcp = FCPORT(ocs, bus); trsrc = ocs_tgt_resource_get(fcp, &accb->ccb_h, &status); if (trsrc) { STAILQ_FOREACH(cur, &trsrc->inot, sim_links.stqe) { if (cur != &accb->ccb_h) continue; STAILQ_REMOVE(&trsrc->inot, cur, ccb_hdr, sim_links.stqe); accb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(accb); ocs_set_ccb_status(ccb, CAM_REQ_CMP); return; } } ocs_set_ccb_status(ccb, CAM_UA_ABORT); return; } static uint32_t ocs_abort_initiator_io(struct ocs_softc *ocs, union ccb *accb) { ocs_node_t *node = NULL; ocs_io_t *io = NULL; int32_t rc = 0; struct ccb_scsiio *csio = &accb->csio; ocs_fcport *fcp = FCPORT(ocs, cam_sim_bus(xpt_path_sim((accb)->ccb_h.path))); node = ocs_node_get_instance(ocs, fcp->tgt[accb->ccb_h.target_id].node_id); if (node == NULL) { device_printf(ocs->dev, "%s: no device %d\n", __func__, accb->ccb_h.target_id); ocs_set_ccb_status(accb, CAM_DEV_NOT_THERE); xpt_done(accb); return (-1); } io = ocs_scsi_io_alloc(node, OCS_SCSI_IO_ROLE_ORIGINATOR); if (io == NULL) { device_printf(ocs->dev, "%s: unable to alloc IO\n", __func__); ocs_set_ccb_status(accb, CAM_REQ_CMP_ERR); xpt_done(accb); return (-1); } rc = ocs_scsi_send_tmf(node, io, (ocs_io_t *)csio->ccb_h.ccb_io_ptr, accb->ccb_h.target_lun, OCS_SCSI_TMF_ABORT_TASK, NULL, 0, 0, ocs_initiator_tmf_cb, NULL/*arg*/); return rc; } void ocs_scsi_ini_ddump(ocs_textbuf_t *textbuf, ocs_scsi_ddump_type_e type, void *obj) { switch(type) { case OCS_SCSI_DDUMP_DEVICE: { //ocs_t *ocs = obj; break; } case OCS_SCSI_DDUMP_DOMAIN: { //ocs_domain_t *domain = obj; break; } case OCS_SCSI_DDUMP_SPORT: { //ocs_sport_t *sport = obj; break; } case OCS_SCSI_DDUMP_NODE: { //ocs_node_t *node = obj; break; } case OCS_SCSI_DDUMP_IO: { //ocs_io_t *io = obj; break; } default: { break; } } } void ocs_scsi_tgt_ddump(ocs_textbuf_t *textbuf, ocs_scsi_ddump_type_e type, void *obj) { switch(type) { case OCS_SCSI_DDUMP_DEVICE: { //ocs_t *ocs = obj; break; } case OCS_SCSI_DDUMP_DOMAIN: { //ocs_domain_t *domain = obj; break; } case OCS_SCSI_DDUMP_SPORT: { //ocs_sport_t *sport = obj; break; } case OCS_SCSI_DDUMP_NODE: { //ocs_node_t *node = obj; break; } case OCS_SCSI_DDUMP_IO: { ocs_io_t *io = obj; char *state_str = NULL; switch (io->tgt_io.state) { case OCS_CAM_IO_FREE: state_str = "FREE"; break; case OCS_CAM_IO_COMMAND: state_str = "COMMAND"; break; case OCS_CAM_IO_DATA: state_str = "DATA"; break; case OCS_CAM_IO_DATA_DONE: state_str = "DATA_DONE"; break; case OCS_CAM_IO_RESP: state_str = "RESP"; break; default: state_str = "xxx BAD xxx"; } ocs_ddump_value(textbuf, "cam_st", "%s", state_str); if (io->tgt_io.app) { ocs_ddump_value(textbuf, "cam_flags", "%#x", ((union ccb *)(io->tgt_io.app))->ccb_h.flags); ocs_ddump_value(textbuf, "cam_status", "%#x", ((union ccb *)(io->tgt_io.app))->ccb_h.status); } break; } default: { break; } } } int32_t ocs_scsi_get_block_vaddr(ocs_io_t *io, uint64_t blocknumber, ocs_scsi_vaddr_len_t addrlen[], uint32_t max_addrlen, void **dif_vaddr) { return -1; } uint32_t ocs_get_crn(ocs_node_t *node, uint8_t *crn, uint64_t lun) { uint32_t idx; struct ocs_lun_crn *lcrn = NULL; idx = lun % OCS_MAX_LUN; lcrn = node->ini_node.lun_crn[idx]; if (lcrn == NULL) { lcrn = ocs_malloc(node->ocs, sizeof(struct ocs_lun_crn), M_ZERO|M_NOWAIT); if (lcrn == NULL) { return (1); } lcrn->lun = lun; node->ini_node.lun_crn[idx] = lcrn; } if (lcrn->lun != lun) { return (1); } if (lcrn->crnseed == 0) lcrn->crnseed = 1; *crn = lcrn->crnseed++; return (0); } void ocs_del_crn(ocs_node_t *node) { uint32_t i; struct ocs_lun_crn *lcrn = NULL; for(i = 0; i < OCS_MAX_LUN; i++) { lcrn = node->ini_node.lun_crn[i]; if (lcrn) { ocs_free(node->ocs, lcrn, sizeof(*lcrn)); } } return; } void ocs_reset_crn(ocs_node_t *node, uint64_t lun) { uint32_t idx; struct ocs_lun_crn *lcrn = NULL; idx = lun % OCS_MAX_LUN; lcrn = node->ini_node.lun_crn[idx]; if (lcrn) lcrn->crnseed = 0; return; }