changeset 11081:5c9c847827a4

6766563 iscsit should support phase collapse
author Priya Krishnan <Priya.Krishnan@Sun.COM>
date Tue, 17 Nov 2009 15:48:05 -0500
parents 368ac1f03f55
children 2209146bde7a
files usr/src/uts/common/io/comstar/port/iscsit/iscsit.c usr/src/uts/common/io/comstar/port/iscsit/iscsit.h usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c usr/src/uts/common/io/ib/clients/iser/iser_xfer.c usr/src/uts/common/io/idm/idm.c usr/src/uts/common/io/idm/idm_impl.c usr/src/uts/common/io/idm/idm_so.c usr/src/uts/common/sys/idm/idm.h usr/src/uts/common/sys/idm/idm_impl.h
diffstat 11 files changed, 199 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c	Tue Nov 17 15:48:05 2009 -0500
@@ -141,9 +141,6 @@
 iscsit_set_cmdsn(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
 
 static void
-iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp);
-
-static void
 iscsit_deferred_dispatch(idm_pdu_t *rx_pdu);
 
 static void
@@ -949,6 +946,48 @@
 	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
+iscsit_update_statsn(idm_task_t *idm_task, idm_pdu_t *pdu)
+{
+	iscsi_scsi_rsp_hdr_t *rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
+	iscsit_conn_t *ict = (iscsit_conn_t *)pdu->isp_ic->ic_handle;
+	iscsit_task_t *itask = NULL;
+	scsi_task_t *task = NULL;
+
+	mutex_enter(&ict->ict_statsn_mutex);
+	rsp->statsn = htonl(ict->ict_statsn);
+	if (pdu->isp_flags & IDM_PDU_ADVANCE_STATSN)
+		ict->ict_statsn++;
+	mutex_exit(&ict->ict_statsn_mutex);
+
+	/*
+	 * 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.
+	 */
+	if ((idm_task) && (idm_task->idt_flags & IDM_TASK_PHASECOLLAPSE_REQ)) {
+		itask = idm_task->idt_private;
+		task = itask->it_stmf_task;
+
+		rsp->cmd_status = task->task_scsi_status;
+		rsp->flags	|= ISCSI_FLAG_DATA_STATUS;
+		if (task->task_status_ctrl & TASK_SCTRL_OVER) {
+			rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW;
+		} else if (task->task_status_ctrl & TASK_SCTRL_UNDER) {
+			rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW;
+		}
+		rsp->residual_count = htonl(task->task_resid);
+	}
+}
 
 void
 iscsit_build_hdr(idm_task_t *idm_task, idm_pdu_t *pdu, uint8_t opcode)
@@ -971,6 +1010,7 @@
 	/* Maintain current statsn for RTT responses */
 	dh->statsn = (opcode == ISCSI_OP_RTT_RSP) ?
 	    htonl(itask->it_ict->ict_statsn) : 0;
+
 	dh->expcmdsn = htonl(itask->it_ict->ict_sess->ist_expcmdsn);
 	dh->maxcmdsn = htonl(itask->it_ict->ict_sess->ist_maxcmdsn);
 
@@ -981,7 +1021,7 @@
 	 * data.dlength
 	 * data.datasn
 	 * data.offset
-	 * residual_count and cmd_status (if we ever implement phase collapse)
+	 * statsn, residual_count and cmd_status (for phase collapse)
 	 * rtt.rttsn
 	 * rtt.data_offset
 	 * rtt.data_length
@@ -1006,13 +1046,20 @@
 	 */
 	nop_in_pdu = idm_pdu_alloc(sizeof (*nop_in), 0);
 	idm_pdu_init(nop_in_pdu, ic, NULL, NULL);
