/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*
* Copyright 2014, 2015 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/stmf_ioctl.h>
#include "iscsit_isns.h"
#include "iscsit.h"
/*
* DDI entry points.
*/
static boolean_t iscsit_drv_busy(void);
extern struct mod_ops mod_miscops;
iscsit_drv_open, /* cb_open */
iscsit_drv_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
iscsit_drv_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_streamtab */
D_MP, /* cb_flag */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev, /* cb_awrite */
};
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
iscsit_drv_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
iscsit_drv_attach, /* devo_attach */
iscsit_drv_detach, /* devo_detach */
nodev, /* devo_reset */
&iscsit_cb_ops, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
ddi_quiesce_not_needed, /* quiesce */
};
"iSCSI Target",
};
&modldrv,
NULL,
};
static void iscsit_disable_svc(void);
static int
static void
static idm_pdu_t *
static void
static void
static void
iscsit_rxpdu_queue_monitor(void *arg);
static void
static void
static void
static void
static void
void
static void
int iscsit_cmd_window();
static int
void
static void
static void
iscsit_deferred(void *rx_pdu_void);
static idm_status_t
static idm_status_t
static idm_status_t
static idm_status_t
static idm_status_t
static stmf_data_buf_t *
static void
static void
static void
static void
static stmf_status_t
static iscsit_task_t *
static void
static iscsit_task_t *
static void
static idm_status_t
static void
static int
static void
static it_cfg_status_t
static idm_status_t
/*
* MC/S: Out-of-order commands are staged on a session-wide wait
* queue until a system-tunable threshold is reached. A separate
* thread is used to scan the staging queue on all the session,
* If a delayed PDU does not arrive within a timeout, the target
* will advance to the staged PDU that is next in sequence, skipping
* over the missing PDU(s) to go past a hole in the sequence.
*/
int
_init(void)
{
int rc;
MUTEX_DRIVER, NULL);
MUTEX_DRIVER, NULL);
return (rc);
}
return (rc);
}
int
{
}
int
_fini(void)
{
int rc;
if (rc == 0) {
}
return (rc);
}
/*
* DDI entry points.
*/
/* ARGSUSED */
static int
void **result)
{
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
return (DDI_SUCCESS);
default:
break;
}
return (DDI_FAILURE);
}
static int
{
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_get_instance(dip) != 0) {
/* we only allow instance 0 to attach */
return (DDI_FAILURE);
}
/* create the minor node */
DDI_PSEUDO, 0) != DDI_SUCCESS) {
"failed creating minor node");
return (DDI_FAILURE);
}
"failed to initialize");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
/*
* drv_detach is called in a context that owns the
* may end up in a deadlock with this thread.Hence, we use a
* separate lock just for the structures that drv_detach needs
* to access.
*/
if (iscsit_drv_busy()) {
return (EBUSY);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
return (0);
}
/* ARGSUSED */
static int
{
return (0);
}
static boolean_t
iscsit_drv_busy(void)
{
switch (iscsit_global.global_svc_state) {
case ISE_DISABLED:
case ISE_DETACHED:
return (B_FALSE);
default:
return (B_TRUE);
}
/* NOTREACHED */
}
/* ARGSUSED */
static int
int *retval)
{
int rc = 0;
return (EPERM);
}
/*
* Validate ioctl requests against global service state
*/
switch (iscsit_global.global_svc_state) {
case ISE_ENABLED:
if (cmd == ISCSIT_IOC_DISABLE_SVC) {
} else if (cmd == ISCSIT_IOC_ENABLE_SVC) {
/* Already enabled */
return (0);
} else {
}
break;
case ISE_DISABLED:
if (cmd == ISCSIT_IOC_ENABLE_SVC) {
} else if (cmd == ISCSIT_IOC_DISABLE_SVC) {
/* Already disabled */
return (0);
} else {
}
break;
case ISE_BUSY:
case ISE_ENABLING:
case ISE_DISABLING:
break;
case ISE_DETACHED:
default:
break;
}
if (rc != 0)
return (rc);
switch (cmd) {
case ISCSIT_IOC_SET_CONFIG:
/* Any errors must set state back to ISE_ENABLED */
case DDI_MODEL_ILP32:
sizeof (iscsit_ioc_set_config32_t), flag) != 0) {
goto cleanup;
}
break;
case DDI_MODEL_NONE:
sizeof (iscsit_ioc_set_config_t), flag) != 0) {
goto cleanup;
}
break;
default:
goto cleanup;
}
/* Check API version */
goto cleanup;
}
/* Config is in packed nvlist format so unpack it */
KM_SLEEP);
goto cleanup;
}
&cfg_nvlist, KM_SLEEP);
if (rc != 0) {
goto cleanup;
}
/* Translate nvlist */
if (rc != 0) {
goto cleanup;
}
/* Update config */
/* FALLTHROUGH */
if (cfg)
if (cfg_pnvlist)
/*
* Now that the reconfig is complete set our state back to
* enabled.
*/
break;
case ISCSIT_IOC_ENABLE_SVC: {
return (EFAULT);
}
return (EFAULT);
}
if (idmrc == IDM_STATUS_SUCCESS) {
} else {
}
break;
}
case ISCSIT_IOC_DISABLE_SVC:
break;
default:
}
return (rc);
}
static idm_status_t
{
int rc;
return (IDM_STATUS_SUCCESS);
}
/*
* iscsit_enable_svc
*
* registers all the configured targets and target portals with STMF
*/
static idm_status_t
{
/*
* Make sure that can tell if we have partially allocated
* in case we need to exit and tear down anything allocated.
*/
/* Setup remaining fields in iscsit_global_t */
iscsit_sess_avl_compare, sizeof (iscsit_sess_t),
iscsit_tgt_avl_compare, sizeof (iscsit_tgt_t),
sizeof (iscsit_tgt_t),
iscsit_tpg_avl_compare, sizeof (iscsit_tpg_t),
iscsit_ini_avl_compare, sizeof (iscsit_ini_t),
/*
* Setup STMF dbuf store. Our buffers are bound to a specific
* connection so we really can't let STMF cache buffers for us.
* Consequently we'll just allocate one global buffer store.
*/
if (dbuf_store == NULL) {
goto tear_down_and_return;
}
/* Status PDU cache */
/* Default TPG and portal */
goto tear_down_and_return;
}
/* initialize isns client */
(void) iscsit_isns_init(hostinfo);
/* Register port provider */
goto tear_down_and_return;
}
pp->pp_instance = 0;
goto tear_down_and_return;
}
/* Scan staged PDUs, meaningful in MC/S situations */
return (IDM_STATUS_SUCCESS);
}
if (did_iscsit_isns_init)
if (iscsit_global.global_default_tpg) {
}
if (iscsit_global.global_pp)
if (pp)
if (iscsit_status_pdu_cache) {
}
if (iscsit_global.global_dbuf_store) {
}
if (iscsit_global.global_tsih_pool) {
}
return (retval);
}
/*
* iscsit_disable_svc
*
* clean up all existing connections and deregister targets from STMF
*/
static void
iscsit_disable_svc(void)
{
/* tear down discovery sessions */
/*
* Passing NULL to iscsit_config_merge tells it to go to an empty
* config.
*/
(void) iscsit_config_merge(NULL);
/*
* Wait until there are no more global references
*/
/*
* Default TPG must be destroyed after global_refcnt is 0.
*/
}
void
{
/*
* To take out a global hold, we must either own the global
* state mutex or we must be running inside of an ioctl that
* has set the global state to ISE_BUSY, ISE_DISABLING, or
* ISE_ENABLING. We don't track the "owner" for these flags,
* so just checking if they are set is enough for now.
*/
}
void
{
}
void
{
}
/*
* IDM callbacks
*/
/*ARGSUSED*/
void
{
switch (IDM_PDU_OPCODE(rx_pdu)) {
case ISCSI_OP_SCSI_CMD:
ASSERT(0); /* Shouldn't happen */
break;
case ISCSI_OP_SNACK_CMD:
/*
* We'll need to handle this when we support ERL1/2. For
* now we treat it as a protocol error.
*/
break;
if (iscsit_check_cmdsn_and_queue(rx_pdu)) {
}
break;
case ISCSI_OP_NOOP_OUT:
case ISCSI_OP_LOGIN_CMD:
case ISCSI_OP_TEXT_CMD:
case ISCSI_OP_LOGOUT_CMD:
/*
* will be handled by iscsitd.
*/
break;
default:
/* Protocol error */
break;
}
}
/*ARGSUSED*/
void
{
}
/*
* iscsit_rx_scsi_rsp -- cause the connection to be closed if response rx'd
*
* A target sends an SCSI Response PDU, it should never receive one.
* This has been seen when running the Codemonicon suite of tests which
* does negative testing of the protocol. If such a condition occurs using
* a normal initiator it most likely means there's data corruption in the
* header and that's grounds for dropping the connection as well.
*/
void
{
}
void
{
switch (status) {
case IDM_STATUS_SUSPENDED:
break;
case IDM_STATUS_ABORTED:
/*
* We rely on the fact that STMF tracks outstanding
* buffer transfers and will free all of our buffers
* before freeing the task so we don't need to
*/
if (itask->it_stmf_abort) {
/*
* Task is no longer active
*/
/*
* STMF has already asked for this task to be aborted
*
* STMF specification is wrong... says to return
* STMF_ABORTED, the code actually looks for
* STMF_ABORT_SUCCESS.
*/
return;
} else {
/*
* Tell STMF to stop processing the task.
*/
STMF_ABORTED, NULL);
return;
}
/*NOTREACHED*/
default:
ASSERT(0);
}
}
/*ARGSUSED*/
{
/*
* IDM client notifications will never occur at interrupt level
* since they are generated from the connection state machine which
* running on taskq threads.
*
*/
switch (icn) {
case CN_CONNECT_ACCEPT:
break;
case CN_FFP_ENABLED:
break;
case CN_FFP_DISABLED:
/*
* Data indicates whether this was the result of an
* explicit logout request.
*/
break;
case CN_CONNECT_LOST:
break;
case CN_CONNECT_DESTROY:
break;
case CN_LOGIN_FAIL:
/*
* Force the login state machine to completion
*/
break;
default:
break;
}
return (rc);
}
/*
* iscsit_update_statsn is invoked for all the PDUs which have the StatSN
* field in the header. The StatSN is incremented if the IDM_PDU_ADVANCE_STATSN
* flag is set in the pdu flags field. The StatSN is connection-wide and is
* protected by the mutex ict_statsn_mutex. For Data-In PDUs, if the flag
* IDM_TASK_PHASECOLLAPSE_REQ is set, the status (phase-collapse) is also filled
*/
void
{
ict->ict_statsn++;
/*
* The last SCSI Data PDU passed for a command may also contain the
* status if the status indicates termination with no expections, i.e.
* no sense data or response involved. If the command completes with
* an error, then the response and sense data will be sent in a
* separate iSCSI Response PDU.
*/
}
/*
* Removing the task from the session task list
* just before the status is sent in the last
* Data PDU transfer
*/
}
}
void
{
/*
* We acquired iscsit_sess_t.ist_sn_mutex in iscsit_xfer_scsi_data
*/
/*
* On incoming data, the target transfer tag and Lun is only
* provided by the target if the A bit is set, Since the target
* does not currently support Error Recovery Level 1, the A
* bit is never set.
*/
/*
* IDM must set:
*
* data.flags and rtt.flags
* data.dlength
* data.datasn
* data.offset
* statsn, residual_count and cmd_status (for phase collapse)
* rtt.rttsn
* rtt.data_offset
* rtt.data_length
*/
}
void
{
/*
* IDM noticed the connection has been idle for too long so it's
* time to provoke some activity. Build and transmit an iSCSI
* nop-in PDU -- when the initiator responds it will be counted
* as "activity" and keep the connection alive.
*
* We don't actually care about the response here at the iscsit level
* so we will just throw it away without looking at it when it arrives.
*/
/*
* When the target sends a NOP-In as a Ping, the target transfer tag
* is set to a valid (not reserved) value and the initiator task tag
* is set to ISCSI_RSVD_TASK_TAG (0xffffffff). In this case the StatSN
* will always contain the next sequence number but the StatSN for the
* connection is not advanced after this PDU is sent.
*/
/*
* This works because we don't currently allocate ttt's anywhere else
* in iscsit so as long as we stay out of IDM's range we are safe.
* If we need to allocate ttt's for other PDU's in the future this will
* need to be improved.
*/
ict->ict_keepalive_ttt++;
}
static idm_status_t
{
/*
* We need to get a global hold here to ensure that the service
* doesn't get shutdown prior to establishing a session. This
* gets released in iscsit_conn_destroy().
*/
return (IDM_STATUS_FAIL);
}
/*
* Allocate an associated iscsit structure to represent this
* connection. We shouldn't really create a session until we
* get the first login PDU.
*/
/*
* Initialize login state machine
*/
/*
* Cleanup the ict after idm notifies us about this failure
*/
return (IDM_STATUS_FAIL);
}
return (IDM_STATUS_SUCCESS);
}
{
/*
* Note in new connection state that this connection is
* reinstating an existing connection.
*/
/*
* Now generate connection state machine event to existing connection
* so that it starts the cleanup process.
*/
return (result);
}
void
{
}
void
{
}
void
{
}
void
{
}
static idm_status_t
{
/* Generate login state machine event */
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
/* Generate session state machine event */
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
/* Generate session state machine event */
switch (disable_class) {
case FD_CONN_FAIL:
break;
case FD_CONN_LOGOUT:
break;
case FD_SESS_LOGOUT:
break;
default:
ASSERT(0);
}
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
int i;
/*
* scrub the staging queue for all PDUs on this connection
*/
i++) {
/* conn is lost, drop the pdu */
cbuf->cb_num_elems--;
}
}
}
/*
* Make sure there aren't any PDU's transitioning from the receive
* handler to the dispatch taskq.
*/
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
/* Generate session state machine event */
/*
* Session state machine will call iscsit_conn_destroy_done()
* when it has removed references to this connection.
*/
}
/*
* The session state machine does not need to post
* events to IDM any longer, so it is safe to set
* the idm connection reference to NULL
*/
/* Reap the login state machine */
/* Clean up any text command remnants */
return (IDM_STATUS_SUCCESS);
}
void
{
/*
* If the iscsi connection is active, then
* logout the IDM connection by sending a
* CE_LOGOUT_SESSION_SUCCESS, else, no action
* needs to be taken because the connection
* is already in the teardown process.
*/
}
}
/*
* STMF-related functions
*
* iSCSI to STMF mapping
*
* Session == ?
* Connection == bound to local port but not itself a local port
* Target
* Target portal (group?) == local port (really but we're not going to do this)
* iscsit needs to map connections to local ports (whatever we decide
* they are)
* Target == ?
*/
/*ARGSUSED*/
static stmf_data_buf_t *
{
/*
* If the requested size is larger than MaxBurstLength and the
* given pminsize is also larger than MaxBurstLength, then the
* allocation fails (dbuf = NULL) and pminsize is modified to
* this function with the corrected values for transfer.
*/
} else {
return (NULL);
}
/* Alloc buffer */
if (idm_buffer != NULL) {
sizeof (iscsit_buf_t), 0);
/* Fill in stmf_data_buf_t */
return (result);
}
/* Couldn't get the stmf_data_buf_t so free the buffer */
}
return (NULL);
}
/*ARGSUSED*/
static void
{
if (ibuf->ibuf_is_immed) {
/*
* The iscsit_buf_t structure itself will be freed with its
* associated task. Here we just need to free the PDU that
* held the immediate data.
*/
ibuf->ibuf_immed_data_pdu = 0;
} else {
}
}
/*ARGSUSED*/
{
int idm_rc;
/*
* If we are aborting then we can ignore this request
*/
if (iscsit_task->it_stmf_abort) {
return (STMF_SUCCESS);
}
/*
* If it's not immediate data then start the transfer
*/
/*
* The DB_SEND_STATUS_GOOD flag in the STMF data buffer allows
* the port provider to phase-collapse, i.e. send the status
* along with the final data PDU for the command. The port
* provider passes this request to the transport layer by
* setting a flag IDM_TASK_PHASECOLLAPSE_REQ in the task.
*/
/*
* IDM will call iscsit_build_hdr so lock now to serialize
* access to the SN values. We need to lock here to enforce
* lock ordering
*/
return (iscsit_idm_to_stmf(idm_rc));
/* Grab the SN lock (see comment above) */
return (iscsit_idm_to_stmf(idm_rc));
}
/* What are we supposed to do if there is no direction? */
return (STMF_INVALID_ARG);
}
static void
{
/*
* If the task has been aborted then we don't need to call STMF
*/
if (itask->it_stmf_abort) {
return;
}
/*
* For ISCSI over TCP (not iSER), the last SCSI Data PDU passed
* for a successful command contains the status as requested by
* by COMSTAR (via the DB_SEND_STATUS_GOOD flag). But the iSER
* transport does not support phase-collapse. So pretend we are
* COMSTAR and send the status in a separate PDU now.
*/
/*
* Mark task complete and notify COMSTAR
* that the status has been sent.
*/
status == IDM_STATUS_SUCCESS) {
/*
* The iscsi target port provider - for iSER, emulates the
* DB_SEND_STATUS_GOOD optimization if requested by STMF;
* it sends the status in a separate PDU after the data
* transfer. In this case the port provider should first
* call stmf_data_xfer_done() to mark the transfer complete
* and then send the status. Although STMF will free the
* buffer at the time the task is freed, even if the transfer
* is not marked complete, this behavior makes statistics
* gathering and task state tracking more difficult than it
* needs to be.
*/
!= STMF_SUCCESS) {
}
} else {
/* don't touch dbuf after stmf_data_xfer_done */
}
}
/*ARGSUSED*/
{
int resp_datalen;
/*
* If this task is aborted then we don't need to respond.
*/
if (itask->it_stmf_abort) {
return (STMF_SUCCESS);
}
/*
* If this is a task management status, handle it elsewhere.
*/
/*
* Don't wait for the PDU completion to tell STMF
* the task is done -- it doesn't really matter and
* it makes life complicated if STMF later asks us to
* abort the request and we don't know whether the
* status has been sent or not.
*/
return (STMF_SUCCESS);
}
/*
* Remove the task from the session task list
*/
/*
* Send status
*/
(task->task_sense_length == 0) &&
(task->task_resid == 0)) {
/* PDU callback releases task hold */
/*
* Fast path. Cached status PDU's are already
* initialized. We just need to fill in
* connection and task information. StatSN is
* incremented by 1 for every status sent a
* connection.
*/
/*
* ExpDataSN is the number of R2T and Data-In (read)
* PDUs the target has sent for the SCSI command.
*
* Since there is no support for bidirectional transfer
* yet, either idt_exp_datasn or idt_exp_rttsn, but not
* both is valid at any time
*/
return (STMF_SUCCESS);
} else {
return (STMF_FAILURE);
}
/* PDU callback releases task hold */
}
rsp->bi_residual_count = 0;
if (task->task_sense_length != 0) {
/*
* Add a byte to provide the sense length in
* the response
*/
sizeof (uint16_t),
}
scsi_task_t *, task);
return (STMF_SUCCESS);
}
}
/*ARGSUSED*/
static void
{
/*
* After releasing the hold the task may be freed at any time so
* don't touch it.
*/
if (!aborted) {
}
}
/*ARGSUSED*/
static void
{
/*
* After releasing the hold the task may be freed at any time so
* don't touch it.
*/
if (!aborted) {
}
}
void
{
/* We only call idm_task_start for regular tasks, not task management */
return;
} else {
}
}
/*ARGSUSED*/
{
/*
* If this is a task management request then there's really not much to
* do.
*/
return (STMF_ABORT_SUCCESS);
}
/*
* Regular task, start cleaning up
*/
if (iscsit_task->it_aborted) {
/*
* Task is no longer active
*/
/*
* STMF specification is wrong... says to return
* STMF_ABORTED, the code actually looks for
* STMF_ABORT_SUCCESS.
*/
return (STMF_ABORT_SUCCESS);
} else {
/*
* Call IDM to abort the task. Due to a variety of
* circumstances the task may already be in the process of
* aborting.
* We'll let IDM worry about rationalizing all that except
* for one particular instance. If the state of the task
* is TASK_COMPLETE, we need to indicate to the framework
* that we are in fact done. This typically happens with
* framework-initiated task management type requests
* (e.g. abort task).
*/
return (STMF_ABORT_SUCCESS);
} else {
return (STMF_SUCCESS);
}
}
/*NOTREACHED*/
}
/*ARGSUSED*/
void
{
(cmd == STMF_ACK_LPORT_ONLINE_COMPLETE) ||
(cmd == STMF_CMD_LPORT_OFFLINE) ||
switch (cmd) {
case STMF_CMD_LPORT_ONLINE:
break;
case STMF_CMD_LPORT_OFFLINE:
break;
break;
break;
default:
break;
}
}
static stmf_status_t
{
switch (idmrc) {
case IDM_STATUS_SUCCESS:
return (STMF_SUCCESS);
default:
return (STMF_FAILURE);
}
/*NOTREACHED*/
}
void
{
if (iscsit_check_cmdsn_and_queue(rx_pdu)) {
}
}
/*
* ISCSI protocol
*/
void
{
/* Finish processing request */
return;
}
/*
* Note CmdSN and ITT in task. IDM will have already validated this
* request against the connection state so we don't need to check
* that (the connection may have changed state in the meantime but
* we will catch that when we try to send a response)
*/
/*
* Check for extended CDB AHS
*/
if (iscsi_scsi->hlength > 0) {
iscsi_scsi->hlength) {
/* Mangled header info, drop it */
return;
}
}
/*
* Add task to session list. This function will also check to
* ensure that the task does not already exist.
*/
/*
* Task exists, free all resources and reject. Don't
* update expcmdsn in this case because RFC 3720 says
* "The CmdSN of the rejected command PDU (if it is a
* non-immediate command) MUST NOT be considered received
* by the target (i.e., a command sequence gap must be
* assumed for the CmdSN), even though the CmdSN of the
* rejected command PDU may be reliably ascertained. Upon
* receiving the Reject, the initiator MUST plug the CmdSN
* gap in order to continue to use the session. The gap
* may be plugged either by transmitting a command PDU
* with the same CmdSN, or by aborting the task (see section
* 6.9 on how an abort may plug a CmdSN gap)." (Section 6.3)
*/
return;
}
/* Update sequence numbers */
/*
* Allocate STMF task
*/
16 + addl_cdb_len, 0);
/*
* Either stmf really couldn't get memory for a task or,
* more likely, the LU is currently in reset. Either way
* we have no choice but to fail the request.
*/
return;
}
/*
* iSCSI and Comstar use the same values. Should we rely on this
* or translate them bit-wise?
*/
task->task_flags =
case ISCSI_ATTR_UNTAGGED:
break;
case ISCSI_ATTR_SIMPLE:
break;
case ISCSI_ATTR_ORDERED:
break;
case ISCSI_ATTR_HEAD_OF_QUEUE:
break;
case ISCSI_ATTR_ACA:
break;
default:
/* Protocol error but just take it, treat as untagged */
break;
}
task->task_additional_flags = 0;
task->task_priority = 0;
/*
* This "task_max_nbufs" doesn't map well to BIDI. We probably need
* parameter for each direction. "MaxOutstandingR2T" may very well
* be set to one which could prevent us from doing simultaneous
* transfers in each direction.
*/
/* Copy CDB */
if (addl_cdb_len > 0) {
}
scsi_task_t *, task);
/*
* Copy the transport header into the task handle from the PDU
* handle. The transport header describes this task's remote tagged
* buffer.
*/
if (rx_pdu->isp_transport_hdrlen != 0) {
}
/*
* Tell IDM about our new active task
*/
/*
* If we have any immediate data then setup the immediate buffer
* context that comes with the task
*/
if (rx_pdu->isp_datalen) {
/*
* For immediate data transfer, there is no callback from
* stmf to indicate that the initial burst of data is
* transferred successfully. In some cases, the task can
* get freed before execution returns from stmf_post_task.
* Although this xfer-start/done probe accurately tracks
* the size of the transfer, it does only provide a best
* effort on the timing of the transfer.
*/
} else {
}
}
void
{
/*
* If this isn't a login packet, we need a session. Otherwise
* this is a protocol error (perhaps one IDM should've caught?).
*/
return;
}
/*
* If the connection has been lost then ignore new PDU's
*/
return;
}
/*
* Grab a hold on the connection to prevent it from going away
* between now and when the taskq function is called.
*/
}
static void
{
/*
* NOP and Task Management Commands can be marked for immediate
* delivery. Commands marked as 'Immediate' are to be considered
* for execution as soon as they arrive on the target. So these
* should not be checked for sequence order and put in a queue.
* The CmdSN is not advanced for Immediate Commands.
*/
switch (IDM_PDU_OPCODE(rx_pdu)) {
case ISCSI_OP_NOOP_OUT:
if (iscsit_check_cmdsn_and_queue(rx_pdu)) {
}
break;
case ISCSI_OP_LOGIN_CMD:
return;
case ISCSI_OP_TEXT_CMD:
if (iscsit_check_cmdsn_and_queue(rx_pdu)) {
}
break;
case ISCSI_OP_LOGOUT_CMD:
if (iscsit_check_cmdsn_and_queue(rx_pdu)) {
}
break;
default:
/* Protocol error. IDM should have caught this */
ASSERT(0);
break;
}
/*
* Check if there are other PDUs in the session staging queue
* waiting to be posted to SCSI layer.
*/
}
static void
{
/*
* StatSN is incremented by 1 for every response sent on
* a connection except for responses sent as a result of
* a retry or SNACK
*/
if ((response == ISCSI_STATUS_CMD_COMPLETED) &&
(req->data_length != 0) &&
}
iscsit_conn_t *, ict,
}
void
{
/*
* The target must take note of the last-sent StatSN.
* The StatSN is to be incremented after sending a
* task management response. Digest recovery can only
* work if StatSN is incremented.
*/
idm_pdu_t *, tm_resp_pdu);
}
void
{
/*
* Setup response PDU (response field will get filled in later)
*/
if (tm_resp_pdu == NULL) {
/* Can't respond, just drop it */
return;
}
/*
* Figure out what we're being asked to do.
*/
iscsit_conn_t *, ict,
case ISCSI_TM_FUNC_ABORT_TASK:
/*
* STMF doesn't currently support the "abort task" task
* management command although it does support aborting
* an individual task. We'll get STMF to abort the task
* for us but handle the details of the task management
* command ourselves.
*
* Find the task associated with the referenced task tag.
*/
/*
* Task was not found. But the SCSI command could be
* on the rxpdu wait queue. If RefCmdSN is within
* the CmdSN window and less than CmdSN of the TM
* function, return "Function Complete". Otherwise,
* return "Task Does Not Exist".
*/
}
} else {
}
} else {
/*
* Tell STMF to abort the task. This will do no harm
* if the task is already complete.
*/
STMF_ABORTED, NULL);
/*
* Make sure the task hasn't already completed
*/
/*
* Task is complete, return "Task Does Not
* Exist"
*/
} else {
/*
* STMF is now aborting the task, return
* "Function Complete"
*/
}
}
return;
break;
case ISCSI_TM_FUNC_CLEAR_ACA:
break;
break;
break;
break;
break;
/*
* We do not currently support allegiance reassignment. When
* we start supporting ERL1+, we will need to.
*/
return;
default:
return;
}
return;
}
0, STMF_TASK_EXT_NONE);
/*
* If this happens, either the LU is in reset, couldn't
* get memory, or some other condition in which we simply
* can't complete this request. It would be nice to return
* an error code like "busy" but the closest we have is
* "rejected".
*/
return;
}
task->task_priority = 0;
}
static void
{
int resp_datalen;
/* Ignore the response from initiator */
return;
}
/* Allocate a PDU to respond */
if (resp_datalen > 0) {
}
/*
* When sending a NOP-In as a response to a NOP-Out from the initiator,
* the target must respond with the same initiator task tag that was
* provided in the NOP-Out request, the target transfer tag must be
* ISCSI_RSVD_TASK_TAG (0xffffffff) and StatSN will contain the next
* status sequence number. The StatSN for the connection is advanced
* after this PDU is sent.
*/
/* Any other field in resp to be set? */
}
static void
{
/*
* Submit PDU to login state machine. State machine will free the
* PDU.
*/
}
void
{
/* Allocate a PDU to respond */
/*
* The StatSN is to be sent to the initiator,
* it is not required to increment the number
* as the connection is terminating.
*/
/*
* Logout results in the immediate termination of all tasks except
* if the logout reason is ISCSI_LOGOUT_REASON_RECOVERY. The
* connection state machine will drive this task cleanup automatically
* so we don't need to handle that here.
*/
} else {
}
}
/*
* Calculate the number of outstanding commands we can process
*/
int
{
/*
* Instead of using a pre-defined constant for the command window,
* it should be made confiurable and dynamic. With MC/S, sequence
* numbers will be used up at a much faster rate than with SC/S.
*/
return (ISCSIT_MAX_WINDOW);
}
/*
* Set local registers based on incoming PDU
*/
void
{
/* no cmdsn increment for immediate PDUs */
return;
}
/* Ensure that the ExpCmdSN advances in an orderly manner */
}
/*
* Wrapper funtion, calls iscsi_calc_rspsn and idm_pdu_tx
*/
void
{
/*
* The command sequence numbers are session-wide and must stay
* consistent across the transfer, so protect the cmdsn with a
* mutex lock on the session. The status sequence number will
* be updated just before the transport layer transmits the PDU.
*/
/* Set ExpCmdSN and MaxCmdSN */
}
/*
* Internal functions
*/
void
{
/*
* Get a PDU to build the abort request.
*/
return;
}
/*
* A asynchronous message is sent by the target to request a logout.
* The StatSN for the connection is advanced after the PDU is sent
* to allow for initiator and target state synchronization.
*/
abt->isp_datalen = 0;
switch (event) {
break;
default:
ASSERT(0);
}
}
void
{
/*
* Get a PDU to build the abort request.
*/
if (reject_pdu == NULL) {
return;
}
/* StatSN is advanced after a Reject PDU */
}
static iscsit_task_t *
{
/*
* Possible items to pre-alloc if we cache iscsit_task_t's:
*
* Status PDU w/ sense buffer
* stmf_data_buf_t for immediate data
*/
sizeof (stmf_data_buf_t), KM_NOSLEEP);
itask->it_tm_task = 0;
return (itask);
} else {
sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t));
}
}
return (NULL);
}
static void
{
sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t));
}
static iscsit_task_t *
{
itask->it_tm_responded = 0;
}
return (itask);
}
static void
{
/*
* If we responded then the call to idm_pdu_complete will free the
* PDU. Otherwise we got aborted before the TM function could
* complete and we need to free the PDU explicitly.
*/
}
static idm_status_t
{
/*
* Sanity check the ITT and ensure that this task does not already
* exist. If not then add the task to the session task list.
*/
/* New task, add to AVL */
return (IDM_STATUS_SUCCESS);
}
return (IDM_STATUS_REJECT);
}
static void
{
}
}
/*
* iscsit status PDU cache
*/
/*ARGSUSED*/
static int
{
/* Setup status response */
return (0);
}
/*
* iscsit private data handler
*/
/*ARGSUSED*/
static void
{
return;
}
/* Translate nvlist */
return;
}
/* Check that no iSCSI ioctl is currently running */
switch (iscsit_global.global_svc_state) {
case ISE_ENABLED:
case ISE_DISABLED:
break;
case ISE_ENABLING:
/*
* It is OK for the iscsit_pp_cb to be called from inside of
* an iSCSI ioctl only if we are currently executing inside
* of stmf_register_port_provider.
*/
break;
default:
" is not ENABLED(0x%x) -- ignoring",
return;
}
/* Update config */
(void) iscsit_config_merge(cfg);
/* Restore old iSCSI driver global state */
}
static it_cfg_status_t
{
if (in_cfg) {
} else {
/* Make empty config */
}
/*
* Update targets, initiator contexts, target portal groups,
* and iSNS client
*/
!= 0) ||
return (status);
}
/* Update other global config parameters */
if (iscsit_global.global_props) {
}
if (in_cfg) {
}
return (ITCFG_SUCCESS);
}
/*
* iscsit_sna_lt[e]
*
* Compare serial numbers using serial number arithmetic as defined in
* RFC 1982.
*
* NOTE: This code is duplicated in the isns server. It ought to be common.
*/
static int
{
}
static int
{
}
static boolean_t
{
/*
* If cmdsn is less than ist_expcmdsn - iscsit_cmd_window() or
* greater than ist_expcmdsn, it's not in the window.
*/
}
return (rval);
}
/*
* iscsit_check_cmdsn_and_queue
*
* Independent of the order in which the iSCSI target receives non-immediate
* command PDU across the entire session and any multiple connections within
* the session, the target must deliver the commands to the SCSI layer in
* CmdSN order. So out-of-order non-immediate commands are queued up on a
* session-wide wait queue. Duplicate commands are ignored.
*
*/
static int
{
/* do not queue, handle it immediately */
return (ISCSIT_CMDSN_EQ_EXPCMDSN);
}
/*
* Out-of-order commands (cmdSN higher than ExpCmdSN)
* are staged on a fixed-size circular buffer until
* the missing command is delivered to the SCSI layer.
* Irrespective of the order of insertion into the
* staging queue, the commands are processed out of the
* queue in cmdSN order only.
*/
return (ISCSIT_CMDSN_GT_EXPCMDSN);
return (ISCSIT_CMDSN_LT_EXPCMDSN);
} else {
return (ISCSIT_CMDSN_EQ_EXPCMDSN);
}
}
/*
* iscsit_add_pdu_to_queue() adds PDUs into the array indexed by
* their cmdsn value. The length of the array is kept above the
* maximum window size. The window keeps the cmdsn within a range
* such that there are no collisons. e.g. the assumption is that
* the windowing checks make it impossible to receive PDUs that
* index into the same location in the array.
*/
static void
{
/*
* If the connection is being torn down, then
* don't add the PDU to the staging queue
*/
return;
}
/*
* In the normal case, assuming that the Initiator is not
* buggy and that we don't have packet duplication occuring,
* the entry in the array will be NULL. However, we may have
* received a duplicate PDU with cmdsn > expsn , and in that
* case we just ignore this PDU -- the previously received one
* remains queued for processing. We need to be careful not
* to leak this one however.
*/
} else {
cbuf->cb_num_elems++;
}
}
static idm_pdu_t *
{
cbuf->cb_num_elems--;
return (pdu);
}
return (NULL);
}
/*
* iscsit_process_pdu_in_queue() finds the next pdu in sequence
* and posts it to the SCSI layer
*/
static void
{
for (;;) {
if (cbuf->cb_num_elems == 0) {
break;
}
== NULL) {
break;
}
}
}
static void
{
/* Post the PDU to the SCSI layer */
switch (IDM_PDU_OPCODE(rx_pdu)) {
case ISCSI_OP_NOOP_OUT:
break;
case ISCSI_OP_TEXT_CMD:
break;
break;
case ISCSI_OP_SCSI_CMD:
/* cmdSN will be incremented after creating itask */
break;
case ISCSI_OP_LOGOUT_CMD:
break;
default:
/* No other PDUs should be placed on the queue */
ASSERT(0);
}
}
/* ARGSUSED */
void
{
return;
}
while (!iscsit_rxpdu_queue_monitor_thr_running) {
}
}
/* ARGSUSED */
void
{
return;
}
}
/*
* A separate thread is used to scan the staging queue on all the
* sessions, If a delayed PDU does not arrive within a timeout, the
* target will advance to the staged PDU that is next in sequence
* and exceeded the threshold wait time. It is up to the initiator
* to note that the target has not acknowledged a particular cmdsn
* and take appropriate action.
*/
/* ARGSUSED */
static void
{
while (iscsit_rxpdu_queue_monitor_thr_running) {
}
}
break;
}
}
thread_exit();
}
static void
{
/*
* Assume that all PDUs in the staging queue have a cmdsn >= expcmdsn.
* Starting with the expcmdsn, iterate over the staged PDUs to find
* the next PDU with a wait time greater than the threshold. If found
* advance the staged PDU to the SCSI layer, skipping over the missing
* PDU(s) to get past the hole in the command sequence. It is up to
* the initiator to note that the target has not acknowledged a cmdsn
* and take appropriate action.
*
* Since the PDU(s) arrive in any random order, it is possible that
* that the actual wait time for a particular PDU is much longer than
* the defined threshold. e.g. Consider a case where commands are sent
* over 4 different connections, and cmdsn = 1004 arrives first, then
* 1003, and 1002 and 1001 are lost due to a connection failure.
* So now 1003 is waiting for 1002 to be delivered, and although the
* wait time of 1004 > wait time of 1003, only 1003 will be considered
* by the monitor thread. 1004 will be automatically processed by
* iscsit_process_pdu_in_queue() once the scan is complete and the
* expcmdsn becomes current.
*/
if (cbuf->cb_num_elems == 0) {
return;
}
/*
* If the PDU wait time has not exceeded threshold
* stop scanning the staging queue until the timer
* fires again
*/
< (rxpdu_queue_threshold * NANOSEC)) {
return;
}
/*
* Remove the next PDU from the queue and post it
* to the SCSI layer, skipping over the missing
* PDU. Stop scanning the staging queue until
* the monitor timer fires again
*/
/* Deliver any subsequent PDUs immediately */
return;
}
/*
* Skipping over i PDUs, e.g. a case where commands 1001 and
* 1002 are lost in the network, skip over both and post 1003
* expcmdsn then becomes 1004 at the end of the scan.
*/
}
/*
* following the assumption, staged cmdsn >= expcmdsn, this statement
* is never reached.
*/
}