freebsd-skq/sys/dev/ocs_fc/ocs_cam.c
Alexander Motin 8836496815 Introduce support of SCSI Command Priority.
SAM-3 specification introduced concept of Task Priority, that was renamed
to Command Priority in SAM-4, and supported by all modern SCSI transports.
It provides 15 levels of relative priorities: 1 - highest, 15 - lowest and
0 - default.  SAT specification for SATA devices translates priorities 1-3
into NCQ high priority.

This change adds new "priority" field into empty spots of struct ccb_scsiio
and struct ccb_accept_tio of CAM and struct ctl_scsiio of CTL.  Respective
support is added into iscsi(4), isp(4), mpr(4), mps(4) and ocs_fc(4) drivers
for both initiator and where applicable target roles.  Minimal support was
added to CTL to receive the priority value from different frontends, pass it
between HA controllers and report in few places.

This patch does not add consumers of this functionality, so nothing should
really change yet, since the field is still set to 0 (default) on initiator
and not actively used on target.  Those are to be implemented separately.

I've confirmed priority working on WD Red SATA disks connected via mpr(4)
and properly transferred to CTL target via iscsi(4), isp(4) and ocs_fc(4).

While there, added missing tag_action support to ocs_fc(4) initiator role.

MFC after:	1 month
Relnotes:	yes
Sponsored by:	iXsystems, Inc.
2020-10-25 19:34:02 +00:00

2864 lines
71 KiB
C

/*-
* Copyright (c) 2017 Broadcom. All rights reserved.
* The term "Broadcom" refers to Broadcom Limited and/or its subsidiaries.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
/**
* @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 <b>ocs_scsi_tgt_domain_t tgt_domain</b>
* 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 <b>ocs_scsi_tgt_sport_t tgt_sport</b>
* 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 <b>must</b> 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 <b>ocs_scsi_ini_domain_t ini_domain</b>
* 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;
}
if (tgt->is_target) {
tgt->is_target = 0;
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;
}
csio->resid = rsp->residual;
if (rsp->residual > 0) {
uint32_t length = rsp->response_wire_length;
/* underflow */
if (csio->dxfer_len == (length + csio->resid)) {
ccb_status = CAM_REQ_CMP;
}
} else if (rsp->residual < 0) {
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;
ocs_set_ccb_status(ccb, ccb_status);
csio->ccb_h.status |= CAM_DEV_QFRZN;
xpt_freeze_devq(csio->ccb_h.path, 1);
} 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;
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[OCS_FC_MAX_SGL];
int32_t sgl_count = 0;
io->tgt_io.state = OCS_CAM_IO_DATA;
if (sendstatus)
io->tgt_io.sendresp = 1;
sgl_count = ocs_build_scsi_sgl(ocs, ccb, io, sgl, ARRAY_SIZE(sgl));
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[OCS_FC_MAX_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_count = ocs_build_scsi_sgl(ocs, ccb, io, sgl, ARRAY_SIZE(sgl));
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;
uint32_t count;
count = 0;
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);
count++;
}
} while (ccb);
count = 0;
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);
count++;
}
} 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;
}