-
 	nop_in = (iscsi_nop_in_hdr_t *)nop_in_pdu->isp_hdr;
 	bzero(nop_in, sizeof (*nop_in));
 	nop_in->opcode = ISCSI_OP_NOOP_IN;
 	nop_in->flags = ISCSI_FLAG_FINAL;
 	nop_in->itt = ISCSI_RSVD_TASK_TAG;
 	/*
+	 * 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.
+	 */
+	nop_in_pdu->isp_flags |= IDM_PDU_SET_STATSN;
+	/*
 	 * 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
@@ -1058,6 +1105,7 @@
 	ict->ict_keepalive_ttt = IDM_TASKIDS_MAX; /* Avoid IDM TT range */
 	ic->ic_handle = ict;
 	mutex_init(&ict->ict_mutex, NULL, MUTEX_DRIVER, NULL);
+	mutex_init(&ict->ict_statsn_mutex, NULL, MUTEX_DRIVER, NULL);
 	idm_refcnt_init(&ict->ict_refcnt, ict);
 
 	/*
@@ -1317,6 +1365,7 @@
     uint32_t ioflags)
 {
 	iscsit_task_t *iscsit_task = task->task_port_private;
+	iscsit_sess_t *ict_sess = iscsit_task->it_ict->ict_sess;
 	iscsit_buf_t *ibuf = dbuf->db_port_private;
 	int idm_rc;
 
@@ -1333,26 +1382,34 @@
 	ASSERT(ibuf->ibuf_is_immed == B_FALSE);
 	if (dbuf->db_flags & DB_DIRECTION_TO_RPORT) {
 		/*
+		 * 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.
+		 */
+		if (dbuf->db_flags & DB_SEND_STATUS_GOOD)
+			iscsit_task->it_idm_task->idt_flags |=
+			    IDM_TASK_PHASECOLLAPSE_REQ;
+		/*
 		 * 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
 		 */
-		rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock,
-		    RW_READER);
+		rw_enter(&ict_sess->ist_sn_rwlock, RW_READER);
 		idm_rc = idm_buf_tx_to_ini(iscsit_task->it_idm_task,
 		    ibuf->ibuf_idm_buf, dbuf->db_relative_offset,
 		    dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf);
-		rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock);
+		rw_exit(&ict_sess->ist_sn_rwlock);
 
 		return (iscsit_idm_to_stmf(idm_rc));
 	} else if (dbuf->db_flags & DB_DIRECTION_FROM_RPORT) {
 		/* Grab the SN lock (see comment above) */
-		rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock,
-		    RW_READER);
+		rw_enter(&ict_sess->ist_sn_rwlock, RW_READER);
 		idm_rc = idm_buf_rx_from_ini(iscsit_task->it_idm_task,
 		    ibuf->ibuf_idm_buf, dbuf->db_relative_offset,
 		    dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf);
-		rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock);
+		rw_exit(&ict_sess->ist_sn_rwlock);
 
 		return (iscsit_idm_to_stmf(idm_rc));
 	}
@@ -1377,13 +1434,24 @@
 	}
 
 	/*
-	 * COMSTAR currently requires port providers to support
-	 * the DB_SEND_STATUS_GOOD flag even if phase collapse is
-	 * not supported.  So we will roll our own... pretend we are
-	 * COMSTAR and ask for a status PDU.
+	 * 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.
 	 */
-	if ((dbuf->db_flags & DB_SEND_STATUS_GOOD) &&
+	if (idb->idb_task_binding->idt_flags & IDM_TASK_PHASECOLLAPSE_SUCCESS) {
+		/*
+		 * Mark task complete, remove task from the session task
+		 * list and notify COMSTAR that the status has been sent.
+		 */
+		iscsit_task_done(itask);
+		itask->it_idm_task->idt_state = TASK_COMPLETE;
+		stmf_send_status_done(itask->it_stmf_task,
+		    iscsit_idm_to_stmf(status), STMF_IOF_LPORT_DONE);
+	} else if ((dbuf->db_flags & DB_SEND_STATUS_GOOD) &&
 	    status == IDM_STATUS_SUCCESS) {
+
 		/*
 		 * If iscsit_send_scsi_status succeeds then the TX PDU
 		 * callback will call stmf_send_status_done and set
@@ -1465,11 +1533,14 @@
 		/*
 		 * Fast path.  Cached status PDU's are already
 		 * initialized.  We just need to fill in
-		 * connection and task information.
+		 * connection and task information. StatSN is
+		 * incremented by 1 for every status sent a
+		 * connection.
 		 */
 		pdu = kmem_cache_alloc(iscsit_status_pdu_cache, KM_SLEEP);
 		pdu->isp_ic = itask->it_ict->ict_ic;
 		pdu->isp_private = itask;
+		pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 
 		rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
 		rsp->itt = itask->it_itt;
@@ -1492,6 +1563,7 @@
 		pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), resp_datalen);
 		idm_pdu_init(pdu, itask->it_ict->ict_ic, itask,
 		    iscsit_send_status_done);
+		pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 
 		rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
 		bzero(rsp, sizeof (*rsp));
@@ -2009,6 +2081,13 @@
 
 	rsp_pdu = idm_pdu_alloc(sizeof (iscsi_scsi_rsp_hdr_t), 0);
 	idm_pdu_init(rsp_pdu, ic, NULL, NULL);
+	/*
+	 * StatSN is incremented by 1 for every response sent on
+	 * a connection except for responses sent as a result of
+	 * a retry or SNACK
+	 */
+	rsp_pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
+
 	resp = (iscsi_scsi_rsp_hdr_t *)rsp_pdu->isp_hdr;
 
 	resp->opcode = ISCSI_OP_SCSI_RSP;
@@ -2038,6 +2117,13 @@
 {
 	iscsi_scsi_task_mgt_rsp_hdr_t	*tm_resp;
 
+	/*
+	 * 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.
+	 */
+	tm_resp_pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 	tm_resp = (iscsi_scsi_task_mgt_rsp_hdr_t *)tm_resp_pdu->isp_hdr;
 	tm_resp->response = tm_status;
 
@@ -2261,6 +2347,14 @@
 		bcopy(rx_pdu->isp_data, resp->isp_data, resp_datalen);
 	}
 
+	/*
+	 * 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.
+	 */
 	in = (iscsi_nop_in_hdr_t *)resp->isp_hdr;
 	bzero(in, sizeof (*in));
 	in->opcode = ISCSI_OP_NOOP_IN;
@@ -2269,7 +2363,7 @@
 	in->itt		= out->itt;
 	in->ttt		= ISCSI_RSVD_TASK_TAG;
 	hton24(in->dlength, resp_datalen);
-
+	resp->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 	/* Any other field in resp to be set? */
 	iscsit_pdu_tx(resp);
 	idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
@@ -2297,7 +2391,12 @@
 	/* Allocate a PDU to respond */
 	resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
 	idm_pdu_init(resp, ict->ict_ic, NULL, NULL);
-
+	/*
+	 * The StatSN is to be sent to the initiator,
+	 * it is not required to increment the number
+	 * as the connection is terminating.
+	 */
+	resp->isp_flags |= IDM_PDU_SET_STATSN;
 	/*
 	 * Logout results in the immediate termination of all tasks except
 	 * if the logout reason is ISCSI_LOGOUT_REASON_RECOVERY.  The
@@ -2350,64 +2449,26 @@
 }
 
 /*
- * Update local StatSN and set SNs in response
- */
-static void
-iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp)
-{
-	iscsit_sess_t *ist;
-	iscsi_scsi_rsp_hdr_t *rsp;
-
-	/* Get iSCSI session handle */
-	ist = ict->ict_sess;
-
-	rsp = (iscsi_scsi_rsp_hdr_t *)resp->isp_hdr;
-
-	/* Update StatSN */
-	rsp->statsn = htonl(ict->ict_statsn);
-	switch (IDM_PDU_OPCODE(resp)) {
-	case ISCSI_OP_RTT_RSP:
-		/* Do nothing */
-		break;
-	case ISCSI_OP_NOOP_IN:
-		/*
-		 * Refer to section 10.19.1, RFC3720.
-		 * Advance only if target is responding initiator
-		 */
-		if (((iscsi_nop_in_hdr_t *)rsp)->ttt == ISCSI_RSVD_TASK_TAG)
-			ict->ict_statsn++;
-		break;
-	case ISCSI_OP_SCSI_DATA_RSP:
-		if (rsp->flags & ISCSI_FLAG_DATA_STATUS)
-			ict->ict_statsn++;
-		else
-			rsp->statsn = 0;
-		break;
-	default:
-		ict->ict_statsn++;
-		break;
-	}
-
-	/* Set ExpCmdSN and MaxCmdSN */
-	rsp->maxcmdsn = htonl(ist->ist_maxcmdsn);
-	rsp->expcmdsn = htonl(ist->ist_expcmdsn);
-}
-
-/*
  * Wrapper funtion, calls iscsi_calc_rspsn and idm_pdu_tx
  */
 void
 iscsit_pdu_tx(idm_pdu_t *pdu)
 {
 	iscsit_conn_t *ict = pdu->isp_ic->ic_handle;
+	iscsi_scsi_rsp_hdr_t *rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
+	iscsit_sess_t *ist = ict->ict_sess;
 
 	/*
-	 * Protect ict->ict_statsn, ist->ist_maxcmdsn, and ist->ist_expcmdsn
-	 * (which are used by iscsit_calc_rspsn) with the session mutex
-	 * (ist->ist_sn_mutex).
+	 * The command sequence numbers are session-wide and must stay
+	 * consistent across the transfer, so protect the cmdsn with a
+	 * reader lock on the session. The status sequence number will
+	 * be updated just before the transport layer transmits the PDU.
 	 */
-	rw_enter(&ict->ict_sess->ist_sn_rwlock, RW_WRITER);
-	iscsit_calc_rspsn(ict, pdu);
+
+	rw_enter(&ict->ict_sess->ist_sn_rwlock, RW_READER);
+	/* Set ExpCmdSN and MaxCmdSN */
+	rsp->maxcmdsn = htonl(ist->ist_maxcmdsn);
+	rsp->expcmdsn = htonl(ist->ist_expcmdsn);
 	idm_pdu_tx(pdu);
 	rw_exit(&ict->ict_sess->ist_sn_rwlock);
 }
@@ -2431,8 +2492,14 @@
 		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.
+	 */
 	idm_pdu_init(abt, ict->ict_ic, NULL, NULL);
 	abt->isp_datalen = 0;
+	abt->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 
 	async_abt = (iscsi_async_evt_hdr_t *)abt->isp_hdr;
 	bzero(async_abt, sizeof (*async_abt));
@@ -2475,7 +2542,8 @@
 		return;
 	}
 	idm_pdu_init(reject_pdu, ict->ict_ic, NULL, NULL);
-
+	/* StatSN is advanced after a Reject PDU */
+	reject_pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 	reject_pdu->isp_datalen = rejected_pdu->isp_hdrlen;
 	bcopy(rejected_pdu->isp_hdr, reject_pdu->isp_data,
 	    rejected_pdu->isp_hdrlen);
--- a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h	Tue Nov 17 15:48:05 2009 -0500
@@ -491,6 +491,7 @@
 	iscsit_op_params_t	ict_op;
 	uint16_t		ict_cid;
 	uint32_t		ict_statsn;
+	kmutex_t		ict_statsn_mutex;
 	uint32_t		ict_keepalive_ttt;
 	struct iscsit_conn_s	*ict_reinstate_conn;
 	uint32_t		ict_reinstating:1,
@@ -622,6 +623,7 @@
 idm_task_cb_t		iscsit_task_aborted;
 idm_client_notify_cb_t	iscsit_client_notify;
 idm_build_hdr_cb_t	iscsit_build_hdr;
+idm_update_statsn_cb_t	iscsit_update_statsn;
 idm_keepalive_cb_t	iscsit_keepalive;
 
 /*
--- a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c	Tue Nov 17 15:48:05 2009 -0500
@@ -890,8 +890,8 @@
 	hton24(lh_resp->dlength, pdu->isp_datalen);
 
 	/*
-	 * If this is going to be the last PDU of a login response
-	 * that moves us to FFP then generate the ILE_LOGIN_FFP event.
+	 * If the login is successful, this login response will contain
+	 * the next StatSN and advance the StatSN for the connection.
 	 */
 	if (lh_resp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
 		ASSERT(ict->ict_sess != NULL);
@@ -911,6 +911,7 @@
 		}
 
 		iscsit_conn_hold(ict);
+		pdu->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 		iscsit_pdu_tx(pdu);
 	} else {
 		/*
--- a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c	Tue Nov 17 15:48:05 2009 -0500
@@ -397,6 +397,8 @@
 	 */
 	resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), len);
 	idm_pdu_init(resp, ict->ict_ic, ict, iscsit_text_resp_complete_cb);
+	/* Advance the StatSN for each Text Response sent */
+	resp->isp_flags |= IDM_PDU_SET_STATSN | IDM_PDU_ADVANCE_STATSN;
 	base = ict->ict_text_rsp_buf + ict->ict_text_rsp_off;
 	bcopy(base, resp->isp_data, len);
 	/*
--- a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c	Tue Nov 17 15:48:05 2009 -0500
@@ -1948,6 +1948,8 @@
 			sr.sr_conn_ops.icb_client_notify =
 			    &iscsit_client_notify;
 			sr.sr_conn_ops.icb_build_hdr = &iscsit_build_hdr;
+			sr.sr_conn_ops.icb_update_statsn =
+			    &iscsit_update_statsn;
 			sr.sr_conn_ops.icb_keepalive = &iscsit_keepalive;
 
 			if (idm_tgt_svc_create(&sr, &svc) !=
--- a/usr/src/uts/common/io/ib/clients/iser/iser_xfer.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/ib/clients/iser/iser_xfer.c	Tue Nov 17 15:48:05 2009 -0500
@@ -246,10 +246,22 @@
 		return (ISER_STATUS_FAIL);
 	}
 
+	ic = chan->ic_conn->ic_idmc;
+
+	/* Pull the BHS out of the PDU handle */
+	bhs = (iscsi_data_hdr_t *)pdu->isp_hdr;
+
 	/*
 	 * All SCSI command PDU (except SCSI Read and SCSI Write) and the SCSI
 	 * Response PDU are sent to the remote end using the SendSE Message.
 	 *
+	 * The StatSN may need to be sent (and possibly advanced) at this time
+	 * for some PDUs, identified by the IDM_PDU_SET_STATSN flag.
+	 */
+	if (pdu->isp_flags & IDM_PDU_SET_STATSN) {
+		(ic->ic_conn_ops.icb_update_statsn)(NULL, pdu);
+	}
+	/*
 	 * Setup a Send Message for carrying the iSCSI control-type PDU
 	 * preceeded by an iSER header.
 	 */
@@ -268,11 +280,6 @@
 		return (ISER_STATUS_FAIL);
 	}
 
-	/* Pull the BHS out of the PDU handle */
-	bhs = (iscsi_data_hdr_t *)pdu->isp_hdr;
-
-	ic = chan->ic_conn->ic_idmc;
-
 	hdr = (iser_ctrl_hdr_t *)(uintptr_t)msg->msg_ds.ds_va;
 
 	/*
--- a/usr/src/uts/common/io/idm/idm.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/idm/idm.c	Tue Nov 17 15:48:05 2009 -0500
@@ -1285,7 +1285,7 @@
 	idt->idt_private 	= NULL;
 	idt->idt_exp_datasn	= 0;
 	idt->idt_exp_rttsn	= 0;
-
+	idt->idt_flags		= 0;
 	return (idt);
 }
 
--- a/usr/src/uts/common/io/idm/idm_impl.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/idm/idm_impl.c	Tue Nov 17 15:48:05 2009 -0500
@@ -1026,7 +1026,7 @@
 	 * transport that requires a different size, we'll revisit this.
 	 */
 	idt->idt_transport_hdr = (void *)(idt + 1); /* pointer arithmetic */
-
+	idt->idt_flags = 0;
 	return (0);
 }
 
--- a/usr/src/uts/common/io/idm/idm_so.c	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/io/idm/idm_so.c	Tue Nov 17 15:48:05 2009 -0500
@@ -2298,8 +2298,10 @@
  * DataSN starts with 0 for the first data PDU of an input command and advances
  * by 1 for each subsequent data PDU. Each sequence will have its own F bit,
  * which is set to 1 for the last data PDU of a sequence.
+ * If the initiator supports phase collapse, the status bit must be set along
+ * with the F bit to indicate that the status is shipped together with the last
+ * Data-In PDU.
  *
- * Scope for Prototype build:
  * The data PDUs within a sequence will be sent in order with the buffer offset
  * in increasing order. i.e. initiator and target must have negotiated the
  * "DataPDUInOrder" to "Yes". The order between sequences is not enforced.
@@ -2391,6 +2393,7 @@
 
 	pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP);
 	pdu->isp_ic = idt->idt_ic;
+	pdu->isp_flags = 0;	/* initialize isp_flags */
 	bzero(pdu->isp_hdr, sizeof (iscsi_rtt_hdr_t));
 
 	/* iSCSI layer fills the TTT, ITT, StatSN, ExpCmdSN, MaxCmdSN */
@@ -2588,6 +2591,7 @@
 		/* Data PDU headers will always be sizeof (iscsi_hdr_t) */
 		pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP);
 		pdu->isp_ic = ic;
+		pdu->isp_flags = 0;	/* initialize isp_flags */
 
 		/*
 		 * We've already built a build a header template
@@ -2609,10 +2613,27 @@
 		hton24(bhs->dlength, chunk);
 		bhs->offset = htonl(idb->idb_bufoffset + data_offset);
 
+		/* setup data */
+		pdu->isp_data	=  (uint8_t *)idb->idb_buf + data_offset;
+		pdu->isp_datalen = (uint_t)chunk;
+
 		if (chunk == remainder) {
 			bhs->flags = ISCSI_FLAG_FINAL; /* F bit set to 1 */
+			/* Piggyback the status with the last data PDU */
+			if (idt->idt_flags & IDM_TASK_PHASECOLLAPSE_REQ) {
+				pdu->isp_flags |= IDM_PDU_SET_STATSN |
+				    IDM_PDU_ADVANCE_STATSN;
+				(*idt->idt_ic->ic_conn_ops.icb_update_statsn)
+				    (idt, pdu);
+				idt->idt_flags |=
+				    IDM_TASK_PHASECOLLAPSE_SUCCESS;
+
+			}
 		}
 
+		remainder	-= chunk;
+		data_offset	+= chunk;
+
 		/* Instrument the data-send DTrace probe. */
 		if (IDM_PDU_OPCODE(pdu) == ISCSI_OP_SCSI_DATA_RSP) {
 			DTRACE_ISCSI_2(data__send,
@@ -2620,11 +2641,6 @@
 			    iscsi_data_rsp_hdr_t *,
 			    (iscsi_data_rsp_hdr_t *)pdu->isp_hdr);
 		}
-		/* setup data */
-		pdu->isp_data	=  (uint8_t *)idb->idb_buf + data_offset;
-		pdu->isp_datalen = (uint_t)chunk;
-		remainder	-= chunk;
-		data_offset	+= chunk;
 
 		/*
 		 * Now that we're done working with idt_exp_datasn,
@@ -2767,13 +2783,18 @@
 		mutex_exit(&so_conn->ic_tx_mutex);
 
 		switch (object->idm_tx_obj_magic) {
-		case IDM_PDU_MAGIC:
+		case IDM_PDU_MAGIC: {
+			idm_pdu_t *pdu = (idm_pdu_t *)object;
 			DTRACE_PROBE2(soconn__tx__pdu, idm_conn_t *, ic,
 			    idm_pdu_t *, (idm_pdu_t *)object);
 
+			if (pdu->isp_flags & IDM_PDU_SET_STATSN) {
+				/* No IDM task */
+				(ic->ic_conn_ops.icb_update_statsn)(NULL, pdu);
+			}
 			status = idm_i_so_tx((idm_pdu_t *)object);
 			break;
-
+		}
 		case IDM_BUF_MAGIC: {
 			idm_buf_t *idb = (idm_buf_t *)object;
 			idm_task_t *idt = idb->idb_task_binding;
--- a/usr/src/uts/common/sys/idm/idm.h	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/sys/idm/idm.h	Tue Nov 17 15:48:05 2009 -0500
@@ -149,6 +149,9 @@
 typedef void (idm_build_hdr_cb_t)(
     struct idm_task_s *task, struct idm_pdu_s *pdu, uint8_t opcode);
 
+typedef void (idm_update_statsn_cb_t)(
+    struct idm_task_s *task, struct idm_pdu_s *pdu);
+
 typedef void (idm_keepalive_cb_t)(struct idm_conn_s *ic);
 
 typedef union idm_sockaddr {
@@ -169,6 +172,7 @@
 	idm_task_cb_t		*icb_task_aborted;
 	idm_client_notify_cb_t	*icb_client_notify;
 	idm_build_hdr_cb_t	*icb_build_hdr;
+	idm_update_statsn_cb_t	*icb_update_statsn; /* advance statsn */
 	idm_keepalive_cb_t	*icb_keepalive;
 } idm_conn_ops_t;
 
--- a/usr/src/uts/common/sys/idm/idm_impl.h	Tue Nov 17 11:03:15 2009 -0800
+++ b/usr/src/uts/common/sys/idm/idm_impl.h	Tue Nov 17 15:48:05 2009 -0500
@@ -260,6 +260,7 @@
 	 */
 	int			idt_transport_hdrlen;
 	void			*idt_transport_hdr;
+	uint32_t		idt_flags;	/* phase collapse */
 } idm_task_t;
 
 int idm_task_constructor(void *task_void, void *arg, int flags);
@@ -268,6 +269,9 @@
 #define	IDM_TASKIDS_MAX		16384
 #define	IDM_BUF_MAGIC		0x49425546	/* "IBUF" */
 
+#define	IDM_TASK_PHASECOLLAPSE_REQ	0x00000001 /* request phase collapse */
+#define	IDM_TASK_PHASECOLLAPSE_SUCCESS	0x00000002 /* phase collapse success */
+
 /* Protect with task mutex */
 typedef struct idm_buf_s {
 	uint32_t	idb_magic;	/* "IBUF" */
@@ -392,6 +396,8 @@
 #define	IDM_PDU_ADDL_HDR	0x00000002
 #define	IDM_PDU_ADDL_DATA	0x00000004
 #define	IDM_PDU_LOGIN_TX	0x00000008
+#define	IDM_PDU_SET_STATSN	0x00000010
+#define	IDM_PDU_ADVANCE_STATSN	0x00000020
 
 #define	OSD_EXT_CDB_AHSLEN	(200 - 15)
 #define	BIDI_AHS_LENGTH		5